@lvl99dotcom/paintcanvas 0.7.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 +1 -0
- 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 +111 -23
- package/dist/lib/classes/PaintCanvas/index.js +219 -58
- 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/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 +198 -76
- package/dist/lib/settings/schema.js +92 -86
- package/dist/lib/storage/index.d.ts +82 -3
- package/dist/lib/storage/index.js +82 -5
- package/dist/lib/validation/validators.generated.d.ts +19 -8
- package/dist/lib/validation/validators.generated.js +44716 -18900
- package/dist/lib/version/index.js +1 -1
- package/dist/locales/en-GB.js +23 -5
- package/dist/locales/en-US.js +23 -5
- package/dist/locales/fr.js +23 -5
- package/dist/locales/ja.js +23 -5
- package/dist/locales/ko.js +23 -5
- package/dist/locales/zh-Hans.js +23 -5
- package/dist/locales/zh-Hant.js +23 -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 +6 -2
- package/dist/ui/elements/PaintCanvas/index.js +20 -6
- package/dist/ui/elements/PcAppSettings/index.d.ts +5 -6
- package/dist/ui/elements/PcAppSettings/index.js +17 -27
- 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/PcStats/index.d.ts +8 -4
- package/dist/ui/elements/PcStats/index.js +39 -5
- package/locales/en-GB.json +23 -5
- package/locales/en-US.json +23 -5
- package/locales/fr.json +23 -5
- package/locales/ja.json +23 -5
- package/locales/ko.json +23 -5
- package/locales/zh-Hans.json +23 -5
- package/locales/zh-Hant.json +23 -5
- package/package.json +2 -1
|
@@ -14,18 +14,24 @@ import Lasso, { type LassoResult } from "../../selection/Lasso";
|
|
|
14
14
|
import Transformer from "../../transform/Transformer";
|
|
15
15
|
import { type CanvasFactory, type LayerConfig } from "../Layer";
|
|
16
16
|
import LayerAutosave, { type AutosaveOptions } from "../LayerAutosave";
|
|
17
|
-
import type { LayerStoreLike } from "../../storage";
|
|
17
|
+
import type { AssetStores, DocumentStoreLike, LayerStoreLike } from "../../storage";
|
|
18
|
+
import RemoteDocumentAutosave, { type RemoteAutosaveOptions } from "../RemoteDocumentAutosave";
|
|
19
|
+
import type { ProfileGraph } from "../../profiles";
|
|
18
20
|
import type { AssetAuthor, BrushConfig } from "../../brushes/types";
|
|
19
|
-
import CustomPresets
|
|
21
|
+
import CustomPresets from "../CustomPresets";
|
|
22
|
+
import AssetSync from "../AssetSync";
|
|
23
|
+
import ColourPalettes from "../ColourPalettes";
|
|
24
|
+
import type { ColourPalette } from "../../palette";
|
|
20
25
|
import InputSettings from "../InputSettings";
|
|
21
26
|
import UiSettings, { type Rendering } from "../UiSettings";
|
|
22
27
|
import Palette from "../Palette";
|
|
23
28
|
import ProfileGraphs from "../ProfileGraphs";
|
|
24
29
|
import ShortcutSettings from "../ShortcutSettings";
|
|
30
|
+
import SettingsExtensions from "../SettingsExtensions";
|
|
25
31
|
import ExternalSheets from "../ExternalSheets";
|
|
26
32
|
import { type CanvasMessage, type PatchMessage, type StrokeMessage } from "../../messages";
|
|
27
33
|
import { type BoundingBox } from "../../image";
|
|
28
|
-
import { type
|
|
34
|
+
import { type DocumentFile, type DocumentInfo, type DrawingDocument } from "../../document";
|
|
29
35
|
export { Tool };
|
|
30
36
|
/** How the colour sample tool samples: the pixel under the pointer, or the average of a dragged rectangle */
|
|
31
37
|
export type SampleMode = "point" | "area";
|
|
@@ -54,8 +60,6 @@ export declare const DEFAULT_BACKGROUND_COLOUR = "#ffffff";
|
|
|
54
60
|
* (painted by the eraser and Delete when the layer's alpha lock is on, and by background fills).
|
|
55
61
|
*/
|
|
56
62
|
export type ColourSlot = "foreground" | "background";
|
|
57
|
-
/** Owner of the custom presets when the `ownerId` option isn't given */
|
|
58
|
-
export declare const DEFAULT_OWNER_ID = "local";
|
|
59
63
|
/** Author name of presets made with `newPreset` when the `presetAuthor` option isn't given */
|
|
60
64
|
export declare const DEFAULT_AUTHOR = "Unknown";
|
|
61
65
|
/** Names the implementer can change: what users see and what files are called */
|
|
@@ -64,12 +68,6 @@ export interface PaintCanvasNames {
|
|
|
64
68
|
app: string;
|
|
65
69
|
/** Start of the names of saved files, e.g. screenshots and exported presets */
|
|
66
70
|
filePrefix: string;
|
|
67
|
-
/** The `format` written in exported brush preset files */
|
|
68
|
-
presetFileFormat: string;
|
|
69
|
-
/** The `format` written in exported colour palette files */
|
|
70
|
-
paletteFileFormat: string;
|
|
71
|
-
/** The `format` written in exported settings files */
|
|
72
|
-
settingsFileFormat: string;
|
|
73
71
|
/** Author name of presets made by a user without a handle, e.g. from `presetAuthor` */
|
|
74
72
|
unknownAuthor: string;
|
|
75
73
|
}
|
|
@@ -93,6 +91,11 @@ export interface CanvasStats {
|
|
|
93
91
|
/** Most undo steps kept */
|
|
94
92
|
historyLimit: number;
|
|
95
93
|
}
|
|
94
|
+
/** Options for `PaintCanvas.createDocument` */
|
|
95
|
+
export interface CreateDocumentOptions {
|
|
96
|
+
/** Whether to include a thumbnail (default true) */
|
|
97
|
+
thumbnail?: boolean;
|
|
98
|
+
}
|
|
96
99
|
/** Receives the drawing messages PaintCanvas sends; a returned promise that rejects means a patch wasn't delivered */
|
|
97
100
|
export type MessageListener = (message: CanvasMessage) => void | Promise<void>;
|
|
98
101
|
/** Options for sending drawing messages (see `startSending`) */
|
|
@@ -127,16 +130,16 @@ export interface PaintCanvasOptions {
|
|
|
127
130
|
* given (replaceable for tests).
|
|
128
131
|
*/
|
|
129
132
|
createCanvas?: CanvasFactory;
|
|
130
|
-
/** Where
|
|
131
|
-
|
|
132
|
-
/** Who the custom presets belong to, passed to the preset store (default `DEFAULT_OWNER_ID`) */
|
|
133
|
-
ownerId?: string;
|
|
133
|
+
/** Where the user's assets (brush presets, colour palettes, profile graphs) are kept (see `useAssetStores`) */
|
|
134
|
+
stores?: AssetStores;
|
|
134
135
|
/** Gives the author of presets made with `newPreset` (asked each time, so it can follow name changes) */
|
|
135
136
|
presetAuthor?: () => AssetAuthor;
|
|
136
137
|
/** Names to use instead of PaintCanvas's own (see `DEFAULT_NAMES`) */
|
|
137
138
|
names?: Partial<PaintCanvasNames>;
|
|
138
139
|
/** Where to keep the user's layers so the drawing survives reloading the page (see `useLayerStore`) */
|
|
139
140
|
layerStore?: LayerStoreLike;
|
|
141
|
+
/** Where to keep the whole drawing away from the browser, saved less often (see `useRemoteDocumentStore`) */
|
|
142
|
+
remoteDocumentStore?: DocumentStoreLike;
|
|
140
143
|
/**
|
|
141
144
|
* The system clipboard, which copying and cutting also put a PNG of the copied image data on so other apps (and brush
|
|
142
145
|
* shapes) can use it (the browser's by default; `null` for none)
|
|
@@ -184,6 +187,20 @@ export default class PaintCanvas extends EventTarget {
|
|
|
184
187
|
readonly palette: Palette;
|
|
185
188
|
/** The user's own profile graphs (and the presets), for shaping how brush settings vary */
|
|
186
189
|
readonly profiles: ProfileGraphs;
|
|
190
|
+
/** The drawing's details (see `documentInfo`) */
|
|
191
|
+
private info;
|
|
192
|
+
/** Keeps the drawing in a remote store */
|
|
193
|
+
private remoteDocumentAutosave?;
|
|
194
|
+
/** Settings wrapper apps add to the app settings (see `SettingsExtension`) */
|
|
195
|
+
readonly settingsExtensions: SettingsExtensions;
|
|
196
|
+
/** The user's own colour palettes (the one PaintCanvas comes with is `palette`) */
|
|
197
|
+
readonly palettes: ColourPalettes;
|
|
198
|
+
/** Where the user's assets are kept */
|
|
199
|
+
private stores;
|
|
200
|
+
/** Keeps the colour palettes in their store */
|
|
201
|
+
private palettesAutosave?;
|
|
202
|
+
/** Keeps the profile graphs in their store */
|
|
203
|
+
private profilesAutosave?;
|
|
187
204
|
/** The user's keyboard shortcuts: each configurable action's key combination */
|
|
188
205
|
readonly shortcuts: ShortcutSettings;
|
|
189
206
|
/** The user's pointer position over the canvas, in canvas pixels (undefined when off the canvas) */
|
|
@@ -244,7 +261,6 @@ export default class PaintCanvas extends EventTarget {
|
|
|
244
261
|
/** Creates the canvases the app draws on (fake ones in tests) */
|
|
245
262
|
readonly createCanvas: CanvasFactory;
|
|
246
263
|
/** Who the custom presets belong to (passed to the preset store) */
|
|
247
|
-
readonly ownerId: string;
|
|
248
264
|
/** The largest canvas width or height allowed */
|
|
249
265
|
readonly maxCanvasSize: number;
|
|
250
266
|
/** Most layers the user can have */
|
|
@@ -316,23 +332,60 @@ export default class PaintCanvas extends EventTarget {
|
|
|
316
332
|
* transport.onMessage((from, message) => canvas.receive(from, message));
|
|
317
333
|
*/
|
|
318
334
|
receive(source: string, message: unknown): Promise<boolean>;
|
|
335
|
+
/** The drawing's details: its ID, name, authors and when it was made and last opened */
|
|
336
|
+
get documentInfo(): Readonly<DocumentInfo>;
|
|
337
|
+
/**
|
|
338
|
+
* Rename the drawing. Returns whether it was renamed (not if the name is empty).
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* canvas.renameDocument("Sunset at sea");
|
|
342
|
+
*/
|
|
343
|
+
renameDocument(name: string): boolean;
|
|
344
|
+
/**
|
|
345
|
+
* Use details of a drawing being restored (e.g. kept in the browser), marking it opened now.
|
|
346
|
+
*/
|
|
347
|
+
restoreDocumentInfo(info: DocumentInfo): void;
|
|
348
|
+
/**
|
|
349
|
+
* Start a new drawing: new details (a new ID, the user as its author, made now), and the layers replaced with one
|
|
350
|
+
* empty layer. Undo history starts again.
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* canvas.newDocument("Sketch");
|
|
354
|
+
*/
|
|
355
|
+
newDocument(name?: string): void;
|
|
356
|
+
/**
|
|
357
|
+
* The drawing as a document (see `DrawingDocument`): its details, size, the user's layers and drawings from elsewhere
|
|
358
|
+
* as PNG blobs, and a thumbnail (at most `THUMBNAIL_SIZE` pixels on its longest side) unless left out. The user is
|
|
359
|
+
* added to its authors.
|
|
360
|
+
*
|
|
361
|
+
* @example
|
|
362
|
+
* await myStore.save(await canvas.createDocument());
|
|
363
|
+
*/
|
|
364
|
+
createDocument({ thumbnail, }?: CreateDocumentOptions): Promise<DrawingDocument>;
|
|
319
365
|
/**
|
|
320
|
-
*
|
|
321
|
-
*
|
|
366
|
+
* Replace the drawing with a document (e.g. from a store): its details (marked opened now), size, layers and drawings
|
|
367
|
+
* from elsewhere. Undo history starts again. Returns whether it was opened: not when it has too many layers, is too
|
|
368
|
+
* big, doesn't have its selected layer, or an image can't be decoded, and then nothing changes.
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* await canvas.openDocument(await myStore.load(id));
|
|
372
|
+
*/
|
|
373
|
+
openDocument(document: DrawingDocument): Promise<boolean>;
|
|
374
|
+
/**
|
|
375
|
+
* The drawing as a document file (see `DocumentFile`): turn it into JSON to save it as a file.
|
|
322
376
|
*
|
|
323
377
|
* @example
|
|
324
378
|
* const json = JSON.stringify(await canvas.exportDocument());
|
|
325
379
|
*/
|
|
326
|
-
exportDocument(): Promise<
|
|
380
|
+
exportDocument(): Promise<DocumentFile>;
|
|
327
381
|
/**
|
|
328
|
-
* Replace the drawing with a document (e.g. read from a file
|
|
329
|
-
*
|
|
330
|
-
* decoded, and then nothing changes.
|
|
382
|
+
* Replace the drawing with a document file (e.g. read from a file, see `documentFromFile`). Returns whether it was
|
|
383
|
+
* opened: not when it isn't a valid document or can't be opened (see `openDocument`), and then nothing changes.
|
|
331
384
|
*
|
|
332
385
|
* @example
|
|
333
386
|
* await canvas.importDocument(JSON.parse(text));
|
|
334
387
|
*/
|
|
335
|
-
importDocument(
|
|
388
|
+
importDocument(file: unknown): Promise<boolean>;
|
|
336
389
|
/**
|
|
337
390
|
* Replace the canvas size and the user's layers (top first, each with its image) and select one by ID. Undo history
|
|
338
391
|
* starts again. Throws if the size or number of layers isn't allowed.
|
|
@@ -341,6 +394,32 @@ export default class PaintCanvas extends EventTarget {
|
|
|
341
394
|
* canvas.restoreLayers(800, 600, [{ id: "a", name: "Sketch" }], [sketchImage], "a");
|
|
342
395
|
*/
|
|
343
396
|
restoreLayers(width: number, height: number, layers: readonly LayerConfig[], images: readonly CanvasImageSource[], activeLayer: string): void;
|
|
397
|
+
/** Where the user's assets are kept (see `useAssetStores`) */
|
|
398
|
+
get assetStores(): Readonly<AssetStores>;
|
|
399
|
+
/** Keeps the user's profile graphs in their store, while one is used (see `useAssetStores`) */
|
|
400
|
+
get profileSync(): AssetSync<ProfileGraph> | undefined;
|
|
401
|
+
/** Keeps the user's colour palettes in their store, while one is used (see `useAssetStores`) */
|
|
402
|
+
get paletteSync(): AssetSync<ColourPalette> | undefined;
|
|
403
|
+
/**
|
|
404
|
+
* Keep the user's assets in stores, by kind: custom brush presets are loaded from theirs (in place of the current
|
|
405
|
+
* ones) and saved when they change; colour palettes and profile graphs saved there are added and changes saved (see
|
|
406
|
+
* `AssetSync`). Assets of a kind without a store are kept in memory only.
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* canvas.useAssetStores({ brushes: new IndexedDbAssetStore("brushes"), profiles: new IndexedDbAssetStore("profiles") });
|
|
410
|
+
*/
|
|
411
|
+
useAssetStores(stores: AssetStores): void;
|
|
412
|
+
/** Keeps the drawing in a remote store, while one is used (see `useRemoteDocumentStore`) */
|
|
413
|
+
get remoteAutosave(): RemoteDocumentAutosave | undefined;
|
|
414
|
+
/**
|
|
415
|
+
* Keep the whole drawing in a remote store (e.g. the user's account online), or stop, without one: changes are saved
|
|
416
|
+
* every few minutes (see `RemoteDocumentAutosave`), or when asked with `canvas.remoteAutosave.save()`. Returns the
|
|
417
|
+
* autosave.
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* canvas.useRemoteDocumentStore(myAccountStore, { intervalMs: 10 * 60 * 1000 });
|
|
421
|
+
*/
|
|
422
|
+
useRemoteDocumentStore(store: DocumentStoreLike | undefined, options?: RemoteAutosaveOptions): RemoteDocumentAutosave | undefined;
|
|
344
423
|
/** Keeps the user's layers in a store, while one is used (see `useLayerStore`) */
|
|
345
424
|
get autosave(): LayerAutosave | undefined;
|
|
346
425
|
/**
|
|
@@ -561,6 +640,13 @@ export default class PaintCanvas extends EventTarget {
|
|
|
561
640
|
* own presets are the ones with this handle (see `isOwnAuthor`).
|
|
562
641
|
*/
|
|
563
642
|
get authorHandle(): string;
|
|
643
|
+
/**
|
|
644
|
+
* The author of assets the user makes, e.g. colour palettes (from the `presetAuthor` option).
|
|
645
|
+
*
|
|
646
|
+
* @example
|
|
647
|
+
* app.palettes.create("Sunset", [], app.author);
|
|
648
|
+
*/
|
|
649
|
+
get author(): AssetAuthor;
|
|
564
650
|
/**
|
|
565
651
|
* Make a new custom preset from settings: a new ID, the given name, and the author from the `presetAuthor` option.
|
|
566
652
|
*
|
|
@@ -660,5 +746,7 @@ export default class PaintCanvas extends EventTarget {
|
|
|
660
746
|
/**
|
|
661
747
|
* Dispatch a `change` event.
|
|
662
748
|
*/
|
|
749
|
+
/** Use the drawing's details and tell listeners */
|
|
750
|
+
private setDocumentInfo;
|
|
663
751
|
protected notify(): void;
|
|
664
752
|
}
|
|
@@ -13,28 +13,27 @@ import Transformer, { applyMask, contentBounds, copyArea, drawTransformed } from
|
|
|
13
13
|
import { boxBounds } from "../../transform/geometry/index.js";
|
|
14
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
19
|
import CustomPresets from "../CustomPresets/index.js";
|
|
20
|
-
import
|
|
20
|
+
import AssetSync from "../AssetSync/index.js";
|
|
21
|
+
import ColourPalettes from "../ColourPalettes/index.js";
|
|
21
22
|
import InputSettings from "../InputSettings/index.js";
|
|
22
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) => {
|
|
@@ -133,6 +122,20 @@ var PaintCanvas = class extends EventTarget {
|
|
|
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) */
|
|
@@ -217,7 +220,6 @@ var PaintCanvas = class extends EventTarget {
|
|
|
217
220
|
/** Creates the canvases the app draws on (fake ones in tests) */
|
|
218
221
|
createCanvas;
|
|
219
222
|
/** Who the custom presets belong to (passed to the preset store) */
|
|
220
|
-
ownerId;
|
|
221
223
|
/** The largest canvas width or height allowed */
|
|
222
224
|
maxCanvasSize;
|
|
223
225
|
/** Most layers the user can have */
|
|
@@ -231,7 +233,6 @@ var PaintCanvas = class extends EventTarget {
|
|
|
231
233
|
constructor(options = {}) {
|
|
232
234
|
super();
|
|
233
235
|
this.strokeLog.enabled = false;
|
|
234
|
-
this.ownerId = options.ownerId ?? "local";
|
|
235
236
|
this.maxCanvasSize = options.maxCanvasSize ?? 2e3;
|
|
236
237
|
this.maxLayers = options.maxLayers ?? 5;
|
|
237
238
|
this.names = {
|
|
@@ -239,11 +240,12 @@ var PaintCanvas = class extends EventTarget {
|
|
|
239
240
|
...options.names
|
|
240
241
|
};
|
|
241
242
|
this.presetAuthor = options.presetAuthor ?? (() => ({ name: DEFAULT_AUTHOR }));
|
|
243
|
+
this.info = newDocumentInfo(this.author);
|
|
242
244
|
this.tips.preload(this.brush.presets);
|
|
243
245
|
this.tips.preload(this.eraser.presets);
|
|
244
|
-
this.customPresets = new CustomPresets(
|
|
246
|
+
this.customPresets = new CustomPresets();
|
|
245
247
|
this.customPresets.addEventListener("change", () => this.listCustomPresets());
|
|
246
|
-
this.
|
|
248
|
+
this.useAssetStores(options.stores ?? {});
|
|
247
249
|
this.rendering = options.rendering ?? DEFAULT_UI_SETTINGS.rendering;
|
|
248
250
|
this.ui = new UiSettings({ rendering: this.rendering });
|
|
249
251
|
this.createCanvas = options.createCanvas ?? (this.rendering === "gpu" ? createOffscreenCanvas : createMemoryCanvas);
|
|
@@ -259,6 +261,7 @@ var PaintCanvas = class extends EventTarget {
|
|
|
259
261
|
this.watchLayersAndMask();
|
|
260
262
|
if (options.width !== void 0 || options.height !== void 0) this.setCanvasSize(options.width ?? this.canvasWidth, options.height ?? this.canvasHeight);
|
|
261
263
|
if (options.layerStore) this.useLayerStore(options.layerStore);
|
|
264
|
+
if (options.remoteDocumentStore) this.useRemoteDocumentStore(options.remoteDocumentStore);
|
|
262
265
|
}
|
|
263
266
|
/** Canvas width in pixels */
|
|
264
267
|
get width() {
|
|
@@ -396,65 +399,161 @@ var PaintCanvas = class extends EventTarget {
|
|
|
396
399
|
} }));
|
|
397
400
|
return true;
|
|
398
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
|
+
}
|
|
399
406
|
/**
|
|
400
|
-
*
|
|
401
|
-
* `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).
|
|
402
408
|
*
|
|
403
409
|
* @example
|
|
404
|
-
*
|
|
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.
|
|
405
423
|
*/
|
|
406
|
-
|
|
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));
|
|
407
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
|
+
]);
|
|
408
471
|
return {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
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();
|
|
415
477
|
return {
|
|
478
|
+
id,
|
|
416
479
|
name,
|
|
417
480
|
visible,
|
|
418
481
|
opacity,
|
|
419
|
-
blendMode,
|
|
482
|
+
blendingMode: blendMode,
|
|
420
483
|
alphaLock,
|
|
421
|
-
|
|
484
|
+
content: layerImages[i]
|
|
422
485
|
};
|
|
423
|
-
})
|
|
424
|
-
activeLayer:
|
|
425
|
-
external:
|
|
486
|
+
}),
|
|
487
|
+
activeLayer: this.layerStack.active.id,
|
|
488
|
+
external: this.externalSheets.sheets.map(({ source }, i) => ({
|
|
426
489
|
source,
|
|
427
|
-
|
|
428
|
-
}))
|
|
490
|
+
content: sheetImages[i]
|
|
491
|
+
})),
|
|
492
|
+
...thumbnailImage ? { thumbnail: {
|
|
493
|
+
width: thumbnailWidth,
|
|
494
|
+
height: thumbnailHeight,
|
|
495
|
+
content: thumbnailImage
|
|
496
|
+
} } : {}
|
|
429
497
|
};
|
|
430
498
|
}
|
|
431
499
|
/**
|
|
432
|
-
* Replace the drawing with a document (e.g.
|
|
433
|
-
* history starts again. Returns whether it was
|
|
434
|
-
* 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.
|
|
435
503
|
*
|
|
436
504
|
* @example
|
|
437
|
-
* await canvas.
|
|
505
|
+
* await canvas.openDocument(await myStore.load(id));
|
|
438
506
|
*/
|
|
439
|
-
async
|
|
440
|
-
const
|
|
441
|
-
if (
|
|
442
|
-
const
|
|
443
|
-
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);
|
|
444
511
|
if (!images) return false;
|
|
445
|
-
|
|
446
|
-
id
|
|
512
|
+
this.restoreLayers(width, height, layers.map(({ id, name, visible, opacity, blendingMode, alphaLock }) => ({
|
|
513
|
+
id,
|
|
447
514
|
name,
|
|
448
515
|
visible,
|
|
449
516
|
opacity,
|
|
450
|
-
blendMode,
|
|
517
|
+
blendMode: blendingMode,
|
|
451
518
|
alphaLock
|
|
452
|
-
}));
|
|
453
|
-
|
|
454
|
-
|
|
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
|
+
});
|
|
455
529
|
return true;
|
|
456
530
|
}
|
|
457
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
|
+
/**
|
|
458
557
|
* Replace the canvas size and the user's layers (top first, each with its image) and select one by ID. Undo history
|
|
459
558
|
* starts again. Throws if the size or number of layers isn't allowed.
|
|
460
559
|
*
|
|
@@ -468,6 +567,54 @@ var PaintCanvas = class extends EventTarget {
|
|
|
468
567
|
this.layerStack.select(activeLayer);
|
|
469
568
|
this.history.clear();
|
|
470
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
|
+
}
|
|
471
618
|
/** Keeps the user's layers in a store, while one is used (see `useLayerStore`) */
|
|
472
619
|
get autosave() {
|
|
473
620
|
return this.layerAutosave;
|
|
@@ -949,6 +1096,15 @@ var PaintCanvas = class extends EventTarget {
|
|
|
949
1096
|
return authorHandle(this.presetAuthor()) ?? "";
|
|
950
1097
|
}
|
|
951
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
|
+
/**
|
|
952
1108
|
* Make a new custom preset from settings: a new ID, the given name, and the author from the `presetAuthor` option.
|
|
953
1109
|
*
|
|
954
1110
|
* @example
|
|
@@ -1157,8 +1313,13 @@ var PaintCanvas = class extends EventTarget {
|
|
|
1157
1313
|
/**
|
|
1158
1314
|
* Dispatch a `change` event.
|
|
1159
1315
|
*/
|
|
1316
|
+
/** Use the drawing's details and tell listeners */
|
|
1317
|
+
setDocumentInfo(info) {
|
|
1318
|
+
this.info = info;
|
|
1319
|
+
this.notify();
|
|
1320
|
+
}
|
|
1160
1321
|
notify() {
|
|
1161
1322
|
this.dispatchEvent(new Event("change"));
|
|
1162
1323
|
}
|
|
1163
1324
|
};
|
|
1164
|
-
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
|
*
|