@lvl99dotcom/paintcanvas 0.3.1 → 0.5.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.
- package/README.md +30 -15
- package/dist/lib/brushes/StrokeEngine/index.d.ts +2 -2
- package/dist/lib/brushes/StrokeEngine/index.js +26 -28
- package/dist/lib/brushes/presets/round/index.d.ts +1 -1
- package/dist/lib/brushes/presets/round/index.js +12 -7
- package/dist/lib/brushes/presets/square/index.d.ts +1 -1
- package/dist/lib/brushes/presets/square/index.js +7 -3
- package/dist/lib/brushes/shapeImage/index.d.ts +7 -3
- package/dist/lib/brushes/shapeImage/index.js +15 -9
- package/dist/lib/brushes/shapes/index.d.ts +18 -0
- package/dist/lib/brushes/shapes/index.js +86 -0
- package/dist/lib/classes/BrushSettings/index.d.ts +53 -2
- package/dist/lib/classes/BrushSettings/index.js +85 -14
- package/dist/lib/classes/KeyboardShortcuts/index.d.ts +47 -7
- package/dist/lib/classes/KeyboardShortcuts/index.js +145 -20
- package/dist/lib/classes/PaintCanvas/index.d.ts +123 -27
- package/dist/lib/classes/PaintCanvas/index.js +208 -41
- package/dist/lib/classes/PaintCanvas/tool.d.ts +15 -0
- package/dist/lib/classes/PaintCanvas/tool.js +18 -0
- package/dist/lib/classes/Palette/index.d.ts +58 -0
- package/dist/lib/classes/Palette/index.js +92 -0
- package/dist/lib/classes/SettingsAutosave/index.d.ts +53 -0
- package/dist/lib/classes/SettingsAutosave/index.js +98 -0
- package/dist/lib/classes/ShortcutSettings/index.d.ts +63 -0
- package/dist/lib/classes/ShortcutSettings/index.js +110 -0
- package/dist/lib/classes/UiSettings/index.d.ts +68 -0
- package/dist/lib/classes/UiSettings/index.js +82 -0
- package/dist/lib/palette/index.d.ts +49 -0
- package/dist/lib/palette/index.js +82 -0
- package/dist/lib/palette/schema.d.ts +45 -0
- package/dist/lib/palette/schema.js +41 -0
- package/dist/lib/rendering/cursors/index.d.ts +2 -0
- package/dist/lib/rendering/cursors/index.js +3 -1
- package/dist/lib/rendering/sample/index.js +3 -2
- package/dist/lib/settings/index.d.ts +63 -0
- package/dist/lib/settings/index.js +93 -0
- package/dist/lib/settings/schema.d.ts +168 -0
- package/dist/lib/settings/schema.js +116 -0
- package/dist/lib/storage/index.d.ts +36 -5
- package/dist/lib/storage/index.js +87 -29
- package/dist/lib/utils/colour/index.d.ts +72 -0
- package/dist/lib/utils/colour/index.js +154 -0
- package/dist/lib/utils/shortcuts/index.d.ts +174 -11
- package/dist/lib/utils/shortcuts/index.js +391 -145
- package/dist/lib/validation/validators.generated.d.ts +5 -0
- package/dist/lib/validation/validators.generated.js +1040 -1
- package/dist/locales/en-GB.js +96 -3
- package/dist/locales/en-US.js +96 -3
- package/dist/locales/fr.js +96 -3
- package/dist/locales/ja.js +96 -3
- package/dist/locales/ko.js +96 -3
- package/dist/locales/zh-Hans.js +96 -3
- package/dist/locales/zh-Hant.js +96 -3
- package/dist/package.js +1 -1
- package/dist/testing/recordingCanvas.js +2 -1
- package/dist/ui/base/index.d.ts +2 -2
- package/dist/ui/dismiss/index.d.ts +21 -0
- package/dist/ui/dismiss/index.js +27 -0
- package/dist/ui/drag/index.d.ts +51 -0
- package/dist/ui/drag/index.js +81 -0
- package/dist/ui/elements/PaintCanvas/index.d.ts +57 -50
- package/dist/ui/elements/PaintCanvas/index.js +141 -174
- package/dist/ui/elements/PaintCanvas/style.js +1 -1
- package/dist/ui/elements/PcAppSettings/index.d.ts +59 -0
- package/dist/ui/elements/PcAppSettings/index.js +222 -0
- package/dist/ui/elements/PcBrushConfig/index.d.ts +9 -2
- package/dist/ui/elements/PcBrushConfig/index.js +47 -7
- package/dist/ui/elements/PcBrushConfig/style.js +1 -1
- package/dist/ui/elements/PcBrushSize/index.d.ts +53 -0
- package/dist/ui/elements/PcBrushSize/index.js +66 -0
- package/dist/ui/elements/PcBrushTest/index.d.ts +26 -0
- package/dist/ui/elements/PcBrushTest/index.js +73 -5
- package/dist/ui/elements/PcBrushTest/style.js +1 -1
- package/dist/ui/elements/PcCanvas/index.d.ts +22 -2
- package/dist/ui/elements/PcCanvas/index.js +60 -15
- package/dist/ui/elements/PcCanvasMap/style.js +1 -1
- package/dist/ui/elements/PcClipboardTools/index.d.ts +48 -0
- package/dist/ui/elements/PcClipboardTools/index.js +118 -0
- package/dist/ui/elements/PcColourPair/index.d.ts +43 -0
- package/dist/ui/elements/PcColourPair/index.js +125 -0
- package/dist/ui/elements/PcContextMenu/index.d.ts +71 -34
- package/dist/ui/elements/PcContextMenu/index.js +128 -97
- package/dist/ui/elements/PcInputSettings/index.d.ts +13 -0
- package/dist/ui/elements/PcInputSettings/index.js +16 -1
- package/dist/ui/elements/PcLayerItem/index.d.ts +13 -34
- package/dist/ui/elements/PcLayerItem/index.js +39 -91
- package/dist/ui/elements/PcLayerItem/style.js +1 -1
- package/dist/ui/elements/PcMenu/index.d.ts +138 -0
- package/dist/ui/elements/PcMenu/index.js +296 -0
- package/dist/ui/elements/PcNumberSlider/index.d.ts +137 -0
- package/dist/ui/elements/PcNumberSlider/index.js +247 -0
- package/dist/ui/elements/PcPane/index.d.ts +120 -0
- package/dist/ui/elements/PcPane/index.js +394 -0
- package/dist/ui/elements/PcPanel/index.d.ts +26 -8
- package/dist/ui/elements/PcPanel/index.js +76 -8
- package/dist/ui/elements/{PcBrushMenu → PcPanelBrushes}/index.d.ts +22 -21
- package/dist/ui/elements/{PcBrushMenu → PcPanelBrushes}/index.js +24 -57
- package/dist/ui/elements/PcPanelBrushes/style.js +3 -0
- package/dist/ui/elements/PcPanelColours/index.d.ts +171 -0
- package/dist/ui/elements/PcPanelColours/index.js +650 -0
- package/dist/ui/elements/PcPanelColours/style.js +3 -0
- package/dist/ui/elements/{PcLayersMenu → PcPanelLayers}/index.d.ts +17 -4
- package/dist/ui/elements/{PcLayersMenu → PcPanelLayers}/index.js +17 -10
- package/dist/ui/elements/PcPanelLayers/style.js +3 -0
- package/dist/ui/elements/PcPopover/index.d.ts +39 -1
- package/dist/ui/elements/PcPopover/index.js +72 -4
- package/dist/ui/elements/PcShortcutSettings/index.d.ts +69 -0
- package/dist/ui/elements/PcShortcutSettings/index.js +273 -0
- package/dist/ui/elements/PcThemePicker/index.d.ts +17 -15
- package/dist/ui/elements/PcThemePicker/index.js +22 -21
- package/dist/ui/elements/PcToolSettings/index.d.ts +48 -9
- package/dist/ui/elements/PcToolSettings/index.js +384 -319
- package/dist/ui/elements/PcToolSettings/style.js +1 -1
- package/dist/ui/elements/PcToolbar/index.d.ts +62 -12
- package/dist/ui/elements/PcToolbar/index.js +119 -57
- package/dist/ui/elements/PcToolbar/style.js +1 -1
- package/dist/ui/icons/images/icon-brush@2x.js +3 -0
- package/dist/ui/icons/images/icon-circle-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-clipboard@2x.js +3 -0
- package/dist/ui/icons/images/icon-cog@2x.js +3 -0
- package/dist/ui/icons/images/icon-cursor@2x.js +3 -0
- package/dist/ui/icons/images/icon-diagonal-tlbr-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-diagonal-trbl-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-eraser@2x.js +3 -0
- package/dist/ui/icons/images/icon-eyedropper@2x.js +3 -0
- package/dist/ui/icons/images/icon-flip-horizontal@2x.js +3 -0
- package/dist/ui/icons/images/icon-flip-vertical@2x.js +3 -0
- package/dist/ui/icons/images/icon-focus-pen@2x.js +3 -0
- package/dist/ui/icons/images/icon-freehand-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-hidden@2x.js +3 -0
- package/dist/ui/icons/images/icon-horizontal-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-layers@2x.js +3 -0
- package/dist/ui/icons/images/icon-linked@2x.js +3 -0
- package/dist/ui/icons/images/icon-locked@2x.js +3 -0
- package/dist/ui/icons/images/icon-moon@2x.js +3 -0
- package/dist/ui/icons/images/icon-pan@2x.js +3 -0
- package/dist/ui/icons/images/icon-polygon-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-pressure@2x.js +3 -0
- package/dist/ui/icons/images/icon-randomise@2x.js +3 -0
- package/dist/ui/icons/images/icon-rectangle-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-redo@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate-90-left@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate-90-right@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-bezier-cubic@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-bezier-quad@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-linear@2x.js +3 -0
- package/dist/ui/icons/images/icon-star@2x.js +3 -0
- package/dist/ui/icons/images/icon-sun@2x.js +3 -0
- package/dist/ui/icons/images/icon-tilt@2x.js +3 -0
- package/dist/ui/icons/images/icon-undo@2x.js +3 -0
- package/dist/ui/icons/images/icon-unlinked@2x.js +3 -0
- package/dist/ui/icons/images/icon-unlocked@2x.js +3 -0
- package/dist/ui/icons/images/icon-vertical-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-visible@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom-in@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom-out@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom@2x.js +3 -0
- package/dist/ui/icons/index.d.ts +1 -1
- package/dist/ui/icons/index.js +84 -72
- package/dist/ui/index.d.ts +13 -2
- package/dist/ui/index.js +16 -4
- package/dist/ui/layout/index.d.ts +9 -1
- package/dist/ui/layout/index.js +11 -2
- package/dist/ui/tokens/index.d.ts +5 -4
- package/dist/ui/tokens/index.js +15 -8
- package/locales/en-GB.json +96 -3
- package/locales/en-US.json +96 -3
- package/locales/fr.json +96 -3
- package/locales/ja.json +96 -3
- package/locales/ko.json +96 -3
- package/locales/zh-Hans.json +96 -3
- package/locales/zh-Hant.json +96 -3
- package/package.json +11 -4
- package/dist/ui/elements/PcBrushMenu/style.js +0 -3
- package/dist/ui/elements/PcLayersMenu/style.js +0 -3
- package/dist/ui/icons/images/brush.js +0 -3
- package/dist/ui/icons/images/circle-mask.js +0 -3
- package/dist/ui/icons/images/clipboard.js +0 -3
- package/dist/ui/icons/images/cursor.js +0 -3
- package/dist/ui/icons/images/eraser.js +0 -3
- package/dist/ui/icons/images/eyedropper.js +0 -3
- package/dist/ui/icons/images/flip-horizontal.js +0 -3
- package/dist/ui/icons/images/flip-vertical.js +0 -3
- package/dist/ui/icons/images/focus-pen.js +0 -3
- package/dist/ui/icons/images/freehand-mask.js +0 -3
- package/dist/ui/icons/images/hidden.js +0 -3
- package/dist/ui/icons/images/layers.js +0 -3
- package/dist/ui/icons/images/linked.js +0 -3
- package/dist/ui/icons/images/locked.js +0 -3
- package/dist/ui/icons/images/moon.js +0 -3
- package/dist/ui/icons/images/pan.js +0 -3
- package/dist/ui/icons/images/polygon-mask.js +0 -3
- package/dist/ui/icons/images/pressure.js +0 -3
- package/dist/ui/icons/images/randomise.js +0 -3
- package/dist/ui/icons/images/rectangle-mask.js +0 -3
- package/dist/ui/icons/images/redo.js +0 -3
- package/dist/ui/icons/images/rotate-90-left.js +0 -3
- package/dist/ui/icons/images/rotate-90-right.js +0 -3
- package/dist/ui/icons/images/smoothing-bezier-cubic.js +0 -3
- package/dist/ui/icons/images/smoothing-bezier-quad.js +0 -3
- package/dist/ui/icons/images/smoothing-linear.js +0 -3
- package/dist/ui/icons/images/star.js +0 -3
- package/dist/ui/icons/images/sun.js +0 -3
- package/dist/ui/icons/images/tilt.js +0 -3
- package/dist/ui/icons/images/undo.js +0 -3
- package/dist/ui/icons/images/unlinked.js +0 -3
- package/dist/ui/icons/images/unlocked.js +0 -3
- package/dist/ui/icons/images/visible.js +0 -3
- package/dist/ui/icons/images/zoom-in.js +0 -3
- package/dist/ui/icons/images/zoom-out.js +0 -3
- package/dist/ui/icons/images/zoom.js +0 -3
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/lib/classes/PaintCanvas/tool.ts
|
|
2
|
+
/** Drawing and navigation tools */
|
|
3
|
+
var Tool = /* @__PURE__ */ function(Tool) {
|
|
4
|
+
Tool["Brush"] = "brush";
|
|
5
|
+
Tool["Eraser"] = "eraser";
|
|
6
|
+
/** Move, scale and rotate */
|
|
7
|
+
Tool["Move"] = "move";
|
|
8
|
+
Tool["RectangleLasso"] = "rectangle-lasso";
|
|
9
|
+
Tool["CircleLasso"] = "circle-lasso";
|
|
10
|
+
Tool["PolygonLasso"] = "polygon-lasso";
|
|
11
|
+
Tool["FreeformLasso"] = "freeform-lasso";
|
|
12
|
+
Tool["Pan"] = "pan";
|
|
13
|
+
Tool["Zoom"] = "zoom";
|
|
14
|
+
/** Set the current colour from the canvas (eyedropper) */
|
|
15
|
+
Tool["ColourSample"] = "colour-sample";
|
|
16
|
+
return Tool;
|
|
17
|
+
}({});
|
|
18
|
+
export { Tool };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep only valid colours from an untrusted value (e.g. stored or imported): lowercase `#rrggbb` strings, each once, up
|
|
3
|
+
* to `MAX_PALETTE_COLOURS`. Undefined if the value isn't an array.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* sanitisePalette(["#FF0000", "red", "#ff0000", "#00ff00"]); // ["#ff0000", "#00ff00"]
|
|
7
|
+
* sanitisePalette("#ff0000"); // undefined
|
|
8
|
+
*/
|
|
9
|
+
export declare const sanitisePalette: (input: unknown) => string[] | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* The user's saved colours, shown as swatches. Each colour is kept once, as lowercase `#rrggbb`, up to
|
|
12
|
+
* `MAX_PALETTE_COLOURS`. Kept in memory; the implementer saves and restores them (see `set` and the `change` event).
|
|
13
|
+
*
|
|
14
|
+
* Dispatches a `change` event when the colours change.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* const palette = new Palette();
|
|
18
|
+
* palette.add("#123456");
|
|
19
|
+
* palette.colours.at(-1); // "#123456"
|
|
20
|
+
*/
|
|
21
|
+
export default class Palette extends EventTarget {
|
|
22
|
+
private values;
|
|
23
|
+
/** Start with some colours (invalid ones are left out), or `DEFAULT_PALETTE` */
|
|
24
|
+
constructor(colours?: readonly string[]);
|
|
25
|
+
/** The colours in order (read-only copy) */
|
|
26
|
+
get colours(): readonly string[];
|
|
27
|
+
/**
|
|
28
|
+
* Add a colour at the end. Returns whether it was added: not if it's already there, the palette is full, or it isn't a
|
|
29
|
+
* `#rrggbb` colour.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* palette.add("#FF0000"); // true, added as "#ff0000"
|
|
33
|
+
*/
|
|
34
|
+
add(colour: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Add colours at the end, e.g. from an imported palette file, leaving out ones already there. Returns how many were
|
|
37
|
+
* added.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* palette.merge(["#ff0000", "#00ff00"]); // 1, if "#ff0000" was already there
|
|
41
|
+
*/
|
|
42
|
+
merge(colours: readonly string[]): number;
|
|
43
|
+
/**
|
|
44
|
+
* Remove the colour at a position.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* palette.remove(0);
|
|
48
|
+
*/
|
|
49
|
+
remove(index: number): void;
|
|
50
|
+
/**
|
|
51
|
+
* Replace all the colours (e.g. when restoring saved ones), keeping only valid ones, and dispatch `change` if they
|
|
52
|
+
* changed.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* palette.set(["#000000", "#ffffff"]);
|
|
56
|
+
*/
|
|
57
|
+
set(colours: unknown): void;
|
|
58
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { isHexColour } from "../../utils/colour/index.js";
|
|
2
|
+
import "../../palette/schema.js";
|
|
3
|
+
import { DEFAULT_PALETTE } from "../../palette/index.js";
|
|
4
|
+
// src/lib/classes/Palette/index.ts
|
|
5
|
+
/**
|
|
6
|
+
* Keep only valid colours from an untrusted value (e.g. stored or imported): lowercase `#rrggbb` strings, each once, up
|
|
7
|
+
* to `MAX_PALETTE_COLOURS`. Undefined if the value isn't an array.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* sanitisePalette(["#FF0000", "red", "#ff0000", "#00ff00"]); // ["#ff0000", "#00ff00"]
|
|
11
|
+
* sanitisePalette("#ff0000"); // undefined
|
|
12
|
+
*/
|
|
13
|
+
var sanitisePalette = (input) => {
|
|
14
|
+
if (!Array.isArray(input)) return void 0;
|
|
15
|
+
const colours = /* @__PURE__ */ new Set();
|
|
16
|
+
for (const value of input) {
|
|
17
|
+
if (colours.size >= 1e3) break;
|
|
18
|
+
if (isHexColour(value)) colours.add(value.toLowerCase());
|
|
19
|
+
}
|
|
20
|
+
return [...colours];
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The user's saved colours, shown as swatches. Each colour is kept once, as lowercase `#rrggbb`, up to
|
|
24
|
+
* `MAX_PALETTE_COLOURS`. Kept in memory; the implementer saves and restores them (see `set` and the `change` event).
|
|
25
|
+
*
|
|
26
|
+
* Dispatches a `change` event when the colours change.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* const palette = new Palette();
|
|
30
|
+
* palette.add("#123456");
|
|
31
|
+
* palette.colours.at(-1); // "#123456"
|
|
32
|
+
*/
|
|
33
|
+
var Palette = class extends EventTarget {
|
|
34
|
+
values;
|
|
35
|
+
/** Start with some colours (invalid ones are left out), or `DEFAULT_PALETTE` */
|
|
36
|
+
constructor(colours = DEFAULT_PALETTE) {
|
|
37
|
+
super();
|
|
38
|
+
this.values = sanitisePalette(colours);
|
|
39
|
+
}
|
|
40
|
+
/** The colours in order (read-only copy) */
|
|
41
|
+
get colours() {
|
|
42
|
+
return this.values;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Add a colour at the end. Returns whether it was added: not if it's already there, the palette is full, or it isn't a
|
|
46
|
+
* `#rrggbb` colour.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* palette.add("#FF0000"); // true, added as "#ff0000"
|
|
50
|
+
*/
|
|
51
|
+
add(colour) {
|
|
52
|
+
const before = this.values.length;
|
|
53
|
+
this.set([...this.values, colour]);
|
|
54
|
+
return this.values.length > before;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Add colours at the end, e.g. from an imported palette file, leaving out ones already there. Returns how many were
|
|
58
|
+
* added.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* palette.merge(["#ff0000", "#00ff00"]); // 1, if "#ff0000" was already there
|
|
62
|
+
*/
|
|
63
|
+
merge(colours) {
|
|
64
|
+
const before = this.values.length;
|
|
65
|
+
this.set([...this.values, ...colours]);
|
|
66
|
+
return this.values.length - before;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Remove the colour at a position.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* palette.remove(0);
|
|
73
|
+
*/
|
|
74
|
+
remove(index) {
|
|
75
|
+
if (index < 0 || index >= this.values.length) return;
|
|
76
|
+
this.set(this.values.filter((_, i) => i !== index));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Replace all the colours (e.g. when restoring saved ones), keeping only valid ones, and dispatch `change` if they
|
|
80
|
+
* changed.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* palette.set(["#000000", "#ffffff"]);
|
|
84
|
+
*/
|
|
85
|
+
set(colours) {
|
|
86
|
+
const next = sanitisePalette(colours);
|
|
87
|
+
if (!next || next.length === this.values.length && next.every((colour, i) => colour === this.values[i])) return;
|
|
88
|
+
this.values = next;
|
|
89
|
+
this.dispatchEvent(new Event("change"));
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
export { Palette as default, sanitisePalette };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type PaintCanvas from "../PaintCanvas";
|
|
2
|
+
import type { SettingsStoreLike } from "../../storage";
|
|
3
|
+
/** How long to wait after settings change before saving, so quick changes (e.g. dragging a colour slider) are saved together (milliseconds) */
|
|
4
|
+
export declare const SETTINGS_SAVE_DELAY_MS = 500;
|
|
5
|
+
/** Event dispatched when loading or saving settings fails (detail: the error) */
|
|
6
|
+
export declare const SETTINGS_AUTOSAVE_ERROR_EVENT = "error";
|
|
7
|
+
/** Options for `SettingsAutosave` */
|
|
8
|
+
export interface SettingsAutosaveOptions {
|
|
9
|
+
/** How long to wait after settings change before saving (default `SETTINGS_SAVE_DELAY_MS`) */
|
|
10
|
+
delayMs?: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Keeps a canvas's app settings (input settings, keyboard shortcuts, colour palette, colours, theme and language) in a
|
|
14
|
+
* store: the saved settings are restored when it starts (invalid ones are ignored), then changes are saved soon after
|
|
15
|
+
* they happen.
|
|
16
|
+
*
|
|
17
|
+
* Dispatches an `error` event (with the error as `detail`) when loading or saving fails.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const autosave = new SettingsAutosave(canvas, new IndexedDbSettingsStore());
|
|
21
|
+
* await autosave.ready;
|
|
22
|
+
* autosave.stop(); // saves any pending change
|
|
23
|
+
*/
|
|
24
|
+
export default class SettingsAutosave extends EventTarget {
|
|
25
|
+
/** Where the settings are kept */
|
|
26
|
+
readonly store: SettingsStoreLike;
|
|
27
|
+
/** Resolves once the saved settings are restored (or there were none) */
|
|
28
|
+
readonly ready: Promise<void>;
|
|
29
|
+
private readonly app;
|
|
30
|
+
private readonly delayMs;
|
|
31
|
+
private timer?;
|
|
32
|
+
/** The settings last saved or restored, as JSON, to skip saving unchanged settings */
|
|
33
|
+
private saved?;
|
|
34
|
+
private stopped;
|
|
35
|
+
private readonly onChange;
|
|
36
|
+
constructor(app: PaintCanvas, store: SettingsStoreLike, options?: SettingsAutosaveOptions);
|
|
37
|
+
/** The canvas parts whose changes are saved */
|
|
38
|
+
private get sources();
|
|
39
|
+
/**
|
|
40
|
+
* Stop saving changes, saving any that are waiting.
|
|
41
|
+
*/
|
|
42
|
+
stop(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Save the settings now if they changed since they were last saved.
|
|
45
|
+
*/
|
|
46
|
+
save(): Promise<void>;
|
|
47
|
+
/** Restore the saved settings, then listen for changes */
|
|
48
|
+
private start;
|
|
49
|
+
/** Save soon, after changes stop for a moment */
|
|
50
|
+
private schedule;
|
|
51
|
+
/** Tell listeners loading or saving failed */
|
|
52
|
+
private fail;
|
|
53
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { applySettings, collectSettings, isValidSettings } from "../../settings/index.js";
|
|
2
|
+
// src/lib/classes/SettingsAutosave/index.ts
|
|
3
|
+
/** How long to wait after settings change before saving, so quick changes (e.g. dragging a colour slider) are saved together (milliseconds) */
|
|
4
|
+
var SETTINGS_SAVE_DELAY_MS = 500;
|
|
5
|
+
/** Event dispatched when loading or saving settings fails (detail: the error) */
|
|
6
|
+
var SETTINGS_AUTOSAVE_ERROR_EVENT = "error";
|
|
7
|
+
/**
|
|
8
|
+
* Keeps a canvas's app settings (input settings, keyboard shortcuts, colour palette, colours, theme and language) in a
|
|
9
|
+
* store: the saved settings are restored when it starts (invalid ones are ignored), then changes are saved soon after
|
|
10
|
+
* they happen.
|
|
11
|
+
*
|
|
12
|
+
* Dispatches an `error` event (with the error as `detail`) when loading or saving fails.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const autosave = new SettingsAutosave(canvas, new IndexedDbSettingsStore());
|
|
16
|
+
* await autosave.ready;
|
|
17
|
+
* autosave.stop(); // saves any pending change
|
|
18
|
+
*/
|
|
19
|
+
var SettingsAutosave = class extends EventTarget {
|
|
20
|
+
/** Where the settings are kept */
|
|
21
|
+
store;
|
|
22
|
+
/** Resolves once the saved settings are restored (or there were none) */
|
|
23
|
+
ready;
|
|
24
|
+
app;
|
|
25
|
+
delayMs;
|
|
26
|
+
timer;
|
|
27
|
+
/** The settings last saved or restored, as JSON, to skip saving unchanged settings */
|
|
28
|
+
saved;
|
|
29
|
+
stopped = false;
|
|
30
|
+
onChange = () => this.schedule();
|
|
31
|
+
constructor(app, store, options = {}) {
|
|
32
|
+
super();
|
|
33
|
+
this.app = app;
|
|
34
|
+
this.store = store;
|
|
35
|
+
this.delayMs = options.delayMs ?? 500;
|
|
36
|
+
this.ready = this.start();
|
|
37
|
+
}
|
|
38
|
+
/** The canvas parts whose changes are saved */
|
|
39
|
+
get sources() {
|
|
40
|
+
const { app } = this;
|
|
41
|
+
return [
|
|
42
|
+
app,
|
|
43
|
+
app.input,
|
|
44
|
+
app.shortcuts,
|
|
45
|
+
app.palette,
|
|
46
|
+
app.ui
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Stop saving changes, saving any that are waiting.
|
|
51
|
+
*/
|
|
52
|
+
stop() {
|
|
53
|
+
if (this.stopped) return;
|
|
54
|
+
this.stopped = true;
|
|
55
|
+
for (const source of this.sources) source.removeEventListener("change", this.onChange);
|
|
56
|
+
if (this.timer === void 0) return;
|
|
57
|
+
clearTimeout(this.timer);
|
|
58
|
+
this.save();
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Save the settings now if they changed since they were last saved.
|
|
62
|
+
*/
|
|
63
|
+
async save() {
|
|
64
|
+
this.timer = void 0;
|
|
65
|
+
const settings = collectSettings(this.app);
|
|
66
|
+
const json = JSON.stringify(settings);
|
|
67
|
+
if (json === this.saved) return;
|
|
68
|
+
this.saved = json;
|
|
69
|
+
try {
|
|
70
|
+
await this.store.save(settings);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
this.saved = void 0;
|
|
73
|
+
this.fail(error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Restore the saved settings, then listen for changes */
|
|
77
|
+
async start() {
|
|
78
|
+
try {
|
|
79
|
+
const result = isValidSettings(await this.store.load());
|
|
80
|
+
if (result.valid && !this.stopped) applySettings(this.app, result.value);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
this.fail(error);
|
|
83
|
+
}
|
|
84
|
+
if (this.stopped) return;
|
|
85
|
+
this.saved = JSON.stringify(collectSettings(this.app));
|
|
86
|
+
for (const source of this.sources) source.addEventListener("change", this.onChange);
|
|
87
|
+
}
|
|
88
|
+
/** Save soon, after changes stop for a moment */
|
|
89
|
+
schedule() {
|
|
90
|
+
clearTimeout(this.timer);
|
|
91
|
+
this.timer = setTimeout(() => void this.save(), this.delayMs);
|
|
92
|
+
}
|
|
93
|
+
/** Tell listeners loading or saving failed */
|
|
94
|
+
fail(error) {
|
|
95
|
+
this.dispatchEvent(new CustomEvent(SETTINGS_AUTOSAVE_ERROR_EVENT, { detail: error }));
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
export { SETTINGS_AUTOSAVE_ERROR_EVENT, SETTINGS_SAVE_DELAY_MS, SettingsAutosave as default };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type KeyCombo, type Shortcut, type ShortcutBindings } from "../../utils/shortcuts";
|
|
2
|
+
/** What happened when setting an action's key combination */
|
|
3
|
+
export interface ShortcutChangeResult {
|
|
4
|
+
/** Whether the key combination was set */
|
|
5
|
+
changed: boolean;
|
|
6
|
+
/** The action that had the key combination and lost it, if any */
|
|
7
|
+
movedFrom?: string;
|
|
8
|
+
/** The fixed action that uses the key combination, so it wasn't set */
|
|
9
|
+
fixed?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The user's keyboard shortcuts: the key combination of each configurable action (see `CONFIGURABLE_SHORTCUTS`), each
|
|
13
|
+
* used by one action at most. Kept in memory; the implementer saves and restores them (see `load` and the `change`
|
|
14
|
+
* event).
|
|
15
|
+
*
|
|
16
|
+
* Dispatches a `change` event when a key combination changes.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const shortcuts = new ShortcutSettings();
|
|
20
|
+
* shortcuts.set("tool-brush", { key: "p" });
|
|
21
|
+
* shortcuts.combo("tool-brush"); // { key: "p" }
|
|
22
|
+
*/
|
|
23
|
+
export default class ShortcutSettings extends EventTarget {
|
|
24
|
+
private values;
|
|
25
|
+
private list;
|
|
26
|
+
constructor(initial?: unknown);
|
|
27
|
+
/** Each configurable action's key combination, by ID (read-only copy) */
|
|
28
|
+
get bindings(): Readonly<ShortcutBindings>;
|
|
29
|
+
/** The shortcuts key presses are matched against (except the hold-to-pan key) */
|
|
30
|
+
get shortcuts(): readonly Shortcut[];
|
|
31
|
+
/** The key held to pan, returning to the last tool when released (null when it has none) */
|
|
32
|
+
get holdToPan(): KeyCombo | null;
|
|
33
|
+
/**
|
|
34
|
+
* A configurable action's key combination (null when it has none or there's no such action).
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* shortcuts.combo("deselect"); // { key: "d", cmd: true }
|
|
38
|
+
*/
|
|
39
|
+
combo(id: string): KeyCombo | null;
|
|
40
|
+
/**
|
|
41
|
+
* Set or remove (null) a configurable action's key combination. If another configurable action has it, that action
|
|
42
|
+
* loses it (`movedFrom`). Nothing changes for a key combination a fixed action uses (`fixed`), or an unknown action.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* shortcuts.set("tool-brush", { key: "e" }); // { changed: true, movedFrom: "tool-eraser" }
|
|
46
|
+
* shortcuts.set("deselect", { key: "z", cmd: true }); // { changed: false, fixed: "undo" }
|
|
47
|
+
*/
|
|
48
|
+
set(id: string, combo: KeyCombo | null): ShortcutChangeResult;
|
|
49
|
+
/**
|
|
50
|
+
* Go back to the default key combinations.
|
|
51
|
+
*/
|
|
52
|
+
reset(): void;
|
|
53
|
+
/**
|
|
54
|
+
* Replace the key combinations, e.g. with saved or imported ones (see `sanitiseShortcutBindings`; actions left out
|
|
55
|
+
* get their defaults), and dispatch `change` if anything changed.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* shortcuts.load({ "tool-brush": { key: "p" } });
|
|
59
|
+
*/
|
|
60
|
+
load(input: unknown): void;
|
|
61
|
+
/** Use new key combinations and tell listeners */
|
|
62
|
+
private apply;
|
|
63
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { CONFIGURABLE_SHORTCUTS, defaultShortcutBindings, fixedShortcutFor, normaliseCombo, sameCombo, sanitiseShortcutBindings, shortcutsFor } from "../../utils/shortcuts/index.js";
|
|
2
|
+
// src/lib/classes/ShortcutSettings/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* The user's keyboard shortcuts: the key combination of each configurable action (see `CONFIGURABLE_SHORTCUTS`), each
|
|
5
|
+
* used by one action at most. Kept in memory; the implementer saves and restores them (see `load` and the `change`
|
|
6
|
+
* event).
|
|
7
|
+
*
|
|
8
|
+
* Dispatches a `change` event when a key combination changes.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const shortcuts = new ShortcutSettings();
|
|
12
|
+
* shortcuts.set("tool-brush", { key: "p" });
|
|
13
|
+
* shortcuts.combo("tool-brush"); // { key: "p" }
|
|
14
|
+
*/
|
|
15
|
+
var ShortcutSettings = class extends EventTarget {
|
|
16
|
+
values;
|
|
17
|
+
list;
|
|
18
|
+
constructor(initial) {
|
|
19
|
+
super();
|
|
20
|
+
this.values = sanitiseShortcutBindings(initial, defaultShortcutBindings());
|
|
21
|
+
this.list = shortcutsFor(this.values);
|
|
22
|
+
}
|
|
23
|
+
/** Each configurable action's key combination, by ID (read-only copy) */
|
|
24
|
+
get bindings() {
|
|
25
|
+
return this.values;
|
|
26
|
+
}
|
|
27
|
+
/** The shortcuts key presses are matched against (except the hold-to-pan key) */
|
|
28
|
+
get shortcuts() {
|
|
29
|
+
return this.list;
|
|
30
|
+
}
|
|
31
|
+
/** The key held to pan, returning to the last tool when released (null when it has none) */
|
|
32
|
+
get holdToPan() {
|
|
33
|
+
return this.values["pan"] ?? null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A configurable action's key combination (null when it has none or there's no such action).
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* shortcuts.combo("deselect"); // { key: "d", cmd: true }
|
|
40
|
+
*/
|
|
41
|
+
combo(id) {
|
|
42
|
+
return this.values[id] ?? null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Set or remove (null) a configurable action's key combination. If another configurable action has it, that action
|
|
46
|
+
* loses it (`movedFrom`). Nothing changes for a key combination a fixed action uses (`fixed`), or an unknown action.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* shortcuts.set("tool-brush", { key: "e" }); // { changed: true, movedFrom: "tool-eraser" }
|
|
50
|
+
* shortcuts.set("deselect", { key: "z", cmd: true }); // { changed: false, fixed: "undo" }
|
|
51
|
+
*/
|
|
52
|
+
set(id, combo) {
|
|
53
|
+
if (!CONFIGURABLE_SHORTCUTS.some((definition) => definition.id === id)) return { changed: false };
|
|
54
|
+
const next = combo && normaliseCombo(combo);
|
|
55
|
+
const current = this.values[id] ?? null;
|
|
56
|
+
if (next ? current && sameCombo(current, next) : !current) return { changed: false };
|
|
57
|
+
if (!next) {
|
|
58
|
+
this.apply({
|
|
59
|
+
...this.values,
|
|
60
|
+
[id]: null
|
|
61
|
+
});
|
|
62
|
+
return { changed: true };
|
|
63
|
+
}
|
|
64
|
+
const fixed = fixedShortcutFor(next);
|
|
65
|
+
if (fixed) return {
|
|
66
|
+
changed: false,
|
|
67
|
+
fixed: fixed.id
|
|
68
|
+
};
|
|
69
|
+
const movedFrom = Object.keys(this.values).find((other) => {
|
|
70
|
+
const otherCombo = this.values[other];
|
|
71
|
+
return other !== id && otherCombo && sameCombo(otherCombo, next);
|
|
72
|
+
});
|
|
73
|
+
this.apply({
|
|
74
|
+
...this.values,
|
|
75
|
+
...movedFrom && { [movedFrom]: null },
|
|
76
|
+
[id]: next
|
|
77
|
+
});
|
|
78
|
+
return movedFrom ? {
|
|
79
|
+
changed: true,
|
|
80
|
+
movedFrom
|
|
81
|
+
} : { changed: true };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Go back to the default key combinations.
|
|
85
|
+
*/
|
|
86
|
+
reset() {
|
|
87
|
+
this.load(defaultShortcutBindings());
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Replace the key combinations, e.g. with saved or imported ones (see `sanitiseShortcutBindings`; actions left out
|
|
91
|
+
* get their defaults), and dispatch `change` if anything changed.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* shortcuts.load({ "tool-brush": { key: "p" } });
|
|
95
|
+
*/
|
|
96
|
+
load(input) {
|
|
97
|
+
const next = sanitiseShortcutBindings(input, defaultShortcutBindings());
|
|
98
|
+
if (Object.keys(next).some((id) => {
|
|
99
|
+
const [a, b] = [next[id], this.values[id]];
|
|
100
|
+
return a && b ? !sameCombo(a, b) : a !== b;
|
|
101
|
+
})) this.apply(next);
|
|
102
|
+
}
|
|
103
|
+
/** Use new key combinations and tell listeners */
|
|
104
|
+
apply(bindings) {
|
|
105
|
+
this.values = bindings;
|
|
106
|
+
this.list = shortcutsFor(bindings);
|
|
107
|
+
this.dispatchEvent(new Event("change"));
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
export { ShortcutSettings as default };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** The themes: light, dark, or following the device's (system) */
|
|
2
|
+
export declare const THEMES: readonly ["light", "dark", "system"];
|
|
3
|
+
export type Theme = (typeof THEMES)[number];
|
|
4
|
+
/** Longest locale tag kept in UI settings (characters) */
|
|
5
|
+
export declare const MAX_LOCALE_LENGTH = 35;
|
|
6
|
+
/** A locale tag's characters: letters, digits and hyphens (e.g. `zh-Hant`) */
|
|
7
|
+
export declare const LOCALE_PATTERN = "^[A-Za-z0-9-]+$";
|
|
8
|
+
/**
|
|
9
|
+
* Whether a value is one of the themes.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* isTheme("dark"); // true
|
|
13
|
+
* isTheme(undefined); // false
|
|
14
|
+
*/
|
|
15
|
+
export declare const isTheme: (value: unknown) => value is Theme;
|
|
16
|
+
/**
|
|
17
|
+
* Whether a value can be a locale tag (letters, digits and hyphens, at most `MAX_LOCALE_LENGTH` characters).
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* isLocaleTag("zh-Hant"); // true
|
|
21
|
+
* isLocaleTag("<script>"); // false
|
|
22
|
+
*/
|
|
23
|
+
export declare const isLocaleTag: (value: unknown) => value is string;
|
|
24
|
+
/** The user's interface choices */
|
|
25
|
+
export interface UiSettingsData {
|
|
26
|
+
/** The theme (`system` by default) */
|
|
27
|
+
theme: Theme;
|
|
28
|
+
/** The language's locale tag, e.g. `fr`, or undefined to follow the device's languages */
|
|
29
|
+
locale?: string;
|
|
30
|
+
}
|
|
31
|
+
/** UI settings the UI can show and change: PaintCanvas's `UiSettings`, or anything with the same data and `update` */
|
|
32
|
+
export interface UiSettingsLike extends EventTarget {
|
|
33
|
+
readonly data: Readonly<UiSettingsData>;
|
|
34
|
+
update(changes: Partial<UiSettingsData>): void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Keep only valid UI settings from an untrusted value (e.g. stored or imported), using `defaults` for anything missing
|
|
38
|
+
* or invalid. Other properties are left out.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* sanitiseUiSettings({ theme: "dark", locale: 42 }, { theme: "system" }); // { theme: "dark" }
|
|
42
|
+
*/
|
|
43
|
+
export declare const sanitiseUiSettings: (input: unknown, defaults: UiSettingsData) => UiSettingsData;
|
|
44
|
+
/**
|
|
45
|
+
* The user's interface choices: the theme and the language. Kept in memory; the implementer saves and restores them
|
|
46
|
+
* with the other app settings (see `SettingsAutosave`), and the UI shows and applies them (e.g. `<pc-theme-picker>` and
|
|
47
|
+
* `<pc-input-settings>`).
|
|
48
|
+
*
|
|
49
|
+
* Dispatches a `change` event when a setting changes.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* const ui = new UiSettings();
|
|
53
|
+
* ui.update({ theme: "dark", locale: "fr" });
|
|
54
|
+
* ui.data.theme; // "dark"
|
|
55
|
+
*/
|
|
56
|
+
export default class UiSettings extends EventTarget {
|
|
57
|
+
private values;
|
|
58
|
+
constructor(initial?: unknown);
|
|
59
|
+
/** Current settings (read-only copy) */
|
|
60
|
+
get data(): Readonly<UiSettingsData>;
|
|
61
|
+
/**
|
|
62
|
+
* Change some settings and dispatch `change` if anything changed. Invalid values and other properties are ignored.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ui.update({ locale: "ja" });
|
|
66
|
+
*/
|
|
67
|
+
update(changes: Partial<UiSettingsData>): void;
|
|
68
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// src/lib/classes/UiSettings/index.ts
|
|
2
|
+
/** The themes: light, dark, or following the device's (system) */
|
|
3
|
+
var THEMES = [
|
|
4
|
+
"light",
|
|
5
|
+
"dark",
|
|
6
|
+
"system"
|
|
7
|
+
];
|
|
8
|
+
/** Longest locale tag kept in UI settings (characters) */
|
|
9
|
+
var MAX_LOCALE_LENGTH = 35;
|
|
10
|
+
/** A locale tag's characters: letters, digits and hyphens (e.g. `zh-Hant`) */
|
|
11
|
+
var LOCALE_PATTERN = "^[A-Za-z0-9-]+$";
|
|
12
|
+
/**
|
|
13
|
+
* Whether a value is one of the themes.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* isTheme("dark"); // true
|
|
17
|
+
* isTheme(undefined); // false
|
|
18
|
+
*/
|
|
19
|
+
var isTheme = (value) => THEMES.includes(value);
|
|
20
|
+
/**
|
|
21
|
+
* Whether a value can be a locale tag (letters, digits and hyphens, at most `MAX_LOCALE_LENGTH` characters).
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* isLocaleTag("zh-Hant"); // true
|
|
25
|
+
* isLocaleTag("<script>"); // false
|
|
26
|
+
*/
|
|
27
|
+
var isLocaleTag = (value) => typeof value === "string" && value.length <= 35 && (/* @__PURE__ */ new RegExp("^[A-Za-z0-9-]+$")).test(value);
|
|
28
|
+
/**
|
|
29
|
+
* Keep only valid UI settings from an untrusted value (e.g. stored or imported), using `defaults` for anything missing
|
|
30
|
+
* or invalid. Other properties are left out.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* sanitiseUiSettings({ theme: "dark", locale: 42 }, { theme: "system" }); // { theme: "dark" }
|
|
34
|
+
*/
|
|
35
|
+
var sanitiseUiSettings = (input, defaults) => {
|
|
36
|
+
const value = typeof input === "object" && input !== null ? input : {};
|
|
37
|
+
const theme = isTheme(value.theme) ? value.theme : defaults.theme;
|
|
38
|
+
const locale = isLocaleTag(value.locale) ? value.locale : defaults.locale;
|
|
39
|
+
return locale === void 0 ? { theme } : {
|
|
40
|
+
theme,
|
|
41
|
+
locale
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* The user's interface choices: the theme and the language. Kept in memory; the implementer saves and restores them
|
|
46
|
+
* with the other app settings (see `SettingsAutosave`), and the UI shows and applies them (e.g. `<pc-theme-picker>` and
|
|
47
|
+
* `<pc-input-settings>`).
|
|
48
|
+
*
|
|
49
|
+
* Dispatches a `change` event when a setting changes.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* const ui = new UiSettings();
|
|
53
|
+
* ui.update({ theme: "dark", locale: "fr" });
|
|
54
|
+
* ui.data.theme; // "dark"
|
|
55
|
+
*/
|
|
56
|
+
var UiSettings = class extends EventTarget {
|
|
57
|
+
values;
|
|
58
|
+
constructor(initial) {
|
|
59
|
+
super();
|
|
60
|
+
this.values = sanitiseUiSettings(initial, { theme: "system" });
|
|
61
|
+
}
|
|
62
|
+
/** Current settings (read-only copy) */
|
|
63
|
+
get data() {
|
|
64
|
+
return this.values;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Change some settings and dispatch `change` if anything changed. Invalid values and other properties are ignored.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ui.update({ locale: "ja" });
|
|
71
|
+
*/
|
|
72
|
+
update(changes) {
|
|
73
|
+
const next = sanitiseUiSettings({
|
|
74
|
+
...this.values,
|
|
75
|
+
...changes
|
|
76
|
+
}, this.values);
|
|
77
|
+
if (next.theme === this.values.theme && next.locale === this.values.locale) return;
|
|
78
|
+
this.values = next;
|
|
79
|
+
this.dispatchEvent(new Event("change"));
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
export { LOCALE_PATTERN, MAX_LOCALE_LENGTH, THEMES, UiSettings as default, isLocaleTag, isTheme, sanitiseUiSettings };
|