@pixerate/editor-svelte 0.169.0 → 0.170.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/AnimationStage.svelte +6 -2
- package/dist/animation-editor/ShotCameraMoves.svelte +122 -0
- package/dist/animation-editor/ShotCameraMoves.svelte.d.ts +10 -0
- package/dist/animation-editor/ShotInspector.svelte +11 -7
- 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,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { highlightMotionSvg, motionNodeAtElement, type AnimationEditorController, type MotionFrameOptions, type MotionOnionSkin, type MotionRecordGestureMode } from '@pixerate/editor/animation-editor';
|
|
2
|
+
import { highlightMotionSvg, motionCameraTransform, motionCameraViewAt, 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 {
|
|
@@ -59,6 +59,9 @@
|
|
|
59
59
|
highlight ?? []
|
|
60
60
|
)
|
|
61
61
|
);
|
|
62
|
+
// A camera move's tilt and turn are 3D, so the stage element turns (its roll and blur are in the SVG).
|
|
63
|
+
let stage = $state<HTMLDivElement | null>(null);
|
|
64
|
+
const transform = $derived(motionCameraTransform(motionCameraViewAt(controller.viewScene(), view.current.time, view.current.effectsProvider), stage?.clientWidth || view.current.scene.artwork.width));
|
|
62
65
|
function onclick(event: MouseEvent) {
|
|
63
66
|
onpick?.(motionNodeAtElement(view.current.scene, event.target as Element, event.currentTarget as Element), event);
|
|
64
67
|
}
|
|
@@ -68,7 +71,8 @@
|
|
|
68
71
|
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_noninteractive_element_interactions -->
|
|
69
72
|
<div
|
|
70
73
|
class="relative overflow-hidden {recording ? 'cursor-grab touch-none' : onpick ? 'cursor-crosshair' : ''} {className}"
|
|
71
|
-
|
|
74
|
+
bind:this={stage}
|
|
75
|
+
style="aspect-ratio:{view.current.scene.artwork.width} / {view.current.scene.artwork.height}{transform ? `; transform:${transform}` : ''}"
|
|
72
76
|
data-animation-stage=""
|
|
73
77
|
data-recording={recording ? '' : undefined}
|
|
74
78
|
role="img"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { motionShotCameraMove, type AnimationEditorController, type MotionCameraMoveResult } from '@pixerate/editor/animation-editor';
|
|
3
|
+
import type { CameraMoveRole, EffectSummary } from '@pixerate/editor/effects';
|
|
4
|
+
import EffectParameters from '../effects/EffectParameters.svelte';
|
|
5
|
+
import { effectDescriptionOf } from '../effects/effects.svelte.js';
|
|
6
|
+
import { animationEditorStateOf } from './createAnimationEditor.svelte.js';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
controller: AnimationEditorController;
|
|
10
|
+
/** The shot whose moves to edit. */
|
|
11
|
+
shot: string;
|
|
12
|
+
class?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let { controller, shot, class: className = '' }: Props = $props();
|
|
16
|
+
const view = animationEditorStateOf(() => controller);
|
|
17
|
+
const editor = $derived(view.current);
|
|
18
|
+
const current = $derived(editor.scene.shots.find((s) => s.id === shot));
|
|
19
|
+
const roles: { role: CameraMoveRole; label: string; none: string }[] = [
|
|
20
|
+
{ role: 'transition', label: 'Move in', none: 'ease in' },
|
|
21
|
+
{ role: 'hold', label: 'While holding', none: 'stay still' }
|
|
22
|
+
];
|
|
23
|
+
const input = 'w-full min-w-0 rounded border border-border bg-transparent px-1 py-0.5 text-xs';
|
|
24
|
+
|
|
25
|
+
// The provider's camera moves of each role, loaded when the provider changes.
|
|
26
|
+
let moves = $state<Record<CameraMoveRole, EffectSummary[]>>({ transition: [], hold: [] });
|
|
27
|
+
$effect(() => {
|
|
28
|
+
void editor.effectsProvider;
|
|
29
|
+
let live = true;
|
|
30
|
+
void Promise.all([controller.cameraMoves('transition'), controller.cameraMoves('hold')]).then(
|
|
31
|
+
([transition, hold]) => live && (moves = { transition, hold }),
|
|
32
|
+
() => live && (moves = { transition: [], hold: [] })
|
|
33
|
+
);
|
|
34
|
+
return () => (live = false);
|
|
35
|
+
});
|
|
36
|
+
const has = $derived(moves.transition.length + moves.hold.length > 0);
|
|
37
|
+
const named = $derived(current?.camera?.in !== undefined || current?.camera?.during !== undefined);
|
|
38
|
+
const inMove = $derived(motionShotCameraMove(current, 'transition'));
|
|
39
|
+
const holdMove = $derived(motionShotCameraMove(current, 'hold'));
|
|
40
|
+
const inDescription = effectDescriptionOf(() => controller.effectsProvider, () => inMove?.move);
|
|
41
|
+
const holdDescription = effectDescriptionOf(() => controller.effectsProvider, () => holdMove?.move);
|
|
42
|
+
let message = $state<string | null>(null);
|
|
43
|
+
|
|
44
|
+
function report(r: Promise<MotionCameraMoveResult> | MotionCameraMoveResult) {
|
|
45
|
+
void Promise.resolve(r).then((x) => (message = x.ok ? null : x.error));
|
|
46
|
+
}
|
|
47
|
+
function pick(role: CameraMoveRole, id: string) {
|
|
48
|
+
report(id ? controller.setShotCameraMove(shot, role, id) : controller.clearShotCameraMove(shot, role));
|
|
49
|
+
}
|
|
50
|
+
// Changes over the move as saved; null removes a field (a changed value drops the preset's name).
|
|
51
|
+
function set(role: CameraMoveRole, changes: { values?: Record<string, unknown>; preset?: string | null; ms?: number | null }, merge?: string | null) {
|
|
52
|
+
const move = motionShotCameraMove(current, role);
|
|
53
|
+
if (!move) return;
|
|
54
|
+
const next: Record<string, unknown> = { ...move, ...changes };
|
|
55
|
+
for (const k of Object.keys(next)) if (next[k] === null || (k === 'values' && !Object.keys(next[k] as object).length)) delete next[k];
|
|
56
|
+
report(controller.setShotCameraMove(shot, role, next as never, { merge: merge ?? undefined }));
|
|
57
|
+
}
|
|
58
|
+
function length(raw: string) {
|
|
59
|
+
if (raw.trim() === '') return set('transition', { ms: null });
|
|
60
|
+
const n = Number(raw);
|
|
61
|
+
if (Number.isFinite(n) && n >= 0) set('transition', { ms: n });
|
|
62
|
+
else message = 'Length is a number of ms, 0 or more.';
|
|
63
|
+
}
|
|
64
|
+
function reset(role: CameraMoveRole, k: string) {
|
|
65
|
+
const { [k]: _gone, ...rest } = motionShotCameraMove(current, role)?.values ?? {};
|
|
66
|
+
set(role, { values: rest, preset: null });
|
|
67
|
+
}
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<!--
|
|
71
|
+
A shot's camera moves from the effects provider: the move it gets in with and the move it makes
|
|
72
|
+
while it holds, each with its length (a move in), presets and controls. Hidden with the reason
|
|
73
|
+
when the provider has no camera moves. Every change is one undo step (a slider drag is one).
|
|
74
|
+
-->
|
|
75
|
+
{#if current}
|
|
76
|
+
{#if !has}
|
|
77
|
+
{#if named}
|
|
78
|
+
<p class="text-[10px] text-muted-foreground {className}" data-camera-moves="unavailable">
|
|
79
|
+
This shot names camera moves, but {editor.effectsProvider ? `${editor.effectsProvider.name} has none` : 'no effects provider is set'}, so the camera eases instead.
|
|
80
|
+
</p>
|
|
81
|
+
{/if}
|
|
82
|
+
{:else}
|
|
83
|
+
<div class="grid grid-cols-[6.5rem_1fr] items-center gap-x-2 gap-y-1 {className}" data-camera-moves="">
|
|
84
|
+
{#each roles as { role, label, none } (role)}
|
|
85
|
+
{@const move = role === 'transition' ? inMove : holdMove}
|
|
86
|
+
{@const loaded = role === 'transition' ? inDescription : holdDescription}
|
|
87
|
+
{@const known = moves[role].some((m) => m.id === move?.move)}
|
|
88
|
+
<span class="text-muted-foreground">{label}</span>
|
|
89
|
+
<select class={input} aria-label={label} disabled={editor.readOnly} value={move?.move ?? ''} onchange={(e) => pick(role, e.currentTarget.value)}>
|
|
90
|
+
<option value="">{none}</option>
|
|
91
|
+
{#if move && !known}<option value={move.move}>{move.move} (not in the provider)</option>{/if}
|
|
92
|
+
{#each moves[role] as m (m.id)}
|
|
93
|
+
<option value={m.id}>{m.name}</option>
|
|
94
|
+
{/each}
|
|
95
|
+
</select>
|
|
96
|
+
{#if move && known && !editor.readOnly}
|
|
97
|
+
{#if loaded.error}
|
|
98
|
+
<p class="col-span-2 text-[11px] text-amber-500">{loaded.error}</p>
|
|
99
|
+
{:else if loaded.description}
|
|
100
|
+
<div class="col-span-2 flex flex-col gap-1 border-l border-border pl-2" data-camera-move-controls={role}>
|
|
101
|
+
{#if role === 'transition'}
|
|
102
|
+
<div class="flex items-center gap-1">
|
|
103
|
+
<span class="w-16 text-muted-foreground">Length</span>
|
|
104
|
+
<input class={input} type="number" min="0" step="50" aria-label="Move length" placeholder={String(loaded.description.camera?.durationMs ?? '')} value={move.ms ?? ''} onchange={(e) => length(e.currentTarget.value)} />
|
|
105
|
+
<span class="text-[10px] text-muted-foreground">ms</span>
|
|
106
|
+
</div>
|
|
107
|
+
{/if}
|
|
108
|
+
<EffectParameters
|
|
109
|
+
description={loaded.description}
|
|
110
|
+
values={move.values ?? {}}
|
|
111
|
+
onchange={(k, v, g) => set(role, { values: { ...(move.values ?? {}), [k]: v }, preset: null }, g)}
|
|
112
|
+
onreset={(k) => reset(role, k)}
|
|
113
|
+
onpresetname={(name) => set(role, { preset: name, values: {} })}
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
{/if}
|
|
117
|
+
{/if}
|
|
118
|
+
{/each}
|
|
119
|
+
{#if message}<p class="col-span-2 text-[11px] text-destructive" role="alert">{message}</p>{/if}
|
|
120
|
+
</div>
|
|
121
|
+
{/if}
|
|
122
|
+
{/if}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type AnimationEditorController } from '@pixerate/editor/animation-editor';
|
|
2
|
+
interface Props {
|
|
3
|
+
controller: AnimationEditorController;
|
|
4
|
+
/** The shot whose moves to edit. */
|
|
5
|
+
shot: string;
|
|
6
|
+
class?: string;
|
|
7
|
+
}
|
|
8
|
+
declare const ShotCameraMoves: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type ShotCameraMoves = ReturnType<typeof ShotCameraMoves>;
|
|
10
|
+
export default ShotCameraMoves;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { describeMotionElements, freshShotId, motionCueChoices, motionCueFromKey, motionCueKey, type AnimationEditorController, type MotionEdit } from '@pixerate/editor/animation-editor';
|
|
2
|
+
import { describeMotionElements, freshShotId, motionCameraFramed, motionCueChoices, motionCueFromKey, motionCueKey, type AnimationEditorController, type MotionEdit } from '@pixerate/editor/animation-editor';
|
|
3
3
|
import { animationEditorStateOf } from './createAnimationEditor.svelte.js';
|
|
4
|
+
import ShotCameraMoves from './ShotCameraMoves.svelte';
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
6
7
|
controller: AnimationEditorController;
|
|
@@ -53,8 +54,10 @@
|
|
|
53
54
|
apply(raw.trim() ? { op: 'update_shot', shot: current.id, set: { label: raw.trim() } } : { op: 'update_shot', shot: current.id, remove: ['label'] }, 'Rename shot');
|
|
54
55
|
}
|
|
55
56
|
function setCamera(v: string) {
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
// Its camera moves stay when the framing changes.
|
|
58
|
+
const moves = { ...(current.camera?.in !== undefined ? { in: current.camera.in } : {}), ...(current.camera?.during !== undefined ? { during: current.camera.during } : {}) };
|
|
59
|
+
if (!v) apply(Object.keys(moves).length ? { op: 'update_shot', shot: current.id, set: { camera: moves } } : { op: 'update_shot', shot: current.id, remove: ['camera'] }, 'Clear camera');
|
|
60
|
+
else if (v.startsWith('frame:')) apply({ op: 'update_shot', shot: current.id, set: { camera: { ...moves, frame: v.slice(6), padding: 24 } } }, 'Frame camera');
|
|
58
61
|
}
|
|
59
62
|
function setCue(v: string) {
|
|
60
63
|
const cue = motionCueFromKey(v);
|
|
@@ -63,8 +66,8 @@
|
|
|
63
66
|
</script>
|
|
64
67
|
|
|
65
68
|
<!--
|
|
66
|
-
The selected shot: label, hold, speed, the node the camera frames
|
|
67
|
-
beat or a narration word). Also adds, moves and deletes shots. Every change is one undo step.
|
|
69
|
+
The selected shot: label, hold, speed, the node the camera frames, the cue it starts on (a
|
|
70
|
+
beat or a narration word) and its camera moves (ShotCameraMoves). Also adds, moves and deletes shots. Every change is one undo step.
|
|
68
71
|
-->
|
|
69
72
|
<div class="flex flex-col gap-1.5 text-xs {className}" data-shot-inspector="">
|
|
70
73
|
<div class="flex items-center gap-1">
|
|
@@ -94,9 +97,9 @@
|
|
|
94
97
|
<span class="text-[10px] text-muted-foreground">×</span>
|
|
95
98
|
</div>
|
|
96
99
|
<span class="text-muted-foreground">Camera</span>
|
|
97
|
-
<select class={input} aria-label="Camera frames" disabled={editor.readOnly} value={current.camera ? (frame ? `frame:${frame}` : 'rect') : ''} onchange={(e) => setCamera(e.currentTarget.value)}>
|
|
100
|
+
<select class={input} aria-label="Camera frames" disabled={editor.readOnly} value={current.camera && motionCameraFramed(current.camera) ? (frame ? `frame:${frame}` : 'rect') : ''} onchange={(e) => setCamera(e.currentTarget.value)}>
|
|
98
101
|
<option value="">keep framing</option>
|
|
99
|
-
{#if current.camera && !frame}<option value="rect">a rectangle</option>{/if}
|
|
102
|
+
{#if current.camera && motionCameraFramed(current.camera) && !frame}<option value="rect">a rectangle</option>{/if}
|
|
100
103
|
{#each elements as e (e.id)}
|
|
101
104
|
<option value="frame:{e.id}">frame {e.name ?? e.id}</option>
|
|
102
105
|
{/each}
|
|
@@ -114,6 +117,7 @@
|
|
|
114
117
|
{/if}
|
|
115
118
|
</div>
|
|
116
119
|
{/if}
|
|
120
|
+
{#if current}<ShotCameraMoves {controller} shot={current.id} />{/if}
|
|
117
121
|
{#if message}
|
|
118
122
|
<p class="text-red-500" role="alert" data-edit-error="">{message}</p>
|
|
119
123
|
{/if}
|
|
@@ -7,6 +7,7 @@ import type CommandListComponent from "./CommandList.svelte";
|
|
|
7
7
|
import type CommandInspectorComponent from "./CommandInspector.svelte";
|
|
8
8
|
import type EasePickerComponent from "./EasePicker.svelte";
|
|
9
9
|
import type ShotInspectorComponent from "./ShotInspector.svelte";
|
|
10
|
+
import type ShotCameraMovesComponent from "./ShotCameraMoves.svelte";
|
|
10
11
|
import type SceneSettingsComponent from "./SceneSettings.svelte";
|
|
11
12
|
import type AnimationRecordPanelComponent from "./AnimationRecordPanel.svelte";
|
|
12
13
|
export { default as AnimationEditor } from "./AnimationEditor.svelte";
|
|
@@ -17,6 +18,7 @@ export { default as CommandList } from "./CommandList.svelte";
|
|
|
17
18
|
export { default as CommandInspector } from "./CommandInspector.svelte";
|
|
18
19
|
export { default as EasePicker } from "./EasePicker.svelte";
|
|
19
20
|
export { default as ShotInspector } from "./ShotInspector.svelte";
|
|
21
|
+
export { default as ShotCameraMoves } from "./ShotCameraMoves.svelte";
|
|
20
22
|
export { default as SceneSettings } from "./SceneSettings.svelte";
|
|
21
23
|
export { default as AnimationEffectsPanel } from "./AnimationEffectsPanel.svelte";
|
|
22
24
|
export { default as AnimationRecordPanel } from "./AnimationRecordPanel.svelte";
|
|
@@ -29,5 +31,6 @@ export type CommandListProps = ComponentProps<typeof CommandListComponent>;
|
|
|
29
31
|
export type CommandInspectorProps = ComponentProps<typeof CommandInspectorComponent>;
|
|
30
32
|
export type EasePickerProps = ComponentProps<typeof EasePickerComponent>;
|
|
31
33
|
export type ShotInspectorProps = ComponentProps<typeof ShotInspectorComponent>;
|
|
34
|
+
export type ShotCameraMovesProps = ComponentProps<typeof ShotCameraMovesComponent>;
|
|
32
35
|
export type SceneSettingsProps = ComponentProps<typeof SceneSettingsComponent>;
|
|
33
36
|
export type AnimationRecordPanelProps = ComponentProps<typeof AnimationRecordPanelComponent>;
|
|
@@ -6,6 +6,7 @@ export { default as CommandList } from "./CommandList.svelte";
|
|
|
6
6
|
export { default as CommandInspector } from "./CommandInspector.svelte";
|
|
7
7
|
export { default as EasePicker } from "./EasePicker.svelte";
|
|
8
8
|
export { default as ShotInspector } from "./ShotInspector.svelte";
|
|
9
|
+
export { default as ShotCameraMoves } from "./ShotCameraMoves.svelte";
|
|
9
10
|
export { default as SceneSettings } from "./SceneSettings.svelte";
|
|
10
11
|
export { default as AnimationEffectsPanel } from "./AnimationEffectsPanel.svelte";
|
|
11
12
|
export { default as AnimationRecordPanel } from "./AnimationRecordPanel.svelte";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/editor-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.170.0",
|
|
4
4
|
"description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
|
|
5
5
|
"author": "Pixerate",
|
|
6
6
|
"license": "MIT",
|
|
@@ -165,7 +165,7 @@
|
|
|
165
165
|
"**/*.css"
|
|
166
166
|
],
|
|
167
167
|
"dependencies": {
|
|
168
|
-
"@pixerate/editor": "0.
|
|
168
|
+
"@pixerate/editor": "0.230.0"
|
|
169
169
|
},
|
|
170
170
|
"peerDependencies": {
|
|
171
171
|
"@dagrejs/dagre": "^1.1.4",
|