@jxsuite/studio 0.9.0 → 0.10.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/studio.js +137161 -137144
- package/dist/studio.js.map +100 -97
- package/package.json +2 -2
- package/src/canvas/canvas-helpers.js +121 -0
- package/src/canvas/canvas-live-render.js +286 -0
- package/src/canvas/canvas-render.js +429 -0
- package/src/canvas/canvas-utils.js +33 -69
- package/src/editor/component-inline-edit.js +5 -4
- package/src/editor/content-inline-edit.js +11 -18
- package/src/panels/block-action-bar.js +8 -4
- package/src/panels/canvas-dnd.js +2 -13
- package/src/panels/left-panel.js +1 -2
- package/src/panels/overlays.js +9 -23
- package/src/panels/panel-events.js +10 -13
- package/src/panels/pseudo-preview.js +7 -14
- package/src/panels/shared.js +1 -1
- package/src/platform.js +9 -6
- package/src/state.js +9 -1
- package/src/store.js +9 -0
- package/src/studio.js +57 -807
- package/src/view.js +0 -2
|
@@ -23,6 +23,7 @@ import { isEditing, getActiveElement, getInlineActions } from "../editor/inline-
|
|
|
23
23
|
import { toggleInlineFormat, isTagActiveInSelection } from "../editor/inline-format.js";
|
|
24
24
|
import { componentRegistry } from "../files/components.js";
|
|
25
25
|
import { convertToComponent } from "../editor/convert-to-component.js";
|
|
26
|
+
import { findCanvasElement, getActivePanel } from "../canvas/canvas-helpers.js";
|
|
26
27
|
|
|
27
28
|
/** @type {any} */
|
|
28
29
|
let _ctx = null;
|
|
@@ -32,8 +33,6 @@ let _ctx = null;
|
|
|
32
33
|
*
|
|
33
34
|
* @param {{
|
|
34
35
|
* getCanvasMode: () => string;
|
|
35
|
-
* findCanvasElement: Function;
|
|
36
|
-
* getActivePanel: Function;
|
|
37
36
|
* navigateToComponent: Function;
|
|
38
37
|
* createFloatingContainer: Function;
|
|
39
38
|
* }} ctx
|
|
@@ -186,6 +185,11 @@ export function dismissLinkPopover() {
|
|
|
186
185
|
if (view.linkPopoverHost) litRender(nothing, view.linkPopoverHost);
|
|
187
186
|
}
|
|
188
187
|
|
|
188
|
+
/** Dismiss the block action bar. */
|
|
189
|
+
export function dismissBlockActionBar() {
|
|
190
|
+
if (view.blockActionBarEl) litRender(nothing, view.blockActionBarEl);
|
|
191
|
+
}
|
|
192
|
+
|
|
189
193
|
/** @param {any} anchorBtn */
|
|
190
194
|
function showLinkPopover(anchorBtn) {
|
|
191
195
|
litRender(nothing, view.linkPopoverHost);
|
|
@@ -312,12 +316,12 @@ export function renderBlockActionBar() {
|
|
|
312
316
|
return;
|
|
313
317
|
}
|
|
314
318
|
|
|
315
|
-
const activePanel =
|
|
319
|
+
const activePanel = getActivePanel();
|
|
316
320
|
if (!activePanel) {
|
|
317
321
|
litRender(nothing, view.blockActionBarEl);
|
|
318
322
|
return;
|
|
319
323
|
}
|
|
320
|
-
const el =
|
|
324
|
+
const el = findCanvasElement(S.selection, activePanel.canvas);
|
|
321
325
|
const node = el && getNodeAtPath(S.document, S.selection);
|
|
322
326
|
if (!el || !node) {
|
|
323
327
|
litRender(nothing, view.blockActionBarEl);
|
package/src/panels/canvas-dnd.js
CHANGED
|
@@ -18,18 +18,7 @@ import {
|
|
|
18
18
|
} from "../store.js";
|
|
19
19
|
import { view } from "../view.js";
|
|
20
20
|
import { applyDropInstruction } from "../panels/dnd.js";
|
|
21
|
-
|
|
22
|
-
/** @type {any} */
|
|
23
|
-
let _ctx = null;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Initialize the canvas DnD module.
|
|
27
|
-
*
|
|
28
|
-
* @param {{ effectiveZoom: () => number }} ctx
|
|
29
|
-
*/
|
|
30
|
-
export function initCanvasDnD(ctx) {
|
|
31
|
-
_ctx = ctx;
|
|
32
|
-
}
|
|
21
|
+
import { effectiveZoom } from "../canvas/canvas-helpers.js";
|
|
33
22
|
|
|
34
23
|
/**
|
|
35
24
|
* Register all canvas elements in a panel as DnD drop targets.
|
|
@@ -133,7 +122,7 @@ function showCanvasDropIndicator(el, elPath, isVoid, panel) {
|
|
|
133
122
|
return;
|
|
134
123
|
}
|
|
135
124
|
|
|
136
|
-
const scale =
|
|
125
|
+
const scale = effectiveZoom();
|
|
137
126
|
const wrapRect = viewport.getBoundingClientRect();
|
|
138
127
|
const elRect = el.getBoundingClientRect();
|
|
139
128
|
const left = (elRect.left - wrapRect.left + viewport.scrollLeft) / scale;
|
package/src/panels/left-panel.js
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
applyMutation,
|
|
16
16
|
updateFrontmatter,
|
|
17
17
|
} from "../store.js";
|
|
18
|
-
import { view } from "../view.js";
|
|
19
18
|
import { ensureLitState } from "./shared.js";
|
|
20
19
|
import { renderLayersTemplate } from "./layers-panel.js";
|
|
21
20
|
import { renderStylebookLayersTemplate } from "./stylebook-layers-panel.js";
|
|
@@ -96,7 +95,7 @@ function _render() {
|
|
|
96
95
|
updateSession,
|
|
97
96
|
});
|
|
98
97
|
else if (tab === "data")
|
|
99
|
-
content = _ctx.renderDataExplorerTemplate(S.document.state,
|
|
98
|
+
content = _ctx.renderDataExplorerTemplate(S.document.state, S.canvas?.scope ?? null, {
|
|
100
99
|
renderCanvas: _ctx.renderCanvas,
|
|
101
100
|
renderLeftPanel: render,
|
|
102
101
|
defCategory: _ctx.defCategory,
|
package/src/panels/overlays.js
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
import { html, render as litRender, nothing } from "lit-html";
|
|
7
7
|
import { getState, canvasPanels, pathsEqual, subscribe } from "../store.js";
|
|
8
8
|
import { view } from "../view.js";
|
|
9
|
+
import {
|
|
10
|
+
findCanvasElement,
|
|
11
|
+
getActivePanel,
|
|
12
|
+
overlayBoxDescriptor,
|
|
13
|
+
} from "../canvas/canvas-helpers.js";
|
|
9
14
|
|
|
10
15
|
/** @type {any} */
|
|
11
16
|
let _ctx = null;
|
|
@@ -16,8 +21,7 @@ let _unsub = null;
|
|
|
16
21
|
/**
|
|
17
22
|
* Mount the overlays panel.
|
|
18
23
|
*
|
|
19
|
-
* @param {any} ctx — {
|
|
20
|
-
* findCanvasElement, getActivePanel }
|
|
24
|
+
* @param {any} ctx — { getCanvasMode, isEditing, renderBlockActionBar }
|
|
21
25
|
*/
|
|
22
26
|
export function mount(ctx) {
|
|
23
27
|
_ctx = ctx;
|
|
@@ -32,24 +36,6 @@ export function unmount() {
|
|
|
32
36
|
_ctx = null;
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
/**
|
|
36
|
-
* @param {any} el
|
|
37
|
-
* @param {any} type
|
|
38
|
-
* @param {any} panel
|
|
39
|
-
*/
|
|
40
|
-
function overlayBoxDescriptor(el, type, panel) {
|
|
41
|
-
const vpRect = panel.viewport.getBoundingClientRect();
|
|
42
|
-
const elRect = el.getBoundingClientRect();
|
|
43
|
-
const scale = _ctx.effectiveZoom();
|
|
44
|
-
return {
|
|
45
|
-
cls: `overlay-box overlay-${type}`,
|
|
46
|
-
top: `${(elRect.top - vpRect.top + panel.viewport.scrollTop) / scale}px`,
|
|
47
|
-
left: `${(elRect.left - vpRect.left + panel.viewport.scrollLeft) / scale}px`,
|
|
48
|
-
width: `${elRect.width / scale}px`,
|
|
49
|
-
height: `${elRect.height / scale}px`,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
39
|
export function render() {
|
|
54
40
|
if (!_ctx) return;
|
|
55
41
|
const S = getState();
|
|
@@ -98,12 +84,12 @@ export function render() {
|
|
|
98
84
|
const boxes = [];
|
|
99
85
|
|
|
100
86
|
if (S.hover && !pathsEqual(S.hover, S.selection)) {
|
|
101
|
-
const el =
|
|
87
|
+
const el = findCanvasElement(S.hover, p.canvas);
|
|
102
88
|
if (el) boxes.push(overlayBoxDescriptor(el, "hover", p));
|
|
103
89
|
}
|
|
104
90
|
|
|
105
|
-
if (S.selection && p ===
|
|
106
|
-
const el =
|
|
91
|
+
if (S.selection && p === getActivePanel()) {
|
|
92
|
+
const el = findCanvasElement(S.selection, p.canvas);
|
|
107
93
|
if (el) {
|
|
108
94
|
const desc = overlayBoxDescriptor(el, "selection", p);
|
|
109
95
|
if (view.componentInlineEdit || _ctx.isEditing()) /** @type {any} */ (desc).border = "none";
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
update,
|
|
9
|
+
updateUi,
|
|
9
10
|
selectNode,
|
|
10
11
|
hoverNode,
|
|
11
12
|
elToPath,
|
|
@@ -21,6 +22,7 @@ import { stopEditing, isEditing, isEditableBlock } from "../editor/inline-edit.j
|
|
|
21
22
|
import { showContextMenu } from "../editor/context-menu.js";
|
|
22
23
|
import * as insertionHelper from "../editor/insertion-helper.js";
|
|
23
24
|
import { defaultDef } from "../panels/shared.js";
|
|
25
|
+
import { bubbleInlinePath, findCanvasElement, effectiveZoom } from "../canvas/canvas-helpers.js";
|
|
24
26
|
|
|
25
27
|
/** @type {any} */
|
|
26
28
|
let _ctx = null;
|
|
@@ -32,11 +34,8 @@ let _ctx = null;
|
|
|
32
34
|
* getState: () => any;
|
|
33
35
|
* setState: (s: any) => void;
|
|
34
36
|
* getCanvasMode: () => string;
|
|
35
|
-
* bubbleInlinePath: (doc: any, path: any) => any;
|
|
36
|
-
* findCanvasElement: (path: any, canvasEl: any) => any;
|
|
37
37
|
* enterInlineEdit: (el: any, path: any) => void;
|
|
38
38
|
* navigateToComponent: (path: any) => void;
|
|
39
|
-
* effectiveZoom: () => number;
|
|
40
39
|
* }} ctx
|
|
41
40
|
*/
|
|
42
41
|
export function initPanelEvents(ctx) {
|
|
@@ -90,12 +89,11 @@ export function registerPanelEvents(panel) {
|
|
|
90
89
|
if (canvas.contains(el) && el !== canvas) {
|
|
91
90
|
const originalPath = elToPath.get(el);
|
|
92
91
|
if (originalPath) {
|
|
93
|
-
let path =
|
|
92
|
+
let path = bubbleInlinePath(S.document, originalPath);
|
|
94
93
|
const newMedia = mediaName === "base" ? null : (mediaName ?? null);
|
|
95
94
|
const withMedia = { ...S, ui: { ...S.ui, activeMedia: newMedia } };
|
|
96
95
|
|
|
97
|
-
const resolvedEl =
|
|
98
|
-
path === originalPath ? el : _ctx.findCanvasElement(path, canvas) || el;
|
|
96
|
+
const resolvedEl = path === originalPath ? el : findCanvasElement(path, canvas) || el;
|
|
99
97
|
|
|
100
98
|
if (
|
|
101
99
|
pathsEqual(path, S.selection) &&
|
|
@@ -108,7 +106,7 @@ export function registerPanelEvents(panel) {
|
|
|
108
106
|
}
|
|
109
107
|
|
|
110
108
|
if (canvasMode === "design" && S.mode !== "content") {
|
|
111
|
-
|
|
109
|
+
updateUi("pendingInlineEdit", { path, mediaName });
|
|
112
110
|
update(selectNode(withMedia, path));
|
|
113
111
|
return;
|
|
114
112
|
}
|
|
@@ -149,9 +147,8 @@ export function registerPanelEvents(panel) {
|
|
|
149
147
|
if (canvas.contains(el) && el !== canvas) {
|
|
150
148
|
const originalPath = elToPath.get(el);
|
|
151
149
|
if (originalPath) {
|
|
152
|
-
const path =
|
|
153
|
-
const resolvedEl =
|
|
154
|
-
path === originalPath ? el : _ctx.findCanvasElement(path, canvas) || el;
|
|
150
|
+
const path = bubbleInlinePath(S.document, originalPath);
|
|
151
|
+
const resolvedEl = path === originalPath ? el : findCanvasElement(path, canvas) || el;
|
|
155
152
|
if (isEditableBlock(resolvedEl)) {
|
|
156
153
|
const newMedia = mediaName === "base" ? null : (mediaName ?? null);
|
|
157
154
|
const withMedia = { ...S, ui: { ...S.ui, activeMedia: newMedia } };
|
|
@@ -188,7 +185,7 @@ export function registerPanelEvents(panel) {
|
|
|
188
185
|
if (canvas.contains(el) && el !== canvas) {
|
|
189
186
|
let path = elToPath.get(el);
|
|
190
187
|
if (path) {
|
|
191
|
-
path =
|
|
188
|
+
path = bubbleInlinePath(S.document, path);
|
|
192
189
|
showContextMenu(e, path, S, { onEditComponent: _ctx.navigateToComponent });
|
|
193
190
|
return;
|
|
194
191
|
}
|
|
@@ -218,7 +215,7 @@ export function registerPanelEvents(panel) {
|
|
|
218
215
|
if (el && canvas.contains(el) && el !== canvas) {
|
|
219
216
|
let path = elToPath.get(el);
|
|
220
217
|
if (path) {
|
|
221
|
-
path =
|
|
218
|
+
path = bubbleInlinePath(S.document, path);
|
|
222
219
|
if (!pathsEqual(path, S.hover)) {
|
|
223
220
|
_ctx.setState(hoverNode(S, path));
|
|
224
221
|
renderOnly("overlays");
|
|
@@ -249,7 +246,7 @@ export function registerPanelEvents(panel) {
|
|
|
249
246
|
update,
|
|
250
247
|
getCanvasMode: _ctx.getCanvasMode,
|
|
251
248
|
withPanelPointerEvents,
|
|
252
|
-
effectiveZoom:
|
|
249
|
+
effectiveZoom: effectiveZoom,
|
|
253
250
|
defaultDef,
|
|
254
251
|
insertNode,
|
|
255
252
|
selectNode,
|
|
@@ -5,18 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
import { getState, getNodeAtPath } from "../store.js";
|
|
7
7
|
import { view } from "../view.js";
|
|
8
|
+
import { getActivePanel, findCanvasElement } from "../canvas/canvas-helpers.js";
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Initialize the pseudo-preview module.
|
|
14
|
-
*
|
|
15
|
-
* @param {{ getActivePanel: Function; findCanvasElement: Function }} ctx
|
|
16
|
-
*/
|
|
17
|
-
export function initPseudoPreview(ctx) {
|
|
18
|
-
_ctx = ctx;
|
|
19
|
-
}
|
|
10
|
+
const pseudoStyleHost = document.createElement("div");
|
|
11
|
+
pseudoStyleHost.style.display = "contents";
|
|
12
|
+
(document.querySelector("sp-theme") || document.body).appendChild(pseudoStyleHost);
|
|
20
13
|
|
|
21
14
|
export function updateForcedPseudoPreview() {
|
|
22
15
|
if (view.forcedStyleTag) {
|
|
@@ -32,9 +25,9 @@ export function updateForcedPseudoPreview() {
|
|
|
32
25
|
const sel = S.ui?.activeSelector;
|
|
33
26
|
if (!sel || !sel.startsWith(":") || !S.selection) return;
|
|
34
27
|
|
|
35
|
-
const panel =
|
|
28
|
+
const panel = getActivePanel();
|
|
36
29
|
if (!panel) return;
|
|
37
|
-
const el =
|
|
30
|
+
const el = findCanvasElement(S.selection, panel.canvas);
|
|
38
31
|
if (!el) return;
|
|
39
32
|
|
|
40
33
|
const node = getNodeAtPath(S.document, S.selection);
|
|
@@ -59,6 +52,6 @@ export function updateForcedPseudoPreview() {
|
|
|
59
52
|
|
|
60
53
|
const tag = document.createElement("style");
|
|
61
54
|
tag.textContent = `[data-studio-forced] { ${cssProps} }`;
|
|
62
|
-
|
|
55
|
+
pseudoStyleHost.appendChild(tag);
|
|
63
56
|
view.forcedStyleTag = tag;
|
|
64
57
|
}
|
package/src/panels/shared.js
CHANGED
|
@@ -31,7 +31,7 @@ export function ensureLitState(container) {
|
|
|
31
31
|
const start = part._$startNode;
|
|
32
32
|
const end = part._$endNode;
|
|
33
33
|
const startBad = start && start.parentNode !== container;
|
|
34
|
-
const endBad = end && end !== container && end.parentNode !== container;
|
|
34
|
+
const endBad = (end && end !== container && end.parentNode !== container) || (!end && start);
|
|
35
35
|
if (startBad || endBad) {
|
|
36
36
|
console.warn("ensureLitState: clearing corrupted Lit state on", container.id || container);
|
|
37
37
|
container.textContent = "";
|
package/src/platform.js
CHANGED
|
@@ -5,27 +5,30 @@
|
|
|
5
5
|
* platform adapter at startup. All file I/O, project loading, and component discovery goes through
|
|
6
6
|
* this interface.
|
|
7
7
|
*
|
|
8
|
+
* Uses window.__jxPlatform so the platform can be registered from a separate script bundle (e.g.
|
|
9
|
+
* init.js) before studio.js loads.
|
|
10
|
+
*
|
|
8
11
|
* See spec/desktop.md §3 for the full StudioPlatform interface.
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
14
|
/** @typedef {Record<string, any>} StudioPlatform */
|
|
12
15
|
|
|
13
|
-
/** @type {
|
|
14
|
-
|
|
16
|
+
/** @type {any} */
|
|
17
|
+
const g = globalThis;
|
|
15
18
|
|
|
16
19
|
/** @param {StudioPlatform} platform */
|
|
17
20
|
export function registerPlatform(platform) {
|
|
18
|
-
|
|
21
|
+
g.__jxPlatform = platform;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
/** @returns {StudioPlatform} */
|
|
22
25
|
export function getPlatform() {
|
|
23
|
-
if (!
|
|
26
|
+
if (!g.__jxPlatform)
|
|
24
27
|
throw new Error("No platform registered. Call registerPlatform() before starting Studio.");
|
|
25
|
-
return
|
|
28
|
+
return g.__jxPlatform;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
/** @returns {boolean} */
|
|
29
32
|
export function hasPlatform() {
|
|
30
|
-
return
|
|
33
|
+
return g.__jxPlatform != null;
|
|
31
34
|
}
|
package/src/state.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
* mode: string;
|
|
30
30
|
* content: { frontmatter: Record<string, any> };
|
|
31
31
|
* ui: Record<string, any>;
|
|
32
|
+
* canvas: { status: string; scope: any; error: string | null };
|
|
32
33
|
* }} StudioState
|
|
33
34
|
*/
|
|
34
35
|
|
|
@@ -234,6 +235,12 @@ export function createState(doc) {
|
|
|
234
235
|
gitCommitMessage: "", // commit message input
|
|
235
236
|
gitLoading: false, // loading indicator during async ops
|
|
236
237
|
gitError: null, // error message string
|
|
238
|
+
pendingInlineEdit: null, // null | { path, mediaName } — deferred inline edit awaiting canvas readiness
|
|
239
|
+
},
|
|
240
|
+
canvas: {
|
|
241
|
+
status: "idle", // "idle" | "loading" | "ready" | "error"
|
|
242
|
+
scope: null, // $defs scope from runtime buildScope
|
|
243
|
+
error: null, // error message on failure
|
|
237
244
|
},
|
|
238
245
|
};
|
|
239
246
|
}
|
|
@@ -272,6 +279,7 @@ export function fromFlat(S) {
|
|
|
272
279
|
selection,
|
|
273
280
|
hover,
|
|
274
281
|
ui,
|
|
282
|
+
canvas,
|
|
275
283
|
} = S;
|
|
276
284
|
return {
|
|
277
285
|
doc: {
|
|
@@ -286,7 +294,7 @@ export function fromFlat(S) {
|
|
|
286
294
|
history,
|
|
287
295
|
historyIndex,
|
|
288
296
|
},
|
|
289
|
-
session: { selection, hover, ui },
|
|
297
|
+
session: { selection, hover, ui, canvas },
|
|
290
298
|
};
|
|
291
299
|
}
|
|
292
300
|
|
package/src/store.js
CHANGED
|
@@ -324,6 +324,15 @@ export function updateUi(field, value) {
|
|
|
324
324
|
_updateSessionFn({ ui: { [field]: value } });
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Update the canvas async state (status, scope, error).
|
|
329
|
+
*
|
|
330
|
+
* @param {any} patch
|
|
331
|
+
*/
|
|
332
|
+
export function updateCanvas(patch) {
|
|
333
|
+
_updateSessionFn({ canvas: patch });
|
|
334
|
+
}
|
|
335
|
+
|
|
327
336
|
// ─── Subscription system ────────────────────────────────────────────────────
|
|
328
337
|
// Panels subscribe to state changes and decide when to re-render, rather than
|
|
329
338
|
// being called unconditionally from _update/_updateSession.
|