@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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { updateCanvas, getDef, subscribe, updateMeta,
|
|
2
|
-
import { activeAppearance, animationDocumentSchema, resolveAsset } from '@kokoa/clotho';
|
|
1
|
+
import { updateCanvas, getDef, subscribe, updateMeta, mountEditorPlugins, setSelection, getSelection, setDef, undo, redo, downloadAnimationJson, importAnimation, markClean, updateSettings, isDirty, isDraft, canUndo, canRedo, uniqueChapterId, getCurrentTime, addChapter, uniqueElementId, registerExternalAsset, addElement, registerDataUriAsset, listAnimations, setDraft, saveAnimation, deleteAnimation, duplicateAnimation, createAnimation, getSelectedElementIds, deleteElement, deleteChapter, deleteEffect, updateChapter, isGroup, ungroupElement, groupElements, moveElementToEnd, moveElementToFront, reorderElement, loadAnimation, getCurrentSnapshot, setElementValueAtTime, findContainingGroup, isElementSelected, updateElementBase, createLayout, detachFromLayout, addCheckpoint, uniqueCheckpointId, deleteCheckpoint, uniqueEffectId, addEffect, removeAppearance, removeTrack, removeTrackKeyframe, setTrackKeyframe, setCurrentTime, addAppearance, jumpBack, jumpForward, getHistory, promoteDraftToSaved, toggleSelectionFor, moveGroupBy, placeholderImageUrl, groupBbox, layoutIdsFor, findLayoutCollisions, updateLocales, updateData, updateResponsive, updateDuration, updateCheckpoint, updateAppearance, updateEffect, childIdsOf } from './chunk-FAI2G3XK.js';
|
|
2
|
+
import { activeAppearance, animationDocumentSchema, bindablePropertiesFor, resolveAsset, compileDataBindings } from '@kokoa/clotho';
|
|
3
3
|
|
|
4
4
|
// src/legacy/grid.ts
|
|
5
5
|
var STORAGE_KEY = "studio.grid";
|
|
@@ -646,6 +646,7 @@ function makeDefaultElement(type, id, cx, cy, polygonSides = 6) {
|
|
|
646
646
|
rotation: 0,
|
|
647
647
|
appearances: [],
|
|
648
648
|
tracks: [],
|
|
649
|
+
bindings: [],
|
|
649
650
|
x: cx - 60,
|
|
650
651
|
y: cy - 30,
|
|
651
652
|
width: 120,
|
|
@@ -665,6 +666,7 @@ function makeDefaultElement(type, id, cx, cy, polygonSides = 6) {
|
|
|
665
666
|
rotation: 0,
|
|
666
667
|
appearances: [],
|
|
667
668
|
tracks: [],
|
|
669
|
+
bindings: [],
|
|
668
670
|
cx,
|
|
669
671
|
cy,
|
|
670
672
|
r: 36,
|
|
@@ -682,6 +684,7 @@ function makeDefaultElement(type, id, cx, cy, polygonSides = 6) {
|
|
|
682
684
|
rotation: 0,
|
|
683
685
|
appearances: [],
|
|
684
686
|
tracks: [],
|
|
687
|
+
bindings: [],
|
|
685
688
|
x1: cx - 80,
|
|
686
689
|
y1: cy,
|
|
687
690
|
x2: cx + 80,
|
|
@@ -698,6 +701,7 @@ function makeDefaultElement(type, id, cx, cy, polygonSides = 6) {
|
|
|
698
701
|
rotation: 0,
|
|
699
702
|
appearances: [],
|
|
700
703
|
tracks: [],
|
|
704
|
+
bindings: [],
|
|
701
705
|
x1: cx - 100,
|
|
702
706
|
y1: cy,
|
|
703
707
|
x2: cx + 100,
|
|
@@ -718,9 +722,12 @@ function makeDefaultElement(type, id, cx, cy, polygonSides = 6) {
|
|
|
718
722
|
rotation: 0,
|
|
719
723
|
appearances: [],
|
|
720
724
|
tracks: [],
|
|
725
|
+
bindings: [],
|
|
721
726
|
x: cx,
|
|
722
727
|
y: cy,
|
|
723
728
|
content: id,
|
|
729
|
+
translations: {},
|
|
730
|
+
references: {},
|
|
724
731
|
fontSize: 18,
|
|
725
732
|
fontWeight: 400,
|
|
726
733
|
color: "#18181b",
|
|
@@ -733,6 +740,7 @@ function makeDefaultElement(type, id, cx, cy, polygonSides = 6) {
|
|
|
733
740
|
rotation: 0,
|
|
734
741
|
appearances: [],
|
|
735
742
|
tracks: [],
|
|
743
|
+
bindings: [],
|
|
736
744
|
x: cx - 50,
|
|
737
745
|
y: cy - 50,
|
|
738
746
|
width: 100,
|
|
@@ -748,6 +756,7 @@ function makeDefaultElement(type, id, cx, cy, polygonSides = 6) {
|
|
|
748
756
|
rotation: 0,
|
|
749
757
|
appearances: [],
|
|
750
758
|
tracks: [],
|
|
759
|
+
bindings: [],
|
|
751
760
|
x: cx - 40,
|
|
752
761
|
y: cy - 40,
|
|
753
762
|
d: "M 0 0 L 80 0 L 40 80 Z",
|
|
@@ -769,6 +778,7 @@ function makeDefaultElement(type, id, cx, cy, polygonSides = 6) {
|
|
|
769
778
|
rotation: 0,
|
|
770
779
|
appearances: [],
|
|
771
780
|
tracks: [],
|
|
781
|
+
bindings: [],
|
|
772
782
|
points: pts,
|
|
773
783
|
fill: "#a5b4fc",
|
|
774
784
|
stroke: "#6366f1",
|
|
@@ -1044,8 +1054,6 @@ function renderSelectionOutline(canvasEl2, elId, snap2, byId) {
|
|
|
1044
1054
|
rect.classList.add("element-selected-outline");
|
|
1045
1055
|
return rect;
|
|
1046
1056
|
}
|
|
1047
|
-
|
|
1048
|
-
// src/legacy/canvas-preview.ts
|
|
1049
1057
|
var previewRoot = null;
|
|
1050
1058
|
var previewHandle = null;
|
|
1051
1059
|
var unsubscribePreview = null;
|
|
@@ -1074,7 +1082,7 @@ function showPreview(canvasEl2, def, options) {
|
|
|
1074
1082
|
previewRoot.style.cssText = `position:absolute;left:0;top:0;width:${def.canvas.width}px;height:${def.canvas.height}px;background:${bg};z-index:2;overflow:hidden;`;
|
|
1075
1083
|
parent.style.position = "relative";
|
|
1076
1084
|
parent.appendChild(previewRoot);
|
|
1077
|
-
void mountPreview(previewRoot, def, options);
|
|
1085
|
+
void mountPreview(previewRoot, compileDataBindings(def).document, options);
|
|
1078
1086
|
}
|
|
1079
1087
|
function hidePreview(canvasEl2) {
|
|
1080
1088
|
if (canvasEl2) canvasEl2.style.visibility = "";
|
|
@@ -1823,6 +1831,7 @@ function onMouseUp(e) {
|
|
|
1823
1831
|
rotation: 0,
|
|
1824
1832
|
appearances: [],
|
|
1825
1833
|
tracks: [],
|
|
1834
|
+
bindings: [],
|
|
1826
1835
|
fromId: connectState.fromId,
|
|
1827
1836
|
toId: elemId,
|
|
1828
1837
|
fromAnchor: connectState.fromAnchor,
|
|
@@ -2697,6 +2706,51 @@ function distributeSelected(kind) {
|
|
|
2697
2706
|
var panelEl = null;
|
|
2698
2707
|
var closedSections = /* @__PURE__ */ new Set();
|
|
2699
2708
|
var isComposingFallback = false;
|
|
2709
|
+
var DEFAULT_LOCALES = ["ko", "en"];
|
|
2710
|
+
function parseLocales(value) {
|
|
2711
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2712
|
+
return value.split(",").map((locale) => locale.trim()).filter((locale) => {
|
|
2713
|
+
const key = locale.toLowerCase();
|
|
2714
|
+
if (!locale || seen.has(key)) return false;
|
|
2715
|
+
seen.add(key);
|
|
2716
|
+
return true;
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2719
|
+
function annotationTokens(...values) {
|
|
2720
|
+
const tokens = values.flatMap(
|
|
2721
|
+
(value) => [...value.matchAll(/\{([a-z][a-z0-9_-]*)\}/g)].map((match) => match[1])
|
|
2722
|
+
);
|
|
2723
|
+
return [...new Set(tokens)];
|
|
2724
|
+
}
|
|
2725
|
+
function annotationReferenceFields(keyPrefix, values, references = {}) {
|
|
2726
|
+
const tokens = annotationTokens(...values);
|
|
2727
|
+
if (tokens.length === 0) {
|
|
2728
|
+
return [
|
|
2729
|
+
'<p class="studio-props-empty studio-annotation-hint">\uBB38\uAD6C\uC5D0 {token}\uC744 \uC785\uB825\uD558\uBA74 \uC7A5\uBA74 \uC694\uC18C\uC640 \uC5F0\uACB0\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.</p>'
|
|
2730
|
+
];
|
|
2731
|
+
}
|
|
2732
|
+
return [
|
|
2733
|
+
'<p class="studio-props-empty studio-annotation-hint">\uB300\uC0C1 element id\uB97C \uC27C\uD45C\uB85C \uAD6C\uBD84\uD558\uC138\uC694. \uC5EC\uB7EC \uC694\uC18C\uB97C \uD568\uAED8 \uAC15\uC870\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.</p>',
|
|
2734
|
+
...tokens.map((token) => {
|
|
2735
|
+
const target = references[token];
|
|
2736
|
+
const value = typeof target === "string" ? target : target?.join(", ") ?? "";
|
|
2737
|
+
return textField(
|
|
2738
|
+
`{${token}} \uB300\uC0C1`,
|
|
2739
|
+
`${keyPrefix}.reference.${token}`,
|
|
2740
|
+
value
|
|
2741
|
+
);
|
|
2742
|
+
})
|
|
2743
|
+
];
|
|
2744
|
+
}
|
|
2745
|
+
function parseReferenceTargets(value) {
|
|
2746
|
+
const targets = [
|
|
2747
|
+
...new Set(
|
|
2748
|
+
value.split(",").map((id) => id.trim()).filter(Boolean)
|
|
2749
|
+
)
|
|
2750
|
+
];
|
|
2751
|
+
if (targets.length === 0) return void 0;
|
|
2752
|
+
return targets.length === 1 ? targets[0] : targets;
|
|
2753
|
+
}
|
|
2700
2754
|
function initProperties(root) {
|
|
2701
2755
|
panelEl = root;
|
|
2702
2756
|
subscribe(render3);
|
|
@@ -2768,6 +2822,9 @@ function textField(label, key, value) {
|
|
|
2768
2822
|
<input type="text" data-prop-key="${escapeHtml2(key)}" value="${escapeHtml2(value ?? "")}" />
|
|
2769
2823
|
</label>`;
|
|
2770
2824
|
}
|
|
2825
|
+
function textareaField(label, key, value) {
|
|
2826
|
+
return `<label class="studio-field"><span>${escapeHtml2(label)}</span><textarea rows="7" spellcheck="false" data-prop-key="${escapeHtml2(key)}">${escapeHtml2(value)}</textarea></label>`;
|
|
2827
|
+
}
|
|
2771
2828
|
function numberField(label, key, value, step = 1) {
|
|
2772
2829
|
return `<label class="studio-field">
|
|
2773
2830
|
<span>${escapeHtml2(label)}</span>
|
|
@@ -2810,6 +2867,11 @@ function renderInner() {
|
|
|
2810
2867
|
const metaBody = [
|
|
2811
2868
|
textField("title", "meta.title", def.title),
|
|
2812
2869
|
textField("description", "meta.description", def.description),
|
|
2870
|
+
textField(
|
|
2871
|
+
"\uBB38\uC11C \uC5B8\uC5B4 (\uC27C\uD45C\uB85C \uAD6C\uBD84)",
|
|
2872
|
+
"meta.locales",
|
|
2873
|
+
(def.locales ?? DEFAULT_LOCALES).join(", ")
|
|
2874
|
+
),
|
|
2813
2875
|
numberField("duration (ms)", "meta.duration", def.duration, 100),
|
|
2814
2876
|
numberField("canvas.width", "canvas.width", def.canvas.width),
|
|
2815
2877
|
numberField("canvas.height", "canvas.height", def.canvas.height),
|
|
@@ -2817,7 +2879,18 @@ function renderInner() {
|
|
|
2817
2879
|
"canvas.background",
|
|
2818
2880
|
"canvas.background",
|
|
2819
2881
|
def.canvas.background
|
|
2820
|
-
)
|
|
2882
|
+
),
|
|
2883
|
+
textareaField(
|
|
2884
|
+
"\uC0D8\uD50C \uB370\uC774\uD130 (JSON)",
|
|
2885
|
+
"meta.data",
|
|
2886
|
+
JSON.stringify(def.data, null, 2)
|
|
2887
|
+
),
|
|
2888
|
+
textareaField(
|
|
2889
|
+
"Responsive variants (JSON)",
|
|
2890
|
+
"meta.responsive",
|
|
2891
|
+
JSON.stringify(def.responsive ?? [], null, 2)
|
|
2892
|
+
),
|
|
2893
|
+
`<p class="studio-props-empty">JSON Pointer\uB85C \uC694\uC18C \uC18D\uC131\uC5D0 \uC5F0\uACB0\uD569\uB2C8\uB2E4. \uC774 \uB370\uC774\uD130\uB294 \uBBF8\uB9AC\uBCF4\uAE30\uC640 \uB0B4\uBCF4\uB0B4\uAE30\uC5D0 \uD568\uAED8 \uC800\uC7A5\uB429\uB2C8\uB2E4.</p>`
|
|
2821
2894
|
].join("");
|
|
2822
2895
|
const settingsHeader = `<span class="studio-props-header-title">\uC124\uC815</span>`;
|
|
2823
2896
|
const settingsBody = [
|
|
@@ -2845,10 +2918,61 @@ function renderInner() {
|
|
|
2845
2918
|
]
|
|
2846
2919
|
)
|
|
2847
2920
|
].join("");
|
|
2921
|
+
const checkpoints = def.checkpoints.map((checkpoint, index) => {
|
|
2922
|
+
const specific = checkpoint.interaction === "choice" ? textField(
|
|
2923
|
+
"\uC120\uD0DD\uC9C0 (value:label, \uC27C\uD45C \uAD6C\uBD84)",
|
|
2924
|
+
`checkpoint.${index}.options`,
|
|
2925
|
+
checkpoint.options.map(({ value, label }) => `${value}:${label}`).join(", ")
|
|
2926
|
+
) : checkpoint.interaction === "select-element" ? textField(
|
|
2927
|
+
"\uC120\uD0DD \uAC00\uB2A5\uD55C element id",
|
|
2928
|
+
`checkpoint.${index}.elementIds`,
|
|
2929
|
+
checkpoint.elementIds.join(", ")
|
|
2930
|
+
) : checkpoint.interaction === "number-input" ? [
|
|
2931
|
+
numberField(
|
|
2932
|
+
"\uCD5C\uC19F\uAC12",
|
|
2933
|
+
`checkpoint.${index}.min`,
|
|
2934
|
+
checkpoint.min
|
|
2935
|
+
),
|
|
2936
|
+
numberField(
|
|
2937
|
+
"\uCD5C\uB313\uAC12",
|
|
2938
|
+
`checkpoint.${index}.max`,
|
|
2939
|
+
checkpoint.max
|
|
2940
|
+
),
|
|
2941
|
+
numberField(
|
|
2942
|
+
"\uAC04\uACA9",
|
|
2943
|
+
`checkpoint.${index}.step`,
|
|
2944
|
+
checkpoint.step
|
|
2945
|
+
)
|
|
2946
|
+
].join("") : "";
|
|
2947
|
+
const predicate = "predicate" in checkpoint && checkpoint.predicate?.type === "equals" ? textField(
|
|
2948
|
+
"\uC815\uB2F5 (equals)",
|
|
2949
|
+
`checkpoint.${index}.answer`,
|
|
2950
|
+
String(checkpoint.predicate.value)
|
|
2951
|
+
) : "";
|
|
2952
|
+
return `<div class="studio-checkpoint-card" data-checkpoint-id="${escapeHtml2(checkpoint.id)}">
|
|
2953
|
+
<div class="studio-props-header"><span class="studio-props-header-title">${escapeHtml2(checkpoint.id)}</span><button type="button" class="studio-btn studio-btn-danger" data-delete-checkpoint="${escapeHtml2(checkpoint.id)}">\uC0AD\uC81C</button></div>
|
|
2954
|
+
${numberField("time (ms)", `checkpoint.${index}.time`, checkpoint.time, 50)}
|
|
2955
|
+
${textField("\uC9C8\uBB38", `checkpoint.${index}.prompt`, checkpoint.prompt)}
|
|
2956
|
+
${selectField(
|
|
2957
|
+
"\uC0C1\uD638\uC791\uC6A9",
|
|
2958
|
+
`checkpoint.${index}.interaction`,
|
|
2959
|
+
checkpoint.interaction,
|
|
2960
|
+
[
|
|
2961
|
+
{ value: "continue", label: "\uACC4\uC18D" },
|
|
2962
|
+
{ value: "choice", label: "\uC120\uD0DD\uC9C0" },
|
|
2963
|
+
{ value: "select-element", label: "\uC694\uC18C \uC120\uD0DD" },
|
|
2964
|
+
{ value: "number-input", label: "\uC22B\uC790 \uC785\uB825" }
|
|
2965
|
+
]
|
|
2966
|
+
)}
|
|
2967
|
+
${checkboxField("\uC751\uB2F5 \uD544\uC218", `checkpoint.${index}.required`, checkpoint.required)}
|
|
2968
|
+
${specific}${predicate}
|
|
2969
|
+
</div>`;
|
|
2970
|
+
}).join("");
|
|
2848
2971
|
panelEl.innerHTML = `
|
|
2849
2972
|
${timeHint}
|
|
2850
2973
|
${section("meta", metaHeader, metaBody)}
|
|
2851
2974
|
${section("settings", settingsHeader, settingsBody)}
|
|
2975
|
+
${section("checkpoints", `<span class="studio-props-header-title">Checkpoint (${def.checkpoints.length})</span>`, `${checkpoints}<button type="button" class="studio-btn" data-add-checkpoint>\uFF0B \uD604\uC7AC \uC2DC\uAC04\uC5D0 checkpoint \uCD94\uAC00</button>`)}
|
|
2852
2976
|
<div class="studio-props-header" style="margin-top:0.6rem"><span class="studio-props-header-title">\uBAA9\uCC28 (${def.chapters.length})</span></div>
|
|
2853
2977
|
<button type="button" class="studio-btn" data-add-chapter>\uFF0B \uD604\uC7AC \uC2DC\uAC04\uC5D0 chapter \uCD94\uAC00</button>
|
|
2854
2978
|
<div class="studio-props-header" style="margin-top:0.6rem"><span class="studio-props-header-title">\uD6A8\uACFC (${def.effects.length})</span></div>
|
|
@@ -2866,6 +2990,10 @@ function renderInner() {
|
|
|
2866
2990
|
if (sel.kind === "elements") {
|
|
2867
2991
|
const alignBtn = (kind, label, title) => `<button type="button" class="studio-btn studio-align-btn" data-align="${kind}" title="${escapeHtml2(title)}" aria-label="${escapeHtml2(title)}">${label}</button>`;
|
|
2868
2992
|
const distributeDisabled = sel.elementIds.length < 3 ? "disabled" : "";
|
|
2993
|
+
const layoutIds = layoutIdsFor(sel.elementIds);
|
|
2994
|
+
const collisions = findLayoutCollisions(def).filter(
|
|
2995
|
+
({ firstId, secondId }) => sel.elementIds.includes(firstId) || sel.elementIds.includes(secondId)
|
|
2996
|
+
);
|
|
2869
2997
|
panelEl.innerHTML = `
|
|
2870
2998
|
${timeHint}
|
|
2871
2999
|
<div class="studio-props-header"><span class="studio-props-header-title">\uB2E4\uC911 \uC120\uD0DD</span><span class="studio-props-header-type">${sel.elementIds.length} elements</span></div>
|
|
@@ -2889,6 +3017,16 @@ function renderInner() {
|
|
|
2889
3017
|
<button type="button" class="studio-btn studio-align-btn" data-distribute="vertical" title="\uC138\uB85C \uADE0\uB4F1 \uBD84\uD3EC" ${distributeDisabled}>\u2195</button>
|
|
2890
3018
|
</div>
|
|
2891
3019
|
</div>
|
|
3020
|
+
<div class="studio-align-section studio-layout-section">
|
|
3021
|
+
<div class="studio-align-title">Constraint Layout</div>
|
|
3022
|
+
<div class="studio-align-row">
|
|
3023
|
+
<button type="button" class="studio-btn" data-create-layout="row">\uAC00\uB85C</button>
|
|
3024
|
+
<button type="button" class="studio-btn" data-create-layout="column">\uC138\uB85C</button>
|
|
3025
|
+
<button type="button" class="studio-btn" data-create-layout="grid">\uACA9\uC790</button>
|
|
3026
|
+
</div>
|
|
3027
|
+
${layoutIds.length > 0 ? `<p class="studio-layout-status">\uC801\uC6A9 \uC911: ${layoutIds.map(escapeHtml2).join(", ")}</p><button type="button" class="studio-btn" data-detach-layout>\uD604\uC7AC \uC88C\uD45C\uB85C \uACE0\uC815</button>` : '<p class="studio-layout-status">\uC120\uD0DD\uD55C \uC694\uC18C\uC5D0 \uC801\uC6A9\uB41C layout\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.</p>'}
|
|
3028
|
+
${collisions.length > 0 ? `<p class="studio-layout-collision" role="alert">\uACB9\uCE68: ${collisions.map(({ firstId, secondId }) => `${escapeHtml2(firstId)} \u2194 ${escapeHtml2(secondId)}`).join(", ")}</p>` : ""}
|
|
3029
|
+
</div>
|
|
2892
3030
|
<ul style="font-family:var(--font-mono);font-size:0.72rem;color:var(--color-fg-muted);padding-left:1rem;margin:0.6rem 0 0">
|
|
2893
3031
|
${sel.elementIds.map((id) => `<li>${escapeHtml2(id)}</li>`).join("")}
|
|
2894
3032
|
</ul>
|
|
@@ -2910,12 +3048,14 @@ function renderInner() {
|
|
|
2910
3048
|
setSelection({ kind: "none" });
|
|
2911
3049
|
return;
|
|
2912
3050
|
}
|
|
3051
|
+
const chapterReferences = ch.references;
|
|
2913
3052
|
panelEl.innerHTML = `
|
|
2914
3053
|
${timeHint}
|
|
2915
3054
|
<div class="studio-props-header"><span class="studio-props-header-title">${escapeHtml2(ch.id)}</span><span class="studio-props-header-type">chapter</span></div>
|
|
2916
3055
|
${numberField("time (ms)", "chapter.time", ch.time, 50)}
|
|
2917
3056
|
${textField("label", "chapter.label", ch.label)}
|
|
2918
3057
|
${textField("subtitle", "chapter.subtitle", ch.subtitle)}
|
|
3058
|
+
${annotationReferenceFields("chapter", [ch.label, ch.subtitle], chapterReferences).join("")}
|
|
2919
3059
|
<button type="button" class="studio-btn studio-btn-danger" data-delete-chapter style="margin-top:0.6rem">\u{1F5D1} chapter \uC0AD\uC81C</button>
|
|
2920
3060
|
`;
|
|
2921
3061
|
return;
|
|
@@ -2969,9 +3109,10 @@ function renderElementForm(def, el) {
|
|
|
2969
3109
|
`;
|
|
2970
3110
|
return;
|
|
2971
3111
|
}
|
|
2972
|
-
const baseFields = renderBaseFields(el);
|
|
3112
|
+
const baseFields = renderBaseFields(def, el);
|
|
2973
3113
|
const appearances = renderAppearances(def, el);
|
|
2974
3114
|
const tracks = renderTracks(el);
|
|
3115
|
+
const bindings = renderBindings(el);
|
|
2975
3116
|
const baseHeader = `<span class="studio-props-header-title">${escapeHtml2(el.id)}</span><span class="studio-props-header-type">${escapeHtml2(el.type)}</span>`;
|
|
2976
3117
|
const apHeader = `<span class="studio-props-header-title">\uCD9C\uD604 (Appearances)</span><button type="button" class="studio-btn studio-btn-small" data-add-appearance>\uFF0B</button>`;
|
|
2977
3118
|
const tracksHeader = `<span class="studio-props-header-title">\uD0A4\uD504\uB808\uC784 \uD2B8\uB799 (${el.tracks.length})</span>`;
|
|
@@ -2980,13 +3121,46 @@ function renderElementForm(def, el) {
|
|
|
2980
3121
|
${section("el-base", baseHeader, baseFields)}
|
|
2981
3122
|
${section("el-appearances", apHeader, appearances)}
|
|
2982
3123
|
${section("el-tracks", tracksHeader, tracks)}
|
|
3124
|
+
${section("el-bindings", `<span class="studio-props-header-title">\uB370\uC774\uD130 \uC5F0\uACB0 (${el.bindings.length})</span>`, bindings)}
|
|
2983
3125
|
<div class="studio-props-empty" style="font-size:0.72rem;margin-top:0.5rem">
|
|
2984
3126
|
base \uC18D\uC131\uC744 \uBCC0\uACBD\uD558\uBA74 \u2192 t=${getCurrentTime()} ms \uC5D0 keyframe \uCD94\uAC00<br/>
|
|
2985
3127
|
\uD2B8\uB799\uC774 \uC5C6\uB294 \uC18D\uC131\uC740 base \uAC12\uC774 \uD56D\uC0C1 \uC0AC\uC6A9\uB428
|
|
2986
3128
|
</div>
|
|
2987
3129
|
`;
|
|
2988
3130
|
}
|
|
2989
|
-
function
|
|
3131
|
+
function renderBindings(el) {
|
|
3132
|
+
const properties = bindablePropertiesFor(el);
|
|
3133
|
+
const rows = el.bindings.map(
|
|
3134
|
+
(binding, index) => `<div class="studio-appearance-row">
|
|
3135
|
+
<div class="studio-appearance-row-head"><span class="studio-appearance-row-title">#${index + 1}</span><button type="button" class="studio-btn studio-btn-small studio-btn-danger" data-delete-binding="${index}">\u2715</button></div>
|
|
3136
|
+
${selectField(
|
|
3137
|
+
"\uC18D\uC131",
|
|
3138
|
+
`binding.${index}.property`,
|
|
3139
|
+
binding.property,
|
|
3140
|
+
properties.map((value) => ({ value }))
|
|
3141
|
+
)}
|
|
3142
|
+
${textField("JSON Pointer", `binding.${index}.pointer`, binding.pointer)}
|
|
3143
|
+
${selectField(
|
|
3144
|
+
"\uD45C\uD604 \uBC29\uC2DD",
|
|
3145
|
+
`binding.${index}.formatter`,
|
|
3146
|
+
binding.formatter,
|
|
3147
|
+
[
|
|
3148
|
+
"identity",
|
|
3149
|
+
"string",
|
|
3150
|
+
"number",
|
|
3151
|
+
"fixed",
|
|
3152
|
+
"percent",
|
|
3153
|
+
"uppercase",
|
|
3154
|
+
"lowercase",
|
|
3155
|
+
"color"
|
|
3156
|
+
].map((value) => ({ value }))
|
|
3157
|
+
)}
|
|
3158
|
+
${textField("\uB300\uCCB4 \uAC12", `binding.${index}.fallback`, binding.fallback === void 0 ? "" : String(binding.fallback))}
|
|
3159
|
+
</div>`
|
|
3160
|
+
).join("");
|
|
3161
|
+
return `${rows || '<p class="studio-props-empty">\uC5F0\uACB0\uB41C \uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.</p>'}<button type="button" class="studio-btn" data-add-binding ${properties.length === 0 ? "disabled" : ""}>\uFF0B \uB370\uC774\uD130 \uC5F0\uACB0</button>`;
|
|
3162
|
+
}
|
|
3163
|
+
function renderBaseFields(def, el) {
|
|
2990
3164
|
const numberFields = [];
|
|
2991
3165
|
const colorFields = [];
|
|
2992
3166
|
const textFields = [];
|
|
@@ -3099,8 +3273,35 @@ function renderBaseFields(el) {
|
|
|
3099
3273
|
value: e.rotation,
|
|
3100
3274
|
step: 5
|
|
3101
3275
|
});
|
|
3276
|
+
const localizedTextFields = (() => {
|
|
3277
|
+
if (el.type !== "text") return [];
|
|
3278
|
+
const localized = el;
|
|
3279
|
+
const documentLocales = def.locales ?? DEFAULT_LOCALES;
|
|
3280
|
+
const locales = localized.locales ?? documentLocales;
|
|
3281
|
+
return [
|
|
3282
|
+
textField(
|
|
3283
|
+
"\uC774 \uC694\uC18C\uC758 \uC5B8\uC5B4 (\uBE44\uC6B0\uBA74 \uBB38\uC11C \uC124\uC815 \uC0AC\uC6A9)",
|
|
3284
|
+
"el.locales",
|
|
3285
|
+
localized.locales?.join(", ") ?? ""
|
|
3286
|
+
),
|
|
3287
|
+
`<p class="studio-props-empty studio-i18n-hint">\uAE30\uBCF8 \uBB38\uAD6C\uB294 content\uC785\uB2C8\uB2E4. \uBC88\uC5ED\uC774 \uC5C6\uAC70\uB098 \uD604\uC7AC \uC5B8\uC5B4\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC73C\uBA74 \uAE30\uBCF8 \uBB38\uAD6C\uB97C \uD45C\uC2DC\uD569\uB2C8\uB2E4.</p>`,
|
|
3288
|
+
...locales.map(
|
|
3289
|
+
(locale) => textField(
|
|
3290
|
+
`${locale} \uBC88\uC5ED`,
|
|
3291
|
+
`el.translation.${locale}`,
|
|
3292
|
+
localized.translations?.[locale] ?? ""
|
|
3293
|
+
)
|
|
3294
|
+
),
|
|
3295
|
+
...annotationReferenceFields(
|
|
3296
|
+
"el",
|
|
3297
|
+
[el.content, ...Object.values(localized.translations ?? {})],
|
|
3298
|
+
localized.references
|
|
3299
|
+
)
|
|
3300
|
+
];
|
|
3301
|
+
})();
|
|
3102
3302
|
const html = [
|
|
3103
3303
|
...textFields.map((f) => textField(f.label, `el.${f.key}`, f.value)),
|
|
3304
|
+
...localizedTextFields,
|
|
3104
3305
|
...numberFields.map(
|
|
3105
3306
|
(f) => numberField(f.label, `el.${f.key}`, f.value, f.step)
|
|
3106
3307
|
),
|
|
@@ -3170,7 +3371,7 @@ function onInput(e) {
|
|
|
3170
3371
|
if (!key) return;
|
|
3171
3372
|
if (target.type === "text" && e.isComposing) return;
|
|
3172
3373
|
if (target.type === "text" && isComposingFallback) return;
|
|
3173
|
-
if (target.type === "color") {
|
|
3374
|
+
if (target instanceof HTMLInputElement && target.type === "color") {
|
|
3174
3375
|
const textInput = target.parentElement?.querySelector(
|
|
3175
3376
|
`input[type="text"][data-prop-key="${CSS.escape(key)}"]`
|
|
3176
3377
|
);
|
|
@@ -3178,8 +3379,10 @@ function onInput(e) {
|
|
|
3178
3379
|
return;
|
|
3179
3380
|
}
|
|
3180
3381
|
let value = target.value;
|
|
3181
|
-
if (target.type === "number")
|
|
3182
|
-
|
|
3382
|
+
if (target instanceof HTMLInputElement && target.type === "number")
|
|
3383
|
+
value = Number(target.value);
|
|
3384
|
+
else if (target instanceof HTMLInputElement && target.type === "checkbox")
|
|
3385
|
+
value = target.checked;
|
|
3183
3386
|
apply(key, value);
|
|
3184
3387
|
}
|
|
3185
3388
|
function onChange(e) {
|
|
@@ -3198,7 +3401,26 @@ function apply(key, value) {
|
|
|
3198
3401
|
if (key === "meta.title") updateMeta({ title: String(value) });
|
|
3199
3402
|
else if (key === "meta.description")
|
|
3200
3403
|
updateMeta({ description: String(value) });
|
|
3201
|
-
else if (key === "meta.
|
|
3404
|
+
else if (key === "meta.locales") {
|
|
3405
|
+
const locales = parseLocales(String(value));
|
|
3406
|
+
if (locales.length > 0) updateLocales(locales);
|
|
3407
|
+
} else if (key === "meta.data") {
|
|
3408
|
+
try {
|
|
3409
|
+
const parsed = JSON.parse(String(value));
|
|
3410
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed))
|
|
3411
|
+
updateData(parsed);
|
|
3412
|
+
} catch {
|
|
3413
|
+
}
|
|
3414
|
+
} else if (key === "meta.responsive") {
|
|
3415
|
+
try {
|
|
3416
|
+
const parsed = JSON.parse(String(value));
|
|
3417
|
+
if (Array.isArray(parsed))
|
|
3418
|
+
updateResponsive(
|
|
3419
|
+
parsed
|
|
3420
|
+
);
|
|
3421
|
+
} catch {
|
|
3422
|
+
}
|
|
3423
|
+
} else if (key === "meta.duration") updateDuration(Number(value));
|
|
3202
3424
|
else if (key === "canvas.width") updateCanvas({ width: Number(value) });
|
|
3203
3425
|
else if (key === "canvas.height") updateCanvas({ height: Number(value) });
|
|
3204
3426
|
else if (key === "canvas.background")
|
|
@@ -3214,11 +3436,86 @@ function apply(key, value) {
|
|
|
3214
3436
|
updateSettings({
|
|
3215
3437
|
chapterListPosition: String(value)
|
|
3216
3438
|
});
|
|
3217
|
-
else if (key.startsWith("
|
|
3439
|
+
else if (key.startsWith("checkpoint.")) {
|
|
3440
|
+
const [, indexText, property] = key.split(".");
|
|
3441
|
+
const checkpoint = def.checkpoints[Number(indexText)];
|
|
3442
|
+
if (!checkpoint || !property) return;
|
|
3443
|
+
if (property === "interaction") {
|
|
3444
|
+
const base = {
|
|
3445
|
+
id: checkpoint.id,
|
|
3446
|
+
time: checkpoint.time,
|
|
3447
|
+
prompt: checkpoint.prompt,
|
|
3448
|
+
required: checkpoint.required
|
|
3449
|
+
};
|
|
3450
|
+
const interaction = String(value);
|
|
3451
|
+
const replacement = interaction === "choice" ? {
|
|
3452
|
+
...base,
|
|
3453
|
+
interaction,
|
|
3454
|
+
options: [{ value: "option", label: "\uC120\uD0DD\uC9C0" }]
|
|
3455
|
+
} : interaction === "select-element" ? {
|
|
3456
|
+
...base,
|
|
3457
|
+
interaction,
|
|
3458
|
+
elementIds: [def.elements[0]?.id ?? "element"]
|
|
3459
|
+
} : interaction === "number-input" ? { ...base, interaction } : { ...base, interaction: "continue" };
|
|
3460
|
+
updateCheckpoint(checkpoint.id, replacement);
|
|
3461
|
+
} else if (property === "time")
|
|
3462
|
+
updateCheckpoint(checkpoint.id, { time: Number(value) });
|
|
3463
|
+
else if (property === "prompt")
|
|
3464
|
+
updateCheckpoint(checkpoint.id, { prompt: String(value) });
|
|
3465
|
+
else if (property === "required")
|
|
3466
|
+
updateCheckpoint(checkpoint.id, { required: Boolean(value) });
|
|
3467
|
+
else if (property === "options" && checkpoint.interaction === "choice") {
|
|
3468
|
+
const options = String(value).split(",").map((entry) => entry.trim()).filter(Boolean).map((entry) => {
|
|
3469
|
+
const [optionValue, ...label] = entry.split(":");
|
|
3470
|
+
return {
|
|
3471
|
+
value: optionValue || "option",
|
|
3472
|
+
label: label.join(":") || optionValue || "\uC120\uD0DD\uC9C0"
|
|
3473
|
+
};
|
|
3474
|
+
});
|
|
3475
|
+
if (options.length > 0) updateCheckpoint(checkpoint.id, { options });
|
|
3476
|
+
} else if (property === "elementIds" && checkpoint.interaction === "select-element") {
|
|
3477
|
+
const elementIds = String(value).split(",").map((id) => id.trim()).filter(Boolean);
|
|
3478
|
+
if (elementIds.length > 0)
|
|
3479
|
+
updateCheckpoint(checkpoint.id, { elementIds });
|
|
3480
|
+
} else if (["min", "max", "step"].includes(property) && checkpoint.interaction === "number-input") {
|
|
3481
|
+
updateCheckpoint(checkpoint.id, { [property]: Number(value) });
|
|
3482
|
+
} else if (property === "answer" && checkpoint.interaction !== "continue") {
|
|
3483
|
+
const answer = checkpoint.interaction === "number-input" ? Number(value) : String(value);
|
|
3484
|
+
updateCheckpoint(checkpoint.id, {
|
|
3485
|
+
predicate: { type: "equals", value: answer }
|
|
3486
|
+
});
|
|
3487
|
+
}
|
|
3488
|
+
} else if (key.startsWith("el.") && sel.kind === "element") {
|
|
3218
3489
|
const prop = key.slice(3);
|
|
3219
3490
|
const time = getCurrentTime();
|
|
3220
3491
|
const el = def.elements.find((e) => e.id === sel.elementId);
|
|
3221
3492
|
if (!el) return;
|
|
3493
|
+
if (prop === "locales" && el.type === "text") {
|
|
3494
|
+
const locales = parseLocales(String(value));
|
|
3495
|
+
updateElementBase(sel.elementId, {
|
|
3496
|
+
locales: locales.length > 0 ? locales : void 0
|
|
3497
|
+
});
|
|
3498
|
+
return;
|
|
3499
|
+
}
|
|
3500
|
+
if (prop.startsWith("translation.") && el.type === "text") {
|
|
3501
|
+
const locale = prop.slice("translation.".length);
|
|
3502
|
+
const localized = el;
|
|
3503
|
+
const translations = { ...localized.translations ?? {} };
|
|
3504
|
+
if (String(value)) translations[locale] = String(value);
|
|
3505
|
+
else delete translations[locale];
|
|
3506
|
+
updateElementBase(sel.elementId, { translations });
|
|
3507
|
+
return;
|
|
3508
|
+
}
|
|
3509
|
+
if (prop.startsWith("reference.") && el.type === "text") {
|
|
3510
|
+
const token = prop.slice("reference.".length);
|
|
3511
|
+
const annotated = el;
|
|
3512
|
+
const references = { ...annotated.references ?? {} };
|
|
3513
|
+
const targets = parseReferenceTargets(String(value));
|
|
3514
|
+
if (targets) references[token] = targets;
|
|
3515
|
+
else delete references[token];
|
|
3516
|
+
updateElementBase(sel.elementId, { references });
|
|
3517
|
+
return;
|
|
3518
|
+
}
|
|
3222
3519
|
if (prop === "polygonSides" && el.type === "polygon") {
|
|
3223
3520
|
const points = el.points.trim().split(/\s+/).map((point) => point.split(",").map(Number)).filter(([x, y]) => Number.isFinite(x) && Number.isFinite(y));
|
|
3224
3521
|
if (points.length < 3) return;
|
|
@@ -3243,6 +3540,18 @@ function apply(key, value) {
|
|
|
3243
3540
|
} else {
|
|
3244
3541
|
updateElementBase(sel.elementId, { [prop]: value });
|
|
3245
3542
|
}
|
|
3543
|
+
} else if (key.startsWith("binding.") && sel.kind === "element") {
|
|
3544
|
+
const [, indexText, property] = key.split(".");
|
|
3545
|
+
const el = def.elements.find((item) => item.id === sel.elementId);
|
|
3546
|
+
const binding = el?.bindings[Number(indexText)];
|
|
3547
|
+
if (!el || !binding || !property) return;
|
|
3548
|
+
const bindings = el.bindings.map(
|
|
3549
|
+
(item, index) => index === Number(indexText) ? {
|
|
3550
|
+
...item,
|
|
3551
|
+
[property]: property === "fallback" ? parseBindingFallback(String(value)) : String(value)
|
|
3552
|
+
} : item
|
|
3553
|
+
);
|
|
3554
|
+
updateElementBase(el.id, { bindings });
|
|
3246
3555
|
} else if (key.startsWith("ap.") && sel.kind === "element") {
|
|
3247
3556
|
const [, idxStr, prop] = key.split(".");
|
|
3248
3557
|
const idx = Number(idxStr);
|
|
@@ -3260,6 +3569,16 @@ function apply(key, value) {
|
|
|
3260
3569
|
updateChapter(sel.chapterId, { label: String(value) });
|
|
3261
3570
|
} else if (key === "chapter.subtitle" && sel.kind === "chapter") {
|
|
3262
3571
|
updateChapter(sel.chapterId, { subtitle: String(value) });
|
|
3572
|
+
} else if (key.startsWith("chapter.reference.") && sel.kind === "chapter") {
|
|
3573
|
+
const chapter = def.chapters.find(({ id }) => id === sel.chapterId);
|
|
3574
|
+
if (!chapter) return;
|
|
3575
|
+
const token = key.slice("chapter.reference.".length);
|
|
3576
|
+
const annotated = chapter;
|
|
3577
|
+
const references = { ...annotated.references ?? {} };
|
|
3578
|
+
const targets = parseReferenceTargets(String(value));
|
|
3579
|
+
if (targets) references[token] = targets;
|
|
3580
|
+
else delete references[token];
|
|
3581
|
+
updateChapter(sel.chapterId, { references });
|
|
3263
3582
|
} else if (key.startsWith("effect.") && sel.kind === "effect") {
|
|
3264
3583
|
const prop = key.slice(7);
|
|
3265
3584
|
const patch = {};
|
|
@@ -3271,11 +3590,44 @@ function apply(key, value) {
|
|
|
3271
3590
|
updateEffect(sel.effectId, patch);
|
|
3272
3591
|
}
|
|
3273
3592
|
}
|
|
3593
|
+
function parseBindingFallback(value) {
|
|
3594
|
+
if (value === "") return void 0;
|
|
3595
|
+
if (value === "true") return true;
|
|
3596
|
+
if (value === "false") return false;
|
|
3597
|
+
const number = Number(value);
|
|
3598
|
+
return Number.isFinite(number) && value.trim() !== "" ? number : value;
|
|
3599
|
+
}
|
|
3274
3600
|
function onClick2(e) {
|
|
3275
3601
|
const target = e.target;
|
|
3276
3602
|
const def = getDef();
|
|
3277
3603
|
if (!def) return;
|
|
3278
3604
|
const sel = getSelection();
|
|
3605
|
+
if (target.closest("[data-add-binding]") && sel.kind === "element") {
|
|
3606
|
+
const el = def.elements.find((item) => item.id === sel.elementId);
|
|
3607
|
+
if (!el) return;
|
|
3608
|
+
const property = bindablePropertiesFor(el).find(
|
|
3609
|
+
(candidate) => !el.bindings.some((binding) => binding.property === candidate)
|
|
3610
|
+
);
|
|
3611
|
+
if (property)
|
|
3612
|
+
updateElementBase(el.id, {
|
|
3613
|
+
bindings: [
|
|
3614
|
+
...el.bindings,
|
|
3615
|
+
{ property, pointer: "", formatter: "identity" }
|
|
3616
|
+
]
|
|
3617
|
+
});
|
|
3618
|
+
return;
|
|
3619
|
+
}
|
|
3620
|
+
const deleteBinding = target.closest("[data-delete-binding]");
|
|
3621
|
+
if (deleteBinding && sel.kind === "element") {
|
|
3622
|
+
const el = def.elements.find((item) => item.id === sel.elementId);
|
|
3623
|
+
if (el)
|
|
3624
|
+
updateElementBase(el.id, {
|
|
3625
|
+
bindings: el.bindings.filter(
|
|
3626
|
+
(_, index) => index !== Number(deleteBinding.dataset.deleteBinding)
|
|
3627
|
+
)
|
|
3628
|
+
});
|
|
3629
|
+
return;
|
|
3630
|
+
}
|
|
3279
3631
|
const alignBtn = target.closest("[data-align]");
|
|
3280
3632
|
if (alignBtn) {
|
|
3281
3633
|
alignSelected(alignBtn.dataset.align);
|
|
@@ -3286,6 +3638,37 @@ function onClick2(e) {
|
|
|
3286
3638
|
distributeSelected(distBtn.dataset.distribute);
|
|
3287
3639
|
return;
|
|
3288
3640
|
}
|
|
3641
|
+
const createLayoutButton = target.closest(
|
|
3642
|
+
"[data-create-layout]"
|
|
3643
|
+
);
|
|
3644
|
+
if (createLayoutButton && sel.kind === "elements") {
|
|
3645
|
+
createLayout(
|
|
3646
|
+
sel.elementIds,
|
|
3647
|
+
createLayoutButton.dataset.createLayout
|
|
3648
|
+
);
|
|
3649
|
+
return;
|
|
3650
|
+
}
|
|
3651
|
+
if (target.closest("[data-detach-layout]") && sel.kind === "elements") {
|
|
3652
|
+
detachFromLayout(sel.elementIds);
|
|
3653
|
+
return;
|
|
3654
|
+
}
|
|
3655
|
+
if (target.closest("[data-add-checkpoint]")) {
|
|
3656
|
+
addCheckpoint({
|
|
3657
|
+
id: uniqueCheckpointId(),
|
|
3658
|
+
time: getCurrentTime(),
|
|
3659
|
+
prompt: "\uACC4\uC18D \uC9C4\uD589\uD560\uAE4C\uC694?",
|
|
3660
|
+
required: true,
|
|
3661
|
+
interaction: "continue"
|
|
3662
|
+
});
|
|
3663
|
+
return;
|
|
3664
|
+
}
|
|
3665
|
+
const deleteCheckpointButton = target.closest(
|
|
3666
|
+
"[data-delete-checkpoint]"
|
|
3667
|
+
);
|
|
3668
|
+
if (deleteCheckpointButton?.dataset.deleteCheckpoint) {
|
|
3669
|
+
deleteCheckpoint(deleteCheckpointButton.dataset.deleteCheckpoint);
|
|
3670
|
+
return;
|
|
3671
|
+
}
|
|
3289
3672
|
if (target.closest("[data-ungroup]") && sel.kind === "element") {
|
|
3290
3673
|
ungroupElement(sel.elementId);
|
|
3291
3674
|
return;
|
|
@@ -3305,7 +3688,8 @@ function onClick2(e) {
|
|
|
3305
3688
|
id,
|
|
3306
3689
|
time: getCurrentTime(),
|
|
3307
3690
|
label: `Chapter ${id.split("-")[1]}`,
|
|
3308
|
-
subtitle: ""
|
|
3691
|
+
subtitle: "",
|
|
3692
|
+
references: {}
|
|
3309
3693
|
});
|
|
3310
3694
|
return;
|
|
3311
3695
|
}
|
|
@@ -3440,7 +3824,8 @@ function initTimeline(tracksRoot, addChapterBtn, elementTracks) {
|
|
|
3440
3824
|
id,
|
|
3441
3825
|
time: newTime,
|
|
3442
3826
|
label: `Chapter ${id.split("-")[1]}`,
|
|
3443
|
-
subtitle: ""
|
|
3827
|
+
subtitle: "",
|
|
3828
|
+
references: {}
|
|
3444
3829
|
});
|
|
3445
3830
|
setSelection({ kind: "chapter", chapterId: id });
|
|
3446
3831
|
});
|
|
@@ -15291,6 +15676,7 @@ var IconLibraryDialog = class _IconLibraryDialog {
|
|
|
15291
15676
|
rotation: 0,
|
|
15292
15677
|
appearances: [],
|
|
15293
15678
|
tracks: [],
|
|
15679
|
+
bindings: [],
|
|
15294
15680
|
x: cx - 32,
|
|
15295
15681
|
y: cy - 32,
|
|
15296
15682
|
width: 64,
|
|
@@ -15351,6 +15737,7 @@ async function uploadAndInsertImage(file, host) {
|
|
|
15351
15737
|
rotation: 0,
|
|
15352
15738
|
appearances: [],
|
|
15353
15739
|
tracks: [],
|
|
15740
|
+
bindings: [],
|
|
15354
15741
|
x: Math.round(cx - w / 2),
|
|
15355
15742
|
y: Math.round(cy - h / 2),
|
|
15356
15743
|
width: w,
|
|
@@ -15774,6 +16161,8 @@ var KIND_ICON = {
|
|
|
15774
16161
|
effect: "\u2728",
|
|
15775
16162
|
asset: "\u{1F5BC}",
|
|
15776
16163
|
group: "\u2B1A",
|
|
16164
|
+
layout: "\u25A6",
|
|
16165
|
+
checkpoint: "\u25C6",
|
|
15777
16166
|
other: "\xB7"
|
|
15778
16167
|
};
|
|
15779
16168
|
var dialogEl2 = null;
|
|
@@ -16709,7 +17098,8 @@ function initStudio(opts = {}) {
|
|
|
16709
17098
|
const ui = queryUi();
|
|
16710
17099
|
if (!ui) {
|
|
16711
17100
|
console.error("[studio] missing required elements");
|
|
16712
|
-
return
|
|
17101
|
+
return () => {
|
|
17102
|
+
};
|
|
16713
17103
|
}
|
|
16714
17104
|
document.body.classList.add("editor-active");
|
|
16715
17105
|
document.documentElement.classList.add("editor-active");
|
|
@@ -16799,6 +17189,18 @@ function initStudio(opts = {}) {
|
|
|
16799
17189
|
ui.newBtn.addEventListener("click", () => openNewDialog(ui));
|
|
16800
17190
|
initPalette();
|
|
16801
17191
|
initHistoryPanel();
|
|
17192
|
+
if (!getDef()) startDraft();
|
|
17193
|
+
const pluginMount = mountEditorPlugins(
|
|
17194
|
+
ui.app,
|
|
17195
|
+
opts.plugins ?? [],
|
|
17196
|
+
opts.resolvePluginPermissions ?? (() => ({})),
|
|
17197
|
+
{
|
|
17198
|
+
getDocument: getDef,
|
|
17199
|
+
replaceDocument: setDef,
|
|
17200
|
+
getSelection,
|
|
17201
|
+
setSelection
|
|
17202
|
+
}
|
|
17203
|
+
);
|
|
16802
17204
|
registerCommands([
|
|
16803
17205
|
{
|
|
16804
17206
|
id: "open",
|
|
@@ -16844,7 +17246,8 @@ function initStudio(opts = {}) {
|
|
|
16844
17246
|
hint: "G",
|
|
16845
17247
|
run: () => setGridEnabled(!isGridEnabled())
|
|
16846
17248
|
},
|
|
16847
|
-
{ id: "help", label: "\u2328 \uB2E8\uCD95\uD0A4 \uBCF4\uAE30", hint: "? / Shift+/", run: openHelp }
|
|
17249
|
+
{ id: "help", label: "\u2328 \uB2E8\uCD95\uD0A4 \uBCF4\uAE30", hint: "? / Shift+/", run: openHelp },
|
|
17250
|
+
...pluginMount.commands
|
|
16848
17251
|
]);
|
|
16849
17252
|
ui.saveBtn.addEventListener("click", () => void saveCurrent(ui));
|
|
16850
17253
|
ui.exportBtn.addEventListener("click", () => {
|
|
@@ -16858,7 +17261,9 @@ function initStudio(opts = {}) {
|
|
|
16858
17261
|
const file = ui.importFileInput.files?.[0];
|
|
16859
17262
|
ui.importFileInput.value = "";
|
|
16860
17263
|
if (!file) return;
|
|
16861
|
-
void file.text().then((text) =>
|
|
17264
|
+
void file.text().then((text) => JSON.parse(text)).then(
|
|
17265
|
+
(input) => opts.importDocument ? opts.importDocument(input) : animationDocumentSchema.parse(input)
|
|
17266
|
+
).then((def) => importAnimation(def)).then((def) => {
|
|
16862
17267
|
setDef(def);
|
|
16863
17268
|
markClean();
|
|
16864
17269
|
setStatus(ui, `${def.id}.json \uD30C\uC77C\uC744 \uAC00\uC838\uC654\uC2B5\uB2C8\uB2E4.`, "ok");
|
|
@@ -16921,12 +17326,12 @@ function initStudio(opts = {}) {
|
|
|
16921
17326
|
});
|
|
16922
17327
|
if (opts.initialId) {
|
|
16923
17328
|
void loadAnimation2(ui, opts.initialId);
|
|
16924
|
-
} else if (
|
|
16925
|
-
startDraft();
|
|
17329
|
+
} else if (isDraft()) {
|
|
16926
17330
|
setStatus(ui, "\uC784\uC2DC \uC791\uC5C5 (Draft), \uC800\uC7A5 \uC2DC ID/\uC81C\uBAA9 \uC785\uB825", "ok");
|
|
16927
17331
|
} else {
|
|
16928
17332
|
setStatus(ui, "\uC900\uBE44\uB428", "ok");
|
|
16929
17333
|
}
|
|
17334
|
+
return () => pluginMount.dispose();
|
|
16930
17335
|
}
|
|
16931
17336
|
function reflectState(ui) {
|
|
16932
17337
|
ui.undoBtn.disabled = !canUndo();
|
|
@@ -16967,5 +17372,5 @@ function reflectState(ui) {
|
|
|
16967
17372
|
}
|
|
16968
17373
|
|
|
16969
17374
|
export { getVisibleElementIds, initStudio };
|
|
16970
|
-
//# sourceMappingURL=main-
|
|
16971
|
-
//# sourceMappingURL=main-
|
|
17375
|
+
//# sourceMappingURL=main-SKM3P4X7.js.map
|
|
17376
|
+
//# sourceMappingURL=main-SKM3P4X7.js.map
|