@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.
Files changed (89) hide show
  1. package/README.md +1 -0
  2. package/dist/lib/assets/index.d.ts +92 -0
  3. package/dist/lib/assets/index.js +106 -0
  4. package/dist/lib/assets/schema.d.ts +151 -0
  5. package/dist/lib/assets/schema.js +76 -0
  6. package/dist/lib/brushes/presetFile/index.d.ts +6 -27
  7. package/dist/lib/brushes/presetFile/index.js +34 -47
  8. package/dist/lib/brushes/types/index.d.ts +2 -11
  9. package/dist/lib/classes/AssetSync/index.d.ts +45 -0
  10. package/dist/lib/classes/AssetSync/index.js +80 -0
  11. package/dist/lib/classes/ColourPalettes/index.d.ts +83 -0
  12. package/dist/lib/classes/ColourPalettes/index.js +165 -0
  13. package/dist/lib/classes/CustomPresets/index.d.ts +14 -15
  14. package/dist/lib/classes/CustomPresets/index.js +25 -10
  15. package/dist/lib/classes/LayerAutosave/index.d.ts +9 -2
  16. package/dist/lib/classes/LayerAutosave/index.js +16 -6
  17. package/dist/lib/classes/PaintCanvas/index.d.ts +111 -23
  18. package/dist/lib/classes/PaintCanvas/index.js +219 -58
  19. package/dist/lib/classes/Palette/index.d.ts +3 -2
  20. package/dist/lib/classes/Palette/index.js +3 -2
  21. package/dist/lib/classes/ProfileGraphs/index.d.ts +13 -2
  22. package/dist/lib/classes/ProfileGraphs/index.js +22 -2
  23. package/dist/lib/classes/RemoteDocumentAutosave/index.d.ts +73 -0
  24. package/dist/lib/classes/RemoteDocumentAutosave/index.js +119 -0
  25. package/dist/lib/classes/SettingsAutosave/index.d.ts +7 -3
  26. package/dist/lib/classes/SettingsAutosave/index.js +28 -7
  27. package/dist/lib/classes/SettingsExtensions/index.d.ts +67 -0
  28. package/dist/lib/classes/SettingsExtensions/index.js +88 -0
  29. package/dist/lib/document/index.d.ts +61 -4
  30. package/dist/lib/document/index.js +173 -4
  31. package/dist/lib/document/schema.d.ts +154 -39
  32. package/dist/lib/document/schema.js +91 -25
  33. package/dist/lib/messages/schema.d.ts +176 -74
  34. package/dist/lib/messages/schema.js +2 -1
  35. package/dist/lib/palette/index.d.ts +8 -42
  36. package/dist/lib/palette/index.js +26 -59
  37. package/dist/lib/palette/schema.d.ts +111 -22
  38. package/dist/lib/palette/schema.js +51 -26
  39. package/dist/lib/presets/index.d.ts +1 -9
  40. package/dist/lib/presets/index.js +4 -10
  41. package/dist/lib/presets/schema.d.ts +118 -73
  42. package/dist/lib/presets/schema.js +28 -60
  43. package/dist/lib/profiles/profileFile/index.d.ts +10 -0
  44. package/dist/lib/profiles/profileFile/index.js +19 -0
  45. package/dist/lib/profiles/schema.d.ts +96 -3
  46. package/dist/lib/profiles/schema.js +31 -7
  47. package/dist/lib/schemas/index.d.ts +42 -0
  48. package/dist/lib/schemas/index.js +60 -0
  49. package/dist/lib/settings/index.d.ts +37 -22
  50. package/dist/lib/settings/index.js +78 -37
  51. package/dist/lib/settings/schema.d.ts +198 -76
  52. package/dist/lib/settings/schema.js +92 -86
  53. package/dist/lib/storage/index.d.ts +82 -3
  54. package/dist/lib/storage/index.js +82 -5
  55. package/dist/lib/validation/validators.generated.d.ts +19 -8
  56. package/dist/lib/validation/validators.generated.js +44716 -18900
  57. package/dist/lib/version/index.js +1 -1
  58. package/dist/locales/en-GB.js +23 -5
  59. package/dist/locales/en-US.js +23 -5
  60. package/dist/locales/fr.js +23 -5
  61. package/dist/locales/ja.js +23 -5
  62. package/dist/locales/ko.js +23 -5
  63. package/dist/locales/zh-Hans.js +23 -5
  64. package/dist/locales/zh-Hant.js +23 -5
  65. package/dist/package.js +1 -1
  66. package/dist/testing/app.d.ts +6 -2
  67. package/dist/testing/app.js +7 -2
  68. package/dist/testing/documentStore.d.ts +26 -0
  69. package/dist/testing/documentStore.js +31 -0
  70. package/dist/ui/elements/PaintCanvas/index.d.ts +6 -2
  71. package/dist/ui/elements/PaintCanvas/index.js +20 -6
  72. package/dist/ui/elements/PcAppSettings/index.d.ts +5 -6
  73. package/dist/ui/elements/PcAppSettings/index.js +17 -27
  74. package/dist/ui/elements/PcPanelBrushes/index.d.ts +1 -1
  75. package/dist/ui/elements/PcPanelBrushes/index.js +22 -28
  76. package/dist/ui/elements/PcPanelBrushes/style.js +1 -1
  77. package/dist/ui/elements/PcPanelColours/index.d.ts +56 -19
  78. package/dist/ui/elements/PcPanelColours/index.js +251 -104
  79. package/dist/ui/elements/PcPanelColours/style.js +1 -1
  80. package/dist/ui/elements/PcStats/index.d.ts +8 -4
  81. package/dist/ui/elements/PcStats/index.js +39 -5
  82. package/locales/en-GB.json +23 -5
  83. package/locales/en-US.json +23 -5
  84. package/locales/fr.json +23 -5
  85. package/locales/ja.json +23 -5
  86. package/locales/ko.json +23 -5
  87. package/locales/zh-Hans.json +23 -5
  88. package/locales/zh-Hant.json +23 -5
  89. 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
- /** Where custom presets are kept (see `PresetStore`) */
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
- * given (otherwise only until the page closes).
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("user-1", myPresetStore);
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 readonly ownerId;
26
- private readonly store?;
27
- constructor(ownerId: string, store?: PresetStoreLike);
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
- * given (otherwise only until the page closes).
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("user-1", myPresetStore);
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(ownerId, store) {
19
+ constructor(store) {
20
20
  super();
21
- this.ownerId = ownerId;
22
21
  this.store = store;
23
22
  }
24
23
  /**
25
- * Load the user's presets from the store.
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
- if (!this.store) return;
29
- this.presets = await this.store.list(this.ownerId);
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(this.ownerId, preset);
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(this.ownerId, id);
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 saved drawing when it
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 saved drawing when it
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 === this.watched) return;
81
- this.watch();
82
- this.onLayersChange();
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) this.remember();
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) {