@animastor/web-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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Animastor
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # @animastor/web-editor
2
+
3
+ Schema-driven editor UI primitives for Animastor web frontend.
4
+
5
+ ## Scope
6
+
7
+ This package provides reusable editor components, canvas waveform rendering, and preview ID generation:
8
+
9
+ - **Entity Editor** — schema-driven Add/Delete dialogs for Characters, Locations, Voices, Behaviors
10
+ - **Structure Editor** — Add dialog for Chapters, Scenes, Units (hierarchy with parent selection)
11
+ - **Waveform** — canvas-based waveform renderer with draggable range selection and playback playhead
12
+ - **ID Generator** — preview ID generation for chapters, scenes, units (client-side preview only)
13
+
14
+ **Host capabilities injected via EditorPorts.** The package imports nothing from the host app directly — i18n, icons, and modal arrive through the Ports contract.
15
+
16
+ ## Install
17
+
18
+ ```sh
19
+ npm install @animastor/web-editor
20
+ ```
21
+
22
+ Peer dependencies: `preact >=10.5.0`, `@preact/signals >=1.0.0`.
23
+
24
+ ## Usage
25
+
26
+ ```tsx
27
+ import { EntityAddButton, Waveform, chapterId, type EditorPorts } from '@animastor/web-editor';
28
+
29
+ // Inject host capabilities via EditorPorts
30
+ const ports: EditorPorts = {
31
+ i18n: { t: (key) => translations[key] },
32
+ icons: { Add: IconAdd, Minus: IconMinus },
33
+ modal: { Modal: HostModal },
34
+ };
35
+
36
+ // Use in a component
37
+ <EntityAddButton ports={ports} onClick={handleAdd} />
38
+
39
+ // Waveform
40
+ <Waveform peaks={peaks} durationMs={60000} selection={sel} />
41
+
42
+ // Preview IDs
43
+ const id = chapterId(); // "ch-a1b2c3d4"
44
+ ```
45
+
46
+ ## Public API
47
+
48
+ | Export | Kind | Purpose |
49
+ |---|---|---|
50
+ | `EditorPorts` | type | Host contract (i18n + icons + modal) |
51
+ | `EditorI18nKey` | type | Typed i18n key union (45 keys) |
52
+ | `EditorT` | type | Translation function signature |
53
+ | `EditorIconProps` | type | Icon component props |
54
+ | `EditorIconComponent` | type | Icon component function type |
55
+ | `EditorModalProps` | type | Modal component props |
56
+ | `WaveformPeak` | type | Package-owned peak pair (pos/neg) |
57
+ | `EntityKind` | type | `'character' | 'location' | 'voice' | 'behavior'` |
58
+ | `EntitySchema` | type | Schema for an entity kind |
59
+ | `EntityFieldDef` | type | Field definition in an entity schema |
60
+ | `StructureKind` | type | `'chapter' | 'scene' | 'unit'` |
61
+ | `StructureParentOption` | type | Parent option for structure add dialog |
62
+ | `ENTITY_SCHEMAS` | const | Maps EntityKind → EntitySchema |
63
+ | `EntityAddButton` | component | Round "+" overlay button |
64
+ | `EntityDeleteButton` | component | Small "−" destructive button |
65
+ | `EntityEditorDialog` | component | Schema-driven add dialog |
66
+ | `BehaviorAddDialog` | component | Behavior-specific add dialog (character picker) |
67
+ | `DeleteConfirmDialog` | component | Destructive action confirmation |
68
+ | `StructureAddDialog` | component | Chapter/Scene/Unit add dialog |
69
+ | `Waveform` | component | Canvas waveform with range selection |
70
+ | `chapterId` | function | Preview ID: `ch-<8hex>` |
71
+ | `sceneId` | function | Preview ID: `sc-<8hex>` |
72
+ | `unitId` | function | Preview ID: `iu-<8hex>` |
73
+
74
+ ## Package boundary
75
+
76
+ - The package imports only its own internal modules — zero host imports.
77
+ - `@animastor/web-editor → host` = forbidden; `host → @animastor/web-editor` = allowed through the public entry point only.
78
+ - All host capabilities arrive via the injected `EditorPorts` contract.
79
+
80
+ ## Development
81
+
82
+ ```sh
83
+ npm install
84
+ npm run typecheck # tsc --noEmit
85
+ npm run test # vitest (unit tests)
86
+ npm run build # tsup → dist/ (ESM + d.ts + sourcemaps)
87
+ ```
88
+
89
+ ## License
90
+
91
+ MIT
@@ -0,0 +1,155 @@
1
+ import * as preact from 'preact';
2
+ import { JSX } from 'preact';
3
+ import { Signal } from '@preact/signals';
4
+
5
+ /** i18n keys the Editor surface needs (the dictionary stays host-owned). */
6
+ type EditorI18nKey = 'entity_add' | 'entity_delete' | 'entity_add_character' | 'entity_delete_character' | 'entity_delete_character_confirm' | 'entity_add_location' | 'entity_delete_location' | 'entity_delete_location_confirm' | 'entity_add_voice' | 'entity_delete_voice' | 'entity_delete_voice_confirm' | 'entity_add_behavior' | 'entity_delete_behavior' | 'entity_delete_behavior_confirm' | 'field_appearance' | 'field_clothes' | 'field_video_tokens' | 'field_description' | 'field_time' | 'field_season' | 'field_lighting' | 'field_weather' | 'field_mood' | 'field_atmosphere' | 'field_instruction' | 'field_baseline' | 'entity_name_required' | 'entity_id_exists' | 'dialog_cancel' | 'edit_saving' | 'edit_save' | 'entity_id' | 'entity_id_placeholder' | 'entity_id_hint' | 'field_name' | 'entity_delete_btn' | 'behavior_character' | 'behavior_character_hint' | 'behavior_character_required' | 'field_chapter_title' | 'field_scene_title' | 'structure_parent_required' | 'structure_add_chapter' | 'structure_add_scene' | 'structure_add_unit' | 'structure_id_hint' | 'chapter_auto_create_hint' | 'scene_auto_create_hint';
7
+ type EditorT = (key: EditorI18nKey, fallback?: string) => string;
8
+ /** Icon component props (the SVG kit stays host-owned). */
9
+ type EditorIconProps = JSX.SVGAttributes<SVGSVGElement>;
10
+ type EditorIconComponent = (props: EditorIconProps) => JSX.Element;
11
+ /** Minimal modal contract consumed by the Editor dialogs. */
12
+ interface EditorModalProps {
13
+ title?: string;
14
+ onClose: () => void;
15
+ footer?: JSX.Element | JSX.Element[];
16
+ children: JSX.Element | JSX.Element[];
17
+ }
18
+ /** I18nPort — app/i18n stays host (typed key union, Player/File precedent). */
19
+ interface EditorI18nPort {
20
+ t: EditorT;
21
+ }
22
+ /** IconsPort — app/icons stays host (2 icons used by the Editor surface). */
23
+ interface EditorIconsPort {
24
+ Add: EditorIconComponent;
25
+ Minus: EditorIconComponent;
26
+ }
27
+ /** ModalPort — lib/ui Modal stays host (component injection). */
28
+ interface EditorModalPort {
29
+ Modal: (props: EditorModalProps) => JSX.Element;
30
+ }
31
+ /** The complete host contract consumed by the Editor modules. */
32
+ interface EditorPorts {
33
+ i18n: EditorI18nPort;
34
+ icons: EditorIconsPort;
35
+ modal: EditorModalPort;
36
+ }
37
+
38
+ /** A single peak pair (positive / negative amplitude, 0..1 range). */
39
+ interface WaveformPeak {
40
+ pos: number;
41
+ neg: number;
42
+ }
43
+
44
+ declare const chapterId: () => string;
45
+ declare const sceneId: () => string;
46
+ declare const unitId: () => string;
47
+
48
+ type EntityKind = 'character' | 'location' | 'voice' | 'behavior';
49
+ interface EntityFieldDef {
50
+ /** Flat form key — dotted for nested payload keys (passport.appearance). */
51
+ key: string;
52
+ /** i18n key of the field label. */
53
+ labelKey: EditorI18nKey;
54
+ multiline?: boolean;
55
+ }
56
+ interface EntitySchema {
57
+ kind: EntityKind;
58
+ /** i18n key: add-dialog title ("Add character"). */
59
+ addTitleKey: EditorI18nKey;
60
+ /** i18n key: delete-confirm title ("Delete character?"). */
61
+ deleteTitleKey: EditorI18nKey;
62
+ /** i18n key: delete-confirm body ("Are you sure…"). */
63
+ deleteConfirmKey: EditorI18nKey;
64
+ /** Extra fields beyond id + name, in form order. */
65
+ fields: EntityFieldDef[];
66
+ }
67
+ declare const ENTITY_SCHEMAS: Record<EntityKind, EntitySchema>;
68
+ declare function EntityAddButton({ ports, onClick }: {
69
+ ports: EditorPorts;
70
+ onClick: () => void;
71
+ }): JSX.Element;
72
+ declare function EntityDeleteButton({ ports, onClick }: {
73
+ ports: EditorPorts;
74
+ onClick: () => void;
75
+ }): JSX.Element;
76
+ declare function EntityEditorDialog({ ports, schema, existingIds, busy, error, onSave, onClose }: {
77
+ ports: EditorPorts;
78
+ schema: EntitySchema;
79
+ existingIds: ReadonlySet<string>;
80
+ busy: boolean;
81
+ error: string | null;
82
+ onSave: (values: Record<string, string>) => void;
83
+ onClose: () => void;
84
+ }): JSX.Element;
85
+ declare function BehaviorAddDialog({ ports, characters, busy, error, onSave, onClose }: {
86
+ ports: EditorPorts;
87
+ /** Characters without a behavior yet: { id, name }. */
88
+ characters: {
89
+ id: string;
90
+ name: string;
91
+ }[];
92
+ busy: boolean;
93
+ error: string | null;
94
+ onSave: (values: {
95
+ characterId: string;
96
+ baseline: string;
97
+ }) => void;
98
+ onClose: () => void;
99
+ }): JSX.Element;
100
+ declare function DeleteConfirmDialog({ ports, title, message, busy, error, onConfirm, onClose }: {
101
+ ports: EditorPorts;
102
+ title: string;
103
+ message: string;
104
+ busy: boolean;
105
+ error: string | null;
106
+ onConfirm: () => void;
107
+ onClose: () => void;
108
+ }): JSX.Element;
109
+ type StructureKind = 'chapter' | 'scene' | 'unit';
110
+ interface StructureParentOption {
111
+ /** chapter_id (for scenes) / scene_id (for units). */
112
+ id: string;
113
+ /** Human label: «Глава 3 — «Прибытие в Москву»» / scene title; hex fallback. */
114
+ label: string;
115
+ }
116
+ declare function StructureAddDialog({ ports, kind, id, chapters, scenes, defaultChapterId, defaultSceneId, busy, error, onSave, onClose }: {
117
+ ports: EditorPorts;
118
+ kind: StructureKind;
119
+ /** Readonly preview id (generated client-side, verified server-side). */
120
+ id: string;
121
+ /** Chapters available as the parent for a scene. */
122
+ chapters: StructureParentOption[];
123
+ /** Scenes available as the parent for a unit. */
124
+ scenes: StructureParentOption[];
125
+ /** Pre-filled parent (current selection), still changeable. */
126
+ defaultChapterId: string | null;
127
+ defaultSceneId: string | null;
128
+ busy: boolean;
129
+ error: string | null;
130
+ onSave: (values: {
131
+ chapterId: string | null;
132
+ sceneId: string | null;
133
+ title: string;
134
+ }) => void;
135
+ onClose: () => void;
136
+ }): JSX.Element;
137
+
138
+ interface WaveformSelection {
139
+ startMs: number;
140
+ endMs: number;
141
+ }
142
+ interface WaveformProps {
143
+ peaks: WaveformPeak[];
144
+ durationMs: number;
145
+ selection: WaveformSelection | null;
146
+ /** -1 = hidden. */
147
+ playbackPositionMs?: number;
148
+ /** Optional signal; when set, the playhead follows it per tick without re-render. */
149
+ playbackSignal?: Signal<number>;
150
+ onRangeChange?: (startMs: number, endMs: number) => void;
151
+ onRangeChangeEnd?: (startMs: number, endMs: number) => void;
152
+ }
153
+ declare function Waveform(props: WaveformProps): preact.JSX.Element;
154
+
155
+ export { BehaviorAddDialog, DeleteConfirmDialog, ENTITY_SCHEMAS, type EditorI18nKey, type EditorI18nPort, type EditorIconComponent, type EditorIconProps, type EditorIconsPort, type EditorModalPort, type EditorModalProps, type EditorPorts, type EditorT, EntityAddButton, EntityDeleteButton, EntityEditorDialog, type EntityFieldDef, type EntityKind, type EntitySchema, StructureAddDialog, type StructureKind, type StructureParentOption, Waveform, type WaveformPeak, chapterId, sceneId, unitId };
package/dist/index.js ADDED
@@ -0,0 +1,542 @@
1
+ import { useState, useRef, useCallback, useEffect } from 'preact/hooks';
2
+ import { jsx, jsxs, Fragment } from 'preact/jsx-runtime';
3
+ import { effect } from '@preact/signals';
4
+
5
+ // src/idgen.ts
6
+ function generateHexId(prefix) {
7
+ const bytes = new Uint8Array(4);
8
+ crypto.getRandomValues(bytes);
9
+ let hex = "";
10
+ for (const b of bytes) hex += b.toString(16).padStart(2, "0");
11
+ return `${prefix}-${hex}`;
12
+ }
13
+ var chapterId = () => generateHexId("ch");
14
+ var sceneId = () => generateHexId("sc");
15
+ var unitId = () => generateHexId("iu");
16
+ var ENTITY_SCHEMAS = {
17
+ character: {
18
+ kind: "character",
19
+ addTitleKey: "entity_add_character",
20
+ deleteTitleKey: "entity_delete_character",
21
+ deleteConfirmKey: "entity_delete_character_confirm",
22
+ fields: [
23
+ { key: "passport.appearance", labelKey: "field_appearance", multiline: true },
24
+ { key: "passport.clothes", labelKey: "field_clothes", multiline: true },
25
+ { key: "passport.video_tokens", labelKey: "field_video_tokens", multiline: true }
26
+ ]
27
+ },
28
+ location: {
29
+ kind: "location",
30
+ addTitleKey: "entity_add_location",
31
+ deleteTitleKey: "entity_delete_location",
32
+ deleteConfirmKey: "entity_delete_location_confirm",
33
+ fields: [
34
+ { key: "description", labelKey: "field_description", multiline: true },
35
+ { key: "environment.time", labelKey: "field_time" },
36
+ { key: "environment.season", labelKey: "field_season" },
37
+ { key: "environment.lighting", labelKey: "field_lighting" },
38
+ { key: "environment.weather", labelKey: "field_weather" },
39
+ { key: "environment.mood", labelKey: "field_mood" },
40
+ { key: "environment.atmosphere", labelKey: "field_atmosphere" }
41
+ ]
42
+ },
43
+ voice: {
44
+ kind: "voice",
45
+ addTitleKey: "entity_add_voice",
46
+ deleteTitleKey: "entity_delete_voice",
47
+ deleteConfirmKey: "entity_delete_voice_confirm",
48
+ fields: [
49
+ { key: "instruction", labelKey: "field_instruction", multiline: true }
50
+ ]
51
+ },
52
+ // Behavior is keyed by an EXISTING character_id (behavior.json mirrors
53
+ // voices.json), so the generic add dialog (free-form id + name) is not used
54
+ // for it — BehaviorAddDialog below picks the character instead. The schema
55
+ // still carries the delete-confirm strings and the field list.
56
+ behavior: {
57
+ kind: "behavior",
58
+ addTitleKey: "entity_add_behavior",
59
+ deleteTitleKey: "entity_delete_behavior",
60
+ deleteConfirmKey: "entity_delete_behavior_confirm",
61
+ fields: [
62
+ { key: "baseline", labelKey: "field_baseline", multiline: true }
63
+ ]
64
+ }
65
+ };
66
+ function EntityAddButton({ ports, onClick }) {
67
+ const { t } = ports.i18n;
68
+ return /* @__PURE__ */ jsx("button", { class: "entity-add-btn", type: "button", "aria-label": t("entity_add"), title: t("entity_add"), onClick, children: /* @__PURE__ */ jsx(ports.icons.Add, { width: 15, height: 15 }) });
69
+ }
70
+ function EntityDeleteButton({ ports, onClick }) {
71
+ const { t } = ports.i18n;
72
+ return /* @__PURE__ */ jsx(
73
+ "button",
74
+ {
75
+ class: "entity-del-btn",
76
+ type: "button",
77
+ "aria-label": t("entity_delete"),
78
+ title: t("entity_delete"),
79
+ onClick: (e) => {
80
+ e.stopPropagation();
81
+ onClick();
82
+ },
83
+ children: /* @__PURE__ */ jsx(ports.icons.Minus, { width: 15, height: 15 })
84
+ }
85
+ );
86
+ }
87
+ function EntityEditorDialog({ ports, schema, existingIds, busy, error, onSave, onClose }) {
88
+ const { t } = ports.i18n;
89
+ const { Modal } = ports.modal;
90
+ const [values, setValues] = useState({});
91
+ const [formError, setFormError] = useState(null);
92
+ const set = (key, v) => setValues((prev) => ({ ...prev, [key]: v }));
93
+ const handleSave = () => {
94
+ const name = (values.name ?? "").trim();
95
+ if (!name) {
96
+ setFormError(t("entity_name_required"));
97
+ return;
98
+ }
99
+ const id = (values.id ?? "").trim();
100
+ if (id && existingIds.has(id)) {
101
+ setFormError(t("entity_id_exists"));
102
+ return;
103
+ }
104
+ setFormError(null);
105
+ onSave(values);
106
+ };
107
+ return /* @__PURE__ */ jsx(Modal, { title: t(schema.addTitleKey), onClose, footer: /* @__PURE__ */ jsxs(Fragment, { children: [
108
+ /* @__PURE__ */ jsx("button", { type: "button", class: "btn btn--outlined", disabled: busy, onClick: onClose, children: t("dialog_cancel") }),
109
+ /* @__PURE__ */ jsx("button", { type: "button", class: "btn", disabled: busy, onClick: handleSave, children: busy ? t("edit_saving") : t("edit_save") })
110
+ ] }), children: /* @__PURE__ */ jsxs("div", { class: "entity-form", children: [
111
+ /* @__PURE__ */ jsxs("div", { class: "edit-field", children: [
112
+ /* @__PURE__ */ jsx("label", { class: "edit-field__label", for: "entity-id", children: t("entity_id") }),
113
+ /* @__PURE__ */ jsx(
114
+ "input",
115
+ {
116
+ id: "entity-id",
117
+ class: "edit-field__input",
118
+ type: "text",
119
+ value: values.id ?? "",
120
+ placeholder: t("entity_id_placeholder"),
121
+ onInput: (e) => set("id", e.target.value)
122
+ }
123
+ ),
124
+ /* @__PURE__ */ jsx("span", { class: "entity-form__hint", children: t("entity_id_hint") })
125
+ ] }),
126
+ /* @__PURE__ */ jsxs("div", { class: "edit-field", children: [
127
+ /* @__PURE__ */ jsxs("label", { class: "edit-field__label", for: "entity-name", children: [
128
+ t("field_name"),
129
+ " *"
130
+ ] }),
131
+ /* @__PURE__ */ jsx(
132
+ "input",
133
+ {
134
+ id: "entity-name",
135
+ class: "edit-field__input",
136
+ type: "text",
137
+ value: values.name ?? "",
138
+ onInput: (e) => set("name", e.target.value)
139
+ }
140
+ )
141
+ ] }),
142
+ schema.fields.map((f) => /* @__PURE__ */ jsxs("div", { class: "edit-field", children: [
143
+ /* @__PURE__ */ jsx("label", { class: "edit-field__label", for: `entity-${f.key}`, children: t(f.labelKey) }),
144
+ f.multiline ? /* @__PURE__ */ jsx(
145
+ "textarea",
146
+ {
147
+ id: `entity-${f.key}`,
148
+ class: "edit-field__input edit-field__input--area",
149
+ rows: 3,
150
+ value: values[f.key] ?? "",
151
+ onInput: (e) => set(f.key, e.target.value)
152
+ }
153
+ ) : /* @__PURE__ */ jsx(
154
+ "input",
155
+ {
156
+ id: `entity-${f.key}`,
157
+ class: "edit-field__input",
158
+ type: "text",
159
+ value: values[f.key] ?? "",
160
+ onInput: (e) => set(f.key, e.target.value)
161
+ }
162
+ )
163
+ ] }, f.key)),
164
+ (formError || error) && /* @__PURE__ */ jsx("div", { class: "entity-form__error", children: formError ?? error })
165
+ ] }) });
166
+ }
167
+ function BehaviorAddDialog({ ports, characters, busy, error, onSave, onClose }) {
168
+ const { t } = ports.i18n;
169
+ const { Modal } = ports.modal;
170
+ const [characterId, setCharacterId] = useState(characters[0]?.id ?? "");
171
+ const [baseline, setBaseline] = useState("");
172
+ const [formError, setFormError] = useState(null);
173
+ const handleSave = () => {
174
+ if (!characterId) {
175
+ setFormError(t("behavior_character_required"));
176
+ return;
177
+ }
178
+ setFormError(null);
179
+ onSave({ characterId, baseline: baseline.trim() });
180
+ };
181
+ return /* @__PURE__ */ jsx(Modal, { title: t("entity_add_behavior"), onClose, footer: /* @__PURE__ */ jsxs(Fragment, { children: [
182
+ /* @__PURE__ */ jsx("button", { type: "button", class: "btn btn--outlined", disabled: busy, onClick: onClose, children: t("dialog_cancel") }),
183
+ /* @__PURE__ */ jsx("button", { type: "button", class: "btn", disabled: busy, onClick: handleSave, children: busy ? t("edit_saving") : t("edit_save") })
184
+ ] }), children: /* @__PURE__ */ jsxs("div", { class: "entity-form", children: [
185
+ /* @__PURE__ */ jsxs("div", { class: "edit-field", children: [
186
+ /* @__PURE__ */ jsxs("label", { class: "edit-field__label", for: "behavior-character", children: [
187
+ t("behavior_character"),
188
+ " *"
189
+ ] }),
190
+ /* @__PURE__ */ jsx(
191
+ "select",
192
+ {
193
+ id: "behavior-character",
194
+ class: "edit-field__input",
195
+ value: characterId,
196
+ onChange: (e) => setCharacterId(e.target.value),
197
+ children: characters.map((c) => /* @__PURE__ */ jsx("option", { value: c.id, children: c.name ? `${c.name} (${c.id})` : c.id }, c.id))
198
+ }
199
+ ),
200
+ /* @__PURE__ */ jsx("span", { class: "entity-form__hint", children: t("behavior_character_hint") })
201
+ ] }),
202
+ /* @__PURE__ */ jsxs("div", { class: "edit-field", children: [
203
+ /* @__PURE__ */ jsx("label", { class: "edit-field__label", for: "behavior-baseline", children: t("field_baseline") }),
204
+ /* @__PURE__ */ jsx(
205
+ "textarea",
206
+ {
207
+ id: "behavior-baseline",
208
+ class: "edit-field__input edit-field__input--area",
209
+ rows: 3,
210
+ value: baseline,
211
+ onInput: (e) => setBaseline(e.target.value)
212
+ }
213
+ )
214
+ ] }),
215
+ (formError || error) && /* @__PURE__ */ jsx("div", { class: "entity-form__error", children: formError ?? error })
216
+ ] }) });
217
+ }
218
+ function DeleteConfirmDialog({ ports, title, message, busy, error, onConfirm, onClose }) {
219
+ const { t } = ports.i18n;
220
+ const { Modal } = ports.modal;
221
+ return /* @__PURE__ */ jsx(Modal, { title, onClose, footer: /* @__PURE__ */ jsxs(Fragment, { children: [
222
+ /* @__PURE__ */ jsx("button", { type: "button", class: "btn btn--outlined", disabled: busy, onClick: onClose, children: t("dialog_cancel") }),
223
+ /* @__PURE__ */ jsx("button", { type: "button", class: "btn btn--error-solid", disabled: busy, onClick: onConfirm, children: busy ? t("edit_saving") : t("entity_delete_btn") })
224
+ ] }), children: /* @__PURE__ */ jsxs(Fragment, { children: [
225
+ /* @__PURE__ */ jsx("p", { class: "modal__notice", children: message }),
226
+ error && /* @__PURE__ */ jsx("div", { class: "entity-form__error", children: error })
227
+ ] }) });
228
+ }
229
+ function StructureAddDialog({ ports, kind, id, chapters, scenes, defaultChapterId, defaultSceneId, busy, error, onSave, onClose }) {
230
+ const { t } = ports.i18n;
231
+ const { Modal } = ports.modal;
232
+ const [title, setTitle] = useState("");
233
+ const [chapterId2, setChapterId] = useState(defaultChapterId ?? chapters[0]?.id ?? "");
234
+ const [sceneId2, setSceneId] = useState(defaultSceneId ?? scenes[0]?.id ?? "");
235
+ const [formError, setFormError] = useState(null);
236
+ const titleKey = kind === "chapter" ? "field_chapter_title" : kind === "scene" ? "field_scene_title" : "field_name";
237
+ const handleSave = () => {
238
+ if (kind === "scene" && !chapterId2) {
239
+ setFormError(t("structure_parent_required"));
240
+ return;
241
+ }
242
+ if (kind === "unit" && !sceneId2) {
243
+ setFormError(t("structure_parent_required"));
244
+ return;
245
+ }
246
+ setFormError(null);
247
+ onSave({ chapterId: kind === "scene" ? chapterId2 : null, sceneId: kind === "unit" ? sceneId2 : null, title: title.trim() });
248
+ };
249
+ return /* @__PURE__ */ jsx(Modal, { title: t(kind === "chapter" ? "structure_add_chapter" : kind === "scene" ? "structure_add_scene" : "structure_add_unit"), onClose, footer: /* @__PURE__ */ jsxs(Fragment, { children: [
250
+ /* @__PURE__ */ jsx("button", { type: "button", class: "btn btn--outlined", disabled: busy, onClick: onClose, children: t("dialog_cancel") }),
251
+ /* @__PURE__ */ jsx("button", { type: "button", class: "btn", disabled: busy, onClick: handleSave, children: busy ? t("edit_saving") : t("edit_save") })
252
+ ] }), children: /* @__PURE__ */ jsxs("div", { class: "entity-form", children: [
253
+ /* @__PURE__ */ jsxs("div", { class: "edit-field", children: [
254
+ /* @__PURE__ */ jsx("label", { class: "edit-field__label", for: "structure-id", children: t("entity_id") }),
255
+ /* @__PURE__ */ jsx("input", { id: "structure-id", class: "edit-field__input", type: "text", value: id, readOnly: true }),
256
+ /* @__PURE__ */ jsx("span", { class: "entity-form__hint", children: t("structure_id_hint") })
257
+ ] }),
258
+ kind === "scene" && /* @__PURE__ */ jsxs("div", { class: "edit-field", children: [
259
+ /* @__PURE__ */ jsx("label", { class: "edit-field__label", for: "structure-chapter", children: t("field_chapter_title") }),
260
+ /* @__PURE__ */ jsx(
261
+ "select",
262
+ {
263
+ id: "structure-chapter",
264
+ class: "edit-field__input",
265
+ value: chapterId2,
266
+ onChange: (e) => setChapterId(e.target.value),
267
+ children: chapters.map((c) => /* @__PURE__ */ jsx("option", { value: c.id, children: c.label }, c.id))
268
+ }
269
+ )
270
+ ] }),
271
+ kind === "unit" && /* @__PURE__ */ jsxs("div", { class: "edit-field", children: [
272
+ /* @__PURE__ */ jsx("label", { class: "edit-field__label", for: "structure-scene", children: t("field_scene_title") }),
273
+ /* @__PURE__ */ jsx(
274
+ "select",
275
+ {
276
+ id: "structure-scene",
277
+ class: "edit-field__input",
278
+ value: sceneId2,
279
+ onChange: (e) => setSceneId(e.target.value),
280
+ children: scenes.map((s) => /* @__PURE__ */ jsx("option", { value: s.id, children: s.label }, s.id))
281
+ }
282
+ )
283
+ ] }),
284
+ /* @__PURE__ */ jsxs("div", { class: "edit-field", children: [
285
+ /* @__PURE__ */ jsx("label", { class: "edit-field__label", for: "structure-title", children: t(titleKey) }),
286
+ /* @__PURE__ */ jsx(
287
+ "input",
288
+ {
289
+ id: "structure-title",
290
+ class: "edit-field__input",
291
+ type: "text",
292
+ value: title,
293
+ onInput: (e) => setTitle(e.target.value)
294
+ }
295
+ )
296
+ ] }),
297
+ kind === "chapter" && /* @__PURE__ */ jsx("p", { class: "entity-form__hint", style: "margin-top:4px;", children: t("chapter_auto_create_hint") }),
298
+ kind === "scene" && /* @__PURE__ */ jsx("p", { class: "entity-form__hint", style: "margin-top:4px;", children: t("scene_auto_create_hint") }),
299
+ (formError || error) && /* @__PURE__ */ jsx("div", { class: "entity-form__error", children: formError ?? error })
300
+ ] }) });
301
+ }
302
+ var PAD = 12;
303
+ var TOUCH_SLOP = 24;
304
+ function formatMs(ms) {
305
+ const totalSec = Math.floor(ms / 1e3);
306
+ const minutes = Math.floor(totalSec / 60);
307
+ const seconds = totalSec % 60;
308
+ const millis = Math.floor(ms % 1e3 / 100);
309
+ return `${minutes}:${String(seconds).padStart(2, "0")}.${millis}`;
310
+ }
311
+ function waveformTextColor() {
312
+ return document.documentElement.dataset.theme === "light" ? "#C9A15A" : "#F2E9DC";
313
+ }
314
+ function Waveform(props) {
315
+ const { peaks, durationMs, selection, onRangeChange, onRangeChangeEnd } = props;
316
+ const canvasRef = useRef(null);
317
+ const wrapRef = useRef(null);
318
+ const dragRef = useRef({
319
+ target: "none",
320
+ startMs: 0,
321
+ endMs: 0
322
+ });
323
+ const playbackRef = useRef(props.playbackPositionMs ?? -1);
324
+ playbackRef.current = props.playbackPositionMs ?? -1;
325
+ const draw = useCallback(() => {
326
+ const canvas = canvasRef.current;
327
+ const wrap = wrapRef.current;
328
+ if (!canvas || !wrap) return;
329
+ const dpr = window.devicePixelRatio || 1;
330
+ const cssW = wrap.clientWidth;
331
+ const cssH = wrap.clientHeight;
332
+ if (cssW <= 0 || cssH <= 0) return;
333
+ if (canvas.width !== Math.round(cssW * dpr) || canvas.height !== Math.round(cssH * dpr)) {
334
+ canvas.width = Math.round(cssW * dpr);
335
+ canvas.height = Math.round(cssH * dpr);
336
+ }
337
+ const ctx = canvas.getContext("2d");
338
+ if (!ctx) return;
339
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
340
+ ctx.clearRect(0, 0, cssW, cssH);
341
+ const w = cssW;
342
+ const h = cssH;
343
+ const drawLeft = PAD;
344
+ const drawRight = w - PAD;
345
+ const drawWidth = drawRight - drawLeft;
346
+ const midY = h / 2;
347
+ const s = h / 72;
348
+ const playbackPositionMs = playbackRef.current;
349
+ const msToX = (ms) => durationMs > 0 ? ms / durationMs * drawWidth : 0;
350
+ if (!peaks || peaks.length === 0) {
351
+ ctx.fillStyle = waveformTextColor();
352
+ ctx.textAlign = "center";
353
+ ctx.textBaseline = "middle";
354
+ ctx.font = `500 12px system-ui, sans-serif`;
355
+ ctx.fillText("No waveform data", w / 2, midY);
356
+ } else {
357
+ ctx.strokeStyle = "rgba(144,202,249,.53)";
358
+ ctx.lineWidth = 1.5;
359
+ ctx.lineCap = "round";
360
+ const barCount = peaks.length;
361
+ const barWidth = drawWidth / barCount;
362
+ ctx.beginPath();
363
+ for (let i = 0; i < barCount; i++) {
364
+ const peak = peaks[i];
365
+ const x = drawLeft + i * barWidth + barWidth / 2;
366
+ const posHeight = Math.min(Math.abs(peak.pos) * midY * 0.9, midY - 4);
367
+ const negHeight = Math.min(Math.abs(peak.neg) * midY * 0.9, midY - 4);
368
+ ctx.moveTo(x, midY - posHeight);
369
+ ctx.lineTo(x, midY + negHeight);
370
+ }
371
+ ctx.stroke();
372
+ }
373
+ let selLeft = 0;
374
+ let selRight = 0;
375
+ if (selection && durationMs > 0) {
376
+ selLeft = drawLeft + msToX(selection.startMs);
377
+ selRight = drawLeft + msToX(selection.endMs);
378
+ if (selRight <= selLeft) selRight = selLeft + 4;
379
+ ctx.fillStyle = "rgba(144,202,249,.20)";
380
+ ctx.fillRect(selLeft, 0, selRight - selLeft, h);
381
+ ctx.strokeStyle = "rgba(255,183,77,.33)";
382
+ ctx.lineWidth = 1;
383
+ ctx.strokeRect(selLeft, 0, selRight - selLeft, h);
384
+ ctx.strokeStyle = "#FFB74D";
385
+ ctx.lineWidth = 3;
386
+ ctx.fillStyle = "#FFB74D";
387
+ const handleRadius = 8 * s;
388
+ for (const hx of [selLeft, selRight]) {
389
+ ctx.beginPath();
390
+ ctx.moveTo(hx, 0);
391
+ ctx.lineTo(hx, h);
392
+ ctx.stroke();
393
+ ctx.beginPath();
394
+ ctx.arc(hx, midY, handleRadius, 0, Math.PI * 2);
395
+ ctx.fill();
396
+ ctx.beginPath();
397
+ ctx.arc(hx, midY - 16 * s, 3 * s, 0, Math.PI * 2);
398
+ ctx.fill();
399
+ ctx.beginPath();
400
+ ctx.arc(hx, midY + 16 * s, 3 * s, 0, Math.PI * 2);
401
+ ctx.fill();
402
+ }
403
+ }
404
+ if (playbackPositionMs >= 0 && durationMs > 0) {
405
+ const px = drawLeft + msToX(playbackPositionMs);
406
+ ctx.strokeStyle = "#FFFFFF";
407
+ ctx.lineWidth = 2.5;
408
+ ctx.beginPath();
409
+ ctx.moveTo(px, 0);
410
+ ctx.lineTo(px, h);
411
+ ctx.stroke();
412
+ const tri = 8 * s;
413
+ ctx.beginPath();
414
+ ctx.moveTo(px, 0);
415
+ ctx.lineTo(px - tri, -tri);
416
+ ctx.lineTo(px + tri, -tri);
417
+ ctx.closePath();
418
+ ctx.stroke();
419
+ }
420
+ if (selection && durationMs > 0) {
421
+ const startLabel = formatMs(selection.startMs);
422
+ const endLabel = formatMs(selection.endMs);
423
+ const totalLabel = formatMs(durationMs);
424
+ ctx.fillStyle = waveformTextColor();
425
+ ctx.font = "14px system-ui, sans-serif";
426
+ ctx.textBaseline = "alphabetic";
427
+ ctx.textAlign = "right";
428
+ const startX = Math.max(selLeft - 6, ctx.measureText(startLabel).width);
429
+ ctx.fillText(startLabel, startX, h - 7);
430
+ ctx.textAlign = "left";
431
+ const endX = Math.max(
432
+ Math.min(selRight + 6, w - ctx.measureText(endLabel).width),
433
+ startX
434
+ );
435
+ ctx.fillText(endLabel, endX, h - 7);
436
+ ctx.textAlign = "left";
437
+ ctx.fillText(totalLabel, drawLeft + 2, 14);
438
+ }
439
+ }, [peaks, durationMs, selection]);
440
+ useEffect(() => {
441
+ draw();
442
+ }, [draw]);
443
+ useEffect(() => {
444
+ const wrap = wrapRef.current;
445
+ if (!wrap) return;
446
+ if (typeof ResizeObserver === "undefined") {
447
+ const onResize = () => draw();
448
+ window.addEventListener("resize", onResize);
449
+ return () => window.removeEventListener("resize", onResize);
450
+ }
451
+ const ro = new ResizeObserver(() => draw());
452
+ ro.observe(wrap);
453
+ return () => ro.disconnect();
454
+ }, [draw]);
455
+ const playbackSignal = props.playbackSignal;
456
+ useEffect(() => {
457
+ if (!playbackSignal) return;
458
+ return effect(() => {
459
+ playbackRef.current = playbackSignal.value;
460
+ draw();
461
+ });
462
+ }, [draw, playbackSignal]);
463
+ const onPointerDown = (e) => {
464
+ if (durationMs <= 0) return;
465
+ const canvas = canvasRef.current;
466
+ if (!canvas) return;
467
+ const rect = canvas.getBoundingClientRect();
468
+ const drawWidth = rect.width - PAD * 2;
469
+ if (drawWidth <= 0) return;
470
+ const drawLeft = PAD;
471
+ const selLeft = drawLeft + (selection ? selection.startMs / durationMs * drawWidth : 0);
472
+ const selRight = drawLeft + (selection ? selection.endMs / durationMs * drawWidth : 0);
473
+ const x = e.clientX - rect.left;
474
+ const distToStart = Math.abs(x - selLeft);
475
+ const distToEnd = Math.abs(x - selRight);
476
+ let target = "none";
477
+ if (distToStart < TOUCH_SLOP * 2) target = "start";
478
+ else if (distToEnd < TOUCH_SLOP * 2) target = "end";
479
+ if (target === "none") return;
480
+ dragRef.current = {
481
+ target,
482
+ startMs: selection?.startMs ?? 0,
483
+ endMs: selection?.endMs ?? 0
484
+ };
485
+ try {
486
+ canvas.setPointerCapture(e.pointerId);
487
+ } catch {
488
+ }
489
+ e.preventDefault();
490
+ };
491
+ const onPointerMove = (e) => {
492
+ const drag = dragRef.current;
493
+ if (drag.target === "none") return;
494
+ const canvas = canvasRef.current;
495
+ if (!canvas) return;
496
+ const rect = canvas.getBoundingClientRect();
497
+ const drawLeft = PAD;
498
+ const drawWidth = rect.width - PAD * 2;
499
+ if (drawWidth <= 0) return;
500
+ const x = Math.min(Math.max(e.clientX - rect.left - drawLeft, 0), drawWidth);
501
+ const ms = Math.round(x / drawWidth * durationMs);
502
+ let startMs = drag.startMs;
503
+ let endMs = drag.endMs;
504
+ if (drag.target === "start") {
505
+ startMs = Math.min(ms, endMs - 50);
506
+ } else {
507
+ endMs = Math.max(ms, startMs + 50);
508
+ endMs = Math.min(endMs, durationMs);
509
+ }
510
+ if (startMs !== drag.startMs || endMs !== drag.endMs) {
511
+ drag.startMs = startMs;
512
+ drag.endMs = endMs;
513
+ onRangeChange?.(startMs, endMs);
514
+ }
515
+ };
516
+ const onPointerUp = (e) => {
517
+ const drag = dragRef.current;
518
+ if (drag.target === "none") return;
519
+ drag.target = "none";
520
+ const canvas = canvasRef.current;
521
+ try {
522
+ canvas?.releasePointerCapture(e.pointerId);
523
+ } catch {
524
+ }
525
+ onRangeChangeEnd?.(drag.startMs, drag.endMs);
526
+ };
527
+ return /* @__PURE__ */ jsx("div", { ref: wrapRef, class: "waveform", style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx(
528
+ "canvas",
529
+ {
530
+ ref: canvasRef,
531
+ style: { width: "100%", height: "100%", display: "block", touchAction: "none" },
532
+ onPointerDown,
533
+ onPointerMove,
534
+ onPointerUp,
535
+ onPointerCancel: onPointerUp
536
+ }
537
+ ) });
538
+ }
539
+
540
+ export { BehaviorAddDialog, DeleteConfirmDialog, ENTITY_SCHEMAS, EntityAddButton, EntityDeleteButton, EntityEditorDialog, StructureAddDialog, Waveform, chapterId, sceneId, unitId };
541
+ //# sourceMappingURL=index.js.map
542
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/idgen.ts","../src/entityEditor.tsx","../src/waveform.tsx"],"names":["chapterId","sceneId","jsx"],"mappings":"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","file":"index.js","sourcesContent":["// ─────────────────────────────────────────────────────\n// Structure id PREVIEW helper (chapter / scene / unit).\n//\n// The server is the single authority for ids (backend/book/lazy-book/paths.js:\n// `prefix + '-' + crypto.randomBytes(4).toString('hex')`). These mirrors exist\n// ONLY so the add dialogs can show a readonly id preview; on save the server\n// keeps the proposed id when unique and otherwise regenerates it (the dialogs\n// never validate uniqueness themselves). This is a trivial random-hex format,\n// NOT the cyrToLatin transliteration algorithm — that stays server-only.\n// ─────────────────────────────────────────────────────\nfunction generateHexId(prefix: string): string {\n const bytes = new Uint8Array(4);\n crypto.getRandomValues(bytes);\n let hex = '';\n for (const b of bytes) hex += b.toString(16).padStart(2, '0');\n return `${prefix}-${hex}`;\n}\n\nexport const chapterId = (): string => generateHexId('ch');\nexport const sceneId = (): string => generateHexId('sc');\nexport const unitId = (): string => generateHexId('iu');\n","// ─────────────────────────────────────────────────────\n// Entity Editor — reusable Add/Delete pattern for the\n// Editor tables (Characters / Locations / Voices, and in\n// the future Unit / Scene). One schema-driven add dialog\n// and one confirmation dialog, so three (or five) nearly\n// identical implementations never get forked.\n//\n// Table ── EntityAddButton (overlay, top-right)\n// ↓ EntityEditorDialog (schema form)\n// ↓ Save → POST /book/{id}/{entities}\n// Row ── EntityDeleteButton\n// ↓ DeleteConfirmDialog\n// ↓ Delete → DELETE /book/{id}/{entities}/{id}\n//\n// The id field is free-form on purpose: a canonical latin\n// snake id is kept verbatim, anything else (Cyrillic, …)\n// is transliterated SERVER-SIDE via the existing backend\n// utility (utils/entity-id reusing cyrToLatin) — the\n// frontends never duplicate the algorithm.\n//\n// HOST DEPENDENCIES (injected via EditorPorts):\n// - i18n (t function, typed key union)\n// - icons (IconAdd, IconMinus)\n// - modal (Modal component)\n// ─────────────────────────────────────────────────────\nimport type { JSX } from 'preact';\nimport { useState } from 'preact/hooks';\nimport type { EditorI18nKey, EditorPorts } from './ports';\n\nexport type EntityKind = 'character' | 'location' | 'voice' | 'behavior';\n\nexport interface EntityFieldDef {\n /** Flat form key — dotted for nested payload keys (passport.appearance). */\n key: string;\n /** i18n key of the field label. */\n labelKey: EditorI18nKey;\n multiline?: boolean;\n}\n\nexport interface EntitySchema {\n kind: EntityKind;\n /** i18n key: add-dialog title (\"Add character\"). */\n addTitleKey: EditorI18nKey;\n /** i18n key: delete-confirm title (\"Delete character?\"). */\n deleteTitleKey: EditorI18nKey;\n /** i18n key: delete-confirm body (\"Are you sure…\"). */\n deleteConfirmKey: EditorI18nKey;\n /** Extra fields beyond id + name, in form order. */\n fields: EntityFieldDef[];\n}\n\n// ── Schemas built from the CURRENT entity JSON structure (the fields the\n// editor cards render and PATCH supports — legacy demo keys like\n// base_appearance / cinematic_space are not part of the schema anymore). ──\nexport const ENTITY_SCHEMAS: Record<EntityKind, EntitySchema> = {\n character: {\n kind: 'character',\n addTitleKey: 'entity_add_character',\n deleteTitleKey: 'entity_delete_character',\n deleteConfirmKey: 'entity_delete_character_confirm',\n fields: [\n { key: 'passport.appearance', labelKey: 'field_appearance', multiline: true },\n { key: 'passport.clothes', labelKey: 'field_clothes', multiline: true },\n { key: 'passport.video_tokens', labelKey: 'field_video_tokens', multiline: true },\n ],\n },\n location: {\n kind: 'location',\n addTitleKey: 'entity_add_location',\n deleteTitleKey: 'entity_delete_location',\n deleteConfirmKey: 'entity_delete_location_confirm',\n fields: [\n { key: 'description', labelKey: 'field_description', multiline: true },\n { key: 'environment.time', labelKey: 'field_time' },\n { key: 'environment.season', labelKey: 'field_season' },\n { key: 'environment.lighting', labelKey: 'field_lighting' },\n { key: 'environment.weather', labelKey: 'field_weather' },\n { key: 'environment.mood', labelKey: 'field_mood' },\n { key: 'environment.atmosphere', labelKey: 'field_atmosphere' },\n ],\n },\n voice: {\n kind: 'voice',\n addTitleKey: 'entity_add_voice',\n deleteTitleKey: 'entity_delete_voice',\n deleteConfirmKey: 'entity_delete_voice_confirm',\n fields: [\n { key: 'instruction', labelKey: 'field_instruction', multiline: true },\n ],\n },\n // Behavior is keyed by an EXISTING character_id (behavior.json mirrors\n // voices.json), so the generic add dialog (free-form id + name) is not used\n // for it — BehaviorAddDialog below picks the character instead. The schema\n // still carries the delete-confirm strings and the field list.\n behavior: {\n kind: 'behavior',\n addTitleKey: 'entity_add_behavior',\n deleteTitleKey: 'entity_delete_behavior',\n deleteConfirmKey: 'entity_delete_behavior_confirm',\n fields: [\n { key: 'baseline', labelKey: 'field_baseline', multiline: true },\n ],\n },\n};\n\n// ── Round \"+\" — floats in the table's top-right corner (overlay, no layout\n// space, sized close to the existing 24dp collapse chevrons). ──\nexport function EntityAddButton({ ports, onClick }: { ports: EditorPorts; onClick: () => void }): JSX.Element {\n const { t } = ports.i18n;\n return (\n <button class=\"entity-add-btn\" type=\"button\" aria-label={t('entity_add')} title={t('entity_add')} onClick={onClick}>\n <ports.icons.Add width={15} height={15} />\n </button>\n );\n}\n\n// ── Small square \"−\" with a soft destructive accent, next to the row's id. ──\nexport function EntityDeleteButton({ ports, onClick }: { ports: EditorPorts; onClick: () => void }): JSX.Element {\n const { t } = ports.i18n;\n return (\n <button\n class=\"entity-del-btn\"\n type=\"button\"\n aria-label={t('entity_delete')}\n title={t('entity_delete')}\n onClick={(e) => { e.stopPropagation(); onClick(); }}\n >\n <ports.icons.Minus width={15} height={15} />\n </button>\n );\n}\n\n// ── Schema-driven add dialog. Validates the required name + id uniqueness\n// client-side (the backend re-checks on save); the id is left free-form so\n// the server can transliterate it. ──\nexport function EntityEditorDialog({ ports, schema, existingIds, busy, error, onSave, onClose }: {\n ports: EditorPorts;\n schema: EntitySchema;\n existingIds: ReadonlySet<string>;\n busy: boolean;\n error: string | null;\n onSave: (values: Record<string, string>) => void;\n onClose: () => void;\n}): JSX.Element {\n const { t } = ports.i18n;\n const { Modal } = ports.modal;\n const [values, setValues] = useState<Record<string, string>>({});\n const [formError, setFormError] = useState<string | null>(null);\n\n const set = (key: string, v: string) => setValues((prev) => ({ ...prev, [key]: v }));\n\n const handleSave = () => {\n const name = (values.name ?? '').trim();\n if (!name) {\n setFormError(t('entity_name_required'));\n return;\n }\n const id = (values.id ?? '').trim();\n if (id && existingIds.has(id)) {\n setFormError(t('entity_id_exists'));\n return;\n }\n setFormError(null);\n onSave(values);\n };\n\n return (\n <Modal title={t(schema.addTitleKey)} onClose={onClose} footer={\n <>\n <button type=\"button\" class=\"btn btn--outlined\" disabled={busy} onClick={onClose}>{t('dialog_cancel')}</button>\n <button type=\"button\" class=\"btn\" disabled={busy} onClick={handleSave}>\n {busy ? t('edit_saving') : t('edit_save')}\n </button>\n </>\n }>\n <div class=\"entity-form\">\n <div class=\"edit-field\">\n <label class=\"edit-field__label\" for=\"entity-id\">{t('entity_id')}</label>\n <input\n id=\"entity-id\"\n class=\"edit-field__input\"\n type=\"text\"\n value={values.id ?? ''}\n placeholder={t('entity_id_placeholder')}\n onInput={(e) => set('id', (e.target as HTMLInputElement).value)}\n />\n <span class=\"entity-form__hint\">{t('entity_id_hint')}</span>\n </div>\n <div class=\"edit-field\">\n <label class=\"edit-field__label\" for=\"entity-name\">{t('field_name')} *</label>\n <input\n id=\"entity-name\"\n class=\"edit-field__input\"\n type=\"text\"\n value={values.name ?? ''}\n onInput={(e) => set('name', (e.target as HTMLInputElement).value)}\n />\n </div>\n {schema.fields.map((f) => (\n <div class=\"edit-field\" key={f.key}>\n <label class=\"edit-field__label\" for={`entity-${f.key}`}>{t(f.labelKey)}</label>\n {f.multiline ? (\n <textarea\n id={`entity-${f.key}`}\n class=\"edit-field__input edit-field__input--area\"\n rows={3}\n value={values[f.key] ?? ''}\n onInput={(e) => set(f.key, (e.target as HTMLTextAreaElement).value)}\n />\n ) : (\n <input\n id={`entity-${f.key}`}\n class=\"edit-field__input\"\n type=\"text\"\n value={values[f.key] ?? ''}\n onInput={(e) => set(f.key, (e.target as HTMLInputElement).value)}\n />\n )}\n </div>\n ))}\n {(formError || error) && <div class=\"entity-form__error\">{(formError ?? error) as string}</div>}\n </div>\n </Modal>\n );\n}\n\n// ── Behavior add dialog — a behavior belongs to an EXISTING character\n// (behavior.json is keyed by character_id), so instead of the generic\n// free-form id field the dialog picks a character that has no behavior\n// yet. Same visual pattern (Modal + entity-form) as EntityEditorDialog. ──\nexport function BehaviorAddDialog({ ports, characters, busy, error, onSave, onClose }: {\n ports: EditorPorts;\n /** Characters without a behavior yet: { id, name }. */\n characters: { id: string; name: string }[];\n busy: boolean;\n error: string | null;\n onSave: (values: { characterId: string; baseline: string }) => void;\n onClose: () => void;\n}): JSX.Element {\n const { t } = ports.i18n;\n const { Modal } = ports.modal;\n const [characterId, setCharacterId] = useState(characters[0]?.id ?? '');\n const [baseline, setBaseline] = useState('');\n const [formError, setFormError] = useState<string | null>(null);\n\n const handleSave = () => {\n if (!characterId) {\n setFormError(t('behavior_character_required'));\n return;\n }\n setFormError(null);\n onSave({ characterId, baseline: baseline.trim() });\n };\n\n return (\n <Modal title={t('entity_add_behavior')} onClose={onClose} footer={\n <>\n <button type=\"button\" class=\"btn btn--outlined\" disabled={busy} onClick={onClose}>{t('dialog_cancel')}</button>\n <button type=\"button\" class=\"btn\" disabled={busy} onClick={handleSave}>\n {busy ? t('edit_saving') : t('edit_save')}\n </button>\n </>\n }>\n <div class=\"entity-form\">\n <div class=\"edit-field\">\n <label class=\"edit-field__label\" for=\"behavior-character\">{t('behavior_character')} *</label>\n <select\n id=\"behavior-character\"\n class=\"edit-field__input\"\n value={characterId}\n onChange={(e) => setCharacterId((e.target as HTMLSelectElement).value)}\n >\n {characters.map((c) => (\n <option key={c.id} value={c.id}>{c.name ? `${c.name} (${c.id})` : c.id}</option>\n ))}\n </select>\n <span class=\"entity-form__hint\">{t('behavior_character_hint')}</span>\n </div>\n <div class=\"edit-field\">\n <label class=\"edit-field__label\" for=\"behavior-baseline\">{t('field_baseline')}</label>\n <textarea\n id=\"behavior-baseline\"\n class=\"edit-field__input edit-field__input--area\"\n rows={3}\n value={baseline}\n onInput={(e) => setBaseline((e.target as HTMLTextAreaElement).value)}\n />\n </div>\n {(formError || error) && <div class=\"entity-form__error\">{(formError ?? error) as string}</div>}\n </div>\n </Modal>\n );\n}\n\n// ── Delete confirmation — destructive action requires explicit confirmation. ──\nexport function DeleteConfirmDialog({ ports, title, message, busy, error, onConfirm, onClose }: {\n ports: EditorPorts;\n title: string;\n message: string;\n busy: boolean;\n error: string | null;\n onConfirm: () => void;\n onClose: () => void;\n}): JSX.Element {\n const { t } = ports.i18n;\n const { Modal } = ports.modal;\n return (\n <Modal title={title} onClose={onClose} footer={\n <>\n <button type=\"button\" class=\"btn btn--outlined\" disabled={busy} onClick={onClose}>{t('dialog_cancel')}</button>\n <button type=\"button\" class=\"btn btn--error-solid\" disabled={busy} onClick={onConfirm}>\n {busy ? t('edit_saving') : t('entity_delete_btn')}\n </button>\n </>\n }>\n <>\n <p class=\"modal__notice\">{message}</p>\n {error && <div class=\"entity-form__error\">{error}</div>}\n </>\n </Modal>\n );\n}\n\n// ═════════════════════════════════════════════════════════════════\n// STRUCTURE add/delete — chapters / scenes / units (editor hierarchy)\n//\n// Глава → Сцена → Модуль. The add dialog always shows the readonly id\n// preview; a scene additionally picks its PARENT chapter (pre-filled with the\n// current chapter, still changeable) and a unit picks its parent scene (the\n// chapter is derived from the scene — no chapter selection for modules).\n// Titles are optional (the entity's name/title is edited in the tab after).\n// ═════════════════════════════════════════════════════════════════\n\nexport type StructureKind = 'chapter' | 'scene' | 'unit';\n\nexport interface StructureParentOption {\n /** chapter_id (for scenes) / scene_id (for units). */\n id: string;\n /** Human label: «Глава 3 — «Прибытие в Москву»» / scene title; hex fallback. */\n label: string;\n}\n\nexport function StructureAddDialog({ ports, kind, id, chapters, scenes, defaultChapterId, defaultSceneId, busy, error, onSave, onClose }: {\n ports: EditorPorts;\n kind: StructureKind;\n /** Readonly preview id (generated client-side, verified server-side). */\n id: string;\n /** Chapters available as the parent for a scene. */\n chapters: StructureParentOption[];\n /** Scenes available as the parent for a unit. */\n scenes: StructureParentOption[];\n /** Pre-filled parent (current selection), still changeable. */\n defaultChapterId: string | null;\n defaultSceneId: string | null;\n busy: boolean;\n error: string | null;\n onSave: (values: { chapterId: string | null; sceneId: string | null; title: string }) => void;\n onClose: () => void;\n}): JSX.Element {\n const { t } = ports.i18n;\n const { Modal } = ports.modal;\n const [title, setTitle] = useState('');\n const [chapterId, setChapterId] = useState(defaultChapterId ?? chapters[0]?.id ?? '');\n const [sceneId, setSceneId] = useState(defaultSceneId ?? scenes[0]?.id ?? '');\n const [formError, setFormError] = useState<string | null>(null);\n\n const titleKey: EditorI18nKey =\n kind === 'chapter' ? 'field_chapter_title' : kind === 'scene' ? 'field_scene_title' : 'field_name';\n\n const handleSave = () => {\n if (kind === 'scene' && !chapterId) {\n setFormError(t('structure_parent_required'));\n return;\n }\n if (kind === 'unit' && !sceneId) {\n setFormError(t('structure_parent_required'));\n return;\n }\n setFormError(null);\n onSave({ chapterId: kind === 'scene' ? chapterId : null, sceneId: kind === 'unit' ? sceneId : null, title: title.trim() });\n };\n\n return (\n <Modal title={t(kind === 'chapter' ? 'structure_add_chapter' : kind === 'scene' ? 'structure_add_scene' : 'structure_add_unit')} onClose={onClose} footer={\n <>\n <button type=\"button\" class=\"btn btn--outlined\" disabled={busy} onClick={onClose}>{t('dialog_cancel')}</button>\n <button type=\"button\" class=\"btn\" disabled={busy} onClick={handleSave}>\n {busy ? t('edit_saving') : t('edit_save')}\n </button>\n </>\n }>\n <div class=\"entity-form\">\n <div class=\"edit-field\">\n <label class=\"edit-field__label\" for=\"structure-id\">{t('entity_id')}</label>\n <input id=\"structure-id\" class=\"edit-field__input\" type=\"text\" value={id} readOnly />\n <span class=\"entity-form__hint\">{t('structure_id_hint')}</span>\n </div>\n {kind === 'scene' && (\n <div class=\"edit-field\">\n <label class=\"edit-field__label\" for=\"structure-chapter\">{t('field_chapter_title')}</label>\n <select\n id=\"structure-chapter\"\n class=\"edit-field__input\"\n value={chapterId}\n onChange={(e) => setChapterId((e.target as HTMLSelectElement).value)}\n >\n {chapters.map((c) => <option key={c.id} value={c.id}>{c.label}</option>)}\n </select>\n </div>\n )}\n {kind === 'unit' && (\n <div class=\"edit-field\">\n <label class=\"edit-field__label\" for=\"structure-scene\">{t('field_scene_title')}</label>\n <select\n id=\"structure-scene\"\n class=\"edit-field__input\"\n value={sceneId}\n onChange={(e) => setSceneId((e.target as HTMLSelectElement).value)}\n >\n {scenes.map((s) => <option key={s.id} value={s.id}>{s.label}</option>)}\n </select>\n </div>\n )}\n <div class=\"edit-field\">\n <label class=\"edit-field__label\" for=\"structure-title\">{t(titleKey)}</label>\n <input\n id=\"structure-title\"\n class=\"edit-field__input\"\n type=\"text\"\n value={title}\n onInput={(e) => setTitle((e.target as HTMLInputElement).value)}\n />\n </div>\n {kind === 'chapter' && <p class=\"entity-form__hint\" style=\"margin-top:4px;\">{t('chapter_auto_create_hint')}</p>}\n {kind === 'scene' && <p class=\"entity-form__hint\" style=\"margin-top:4px;\">{t('scene_auto_create_hint')}</p>}\n {(formError || error) && <div class=\"entity-form__error\">{(formError ?? error) as string}</div>}\n </div>\n </Modal>\n );\n}\n","// lib/waveform.tsx — WaveformView.kt port (06 §2, R10). Canvas render identical to\n// the Android custom View.draw(): vertical peak lines centered on midY, selection\n// fill + border, draggable start/end handles, white playhead with top triangle,\n// time labels (start/end/total in M:SS.d), and a \"No waveform data\" fallback.\n//\n// The Android paints are hardcoded colors (not theme attributes) — kept as-is;\n// text paints follow the active theme (Android attr waveformTimingText; web\n// waveformTextColor() — canvas can't resolve CSS var() reliably on all browsers):\n// waveform 0x8890CAF9 → rgba(144,202,249,.53)\n// selection 0x3390CAF9 → rgba(144,202,249,.20)\n// sel border 0x55FFB74D → rgba(255,183,77,.33)\n// handle #FFB74D\n// playhead #FFFFFF\n// text #F2E9DC (dark) / #C9A15A (light — active tab accent)\n// time text #F2E9DC (dark) / #C9A15A (light — active tab accent); 14px (14sp)\n//\n// Deviation (06 §15): Android touch coordinates are raw px (scaled by density);\n// here CSS px with the same 24px touchSlop (≈ the Android value at ~1x density).\n// The playback playhead is driven either by a `playbackPositionMs` prop or by a\n// `playbackSignal` (Signal<number>) — the signal path redraws without re-rendering\n// the page, matching Android's 50ms playback tick without per-frame React state.\n\nimport { useEffect, useRef, useCallback } from 'preact/hooks';\nimport { effect } from '@preact/signals';\nimport type { Signal } from '@preact/signals';\nimport type { WaveformPeak } from './models';\n\nexport interface WaveformSelection {\n startMs: number;\n endMs: number;\n}\n\nexport interface WaveformProps {\n peaks: WaveformPeak[];\n durationMs: number;\n selection: WaveformSelection | null;\n /** -1 = hidden. */\n playbackPositionMs?: number;\n /** Optional signal; when set, the playhead follows it per tick without re-render. */\n playbackSignal?: Signal<number>;\n onRangeChange?: (startMs: number, endMs: number) => void;\n onRangeChangeEnd?: (startMs: number, endMs: number) => void;\n}\n\nconst PAD = 12;\nconst TOUCH_SLOP = 24; // Android touchSlop = 24f\n\ntype DragTarget = 'none' | 'start' | 'end';\n\nfunction formatMs(ms: number): string {\n const totalSec = Math.floor(ms / 1000);\n const minutes = Math.floor(totalSec / 60);\n const seconds = totalSec % 60;\n const millis = Math.floor(ms % 1000 / 100);\n return `${minutes}:${String(seconds).padStart(2, '0')}.${millis}`;\n}\n\n/**\n * Waveform label color, per active theme. Canvas fillStyle can't resolve CSS\n * var() reliably on all mobile browsers — an invalid color is silently ignored\n * and the previous fillStyle is reused (e.g. the orange #FFB74D handle fill), so\n * we use explicit hexes instead of CSS custom properties.\n */\nfunction waveformTextColor(): string {\n // Light theme: gold accent #C9A15A — the active property-tab text color.\n return document.documentElement.dataset.theme === 'light' ? '#C9A15A' : '#F2E9DC';\n}\n\nexport function Waveform(props: WaveformProps) {\n const { peaks, durationMs, selection, onRangeChange, onRangeChangeEnd } = props;\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const wrapRef = useRef<HTMLDivElement | null>(null);\n const dragRef = useRef<{ target: DragTarget; startMs: number; endMs: number }>({\n target: 'none', startMs: 0, endMs: 0,\n });\n // Playhead read inside draw() — keep the latest value without re-running draw()'s\n // dep list: prop updates here, signal ticks update the canvas via the effect below.\n const playbackRef = useRef(props.playbackPositionMs ?? -1);\n playbackRef.current = props.playbackPositionMs ?? -1;\n\n const draw = useCallback(() => {\n const canvas = canvasRef.current;\n const wrap = wrapRef.current;\n if (!canvas || !wrap) return;\n const dpr = window.devicePixelRatio || 1;\n const cssW = wrap.clientWidth;\n const cssH = wrap.clientHeight;\n if (cssW <= 0 || cssH <= 0) return;\n if (canvas.width !== Math.round(cssW * dpr) || canvas.height !== Math.round(cssH * dpr)) {\n canvas.width = Math.round(cssW * dpr);\n canvas.height = Math.round(cssH * dpr);\n }\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, cssW, cssH);\n\n const w = cssW;\n const h = cssH;\n const drawLeft = PAD;\n const drawRight = w - PAD;\n const drawWidth = drawRight - drawLeft;\n const midY = h / 2;\n // Scale handle/dot geometry by view height (Android: fixed px on a 72dp view).\n const s = h / 72;\n const playbackPositionMs = playbackRef.current;\n\n const msToX = (ms: number) => (durationMs > 0 ? (ms / durationMs) * drawWidth : 0);\n\n // ── Waveform bars ──\n if (!peaks || peaks.length === 0) {\n ctx.fillStyle = waveformTextColor();\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.font = `500 12px system-ui, sans-serif`; // textNoData 24sp ≈ 9px @1x\n ctx.fillText('No waveform data', w / 2, midY);\n } else {\n ctx.strokeStyle = 'rgba(144,202,249,.53)';\n ctx.lineWidth = 1.5;\n ctx.lineCap = 'round';\n const barCount = peaks.length;\n const barWidth = drawWidth / barCount;\n ctx.beginPath();\n for (let i = 0; i < barCount; i++) {\n const peak = peaks[i];\n const x = drawLeft + i * barWidth + barWidth / 2;\n const posHeight = Math.min(Math.abs(peak.pos) * midY * 0.9, midY - 4);\n const negHeight = Math.min(Math.abs(peak.neg) * midY * 0.9, midY - 4);\n ctx.moveTo(x, midY - posHeight);\n ctx.lineTo(x, midY + negHeight);\n }\n ctx.stroke();\n }\n\n // ── Selection (fill + border) + handles ──\n let selLeft = 0;\n let selRight = 0;\n if (selection && durationMs > 0) {\n selLeft = drawLeft + msToX(selection.startMs);\n selRight = drawLeft + msToX(selection.endMs);\n if (selRight <= selLeft) selRight = selLeft + 4;\n ctx.fillStyle = 'rgba(144,202,249,.20)';\n ctx.fillRect(selLeft, 0, selRight - selLeft, h);\n ctx.strokeStyle = 'rgba(255,183,77,.33)';\n ctx.lineWidth = 1;\n ctx.strokeRect(selLeft, 0, selRight - selLeft, h);\n\n ctx.strokeStyle = '#FFB74D';\n ctx.lineWidth = 3;\n ctx.fillStyle = '#FFB74D';\n const handleRadius = 8 * s;\n for (const hx of [selLeft, selRight]) {\n ctx.beginPath();\n ctx.moveTo(hx, 0);\n ctx.lineTo(hx, h);\n ctx.stroke();\n ctx.beginPath();\n ctx.arc(hx, midY, handleRadius, 0, Math.PI * 2);\n ctx.fill();\n ctx.beginPath();\n ctx.arc(hx, midY - 16 * s, 3 * s, 0, Math.PI * 2);\n ctx.fill();\n ctx.beginPath();\n ctx.arc(hx, midY + 16 * s, 3 * s, 0, Math.PI * 2);\n ctx.fill();\n }\n }\n\n // ── Playhead (WaveformView.drawPlayhead: 2.5f white line + 8px triangle) ──\n if (playbackPositionMs >= 0 && durationMs > 0) {\n const px = drawLeft + msToX(playbackPositionMs);\n ctx.strokeStyle = '#FFFFFF';\n ctx.lineWidth = 2.5;\n ctx.beginPath();\n ctx.moveTo(px, 0);\n ctx.lineTo(px, h);\n ctx.stroke();\n const tri = 8 * s;\n ctx.beginPath();\n ctx.moveTo(px, 0);\n ctx.lineTo(px - tri, -tri);\n ctx.lineTo(px + tri, -tri);\n ctx.closePath();\n ctx.stroke(); // outline, like Android's three drawLine calls (clipped at top)\n }\n\n // ── Time labels ──\n if (selection && durationMs > 0) {\n const startLabel = formatMs(selection.startMs);\n const endLabel = formatMs(selection.endMs);\n const totalLabel = formatMs(durationMs);\n // Waveform label color: near-white in dark, muted warm brown in light —\n // readable but not loud on the bars (see waveformTextColor()).\n ctx.fillStyle = waveformTextColor();\n ctx.font = '14px system-ui, sans-serif'; // 14sp — edit-field input text (table under tabs)\n ctx.textBaseline = 'alphabetic';\n // Start label: outside the range, left of its left border (right-aligned),\n // clamped so it never falls off the left edge at full range.\n ctx.textAlign = 'right';\n const startX = Math.max(selLeft - 6, ctx.measureText(startLabel).width);\n ctx.fillText(startLabel, startX, h - 7);\n // End label: outside the range, right of its right border (left-aligned),\n // clamped so it never falls off the right edge at full range.\n ctx.textAlign = 'left';\n const endX = Math.max(\n Math.min(selRight + 6, w - ctx.measureText(endLabel).width),\n startX,\n );\n ctx.fillText(endLabel, endX, h - 7);\n ctx.textAlign = 'left';\n ctx.fillText(totalLabel, drawLeft + 2, 14);\n }\n }, [peaks, durationMs, selection]);\n\n // Redraw on prop changes (invalidates()).\n useEffect(() => { draw(); }, [draw]);\n\n // Redraw when the container resizes. Collapsing/expanding the desktop side\n // wings (or resizing the window / rotating the device) changes the waveform's\n // width without changing any prop — without a redraw the old canvas bitmap is\n // CSS-stretched to the new box and the waveform smears, losing its\n // proportions. The height stays fixed by CSS (4.5rem/72dp), so the redraw\n // re-renders the peaks at the new width: more horizontal detail, same shape.\n // ResizeObserver catches any layout change; window resize is the fallback\n // where the API is unavailable (same pattern as the edit tab chevrons).\n useEffect(() => {\n const wrap = wrapRef.current;\n if (!wrap) return;\n if (typeof ResizeObserver === 'undefined') {\n const onResize = () => draw();\n window.addEventListener('resize', onResize);\n return () => window.removeEventListener('resize', onResize);\n }\n const ro = new ResizeObserver(() => draw());\n ro.observe(wrap);\n return () => ro.disconnect();\n }, [draw]);\n\n // Redraw per signal tick (playback rAF loop) without re-rendering the page.\n // The signal value must be written into playbackRef (which draw() reads) —\n // signal ticks alone never re-render, so without this the playhead would\n // stay at -1 and never appear (WaveformView.setPlaybackPosition equivalent).\n const playbackSignal = props.playbackSignal;\n useEffect(() => {\n if (!playbackSignal) return;\n return effect(() => {\n playbackRef.current = playbackSignal.value;\n draw();\n });\n }, [draw, playbackSignal]);\n\n // ── Pointer drag (onTouchEvent equivalent) ──\n const onPointerDown = (e: PointerEvent) => {\n if (durationMs <= 0) return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n const rect = canvas.getBoundingClientRect();\n const drawWidth = rect.width - PAD * 2;\n if (drawWidth <= 0) return;\n const drawLeft = PAD;\n const selLeft = drawLeft + (selection ? (selection.startMs / durationMs) * drawWidth : 0);\n const selRight = drawLeft + (selection ? (selection.endMs / durationMs) * drawWidth : 0);\n const x = e.clientX - rect.left;\n const distToStart = Math.abs(x - selLeft);\n const distToEnd = Math.abs(x - selRight);\n let target: DragTarget = 'none';\n if (distToStart < TOUCH_SLOP * 2) target = 'start';\n else if (distToEnd < TOUCH_SLOP * 2) target = 'end';\n if (target === 'none') return;\n dragRef.current = {\n target,\n startMs: selection?.startMs ?? 0,\n endMs: selection?.endMs ?? 0,\n };\n try { canvas.setPointerCapture(e.pointerId); } catch { /* ignore */ }\n e.preventDefault();\n };\n\n const onPointerMove = (e: PointerEvent) => {\n const drag = dragRef.current;\n if (drag.target === 'none') return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n const rect = canvas.getBoundingClientRect();\n const drawLeft = PAD;\n const drawWidth = rect.width - PAD * 2;\n if (drawWidth <= 0) return;\n const x = Math.min(Math.max(e.clientX - rect.left - drawLeft, 0), drawWidth);\n const ms = Math.round((x / drawWidth) * durationMs);\n let startMs = drag.startMs;\n let endMs = drag.endMs;\n if (drag.target === 'start') {\n startMs = Math.min(ms, endMs - 50);\n } else {\n endMs = Math.max(ms, startMs + 50);\n endMs = Math.min(endMs, durationMs);\n }\n if (startMs !== drag.startMs || endMs !== drag.endMs) {\n drag.startMs = startMs;\n drag.endMs = endMs;\n onRangeChange?.(startMs, endMs);\n }\n };\n\n const onPointerUp = (e: PointerEvent) => {\n const drag = dragRef.current;\n if (drag.target === 'none') return;\n drag.target = 'none';\n const canvas = canvasRef.current;\n try { canvas?.releasePointerCapture(e.pointerId); } catch { /* already released */ }\n onRangeChangeEnd?.(drag.startMs, drag.endMs);\n };\n\n return (\n <div ref={wrapRef} class=\"waveform\" style={{ width: '100%', height: '100%' }}>\n <canvas\n ref={canvasRef}\n style={{ width: '100%', height: '100%', display: 'block', touchAction: 'none' }}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerCancel={onPointerUp}\n />\n </div>\n );\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@animastor/web-editor",
3
+ "version": "0.1.0",
4
+ "description": "Schema-driven editor UI primitives for Animastor web frontend — entity add/delete dialogs, structure add dialog, canvas waveform renderer, and preview ID generator. Host capabilities (i18n, icons, modal) arrive via the injected EditorPorts contract; the package never imports host stores, api/client, or app-level UI directly.",
5
+ "keywords": [
6
+ "animastor",
7
+ "editor",
8
+ "entity-editor",
9
+ "waveform",
10
+ "preact",
11
+ "web-ui",
12
+ "frontend",
13
+ "ui-component",
14
+ "ports-and-adapters"
15
+ ],
16
+ "type": "module",
17
+ "main": "dist/index.js",
18
+ "module": "dist/index.js",
19
+ "types": "dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "README.md",
31
+ "LICENSE"
32
+ ],
33
+ "sideEffects": false,
34
+ "scripts": {
35
+ "build": "tsup",
36
+ "typecheck": "tsc --noEmit",
37
+ "test": "vitest run",
38
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build"
39
+ },
40
+ "peerDependencies": {
41
+ "@preact/signals": ">=1.0.0",
42
+ "preact": ">=10.5.0"
43
+ },
44
+ "devDependencies": {
45
+ "@preact/signals": "^1.3.0",
46
+ "preact": "^10.24.0",
47
+ "tsup": "^8.3.5",
48
+ "typescript": "^5.6.0",
49
+ "vitest": "4.1.11"
50
+ },
51
+ "license": "MIT",
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "git+https://github.com/Animastor/animastor.git",
58
+ "directory": "packages/animastor-web-editor"
59
+ },
60
+ "homepage": "https://github.com/Animastor/animastor/tree/main/packages/animastor-web-editor#readme",
61
+ "bugs": {
62
+ "url": "https://github.com/Animastor/animastor/issues"
63
+ }
64
+ }