@lvl99dotcom/paintcanvas 0.4.0 → 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/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 +29 -2
- package/dist/lib/classes/BrushSettings/index.js +51 -14
- package/dist/lib/classes/KeyboardShortcuts/index.d.ts +31 -2
- package/dist/lib/classes/KeyboardShortcuts/index.js +66 -4
- package/dist/lib/classes/PaintCanvas/index.d.ts +46 -0
- package/dist/lib/classes/PaintCanvas/index.js +67 -0
- package/dist/lib/classes/SettingsAutosave/index.d.ts +3 -2
- package/dist/lib/classes/SettingsAutosave/index.js +5 -3
- package/dist/lib/classes/UiSettings/index.d.ts +68 -0
- package/dist/lib/classes/UiSettings/index.js +82 -0
- package/dist/lib/settings/index.d.ts +1 -1
- package/dist/lib/settings/index.js +4 -2
- package/dist/lib/settings/schema.d.ts +18 -0
- package/dist/lib/settings/schema.js +14 -0
- package/dist/lib/validation/validators.generated.js +169 -1
- package/dist/locales/en-GB.js +9 -2
- package/dist/locales/en-US.js +9 -2
- package/dist/locales/fr.js +9 -2
- package/dist/locales/ja.js +9 -2
- package/dist/locales/ko.js +9 -2
- package/dist/locales/zh-Hans.js +9 -2
- package/dist/locales/zh-Hant.js +9 -2
- 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 +17 -12
- package/dist/ui/elements/PaintCanvas/index.js +34 -17
- package/dist/ui/elements/PaintCanvas/style.js +1 -1
- package/dist/ui/elements/PcAppSettings/index.d.ts +1 -1
- package/dist/ui/elements/PcAppSettings/index.js +1 -1
- 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 +21 -1
- package/dist/ui/elements/PcCanvas/index.js +53 -10
- 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/PcContextMenu/index.d.ts +2 -2
- package/dist/ui/elements/PcInputSettings/index.d.ts +13 -0
- package/dist/ui/elements/PcInputSettings/index.js +16 -1
- package/dist/ui/elements/PcMenu/index.d.ts +48 -3
- package/dist/ui/elements/PcMenu/index.js +111 -19
- 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 +3 -1
- package/dist/ui/elements/PcPane/index.js +44 -7
- package/dist/ui/elements/PcPanel/index.d.ts +2 -2
- package/dist/ui/elements/PcPanel/index.js +8 -8
- package/dist/ui/elements/PcPanelBrushes/index.d.ts +2 -15
- package/dist/ui/elements/PcPanelBrushes/index.js +6 -43
- package/dist/ui/elements/PcPanelBrushes/style.js +1 -1
- package/dist/ui/elements/PcPopover/index.d.ts +39 -1
- package/dist/ui/elements/PcPopover/index.js +72 -4
- 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 +382 -318
- package/dist/ui/elements/PcToolSettings/style.js +1 -1
- package/dist/ui/elements/PcToolbar/index.d.ts +15 -0
- package/dist/ui/elements/PcToolbar/index.js +55 -14
- 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 +7 -2
- package/dist/ui/index.js +10 -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 +12 -7
- package/locales/en-GB.json +9 -2
- package/locales/en-US.json +9 -2
- package/locales/fr.json +9 -2
- package/locales/ja.json +9 -2
- package/locales/ko.json +9 -2
- package/locales/zh-Hans.json +9 -2
- package/locales/zh-Hant.json +9 -2
- package/package.json +11 -4
- 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
|
@@ -7,6 +7,18 @@ import { comboFromEvent, isTypingTarget, sameCombo } from "../../utils/shortcuts
|
|
|
7
7
|
* Escape is left to close it.
|
|
8
8
|
*/
|
|
9
9
|
var inOpenDialog = (path) => path.some((target) => target instanceof HTMLDialogElement && target.open);
|
|
10
|
+
/**
|
|
11
|
+
* How long (milliseconds) the paste shortcut waits for the browser's `paste` event (which carries other apps' images)
|
|
12
|
+
* before pasting the image data copied here
|
|
13
|
+
*/
|
|
14
|
+
var PASTE_EVENT_WAIT_MS = 50;
|
|
15
|
+
/**
|
|
16
|
+
* The first image in a paste's clipboard data (e.g. copied in another app), if any.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* pastedImage(event.clipboardData); // File of type "image/png", or undefined
|
|
20
|
+
*/
|
|
21
|
+
var pastedImage = (data) => [...data?.files ?? []].find((file) => file.type.startsWith("image/"));
|
|
10
22
|
/** Event dispatched on the window for shortcut actions handled elsewhere (e.g. saving) */
|
|
11
23
|
var SHORTCUT_EVENT = "pc-shortcut";
|
|
12
24
|
/**
|
|
@@ -16,7 +28,10 @@ var SHORTCUT_EVENT = "pc-shortcut";
|
|
|
16
28
|
*
|
|
17
29
|
* Tool selection, brush/eraser size and opacity, interface visibility, select all/invert/deselect, undo/redo,
|
|
18
30
|
* cut/copy/paste, deleting the masked image data (Delete or Backspace), fills, swapping colours, zooming, layer opacity
|
|
19
|
-
* and finishing or cancelling a lasso shape are handled here
|
|
31
|
+
* and finishing or cancelling a lasso shape are handled here. Pasting (the shortcut, or the browser's own paste, e.g. from
|
|
32
|
+
* its Edit menu) pastes the image data copied here, unless the window has lost focus since it was copied and the
|
|
33
|
+
* clipboard has an image (e.g. copied in another app): that image is pasted instead (see `PaintCanvas.pasteImage`).
|
|
34
|
+
* Other actions other actions (and Escape/Enter for later features)
|
|
20
35
|
* dispatch an `pc-shortcut` event with the action as `detail`. Holding the hold-to-pan key (Space by default) pans
|
|
21
36
|
* temporarily; releasing it returns to the selected tool.
|
|
22
37
|
*
|
|
@@ -31,9 +46,20 @@ var KeyboardShortcuts = class {
|
|
|
31
46
|
heldKey;
|
|
32
47
|
onKeyDown = (event) => this.handle(event);
|
|
33
48
|
onKeyUp = (event) => this.handleKeyUp(event);
|
|
34
|
-
|
|
35
|
-
|
|
49
|
+
/** Turns pasted image files into images (replaceable for tests) */
|
|
50
|
+
decodeImage;
|
|
51
|
+
/** `app.copyCount` when the window last lost focus: copies since then were made here */
|
|
52
|
+
copiesBeforeBlur = 0;
|
|
53
|
+
/** Pastes the image data copied here if the browser doesn't fire `paste` soon after the shortcut */
|
|
54
|
+
pasteTimer;
|
|
55
|
+
onBlur = () => {
|
|
56
|
+
this.release();
|
|
57
|
+
this.copiesBeforeBlur = this.app.copyCount;
|
|
58
|
+
};
|
|
59
|
+
onPaste = (event) => void this.handlePaste(event);
|
|
60
|
+
constructor(app, decodeImage = (blob) => createImageBitmap(blob)) {
|
|
36
61
|
this.app = app;
|
|
62
|
+
this.decodeImage = decodeImage;
|
|
37
63
|
}
|
|
38
64
|
/**
|
|
39
65
|
* Start listening for key presses on a target (usually `window`).
|
|
@@ -44,6 +70,7 @@ var KeyboardShortcuts = class {
|
|
|
44
70
|
target.addEventListener("keydown", this.onKeyDown);
|
|
45
71
|
target.addEventListener("keyup", this.onKeyUp);
|
|
46
72
|
target.addEventListener("blur", this.onBlur);
|
|
73
|
+
target.addEventListener("paste", this.onPaste);
|
|
47
74
|
}
|
|
48
75
|
/**
|
|
49
76
|
* Stop listening for key presses.
|
|
@@ -52,7 +79,9 @@ var KeyboardShortcuts = class {
|
|
|
52
79
|
this.target?.removeEventListener("keydown", this.onKeyDown);
|
|
53
80
|
this.target?.removeEventListener("keyup", this.onKeyUp);
|
|
54
81
|
this.target?.removeEventListener("blur", this.onBlur);
|
|
82
|
+
this.target?.removeEventListener("paste", this.onPaste);
|
|
55
83
|
this.target = null;
|
|
84
|
+
this.cancelPasteTimer();
|
|
56
85
|
}
|
|
57
86
|
/**
|
|
58
87
|
* Run the shortcut for a key press, if any. Returns the action run.
|
|
@@ -71,11 +100,44 @@ var KeyboardShortcuts = class {
|
|
|
71
100
|
}
|
|
72
101
|
const action = shortcuts.find((shortcut) => sameCombo(shortcut, combo))?.action;
|
|
73
102
|
if (!action) return void 0;
|
|
103
|
+
if (action.type === "paste") {
|
|
104
|
+
this.cancelPasteTimer();
|
|
105
|
+
this.pasteTimer = setTimeout(() => {
|
|
106
|
+
this.pasteTimer = void 0;
|
|
107
|
+
this.app.paste();
|
|
108
|
+
}, 50);
|
|
109
|
+
return action;
|
|
110
|
+
}
|
|
74
111
|
event.preventDefault();
|
|
75
112
|
this.run(action);
|
|
76
113
|
return action;
|
|
77
114
|
}
|
|
78
115
|
/**
|
|
116
|
+
* Paste from the browser's `paste` event: the image data copied here if it was copied since the window last lost
|
|
117
|
+
* focus (or the clipboard has no image), otherwise the clipboard's image. Ignored in text fields and open dialogs.
|
|
118
|
+
*/
|
|
119
|
+
async handlePaste(event) {
|
|
120
|
+
const path = event.composedPath();
|
|
121
|
+
if (isTypingTarget(path[0] ?? event.target) || inOpenDialog(path)) return;
|
|
122
|
+
this.cancelPasteTimer();
|
|
123
|
+
event.preventDefault();
|
|
124
|
+
const app = this.app;
|
|
125
|
+
const image = pastedImage(event.clipboardData);
|
|
126
|
+
const copiedHere = app.copyCount > this.copiesBeforeBlur;
|
|
127
|
+
if (!image || copiedHere && app.hasClipboard) {
|
|
128
|
+
app.paste();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
app.pasteImage(await this.decodeImage(image));
|
|
133
|
+
} catch {}
|
|
134
|
+
}
|
|
135
|
+
/** Stop waiting for the browser's `paste` event */
|
|
136
|
+
cancelPasteTimer() {
|
|
137
|
+
clearTimeout(this.pasteTimer);
|
|
138
|
+
this.pasteTimer = void 0;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
79
141
|
* Return to the selected tool when the hold-to-pan key is released (whatever modifiers are held by then).
|
|
80
142
|
*/
|
|
81
143
|
handleKeyUp(event) {
|
|
@@ -197,4 +259,4 @@ var KeyboardShortcuts = class {
|
|
|
197
259
|
(this.target ?? this.app).dispatchEvent(new CustomEvent(SHORTCUT_EVENT, { detail: action }));
|
|
198
260
|
}
|
|
199
261
|
};
|
|
200
|
-
export { SHORTCUT_EVENT, KeyboardShortcuts as default };
|
|
262
|
+
export { PASTE_EVENT_WAIT_MS, SHORTCUT_EVENT, KeyboardShortcuts as default, pastedImage };
|
|
@@ -18,6 +18,7 @@ import type { LayerStoreLike } from "../../storage";
|
|
|
18
18
|
import type { AssetAuthor, BrushConfig } from "../../brushes/types";
|
|
19
19
|
import CustomPresets, { type PresetStoreLike } from "../CustomPresets";
|
|
20
20
|
import InputSettings from "../InputSettings";
|
|
21
|
+
import UiSettings from "../UiSettings";
|
|
21
22
|
import Palette from "../Palette";
|
|
22
23
|
import ShortcutSettings from "../ShortcutSettings";
|
|
23
24
|
import ExternalSheets from "../ExternalSheets";
|
|
@@ -125,7 +126,14 @@ export interface PaintCanvasOptions {
|
|
|
125
126
|
names?: Partial<PaintCanvasNames>;
|
|
126
127
|
/** Where to keep the user's layers so the drawing survives reloading the page (see `useLayerStore`) */
|
|
127
128
|
layerStore?: LayerStoreLike;
|
|
129
|
+
/**
|
|
130
|
+
* The system clipboard, which copying and cutting also put a PNG of the copied image data on so other apps (and brush
|
|
131
|
+
* shapes) can use it (the browser's by default; `null` for none)
|
|
132
|
+
*/
|
|
133
|
+
systemClipboard?: SystemClipboardLike | null;
|
|
128
134
|
}
|
|
135
|
+
/** The part of the system clipboard PaintCanvas writes to (the browser's `navigator.clipboard`) */
|
|
136
|
+
export type SystemClipboardLike = Pick<Clipboard, "write">;
|
|
129
137
|
/**
|
|
130
138
|
* The painting engine: canvas size, layers, brush and eraser settings, custom presets, the selected tool, foreground and background colours,
|
|
131
139
|
* selection, transforms, clipboard, undo/redo history, viewport and input settings. Where its data is kept and who it
|
|
@@ -159,6 +167,8 @@ export default class PaintCanvas extends EventTarget {
|
|
|
159
167
|
readonly background: BackgroundWork;
|
|
160
168
|
/** How the user's input devices control the canvas (scrolling, pinching, touch gestures, focus pen mode) */
|
|
161
169
|
readonly input: InputSettings;
|
|
170
|
+
/** The user's interface choices (theme and language), kept with the other app settings */
|
|
171
|
+
readonly ui: UiSettings;
|
|
162
172
|
/** The user's saved colours, shown as swatches */
|
|
163
173
|
readonly palette: Palette;
|
|
164
174
|
/** The user's keyboard shortcuts: each configurable action's key combination */
|
|
@@ -203,6 +213,10 @@ export default class PaintCanvas extends EventTarget {
|
|
|
203
213
|
private currentSelectedColour;
|
|
204
214
|
/** Image data copied with cut or copy, and the area it was copied from */
|
|
205
215
|
private clipboard?;
|
|
216
|
+
/** How many times image data has been copied (see `copyCount`) */
|
|
217
|
+
private copies;
|
|
218
|
+
/** The system clipboard copies are also put on, if any */
|
|
219
|
+
private readonly systemClipboard?;
|
|
206
220
|
/** Drawings from elsewhere */
|
|
207
221
|
private externalSheets;
|
|
208
222
|
/** Who receives the drawing messages this canvas sends */
|
|
@@ -425,6 +439,26 @@ export default class PaintCanvas extends EventTarget {
|
|
|
425
439
|
* nothing during a stroke or while a transform is pending. Returns the area copied, if any.
|
|
426
440
|
*/
|
|
427
441
|
copy(): Rect | undefined;
|
|
442
|
+
/**
|
|
443
|
+
* How many times image data has been copied or cut, to tell whether it was copied here since something else may have
|
|
444
|
+
* been copied in another app (see `KeyboardShortcuts`).
|
|
445
|
+
*/
|
|
446
|
+
get copyCount(): number;
|
|
447
|
+
/** Whether there is copied image data to paste (see `copy` and `cut`) */
|
|
448
|
+
get hasClipboard(): boolean;
|
|
449
|
+
/**
|
|
450
|
+
* The copied image data as a PNG, if there is any (e.g. to use as a brush shape).
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* const png = await canvas.copiedPng(); // Blob of type "image/png", or undefined
|
|
454
|
+
*/
|
|
455
|
+
copiedPng(): Promise<Blob | undefined>;
|
|
456
|
+
/**
|
|
457
|
+
* Put copied image data on the system clipboard as a PNG, if there is one and the browser can. The item is made
|
|
458
|
+
* straight away (some browsers only allow writing while handling the key press or click) with the PNG to follow.
|
|
459
|
+
* Failing (e.g. the page isn't focused) changes nothing: the image data can still be pasted here.
|
|
460
|
+
*/
|
|
461
|
+
private writeSystemClipboard;
|
|
428
462
|
/**
|
|
429
463
|
* Copy the masked image data of the selected layer to the clipboard, then remove it from the layer as one undoable
|
|
430
464
|
* action (see `deleteMasked`).
|
|
@@ -471,6 +505,18 @@ export default class PaintCanvas extends EventTarget {
|
|
|
471
505
|
* discards it. A pending transform is applied first.
|
|
472
506
|
*/
|
|
473
507
|
paste(): void;
|
|
508
|
+
/**
|
|
509
|
+
* Show an image from elsewhere (e.g. pasted from another app) over the selected layer, in the middle of the part of
|
|
510
|
+
* the canvas in view, and select the Move tool to place, scale and rotate it, like `paste`. An image larger than the
|
|
511
|
+
* canvas is shrunk to fit it. Does nothing during a stroke.
|
|
512
|
+
*
|
|
513
|
+
* @example
|
|
514
|
+
* canvas.pasteImage(await createImageBitmap(file));
|
|
515
|
+
*/
|
|
516
|
+
pasteImage(source: CanvasImageSource & {
|
|
517
|
+
width: number;
|
|
518
|
+
height: number;
|
|
519
|
+
}): void;
|
|
474
520
|
/**
|
|
475
521
|
* Apply a finished lasso action to the mask: a shape is added, subtracted or replaces it; a plain click deselects.
|
|
476
522
|
*/
|
|
@@ -19,6 +19,7 @@ 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
23
|
import Palette from "../Palette/index.js";
|
|
23
24
|
import ShortcutSettings from "../ShortcutSettings/index.js";
|
|
24
25
|
import { PALETTE_FILE_FORMAT } from "../../palette/schema.js";
|
|
@@ -125,6 +126,8 @@ var PaintCanvas = class extends EventTarget {
|
|
|
125
126
|
background = new BackgroundWork(this.strokeLog);
|
|
126
127
|
/** How the user's input devices control the canvas (scrolling, pinching, touch gestures, focus pen mode) */
|
|
127
128
|
input = new InputSettings();
|
|
129
|
+
/** The user's interface choices (theme and language), kept with the other app settings */
|
|
130
|
+
ui = new UiSettings();
|
|
128
131
|
/** The user's saved colours, shown as swatches */
|
|
129
132
|
palette = new Palette();
|
|
130
133
|
/** The user's keyboard shortcuts: each configurable action's key combination */
|
|
@@ -193,6 +196,10 @@ var PaintCanvas = class extends EventTarget {
|
|
|
193
196
|
currentSelectedColour = "foreground";
|
|
194
197
|
/** Image data copied with cut or copy, and the area it was copied from */
|
|
195
198
|
clipboard;
|
|
199
|
+
/** How many times image data has been copied (see `copyCount`) */
|
|
200
|
+
copies = 0;
|
|
201
|
+
/** The system clipboard copies are also put on, if any */
|
|
202
|
+
systemClipboard;
|
|
196
203
|
/** Drawings from elsewhere */
|
|
197
204
|
externalSheets;
|
|
198
205
|
/** Who receives the drawing messages this canvas sends */
|
|
@@ -233,6 +240,7 @@ var PaintCanvas = class extends EventTarget {
|
|
|
233
240
|
this.customPresets.addEventListener("change", () => this.listCustomPresets());
|
|
234
241
|
this.customPresets.load().catch(() => void 0);
|
|
235
242
|
this.createCanvas = options.createCanvas ?? createOffscreenCanvas;
|
|
243
|
+
this.systemClipboard = options.systemClipboard === null ? void 0 : options.systemClipboard ?? globalThis.navigator?.clipboard;
|
|
236
244
|
this.canvasWidth = DEFAULT_CANVAS_SIZE;
|
|
237
245
|
this.canvasHeight = DEFAULT_CANVAS_SIZE;
|
|
238
246
|
this.layerStack = new LayerStack(this.canvasWidth, this.canvasHeight, this.createCanvas, this.maxLayers);
|
|
@@ -717,9 +725,42 @@ var PaintCanvas = class extends EventTarget {
|
|
|
717
725
|
image,
|
|
718
726
|
area
|
|
719
727
|
};
|
|
728
|
+
this.copies++;
|
|
729
|
+
this.writeSystemClipboard(image);
|
|
730
|
+
this.notify();
|
|
720
731
|
return area;
|
|
721
732
|
}
|
|
722
733
|
/**
|
|
734
|
+
* How many times image data has been copied or cut, to tell whether it was copied here since something else may have
|
|
735
|
+
* been copied in another app (see `KeyboardShortcuts`).
|
|
736
|
+
*/
|
|
737
|
+
get copyCount() {
|
|
738
|
+
return this.copies;
|
|
739
|
+
}
|
|
740
|
+
/** Whether there is copied image data to paste (see `copy` and `cut`) */
|
|
741
|
+
get hasClipboard() {
|
|
742
|
+
return this.clipboard !== void 0;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* The copied image data as a PNG, if there is any (e.g. to use as a brush shape).
|
|
746
|
+
*
|
|
747
|
+
* @example
|
|
748
|
+
* const png = await canvas.copiedPng(); // Blob of type "image/png", or undefined
|
|
749
|
+
*/
|
|
750
|
+
async copiedPng() {
|
|
751
|
+
return this.clipboard?.image.convertToBlob({ type: "image/png" });
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Put copied image data on the system clipboard as a PNG, if there is one and the browser can. The item is made
|
|
755
|
+
* straight away (some browsers only allow writing while handling the key press or click) with the PNG to follow.
|
|
756
|
+
* Failing (e.g. the page isn't focused) changes nothing: the image data can still be pasted here.
|
|
757
|
+
*/
|
|
758
|
+
writeSystemClipboard(image) {
|
|
759
|
+
if (!this.systemClipboard || typeof ClipboardItem === "undefined") return;
|
|
760
|
+
const item = new ClipboardItem({ "image/png": image.convertToBlob({ type: "image/png" }) });
|
|
761
|
+
this.systemClipboard.write([item]).catch(() => void 0);
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
723
764
|
* Copy the masked image data of the selected layer to the clipboard, then remove it from the layer as one undoable
|
|
724
765
|
* action (see `deleteMasked`).
|
|
725
766
|
*/
|
|
@@ -830,6 +871,32 @@ var PaintCanvas = class extends EventTarget {
|
|
|
830
871
|
this.transformer.float(this.layerStack.active, clipboard.image, clipboard.area);
|
|
831
872
|
}
|
|
832
873
|
/**
|
|
874
|
+
* Show an image from elsewhere (e.g. pasted from another app) over the selected layer, in the middle of the part of
|
|
875
|
+
* the canvas in view, and select the Move tool to place, scale and rotate it, like `paste`. An image larger than the
|
|
876
|
+
* canvas is shrunk to fit it. Does nothing during a stroke.
|
|
877
|
+
*
|
|
878
|
+
* @example
|
|
879
|
+
* canvas.pasteImage(await createImageBitmap(file));
|
|
880
|
+
*/
|
|
881
|
+
pasteImage(source) {
|
|
882
|
+
if (this.strokeEngine.drawing) return;
|
|
883
|
+
const scale = Math.min(1, this.canvasWidth / source.width, this.canvasHeight / source.height);
|
|
884
|
+
const width = Math.max(1, Math.round(source.width * scale));
|
|
885
|
+
const height = Math.max(1, Math.round(source.height * scale));
|
|
886
|
+
const image = this.createCanvas(width, height);
|
|
887
|
+
image.getContext("2d").drawImage(source, 0, 0, width, height);
|
|
888
|
+
const view = this.view.visibleRect();
|
|
889
|
+
const area = {
|
|
890
|
+
x: Math.round(view.x + (view.width - width) / 2),
|
|
891
|
+
y: Math.round(view.y + (view.height - height) / 2),
|
|
892
|
+
width,
|
|
893
|
+
height
|
|
894
|
+
};
|
|
895
|
+
if (this.transformer.pending) this.commitTransform();
|
|
896
|
+
this.selectTool(Tool.Move);
|
|
897
|
+
this.transformer.float(this.layerStack.active, image, area);
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
833
900
|
* Apply a finished lasso action to the mask: a shape is added, subtracted or replaces it; a plain click deselects.
|
|
834
901
|
*/
|
|
835
902
|
applyLasso(result) {
|
|
@@ -10,8 +10,9 @@ export interface SettingsAutosaveOptions {
|
|
|
10
10
|
delayMs?: number;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* Keeps a canvas's app settings (input settings, keyboard shortcuts, colour palette and
|
|
14
|
-
* settings are restored when it starts (invalid ones are ignored), then changes are saved soon after
|
|
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.
|
|
15
16
|
*
|
|
16
17
|
* Dispatches an `error` event (with the error as `detail`) when loading or saving fails.
|
|
17
18
|
*
|
|
@@ -5,8 +5,9 @@ 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 and
|
|
9
|
-
* settings are restored when it starts (invalid ones are ignored), then changes are saved soon after
|
|
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.
|
|
10
11
|
*
|
|
11
12
|
* Dispatches an `error` event (with the error as `detail`) when loading or saving fails.
|
|
12
13
|
*
|
|
@@ -41,7 +42,8 @@ var SettingsAutosave = class extends EventTarget {
|
|
|
41
42
|
app,
|
|
42
43
|
app.input,
|
|
43
44
|
app.shortcuts,
|
|
44
|
-
app.palette
|
|
45
|
+
app.palette,
|
|
46
|
+
app.ui
|
|
45
47
|
];
|
|
46
48
|
}
|
|
47
49
|
/**
|
|
@@ -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 };
|
|
@@ -25,7 +25,7 @@ export type SettingsValidationResult = {
|
|
|
25
25
|
*/
|
|
26
26
|
export declare const isValidSettings: (input: unknown) => SettingsValidationResult;
|
|
27
27
|
/**
|
|
28
|
-
* The app settings of a canvas: its input settings, keyboard shortcuts, colour palette and
|
|
28
|
+
* The app settings of a canvas: its input settings, keyboard shortcuts, colour palette, colours, theme and language.
|
|
29
29
|
*
|
|
30
30
|
* @example
|
|
31
31
|
* collectSettings(canvas).palette; // ["#2c6bff", ...]
|
|
@@ -23,7 +23,7 @@ var isValidSettings = (input) => validateSettings(input) ? {
|
|
|
23
23
|
errors: formatErrors(validateSettings.errors)
|
|
24
24
|
};
|
|
25
25
|
/**
|
|
26
|
-
* The app settings of a canvas: its input settings, keyboard shortcuts, colour palette and
|
|
26
|
+
* The app settings of a canvas: its input settings, keyboard shortcuts, colour palette, colours, theme and language.
|
|
27
27
|
*
|
|
28
28
|
* @example
|
|
29
29
|
* collectSettings(canvas).palette; // ["#2c6bff", ...]
|
|
@@ -32,7 +32,8 @@ var collectSettings = (app) => ({
|
|
|
32
32
|
input: structuredClone(app.input.data),
|
|
33
33
|
shortcuts: structuredClone(app.shortcuts.bindings),
|
|
34
34
|
palette: [...app.palette.colours],
|
|
35
|
-
colours: { ...app.colours }
|
|
35
|
+
colours: { ...app.colours },
|
|
36
|
+
ui: { ...app.ui.data }
|
|
36
37
|
});
|
|
37
38
|
/**
|
|
38
39
|
* Use app settings on a canvas, e.g. ones kept in the browser or imported. Parts left out stay as they are; invalid
|
|
@@ -49,6 +50,7 @@ var applySettings = (app, settings) => {
|
|
|
49
50
|
app.setColour(settings.colours.foreground, "foreground");
|
|
50
51
|
app.setColour(settings.colours.background, "background");
|
|
51
52
|
}
|
|
53
|
+
if (settings.ui) app.ui.update(settings.ui);
|
|
52
54
|
};
|
|
53
55
|
/**
|
|
54
56
|
* The file name settings are exported to on a day, starting with a prefix (see `PaintCanvasNames.filePrefix`).
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { InputSettingsData } from "../classes/InputSettings";
|
|
2
|
+
import { type UiSettingsData } from "../classes/UiSettings/index.ts";
|
|
2
3
|
import type { KeyCombo } from "../utils/shortcuts";
|
|
3
4
|
/** The `format` of a settings file, unless the implementer names their own (see `PaintCanvasNames`) */
|
|
4
5
|
export declare const SETTINGS_FILE_FORMAT = "paintcanvas-settings";
|
|
@@ -23,6 +24,8 @@ export interface AppSettings {
|
|
|
23
24
|
foreground: string;
|
|
24
25
|
background: string;
|
|
25
26
|
};
|
|
27
|
+
/** The theme and language */
|
|
28
|
+
ui?: Partial<UiSettingsData>;
|
|
26
29
|
}
|
|
27
30
|
/** A settings file: what users export and import */
|
|
28
31
|
export interface SettingsFile {
|
|
@@ -126,6 +129,21 @@ export declare const settingsSchema: {
|
|
|
126
129
|
};
|
|
127
130
|
};
|
|
128
131
|
};
|
|
132
|
+
readonly ui: {
|
|
133
|
+
readonly type: "object";
|
|
134
|
+
readonly additionalProperties: false;
|
|
135
|
+
readonly properties: {
|
|
136
|
+
readonly theme: {
|
|
137
|
+
readonly enum: readonly ["light", "dark", "system"];
|
|
138
|
+
};
|
|
139
|
+
readonly locale: {
|
|
140
|
+
readonly type: "string";
|
|
141
|
+
readonly minLength: 1;
|
|
142
|
+
readonly maxLength: 35;
|
|
143
|
+
readonly pattern: "^[A-Za-z0-9-]+$";
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
};
|
|
129
147
|
};
|
|
130
148
|
};
|
|
131
149
|
/** Schema of a settings file */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LOCALE_PATTERN, THEMES } from "../classes/UiSettings/index.js";
|
|
1
2
|
import { HEX_COLOUR_PATTERN, MAX_PALETTE_COLOURS, paletteFileSchema } from "../palette/schema.js";
|
|
2
3
|
// src/lib/settings/schema.ts
|
|
3
4
|
/** The `format` of a settings file, unless the implementer names their own (see `PaintCanvasNames`) */
|
|
@@ -76,6 +77,19 @@ var settingsSchema = {
|
|
|
76
77
|
foreground: hexColour,
|
|
77
78
|
background: hexColour
|
|
78
79
|
}
|
|
80
|
+
},
|
|
81
|
+
ui: {
|
|
82
|
+
type: "object",
|
|
83
|
+
additionalProperties: false,
|
|
84
|
+
properties: {
|
|
85
|
+
theme: { enum: THEMES },
|
|
86
|
+
locale: {
|
|
87
|
+
type: "string",
|
|
88
|
+
minLength: 1,
|
|
89
|
+
maxLength: 35,
|
|
90
|
+
pattern: LOCALE_PATTERN
|
|
91
|
+
}
|
|
92
|
+
}
|
|
79
93
|
}
|
|
80
94
|
}
|
|
81
95
|
};
|