@kokoa/clotho-editor 0.1.0 → 0.1.3
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 +73 -13
- package/dist/{chunk-UE32Q6U5.js → chunk-Z2K5HFHI.js} +176 -102
- package/dist/chunk-Z2K5HFHI.js.map +1 -0
- package/dist/clotho-editor.css +145 -18
- package/dist/index.d.ts +38 -2
- package/dist/index.js +128 -17
- package/dist/index.js.map +1 -1
- package/dist/{main-SWHHYTLM.js → main-5YXLTYPQ.js} +1742 -985
- package/dist/main-5YXLTYPQ.js.map +1 -0
- package/docs/PORTING.md +16 -33
- package/package.json +19 -4
- package/dist/chunk-UE32Q6U5.js.map +0 -1
- package/dist/main-SWHHYTLM.js.map +0 -1
package/dist/clotho-editor.css
CHANGED
|
@@ -4,6 +4,27 @@ body.editor-active {
|
|
|
4
4
|
overflow: hidden;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
body.studio-timeline-popout {
|
|
8
|
+
min-width: 0;
|
|
9
|
+
margin: 0;
|
|
10
|
+
background: var(--color-surface-elevated, #f8fafc);
|
|
11
|
+
color: var(--color-fg, #0f172a);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body.studio-player-popout {
|
|
15
|
+
min-width: 0;
|
|
16
|
+
margin: 0;
|
|
17
|
+
padding: clamp(1rem, 3vw, 2.5rem);
|
|
18
|
+
box-sizing: border-box;
|
|
19
|
+
background: var(--color-bg, #f1f5f9);
|
|
20
|
+
color: var(--color-fg, #0f172a);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
body.studio-player-popout #clotho-player-preview {
|
|
24
|
+
width: min(100%, 1200px);
|
|
25
|
+
margin: 0 auto;
|
|
26
|
+
}
|
|
27
|
+
|
|
7
28
|
@layer components {
|
|
8
29
|
.studio-shell {
|
|
9
30
|
height: calc(100vh - 80px);
|
|
@@ -18,10 +39,18 @@ body.editor-active {
|
|
|
18
39
|
height: 100%;
|
|
19
40
|
flex: 1;
|
|
20
41
|
min-height: 0;
|
|
21
|
-
border: 1px solid var(--color-border);
|
|
42
|
+
border: 1px solid var(--color-border, #d1d5db);
|
|
22
43
|
border-radius: 0.5rem;
|
|
23
44
|
background-color: var(--color-surface);
|
|
24
45
|
overflow: hidden;
|
|
46
|
+
box-shadow: 0 8px 30px rgb(15 23 42 / 10%);
|
|
47
|
+
}
|
|
48
|
+
.studio-app.is-timeline-detached {
|
|
49
|
+
grid-template-rows: auto minmax(220px, 1fr) 0 0;
|
|
50
|
+
}
|
|
51
|
+
.studio-app.is-timeline-detached .studio-timeline-resizer,
|
|
52
|
+
.studio-app.is-timeline-detached .studio-timeline-wrap {
|
|
53
|
+
display: none;
|
|
25
54
|
}
|
|
26
55
|
.studio-timeline-resizer {
|
|
27
56
|
height: 6px;
|
|
@@ -33,25 +62,37 @@ body.editor-active {
|
|
|
33
62
|
transition: background-color 100ms ease-out;
|
|
34
63
|
}
|
|
35
64
|
.studio-timeline-resizer::before {
|
|
36
|
-
content:
|
|
65
|
+
content: none;
|
|
66
|
+
}
|
|
67
|
+
.studio-timeline-resizer-grip {
|
|
37
68
|
position: absolute;
|
|
38
69
|
left: 50%;
|
|
39
70
|
top: 50%;
|
|
40
|
-
|
|
41
|
-
|
|
71
|
+
display: grid;
|
|
72
|
+
gap: 2px;
|
|
73
|
+
width: 42px;
|
|
74
|
+
padding: 4px 7px;
|
|
42
75
|
transform: translate(-50%, -50%);
|
|
43
|
-
|
|
44
|
-
border-radius:
|
|
45
|
-
|
|
76
|
+
border: 1px solid var(--color-border, #9ca3af);
|
|
77
|
+
border-radius: 999px;
|
|
78
|
+
background: var(--color-surface-elevated, #f8fafc);
|
|
79
|
+
box-shadow: 0 1px 4px rgb(15 23 42 / 20%);
|
|
80
|
+
}
|
|
81
|
+
.studio-timeline-resizer-grip i {
|
|
82
|
+
display: block;
|
|
83
|
+
height: 1px;
|
|
84
|
+
background: var(--color-fg-muted, #64748b);
|
|
46
85
|
}
|
|
47
86
|
.studio-timeline-resizer:hover,
|
|
48
87
|
.studio-timeline-resizer.is-dragging {
|
|
49
88
|
background-color: var(--color-accent);
|
|
50
89
|
}
|
|
51
|
-
.studio-timeline-resizer:hover
|
|
52
|
-
.studio-timeline-resizer.is-dragging
|
|
53
|
-
|
|
54
|
-
|
|
90
|
+
.studio-timeline-resizer:hover .studio-timeline-resizer-grip,
|
|
91
|
+
.studio-timeline-resizer.is-dragging .studio-timeline-resizer-grip,
|
|
92
|
+
.studio-timeline-resizer:focus-visible .studio-timeline-resizer-grip {
|
|
93
|
+
border-color: var(--color-accent, #4f46e5);
|
|
94
|
+
box-shadow: 0 0 0 3px
|
|
95
|
+
color-mix(in srgb, var(--color-accent, #4f46e5) 20%, transparent);
|
|
55
96
|
}
|
|
56
97
|
.studio-header {
|
|
57
98
|
display: flex;
|
|
@@ -277,7 +318,9 @@ body.editor-active {
|
|
|
277
318
|
letter-spacing: 0.05em;
|
|
278
319
|
}
|
|
279
320
|
.studio-tool-btn {
|
|
280
|
-
display:
|
|
321
|
+
display: flex;
|
|
322
|
+
align-items: center;
|
|
323
|
+
gap: 0.35rem;
|
|
281
324
|
width: 100%;
|
|
282
325
|
text-align: left;
|
|
283
326
|
padding: 0.35rem 0.5rem;
|
|
@@ -289,10 +332,62 @@ body.editor-active {
|
|
|
289
332
|
cursor: pointer;
|
|
290
333
|
font-size: 0.78rem;
|
|
291
334
|
}
|
|
335
|
+
.studio-tool-btn kbd {
|
|
336
|
+
margin-left: auto;
|
|
337
|
+
min-width: 1.15rem;
|
|
338
|
+
padding: 0.05rem 0.22rem;
|
|
339
|
+
border: 1px solid var(--color-border);
|
|
340
|
+
border-radius: 0.22rem;
|
|
341
|
+
background: var(--color-surface-elevated);
|
|
342
|
+
color: var(--color-fg-muted);
|
|
343
|
+
font-family: var(--font-mono);
|
|
344
|
+
font-size: 0.6rem;
|
|
345
|
+
line-height: 1.15;
|
|
346
|
+
text-align: center;
|
|
347
|
+
}
|
|
348
|
+
.studio-tool-btn.is-active kbd {
|
|
349
|
+
border-color: color-mix(in srgb, currentColor 35%, transparent);
|
|
350
|
+
background: color-mix(in srgb, currentColor 10%, transparent);
|
|
351
|
+
color: inherit;
|
|
352
|
+
}
|
|
353
|
+
.studio-tool-with-option {
|
|
354
|
+
display: grid;
|
|
355
|
+
grid-template-columns: 1fr auto;
|
|
356
|
+
gap: 0.35rem;
|
|
357
|
+
align-items: center;
|
|
358
|
+
}
|
|
359
|
+
.studio-tool-with-option label {
|
|
360
|
+
display: inline-flex;
|
|
361
|
+
align-items: center;
|
|
362
|
+
gap: 0.2rem;
|
|
363
|
+
font-size: 0.68rem;
|
|
364
|
+
color: var(--color-fg-muted);
|
|
365
|
+
}
|
|
366
|
+
.studio-tool-with-option input {
|
|
367
|
+
width: 2.8rem;
|
|
368
|
+
padding: 0.25rem;
|
|
369
|
+
border: 1px solid var(--color-border);
|
|
370
|
+
border-radius: 0.25rem;
|
|
371
|
+
background: var(--color-surface);
|
|
372
|
+
color: var(--color-fg);
|
|
373
|
+
}
|
|
292
374
|
.studio-tool-btn:hover {
|
|
293
375
|
border-color: var(--color-accent);
|
|
294
376
|
color: var(--color-accent);
|
|
295
377
|
}
|
|
378
|
+
.studio-tool-btn.is-active {
|
|
379
|
+
border-color: var(--color-accent);
|
|
380
|
+
background: color-mix(
|
|
381
|
+
in srgb,
|
|
382
|
+
var(--color-accent) 16%,
|
|
383
|
+
var(--color-surface)
|
|
384
|
+
);
|
|
385
|
+
color: var(--color-accent);
|
|
386
|
+
box-shadow: inset 3px 0 0 var(--color-accent);
|
|
387
|
+
}
|
|
388
|
+
body:not([data-studio-tool="select"]) .studio-canvas {
|
|
389
|
+
cursor: crosshair;
|
|
390
|
+
}
|
|
296
391
|
.studio-element-search {
|
|
297
392
|
width: 100%;
|
|
298
393
|
box-sizing: border-box;
|
|
@@ -340,6 +435,18 @@ body.editor-active {
|
|
|
340
435
|
align-items: center;
|
|
341
436
|
gap: 0.25rem;
|
|
342
437
|
}
|
|
438
|
+
.studio-element-list-item {
|
|
439
|
+
padding-left: calc(0.35rem + var(--studio-tree-depth, 0) * 0.9rem);
|
|
440
|
+
}
|
|
441
|
+
.studio-element-list-item.is-group {
|
|
442
|
+
background: color-mix(
|
|
443
|
+
in srgb,
|
|
444
|
+
var(--color-accent) 9%,
|
|
445
|
+
var(--color-surface)
|
|
446
|
+
);
|
|
447
|
+
border-left: 3px solid var(--color-accent);
|
|
448
|
+
font-weight: 650;
|
|
449
|
+
}
|
|
343
450
|
.studio-element-list-item:hover {
|
|
344
451
|
background-color: var(--color-surface);
|
|
345
452
|
}
|
|
@@ -726,6 +833,11 @@ body.editor-active {
|
|
|
726
833
|
gap: 0.4rem;
|
|
727
834
|
min-height: 0;
|
|
728
835
|
}
|
|
836
|
+
body.studio-timeline-popout > .studio-timeline-wrap {
|
|
837
|
+
width: 100vw;
|
|
838
|
+
height: 100vh;
|
|
839
|
+
box-sizing: border-box;
|
|
840
|
+
}
|
|
729
841
|
.studio-timeline-tracks-wrap {
|
|
730
842
|
overflow-x: auto;
|
|
731
843
|
overflow-y: hidden;
|
|
@@ -755,6 +867,13 @@ body.editor-active {
|
|
|
755
867
|
.studio-speed input[type="range"] {
|
|
756
868
|
width: 100px;
|
|
757
869
|
}
|
|
870
|
+
.studio-timeline-option {
|
|
871
|
+
display: inline-flex;
|
|
872
|
+
align-items: center;
|
|
873
|
+
gap: 0.3rem;
|
|
874
|
+
font-size: 0.75rem;
|
|
875
|
+
color: var(--color-fg-muted);
|
|
876
|
+
}
|
|
758
877
|
.studio-timeline-tracks {
|
|
759
878
|
display: block;
|
|
760
879
|
padding-bottom: 0.25rem;
|
|
@@ -785,6 +904,13 @@ body.editor-active {
|
|
|
785
904
|
text-overflow: ellipsis;
|
|
786
905
|
white-space: nowrap;
|
|
787
906
|
}
|
|
907
|
+
.studio-tl-element-label {
|
|
908
|
+
padding-left: calc(0.5rem + var(--studio-tree-depth, 0) * 0.8rem);
|
|
909
|
+
}
|
|
910
|
+
.studio-tl-element-row.is-group .studio-tl-gutter {
|
|
911
|
+
color: var(--color-accent);
|
|
912
|
+
font-weight: 700;
|
|
913
|
+
}
|
|
788
914
|
.studio-tl-gutter-header {
|
|
789
915
|
font-weight: 700;
|
|
790
916
|
color: var(--color-accent);
|
|
@@ -1094,11 +1220,12 @@ body.editor-active {
|
|
|
1094
1220
|
width: min(640px, 92vw);
|
|
1095
1221
|
max-height: 80vh;
|
|
1096
1222
|
padding: 0;
|
|
1097
|
-
border: 1px solid var(--color-border);
|
|
1223
|
+
border: 1px solid var(--color-border, #d1d5db);
|
|
1098
1224
|
border-radius: 0.5rem;
|
|
1099
|
-
background-color: var(--color-surface);
|
|
1100
|
-
color: var(--color-fg);
|
|
1225
|
+
background-color: var(--color-surface, #ffffff);
|
|
1226
|
+
color: var(--color-fg, #111827);
|
|
1101
1227
|
overflow: hidden;
|
|
1228
|
+
box-shadow: 0 24px 64px rgb(0 0 0 / 35%);
|
|
1102
1229
|
}
|
|
1103
1230
|
.studio-dialog[open] {
|
|
1104
1231
|
display: flex;
|
|
@@ -1111,8 +1238,8 @@ body.editor-active {
|
|
|
1111
1238
|
width: min(420px, 92vw);
|
|
1112
1239
|
}
|
|
1113
1240
|
.studio-dialog::backdrop {
|
|
1114
|
-
background-color:
|
|
1115
|
-
backdrop-filter: blur(
|
|
1241
|
+
background-color: rgb(15 23 42 / 55%);
|
|
1242
|
+
backdrop-filter: blur(2px);
|
|
1116
1243
|
}
|
|
1117
1244
|
.studio-dialog-header {
|
|
1118
1245
|
display: flex;
|
|
@@ -1120,7 +1247,7 @@ body.editor-active {
|
|
|
1120
1247
|
align-items: center;
|
|
1121
1248
|
padding: 0.6rem 0.85rem;
|
|
1122
1249
|
border-bottom: 1px solid var(--color-border);
|
|
1123
|
-
background-color: var(--color-surface-elevated);
|
|
1250
|
+
background-color: var(--color-surface-elevated, #f8fafc);
|
|
1124
1251
|
}
|
|
1125
1252
|
.studio-dialog-header h2 {
|
|
1126
1253
|
margin: 0;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,10 +6,45 @@ interface StudioProps {
|
|
|
6
6
|
}
|
|
7
7
|
declare function Studio({ initial, onSave }: StudioProps): React.JSX.Element;
|
|
8
8
|
|
|
9
|
+
interface AnimationSummary {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
description: string;
|
|
13
|
+
updatedAt?: string;
|
|
14
|
+
source?: "saved" | "example";
|
|
15
|
+
}
|
|
16
|
+
interface AnimationRepository {
|
|
17
|
+
list(): Promise<AnimationSummary[]>;
|
|
18
|
+
load(id: string): Promise<AnimationDocument>;
|
|
19
|
+
create(id: string, title: string): Promise<AnimationDocument>;
|
|
20
|
+
save(def: AnimationDocument): Promise<AnimationDocument>;
|
|
21
|
+
delete(id: string): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
interface LocalStorageRepositoryOptions {
|
|
24
|
+
storageKey?: string;
|
|
25
|
+
examples?: readonly AnimationDocument[];
|
|
26
|
+
storage?: Pick<Storage, "getItem" | "setItem" | "removeItem">;
|
|
27
|
+
}
|
|
28
|
+
declare function createLocalStorageRepository(options?: LocalStorageRepositoryOptions): AnimationRepository;
|
|
29
|
+
|
|
30
|
+
type ImageUploadResolver = (file: File) => Promise<string> | string;
|
|
31
|
+
|
|
9
32
|
interface StudioMountProps {
|
|
10
33
|
initialId?: string;
|
|
34
|
+
repository?: AnimationRepository;
|
|
35
|
+
editorTitle?: string;
|
|
36
|
+
resolveImage?: ImageUploadResolver;
|
|
11
37
|
}
|
|
12
|
-
declare function StudioMount({ initialId, }: StudioMountProps): React.JSX.Element;
|
|
38
|
+
declare function StudioMount({ initialId, repository, editorTitle, resolveImage, }: StudioMountProps): React.JSX.Element;
|
|
39
|
+
|
|
40
|
+
/** 검증된 애니메이션 문서를 읽기 쉬운 JSON으로 변환한다. */
|
|
41
|
+
declare function animationDocumentToJson(def: AnimationDocument): string;
|
|
42
|
+
/** 어떤 image 요소에서도 참조하지 않는 asset을 제거한 새 문서를 반환한다. */
|
|
43
|
+
declare function garbageCollectAnimationAssets(def: AnimationDocument): AnimationDocument;
|
|
44
|
+
/** 문서 ID를 운영체제에서 안전하게 사용할 수 있는 파일 이름으로 바꾼다. */
|
|
45
|
+
declare function animationDocumentFileName(def: AnimationDocument): string;
|
|
46
|
+
/** 현재 문서를 브라우저에서 JSON 파일로 내려받는다. */
|
|
47
|
+
declare function downloadAnimationJson(def: AnimationDocument): void;
|
|
13
48
|
|
|
14
49
|
type Selection = {
|
|
15
50
|
kind: "none";
|
|
@@ -127,6 +162,7 @@ declare function configureApi(options: {
|
|
|
127
162
|
}): void;
|
|
128
163
|
/** The base currently in use, for hosts that need to build matching links. */
|
|
129
164
|
declare function apiBaseUrl(): string;
|
|
165
|
+
declare function configureAnimationRepository(next: AnimationRepository): void;
|
|
130
166
|
|
|
131
167
|
interface HostOptions {
|
|
132
168
|
/** Image used when inserting a placeholder image element. */
|
|
@@ -135,4 +171,4 @@ interface HostOptions {
|
|
|
135
171
|
declare function configureHost(options: HostOptions): void;
|
|
136
172
|
declare function placeholderImageUrl(): string;
|
|
137
173
|
|
|
138
|
-
export { type HistoryEntry, type HistoryKind, type HostOptions, type Selection, Studio, StudioMount, type StudioMountProps, type StudioProps, addAppearance, addChapter, addEffect, addElement, apiBaseUrl, beginTransient, canRedo, canUndo, childIdsOf, configureApi, configureHost, deleteChapter, deleteEffect, deleteElement, endTransient, 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 };
|
|
174
|
+
export { type AnimationRepository, type AnimationSummary, type HistoryEntry, type HistoryKind, type HostOptions, type ImageUploadResolver, type LocalStorageRepositoryOptions, type Selection, Studio, StudioMount, type StudioMountProps, type StudioProps, addAppearance, addChapter, addEffect, addElement, animationDocumentFileName, animationDocumentToJson, apiBaseUrl, beginTransient, canRedo, canUndo, childIdsOf, configureAnimationRepository, configureApi, configureHost, createLocalStorageRepository, 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 };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { downloadAnimationJson, configureAnimationRepository } from './chunk-Z2K5HFHI.js';
|
|
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-Z2K5HFHI.js';
|
|
2
3
|
import { useState, useEffect, useRef } from 'react';
|
|
3
4
|
import { usePlayer, AnimationStage } from '@kokoa/clotho/react';
|
|
4
5
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
|
+
import { animationDocumentSchema } from '@kokoa/clotho';
|
|
5
7
|
|
|
6
8
|
function elementsOf(def) {
|
|
7
9
|
return def.elements;
|
|
@@ -98,6 +100,15 @@ function Studio({ initial, onSave }) {
|
|
|
98
100
|
] }),
|
|
99
101
|
/* @__PURE__ */ jsxs("div", { className: "studio-preview", children: [
|
|
100
102
|
/* @__PURE__ */ jsxs("div", { className: "studio-preview-bar", children: [
|
|
103
|
+
/* @__PURE__ */ jsx(
|
|
104
|
+
"button",
|
|
105
|
+
{
|
|
106
|
+
type: "button",
|
|
107
|
+
className: "studio-btn",
|
|
108
|
+
onClick: () => downloadAnimationJson(def),
|
|
109
|
+
children: "JSON \uB0B4\uBCF4\uB0B4\uAE30"
|
|
110
|
+
}
|
|
111
|
+
),
|
|
101
112
|
/* @__PURE__ */ jsx(
|
|
102
113
|
"button",
|
|
103
114
|
{
|
|
@@ -224,7 +235,7 @@ var PRESET_HTML = CANVAS_PRESETS.map(
|
|
|
224
235
|
var SKELETON = `
|
|
225
236
|
<div id="studio-app" class="studio-app">
|
|
226
237
|
<header class="studio-header">
|
|
227
|
-
<h1 class="studio-header-title"
|
|
238
|
+
<h1 id="studio-editor-title" class="studio-header-title">Clotho Editor</h1>
|
|
228
239
|
<div class="studio-header-actions">
|
|
229
240
|
<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>
|
|
230
241
|
<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>
|
|
@@ -235,6 +246,10 @@ var SKELETON = `
|
|
|
235
246
|
<span id="studio-id-display" class="studio-id-display"></span>
|
|
236
247
|
<span id="studio-status" class="studio-status" aria-live="polite">\uB300\uAE30 \uC911</span>
|
|
237
248
|
<button type="button" id="studio-save" class="studio-btn studio-btn-primary" aria-label="\uC800\uC7A5" disabled>\u{1F4BE} \uC800\uC7A5 (\u2318S)</button>
|
|
249
|
+
<button type="button" id="studio-export-json" class="studio-btn" aria-label="\uD604\uC7AC \uC560\uB2C8\uBA54\uC774\uC158\uC744 JSON \uD30C\uC77C\uB85C \uB0B4\uBCF4\uB0B4\uAE30" disabled>\u2B07 JSON \uB0B4\uBCF4\uB0B4\uAE30</button>
|
|
250
|
+
<button type="button" id="studio-player-preview" class="studio-btn" aria-label="\uC2E4\uC81C \uC560\uB2C8\uBA54\uC774\uC158 \uCEF4\uD3EC\uB10C\uD2B8 \uBBF8\uB9AC\uBCF4\uAE30" aria-pressed="false">\u2197 \uC2E4\uC81C \uBBF8\uB9AC\uBCF4\uAE30</button>
|
|
251
|
+
<button type="button" id="studio-import-json" class="studio-btn" aria-label="JSON \uD30C\uC77C\uC5D0\uC11C \uC560\uB2C8\uBA54\uC774\uC158 \uAC00\uC838\uC624\uAE30">\u2B06 JSON \uAC00\uC838\uC624\uAE30</button>
|
|
252
|
+
<input type="file" id="studio-import-file" accept="application/json,.json" hidden />
|
|
238
253
|
<button type="button" id="studio-delete" class="studio-btn studio-btn-danger" aria-label="\uC0AD\uC81C" disabled>\u{1F5D1} \uC0AD\uC81C</button>
|
|
239
254
|
</div>
|
|
240
255
|
</header>
|
|
@@ -242,15 +257,16 @@ var SKELETON = `
|
|
|
242
257
|
<div class="studio-body">
|
|
243
258
|
<aside class="studio-tools" aria-label="\uC694\uC18C \uCD94\uAC00">
|
|
244
259
|
<div class="studio-tools-section">
|
|
245
|
-
<div class="studio-tools-title">\
|
|
246
|
-
<button type="button" class="studio-tool-btn" data-
|
|
247
|
-
<button type="button" class="studio-tool-btn" data-add-element="
|
|
248
|
-
<button type="button" class="studio-tool-btn" data-add-element="
|
|
249
|
-
<button type="button" class="studio-tool-btn" data-add-element="
|
|
250
|
-
<button type="button" class="studio-tool-btn" data-add-element="
|
|
251
|
-
<button type="button" class="studio-tool-btn" data-add-element="
|
|
252
|
-
<button type="button" class="studio-tool-btn" data-add-element="
|
|
253
|
-
<button type="button" class="studio-tool-btn" data-add-element="
|
|
260
|
+
<div class="studio-tools-title">\uB3C4\uAD6C</div>
|
|
261
|
+
<button type="button" class="studio-tool-btn is-active" data-studio-tool="select" aria-pressed="true" title="\uC120\uD0DD \xB7 \uC774\uB3D9 \xB7 \uD06C\uAE30 (V)"><span>\u2196 \uC120\uD0DD \xB7 \uC774\uB3D9 \xB7 \uD06C\uAE30</span><kbd>V</kbd></button>
|
|
262
|
+
<button type="button" class="studio-tool-btn" data-add-element="rect" title="\uC0AC\uAC01\uD615 (R)" aria-label="\uC0AC\uAC01\uD615 \uB3C4\uAD6C"><span>\u25A1 Rect</span><kbd>R</kbd></button>
|
|
263
|
+
<button type="button" class="studio-tool-btn" data-add-element="circle" title="\uC6D0 (O)" aria-label="\uC6D0 \uB3C4\uAD6C"><span>\u25CB Circle</span><kbd>O</kbd></button>
|
|
264
|
+
<button type="button" class="studio-tool-btn" data-add-element="line" title="\uC120 (L)" aria-label="\uC120 \uB3C4\uAD6C"><span>\uFF0F Line</span><kbd>L</kbd></button>
|
|
265
|
+
<button type="button" class="studio-tool-btn" data-add-element="arrow" title="\uD654\uC0B4\uD45C (A)" aria-label="\uD654\uC0B4\uD45C \uB3C4\uAD6C"><span>\u2197 Arrow</span><kbd>A</kbd></button>
|
|
266
|
+
<button type="button" class="studio-tool-btn" data-add-element="text" title="\uD14D\uC2A4\uD2B8 (T)" aria-label="\uD14D\uC2A4\uD2B8 \uB3C4\uAD6C"><span>T Text</span><kbd>T</kbd></button>
|
|
267
|
+
<button type="button" class="studio-tool-btn" data-add-element="image" title="\uC774\uBBF8\uC9C0 (I)" aria-label="\uC774\uBBF8\uC9C0 \uB3C4\uAD6C"><span>\u{1F5BC} Image</span><kbd>I</kbd></button>
|
|
268
|
+
<button type="button" class="studio-tool-btn" data-add-element="path" title="Path (B)" aria-label="Path \uB3C4\uAD6C"><span>\u270E Path</span><kbd>B</kbd></button>
|
|
269
|
+
<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>
|
|
254
270
|
<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>
|
|
255
271
|
</div>
|
|
256
272
|
<div class="studio-tools-section">
|
|
@@ -297,7 +313,7 @@ var SKELETON = `
|
|
|
297
313
|
</aside>
|
|
298
314
|
</div>
|
|
299
315
|
|
|
300
|
-
<div id="studio-timeline-resizer" class="studio-timeline-resizer" role="separator" aria-orientation="horizontal" aria-label="\uD0C0\uC784\uB77C\uC778 \uC601\uC5ED \uD06C\uAE30 \uC870\uC815"></div>
|
|
316
|
+
<div id="studio-timeline-resizer" class="studio-timeline-resizer" role="separator" aria-orientation="horizontal" aria-label="\uD0C0\uC784\uB77C\uC778 \uC601\uC5ED \uD06C\uAE30 \uC870\uC815" tabindex="0"><span class="studio-timeline-resizer-grip" aria-hidden="true"><i></i><i></i><i></i></span></div>
|
|
301
317
|
<footer class="studio-timeline-wrap">
|
|
302
318
|
<div class="studio-timeline-header">
|
|
303
319
|
<button type="button" id="studio-play" class="studio-btn">\u25B6 Play</button>
|
|
@@ -306,6 +322,8 @@ var SKELETON = `
|
|
|
306
322
|
<input type="range" id="studio-speed" min="0.25" max="3" step="0.25" value="1" />
|
|
307
323
|
<span id="studio-speed-value">1.00x</span>
|
|
308
324
|
</label>
|
|
325
|
+
<label class="studio-timeline-option"><input type="checkbox" id="studio-preview-loop" /> \uBB34\uD55C \uC7AC\uC0DD</label>
|
|
326
|
+
<button type="button" id="studio-detach-timeline" class="studio-btn" aria-pressed="false">\u25A3 \uD0C0\uC784\uB77C\uC778 \uBD84\uB9AC</button>
|
|
309
327
|
<span class="studio-timeline-spacer"></span>
|
|
310
328
|
<button type="button" id="studio-add-step" class="studio-btn">\uFF0B Chapter \uCD94\uAC00</button>
|
|
311
329
|
</div>
|
|
@@ -349,6 +367,10 @@ var SKELETON = `
|
|
|
349
367
|
<tr><th>\u2318 S</th><td>\uC800\uC7A5</td></tr>
|
|
350
368
|
<tr><th>\u2318 Z / \u2318 \u21E7 Z</th><td>Undo / Redo</td></tr>
|
|
351
369
|
<tr><th>\u2318 C / V / X</th><td>\uC694\uC18C \uBCF5\uC0AC / \uBD99\uC5EC\uB123\uAE30 / \uC798\uB77C\uB0B4\uAE30</td></tr>
|
|
370
|
+
<tr><th>V / R / O</th><td>\uC120\uD0DD / \uC0AC\uAC01\uD615 / \uC6D0 \uB3C4\uAD6C</td></tr>
|
|
371
|
+
<tr><th>L / A</th><td>\uC120 / \uD654\uC0B4\uD45C \uB3C4\uAD6C</td></tr>
|
|
372
|
+
<tr><th>T / I / B / Y</th><td>\uD14D\uC2A4\uD2B8 / \uC774\uBBF8\uC9C0 / Path / \uB2E4\uAC01\uD615 \uB3C4\uAD6C</td></tr>
|
|
373
|
+
<tr><th>\u2318\xB7Ctrl\xB7Shift + \uD074\uB9AD</th><td>\uC5EC\uB7EC \uC694\uC18C \uC120\uD0DD \uB610\uB294 \uC120\uD0DD \uD574\uC81C</td></tr>
|
|
352
374
|
<tr><th>Delete / Backspace</th><td>\uC120\uD0DD\uB41C \uC694\uC18C \uB610\uB294 step \uC0AD\uC81C</td></tr>
|
|
353
375
|
<tr><th>?</th><td>\uC774 \uD654\uBA74 \uC5F4\uAE30</td></tr>
|
|
354
376
|
<tr><th>Esc</th><td>\uC120\uD0DD \uD574\uC81C / \uB2E4\uC774\uC5BC\uB85C\uADF8 \uB2EB\uAE30</td></tr>
|
|
@@ -400,26 +422,115 @@ var SKELETON = `
|
|
|
400
422
|
</div>
|
|
401
423
|
`;
|
|
402
424
|
function StudioMount({
|
|
403
|
-
initialId
|
|
425
|
+
initialId,
|
|
426
|
+
repository,
|
|
427
|
+
editorTitle = "Clotho Editor",
|
|
428
|
+
resolveImage
|
|
404
429
|
}) {
|
|
405
430
|
const inited = useRef(false);
|
|
406
431
|
useEffect(() => {
|
|
407
432
|
if (inited.current) return;
|
|
408
433
|
inited.current = true;
|
|
434
|
+
if (repository) configureAnimationRepository(repository);
|
|
409
435
|
let disposed = false;
|
|
410
|
-
void import('./main-
|
|
436
|
+
void import('./main-5YXLTYPQ.js').then(({ initStudio }) => {
|
|
411
437
|
if (disposed) return;
|
|
412
|
-
initStudio(
|
|
438
|
+
initStudio({ initialId, editorTitle, resolveImage });
|
|
413
439
|
});
|
|
414
440
|
return () => {
|
|
415
441
|
disposed = true;
|
|
416
442
|
document.body.classList.remove("editor-active");
|
|
417
443
|
document.documentElement.classList.remove("editor-active");
|
|
418
444
|
};
|
|
419
|
-
}, [initialId]);
|
|
445
|
+
}, [editorTitle, initialId, repository, resolveImage]);
|
|
420
446
|
return /* @__PURE__ */ jsx("section", { className: "studio-shell w-full", "data-pagefind-ignore": "all", children: /* @__PURE__ */ jsx("div", { dangerouslySetInnerHTML: { __html: SKELETON } }) });
|
|
421
447
|
}
|
|
448
|
+
function browserStorage() {
|
|
449
|
+
if (typeof localStorage === "undefined")
|
|
450
|
+
throw new Error("\uC774 \uC800\uC7A5\uC18C\uB294 \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C\uB9CC \uC0AC\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.");
|
|
451
|
+
return localStorage;
|
|
452
|
+
}
|
|
453
|
+
function createLocalStorageRepository(options = {}) {
|
|
454
|
+
const prefix = options.storageKey ?? "clotho-editor.animations";
|
|
455
|
+
const examples = new Map(
|
|
456
|
+
(options.examples ?? []).map((value) => {
|
|
457
|
+
const def = animationDocumentSchema.parse(value);
|
|
458
|
+
return [def.id, def];
|
|
459
|
+
})
|
|
460
|
+
);
|
|
461
|
+
const storage = () => options.storage ?? browserStorage();
|
|
462
|
+
const indexKey = `${prefix}:index`;
|
|
463
|
+
const documentKey = (id) => `${prefix}:document:${id}`;
|
|
464
|
+
const readIds = () => {
|
|
465
|
+
const value = storage().getItem(indexKey);
|
|
466
|
+
if (!value) return [];
|
|
467
|
+
try {
|
|
468
|
+
const parsed = JSON.parse(value);
|
|
469
|
+
return Array.isArray(parsed) ? parsed.filter((id) => typeof id === "string") : [];
|
|
470
|
+
} catch {
|
|
471
|
+
return [];
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
const write = (def) => {
|
|
475
|
+
const parsed = animationDocumentSchema.parse(def);
|
|
476
|
+
const ids = readIds();
|
|
477
|
+
if (!ids.includes(parsed.id))
|
|
478
|
+
storage().setItem(indexKey, JSON.stringify([...ids, parsed.id]));
|
|
479
|
+
storage().setItem(documentKey(parsed.id), JSON.stringify(parsed));
|
|
480
|
+
return parsed;
|
|
481
|
+
};
|
|
482
|
+
const read = (id) => {
|
|
483
|
+
const value = storage().getItem(documentKey(id));
|
|
484
|
+
return value ? animationDocumentSchema.parse(JSON.parse(value)) : null;
|
|
485
|
+
};
|
|
486
|
+
return {
|
|
487
|
+
async list() {
|
|
488
|
+
const saved = readIds().map((id) => read(id)).filter((def) => def !== null);
|
|
489
|
+
const savedIds = new Set(saved.map((def) => def.id));
|
|
490
|
+
return [
|
|
491
|
+
...saved.map((def) => ({
|
|
492
|
+
id: def.id,
|
|
493
|
+
title: def.title || def.id,
|
|
494
|
+
description: def.description,
|
|
495
|
+
source: "saved"
|
|
496
|
+
})),
|
|
497
|
+
...[...examples.values()].filter((def) => !savedIds.has(def.id)).map((def) => ({
|
|
498
|
+
id: def.id,
|
|
499
|
+
title: def.title || def.id,
|
|
500
|
+
description: def.description,
|
|
501
|
+
source: "example"
|
|
502
|
+
}))
|
|
503
|
+
];
|
|
504
|
+
},
|
|
505
|
+
async load(id) {
|
|
506
|
+
const def = read(id) ?? examples.get(id);
|
|
507
|
+
if (!def) throw new Error(`'${id}' \uC560\uB2C8\uBA54\uC774\uC158\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`);
|
|
508
|
+
return structuredClone(def);
|
|
509
|
+
},
|
|
510
|
+
async create(id, title) {
|
|
511
|
+
if (read(id)) throw new Error(`'${id}' \uC560\uB2C8\uBA54\uC774\uC158\uC774 \uC774\uBBF8 \uC788\uC2B5\uB2C8\uB2E4.`);
|
|
512
|
+
return write(
|
|
513
|
+
animationDocumentSchema.parse({
|
|
514
|
+
clothoVersion: 1,
|
|
515
|
+
id,
|
|
516
|
+
title,
|
|
517
|
+
description: ""
|
|
518
|
+
})
|
|
519
|
+
);
|
|
520
|
+
},
|
|
521
|
+
async save(def) {
|
|
522
|
+
return write(def);
|
|
523
|
+
},
|
|
524
|
+
async delete(id) {
|
|
525
|
+
storage().removeItem(documentKey(id));
|
|
526
|
+
storage().setItem(
|
|
527
|
+
indexKey,
|
|
528
|
+
JSON.stringify(readIds().filter((value) => value !== id))
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
}
|
|
422
533
|
|
|
423
|
-
export { Studio, StudioMount };
|
|
534
|
+
export { Studio, StudioMount, createLocalStorageRepository };
|
|
424
535
|
//# sourceMappingURL=index.js.map
|
|
425
536
|
//# sourceMappingURL=index.js.map
|