@kokoa/clotho-editor 0.1.3 → 0.2.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.
- package/README.md +101 -56
- package/dist/{chunk-Z2K5HFHI.js → chunk-FAI2G3XK.js} +284 -21
- package/dist/chunk-FAI2G3XK.js.map +1 -0
- package/dist/clotho-editor.css +134 -0
- package/dist/index.d.ts +151 -18
- package/dist/index.js +515 -9
- package/dist/index.js.map +1 -1
- package/dist/{main-5YXLTYPQ.js → main-SKM3P4X7.js} +427 -22
- package/dist/main-SKM3P4X7.js.map +1 -0
- package/package.json +7 -3
- package/dist/chunk-Z2K5HFHI.js.map +0 -1
- package/dist/main-5YXLTYPQ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { downloadAnimationJson, configureAnimationRepository } from './chunk-
|
|
2
|
-
export { addAppearance, addChapter, addEffect, addElement, animationDocumentFileName, animationDocumentToJson, apiBaseUrl, beginTransient, canRedo, canUndo, childIdsOf, configureAnimationRepository, configureApi, configureHost, deleteChapter, deleteEffect, deleteElement, downloadAnimationJson, endTransient, garbageCollectAnimationAssets, getCurrentSnapshot, getCurrentTime, getDef, getHistory, getSelectedElementIds, getSelection, groupElements, isDirty, isDraft, isElementSelected, isGroup, jumpBack, jumpForward, markClean, moveElementToEnd, moveElementToFront, placeholderImageUrl, promoteDraftToSaved, redo, registerDataUriAsset, registerExternalAsset, registerInlineAsset, removeAppearance, removeTrack, removeTrackKeyframe, reorderElement, resetHistory, setCurrentTime, setDef, setDraft, setElementValueAtTime, setSelection, setTrackKeyframe, subscribe, toggleSelectionFor, undo, ungroupElement, uniqueChapterId, uniqueEffectId, uniqueElementId, updateAppearance, updateCanvas, updateChapter, updateDuration, updateEffect, updateElementBase, updateMeta, updateSettings } from './chunk-
|
|
3
|
-
import { useState, useEffect, useRef } from 'react';
|
|
1
|
+
import { downloadAnimationJson, configureAnimationRepository } from './chunk-FAI2G3XK.js';
|
|
2
|
+
export { addAppearance, addChapter, addCheckpoint, addEffect, addElement, animationDocumentFileName, animationDocumentToJson, apiBaseUrl, beginTransient, canRedo, canUndo, childIdsOf, configureAnimationRepository, configureApi, configureHost, createEditorPluginContext, createLayout, deleteChapter, deleteCheckpoint, deleteEffect, deleteElement, detachFromLayout, downloadAnimationJson, endTransient, findLayoutCollisions, garbageCollectAnimationAssets, getCurrentSnapshot, getCurrentTime, getDef, getHistory, getSelectedElementIds, getSelection, groupElements, isDirty, isDraft, isElementSelected, isGroup, jumpBack, jumpForward, layoutIdsFor, markClean, mountEditorPlugins, moveElementToEnd, moveElementToFront, placeholderImageUrl, promoteDraftToSaved, redo, registerDataUriAsset, registerExternalAsset, registerInlineAsset, removeAppearance, removeTrack, removeTrackKeyframe, reorderElement, resetHistory, setCurrentTime, setDef, setDraft, setElementValueAtTime, setSelection, setTrackKeyframe, subscribe, toggleSelectionFor, undo, ungroupElement, uniqueChapterId, uniqueCheckpointId, uniqueEffectId, uniqueElementId, updateAppearance, updateCanvas, updateChapter, updateCheckpoint, updateData, updateDuration, updateEffect, updateElementBase, updateLocales, updateMeta, updateResponsive, updateSettings, validateEditorPlugin } from './chunk-FAI2G3XK.js';
|
|
3
|
+
import { useState, useEffect, useRef, useMemo } from 'react';
|
|
4
4
|
import { usePlayer, AnimationStage } from '@kokoa/clotho/react';
|
|
5
5
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
|
-
import { animationDocumentSchema } from '@kokoa/clotho';
|
|
6
|
+
import { defineStory, animationDocumentSchema, compileSceneDependencyPlan, buildScene, lintDocument, autofixDocument, compileResponsiveStage } from '@kokoa/clotho';
|
|
7
|
+
import { animationSampleTimes, expectAnimation, snapshotAnimationMatrix, animationFailureReport, AnimationAssertionError } from '@kokoa/clotho/testing';
|
|
8
|
+
import { renderDocumentToSvg } from '@kokoa/clotho/svg';
|
|
7
9
|
|
|
8
10
|
function elementsOf(def) {
|
|
9
11
|
return def.elements;
|
|
@@ -53,6 +55,8 @@ function Studio({ initial, onSave }) {
|
|
|
53
55
|
y: 100,
|
|
54
56
|
rotation: 0,
|
|
55
57
|
content: "New",
|
|
58
|
+
translations: {},
|
|
59
|
+
references: {},
|
|
56
60
|
fontSize: 24,
|
|
57
61
|
color: "#4f46e5",
|
|
58
62
|
textAnchor: "start",
|
|
@@ -214,6 +218,250 @@ function Studio({ initial, onSave }) {
|
|
|
214
218
|
] })
|
|
215
219
|
] });
|
|
216
220
|
}
|
|
221
|
+
|
|
222
|
+
// src/template-editor-plugin.ts
|
|
223
|
+
function initialParameterValue(schema) {
|
|
224
|
+
if (schema.default !== void 0) return structuredClone(schema.default);
|
|
225
|
+
if (schema.type === "string") return "";
|
|
226
|
+
if (schema.type === "number") return schema.min ?? 0;
|
|
227
|
+
if (schema.type === "boolean") return false;
|
|
228
|
+
if (schema.type === "enum") return schema.values[0];
|
|
229
|
+
if (schema.type === "array") return [];
|
|
230
|
+
return Object.fromEntries(
|
|
231
|
+
Object.entries(schema.properties).map(([key, child]) => [
|
|
232
|
+
key,
|
|
233
|
+
initialParameterValue(child)
|
|
234
|
+
])
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
function parameterInputValue(schema, raw) {
|
|
238
|
+
if (schema.type === "boolean") return Boolean(raw);
|
|
239
|
+
if (schema.type === "number") return Number(raw);
|
|
240
|
+
if (schema.type === "array" || schema.type === "object")
|
|
241
|
+
return JSON.parse(String(raw));
|
|
242
|
+
return String(raw);
|
|
243
|
+
}
|
|
244
|
+
function downloadReference(reference) {
|
|
245
|
+
const blob = new Blob([JSON.stringify(reference, null, 2)], {
|
|
246
|
+
type: "application/json"
|
|
247
|
+
});
|
|
248
|
+
const url = URL.createObjectURL(blob);
|
|
249
|
+
const anchor = document.createElement("a");
|
|
250
|
+
anchor.href = url;
|
|
251
|
+
anchor.download = `${reference.templateId.replace(/[^a-z0-9._-]/gi, "-")}.template.json`;
|
|
252
|
+
anchor.click();
|
|
253
|
+
URL.revokeObjectURL(url);
|
|
254
|
+
}
|
|
255
|
+
function field(key, schema, value, onChange) {
|
|
256
|
+
const label = document.createElement("label");
|
|
257
|
+
label.className = "studio-field studio-template-field";
|
|
258
|
+
const title = document.createElement("span");
|
|
259
|
+
title.textContent = key;
|
|
260
|
+
if (schema.description) title.title = schema.description;
|
|
261
|
+
label.append(title);
|
|
262
|
+
let control;
|
|
263
|
+
if (schema.type === "enum") {
|
|
264
|
+
control = document.createElement("select");
|
|
265
|
+
for (const option of schema.values) {
|
|
266
|
+
const element = document.createElement("option");
|
|
267
|
+
element.value = option;
|
|
268
|
+
element.textContent = option;
|
|
269
|
+
control.append(element);
|
|
270
|
+
}
|
|
271
|
+
control.value = String(value);
|
|
272
|
+
} else if (schema.type === "array" || schema.type === "object") {
|
|
273
|
+
control = document.createElement("textarea");
|
|
274
|
+
control.rows = 3;
|
|
275
|
+
control.value = JSON.stringify(value, null, 2);
|
|
276
|
+
} else {
|
|
277
|
+
control = document.createElement("input");
|
|
278
|
+
control.type = schema.type === "boolean" ? "checkbox" : schema.type;
|
|
279
|
+
if (schema.type === "boolean") control.checked = Boolean(value);
|
|
280
|
+
else control.value = String(value);
|
|
281
|
+
if (schema.type === "number") {
|
|
282
|
+
if (schema.min !== void 0) control.min = String(schema.min);
|
|
283
|
+
if (schema.max !== void 0) control.max = String(schema.max);
|
|
284
|
+
control.step = schema.integer ? "1" : "any";
|
|
285
|
+
}
|
|
286
|
+
if (schema.type === "string") {
|
|
287
|
+
if (schema.minLength !== void 0) control.minLength = schema.minLength;
|
|
288
|
+
if (schema.maxLength !== void 0) control.maxLength = schema.maxLength;
|
|
289
|
+
if (schema.pattern !== void 0) control.pattern = schema.pattern;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
control.dataset.templateParameter = key;
|
|
293
|
+
const update = () => {
|
|
294
|
+
try {
|
|
295
|
+
const raw = control instanceof HTMLInputElement && control.type === "checkbox" ? control.checked : control.value;
|
|
296
|
+
onChange(parameterInputValue(schema, raw));
|
|
297
|
+
control.setCustomValidity("");
|
|
298
|
+
} catch {
|
|
299
|
+
control.setCustomValidity("\uC62C\uBC14\uB978 JSON\uC744 \uC785\uB825\uD558\uC138\uC694.");
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
control.addEventListener(
|
|
303
|
+
schema.type === "enum" || schema.type === "boolean" ? "change" : "input",
|
|
304
|
+
update
|
|
305
|
+
);
|
|
306
|
+
label.append(control);
|
|
307
|
+
return label;
|
|
308
|
+
}
|
|
309
|
+
function createTemplateEditorPlugin(templates) {
|
|
310
|
+
return {
|
|
311
|
+
manifest: {
|
|
312
|
+
id: "dev.clotho.templates",
|
|
313
|
+
name: "Template parameters",
|
|
314
|
+
capabilities: ["editor"],
|
|
315
|
+
editor: { panels: ["parameters"] }
|
|
316
|
+
},
|
|
317
|
+
panels: {
|
|
318
|
+
parameters: {
|
|
319
|
+
id: "parameters",
|
|
320
|
+
label: "Template parameters",
|
|
321
|
+
mount(container, context) {
|
|
322
|
+
if (templates.length === 0) return;
|
|
323
|
+
container.classList.add(
|
|
324
|
+
"studio-tools-section",
|
|
325
|
+
"studio-template-panel"
|
|
326
|
+
);
|
|
327
|
+
const heading = document.createElement("div");
|
|
328
|
+
heading.className = "studio-tools-title";
|
|
329
|
+
heading.textContent = "Template";
|
|
330
|
+
const select = document.createElement("select");
|
|
331
|
+
select.className = "studio-template-select";
|
|
332
|
+
for (const template of templates) {
|
|
333
|
+
const option = document.createElement("option");
|
|
334
|
+
option.value = template.id;
|
|
335
|
+
option.textContent = template.id;
|
|
336
|
+
select.append(option);
|
|
337
|
+
}
|
|
338
|
+
const form = document.createElement("div");
|
|
339
|
+
const status = document.createElement("p");
|
|
340
|
+
status.className = "studio-props-empty studio-template-status";
|
|
341
|
+
const actions = document.createElement("div");
|
|
342
|
+
actions.className = "studio-align-row";
|
|
343
|
+
const standalone = document.createElement("button");
|
|
344
|
+
standalone.className = "studio-btn";
|
|
345
|
+
standalone.textContent = "Standalone JSON";
|
|
346
|
+
const reference = document.createElement("button");
|
|
347
|
+
reference.className = "studio-btn";
|
|
348
|
+
reference.textContent = "Template \uCC38\uC870";
|
|
349
|
+
actions.append(standalone, reference);
|
|
350
|
+
container.append(heading, select, form, status, actions);
|
|
351
|
+
let active = templates[0];
|
|
352
|
+
let values = {};
|
|
353
|
+
let previewDocument = context.getDocument();
|
|
354
|
+
const rebuild = () => {
|
|
355
|
+
try {
|
|
356
|
+
previewDocument = active.instantiate(values);
|
|
357
|
+
context.replaceDocument(previewDocument);
|
|
358
|
+
status.textContent = "parameter\uAC00 \uBBF8\uB9AC\uBCF4\uAE30\uC5D0 \uBC18\uC601\uB418\uC5C8\uC2B5\uB2C8\uB2E4.";
|
|
359
|
+
status.removeAttribute("data-error");
|
|
360
|
+
} catch (error) {
|
|
361
|
+
const issues = error.issues;
|
|
362
|
+
status.textContent = issues ? issues.map(({ path, message }) => `${path}: ${message}`).join(" \xB7 ") : String(error);
|
|
363
|
+
status.dataset.error = "true";
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
const render = () => {
|
|
367
|
+
values = Object.fromEntries(
|
|
368
|
+
Object.entries(active.parameters).map(([key, schema]) => [
|
|
369
|
+
key,
|
|
370
|
+
initialParameterValue(schema)
|
|
371
|
+
])
|
|
372
|
+
);
|
|
373
|
+
form.replaceChildren(
|
|
374
|
+
...Object.entries(active.parameters).map(
|
|
375
|
+
([key, schema]) => field(key, schema, values[key], (value) => {
|
|
376
|
+
values[key] = value;
|
|
377
|
+
rebuild();
|
|
378
|
+
})
|
|
379
|
+
)
|
|
380
|
+
);
|
|
381
|
+
rebuild();
|
|
382
|
+
};
|
|
383
|
+
select.addEventListener("change", () => {
|
|
384
|
+
active = templates.find(({ id }) => id === select.value) ?? templates[0];
|
|
385
|
+
render();
|
|
386
|
+
});
|
|
387
|
+
standalone.addEventListener(
|
|
388
|
+
"click",
|
|
389
|
+
() => downloadAnimationJson(previewDocument)
|
|
390
|
+
);
|
|
391
|
+
reference.addEventListener(
|
|
392
|
+
"click",
|
|
393
|
+
() => downloadReference(active.reference(values))
|
|
394
|
+
);
|
|
395
|
+
render();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function replaceStoryNodeDocument(manifest, nodeId, document2) {
|
|
402
|
+
return defineStory({
|
|
403
|
+
...manifest,
|
|
404
|
+
nodes: manifest.nodes.map(
|
|
405
|
+
(node) => node.id === nodeId ? { ...node, document: document2 } : node
|
|
406
|
+
)
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
function appendStoryEdge(manifest, edge) {
|
|
410
|
+
return defineStory({ ...manifest, edges: [...manifest.edges, edge] });
|
|
411
|
+
}
|
|
412
|
+
function createStoryEditorPlugin(options) {
|
|
413
|
+
let manifest = structuredClone(options.manifest);
|
|
414
|
+
let activeNode = manifest.initialNode;
|
|
415
|
+
return {
|
|
416
|
+
manifest: {
|
|
417
|
+
id: "dev.clotho.story",
|
|
418
|
+
name: "Story Graph",
|
|
419
|
+
capabilities: ["editor"],
|
|
420
|
+
editor: { panels: ["story-graph"] }
|
|
421
|
+
},
|
|
422
|
+
panels: {
|
|
423
|
+
"story-graph": {
|
|
424
|
+
id: "story-graph",
|
|
425
|
+
label: "Story Graph",
|
|
426
|
+
mount(container, context) {
|
|
427
|
+
const render = () => {
|
|
428
|
+
container.innerHTML = `<div class="studio-tools-section"><div class="studio-tools-title">Story Graph</div><div class="studio-story-nodes">${manifest.nodes.map((node) => `<button type="button" class="studio-btn${node.id === activeNode ? " is-active" : ""}" data-story-node="${escapeAttribute(node.id)}">${escapeHtml(node.title || node.id)}</button>`).join("")}</div><div class="studio-tools-hint">${manifest.edges.map((edge) => `${escapeHtml(edge.from)} \u2192 ${escapeHtml(edge.to)}${edge.label ? ` \xB7 ${escapeHtml(edge.label)}` : ""}`).join("<br>") || "\uC5F0\uACB0\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."}</div></div>`;
|
|
429
|
+
};
|
|
430
|
+
const click = (event) => {
|
|
431
|
+
const button = event.target.closest(
|
|
432
|
+
"[data-story-node]"
|
|
433
|
+
);
|
|
434
|
+
const nextId = button?.dataset.storyNode;
|
|
435
|
+
if (!nextId || nextId === activeNode) return;
|
|
436
|
+
manifest = replaceStoryNodeDocument(
|
|
437
|
+
manifest,
|
|
438
|
+
activeNode,
|
|
439
|
+
context.getDocument()
|
|
440
|
+
);
|
|
441
|
+
const node = manifest.nodes.find(({ id }) => id === nextId);
|
|
442
|
+
if (!node) return;
|
|
443
|
+
activeNode = nextId;
|
|
444
|
+
context.replaceDocument(node.document);
|
|
445
|
+
options.onChange?.(structuredClone(manifest));
|
|
446
|
+
render();
|
|
447
|
+
};
|
|
448
|
+
container.addEventListener("click", click);
|
|
449
|
+
render();
|
|
450
|
+
return () => container.removeEventListener("click", click);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function escapeHtml(value) {
|
|
457
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
458
|
+
}
|
|
459
|
+
function escapeAttribute(value) {
|
|
460
|
+
return escapeHtml(value).replaceAll('"', """);
|
|
461
|
+
}
|
|
462
|
+
var EMPTY_EDITOR_PLUGINS = [];
|
|
463
|
+
var EMPTY_TEMPLATES = [];
|
|
464
|
+
var DENY_PLUGIN_PERMISSIONS = () => ({});
|
|
217
465
|
var CANVAS_PRESETS = [
|
|
218
466
|
{ id: "800x500", label: "8:5", sub: "800\xD7500", w: 32, h: 20 },
|
|
219
467
|
{ id: "1280x720", label: "16:9", sub: "HD", w: 32, h: 18 },
|
|
@@ -237,6 +485,7 @@ var SKELETON = `
|
|
|
237
485
|
<header class="studio-header">
|
|
238
486
|
<h1 id="studio-editor-title" class="studio-header-title">Clotho Editor</h1>
|
|
239
487
|
<div class="studio-header-actions">
|
|
488
|
+
<span class="studio-plugin-toolbar" data-editor-plugin-slot="toolbar"></span>
|
|
240
489
|
<button type="button" id="studio-open" class="studio-btn" aria-label="\uC800\uC7A5\uB41C \uC560\uB2C8\uBA54\uC774\uC158 \uC5F4\uAE30">\u{1F4C1} \uC5F4\uAE30</button>
|
|
241
490
|
<button type="button" id="studio-new" class="studio-btn" aria-label="\uC0C8 \uC560\uB2C8\uBA54\uC774\uC158 \uB9CC\uB4E4\uAE30">\uFF0B \uC0C8 \uC560\uB2C8\uBA54\uC774\uC158</button>
|
|
242
491
|
<button type="button" id="studio-undo" class="studio-btn studio-btn-icon" aria-label="\uC2E4\uD589 \uCDE8\uC18C" title="\uC2E4\uD589 \uCDE8\uC18C (\u2318Z)" disabled>\u21B6</button>
|
|
@@ -269,6 +518,7 @@ var SKELETON = `
|
|
|
269
518
|
<div class="studio-tool-with-option"><button type="button" class="studio-tool-btn" data-add-element="polygon" title="\uB2E4\uAC01\uD615 (Y)" aria-label="\uB2E4\uAC01\uD615 \uB3C4\uAD6C"><span>\u2B22 Polygon</span><kbd>Y</kbd></button><label>\uBCC0 <input type="number" id="studio-polygon-sides" min="3" max="24" value="6" aria-label="\uB2E4\uAC01\uD615\uC758 \uBCC0 \uAC1C\uC218" /></label></div>
|
|
270
519
|
<button type="button" class="studio-tool-btn" id="studio-open-icons" title="\uC544\uC774\uCF58 \uB77C\uC774\uBE0C\uB7EC\uB9AC" aria-label="\uC544\uC774\uCF58 \uB77C\uC774\uBE0C\uB7EC\uB9AC \uCD94\uAC00">\u{1F3A8} Icons</button>
|
|
271
520
|
</div>
|
|
521
|
+
<div class="studio-plugin-panels" data-editor-plugin-slot="panel"></div>
|
|
272
522
|
<div class="studio-tools-section">
|
|
273
523
|
<div class="studio-tools-title">\uCE94\uBC84\uC2A4 \uD06C\uAE30</div>
|
|
274
524
|
<div class="studio-canvas-size-row">
|
|
@@ -310,6 +560,7 @@ var SKELETON = `
|
|
|
310
560
|
<div id="studio-props-content" class="studio-props-content">
|
|
311
561
|
<p class="studio-props-empty">\uC694\uC18C \uB610\uB294 step \uC744 \uC120\uD0DD\uD558\uC138\uC694.</p>
|
|
312
562
|
</div>
|
|
563
|
+
<div class="studio-plugin-inspectors" data-editor-plugin-slot="inspector"></div>
|
|
313
564
|
</aside>
|
|
314
565
|
</div>
|
|
315
566
|
|
|
@@ -425,24 +676,58 @@ function StudioMount({
|
|
|
425
676
|
initialId,
|
|
426
677
|
repository,
|
|
427
678
|
editorTitle = "Clotho Editor",
|
|
428
|
-
resolveImage
|
|
679
|
+
resolveImage,
|
|
680
|
+
importDocument,
|
|
681
|
+
plugins = EMPTY_EDITOR_PLUGINS,
|
|
682
|
+
resolvePluginPermissions = DENY_PLUGIN_PERMISSIONS,
|
|
683
|
+
templates = EMPTY_TEMPLATES,
|
|
684
|
+
story
|
|
429
685
|
}) {
|
|
430
686
|
const inited = useRef(false);
|
|
687
|
+
const mountedPlugins = useMemo(
|
|
688
|
+
() => [
|
|
689
|
+
...templates.length > 0 ? [createTemplateEditorPlugin(templates)] : [],
|
|
690
|
+
...story ? [createStoryEditorPlugin(story)] : [],
|
|
691
|
+
...plugins
|
|
692
|
+
],
|
|
693
|
+
[plugins, templates, story]
|
|
694
|
+
);
|
|
695
|
+
const permissionResolver = useMemo(
|
|
696
|
+
() => (plugin) => plugin.id === "dev.clotho.templates" || plugin.id === "dev.clotho.story" ? { ui: true, documentRead: true, documentWrite: true } : resolvePluginPermissions(plugin),
|
|
697
|
+
[resolvePluginPermissions]
|
|
698
|
+
);
|
|
431
699
|
useEffect(() => {
|
|
432
700
|
if (inited.current) return;
|
|
433
701
|
inited.current = true;
|
|
434
702
|
if (repository) configureAnimationRepository(repository);
|
|
435
703
|
let disposed = false;
|
|
436
|
-
|
|
704
|
+
let cleanup;
|
|
705
|
+
void import('./main-SKM3P4X7.js').then(({ initStudio }) => {
|
|
437
706
|
if (disposed) return;
|
|
438
|
-
initStudio({
|
|
707
|
+
cleanup = initStudio({
|
|
708
|
+
initialId,
|
|
709
|
+
editorTitle,
|
|
710
|
+
resolveImage,
|
|
711
|
+
importDocument,
|
|
712
|
+
plugins: mountedPlugins,
|
|
713
|
+
resolvePluginPermissions: permissionResolver
|
|
714
|
+
});
|
|
439
715
|
});
|
|
440
716
|
return () => {
|
|
441
717
|
disposed = true;
|
|
718
|
+
cleanup?.();
|
|
442
719
|
document.body.classList.remove("editor-active");
|
|
443
720
|
document.documentElement.classList.remove("editor-active");
|
|
444
721
|
};
|
|
445
|
-
}, [
|
|
722
|
+
}, [
|
|
723
|
+
editorTitle,
|
|
724
|
+
importDocument,
|
|
725
|
+
initialId,
|
|
726
|
+
mountedPlugins,
|
|
727
|
+
repository,
|
|
728
|
+
resolveImage,
|
|
729
|
+
permissionResolver
|
|
730
|
+
]);
|
|
446
731
|
return /* @__PURE__ */ jsx("section", { className: "studio-shell w-full", "data-pagefind-ignore": "all", children: /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: SKELETON } }) });
|
|
447
732
|
}
|
|
448
733
|
function browserStorage() {
|
|
@@ -530,7 +815,228 @@ function createLocalStorageRepository(options = {}) {
|
|
|
530
815
|
}
|
|
531
816
|
};
|
|
532
817
|
}
|
|
818
|
+
function downloadReport(html) {
|
|
819
|
+
const url = URL.createObjectURL(new Blob([html], { type: "text/html" }));
|
|
820
|
+
const link = document.createElement("a");
|
|
821
|
+
link.href = url;
|
|
822
|
+
link.download = "clotho-animation-qa.html";
|
|
823
|
+
link.click();
|
|
824
|
+
URL.revokeObjectURL(url);
|
|
825
|
+
}
|
|
826
|
+
function createVisualRegressionPlugin() {
|
|
827
|
+
return {
|
|
828
|
+
manifest: {
|
|
829
|
+
id: "dev.clotho.visual-regression",
|
|
830
|
+
name: "Animation QA",
|
|
831
|
+
capabilities: ["editor"],
|
|
832
|
+
editor: { inspectors: ["animation-qa"] }
|
|
833
|
+
},
|
|
834
|
+
inspectors: {
|
|
835
|
+
"animation-qa": {
|
|
836
|
+
id: "animation-qa",
|
|
837
|
+
label: "Animation QA",
|
|
838
|
+
mount(container, context) {
|
|
839
|
+
container.classList.add("studio-qa-panel");
|
|
840
|
+
const title = document.createElement("strong");
|
|
841
|
+
title.textContent = "Animation QA";
|
|
842
|
+
const run = document.createElement("button");
|
|
843
|
+
run.type = "button";
|
|
844
|
+
run.className = "studio-btn";
|
|
845
|
+
run.textContent = "\uC804\uCCB4 frame \uAC80\uC0AC";
|
|
846
|
+
const status = document.createElement("p");
|
|
847
|
+
status.className = "studio-props-empty";
|
|
848
|
+
container.append(title, run, status);
|
|
849
|
+
run.addEventListener("click", () => {
|
|
850
|
+
const animation = context.getDocument();
|
|
851
|
+
const errors = [];
|
|
852
|
+
for (const time of animationSampleTimes(animation)) {
|
|
853
|
+
for (const element of animation.elements) {
|
|
854
|
+
if (!["rect", "circle", "image", "code"].includes(element.type))
|
|
855
|
+
continue;
|
|
856
|
+
try {
|
|
857
|
+
expectAnimation(animation).at(time).insideCanvas(element.id);
|
|
858
|
+
} catch (error) {
|
|
859
|
+
if (error instanceof AnimationAssertionError)
|
|
860
|
+
errors.push(error);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
const snapshots = snapshotAnimationMatrix(animation);
|
|
865
|
+
if (errors.length === 0) {
|
|
866
|
+
status.textContent = `${snapshots.length}\uAC1C locale\xB7theme frame\uC744 \uAC80\uC0AC\uD588\uC2B5\uB2C8\uB2E4. \uBB38\uC81C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.`;
|
|
867
|
+
status.removeAttribute("data-error");
|
|
868
|
+
} else {
|
|
869
|
+
status.textContent = `${errors.length}\uAC1C \uBB38\uC81C\uB97C \uBC1C\uACAC\uD588\uC2B5\uB2C8\uB2E4. HTML report\uB97C \uB0B4\uB824\uBC1B\uC2B5\uB2C8\uB2E4.`;
|
|
870
|
+
status.dataset.error = "true";
|
|
871
|
+
downloadReport(animationFailureReport(errors));
|
|
872
|
+
}
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
var RESPONSIVE_VIEWPORTS = [
|
|
880
|
+
{ id: "compact", label: "\uBAA8\uBC14\uC77C", width: 375 },
|
|
881
|
+
{ id: "regular", label: "\uBCF8\uBB38", width: 768 },
|
|
882
|
+
{ id: "wide", label: "\uB113\uC740 \uD654\uBA74", width: 1280 }
|
|
883
|
+
];
|
|
884
|
+
function addDefaultResponsiveVariants(document2) {
|
|
885
|
+
if (document2.responsive?.length) return document2;
|
|
886
|
+
return {
|
|
887
|
+
...document2,
|
|
888
|
+
responsive: [
|
|
889
|
+
{
|
|
890
|
+
id: "compact",
|
|
891
|
+
minWidth: 0,
|
|
892
|
+
maxWidth: 479,
|
|
893
|
+
chapterListPosition: "bottom",
|
|
894
|
+
elementOverrides: {}
|
|
895
|
+
},
|
|
896
|
+
{ id: "regular", minWidth: 480, maxWidth: 959, elementOverrides: {} },
|
|
897
|
+
{ id: "wide", minWidth: 960, elementOverrides: {} }
|
|
898
|
+
]
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
function createResponsiveInspectorPlugin() {
|
|
902
|
+
return {
|
|
903
|
+
manifest: {
|
|
904
|
+
id: "dev.clotho.responsive",
|
|
905
|
+
name: "Responsive Stage",
|
|
906
|
+
capabilities: ["editor"],
|
|
907
|
+
editor: { inspectors: ["responsive"] }
|
|
908
|
+
},
|
|
909
|
+
inspectors: {
|
|
910
|
+
responsive: {
|
|
911
|
+
id: "responsive",
|
|
912
|
+
label: "Responsive Stage",
|
|
913
|
+
mount(container, context) {
|
|
914
|
+
const render = () => {
|
|
915
|
+
const document2 = context.getDocument();
|
|
916
|
+
container.innerHTML = `<div class="studio-props-header"><span class="studio-props-header-title">Responsive Stage</span></div><button type="button" class="studio-btn" data-responsive-defaults>\uAE30\uBCF8 breakpoint \uB9CC\uB4E4\uAE30</button><div class="studio-responsive-grid">${RESPONSIVE_VIEWPORTS.map((viewport) => `<figure><figcaption>${viewport.label} \xB7 ${viewport.width}px</figcaption>${renderDocumentToSvg(compileResponsiveStage(document2, viewport.width), 0)}</figure>`).join("")}</div>`;
|
|
917
|
+
};
|
|
918
|
+
const click = (event) => {
|
|
919
|
+
if (!event.target.closest(
|
|
920
|
+
"[data-responsive-defaults]"
|
|
921
|
+
))
|
|
922
|
+
return;
|
|
923
|
+
context.replaceDocument(
|
|
924
|
+
addDefaultResponsiveVariants(context.getDocument())
|
|
925
|
+
);
|
|
926
|
+
render();
|
|
927
|
+
};
|
|
928
|
+
container.addEventListener("click", click);
|
|
929
|
+
render();
|
|
930
|
+
return () => container.removeEventListener("click", click);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
function profileAnimation(document2, sampleCount = 30, frameBudgetMs = 16.7) {
|
|
937
|
+
const plan = compileSceneDependencyPlan(document2);
|
|
938
|
+
const samples = [];
|
|
939
|
+
for (let index = 0; index < sampleCount; index += 1) {
|
|
940
|
+
const time = sampleCount === 1 ? 0 : document2.duration * index / (sampleCount - 1);
|
|
941
|
+
const started = performance.now();
|
|
942
|
+
buildScene(document2, time);
|
|
943
|
+
samples.push(performance.now() - started);
|
|
944
|
+
}
|
|
945
|
+
const totalMs = samples.reduce((sum, value) => sum + value, 0);
|
|
946
|
+
const maxMs = Math.max(0, ...samples);
|
|
947
|
+
return {
|
|
948
|
+
elementCount: plan.elementCount,
|
|
949
|
+
trackCount: plan.trackCount,
|
|
950
|
+
keyframeCount: plan.keyframeCount,
|
|
951
|
+
sampleCount,
|
|
952
|
+
totalMs,
|
|
953
|
+
averageMs: totalMs / Math.max(1, sampleCount),
|
|
954
|
+
maxMs,
|
|
955
|
+
overBudget: maxMs > frameBudgetMs
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
function createPerformanceProfilerPlugin() {
|
|
959
|
+
return {
|
|
960
|
+
manifest: {
|
|
961
|
+
id: "dev.clotho.performance",
|
|
962
|
+
name: "Scene Profiler",
|
|
963
|
+
capabilities: ["editor"],
|
|
964
|
+
editor: { inspectors: ["performance"] }
|
|
965
|
+
},
|
|
966
|
+
inspectors: {
|
|
967
|
+
performance: {
|
|
968
|
+
id: "performance",
|
|
969
|
+
label: "Scene Profiler",
|
|
970
|
+
mount(container, context) {
|
|
971
|
+
const run = () => {
|
|
972
|
+
const profile = profileAnimation(context.getDocument());
|
|
973
|
+
container.innerHTML = `<div class="studio-props-header"><span class="studio-props-header-title">Scene Profiler</span></div><dl class="studio-profile"><div><dt>Elements</dt><dd>${profile.elementCount}</dd></div><div><dt>Tracks / keyframes</dt><dd>${profile.trackCount} / ${profile.keyframeCount}</dd></div><div><dt>\uD3C9\uADE0 / \uCD5C\uB300</dt><dd>${profile.averageMs.toFixed(2)} / ${profile.maxMs.toFixed(2)} ms</dd></div></dl><p class="studio-template-status" data-error="${profile.overBudget}">${profile.overBudget ? "16.7ms frame budget\uC744 \uB118\uC5C8\uC2B5\uB2C8\uB2E4." : "60fps frame budget \uC548\uC5D0 \uC788\uC2B5\uB2C8\uB2E4."}</p><button type="button" class="studio-btn" data-run-profiler>\uB2E4\uC2DC \uCE21\uC815</button>`;
|
|
974
|
+
};
|
|
975
|
+
const click = (event) => {
|
|
976
|
+
if (event.target.closest("[data-run-profiler]"))
|
|
977
|
+
run();
|
|
978
|
+
};
|
|
979
|
+
container.addEventListener("click", click);
|
|
980
|
+
run();
|
|
981
|
+
return () => container.removeEventListener("click", click);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
function createLinterPlugin() {
|
|
988
|
+
return {
|
|
989
|
+
manifest: {
|
|
990
|
+
id: "dev.clotho.linter",
|
|
991
|
+
name: "Clotho Linter",
|
|
992
|
+
capabilities: ["editor"],
|
|
993
|
+
editor: { inspectors: ["linter"] }
|
|
994
|
+
},
|
|
995
|
+
inspectors: {
|
|
996
|
+
linter: {
|
|
997
|
+
id: "linter",
|
|
998
|
+
label: "Clotho Linter",
|
|
999
|
+
mount(container, context) {
|
|
1000
|
+
let selectedIndex = 0;
|
|
1001
|
+
const render = () => {
|
|
1002
|
+
const document2 = context.getDocument();
|
|
1003
|
+
const issues = lintDocument(document2);
|
|
1004
|
+
const selected = issues[selectedIndex] ?? issues[0];
|
|
1005
|
+
const fixed = autofixDocument(document2).document;
|
|
1006
|
+
container.innerHTML = `<div class="studio-props-header"><span class="studio-props-header-title">Clotho Linter (${issues.length})</span><button type="button" class="studio-btn" data-lint-fix ${issues.some(({ fixable }) => fixable) ? "" : "disabled"}>\uC548\uC804\uD55C \uC218\uC815 \uC801\uC6A9</button></div><ol class="studio-lint-list">${issues.map((issue, index) => `<li><button type="button" data-lint-index="${index}" data-element-id="${issue.elementId ?? ""}"><strong>${escapeHtml2(issue.ruleId)}</strong><span>${escapeHtml2(issue.message)}</span>${issue.fixable ? "<em>\uC790\uB3D9 \uC218\uC815</em>" : ""}</button></li>`).join("") || "<li>\uBB38\uC81C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.</li>"}</ol>${selected ? `<div class="studio-lint-compare"><figure><figcaption>\uD604\uC7AC</figcaption>${renderDocumentToSvg(document2, selected.time ?? 0)}</figure><figure><figcaption>\uC218\uC815 \uD6C4</figcaption>${renderDocumentToSvg(fixed, selected.time ?? 0)}</figure></div>` : ""}`;
|
|
1007
|
+
};
|
|
1008
|
+
const click = (event) => {
|
|
1009
|
+
const target = event.target;
|
|
1010
|
+
if (target.closest("[data-lint-fix]")) {
|
|
1011
|
+
context.replaceDocument(
|
|
1012
|
+
autofixDocument(context.getDocument()).document
|
|
1013
|
+
);
|
|
1014
|
+
selectedIndex = 0;
|
|
1015
|
+
render();
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
const issue = target.closest("[data-lint-index]");
|
|
1019
|
+
if (!issue) return;
|
|
1020
|
+
selectedIndex = Number(issue.dataset.lintIndex);
|
|
1021
|
+
if (issue.dataset.elementId)
|
|
1022
|
+
context.setSelection({
|
|
1023
|
+
kind: "element",
|
|
1024
|
+
elementId: issue.dataset.elementId
|
|
1025
|
+
});
|
|
1026
|
+
render();
|
|
1027
|
+
};
|
|
1028
|
+
container.addEventListener("click", click);
|
|
1029
|
+
render();
|
|
1030
|
+
return () => container.removeEventListener("click", click);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
}
|
|
1036
|
+
function escapeHtml2(value) {
|
|
1037
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
1038
|
+
}
|
|
533
1039
|
|
|
534
|
-
export { Studio, StudioMount, createLocalStorageRepository };
|
|
1040
|
+
export { RESPONSIVE_VIEWPORTS, Studio, StudioMount, addDefaultResponsiveVariants, appendStoryEdge, createLinterPlugin, createLocalStorageRepository, createPerformanceProfilerPlugin, createResponsiveInspectorPlugin, createStoryEditorPlugin, createTemplateEditorPlugin, createVisualRegressionPlugin, initialParameterValue, parameterInputValue, profileAnimation, replaceStoryNodeDocument };
|
|
535
1041
|
//# sourceMappingURL=index.js.map
|
|
536
1042
|
//# sourceMappingURL=index.js.map
|