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,251 @@
|
|
|
1
|
+
//#region src/core/editing/fieldSlot.ts
|
|
2
|
+
/**
|
|
3
|
+
* Whether a block places the record. Checks `blockId` rather than `layoutId`, because editing
|
|
4
|
+
* events spread a layout editing owner's `layoutId` next to block-owned placements.
|
|
5
|
+
*/
|
|
6
|
+
function isBlockPlacement(placement) {
|
|
7
|
+
return "blockId" in placement && placement.blockId != null;
|
|
8
|
+
}
|
|
9
|
+
function copyNested(placement) {
|
|
10
|
+
if (!placement.nested) return {};
|
|
11
|
+
return { nested: {
|
|
12
|
+
fieldName: placement.nested.fieldName,
|
|
13
|
+
recordId: placement.nested.recordId
|
|
14
|
+
} };
|
|
15
|
+
}
|
|
16
|
+
function recordPlacement(placement) {
|
|
17
|
+
if (!isBlockPlacement(placement)) return {
|
|
18
|
+
layoutId: placement.layoutId,
|
|
19
|
+
recordId: placement.recordId,
|
|
20
|
+
...copyNested(placement)
|
|
21
|
+
};
|
|
22
|
+
return {
|
|
23
|
+
blockId: placement.blockId,
|
|
24
|
+
...placement.itemId == null ? {} : { itemId: placement.itemId },
|
|
25
|
+
fieldName: placement.fieldName,
|
|
26
|
+
recordId: placement.recordId,
|
|
27
|
+
...copyNested(placement)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Whether two placements are the same occurrence: same owner, field and record. */
|
|
31
|
+
function samePlacement(a, b) {
|
|
32
|
+
if (a.recordId !== b.recordId) return false;
|
|
33
|
+
if (a.nested?.fieldName !== b.nested?.fieldName) return false;
|
|
34
|
+
if (a.nested?.recordId !== b.nested?.recordId) return false;
|
|
35
|
+
if (!isBlockPlacement(a)) return !isBlockPlacement(b) && a.layoutId === b.layoutId;
|
|
36
|
+
if (!isBlockPlacement(b)) return false;
|
|
37
|
+
return a.blockId === b.blockId && (a.itemId ?? null) === (b.itemId ?? null) && a.fieldName === b.fieldName;
|
|
38
|
+
}
|
|
39
|
+
/** The owner the loose ids name: a placed record's, else a repeatable item's, else the block's. */
|
|
40
|
+
function fieldOwner({ blockId, itemId, placement }) {
|
|
41
|
+
if (placement) return {
|
|
42
|
+
kind: "record",
|
|
43
|
+
placement
|
|
44
|
+
};
|
|
45
|
+
if (itemId != null) return {
|
|
46
|
+
kind: "item",
|
|
47
|
+
blockId,
|
|
48
|
+
itemId
|
|
49
|
+
};
|
|
50
|
+
return {
|
|
51
|
+
kind: "block",
|
|
52
|
+
blockId
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** The selection that selects the slot. */
|
|
56
|
+
function slotSelection({ owner, name, type }) {
|
|
57
|
+
switch (owner.kind) {
|
|
58
|
+
case "block": return {
|
|
59
|
+
type: "block-field",
|
|
60
|
+
blockId: owner.blockId,
|
|
61
|
+
fieldName: name,
|
|
62
|
+
fieldType: type
|
|
63
|
+
};
|
|
64
|
+
case "item": return {
|
|
65
|
+
type: "item-field",
|
|
66
|
+
blockId: owner.blockId,
|
|
67
|
+
itemId: owner.itemId,
|
|
68
|
+
fieldName: name,
|
|
69
|
+
fieldType: type
|
|
70
|
+
};
|
|
71
|
+
case "record": return {
|
|
72
|
+
type: "record-field",
|
|
73
|
+
...recordPlacement(owner.placement),
|
|
74
|
+
recordFieldName: name,
|
|
75
|
+
recordFieldType: type
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** The slot a selection selects, or null when it selects no field. */
|
|
80
|
+
function slotOf(selection) {
|
|
81
|
+
switch (selection?.type) {
|
|
82
|
+
case "block-field": return {
|
|
83
|
+
owner: {
|
|
84
|
+
kind: "block",
|
|
85
|
+
blockId: selection.blockId
|
|
86
|
+
},
|
|
87
|
+
name: selection.fieldName,
|
|
88
|
+
type: selection.fieldType
|
|
89
|
+
};
|
|
90
|
+
case "item-field": return {
|
|
91
|
+
owner: {
|
|
92
|
+
kind: "item",
|
|
93
|
+
blockId: selection.blockId,
|
|
94
|
+
itemId: selection.itemId
|
|
95
|
+
},
|
|
96
|
+
name: selection.fieldName,
|
|
97
|
+
type: selection.fieldType
|
|
98
|
+
};
|
|
99
|
+
case "record-field": return {
|
|
100
|
+
owner: {
|
|
101
|
+
kind: "record",
|
|
102
|
+
placement: recordPlacement(selection)
|
|
103
|
+
},
|
|
104
|
+
name: selection.recordFieldName,
|
|
105
|
+
type: selection.recordFieldType
|
|
106
|
+
};
|
|
107
|
+
default: return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** The selection that selects the owner itself: a slot's parent. */
|
|
111
|
+
function ownerSelection(owner) {
|
|
112
|
+
switch (owner.kind) {
|
|
113
|
+
case "block": return {
|
|
114
|
+
type: "block",
|
|
115
|
+
blockId: owner.blockId
|
|
116
|
+
};
|
|
117
|
+
case "item": return {
|
|
118
|
+
type: "item",
|
|
119
|
+
blockId: owner.blockId,
|
|
120
|
+
itemId: owner.itemId
|
|
121
|
+
};
|
|
122
|
+
case "record": return {
|
|
123
|
+
type: "record",
|
|
124
|
+
...recordPlacement(owner.placement)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* What selecting the parent of `selection` selects: a field's owner; a placed record's
|
|
130
|
+
* reference field, in the block, item or record that places it; an item's block. A block and a
|
|
131
|
+
* record page's record have none.
|
|
132
|
+
*/
|
|
133
|
+
function parentOf(selection) {
|
|
134
|
+
const slot = slotOf(selection);
|
|
135
|
+
if (slot) return ownerSelection(slot.owner);
|
|
136
|
+
switch (selection.type) {
|
|
137
|
+
case "item": return {
|
|
138
|
+
type: "block",
|
|
139
|
+
blockId: selection.blockId
|
|
140
|
+
};
|
|
141
|
+
case "record":
|
|
142
|
+
if (selection.nested) {
|
|
143
|
+
const { nested, ...outer } = recordPlacement(selection);
|
|
144
|
+
return slotSelection({
|
|
145
|
+
owner: {
|
|
146
|
+
kind: "record",
|
|
147
|
+
placement: outer
|
|
148
|
+
},
|
|
149
|
+
name: nested.fieldName,
|
|
150
|
+
type: "Reference"
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (!isBlockPlacement(selection)) return null;
|
|
154
|
+
return slotSelection({
|
|
155
|
+
owner: fieldOwner(selection),
|
|
156
|
+
name: selection.fieldName,
|
|
157
|
+
type: "Reference"
|
|
158
|
+
});
|
|
159
|
+
default: return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Where a record is placed through the reference field `fieldName` of `owner`: directly by a
|
|
164
|
+
* block or repeatable item, or, from a placed record, as the second hop it links.
|
|
165
|
+
*/
|
|
166
|
+
function placementThrough(owner, fieldName, recordId) {
|
|
167
|
+
if (owner.kind === "record") return {
|
|
168
|
+
...recordPlacement(owner.placement),
|
|
169
|
+
nested: {
|
|
170
|
+
fieldName,
|
|
171
|
+
recordId
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
return {
|
|
175
|
+
blockId: owner.blockId,
|
|
176
|
+
...owner.kind === "item" ? { itemId: owner.itemId } : {},
|
|
177
|
+
fieldName,
|
|
178
|
+
recordId
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The overlay id of one placement of a record: `block__referenceField__record`, or
|
|
183
|
+
* `block__item__referenceField__record` when a repeatable item places it, or
|
|
184
|
+
* `layout-layoutId__record` when a layout renders it on a record page. Followed by
|
|
185
|
+
* `__recordField__linkedRecord` for a record that record links. Unique per record
|
|
186
|
+
* within a reference list, and distinct from the same record placed elsewhere.
|
|
187
|
+
*/
|
|
188
|
+
function placementId(placement) {
|
|
189
|
+
const id = isBlockPlacement(placement) ? `${ownerId(fieldOwner(placement))}__${placement.fieldName}__${placement.recordId}` : `layout-${placement.layoutId}__${placement.recordId}`;
|
|
190
|
+
if (!placement.nested) return id;
|
|
191
|
+
return `${id}__${placement.nested.fieldName}__${placement.nested.recordId}`;
|
|
192
|
+
}
|
|
193
|
+
function ownerId(owner) {
|
|
194
|
+
switch (owner.kind) {
|
|
195
|
+
case "block": return owner.blockId;
|
|
196
|
+
case "item": return `${owner.blockId}__${owner.itemId}`;
|
|
197
|
+
case "record": return placementId(owner.placement);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The slot's overlay id: `block__field`, `block__item__field`, or the record placement's id
|
|
202
|
+
* followed by `__recordField`, so each record of a reference list is its own target.
|
|
203
|
+
*/
|
|
204
|
+
function slotId({ owner, name }) {
|
|
205
|
+
return `${ownerId(owner)}__${name}`;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Whether two selections point to the same thing. A field is the same field whatever kind it is
|
|
209
|
+
* read as: its name is unique in its owner.
|
|
210
|
+
*/
|
|
211
|
+
function sameTarget(a, b) {
|
|
212
|
+
if (!a || !b) return false;
|
|
213
|
+
const slotA = slotOf(a);
|
|
214
|
+
const slotB = slotOf(b);
|
|
215
|
+
if (slotA || slotB) return !!slotA && !!slotB && slotA.name === slotB.name && sameOwner(slotA.owner, slotB.owner);
|
|
216
|
+
if (a.type === "record" || b.type === "record") return a.type === "record" && b.type === "record" && samePlacement(a, b);
|
|
217
|
+
if (a.type === "item" || b.type === "item") return a.type === "item" && b.type === "item" && a.itemId === b.itemId;
|
|
218
|
+
return a.type === "block" && b.type === "block" && a.blockId === b.blockId;
|
|
219
|
+
}
|
|
220
|
+
function sameOwner(a, b) {
|
|
221
|
+
if (a.kind === "record" || b.kind === "record") return a.kind === "record" && b.kind === "record" && samePlacement(a.placement, b.placement);
|
|
222
|
+
if (a.kind === "item" || b.kind === "item") return a.kind === "item" && b.kind === "item" && a.itemId === b.itemId;
|
|
223
|
+
return a.blockId === b.blockId;
|
|
224
|
+
}
|
|
225
|
+
/** The edit that writes `value` to field `name` of the target. */
|
|
226
|
+
function fieldEdit(target, name, value) {
|
|
227
|
+
switch (target.kind) {
|
|
228
|
+
case "block": return {
|
|
229
|
+
type: "blockContent",
|
|
230
|
+
blockId: target.blockId,
|
|
231
|
+
content: { [name]: value }
|
|
232
|
+
};
|
|
233
|
+
case "item": return {
|
|
234
|
+
type: "itemContent",
|
|
235
|
+
itemId: target.itemId,
|
|
236
|
+
content: { [name]: value }
|
|
237
|
+
};
|
|
238
|
+
case "record":
|
|
239
|
+
const { projectUuid, record } = target;
|
|
240
|
+
return {
|
|
241
|
+
type: "recordField",
|
|
242
|
+
projectUuid,
|
|
243
|
+
record,
|
|
244
|
+
field: name,
|
|
245
|
+
value
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
//#endregion
|
|
251
|
+
export { fieldEdit, fieldOwner, isBlockPlacement, ownerSelection, parentOf, placementId, placementThrough, recordPlacement, samePlacement, sameTarget, slotId, slotOf, slotSelection };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { useIsAuthenticated } from "../../lib/auth.js";
|
|
2
|
+
import { fieldEdit, ownerSelection, sameTarget, slotId, slotSelection } from "./fieldSlot.js";
|
|
3
|
+
import { previewStore, selectIsCanvasShown } from "../../features/preview/previewStore.js";
|
|
4
|
+
import { isOverlayMessage, selectionHoverMessage } from "../../features/preview/overlayMessages.js";
|
|
5
|
+
import { useFrame } from "../../features/preview/components/Frame.js";
|
|
6
|
+
import { usePreviewSelection, usePreviewTargetSelection } from "../../features/preview/previewSelection.js";
|
|
7
|
+
import { useApplyContentEdit } from "./contentEdits.js";
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import { useSelector } from "@xstate/store-react";
|
|
10
|
+
|
|
11
|
+
//#region src/core/editing/useFieldSlot.tsx
|
|
12
|
+
/** Whether `data` carries every entry of `message`. */
|
|
13
|
+
const carries = (data, message) => Object.entries(message).every(([key, value]) => data[key] === value);
|
|
14
|
+
/**
|
|
15
|
+
* The studio's transport: the preview store, scoped to the page the frame edits, and the
|
|
16
|
+
* sidebar's hover messages posted to the frame's window.
|
|
17
|
+
*/
|
|
18
|
+
const studioTransport = {
|
|
19
|
+
useEditing() {
|
|
20
|
+
const isAuthenticated = useIsAuthenticated();
|
|
21
|
+
const isCanvasShown = useSelector(previewStore, selectIsCanvasShown);
|
|
22
|
+
return isAuthenticated && isCanvasShown;
|
|
23
|
+
},
|
|
24
|
+
useSelection: usePreviewTargetSelection,
|
|
25
|
+
useSelect: usePreviewSelection,
|
|
26
|
+
useSidebarHover(target, enabled) {
|
|
27
|
+
const { window: frameWindow } = useFrame();
|
|
28
|
+
const [hovered, setHovered] = React.useState(false);
|
|
29
|
+
const start = target ? JSON.stringify(selectionHoverMessage(target, true)) : null;
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
if (!enabled || !frameWindow || !start) return;
|
|
32
|
+
const startMessage = JSON.parse(start);
|
|
33
|
+
const endMessage = {
|
|
34
|
+
...startMessage,
|
|
35
|
+
type: `${String(startMessage.type)}_END`
|
|
36
|
+
};
|
|
37
|
+
const handleMessage = (event) => {
|
|
38
|
+
if (!isOverlayMessage(event.data)) return;
|
|
39
|
+
const data = event.data;
|
|
40
|
+
if (carries(data, startMessage)) setHovered(true);
|
|
41
|
+
else if (carries(data, endMessage)) setHovered(false);
|
|
42
|
+
};
|
|
43
|
+
frameWindow.addEventListener("message", handleMessage);
|
|
44
|
+
return () => frameWindow.removeEventListener("message", handleMessage);
|
|
45
|
+
}, [
|
|
46
|
+
enabled,
|
|
47
|
+
frameWindow,
|
|
48
|
+
start
|
|
49
|
+
]);
|
|
50
|
+
return enabled && hovered;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const SelectionTransportContext = React.createContext(studioTransport);
|
|
54
|
+
function useTransport() {
|
|
55
|
+
return React.use(SelectionTransportContext);
|
|
56
|
+
}
|
|
57
|
+
/** What is selected on the page this frame edits, or null. */
|
|
58
|
+
function useEditingSelection() {
|
|
59
|
+
return useTransport().useSelection();
|
|
60
|
+
}
|
|
61
|
+
/** Selects a target on the page this frame edits. */
|
|
62
|
+
function useSelectTarget() {
|
|
63
|
+
return useTransport().useSelect();
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Whether the frame offers editing while the Canvas shows. Only the selected Page's frame does:
|
|
67
|
+
* the others render as the site does, so a click on them only selects the Page. Frames keep
|
|
68
|
+
* the editing runtime either way, so toggling never remounts the page.
|
|
69
|
+
*/
|
|
70
|
+
const EditableSurfaceContext = React.createContext(true);
|
|
71
|
+
/**
|
|
72
|
+
* Whether content on this page edits in place: for a member, while the Canvas shows, in the
|
|
73
|
+
* frame that offers editing.
|
|
74
|
+
*/
|
|
75
|
+
function useIsEditable() {
|
|
76
|
+
return useTransport().useEditing() && React.use(EditableSurfaceContext);
|
|
77
|
+
}
|
|
78
|
+
/** Whether the sidebar hovers `target`, followed only while `enabled`. */
|
|
79
|
+
function useSidebarHover(target, enabled) {
|
|
80
|
+
return useTransport().useSidebarHover(target, enabled);
|
|
81
|
+
}
|
|
82
|
+
const FieldScopeContext = React.createContext(null);
|
|
83
|
+
/** The scope fields render in here, or null outside any. */
|
|
84
|
+
function useFieldScope() {
|
|
85
|
+
return React.use(FieldScopeContext);
|
|
86
|
+
}
|
|
87
|
+
/** Inline field writes report no failure unless their owner follows them. */
|
|
88
|
+
const ignore = () => {};
|
|
89
|
+
/** How an editable element is outlined: shared content is told apart from the block's own. */
|
|
90
|
+
function overlayMode(owner, synced) {
|
|
91
|
+
if (owner.kind === "record") return "reference";
|
|
92
|
+
return synced ? "synced" : void 0;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* What a field kind needs to edit one field where it renders: its value and schema, whether it
|
|
96
|
+
* is hovered, selected and focused, how to select it, how to write it, and its parent. Its
|
|
97
|
+
* overlay props, spread onto the element that stands for the field, outline it and select it on
|
|
98
|
+
* click.
|
|
99
|
+
*/
|
|
100
|
+
function useFieldSlot(name, type, { editing = false, enabled = true } = {}) {
|
|
101
|
+
const scope = useFieldScope();
|
|
102
|
+
if (!scope) throw new Error(`The "${name}" field must render inside a Block Component`);
|
|
103
|
+
const transport = useTransport();
|
|
104
|
+
const editable = useIsEditable() && enabled;
|
|
105
|
+
const slot = scope.list ? {
|
|
106
|
+
owner: scope.owner,
|
|
107
|
+
name: scope.list.name,
|
|
108
|
+
type: scope.list.type
|
|
109
|
+
} : {
|
|
110
|
+
owner: scope.owner,
|
|
111
|
+
name,
|
|
112
|
+
type
|
|
113
|
+
};
|
|
114
|
+
const target = slotSelection(slot);
|
|
115
|
+
const selection = transport.useSelection();
|
|
116
|
+
const selectTarget = transport.useSelect();
|
|
117
|
+
const sidebarHovered = transport.useSidebarHover(target, editable);
|
|
118
|
+
const [pointerHovered, setPointerHovered] = React.useState(false);
|
|
119
|
+
const applyEdit = useApplyContentEdit();
|
|
120
|
+
const selected = sameTarget(selection, target);
|
|
121
|
+
const hovered = editable && (pointerHovered || sidebarHovered);
|
|
122
|
+
const focused = editable && (selected || editing);
|
|
123
|
+
const select = () => {
|
|
124
|
+
if (editable) selectTarget(target);
|
|
125
|
+
};
|
|
126
|
+
const write = (value) => {
|
|
127
|
+
if (!scope.target) return;
|
|
128
|
+
const writing = applyEdit(fieldEdit(scope.target, slot.name, value));
|
|
129
|
+
if (scope.onWrite) scope.onWrite(writing);
|
|
130
|
+
else writing.catch(ignore);
|
|
131
|
+
};
|
|
132
|
+
const id = slotId(slot);
|
|
133
|
+
return {
|
|
134
|
+
slot,
|
|
135
|
+
id,
|
|
136
|
+
editable,
|
|
137
|
+
value: scope.content[name],
|
|
138
|
+
schema: scope.schema?.[slot.name],
|
|
139
|
+
hovered,
|
|
140
|
+
selected,
|
|
141
|
+
focused,
|
|
142
|
+
select,
|
|
143
|
+
write,
|
|
144
|
+
/** The owner's selection: what selecting the field's parent selects. */
|
|
145
|
+
parent: ownerSelection(slot.owner),
|
|
146
|
+
overlayProps: {
|
|
147
|
+
"data-camox-field-id": id,
|
|
148
|
+
"data-camox-hovered": hovered || void 0,
|
|
149
|
+
"data-camox-focused": focused || void 0,
|
|
150
|
+
"data-camox-overlay-mode": overlayMode(slot.owner, scope.synced),
|
|
151
|
+
onMouseEnter: () => setPointerHovered(true),
|
|
152
|
+
onMouseLeave: () => setPointerHovered(false),
|
|
153
|
+
onClickCapture: select
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
//#endregion
|
|
159
|
+
export { EditableSurfaceContext, FieldScopeContext, SelectionTransportContext, studioTransport, useEditingSelection, useFieldScope, useFieldSlot, useIsEditable, useSelectTarget, useSidebarHover };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { localizedPath } from "../lib/locale-path.js";
|
|
2
|
+
import { asCollectionItem, recordPageValues } from "./recordItem.js";
|
|
2
3
|
|
|
3
4
|
//#region src/core/layoutContext.ts
|
|
4
5
|
/**
|
|
@@ -61,6 +62,31 @@ function buildLayoutContext(layout, source) {
|
|
|
61
62
|
...source.record && { content: source.record.content }
|
|
62
63
|
};
|
|
63
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* A Layout Page's context, from what its loader returned, and the record it renders when it's a
|
|
67
|
+
* Record Page. The server's Page Read and the client's render build it the same way.
|
|
68
|
+
*/
|
|
69
|
+
function layoutPageContext(app, layout, source) {
|
|
70
|
+
const item = asCollectionItem(source.result);
|
|
71
|
+
const collection = item && app.getCollectionById(item.collectionId);
|
|
72
|
+
const record = item && collection && {
|
|
73
|
+
item,
|
|
74
|
+
collection,
|
|
75
|
+
values: recordPageValues(collection, item)
|
|
76
|
+
};
|
|
77
|
+
const context = buildLayoutContext(layout, {
|
|
78
|
+
path: source.path,
|
|
79
|
+
locale: source.locale,
|
|
80
|
+
projectName: source.projectName,
|
|
81
|
+
data: source.result?.data,
|
|
82
|
+
params: source.params,
|
|
83
|
+
record: record?.values
|
|
84
|
+
});
|
|
85
|
+
return record ? {
|
|
86
|
+
context,
|
|
87
|
+
record
|
|
88
|
+
} : { context };
|
|
89
|
+
}
|
|
64
90
|
|
|
65
91
|
//#endregion
|
|
66
|
-
export { buildLayoutContext, curatedLayoutContext, curatedPageTitle };
|
|
92
|
+
export { buildLayoutContext, curatedLayoutContext, curatedPageTitle, layoutPageContext };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { ArtifactsRunContext } from "../../core/hooks/useArtifactHost.js";
|
|
2
2
|
import { useEditingFrameClaim, useEditingOwner } from "../preview/editingFrame.js";
|
|
3
|
-
import {
|
|
3
|
+
import { PreviewDocumentContext } from "../runtime/PreviewDocumentContext.js";
|
|
4
4
|
import { PreviewEditingOwnerContext } from "../preview/previewSelection.js";
|
|
5
|
+
import { EditableSurfaceContext } from "../../core/editing/useFieldSlot.js";
|
|
5
6
|
import { RendererSlot, useRendererHost } from "../renderer/RendererFrames.js";
|
|
6
7
|
import { PreviewPreparationContext } from "../preview/previewPreparation.js";
|
|
7
8
|
import { canvasPageHref, measureCanvasDocument } from "./canvasFrameDocument.js";
|
|
8
|
-
import { PreviewDocumentContext } from "../runtime/PreviewDocumentContext.js";
|
|
9
9
|
import { Overlays } from "../preview/components/Overlays.js";
|
|
10
10
|
import { PreviewFrameEffects } from "../preview/components/PreviewPanel.js";
|
|
11
11
|
import { CanvasOverlays } from "./CanvasOverlays.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useEditingFrameClaim, useEditingOwner } from "../preview/editingFrame.js";
|
|
2
|
+
import { ViewportContinuityContext } from "../preview/viewportContinuity.js";
|
|
2
3
|
import { CanvasWorkspaceContext } from "./canvasZoom.js";
|
|
3
4
|
import { usePreviewPageFrame } from "../preview/previewPageFrame.js";
|
|
4
5
|
import { PreviewPreparationContext } from "../preview/previewPreparation.js";
|
|
5
|
-
import { ViewportContinuityContext } from "../preview/viewportContinuity.js";
|
|
6
6
|
import { useCanvasFrameMode } from "./canvasFrameMode.js";
|
|
7
7
|
import { CanvasOverlays } from "./CanvasOverlays.js";
|
|
8
8
|
import { CanvasPageEdge } from "./CanvasPageEdge.js";
|