@lvl99dotcom/paintcanvas 0.6.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 (119) hide show
  1. package/README.md +3 -2
  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 +124 -28
  18. package/dist/lib/classes/PaintCanvas/index.js +228 -63
  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/classes/UiSettings/index.d.ts +24 -2
  30. package/dist/lib/classes/UiSettings/index.js +32 -7
  31. package/dist/lib/document/index.d.ts +61 -4
  32. package/dist/lib/document/index.js +173 -4
  33. package/dist/lib/document/schema.d.ts +154 -39
  34. package/dist/lib/document/schema.js +91 -25
  35. package/dist/lib/messages/schema.d.ts +176 -74
  36. package/dist/lib/messages/schema.js +2 -1
  37. package/dist/lib/palette/index.d.ts +8 -42
  38. package/dist/lib/palette/index.js +26 -59
  39. package/dist/lib/palette/schema.d.ts +111 -22
  40. package/dist/lib/palette/schema.js +51 -26
  41. package/dist/lib/presets/index.d.ts +1 -9
  42. package/dist/lib/presets/index.js +4 -10
  43. package/dist/lib/presets/schema.d.ts +118 -73
  44. package/dist/lib/presets/schema.js +28 -60
  45. package/dist/lib/profiles/profileFile/index.d.ts +10 -0
  46. package/dist/lib/profiles/profileFile/index.js +19 -0
  47. package/dist/lib/profiles/schema.d.ts +96 -3
  48. package/dist/lib/profiles/schema.js +31 -7
  49. package/dist/lib/schemas/index.d.ts +42 -0
  50. package/dist/lib/schemas/index.js +60 -0
  51. package/dist/lib/settings/index.d.ts +37 -22
  52. package/dist/lib/settings/index.js +78 -37
  53. package/dist/lib/settings/schema.d.ts +202 -77
  54. package/dist/lib/settings/schema.js +93 -86
  55. package/dist/lib/storage/index.d.ts +108 -3
  56. package/dist/lib/storage/index.js +119 -5
  57. package/dist/lib/validation/validators.generated.d.ts +19 -8
  58. package/dist/lib/validation/validators.generated.js +44723 -18889
  59. package/dist/lib/version/index.d.ts +25 -0
  60. package/dist/lib/version/index.js +35 -0
  61. package/dist/locales/en-GB.js +28 -5
  62. package/dist/locales/en-US.js +28 -5
  63. package/dist/locales/fr.js +28 -5
  64. package/dist/locales/ja.js +28 -5
  65. package/dist/locales/ko.js +28 -5
  66. package/dist/locales/zh-Hans.js +28 -5
  67. package/dist/locales/zh-Hant.js +28 -5
  68. package/dist/package.js +1 -1
  69. package/dist/testing/app.d.ts +6 -2
  70. package/dist/testing/app.js +7 -2
  71. package/dist/testing/documentStore.d.ts +26 -0
  72. package/dist/testing/documentStore.js +31 -0
  73. package/dist/ui/elements/PaintCanvas/index.d.ts +19 -5
  74. package/dist/ui/elements/PaintCanvas/index.js +41 -10
  75. package/dist/ui/elements/PcAppSettings/index.d.ts +14 -7
  76. package/dist/ui/elements/PcAppSettings/index.js +58 -33
  77. package/dist/ui/elements/PcBrushConfig/index.d.ts +1 -0
  78. package/dist/ui/elements/PcBrushConfig/index.js +8 -9
  79. package/dist/ui/elements/PcBrushTest/index.d.ts +1 -0
  80. package/dist/ui/elements/PcBrushTest/index.js +8 -7
  81. package/dist/ui/elements/PcBrushTest/style.js +1 -1
  82. package/dist/ui/elements/PcButtonMenu/index.d.ts +88 -0
  83. package/dist/ui/elements/PcButtonMenu/index.js +125 -0
  84. package/dist/ui/elements/PcCanvas/index.d.ts +9 -0
  85. package/dist/ui/elements/PcCanvas/index.js +33 -17
  86. package/dist/ui/elements/PcClipboardTools/index.d.ts +7 -4
  87. package/dist/ui/elements/PcClipboardTools/index.js +20 -26
  88. package/dist/ui/elements/PcInputSettings/index.d.ts +2 -1
  89. package/dist/ui/elements/PcInputSettings/index.js +9 -15
  90. package/dist/ui/elements/PcLayerItem/index.d.ts +21 -6
  91. package/dist/ui/elements/PcLayerItem/index.js +53 -26
  92. package/dist/ui/elements/PcLayerItem/style.js +1 -1
  93. package/dist/ui/elements/PcPanelBrushes/index.d.ts +1 -1
  94. package/dist/ui/elements/PcPanelBrushes/index.js +22 -28
  95. package/dist/ui/elements/PcPanelBrushes/style.js +1 -1
  96. package/dist/ui/elements/PcPanelColours/index.d.ts +56 -19
  97. package/dist/ui/elements/PcPanelColours/index.js +251 -104
  98. package/dist/ui/elements/PcPanelColours/style.js +1 -1
  99. package/dist/ui/elements/PcSliderRange/index.js +3 -2
  100. package/dist/ui/elements/PcStats/index.d.ts +8 -4
  101. package/dist/ui/elements/PcStats/index.js +39 -5
  102. package/dist/ui/elements/PcToggleCheckbox/index.d.ts +13 -2
  103. package/dist/ui/elements/PcToggleCheckbox/index.js +27 -8
  104. package/dist/ui/elements/PcToolSettings/index.d.ts +1 -0
  105. package/dist/ui/elements/PcToolSettings/index.js +8 -8
  106. package/dist/ui/elements/PcVersion/index.d.ts +12 -0
  107. package/dist/ui/elements/PcVersion/index.js +43 -0
  108. package/dist/ui/index.d.ts +3 -1
  109. package/dist/ui/index.js +4 -2
  110. package/dist/ui/layout/index.d.ts +2 -2
  111. package/dist/ui/layout/index.js +2 -2
  112. package/locales/en-GB.json +28 -5
  113. package/locales/en-US.json +28 -5
  114. package/locales/fr.json +28 -5
  115. package/locales/ja.json +28 -5
  116. package/locales/ko.json +28 -5
  117. package/locales/zh-Hans.json +28 -5
  118. package/locales/zh-Hant.json +28 -5
  119. package/package.json +3 -2
@@ -0,0 +1,119 @@
1
+ // src/lib/classes/RemoteDocumentAutosave/index.ts
2
+ /** How often changes are saved to the remote store, at most (milliseconds): every 5 minutes */
3
+ var REMOTE_SAVE_INTERVAL_MS = 3e5;
4
+ /** Event dispatched when saving fails (detail: the error) */
5
+ var REMOTE_AUTOSAVE_ERROR_EVENT = "error";
6
+ /**
7
+ * Keeps a canvas's drawing in a remote store (e.g. a user's account online), less often than the browser keeps it:
8
+ * every `intervalMs` while there are changes not saved yet, or when asked (`save`). The whole document is saved each
9
+ * time, with a thumbnail.
10
+ *
11
+ * Dispatches `change` when saving starts, makes progress or ends, and `error` (detail: the error) when it fails (the
12
+ * changes are tried again next time).
13
+ *
14
+ * @example
15
+ * const autosave = new RemoteDocumentAutosave(canvas, myAccountStore);
16
+ * autosave.start();
17
+ * saveButton.onclick = () => autosave.save();
18
+ */
19
+ var RemoteDocumentAutosave = class extends EventTarget {
20
+ /** Where the drawing is kept */
21
+ store;
22
+ /** The last saving error, until a save succeeds */
23
+ error;
24
+ /** When the drawing was last saved, if it has been */
25
+ lastSaved;
26
+ /** How much of the save under way is done (0 to 1), if known */
27
+ progress;
28
+ canvas;
29
+ intervalMs;
30
+ timer;
31
+ /** The layers being watched for changes */
32
+ watched;
33
+ /** Whether there are changes not saved yet */
34
+ dirty = false;
35
+ /** The save being written, if any */
36
+ current;
37
+ onChange = () => {
38
+ this.dirty = true;
39
+ };
40
+ onCanvasChange = () => {
41
+ this.dirty = true;
42
+ if (this.canvas.layers === this.watched) return;
43
+ this.watched?.removeEventListener("change", this.onChange);
44
+ this.watched = this.canvas.layers;
45
+ this.watched.addEventListener("change", this.onChange);
46
+ };
47
+ constructor(canvas, store, options = {}) {
48
+ super();
49
+ this.canvas = canvas;
50
+ this.store = store;
51
+ this.intervalMs = options.intervalMs ?? 3e5;
52
+ }
53
+ /** Whether a save is under way */
54
+ get saving() {
55
+ return this.current !== void 0;
56
+ }
57
+ /** Whether there are changes not saved yet */
58
+ get hasChanges() {
59
+ return this.dirty;
60
+ }
61
+ /**
62
+ * Start saving changes every `intervalMs`.
63
+ */
64
+ start() {
65
+ if (this.timer !== void 0) return;
66
+ this.watched = this.canvas.layers;
67
+ this.watched.addEventListener("change", this.onChange);
68
+ this.canvas.addEventListener("change", this.onCanvasChange);
69
+ this.timer = setInterval(() => {
70
+ if (this.dirty) this.save();
71
+ }, this.intervalMs);
72
+ }
73
+ /**
74
+ * Stop saving changes (a save under way finishes). Changes not saved yet aren't saved.
75
+ */
76
+ stop() {
77
+ clearInterval(this.timer);
78
+ this.timer = void 0;
79
+ this.watched?.removeEventListener("change", this.onChange);
80
+ this.watched = void 0;
81
+ this.canvas.removeEventListener("change", this.onCanvasChange);
82
+ }
83
+ /**
84
+ * Save the drawing now (e.g. when the user asks). While a save is under way, that save is returned.
85
+ */
86
+ save() {
87
+ if (this.current) return this.current;
88
+ this.dirty = false;
89
+ this.progress = void 0;
90
+ this.current = this.write().finally(() => {
91
+ this.current = void 0;
92
+ this.progress = void 0;
93
+ this.notify();
94
+ });
95
+ this.notify();
96
+ return this.current;
97
+ }
98
+ /** Save the whole drawing */
99
+ async write() {
100
+ try {
101
+ const document = await this.canvas.createDocument();
102
+ await this.store.save(document, (done) => {
103
+ this.progress = Math.min(1, Math.max(0, done));
104
+ this.notify();
105
+ });
106
+ this.lastSaved = /* @__PURE__ */ new Date();
107
+ this.error = void 0;
108
+ } catch (error) {
109
+ this.dirty = true;
110
+ this.error = error;
111
+ this.dispatchEvent(new CustomEvent(REMOTE_AUTOSAVE_ERROR_EVENT, { detail: error }));
112
+ }
113
+ }
114
+ /** Tell listeners something changed */
115
+ notify() {
116
+ this.dispatchEvent(new Event("change"));
117
+ }
118
+ };
119
+ export { REMOTE_AUTOSAVE_ERROR_EVENT, REMOTE_SAVE_INTERVAL_MS, RemoteDocumentAutosave as default };
@@ -10,9 +10,11 @@ export interface SettingsAutosaveOptions {
10
10
  delayMs?: number;
11
11
  }
12
12
  /**
13
- * Keeps a canvas's app settings (input settings, keyboard shortcuts, colour palette, colours, profile graphs, theme and language) in a
14
- * store: the saved settings are restored when it starts (invalid ones are ignored), then changes are saved soon after
15
- * they happen.
13
+ * Keeps a canvas's app settings (input settings, keyboard shortcuts, colour palettes, colours, profile graphs, theme and
14
+ * language) in a store: the saved settings are restored when it starts (invalid ones are ignored), then changes are
15
+ * saved soon after they happen. While the canvas keeps colour palettes or profile graphs in stores of their own (see
16
+ * `PaintCanvas.useAssetStores`), they are left out of the saved settings (ones restored from older settings are added,
17
+ * so those stores keep them).
16
18
  *
17
19
  * Dispatches an `error` event (with the error as `detail`) when loading or saving fails.
18
20
  *
@@ -46,6 +48,8 @@ export default class SettingsAutosave extends EventTarget {
46
48
  save(): Promise<void>;
47
49
  /** Restore the saved settings, then listen for changes */
48
50
  private start;
51
+ /** The canvas's settings to save, without the assets kept in stores of their own */
52
+ private collect;
49
53
  /** Save soon, after changes stop for a moment */
50
54
  private schedule;
51
55
  /** Tell listeners loading or saving failed */
@@ -5,9 +5,11 @@ var SETTINGS_SAVE_DELAY_MS = 500;
5
5
  /** Event dispatched when loading or saving settings fails (detail: the error) */
6
6
  var SETTINGS_AUTOSAVE_ERROR_EVENT = "error";
7
7
  /**
8
- * Keeps a canvas's app settings (input settings, keyboard shortcuts, colour palette, colours, profile graphs, theme and language) in a
9
- * store: the saved settings are restored when it starts (invalid ones are ignored), then changes are saved soon after
10
- * they happen.
8
+ * Keeps a canvas's app settings (input settings, keyboard shortcuts, colour palettes, colours, profile graphs, theme and
9
+ * language) in a store: the saved settings are restored when it starts (invalid ones are ignored), then changes are
10
+ * saved soon after they happen. While the canvas keeps colour palettes or profile graphs in stores of their own (see
11
+ * `PaintCanvas.useAssetStores`), they are left out of the saved settings (ones restored from older settings are added,
12
+ * so those stores keep them).
11
13
  *
12
14
  * Dispatches an `error` event (with the error as `detail`) when loading or saving fails.
13
15
  *
@@ -42,8 +44,10 @@ var SettingsAutosave = class extends EventTarget {
42
44
  app,
43
45
  app.input,
44
46
  app.shortcuts,
45
- app.palette,
47
+ app.palettes,
46
48
  app.profiles,
49
+ app.customPresets,
50
+ app.settingsExtensions,
47
51
  app.ui
48
52
  ];
49
53
  }
@@ -63,7 +67,7 @@ var SettingsAutosave = class extends EventTarget {
63
67
  */
64
68
  async save() {
65
69
  this.timer = void 0;
66
- const settings = collectSettings(this.app);
70
+ const settings = this.collect();
67
71
  const json = JSON.stringify(settings);
68
72
  if (json === this.saved) return;
69
73
  this.saved = json;
@@ -76,15 +80,32 @@ var SettingsAutosave = class extends EventTarget {
76
80
  }
77
81
  /** Restore the saved settings, then listen for changes */
78
82
  async start() {
83
+ let older = false;
79
84
  try {
80
85
  const result = isValidSettings(await this.store.load());
81
- if (result.valid && !this.stopped) applySettings(this.app, result.value);
86
+ if (result.valid && !this.stopped) {
87
+ await applySettings(this.app, result.value);
88
+ older = result.value.palette !== void 0;
89
+ }
82
90
  } catch (error) {
83
91
  this.fail(error);
84
92
  }
85
93
  if (this.stopped) return;
86
- this.saved = JSON.stringify(collectSettings(this.app));
94
+ this.saved = JSON.stringify(this.collect());
87
95
  for (const source of this.sources) source.addEventListener("change", this.onChange);
96
+ if (older) {
97
+ this.saved = void 0;
98
+ await this.save();
99
+ }
100
+ }
101
+ /** The canvas's settings to save, without the assets kept in stores of their own */
102
+ collect() {
103
+ const stores = this.app.assetStores;
104
+ return collectSettings(this.app, { without: [
105
+ "brushes",
106
+ "palettes",
107
+ "profiles"
108
+ ].filter((kind) => stores[kind] !== void 0) });
88
109
  }
89
110
  /** Save soon, after changes stop for a moment */
90
111
  schedule() {
@@ -0,0 +1,67 @@
1
+ /** Keys of app settings PaintCanvas uses (and those of settings files), which extensions can't use */
2
+ export declare const RESERVED_SETTINGS_KEYS: readonly string[];
3
+ /**
4
+ * Settings a wrapper app adds to PaintCanvas's app settings, under a key of its own (e.g. `livecanvas`). They're kept,
5
+ * exported and imported with the app settings, wherever those are kept.
6
+ *
7
+ * @example
8
+ * const extension: SettingsExtension<{ sound: boolean }> = {
9
+ * key: "livecanvas",
10
+ * validate: (value): value is { sound: boolean } => typeof (value as { sound?: unknown })?.sound === "boolean",
11
+ * collect: () => ({ sound: mySettings.sound }),
12
+ * apply: (value) => mySettings.update(value),
13
+ * target: mySettings, // dispatches `change`
14
+ * };
15
+ */
16
+ export interface SettingsExtension<T = unknown> {
17
+ /** Key of its settings in the app settings (not one of `RESERVED_SETTINGS_KEYS`) */
18
+ key: string;
19
+ /** Whether a value (read from a store or a file, so untrusted) is valid settings for it */
20
+ validate(value: unknown): value is T;
21
+ /** Its current settings (JSON-friendly) */
22
+ collect(): T;
23
+ /** Use settings read from a store or a file */
24
+ apply(value: T): void;
25
+ /** Dispatches `change` when its settings change, so they're saved */
26
+ target?: EventTarget;
27
+ }
28
+ /**
29
+ * The settings extensions of a canvas (see `SettingsExtension`). Settings read for an extension that isn't added yet are
30
+ * kept, so they're used when it's added and aren't lost when the settings are saved meanwhile.
31
+ *
32
+ * Dispatches `change` when an extension's settings change, or one is added or removed.
33
+ *
34
+ * @example
35
+ * canvas.settingsExtensions.add(liveCanvasSettings);
36
+ */
37
+ export default class SettingsExtensions extends EventTarget {
38
+ private readonly extensions;
39
+ /** Settings read for extensions not added yet, by key */
40
+ private readonly pending;
41
+ private readonly onChange;
42
+ /** The extensions added, in order */
43
+ get items(): readonly SettingsExtension[];
44
+ /**
45
+ * Add an extension, using settings already read for it if they're valid. Throws if its key is PaintCanvas's or
46
+ * another extension's.
47
+ */
48
+ add<T>(extension: SettingsExtension<T>): void;
49
+ /**
50
+ * Remove an extension by key. Returns whether it was there.
51
+ */
52
+ remove(key: string): boolean;
53
+ /**
54
+ * Every extension's settings, by key, with settings kept for extensions not added yet.
55
+ *
56
+ * @example
57
+ * canvas.settingsExtensions.collect(); // { livecanvas: { sound: true } }
58
+ */
59
+ collect(): Record<string, unknown>;
60
+ /**
61
+ * Use the parts of app settings that aren't PaintCanvas's: each extension's, if valid; ones for extensions not added
62
+ * yet are kept for them.
63
+ */
64
+ apply(settings: Record<string, unknown>): void;
65
+ /** Tell listeners something changed */
66
+ private notify;
67
+ }
@@ -0,0 +1,88 @@
1
+ // src/lib/classes/SettingsExtensions/index.ts
2
+ /** Keys of app settings PaintCanvas uses (and those of settings files), which extensions can't use */
3
+ var RESERVED_SETTINGS_KEYS = [
4
+ "$schema",
5
+ "@type",
6
+ "input",
7
+ "shortcuts",
8
+ "palette",
9
+ "palettes",
10
+ "colours",
11
+ "profiles",
12
+ "brushes",
13
+ "ui"
14
+ ];
15
+ /**
16
+ * The settings extensions of a canvas (see `SettingsExtension`). Settings read for an extension that isn't added yet are
17
+ * kept, so they're used when it's added and aren't lost when the settings are saved meanwhile.
18
+ *
19
+ * Dispatches `change` when an extension's settings change, or one is added or removed.
20
+ *
21
+ * @example
22
+ * canvas.settingsExtensions.add(liveCanvasSettings);
23
+ */
24
+ var SettingsExtensions = class extends EventTarget {
25
+ extensions = /* @__PURE__ */ new Map();
26
+ /** Settings read for extensions not added yet, by key */
27
+ pending = /* @__PURE__ */ new Map();
28
+ onChange = () => this.notify();
29
+ /** The extensions added, in order */
30
+ get items() {
31
+ return [...this.extensions.values()];
32
+ }
33
+ /**
34
+ * Add an extension, using settings already read for it if they're valid. Throws if its key is PaintCanvas's or
35
+ * another extension's.
36
+ */
37
+ add(extension) {
38
+ const { key } = extension;
39
+ if (RESERVED_SETTINGS_KEYS.includes(key) || this.extensions.has(key)) throw new Error(`Settings key "${key}" is already used`);
40
+ this.extensions.set(key, extension);
41
+ if (this.pending.has(key)) {
42
+ const value = this.pending.get(key);
43
+ this.pending.delete(key);
44
+ if (extension.validate(value)) extension.apply(value);
45
+ }
46
+ extension.target?.addEventListener("change", this.onChange);
47
+ this.notify();
48
+ }
49
+ /**
50
+ * Remove an extension by key. Returns whether it was there.
51
+ */
52
+ remove(key) {
53
+ const extension = this.extensions.get(key);
54
+ if (!extension) return false;
55
+ extension.target?.removeEventListener("change", this.onChange);
56
+ this.extensions.delete(key);
57
+ this.notify();
58
+ return true;
59
+ }
60
+ /**
61
+ * Every extension's settings, by key, with settings kept for extensions not added yet.
62
+ *
63
+ * @example
64
+ * canvas.settingsExtensions.collect(); // { livecanvas: { sound: true } }
65
+ */
66
+ collect() {
67
+ const parts = Object.fromEntries(this.pending);
68
+ for (const [key, extension] of this.extensions) parts[key] = extension.collect();
69
+ return parts;
70
+ }
71
+ /**
72
+ * Use the parts of app settings that aren't PaintCanvas's: each extension's, if valid; ones for extensions not added
73
+ * yet are kept for them.
74
+ */
75
+ apply(settings) {
76
+ for (const [key, value] of Object.entries(settings)) {
77
+ if (RESERVED_SETTINGS_KEYS.includes(key) || value === void 0) continue;
78
+ const extension = this.extensions.get(key);
79
+ if (!extension) this.pending.set(key, value);
80
+ else if (extension.validate(value)) extension.apply(value);
81
+ }
82
+ }
83
+ /** Tell listeners something changed */
84
+ notify() {
85
+ this.dispatchEvent(new Event("change"));
86
+ }
87
+ };
88
+ export { RESERVED_SETTINGS_KEYS, SettingsExtensions as default };
@@ -1,6 +1,12 @@
1
1
  /** The themes: light, dark, or following the device's (system) */
2
2
  export declare const THEMES: readonly ["light", "dark", "system"];
3
3
  export type Theme = (typeof THEMES)[number];
4
+ /**
5
+ * Where canvases draw: `cpu` keeps their pixels in regular memory (reading and saving them never waits for the GPU),
6
+ * `gpu` asks the browser to keep them on the GPU (drawing may be quicker; the browser can still use the CPU)
7
+ */
8
+ export declare const RENDERINGS: readonly ["cpu", "gpu"];
9
+ export type Rendering = (typeof RENDERINGS)[number];
4
10
  /** Longest locale tag kept in UI settings (characters) */
5
11
  export declare const MAX_LOCALE_LENGTH = 35;
6
12
  /** A locale tag's characters: letters, digits and hyphens (e.g. `zh-Hant`) */
@@ -13,6 +19,14 @@ export declare const LOCALE_PATTERN = "^[A-Za-z0-9-]+$";
13
19
  * isTheme(undefined); // false
14
20
  */
15
21
  export declare const isTheme: (value: unknown) => value is Theme;
22
+ /**
23
+ * Whether a value is one of the renderings.
24
+ *
25
+ * @example
26
+ * isRendering("gpu"); // true
27
+ * isRendering("webgl"); // false
28
+ */
29
+ export declare const isRendering: (value: unknown) => value is Rendering;
16
30
  /**
17
31
  * Whether a value can be a locale tag (letters, digits and hyphens, at most `MAX_LOCALE_LENGTH` characters).
18
32
  *
@@ -27,7 +41,14 @@ export interface UiSettingsData {
27
41
  theme: Theme;
28
42
  /** The language's locale tag, e.g. `fr`, or undefined to follow the device's languages */
29
43
  locale?: string;
44
+ /**
45
+ * Where canvases draw (`cpu` by default). Browsers fix this when a canvas is made, so a change applies the next time
46
+ * the canvas starts (e.g. after reloading the page; see `PaintCanvasOptions.rendering`).
47
+ */
48
+ rendering: Rendering;
30
49
  }
50
+ /** UI settings used for anything not given */
51
+ export declare const DEFAULT_UI_SETTINGS: Readonly<UiSettingsData>;
31
52
  /** UI settings the UI can show and change: PaintCanvas's `UiSettings`, or anything with the same data and `update` */
32
53
  export interface UiSettingsLike extends EventTarget {
33
54
  readonly data: Readonly<UiSettingsData>;
@@ -38,11 +59,12 @@ export interface UiSettingsLike extends EventTarget {
38
59
  * or invalid. Other properties are left out.
39
60
  *
40
61
  * @example
41
- * sanitiseUiSettings({ theme: "dark", locale: 42 }, { theme: "system" }); // { theme: "dark" }
62
+ * sanitiseUiSettings({ theme: "dark", locale: 42 }, { theme: "system", rendering: "cpu" });
63
+ * // { theme: "dark", rendering: "cpu" }
42
64
  */
43
65
  export declare const sanitiseUiSettings: (input: unknown, defaults: UiSettingsData) => UiSettingsData;
44
66
  /**
45
- * The user's interface choices: the theme and the language. Kept in memory; the implementer saves and restores them
67
+ * The user's interface choices: the theme, the language and where canvases draw. Kept in memory; the implementer saves and restores them
46
68
  * with the other app settings (see `SettingsAutosave`), and the UI shows and applies them (e.g. `<pc-theme-picker>` and
47
69
  * `<pc-input-settings>`).
48
70
  *
@@ -5,6 +5,11 @@ var THEMES = [
5
5
  "dark",
6
6
  "system"
7
7
  ];
8
+ /**
9
+ * Where canvases draw: `cpu` keeps their pixels in regular memory (reading and saving them never waits for the GPU),
10
+ * `gpu` asks the browser to keep them on the GPU (drawing may be quicker; the browser can still use the CPU)
11
+ */
12
+ var RENDERINGS = ["cpu", "gpu"];
8
13
  /** Longest locale tag kept in UI settings (characters) */
9
14
  var MAX_LOCALE_LENGTH = 35;
10
15
  /** A locale tag's characters: letters, digits and hyphens (e.g. `zh-Hant`) */
@@ -18,6 +23,14 @@ var LOCALE_PATTERN = "^[A-Za-z0-9-]+$";
18
23
  */
19
24
  var isTheme = (value) => THEMES.includes(value);
20
25
  /**
26
+ * Whether a value is one of the renderings.
27
+ *
28
+ * @example
29
+ * isRendering("gpu"); // true
30
+ * isRendering("webgl"); // false
31
+ */
32
+ var isRendering = (value) => RENDERINGS.includes(value);
33
+ /**
21
34
  * Whether a value can be a locale tag (letters, digits and hyphens, at most `MAX_LOCALE_LENGTH` characters).
22
35
  *
23
36
  * @example
@@ -25,24 +38,35 @@ var isTheme = (value) => THEMES.includes(value);
25
38
  * isLocaleTag("<script>"); // false
26
39
  */
27
40
  var isLocaleTag = (value) => typeof value === "string" && value.length <= 35 && (/* @__PURE__ */ new RegExp("^[A-Za-z0-9-]+$")).test(value);
41
+ /** UI settings used for anything not given */
42
+ var DEFAULT_UI_SETTINGS = {
43
+ theme: "system",
44
+ rendering: "cpu"
45
+ };
28
46
  /**
29
47
  * Keep only valid UI settings from an untrusted value (e.g. stored or imported), using `defaults` for anything missing
30
48
  * or invalid. Other properties are left out.
31
49
  *
32
50
  * @example
33
- * sanitiseUiSettings({ theme: "dark", locale: 42 }, { theme: "system" }); // { theme: "dark" }
51
+ * sanitiseUiSettings({ theme: "dark", locale: 42 }, { theme: "system", rendering: "cpu" });
52
+ * // { theme: "dark", rendering: "cpu" }
34
53
  */
35
54
  var sanitiseUiSettings = (input, defaults) => {
36
55
  const value = typeof input === "object" && input !== null ? input : {};
37
56
  const theme = isTheme(value.theme) ? value.theme : defaults.theme;
38
57
  const locale = isLocaleTag(value.locale) ? value.locale : defaults.locale;
39
- return locale === void 0 ? { theme } : {
58
+ const rendering = isRendering(value.rendering) ? value.rendering : defaults.rendering;
59
+ return locale === void 0 ? {
60
+ theme,
61
+ rendering
62
+ } : {
40
63
  theme,
41
- locale
64
+ locale,
65
+ rendering
42
66
  };
43
67
  };
44
68
  /**
45
- * The user's interface choices: the theme and the language. Kept in memory; the implementer saves and restores them
69
+ * The user's interface choices: the theme, the language and where canvases draw. Kept in memory; the implementer saves and restores them
46
70
  * with the other app settings (see `SettingsAutosave`), and the UI shows and applies them (e.g. `<pc-theme-picker>` and
47
71
  * `<pc-input-settings>`).
48
72
  *
@@ -57,7 +81,7 @@ var UiSettings = class extends EventTarget {
57
81
  values;
58
82
  constructor(initial) {
59
83
  super();
60
- this.values = sanitiseUiSettings(initial, { theme: "system" });
84
+ this.values = sanitiseUiSettings(initial, DEFAULT_UI_SETTINGS);
61
85
  }
62
86
  /** Current settings (read-only copy) */
63
87
  get data() {
@@ -74,9 +98,10 @@ var UiSettings = class extends EventTarget {
74
98
  ...this.values,
75
99
  ...changes
76
100
  }, this.values);
77
- if (next.theme === this.values.theme && next.locale === this.values.locale) return;
101
+ const keys = Object.keys(next);
102
+ if (keys.length === Object.keys(this.values).length && keys.every((key) => next[key] === this.values[key])) return;
78
103
  this.values = next;
79
104
  this.dispatchEvent(new Event("change"));
80
105
  }
81
106
  };
82
- export { LOCALE_PATTERN, MAX_LOCALE_LENGTH, THEMES, UiSettings as default, isLocaleTag, isTheme, sanitiseUiSettings };
107
+ export { DEFAULT_UI_SETTINGS, LOCALE_PATTERN, MAX_LOCALE_LENGTH, RENDERINGS, THEMES, UiSettings as default, isLocaleTag, isRendering, isTheme, sanitiseUiSettings };
@@ -1,17 +1,74 @@
1
- import type { PaintCanvasDocument } from "./schema";
1
+ import type { AssetAuthor, AssetAuthorDetails } from "../assets/schema";
2
+ import { type DocumentFile, type DocumentInfo, type DrawingDocument } from "./schema";
2
3
  export * from "./schema";
3
- /** Result of checking a document */
4
+ /** The `format` of documents from before versioned schemas */
5
+ export declare const LEGACY_DOCUMENT_FORMAT = "paintcanvas-document";
6
+ /** Name of a new document, or one from before documents had names */
7
+ export declare const UNTITLED_DOCUMENT_NAME = "Untitled";
8
+ /** Result of checking a document file */
4
9
  export type DocumentValidationResult = {
5
10
  valid: true;
6
- value: PaintCanvasDocument;
11
+ value: DocumentFile;
7
12
  } | {
8
13
  valid: false;
9
14
  errors: string[];
10
15
  };
11
16
  /**
12
- * Check a document (e.g. read from a file) against `documentSchema`, and that its selected layer exists.
17
+ * Check a document file (e.g. read from a file) against `documentSchema`, and that its selected layer exists.
13
18
  *
14
19
  * @example
15
20
  * isValidDocument(JSON.parse(text)).valid; // true
16
21
  */
17
22
  export declare const isValidDocument: (input: unknown) => DocumentValidationResult;
23
+ /**
24
+ * An author's details: their name alone becomes `{ name }`.
25
+ *
26
+ * @example
27
+ * authorDetails("Ada"); // { name: "Ada" }
28
+ */
29
+ export declare const authorDetails: (author: AssetAuthor) => AssetAuthorDetails;
30
+ /**
31
+ * Whether two authors are the same person: by DID when both have one, otherwise by handle when both have one,
32
+ * otherwise by name.
33
+ *
34
+ * @example
35
+ * sameAuthor({ name: "Ada", handle: "ada.test" }, { name: "Ada L", handle: "ada.test" }); // true
36
+ */
37
+ export declare const sameAuthor: (a: AssetAuthorDetails, b: AssetAuthorDetails) => boolean;
38
+ /**
39
+ * A document's authors with another added at the end, unless they're already one of them.
40
+ *
41
+ * @example
42
+ * withAuthor([{ name: "Ada" }], "Ann"); // [{ name: "Ada" }, { name: "Ann" }]
43
+ */
44
+ export declare const withAuthor: (authors: readonly AssetAuthorDetails[], author: AssetAuthor) => AssetAuthorDetails[];
45
+ /**
46
+ * Details for a new document: a new ID, a name, an author and the time now.
47
+ *
48
+ * @example
49
+ * newDocumentInfo("Ada"); // { id: "1b9d…", name: "Untitled", author: [{ name: "Ada" }], dateCreated: "2026-…", … }
50
+ */
51
+ export declare const newDocumentInfo: (author?: AssetAuthor, name?: string, now?: Date) => DocumentInfo;
52
+ /**
53
+ * A document as a file (JSON), with its images as PNG data URLs.
54
+ *
55
+ * @example
56
+ * JSON.stringify(await documentToFile(await canvas.createDocument()));
57
+ */
58
+ export declare const documentToFile: (document: DrawingDocument) => Promise<DocumentFile>;
59
+ /**
60
+ * Read a document file (e.g. parsed from JSON), with its images as PNG blobs. Files from before versioned schemas are
61
+ * migrated. Throws an `AssetFileError`: `invalid` if it isn't a document or doesn't match its schema, `unsupported` if
62
+ * it's a version the app can't read.
63
+ *
64
+ * @example
65
+ * await canvas.openDocument(documentFromFile(JSON.parse(text)));
66
+ */
67
+ export declare const documentFromFile: (data: unknown, now?: Date) => DrawingDocument;
68
+ /**
69
+ * The name of the file a document is exported to, starting with a prefix (see `PaintCanvasNames.filePrefix`).
70
+ *
71
+ * @example
72
+ * documentFileName("Sunset at sea"); // "paintcanvas-sunset-at-sea.json"
73
+ */
74
+ export declare const documentFileName: (name: string, prefix?: string) => string;