@pixerate/editor-svelte 0.58.0 → 0.59.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.
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
findVideoClip,
|
|
4
|
+
formatVideoTimecode,
|
|
5
|
+
VIDEO_TRANSITION_TYPES,
|
|
6
|
+
videoKeyframeRows,
|
|
7
|
+
videoKeyframeToggleEdit,
|
|
8
|
+
videoKeyframeValueEdit,
|
|
9
|
+
type VideoEdit,
|
|
10
|
+
type VideoEditorController,
|
|
11
|
+
type VideoKeyframeRow,
|
|
12
|
+
type VideoTransitionType
|
|
13
|
+
} from '@pixerate/editor/video-editor';
|
|
3
14
|
import { videoEditorStateOf } from './createVideoEditor.svelte.js';
|
|
4
15
|
|
|
5
16
|
interface Props {
|
|
@@ -17,6 +28,7 @@
|
|
|
17
28
|
let localError = $state<{ text: string; revision: number } | null>(null);
|
|
18
29
|
// A local error shows until the editor changes; then the editor's own error (if any) shows.
|
|
19
30
|
const message = $derived((localError && localError.revision === editor.revision ? localError.text : null) ?? editor.error);
|
|
31
|
+
const rows = $derived(clip ? videoKeyframeRows(editor.project, clip.id, editor.time) : []);
|
|
20
32
|
const input = 'w-full min-w-0 rounded border border-border bg-transparent px-1 py-0.5 text-xs disabled:opacity-50';
|
|
21
33
|
|
|
22
34
|
function apply(edit: VideoEdit, label: string) {
|
|
@@ -47,6 +59,28 @@
|
|
|
47
59
|
}
|
|
48
60
|
set(key, key === 'text' ? raw : raw.trim(), label);
|
|
49
61
|
}
|
|
62
|
+
/** The value shown in a property's field: blank when it is plain and at its default. */
|
|
63
|
+
function shown(row: VideoKeyframeRow): string | number {
|
|
64
|
+
if (!clip || row.keyed) return row.value;
|
|
65
|
+
const plain = row.property === 'opacity' ? clip.opacity : row.property === 'volume' ? (clip as { volume?: unknown }).volume : clip.transform?.[row.property];
|
|
66
|
+
return plain === undefined ? '' : row.value;
|
|
67
|
+
}
|
|
68
|
+
/** Typing a value: sets the key at the playhead when the property is keyed, else the plain value (empty resets it). */
|
|
69
|
+
function animatable(row: VideoKeyframeRow, raw: string) {
|
|
70
|
+
if (!clip) return;
|
|
71
|
+
const n = Number(raw);
|
|
72
|
+
if (raw.trim() !== '' && (!Number.isFinite(n) || (row.min !== undefined && n < row.min) || (row.max !== undefined && n > row.max))) {
|
|
73
|
+
localError = { text: `${row.label} must be ${row.max !== undefined ? `between ${row.min} and ${row.max}` : row.min !== undefined ? `${row.min} or more` : 'a number'}.`, revision: editor.revision };
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const edit = videoKeyframeValueEdit(editor.project, clip.id, row.property, editor.time, raw.trim() === '' ? undefined : n);
|
|
77
|
+
if (edit) apply(edit, row.keyed ? `Keyframe ${row.label.toLowerCase()}` : `Change ${row.label.toLowerCase()}`);
|
|
78
|
+
}
|
|
79
|
+
function toggleKey(row: VideoKeyframeRow) {
|
|
80
|
+
if (!clip) return;
|
|
81
|
+
const edit = videoKeyframeToggleEdit(editor.project, clip.id, row.property, editor.time);
|
|
82
|
+
if (edit) apply(edit, `${row.onKey ? 'Remove' : 'Add'} ${row.label.toLowerCase()} keyframe`);
|
|
83
|
+
}
|
|
50
84
|
function setTransition(type: string) {
|
|
51
85
|
if (!clip) return;
|
|
52
86
|
if (!type) apply({ op: 'set_transition', clip: clip.id, transition: null }, 'Remove transition');
|
|
@@ -64,7 +98,9 @@
|
|
|
64
98
|
</script>
|
|
65
99
|
|
|
66
100
|
<!--
|
|
67
|
-
The selected clip: its name, timing
|
|
101
|
+
The selected clip: its name, timing and fades; opacity, volume and transform, each with a keyframe
|
|
102
|
+
toggle at the playhead (◆ adds a key holding the value there or removes the one there; typing a
|
|
103
|
+
value on a keyed property sets the key at the playhead); speed and mute for media; text,
|
|
68
104
|
colours and size for titles; the colour of a colour clip; and the transition into it. Every
|
|
69
105
|
committed change is one undo step.
|
|
70
106
|
-->
|
|
@@ -87,15 +123,28 @@
|
|
|
87
123
|
<span class="tabular-nums" data-field="start">{formatVideoTimecode(clip.start, fps)}</span>
|
|
88
124
|
<span class="text-muted-foreground">Duration</span>
|
|
89
125
|
<span class="tabular-nums" data-field="duration">{formatVideoTimecode(clip.duration, fps)} ({(clip.duration / 1000).toFixed(2)} s)</span>
|
|
90
|
-
<span class="text-muted-foreground">Opacity</span>
|
|
91
|
-
<input class={input} type="number" min="0" max="1" step="0.05" placeholder="1" aria-label="Opacity" {disabled} value={clip.opacity ?? ''} onchange={(e) => number('opacity', e.currentTarget.value, 'Change opacity', { min: 0, max: 1, what: 'Opacity must be between 0 and 1.' })} data-field="opacity" />
|
|
92
126
|
<span class="text-muted-foreground">Fade in</span>
|
|
93
127
|
<input class={input} type="number" min="0" step="100" placeholder="0 ms" aria-label="Fade in" {disabled} value={clip.fadeIn ?? ''} onchange={(e) => number('fadeIn', e.currentTarget.value, 'Change fade in', { min: 0, what: 'Fade in must be a number of ms, 0 or more.' })} data-field="fadeIn" />
|
|
94
128
|
<span class="text-muted-foreground">Fade out</span>
|
|
95
129
|
<input class={input} type="number" min="0" step="100" placeholder="0 ms" aria-label="Fade out" {disabled} value={clip.fadeOut ?? ''} onchange={(e) => number('fadeOut', e.currentTarget.value, 'Change fade out', { min: 0, what: 'Fade out must be a number of ms, 0 or more.' })} data-field="fadeOut" />
|
|
130
|
+
{#each rows as row (row.property)}
|
|
131
|
+
<span class="text-muted-foreground">{row.label}</span>
|
|
132
|
+
<div class="flex min-w-0 items-center gap-1" data-keyframe-row={row.property}>
|
|
133
|
+
<input class={input} type="number" min={row.min} max={row.max} step={row.step} placeholder={String(row.value)} aria-label={row.label} {disabled} value={shown(row)} onchange={(e) => animatable(row, e.currentTarget.value)} data-field={row.property} />
|
|
134
|
+
<button
|
|
135
|
+
type="button"
|
|
136
|
+
class="shrink-0 rounded px-1 disabled:opacity-50 {row.onKey ? 'text-amber-500' : row.keyed ? 'text-foreground' : 'text-muted-foreground'}"
|
|
137
|
+
aria-label="{row.onKey ? 'Remove' : 'Add'} {row.label.toLowerCase()} keyframe at playhead"
|
|
138
|
+
aria-pressed={row.onKey}
|
|
139
|
+
title={row.keyed ? `${row.keys} key${row.keys === 1 ? '' : 's'}; ${row.onKey ? 'remove the one' : 'add one'} at the playhead` : 'Animate: add a key at the playhead'}
|
|
140
|
+
{disabled}
|
|
141
|
+
onclick={() => toggleKey(row)}
|
|
142
|
+
data-keyframe-toggle={row.property}>{row.onKey ? '◆' : '◇'}</button
|
|
143
|
+
>
|
|
144
|
+
{#if row.keyed}<span class="shrink-0 text-[10px] tabular-nums text-muted-foreground" data-keyframe-count={row.property}>{row.keys}</span>{/if}
|
|
145
|
+
</div>
|
|
146
|
+
{/each}
|
|
96
147
|
{#if clip.type === 'media'}
|
|
97
|
-
<span class="text-muted-foreground">Volume</span>
|
|
98
|
-
<input class={input} type="number" min="0" step="0.05" placeholder="1" aria-label="Volume" {disabled} value={clip.volume ?? ''} onchange={(e) => number('volume', e.currentTarget.value, 'Change volume', { min: 0, what: 'Volume must be 0 or more (1 = unchanged).' })} data-field="volume" />
|
|
99
148
|
<span class="text-muted-foreground">Speed</span>
|
|
100
149
|
<input class={input} type="number" min="0.05" step="0.05" placeholder="1" aria-label="Speed" {disabled} value={clip.speed ?? ''} onchange={(e) => number('speed', e.currentTarget.value, 'Change speed', { above: 0, what: 'Speed must be a number above 0 (1 = normal speed).' })} data-field="speed" />
|
|
101
150
|
<span class="text-muted-foreground">Muted</span>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClipInspector.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/ClipInspector.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"ClipInspector.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/ClipInspector.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAQH,KAAK,qBAAqB,EAG3B,MAAM,+BAA+B,CAAC;AAIvC,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA6JH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/editor-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.0",
|
|
4
4
|
"description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
|
|
5
5
|
"author": "Pixerate",
|
|
6
6
|
"license": "MIT",
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
"@tiptap/extension-bubble-menu": "^2.11.5",
|
|
132
132
|
"@tiptap/extension-placeholder": "^2.11.5",
|
|
133
133
|
"@tiptap/starter-kit": "^2.11.5",
|
|
134
|
-
"@pixerate/editor": "0.
|
|
134
|
+
"@pixerate/editor": "0.79.0"
|
|
135
135
|
},
|
|
136
136
|
"peerDependencies": {
|
|
137
137
|
"@dagrejs/dagre": "^1.1.4",
|