sozsoft-platform/ui/src/components/visualDesigner/catalog.ts

942 lines
30 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { CUSTOM_COMPONENTS, HTML_ELEMENTS } from '@/components/codeLayout/data/componentDefinitions'
import generatedComponentProps from './generated/componentProps.json'
import {
DESIGNER_DATA_COMPONENT_NAMES,
SQL_DATA_SOURCE_TYPE,
getDesignerCollectionProperty,
isDesignerOptionComponent,
isDesignerTabularComponent,
type DesignerComponentDefinition,
type DesignerNodeKind,
} from './types'
export { DESIGNER_DATA_COMPONENT_NAMES }
interface GeneratedProperty {
name: string
tsType: string
type: 'string' | 'boolean' | 'number' | 'function' | 'object' | 'array' | 'select'
required: boolean
options?: string[]
defaultValue?: unknown
}
interface GeneratedComponentMetadata {
interfaceName: string
sourceFile: string
properties: GeneratedProperty[]
}
const generatedMetadata = generatedComponentProps as Record<string, GeneratedComponentMetadata>
const CONTAINER_NAMES = new Set(['div', 'Card', 'FormContainer', 'Table', 'Tabs', 'Timeline'])
const getToolboxGroup = (name: string): DesignerComponentDefinition['toolboxGroup'] =>
name === 'Table' ? 'layout' : DESIGNER_DATA_COMPONENT_NAMES.has(name) ? 'data' : 'ui'
/** Components intentionally kept out of the toolbox. */
const EXCLUDED_COMPONENTS = new Set([
'Alert',
'Breadcrumb',
'Dialog',
'Drawer',
'FormContainer',
'FormItem',
'InputGroup',
'MenuItem',
'Segment',
'Steps',
])
interface PropertyOverride {
/** Seed value that makes a freshly dropped component render something useful. */
value?: unknown
/** Corrects an editor type the metadata generator could not infer. */
type?: GeneratedProperty['type']
options?: string[]
}
const PROPERTY_OVERRIDES: Record<string, Record<string, PropertyOverride>> = {
// `Exclude<Shape, 'none'> | 'square'` collapses to a single option.
Avatar: { shape: { options: ['round', 'circle', 'square'], value: 'circle' } },
Chips: { value: { value: ['Deneme', 'Deneme2'] } },
// ImageViewer renders nothing without images, and its clickable gallery only
// appears when `children` is set — so a dropped component would be invisible.
ImageViewer: {
images: {
value: [
{ src: 'https://picsum.photos/id/1015/1200/800', alt: 'Görsel 1', caption: 'Görsel 1' },
{ src: 'https://picsum.photos/id/1025/1200/800', alt: 'Görsel 2', caption: 'Görsel 2' },
],
},
children: { value: 'Galeri' },
},
Marquee: { gradient: { value: false }, speed: { value: 100 } },
}
const HTML_UI_DUPLICATES = new Set(['input', 'button', 'textarea', 'select', 'checkbox'])
const UI_PROPERTY_ALIASES: Record<string, Record<string, string>> = {
Select: {
clearable: 'isClearable',
disabled: 'isDisabled',
multiple: 'isMulti',
onSearch: 'onInputChange',
searchable: 'isSearchable',
},
}
export const DESIGNER_PRIMARY_EVENTS: Record<string, string[]> = {
AutoComplete: ['onInputChange', 'onSelect'],
Button: ['onClick'],
Checkbox: ['onChange'],
Dropdown: ['onSelect'],
Input: ['onChange'],
Menu: ['onSelect'],
Pagination: ['onChange'],
'Radio.Group': ['onChange'],
Select: ['onChange'],
Tabs: ['onChange'],
}
const DESIGNER_EVENT_SNIPPETS: Record<string, Record<string, string>> = {
Button: {
onClick: `// Butona tıklandığında çalışır.
// Sayfadaki diğer komponentlere ref adlarıyla erişebilirsiniz:
// refs.input1.setValue('Merhaba')
// refs.input1.setEnabled(false)
// refs.card1.setVisible(false)
console.log('Button clicked', event)`,
},
Checkbox: {
onChange: `// Checkbox bileşeninin yeni checked değeri.
const checked = event.checked
console.log('Checked:', checked)`,
},
Input: {
onChange: `// Input alanının güncel metin değeri.
const value = event.target.value
console.log('Value:', value)`,
},
Menu: {
onSelect: `// Seçilen menü öğesinin value değeridir.
console.log('Menu value:', event)`,
},
Dropdown: {
onSelect: `// Seçilen dropdown öğesinin value değeridir.
console.log('Dropdown value:', event)`,
},
Pagination: {
onChange: `// Seçilen sayfa numarasıdır.
console.log('Page:', event)`,
},
'Radio.Group': {
onChange: `// Seçilen radio öğesinin value değeridir.
console.log('Radio value:', event)`,
},
Select: {
onChange: `// Seçilen option nesnesi; temizlendiğinde null olur.
const selectedValue = event?.value ?? null
const selectedLabel = event?.label ?? ''
console.log({ selectedValue, selectedLabel })`,
},
}
/**
* The script API, as copy/paste ready examples. This is the single source of
* truth behind the designer's "Script API" panel, so anything an event script
* can do is discoverable without reading the code generator.
*
* `{{ref}}`, `{{sql}}` and `{{url}}` are replaced by the designer with real
* names from the page being edited, so a copied example runs as it is.
*/
export interface DesignerScriptRecipe {
group: string
title: string
description: string
code: string
}
export const DESIGNER_SCRIPT_RECIPE_GROUPS = [
'Komponent erişimi',
'API çağrıları',
'SqlDataSource',
'Event ve sayfa',
] as const
export const DESIGNER_SCRIPT_RECIPES: DesignerScriptRecipe[] = [
{
group: 'Komponent erişimi',
title: 'Değer atama ve okuma',
description: 'Her komponent kendi ref adıyla adreslenir.',
code: `refs.{{ref}}.setValue('Merhaba')
const value = refs.{{ref}}.getValue()
console.log(value)`,
},
{
group: 'Komponent erişimi',
title: 'Aktif / pasif yapma',
description: 'Kaydet butonunu koşula göre kilitlemek için.',
code: `refs.{{ref}}.setEnabled(false)
console.log(refs.{{ref}}.isEnabled())`,
},
{
group: 'Komponent erişimi',
title: 'Gösterme / gizleme',
description: 'Gizlenen komponent sayfada hiç render edilmez.',
code: `refs.{{ref}}.setVisible(false)
// veya: refs.{{ref}}.hide() / refs.{{ref}}.show()`,
},
{
group: 'Komponent erişimi',
title: 'Salt okunur yapma',
description: 'Değeri görünür kalsın ama düzenlenemesin.',
code: `refs.{{ref}}.setReadOnly(true)`,
},
{
group: 'Komponent erişimi',
title: 'Etiket / metin değiştirme',
description: 'Buton ve etiket yazısını çalışma anında değiştirir.',
code: `refs.{{ref}}.setText('Kaydediliyor…')`,
},
{
group: 'Komponent erişimi',
title: 'Serbest property atama',
description: 'Property panelindeki her property script ile de yazılabilir.',
code: `refs.{{ref}}.setProps({ placeholder: 'Ara…', className: 'border-red-500' })
refs.{{ref}}.setProp('size', 'sm')`,
},
{
group: 'Komponent erişimi',
title: 'Script ile yapılanları geri alma',
description: 'Komponenti tasarımdaki haline döndürür.',
code: `refs.{{ref}}.reset()`,
},
{
group: 'API çağrıları',
title: 'GET — kayıt okuma',
description: 'Sonuç doğrudan response bodysidir.',
code: `const rows = await api.get('{{url}}')
console.log(rows)`,
},
{
group: 'API çağrıları',
title: 'GET — parametre ile',
description: 'İkinci argüman query string olarak gönderilir.',
code: `const rows = await api.get('{{url}}', { maxResultCount: 10 })
console.log(rows)`,
},
{
group: 'API çağrıları',
title: 'POST — yeni kayıt',
description: 'Gövde ikinci argüman olarak verilir.',
code: `const created = await api.post('{{url}}', {
name: refs.{{ref}}.getValue(),
})
console.log(created)`,
},
{
group: 'API çağrıları',
title: 'PUT ve DELETE',
description: 'Güncelleme ve silme aynı imzayı kullanır.',
code: `await api.put('{{url}}/5', { name: 'Yeni ad' })
await api.delete('{{url}}/5')`,
},
{
group: 'API çağrıları',
title: 'Hata yönetimi ve bildirim',
description: 'notify tipleri: success, info, warning, danger.',
code: `try {
await api.post('{{url}}', { name: refs.{{ref}}.getValue() })
notify('Kayıt tamamlandı', 'success')
} catch (error) {
notify(api.errorMessage(error), 'danger')
}`,
},
{
group: 'API çağrıları',
title: 'Çağrı boyunca butonu kilitleme',
description: 'Çift tıklamayı ve mükerrer isteği önler.',
code: `refs.{{ref}}.setEnabled(false)
refs.{{ref}}.setText('Gönderiliyor…')
try {
await api.get('{{url}}')
notify('Tamamlandı', 'success')
} catch (error) {
notify(api.errorMessage(error), 'danger')
} finally {
refs.{{ref}}.setEnabled(true)
refs.{{ref}}.setText('Gönder')
}`,
},
{
group: 'API çağrıları',
title: 'Serbest istek',
description: 'Header, timeout gibi ayarlar gerektiğinde.',
code: `const result = await api.request({
url: '{{url}}',
method: 'GET',
params: { skipCount: 0 },
})
console.log(result)`,
},
{
group: 'SqlDataSource',
title: 'Kaydet / sil / yenile',
description: 'Toolbarı gizleyip kendi butonlarınızı kullanabilirsiniz.',
code: `await refs.{{sql}}.save()
// await refs.{{sql}}.remove()
// await refs.{{sql}}.reload()
// refs.{{sql}}.newRecord()`,
},
{
group: 'SqlDataSource',
title: 'Aktif kaydı okuma ve yazma',
description: 'Alan adı Select sonucundaki sütun adıdır.',
code: `const record = refs.{{sql}}.getRecord()
console.log(record)
refs.{{sql}}.setField('name', 'Yeni ad')`,
},
{
group: 'SqlDataSource',
title: 'Kayıtlar arasında gezinme',
description: 'Select birden fazla satır döndürdüğünde.',
code: `const rows = refs.{{sql}}.getRows()
refs.{{sql}}.goToRow(rows.length - 1)`,
},
{
group: 'SqlDataSource',
title: 'Kaydetmeden önce doğrulama',
description: 'Zorunlu alan boşsa işlemi durdurur.',
code: `const record = refs.{{sql}}.getRecord()
if (!record.name) {
notify('Ad alanı zorunludur', 'warning')
return
}
await refs.{{sql}}.save()`,
},
{
group: 'SqlDataSource',
title: 'Durum bilgisi',
description: 'Yeni kayıt / düzenleme, meşgul ve hata durumu.',
code: `console.log(refs.{{sql}}.getMode(), refs.{{sql}}.isBusy(), refs.{{sql}}.getError())`,
},
{
group: 'Event ve sayfa',
title: 'Event verisi',
description: 'event içeriği komponent tipine göre değişir.',
code: `// Input: event.target.value · Checkbox: event.checked
// Select/Dropdown/Menu: seçilen değer · Button: tıklama eventi
console.log(event)`,
},
{
group: 'Event ve sayfa',
title: 'Komponentin kendi bilgileri',
description: 'Scriptin bağlı olduğu komponent ve tasarım propertyleri.',
code: `console.log(component.type, component.ref)
console.log(props)`,
},
{
group: 'Event ve sayfa',
title: 'Sayfa yönlendirme',
description: 'Kayıt sonrası liste sayfasına dönmek için.',
code: `window.location.href = '/app/orders'`,
},
{
group: 'Event ve sayfa',
title: 'URL parametresi okuma',
description: 'Detay sayfasında anahtar değerini almak için.',
code: `const id = new URLSearchParams(window.location.search).get('id')
console.log(id)`,
},
]
export const getDesignerEventSnippet = (componentName: string, eventName: string) =>
DESIGNER_EVENT_SNIPPETS[componentName]?.[eventName] ||
`// ${componentName}.${eventName}(event)\nconsole.log(event)`
const COLLECTION_ITEMS_PROPERTY = {
name: 'items',
type: 'array' as const,
value: [],
category: 'properties' as const,
description: 'Statik veya endpoint kaynaklı veri koleksiyonu',
tsType: 'unknown[]',
options: undefined,
required: false,
}
const GRID_COMPACT_PROPERTY = {
name: 'compact',
type: 'boolean' as const,
value: false,
category: 'properties' as const,
description: 'Satır ve hücre boşluklarını azaltır',
tsType: 'boolean',
options: undefined,
required: false,
}
const GRID_TABLE_BEHAVIOR_PROPERTIES = [
{
name: 'hoverable',
type: 'boolean' as const,
value: true,
category: 'properties' as const,
description: 'Satırların üzerine gelindiğinde vurgu rengi gösterir',
tsType: 'boolean',
options: undefined,
required: false,
},
{
name: 'overflow',
type: 'boolean' as const,
value: true,
category: 'properties' as const,
description: 'Geniş tablolarda yatay kaydırmayı etkinleştirir',
tsType: 'boolean',
options: undefined,
required: false,
},
{
name: 'borderlessRow',
type: 'boolean' as const,
value: false,
category: 'properties' as const,
description: 'Veri satırları arasındaki kenarlıkları kaldırır',
tsType: 'boolean',
options: undefined,
required: false,
},
]
const TABLE_DIMENSION_PROPERTIES = [
{
name: 'rowCount',
type: 'number' as const,
value: 2,
category: 'properties' as const,
description: 'Table içindeki statik satır sayısı',
tsType: 'number',
options: undefined,
required: false,
},
{
name: 'columnCount',
type: 'number' as const,
value: 3,
category: 'properties' as const,
description: 'Her satırdaki statik hücre sayısı',
tsType: 'number',
options: undefined,
required: false,
},
]
const SELECT_OPTIONS_PROPERTY = {
name: 'options',
type: 'array' as const,
value: [
{ label: 'Seçenek 1', value: '1' },
{ label: 'Seçenek 2', value: '2' },
],
category: 'properties' as const,
description: 'Select items; statik olarak veya endpoint koleksiyonundan beslenebilir',
tsType: 'Array<{ label: string; value: unknown }>',
options: undefined,
required: false,
}
const MENU_ITEMS_PROPERTY = {
...SELECT_OPTIONS_PROPERTY,
name: 'items',
description: 'Menu items; statik olarak veya endpoint koleksiyonundan beslenebilir',
}
const TABS_ITEMS_PROPERTY = {
...SELECT_OPTIONS_PROPERTY,
name: 'items',
value: [
{ label: 'Sekme 1', value: '1', content: 'Sekme 1 içeriği' },
{ label: 'Sekme 2', value: '2', content: 'Sekme 2 içeriği' },
],
description:
'Sekmeler; statik olarak veya endpoint koleksiyonundan beslenebilir. content alanı sekme içeriğini yazar.',
tsType: 'Array<{ label: string; value: unknown; content?: string }>',
}
/** Seed definition for a data component's collection property. */
const getCollectionPropertyDefinition = (name: string, collectionProperty: string) => {
if (isDesignerTabularComponent(name)) return COLLECTION_ITEMS_PROPERTY
if (name === 'Tabs') return TABS_ITEMS_PROPERTY
return collectionProperty === 'items' ? MENU_ITEMS_PROPERTY : SELECT_OPTIONS_PROPERTY
}
const normalizeDefinition = (
definition: (typeof CUSTOM_COMPONENTS)[number],
kind: DesignerNodeKind,
toolboxGroup: DesignerComponentDefinition['toolboxGroup'],
): DesignerComponentDefinition => {
const sourceProperties =
definition.name === 'Grid'
? definition.properties.filter((property) => property.name !== 'children')
: definition.properties
let properties =
definition.name === 'Grid' && !sourceProperties.some((property) => property.name === 'items')
? [COLLECTION_ITEMS_PROPERTY, ...sourceProperties]
: sourceProperties
if (definition.name === 'Grid' && !properties.some((property) => property.name === 'compact')) {
properties = [GRID_COMPACT_PROPERTY, ...properties]
}
if (definition.name === 'Grid') {
properties = [
...GRID_TABLE_BEHAVIOR_PROPERTIES.filter(
(candidate) => !properties.some((property) => property.name === candidate.name),
),
...properties,
]
}
if (definition.name === 'Table') {
properties = [
...TABLE_DIMENSION_PROPERTIES.filter(
(candidate) => !properties.some((property) => property.name === candidate.name),
),
...properties.filter((property) => !['columns', 'data', 'items'].includes(property.name)),
]
}
return {
...definition,
properties,
kind,
toolboxGroup,
acceptsChildren: CONTAINER_NAMES.has(definition.name),
}
}
const fallbackValue = (type: GeneratedProperty['type']) => {
if (type === 'boolean') return false
if (type === 'number') return 0
if (type === 'array') return []
if (type === 'object') return {}
return ''
}
const inferUiCategory = (name: string) => {
if (
new Set([
'AutoComplete',
'Checkbox',
'Chips',
'ColorPicker',
'DatePicker',
'Input',
'Knob',
'Radio',
'Rate',
'Select',
'Slider',
'Switcher',
'TimeInput',
'Upload',
]).has(name)
)
return 'form'
if (new Set(['Alert', 'Notification', 'Progress', 'Skeleton', 'Spinner']).has(name))
return 'feedback'
if (new Set(['Breadcrumb', 'Dropdown', 'Menu', 'Pagination', 'Steps', 'Tabs']).has(name))
return 'navigation'
return 'display'
}
const generatedPropertiesToDesigner = (
generated: GeneratedComponentMetadata,
existingProperties = new Map<string, (typeof CUSTOM_COMPONENTS)[number]['properties'][number]>(),
componentName = '',
) =>
generated.properties.map((property) => {
const existing = existingProperties.get(property.name)
const override = PROPERTY_OVERRIDES[componentName]?.[property.name]
const type = override?.type || property.type
return {
name: property.name,
type,
value:
override && 'value' in override
? override.value
: property.defaultValue !== undefined
? property.defaultValue
: existing?.value !== undefined
? existing.value
: fallbackValue(type),
options: override?.type
? override.options
: override?.options || property.options || existing?.options,
description: property.tsType,
category:
property.type === 'function'
? ('events' as const)
: existing?.category ||
(property.name === 'className' || property.name === 'style'
? ('styling' as const)
: ('properties' as const)),
tsType: property.tsType,
required: property.required,
}
})
const normalizeUiDefinition = (
definition: (typeof CUSTOM_COMPONENTS)[number],
): DesignerComponentDefinition => {
const generated = generatedMetadata[definition.name]
if (!generated) {
return normalizeDefinition(definition, 'ui', getToolboxGroup(definition.name))
}
const propertyAliases = UI_PROPERTY_ALIASES[definition.name] || {}
const curatedProperties = definition.properties
.filter(
(property) =>
(definition.name !== 'Select' || property.name !== 'items') &&
(definition.name !== 'Menu' || property.name !== 'variant') &&
(definition.name !== 'Table' || !['columns', 'data'].includes(property.name)),
)
.map((property) => ({
...property,
name: propertyAliases[property.name] || property.name,
}))
const existingProperties = new Map(curatedProperties.map((property) => [property.name, property]))
const generatedProperties = generatedPropertiesToDesigner(
generated,
existingProperties,
definition.name,
)
// Every data component must expose its collection property so the data panel
// can drive it from static values or an endpoint.
const collectionProperty = getDesignerCollectionProperty(definition.name)
if (
isDesignerOptionComponent(definition.name) &&
!generatedProperties.some((property) => property.name === collectionProperty)
) {
generatedProperties.unshift(
getCollectionPropertyDefinition(definition.name, collectionProperty),
)
}
if (
isDesignerTabularComponent(definition.name) &&
!generatedProperties.some((property) => property.name === collectionProperty)
) {
generatedProperties.unshift(COLLECTION_ITEMS_PROPERTY)
}
if (definition.name === 'Table') {
generatedProperties.unshift(
...TABLE_DIMENSION_PROPERTIES.filter(
(candidate) => !generatedProperties.some((property) => property.name === candidate.name),
),
)
}
const generatedPropertyNames = new Set(generatedProperties.map((property) => property.name))
const properties = [
...generatedProperties,
// Keep explicitly curated inherited React props such as Input.value and placeholder.
...curatedProperties.filter((property) => !generatedPropertyNames.has(property.name)),
].filter(
(property) =>
!(definition.name === 'Checkbox' && property.name === 'defaultChecked') &&
!(definition.name === 'Menu' && property.name === 'variant'),
)
const primaryEvents = DESIGNER_PRIMARY_EVENTS[definition.name]
return {
...definition,
kind: 'ui',
toolboxGroup: getToolboxGroup(definition.name),
acceptsChildren: CONTAINER_NAMES.has(definition.name),
sourceFile: generated.sourceFile,
interfaceName: generated.interfaceName,
properties: primaryEvents
? properties.filter(
(property) =>
(property.category !== 'events' && property.type !== 'function') ||
primaryEvents.includes(property.name),
)
: properties,
}
}
const platformDefinition = (
name: string,
sourceName: string,
description: string,
height = '520px',
): DesignerComponentDefinition => ({
name,
sourceName,
icon: 'PanelsTopLeft',
category: 'platform',
kind: 'platform',
toolboxGroup: 'platform',
description,
properties: [
{ name: 'listFormCode', type: 'string', value: '', category: 'properties', required: true },
{ name: 'height', type: 'string', value: height, category: 'styling' },
],
hooks: [],
})
export const PLATFORM_COMPONENTS: DesignerComponentDefinition[] = [
platformDefinition(
'ListView',
'List',
'Tüm etkin platform görünümlerini /admin/list/:listFormCode ile aynı şekilde yönetir',
),
platformDefinition('DataGridView', 'Grid', 'Sozsoft dinamik veri grid görünümü'),
platformDefinition('TreeView', 'Tree', 'Sozsoft dinamik ağaç görünümü'),
platformDefinition('GanttView', 'GanttView', 'Sozsoft dinamik Gantt görünümü'),
platformDefinition('TodoBoard', 'TodoBoard', 'Sozsoft dinamik iş panosu'),
platformDefinition('CardView', 'CardView', 'Sozsoft dinamik kart görünümü'),
platformDefinition('SchedulerView', 'SchedulerView', 'Sozsoft dinamik planlayıcı görünümü'),
platformDefinition('PivotView', 'Pivot', 'Sozsoft dinamik pivot görünümü'),
platformDefinition('ChartView', 'Chart', 'Sozsoft dinamik grafik görünümü', '420px'),
]
/**
* ASP.NET's SqlDataSource + FormView in one component: it owns the four CRUD
* endpoints and acts as a container, so every component dropped inside it can
* bind to a column of the Select result and write back through Save/Delete.
* The endpoints are configured from the inspector's Data tab, not here.
*/
export const SQL_DATA_SOURCE_DEFINITION: DesignerComponentDefinition = {
name: SQL_DATA_SOURCE_TYPE,
icon: 'Database',
category: 'data',
kind: 'layout',
toolboxGroup: 'data',
description:
'GET/POST/PUT/DELETE endpointlerini tek kayıt üzerinde yöneten veri kabı; içine bırakılan komponentler sütunlara bağlanır',
acceptsChildren: true,
properties: [
{
name: 'selectEndpoint',
type: 'string',
value: '',
category: 'properties',
description: 'Kaydı okuyan GET endpointinin data source idsi (Data sekmesinden seçilir)',
},
{
name: 'insertEndpoint',
type: 'string',
value: '',
category: 'properties',
description: 'Yeni kayıt için POST endpointi',
},
{
name: 'updateEndpoint',
type: 'string',
value: '',
category: 'properties',
description: 'Mevcut kaydı güncelleyen PUT endpointi',
},
{
name: 'deleteEndpoint',
type: 'string',
value: '',
category: 'properties',
description: 'Kaydı silen DELETE endpointi',
},
{
name: 'keyFieldName',
type: 'string',
value: 'id',
category: 'properties',
description: 'Insert/Update ayrımı ve endpoint parametreleri bu sütundan doldurulur',
},
{
name: 'collectionPath',
type: 'string',
value: '',
category: 'properties',
description: 'GET cevabı içinde satırların bulunduğu path (boşsa cevabın kendisi kullanılır)',
},
{
name: 'keySource',
type: 'select',
value: 'query',
options: ['query', 'route'],
category: 'properties',
description: 'Key değerinin sayfa URLsinden okunma şekli',
},
{
name: 'keyParamName',
type: 'string',
value: '',
category: 'properties',
description: 'URLden okunacak parametre adı (boşsa key field kullanılır)',
},
{
name: 'previewKeyValue',
type: 'string',
value: '',
category: 'properties',
description:
'Sadece tasarım ekranı: GetById endpointinin sütunlarını okuyabilmek için örnek key değeri',
},
{
name: 'autoLoad',
type: 'boolean',
value: true,
category: 'properties',
description: 'Sayfa açıldığında Select endpointini otomatik çağırır',
},
{
name: 'showToolbar',
type: 'boolean',
value: true,
category: 'properties',
description: 'New / Save / Delete / Reload butonlarını gösterir',
},
{
name: 'gap',
type: 'number',
value: 16,
category: 'styling',
},
{ name: 'className', type: 'string', value: '', category: 'styling' },
],
hooks: [],
}
export const DESIGNER_EXTRAS: DesignerComponentDefinition[] = [
{
name: 'PageContainer',
icon: 'PanelTop',
category: 'layout',
kind: 'layout',
toolboxGroup: 'layout',
description: 'Intranet içeriği için ortalanmış sayfa alanı',
acceptsChildren: true,
properties: [
{ name: 'maxWidth', type: 'string', value: '1280px', category: 'styling' },
{ name: 'padding', type: 'number', value: 24, category: 'styling' },
{ name: 'gap', type: 'number', value: 16, category: 'styling' },
{ name: 'className', type: 'string', value: '', category: 'styling' },
],
hooks: [],
},
{
name: 'TwoColumns',
icon: 'Columns2',
category: 'layout',
kind: 'layout',
toolboxGroup: 'layout',
description: 'Sayfayı iki eşit kolona böler',
acceptsChildren: true,
properties: [
{ name: 'gap', type: 'number', value: 16, category: 'styling' },
{ name: 'className', type: 'string', value: '', category: 'styling' },
],
hooks: [],
},
{
name: 'SidebarContent',
icon: 'PanelLeft',
category: 'layout',
kind: 'layout',
toolboxGroup: 'layout',
description: 'Dar sidebar ve geniş içerik alanı',
acceptsChildren: true,
properties: [
{ name: 'sidebarWidth', type: 'string', value: '280px', category: 'styling' },
{ name: 'gap', type: 'number', value: 16, category: 'styling' },
{ name: 'className', type: 'string', value: '', category: 'styling' },
],
hooks: [],
},
{
name: 'HeaderContent',
icon: 'PanelTop',
category: 'layout',
kind: 'layout',
toolboxGroup: 'layout',
description: 'Üst alan ve alt içerik için dikey yerleşim',
acceptsChildren: true,
properties: [
{ name: 'gap', type: 'number', value: 16, category: 'styling' },
{ name: 'className', type: 'string', value: '', category: 'styling' },
],
hooks: [],
},
{
name: 'FlexRow',
icon: 'Columns3',
category: 'layout',
kind: 'layout',
toolboxGroup: 'layout',
description: 'Yatay ve responsive yerleşim',
acceptsChildren: true,
properties: [
{ name: 'gap', type: 'number', value: 16, category: 'styling' },
{ name: 'wrap', type: 'boolean', value: true, category: 'styling' },
{
name: 'align',
type: 'select',
value: 'stretch',
options: ['stretch', 'start', 'center', 'end'],
category: 'styling',
},
{ name: 'className', type: 'string', value: '', category: 'styling' },
],
hooks: [],
},
{
name: 'Spacer',
icon: 'Space',
category: 'layout',
kind: 'layout',
toolboxGroup: 'ui',
description: 'Bileşenler arasında boşluk',
properties: [{ name: 'height', type: 'number', value: 24, category: 'styling' }],
hooks: [],
},
]
export const getDesignerCatalog = (customNames: string[] = []): DesignerComponentDefinition[] => {
const html = HTML_ELEMENTS.filter((definition) => !HTML_UI_DUPLICATES.has(definition.name)).map(
(definition) => normalizeDefinition(definition, 'html', 'html'),
)
const ui = CUSTOM_COMPONENTS.filter(
(definition) => !EXCLUDED_COMPONENTS.has(definition.name),
).map(normalizeUiDefinition)
const definedUiNames = new Set(CUSTOM_COMPONENTS.map((definition) => definition.name))
const generatedOnlyUi: DesignerComponentDefinition[] = Object.entries(generatedMetadata)
.filter(([name]) => !definedUiNames.has(name) && !EXCLUDED_COMPONENTS.has(name))
.map(([name, metadata]) => {
const properties = generatedPropertiesToDesigner(metadata, undefined, name)
const collectionProperty = getDesignerCollectionProperty(name)
if (
DESIGNER_DATA_COMPONENT_NAMES.has(name) &&
!properties.some((property) => property.name === collectionProperty)
) {
properties.unshift(getCollectionPropertyDefinition(name, collectionProperty))
}
return {
name,
icon: 'Blocks',
category: inferUiCategory(name),
kind: 'ui' as const,
toolboxGroup: getToolboxGroup(name),
description: `${metadata.interfaceName} sözleşmesini kullanan Sozsoft UI componenti`,
sourceFile: metadata.sourceFile,
interfaceName: metadata.interfaceName,
acceptsChildren: CONTAINER_NAMES.has(name),
properties,
hooks: [],
}
})
const custom: DesignerComponentDefinition[] = customNames.map((name) => ({
name,
icon: 'Blocks',
category: 'custom',
kind: 'custom',
toolboxGroup: 'custom',
description: 'Veritabanındaki yeniden kullanılabilir bileşen',
acceptsChildren: true,
properties: [{ name: 'className', type: 'string', value: '', category: 'styling' }],
hooks: [],
}))
return [
...DESIGNER_EXTRAS,
SQL_DATA_SOURCE_DEFINITION,
...PLATFORM_COMPONENTS,
...html,
...ui,
...generatedOnlyUi,
...custom,
]
}