@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
|
@@ -11,30 +11,29 @@ 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, { createMemoryCanvas } from "../Layer/index.js";
|
|
14
|
+
import Layer, { createMemoryCanvas, createOffscreenCanvas } from "../Layer/index.js";
|
|
15
15
|
import LayerAutosave from "../LayerAutosave/index.js";
|
|
16
|
+
import RemoteDocumentAutosave from "../RemoteDocumentAutosave/index.js";
|
|
16
17
|
import { brushPresets, eraserPresets } from "../../brushes/index.js";
|
|
17
18
|
import { BUILT_IN_AUTHOR, UNKNOWN_AUTHOR, authorHandle } from "../../brushes/authors/index.js";
|
|
18
|
-
import "../../presets/index.js";
|
|
19
|
-
import { BRUSH_PRESET_FILE_FORMAT } from "../../presets/schema.js";
|
|
20
19
|
import CustomPresets from "../CustomPresets/index.js";
|
|
20
|
+
import AssetSync from "../AssetSync/index.js";
|
|
21
|
+
import ColourPalettes from "../ColourPalettes/index.js";
|
|
21
22
|
import InputSettings from "../InputSettings/index.js";
|
|
22
|
-
import UiSettings from "../UiSettings/index.js";
|
|
23
|
+
import UiSettings, { DEFAULT_UI_SETTINGS } from "../UiSettings/index.js";
|
|
23
24
|
import Palette from "../Palette/index.js";
|
|
24
|
-
import { PALETTE_FILE_FORMAT } from "../../palette/schema.js";
|
|
25
25
|
import ProfileGraphs from "../ProfileGraphs/index.js";
|
|
26
26
|
import ShortcutSettings from "../ShortcutSettings/index.js";
|
|
27
|
-
import "
|
|
28
|
-
import { SETTINGS_FILE_FORMAT } from "../../settings/schema.js";
|
|
27
|
+
import SettingsExtensions from "../SettingsExtensions/index.js";
|
|
29
28
|
import ExternalSheets from "../ExternalSheets/index.js";
|
|
30
29
|
import PatchSender from "../../messages/PatchSender/index.js";
|
|
31
30
|
import { createPatchMessage, validateCanvasMessage } from "../../messages/index.js";
|
|
32
31
|
import { REDO_EVENT, UNDO_EVENT } from "../History/index.js";
|
|
33
32
|
import { CanvasMessageType } from "../../messages/schema.js";
|
|
34
|
-
import { ImageEncoding, base64ToBytes, boundingBoxSize,
|
|
33
|
+
import { ImageEncoding, base64ToBytes, boundingBoxSize, decodeRgbaDeflate, encodeRgbaDeflate } from "../../image/index.js";
|
|
35
34
|
import { drawLayers } from "../../rendering/compositing/index.js";
|
|
36
|
-
import
|
|
37
|
-
import {
|
|
35
|
+
import "../../document/schema.js";
|
|
36
|
+
import { documentFromFile, documentToFile, newDocumentInfo, withAuthor } from "../../document/index.js";
|
|
38
37
|
import { averageColour, flattenArea, pixelRect, readPixels } from "../../rendering/sample/index.js";
|
|
39
38
|
// src/lib/classes/PaintCanvas/index.ts
|
|
40
39
|
/** Toggleable panels */
|
|
@@ -61,32 +60,22 @@ var MAX_CANVAS_SIZE = 2e3;
|
|
|
61
60
|
var DEFAULT_COLOUR = "#000000";
|
|
62
61
|
/** Background colour to start with */
|
|
63
62
|
var DEFAULT_BACKGROUND_COLOUR = "#ffffff";
|
|
64
|
-
/** Owner of the custom presets when the `ownerId` option isn't given */
|
|
65
|
-
var DEFAULT_OWNER_ID = "local";
|
|
66
63
|
/** Author name of presets made with `newPreset` when the `presetAuthor` option isn't given */
|
|
67
64
|
var DEFAULT_AUTHOR = UNKNOWN_AUTHOR;
|
|
68
65
|
/** PaintCanvas's own names */
|
|
69
66
|
var DEFAULT_NAMES = {
|
|
70
67
|
app: BUILT_IN_AUTHOR,
|
|
71
68
|
filePrefix: "paintcanvas",
|
|
72
|
-
presetFileFormat: BRUSH_PRESET_FILE_FORMAT,
|
|
73
|
-
paletteFileFormat: PALETTE_FILE_FORMAT,
|
|
74
|
-
settingsFileFormat: SETTINGS_FILE_FORMAT,
|
|
75
69
|
unknownAuthor: UNKNOWN_AUTHOR
|
|
76
70
|
};
|
|
77
71
|
/**
|
|
78
|
-
* A canvas's pixels as a PNG
|
|
72
|
+
* A canvas's pixels as a PNG.
|
|
79
73
|
*/
|
|
80
|
-
var
|
|
74
|
+
var toPng = (canvas) => {
|
|
81
75
|
canvas.getContext("2d");
|
|
82
|
-
|
|
83
|
-
return `data:image/png;base64,${bytesToBase64(new Uint8Array(await blob.arrayBuffer()))}`;
|
|
76
|
+
return canvas.convertToBlob({ type: "image/png" });
|
|
84
77
|
};
|
|
85
78
|
/**
|
|
86
|
-
* Decode a PNG data URL into an image.
|
|
87
|
-
*/
|
|
88
|
-
var fromPngDataUrl = (dataUrl) => createImageBitmap(new Blob([base64ToBytes(dataUrl.slice(dataUrl.indexOf(",") + 1))], { type: "image/png" }));
|
|
89
|
-
/**
|
|
90
79
|
* Decode a patch into pixels.
|
|
91
80
|
*/
|
|
92
81
|
var decodePatch = async (patch) => {
|
|
@@ -127,12 +116,26 @@ var PaintCanvas = class extends EventTarget {
|
|
|
127
116
|
background = new BackgroundWork(this.strokeLog);
|
|
128
117
|
/** How the user's input devices control the canvas (scrolling, pinching, touch gestures, focus pen mode) */
|
|
129
118
|
input = new InputSettings();
|
|
130
|
-
/** The user's interface choices (theme and
|
|
131
|
-
ui
|
|
119
|
+
/** The user's interface choices (theme, language and where canvases draw), kept with the other app settings */
|
|
120
|
+
ui;
|
|
132
121
|
/** The user's saved colours, shown as swatches */
|
|
133
122
|
palette = new Palette();
|
|
134
123
|
/** The user's own profile graphs (and the presets), for shaping how brush settings vary */
|
|
135
124
|
profiles = new ProfileGraphs();
|
|
125
|
+
/** The drawing's details (see `documentInfo`) */
|
|
126
|
+
info;
|
|
127
|
+
/** Keeps the drawing in a remote store */
|
|
128
|
+
remoteDocumentAutosave;
|
|
129
|
+
/** Settings wrapper apps add to the app settings (see `SettingsExtension`) */
|
|
130
|
+
settingsExtensions = new SettingsExtensions();
|
|
131
|
+
/** The user's own colour palettes (the one PaintCanvas comes with is `palette`) */
|
|
132
|
+
palettes = new ColourPalettes();
|
|
133
|
+
/** Where the user's assets are kept */
|
|
134
|
+
stores = {};
|
|
135
|
+
/** Keeps the colour palettes in their store */
|
|
136
|
+
palettesAutosave;
|
|
137
|
+
/** Keeps the profile graphs in their store */
|
|
138
|
+
profilesAutosave;
|
|
136
139
|
/** The user's keyboard shortcuts: each configurable action's key combination */
|
|
137
140
|
shortcuts = new ShortcutSettings();
|
|
138
141
|
/** The user's pointer position over the canvas, in canvas pixels (undefined when off the canvas) */
|
|
@@ -212,10 +215,11 @@ var PaintCanvas = class extends EventTarget {
|
|
|
212
215
|
/** The layer stack whose changes are being sent */
|
|
213
216
|
sendingLayers;
|
|
214
217
|
onLayersChangeToSend = () => this.sender?.schedule();
|
|
218
|
+
/** Where the canvases draw, chosen when the canvas started (the user's choice for next time is `ui.data.rendering`) */
|
|
219
|
+
rendering;
|
|
215
220
|
/** Creates the canvases the app draws on (fake ones in tests) */
|
|
216
221
|
createCanvas;
|
|
217
222
|
/** Who the custom presets belong to (passed to the preset store) */
|
|
218
|
-
ownerId;
|
|
219
223
|
/** The largest canvas width or height allowed */
|
|
220
224
|
maxCanvasSize;
|
|
221
225
|
/** Most layers the user can have */
|
|
@@ -229,7 +233,6 @@ var PaintCanvas = class extends EventTarget {
|
|
|
229
233
|
constructor(options = {}) {
|
|
230
234
|
super();
|
|
231
235
|
this.strokeLog.enabled = false;
|
|
232
|
-
this.ownerId = options.ownerId ?? "local";
|
|
233
236
|
this.maxCanvasSize = options.maxCanvasSize ?? 2e3;
|
|
234
237
|
this.maxLayers = options.maxLayers ?? 5;
|
|
235
238
|
this.names = {
|
|
@@ -237,12 +240,15 @@ var PaintCanvas = class extends EventTarget {
|
|
|
237
240
|
...options.names
|
|
238
241
|
};
|
|
239
242
|
this.presetAuthor = options.presetAuthor ?? (() => ({ name: DEFAULT_AUTHOR }));
|
|
243
|
+
this.info = newDocumentInfo(this.author);
|
|
240
244
|
this.tips.preload(this.brush.presets);
|
|
241
245
|
this.tips.preload(this.eraser.presets);
|
|
242
|
-
this.customPresets = new CustomPresets(
|
|
246
|
+
this.customPresets = new CustomPresets();
|
|
243
247
|
this.customPresets.addEventListener("change", () => this.listCustomPresets());
|
|
244
|
-
this.
|
|
245
|
-
this.
|
|
248
|
+
this.useAssetStores(options.stores ?? {});
|
|
249
|
+
this.rendering = options.rendering ?? DEFAULT_UI_SETTINGS.rendering;
|
|
250
|
+
this.ui = new UiSettings({ rendering: this.rendering });
|
|
251
|
+
this.createCanvas = options.createCanvas ?? (this.rendering === "gpu" ? createOffscreenCanvas : createMemoryCanvas);
|
|
246
252
|
this.systemClipboard = options.systemClipboard === null ? void 0 : options.systemClipboard ?? globalThis.navigator?.clipboard;
|
|
247
253
|
this.canvasWidth = DEFAULT_CANVAS_SIZE;
|
|
248
254
|
this.canvasHeight = DEFAULT_CANVAS_SIZE;
|
|
@@ -255,6 +261,7 @@ var PaintCanvas = class extends EventTarget {
|
|
|
255
261
|
this.watchLayersAndMask();
|
|
256
262
|
if (options.width !== void 0 || options.height !== void 0) this.setCanvasSize(options.width ?? this.canvasWidth, options.height ?? this.canvasHeight);
|
|
257
263
|
if (options.layerStore) this.useLayerStore(options.layerStore);
|
|
264
|
+
if (options.remoteDocumentStore) this.useRemoteDocumentStore(options.remoteDocumentStore);
|
|
258
265
|
}
|
|
259
266
|
/** Canvas width in pixels */
|
|
260
267
|
get width() {
|
|
@@ -392,65 +399,161 @@ var PaintCanvas = class extends EventTarget {
|
|
|
392
399
|
} }));
|
|
393
400
|
return true;
|
|
394
401
|
}
|
|
402
|
+
/** The drawing's details: its ID, name, authors and when it was made and last opened */
|
|
403
|
+
get documentInfo() {
|
|
404
|
+
return this.info;
|
|
405
|
+
}
|
|
395
406
|
/**
|
|
396
|
-
*
|
|
397
|
-
* `PaintCanvasDocument`). Turn it into JSON to save it as a file.
|
|
407
|
+
* Rename the drawing. Returns whether it was renamed (not if the name is empty).
|
|
398
408
|
*
|
|
399
409
|
* @example
|
|
400
|
-
*
|
|
410
|
+
* canvas.renameDocument("Sunset at sea");
|
|
411
|
+
*/
|
|
412
|
+
renameDocument(name) {
|
|
413
|
+
const trimmed = name.trim();
|
|
414
|
+
if (!trimmed) return false;
|
|
415
|
+
this.setDocumentInfo({
|
|
416
|
+
...this.info,
|
|
417
|
+
name: trimmed
|
|
418
|
+
});
|
|
419
|
+
return true;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Use details of a drawing being restored (e.g. kept in the browser), marking it opened now.
|
|
401
423
|
*/
|
|
402
|
-
|
|
424
|
+
restoreDocumentInfo(info) {
|
|
425
|
+
this.setDocumentInfo({
|
|
426
|
+
...info,
|
|
427
|
+
dateOpened: (/* @__PURE__ */ new Date()).toISOString()
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Start a new drawing: new details (a new ID, the user as its author, made now), and the layers replaced with one
|
|
432
|
+
* empty layer. Undo history starts again.
|
|
433
|
+
*
|
|
434
|
+
* @example
|
|
435
|
+
* canvas.newDocument("Sketch");
|
|
436
|
+
*/
|
|
437
|
+
newDocument(name) {
|
|
438
|
+
this.layerStack.replace([{ name: "Layer 1" }]);
|
|
439
|
+
this.history.clear();
|
|
440
|
+
this.setDocumentInfo(newDocumentInfo(this.author, name));
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* The drawing as a document (see `DrawingDocument`): its details, size, the user's layers and drawings from elsewhere
|
|
444
|
+
* as PNG blobs, and a thumbnail (at most `THUMBNAIL_SIZE` pixels on its longest side) unless left out. The user is
|
|
445
|
+
* added to its authors.
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* await myStore.save(await canvas.createDocument());
|
|
449
|
+
*/
|
|
450
|
+
async createDocument({ thumbnail = true } = {}) {
|
|
451
|
+
const author = withAuthor(this.info.author, this.author);
|
|
452
|
+
if (author.length !== this.info.author.length) this.setDocumentInfo({
|
|
453
|
+
...this.info,
|
|
454
|
+
author
|
|
455
|
+
});
|
|
456
|
+
const { width, height } = this;
|
|
457
|
+
const scale = Math.min(1, 200 / Math.max(width, height));
|
|
458
|
+
const thumbnailWidth = Math.max(1, Math.round(width * scale));
|
|
459
|
+
const thumbnailHeight = Math.max(1, Math.round(height * scale));
|
|
403
460
|
const layers = this.layerStack.layers;
|
|
461
|
+
const [layerImages, sheetImages, thumbnailImage] = await Promise.all([
|
|
462
|
+
Promise.all(layers.map((layer) => toPng(layer.canvas))),
|
|
463
|
+
Promise.all(this.externalSheets.sheets.map(({ canvas }) => toPng(canvas))),
|
|
464
|
+
thumbnail ? toPng(this.flatten({
|
|
465
|
+
x: 0,
|
|
466
|
+
y: 0,
|
|
467
|
+
width,
|
|
468
|
+
height
|
|
469
|
+
}, thumbnailWidth, thumbnailHeight)) : void 0
|
|
470
|
+
]);
|
|
404
471
|
return {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const { name, visible, opacity, blendMode, alphaLock } = layer.toConfig();
|
|
472
|
+
...structuredClone(this.info),
|
|
473
|
+
width,
|
|
474
|
+
height,
|
|
475
|
+
layers: layers.map((layer, i) => {
|
|
476
|
+
const { id, name, visible, opacity, blendMode, alphaLock } = layer.toConfig();
|
|
411
477
|
return {
|
|
478
|
+
id,
|
|
412
479
|
name,
|
|
413
480
|
visible,
|
|
414
481
|
opacity,
|
|
415
|
-
blendMode,
|
|
482
|
+
blendingMode: blendMode,
|
|
416
483
|
alphaLock,
|
|
417
|
-
|
|
484
|
+
content: layerImages[i]
|
|
418
485
|
};
|
|
419
|
-
})
|
|
420
|
-
activeLayer:
|
|
421
|
-
external:
|
|
486
|
+
}),
|
|
487
|
+
activeLayer: this.layerStack.active.id,
|
|
488
|
+
external: this.externalSheets.sheets.map(({ source }, i) => ({
|
|
422
489
|
source,
|
|
423
|
-
|
|
424
|
-
}))
|
|
490
|
+
content: sheetImages[i]
|
|
491
|
+
})),
|
|
492
|
+
...thumbnailImage ? { thumbnail: {
|
|
493
|
+
width: thumbnailWidth,
|
|
494
|
+
height: thumbnailHeight,
|
|
495
|
+
content: thumbnailImage
|
|
496
|
+
} } : {}
|
|
425
497
|
};
|
|
426
498
|
}
|
|
427
499
|
/**
|
|
428
|
-
* Replace the drawing with a document (e.g.
|
|
429
|
-
* history starts again. Returns whether it was
|
|
430
|
-
* decoded, and then nothing changes.
|
|
500
|
+
* Replace the drawing with a document (e.g. from a store): its details (marked opened now), size, layers and drawings
|
|
501
|
+
* from elsewhere. Undo history starts again. Returns whether it was opened: not when it has too many layers, is too
|
|
502
|
+
* big, doesn't have its selected layer, or an image can't be decoded, and then nothing changes.
|
|
431
503
|
*
|
|
432
504
|
* @example
|
|
433
|
-
* await canvas.
|
|
505
|
+
* await canvas.openDocument(await myStore.load(id));
|
|
434
506
|
*/
|
|
435
|
-
async
|
|
436
|
-
const
|
|
437
|
-
if (
|
|
438
|
-
const
|
|
439
|
-
const images = await Promise.all([...doc.layers.map((layer) => fromPngDataUrl(layer.image)), ...doc.external.map((sheet) => fromPngDataUrl(sheet.image))]).catch(() => void 0);
|
|
507
|
+
async openDocument(document) {
|
|
508
|
+
const { layers, external, width, height, activeLayer } = document;
|
|
509
|
+
if (layers.length === 0 || layers.length > this.maxLayers || Math.max(width, height) > Math.min(this.maxCanvasSize, 1e4) || !layers.some((layer) => layer.id === activeLayer)) return false;
|
|
510
|
+
const images = await Promise.all([...layers, ...external].map((item) => createImageBitmap(item.content))).catch(() => void 0);
|
|
440
511
|
if (!images) return false;
|
|
441
|
-
|
|
442
|
-
id
|
|
512
|
+
this.restoreLayers(width, height, layers.map(({ id, name, visible, opacity, blendingMode, alphaLock }) => ({
|
|
513
|
+
id,
|
|
443
514
|
name,
|
|
444
515
|
visible,
|
|
445
516
|
opacity,
|
|
446
|
-
blendMode,
|
|
517
|
+
blendMode: blendingMode,
|
|
447
518
|
alphaLock
|
|
448
|
-
}));
|
|
449
|
-
|
|
450
|
-
|
|
519
|
+
})), images, activeLayer);
|
|
520
|
+
external.forEach((sheet, i) => this.externalSheets.restore(sheet.source, images[layers.length + i]));
|
|
521
|
+
const { id, name, author, dateCreated, dateOpened } = document;
|
|
522
|
+
this.restoreDocumentInfo({
|
|
523
|
+
id,
|
|
524
|
+
name,
|
|
525
|
+
author,
|
|
526
|
+
dateCreated,
|
|
527
|
+
dateOpened
|
|
528
|
+
});
|
|
451
529
|
return true;
|
|
452
530
|
}
|
|
453
531
|
/**
|
|
532
|
+
* The drawing as a document file (see `DocumentFile`): turn it into JSON to save it as a file.
|
|
533
|
+
*
|
|
534
|
+
* @example
|
|
535
|
+
* const json = JSON.stringify(await canvas.exportDocument());
|
|
536
|
+
*/
|
|
537
|
+
async exportDocument() {
|
|
538
|
+
return documentToFile(await this.createDocument());
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Replace the drawing with a document file (e.g. read from a file, see `documentFromFile`). Returns whether it was
|
|
542
|
+
* opened: not when it isn't a valid document or can't be opened (see `openDocument`), and then nothing changes.
|
|
543
|
+
*
|
|
544
|
+
* @example
|
|
545
|
+
* await canvas.importDocument(JSON.parse(text));
|
|
546
|
+
*/
|
|
547
|
+
async importDocument(file) {
|
|
548
|
+
let document;
|
|
549
|
+
try {
|
|
550
|
+
document = documentFromFile(file);
|
|
551
|
+
} catch {
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
return this.openDocument(document);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
454
557
|
* Replace the canvas size and the user's layers (top first, each with its image) and select one by ID. Undo history
|
|
455
558
|
* starts again. Throws if the size or number of layers isn't allowed.
|
|
456
559
|
*
|
|
@@ -464,6 +567,54 @@ var PaintCanvas = class extends EventTarget {
|
|
|
464
567
|
this.layerStack.select(activeLayer);
|
|
465
568
|
this.history.clear();
|
|
466
569
|
}
|
|
570
|
+
/** Where the user's assets are kept (see `useAssetStores`) */
|
|
571
|
+
get assetStores() {
|
|
572
|
+
return this.stores;
|
|
573
|
+
}
|
|
574
|
+
/** Keeps the user's profile graphs in their store, while one is used (see `useAssetStores`) */
|
|
575
|
+
get profileSync() {
|
|
576
|
+
return this.profilesAutosave;
|
|
577
|
+
}
|
|
578
|
+
/** Keeps the user's colour palettes in their store, while one is used (see `useAssetStores`) */
|
|
579
|
+
get paletteSync() {
|
|
580
|
+
return this.palettesAutosave;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Keep the user's assets in stores, by kind: custom brush presets are loaded from theirs (in place of the current
|
|
584
|
+
* ones) and saved when they change; colour palettes and profile graphs saved there are added and changes saved (see
|
|
585
|
+
* `AssetSync`). Assets of a kind without a store are kept in memory only.
|
|
586
|
+
*
|
|
587
|
+
* @example
|
|
588
|
+
* canvas.useAssetStores({ brushes: new IndexedDbAssetStore("brushes"), profiles: new IndexedDbAssetStore("profiles") });
|
|
589
|
+
*/
|
|
590
|
+
useAssetStores(stores) {
|
|
591
|
+
this.stores = { ...stores };
|
|
592
|
+
this.customPresets.useStore(stores.brushes).catch(() => void 0);
|
|
593
|
+
this.profilesAutosave?.stop();
|
|
594
|
+
this.profilesAutosave = stores.profiles ? new AssetSync(this.profiles, stores.profiles) : void 0;
|
|
595
|
+
this.palettesAutosave?.stop();
|
|
596
|
+
this.palettesAutosave = stores.palettes ? new AssetSync(this.palettes, stores.palettes) : void 0;
|
|
597
|
+
this.notify();
|
|
598
|
+
}
|
|
599
|
+
/** Keeps the drawing in a remote store, while one is used (see `useRemoteDocumentStore`) */
|
|
600
|
+
get remoteAutosave() {
|
|
601
|
+
return this.remoteDocumentAutosave;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Keep the whole drawing in a remote store (e.g. the user's account online), or stop, without one: changes are saved
|
|
605
|
+
* every few minutes (see `RemoteDocumentAutosave`), or when asked with `canvas.remoteAutosave.save()`. Returns the
|
|
606
|
+
* autosave.
|
|
607
|
+
*
|
|
608
|
+
* @example
|
|
609
|
+
* canvas.useRemoteDocumentStore(myAccountStore, { intervalMs: 10 * 60 * 1000 });
|
|
610
|
+
*/
|
|
611
|
+
useRemoteDocumentStore(store, options) {
|
|
612
|
+
this.remoteDocumentAutosave?.stop();
|
|
613
|
+
this.remoteDocumentAutosave = store ? new RemoteDocumentAutosave(this, store, options) : void 0;
|
|
614
|
+
this.remoteDocumentAutosave?.start();
|
|
615
|
+
this.notify();
|
|
616
|
+
return this.remoteDocumentAutosave;
|
|
617
|
+
}
|
|
467
618
|
/** Keeps the user's layers in a store, while one is used (see `useLayerStore`) */
|
|
468
619
|
get autosave() {
|
|
469
620
|
return this.layerAutosave;
|
|
@@ -945,6 +1096,15 @@ var PaintCanvas = class extends EventTarget {
|
|
|
945
1096
|
return authorHandle(this.presetAuthor()) ?? "";
|
|
946
1097
|
}
|
|
947
1098
|
/**
|
|
1099
|
+
* The author of assets the user makes, e.g. colour palettes (from the `presetAuthor` option).
|
|
1100
|
+
*
|
|
1101
|
+
* @example
|
|
1102
|
+
* app.palettes.create("Sunset", [], app.author);
|
|
1103
|
+
*/
|
|
1104
|
+
get author() {
|
|
1105
|
+
return this.presetAuthor();
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
948
1108
|
* Make a new custom preset from settings: a new ID, the given name, and the author from the `presetAuthor` option.
|
|
949
1109
|
*
|
|
950
1110
|
* @example
|
|
@@ -1153,8 +1313,13 @@ var PaintCanvas = class extends EventTarget {
|
|
|
1153
1313
|
/**
|
|
1154
1314
|
* Dispatch a `change` event.
|
|
1155
1315
|
*/
|
|
1316
|
+
/** Use the drawing's details and tell listeners */
|
|
1317
|
+
setDocumentInfo(info) {
|
|
1318
|
+
this.info = info;
|
|
1319
|
+
this.notify();
|
|
1320
|
+
}
|
|
1156
1321
|
notify() {
|
|
1157
1322
|
this.dispatchEvent(new Event("change"));
|
|
1158
1323
|
}
|
|
1159
1324
|
};
|
|
1160
|
-
export { DEFAULT_AUTHOR, DEFAULT_BACKGROUND_COLOUR, DEFAULT_CANVAS_SIZE, DEFAULT_COLOUR, DEFAULT_NAMES,
|
|
1325
|
+
export { DEFAULT_AUTHOR, DEFAULT_BACKGROUND_COLOUR, DEFAULT_CANVAS_SIZE, DEFAULT_COLOUR, DEFAULT_NAMES, MAX_CANVAS_SIZE, Panel, TOOL_PANELS, Tool, PaintCanvas as default, isHexColour };
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export declare const sanitisePalette: (input: unknown) => string[] | undefined;
|
|
10
10
|
/**
|
|
11
|
-
* The
|
|
12
|
-
*
|
|
11
|
+
* The colours of the palette PaintCanvas comes with (`canvas.palette`), shown as swatches the user can pick from but not
|
|
12
|
+
* change. Each colour is kept once, as lowercase `#rrggbb`, up to `MAX_PALETTE_COLOURS`. An implementer can give it other
|
|
13
|
+
* colours (see `set` and the `change` event); it isn't saved.
|
|
13
14
|
*
|
|
14
15
|
* Dispatches a `change` event when the colours change.
|
|
15
16
|
*
|
|
@@ -20,8 +20,9 @@ var sanitisePalette = (input) => {
|
|
|
20
20
|
return [...colours];
|
|
21
21
|
};
|
|
22
22
|
/**
|
|
23
|
-
* The
|
|
24
|
-
*
|
|
23
|
+
* The colours of the palette PaintCanvas comes with (`canvas.palette`), shown as swatches the user can pick from but not
|
|
24
|
+
* change. Each colour is kept once, as lowercase `#rrggbb`, up to `MAX_PALETTE_COLOURS`. An implementer can give it other
|
|
25
|
+
* colours (see `set` and the `change` event); it isn't saved.
|
|
25
26
|
*
|
|
26
27
|
* Dispatches a `change` event when the colours change.
|
|
27
28
|
*
|
|
@@ -23,8 +23,9 @@ export interface ProfileGraphDraft {
|
|
|
23
23
|
points: readonly ProfilePoint[];
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
|
-
* The user's own profile graphs, alongside the ones PaintCanvas comes with (`PROFILE_PRESETS`). Kept in memory;
|
|
27
|
-
*
|
|
26
|
+
* The user's own profile graphs, alongside the ones PaintCanvas comes with (`PROFILE_PRESETS`). Kept in memory; a
|
|
27
|
+
* profile store keeps them (see `PaintCanvas.useAssetStores`), or save and restore them yourself (see `set`, `merge` and
|
|
28
|
+
* the `change` event). Each gets a UUID when it's first saved.
|
|
28
29
|
*
|
|
29
30
|
* Dispatches a `change` event when the user's graphs change.
|
|
30
31
|
*
|
|
@@ -37,6 +38,8 @@ export default class ProfileGraphs extends EventTarget {
|
|
|
37
38
|
private values;
|
|
38
39
|
/** The user's own graphs, in the order they were first saved */
|
|
39
40
|
get graphs(): readonly ProfileGraph[];
|
|
41
|
+
/** The user's own graphs (the same as `graphs`, so a store can keep them, see `AssetSync`) */
|
|
42
|
+
get items(): readonly ProfileGraph[];
|
|
40
43
|
/** Every graph: the presets, then the user's own */
|
|
41
44
|
get all(): readonly ProfileGraph[];
|
|
42
45
|
/**
|
|
@@ -84,4 +87,12 @@ export default class ProfileGraphs extends EventTarget {
|
|
|
84
87
|
* profiles.set(settings.profiles);
|
|
85
88
|
*/
|
|
86
89
|
set(graphs: unknown): void;
|
|
90
|
+
/**
|
|
91
|
+
* Add graphs (e.g. loaded from a store or imported), replacing the user's graphs with the same IDs and keeping the
|
|
92
|
+
* others, up to `MAX_SAVED_PROFILES`. Invalid graphs are left out (see `sanitiseProfiles`).
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* profiles.merge(await store.list());
|
|
96
|
+
*/
|
|
97
|
+
merge(graphs: unknown): void;
|
|
87
98
|
}
|
|
@@ -45,8 +45,9 @@ var sanitiseProfiles = (input) => {
|
|
|
45
45
|
return [...graphs.values()];
|
|
46
46
|
};
|
|
47
47
|
/**
|
|
48
|
-
* The user's own profile graphs, alongside the ones PaintCanvas comes with (`PROFILE_PRESETS`). Kept in memory;
|
|
49
|
-
*
|
|
48
|
+
* The user's own profile graphs, alongside the ones PaintCanvas comes with (`PROFILE_PRESETS`). Kept in memory; a
|
|
49
|
+
* profile store keeps them (see `PaintCanvas.useAssetStores`), or save and restore them yourself (see `set`, `merge` and
|
|
50
|
+
* the `change` event). Each gets a UUID when it's first saved.
|
|
50
51
|
*
|
|
51
52
|
* Dispatches a `change` event when the user's graphs change.
|
|
52
53
|
*
|
|
@@ -61,6 +62,10 @@ var ProfileGraphs = class extends EventTarget {
|
|
|
61
62
|
get graphs() {
|
|
62
63
|
return this.values;
|
|
63
64
|
}
|
|
65
|
+
/** The user's own graphs (the same as `graphs`, so a store can keep them, see `AssetSync`) */
|
|
66
|
+
get items() {
|
|
67
|
+
return this.values;
|
|
68
|
+
}
|
|
64
69
|
/** Every graph: the presets, then the user's own */
|
|
65
70
|
get all() {
|
|
66
71
|
return [...PROFILE_PRESETS, ...this.values];
|
|
@@ -144,5 +149,20 @@ var ProfileGraphs = class extends EventTarget {
|
|
|
144
149
|
this.values = next;
|
|
145
150
|
this.dispatchEvent(new Event("change"));
|
|
146
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Add graphs (e.g. loaded from a store or imported), replacing the user's graphs with the same IDs and keeping the
|
|
154
|
+
* others, up to `MAX_SAVED_PROFILES`. Invalid graphs are left out (see `sanitiseProfiles`).
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* profiles.merge(await store.list());
|
|
158
|
+
*/
|
|
159
|
+
merge(graphs) {
|
|
160
|
+
const added = sanitiseProfiles(graphs);
|
|
161
|
+
if (!added?.length) return;
|
|
162
|
+
const byId = new Map(this.values.map((graph) => [graph.id, graph]));
|
|
163
|
+
for (const graph of added) byId.set(graph.id, graph);
|
|
164
|
+
this.values = [...byId.values()].slice(0, 100);
|
|
165
|
+
this.dispatchEvent(new Event("change"));
|
|
166
|
+
}
|
|
147
167
|
};
|
|
148
168
|
export { ProfileGraphs as default, sanitisePoints, sanitiseProfiles };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { DocumentStoreLike } from "../../storage";
|
|
2
|
+
import type { DrawingDocument } from "../../document/schema";
|
|
3
|
+
/** How often changes are saved to the remote store, at most (milliseconds): every 5 minutes */
|
|
4
|
+
export declare const REMOTE_SAVE_INTERVAL_MS: number;
|
|
5
|
+
/** Event dispatched when saving fails (detail: the error) */
|
|
6
|
+
export declare const REMOTE_AUTOSAVE_ERROR_EVENT = "error";
|
|
7
|
+
/** The canvas whose drawing is saved (`PaintCanvas`) */
|
|
8
|
+
export interface RemoteAutosaveCanvas extends EventTarget {
|
|
9
|
+
/** The user's layers (replaced when the canvas size changes, followed by a `change` event) */
|
|
10
|
+
readonly layers: EventTarget;
|
|
11
|
+
/** The drawing as a document */
|
|
12
|
+
createDocument(): Promise<DrawingDocument>;
|
|
13
|
+
}
|
|
14
|
+
/** Options for `RemoteDocumentAutosave` */
|
|
15
|
+
export interface RemoteAutosaveOptions {
|
|
16
|
+
/** How often changes are saved, at most (default `REMOTE_SAVE_INTERVAL_MS`) */
|
|
17
|
+
intervalMs?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Keeps a canvas's drawing in a remote store (e.g. a user's account online), less often than the browser keeps it:
|
|
21
|
+
* every `intervalMs` while there are changes not saved yet, or when asked (`save`). The whole document is saved each
|
|
22
|
+
* time, with a thumbnail.
|
|
23
|
+
*
|
|
24
|
+
* Dispatches `change` when saving starts, makes progress or ends, and `error` (detail: the error) when it fails (the
|
|
25
|
+
* changes are tried again next time).
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* const autosave = new RemoteDocumentAutosave(canvas, myAccountStore);
|
|
29
|
+
* autosave.start();
|
|
30
|
+
* saveButton.onclick = () => autosave.save();
|
|
31
|
+
*/
|
|
32
|
+
export default class RemoteDocumentAutosave extends EventTarget {
|
|
33
|
+
/** Where the drawing is kept */
|
|
34
|
+
readonly store: DocumentStoreLike;
|
|
35
|
+
/** The last saving error, until a save succeeds */
|
|
36
|
+
error?: unknown;
|
|
37
|
+
/** When the drawing was last saved, if it has been */
|
|
38
|
+
lastSaved?: Date;
|
|
39
|
+
/** How much of the save under way is done (0 to 1), if known */
|
|
40
|
+
progress?: number;
|
|
41
|
+
private readonly canvas;
|
|
42
|
+
private readonly intervalMs;
|
|
43
|
+
private timer?;
|
|
44
|
+
/** The layers being watched for changes */
|
|
45
|
+
private watched?;
|
|
46
|
+
/** Whether there are changes not saved yet */
|
|
47
|
+
private dirty;
|
|
48
|
+
/** The save being written, if any */
|
|
49
|
+
private current?;
|
|
50
|
+
private readonly onChange;
|
|
51
|
+
private readonly onCanvasChange;
|
|
52
|
+
constructor(canvas: RemoteAutosaveCanvas, store: DocumentStoreLike, options?: RemoteAutosaveOptions);
|
|
53
|
+
/** Whether a save is under way */
|
|
54
|
+
get saving(): boolean;
|
|
55
|
+
/** Whether there are changes not saved yet */
|
|
56
|
+
get hasChanges(): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Start saving changes every `intervalMs`.
|
|
59
|
+
*/
|
|
60
|
+
start(): void;
|
|
61
|
+
/**
|
|
62
|
+
* Stop saving changes (a save under way finishes). Changes not saved yet aren't saved.
|
|
63
|
+
*/
|
|
64
|
+
stop(): void;
|
|
65
|
+
/**
|
|
66
|
+
* Save the drawing now (e.g. when the user asks). While a save is under way, that save is returned.
|
|
67
|
+
*/
|
|
68
|
+
save(): Promise<void>;
|
|
69
|
+
/** Save the whole drawing */
|
|
70
|
+
private write;
|
|
71
|
+
/** Tell listeners something changed */
|
|
72
|
+
private notify;
|
|
73
|
+
}
|