windcraft-editor 0.0.0-stage → 0.1.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.
Files changed (60) hide show
  1. package/README.md +49 -2
  2. package/core/board-layout.js +71 -0
  3. package/core/class-groups.js +210 -0
  4. package/core/design-spec.js +303 -0
  5. package/core/exporters.js +156 -0
  6. package/core/pages.js +39 -0
  7. package/core/prop-tailwind.js +158 -0
  8. package/core/split-sections.js +99 -0
  9. package/core/theme.js +87 -0
  10. package/design/agent.js +2118 -0
  11. package/design/classes-section.js +103 -0
  12. package/design/code-editor.js +293 -0
  13. package/design/code-view.js +158 -0
  14. package/design/color-picker.js +107 -0
  15. package/design/config.js +5 -0
  16. package/design/controls.js +132 -0
  17. package/design/css-props.js +111 -0
  18. package/design/design-tab.js +120 -0
  19. package/design/device-frame.js +40 -0
  20. package/design/editor/add-menu.js +151 -0
  21. package/design/editor/api.js +47 -0
  22. package/design/editor/base.js +55 -0
  23. package/design/editor/code-panel.js +206 -0
  24. package/design/editor/color-ui.js +135 -0
  25. package/design/editor/designs.js +606 -0
  26. package/design/editor/docs.js +121 -0
  27. package/design/editor/frame-handles.js +223 -0
  28. package/design/editor/frame-messages.js +195 -0
  29. package/design/editor/inspector.js +461 -0
  30. package/design/editor/layers.js +61 -0
  31. package/design/editor/pages-panel.js +161 -0
  32. package/design/editor/panel-resize.js +47 -0
  33. package/design/editor/plane.js +132 -0
  34. package/design/editor/present.js +52 -0
  35. package/design/editor/toolbar.js +267 -0
  36. package/design/editor.js +47 -0
  37. package/design/figma-sections.js +440 -0
  38. package/design/float-toolbar.js +163 -0
  39. package/design/group-controls.js +52 -0
  40. package/design/host.js +22 -0
  41. package/design/panel-model.js +130 -0
  42. package/design/panel-state.js +40 -0
  43. package/design/panel.js +212 -0
  44. package/design/presenter.js +312 -0
  45. package/design/prop-sections.js +18 -0
  46. package/design/selection-overlay.js +93 -0
  47. package/design/store.js +29 -0
  48. package/design/style-sections.js +315 -0
  49. package/design/tailwind-classes.js +65 -0
  50. package/embed-frame.js +43 -0
  51. package/embed.html +15 -0
  52. package/embed.js +60 -0
  53. package/index.js +62 -0
  54. package/package.json +50 -5
  55. package/playground/component-source.js +124 -0
  56. package/styles.css +402 -0
  57. package/template.js +247 -0
  58. package/vendor/codemirror.js +24 -0
  59. package/vendor/preact-htm.js +1 -0
  60. package/vendor/prettier.js +171 -0
@@ -0,0 +1,267 @@
1
+ import { host } from "../host.js";
2
+ import { config } from "../config.js";
3
+ import { $, S, data, send } from "./base.js";
4
+ import { assembleDoc, loadFrame, step, updateChrome } from "./docs.js";
5
+ import { pref } from "./color-ui.js";
6
+ import { applyView, canvas, canvasSize, fitTo, handleWheel, layout, view, zoomAt } from "./plane.js";
7
+ import { activate, anyDirty, boardPayload, docDirty, docs, loadActiveFromStash, pinBoard, remapKey, saveBoard } from "./designs.js";
8
+ import { openDetails } from "./add-menu.js";
9
+ import { closePreview, openPreview, previewOpen } from "./present.js";
10
+
11
+ // ── Toolbar ──────────────────────────────────────────────────────────────────
12
+ /**
13
+ * Editing, or the live page ("preview"): the Interact tool (P) shows the page as it runs on a device, at the
14
+ * device's width and height, with its hover, focus, clicks, typing, scrolling and JavaScript; any other tool, V or Esc edits again.
15
+ */
16
+ function setMode(next) {
17
+ if (next === S.mode) return;
18
+ S.mode = next;
19
+ $("inspector").style.opacity = $("layers").style.opacity = S.mode === "edit" ? "" : "0.4";
20
+ $("inspector").style.pointerEvents = $("layers").style.pointerEvents = S.mode === "edit" ? "" : "none";
21
+ $("canvasHint").textContent = S.mode === "edit" ? "Click to select. Double-click text to edit it. Alt-drag to place freely. Scroll to pan, Ctrl/Cmd + scroll to zoom." : "Interact: hover, click, type and scroll as on the device. Press Esc or V to edit again.";
22
+ updateShield();
23
+ loadFrame();
24
+ layout(); // the frame takes the device's height while it is live, and its content's height again when editing
25
+ }
26
+
27
+ export const DEFAULT_CATEGORY = "apps-general";
28
+
29
+ /** Writes one design: creates it the first time, a new version after that. */
30
+ async function persist(d) {
31
+ const m = d.meta;
32
+ const body = { htmlCode: assembleDoc(d.parsed, d.markup) };
33
+ if (d.isNew) {
34
+ const saved = await host.createComponent({
35
+ ...body, title: m.title || "Untitled page", category: m.category || DEFAULT_CATEGORY, description: m.description || "A page on the canvas.",
36
+ tags: (m.tags || []).join(","), isPublished: String(m.isPublished), useAsReference: m.useAsReference !== false, project: m.project ? m.project.id : "",
37
+ ...(S.draftKey && d.key === S.draftKey ? { draftId: data.draftId } : {}), // a draft from Figma: its Figma data moves to this page
38
+ });
39
+ m.id = saved._id;
40
+ m.version = saved.version || 1;
41
+ d.isNew = false;
42
+ const old = d.key;
43
+ remapKey(old, m.id);
44
+ if (S.draftKey && old === S.draftKey) { S.draftKey = null; if (saved.figmaSource) setupFigmaButton(saved.figmaSource, m.id); } // after the key moved: the button shows for the design being edited
45
+ if (m.project) pinBoard(m.id, false);
46
+ if (d.board && m.project) saveBoard([boardPayload([m.id, d.board])]);
47
+ if (m.id === S.curKey) host.showDesign(m.id);
48
+ } else {
49
+ const saved = await host.updateComponent(m.id, { ...body, note: "Edited in design canvas" });
50
+ m.version = saved.version;
51
+ }
52
+ d.savedMarkup = d.markup;
53
+ }
54
+
55
+ export async function save() {
56
+ if (!anyDirty()) return;
57
+ const dirty = [...docs.values()].filter(docDirty);
58
+ const needsDetails = dirty.find((d) => d.isNew && !d.meta.category);
59
+ $("saveBtn").disabled = true;
60
+ $("saveState").textContent = "Saving...";
61
+ try {
62
+ for (const d of dirty) if (d !== needsDetails) await persist(d);
63
+ } catch (err) {
64
+ loadActiveFromStash();
65
+ updateChrome();
66
+ $("saveState").textContent = "Save failed";
67
+ return alert(err.message || "Could not save");
68
+ }
69
+ loadActiveFromStash();
70
+ if (needsDetails) { if (needsDetails.key !== S.curKey) activate(needsDetails.key); updateChrome(); return openDetails(); }
71
+ updateChrome();
72
+ $("saveState").textContent = dirty.length > 1 ? `Saved ${dirty.length} designs` : `Saved as v${data.version}`;
73
+ }
74
+
75
+ // The save button carries the save status itself ("Save", "Saving…", "Saved", "Retry"); the full message is its tooltip.
76
+ const SAVE_LABELS = [[/^saving/i, "Saving…"], [/failed/i, "Retry"], [/^saved/i, "Saved"]];
77
+ function syncSaveButton() {
78
+ const text = $("saveState").textContent.trim();
79
+ const hit = SAVE_LABELS.find(([re]) => re.test(text));
80
+ $("saveBtn").textContent = hit ? hit[1] : "Save";
81
+ $("saveBtn").title = text || "Save (Ctrl/Cmd+S)";
82
+ }
83
+
84
+ // ── Keyboard ─────────────────────────────────────────────────────────────────
85
+ const TOOL_KEYS = { v: "move", h: "hand", p: "interact", f: "frame", r: "rect", o: "ellipse", l: "line", t: "text", i: "image", b: "button" };
86
+
87
+ export function handleKey({ key, meta, shift, code }) {
88
+ if (key === "escape" && S.mode === "preview") return setTool("move"); // Esc in the live page (it passes Esc on) or in the editor
89
+ if (meta && key === "z") return step(shift ? 1 : -1);
90
+ if (meta && key === "y") return step(1);
91
+ if (meta && key === "s") return save();
92
+ if (key === "!") { S.viewTouched = true; return fitTo([...docs.keys()]); }
93
+ if (key === "@") { S.viewTouched = true; return fitTo([S.curKey]); }
94
+ if (!meta && key === "l" && shift) return setTool("arrow");
95
+ if (!meta && TOOL_KEYS[key]) return setTool(TOOL_KEYS[key]);
96
+ if (!S.selected) return;
97
+ // layer order (Ctrl/Cmd+] / [ one step, with Shift all the way), lock and hide, as in Figma
98
+ if (meta && (code === "BracketRight" || code === "BracketLeft")) return send({ cmd: "order", mode: code === "BracketRight" ? (shift ? "front" : "forward") : (shift ? "back" : "backward") });
99
+ if (meta && shift && key === "l") return send({ cmd: "lockSel" });
100
+ if (meta && shift && key === "h") return send({ cmd: "hideSel" });
101
+ if (meta && key === "d") return send({ cmd: "duplicateSel" });
102
+ if (meta && key === "g") return send({ cmd: shift ? "ungroup" : "group" });
103
+ if (!meta && shift && key === "a") return send({ cmd: "autoLayout" });
104
+ if (!meta && shift && key === "g") return send({ cmd: "gridLayout" });
105
+ if (S.selected.tag === "body") return;
106
+ if (key === "delete" || key === "backspace") return send({ cmd: "deleteSel" });
107
+ if (meta && key === "c") return send({ cmd: "copySel" });
108
+ if (meta && key === "x") return send({ cmd: "cutSel" });
109
+ if (meta && key === "v") return send({ cmd: "pasteSel" });
110
+ if (key.startsWith("arrow")) { const s = shift ? 10 : 1; return send({ cmd: "nudgeSel", dx: key === "arrowleft" ? -s : key === "arrowright" ? s : 0, dy: key === "arrowup" ? -s : key === "arrowdown" ? s : 0 }); }
111
+ }
112
+ /** Ctrl/Cmd + = / - zoom the plane around the middle of the canvas, Ctrl/Cmd + 0 back to 100% (the browser's own zoom is off). */
113
+ export function keyZoom(key) {
114
+ const factor = key === "=" || key === "+" ? 1.25 : key === "-" || key === "_" ? 0.8 : key === "0" ? 1 / view.z : 0;
115
+ if (!factor) return false;
116
+ const [W, H] = canvasSize();
117
+ S.viewTouched = true;
118
+ zoomAt(factor, W / 2, H / 2);
119
+ return true;
120
+ }
121
+
122
+ // ── Design tools: palette, panning, zoom ─────────────────────────────────────
123
+ const ICONS = {
124
+ move: '<path d="M5 3l14 8-6 2-2 6z"/>',
125
+ hand: '<path stroke-linecap="round" stroke-linejoin="round" d="M10 4v8m0-8a1.5 1.5 0 013 0v7m0-5.5a1.5 1.5 0 013 0V13m0-4.5a1.5 1.5 0 013 0V15a6 6 0 01-6 6h-1a6 6 0 01-5-2.7L5 14a1.5 1.5 0 012.5-1.7L10 15"/>',
126
+ interact: '<path stroke-linecap="round" stroke-linejoin="round" d="M9 9l11 4.5-4.6 1.4L14 19.5z"/><path stroke-linecap="round" d="M6.5 3.5 7 6M3.5 6.5 6 7M4.5 10.5l2-1.5M10.5 4.5 9 6.5"/>',
127
+ frame: '<path stroke-linecap="round" stroke-linejoin="round" d="M7 3v18M17 3v18M3 7h18M3 17h18"/>',
128
+ rect: '<rect x="4" y="6" width="16" height="12" rx="1"/>',
129
+ ellipse: '<circle cx="12" cy="12" r="8"/>',
130
+ line: '<path stroke-linecap="round" d="M5 19 19 5"/>',
131
+ arrow: '<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M9 6h9v9"/>',
132
+ text: '<path stroke-linecap="round" stroke-linejoin="round" d="M5 6V4h14v2M12 4v16m-3 0h6"/>',
133
+ image: '<rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="10" r="1.5"/><path stroke-linecap="round" stroke-linejoin="round" d="M21 16l-5-5-8 8"/>',
134
+ button: '<rect x="3" y="8" width="18" height="8" rx="4"/><path stroke-linecap="round" d="M9 12h6"/>',
135
+ };
136
+ const TOOLS = [["move", "Move", "V"], ["hand", "Hand (or hold Space)", "H"], ["interact", "Interact: use the page as on the device (hover, click, type, scroll)", "P"], ["frame", "Frame", "F"], ["shape"], ["text", "Text", "T"]]; // Image and Button live in the + (insert a block) menu; their I and B keys still draw them
137
+ // Rectangle, Oval, Arrow and Line share one button: it draws the shape you used last, the chevron picks another
138
+ const SHAPES = [["rect", "Rectangle", "R"], ["ellipse", "Oval", "O"], ["arrow", "Arrow", "⇧L"], ["line", "Line", "L"]];
139
+ let shape = "rect";
140
+ let spaceDown = false;
141
+
142
+ const toolIcon = (id) => `<svg class="h-[18px] w-[18px]" fill="${id === "move" ? "currentColor" : "none"}" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.6">${ICONS[id]}</svg>`;
143
+ const CHEVRON = '<svg class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>';
144
+ function renderTools() {
145
+ const menu = $("shapeMenu");
146
+ const open = Boolean(menu) && !menu.classList.contains("hidden");
147
+ const sh = SHAPES.find(([id]) => id === shape);
148
+ $("toolbar").innerHTML = TOOLS.filter(([id]) => !config.ui.tools || config.ui.tools.includes(id)).map(([id, label, key]) => {
149
+ const sep = id === "frame" && (!config.ui.tools || config.ui.tools.includes("move")) ? '<span class="mx-1 h-5 w-px bg-neutral-200"></span>' : "";
150
+ if (id === "shape") {
151
+ return `<div class="relative flex items-center"><button type="button" data-tool="${shape}" data-shapebtn title="${sh[1]} (${sh[2]})" class="flex h-8 w-8 items-center justify-center rounded-lg text-neutral-600 hover:bg-neutral-100">${toolIcon(shape)}</button><button type="button" id="shapeMore" title="More shapes" class="flex h-8 w-5 items-center justify-center rounded-lg text-neutral-500 hover:bg-neutral-100">${CHEVRON}</button>
152
+ <div id="shapeMenu" class="pop absolute left-0 top-10 z-50 ${open ? "" : "hidden"} w-48 p-1">${SHAPES.map(([sid, sl, sk], j) => `${j === 2 ? '<div class="my-1 h-px bg-neutral-100"></div>' : ""}<button type="button" data-shape="${sid}" class="flex h-8 w-full items-center gap-2 rounded-lg px-2 text-left text-[12px] hover:bg-neutral-100">${toolIcon(sid)}<span>${sl}</span><span class="ml-auto text-neutral-400">${sk}</span></button>`).join("")}</div></div>`;
153
+ }
154
+ return `${sep}<button type="button" data-tool="${id}" title="${label} (${key})" class="flex h-8 w-8 items-center justify-center rounded-lg text-neutral-600 hover:bg-neutral-100">${toolIcon(id)}</button>`;
155
+ }).join("");
156
+ paintTools();
157
+ }
158
+ function paintTools() {
159
+ $("toolbar").querySelectorAll("[data-tool]").forEach((b) => {
160
+ const on = b.dataset.tool === S.tool;
161
+ b.classList.toggle("bg-[#111]", on);
162
+ b.classList.toggle("text-white", on);
163
+ b.classList.toggle("text-neutral-600", !on);
164
+ });
165
+ }
166
+
167
+ export function setTool(t, fromAgent) {
168
+ S.tool = t;
169
+ canvas.dataset.tool = t; // the canvas shows the pointer of the tool in use (styles.css)
170
+ setMode(t === "interact" ? "preview" : "edit");
171
+ if (SHAPES.some(([id]) => id === t) && shape !== t) { shape = t; renderTools(); } else paintTools();
172
+ updateShield();
173
+ if (!fromAgent) send({ cmd: "tool", tool: t });
174
+ }
175
+ export function setSpace(down) { spaceDown = down; updateShield(); }
176
+ function updateShield() { $("panShield").classList.toggle("hidden", !(S.mode === "edit" && (S.tool === "hand" || spaceDown))); }
177
+
178
+ // the shield catches drags over the canvas so panning uses real pointer coordinates
179
+ let pan = null;
180
+
181
+ /** Registers this part's listeners and does its first draw (called once, in order, by editor.js). */
182
+ /**
183
+ * A design converted from Figma keeps its layer data: it can be converted again with the latest rules (a new version; the old code stays
184
+ * in its history). The button belongs to one design (the page the editor opened with, or the draft once it is saved) and shows while
185
+ * that design is the one being edited.
186
+ */
187
+ let figmaButtonReady = false;
188
+ function setupFigmaButton(info, designId) {
189
+ if (!host.updateFromFigma) return;
190
+ const btn = $("figmaBtn");
191
+ const label = info.outdated ? "Update from Figma · new" : "Update from Figma";
192
+ btn.textContent = label;
193
+ btn.title = info.outdated ? "A newer converter is available: convert this design again from its Figma data" : "Convert this design again from the Figma data kept with it";
194
+ btn.dataset.design = designId;
195
+ const show = () => btn.classList.toggle("hidden", S.curKey !== btn.dataset.design);
196
+ show();
197
+ if (figmaButtonReady) return; // the listeners are set once; a later save only changes which design the button belongs to
198
+ figmaButtonReady = true;
199
+ document.addEventListener("design-switched", show);
200
+ btn.addEventListener("click", async () => {
201
+ if (!confirm("Convert this design again from its Figma data with the latest converter?\n\nThe new conversion replaces the current code, including edits made in Windcraft since it was saved. The current code stays in the version history.")) return;
202
+ btn.disabled = true; btn.textContent = "Updating…";
203
+ try {
204
+ const done = await host.updateFromFigma(btn.dataset.design);
205
+ if (done && done.changed === false) { btn.disabled = false; btn.textContent = label; return alert("Already up to date: converting this design again gives exactly the same code, so no new version was made."); }
206
+ host.openDesign(btn.dataset.design);
207
+ }
208
+ catch (err) { btn.disabled = false; btn.textContent = label; alert((err && err.message) || "Could not update this design."); }
209
+ });
210
+ }
211
+
212
+ export function init() {
213
+ $("playBtn").addEventListener("click", () => (previewOpen() ? closePreview() : openPreview())); // Preview: every page, one at a time, full screen (Interact is its own tool)
214
+ $("zoomSel").addEventListener("change", (e) => {
215
+ const v = e.target.value;
216
+ if (v === "custom") return;
217
+ S.viewTouched = true;
218
+ if (v === "fit") return fitTo([...docs.keys()]);
219
+ if (v === "frame") return fitTo([S.curKey]);
220
+ const [W, H] = canvasSize();
221
+ zoomAt(Number(v) / view.z, W / 2, H / 2);
222
+ });
223
+ $("undoBtn").addEventListener("click", () => step(-1));
224
+ $("redoBtn").addEventListener("click", () => step(1));
225
+ window.addEventListener("resize", layout);
226
+ $("saveBtn").addEventListener("click", save);
227
+ if (data.figmaSource && data.id) setupFigmaButton(data.figmaSource, data.id);
228
+ document.addEventListener("figma-source-ready", (e) => setupFigmaButton(e.detail.info, e.detail.id)); // from the details dialog's first save
229
+ new MutationObserver(syncSaveButton).observe($("saveState"), { childList: true, characterData: true, subtree: true });
230
+ $("detailsBtn").addEventListener("click", () => $("designsMenu").classList.add("hidden"));
231
+ document.addEventListener("keydown", (e) => {
232
+ if ((e.metaKey || e.ctrlKey) && !e.altKey && keyZoom(e.key)) return e.preventDefault();
233
+ if (/^(INPUT|TEXTAREA|SELECT)$/.test(e.target.tagName) || e.target.isContentEditable) return;
234
+ const meta = e.metaKey || e.ctrlKey;
235
+ const key = e.key.toLowerCase();
236
+ if (e.key === "Escape" && S.mode === "preview") { e.preventDefault(); handleKey({ key: "escape" }); return; }
237
+ if (e.code === "Space") { e.preventDefault(); setSpace(true); return; }
238
+ if (e.shiftKey && (e.key === "!" || e.key === "@")) { e.preventDefault(); handleKey({ key: e.key, meta: false, shift: true }); return; }
239
+ if (["delete", "backspace"].includes(key) || (meta && ["z", "y", "d", "s", "g"].includes(key)) || (meta && S.selected && ["c", "x", "v"].includes(key)) || (S.selected && key.startsWith("arrow")) || (meta && (e.code === "BracketLeft" || e.code === "BracketRight" || (e.shiftKey && (key === "l" || key === "h")))) || (!meta && !e.altKey && TOOL_KEYS[key]) || (!meta && e.shiftKey && (key === "a" || key === "g"))) {
240
+ e.preventDefault();
241
+ handleKey({ key, meta, shift: e.shiftKey, code: e.code });
242
+ }
243
+ });
244
+ document.addEventListener("keyup", (e) => { if (e.code === "Space") setSpace(false); });
245
+ $("toolbar").addEventListener("click", (e) => {
246
+ if (e.target.closest("#shapeMore")) { $("shapeMenu").classList.toggle("hidden"); return; }
247
+ const pick = e.target.closest("[data-shape]");
248
+ if (pick) { $("shapeMenu").classList.add("hidden"); setTool(pick.dataset.shape); return; }
249
+ const b = e.target.closest("[data-tool]");
250
+ if (b) setTool(b.dataset.tool);
251
+ });
252
+ document.addEventListener("pointerdown", (e) => { const m = $("shapeMenu"); if (m && !m.classList.contains("hidden") && !e.target.closest("#shapeMenu, #shapeMore")) m.classList.add("hidden"); }, true);
253
+
254
+ renderTools();
255
+ $("panShield").addEventListener("pointerdown", (e) => { S.viewTouched = true; pan = { x: e.clientX, y: e.clientY, vx: view.x, vy: view.y }; $("panShield").setPointerCapture(e.pointerId); $("panShield").style.cursor = "grabbing"; });
256
+ $("panShield").addEventListener("pointermove", (e) => { if (!pan) return; view.x = pan.vx + e.clientX - pan.x; view.y = pan.vy + e.clientY - pan.y; applyView(); });
257
+ $("panShield").addEventListener("pointerup", () => { pan = null; $("panShield").style.cursor = "grab"; });
258
+
259
+ // scroll pans, Ctrl/Cmd + scroll (or a pinch) zooms around the pointer
260
+ canvas.addEventListener("wheel", (e) => {
261
+ e.preventDefault();
262
+ const r = canvas.getBoundingClientRect();
263
+ handleWheel({ dx: e.deltaX, dy: e.deltaY, zoomKey: e.ctrlKey || e.metaKey, cx: e.clientX - r.left, cy: e.clientY - r.top });
264
+ }, { passive: false });
265
+
266
+ window.addEventListener("beforeunload", (e) => { if (!config.leaving && anyDirty()) { e.preventDefault(); e.returnValue = ""; } });
267
+ }
@@ -0,0 +1,47 @@
1
+ // The design editor: builds the page from its parts, in order (each part is in editor/).
2
+ import { config } from "./config.js";
3
+ import { setPreviewTheme } from "../playground/component-source.js";
4
+ import { mountDatalists } from "./panel-model.js";
5
+ import { pref, setLeft, setPanel, setPanelTab } from "./editor/color-ui.js";
6
+ import { setTool } from "./editor/toolbar.js";
7
+ import { buildWraps } from "./editor/designs.js";
8
+ import { loadFrame, updateChrome } from "./editor/docs.js";
9
+ import { renderInspector } from "./editor/inspector.js";
10
+ import { layout } from "./editor/plane.js";
11
+ import { init as init_frame_messages } from "./editor/frame-messages.js";
12
+ import { init as init_pages_panel } from "./editor/pages-panel.js";
13
+ import { init as init_inspector } from "./editor/inspector.js";
14
+ import { init as init_toolbar } from "./editor/toolbar.js";
15
+ import { init as init_code_panel } from "./editor/code-panel.js";
16
+ import { init as init_add_menu } from "./editor/add-menu.js";
17
+ import { init as init_designs } from "./editor/designs.js";
18
+ import { init as init_frame_handles } from "./editor/frame-handles.js";
19
+ import { init as init_color_ui } from "./editor/color-ui.js";
20
+ import { init as init_panel_resize } from "./editor/panel-resize.js";
21
+
22
+ // the project's theme, before the first frame is built
23
+ setPreviewTheme(config.data.theme);
24
+
25
+ init_frame_messages();
26
+ init_pages_panel();
27
+ init_inspector();
28
+ init_toolbar();
29
+ init_code_panel();
30
+ init_add_menu();
31
+ init_designs();
32
+ init_frame_handles();
33
+ init_color_ui();
34
+ init_panel_resize();
35
+
36
+ // ── Go ───────────────────────────────────────────────────────────────────────
37
+ mountDatalists();
38
+ setPanelTab("props");
39
+ const panels = config.ui.panels !== false;
40
+ setPanel(panels && pref("wc:panel") === "1");
41
+ setLeft(panels && pref("wc:left") === "1");
42
+ setTool("move", true); // start on the arrow, highlighted in the toolbar
43
+ buildWraps();
44
+ updateChrome();
45
+ renderInspector();
46
+ layout();
47
+ loadFrame();