@object-ui/app-shell 6.2.0 → 6.2.2
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 +61 -0
- package/dist/console/AppContent.js +30 -1
- package/dist/console/ai/AiChatPage.js +19 -22
- package/dist/layout/ConsoleFloatingChatbot.js +44 -24
- package/dist/services/builtinComponents.js +10 -7
- package/dist/views/metadata-admin/DesignerEditorWrapper.d.ts +11 -1
- package/dist/views/metadata-admin/DesignerEditorWrapper.js +21 -3
- package/dist/views/metadata-admin/DirectoryPage.js +1 -1
- package/dist/views/metadata-admin/EmbeddedItemEditor.d.ts +15 -0
- package/dist/views/metadata-admin/EmbeddedItemEditor.js +194 -0
- package/dist/views/metadata-admin/MetadataDetailDrawer.d.ts +13 -0
- package/dist/views/metadata-admin/MetadataDetailDrawer.js +28 -0
- package/dist/views/metadata-admin/PageShell.js +9 -2
- package/dist/views/metadata-admin/QuickFind.js +2 -2
- package/dist/views/metadata-admin/RelatedPanel.d.ts +37 -0
- package/dist/views/metadata-admin/RelatedPanel.js +173 -0
- package/dist/views/metadata-admin/ResourceEditPage.d.ts +9 -3
- package/dist/views/metadata-admin/ResourceEditPage.js +105 -14
- package/dist/views/metadata-admin/ResourceHistoryPage.d.ts +3 -3
- package/dist/views/metadata-admin/ResourceHistoryPage.js +6 -3
- package/dist/views/metadata-admin/ResourceListPage.d.ts +2 -2
- package/dist/views/metadata-admin/ResourceListPage.js +6 -4
- package/dist/views/metadata-admin/SchemaForm.js +10 -1
- package/dist/views/metadata-admin/anchors.d.ts +1 -0
- package/dist/views/metadata-admin/anchors.js +235 -0
- package/dist/views/metadata-admin/index.d.ts +7 -3
- package/dist/views/metadata-admin/index.js +14 -2
- package/dist/views/metadata-admin/preview-registry.d.ts +43 -0
- package/dist/views/metadata-admin/preview-registry.js +18 -0
- package/dist/views/metadata-admin/previews/AppPreview.d.ts +2 -0
- package/dist/views/metadata-admin/previews/AppPreview.js +101 -0
- package/dist/views/metadata-admin/previews/DashboardPreview.d.ts +2 -0
- package/dist/views/metadata-admin/previews/DashboardPreview.js +25 -0
- package/dist/views/metadata-admin/previews/EmailTemplatePreview.d.ts +2 -0
- package/dist/views/metadata-admin/previews/EmailTemplatePreview.js +65 -0
- package/dist/views/metadata-admin/previews/ObjectPreview.d.ts +2 -0
- package/dist/views/metadata-admin/previews/ObjectPreview.js +36 -0
- package/dist/views/metadata-admin/previews/PagePreview.d.ts +2 -0
- package/dist/views/metadata-admin/previews/PagePreview.js +26 -0
- package/dist/views/metadata-admin/previews/PreviewShell.d.ts +40 -0
- package/dist/views/metadata-admin/previews/PreviewShell.js +49 -0
- package/dist/views/metadata-admin/previews/ReportPreview.d.ts +2 -0
- package/dist/views/metadata-admin/previews/ReportPreview.js +27 -0
- package/dist/views/metadata-admin/previews/ViewPreview.d.ts +2 -0
- package/dist/views/metadata-admin/previews/ViewPreview.js +113 -0
- package/dist/views/metadata-admin/previews/index.d.ts +1 -0
- package/dist/views/metadata-admin/previews/index.js +26 -0
- package/dist/views/metadata-admin/registry.d.ts +124 -0
- package/dist/views/metadata-admin/registry.js +58 -0
- package/package.json +26 -26
|
@@ -20,8 +20,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
20
20
|
* EditPage via `registerMetadataResource()`.
|
|
21
21
|
*/
|
|
22
22
|
import * as React from 'react';
|
|
23
|
-
import { useNavigate } from 'react-router-dom';
|
|
24
|
-
import { Save, RotateCcw, History, Link2, Loader2, AlertTriangle, } from 'lucide-react';
|
|
23
|
+
import { useNavigate, useParams } from 'react-router-dom';
|
|
24
|
+
import { Save, RotateCcw, History, Link2, Loader2, AlertTriangle, Layers3, Eye, Pencil, X, } from 'lucide-react';
|
|
25
25
|
import { Button } from '@object-ui/components';
|
|
26
26
|
import { Badge } from '@object-ui/components';
|
|
27
27
|
import { Tabs, TabsContent, TabsList, TabsTrigger, } from '@object-ui/components';
|
|
@@ -31,8 +31,14 @@ import { PageShell } from './PageShell';
|
|
|
31
31
|
import { LayeredDiff } from './LayeredDiff';
|
|
32
32
|
import { SchemaForm } from './SchemaForm';
|
|
33
33
|
import { useMetadataClient, useMetadataTypes, } from './useMetadata';
|
|
34
|
-
import { getMetadataResource, resolveResourceConfig, } from './registry';
|
|
35
|
-
|
|
34
|
+
import { getMetadataResource, resolveResourceConfig, listAnchorsFor, } from './registry';
|
|
35
|
+
import { RelatedPanel } from './RelatedPanel';
|
|
36
|
+
import { MetadataDetailDrawer } from './MetadataDetailDrawer';
|
|
37
|
+
import { getMetadataPreview } from './preview-registry';
|
|
38
|
+
export function MetadataResourceEditPage({ type: typeProp, name: nameProp, createMode = false, embedded = false, }) {
|
|
39
|
+
const params = useParams();
|
|
40
|
+
const type = typeProp ?? params.type ?? '';
|
|
41
|
+
const name = nameProp ?? params.name ?? '';
|
|
36
42
|
const navigate = useNavigate();
|
|
37
43
|
const client = useMetadataClient();
|
|
38
44
|
const { entries } = useMetadataTypes(client);
|
|
@@ -57,6 +63,15 @@ export function MetadataResourceEditPage({ type, name, createMode = false, }) {
|
|
|
57
63
|
const [issues, setIssues] = React.useState([]);
|
|
58
64
|
const [destructiveIssues, setDestructiveIssues] = React.useState(null);
|
|
59
65
|
const [pendingItem, setPendingItem] = React.useState(null);
|
|
66
|
+
// Form edit mode. The form is read-only by default — admins land in a
|
|
67
|
+
// "view" state and must click Edit to mutate, mirroring the Salesforce /
|
|
68
|
+
// Notion convention. createMode is always editing (you can't view what
|
|
69
|
+
// doesn't exist yet). Truly read-only types (no allowOrgOverride) stay
|
|
70
|
+
// read-only regardless.
|
|
71
|
+
const [editing, setEditing] = React.useState(!!createMode);
|
|
72
|
+
// Snapshot of the last saved draft. Used by Cancel to revert in-flight
|
|
73
|
+
// edits, and as the source-of-truth when entering edit mode.
|
|
74
|
+
const draftSnapshotRef = React.useRef(null);
|
|
60
75
|
// Prefetch object name list once — fuels the `ref:object` widget.
|
|
61
76
|
// We don't block render on it; the widget shows a "Loading…" state.
|
|
62
77
|
const [objectNames, setObjectNames] = React.useState([]);
|
|
@@ -102,6 +117,7 @@ export function MetadataResourceEditPage({ type, name, createMode = false, }) {
|
|
|
102
117
|
// Initial draft = effective if available, otherwise code.
|
|
103
118
|
const initial = (lay.effective ?? lay.code ?? {});
|
|
104
119
|
setDraft(initial);
|
|
120
|
+
draftSnapshotRef.current = initial;
|
|
105
121
|
setLoading(false);
|
|
106
122
|
}
|
|
107
123
|
catch (err) {
|
|
@@ -134,6 +150,45 @@ export function MetadataResourceEditPage({ type, name, createMode = false, }) {
|
|
|
134
150
|
setRefsLoading(false);
|
|
135
151
|
}
|
|
136
152
|
}
|
|
153
|
+
// Related drawer state. `null` = closed. We avoid querystring round-
|
|
154
|
+
// trips on every keystroke; URL state is best-effort sync via effect
|
|
155
|
+
// below.
|
|
156
|
+
const [relatedTarget, setRelatedTarget] = React.useState(null);
|
|
157
|
+
const hasAnchors = React.useMemo(() => !createMode && !embedded && listAnchorsFor(type).length > 0, [type, createMode, embedded]);
|
|
158
|
+
// Read ?tab and ?open on first mount so deep-links work. Embedded
|
|
159
|
+
// items are not deep-linkable (they live in the parent body and need
|
|
160
|
+
// the parent payload to materialise) so we only restore metadata
|
|
161
|
+
// targets here.
|
|
162
|
+
const initialTabRef = React.useRef(null);
|
|
163
|
+
React.useEffect(() => {
|
|
164
|
+
if (typeof window === 'undefined' || embedded)
|
|
165
|
+
return;
|
|
166
|
+
const sp = new URLSearchParams(window.location.search);
|
|
167
|
+
const tab = sp.get('tab');
|
|
168
|
+
if (tab)
|
|
169
|
+
initialTabRef.current = tab;
|
|
170
|
+
const open = sp.get('open');
|
|
171
|
+
if (open && open.includes(':')) {
|
|
172
|
+
const [t, n] = open.split(':', 2);
|
|
173
|
+
if (t && n)
|
|
174
|
+
setRelatedTarget({ kind: 'metadata', type: t, name: n });
|
|
175
|
+
}
|
|
176
|
+
// intentionally empty deps — first mount only
|
|
177
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
178
|
+
}, []);
|
|
179
|
+
// Reflect drawer target into the URL so refresh/share works.
|
|
180
|
+
React.useEffect(() => {
|
|
181
|
+
if (typeof window === 'undefined' || embedded)
|
|
182
|
+
return;
|
|
183
|
+
const url = new URL(window.location.href);
|
|
184
|
+
if (relatedTarget?.kind === 'metadata') {
|
|
185
|
+
url.searchParams.set('open', `${relatedTarget.type}:${relatedTarget.name}`);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
url.searchParams.delete('open');
|
|
189
|
+
}
|
|
190
|
+
window.history.replaceState({}, '', url.toString());
|
|
191
|
+
}, [relatedTarget, embedded]);
|
|
137
192
|
async function doSave(force) {
|
|
138
193
|
setSaving(true);
|
|
139
194
|
setError(null);
|
|
@@ -153,17 +208,17 @@ export function MetadataResourceEditPage({ type, name, createMode = false, }) {
|
|
|
153
208
|
// Refresh layered after save.
|
|
154
209
|
const lay = await client.layered(type, savedName);
|
|
155
210
|
setLayered(lay);
|
|
156
|
-
|
|
211
|
+
const fresh = (lay.effective ?? itemToSave);
|
|
212
|
+
setDraft(fresh);
|
|
213
|
+
draftSnapshotRef.current = fresh;
|
|
157
214
|
setDestructiveIssues(null);
|
|
158
215
|
setPendingItem(null);
|
|
216
|
+
// Exit edit mode on successful save (unless we were creating —
|
|
217
|
+
// navigation to the new record's URL will reset state anyway).
|
|
218
|
+
if (!createMode)
|
|
219
|
+
setEditing(false);
|
|
159
220
|
if (createMode) {
|
|
160
|
-
|
|
161
|
-
// matched route pattern (`/apps/:app/component/:ns/:name/*`),
|
|
162
|
-
// which overshoots and lands at `/apps/:app`. Anchor on the
|
|
163
|
-
// location's pathname for predictable behaviour.
|
|
164
|
-
const here = window.location.pathname; // .../resource/new
|
|
165
|
-
const parent = here.replace(/\/new\/?$/, '');
|
|
166
|
-
navigate(`${parent}/${encodeURIComponent(savedName)}?type=${encodeURIComponent(type)}`);
|
|
221
|
+
navigate(`../${encodeURIComponent(savedName)}`);
|
|
167
222
|
}
|
|
168
223
|
}
|
|
169
224
|
catch (err) {
|
|
@@ -223,7 +278,10 @@ export function MetadataResourceEditPage({ type, name, createMode = false, }) {
|
|
|
223
278
|
await client.reset(type, name);
|
|
224
279
|
const lay = await client.layered(type, name);
|
|
225
280
|
setLayered(lay);
|
|
226
|
-
|
|
281
|
+
const fresh = (lay.effective ?? lay.code ?? {});
|
|
282
|
+
setDraft(fresh);
|
|
283
|
+
draftSnapshotRef.current = fresh;
|
|
284
|
+
setEditing(false);
|
|
227
285
|
}
|
|
228
286
|
catch (err) {
|
|
229
287
|
setError(err?.message ?? String(err));
|
|
@@ -238,7 +296,40 @@ export function MetadataResourceEditPage({ type, name, createMode = false, }) {
|
|
|
238
296
|
const schema = entry?.schema ??
|
|
239
297
|
config.defaultSchema;
|
|
240
298
|
const readOnly = !entry?.allowOrgOverride && !createMode;
|
|
241
|
-
|
|
299
|
+
const DesignerTab = !createMode ? customConfig?.DesignerTab : undefined;
|
|
300
|
+
const designerTabLabel = customConfig?.designerTabLabel ?? 'Designer';
|
|
301
|
+
// Preview tab — opt-in via `registerMetadataPreview()`. Hidden in
|
|
302
|
+
// create mode (nothing to preview yet) and inside the embedded
|
|
303
|
+
// drawer (the parent context owns the preview surface).
|
|
304
|
+
const PreviewComponent = !createMode && !embedded ? getMetadataPreview(type) : undefined;
|
|
305
|
+
// Cancel edits: revert the draft to the last saved snapshot and exit
|
|
306
|
+
// edit mode. Safe to call even with no snapshot (no-op).
|
|
307
|
+
function doCancelEdit() {
|
|
308
|
+
if (draftSnapshotRef.current) {
|
|
309
|
+
setDraft(draftSnapshotRef.current);
|
|
310
|
+
}
|
|
311
|
+
setIssues([]);
|
|
312
|
+
setError(null);
|
|
313
|
+
setEditing(false);
|
|
314
|
+
}
|
|
315
|
+
// When the form is "live" but not yet in edit mode, it renders as
|
|
316
|
+
// read-only. createMode is always editing; truly read-only types
|
|
317
|
+
// (no allowOrgOverride) ignore the editing toggle entirely.
|
|
318
|
+
const formReadOnly = readOnly || (!editing && !createMode);
|
|
319
|
+
// Default tab priority:
|
|
320
|
+
// 1. URL ?tab= (explicit user nav / deep link)
|
|
321
|
+
// 2. Designer (custom rich editor present)
|
|
322
|
+
// 3. Preview (live preview present — most informative landing)
|
|
323
|
+
// 4. Form
|
|
324
|
+
const defaultTab = initialTabRef.current ??
|
|
325
|
+
(DesignerTab ? 'designer' : PreviewComponent ? 'preview' : 'form');
|
|
326
|
+
return (_jsxs(PageShell, { entry: entry ?? { type, label: type }, itemName: createMode ? '(new)' : name, subtitle: createMode ? 'Create new' : 'Edit overlay', actions: _jsxs(_Fragment, { children: [!createMode && entry?.allowOrgOverride && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: doReset, disabled: saving, children: [_jsx(RotateCcw, { className: "h-4 w-4 mr-1" }), "Reset overlay"] })), !createMode && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => navigate(`./history`), children: [_jsx(History, { className: "h-4 w-4 mr-1" }), "History"] })), entry?.allowOrgOverride && !createMode && !editing && (_jsxs(Button, { size: "sm", onClick: () => setEditing(true), children: [_jsx(Pencil, { className: "h-4 w-4 mr-1" }), "Edit"] })), entry?.allowOrgOverride && !createMode && editing && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: doCancelEdit, disabled: saving, children: [_jsx(X, { className: "h-4 w-4 mr-1" }), "Cancel"] })), entry?.allowOrgOverride && (editing || createMode) && (_jsxs(Button, { size: "sm", onClick: () => doSave(false), disabled: saving, children: [saving ? (_jsx(Loader2, { className: "h-4 w-4 mr-1 animate-spin" })) : (_jsx(Save, { className: "h-4 w-4 mr-1" })), "Save"] }))] }), children: [_jsxs("div", { className: "p-6 space-y-6 max-w-7xl", children: [error && (_jsx("div", { className: "text-sm text-destructive border border-destructive/30 rounded p-3 bg-destructive/5", children: error })), readOnly && (_jsxs("div", { className: "text-xs text-amber-800 border border-amber-300 bg-amber-50 rounded p-3", children: ["This type is read-only. To enable runtime editing, set", ' ', _jsx("code", { className: "font-mono", children: "OBJECTSTACK_METADATA_WRITABLE" }), " to include ", _jsx("code", { className: "font-mono", children: type }), ", or flip", ' ', _jsx("code", { className: "font-mono", children: "allowOrgOverride" }), " in the registry."] })), _jsxs(Tabs, { defaultValue: defaultTab, className: "w-full", onValueChange: (v) => {
|
|
327
|
+
if (typeof window === 'undefined' || embedded)
|
|
328
|
+
return;
|
|
329
|
+
const url = new URL(window.location.href);
|
|
330
|
+
url.searchParams.set('tab', v);
|
|
331
|
+
window.history.replaceState({}, '', url.toString());
|
|
332
|
+
}, children: [_jsxs(TabsList, { children: [DesignerTab && (_jsx(TabsTrigger, { value: "designer", children: designerTabLabel })), PreviewComponent && (_jsxs(TabsTrigger, { value: "preview", children: [_jsx(Eye, { className: "h-3.5 w-3.5 mr-1" }), "Preview"] })), _jsx(TabsTrigger, { value: "form", children: "Detail" }), !createMode && (_jsxs(TabsTrigger, { value: "layers", children: ["Layers", layered?.overlay && (_jsx(Badge, { className: "ml-1.5 text-[10px] bg-emerald-600 text-emerald-50", children: "overlay" }))] })), !createMode && (_jsxs(TabsTrigger, { value: "references", onClick: loadReferences, children: [_jsx(Link2, { className: "h-3.5 w-3.5 mr-1" }), "References", refs && (_jsx(Badge, { variant: "outline", className: "ml-1.5 text-[10px]", children: refs.length }))] })), hasAnchors && (_jsxs(TabsTrigger, { value: "related", children: [_jsx(Layers3, { className: "h-3.5 w-3.5 mr-1" }), "Related"] }))] }), DesignerTab && (_jsx(TabsContent, { value: "designer", className: "mt-4", children: _jsx(DesignerTab, { type: type, name: name }) })), _jsxs(TabsContent, { value: "form", className: "mt-4 space-y-3", children: [formReadOnly && !readOnly && entry?.allowOrgOverride && !createMode && (_jsxs("div", { className: "flex items-center justify-between gap-3 text-xs text-muted-foreground border rounded p-2.5 bg-muted/30", children: [_jsxs("span", { children: ["Viewing in read-only mode. Click ", _jsx("strong", { children: "Edit" }), " to make changes."] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => setEditing(true), children: [_jsx(Pencil, { className: "h-3.5 w-3.5 mr-1" }), "Edit"] })] })), _jsx(SchemaForm, { schema: schema, form: entry?.form, value: draft, onChange: setDraft, issues: issues, hiddenFields: config.hiddenFields, fieldOrder: config.fieldOrder, readOnly: formReadOnly, createMode: createMode, widgetContext: widgetContext })] }), PreviewComponent && (_jsx(TabsContent, { value: "preview", className: "mt-4", children: _jsx(PreviewComponent, { type: type, name: name, draft: draft }) })), !createMode && (_jsx(TabsContent, { value: "layers", className: "mt-4", children: _jsx(LayeredDiff, { layered: layered }) })), !createMode && (_jsx(TabsContent, { value: "references", className: "mt-4", children: _jsx(ReferencesPanel, { refs: refs, loading: refsLoading }) })), hasAnchors && (_jsx(TabsContent, { value: "related", className: "mt-4", children: _jsx(RelatedPanel, { type: type, name: name, parentItem: draft, onOpen: (t) => setRelatedTarget(t) }) }))] })] }), _jsx(MetadataDetailDrawer, { target: relatedTarget, onClose: () => setRelatedTarget(null), parentContext: { type, name } }), _jsx(Dialog, { open: destructiveIssues != null, onOpenChange: (open) => {
|
|
242
333
|
if (!open) {
|
|
243
334
|
setDestructiveIssues(null);
|
|
244
335
|
setPendingItem(null);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface MetadataResourceHistoryPageProps {
|
|
2
|
-
type
|
|
3
|
-
name
|
|
2
|
+
type?: string;
|
|
3
|
+
name?: string;
|
|
4
4
|
}
|
|
5
|
-
export declare function MetadataResourceHistoryPage({ type, name, }: MetadataResourceHistoryPageProps): import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
export declare function MetadataResourceHistoryPage({ type: typeProp, name: nameProp, }: MetadataResourceHistoryPageProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -15,14 +15,17 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
15
15
|
* once admins ask for it.
|
|
16
16
|
*/
|
|
17
17
|
import * as React from 'react';
|
|
18
|
-
import { useNavigate } from 'react-router-dom';
|
|
18
|
+
import { useNavigate, useParams } from 'react-router-dom';
|
|
19
19
|
import { ArrowLeft, RefreshCw, Loader2 } from 'lucide-react';
|
|
20
20
|
import { Button } from '@object-ui/components';
|
|
21
21
|
import { Badge } from '@object-ui/components';
|
|
22
22
|
import { Empty, EmptyTitle, EmptyDescription } from '@object-ui/components';
|
|
23
23
|
import { PageShell } from './PageShell';
|
|
24
24
|
import { useMetadataClient, useMetadataTypes, } from './useMetadata';
|
|
25
|
-
export function MetadataResourceHistoryPage({ type, name, }) {
|
|
25
|
+
export function MetadataResourceHistoryPage({ type: typeProp, name: nameProp, }) {
|
|
26
|
+
const params = useParams();
|
|
27
|
+
const type = typeProp ?? params.type ?? '';
|
|
28
|
+
const name = nameProp ?? params.name ?? '';
|
|
26
29
|
const navigate = useNavigate();
|
|
27
30
|
const client = useMetadataClient();
|
|
28
31
|
const { entries } = useMetadataTypes(client);
|
|
@@ -62,7 +65,7 @@ export function MetadataResourceHistoryPage({ type, name, }) {
|
|
|
62
65
|
cancelled = true;
|
|
63
66
|
};
|
|
64
67
|
}, [client, type, name, refreshKey]);
|
|
65
|
-
return (_jsx(PageShell, { entry: entry ?? { type, label: type }, itemName: name, subtitle: "Version history", stats: [{ label: 'Events', value: events.length }], actions: _jsxs(_Fragment, { children: [_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => navigate(`../${encodeURIComponent(name)}
|
|
68
|
+
return (_jsx(PageShell, { entry: entry ?? { type, label: type }, itemName: name, subtitle: "Version history", stats: [{ label: 'Events', value: events.length }], actions: _jsxs(_Fragment, { children: [_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => navigate(`../${encodeURIComponent(name)}`), children: [_jsx(ArrowLeft, { className: "h-4 w-4 mr-1" }), "Back to item"] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setRefreshKey((k) => k + 1), children: _jsx(RefreshCw, { className: "h-4 w-4" }) })] }), children: _jsxs("div", { className: "p-6 max-w-4xl", children: [loading && (_jsxs("div", { className: "text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Loading history\u2026"] })), error && (_jsx("div", { className: "text-sm text-destructive border border-destructive/30 rounded p-3 bg-destructive/5", children: error })), !loading && !error && events.length === 0 && (_jsxs(Empty, { children: [_jsx(EmptyTitle, { children: "No history yet" }), _jsx(EmptyDescription, { children: "This item has never been edited via an overlay. The first save will create the initial history record." })] })), !loading && events.length > 0 && (_jsx("ol", { className: "space-y-2 relative pl-6 border-l", children: events.map((ev, i) => {
|
|
66
69
|
const isOpen = expanded === i;
|
|
67
70
|
return (_jsxs("li", { className: "relative", children: [_jsx("span", { className: "absolute -left-[27px] top-2 w-3 h-3 rounded-full bg-primary ring-4 ring-background" }), _jsxs("div", { role: "button", tabIndex: 0, onClick: () => setExpanded(isOpen ? null : i), onKeyDown: (e) => {
|
|
68
71
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export interface MetadataResourceListPageProps {
|
|
2
|
-
type
|
|
2
|
+
type?: string;
|
|
3
3
|
}
|
|
4
|
-
export declare function MetadataResourceListPage({ type }: MetadataResourceListPageProps): import("react/jsx-runtime").JSX.Element;
|
|
4
|
+
export declare function MetadataResourceListPage({ type: typeProp }: MetadataResourceListPageProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -12,7 +12,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
12
12
|
* per type, well under the threshold where it'd matter.
|
|
13
13
|
*/
|
|
14
14
|
import * as React from 'react';
|
|
15
|
-
import { Link, useNavigate } from 'react-router-dom';
|
|
15
|
+
import { Link, useNavigate, useParams } from 'react-router-dom';
|
|
16
16
|
import { Plus, Search, RefreshCw } from 'lucide-react';
|
|
17
17
|
import { Button } from '@object-ui/components';
|
|
18
18
|
import { Input } from '@object-ui/components';
|
|
@@ -23,7 +23,9 @@ import { PageShell } from './PageShell';
|
|
|
23
23
|
import { useMetadataClient, useMetadataTypes, matchesQuery, } from './useMetadata';
|
|
24
24
|
import { getMetadataResource, resolveResourceConfig, } from './registry';
|
|
25
25
|
import { t, detectLocale } from './i18n';
|
|
26
|
-
export function MetadataResourceListPage({ type }) {
|
|
26
|
+
export function MetadataResourceListPage({ type: typeProp }) {
|
|
27
|
+
const params = useParams();
|
|
28
|
+
const type = typeProp ?? params.type ?? '';
|
|
27
29
|
const navigate = useNavigate();
|
|
28
30
|
const client = useMetadataClient();
|
|
29
31
|
const { entries: typesEntries } = useMetadataTypes(client);
|
|
@@ -101,7 +103,7 @@ export function MetadataResourceListPage({ type }) {
|
|
|
101
103
|
return (_jsx(PageShell, { entry: entry ?? { type, label: type }, stats: [
|
|
102
104
|
{ label: t('engine.list.items', locale), value: items.length },
|
|
103
105
|
{ label: t('engine.list.filtered', locale), value: filtered.length },
|
|
104
|
-
], actions: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setRefreshKey((k) => k + 1), title: t('engine.list.refresh', locale), children: _jsx(RefreshCw, { className: "h-4 w-4" }) }), entry?.allowOrgOverride && (_jsxs(Button, { size: "sm", onClick: () => navigate(`./new
|
|
106
|
+
], actions: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: () => setRefreshKey((k) => k + 1), title: t('engine.list.refresh', locale), children: _jsx(RefreshCw, { className: "h-4 w-4" }) }), entry?.allowOrgOverride && (_jsxs(Button, { size: "sm", onClick: () => navigate(`./new`), children: [_jsx(Plus, { className: "h-4 w-4 mr-1" }), t('engine.list.create', locale)] }))] }), children: _jsxs("div", { className: "p-6 space-y-4", children: [_jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [_jsxs("div", { className: "relative flex-1 min-w-[200px] max-w-md", children: [_jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" }), _jsx(Input, { className: "pl-8", placeholder: t('engine.list.search', locale), value: query, onChange: (e) => setQuery(e.target.value) })] }), _jsxs(Select, { value: sourceFilter, onValueChange: setSourceFilter, children: [_jsx(SelectTrigger, { className: "w-[180px]", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsxs(SelectItem, { value: "all", children: [t('engine.list.allSources', locale), " (", sourceCounts.all, ")"] }), _jsxs(SelectItem, { value: "code", children: ["Code (", sourceCounts.code, ")"] }), _jsxs(SelectItem, { value: "overlay", children: ["Overlay (", sourceCounts.overlay, ")"] }), _jsxs(SelectItem, { value: "effective", children: ["Effective (", sourceCounts.effective, ")"] })] })] })] }), loading && (_jsxs("div", { className: "text-sm text-muted-foreground", children: [t('engine.edit.loading', locale), " ", type, "\u2026"] })), error && (_jsx("div", { className: "text-sm text-destructive border border-destructive/30 rounded p-3 bg-destructive/5", children: error })), !loading && !error && filtered.length === 0 && (_jsxs(Empty, { children: [_jsx(EmptyTitle, { children: items.length === 0
|
|
105
107
|
? `No ${type} items registered`
|
|
106
108
|
: `No matches for "${query}"` }), _jsx(EmptyDescription, { children: config.emptyStateHint ??
|
|
107
109
|
(entry?.allowOrgOverride
|
|
@@ -112,7 +114,7 @@ export function MetadataResourceListPage({ type }) {
|
|
|
112
114
|
return (_jsxs("tr", { className: "hover:bg-accent/50", children: [columns.map((c, ci) => {
|
|
113
115
|
const value = row.item[c.key];
|
|
114
116
|
const cell = c.render ? c.render(value, row.item) : defaultCell(value);
|
|
115
|
-
return (_jsx("td", { className: "px-3 py-2 align-top", children: ci === 0 ? (_jsx(Link, { to: `./${encodeURIComponent(name)}
|
|
117
|
+
return (_jsx("td", { className: "px-3 py-2 align-top", children: ci === 0 ? (_jsx(Link, { to: `./${encodeURIComponent(name)}`, className: "text-primary hover:underline font-mono", children: cell })) : (cell) }, c.key));
|
|
116
118
|
}), _jsx("td", { className: "px-3 py-2 text-right align-top", children: row.source ? (_jsx(Badge, { variant: "outline", className: 'text-[10px] ' +
|
|
117
119
|
(row.source === 'overlay'
|
|
118
120
|
? 'border-emerald-500 text-emerald-700'
|
|
@@ -321,7 +321,16 @@ function FieldControl({ id, schema, value, onChange, readOnly, widget, fieldSpec
|
|
|
321
321
|
}
|
|
322
322
|
// Boolean → Switch (no redundant "true/false" text; the toggle state
|
|
323
323
|
// already conveys the value).
|
|
324
|
-
|
|
324
|
+
//
|
|
325
|
+
// We must also honor `widget === 'switch'` (resolved by inferWidget from
|
|
326
|
+
// `fieldSpec.type === 'boolean'` / `'toggle'`), because for composite
|
|
327
|
+
// sub-fields the JSON schema fragment is often `{}` — the parent declares
|
|
328
|
+
// `additionalProperties: true` and no per-property `properties`, so
|
|
329
|
+
// `schema?.type` is undefined even though the form spec clearly marks
|
|
330
|
+
// the sub-field as boolean. Without this, capability toggles inside the
|
|
331
|
+
// Object editor's "Capabilities" section fell through to RawJsonEditor
|
|
332
|
+
// and rendered as empty textareas.
|
|
333
|
+
if (schema?.type === 'boolean' || widget === 'switch' || fieldSpec?.type === 'boolean' || fieldSpec?.type === 'toggle') {
|
|
325
334
|
return (_jsx(Switch, { id: id, checked: !!value, onCheckedChange: (c) => onChange(c), disabled: readOnly }));
|
|
326
335
|
}
|
|
327
336
|
// Number / integer → numeric input with min/max from fieldSpec.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerBuiltinAnchors(): void;
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
/**
|
|
3
|
+
* Built-in anchor relationships for the Related tab.
|
|
4
|
+
*
|
|
5
|
+
* Each entry tells the metadata-admin engine "items of type X belong to
|
|
6
|
+
* (are anchored at) items of type Y when this predicate matches". The
|
|
7
|
+
* Related panel on the parent's edit page uses this registry to decide
|
|
8
|
+
* which `client.list(type)` calls to make and how to group the results.
|
|
9
|
+
*
|
|
10
|
+
* Adding a new relationship is intentionally a one-liner — declare it
|
|
11
|
+
* here (or in your own plugin's bootstrap) and the UI picks it up with
|
|
12
|
+
* no further wiring. Predicates are kept tiny because they run client-
|
|
13
|
+
* side over every item returned from `list()`.
|
|
14
|
+
*
|
|
15
|
+
* Why not parse the schema and infer this? Some types nest the anchor
|
|
16
|
+
* deep in `data.object` or `list.data.object`; some types live on the
|
|
17
|
+
* parent (e.g. `fields` is embedded inside the object item itself).
|
|
18
|
+
* Hand-rolled declarations are clearer than schema-walking heuristics.
|
|
19
|
+
*/
|
|
20
|
+
import { registerMetadataResource, anchorByField } from './registry';
|
|
21
|
+
export function registerBuiltinAnchors() {
|
|
22
|
+
// ── EMBEDDED: items stored inside the object body itself ──────────
|
|
23
|
+
//
|
|
24
|
+
// Fields, indexes, and embedded validations don't have a top-level
|
|
25
|
+
// metadata type; they live under `object.fields`, `object.indexes[]`,
|
|
26
|
+
// `object.validations[]`. We surface them with `source: 'embedded'`
|
|
27
|
+
// so the Related panel can list them without a separate API call.
|
|
28
|
+
//
|
|
29
|
+
// `fields` is stored as a NAME-KEYED MAP (`{ email: {...}, name: {...} }`)
|
|
30
|
+
// — not an array — so we adapt it into an array carrying the key as
|
|
31
|
+
// `name` for the panel to render.
|
|
32
|
+
registerMetadataResource({
|
|
33
|
+
type: '__object_field',
|
|
34
|
+
label: 'Field',
|
|
35
|
+
anchors: [{
|
|
36
|
+
anchorType: 'object',
|
|
37
|
+
source: 'embedded',
|
|
38
|
+
editAs: 'field',
|
|
39
|
+
embeddedPath: 'fields',
|
|
40
|
+
extract: (parent) => mapOrArrayToList(parent.fields),
|
|
41
|
+
groupLabel: 'Fields',
|
|
42
|
+
order: 5,
|
|
43
|
+
}],
|
|
44
|
+
});
|
|
45
|
+
registerMetadataResource({
|
|
46
|
+
type: '__object_index',
|
|
47
|
+
label: 'Index',
|
|
48
|
+
anchors: [{
|
|
49
|
+
anchorType: 'object',
|
|
50
|
+
source: 'embedded',
|
|
51
|
+
editAs: 'index',
|
|
52
|
+
embeddedPath: 'indexes',
|
|
53
|
+
extract: (parent) => {
|
|
54
|
+
const raw = parent.indexes;
|
|
55
|
+
if (!Array.isArray(raw))
|
|
56
|
+
return [];
|
|
57
|
+
// Indexes have no `name`; synthesise one from their column list.
|
|
58
|
+
return raw.map((idx, i) => {
|
|
59
|
+
const fields = Array.isArray(idx.fields) ? idx.fields.join(',') : '';
|
|
60
|
+
const synthesised = fields ? `idx_${fields}` : `index_${i}`;
|
|
61
|
+
return { name: synthesised, ...idx };
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
groupLabel: 'Indexes',
|
|
65
|
+
order: 10,
|
|
66
|
+
}],
|
|
67
|
+
});
|
|
68
|
+
registerMetadataResource({
|
|
69
|
+
type: '__object_validation',
|
|
70
|
+
label: 'Embedded validation',
|
|
71
|
+
anchors: [{
|
|
72
|
+
anchorType: 'object',
|
|
73
|
+
source: 'embedded',
|
|
74
|
+
editAs: 'validation',
|
|
75
|
+
embeddedPath: 'validations',
|
|
76
|
+
extract: (parent) => mapOrArrayToList(parent.validations),
|
|
77
|
+
groupLabel: 'Embedded Validations',
|
|
78
|
+
order: 15,
|
|
79
|
+
}],
|
|
80
|
+
});
|
|
81
|
+
// ── LIST: standalone child metadata types ─────────────────────────
|
|
82
|
+
// hook.object → object (beforeInsert / afterUpdate / …)
|
|
83
|
+
registerMetadataResource({
|
|
84
|
+
type: 'hook',
|
|
85
|
+
anchors: [{
|
|
86
|
+
anchorType: 'object',
|
|
87
|
+
match: anchorByField('object'),
|
|
88
|
+
groupLabel: 'Hooks',
|
|
89
|
+
order: 20,
|
|
90
|
+
}],
|
|
91
|
+
});
|
|
92
|
+
// approval.object → object (approval processes targeting this object)
|
|
93
|
+
registerMetadataResource({
|
|
94
|
+
type: 'approval',
|
|
95
|
+
anchors: [{
|
|
96
|
+
anchorType: 'object',
|
|
97
|
+
match: anchorByField('object'),
|
|
98
|
+
groupLabel: 'Approval Processes',
|
|
99
|
+
order: 70,
|
|
100
|
+
}],
|
|
101
|
+
});
|
|
102
|
+
// page.object → object (auto-generated record pages, etc.)
|
|
103
|
+
registerMetadataResource({
|
|
104
|
+
type: 'page',
|
|
105
|
+
anchors: [{
|
|
106
|
+
anchorType: 'object',
|
|
107
|
+
match: anchorByField('object'),
|
|
108
|
+
groupLabel: 'Pages',
|
|
109
|
+
order: 40,
|
|
110
|
+
}],
|
|
111
|
+
});
|
|
112
|
+
// view binds to an object via data.object (list view / form view variants)
|
|
113
|
+
registerMetadataResource({
|
|
114
|
+
type: 'view',
|
|
115
|
+
anchors: [{
|
|
116
|
+
anchorType: 'object',
|
|
117
|
+
match: anchorByField([
|
|
118
|
+
'data.object',
|
|
119
|
+
'list.data.object',
|
|
120
|
+
'form.data.object',
|
|
121
|
+
'object',
|
|
122
|
+
]),
|
|
123
|
+
groupLabel: 'Views',
|
|
124
|
+
order: 30,
|
|
125
|
+
}],
|
|
126
|
+
});
|
|
127
|
+
// flow / workflow may reference an object at the root or under `on`
|
|
128
|
+
registerMetadataResource({
|
|
129
|
+
type: 'flow',
|
|
130
|
+
anchors: [{
|
|
131
|
+
anchorType: 'object',
|
|
132
|
+
match: anchorByField(['object', 'on.object', 'trigger.object']),
|
|
133
|
+
groupLabel: 'Flows',
|
|
134
|
+
order: 50,
|
|
135
|
+
}],
|
|
136
|
+
});
|
|
137
|
+
registerMetadataResource({
|
|
138
|
+
type: 'workflow',
|
|
139
|
+
anchors: [{
|
|
140
|
+
anchorType: 'object',
|
|
141
|
+
match: anchorByField(['object', 'on.object']),
|
|
142
|
+
groupLabel: 'Workflow Rules',
|
|
143
|
+
order: 51,
|
|
144
|
+
}],
|
|
145
|
+
});
|
|
146
|
+
// trigger.object → object (low-level DB-style triggers)
|
|
147
|
+
registerMetadataResource({
|
|
148
|
+
type: 'trigger',
|
|
149
|
+
anchors: [{
|
|
150
|
+
anchorType: 'object',
|
|
151
|
+
match: anchorByField(['object', 'on.object']),
|
|
152
|
+
groupLabel: 'Triggers',
|
|
153
|
+
order: 52,
|
|
154
|
+
}],
|
|
155
|
+
});
|
|
156
|
+
// validation: usually embedded in the object, but standalone variants
|
|
157
|
+
// do exist. Match anything whose `object` points back at us.
|
|
158
|
+
registerMetadataResource({
|
|
159
|
+
type: 'validation',
|
|
160
|
+
anchors: [{
|
|
161
|
+
anchorType: 'object',
|
|
162
|
+
match: anchorByField('object'),
|
|
163
|
+
groupLabel: 'Validations',
|
|
164
|
+
order: 25,
|
|
165
|
+
}],
|
|
166
|
+
});
|
|
167
|
+
// permission has a sparse object-keyed map under `objects`. Match by
|
|
168
|
+
// membership of the parent name in that map.
|
|
169
|
+
registerMetadataResource({
|
|
170
|
+
type: 'permission',
|
|
171
|
+
anchors: [{
|
|
172
|
+
anchorType: 'object',
|
|
173
|
+
match: (item, name) => {
|
|
174
|
+
const objs = item?.objects;
|
|
175
|
+
return !!objs && typeof objs === 'object' && name in objs;
|
|
176
|
+
},
|
|
177
|
+
groupLabel: 'Permissions',
|
|
178
|
+
order: 60,
|
|
179
|
+
}],
|
|
180
|
+
});
|
|
181
|
+
// action — both record-scoped and object-scoped actions live here.
|
|
182
|
+
// Schemas vary, so accept any of the three common shapes.
|
|
183
|
+
registerMetadataResource({
|
|
184
|
+
type: 'action',
|
|
185
|
+
anchors: [{
|
|
186
|
+
anchorType: 'object',
|
|
187
|
+
match: anchorByField([
|
|
188
|
+
'object',
|
|
189
|
+
'target.object',
|
|
190
|
+
'on.object',
|
|
191
|
+
]),
|
|
192
|
+
groupLabel: 'Actions',
|
|
193
|
+
order: 55,
|
|
194
|
+
}],
|
|
195
|
+
});
|
|
196
|
+
// dashboard / report — surface ones bound to a specific object.
|
|
197
|
+
// Many will not have an explicit anchor; those simply don't appear.
|
|
198
|
+
registerMetadataResource({
|
|
199
|
+
type: 'dashboard',
|
|
200
|
+
anchors: [{
|
|
201
|
+
anchorType: 'object',
|
|
202
|
+
match: anchorByField(['object', 'data.object']),
|
|
203
|
+
groupLabel: 'Dashboards',
|
|
204
|
+
order: 80,
|
|
205
|
+
}],
|
|
206
|
+
});
|
|
207
|
+
registerMetadataResource({
|
|
208
|
+
type: 'report',
|
|
209
|
+
anchors: [{
|
|
210
|
+
anchorType: 'object',
|
|
211
|
+
match: anchorByField(['object', 'data.object']),
|
|
212
|
+
groupLabel: 'Reports',
|
|
213
|
+
order: 81,
|
|
214
|
+
}],
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Coerce an embedded "collection" into a list of `{ name, …rest }`.
|
|
219
|
+
*
|
|
220
|
+
* ObjectStack stores some embedded collections as name-keyed maps
|
|
221
|
+
* (`object.fields`) and others as arrays (`object.indexes`). Both want
|
|
222
|
+
* to render the same way in Related; this helper papers over the gap
|
|
223
|
+
* by injecting the map key as `name` when needed.
|
|
224
|
+
*/
|
|
225
|
+
function mapOrArrayToList(raw) {
|
|
226
|
+
if (Array.isArray(raw))
|
|
227
|
+
return raw;
|
|
228
|
+
if (raw && typeof raw === 'object') {
|
|
229
|
+
return Object.entries(raw).map(([k, v]) => {
|
|
230
|
+
const body = v && typeof v === 'object' ? v : {};
|
|
231
|
+
return { name: k, ...body };
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
@@ -15,17 +15,21 @@ export { MetadataDirectoryPage } from './DirectoryPage';
|
|
|
15
15
|
export { MetadataResourceRouter } from './ResourceRouter';
|
|
16
16
|
export { MetadataResourceListPage } from './ResourceListPage';
|
|
17
17
|
export { MetadataResourceEditPage } from './ResourceEditPage';
|
|
18
|
+
export { RelatedPanel } from './RelatedPanel';
|
|
19
|
+
export { MetadataDetailDrawer } from './MetadataDetailDrawer';
|
|
18
20
|
export { MetadataResourceHistoryPage } from './ResourceHistoryPage';
|
|
19
21
|
export { MetadataQuickFind } from './QuickFind';
|
|
20
22
|
export { PageShell as MetadataPageShell } from './PageShell';
|
|
21
23
|
export { SchemaForm } from './SchemaForm';
|
|
22
24
|
export { LayeredDiff } from './LayeredDiff';
|
|
23
25
|
export { PermissionMatrixEditPage } from './PermissionMatrixEditor';
|
|
24
|
-
export { DesignerEditorWrapper } from './DesignerEditorWrapper';
|
|
26
|
+
export { DesignerEditorWrapper, DesignerEditorBody } from './DesignerEditorWrapper';
|
|
25
27
|
export type { DesignerEditorWrapperProps } from './DesignerEditorWrapper';
|
|
26
28
|
export { translateMetadataType, translateMetadataDomain, t as translateMetadataAdmin, detectLocale, } from './i18n';
|
|
27
29
|
export type { SupportedLocale } from './i18n';
|
|
28
|
-
export { registerMetadataResource, getMetadataResource, listMetadataResources, resolveResourceConfig, } from './registry';
|
|
29
|
-
export type { MetadataResourceConfig, MetadataDomain, } from './registry';
|
|
30
|
+
export { registerMetadataResource, getMetadataResource, listMetadataResources, listAnchorsFor, resolveResourceConfig, anchorByField, } from './registry';
|
|
31
|
+
export type { MetadataResourceConfig, MetadataDomain, MetadataAnchor, } from './registry';
|
|
32
|
+
export { registerMetadataPreview, getMetadataPreview, listMetadataPreviewTypes, } from './preview-registry';
|
|
33
|
+
export type { MetadataPreview, MetadataPreviewProps } from './preview-registry';
|
|
30
34
|
export { useMetadataClient, useMetadataTypes, useTypesIndex, matchesQuery, } from './useMetadata';
|
|
31
35
|
export type { RichMetadataTypeEntry } from './useMetadata';
|
|
@@ -16,18 +16,30 @@ export { MetadataDirectoryPage } from './DirectoryPage';
|
|
|
16
16
|
export { MetadataResourceRouter } from './ResourceRouter';
|
|
17
17
|
export { MetadataResourceListPage } from './ResourceListPage';
|
|
18
18
|
export { MetadataResourceEditPage } from './ResourceEditPage';
|
|
19
|
+
export { RelatedPanel } from './RelatedPanel';
|
|
20
|
+
export { MetadataDetailDrawer } from './MetadataDetailDrawer';
|
|
19
21
|
export { MetadataResourceHistoryPage } from './ResourceHistoryPage';
|
|
20
22
|
export { MetadataQuickFind } from './QuickFind';
|
|
21
23
|
export { PageShell as MetadataPageShell } from './PageShell';
|
|
22
24
|
export { SchemaForm } from './SchemaForm';
|
|
23
25
|
export { LayeredDiff } from './LayeredDiff';
|
|
24
26
|
export { PermissionMatrixEditPage } from './PermissionMatrixEditor';
|
|
25
|
-
export { DesignerEditorWrapper } from './DesignerEditorWrapper';
|
|
27
|
+
export { DesignerEditorWrapper, DesignerEditorBody } from './DesignerEditorWrapper';
|
|
26
28
|
export { translateMetadataType, translateMetadataDomain, t as translateMetadataAdmin, detectLocale, } from './i18n';
|
|
27
|
-
export { registerMetadataResource, getMetadataResource, listMetadataResources, resolveResourceConfig, } from './registry';
|
|
29
|
+
export { registerMetadataResource, getMetadataResource, listMetadataResources, listAnchorsFor, resolveResourceConfig, anchorByField, } from './registry';
|
|
30
|
+
// Side-effect: register the built-in anchor relationships so the Related
|
|
31
|
+
// tab works out of the box for objects (hooks, views, pages, …).
|
|
32
|
+
import { registerBuiltinAnchors } from './anchors';
|
|
33
|
+
registerBuiltinAnchors();
|
|
28
34
|
// Side-effect: register fallback JSONSchemas for the 12 writable types
|
|
29
35
|
// so the generic SchemaForm renders a real form (vs raw-JSON fallback)
|
|
30
36
|
// until the framework wires Zod→JSONSchema generation into /meta/types.
|
|
31
37
|
import { registerDefaultMetadataSchemas } from './default-schemas';
|
|
32
38
|
registerDefaultMetadataSchemas();
|
|
39
|
+
// Side-effect: register built-in Preview-tab renderers (page, view,
|
|
40
|
+
// dashboard, report, app, object, email_template). Plugins can add or
|
|
41
|
+
// override entries via `registerMetadataPreview()`.
|
|
42
|
+
import { registerBuiltinPreviews } from './previews';
|
|
43
|
+
registerBuiltinPreviews();
|
|
44
|
+
export { registerMetadataPreview, getMetadataPreview, listMetadataPreviewTypes, } from './preview-registry';
|
|
33
45
|
export { useMetadataClient, useMetadataTypes, useTypesIndex, matchesQuery, } from './useMetadata';
|