@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
@@ -67,8 +67,8 @@ var zh_Hant_default = {
67
67
  "paintCanvas.palette.hue": "色相",
68
68
  "paintCanvas.palette.saturation": "飽和度",
69
69
  "paintCanvas.palette.lightness": "亮度",
70
- "paintCanvas.palette.saved": "已儲存的顏色",
71
- "paintCanvas.palette.add": "將 {colour} 儲存到調色盤",
70
+ "paintCanvas.palette.saved": "調色盤列表",
71
+ "paintCanvas.palette.add": "將 {colour} 儲存到 {name}",
72
72
  "paintCanvas.palette.colourActions": "{colour} 的操作",
73
73
  "paintCanvas.palette.deleteColour": "刪除顏色",
74
74
  "paintCanvas.palette.deleteMessage": "要刪除這個顏色嗎?",
@@ -76,11 +76,24 @@ var zh_Hant_default = {
76
76
  "paintCanvas.palette.import": "匯入",
77
77
  "paintCanvas.palette.export": "匯出",
78
78
  "paintCanvas.palette.importFile": "匯入調色盤檔案",
79
- "paintCanvas.palette.imported.one": "已新增 {count} 個顏色",
80
- "paintCanvas.palette.imported.other": "已新增 {count} 個顏色",
79
+ "paintCanvas.palette.imported.one": "已匯入 {count} 個調色盤",
80
+ "paintCanvas.palette.imported.other": "已匯入 {count} 個調色盤",
81
81
  "paintCanvas.palette.fileIncompatible": "不相容的調色盤 JSON 檔案",
82
+ "paintCanvas.palette.fileUnsupported": "不支援的調色盤檔案版本",
82
83
  "paintCanvas.palette.fileReadFailed": "無法讀取調色盤 JSON 檔案",
83
84
  "paintCanvas.palette.exportFailed": "匯出調色盤 JSON 檔案時發生錯誤",
85
+ "paintCanvas.palette.newPalette": "新增調色盤",
86
+ "paintCanvas.palette.newPaletteHeading": "新增調色盤",
87
+ "paintCanvas.palette.name": "名稱",
88
+ "paintCanvas.palette.defaultName": "我的調色盤",
89
+ "paintCanvas.palette.renameHeading": "重新命名調色盤",
90
+ "paintCanvas.palette.paletteActions": "{name} 動作",
91
+ "paintCanvas.palette.renamePalette": "重新命名…",
92
+ "paintCanvas.palette.exportPalette": "匯出",
93
+ "paintCanvas.palette.deletePalette": "刪除…",
94
+ "paintCanvas.palette.deletePaletteHeading": "刪除調色盤",
95
+ "paintCanvas.palette.deletePaletteConfirm": "刪除「{name}」?此動作無法復原。",
96
+ "paintCanvas.palette.nothingToExport": "沒有可匯出的調色盤",
84
97
  "paintCanvas.toolbar.label": "工具",
85
98
  "paintCanvas.toolbar.layers": "圖層",
86
99
  "paintCanvas.toolbar.menu": "選單",
@@ -109,7 +122,7 @@ var zh_Hant_default = {
109
122
  "paintCanvas.brushMenu.size": "{tool}大小",
110
123
  "paintCanvas.numberSlider.hint": "左右拖曳以變更,點一下以輸入",
111
124
  "paintCanvas.brushMenu.newPreset": "新預設",
112
- "paintCanvas.brushMenu.create": "建立",
125
+ "paintCanvas.brushMenu.create": "新增筆刷",
113
126
  "paintCanvas.brushMenu.import": "匯入",
114
127
  "paintCanvas.brushMenu.export": "匯出",
115
128
  "paintCanvas.presets.myPresets": "我的預設",
@@ -126,6 +139,7 @@ var zh_Hant_default = {
126
139
  "paintCanvas.presets.imported.one": "已匯入 {count} 個筆刷預設",
127
140
  "paintCanvas.presets.imported.other": "已匯入 {count} 個筆刷預設",
128
141
  "paintCanvas.presets.fileIncompatible": "不相容的筆刷預設 JSON 檔案",
142
+ "paintCanvas.presets.fileUnsupported": "不支援的筆刷預設檔案版本",
129
143
  "paintCanvas.presets.fileReadFailed": "無法讀取筆刷預設 JSON 檔案",
130
144
  "paintCanvas.presets.exportFailed": "匯出筆刷預設 JSON 檔案時發生錯誤",
131
145
  "paintCanvas.presets.nothingToExport": "沒有可匯出的筆刷預設",
@@ -164,6 +178,8 @@ var zh_Hant_default = {
164
178
  "paintCanvas.layers.alphaLock": "鎖定透明像素",
165
179
  "paintCanvas.layers.blendMode": "混合模式",
166
180
  "paintCanvas.layers.actions": "圖層動作",
181
+ "paintCanvas.layers.rename": "重新命名…",
182
+ "paintCanvas.layers.renameTitle": "重新命名圖層",
167
183
  "paintCanvas.layers.reorder": "調整{name}的順序:拖曳,或按下以顯示動作",
168
184
  "paintCanvas.layers.duplicate": "複製",
169
185
  "paintCanvas.layers.createMask": "建立遮罩",
@@ -289,13 +305,20 @@ var zh_Hant_default = {
289
305
  "paintCanvas.settings.importFile": "匯入設定檔",
290
306
  "paintCanvas.settings.imported": "已匯入設定",
291
307
  "paintCanvas.settings.fileIncompatible": "不相容的設定 JSON 檔案",
308
+ "paintCanvas.settings.fileUnsupported": "不支援的設定檔案版本",
292
309
  "paintCanvas.settings.fileReadFailed": "無法讀取設定 JSON 檔案",
293
310
  "paintCanvas.settings.exportFailed": "匯出設定 JSON 檔案時發生錯誤",
311
+ "paintCanvas.settings.gpu": "使用 GPU 繪製",
312
+ "paintCanvas.settings.gpuHint": "如果繪製緩慢,請嘗試此選項。重新載入頁面後生效。",
313
+ "paintCanvas.settings.reloadToApply": "重新載入頁面以套用此變更。",
294
314
  "paintCanvas.stats.label": "畫布詳細資料",
295
315
  "paintCanvas.stats.size": "畫布:{width} × {height} 像素",
296
316
  "paintCanvas.stats.zoom": "縮放:{zoom}",
297
317
  "paintCanvas.stats.colour": "顏色:{colour}",
298
318
  "paintCanvas.stats.stored": "已儲存:{size}",
319
+ "paintCanvas.stats.remoteSaving": "正在儲存到{store}…",
320
+ "paintCanvas.stats.remoteFailed": "無法儲存到{store}",
321
+ "paintCanvas.stats.remoteStore": "雲端",
299
322
  "paintCanvas.theme.label": "主題",
300
323
  "paintCanvas.theme.light": "淺色",
301
324
  "paintCanvas.theme.dark": "深色",
package/dist/package.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // package.json
2
- var version = "0.6.0";
2
+ var version = "0.8.0";
3
3
  export { version };
@@ -1,7 +1,11 @@
1
- import PaintCanvas from "../lib/classes/PaintCanvas";
1
+ import PaintCanvas, { type PaintCanvasOptions } from "../lib/classes/PaintCanvas";
2
2
  /** The handle of the author of presets made in apps from `createApp` */
3
3
  export declare const TEST_HANDLE = "ada.example";
4
4
  /**
5
5
  * Create a canvas whose canvases record drawing calls (jsdom cannot draw), with "Ada" as the author of new presets.
6
+ * Other options are passed on.
7
+ *
8
+ * @example
9
+ * const app = createApp({ stores: { brushes: myStore } });
6
10
  */
7
- export declare const createApp: () => PaintCanvas;
11
+ export declare const createApp: (options?: PaintCanvasOptions) => PaintCanvas;
@@ -5,12 +5,17 @@ import { createRecordingCanvasFactory } from "./recordingCanvas.js";
5
5
  var TEST_HANDLE = "ada.example";
6
6
  /**
7
7
  * Create a canvas whose canvases record drawing calls (jsdom cannot draw), with "Ada" as the author of new presets.
8
+ * Other options are passed on.
9
+ *
10
+ * @example
11
+ * const app = createApp({ stores: { brushes: myStore } });
8
12
  */
9
- var createApp = () => new PaintCanvas({
13
+ var createApp = (options = {}) => new PaintCanvas({
10
14
  createCanvas: createRecordingCanvasFactory().factory,
11
15
  presetAuthor: () => ({
12
16
  name: "Ada",
13
17
  handle: TEST_HANDLE
14
- })
18
+ }),
19
+ ...options
15
20
  });
16
21
  export { TEST_HANDLE, createApp };
@@ -0,0 +1,26 @@
1
+ import type { DocumentStoreLike } from "../lib/storage";
2
+ import type { DrawingDocument } from "../lib/document/schema";
3
+ /** Options for `MemoryDocumentStore` */
4
+ export interface MemoryDocumentStoreOptions {
5
+ /** Names the store for the user */
6
+ label?: string;
7
+ /** How long each save takes (milliseconds), reporting progress in 4 steps, e.g. to try out a saving indicator */
8
+ delayMs?: number;
9
+ }
10
+ /**
11
+ * A document store kept in memory (e.g. for tests, or to try out remote saving), recording each save.
12
+ *
13
+ * @example
14
+ * canvas.useRemoteDocumentStore(new MemoryDocumentStore({ label: "Test", delayMs: 2000 }));
15
+ */
16
+ export declare class MemoryDocumentStore implements DocumentStoreLike {
17
+ readonly label?: string;
18
+ /** The saved documents, by ID */
19
+ readonly documents: Map<string, DrawingDocument>;
20
+ /** Each save, in order */
21
+ readonly saves: DrawingDocument[];
22
+ private readonly delayMs;
23
+ constructor({ label, delayMs }?: MemoryDocumentStoreOptions);
24
+ save(document: DrawingDocument, onProgress?: (done: number) => void): Promise<void>;
25
+ load(id: string): Promise<DrawingDocument | undefined>;
26
+ }
@@ -0,0 +1,31 @@
1
+ // src/testing/documentStore.ts
2
+ /**
3
+ * A document store kept in memory (e.g. for tests, or to try out remote saving), recording each save.
4
+ *
5
+ * @example
6
+ * canvas.useRemoteDocumentStore(new MemoryDocumentStore({ label: "Test", delayMs: 2000 }));
7
+ */
8
+ var MemoryDocumentStore = class {
9
+ label;
10
+ /** The saved documents, by ID */
11
+ documents = /* @__PURE__ */ new Map();
12
+ /** Each save, in order */
13
+ saves = [];
14
+ delayMs;
15
+ constructor({ label, delayMs = 0 } = {}) {
16
+ this.label = label;
17
+ this.delayMs = delayMs;
18
+ }
19
+ async save(document, onProgress) {
20
+ for (let step = 1; step <= 4; step++) {
21
+ if (this.delayMs > 0) await new Promise((resolve) => setTimeout(resolve, this.delayMs / 4));
22
+ onProgress?.(step / 4);
23
+ }
24
+ this.saves.push(document);
25
+ this.documents.set(document.id, document);
26
+ }
27
+ async load(id) {
28
+ return this.documents.get(id);
29
+ }
30
+ };
31
+ export { MemoryDocumentStore };
@@ -23,7 +23,8 @@ import "../PcDialog";
23
23
  * - `left`: `<pc-toolbar>` (a pane) by default; toolbars in the sides are minimised (their tools one button that opens a
24
24
  * menu of them) while the layout is narrow
25
25
  * - `top`: a horizontal `<pc-pane>` with `<pc-tool-settings>` (for tools that have settings) and, at its end,
26
- * `<pc-clipboard-tools>` (cut, copy and paste; a ⋮ menu while the layout is narrow) by default
26
+ * `<pc-clipboard-tools>` (cut, copy and paste; a ⋮ menu while the layout is narrow or the window is narrower than
27
+ * 900px) by default
27
28
  * - `right`: a `<pc-pane>` with `<pc-panel-layers>` by default
28
29
  * - `center`: `<pc-canvas>` by default
29
30
  * - `overlay`: drawn over the canvas; `<pc-canvas-map>` by default
@@ -36,7 +37,7 @@ import "../PcDialog";
36
37
  * does by default), the toolbar has no layers menu button.
37
38
  *
38
39
  * Each side has a mode, chosen by the layout's width from `left-modes` and `right-modes` (default `"docked,
39
- * rail@600"`), which the layout gives the side's `<pc-pane>`s (see `PcPane`): `docked` beside the canvas; `rail`, a
40
+ * floating@600"`), which the layout gives the side's `<pc-pane>`s (see `PcPane`): `docked` beside the canvas; `rail`, a
40
41
  * column of buttons (one per panel) that open each panel over the canvas; `floating`, the pane over the canvas as a
41
42
  * box that collapses to a button; or `overlay`, a rail over the canvas without a background (the canvas fills the
42
43
  * width behind it). Give side content in a `<pc-pane>` to have it follow the side's mode.
@@ -50,7 +51,9 @@ import "../PcDialog";
50
51
  * that offers to draw here instead. Fires `pc-toast` when the drawing can't be loaded or saved.
51
52
  *
52
53
  * With a `settings-store` name, the user's app settings (input settings, keyboard shortcuts, colour palette, colours,
53
- * theme and language) are kept in the browser under that name (see `SettingsAutosave`). Fires `pc-toast` when they can't be.
54
+ * theme, language and rendering) are kept in the browser under that name (see `SettingsAutosave`). Fires `pc-toast` when
55
+ * they can't be. The rendering chosen there is also kept in `localStorage` (see `saveRenderingChoice`), so the canvas the
56
+ * layout makes draws where the user last chose (see `PaintCanvasOptions.rendering`).
54
57
  *
55
58
  * @example
56
59
  * html`<paint-canvas style="height: 600px"></paint-canvas>`
@@ -154,6 +157,10 @@ export declare class PaintCanvasElement extends PcElement {
154
157
  private watchedAutosave?;
155
158
  /** Keeps the app settings under the `settings-store` name, if any */
156
159
  private ownSettingsAutosave?;
160
+ /** The asset stores the layout gave the canvas for `settings-store`, if any */
161
+ private ownAssetStores?;
162
+ /** Stops keeping the rendering chosen in the canvas's UI settings under the `settings-store` name, if kept */
163
+ private stopKeepingRendering?;
157
164
  /** Whether drawing was blocked when shortcuts were last watched */
158
165
  private blockedShortcuts;
159
166
  /** Whether each side's slot has a layers menu (the toolbar's isn't needed then) */
@@ -200,10 +207,17 @@ export declare class PaintCanvasElement extends PcElement {
200
207
  /** Keeps the app settings under the `settings-store` name, if any (e.g. to wait until they are restored with `ready`) */
201
208
  get settingsAutosave(): SettingsAutosave | undefined;
202
209
  /**
203
- * Keep the canvas's app settings under the `settings-store` name, stopping the previous settings autosave (which
204
- * 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.
205
214
  */
206
215
  private useSettingsStore;
216
+ /**
217
+ * Keep the rendering chosen in the canvas's UI settings (e.g. when they're restored, imported or changed) under a
218
+ * name, for the next time the layout makes a canvas. Returns what stops it.
219
+ */
220
+ private keepRendering;
207
221
  /** Show errors from the canvas's current autosave */
208
222
  private watchAutosaveErrors;
209
223
  /** Handle keyboard shortcuts for the current canvas, if asked to (not while blocked) */
@@ -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 } 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";
@@ -45,7 +46,8 @@ var TAKE_OVER_CHOICE = "take-over";
45
46
  * - `left`: `<pc-toolbar>` (a pane) by default; toolbars in the sides are minimised (their tools one button that opens a
46
47
  * menu of them) while the layout is narrow
47
48
  * - `top`: a horizontal `<pc-pane>` with `<pc-tool-settings>` (for tools that have settings) and, at its end,
48
- * `<pc-clipboard-tools>` (cut, copy and paste; a ⋮ menu while the layout is narrow) by default
49
+ * `<pc-clipboard-tools>` (cut, copy and paste; a ⋮ menu while the layout is narrow or the window is narrower than
50
+ * 900px) by default
49
51
  * - `right`: a `<pc-pane>` with `<pc-panel-layers>` by default
50
52
  * - `center`: `<pc-canvas>` by default
51
53
  * - `overlay`: drawn over the canvas; `<pc-canvas-map>` by default
@@ -58,7 +60,7 @@ var TAKE_OVER_CHOICE = "take-over";
58
60
  * does by default), the toolbar has no layers menu button.
59
61
  *
60
62
  * Each side has a mode, chosen by the layout's width from `left-modes` and `right-modes` (default `"docked,
61
- * rail@600"`), which the layout gives the side's `<pc-pane>`s (see `PcPane`): `docked` beside the canvas; `rail`, a
63
+ * floating@600"`), which the layout gives the side's `<pc-pane>`s (see `PcPane`): `docked` beside the canvas; `rail`, a
62
64
  * column of buttons (one per panel) that open each panel over the canvas; `floating`, the pane over the canvas as a
63
65
  * box that collapses to a button; or `overlay`, a rail over the canvas without a background (the canvas fills the
64
66
  * width behind it). Give side content in a `<pc-pane>` to have it follow the side's mode.
@@ -72,7 +74,9 @@ var TAKE_OVER_CHOICE = "take-over";
72
74
  * that offers to draw here instead. Fires `pc-toast` when the drawing can't be loaded or saved.
73
75
  *
74
76
  * With a `settings-store` name, the user's app settings (input settings, keyboard shortcuts, colour palette, colours,
75
- * theme and language) are kept in the browser under that name (see `SettingsAutosave`). Fires `pc-toast` when they can't be.
77
+ * theme, language and rendering) are kept in the browser under that name (see `SettingsAutosave`). Fires `pc-toast` when
78
+ * they can't be. The rendering chosen there is also kept in `localStorage` (see `saveRenderingChoice`), so the canvas the
79
+ * layout makes draws where the user last chose (see `PaintCanvasOptions.rendering`).
76
80
  *
77
81
  * @example
78
82
  * html`<paint-canvas style="height: 600px"></paint-canvas>`
@@ -127,6 +131,10 @@ var PaintCanvasElement = class extends PcElement {
127
131
  watchedAutosave;
128
132
  /** Keeps the app settings under the `settings-store` name, if any */
129
133
  ownSettingsAutosave;
134
+ /** The asset stores the layout gave the canvas for `settings-store`, if any */
135
+ ownAssetStores;
136
+ /** Stops keeping the rendering chosen in the canvas's UI settings under the `settings-store` name, if kept */
137
+ stopKeepingRendering;
130
138
  /** Whether drawing was blocked when shortcuts were last watched */
131
139
  blockedShortcuts = false;
132
140
  /** Whether each side's slot has a layers menu (the toolbar's isn't needed then) */
@@ -194,7 +202,7 @@ var PaintCanvasElement = class extends PcElement {
194
202
  return sideModeFor(parseSideModes(side === "left" ? this.leftModes : this.rightModes), this.width);
195
203
  }
196
204
  connectedCallback() {
197
- this.app ??= new PaintCanvas();
205
+ this.app ??= new PaintCanvas({ rendering: this.settingsStore ? loadRenderingChoice(this.settingsStore) : void 0 });
198
206
  super.connectedCallback();
199
207
  this.resizeObserver?.observe(this);
200
208
  this.addEventListener("pointerdown", this.onPointerDown, true);
@@ -213,7 +221,7 @@ var PaintCanvasElement = class extends PcElement {
213
221
  willUpdate(changed) {
214
222
  super.willUpdate(changed);
215
223
  if (changed.has("app") || changed.has("store")) this.useStore(changed.get("app"));
216
- if (changed.has("app") || changed.has("settingsStore")) this.useSettingsStore();
224
+ if (changed.has("app") || changed.has("settingsStore")) this.useSettingsStore(changed.get("app"));
217
225
  this.watchAutosaveErrors();
218
226
  if (this.isConnected && (changed.has("app") || changed.has("shortcuts") || this.blocked !== this.blockedShortcuts)) this.watchShortcuts();
219
227
  }
@@ -279,14 +287,37 @@ var PaintCanvasElement = class extends PcElement {
279
287
  return this.ownSettingsAutosave;
280
288
  }
281
289
  /**
282
- * Keep the canvas's app settings under the `settings-store` name, stopping the previous settings autosave (which
283
- * 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.
284
294
  */
285
- useSettingsStore() {
295
+ useSettingsStore(previousApp) {
296
+ const app = this.app;
286
297
  this.ownSettingsAutosave?.stop();
298
+ this.stopKeepingRendering?.();
287
299
  const name = this.settingsStore || void 0;
288
- this.ownSettingsAutosave = name === void 0 ? void 0 : new SettingsAutosave(this.app, new IndexedDbSettingsStore(name));
300
+ this.stopKeepingRendering = name === void 0 ? void 0 : this.keepRendering(name);
301
+ const { settings, ...assets } = name === void 0 ? { settings: void 0 } : indexedDbStores(name);
302
+ this.ownSettingsAutosave = settings && new SettingsAutosave(app, settings);
289
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);
311
+ }
312
+ /**
313
+ * Keep the rendering chosen in the canvas's UI settings (e.g. when they're restored, imported or changed) under a
314
+ * name, for the next time the layout makes a canvas. Returns what stops it.
315
+ */
316
+ keepRendering(name) {
317
+ const ui = this.app.ui;
318
+ const keep = () => saveRenderingChoice(name, ui.data.rendering);
319
+ ui.addEventListener("change", keep);
320
+ return () => ui.removeEventListener("change", keep);
290
321
  }
291
322
  /** Show errors from the canvas's current autosave */
292
323
  watchAutosaveErrors() {
@@ -1,14 +1,16 @@
1
1
  import { nothing } from "lit";
2
2
  import { PcElement } from "../../base";
3
3
  import "../PcShortcutSettings";
4
+ import "../PcToggleCheckbox";
4
5
  import "../PcDialogWindow";
5
6
  import type PaintCanvas from "../../../lib/classes/PaintCanvas";
6
7
  import { type SaveMethod } from "../../../lib/rendering/screenshot";
7
8
  /**
8
- * Buttons for the user's app settings: "Keyboard shortcuts", which opens a window to change them (see
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
+ * says when the page needs reloading), and buttons: "Keyboard shortcuts", which opens a window to change them (see
9
11
  * `pc-shortcut-settings`), and "Import settings" and "Export settings", which read and write a settings JSON file (input
10
- * settings, keyboard shortcuts, colour palette, colours, profile graphs, theme and language). The window is a shaded
11
- * `<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
12
14
  * back to the button. Fires `pc-toast` after importing, or when importing or exporting fails.
13
15
  *
14
16
  * @example
@@ -40,20 +42,25 @@ export declare class PcAppSettings extends PcElement {
40
42
  shortcutsOpen: boolean;
41
43
  /** Saves exported files (replaceable for tests) */
42
44
  saveFile: (blob: Blob, name: string) => Promise<SaveMethod | undefined>;
45
+ private changes;
43
46
  constructor();
44
47
  /** Close the keyboard shortcuts window */
45
48
  closeShortcuts(): void;
46
49
  /**
47
- * 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).
48
52
  */
49
53
  importFile(file: File): Promise<void>;
50
54
  /**
51
- * 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.
52
56
  */
53
57
  exportFile(): Promise<void>;
54
- /** Show a settings file error (one of `SETTINGS_FILE_ERRORS`) in the chosen language */
55
- private toastError;
56
58
  /** The keyboard shortcuts window, with its contents only while open */
57
59
  private renderShortcuts;
60
+ /**
61
+ * The switch that draws with the GPU (or the CPU) from the next start, saying when the page needs reloading to use
62
+ * the choice (see `PaintCanvas.rendering`).
63
+ */
64
+ private renderRendering;
58
65
  render(): import("lit").TemplateResult<1> | typeof nothing;
59
66
  }
@@ -1,22 +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";
3
+ import ChangeController from "../../ChangeController/index.js";
4
4
  import "../PcShortcutSettings/index.js";
5
+ import "../PcToggleCheckbox/index.js";
5
6
  import "../PcDialogWindow/index.js";
6
- 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";
7
9
  import { JSON_FILE_TYPE, saveFile } from "../../../lib/rendering/screenshot/index.js";
8
10
  import { css, html, nothing, unsafeCSS } from "lit";
9
11
  // src/ui/elements/PcAppSettings/index.ts
10
- /** Translation keys of the settings file error messages */
11
- var SETTINGS_FILE_ERROR_KEYS = {
12
- [SETTINGS_FILE_ERRORS.incompatible]: "paintCanvas.settings.fileIncompatible",
13
- [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"
14
16
  };
15
17
  /**
16
- * Buttons for the user's app settings: "Keyboard shortcuts", which opens a window to change them (see
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
+ * says when the page needs reloading), and buttons: "Keyboard shortcuts", which opens a window to change them (see
17
20
  * `pc-shortcut-settings`), and "Import settings" and "Export settings", which read and write a settings JSON file (input
18
- * settings, keyboard shortcuts, colour palette, colours, profile graphs, theme and language). The window is a shaded
19
- * `<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
20
23
  * back to the button. Fires `pc-toast` after importing, or when importing or exporting fails.
21
24
  *
22
25
  * @example
@@ -44,11 +47,23 @@ var PcAppSettings = class extends PcElement {
44
47
  font-family: var(--pc-font);
45
48
  font-size: var(--pc-font-size);
46
49
  }
50
+ /* One full-width button per row */
47
51
  .actions {
48
52
  display: flex;
49
- flex-wrap: wrap;
53
+ flex-direction: column;
50
54
  gap: var(--pc-space-2);
51
55
  }
56
+ .actions button {
57
+ width: 100%;
58
+ }
59
+ .hint {
60
+ display: block;
61
+ color: var(--pc-text-muted);
62
+ font-size: 0.85em;
63
+ }
64
+ .pending {
65
+ color: var(--pc-text);
66
+ }
52
67
  pc-dialog-window {
53
68
  --pc-dialog-width: 480px;
54
69
  }
@@ -60,6 +75,7 @@ var PcAppSettings = class extends PcElement {
60
75
  ];
61
76
  /** Saves exported files (replaceable for tests) */
62
77
  saveFile = (blob, name) => saveFile(blob, name, void 0, JSON_FILE_TYPE);
78
+ changes = new ChangeController(this, () => [this.app?.ui]);
63
79
  constructor() {
64
80
  super();
65
81
  this.shortcutsOpen = false;
@@ -69,36 +85,33 @@ var PcAppSettings = class extends PcElement {
69
85
  this.shortcutsOpen = false;
70
86
  }
71
87
  /**
72
- * 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).
73
90
  */
74
91
  async importFile(file) {
75
92
  const app = this.app;
76
93
  if (!app) return;
77
- let text;
78
94
  try {
79
- text = await file.text();
80
- } catch {
81
- this.toastError(SETTINGS_FILE_ERRORS.read);
82
- return;
83
- }
84
- try {
85
- applySettings(app, parseSettingsFile(text, [app.names.settingsFileFormat, SETTINGS_FILE_FORMAT]));
95
+ await applySettings(app, parseSettingsFile(await file.text()));
86
96
  emit(this, "pc-toast", {
87
97
  message: this.t("paintCanvas.settings.imported"),
88
98
  kind: "success"
89
99
  });
90
100
  } catch (error) {
91
- 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
+ });
92
105
  }
93
106
  }
94
107
  /**
95
- * 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.
96
109
  */
97
110
  async exportFile() {
98
111
  const app = this.app;
99
112
  if (!app) return;
100
113
  try {
101
- 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));
102
115
  } catch {
103
116
  emit(this, "pc-toast", {
104
117
  message: this.t("paintCanvas.settings.exportFailed"),
@@ -106,13 +119,6 @@ var PcAppSettings = class extends PcElement {
106
119
  });
107
120
  }
108
121
  }
109
- /** Show a settings file error (one of `SETTINGS_FILE_ERRORS`) in the chosen language */
110
- toastError(message) {
111
- emit(this, "pc-toast", {
112
- message: this.t(SETTINGS_FILE_ERROR_KEYS[message]),
113
- kind: "error"
114
- });
115
- }
116
122
  /** The keyboard shortcuts window, with its contents only while open */
117
123
  renderShortcuts(app) {
118
124
  return html`<pc-dialog-window
@@ -127,14 +133,33 @@ var PcAppSettings = class extends PcElement {
127
133
  .settings=${app.shortcuts}
128
134
  ></pc-shortcut-settings>` : nothing}
129
135
  </pc-dialog-window>`;
136
+ }
137
+ /**
138
+ * The switch that draws with the GPU (or the CPU) from the next start, saying when the page needs reloading to use
139
+ * the choice (see `PaintCanvas.rendering`).
140
+ */
141
+ renderRendering(app) {
142
+ const chosen = app.ui.data.rendering;
143
+ const pending = chosen !== app.rendering;
144
+ return html`<pc-toggle-checkbox
145
+ class="gpu"
146
+ .checked=${chosen === "gpu"}
147
+ @change=${(e) => app.ui.update({ rendering: e.detail.checked ? "gpu" : "cpu" })}
148
+ >
149
+ ${this.t("paintCanvas.settings.gpu")}
150
+ <span class="hint ${pending ? "pending" : ""}" aria-live="polite">
151
+ ${this.t(pending ? "paintCanvas.settings.reloadToApply" : "paintCanvas.settings.gpuHint")}
152
+ </span>
153
+ </pc-toggle-checkbox>`;
130
154
  }
131
155
  render() {
132
156
  const app = this.app;
133
157
  if (!app) return nothing;
134
- return html`<div class="actions">
158
+ return html`${this.renderRendering(app)}
159
+ <div class="actions">
135
160
  <button
136
161
  type="button"
137
- class="pill-s shortcuts"
162
+ class="pill shortcuts"
138
163
  aria-haspopup="dialog"
139
164
  @click=${() => this.shortcutsOpen = true}
140
165
  >
@@ -142,14 +167,14 @@ var PcAppSettings = class extends PcElement {
142
167
  </button>
143
168
  <button
144
169
  type="button"
145
- class="pill-s import"
170
+ class="pill import"
146
171
  @click=${() => this.renderRoot.querySelector(".import-file")?.click()}
147
172
  >
148
173
  ${this.t("paintCanvas.settings.import")}
149
174
  </button>
150
175
  <button
151
176
  type="button"
152
- class="pill-s export"
177
+ class="pill export"
153
178
  @click=${() => void this.exportFile()}
154
179
  >
155
180
  ${this.t("paintCanvas.settings.export")}
@@ -2,6 +2,7 @@ import { nothing } from "lit";
2
2
  import { type ConfirmFn, type PromptFn } from "../../ask";
3
3
  import { PcElement } from "../../base";
4
4
  import "../PcBrushTest";
5
+ import "../PcToggleCheckbox";
5
6
  import "../PcDialogWindow";
6
7
  import "../PcSliderRange";
7
8
  import "../PcProfileEditor";
@@ -4,6 +4,7 @@ import { iconLabel } from "../../icons/index.js";
4
4
  import { askConfirm, askText, deletePresetQuestion } from "../../ask/index.js";
5
5
  import { PcElement, defineElement } from "../../base/index.js";
6
6
  import "../PcBrushTest/index.js";
7
+ import "../PcToggleCheckbox/index.js";
7
8
  import "../PcDialogWindow/index.js";
8
9
  import "../PcSliderRange/index.js";
9
10
  import "../PcProfileEditor/index.js";
@@ -500,16 +501,14 @@ var PcBrushConfig = class extends PcElement {
500
501
  >
501
502
  ${this.single("spacing", this.t("paintCanvas.brushConfig.spacing"), draft.spacing ?? 5, spacingLimits, (spacing) => this.change({ spacing }))}
502
503
  </div>
503
- <label>
504
- <input
505
- type="checkbox"
506
- class="snap"
507
- .checked=${draft.snapToPixelGrid ?? false}
508
- ?disabled=${this.busy}
509
- @change=${(e) => this.change({ snapToPixelGrid: e.target.checked })}
510
- />
504
+ <pc-toggle-checkbox
505
+ class="snap"
506
+ .checked=${draft.snapToPixelGrid ?? false}
507
+ ?disabled=${this.busy}
508
+ @change=${(e) => this.change({ snapToPixelGrid: e.detail.checked })}
509
+ >
511
510
  ${this.t("paintCanvas.brushConfig.snapToPixelGrid")}
512
- </label>
511
+ </pc-toggle-checkbox>
513
512
  </div>
514
513
  ${this.editingId ? html`<button
515
514
  slot="footer-start"
@@ -1,4 +1,5 @@
1
1
  import { PcElement } from "../../base";
2
+ import "../PcToggleCheckbox";
2
3
  import type { BrushConfig } from "../../../lib/brushes/types";
3
4
  import type BrushTipCache from "../../../lib/brushes/BrushTipCache";
4
5
  import { type CanvasFactory } from "../../../lib/classes/Layer";