@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BuilderLanding — the application builder's front door.
|
|
3
3
|
*
|
|
4
|
-
* The journey from login: Home → Studio app →
|
|
4
|
+
* The journey from login: Home → Studio app → the App Builder landing (this page, embedded
|
|
5
5
|
* in the app chrome via the `studio:builder` component ref) → pick or create a
|
|
6
6
|
* writable base package → the full-screen pillar builder
|
|
7
7
|
* (`/studio/:packageId/:tab`). Also served standalone at bare `/studio` so the
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
/**
|
|
4
4
|
* BuilderLanding — the application builder's front door.
|
|
5
5
|
*
|
|
6
|
-
* The journey from login: Home → Studio app →
|
|
6
|
+
* The journey from login: Home → Studio app → the App Builder landing (this page, embedded
|
|
7
7
|
* in the app chrome via the `studio:builder` component ref) → pick or create a
|
|
8
8
|
* writable base package → the full-screen pillar builder
|
|
9
9
|
* (`/studio/:packageId/:tab`). Also served standalone at bare `/studio` so the
|
|
@@ -18,9 +18,12 @@ import { useNavigate } from 'react-router-dom';
|
|
|
18
18
|
import { Boxes, Hammer, Lock, Plus, Loader2, Copy } from 'lucide-react';
|
|
19
19
|
import { toast } from 'sonner';
|
|
20
20
|
import { toFieldNameLoose } from '../metadata-admin/previews/object-fields-io';
|
|
21
|
+
import { t, tFormat, useMetadataLocale } from '../metadata-admin/i18n';
|
|
21
22
|
import { fetchPackages, createBasePackage, duplicatePackage, PACKAGE_ID_RE } from './packages-io';
|
|
23
|
+
import { PackageIdInput, PackageIdSuggestionHint } from './PackageIdInput';
|
|
22
24
|
export function BuilderLanding() {
|
|
23
25
|
const navigate = useNavigate();
|
|
26
|
+
const locale = useMetadataLocale();
|
|
24
27
|
const [pkgs, setPkgs] = React.useState(null);
|
|
25
28
|
const [error, setError] = React.useState(null);
|
|
26
29
|
const [creating, setCreating] = React.useState(false);
|
|
@@ -64,7 +67,7 @@ export function BuilderLanding() {
|
|
|
64
67
|
};
|
|
65
68
|
const writable = pkgs?.filter((p) => p.writable) ?? [];
|
|
66
69
|
const readonly = pkgs?.filter((p) => !p.writable) ?? [];
|
|
67
|
-
//
|
|
70
|
+
// Duplicate into a writable copy (ADR-0070 D4): a read-only code package is a STARTING POINT,
|
|
68
71
|
// not a dead end — duplicate re-namespaces it into a new writable base and
|
|
69
72
|
// drops the user straight into its builder. This is also the real substance
|
|
70
73
|
// behind Home's "Start with a template".
|
|
@@ -75,7 +78,7 @@ export function BuilderLanding() {
|
|
|
75
78
|
const [dupErr, setDupErr] = React.useState(null);
|
|
76
79
|
const startDup = (p) => {
|
|
77
80
|
setDupFor(p.id);
|
|
78
|
-
setDupName(
|
|
81
|
+
setDupName(tFormat('engine.studio.landing.dupDefaultName', locale, { name: p.name }));
|
|
79
82
|
setDupId(`${p.id}-copy`);
|
|
80
83
|
setDupErr(null);
|
|
81
84
|
};
|
|
@@ -90,7 +93,7 @@ export function BuilderLanding() {
|
|
|
90
93
|
setDupErr(null);
|
|
91
94
|
try {
|
|
92
95
|
await duplicatePackage(dupFor, id, name);
|
|
93
|
-
toast.success(
|
|
96
|
+
toast.success(tFormat('engine.studio.landing.dupCreated', locale, { name }));
|
|
94
97
|
open(id);
|
|
95
98
|
}
|
|
96
99
|
catch (e) {
|
|
@@ -100,17 +103,12 @@ export function BuilderLanding() {
|
|
|
100
103
|
setDupBusy(false);
|
|
101
104
|
}
|
|
102
105
|
};
|
|
103
|
-
return (_jsxs("div", { className: "mx-auto w-full max-w-3xl p-6", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx(Hammer, { className: "h-5 w-5 text-primary" }), _jsx("h1", { className: "text-lg font-semibold", children:
|
|
106
|
+
return (_jsxs("div", { className: "mx-auto w-full max-w-3xl p-6", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx(Hammer, { className: "h-5 w-5 text-primary" }), _jsx("h1", { className: "text-lg font-semibold", children: t('engine.studio.landing.title', locale) })] }), _jsx("p", { className: "mb-5 text-xs leading-5 text-muted-foreground", children: t('engine.studio.landing.description', locale) }), error && (_jsx("div", { className: "mb-3 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-1.5 text-[11px] text-destructive", children: error })), _jsx("h2", { className: "mb-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: t('engine.studio.landing.mineHeading', locale) }), _jsxs("div", { className: "mb-6 grid grid-cols-1 gap-2 sm:grid-cols-2", children: [pkgs === null && _jsx("p", { className: "text-[11px] text-muted-foreground", children: t('engine.studio.loading', locale) }), pkgs !== null && writable.length === 0 && (_jsx("p", { className: "text-[11px] text-muted-foreground", children: t('engine.studio.landing.noneWritable', locale) })), writable.map((p) => (_jsxs("div", { className: "rounded-lg border bg-background", children: [_jsxs("div", { className: "flex items-center gap-2.5 px-3 py-2.5", children: [_jsxs("button", { type: "button", onClick: () => open(p.id), className: "flex min-w-0 flex-1 items-center gap-2.5 text-left", children: [_jsx(Boxes, { className: "h-4 w-4 shrink-0 text-primary" }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate text-[13px] font-medium", children: p.name }), _jsx("span", { className: "block truncate font-mono text-[10px] text-muted-foreground", children: p.id })] })] }), _jsx("span", { className: "rounded bg-emerald-400/15 px-1.5 py-0.5 text-[10px] text-emerald-600 dark:text-emerald-300", children: t('engine.studio.pkg.writable', locale) }), _jsxs("button", { type: "button", onClick: () => (dupFor === p.id ? setDupFor(null) : startDup(p)), title: t('engine.studio.landing.dupTitle', locale), className: "inline-flex items-center gap-1 rounded-md border px-2 py-1 text-[10px] text-muted-foreground hover:bg-muted hover:text-foreground", children: [_jsx(Copy, { className: "h-3 w-3" }), " ", t('engine.studio.landing.dup', locale)] })] }), dupFor === p.id && (_jsxs("div", { className: "flex flex-col gap-1.5 border-t px-3 py-2.5", children: [_jsx("input", { autoFocus: true, value: dupName, onChange: (e) => setDupName(e.target.value), onKeyDown: (e) => {
|
|
104
107
|
if (e.key === 'Enter')
|
|
105
108
|
void doDup();
|
|
106
109
|
if (e.key === 'Escape')
|
|
107
110
|
setDupFor(null);
|
|
108
|
-
}, placeholder:
|
|
109
|
-
if (e.key === 'Enter')
|
|
110
|
-
void doDup();
|
|
111
|
-
if (e.key === 'Escape')
|
|
112
|
-
setDupFor(null);
|
|
113
|
-
}, placeholder: "\u526F\u672C\u5305 ID", className: "h-7 w-full rounded-md border bg-background px-2 font-mono text-[11px] outline-none focus:ring-1 focus:ring-primary" }), dupErr && _jsx("p", { className: "text-[10px] text-destructive", children: dupErr }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs("button", { type: "button", onClick: () => void doDup(), disabled: dupBusy || !dupName.trim() || !PACKAGE_ID_RE.test(dupId.trim()), className: "inline-flex flex-1 items-center justify-center gap-1 rounded-md bg-primary px-2 py-1 text-[11px] font-medium text-primary-foreground disabled:opacity-50", children: [dupBusy ? _jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : _jsx(Copy, { className: "h-3 w-3" }), "\u590D\u5236\u5E76\u8FDB\u5165\u6784\u5EFA\u5668"] }), _jsx("button", { type: "button", onClick: () => setDupFor(null), className: "rounded-md border px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted", children: "\u53D6\u6D88" })] })] }))] }, p.id))), creating ? (_jsxs("div", { className: "flex flex-col gap-1.5 rounded-lg border border-dashed bg-muted/20 px-3 py-2.5", children: [_jsx("input", { autoFocus: true, value: newName, onChange: (e) => {
|
|
111
|
+
}, placeholder: t('engine.studio.landing.dupNamePlaceholder', locale), className: "h-7 w-full rounded-md border bg-background px-2 text-[11px] outline-none focus:ring-1 focus:ring-primary" }), _jsx(PackageIdInput, { value: dupId, onChange: setDupId, onEnter: () => void doDup(), onEscape: () => setDupFor(null), placeholder: t('engine.studio.landing.dupIdPlaceholder', locale), locale: locale, testId: "pkg-dup-id-input" }), dupErr && _jsx("p", { className: "text-[10px] text-destructive", children: dupErr }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs("button", { type: "button", onClick: () => void doDup(), disabled: dupBusy || !dupName.trim() || !PACKAGE_ID_RE.test(dupId.trim()), className: "inline-flex flex-1 items-center justify-center gap-1 rounded-md bg-primary px-2 py-1 text-[11px] font-medium text-primary-foreground disabled:opacity-50", children: [dupBusy ? _jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : _jsx(Copy, { className: "h-3 w-3" }), t('engine.studio.landing.dupGo', locale)] }), _jsx("button", { type: "button", onClick: () => setDupFor(null), className: "rounded-md border px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted", children: t('engine.studio.cancel', locale) })] })] }))] }, p.id))), creating ? (_jsxs("div", { className: "flex flex-col gap-1.5 rounded-lg border border-dashed bg-muted/20 px-3 py-2.5", children: [_jsx("input", { autoFocus: true, value: newName, onChange: (e) => {
|
|
114
112
|
setNewName(e.target.value);
|
|
115
113
|
if (!idTouched) {
|
|
116
114
|
const slug = toFieldNameLoose(e.target.value).replace(/_/g, '-');
|
|
@@ -121,13 +119,8 @@ export function BuilderLanding() {
|
|
|
121
119
|
void doCreate();
|
|
122
120
|
if (e.key === 'Escape')
|
|
123
121
|
setCreating(false);
|
|
124
|
-
}, placeholder:
|
|
122
|
+
}, placeholder: t('engine.studio.pkg.namePlaceholder', locale), className: "h-7 w-full rounded-md border bg-background px-2 text-[11px] outline-none focus:ring-1 focus:ring-primary" }), _jsx(PackageIdSuggestionHint, { show: !idTouched && !!newName.trim() && !newId, locale: locale }), _jsx(PackageIdInput, { value: newId, onChange: (v) => {
|
|
125
123
|
setIdTouched(true);
|
|
126
|
-
setNewId(
|
|
127
|
-
},
|
|
128
|
-
if (e.key === 'Enter')
|
|
129
|
-
void doCreate();
|
|
130
|
-
if (e.key === 'Escape')
|
|
131
|
-
setCreating(false);
|
|
132
|
-
}, placeholder: "\u5305 ID(\u5982:com.example.repairs)", className: "h-7 w-full rounded-md border bg-background px-2 font-mono text-[11px] outline-none focus:ring-1 focus:ring-primary" }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs("button", { type: "button", onClick: () => void doCreate(), disabled: busy || !newName.trim() || !PACKAGE_ID_RE.test(newId.trim()), className: "inline-flex flex-1 items-center justify-center gap-1 rounded-md bg-primary px-2 py-1 text-[11px] font-medium text-primary-foreground disabled:opacity-50", children: [busy ? _jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : _jsx(Plus, { className: "h-3 w-3" }), "\u521B\u5EFA\u5E76\u5F00\u59CB\u6784\u5EFA"] }), _jsx("button", { type: "button", onClick: () => setCreating(false), className: "rounded-md border px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted", children: "\u53D6\u6D88" })] })] })) : (_jsxs("button", { type: "button", onClick: () => setCreating(true), className: "flex items-center justify-center gap-1.5 rounded-lg border border-dashed px-3 py-2.5 text-xs text-muted-foreground hover:border-primary/50 hover:text-foreground", children: [_jsx(Plus, { className: "h-4 w-4" }), " \u65B0\u5EFA\u8F6F\u4EF6\u5305"] }))] }), readonly.length > 0 && (_jsxs(_Fragment, { children: [_jsx("h2", { className: "mb-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "\u5DF2\u5B89\u88C5(\u53EA\u8BFB \u00B7 \u53EF\u6D4F\u89C8)" }), _jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: readonly.map((p) => (_jsxs("button", { type: "button", onClick: () => open(p.id), className: "flex items-center gap-2.5 rounded-lg border bg-muted/20 px-3 py-2.5 text-left hover:bg-muted/40", children: [_jsx(Boxes, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate text-[13px]", children: p.name }), _jsx("span", { className: "block truncate font-mono text-[10px] text-muted-foreground", children: p.id })] }), _jsxs("span", { className: "inline-flex items-center gap-0.5 rounded bg-amber-400/15 px-1.5 py-0.5 text-[10px] text-amber-600 dark:text-amber-300", children: [_jsx(Lock, { className: "h-2.5 w-2.5" }), " \u53EA\u8BFB"] })] }, p.id))) })] }))] }));
|
|
124
|
+
setNewId(v);
|
|
125
|
+
}, onEnter: () => void doCreate(), onEscape: () => setCreating(false), placeholder: t('engine.studio.pkg.idPlaceholder', locale), locale: locale, testId: "pkg-landing-id-input" }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs("button", { type: "button", onClick: () => void doCreate(), disabled: busy || !newName.trim() || !PACKAGE_ID_RE.test(newId.trim()), className: "inline-flex flex-1 items-center justify-center gap-1 rounded-md bg-primary px-2 py-1 text-[11px] font-medium text-primary-foreground disabled:opacity-50", children: [busy ? _jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : _jsx(Plus, { className: "h-3 w-3" }), t('engine.studio.landing.createGo', locale)] }), _jsx("button", { type: "button", onClick: () => setCreating(false), className: "rounded-md border px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted", children: t('engine.studio.cancel', locale) })] })] })) : (_jsxs("button", { type: "button", onClick: () => setCreating(true), className: "flex items-center justify-center gap-1.5 rounded-lg border border-dashed px-3 py-2.5 text-xs text-muted-foreground hover:border-primary/50 hover:text-foreground", children: [_jsx(Plus, { className: "h-4 w-4" }), " ", t('engine.studio.pkg.new', locale)] }))] }), readonly.length > 0 && (_jsxs(_Fragment, { children: [_jsx("h2", { className: "mb-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: t('engine.studio.landing.installedHeading', locale) }), _jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: readonly.map((p) => (_jsxs("button", { type: "button", onClick: () => open(p.id), className: "flex items-center gap-2.5 rounded-lg border bg-muted/20 px-3 py-2.5 text-left hover:bg-muted/40", children: [_jsx(Boxes, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate text-[13px]", children: p.name }), _jsx("span", { className: "block truncate font-mono text-[10px] text-muted-foreground", children: p.id })] }), _jsxs("span", { className: "inline-flex items-center gap-0.5 rounded bg-amber-400/15 px-1.5 py-0.5 text-[10px] text-amber-600 dark:text-amber-300", children: [_jsx(Lock, { className: "h-2.5 w-2.5" }), " ", t('engine.studio.pkg.readonly', locale)] })] }, p.id))) })] }))] }));
|
|
133
126
|
}
|
|
@@ -25,7 +25,9 @@ export interface ObjectFormDesignerProps {
|
|
|
25
25
|
selectedField?: string | null;
|
|
26
26
|
/** Select a field → opens the shared field inspector. */
|
|
27
27
|
onSelectField: (name: string) => void;
|
|
28
|
-
/** Append a new field (reuses the pillar's add-field). */
|
|
29
|
-
onAddField
|
|
28
|
+
/** Append a new field (reuses the pillar's add-field). Omit to hide the button — e.g. a read-only package. */
|
|
29
|
+
onAddField?: () => void;
|
|
30
|
+
/** Courtesy gate: layout stays viewable, but add/rename/reorder/delete are off. */
|
|
31
|
+
readOnly?: boolean;
|
|
30
32
|
}
|
|
31
|
-
export declare function ObjectFormDesigner({ draft, systemFieldNames, onChange, selectedField, onSelectField, onAddField, }: ObjectFormDesignerProps): React.ReactElement;
|
|
33
|
+
export declare function ObjectFormDesigner({ draft, systemFieldNames, onChange, selectedField, onSelectField, onAddField, readOnly, }: ObjectFormDesignerProps): React.ReactElement;
|
|
@@ -21,6 +21,7 @@ import { SortableContext, verticalListSortingStrategy, useSortable, arrayMove, s
|
|
|
21
21
|
import { CSS } from '@dnd-kit/utilities';
|
|
22
22
|
import { GripVertical, Plus, Trash2, ChevronUp, ChevronDown, Rows3 } from 'lucide-react';
|
|
23
23
|
import { readFields, writeFields, readGroups, addGroup, renameGroup, removeGroup, moveGroup, clearFieldGroup, } from '../metadata-admin/previews/object-fields-io';
|
|
24
|
+
import { t, tFormat, useMetadataLocale } from '../metadata-admin/i18n';
|
|
24
25
|
const UNGROUPED = '__ungrouped__';
|
|
25
26
|
const cid = (key) => `g:${key}`; // container (section) droppable id
|
|
26
27
|
const fid = (name) => `f:${name}`; // sortable field id
|
|
@@ -28,6 +29,7 @@ const unCid = (id) => id.slice(2);
|
|
|
28
29
|
const unFid = (id) => id.slice(2);
|
|
29
30
|
/** A faithful, non-interactive preview of a field's control (by type). */
|
|
30
31
|
function FieldControlPreview({ type }) {
|
|
32
|
+
const locale = useMetadataLocale();
|
|
31
33
|
const box = 'mt-1 flex items-center rounded-md border bg-muted/30 px-2 text-[11px] text-muted-foreground';
|
|
32
34
|
switch (type) {
|
|
33
35
|
case 'select':
|
|
@@ -36,7 +38,7 @@ function FieldControlPreview({ type }) {
|
|
|
36
38
|
case 'reference':
|
|
37
39
|
case 'user':
|
|
38
40
|
case 'multiselect':
|
|
39
|
-
return (_jsxs("div", { className: `${box} h-7 justify-between`, children: [_jsx("span", { children: type === 'lookup' || type === 'reference' || type === 'user' ? '
|
|
41
|
+
return (_jsxs("div", { className: `${box} h-7 justify-between`, children: [_jsx("span", { children: type === 'lookup' || type === 'reference' || type === 'user' ? t('engine.studio.designer.search', locale) : t('engine.studio.designer.select', locale) }), _jsx("span", { children: "\u25BE" })] }));
|
|
40
42
|
case 'textarea':
|
|
41
43
|
case 'html':
|
|
42
44
|
case 'markdown':
|
|
@@ -54,28 +56,30 @@ function FieldControlPreview({ type }) {
|
|
|
54
56
|
case 'date':
|
|
55
57
|
case 'datetime':
|
|
56
58
|
case 'time':
|
|
57
|
-
return _jsx("div", { className: `${box} h-7`, children:
|
|
59
|
+
return _jsx("div", { className: `${box} h-7`, children: t('engine.studio.designer.pickDate', locale) });
|
|
58
60
|
default:
|
|
59
61
|
return _jsx("div", { className: `${box} h-7`, children: "\u00A0" });
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
/** One draggable field card inside a section. */
|
|
63
65
|
function SortableField({ entry, selected, onSelect, }) {
|
|
66
|
+
const locale = useMetadataLocale();
|
|
64
67
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: fid(entry.name) });
|
|
65
68
|
const type = String(entry.def.type ?? 'text');
|
|
66
69
|
const label = String(entry.def.label ?? entry.name);
|
|
67
70
|
const required = !!entry.def.required;
|
|
68
|
-
return (_jsxs("div", { ref: setNodeRef, style: { transform: CSS.Transform.toString(transform), transition }, onClick: onSelect, ...attributes, ...listeners, "aria-label":
|
|
71
|
+
return (_jsxs("div", { ref: setNodeRef, style: { transform: CSS.Transform.toString(transform), transition }, onClick: onSelect, ...attributes, ...listeners, "aria-label": tFormat('engine.studio.designer.fieldAria', locale, { label }), className: 'group relative flex cursor-grab touch-none select-none items-start gap-1.5 rounded-md border bg-background px-2 py-2 active:cursor-grabbing ' +
|
|
69
72
|
(selected ? 'ring-2 ring-primary' : 'hover:border-foreground/25') +
|
|
70
73
|
(isDragging ? ' opacity-40' : ''), children: [_jsx("span", { className: "mt-0.5 text-muted-foreground opacity-0 group-hover:opacity-100", children: _jsx(GripVertical, { className: "h-3.5 w-3.5" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex items-center gap-1 text-xs font-medium", children: [_jsx("span", { className: "truncate", children: label }), required && _jsx("span", { className: "text-destructive", children: "*" }), _jsx("span", { className: "ml-1 rounded bg-muted px-1 py-px text-[9px] uppercase text-muted-foreground", children: type })] }), _jsx(FieldControlPreview, { type: type })] })] }));
|
|
71
74
|
}
|
|
72
75
|
/** A section (declared group or the implicit ungrouped bucket) = a drop zone. */
|
|
73
|
-
function Section({ containerId, title, fieldIds, isDeclared, canMoveUp, canMoveDown, entryByName, selectedField, onSelectField, onRename, onDelete, onMove, }) {
|
|
76
|
+
function Section({ containerId, title, fieldIds, isDeclared, canMoveUp, canMoveDown, entryByName, selectedField, onSelectField, onRename, onDelete, onMove, readOnly = false, }) {
|
|
77
|
+
const locale = useMetadataLocale();
|
|
74
78
|
const { setNodeRef, isOver } = useDroppable({ id: containerId });
|
|
75
|
-
return (_jsxs("div", { className: 'rounded-lg border ' + (isOver ? 'border-primary bg-primary/5' : 'bg-muted/20'), children: [_jsxs("div", { className: "flex items-center gap-1 border-b px-3 py-1.5", children: [isDeclared ? (_jsx("input", { defaultValue: title, onBlur: (e) => e.target.value.trim() && e.target.value !== title && onRename(e.target.value), onKeyDown: (e) => {
|
|
79
|
+
return (_jsxs("div", { className: 'rounded-lg border ' + (isOver ? 'border-primary bg-primary/5' : 'bg-muted/20'), children: [_jsxs("div", { className: "flex items-center gap-1 border-b px-3 py-1.5", children: [isDeclared && !readOnly ? (_jsx("input", { defaultValue: title, onBlur: (e) => e.target.value.trim() && e.target.value !== title && onRename(e.target.value), onKeyDown: (e) => {
|
|
76
80
|
if (e.key === 'Enter')
|
|
77
81
|
e.target.blur();
|
|
78
|
-
}, className: "min-w-0 flex-1 rounded bg-transparent px-1 py-0.5 text-[13px] font-medium outline-none hover:bg-muted focus:bg-background focus:ring-1 focus:ring-primary" })) : (_jsx("span", { className: "flex-1 px-1 text-[13px] font-medium text-muted-foreground", children: title })), isDeclared && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", disabled: !canMoveUp, onClick: () => onMove(-1), "aria-label":
|
|
82
|
+
}, className: "min-w-0 flex-1 rounded bg-transparent px-1 py-0.5 text-[13px] font-medium outline-none hover:bg-muted focus:bg-background focus:ring-1 focus:ring-primary" })) : (_jsx("span", { className: "flex-1 px-1 text-[13px] font-medium text-muted-foreground", children: title })), isDeclared && !readOnly && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", disabled: !canMoveUp, onClick: () => onMove(-1), "aria-label": t('engine.studio.designer.groupUp', locale), className: "rounded p-0.5 text-muted-foreground hover:bg-muted disabled:opacity-30", children: _jsx(ChevronUp, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", disabled: !canMoveDown, onClick: () => onMove(1), "aria-label": t('engine.studio.designer.groupDown', locale), className: "rounded p-0.5 text-muted-foreground hover:bg-muted disabled:opacity-30", children: _jsx(ChevronDown, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", onClick: onDelete, "aria-label": t('engine.studio.designer.groupDelete', locale), title: t('engine.studio.designer.groupDeleteTitle', locale), className: "rounded p-0.5 text-muted-foreground hover:bg-destructive/10 hover:text-destructive", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) })] }))] }), _jsx(SortableContext, { items: fieldIds, strategy: verticalListSortingStrategy, children: _jsxs("div", { ref: setNodeRef, className: "grid min-h-[52px] grid-cols-2 gap-2 p-2.5", children: [fieldIds.length === 0 && (_jsx("div", { className: "col-span-2 flex items-center justify-center rounded-md border border-dashed py-3 text-[11px] text-muted-foreground", children: t('engine.studio.designer.dropHere', locale) })), fieldIds.map((id) => {
|
|
79
83
|
const name = unFid(id);
|
|
80
84
|
const entry = entryByName.get(name);
|
|
81
85
|
if (!entry)
|
|
@@ -83,7 +87,8 @@ function Section({ containerId, title, fieldIds, isDeclared, canMoveUp, canMoveD
|
|
|
83
87
|
return (_jsx(SortableField, { entry: entry, selected: selectedField === name, onSelect: () => onSelectField(name) }, id));
|
|
84
88
|
})] }) })] }));
|
|
85
89
|
}
|
|
86
|
-
export function ObjectFormDesigner({ draft, systemFieldNames, onChange, selectedField, onSelectField, onAddField, }) {
|
|
90
|
+
export function ObjectFormDesigner({ draft, systemFieldNames, onChange, selectedField, onSelectField, onAddField, readOnly = false, }) {
|
|
91
|
+
const locale = useMetadataLocale();
|
|
87
92
|
const view = React.useMemo(() => readFields(draft.fields), [draft.fields]);
|
|
88
93
|
const groups = React.useMemo(() => readGroups(draft.fieldGroups), [draft.fieldGroups]);
|
|
89
94
|
const entryByName = React.useMemo(() => new Map(view.entries.map((e) => [e.name, e])), [view]);
|
|
@@ -93,9 +98,9 @@ export function ObjectFormDesigner({ draft, systemFieldNames, onChange, selected
|
|
|
93
98
|
const m = new Map();
|
|
94
99
|
for (const g of groups)
|
|
95
100
|
m.set(cid(g.key), g.label || g.key);
|
|
96
|
-
m.set(cid(UNGROUPED), '
|
|
101
|
+
m.set(cid(UNGROUPED), t('engine.studio.designer.ungrouped', locale));
|
|
97
102
|
return m;
|
|
98
|
-
}, [groups]);
|
|
103
|
+
}, [groups, locale]);
|
|
99
104
|
// Derive container → ordered field ids from the draft (editable fields only;
|
|
100
105
|
// system/audit fields are preserved on write but never shown in the layout).
|
|
101
106
|
const derived = React.useMemo(() => {
|
|
@@ -210,17 +215,17 @@ export function ObjectFormDesigner({ draft, systemFieldNames, onChange, selected
|
|
|
210
215
|
setItems(next);
|
|
211
216
|
commit(next);
|
|
212
217
|
};
|
|
213
|
-
const addSection = () => onChange({ fieldGroups: addGroup(groups, '
|
|
218
|
+
const addSection = () => onChange({ fieldGroups: addGroup(groups, t('engine.studio.designer.newGroup', locale)) });
|
|
214
219
|
const renameSection = (key, label) => onChange({ fieldGroups: renameGroup(groups, key, label) });
|
|
215
220
|
const moveSection = (key, dir) => onChange({ fieldGroups: moveGroup(groups, key, dir) });
|
|
216
221
|
const deleteSection = (key) => onChange({ fieldGroups: removeGroup(groups, key), fields: writeFields(clearFieldGroup(view, key)) });
|
|
217
222
|
const activeEntry = activeId && !activeId.startsWith('g:') ? entryByName.get(unFid(activeId)) : null;
|
|
218
|
-
return (_jsxs("div", { className: "min-h-0 flex-1 overflow-auto rounded-lg border bg-background p-4", children: [_jsxs("div", { className: "mb-3 flex items-center gap-2", children: [_jsxs("span", { className: "inline-flex items-center gap-1 text-[11px] text-muted-foreground", children: [_jsx(Rows3, { className: "h-3.5 w-3.5" }), "
|
|
223
|
+
return (_jsxs("div", { className: "min-h-0 flex-1 overflow-auto rounded-lg border bg-background p-4", children: [_jsxs("div", { className: "mb-3 flex items-center gap-2", children: [_jsxs("span", { className: "inline-flex items-center gap-1 text-[11px] text-muted-foreground", children: [_jsx(Rows3, { className: "h-3.5 w-3.5" }), " ", t('engine.studio.designer.hint', locale)] }), !readOnly && (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", onClick: addSection, className: "ml-auto inline-flex items-center gap-1 rounded-md border px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), " ", t('engine.studio.designer.addGroup', locale)] }), onAddField && (_jsxs("button", { type: "button", onClick: onAddField, className: "inline-flex items-center gap-1 rounded-md border px-2 py-1 text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground", children: [_jsx(Plus, { className: "h-3.5 w-3.5" }), " ", t('engine.studio.data.addField', locale)] }))] }))] }), _jsxs(DndContext, { sensors: readOnly ? [] : sensors, collisionDetection: pointerWithin, onDragStart: onDragStart, onDragOver: onDragOver, onDragEnd: onDragEnd, onDragCancel: () => setActiveId(null), children: [_jsx("div", { className: "mx-auto flex max-w-3xl flex-col gap-3", children: containerOrder.map((c) => {
|
|
219
224
|
const isUngrouped = c === cid(UNGROUPED);
|
|
220
225
|
// Hide the ungrouped bucket only when it is empty AND groups exist.
|
|
221
226
|
if (isUngrouped && (items[c]?.length ?? 0) === 0 && groups.length > 0)
|
|
222
227
|
return null;
|
|
223
228
|
const declaredIdx = groups.findIndex((g) => cid(g.key) === c);
|
|
224
|
-
return (_jsx(Section, { containerId: c, title: labelOf.get(c) ?? '
|
|
229
|
+
return (_jsx(Section, { containerId: c, title: labelOf.get(c) ?? t('engine.studio.designer.ungrouped', locale), fieldIds: items[c] ?? [], isDeclared: !isUngrouped, canMoveUp: declaredIdx > 0, canMoveDown: declaredIdx >= 0 && declaredIdx < groups.length - 1, entryByName: entryByName, selectedField: selectedField, onSelectField: onSelectField, onRename: (label) => renameSection(unCid(c), label), onDelete: () => deleteSection(unCid(c)), onMove: (dir) => moveSection(unCid(c), dir), readOnly: readOnly }, c));
|
|
225
230
|
}) }), _jsx(DragOverlay, { children: activeEntry ? (_jsxs("div", { className: "flex items-center gap-1.5 rounded-md border bg-background px-2 py-2 shadow-lg", children: [_jsx(GripVertical, { className: "h-3.5 w-3.5 text-muted-foreground" }), _jsx("span", { className: "text-xs font-medium", children: String(activeEntry.def.label ?? activeEntry.name) })] })) : null })] })] }));
|
|
226
231
|
}
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* back to guessing which heuristic picked their title/stepper/columns.
|
|
21
21
|
*/
|
|
22
22
|
import React from 'react';
|
|
23
|
-
import type
|
|
23
|
+
import { type SupportedLocale } from '../metadata-admin/i18n';
|
|
24
24
|
export declare function ObjectSettingsPanel({ name, draft, onPatch, disabled, locale, }: {
|
|
25
25
|
name: string;
|
|
26
26
|
draft: Record<string, unknown>;
|
|
@@ -24,6 +24,7 @@ import React from 'react';
|
|
|
24
24
|
import { Settings2, Sparkles, X } from 'lucide-react';
|
|
25
25
|
import { getMetadataDefaultInspector } from '../metadata-admin/default-inspector-registry';
|
|
26
26
|
import { readFields } from '../metadata-admin/previews/object-fields-io';
|
|
27
|
+
import { t, tFormat } from '../metadata-admin/i18n';
|
|
27
28
|
export function ObjectSettingsPanel({ name, draft, onPatch, disabled, locale, }) {
|
|
28
29
|
const DefaultInspector = getMetadataDefaultInspector('object');
|
|
29
30
|
const fields = React.useMemo(() => readFields(draft.fields).entries, [draft.fields]);
|
|
@@ -34,12 +35,12 @@ export function ObjectSettingsPanel({ name, draft, onPatch, disabled, locale, })
|
|
|
34
35
|
? draft.highlightFields.filter((f) => typeof f === 'string')
|
|
35
36
|
: [];
|
|
36
37
|
const highlightCandidates = fields.filter((e) => e.def.hidden !== true && !highlightFields.includes(e.name));
|
|
37
|
-
return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-4 overflow-auto", children: [_jsxs("section", { className: "rounded-lg border", children: [_jsxs("header", { className: "flex items-center gap-2 border-b px-3 py-2", children: [_jsx(Settings2, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "text-[13px] font-medium", children:
|
|
38
|
+
return (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-4 overflow-auto", children: [_jsxs("section", { className: "rounded-lg border", children: [_jsxs("header", { className: "flex items-center gap-2 border-b px-3 py-2", children: [_jsx(Settings2, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "text-[13px] font-medium", children: t('engine.studio.settings.basics', locale) })] }), _jsx("div", { className: "max-w-xl p-3", children: DefaultInspector ? (_jsx(DefaultInspector, { type: "object", name: name, draft: draft, onPatch: onPatch, readOnly: !!disabled, locale: locale })) : (_jsx("p", { className: "text-[12px] text-muted-foreground", children: t('engine.studio.settings.noInspector', locale) })) })] }), _jsxs("section", { className: "rounded-lg border", children: [_jsxs("header", { className: "flex items-center gap-2 border-b px-3 py-2", children: [_jsx(Sparkles, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "text-[13px] font-medium", children: t('engine.studio.settings.semanticRoles', locale) }), _jsx("span", { className: "text-[11px] text-muted-foreground", children: t('engine.studio.settings.semanticHint', locale) })] }), _jsxs("div", { className: "grid max-w-xl gap-4 p-3", children: [_jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: t('engine.studio.settings.nameField', locale) }), _jsxs("select", { value: nameField, disabled: disabled, onChange: (e) => onPatch(e.target.value ? { nameField: e.target.value } : { nameField: undefined }), className: "w-full rounded border bg-background px-2 py-1 text-[12px]", children: [_jsx("option", { value: "", children: t('engine.studio.settings.autoDerive', locale) }), fields.map((e) => (_jsx("option", { value: e.name, children: typeof e.def.label === 'string' ? `${e.def.label} (${e.name})` : e.name }, e.name)))] })] }), _jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: t('engine.studio.settings.stageField', locale) }), _jsxs("select", { value: stageField === false ? '__none__' : (stageField ?? ''), disabled: disabled, onChange: (e) => {
|
|
38
39
|
const v = e.target.value;
|
|
39
40
|
onPatch({ stageField: v === '__none__' ? false : v === '' ? undefined : v });
|
|
40
|
-
}, className: "w-full rounded border bg-background px-2 py-1 text-[12px]", children: [_jsx("option", { value: "", children:
|
|
41
|
+
}, className: "w-full rounded border bg-background px-2 py-1 text-[12px]", children: [_jsx("option", { value: "", children: t('engine.studio.settings.autoDetect', locale) }), _jsx("option", { value: "__none__", children: t('engine.studio.settings.stageNone', locale) }), selectFields.map((e) => (_jsx("option", { value: e.name, children: typeof e.def.label === 'string' ? `${e.def.label} (${e.name})` : e.name }, e.name)))] })] }), _jsxs("div", { children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: t('engine.studio.settings.highlightFields', locale) }), _jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [highlightFields.map((f) => (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-primary/10 px-2 py-0.5 text-[11px] text-primary", children: [f, !disabled && (_jsx("button", { type: "button", "aria-label": tFormat('engine.studio.settings.removeField', locale, { field: f }), onClick: () => onPatch({ highlightFields: highlightFields.filter((x) => x !== f) }), className: "rounded-full hover:bg-primary/20", children: _jsx(X, { className: "h-3 w-3" }) }))] }, f))), !disabled && highlightCandidates.length > 0 && (_jsxs("select", { value: "", onChange: (e) => {
|
|
41
42
|
if (!e.target.value)
|
|
42
43
|
return;
|
|
43
44
|
onPatch({ highlightFields: [...highlightFields, e.target.value] });
|
|
44
|
-
}, className: "rounded border bg-background px-1.5 py-0.5 text-[11px] text-muted-foreground", children: [_jsx("option", { value: "", children:
|
|
45
|
+
}, className: "rounded border bg-background px-1.5 py-0.5 text-[11px] text-muted-foreground", children: [_jsx("option", { value: "", children: t('engine.studio.settings.addFieldOption', locale) }), highlightCandidates.map((e) => (_jsx("option", { value: e.name, children: typeof e.def.label === 'string' ? `${e.def.label} (${e.name})` : e.name }, e.name)))] })), highlightFields.length === 0 && (_jsx("span", { className: "text-[11px] text-muted-foreground", children: t('engine.studio.settings.undeclared', locale) }))] })] })] })] })] }));
|
|
45
46
|
}
|
|
@@ -27,6 +27,7 @@ import React from 'react';
|
|
|
27
27
|
import { Plus, Trash2, ShieldAlert } from 'lucide-react';
|
|
28
28
|
import { ConditionBuilder } from '../metadata-admin/inspectors/ConditionBuilder';
|
|
29
29
|
import { readFields } from '../metadata-admin/previews/object-fields-io';
|
|
30
|
+
import { t, tFormat, useMetadataLocale } from '../metadata-admin/i18n';
|
|
30
31
|
function readRules(input) {
|
|
31
32
|
if (!Array.isArray(input))
|
|
32
33
|
return [];
|
|
@@ -41,6 +42,7 @@ function nextRuleName(existing) {
|
|
|
41
42
|
return name;
|
|
42
43
|
}
|
|
43
44
|
export function ObjectValidationsPanel({ draft, onPatch, disabled, }) {
|
|
45
|
+
const locale = useMetadataLocale();
|
|
44
46
|
const rules = readRules(draft.validations);
|
|
45
47
|
const [selected, setSelected] = React.useState(null);
|
|
46
48
|
const fields = React.useMemo(() => readFields(draft.fields).entries.map((e) => ({
|
|
@@ -66,13 +68,13 @@ export function ObjectValidationsPanel({ draft, onPatch, disabled, }) {
|
|
|
66
68
|
setSelected(null);
|
|
67
69
|
};
|
|
68
70
|
const sel = rules.find((r) => r.name === selected) ?? null;
|
|
69
|
-
return (_jsxs("div", { className: "flex min-h-0 flex-1 gap-4", children: [_jsxs("div", { className: "flex w-72 shrink-0 flex-col rounded-lg border", children: [_jsxs("header", { className: "flex items-center gap-2 border-b px-3 py-2", children: [_jsx(ShieldAlert, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "text-[13px] font-medium", children:
|
|
70
|
-
(selected === r.name ? 'bg-muted' : 'hover:bg-muted/50'), children: [_jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate font-medium", children: r.label || r.name }), _jsx("span", { className: "block truncate text-[11px] text-muted-foreground", children: r.message ||
|
|
71
|
+
return (_jsxs("div", { className: "flex min-h-0 flex-1 gap-4", children: [_jsxs("div", { className: "flex w-72 shrink-0 flex-col rounded-lg border", children: [_jsxs("header", { className: "flex items-center gap-2 border-b px-3 py-2", children: [_jsx(ShieldAlert, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "text-[13px] font-medium", children: t('engine.studio.rules.title', locale) }), _jsxs("span", { className: "text-[11px] text-muted-foreground", children: ["(", rules.length, ")"] }), !disabled && (_jsxs("button", { type: "button", onClick: addRule, className: "ml-auto inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-[11px] hover:bg-muted", children: [_jsx(Plus, { className: "h-3 w-3" }), " ", t('engine.studio.new', locale)] }))] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto", children: rules.length === 0 ? (_jsxs("p", { className: "px-3 py-6 text-center text-[11px] leading-5 text-muted-foreground", children: [t('engine.studio.rules.none', locale), _jsx("br", {}), t('engine.studio.rules.explain', locale)] })) : (rules.map((r) => (_jsxs("button", { type: "button", onClick: () => setSelected(r.name ?? null), className: 'flex w-full items-start gap-2 border-b px-3 py-2 text-left text-[12px] ' +
|
|
72
|
+
(selected === r.name ? 'bg-muted' : 'hover:bg-muted/50'), children: [_jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "block truncate font-medium", children: r.label || r.name }), _jsx("span", { className: "block truncate text-[11px] text-muted-foreground", children: r.message || t('engine.studio.rules.noMessage', locale) })] }), _jsx("span", { className: 'shrink-0 rounded-full px-1.5 py-0.5 text-[10px] ' +
|
|
71
73
|
(r.type === 'script'
|
|
72
74
|
? 'bg-primary/10 text-primary'
|
|
73
|
-
: 'bg-muted text-muted-foreground'), children: r.type ?? 'script' })] }, r.name)))) })] }), _jsx("div", { className: "flex min-w-0 flex-1 flex-col rounded-lg border", children: !sel ? (_jsx("div", { className: "flex flex-1 items-center justify-center p-6 text-center text-[12px] text-muted-foreground", children:
|
|
75
|
+
: 'bg-muted text-muted-foreground'), children: r.type ?? 'script' })] }, r.name)))) })] }), _jsx("div", { className: "flex min-w-0 flex-1 flex-col rounded-lg border", children: !sel ? (_jsx("div", { className: "flex flex-1 items-center justify-center p-6 text-center text-[12px] text-muted-foreground", children: t('engine.studio.rules.pick', locale) })) : sel.type !== 'script' ? (_jsxs("div", { className: "flex flex-1 flex-col gap-2 p-4", children: [_jsxs("p", { className: "text-[13px] font-medium", children: [sel.label || sel.name, _jsx("span", { className: "ml-2 rounded-full bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground", children: sel.type })] }), _jsx("p", { className: "text-[12px] leading-5 text-muted-foreground", children: tFormat('engine.studio.rules.structured', locale, { type: String(sel.type), message: sel.message || t('engine.studio.rules.none2', locale) }) }), !disabled && (_jsxs("button", { type: "button", onClick: () => removeRule(sel.name), className: "mt-auto inline-flex w-fit items-center gap-1 rounded border border-destructive/40 px-2 py-1 text-[11px] text-destructive hover:bg-destructive/10", children: [_jsx(Trash2, { className: "h-3 w-3" }), " ", t('engine.studio.rules.deleteRule', locale)] }))] })) : (_jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-3 overflow-auto p-4", children: [_jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: t('engine.studio.rules.nameLabel', locale) }), _jsx("input", { value: sel.name ?? '', disabled: disabled, onChange: (e) => {
|
|
74
76
|
const name = e.target.value;
|
|
75
77
|
patchRule(sel.name, { name });
|
|
76
78
|
setSelected(name);
|
|
77
|
-
}, className: "w-full rounded border bg-background px-2 py-1 text-[12px]" })] }), _jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children:
|
|
79
|
+
}, className: "w-full rounded border bg-background px-2 py-1 text-[12px]" })] }), _jsxs("label", { className: "block", children: [_jsx("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: t('engine.studio.rules.messageLabel', locale) }), _jsx("input", { value: sel.message ?? '', disabled: disabled, onChange: (e) => patchRule(sel.name, { message: e.target.value }), placeholder: t('engine.studio.rules.messagePlaceholder', locale), className: "w-full rounded border bg-background px-2 py-1 text-[12px]" })] }), _jsxs("div", { children: [_jsxs("span", { className: "mb-1 block text-[11px] text-muted-foreground", children: [t('engine.studio.rules.celPre', locale), _jsx("b", { children: t('engine.studio.rules.celTrue', locale) }), t('engine.studio.rules.celMid', locale), _jsx("code", { className: "rounded bg-muted px-1", children: "false" }), t('engine.studio.rules.celPost', locale)] }), _jsx(ConditionBuilder, { value: typeof sel.condition === 'string' ? sel.condition : '', onCommit: (cel) => patchRule(sel.name, { condition: cel }), fields: fields, disabled: disabled })] }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("label", { className: "flex items-center gap-1.5 text-[12px]", children: [_jsx("span", { className: "text-muted-foreground", children: t('engine.studio.rules.severity', locale) }), _jsxs("select", { value: sel.severity ?? 'error', disabled: disabled, onChange: (e) => patchRule(sel.name, { severity: e.target.value }), className: "rounded border bg-background px-1.5 py-0.5 text-[12px]", children: [_jsx("option", { value: "error", children: t('engine.studio.rules.severityError', locale) }), _jsx("option", { value: "warning", children: "warning" }), _jsx("option", { value: "info", children: "info" })] })] }), _jsxs("label", { className: "flex items-center gap-1.5 text-[12px]", children: [_jsx("input", { type: "checkbox", checked: sel.active !== false, disabled: disabled, onChange: (e) => patchRule(sel.name, { active: e.target.checked }) }), t('engine.studio.rules.enabled', locale)] }), !disabled && (_jsxs("button", { type: "button", onClick: () => removeRule(sel.name), className: "ml-auto inline-flex items-center gap-1 rounded border border-destructive/40 px-2 py-1 text-[11px] text-destructive hover:bg-destructive/10", children: [_jsx(Trash2, { className: "h-3 w-3" }), " ", t('engine.studio.rules.delete', locale)] }))] })] })) })] }));
|
|
78
80
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-identifier input shared by the three package wizards (switcher
|
|
3
|
+
* create, landing create, landing duplicate). Fixes the dogfood wizard
|
|
4
|
+
* findings (framework#2615 P2): illegal characters are still normalized
|
|
5
|
+
* away, but no longer silently — a notice says so — and while the value
|
|
6
|
+
* doesn't parse as a package id yet, an inline hint spells out the
|
|
7
|
+
* reverse-domain format instead of leaving the user staring at a disabled
|
|
8
|
+
* create button.
|
|
9
|
+
*/
|
|
10
|
+
import * as React from 'react';
|
|
11
|
+
export interface PackageIdInputProps {
|
|
12
|
+
value: string;
|
|
13
|
+
/** Receives the sanitized value on every keystroke. */
|
|
14
|
+
onChange: (value: string) => void;
|
|
15
|
+
onEnter?: () => void;
|
|
16
|
+
onEscape?: () => void;
|
|
17
|
+
placeholder?: string;
|
|
18
|
+
autoFocus?: boolean;
|
|
19
|
+
locale?: string;
|
|
20
|
+
testId?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function PackageIdInput({ value, onChange, onEnter, onEscape, placeholder, autoFocus, locale, testId, }: PackageIdInputProps): React.ReactElement;
|
|
23
|
+
/**
|
|
24
|
+
* Hint under the display-name input when the name yields no identifier
|
|
25
|
+
* suggestion (CJK-only names slug to nothing) — say the id must be typed
|
|
26
|
+
* manually instead of leaving the id box silently empty.
|
|
27
|
+
*/
|
|
28
|
+
export declare function PackageIdSuggestionHint({ show, locale, }: {
|
|
29
|
+
show: boolean;
|
|
30
|
+
locale?: string;
|
|
31
|
+
}): React.ReactElement | null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
3
|
+
/**
|
|
4
|
+
* Package-identifier input shared by the three package wizards (switcher
|
|
5
|
+
* create, landing create, landing duplicate). Fixes the dogfood wizard
|
|
6
|
+
* findings (framework#2615 P2): illegal characters are still normalized
|
|
7
|
+
* away, but no longer silently — a notice says so — and while the value
|
|
8
|
+
* doesn't parse as a package id yet, an inline hint spells out the
|
|
9
|
+
* reverse-domain format instead of leaving the user staring at a disabled
|
|
10
|
+
* create button.
|
|
11
|
+
*/
|
|
12
|
+
import * as React from 'react';
|
|
13
|
+
import { PACKAGE_ID_RE, sanitizePackageId } from './packages-io';
|
|
14
|
+
import { t } from '../metadata-admin/i18n';
|
|
15
|
+
export function PackageIdInput({ value, onChange, onEnter, onEscape, placeholder, autoFocus, locale, testId, }) {
|
|
16
|
+
// "I typed something and it vanished" — show what was dropped until the
|
|
17
|
+
// next clean keystroke.
|
|
18
|
+
const [strippedNotice, setStrippedNotice] = React.useState(false);
|
|
19
|
+
const invalid = value.trim().length > 0 && !PACKAGE_ID_RE.test(value.trim());
|
|
20
|
+
return (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("input", { autoFocus: autoFocus, value: value, onChange: (e) => {
|
|
21
|
+
const { value: next, stripped } = sanitizePackageId(e.target.value);
|
|
22
|
+
setStrippedNotice(stripped);
|
|
23
|
+
onChange(next);
|
|
24
|
+
}, onKeyDown: (e) => {
|
|
25
|
+
if (e.key === 'Enter')
|
|
26
|
+
onEnter?.();
|
|
27
|
+
if (e.key === 'Escape')
|
|
28
|
+
onEscape?.();
|
|
29
|
+
}, placeholder: placeholder, "data-testid": testId, className: "h-7 w-full rounded-md border bg-background px-2 font-mono text-[11px] outline-none focus:ring-1 focus:ring-primary" }), strippedNotice && (_jsx("p", { className: "text-[10px] text-amber-600 dark:text-amber-400", "data-testid": "pkg-id-stripped", children: t('engine.studio.pkg.idStrippedNotice', locale) })), invalid && (_jsx("p", { className: "text-[10px] text-muted-foreground", "data-testid": "pkg-id-format-hint", children: t('engine.studio.pkg.idFormatHint', locale) }))] }));
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Hint under the display-name input when the name yields no identifier
|
|
33
|
+
* suggestion (CJK-only names slug to nothing) — say the id must be typed
|
|
34
|
+
* manually instead of leaving the id box silently empty.
|
|
35
|
+
*/
|
|
36
|
+
export function PackageIdSuggestionHint({ show, locale, }) {
|
|
37
|
+
if (!show)
|
|
38
|
+
return null;
|
|
39
|
+
return (_jsx("p", { className: "text-[10px] text-muted-foreground", "data-testid": "pkg-id-manual-hint", children: t('engine.studio.pkg.idFromNameUnavailable', locale) }));
|
|
40
|
+
}
|
|
@@ -17,4 +17,17 @@ export interface StudioDesignSurfaceProps {
|
|
|
17
17
|
aiSlot?: React.ReactNode;
|
|
18
18
|
}
|
|
19
19
|
export declare function StudioDesignSurface({ aiSlot }: StudioDesignSurfaceProps): React.ReactElement;
|
|
20
|
+
/**
|
|
21
|
+
* A flow's live status in the Automations rail: a colored dot + On/Off, from the
|
|
22
|
+
* engine's runtime state (persisted `status` is intent; this is what's actually
|
|
23
|
+
* live). Renders nothing for a flow the engine doesn't know yet (never published)
|
|
24
|
+
* — the amber "unpublished draft" chip already covers that case.
|
|
25
|
+
*/
|
|
26
|
+
export declare function FlowStatusDot({ state, locale }: {
|
|
27
|
+
state?: {
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
bound: boolean;
|
|
30
|
+
};
|
|
31
|
+
locale: string;
|
|
32
|
+
}): React.ReactElement | null;
|
|
20
33
|
export default StudioDesignSurface;
|