@lvl99dotcom/paintcanvas 0.5.0 → 0.7.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 (120) hide show
  1. package/README.md +2 -2
  2. package/dist/lib/brushes/configRanges/index.d.ts +17 -0
  3. package/dist/lib/brushes/configRanges/index.js +31 -1
  4. package/dist/lib/brushes/params/index.d.ts +2 -1
  5. package/dist/lib/brushes/params/index.js +7 -4
  6. package/dist/lib/brushes/types/index.d.ts +8 -0
  7. package/dist/lib/classes/Layer/index.d.ts +10 -0
  8. package/dist/lib/classes/Layer/index.js +15 -1
  9. package/dist/lib/classes/LayerAutosave/index.d.ts +9 -15
  10. package/dist/lib/classes/LayerAutosave/index.js +30 -43
  11. package/dist/lib/classes/LayerStack/index.d.ts +15 -0
  12. package/dist/lib/classes/LayerStack/index.js +26 -2
  13. package/dist/lib/classes/PaintCanvas/index.d.ts +36 -5
  14. package/dist/lib/classes/PaintCanvas/index.js +45 -14
  15. package/dist/lib/classes/PngEncoder/index.d.ts +41 -0
  16. package/dist/lib/classes/PngEncoder/index.js +104 -0
  17. package/dist/lib/classes/ProfileGraphs/index.d.ts +87 -0
  18. package/dist/lib/classes/ProfileGraphs/index.js +148 -0
  19. package/dist/lib/classes/SettingsAutosave/index.d.ts +1 -1
  20. package/dist/lib/classes/SettingsAutosave/index.js +2 -1
  21. package/dist/lib/classes/UiSettings/index.d.ts +24 -2
  22. package/dist/lib/classes/UiSettings/index.js +32 -7
  23. package/dist/lib/messages/schema.d.ts +720 -0
  24. package/dist/lib/presets/schema.d.ts +360 -0
  25. package/dist/lib/presets/schema.js +8 -1
  26. package/dist/lib/profiles/index.d.ts +61 -0
  27. package/dist/lib/profiles/index.js +131 -0
  28. package/dist/lib/profiles/schema.d.ts +73 -0
  29. package/dist/lib/profiles/schema.js +53 -0
  30. package/dist/lib/rendering/sample/index.d.ts +11 -0
  31. package/dist/lib/rendering/sample/index.js +36 -5
  32. package/dist/lib/settings/index.d.ts +2 -1
  33. package/dist/lib/settings/index.js +4 -1
  34. package/dist/lib/settings/schema.d.ts +53 -1
  35. package/dist/lib/settings/schema.js +9 -2
  36. package/dist/lib/storage/index.d.ts +26 -0
  37. package/dist/lib/storage/index.js +38 -1
  38. package/dist/lib/validation/validators.generated.js +13061 -3675
  39. package/dist/lib/version/index.d.ts +25 -0
  40. package/dist/lib/version/index.js +35 -0
  41. package/dist/locales/en-GB.js +35 -0
  42. package/dist/locales/en-US.js +35 -0
  43. package/dist/locales/fr.js +35 -0
  44. package/dist/locales/ja.js +35 -0
  45. package/dist/locales/ko.js +35 -0
  46. package/dist/locales/zh-Hans.js +35 -0
  47. package/dist/locales/zh-Hant.js +35 -0
  48. package/dist/package.js +1 -1
  49. package/dist/ui/ask/index.d.ts +48 -0
  50. package/dist/ui/ask/index.js +67 -0
  51. package/dist/ui/base/index.d.ts +8 -1
  52. package/dist/ui/base/index.js +19 -4
  53. package/dist/ui/elements/PaintCanvas/index.d.ts +13 -3
  54. package/dist/ui/elements/PaintCanvas/index.js +25 -6
  55. package/dist/ui/elements/PaintCanvas/style.js +1 -1
  56. package/dist/ui/elements/PcAppSettings/index.d.ts +15 -7
  57. package/dist/ui/elements/PcAppSettings/index.js +53 -62
  58. package/dist/ui/elements/PcBrushConfig/index.d.ts +26 -14
  59. package/dist/ui/elements/PcBrushConfig/index.js +214 -145
  60. package/dist/ui/elements/PcBrushConfig/style.js +1 -1
  61. package/dist/ui/elements/PcBrushTest/index.d.ts +1 -0
  62. package/dist/ui/elements/PcBrushTest/index.js +15 -9
  63. package/dist/ui/elements/PcBrushTest/style.js +1 -1
  64. package/dist/ui/elements/PcButton/index.d.ts +12 -3
  65. package/dist/ui/elements/PcButton/index.js +18 -10
  66. package/dist/ui/elements/PcButtonMenu/index.d.ts +88 -0
  67. package/dist/ui/elements/PcButtonMenu/index.js +125 -0
  68. package/dist/ui/elements/PcCanvas/index.d.ts +15 -1
  69. package/dist/ui/elements/PcCanvas/index.js +42 -19
  70. package/dist/ui/elements/PcCanvas/style.js +1 -1
  71. package/dist/ui/elements/PcClipboardTools/index.d.ts +11 -7
  72. package/dist/ui/elements/PcClipboardTools/index.js +30 -29
  73. package/dist/ui/elements/PcDialog/index.d.ts +46 -10
  74. package/dist/ui/elements/PcDialog/index.js +102 -51
  75. package/dist/ui/elements/PcDialogWindow/index.d.ts +141 -0
  76. package/dist/ui/elements/PcDialogWindow/index.js +385 -0
  77. package/dist/ui/elements/PcInputSettings/index.d.ts +2 -1
  78. package/dist/ui/elements/PcInputSettings/index.js +9 -15
  79. package/dist/ui/elements/PcLayerItem/index.d.ts +21 -6
  80. package/dist/ui/elements/PcLayerItem/index.js +53 -26
  81. package/dist/ui/elements/PcLayerItem/style.js +1 -1
  82. package/dist/ui/elements/PcPanelBrushes/index.d.ts +3 -2
  83. package/dist/ui/elements/PcPanelBrushes/index.js +7 -5
  84. package/dist/ui/elements/PcPanelBrushes/style.js +1 -1
  85. package/dist/ui/elements/PcPanelColours/index.js +2 -2
  86. package/dist/ui/elements/PcPanelColours/style.js +1 -1
  87. package/dist/ui/elements/PcPanelLayers/index.js +1 -1
  88. package/dist/ui/elements/PcPanelLayers/style.js +1 -1
  89. package/dist/ui/elements/PcProfileEditor/index.d.ts +103 -0
  90. package/dist/ui/elements/PcProfileEditor/index.js +259 -0
  91. package/dist/ui/elements/PcProfileGraph/index.d.ts +84 -0
  92. package/dist/ui/elements/PcProfileGraph/index.js +317 -0
  93. package/dist/ui/elements/PcShortcutSettings/index.js +9 -13
  94. package/dist/ui/elements/PcSliderRange/index.d.ts +147 -0
  95. package/dist/ui/elements/PcSliderRange/index.js +308 -0
  96. package/dist/ui/elements/PcStats/index.d.ts +19 -4
  97. package/dist/ui/elements/PcStats/index.js +29 -13
  98. package/dist/ui/elements/PcToggleCheckbox/index.d.ts +13 -2
  99. package/dist/ui/elements/PcToggleCheckbox/index.js +27 -8
  100. package/dist/ui/elements/PcToolSettings/index.d.ts +1 -0
  101. package/dist/ui/elements/PcToolSettings/index.js +14 -14
  102. package/dist/ui/elements/PcVersion/index.d.ts +12 -0
  103. package/dist/ui/elements/PcVersion/index.js +43 -0
  104. package/dist/ui/icons/images/icon-profile-graph@2x.js +3 -0
  105. package/dist/ui/icons/index.d.ts +1 -1
  106. package/dist/ui/icons/index.js +2 -0
  107. package/dist/ui/index.d.ts +9 -2
  108. package/dist/ui/index.js +9 -2
  109. package/dist/ui/layout/index.d.ts +2 -2
  110. package/dist/ui/layout/index.js +2 -2
  111. package/dist/ui/styles/index.d.ts +21 -1
  112. package/dist/ui/styles/index.js +73 -14
  113. package/locales/en-GB.json +35 -0
  114. package/locales/en-US.json +35 -0
  115. package/locales/fr.json +35 -0
  116. package/locales/ja.json +35 -0
  117. package/locales/ko.json +35 -0
  118. package/locales/zh-Hans.json +35 -0
  119. package/locales/zh-Hant.json +35 -0
  120. package/package.json +25 -23
@@ -0,0 +1,48 @@
1
+ import "../elements/PcDialog";
2
+ import type Translations from "../../lib/i18n";
3
+ import { type DialogField } from "../elements/PcDialog";
4
+ /** The choice that agrees, in the dialogs `askConfirm` and `askText` show */
5
+ export declare const OK_CHOICE = "ok";
6
+ /** What to ask */
7
+ export interface AskOptions {
8
+ heading: string;
9
+ message?: string;
10
+ /** The button that agrees */
11
+ confirm: string;
12
+ /** The button that doesn't */
13
+ cancel: string;
14
+ /** Whether agreeing removes or replaces something (the button is pink) */
15
+ cancelling?: boolean;
16
+ }
17
+ /** What to ask for text: as `AskOptions`, with the field's label and the text it starts with */
18
+ export interface AskTextOptions extends AskOptions, DialogField {
19
+ }
20
+ /** Asks the user to confirm, resolving true if they agree */
21
+ export type ConfirmFn = (options: AskOptions) => boolean | Promise<boolean>;
22
+ /** Asks the user for text, resolving with it, or null if they don't give it */
23
+ export type PromptFn = (options: AskTextOptions) => string | null | Promise<string | null>;
24
+ /**
25
+ * Ask the user to confirm in a dialog shown in `parent`, resolving true if they agree (false for the cancel button,
26
+ * Escape or a press outside it).
27
+ *
28
+ * @example
29
+ * if (await askConfirm(this.renderRoot, { heading: "Delete preset", message: 'Delete "Ink"?', confirm: "Delete",
30
+ * cancel: "Cancel", cancelling: true })) remove();
31
+ */
32
+ export declare const askConfirm: (parent: Node, options: AskOptions) => Promise<boolean>;
33
+ /**
34
+ * Ask the user for text in a dialog shown in `parent`, resolving with what they typed when they agree (or press Enter),
35
+ * or null if they cancel.
36
+ *
37
+ * @example
38
+ * const name = await askText(this.renderRoot, { heading: "Add to presets", label: "Name", value: "Ink",
39
+ * confirm: "Save", cancel: "Cancel" });
40
+ */
41
+ export declare const askText: (parent: Node, { label, value, ...options }: AskTextOptions) => Promise<string | null>;
42
+ /**
43
+ * The question asked before deleting one of the user's brush presets, in the chosen language.
44
+ *
45
+ * @example
46
+ * await askConfirm(this.renderRoot, deletePresetQuestion(this.translations, "Ink"));
47
+ */
48
+ export declare const deletePresetQuestion: (translations: Pick<Translations, "t">, name: string) => AskOptions;
@@ -0,0 +1,67 @@
1
+ import { CANCEL_CHOICE } from "../elements/PcDialog/index.js";
2
+ // src/ui/ask/index.ts
3
+ /** The choice that agrees, in the dialogs `askConfirm` and `askText` show */
4
+ var OK_CHOICE = "ok";
5
+ /**
6
+ * Show a dialog in a parent (e.g. an element's shadow root) until the user chooses, then remove it. The choice doesn't
7
+ * reach the parent's listeners.
8
+ */
9
+ var ask = (parent, options, field) => new Promise((resolve) => {
10
+ const dialog = document.createElement("pc-dialog");
11
+ dialog.heading = options.heading;
12
+ dialog.message = options.message ?? "";
13
+ dialog.field = field;
14
+ dialog.choices = [{
15
+ value: "ok",
16
+ label: options.confirm,
17
+ cancelling: options.cancelling
18
+ }, {
19
+ value: CANCEL_CHOICE,
20
+ label: options.cancel,
21
+ variant: "dismiss"
22
+ }];
23
+ dialog.addEventListener("pc-choose", (event) => {
24
+ event.stopPropagation();
25
+ dialog.remove();
26
+ resolve(event.detail);
27
+ }, { once: true });
28
+ parent.appendChild(dialog);
29
+ });
30
+ /**
31
+ * Ask the user to confirm in a dialog shown in `parent`, resolving true if they agree (false for the cancel button,
32
+ * Escape or a press outside it).
33
+ *
34
+ * @example
35
+ * if (await askConfirm(this.renderRoot, { heading: "Delete preset", message: 'Delete "Ink"?', confirm: "Delete",
36
+ * cancel: "Cancel", cancelling: true })) remove();
37
+ */
38
+ var askConfirm = async (parent, options) => (await ask(parent, options)).value === "ok";
39
+ /**
40
+ * Ask the user for text in a dialog shown in `parent`, resolving with what they typed when they agree (or press Enter),
41
+ * or null if they cancel.
42
+ *
43
+ * @example
44
+ * const name = await askText(this.renderRoot, { heading: "Add to presets", label: "Name", value: "Ink",
45
+ * confirm: "Save", cancel: "Cancel" });
46
+ */
47
+ var askText = async (parent, { label, value, ...options }) => {
48
+ const choice = await ask(parent, options, {
49
+ label,
50
+ value
51
+ });
52
+ return choice.value === "ok" ? choice.text : null;
53
+ };
54
+ /**
55
+ * The question asked before deleting one of the user's brush presets, in the chosen language.
56
+ *
57
+ * @example
58
+ * await askConfirm(this.renderRoot, deletePresetQuestion(this.translations, "Ink"));
59
+ */
60
+ var deletePresetQuestion = (translations, name) => ({
61
+ heading: translations.t("paintCanvas.presets.deleteHeading"),
62
+ message: translations.t("paintCanvas.presets.deleteConfirm", { name }),
63
+ confirm: translations.t("paintCanvas.common.delete"),
64
+ cancel: translations.t("paintCanvas.common.cancel"),
65
+ cancelling: true
66
+ });
67
+ export { OK_CHOICE, askConfirm, askText, deletePresetQuestion };
@@ -16,8 +16,15 @@ export declare const defineElement: (name: string, constructor: CustomElementCon
16
16
  * emit(this, "pc-select", { value: "duplicate" });
17
17
  */
18
18
  export declare const emit: <T>(target: EventTarget, type: string, detail?: T) => boolean;
19
+ /** The least font size of form fields on touch screens: iOS zooms the page in on fields with smaller text */
20
+ export declare const TOUCH_FIELD_FONT_SIZE = "16px";
19
21
  /**
20
- * The base of PaintCanvas's elements: the design tokens, the translations and icons they use (shared ones unless
22
+ * Form fields' text on touch screens: at least 16px, so iOS doesn't zoom the page in when one is focused (which is
23
+ * hard to undo). It wins over elements' own field sizes, as zooming makes the whole interface hard to use.
24
+ */
25
+ export declare const touchFields: import("lit").CSSResult;
26
+ /**
27
+ * The base of PaintCanvas's elements: the design tokens, 16px form fields on touch screens (see `touchFields`), the translations and icons they use (shared ones unless
21
28
  * given others), and re-rendering when either changes.
22
29
  *
23
30
  * @example
@@ -1,7 +1,7 @@
1
1
  import { installTokens, tokens } from "../tokens/index.js";
2
2
  import { icons } from "../icons/index.js";
3
3
  import { translations } from "../../lib/i18n/index.js";
4
- import { LitElement } from "lit";
4
+ import { LitElement, css, unsafeCSS } from "lit";
5
5
  // src/ui/base/index.ts
6
6
  /**
7
7
  * Register a custom element unless the name is already registered (e.g. by another copy of PaintCanvas, or after hot
@@ -25,8 +25,23 @@ var emit = (target, type, detail) => target.dispatchEvent(new CustomEvent(type,
25
25
  composed: true,
26
26
  cancelable: true
27
27
  }));
28
+ /** The least font size of form fields on touch screens: iOS zooms the page in on fields with smaller text */
29
+ var TOUCH_FIELD_FONT_SIZE = "16px";
28
30
  /**
29
- * The base of PaintCanvas's elements: the design tokens, the translations and icons they use (shared ones unless
31
+ * Form fields' text on touch screens: at least 16px, so iOS doesn't zoom the page in when one is focused (which is
32
+ * hard to undo). It wins over elements' own field sizes, as zooming makes the whole interface hard to use.
33
+ */
34
+ var touchFields = css`
35
+ @media (pointer: coarse) {
36
+ input,
37
+ select,
38
+ textarea {
39
+ font-size: max(${unsafeCSS(TOUCH_FIELD_FONT_SIZE)}, 1em) !important;
40
+ }
41
+ }
42
+ `;
43
+ /**
44
+ * The base of PaintCanvas's elements: the design tokens, 16px form fields on touch screens (see `touchFields`), the translations and icons they use (shared ones unless
30
45
  * given others), and re-rendering when either changes.
31
46
  *
32
47
  * @example
@@ -37,7 +52,7 @@ var emit = (target, type, detail) => target.dispatchEvent(new CustomEvent(type,
37
52
  * }
38
53
  */
39
54
  var PcElement = class extends LitElement {
40
- static styles = [tokens];
55
+ static styles = [tokens, touchFields];
41
56
  static properties = {
42
57
  translations: { attribute: false },
43
58
  icons: { attribute: false },
@@ -91,4 +106,4 @@ var PcElement = class extends LitElement {
91
106
  this.watched = [];
92
107
  }
93
108
  };
94
- export { PcElement, defineElement, emit };
109
+ export { PcElement, TOUCH_FIELD_FONT_SIZE, defineElement, emit, touchFields };
@@ -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,8 @@ 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
+ /** Stops keeping the rendering chosen in the canvas's UI settings under the `settings-store` name, if kept */
161
+ private stopKeepingRendering?;
157
162
  /** Whether drawing was blocked when shortcuts were last watched */
158
163
  private blockedShortcuts;
159
164
  /** Whether each side's slot has a layers menu (the toolbar's isn't needed then) */
@@ -204,6 +209,11 @@ export declare class PaintCanvasElement extends PcElement {
204
209
  * saves any change waiting to be saved).
205
210
  */
206
211
  private useSettingsStore;
212
+ /**
213
+ * Keep the rendering chosen in the canvas's UI settings (e.g. when they're restored, imported or changed) under a
214
+ * name, for the next time the layout makes a canvas. Returns what stops it.
215
+ */
216
+ private keepRendering;
207
217
  /** Show errors from the canvas's current autosave */
208
218
  private watchAutosaveErrors;
209
219
  /** Handle keyboard shortcuts for the current canvas, if asked to (not while blocked) */
@@ -5,7 +5,7 @@ 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, { IndexedDbSettingsStore, loadRenderingChoice, saveRenderingChoice } from "../../../lib/storage/index.js";
9
9
  import SettingsAutosave, { SETTINGS_AUTOSAVE_ERROR_EVENT } from "../../../lib/classes/SettingsAutosave/index.js";
10
10
  import { AUTOSAVE_ERROR_EVENT, AutosaveState } from "../../../lib/classes/LayerAutosave/index.js";
11
11
  import { isOwnAuthor } from "../../../lib/brushes/authors/index.js";
@@ -45,7 +45,8 @@ var TAKE_OVER_CHOICE = "take-over";
45
45
  * - `left`: `<pc-toolbar>` (a pane) by default; toolbars in the sides are minimised (their tools one button that opens a
46
46
  * menu of them) while the layout is narrow
47
47
  * - `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
48
+ * `<pc-clipboard-tools>` (cut, copy and paste; a ⋮ menu while the layout is narrow or the window is narrower than
49
+ * 900px) by default
49
50
  * - `right`: a `<pc-pane>` with `<pc-panel-layers>` by default
50
51
  * - `center`: `<pc-canvas>` by default
51
52
  * - `overlay`: drawn over the canvas; `<pc-canvas-map>` by default
@@ -58,7 +59,7 @@ var TAKE_OVER_CHOICE = "take-over";
58
59
  * does by default), the toolbar has no layers menu button.
59
60
  *
60
61
  * 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
62
+ * floating@600"`), which the layout gives the side's `<pc-pane>`s (see `PcPane`): `docked` beside the canvas; `rail`, a
62
63
  * column of buttons (one per panel) that open each panel over the canvas; `floating`, the pane over the canvas as a
63
64
  * box that collapses to a button; or `overlay`, a rail over the canvas without a background (the canvas fills the
64
65
  * width behind it). Give side content in a `<pc-pane>` to have it follow the side's mode.
@@ -72,7 +73,9 @@ var TAKE_OVER_CHOICE = "take-over";
72
73
  * that offers to draw here instead. Fires `pc-toast` when the drawing can't be loaded or saved.
73
74
  *
74
75
  * 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.
76
+ * theme, language and rendering) are kept in the browser under that name (see `SettingsAutosave`). Fires `pc-toast` when
77
+ * they can't be. The rendering chosen there is also kept in `localStorage` (see `saveRenderingChoice`), so the canvas the
78
+ * layout makes draws where the user last chose (see `PaintCanvasOptions.rendering`).
76
79
  *
77
80
  * @example
78
81
  * html`<paint-canvas style="height: 600px"></paint-canvas>`
@@ -127,6 +130,8 @@ var PaintCanvasElement = class extends PcElement {
127
130
  watchedAutosave;
128
131
  /** Keeps the app settings under the `settings-store` name, if any */
129
132
  ownSettingsAutosave;
133
+ /** Stops keeping the rendering chosen in the canvas's UI settings under the `settings-store` name, if kept */
134
+ stopKeepingRendering;
130
135
  /** Whether drawing was blocked when shortcuts were last watched */
131
136
  blockedShortcuts = false;
132
137
  /** Whether each side's slot has a layers menu (the toolbar's isn't needed then) */
@@ -194,7 +199,7 @@ var PaintCanvasElement = class extends PcElement {
194
199
  return sideModeFor(parseSideModes(side === "left" ? this.leftModes : this.rightModes), this.width);
195
200
  }
196
201
  connectedCallback() {
197
- this.app ??= new PaintCanvas();
202
+ this.app ??= new PaintCanvas({ rendering: this.settingsStore ? loadRenderingChoice(this.settingsStore) : void 0 });
198
203
  super.connectedCallback();
199
204
  this.resizeObserver?.observe(this);
200
205
  this.addEventListener("pointerdown", this.onPointerDown, true);
@@ -284,10 +289,22 @@ var PaintCanvasElement = class extends PcElement {
284
289
  */
285
290
  useSettingsStore() {
286
291
  this.ownSettingsAutosave?.stop();
292
+ this.stopKeepingRendering?.();
287
293
  const name = this.settingsStore || void 0;
294
+ this.stopKeepingRendering = name === void 0 ? void 0 : this.keepRendering(name);
288
295
  this.ownSettingsAutosave = name === void 0 ? void 0 : new SettingsAutosave(this.app, new IndexedDbSettingsStore(name));
289
296
  this.ownSettingsAutosave?.addEventListener(SETTINGS_AUTOSAVE_ERROR_EVENT, this.onSettingsAutosaveError);
290
297
  }
298
+ /**
299
+ * Keep the rendering chosen in the canvas's UI settings (e.g. when they're restored, imported or changed) under a
300
+ * name, for the next time the layout makes a canvas. Returns what stops it.
301
+ */
302
+ keepRendering(name) {
303
+ const ui = this.app.ui;
304
+ const keep = () => saveRenderingChoice(name, ui.data.rendering);
305
+ ui.addEventListener("change", keep);
306
+ return () => ui.removeEventListener("change", keep);
307
+ }
291
308
  /** Show errors from the canvas's current autosave */
292
309
  watchAutosaveErrors() {
293
310
  const autosave = this.app?.autosave;
@@ -433,7 +450,7 @@ var PaintCanvasElement = class extends PcElement {
433
450
  closePanelOutside(event) {
434
451
  const panel = this.app?.panel;
435
452
  if (!panel) return;
436
- if (event.composedPath().some((target) => target instanceof Element && (target.classList.contains("panel") && target.getRootNode() === this.renderRoot || target.matches("dialog, pc-dialog") || target.getAttribute("data-panel") === panel || panel === Panel.Palette && target.localName === "pc-colour-pair"))) return;
453
+ if (event.composedPath().some((target) => target instanceof Element && (target.classList.contains("panel") && target.getRootNode() === this.renderRoot || target.matches("dialog, pc-dialog, pc-dialog-window") || target.getAttribute("data-panel") === panel || panel === Panel.Palette && target.localName === "pc-colour-pair"))) return;
437
454
  markDismissingPress(event);
438
455
  this.app.closePanel();
439
456
  }
@@ -484,6 +501,8 @@ var PaintCanvasElement = class extends PcElement {
484
501
  }];
485
502
  return html`<pc-dialog
486
503
  class="elsewhere"
504
+ inline
505
+ .dismissible=${false}
487
506
  heading=${this.t("paintCanvas.autosave.elsewhere")}
488
507
  message=${this.t("paintCanvas.autosave.elsewhereMessage")}
489
508
  .choices=${choices}
@@ -1,3 +1,3 @@
1
1
  // src/ui/elements/PaintCanvas/style.css?inline
2
- var style_default = "/* The layout: header across the top, footer across the bottom, sides left and right, and between them the tool\n settings, the canvas and the bottom bar. Empty areas take no space. */\n:host {\n position: relative;\n display: grid;\n grid-template:\n \"header header header\" auto\n \"left top right\" auto\n \"left center right\" 1fr\n \"left bottom right\" auto\n \"footer footer footer\" auto / auto 1fr auto;\n overflow: hidden;\n background: var(--pc-bar);\n font-family: var(--pc-font);\n font-size: var(--pc-font-size);\n}\nheader {\n grid-area: header;\n}\n.top {\n grid-area: top;\n min-width: 0;\n}\n/* The tool settings fill the top pane */\n.top pc-tool-settings {\n flex: 1;\n}\n.center {\n position: relative;\n grid-area: center;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n}\n.bottom {\n grid-area: bottom;\n min-width: 0;\n}\nfooter {\n grid-area: footer;\n}\n.side {\n position: relative;\n z-index: 3;\n display: flex;\n min-height: 0;\n}\n/* A side's panes fill it */\n.side pc-pane,\n.side pc-toolbar,\n.side ::slotted(*) {\n flex: 1;\n}\n.side.left {\n grid-area: left;\n}\n.side.right {\n grid-area: right;\n}\n\n/* Hiding the interface (Tab key) leaves only the canvas */\n:host([ui-hidden]) header,\n:host([ui-hidden]) .top,\n:host([ui-hidden]) .bottom,\n:host([ui-hidden]) footer,\n:host([ui-hidden]) .side {\n display: none;\n}\n\n/* The canvas fills the middle; things drawn over it go in the overlay */\n.center ::slotted([slot=\"center\"]),\n.center pc-canvas {\n position: absolute;\n inset: 0;\n}\n.overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n.overlay > * {\n pointer-events: auto;\n}\npc-canvas-map {\n position: absolute;\n right: 20px;\n bottom: 20px;\n}\n\n/* Menus opened from the toolbar float over the canvas, beside the toolbar */\n.panel {\n position: absolute;\n z-index: 4;\n left: 0;\n bottom: 0;\n max-height: 100%;\n overflow: auto;\n}\n.panel.top-panel {\n top: 0;\n bottom: auto;\n}\n/* The app menu: the implementer's content in a panel */\n.menu-panel:not([open]) {\n display: none;\n}\n\n/* Floating: the side's panes float over the canvas (see pc-pane), so the side takes no space */\n.side[data-mode=\"floating\"] {\n /* Beside the canvas only, so it floats over the canvas and not the tool settings */\n grid-row: 3;\n width: 0;\n}\n/* Overlay: the side's rail sits over the canvas (beside it only, not the tool settings), so the canvas fills the\n width behind it; presses between its panes' buttons (which take presses themselves) reach the canvas */\n.side[data-mode=\"overlay\"] {\n grid-row: 3;\n grid-column: 1 / -1;\n pointer-events: none;\n}\n.side.left[data-mode=\"overlay\"] {\n justify-self: start;\n}\n.side.right[data-mode=\"overlay\"] {\n justify-self: end;\n}\n/* The toolbar's panels open beside a left overlay rail, not under it */\n:host([left-mode=\"overlay\"]) .panel {\n left: var(--pc-left-inset);\n}\n/* While another window is drawing, its message covers only the layout (not the whole page) */\npc-dialog.elsewhere {\n position: absolute;\n}\n";
2
+ var style_default = "/* The layout: header across the top, footer across the bottom, sides left and right, and between them the tool\n settings, the canvas and the bottom bar. Empty areas take no space. */\n:host {\n position: relative;\n display: grid;\n grid-template:\n \"header header header\" auto\n \"left top right\" auto\n \"left center right\" 1fr\n \"left bottom right\" auto\n \"footer footer footer\" auto / auto 1fr auto;\n overflow: hidden;\n background: var(--pc-bar);\n font-family: var(--pc-font);\n font-size: var(--pc-font-size);\n}\nheader {\n grid-area: header;\n}\n.top {\n grid-area: top;\n min-width: 0;\n}\n/* The tool settings fill the top pane */\n.top pc-tool-settings {\n flex: 1;\n}\n.center {\n position: relative;\n grid-area: center;\n min-width: 0;\n min-height: 0;\n overflow: hidden;\n}\n.bottom {\n grid-area: bottom;\n min-width: 0;\n}\nfooter {\n grid-area: footer;\n}\n.side {\n position: relative;\n z-index: 3;\n display: flex;\n min-height: 0;\n}\n/* A side's panes fill it */\n.side pc-pane,\n.side pc-toolbar,\n.side ::slotted(*) {\n flex: 1;\n}\n.side.left {\n grid-area: left;\n}\n.side.right {\n grid-area: right;\n}\n\n/* Hiding the interface (Tab key) leaves only the canvas */\n:host([ui-hidden]) header,\n:host([ui-hidden]) .top,\n:host([ui-hidden]) .bottom,\n:host([ui-hidden]) footer,\n:host([ui-hidden]) .side {\n display: none;\n}\n\n/* The canvas fills the middle; things drawn over it go in the overlay */\n.center ::slotted([slot=\"center\"]),\n.center pc-canvas {\n position: absolute;\n inset: 0;\n}\n.overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n.overlay > * {\n pointer-events: auto;\n}\npc-canvas-map {\n position: absolute;\n right: 20px;\n bottom: 20px;\n}\n\n/* Menus opened from the toolbar float over the canvas, beside the toolbar */\n.panel {\n position: absolute;\n z-index: 4;\n left: 0;\n bottom: 0;\n max-height: 100%;\n overflow: auto;\n}\n.panel.top-panel {\n top: 0;\n bottom: auto;\n}\n/* The app menu: the implementer's content in a panel */\n.menu-panel:not([open]) {\n display: none;\n}\n\n/* Floating: the side's panes float over the canvas (see pc-pane), so the side takes no space */\n.side[data-mode=\"floating\"] {\n /* Beside the canvas only, so it floats over the canvas and not the tool settings */\n grid-row: 3;\n width: 0;\n}\n/* Overlay: the side's rail sits over the canvas (beside it only, not the tool settings), so the canvas fills the\n width behind it; presses between its panes' buttons (which take presses themselves) reach the canvas */\n.side[data-mode=\"overlay\"] {\n grid-row: 3;\n grid-column: 1 / -1;\n pointer-events: none;\n}\n.side.left[data-mode=\"overlay\"] {\n justify-self: start;\n}\n.side.right[data-mode=\"overlay\"] {\n justify-self: end;\n}\n/* The toolbar's panels open beside a left overlay rail, not under it */\n:host([left-mode=\"overlay\"]) .panel {\n left: var(--pc-left-inset);\n}\n";
3
3
  export { style_default as default };
@@ -1,14 +1,17 @@
1
- import { nothing, type PropertyValues } from "lit";
1
+ import { nothing } from "lit";
2
2
  import { PcElement } from "../../base";
3
3
  import "../PcShortcutSettings";
4
+ import "../PcToggleCheckbox";
5
+ import "../PcDialogWindow";
4
6
  import type PaintCanvas from "../../../lib/classes/PaintCanvas";
5
7
  import { type SaveMethod } from "../../../lib/rendering/screenshot";
6
8
  /**
7
- * 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
8
11
  * `pc-shortcut-settings`), and "Import settings" and "Export settings", which read and write a settings JSON file (input
9
- * settings, keyboard shortcuts, colour palette, colours, theme and language). The window is a modal `<dialog>` above the whole page; it
10
- * closes with its Close button, Escape, or a press on the backdrop, and the browser gives focus back to the button. Fires `pc-toast` after importing, or when importing or
11
- * exporting fails.
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
14
+ * back to the button. Fires `pc-toast` after importing, or when importing or exporting fails.
12
15
  *
13
16
  * @example
14
17
  * html`<pc-app-settings .app=${canvas}></pc-app-settings>`
@@ -39,8 +42,8 @@ export declare class PcAppSettings extends PcElement {
39
42
  shortcutsOpen: boolean;
40
43
  /** Saves exported files (replaceable for tests) */
41
44
  saveFile: (blob: Blob, name: string) => Promise<SaveMethod | undefined>;
45
+ private changes;
42
46
  constructor();
43
- protected updated(changed: PropertyValues<this>): void;
44
47
  /** Close the keyboard shortcuts window */
45
48
  closeShortcuts(): void;
46
49
  /**
@@ -53,7 +56,12 @@ export declare class PcAppSettings extends PcElement {
53
56
  exportFile(): Promise<void>;
54
57
  /** Show a settings file error (one of `SETTINGS_FILE_ERRORS`) in the chosen language */
55
58
  private toastError;
56
- /** The keyboard shortcuts window: a modal dialog, with its contents only while open */
59
+ /** The keyboard shortcuts window, with its contents only while open */
57
60
  private renderShortcuts;
61
+ /**
62
+ * The switch that draws with the GPU (or the CPU) from the next start, saying when the page needs reloading to use
63
+ * the choice (see `PaintCanvas.rendering`).
64
+ */
65
+ private renderRendering;
58
66
  render(): import("lit").TemplateResult<1> | typeof nothing;
59
67
  }
@@ -1,7 +1,10 @@
1
1
  import { SETTINGS_FILE_FORMAT } from "../../../lib/settings/schema.js";
2
2
  import { ICON_CONTROLS_CSS, PILLS_CSS, WINDOWS_CSS } from "../../styles/index.js";
3
3
  import { PcElement, defineElement, emit } from "../../base/index.js";
4
+ import ChangeController from "../../ChangeController/index.js";
4
5
  import "../PcShortcutSettings/index.js";
6
+ import "../PcToggleCheckbox/index.js";
7
+ import "../PcDialogWindow/index.js";
5
8
  import { SETTINGS_FILE_ERRORS, SettingsFileError, applySettings, collectSettings, exportSettingsFile, parseSettingsFile, settingsFileName } from "../../../lib/settings/index.js";
6
9
  import { JSON_FILE_TYPE, saveFile } from "../../../lib/rendering/screenshot/index.js";
7
10
  import { css, html, nothing, unsafeCSS } from "lit";
@@ -12,11 +15,12 @@ var SETTINGS_FILE_ERROR_KEYS = {
12
15
  [SETTINGS_FILE_ERRORS.read]: "paintCanvas.settings.fileReadFailed"
13
16
  };
14
17
  /**
15
- * 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
16
20
  * `pc-shortcut-settings`), and "Import settings" and "Export settings", which read and write a settings JSON file (input
17
- * settings, keyboard shortcuts, colour palette, colours, theme and language). The window is a modal `<dialog>` above the whole page; it
18
- * closes with its Close button, Escape, or a press on the backdrop, and the browser gives focus back to the button. Fires `pc-toast` after importing, or when importing or
19
- * exporting fails.
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
23
+ * back to the button. Fires `pc-toast` after importing, or when importing or exporting fails.
20
24
  *
21
25
  * @example
22
26
  * html`<pc-app-settings .app=${canvas}></pc-app-settings>`
@@ -43,37 +47,25 @@ var PcAppSettings = class extends PcElement {
43
47
  font-family: var(--pc-font);
44
48
  font-size: var(--pc-font-size);
45
49
  }
50
+ /* One full-width button per row */
46
51
  .actions {
47
52
  display: flex;
48
- flex-wrap: wrap;
53
+ flex-direction: column;
49
54
  gap: var(--pc-space-2);
50
55
  }
51
- /* A modal window in the browser's top layer, above everything on the page */
52
- dialog {
53
- box-sizing: border-box;
54
- width: min(480px, calc(100vw - 2 * var(--pc-space-3)));
55
- max-height: calc(100vh - 2 * var(--pc-space-3));
56
- padding: 0;
57
- border: 0;
58
- border-radius: var(--pc-radius);
59
- background: var(--pc-surface-raised);
60
- color: var(--pc-text);
61
- box-shadow: var(--pc-shadow);
56
+ .actions button {
57
+ width: 100%;
62
58
  }
63
- dialog::backdrop {
64
- background: var(--pc-backdrop);
59
+ .hint {
60
+ display: block;
61
+ color: var(--pc-text-muted);
62
+ font-size: 0.85em;
65
63
  }
66
- .window {
67
- display: flex;
68
- flex-direction: column;
69
- gap: var(--pc-space-3);
70
- box-shadow: none;
64
+ .pending {
65
+ color: var(--pc-text);
71
66
  }
72
- .header {
73
- display: flex;
74
- gap: var(--pc-space-2);
75
- align-items: center;
76
- justify-content: space-between;
67
+ pc-dialog-window {
68
+ --pc-dialog-width: 480px;
77
69
  }
78
70
  h2 {
79
71
  margin: 0;
@@ -83,17 +75,11 @@ var PcAppSettings = class extends PcElement {
83
75
  ];
84
76
  /** Saves exported files (replaceable for tests) */
85
77
  saveFile = (blob, name) => saveFile(blob, name, void 0, JSON_FILE_TYPE);
78
+ changes = new ChangeController(this, () => [this.app?.ui]);
86
79
  constructor() {
87
80
  super();
88
81
  this.shortcutsOpen = false;
89
82
  }
90
- updated(changed) {
91
- if (!changed.has("shortcutsOpen")) return;
92
- const dialog = this.renderRoot.querySelector("dialog");
93
- if (!dialog) return;
94
- if (this.shortcutsOpen && !dialog.open) dialog.showModal();
95
- else if (!this.shortcutsOpen && dialog.open) dialog.close();
96
- }
97
83
  /** Close the keyboard shortcuts window */
98
84
  closeShortcuts() {
99
85
  this.shortcutsOpen = false;
@@ -143,39 +129,44 @@ var PcAppSettings = class extends PcElement {
143
129
  kind: "error"
144
130
  });
145
131
  }
146
- /** The keyboard shortcuts window: a modal dialog, with its contents only while open */
132
+ /** The keyboard shortcuts window, with its contents only while open */
147
133
  renderShortcuts(app) {
148
- return html`<dialog
149
- aria-labelledby="shortcuts-title"
150
- @close=${() => this.closeShortcuts()}
151
- @click=${(e) => {
152
- if (e.target === e.currentTarget) this.closeShortcuts();
153
- }}
134
+ return html`<pc-dialog-window
135
+ class="shortcuts-window"
136
+ shaded
137
+ ?open=${this.shortcutsOpen}
138
+ @pc-dismiss=${() => this.closeShortcuts()}
139
+ >
140
+ <h2 slot="header-middle">${this.t("paintCanvas.shortcuts.title")}</h2>
141
+ ${this.shortcutsOpen ? html`<pc-shortcut-settings
142
+ slot="body"
143
+ .settings=${app.shortcuts}
144
+ ></pc-shortcut-settings>` : nothing}
145
+ </pc-dialog-window>`;
146
+ }
147
+ /**
148
+ * The switch that draws with the GPU (or the CPU) from the next start, saying when the page needs reloading to use
149
+ * the choice (see `PaintCanvas.rendering`).
150
+ */
151
+ renderRendering(app) {
152
+ const chosen = app.ui.data.rendering;
153
+ const pending = chosen !== app.rendering;
154
+ return html`<pc-toggle-checkbox
155
+ class="gpu"
156
+ .checked=${chosen === "gpu"}
157
+ @change=${(e) => app.ui.update({ rendering: e.detail.checked ? "gpu" : "cpu" })}
154
158
  >
155
- ${this.shortcutsOpen ? html`<div class="window">
156
- <div class="header">
157
- <h2 id="shortcuts-title">
158
- ${this.t("paintCanvas.shortcuts.title")}
159
- </h2>
160
- <button
161
- type="button"
162
- class="close dismiss"
163
- autofocus
164
- @click=${() => this.closeShortcuts()}
165
- >
166
- ${this.t("paintCanvas.common.close")}
167
- </button>
168
- </div>
169
- <pc-shortcut-settings
170
- .settings=${app.shortcuts}
171
- ></pc-shortcut-settings>
172
- </div>` : nothing}
173
- </dialog>`;
159
+ ${this.t("paintCanvas.settings.gpu")}
160
+ <span class="hint ${pending ? "pending" : ""}" aria-live="polite">
161
+ ${this.t(pending ? "paintCanvas.settings.reloadToApply" : "paintCanvas.settings.gpuHint")}
162
+ </span>
163
+ </pc-toggle-checkbox>`;
174
164
  }
175
165
  render() {
176
166
  const app = this.app;
177
167
  if (!app) return nothing;
178
- return html`<div class="actions">
168
+ return html`${this.renderRendering(app)}
169
+ <div class="actions">
179
170
  <button
180
171
  type="button"
181
172
  class="pill shortcuts"