@kokoa/clotho-editor 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +54 -0
- package/dist/chunk-UE32Q6U5.js +943 -0
- package/dist/chunk-UE32Q6U5.js.map +1 -0
- package/dist/clotho-editor.css +1921 -0
- package/dist/index.d.ts +138 -0
- package/dist/index.js +425 -0
- package/dist/index.js.map +1 -0
- package/dist/main-SWHHYTLM.js +16214 -0
- package/dist/main-SWHHYTLM.js.map +1 -0
- package/docs/PORTING.md +136 -0
- package/package.json +64 -0
|
@@ -0,0 +1,943 @@
|
|
|
1
|
+
import { computeSnapshot, encodeImageAsset, inlineAssetFromDataUri, animationDocumentSchema } from '@kokoa/clotho';
|
|
2
|
+
|
|
3
|
+
// src/legacy/state/internals.ts
|
|
4
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
5
|
+
var state = {
|
|
6
|
+
def: null,
|
|
7
|
+
dirty: false,
|
|
8
|
+
selection: { kind: "none" },
|
|
9
|
+
currentTime: 0,
|
|
10
|
+
isDraft: false
|
|
11
|
+
};
|
|
12
|
+
var HISTORY_LIMIT = 60;
|
|
13
|
+
var past = [];
|
|
14
|
+
var future = [];
|
|
15
|
+
var inTransient = false;
|
|
16
|
+
function snapshotJson() {
|
|
17
|
+
return state.def ? JSON.stringify(state.def) : null;
|
|
18
|
+
}
|
|
19
|
+
function pushHistory(label, kind) {
|
|
20
|
+
const snap = snapshotJson();
|
|
21
|
+
if (snap === null) return;
|
|
22
|
+
past.push({ snap, label, kind, timestamp: Date.now() });
|
|
23
|
+
if (past.length > HISTORY_LIMIT) past.shift();
|
|
24
|
+
future.length = 0;
|
|
25
|
+
}
|
|
26
|
+
function setInTransient(value) {
|
|
27
|
+
inTransient = value;
|
|
28
|
+
}
|
|
29
|
+
function subscribe(fn) {
|
|
30
|
+
listeners.add(fn);
|
|
31
|
+
return () => listeners.delete(fn);
|
|
32
|
+
}
|
|
33
|
+
function emit() {
|
|
34
|
+
for (const fn of listeners) fn();
|
|
35
|
+
}
|
|
36
|
+
function mutateDef(fn, label = "edit", kind = "other") {
|
|
37
|
+
if (!state.def) return;
|
|
38
|
+
if (!inTransient) pushHistory(label, kind);
|
|
39
|
+
const cloned = JSON.parse(JSON.stringify(state.def));
|
|
40
|
+
fn(cloned);
|
|
41
|
+
const parsed = animationDocumentSchema.safeParse(cloned);
|
|
42
|
+
if (!parsed.success) {
|
|
43
|
+
past.pop();
|
|
44
|
+
console.warn("[studio.state] invalid mutation", parsed.error.issues);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
state.def = parsed.data;
|
|
48
|
+
state.dirty = true;
|
|
49
|
+
emit();
|
|
50
|
+
}
|
|
51
|
+
function isDraft() {
|
|
52
|
+
return state.isDraft;
|
|
53
|
+
}
|
|
54
|
+
function setDraft(def) {
|
|
55
|
+
state.def = def;
|
|
56
|
+
state.dirty = false;
|
|
57
|
+
state.isDraft = true;
|
|
58
|
+
state.selection = { kind: "none" };
|
|
59
|
+
state.currentTime = 0;
|
|
60
|
+
past.length = 0;
|
|
61
|
+
future.length = 0;
|
|
62
|
+
emit();
|
|
63
|
+
}
|
|
64
|
+
function promoteDraftToSaved() {
|
|
65
|
+
state.isDraft = false;
|
|
66
|
+
emit();
|
|
67
|
+
}
|
|
68
|
+
function getDef() {
|
|
69
|
+
return state.def;
|
|
70
|
+
}
|
|
71
|
+
function getSelection() {
|
|
72
|
+
return state.selection;
|
|
73
|
+
}
|
|
74
|
+
function isDirty() {
|
|
75
|
+
return state.dirty;
|
|
76
|
+
}
|
|
77
|
+
function getCurrentTime() {
|
|
78
|
+
return state.currentTime;
|
|
79
|
+
}
|
|
80
|
+
function setCurrentTime(time) {
|
|
81
|
+
state.currentTime = Math.max(0, Math.round(time));
|
|
82
|
+
emit();
|
|
83
|
+
}
|
|
84
|
+
function setDef(def, markDirty = false) {
|
|
85
|
+
state.def = def;
|
|
86
|
+
state.dirty = markDirty;
|
|
87
|
+
state.isDraft = false;
|
|
88
|
+
state.selection = { kind: "none" };
|
|
89
|
+
state.currentTime = 0;
|
|
90
|
+
past.length = 0;
|
|
91
|
+
future.length = 0;
|
|
92
|
+
emit();
|
|
93
|
+
}
|
|
94
|
+
function markClean() {
|
|
95
|
+
state.dirty = false;
|
|
96
|
+
emit();
|
|
97
|
+
}
|
|
98
|
+
function setSelection(sel) {
|
|
99
|
+
state.selection = sel;
|
|
100
|
+
emit();
|
|
101
|
+
}
|
|
102
|
+
function getSelectedElementIds(sel) {
|
|
103
|
+
if (sel.kind === "element") return [sel.elementId];
|
|
104
|
+
if (sel.kind === "elements") return sel.elementIds;
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
function isElementSelected(sel, id) {
|
|
108
|
+
if (sel.kind === "element") return sel.elementId === id;
|
|
109
|
+
if (sel.kind === "elements") return sel.elementIds.includes(id);
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
function toggleSelectionFor(sel, id) {
|
|
113
|
+
const cur = getSelectedElementIds(sel);
|
|
114
|
+
if (cur.includes(id)) {
|
|
115
|
+
const next2 = cur.filter((x) => x !== id);
|
|
116
|
+
if (next2.length === 0) return { kind: "none" };
|
|
117
|
+
if (next2.length === 1) return { kind: "element", elementId: next2[0] };
|
|
118
|
+
return { kind: "elements", elementIds: next2 };
|
|
119
|
+
}
|
|
120
|
+
const next = [...cur, id];
|
|
121
|
+
if (next.length === 1) return { kind: "element", elementId: next[0] };
|
|
122
|
+
return { kind: "elements", elementIds: next };
|
|
123
|
+
}
|
|
124
|
+
function getCurrentSnapshot() {
|
|
125
|
+
if (!state.def) return /* @__PURE__ */ new Map();
|
|
126
|
+
return computeSnapshot(state.def, state.currentTime);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/legacy/state/history.ts
|
|
130
|
+
function beginTransient(label = "edit", kind = "other") {
|
|
131
|
+
if (!state.def) return;
|
|
132
|
+
pushHistory(label, kind);
|
|
133
|
+
setInTransient(true);
|
|
134
|
+
}
|
|
135
|
+
function endTransient() {
|
|
136
|
+
setInTransient(false);
|
|
137
|
+
}
|
|
138
|
+
function canUndo() {
|
|
139
|
+
return past.length > 0;
|
|
140
|
+
}
|
|
141
|
+
function canRedo() {
|
|
142
|
+
return future.length > 0;
|
|
143
|
+
}
|
|
144
|
+
function undo() {
|
|
145
|
+
if (past.length === 0) return;
|
|
146
|
+
const prev = past[past.length - 1];
|
|
147
|
+
const cur = snapshotJson();
|
|
148
|
+
if (cur !== null) {
|
|
149
|
+
future.push({
|
|
150
|
+
snap: cur,
|
|
151
|
+
label: prev.label,
|
|
152
|
+
kind: prev.kind,
|
|
153
|
+
timestamp: prev.timestamp
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
past.pop();
|
|
157
|
+
state.def = JSON.parse(prev.snap);
|
|
158
|
+
state.dirty = true;
|
|
159
|
+
emit();
|
|
160
|
+
}
|
|
161
|
+
function redo() {
|
|
162
|
+
if (future.length === 0) return;
|
|
163
|
+
const next = future[future.length - 1];
|
|
164
|
+
const cur = snapshotJson();
|
|
165
|
+
if (cur !== null) {
|
|
166
|
+
past.push({
|
|
167
|
+
snap: cur,
|
|
168
|
+
label: next.label,
|
|
169
|
+
kind: next.kind,
|
|
170
|
+
timestamp: next.timestamp
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
future.pop();
|
|
174
|
+
state.def = JSON.parse(next.snap);
|
|
175
|
+
state.dirty = true;
|
|
176
|
+
emit();
|
|
177
|
+
}
|
|
178
|
+
function resetHistory() {
|
|
179
|
+
past.length = 0;
|
|
180
|
+
future.length = 0;
|
|
181
|
+
}
|
|
182
|
+
function getHistory() {
|
|
183
|
+
return { past, future };
|
|
184
|
+
}
|
|
185
|
+
function jumpBack(steps) {
|
|
186
|
+
if (steps <= 0 || past.length === 0) return;
|
|
187
|
+
const n = Math.min(steps, past.length);
|
|
188
|
+
for (let i = 0; i < n; i += 1) undo();
|
|
189
|
+
}
|
|
190
|
+
function jumpForward(steps) {
|
|
191
|
+
if (steps <= 0 || future.length === 0) return;
|
|
192
|
+
const n = Math.min(steps, future.length);
|
|
193
|
+
for (let i = 0; i < n; i += 1) redo();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/legacy/state/elements.ts
|
|
197
|
+
function ensureAppearance(el, def) {
|
|
198
|
+
if (!el.appearances || el.appearances.length === 0) {
|
|
199
|
+
el.appearances = [
|
|
200
|
+
{ start: 0, end: def.duration, entryDuration: 300, exitDuration: 300 }
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
function addElement(el) {
|
|
205
|
+
const kind = el.type === "group" ? "group" : "add";
|
|
206
|
+
const label = el.type === "group" ? "\uADF8\uB8F9 \uC0DD\uC131" : `\uC694\uC18C \uCD94\uAC00: ${el.type}`;
|
|
207
|
+
mutateDef(
|
|
208
|
+
(def) => {
|
|
209
|
+
const cloned = JSON.parse(JSON.stringify(el));
|
|
210
|
+
ensureAppearance(cloned, def);
|
|
211
|
+
if (!cloned.tracks) cloned.tracks = [];
|
|
212
|
+
def.elements.push(cloned);
|
|
213
|
+
},
|
|
214
|
+
label,
|
|
215
|
+
kind
|
|
216
|
+
);
|
|
217
|
+
state.selection = { kind: "element", elementId: el.id };
|
|
218
|
+
emit();
|
|
219
|
+
}
|
|
220
|
+
function deleteElement(id) {
|
|
221
|
+
const targetEl = state.def?.elements.find((e) => e.id === id);
|
|
222
|
+
const isGroupKind = targetEl?.type === "group";
|
|
223
|
+
mutateDef(
|
|
224
|
+
(def) => {
|
|
225
|
+
def.elements = def.elements.filter((e) => e.id !== id);
|
|
226
|
+
def.effects = def.effects.filter((e) => e.elementId !== id);
|
|
227
|
+
for (const el of def.elements) {
|
|
228
|
+
if (el.parentId === id) delete el.parentId;
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
isGroupKind ? `\uADF8\uB8F9 \uD574\uC81C` : `\uC694\uC18C \uC0AD\uC81C: ${id}`,
|
|
232
|
+
isGroupKind ? "group" : "delete"
|
|
233
|
+
);
|
|
234
|
+
if (state.selection.kind === "element" && state.selection.elementId === id) {
|
|
235
|
+
state.selection = { kind: "none" };
|
|
236
|
+
} else if (state.selection.kind === "elements") {
|
|
237
|
+
const remaining = state.selection.elementIds.filter((x) => x !== id);
|
|
238
|
+
if (remaining.length === 0) state.selection = { kind: "none" };
|
|
239
|
+
else if (remaining.length === 1)
|
|
240
|
+
state.selection = { kind: "element", elementId: remaining[0] };
|
|
241
|
+
else state.selection = { kind: "elements", elementIds: remaining };
|
|
242
|
+
}
|
|
243
|
+
emit();
|
|
244
|
+
}
|
|
245
|
+
function labelForPatch(id, patch) {
|
|
246
|
+
const keys = Object.keys(patch);
|
|
247
|
+
if (keys.length === 0) return { label: `\uC694\uC18C \uC218\uC815: ${id}`, kind: "other" };
|
|
248
|
+
const posKeys = ["x", "y", "cx", "cy", "x1", "y1", "x2", "y2", "points"];
|
|
249
|
+
const sizeKeys = ["width", "height", "r", "fontSize", "cellWidth"];
|
|
250
|
+
const styleKeys = [
|
|
251
|
+
"fill",
|
|
252
|
+
"stroke",
|
|
253
|
+
"strokeWidth",
|
|
254
|
+
"color",
|
|
255
|
+
"opacity",
|
|
256
|
+
"cornerRadius",
|
|
257
|
+
"labelColor",
|
|
258
|
+
"labelSize"
|
|
259
|
+
];
|
|
260
|
+
if (keys.some((k) => posKeys.includes(k)))
|
|
261
|
+
return { label: `\uC774\uB3D9: ${id}`, kind: "move" };
|
|
262
|
+
if (keys.some((k) => sizeKeys.includes(k)))
|
|
263
|
+
return { label: `\uD06C\uAE30 \uBCC0\uACBD: ${id}`, kind: "resize" };
|
|
264
|
+
if (keys.includes("rotation"))
|
|
265
|
+
return { label: `\uD68C\uC804: ${id}`, kind: "rotate" };
|
|
266
|
+
if (keys.some((k) => styleKeys.includes(k)))
|
|
267
|
+
return { label: `\uC2A4\uD0C0\uC77C: ${id} (${keys.join(", ")})`, kind: "style" };
|
|
268
|
+
if (keys.includes("name")) return { label: `\uC774\uB984 \uBCC0\uACBD: ${id}`, kind: "meta" };
|
|
269
|
+
return { label: `\uC694\uC18C \uC218\uC815: ${id} (${keys.join(", ")})`, kind: "other" };
|
|
270
|
+
}
|
|
271
|
+
function updateElementBase(id, patch) {
|
|
272
|
+
const { label, kind } = labelForPatch(id, patch);
|
|
273
|
+
mutateDef(
|
|
274
|
+
(def) => {
|
|
275
|
+
const idx = def.elements.findIndex((e) => e.id === id);
|
|
276
|
+
if (idx < 0) return;
|
|
277
|
+
const baseEl = def.elements[idx];
|
|
278
|
+
const merged = {
|
|
279
|
+
...baseEl
|
|
280
|
+
};
|
|
281
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
282
|
+
if (v === null || v === void 0) delete merged[k];
|
|
283
|
+
else merged[k] = v;
|
|
284
|
+
}
|
|
285
|
+
def.elements[idx] = merged;
|
|
286
|
+
},
|
|
287
|
+
label,
|
|
288
|
+
kind
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
function reorderElement(sourceId, targetId, position) {
|
|
292
|
+
mutateDef(
|
|
293
|
+
(def) => {
|
|
294
|
+
const srcIdx = def.elements.findIndex((e) => e.id === sourceId);
|
|
295
|
+
if (srcIdx < 0) return;
|
|
296
|
+
const [moved] = def.elements.splice(srcIdx, 1);
|
|
297
|
+
let targetIdx = def.elements.findIndex((e) => e.id === targetId);
|
|
298
|
+
if (targetIdx < 0) {
|
|
299
|
+
def.elements.push(moved);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (position === "after") targetIdx += 1;
|
|
303
|
+
def.elements.splice(targetIdx, 0, moved);
|
|
304
|
+
},
|
|
305
|
+
`\uC21C\uC11C \uBCC0\uACBD: ${sourceId}`,
|
|
306
|
+
"reorder"
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
function moveElementToEnd(id) {
|
|
310
|
+
mutateDef(
|
|
311
|
+
(def) => {
|
|
312
|
+
const idx = def.elements.findIndex((e) => e.id === id);
|
|
313
|
+
if (idx < 0) return;
|
|
314
|
+
const [moved] = def.elements.splice(idx, 1);
|
|
315
|
+
def.elements.push(moved);
|
|
316
|
+
},
|
|
317
|
+
`\uB9E8 \uC55E\uC73C\uB85C: ${id}`,
|
|
318
|
+
"reorder"
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
function moveElementToFront(id) {
|
|
322
|
+
mutateDef(
|
|
323
|
+
(def) => {
|
|
324
|
+
const idx = def.elements.findIndex((e) => e.id === id);
|
|
325
|
+
if (idx < 0) return;
|
|
326
|
+
const [moved] = def.elements.splice(idx, 1);
|
|
327
|
+
def.elements.unshift(moved);
|
|
328
|
+
},
|
|
329
|
+
`\uB9E8 \uB4A4\uB85C: ${id}`,
|
|
330
|
+
"reorder"
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
function addAppearance(id, ap) {
|
|
334
|
+
mutateDef(
|
|
335
|
+
(def) => {
|
|
336
|
+
const el = def.elements.find((e) => e.id === id);
|
|
337
|
+
if (!el) return;
|
|
338
|
+
el.appearances.push(ap);
|
|
339
|
+
el.appearances.sort((a, b) => a.start - b.start);
|
|
340
|
+
},
|
|
341
|
+
`\uCD9C\uD604 \uCD94\uAC00: ${id}`,
|
|
342
|
+
"appearance"
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
function updateAppearance(id, apIdx, patch) {
|
|
346
|
+
mutateDef(
|
|
347
|
+
(def) => {
|
|
348
|
+
const el = def.elements.find((e) => e.id === id);
|
|
349
|
+
if (!el || !el.appearances[apIdx]) return;
|
|
350
|
+
el.appearances[apIdx] = { ...el.appearances[apIdx], ...patch };
|
|
351
|
+
el.appearances.sort((a, b) => a.start - b.start);
|
|
352
|
+
},
|
|
353
|
+
`\uCD9C\uD604 \uC870\uC815: ${id}`,
|
|
354
|
+
"appearance"
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
function removeAppearance(id, apIdx) {
|
|
358
|
+
mutateDef(
|
|
359
|
+
(def) => {
|
|
360
|
+
const el = def.elements.find((e) => e.id === id);
|
|
361
|
+
if (!el) return;
|
|
362
|
+
el.appearances.splice(apIdx, 1);
|
|
363
|
+
if (el.appearances.length === 0) ensureAppearance(el, def);
|
|
364
|
+
},
|
|
365
|
+
`\uCD9C\uD604 \uC0AD\uC81C: ${id}`,
|
|
366
|
+
"appearance"
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
function findTrack(el, property) {
|
|
370
|
+
return el.tracks.find((t) => t.property === property);
|
|
371
|
+
}
|
|
372
|
+
function setTrackKeyframe(elementId, property, time, value) {
|
|
373
|
+
mutateDef(
|
|
374
|
+
(def) => {
|
|
375
|
+
const el = def.elements.find((e) => e.id === elementId);
|
|
376
|
+
if (!el) return;
|
|
377
|
+
let track = findTrack(el, property);
|
|
378
|
+
if (!track) {
|
|
379
|
+
track = { property, keyframes: [] };
|
|
380
|
+
el.tracks.push(track);
|
|
381
|
+
}
|
|
382
|
+
const existing = track.keyframes.find((k) => k.time === time);
|
|
383
|
+
if (existing) {
|
|
384
|
+
existing.value = value;
|
|
385
|
+
} else {
|
|
386
|
+
track.keyframes.push({ time, value });
|
|
387
|
+
track.keyframes.sort((a, b) => a.time - b.time);
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
`keyframe ${property} @ ${time}ms`,
|
|
391
|
+
"track"
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
function removeTrackKeyframe(elementId, property, time) {
|
|
395
|
+
mutateDef(
|
|
396
|
+
(def) => {
|
|
397
|
+
const el = def.elements.find((e) => e.id === elementId);
|
|
398
|
+
if (!el) return;
|
|
399
|
+
const track = findTrack(el, property);
|
|
400
|
+
if (!track) return;
|
|
401
|
+
track.keyframes = track.keyframes.filter((k) => k.time !== time);
|
|
402
|
+
if (track.keyframes.length === 0) {
|
|
403
|
+
el.tracks = el.tracks.filter((t) => t.property !== property);
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
`keyframe \uC0AD\uC81C ${property} @ ${time}ms`,
|
|
407
|
+
"track"
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
function setElementValueAtTime(elementId, patch) {
|
|
411
|
+
const time = state.currentTime;
|
|
412
|
+
if (time <= 0) {
|
|
413
|
+
updateElementBase(elementId, patch);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (!state.def) return;
|
|
417
|
+
const el = state.def.elements.find((e) => e.id === elementId);
|
|
418
|
+
if (!el) return;
|
|
419
|
+
for (const [prop, value] of Object.entries(patch)) {
|
|
420
|
+
if (value === null || value === void 0) continue;
|
|
421
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean")
|
|
422
|
+
continue;
|
|
423
|
+
const hasTrack = el.tracks.some((t) => t.property === prop);
|
|
424
|
+
if (!hasTrack) {
|
|
425
|
+
const baseVal = el[prop];
|
|
426
|
+
if (typeof baseVal === "string" || typeof baseVal === "number" || typeof baseVal === "boolean") {
|
|
427
|
+
setTrackKeyframe(elementId, prop, 0, baseVal);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
setTrackKeyframe(elementId, prop, time, value);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function removeTrack(elementId, property) {
|
|
434
|
+
mutateDef(
|
|
435
|
+
(def) => {
|
|
436
|
+
const el = def.elements.find((e) => e.id === elementId);
|
|
437
|
+
if (!el) return;
|
|
438
|
+
el.tracks = el.tracks.filter((t) => t.property !== property);
|
|
439
|
+
},
|
|
440
|
+
`\uD2B8\uB799 \uC0AD\uC81C: ${property}`,
|
|
441
|
+
"track"
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/legacy/state/timeline.ts
|
|
446
|
+
function addChapter(c) {
|
|
447
|
+
mutateDef(
|
|
448
|
+
(def) => {
|
|
449
|
+
def.chapters.push(c);
|
|
450
|
+
def.chapters.sort((a, b) => a.time - b.time);
|
|
451
|
+
},
|
|
452
|
+
`Chapter \uCD94\uAC00: ${c.label || c.id}`,
|
|
453
|
+
"chapter"
|
|
454
|
+
);
|
|
455
|
+
state.selection = { kind: "chapter", chapterId: c.id };
|
|
456
|
+
emit();
|
|
457
|
+
}
|
|
458
|
+
function updateChapter(id, patch) {
|
|
459
|
+
const keys = Object.keys(patch).join(", ");
|
|
460
|
+
mutateDef(
|
|
461
|
+
(def) => {
|
|
462
|
+
const idx = def.chapters.findIndex((c) => c.id === id);
|
|
463
|
+
if (idx < 0) return;
|
|
464
|
+
def.chapters[idx] = { ...def.chapters[idx], ...patch };
|
|
465
|
+
def.chapters.sort((a, b) => a.time - b.time);
|
|
466
|
+
},
|
|
467
|
+
`Chapter \uC218\uC815: ${id} (${keys})`,
|
|
468
|
+
"chapter"
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
function deleteChapter(id) {
|
|
472
|
+
mutateDef(
|
|
473
|
+
(def) => {
|
|
474
|
+
def.chapters = def.chapters.filter((c) => c.id !== id);
|
|
475
|
+
},
|
|
476
|
+
`Chapter \uC0AD\uC81C: ${id}`,
|
|
477
|
+
"chapter"
|
|
478
|
+
);
|
|
479
|
+
if (state.selection.kind === "chapter" && state.selection.chapterId === id) {
|
|
480
|
+
state.selection = { kind: "none" };
|
|
481
|
+
}
|
|
482
|
+
emit();
|
|
483
|
+
}
|
|
484
|
+
function addEffect(eff) {
|
|
485
|
+
mutateDef(
|
|
486
|
+
(def) => {
|
|
487
|
+
def.effects.push(eff);
|
|
488
|
+
def.effects.sort((a, b) => a.time - b.time);
|
|
489
|
+
},
|
|
490
|
+
`\uD6A8\uACFC \uCD94\uAC00: ${eff.type}`,
|
|
491
|
+
"effect"
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
function updateEffect(id, patch) {
|
|
495
|
+
mutateDef(
|
|
496
|
+
(def) => {
|
|
497
|
+
const idx = def.effects.findIndex((e) => e.id === id);
|
|
498
|
+
if (idx < 0) return;
|
|
499
|
+
def.effects[idx] = { ...def.effects[idx], ...patch };
|
|
500
|
+
def.effects.sort((a, b) => a.time - b.time);
|
|
501
|
+
},
|
|
502
|
+
`\uD6A8\uACFC \uC218\uC815: ${id}`,
|
|
503
|
+
"effect"
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
function deleteEffect(id) {
|
|
507
|
+
mutateDef(
|
|
508
|
+
(def) => {
|
|
509
|
+
def.effects = def.effects.filter((e) => e.id !== id);
|
|
510
|
+
},
|
|
511
|
+
`\uD6A8\uACFC \uC0AD\uC81C: ${id}`,
|
|
512
|
+
"effect"
|
|
513
|
+
);
|
|
514
|
+
if (state.selection.kind === "effect" && state.selection.effectId === id) {
|
|
515
|
+
state.selection = { kind: "none" };
|
|
516
|
+
}
|
|
517
|
+
emit();
|
|
518
|
+
}
|
|
519
|
+
function updateDuration(ms) {
|
|
520
|
+
mutateDef(
|
|
521
|
+
(def) => {
|
|
522
|
+
def.duration = Math.max(0, Math.round(ms));
|
|
523
|
+
for (const el of def.elements) {
|
|
524
|
+
for (const ap of el.appearances) {
|
|
525
|
+
if (ap.end > def.duration) ap.end = def.duration;
|
|
526
|
+
if (ap.start > def.duration) ap.start = def.duration;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
for (const ch of def.chapters) {
|
|
530
|
+
if (ch.time > def.duration) ch.time = def.duration;
|
|
531
|
+
}
|
|
532
|
+
for (const eff of def.effects) {
|
|
533
|
+
if (eff.time > def.duration) eff.time = def.duration;
|
|
534
|
+
}
|
|
535
|
+
},
|
|
536
|
+
`duration: ${ms} ms`,
|
|
537
|
+
"meta"
|
|
538
|
+
);
|
|
539
|
+
if (state.currentTime > ms) state.currentTime = ms;
|
|
540
|
+
}
|
|
541
|
+
function updateMeta(patch) {
|
|
542
|
+
const keys = Object.keys(patch).join(", ");
|
|
543
|
+
mutateDef(
|
|
544
|
+
(def) => {
|
|
545
|
+
Object.assign(def, patch);
|
|
546
|
+
},
|
|
547
|
+
`\uBA54\uD0C0 \uC218\uC815: ${keys}`,
|
|
548
|
+
"meta"
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
function updateCanvas(patch) {
|
|
552
|
+
const keys = Object.keys(patch).join(", ");
|
|
553
|
+
mutateDef(
|
|
554
|
+
(def) => {
|
|
555
|
+
def.canvas = { ...def.canvas, ...patch };
|
|
556
|
+
},
|
|
557
|
+
`\uCE94\uBC84\uC2A4: ${keys}`,
|
|
558
|
+
"canvas"
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
function updateSettings(patch) {
|
|
562
|
+
const keys = Object.keys(patch).join(", ");
|
|
563
|
+
mutateDef(
|
|
564
|
+
(def) => {
|
|
565
|
+
def.settings = { ...def.settings, ...patch };
|
|
566
|
+
},
|
|
567
|
+
`\uC124\uC815: ${keys}`,
|
|
568
|
+
"settings"
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
function uniqueElementId(type) {
|
|
572
|
+
if (!state.def) return type + "-1";
|
|
573
|
+
const used = new Set(state.def.elements.map((e) => e.id));
|
|
574
|
+
let i = 1;
|
|
575
|
+
while (used.has(`${type}-${i}`)) i += 1;
|
|
576
|
+
return `${type}-${i}`;
|
|
577
|
+
}
|
|
578
|
+
function uniqueChapterId() {
|
|
579
|
+
if (!state.def) return "chapter-1";
|
|
580
|
+
const used = new Set(state.def.chapters.map((c) => c.id));
|
|
581
|
+
let i = 1;
|
|
582
|
+
while (used.has(`chapter-${i}`)) i += 1;
|
|
583
|
+
return `chapter-${i}`;
|
|
584
|
+
}
|
|
585
|
+
function uniqueEffectId() {
|
|
586
|
+
if (!state.def) return "effect-1";
|
|
587
|
+
const used = new Set(state.def.effects.map((e) => e.id));
|
|
588
|
+
let i = 1;
|
|
589
|
+
while (used.has(`effect-${i}`)) i += 1;
|
|
590
|
+
return `effect-${i}`;
|
|
591
|
+
}
|
|
592
|
+
function registerExternalAsset(url) {
|
|
593
|
+
const def = getDef();
|
|
594
|
+
if (def) {
|
|
595
|
+
for (const [id2, asset] of Object.entries(def.assets)) {
|
|
596
|
+
if (asset.kind === "external" && asset.url === url) return id2;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
const id = uniqueAssetId();
|
|
600
|
+
mutateDef(
|
|
601
|
+
(draft) => {
|
|
602
|
+
draft.assets[id] = { kind: "external", url };
|
|
603
|
+
},
|
|
604
|
+
`\uC5D0\uC14B \uB4F1\uB85D: ${id}`,
|
|
605
|
+
"asset"
|
|
606
|
+
);
|
|
607
|
+
return id;
|
|
608
|
+
}
|
|
609
|
+
function registerInlineAsset(bytes, mime) {
|
|
610
|
+
const { asset } = encodeImageAsset(bytes, mime);
|
|
611
|
+
const id = uniqueAssetId();
|
|
612
|
+
mutateDef(
|
|
613
|
+
(draft) => {
|
|
614
|
+
draft.assets[id] = asset;
|
|
615
|
+
},
|
|
616
|
+
`\uC5D0\uC14B \uB4F1\uB85D: ${id}`,
|
|
617
|
+
"asset"
|
|
618
|
+
);
|
|
619
|
+
return id;
|
|
620
|
+
}
|
|
621
|
+
function uniqueAssetId() {
|
|
622
|
+
const def = getDef();
|
|
623
|
+
const existing = def ? new Set(Object.keys(def.assets)) : /* @__PURE__ */ new Set();
|
|
624
|
+
let n = 1;
|
|
625
|
+
while (existing.has(`asset-${n}`)) n += 1;
|
|
626
|
+
return `asset-${n}`;
|
|
627
|
+
}
|
|
628
|
+
function registerDataUriAsset(dataUri) {
|
|
629
|
+
const inline = inlineAssetFromDataUri(dataUri);
|
|
630
|
+
if (!inline) return registerExternalAsset(dataUri);
|
|
631
|
+
const id = uniqueAssetId();
|
|
632
|
+
mutateDef(
|
|
633
|
+
(draft) => {
|
|
634
|
+
draft.assets[id] = inline;
|
|
635
|
+
},
|
|
636
|
+
`\uC5D0\uC14B \uB4F1\uB85D: ${id}`,
|
|
637
|
+
"asset"
|
|
638
|
+
);
|
|
639
|
+
return id;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// src/legacy/studio-groups.ts
|
|
643
|
+
function isGroup(el) {
|
|
644
|
+
return el.type === "group";
|
|
645
|
+
}
|
|
646
|
+
function childIdsOf(groupId) {
|
|
647
|
+
const def = getDef();
|
|
648
|
+
if (!def) return [];
|
|
649
|
+
return def.elements.filter((el) => el.parentId === groupId).map((el) => el.id);
|
|
650
|
+
}
|
|
651
|
+
function findContainingGroup(elementId) {
|
|
652
|
+
const def = getDef();
|
|
653
|
+
if (!def) return null;
|
|
654
|
+
const element = def.elements.find((el) => el.id === elementId);
|
|
655
|
+
const parentId = element?.parentId;
|
|
656
|
+
if (parentId === void 0) return null;
|
|
657
|
+
const parent = def.elements.find((el) => el.id === parentId);
|
|
658
|
+
if (!parent || !isGroup(parent)) return null;
|
|
659
|
+
return findContainingGroup(parent.id) ?? parent;
|
|
660
|
+
}
|
|
661
|
+
function expandToLeafIds(ids) {
|
|
662
|
+
const def = getDef();
|
|
663
|
+
if (!def) return ids;
|
|
664
|
+
const seen = /* @__PURE__ */ new Set();
|
|
665
|
+
const stack = [...ids];
|
|
666
|
+
while (stack.length) {
|
|
667
|
+
const id = stack.pop();
|
|
668
|
+
if (seen.has(id)) continue;
|
|
669
|
+
seen.add(id);
|
|
670
|
+
const el = def.elements.find((e) => e.id === id);
|
|
671
|
+
if (el && isGroup(el)) {
|
|
672
|
+
for (const childId of childIdsOf(el.id)) stack.push(childId);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
const out = [];
|
|
676
|
+
for (const id of seen) {
|
|
677
|
+
const el = def.elements.find((e) => e.id === id);
|
|
678
|
+
if (el && !isGroup(el)) out.push(id);
|
|
679
|
+
}
|
|
680
|
+
return out;
|
|
681
|
+
}
|
|
682
|
+
function groupBbox(groupId) {
|
|
683
|
+
const def = getDef();
|
|
684
|
+
if (!def) return null;
|
|
685
|
+
const leafIds = expandToLeafIds([groupId]);
|
|
686
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
687
|
+
let any = false;
|
|
688
|
+
for (const id of leafIds) {
|
|
689
|
+
const el = def.elements.find((e) => e.id === id);
|
|
690
|
+
if (!el) continue;
|
|
691
|
+
const b = elementBbox(el);
|
|
692
|
+
if (!b) continue;
|
|
693
|
+
any = true;
|
|
694
|
+
if (b.x < minX) minX = b.x;
|
|
695
|
+
if (b.y < minY) minY = b.y;
|
|
696
|
+
if (b.x + b.w > maxX) maxX = b.x + b.w;
|
|
697
|
+
if (b.y + b.h > maxY) maxY = b.y + b.h;
|
|
698
|
+
}
|
|
699
|
+
return any ? { x: minX, y: minY, w: maxX - minX, h: maxY - minY } : null;
|
|
700
|
+
}
|
|
701
|
+
function elementBbox(el) {
|
|
702
|
+
if (el.type === "rect" || el.type === "image")
|
|
703
|
+
return { x: el.x, y: el.y, w: el.width, h: el.height };
|
|
704
|
+
if (el.type === "circle")
|
|
705
|
+
return { x: el.cx - el.r, y: el.cy - el.r, w: el.r * 2, h: el.r * 2 };
|
|
706
|
+
if (el.type === "text")
|
|
707
|
+
return {
|
|
708
|
+
x: el.x - 40,
|
|
709
|
+
y: el.y - (el.fontSize ?? 16),
|
|
710
|
+
w: 80,
|
|
711
|
+
h: el.fontSize ?? 16
|
|
712
|
+
};
|
|
713
|
+
if (el.type === "line" || el.type === "arrow") {
|
|
714
|
+
if (typeof el.x1 !== "number" || typeof el.x2 !== "number" || typeof el.y1 !== "number" || typeof el.y2 !== "number")
|
|
715
|
+
return null;
|
|
716
|
+
return {
|
|
717
|
+
x: Math.min(el.x1, el.x2),
|
|
718
|
+
y: Math.min(el.y1, el.y2),
|
|
719
|
+
w: Math.abs(el.x2 - el.x1),
|
|
720
|
+
h: Math.abs(el.y2 - el.y1)
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
if (el.type === "polygon") {
|
|
724
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
725
|
+
let any = false;
|
|
726
|
+
for (const pair of el.points.trim().split(/\s+/)) {
|
|
727
|
+
const [x, y] = pair.split(",").map(Number);
|
|
728
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) continue;
|
|
729
|
+
any = true;
|
|
730
|
+
if (x < minX) minX = x;
|
|
731
|
+
if (y < minY) minY = y;
|
|
732
|
+
if (x > maxX) maxX = x;
|
|
733
|
+
if (y > maxY) maxY = y;
|
|
734
|
+
}
|
|
735
|
+
return any ? { x: minX, y: minY, w: maxX - minX, h: maxY - minY } : null;
|
|
736
|
+
}
|
|
737
|
+
if (el.type === "path") return { x: el.x ?? 0, y: el.y ?? 0, w: 60, h: 60 };
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
function shiftLeafBy(el, dx, dy) {
|
|
741
|
+
if (dx === 0 && dy === 0) return;
|
|
742
|
+
if (el.type === "rect" || el.type === "image" || el.type === "text") {
|
|
743
|
+
updateElementBase(el.id, { x: el.x + dx, y: el.y + dy });
|
|
744
|
+
} else if (el.type === "circle") {
|
|
745
|
+
updateElementBase(el.id, { cx: el.cx + dx, cy: el.cy + dy });
|
|
746
|
+
} else if (el.type === "line" || el.type === "arrow") {
|
|
747
|
+
const patch = {};
|
|
748
|
+
if (typeof el.x1 === "number") patch.x1 = el.x1 + dx;
|
|
749
|
+
if (typeof el.y1 === "number") patch.y1 = el.y1 + dy;
|
|
750
|
+
if (typeof el.x2 === "number") patch.x2 = el.x2 + dx;
|
|
751
|
+
if (typeof el.y2 === "number") patch.y2 = el.y2 + dy;
|
|
752
|
+
updateElementBase(el.id, patch);
|
|
753
|
+
} else if (el.type === "path") {
|
|
754
|
+
updateElementBase(el.id, { x: (el.x ?? 0) + dx, y: (el.y ?? 0) + dy });
|
|
755
|
+
} else if (el.type === "polygon") {
|
|
756
|
+
const shifted = el.points.trim().split(/\s+/).map((pair) => {
|
|
757
|
+
const [x, y] = pair.split(",").map(Number);
|
|
758
|
+
if (Number.isFinite(x) && Number.isFinite(y))
|
|
759
|
+
return `${(x + dx).toFixed(1)},${(y + dy).toFixed(1)}`;
|
|
760
|
+
return pair;
|
|
761
|
+
}).join(" ");
|
|
762
|
+
updateElementBase(el.id, { points: shifted });
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
function moveGroupBy(groupId, dx, dy) {
|
|
766
|
+
const def = getDef();
|
|
767
|
+
if (!def) return;
|
|
768
|
+
const leafIds = expandToLeafIds([groupId]);
|
|
769
|
+
for (const id of leafIds) {
|
|
770
|
+
const el = def.elements.find((e) => e.id === id);
|
|
771
|
+
if (el) shiftLeafBy(el, dx, dy);
|
|
772
|
+
}
|
|
773
|
+
const group = def.elements.find((e) => e.id === groupId);
|
|
774
|
+
if (group && isGroup(group)) {
|
|
775
|
+
updateElementBase(groupId, { x: group.x + dx, y: group.y + dy });
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
function groupElements(ids) {
|
|
779
|
+
if (ids.length < 2) return null;
|
|
780
|
+
const def = getDef();
|
|
781
|
+
if (!def) return null;
|
|
782
|
+
const valid = ids.filter((id) => def.elements.some((e) => e.id === id));
|
|
783
|
+
if (valid.length < 2) return null;
|
|
784
|
+
const newId = uniqueElementId("group");
|
|
785
|
+
(() => {
|
|
786
|
+
let minX = Infinity, minY = Infinity;
|
|
787
|
+
for (const id of valid) {
|
|
788
|
+
const el = def.elements.find((e) => e.id === id);
|
|
789
|
+
if (!el) continue;
|
|
790
|
+
const b = elementBbox(el);
|
|
791
|
+
if (!b) continue;
|
|
792
|
+
if (b.x < minX) minX = b.x;
|
|
793
|
+
if (b.y < minY) minY = b.y;
|
|
794
|
+
}
|
|
795
|
+
return {
|
|
796
|
+
x: Number.isFinite(minX) ? minX : 0,
|
|
797
|
+
y: Number.isFinite(minY) ? minY : 0
|
|
798
|
+
};
|
|
799
|
+
})();
|
|
800
|
+
const group = {
|
|
801
|
+
type: "group",
|
|
802
|
+
id: newId,
|
|
803
|
+
name: `Group ${newId.split("-")[1] ?? ""}`.trim(),
|
|
804
|
+
rotation: 0,
|
|
805
|
+
appearances: [],
|
|
806
|
+
tracks: [],
|
|
807
|
+
// Children keep their absolute coordinates, so the group's own transform starts at
|
|
808
|
+
// the identity. Setting x/y here would shift every member on the next render.
|
|
809
|
+
x: 0,
|
|
810
|
+
y: 0
|
|
811
|
+
};
|
|
812
|
+
addElement(group);
|
|
813
|
+
for (const id of valid) updateElementBase(id, { parentId: newId });
|
|
814
|
+
setSelection({ kind: "element", elementId: newId });
|
|
815
|
+
return newId;
|
|
816
|
+
}
|
|
817
|
+
function ungroupElement(groupId) {
|
|
818
|
+
const def = getDef();
|
|
819
|
+
if (!def) return [];
|
|
820
|
+
const group = def.elements.find((e) => e.id === groupId);
|
|
821
|
+
if (!group || !isGroup(group)) return [];
|
|
822
|
+
const childIds = childIdsOf(groupId);
|
|
823
|
+
for (const id of childIds) updateElementBase(id, { parentId: void 0 });
|
|
824
|
+
deleteElement(groupId);
|
|
825
|
+
if (childIds.length === 1) {
|
|
826
|
+
setSelection({ kind: "element", elementId: childIds[0] });
|
|
827
|
+
} else if (childIds.length > 1) {
|
|
828
|
+
setSelection({ kind: "elements", elementIds: childIds });
|
|
829
|
+
} else {
|
|
830
|
+
setSelection({ kind: "none" });
|
|
831
|
+
}
|
|
832
|
+
return childIds;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// src/legacy/host.ts
|
|
836
|
+
var placeholderUrl = "/uploads/placeholder.png";
|
|
837
|
+
function configureHost(options) {
|
|
838
|
+
if (options.placeholderImageUrl) placeholderUrl = options.placeholderImageUrl;
|
|
839
|
+
}
|
|
840
|
+
function placeholderImageUrl() {
|
|
841
|
+
return placeholderUrl;
|
|
842
|
+
}
|
|
843
|
+
var DEFAULT_BASE = "/api/admin/animations";
|
|
844
|
+
var BASE = DEFAULT_BASE;
|
|
845
|
+
function configureApi(options) {
|
|
846
|
+
if (options.baseUrl) BASE = options.baseUrl.replace(/\/+$/, "");
|
|
847
|
+
}
|
|
848
|
+
function apiBaseUrl() {
|
|
849
|
+
return BASE;
|
|
850
|
+
}
|
|
851
|
+
var revisions = /* @__PURE__ */ new Map();
|
|
852
|
+
var MissingAnimationRevisionError = class extends Error {
|
|
853
|
+
};
|
|
854
|
+
var AnimationStudioApiError = class extends Error {
|
|
855
|
+
};
|
|
856
|
+
function isRecord(value) {
|
|
857
|
+
return typeof value === "object" && value !== null;
|
|
858
|
+
}
|
|
859
|
+
async function readJson(res) {
|
|
860
|
+
if (!res.ok) {
|
|
861
|
+
const text = await res.text();
|
|
862
|
+
throw new AnimationStudioApiError(`HTTP ${res.status}: ${text}`);
|
|
863
|
+
}
|
|
864
|
+
return res.json();
|
|
865
|
+
}
|
|
866
|
+
function parseRevision(value) {
|
|
867
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) {
|
|
868
|
+
throw new TypeError("Animation revision is malformed");
|
|
869
|
+
}
|
|
870
|
+
return value;
|
|
871
|
+
}
|
|
872
|
+
function parseAnimationEnvelope(value) {
|
|
873
|
+
if (!isRecord(value)) throw new TypeError("Animation response is malformed");
|
|
874
|
+
return {
|
|
875
|
+
def: animationDocumentSchema.parse(value.def),
|
|
876
|
+
revision: parseRevision(value.revision)
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
async function listAnimations() {
|
|
880
|
+
const res = await fetch(BASE);
|
|
881
|
+
const data = await readJson(res);
|
|
882
|
+
if (!isRecord(data) || !Array.isArray(data.items))
|
|
883
|
+
throw new TypeError("Animation list response is malformed");
|
|
884
|
+
return data.items.map((item) => {
|
|
885
|
+
if (!isRecord(item) || typeof item.id !== "string" || typeof item.title !== "string" || typeof item.description !== "string") {
|
|
886
|
+
throw new TypeError("Animation summary is malformed");
|
|
887
|
+
}
|
|
888
|
+
return {
|
|
889
|
+
id: item.id,
|
|
890
|
+
title: item.title,
|
|
891
|
+
description: item.description,
|
|
892
|
+
...typeof item.updatedAt === "string" ? { updatedAt: item.updatedAt } : {}
|
|
893
|
+
};
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
async function loadAnimation(id) {
|
|
897
|
+
const res = await fetch(`${BASE}/${encodeURIComponent(id)}`);
|
|
898
|
+
const data = parseAnimationEnvelope(await readJson(res));
|
|
899
|
+
revisions.set(data.def.id, data.revision);
|
|
900
|
+
return data.def;
|
|
901
|
+
}
|
|
902
|
+
async function saveAnimation(def) {
|
|
903
|
+
const revision = revisions.get(def.id);
|
|
904
|
+
if (revision === void 0)
|
|
905
|
+
throw new MissingAnimationRevisionError(
|
|
906
|
+
`Animation '${def.id}' has no loaded revision`
|
|
907
|
+
);
|
|
908
|
+
const res = await fetch(`${BASE}/${encodeURIComponent(def.id)}`, {
|
|
909
|
+
method: "PUT",
|
|
910
|
+
headers: { "Content-Type": "application/json" },
|
|
911
|
+
body: JSON.stringify({ def, revision })
|
|
912
|
+
});
|
|
913
|
+
const data = parseAnimationEnvelope(await readJson(res));
|
|
914
|
+
revisions.set(data.def.id, data.revision);
|
|
915
|
+
return data.def;
|
|
916
|
+
}
|
|
917
|
+
async function createAnimation(id, title) {
|
|
918
|
+
const res = await fetch(BASE, {
|
|
919
|
+
method: "POST",
|
|
920
|
+
headers: { "Content-Type": "application/json" },
|
|
921
|
+
body: JSON.stringify({ id, title })
|
|
922
|
+
});
|
|
923
|
+
const data = parseAnimationEnvelope(await readJson(res));
|
|
924
|
+
revisions.set(data.def.id, data.revision);
|
|
925
|
+
return data.def;
|
|
926
|
+
}
|
|
927
|
+
async function deleteAnimation(id) {
|
|
928
|
+
const res = await fetch(`${BASE}/${encodeURIComponent(id)}`, {
|
|
929
|
+
method: "DELETE"
|
|
930
|
+
});
|
|
931
|
+
await readJson(res);
|
|
932
|
+
revisions.delete(id);
|
|
933
|
+
}
|
|
934
|
+
async function duplicateAnimation(sourceId, newId, newTitle) {
|
|
935
|
+
const source = await loadAnimation(sourceId);
|
|
936
|
+
const cloned = { ...source, id: newId, title: newTitle };
|
|
937
|
+
await createAnimation(newId, newTitle);
|
|
938
|
+
return await saveAnimation(cloned);
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
export { addAppearance, addChapter, addEffect, addElement, apiBaseUrl, beginTransient, canRedo, canUndo, childIdsOf, configureApi, configureHost, createAnimation, deleteAnimation, deleteChapter, deleteEffect, deleteElement, duplicateAnimation, endTransient, findContainingGroup, getCurrentSnapshot, getCurrentTime, getDef, getHistory, getSelectedElementIds, getSelection, groupBbox, groupElements, isDirty, isDraft, isElementSelected, isGroup, jumpBack, jumpForward, listAnimations, loadAnimation, markClean, moveElementToEnd, moveElementToFront, moveGroupBy, placeholderImageUrl, promoteDraftToSaved, redo, registerDataUriAsset, registerExternalAsset, registerInlineAsset, removeAppearance, removeTrack, removeTrackKeyframe, reorderElement, resetHistory, saveAnimation, setCurrentTime, setDef, setDraft, setElementValueAtTime, setSelection, setTrackKeyframe, subscribe, toggleSelectionFor, undo, ungroupElement, uniqueChapterId, uniqueEffectId, uniqueElementId, updateAppearance, updateCanvas, updateChapter, updateDuration, updateEffect, updateElementBase, updateMeta, updateSettings };
|
|
942
|
+
//# sourceMappingURL=chunk-UE32Q6U5.js.map
|
|
943
|
+
//# sourceMappingURL=chunk-UE32Q6U5.js.map
|