@jxsuite/studio 0.0.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.css +3676 -0
- package/dist/studio.js +188743 -0
- package/dist/studio.js.map +1448 -0
- package/package.json +67 -0
- package/src/editor/context-menu.js +144 -0
- package/src/editor/inline-edit.js +597 -0
- package/src/editor/inline-format.js +572 -0
- package/src/editor/shortcuts.js +275 -0
- package/src/editor/slash-menu.js +167 -0
- package/src/files/components.js +40 -0
- package/src/files/file-ops.js +195 -0
- package/src/files/files.js +569 -0
- package/src/markdown/md-allowlist.js +101 -0
- package/src/markdown/md-convert.js +491 -0
- package/src/panels/activity-bar.js +69 -0
- package/src/panels/data-explorer.js +181 -0
- package/src/panels/events-panel.js +235 -0
- package/src/panels/imports-panel.js +427 -0
- package/src/panels/signals-panel.js +1093 -0
- package/src/panels/statusbar.js +56 -0
- package/src/platform.js +31 -0
- package/src/platforms/devserver.js +293 -0
- package/src/services/cem-export.js +130 -0
- package/src/services/code-services.js +98 -0
- package/src/site-context.js +122 -0
- package/src/state.js +744 -0
- package/src/store.js +332 -0
- package/src/studio.js +7692 -0
- package/src/ui/icons.js +83 -0
- package/src/ui/jx-styled-combobox.js +142 -0
- package/src/ui/spectrum.js +238 -0
- package/src/utils/studio-utils.js +185 -0
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Md-convert.js — Bidirectional mdast ↔ Jx conversion
|
|
3
|
+
*
|
|
4
|
+
* MdToJx(mdast) → Jx element tree (for loading into the canvas) jxToMd(jx) → mdast (for saving back
|
|
5
|
+
* to markdown)
|
|
6
|
+
*
|
|
7
|
+
* Both are pure tree transformations. The remark ecosystem handles all actual parsing and
|
|
8
|
+
* serialization.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { MD_ALL } from "./md-allowlist.js";
|
|
12
|
+
|
|
13
|
+
// ─── mdast → Jx ──────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Mdast node-type → Jx tagName mapping
|
|
17
|
+
*
|
|
18
|
+
* @type {Record<string, (n: any) => string>}
|
|
19
|
+
*/
|
|
20
|
+
const MDAST_TAG_MAP = {
|
|
21
|
+
heading: (/** @type {any} */ n) => `h${n.depth}`,
|
|
22
|
+
paragraph: () => "p",
|
|
23
|
+
text: () => "span",
|
|
24
|
+
emphasis: () => "em",
|
|
25
|
+
strong: () => "strong",
|
|
26
|
+
delete: () => "del",
|
|
27
|
+
inlineCode: () => "code",
|
|
28
|
+
link: () => "a",
|
|
29
|
+
image: () => "img",
|
|
30
|
+
blockquote: () => "blockquote",
|
|
31
|
+
list: (/** @type {any} */ n) => (n.ordered ? "ol" : "ul"),
|
|
32
|
+
listItem: () => "li",
|
|
33
|
+
code: () => "pre",
|
|
34
|
+
thematicBreak: () => "hr",
|
|
35
|
+
table: () => "table",
|
|
36
|
+
tableRow: () => "tr",
|
|
37
|
+
tableCell: (/** @type {any} */ n) => (n.isHeader ? "th" : "td"),
|
|
38
|
+
html: () => "div",
|
|
39
|
+
break: () => "br",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Convert an mdast tree to a Jx element tree.
|
|
44
|
+
*
|
|
45
|
+
* @param {any} mdast - Root mdast node (type: 'root')
|
|
46
|
+
* @returns {any} Jx element tree
|
|
47
|
+
*/
|
|
48
|
+
export function mdToJx(mdast) {
|
|
49
|
+
if (mdast.type === "root") {
|
|
50
|
+
return {
|
|
51
|
+
tagName: "div",
|
|
52
|
+
$id: "content",
|
|
53
|
+
children: (mdast.children ?? [])
|
|
54
|
+
.filter((/** @type {any} */ n) => n.type !== "yaml" && n.type !== "toml")
|
|
55
|
+
.map(convertMdastNode)
|
|
56
|
+
.filter(Boolean),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return convertMdastNode(mdast);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {any} node
|
|
64
|
+
* @returns {any}
|
|
65
|
+
*/
|
|
66
|
+
function convertMdastNode(node) {
|
|
67
|
+
if (!node) return null;
|
|
68
|
+
|
|
69
|
+
// Directive nodes → custom elements
|
|
70
|
+
if (
|
|
71
|
+
node.type === "containerDirective" ||
|
|
72
|
+
node.type === "leafDirective" ||
|
|
73
|
+
node.type === "textDirective"
|
|
74
|
+
) {
|
|
75
|
+
return convertDirective(node);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const tagFn = MDAST_TAG_MAP[node.type];
|
|
79
|
+
if (!tagFn) return null;
|
|
80
|
+
|
|
81
|
+
const tag = tagFn(node);
|
|
82
|
+
/** @type {Record<string, any>} */
|
|
83
|
+
const el = { tagName: tag };
|
|
84
|
+
|
|
85
|
+
switch (node.type) {
|
|
86
|
+
case "heading":
|
|
87
|
+
case "paragraph": {
|
|
88
|
+
// If contains only a single text child, flatten to textContent
|
|
89
|
+
if (node.children?.length === 1 && node.children[0].type === "text") {
|
|
90
|
+
el.textContent = node.children[0].value;
|
|
91
|
+
} else if (node.children?.length > 0) {
|
|
92
|
+
el.children = node.children.map(convertMdastNode).filter(Boolean);
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
case "text":
|
|
98
|
+
el.textContent = node.value;
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
case "emphasis":
|
|
102
|
+
case "strong":
|
|
103
|
+
case "delete": {
|
|
104
|
+
if (node.children?.length === 1 && node.children[0].type === "text") {
|
|
105
|
+
el.textContent = node.children[0].value;
|
|
106
|
+
} else if (node.children?.length > 0) {
|
|
107
|
+
el.children = node.children.map(convertMdastNode).filter(Boolean);
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
case "inlineCode":
|
|
113
|
+
el.textContent = node.value;
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
case "link":
|
|
117
|
+
el.attributes = { href: node.url };
|
|
118
|
+
if (node.title) el.attributes.title = node.title;
|
|
119
|
+
if (node.children?.length === 1 && node.children[0].type === "text") {
|
|
120
|
+
el.textContent = node.children[0].value;
|
|
121
|
+
} else if (node.children?.length > 0) {
|
|
122
|
+
el.children = node.children.map(convertMdastNode).filter(Boolean);
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
|
|
126
|
+
case "image":
|
|
127
|
+
el.attributes = { src: node.url, alt: node.alt ?? "" };
|
|
128
|
+
if (node.title) el.attributes.title = node.title;
|
|
129
|
+
break;
|
|
130
|
+
|
|
131
|
+
case "blockquote":
|
|
132
|
+
case "listItem":
|
|
133
|
+
if (node.children?.length > 0) {
|
|
134
|
+
el.children = node.children.map(convertMdastNode).filter(Boolean);
|
|
135
|
+
}
|
|
136
|
+
break;
|
|
137
|
+
|
|
138
|
+
case "list":
|
|
139
|
+
if (node.children?.length > 0) {
|
|
140
|
+
el.children = node.children.map(convertMdastNode).filter(Boolean);
|
|
141
|
+
}
|
|
142
|
+
if (node.start != null && node.start !== 1) {
|
|
143
|
+
el.attributes = { start: String(node.start) };
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
|
|
147
|
+
case "code":
|
|
148
|
+
// Fenced code → pre > code
|
|
149
|
+
el.children = [
|
|
150
|
+
{
|
|
151
|
+
tagName: "code",
|
|
152
|
+
textContent: node.value,
|
|
153
|
+
...(node.lang ? { attributes: { class: `language-${node.lang}` } } : {}),
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
break;
|
|
157
|
+
|
|
158
|
+
case "thematicBreak":
|
|
159
|
+
case "break":
|
|
160
|
+
// Void elements — no content
|
|
161
|
+
break;
|
|
162
|
+
|
|
163
|
+
case "table": {
|
|
164
|
+
// Mdast tables have rows directly; split into thead/tbody
|
|
165
|
+
const rows = (node.children ?? []).map(convertMdastNode).filter(Boolean);
|
|
166
|
+
const thead = rows.length > 0 ? { tagName: "thead", children: [rows[0]] } : null;
|
|
167
|
+
const tbody = rows.length > 1 ? { tagName: "tbody", children: rows.slice(1) } : null;
|
|
168
|
+
el.children = [thead, tbody].filter(Boolean);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
case "tableRow":
|
|
173
|
+
if (node.children?.length > 0) {
|
|
174
|
+
el.children = node.children.map(convertMdastNode).filter(Boolean);
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
|
|
178
|
+
case "tableCell":
|
|
179
|
+
if (node.children?.length === 1 && node.children[0].type === "text") {
|
|
180
|
+
el.textContent = node.children[0].value;
|
|
181
|
+
} else if (node.children?.length > 0) {
|
|
182
|
+
el.children = node.children.map(convertMdastNode).filter(Boolean);
|
|
183
|
+
}
|
|
184
|
+
break;
|
|
185
|
+
|
|
186
|
+
case "html":
|
|
187
|
+
el.innerHTML = node.value;
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return el;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* @param {any} node
|
|
196
|
+
* @returns {any}
|
|
197
|
+
*/
|
|
198
|
+
function convertDirective(node) {
|
|
199
|
+
/** @type {Record<string, any>} */
|
|
200
|
+
const el = { tagName: node.name };
|
|
201
|
+
if (node.attributes && Object.keys(node.attributes).length > 0) {
|
|
202
|
+
el.attributes = { ...node.attributes };
|
|
203
|
+
}
|
|
204
|
+
if (node.type === "textDirective") {
|
|
205
|
+
// Text directives place label as textContent
|
|
206
|
+
if (node.children?.length === 1 && node.children[0].type === "text") {
|
|
207
|
+
el.textContent = node.children[0].value;
|
|
208
|
+
} else if (node.children?.length > 0) {
|
|
209
|
+
el.children = node.children.map(convertMdastNode).filter(Boolean);
|
|
210
|
+
}
|
|
211
|
+
} else if (node.type === "containerDirective" && node.children?.length > 0) {
|
|
212
|
+
el.children = node.children.map(convertMdastNode).filter(Boolean);
|
|
213
|
+
}
|
|
214
|
+
return el;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ─── Jx → mdast ──────────────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Jx tagName → mdast node-type mapping (inverse of MDAST_TAG_MAP)
|
|
221
|
+
*
|
|
222
|
+
* @type {Record<string, string>}
|
|
223
|
+
*/
|
|
224
|
+
const TAG_MDAST_MAP = {
|
|
225
|
+
h1: "heading",
|
|
226
|
+
h2: "heading",
|
|
227
|
+
h3: "heading",
|
|
228
|
+
h4: "heading",
|
|
229
|
+
h5: "heading",
|
|
230
|
+
h6: "heading",
|
|
231
|
+
p: "paragraph",
|
|
232
|
+
span: "text",
|
|
233
|
+
em: "emphasis",
|
|
234
|
+
strong: "strong",
|
|
235
|
+
del: "delete",
|
|
236
|
+
code: "inlineCode",
|
|
237
|
+
a: "link",
|
|
238
|
+
img: "image",
|
|
239
|
+
blockquote: "blockquote",
|
|
240
|
+
ul: "list",
|
|
241
|
+
ol: "list",
|
|
242
|
+
li: "listItem",
|
|
243
|
+
pre: "code",
|
|
244
|
+
hr: "thematicBreak",
|
|
245
|
+
table: "table",
|
|
246
|
+
tr: "tableRow",
|
|
247
|
+
th: "tableCell",
|
|
248
|
+
td: "tableCell",
|
|
249
|
+
br: "break",
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Convert a Jx element tree to an mdast tree.
|
|
254
|
+
*
|
|
255
|
+
* @param {any} jx - Jx element tree (root content div)
|
|
256
|
+
* @returns {any} Mdast root node
|
|
257
|
+
*/
|
|
258
|
+
export function jxToMd(jx) {
|
|
259
|
+
const children = (jx.children ?? [])
|
|
260
|
+
.map((/** @type {any} */ child, /** @type {number} */ _i) => convertJxNode(child, true))
|
|
261
|
+
.filter(Boolean);
|
|
262
|
+
|
|
263
|
+
return { type: "root", children };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Convert a single Jx element to an mdast node.
|
|
268
|
+
*
|
|
269
|
+
* @param {any} el - Jx element
|
|
270
|
+
* @param {boolean} isBlock - Whether this element is in a block context
|
|
271
|
+
* @returns {any} Mdast node
|
|
272
|
+
*/
|
|
273
|
+
function convertJxNode(el, isBlock) {
|
|
274
|
+
if (!el || typeof el !== "object") return null;
|
|
275
|
+
|
|
276
|
+
const tag = el.tagName ?? "div";
|
|
277
|
+
|
|
278
|
+
// If not in the markdown allowlist, convert to directive
|
|
279
|
+
if (!MD_ALL.has(tag)) {
|
|
280
|
+
return convertToDirective(el, isBlock);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const mdastType = TAG_MDAST_MAP[tag];
|
|
284
|
+
if (!mdastType) return null;
|
|
285
|
+
|
|
286
|
+
switch (mdastType) {
|
|
287
|
+
case "heading":
|
|
288
|
+
return {
|
|
289
|
+
type: "heading",
|
|
290
|
+
depth: parseInt(tag.slice(1), 10),
|
|
291
|
+
children: inlineChildren(el),
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
case "paragraph":
|
|
295
|
+
return {
|
|
296
|
+
type: "paragraph",
|
|
297
|
+
children: inlineChildren(el),
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
case "text":
|
|
301
|
+
return { type: "text", value: el.textContent ?? "" };
|
|
302
|
+
|
|
303
|
+
case "emphasis":
|
|
304
|
+
case "strong":
|
|
305
|
+
case "delete":
|
|
306
|
+
return {
|
|
307
|
+
type: mdastType,
|
|
308
|
+
children: inlineChildren(el),
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
case "inlineCode":
|
|
312
|
+
return { type: "inlineCode", value: el.textContent ?? "" };
|
|
313
|
+
|
|
314
|
+
case "link":
|
|
315
|
+
return {
|
|
316
|
+
type: "link",
|
|
317
|
+
url: el.attributes?.href ?? "",
|
|
318
|
+
title: el.attributes?.title ?? null,
|
|
319
|
+
children: inlineChildren(el),
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
case "image":
|
|
323
|
+
return {
|
|
324
|
+
type: "image",
|
|
325
|
+
url: el.attributes?.src ?? "",
|
|
326
|
+
alt: el.attributes?.alt ?? "",
|
|
327
|
+
title: el.attributes?.title ?? null,
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
case "blockquote":
|
|
331
|
+
return {
|
|
332
|
+
type: "blockquote",
|
|
333
|
+
children: blockChildren(el),
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
case "list":
|
|
337
|
+
return {
|
|
338
|
+
type: "list",
|
|
339
|
+
ordered: tag === "ol",
|
|
340
|
+
start: tag === "ol" ? parseInt(el.attributes?.start, 10) || 1 : null,
|
|
341
|
+
spread: false,
|
|
342
|
+
children: (el.children ?? [])
|
|
343
|
+
.map((/** @type {any} */ c) => convertJxNode(c, true))
|
|
344
|
+
.filter(Boolean),
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
case "listItem":
|
|
348
|
+
return {
|
|
349
|
+
type: "listItem",
|
|
350
|
+
spread: false,
|
|
351
|
+
children: blockChildren(el),
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
case "code": {
|
|
355
|
+
// pre > code → fenced code block
|
|
356
|
+
const codeChild = el.children?.[0];
|
|
357
|
+
const langClass = codeChild?.attributes?.class ?? "";
|
|
358
|
+
const lang = langClass.replace("language-", "") || null;
|
|
359
|
+
return {
|
|
360
|
+
type: "code",
|
|
361
|
+
lang,
|
|
362
|
+
value: codeChild?.textContent ?? el.textContent ?? "",
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
case "thematicBreak":
|
|
367
|
+
return { type: "thematicBreak" };
|
|
368
|
+
|
|
369
|
+
case "break":
|
|
370
|
+
return { type: "break" };
|
|
371
|
+
|
|
372
|
+
case "table": {
|
|
373
|
+
// Flatten thead/tbody back to rows
|
|
374
|
+
/** @type {any[]} */
|
|
375
|
+
const rows = [];
|
|
376
|
+
for (const section of el.children ?? []) {
|
|
377
|
+
if (section.tagName === "thead" || section.tagName === "tbody") {
|
|
378
|
+
for (const row of section.children ?? []) {
|
|
379
|
+
const mdRow = convertJxNode(row, true);
|
|
380
|
+
if (mdRow) {
|
|
381
|
+
// Mark header cells
|
|
382
|
+
if (section.tagName === "thead") {
|
|
383
|
+
for (const cell of mdRow.children ?? []) {
|
|
384
|
+
cell.isHeader = true;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
rows.push(mdRow);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
type: "table",
|
|
394
|
+
align: null,
|
|
395
|
+
children: rows,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
case "tableRow":
|
|
400
|
+
return {
|
|
401
|
+
type: "tableRow",
|
|
402
|
+
children: (el.children ?? [])
|
|
403
|
+
.map((/** @type {any} */ c) => convertJxNode(c, false))
|
|
404
|
+
.filter(Boolean),
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
case "tableCell":
|
|
408
|
+
return {
|
|
409
|
+
type: "tableCell",
|
|
410
|
+
children: inlineChildren(el),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Get inline children from a Jx element as mdast nodes. Handles both textContent shorthand and
|
|
419
|
+
* explicit children array.
|
|
420
|
+
*
|
|
421
|
+
* @param {any} el
|
|
422
|
+
* @returns {any[]}
|
|
423
|
+
*/
|
|
424
|
+
function inlineChildren(el) {
|
|
425
|
+
if (el.textContent != null) {
|
|
426
|
+
return [{ type: "text", value: String(el.textContent) }];
|
|
427
|
+
}
|
|
428
|
+
return (el.children ?? []).map((/** @type {any} */ c) => convertJxNode(c, false)).filter(Boolean);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Get block children from a Jx element as mdast nodes.
|
|
433
|
+
*
|
|
434
|
+
* @param {any} el
|
|
435
|
+
* @returns {any[]}
|
|
436
|
+
*/
|
|
437
|
+
function blockChildren(el) {
|
|
438
|
+
if (el.textContent != null) {
|
|
439
|
+
// Wrap bare text in a paragraph
|
|
440
|
+
return [{ type: "paragraph", children: [{ type: "text", value: String(el.textContent) }] }];
|
|
441
|
+
}
|
|
442
|
+
return (el.children ?? []).map((/** @type {any} */ c) => convertJxNode(c, true)).filter(Boolean);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Convert a non-markdown-native Jx element to a directive node.
|
|
447
|
+
*
|
|
448
|
+
* @param {any} el
|
|
449
|
+
* @param {boolean} isBlock
|
|
450
|
+
* @returns {any}
|
|
451
|
+
*/
|
|
452
|
+
function convertToDirective(el, isBlock) {
|
|
453
|
+
const tag = el.tagName ?? "div";
|
|
454
|
+
const attrs = el.attributes ? { ...el.attributes } : {};
|
|
455
|
+
|
|
456
|
+
if (!isBlock) {
|
|
457
|
+
// Inline → textDirective
|
|
458
|
+
return {
|
|
459
|
+
type: "textDirective",
|
|
460
|
+
name: tag,
|
|
461
|
+
attributes: attrs,
|
|
462
|
+
children:
|
|
463
|
+
el.textContent != null
|
|
464
|
+
? [{ type: "text", value: String(el.textContent) }]
|
|
465
|
+
: (el.children ?? [])
|
|
466
|
+
.map((/** @type {any} */ c) => convertJxNode(c, false))
|
|
467
|
+
.filter(Boolean),
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Block without children → leafDirective
|
|
472
|
+
if (!el.children?.length && el.textContent == null) {
|
|
473
|
+
return {
|
|
474
|
+
type: "leafDirective",
|
|
475
|
+
name: tag,
|
|
476
|
+
attributes: attrs,
|
|
477
|
+
children: [],
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Block with children → containerDirective
|
|
482
|
+
return {
|
|
483
|
+
type: "containerDirective",
|
|
484
|
+
name: tag,
|
|
485
|
+
attributes: attrs,
|
|
486
|
+
children:
|
|
487
|
+
el.textContent != null
|
|
488
|
+
? [{ type: "paragraph", children: [{ type: "text", value: String(el.textContent) }] }]
|
|
489
|
+
: (el.children ?? []).map((/** @type {any} */ c) => convertJxNode(c, true)).filter(Boolean),
|
|
490
|
+
};
|
|
491
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** Activity bar — tab icons for switching left panel views. */
|
|
2
|
+
|
|
3
|
+
import { html, render as litRender, nothing } from "lit-html";
|
|
4
|
+
import { activityBar, update, getState, renderOnly } from "../store.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {any} tag
|
|
8
|
+
* @param {any} size
|
|
9
|
+
*/
|
|
10
|
+
export function tabIcon(tag, size) {
|
|
11
|
+
/** @type {Record<string, any>} */
|
|
12
|
+
const m = {
|
|
13
|
+
"sp-icon-folder": (/** @type {any} */ s) =>
|
|
14
|
+
html`<sp-icon-folder slot="icon" size=${s}></sp-icon-folder>`,
|
|
15
|
+
"sp-icon-layers": (/** @type {any} */ s) =>
|
|
16
|
+
html`<sp-icon-layers slot="icon" size=${s}></sp-icon-layers>`,
|
|
17
|
+
"sp-icon-view-grid": (/** @type {any} */ s) =>
|
|
18
|
+
html`<sp-icon-view-grid slot="icon" size=${s}></sp-icon-view-grid>`,
|
|
19
|
+
"sp-icon-brackets": (/** @type {any} */ s) =>
|
|
20
|
+
html`<sp-icon-brackets slot="icon" size=${s}></sp-icon-brackets>`,
|
|
21
|
+
"sp-icon-data": (/** @type {any} */ s) =>
|
|
22
|
+
html`<sp-icon-data slot="icon" size=${s}></sp-icon-data>`,
|
|
23
|
+
"sp-icon-properties": (/** @type {any} */ s) =>
|
|
24
|
+
html`<sp-icon-properties slot="icon" size=${s}></sp-icon-properties>`,
|
|
25
|
+
"sp-icon-event": (/** @type {any} */ s) =>
|
|
26
|
+
html`<sp-icon-event slot="icon" size=${s}></sp-icon-event>`,
|
|
27
|
+
"sp-icon-brush": (/** @type {any} */ s) =>
|
|
28
|
+
html`<sp-icon-brush slot="icon" size=${s}></sp-icon-brush>`,
|
|
29
|
+
"sp-icon-artboard": (/** @type {any} */ s) =>
|
|
30
|
+
html`<sp-icon-artboard slot="icon" size=${s}></sp-icon-artboard>`,
|
|
31
|
+
"sp-icon-box": (/** @type {any} */ s) =>
|
|
32
|
+
html`<sp-icon-box slot="icon" size=${s}></sp-icon-box>`,
|
|
33
|
+
};
|
|
34
|
+
const fn = m[tag];
|
|
35
|
+
return fn ? fn(size || "s") : nothing;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** @param {any} S — current studio state */
|
|
39
|
+
export function renderActivityBar(S) {
|
|
40
|
+
const tabs = [
|
|
41
|
+
{ value: "files", icon: "sp-icon-folder", label: "Files" },
|
|
42
|
+
{ value: "layers", icon: "sp-icon-layers", label: "Layers" },
|
|
43
|
+
{ value: "imports", icon: "sp-icon-box", label: "Imports" },
|
|
44
|
+
{ value: "blocks", icon: "sp-icon-view-grid", label: "Elements" },
|
|
45
|
+
{ value: "state", icon: "sp-icon-brackets", label: "State" },
|
|
46
|
+
{ value: "data", icon: "sp-icon-data", label: "Data" },
|
|
47
|
+
];
|
|
48
|
+
const tpl = html`
|
|
49
|
+
<sp-tabs
|
|
50
|
+
selected=${S.ui.leftTab}
|
|
51
|
+
direction="vertical"
|
|
52
|
+
quiet
|
|
53
|
+
@change=${(/** @type {any} */ e) => {
|
|
54
|
+
const current = getState();
|
|
55
|
+
update({ ...current, ui: { ...current.ui, leftTab: e.target.selected } });
|
|
56
|
+
renderOnly("activityBar", "leftPanel");
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
${tabs.map(
|
|
60
|
+
(t) => html`
|
|
61
|
+
<sp-tab value=${t.value} title=${t.label} aria-label=${t.label}>
|
|
62
|
+
${tabIcon(t.icon, "m")}
|
|
63
|
+
</sp-tab>
|
|
64
|
+
`,
|
|
65
|
+
)}
|
|
66
|
+
</sp-tabs>
|
|
67
|
+
`;
|
|
68
|
+
litRender(tpl, /** @type {any} */ (activityBar));
|
|
69
|
+
}
|