@jxsuite/studio 0.9.0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/studio.js +117584 -117551
- package/dist/studio.js.map +103 -100
- package/package.json +29 -29
- package/src/browse/browse.js +21 -19
- package/src/canvas/canvas-helpers.js +121 -0
- package/src/canvas/canvas-live-render.js +292 -0
- package/src/canvas/canvas-render.js +431 -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/files/files.js +2 -0
- package/src/panels/block-action-bar.js +8 -4
- package/src/panels/canvas-dnd.js +2 -13
- package/src/panels/imports-panel.js +2 -2
- 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/properties-panel.js +3 -3
- package/src/panels/pseudo-preview.js +7 -14
- package/src/panels/shared.js +1 -1
- package/src/panels/stylebook-panel.js +27 -20
- package/src/platform.js +9 -6
- package/src/settings/{collections-editor.js → content-types-editor.js} +48 -48
- package/src/settings/schema-field-ui.js +1 -1
- package/src/state.js +10 -2
- package/src/store.js +9 -0
- package/src/studio.js +67 -811
- package/src/utils/studio-utils.js +5 -5
- package/src/view.js +0 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Content Types Editor — visual schema builder for project content types.
|
|
3
3
|
*
|
|
4
|
-
* Renders inside the Settings view "
|
|
5
|
-
*
|
|
4
|
+
* Renders inside the Settings view "Content Types" tab. Two-column layout: left column lists
|
|
5
|
+
* content type names, right column edits the selected content type's schema.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { html, render as litRender } from "lit-html";
|
|
@@ -13,11 +13,11 @@ import { fieldCardTpl, addFieldFormTpl, schemaForType } from "./schema-field-ui.
|
|
|
13
13
|
// ─── Module state ─────────────────────────────────────────────────────────────
|
|
14
14
|
|
|
15
15
|
/** @type {string | null} */
|
|
16
|
-
let
|
|
16
|
+
let selectedContentType = null;
|
|
17
17
|
let showAddField = false;
|
|
18
18
|
let newFieldState = { name: "", type: "string", required: false };
|
|
19
|
-
let
|
|
20
|
-
let
|
|
19
|
+
let showNewContentType = false;
|
|
20
|
+
let newContentTypeName = "";
|
|
21
21
|
|
|
22
22
|
// ─── Persistence ──────────────────────────────────────────────────────────────
|
|
23
23
|
|
|
@@ -29,17 +29,17 @@ async function saveProjectConfig() {
|
|
|
29
29
|
|
|
30
30
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
31
31
|
|
|
32
|
-
/** Get the schema object for the selected
|
|
32
|
+
/** Get the schema object for the selected content type. */
|
|
33
33
|
function getSelectedSchema() {
|
|
34
34
|
const config = projectState?.projectConfig;
|
|
35
|
-
return config?.
|
|
35
|
+
return config?.contentTypes?.[/** @type {string} */ (selectedContentType)]?.schema;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
// ─── Handlers ─────────────────────────────────────────────────────────────────
|
|
39
39
|
|
|
40
40
|
/** @param {() => void} rerender */
|
|
41
|
-
function
|
|
42
|
-
const slug =
|
|
41
|
+
function handleNewContentType(rerender) {
|
|
42
|
+
const slug = newContentTypeName
|
|
43
43
|
.toLowerCase()
|
|
44
44
|
.replace(/\s+/g, "-")
|
|
45
45
|
.replace(/[^a-z0-9-]/g, "");
|
|
@@ -47,30 +47,30 @@ function handleNewCollection(rerender) {
|
|
|
47
47
|
|
|
48
48
|
const config = projectState?.projectConfig;
|
|
49
49
|
if (!config) return;
|
|
50
|
-
if (!config.
|
|
51
|
-
if (config.
|
|
50
|
+
if (!config.contentTypes) config.contentTypes = {};
|
|
51
|
+
if (config.contentTypes[slug]) return; // already exists
|
|
52
52
|
|
|
53
|
-
config.
|
|
54
|
-
source:
|
|
53
|
+
config.contentTypes[slug] = {
|
|
54
|
+
source: `./content/${slug}/**/*.md`,
|
|
55
55
|
schema: { type: "object", properties: {}, required: [] },
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
selectedContentType = slug;
|
|
59
|
+
showNewContentType = false;
|
|
60
|
+
newContentTypeName = "";
|
|
61
61
|
rerender();
|
|
62
62
|
|
|
63
63
|
// Persist in background
|
|
64
64
|
saveProjectConfig().then(async () => {
|
|
65
65
|
const platform = getPlatform();
|
|
66
|
-
await platform.writeFile(
|
|
66
|
+
await platform.writeFile(`content/${slug}/.gitkeep`, "");
|
|
67
67
|
});
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/** @param {() => void} rerender */
|
|
71
71
|
function handleAddField(rerender) {
|
|
72
72
|
const name = newFieldState.name.trim();
|
|
73
|
-
if (!name || !
|
|
73
|
+
if (!name || !selectedContentType) return;
|
|
74
74
|
|
|
75
75
|
const schema = getSelectedSchema();
|
|
76
76
|
if (!schema) return;
|
|
@@ -270,13 +270,13 @@ function handleChangeNestedType(parentName, childName, newType, rerender) {
|
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
/** @param {() => void} rerender */
|
|
273
|
-
function
|
|
274
|
-
if (!
|
|
273
|
+
function handleDeleteContentType(rerender) {
|
|
274
|
+
if (!selectedContentType) return;
|
|
275
275
|
const config = projectState?.projectConfig;
|
|
276
|
-
if (!config?.
|
|
276
|
+
if (!config?.contentTypes?.[selectedContentType]) return;
|
|
277
277
|
|
|
278
|
-
delete config.
|
|
279
|
-
|
|
278
|
+
delete config.contentTypes[selectedContentType];
|
|
279
|
+
selectedContentType = null;
|
|
280
280
|
|
|
281
281
|
rerender();
|
|
282
282
|
saveProjectConfig();
|
|
@@ -285,26 +285,26 @@ function handleDeleteCollection(rerender) {
|
|
|
285
285
|
// ─── Render ───────────────────────────────────────────────────────────────────
|
|
286
286
|
|
|
287
287
|
/**
|
|
288
|
-
* Render the
|
|
288
|
+
* Render the content types editor.
|
|
289
289
|
*
|
|
290
290
|
* @param {HTMLElement} container
|
|
291
291
|
*/
|
|
292
|
-
export function
|
|
293
|
-
const rerender = () =>
|
|
292
|
+
export function renderContentTypesEditor(container) {
|
|
293
|
+
const rerender = () => renderContentTypesEditor(container);
|
|
294
294
|
const config = projectState?.projectConfig;
|
|
295
|
-
const
|
|
296
|
-
const
|
|
295
|
+
const contentTypes = config?.contentTypes || {};
|
|
296
|
+
const contentTypeNames = Object.keys(contentTypes);
|
|
297
297
|
|
|
298
|
-
// Left column —
|
|
298
|
+
// Left column — content type list
|
|
299
299
|
const listTpl = html`
|
|
300
300
|
<div class="settings-list-panel">
|
|
301
|
-
${
|
|
301
|
+
${contentTypeNames.map(
|
|
302
302
|
(name) => html`
|
|
303
303
|
<sp-action-button
|
|
304
304
|
size="s"
|
|
305
|
-
?selected=${
|
|
305
|
+
?selected=${selectedContentType === name}
|
|
306
306
|
@click=${() => {
|
|
307
|
-
|
|
307
|
+
selectedContentType = name;
|
|
308
308
|
showAddField = false;
|
|
309
309
|
rerender();
|
|
310
310
|
}}
|
|
@@ -313,25 +313,25 @@ export function renderCollectionsEditor(container) {
|
|
|
313
313
|
</sp-action-button>
|
|
314
314
|
`,
|
|
315
315
|
)}
|
|
316
|
-
${
|
|
316
|
+
${showNewContentType
|
|
317
317
|
? html`
|
|
318
318
|
<div class="settings-inline-form">
|
|
319
319
|
<sp-textfield
|
|
320
320
|
size="s"
|
|
321
|
-
placeholder="
|
|
322
|
-
.value=${
|
|
321
|
+
placeholder="content-type-name"
|
|
322
|
+
.value=${newContentTypeName}
|
|
323
323
|
@input=${(/** @type {any} */ e) => {
|
|
324
|
-
|
|
324
|
+
newContentTypeName = e.target.value;
|
|
325
325
|
}}
|
|
326
326
|
@keydown=${(/** @type {any} */ e) => {
|
|
327
|
-
if (e.key === "Enter")
|
|
327
|
+
if (e.key === "Enter") handleNewContentType(rerender);
|
|
328
328
|
if (e.key === "Escape") {
|
|
329
|
-
|
|
329
|
+
showNewContentType = false;
|
|
330
330
|
rerender();
|
|
331
331
|
}
|
|
332
332
|
}}
|
|
333
333
|
></sp-textfield>
|
|
334
|
-
<sp-action-button size="s" @click=${() =>
|
|
334
|
+
<sp-action-button size="s" @click=${() => handleNewContentType(rerender)}>
|
|
335
335
|
Create
|
|
336
336
|
</sp-action-button>
|
|
337
337
|
</div>
|
|
@@ -341,11 +341,11 @@ export function renderCollectionsEditor(container) {
|
|
|
341
341
|
size="s"
|
|
342
342
|
quiet
|
|
343
343
|
@click=${() => {
|
|
344
|
-
|
|
344
|
+
showNewContentType = true;
|
|
345
345
|
rerender();
|
|
346
346
|
}}
|
|
347
347
|
>
|
|
348
|
-
<sp-icon-add slot="icon"></sp-icon-add> New
|
|
348
|
+
<sp-icon-add slot="icon"></sp-icon-add> New Content Type
|
|
349
349
|
</sp-action-button>
|
|
350
350
|
`}
|
|
351
351
|
</div>
|
|
@@ -353,10 +353,10 @@ export function renderCollectionsEditor(container) {
|
|
|
353
353
|
|
|
354
354
|
// Right column — schema editor
|
|
355
355
|
let editorTpl;
|
|
356
|
-
if (!
|
|
357
|
-
editorTpl = html`<div class="settings-empty-state">Select or create a
|
|
356
|
+
if (!selectedContentType || !contentTypes[selectedContentType]) {
|
|
357
|
+
editorTpl = html`<div class="settings-empty-state">Select or create a content type</div>`;
|
|
358
358
|
} else {
|
|
359
|
-
const col =
|
|
359
|
+
const col = contentTypes[selectedContentType];
|
|
360
360
|
const schema = col.schema || {};
|
|
361
361
|
const properties = schema.properties || {};
|
|
362
362
|
const required = schema.required || [];
|
|
@@ -381,13 +381,13 @@ export function renderCollectionsEditor(container) {
|
|
|
381
381
|
editorTpl = html`
|
|
382
382
|
<div class="settings-editor-panel">
|
|
383
383
|
<div class="settings-editor-header">
|
|
384
|
-
<h3>${
|
|
384
|
+
<h3>${selectedContentType}</h3>
|
|
385
385
|
<sp-field-label size="s">Source: ${col.source || "—"}</sp-field-label>
|
|
386
386
|
<sp-action-button
|
|
387
387
|
size="xs"
|
|
388
388
|
quiet
|
|
389
|
-
title="Delete
|
|
390
|
-
@click=${() =>
|
|
389
|
+
title="Delete content type"
|
|
390
|
+
@click=${() => handleDeleteContentType(rerender)}
|
|
391
391
|
>
|
|
392
392
|
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
393
393
|
</sp-action-button>
|
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
|
|
|
@@ -228,12 +229,18 @@ export function createState(doc) {
|
|
|
228
229
|
stylebookTab: "elements", // "elements" | "variables"
|
|
229
230
|
stylebookFilter: "", // search filter text
|
|
230
231
|
stylebookCustomizedOnly: false, // show only customized elements
|
|
231
|
-
settingsTab: "stylebook", // "stylebook" | "definitions" | "
|
|
232
|
+
settingsTab: "stylebook", // "stylebook" | "definitions" | "contentTypes"
|
|
232
233
|
gitStatus: null, // { branch, ahead, behind, files: [] }
|
|
233
234
|
gitBranches: null, // { current, branches: [] }
|
|
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.
|