@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
@@ -157,6 +157,8 @@ export declare class PaintCanvasElement extends PcElement {
157
157
  private watchedAutosave?;
158
158
  /** Keeps the app settings under the `settings-store` name, if any */
159
159
  private ownSettingsAutosave?;
160
+ /** The asset stores the layout gave the canvas for `settings-store`, if any */
161
+ private ownAssetStores?;
160
162
  /** Stops keeping the rendering chosen in the canvas's UI settings under the `settings-store` name, if kept */
161
163
  private stopKeepingRendering?;
162
164
  /** Whether drawing was blocked when shortcuts were last watched */
@@ -205,8 +207,10 @@ export declare class PaintCanvasElement extends PcElement {
205
207
  /** Keeps the app settings under the `settings-store` name, if any (e.g. to wait until they are restored with `ready`) */
206
208
  get settingsAutosave(): SettingsAutosave | undefined;
207
209
  /**
208
- * Keep the canvas's app settings under the `settings-store` name, stopping the previous settings autosave (which
209
- * saves any change waiting to be saved).
210
+ * Keep the canvas's app settings and assets (brush presets, colour palettes, profile graphs) under the
211
+ * `settings-store` name, stopping the previous settings autosave (which saves any change waiting to be saved). Asset
212
+ * stores the layout gave a previous canvas, or this one under another name, are taken back; ones the implementer gave
213
+ * are replaced only when there's a name.
210
214
  */
211
215
  private useSettingsStore;
212
216
  /**
@@ -5,7 +5,8 @@ import ChangeController from "../../ChangeController/index.js";
5
5
  import PaintCanvas, { Panel } from "../../../lib/classes/PaintCanvas/index.js";
6
6
  import KeyboardShortcuts, { SHORTCUT_EVENT } from "../../../lib/classes/KeyboardShortcuts/index.js";
7
7
  import { isTouchDevice } from "../../../lib/classes/InputSettings/index.js";
8
- import IndexedDbLayerStore, { IndexedDbSettingsStore, loadRenderingChoice, saveRenderingChoice } from "../../../lib/storage/index.js";
8
+ import IndexedDbLayerStore, { indexedDbStores, loadRenderingChoice, saveRenderingChoice } from "../../../lib/storage/index.js";
9
+ import { ASSET_SYNC_ERROR_EVENT } from "../../../lib/classes/AssetSync/index.js";
9
10
  import SettingsAutosave, { SETTINGS_AUTOSAVE_ERROR_EVENT } from "../../../lib/classes/SettingsAutosave/index.js";
10
11
  import { AUTOSAVE_ERROR_EVENT, AutosaveState } from "../../../lib/classes/LayerAutosave/index.js";
11
12
  import { isOwnAuthor } from "../../../lib/brushes/authors/index.js";
@@ -130,6 +131,8 @@ var PaintCanvasElement = class extends PcElement {
130
131
  watchedAutosave;
131
132
  /** Keeps the app settings under the `settings-store` name, if any */
132
133
  ownSettingsAutosave;
134
+ /** The asset stores the layout gave the canvas for `settings-store`, if any */
135
+ ownAssetStores;
133
136
  /** Stops keeping the rendering chosen in the canvas's UI settings under the `settings-store` name, if kept */
134
137
  stopKeepingRendering;
135
138
  /** Whether drawing was blocked when shortcuts were last watched */
@@ -218,7 +221,7 @@ var PaintCanvasElement = class extends PcElement {
218
221
  willUpdate(changed) {
219
222
  super.willUpdate(changed);
220
223
  if (changed.has("app") || changed.has("store")) this.useStore(changed.get("app"));
221
- if (changed.has("app") || changed.has("settingsStore")) this.useSettingsStore();
224
+ if (changed.has("app") || changed.has("settingsStore")) this.useSettingsStore(changed.get("app"));
222
225
  this.watchAutosaveErrors();
223
226
  if (this.isConnected && (changed.has("app") || changed.has("shortcuts") || this.blocked !== this.blockedShortcuts)) this.watchShortcuts();
224
227
  }
@@ -284,16 +287,27 @@ var PaintCanvasElement = class extends PcElement {
284
287
  return this.ownSettingsAutosave;
285
288
  }
286
289
  /**
287
- * Keep the canvas's app settings under the `settings-store` name, stopping the previous settings autosave (which
288
- * saves any change waiting to be saved).
290
+ * Keep the canvas's app settings and assets (brush presets, colour palettes, profile graphs) under the
291
+ * `settings-store` name, stopping the previous settings autosave (which saves any change waiting to be saved). Asset
292
+ * stores the layout gave a previous canvas, or this one under another name, are taken back; ones the implementer gave
293
+ * are replaced only when there's a name.
289
294
  */
290
- useSettingsStore() {
295
+ useSettingsStore(previousApp) {
296
+ const app = this.app;
291
297
  this.ownSettingsAutosave?.stop();
292
298
  this.stopKeepingRendering?.();
293
299
  const name = this.settingsStore || void 0;
294
300
  this.stopKeepingRendering = name === void 0 ? void 0 : this.keepRendering(name);
295
- this.ownSettingsAutosave = name === void 0 ? void 0 : new SettingsAutosave(this.app, new IndexedDbSettingsStore(name));
301
+ const { settings, ...assets } = name === void 0 ? { settings: void 0 } : indexedDbStores(name);
302
+ this.ownSettingsAutosave = settings && new SettingsAutosave(app, settings);
296
303
  this.ownSettingsAutosave?.addEventListener(SETTINGS_AUTOSAVE_ERROR_EVENT, this.onSettingsAutosaveError);
304
+ const own = this.ownAssetStores;
305
+ const isOwn = (canvas) => own !== void 0 && canvas?.assetStores.brushes === own.brushes;
306
+ if (previousApp !== app && isOwn(previousApp)) previousApp?.useAssetStores({});
307
+ if (settings) app.useAssetStores(assets);
308
+ else if (isOwn(app)) app.useAssetStores({});
309
+ this.ownAssetStores = settings ? assets : void 0;
310
+ app.profileSync?.addEventListener(ASSET_SYNC_ERROR_EVENT, this.onSettingsAutosaveError);
297
311
  }
298
312
  /**
299
313
  * Keep the rendering chosen in the canvas's UI settings (e.g. when they're restored, imported or changed) under a
@@ -9,8 +9,8 @@ import { type SaveMethod } from "../../../lib/rendering/screenshot";
9
9
  * The user's app settings: a "Draw with the GPU" switch (the user's `ui.data.rendering`, used from the next start, so it
10
10
  * says when the page needs reloading), and buttons: "Keyboard shortcuts", which opens a window to change them (see
11
11
  * `pc-shortcut-settings`), and "Import settings" and "Export settings", which read and write a settings JSON file (input
12
- * settings, keyboard shortcuts, colour palette, colours, profile graphs, theme, language and rendering). The window is a shaded
13
- * `<pc-dialog-window>` over the whole page; it closes with its ⨉ button, Escape or a press outside it, and focus goes
12
+ * settings, keyboard shortcuts, colours, the user's colour palettes, profile graphs and brush presets, theme, language,
13
+ * rendering, and settings wrapper apps added). The window is a shaded `<pc-dialog-window>` over the whole page; it closes with its ⨉ button, Escape or a press outside it, and focus goes
14
14
  * back to the button. Fires `pc-toast` after importing, or when importing or exporting fails.
15
15
  *
16
16
  * @example
@@ -47,15 +47,14 @@ export declare class PcAppSettings extends PcElement {
47
47
  /** Close the keyboard shortcuts window */
48
48
  closeShortcuts(): void;
49
49
  /**
50
- * Import the settings in a settings file, using the parts it has.
50
+ * Import the settings in a settings file, using the parts it has (the user's colour palettes, profile graphs and brush
51
+ * presets are added to theirs).
51
52
  */
52
53
  importFile(file: File): Promise<void>;
53
54
  /**
54
- * Export the settings as a settings file.
55
+ * Export the settings, with the user's colour palettes, profile graphs and brush presets, as a settings file.
55
56
  */
56
57
  exportFile(): Promise<void>;
57
- /** Show a settings file error (one of `SETTINGS_FILE_ERRORS`) in the chosen language */
58
- private toastError;
59
58
  /** The keyboard shortcuts window, with its contents only while open */
60
59
  private renderShortcuts;
61
60
  /**
@@ -1,25 +1,25 @@
1
- import { SETTINGS_FILE_FORMAT } from "../../../lib/settings/schema.js";
2
1
  import { ICON_CONTROLS_CSS, PILLS_CSS, WINDOWS_CSS } from "../../styles/index.js";
3
2
  import { PcElement, defineElement, emit } from "../../base/index.js";
4
3
  import ChangeController from "../../ChangeController/index.js";
5
4
  import "../PcShortcutSettings/index.js";
6
5
  import "../PcToggleCheckbox/index.js";
7
6
  import "../PcDialogWindow/index.js";
8
- import { SETTINGS_FILE_ERRORS, SettingsFileError, applySettings, collectSettings, exportSettingsFile, parseSettingsFile, settingsFileName } from "../../../lib/settings/index.js";
7
+ import { AssetFileError } from "../../../lib/assets/index.js";
8
+ import { applySettings, collectSettings, exportSettingsFile, parseSettingsFile, settingsFileName } from "../../../lib/settings/index.js";
9
9
  import { JSON_FILE_TYPE, saveFile } from "../../../lib/rendering/screenshot/index.js";
10
10
  import { css, html, nothing, unsafeCSS } from "lit";
11
11
  // src/ui/elements/PcAppSettings/index.ts
12
- /** Translation keys of the settings file error messages */
13
- var SETTINGS_FILE_ERROR_KEYS = {
14
- [SETTINGS_FILE_ERRORS.incompatible]: "paintCanvas.settings.fileIncompatible",
15
- [SETTINGS_FILE_ERRORS.read]: "paintCanvas.settings.fileReadFailed"
12
+ /** Translation keys of the messages shown when importing a settings file fails, by reason */
13
+ var IMPORT_ERROR_KEYS = {
14
+ invalid: "paintCanvas.settings.fileIncompatible",
15
+ unsupported: "paintCanvas.settings.fileUnsupported"
16
16
  };
17
17
  /**
18
18
  * The user's app settings: a "Draw with the GPU" switch (the user's `ui.data.rendering`, used from the next start, so it
19
19
  * says when the page needs reloading), and buttons: "Keyboard shortcuts", which opens a window to change them (see
20
20
  * `pc-shortcut-settings`), and "Import settings" and "Export settings", which read and write a settings JSON file (input
21
- * settings, keyboard shortcuts, colour palette, colours, profile graphs, theme, language and rendering). The window is a shaded
22
- * `<pc-dialog-window>` over the whole page; it closes with its ⨉ button, Escape or a press outside it, and focus goes
21
+ * settings, keyboard shortcuts, colours, the user's colour palettes, profile graphs and brush presets, theme, language,
22
+ * rendering, and settings wrapper apps added). The window is a shaded `<pc-dialog-window>` over the whole page; it closes with its ⨉ button, Escape or a press outside it, and focus goes
23
23
  * back to the button. Fires `pc-toast` after importing, or when importing or exporting fails.
24
24
  *
25
25
  * @example
@@ -85,36 +85,33 @@ var PcAppSettings = class extends PcElement {
85
85
  this.shortcutsOpen = false;
86
86
  }
87
87
  /**
88
- * Import the settings in a settings file, using the parts it has.
88
+ * Import the settings in a settings file, using the parts it has (the user's colour palettes, profile graphs and brush
89
+ * presets are added to theirs).
89
90
  */
90
91
  async importFile(file) {
91
92
  const app = this.app;
92
93
  if (!app) return;
93
- let text;
94
94
  try {
95
- text = await file.text();
96
- } catch {
97
- this.toastError(SETTINGS_FILE_ERRORS.read);
98
- return;
99
- }
100
- try {
101
- applySettings(app, parseSettingsFile(text, [app.names.settingsFileFormat, SETTINGS_FILE_FORMAT]));
95
+ await applySettings(app, parseSettingsFile(await file.text()));
102
96
  emit(this, "pc-toast", {
103
97
  message: this.t("paintCanvas.settings.imported"),
104
98
  kind: "success"
105
99
  });
106
100
  } catch (error) {
107
- this.toastError(error instanceof SettingsFileError ? error.message : SETTINGS_FILE_ERRORS.read);
101
+ emit(this, "pc-toast", {
102
+ message: this.t(error instanceof AssetFileError && IMPORT_ERROR_KEYS[error.reason] || "paintCanvas.settings.fileReadFailed"),
103
+ kind: "error"
104
+ });
108
105
  }
109
106
  }
110
107
  /**
111
- * Export the settings as a settings file.
108
+ * Export the settings, with the user's colour palettes, profile graphs and brush presets, as a settings file.
112
109
  */
113
110
  async exportFile() {
114
111
  const app = this.app;
115
112
  if (!app) return;
116
113
  try {
117
- await this.saveFile(new Blob([exportSettingsFile(collectSettings(app), app.names.settingsFileFormat)], { type: "application/json" }), settingsFileName(void 0, app.names.filePrefix));
114
+ await this.saveFile(new Blob([exportSettingsFile(collectSettings(app))], { type: "application/json" }), settingsFileName(void 0, app.names.filePrefix));
118
115
  } catch {
119
116
  emit(this, "pc-toast", {
120
117
  message: this.t("paintCanvas.settings.exportFailed"),
@@ -122,13 +119,6 @@ var PcAppSettings = class extends PcElement {
122
119
  });
123
120
  }
124
121
  }
125
- /** Show a settings file error (one of `SETTINGS_FILE_ERRORS`) in the chosen language */
126
- toastError(message) {
127
- emit(this, "pc-toast", {
128
- message: this.t(SETTINGS_FILE_ERROR_KEYS[message]),
129
- kind: "error"
130
- });
131
- }
132
122
  /** The keyboard shortcuts window, with its contents only while open */
133
123
  renderShortcuts(app) {
134
124
  return html`<pc-dialog-window
@@ -102,7 +102,7 @@ export declare class PcPanelBrushes extends PcPanel {
102
102
  */
103
103
  exportFile(): Promise<void>;
104
104
  /**
105
- * A preset file error's message in the chosen language (or the fallback's, for any other error).
105
+ * The message, in the chosen language, for importing or exporting a preset file failing.
106
106
  */
107
107
  private presetFileError;
108
108
  /**
@@ -1,5 +1,4 @@
1
1
  import style_default from "./style.js";
2
- import { BRUSH_PRESET_FILE_FORMAT } from "../../../lib/presets/schema.js";
3
2
  import { NO_TEXT_SELECTION_CSS, PILLS_CSS, WINDOWS_CSS } from "../../styles/index.js";
4
3
  import { defineElement } from "../../base/index.js";
5
4
  import { PcPanel } from "../PcPanel/index.js";
@@ -7,19 +6,25 @@ import { askConfirm, deletePresetQuestion } from "../../ask/index.js";
7
6
  import "../PcBrushSize/index.js";
8
7
  import ChangeController from "../../ChangeController/index.js";
9
8
  import { groupPresetsByAuthor } from "../../../lib/brushes/authors/index.js";
10
- import "../../../lib/presets/index.js";
11
- import { PRESET_FILE_ERRORS, PresetFileError, exportPresetFile, parsePresetFile, presetFileName } from "../../../lib/brushes/presetFile/index.js";
9
+ import { brushPresetFile } from "../../../lib/brushes/presetFile/index.js";
10
+ import { AssetFileError } from "../../../lib/assets/index.js";
12
11
  import { DELETE_CURSOR } from "../../../lib/rendering/cursors/index.js";
13
12
  import { JSON_FILE_TYPE, saveFile } from "../../../lib/rendering/screenshot/index.js";
14
13
  import { html, nothing, unsafeCSS } from "lit";
15
14
  import { styleMap } from "lit/directives/style-map.js";
16
15
  // src/ui/elements/PcPanelBrushes/index.ts
17
- /** Translation keys of the preset file error messages */
16
+ /** Translation keys of the messages shown when importing or exporting a preset file fails, by reason */
18
17
  var PRESET_FILE_ERROR_KEYS = {
19
- [PRESET_FILE_ERRORS.incompatible]: "paintCanvas.presets.fileIncompatible",
20
- [PRESET_FILE_ERRORS.read]: "paintCanvas.presets.fileReadFailed",
21
- [PRESET_FILE_ERRORS.export]: "paintCanvas.presets.exportFailed",
22
- [PRESET_FILE_ERRORS.empty]: "paintCanvas.presets.nothingToExport"
18
+ import: {
19
+ invalid: "paintCanvas.presets.fileIncompatible",
20
+ unsupported: "paintCanvas.presets.fileUnsupported"
21
+ },
22
+ export: { empty: "paintCanvas.presets.nothingToExport" }
23
+ };
24
+ /** Translation keys of the messages shown when importing or exporting fails for any other reason */
25
+ var PRESET_FILE_FALLBACK_KEYS = {
26
+ import: "paintCanvas.presets.fileReadFailed",
27
+ export: "paintCanvas.presets.exportFailed"
23
28
  };
24
29
  /** Size the previews of presets without a family are measured against, so a 50px preset fills its tile */
25
30
  var UNGROUPED_FULL_SIZE = 50;
@@ -149,18 +154,8 @@ var PcPanelBrushes = class extends PcPanel {
149
154
  async importFile(file) {
150
155
  const app = this.app;
151
156
  if (!app) return;
152
- let text;
153
- try {
154
- text = await file.text();
155
- } catch {
156
- this.emit("pc-toast", {
157
- message: this.presetFileError(void 0, PRESET_FILE_ERRORS.read),
158
- kind: "error"
159
- });
160
- return;
161
- }
162
157
  try {
163
- const presets = parsePresetFile(text, [app.names.presetFileFormat, BRUSH_PRESET_FILE_FORMAT]);
158
+ const presets = brushPresetFile.parse(await file.text());
164
159
  await app.customPresets.save(...presets);
165
160
  this.emit("pc-toast", {
166
161
  message: this.t("paintCanvas.presets.imported", { count: presets.length }),
@@ -168,7 +163,7 @@ var PcPanelBrushes = class extends PcPanel {
168
163
  });
169
164
  } catch (error) {
170
165
  this.emit("pc-toast", {
171
- message: this.presetFileError(error, PRESET_FILE_ERRORS.read),
166
+ message: this.presetFileError(error, "import"),
172
167
  kind: "error"
173
168
  });
174
169
  }
@@ -180,22 +175,21 @@ var PcPanelBrushes = class extends PcPanel {
180
175
  const app = this.app;
181
176
  if (!app) return;
182
177
  try {
183
- const json = exportPresetFile(app.customPresets.presets, app.names.presetFileFormat);
184
- await this.saveFile(new Blob([json], { type: "application/json" }), presetFileName(void 0, app.names.filePrefix));
178
+ const json = brushPresetFile.write(app.customPresets.presets);
179
+ await this.saveFile(new Blob([json], { type: "application/json" }), brushPresetFile.fileName(void 0, app.names.filePrefix));
185
180
  } catch (error) {
186
181
  this.emit("pc-toast", {
187
- message: this.presetFileError(error, PRESET_FILE_ERRORS.export),
182
+ message: this.presetFileError(error, "export"),
188
183
  kind: "error"
189
184
  });
190
185
  }
191
186
  }
192
187
  /**
193
- * A preset file error's message in the chosen language (or the fallback's, for any other error).
188
+ * The message, in the chosen language, for importing or exporting a preset file failing.
194
189
  */
195
- presetFileError(error, fallback) {
196
- const message = error instanceof PresetFileError ? error.message : fallback;
197
- const key = PRESET_FILE_ERROR_KEYS[message];
198
- return key ? this.t(key) : message;
190
+ presetFileError(error, direction) {
191
+ const key = error instanceof AssetFileError && PRESET_FILE_ERROR_KEYS[direction][error.reason] || PRESET_FILE_FALLBACK_KEYS[direction];
192
+ return this.t(key);
199
193
  }
200
194
  /**
201
195
  * A preset group's heading in the chosen language ("My presets" and "Unknown" are translated; author names aren't).
@@ -1,3 +1,3 @@
1
1
  // src/ui/elements/PcPanelBrushes/style.css?inline
2
- var style_default = "/* Narrower than other panels by default (set --pc-panel-width to change it) */\n:host {\n --pc-panel-default-width: 240px;\n}\n/* The current preset on one row: its shape, name and size */\n.current {\n display: flex;\n align-items: center;\n gap: 8px;\n min-height: 44px;\n padding: 4px 8px;\n border: 1px solid var(--pc-border);\n background: var(--pc-surface);\n}\n.current-shape {\n flex: none;\n width: 28px;\n height: 28px;\n border-radius: 50%;\n background: var(--pc-text);\n}\n.current-shape.image {\n border-radius: 0;\n mask-repeat: no-repeat;\n mask-size: contain;\n mask-position: center;\n}\n.name {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.size {\n flex: none;\n align-self: stretch;\n margin: -4px -8px -4px 0;\n border-left: 1px solid var(--pc-border);\n}\n/* The presets scroll, at most 400px tall, or less so the whole menu fits under the settings bar in a shorter window */\n.presets {\n flex: 1 1 auto;\n min-height: 0;\n max-height: min(400px, calc(100vh - 180px));\n overflow-y: auto;\n}\nh3 {\n margin: 4px 0;\n font-size: 14px;\n}\nul {\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n align-content: start;\n margin: 0;\n padding: 0;\n list-style: none;\n border: 1px solid var(--pc-border);\n background: var(--pc-bar);\n}\n:host([deleting]) li button.custom {\n cursor: var(--pc-delete-cursor);\n}\n/* Create, Import and Export side by side */\n.actions {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 6px;\n}\n.visually-hidden {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\nli button {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n aspect-ratio: 1;\n padding: 0;\n border: none;\n border-right: 1px solid var(--pc-border);\n border-bottom: 1px solid var(--pc-border);\n background: var(--pc-bar);\n cursor: pointer;\n}\nli button:hover {\n background: var(--pc-surface-hover);\n}\nli button[aria-pressed=\"true\"] {\n background: var(--pc-surface-active);\n}\n.shape {\n background: var(--pc-text);\n border-radius: 50%;\n}\n.shape.image {\n border-radius: 0;\n mask-repeat: no-repeat;\n mask-size: 100% 100%;\n}\n";
2
+ var style_default = "/* Narrower than other panels by default (set --pc-panel-width to change it) */\n:host {\n --pc-panel-default-width: 240px;\n}\n/* The current preset on one row: its shape, name and size */\n.current {\n display: flex;\n align-items: center;\n gap: 8px;\n min-height: 44px;\n padding: 4px 8px;\n border: 1px solid var(--pc-border);\n background: var(--pc-surface);\n}\n.current-shape {\n flex: none;\n width: 28px;\n height: 28px;\n border-radius: 50%;\n background: var(--pc-text);\n}\n.current-shape.image {\n border-radius: 0;\n mask-repeat: no-repeat;\n mask-size: contain;\n mask-position: center;\n}\n.name {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.size {\n flex: none;\n align-self: stretch;\n margin: -4px -8px -4px 0;\n border-left: 1px solid var(--pc-border);\n}\n/* The presets scroll, at most 400px tall, or less so the whole menu fits under the settings bar in a shorter window */\n.presets {\n flex: 1 1 auto;\n min-height: 0;\n max-height: min(400px, calc(100vh - 180px));\n overflow-y: auto;\n}\nh3 {\n margin: 4px 0;\n font-size: 14px;\n}\nul {\n display: grid;\n grid-template-columns: repeat(5, 1fr);\n align-content: start;\n margin: 0;\n padding: 0;\n list-style: none;\n border: 1px solid var(--pc-border);\n background: var(--pc-bar);\n}\n:host([deleting]) li button.custom {\n cursor: var(--pc-delete-cursor);\n}\n/* Create, Import and Export side by side */\n.actions {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 6px;\n}\n/* New brush has a row of its own, above Import and Export */\n.actions .new-preset {\n grid-column: 1 / -1;\n}\n.visually-hidden {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip-path: inset(50%);\n white-space: nowrap;\n}\nli button {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n aspect-ratio: 1;\n padding: 0;\n border: none;\n border-right: 1px solid var(--pc-border);\n border-bottom: 1px solid var(--pc-border);\n background: var(--pc-bar);\n cursor: pointer;\n}\nli button:hover {\n background: var(--pc-surface-hover);\n}\nli button[aria-pressed=\"true\"] {\n background: var(--pc-surface-active);\n}\n.shape {\n background: var(--pc-text);\n border-radius: 50%;\n}\n.shape.image {\n border-radius: 0;\n mask-repeat: no-repeat;\n mask-size: 100% 100%;\n}\n";
3
3
  export { style_default as default };
@@ -2,9 +2,12 @@ import { nothing, type PropertyValues } from "lit";
2
2
  import { PcPanel } from "../PcPanel";
3
3
  import "../PcColourPair";
4
4
  import "../PcMenu";
5
+ import "../PcContextMenu";
5
6
  import "../PcDialog";
6
7
  import type { default as PaintCanvas } from "../../../lib/classes/PaintCanvas";
7
8
  import { type Hsl, type Rgb } from "../../../lib/utils/colour";
9
+ import { type ColourPalette } from "../../../lib/palette";
10
+ import { type ConfirmFn, type PromptFn } from "../../ask";
8
11
  import { type SaveMethod } from "../../../lib/rendering/screenshot";
9
12
  /** The colour models the sliders can show */
10
13
  export type ColourModel = "rgb" | "hsl";
@@ -21,13 +24,31 @@ export type ColourModel = "rgb" | "hsl";
21
24
  export declare const channelGradient: (key: string, rgb: Rgb, hsl: Hsl) => string;
22
25
  /** How long a touch is held on a saved colour to open its menu (milliseconds) */
23
26
  export declare const LONG_PRESS_MS = 500;
27
+ /** A palette shown in the panel: one of the user's own, or the one PaintCanvas comes with */
28
+ interface ShownPalette {
29
+ id: string;
30
+ name: string;
31
+ colours: readonly string[];
32
+ /** Whether it's one of the user's own (which can be renamed, exported and deleted) */
33
+ own: boolean;
34
+ }
35
+ /** A saved colour: its palette's ID and its position */
36
+ interface SwatchRef {
37
+ palette: string;
38
+ index: number;
39
+ }
40
+ /** Actions in a palette's menu */
41
+ declare const PALETTE_ACTIONS: readonly ["rename", "export", "delete"];
24
42
  /**
25
43
  * The colour palette menu: the foreground and background colours (the selected one is configured), its hex value, RGB
26
- * and HSL sliders, the saved colours, and buttons to import and export them as a JSON file.
44
+ * and HSL sliders, the colour palettes (the user's own above the one PaintCanvas comes with), and buttons to make a
45
+ * palette and to import and export the user's palettes as a JSON file.
27
46
  *
28
47
  * - Pressing the foreground or background colour selects it.
29
- * - Pressing a saved colour sets the selected colour; [+] saves the selected colour.
30
- * - A saved colour is deleted, after asking, by clicking it with Alt/Option held (the cursor shows an "X"), from its
48
+ * - Pressing a saved colour sets the selected colour (changing it afterwards doesn't change the palette); a palette's [+]
49
+ * saves the selected colour in it. PaintCanvas's palette can't be changed.
50
+ * - Each of the user's palettes has a ⋮ menu to rename, export or delete it (deleting asks first).
51
+ * - A colour in one of the user's palettes is deleted, after asking, by clicking it with Alt/Option held (the cursor shows an "X"), from its
31
52
  * menu (right-click, a long press on touch screens, or the context menu key), or with Delete or Backspace. Arrow keys,
32
53
  * Home and End move between saved colours.
33
54
  * - Escape closes it (closes the canvas's open panel).
@@ -82,19 +103,22 @@ export declare class PcPanelColours extends PcPanel {
82
103
  /** The hex value being typed, while it differs from the colour */
83
104
  hexDraft?: string;
84
105
  /** The saved colour whose menu is open, and where */
85
- menu?: {
86
- index: number;
106
+ menu?: SwatchRef & {
87
107
  x: number;
88
108
  y: number;
89
109
  };
90
110
  /** The saved colour being deleted, while asking */
91
- confirming?: number;
111
+ confirming?: SwatchRef;
112
+ /** Asks the user to confirm, in a dialog (replaceable for tests) */
113
+ confirm: ConfirmFn;
114
+ /** Asks the user for text, in a dialog (replaceable for tests) */
115
+ prompt: PromptFn;
92
116
  /** Saves exported files (replaceable for tests) */
93
117
  saveFile: (blob: Blob, name: string) => Promise<SaveMethod | undefined>;
94
118
  /** The HSL values shown, kept while they give the selected colour (so hue stays put for greys) */
95
119
  private hsl;
96
- /** The saved colour that is reached with Tab (the others with arrow keys) */
97
- private focusIndex;
120
+ /** The saved colour that is reached with Tab in its palette (the others with arrow keys; other palettes' first) */
121
+ private focused;
98
122
  /** A touch being held on a saved colour, until it opens its menu */
99
123
  private press?;
100
124
  /** Whether a long press opened a menu, so the click that follows is ignored */
@@ -108,6 +132,12 @@ export declare class PcPanelColours extends PcPanel {
108
132
  /** The selected colour's value, e.g. `#000000` */
109
133
  get colour(): string;
110
134
  protected willUpdate(changed: PropertyValues<this>): void;
135
+ /** The palettes shown: the user's own, then the one PaintCanvas comes with (named after the app) */
136
+ shown(): ShownPalette[];
137
+ /** A palette's colours */
138
+ private coloursOf;
139
+ /** The saved colour at a position */
140
+ private colourAt;
111
141
  /** Set the selected colour */
112
142
  private setColour;
113
143
  /** Select the pressed colour */
@@ -122,9 +152,9 @@ export declare class PcPanelColours extends PcPanel {
122
152
  private selectModel;
123
153
  /** Change one channel of the colour model shown */
124
154
  private onChannel;
125
- /** Set the selected colour from a saved colour, or ask to delete it with Alt/Option held */
155
+ /** Set the selected colour from a saved colour, or ask to delete one of the user's with Alt/Option held */
126
156
  private onSwatchClick;
127
- /** Open a saved colour's menu where it was right-clicked */
157
+ /** Open the menu of a colour in one of the user's palettes where it was right-clicked */
128
158
  private onSwatchContextMenu;
129
159
  /** Start waiting for a touch held on a saved colour to open its menu */
130
160
  private onSwatchPointerDown;
@@ -137,30 +167,36 @@ export declare class PcPanelColours extends PcPanel {
137
167
  * keyboard shortcuts (e.g. Delete doesn't also delete the masked image data).
138
168
  */
139
169
  private onSwatchKeyDown;
140
- /** How many saved colours fit in a row */
170
+ /** How many saved colours fit in a row of a palette */
141
171
  private columns;
142
172
  /** Focus a saved colour */
143
173
  private focusSwatch;
144
- /** Save the selected colour */
174
+ /** Save the selected colour in one of the user's palettes */
145
175
  private onAdd;
146
176
  /** Delete the saved colour that was asked about, if confirmed */
147
177
  private onConfirm;
178
+ /**
179
+ * Make a palette, asking for its name. The user is its author (see `PaintCanvas.author`).
180
+ */
181
+ createPalette(): Promise<ColourPalette | undefined>;
182
+ /**
183
+ * Do one of a palette's menu actions: rename it (asking for a name), export it, or delete it (asking first).
184
+ */
185
+ actOnPalette(id: string, action: (typeof PALETTE_ACTIONS)[number]): Promise<void>;
148
186
  /** Close the palette with Escape (unless a dialog or menu is open, which closes first) */
149
187
  private onKeyDown;
150
188
  /**
151
- * Import the colours in a colour palette file, adding them after the saved ones (leaving out ones already saved).
189
+ * Import the palettes in a colour palette file as the user's own, replacing ones with the same IDs.
152
190
  */
153
191
  importFile(file: File): Promise<void>;
154
192
  /**
155
- * Export the saved colours as a colour palette file.
193
+ * Export palettes (the user's own by default) as a colour palette file, or say there are none.
156
194
  */
157
- exportFile(): Promise<void>;
158
- /** Show a palette file error (one of `PALETTE_FILE_ERRORS`) in the chosen language */
159
- private toastError;
195
+ exportFile(palettes?: readonly ColourPalette[]): Promise<void>;
160
196
  /** The colour model tabs and the selected model's sliders */
161
197
  private renderChannels;
162
- /** The saved colours and the [+] button */
163
- private renderSwatches;
198
+ /** A palette: its name (and the ⋮ menu of the user's own), its colours and its [+] button */
199
+ private renderPalette;
164
200
  /** The saved colour's menu, while open */
165
201
  private renderMenu;
166
202
  /** The dialog asking before deleting a saved colour, while asking */
@@ -169,3 +205,4 @@ export declare class PcPanelColours extends PcPanel {
169
205
  protected get defaultLabel(): string;
170
206
  protected renderBody(): import("lit").TemplateResult<1> | typeof nothing;
171
207
  }
208
+ export {};