@pixerate/editor-svelte 0.76.3 → 0.77.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/README.md +1 -0
- package/dist/paint-editor/BrushPanel.svelte +96 -0
- package/dist/paint-editor/BrushPanel.svelte.d.ts +11 -0
- package/dist/paint-editor/BrushPanel.svelte.d.ts.map +1 -0
- package/dist/paint-editor/ColorPanel.svelte +175 -0
- package/dist/paint-editor/ColorPanel.svelte.d.ts +13 -0
- package/dist/paint-editor/ColorPanel.svelte.d.ts.map +1 -0
- package/dist/paint-editor/PaintCanvas.svelte +163 -0
- package/dist/paint-editor/PaintCanvas.svelte.d.ts +17 -0
- package/dist/paint-editor/PaintCanvas.svelte.d.ts.map +1 -0
- package/dist/paint-editor/PaintEditor.svelte +45 -0
- package/dist/paint-editor/PaintEditor.svelte.d.ts +13 -0
- package/dist/paint-editor/PaintEditor.svelte.d.ts.map +1 -0
- package/dist/paint-editor/PaintLayersPanel.svelte +108 -0
- package/dist/paint-editor/PaintLayersPanel.svelte.d.ts +9 -0
- package/dist/paint-editor/PaintLayersPanel.svelte.d.ts.map +1 -0
- package/dist/paint-editor/PaintSlider.svelte +41 -0
- package/dist/paint-editor/PaintSlider.svelte.d.ts +13 -0
- package/dist/paint-editor/PaintSlider.svelte.d.ts.map +1 -0
- package/dist/paint-editor/PaintToolbar.svelte +132 -0
- package/dist/paint-editor/PaintToolbar.svelte.d.ts +13 -0
- package/dist/paint-editor/PaintToolbar.svelte.d.ts.map +1 -0
- package/dist/paint-editor/PaperPanel.svelte +54 -0
- package/dist/paint-editor/PaperPanel.svelte.d.ts +9 -0
- package/dist/paint-editor/PaperPanel.svelte.d.ts.map +1 -0
- package/dist/paint-editor/PixelCanvas.svelte +22 -0
- package/dist/paint-editor/PixelCanvas.svelte.d.ts +14 -0
- package/dist/paint-editor/PixelCanvas.svelte.d.ts.map +1 -0
- package/dist/paint-editor/ReferencePanel.svelte +51 -0
- package/dist/paint-editor/ReferencePanel.svelte.d.ts +9 -0
- package/dist/paint-editor/ReferencePanel.svelte.d.ts.map +1 -0
- package/dist/paint-editor/index.d.ts +25 -0
- package/dist/paint-editor/index.d.ts.map +1 -0
- package/dist/paint-editor/index.js +9 -0
- package/dist/paint-editor/paintEditorRune.svelte.d.ts +20 -0
- package/dist/paint-editor/paintEditorRune.svelte.d.ts.map +1 -0
- package/dist/paint-editor/paintEditorRune.svelte.js +61 -0
- package/package.json +10 -2
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { BLEND_MODES, type BlendMode, type PaintEditorController, type PaintLayerInfo } from '@pixerate/editor/paint-editor';
|
|
3
|
+
import { untrack } from 'svelte';
|
|
4
|
+
import { attempt, button, chip, paintEditorState, panel } from './paintEditorRune.svelte.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
controller: PaintEditorController;
|
|
8
|
+
class?: string;
|
|
9
|
+
}
|
|
10
|
+
let { controller, class: className = '' }: Props = $props();
|
|
11
|
+
const editor = paintEditorState(() => controller);
|
|
12
|
+
const layers = $derived.by(() => {
|
|
13
|
+
void editor.current;
|
|
14
|
+
return untrack(() => controller.listLayers());
|
|
15
|
+
});
|
|
16
|
+
const active = $derived(layers.find((l) => l.active) ?? null);
|
|
17
|
+
let error = $state<string | null>(null);
|
|
18
|
+
let renaming = $state<string | null>(null);
|
|
19
|
+
const run = (fn: () => unknown) => attempt(fn, (m) => (error = m));
|
|
20
|
+
const move = (delta: number) => {
|
|
21
|
+
if (!active) return;
|
|
22
|
+
const sibs: PaintLayerInfo[] = layers.filter((x) => x.parentId === active.parentId).reverse();
|
|
23
|
+
const index = sibs.findIndex((x) => x.id === active.id);
|
|
24
|
+
const to = Math.min(sibs.length - 1, Math.max(0, index + delta));
|
|
25
|
+
if (to !== index) run(() => controller.moveLayer(active.id, to));
|
|
26
|
+
};
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<!-- The layer stack, top first, with the active layer's opacity, blend and locks. -->
|
|
30
|
+
<div class={`${panel} ${className}`} data-testid="paint-layers-panel">
|
|
31
|
+
<div class="flex flex-wrap gap-1">
|
|
32
|
+
<button type="button" class={button} onclick={() => run(() => controller.addLayer())}>New layer</button>
|
|
33
|
+
<button type="button" class={button} onclick={() => run(() => controller.addLayer({ type: 'group' }))}>New group</button>
|
|
34
|
+
<button type="button" class={button} disabled={!active} onclick={() => active && run(() => controller.duplicateLayer(active.id))}>Duplicate</button>
|
|
35
|
+
<button type="button" class={button} disabled={!active} aria-label="Move layer up" onclick={() => move(1)}>↑</button>
|
|
36
|
+
<button type="button" class={button} disabled={!active} aria-label="Move layer down" onclick={() => move(-1)}>↓</button>
|
|
37
|
+
<button type="button" class={button} disabled={!active} onclick={() => active && run(() => controller.mergeDown(active.id))}>Merge down</button>
|
|
38
|
+
<button type="button" class={button} disabled={!active || layers.length < 2} onclick={() => active && run(() => controller.removeLayer(active.id))}>Delete</button>
|
|
39
|
+
</div>
|
|
40
|
+
<ul role="tree" aria-label="Layers" class="flex flex-col gap-0.5">
|
|
41
|
+
{#each layers as l (l.id)}
|
|
42
|
+
<li
|
|
43
|
+
role="treeitem"
|
|
44
|
+
aria-selected={l.active}
|
|
45
|
+
class={`flex items-center gap-1.5 rounded px-1.5 py-1 cursor-pointer ${l.active ? 'bg-indigo-600/30 ring-1 ring-indigo-500' : 'hover:bg-slate-800'}`}
|
|
46
|
+
style={`padding-left: ${6 + l.depth * 12}px`}
|
|
47
|
+
onclick={() => controller.setActiveLayer(l.id)}
|
|
48
|
+
onkeydown={(e) => e.key === 'Enter' && controller.setActiveLayer(l.id)}
|
|
49
|
+
>
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
aria-label={l.visible ? `Hide ${l.name}` : `Show ${l.name}`}
|
|
53
|
+
class={`w-5 text-[11px] ${l.visible ? 'text-slate-200' : 'text-slate-600'}`}
|
|
54
|
+
onclick={(e) => {
|
|
55
|
+
e.stopPropagation();
|
|
56
|
+
run(() => controller.updateLayer(l.id, { visible: !l.visible }));
|
|
57
|
+
}}>{l.visible ? '●' : '○'}</button
|
|
58
|
+
>
|
|
59
|
+
{#if renaming === l.id}
|
|
60
|
+
<!-- svelte-ignore a11y_autofocus -->
|
|
61
|
+
<input
|
|
62
|
+
autofocus
|
|
63
|
+
aria-label="Layer name"
|
|
64
|
+
value={l.name}
|
|
65
|
+
class="flex-1 rounded bg-slate-800 px-1"
|
|
66
|
+
onclick={(e) => e.stopPropagation()}
|
|
67
|
+
onblur={(e) => {
|
|
68
|
+
const v = e.currentTarget.value;
|
|
69
|
+
renaming = null;
|
|
70
|
+
if (v.trim() && v !== l.name) run(() => controller.updateLayer(l.id, { name: v }));
|
|
71
|
+
}}
|
|
72
|
+
onkeydown={(e) => {
|
|
73
|
+
if (e.key === 'Enter') e.currentTarget.blur();
|
|
74
|
+
if (e.key === 'Escape') renaming = null;
|
|
75
|
+
}}
|
|
76
|
+
/>
|
|
77
|
+
{:else}
|
|
78
|
+
<span class="flex-1 truncate" role="presentation" ondblclick={() => (renaming = l.id)}>{l.type === 'group' ? '▸ ' : ''}{l.name}</span>
|
|
79
|
+
{/if}
|
|
80
|
+
{#if l.locks.pixels || l.locks.all}<span title="Locked">🔒</span>{/if}
|
|
81
|
+
</li>
|
|
82
|
+
{/each}
|
|
83
|
+
</ul>
|
|
84
|
+
{#if active}
|
|
85
|
+
<div class="flex flex-col gap-1.5">
|
|
86
|
+
<label class="flex items-center gap-2">
|
|
87
|
+
<span class="w-14 text-slate-400">Opacity</span>
|
|
88
|
+
<input type="range" aria-label="Layer opacity" min="0" max="1" step="0.01" value={active.opacity} oninput={(e) => run(() => controller.updateLayer(active.id, { opacity: Number(e.currentTarget.value) }))} class="flex-1 accent-indigo-500" />
|
|
89
|
+
</label>
|
|
90
|
+
<label class="flex items-center gap-2">
|
|
91
|
+
<span class="w-14 text-slate-400">Blend</span>
|
|
92
|
+
<select aria-label="Layer blend" value={active.blend} onchange={(e) => run(() => controller.updateLayer(active.id, { blend: e.currentTarget.value as BlendMode }))} class="flex-1 rounded bg-slate-800 px-1 py-0.5">
|
|
93
|
+
{#if active.type === 'group'}<option value="pass-through">Pass through</option>{/if}
|
|
94
|
+
{#each BLEND_MODES as m (m.id)}
|
|
95
|
+
<option value={m.id}>{m.label}</option>
|
|
96
|
+
{/each}
|
|
97
|
+
</select>
|
|
98
|
+
</label>
|
|
99
|
+
<div class="flex gap-1">
|
|
100
|
+
{#each ['pixels', 'transparency'] as const as k (k)}
|
|
101
|
+
<button type="button" aria-pressed={Boolean(active.locks[k])} class={chip(Boolean(active.locks[k]))} onclick={() => run(() => controller.updateLayer(active.id, { locks: { [k]: !active.locks[k] } }))}>{k === 'pixels' ? 'Lock pixels' : 'Lock transparency'}</button>
|
|
102
|
+
{/each}
|
|
103
|
+
<button type="button" aria-pressed={active.clipped} class={chip(active.clipped)} onclick={() => run(() => controller.updateLayer(active.id, { clipped: !active.clipped }))}>Clip</button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
{/if}
|
|
107
|
+
{#if error}<p role="alert" class="text-[11px] text-rose-400">{error}</p>{/if}
|
|
108
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type PaintEditorController } from '@pixerate/editor/paint-editor';
|
|
2
|
+
interface Props {
|
|
3
|
+
controller: PaintEditorController;
|
|
4
|
+
class?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const PaintLayersPanel: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type PaintLayersPanel = ReturnType<typeof PaintLayersPanel>;
|
|
8
|
+
export default PaintLayersPanel;
|
|
9
|
+
//# sourceMappingURL=PaintLayersPanel.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PaintLayersPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/PaintLayersPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAA+B,KAAK,qBAAqB,EAAuB,MAAM,+BAA+B,CAAC;AAK3H,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAwFH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { SliderControl } from '@pixerate/editor/paint-editor';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
control: SliderControl;
|
|
6
|
+
value: number;
|
|
7
|
+
/** Runs while dragging. */
|
|
8
|
+
onchange?: (v: number) => void;
|
|
9
|
+
/** Runs once on release (the native `change` event), for settings that are undo steps. */
|
|
10
|
+
oncommit?: (v: number) => void;
|
|
11
|
+
}
|
|
12
|
+
let { control, value, onchange, oncommit }: Props = $props();
|
|
13
|
+
let draft = $state<number | null>(null);
|
|
14
|
+
const shown = $derived(draft ?? value);
|
|
15
|
+
const fmt = (v: number) => (control.step >= 1 ? String(Math.round(v)) : v.toFixed(2));
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<label class="flex flex-col gap-0.5" data-path={control.path}>
|
|
19
|
+
<span class="flex justify-between text-slate-400">
|
|
20
|
+
<span>{control.label}</span>
|
|
21
|
+
<span class="font-mono">{fmt(shown)}</span>
|
|
22
|
+
</span>
|
|
23
|
+
<input
|
|
24
|
+
type="range"
|
|
25
|
+
aria-label={control.label}
|
|
26
|
+
min={control.min}
|
|
27
|
+
max={control.max}
|
|
28
|
+
step={control.step}
|
|
29
|
+
value={shown}
|
|
30
|
+
oninput={(e) => {
|
|
31
|
+
const v = Number(e.currentTarget.value);
|
|
32
|
+
if (oncommit) draft = v;
|
|
33
|
+
onchange?.(v);
|
|
34
|
+
}}
|
|
35
|
+
onchange={(e) => {
|
|
36
|
+
oncommit?.(Number(e.currentTarget.value));
|
|
37
|
+
draft = null;
|
|
38
|
+
}}
|
|
39
|
+
class="w-full accent-indigo-500"
|
|
40
|
+
/>
|
|
41
|
+
</label>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SliderControl } from '@pixerate/editor/paint-editor';
|
|
2
|
+
interface Props {
|
|
3
|
+
control: SliderControl;
|
|
4
|
+
value: number;
|
|
5
|
+
/** Runs while dragging. */
|
|
6
|
+
onchange?: (v: number) => void;
|
|
7
|
+
/** Runs once on release (the native `change` event), for settings that are undo steps. */
|
|
8
|
+
oncommit?: (v: number) => void;
|
|
9
|
+
}
|
|
10
|
+
declare const PaintSlider: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type PaintSlider = ReturnType<typeof PaintSlider>;
|
|
12
|
+
export default PaintSlider;
|
|
13
|
+
//# sourceMappingURL=PaintSlider.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PaintSlider.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/PaintSlider.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAGjE,UAAU,KAAK;IACb,OAAO,EAAE,aAAa,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AA2BH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { PAINT_TOOLS, SYMMETRY_MODES, decodeImageBlob, downloadBytes, type PaintEditorController, type PaintTool, type SymmetryMode } from '@pixerate/editor/paint-editor';
|
|
3
|
+
import { attempt, button, chip, paintEditorState } from './paintEditorRune.svelte.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
controller: PaintEditorController;
|
|
7
|
+
tool: PaintTool;
|
|
8
|
+
ontoolchange: (tool: PaintTool) => void;
|
|
9
|
+
/** Base name of saved and exported files (default `painting`). */
|
|
10
|
+
fileName?: string;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
let { controller, tool, ontoolchange, fileName = 'painting', class: className = '' }: Props = $props();
|
|
14
|
+
const editor = paintEditorState(() => controller);
|
|
15
|
+
const snap = $derived(editor.current);
|
|
16
|
+
const symmetry = $derived(snap.guides.symmetry);
|
|
17
|
+
const perspective = $derived(snap.guides.perspective);
|
|
18
|
+
const tracing = $derived(snap.guides.tracing);
|
|
19
|
+
const canvas = $derived(snap.document.canvas);
|
|
20
|
+
let error = $state<string | null>(null);
|
|
21
|
+
let openInput = $state<HTMLInputElement | null>(null);
|
|
22
|
+
let tracingInput = $state<HTMLInputElement | null>(null);
|
|
23
|
+
let refInput = $state<HTMLInputElement | null>(null);
|
|
24
|
+
const run = (fn: () => unknown) => attempt(fn, (m) => (error = m));
|
|
25
|
+
const exportImage = (format: 'image/png' | 'image/jpeg') =>
|
|
26
|
+
run(async () => downloadBytes(await controller.exportImage({ format }), `${fileName}.${format === 'image/png' ? 'png' : 'jpg'}`, format));
|
|
27
|
+
const vanishing = (n: number) => {
|
|
28
|
+
const y = canvas.height * 0.4;
|
|
29
|
+
return n === 1 ? [{ x: canvas.width / 2, y }] : n === 2 ? [{ x: -canvas.width * 0.5, y }, { x: canvas.width * 1.5, y }] : [{ x: -canvas.width * 0.5, y }, { x: canvas.width * 1.5, y }, { x: canvas.width / 2, y: canvas.height * 3 }];
|
|
30
|
+
};
|
|
31
|
+
const fileOf = (e: Event) => {
|
|
32
|
+
const input = e.currentTarget as HTMLInputElement;
|
|
33
|
+
const f = input.files?.[0];
|
|
34
|
+
input.value = '';
|
|
35
|
+
return f;
|
|
36
|
+
};
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<!-- Tools, undo / redo, view rotation and mirroring, guides, tracing and reference images, hand, files. -->
|
|
40
|
+
<div class={`flex flex-wrap items-center gap-1.5 text-xs text-slate-200 ${className}`} role="toolbar" aria-label="Paint toolbar" data-testid="paint-toolbar">
|
|
41
|
+
{#each PAINT_TOOLS as t (t.id)}
|
|
42
|
+
<button type="button" aria-pressed={tool === t.id} title={`${t.label} (${t.key.toUpperCase()})`} class={chip(tool === t.id)} onclick={() => ontoolchange(t.id)}>{t.label}</button>
|
|
43
|
+
{/each}
|
|
44
|
+
<span class="mx-1 h-4 w-px bg-slate-700"></span>
|
|
45
|
+
<button type="button" class={button} disabled={!snap.canUndo} title={snap.undoLabel ? `Undo ${snap.undoLabel}` : 'Undo'} onclick={() => controller.undo()}>Undo</button>
|
|
46
|
+
<button type="button" class={button} disabled={!snap.canRedo} title={snap.redoLabel ? `Redo ${snap.redoLabel}` : 'Redo'} onclick={() => controller.redo()}>Redo</button>
|
|
47
|
+
<span class="mx-1 h-4 w-px bg-slate-700"></span>
|
|
48
|
+
<button type="button" class={button} aria-label="Rotate view left" onclick={() => controller.setView({ rotation: snap.view.rotation - 15 })}>⟲</button>
|
|
49
|
+
<button type="button" class={button} aria-label="Rotate view right" onclick={() => controller.setView({ rotation: snap.view.rotation + 15 })}>⟳</button>
|
|
50
|
+
<button type="button" class={chip(snap.view.mirror)} aria-pressed={snap.view.mirror} onclick={() => controller.setView({ mirror: !snap.view.mirror })}>Mirror view</button>
|
|
51
|
+
<button type="button" class={button} onclick={() => controller.resetView()}>Reset view</button>
|
|
52
|
+
<span class="mx-1 h-4 w-px bg-slate-700"></span>
|
|
53
|
+
<label class="flex items-center gap-1">
|
|
54
|
+
Symmetry
|
|
55
|
+
<select
|
|
56
|
+
aria-label="Symmetry"
|
|
57
|
+
value={symmetry.mode}
|
|
58
|
+
onchange={(e) => controller.setSymmetry({ mode: e.currentTarget.value as SymmetryMode, center: symmetry.mode === 'off' ? { x: canvas.width / 2, y: canvas.height / 2 } : symmetry.center })}
|
|
59
|
+
class="rounded bg-slate-800 px-1 py-0.5"
|
|
60
|
+
>
|
|
61
|
+
{#each SYMMETRY_MODES as m (m)}
|
|
62
|
+
<option value={m}>{m}</option>
|
|
63
|
+
{/each}
|
|
64
|
+
</select>
|
|
65
|
+
</label>
|
|
66
|
+
{#if symmetry.mode === 'kaleidoscope'}
|
|
67
|
+
<input type="number" aria-label="Kaleidoscope segments" min="2" max="32" value={symmetry.segments} oninput={(e) => controller.setSymmetry({ segments: Number(e.currentTarget.value) })} class="w-12 rounded bg-slate-800 px-1 py-0.5" />
|
|
68
|
+
{/if}
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
class={chip(perspective.enabled)}
|
|
72
|
+
aria-pressed={perspective.enabled}
|
|
73
|
+
onclick={() => run(() => controller.setPerspective(perspective.enabled ? { enabled: false } : { enabled: true, points: perspective.points.length ? perspective.points : vanishing(1) }))}
|
|
74
|
+
>Perspective</button>
|
|
75
|
+
{#if perspective.enabled}
|
|
76
|
+
<select aria-label="Vanishing points" value={perspective.points.length} onchange={(e) => run(() => controller.setPerspective({ points: vanishing(Number(e.currentTarget.value)) }))} class="rounded bg-slate-800 px-1 py-0.5">
|
|
77
|
+
<option value={1}>1-point</option>
|
|
78
|
+
<option value={2}>2-point</option>
|
|
79
|
+
<option value={3}>3-point</option>
|
|
80
|
+
</select>
|
|
81
|
+
{/if}
|
|
82
|
+
<span class="mx-1 h-4 w-px bg-slate-700"></span>
|
|
83
|
+
<button type="button" class={button} onclick={() => tracingInput?.click()}>Tracing image…</button>
|
|
84
|
+
{#if tracing}
|
|
85
|
+
<input type="range" aria-label="Tracing opacity" min="0" max="1" step="0.05" value={tracing.opacity} oninput={(e) => controller.updateTracing({ opacity: Number(e.currentTarget.value) })} class="w-16 accent-indigo-500" />
|
|
86
|
+
<button type="button" class={chip(tracing.sampleColors)} aria-pressed={tracing.sampleColors} onclick={() => controller.updateTracing({ sampleColors: !tracing.sampleColors })}>Trace colours</button>
|
|
87
|
+
<button type="button" class={chip(tracing.visible)} aria-pressed={tracing.visible} onclick={() => controller.updateTracing({ visible: !tracing.visible })}>Show tracing</button>
|
|
88
|
+
<button type="button" class={button} onclick={() => controller.clearTracingImage()}>Remove tracing</button>
|
|
89
|
+
{/if}
|
|
90
|
+
<button type="button" class={button} onclick={() => refInput?.click()}>Reference…</button>
|
|
91
|
+
<button type="button" class={button} title="Which hand holds the pen (palm rejection)" onclick={() => controller.setHand(snap.hand === 'right' ? 'left' : 'right')}>{snap.hand === 'right' ? 'Right-handed' : 'Left-handed'}</button>
|
|
92
|
+
<span class="mx-1 h-4 w-px bg-slate-700"></span>
|
|
93
|
+
<button type="button" class={button} onclick={() => run(() => downloadBytes(JSON.stringify(controller.toJSON()), `${fileName}.paint.json`, 'application/json'))}>Save</button>
|
|
94
|
+
<button type="button" class={button} onclick={() => openInput?.click()}>Open…</button>
|
|
95
|
+
<button type="button" class={button} onclick={() => exportImage('image/png')}>PNG</button>
|
|
96
|
+
<button type="button" class={button} onclick={() => exportImage('image/jpeg')}>JPEG</button>
|
|
97
|
+
<button type="button" class={button} onclick={() => run(() => downloadBytes(controller.exportPsd().bytes, `${fileName}.psd`, 'image/vnd.adobe.photoshop'))}>PSD</button>
|
|
98
|
+
<input
|
|
99
|
+
bind:this={openInput}
|
|
100
|
+
type="file"
|
|
101
|
+
accept=".json,application/json"
|
|
102
|
+
aria-label="Open painting"
|
|
103
|
+
class="hidden"
|
|
104
|
+
onchange={(e) => {
|
|
105
|
+
const f = fileOf(e);
|
|
106
|
+
if (f) run(async () => controller.loadJSON(JSON.parse(await f.text())));
|
|
107
|
+
}}
|
|
108
|
+
/>
|
|
109
|
+
<input
|
|
110
|
+
bind:this={tracingInput}
|
|
111
|
+
type="file"
|
|
112
|
+
accept="image/*"
|
|
113
|
+
aria-label="Load tracing image"
|
|
114
|
+
class="hidden"
|
|
115
|
+
onchange={(e) => {
|
|
116
|
+
const f = fileOf(e);
|
|
117
|
+
if (f) run(async () => controller.setTracingImage({ pixels: await decodeImageBlob(f) }));
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
<input
|
|
121
|
+
bind:this={refInput}
|
|
122
|
+
type="file"
|
|
123
|
+
accept="image/*"
|
|
124
|
+
aria-label="Add reference image"
|
|
125
|
+
class="hidden"
|
|
126
|
+
onchange={(e) => {
|
|
127
|
+
const f = fileOf(e);
|
|
128
|
+
if (f) run(async () => controller.addReferenceImage({ pixels: await decodeImageBlob(f), name: f.name }));
|
|
129
|
+
}}
|
|
130
|
+
/>
|
|
131
|
+
{#if error}<p role="alert" class="text-[11px] text-rose-400">{error}</p>{/if}
|
|
132
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type PaintEditorController, type PaintTool } from '@pixerate/editor/paint-editor';
|
|
2
|
+
interface Props {
|
|
3
|
+
controller: PaintEditorController;
|
|
4
|
+
tool: PaintTool;
|
|
5
|
+
ontoolchange: (tool: PaintTool) => void;
|
|
6
|
+
/** Base name of saved and exported files (default `painting`). */
|
|
7
|
+
fileName?: string;
|
|
8
|
+
class?: string;
|
|
9
|
+
}
|
|
10
|
+
declare const PaintToolbar: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type PaintToolbar = ReturnType<typeof PaintToolbar>;
|
|
12
|
+
export default PaintToolbar;
|
|
13
|
+
//# sourceMappingURL=PaintToolbar.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PaintToolbar.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/PaintToolbar.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAA+D,KAAK,qBAAqB,EAAE,KAAK,SAAS,EAAqB,MAAM,+BAA+B,CAAC;AAIzK,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,IAAI,EAAE,SAAS,CAAC;IAChB,YAAY,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IACxC,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAiGH,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { PAPER_FAMILIES, PAPER_LIBRARY, PAPER_SLIDERS, patchPath, readPath, renderPaper, type PaintEditorController } from '@pixerate/editor/paint-editor';
|
|
3
|
+
import PaintSlider from './PaintSlider.svelte';
|
|
4
|
+
import PixelCanvas from './PixelCanvas.svelte';
|
|
5
|
+
import { attempt, button, paintEditorState, panel } from './paintEditorRune.svelte.js';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
controller: PaintEditorController;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
let { controller, class: className = '' }: Props = $props();
|
|
12
|
+
const editor = paintEditorState(() => controller);
|
|
13
|
+
const paper = $derived(editor.current.document.paper);
|
|
14
|
+
let error = $state<string | null>(null);
|
|
15
|
+
const run = (fn: () => unknown) => attempt(fn, (m) => (error = m));
|
|
16
|
+
const SWATCH = { x: 0, y: 0, width: 64, height: 40 };
|
|
17
|
+
const previews = new Map(PAPER_LIBRARY.map((p) => [p.preset, { ...SWATCH, data: renderPaper({ ...p, shading: Math.max(0.8, p.shading) }, SWATCH) }]));
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<!-- The paper library by family and the current paper's settings (each change one undo step). -->
|
|
21
|
+
<div class={`${panel} ${className}`} data-testid="paper-panel">
|
|
22
|
+
{#each PAPER_FAMILIES as family (family)}
|
|
23
|
+
<div>
|
|
24
|
+
<div class="mb-0.5 text-[10px] uppercase tracking-wide text-slate-500">{family}</div>
|
|
25
|
+
<div class="flex flex-wrap gap-1" role="listbox" aria-label={`${family} papers`}>
|
|
26
|
+
{#each PAPER_LIBRARY.filter((p) => p.family === family) as p (p.preset)}
|
|
27
|
+
<button
|
|
28
|
+
type="button"
|
|
29
|
+
role="option"
|
|
30
|
+
aria-selected={paper.preset === p.preset}
|
|
31
|
+
title={p.name}
|
|
32
|
+
class={`rounded p-0.5 ${paper.preset === p.preset ? 'ring-2 ring-indigo-500' : 'ring-1 ring-slate-700 hover:ring-slate-500'}`}
|
|
33
|
+
onclick={() => run(() => controller.setPaper({ preset: p.preset }))}
|
|
34
|
+
>
|
|
35
|
+
<PixelCanvas pixels={previews.get(p.preset) ?? null} label={p.name} style="width: 48px; height: 30px" />
|
|
36
|
+
</button>
|
|
37
|
+
{/each}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
{/each}
|
|
41
|
+
<div class="text-slate-300">{paper.name}</div>
|
|
42
|
+
<label class="flex items-center justify-between">
|
|
43
|
+
<span class="text-slate-400">Colour</span>
|
|
44
|
+
<input type="color" aria-label="Paper colour" value={paper.color} onchange={(e) => run(() => controller.setPaper({ color: e.currentTarget.value }))} class="h-6 w-10 rounded bg-transparent" />
|
|
45
|
+
</label>
|
|
46
|
+
{#each PAPER_SLIDERS as c (c.path)}
|
|
47
|
+
<PaintSlider control={c} value={readPath(paper, c.path)} oncommit={(v) => run(() => controller.setPaper(patchPath(paper as never, c.path, v) as never))} />
|
|
48
|
+
{/each}
|
|
49
|
+
<label class="flex items-center gap-2">
|
|
50
|
+
<input type="checkbox" checked={paper.tiling === 'mirror'} onchange={(e) => run(() => controller.setPaper({ tiling: e.currentTarget.checked ? 'mirror' : 'repeat' }))} /> Mirror tiling
|
|
51
|
+
</label>
|
|
52
|
+
<button type="button" class={`self-start ${button}`} onclick={() => run(() => controller.setPaper({ seed: paper.seed + 1 }))}>New sheet</button>
|
|
53
|
+
{#if error}<p role="alert" class="text-[11px] text-rose-400">{error}</p>{/if}
|
|
54
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type PaintEditorController } from '@pixerate/editor/paint-editor';
|
|
2
|
+
interface Props {
|
|
3
|
+
controller: PaintEditorController;
|
|
4
|
+
class?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const PaperPanel: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type PaperPanel = ReturnType<typeof PaperPanel>;
|
|
8
|
+
export default PaperPanel;
|
|
9
|
+
//# sourceMappingURL=PaperPanel.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PaperPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/PaperPanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkF,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAMzJ,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA+CH,QAAA,MAAM,UAAU,2CAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
pixels: { width: number; height: number; data: Uint8ClampedArray } | null;
|
|
4
|
+
label: string;
|
|
5
|
+
class?: string;
|
|
6
|
+
style?: string;
|
|
7
|
+
}
|
|
8
|
+
let { pixels, label, class: className = '', style = '' }: Props = $props();
|
|
9
|
+
let canvas = $state<HTMLCanvasElement | null>(null);
|
|
10
|
+
|
|
11
|
+
$effect(() => {
|
|
12
|
+
const ctx = canvas?.getContext('2d');
|
|
13
|
+
if (!canvas || !ctx || !pixels || typeof ctx.createImageData !== 'function') return;
|
|
14
|
+
canvas.width = pixels.width;
|
|
15
|
+
canvas.height = pixels.height;
|
|
16
|
+
const img = ctx.createImageData(pixels.width, pixels.height);
|
|
17
|
+
img.data.set(pixels.data);
|
|
18
|
+
ctx.putImageData(img, 0, 0);
|
|
19
|
+
});
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<canvas bind:this={canvas} aria-label={label} class={className} {style} width={pixels?.width} height={pixels?.height}></canvas>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
pixels: {
|
|
3
|
+
width: number;
|
|
4
|
+
height: number;
|
|
5
|
+
data: Uint8ClampedArray;
|
|
6
|
+
} | null;
|
|
7
|
+
label: string;
|
|
8
|
+
class?: string;
|
|
9
|
+
style?: string;
|
|
10
|
+
}
|
|
11
|
+
declare const PixelCanvas: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type PixelCanvas = ReturnType<typeof PixelCanvas>;
|
|
13
|
+
export default PixelCanvas;
|
|
14
|
+
//# sourceMappingURL=PixelCanvas.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PixelCanvas.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/PixelCanvas.svelte.ts"],"names":[],"mappings":"AAGE,UAAU,KAAK;IACb,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,iBAAiB,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqBH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { PaintEditorController, ReferenceImage } from '@pixerate/editor/paint-editor';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
controller: PaintEditorController;
|
|
6
|
+
reference: ReferenceImage;
|
|
7
|
+
}
|
|
8
|
+
let { controller, reference }: Props = $props();
|
|
9
|
+
let drag: { x: number; y: number; ox: number; oy: number } | null = null;
|
|
10
|
+
const h = $derived((reference.height / reference.width) * reference.displayWidth);
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<!-- A floating reference image: drag its title bar to move it, click the image to pick a colour. -->
|
|
14
|
+
<div
|
|
15
|
+
class="absolute rounded border border-slate-700 bg-slate-900/95 shadow-xl"
|
|
16
|
+
style={`left: ${reference.x}px; top: ${reference.y}px; width: ${reference.displayWidth}px`}
|
|
17
|
+
data-testid="paint-reference"
|
|
18
|
+
role="dialog"
|
|
19
|
+
aria-label={reference.name}
|
|
20
|
+
tabindex="-1"
|
|
21
|
+
onpointerdown={(e) => e.stopPropagation()}
|
|
22
|
+
>
|
|
23
|
+
<div
|
|
24
|
+
class="flex items-center justify-between px-1.5 py-0.5 text-[10px] text-slate-300 cursor-move"
|
|
25
|
+
role="toolbar"
|
|
26
|
+
tabindex="-1"
|
|
27
|
+
onpointerdown={(e) => {
|
|
28
|
+
drag = { x: e.clientX, y: e.clientY, ox: reference.x, oy: reference.y };
|
|
29
|
+
(e.target as Element).setPointerCapture?.(e.pointerId);
|
|
30
|
+
}}
|
|
31
|
+
onpointermove={(e) => {
|
|
32
|
+
if (drag) controller.updateReferenceImage(reference.id, { x: drag.ox + e.clientX - drag.x, y: drag.oy + e.clientY - drag.y });
|
|
33
|
+
}}
|
|
34
|
+
onpointerup={() => (drag = null)}
|
|
35
|
+
>
|
|
36
|
+
<span class="truncate">{reference.name}</span>
|
|
37
|
+
<button type="button" aria-label={`Hide ${reference.name}`} class="px-1 text-slate-400 hover:text-white" onclick={() => controller.updateReferenceImage(reference.id, { visible: false })}>×</button>
|
|
38
|
+
</div>
|
|
39
|
+
<button
|
|
40
|
+
type="button"
|
|
41
|
+
class="block cursor-copy p-0"
|
|
42
|
+
aria-label={`Pick a colour from ${reference.name}`}
|
|
43
|
+
onclick={(e) => {
|
|
44
|
+
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
45
|
+
const k = reference.width / (rect.width || reference.displayWidth);
|
|
46
|
+
controller.setColor(controller.pickReferenceColor(reference.id, Math.min(reference.width - 1, (e.clientX - rect.left) * k), Math.min(reference.height - 1, (e.clientY - rect.top) * k)));
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
<img src={reference.src} alt={reference.name} draggable="false" width={reference.displayWidth} height={h} class="block" />
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PaintEditorController, ReferenceImage } from '@pixerate/editor/paint-editor';
|
|
2
|
+
interface Props {
|
|
3
|
+
controller: PaintEditorController;
|
|
4
|
+
reference: ReferenceImage;
|
|
5
|
+
}
|
|
6
|
+
declare const ReferencePanel: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type ReferencePanel = ReturnType<typeof ReferencePanel>;
|
|
8
|
+
export default ReferencePanel;
|
|
9
|
+
//# sourceMappingURL=ReferencePanel.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ReferencePanel.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/ReferencePanel.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAGzF,UAAU,KAAK;IACb,UAAU,EAAE,qBAAqB,CAAC;IAClC,SAAS,EAAE,cAAc,CAAC;CAC3B;AA+BH,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ComponentProps } from 'svelte';
|
|
2
|
+
import type PaintEditorComponent from './PaintEditor.svelte';
|
|
3
|
+
import type PaintCanvasComponent from './PaintCanvas.svelte';
|
|
4
|
+
import type PaintToolbarComponent from './PaintToolbar.svelte';
|
|
5
|
+
import type BrushPanelComponent from './BrushPanel.svelte';
|
|
6
|
+
import type PaperPanelComponent from './PaperPanel.svelte';
|
|
7
|
+
import type ColorPanelComponent from './ColorPanel.svelte';
|
|
8
|
+
import type PaintLayersPanelComponent from './PaintLayersPanel.svelte';
|
|
9
|
+
export { default as PaintEditor } from './PaintEditor.svelte';
|
|
10
|
+
export { default as PaintCanvas } from './PaintCanvas.svelte';
|
|
11
|
+
export { default as PaintToolbar } from './PaintToolbar.svelte';
|
|
12
|
+
export { default as BrushPanel } from './BrushPanel.svelte';
|
|
13
|
+
export { default as PaperPanel } from './PaperPanel.svelte';
|
|
14
|
+
export { default as ColorPanel } from './ColorPanel.svelte';
|
|
15
|
+
export { default as PaintLayersPanel } from './PaintLayersPanel.svelte';
|
|
16
|
+
export { default as PaintSlider } from './PaintSlider.svelte';
|
|
17
|
+
export { createReactivePaintEditor, paintEditorState } from './paintEditorRune.svelte.js';
|
|
18
|
+
export type PaintEditorProps = ComponentProps<typeof PaintEditorComponent>;
|
|
19
|
+
export type PaintCanvasProps = ComponentProps<typeof PaintCanvasComponent>;
|
|
20
|
+
export type PaintToolbarProps = ComponentProps<typeof PaintToolbarComponent>;
|
|
21
|
+
export type BrushPanelProps = ComponentProps<typeof BrushPanelComponent>;
|
|
22
|
+
export type PaperPanelProps = ComponentProps<typeof PaperPanelComponent>;
|
|
23
|
+
export type ColorPanelProps = ComponentProps<typeof ColorPanelComponent>;
|
|
24
|
+
export type PaintLayersPanelProps = ComponentProps<typeof PaintLayersPanelComponent>;
|
|
25
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/paint-editor/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,oBAAoB,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,oBAAoB,MAAM,sBAAsB,CAAC;AAC7D,OAAO,KAAK,qBAAqB,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,mBAAmB,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,mBAAmB,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,mBAAmB,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,yBAAyB,MAAM,2BAA2B,CAAC;AAEvE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AACxE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAE1F,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAC3E,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAC3E,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC7E,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACzE,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACzE,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC,OAAO,mBAAmB,CAAC,CAAC;AACzE,MAAM,MAAM,qBAAqB,GAAG,cAAc,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as PaintEditor } from './PaintEditor.svelte';
|
|
2
|
+
export { default as PaintCanvas } from './PaintCanvas.svelte';
|
|
3
|
+
export { default as PaintToolbar } from './PaintToolbar.svelte';
|
|
4
|
+
export { default as BrushPanel } from './BrushPanel.svelte';
|
|
5
|
+
export { default as PaperPanel } from './PaperPanel.svelte';
|
|
6
|
+
export { default as ColorPanel } from './ColorPanel.svelte';
|
|
7
|
+
export { default as PaintLayersPanel } from './PaintLayersPanel.svelte';
|
|
8
|
+
export { default as PaintSlider } from './PaintSlider.svelte';
|
|
9
|
+
export { createReactivePaintEditor, paintEditorState } from './paintEditorRune.svelte.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { PaintEditorController, type PaintEditorOptions, type PaintEditorState } from '@pixerate/editor/paint-editor';
|
|
2
|
+
/**
|
|
3
|
+
* A `PaintEditorController` (created from `options`, or the one given) with its state as a
|
|
4
|
+
* rune: `editor.state` updates after every change. Call `destroy()` to unsubscribe.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createReactivePaintEditor(options?: PaintEditorOptions | PaintEditorController): {
|
|
7
|
+
readonly controller: PaintEditorController;
|
|
8
|
+
readonly state: PaintEditorState;
|
|
9
|
+
destroy(): void;
|
|
10
|
+
};
|
|
11
|
+
/** The controller's state as a rune, for components (subscribe in an effect, see GOTCHAS). */
|
|
12
|
+
export declare function paintEditorState(controller: () => PaintEditorController): {
|
|
13
|
+
readonly current: PaintEditorState;
|
|
14
|
+
};
|
|
15
|
+
export declare const chip: (on: boolean) => string;
|
|
16
|
+
export declare const button = "px-2 py-1 rounded text-[11px] bg-slate-800 text-slate-200 hover:bg-slate-700 disabled:opacity-40";
|
|
17
|
+
export declare const panel = "flex flex-col gap-2 text-xs text-slate-200";
|
|
18
|
+
/** Runs `fn` and returns the error message it threw (or a rejected promise's, through `onAsync`). */
|
|
19
|
+
export declare function attempt(fn: () => unknown, onError: (msg: string | null) => void): void;
|
|
20
|
+
//# sourceMappingURL=paintEditorRune.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paintEditorRune.svelte.d.ts","sourceRoot":"","sources":["../../src/paint-editor/paintEditorRune.svelte.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,KAAK,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAEtH;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,GAAE,kBAAkB,GAAG,qBAA0B;;;;EAiBjG;AAED,8FAA8F;AAC9F,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,qBAAqB,GAAG;IAAE,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAA;CAAE,CAoBhH;AAED,eAAO,MAAM,IAAI,GAAI,IAAI,OAAO,WAAwH,CAAC;AACzJ,eAAO,MAAM,MAAM,qGAAqG,CAAC;AACzH,eAAO,MAAM,KAAK,+CAA+C,CAAC;AAElE,qGAAqG;AACrG,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,OAAO,EAAE,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,CAQtF"}
|