@pixerate/editor-svelte 0.188.0 → 0.190.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/paint-editor/BrushEditor.svelte +37 -13
- package/dist/paint-editor/BrushPanel.svelte +16 -2
- package/dist/paint-editor/PaintCanvas.svelte +6 -1
- package/dist/paint-editor/PaintCanvas.svelte.d.ts +3 -1
- package/dist/paint-editor/PaintEditor.svelte +5 -3
- package/dist/paint-editor/PaintEditor.svelte.d.ts +3 -1
- package/dist/paint-editor/PaintProposalGhosts.svelte +46 -0
- package/dist/paint-editor/PaintProposalGhosts.svelte.d.ts +10 -0
- package/dist/paint-editor/index.d.ts +2 -0
- package/dist/paint-editor/index.js +2 -0
- package/dist/paint-editor/paintProposalRune.svelte.d.ts +32 -0
- package/dist/paint-editor/paintProposalRune.svelte.js +45 -0
- package/package.json +2 -2
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
problems = r.problems.length ? r.problems.join(' ') : null;
|
|
30
30
|
if (r.brush !== brush) onchange(r.brush);
|
|
31
31
|
};
|
|
32
|
+
const setColour = (path: string, light: string, dark: string | null) => set(path, dark === null ? light : { light, dark });
|
|
33
|
+
const setAt = (path: string, list: Array<string | { light: string; dark: string }>, i: number, entry: string | { light: string; dark: string }) => set(path, list.map((x, j) => (j === i ? entry : x)));
|
|
32
34
|
const save = async () => {
|
|
33
35
|
try {
|
|
34
36
|
await onsaveas?.(name.trim());
|
|
@@ -71,22 +73,44 @@
|
|
|
71
73
|
<input aria-label={c.ariaLabel} value={c.value} placeholder={c.placeholder} onblur={(e) => e.currentTarget.value !== c.value && set(c.path, e.currentTarget.value.trim().toLowerCase())} class="w-28 rounded bg-slate-800 px-1.5 py-0.5" />
|
|
72
74
|
</label>
|
|
73
75
|
{:else if c.kind === 'colour'}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
<
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
<!-- One colour, or a light / dark pair resolved for the host's colour scheme. -->
|
|
77
|
+
<div class="flex flex-col gap-1">
|
|
78
|
+
<div class="flex items-center justify-between gap-2">
|
|
79
|
+
<span class="text-slate-400">{c.label}</span>
|
|
80
|
+
<span class="flex items-center gap-1">
|
|
81
|
+
<input type="color" aria-label={c.ariaLabel} title={c.dark !== null ? 'On light backgrounds' : undefined} value={c.value ?? '#000000'} onchange={(e) => setColour(c.path, e.currentTarget.value, c.dark)} class="h-5 w-8" />
|
|
82
|
+
{#if c.dark !== null}
|
|
83
|
+
<input type="color" aria-label={`${c.ariaLabel} (dark)`} title="On dark backgrounds" value={c.dark} onchange={(e) => setColour(c.path, c.value ?? '#000000', e.currentTarget.value)} class="h-5 w-8" />
|
|
84
|
+
{/if}
|
|
85
|
+
{#if c.value || c.gradient}<button type="button" class={button} onclick={() => set(c.path, undefined)}>None</button>{/if}
|
|
86
|
+
</span>
|
|
87
|
+
</div>
|
|
88
|
+
{#if c.gradient}
|
|
89
|
+
<span class="text-[10px] text-slate-500">A gradient (its own paint).</span>
|
|
90
|
+
{:else}
|
|
91
|
+
<label class="flex items-center gap-2">
|
|
92
|
+
<input type="checkbox" aria-label={`${c.ariaLabel}: light and dark`} checked={c.dark !== null} onchange={(e) => setColour(c.path, c.value ?? '#000000', e.currentTarget.checked ? (c.value ?? '#ffffff') : null)} /> Light / dark
|
|
93
|
+
</label>
|
|
94
|
+
{/if}
|
|
80
95
|
</div>
|
|
81
96
|
{:else if c.kind === 'palette'}
|
|
82
|
-
<div class="flex flex-
|
|
97
|
+
<div class="flex flex-col gap-1" role="group" aria-label={c.ariaLabel}>
|
|
83
98
|
<span class="text-slate-400">{c.label}</span>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
<div class="flex flex-wrap items-center gap-1">
|
|
100
|
+
{#each c.value as col, i (i)}
|
|
101
|
+
<span class="flex items-center gap-0.5 rounded bg-slate-900 p-0.5">
|
|
102
|
+
<input type="color" aria-label={`Palette colour ${i + 1}`} value={typeof col === 'string' ? col : col.light} onchange={(e) => setAt(c.path, c.value, i, typeof col === 'string' ? e.currentTarget.value : { ...col, light: e.currentTarget.value })} class="h-4 w-5" />
|
|
103
|
+
{#if typeof col !== 'string'}
|
|
104
|
+
<input type="color" aria-label={`Palette colour ${i + 1} (dark)`} value={col.dark} onchange={(e) => setAt(c.path, c.value, i, { ...col, dark: e.currentTarget.value })} class="h-4 w-5" />
|
|
105
|
+
{/if}
|
|
106
|
+
<button type="button" aria-label={`Remove palette colour ${i + 1}`} class="px-0.5 text-slate-400 hover:text-white" onclick={() => set(c.path, c.value.filter((_, j) => j !== i))}>×</button>
|
|
107
|
+
</span>
|
|
108
|
+
{/each}
|
|
109
|
+
{#if c.value.length < c.max}
|
|
110
|
+
<input type="color" aria-label="Add a palette colour" onchange={(e) => set(c.path, [...c.value, e.currentTarget.value])} class="h-4 w-6" />
|
|
111
|
+
<button type="button" class={button} onclick={() => set(c.path, [...c.value, { light: '#334155', dark: '#e2e8f0' }])}>Add light / dark pair</button>
|
|
112
|
+
{/if}
|
|
113
|
+
</div>
|
|
90
114
|
</div>
|
|
91
115
|
{:else if c.kind === 'stamps'}
|
|
92
116
|
<div class="flex flex-wrap gap-1" role="group" aria-label={c.ariaLabel} title={`Up to ${c.max}`}>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import StickerPicker from './StickerPicker.svelte';
|
|
3
3
|
import { brushLoadColor, brushTakesPaint, downloadBytes, toggleStickerRef, DEFAULT_PAINT_GRADIENT, type PaintEditorController } from '@pixerate/editor/paint-editor';
|
|
4
|
-
import { PAINT_PROFILE, type Brush, type BrushLibraryEntry } from '@pixerate/editor/brushes';
|
|
4
|
+
import { brushColourHex, PAINT_PROFILE, type Brush, type BrushLibraryEntry } from '@pixerate/editor/brushes';
|
|
5
5
|
import { untrack } from 'svelte';
|
|
6
6
|
import BrushEditor from './BrushEditor.svelte';
|
|
7
7
|
import BrushLibraryBrowser from '../brushes/BrushLibraryBrowser.svelte';
|
|
@@ -49,6 +49,8 @@
|
|
|
49
49
|
return untrack(() => controller.brushThumbnail(brush.id, { width: 240, height: 64, color }));
|
|
50
50
|
});
|
|
51
51
|
const carried = $derived(brushLoadColor(snap.mixerLoad));
|
|
52
|
+
// The brush's suggested colours, resolved for the colour scheme: one click makes one the paint colour.
|
|
53
|
+
const palette = $derived((spec.colour?.palette ?? []).flatMap((c) => brushColourHex(c, snap.colorScheme) ?? []));
|
|
52
54
|
const loads = $derived(['dab', 'mixer', 'impasto'].includes(brush.engine) || (brush.engine === 'wet' && brush.wet.tool === 'paint'));
|
|
53
55
|
const slug = (name: string) => name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 60) || 'brush';
|
|
54
56
|
</script>
|
|
@@ -65,7 +67,18 @@
|
|
|
65
67
|
{/if}
|
|
66
68
|
<button type="button" class={button} onclick={() => controller.cleanMixerBrush(brush.engine === 'mixer')}>Clean</button>
|
|
67
69
|
</div>
|
|
68
|
-
{:else if section === 'colour'
|
|
70
|
+
{:else if section === 'colour'}
|
|
71
|
+
{#if palette.length}
|
|
72
|
+
<div class="flex flex-wrap items-center gap-1" role="group" aria-label="Paint with a palette colour">
|
|
73
|
+
{#each palette as hex, i (`${hex}-${i}`)}
|
|
74
|
+
<button type="button" aria-label={`Paint with ${hex}`} title={hex} aria-pressed={snap.color === hex} class={`h-5 w-5 rounded ring-1 ${snap.color === hex ? 'ring-white' : 'ring-slate-700'}`} style={`background: ${hex}`} onclick={() => controller.setColor(hex)}></button>
|
|
75
|
+
{/each}
|
|
76
|
+
</div>
|
|
77
|
+
{/if}
|
|
78
|
+
<label class="flex items-center gap-2" title="Choosing a brush with a default colour (the highlighter's yellow) makes it your colour unless this is on">
|
|
79
|
+
<input type="checkbox" aria-label="Keep my colour" checked={snap.colorLocked} onchange={(e) => controller.setColorLocked(e.currentTarget.checked)} /> Keep my colour when choosing a brush
|
|
80
|
+
</label>
|
|
81
|
+
{#if brushTakesPaint(brush)}
|
|
69
82
|
<div class="flex flex-col gap-1.5" data-testid="brush-gradient">
|
|
70
83
|
<div class="flex items-center justify-between gap-2">
|
|
71
84
|
<span class="text-slate-400" title="A gradient of the brush's own, used instead of the paint (the multi-colour brush)">Brush gradient</span>
|
|
@@ -75,6 +88,7 @@
|
|
|
75
88
|
<PaintGradientEditor value={brush.paint} colors={{ paint: snap.color, secondary: snap.secondaryColor }} onchange={(g) => run(() => controller.setBrush({ paint: g }))} />
|
|
76
89
|
{/if}
|
|
77
90
|
</div>
|
|
91
|
+
{/if}
|
|
78
92
|
{/if}
|
|
79
93
|
{/snippet}
|
|
80
94
|
|
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
viewCssTransform,
|
|
11
11
|
type PaintEditorController,
|
|
12
12
|
type PaintPresence,
|
|
13
|
+
type PaintProposals,
|
|
13
14
|
type PaintTool,
|
|
14
15
|
} from '@pixerate/editor/paint-editor';
|
|
15
16
|
import { untrack } from 'svelte';
|
|
16
17
|
import { paintEditorState } from './paintEditorRune.svelte.js';
|
|
17
18
|
import PaintPresenceLayer from './PaintPresenceLayer.svelte';
|
|
19
|
+
import PaintProposalGhosts from './PaintProposalGhosts.svelte';
|
|
18
20
|
import ReferencePanel from './ReferencePanel.svelte';
|
|
19
21
|
|
|
20
22
|
interface Props {
|
|
@@ -29,9 +31,11 @@
|
|
|
29
31
|
onerror?: (message: string | null) => void;
|
|
30
32
|
/** Agent cursors to draw over the canvas (see `PaintDirector` and `createPaintDirector`). */
|
|
31
33
|
presence?: PaintPresence;
|
|
34
|
+
/** Paint an agent suggests, drawn over the canvas as ghosts (see `createPaintProposalState`). */
|
|
35
|
+
proposals?: PaintProposals;
|
|
32
36
|
class?: string;
|
|
33
37
|
}
|
|
34
|
-
let { controller, tool = 'brush', showGuides = true, fitOnMount = true, onerror, presence, class: className = '' }: Props = $props();
|
|
38
|
+
let { controller, tool = 'brush', showGuides = true, fitOnMount = true, onerror, presence, proposals, class: className = '' }: Props = $props();
|
|
35
39
|
|
|
36
40
|
const editor = paintEditorState(() => controller);
|
|
37
41
|
const snap = $derived(editor.current);
|
|
@@ -201,6 +205,7 @@
|
|
|
201
205
|
{#each snap.guides.references.filter((r) => r.visible) as r (r.id)}
|
|
202
206
|
<ReferencePanel {controller} reference={r} />
|
|
203
207
|
{/each}
|
|
208
|
+
{#if proposals}<PaintProposalGhosts {proposals} {transform} zoom={snap.view.zoom} />{/if}
|
|
204
209
|
{#if presence}<PaintPresenceLayer {presence} view={snap.view} canvas={doc} viewport={size} />{/if}
|
|
205
210
|
<div class="absolute bottom-1 right-2 text-[10px] text-slate-500 pointer-events-none" data-testid="paint-view-status">
|
|
206
211
|
{Math.round(snap.view.zoom * 100)}% · {Math.round(snap.view.rotation)}°{snap.view.mirror ? ' · mirrored' : ''}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type PaintEditorController, type PaintPresence, type PaintTool } from '@pixerate/editor/paint-editor';
|
|
1
|
+
import { type PaintEditorController, type PaintPresence, type PaintProposals, type PaintTool } from '@pixerate/editor/paint-editor';
|
|
2
2
|
interface Props {
|
|
3
3
|
controller: PaintEditorController;
|
|
4
4
|
/** The active tool (default brush). */
|
|
@@ -11,6 +11,8 @@ interface Props {
|
|
|
11
11
|
onerror?: (message: string | null) => void;
|
|
12
12
|
/** Agent cursors to draw over the canvas (see `PaintDirector` and `createPaintDirector`). */
|
|
13
13
|
presence?: PaintPresence;
|
|
14
|
+
/** Paint an agent suggests, drawn over the canvas as ghosts (see `createPaintProposalState`). */
|
|
15
|
+
proposals?: PaintProposals;
|
|
14
16
|
class?: string;
|
|
15
17
|
}
|
|
16
18
|
declare const PaintCanvas: import("svelte").Component<Props, {}, "">;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { PaintPresence, documentToScreen, screenToDocument, type PaintEditorController, type PaintTool } from '@pixerate/editor/paint-editor';
|
|
2
|
+
import { PaintPresence, documentToScreen, screenToDocument, type PaintEditorController, type PaintProposals, type PaintTool } from '@pixerate/editor/paint-editor';
|
|
3
3
|
import type { AgentPlayback, PlaybackPoint, PlaybackRect, PlaybackRef } from '@pixerate/editor/tools';
|
|
4
4
|
import { createPlaybackSurface, getAgentPlayback } from '../playback/playback.svelte.js';
|
|
5
5
|
import PresenceLayer from '../playback/PresenceLayer.svelte';
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
height?: number | string;
|
|
24
24
|
/** Agent cursors to draw over the canvas (see `createPaintDirector`). */
|
|
25
25
|
presence?: PaintPresence;
|
|
26
|
+
/** Paint an agent suggests, drawn over the canvas as ghosts (see `createPaintProposalState`). */
|
|
27
|
+
proposals?: PaintProposals;
|
|
26
28
|
class?: string;
|
|
27
29
|
/**
|
|
28
30
|
* Agent playback: act out the paint tools' calls here with the agent's cursor, opening the
|
|
@@ -35,7 +37,7 @@
|
|
|
35
37
|
/** Bring the editor on screen (switch to its tab) before the agent acts. */
|
|
36
38
|
onplaybackreveal?: () => void | Promise<void>;
|
|
37
39
|
}
|
|
38
|
-
let { controller, fileName, height = 720, presence, class: className = '', playback, playbackId = 'paint', onplaybackreveal }: Props = $props();
|
|
40
|
+
let { controller, fileName, height = 720, presence, proposals, class: className = '', playback, playbackId = 'paint', onplaybackreveal }: Props = $props();
|
|
39
41
|
|
|
40
42
|
/** Controls a paint plan can point at, by `{ kind: 'control', id }`. */
|
|
41
43
|
const PAINT_CONTROLS: Record<string, string> = {
|
|
@@ -116,7 +118,7 @@
|
|
|
116
118
|
<BrushPanel {controller} class="w-60 shrink-0 overflow-y-auto pr-1" />
|
|
117
119
|
<div class="flex min-w-0 flex-1 flex-col">
|
|
118
120
|
<!-- With playback the editor draws the cursor (over the panels too), so the canvas doesn't. -->
|
|
119
|
-
<PaintCanvas {controller} {tool} presence={playing ? undefined : presence} onerror={(e) => (error = e)} class="flex-1 rounded-lg" />
|
|
121
|
+
<PaintCanvas {controller} {tool} presence={playing ? undefined : presence} {proposals} onerror={(e) => (error = e)} class="flex-1 rounded-lg" />
|
|
120
122
|
{#if error}<p role="alert" class="mt-1 text-[11px] text-rose-400">{error}</p>{/if}
|
|
121
123
|
</div>
|
|
122
124
|
<div class="flex w-64 shrink-0 flex-col gap-2 overflow-y-auto pr-1">
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PaintPresence, type PaintEditorController } from '@pixerate/editor/paint-editor';
|
|
1
|
+
import { PaintPresence, type PaintEditorController, type PaintProposals } from '@pixerate/editor/paint-editor';
|
|
2
2
|
import type { AgentPlayback } from '@pixerate/editor/tools';
|
|
3
3
|
interface Props {
|
|
4
4
|
controller: PaintEditorController;
|
|
@@ -8,6 +8,8 @@ interface Props {
|
|
|
8
8
|
height?: number | string;
|
|
9
9
|
/** Agent cursors to draw over the canvas (see `createPaintDirector`). */
|
|
10
10
|
presence?: PaintPresence;
|
|
11
|
+
/** Paint an agent suggests, drawn over the canvas as ghosts (see `createPaintProposalState`). */
|
|
12
|
+
proposals?: PaintProposals;
|
|
11
13
|
class?: string;
|
|
12
14
|
/**
|
|
13
15
|
* Agent playback: act out the paint tools' calls here with the agent's cursor, opening the
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { paintProposalGhost, type PaintProposals, type PaintProposalsState } from '@pixerate/editor/paint-editor';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
proposals: PaintProposals;
|
|
6
|
+
/** The view's CSS transform (`viewCssTransform`). */
|
|
7
|
+
transform: string;
|
|
8
|
+
zoom: number;
|
|
9
|
+
}
|
|
10
|
+
let { proposals, transform, zoom }: Props = $props();
|
|
11
|
+
|
|
12
|
+
let pending = $state.raw<PaintProposalsState>({ proposals: [] });
|
|
13
|
+
$effect(() => {
|
|
14
|
+
pending = proposals.getState();
|
|
15
|
+
return proposals.subscribe((next) => {
|
|
16
|
+
pending = next;
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
/** Draws a ghost's pixels into its canvas. */
|
|
21
|
+
function ghost(node: HTMLCanvasElement, data: Uint8ClampedArray) {
|
|
22
|
+
const draw = (d: Uint8ClampedArray) => {
|
|
23
|
+
const ctx = node.getContext('2d');
|
|
24
|
+
if (ctx && typeof ImageData !== 'undefined') ctx.putImageData(new ImageData(new Uint8ClampedArray(d), node.width, node.height), 0, 0);
|
|
25
|
+
};
|
|
26
|
+
draw(data);
|
|
27
|
+
return { update: draw };
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<!-- Pending paint proposals drawn over the canvas: each ghost where its paint would go, translucent with a dashed edge. -->
|
|
32
|
+
{#each pending.proposals as p (p.id)}
|
|
33
|
+
{@const g = paintProposalGhost(p)}
|
|
34
|
+
{#if g}
|
|
35
|
+
<canvas
|
|
36
|
+
use:ghost={g.data}
|
|
37
|
+
width={g.width}
|
|
38
|
+
height={g.height}
|
|
39
|
+
data-paint-proposal={p.id}
|
|
40
|
+
title={p.rationale ? `${p.title}: ${p.rationale}` : p.title}
|
|
41
|
+
aria-label={`Suggested: ${p.title}`}
|
|
42
|
+
class="absolute left-0 top-0 pointer-events-none"
|
|
43
|
+
style={`transform: ${transform} translate(${g.x}px, ${g.y}px); transform-origin: 0 0; opacity: 0.7; outline: ${1.5 / zoom}px dashed #a78bfa; outline-offset: ${2 / zoom}px`}
|
|
44
|
+
></canvas>
|
|
45
|
+
{/if}
|
|
46
|
+
{/each}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type PaintProposals } from '@pixerate/editor/paint-editor';
|
|
2
|
+
interface Props {
|
|
3
|
+
proposals: PaintProposals;
|
|
4
|
+
/** The view's CSS transform (`viewCssTransform`). */
|
|
5
|
+
transform: string;
|
|
6
|
+
zoom: number;
|
|
7
|
+
}
|
|
8
|
+
declare const PaintProposalGhosts: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type PaintProposalGhosts = ReturnType<typeof PaintProposalGhosts>;
|
|
10
|
+
export default PaintProposalGhosts;
|
|
@@ -31,6 +31,8 @@ export { default as PaintEffectsPanel } from './PaintEffectsPanel.svelte';
|
|
|
31
31
|
export { default as PaintGradientEditor } from './PaintGradientEditor.svelte';
|
|
32
32
|
export { default as PaintSlider } from './PaintSlider.svelte';
|
|
33
33
|
export { default as PaintPresenceLayer } from './PaintPresenceLayer.svelte';
|
|
34
|
+
export { default as PaintProposalGhosts } from './PaintProposalGhosts.svelte';
|
|
35
|
+
export { createPaintChangeWatcher, createPaintProposalState, type PaintChangeWatcherRune, type PaintProposalStateRune } from './paintProposalRune.svelte.js';
|
|
34
36
|
export { createPaintDirector, createReactivePaintEditor, paintEditorState, paintEffectsProvider, usePaintEffectsTools } from './paintEditorRune.svelte.js';
|
|
35
37
|
export type PaintEditorProps = ComponentProps<typeof PaintEditorComponent>;
|
|
36
38
|
export type PaintCanvasProps = ComponentProps<typeof PaintCanvasComponent>;
|
|
@@ -16,4 +16,6 @@ export { default as PaintEffectsPanel } from './PaintEffectsPanel.svelte';
|
|
|
16
16
|
export { default as PaintGradientEditor } from './PaintGradientEditor.svelte';
|
|
17
17
|
export { default as PaintSlider } from './PaintSlider.svelte';
|
|
18
18
|
export { default as PaintPresenceLayer } from './PaintPresenceLayer.svelte';
|
|
19
|
+
export { default as PaintProposalGhosts } from './PaintProposalGhosts.svelte';
|
|
20
|
+
export { createPaintChangeWatcher, createPaintProposalState } from './paintProposalRune.svelte.js';
|
|
19
21
|
export { createPaintDirector, createReactivePaintEditor, paintEditorState, paintEffectsProvider, usePaintEffectsTools } from './paintEditorRune.svelte.js';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type PaintChange, type PaintChangeFeed, type PaintEditorController, type PaintProposal, type PaintProposals, type PaintProposalsOptions } from '@pixerate/editor/paint-editor';
|
|
2
|
+
export interface PaintProposalStateRune {
|
|
3
|
+
/** The framework-free store (for `createPaintProposalToolProvider` and agents). */
|
|
4
|
+
readonly store: PaintProposals;
|
|
5
|
+
/** Pending proposals (reactive); pass `store` to `PaintCanvas` (`proposals`) to draw them as ghosts. */
|
|
6
|
+
readonly proposals: readonly PaintProposal[];
|
|
7
|
+
propose: PaintProposals['propose'];
|
|
8
|
+
accept: PaintProposals['accept'];
|
|
9
|
+
reject: PaintProposals['reject'];
|
|
10
|
+
clear: PaintProposals['clear'];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Paint an agent suggests without painting it, as a Svelte 5 rune: ghosts the user accepts
|
|
14
|
+
* (painted on a layer of their own as one undo step, made by the proposer) or dismisses. Pass
|
|
15
|
+
* `store` to `PaintCanvas` as `proposals` to draw the ghosts, and to
|
|
16
|
+
* `createPaintProposalToolProvider` for agents. The same API as React's `usePaintProposals`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createPaintProposalState(controller: PaintEditorController, options?: Omit<PaintProposalsOptions, 'controller'>): PaintProposalStateRune;
|
|
19
|
+
export interface PaintChangeWatcherRune {
|
|
20
|
+
readonly feed: PaintChangeFeed;
|
|
21
|
+
/** The latest change (reactive). */
|
|
22
|
+
readonly last: PaintChange | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* What changed in the painting, as a Svelte 5 rune: strokes, fills and layers added, removed or
|
|
26
|
+
* changed, and the start and end of each stroke, with who made it. For agents that paint
|
|
27
|
+
* alongside the user. Call during component initialisation; the feed stops on unmount. The same
|
|
28
|
+
* API as React's `usePaintChangeFeed`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createPaintChangeWatcher(controller: PaintEditorController, options?: {
|
|
31
|
+
onChange?: (change: PaintChange) => void;
|
|
32
|
+
}): PaintChangeWatcherRune;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { createPaintChangeFeed, createPaintProposals, } from '@pixerate/editor/paint-editor';
|
|
2
|
+
/**
|
|
3
|
+
* Paint an agent suggests without painting it, as a Svelte 5 rune: ghosts the user accepts
|
|
4
|
+
* (painted on a layer of their own as one undo step, made by the proposer) or dismisses. Pass
|
|
5
|
+
* `store` to `PaintCanvas` as `proposals` to draw the ghosts, and to
|
|
6
|
+
* `createPaintProposalToolProvider` for agents. The same API as React's `usePaintProposals`.
|
|
7
|
+
*/
|
|
8
|
+
export function createPaintProposalState(controller, options = {}) {
|
|
9
|
+
const store = createPaintProposals({ ...options, controller });
|
|
10
|
+
let state = $state.raw(store.getState());
|
|
11
|
+
store.subscribe((next) => {
|
|
12
|
+
state = next;
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
store,
|
|
16
|
+
get proposals() {
|
|
17
|
+
return state.proposals;
|
|
18
|
+
},
|
|
19
|
+
propose: store.propose,
|
|
20
|
+
accept: store.accept,
|
|
21
|
+
reject: store.reject,
|
|
22
|
+
clear: store.clear,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* What changed in the painting, as a Svelte 5 rune: strokes, fills and layers added, removed or
|
|
27
|
+
* changed, and the start and end of each stroke, with who made it. For agents that paint
|
|
28
|
+
* alongside the user. Call during component initialisation; the feed stops on unmount. The same
|
|
29
|
+
* API as React's `usePaintChangeFeed`.
|
|
30
|
+
*/
|
|
31
|
+
export function createPaintChangeWatcher(controller, options = {}) {
|
|
32
|
+
const feed = createPaintChangeFeed(controller);
|
|
33
|
+
let last = $state.raw(null);
|
|
34
|
+
feed.subscribe((change) => {
|
|
35
|
+
last = change;
|
|
36
|
+
options.onChange?.(change);
|
|
37
|
+
});
|
|
38
|
+
$effect(() => () => feed.dispose());
|
|
39
|
+
return {
|
|
40
|
+
feed,
|
|
41
|
+
get last() {
|
|
42
|
+
return last;
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/editor-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.190.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.257.0"
|
|
179
179
|
},
|
|
180
180
|
"peerDependencies": {
|
|
181
181
|
"@dagrejs/dagre": "^1.1.4",
|