@pixerate/editor-svelte 0.103.3 → 0.104.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/AnimationEditor.svelte +10 -3
- package/dist/animation-editor/AnimationRecordPanel.svelte +62 -0
- package/dist/animation-editor/AnimationRecordPanel.svelte.d.ts +13 -0
- package/dist/animation-editor/AnimationStage.svelte +37 -3
- package/dist/animation-editor/AnimationStage.svelte.d.ts +7 -1
- package/dist/animation-editor/index.d.ts +3 -0
- package/dist/animation-editor/index.js +1 -0
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { indexMotionNodes, motionIssueLine, type AnimationEditorController, type MotionFrameOptions, type MotionOnionSkin } from '@pixerate/editor/animation-editor';
|
|
2
|
+
import { indexMotionNodes, motionIssueLine, type AnimationEditorController, type MotionFrameOptions, type MotionOnionSkin, type MotionRecordGestureMode } from '@pixerate/editor/animation-editor';
|
|
3
|
+
import AnimationRecordPanel from './AnimationRecordPanel.svelte';
|
|
3
4
|
import AnimationStage from './AnimationStage.svelte';
|
|
4
5
|
import CommandInspector from './CommandInspector.svelte';
|
|
5
6
|
import CommandList from './CommandList.svelte';
|
|
@@ -70,6 +71,7 @@
|
|
|
70
71
|
let picked: string | null = $state(null);
|
|
71
72
|
let onion = $state(false);
|
|
72
73
|
let paths = $state(false);
|
|
74
|
+
let recordMode: MotionRecordGestureMode = $state('move');
|
|
73
75
|
const nodes = $derived(indexMotionNodes(editor.scene.artwork.nodes));
|
|
74
76
|
const pickedValid = $derived(picked && nodes.has(picked) ? picked : null);
|
|
75
77
|
const command = $derived(editor.selection?.command !== undefined ? editor.scene.shots.find((s) => s.id === editor.selection!.shot)?.run[editor.selection.command] : undefined);
|
|
@@ -106,8 +108,9 @@
|
|
|
106
108
|
|
|
107
109
|
<!--
|
|
108
110
|
A whole animation editor: the stage (click to pick an element, shift-click to toggle it as a
|
|
109
|
-
target of the selected command), the playhead scrubber,
|
|
110
|
-
|
|
111
|
+
target of the selected command), the playhead scrubber, a Record button to perform changes live
|
|
112
|
+
on the stage and keep them as a shot, and panels to edit commands, shots and scene settings by
|
|
113
|
+
hand. Every edit is one undo step.
|
|
111
114
|
-->
|
|
112
115
|
{#if !editable}
|
|
113
116
|
<div bind:this={root} class="relative flex flex-col gap-2 {className}" data-animation-editor="">
|
|
@@ -132,6 +135,7 @@
|
|
|
132
135
|
motionPaths={motionPaths ?? paths}
|
|
133
136
|
{onpick}
|
|
134
137
|
{highlight}
|
|
138
|
+
{recordMode}
|
|
135
139
|
class="rounded-md border border-border bg-muted/40"
|
|
136
140
|
/>
|
|
137
141
|
<PlayheadScrubber {controller} />
|
|
@@ -153,6 +157,9 @@
|
|
|
153
157
|
{/if}
|
|
154
158
|
</span>
|
|
155
159
|
</div>
|
|
160
|
+
{#if !editor.readOnly}
|
|
161
|
+
<AnimationRecordPanel {controller} node={pickedValid} mode={recordMode} onmodechange={(m) => (recordMode = m)} class="border-t border-border pt-2" />
|
|
162
|
+
{/if}
|
|
156
163
|
{@render issueList()}
|
|
157
164
|
</div>
|
|
158
165
|
<div class="flex min-w-0 flex-col gap-3">
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { MOTION_RECORD_GESTURE_MODES, type AnimationEditorController, type MotionRecordGestureMode, type MotionTakeSample } from '@pixerate/editor/animation-editor';
|
|
3
|
+
import { animationEditorStateOf } from './createAnimationEditor.svelte.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
controller: AnimationEditorController;
|
|
7
|
+
/** The element the paint and opacity controls record (e.g. the one picked on the stage). */
|
|
8
|
+
node?: string | null;
|
|
9
|
+
/** What a stage drag does while recording (pass the same to `<AnimationStage recordMode>`). */
|
|
10
|
+
mode: MotionRecordGestureMode;
|
|
11
|
+
onmodechange: (mode: MotionRecordGestureMode) => void;
|
|
12
|
+
class?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let { controller, node = null, mode, onmodechange, class: className = '' }: Props = $props();
|
|
16
|
+
const view = animationEditorStateOf(() => controller);
|
|
17
|
+
const editor = $derived(view.current);
|
|
18
|
+
const pose = $derived(editor.recording && node ? controller.pose(node) : null);
|
|
19
|
+
const btn = 'rounded border border-border px-2 py-0.5 text-xs disabled:opacity-40';
|
|
20
|
+
const MODE_LABELS: Record<MotionRecordGestureMode, string> = { move: 'Move', scale: 'Scale', rotate: 'Rotate' };
|
|
21
|
+
const hex6 = (c: string | undefined) => (c && /^#[0-9a-f]{6}/i.test(c) ? c.slice(0, 7).toLowerCase() : '#000000');
|
|
22
|
+
const sample = (values: Omit<MotionTakeSample, 'node' | 't'>) => node && controller.recordSample(node, values);
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<!--
|
|
26
|
+
Record a take: Record plays on from the end of the scene while you drag elements on the stage
|
|
27
|
+
(moving, scaling or turning them, as `mode` says) and change the picked element's paint and
|
|
28
|
+
opacity; Stop keeps the performance as a new shot of ordinary commands, one undo step.
|
|
29
|
+
-->
|
|
30
|
+
<div class="flex flex-wrap items-center gap-2 text-xs {className}" data-animation-record="">
|
|
31
|
+
{#if editor.recording}
|
|
32
|
+
<button type="button" class="{btn} border-red-500 text-red-600" onclick={() => controller.stopRecording()} data-record-stop="">■ Stop</button>
|
|
33
|
+
<button type="button" class={btn} onclick={() => controller.cancelRecording()} data-record-cancel="">Cancel</button>
|
|
34
|
+
<button type="button" class={btn} onclick={() => controller.toggle()}>{editor.playing ? 'Pause' : 'Resume'}</button>
|
|
35
|
+
<span class="tabular-nums text-red-600" aria-live="polite">● REC {(editor.recording.elapsed / 1000).toFixed(1)} s · {editor.recording.samples} samples</span>
|
|
36
|
+
{:else}
|
|
37
|
+
<button type="button" class={btn} onclick={() => controller.startRecording()} disabled={editor.readOnly} title="Perform changes live on the stage and keep them as a new shot" data-record-start="">● Record</button>
|
|
38
|
+
{/if}
|
|
39
|
+
<span class="flex items-center gap-1" role="radiogroup" aria-label="Drag to">
|
|
40
|
+
Drag to
|
|
41
|
+
{#each MOTION_RECORD_GESTURE_MODES as m (m)}
|
|
42
|
+
<label class="flex items-center gap-0.5"><input type="radio" name="animation-record-mode" checked={mode === m} onchange={() => onmodechange(m)} /> {MODE_LABELS[m]}</label>
|
|
43
|
+
{/each}
|
|
44
|
+
</span>
|
|
45
|
+
{#if pose}
|
|
46
|
+
<span class="flex items-center gap-2" data-record-paint="">
|
|
47
|
+
{#if pose.fill !== undefined}
|
|
48
|
+
<label class="flex items-center gap-1">Fill <input type="color" value={hex6(pose.fill)} oninput={(e) => sample({ fill: e.currentTarget.value })} /></label>
|
|
49
|
+
{/if}
|
|
50
|
+
{#if pose.stroke !== undefined}
|
|
51
|
+
<label class="flex items-center gap-1">Stroke <input type="color" value={hex6(pose.stroke)} oninput={(e) => sample({ stroke: e.currentTarget.value })} /></label>
|
|
52
|
+
{/if}
|
|
53
|
+
<label class="flex items-center gap-1">Opacity <input type="range" min="0" max="1" step="0.01" value={pose.opacity} oninput={(e) => sample({ opacity: Number(e.currentTarget.value) })} /></label>
|
|
54
|
+
</span>
|
|
55
|
+
{/if}
|
|
56
|
+
{#if editor.recording && !node}
|
|
57
|
+
<span class="text-muted-foreground">Drag an element; click one to record its paint and opacity.</span>
|
|
58
|
+
{/if}
|
|
59
|
+
{#if !editor.recording && editor.error && /record/i.test(editor.error)}
|
|
60
|
+
<span class="text-amber-600">{editor.error}</span>
|
|
61
|
+
{/if}
|
|
62
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AnimationEditorController, type MotionRecordGestureMode } from '@pixerate/editor/animation-editor';
|
|
2
|
+
interface Props {
|
|
3
|
+
controller: AnimationEditorController;
|
|
4
|
+
/** The element the paint and opacity controls record (e.g. the one picked on the stage). */
|
|
5
|
+
node?: string | null;
|
|
6
|
+
/** What a stage drag does while recording (pass the same to `<AnimationStage recordMode>`). */
|
|
7
|
+
mode: MotionRecordGestureMode;
|
|
8
|
+
onmodechange: (mode: MotionRecordGestureMode) => void;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
declare const AnimationRecordPanel: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type AnimationRecordPanel = ReturnType<typeof AnimationRecordPanel>;
|
|
13
|
+
export default AnimationRecordPanel;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { highlightMotionSvg, motionNodeAtElement, type AnimationEditorController, type MotionFrameOptions, type MotionOnionSkin } from '@pixerate/editor/animation-editor';
|
|
2
|
+
import { highlightMotionSvg, motionNodeAtElement, type AnimationEditorController, type MotionFrameOptions, type MotionOnionSkin, type MotionRecordGestureMode } from '@pixerate/editor/animation-editor';
|
|
3
3
|
import { animationEditorStateOf } from './createAnimationEditor.svelte.js';
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
@@ -17,11 +17,40 @@
|
|
|
17
17
|
onpick?: (id: string | null, event: MouseEvent) => void;
|
|
18
18
|
/** Node ids to outline (e.g. the picked element and the selected command's targets). */
|
|
19
19
|
highlight?: readonly string[];
|
|
20
|
+
/**
|
|
21
|
+
* While the controller records a take (`startRecording`), what dragging an element does:
|
|
22
|
+
* `move` it, `scale` it about its centre or `rotate` it (default `move`). Each pointer move
|
|
23
|
+
* is a sample of the take.
|
|
24
|
+
*/
|
|
25
|
+
recordMode?: MotionRecordGestureMode;
|
|
20
26
|
class?: string;
|
|
21
27
|
}
|
|
22
28
|
|
|
23
|
-
let { controller, values, onionSkin, motionPaths, onpick, highlight, class: className = '' }: Props = $props();
|
|
29
|
+
let { controller, values, onionSkin, motionPaths, onpick, highlight, recordMode = 'move', class: className = '' }: Props = $props();
|
|
24
30
|
const view = animationEditorStateOf(() => controller);
|
|
31
|
+
const recording = $derived(view.current.recording);
|
|
32
|
+
let dragging: number | null = null;
|
|
33
|
+
const point = (e: PointerEvent) => controller.clientToDocument((e.currentTarget as Element).getBoundingClientRect(), e.clientX, e.clientY);
|
|
34
|
+
function onpointerdown(e: PointerEvent) {
|
|
35
|
+
const id = motionNodeAtElement(view.current.scene, e.target as Element, e.currentTarget as Element);
|
|
36
|
+
if (!id || !controller.beginRecordGesture(id, recordMode, point(e))) return;
|
|
37
|
+
dragging = e.pointerId;
|
|
38
|
+
(e.currentTarget as Element).setPointerCapture?.(e.pointerId);
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
}
|
|
41
|
+
function onpointermove(e: PointerEvent) {
|
|
42
|
+
if (dragging === e.pointerId) controller.updateRecordGesture(point(e));
|
|
43
|
+
}
|
|
44
|
+
function onpointerup(e: PointerEvent) {
|
|
45
|
+
if (dragging !== e.pointerId) return;
|
|
46
|
+
dragging = null;
|
|
47
|
+
controller.endRecordGesture();
|
|
48
|
+
}
|
|
49
|
+
function onpointercancel() {
|
|
50
|
+
dragging = null;
|
|
51
|
+
controller.endRecordGesture();
|
|
52
|
+
}
|
|
53
|
+
// Re-drawn on every state change (a recorded sample changes the frame without moving the playhead).
|
|
25
54
|
const svg = $derived(
|
|
26
55
|
highlightMotionSvg(
|
|
27
56
|
controller
|
|
@@ -38,12 +67,17 @@
|
|
|
38
67
|
<!-- The frame at the playhead as inline SVG (keeping node ids, so a click can pick an element). -->
|
|
39
68
|
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
|
|
40
69
|
<div
|
|
41
|
-
class="relative overflow-hidden {onpick ? 'cursor-crosshair' : ''} {className}"
|
|
70
|
+
class="relative overflow-hidden {recording ? 'cursor-grab touch-none' : onpick ? 'cursor-crosshair' : ''} {className}"
|
|
42
71
|
style="aspect-ratio:{view.current.scene.artwork.width} / {view.current.scene.artwork.height}"
|
|
43
72
|
data-animation-stage=""
|
|
73
|
+
data-recording={recording ? '' : undefined}
|
|
44
74
|
role="img"
|
|
45
75
|
aria-label="{view.current.scene.name} at {(view.current.time / 1000).toFixed(2)} s"
|
|
46
76
|
onclick={onpick ? onclick : undefined}
|
|
77
|
+
onpointerdown={recording ? onpointerdown : undefined}
|
|
78
|
+
onpointermove={recording ? onpointermove : undefined}
|
|
79
|
+
onpointerup={recording ? onpointerup : undefined}
|
|
80
|
+
onpointercancel={recording ? onpointercancel : undefined}
|
|
47
81
|
>
|
|
48
82
|
{@html svg}
|
|
49
83
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnimationEditorController, type MotionFrameOptions, type MotionOnionSkin } from '@pixerate/editor/animation-editor';
|
|
1
|
+
import { type AnimationEditorController, type MotionFrameOptions, type MotionOnionSkin, type MotionRecordGestureMode } from '@pixerate/editor/animation-editor';
|
|
2
2
|
interface Props {
|
|
3
3
|
controller: AnimationEditorController;
|
|
4
4
|
/** Control values to preview (`{ accent: "#0ea5e9" }`). */
|
|
@@ -14,6 +14,12 @@ interface Props {
|
|
|
14
14
|
onpick?: (id: string | null, event: MouseEvent) => void;
|
|
15
15
|
/** Node ids to outline (e.g. the picked element and the selected command's targets). */
|
|
16
16
|
highlight?: readonly string[];
|
|
17
|
+
/**
|
|
18
|
+
* While the controller records a take (`startRecording`), what dragging an element does:
|
|
19
|
+
* `move` it, `scale` it about its centre or `rotate` it (default `move`). Each pointer move
|
|
20
|
+
* is a sample of the take.
|
|
21
|
+
*/
|
|
22
|
+
recordMode?: MotionRecordGestureMode;
|
|
17
23
|
class?: string;
|
|
18
24
|
}
|
|
19
25
|
declare const AnimationStage: import("svelte").Component<Props, {}, "">;
|
|
@@ -8,6 +8,7 @@ import type CommandInspectorComponent from "./CommandInspector.svelte";
|
|
|
8
8
|
import type EasePickerComponent from "./EasePicker.svelte";
|
|
9
9
|
import type ShotInspectorComponent from "./ShotInspector.svelte";
|
|
10
10
|
import type SceneSettingsComponent from "./SceneSettings.svelte";
|
|
11
|
+
import type AnimationRecordPanelComponent from "./AnimationRecordPanel.svelte";
|
|
11
12
|
export { default as AnimationEditor } from "./AnimationEditor.svelte";
|
|
12
13
|
export { default as AnimationStage } from "./AnimationStage.svelte";
|
|
13
14
|
export { default as ShotTimeline } from "./ShotTimeline.svelte";
|
|
@@ -18,6 +19,7 @@ export { default as EasePicker } from "./EasePicker.svelte";
|
|
|
18
19
|
export { default as ShotInspector } from "./ShotInspector.svelte";
|
|
19
20
|
export { default as SceneSettings } from "./SceneSettings.svelte";
|
|
20
21
|
export { default as AnimationEffectsPanel } from "./AnimationEffectsPanel.svelte";
|
|
22
|
+
export { default as AnimationRecordPanel } from "./AnimationRecordPanel.svelte";
|
|
21
23
|
export * from "./createAnimationEditor.svelte.js";
|
|
22
24
|
export type AnimationEditorProps = ComponentProps<typeof AnimationEditorComponent>;
|
|
23
25
|
export type AnimationStageProps = ComponentProps<typeof AnimationStageComponent>;
|
|
@@ -28,3 +30,4 @@ export type CommandInspectorProps = ComponentProps<typeof CommandInspectorCompon
|
|
|
28
30
|
export type EasePickerProps = ComponentProps<typeof EasePickerComponent>;
|
|
29
31
|
export type ShotInspectorProps = ComponentProps<typeof ShotInspectorComponent>;
|
|
30
32
|
export type SceneSettingsProps = ComponentProps<typeof SceneSettingsComponent>;
|
|
33
|
+
export type AnimationRecordPanelProps = ComponentProps<typeof AnimationRecordPanelComponent>;
|
|
@@ -8,4 +8,5 @@ export { default as EasePicker } from "./EasePicker.svelte";
|
|
|
8
8
|
export { default as ShotInspector } from "./ShotInspector.svelte";
|
|
9
9
|
export { default as SceneSettings } from "./SceneSettings.svelte";
|
|
10
10
|
export { default as AnimationEffectsPanel } from "./AnimationEffectsPanel.svelte";
|
|
11
|
+
export { default as AnimationRecordPanel } from "./AnimationRecordPanel.svelte";
|
|
11
12
|
export * from "./createAnimationEditor.svelte.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/editor-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.104.0",
|
|
4
4
|
"description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
|
|
5
5
|
"author": "Pixerate",
|
|
6
6
|
"license": "MIT",
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
"@tiptap/extension-bubble-menu": "^2.27.3",
|
|
145
145
|
"@tiptap/extension-placeholder": "^2.27.3",
|
|
146
146
|
"@tiptap/starter-kit": "^2.27.3",
|
|
147
|
-
"@pixerate/editor": "0.
|
|
147
|
+
"@pixerate/editor": "0.145.0"
|
|
148
148
|
},
|
|
149
149
|
"peerDependencies": {
|
|
150
150
|
"@dagrejs/dagre": "^1.1.4",
|