@pixerate/editor-svelte 0.87.0 → 0.88.1
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/canvas/createCanvasAnimation.svelte.d.ts.map +1 -1
- package/dist/canvas/createCanvasArrange.svelte.d.ts.map +1 -1
- package/dist/canvas/createCanvasAutoLayout.svelte.d.ts.map +1 -1
- package/dist/canvas/createCanvasComponents.svelte.d.ts.map +1 -1
- package/dist/effects/EffectParameters.svelte +73 -0
- package/dist/effects/EffectParameters.svelte.d.ts +25 -0
- package/dist/effects/EffectParameters.svelte.d.ts.map +1 -0
- package/dist/effects/EffectThumbnail.svelte +48 -0
- package/dist/effects/EffectThumbnail.svelte.d.ts +13 -0
- package/dist/effects/EffectThumbnail.svelte.d.ts.map +1 -0
- package/dist/effects/EffectsBrowser.svelte +112 -0
- package/dist/effects/EffectsBrowser.svelte.d.ts +18 -0
- package/dist/effects/EffectsBrowser.svelte.d.ts.map +1 -0
- package/dist/effects/effects.svelte.d.ts +17 -0
- package/dist/effects/effects.svelte.d.ts.map +1 -0
- package/dist/effects/effects.svelte.js +49 -0
- package/dist/effects/index.d.ts +12 -0
- package/dist/effects/index.d.ts.map +1 -0
- package/dist/effects/index.js +4 -0
- package/dist/image-editor/ImageEditor.svelte +15 -0
- package/dist/image-editor/ImageEditor.svelte.d.ts +7 -0
- package/dist/image-editor/ImageEditor.svelte.d.ts.map +1 -1
- package/dist/image-editor/ImageEffectControls.svelte +29 -0
- package/dist/image-editor/ImageEffectControls.svelte.d.ts +13 -0
- package/dist/image-editor/ImageEffectControls.svelte.d.ts.map +1 -0
- package/dist/image-editor/ImageProviderEffectsPanel.svelte +129 -0
- package/dist/image-editor/ImageProviderEffectsPanel.svelte.d.ts +9 -0
- package/dist/image-editor/ImageProviderEffectsPanel.svelte.d.ts.map +1 -0
- package/dist/image-editor/imageEditorState.svelte.d.ts +9 -1
- package/dist/image-editor/imageEditorState.svelte.d.ts.map +1 -1
- package/dist/image-editor/imageEditorState.svelte.js +15 -1
- package/dist/image-editor/index.d.ts +3 -0
- package/dist/image-editor/index.d.ts.map +1 -1
- package/dist/image-editor/index.js +1 -0
- package/dist/video-editor/ClipInspector.svelte +10 -3
- package/dist/video-editor/ClipInspector.svelte.d.ts.map +1 -1
- package/dist/video-editor/EffectControls.svelte +32 -0
- package/dist/video-editor/EffectControls.svelte.d.ts +15 -0
- package/dist/video-editor/EffectControls.svelte.d.ts.map +1 -0
- package/dist/video-editor/VideoEditor.svelte +18 -2
- package/dist/video-editor/VideoEditor.svelte.d.ts +9 -0
- package/dist/video-editor/VideoEditor.svelte.d.ts.map +1 -1
- package/dist/video-editor/VideoEffectsPanel.svelte +154 -0
- package/dist/video-editor/VideoEffectsPanel.svelte.d.ts +9 -0
- package/dist/video-editor/VideoEffectsPanel.svelte.d.ts.map +1 -0
- package/dist/video-editor/createVideoEditor.svelte.d.ts +8 -1
- package/dist/video-editor/createVideoEditor.svelte.d.ts.map +1 -1
- package/dist/video-editor/createVideoEditor.svelte.js +15 -1
- package/dist/video-editor/index.d.ts +3 -0
- package/dist/video-editor/index.d.ts.map +1 -1
- package/dist/video-editor/index.js +1 -0
- package/package.json +7 -2
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { findLayer, type ImageEditorController, type ImageLayer, type SmartObjectLayer } from '@pixerate/editor/image-editor';
|
|
3
|
+
import { untrack } from 'svelte';
|
|
4
|
+
import EffectsBrowser from '../effects/EffectsBrowser.svelte';
|
|
5
|
+
import ImageEffectControls from './ImageEffectControls.svelte';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
controller: ImageEditorController;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { controller, class: className = '' }: Props = $props();
|
|
13
|
+
let tick = $state(0);
|
|
14
|
+
$effect(() => {
|
|
15
|
+
const c = controller;
|
|
16
|
+
return untrack(() => c.subscribe(() => tick++));
|
|
17
|
+
});
|
|
18
|
+
let browse = $state<null | 'filter' | 'layer'>(null);
|
|
19
|
+
let message = $state<string | null>(null);
|
|
20
|
+
let open = $state<string | null>(null);
|
|
21
|
+
const snapshot = $derived.by(() => {
|
|
22
|
+
void tick;
|
|
23
|
+
const s = controller.getState();
|
|
24
|
+
const layer: ImageLayer | null = s.activeLayerId ? (findLayer(s.layers, s.activeLayerId)?.layer ?? null) : null;
|
|
25
|
+
return { provider: controller.effectsProvider, layer };
|
|
26
|
+
});
|
|
27
|
+
const provider = $derived(snapshot.provider);
|
|
28
|
+
const layer = $derived(snapshot.layer);
|
|
29
|
+
const all = $derived(layer?.type === 'smart' ? ((layer as SmartObjectLayer).smartFilters?.filters ?? []) : []);
|
|
30
|
+
const generator = $derived(layer?.type === 'pixel' ? layer.generator : undefined);
|
|
31
|
+
const canFilter = $derived(!!layer && layer.type !== 'adjustment' && layer.type !== 'annotation');
|
|
32
|
+
const btn = 'rounded border border-slate-700 px-1.5 py-0.5 text-[11px] text-slate-300 hover:text-white disabled:opacity-40';
|
|
33
|
+
const drags = new Set<string>();
|
|
34
|
+
/** Changes during a drag skip the undo stack after the first, so the drag is one step. */
|
|
35
|
+
function commit(gesture: string | null) {
|
|
36
|
+
if (!gesture) return true;
|
|
37
|
+
if (drags.has(gesture)) return false;
|
|
38
|
+
drags.add(gesture);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
function run(work: () => unknown) {
|
|
42
|
+
message = null;
|
|
43
|
+
Promise.resolve()
|
|
44
|
+
.then(work)
|
|
45
|
+
.catch((e: unknown) => (message = e instanceof Error ? e.message : String(e)));
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<!--
|
|
50
|
+
Provider effects for the active layer: its effect smart filters (apply, turn on and off, opacity,
|
|
51
|
+
reorder, change controls, remove) and, for an effect layer, its own controls; plus adding effect
|
|
52
|
+
layers. Applying a filter effect to a pixel layer converts it to a smart object in the same undo
|
|
53
|
+
step. Without a provider it says why nothing can be added.
|
|
54
|
+
-->
|
|
55
|
+
<section class="flex flex-col gap-2 text-xs {className}" aria-label="Provider effects" data-image-provider-effects="">
|
|
56
|
+
<div class="flex items-center gap-1">
|
|
57
|
+
<span class="flex-1 text-[11px] font-semibold uppercase tracking-wider text-slate-200">Effects</span>
|
|
58
|
+
<button type="button" class={btn} disabled={!provider || !canFilter} aria-pressed={browse === 'filter'} onclick={() => (browse = browse === 'filter' ? null : 'filter')}>Apply effect…</button>
|
|
59
|
+
<button type="button" class={btn} disabled={!provider} aria-pressed={browse === 'layer'} onclick={() => (browse = browse === 'layer' ? null : 'layer')}>Effect layer…</button>
|
|
60
|
+
</div>
|
|
61
|
+
{#if !provider}<p class="text-[11px] text-slate-400">No effects provider is registered. Effects in the document are kept.</p>{/if}
|
|
62
|
+
{#if browse === 'filter' && layer}
|
|
63
|
+
{@const id = layer.id}
|
|
64
|
+
<EffectsBrowser {provider} uses={['filter']} title="Filters" onpick={(e) => ((browse = null), run(() => controller.applyEffectFilter(id, e.id)))} />
|
|
65
|
+
{/if}
|
|
66
|
+
{#if browse === 'layer'}
|
|
67
|
+
<EffectsBrowser {provider} uses={['generator', 'overlay', 'title']} title="Generators and overlays" onpick={(e) => ((browse = null), run(() => controller.addEffectLayer(e.id)))} />
|
|
68
|
+
{/if}
|
|
69
|
+
{#if generator && layer}
|
|
70
|
+
{@const id = layer.id}
|
|
71
|
+
<div class="space-y-1" data-effect-part="generator">
|
|
72
|
+
<div class="flex items-center gap-1">
|
|
73
|
+
<span class="flex-1 font-medium text-slate-200">{generator.name ?? generator.effect}</span>
|
|
74
|
+
<button type="button" class={btn} title="Keep the pixels as a plain layer" onclick={() => run(() => controller.rasterizeEffectLayer(id))}>Rasterize</button>
|
|
75
|
+
</div>
|
|
76
|
+
<ImageEffectControls
|
|
77
|
+
{provider}
|
|
78
|
+
effect={generator.effect}
|
|
79
|
+
name={generator.name}
|
|
80
|
+
values={generator.values}
|
|
81
|
+
onchange={(k, v, g) => run(() => controller.updateEffectLayer(id, { values: { [k]: v as never } }, commit(g)))}
|
|
82
|
+
onpreset={(values) => run(() => controller.updateEffectLayer(id, { values: values as never }))}
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
{/if}
|
|
86
|
+
{#if layer && all.some((f) => f.kind === 'effect')}
|
|
87
|
+
{@const id = layer.id}
|
|
88
|
+
<ol class="space-y-1" data-effect-part="filters">
|
|
89
|
+
{#each all as f, index (f.id)}
|
|
90
|
+
{#if f.kind === 'effect'}
|
|
91
|
+
{@const label = f.name ?? f.effect}
|
|
92
|
+
<li class="rounded border border-slate-700 p-1" data-smart-effect={f.id}>
|
|
93
|
+
<div class="flex items-center gap-1">
|
|
94
|
+
<input type="checkbox" checked={f.enabled} aria-label="{label} on" onchange={(e) => {
|
|
95
|
+
const enabled = e.currentTarget.checked;
|
|
96
|
+
run(() => controller.updateEffectFilter(id, f.id, { enabled }));
|
|
97
|
+
}} />
|
|
98
|
+
<button type="button" class="min-w-0 flex-1 truncate text-left text-slate-200" aria-expanded={open === f.id} onclick={() => (open = open === f.id ? null : f.id)}>{label}</button>
|
|
99
|
+
<button type="button" class={btn} disabled={index === all.length - 1} aria-label="Move up" onclick={() => run(() => controller.updateEffectFilter(id, f.id, { index: index + 1 }))}>↑</button>
|
|
100
|
+
<button type="button" class={btn} disabled={index === 0} aria-label="Move down" onclick={() => run(() => controller.updateEffectFilter(id, f.id, { index: index - 1 }))}>↓</button>
|
|
101
|
+
<button type="button" class={btn} aria-label="Remove {label}" onclick={() => run(() => controller.removeSmartFilter(id, f.id))}>✕</button>
|
|
102
|
+
</div>
|
|
103
|
+
{#if open === f.id}
|
|
104
|
+
<div class="mt-1 space-y-2">
|
|
105
|
+
<label class="flex items-center gap-1">
|
|
106
|
+
<span class="w-14 text-[11px] text-slate-300">Opacity</span>
|
|
107
|
+
<input type="range" min="0" max="1" step="0.01" class="flex-1" value={f.opacity} aria-label="{label} opacity" oninput={(e) => {
|
|
108
|
+
const opacity = Number(e.currentTarget.value);
|
|
109
|
+
run(() => controller.updateEffectFilter(id, f.id, { opacity }));
|
|
110
|
+
}} />
|
|
111
|
+
</label>
|
|
112
|
+
<ImageEffectControls
|
|
113
|
+
{provider}
|
|
114
|
+
effect={f.effect}
|
|
115
|
+
name={f.name}
|
|
116
|
+
values={f.values}
|
|
117
|
+
onchange={(k, v, g) => run(() => controller.updateEffectFilter(id, f.id, { values: { [k]: v as never } }, commit(g)))}
|
|
118
|
+
onpreset={(values) => run(() => controller.updateEffectFilter(id, f.id, { values: values as never }))}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
{/if}
|
|
122
|
+
</li>
|
|
123
|
+
{/if}
|
|
124
|
+
{/each}
|
|
125
|
+
</ol>
|
|
126
|
+
{/if}
|
|
127
|
+
{#if !layer}<p class="text-[11px] text-slate-400">Select a layer to apply effects to it.</p>{/if}
|
|
128
|
+
{#if message}<p role="alert" class="text-[11px] text-rose-400">{message}</p>{/if}
|
|
129
|
+
</section>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ImageEditorController } from '@pixerate/editor/image-editor';
|
|
2
|
+
interface Props {
|
|
3
|
+
controller: ImageEditorController;
|
|
4
|
+
class?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const ImageProviderEffectsPanel: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type ImageProviderEffectsPanel = ReturnType<typeof ImageProviderEffectsPanel>;
|
|
8
|
+
export default ImageProviderEffectsPanel;
|
|
9
|
+
//# sourceMappingURL=ImageProviderEffectsPanel.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImageProviderEffectsPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/image-editor/ImageProviderEffectsPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,qBAAqB,EAA0C,MAAM,+BAA+B,CAAC;AAM5H,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA8GH,QAAA,MAAM,yBAAyB,2CAAwC,CAAC;AACxE,KAAK,yBAAyB,GAAG,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAC9E,eAAe,yBAAyB,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { ToolRegistry } from '@pixerate/editor/tools';
|
|
2
|
+
import { ImageEditorController, type ImageEffectsToolProviderOptions, type ImageEditorFit, type ImageEditorOptions, type ImageEditorState, type ImageEditorTool, type CropBox, type AspectRatio, type ImageAdjustments, type DepthMaskOptions, type Annotation, type CreateAnnotationPayload, type ExportOptions, type SerializedImageEditorState, type AIResultOptions, type ImagePoint, type AdjustmentKind, type AdjustmentLayerInit, type AdjustmentLayerUpdate, type ImageHistogram } from '@pixerate/editor/image-editor';
|
|
2
3
|
export declare function createReactiveImageEditor(options?: ImageEditorOptions): {
|
|
3
4
|
readonly controller: ImageEditorController;
|
|
4
5
|
readonly state: ImageEditorState;
|
|
@@ -68,4 +69,11 @@ export declare function createImageHistogram(controller: ImageEditorController):
|
|
|
68
69
|
readonly current: ImageHistogram | null;
|
|
69
70
|
destroy(): void;
|
|
70
71
|
};
|
|
72
|
+
/**
|
|
73
|
+
* Registers the image editor's provider-effect tools (`search_effects`, `describe_effect`,
|
|
74
|
+
* `apply_effect`, `add_effect_layer`, …) for the lifetime of the calling component. They work once
|
|
75
|
+
* an effects provider is registered (see `useEffects`); before that the read tools say so and the
|
|
76
|
+
* write tools refuse.
|
|
77
|
+
*/
|
|
78
|
+
export declare function useImageEffectsTools(registry: ToolRegistry | null | undefined | (() => ToolRegistry | null | undefined), controller: ImageEditorController, options?: ImageEffectsToolProviderOptions): void;
|
|
71
79
|
//# sourceMappingURL=imageEditorState.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"imageEditorState.svelte.d.ts","sourceRoot":"","sources":["../../src/image-editor/imageEditorState.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,
|
|
1
|
+
{"version":3,"file":"imageEditorState.svelte.d.ts","sourceRoot":"","sources":["../../src/image-editor/imageEditorState.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACL,qBAAqB,EAErB,KAAK,+BAA+B,EACpC,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,UAAU,EACf,KAAK,uBAAuB,EAC5B,KAAK,aAAa,EAClB,KAAK,0BAA0B,EAC/B,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACpB,MAAM,+BAA+B,CAAC;AAEvC,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,kBAAuB;;;;IA2BtE,mGAAmG;;;kBAQrF,eAAe;kBAGf,OAAO,GAAG,IAAI;2BAGL,WAAW;;oBAMlB,MAAM;yBAGD,MAAM;;;4BASH,OAAO,CAAC,gBAAgB,CAAC;;IAMjD,oGAAoG;;IAIpG,mEAAmE;6BAC1C,cAAc,GAAG,mBAAmB,UAAU,MAAM;8BAGnD,MAAM,UAAU,qBAAqB;8BAGrC,MAAM;4BAGR,MAAM,WAAW,MAAM;8BAGrB,MAAM,YAAY,OAAO;0BAG7B,OAAO,CAAC,gBAAgB,CAAC;;uBAM5B,uBAAuB;yBAGrB,MAAM,WAAW,OAAO,CAAC,UAAU,CAAC;yBAGpC,MAAM;;yBAMN,MAAM,GAAG,IAAI;;;;oBAYlB,MAAM;qBAGL,MAAM;;kBAMT,MAAM;gBAGR,UAAU;4BAGE,OAAO;gBAGnB,cAAc;mBAGX,MAAM,GAAG,gBAAgB;qBAGvB,aAAa;kBAGhB,aAAa;;mBAMZ,0BAA0B;sBAGvB;QAAE,cAAc,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE;yBAG5C;QAAE,cAAc,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE;2BAG7C,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,SAAS,CAAC;IAGhE,kGAAkG;oCAClE,MAAM,YAAY,eAAe;IAGjE,6GAA6G;4CACrE,MAAM,YAAY,eAAe;IAGzE,8EAA8E;;EAMjF;AAED,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAE/E,6GAA6G;AAC7G,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,qBAAqB;;;EAarE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,GAAG,CAAC,MAAM,YAAY,GAAG,IAAI,GAAG,SAAS,CAAC,EACnF,UAAU,EAAE,qBAAqB,EACjC,OAAO,GAAE,+BAAoC,GAC5C,IAAI,CAMN"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ImageEditorController, } from '@pixerate/editor/image-editor';
|
|
1
|
+
import { ImageEditorController, createImageEffectsToolProvider, } from '@pixerate/editor/image-editor';
|
|
2
2
|
export function createReactiveImageEditor(options = {}) {
|
|
3
3
|
const controller = new ImageEditorController(options);
|
|
4
4
|
let state = $state(controller.getState());
|
|
@@ -186,3 +186,17 @@ export function createImageHistogram(controller) {
|
|
|
186
186
|
},
|
|
187
187
|
};
|
|
188
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Registers the image editor's provider-effect tools (`search_effects`, `describe_effect`,
|
|
191
|
+
* `apply_effect`, `add_effect_layer`, …) for the lifetime of the calling component. They work once
|
|
192
|
+
* an effects provider is registered (see `useEffects`); before that the read tools say so and the
|
|
193
|
+
* write tools refuse.
|
|
194
|
+
*/
|
|
195
|
+
export function useImageEffectsTools(registry, controller, options = {}) {
|
|
196
|
+
$effect(() => {
|
|
197
|
+
const target = typeof registry === 'function' ? registry() : registry;
|
|
198
|
+
if (!target)
|
|
199
|
+
return;
|
|
200
|
+
return target.register(createImageEffectsToolProvider(controller, options));
|
|
201
|
+
});
|
|
202
|
+
}
|
|
@@ -13,6 +13,7 @@ import type BrushOptionsBarComponent from "./BrushOptionsBar.svelte";
|
|
|
13
13
|
import type FreeTransformOptionsBarComponent from "./FreeTransformOptionsBar.svelte";
|
|
14
14
|
import type ShapeOptionsBarComponent from "./ShapeOptionsBar.svelte";
|
|
15
15
|
import type TypeOptionsBarComponent from "./TypeOptionsBar.svelte";
|
|
16
|
+
import type ImageProviderEffectsPanelComponent from "./ImageProviderEffectsPanel.svelte";
|
|
16
17
|
export { default as ImageEditor } from './ImageEditor.svelte';
|
|
17
18
|
export { default as PaintGradientEditor } from './PaintGradientEditor.svelte';
|
|
18
19
|
export { default as Histogram } from './Histogram.svelte';
|
|
@@ -28,6 +29,7 @@ export { default as BrushOptionsBar } from './BrushOptionsBar.svelte';
|
|
|
28
29
|
export { default as FreeTransformOptionsBar } from './FreeTransformOptionsBar.svelte';
|
|
29
30
|
export { default as ShapeOptionsBar } from './ShapeOptionsBar.svelte';
|
|
30
31
|
export { default as TypeOptionsBar } from './TypeOptionsBar.svelte';
|
|
32
|
+
export { default as ImageProviderEffectsPanel } from './ImageProviderEffectsPanel.svelte';
|
|
31
33
|
export * from './imageEditorState.svelte.js';
|
|
32
34
|
export type ImageEditorProps = ComponentProps<typeof ImageEditorComponent>;
|
|
33
35
|
export type HistogramProps = ComponentProps<typeof HistogramComponent>;
|
|
@@ -43,5 +45,6 @@ export type BrushOptionsBarProps = ComponentProps<typeof BrushOptionsBarComponen
|
|
|
43
45
|
export type FreeTransformOptionsBarProps = ComponentProps<typeof FreeTransformOptionsBarComponent>;
|
|
44
46
|
export type ShapeOptionsBarProps = ComponentProps<typeof ShapeOptionsBarComponent>;
|
|
45
47
|
export type TypeOptionsBarProps = ComponentProps<typeof TypeOptionsBarComponent>;
|
|
48
|
+
export type ImageProviderEffectsPanelProps = ComponentProps<typeof ImageProviderEffectsPanelComponent>;
|
|
46
49
|
export { applyRegionMosaic, applyRegionBlur, resolveMosaicRegionBounds, type MosaicRegion, type MosaicImageDataLike } from '@pixerate/editor/image-editor';
|
|
47
50
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/image-editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,oBAAoB,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,kBAAkB,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,8BAA8B,MAAM,gCAAgC,CAAC;AACjF,OAAO,KAAK,qBAAqB,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,sBAAsB,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,4BAA4B,MAAM,8BAA8B,CAAC;AAC7E,OAAO,KAAK,wBAAwB,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,oBAAoB,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,0BAA0B,MAAM,4BAA4B,CAAC;AACzE,OAAO,KAAK,0BAA0B,MAAM,4BAA4B,CAAC;AACzE,OAAO,KAAK,wBAAwB,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,gCAAgC,MAAM,kCAAkC,CAAC;AACrF,OAAO,KAAK,wBAAwB,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,uBAAuB,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/image-editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,oBAAoB,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,kBAAkB,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,8BAA8B,MAAM,gCAAgC,CAAC;AACjF,OAAO,KAAK,qBAAqB,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,sBAAsB,MAAM,wBAAwB,CAAC;AACjE,OAAO,KAAK,4BAA4B,MAAM,8BAA8B,CAAC;AAC7E,OAAO,KAAK,wBAAwB,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,oBAAoB,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,0BAA0B,MAAM,4BAA4B,CAAC;AACzE,OAAO,KAAK,0BAA0B,MAAM,4BAA4B,CAAC;AACzE,OAAO,KAAK,wBAAwB,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,gCAAgC,MAAM,kCAAkC,CAAC;AACrF,OAAO,KAAK,wBAAwB,MAAM,0BAA0B,CAAC;AACrE,OAAO,KAAK,uBAAuB,MAAM,yBAAyB,CAAC;AACnE,OAAO,KAAK,kCAAkC,MAAM,oCAAoC,CAAC;AAEzF,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,uBAAuB,EAAE,MAAM,kCAAkC,CAAC;AACtF,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AAC1F,cAAc,8BAA8B,CAAC;AAE7C,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAC3E,MAAM,MAAM,cAAc,GAAG,cAAc,CAAC,OAAO,kBAAkB,CAAC,CAAC;AACvE,MAAM,MAAM,0BAA0B,GAAG,cAAc,CAAC,OAAO,8BAA8B,CAAC,CAAC;AAC/F,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC7E,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAC/E,MAAM,MAAM,wBAAwB,GAAG,cAAc,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAC3F,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,wBAAwB,CAAC,CAAC;AACnF,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAC3E,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC,OAAO,0BAA0B,CAAC,CAAC;AACvF,MAAM,MAAM,sBAAsB,GAAG,cAAc,CAAC,OAAO,0BAA0B,CAAC,CAAC;AACvF,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,wBAAwB,CAAC,CAAC;AACnF,MAAM,MAAM,4BAA4B,GAAG,cAAc,CAAC,OAAO,gCAAgC,CAAC,CAAC;AACnG,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,wBAAwB,CAAC,CAAC;AACnF,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,uBAAuB,CAAC,CAAC;AACjF,MAAM,MAAM,8BAA8B,GAAG,cAAc,CAAC,OAAO,kCAAkC,CAAC,CAAC;AAEvG,OAAO,EACN,iBAAiB,EACjB,eAAe,EACf,yBAAyB,EACzB,KAAK,YAAY,EACjB,KAAK,mBAAmB,EACxB,MAAM,+BAA+B,CAAC"}
|
|
@@ -13,5 +13,6 @@ export { default as BrushOptionsBar } from './BrushOptionsBar.svelte';
|
|
|
13
13
|
export { default as FreeTransformOptionsBar } from './FreeTransformOptionsBar.svelte';
|
|
14
14
|
export { default as ShapeOptionsBar } from './ShapeOptionsBar.svelte';
|
|
15
15
|
export { default as TypeOptionsBar } from './TypeOptionsBar.svelte';
|
|
16
|
+
export { default as ImageProviderEffectsPanel } from './ImageProviderEffectsPanel.svelte';
|
|
16
17
|
export * from './imageEditorState.svelte.js';
|
|
17
18
|
export { applyRegionMosaic, applyRegionBlur, resolveMosaicRegionBounds } from '@pixerate/editor/image-editor';
|
|
@@ -109,7 +109,11 @@
|
|
|
109
109
|
function setTransition(type: string) {
|
|
110
110
|
if (!clip) return;
|
|
111
111
|
if (!type) apply({ op: 'set_transition', clip: clip.id, transition: null }, 'Remove transition');
|
|
112
|
-
else
|
|
112
|
+
else {
|
|
113
|
+
// A provider transition's effect does not carry over to a built-in type.
|
|
114
|
+
const { effect: _effect, ...rest } = clip.transition ?? { duration: 500 };
|
|
115
|
+
apply({ op: 'set_transition', clip: clip.id, transition: { ...rest, type: type as VideoTransitionType, duration: clip.transition?.duration ?? 500 } }, 'Set transition');
|
|
116
|
+
}
|
|
113
117
|
}
|
|
114
118
|
function gradeValue(c: VideoGradeControl, raw: string) {
|
|
115
119
|
if (!clip) return;
|
|
@@ -318,6 +322,9 @@
|
|
|
318
322
|
<span class="truncate" title="A compound clip plays its sequence as one picture and sound; its clips cannot be edited here yet" data-field="sequence">{sequence?.name ?? clip.sequence} · from {formatVideoSeconds(clip.in ?? 0)}</span>
|
|
319
323
|
<span class="text-muted-foreground">Muted</span>
|
|
320
324
|
<input type="checkbox" class="justify-self-start" aria-label="Muted" {disabled} checked={!!clip.muted} onchange={(e) => set('muted', e.currentTarget.checked || undefined, e.currentTarget.checked ? 'Mute clip' : 'Unmute clip')} data-field="muted" />
|
|
325
|
+
{:else if clip.type === 'effect'}
|
|
326
|
+
<span class="text-muted-foreground">Effect</span>
|
|
327
|
+
<span class="truncate" title="Change its controls in the Effects panel" data-field="effect">{clip.name ?? clip.effect}</span>
|
|
321
328
|
{:else}
|
|
322
329
|
<span class="text-muted-foreground">Colour</span>
|
|
323
330
|
<input class={input} aria-label="Clip colour" {disabled} value={clip.color} onchange={(e) => text('color', e.currentTarget.value, 'Change colour', true)} data-field="color" />
|
|
@@ -326,8 +333,8 @@
|
|
|
326
333
|
<div class="flex items-center gap-1">
|
|
327
334
|
<select class={input} aria-label="Transition" {disabled} value={clip.transition?.type ?? ''} onchange={(e) => setTransition(e.currentTarget.value)} data-field="transition">
|
|
328
335
|
<option value="">none</option>
|
|
329
|
-
{#each VIDEO_TRANSITION_TYPES as t (t)}
|
|
330
|
-
<option value={t}>{VIDEO_TRANSITION_LABELS[t]}</option>
|
|
336
|
+
{#each VIDEO_TRANSITION_TYPES.filter((t) => t !== 'effect' || clip?.transition?.type === 'effect') as t (t)}
|
|
337
|
+
<option value={t}>{t === 'effect' ? `Effect: ${clip.transition?.effect?.name ?? clip.transition?.effect?.effect}` : VIDEO_TRANSITION_LABELS[t]}</option>
|
|
331
338
|
{/each}
|
|
332
339
|
</select>
|
|
333
340
|
{#if clip.transition}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClipInspector.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/ClipInspector.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EA2BH,KAAK,qBAAqB,EAQ3B,MAAM,+BAA+B,CAAC;AAIvC,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;
|
|
1
|
+
{"version":3,"file":"ClipInspector.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/ClipInspector.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EA2BH,KAAK,qBAAqB,EAQ3B,MAAM,+BAA+B,CAAC;AAIvC,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA4TH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { EffectProvider } from '@pixerate/editor/effects';
|
|
3
|
+
import { isVideoKeyframes, type VideoEffectValues } from '@pixerate/editor/video-editor';
|
|
4
|
+
import EffectParameters from '../effects/EffectParameters.svelte';
|
|
5
|
+
import { effectDescriptionOf } from '../effects/effects.svelte.js';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
provider: EffectProvider | null;
|
|
9
|
+
effect: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
values: VideoEffectValues | undefined;
|
|
12
|
+
disabled: boolean;
|
|
13
|
+
onchange: (control: string, value: unknown, gesture: string | null) => void;
|
|
14
|
+
onpreset: (values: Readonly<Record<string, unknown>>) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let { provider, effect, name, values, disabled, onchange, onpreset }: Props = $props();
|
|
18
|
+
const loaded = effectDescriptionOf(() => provider, () => effect);
|
|
19
|
+
const keyed = $derived(new Set(Object.entries(values ?? {}).filter(([, v]) => isVideoKeyframes(v)).map(([k]) => k)));
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<!-- One effect's controls, loaded from the provider; a note when the provider does not have it. -->
|
|
23
|
+
{#if !provider}
|
|
24
|
+
<p class="text-[11px] text-muted-foreground">No effects provider: "{name ?? effect}" is kept and shown as a placeholder.</p>
|
|
25
|
+
{:else if loaded.error}
|
|
26
|
+
<p class="text-[11px] text-amber-500">{loaded.error}</p>
|
|
27
|
+
{:else if !loaded.description}
|
|
28
|
+
<p class="text-[11px] text-muted-foreground">Loading…</p>
|
|
29
|
+
{:else}
|
|
30
|
+
{@const description = loaded.description}
|
|
31
|
+
<EffectParameters {description} values={values ?? {}} keyframed={keyed} {disabled} {onchange} onreset={(k) => onchange(k, description.defaults[k], null)} {onpreset} />
|
|
32
|
+
{/if}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { EffectProvider } from '@pixerate/editor/effects';
|
|
2
|
+
import { type VideoEffectValues } from '@pixerate/editor/video-editor';
|
|
3
|
+
interface Props {
|
|
4
|
+
provider: EffectProvider | null;
|
|
5
|
+
effect: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
values: VideoEffectValues | undefined;
|
|
8
|
+
disabled: boolean;
|
|
9
|
+
onchange: (control: string, value: unknown, gesture: string | null) => void;
|
|
10
|
+
onpreset: (values: Readonly<Record<string, unknown>>) => void;
|
|
11
|
+
}
|
|
12
|
+
declare const EffectControls: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type EffectControls = ReturnType<typeof EffectControls>;
|
|
14
|
+
export default EffectControls;
|
|
15
|
+
//# sourceMappingURL=EffectControls.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EffectControls.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/EffectControls.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAKvF,UAAU,KAAK;IACb,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC5E,QAAQ,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC;CAC/D;AA2BH,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
import VideoTimeline from './VideoTimeline.svelte';
|
|
10
10
|
import VideoTransport from './VideoTransport.svelte';
|
|
11
11
|
import { videoEditorStateOf } from './createVideoEditor.svelte.js';
|
|
12
|
+
import type { EffectProvider } from '@pixerate/editor/effects';
|
|
13
|
+
import { useEffects } from '../effects/effects.svelte.js';
|
|
14
|
+
import VideoEffectsPanel from './VideoEffectsPanel.svelte';
|
|
12
15
|
|
|
13
16
|
interface Props {
|
|
14
17
|
/** The editor to show (default: one made from `options`, destroyed with the component). */
|
|
@@ -28,16 +31,26 @@
|
|
|
28
31
|
showTranscript?: boolean;
|
|
29
32
|
/** Show colour scopes (histogram, luma waveform, vectorscope) of the preview under it (default false). */
|
|
30
33
|
showScopes?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* An effects provider (see `@pixerate/editor/effects`), registered with the controller while
|
|
36
|
+
* mounted: clip effects, effect transitions and effect clips, with the effects panel. Optional:
|
|
37
|
+
* without one the editor is as before (effects in a project show as placeholders).
|
|
38
|
+
*/
|
|
39
|
+
effects?: EffectProvider | null;
|
|
40
|
+
/** Show the effects panel (default: when a provider is given or the project has effects). */
|
|
41
|
+
showEffects?: boolean;
|
|
31
42
|
class?: string;
|
|
32
43
|
}
|
|
33
44
|
|
|
34
|
-
let { controller, options, editable = true, pxPerSecond = $bindable(100), showIssues = true, showTranscript = true, showScopes = false, class: className = '' }: Props = $props();
|
|
45
|
+
let { controller, options, editable = true, pxPerSecond = $bindable(100), showIssues = true, showTranscript = true, showScopes = false, effects, showEffects, class: className = '' }: Props = $props();
|
|
35
46
|
let owned: VideoEditorController | null = null;
|
|
36
47
|
// A controller is made only when none is passed, and kept for the component's lifetime.
|
|
37
48
|
const ctl = $derived(controller ?? (owned ??= new VideoEditorController(options)));
|
|
38
49
|
onDestroy(() => owned?.destroy());
|
|
39
50
|
const view = videoEditorStateOf(() => ctl);
|
|
40
51
|
const editor = $derived(view.current);
|
|
52
|
+
useEffects(() => ctl, () => effects);
|
|
53
|
+
const effectsShown = $derived(showEffects ?? !!(editor.effectsProvider || editor.project.tracks.some((t) => t.clips.some((c) => c.type === 'effect' || c.effects?.length || c.transition?.type === 'effect'))));
|
|
41
54
|
</script>
|
|
42
55
|
|
|
43
56
|
{#snippet issueList()}
|
|
@@ -72,7 +85,10 @@
|
|
|
72
85
|
<VideoTransport controller={ctl} />
|
|
73
86
|
{#if showScopes}<VideoScopes controller={ctl} />{/if}
|
|
74
87
|
</div>
|
|
75
|
-
<
|
|
88
|
+
<div class="flex min-w-0 flex-col gap-3">
|
|
89
|
+
<ClipInspector controller={ctl} class="min-w-0" />
|
|
90
|
+
{#if effectsShown}<VideoEffectsPanel controller={ctl} class="min-w-0" />{/if}
|
|
91
|
+
</div>
|
|
76
92
|
<div class="flex min-w-0 flex-col gap-2 lg:col-span-3">
|
|
77
93
|
<VideoTimeline controller={ctl} bind:pxPerSecond />
|
|
78
94
|
{#if showTranscript}<TranscriptPanel controller={ctl} />{/if}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { VideoEditorController, type VideoEditorOptions } from '@pixerate/editor/video-editor';
|
|
2
|
+
import type { EffectProvider } from '@pixerate/editor/effects';
|
|
2
3
|
interface Props {
|
|
3
4
|
/** The editor to show (default: one made from `options`, destroyed with the component). */
|
|
4
5
|
controller?: VideoEditorController;
|
|
@@ -17,6 +18,14 @@ interface Props {
|
|
|
17
18
|
showTranscript?: boolean;
|
|
18
19
|
/** Show colour scopes (histogram, luma waveform, vectorscope) of the preview under it (default false). */
|
|
19
20
|
showScopes?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* An effects provider (see `@pixerate/editor/effects`), registered with the controller while
|
|
23
|
+
* mounted: clip effects, effect transitions and effect clips, with the effects panel. Optional:
|
|
24
|
+
* without one the editor is as before (effects in a project show as placeholders).
|
|
25
|
+
*/
|
|
26
|
+
effects?: EffectProvider | null;
|
|
27
|
+
/** Show the effects panel (default: when a provider is given or the project has effects). */
|
|
28
|
+
showEffects?: boolean;
|
|
20
29
|
class?: string;
|
|
21
30
|
}
|
|
22
31
|
declare const VideoEditor: import("svelte").Component<Props, {}, "pxPerSecond">;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/VideoEditor.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,qBAAqB,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"VideoEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/VideoEditor.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,qBAAqB,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAS/F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAK7D,UAAU,KAAK;IACb,2FAA2F;IAC3F,UAAU,CAAC,EAAE,qBAAqB,CAAC;IACnC,8EAA8E;IAC9E,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oEAAoE;IACpE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,0GAA0G;IAC1G,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IAChC,6FAA6F;IAC7F,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAsEH,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { findVideoClip, isVideoKeyframes, type VideoEditorController, type VideoEffectEditResult, type VideoEffectValues } from '@pixerate/editor/video-editor';
|
|
3
|
+
import EffectsBrowser from '../effects/EffectsBrowser.svelte';
|
|
4
|
+
import EffectControls from './EffectControls.svelte';
|
|
5
|
+
import { videoEditorStateOf } from './createVideoEditor.svelte.js';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
controller: VideoEditorController;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { controller, class: className = '' }: Props = $props();
|
|
13
|
+
const view = videoEditorStateOf(() => controller);
|
|
14
|
+
const ed = $derived(view.current);
|
|
15
|
+
const provider = $derived(ed.effectsProvider);
|
|
16
|
+
let browse = $state<null | 'clip' | 'transition' | 'generator'>(null);
|
|
17
|
+
let message = $state<string | null>(null);
|
|
18
|
+
let open = $state<string | null>(null);
|
|
19
|
+
const at = $derived(ed.selection[0] ? findVideoClip(ed.project, ed.selection[0]) : null);
|
|
20
|
+
const clip = $derived(at?.clip ?? null);
|
|
21
|
+
const disabled = $derived(ed.readOnly);
|
|
22
|
+
const btn = 'rounded border border-border px-1.5 py-0.5 text-[11px] disabled:opacity-40';
|
|
23
|
+
|
|
24
|
+
function report(r: VideoEffectEditResult | Promise<VideoEffectEditResult>) {
|
|
25
|
+
void Promise.resolve(r).then((x) => (message = x.ok ? null : x.error));
|
|
26
|
+
}
|
|
27
|
+
function addEffectClip(effect: string) {
|
|
28
|
+
const track = at && at.track.kind === 'video' ? at.track.id : ed.project.tracks.find((t) => t.kind === 'video')?.id;
|
|
29
|
+
if (!track) return (message = 'Add a video track first.');
|
|
30
|
+
report(
|
|
31
|
+
controller.addEffectClip(track, effect, { start: Math.round(ed.time), mode: 'overwrite' }).then((r) => {
|
|
32
|
+
if (r.ok && r.clip) controller.select([r.clip]);
|
|
33
|
+
return r;
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const merge = (gesture: string | null) => ({ merge: gesture ?? undefined });
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<!--
|
|
41
|
+
Provider effects for the selected clip: its effect stack (add, turn on and off, reorder, change
|
|
42
|
+
controls, remove), its effect transition, and an effect clip's own controls; plus adding effect
|
|
43
|
+
clips at the playhead. Every change is one undo step (a slider drag is one). Without a provider it
|
|
44
|
+
lists what the project holds and says why nothing can be added.
|
|
45
|
+
-->
|
|
46
|
+
<section class="flex flex-col gap-2 rounded-md border border-border p-2 text-xs {className}" aria-label="Effects" data-video-effects="">
|
|
47
|
+
<div class="flex items-center gap-1">
|
|
48
|
+
<h3 class="flex-1 font-semibold">Effects</h3>
|
|
49
|
+
<button type="button" class={btn} disabled={disabled || !provider} aria-pressed={browse === 'generator'} onclick={() => (browse = browse === 'generator' ? null : 'generator')}>Add effect clip</button>
|
|
50
|
+
</div>
|
|
51
|
+
{#if !provider}
|
|
52
|
+
<p class="text-[11px] text-muted-foreground">No effects provider is registered. Effects in the project are kept and drawn as placeholders.</p>
|
|
53
|
+
{/if}
|
|
54
|
+
{#if browse === 'generator'}
|
|
55
|
+
<EffectsBrowser {provider} uses={['generator', 'title', 'overlay']} title="Generators and titles" onpick={(e) => ((browse = null), addEffectClip(e.id))} />
|
|
56
|
+
{/if}
|
|
57
|
+
{#if !clip}
|
|
58
|
+
<p class="text-[11px] text-muted-foreground">Select a clip to see its effects.</p>
|
|
59
|
+
{:else}
|
|
60
|
+
{#if clip.type === 'effect'}
|
|
61
|
+
{@const c = clip}
|
|
62
|
+
<div class="space-y-1" data-effect-part="generator">
|
|
63
|
+
<div class="font-medium">{c.name ?? c.effect}</div>
|
|
64
|
+
<EffectControls
|
|
65
|
+
{provider}
|
|
66
|
+
effect={c.effect}
|
|
67
|
+
name={c.name}
|
|
68
|
+
values={c.values}
|
|
69
|
+
{disabled}
|
|
70
|
+
onchange={(k, v, g) => report(controller.updateEffectValues(c.id, 'generator', { [k]: v } as VideoEffectValues, merge(g)))}
|
|
71
|
+
onpreset={(values) => report(controller.updateEffectValues(c.id, 'generator', values as VideoEffectValues))}
|
|
72
|
+
/>
|
|
73
|
+
</div>
|
|
74
|
+
{/if}
|
|
75
|
+
<div class="space-y-1" data-effect-part="transition">
|
|
76
|
+
<div class="flex items-center gap-1">
|
|
77
|
+
<span class="flex-1 font-medium">Transition in</span>
|
|
78
|
+
{#if clip.transition?.type === 'effect'}
|
|
79
|
+
<button type="button" class={btn} {disabled} onclick={() => report(controller.apply({ op: 'set_transition', clip: clip!.id, transition: null }, 'Remove transition') as VideoEffectEditResult)}>Remove</button>
|
|
80
|
+
{:else}
|
|
81
|
+
<button type="button" class={btn} disabled={disabled || !provider} aria-pressed={browse === 'transition'} onclick={() => (browse = browse === 'transition' ? null : 'transition')}>Effect transition…</button>
|
|
82
|
+
{/if}
|
|
83
|
+
</div>
|
|
84
|
+
{#if browse === 'transition'}
|
|
85
|
+
<EffectsBrowser {provider} uses={['transition']} title="Transitions" onpick={(e) => ((browse = null), report(controller.setEffectTransition(clip!.id, e.id)))} />
|
|
86
|
+
{/if}
|
|
87
|
+
{#if clip.transition?.type === 'effect' && clip.transition.effect}
|
|
88
|
+
{@const t = clip.transition.effect}
|
|
89
|
+
<EffectControls
|
|
90
|
+
{provider}
|
|
91
|
+
effect={t.effect}
|
|
92
|
+
name={t.name}
|
|
93
|
+
values={t.values}
|
|
94
|
+
{disabled}
|
|
95
|
+
onchange={(k, v, g) => report(controller.updateEffectValues(clip!.id, 'transition', { [k]: v } as VideoEffectValues, merge(g)))}
|
|
96
|
+
onpreset={(values) => report(controller.updateEffectValues(clip!.id, 'transition', values as VideoEffectValues))}
|
|
97
|
+
/>
|
|
98
|
+
{/if}
|
|
99
|
+
</div>
|
|
100
|
+
<div class="space-y-1" data-effect-part="stack">
|
|
101
|
+
<div class="flex items-center gap-1">
|
|
102
|
+
<span class="flex-1 font-medium">On this clip</span>
|
|
103
|
+
<button type="button" class={btn} disabled={disabled || !provider} aria-pressed={browse === 'clip'} onclick={() => (browse = browse === 'clip' ? null : 'clip')}>Add effect…</button>
|
|
104
|
+
</div>
|
|
105
|
+
{#if browse === 'clip'}
|
|
106
|
+
<EffectsBrowser {provider} uses={['filter', 'overlay']} title="Filters and overlays" onpick={(e) => ((browse = null), report(controller.applyClipEffect(clip!.id, e.id)))} />
|
|
107
|
+
{/if}
|
|
108
|
+
<ol class="space-y-1">
|
|
109
|
+
{#each clip.effects ?? [] as e, i (e.id)}
|
|
110
|
+
{@const label = e.name ?? e.effect}
|
|
111
|
+
{@const count = clip.effects?.length ?? 0}
|
|
112
|
+
<li class="rounded border border-border p-1" data-clip-effect={e.id}>
|
|
113
|
+
<div class="flex items-center gap-1">
|
|
114
|
+
<input type="checkbox" checked={e.enabled !== false} {disabled} aria-label="{label} on" onchange={(ev) => report(controller.updateClipEffect(clip!.id, e.id, { enabled: ev.currentTarget.checked }))} />
|
|
115
|
+
<button type="button" class="min-w-0 flex-1 truncate text-left" aria-expanded={open === e.id} onclick={() => (open = open === e.id ? null : e.id)}>{label}</button>
|
|
116
|
+
<button type="button" class={btn} disabled={disabled || i === 0} aria-label="Move up" onclick={() => report(controller.updateClipEffect(clip!.id, e.id, { index: i - 1 }))}>↑</button>
|
|
117
|
+
<button type="button" class={btn} disabled={disabled || i === count - 1} aria-label="Move down" onclick={() => report(controller.updateClipEffect(clip!.id, e.id, { index: i + 1 }))}>↓</button>
|
|
118
|
+
<button type="button" class={btn} {disabled} aria-label="Remove {label}" onclick={() => report(controller.removeClipEffect(clip!.id, e.id))}>✕</button>
|
|
119
|
+
</div>
|
|
120
|
+
{#if open === e.id}
|
|
121
|
+
<div class="mt-1 space-y-2">
|
|
122
|
+
<label class="flex items-center gap-1">
|
|
123
|
+
<span class="w-14 text-[11px]">Opacity</span>
|
|
124
|
+
<input
|
|
125
|
+
type="range"
|
|
126
|
+
min="0"
|
|
127
|
+
max="1"
|
|
128
|
+
step="0.01"
|
|
129
|
+
class="flex-1"
|
|
130
|
+
disabled={disabled || isVideoKeyframes(e.opacity)}
|
|
131
|
+
value={typeof e.opacity === 'number' ? e.opacity : 1}
|
|
132
|
+
aria-label="{label} opacity"
|
|
133
|
+
oninput={(ev) => report(controller.updateClipEffect(clip!.id, e.id, { opacity: Number(ev.currentTarget.value) }, { merge: `opacity:${clip!.id}:${e.id}` }))}
|
|
134
|
+
/>
|
|
135
|
+
</label>
|
|
136
|
+
<EffectControls
|
|
137
|
+
{provider}
|
|
138
|
+
effect={e.effect}
|
|
139
|
+
name={e.name}
|
|
140
|
+
values={e.values}
|
|
141
|
+
{disabled}
|
|
142
|
+
onchange={(k, v, g) => report(controller.updateClipEffect(clip!.id, e.id, { values: { [k]: v as never } }, merge(g)))}
|
|
143
|
+
onpreset={(values) => report(controller.updateClipEffect(clip!.id, e.id, { values: values as never }))}
|
|
144
|
+
/>
|
|
145
|
+
</div>
|
|
146
|
+
{/if}
|
|
147
|
+
</li>
|
|
148
|
+
{/each}
|
|
149
|
+
</ol>
|
|
150
|
+
{#if !clip.effects?.length}<p class="text-[11px] text-muted-foreground">No effects on this clip.</p>{/if}
|
|
151
|
+
</div>
|
|
152
|
+
{/if}
|
|
153
|
+
{#if message}<p role="alert" class="text-[11px] text-destructive">{message}</p>{/if}
|
|
154
|
+
</section>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type VideoEditorController } from '@pixerate/editor/video-editor';
|
|
2
|
+
interface Props {
|
|
3
|
+
controller: VideoEditorController;
|
|
4
|
+
class?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const VideoEffectsPanel: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type VideoEffectsPanel = ReturnType<typeof VideoEffectsPanel>;
|
|
8
|
+
export default VideoEffectsPanel;
|
|
9
|
+
//# sourceMappingURL=VideoEffectsPanel.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VideoEffectsPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/VideoEffectsPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAmC,KAAK,qBAAqB,EAAsD,MAAM,+BAA+B,CAAC;AAM9J,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAmHH,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { VideoEditorController, type VideoEditorOptions, type VideoEditorState, type VideoEditorToolProviderOptions, type VideoEditResult } from "@pixerate/editor/video-editor";
|
|
1
|
+
import { VideoEditorController, type VideoEffectsToolProviderOptions, type VideoEditorOptions, type VideoEditorState, type VideoEditorToolProviderOptions, type VideoEditResult } from "@pixerate/editor/video-editor";
|
|
2
2
|
import type { ToolRegistry } from "@pixerate/editor/tools";
|
|
3
3
|
export interface SvelteVideoEditor {
|
|
4
4
|
readonly controller: VideoEditorController;
|
|
@@ -37,4 +37,11 @@ export declare function videoEditorStateOf(getController: () => VideoEditorContr
|
|
|
37
37
|
* initialisation). Pass a getter to follow a changing registry.
|
|
38
38
|
*/
|
|
39
39
|
export declare function useVideoEditorTools(registry: ToolRegistry | null | undefined | (() => ToolRegistry | null | undefined), controller: VideoEditorController, options?: VideoEditorToolProviderOptions): void;
|
|
40
|
+
/**
|
|
41
|
+
* Registers the video editor's effect tools (`search_effects`, `describe_effect`, `apply_effect`,
|
|
42
|
+
* `add_video_effect_clip`, `set_video_effect_transition`, …) for the lifetime of the calling
|
|
43
|
+
* component. They work once an effects provider is registered (see `useEffects`); before that the
|
|
44
|
+
* read tools say so and the write tools refuse.
|
|
45
|
+
*/
|
|
46
|
+
export declare function useVideoEffectsTools(registry: ToolRegistry | null | undefined | (() => ToolRegistry | null | undefined), controller: VideoEditorController, options?: VideoEffectsToolProviderOptions): void;
|
|
40
47
|
//# sourceMappingURL=createVideoEditor.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createVideoEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/createVideoEditor.svelte.ts"],"names":[],"mappings":"AACA,OAAO,EACL,qBAAqB,
|
|
1
|
+
{"version":3,"file":"createVideoEditor.svelte.d.ts","sourceRoot":"","sources":["../../src/video-editor/createVideoEditor.svelte.ts"],"names":[],"mappings":"AACA,OAAO,EACL,qBAAqB,EAGrB,KAAK,+BAA+B,EACpC,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,8BAA8B,EACnC,KAAK,eAAe,EACrB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC;IAC3C,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAC;IACjC,IAAI,IAAI,IAAI,CAAC;IACb,KAAK,IAAI,IAAI,CAAC;IACd,MAAM,IAAI,IAAI,CAAC;IACf,+BAA+B;IAC/B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,yEAAyE;IACzE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,IAAI,IAAI,CAAC;IACb,IAAI,IAAI,IAAI,CAAC;IACb,sFAAsF;IACtF,eAAe,IAAI,eAAe,CAAC;IACnC,uEAAuE;IACvE,eAAe,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,eAAe,CAAC;IACjE,qFAAqF;IACrF,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,mBAAmB,GAAE,qBAAqB,GAAG,kBAAuB,GAAG,iBAAiB,CA0BzH;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,qBAAqB,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,CAgBrH;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,GAAG,CAAC,MAAM,YAAY,GAAG,IAAI,GAAG,SAAS,CAAC,EACnF,UAAU,EAAE,qBAAqB,EACjC,OAAO,GAAE,8BAAmC,GAC3C,IAAI,CAMN;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,GAAG,CAAC,MAAM,YAAY,GAAG,IAAI,GAAG,SAAS,CAAC,EACnF,UAAU,EAAE,qBAAqB,EACjC,OAAO,GAAE,+BAAoC,GAC5C,IAAI,CAMN"}
|