@pixerate/editor-svelte 0.196.2 → 0.197.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/AnimationEffectControls.svelte +48 -2
- package/dist/animation-editor/AnimationEffectControls.svelte.d.ts +3 -0
- package/dist/animation-editor/AnimationEffectsPanel.svelte +53 -6
- package/dist/canvas/createCanvasDraw.svelte.d.ts +12 -2
- package/dist/canvas/createCanvasDraw.svelte.js +25 -2
- package/dist/vector/VectorAppearancePanel.svelte +6 -0
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { AnimationEditorController, MotionEffectResult } from '@pixerate/editor/animation-editor';
|
|
3
|
+
import { pathControlsOf } from '@pixerate/editor/effects';
|
|
3
4
|
import EffectParameters from '../effects/EffectParameters.svelte';
|
|
4
5
|
import { effectDescriptionOf } from '../effects/effects.svelte.js';
|
|
5
6
|
|
|
@@ -8,11 +9,20 @@
|
|
|
8
9
|
instance: string;
|
|
9
10
|
effect: string;
|
|
10
11
|
values: Readonly<Record<string, unknown>>;
|
|
12
|
+
pathNodes?: readonly string[];
|
|
13
|
+
defaultPathNode?: string;
|
|
14
|
+
disabled?: boolean;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
let { controller, instance, effect, values }: Props = $props();
|
|
17
|
+
let { controller, instance, effect, values, pathNodes = [], defaultPathNode = '', disabled = false }: Props = $props();
|
|
14
18
|
const loaded = effectDescriptionOf(() => controller.effectsProvider, () => effect);
|
|
15
19
|
let message = $state<string | null>(null);
|
|
20
|
+
let pathNode = $state('');
|
|
21
|
+
let words = $state('');
|
|
22
|
+
const chosenPathNode = $derived(
|
|
23
|
+
pathNode && pathNodes.includes(pathNode) ? pathNode : defaultPathNode && pathNodes.includes(defaultPathNode) ? defaultPathNode : pathNodes[0] ?? ''
|
|
24
|
+
);
|
|
25
|
+
const btn = 'rounded border border-border px-1.5 py-0.5 text-[11px] disabled:opacity-40';
|
|
16
26
|
const report = (r: Promise<MotionEffectResult>) => void r.then((x) => (message = x.ok ? null : x.error));
|
|
17
27
|
</script>
|
|
18
28
|
|
|
@@ -24,9 +34,45 @@
|
|
|
24
34
|
{:else if !loaded.description}
|
|
25
35
|
<p class="text-[11px] text-muted-foreground">Loading…</p>
|
|
26
36
|
{:else}
|
|
37
|
+
{@const description = loaded.description}
|
|
38
|
+
{@const pathControls = description.kind === 'vector' ? pathControlsOf(description) : []}
|
|
39
|
+
{#if pathControls.length > 0}
|
|
40
|
+
<div class="mb-1 flex flex-wrap items-center gap-1" data-effect-part="path">
|
|
41
|
+
<span class="text-muted-foreground">Path</span>
|
|
42
|
+
<select
|
|
43
|
+
class="rounded border border-border bg-transparent px-1 py-0.5 text-[11px]"
|
|
44
|
+
aria-label="Path node"
|
|
45
|
+
value={chosenPathNode}
|
|
46
|
+
disabled={disabled || !pathNodes.length}
|
|
47
|
+
onchange={(e) => (pathNode = e.currentTarget.value)}
|
|
48
|
+
>
|
|
49
|
+
<option value="">Path node…</option>
|
|
50
|
+
{#each pathNodes as id (id)}<option value={id}>{id}</option>{/each}
|
|
51
|
+
</select>
|
|
52
|
+
<button type="button" class={btn} disabled={disabled || !chosenPathNode} onclick={() => report(controller.setVectorEffectPath(instance, { node: chosenPathNode }))}>
|
|
53
|
+
Use path node
|
|
54
|
+
</button>
|
|
55
|
+
{#if controller.effectsProvider.pathFromText}
|
|
56
|
+
<input
|
|
57
|
+
class="w-24 rounded border border-border bg-transparent px-1 py-0.5"
|
|
58
|
+
aria-label="Words to write along the path"
|
|
59
|
+
placeholder="Words"
|
|
60
|
+
bind:value={words}
|
|
61
|
+
{disabled}
|
|
62
|
+
/>
|
|
63
|
+
<button type="button" class={btn} disabled={disabled || !words.trim()} onclick={() => report(controller.setVectorEffectPath(instance, { text: words }))}>
|
|
64
|
+
Write
|
|
65
|
+
</button>
|
|
66
|
+
{/if}
|
|
67
|
+
<button type="button" class={btn} {disabled} onclick={() => report(controller.updateMotionEffect(instance, { [pathControls[0]]: '' }))}>
|
|
68
|
+
Clear path
|
|
69
|
+
</button>
|
|
70
|
+
</div>
|
|
71
|
+
{/if}
|
|
27
72
|
<EffectParameters
|
|
28
|
-
|
|
73
|
+
{description}
|
|
29
74
|
{values}
|
|
75
|
+
{disabled}
|
|
30
76
|
onchange={(k, v, g) => report(controller.updateMotionEffect(instance, { [k]: v as never }, { merge: g ?? undefined }))}
|
|
31
77
|
onreset={(k) => report(controller.updateMotionEffect(instance, { [k]: null }))}
|
|
32
78
|
onpreset={(v) => report(controller.updateMotionEffect(instance, v as never))}
|
|
@@ -4,6 +4,9 @@ interface Props {
|
|
|
4
4
|
instance: string;
|
|
5
5
|
effect: string;
|
|
6
6
|
values: Readonly<Record<string, unknown>>;
|
|
7
|
+
pathNodes?: readonly string[];
|
|
8
|
+
defaultPathNode?: string;
|
|
9
|
+
disabled?: boolean;
|
|
7
10
|
}
|
|
8
11
|
declare const AnimationEffectControls: import("svelte").Component<Props, {}, "">;
|
|
9
12
|
type AnimationEffectControls = ReturnType<typeof AnimationEffectControls>;
|
|
@@ -16,16 +16,24 @@
|
|
|
16
16
|
const view = animationEditorStateOf(() => controller);
|
|
17
17
|
const ed = $derived(view.current);
|
|
18
18
|
const provider = $derived(ed.effectsProvider);
|
|
19
|
+
const applied = $derived.by(() => {
|
|
20
|
+
void ed.scene;
|
|
21
|
+
return controller.appliedEffects();
|
|
22
|
+
});
|
|
19
23
|
const nodes = $derived([...indexMotionNodes(ed.scene.artwork.nodes).keys()]);
|
|
24
|
+
const pathNodes = $derived.by(() => {
|
|
25
|
+
const vectorGroups = new Set(applied.filter((e) => e.kind === 'vector').map((e) => e.instance));
|
|
26
|
+
const own = ed.scene.artwork.nodes.filter((n) => !vectorGroups.has(n.id));
|
|
27
|
+
return [...indexMotionNodes(own).values()].filter((e) => e.node.type === 'path').map((e) => e.node.id);
|
|
28
|
+
});
|
|
20
29
|
let chosen = $state('');
|
|
21
30
|
const target = $derived(node && nodes.includes(node) ? node : chosen && nodes.includes(chosen) ? chosen : '');
|
|
22
31
|
let browse = $state<null | 'motion' | 'vector'>(null);
|
|
32
|
+
let vectorPathNode = $state('');
|
|
33
|
+
let vectorWords = $state('');
|
|
34
|
+
const activeVectorPathNode = $derived(vectorPathNode && pathNodes.includes(vectorPathNode) ? vectorPathNode : '');
|
|
23
35
|
let open = $state<string | null>(null);
|
|
24
36
|
let message = $state<string | null>(null);
|
|
25
|
-
const applied = $derived.by(() => {
|
|
26
|
-
void ed.scene;
|
|
27
|
-
return controller.appliedEffects();
|
|
28
|
-
});
|
|
29
37
|
const btn = 'rounded border border-border px-1.5 py-0.5 text-[11px] disabled:opacity-40';
|
|
30
38
|
function report(r: MotionEffectResult | Promise<MotionEffectResult>) {
|
|
31
39
|
void Promise.resolve(r).then((x) => (message = x.ok ? (x.notes.length ? x.notes.join(' ') : null) : x.error));
|
|
@@ -53,7 +61,46 @@
|
|
|
53
61
|
{/if}
|
|
54
62
|
<EffectTemplates {provider} disabled={ed.readOnly} fillLabel="New scene from template" onfill={(t, v) => controller.fillTemplate(t, v).then((r) => (r.ok && r.notes.length && (message = r.notes.join(' ')), r))} />
|
|
55
63
|
{#if browse === 'vector'}
|
|
56
|
-
<
|
|
64
|
+
<div class="space-y-1">
|
|
65
|
+
<div class="flex flex-wrap items-center gap-1" data-effect-part="vector-path">
|
|
66
|
+
<span class="text-muted-foreground">Path</span>
|
|
67
|
+
<select
|
|
68
|
+
class="rounded border border-border bg-transparent px-1 py-0.5 text-[11px]"
|
|
69
|
+
aria-label="Path node for vector effect"
|
|
70
|
+
value={activeVectorPathNode}
|
|
71
|
+
disabled={ed.readOnly || !pathNodes.length}
|
|
72
|
+
onchange={(e) => (vectorPathNode = e.currentTarget.value)}
|
|
73
|
+
>
|
|
74
|
+
<option value="">Default path</option>
|
|
75
|
+
{#each pathNodes as id (id)}<option value={id}>{id}</option>{/each}
|
|
76
|
+
</select>
|
|
77
|
+
{#if provider?.pathFromText}
|
|
78
|
+
<input
|
|
79
|
+
class="w-24 rounded border border-border bg-transparent px-1 py-0.5"
|
|
80
|
+
aria-label="Words for vector effect path"
|
|
81
|
+
placeholder="Words"
|
|
82
|
+
bind:value={vectorWords}
|
|
83
|
+
disabled={ed.readOnly}
|
|
84
|
+
/>
|
|
85
|
+
{/if}
|
|
86
|
+
</div>
|
|
87
|
+
<EffectsBrowser
|
|
88
|
+
{provider}
|
|
89
|
+
kinds={['vector']}
|
|
90
|
+
title="Vector effects"
|
|
91
|
+
onpick={(e) => {
|
|
92
|
+
browse = null;
|
|
93
|
+
const path = e.accepts?.includes('path')
|
|
94
|
+
? vectorWords.trim()
|
|
95
|
+
? { text: vectorWords }
|
|
96
|
+
: activeVectorPathNode
|
|
97
|
+
? { node: activeVectorPathNode }
|
|
98
|
+
: undefined
|
|
99
|
+
: undefined;
|
|
100
|
+
report(controller.addVectorEffect(e.id, path ? { path } : undefined));
|
|
101
|
+
}}
|
|
102
|
+
/>
|
|
103
|
+
</div>
|
|
57
104
|
{/if}
|
|
58
105
|
<ol class="space-y-1">
|
|
59
106
|
{#each applied as e (e.instance)}
|
|
@@ -64,7 +111,7 @@
|
|
|
64
111
|
</button>
|
|
65
112
|
<button type="button" class={btn} disabled={ed.readOnly} aria-label="Remove {e.name ?? e.effect}" onclick={() => report(controller.removeMotionEffect(e.instance))}>✕</button>
|
|
66
113
|
</div>
|
|
67
|
-
{#if open === e.instance}<div class="mt-1"><AnimationEffectControls {controller} instance={e.instance} effect={e.effect} values={e.values} /></div>{/if}
|
|
114
|
+
{#if open === e.instance}<div class="mt-1"><AnimationEffectControls {controller} instance={e.instance} effect={e.effect} values={e.values} {pathNodes} defaultPathNode={target} disabled={ed.readOnly} /></div>{/if}
|
|
68
115
|
</li>
|
|
69
116
|
{/each}
|
|
70
117
|
</ol>
|
|
@@ -41,8 +41,18 @@ export declare function createCanvasDraw<TNode extends CanvasNode = CanvasNode>(
|
|
|
41
41
|
selectBrush: (id: string | null, choice?: {
|
|
42
42
|
keepColour?: boolean;
|
|
43
43
|
}) => Promise<import("@pixerate/editor/canvas").CanvasBrushPreset | null>;
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
readonly colorScheme: "light" | "dark";
|
|
45
|
+
/**
|
|
46
|
+
* Sets the colour scheme that picks the side of a brush's light / dark default colour; a
|
|
47
|
+
* stroke colour taken from one (`selectBrush`) follows.
|
|
48
|
+
*/
|
|
49
|
+
setColorScheme(scheme: "light" | "dark"): void;
|
|
50
|
+
/** "Keep my colour": choosing a brush keeps the stroke colour. */
|
|
51
|
+
readonly colorLocked: boolean;
|
|
52
|
+
/** Locks the stroke colour ("Keep my colour"): `selectBrush` then keeps it. */
|
|
53
|
+
setColorLocked: (locked: boolean) => void;
|
|
54
|
+
/** What the brush picker is set to (brush, colour, scheme, lock), read now; `canvas.selectBrush` returns it. */
|
|
55
|
+
brushSettings: () => import("@pixerate/editor/canvas").CanvasBrushChoice;
|
|
46
56
|
};
|
|
47
57
|
export type CanvasDraw<TNode extends CanvasNode = CanvasNode> = ReturnType<typeof createCanvasDraw<TNode>>;
|
|
48
58
|
export {};
|
|
@@ -5,8 +5,14 @@ import { createCanvasDrawController } from '@pixerate/editor/canvas';
|
|
|
5
5
|
export function createCanvasDraw(options) {
|
|
6
6
|
let tool = $state.raw(options.tool ?? 'select');
|
|
7
7
|
let activeStroke = $state.raw(null);
|
|
8
|
+
let colorLocked = $state(!!options.colorLocked);
|
|
9
|
+
let colorScheme = $state(options.colorScheme === 'dark' ? 'dark' : 'light');
|
|
8
10
|
const controller = createCanvasDrawController({
|
|
9
11
|
...options,
|
|
12
|
+
onColorLockChange: (locked) => {
|
|
13
|
+
colorLocked = locked;
|
|
14
|
+
options.onColorLockChange?.(locked);
|
|
15
|
+
},
|
|
10
16
|
onToolChange: (nextTool) => {
|
|
11
17
|
tool = nextTool;
|
|
12
18
|
style = controller.style;
|
|
@@ -53,7 +59,24 @@ export function createCanvasDraw(options) {
|
|
|
53
59
|
* Resolves to the preset; rejects for a brush the canvas can't paint.
|
|
54
60
|
*/
|
|
55
61
|
selectBrush: controller.selectBrush,
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
get colorScheme() {
|
|
63
|
+
return colorScheme;
|
|
64
|
+
},
|
|
65
|
+
/**
|
|
66
|
+
* Sets the colour scheme that picks the side of a brush's light / dark default colour; a
|
|
67
|
+
* stroke colour taken from one (`selectBrush`) follows.
|
|
68
|
+
*/
|
|
69
|
+
setColorScheme(scheme) {
|
|
70
|
+
controller.setColorScheme(scheme);
|
|
71
|
+
colorScheme = controller.colorScheme;
|
|
72
|
+
},
|
|
73
|
+
/** "Keep my colour": choosing a brush keeps the stroke colour. */
|
|
74
|
+
get colorLocked() {
|
|
75
|
+
return colorLocked;
|
|
76
|
+
},
|
|
77
|
+
/** Locks the stroke colour ("Keep my colour"): `selectBrush` then keeps it. */
|
|
78
|
+
setColorLocked: controller.setColorLocked,
|
|
79
|
+
/** What the brush picker is set to (brush, colour, scheme, lock), read now; `canvas.selectBrush` returns it. */
|
|
80
|
+
brushSettings: controller.brushSettings
|
|
58
81
|
};
|
|
59
82
|
}
|
|
@@ -211,6 +211,12 @@
|
|
|
211
211
|
</div>
|
|
212
212
|
<!-- Top layer first, as it draws. -->
|
|
213
213
|
{#each [...layers].reverse() as l (`${l.kind}-${l.index}`)}{@render layerRow(l, layers.length, ids)}{/each}
|
|
214
|
+
{#if kind === 'stroke' && layers.some((l) => l.brushstroke)}
|
|
215
|
+
<!-- Editor state: brushstrokes added or rebrushed from now on keep their colour. -->
|
|
216
|
+
<label class="flex items-center gap-1 text-[11px] text-muted-foreground" title="Choosing a brush keeps the stroke's colour instead of taking the brush's default colour">
|
|
217
|
+
<input type="checkbox" aria-label="Keep my colour" data-vector-keep-colour="" checked={view.colorLocked} onchange={(e) => controller.setColorLocked(e.currentTarget.checked)} /> Keep my colour when choosing a brush
|
|
218
|
+
</label>
|
|
219
|
+
{/if}
|
|
214
220
|
</div>
|
|
215
221
|
{/snippet}
|
|
216
222
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/editor-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.197.0",
|
|
4
4
|
"description": "Svelte bindings and prompt/rich-text components for @pixerate/editor",
|
|
5
5
|
"author": "Pixerate",
|
|
6
6
|
"license": "MIT",
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
"**/*.css"
|
|
176
176
|
],
|
|
177
177
|
"dependencies": {
|
|
178
|
-
"@pixerate/editor": "0.
|
|
178
|
+
"@pixerate/editor": "0.267.0"
|
|
179
179
|
},
|
|
180
180
|
"peerDependencies": {
|
|
181
181
|
"@dagrejs/dagre": "^1.1.4",
|