@object-ui/app-shell 11.2.0 → 11.3.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.
@@ -0,0 +1,659 @@
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
+ * StudioDesignSurface — the open-source WYSIWYG design surface (ADR-0080).
5
+ *
6
+ * Routed as /studio/:packageId/{data|automations|interfaces} — three pillars,
7
+ * each composed AROUND existing renderers (no new editor code):
8
+ * - Interfaces: the real App navigation tree → live canvas (getMetadataPreview)
9
+ * + inspector (getMetadataInspector), edits persisting via draft → publish.
10
+ * - Data: the package's objects → fields + record grid.
11
+ * - Automations: flows → FlowPreview (default OFF / review-then-enable).
12
+ *
13
+ * Open-core boundary: the left AI copilot is NOT part of the open-source
14
+ * surface — it is an injected slot (`aiSlot`) the cloud edition fills.
15
+ */
16
+ import * as React from 'react';
17
+ import { useParams, Link } from 'react-router-dom';
18
+ import { SchemaRenderer, useAdapter, SchemaRendererProvider } from '@object-ui/react';
19
+ import { GridFieldAuthoringProvider } from '@object-ui/components';
20
+ import { ObjectView as PluginObjectView } from '@object-ui/plugin-view';
21
+ import { ListView } from '@object-ui/plugin-list';
22
+ import { Boxes, FileText, Database, LayoutDashboard, BarChart3, Table2, Folder, Compass, Workflow, SlidersHorizontal, MousePointer2, Eye, Loader2, Save, Pencil, Check, Plus, X, } from 'lucide-react';
23
+ import { getMetadataPreview } from '../metadata-admin/preview-registry';
24
+ import { getMetadataInspector } from '../metadata-admin/inspector-registry';
25
+ import { useMetadataClient } from '../metadata-admin/useMetadata';
26
+ import { AppNavCanvas } from '../metadata-admin/previews/AppNavCanvas';
27
+ import { readFields, writeFields, newField } from '../metadata-admin/previews/object-fields-io';
28
+ const PILLARS = [
29
+ { key: 'data', label: 'Data', Icon: Database },
30
+ { key: 'automations', label: 'Automations', Icon: Workflow },
31
+ { key: 'interfaces', label: 'Interfaces', Icon: LayoutDashboard },
32
+ ];
33
+ const KIND_ICON = {
34
+ group: Folder,
35
+ page: FileText,
36
+ object: Database,
37
+ dashboard: LayoutDashboard,
38
+ report: BarChart3,
39
+ view: Table2,
40
+ };
41
+ const navIcon = (type) => KIND_ICON[type ?? ''] ?? Compass;
42
+ /** Resolve a leaf nav node → the surface {type,name} it binds to. */
43
+ function resolveSurface(node) {
44
+ const label = String(node.label ?? '');
45
+ switch (node.type) {
46
+ case 'page':
47
+ return node.pageName || node.page ? { type: 'page', name: String(node.pageName || node.page), label } : null;
48
+ case 'object':
49
+ return node.objectName || node.object
50
+ ? { type: 'object', name: String(node.objectName || node.object), label }
51
+ : null;
52
+ case 'dashboard':
53
+ return node.dashboardName || node.dashboard
54
+ ? { type: 'dashboard', name: String(node.dashboardName || node.dashboard), label }
55
+ : null;
56
+ case 'report':
57
+ return node.reportName || node.report
58
+ ? { type: 'report', name: String(node.reportName || node.report), label }
59
+ : null;
60
+ case 'view':
61
+ return node.viewName || node.view ? { type: 'view', name: String(node.viewName || node.view), label } : null;
62
+ default:
63
+ return null;
64
+ }
65
+ }
66
+ /** Normalize the framework draft envelope `{ type, name, item }` → body | null. */
67
+ function extractDraftBody(resp) {
68
+ if (!resp || typeof resp !== 'object')
69
+ return null;
70
+ const env = resp;
71
+ if (!('item' in env))
72
+ return null;
73
+ const body = env.item;
74
+ if (!body || typeof body !== 'object')
75
+ return null;
76
+ return Object.keys(body).length > 0 ? body : null;
77
+ }
78
+ export function StudioDesignSurface({ aiSlot }) {
79
+ const params = useParams();
80
+ const packageId = params.packageId ?? 'com.example.showcase';
81
+ const tab = params.tab ?? 'interfaces';
82
+ return (_jsxs("div", { className: "flex h-screen w-full overflow-hidden bg-background text-foreground", children: [aiSlot ? _jsx("aside", { className: "w-64 shrink-0 overflow-auto border-r bg-muted/40", children: aiSlot }) : null, _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsxs("header", { className: "flex items-center gap-3 border-b px-3 py-2", children: [_jsxs("span", { className: "flex items-center gap-1.5 whitespace-nowrap text-[13px] font-medium", children: [_jsx(Boxes, { className: "h-4 w-4" }), " ", packageId] }), _jsx("span", { className: "text-muted-foreground", children: "\u00B7" }), _jsx("nav", { className: "flex gap-1", children: PILLARS.map((p) => (_jsxs(Link, { to: `/studio/${packageId}/${p.key}`, className: 'inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs transition-colors ' +
83
+ (tab === p.key
84
+ ? 'bg-primary/10 font-medium text-primary'
85
+ : 'text-muted-foreground hover:bg-muted hover:text-foreground'), children: [_jsx(p.Icon, { className: "h-3.5 w-3.5" }), p.label] }, p.key))) })] }), _jsx("div", { className: "min-h-0 flex-1", children: tab === 'data' ? (_jsx(DataPillar, { packageId: packageId })) : tab === 'automations' ? (_jsx(AutomationsPillar, { packageId: packageId })) : (_jsx(InterfacesPillar, { packageId: packageId })) })] })] }));
86
+ }
87
+ /** Recursive App-navigation tree (groups + typed leaves). */
88
+ function NavTree({ nodes, active, onPick, }) {
89
+ return (_jsx(_Fragment, { children: nodes.map((node, i) => {
90
+ if (node.type === 'group' || (Array.isArray(node.children) && node.children.length)) {
91
+ return (_jsxs("div", { className: "mb-1", children: [_jsxs("p", { className: "flex items-center gap-1 px-2 pb-1 pt-3 text-[11px] text-muted-foreground", children: [_jsx(Folder, { className: "h-3 w-3" }), " ", node.label] }), _jsx("div", { className: "pl-1.5", children: _jsx(NavTree, { nodes: node.children ?? [], active: active, onPick: onPick }) })] }, node.id ?? i));
92
+ }
93
+ const surface = resolveSurface(node);
94
+ const Icon = navIcon(node.type);
95
+ const isActive = !!surface && active?.type === surface.type && active?.name === surface.name;
96
+ return (_jsxs("button", { onClick: () => surface && onPick(surface), disabled: !surface, title: surface ? `${surface.type} · ${surface.name}` : node.label, className: 'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-xs disabled:opacity-40 ' +
97
+ (isActive ? 'bg-muted font-medium' : 'text-foreground/90 hover:bg-muted/60'), children: [_jsx(Icon, { className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "flex-1 truncate", children: node.label }), surface && surface.type !== 'page' && (_jsx("span", { className: "rounded bg-muted px-1 py-px text-[9px] uppercase text-muted-foreground", children: surface.type }))] }, node.id ?? i));
98
+ }) }));
99
+ }
100
+ /** Interfaces pillar — real App nav · live canvas · inspector. */
101
+ function InterfacesPillar({ packageId }) {
102
+ const client = useMetadataClient();
103
+ const locale = 'zh-CN';
104
+ const [appLabel, setAppLabel] = React.useState(packageId);
105
+ const [appName, setAppName] = React.useState(null);
106
+ const [appDraft, setAppDraft] = React.useState({});
107
+ const navTree = React.useMemo(() => (Array.isArray(appDraft.navigation) ? appDraft.navigation : []), [appDraft]);
108
+ // nav editing — drag-drop reorder / rename / add / remove via AppNavCanvas
109
+ const [editNav, setEditNav] = React.useState(false);
110
+ const [navSel, setNavSel] = React.useState(null);
111
+ const [navDirty, setNavDirty] = React.useState(false);
112
+ const [navHasDraft, setNavHasDraft] = React.useState(false);
113
+ const [navSaving, setNavSaving] = React.useState(false);
114
+ const [current, setCurrent] = React.useState(null);
115
+ const [draft, setDraft] = React.useState({});
116
+ const [selection, setSelection] = React.useState(null);
117
+ const [loading, setLoading] = React.useState(false);
118
+ const [saving, setSaving] = React.useState(false);
119
+ const [hasDraft, setHasDraft] = React.useState(false);
120
+ const [error, setError] = React.useState(null);
121
+ // Resolve the App by package id → load its navigation tree.
122
+ React.useEffect(() => {
123
+ let cancelled = false;
124
+ (async () => {
125
+ try {
126
+ const apps = (await client.list('app'));
127
+ if (cancelled)
128
+ return;
129
+ const app = (apps || []).find((a) => a._packageId === packageId || a.packageId === packageId || a.name === packageId) ?? (apps || [])[0];
130
+ if (!app)
131
+ return;
132
+ setAppLabel(String(app.label ?? app.name ?? packageId));
133
+ setAppName(String(app.name));
134
+ const [layRaw, appDraftResp] = await Promise.all([
135
+ client.layered('app', String(app.name)),
136
+ client.getDraft('app', String(app.name)).catch(() => null),
137
+ ]);
138
+ if (cancelled)
139
+ return;
140
+ const lay = layRaw;
141
+ const eff = (lay.effective ?? lay.code ?? {});
142
+ const appDraftBody = extractDraftBody(appDraftResp);
143
+ const body = appDraftBody ? { ...eff, ...appDraftBody } : eff;
144
+ setAppDraft(body);
145
+ setNavHasDraft(!!appDraftBody);
146
+ const tree = Array.isArray(body.navigation) ? body.navigation : [];
147
+ // auto-open the first resolvable leaf
148
+ const firstLeaf = (function find(nodes) {
149
+ for (const n of nodes) {
150
+ if (n.type === 'group' || n.children?.length) {
151
+ const r = find(n.children ?? []);
152
+ if (r)
153
+ return r;
154
+ }
155
+ else {
156
+ const s = resolveSurface(n);
157
+ if (s)
158
+ return s;
159
+ }
160
+ }
161
+ return null;
162
+ })(tree);
163
+ setCurrent((cur) => cur ?? firstLeaf);
164
+ }
165
+ catch (e) {
166
+ if (!cancelled)
167
+ setError(e instanceof Error ? e.message : String(e));
168
+ }
169
+ })();
170
+ return () => {
171
+ cancelled = true;
172
+ };
173
+ }, [client, packageId]);
174
+ const Preview = getMetadataPreview(current?.type ?? '');
175
+ const Inspector = getMetadataInspector(current?.type ?? '');
176
+ // Object leaves render as a runtime records grid (preview = runtime); schema
177
+ // editing is the Data pillar's job, so they are not draft-editable in this canvas.
178
+ const isEditable = !!Preview && current?.type !== 'object';
179
+ // Load the selected surface's draft (only for editable preview types).
180
+ React.useEffect(() => {
181
+ if (!current || !isEditable) {
182
+ setDraft({});
183
+ setHasDraft(false);
184
+ return;
185
+ }
186
+ let cancelled = false;
187
+ setLoading(true);
188
+ setError(null);
189
+ setSelection(null);
190
+ (async () => {
191
+ try {
192
+ const [lay, draftResp] = await Promise.all([
193
+ client.layered(current.type, current.name),
194
+ client.getDraft(current.type, current.name).catch(() => null),
195
+ ]);
196
+ if (cancelled)
197
+ return;
198
+ const baseline = (lay.effective ??
199
+ lay.code ??
200
+ {});
201
+ const body = extractDraftBody(draftResp);
202
+ setDraft(body ? { ...baseline, ...body } : baseline);
203
+ setHasDraft(!!body);
204
+ }
205
+ catch (e) {
206
+ if (!cancelled)
207
+ setError(e instanceof Error ? e.message : String(e));
208
+ }
209
+ finally {
210
+ if (!cancelled)
211
+ setLoading(false);
212
+ }
213
+ })();
214
+ return () => {
215
+ cancelled = true;
216
+ };
217
+ }, [client, current, isEditable]);
218
+ const onPatch = React.useCallback((patch) => setDraft((d) => ({ ...d, ...patch })), []);
219
+ const doSave = React.useCallback(async () => {
220
+ if (!current)
221
+ return;
222
+ setSaving('draft');
223
+ try {
224
+ await client.save(current.type, current.name, draft, { mode: 'draft' });
225
+ setHasDraft(true);
226
+ }
227
+ catch (e) {
228
+ setError(e instanceof Error ? e.message : String(e));
229
+ }
230
+ finally {
231
+ setSaving(false);
232
+ }
233
+ }, [client, current, draft]);
234
+ const doPublish = React.useCallback(async () => {
235
+ if (!current)
236
+ return;
237
+ setSaving('publish');
238
+ try {
239
+ await client.publish(current.type, current.name);
240
+ setHasDraft(false);
241
+ }
242
+ catch (e) {
243
+ setError(e instanceof Error ? e.message : String(e));
244
+ }
245
+ finally {
246
+ setSaving(false);
247
+ }
248
+ }, [client, current]);
249
+ // nav editing — patch appDraft.navigation, then save/publish the App overlay
250
+ const onNavPatch = React.useCallback((patch) => {
251
+ setAppDraft((d) => ({ ...d, ...patch }));
252
+ setNavDirty(true);
253
+ }, []);
254
+ const doNavSave = React.useCallback(async () => {
255
+ if (!appName)
256
+ return;
257
+ setNavSaving('draft');
258
+ try {
259
+ await client.save('app', appName, appDraft, { mode: 'draft' });
260
+ setNavHasDraft(true);
261
+ setNavDirty(false);
262
+ }
263
+ catch (e) {
264
+ setError(e instanceof Error ? e.message : String(e));
265
+ }
266
+ finally {
267
+ setNavSaving(false);
268
+ }
269
+ }, [client, appName, appDraft]);
270
+ const doNavPublish = React.useCallback(async () => {
271
+ if (!appName)
272
+ return;
273
+ setNavSaving('publish');
274
+ try {
275
+ await client.publish('app', appName);
276
+ setNavHasDraft(false);
277
+ }
278
+ catch (e) {
279
+ setError(e instanceof Error ? e.message : String(e));
280
+ }
281
+ finally {
282
+ setNavSaving(false);
283
+ }
284
+ }, [client, appName]);
285
+ return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs("div", { className: "flex items-center gap-2 border-b px-3 py-1.5", children: [current ? (_jsxs("span", { className: "flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [_jsx("span", { className: "text-[13px] font-medium text-foreground", children: current.label }), _jsxs("span", { className: "rounded bg-muted px-1.5 py-0.5", children: [current.type, " \u00B7 ", current.name] })] })) : (_jsx("span", { className: "text-[11px] text-muted-foreground", children: "\u4ECE\u5DE6\u4FA7\u9009\u62E9\u4E00\u4E2A\u83DC\u5355\u9879" })), hasDraft && (_jsx("span", { className: "rounded bg-amber-400/15 px-2 py-0.5 text-[11px] text-amber-600 dark:text-amber-300", children: "\u672A\u53D1\u5E03\u8349\u7A3F" })), _jsxs("button", { onClick: doSave, disabled: !current || !isEditable || !!saving, className: "ml-auto inline-flex items-center gap-1 rounded-md border px-2.5 py-1 text-xs hover:bg-muted disabled:opacity-50", children: [saving === 'draft' ? _jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : _jsx(Save, { className: "h-3.5 w-3.5" }), "\u4FDD\u5B58\u8349\u7A3F"] }), _jsxs("button", { onClick: doPublish, disabled: !current || !isEditable || !hasDraft || !!saving, className: "inline-flex items-center gap-1 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50", children: [saving === 'publish' ? _jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : null, "\u53D1\u5E03"] })] }), _jsxs("div", { className: "flex min-h-0 flex-1", children: [_jsxs("nav", { className: (editNav ? 'w-72' : 'w-52') + ' flex shrink-0 flex-col border-r', children: [_jsxs("div", { className: "shrink-0 border-b px-2 py-1.5", children: [_jsxs("div", { className: "flex items-center justify-between gap-1", children: [_jsxs("p", { className: "truncate text-[11px] font-medium text-muted-foreground", children: [appLabel, " \u00B7 \u5BFC\u822A"] }), _jsx("button", { type: "button", onClick: () => {
286
+ setEditNav((v) => !v);
287
+ setNavSel(null);
288
+ }, title: editNav ? '完成编辑' : '编辑导航(拖拽排序 / 重命名 / 增删)', className: 'inline-flex shrink-0 items-center gap-1 rounded px-1.5 py-0.5 text-[10px] ' +
289
+ (editNav ? 'bg-primary/10 text-primary' : 'text-muted-foreground hover:bg-muted'), children: editNav ? (_jsxs(_Fragment, { children: [_jsx(Check, { className: "h-3 w-3" }), " \u5B8C\u6210"] })) : (_jsxs(_Fragment, { children: [_jsx(Pencil, { className: "h-3 w-3" }), " \u7F16\u8F91"] })) })] }), editNav && (_jsxs("div", { className: "mt-1.5 flex items-center gap-1.5", children: [navHasDraft && (_jsx("span", { className: "rounded bg-amber-400/15 px-1.5 py-0.5 text-[10px] text-amber-600 dark:text-amber-300", children: "\u672A\u53D1\u5E03" })), _jsxs("button", { onClick: doNavSave, disabled: !navDirty || !!navSaving, className: "inline-flex items-center gap-1 rounded-md border px-2 py-1 text-[11px] hover:bg-muted disabled:opacity-50", children: [navSaving === 'draft' ? (_jsx(Loader2, { className: "h-3 w-3 animate-spin" })) : (_jsx(Save, { className: "h-3 w-3" })), "\u4FDD\u5B58\u8349\u7A3F"] }), _jsxs("button", { onClick: doNavPublish, disabled: !navHasDraft || !!navSaving, className: "inline-flex items-center gap-1 rounded-md bg-primary px-2 py-1 text-[11px] font-medium text-primary-foreground disabled:opacity-50", children: [navSaving === 'publish' ? _jsx(Loader2, { className: "h-3 w-3 animate-spin" }) : null, "\u53D1\u5E03"] })] }))] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-2", children: navTree.length === 0 ? (_jsx("p", { className: "px-2 py-3 text-[11px] text-muted-foreground", children: error ? '加载失败' : '加载中…' })) : editNav ? (_jsx(AppNavCanvas, { draft: appDraft, rootKey: "navigation", onPatch: onNavPatch, selection: navSel, onSelectionChange: (s) => setNavSel(s ? { kind: s.kind, id: s.id } : null) })) : (_jsx(NavTree, { nodes: navTree, active: current, onPick: setCurrent })) })] }), _jsxs("main", { className: "min-w-0 flex-1 overflow-auto bg-muted/30 p-4", children: [_jsxs("div", { className: "mb-3 flex items-center gap-2", children: [_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-primary/10 px-2 py-0.5 text-[11px] text-primary", children: [_jsx(Eye, { className: "h-3 w-3" }), " \u9884\u89C8\u5373\u8FD0\u884C \u00B7 \u540C\u4E00\u6E32\u67D3\u5668"] }), current && (_jsxs("span", { className: "text-[11px] text-muted-foreground", children: [current.type, " \u00B7 ", current.name] }))] }), error && (_jsx("div", { className: "mb-3 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: error })), _jsx("div", { className: "rounded-lg border bg-background p-4", children: !current ? (_jsx("div", { className: "py-16 text-center text-sm text-muted-foreground", children: "\u4ECE\u5DE6\u4FA7\u9009\u62E9\u4E00\u4E2A\u83DC\u5355\u9879" })) : loading ? (_jsxs("div", { className: "flex items-center justify-center gap-2 py-16 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " \u52A0\u8F7D\u4E2D\u2026"] })) : current.type === 'object' ? (_jsx(SchemaRenderer, { schema: { type: 'object-view', objectName: current.name } })) : Preview ? (_jsx(Preview, { type: current.type, name: current.name, draft: draft, editing: true, selection: selection, onSelectionChange: setSelection, onPatch: onPatch, locale: locale })) : (_jsxs("div", { className: "py-12 text-center text-xs text-muted-foreground", children: [current.type, " \u6682\u7528\u53EA\u8BFB\u9884\u89C8,\u8BBE\u8BA1\u80FD\u529B\u5EFA\u8BBE\u4E2D\u3002"] })) }), isEditable ? (_jsxs("p", { className: "mt-2 flex items-center gap-1 text-[11px] text-muted-foreground", children: [_jsx(MousePointer2, { className: "h-3 w-3" }), " \u70B9\u9009\u79EF\u6728 \u2192 \u53F3\u4FA7\u76F4\u63A5\u6539 \u00B7 \u6539\u5B8C\u300C\u4FDD\u5B58\u8349\u7A3F\u300D\u2192\u300C\u53D1\u5E03\u300D"] })) : current?.type === 'object' ? (_jsxs("p", { className: "mt-2 flex items-center gap-1 text-[11px] text-muted-foreground", children: [_jsx(Database, { className: "h-3 w-3" }), " \u8FD0\u884C\u6001\u5217\u8868\u9884\u89C8 \u00B7 \u6539\u5B57\u6BB5 / \u7ED3\u6784\u8BF7\u5230 ", _jsx("span", { className: "font-medium", children: "Data" }), " \u652F\u67F1"] })) : null] }), _jsxs("aside", { className: "w-72 shrink-0 overflow-auto border-l", children: [_jsxs("header", { className: "sticky top-0 z-10 flex items-center gap-2 border-b bg-background/95 px-3 py-2 backdrop-blur", children: [_jsx(SlidersHorizontal, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "text-[13px] font-medium", children: "\u5C5E\u6027" }), selection && (_jsx("button", { type: "button", onClick: () => setSelection(null), className: "ml-auto rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground", "aria-label": "\u53D6\u6D88\u9009\u62E9", children: _jsx(X, { className: "h-3.5 w-3.5" }) }))] }), _jsx("div", { className: "p-3", children: selection && Inspector && current ? (_jsx(Inspector, { type: current.type, name: current.name, draft: draft, selection: selection, onPatch: onPatch, onClearSelection: () => setSelection(null), onSelectionChange: setSelection, readOnly: false, locale: locale })) : (_jsxs("div", { className: "flex flex-col items-center gap-2 px-2 py-10 text-center text-xs text-muted-foreground", children: [_jsx(MousePointer2, { className: "h-5 w-5" }), "\u5728\u753B\u5E03\u91CC\u70B9\u9009\u4E00\u4E2A\u79EF\u6728,", _jsx("br", {}), "\u5B83\u7684\u5C5E\u6027\u4F1A\u5728\u8FD9\u91CC\u76F4\u63A5\u7F16\u8F91\u3002"] })) })] })] })] }));
290
+ }
291
+ /** Next unused `field_N` name for a freshly-added field. */
292
+ function nextFieldName(existing) {
293
+ let i = existing.length + 1;
294
+ let name = `field_${i}`;
295
+ while (existing.includes(name))
296
+ name = `field_${++i}`;
297
+ return name;
298
+ }
299
+ /**
300
+ * Data pillar — the package's objects: a records grid (Airtable parity) plus
301
+ * table-based field management. Add a field, or click a column header's edit
302
+ * affordance, to open ObjectFieldInspector (full type list + per-type config)
303
+ * in the right panel; changes persist via the object draft → publish overlay.
304
+ */
305
+ /**
306
+ * Framework-managed/audit fields. They lead the raw metadata order but aren't
307
+ * what a user manages in a data grid, so the Data pillar drops them from the
308
+ * column set (mirrors ObjectGrid's regular-vs-system split) to open on the
309
+ * meaningful fields first — the same way Airtable hides system columns.
310
+ */
311
+ const STUDIO_SYSTEM_FIELD_NAMES = new Set([
312
+ '_id', 'id', 'organization_id', 'org_id', 'space_id',
313
+ 'created_at', 'created_by', 'updated_at', 'updated_by',
314
+ 'modified_at', 'modified_by', 'created_time', 'modified_time', 'updated_time',
315
+ 'deleted_at', 'deleted_by',
316
+ ]);
317
+ /**
318
+ * Render the Data pillar's records grid using the SAME rich list surface as the
319
+ * runtime list pages — the standard toolbar (view switcher, search, sort, filter,
320
+ * group, hide-fields) plus Airtable-style inline data management. This is the
321
+ * plugin ObjectView's `renderListView` slot, so the object-view still owns data
322
+ * fetching while ListView owns the toolbar + grid. Defined at module scope (not
323
+ * inline) so it stays a static component reference.
324
+ */
325
+ function renderStudioGridList(props) {
326
+ const { schema: listSchema, dataSource: ds, onEdit, className, refreshKey } = props;
327
+ return (_jsx(ListView, { schema: {
328
+ ...listSchema,
329
+ viewType: 'grid',
330
+ showSearch: true,
331
+ showSort: true,
332
+ showFilters: true,
333
+ showGroup: true,
334
+ showHideFields: true,
335
+ inlineEdit: true,
336
+ addDeleteRecordsInline: true,
337
+ }, dataSource: ds, onEdit: onEdit, className: className, refreshKey: refreshKey }));
338
+ }
339
+ function DataPillar({ packageId }) {
340
+ const client = useMetadataClient();
341
+ const adapter = useAdapter();
342
+ const locale = 'zh-CN';
343
+ const [objects, setObjects] = React.useState([]);
344
+ const [current, setCurrent] = React.useState(null);
345
+ const [objDraft, setObjDraft] = React.useState({});
346
+ const [loading, setLoading] = React.useState(false);
347
+ const [error, setError] = React.useState(null);
348
+ // field management — a selected field opens ObjectFieldInspector (full type + config)
349
+ const [fieldSel, setFieldSel] = React.useState(null);
350
+ const [dirty, setDirty] = React.useState(false);
351
+ const [hasDraft, setHasDraft] = React.useState(false);
352
+ const [saving, setSaving] = React.useState(false);
353
+ const [gridVer, setGridVer] = React.useState(0);
354
+ React.useEffect(() => {
355
+ let cancelled = false;
356
+ (async () => {
357
+ try {
358
+ const list = (await client.list('object', { packageId }));
359
+ if (cancelled)
360
+ return;
361
+ const items = (list || [])
362
+ .map((o) => ({ type: 'object', name: String(o.name ?? ''), label: String(o.label ?? o.name ?? '') }))
363
+ .filter((o) => o.name);
364
+ setObjects(items);
365
+ setCurrent((c) => c ?? items[0] ?? null);
366
+ }
367
+ catch (e) {
368
+ if (!cancelled)
369
+ setError(e instanceof Error ? e.message : String(e));
370
+ }
371
+ })();
372
+ return () => {
373
+ cancelled = true;
374
+ };
375
+ }, [client, packageId]);
376
+ React.useEffect(() => {
377
+ if (!current)
378
+ return;
379
+ let cancelled = false;
380
+ setLoading(true);
381
+ setError(null);
382
+ setFieldSel(null);
383
+ setDirty(false);
384
+ (async () => {
385
+ try {
386
+ const [layRaw, draftResp] = await Promise.all([
387
+ client.layered('object', current.name),
388
+ client.getDraft('object', current.name).catch(() => null),
389
+ ]);
390
+ if (cancelled)
391
+ return;
392
+ const lay = layRaw;
393
+ const baseline = (lay.effective ?? lay.code ?? {});
394
+ const draftBody = extractDraftBody(draftResp);
395
+ setObjDraft(draftBody ? { ...baseline, ...draftBody } : baseline);
396
+ setHasDraft(!!draftBody);
397
+ }
398
+ catch (e) {
399
+ if (!cancelled)
400
+ setError(e instanceof Error ? e.message : String(e));
401
+ }
402
+ finally {
403
+ if (!cancelled)
404
+ setLoading(false);
405
+ }
406
+ })();
407
+ return () => {
408
+ cancelled = true;
409
+ };
410
+ }, [client, current]);
411
+ const fieldCount = React.useMemo(() => readFields(objDraft.fields).entries.length, [objDraft]);
412
+ const onPatch = React.useCallback((patch) => {
413
+ setObjDraft((d) => ({ ...d, ...patch }));
414
+ setDirty(true);
415
+ }, []);
416
+ // "+ add field": append a fresh text field and select it for editing in the panel.
417
+ const addField = React.useCallback(() => {
418
+ const view = readFields(objDraft.fields);
419
+ const name = nextFieldName(view.entries.map((e) => e.name));
420
+ view.entries.push(newField(name, 'text', '新字段'));
421
+ setObjDraft((d) => ({ ...d, fields: writeFields(view) }));
422
+ setDirty(true);
423
+ setFieldSel({ kind: 'field', id: name });
424
+ }, [objDraft]);
425
+ const doSave = React.useCallback(async () => {
426
+ if (!current)
427
+ return;
428
+ setSaving('draft');
429
+ setError(null);
430
+ try {
431
+ await client.save('object', current.name, objDraft, { mode: 'draft' });
432
+ setHasDraft(true);
433
+ setDirty(false);
434
+ }
435
+ catch (e) {
436
+ setError(e instanceof Error ? e.message : String(e));
437
+ }
438
+ finally {
439
+ setSaving(false);
440
+ }
441
+ }, [client, current, objDraft]);
442
+ const doPublish = React.useCallback(async () => {
443
+ if (!current)
444
+ return;
445
+ setSaving('publish');
446
+ setError(null);
447
+ try {
448
+ await client.publish('object', current.name);
449
+ // Bust the data-layer object-schema cache (separate from the metadata client)
450
+ // so the remounted grid re-fetches the new/edited columns without a reload.
451
+ adapter?.clearCache?.();
452
+ setHasDraft(false);
453
+ setDirty(false);
454
+ setGridVer((v) => v + 1);
455
+ }
456
+ catch (e) {
457
+ setError(e instanceof Error ? e.message : String(e));
458
+ }
459
+ finally {
460
+ setSaving(false);
461
+ }
462
+ }, [adapter, client, current]);
463
+ // Drag-reorder columns → reorder the object's `fields` metadata (field display
464
+ // order follows metadata order), then publish so the new order persists.
465
+ const doReorderFields = React.useCallback(async (orderedNames) => {
466
+ if (!current)
467
+ return;
468
+ const view = readFields(objDraft.fields);
469
+ // Reorder only the visible fields among their own slots; keep system /
470
+ // hidden fields (not shown as columns) in their original positions.
471
+ const visible = new Set(orderedNames);
472
+ const visibleInOrder = orderedNames
473
+ .map((n) => view.entries.find((e) => e.name === n))
474
+ .filter((e) => Boolean(e));
475
+ let vi = 0;
476
+ const entries = view.entries.map((e) => (visible.has(e.name) ? visibleInOrder[vi++] : e));
477
+ const body = { ...objDraft, fields: writeFields({ ...view, entries }) };
478
+ setObjDraft(body);
479
+ setSaving('publish');
480
+ setError(null);
481
+ try {
482
+ await client.save('object', current.name, body, { mode: 'draft' });
483
+ await client.publish('object', current.name);
484
+ adapter?.clearCache?.();
485
+ setHasDraft(false);
486
+ setDirty(false);
487
+ setGridVer((v) => v + 1); // remount so the grid reflects the persisted order
488
+ }
489
+ catch (e) {
490
+ setError(e instanceof Error ? e.message : String(e));
491
+ }
492
+ finally {
493
+ setSaving(false);
494
+ }
495
+ }, [client, current, objDraft, adapter]);
496
+ const inspector = getMetadataInspector('object');
497
+ return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs("div", { className: "flex items-center gap-2 border-b px-3 py-1.5", children: [current ? (_jsxs("span", { className: "flex items-center gap-1.5 text-[11px] text-muted-foreground", children: [_jsx("span", { className: "text-[13px] font-medium text-foreground", children: current.label }), _jsxs("span", { className: "rounded bg-muted px-1.5 py-0.5", children: ["object \u00B7 ", current.name] }), _jsxs("span", { children: [fieldCount, " \u5B57\u6BB5"] })] })) : (_jsx("span", { className: "text-[11px] text-muted-foreground", children: "\u9009\u62E9\u4E00\u4E2A\u5BF9\u8C61" })), hasDraft && (_jsx("span", { className: "rounded bg-amber-400/15 px-2 py-0.5 text-[11px] text-amber-600 dark:text-amber-300", children: "\u672A\u53D1\u5E03\u8349\u7A3F" })), _jsxs("button", { onClick: doSave, disabled: !current || !dirty || !!saving, className: "ml-auto inline-flex items-center gap-1 rounded-md border px-2.5 py-1 text-xs hover:bg-muted disabled:opacity-50", children: [saving === 'draft' ? _jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : _jsx(Save, { className: "h-3.5 w-3.5" }), "\u4FDD\u5B58\u8349\u7A3F"] }), _jsxs("button", { onClick: doPublish, disabled: !current || !hasDraft || !!saving, className: "inline-flex items-center gap-1 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50", children: [saving === 'publish' ? _jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : null, "\u53D1\u5E03"] })] }), _jsxs("div", { className: "flex min-h-0 flex-1", children: [_jsxs("nav", { className: "w-52 shrink-0 overflow-auto border-r p-2", children: [_jsx("p", { className: "px-2 pb-1 pt-1 text-[11px] font-medium text-muted-foreground", children: "\u5BF9\u8C61" }), objects.length === 0 && (_jsx("p", { className: "px-2 py-3 text-[11px] text-muted-foreground", children: error ? '加载失败' : '加载中…' })), objects.map((o) => (_jsxs("button", { onClick: () => setCurrent(o), className: 'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-xs ' +
498
+ (current?.name === o.name ? 'bg-muted font-medium' : 'text-foreground/90 hover:bg-muted/60'), children: [_jsx(Database, { className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "flex-1 truncate", children: o.label })] }, o.name)))] }), _jsx("main", { className: "flex min-w-0 flex-1 flex-col overflow-hidden p-4", children: !current ? (_jsx("div", { className: "py-16 text-center text-sm text-muted-foreground", children: "\u9009\u62E9\u4E00\u4E2A\u5BF9\u8C61" })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "mb-3 flex shrink-0 items-center gap-2", children: [_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-primary/10 px-2 py-0.5 text-[11px] text-primary", children: [_jsx(Eye, { className: "h-3 w-3" }), " \u8FD0\u884C\u6001\u5217\u8868 \u00B7 \u540C\u4E00\u6E32\u67D3\u5668"] }), _jsxs("button", { type: "button", onClick: addField, title: "\u6DFB\u52A0\u4E00\u4E2A\u5B57\u6BB5(\u968F\u540E\u5728\u53F3\u4FA7\u8BBE\u7F6E\u7C7B\u578B\u4E0E\u5C5E\u6027)", 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" }), " \u6DFB\u52A0\u5B57\u6BB5"] })] }), error && (_jsx("div", { className: "mb-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-1.5 text-[11px] text-destructive", children: error })), _jsx("div", { className: "min-h-0 flex-1 overflow-auto rounded-lg border bg-background", children: _jsx(GridFieldAuthoringProvider, { value: {
499
+ onAddColumn: addField,
500
+ addColumnLabel: '添加字段',
501
+ onEditColumn: (fieldName) => {
502
+ // ignore non-field columns (e.g. the row-actions column)
503
+ if (readFields(objDraft.fields).entries.some((e) => e.name === fieldName)) {
504
+ setFieldSel({ kind: 'field', id: fieldName });
505
+ }
506
+ },
507
+ editColumnLabel: '编辑字段属性',
508
+ onReorderFields: doReorderFields,
509
+ }, children: _jsx(SchemaRendererProvider, { dataSource: adapter, children: _jsx(PluginObjectView, { schema: {
510
+ type: 'object-view',
511
+ objectName: current.name,
512
+ // No saved view exists in design mode, so show the object's
513
+ // own fields as columns (in metadata order), dropping
514
+ // framework-managed/audit fields so the grid opens on the
515
+ // meaningful columns first — the way Airtable does.
516
+ table: {
517
+ fields: readFields(objDraft.fields)
518
+ .entries.map((e) => e.name)
519
+ .filter((n) => !STUDIO_SYSTEM_FIELD_NAMES.has(n)),
520
+ },
521
+ }, dataSource: adapter, renderListView: renderStudioGridList }, `${current.name}:${gridVer}`) }) }) }), _jsxs("p", { className: "mt-2 flex items-center gap-1 text-[11px] text-muted-foreground", children: [_jsx(MousePointer2, { className: "h-3 w-3" }), " \u5217\u5934\u300C+\u300D\u52A0\u5B57\u6BB5 \u00B7 \u7B14\u5F62\u6539\u5C5E\u6027 \u00B7 \u62D6\u5217\u5934\u91CD\u6392 \u00B7 \u6539\u5B8C\u300C\u4FDD\u5B58\u8349\u7A3F\u300D\u2192\u300C\u53D1\u5E03\u300D"] })] })) }), current && fieldSel && inspector && (_jsxs("aside", { className: "flex w-80 shrink-0 flex-col border-l", children: [_jsxs("header", { className: "sticky top-0 z-10 flex items-center gap-2 border-b bg-background/95 px-3 py-2 backdrop-blur", children: [_jsx(SlidersHorizontal, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "text-[13px] font-medium", children: "\u5B57\u6BB5\u5C5E\u6027" }), _jsx("button", { type: "button", onClick: () => setFieldSel(null), className: "ml-auto rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground", "aria-label": "\u5173\u95ED", children: _jsx(X, { className: "h-3.5 w-3.5" }) })] }), _jsx("div", { className: "min-h-0 flex-1 overflow-auto p-3", children: React.createElement(inspector, {
522
+ type: 'object',
523
+ name: current.name,
524
+ draft: objDraft,
525
+ selection: fieldSel,
526
+ onPatch,
527
+ onClearSelection: () => setFieldSel(null),
528
+ onSelectionChange: setFieldSel,
529
+ readOnly: false,
530
+ locale,
531
+ }) })] }))] })] }));
532
+ }
533
+ /** Automations pillar — flows: list → FlowPreview (default OFF / review-then-enable). */
534
+ function AutomationsPillar({ packageId }) {
535
+ const client = useMetadataClient();
536
+ const locale = 'zh-CN';
537
+ const [flows, setFlows] = React.useState([]);
538
+ const [current, setCurrent] = React.useState(null);
539
+ const [draft, setDraft] = React.useState({});
540
+ const [selection, setSelection] = React.useState(null);
541
+ const [loading, setLoading] = React.useState(false);
542
+ const [saving, setSaving] = React.useState(false);
543
+ const [hasDraft, setHasDraft] = React.useState(false);
544
+ const [error, setError] = React.useState(null);
545
+ const Preview = getMetadataPreview(current?.type ?? '');
546
+ const inspector = getMetadataInspector('flow');
547
+ const isEditable = !!Preview;
548
+ React.useEffect(() => {
549
+ let cancelled = false;
550
+ (async () => {
551
+ try {
552
+ const list = (await client.list('flow', { packageId }));
553
+ if (cancelled)
554
+ return;
555
+ const items = (list || [])
556
+ .map((f) => ({ type: 'flow', name: String(f.name ?? ''), label: String(f.label ?? f.name ?? '') }))
557
+ .filter((f) => f.name);
558
+ setFlows(items);
559
+ setCurrent((c) => c ?? items[0] ?? null);
560
+ }
561
+ catch (e) {
562
+ if (!cancelled)
563
+ setError(e instanceof Error ? e.message : String(e));
564
+ }
565
+ })();
566
+ return () => {
567
+ cancelled = true;
568
+ };
569
+ }, [client, packageId]);
570
+ React.useEffect(() => {
571
+ if (!current)
572
+ return;
573
+ let cancelled = false;
574
+ setLoading(true);
575
+ setError(null);
576
+ setSelection(null);
577
+ (async () => {
578
+ try {
579
+ const [layRaw, draftResp] = await Promise.all([
580
+ client.layered('flow', current.name),
581
+ client.getDraft('flow', current.name).catch(() => null),
582
+ ]);
583
+ if (cancelled)
584
+ return;
585
+ const lay = layRaw;
586
+ const baseline = (lay.effective ?? lay.code ?? {});
587
+ const draftBody = extractDraftBody(draftResp);
588
+ setDraft(draftBody ? { ...baseline, ...draftBody } : baseline);
589
+ setHasDraft(!!draftBody);
590
+ }
591
+ catch (e) {
592
+ if (!cancelled)
593
+ setError(e instanceof Error ? e.message : String(e));
594
+ }
595
+ finally {
596
+ if (!cancelled)
597
+ setLoading(false);
598
+ }
599
+ })();
600
+ return () => {
601
+ cancelled = true;
602
+ };
603
+ }, [client, current]);
604
+ const onPatch = React.useCallback((patch) => setDraft((d) => ({ ...d, ...patch })), []);
605
+ const doSave = React.useCallback(async () => {
606
+ if (!current)
607
+ return;
608
+ setSaving('draft');
609
+ setError(null);
610
+ try {
611
+ await client.save('flow', current.name, draft, { mode: 'draft' });
612
+ setHasDraft(true);
613
+ }
614
+ catch (e) {
615
+ setError(e instanceof Error ? e.message : String(e));
616
+ }
617
+ finally {
618
+ setSaving(false);
619
+ }
620
+ }, [client, current, draft]);
621
+ const doPublish = React.useCallback(async () => {
622
+ if (!current)
623
+ return;
624
+ setSaving('publish');
625
+ setError(null);
626
+ try {
627
+ await client.publish('flow', current.name);
628
+ setHasDraft(false);
629
+ }
630
+ catch (e) {
631
+ setError(e instanceof Error ? e.message : String(e));
632
+ }
633
+ finally {
634
+ setSaving(false);
635
+ }
636
+ }, [client, current]);
637
+ return (_jsxs("div", { className: "flex h-full flex-col", children: [_jsxs("div", { className: "flex items-center gap-2 border-b px-3 py-1.5", children: [_jsx("span", { className: "text-[11px] text-muted-foreground", children: "\u9ED8\u8BA4 OFF \u00B7 \u5BA1\u9605\u540E\u518D\u542F\u7528" }), hasDraft && (_jsx("span", { className: "rounded bg-amber-400/15 px-2 py-0.5 text-[11px] text-amber-600 dark:text-amber-300", children: "\u672A\u53D1\u5E03\u8349\u7A3F" })), _jsxs("button", { onClick: doSave, disabled: !current || !isEditable || !!saving, className: "ml-auto inline-flex items-center gap-1 rounded-md border px-2.5 py-1 text-xs hover:bg-muted disabled:opacity-50", children: [saving === 'draft' ? _jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : _jsx(Save, { className: "h-3.5 w-3.5" }), "\u4FDD\u5B58\u8349\u7A3F"] }), _jsxs("button", { onClick: doPublish, disabled: !current || !isEditable || !hasDraft || !!saving, className: "inline-flex items-center gap-1 rounded-md bg-primary px-2.5 py-1 text-xs font-medium text-primary-foreground disabled:opacity-50", children: [saving === 'publish' ? _jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : null, "\u53D1\u5E03"] })] }), _jsxs("div", { className: "flex min-h-0 flex-1", children: [_jsxs("nav", { className: "w-52 shrink-0 overflow-auto border-r p-2", children: [_jsx("p", { className: "px-2 pb-1 pt-1 text-[11px] font-medium text-muted-foreground", children: "\u81EA\u52A8\u5316 \u00B7 flow" }), flows.length === 0 ? (_jsx("p", { className: "px-2 py-3 text-[11px] text-muted-foreground", children: error ? '加载失败' : '加载中…' })) : (flows.map((f) => (_jsxs("button", { onClick: () => setCurrent(f), className: 'flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-xs ' +
638
+ (current?.name === f.name ? 'bg-muted font-medium' : 'text-foreground/90 hover:bg-muted/60'), children: [_jsx(Workflow, { className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "flex-1 truncate", children: f.label })] }, f.name))))] }), _jsxs("main", { className: "min-w-0 flex-1 overflow-auto bg-muted/30 p-4", children: [_jsxs("div", { className: "mb-3 flex items-center gap-2", children: [_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-primary/10 px-2 py-0.5 text-[11px] text-primary", children: [_jsx(Workflow, { className: "h-3 w-3" }), " \u53EF\u89C6\u5316\u7F16\u6392 \u00B7 \u70B9\u9009\u8282\u70B9\u914D\u7F6E"] }), current && _jsxs("span", { className: "text-[11px] text-muted-foreground", children: ["flow \u00B7 ", current.name] })] }), error && (_jsx("div", { className: "mb-3 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: error })), _jsx("div", { className: "rounded-lg border bg-background p-4", children: !current ? (_jsx("div", { className: "py-16 text-center text-sm text-muted-foreground", children: "\u9009\u62E9\u4E00\u4E2A\u81EA\u52A8\u5316" })) : loading ? (_jsxs("div", { className: "flex items-center justify-center gap-2 py-16 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " \u52A0\u8F7D\u4E2D\u2026"] })) : Preview ? (React.createElement(Preview, {
639
+ type: current.type,
640
+ name: current.name,
641
+ draft,
642
+ editing: true,
643
+ selection,
644
+ onSelectionChange: setSelection,
645
+ onPatch,
646
+ locale,
647
+ })) : (_jsx("pre", { className: "overflow-auto text-[11px] text-muted-foreground", children: JSON.stringify(draft, null, 2) })) }), isEditable && (_jsxs("p", { className: "mt-2 flex items-center gap-1 text-[11px] text-muted-foreground", children: [_jsx(MousePointer2, { className: "h-3 w-3" }), " \u70B9\u9009\u8282\u70B9 \u2192 \u53F3\u4FA7\u914D\u7F6E \u00B7 \u6539\u5B8C\u300C\u4FDD\u5B58\u8349\u7A3F\u300D\u2192\u300C\u53D1\u5E03\u300D"] }))] }), _jsxs("aside", { className: "w-72 shrink-0 overflow-auto border-l", children: [_jsxs("header", { className: "sticky top-0 z-10 flex items-center gap-2 border-b bg-background/95 px-3 py-2 backdrop-blur", children: [_jsx(SlidersHorizontal, { className: "h-3.5 w-3.5" }), _jsx("span", { className: "text-[13px] font-medium", children: "\u914D\u7F6E" }), selection && (_jsx("button", { type: "button", onClick: () => setSelection(null), className: "ml-auto rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground", "aria-label": "\u53D6\u6D88\u9009\u62E9", children: _jsx(X, { className: "h-3.5 w-3.5" }) }))] }), _jsx("div", { className: "p-3", children: selection && inspector && current ? (React.createElement(inspector, {
648
+ type: 'flow',
649
+ name: current.name,
650
+ draft,
651
+ selection,
652
+ onPatch,
653
+ onClearSelection: () => setSelection(null),
654
+ onSelectionChange: setSelection,
655
+ readOnly: false,
656
+ locale,
657
+ })) : (_jsxs("div", { className: "flex flex-col items-center gap-2 px-2 py-10 text-center text-xs text-muted-foreground", children: [_jsx(MousePointer2, { className: "h-5 w-5" }), "\u5728\u753B\u5E03\u91CC\u70B9\u9009\u4E00\u4E2A\u8282\u70B9,", _jsx("br", {}), "\u5B83\u7684\u914D\u7F6E\u4F1A\u5728\u8FD9\u91CC\u663E\u793A\u3002"] })) })] })] })] }));
658
+ }
659
+ export default StudioDesignSurface;