@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.
- package/README.md +2 -2
- package/dist/lib/brushes/configRanges/index.d.ts +17 -0
- package/dist/lib/brushes/configRanges/index.js +31 -1
- package/dist/lib/brushes/params/index.d.ts +2 -1
- package/dist/lib/brushes/params/index.js +7 -4
- package/dist/lib/brushes/types/index.d.ts +8 -0
- package/dist/lib/classes/Layer/index.d.ts +10 -0
- package/dist/lib/classes/Layer/index.js +15 -1
- package/dist/lib/classes/LayerAutosave/index.d.ts +9 -15
- package/dist/lib/classes/LayerAutosave/index.js +30 -43
- package/dist/lib/classes/LayerStack/index.d.ts +15 -0
- package/dist/lib/classes/LayerStack/index.js +26 -2
- package/dist/lib/classes/PaintCanvas/index.d.ts +36 -5
- package/dist/lib/classes/PaintCanvas/index.js +45 -14
- package/dist/lib/classes/PngEncoder/index.d.ts +41 -0
- package/dist/lib/classes/PngEncoder/index.js +104 -0
- package/dist/lib/classes/ProfileGraphs/index.d.ts +87 -0
- package/dist/lib/classes/ProfileGraphs/index.js +148 -0
- package/dist/lib/classes/SettingsAutosave/index.d.ts +1 -1
- package/dist/lib/classes/SettingsAutosave/index.js +2 -1
- package/dist/lib/classes/UiSettings/index.d.ts +24 -2
- package/dist/lib/classes/UiSettings/index.js +32 -7
- package/dist/lib/messages/schema.d.ts +720 -0
- package/dist/lib/presets/schema.d.ts +360 -0
- package/dist/lib/presets/schema.js +8 -1
- package/dist/lib/profiles/index.d.ts +61 -0
- package/dist/lib/profiles/index.js +131 -0
- package/dist/lib/profiles/schema.d.ts +73 -0
- package/dist/lib/profiles/schema.js +53 -0
- package/dist/lib/rendering/sample/index.d.ts +11 -0
- package/dist/lib/rendering/sample/index.js +36 -5
- package/dist/lib/settings/index.d.ts +2 -1
- package/dist/lib/settings/index.js +4 -1
- package/dist/lib/settings/schema.d.ts +53 -1
- package/dist/lib/settings/schema.js +9 -2
- package/dist/lib/storage/index.d.ts +26 -0
- package/dist/lib/storage/index.js +38 -1
- package/dist/lib/validation/validators.generated.js +13061 -3675
- package/dist/lib/version/index.d.ts +25 -0
- package/dist/lib/version/index.js +35 -0
- package/dist/locales/en-GB.js +35 -0
- package/dist/locales/en-US.js +35 -0
- package/dist/locales/fr.js +35 -0
- package/dist/locales/ja.js +35 -0
- package/dist/locales/ko.js +35 -0
- package/dist/locales/zh-Hans.js +35 -0
- package/dist/locales/zh-Hant.js +35 -0
- package/dist/package.js +1 -1
- package/dist/ui/ask/index.d.ts +48 -0
- package/dist/ui/ask/index.js +67 -0
- package/dist/ui/base/index.d.ts +8 -1
- package/dist/ui/base/index.js +19 -4
- package/dist/ui/elements/PaintCanvas/index.d.ts +13 -3
- package/dist/ui/elements/PaintCanvas/index.js +25 -6
- package/dist/ui/elements/PaintCanvas/style.js +1 -1
- package/dist/ui/elements/PcAppSettings/index.d.ts +15 -7
- package/dist/ui/elements/PcAppSettings/index.js +53 -62
- package/dist/ui/elements/PcBrushConfig/index.d.ts +26 -14
- package/dist/ui/elements/PcBrushConfig/index.js +214 -145
- package/dist/ui/elements/PcBrushConfig/style.js +1 -1
- package/dist/ui/elements/PcBrushTest/index.d.ts +1 -0
- package/dist/ui/elements/PcBrushTest/index.js +15 -9
- package/dist/ui/elements/PcBrushTest/style.js +1 -1
- package/dist/ui/elements/PcButton/index.d.ts +12 -3
- package/dist/ui/elements/PcButton/index.js +18 -10
- package/dist/ui/elements/PcButtonMenu/index.d.ts +88 -0
- package/dist/ui/elements/PcButtonMenu/index.js +125 -0
- package/dist/ui/elements/PcCanvas/index.d.ts +15 -1
- package/dist/ui/elements/PcCanvas/index.js +42 -19
- package/dist/ui/elements/PcCanvas/style.js +1 -1
- package/dist/ui/elements/PcClipboardTools/index.d.ts +11 -7
- package/dist/ui/elements/PcClipboardTools/index.js +30 -29
- package/dist/ui/elements/PcDialog/index.d.ts +46 -10
- package/dist/ui/elements/PcDialog/index.js +102 -51
- package/dist/ui/elements/PcDialogWindow/index.d.ts +141 -0
- package/dist/ui/elements/PcDialogWindow/index.js +385 -0
- package/dist/ui/elements/PcInputSettings/index.d.ts +2 -1
- package/dist/ui/elements/PcInputSettings/index.js +9 -15
- package/dist/ui/elements/PcLayerItem/index.d.ts +21 -6
- package/dist/ui/elements/PcLayerItem/index.js +53 -26
- package/dist/ui/elements/PcLayerItem/style.js +1 -1
- package/dist/ui/elements/PcPanelBrushes/index.d.ts +3 -2
- package/dist/ui/elements/PcPanelBrushes/index.js +7 -5
- package/dist/ui/elements/PcPanelBrushes/style.js +1 -1
- package/dist/ui/elements/PcPanelColours/index.js +2 -2
- package/dist/ui/elements/PcPanelColours/style.js +1 -1
- package/dist/ui/elements/PcPanelLayers/index.js +1 -1
- package/dist/ui/elements/PcPanelLayers/style.js +1 -1
- package/dist/ui/elements/PcProfileEditor/index.d.ts +103 -0
- package/dist/ui/elements/PcProfileEditor/index.js +259 -0
- package/dist/ui/elements/PcProfileGraph/index.d.ts +84 -0
- package/dist/ui/elements/PcProfileGraph/index.js +317 -0
- package/dist/ui/elements/PcShortcutSettings/index.js +9 -13
- package/dist/ui/elements/PcSliderRange/index.d.ts +147 -0
- package/dist/ui/elements/PcSliderRange/index.js +308 -0
- package/dist/ui/elements/PcStats/index.d.ts +19 -4
- package/dist/ui/elements/PcStats/index.js +29 -13
- package/dist/ui/elements/PcToggleCheckbox/index.d.ts +13 -2
- package/dist/ui/elements/PcToggleCheckbox/index.js +27 -8
- package/dist/ui/elements/PcToolSettings/index.d.ts +1 -0
- package/dist/ui/elements/PcToolSettings/index.js +14 -14
- package/dist/ui/elements/PcVersion/index.d.ts +12 -0
- package/dist/ui/elements/PcVersion/index.js +43 -0
- package/dist/ui/icons/images/icon-profile-graph@2x.js +3 -0
- package/dist/ui/icons/index.d.ts +1 -1
- package/dist/ui/icons/index.js +2 -0
- package/dist/ui/index.d.ts +9 -2
- package/dist/ui/index.js +9 -2
- package/dist/ui/layout/index.d.ts +2 -2
- package/dist/ui/layout/index.js +2 -2
- package/dist/ui/styles/index.d.ts +21 -1
- package/dist/ui/styles/index.js +73 -14
- package/locales/en-GB.json +35 -0
- package/locales/en-US.json +35 -0
- package/locales/fr.json +35 -0
- package/locales/ja.json +35 -0
- package/locales/ko.json +35 -0
- package/locales/zh-Hans.json +35 -0
- package/locales/zh-Hant.json +35 -0
- package/package.json +25 -23
|
@@ -11,7 +11,7 @@ import Selection from "../../selection/Selection/index.js";
|
|
|
11
11
|
import Lasso from "../../selection/Lasso/index.js";
|
|
12
12
|
import Transformer, { applyMask, contentBounds, copyArea, drawTransformed } from "../../transform/Transformer/index.js";
|
|
13
13
|
import { boxBounds } from "../../transform/geometry/index.js";
|
|
14
|
-
import Layer, { createOffscreenCanvas } from "../Layer/index.js";
|
|
14
|
+
import Layer, { createMemoryCanvas, createOffscreenCanvas } from "../Layer/index.js";
|
|
15
15
|
import LayerAutosave from "../LayerAutosave/index.js";
|
|
16
16
|
import { brushPresets, eraserPresets } from "../../brushes/index.js";
|
|
17
17
|
import { BUILT_IN_AUTHOR, UNKNOWN_AUTHOR, authorHandle } from "../../brushes/authors/index.js";
|
|
@@ -19,10 +19,11 @@ import "../../presets/index.js";
|
|
|
19
19
|
import CustomPresets from "../CustomPresets/index.js";
|
|
20
20
|
import { BRUSH_PRESET_FILE_FORMAT } from "../../presets/schema.js";
|
|
21
21
|
import InputSettings from "../InputSettings/index.js";
|
|
22
|
-
import UiSettings from "../UiSettings/index.js";
|
|
22
|
+
import UiSettings, { DEFAULT_UI_SETTINGS } from "../UiSettings/index.js";
|
|
23
23
|
import Palette from "../Palette/index.js";
|
|
24
|
-
import ShortcutSettings from "../ShortcutSettings/index.js";
|
|
25
24
|
import { PALETTE_FILE_FORMAT } from "../../palette/schema.js";
|
|
25
|
+
import ProfileGraphs from "../ProfileGraphs/index.js";
|
|
26
|
+
import ShortcutSettings from "../ShortcutSettings/index.js";
|
|
26
27
|
import "../../palette/index.js";
|
|
27
28
|
import { SETTINGS_FILE_FORMAT } from "../../settings/schema.js";
|
|
28
29
|
import ExternalSheets from "../ExternalSheets/index.js";
|
|
@@ -126,10 +127,12 @@ var PaintCanvas = class extends EventTarget {
|
|
|
126
127
|
background = new BackgroundWork(this.strokeLog);
|
|
127
128
|
/** How the user's input devices control the canvas (scrolling, pinching, touch gestures, focus pen mode) */
|
|
128
129
|
input = new InputSettings();
|
|
129
|
-
/** The user's interface choices (theme and
|
|
130
|
-
ui
|
|
130
|
+
/** The user's interface choices (theme, language and where canvases draw), kept with the other app settings */
|
|
131
|
+
ui;
|
|
131
132
|
/** The user's saved colours, shown as swatches */
|
|
132
133
|
palette = new Palette();
|
|
134
|
+
/** The user's own profile graphs (and the presets), for shaping how brush settings vary */
|
|
135
|
+
profiles = new ProfileGraphs();
|
|
133
136
|
/** The user's keyboard shortcuts: each configurable action's key combination */
|
|
134
137
|
shortcuts = new ShortcutSettings();
|
|
135
138
|
/** The user's pointer position over the canvas, in canvas pixels (undefined when off the canvas) */
|
|
@@ -209,6 +212,8 @@ var PaintCanvas = class extends EventTarget {
|
|
|
209
212
|
/** The layer stack whose changes are being sent */
|
|
210
213
|
sendingLayers;
|
|
211
214
|
onLayersChangeToSend = () => this.sender?.schedule();
|
|
215
|
+
/** Where the canvases draw, chosen when the canvas started (the user's choice for next time is `ui.data.rendering`) */
|
|
216
|
+
rendering;
|
|
212
217
|
/** Creates the canvases the app draws on (fake ones in tests) */
|
|
213
218
|
createCanvas;
|
|
214
219
|
/** Who the custom presets belong to (passed to the preset store) */
|
|
@@ -239,7 +244,9 @@ var PaintCanvas = class extends EventTarget {
|
|
|
239
244
|
this.customPresets = new CustomPresets(this.ownerId, options.presetStore);
|
|
240
245
|
this.customPresets.addEventListener("change", () => this.listCustomPresets());
|
|
241
246
|
this.customPresets.load().catch(() => void 0);
|
|
242
|
-
this.
|
|
247
|
+
this.rendering = options.rendering ?? DEFAULT_UI_SETTINGS.rendering;
|
|
248
|
+
this.ui = new UiSettings({ rendering: this.rendering });
|
|
249
|
+
this.createCanvas = options.createCanvas ?? (this.rendering === "gpu" ? createOffscreenCanvas : createMemoryCanvas);
|
|
243
250
|
this.systemClipboard = options.systemClipboard === null ? void 0 : options.systemClipboard ?? globalThis.navigator?.clipboard;
|
|
244
251
|
this.canvasWidth = DEFAULT_CANVAS_SIZE;
|
|
245
252
|
this.canvasHeight = DEFAULT_CANVAS_SIZE;
|
|
@@ -715,11 +722,31 @@ var PaintCanvas = class extends EventTarget {
|
|
|
715
722
|
* nothing during a stroke or while a transform is pending. Returns the area copied, if any.
|
|
716
723
|
*/
|
|
717
724
|
copy() {
|
|
718
|
-
|
|
725
|
+
return this.copyMasked(this.layerStack.active.canvas);
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Copy the masked image data of all the shown layers, mixed as they're shown (each layer's opacity and blend mode,
|
|
729
|
+
* without the white background), to the clipboard. Hidden layers are left out. Like `copy`, it needs a mask with image
|
|
730
|
+
* data inside it, and does nothing during a stroke or while a transform is pending. Returns the area copied, if any.
|
|
731
|
+
*
|
|
732
|
+
* @example
|
|
733
|
+
* app.selection.selectAll();
|
|
734
|
+
* app.copyMerged(); // { x: 10, y: 20, width: 30, height: 5 }: the part of the canvas with paint on any shown layer
|
|
735
|
+
*/
|
|
736
|
+
copyMerged() {
|
|
737
|
+
if (!this.canCopy) return void 0;
|
|
738
|
+
const merged = this.createCanvas(this.width, this.height);
|
|
739
|
+
drawLayers(merged.getContext("2d"), this.layerStack.layers);
|
|
740
|
+
return this.copyMasked(merged);
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Copy the masked part of a canvas-sized image to the clipboard (see `copy`), returning the area copied, if any.
|
|
744
|
+
*/
|
|
745
|
+
copyMasked(source) {
|
|
746
|
+
const area = this.maskedArea(source);
|
|
719
747
|
const mask = this.currentSelection.mask;
|
|
720
748
|
if (!area || !mask) return void 0;
|
|
721
|
-
const
|
|
722
|
-
const image = copyArea(this.createCanvas, layer.canvas, area);
|
|
749
|
+
const image = copyArea(this.createCanvas, source, area);
|
|
723
750
|
applyMask(image, copyArea(this.createCanvas, mask, area));
|
|
724
751
|
this.clipboard = {
|
|
725
752
|
image,
|
|
@@ -807,15 +834,19 @@ var PaintCanvas = class extends EventTarget {
|
|
|
807
834
|
height
|
|
808
835
|
};
|
|
809
836
|
}
|
|
837
|
+
/** Whether image data can be copied now: not during a stroke or while a transform is pending */
|
|
838
|
+
get canCopy() {
|
|
839
|
+
return !this.strokeEngine.drawing && !this.transformer.pending;
|
|
840
|
+
}
|
|
810
841
|
/**
|
|
811
|
-
* The bounds of the selected layer's
|
|
812
|
-
* during a stroke or while a transform is pending.
|
|
842
|
+
* The bounds of a canvas-sized image's data (the selected layer's by default) inside the mask, if there is a mask
|
|
843
|
+
* with image data inside it. None during a stroke or while a transform is pending.
|
|
813
844
|
*/
|
|
814
|
-
maskedArea() {
|
|
845
|
+
maskedArea(source = this.layerStack.active.canvas) {
|
|
815
846
|
const mask = this.currentSelection.mask;
|
|
816
|
-
if (!mask || this.
|
|
847
|
+
if (!mask || !this.canCopy) return void 0;
|
|
817
848
|
const { width, height } = this;
|
|
818
|
-
return contentBounds(
|
|
849
|
+
return contentBounds(source.getContext("2d").getImageData(0, 0, width, height).data, width, height, mask.getContext("2d").getImageData(0, 0, width, height).data);
|
|
819
850
|
}
|
|
820
851
|
/**
|
|
821
852
|
* Erase the mask's part of an area from the selected layer, or paint it with the background colour when the layer's
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode a canvas as a PNG on the page's own thread (which blocks the page while it runs: seconds on phones for a large
|
|
3
|
+
* canvas).
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* const png = await encodePngHere(layer.canvas);
|
|
7
|
+
*/
|
|
8
|
+
export declare const encodePngHere: (canvas: OffscreenCanvas) => Promise<Blob>;
|
|
9
|
+
/** The worker's code: draws the image it's sent onto its own canvas and encodes that as a PNG */
|
|
10
|
+
export declare const PNG_WORKER_SOURCE = "self.onmessage = async (event) => {\n const { id, bitmap } = event.data;\n try {\n const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);\n canvas.getContext(\"2d\").drawImage(bitmap, 0, 0);\n bitmap.close();\n self.postMessage({ id, blob: await canvas.convertToBlob({ type: \"image/png\" }) });\n } catch (error) {\n self.postMessage({ id, error: String(error) });\n }\n};";
|
|
11
|
+
/** Makes the worker, or gives null where workers can't be used */
|
|
12
|
+
export type WorkerFactory = () => Worker | null;
|
|
13
|
+
/**
|
|
14
|
+
* Make the PNG worker from its code (no separate file, so any bundler works), or null where there are no workers or
|
|
15
|
+
* `createImageBitmap`, or the page doesn't allow workers from `blob:` addresses.
|
|
16
|
+
*/
|
|
17
|
+
export declare const createPngWorker: WorkerFactory;
|
|
18
|
+
/**
|
|
19
|
+
* Encodes canvases as PNGs in a background worker, so the page keeps responding while large layers are saved. The
|
|
20
|
+
* canvas is copied (`createImageBitmap`) and sent to the worker, which encodes the copy. Where a worker can't be used,
|
|
21
|
+
* or it fails, it encodes on the page's own thread instead (see `encodePngHere`).
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const encoder = new PngEncoder();
|
|
25
|
+
* const png = await encoder.encode(layer.canvas);
|
|
26
|
+
*/
|
|
27
|
+
export default class PngEncoder {
|
|
28
|
+
private readonly createWorker;
|
|
29
|
+
/** The worker, once made (null when it can't be used) */
|
|
30
|
+
private worker?;
|
|
31
|
+
private nextId;
|
|
32
|
+
/** Encodings waiting for the worker, by ID */
|
|
33
|
+
private readonly waiting;
|
|
34
|
+
constructor(createWorker?: WorkerFactory);
|
|
35
|
+
/**
|
|
36
|
+
* Encode a canvas as a PNG, in the worker where it can be used.
|
|
37
|
+
*/
|
|
38
|
+
encode: (canvas: OffscreenCanvas) => Promise<Blob>;
|
|
39
|
+
/** The worker, made on first use */
|
|
40
|
+
private getWorker;
|
|
41
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// src/lib/classes/PngEncoder/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* Encode a canvas as a PNG on the page's own thread (which blocks the page while it runs: seconds on phones for a large
|
|
4
|
+
* canvas).
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const png = await encodePngHere(layer.canvas);
|
|
8
|
+
*/
|
|
9
|
+
var encodePngHere = (canvas) => {
|
|
10
|
+
canvas.getContext("2d");
|
|
11
|
+
return canvas.convertToBlob({ type: "image/png" });
|
|
12
|
+
};
|
|
13
|
+
/** The worker's code: draws the image it's sent onto its own canvas and encodes that as a PNG */
|
|
14
|
+
var PNG_WORKER_SOURCE = `self.onmessage = async (event) => {
|
|
15
|
+
const { id, bitmap } = event.data;
|
|
16
|
+
try {
|
|
17
|
+
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
|
|
18
|
+
canvas.getContext("2d").drawImage(bitmap, 0, 0);
|
|
19
|
+
bitmap.close();
|
|
20
|
+
self.postMessage({ id, blob: await canvas.convertToBlob({ type: "image/png" }) });
|
|
21
|
+
} catch (error) {
|
|
22
|
+
self.postMessage({ id, error: String(error) });
|
|
23
|
+
}
|
|
24
|
+
};`;
|
|
25
|
+
/**
|
|
26
|
+
* Make the PNG worker from its code (no separate file, so any bundler works), or null where there are no workers or
|
|
27
|
+
* `createImageBitmap`, or the page doesn't allow workers from `blob:` addresses.
|
|
28
|
+
*/
|
|
29
|
+
var createPngWorker = () => {
|
|
30
|
+
if (typeof Worker === "undefined" || typeof createImageBitmap === "undefined") return null;
|
|
31
|
+
try {
|
|
32
|
+
const url = URL.createObjectURL(new Blob([PNG_WORKER_SOURCE], { type: "text/javascript" }));
|
|
33
|
+
return new Worker(url);
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Encodes canvases as PNGs in a background worker, so the page keeps responding while large layers are saved. The
|
|
40
|
+
* canvas is copied (`createImageBitmap`) and sent to the worker, which encodes the copy. Where a worker can't be used,
|
|
41
|
+
* or it fails, it encodes on the page's own thread instead (see `encodePngHere`).
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const encoder = new PngEncoder();
|
|
45
|
+
* const png = await encoder.encode(layer.canvas);
|
|
46
|
+
*/
|
|
47
|
+
var PngEncoder = class {
|
|
48
|
+
createWorker;
|
|
49
|
+
/** The worker, once made (null when it can't be used) */
|
|
50
|
+
worker;
|
|
51
|
+
nextId = 0;
|
|
52
|
+
/** Encodings waiting for the worker, by ID */
|
|
53
|
+
waiting = /* @__PURE__ */ new Map();
|
|
54
|
+
constructor(createWorker = createPngWorker) {
|
|
55
|
+
this.createWorker = createWorker;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Encode a canvas as a PNG, in the worker where it can be used.
|
|
59
|
+
*/
|
|
60
|
+
encode = async (canvas) => {
|
|
61
|
+
const worker = this.getWorker();
|
|
62
|
+
if (!worker) return encodePngHere(canvas);
|
|
63
|
+
try {
|
|
64
|
+
canvas.getContext("2d");
|
|
65
|
+
const bitmap = await createImageBitmap(canvas);
|
|
66
|
+
const id = this.nextId++;
|
|
67
|
+
return await new Promise((resolve, reject) => {
|
|
68
|
+
this.waiting.set(id, {
|
|
69
|
+
resolve,
|
|
70
|
+
reject
|
|
71
|
+
});
|
|
72
|
+
worker.postMessage({
|
|
73
|
+
id,
|
|
74
|
+
bitmap
|
|
75
|
+
}, [bitmap]);
|
|
76
|
+
});
|
|
77
|
+
} catch {
|
|
78
|
+
return encodePngHere(canvas);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
/** The worker, made on first use */
|
|
82
|
+
getWorker() {
|
|
83
|
+
if (this.worker !== void 0) return this.worker;
|
|
84
|
+
const worker = this.createWorker();
|
|
85
|
+
this.worker = worker;
|
|
86
|
+
if (!worker) return null;
|
|
87
|
+
worker.addEventListener("message", (event) => {
|
|
88
|
+
const { id, blob, error } = event.data;
|
|
89
|
+
const waiting = this.waiting.get(id);
|
|
90
|
+
if (!waiting) return;
|
|
91
|
+
this.waiting.delete(id);
|
|
92
|
+
if (blob) waiting.resolve(blob);
|
|
93
|
+
else waiting.reject(new Error(error));
|
|
94
|
+
});
|
|
95
|
+
worker.addEventListener("error", () => {
|
|
96
|
+
worker.terminate();
|
|
97
|
+
this.worker = null;
|
|
98
|
+
for (const { reject } of this.waiting.values()) reject(/* @__PURE__ */ new Error("PNG worker failed"));
|
|
99
|
+
this.waiting.clear();
|
|
100
|
+
});
|
|
101
|
+
return worker;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
export { PNG_WORKER_SOURCE, createPngWorker, PngEncoder as default, encodePngHere };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type ProfileGraph, type ProfilePoint } from "../../profiles";
|
|
2
|
+
/**
|
|
3
|
+
* Keep only valid points from an untrusted value, in order from left to right: each on the grid, with a known
|
|
4
|
+
* interpolation. Undefined if there aren't 2 to `MAX_PROFILE_POINTS` of them.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* sanitisePoints([{ x: 10, y: 0, i: "linear" }, { x: 0, y: -10, i: "linear" }]); // sorted by x
|
|
8
|
+
*/
|
|
9
|
+
export declare const sanitisePoints: (input: unknown) => ProfilePoint[] | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Keep only valid profile graphs from an untrusted value (e.g. stored or imported): each with an ID and name and valid
|
|
12
|
+
* points (see `sanitisePoints`), each ID once and none of the presets', up to `MAX_SAVED_PROFILES`. Undefined if the
|
|
13
|
+
* value isn't an array.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* sanitiseProfiles([{ id: "a", name: "Soft", points: [...] }, { id: "a", name: "Again", points: [...] }]); // the first
|
|
17
|
+
*/
|
|
18
|
+
export declare const sanitiseProfiles: (input: unknown) => ProfileGraph[] | undefined;
|
|
19
|
+
/** What a profile graph is saved from: its name and points, and its ID when replacing one */
|
|
20
|
+
export interface ProfileGraphDraft {
|
|
21
|
+
id?: string;
|
|
22
|
+
name: string;
|
|
23
|
+
points: readonly ProfilePoint[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The user's own profile graphs, alongside the ones PaintCanvas comes with (`PROFILE_PRESETS`). Kept in memory; the app
|
|
27
|
+
* settings save and restore them (see `set` and the `change` event). Each gets a UUID when it's first saved.
|
|
28
|
+
*
|
|
29
|
+
* Dispatches a `change` event when the user's graphs change.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const profiles = new ProfileGraphs();
|
|
33
|
+
* const soft = profiles.save({ name: "Soft", points: PROFILE_PRESETS[1].points });
|
|
34
|
+
* profiles.get(soft.id)?.name; // "Soft"
|
|
35
|
+
*/
|
|
36
|
+
export default class ProfileGraphs extends EventTarget {
|
|
37
|
+
private values;
|
|
38
|
+
/** The user's own graphs, in the order they were first saved */
|
|
39
|
+
get graphs(): readonly ProfileGraph[];
|
|
40
|
+
/** Every graph: the presets, then the user's own */
|
|
41
|
+
get all(): readonly ProfileGraph[];
|
|
42
|
+
/**
|
|
43
|
+
* A graph by its ID: a preset or one of the user's own.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* profiles.get("overweight")?.name; // "Overweight"
|
|
47
|
+
*/
|
|
48
|
+
get(id: string | undefined): ProfileGraph | undefined;
|
|
49
|
+
/** Whether a graph is one PaintCanvas comes with (so it can't be renamed, replaced or deleted) */
|
|
50
|
+
isPreset(id: string | undefined): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* One of the user's own graphs with a name, ignoring case and spaces around it.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* profiles.findByName(" soft "); // the graph called "Soft"
|
|
56
|
+
*/
|
|
57
|
+
findByName(name: string): ProfileGraph | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Save a graph as one of the user's own: as a new one (with a new UUID), or in place of the one with its ID. Returns
|
|
60
|
+
* the saved graph, or undefined if it can't be saved (no name, invalid points, a preset's ID, or no room).
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* profiles.save({ name: "Soft", points }); // { id: "1b9d…", name: "Soft", points }
|
|
64
|
+
*/
|
|
65
|
+
save(draft: ProfileGraphDraft): ProfileGraph | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Rename one of the user's own graphs. Returns whether it was renamed.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* profiles.rename(id, "Softer");
|
|
71
|
+
*/
|
|
72
|
+
rename(id: string, name: string): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Delete one of the user's own graphs. Returns whether it was deleted.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* profiles.remove(id);
|
|
78
|
+
*/
|
|
79
|
+
remove(id: string): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Replace all the user's graphs (e.g. when restoring saved ones), keeping only valid ones (see `sanitiseProfiles`).
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* profiles.set(settings.profiles);
|
|
85
|
+
*/
|
|
86
|
+
set(graphs: unknown): void;
|
|
87
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Interpolation, PROFILE_PRESETS } from "../../profiles/index.js";
|
|
2
|
+
import "../../profiles/schema.js";
|
|
3
|
+
// src/lib/classes/ProfileGraphs/index.ts
|
|
4
|
+
/** The interpolations a point can have */
|
|
5
|
+
var INTERPOLATIONS = Object.values(Interpolation);
|
|
6
|
+
/** Whether a value is text of a usable length for an ID or name */
|
|
7
|
+
var isText = (value) => typeof value === "string" && value.trim().length > 0 && value.length <= 100;
|
|
8
|
+
/**
|
|
9
|
+
* Keep only valid points from an untrusted value, in order from left to right: each on the grid, with a known
|
|
10
|
+
* interpolation. Undefined if there aren't 2 to `MAX_PROFILE_POINTS` of them.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* sanitisePoints([{ x: 10, y: 0, i: "linear" }, { x: 0, y: -10, i: "linear" }]); // sorted by x
|
|
14
|
+
*/
|
|
15
|
+
var sanitisePoints = (input) => {
|
|
16
|
+
if (!Array.isArray(input)) return void 0;
|
|
17
|
+
const points = input.filter((point) => typeof point?.x === "number" && typeof point.y === "number" && point.x >= 0 && point.x <= 10 && point.y >= -10 && point.y <= 0 && INTERPOLATIONS.includes(point.i)).map(({ x, y, i }) => ({
|
|
18
|
+
x,
|
|
19
|
+
y,
|
|
20
|
+
i
|
|
21
|
+
})).sort((a, b) => a.x - b.x);
|
|
22
|
+
return points.length >= 2 && points.length <= 32 ? points : void 0;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Keep only valid profile graphs from an untrusted value (e.g. stored or imported): each with an ID and name and valid
|
|
26
|
+
* points (see `sanitisePoints`), each ID once and none of the presets', up to `MAX_SAVED_PROFILES`. Undefined if the
|
|
27
|
+
* value isn't an array.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* sanitiseProfiles([{ id: "a", name: "Soft", points: [...] }, { id: "a", name: "Again", points: [...] }]); // the first
|
|
31
|
+
*/
|
|
32
|
+
var sanitiseProfiles = (input) => {
|
|
33
|
+
if (!Array.isArray(input)) return void 0;
|
|
34
|
+
const graphs = /* @__PURE__ */ new Map();
|
|
35
|
+
for (const graph of input) {
|
|
36
|
+
if (graphs.size >= 100) break;
|
|
37
|
+
const points = sanitisePoints(graph?.points);
|
|
38
|
+
if (!points || !isText(graph.id) || !isText(graph.name) || graphs.has(graph.id) || PROFILE_PRESETS.some((preset) => preset.id === graph.id)) continue;
|
|
39
|
+
graphs.set(graph.id, {
|
|
40
|
+
id: graph.id,
|
|
41
|
+
name: graph.name.trim(),
|
|
42
|
+
points
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return [...graphs.values()];
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The user's own profile graphs, alongside the ones PaintCanvas comes with (`PROFILE_PRESETS`). Kept in memory; the app
|
|
49
|
+
* settings save and restore them (see `set` and the `change` event). Each gets a UUID when it's first saved.
|
|
50
|
+
*
|
|
51
|
+
* Dispatches a `change` event when the user's graphs change.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const profiles = new ProfileGraphs();
|
|
55
|
+
* const soft = profiles.save({ name: "Soft", points: PROFILE_PRESETS[1].points });
|
|
56
|
+
* profiles.get(soft.id)?.name; // "Soft"
|
|
57
|
+
*/
|
|
58
|
+
var ProfileGraphs = class extends EventTarget {
|
|
59
|
+
values = [];
|
|
60
|
+
/** The user's own graphs, in the order they were first saved */
|
|
61
|
+
get graphs() {
|
|
62
|
+
return this.values;
|
|
63
|
+
}
|
|
64
|
+
/** Every graph: the presets, then the user's own */
|
|
65
|
+
get all() {
|
|
66
|
+
return [...PROFILE_PRESETS, ...this.values];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A graph by its ID: a preset or one of the user's own.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* profiles.get("overweight")?.name; // "Overweight"
|
|
73
|
+
*/
|
|
74
|
+
get(id) {
|
|
75
|
+
return this.all.find((graph) => graph.id === id);
|
|
76
|
+
}
|
|
77
|
+
/** Whether a graph is one PaintCanvas comes with (so it can't be renamed, replaced or deleted) */
|
|
78
|
+
isPreset(id) {
|
|
79
|
+
return PROFILE_PRESETS.some((graph) => graph.id === id);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* One of the user's own graphs with a name, ignoring case and spaces around it.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* profiles.findByName(" soft "); // the graph called "Soft"
|
|
86
|
+
*/
|
|
87
|
+
findByName(name) {
|
|
88
|
+
const wanted = name.trim().toLowerCase();
|
|
89
|
+
return this.values.find((graph) => graph.name.toLowerCase() === wanted);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Save a graph as one of the user's own: as a new one (with a new UUID), or in place of the one with its ID. Returns
|
|
93
|
+
* the saved graph, or undefined if it can't be saved (no name, invalid points, a preset's ID, or no room).
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* profiles.save({ name: "Soft", points }); // { id: "1b9d…", name: "Soft", points }
|
|
97
|
+
*/
|
|
98
|
+
save(draft) {
|
|
99
|
+
const index = this.values.findIndex((graph) => graph.id === draft.id);
|
|
100
|
+
const [graph] = sanitiseProfiles([{
|
|
101
|
+
...draft,
|
|
102
|
+
id: index >= 0 ? draft.id : crypto.randomUUID()
|
|
103
|
+
}]);
|
|
104
|
+
if (!graph || index < 0 && this.values.length >= 100) return void 0;
|
|
105
|
+
this.values = index >= 0 ? this.values.map((saved, i) => i === index ? graph : saved) : [...this.values, graph];
|
|
106
|
+
this.dispatchEvent(new Event("change"));
|
|
107
|
+
return graph;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Rename one of the user's own graphs. Returns whether it was renamed.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* profiles.rename(id, "Softer");
|
|
114
|
+
*/
|
|
115
|
+
rename(id, name) {
|
|
116
|
+
const graph = this.values.find((saved) => saved.id === id);
|
|
117
|
+
return graph !== void 0 && this.save({
|
|
118
|
+
...graph,
|
|
119
|
+
name
|
|
120
|
+
}) !== void 0;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Delete one of the user's own graphs. Returns whether it was deleted.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* profiles.remove(id);
|
|
127
|
+
*/
|
|
128
|
+
remove(id) {
|
|
129
|
+
const next = this.values.filter((graph) => graph.id !== id);
|
|
130
|
+
if (next.length === this.values.length) return false;
|
|
131
|
+
this.values = next;
|
|
132
|
+
this.dispatchEvent(new Event("change"));
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Replace all the user's graphs (e.g. when restoring saved ones), keeping only valid ones (see `sanitiseProfiles`).
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* profiles.set(settings.profiles);
|
|
140
|
+
*/
|
|
141
|
+
set(graphs) {
|
|
142
|
+
const next = sanitiseProfiles(graphs);
|
|
143
|
+
if (!next) return;
|
|
144
|
+
this.values = next;
|
|
145
|
+
this.dispatchEvent(new Event("change"));
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
export { ProfileGraphs as default, sanitisePoints, sanitiseProfiles };
|
|
@@ -10,7 +10,7 @@ export interface SettingsAutosaveOptions {
|
|
|
10
10
|
delayMs?: number;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* Keeps a canvas's app settings (input settings, keyboard shortcuts, colour palette, colours, theme and language) in a
|
|
13
|
+
* Keeps a canvas's app settings (input settings, keyboard shortcuts, colour palette, colours, profile graphs, theme and language) in a
|
|
14
14
|
* store: the saved settings are restored when it starts (invalid ones are ignored), then changes are saved soon after
|
|
15
15
|
* they happen.
|
|
16
16
|
*
|
|
@@ -5,7 +5,7 @@ var SETTINGS_SAVE_DELAY_MS = 500;
|
|
|
5
5
|
/** Event dispatched when loading or saving settings fails (detail: the error) */
|
|
6
6
|
var SETTINGS_AUTOSAVE_ERROR_EVENT = "error";
|
|
7
7
|
/**
|
|
8
|
-
* Keeps a canvas's app settings (input settings, keyboard shortcuts, colour palette, colours, theme and language) in a
|
|
8
|
+
* Keeps a canvas's app settings (input settings, keyboard shortcuts, colour palette, colours, profile graphs, theme and language) in a
|
|
9
9
|
* store: the saved settings are restored when it starts (invalid ones are ignored), then changes are saved soon after
|
|
10
10
|
* they happen.
|
|
11
11
|
*
|
|
@@ -43,6 +43,7 @@ var SettingsAutosave = class extends EventTarget {
|
|
|
43
43
|
app.input,
|
|
44
44
|
app.shortcuts,
|
|
45
45
|
app.palette,
|
|
46
|
+
app.profiles,
|
|
46
47
|
app.ui
|
|
47
48
|
];
|
|
48
49
|
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
/** The themes: light, dark, or following the device's (system) */
|
|
2
2
|
export declare const THEMES: readonly ["light", "dark", "system"];
|
|
3
3
|
export type Theme = (typeof THEMES)[number];
|
|
4
|
+
/**
|
|
5
|
+
* Where canvases draw: `cpu` keeps their pixels in regular memory (reading and saving them never waits for the GPU),
|
|
6
|
+
* `gpu` asks the browser to keep them on the GPU (drawing may be quicker; the browser can still use the CPU)
|
|
7
|
+
*/
|
|
8
|
+
export declare const RENDERINGS: readonly ["cpu", "gpu"];
|
|
9
|
+
export type Rendering = (typeof RENDERINGS)[number];
|
|
4
10
|
/** Longest locale tag kept in UI settings (characters) */
|
|
5
11
|
export declare const MAX_LOCALE_LENGTH = 35;
|
|
6
12
|
/** A locale tag's characters: letters, digits and hyphens (e.g. `zh-Hant`) */
|
|
@@ -13,6 +19,14 @@ export declare const LOCALE_PATTERN = "^[A-Za-z0-9-]+$";
|
|
|
13
19
|
* isTheme(undefined); // false
|
|
14
20
|
*/
|
|
15
21
|
export declare const isTheme: (value: unknown) => value is Theme;
|
|
22
|
+
/**
|
|
23
|
+
* Whether a value is one of the renderings.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* isRendering("gpu"); // true
|
|
27
|
+
* isRendering("webgl"); // false
|
|
28
|
+
*/
|
|
29
|
+
export declare const isRendering: (value: unknown) => value is Rendering;
|
|
16
30
|
/**
|
|
17
31
|
* Whether a value can be a locale tag (letters, digits and hyphens, at most `MAX_LOCALE_LENGTH` characters).
|
|
18
32
|
*
|
|
@@ -27,7 +41,14 @@ export interface UiSettingsData {
|
|
|
27
41
|
theme: Theme;
|
|
28
42
|
/** The language's locale tag, e.g. `fr`, or undefined to follow the device's languages */
|
|
29
43
|
locale?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Where canvases draw (`cpu` by default). Browsers fix this when a canvas is made, so a change applies the next time
|
|
46
|
+
* the canvas starts (e.g. after reloading the page; see `PaintCanvasOptions.rendering`).
|
|
47
|
+
*/
|
|
48
|
+
rendering: Rendering;
|
|
30
49
|
}
|
|
50
|
+
/** UI settings used for anything not given */
|
|
51
|
+
export declare const DEFAULT_UI_SETTINGS: Readonly<UiSettingsData>;
|
|
31
52
|
/** UI settings the UI can show and change: PaintCanvas's `UiSettings`, or anything with the same data and `update` */
|
|
32
53
|
export interface UiSettingsLike extends EventTarget {
|
|
33
54
|
readonly data: Readonly<UiSettingsData>;
|
|
@@ -38,11 +59,12 @@ export interface UiSettingsLike extends EventTarget {
|
|
|
38
59
|
* or invalid. Other properties are left out.
|
|
39
60
|
*
|
|
40
61
|
* @example
|
|
41
|
-
* sanitiseUiSettings({ theme: "dark", locale: 42 }, { theme: "system"
|
|
62
|
+
* sanitiseUiSettings({ theme: "dark", locale: 42 }, { theme: "system", rendering: "cpu" });
|
|
63
|
+
* // { theme: "dark", rendering: "cpu" }
|
|
42
64
|
*/
|
|
43
65
|
export declare const sanitiseUiSettings: (input: unknown, defaults: UiSettingsData) => UiSettingsData;
|
|
44
66
|
/**
|
|
45
|
-
* The user's interface choices: the theme and
|
|
67
|
+
* The user's interface choices: the theme, the language and where canvases draw. Kept in memory; the implementer saves and restores them
|
|
46
68
|
* with the other app settings (see `SettingsAutosave`), and the UI shows and applies them (e.g. `<pc-theme-picker>` and
|
|
47
69
|
* `<pc-input-settings>`).
|
|
48
70
|
*
|
|
@@ -5,6 +5,11 @@ var THEMES = [
|
|
|
5
5
|
"dark",
|
|
6
6
|
"system"
|
|
7
7
|
];
|
|
8
|
+
/**
|
|
9
|
+
* Where canvases draw: `cpu` keeps their pixels in regular memory (reading and saving them never waits for the GPU),
|
|
10
|
+
* `gpu` asks the browser to keep them on the GPU (drawing may be quicker; the browser can still use the CPU)
|
|
11
|
+
*/
|
|
12
|
+
var RENDERINGS = ["cpu", "gpu"];
|
|
8
13
|
/** Longest locale tag kept in UI settings (characters) */
|
|
9
14
|
var MAX_LOCALE_LENGTH = 35;
|
|
10
15
|
/** A locale tag's characters: letters, digits and hyphens (e.g. `zh-Hant`) */
|
|
@@ -18,6 +23,14 @@ var LOCALE_PATTERN = "^[A-Za-z0-9-]+$";
|
|
|
18
23
|
*/
|
|
19
24
|
var isTheme = (value) => THEMES.includes(value);
|
|
20
25
|
/**
|
|
26
|
+
* Whether a value is one of the renderings.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* isRendering("gpu"); // true
|
|
30
|
+
* isRendering("webgl"); // false
|
|
31
|
+
*/
|
|
32
|
+
var isRendering = (value) => RENDERINGS.includes(value);
|
|
33
|
+
/**
|
|
21
34
|
* Whether a value can be a locale tag (letters, digits and hyphens, at most `MAX_LOCALE_LENGTH` characters).
|
|
22
35
|
*
|
|
23
36
|
* @example
|
|
@@ -25,24 +38,35 @@ var isTheme = (value) => THEMES.includes(value);
|
|
|
25
38
|
* isLocaleTag("<script>"); // false
|
|
26
39
|
*/
|
|
27
40
|
var isLocaleTag = (value) => typeof value === "string" && value.length <= 35 && (/* @__PURE__ */ new RegExp("^[A-Za-z0-9-]+$")).test(value);
|
|
41
|
+
/** UI settings used for anything not given */
|
|
42
|
+
var DEFAULT_UI_SETTINGS = {
|
|
43
|
+
theme: "system",
|
|
44
|
+
rendering: "cpu"
|
|
45
|
+
};
|
|
28
46
|
/**
|
|
29
47
|
* Keep only valid UI settings from an untrusted value (e.g. stored or imported), using `defaults` for anything missing
|
|
30
48
|
* or invalid. Other properties are left out.
|
|
31
49
|
*
|
|
32
50
|
* @example
|
|
33
|
-
* sanitiseUiSettings({ theme: "dark", locale: 42 }, { theme: "system"
|
|
51
|
+
* sanitiseUiSettings({ theme: "dark", locale: 42 }, { theme: "system", rendering: "cpu" });
|
|
52
|
+
* // { theme: "dark", rendering: "cpu" }
|
|
34
53
|
*/
|
|
35
54
|
var sanitiseUiSettings = (input, defaults) => {
|
|
36
55
|
const value = typeof input === "object" && input !== null ? input : {};
|
|
37
56
|
const theme = isTheme(value.theme) ? value.theme : defaults.theme;
|
|
38
57
|
const locale = isLocaleTag(value.locale) ? value.locale : defaults.locale;
|
|
39
|
-
|
|
58
|
+
const rendering = isRendering(value.rendering) ? value.rendering : defaults.rendering;
|
|
59
|
+
return locale === void 0 ? {
|
|
60
|
+
theme,
|
|
61
|
+
rendering
|
|
62
|
+
} : {
|
|
40
63
|
theme,
|
|
41
|
-
locale
|
|
64
|
+
locale,
|
|
65
|
+
rendering
|
|
42
66
|
};
|
|
43
67
|
};
|
|
44
68
|
/**
|
|
45
|
-
* The user's interface choices: the theme and
|
|
69
|
+
* The user's interface choices: the theme, the language and where canvases draw. Kept in memory; the implementer saves and restores them
|
|
46
70
|
* with the other app settings (see `SettingsAutosave`), and the UI shows and applies them (e.g. `<pc-theme-picker>` and
|
|
47
71
|
* `<pc-input-settings>`).
|
|
48
72
|
*
|
|
@@ -57,7 +81,7 @@ var UiSettings = class extends EventTarget {
|
|
|
57
81
|
values;
|
|
58
82
|
constructor(initial) {
|
|
59
83
|
super();
|
|
60
|
-
this.values = sanitiseUiSettings(initial,
|
|
84
|
+
this.values = sanitiseUiSettings(initial, DEFAULT_UI_SETTINGS);
|
|
61
85
|
}
|
|
62
86
|
/** Current settings (read-only copy) */
|
|
63
87
|
get data() {
|
|
@@ -74,9 +98,10 @@ var UiSettings = class extends EventTarget {
|
|
|
74
98
|
...this.values,
|
|
75
99
|
...changes
|
|
76
100
|
}, this.values);
|
|
77
|
-
|
|
101
|
+
const keys = Object.keys(next);
|
|
102
|
+
if (keys.length === Object.keys(this.values).length && keys.every((key) => next[key] === this.values[key])) return;
|
|
78
103
|
this.values = next;
|
|
79
104
|
this.dispatchEvent(new Event("change"));
|
|
80
105
|
}
|
|
81
106
|
};
|
|
82
|
-
export { LOCALE_PATTERN, MAX_LOCALE_LENGTH, THEMES, UiSettings as default, isLocaleTag, isTheme, sanitiseUiSettings };
|
|
107
|
+
export { DEFAULT_UI_SETTINGS, LOCALE_PATTERN, MAX_LOCALE_LENGTH, RENDERINGS, THEMES, UiSettings as default, isLocaleTag, isRendering, isTheme, sanitiseUiSettings };
|