@pixerate/editor-svelte 0.178.0 → 0.179.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,6 +1,7 @@
1
1
  <script lang="ts">
2
- import type { EffectProvider } from '@pixerate/editor/effects';
3
- import { isVideoKeyframes, type VideoEffectValues } from '@pixerate/editor/video-editor';
2
+ import { pathControlsOf, type EffectProvider } from '@pixerate/editor/effects';
3
+ import { isVideoKeyframes, type VideoClip, type VideoEditorController, type VideoEffectEditResult, type VideoEffectValues } from '@pixerate/editor/video-editor';
4
+ import PathControls from './PathControls.svelte';
4
5
  import EffectParameters from '../effects/EffectParameters.svelte';
5
6
  import { effectDescriptionOf } from '../effects/effects.svelte.js';
6
7
 
@@ -12,9 +13,11 @@
12
13
  disabled: boolean;
13
14
  onchange: (control: string, value: unknown, gesture: string | null) => void;
14
15
  onpreset: (values: Readonly<Record<string, unknown>>) => void;
16
+ /** For an effect on a clip: where its path comes from, when it draws along one. */
17
+ path?: { controller: VideoEditorController; clip: VideoClip; instance: string; report: (r: VideoEffectEditResult | Promise<VideoEffectEditResult>) => void };
15
18
  }
16
19
 
17
- let { provider, effect, name, values, disabled, onchange, onpreset }: Props = $props();
20
+ let { provider, effect, name, values, disabled, onchange, onpreset, path }: Props = $props();
18
21
  const loaded = effectDescriptionOf(() => provider, () => effect);
19
22
  const keyed = $derived(new Set(Object.entries(values ?? {}).filter(([, v]) => isVideoKeyframes(v)).map(([k]) => k)));
20
23
  </script>
@@ -28,5 +31,8 @@
28
31
  <p class="text-[11px] text-muted-foreground">Loading…</p>
29
32
  {:else}
30
33
  {@const description = loaded.description}
34
+ {#if path && pathControlsOf(description).length}
35
+ <PathControls controller={path.controller} clip={path.clip} instance={path.instance} report={path.report} {provider} {disabled} />
36
+ {/if}
31
37
  <EffectParameters {description} values={values ?? {}} keyframed={keyed} {disabled} {onchange} onreset={(k) => onchange(k, description.defaults[k], null)} {onpreset} />
32
38
  {/if}
@@ -1,5 +1,5 @@
1
- import type { EffectProvider } from '@pixerate/editor/effects';
2
- import { type VideoEffectValues } from '@pixerate/editor/video-editor';
1
+ import { type EffectProvider } from '@pixerate/editor/effects';
2
+ import { type VideoClip, type VideoEditorController, type VideoEffectEditResult, type VideoEffectValues } from '@pixerate/editor/video-editor';
3
3
  interface Props {
4
4
  provider: EffectProvider | null;
5
5
  effect: string;
@@ -8,6 +8,13 @@ interface Props {
8
8
  disabled: boolean;
9
9
  onchange: (control: string, value: unknown, gesture: string | null) => void;
10
10
  onpreset: (values: Readonly<Record<string, unknown>>) => void;
11
+ /** For an effect on a clip: where its path comes from, when it draws along one. */
12
+ path?: {
13
+ controller: VideoEditorController;
14
+ clip: VideoClip;
15
+ instance: string;
16
+ report: (r: VideoEffectEditResult | Promise<VideoEffectEditResult>) => void;
17
+ };
11
18
  }
12
19
  declare const EffectControls: import("svelte").Component<Props, {}, "">;
13
20
  type EffectControls = ReturnType<typeof EffectControls>;
@@ -0,0 +1,32 @@
1
+ <script lang="ts">
2
+ import type { EffectProvider } from '@pixerate/editor/effects';
3
+ import type { VideoClip, VideoEditorController, VideoEffectEditResult } from '@pixerate/editor/video-editor';
4
+ import { videoEditorStateOf } from './createVideoEditor.svelte.js';
5
+
6
+ interface Props {
7
+ controller: VideoEditorController;
8
+ clip: VideoClip;
9
+ instance: string;
10
+ provider: EffectProvider;
11
+ disabled: boolean;
12
+ report: (r: VideoEffectEditResult | Promise<VideoEffectEditResult>) => void;
13
+ }
14
+
15
+ let { controller, clip, instance, provider, disabled, report }: Props = $props();
16
+ const view = videoEditorStateOf(() => controller);
17
+ const drawing = $derived(view.current.pathDrawing?.clip === clip.id && view.current.pathDrawing.instance === instance);
18
+ let words = $state('');
19
+ const btn = 'rounded border border-border px-1.5 py-0.5 text-[11px] disabled:opacity-40';
20
+ </script>
21
+
22
+ <!-- Where an effect's path comes from, for an effect that draws along one: drawn on the preview, a mask outline, words, or none. -->
23
+ <div class="flex flex-wrap items-center gap-1" data-effect-part="path">
24
+ <span class="text-muted-foreground">Path</span>
25
+ <button type="button" class={btn} {disabled} aria-pressed={drawing} title="Draw the path on the preview" onclick={() => (drawing ? controller.cancelPathDrawing() : controller.startPathDrawing({ clip: clip.id, instance }))}>{drawing ? 'Drawing… (Esc)' : 'Draw path'}</button>
26
+ <button type="button" class={btn} disabled={disabled || !clip.mask} title={clip.mask ? "Run it along this clip's mask outline" : 'Give the clip a mask first'} onclick={() => report(controller.setEffectPath(clip.id, instance, { mask: { clip: clip.id } }))}>Use mask outline</button>
27
+ {#if provider.pathFromText}
28
+ <input class="w-24 rounded border border-border bg-transparent px-1 py-0.5" aria-label="Words to write along the path" placeholder="Words" bind:value={words} {disabled} />
29
+ <button type="button" class={btn} disabled={disabled || !words.trim()} onclick={() => report(controller.setEffectPath(clip.id, instance, { text: words }))}>Write</button>
30
+ {/if}
31
+ <button type="button" class={btn} {disabled} onclick={() => report(controller.setEffectPath(clip.id, instance, { d: '' }))}>Clear path</button>
32
+ </div>
@@ -0,0 +1,13 @@
1
+ import type { EffectProvider } from '@pixerate/editor/effects';
2
+ import type { VideoClip, VideoEditorController, VideoEffectEditResult } from '@pixerate/editor/video-editor';
3
+ interface Props {
4
+ controller: VideoEditorController;
5
+ clip: VideoClip;
6
+ instance: string;
7
+ provider: EffectProvider;
8
+ disabled: boolean;
9
+ report: (r: VideoEffectEditResult | Promise<VideoEffectEditResult>) => void;
10
+ }
11
+ declare const PathControls: import("svelte").Component<Props, {}, "">;
12
+ type PathControls = ReturnType<typeof PathControls>;
13
+ export default PathControls;
@@ -92,6 +92,7 @@
92
92
  {disabled}
93
93
  onchange={(k, v, g) => report(controller.updateEffectValues(c.id, 'generator', { [k]: v } as VideoEffectValues, merge(g)))}
94
94
  onpreset={(values) => report(controller.updateEffectValues(c.id, 'generator', values as VideoEffectValues))}
95
+ path={{ controller, clip: c, instance: 'generator', report }}
95
96
  />
96
97
  </div>
97
98
  {/if}
@@ -165,6 +166,7 @@
165
166
  {disabled}
166
167
  onchange={(k, v, g) => report(controller.updateClipEffect(clip!.id, e.id, { values: { [k]: v as never } }, merge(g)))}
167
168
  onpreset={(values) => report(controller.updateClipEffect(clip!.id, e.id, { values: values as never }))}
169
+ path={{ controller, clip: clip!, instance: e.id, report }}
168
170
  />
169
171
  </div>
170
172
  {/if}
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { untrack } from 'svelte';
3
- import { formatVideoTimecode, VideoPreviewEngine, type VideoEditorController } from '@pixerate/editor/video-editor';
3
+ import { formatVideoTimecode, VideoPathGesture, VideoPreviewEngine, type VideoEditorController } from '@pixerate/editor/video-editor';
4
4
  import { videoEditorStateOf } from './createVideoEditor.svelte.js';
5
5
 
6
6
  interface Props {
@@ -24,13 +24,50 @@
24
24
  const engine = untrack(() => new VideoPreviewEngine(source, el, options));
25
25
  return () => engine.destroy();
26
26
  });
27
+ // Drawing a path for an effect (the effects panel's Draw path): the preview takes the pointer.
28
+ const pathDrawing = $derived(view.current.pathDrawing);
29
+ const gesture = $derived(new VideoPathGesture({ width: project.width, height: project.height }));
30
+ let drawn = $state<number[]>([]);
31
+ $effect(() => {
32
+ if (!pathDrawing) return;
33
+ const c = controller;
34
+ const onKey = (e: KeyboardEvent) => e.key === 'Escape' && c.cancelPathDrawing();
35
+ window.addEventListener('keydown', onKey);
36
+ return () => window.removeEventListener('keydown', onKey);
37
+ });
27
38
  </script>
28
39
 
29
40
  <!-- The frame at the playhead, drawn on a canvas with the project's aspect ratio; media plays in step with the timeline. -->
30
- <canvas
31
- bind:this={canvas}
32
- class="block w-full bg-black {className}"
33
- style="aspect-ratio:{project.width} / {project.height};{style}"
34
- data-video-preview=""
35
- aria-label="{project.name ?? 'Video'} at {formatVideoTimecode(view.current.time, project.fps)}"
36
- ></canvas>
41
+ <div style="position: relative">
42
+ <canvas
43
+ bind:this={canvas}
44
+ class="block w-full bg-black {className}"
45
+ style="aspect-ratio:{project.width} / {project.height};{style}"
46
+ data-video-preview=""
47
+ aria-label="{project.name ?? 'Video'} at {formatVideoTimecode(view.current.time, project.fps)}"
48
+ ></canvas>
49
+ {#if pathDrawing}
50
+ <svg
51
+ viewBox="0 0 {project.width} {project.height}"
52
+ style="position: absolute; inset: 0; width: 100%; height: 100%; cursor: crosshair; touch-action: none"
53
+ role="application"
54
+ aria-label="Draw the effect's path"
55
+ data-video-path-drawing=""
56
+ onpointerdown={(e) => {
57
+ e.currentTarget.setPointerCapture?.(e.pointerId);
58
+ gesture.down(e, e.currentTarget.getBoundingClientRect());
59
+ drawn = [...gesture.points];
60
+ }}
61
+ onpointermove={(e) => {
62
+ if (gesture.move(e, e.currentTarget.getBoundingClientRect())) drawn = [...gesture.points];
63
+ }}
64
+ onpointerup={() => {
65
+ const points = gesture.up();
66
+ drawn = [];
67
+ if (points) void controller.finishPathDrawing(points);
68
+ }}
69
+ >
70
+ <polyline points={drawn.join(' ')} fill="none" stroke="#22d3ee" stroke-width={Math.max(2, project.width / 300)} stroke-linecap="round" stroke-linejoin="round" />
71
+ </svg>
72
+ {/if}
73
+ </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor-svelte",
3
- "version": "0.178.0",
3
+ "version": "0.179.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.241.0"
168
+ "@pixerate/editor": "0.242.0"
169
169
  },
170
170
  "peerDependencies": {
171
171
  "@dagrejs/dagre": "^1.1.4",