@kokoa/clotho-editor 0.1.3 → 0.1.4
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 +55 -62
- package/dist/{chunk-Z2K5HFHI.js → chunk-GMGB7NIL.js} +25 -4
- package/dist/chunk-GMGB7NIL.js.map +1 -0
- package/dist/clotho-editor.css +5 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/{main-5YXLTYPQ.js → main-NCPWDB2U.js} +63 -6
- package/dist/main-NCPWDB2U.js.map +1 -0
- package/package.json +3 -3
- package/dist/chunk-Z2K5HFHI.js.map +0 -1
- package/dist/main-5YXLTYPQ.js.map +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/Studio.tsx","../src/StudioMount.tsx","../src/repository.ts"],"names":["setDef","addElement","deleteElement","useEffect","jsx"],"mappings":"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","file":"index.js","sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { AnimationDocument } from \"@kokoa/clotho\";\nimport { AnimationStage, usePlayer } from \"@kokoa/clotho/react\";\nimport { downloadAnimationJson } from \"./export-json\";\n\ntype EditableElement = Record<string, unknown> & { id: string; type: string };\ntype ElementList = AnimationDocument[\"elements\"];\n\nexport interface StudioProps {\n initial: AnimationDocument;\n onSave: (def: AnimationDocument) => void | Promise<void>;\n}\n\nfunction elementsOf(def: AnimationDocument): EditableElement[] {\n return def.elements as unknown as EditableElement[];\n}\n\nexport function Studio({ initial, onSave }: StudioProps): React.JSX.Element {\n const [def, setDef] = useState<AnimationDocument>(initial);\n const [selectedId, setSelectedId] = useState<string | null>(\n elementsOf(initial)[0]?.id ?? null,\n );\n const [playing, setPlaying] = useState(false);\n const [time, setTime] = useState(0);\n const [saving, setSaving] = useState(false);\n const [saved, setSaved] = useState(false);\n\n // The clock for preview playback. It lives outside React (core/player), so the\n // editor subscribes rather than owning a rAF loop of its own.\n const { player, state: playerState } = usePlayer(def, { autoplay: false });\n\n useEffect(() => {\n if (playing) player.play();\n else {\n player.pause();\n player.seek(time);\n }\n }, [playing, player, time]);\n\n const elements = elementsOf(def);\n const selected = elements.find((e) => e.id === selectedId) ?? null;\n const scalarProps = selected\n ? Object.entries(selected).filter(\n ([key, value]) =>\n (typeof value === \"string\" || typeof value === \"number\") &&\n key !== \"id\" &&\n key !== \"type\",\n )\n : [];\n\n function setElements(next: EditableElement[]): void {\n setDef((current) => ({\n ...current,\n elements: next as unknown as ElementList,\n }));\n setSaved(false);\n }\n\n function updateElement(\n id: string,\n key: string,\n raw: string,\n numeric: boolean,\n ): void {\n setElements(\n elements.map((e) =>\n e.id === id ? { ...e, [key]: numeric ? Number(raw) : raw } : e,\n ),\n );\n }\n\n function addElement(): void {\n const id = `el-${Date.now().toString(36)}`;\n const el: EditableElement = {\n type: \"text\",\n id,\n x: 100,\n y: 100,\n rotation: 0,\n content: \"New\",\n fontSize: 24,\n color: \"#4f46e5\",\n textAnchor: \"start\",\n appearances: [{ start: 0, end: def.duration }],\n tracks: [],\n };\n setElements([...elements, el]);\n setSelectedId(id);\n }\n\n function deleteElement(id: string): void {\n setElements(elements.filter((e) => e.id !== id));\n if (selectedId === id) setSelectedId(null);\n }\n\n async function handleSave(): Promise<void> {\n setSaving(true);\n try {\n await onSave(def);\n setSaved(true);\n } finally {\n setSaving(false);\n }\n }\n\n return (\n <div className=\"studio\">\n <aside className=\"studio-panel studio-left\">\n <div className=\"studio-panel-head\">\n <span>요소 ({elements.length})</span>\n <button type=\"button\" className=\"studio-btn\" onClick={addElement}>\n + 추가\n </button>\n </div>\n <ul className=\"studio-el-list\">\n {elements.map((e) => (\n <li key={e.id}>\n <button\n type=\"button\"\n className={\n e.id === selectedId ? \"studio-el active\" : \"studio-el\"\n }\n onClick={() => setSelectedId(e.id)}\n >\n <span className=\"studio-el-type\">{e.type}</span>\n <span className=\"studio-el-id\">{e.id}</span>\n </button>\n </li>\n ))}\n </ul>\n </aside>\n\n <div className=\"studio-preview\">\n <div className=\"studio-preview-bar\">\n <button\n type=\"button\"\n className=\"studio-btn\"\n onClick={() => downloadAnimationJson(def)}\n >\n JSON 내보내기\n </button>\n <button\n type=\"button\"\n className=\"studio-btn\"\n onClick={() => setPlaying((p) => !p)}\n >\n {playing ? \"⏸ 정지\" : \"▶ 재생\"}\n </button>\n <input\n type=\"range\"\n min={0}\n max={def.duration}\n step={50}\n value={time}\n disabled={playing}\n onChange={(e) => {\n setTime(Number(e.target.value));\n setPlaying(false);\n }}\n />\n <span className=\"studio-time\">\n {time} / {def.duration}ms\n </span>\n <button\n type=\"button\"\n className=\"studio-btn studio-save\"\n onClick={() => void handleSave()}\n disabled={saving}\n >\n {saving ? \"저장 중…\" : saved ? \"✓ 저장됨\" : \"저장\"}\n </button>\n </div>\n <div className=\"studio-canvas\">\n {/* One frame at a time: the editor's scrubber is the source of truth, and the\n player only supplies the clock while previewing. Same buildScene path as\n the published animation, so preview and production cannot diverge. */}\n <AnimationStage doc={def} time={playing ? playerState.time : time} />\n </div>\n </div>\n\n <aside className=\"studio-panel studio-right\">\n <div className=\"studio-panel-head\">메타</div>\n <label className=\"studio-field\">\n <span>제목</span>\n <input\n value={def.title}\n onChange={(e) => {\n setDef((d) => ({ ...d, title: e.target.value }));\n setSaved(false);\n }}\n />\n </label>\n <label className=\"studio-field\">\n <span>duration (ms)</span>\n <input\n type=\"number\"\n value={def.duration}\n onChange={(e) => {\n setDef((d) => ({ ...d, duration: Number(e.target.value) }));\n setSaved(false);\n }}\n />\n </label>\n <div className=\"studio-panel-head\">\n 속성 {selected ? `(${selected.type})` : \"\"}\n </div>\n {selected ? (\n <>\n {scalarProps.map(([key, value]) => (\n <label key={key} className=\"studio-field\">\n <span>{key}</span>\n <input\n value={String(value)}\n onChange={(e) =>\n updateElement(\n selected.id,\n key,\n e.target.value,\n typeof value === \"number\",\n )\n }\n />\n </label>\n ))}\n <button\n type=\"button\"\n className=\"studio-btn studio-del\"\n onClick={() => deleteElement(selected.id)}\n >\n 요소 삭제\n </button>\n </>\n ) : (\n <p className=\"studio-empty\">요소를 선택하세요.</p>\n )}\n </aside>\n </div>\n );\n}\n","\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport type { AnimationRepository } from \"./repository\";\nimport { configureAnimationRepository } from \"./legacy/api\";\nimport type { ImageUploadResolver } from \"./legacy/studio-image-upload\";\n// The stylesheet ships as a separate entry rather than an import, so a consumer that\n// only wants the headless pieces does not pull CSS into its bundle:\n// import \"@kokoa/clotho-editor/styles.css\";\n\nexport interface StudioMountProps {\n initialId?: string;\n repository?: AnimationRepository;\n editorTitle?: string;\n resolveImage?: ImageUploadResolver;\n}\n\nconst CANVAS_PRESETS: {\n id: string;\n label: string;\n sub: string;\n w: number;\n h: number;\n}[] = [\n { id: \"800x500\", label: \"8:5\", sub: \"800×500\", w: 32, h: 20 },\n { id: \"1280x720\", label: \"16:9\", sub: \"HD\", w: 32, h: 18 },\n { id: \"1024x768\", label: \"4:3\", sub: \"1024×768\", w: 28, h: 21 },\n { id: \"600x600\", label: \"1:1\", sub: \"정사각\", w: 22, h: 22 },\n { id: \"1200x400\", label: \"3:1\", sub: \"와이드\", w: 36, h: 12 },\n { id: \"375x812\", label: \"9:19\", sub: \"모바일\", w: 14, h: 30 },\n];\n\nconst PRESET_HTML = CANVAS_PRESETS.map(\n (p) => `\n <button type=\"button\" class=\"studio-preset-btn\" aria-label=\"캔버스 크기 ${p.id} (${p.sub})\" title=\"${p.id} · ${p.sub}\" data-canvas-preset=\"${p.id}\">\n <svg class=\"studio-preset-thumb\" viewBox=\"0 0 ${p.w + 2} ${p.h + 2}\" width=\"${p.w + 2}\" height=\"${p.h + 2}\" aria-hidden=\"true\">\n <rect x=\"1\" y=\"1\" width=\"${p.w}\" height=\"${p.h}\" rx=\"2\" />\n </svg>\n <span class=\"studio-preset-label\">${p.label}</span>\n <span class=\"studio-preset-sub\">${p.sub}</span>\n </button>`,\n).join(\"\");\n\nconst SKELETON = `\n<div id=\"studio-app\" class=\"studio-app\">\n <header class=\"studio-header\">\n <h1 id=\"studio-editor-title\" class=\"studio-header-title\">Clotho Editor</h1>\n <div class=\"studio-header-actions\">\n <button type=\"button\" id=\"studio-open\" class=\"studio-btn\" aria-label=\"저장된 애니메이션 열기\">📁 열기</button>\n <button type=\"button\" id=\"studio-new\" class=\"studio-btn\" aria-label=\"새 애니메이션 만들기\">+ 새 애니메이션</button>\n <button type=\"button\" id=\"studio-undo\" class=\"studio-btn studio-btn-icon\" aria-label=\"실행 취소\" title=\"실행 취소 (⌘Z)\" disabled>↶</button>\n <button type=\"button\" id=\"studio-redo\" class=\"studio-btn studio-btn-icon\" aria-label=\"다시 실행\" title=\"다시 실행 (⌘⇧Z / ⌘Y)\" disabled>↷</button>\n <button type=\"button\" id=\"studio-grid-toggle\" class=\"studio-btn studio-btn-grid\" aria-label=\"격자 + 스냅\" title=\"격자 + 스냅 (G)\" aria-pressed=\"false\"><span class=\"studio-btn-grid-icon\">⊞</span><span class=\"studio-btn-grid-label\" id=\"studio-grid-label\">격자 끔</span></button>\n <input type=\"text\" id=\"studio-title\" class=\"studio-title-input\" placeholder=\"제목\" aria-label=\"애니메이션 제목\" disabled />\n <span id=\"studio-id-display\" class=\"studio-id-display\"></span>\n <span id=\"studio-status\" class=\"studio-status\" aria-live=\"polite\">대기 중</span>\n <button type=\"button\" id=\"studio-save\" class=\"studio-btn studio-btn-primary\" aria-label=\"저장\" disabled>💾 저장 (⌘S)</button>\n <button type=\"button\" id=\"studio-export-json\" class=\"studio-btn\" aria-label=\"현재 애니메이션을 JSON 파일로 내보내기\" disabled>⬇ JSON 내보내기</button>\n <button type=\"button\" id=\"studio-player-preview\" class=\"studio-btn\" aria-label=\"실제 애니메이션 컴포넌트 미리보기\" aria-pressed=\"false\">↗ 실제 미리보기</button>\n <button type=\"button\" id=\"studio-import-json\" class=\"studio-btn\" aria-label=\"JSON 파일에서 애니메이션 가져오기\">⬆ JSON 가져오기</button>\n <input type=\"file\" id=\"studio-import-file\" accept=\"application/json,.json\" hidden />\n <button type=\"button\" id=\"studio-delete\" class=\"studio-btn studio-btn-danger\" aria-label=\"삭제\" disabled>🗑 삭제</button>\n </div>\n </header>\n\n <div class=\"studio-body\">\n <aside class=\"studio-tools\" aria-label=\"요소 추가\">\n <div class=\"studio-tools-section\">\n <div class=\"studio-tools-title\">도구</div>\n <button type=\"button\" class=\"studio-tool-btn is-active\" data-studio-tool=\"select\" aria-pressed=\"true\" title=\"선택 · 이동 · 크기 (V)\"><span>↖ 선택 · 이동 · 크기</span><kbd>V</kbd></button>\n <button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"rect\" title=\"사각형 (R)\" aria-label=\"사각형 도구\"><span>□ Rect</span><kbd>R</kbd></button>\n <button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"circle\" title=\"원 (O)\" aria-label=\"원 도구\"><span>○ Circle</span><kbd>O</kbd></button>\n <button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"line\" title=\"선 (L)\" aria-label=\"선 도구\"><span>/ Line</span><kbd>L</kbd></button>\n <button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"arrow\" title=\"화살표 (A)\" aria-label=\"화살표 도구\"><span>↗ Arrow</span><kbd>A</kbd></button>\n <button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"text\" title=\"텍스트 (T)\" aria-label=\"텍스트 도구\"><span>T Text</span><kbd>T</kbd></button>\n <button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"image\" title=\"이미지 (I)\" aria-label=\"이미지 도구\"><span>🖼 Image</span><kbd>I</kbd></button>\n <button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"path\" title=\"Path (B)\" aria-label=\"Path 도구\"><span>✎ Path</span><kbd>B</kbd></button>\n <div class=\"studio-tool-with-option\"><button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"polygon\" title=\"다각형 (Y)\" aria-label=\"다각형 도구\"><span>⬢ Polygon</span><kbd>Y</kbd></button><label>변 <input type=\"number\" id=\"studio-polygon-sides\" min=\"3\" max=\"24\" value=\"6\" aria-label=\"다각형의 변 개수\" /></label></div>\n <button type=\"button\" class=\"studio-tool-btn\" id=\"studio-open-icons\" title=\"아이콘 라이브러리\" aria-label=\"아이콘 라이브러리 추가\">🎨 Icons</button>\n </div>\n <div class=\"studio-tools-section\">\n <div class=\"studio-tools-title\">캔버스 크기</div>\n <div class=\"studio-canvas-size-row\">\n <input type=\"number\" id=\"studio-canvas-width\" class=\"studio-canvas-size-input\" min=\"100\" max=\"8000\" step=\"10\" placeholder=\"width\" />\n <span class=\"studio-canvas-size-x\">×</span>\n <input type=\"number\" id=\"studio-canvas-height\" class=\"studio-canvas-size-input\" min=\"100\" max=\"8000\" step=\"10\" placeholder=\"height\" />\n </div>\n <div class=\"studio-canvas-presets\">${PRESET_HTML}</div>\n </div>\n <div class=\"studio-tools-section\">\n <div class=\"studio-tools-title\">이미지</div>\n <button type=\"button\" class=\"studio-tool-btn\" id=\"studio-image-upload\">📤 이미지 업로드</button>\n <input type=\"file\" id=\"studio-image-file\" accept=\"image/*\" hidden />\n <div class=\"studio-tools-hint\">이미지를 캔버스에 직접 드래그하거나 ⌘V 로 붙여넣어도 됩니다.</div>\n </div>\n <div class=\"studio-tools-section\">\n <div class=\"studio-tools-title\">단축키</div>\n <button type=\"button\" class=\"studio-tool-btn\" id=\"studio-help\">⌨ 단축키 보기 (?)</button>\n </div>\n <div class=\"studio-tools-section\">\n <div class=\"studio-tools-title\">요소 목록</div>\n <input type=\"text\" id=\"studio-element-search\" class=\"studio-element-search\" placeholder=\"🔍 검색 (name / label / id / type)\" autocomplete=\"off\" />\n <ul id=\"studio-element-list\" class=\"studio-element-list\"></ul>\n </div>\n </aside>\n\n <main class=\"studio-canvas-wrap\">\n <div class=\"studio-canvas-frame\">\n <svg id=\"studio-canvas\" class=\"studio-canvas\" xmlns=\"http://www.w3.org/2000/svg\"></svg>\n </div>\n <button type=\"button\" id=\"studio-floating-help\" class=\"studio-floating-help\" aria-label=\"단축키 보기\" title=\"단축키 보기 (? / Shift+/)\">?</button>\n <div id=\"studio-help-hint\" class=\"studio-help-hint\" hidden>\n <span>💡 단축키는 <kbd>?</kbd> 또는 우측 하단 버튼으로 확인하세요</span>\n <button type=\"button\" id=\"studio-help-hint-close\" aria-label=\"닫기\">✕</button>\n </div>\n </main>\n\n <aside class=\"studio-props\" aria-label=\"속성 패널\">\n <div id=\"studio-props-content\" class=\"studio-props-content\">\n <p class=\"studio-props-empty\">요소 또는 step 을 선택하세요.</p>\n </div>\n </aside>\n </div>\n\n <div id=\"studio-timeline-resizer\" class=\"studio-timeline-resizer\" role=\"separator\" aria-orientation=\"horizontal\" aria-label=\"타임라인 영역 크기 조정\" tabindex=\"0\"><span class=\"studio-timeline-resizer-grip\" aria-hidden=\"true\"><i></i><i></i><i></i></span></div>\n <footer class=\"studio-timeline-wrap\">\n <div class=\"studio-timeline-header\">\n <button type=\"button\" id=\"studio-play\" class=\"studio-btn\">▶ Play</button>\n <button type=\"button\" id=\"studio-restart\" class=\"studio-btn\">⟲ Reset</button>\n <label class=\"studio-speed\">속도\n <input type=\"range\" id=\"studio-speed\" min=\"0.25\" max=\"3\" step=\"0.25\" value=\"1\" />\n <span id=\"studio-speed-value\">1.00x</span>\n </label>\n <label class=\"studio-timeline-option\"><input type=\"checkbox\" id=\"studio-preview-loop\" /> 무한 재생</label>\n <button type=\"button\" id=\"studio-detach-timeline\" class=\"studio-btn\" aria-pressed=\"false\">▣ 타임라인 분리</button>\n <span class=\"studio-timeline-spacer\"></span>\n <button type=\"button\" id=\"studio-add-step\" class=\"studio-btn\">+ Chapter 추가</button>\n </div>\n <div class=\"studio-timeline-tracks-wrap\">\n <div id=\"studio-timeline-tracks\" class=\"studio-timeline-tracks\"></div>\n </div>\n <div class=\"studio-element-tracks-wrap\">\n <div id=\"studio-element-tracks\" class=\"studio-element-tracks\"></div>\n </div>\n </footer>\n\n <dialog id=\"studio-library-dialog\" class=\"studio-dialog\">\n <div class=\"studio-dialog-header\">\n <h2>저장된 애니메이션</h2>\n <button type=\"button\" class=\"studio-dialog-close\" data-studio-dialog-close>✕</button>\n </div>\n <div class=\"studio-dialog-body\">\n <ul id=\"studio-library-list\" class=\"studio-library-list\"></ul>\n </div>\n </dialog>\n\n <dialog id=\"studio-icon-dialog\" class=\"studio-dialog\">\n <div class=\"studio-dialog-header\">\n <h2>🎨 아이콘 라이브러리</h2>\n <button type=\"button\" class=\"studio-dialog-close\" data-icon-dialog-close>✕</button>\n </div>\n <div class=\"studio-dialog-body\">\n <input type=\"search\" id=\"studio-icon-search\" placeholder=\"아이콘 검색…\" class=\"studio-icon-search-input\" />\n <div id=\"studio-icon-list\"></div>\n </div>\n </dialog>\n\n <dialog id=\"studio-help-dialog\" class=\"studio-dialog studio-dialog-small\">\n <div class=\"studio-dialog-header\">\n <h2>⌨ 단축키</h2>\n <button type=\"button\" class=\"studio-dialog-close\" data-studio-dialog-close>✕</button>\n </div>\n <div class=\"studio-dialog-body\">\n <table class=\"studio-help-table\">\n <tbody>\n <tr><th>⌘ S</th><td>저장</td></tr>\n <tr><th>⌘ Z / ⌘ ⇧ Z</th><td>Undo / Redo</td></tr>\n <tr><th>⌘ C / V / X</th><td>요소 복사 / 붙여넣기 / 잘라내기</td></tr>\n <tr><th>V / R / O</th><td>선택 / 사각형 / 원 도구</td></tr>\n <tr><th>L / A</th><td>선 / 화살표 도구</td></tr>\n <tr><th>T / I / B / Y</th><td>텍스트 / 이미지 / Path / 다각형 도구</td></tr>\n <tr><th>⌘·Ctrl·Shift + 클릭</th><td>여러 요소 선택 또는 선택 해제</td></tr>\n <tr><th>Delete / Backspace</th><td>선택된 요소 또는 step 삭제</td></tr>\n <tr><th>?</th><td>이 화면 열기</td></tr>\n <tr><th>Esc</th><td>선택 해제 / 다이얼로그 닫기</td></tr>\n <tr><th>드래그 (캔버스)</th><td>요소 이동</td></tr>\n <tr><th>드래그 (요소 위 dot)</th><td>화살표 연결</td></tr>\n <tr><th>드래그 (선/화살표 끝점)</th><td>끝점 재배치 + 다른 요소에 sticky</td></tr>\n <tr><th>드래그 (회전 핸들)</th><td>요소 회전</td></tr>\n <tr><th>드래그 (타임라인 바 우측)</th><td>step duration 조정</td></tr>\n <tr><th>이미지 드래그 in</th><td>이미지 업로드 + 배치</td></tr>\n </tbody>\n </table>\n </div>\n </dialog>\n\n <dialog id=\"studio-new-dialog\" class=\"studio-dialog studio-dialog-small\">\n <div class=\"studio-dialog-header\">\n <h2 id=\"studio-new-dialog-title\">새 애니메이션 만들기</h2>\n <button type=\"button\" class=\"studio-dialog-close\" data-studio-dialog-close aria-label=\"닫기\">✕</button>\n </div>\n <div class=\"studio-dialog-body\">\n <label class=\"studio-field\"><span>ID (영문 소문자 / 숫자 / - / _)</span>\n <input type=\"text\" id=\"studio-new-id\" placeholder=\"예: user-login-flow\" />\n </label>\n <label class=\"studio-field\"><span>제목</span>\n <input type=\"text\" id=\"studio-new-title\" placeholder=\"사용자 로그인 흐름\" />\n </label>\n <div id=\"studio-new-error\" class=\"studio-new-error\"></div>\n </div>\n <footer class=\"studio-dialog-footer\">\n <button type=\"button\" class=\"studio-btn\" data-studio-dialog-close>취소</button>\n <button type=\"button\" id=\"studio-new-create\" class=\"studio-btn studio-btn-primary\">만들기</button>\n </footer>\n </dialog>\n\n <dialog id=\"studio-palette-dialog\" class=\"studio-palette-dialog\">\n <input type=\"text\" id=\"studio-palette-input\" class=\"studio-palette-input\" placeholder=\"🔍 명령 또는 요소 검색…\" autocomplete=\"off\" spellcheck=\"false\" />\n <ul id=\"studio-palette-list\" class=\"studio-palette-list\"></ul>\n <div class=\"studio-palette-footer\">↑↓ 이동 · Enter 실행 · Esc 닫기</div>\n </dialog>\n\n <dialog id=\"studio-history-dialog\" class=\"studio-history-dialog\">\n <div class=\"studio-history-header\">\n <h2>📜 작업 이력</h2>\n <button type=\"button\" class=\"studio-dialog-close\" data-studio-dialog-close aria-label=\"닫기\">✕</button>\n </div>\n <ul id=\"studio-history-list\" class=\"studio-history-list\"></ul>\n <div class=\"studio-history-footer\">항목을 클릭하면 해당 시점으로 이동합니다 · ⌘Z / ⌘⇧Z</div>\n </dialog>\n</div>\n`;\n\nexport function StudioMount({\n initialId,\n repository,\n editorTitle = \"Clotho Editor\",\n resolveImage,\n}: StudioMountProps): React.JSX.Element {\n const inited = useRef(false);\n\n useEffect(() => {\n if (inited.current) return;\n inited.current = true;\n if (repository) configureAnimationRepository(repository);\n let disposed = false;\n void import(\"./legacy/main\").then(({ initStudio }) => {\n if (disposed) return;\n initStudio({ initialId, editorTitle, resolveImage });\n });\n return () => {\n disposed = true;\n document.body.classList.remove(\"editor-active\");\n document.documentElement.classList.remove(\"editor-active\");\n };\n }, [editorTitle, initialId, repository, resolveImage]);\n\n return (\n <section className=\"studio-shell w-full\" data-pagefind-ignore=\"all\">\n <div dangerouslySetInnerHTML={{ __html: SKELETON }} />\n </section>\n );\n}\n","import { animationDocumentSchema, type AnimationDocument } from \"@kokoa/clotho\";\n\nexport interface AnimationSummary {\n id: string;\n title: string;\n description: string;\n updatedAt?: string;\n source?: \"saved\" | \"example\";\n}\n\nexport interface AnimationRepository {\n list(): Promise<AnimationSummary[]>;\n load(id: string): Promise<AnimationDocument>;\n create(id: string, title: string): Promise<AnimationDocument>;\n save(def: AnimationDocument): Promise<AnimationDocument>;\n delete(id: string): Promise<void>;\n}\n\nexport interface LocalStorageRepositoryOptions {\n storageKey?: string;\n examples?: readonly AnimationDocument[];\n storage?: Pick<Storage, \"getItem\" | \"setItem\" | \"removeItem\">;\n}\n\nfunction browserStorage(): Storage {\n if (typeof localStorage === \"undefined\")\n throw new Error(\"이 저장소는 브라우저에서만 사용할 수 있습니다.\");\n return localStorage;\n}\n\nexport function createLocalStorageRepository(\n options: LocalStorageRepositoryOptions = {},\n): AnimationRepository {\n const prefix = options.storageKey ?? \"clotho-editor.animations\";\n const examples = new Map(\n (options.examples ?? []).map((value) => {\n const def = animationDocumentSchema.parse(value);\n return [def.id, def] as const;\n }),\n );\n const storage = (): Pick<Storage, \"getItem\" | \"setItem\" | \"removeItem\"> =>\n options.storage ?? browserStorage();\n const indexKey = `${prefix}:index`;\n const documentKey = (id: string): string => `${prefix}:document:${id}`;\n const readIds = (): string[] => {\n const value = storage().getItem(indexKey);\n if (!value) return [];\n try {\n const parsed: unknown = JSON.parse(value);\n return Array.isArray(parsed)\n ? parsed.filter((id): id is string => typeof id === \"string\")\n : [];\n } catch {\n return [];\n }\n };\n const write = (def: AnimationDocument): AnimationDocument => {\n const parsed = animationDocumentSchema.parse(def);\n const ids = readIds();\n if (!ids.includes(parsed.id))\n storage().setItem(indexKey, JSON.stringify([...ids, parsed.id]));\n storage().setItem(documentKey(parsed.id), JSON.stringify(parsed));\n return parsed;\n };\n const read = (id: string): AnimationDocument | null => {\n const value = storage().getItem(documentKey(id));\n return value ? animationDocumentSchema.parse(JSON.parse(value)) : null;\n };\n\n return {\n async list() {\n const saved = readIds()\n .map((id) => read(id))\n .filter((def): def is AnimationDocument => def !== null);\n const savedIds = new Set(saved.map((def) => def.id));\n return [\n ...saved.map((def) => ({\n id: def.id,\n title: def.title || def.id,\n description: def.description,\n source: \"saved\" as const,\n })),\n ...[...examples.values()]\n .filter((def) => !savedIds.has(def.id))\n .map((def) => ({\n id: def.id,\n title: def.title || def.id,\n description: def.description,\n source: \"example\" as const,\n })),\n ];\n },\n async load(id) {\n const def = read(id) ?? examples.get(id);\n if (!def) throw new Error(`'${id}' 애니메이션을 찾을 수 없습니다.`);\n return structuredClone(def);\n },\n async create(id, title) {\n if (read(id)) throw new Error(`'${id}' 애니메이션이 이미 있습니다.`);\n return write(\n animationDocumentSchema.parse({\n clothoVersion: 1,\n id,\n title,\n description: \"\",\n }),\n );\n },\n async save(def) {\n return write(def);\n },\n async delete(id) {\n storage().removeItem(documentKey(id));\n storage().setItem(\n indexKey,\n JSON.stringify(readIds().filter((value) => value !== id)),\n );\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/Studio.tsx","../src/StudioMount.tsx","../src/repository.ts"],"names":["setDef","addElement","deleteElement","useEffect","jsx"],"mappings":"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","file":"index.js","sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport type { AnimationDocument } from \"@kokoa/clotho\";\nimport { AnimationStage, usePlayer } from \"@kokoa/clotho/react\";\nimport { downloadAnimationJson } from \"./export-json\";\n\ntype EditableElement = Record<string, unknown> & { id: string; type: string };\ntype ElementList = AnimationDocument[\"elements\"];\n\nexport interface StudioProps {\n initial: AnimationDocument;\n onSave: (def: AnimationDocument) => void | Promise<void>;\n}\n\nfunction elementsOf(def: AnimationDocument): EditableElement[] {\n return def.elements as unknown as EditableElement[];\n}\n\nexport function Studio({ initial, onSave }: StudioProps): React.JSX.Element {\n const [def, setDef] = useState<AnimationDocument>(initial);\n const [selectedId, setSelectedId] = useState<string | null>(\n elementsOf(initial)[0]?.id ?? null,\n );\n const [playing, setPlaying] = useState(false);\n const [time, setTime] = useState(0);\n const [saving, setSaving] = useState(false);\n const [saved, setSaved] = useState(false);\n\n // The clock for preview playback. It lives outside React (core/player), so the\n // editor subscribes rather than owning a rAF loop of its own.\n const { player, state: playerState } = usePlayer(def, { autoplay: false });\n\n useEffect(() => {\n if (playing) player.play();\n else {\n player.pause();\n player.seek(time);\n }\n }, [playing, player, time]);\n\n const elements = elementsOf(def);\n const selected = elements.find((e) => e.id === selectedId) ?? null;\n const scalarProps = selected\n ? Object.entries(selected).filter(\n ([key, value]) =>\n (typeof value === \"string\" || typeof value === \"number\") &&\n key !== \"id\" &&\n key !== \"type\",\n )\n : [];\n\n function setElements(next: EditableElement[]): void {\n setDef((current) => ({\n ...current,\n elements: next as unknown as ElementList,\n }));\n setSaved(false);\n }\n\n function updateElement(\n id: string,\n key: string,\n raw: string,\n numeric: boolean,\n ): void {\n setElements(\n elements.map((e) =>\n e.id === id ? { ...e, [key]: numeric ? Number(raw) : raw } : e,\n ),\n );\n }\n\n function addElement(): void {\n const id = `el-${Date.now().toString(36)}`;\n const el: EditableElement = {\n type: \"text\",\n id,\n x: 100,\n y: 100,\n rotation: 0,\n content: \"New\",\n translations: {},\n fontSize: 24,\n color: \"#4f46e5\",\n textAnchor: \"start\",\n appearances: [{ start: 0, end: def.duration }],\n tracks: [],\n };\n setElements([...elements, el]);\n setSelectedId(id);\n }\n\n function deleteElement(id: string): void {\n setElements(elements.filter((e) => e.id !== id));\n if (selectedId === id) setSelectedId(null);\n }\n\n async function handleSave(): Promise<void> {\n setSaving(true);\n try {\n await onSave(def);\n setSaved(true);\n } finally {\n setSaving(false);\n }\n }\n\n return (\n <div className=\"studio\">\n <aside className=\"studio-panel studio-left\">\n <div className=\"studio-panel-head\">\n <span>요소 ({elements.length})</span>\n <button type=\"button\" className=\"studio-btn\" onClick={addElement}>\n + 추가\n </button>\n </div>\n <ul className=\"studio-el-list\">\n {elements.map((e) => (\n <li key={e.id}>\n <button\n type=\"button\"\n className={\n e.id === selectedId ? \"studio-el active\" : \"studio-el\"\n }\n onClick={() => setSelectedId(e.id)}\n >\n <span className=\"studio-el-type\">{e.type}</span>\n <span className=\"studio-el-id\">{e.id}</span>\n </button>\n </li>\n ))}\n </ul>\n </aside>\n\n <div className=\"studio-preview\">\n <div className=\"studio-preview-bar\">\n <button\n type=\"button\"\n className=\"studio-btn\"\n onClick={() => downloadAnimationJson(def)}\n >\n JSON 내보내기\n </button>\n <button\n type=\"button\"\n className=\"studio-btn\"\n onClick={() => setPlaying((p) => !p)}\n >\n {playing ? \"⏸ 정지\" : \"▶ 재생\"}\n </button>\n <input\n type=\"range\"\n min={0}\n max={def.duration}\n step={50}\n value={time}\n disabled={playing}\n onChange={(e) => {\n setTime(Number(e.target.value));\n setPlaying(false);\n }}\n />\n <span className=\"studio-time\">\n {time} / {def.duration}ms\n </span>\n <button\n type=\"button\"\n className=\"studio-btn studio-save\"\n onClick={() => void handleSave()}\n disabled={saving}\n >\n {saving ? \"저장 중…\" : saved ? \"✓ 저장됨\" : \"저장\"}\n </button>\n </div>\n <div className=\"studio-canvas\">\n {/* One frame at a time: the editor's scrubber is the source of truth, and the\n player only supplies the clock while previewing. Same buildScene path as\n the published animation, so preview and production cannot diverge. */}\n <AnimationStage doc={def} time={playing ? playerState.time : time} />\n </div>\n </div>\n\n <aside className=\"studio-panel studio-right\">\n <div className=\"studio-panel-head\">메타</div>\n <label className=\"studio-field\">\n <span>제목</span>\n <input\n value={def.title}\n onChange={(e) => {\n setDef((d) => ({ ...d, title: e.target.value }));\n setSaved(false);\n }}\n />\n </label>\n <label className=\"studio-field\">\n <span>duration (ms)</span>\n <input\n type=\"number\"\n value={def.duration}\n onChange={(e) => {\n setDef((d) => ({ ...d, duration: Number(e.target.value) }));\n setSaved(false);\n }}\n />\n </label>\n <div className=\"studio-panel-head\">\n 속성 {selected ? `(${selected.type})` : \"\"}\n </div>\n {selected ? (\n <>\n {scalarProps.map(([key, value]) => (\n <label key={key} className=\"studio-field\">\n <span>{key}</span>\n <input\n value={String(value)}\n onChange={(e) =>\n updateElement(\n selected.id,\n key,\n e.target.value,\n typeof value === \"number\",\n )\n }\n />\n </label>\n ))}\n <button\n type=\"button\"\n className=\"studio-btn studio-del\"\n onClick={() => deleteElement(selected.id)}\n >\n 요소 삭제\n </button>\n </>\n ) : (\n <p className=\"studio-empty\">요소를 선택하세요.</p>\n )}\n </aside>\n </div>\n );\n}\n","\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport type { AnimationRepository } from \"./repository\";\nimport { configureAnimationRepository } from \"./legacy/api\";\nimport type { ImageUploadResolver } from \"./legacy/studio-image-upload\";\n// The stylesheet ships as a separate entry rather than an import, so a consumer that\n// only wants the headless pieces does not pull CSS into its bundle:\n// import \"@kokoa/clotho-editor/styles.css\";\n\nexport interface StudioMountProps {\n initialId?: string;\n repository?: AnimationRepository;\n editorTitle?: string;\n resolveImage?: ImageUploadResolver;\n}\n\nconst CANVAS_PRESETS: {\n id: string;\n label: string;\n sub: string;\n w: number;\n h: number;\n}[] = [\n { id: \"800x500\", label: \"8:5\", sub: \"800×500\", w: 32, h: 20 },\n { id: \"1280x720\", label: \"16:9\", sub: \"HD\", w: 32, h: 18 },\n { id: \"1024x768\", label: \"4:3\", sub: \"1024×768\", w: 28, h: 21 },\n { id: \"600x600\", label: \"1:1\", sub: \"정사각\", w: 22, h: 22 },\n { id: \"1200x400\", label: \"3:1\", sub: \"와이드\", w: 36, h: 12 },\n { id: \"375x812\", label: \"9:19\", sub: \"모바일\", w: 14, h: 30 },\n];\n\nconst PRESET_HTML = CANVAS_PRESETS.map(\n (p) => `\n <button type=\"button\" class=\"studio-preset-btn\" aria-label=\"캔버스 크기 ${p.id} (${p.sub})\" title=\"${p.id} · ${p.sub}\" data-canvas-preset=\"${p.id}\">\n <svg class=\"studio-preset-thumb\" viewBox=\"0 0 ${p.w + 2} ${p.h + 2}\" width=\"${p.w + 2}\" height=\"${p.h + 2}\" aria-hidden=\"true\">\n <rect x=\"1\" y=\"1\" width=\"${p.w}\" height=\"${p.h}\" rx=\"2\" />\n </svg>\n <span class=\"studio-preset-label\">${p.label}</span>\n <span class=\"studio-preset-sub\">${p.sub}</span>\n </button>`,\n).join(\"\");\n\nconst SKELETON = `\n<div id=\"studio-app\" class=\"studio-app\">\n <header class=\"studio-header\">\n <h1 id=\"studio-editor-title\" class=\"studio-header-title\">Clotho Editor</h1>\n <div class=\"studio-header-actions\">\n <button type=\"button\" id=\"studio-open\" class=\"studio-btn\" aria-label=\"저장된 애니메이션 열기\">📁 열기</button>\n <button type=\"button\" id=\"studio-new\" class=\"studio-btn\" aria-label=\"새 애니메이션 만들기\">+ 새 애니메이션</button>\n <button type=\"button\" id=\"studio-undo\" class=\"studio-btn studio-btn-icon\" aria-label=\"실행 취소\" title=\"실행 취소 (⌘Z)\" disabled>↶</button>\n <button type=\"button\" id=\"studio-redo\" class=\"studio-btn studio-btn-icon\" aria-label=\"다시 실행\" title=\"다시 실행 (⌘⇧Z / ⌘Y)\" disabled>↷</button>\n <button type=\"button\" id=\"studio-grid-toggle\" class=\"studio-btn studio-btn-grid\" aria-label=\"격자 + 스냅\" title=\"격자 + 스냅 (G)\" aria-pressed=\"false\"><span class=\"studio-btn-grid-icon\">⊞</span><span class=\"studio-btn-grid-label\" id=\"studio-grid-label\">격자 끔</span></button>\n <input type=\"text\" id=\"studio-title\" class=\"studio-title-input\" placeholder=\"제목\" aria-label=\"애니메이션 제목\" disabled />\n <span id=\"studio-id-display\" class=\"studio-id-display\"></span>\n <span id=\"studio-status\" class=\"studio-status\" aria-live=\"polite\">대기 중</span>\n <button type=\"button\" id=\"studio-save\" class=\"studio-btn studio-btn-primary\" aria-label=\"저장\" disabled>💾 저장 (⌘S)</button>\n <button type=\"button\" id=\"studio-export-json\" class=\"studio-btn\" aria-label=\"현재 애니메이션을 JSON 파일로 내보내기\" disabled>⬇ JSON 내보내기</button>\n <button type=\"button\" id=\"studio-player-preview\" class=\"studio-btn\" aria-label=\"실제 애니메이션 컴포넌트 미리보기\" aria-pressed=\"false\">↗ 실제 미리보기</button>\n <button type=\"button\" id=\"studio-import-json\" class=\"studio-btn\" aria-label=\"JSON 파일에서 애니메이션 가져오기\">⬆ JSON 가져오기</button>\n <input type=\"file\" id=\"studio-import-file\" accept=\"application/json,.json\" hidden />\n <button type=\"button\" id=\"studio-delete\" class=\"studio-btn studio-btn-danger\" aria-label=\"삭제\" disabled>🗑 삭제</button>\n </div>\n </header>\n\n <div class=\"studio-body\">\n <aside class=\"studio-tools\" aria-label=\"요소 추가\">\n <div class=\"studio-tools-section\">\n <div class=\"studio-tools-title\">도구</div>\n <button type=\"button\" class=\"studio-tool-btn is-active\" data-studio-tool=\"select\" aria-pressed=\"true\" title=\"선택 · 이동 · 크기 (V)\"><span>↖ 선택 · 이동 · 크기</span><kbd>V</kbd></button>\n <button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"rect\" title=\"사각형 (R)\" aria-label=\"사각형 도구\"><span>□ Rect</span><kbd>R</kbd></button>\n <button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"circle\" title=\"원 (O)\" aria-label=\"원 도구\"><span>○ Circle</span><kbd>O</kbd></button>\n <button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"line\" title=\"선 (L)\" aria-label=\"선 도구\"><span>/ Line</span><kbd>L</kbd></button>\n <button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"arrow\" title=\"화살표 (A)\" aria-label=\"화살표 도구\"><span>↗ Arrow</span><kbd>A</kbd></button>\n <button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"text\" title=\"텍스트 (T)\" aria-label=\"텍스트 도구\"><span>T Text</span><kbd>T</kbd></button>\n <button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"image\" title=\"이미지 (I)\" aria-label=\"이미지 도구\"><span>🖼 Image</span><kbd>I</kbd></button>\n <button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"path\" title=\"Path (B)\" aria-label=\"Path 도구\"><span>✎ Path</span><kbd>B</kbd></button>\n <div class=\"studio-tool-with-option\"><button type=\"button\" class=\"studio-tool-btn\" data-add-element=\"polygon\" title=\"다각형 (Y)\" aria-label=\"다각형 도구\"><span>⬢ Polygon</span><kbd>Y</kbd></button><label>변 <input type=\"number\" id=\"studio-polygon-sides\" min=\"3\" max=\"24\" value=\"6\" aria-label=\"다각형의 변 개수\" /></label></div>\n <button type=\"button\" class=\"studio-tool-btn\" id=\"studio-open-icons\" title=\"아이콘 라이브러리\" aria-label=\"아이콘 라이브러리 추가\">🎨 Icons</button>\n </div>\n <div class=\"studio-tools-section\">\n <div class=\"studio-tools-title\">캔버스 크기</div>\n <div class=\"studio-canvas-size-row\">\n <input type=\"number\" id=\"studio-canvas-width\" class=\"studio-canvas-size-input\" min=\"100\" max=\"8000\" step=\"10\" placeholder=\"width\" />\n <span class=\"studio-canvas-size-x\">×</span>\n <input type=\"number\" id=\"studio-canvas-height\" class=\"studio-canvas-size-input\" min=\"100\" max=\"8000\" step=\"10\" placeholder=\"height\" />\n </div>\n <div class=\"studio-canvas-presets\">${PRESET_HTML}</div>\n </div>\n <div class=\"studio-tools-section\">\n <div class=\"studio-tools-title\">이미지</div>\n <button type=\"button\" class=\"studio-tool-btn\" id=\"studio-image-upload\">📤 이미지 업로드</button>\n <input type=\"file\" id=\"studio-image-file\" accept=\"image/*\" hidden />\n <div class=\"studio-tools-hint\">이미지를 캔버스에 직접 드래그하거나 ⌘V 로 붙여넣어도 됩니다.</div>\n </div>\n <div class=\"studio-tools-section\">\n <div class=\"studio-tools-title\">단축키</div>\n <button type=\"button\" class=\"studio-tool-btn\" id=\"studio-help\">⌨ 단축키 보기 (?)</button>\n </div>\n <div class=\"studio-tools-section\">\n <div class=\"studio-tools-title\">요소 목록</div>\n <input type=\"text\" id=\"studio-element-search\" class=\"studio-element-search\" placeholder=\"🔍 검색 (name / label / id / type)\" autocomplete=\"off\" />\n <ul id=\"studio-element-list\" class=\"studio-element-list\"></ul>\n </div>\n </aside>\n\n <main class=\"studio-canvas-wrap\">\n <div class=\"studio-canvas-frame\">\n <svg id=\"studio-canvas\" class=\"studio-canvas\" xmlns=\"http://www.w3.org/2000/svg\"></svg>\n </div>\n <button type=\"button\" id=\"studio-floating-help\" class=\"studio-floating-help\" aria-label=\"단축키 보기\" title=\"단축키 보기 (? / Shift+/)\">?</button>\n <div id=\"studio-help-hint\" class=\"studio-help-hint\" hidden>\n <span>💡 단축키는 <kbd>?</kbd> 또는 우측 하단 버튼으로 확인하세요</span>\n <button type=\"button\" id=\"studio-help-hint-close\" aria-label=\"닫기\">✕</button>\n </div>\n </main>\n\n <aside class=\"studio-props\" aria-label=\"속성 패널\">\n <div id=\"studio-props-content\" class=\"studio-props-content\">\n <p class=\"studio-props-empty\">요소 또는 step 을 선택하세요.</p>\n </div>\n </aside>\n </div>\n\n <div id=\"studio-timeline-resizer\" class=\"studio-timeline-resizer\" role=\"separator\" aria-orientation=\"horizontal\" aria-label=\"타임라인 영역 크기 조정\" tabindex=\"0\"><span class=\"studio-timeline-resizer-grip\" aria-hidden=\"true\"><i></i><i></i><i></i></span></div>\n <footer class=\"studio-timeline-wrap\">\n <div class=\"studio-timeline-header\">\n <button type=\"button\" id=\"studio-play\" class=\"studio-btn\">▶ Play</button>\n <button type=\"button\" id=\"studio-restart\" class=\"studio-btn\">⟲ Reset</button>\n <label class=\"studio-speed\">속도\n <input type=\"range\" id=\"studio-speed\" min=\"0.25\" max=\"3\" step=\"0.25\" value=\"1\" />\n <span id=\"studio-speed-value\">1.00x</span>\n </label>\n <label class=\"studio-timeline-option\"><input type=\"checkbox\" id=\"studio-preview-loop\" /> 무한 재생</label>\n <button type=\"button\" id=\"studio-detach-timeline\" class=\"studio-btn\" aria-pressed=\"false\">▣ 타임라인 분리</button>\n <span class=\"studio-timeline-spacer\"></span>\n <button type=\"button\" id=\"studio-add-step\" class=\"studio-btn\">+ Chapter 추가</button>\n </div>\n <div class=\"studio-timeline-tracks-wrap\">\n <div id=\"studio-timeline-tracks\" class=\"studio-timeline-tracks\"></div>\n </div>\n <div class=\"studio-element-tracks-wrap\">\n <div id=\"studio-element-tracks\" class=\"studio-element-tracks\"></div>\n </div>\n </footer>\n\n <dialog id=\"studio-library-dialog\" class=\"studio-dialog\">\n <div class=\"studio-dialog-header\">\n <h2>저장된 애니메이션</h2>\n <button type=\"button\" class=\"studio-dialog-close\" data-studio-dialog-close>✕</button>\n </div>\n <div class=\"studio-dialog-body\">\n <ul id=\"studio-library-list\" class=\"studio-library-list\"></ul>\n </div>\n </dialog>\n\n <dialog id=\"studio-icon-dialog\" class=\"studio-dialog\">\n <div class=\"studio-dialog-header\">\n <h2>🎨 아이콘 라이브러리</h2>\n <button type=\"button\" class=\"studio-dialog-close\" data-icon-dialog-close>✕</button>\n </div>\n <div class=\"studio-dialog-body\">\n <input type=\"search\" id=\"studio-icon-search\" placeholder=\"아이콘 검색…\" class=\"studio-icon-search-input\" />\n <div id=\"studio-icon-list\"></div>\n </div>\n </dialog>\n\n <dialog id=\"studio-help-dialog\" class=\"studio-dialog studio-dialog-small\">\n <div class=\"studio-dialog-header\">\n <h2>⌨ 단축키</h2>\n <button type=\"button\" class=\"studio-dialog-close\" data-studio-dialog-close>✕</button>\n </div>\n <div class=\"studio-dialog-body\">\n <table class=\"studio-help-table\">\n <tbody>\n <tr><th>⌘ S</th><td>저장</td></tr>\n <tr><th>⌘ Z / ⌘ ⇧ Z</th><td>Undo / Redo</td></tr>\n <tr><th>⌘ C / V / X</th><td>요소 복사 / 붙여넣기 / 잘라내기</td></tr>\n <tr><th>V / R / O</th><td>선택 / 사각형 / 원 도구</td></tr>\n <tr><th>L / A</th><td>선 / 화살표 도구</td></tr>\n <tr><th>T / I / B / Y</th><td>텍스트 / 이미지 / Path / 다각형 도구</td></tr>\n <tr><th>⌘·Ctrl·Shift + 클릭</th><td>여러 요소 선택 또는 선택 해제</td></tr>\n <tr><th>Delete / Backspace</th><td>선택된 요소 또는 step 삭제</td></tr>\n <tr><th>?</th><td>이 화면 열기</td></tr>\n <tr><th>Esc</th><td>선택 해제 / 다이얼로그 닫기</td></tr>\n <tr><th>드래그 (캔버스)</th><td>요소 이동</td></tr>\n <tr><th>드래그 (요소 위 dot)</th><td>화살표 연결</td></tr>\n <tr><th>드래그 (선/화살표 끝점)</th><td>끝점 재배치 + 다른 요소에 sticky</td></tr>\n <tr><th>드래그 (회전 핸들)</th><td>요소 회전</td></tr>\n <tr><th>드래그 (타임라인 바 우측)</th><td>step duration 조정</td></tr>\n <tr><th>이미지 드래그 in</th><td>이미지 업로드 + 배치</td></tr>\n </tbody>\n </table>\n </div>\n </dialog>\n\n <dialog id=\"studio-new-dialog\" class=\"studio-dialog studio-dialog-small\">\n <div class=\"studio-dialog-header\">\n <h2 id=\"studio-new-dialog-title\">새 애니메이션 만들기</h2>\n <button type=\"button\" class=\"studio-dialog-close\" data-studio-dialog-close aria-label=\"닫기\">✕</button>\n </div>\n <div class=\"studio-dialog-body\">\n <label class=\"studio-field\"><span>ID (영문 소문자 / 숫자 / - / _)</span>\n <input type=\"text\" id=\"studio-new-id\" placeholder=\"예: user-login-flow\" />\n </label>\n <label class=\"studio-field\"><span>제목</span>\n <input type=\"text\" id=\"studio-new-title\" placeholder=\"사용자 로그인 흐름\" />\n </label>\n <div id=\"studio-new-error\" class=\"studio-new-error\"></div>\n </div>\n <footer class=\"studio-dialog-footer\">\n <button type=\"button\" class=\"studio-btn\" data-studio-dialog-close>취소</button>\n <button type=\"button\" id=\"studio-new-create\" class=\"studio-btn studio-btn-primary\">만들기</button>\n </footer>\n </dialog>\n\n <dialog id=\"studio-palette-dialog\" class=\"studio-palette-dialog\">\n <input type=\"text\" id=\"studio-palette-input\" class=\"studio-palette-input\" placeholder=\"🔍 명령 또는 요소 검색…\" autocomplete=\"off\" spellcheck=\"false\" />\n <ul id=\"studio-palette-list\" class=\"studio-palette-list\"></ul>\n <div class=\"studio-palette-footer\">↑↓ 이동 · Enter 실행 · Esc 닫기</div>\n </dialog>\n\n <dialog id=\"studio-history-dialog\" class=\"studio-history-dialog\">\n <div class=\"studio-history-header\">\n <h2>📜 작업 이력</h2>\n <button type=\"button\" class=\"studio-dialog-close\" data-studio-dialog-close aria-label=\"닫기\">✕</button>\n </div>\n <ul id=\"studio-history-list\" class=\"studio-history-list\"></ul>\n <div class=\"studio-history-footer\">항목을 클릭하면 해당 시점으로 이동합니다 · ⌘Z / ⌘⇧Z</div>\n </dialog>\n</div>\n`;\n\nexport function StudioMount({\n initialId,\n repository,\n editorTitle = \"Clotho Editor\",\n resolveImage,\n}: StudioMountProps): React.JSX.Element {\n const inited = useRef(false);\n\n useEffect(() => {\n if (inited.current) return;\n inited.current = true;\n if (repository) configureAnimationRepository(repository);\n let disposed = false;\n void import(\"./legacy/main\").then(({ initStudio }) => {\n if (disposed) return;\n initStudio({ initialId, editorTitle, resolveImage });\n });\n return () => {\n disposed = true;\n document.body.classList.remove(\"editor-active\");\n document.documentElement.classList.remove(\"editor-active\");\n };\n }, [editorTitle, initialId, repository, resolveImage]);\n\n return (\n <section className=\"studio-shell w-full\" data-pagefind-ignore=\"all\">\n <div dangerouslySetInnerHTML={{ __html: SKELETON }} />\n </section>\n );\n}\n","import { animationDocumentSchema, type AnimationDocument } from \"@kokoa/clotho\";\n\nexport interface AnimationSummary {\n id: string;\n title: string;\n description: string;\n updatedAt?: string;\n source?: \"saved\" | \"example\";\n}\n\nexport interface AnimationRepository {\n list(): Promise<AnimationSummary[]>;\n load(id: string): Promise<AnimationDocument>;\n create(id: string, title: string): Promise<AnimationDocument>;\n save(def: AnimationDocument): Promise<AnimationDocument>;\n delete(id: string): Promise<void>;\n}\n\nexport interface LocalStorageRepositoryOptions {\n storageKey?: string;\n examples?: readonly AnimationDocument[];\n storage?: Pick<Storage, \"getItem\" | \"setItem\" | \"removeItem\">;\n}\n\nfunction browserStorage(): Storage {\n if (typeof localStorage === \"undefined\")\n throw new Error(\"이 저장소는 브라우저에서만 사용할 수 있습니다.\");\n return localStorage;\n}\n\nexport function createLocalStorageRepository(\n options: LocalStorageRepositoryOptions = {},\n): AnimationRepository {\n const prefix = options.storageKey ?? \"clotho-editor.animations\";\n const examples = new Map(\n (options.examples ?? []).map((value) => {\n const def = animationDocumentSchema.parse(value);\n return [def.id, def] as const;\n }),\n );\n const storage = (): Pick<Storage, \"getItem\" | \"setItem\" | \"removeItem\"> =>\n options.storage ?? browserStorage();\n const indexKey = `${prefix}:index`;\n const documentKey = (id: string): string => `${prefix}:document:${id}`;\n const readIds = (): string[] => {\n const value = storage().getItem(indexKey);\n if (!value) return [];\n try {\n const parsed: unknown = JSON.parse(value);\n return Array.isArray(parsed)\n ? parsed.filter((id): id is string => typeof id === \"string\")\n : [];\n } catch {\n return [];\n }\n };\n const write = (def: AnimationDocument): AnimationDocument => {\n const parsed = animationDocumentSchema.parse(def);\n const ids = readIds();\n if (!ids.includes(parsed.id))\n storage().setItem(indexKey, JSON.stringify([...ids, parsed.id]));\n storage().setItem(documentKey(parsed.id), JSON.stringify(parsed));\n return parsed;\n };\n const read = (id: string): AnimationDocument | null => {\n const value = storage().getItem(documentKey(id));\n return value ? animationDocumentSchema.parse(JSON.parse(value)) : null;\n };\n\n return {\n async list() {\n const saved = readIds()\n .map((id) => read(id))\n .filter((def): def is AnimationDocument => def !== null);\n const savedIds = new Set(saved.map((def) => def.id));\n return [\n ...saved.map((def) => ({\n id: def.id,\n title: def.title || def.id,\n description: def.description,\n source: \"saved\" as const,\n })),\n ...[...examples.values()]\n .filter((def) => !savedIds.has(def.id))\n .map((def) => ({\n id: def.id,\n title: def.title || def.id,\n description: def.description,\n source: \"example\" as const,\n })),\n ];\n },\n async load(id) {\n const def = read(id) ?? examples.get(id);\n if (!def) throw new Error(`'${id}' 애니메이션을 찾을 수 없습니다.`);\n return structuredClone(def);\n },\n async create(id, title) {\n if (read(id)) throw new Error(`'${id}' 애니메이션이 이미 있습니다.`);\n return write(\n animationDocumentSchema.parse({\n clothoVersion: 1,\n id,\n title,\n description: \"\",\n }),\n );\n },\n async save(def) {\n return write(def);\n },\n async delete(id) {\n storage().removeItem(documentKey(id));\n storage().setItem(\n indexKey,\n JSON.stringify(readIds().filter((value) => value !== id)),\n );\n },\n };\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { updateCanvas, getDef, subscribe, updateMeta, downloadAnimationJson, importAnimation, setDef, markClean, undo, redo, updateSettings, isDirty, canUndo, canRedo, isDraft, uniqueChapterId, getCurrentTime, addChapter, setSelection, uniqueElementId, registerExternalAsset, addElement, registerDataUriAsset, listAnimations, saveAnimation, deleteAnimation, duplicateAnimation, createAnimation, getSelection, getSelectedElementIds, deleteElement, deleteChapter, deleteEffect, updateChapter, isGroup, ungroupElement, groupElements, moveElementToEnd, moveElementToFront, reorderElement, loadAnimation, setDraft, getCurrentSnapshot, setElementValueAtTime, findContainingGroup, isElementSelected, updateElementBase, uniqueEffectId, addEffect, removeAppearance, removeTrack, removeTrackKeyframe, setTrackKeyframe, setCurrentTime, addAppearance, jumpBack, jumpForward, getHistory, promoteDraftToSaved, toggleSelectionFor, moveGroupBy, placeholderImageUrl, groupBbox, updateDuration, updateAppearance, updateEffect, childIdsOf } from './chunk-
|
|
1
|
+
import { updateCanvas, getDef, subscribe, updateMeta, downloadAnimationJson, importAnimation, setDef, markClean, undo, redo, updateSettings, isDirty, canUndo, canRedo, isDraft, uniqueChapterId, getCurrentTime, addChapter, setSelection, uniqueElementId, registerExternalAsset, addElement, registerDataUriAsset, listAnimations, saveAnimation, deleteAnimation, duplicateAnimation, createAnimation, getSelection, getSelectedElementIds, deleteElement, deleteChapter, deleteEffect, updateChapter, isGroup, ungroupElement, groupElements, moveElementToEnd, moveElementToFront, reorderElement, loadAnimation, setDraft, getCurrentSnapshot, setElementValueAtTime, findContainingGroup, isElementSelected, updateElementBase, uniqueEffectId, addEffect, removeAppearance, removeTrack, removeTrackKeyframe, setTrackKeyframe, setCurrentTime, addAppearance, jumpBack, jumpForward, getHistory, promoteDraftToSaved, toggleSelectionFor, moveGroupBy, placeholderImageUrl, groupBbox, updateLocales, updateDuration, updateAppearance, updateEffect, childIdsOf } from './chunk-GMGB7NIL.js';
|
|
2
2
|
import { activeAppearance, animationDocumentSchema, resolveAsset } from '@kokoa/clotho';
|
|
3
3
|
|
|
4
4
|
// src/legacy/grid.ts
|
|
@@ -721,6 +721,7 @@ function makeDefaultElement(type, id, cx, cy, polygonSides = 6) {
|
|
|
721
721
|
x: cx,
|
|
722
722
|
y: cy,
|
|
723
723
|
content: id,
|
|
724
|
+
translations: {},
|
|
724
725
|
fontSize: 18,
|
|
725
726
|
fontWeight: 400,
|
|
726
727
|
color: "#18181b",
|
|
@@ -2697,6 +2698,16 @@ function distributeSelected(kind) {
|
|
|
2697
2698
|
var panelEl = null;
|
|
2698
2699
|
var closedSections = /* @__PURE__ */ new Set();
|
|
2699
2700
|
var isComposingFallback = false;
|
|
2701
|
+
var DEFAULT_LOCALES = ["ko", "en"];
|
|
2702
|
+
function parseLocales(value) {
|
|
2703
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2704
|
+
return value.split(",").map((locale) => locale.trim()).filter((locale) => {
|
|
2705
|
+
const key = locale.toLowerCase();
|
|
2706
|
+
if (!locale || seen.has(key)) return false;
|
|
2707
|
+
seen.add(key);
|
|
2708
|
+
return true;
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2700
2711
|
function initProperties(root) {
|
|
2701
2712
|
panelEl = root;
|
|
2702
2713
|
subscribe(render3);
|
|
@@ -2810,6 +2821,11 @@ function renderInner() {
|
|
|
2810
2821
|
const metaBody = [
|
|
2811
2822
|
textField("title", "meta.title", def.title),
|
|
2812
2823
|
textField("description", "meta.description", def.description),
|
|
2824
|
+
textField(
|
|
2825
|
+
"\uBB38\uC11C \uC5B8\uC5B4 (\uC27C\uD45C\uB85C \uAD6C\uBD84)",
|
|
2826
|
+
"meta.locales",
|
|
2827
|
+
(def.locales ?? DEFAULT_LOCALES).join(", ")
|
|
2828
|
+
),
|
|
2813
2829
|
numberField("duration (ms)", "meta.duration", def.duration, 100),
|
|
2814
2830
|
numberField("canvas.width", "canvas.width", def.canvas.width),
|
|
2815
2831
|
numberField("canvas.height", "canvas.height", def.canvas.height),
|
|
@@ -2969,7 +2985,7 @@ function renderElementForm(def, el) {
|
|
|
2969
2985
|
`;
|
|
2970
2986
|
return;
|
|
2971
2987
|
}
|
|
2972
|
-
const baseFields = renderBaseFields(el);
|
|
2988
|
+
const baseFields = renderBaseFields(def, el);
|
|
2973
2989
|
const appearances = renderAppearances(def, el);
|
|
2974
2990
|
const tracks = renderTracks(el);
|
|
2975
2991
|
const baseHeader = `<span class="studio-props-header-title">${escapeHtml2(el.id)}</span><span class="studio-props-header-type">${escapeHtml2(el.type)}</span>`;
|
|
@@ -2986,7 +3002,7 @@ function renderElementForm(def, el) {
|
|
|
2986
3002
|
</div>
|
|
2987
3003
|
`;
|
|
2988
3004
|
}
|
|
2989
|
-
function renderBaseFields(el) {
|
|
3005
|
+
function renderBaseFields(def, el) {
|
|
2990
3006
|
const numberFields = [];
|
|
2991
3007
|
const colorFields = [];
|
|
2992
3008
|
const textFields = [];
|
|
@@ -3099,8 +3115,30 @@ function renderBaseFields(el) {
|
|
|
3099
3115
|
value: e.rotation,
|
|
3100
3116
|
step: 5
|
|
3101
3117
|
});
|
|
3118
|
+
const localizedTextFields = (() => {
|
|
3119
|
+
if (el.type !== "text") return [];
|
|
3120
|
+
const localized = el;
|
|
3121
|
+
const documentLocales = def.locales ?? DEFAULT_LOCALES;
|
|
3122
|
+
const locales = localized.locales ?? documentLocales;
|
|
3123
|
+
return [
|
|
3124
|
+
textField(
|
|
3125
|
+
"\uC774 \uC694\uC18C\uC758 \uC5B8\uC5B4 (\uBE44\uC6B0\uBA74 \uBB38\uC11C \uC124\uC815 \uC0AC\uC6A9)",
|
|
3126
|
+
"el.locales",
|
|
3127
|
+
localized.locales?.join(", ") ?? ""
|
|
3128
|
+
),
|
|
3129
|
+
`<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>`,
|
|
3130
|
+
...locales.map(
|
|
3131
|
+
(locale) => textField(
|
|
3132
|
+
`${locale} \uBC88\uC5ED`,
|
|
3133
|
+
`el.translation.${locale}`,
|
|
3134
|
+
localized.translations?.[locale] ?? ""
|
|
3135
|
+
)
|
|
3136
|
+
)
|
|
3137
|
+
];
|
|
3138
|
+
})();
|
|
3102
3139
|
const html = [
|
|
3103
3140
|
...textFields.map((f) => textField(f.label, `el.${f.key}`, f.value)),
|
|
3141
|
+
...localizedTextFields,
|
|
3104
3142
|
...numberFields.map(
|
|
3105
3143
|
(f) => numberField(f.label, `el.${f.key}`, f.value, f.step)
|
|
3106
3144
|
),
|
|
@@ -3198,7 +3236,10 @@ function apply(key, value) {
|
|
|
3198
3236
|
if (key === "meta.title") updateMeta({ title: String(value) });
|
|
3199
3237
|
else if (key === "meta.description")
|
|
3200
3238
|
updateMeta({ description: String(value) });
|
|
3201
|
-
else if (key === "meta.
|
|
3239
|
+
else if (key === "meta.locales") {
|
|
3240
|
+
const locales = parseLocales(String(value));
|
|
3241
|
+
if (locales.length > 0) updateLocales(locales);
|
|
3242
|
+
} else if (key === "meta.duration") updateDuration(Number(value));
|
|
3202
3243
|
else if (key === "canvas.width") updateCanvas({ width: Number(value) });
|
|
3203
3244
|
else if (key === "canvas.height") updateCanvas({ height: Number(value) });
|
|
3204
3245
|
else if (key === "canvas.background")
|
|
@@ -3219,6 +3260,22 @@ function apply(key, value) {
|
|
|
3219
3260
|
const time = getCurrentTime();
|
|
3220
3261
|
const el = def.elements.find((e) => e.id === sel.elementId);
|
|
3221
3262
|
if (!el) return;
|
|
3263
|
+
if (prop === "locales" && el.type === "text") {
|
|
3264
|
+
const locales = parseLocales(String(value));
|
|
3265
|
+
updateElementBase(sel.elementId, {
|
|
3266
|
+
locales: locales.length > 0 ? locales : void 0
|
|
3267
|
+
});
|
|
3268
|
+
return;
|
|
3269
|
+
}
|
|
3270
|
+
if (prop.startsWith("translation.") && el.type === "text") {
|
|
3271
|
+
const locale = prop.slice("translation.".length);
|
|
3272
|
+
const localized = el;
|
|
3273
|
+
const translations = { ...localized.translations ?? {} };
|
|
3274
|
+
if (String(value)) translations[locale] = String(value);
|
|
3275
|
+
else delete translations[locale];
|
|
3276
|
+
updateElementBase(sel.elementId, { translations });
|
|
3277
|
+
return;
|
|
3278
|
+
}
|
|
3222
3279
|
if (prop === "polygonSides" && el.type === "polygon") {
|
|
3223
3280
|
const points = el.points.trim().split(/\s+/).map((point) => point.split(",").map(Number)).filter(([x, y]) => Number.isFinite(x) && Number.isFinite(y));
|
|
3224
3281
|
if (points.length < 3) return;
|
|
@@ -16967,5 +17024,5 @@ function reflectState(ui) {
|
|
|
16967
17024
|
}
|
|
16968
17025
|
|
|
16969
17026
|
export { getVisibleElementIds, initStudio };
|
|
16970
|
-
//# sourceMappingURL=main-
|
|
16971
|
-
//# sourceMappingURL=main-
|
|
17027
|
+
//# sourceMappingURL=main-NCPWDB2U.js.map
|
|
17028
|
+
//# sourceMappingURL=main-NCPWDB2U.js.map
|