@lvl99dotcom/paintcanvas 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/lib/assets/index.d.ts +92 -0
- package/dist/lib/assets/index.js +106 -0
- package/dist/lib/assets/schema.d.ts +151 -0
- package/dist/lib/assets/schema.js +76 -0
- package/dist/lib/brushes/presetFile/index.d.ts +6 -27
- package/dist/lib/brushes/presetFile/index.js +34 -47
- package/dist/lib/brushes/types/index.d.ts +2 -11
- package/dist/lib/classes/AssetSync/index.d.ts +45 -0
- package/dist/lib/classes/AssetSync/index.js +80 -0
- package/dist/lib/classes/ColourPalettes/index.d.ts +83 -0
- package/dist/lib/classes/ColourPalettes/index.js +165 -0
- package/dist/lib/classes/CustomPresets/index.d.ts +14 -15
- package/dist/lib/classes/CustomPresets/index.js +25 -10
- package/dist/lib/classes/LayerAutosave/index.d.ts +9 -2
- package/dist/lib/classes/LayerAutosave/index.js +16 -6
- package/dist/lib/classes/PaintCanvas/index.d.ts +124 -28
- package/dist/lib/classes/PaintCanvas/index.js +228 -63
- package/dist/lib/classes/Palette/index.d.ts +3 -2
- package/dist/lib/classes/Palette/index.js +3 -2
- package/dist/lib/classes/ProfileGraphs/index.d.ts +13 -2
- package/dist/lib/classes/ProfileGraphs/index.js +22 -2
- package/dist/lib/classes/RemoteDocumentAutosave/index.d.ts +73 -0
- package/dist/lib/classes/RemoteDocumentAutosave/index.js +119 -0
- package/dist/lib/classes/SettingsAutosave/index.d.ts +7 -3
- package/dist/lib/classes/SettingsAutosave/index.js +28 -7
- package/dist/lib/classes/SettingsExtensions/index.d.ts +67 -0
- package/dist/lib/classes/SettingsExtensions/index.js +88 -0
- package/dist/lib/classes/UiSettings/index.d.ts +24 -2
- package/dist/lib/classes/UiSettings/index.js +32 -7
- package/dist/lib/document/index.d.ts +61 -4
- package/dist/lib/document/index.js +173 -4
- package/dist/lib/document/schema.d.ts +154 -39
- package/dist/lib/document/schema.js +91 -25
- package/dist/lib/messages/schema.d.ts +176 -74
- package/dist/lib/messages/schema.js +2 -1
- package/dist/lib/palette/index.d.ts +8 -42
- package/dist/lib/palette/index.js +26 -59
- package/dist/lib/palette/schema.d.ts +111 -22
- package/dist/lib/palette/schema.js +51 -26
- package/dist/lib/presets/index.d.ts +1 -9
- package/dist/lib/presets/index.js +4 -10
- package/dist/lib/presets/schema.d.ts +118 -73
- package/dist/lib/presets/schema.js +28 -60
- package/dist/lib/profiles/profileFile/index.d.ts +10 -0
- package/dist/lib/profiles/profileFile/index.js +19 -0
- package/dist/lib/profiles/schema.d.ts +96 -3
- package/dist/lib/profiles/schema.js +31 -7
- package/dist/lib/schemas/index.d.ts +42 -0
- package/dist/lib/schemas/index.js +60 -0
- package/dist/lib/settings/index.d.ts +37 -22
- package/dist/lib/settings/index.js +78 -37
- package/dist/lib/settings/schema.d.ts +202 -77
- package/dist/lib/settings/schema.js +93 -86
- package/dist/lib/storage/index.d.ts +108 -3
- package/dist/lib/storage/index.js +119 -5
- package/dist/lib/validation/validators.generated.d.ts +19 -8
- package/dist/lib/validation/validators.generated.js +44723 -18889
- package/dist/lib/version/index.d.ts +25 -0
- package/dist/lib/version/index.js +35 -0
- package/dist/locales/en-GB.js +28 -5
- package/dist/locales/en-US.js +28 -5
- package/dist/locales/fr.js +28 -5
- package/dist/locales/ja.js +28 -5
- package/dist/locales/ko.js +28 -5
- package/dist/locales/zh-Hans.js +28 -5
- package/dist/locales/zh-Hant.js +28 -5
- package/dist/package.js +1 -1
- package/dist/testing/app.d.ts +6 -2
- package/dist/testing/app.js +7 -2
- package/dist/testing/documentStore.d.ts +26 -0
- package/dist/testing/documentStore.js +31 -0
- package/dist/ui/elements/PaintCanvas/index.d.ts +19 -5
- package/dist/ui/elements/PaintCanvas/index.js +41 -10
- package/dist/ui/elements/PcAppSettings/index.d.ts +14 -7
- package/dist/ui/elements/PcAppSettings/index.js +58 -33
- package/dist/ui/elements/PcBrushConfig/index.d.ts +1 -0
- package/dist/ui/elements/PcBrushConfig/index.js +8 -9
- package/dist/ui/elements/PcBrushTest/index.d.ts +1 -0
- package/dist/ui/elements/PcBrushTest/index.js +8 -7
- package/dist/ui/elements/PcBrushTest/style.js +1 -1
- 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 +9 -0
- package/dist/ui/elements/PcCanvas/index.js +33 -17
- package/dist/ui/elements/PcClipboardTools/index.d.ts +7 -4
- package/dist/ui/elements/PcClipboardTools/index.js +20 -26
- 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 +1 -1
- package/dist/ui/elements/PcPanelBrushes/index.js +22 -28
- package/dist/ui/elements/PcPanelBrushes/style.js +1 -1
- package/dist/ui/elements/PcPanelColours/index.d.ts +56 -19
- package/dist/ui/elements/PcPanelColours/index.js +251 -104
- package/dist/ui/elements/PcPanelColours/style.js +1 -1
- package/dist/ui/elements/PcSliderRange/index.js +3 -2
- package/dist/ui/elements/PcStats/index.d.ts +8 -4
- package/dist/ui/elements/PcStats/index.js +39 -5
- 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 +8 -8
- package/dist/ui/elements/PcVersion/index.d.ts +12 -0
- package/dist/ui/elements/PcVersion/index.js +43 -0
- package/dist/ui/index.d.ts +3 -1
- package/dist/ui/index.js +4 -2
- package/dist/ui/layout/index.d.ts +2 -2
- package/dist/ui/layout/index.js +2 -2
- package/locales/en-GB.json +28 -5
- package/locales/en-US.json +28 -5
- package/locales/fr.json +28 -5
- package/locales/ja.json +28 -5
- package/locales/ko.json +28 -5
- package/locales/zh-Hans.json +28 -5
- package/locales/zh-Hant.json +28 -5
- package/package.json +3 -2
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// src/lib/classes/RemoteDocumentAutosave/index.ts
|
|
2
|
+
/** How often changes are saved to the remote store, at most (milliseconds): every 5 minutes */
|
|
3
|
+
var REMOTE_SAVE_INTERVAL_MS = 3e5;
|
|
4
|
+
/** Event dispatched when saving fails (detail: the error) */
|
|
5
|
+
var REMOTE_AUTOSAVE_ERROR_EVENT = "error";
|
|
6
|
+
/**
|
|
7
|
+
* Keeps a canvas's drawing in a remote store (e.g. a user's account online), less often than the browser keeps it:
|
|
8
|
+
* every `intervalMs` while there are changes not saved yet, or when asked (`save`). The whole document is saved each
|
|
9
|
+
* time, with a thumbnail.
|
|
10
|
+
*
|
|
11
|
+
* Dispatches `change` when saving starts, makes progress or ends, and `error` (detail: the error) when it fails (the
|
|
12
|
+
* changes are tried again next time).
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* const autosave = new RemoteDocumentAutosave(canvas, myAccountStore);
|
|
16
|
+
* autosave.start();
|
|
17
|
+
* saveButton.onclick = () => autosave.save();
|
|
18
|
+
*/
|
|
19
|
+
var RemoteDocumentAutosave = class extends EventTarget {
|
|
20
|
+
/** Where the drawing is kept */
|
|
21
|
+
store;
|
|
22
|
+
/** The last saving error, until a save succeeds */
|
|
23
|
+
error;
|
|
24
|
+
/** When the drawing was last saved, if it has been */
|
|
25
|
+
lastSaved;
|
|
26
|
+
/** How much of the save under way is done (0 to 1), if known */
|
|
27
|
+
progress;
|
|
28
|
+
canvas;
|
|
29
|
+
intervalMs;
|
|
30
|
+
timer;
|
|
31
|
+
/** The layers being watched for changes */
|
|
32
|
+
watched;
|
|
33
|
+
/** Whether there are changes not saved yet */
|
|
34
|
+
dirty = false;
|
|
35
|
+
/** The save being written, if any */
|
|
36
|
+
current;
|
|
37
|
+
onChange = () => {
|
|
38
|
+
this.dirty = true;
|
|
39
|
+
};
|
|
40
|
+
onCanvasChange = () => {
|
|
41
|
+
this.dirty = true;
|
|
42
|
+
if (this.canvas.layers === this.watched) return;
|
|
43
|
+
this.watched?.removeEventListener("change", this.onChange);
|
|
44
|
+
this.watched = this.canvas.layers;
|
|
45
|
+
this.watched.addEventListener("change", this.onChange);
|
|
46
|
+
};
|
|
47
|
+
constructor(canvas, store, options = {}) {
|
|
48
|
+
super();
|
|
49
|
+
this.canvas = canvas;
|
|
50
|
+
this.store = store;
|
|
51
|
+
this.intervalMs = options.intervalMs ?? 3e5;
|
|
52
|
+
}
|
|
53
|
+
/** Whether a save is under way */
|
|
54
|
+
get saving() {
|
|
55
|
+
return this.current !== void 0;
|
|
56
|
+
}
|
|
57
|
+
/** Whether there are changes not saved yet */
|
|
58
|
+
get hasChanges() {
|
|
59
|
+
return this.dirty;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Start saving changes every `intervalMs`.
|
|
63
|
+
*/
|
|
64
|
+
start() {
|
|
65
|
+
if (this.timer !== void 0) return;
|
|
66
|
+
this.watched = this.canvas.layers;
|
|
67
|
+
this.watched.addEventListener("change", this.onChange);
|
|
68
|
+
this.canvas.addEventListener("change", this.onCanvasChange);
|
|
69
|
+
this.timer = setInterval(() => {
|
|
70
|
+
if (this.dirty) this.save();
|
|
71
|
+
}, this.intervalMs);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Stop saving changes (a save under way finishes). Changes not saved yet aren't saved.
|
|
75
|
+
*/
|
|
76
|
+
stop() {
|
|
77
|
+
clearInterval(this.timer);
|
|
78
|
+
this.timer = void 0;
|
|
79
|
+
this.watched?.removeEventListener("change", this.onChange);
|
|
80
|
+
this.watched = void 0;
|
|
81
|
+
this.canvas.removeEventListener("change", this.onCanvasChange);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Save the drawing now (e.g. when the user asks). While a save is under way, that save is returned.
|
|
85
|
+
*/
|
|
86
|
+
save() {
|
|
87
|
+
if (this.current) return this.current;
|
|
88
|
+
this.dirty = false;
|
|
89
|
+
this.progress = void 0;
|
|
90
|
+
this.current = this.write().finally(() => {
|
|
91
|
+
this.current = void 0;
|
|
92
|
+
this.progress = void 0;
|
|
93
|
+
this.notify();
|
|
94
|
+
});
|
|
95
|
+
this.notify();
|
|
96
|
+
return this.current;
|
|
97
|
+
}
|
|
98
|
+
/** Save the whole drawing */
|
|
99
|
+
async write() {
|
|
100
|
+
try {
|
|
101
|
+
const document = await this.canvas.createDocument();
|
|
102
|
+
await this.store.save(document, (done) => {
|
|
103
|
+
this.progress = Math.min(1, Math.max(0, done));
|
|
104
|
+
this.notify();
|
|
105
|
+
});
|
|
106
|
+
this.lastSaved = /* @__PURE__ */ new Date();
|
|
107
|
+
this.error = void 0;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
this.dirty = true;
|
|
110
|
+
this.error = error;
|
|
111
|
+
this.dispatchEvent(new CustomEvent(REMOTE_AUTOSAVE_ERROR_EVENT, { detail: error }));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Tell listeners something changed */
|
|
115
|
+
notify() {
|
|
116
|
+
this.dispatchEvent(new Event("change"));
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
export { REMOTE_AUTOSAVE_ERROR_EVENT, REMOTE_SAVE_INTERVAL_MS, RemoteDocumentAutosave as default };
|
|
@@ -10,9 +10,11 @@ export interface SettingsAutosaveOptions {
|
|
|
10
10
|
delayMs?: number;
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* Keeps a canvas's app settings (input settings, keyboard shortcuts, colour
|
|
14
|
-
* store: the saved settings are restored when it starts (invalid ones are ignored), then changes are
|
|
15
|
-
* they happen.
|
|
13
|
+
* Keeps a canvas's app settings (input settings, keyboard shortcuts, colour palettes, colours, profile graphs, theme and
|
|
14
|
+
* language) in a store: the saved settings are restored when it starts (invalid ones are ignored), then changes are
|
|
15
|
+
* saved soon after they happen. While the canvas keeps colour palettes or profile graphs in stores of their own (see
|
|
16
|
+
* `PaintCanvas.useAssetStores`), they are left out of the saved settings (ones restored from older settings are added,
|
|
17
|
+
* so those stores keep them).
|
|
16
18
|
*
|
|
17
19
|
* Dispatches an `error` event (with the error as `detail`) when loading or saving fails.
|
|
18
20
|
*
|
|
@@ -46,6 +48,8 @@ export default class SettingsAutosave extends EventTarget {
|
|
|
46
48
|
save(): Promise<void>;
|
|
47
49
|
/** Restore the saved settings, then listen for changes */
|
|
48
50
|
private start;
|
|
51
|
+
/** The canvas's settings to save, without the assets kept in stores of their own */
|
|
52
|
+
private collect;
|
|
49
53
|
/** Save soon, after changes stop for a moment */
|
|
50
54
|
private schedule;
|
|
51
55
|
/** Tell listeners loading or saving failed */
|
|
@@ -5,9 +5,11 @@ 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
|
|
9
|
-
* store: the saved settings are restored when it starts (invalid ones are ignored), then changes are
|
|
10
|
-
* they happen.
|
|
8
|
+
* Keeps a canvas's app settings (input settings, keyboard shortcuts, colour palettes, colours, profile graphs, theme and
|
|
9
|
+
* language) in a store: the saved settings are restored when it starts (invalid ones are ignored), then changes are
|
|
10
|
+
* saved soon after they happen. While the canvas keeps colour palettes or profile graphs in stores of their own (see
|
|
11
|
+
* `PaintCanvas.useAssetStores`), they are left out of the saved settings (ones restored from older settings are added,
|
|
12
|
+
* so those stores keep them).
|
|
11
13
|
*
|
|
12
14
|
* Dispatches an `error` event (with the error as `detail`) when loading or saving fails.
|
|
13
15
|
*
|
|
@@ -42,8 +44,10 @@ var SettingsAutosave = class extends EventTarget {
|
|
|
42
44
|
app,
|
|
43
45
|
app.input,
|
|
44
46
|
app.shortcuts,
|
|
45
|
-
app.
|
|
47
|
+
app.palettes,
|
|
46
48
|
app.profiles,
|
|
49
|
+
app.customPresets,
|
|
50
|
+
app.settingsExtensions,
|
|
47
51
|
app.ui
|
|
48
52
|
];
|
|
49
53
|
}
|
|
@@ -63,7 +67,7 @@ var SettingsAutosave = class extends EventTarget {
|
|
|
63
67
|
*/
|
|
64
68
|
async save() {
|
|
65
69
|
this.timer = void 0;
|
|
66
|
-
const settings =
|
|
70
|
+
const settings = this.collect();
|
|
67
71
|
const json = JSON.stringify(settings);
|
|
68
72
|
if (json === this.saved) return;
|
|
69
73
|
this.saved = json;
|
|
@@ -76,15 +80,32 @@ var SettingsAutosave = class extends EventTarget {
|
|
|
76
80
|
}
|
|
77
81
|
/** Restore the saved settings, then listen for changes */
|
|
78
82
|
async start() {
|
|
83
|
+
let older = false;
|
|
79
84
|
try {
|
|
80
85
|
const result = isValidSettings(await this.store.load());
|
|
81
|
-
if (result.valid && !this.stopped)
|
|
86
|
+
if (result.valid && !this.stopped) {
|
|
87
|
+
await applySettings(this.app, result.value);
|
|
88
|
+
older = result.value.palette !== void 0;
|
|
89
|
+
}
|
|
82
90
|
} catch (error) {
|
|
83
91
|
this.fail(error);
|
|
84
92
|
}
|
|
85
93
|
if (this.stopped) return;
|
|
86
|
-
this.saved = JSON.stringify(
|
|
94
|
+
this.saved = JSON.stringify(this.collect());
|
|
87
95
|
for (const source of this.sources) source.addEventListener("change", this.onChange);
|
|
96
|
+
if (older) {
|
|
97
|
+
this.saved = void 0;
|
|
98
|
+
await this.save();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** The canvas's settings to save, without the assets kept in stores of their own */
|
|
102
|
+
collect() {
|
|
103
|
+
const stores = this.app.assetStores;
|
|
104
|
+
return collectSettings(this.app, { without: [
|
|
105
|
+
"brushes",
|
|
106
|
+
"palettes",
|
|
107
|
+
"profiles"
|
|
108
|
+
].filter((kind) => stores[kind] !== void 0) });
|
|
88
109
|
}
|
|
89
110
|
/** Save soon, after changes stop for a moment */
|
|
90
111
|
schedule() {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** Keys of app settings PaintCanvas uses (and those of settings files), which extensions can't use */
|
|
2
|
+
export declare const RESERVED_SETTINGS_KEYS: readonly string[];
|
|
3
|
+
/**
|
|
4
|
+
* Settings a wrapper app adds to PaintCanvas's app settings, under a key of its own (e.g. `livecanvas`). They're kept,
|
|
5
|
+
* exported and imported with the app settings, wherever those are kept.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const extension: SettingsExtension<{ sound: boolean }> = {
|
|
9
|
+
* key: "livecanvas",
|
|
10
|
+
* validate: (value): value is { sound: boolean } => typeof (value as { sound?: unknown })?.sound === "boolean",
|
|
11
|
+
* collect: () => ({ sound: mySettings.sound }),
|
|
12
|
+
* apply: (value) => mySettings.update(value),
|
|
13
|
+
* target: mySettings, // dispatches `change`
|
|
14
|
+
* };
|
|
15
|
+
*/
|
|
16
|
+
export interface SettingsExtension<T = unknown> {
|
|
17
|
+
/** Key of its settings in the app settings (not one of `RESERVED_SETTINGS_KEYS`) */
|
|
18
|
+
key: string;
|
|
19
|
+
/** Whether a value (read from a store or a file, so untrusted) is valid settings for it */
|
|
20
|
+
validate(value: unknown): value is T;
|
|
21
|
+
/** Its current settings (JSON-friendly) */
|
|
22
|
+
collect(): T;
|
|
23
|
+
/** Use settings read from a store or a file */
|
|
24
|
+
apply(value: T): void;
|
|
25
|
+
/** Dispatches `change` when its settings change, so they're saved */
|
|
26
|
+
target?: EventTarget;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The settings extensions of a canvas (see `SettingsExtension`). Settings read for an extension that isn't added yet are
|
|
30
|
+
* kept, so they're used when it's added and aren't lost when the settings are saved meanwhile.
|
|
31
|
+
*
|
|
32
|
+
* Dispatches `change` when an extension's settings change, or one is added or removed.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* canvas.settingsExtensions.add(liveCanvasSettings);
|
|
36
|
+
*/
|
|
37
|
+
export default class SettingsExtensions extends EventTarget {
|
|
38
|
+
private readonly extensions;
|
|
39
|
+
/** Settings read for extensions not added yet, by key */
|
|
40
|
+
private readonly pending;
|
|
41
|
+
private readonly onChange;
|
|
42
|
+
/** The extensions added, in order */
|
|
43
|
+
get items(): readonly SettingsExtension[];
|
|
44
|
+
/**
|
|
45
|
+
* Add an extension, using settings already read for it if they're valid. Throws if its key is PaintCanvas's or
|
|
46
|
+
* another extension's.
|
|
47
|
+
*/
|
|
48
|
+
add<T>(extension: SettingsExtension<T>): void;
|
|
49
|
+
/**
|
|
50
|
+
* Remove an extension by key. Returns whether it was there.
|
|
51
|
+
*/
|
|
52
|
+
remove(key: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Every extension's settings, by key, with settings kept for extensions not added yet.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* canvas.settingsExtensions.collect(); // { livecanvas: { sound: true } }
|
|
58
|
+
*/
|
|
59
|
+
collect(): Record<string, unknown>;
|
|
60
|
+
/**
|
|
61
|
+
* Use the parts of app settings that aren't PaintCanvas's: each extension's, if valid; ones for extensions not added
|
|
62
|
+
* yet are kept for them.
|
|
63
|
+
*/
|
|
64
|
+
apply(settings: Record<string, unknown>): void;
|
|
65
|
+
/** Tell listeners something changed */
|
|
66
|
+
private notify;
|
|
67
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// src/lib/classes/SettingsExtensions/index.ts
|
|
2
|
+
/** Keys of app settings PaintCanvas uses (and those of settings files), which extensions can't use */
|
|
3
|
+
var RESERVED_SETTINGS_KEYS = [
|
|
4
|
+
"$schema",
|
|
5
|
+
"@type",
|
|
6
|
+
"input",
|
|
7
|
+
"shortcuts",
|
|
8
|
+
"palette",
|
|
9
|
+
"palettes",
|
|
10
|
+
"colours",
|
|
11
|
+
"profiles",
|
|
12
|
+
"brushes",
|
|
13
|
+
"ui"
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* The settings extensions of a canvas (see `SettingsExtension`). Settings read for an extension that isn't added yet are
|
|
17
|
+
* kept, so they're used when it's added and aren't lost when the settings are saved meanwhile.
|
|
18
|
+
*
|
|
19
|
+
* Dispatches `change` when an extension's settings change, or one is added or removed.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* canvas.settingsExtensions.add(liveCanvasSettings);
|
|
23
|
+
*/
|
|
24
|
+
var SettingsExtensions = class extends EventTarget {
|
|
25
|
+
extensions = /* @__PURE__ */ new Map();
|
|
26
|
+
/** Settings read for extensions not added yet, by key */
|
|
27
|
+
pending = /* @__PURE__ */ new Map();
|
|
28
|
+
onChange = () => this.notify();
|
|
29
|
+
/** The extensions added, in order */
|
|
30
|
+
get items() {
|
|
31
|
+
return [...this.extensions.values()];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Add an extension, using settings already read for it if they're valid. Throws if its key is PaintCanvas's or
|
|
35
|
+
* another extension's.
|
|
36
|
+
*/
|
|
37
|
+
add(extension) {
|
|
38
|
+
const { key } = extension;
|
|
39
|
+
if (RESERVED_SETTINGS_KEYS.includes(key) || this.extensions.has(key)) throw new Error(`Settings key "${key}" is already used`);
|
|
40
|
+
this.extensions.set(key, extension);
|
|
41
|
+
if (this.pending.has(key)) {
|
|
42
|
+
const value = this.pending.get(key);
|
|
43
|
+
this.pending.delete(key);
|
|
44
|
+
if (extension.validate(value)) extension.apply(value);
|
|
45
|
+
}
|
|
46
|
+
extension.target?.addEventListener("change", this.onChange);
|
|
47
|
+
this.notify();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Remove an extension by key. Returns whether it was there.
|
|
51
|
+
*/
|
|
52
|
+
remove(key) {
|
|
53
|
+
const extension = this.extensions.get(key);
|
|
54
|
+
if (!extension) return false;
|
|
55
|
+
extension.target?.removeEventListener("change", this.onChange);
|
|
56
|
+
this.extensions.delete(key);
|
|
57
|
+
this.notify();
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Every extension's settings, by key, with settings kept for extensions not added yet.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* canvas.settingsExtensions.collect(); // { livecanvas: { sound: true } }
|
|
65
|
+
*/
|
|
66
|
+
collect() {
|
|
67
|
+
const parts = Object.fromEntries(this.pending);
|
|
68
|
+
for (const [key, extension] of this.extensions) parts[key] = extension.collect();
|
|
69
|
+
return parts;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Use the parts of app settings that aren't PaintCanvas's: each extension's, if valid; ones for extensions not added
|
|
73
|
+
* yet are kept for them.
|
|
74
|
+
*/
|
|
75
|
+
apply(settings) {
|
|
76
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
77
|
+
if (RESERVED_SETTINGS_KEYS.includes(key) || value === void 0) continue;
|
|
78
|
+
const extension = this.extensions.get(key);
|
|
79
|
+
if (!extension) this.pending.set(key, value);
|
|
80
|
+
else if (extension.validate(value)) extension.apply(value);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/** Tell listeners something changed */
|
|
84
|
+
notify() {
|
|
85
|
+
this.dispatchEvent(new Event("change"));
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
export { RESERVED_SETTINGS_KEYS, SettingsExtensions as default };
|
|
@@ -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 };
|
|
@@ -1,17 +1,74 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AssetAuthor, AssetAuthorDetails } from "../assets/schema";
|
|
2
|
+
import { type DocumentFile, type DocumentInfo, type DrawingDocument } from "./schema";
|
|
2
3
|
export * from "./schema";
|
|
3
|
-
/**
|
|
4
|
+
/** The `format` of documents from before versioned schemas */
|
|
5
|
+
export declare const LEGACY_DOCUMENT_FORMAT = "paintcanvas-document";
|
|
6
|
+
/** Name of a new document, or one from before documents had names */
|
|
7
|
+
export declare const UNTITLED_DOCUMENT_NAME = "Untitled";
|
|
8
|
+
/** Result of checking a document file */
|
|
4
9
|
export type DocumentValidationResult = {
|
|
5
10
|
valid: true;
|
|
6
|
-
value:
|
|
11
|
+
value: DocumentFile;
|
|
7
12
|
} | {
|
|
8
13
|
valid: false;
|
|
9
14
|
errors: string[];
|
|
10
15
|
};
|
|
11
16
|
/**
|
|
12
|
-
* Check a document (e.g. read from a file) against `documentSchema`, and that its selected layer exists.
|
|
17
|
+
* Check a document file (e.g. read from a file) against `documentSchema`, and that its selected layer exists.
|
|
13
18
|
*
|
|
14
19
|
* @example
|
|
15
20
|
* isValidDocument(JSON.parse(text)).valid; // true
|
|
16
21
|
*/
|
|
17
22
|
export declare const isValidDocument: (input: unknown) => DocumentValidationResult;
|
|
23
|
+
/**
|
|
24
|
+
* An author's details: their name alone becomes `{ name }`.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* authorDetails("Ada"); // { name: "Ada" }
|
|
28
|
+
*/
|
|
29
|
+
export declare const authorDetails: (author: AssetAuthor) => AssetAuthorDetails;
|
|
30
|
+
/**
|
|
31
|
+
* Whether two authors are the same person: by DID when both have one, otherwise by handle when both have one,
|
|
32
|
+
* otherwise by name.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* sameAuthor({ name: "Ada", handle: "ada.test" }, { name: "Ada L", handle: "ada.test" }); // true
|
|
36
|
+
*/
|
|
37
|
+
export declare const sameAuthor: (a: AssetAuthorDetails, b: AssetAuthorDetails) => boolean;
|
|
38
|
+
/**
|
|
39
|
+
* A document's authors with another added at the end, unless they're already one of them.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* withAuthor([{ name: "Ada" }], "Ann"); // [{ name: "Ada" }, { name: "Ann" }]
|
|
43
|
+
*/
|
|
44
|
+
export declare const withAuthor: (authors: readonly AssetAuthorDetails[], author: AssetAuthor) => AssetAuthorDetails[];
|
|
45
|
+
/**
|
|
46
|
+
* Details for a new document: a new ID, a name, an author and the time now.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* newDocumentInfo("Ada"); // { id: "1b9d…", name: "Untitled", author: [{ name: "Ada" }], dateCreated: "2026-…", … }
|
|
50
|
+
*/
|
|
51
|
+
export declare const newDocumentInfo: (author?: AssetAuthor, name?: string, now?: Date) => DocumentInfo;
|
|
52
|
+
/**
|
|
53
|
+
* A document as a file (JSON), with its images as PNG data URLs.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* JSON.stringify(await documentToFile(await canvas.createDocument()));
|
|
57
|
+
*/
|
|
58
|
+
export declare const documentToFile: (document: DrawingDocument) => Promise<DocumentFile>;
|
|
59
|
+
/**
|
|
60
|
+
* Read a document file (e.g. parsed from JSON), with its images as PNG blobs. Files from before versioned schemas are
|
|
61
|
+
* migrated. Throws an `AssetFileError`: `invalid` if it isn't a document or doesn't match its schema, `unsupported` if
|
|
62
|
+
* it's a version the app can't read.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* await canvas.openDocument(documentFromFile(JSON.parse(text)));
|
|
66
|
+
*/
|
|
67
|
+
export declare const documentFromFile: (data: unknown, now?: Date) => DrawingDocument;
|
|
68
|
+
/**
|
|
69
|
+
* The name of the file a document is exported to, starting with a prefix (see `PaintCanvasNames.filePrefix`).
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* documentFileName("Sunset at sea"); // "paintcanvas-sunset-at-sea.json"
|
|
73
|
+
*/
|
|
74
|
+
export declare const documentFileName: (name: string, prefix?: string) => string;
|