@pixerate/editor-react 0.40.0 → 0.42.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/dist/animation-editor/index.cjs +192 -0
- package/dist/animation-editor/index.cjs.map +1 -0
- package/dist/animation-editor/index.d.cts +61 -0
- package/dist/animation-editor/index.d.ts +61 -0
- package/dist/animation-editor/index.js +162 -0
- package/dist/animation-editor/index.js.map +1 -0
- package/dist/canvas/index.cjs +536 -313
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +155 -6
- package/dist/canvas/index.d.ts +155 -6
- package/dist/canvas/index.js +221 -0
- package/dist/canvas/index.js.map +1 -1
- package/dist/index.cjs +439 -412
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -2
- package/dist/index.d.ts +16 -2
- package/dist/index.js +52 -26
- package/dist/index.js.map +1 -1
- package/package.json +11 -3
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/animation-editor/index.ts
|
|
21
|
+
var animation_editor_exports = {};
|
|
22
|
+
__export(animation_editor_exports, {
|
|
23
|
+
AnimationEditor: () => AnimationEditor,
|
|
24
|
+
AnimationStage: () => AnimationStage,
|
|
25
|
+
ShotTimeline: () => ShotTimeline,
|
|
26
|
+
useAnimationEditor: () => useAnimationEditor,
|
|
27
|
+
useAnimationEditorController: () => useAnimationEditorController,
|
|
28
|
+
useAnimationEditorState: () => useAnimationEditorState,
|
|
29
|
+
useAnimationEditorTools: () => useAnimationEditorTools
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(animation_editor_exports);
|
|
32
|
+
|
|
33
|
+
// src/animation-editor/useAnimationEditor.ts
|
|
34
|
+
var import_react = require("react");
|
|
35
|
+
var import_animation_editor = require("@pixerate/editor/animation-editor");
|
|
36
|
+
function useAnimationEditorController(options = {}) {
|
|
37
|
+
const ref = (0, import_react.useRef)(null);
|
|
38
|
+
if (!ref.current) ref.current = new import_animation_editor.AnimationEditorController(options);
|
|
39
|
+
const controller = ref.current;
|
|
40
|
+
(0, import_react.useEffect)(() => () => controller.destroy(), [controller]);
|
|
41
|
+
return controller;
|
|
42
|
+
}
|
|
43
|
+
function useAnimationEditorState(controller) {
|
|
44
|
+
return (0, import_react.useSyncExternalStore)(controller.subscribe, controller.getState, controller.getState);
|
|
45
|
+
}
|
|
46
|
+
function useAnimationEditor(controllerOrOptions = {}) {
|
|
47
|
+
const own = (0, import_react.useRef)(null);
|
|
48
|
+
let controller;
|
|
49
|
+
if (controllerOrOptions instanceof import_animation_editor.AnimationEditorController) controller = controllerOrOptions;
|
|
50
|
+
else {
|
|
51
|
+
if (!own.current) own.current = new import_animation_editor.AnimationEditorController(controllerOrOptions);
|
|
52
|
+
controller = own.current;
|
|
53
|
+
}
|
|
54
|
+
(0, import_react.useEffect)(() => () => own.current?.destroy(), []);
|
|
55
|
+
const state = useAnimationEditorState(controller);
|
|
56
|
+
const actions = (0, import_react.useMemo)(
|
|
57
|
+
() => ({
|
|
58
|
+
play: () => controller.play(),
|
|
59
|
+
pause: () => controller.pause(),
|
|
60
|
+
toggle: () => controller.toggle(),
|
|
61
|
+
seek: (time) => controller.seek(time),
|
|
62
|
+
undo: () => void controller.undo(),
|
|
63
|
+
redo: () => void controller.redo(),
|
|
64
|
+
toSvg: () => controller.toSvg()
|
|
65
|
+
}),
|
|
66
|
+
[controller]
|
|
67
|
+
);
|
|
68
|
+
return { controller, state, ...actions };
|
|
69
|
+
}
|
|
70
|
+
function useAnimationEditorTools(registry, controller, options = {}) {
|
|
71
|
+
const { id, namespace, readOnly, rasterize } = options;
|
|
72
|
+
(0, import_react.useEffect)(() => {
|
|
73
|
+
if (!registry) return;
|
|
74
|
+
return registry.register((0, import_animation_editor.createAnimationEditorToolProvider)(controller, { id, namespace, readOnly, rasterize }));
|
|
75
|
+
}, [registry, controller, id, namespace, readOnly, rasterize]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/animation-editor/AnimationStage.tsx
|
|
79
|
+
var import_react2 = require("react");
|
|
80
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
81
|
+
function AnimationStage({ controller, values, className = "" }) {
|
|
82
|
+
const state = useAnimationEditorState(controller);
|
|
83
|
+
const { scene, time } = state;
|
|
84
|
+
const svg = (0, import_react2.useMemo)(
|
|
85
|
+
() => controller.toSvg(time, { pretty: false, idPrefix: "ae-", values }).replace(/<svg ([^>]*?)width="[^"]*" height="[^"]*"/, '<svg $1width="100%" height="100%"'),
|
|
86
|
+
[controller, scene, time, values]
|
|
87
|
+
);
|
|
88
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
89
|
+
"div",
|
|
90
|
+
{
|
|
91
|
+
className: `relative overflow-hidden ${className}`,
|
|
92
|
+
style: { aspectRatio: `${scene.artwork.width} / ${scene.artwork.height}` },
|
|
93
|
+
"data-animation-stage": "",
|
|
94
|
+
role: "img",
|
|
95
|
+
"aria-label": `${scene.name} at ${(time / 1e3).toFixed(2)} s`,
|
|
96
|
+
dangerouslySetInnerHTML: { __html: svg }
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/animation-editor/ShotTimeline.tsx
|
|
102
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
103
|
+
var seconds = (ms) => `${(ms / 1e3).toFixed(1)} s`;
|
|
104
|
+
function worst(issues) {
|
|
105
|
+
if (issues.some((i) => i.severity === "error")) return "error";
|
|
106
|
+
if (issues.some((i) => i.severity === "warning")) return "warning";
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
function ShotTimeline({ controller, className = "" }) {
|
|
110
|
+
const state = useAnimationEditorState(controller);
|
|
111
|
+
const { duration, shots, time, playing, selection, issues } = state;
|
|
112
|
+
const span = Math.max(1, duration);
|
|
113
|
+
const onRuler = (event) => {
|
|
114
|
+
const box = event.currentTarget.getBoundingClientRect();
|
|
115
|
+
controller.seek((event.clientX - box.left) / Math.max(1, box.width) * span);
|
|
116
|
+
};
|
|
117
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `flex flex-col gap-1 ${className}`, "data-shot-timeline": "", children: [
|
|
118
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2 text-xs", children: [
|
|
119
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "rounded border border-border px-2 py-0.5", onClick: () => controller.toggle(), "aria-label": playing ? "Pause" : "Play", disabled: !shots.length, children: playing ? "Pause" : "Play" }),
|
|
120
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "tabular-nums text-muted-foreground", children: [
|
|
121
|
+
seconds(time),
|
|
122
|
+
" / ",
|
|
123
|
+
seconds(duration)
|
|
124
|
+
] })
|
|
125
|
+
] }),
|
|
126
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
127
|
+
"div",
|
|
128
|
+
{
|
|
129
|
+
className: "relative h-3 cursor-pointer rounded bg-muted",
|
|
130
|
+
onPointerDown: onRuler,
|
|
131
|
+
role: "slider",
|
|
132
|
+
"aria-label": "Playhead",
|
|
133
|
+
"aria-valuemin": 0,
|
|
134
|
+
"aria-valuemax": duration,
|
|
135
|
+
"aria-valuenow": Math.round(time),
|
|
136
|
+
tabIndex: 0,
|
|
137
|
+
onKeyDown: (e) => {
|
|
138
|
+
if (e.key === "ArrowRight") controller.seek(time + 100);
|
|
139
|
+
if (e.key === "ArrowLeft") controller.seek(time - 100);
|
|
140
|
+
},
|
|
141
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute inset-y-0 w-0.5 bg-rose-500", style: { left: `${time / span * 100}%` } })
|
|
142
|
+
}
|
|
143
|
+
),
|
|
144
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex w-full gap-px", role: "group", "aria-label": "Shots", children: shots.map((shot) => {
|
|
145
|
+
const mark = worst(issues.filter((i) => i.shot === shot.id));
|
|
146
|
+
const selected = selection?.shot === shot.id;
|
|
147
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
148
|
+
"button",
|
|
149
|
+
{
|
|
150
|
+
type: "button",
|
|
151
|
+
className: `min-w-0 truncate rounded border px-1 py-1 text-left text-[11px] ${selected ? "border-rose-500 bg-rose-500/10" : "border-border"} ${state.currentShot === shot.id ? "font-semibold" : ""}`,
|
|
152
|
+
style: { flexGrow: Math.max(1, shot.end - shot.start), flexBasis: 0 },
|
|
153
|
+
title: `${shot.label ?? shot.id} \xB7 ${seconds(shot.end - shot.start)}`,
|
|
154
|
+
"data-shot": shot.id,
|
|
155
|
+
"data-issue": mark ?? void 0,
|
|
156
|
+
onClick: () => {
|
|
157
|
+
controller.select({ shot: shot.id });
|
|
158
|
+
controller.seekShot(shot.id);
|
|
159
|
+
},
|
|
160
|
+
children: [
|
|
161
|
+
mark && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: mark === "error" ? "text-red-500" : "text-amber-500", children: "\u25CF " }),
|
|
162
|
+
shot.label ?? shot.id
|
|
163
|
+
]
|
|
164
|
+
},
|
|
165
|
+
shot.id
|
|
166
|
+
);
|
|
167
|
+
}) })
|
|
168
|
+
] });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/animation-editor/AnimationEditor.tsx
|
|
172
|
+
var import_animation_editor2 = require("@pixerate/editor/animation-editor");
|
|
173
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
174
|
+
function AnimationEditor({ controller, values, showIssues = true, className = "" }) {
|
|
175
|
+
const { issues } = useAnimationEditorState(controller);
|
|
176
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `flex flex-col gap-2 ${className}`, "data-animation-editor": "", children: [
|
|
177
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AnimationStage, { controller, values, className: "rounded-md border border-border bg-muted/40" }),
|
|
178
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ShotTimeline, { controller }),
|
|
179
|
+
showIssues && issues.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { className: "space-y-0.5 text-xs", "aria-label": "Animation issues", children: issues.map((issue, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("li", { className: issue.severity === "error" ? "text-red-500" : issue.severity === "warning" ? "text-amber-500" : "text-muted-foreground", children: (0, import_animation_editor2.motionIssueLine)(issue) }, i)) })
|
|
180
|
+
] });
|
|
181
|
+
}
|
|
182
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
183
|
+
0 && (module.exports = {
|
|
184
|
+
AnimationEditor,
|
|
185
|
+
AnimationStage,
|
|
186
|
+
ShotTimeline,
|
|
187
|
+
useAnimationEditor,
|
|
188
|
+
useAnimationEditorController,
|
|
189
|
+
useAnimationEditorState,
|
|
190
|
+
useAnimationEditorTools
|
|
191
|
+
});
|
|
192
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/animation-editor/index.ts","../../src/animation-editor/useAnimationEditor.ts","../../src/animation-editor/AnimationStage.tsx","../../src/animation-editor/ShotTimeline.tsx","../../src/animation-editor/AnimationEditor.tsx"],"sourcesContent":["export * from './useAnimationEditor';\nexport * from './AnimationStage';\nexport * from './ShotTimeline';\nexport * from './AnimationEditor';\n","import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport {\n AnimationEditorController,\n createAnimationEditorToolProvider,\n type AnimationEditorOptions,\n type AnimationEditorState,\n type AnimationEditorToolProviderOptions,\n} from '@pixerate/editor/animation-editor';\nimport type { ToolRegistry } from '@pixerate/editor/tools';\n\n/** An `AnimationEditorController` created once for the component's lifetime (destroyed on unmount). */\nexport function useAnimationEditorController(options: AnimationEditorOptions = {}): AnimationEditorController {\n const ref = useRef<AnimationEditorController | null>(null);\n if (!ref.current) ref.current = new AnimationEditorController(options);\n const controller = ref.current;\n useEffect(() => () => controller.destroy(), [controller]);\n return controller;\n}\n\n/** The controller's state (re-renders on every change, including each played frame). */\nexport function useAnimationEditorState(controller: AnimationEditorController): AnimationEditorState {\n return useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);\n}\n\nexport interface UseAnimationEditorResult {\n controller: AnimationEditorController;\n state: AnimationEditorState;\n play: () => void;\n pause: () => void;\n toggle: () => void;\n seek: (time: number) => void;\n undo: () => void;\n redo: () => void;\n /** The frame at the playhead as SVG. */\n toSvg: () => string;\n}\n\n/**\n * Subscribes to an animation editor controller and exposes its state with bound actions.\n * Pass options instead of a controller to create one for the component's lifetime.\n */\nexport function useAnimationEditor(controllerOrOptions: AnimationEditorController | AnimationEditorOptions = {}): UseAnimationEditorResult {\n const own = useRef<AnimationEditorController | null>(null);\n let controller: AnimationEditorController;\n if (controllerOrOptions instanceof AnimationEditorController) controller = controllerOrOptions;\n else {\n if (!own.current) own.current = new AnimationEditorController(controllerOrOptions);\n controller = own.current;\n }\n useEffect(() => () => own.current?.destroy(), []);\n const state = useAnimationEditorState(controller);\n const actions = useMemo(\n () => ({\n play: () => controller.play(),\n pause: () => controller.pause(),\n toggle: () => controller.toggle(),\n seek: (time: number) => controller.seek(time),\n undo: () => void controller.undo(),\n redo: () => void controller.redo(),\n toSvg: () => controller.toSvg(),\n }),\n [controller],\n );\n return { controller, state, ...actions };\n}\n\n/**\n * Registers the animation editor's agent tools (`get_animation`, `add_commands`,\n * `export_flair_effect`, …) in `registry` while mounted.\n */\nexport function useAnimationEditorTools(\n registry: ToolRegistry | null | undefined,\n controller: AnimationEditorController,\n options: AnimationEditorToolProviderOptions = {},\n): void {\n const { id, namespace, readOnly, rasterize } = options;\n useEffect(() => {\n if (!registry) return;\n return registry.register(createAnimationEditorToolProvider(controller, { id, namespace, readOnly, rasterize }));\n }, [registry, controller, id, namespace, readOnly, rasterize]);\n}\n","import React, { useMemo } from 'react';\nimport type { AnimationEditorController, MotionFrameOptions } from '@pixerate/editor/animation-editor';\nimport { useAnimationEditorState } from './useAnimationEditor';\n\nexport interface AnimationStageProps {\n controller: AnimationEditorController;\n /** Control values to preview (`{ accent: \"#0ea5e9\" }`). */\n values?: MotionFrameOptions['values'];\n className?: string;\n}\n\n/** The animation's frame at the playhead, drawn as inline SVG scaled to the stage's width. */\nexport function AnimationStage({ controller, values, className = '' }: AnimationStageProps) {\n const state = useAnimationEditorState(controller);\n const { scene, time } = state;\n const svg = useMemo(\n () => controller.toSvg(time, { pretty: false, idPrefix: 'ae-', values }).replace(/<svg ([^>]*?)width=\"[^\"]*\" height=\"[^\"]*\"/, '<svg $1width=\"100%\" height=\"100%\"'),\n [controller, scene, time, values],\n );\n return (\n <div\n className={`relative overflow-hidden ${className}`}\n style={{ aspectRatio: `${scene.artwork.width} / ${scene.artwork.height}` }}\n data-animation-stage=\"\"\n role=\"img\"\n aria-label={`${scene.name} at ${(time / 1000).toFixed(2)} s`}\n dangerouslySetInnerHTML={{ __html: svg }}\n />\n );\n}\n","import React from 'react';\nimport type { AnimationEditorController, MotionIssue } from '@pixerate/editor/animation-editor';\nimport { useAnimationEditorState } from './useAnimationEditor';\n\nexport interface ShotTimelineProps {\n controller: AnimationEditorController;\n className?: string;\n}\n\nconst seconds = (ms: number) => `${(ms / 1000).toFixed(1)} s`;\n\nfunction worst(issues: readonly MotionIssue[]): 'error' | 'warning' | null {\n if (issues.some((i) => i.severity === 'error')) return 'error';\n if (issues.some((i) => i.severity === 'warning')) return 'warning';\n return null;\n}\n\n/**\n * Play / pause, the playhead and one block per shot (as wide as it is long): click a shot to\n * select it and jump to its start, click the ruler to seek. Shots with errors or warnings are marked.\n */\nexport function ShotTimeline({ controller, className = '' }: ShotTimelineProps) {\n const state = useAnimationEditorState(controller);\n const { duration, shots, time, playing, selection, issues } = state;\n const span = Math.max(1, duration);\n const onRuler = (event: React.PointerEvent<HTMLDivElement>) => {\n const box = event.currentTarget.getBoundingClientRect();\n controller.seek(((event.clientX - box.left) / Math.max(1, box.width)) * span);\n };\n return (\n <div className={`flex flex-col gap-1 ${className}`} data-shot-timeline=\"\">\n <div className=\"flex items-center gap-2 text-xs\">\n <button type=\"button\" className=\"rounded border border-border px-2 py-0.5\" onClick={() => controller.toggle()} aria-label={playing ? 'Pause' : 'Play'} disabled={!shots.length}>\n {playing ? 'Pause' : 'Play'}\n </button>\n <span className=\"tabular-nums text-muted-foreground\">\n {seconds(time)} / {seconds(duration)}\n </span>\n </div>\n <div className=\"relative h-3 cursor-pointer rounded bg-muted\" onPointerDown={onRuler} role=\"slider\" aria-label=\"Playhead\" aria-valuemin={0} aria-valuemax={duration} aria-valuenow={Math.round(time)} tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'ArrowRight') controller.seek(time + 100);\n if (e.key === 'ArrowLeft') controller.seek(time - 100);\n }}>\n <div className=\"absolute inset-y-0 w-0.5 bg-rose-500\" style={{ left: `${(time / span) * 100}%` }} />\n </div>\n <div className=\"flex w-full gap-px\" role=\"group\" aria-label=\"Shots\">\n {shots.map((shot) => {\n const mark = worst(issues.filter((i) => i.shot === shot.id));\n const selected = selection?.shot === shot.id;\n return (\n <button\n key={shot.id}\n type=\"button\"\n className={`min-w-0 truncate rounded border px-1 py-1 text-left text-[11px] ${selected ? 'border-rose-500 bg-rose-500/10' : 'border-border'} ${state.currentShot === shot.id ? 'font-semibold' : ''}`}\n style={{ flexGrow: Math.max(1, shot.end - shot.start), flexBasis: 0 }}\n title={`${shot.label ?? shot.id} · ${seconds(shot.end - shot.start)}`}\n data-shot={shot.id}\n data-issue={mark ?? undefined}\n onClick={() => {\n controller.select({ shot: shot.id });\n controller.seekShot(shot.id);\n }}\n >\n {mark && <span className={mark === 'error' ? 'text-red-500' : 'text-amber-500'}>● </span>}\n {shot.label ?? shot.id}\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n","import React from 'react';\nimport { motionIssueLine, type AnimationEditorController, type MotionFrameOptions } from '@pixerate/editor/animation-editor';\nimport { AnimationStage } from './AnimationStage';\nimport { ShotTimeline } from './ShotTimeline';\nimport { useAnimationEditorState } from './useAnimationEditor';\n\nexport interface AnimationEditorProps {\n controller: AnimationEditorController;\n values?: MotionFrameOptions['values'];\n /** List the scene's issues under the timeline (default true). */\n showIssues?: boolean;\n className?: string;\n}\n\n/** Stage, shot timeline and issue list for an `AnimationEditorController`. */\nexport function AnimationEditor({ controller, values, showIssues = true, className = '' }: AnimationEditorProps) {\n const { issues } = useAnimationEditorState(controller);\n return (\n <div className={`flex flex-col gap-2 ${className}`} data-animation-editor=\"\">\n <AnimationStage controller={controller} values={values} className=\"rounded-md border border-border bg-muted/40\" />\n <ShotTimeline controller={controller} />\n {showIssues && issues.length > 0 && (\n <ul className=\"space-y-0.5 text-xs\" aria-label=\"Animation issues\">\n {issues.map((issue, i) => (\n <li key={i} className={issue.severity === 'error' ? 'text-red-500' : issue.severity === 'warning' ? 'text-amber-500' : 'text-muted-foreground'}>\n {motionIssueLine(issue)}\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime","import_animation_editor","import_jsx_runtime"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { AnimationEditorController, AnimationEditorState, AnimationEditorOptions, AnimationEditorToolProviderOptions, MotionFrameOptions } from '@pixerate/editor/animation-editor';
|
|
2
|
+
import { ToolRegistry } from '@pixerate/editor/tools';
|
|
3
|
+
import React__default from 'react';
|
|
4
|
+
|
|
5
|
+
/** An `AnimationEditorController` created once for the component's lifetime (destroyed on unmount). */
|
|
6
|
+
declare function useAnimationEditorController(options?: AnimationEditorOptions): AnimationEditorController;
|
|
7
|
+
/** The controller's state (re-renders on every change, including each played frame). */
|
|
8
|
+
declare function useAnimationEditorState(controller: AnimationEditorController): AnimationEditorState;
|
|
9
|
+
interface UseAnimationEditorResult {
|
|
10
|
+
controller: AnimationEditorController;
|
|
11
|
+
state: AnimationEditorState;
|
|
12
|
+
play: () => void;
|
|
13
|
+
pause: () => void;
|
|
14
|
+
toggle: () => void;
|
|
15
|
+
seek: (time: number) => void;
|
|
16
|
+
undo: () => void;
|
|
17
|
+
redo: () => void;
|
|
18
|
+
/** The frame at the playhead as SVG. */
|
|
19
|
+
toSvg: () => string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Subscribes to an animation editor controller and exposes its state with bound actions.
|
|
23
|
+
* Pass options instead of a controller to create one for the component's lifetime.
|
|
24
|
+
*/
|
|
25
|
+
declare function useAnimationEditor(controllerOrOptions?: AnimationEditorController | AnimationEditorOptions): UseAnimationEditorResult;
|
|
26
|
+
/**
|
|
27
|
+
* Registers the animation editor's agent tools (`get_animation`, `add_commands`,
|
|
28
|
+
* `export_flair_effect`, …) in `registry` while mounted.
|
|
29
|
+
*/
|
|
30
|
+
declare function useAnimationEditorTools(registry: ToolRegistry | null | undefined, controller: AnimationEditorController, options?: AnimationEditorToolProviderOptions): void;
|
|
31
|
+
|
|
32
|
+
interface AnimationStageProps {
|
|
33
|
+
controller: AnimationEditorController;
|
|
34
|
+
/** Control values to preview (`{ accent: "#0ea5e9" }`). */
|
|
35
|
+
values?: MotionFrameOptions['values'];
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
/** The animation's frame at the playhead, drawn as inline SVG scaled to the stage's width. */
|
|
39
|
+
declare function AnimationStage({ controller, values, className }: AnimationStageProps): React__default.JSX.Element;
|
|
40
|
+
|
|
41
|
+
interface ShotTimelineProps {
|
|
42
|
+
controller: AnimationEditorController;
|
|
43
|
+
className?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Play / pause, the playhead and one block per shot (as wide as it is long): click a shot to
|
|
47
|
+
* select it and jump to its start, click the ruler to seek. Shots with errors or warnings are marked.
|
|
48
|
+
*/
|
|
49
|
+
declare function ShotTimeline({ controller, className }: ShotTimelineProps): React__default.JSX.Element;
|
|
50
|
+
|
|
51
|
+
interface AnimationEditorProps {
|
|
52
|
+
controller: AnimationEditorController;
|
|
53
|
+
values?: MotionFrameOptions['values'];
|
|
54
|
+
/** List the scene's issues under the timeline (default true). */
|
|
55
|
+
showIssues?: boolean;
|
|
56
|
+
className?: string;
|
|
57
|
+
}
|
|
58
|
+
/** Stage, shot timeline and issue list for an `AnimationEditorController`. */
|
|
59
|
+
declare function AnimationEditor({ controller, values, showIssues, className }: AnimationEditorProps): React__default.JSX.Element;
|
|
60
|
+
|
|
61
|
+
export { AnimationEditor, type AnimationEditorProps, AnimationStage, type AnimationStageProps, ShotTimeline, type ShotTimelineProps, type UseAnimationEditorResult, useAnimationEditor, useAnimationEditorController, useAnimationEditorState, useAnimationEditorTools };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { AnimationEditorController, AnimationEditorState, AnimationEditorOptions, AnimationEditorToolProviderOptions, MotionFrameOptions } from '@pixerate/editor/animation-editor';
|
|
2
|
+
import { ToolRegistry } from '@pixerate/editor/tools';
|
|
3
|
+
import React__default from 'react';
|
|
4
|
+
|
|
5
|
+
/** An `AnimationEditorController` created once for the component's lifetime (destroyed on unmount). */
|
|
6
|
+
declare function useAnimationEditorController(options?: AnimationEditorOptions): AnimationEditorController;
|
|
7
|
+
/** The controller's state (re-renders on every change, including each played frame). */
|
|
8
|
+
declare function useAnimationEditorState(controller: AnimationEditorController): AnimationEditorState;
|
|
9
|
+
interface UseAnimationEditorResult {
|
|
10
|
+
controller: AnimationEditorController;
|
|
11
|
+
state: AnimationEditorState;
|
|
12
|
+
play: () => void;
|
|
13
|
+
pause: () => void;
|
|
14
|
+
toggle: () => void;
|
|
15
|
+
seek: (time: number) => void;
|
|
16
|
+
undo: () => void;
|
|
17
|
+
redo: () => void;
|
|
18
|
+
/** The frame at the playhead as SVG. */
|
|
19
|
+
toSvg: () => string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Subscribes to an animation editor controller and exposes its state with bound actions.
|
|
23
|
+
* Pass options instead of a controller to create one for the component's lifetime.
|
|
24
|
+
*/
|
|
25
|
+
declare function useAnimationEditor(controllerOrOptions?: AnimationEditorController | AnimationEditorOptions): UseAnimationEditorResult;
|
|
26
|
+
/**
|
|
27
|
+
* Registers the animation editor's agent tools (`get_animation`, `add_commands`,
|
|
28
|
+
* `export_flair_effect`, …) in `registry` while mounted.
|
|
29
|
+
*/
|
|
30
|
+
declare function useAnimationEditorTools(registry: ToolRegistry | null | undefined, controller: AnimationEditorController, options?: AnimationEditorToolProviderOptions): void;
|
|
31
|
+
|
|
32
|
+
interface AnimationStageProps {
|
|
33
|
+
controller: AnimationEditorController;
|
|
34
|
+
/** Control values to preview (`{ accent: "#0ea5e9" }`). */
|
|
35
|
+
values?: MotionFrameOptions['values'];
|
|
36
|
+
className?: string;
|
|
37
|
+
}
|
|
38
|
+
/** The animation's frame at the playhead, drawn as inline SVG scaled to the stage's width. */
|
|
39
|
+
declare function AnimationStage({ controller, values, className }: AnimationStageProps): React__default.JSX.Element;
|
|
40
|
+
|
|
41
|
+
interface ShotTimelineProps {
|
|
42
|
+
controller: AnimationEditorController;
|
|
43
|
+
className?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Play / pause, the playhead and one block per shot (as wide as it is long): click a shot to
|
|
47
|
+
* select it and jump to its start, click the ruler to seek. Shots with errors or warnings are marked.
|
|
48
|
+
*/
|
|
49
|
+
declare function ShotTimeline({ controller, className }: ShotTimelineProps): React__default.JSX.Element;
|
|
50
|
+
|
|
51
|
+
interface AnimationEditorProps {
|
|
52
|
+
controller: AnimationEditorController;
|
|
53
|
+
values?: MotionFrameOptions['values'];
|
|
54
|
+
/** List the scene's issues under the timeline (default true). */
|
|
55
|
+
showIssues?: boolean;
|
|
56
|
+
className?: string;
|
|
57
|
+
}
|
|
58
|
+
/** Stage, shot timeline and issue list for an `AnimationEditorController`. */
|
|
59
|
+
declare function AnimationEditor({ controller, values, showIssues, className }: AnimationEditorProps): React__default.JSX.Element;
|
|
60
|
+
|
|
61
|
+
export { AnimationEditor, type AnimationEditorProps, AnimationStage, type AnimationStageProps, ShotTimeline, type ShotTimelineProps, type UseAnimationEditorResult, useAnimationEditor, useAnimationEditorController, useAnimationEditorState, useAnimationEditorTools };
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// src/animation-editor/useAnimationEditor.ts
|
|
2
|
+
import { useEffect, useMemo, useRef, useSyncExternalStore } from "react";
|
|
3
|
+
import {
|
|
4
|
+
AnimationEditorController,
|
|
5
|
+
createAnimationEditorToolProvider
|
|
6
|
+
} from "@pixerate/editor/animation-editor";
|
|
7
|
+
function useAnimationEditorController(options = {}) {
|
|
8
|
+
const ref = useRef(null);
|
|
9
|
+
if (!ref.current) ref.current = new AnimationEditorController(options);
|
|
10
|
+
const controller = ref.current;
|
|
11
|
+
useEffect(() => () => controller.destroy(), [controller]);
|
|
12
|
+
return controller;
|
|
13
|
+
}
|
|
14
|
+
function useAnimationEditorState(controller) {
|
|
15
|
+
return useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);
|
|
16
|
+
}
|
|
17
|
+
function useAnimationEditor(controllerOrOptions = {}) {
|
|
18
|
+
const own = useRef(null);
|
|
19
|
+
let controller;
|
|
20
|
+
if (controllerOrOptions instanceof AnimationEditorController) controller = controllerOrOptions;
|
|
21
|
+
else {
|
|
22
|
+
if (!own.current) own.current = new AnimationEditorController(controllerOrOptions);
|
|
23
|
+
controller = own.current;
|
|
24
|
+
}
|
|
25
|
+
useEffect(() => () => own.current?.destroy(), []);
|
|
26
|
+
const state = useAnimationEditorState(controller);
|
|
27
|
+
const actions = useMemo(
|
|
28
|
+
() => ({
|
|
29
|
+
play: () => controller.play(),
|
|
30
|
+
pause: () => controller.pause(),
|
|
31
|
+
toggle: () => controller.toggle(),
|
|
32
|
+
seek: (time) => controller.seek(time),
|
|
33
|
+
undo: () => void controller.undo(),
|
|
34
|
+
redo: () => void controller.redo(),
|
|
35
|
+
toSvg: () => controller.toSvg()
|
|
36
|
+
}),
|
|
37
|
+
[controller]
|
|
38
|
+
);
|
|
39
|
+
return { controller, state, ...actions };
|
|
40
|
+
}
|
|
41
|
+
function useAnimationEditorTools(registry, controller, options = {}) {
|
|
42
|
+
const { id, namespace, readOnly, rasterize } = options;
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!registry) return;
|
|
45
|
+
return registry.register(createAnimationEditorToolProvider(controller, { id, namespace, readOnly, rasterize }));
|
|
46
|
+
}, [registry, controller, id, namespace, readOnly, rasterize]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/animation-editor/AnimationStage.tsx
|
|
50
|
+
import { useMemo as useMemo2 } from "react";
|
|
51
|
+
import { jsx } from "react/jsx-runtime";
|
|
52
|
+
function AnimationStage({ controller, values, className = "" }) {
|
|
53
|
+
const state = useAnimationEditorState(controller);
|
|
54
|
+
const { scene, time } = state;
|
|
55
|
+
const svg = useMemo2(
|
|
56
|
+
() => controller.toSvg(time, { pretty: false, idPrefix: "ae-", values }).replace(/<svg ([^>]*?)width="[^"]*" height="[^"]*"/, '<svg $1width="100%" height="100%"'),
|
|
57
|
+
[controller, scene, time, values]
|
|
58
|
+
);
|
|
59
|
+
return /* @__PURE__ */ jsx(
|
|
60
|
+
"div",
|
|
61
|
+
{
|
|
62
|
+
className: `relative overflow-hidden ${className}`,
|
|
63
|
+
style: { aspectRatio: `${scene.artwork.width} / ${scene.artwork.height}` },
|
|
64
|
+
"data-animation-stage": "",
|
|
65
|
+
role: "img",
|
|
66
|
+
"aria-label": `${scene.name} at ${(time / 1e3).toFixed(2)} s`,
|
|
67
|
+
dangerouslySetInnerHTML: { __html: svg }
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/animation-editor/ShotTimeline.tsx
|
|
73
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
74
|
+
var seconds = (ms) => `${(ms / 1e3).toFixed(1)} s`;
|
|
75
|
+
function worst(issues) {
|
|
76
|
+
if (issues.some((i) => i.severity === "error")) return "error";
|
|
77
|
+
if (issues.some((i) => i.severity === "warning")) return "warning";
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
function ShotTimeline({ controller, className = "" }) {
|
|
81
|
+
const state = useAnimationEditorState(controller);
|
|
82
|
+
const { duration, shots, time, playing, selection, issues } = state;
|
|
83
|
+
const span = Math.max(1, duration);
|
|
84
|
+
const onRuler = (event) => {
|
|
85
|
+
const box = event.currentTarget.getBoundingClientRect();
|
|
86
|
+
controller.seek((event.clientX - box.left) / Math.max(1, box.width) * span);
|
|
87
|
+
};
|
|
88
|
+
return /* @__PURE__ */ jsxs("div", { className: `flex flex-col gap-1 ${className}`, "data-shot-timeline": "", children: [
|
|
89
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs", children: [
|
|
90
|
+
/* @__PURE__ */ jsx2("button", { type: "button", className: "rounded border border-border px-2 py-0.5", onClick: () => controller.toggle(), "aria-label": playing ? "Pause" : "Play", disabled: !shots.length, children: playing ? "Pause" : "Play" }),
|
|
91
|
+
/* @__PURE__ */ jsxs("span", { className: "tabular-nums text-muted-foreground", children: [
|
|
92
|
+
seconds(time),
|
|
93
|
+
" / ",
|
|
94
|
+
seconds(duration)
|
|
95
|
+
] })
|
|
96
|
+
] }),
|
|
97
|
+
/* @__PURE__ */ jsx2(
|
|
98
|
+
"div",
|
|
99
|
+
{
|
|
100
|
+
className: "relative h-3 cursor-pointer rounded bg-muted",
|
|
101
|
+
onPointerDown: onRuler,
|
|
102
|
+
role: "slider",
|
|
103
|
+
"aria-label": "Playhead",
|
|
104
|
+
"aria-valuemin": 0,
|
|
105
|
+
"aria-valuemax": duration,
|
|
106
|
+
"aria-valuenow": Math.round(time),
|
|
107
|
+
tabIndex: 0,
|
|
108
|
+
onKeyDown: (e) => {
|
|
109
|
+
if (e.key === "ArrowRight") controller.seek(time + 100);
|
|
110
|
+
if (e.key === "ArrowLeft") controller.seek(time - 100);
|
|
111
|
+
},
|
|
112
|
+
children: /* @__PURE__ */ jsx2("div", { className: "absolute inset-y-0 w-0.5 bg-rose-500", style: { left: `${time / span * 100}%` } })
|
|
113
|
+
}
|
|
114
|
+
),
|
|
115
|
+
/* @__PURE__ */ jsx2("div", { className: "flex w-full gap-px", role: "group", "aria-label": "Shots", children: shots.map((shot) => {
|
|
116
|
+
const mark = worst(issues.filter((i) => i.shot === shot.id));
|
|
117
|
+
const selected = selection?.shot === shot.id;
|
|
118
|
+
return /* @__PURE__ */ jsxs(
|
|
119
|
+
"button",
|
|
120
|
+
{
|
|
121
|
+
type: "button",
|
|
122
|
+
className: `min-w-0 truncate rounded border px-1 py-1 text-left text-[11px] ${selected ? "border-rose-500 bg-rose-500/10" : "border-border"} ${state.currentShot === shot.id ? "font-semibold" : ""}`,
|
|
123
|
+
style: { flexGrow: Math.max(1, shot.end - shot.start), flexBasis: 0 },
|
|
124
|
+
title: `${shot.label ?? shot.id} \xB7 ${seconds(shot.end - shot.start)}`,
|
|
125
|
+
"data-shot": shot.id,
|
|
126
|
+
"data-issue": mark ?? void 0,
|
|
127
|
+
onClick: () => {
|
|
128
|
+
controller.select({ shot: shot.id });
|
|
129
|
+
controller.seekShot(shot.id);
|
|
130
|
+
},
|
|
131
|
+
children: [
|
|
132
|
+
mark && /* @__PURE__ */ jsx2("span", { className: mark === "error" ? "text-red-500" : "text-amber-500", children: "\u25CF " }),
|
|
133
|
+
shot.label ?? shot.id
|
|
134
|
+
]
|
|
135
|
+
},
|
|
136
|
+
shot.id
|
|
137
|
+
);
|
|
138
|
+
}) })
|
|
139
|
+
] });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/animation-editor/AnimationEditor.tsx
|
|
143
|
+
import { motionIssueLine } from "@pixerate/editor/animation-editor";
|
|
144
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
145
|
+
function AnimationEditor({ controller, values, showIssues = true, className = "" }) {
|
|
146
|
+
const { issues } = useAnimationEditorState(controller);
|
|
147
|
+
return /* @__PURE__ */ jsxs2("div", { className: `flex flex-col gap-2 ${className}`, "data-animation-editor": "", children: [
|
|
148
|
+
/* @__PURE__ */ jsx3(AnimationStage, { controller, values, className: "rounded-md border border-border bg-muted/40" }),
|
|
149
|
+
/* @__PURE__ */ jsx3(ShotTimeline, { controller }),
|
|
150
|
+
showIssues && issues.length > 0 && /* @__PURE__ */ jsx3("ul", { className: "space-y-0.5 text-xs", "aria-label": "Animation issues", children: issues.map((issue, i) => /* @__PURE__ */ jsx3("li", { className: issue.severity === "error" ? "text-red-500" : issue.severity === "warning" ? "text-amber-500" : "text-muted-foreground", children: motionIssueLine(issue) }, i)) })
|
|
151
|
+
] });
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
AnimationEditor,
|
|
155
|
+
AnimationStage,
|
|
156
|
+
ShotTimeline,
|
|
157
|
+
useAnimationEditor,
|
|
158
|
+
useAnimationEditorController,
|
|
159
|
+
useAnimationEditorState,
|
|
160
|
+
useAnimationEditorTools
|
|
161
|
+
};
|
|
162
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/animation-editor/useAnimationEditor.ts","../../src/animation-editor/AnimationStage.tsx","../../src/animation-editor/ShotTimeline.tsx","../../src/animation-editor/AnimationEditor.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport {\n AnimationEditorController,\n createAnimationEditorToolProvider,\n type AnimationEditorOptions,\n type AnimationEditorState,\n type AnimationEditorToolProviderOptions,\n} from '@pixerate/editor/animation-editor';\nimport type { ToolRegistry } from '@pixerate/editor/tools';\n\n/** An `AnimationEditorController` created once for the component's lifetime (destroyed on unmount). */\nexport function useAnimationEditorController(options: AnimationEditorOptions = {}): AnimationEditorController {\n const ref = useRef<AnimationEditorController | null>(null);\n if (!ref.current) ref.current = new AnimationEditorController(options);\n const controller = ref.current;\n useEffect(() => () => controller.destroy(), [controller]);\n return controller;\n}\n\n/** The controller's state (re-renders on every change, including each played frame). */\nexport function useAnimationEditorState(controller: AnimationEditorController): AnimationEditorState {\n return useSyncExternalStore(controller.subscribe, controller.getState, controller.getState);\n}\n\nexport interface UseAnimationEditorResult {\n controller: AnimationEditorController;\n state: AnimationEditorState;\n play: () => void;\n pause: () => void;\n toggle: () => void;\n seek: (time: number) => void;\n undo: () => void;\n redo: () => void;\n /** The frame at the playhead as SVG. */\n toSvg: () => string;\n}\n\n/**\n * Subscribes to an animation editor controller and exposes its state with bound actions.\n * Pass options instead of a controller to create one for the component's lifetime.\n */\nexport function useAnimationEditor(controllerOrOptions: AnimationEditorController | AnimationEditorOptions = {}): UseAnimationEditorResult {\n const own = useRef<AnimationEditorController | null>(null);\n let controller: AnimationEditorController;\n if (controllerOrOptions instanceof AnimationEditorController) controller = controllerOrOptions;\n else {\n if (!own.current) own.current = new AnimationEditorController(controllerOrOptions);\n controller = own.current;\n }\n useEffect(() => () => own.current?.destroy(), []);\n const state = useAnimationEditorState(controller);\n const actions = useMemo(\n () => ({\n play: () => controller.play(),\n pause: () => controller.pause(),\n toggle: () => controller.toggle(),\n seek: (time: number) => controller.seek(time),\n undo: () => void controller.undo(),\n redo: () => void controller.redo(),\n toSvg: () => controller.toSvg(),\n }),\n [controller],\n );\n return { controller, state, ...actions };\n}\n\n/**\n * Registers the animation editor's agent tools (`get_animation`, `add_commands`,\n * `export_flair_effect`, …) in `registry` while mounted.\n */\nexport function useAnimationEditorTools(\n registry: ToolRegistry | null | undefined,\n controller: AnimationEditorController,\n options: AnimationEditorToolProviderOptions = {},\n): void {\n const { id, namespace, readOnly, rasterize } = options;\n useEffect(() => {\n if (!registry) return;\n return registry.register(createAnimationEditorToolProvider(controller, { id, namespace, readOnly, rasterize }));\n }, [registry, controller, id, namespace, readOnly, rasterize]);\n}\n","import React, { useMemo } from 'react';\nimport type { AnimationEditorController, MotionFrameOptions } from '@pixerate/editor/animation-editor';\nimport { useAnimationEditorState } from './useAnimationEditor';\n\nexport interface AnimationStageProps {\n controller: AnimationEditorController;\n /** Control values to preview (`{ accent: \"#0ea5e9\" }`). */\n values?: MotionFrameOptions['values'];\n className?: string;\n}\n\n/** The animation's frame at the playhead, drawn as inline SVG scaled to the stage's width. */\nexport function AnimationStage({ controller, values, className = '' }: AnimationStageProps) {\n const state = useAnimationEditorState(controller);\n const { scene, time } = state;\n const svg = useMemo(\n () => controller.toSvg(time, { pretty: false, idPrefix: 'ae-', values }).replace(/<svg ([^>]*?)width=\"[^\"]*\" height=\"[^\"]*\"/, '<svg $1width=\"100%\" height=\"100%\"'),\n [controller, scene, time, values],\n );\n return (\n <div\n className={`relative overflow-hidden ${className}`}\n style={{ aspectRatio: `${scene.artwork.width} / ${scene.artwork.height}` }}\n data-animation-stage=\"\"\n role=\"img\"\n aria-label={`${scene.name} at ${(time / 1000).toFixed(2)} s`}\n dangerouslySetInnerHTML={{ __html: svg }}\n />\n );\n}\n","import React from 'react';\nimport type { AnimationEditorController, MotionIssue } from '@pixerate/editor/animation-editor';\nimport { useAnimationEditorState } from './useAnimationEditor';\n\nexport interface ShotTimelineProps {\n controller: AnimationEditorController;\n className?: string;\n}\n\nconst seconds = (ms: number) => `${(ms / 1000).toFixed(1)} s`;\n\nfunction worst(issues: readonly MotionIssue[]): 'error' | 'warning' | null {\n if (issues.some((i) => i.severity === 'error')) return 'error';\n if (issues.some((i) => i.severity === 'warning')) return 'warning';\n return null;\n}\n\n/**\n * Play / pause, the playhead and one block per shot (as wide as it is long): click a shot to\n * select it and jump to its start, click the ruler to seek. Shots with errors or warnings are marked.\n */\nexport function ShotTimeline({ controller, className = '' }: ShotTimelineProps) {\n const state = useAnimationEditorState(controller);\n const { duration, shots, time, playing, selection, issues } = state;\n const span = Math.max(1, duration);\n const onRuler = (event: React.PointerEvent<HTMLDivElement>) => {\n const box = event.currentTarget.getBoundingClientRect();\n controller.seek(((event.clientX - box.left) / Math.max(1, box.width)) * span);\n };\n return (\n <div className={`flex flex-col gap-1 ${className}`} data-shot-timeline=\"\">\n <div className=\"flex items-center gap-2 text-xs\">\n <button type=\"button\" className=\"rounded border border-border px-2 py-0.5\" onClick={() => controller.toggle()} aria-label={playing ? 'Pause' : 'Play'} disabled={!shots.length}>\n {playing ? 'Pause' : 'Play'}\n </button>\n <span className=\"tabular-nums text-muted-foreground\">\n {seconds(time)} / {seconds(duration)}\n </span>\n </div>\n <div className=\"relative h-3 cursor-pointer rounded bg-muted\" onPointerDown={onRuler} role=\"slider\" aria-label=\"Playhead\" aria-valuemin={0} aria-valuemax={duration} aria-valuenow={Math.round(time)} tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'ArrowRight') controller.seek(time + 100);\n if (e.key === 'ArrowLeft') controller.seek(time - 100);\n }}>\n <div className=\"absolute inset-y-0 w-0.5 bg-rose-500\" style={{ left: `${(time / span) * 100}%` }} />\n </div>\n <div className=\"flex w-full gap-px\" role=\"group\" aria-label=\"Shots\">\n {shots.map((shot) => {\n const mark = worst(issues.filter((i) => i.shot === shot.id));\n const selected = selection?.shot === shot.id;\n return (\n <button\n key={shot.id}\n type=\"button\"\n className={`min-w-0 truncate rounded border px-1 py-1 text-left text-[11px] ${selected ? 'border-rose-500 bg-rose-500/10' : 'border-border'} ${state.currentShot === shot.id ? 'font-semibold' : ''}`}\n style={{ flexGrow: Math.max(1, shot.end - shot.start), flexBasis: 0 }}\n title={`${shot.label ?? shot.id} · ${seconds(shot.end - shot.start)}`}\n data-shot={shot.id}\n data-issue={mark ?? undefined}\n onClick={() => {\n controller.select({ shot: shot.id });\n controller.seekShot(shot.id);\n }}\n >\n {mark && <span className={mark === 'error' ? 'text-red-500' : 'text-amber-500'}>● </span>}\n {shot.label ?? shot.id}\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n","import React from 'react';\nimport { motionIssueLine, type AnimationEditorController, type MotionFrameOptions } from '@pixerate/editor/animation-editor';\nimport { AnimationStage } from './AnimationStage';\nimport { ShotTimeline } from './ShotTimeline';\nimport { useAnimationEditorState } from './useAnimationEditor';\n\nexport interface AnimationEditorProps {\n controller: AnimationEditorController;\n values?: MotionFrameOptions['values'];\n /** List the scene's issues under the timeline (default true). */\n showIssues?: boolean;\n className?: string;\n}\n\n/** Stage, shot timeline and issue list for an `AnimationEditorController`. */\nexport function AnimationEditor({ controller, values, showIssues = true, className = '' }: AnimationEditorProps) {\n const { issues } = useAnimationEditorState(controller);\n return (\n <div className={`flex flex-col gap-2 ${className}`} data-animation-editor=\"\">\n <AnimationStage controller={controller} values={values} className=\"rounded-md border border-border bg-muted/40\" />\n <ShotTimeline controller={controller} />\n {showIssues && issues.length > 0 && (\n <ul className=\"space-y-0.5 text-xs\" aria-label=\"Animation issues\">\n {issues.map((issue, i) => (\n <li key={i} className={issue.severity === 'error' ? 'text-red-500' : issue.severity === 'warning' ? 'text-amber-500' : 'text-muted-foreground'}>\n {motionIssueLine(issue)}\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["useMemo","useMemo","jsx","jsx","jsxs"]}
|