camox 0.75.0 → 0.76.1
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/core/editing/createEditableBlock.js +1077 -1681
- package/dist/core/editing/fieldSlot.js +251 -0
- package/dist/core/editing/useFieldSlot.js +159 -0
- package/dist/core/layoutContext.d.ts +2 -0
- package/dist/core/layoutContext.js +27 -1
- package/dist/features/canvas/CanvasPageFrame.js +2 -2
- package/dist/features/canvas/CanvasPreviewPageSlot.js +1 -1
- package/dist/features/canvas/canvasCameraController.js +595 -0
- package/dist/features/canvas/useCanvasCamera.js +141 -587
- package/dist/features/canvas/useCanvasFrame.js +1 -1
- package/dist/features/page/DerivedPageContent.js +4 -8
- package/dist/features/preview/components/AssetLightbox.js +6 -65
- package/dist/features/preview/components/CanvasPreview.js +2 -2
- package/dist/features/preview/components/Frame.js +3 -3
- package/dist/features/preview/components/ItemFieldsEditor.js +376 -460
- package/dist/features/preview/components/Overlays.js +4 -9
- package/dist/features/preview/components/PreviewPanel.js +3 -3
- package/dist/features/preview/components/RemovedBlockPlaceholder.js +2 -2
- package/dist/features/preview/components/SelectionEditor.js +285 -339
- package/dist/features/preview/components/ZoomableImage.js +105 -0
- package/dist/features/preview/components/contentFieldSchema.js +3 -1
- package/dist/features/preview/components/useFieldWriter.js +30 -51
- package/dist/features/preview/components/useRecordView.js +3 -3
- package/dist/features/preview/overlayMessages.js +20 -58
- package/dist/features/preview/previewStore.js +6 -123
- package/dist/features/preview/repeatableItemPath.js +47 -0
- package/dist/features/provider/AuthenticatedCamoxProvider.js +75 -63
- package/dist/features/renderer/RendererApp.js +6 -6
- package/dist/features/renderer/RendererFrames.js +2 -2
- package/dist/features/renderer/rendererHost.js +1 -1
- package/dist/features/routes/pageRuntime.d.ts +8 -12
- package/dist/features/routes/pageRuntime.js +26 -106
- package/dist/features/runtime/pageApp.js +1 -1
- package/dist/features/runtime/pageRead.js +105 -0
- package/dist/features/runtime/reader.d.ts +25 -0
- package/dist/features/runtime/reader.js +142 -0
- package/dist/features/runtime/runtime.d.ts +9 -3
- package/dist/features/runtime/runtime.js +608 -776
- package/dist/features/sdkBundle/buildSdkBundle.d.ts +5 -0
- package/dist/features/sdkBundle/buildSdkBundle.js +9 -3
- package/dist/features/studio/components/ChatWidget.js +137 -121
- package/dist/features/studio/components/ClearThread.js +121 -0
- package/dist/features/studio/components/DeleteDraftDialog.js +67 -65
- package/dist/features/studio/components/DraftBar.js +60 -49
- package/dist/features/studio/components/DraftNotices.js +149 -0
- package/dist/features/studio/components/DraftsControl.js +217 -180
- package/dist/features/studio/components/DraftsSheet.js +446 -0
- package/dist/features/studio/components/PublishedTab.js +511 -0
- package/dist/features/studio/components/ReviewDraftDialog.js +104 -89
- package/dist/features/studio/components/SheetTable.js +46 -0
- package/dist/features/studio/components/Specialists.js +1 -1
- package/dist/features/studio/components/ThreadMessages.js +227 -176
- package/dist/features/studio/draftLabel.js +15 -1
- package/dist/features/studio/draftNotice.js +1 -0
- package/dist/features/studio/showInCanvas.js +9 -19
- package/dist/features/studio/studioStore.js +5 -5
- package/dist/features/studio/useArchiveDraft.js +60 -0
- package/dist/features/studio/useEditorSelection.js +8 -19
- package/dist/features/studio/useRestoreDraft.js +70 -0
- package/dist/features/studio/viewSwitch.js +19 -11
- package/dist/lib/draft-scope.d.ts +1 -1
- package/dist/lib/draft-scope.js +6 -3
- package/dist/lib/queries.js +12 -1
- package/dist/lib/studio-reads.js +22 -0
- package/dist/lib/use-project-room.js +19 -13
- package/dist/lib/use-thread.js +1 -1
- package/dist/package.js +1 -1
- package/dist/studio.css +1 -1
- package/package.json +4 -4
- package/dist/core/hooks/useFieldSelection.js +0 -67
- package/dist/core/hooks/useIsEditable.js +0 -21
- package/dist/core/hooks/useOverlayMessage.js +0 -70
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { c } from "react/compiler-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
|
|
5
|
+
//#region src/features/preview/components/ZoomableImage.tsx
|
|
6
|
+
/**
|
|
7
|
+
* An image shown whole in the area it fills, which a click zooms to twice its size and another
|
|
8
|
+
* click zooms back out. Zoomed in, the area scrolls to every edge of the image, centered on where
|
|
9
|
+
* it was clicked. The checkerboard shows only through the image's transparent pixels.
|
|
10
|
+
*/
|
|
11
|
+
function ZoomableImage(t0) {
|
|
12
|
+
const $ = c(14);
|
|
13
|
+
const { src, alt } = t0;
|
|
14
|
+
const containerRef = useRef(null);
|
|
15
|
+
const imgRef = useRef(null);
|
|
16
|
+
const [zoomedWidth, setZoomedWidth] = useState(null);
|
|
17
|
+
const clickFractionRef = useRef(null);
|
|
18
|
+
let t1;
|
|
19
|
+
let t2;
|
|
20
|
+
if ($[0] !== zoomedWidth) {
|
|
21
|
+
t1 = () => {
|
|
22
|
+
const container = containerRef.current;
|
|
23
|
+
const img = imgRef.current;
|
|
24
|
+
const frac = clickFractionRef.current;
|
|
25
|
+
if (zoomedWidth == null || !container || !img || !frac) return;
|
|
26
|
+
const frame = requestAnimationFrame(() => {
|
|
27
|
+
container.scrollTo(img.offsetLeft + img.offsetWidth * frac.x - container.clientWidth / 2, img.offsetTop + img.offsetHeight * frac.y - container.clientHeight / 2);
|
|
28
|
+
clickFractionRef.current = null;
|
|
29
|
+
});
|
|
30
|
+
return () => cancelAnimationFrame(frame);
|
|
31
|
+
};
|
|
32
|
+
t2 = [zoomedWidth];
|
|
33
|
+
$[0] = zoomedWidth;
|
|
34
|
+
$[1] = t1;
|
|
35
|
+
$[2] = t2;
|
|
36
|
+
} else {
|
|
37
|
+
t1 = $[1];
|
|
38
|
+
t2 = $[2];
|
|
39
|
+
}
|
|
40
|
+
useEffect(t1, t2);
|
|
41
|
+
const zoomed = zoomedWidth != null;
|
|
42
|
+
const t3 = `absolute inset-0 ${zoomed ? "overflow-auto" : "flex items-center justify-center overflow-hidden p-6"}`;
|
|
43
|
+
let t4;
|
|
44
|
+
if ($[3] !== zoomed) {
|
|
45
|
+
t4 = (e) => {
|
|
46
|
+
const img_0 = imgRef.current;
|
|
47
|
+
if (!img_0) return;
|
|
48
|
+
if (zoomed) {
|
|
49
|
+
setZoomedWidth(null);
|
|
50
|
+
containerRef.current?.scrollTo(0, 0);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const rect = img_0.getBoundingClientRect();
|
|
54
|
+
clickFractionRef.current = {
|
|
55
|
+
x: (e.clientX - rect.left) / rect.width,
|
|
56
|
+
y: (e.clientY - rect.top) / rect.height
|
|
57
|
+
};
|
|
58
|
+
setZoomedWidth(img_0.clientWidth * 2);
|
|
59
|
+
};
|
|
60
|
+
$[3] = zoomed;
|
|
61
|
+
$[4] = t4;
|
|
62
|
+
} else t4 = $[4];
|
|
63
|
+
let t5;
|
|
64
|
+
if ($[5] !== alt || $[6] !== src || $[7] !== zoomed || $[8] !== zoomedWidth) {
|
|
65
|
+
t5 = zoomed ? /* @__PURE__ */ jsx("div", {
|
|
66
|
+
className: "flex min-h-full w-max min-w-full p-6",
|
|
67
|
+
children: /* @__PURE__ */ jsx("img", {
|
|
68
|
+
ref: imgRef,
|
|
69
|
+
src,
|
|
70
|
+
alt,
|
|
71
|
+
className: "checkered m-auto max-w-none cursor-zoom-out shadow-lg",
|
|
72
|
+
style: { width: zoomedWidth },
|
|
73
|
+
draggable: false
|
|
74
|
+
})
|
|
75
|
+
}) : /* @__PURE__ */ jsx("img", {
|
|
76
|
+
ref: imgRef,
|
|
77
|
+
src,
|
|
78
|
+
alt,
|
|
79
|
+
className: "checkered max-h-full max-w-full cursor-zoom-in object-contain shadow-lg",
|
|
80
|
+
draggable: false
|
|
81
|
+
});
|
|
82
|
+
$[5] = alt;
|
|
83
|
+
$[6] = src;
|
|
84
|
+
$[7] = zoomed;
|
|
85
|
+
$[8] = zoomedWidth;
|
|
86
|
+
$[9] = t5;
|
|
87
|
+
} else t5 = $[9];
|
|
88
|
+
let t6;
|
|
89
|
+
if ($[10] !== t3 || $[11] !== t4 || $[12] !== t5) {
|
|
90
|
+
t6 = /* @__PURE__ */ jsx("div", {
|
|
91
|
+
ref: containerRef,
|
|
92
|
+
className: t3,
|
|
93
|
+
onClick: t4,
|
|
94
|
+
children: t5
|
|
95
|
+
});
|
|
96
|
+
$[10] = t3;
|
|
97
|
+
$[11] = t4;
|
|
98
|
+
$[12] = t5;
|
|
99
|
+
$[13] = t6;
|
|
100
|
+
} else t6 = $[13];
|
|
101
|
+
return t6;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
//#endregion
|
|
105
|
+
export { ZoomableImage };
|
|
@@ -7,6 +7,8 @@ function contentFieldSchema(schema, fieldName) {
|
|
|
7
7
|
function contentFieldNames(schema) {
|
|
8
8
|
return Object.keys(schema?.properties ?? {});
|
|
9
9
|
}
|
|
10
|
+
/** A field's name as a label, for fields without a title: `ctaLabel` reads "Cta Label". */
|
|
11
|
+
const formatFieldName = (fieldName) => fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
|
|
10
12
|
|
|
11
13
|
//#endregion
|
|
12
|
-
export { contentFieldNames, contentFieldSchema };
|
|
14
|
+
export { contentFieldNames, contentFieldSchema, formatFieldName };
|
|
@@ -3,13 +3,13 @@ import { referenceList } from "../../../lib/normalized-data.js";
|
|
|
3
3
|
import { reads, useStudioCache } from "../../../lib/studio-reads.js";
|
|
4
4
|
import { referenceListIds } from "../../../core/lib/reference.js";
|
|
5
5
|
import { recordText } from "../../../core/recordItem.js";
|
|
6
|
-
import { blockMutations,
|
|
7
|
-
import { invalidateCollectionRecordViews } from "../../../lib/collection-cache.js";
|
|
6
|
+
import { blockMutations, repeatableItemMutations } from "../../../lib/queries.js";
|
|
8
7
|
import { serializeAssetField } from "../../content/collection-form.js";
|
|
9
|
-
import {
|
|
8
|
+
import { fieldEdit } from "../../../core/editing/fieldSlot.js";
|
|
9
|
+
import { useApplyContentEdit } from "../../../core/editing/contentEdits.js";
|
|
10
10
|
import { contentFieldSchema } from "./contentFieldSchema.js";
|
|
11
11
|
import { c } from "react/compiler-runtime";
|
|
12
|
-
import { useMutation
|
|
12
|
+
import { useMutation } from "@tanstack/react-query";
|
|
13
13
|
import { toast } from "@camox/ui/toaster";
|
|
14
14
|
|
|
15
15
|
//#region src/features/preview/components/useFieldWriter.ts
|
|
@@ -73,8 +73,7 @@ function applyReferenceListOptimistically(studioCache, blockId, owner, fieldName
|
|
|
73
73
|
* collection asset snapshots rather than block file markers.
|
|
74
74
|
*/
|
|
75
75
|
function useFieldWriter(target, schema) {
|
|
76
|
-
const $ = c(
|
|
77
|
-
const queryClient = useQueryClient();
|
|
76
|
+
const $ = c(10);
|
|
78
77
|
const projectUuid = useProjectUuid();
|
|
79
78
|
let t0;
|
|
80
79
|
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
@@ -88,35 +87,17 @@ function useFieldWriter(target, schema) {
|
|
|
88
87
|
$[1] = t1;
|
|
89
88
|
} else t1 = $[1];
|
|
90
89
|
const updateItemContent = useMutation(t1);
|
|
91
|
-
|
|
92
|
-
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
|
|
93
|
-
t2 = collectionMutations.edit();
|
|
94
|
-
$[2] = t2;
|
|
95
|
-
} else t2 = $[2];
|
|
96
|
-
const editRecord = useMutation(t2);
|
|
90
|
+
const applyEdit = useApplyContentEdit();
|
|
97
91
|
const studioCache = useStudioCache();
|
|
98
|
-
let
|
|
99
|
-
if ($[
|
|
100
|
-
|
|
92
|
+
let t2;
|
|
93
|
+
if ($[2] !== applyEdit || $[3] !== projectUuid || $[4] !== schema || $[5] !== studioCache || $[6] !== target || $[7] !== updateBlockContent || $[8] !== updateItemContent) {
|
|
94
|
+
t2 = (fieldName, value) => {
|
|
101
95
|
if (target?.kind === "record") {
|
|
102
96
|
const fieldType = fieldTypeOf(schema, fieldName);
|
|
103
97
|
const awaited = fieldType === "Reference" || fieldType === "ReferenceList";
|
|
104
|
-
const
|
|
105
|
-
const saving = referenceWritesFor(queryClient).save(record, fieldName, serializeAssetField(fieldType, value), async (input) => {
|
|
106
|
-
const saved = await editRecord.mutateAsync({
|
|
107
|
-
...input,
|
|
108
|
-
projectUuid
|
|
109
|
-
});
|
|
110
|
-
studioCache.set(reads.record({
|
|
111
|
-
projectUuid,
|
|
112
|
-
collectionId: record.collectionId,
|
|
113
|
-
id: record.id
|
|
114
|
-
}), saved);
|
|
115
|
-
invalidateCollectionRecordViews(studioCache, projectUuid, record.collectionId);
|
|
116
|
-
return saved;
|
|
117
|
-
}).then(_temp);
|
|
98
|
+
const saving = applyEdit(fieldEdit(target, fieldName, serializeAssetField(fieldType, value)));
|
|
118
99
|
if (awaited) return saving;
|
|
119
|
-
saving.catch(
|
|
100
|
+
saving.catch(_temp);
|
|
120
101
|
return;
|
|
121
102
|
}
|
|
122
103
|
const content = { [fieldName]: value };
|
|
@@ -124,10 +105,10 @@ function useFieldWriter(target, schema) {
|
|
|
124
105
|
const id = target?.kind === "item" ? target.itemId : target?.blockId;
|
|
125
106
|
const field = contentFieldSchema(schema, fieldName);
|
|
126
107
|
if (field?.fieldType === "ReferenceList" && target) {
|
|
127
|
-
const { collectionId:
|
|
128
|
-
const collectionId =
|
|
108
|
+
const { collectionId: t3 } = field;
|
|
109
|
+
const collectionId = t3 === void 0 ? "" : t3;
|
|
129
110
|
const linkRecord = async (recordId) => {
|
|
130
|
-
const [
|
|
111
|
+
const [record, collection] = await Promise.all([studioCache.ensure(reads.record({
|
|
131
112
|
projectUuid,
|
|
132
113
|
collectionId,
|
|
133
114
|
id: recordId
|
|
@@ -136,10 +117,10 @@ function useFieldWriter(target, schema) {
|
|
|
136
117
|
collectionId
|
|
137
118
|
}))]);
|
|
138
119
|
return {
|
|
139
|
-
id:
|
|
120
|
+
id: record.id,
|
|
140
121
|
collectionId,
|
|
141
|
-
content:
|
|
142
|
-
...recordText(collection,
|
|
122
|
+
content: record.content,
|
|
123
|
+
...recordText(collection, record.content)
|
|
143
124
|
};
|
|
144
125
|
};
|
|
145
126
|
return (async () => {
|
|
@@ -156,8 +137,8 @@ function useFieldWriter(target, schema) {
|
|
|
156
137
|
id: target.blockId,
|
|
157
138
|
content
|
|
158
139
|
});
|
|
159
|
-
} catch (
|
|
160
|
-
const cause_0 =
|
|
140
|
+
} catch (t4) {
|
|
141
|
+
const cause_0 = t4;
|
|
161
142
|
restore();
|
|
162
143
|
throw cause_0;
|
|
163
144
|
}
|
|
@@ -177,22 +158,20 @@ function useFieldWriter(target, schema) {
|
|
|
177
158
|
content
|
|
178
159
|
});
|
|
179
160
|
};
|
|
180
|
-
$[
|
|
181
|
-
$[
|
|
182
|
-
$[
|
|
183
|
-
$[
|
|
184
|
-
$[
|
|
185
|
-
$[
|
|
186
|
-
$[
|
|
187
|
-
$[
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
return t3;
|
|
161
|
+
$[2] = applyEdit;
|
|
162
|
+
$[3] = projectUuid;
|
|
163
|
+
$[4] = schema;
|
|
164
|
+
$[5] = studioCache;
|
|
165
|
+
$[6] = target;
|
|
166
|
+
$[7] = updateBlockContent;
|
|
167
|
+
$[8] = updateItemContent;
|
|
168
|
+
$[9] = t2;
|
|
169
|
+
} else t2 = $[9];
|
|
170
|
+
return t2;
|
|
191
171
|
}
|
|
192
|
-
function
|
|
172
|
+
function _temp(cause) {
|
|
193
173
|
toast.error(cause instanceof Error ? cause.message : "Could not save item");
|
|
194
174
|
}
|
|
195
|
-
function _temp() {}
|
|
196
175
|
|
|
197
176
|
//#endregion
|
|
198
177
|
export { useFieldWriter };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { placedRecords, referenceList } from "../../../lib/normalized-data.js";
|
|
2
2
|
import { referenceListIds } from "../../../core/lib/reference.js";
|
|
3
3
|
import { useCollectionDefinition } from "../../../lib/studio-definitions.js";
|
|
4
|
-
import { isBlockPlacement,
|
|
5
|
-
import {
|
|
6
|
-
import { formatFieldName } from "./
|
|
4
|
+
import { isBlockPlacement, recordPlacement } from "../../../core/editing/fieldSlot.js";
|
|
5
|
+
import { previewStore } from "../previewStore.js";
|
|
6
|
+
import { contentFieldSchema, formatFieldName } from "./contentFieldSchema.js";
|
|
7
7
|
import { c } from "react/compiler-runtime";
|
|
8
8
|
import * as React from "react";
|
|
9
9
|
import { isQueryBacked } from "@camox/api-contract";
|
|
@@ -1,78 +1,40 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { placementId, slotId, slotOf } from "../../core/editing/fieldSlot.js";
|
|
2
2
|
|
|
3
3
|
//#region src/features/preview/overlayMessages.ts
|
|
4
4
|
/**
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
7
|
-
* the placement's ID followed by `__recordField` for the fields of a placed collection record.
|
|
8
|
-
* Record field IDs carry the record ID so that each record of a reference list is its own target.
|
|
5
|
+
* The message that starts or ends the sidebar's hover of what `selection` selects, as the page's
|
|
6
|
+
* editable fields and records follow it.
|
|
9
7
|
*/
|
|
10
|
-
function overlayFieldId(blockId, fieldName, scope = {}) {
|
|
11
|
-
if (scope.placement) return recordFieldId(scope.placement, fieldName);
|
|
12
|
-
if (scope.itemId != null) return `${blockId}__${scope.itemId}__${fieldName}`;
|
|
13
|
-
return `${blockId}__${fieldName}`;
|
|
14
|
-
}
|
|
15
|
-
/** The field ID of one field of a placed collection record. */
|
|
16
|
-
function recordFieldId(placement, recordFieldName) {
|
|
17
|
-
return `${recordPlacementId(placement)}__${recordFieldName}`;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* The overlay ID of one placement of a record: `block__referenceField__record`, or
|
|
21
|
-
* `block__item__referenceField__record` when a repeatable item places it, or
|
|
22
|
-
* `layout-layoutId__record` when a layout renders it on a record page. Followed by
|
|
23
|
-
* `__recordField__linkedRecord` for a record that record links. Unique per record
|
|
24
|
-
* within a reference list, and distinct from the same record placed elsewhere.
|
|
25
|
-
*/
|
|
26
|
-
function recordPlacementId(placement) {
|
|
27
|
-
const id = isBlockPlacement(placement) ? `${blockPlacementOwnerId(placement)}__${placement.fieldName}__${placement.recordId}` : `layout-${placement.layoutId}__${placement.recordId}`;
|
|
28
|
-
if (!placement.nested) return id;
|
|
29
|
-
return `${id}__${placement.nested.fieldName}__${placement.nested.recordId}`;
|
|
30
|
-
}
|
|
31
|
-
function blockPlacementOwnerId(placement) {
|
|
32
|
-
if (placement.itemId == null) return String(placement.blockId);
|
|
33
|
-
return `${placement.blockId}__${placement.itemId}`;
|
|
34
|
-
}
|
|
35
|
-
/** Use the same preview targets for selection-path hover as the sidebar editors. */
|
|
36
8
|
function selectionHoverMessage(selection, hovered) {
|
|
37
9
|
if (!selection) return { type: hovered ? "CAMOX_HOVER_PAGE" : "CAMOX_HOVER_PAGE_END" };
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (selection.type === "record-field") {
|
|
46
|
-
const fieldId = recordFieldId(selection, selection.recordFieldName);
|
|
10
|
+
const field = (fieldId) => ({
|
|
11
|
+
type: hovered ? "CAMOX_HOVER_FIELD" : "CAMOX_HOVER_FIELD_END",
|
|
12
|
+
fieldId
|
|
13
|
+
});
|
|
14
|
+
if (selection.type === "record") return field(placementId(selection));
|
|
15
|
+
if (selection.type === "block") {
|
|
16
|
+
const { blockId } = selection;
|
|
47
17
|
return {
|
|
48
|
-
type: hovered ? "
|
|
49
|
-
|
|
18
|
+
type: hovered ? "CAMOX_HOVER_BLOCK" : "CAMOX_HOVER_BLOCK_END",
|
|
19
|
+
blockId
|
|
50
20
|
};
|
|
51
21
|
}
|
|
52
|
-
const blockId = String(selection.blockId);
|
|
53
|
-
if (selection.type === "block") return {
|
|
54
|
-
type: hovered ? "CAMOX_HOVER_BLOCK" : "CAMOX_HOVER_BLOCK_END",
|
|
55
|
-
blockId
|
|
56
|
-
};
|
|
57
22
|
if (selection.type === "item") return {
|
|
58
23
|
type: hovered ? "CAMOX_HOVER_REPEATABLE_ITEM" : "CAMOX_HOVER_REPEATABLE_ITEM_END",
|
|
59
|
-
blockId,
|
|
60
|
-
itemId:
|
|
24
|
+
blockId: selection.blockId,
|
|
25
|
+
itemId: selection.itemId
|
|
61
26
|
};
|
|
62
|
-
|
|
27
|
+
const slot = slotOf(selection);
|
|
28
|
+
if (slot.type === "Repeater" && slot.owner.kind !== "record") return {
|
|
63
29
|
type: hovered ? "CAMOX_HOVER_REPEATER" : "CAMOX_HOVER_REPEATER_END",
|
|
64
|
-
blockId,
|
|
65
|
-
fieldName:
|
|
66
|
-
};
|
|
67
|
-
const fieldId = overlayFieldId(blockId, selection.fieldName, { itemId: selection.type === "item-field" ? selection.itemId : null });
|
|
68
|
-
return {
|
|
69
|
-
type: hovered ? "CAMOX_HOVER_FIELD" : "CAMOX_HOVER_FIELD_END",
|
|
70
|
-
fieldId
|
|
30
|
+
blockId: slot.owner.blockId,
|
|
31
|
+
fieldName: slot.name
|
|
71
32
|
};
|
|
33
|
+
return field(slotId(slot));
|
|
72
34
|
}
|
|
73
35
|
function isOverlayMessage(data) {
|
|
74
36
|
return typeof data === "object" && data !== null && "type" in data && typeof data.type === "string" && data.type.startsWith("CAMOX_");
|
|
75
37
|
}
|
|
76
38
|
|
|
77
39
|
//#endregion
|
|
78
|
-
export { isOverlayMessage,
|
|
40
|
+
export { isOverlayMessage, selectionHoverMessage };
|
|
@@ -1,43 +1,7 @@
|
|
|
1
|
+
import { isBlockPlacement, parentOf, recordPlacement, samePlacement } from "../../core/editing/fieldSlot.js";
|
|
1
2
|
import { createStore } from "@xstate/store-react";
|
|
2
3
|
|
|
3
4
|
//#region src/features/preview/previewStore.ts
|
|
4
|
-
/**
|
|
5
|
-
* Whether a block places the record. Checks `blockId` rather than `layoutId`, because editing
|
|
6
|
-
* events spread a layout editing owner's `layoutId` next to block-owned placements.
|
|
7
|
-
*/
|
|
8
|
-
function isBlockPlacement(placement) {
|
|
9
|
-
return "blockId" in placement && placement.blockId != null;
|
|
10
|
-
}
|
|
11
|
-
function copyNested(placement) {
|
|
12
|
-
if (!placement.nested) return {};
|
|
13
|
-
return { nested: {
|
|
14
|
-
fieldName: placement.nested.fieldName,
|
|
15
|
-
recordId: placement.nested.recordId
|
|
16
|
-
} };
|
|
17
|
-
}
|
|
18
|
-
function recordPlacement(placement) {
|
|
19
|
-
if (!isBlockPlacement(placement)) return {
|
|
20
|
-
layoutId: placement.layoutId,
|
|
21
|
-
recordId: placement.recordId,
|
|
22
|
-
...copyNested(placement)
|
|
23
|
-
};
|
|
24
|
-
return {
|
|
25
|
-
blockId: placement.blockId,
|
|
26
|
-
...placement.itemId == null ? {} : { itemId: placement.itemId },
|
|
27
|
-
fieldName: placement.fieldName,
|
|
28
|
-
recordId: placement.recordId,
|
|
29
|
-
...copyNested(placement)
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
/** Whether two placements are the same occurrence: same owner, field and record. */
|
|
33
|
-
function samePlacement(a, b) {
|
|
34
|
-
if (a.recordId !== b.recordId) return false;
|
|
35
|
-
if (a.nested?.fieldName !== b.nested?.fieldName) return false;
|
|
36
|
-
if (a.nested?.recordId !== b.nested?.recordId) return false;
|
|
37
|
-
if (!isBlockPlacement(a)) return !isBlockPlacement(b) && a.layoutId === b.layoutId;
|
|
38
|
-
if (!isBlockPlacement(b)) return false;
|
|
39
|
-
return a.blockId === b.blockId && (a.itemId ?? null) === (b.itemId ?? null) && a.fieldName === b.fieldName;
|
|
40
|
-
}
|
|
41
5
|
function sameEditingOwner(a, b) {
|
|
42
6
|
if (!a || !b) return a === b;
|
|
43
7
|
if (a.kind === "page") return b.kind === "page" && a.pageId === b.pageId;
|
|
@@ -84,15 +48,6 @@ function selectionItemId(sel) {
|
|
|
84
48
|
if (sel.type === "record" || sel.type === "record-field") return sel.itemId ?? null;
|
|
85
49
|
return null;
|
|
86
50
|
}
|
|
87
|
-
/** Check if the selection is viewing a terminal field (link, image, file, etc.). */
|
|
88
|
-
function selectionField(sel) {
|
|
89
|
-
if (!sel) return null;
|
|
90
|
-
if (sel.type === "block-field" || sel.type === "item-field") return {
|
|
91
|
-
fieldName: sel.fieldName,
|
|
92
|
-
fieldType: sel.fieldType
|
|
93
|
-
};
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
51
|
const selectIsCanvasShown = (state) => state.context.view === "canvas";
|
|
97
52
|
const initialPreviewContext = {
|
|
98
53
|
view: "preview",
|
|
@@ -221,87 +176,15 @@ const previewStore = createStore({
|
|
|
221
176
|
selectParent: (context) => {
|
|
222
177
|
const editingContext = context.editingContext;
|
|
223
178
|
const sel = editingContext?.selection;
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
editingContext: {
|
|
228
|
-
...editingContext,
|
|
229
|
-
selection: {
|
|
230
|
-
type: "record",
|
|
231
|
-
...recordPlacement(sel)
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
if (sel.type === "record" && sel.nested) {
|
|
236
|
-
const { nested: _nested, ...outer } = recordPlacement(sel);
|
|
237
|
-
return {
|
|
238
|
-
...context,
|
|
239
|
-
editingContext: {
|
|
240
|
-
...editingContext,
|
|
241
|
-
selection: {
|
|
242
|
-
type: "record-field",
|
|
243
|
-
...outer,
|
|
244
|
-
recordFieldName: sel.nested.fieldName,
|
|
245
|
-
recordFieldType: "Reference"
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
if (sel.type === "record" && !isBlockPlacement(sel)) return context;
|
|
251
|
-
if (sel.type === "record") {
|
|
252
|
-
const field = {
|
|
253
|
-
fieldName: sel.fieldName,
|
|
254
|
-
fieldType: "Reference"
|
|
255
|
-
};
|
|
256
|
-
return {
|
|
257
|
-
...context,
|
|
258
|
-
editingContext: {
|
|
259
|
-
...editingContext,
|
|
260
|
-
selection: sel.itemId == null ? {
|
|
261
|
-
type: "block-field",
|
|
262
|
-
blockId: sel.blockId,
|
|
263
|
-
...field
|
|
264
|
-
} : {
|
|
265
|
-
type: "item-field",
|
|
266
|
-
blockId: sel.blockId,
|
|
267
|
-
itemId: sel.itemId,
|
|
268
|
-
...field
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
if (sel.type === "block-field") return {
|
|
274
|
-
...context,
|
|
275
|
-
editingContext: {
|
|
276
|
-
...editingContext,
|
|
277
|
-
selection: {
|
|
278
|
-
type: "block",
|
|
279
|
-
blockId: sel.blockId
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
if (sel.type === "item-field") return {
|
|
284
|
-
...context,
|
|
285
|
-
editingContext: {
|
|
286
|
-
...editingContext,
|
|
287
|
-
selection: {
|
|
288
|
-
type: "item",
|
|
289
|
-
blockId: sel.blockId,
|
|
290
|
-
itemId: sel.itemId
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
|
-
if (sel.type === "item") return {
|
|
179
|
+
const parent = sel ? parentOf(sel) : null;
|
|
180
|
+
if (!parent || !editingContext) return context;
|
|
181
|
+
return {
|
|
295
182
|
...context,
|
|
296
183
|
editingContext: {
|
|
297
184
|
...editingContext,
|
|
298
|
-
selection:
|
|
299
|
-
type: "block",
|
|
300
|
-
blockId: sel.blockId
|
|
301
|
-
}
|
|
185
|
+
selection: parent
|
|
302
186
|
}
|
|
303
187
|
};
|
|
304
|
-
return context;
|
|
305
188
|
},
|
|
306
189
|
clearSelection: (context) => ({
|
|
307
190
|
...context,
|
|
@@ -333,4 +216,4 @@ const previewStore = createStore({
|
|
|
333
216
|
});
|
|
334
217
|
|
|
335
218
|
//#endregion
|
|
336
|
-
export { isBlockPlacement, isBlockSelection, previewStore, recordPlacement, sameEditingOwner,
|
|
219
|
+
export { isBlockPlacement, isBlockSelection, previewStore, recordPlacement, sameEditingOwner, selectIsCanvasShown, selectionBlockId, selectionForOwner, selectionForPage, selectionItemId };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/features/preview/repeatableItemPath.ts
|
|
2
|
+
/** The repeatable items from the block down to `itemId`, each item's parent before it. */
|
|
3
|
+
function itemPath(itemId, itemsMap) {
|
|
4
|
+
const path = [];
|
|
5
|
+
for (let item = itemId == null ? void 0 : itemsMap.get(itemId); item;) {
|
|
6
|
+
path.unshift(item);
|
|
7
|
+
item = item.parentItemId ? itemsMap.get(item.parentItemId) : void 0;
|
|
8
|
+
}
|
|
9
|
+
return path;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The repeater field schemas along a path, from the block's content schema: the one holding each
|
|
13
|
+
* item of the path, in order. Stops where the schema no longer declares the item's repeater.
|
|
14
|
+
*/
|
|
15
|
+
function repeaterSchemas(contentSchema, path) {
|
|
16
|
+
const repeaters = [];
|
|
17
|
+
let schema = contentSchema;
|
|
18
|
+
for (const item of path) {
|
|
19
|
+
const repeater = schema?.properties?.[item.fieldName];
|
|
20
|
+
if (!repeater?.items) break;
|
|
21
|
+
repeaters.push(repeater);
|
|
22
|
+
schema = repeater.items;
|
|
23
|
+
}
|
|
24
|
+
return repeaters;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The content schema of the path's last item: the block's own with no item, or null when the
|
|
28
|
+
* block's schema no longer declares it.
|
|
29
|
+
*/
|
|
30
|
+
function itemContentSchema(contentSchema, path) {
|
|
31
|
+
const repeaters = repeaterSchemas(contentSchema, path);
|
|
32
|
+
if (repeaters.length < path.length) return null;
|
|
33
|
+
const last = repeaters.at(-1);
|
|
34
|
+
return last ? last.items : contentSchema;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The repeater field schema holding the path's last item, where its item settings are declared,
|
|
38
|
+
* or null when the block's schema no longer declares it.
|
|
39
|
+
*/
|
|
40
|
+
function itemRepeaterSchema(contentSchema, path) {
|
|
41
|
+
if (path.length === 0) return null;
|
|
42
|
+
const repeaters = repeaterSchemas(contentSchema, path);
|
|
43
|
+
return repeaters.length < path.length ? null : repeaters.at(-1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { itemContentSchema, itemPath, itemRepeaterSchema };
|