@vertebr/react 0.10.0 → 0.12.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/dist/components/admin/AddonEditor.d.ts.map +1 -1
- package/dist/components/admin/AddonEditor.js +10 -8
- package/dist/components/admin/AdminContent.d.ts.map +1 -1
- package/dist/components/admin/AdminContent.js +62 -17
- package/dist/components/admin/AdminSaveBar.d.ts.map +1 -1
- package/dist/components/admin/AdminSaveBar.js +6 -1
- package/dist/components/admin/AdminSyncPage.d.ts.map +1 -1
- package/dist/components/admin/AdminSyncPage.js +50 -6
- package/dist/components/admin/PagePreviewPane.d.ts +3 -1
- package/dist/components/admin/PagePreviewPane.d.ts.map +1 -1
- package/dist/components/admin/PagePreviewPane.js +41 -9
- package/dist/components/admin/SyncPreviewFrame.d.ts +22 -0
- package/dist/components/admin/SyncPreviewFrame.d.ts.map +1 -0
- package/dist/components/admin/SyncPreviewFrame.js +135 -0
- package/dist/components/admin/VertebrAdmin.d.ts.map +1 -1
- package/dist/components/admin/VertebrAdmin.js +6 -4
- package/dist/components/config/DatetimeInput.d.ts +15 -0
- package/dist/components/config/DatetimeInput.d.ts.map +1 -0
- package/dist/components/config/DatetimeInput.js +287 -0
- package/dist/components/config/EnumInput.d.ts.map +1 -1
- package/dist/components/config/EnumInput.js +4 -4
- package/dist/components/config/EnumListInput.d.ts.map +1 -1
- package/dist/components/config/EnumListInput.js +13 -3
- package/dist/components/config/ImagePickerModal.d.ts +9 -1
- package/dist/components/config/ImagePickerModal.d.ts.map +1 -1
- package/dist/components/config/ImagePickerModal.js +14 -7
- package/dist/components/config/RevisionHistory.d.ts +4 -0
- package/dist/components/config/RevisionHistory.d.ts.map +1 -1
- package/dist/components/config/RevisionHistory.js +63 -8
- package/dist/components/config/form/ConfigField.d.ts.map +1 -1
- package/dist/components/config/form/ConfigField.js +41 -16
- package/dist/components/config/form/ConfigForm.d.ts +1 -1
- package/dist/components/config/form/ConfigForm.d.ts.map +1 -1
- package/dist/components/config/form/ConfigForm.js +54 -9
- package/dist/components/config/form/RecordConfigForm.d.ts.map +1 -1
- package/dist/components/config/form/RecordConfigForm.js +24 -10
- package/dist/components/config/form/types.d.ts +9 -0
- package/dist/components/config/form/types.d.ts.map +1 -1
- package/dist/components/fonts/FontLibraryBoard.d.ts.map +1 -1
- package/dist/components/fonts/FontLibraryBoard.js +1 -1
- package/dist/components/resources/ImageUploadControl.d.ts +3 -1
- package/dist/components/resources/ImageUploadControl.d.ts.map +1 -1
- package/dist/components/resources/ImageUploadControl.js +5 -3
- package/dist/hooks/useSaveBar.d.ts +6 -0
- package/dist/hooks/useSaveBar.d.ts.map +1 -1
- package/dist/styles/form.css +465 -17
- package/dist/styles/modal.css +2 -1
- package/dist/styles/page-preview.css +84 -0
- package/dist/styles/select.css +23 -0
- package/dist/styles/sync.css +388 -4
- package/dist/themes/light.css +27 -0
- package/package.json +3 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AddonEditor.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AddonEditor.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,aAAa,EACd,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"AddonEditor.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AddonEditor.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EACf,cAAc,EACd,aAAa,EACd,MAAM,iBAAiB,CAAC;AAMzB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,eAAe,CAAC;IACzB,KAAK,EAAE,YAAY,CAAC;IACpB,UAAU,EAAE,cAAc,CAAC;IAC3B,aAAa,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9C,aAAa,EAAE,iBAAiB,CAAC,eAAe,CAAC,CAAC;IAClD,iBAAiB,EAAE,iBAAiB,CAAC,mBAAmB,CAAC,CAAC;CAC3D,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,OAAO,EACP,KAAK,EACL,UAAU,EACV,aAAa,EACb,aAAa,EACb,iBAAiB,GAClB,EAAE,gBAAgB,+BAiGlB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useMemo, useState } from "react";
|
|
3
|
+
import { resolvePreviewPath } from "@vertebr/client";
|
|
3
4
|
import { ConfigForm } from "../config/ConfigForm";
|
|
4
5
|
import { hasCap } from "../../lib/capabilities";
|
|
5
6
|
import { addonCategoryLabel } from "./addonLabels";
|
|
@@ -7,18 +8,19 @@ import { PagePreviewPane } from "./PagePreviewPane";
|
|
|
7
8
|
export function AddonEditor({ client, catalog, addon, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }) {
|
|
8
9
|
const [draft, setDraft] = useState(configData.value);
|
|
9
10
|
const onChange = useCallback((value) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
11
|
+
// Merge — react-hook-form watch can omit unchanged keys.
|
|
12
|
+
setDraft((prev) => ({ ...(prev ?? configData.value), ...value }));
|
|
13
|
+
}, [configData.value]);
|
|
14
|
+
const draftValue = draft ?? configData.value;
|
|
15
|
+
const enabled = Boolean(draftValue.enabled);
|
|
16
|
+
const previewPath = resolvePreviewPath(addon.previewPath, addon.previewPathRules, draftValue);
|
|
14
17
|
const showPreview = Boolean(previewPath);
|
|
15
18
|
const canWrite = hasCap(catalog, "addon.write");
|
|
16
19
|
const previewDraft = useMemo(() => {
|
|
17
|
-
const base = draft ?? configData.value;
|
|
18
20
|
if (!showPreview)
|
|
19
|
-
return
|
|
20
|
-
return { ...
|
|
21
|
-
}, [
|
|
21
|
+
return draftValue;
|
|
22
|
+
return { ...draftValue, enabled: true };
|
|
23
|
+
}, [draftValue, showPreview]);
|
|
22
24
|
const form = (_jsx(ConfigForm, { schema: configData.schema, value: configData.value, client: client, canWrite: canWrite, onChange: showPreview ? onChange : undefined, onSave: async (next) => {
|
|
23
25
|
const saved = await client.setConfig(configData.id, next);
|
|
24
26
|
onConfigSaved(saved);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminContent.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminContent.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"AdminContent.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminContent.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,aAAa,EACd,MAAM,iBAAiB,CAAC;AASzB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAM9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,SAAS,CAAC;IACrB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IAC9C,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,cAAc,EAAE,OAAO,CAAC;IACxB,YAAY,EAAE,cAAc,CAAC;IAC7B,oBAAoB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACrD,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,eAAe,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC5C,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,UAAU,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACvC,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,YAAY,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC1C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,iBAAiB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9C,aAAa,EAAE;QACb,aAAa,CAAC,EAAE;YACd,MAAM,EAAE,MAAM,EAAE,CAAC;YACjB,QAAQ,EAAE,MAAM,CAAC;SAClB,CAAC;QACF,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;QACxD,eAAe,CAAC,EAAE,CAChB,KAAK,EAAE,IAAI,EAAE,KACV,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;KACxC,CAAC;IACF,iBAAiB,EAAE;QACjB,KAAK,CAAC,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC;QACrC,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;QAC1D,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;KACtC,CAAC;CACH,CAAC;AAiLF,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,OAAO,EACP,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,OAAO,EACP,eAAe,EACf,WAAW,EACX,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,aAAa,EACb,eAAe,EACf,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,iBAAiB,EACjB,eAAe,EACf,UAAU,EACV,aAAa,EACb,aAAa,EACb,iBAAiB,GAClB,EAAE,iBAAiB,+BAwUnB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
3
|
import { ConfigForm } from "../config/ConfigForm";
|
|
4
4
|
import { RevisionHistory } from "../config/RevisionHistory";
|
|
5
5
|
import { EmailsBoard } from "../emails/EmailsBoard";
|
|
@@ -13,21 +13,68 @@ import { AddonOverview } from "./AddonOverview";
|
|
|
13
13
|
import { AuditBoard } from "./AuditBoard";
|
|
14
14
|
import { PagePreviewPane } from "./PagePreviewPane";
|
|
15
15
|
import { AdminSyncPage } from "./AdminSyncPage";
|
|
16
|
-
function ConfigEditor({ client, catalog, configData, isPageSelection, previewPath, onConfigSaved, onRefreshConfig, galleryPicker, fontLibraryPicker, }) {
|
|
16
|
+
function ConfigEditor({ client, catalog, configData, isPageSelection, previewPath, relatedAddons = [], onConfigSaved, onRefreshConfig, onSelectAddon, galleryPicker, fontLibraryPicker, }) {
|
|
17
17
|
const [draft, setDraft] = useState(configData.value);
|
|
18
|
+
const [relatedDrafts, setRelatedDrafts] = useState({});
|
|
18
19
|
const showPreview = Boolean(previewPath);
|
|
19
20
|
const canWrite = hasCap(catalog, "config.write");
|
|
21
|
+
const canPublish = hasCap(catalog, "config.publish");
|
|
20
22
|
const showRevisions = Boolean(catalog.revisionsEnabled) || Boolean(configData.revisionsEnabled);
|
|
23
|
+
const relatedKey = relatedAddons.map((item) => item.id).join(",");
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
setDraft(configData.value);
|
|
26
|
+
}, [configData.id, configData.activeRevisionId, configData.value]);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!relatedAddons.length) {
|
|
29
|
+
setRelatedDrafts({});
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
let cancelled = false;
|
|
33
|
+
void (async () => {
|
|
34
|
+
const entries = await Promise.all(relatedAddons.map(async (addon) => {
|
|
35
|
+
try {
|
|
36
|
+
const config = await client.getConfig(addon.id);
|
|
37
|
+
const value = { ...config.value };
|
|
38
|
+
// Coming soon page owns site-launch countdown placement.
|
|
39
|
+
if (configData.id === "comingSoon" && addon.id === "countdown") {
|
|
40
|
+
value.target = "comingSoon";
|
|
41
|
+
}
|
|
42
|
+
if (configData.id === "product" && addon.id === "countdown") {
|
|
43
|
+
value.target = "product";
|
|
44
|
+
}
|
|
45
|
+
return [addon.id, value];
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return [addon.id, { enabled: false }];
|
|
49
|
+
}
|
|
50
|
+
}));
|
|
51
|
+
if (cancelled)
|
|
52
|
+
return;
|
|
53
|
+
setRelatedDrafts(Object.fromEntries(entries));
|
|
54
|
+
})();
|
|
55
|
+
return () => {
|
|
56
|
+
cancelled = true;
|
|
57
|
+
};
|
|
58
|
+
}, [client, configData.id, relatedKey, relatedAddons]);
|
|
21
59
|
const onChange = useCallback((value) => {
|
|
22
60
|
setDraft(value);
|
|
23
61
|
}, []);
|
|
24
|
-
const form = (_jsx(ConfigForm, { schema: configData.schema, value: configData.value, client: client, canWrite: canWrite,
|
|
62
|
+
const form = (_jsx(ConfigForm, { schema: configData.schema, value: configData.value, client: client, canWrite: canWrite, revisionsEnabled: showRevisions, canPublish: canPublish && Boolean(configData.draftRevisionId), onPublish: canPublish && configData.draftRevisionId
|
|
63
|
+
? async () => {
|
|
64
|
+
const published = await client.publishRevision(configData.id, configData.draftRevisionId);
|
|
65
|
+
onConfigSaved(published);
|
|
66
|
+
await onRefreshConfig?.();
|
|
67
|
+
}
|
|
68
|
+
: undefined, onChange: showPreview ? onChange : undefined, onSave: async (next) => {
|
|
25
69
|
const saved = await client.setConfig(configData.id, next);
|
|
26
70
|
onConfigSaved(saved);
|
|
27
71
|
}, ...galleryPicker, ...fontLibraryPicker }));
|
|
28
|
-
return (_jsxs("div", { className: "vertebr-config-editor", children: [showPreview ? (_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: isPageSelection ? "Page content" : configData.label }), _jsxs("
|
|
29
|
-
|
|
30
|
-
|
|
72
|
+
return (_jsxs("div", { className: "vertebr-config-editor", children: [showPreview || isPageSelection || showRevisions ? (_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: isPageSelection ? "Page content" : configData.label }), _jsxs("div", { className: "vertebr-panel-head-actions", children: [showPreview || isPageSelection ? (_jsxs("span", { className: "vertebr-count", children: [Object.keys(configData.schema).length, " fields"] })) : null, showRevisions ? (_jsx(RevisionHistory, { client: client, configId: configData.id, onPublished: async () => {
|
|
73
|
+
await onRefreshConfig?.();
|
|
74
|
+
} })) : null] })] })) : null, relatedAddons.length ? (_jsxs("div", { className: "vertebr-page-related-addons", "aria-label": "Linked add-ons", children: [_jsx("span", { className: "vertebr-page-related-addons-label", children: "Linked add-ons" }), _jsx("div", { className: "vertebr-page-related-addons-list", children: relatedAddons.map((addon) => {
|
|
75
|
+
const enabled = relatedDrafts[addon.id]?.enabled === true;
|
|
76
|
+
return (_jsxs("button", { type: "button", className: "vertebr-page-related-addon", "data-enabled": enabled ? "true" : "false", onClick: () => onSelectAddon?.(addon.id), children: [_jsx("span", { children: addon.label }), _jsx("em", { children: enabled ? "on" : "off" })] }, addon.id));
|
|
77
|
+
}) })] })) : null, showPreview && previewPath ? (_jsxs("div", { className: "vertebr-page-editor", children: [_jsx("div", { className: "vertebr-page-editor-form", children: form }), _jsx(PagePreviewPane, { previewPath: previewPath, controlId: configData.id, draft: draft ?? configData.value, relatedDrafts: Object.keys(relatedDrafts).length ? relatedDrafts : undefined })] })) : (form)] }));
|
|
31
78
|
}
|
|
32
79
|
export function AdminContent({ client, catalog, selection, onSelectionChange, authRequired, loading, isPageSelection, metricsData, metricsLoading, metricsRange, onMetricsRangeChange, onRefreshMetrics, galleryData, galleryLoading, onRefreshGallery, fontLibraryData, fontLibraryLoading, onRefreshFonts, emailsData, emailsLoading, onRefreshEmails, resourceData, resourceSearch, onResourceSearchChange, onRefreshResource, onRefreshConfig, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }) {
|
|
33
80
|
if (!catalog) {
|
|
@@ -38,11 +85,8 @@ export function AdminContent({ client, catalog, selection, onSelectionChange, au
|
|
|
38
85
|
: "Unable to load catalog." }) }) }));
|
|
39
86
|
}
|
|
40
87
|
return (_jsx("div", { className: "vertebr-content", children: _jsxs("section", { className: "vertebr-panel", children: [selection?.kind === "overview" ? (_jsx(MetricsBoard, { label: metricsData?.label ?? catalog.metrics?.label, windowLabel: metricsData?.windowLabel, compareLabel: metricsData?.compareLabel, metrics: metricsData?.metrics ?? [], charts: metricsData?.charts ?? [], range: metricsRange, onRangeChange: onMetricsRangeChange, loading: metricsLoading, onRefresh: () => void onRefreshMetrics() })) : null, selection?.kind === "audit" ? (_jsx(AuditBoard, { client: client, label: catalog.audit?.label })) : null, selection?.kind === "sync" ? (_jsx(AdminSyncPage, { client: client, catalog: catalog })) : null, selection?.kind === "gallery" && galleryData ? (_jsx(GalleryBoard, { label: galleryData.label, items: galleryData.items, accept: galleryData.accept, maxBytes: galleryData.maxBytes, canDelete: galleryData.canDelete, loading: galleryLoading, onRefresh: () => void onRefreshGallery(), onUpload: async (files) => {
|
|
41
|
-
|
|
42
|
-
await client.uploadGalleryFile(
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
await client.uploadGalleryFiles(files);
|
|
88
|
+
for (const file of files) {
|
|
89
|
+
await client.uploadGalleryFile(file);
|
|
46
90
|
}
|
|
47
91
|
await onRefreshGallery();
|
|
48
92
|
}, onDelete: galleryData.canDelete
|
|
@@ -145,15 +189,16 @@ export function AdminContent({ client, catalog, selection, onSelectionChange, au
|
|
|
145
189
|
}
|
|
146
190
|
: undefined, onUpload: resourceData.uploads
|
|
147
191
|
? async (field, files) => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
192
|
+
const uploaded = [];
|
|
193
|
+
for (const file of files) {
|
|
194
|
+
const result = await client.uploadResourceFile(resourceData.id, field, file);
|
|
195
|
+
uploaded.push({ url: result.url, name: result.name });
|
|
151
196
|
}
|
|
152
|
-
|
|
153
|
-
return result.files;
|
|
197
|
+
return uploaded;
|
|
154
198
|
}
|
|
155
199
|
: undefined, ...galleryPicker })) : null, selection?.kind === "addons" ? (_jsx(AddonOverview, { client: client, catalog: catalog, onSelectAddon: (id) => onSelectionChange?.({ kind: "addon", id }) })) : null, selection?.kind === "config" && configData ? (_jsx(ConfigEditor, { client: client, catalog: catalog, configData: configData, isPageSelection: isPageSelection, previewPath: catalog.configs.find((item) => item.id === configData.id)
|
|
156
|
-
?.previewPath,
|
|
200
|
+
?.previewPath, relatedAddons: catalog.configs.find((item) => item.id === configData.id)
|
|
201
|
+
?.relatedAddons, onConfigSaved: onConfigSaved, onRefreshConfig: onRefreshConfig, onSelectAddon: (id) => onSelectionChange?.({ kind: "addon", id }), galleryPicker: galleryPicker, fontLibraryPicker: fontLibraryPicker }, configData.id)) : null, selection?.kind === "addon" && configData ? ((() => {
|
|
157
202
|
const addon = catalog.addons?.find((item) => item.id === configData.id);
|
|
158
203
|
if (!addon)
|
|
159
204
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminSaveBar.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminSaveBar.tsx"],"names":[],"mappings":"AAEA,wBAAgB,YAAY,
|
|
1
|
+
{"version":3,"file":"AdminSaveBar.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminSaveBar.tsx"],"names":[],"mappings":"AAEA,wBAAgB,YAAY,uCAqD3B"}
|
|
@@ -6,5 +6,10 @@ export function AdminSaveBar() {
|
|
|
6
6
|
return null;
|
|
7
7
|
return (_jsxs("div", { className: "vertebr-savebar", "data-tone": state.tone, children: [_jsx("div", { className: "vertebr-savebar-message", children: state.saving
|
|
8
8
|
? "Saving…"
|
|
9
|
-
: state.
|
|
9
|
+
: state.publishing
|
|
10
|
+
? "Publishing…"
|
|
11
|
+
: (state.message ??
|
|
12
|
+
(state.isDirty ? "Unsaved changes" : "Saved")) }), _jsxs("div", { className: "vertebr-savebar-actions", children: [state.onDiscard && state.isDirty ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: state.saving || state.publishing, onClick: state.onDiscard, children: "Discard" })) : null, state.onSave ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-primary", disabled: state.saving || state.publishing || !state.isDirty, onClick: () => void state.onSave?.(), children: state.saveLabel ?? "Save" })) : null, state.onPublish ? (_jsx("button", { type: "button", className: "vertebr-btn", disabled: state.saving || state.publishing || state.isDirty, title: state.isDirty
|
|
13
|
+
? "Save draft before publishing"
|
|
14
|
+
: "Publish the current draft to the live storefront", onClick: () => void state.onPublish?.(), children: state.publishLabel ?? "Publish" })) : null] })] }));
|
|
10
15
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminSyncPage.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminSyncPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,eAAe,
|
|
1
|
+
{"version":3,"file":"AdminSyncPage.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminSyncPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,eAAe,EAIf,aAAa,EACd,MAAM,iBAAiB,CAAC;AAQzB,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,eAAe,CAAC;CAC1B,CAAC;AAuPF,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,kBAAkB,+BA0YpE"}
|
|
@@ -5,6 +5,7 @@ import { EnvSelect } from "../env/EnvSelect";
|
|
|
5
5
|
import { useToast } from "../../hooks/useToast";
|
|
6
6
|
import { Badge, Card } from "../ui/primitives";
|
|
7
7
|
import { IconSync } from "../ui/icons";
|
|
8
|
+
import { SyncPreviewFrame } from "./SyncPreviewFrame";
|
|
8
9
|
function envById(environments, id) {
|
|
9
10
|
return environments.find((env) => env.id === id);
|
|
10
11
|
}
|
|
@@ -46,6 +47,36 @@ function statusLabel(status) {
|
|
|
46
47
|
return status;
|
|
47
48
|
}
|
|
48
49
|
}
|
|
50
|
+
function formatDiffValue(value) {
|
|
51
|
+
if (value === undefined || value === null)
|
|
52
|
+
return "—";
|
|
53
|
+
if (typeof value === "string")
|
|
54
|
+
return value.length ? value : '""';
|
|
55
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
56
|
+
return String(value);
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return JSON.stringify(value, null, 2);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return String(value);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function SyncDiffModal({ inspect, sourceLabel, onClose, }) {
|
|
66
|
+
const changes = inspect.item.changes ?? [];
|
|
67
|
+
const preview = inspect.item.preview;
|
|
68
|
+
const hasVisual = Boolean(preview);
|
|
69
|
+
const [tab, setTab] = useState(hasVisual ? "visual" : "fields");
|
|
70
|
+
return (_jsx("div", { className: "vertebr-modal-backdrop", role: "presentation", onClick: onClose, children: _jsxs("div", { className: "vertebr-modal vertebr-sync-diff-modal", "data-has-visual": hasVisual ? "true" : undefined, role: "dialog", "aria-modal": "true", "aria-labelledby": "vertebr-sync-diff-title", onClick: (event) => event.stopPropagation(), children: [_jsxs("header", { className: "vertebr-modal-head vertebr-sync-diff-head", children: [_jsxs("div", { className: "vertebr-modal-heading", children: [_jsx("h2", { id: "vertebr-sync-diff-title", children: inspect.targetLabel }), _jsxs("p", { className: "vertebr-sync-diff-subtitle", children: [_jsxs("span", { children: [sourceLabel, " \u2192 ", inspect.destinationLabel] }), inspect.item.summary ? (_jsx("span", { className: "vertebr-sync-diff-meta", children: inspect.item.summary })) : null] })] }), _jsx("div", { className: "vertebr-modal-actions", children: _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: onClose, children: "Close" }) })] }), hasVisual ? (_jsxs("div", { className: "vertebr-sync-diff-tabs", role: "tablist", "aria-label": "Diff view", children: [_jsx("button", { type: "button", role: "tab", "aria-selected": tab === "visual", "data-active": tab === "visual" ? "true" : undefined, onClick: () => setTab("visual"), children: "Visual" }), _jsx("button", { type: "button", role: "tab", "aria-selected": tab === "fields", "data-active": tab === "fields" ? "true" : undefined, onClick: () => setTab("fields"), children: "Fields" })] })) : null, _jsxs("div", { className: "vertebr-modal-body", children: [inspect.item.warnings?.length ? (_jsx("ul", { className: "vertebr-sync-diff-warnings", children: inspect.item.warnings.map((warning) => (_jsx("li", { children: warning }, warning))) })) : null, _jsxs("div", { className: "vertebr-sync-diff-panels", "data-tab": tab, children: [preview ? (_jsx("div", { className: "vertebr-sync-diff-panel", "data-panel": "visual", "data-active": tab === "visual" ? "true" : undefined, role: "tabpanel", "aria-hidden": tab !== "visual", children: _jsxs("div", { className: "vertebr-sync-diff-visual", children: [_jsx(SyncPreviewFrame, { label: `${inspect.destinationLabel} (now)`, previewPath: preview.previewPath, controlId: preview.controlId, draft: preview.destDraft, relatedDrafts: preview.destRelatedDrafts, addons: preview.addons?.map((addon) => ({
|
|
71
|
+
id: addon.id,
|
|
72
|
+
label: addon.label,
|
|
73
|
+
envEnabled: addon.destEnabled,
|
|
74
|
+
})) }), _jsx(SyncPreviewFrame, { label: `${sourceLabel} (after sync)`, previewPath: preview.previewPath, controlId: preview.controlId, draft: preview.sourceDraft, relatedDrafts: preview.sourceRelatedDrafts, addons: preview.addons?.map((addon) => ({
|
|
75
|
+
id: addon.id,
|
|
76
|
+
label: addon.label,
|
|
77
|
+
envEnabled: addon.sourceEnabled,
|
|
78
|
+
})) })] }) })) : null, _jsx("div", { className: "vertebr-sync-diff-panel", "data-panel": "fields", "data-active": tab === "fields" ? "true" : undefined, role: "tabpanel", "aria-hidden": tab !== "fields", children: changes.length === 0 ? (_jsx("p", { className: "vertebr-sync-diff-empty", children: "No field-level preview for this target. The summary still applies when you sync." })) : (_jsx("div", { className: "vertebr-sync-diff-table-wrap", children: _jsxs("table", { className: "vertebr-sync-diff-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { scope: "col", children: "Field" }), _jsxs("th", { scope: "col", children: [inspect.destinationLabel, " (now)"] }), _jsxs("th", { scope: "col", children: [sourceLabel, " (after sync)"] })] }) }), _jsx("tbody", { children: changes.map((change) => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: _jsx("code", { children: change.path }) }), _jsx("td", { children: _jsx("pre", { children: formatDiffValue(change.destination) }) }), _jsx("td", { children: _jsx("pre", { children: formatDiffValue(change.source) }) })] }, change.path))) })] }) })) })] })] })] }) }));
|
|
79
|
+
}
|
|
49
80
|
export function AdminSyncPage({ client, catalog }) {
|
|
50
81
|
const sync = catalog.sync;
|
|
51
82
|
const { push } = useToast();
|
|
@@ -63,24 +94,27 @@ export function AdminSyncPage({ client, catalog }) {
|
|
|
63
94
|
const [planLoading, setPlanLoading] = useState(false);
|
|
64
95
|
const [running, setRunning] = useState(false);
|
|
65
96
|
const [error, setError] = useState(null);
|
|
97
|
+
const [inspect, setInspect] = useState(null);
|
|
66
98
|
const fromEnvironment = envById(environments, fromEnv);
|
|
67
99
|
const toEnvironments = useMemo(() => toEnv
|
|
68
100
|
.map((id) => envById(environments, id))
|
|
69
101
|
.filter((env) => Boolean(env)), [environments, toEnv]);
|
|
70
102
|
const targetGroups = useMemo(() => groupTargets(catalog), [catalog]);
|
|
71
103
|
const hasTargets = (sync?.targets.length ?? 0) > 0;
|
|
104
|
+
const allTargetIds = useMemo(() => (sync?.targets ?? []).map((target) => target.id), [sync?.targets]);
|
|
72
105
|
const refreshPlan = useCallback(async () => {
|
|
73
|
-
if (!hasTargets || toEnv.length === 0) {
|
|
106
|
+
if (!hasTargets || toEnv.length === 0 || allTargetIds.length === 0) {
|
|
74
107
|
setPlan(null);
|
|
75
108
|
return;
|
|
76
109
|
}
|
|
77
110
|
setPlanLoading(true);
|
|
78
111
|
setError(null);
|
|
79
112
|
try {
|
|
113
|
+
// Plan every target so Match/Changes + View diff work without selecting.
|
|
80
114
|
const next = await client.getSyncPlan({
|
|
81
115
|
from: fromEnv,
|
|
82
116
|
to: toEnv,
|
|
83
|
-
targets:
|
|
117
|
+
targets: allTargetIds,
|
|
84
118
|
});
|
|
85
119
|
setPlan(next);
|
|
86
120
|
}
|
|
@@ -91,7 +125,7 @@ export function AdminSyncPage({ client, catalog }) {
|
|
|
91
125
|
finally {
|
|
92
126
|
setPlanLoading(false);
|
|
93
127
|
}
|
|
94
|
-
}, [client, fromEnv, toEnv,
|
|
128
|
+
}, [client, fromEnv, toEnv, allTargetIds, hasTargets]);
|
|
95
129
|
useEffect(() => {
|
|
96
130
|
void refreshPlan();
|
|
97
131
|
}, [refreshPlan]);
|
|
@@ -155,15 +189,25 @@ export function AdminSyncPage({ client, catalog }) {
|
|
|
155
189
|
setToEnv((current) => current.filter((id) => id !== next));
|
|
156
190
|
} })] }), _jsx("div", { className: "vertebr-sync-connector", "aria-hidden": "true", children: toEnvironments.length > 0 ? (toEnvironments.map((destination) => (_jsx("span", { className: "vertebr-sync-connector-arrow", children: "\u2192" }, destination.id)))) : (_jsx("span", { className: "vertebr-sync-connector-arrow", children: "\u2192" })) }), _jsxs("label", { className: "vertebr-sync-field", children: [_jsx("span", { className: "vertebr-sync-field-label", children: "To" }), _jsx(EnvMultiSelect, { environments: environments, value: toEnv, excludeId: fromEnv, onChange: setToEnv, placeholder: "Select one or more destinations" })] })] }), fromEnvironment && toEnvironments.length > 0 ? (_jsx("div", { className: "vertebr-sync-preview", "aria-label": "Sync direction", children: _jsxs("div", { className: "vertebr-sync-preview-flow", "data-dest-count": toEnvironments.length, children: [_jsxs("div", { className: "vertebr-sync-preview-col vertebr-sync-preview-col-from", children: [_jsx("span", { className: "vertebr-sync-preview-caption", children: "From" }), _jsx(EnvFlowBadge, { environment: fromEnvironment })] }), _jsx("div", { className: "vertebr-sync-preview-col vertebr-sync-preview-col-routes", children: toEnvironments.map((destination, index) => (_jsxs("div", { className: "vertebr-sync-preview-route", children: [_jsx("span", { className: "vertebr-sync-preview-arrow", "aria-hidden": "true", children: "\u2192" }), _jsxs("div", { className: "vertebr-sync-preview-col vertebr-sync-preview-col-to", children: [index === 0 ? (_jsx("span", { className: "vertebr-sync-preview-caption", children: "To" })) : (_jsx("span", { className: "vertebr-sync-preview-caption", "aria-hidden": "true", children: "\u00A0" })), _jsx(EnvFlowBadge, { environment: destination })] })] }, destination.id))) })] }) })) : (_jsx("p", { className: "vertebr-sync-direction-hint", children: "Pick a source environment and one or more destinations to preview changes." }))] }) }), _jsxs(Card, { title: "What to sync", children: [_jsxs("div", { className: "vertebr-sync-targets-head", children: [_jsx("p", { className: "vertebr-sync-targets-copy", children: "Review what will change in each destination before syncing." }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-sync-refresh", disabled: planLoading || toEnv.length === 0, onClick: () => void refreshPlan(), children: planLoading ? "Refreshing…" : "Refresh preview" })] }), [...targetGroups.entries()].map(([group, targets]) => (_jsxs("div", { className: "vertebr-sync-target-group", children: [_jsx("div", { className: "vertebr-sync-target-group-label", children: group }), _jsx("ul", { className: "vertebr-sync-target-list", children: targets.map((target) => {
|
|
157
191
|
const selected = selectedTargets.includes(target.id);
|
|
158
|
-
return (_jsx("li", { children: _jsxs("
|
|
192
|
+
return (_jsx("li", { children: _jsxs("div", { className: "vertebr-sync-target-row", "data-selected": selected || undefined, children: [_jsxs("button", { type: "button", className: "vertebr-sync-target-toggle", onClick: () => toggleTarget(target.id), "aria-pressed": selected, children: [_jsx("span", { className: "vertebr-sync-target-check", "aria-hidden": "true", children: selected ? "✓" : "" }), _jsxs("span", { className: "vertebr-sync-target-main", children: [_jsx("span", { className: "vertebr-sync-target-title", children: target.label }), target.description ? (_jsx("span", { className: "vertebr-sync-target-desc", children: target.description })) : null] })] }), _jsx("span", { className: "vertebr-sync-target-badges", children: toEnvironments.length === 0 ? (_jsx(Badge, { children: "Choose destination" })) : planLoading ? (_jsx(Badge, { children: "Previewing\u2026" })) : (toEnvironments.map((destination) => {
|
|
159
193
|
const item = planForTarget(destination.id, target.id);
|
|
160
194
|
if (!item) {
|
|
161
195
|
return (_jsx(Badge, { children: destination.label }, destination.id));
|
|
162
196
|
}
|
|
163
|
-
|
|
197
|
+
const canInspect = (item.status === "changed" ||
|
|
198
|
+
item.status === "new") &&
|
|
199
|
+
(Boolean(item.changes?.length) ||
|
|
200
|
+
Boolean(item.preview));
|
|
201
|
+
return (_jsxs("span", { className: "vertebr-sync-target-dest", title: item.summary, children: [_jsxs(Badge, { tone: statusTone(item.status), children: [destination.label, ": ", statusLabel(item.status)] }), _jsx("span", { className: "vertebr-sync-target-summary", children: item.summary }), canInspect ? (_jsx("button", { type: "button", className: "vertebr-sync-diff-btn", onClick: () => setInspect({
|
|
202
|
+
targetId: target.id,
|
|
203
|
+
targetLabel: target.label,
|
|
204
|
+
destinationId: destination.id,
|
|
205
|
+
destinationLabel: destination.label,
|
|
206
|
+
item,
|
|
207
|
+
}), children: item.preview ? "View preview" : "View diff" })) : null] }, destination.id));
|
|
164
208
|
})) })] }) }, target.id));
|
|
165
209
|
}) })] }, group)))] }), sync?.excluded.length ? (_jsx(Card, { title: "Never synced", subdued: true, children: _jsx("ul", { className: "vertebr-sync-excluded-list", children: sync.excluded.map((item) => (_jsxs("li", { children: [_jsx("span", { className: "vertebr-sync-excluded-name", children: item.label }), _jsx("span", { className: "vertebr-sync-excluded-reason", children: item.reason })] }, item.label))) }) })) : null, error ? _jsx("div", { className: "vertebr-banner", "data-tone": "danger", children: error }) : null] }), _jsxs("div", { className: "vertebr-savebar vertebr-sync-footer", children: [_jsx("div", { className: "vertebr-savebar-message", children: syncSummary }), _jsx("div", { className: "vertebr-savebar-actions", children: _jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-primary vertebr-sync-run-btn", disabled: running ||
|
|
166
210
|
planLoading ||
|
|
167
211
|
toEnv.length === 0 ||
|
|
168
|
-
selectedTargets.length === 0, onClick: () => void runSync(), children: [_jsx(IconSync, {}), _jsx("span", { children: running ? "Syncing…" : "Sync selected" })] }) })] })] }));
|
|
212
|
+
selectedTargets.length === 0, onClick: () => void runSync(), children: [_jsx(IconSync, {}), _jsx("span", { children: running ? "Syncing…" : "Sync selected" })] }) })] }), inspect ? (_jsx(SyncDiffModal, { inspect: inspect, sourceLabel: fromEnvironment?.label ?? fromEnv, onClose: () => setInspect(null) })) : null] }));
|
|
169
213
|
}
|
|
@@ -6,11 +6,13 @@ export type PagePreviewPaneProps = {
|
|
|
6
6
|
controlId: string;
|
|
7
7
|
/** Latest unsaved form values. */
|
|
8
8
|
draft: Record<string, unknown> | null;
|
|
9
|
+
/** Extra add-on drafts owned by this page (e.g. countdown on coming soon). */
|
|
10
|
+
relatedDrafts?: Record<string, Record<string, unknown>>;
|
|
9
11
|
/** Label for the preview chrome. */
|
|
10
12
|
previewKind?: "page" | "addon";
|
|
11
13
|
};
|
|
12
14
|
/**
|
|
13
15
|
* Live host iframe for Pages. Vertebr posts drafts; the host renders.
|
|
14
16
|
*/
|
|
15
|
-
export declare function PagePreviewPane({ previewPath, controlId, draft, previewKind, }: PagePreviewPaneProps): import("react").JSX.Element;
|
|
17
|
+
export declare function PagePreviewPane({ previewPath, controlId, draft, relatedDrafts, previewKind, }: PagePreviewPaneProps): import("react").JSX.Element;
|
|
16
18
|
//# sourceMappingURL=PagePreviewPane.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PagePreviewPane.d.ts","sourceRoot":"","sources":["../../../src/components/admin/PagePreviewPane.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PagePreviewPane.d.ts","sourceRoot":"","sources":["../../../src/components/admin/PagePreviewPane.tsx"],"names":[],"mappings":"AAQA,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG;IACjC,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACtC,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAChC,CAAC;AAqBF;;GAEG;AACH,wBAAgB,eAAe,CAAC,EAC9B,WAAW,EACX,SAAS,EACT,KAAK,EACL,aAAa,EACb,WAAoB,GACrB,EAAE,oBAAoB,+BAoOtB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
-
import { buildVertebrPreviewUrl, postVertebrPreviewDraft, subscribeVertebrPreviewReady, } from "@vertebr/client";
|
|
3
|
+
import { buildVertebrPreviewUrl, postVertebrPreviewDraft, subscribeVertebrPreviewNavigate, subscribeVertebrPreviewReady, } from "@vertebr/client";
|
|
4
4
|
const VIEWPORTS = [
|
|
5
5
|
{ id: "desktop", label: "Desktop" },
|
|
6
6
|
{ id: "mobile", label: "Mobile" },
|
|
@@ -8,28 +8,52 @@ const VIEWPORTS = [
|
|
|
8
8
|
/** Logical CSS viewport for mobile preview (host media queries use this). */
|
|
9
9
|
const MOBILE_WIDTH = 390;
|
|
10
10
|
const MOBILE_HEIGHT = 844;
|
|
11
|
+
function normalizePreviewPath(path) {
|
|
12
|
+
const trimmed = path.trim() || "/";
|
|
13
|
+
try {
|
|
14
|
+
const url = new URL(trimmed, "http://vertebr.local");
|
|
15
|
+
return `${url.pathname}${url.search}${url.hash}` || "/";
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
11
21
|
/**
|
|
12
22
|
* Live host iframe for Pages. Vertebr posts drafts; the host renders.
|
|
13
23
|
*/
|
|
14
|
-
export function PagePreviewPane({ previewPath, controlId, draft, previewKind = "page", }) {
|
|
24
|
+
export function PagePreviewPane({ previewPath, controlId, draft, relatedDrafts, previewKind = "page", }) {
|
|
15
25
|
const iframeRef = useRef(null);
|
|
16
26
|
const stageRef = useRef(null);
|
|
17
27
|
const draftRef = useRef(draft);
|
|
28
|
+
const relatedRef = useRef(relatedDrafts);
|
|
18
29
|
const [frameReady, setFrameReady] = useState(false);
|
|
19
30
|
const [frameError, setFrameError] = useState(false);
|
|
20
31
|
const [viewport, setViewport] = useState("desktop");
|
|
21
32
|
const [mobileScale, setMobileScale] = useState(1);
|
|
33
|
+
const [activePath, setActivePath] = useState(() => normalizePreviewPath(previewPath));
|
|
22
34
|
draftRef.current = draft;
|
|
35
|
+
relatedRef.current = relatedDrafts;
|
|
23
36
|
const isMobile = viewport === "mobile";
|
|
24
|
-
const
|
|
37
|
+
const basePath = normalizePreviewPath(previewPath);
|
|
38
|
+
const navigatedAway = activePath !== basePath;
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
setActivePath(normalizePreviewPath(previewPath));
|
|
41
|
+
}, [previewPath]);
|
|
42
|
+
const src = useMemo(() => buildVertebrPreviewUrl(activePath, {
|
|
25
43
|
controlId,
|
|
26
|
-
}), [
|
|
44
|
+
}), [activePath, controlId]);
|
|
27
45
|
const pushDraft = useCallback(() => {
|
|
28
46
|
const win = iframeRef.current?.contentWindow;
|
|
29
47
|
const value = draftRef.current;
|
|
30
48
|
if (!win || !value)
|
|
31
49
|
return;
|
|
32
50
|
postVertebrPreviewDraft(win, controlId, value);
|
|
51
|
+
const related = relatedRef.current;
|
|
52
|
+
if (!related)
|
|
53
|
+
return;
|
|
54
|
+
for (const [id, relatedValue] of Object.entries(related)) {
|
|
55
|
+
postVertebrPreviewDraft(win, id, relatedValue);
|
|
56
|
+
}
|
|
33
57
|
}, [controlId]);
|
|
34
58
|
const notifyHostResize = useCallback(() => {
|
|
35
59
|
const win = iframeRef.current?.contentWindow;
|
|
@@ -55,11 +79,19 @@ export function PagePreviewPane({ previewPath, controlId, draft, previewKind = "
|
|
|
55
79
|
pushDraft();
|
|
56
80
|
});
|
|
57
81
|
}, [pushDraft, src]);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
return subscribeVertebrPreviewNavigate((path, source) => {
|
|
84
|
+
const win = iframeRef.current?.contentWindow;
|
|
85
|
+
if (!win || source !== win)
|
|
86
|
+
return;
|
|
87
|
+
setActivePath(normalizePreviewPath(path));
|
|
88
|
+
});
|
|
89
|
+
}, []);
|
|
58
90
|
useEffect(() => {
|
|
59
91
|
if (!frameReady)
|
|
60
92
|
return;
|
|
61
93
|
pushDraft();
|
|
62
|
-
}, [draft, frameReady, pushDraft]);
|
|
94
|
+
}, [draft, relatedDrafts, frameReady, pushDraft]);
|
|
63
95
|
// Fit the fixed mobile viewport into the stage without changing its
|
|
64
96
|
// layout width (transform scale keeps host media queries at 390px).
|
|
65
97
|
useEffect(() => {
|
|
@@ -93,11 +125,11 @@ export function PagePreviewPane({ previewPath, controlId, draft, previewKind = "
|
|
|
93
125
|
const timer = window.setTimeout(notifyHostResize, 50);
|
|
94
126
|
return () => window.clearTimeout(timer);
|
|
95
127
|
}, [viewport, mobileScale, frameReady, notifyHostResize]);
|
|
96
|
-
return (_jsxs("div", { className: "vertebr-page-preview", "data-preview-kind": previewKind, children: [_jsxs("div", { className: "vertebr-page-preview-meta", children: [_jsxs("p", { children: [_jsx("span", { children: previewKind === "addon" ? "Storefront" : "Preview" }), _jsx("strong", { children:
|
|
128
|
+
return (_jsxs("div", { className: "vertebr-page-preview", "data-preview-kind": previewKind, children: [_jsxs("div", { className: "vertebr-page-preview-meta", children: [_jsxs("p", { children: [_jsx("span", { children: previewKind === "addon" ? "Storefront" : "Preview" }), _jsx("strong", { children: activePath })] }), _jsxs("p", { children: [_jsx("span", { children: "Status" }), _jsx("strong", { children: frameError
|
|
97
129
|
? "Unavailable"
|
|
98
130
|
: frameReady
|
|
99
|
-
? "
|
|
100
|
-
: "Loading…" })] }), isMobile ? (_jsxs("p", { children: [_jsx("span", { children: "Viewport" }), _jsxs("strong", { children: [MOBILE_WIDTH, "\u00D7", MOBILE_HEIGHT] })] })) : null, _jsx("div", { className: "vertebr-page-preview-viewports", role: "group", "aria-label": "Preview viewport", children: VIEWPORTS.map((item) => (_jsx("button", { type: "button", "data-active": viewport === item.id ? "true" : undefined, onClick: () => setViewport(item.id), children: item.label }, item.id))) })] }), _jsx("div", { ref: stageRef, className: "vertebr-page-preview-stage", "data-viewport": viewport, children: _jsx("div", { className: "vertebr-page-preview-shell", style: isMobile
|
|
131
|
+
? "Connected"
|
|
132
|
+
: "Loading…" })] }), isMobile ? (_jsxs("p", { children: [_jsx("span", { children: "Viewport" }), _jsxs("strong", { children: [MOBILE_WIDTH, "\u00D7", MOBILE_HEIGHT] })] })) : null, navigatedAway ? (_jsx("button", { type: "button", className: "vertebr-page-preview-back", onClick: () => setActivePath(basePath), children: "Back to page" })) : null, _jsx("div", { className: "vertebr-page-preview-viewports", role: "group", "aria-label": "Preview viewport", children: VIEWPORTS.map((item) => (_jsx("button", { type: "button", "data-active": viewport === item.id ? "true" : undefined, onClick: () => setViewport(item.id), children: item.label }, item.id))) })] }), _jsx("div", { ref: stageRef, className: "vertebr-page-preview-stage", "data-viewport": viewport, children: _jsx("div", { className: "vertebr-page-preview-shell", style: isMobile
|
|
101
133
|
? {
|
|
102
134
|
width: MOBILE_WIDTH * mobileScale,
|
|
103
135
|
height: MOBILE_HEIGHT * mobileScale,
|
|
@@ -109,7 +141,7 @@ export function PagePreviewPane({ previewPath, controlId, draft, previewKind = "
|
|
|
109
141
|
transform: `scale(${mobileScale})`,
|
|
110
142
|
transformOrigin: "top left",
|
|
111
143
|
}
|
|
112
|
-
: undefined, children: frameError ? (_jsxs("div", { className: "vertebr-page-preview-empty", children: ["Could not load host preview at ",
|
|
144
|
+
: undefined, children: frameError ? (_jsxs("div", { className: "vertebr-page-preview-empty", children: ["Could not load host preview at ", activePath, "."] })) : (_jsx("iframe", { ref: iframeRef, title: `Preview ${controlId}`, src: src, width: isMobile ? MOBILE_WIDTH : undefined, height: isMobile ? MOBILE_HEIGHT : undefined, style: isMobile
|
|
113
145
|
? { width: MOBILE_WIDTH, height: MOBILE_HEIGHT }
|
|
114
146
|
: undefined, onLoad: () => {
|
|
115
147
|
// Ready message is preferred; onLoad covers hosts that
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type SyncPreviewAddonChip = {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
/** True when the add-on is enabled in that environment. */
|
|
5
|
+
envEnabled: boolean;
|
|
6
|
+
};
|
|
7
|
+
export type SyncPreviewFrameProps = {
|
|
8
|
+
label: string;
|
|
9
|
+
previewPath: string;
|
|
10
|
+
controlId: string;
|
|
11
|
+
draft: Record<string, unknown>;
|
|
12
|
+
/** Extra controls to inject (e.g. countdown alongside coming soon). */
|
|
13
|
+
relatedDrafts?: Record<string, Record<string, unknown>>;
|
|
14
|
+
/** Add-ons that belong on this page preview (always force-shown in the iframe). */
|
|
15
|
+
addons?: SyncPreviewAddonChip[];
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Compact same-origin preview iframe for Sync visual compare.
|
|
19
|
+
* Fixed mobile-sized frame; posts the given draft after ready.
|
|
20
|
+
*/
|
|
21
|
+
export declare function SyncPreviewFrame({ label, previewPath, controlId, draft, relatedDrafts, addons, }: SyncPreviewFrameProps): import("react").JSX.Element;
|
|
22
|
+
//# sourceMappingURL=SyncPreviewFrame.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SyncPreviewFrame.d.ts","sourceRoot":"","sources":["../../../src/components/admin/SyncPreviewFrame.tsx"],"names":[],"mappings":"AAOA,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACxD,mFAAmF;IACnF,MAAM,CAAC,EAAE,oBAAoB,EAAE,CAAC;CACjC,CAAC;AAqDF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,WAAW,EACX,SAAS,EACT,KAAK,EACL,aAAa,EACb,MAAM,GACP,EAAE,qBAAqB,+BA6JvB"}
|