@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
@@ -5,8 +5,11 @@ import ChangeController from "../../ChangeController/index.js";
5
5
  import { BlendMode } from "../../../lib/classes/Layer/index.js";
6
6
  import { drawCheckerboard, fitSize } from "../../../lib/rendering/checkerboard/index.js";
7
7
  import { CANCEL_CHOICE } from "../PcDialog/index.js";
8
+ import "../PcSliderRange/index.js";
9
+ import { askText } from "../../ask/index.js";
8
10
  import { MENU_MARGIN, menuTop } from "../PcContextMenu/index.js";
9
11
  import { html, nothing, unsafeCSS } from "lit";
12
+ import { live } from "lit/directives/live.js";
10
13
  // src/ui/elements/PcLayerItem/index.ts
11
14
  /** Blend mode options shown in the layer's select input */
12
15
  var BLEND_MODE_OPTIONS = [
@@ -37,6 +40,10 @@ var BLEND_MODE_OPTIONS = [
37
40
  ];
38
41
  /** Context menu options in display order, with the translation keys of their labels */
39
42
  var LAYER_ACTIONS = [
43
+ {
44
+ action: "rename",
45
+ label: "paintCanvas.layers.rename"
46
+ },
40
47
  {
41
48
  action: "mask",
42
49
  label: "paintCanvas.layers.createMask"
@@ -129,7 +136,9 @@ var DRAG_THRESHOLD = 4;
129
136
  /** Event a row dispatches while its layer is dragged by its reorder handle (detail: {@link LayerDragDetail}) */
130
137
  var LAYER_DRAG_EVENT = "pc-layer-drag";
131
138
  /**
132
- * One row in the layers menu: reorder handle, thumbnail and name (then opacity and blend mode under them), alpha lock (padlock) and visibility (eye) icons.
139
+ * One row in the layers menu: reorder handle, thumbnail and name (then opacity and blend mode under them), alpha lock
140
+ * (padlock) and visibility (eye) icons. Pressing anywhere on the row (its controls too) selects the layer, which is then
141
+ * shown with a purple background (`active`). "Rename…" in the actions menu asks for a new name.
133
142
  * Dragging the handle (with mouse, pen or touch) reorders the layer: the row fires {@link LAYER_DRAG_EVENT} and the
134
143
  * layers menu shows where it will land. Clicking or tapping the handle, pressing Enter or Space on it, or right-clicking
135
144
  * it opens the layer's actions menu (which can also reorder it). Cmd/Ctrl + click on the thumbnail or name (or "Create
@@ -154,6 +163,10 @@ var PcLayerItem = class extends PcElement {
154
163
  dropAction: {
155
164
  attribute: "drop-action",
156
165
  reflect: true
166
+ },
167
+ active: {
168
+ type: Boolean,
169
+ reflect: true
157
170
  }
158
171
  };
159
172
  static styles = [
@@ -162,6 +175,8 @@ var PcLayerItem = class extends PcElement {
162
175
  unsafeCSS(style_default),
163
176
  unsafeCSS(WINDOWS_CSS)
164
177
  ];
178
+ /** Asks the user for the layer's new name (replaceable for tests) */
179
+ prompt = (options) => askText(this.renderRoot, options);
165
180
  /** The pointer pressed on the reorder handle, where it was pressed, and whether it has started dragging */
166
181
  press;
167
182
  /** Whether to ignore the next click on the handle (it ended a drag) */
@@ -173,7 +188,15 @@ var PcLayerItem = class extends PcElement {
173
188
  super();
174
189
  this.menuOpen = false;
175
190
  this.dragging = false;
191
+ this.active = false;
192
+ this.addEventListener("click", this.onClick);
176
193
  }
194
+ /** Pressing anywhere on the row selects its layer, except in the dialogs it shows (e.g. after deleting it) */
195
+ onClick = (event) => {
196
+ const { stack, layer } = this;
197
+ if (!stack || !layer || !stack.layers.includes(layer)) return;
198
+ if (!event.composedPath().some((target) => target instanceof Element && target.matches("pc-dialog")) && stack.active !== layer) stack.select(layer.id);
199
+ };
177
200
  /**
178
201
  * Run a context menu action. Merging down, clearing and deleting ask for confirmation first (see `confirmed`).
179
202
  */
@@ -182,6 +205,9 @@ var PcLayerItem = class extends PcElement {
182
205
  this.menuOpen = false;
183
206
  if (!stack || !layer || !this.canRun(action)) return;
184
207
  switch (action) {
208
+ case "rename":
209
+ this.rename(stack, layer);
210
+ break;
185
211
  case "mask":
186
212
  this.requestMask(layer, {
187
213
  shiftKey: false,
@@ -360,10 +386,9 @@ var PcLayerItem = class extends PcElement {
360
386
  }));
361
387
  }
362
388
  /**
363
- * Clicking the row selects the layer. Cmd/Ctrl + click on the thumbnail or name also makes a mask from the layer's alpha.
389
+ * Cmd/Ctrl + click on the thumbnail or name makes a mask from the layer's alpha (the click also selects the layer).
364
390
  */
365
- onRowClick(event, stack, layer) {
366
- stack.select(layer.id);
391
+ onDetailsClick(event, layer) {
367
392
  if (!event.metaKey && !event.ctrlKey) return;
368
393
  const target = event.composedPath()[0];
369
394
  if (!(target instanceof Element) || !target.matches(".thumbnail, .name")) return;
@@ -371,15 +396,21 @@ var PcLayerItem = class extends PcElement {
371
396
  this.requestMask(layer, event);
372
397
  }
373
398
  /**
374
- * Rename a layer when the name input changes. Empty names are reverted.
399
+ * Ask for the layer's new name and use it. Cancelling or an empty name leaves the name as it was.
375
400
  */
376
- onRename(event, stack, layer) {
377
- const input = event.target;
378
- try {
379
- stack.rename(layer.id, input.value);
380
- } catch {
381
- input.value = layer.name;
382
- }
401
+ async rename(stack, layer) {
402
+ const name = (await this.prompt({
403
+ heading: this.t("paintCanvas.layers.renameTitle"),
404
+ label: this.t("paintCanvas.layers.name"),
405
+ value: layer.name,
406
+ confirm: this.t("paintCanvas.common.save"),
407
+ cancel: this.t("paintCanvas.common.cancel")
408
+ }))?.trim();
409
+ if (name && stack.layers.includes(layer)) stack.rename(layer.id, name);
410
+ }
411
+ willUpdate(changed) {
412
+ super.willUpdate(changed);
413
+ this.active = this.layer !== void 0 && this.stack?.active.id === this.layer.id;
383
414
  }
384
415
  updated() {
385
416
  const id = this.layer?.id;
@@ -434,7 +465,6 @@ var PcLayerItem = class extends PcElement {
434
465
  render() {
435
466
  const { stack, layer } = this;
436
467
  if (!stack || !layer) return html``;
437
- const active = stack.active.id === layer.id;
438
468
  return html`
439
469
  <pc-context-menu
440
470
  class="handle"
@@ -451,23 +481,20 @@ var PcLayerItem = class extends PcElement {
451
481
  >
452
482
  <div
453
483
  class="details"
454
- @click=${(e) => this.onRowClick(e, stack, layer)}
484
+ @click=${(e) => this.onDetailsClick(e, layer)}
455
485
  >
456
486
  <canvas class="thumbnail" aria-hidden="true"></canvas>
457
- <input
458
- class="name ${active ? "active" : ""}"
459
- aria-label=${this.t("paintCanvas.layers.name")}
460
- .value=${layer.name}
461
- @change=${(e) => this.onRename(e, stack, layer)}
462
- />
463
- <input
464
- type="range"
487
+ <span class="name" title=${layer.name}>${layer.name}</span>
488
+ <pc-slider-range
489
+ single
490
+ class="opacity"
491
+ label=${this.t("paintCanvas.common.opacity")}
465
492
  min="0"
466
493
  max="100"
467
- aria-label=${this.t("paintCanvas.common.opacity")}
468
- .value=${String(Math.round(layer.opacity * 100))}
469
- @change=${(e) => stack.update(layer.id, { opacity: Number(e.target.value) / 100 })}
470
- />
494
+ unit="%"
495
+ .value=${live(Math.round(layer.opacity * 100))}
496
+ @pc-change=${(e) => stack.update(layer.id, { opacity: e.detail.value / 100 })}
497
+ ></pc-slider-range>
471
498
  <select
472
499
  aria-label=${this.t("paintCanvas.layers.blendMode")}
473
500
  @change=${(e) => stack.update(layer.id, { blendMode: e.target.value })}
@@ -1,3 +1,3 @@
1
1
  // src/ui/elements/PcLayerItem/style.css?inline
2
- var style_default = "/* A white row with space around its parts. The middle column can shrink, so the row never grows wider than its panel */\n:host {\n display: grid;\n grid-template-columns: 30px minmax(0, 1fr) 30px 30px;\n align-items: center;\n column-gap: 6px;\n position: relative;\n padding: 6px;\n border: 1px solid var(--pc-surface-active);\n background: var(--pc-surface);\n}\n.name.active {\n font-weight: bold;\n}\n.thumbnail {\n width: 30px;\n height: 30px;\n}\n/* Thumbnail on the left and name on the right; under them, opacity on the left and blend mode on the right. The blend\n mode gets a set share of the width rather than its own: browsers size dropdowns differently (Safari and Firefox as\n wide as the longest option, Chrome as the chosen one), which would otherwise push the row wider than its panel */\n.details {\n display: grid;\n grid-template-columns: 30px minmax(0, 1fr) minmax(0, 2fr);\n align-items: center;\n gap: 4px 6px;\n min-width: 0;\n}\n.details .name {\n grid-column: 2 / -1;\n}\n.details .name,\n.details select {\n box-sizing: border-box;\n width: 100%;\n min-width: 0;\n}\n.details input[type=\"range\"] {\n grid-column: 1 / 3;\n min-width: 0;\n margin: 0;\n}\n/* The reorder handle drags with mouse, pen or touch (touch drags it rather than scrolling the panel) */\n.handle {\n /* As tall as the row */\n align-self: stretch;\n}\n.handle::part(button) {\n min-width: 0;\n padding: 0;\n cursor: grab;\n touch-action: none;\n}\n/* While dragged: the row floats with the pointer, out of the list (the layers menu places it), and the cursor shows\n what dropping would do (\"+\" duplicates on \"New Layer\") */\n:host([dragging]) {\n position: fixed;\n z-index: 10;\n opacity: 0.9;\n background: Canvas;\n box-shadow: var(--pc-shadow);\n}\n:host([dragging]) .handle::part(button) {\n cursor: grabbing;\n}\n:host([drop-action=\"duplicate\"]) .handle::part(button) {\n cursor: copy;\n}\n:host([drop-action=\"none\"]) .handle::part(button) {\n cursor: no-drop;\n}\n/* Alpha lock and visibility: boxed icon toggles like the settings bar's, darker while on */\n.alpha-lock,\n.visibility {\n width: 30px;\n height: 30px;\n padding: 2px;\n border: 1px solid var(--pc-border);\n border-radius: 4px;\n background: var(--pc-surface);\n cursor: pointer;\n}\n.alpha-lock:hover,\n.visibility:hover {\n background: var(--pc-surface-hover);\n}\n.alpha-lock[aria-pressed=\"true\"],\n.visibility[aria-pressed=\"true\"] {\n background: var(--pc-surface-active);\n}\n.alpha-lock:focus-visible,\n.visibility:focus-visible {\n outline: var(--pc-focus-width) solid var(--pc-focus);\n}\n.alpha-lock img,\n.visibility img {\n display: block;\n width: 24px;\n height: 24px;\n margin: auto;\n}\n";
2
+ var style_default = "/* A white row with space around its parts. The middle column can shrink, so the row never grows wider than its panel */\n:host {\n display: grid;\n grid-template-columns: 30px minmax(0, 1fr) 30px 30px;\n align-items: center;\n column-gap: 6px;\n position: relative;\n padding: 6px;\n border: 1px solid var(--pc-surface-active);\n background: var(--pc-surface);\n}\n/* The selected layer: purple, so it stands out */\n:host([active]) {\n border-color: var(--pc-accent);\n background: var(--pc-accent);\n color: var(--pc-accent-text);\n}\n:host([active]) .name {\n font-weight: bold;\n}\n/* The opacity slider's track and handle in the row's text colour, so they show on purple */\n:host([active]) .opacity {\n --pc-accent: var(--pc-accent-text);\n --pc-border: rgb(255 255 255 / 45%);\n}\n/* The name, on one line (cut short with \"…\"); renamed from the actions menu */\n.name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.thumbnail {\n width: 30px;\n height: 30px;\n}\n/* Thumbnail on the left and name on the right; under them, opacity, then blend mode, each the full width. The blend\n mode never sets its own width: browsers size dropdowns differently (Safari and Firefox as wide as the longest option,\n Chrome as the chosen one), which would otherwise push the row wider than its panel */\n.details {\n display: grid;\n grid-template-columns: 30px minmax(0, 1fr);\n align-items: center;\n gap: 4px 6px;\n min-width: 0;\n}\n.details .opacity,\n.details select {\n grid-column: 1 / -1;\n box-sizing: border-box;\n width: 100%;\n min-width: 0;\n}\n/* The reorder handle drags with mouse, pen or touch (touch drags it rather than scrolling the panel) */\n.handle {\n /* As tall as the row */\n align-self: stretch;\n}\n.handle::part(button) {\n min-width: 0;\n padding: 0;\n cursor: grab;\n touch-action: none;\n}\n/* While dragged: the row floats with the pointer, out of the list (the layers menu places it), and the cursor shows\n what dropping would do (\"+\" duplicates on \"New Layer\") */\n:host([dragging]) {\n position: fixed;\n z-index: 10;\n opacity: 0.9;\n background: Canvas;\n box-shadow: var(--pc-shadow);\n}\n:host([dragging]) .handle::part(button) {\n cursor: grabbing;\n}\n:host([drop-action=\"duplicate\"]) .handle::part(button) {\n cursor: copy;\n}\n:host([drop-action=\"none\"]) .handle::part(button) {\n cursor: no-drop;\n}\n/* Alpha lock and visibility: boxed icon toggles like the settings bar's, darker while on */\n.alpha-lock,\n.visibility {\n width: 30px;\n height: 30px;\n padding: 2px;\n border: 1px solid var(--pc-border);\n border-radius: 4px;\n background: var(--pc-surface);\n cursor: pointer;\n}\n.alpha-lock:hover,\n.visibility:hover {\n background: var(--pc-surface-hover);\n}\n.alpha-lock[aria-pressed=\"true\"],\n.visibility[aria-pressed=\"true\"] {\n background: var(--pc-surface-active);\n}\n.alpha-lock:focus-visible,\n.visibility:focus-visible {\n outline: var(--pc-focus-width) solid var(--pc-focus);\n}\n.alpha-lock img,\n.visibility img {\n display: block;\n width: 24px;\n height: 24px;\n margin: auto;\n}\n";
3
3
  export { style_default as default };
@@ -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 {};