@lvl99dotcom/paintcanvas 0.7.0 → 0.8.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/lib/assets/index.d.ts +92 -0
- package/dist/lib/assets/index.js +106 -0
- package/dist/lib/assets/schema.d.ts +151 -0
- package/dist/lib/assets/schema.js +76 -0
- package/dist/lib/brushes/presetFile/index.d.ts +6 -27
- package/dist/lib/brushes/presetFile/index.js +34 -47
- package/dist/lib/brushes/types/index.d.ts +2 -11
- package/dist/lib/classes/AssetSync/index.d.ts +45 -0
- package/dist/lib/classes/AssetSync/index.js +80 -0
- package/dist/lib/classes/ColourPalettes/index.d.ts +83 -0
- package/dist/lib/classes/ColourPalettes/index.js +165 -0
- package/dist/lib/classes/CustomPresets/index.d.ts +14 -15
- package/dist/lib/classes/CustomPresets/index.js +25 -10
- package/dist/lib/classes/LayerAutosave/index.d.ts +9 -2
- package/dist/lib/classes/LayerAutosave/index.js +16 -6
- package/dist/lib/classes/PaintCanvas/index.d.ts +111 -23
- package/dist/lib/classes/PaintCanvas/index.js +219 -58
- package/dist/lib/classes/Palette/index.d.ts +3 -2
- package/dist/lib/classes/Palette/index.js +3 -2
- package/dist/lib/classes/ProfileGraphs/index.d.ts +13 -2
- package/dist/lib/classes/ProfileGraphs/index.js +22 -2
- package/dist/lib/classes/RemoteDocumentAutosave/index.d.ts +73 -0
- package/dist/lib/classes/RemoteDocumentAutosave/index.js +119 -0
- package/dist/lib/classes/SettingsAutosave/index.d.ts +7 -3
- package/dist/lib/classes/SettingsAutosave/index.js +28 -7
- package/dist/lib/classes/SettingsExtensions/index.d.ts +67 -0
- package/dist/lib/classes/SettingsExtensions/index.js +88 -0
- package/dist/lib/document/index.d.ts +61 -4
- package/dist/lib/document/index.js +173 -4
- package/dist/lib/document/schema.d.ts +154 -39
- package/dist/lib/document/schema.js +91 -25
- package/dist/lib/messages/schema.d.ts +176 -74
- package/dist/lib/messages/schema.js +2 -1
- package/dist/lib/palette/index.d.ts +8 -42
- package/dist/lib/palette/index.js +26 -59
- package/dist/lib/palette/schema.d.ts +111 -22
- package/dist/lib/palette/schema.js +51 -26
- package/dist/lib/presets/index.d.ts +1 -9
- package/dist/lib/presets/index.js +4 -10
- package/dist/lib/presets/schema.d.ts +118 -73
- package/dist/lib/presets/schema.js +28 -60
- package/dist/lib/profiles/profileFile/index.d.ts +10 -0
- package/dist/lib/profiles/profileFile/index.js +19 -0
- package/dist/lib/profiles/schema.d.ts +96 -3
- package/dist/lib/profiles/schema.js +31 -7
- package/dist/lib/schemas/index.d.ts +42 -0
- package/dist/lib/schemas/index.js +60 -0
- package/dist/lib/settings/index.d.ts +37 -22
- package/dist/lib/settings/index.js +78 -37
- package/dist/lib/settings/schema.d.ts +198 -76
- package/dist/lib/settings/schema.js +92 -86
- package/dist/lib/storage/index.d.ts +82 -3
- package/dist/lib/storage/index.js +82 -5
- package/dist/lib/validation/validators.generated.d.ts +19 -8
- package/dist/lib/validation/validators.generated.js +44716 -18900
- package/dist/lib/version/index.js +1 -1
- package/dist/locales/en-GB.js +23 -5
- package/dist/locales/en-US.js +23 -5
- package/dist/locales/fr.js +23 -5
- package/dist/locales/ja.js +23 -5
- package/dist/locales/ko.js +23 -5
- package/dist/locales/zh-Hans.js +23 -5
- package/dist/locales/zh-Hant.js +23 -5
- package/dist/package.js +1 -1
- package/dist/testing/app.d.ts +6 -2
- package/dist/testing/app.js +7 -2
- package/dist/testing/documentStore.d.ts +26 -0
- package/dist/testing/documentStore.js +31 -0
- package/dist/ui/elements/PaintCanvas/index.d.ts +6 -2
- package/dist/ui/elements/PaintCanvas/index.js +20 -6
- package/dist/ui/elements/PcAppSettings/index.d.ts +5 -6
- package/dist/ui/elements/PcAppSettings/index.js +17 -27
- package/dist/ui/elements/PcPanelBrushes/index.d.ts +1 -1
- package/dist/ui/elements/PcPanelBrushes/index.js +22 -28
- package/dist/ui/elements/PcPanelBrushes/style.js +1 -1
- package/dist/ui/elements/PcPanelColours/index.d.ts +56 -19
- package/dist/ui/elements/PcPanelColours/index.js +251 -104
- package/dist/ui/elements/PcPanelColours/style.js +1 -1
- package/dist/ui/elements/PcStats/index.d.ts +8 -4
- package/dist/ui/elements/PcStats/index.js +39 -5
- package/locales/en-GB.json +23 -5
- package/locales/en-US.json +23 -5
- package/locales/fr.json +23 -5
- package/locales/ja.json +23 -5
- package/locales/ko.json +23 -5
- package/locales/zh-Hans.json +23 -5
- package/locales/zh-Hant.json +23 -5
- package/package.json +2 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// src/lib/classes/AssetSync/index.ts
|
|
2
|
+
/** Event dispatched when loading or saving assets fails (detail: the error) */
|
|
3
|
+
var ASSET_SYNC_ERROR_EVENT = "error";
|
|
4
|
+
/**
|
|
5
|
+
* Keeps assets held in memory (e.g. profile graphs) in a store: the saved assets are added when it starts, assets only
|
|
6
|
+
* in memory are saved (e.g. ones restored from older app settings), then each change saves the assets that changed and
|
|
7
|
+
* deletes the ones that are gone.
|
|
8
|
+
*
|
|
9
|
+
* Dispatches an `error` event (with the error as `detail`) when loading or saving fails.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* const sync = new AssetSync(profiles, new IndexedDbAssetStore("profiles"));
|
|
13
|
+
* await sync.ready;
|
|
14
|
+
* sync.stop();
|
|
15
|
+
*/
|
|
16
|
+
var AssetSync = class extends EventTarget {
|
|
17
|
+
assets;
|
|
18
|
+
store;
|
|
19
|
+
/** Resolves once the saved assets are loaded and any only in memory are saved */
|
|
20
|
+
ready;
|
|
21
|
+
/** Each asset as last saved, as JSON, by ID */
|
|
22
|
+
saved = /* @__PURE__ */ new Map();
|
|
23
|
+
stopped = false;
|
|
24
|
+
/** Saving, one change after another */
|
|
25
|
+
saving = Promise.resolve();
|
|
26
|
+
onChange = () => {
|
|
27
|
+
this.saving = this.saving.then(() => this.save());
|
|
28
|
+
};
|
|
29
|
+
constructor(assets, store) {
|
|
30
|
+
super();
|
|
31
|
+
this.assets = assets;
|
|
32
|
+
this.store = store;
|
|
33
|
+
this.ready = this.start();
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Stop keeping changes (ones already being saved finish).
|
|
37
|
+
*/
|
|
38
|
+
stop() {
|
|
39
|
+
this.stopped = true;
|
|
40
|
+
this.assets.removeEventListener("change", this.onChange);
|
|
41
|
+
}
|
|
42
|
+
/** Load the saved assets, then save the ones only in memory and listen for changes */
|
|
43
|
+
async start() {
|
|
44
|
+
try {
|
|
45
|
+
const listed = await this.store.list();
|
|
46
|
+
if (this.stopped) return;
|
|
47
|
+
this.assets.merge(listed);
|
|
48
|
+
for (const asset of listed) if (typeof asset?.id === "string") this.saved.set(asset.id, JSON.stringify(asset));
|
|
49
|
+
} catch (error) {
|
|
50
|
+
this.fail(error);
|
|
51
|
+
}
|
|
52
|
+
if (this.stopped) return;
|
|
53
|
+
this.assets.addEventListener("change", this.onChange);
|
|
54
|
+
await (this.saving = this.save());
|
|
55
|
+
}
|
|
56
|
+
/** Save the assets that changed since they were last saved, and delete the ones that are gone */
|
|
57
|
+
async save() {
|
|
58
|
+
if (this.stopped) return;
|
|
59
|
+
const current = new Map(this.assets.items.map((asset) => [asset.id, asset]));
|
|
60
|
+
try {
|
|
61
|
+
for (const [id, asset] of current) {
|
|
62
|
+
const json = JSON.stringify(asset);
|
|
63
|
+
if (this.saved.get(id) === json) continue;
|
|
64
|
+
await this.store.put(asset);
|
|
65
|
+
this.saved.set(id, json);
|
|
66
|
+
}
|
|
67
|
+
for (const id of [...this.saved.keys()]) if (!current.has(id)) {
|
|
68
|
+
await this.store.remove(id);
|
|
69
|
+
this.saved.delete(id);
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
this.fail(error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/** Tell listeners loading or saving failed */
|
|
76
|
+
fail(error) {
|
|
77
|
+
this.dispatchEvent(new CustomEvent(ASSET_SYNC_ERROR_EVENT, { detail: error }));
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
export { ASSET_SYNC_ERROR_EVENT, AssetSync as default };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { type ColourPalette } from "../../palette";
|
|
2
|
+
import { type AssetAuthor } from "../../assets";
|
|
3
|
+
/** ID of the palette PaintCanvas comes with (the canvas's `palette`), which can't be renamed or deleted */
|
|
4
|
+
export declare const DEFAULT_PALETTE_ID = "paintcanvas";
|
|
5
|
+
/** Most palettes a user can keep */
|
|
6
|
+
export declare const MAX_SAVED_PALETTES = 100;
|
|
7
|
+
/**
|
|
8
|
+
* A palette name, trimmed, or undefined if it's empty or too long.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* paletteName(" Sunset "); // "Sunset"
|
|
12
|
+
*/
|
|
13
|
+
export declare const paletteName: (name: unknown) => string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Keep only valid palettes from an untrusted value (e.g. stored or imported): each with a UUID, a name and valid colours
|
|
16
|
+
* (see `sanitisePalette`), each ID once, up to `MAX_SAVED_PALETTES`. An invalid author is left out. Undefined if the
|
|
17
|
+
* value isn't an array.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* sanitiseColourPalettes([{ id: crypto.randomUUID(), name: "Sunset", colours: ["#FF0000", "red"] }]); // colours ["#ff0000"]
|
|
21
|
+
*/
|
|
22
|
+
export declare const sanitiseColourPalettes: (input: unknown) => ColourPalette[] | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* The user's own colour palettes, each named and with its own colours (as lowercase `#rrggbb`, each once, up to
|
|
25
|
+
* `MAX_PALETTE_COLOURS`), alongside the palette PaintCanvas comes with (the canvas's `palette`). Kept in memory; a
|
|
26
|
+
* palette store keeps them (see `PaintCanvas.useAssetStores`).
|
|
27
|
+
*
|
|
28
|
+
* Dispatches a `change` event when the palettes change.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* const palettes = new ColourPalettes();
|
|
32
|
+
* const sunset = palettes.create("Sunset");
|
|
33
|
+
* palettes.addColour(sunset.id, "#ff7700");
|
|
34
|
+
*/
|
|
35
|
+
export default class ColourPalettes extends EventTarget {
|
|
36
|
+
private values;
|
|
37
|
+
/** The palettes, in the order they were made */
|
|
38
|
+
get items(): readonly ColourPalette[];
|
|
39
|
+
/** A palette by ID */
|
|
40
|
+
get(id: string): ColourPalette | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Make a palette with a name (trimmed) and colours. Returns it, or undefined if the name isn't valid or there's no
|
|
43
|
+
* room.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* palettes.create("Sunset", ["#ff7700"], { name: "Ada" });
|
|
47
|
+
*/
|
|
48
|
+
create(name: string, colours?: readonly string[], author?: AssetAuthor): ColourPalette | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Rename a palette. Returns whether it was renamed.
|
|
51
|
+
*/
|
|
52
|
+
rename(id: string, name: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Delete a palette. Returns whether it was deleted.
|
|
55
|
+
*/
|
|
56
|
+
remove(id: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Add a colour at the end of a palette. Returns whether it was added: not if it's already there, the palette is full,
|
|
59
|
+
* or it isn't a `#rrggbb` colour.
|
|
60
|
+
*/
|
|
61
|
+
addColour(id: string, colour: string): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Remove the colour at a position in a palette. Returns whether it was removed.
|
|
64
|
+
*/
|
|
65
|
+
removeColour(id: string, index: number): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Add palettes (e.g. loaded from a store or imported), replacing the ones with the same IDs and keeping the others,
|
|
68
|
+
* up to `MAX_SAVED_PALETTES`. Invalid palettes are left out (see `sanitiseColourPalettes`). Returns how many were
|
|
69
|
+
* added or replaced.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* palettes.merge(colourPaletteFile.parse(text));
|
|
73
|
+
*/
|
|
74
|
+
merge(palettes: unknown): number;
|
|
75
|
+
/**
|
|
76
|
+
* Replace all the palettes (e.g. when restoring saved ones), keeping only valid ones.
|
|
77
|
+
*/
|
|
78
|
+
set(palettes: unknown): void;
|
|
79
|
+
/** Change a palette's name or colours. Returns whether it exists. */
|
|
80
|
+
private change;
|
|
81
|
+
/** Use the palettes and dispatch `change` */
|
|
82
|
+
private update;
|
|
83
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { sanitisePalette } from "../Palette/index.js";
|
|
2
|
+
import "../../palette/index.js";
|
|
3
|
+
import "../../palette/schema.js";
|
|
4
|
+
import { UUID_PATTERN } from "../../assets/schema.js";
|
|
5
|
+
import { validateColourPalette } from "../../validation/validators.generated.js";
|
|
6
|
+
// src/lib/classes/ColourPalettes/index.ts
|
|
7
|
+
/** ID of the palette PaintCanvas comes with (the canvas's `palette`), which can't be renamed or deleted */
|
|
8
|
+
var DEFAULT_PALETTE_ID = "paintcanvas";
|
|
9
|
+
/** Most palettes a user can keep */
|
|
10
|
+
var MAX_SAVED_PALETTES = 100;
|
|
11
|
+
var UUID = new RegExp(UUID_PATTERN);
|
|
12
|
+
/**
|
|
13
|
+
* A palette name, trimmed, or undefined if it's empty or too long.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* paletteName(" Sunset "); // "Sunset"
|
|
17
|
+
*/
|
|
18
|
+
var paletteName = (name) => {
|
|
19
|
+
if (typeof name !== "string") return void 0;
|
|
20
|
+
const trimmed = name.trim();
|
|
21
|
+
return trimmed.length > 0 && trimmed.length <= 100 ? trimmed : void 0;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Keep only valid palettes from an untrusted value (e.g. stored or imported): each with a UUID, a name and valid colours
|
|
25
|
+
* (see `sanitisePalette`), each ID once, up to `MAX_SAVED_PALETTES`. An invalid author is left out. Undefined if the
|
|
26
|
+
* value isn't an array.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* sanitiseColourPalettes([{ id: crypto.randomUUID(), name: "Sunset", colours: ["#FF0000", "red"] }]); // colours ["#ff0000"]
|
|
30
|
+
*/
|
|
31
|
+
var sanitiseColourPalettes = (input) => {
|
|
32
|
+
if (!Array.isArray(input)) return void 0;
|
|
33
|
+
const palettes = /* @__PURE__ */ new Map();
|
|
34
|
+
for (const palette of input) {
|
|
35
|
+
if (palettes.size >= MAX_SAVED_PALETTES) break;
|
|
36
|
+
const name = paletteName(palette?.name);
|
|
37
|
+
const colours = sanitisePalette(palette?.colours);
|
|
38
|
+
if (typeof palette?.id !== "string" || !UUID.test(palette.id) || palettes.has(palette.id) || !name || !colours) continue;
|
|
39
|
+
const valid = {
|
|
40
|
+
id: palette.id,
|
|
41
|
+
name,
|
|
42
|
+
colours
|
|
43
|
+
};
|
|
44
|
+
const withAuthor = {
|
|
45
|
+
...valid,
|
|
46
|
+
author: palette.author
|
|
47
|
+
};
|
|
48
|
+
palettes.set(palette.id, palette.author !== void 0 && validateColourPalette(withAuthor) ? withAuthor : valid);
|
|
49
|
+
}
|
|
50
|
+
return [...palettes.values()];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* The user's own colour palettes, each named and with its own colours (as lowercase `#rrggbb`, each once, up to
|
|
54
|
+
* `MAX_PALETTE_COLOURS`), alongside the palette PaintCanvas comes with (the canvas's `palette`). Kept in memory; a
|
|
55
|
+
* palette store keeps them (see `PaintCanvas.useAssetStores`).
|
|
56
|
+
*
|
|
57
|
+
* Dispatches a `change` event when the palettes change.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* const palettes = new ColourPalettes();
|
|
61
|
+
* const sunset = palettes.create("Sunset");
|
|
62
|
+
* palettes.addColour(sunset.id, "#ff7700");
|
|
63
|
+
*/
|
|
64
|
+
var ColourPalettes = class extends EventTarget {
|
|
65
|
+
values = [];
|
|
66
|
+
/** The palettes, in the order they were made */
|
|
67
|
+
get items() {
|
|
68
|
+
return this.values;
|
|
69
|
+
}
|
|
70
|
+
/** A palette by ID */
|
|
71
|
+
get(id) {
|
|
72
|
+
return this.values.find((palette) => palette.id === id);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Make a palette with a name (trimmed) and colours. Returns it, or undefined if the name isn't valid or there's no
|
|
76
|
+
* room.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* palettes.create("Sunset", ["#ff7700"], { name: "Ada" });
|
|
80
|
+
*/
|
|
81
|
+
create(name, colours = [], author) {
|
|
82
|
+
const [palette] = sanitiseColourPalettes([{
|
|
83
|
+
id: crypto.randomUUID(),
|
|
84
|
+
name,
|
|
85
|
+
colours,
|
|
86
|
+
author
|
|
87
|
+
}]);
|
|
88
|
+
if (!palette || this.values.length >= MAX_SAVED_PALETTES) return void 0;
|
|
89
|
+
this.update([...this.values, palette]);
|
|
90
|
+
return palette;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Rename a palette. Returns whether it was renamed.
|
|
94
|
+
*/
|
|
95
|
+
rename(id, name) {
|
|
96
|
+
const valid = paletteName(name);
|
|
97
|
+
return valid !== void 0 && this.change(id, { name: valid });
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Delete a palette. Returns whether it was deleted.
|
|
101
|
+
*/
|
|
102
|
+
remove(id) {
|
|
103
|
+
const next = this.values.filter((palette) => palette.id !== id);
|
|
104
|
+
if (next.length === this.values.length) return false;
|
|
105
|
+
this.update(next);
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Add a colour at the end of a palette. Returns whether it was added: not if it's already there, the palette is full,
|
|
110
|
+
* or it isn't a `#rrggbb` colour.
|
|
111
|
+
*/
|
|
112
|
+
addColour(id, colour) {
|
|
113
|
+
const palette = this.get(id);
|
|
114
|
+
if (!palette || palette.colours.length >= 1e3) return false;
|
|
115
|
+
const colours = sanitisePalette([...palette.colours, colour]);
|
|
116
|
+
return colours.length > palette.colours.length && this.change(id, { colours });
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Remove the colour at a position in a palette. Returns whether it was removed.
|
|
120
|
+
*/
|
|
121
|
+
removeColour(id, index) {
|
|
122
|
+
const palette = this.get(id);
|
|
123
|
+
if (!palette || index < 0 || index >= palette.colours.length) return false;
|
|
124
|
+
return this.change(id, { colours: palette.colours.filter((_, i) => i !== index) });
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Add palettes (e.g. loaded from a store or imported), replacing the ones with the same IDs and keeping the others,
|
|
128
|
+
* up to `MAX_SAVED_PALETTES`. Invalid palettes are left out (see `sanitiseColourPalettes`). Returns how many were
|
|
129
|
+
* added or replaced.
|
|
130
|
+
*
|
|
131
|
+
* @example
|
|
132
|
+
* palettes.merge(colourPaletteFile.parse(text));
|
|
133
|
+
*/
|
|
134
|
+
merge(palettes) {
|
|
135
|
+
const added = sanitiseColourPalettes(palettes);
|
|
136
|
+
if (!added?.length) return 0;
|
|
137
|
+
const byId = new Map(this.values.map((palette) => [palette.id, palette]));
|
|
138
|
+
for (const palette of added) byId.set(palette.id, palette);
|
|
139
|
+
const next = [...byId.values()].slice(0, MAX_SAVED_PALETTES);
|
|
140
|
+
this.update(next);
|
|
141
|
+
return added.filter((palette) => next.some(({ id }) => id === palette.id)).length;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Replace all the palettes (e.g. when restoring saved ones), keeping only valid ones.
|
|
145
|
+
*/
|
|
146
|
+
set(palettes) {
|
|
147
|
+
const next = sanitiseColourPalettes(palettes);
|
|
148
|
+
if (next) this.update(next);
|
|
149
|
+
}
|
|
150
|
+
/** Change a palette's name or colours. Returns whether it exists. */
|
|
151
|
+
change(id, changes) {
|
|
152
|
+
if (!this.get(id)) return false;
|
|
153
|
+
this.update(this.values.map((palette) => palette.id === id ? {
|
|
154
|
+
...palette,
|
|
155
|
+
...changes
|
|
156
|
+
} : palette));
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
/** Use the palettes and dispatch `change` */
|
|
160
|
+
update(palettes) {
|
|
161
|
+
this.values = palettes;
|
|
162
|
+
this.dispatchEvent(new Event("change"));
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
export { DEFAULT_PALETTE_ID, MAX_SAVED_PALETTES, ColourPalettes as default, paletteName, sanitiseColourPalettes };
|
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
import type { BrushConfig } from "../../brushes/types";
|
|
2
|
-
|
|
3
|
-
export interface PresetStoreLike {
|
|
4
|
-
/** The owner's presets, in the order they were saved */
|
|
5
|
-
list(ownerId: string): Promise<BrushConfig[]>;
|
|
6
|
-
/** Save one of the owner's presets (replacing one with the same ID) */
|
|
7
|
-
put(ownerId: string, preset: BrushConfig): Promise<void>;
|
|
8
|
-
/** Delete one of the owner's presets */
|
|
9
|
-
remove(ownerId: string, id: string): Promise<void>;
|
|
10
|
-
}
|
|
2
|
+
import type { AssetStoreLike } from "../../storage";
|
|
11
3
|
/**
|
|
12
4
|
* The user's own and imported brush presets, shared by the brush and eraser. They are kept in a store when one is
|
|
13
|
-
*
|
|
5
|
+
* used (otherwise only until the page closes).
|
|
14
6
|
*
|
|
15
7
|
* Dispatches a `change` event when presets are loaded, saved or removed.
|
|
16
8
|
*
|
|
17
9
|
* @example
|
|
18
|
-
* const custom = new CustomPresets("
|
|
10
|
+
* const custom = new CustomPresets(new IndexedDbAssetStore("brushes"));
|
|
19
11
|
* await custom.load();
|
|
20
12
|
* await custom.save({ id: crypto.randomUUID(), name: "My brush", author: { name: "Ada", handle } });
|
|
21
13
|
*/
|
|
22
14
|
export default class CustomPresets extends EventTarget {
|
|
23
15
|
/** Presets, in the order they were added */
|
|
24
16
|
presets: BrushConfig[];
|
|
25
|
-
private
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
private store?;
|
|
18
|
+
constructor(store?: AssetStoreLike<BrushConfig>);
|
|
19
|
+
/**
|
|
20
|
+
* Keep the presets in another store (or in memory only, without one), loading the presets saved there in place of
|
|
21
|
+
* the current ones.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* await custom.useStore(new IndexedDbAssetStore("brushes", "my-app"));
|
|
25
|
+
*/
|
|
26
|
+
useStore(store: AssetStoreLike<BrushConfig> | undefined): Promise<void>;
|
|
28
27
|
/**
|
|
29
|
-
* Load the user's presets from the store.
|
|
28
|
+
* Load the user's presets from the store, leaving out invalid ones.
|
|
30
29
|
*/
|
|
31
30
|
load(): Promise<void>;
|
|
32
31
|
/**
|
|
@@ -1,32 +1,47 @@
|
|
|
1
1
|
import { isOwnAuthor } from "../../brushes/authors/index.js";
|
|
2
|
+
import { isValidBrushPreset } from "../../presets/index.js";
|
|
2
3
|
// src/lib/classes/CustomPresets/index.ts
|
|
3
4
|
/**
|
|
4
5
|
* The user's own and imported brush presets, shared by the brush and eraser. They are kept in a store when one is
|
|
5
|
-
*
|
|
6
|
+
* used (otherwise only until the page closes).
|
|
6
7
|
*
|
|
7
8
|
* Dispatches a `change` event when presets are loaded, saved or removed.
|
|
8
9
|
*
|
|
9
10
|
* @example
|
|
10
|
-
* const custom = new CustomPresets("
|
|
11
|
+
* const custom = new CustomPresets(new IndexedDbAssetStore("brushes"));
|
|
11
12
|
* await custom.load();
|
|
12
13
|
* await custom.save({ id: crypto.randomUUID(), name: "My brush", author: { name: "Ada", handle } });
|
|
13
14
|
*/
|
|
14
15
|
var CustomPresets = class extends EventTarget {
|
|
15
16
|
/** Presets, in the order they were added */
|
|
16
17
|
presets = [];
|
|
17
|
-
ownerId;
|
|
18
18
|
store;
|
|
19
|
-
constructor(
|
|
19
|
+
constructor(store) {
|
|
20
20
|
super();
|
|
21
|
-
this.ownerId = ownerId;
|
|
22
21
|
this.store = store;
|
|
23
22
|
}
|
|
24
23
|
/**
|
|
25
|
-
*
|
|
24
|
+
* Keep the presets in another store (or in memory only, without one), loading the presets saved there in place of
|
|
25
|
+
* the current ones.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* await custom.useStore(new IndexedDbAssetStore("brushes", "my-app"));
|
|
29
|
+
*/
|
|
30
|
+
async useStore(store) {
|
|
31
|
+
this.store = store;
|
|
32
|
+
this.presets = [];
|
|
33
|
+
this.notify();
|
|
34
|
+
await this.load();
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Load the user's presets from the store, leaving out invalid ones.
|
|
26
38
|
*/
|
|
27
39
|
async load() {
|
|
28
|
-
|
|
29
|
-
|
|
40
|
+
const store = this.store;
|
|
41
|
+
if (!store) return;
|
|
42
|
+
const saved = await store.list();
|
|
43
|
+
if (store !== this.store) return;
|
|
44
|
+
this.presets = saved.filter((preset) => isValidBrushPreset(preset).valid);
|
|
30
45
|
this.notify();
|
|
31
46
|
}
|
|
32
47
|
/**
|
|
@@ -49,7 +64,7 @@ var CustomPresets = class extends EventTarget {
|
|
|
49
64
|
* Add presets, replacing any with the same ID, and keep them.
|
|
50
65
|
*/
|
|
51
66
|
async save(...presets) {
|
|
52
|
-
for (const preset of presets) await this.store?.put(
|
|
67
|
+
for (const preset of presets) await this.store?.put(preset);
|
|
53
68
|
const kept = this.presets.map((existing) => presets.find((preset) => preset.id === existing.id) ?? existing);
|
|
54
69
|
const added = presets.filter((preset) => !this.presets.some((existing) => existing.id === preset.id));
|
|
55
70
|
this.presets = [...kept, ...added];
|
|
@@ -59,7 +74,7 @@ var CustomPresets = class extends EventTarget {
|
|
|
59
74
|
* Delete a preset.
|
|
60
75
|
*/
|
|
61
76
|
async remove(id) {
|
|
62
|
-
await this.store?.remove(
|
|
77
|
+
await this.store?.remove(id);
|
|
63
78
|
this.presets = this.presets.filter((preset) => preset.id !== id);
|
|
64
79
|
this.notify();
|
|
65
80
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type LayerStack from "../LayerStack";
|
|
2
2
|
import type BackgroundWork from "../BackgroundWork";
|
|
3
3
|
import { type LayerStoreLike, type SavedLayer } from "../../storage";
|
|
4
|
+
import type { DocumentInfo } from "../../document/schema";
|
|
4
5
|
/** How long to wait after the layers change before saving, so quick changes are saved together (milliseconds) */
|
|
5
6
|
export declare const SAVE_DELAY_MS = 500;
|
|
6
7
|
/** Name of the save in the canvas's background work */
|
|
@@ -25,6 +26,10 @@ export interface AutosaveCanvas extends EventTarget {
|
|
|
25
26
|
readonly layers: LayerStack;
|
|
26
27
|
/** Slow work that waits for pauses in drawing */
|
|
27
28
|
readonly background: BackgroundWork;
|
|
29
|
+
/** The drawing's details (changes are followed by a `change` event) */
|
|
30
|
+
readonly documentInfo: Readonly<DocumentInfo>;
|
|
31
|
+
/** Use the details of the drawing being restored */
|
|
32
|
+
restoreDocumentInfo(info: DocumentInfo): void;
|
|
28
33
|
/** Replace the canvas size and layers with saved ones, drawing each layer's image, and select one */
|
|
29
34
|
restoreLayers(width: number, height: number, layers: readonly SavedLayer[], images: readonly CanvasImageSource[], activeLayer: string): void;
|
|
30
35
|
}
|
|
@@ -42,8 +47,8 @@ export interface AutosaveOptions {
|
|
|
42
47
|
delayMs?: number;
|
|
43
48
|
}
|
|
44
49
|
/**
|
|
45
|
-
* Keeps a canvas's layers in a store, so the drawing survives reloading the page: restores the
|
|
46
|
-
* starts, then saves soon after the layers change (once drawing pauses), and straight away when the page is hidden.
|
|
50
|
+
* Keeps a canvas's layers and the drawing's details in a store, so the drawing survives reloading the page: restores the
|
|
51
|
+
* saved drawing when it starts, then saves soon after the layers or details change (once drawing pauses), and straight away when the page is hidden.
|
|
47
52
|
* Only layers whose pixels changed are encoded and saved again: the layers' change counts tell which (see
|
|
48
53
|
* `LayerStack.pixelVersion`), without reading their pixels, which is slow on phones.
|
|
49
54
|
*
|
|
@@ -86,6 +91,8 @@ export default class LayerAutosave extends EventTarget {
|
|
|
86
91
|
/** Lets go of the drawing's lock, while held */
|
|
87
92
|
private release?;
|
|
88
93
|
private readonly onLayersChange;
|
|
94
|
+
/** The drawing's details as last saved or restored, as JSON */
|
|
95
|
+
private savedInfo?;
|
|
89
96
|
private readonly onCanvasChange;
|
|
90
97
|
private readonly onVisibilityChange;
|
|
91
98
|
constructor(canvas: AutosaveCanvas, store: LayerStoreLike, options?: AutosaveOptions);
|
|
@@ -32,8 +32,8 @@ var encodePng = (canvas) => {
|
|
|
32
32
|
*/
|
|
33
33
|
var isComplete = (drawing, images) => drawing.version === 1 && drawing.layers.length > 0 && drawing.layers.every((layer) => images.has(layer.id)) && drawing.layers.some((layer) => layer.id === drawing.activeLayer);
|
|
34
34
|
/**
|
|
35
|
-
* Keeps a canvas's layers in a store, so the drawing survives reloading the page: restores the
|
|
36
|
-
* starts, then saves soon after the layers change (once drawing pauses), and straight away when the page is hidden.
|
|
35
|
+
* Keeps a canvas's layers and the drawing's details in a store, so the drawing survives reloading the page: restores the
|
|
36
|
+
* saved drawing when it starts, then saves soon after the layers or details change (once drawing pauses), and straight away when the page is hidden.
|
|
37
37
|
* Only layers whose pixels changed are encoded and saved again: the layers' change counts tell which (see
|
|
38
38
|
* `LayerStack.pixelVersion`), without reading their pixels, which is slow on phones.
|
|
39
39
|
*
|
|
@@ -76,10 +76,13 @@ var LayerAutosave = class extends EventTarget {
|
|
|
76
76
|
/** Lets go of the drawing's lock, while held */
|
|
77
77
|
release;
|
|
78
78
|
onLayersChange = () => this.schedule();
|
|
79
|
+
/** The drawing's details as last saved or restored, as JSON */
|
|
80
|
+
savedInfo;
|
|
79
81
|
onCanvasChange = () => {
|
|
80
|
-
if (this.canvas.layers
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
if (this.canvas.layers !== this.watched) {
|
|
83
|
+
this.watch();
|
|
84
|
+
this.onLayersChange();
|
|
85
|
+
} else if (JSON.stringify(this.canvas.documentInfo) !== this.savedInfo) this.onLayersChange();
|
|
83
86
|
};
|
|
84
87
|
onVisibilityChange = (event) => {
|
|
85
88
|
if (event.target.visibilityState === "hidden") this.save();
|
|
@@ -197,7 +200,10 @@ var LayerAutosave = class extends EventTarget {
|
|
|
197
200
|
if (this.currentState !== AutosaveState.Starting) return;
|
|
198
201
|
this.saved = /* @__PURE__ */ new Map();
|
|
199
202
|
this.watch();
|
|
200
|
-
if (restored)
|
|
203
|
+
if (restored) {
|
|
204
|
+
this.remember();
|
|
205
|
+
this.savedInfo = JSON.stringify(this.canvas.documentInfo);
|
|
206
|
+
}
|
|
201
207
|
this.setState(AutosaveState.Ready);
|
|
202
208
|
if (!restored) this.onLayersChange();
|
|
203
209
|
}
|
|
@@ -210,6 +216,7 @@ var LayerAutosave = class extends EventTarget {
|
|
|
210
216
|
const decoded = await Promise.all(drawing.layers.map((layer) => this.decode(images.get(layer.id))));
|
|
211
217
|
if (this.currentState !== AutosaveState.Starting) return false;
|
|
212
218
|
this.unwatch();
|
|
219
|
+
if (drawing.document) this.canvas.restoreDocumentInfo(drawing.document);
|
|
213
220
|
this.canvas.restoreLayers(drawing.width, drawing.height, drawing.layers, decoded, drawing.activeLayer);
|
|
214
221
|
this.sizes = new Map(drawing.layers.map((layer) => [layer.id, images.get(layer.id).size]));
|
|
215
222
|
return true;
|
|
@@ -243,8 +250,10 @@ var LayerAutosave = class extends EventTarget {
|
|
|
243
250
|
encoded.set(layer.id, record);
|
|
244
251
|
encoding.push(this.encode(layer.canvas).then((image) => [layer.id, image]));
|
|
245
252
|
}
|
|
253
|
+
const info = structuredClone(this.canvas.documentInfo);
|
|
246
254
|
const drawing = {
|
|
247
255
|
version: 1,
|
|
256
|
+
document: info,
|
|
248
257
|
width: stack.width,
|
|
249
258
|
height: stack.height,
|
|
250
259
|
layers: stack.layers.map((layer) => layer.toConfig()),
|
|
@@ -256,6 +265,7 @@ var LayerAutosave = class extends EventTarget {
|
|
|
256
265
|
const kept = new Set(drawing.layers.map((layer) => layer.id));
|
|
257
266
|
for (const id of this.saved.keys()) if (!kept.has(id)) this.saved.delete(id);
|
|
258
267
|
for (const [id, record] of encoded) this.saved.set(id, record);
|
|
268
|
+
this.savedInfo = JSON.stringify(info);
|
|
259
269
|
this.error = void 0;
|
|
260
270
|
this.rememberSizes(kept, images);
|
|
261
271
|
} catch (error) {
|