@object-ui/app-shell 11.4.0 → 11.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +178 -0
- package/README.md +9 -0
- package/dist/chrome/KeyboardShortcutsDialog.js +2 -1
- package/dist/console/AppContent.js +145 -26
- package/dist/console/ConsoleShell.js +8 -1
- package/dist/context/CommandPaletteProvider.js +2 -1
- package/dist/hooks/useObjectActions.js +16 -4
- package/dist/layout/AppHeader.js +13 -5
- package/dist/layout/AppSidebar.js +10 -4
- package/dist/observability/sentry.d.ts +5 -0
- package/dist/observability/sentry.js +6 -1
- package/dist/preview/DraftChangesPanel.d.ts +29 -1
- package/dist/preview/DraftChangesPanel.js +141 -14
- package/dist/urlParams.d.ts +68 -0
- package/dist/urlParams.js +76 -0
- package/dist/utils/appRoute.d.ts +15 -0
- package/dist/utils/appRoute.js +22 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/pageTabsUrlSync.d.ts +32 -0
- package/dist/utils/pageTabsUrlSync.js +43 -0
- package/dist/utils/recordFormNavigation.d.ts +40 -0
- package/dist/utils/recordFormNavigation.js +30 -0
- package/dist/views/InterfaceListPage.d.ts +1 -0
- package/dist/views/InterfaceListPage.js +1 -1
- package/dist/views/ObjectDataPage.d.ts +29 -0
- package/dist/views/ObjectDataPage.js +227 -0
- package/dist/views/ObjectView.js +4 -3
- package/dist/views/RecordDetailView.js +61 -20
- package/dist/views/RelatedRecordActionsBridge.d.ts +10 -1
- package/dist/views/RelatedRecordActionsBridge.js +49 -16
- package/dist/views/metadata-admin/ResourceEditPage.js +39 -0
- package/dist/views/metadata-admin/i18n.js +214 -4
- package/dist/views/metadata-admin/inspectors/AppNavInspector.d.ts +11 -4
- package/dist/views/metadata-admin/inspectors/AppNavInspector.js +141 -7
- package/dist/views/metadata-admin/inspectors/FlowReferenceField.d.ts +14 -0
- package/dist/views/metadata-admin/inspectors/FlowReferenceField.js +76 -5
- package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.js +35 -19
- package/dist/views/metadata-admin/inspectors/flow-node-config.d.ts +8 -1
- package/dist/views/metadata-admin/inspectors/flow-node-config.js +3 -2
- package/dist/views/metadata-admin/inspectors/nav-target.d.ts +52 -0
- package/dist/views/metadata-admin/inspectors/nav-target.js +149 -0
- package/dist/views/metadata-admin/nav-selection.d.ts +20 -0
- package/dist/views/metadata-admin/nav-selection.js +81 -0
- package/dist/views/metadata-admin/previews/AppNavCanvas.js +9 -1
- package/dist/views/metadata-admin/previews/AppPreview.js +4 -2
- package/dist/views/studio-design/BuilderLanding.d.ts +1 -1
- package/dist/views/studio-design/BuilderLanding.js +12 -19
- package/dist/views/studio-design/ObjectFormDesigner.d.ts +5 -3
- package/dist/views/studio-design/ObjectFormDesigner.js +17 -12
- package/dist/views/studio-design/ObjectSettingsPanel.d.ts +1 -1
- package/dist/views/studio-design/ObjectSettingsPanel.js +4 -3
- package/dist/views/studio-design/ObjectValidationsPanel.js +6 -4
- package/dist/views/studio-design/PackageIdInput.d.ts +31 -0
- package/dist/views/studio-design/PackageIdInput.js +40 -0
- package/dist/views/studio-design/StudioDesignSurface.d.ts +13 -0
- package/dist/views/studio-design/StudioDesignSurface.js +227 -57
- package/dist/views/studio-design/packageSurfaces.d.ts +49 -0
- package/dist/views/studio-design/packageSurfaces.js +34 -0
- package/dist/views/studio-design/packages-io.d.ts +11 -0
- package/dist/views/studio-design/packages-io.js +12 -0
- package/dist/views/studio-design/skeletons.d.ts +16 -0
- package/dist/views/studio-design/skeletons.js +51 -0
- package/package.json +38 -38
|
@@ -367,10 +367,38 @@ const ENGINE_STRINGS_EN = {
|
|
|
367
367
|
'engine.inspector.appNav.kind': 'Nav Item',
|
|
368
368
|
'engine.inspector.appNav.close': 'Close nav item',
|
|
369
369
|
'engine.inspector.appNav.label': 'Label',
|
|
370
|
-
'engine.inspector.appNav.path': 'Path / Route',
|
|
371
370
|
'engine.inspector.appNav.icon': 'Icon',
|
|
372
|
-
'engine.inspector.appNav.kindField': 'Kind',
|
|
373
371
|
'engine.inspector.appNav.remove': 'Remove nav item',
|
|
372
|
+
'engine.inspector.appNav.typeField': 'Type',
|
|
373
|
+
'engine.inspector.appNav.type.object': 'Object',
|
|
374
|
+
'engine.inspector.appNav.type.page': 'Page',
|
|
375
|
+
'engine.inspector.appNav.type.dashboard': 'Dashboard',
|
|
376
|
+
'engine.inspector.appNav.type.report': 'Report',
|
|
377
|
+
'engine.inspector.appNav.type.url': 'Link (URL)',
|
|
378
|
+
'engine.inspector.appNav.type.group': 'Group',
|
|
379
|
+
'engine.inspector.appNav.object': 'Object',
|
|
380
|
+
'engine.inspector.appNav.targetMode': 'Landing',
|
|
381
|
+
'engine.inspector.appNav.mode.default': 'Default view (workspace)',
|
|
382
|
+
'engine.inspector.appNav.mode.view': 'Named view',
|
|
383
|
+
'engine.inspector.appNav.mode.record': 'Record deep-link',
|
|
384
|
+
'engine.inspector.appNav.mode.filters': 'Filtered slice (/data)',
|
|
385
|
+
'engine.inspector.appNav.view': 'View',
|
|
386
|
+
'engine.inspector.appNav.recordId': 'Record ID',
|
|
387
|
+
'engine.inspector.appNav.recordIdHint': 'Supports {current_user_id} and {current_org_id} template variables.',
|
|
388
|
+
'engine.inspector.appNav.recordMode': 'Open mode',
|
|
389
|
+
'engine.inspector.appNav.recordModeView': 'View',
|
|
390
|
+
'engine.inspector.appNav.recordModeEdit': 'Edit',
|
|
391
|
+
'engine.inspector.appNav.filters': 'URL filters',
|
|
392
|
+
'engine.inspector.appNav.filtersField': 'Field',
|
|
393
|
+
'engine.inspector.appNav.filtersValue': 'Value',
|
|
394
|
+
'engine.inspector.appNav.filtersAdd': 'Add condition',
|
|
395
|
+
'engine.inspector.appNav.filtersRemove': 'Remove condition',
|
|
396
|
+
'engine.inspector.appNav.filtersHint': 'One-off slice carried in the URL — promote it to a named view when it becomes curated and reused.',
|
|
397
|
+
'engine.inspector.appNav.url': 'URL',
|
|
398
|
+
'engine.inspector.appNav.urlTarget': 'Open in',
|
|
399
|
+
'engine.inspector.appNav.urlTargetSelf': 'Same tab',
|
|
400
|
+
'engine.inspector.appNav.urlTargetBlank': 'New tab',
|
|
401
|
+
'engine.inspector.appNav.preview': 'Resolved link',
|
|
374
402
|
// View column inspector
|
|
375
403
|
'engine.inspector.viewColumn.kind': 'Column',
|
|
376
404
|
'engine.inspector.viewColumn.close': 'Close column',
|
|
@@ -834,6 +862,8 @@ const ENGINE_STRINGS_EN = {
|
|
|
834
862
|
'engine.studio.create': 'Create',
|
|
835
863
|
'engine.studio.createDraft': 'Create (save as draft)',
|
|
836
864
|
'engine.studio.saveDraft': 'Save draft',
|
|
865
|
+
'engine.studio.data.savedDraft': 'Object “{label}” saved as draft',
|
|
866
|
+
'engine.studio.data.lastSaved': 'Saved {time}',
|
|
837
867
|
'engine.studio.publish': 'Publish',
|
|
838
868
|
'engine.studio.loading': 'Loading…',
|
|
839
869
|
'engine.studio.loadFailed': 'Failed to load',
|
|
@@ -867,11 +897,74 @@ const ENGINE_STRINGS_EN = {
|
|
|
867
897
|
// Package switcher
|
|
868
898
|
'engine.studio.pkg.switchTitle': 'Switch / create package',
|
|
869
899
|
'engine.studio.pkg.readonly': 'Read-only',
|
|
900
|
+
'engine.studio.pkg.readonlyHint': 'Read-only package — switch to or create a writable package to edit.',
|
|
901
|
+
'engine.studio.app.scaffoldNav': "Add this package's objects as menu items",
|
|
902
|
+
'engine.studio.landing.title': 'App Builder',
|
|
903
|
+
'engine.studio.landing.description': 'Design objects, forms, automations and interfaces inside a writable package; edits save as drafts and the whole package publishes in one release. Code-loaded packages are read-only (browse only).',
|
|
904
|
+
'engine.studio.landing.mineHeading': 'My packages (writable)',
|
|
905
|
+
'engine.studio.landing.noneWritable': 'No writable packages yet — create one to start.',
|
|
906
|
+
'engine.studio.landing.dup': 'Duplicate',
|
|
907
|
+
'engine.studio.landing.dupTitle': 'Duplicate this package into a new writable copy (like “duplicate base”)',
|
|
908
|
+
'engine.studio.landing.dupDefaultName': '{name} copy',
|
|
909
|
+
'engine.studio.landing.dupNamePlaceholder': 'Copy name',
|
|
910
|
+
'engine.studio.landing.dupIdPlaceholder': 'Copy package ID',
|
|
911
|
+
'engine.studio.landing.dupGo': 'Duplicate and open the builder',
|
|
912
|
+
'engine.studio.landing.dupCreated': 'Duplicated into writable package “{name}”',
|
|
913
|
+
'engine.studio.landing.createGo': 'Create and start building',
|
|
914
|
+
'engine.studio.landing.installedHeading': 'Installed (read-only · browsable)',
|
|
915
|
+
'engine.studio.designer.search': 'Search…',
|
|
916
|
+
'engine.studio.designer.select': 'Select…',
|
|
917
|
+
'engine.studio.designer.pickDate': 'Pick a date…',
|
|
918
|
+
'engine.studio.designer.fieldAria': '{label} — click to edit properties, drag to reorder',
|
|
919
|
+
'engine.studio.designer.groupUp': 'Move group up',
|
|
920
|
+
'engine.studio.designer.groupDown': 'Move group down',
|
|
921
|
+
'engine.studio.designer.groupDelete': 'Delete group',
|
|
922
|
+
'engine.studio.designer.groupDeleteTitle': 'Delete group (its fields move to Ungrouped)',
|
|
923
|
+
'engine.studio.designer.dropHere': 'Drag fields here',
|
|
924
|
+
'engine.studio.designer.ungrouped': 'Ungrouped',
|
|
925
|
+
'engine.studio.designer.newGroup': 'New group',
|
|
926
|
+
'engine.studio.designer.hint': 'Drag to reorder / move across groups · click a field to edit its properties',
|
|
927
|
+
'engine.studio.designer.addGroup': 'Add group',
|
|
928
|
+
'engine.studio.rules.title': 'Validation rules',
|
|
929
|
+
'engine.studio.rules.none': 'No validation rules yet.',
|
|
930
|
+
'engine.studio.rules.explain': 'Rules run when a record is saved: when the condition is true, the save is rejected with the message.',
|
|
931
|
+
'engine.studio.rules.noMessage': '(no message)',
|
|
932
|
+
'engine.studio.rules.pick': 'Select a rule on the left, or click “New” to create one.',
|
|
933
|
+
'engine.studio.rules.structured': '“{type}” rules carry structured configuration (state-machine transitions / format constraints etc.) and cannot be edited here yet — maintain them in the code package. Message: {message}',
|
|
934
|
+
'engine.studio.rules.none2': '(none)',
|
|
935
|
+
'engine.studio.rules.deleteRule': 'Delete rule',
|
|
936
|
+
'engine.studio.rules.nameLabel': 'Rule name (snake_case, unique)',
|
|
937
|
+
'engine.studio.rules.messageLabel': 'Error message (shown to the user when validation fails)',
|
|
938
|
+
'engine.studio.rules.messagePlaceholder': 'e.g. Completion date is required when status is Done',
|
|
939
|
+
'engine.studio.rules.celPre': 'Fail condition (CEL) — when the condition is ',
|
|
940
|
+
'engine.studio.rules.celTrue': 'true',
|
|
941
|
+
'engine.studio.rules.celMid': ', the save is rejected with the message above; new rules default to ',
|
|
942
|
+
'engine.studio.rules.celPost': ' (never fires) — replace it with a real condition.',
|
|
943
|
+
'engine.studio.rules.severity': 'Severity',
|
|
944
|
+
'engine.studio.rules.severityError': 'error (rejects the save)',
|
|
945
|
+
'engine.studio.rules.enabled': 'Enabled',
|
|
946
|
+
'engine.studio.rules.delete': 'Delete',
|
|
947
|
+
'engine.studio.settings.basics': 'Basics',
|
|
948
|
+
'engine.studio.settings.noInspector': 'No default object inspector registered.',
|
|
949
|
+
'engine.studio.settings.semanticRoles': 'Semantic roles',
|
|
950
|
+
'engine.studio.settings.semanticHint': 'Applied consistently across forms, lists and detail pages (ADR-0085)',
|
|
951
|
+
'engine.studio.settings.nameField': 'Record name field (nameField) — shown in titles, links and references',
|
|
952
|
+
'engine.studio.settings.autoDerive': '(auto-derived)',
|
|
953
|
+
'engine.studio.settings.stageField': 'Lifecycle field (stageField) — the detail page progress bar renders its options',
|
|
954
|
+
'engine.studio.settings.autoDetect': '(auto-detect status/stage-like field names)',
|
|
955
|
+
'engine.studio.settings.stageNone': 'None — this object has no linear flow; hide the progress bar',
|
|
956
|
+
'engine.studio.settings.highlightFields': 'Highlight fields (highlightFields) — default list columns, cards and detail header take the first 4; order is display order',
|
|
957
|
+
'engine.studio.settings.removeField': 'Remove {field}',
|
|
958
|
+
'engine.studio.settings.addFieldOption': '+ Add field…',
|
|
959
|
+
'engine.studio.settings.undeclared': '(not declared — surfaces pick heuristically)',
|
|
870
960
|
'engine.studio.pkg.writable': 'Writable',
|
|
871
961
|
'engine.studio.pkg.heading': 'Packages (apps)',
|
|
872
962
|
'engine.studio.pkg.none': 'No app packages yet',
|
|
873
963
|
'engine.studio.pkg.namePlaceholder': 'Name (e.g. Repair Center)',
|
|
874
964
|
'engine.studio.pkg.idPlaceholder': 'Package ID (e.g. com.example.repairs)',
|
|
965
|
+
'engine.studio.pkg.idStrippedNotice': 'Unsupported characters were removed — allowed: lowercase letters, digits, dots, hyphens, underscores',
|
|
966
|
+
'engine.studio.pkg.idFormatHint': 'Use a reverse-domain identifier with at least one dot, e.g. com.example.myapp',
|
|
967
|
+
'engine.studio.pkg.idFromNameUnavailable': 'No identifier could be suggested from this name — type one below (e.g. com.example.myapp)',
|
|
875
968
|
'engine.studio.pkg.createWritable': 'Create writable package',
|
|
876
969
|
'engine.studio.pkg.new': 'New package (writable base)',
|
|
877
970
|
'engine.studio.pkg.created': 'Package {name} created (writable)',
|
|
@@ -913,6 +1006,7 @@ const ENGINE_STRINGS_EN = {
|
|
|
913
1006
|
'engine.studio.data.labelPlaceholder': 'Display name (e.g. Repair Ticket)',
|
|
914
1007
|
'engine.studio.data.idPlaceholder': 'Identifier (e.g. repair_ticket)',
|
|
915
1008
|
'engine.studio.data.newObject': 'New object',
|
|
1009
|
+
'engine.studio.data.readOnlyPackage': 'This package is read-only — switch to or create a writable package to add objects',
|
|
916
1010
|
'engine.studio.data.firstObjectTitle': 'Start with your first object',
|
|
917
1011
|
'engine.studio.data.firstObjectHint': 'Objects are your app’s data foundation (e.g. “Orders”, “Customers”). Enter a display name and identifier at the bottom-left to create one; then design its fields, forms, and automations, and publish once at the end.',
|
|
918
1012
|
'engine.studio.data.tab.records': 'Records',
|
|
@@ -946,6 +1040,17 @@ const ENGINE_STRINGS_EN = {
|
|
|
946
1040
|
'engine.studio.auto.savedDraft': 'Automation “{label}” saved as draft',
|
|
947
1041
|
'engine.studio.auto.defaultOff': 'Off by default · review before enabling',
|
|
948
1042
|
'engine.studio.auto.heading': 'Automations · flow',
|
|
1043
|
+
'engine.studio.auto.on': 'On',
|
|
1044
|
+
'engine.studio.auto.off': 'Off',
|
|
1045
|
+
'engine.studio.auto.offTitle': 'Disabled — will not run',
|
|
1046
|
+
'engine.studio.auto.onBound': 'Enabled · bound to its trigger',
|
|
1047
|
+
'engine.studio.auto.onUnbound': 'Enabled · no trigger (run manually)',
|
|
1048
|
+
'engine.studio.auto.enabled': 'Enabled',
|
|
1049
|
+
'engine.studio.auto.disabled': 'Disabled',
|
|
1050
|
+
'engine.studio.auto.enableTitle': 'Enable this automation (publish to apply)',
|
|
1051
|
+
'engine.studio.auto.disableTitle': 'Disable this automation (publish to apply)',
|
|
1052
|
+
'engine.studio.auto.enabledToast': 'Enabled — publish to apply',
|
|
1053
|
+
'engine.studio.auto.disabledToast': 'Disabled — publish to apply',
|
|
949
1054
|
'engine.studio.auto.newTitle': 'New automation',
|
|
950
1055
|
'engine.studio.auto.none': 'No automations yet — click “New” to start',
|
|
951
1056
|
'engine.studio.auto.namePlaceholder': 'Name (e.g. Offer Notice)',
|
|
@@ -1214,10 +1319,38 @@ const ENGINE_STRINGS_ZH = {
|
|
|
1214
1319
|
'engine.inspector.appNav.kind': '导航项',
|
|
1215
1320
|
'engine.inspector.appNav.close': '关闭导航项',
|
|
1216
1321
|
'engine.inspector.appNav.label': '名称',
|
|
1217
|
-
'engine.inspector.appNav.path': '路径 / 路由',
|
|
1218
1322
|
'engine.inspector.appNav.icon': '图标',
|
|
1219
|
-
'engine.inspector.appNav.kindField': '类型',
|
|
1220
1323
|
'engine.inspector.appNav.remove': '删除导航项',
|
|
1324
|
+
'engine.inspector.appNav.typeField': '类型',
|
|
1325
|
+
'engine.inspector.appNav.type.object': '对象',
|
|
1326
|
+
'engine.inspector.appNav.type.page': '页面',
|
|
1327
|
+
'engine.inspector.appNav.type.dashboard': '仪表盘',
|
|
1328
|
+
'engine.inspector.appNav.type.report': '报表',
|
|
1329
|
+
'engine.inspector.appNav.type.url': '链接(URL)',
|
|
1330
|
+
'engine.inspector.appNav.type.group': '分组',
|
|
1331
|
+
'engine.inspector.appNav.object': '对象',
|
|
1332
|
+
'engine.inspector.appNav.targetMode': '落点',
|
|
1333
|
+
'engine.inspector.appNav.mode.default': '默认视图(工作台)',
|
|
1334
|
+
'engine.inspector.appNav.mode.view': '具名视图',
|
|
1335
|
+
'engine.inspector.appNav.mode.record': '记录深链',
|
|
1336
|
+
'engine.inspector.appNav.mode.filters': '参数化切面(/data)',
|
|
1337
|
+
'engine.inspector.appNav.view': '视图',
|
|
1338
|
+
'engine.inspector.appNav.recordId': '记录 ID',
|
|
1339
|
+
'engine.inspector.appNav.recordIdHint': '支持 {current_user_id} 与 {current_org_id} 模板变量。',
|
|
1340
|
+
'engine.inspector.appNav.recordMode': '打开方式',
|
|
1341
|
+
'engine.inspector.appNav.recordModeView': '查看',
|
|
1342
|
+
'engine.inspector.appNav.recordModeEdit': '编辑',
|
|
1343
|
+
'engine.inspector.appNav.filters': 'URL 过滤条件',
|
|
1344
|
+
'engine.inspector.appNav.filtersField': '字段',
|
|
1345
|
+
'engine.inspector.appNav.filtersValue': '值',
|
|
1346
|
+
'engine.inspector.appNav.filtersAdd': '添加条件',
|
|
1347
|
+
'engine.inspector.appNav.filtersRemove': '移除条件',
|
|
1348
|
+
'engine.inspector.appNav.filtersHint': '一次性切面由 URL 承载——当它值得沉淀复用时,请改建具名视图。',
|
|
1349
|
+
'engine.inspector.appNav.url': '链接地址',
|
|
1350
|
+
'engine.inspector.appNav.urlTarget': '打开位置',
|
|
1351
|
+
'engine.inspector.appNav.urlTargetSelf': '当前标签页',
|
|
1352
|
+
'engine.inspector.appNav.urlTargetBlank': '新标签页',
|
|
1353
|
+
'engine.inspector.appNav.preview': '运行期落点',
|
|
1221
1354
|
// View column inspector
|
|
1222
1355
|
'engine.inspector.viewColumn.kind': '列',
|
|
1223
1356
|
'engine.inspector.viewColumn.close': '关闭列',
|
|
@@ -1679,6 +1812,8 @@ const ENGINE_STRINGS_ZH = {
|
|
|
1679
1812
|
'engine.studio.create': '创建',
|
|
1680
1813
|
'engine.studio.createDraft': '创建(存为草稿)',
|
|
1681
1814
|
'engine.studio.saveDraft': '保存草稿',
|
|
1815
|
+
'engine.studio.data.savedDraft': '对象「{label}」已存为草稿',
|
|
1816
|
+
'engine.studio.data.lastSaved': '已于 {time} 保存',
|
|
1682
1817
|
'engine.studio.publish': '发布',
|
|
1683
1818
|
'engine.studio.loading': '加载中…',
|
|
1684
1819
|
'engine.studio.loadFailed': '加载失败',
|
|
@@ -1712,11 +1847,74 @@ const ENGINE_STRINGS_ZH = {
|
|
|
1712
1847
|
// Package switcher
|
|
1713
1848
|
'engine.studio.pkg.switchTitle': '切换 / 新建软件包',
|
|
1714
1849
|
'engine.studio.pkg.readonly': '只读',
|
|
1850
|
+
'engine.studio.pkg.readonlyHint': '只读软件包 — 请切换或新建可写软件包后再编辑。',
|
|
1851
|
+
'engine.studio.app.scaffoldNav': '将本包的对象添加为菜单项',
|
|
1852
|
+
'engine.studio.landing.title': '应用构建',
|
|
1853
|
+
'engine.studio.landing.description': '在一个可写软件包里设计对象、表单、自动化与界面;编辑存为草稿,整包一次发布。源码加载的软件包为只读(仅可浏览)。',
|
|
1854
|
+
'engine.studio.landing.mineHeading': '我的软件包(可写)',
|
|
1855
|
+
'engine.studio.landing.noneWritable': '还没有可写软件包 — 新建一个开始。',
|
|
1856
|
+
'engine.studio.landing.dup': '复制',
|
|
1857
|
+
'engine.studio.landing.dupTitle': '复制这个软件包为新的可写副本(类似 duplicate base)',
|
|
1858
|
+
'engine.studio.landing.dupDefaultName': '{name} 副本',
|
|
1859
|
+
'engine.studio.landing.dupNamePlaceholder': '副本名称',
|
|
1860
|
+
'engine.studio.landing.dupIdPlaceholder': '副本包 ID',
|
|
1861
|
+
'engine.studio.landing.dupGo': '复制并进入构建器',
|
|
1862
|
+
'engine.studio.landing.dupCreated': '已复制为可写软件包「{name}」',
|
|
1863
|
+
'engine.studio.landing.createGo': '创建并开始构建',
|
|
1864
|
+
'engine.studio.landing.installedHeading': '已安装(只读 · 可浏览)',
|
|
1865
|
+
'engine.studio.designer.search': '搜索…',
|
|
1866
|
+
'engine.studio.designer.select': '请选择…',
|
|
1867
|
+
'engine.studio.designer.pickDate': '选择日期…',
|
|
1868
|
+
'engine.studio.designer.fieldAria': '{label} — 点选改属性,拖动排序',
|
|
1869
|
+
'engine.studio.designer.groupUp': '上移分组',
|
|
1870
|
+
'engine.studio.designer.groupDown': '下移分组',
|
|
1871
|
+
'engine.studio.designer.groupDelete': '删除分组',
|
|
1872
|
+
'engine.studio.designer.groupDeleteTitle': '删除分组(字段归为未分组)',
|
|
1873
|
+
'engine.studio.designer.dropHere': '拖字段到这里',
|
|
1874
|
+
'engine.studio.designer.ungrouped': '未分组',
|
|
1875
|
+
'engine.studio.designer.newGroup': '新分组',
|
|
1876
|
+
'engine.studio.designer.hint': '拖动字段排序 / 拖到其它分组 · 点选字段改属性',
|
|
1877
|
+
'engine.studio.designer.addGroup': '添加分组',
|
|
1878
|
+
'engine.studio.rules.title': '验证规则',
|
|
1879
|
+
'engine.studio.rules.none': '还没有验证规则。',
|
|
1880
|
+
'engine.studio.rules.explain': '规则在保存记录时执行:条件为真 ⇒ 拒绝保存并提示消息。',
|
|
1881
|
+
'engine.studio.rules.noMessage': '(无消息)',
|
|
1882
|
+
'engine.studio.rules.pick': '选择左侧的规则进行编辑,或点「新增」创建一条。',
|
|
1883
|
+
'engine.studio.rules.structured': '「{type}」类型的规则带有结构化配置(状态机转移表 / 格式约束等),暂不支持在此编辑 —— 请在代码包中维护。消息:{message}',
|
|
1884
|
+
'engine.studio.rules.none2': '(无)',
|
|
1885
|
+
'engine.studio.rules.deleteRule': '删除规则',
|
|
1886
|
+
'engine.studio.rules.nameLabel': '规则名(snake_case,唯一)',
|
|
1887
|
+
'engine.studio.rules.messageLabel': '错误消息(校验失败时展示给用户)',
|
|
1888
|
+
'engine.studio.rules.messagePlaceholder': '例如:完成日期在状态为已完成时必填',
|
|
1889
|
+
'engine.studio.rules.celPre': '失败条件(CEL)—— 条件为 ',
|
|
1890
|
+
'engine.studio.rules.celTrue': '真',
|
|
1891
|
+
'engine.studio.rules.celMid': ' 时,拒绝保存并显示上面的消息;新规则默认 ',
|
|
1892
|
+
'engine.studio.rules.celPost': ' (永不触发),请改为真实条件。',
|
|
1893
|
+
'engine.studio.rules.severity': '严重度',
|
|
1894
|
+
'engine.studio.rules.severityError': 'error(拒绝保存)',
|
|
1895
|
+
'engine.studio.rules.enabled': '启用',
|
|
1896
|
+
'engine.studio.rules.delete': '删除',
|
|
1897
|
+
'engine.studio.settings.basics': '基础信息',
|
|
1898
|
+
'engine.studio.settings.noInspector': '未注册对象默认检查器。',
|
|
1899
|
+
'engine.studio.settings.semanticRoles': '语义角色',
|
|
1900
|
+
'engine.studio.settings.semanticHint': '跨表单 / 列表 / 详情统一生效(ADR-0085)',
|
|
1901
|
+
'engine.studio.settings.nameField': '记录名称字段(nameField)—— 标题、链接、引用处显示的字段',
|
|
1902
|
+
'engine.studio.settings.autoDerive': '(自动推导)',
|
|
1903
|
+
'engine.studio.settings.stageField': '生命周期字段(stageField)—— 详情页顶部进度条按它的选项渲染',
|
|
1904
|
+
'engine.studio.settings.autoDetect': '(自动探测 status/stage 等字段名)',
|
|
1905
|
+
'engine.studio.settings.stageNone': '无 —— 这个对象没有线性流程,不显示进度条',
|
|
1906
|
+
'engine.studio.settings.highlightFields': '重点字段(highlightFields)—— 默认列表列、卡片、详情顶栏取前 4;顺序即展示顺序',
|
|
1907
|
+
'engine.studio.settings.removeField': '移除 {field}',
|
|
1908
|
+
'engine.studio.settings.addFieldOption': '+ 添加字段…',
|
|
1909
|
+
'engine.studio.settings.undeclared': '(未声明 —— 各处按启发式自动挑选)',
|
|
1715
1910
|
'engine.studio.pkg.writable': '可写',
|
|
1716
1911
|
'engine.studio.pkg.heading': '软件包(应用)',
|
|
1717
1912
|
'engine.studio.pkg.none': '暂无应用软件包',
|
|
1718
1913
|
'engine.studio.pkg.namePlaceholder': '名称(如:维修中心)',
|
|
1719
1914
|
'engine.studio.pkg.idPlaceholder': '包 ID(如:com.example.repairs)',
|
|
1915
|
+
'engine.studio.pkg.idStrippedNotice': '已移除不支持的字符——仅允许小写字母、数字、点、连字符和下划线',
|
|
1916
|
+
'engine.studio.pkg.idFormatHint': '请使用带至少一个点的反向域名标识,如 com.example.myapp',
|
|
1917
|
+
'engine.studio.pkg.idFromNameUnavailable': '无法从该名称生成标识,请在下方手动输入(如 com.example.myapp)',
|
|
1720
1918
|
'engine.studio.pkg.createWritable': '创建可写软件包',
|
|
1721
1919
|
'engine.studio.pkg.new': '新建软件包(可写 base)',
|
|
1722
1920
|
'engine.studio.pkg.created': '软件包 {name} 已创建(可写)',
|
|
@@ -1758,6 +1956,7 @@ const ENGINE_STRINGS_ZH = {
|
|
|
1758
1956
|
'engine.studio.data.labelPlaceholder': '显示名(如:报修工单)',
|
|
1759
1957
|
'engine.studio.data.idPlaceholder': '标识符(如:repair_ticket)',
|
|
1760
1958
|
'engine.studio.data.newObject': '新建对象',
|
|
1959
|
+
'engine.studio.data.readOnlyPackage': '此包为只读——请切换到或新建一个可写的包后再添加对象',
|
|
1761
1960
|
'engine.studio.data.firstObjectTitle': '从第一个对象开始',
|
|
1762
1961
|
'engine.studio.data.firstObjectHint': '对象是应用的数据基座(如「订单」「客户」)。在左下角输入显示名与标识符即可创建;之后为它设计字段、表单与自动化,最后一次发布。',
|
|
1763
1962
|
'engine.studio.data.tab.records': '记录',
|
|
@@ -1791,6 +1990,17 @@ const ENGINE_STRINGS_ZH = {
|
|
|
1791
1990
|
'engine.studio.auto.savedDraft': '自动化「{label}」已存为草稿',
|
|
1792
1991
|
'engine.studio.auto.defaultOff': '默认 OFF · 审阅后再启用',
|
|
1793
1992
|
'engine.studio.auto.heading': '自动化 · flow',
|
|
1993
|
+
'engine.studio.auto.on': '已启用',
|
|
1994
|
+
'engine.studio.auto.off': '已停用',
|
|
1995
|
+
'engine.studio.auto.offTitle': '已停用 — 不会运行',
|
|
1996
|
+
'engine.studio.auto.onBound': '已启用 · 已绑定触发器',
|
|
1997
|
+
'engine.studio.auto.onUnbound': '已启用 · 无触发器(手动运行)',
|
|
1998
|
+
'engine.studio.auto.enabled': '已启用',
|
|
1999
|
+
'engine.studio.auto.disabled': '已停用',
|
|
2000
|
+
'engine.studio.auto.enableTitle': '启用此自动化(发布后生效)',
|
|
2001
|
+
'engine.studio.auto.disableTitle': '停用此自动化(发布后生效)',
|
|
2002
|
+
'engine.studio.auto.enabledToast': '已设为启用 — 发布后生效',
|
|
2003
|
+
'engine.studio.auto.disabledToast': '已设为停用 — 发布后生效',
|
|
1794
2004
|
'engine.studio.auto.newTitle': '新建自动化',
|
|
1795
2005
|
'engine.studio.auto.none': '还没有自动化 — 点「新建」开始',
|
|
1796
2006
|
'engine.studio.auto.namePlaceholder': '名称(如:录用通知)',
|
|
@@ -6,11 +6,18 @@
|
|
|
6
6
|
* `<rootKey>[i]` for top-level items, `<rootKey>[i].children[j]` for
|
|
7
7
|
* nested. This inspector walks the same path to read/write.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* Target editing is contract-first (#2245): a `type` selector plus
|
|
10
|
+
* per-type typed-target pickers replacing the old off-spec `path` /
|
|
11
|
+
* `kind` free-text fields the runtime never read. `object` items expose
|
|
12
|
+
* the four landing modes matching resolveHref precedence (default /
|
|
13
|
+
* named view / record deep-link / filters slice on `/data`, ADR-0055);
|
|
14
|
+
* the mode is derived from field presence — never persisted — and every
|
|
15
|
+
* edit clears the other modes' fields plus all legacy keys, so touching
|
|
16
|
+
* a legacy item migrates it to spec shape. A live href preview renders
|
|
17
|
+
* the REAL runtime landing via resolveHref, making "declared =
|
|
18
|
+
* enforced" visible while editing.
|
|
12
19
|
*/
|
|
13
20
|
import * as React from 'react';
|
|
14
21
|
import type { MetadataInspectorProps } from '../inspector-registry';
|
|
15
|
-
export declare function AppNavInspector({ selection, draft, onPatch, onClearSelection, onSelectionChange, locale, readOnly }: MetadataInspectorProps): React.JSX.Element;
|
|
22
|
+
export declare function AppNavInspector({ selection, draft, name, onPatch, onClearSelection, onSelectionChange, locale, readOnly }: MetadataInspectorProps): React.JSX.Element;
|
|
16
23
|
export declare const APP_NAV_ROOT_KEYS: string[];
|
|
@@ -1,8 +1,34 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
3
|
+
/**
|
|
4
|
+
* AppNavInspector — scoped editor for the selected app navigation item.
|
|
5
|
+
*
|
|
6
|
+
* Apps accept nav under several keys (nav / navigation / tabs / items
|
|
7
|
+
* / menu). The preview emits selection id as a dotted path
|
|
8
|
+
* `<rootKey>[i]` for top-level items, `<rootKey>[i].children[j]` for
|
|
9
|
+
* nested. This inspector walks the same path to read/write.
|
|
10
|
+
*
|
|
11
|
+
* Target editing is contract-first (#2245): a `type` selector plus
|
|
12
|
+
* per-type typed-target pickers replacing the old off-spec `path` /
|
|
13
|
+
* `kind` free-text fields the runtime never read. `object` items expose
|
|
14
|
+
* the four landing modes matching resolveHref precedence (default /
|
|
15
|
+
* named view / record deep-link / filters slice on `/data`, ADR-0055);
|
|
16
|
+
* the mode is derived from field presence — never persisted — and every
|
|
17
|
+
* edit clears the other modes' fields plus all legacy keys, so touching
|
|
18
|
+
* a legacy item migrates it to spec shape. A live href preview renders
|
|
19
|
+
* the REAL runtime landing via resolveHref, making "declared =
|
|
20
|
+
* enforced" visible while editing.
|
|
21
|
+
*/
|
|
22
|
+
import * as React from 'react';
|
|
23
|
+
import { resolveHref } from '@object-ui/layout';
|
|
24
|
+
import { Plus, X } from 'lucide-react';
|
|
2
25
|
import { t } from '../i18n';
|
|
26
|
+
import { useMetadataClient } from '../useMetadata';
|
|
27
|
+
import { useObjectFields } from '../previews/useObjectFields';
|
|
3
28
|
import { InspectorShell, InspectorReorderButtons, InspectorTextField, InspectorSelectField, InspectorRemoveButton, InspectorEmptyState, spliceArray, moveArray, } from './_shared';
|
|
29
|
+
import { InspectorComboField } from './InspectorComboField';
|
|
30
|
+
import { NAV_ITEM_TYPES, NAV_TYPE_TARGETS, OBJECT_TARGET_MODES, OBJECT_MODE_FIELDS, inferNavItemType, deriveObjectTargetMode, clearedTargetPatch, ensureNavId, } from './nav-target';
|
|
4
31
|
const ROOT_KEYS = ['nav', 'navigation', 'tabs', 'items', 'menu'];
|
|
5
|
-
const KINDS = ['object', 'page', 'dashboard', 'report', 'link', 'group'];
|
|
6
32
|
/** Parse "nav[0].children[2]" → [{key:'nav', index:0}, {key:'children', index:2}]. */
|
|
7
33
|
function parsePath(id) {
|
|
8
34
|
const segs = id.split('.');
|
|
@@ -82,16 +108,111 @@ function writeSiblings(draft, hops, nextSiblings) {
|
|
|
82
108
|
arr[parentHop.index] = parentCopy;
|
|
83
109
|
return { [rootKey]: root };
|
|
84
110
|
}
|
|
85
|
-
|
|
111
|
+
/** Fetch a metadata type's items as combobox options (name → label (name)). */
|
|
112
|
+
function useMetadataOptions(type) {
|
|
113
|
+
const client = useMetadataClient();
|
|
114
|
+
const [state, setState] = React.useState({
|
|
115
|
+
options: [],
|
|
116
|
+
loading: !!type,
|
|
117
|
+
});
|
|
118
|
+
React.useEffect(() => {
|
|
119
|
+
if (!type) {
|
|
120
|
+
setState({ options: [], loading: false });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
let cancelled = false;
|
|
124
|
+
setState((s) => ({ ...s, loading: true }));
|
|
125
|
+
client
|
|
126
|
+
.list(type)
|
|
127
|
+
.then((rows) => {
|
|
128
|
+
if (cancelled)
|
|
129
|
+
return;
|
|
130
|
+
const options = (Array.isArray(rows) ? rows : [])
|
|
131
|
+
.filter((r) => r && typeof r.name === 'string' && r.name)
|
|
132
|
+
.map((r) => ({
|
|
133
|
+
value: String(r.name),
|
|
134
|
+
label: typeof r.label === 'string' && r.label && r.label !== r.name ? `${r.label} (${r.name})` : String(r.name),
|
|
135
|
+
}));
|
|
136
|
+
setState({ options, loading: false });
|
|
137
|
+
})
|
|
138
|
+
.catch(() => {
|
|
139
|
+
if (!cancelled)
|
|
140
|
+
setState({ options: [], loading: false });
|
|
141
|
+
});
|
|
142
|
+
return () => {
|
|
143
|
+
cancelled = true;
|
|
144
|
+
};
|
|
145
|
+
}, [client, type]);
|
|
146
|
+
return state;
|
|
147
|
+
}
|
|
148
|
+
/** Preview template context that keeps template vars VISIBLE in the href. */
|
|
149
|
+
const PREVIEW_CTX = {
|
|
150
|
+
currentUserId: '{current_user_id}',
|
|
151
|
+
currentOrgId: '{current_org_id}',
|
|
152
|
+
};
|
|
153
|
+
/** Key/value rows editor for the `filters` landing mode (ADR-0055). */
|
|
154
|
+
function FiltersEditor({ objectName, filters, onCommit, disabled, locale, }) {
|
|
155
|
+
const { fields } = useObjectFields(objectName || undefined);
|
|
156
|
+
const fieldOptions = React.useMemo(() => fields.map((f) => ({ value: f.name, label: f.label && f.label !== f.name ? `${f.label} (${f.name})` : f.name })), [fields]);
|
|
157
|
+
const entries = Object.entries(filters);
|
|
158
|
+
const update = (index, key, value) => {
|
|
159
|
+
const next = entries.slice();
|
|
160
|
+
next[index] = [key, value];
|
|
161
|
+
onCommit(Object.fromEntries(next.filter(([k]) => k !== '')));
|
|
162
|
+
};
|
|
163
|
+
const remove = (index) => {
|
|
164
|
+
const next = entries.slice();
|
|
165
|
+
next.splice(index, 1);
|
|
166
|
+
onCommit(Object.fromEntries(next));
|
|
167
|
+
};
|
|
168
|
+
return (_jsxs("div", { className: "space-y-1.5", children: [_jsx("div", { className: "text-[11px] font-medium text-muted-foreground", children: t('engine.inspector.appNav.filters', locale) }), entries.map(([field, value], i) => (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx("div", { className: "min-w-0 flex-1", children: _jsx(InspectorComboField, { value: field, onCommit: (v) => update(i, v, value), options: fieldOptions, placeholder: t('engine.inspector.appNav.filtersField', locale), disabled: disabled, mono: true }) }), _jsx("span", { className: "text-xs text-muted-foreground", children: "=" }), _jsx("input", { value: value, onChange: (e) => update(i, field, e.target.value), placeholder: t('engine.inspector.appNav.filtersValue', locale), disabled: disabled, className: "h-7 w-0 min-w-0 flex-1 rounded border bg-background px-2 font-mono text-xs", "data-testid": `nav-filter-value-${field || 'new'}` }), _jsx("button", { type: "button", onClick: () => remove(i), disabled: disabled, className: "rounded p-1 text-muted-foreground hover:bg-muted", "aria-label": t('engine.inspector.appNav.filtersRemove', locale), "data-testid": `nav-filter-remove-${field || 'new'}`, children: _jsx(X, { className: "h-3 w-3" }) })] }, `${field}-${i}`))), _jsxs("button", { type: "button", onClick: () => onCommit({ ...filters, '': '' }), disabled: disabled || Object.prototype.hasOwnProperty.call(filters, ''), className: "inline-flex items-center gap-1 rounded border px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted", "data-testid": "nav-filter-add", children: [_jsx(Plus, { className: "h-3 w-3" }), t('engine.inspector.appNav.filtersAdd', locale)] }), _jsx("p", { className: "text-[11px] text-muted-foreground", children: t('engine.inspector.appNav.filtersHint', locale) })] }));
|
|
169
|
+
}
|
|
170
|
+
export function AppNavInspector({ selection, draft, name, onPatch, onClearSelection, onSelectionChange, locale, readOnly }) {
|
|
86
171
|
const hops = parsePath(selection.id);
|
|
87
172
|
const { node, parent, index } = hops ? readAt(draft, hops) : { node: null, parent: [], index: -1 };
|
|
173
|
+
const navType = node ? inferNavItemType(node) : null;
|
|
174
|
+
const objectMode = node ? deriveObjectTargetMode(node) : 'default';
|
|
175
|
+
const objectName = String(node?.objectName ?? node?.object ?? '');
|
|
176
|
+
// Hooks run unconditionally (before the not-found early return) to keep
|
|
177
|
+
// the Rules of Hooks satisfied; `undefined` type disables the fetch.
|
|
178
|
+
const objectOptions = useMetadataOptions(navType === 'object' ? 'object' : undefined);
|
|
179
|
+
const targetMeta = navType && navType !== 'object' ? NAV_TYPE_TARGETS[navType].metaType : undefined;
|
|
180
|
+
const targetOptions = useMetadataOptions(targetMeta);
|
|
181
|
+
const viewOptionsRaw = useMetadataOptions(navType === 'object' && objectMode === 'view' ? 'view' : undefined);
|
|
182
|
+
// Views are named `<object>.<key>` (MetadataProvider) — scope the picker
|
|
183
|
+
// to the bound object instead of offering the whole workspace's views.
|
|
184
|
+
const viewOptions = React.useMemo(() => objectName
|
|
185
|
+
? viewOptionsRaw.options.filter((o) => o.value.startsWith(`${objectName}.`) || !o.value.includes('.'))
|
|
186
|
+
: viewOptionsRaw.options, [viewOptionsRaw.options, objectName]);
|
|
88
187
|
if (!hops || !node) {
|
|
89
188
|
return (_jsx(InspectorShell, { kindLabel: t('engine.inspector.appNav.kind', locale), title: selection.label ?? selection.id, onClose: onClearSelection, closeLabel: t('engine.inspector.appNav.close', locale), children: _jsx(InspectorEmptyState, { message: selection.id }) }));
|
|
90
189
|
}
|
|
91
|
-
const labelOf = node.label ?? node.title ?? node.name ??
|
|
92
|
-
|
|
190
|
+
const labelOf = node.label ?? node.title ?? node.name ?? selection.id;
|
|
191
|
+
/**
|
|
192
|
+
* Every write enforces the spec invariants: snake_case `id`, an explicit
|
|
193
|
+
* `type`, and the caller-provided field clears — editing a legacy item
|
|
194
|
+
* IS its migration to spec shape.
|
|
195
|
+
*/
|
|
93
196
|
const patch = (updates) => {
|
|
94
|
-
|
|
197
|
+
const nextType = (updates.type ?? node.type ?? navType ?? undefined);
|
|
198
|
+
onPatch(writeAt(draft, hops, {
|
|
199
|
+
...node,
|
|
200
|
+
id: ensureNavId(node, parent),
|
|
201
|
+
...(nextType ? { type: nextType } : {}),
|
|
202
|
+
...updates,
|
|
203
|
+
}));
|
|
204
|
+
};
|
|
205
|
+
const switchType = (nextType) => {
|
|
206
|
+
const targetKey = NAV_TYPE_TARGETS[nextType].targetKey;
|
|
207
|
+
const keep = nextType === 'object' ? OBJECT_MODE_FIELDS.default : targetKey ? [targetKey] : [];
|
|
208
|
+
patch({ type: nextType, ...clearedTargetPatch(keep) });
|
|
209
|
+
};
|
|
210
|
+
const switchObjectMode = (mode) => {
|
|
211
|
+
const cleared = clearedTargetPatch(OBJECT_MODE_FIELDS[mode]);
|
|
212
|
+
if (mode === 'filters')
|
|
213
|
+
patch({ ...cleared, objectName, filters: node.filters ?? {} });
|
|
214
|
+
else
|
|
215
|
+
patch({ ...cleared, objectName });
|
|
95
216
|
};
|
|
96
217
|
const remove = () => {
|
|
97
218
|
onPatch(writeAt(draft, hops, null));
|
|
@@ -105,6 +226,19 @@ export function AppNavInspector({ selection, draft, onPatch, onClearSelection, o
|
|
|
105
226
|
const newId = prefix ? `${prefix}.${leafKey}[${to}]` : `${leafKey}[${to}]`;
|
|
106
227
|
onSelectionChange?.({ kind: 'nav', id: newId, label: String(labelOf) });
|
|
107
228
|
};
|
|
108
|
-
|
|
229
|
+
// Live landing preview — the REAL runtime URL for the current config
|
|
230
|
+
// (resolveHref is the single source of truth), template vars kept
|
|
231
|
+
// visible. `#` reads as "no target yet".
|
|
232
|
+
const previewHref = navType && navType !== 'group'
|
|
233
|
+
? resolveHref({ ...node, type: navType }, `/apps/${name || 'app'}`, PREVIEW_CTX).href
|
|
234
|
+
: null;
|
|
235
|
+
const target = NAV_TYPE_TARGETS[navType ?? 'group'];
|
|
236
|
+
return (_jsxs(InspectorShell, { kindLabel: t('engine.inspector.appNav.kind', locale), title: String(labelOf), onClose: onClearSelection, closeLabel: t('engine.inspector.appNav.close', locale), headerActions: _jsx(InspectorReorderButtons, { index: index, total: parent.length, onMove: move, upLabel: t('engine.inspector.reorder.up', locale), downLabel: t('engine.inspector.reorder.down', locale), disabled: readOnly }), footer: _jsx(InspectorRemoveButton, { label: t('engine.inspector.appNav.remove', locale), onClick: remove, disabled: readOnly }), children: [_jsx(InspectorTextField, { label: t('engine.inspector.appNav.label', locale), value: String(node.label ?? node.title ?? node.name ?? ''), onCommit: (v) => patch({ label: v }), disabled: readOnly }), _jsx(InspectorTextField, { label: t('engine.inspector.appNav.icon', locale), value: String(node.icon ?? ''), onCommit: (v) => patch({ icon: v }), disabled: readOnly }), _jsx(InspectorSelectField, { label: t('engine.inspector.appNav.typeField', locale), value: navType ?? '', options: NAV_ITEM_TYPES.map((v) => ({ value: v, label: t(`engine.inspector.appNav.type.${v}`, locale) })), onCommit: (v) => switchType(v), disabled: readOnly }), navType === 'object' && (_jsxs(_Fragment, { children: [_jsx(InspectorComboField, { label: t('engine.inspector.appNav.object', locale), value: objectName, options: objectOptions.options, loading: objectOptions.loading, onCommit: (v) => patch({ objectName: v, object: undefined }), disabled: readOnly, mono: true }), _jsx(InspectorSelectField, { label: t('engine.inspector.appNav.targetMode', locale), value: objectMode, options: OBJECT_TARGET_MODES.map((m) => ({ value: m, label: t(`engine.inspector.appNav.mode.${m}`, locale) })), onCommit: (v) => switchObjectMode(v), disabled: readOnly || !objectName }), objectMode === 'view' && (_jsx(InspectorComboField, { label: t('engine.inspector.appNav.view', locale), value: String(node.viewName ?? ''), options: viewOptions, loading: viewOptionsRaw.loading, onCommit: (v) => patch({ viewName: v }), disabled: readOnly, mono: true })), objectMode === 'record' && (_jsxs(_Fragment, { children: [_jsx(InspectorTextField, { label: t('engine.inspector.appNav.recordId', locale), value: String(node.recordId ?? ''), onCommit: (v) => patch({ recordId: v }), disabled: readOnly, mono: true }), _jsx("p", { className: "text-[11px] text-muted-foreground", children: t('engine.inspector.appNav.recordIdHint', locale) }), _jsx(InspectorSelectField, { label: t('engine.inspector.appNav.recordMode', locale), value: String(node.recordMode ?? 'view'), options: [
|
|
237
|
+
{ value: 'view', label: t('engine.inspector.appNav.recordModeView', locale) },
|
|
238
|
+
{ value: 'edit', label: t('engine.inspector.appNav.recordModeEdit', locale) },
|
|
239
|
+
], onCommit: (v) => patch({ recordMode: v }), disabled: readOnly })] })), objectMode === 'filters' && (_jsx(FiltersEditor, { objectName: objectName, filters: node.filters ?? {}, onCommit: (next) => patch({ filters: next }), disabled: readOnly, locale: locale }))] })), navType && navType !== 'object' && navType !== 'group' && target.targetKey && (target.metaType ? (_jsx(InspectorComboField, { label: t(`engine.inspector.appNav.type.${navType}`, locale), value: String(node[target.targetKey] ?? ''), options: targetOptions.options, loading: targetOptions.loading, onCommit: (v) => patch({ [target.targetKey]: v }), disabled: readOnly, mono: true })) : (_jsxs(_Fragment, { children: [_jsx(InspectorTextField, { label: t('engine.inspector.appNav.url', locale), value: String(node.url ?? node.href ?? ''), onCommit: (v) => patch({ url: v, href: undefined }), disabled: readOnly, mono: true }), _jsx(InspectorSelectField, { label: t('engine.inspector.appNav.urlTarget', locale), value: String(node.target ?? '_self'), options: [
|
|
240
|
+
{ value: '_self', label: t('engine.inspector.appNav.urlTargetSelf', locale) },
|
|
241
|
+
{ value: '_blank', label: t('engine.inspector.appNav.urlTargetBlank', locale) },
|
|
242
|
+
], onCommit: (v) => patch({ target: v }), disabled: readOnly })] }))), previewHref && (_jsxs("div", { className: "rounded border bg-muted/30 px-2 py-1.5", "data-testid": "nav-target-preview", children: [_jsx("div", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: t('engine.inspector.appNav.preview', locale) }), _jsx("div", { className: "break-all font-mono text-[11px]", children: previewHref })] }))] }));
|
|
109
243
|
}
|
|
110
244
|
export const APP_NAV_ROOT_KEYS = ROOT_KEYS;
|
|
@@ -33,10 +33,15 @@ export interface FlowReferenceContext {
|
|
|
33
33
|
/** The node currently being edited — used to resolve sibling config keys. */
|
|
34
34
|
node: Record<string, unknown> | null;
|
|
35
35
|
}
|
|
36
|
+
interface Option {
|
|
37
|
+
value: string;
|
|
38
|
+
label: string;
|
|
39
|
+
}
|
|
36
40
|
/** A concrete (non-polymorphic) reference resolution. */
|
|
37
41
|
export interface ResolvedRef {
|
|
38
42
|
kind: ReferenceKind;
|
|
39
43
|
objectSource?: string;
|
|
44
|
+
connectorSource?: string;
|
|
40
45
|
}
|
|
41
46
|
/**
|
|
42
47
|
* Resolve a (possibly polymorphic) reference spec to a concrete kind. For a
|
|
@@ -45,6 +50,14 @@ export interface ResolvedRef {
|
|
|
45
50
|
* the caller then renders plain free text.
|
|
46
51
|
*/
|
|
47
52
|
export declare function resolveRefKind(ref: FlowReferenceSpec | undefined, sibling: (key: string) => unknown): ResolvedRef | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the chosen connector name for a `connector-action` reference — read
|
|
55
|
+
* from the sibling key on this node's `connectorConfig` block (default
|
|
56
|
+
* `connectorId`), which is where the connector picker writes it.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveConnectorName(kind: ReferenceKind, connectorSource: string | undefined, ctx: FlowReferenceContext): string | undefined;
|
|
59
|
+
/** A connector descriptor's action list → combobox options (exported for test). */
|
|
60
|
+
export declare function connectorActionsToOptions(actions: unknown): Option[];
|
|
48
61
|
export interface ReferenceComboboxProps {
|
|
49
62
|
/** The resolved concrete reference, or undefined → plain free text. */
|
|
50
63
|
resolved: ResolvedRef | undefined;
|
|
@@ -81,3 +94,4 @@ export interface FlowReferenceFieldProps {
|
|
|
81
94
|
* `template` follows `actionType`).
|
|
82
95
|
*/
|
|
83
96
|
export declare function FlowReferenceField({ field, value, onCommit, disabled, context }: FlowReferenceFieldProps): React.JSX.Element;
|
|
97
|
+
export {};
|