@pixerate/editor-svelte 0.197.0 → 0.198.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
|
-
import { brushEditorScope, brushEditorSections, PAINT_PROFILE, setBrushEditorValue, type Brush, type BrushHostProfile } from '@pixerate/editor/brushes';
|
|
3
|
+
import { brushEditorScope, brushEditorSections, PAINT_PROFILE, setBrushEditorValue, withBrushSource, type Brush, type BrushHostProfile, type BrushSource } from '@pixerate/editor/brushes';
|
|
4
4
|
import PaintSlider from '../paint-editor/PaintSlider.svelte';
|
|
5
5
|
import PixelCanvas from '../paint-editor/PixelCanvas.svelte';
|
|
6
6
|
import { button, chip } from './styles.js';
|
|
@@ -27,10 +27,17 @@
|
|
|
27
27
|
onsaveas?: (name: string) => unknown;
|
|
28
28
|
/** Extra controls a host adds to a section (e.g. the sticker picker for stamps). */
|
|
29
29
|
extra?: Snippet<[string]>;
|
|
30
|
+
/** The library's image sources, offered as stamps and as grain. */
|
|
31
|
+
sources?: ReadonlyArray<Pick<BrushSource, 'id' | 'name'>>;
|
|
32
|
+
/**
|
|
33
|
+
* With it, "Add image…" buttons for an image tip and for grain: it stores the picked file as a
|
|
34
|
+
* source (e.g. `addBrushSourceFromFile`) and returns its `source:<id>` ref.
|
|
35
|
+
*/
|
|
36
|
+
onaddsource?: (file: File) => Promise<string>;
|
|
30
37
|
class?: string;
|
|
31
38
|
}
|
|
32
|
-
let { brush, profile = PAINT_PROFILE, onchange, preview, previewBackground, onsave, saveLabel = 'Save', onsaveas, extra, class: className = '' }: Props = $props();
|
|
33
|
-
const sections = $derived(brushEditorSections(brush, profile));
|
|
39
|
+
let { brush, profile = PAINT_PROFILE, onchange, preview, previewBackground, onsave, saveLabel = 'Save', onsaveas, extra, sources, onaddsource, class: className = '' }: Props = $props();
|
|
40
|
+
const sections = $derived(brushEditorSections(brush, profile, { sources }));
|
|
34
41
|
const scope = $derived(brushEditorScope(brush, profile));
|
|
35
42
|
let problems = $state<string | null>(null);
|
|
36
43
|
let name = $state('');
|
|
@@ -39,6 +46,17 @@
|
|
|
39
46
|
problems = r.problems.length ? r.problems.join(' ') : null;
|
|
40
47
|
if (r.brush !== brush) onchange(r.brush);
|
|
41
48
|
};
|
|
49
|
+
/** Stores a picked image as a source and makes it the tip or the grain. */
|
|
50
|
+
const addSource = async (file: File | undefined, as: 'tip' | 'grain') => {
|
|
51
|
+
if (!file || !onaddsource) return;
|
|
52
|
+
try {
|
|
53
|
+
const r = withBrushSource(brush, await onaddsource(file), as);
|
|
54
|
+
problems = r.problems.length ? r.problems.join(' ') : null;
|
|
55
|
+
onchange(r.brush);
|
|
56
|
+
} catch (e) {
|
|
57
|
+
problems = (e as Error).message;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
42
60
|
const setColour = (path: string, light: string, dark: string | null) => set(path, dark === null ? light : { light, dark });
|
|
43
61
|
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)));
|
|
44
62
|
const attemptSave = async (fn: () => unknown, after?: () => void) => {
|
|
@@ -140,6 +158,23 @@
|
|
|
140
158
|
{/each}
|
|
141
159
|
</div>
|
|
142
160
|
{/if}
|
|
161
|
+
{#if onaddsource && (c.path === 'tip.stamps' || c.path === 'tip.grain.source')}
|
|
162
|
+
{@const label = c.path === 'tip.stamps' ? 'Add image tip…' : 'Add grain image…'}
|
|
163
|
+
<label class={`${button} cursor-pointer self-start`}>
|
|
164
|
+
{label}
|
|
165
|
+
<input
|
|
166
|
+
type="file"
|
|
167
|
+
accept="image/*"
|
|
168
|
+
aria-label={label}
|
|
169
|
+
class="sr-only"
|
|
170
|
+
onchange={(e) => {
|
|
171
|
+
const file = e.currentTarget.files?.[0];
|
|
172
|
+
e.currentTarget.value = '';
|
|
173
|
+
void addSource(file, c.path === 'tip.stamps' ? 'tip' : 'grain');
|
|
174
|
+
}}
|
|
175
|
+
/>
|
|
176
|
+
</label>
|
|
177
|
+
{/if}
|
|
143
178
|
{/each}
|
|
144
179
|
{#if extra}{@render extra(s.id)}{/if}
|
|
145
180
|
</section>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
import { type Brush, type BrushHostProfile } from '@pixerate/editor/brushes';
|
|
2
|
+
import { type Brush, type BrushHostProfile, type BrushSource } from '@pixerate/editor/brushes';
|
|
3
3
|
interface Props {
|
|
4
4
|
/** The brush being edited, as facets. */
|
|
5
5
|
brush: Brush;
|
|
@@ -26,6 +26,13 @@ interface Props {
|
|
|
26
26
|
onsaveas?: (name: string) => unknown;
|
|
27
27
|
/** Extra controls a host adds to a section (e.g. the sticker picker for stamps). */
|
|
28
28
|
extra?: Snippet<[string]>;
|
|
29
|
+
/** The library's image sources, offered as stamps and as grain. */
|
|
30
|
+
sources?: ReadonlyArray<Pick<BrushSource, 'id' | 'name'>>;
|
|
31
|
+
/**
|
|
32
|
+
* With it, "Add image…" buttons for an image tip and for grain: it stores the picked file as a
|
|
33
|
+
* source (e.g. `addBrushSourceFromFile`) and returns its `source:<id>` ref.
|
|
34
|
+
*/
|
|
35
|
+
onaddsource?: (file: File) => Promise<string>;
|
|
29
36
|
class?: string;
|
|
30
37
|
}
|
|
31
38
|
declare const BrushEditor: import("svelte").Component<Props, {}, "">;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { brushPreviewColours, checkBrushEdit, newBrushFor, saveBrushEdit, type Brush, type BrushHostProfile, type BrushLibrary, type BrushPreviewRenderer } from '@pixerate/editor/brushes';
|
|
2
|
+
import { addBrushSourceFromFile, brushPreviewColours, checkBrushEdit, newBrushFor, saveBrushEdit, type Brush, type BrushHostProfile, type BrushLibrary, type BrushPreviewRenderer, type BrushSource } from '@pixerate/editor/brushes';
|
|
3
3
|
import { button } from './styles.js';
|
|
4
4
|
import BrushEditor from './BrushEditor.svelte';
|
|
5
5
|
|
|
@@ -32,6 +32,20 @@
|
|
|
32
32
|
let draft = $state<Brush | null>(null);
|
|
33
33
|
let status = $state<string | null>(null);
|
|
34
34
|
let preview = $state<string | null>(null);
|
|
35
|
+
let sources = $state<BrushSource[]>([]);
|
|
36
|
+
|
|
37
|
+
// The library's image sources, for image tips and grain (kept current as sources are added).
|
|
38
|
+
$effect(() => {
|
|
39
|
+
const lib = library;
|
|
40
|
+
let live = true;
|
|
41
|
+
const read = () => void lib.listSources().then((list) => live && (sources = list), () => undefined);
|
|
42
|
+
read();
|
|
43
|
+
const off = lib.subscribe(read);
|
|
44
|
+
return () => {
|
|
45
|
+
live = false;
|
|
46
|
+
off();
|
|
47
|
+
};
|
|
48
|
+
});
|
|
35
49
|
|
|
36
50
|
$effect(() => {
|
|
37
51
|
const lib = library;
|
|
@@ -91,7 +105,7 @@
|
|
|
91
105
|
BrushEditor on a brush library for one host: loads the brush, edits a draft and saves it with saveBrushEdit. A saved
|
|
92
106
|
brush is changed in place, a built-in is saved as a copy, "Save brush" saves under a new name, and "New brush" starts
|
|
93
107
|
from the current one with only what the host uses. Facets the host can't use are kept on save; a new brush must pass
|
|
94
|
-
the host's check.
|
|
108
|
+
the host's check. Images added for an image tip or grain are stored in the library as sources.
|
|
95
109
|
-->
|
|
96
110
|
{#if draft}
|
|
97
111
|
<div class={`flex flex-col gap-2 text-xs text-slate-200 ${className}`} data-testid="brush-library-editor">
|
|
@@ -114,6 +128,8 @@
|
|
|
114
128
|
onsave={original ? () => save() : undefined}
|
|
115
129
|
saveLabel={builtin ? 'Save a copy' : 'Save'}
|
|
116
130
|
onsaveas={(name) => save(name)}
|
|
131
|
+
{sources}
|
|
132
|
+
onaddsource={async (file) => (await addBrushSourceFromFile(library, file)).ref}
|
|
117
133
|
/>
|
|
118
134
|
{#if errors.length}
|
|
119
135
|
<ul class="text-[11px] text-rose-400" data-testid="brush-library-editor-issues">
|
|
@@ -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 { brushColourHex, brushPreviewColours, PAINT_PROFILE, type Brush, type BrushLibraryEntry } from '@pixerate/editor/brushes';
|
|
4
|
+
import { addBrushSourceFromFile, brushColourHex, brushPreviewColours, PAINT_PROFILE, type Brush, type BrushLibraryEntry } from '@pixerate/editor/brushes';
|
|
5
5
|
import { untrack } from 'svelte';
|
|
6
6
|
import BrushEditor from '../brushes/BrushEditor.svelte';
|
|
7
7
|
import BrushLibraryBrowser from '../brushes/BrushLibraryBrowser.svelte';
|
|
@@ -39,6 +39,11 @@
|
|
|
39
39
|
void brush;
|
|
40
40
|
return untrack(() => controller.getBrushSpec()!);
|
|
41
41
|
});
|
|
42
|
+
// The image sources the editor paints with, read again after every change (sources added to the library).
|
|
43
|
+
const sources = $derived.by(() => {
|
|
44
|
+
void snap;
|
|
45
|
+
return untrack(() => controller.brushSources.all());
|
|
46
|
+
});
|
|
42
47
|
// Previewed in the brush's own default colour when it has one, else the paint colour.
|
|
43
48
|
const own = $derived(typeof spec.colour?.default === 'string' && /^#[0-9a-f]{6}/i.test(spec.colour.default) ? spec.colour.default.slice(0, 7) : undefined);
|
|
44
49
|
const preview = $derived.by(() => {
|
|
@@ -92,10 +97,19 @@
|
|
|
92
97
|
{/if}
|
|
93
98
|
{/snippet}
|
|
94
99
|
|
|
95
|
-
<!-- The brush library (BrushLibraryBrowser) and the active brush in the BrushEditor, plus the brush gradient, sticker stamps, what the brush carries, import and export. -->
|
|
100
|
+
<!-- The brush library (BrushLibraryBrowser) and the active brush in the BrushEditor, plus the brush gradient, sticker stamps, what the brush carries, import and export. Images picked for an image tip or grain go into the brush library as sources. -->
|
|
96
101
|
<div class={`${panel} ${className}`} data-testid="brush-panel">
|
|
97
102
|
<BrushLibraryBrowser {entries} activeId={brush.id} onselect={(id) => controller.setBrush({ id })} thumbnail={(id) => thumbs.get(id) ?? null} {thumbnailWidth} {thumbnailHeight} />
|
|
98
|
-
<BrushEditor
|
|
103
|
+
<BrushEditor
|
|
104
|
+
brush={spec}
|
|
105
|
+
profile={PAINT_PROFILE}
|
|
106
|
+
{preview}
|
|
107
|
+
onchange={(next) => run(() => controller.setBrushSpec(next))}
|
|
108
|
+
onsaveas={(name) => controller.saveBrush({ id: slug(name), name })}
|
|
109
|
+
sources={sources}
|
|
110
|
+
onaddsource={async (file) => (await addBrushSourceFromFile(controller.brushLibrary, file)).ref}
|
|
111
|
+
{extra}
|
|
112
|
+
/>
|
|
99
113
|
<div class="flex flex-wrap items-center gap-1">
|
|
100
114
|
<button type="button" class={button} onclick={() => run(async () => downloadBytes(JSON.stringify(await controller.exportBrushes(), null, 2), 'brushes.json', 'application/json'))}>Export</button>
|
|
101
115
|
<button type="button" class={button} onclick={() => fileInput?.click()}>Import…</button>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pixerate/editor-svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.198.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.268.0"
|
|
179
179
|
},
|
|
180
180
|
"peerDependencies": {
|
|
181
181
|
"@dagrejs/dagre": "^1.1.4",
|