@lvl99dotcom/paintcanvas 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -15
- package/dist/lib/brushes/configRanges/index.d.ts +17 -0
- package/dist/lib/brushes/configRanges/index.js +31 -1
- package/dist/lib/brushes/params/index.d.ts +2 -1
- package/dist/lib/brushes/params/index.js +7 -4
- package/dist/lib/brushes/presets/round/index.d.ts +1 -1
- package/dist/lib/brushes/presets/round/index.js +12 -7
- package/dist/lib/brushes/presets/square/index.d.ts +1 -1
- package/dist/lib/brushes/presets/square/index.js +7 -3
- package/dist/lib/brushes/shapeImage/index.d.ts +7 -3
- package/dist/lib/brushes/shapeImage/index.js +15 -9
- package/dist/lib/brushes/shapes/index.d.ts +18 -0
- package/dist/lib/brushes/shapes/index.js +86 -0
- package/dist/lib/brushes/types/index.d.ts +8 -0
- package/dist/lib/classes/BrushSettings/index.d.ts +29 -2
- package/dist/lib/classes/BrushSettings/index.js +51 -14
- package/dist/lib/classes/KeyboardShortcuts/index.d.ts +31 -2
- package/dist/lib/classes/KeyboardShortcuts/index.js +66 -4
- package/dist/lib/classes/Layer/index.d.ts +10 -0
- package/dist/lib/classes/Layer/index.js +15 -1
- package/dist/lib/classes/LayerAutosave/index.d.ts +9 -15
- package/dist/lib/classes/LayerAutosave/index.js +30 -43
- package/dist/lib/classes/LayerStack/index.d.ts +15 -0
- package/dist/lib/classes/LayerStack/index.js +26 -2
- package/dist/lib/classes/PaintCanvas/index.d.ts +72 -3
- package/dist/lib/classes/PaintCanvas/index.js +106 -12
- package/dist/lib/classes/PngEncoder/index.d.ts +41 -0
- package/dist/lib/classes/PngEncoder/index.js +104 -0
- package/dist/lib/classes/ProfileGraphs/index.d.ts +87 -0
- package/dist/lib/classes/ProfileGraphs/index.js +148 -0
- package/dist/lib/classes/SettingsAutosave/index.d.ts +3 -2
- package/dist/lib/classes/SettingsAutosave/index.js +6 -3
- package/dist/lib/classes/UiSettings/index.d.ts +68 -0
- package/dist/lib/classes/UiSettings/index.js +82 -0
- package/dist/lib/messages/schema.d.ts +720 -0
- package/dist/lib/presets/schema.d.ts +360 -0
- package/dist/lib/presets/schema.js +8 -1
- package/dist/lib/profiles/index.d.ts +61 -0
- package/dist/lib/profiles/index.js +131 -0
- package/dist/lib/profiles/schema.d.ts +73 -0
- package/dist/lib/profiles/schema.js +53 -0
- package/dist/lib/rendering/sample/index.d.ts +11 -0
- package/dist/lib/rendering/sample/index.js +36 -5
- package/dist/lib/settings/index.d.ts +2 -1
- package/dist/lib/settings/index.js +7 -2
- package/dist/lib/settings/schema.d.ts +67 -0
- package/dist/lib/settings/schema.js +20 -0
- package/dist/lib/validation/validators.generated.js +13191 -3655
- package/dist/locales/en-GB.js +39 -2
- package/dist/locales/en-US.js +39 -2
- package/dist/locales/fr.js +39 -2
- package/dist/locales/ja.js +39 -2
- package/dist/locales/ko.js +39 -2
- package/dist/locales/zh-Hans.js +39 -2
- package/dist/locales/zh-Hant.js +39 -2
- package/dist/package.js +1 -1
- package/dist/testing/recordingCanvas.js +2 -1
- package/dist/ui/ask/index.d.ts +48 -0
- package/dist/ui/ask/index.js +67 -0
- package/dist/ui/base/index.d.ts +10 -3
- package/dist/ui/base/index.js +19 -4
- package/dist/ui/dismiss/index.d.ts +21 -0
- package/dist/ui/dismiss/index.js +27 -0
- package/dist/ui/drag/index.d.ts +51 -0
- package/dist/ui/drag/index.js +81 -0
- package/dist/ui/elements/PaintCanvas/index.d.ts +17 -12
- package/dist/ui/elements/PaintCanvas/index.js +36 -17
- package/dist/ui/elements/PaintCanvas/style.js +1 -1
- package/dist/ui/elements/PcAppSettings/index.d.ts +6 -6
- package/dist/ui/elements/PcAppSettings/index.js +21 -65
- package/dist/ui/elements/PcBrushConfig/index.d.ts +34 -16
- package/dist/ui/elements/PcBrushConfig/index.js +263 -153
- package/dist/ui/elements/PcBrushConfig/style.js +1 -1
- package/dist/ui/elements/PcBrushSize/index.d.ts +53 -0
- package/dist/ui/elements/PcBrushSize/index.js +66 -0
- package/dist/ui/elements/PcBrushTest/index.d.ts +26 -0
- package/dist/ui/elements/PcBrushTest/index.js +79 -6
- package/dist/ui/elements/PcBrushTest/style.js +1 -1
- package/dist/ui/elements/PcButton/index.d.ts +12 -3
- package/dist/ui/elements/PcButton/index.js +18 -10
- package/dist/ui/elements/PcCanvas/index.d.ts +27 -2
- package/dist/ui/elements/PcCanvas/index.js +63 -13
- package/dist/ui/elements/PcCanvas/style.js +1 -1
- package/dist/ui/elements/PcCanvasMap/style.js +1 -1
- package/dist/ui/elements/PcClipboardTools/index.d.ts +49 -0
- package/dist/ui/elements/PcClipboardTools/index.js +125 -0
- package/dist/ui/elements/PcContextMenu/index.d.ts +2 -2
- package/dist/ui/elements/PcDialog/index.d.ts +46 -10
- package/dist/ui/elements/PcDialog/index.js +102 -51
- package/dist/ui/elements/PcDialogWindow/index.d.ts +141 -0
- package/dist/ui/elements/PcDialogWindow/index.js +385 -0
- package/dist/ui/elements/PcInputSettings/index.d.ts +13 -0
- package/dist/ui/elements/PcInputSettings/index.js +16 -1
- package/dist/ui/elements/PcMenu/index.d.ts +48 -3
- package/dist/ui/elements/PcMenu/index.js +111 -19
- package/dist/ui/elements/PcNumberSlider/index.d.ts +137 -0
- package/dist/ui/elements/PcNumberSlider/index.js +247 -0
- package/dist/ui/elements/PcPane/index.d.ts +3 -1
- package/dist/ui/elements/PcPane/index.js +44 -7
- package/dist/ui/elements/PcPanel/index.d.ts +2 -2
- package/dist/ui/elements/PcPanel/index.js +8 -8
- package/dist/ui/elements/PcPanelBrushes/index.d.ts +5 -17
- package/dist/ui/elements/PcPanelBrushes/index.js +13 -48
- package/dist/ui/elements/PcPanelBrushes/style.js +1 -1
- package/dist/ui/elements/PcPanelColours/index.js +2 -2
- package/dist/ui/elements/PcPanelColours/style.js +1 -1
- package/dist/ui/elements/PcPanelLayers/index.js +1 -1
- package/dist/ui/elements/PcPanelLayers/style.js +1 -1
- package/dist/ui/elements/PcPopover/index.d.ts +39 -1
- package/dist/ui/elements/PcPopover/index.js +72 -4
- package/dist/ui/elements/PcProfileEditor/index.d.ts +103 -0
- package/dist/ui/elements/PcProfileEditor/index.js +259 -0
- package/dist/ui/elements/PcProfileGraph/index.d.ts +84 -0
- package/dist/ui/elements/PcProfileGraph/index.js +317 -0
- package/dist/ui/elements/PcShortcutSettings/index.js +9 -13
- package/dist/ui/elements/PcSliderRange/index.d.ts +147 -0
- package/dist/ui/elements/PcSliderRange/index.js +307 -0
- package/dist/ui/elements/PcStats/index.d.ts +19 -4
- package/dist/ui/elements/PcStats/index.js +29 -13
- package/dist/ui/elements/PcThemePicker/index.d.ts +17 -15
- package/dist/ui/elements/PcThemePicker/index.js +22 -21
- package/dist/ui/elements/PcToolSettings/index.d.ts +48 -9
- package/dist/ui/elements/PcToolSettings/index.js +382 -318
- package/dist/ui/elements/PcToolSettings/style.js +1 -1
- package/dist/ui/elements/PcToolbar/index.d.ts +15 -0
- package/dist/ui/elements/PcToolbar/index.js +55 -14
- package/dist/ui/elements/PcToolbar/style.js +1 -1
- package/dist/ui/icons/images/icon-brush@2x.js +3 -0
- package/dist/ui/icons/images/icon-circle-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-clipboard@2x.js +3 -0
- package/dist/ui/icons/images/icon-cog@2x.js +3 -0
- package/dist/ui/icons/images/icon-cursor@2x.js +3 -0
- package/dist/ui/icons/images/icon-diagonal-tlbr-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-diagonal-trbl-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-eraser@2x.js +3 -0
- package/dist/ui/icons/images/icon-eyedropper@2x.js +3 -0
- package/dist/ui/icons/images/icon-flip-horizontal@2x.js +3 -0
- package/dist/ui/icons/images/icon-flip-vertical@2x.js +3 -0
- package/dist/ui/icons/images/icon-focus-pen@2x.js +3 -0
- package/dist/ui/icons/images/icon-freehand-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-hidden@2x.js +3 -0
- package/dist/ui/icons/images/icon-horizontal-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-layers@2x.js +3 -0
- package/dist/ui/icons/images/icon-linked@2x.js +3 -0
- package/dist/ui/icons/images/icon-locked@2x.js +3 -0
- package/dist/ui/icons/images/icon-moon@2x.js +3 -0
- package/dist/ui/icons/images/icon-pan@2x.js +3 -0
- package/dist/ui/icons/images/icon-polygon-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-pressure@2x.js +3 -0
- package/dist/ui/icons/images/icon-profile-graph@2x.js +3 -0
- package/dist/ui/icons/images/icon-randomise@2x.js +3 -0
- package/dist/ui/icons/images/icon-rectangle-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-redo@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate-90-left@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate-90-right@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-bezier-cubic@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-bezier-quad@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-linear@2x.js +3 -0
- package/dist/ui/icons/images/icon-star@2x.js +3 -0
- package/dist/ui/icons/images/icon-sun@2x.js +3 -0
- package/dist/ui/icons/images/icon-tilt@2x.js +3 -0
- package/dist/ui/icons/images/icon-undo@2x.js +3 -0
- package/dist/ui/icons/images/icon-unlinked@2x.js +3 -0
- package/dist/ui/icons/images/icon-unlocked@2x.js +3 -0
- package/dist/ui/icons/images/icon-vertical-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-visible@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom-in@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom-out@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom@2x.js +3 -0
- package/dist/ui/icons/index.d.ts +1 -1
- package/dist/ui/icons/index.js +86 -72
- package/dist/ui/index.d.ts +13 -3
- package/dist/ui/index.js +15 -4
- package/dist/ui/layout/index.d.ts +9 -1
- package/dist/ui/layout/index.js +11 -2
- package/dist/ui/styles/index.d.ts +21 -1
- package/dist/ui/styles/index.js +73 -14
- package/dist/ui/tokens/index.d.ts +5 -4
- package/dist/ui/tokens/index.js +12 -7
- package/locales/en-GB.json +39 -2
- package/locales/en-US.json +39 -2
- package/locales/fr.json +39 -2
- package/locales/ja.json +39 -2
- package/locales/ko.json +39 -2
- package/locales/zh-Hans.json +39 -2
- package/locales/zh-Hant.json +39 -2
- package/package.json +27 -18
- package/dist/ui/icons/images/brush.js +0 -3
- package/dist/ui/icons/images/circle-mask.js +0 -3
- package/dist/ui/icons/images/clipboard.js +0 -3
- package/dist/ui/icons/images/cursor.js +0 -3
- package/dist/ui/icons/images/eraser.js +0 -3
- package/dist/ui/icons/images/eyedropper.js +0 -3
- package/dist/ui/icons/images/flip-horizontal.js +0 -3
- package/dist/ui/icons/images/flip-vertical.js +0 -3
- package/dist/ui/icons/images/focus-pen.js +0 -3
- package/dist/ui/icons/images/freehand-mask.js +0 -3
- package/dist/ui/icons/images/hidden.js +0 -3
- package/dist/ui/icons/images/layers.js +0 -3
- package/dist/ui/icons/images/linked.js +0 -3
- package/dist/ui/icons/images/locked.js +0 -3
- package/dist/ui/icons/images/moon.js +0 -3
- package/dist/ui/icons/images/pan.js +0 -3
- package/dist/ui/icons/images/polygon-mask.js +0 -3
- package/dist/ui/icons/images/pressure.js +0 -3
- package/dist/ui/icons/images/randomise.js +0 -3
- package/dist/ui/icons/images/rectangle-mask.js +0 -3
- package/dist/ui/icons/images/redo.js +0 -3
- package/dist/ui/icons/images/rotate-90-left.js +0 -3
- package/dist/ui/icons/images/rotate-90-right.js +0 -3
- package/dist/ui/icons/images/smoothing-bezier-cubic.js +0 -3
- package/dist/ui/icons/images/smoothing-bezier-quad.js +0 -3
- package/dist/ui/icons/images/smoothing-linear.js +0 -3
- package/dist/ui/icons/images/star.js +0 -3
- package/dist/ui/icons/images/sun.js +0 -3
- package/dist/ui/icons/images/tilt.js +0 -3
- package/dist/ui/icons/images/undo.js +0 -3
- package/dist/ui/icons/images/unlinked.js +0 -3
- package/dist/ui/icons/images/unlocked.js +0 -3
- package/dist/ui/icons/images/visible.js +0 -3
- package/dist/ui/icons/images/zoom-in.js +0 -3
- package/dist/ui/icons/images/zoom-out.js +0 -3
- package/dist/ui/icons/images/zoom.js +0 -3
|
@@ -18,7 +18,9 @@ import type { LayerStoreLike } from "../../storage";
|
|
|
18
18
|
import type { AssetAuthor, BrushConfig } from "../../brushes/types";
|
|
19
19
|
import CustomPresets, { type PresetStoreLike } from "../CustomPresets";
|
|
20
20
|
import InputSettings from "../InputSettings";
|
|
21
|
+
import UiSettings from "../UiSettings";
|
|
21
22
|
import Palette from "../Palette";
|
|
23
|
+
import ProfileGraphs from "../ProfileGraphs";
|
|
22
24
|
import ShortcutSettings from "../ShortcutSettings";
|
|
23
25
|
import ExternalSheets from "../ExternalSheets";
|
|
24
26
|
import { type CanvasMessage, type PatchMessage, type StrokeMessage } from "../../messages";
|
|
@@ -113,7 +115,11 @@ export interface PaintCanvasOptions {
|
|
|
113
115
|
maxCanvasSize?: number;
|
|
114
116
|
/** Most layers the user can have (default `DEFAULT_MAX_LAYERS`) */
|
|
115
117
|
maxLayers?: number;
|
|
116
|
-
/**
|
|
118
|
+
/**
|
|
119
|
+
* Creates the canvases the engine draws in: layers, stroke buffers, masks and copies. `createMemoryCanvas` by default,
|
|
120
|
+
* keeping pixels in regular memory so saving and copying don't stall phones; pass `createOffscreenCanvas` to keep them
|
|
121
|
+
* on the GPU instead (replaceable for tests).
|
|
122
|
+
*/
|
|
117
123
|
createCanvas?: CanvasFactory;
|
|
118
124
|
/** Where custom brush presets are kept (defaults to keeping them in memory only) */
|
|
119
125
|
presetStore?: PresetStoreLike;
|
|
@@ -125,7 +131,14 @@ export interface PaintCanvasOptions {
|
|
|
125
131
|
names?: Partial<PaintCanvasNames>;
|
|
126
132
|
/** Where to keep the user's layers so the drawing survives reloading the page (see `useLayerStore`) */
|
|
127
133
|
layerStore?: LayerStoreLike;
|
|
134
|
+
/**
|
|
135
|
+
* The system clipboard, which copying and cutting also put a PNG of the copied image data on so other apps (and brush
|
|
136
|
+
* shapes) can use it (the browser's by default; `null` for none)
|
|
137
|
+
*/
|
|
138
|
+
systemClipboard?: SystemClipboardLike | null;
|
|
128
139
|
}
|
|
140
|
+
/** The part of the system clipboard PaintCanvas writes to (the browser's `navigator.clipboard`) */
|
|
141
|
+
export type SystemClipboardLike = Pick<Clipboard, "write">;
|
|
129
142
|
/**
|
|
130
143
|
* The painting engine: canvas size, layers, brush and eraser settings, custom presets, the selected tool, foreground and background colours,
|
|
131
144
|
* selection, transforms, clipboard, undo/redo history, viewport and input settings. Where its data is kept and who it
|
|
@@ -159,8 +172,12 @@ export default class PaintCanvas extends EventTarget {
|
|
|
159
172
|
readonly background: BackgroundWork;
|
|
160
173
|
/** How the user's input devices control the canvas (scrolling, pinching, touch gestures, focus pen mode) */
|
|
161
174
|
readonly input: InputSettings;
|
|
175
|
+
/** The user's interface choices (theme and language), kept with the other app settings */
|
|
176
|
+
readonly ui: UiSettings;
|
|
162
177
|
/** The user's saved colours, shown as swatches */
|
|
163
178
|
readonly palette: Palette;
|
|
179
|
+
/** The user's own profile graphs (and the presets), for shaping how brush settings vary */
|
|
180
|
+
readonly profiles: ProfileGraphs;
|
|
164
181
|
/** The user's keyboard shortcuts: each configurable action's key combination */
|
|
165
182
|
readonly shortcuts: ShortcutSettings;
|
|
166
183
|
/** The user's pointer position over the canvas, in canvas pixels (undefined when off the canvas) */
|
|
@@ -203,6 +220,10 @@ export default class PaintCanvas extends EventTarget {
|
|
|
203
220
|
private currentSelectedColour;
|
|
204
221
|
/** Image data copied with cut or copy, and the area it was copied from */
|
|
205
222
|
private clipboard?;
|
|
223
|
+
/** How many times image data has been copied (see `copyCount`) */
|
|
224
|
+
private copies;
|
|
225
|
+
/** The system clipboard copies are also put on, if any */
|
|
226
|
+
private readonly systemClipboard?;
|
|
206
227
|
/** Drawings from elsewhere */
|
|
207
228
|
private externalSheets;
|
|
208
229
|
/** Who receives the drawing messages this canvas sends */
|
|
@@ -425,6 +446,40 @@ export default class PaintCanvas extends EventTarget {
|
|
|
425
446
|
* nothing during a stroke or while a transform is pending. Returns the area copied, if any.
|
|
426
447
|
*/
|
|
427
448
|
copy(): Rect | undefined;
|
|
449
|
+
/**
|
|
450
|
+
* Copy the masked image data of all the shown layers, mixed as they're shown (each layer's opacity and blend mode,
|
|
451
|
+
* without the white background), to the clipboard. Hidden layers are left out. Like `copy`, it needs a mask with image
|
|
452
|
+
* data inside it, and does nothing during a stroke or while a transform is pending. Returns the area copied, if any.
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* app.selection.selectAll();
|
|
456
|
+
* app.copyMerged(); // { x: 10, y: 20, width: 30, height: 5 }: the part of the canvas with paint on any shown layer
|
|
457
|
+
*/
|
|
458
|
+
copyMerged(): Rect | undefined;
|
|
459
|
+
/**
|
|
460
|
+
* Copy the masked part of a canvas-sized image to the clipboard (see `copy`), returning the area copied, if any.
|
|
461
|
+
*/
|
|
462
|
+
private copyMasked;
|
|
463
|
+
/**
|
|
464
|
+
* How many times image data has been copied or cut, to tell whether it was copied here since something else may have
|
|
465
|
+
* been copied in another app (see `KeyboardShortcuts`).
|
|
466
|
+
*/
|
|
467
|
+
get copyCount(): number;
|
|
468
|
+
/** Whether there is copied image data to paste (see `copy` and `cut`) */
|
|
469
|
+
get hasClipboard(): boolean;
|
|
470
|
+
/**
|
|
471
|
+
* The copied image data as a PNG, if there is any (e.g. to use as a brush shape).
|
|
472
|
+
*
|
|
473
|
+
* @example
|
|
474
|
+
* const png = await canvas.copiedPng(); // Blob of type "image/png", or undefined
|
|
475
|
+
*/
|
|
476
|
+
copiedPng(): Promise<Blob | undefined>;
|
|
477
|
+
/**
|
|
478
|
+
* Put copied image data on the system clipboard as a PNG, if there is one and the browser can. The item is made
|
|
479
|
+
* straight away (some browsers only allow writing while handling the key press or click) with the PNG to follow.
|
|
480
|
+
* Failing (e.g. the page isn't focused) changes nothing: the image data can still be pasted here.
|
|
481
|
+
*/
|
|
482
|
+
private writeSystemClipboard;
|
|
428
483
|
/**
|
|
429
484
|
* Copy the masked image data of the selected layer to the clipboard, then remove it from the layer as one undoable
|
|
430
485
|
* action (see `deleteMasked`).
|
|
@@ -450,9 +505,11 @@ export default class PaintCanvas extends EventTarget {
|
|
|
450
505
|
* data when its alpha lock is on. None if that is empty.
|
|
451
506
|
*/
|
|
452
507
|
private fillArea;
|
|
508
|
+
/** Whether image data can be copied now: not during a stroke or while a transform is pending */
|
|
509
|
+
private get canCopy();
|
|
453
510
|
/**
|
|
454
|
-
* The bounds of the selected layer's
|
|
455
|
-
* during a stroke or while a transform is pending.
|
|
511
|
+
* The bounds of a canvas-sized image's data (the selected layer's by default) inside the mask, if there is a mask
|
|
512
|
+
* with image data inside it. None during a stroke or while a transform is pending.
|
|
456
513
|
*/
|
|
457
514
|
private maskedArea;
|
|
458
515
|
/**
|
|
@@ -471,6 +528,18 @@ export default class PaintCanvas extends EventTarget {
|
|
|
471
528
|
* discards it. A pending transform is applied first.
|
|
472
529
|
*/
|
|
473
530
|
paste(): void;
|
|
531
|
+
/**
|
|
532
|
+
* Show an image from elsewhere (e.g. pasted from another app) over the selected layer, in the middle of the part of
|
|
533
|
+
* the canvas in view, and select the Move tool to place, scale and rotate it, like `paste`. An image larger than the
|
|
534
|
+
* canvas is shrunk to fit it. Does nothing during a stroke.
|
|
535
|
+
*
|
|
536
|
+
* @example
|
|
537
|
+
* canvas.pasteImage(await createImageBitmap(file));
|
|
538
|
+
*/
|
|
539
|
+
pasteImage(source: CanvasImageSource & {
|
|
540
|
+
width: number;
|
|
541
|
+
height: number;
|
|
542
|
+
}): void;
|
|
474
543
|
/**
|
|
475
544
|
* Apply a finished lasso action to the mask: a shape is added, subtracted or replaces it; a plain click deselects.
|
|
476
545
|
*/
|
|
@@ -11,17 +11,19 @@ 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, {
|
|
14
|
+
import Layer, { createMemoryCanvas } from "../Layer/index.js";
|
|
15
15
|
import LayerAutosave from "../LayerAutosave/index.js";
|
|
16
16
|
import { brushPresets, eraserPresets } from "../../brushes/index.js";
|
|
17
17
|
import { BUILT_IN_AUTHOR, UNKNOWN_AUTHOR, authorHandle } from "../../brushes/authors/index.js";
|
|
18
18
|
import "../../presets/index.js";
|
|
19
|
-
import CustomPresets from "../CustomPresets/index.js";
|
|
20
19
|
import { BRUSH_PRESET_FILE_FORMAT } from "../../presets/schema.js";
|
|
20
|
+
import CustomPresets from "../CustomPresets/index.js";
|
|
21
21
|
import InputSettings from "../InputSettings/index.js";
|
|
22
|
+
import UiSettings from "../UiSettings/index.js";
|
|
22
23
|
import Palette from "../Palette/index.js";
|
|
23
|
-
import ShortcutSettings from "../ShortcutSettings/index.js";
|
|
24
24
|
import { PALETTE_FILE_FORMAT } from "../../palette/schema.js";
|
|
25
|
+
import ProfileGraphs from "../ProfileGraphs/index.js";
|
|
26
|
+
import ShortcutSettings from "../ShortcutSettings/index.js";
|
|
25
27
|
import "../../palette/index.js";
|
|
26
28
|
import { SETTINGS_FILE_FORMAT } from "../../settings/schema.js";
|
|
27
29
|
import ExternalSheets from "../ExternalSheets/index.js";
|
|
@@ -125,8 +127,12 @@ var PaintCanvas = class extends EventTarget {
|
|
|
125
127
|
background = new BackgroundWork(this.strokeLog);
|
|
126
128
|
/** How the user's input devices control the canvas (scrolling, pinching, touch gestures, focus pen mode) */
|
|
127
129
|
input = new InputSettings();
|
|
130
|
+
/** The user's interface choices (theme and language), kept with the other app settings */
|
|
131
|
+
ui = new UiSettings();
|
|
128
132
|
/** The user's saved colours, shown as swatches */
|
|
129
133
|
palette = new Palette();
|
|
134
|
+
/** The user's own profile graphs (and the presets), for shaping how brush settings vary */
|
|
135
|
+
profiles = new ProfileGraphs();
|
|
130
136
|
/** The user's keyboard shortcuts: each configurable action's key combination */
|
|
131
137
|
shortcuts = new ShortcutSettings();
|
|
132
138
|
/** The user's pointer position over the canvas, in canvas pixels (undefined when off the canvas) */
|
|
@@ -193,6 +199,10 @@ var PaintCanvas = class extends EventTarget {
|
|
|
193
199
|
currentSelectedColour = "foreground";
|
|
194
200
|
/** Image data copied with cut or copy, and the area it was copied from */
|
|
195
201
|
clipboard;
|
|
202
|
+
/** How many times image data has been copied (see `copyCount`) */
|
|
203
|
+
copies = 0;
|
|
204
|
+
/** The system clipboard copies are also put on, if any */
|
|
205
|
+
systemClipboard;
|
|
196
206
|
/** Drawings from elsewhere */
|
|
197
207
|
externalSheets;
|
|
198
208
|
/** Who receives the drawing messages this canvas sends */
|
|
@@ -232,7 +242,8 @@ var PaintCanvas = class extends EventTarget {
|
|
|
232
242
|
this.customPresets = new CustomPresets(this.ownerId, options.presetStore);
|
|
233
243
|
this.customPresets.addEventListener("change", () => this.listCustomPresets());
|
|
234
244
|
this.customPresets.load().catch(() => void 0);
|
|
235
|
-
this.createCanvas = options.createCanvas ??
|
|
245
|
+
this.createCanvas = options.createCanvas ?? createMemoryCanvas;
|
|
246
|
+
this.systemClipboard = options.systemClipboard === null ? void 0 : options.systemClipboard ?? globalThis.navigator?.clipboard;
|
|
236
247
|
this.canvasWidth = DEFAULT_CANVAS_SIZE;
|
|
237
248
|
this.canvasHeight = DEFAULT_CANVAS_SIZE;
|
|
238
249
|
this.layerStack = new LayerStack(this.canvasWidth, this.canvasHeight, this.createCanvas, this.maxLayers);
|
|
@@ -707,19 +718,72 @@ var PaintCanvas = class extends EventTarget {
|
|
|
707
718
|
* nothing during a stroke or while a transform is pending. Returns the area copied, if any.
|
|
708
719
|
*/
|
|
709
720
|
copy() {
|
|
710
|
-
|
|
721
|
+
return this.copyMasked(this.layerStack.active.canvas);
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Copy the masked image data of all the shown layers, mixed as they're shown (each layer's opacity and blend mode,
|
|
725
|
+
* without the white background), to the clipboard. Hidden layers are left out. Like `copy`, it needs a mask with image
|
|
726
|
+
* data inside it, and does nothing during a stroke or while a transform is pending. Returns the area copied, if any.
|
|
727
|
+
*
|
|
728
|
+
* @example
|
|
729
|
+
* app.selection.selectAll();
|
|
730
|
+
* app.copyMerged(); // { x: 10, y: 20, width: 30, height: 5 }: the part of the canvas with paint on any shown layer
|
|
731
|
+
*/
|
|
732
|
+
copyMerged() {
|
|
733
|
+
if (!this.canCopy) return void 0;
|
|
734
|
+
const merged = this.createCanvas(this.width, this.height);
|
|
735
|
+
drawLayers(merged.getContext("2d"), this.layerStack.layers);
|
|
736
|
+
return this.copyMasked(merged);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Copy the masked part of a canvas-sized image to the clipboard (see `copy`), returning the area copied, if any.
|
|
740
|
+
*/
|
|
741
|
+
copyMasked(source) {
|
|
742
|
+
const area = this.maskedArea(source);
|
|
711
743
|
const mask = this.currentSelection.mask;
|
|
712
744
|
if (!area || !mask) return void 0;
|
|
713
|
-
const
|
|
714
|
-
const image = copyArea(this.createCanvas, layer.canvas, area);
|
|
745
|
+
const image = copyArea(this.createCanvas, source, area);
|
|
715
746
|
applyMask(image, copyArea(this.createCanvas, mask, area));
|
|
716
747
|
this.clipboard = {
|
|
717
748
|
image,
|
|
718
749
|
area
|
|
719
750
|
};
|
|
751
|
+
this.copies++;
|
|
752
|
+
this.writeSystemClipboard(image);
|
|
753
|
+
this.notify();
|
|
720
754
|
return area;
|
|
721
755
|
}
|
|
722
756
|
/**
|
|
757
|
+
* How many times image data has been copied or cut, to tell whether it was copied here since something else may have
|
|
758
|
+
* been copied in another app (see `KeyboardShortcuts`).
|
|
759
|
+
*/
|
|
760
|
+
get copyCount() {
|
|
761
|
+
return this.copies;
|
|
762
|
+
}
|
|
763
|
+
/** Whether there is copied image data to paste (see `copy` and `cut`) */
|
|
764
|
+
get hasClipboard() {
|
|
765
|
+
return this.clipboard !== void 0;
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* The copied image data as a PNG, if there is any (e.g. to use as a brush shape).
|
|
769
|
+
*
|
|
770
|
+
* @example
|
|
771
|
+
* const png = await canvas.copiedPng(); // Blob of type "image/png", or undefined
|
|
772
|
+
*/
|
|
773
|
+
async copiedPng() {
|
|
774
|
+
return this.clipboard?.image.convertToBlob({ type: "image/png" });
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Put copied image data on the system clipboard as a PNG, if there is one and the browser can. The item is made
|
|
778
|
+
* straight away (some browsers only allow writing while handling the key press or click) with the PNG to follow.
|
|
779
|
+
* Failing (e.g. the page isn't focused) changes nothing: the image data can still be pasted here.
|
|
780
|
+
*/
|
|
781
|
+
writeSystemClipboard(image) {
|
|
782
|
+
if (!this.systemClipboard || typeof ClipboardItem === "undefined") return;
|
|
783
|
+
const item = new ClipboardItem({ "image/png": image.convertToBlob({ type: "image/png" }) });
|
|
784
|
+
this.systemClipboard.write([item]).catch(() => void 0);
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
723
787
|
* Copy the masked image data of the selected layer to the clipboard, then remove it from the layer as one undoable
|
|
724
788
|
* action (see `deleteMasked`).
|
|
725
789
|
*/
|
|
@@ -766,15 +830,19 @@ var PaintCanvas = class extends EventTarget {
|
|
|
766
830
|
height
|
|
767
831
|
};
|
|
768
832
|
}
|
|
833
|
+
/** Whether image data can be copied now: not during a stroke or while a transform is pending */
|
|
834
|
+
get canCopy() {
|
|
835
|
+
return !this.strokeEngine.drawing && !this.transformer.pending;
|
|
836
|
+
}
|
|
769
837
|
/**
|
|
770
|
-
* The bounds of the selected layer's
|
|
771
|
-
* during a stroke or while a transform is pending.
|
|
838
|
+
* The bounds of a canvas-sized image's data (the selected layer's by default) inside the mask, if there is a mask
|
|
839
|
+
* with image data inside it. None during a stroke or while a transform is pending.
|
|
772
840
|
*/
|
|
773
|
-
maskedArea() {
|
|
841
|
+
maskedArea(source = this.layerStack.active.canvas) {
|
|
774
842
|
const mask = this.currentSelection.mask;
|
|
775
|
-
if (!mask || this.
|
|
843
|
+
if (!mask || !this.canCopy) return void 0;
|
|
776
844
|
const { width, height } = this;
|
|
777
|
-
return contentBounds(
|
|
845
|
+
return contentBounds(source.getContext("2d").getImageData(0, 0, width, height).data, width, height, mask.getContext("2d").getImageData(0, 0, width, height).data);
|
|
778
846
|
}
|
|
779
847
|
/**
|
|
780
848
|
* Erase the mask's part of an area from the selected layer, or paint it with the background colour when the layer's
|
|
@@ -830,6 +898,32 @@ var PaintCanvas = class extends EventTarget {
|
|
|
830
898
|
this.transformer.float(this.layerStack.active, clipboard.image, clipboard.area);
|
|
831
899
|
}
|
|
832
900
|
/**
|
|
901
|
+
* Show an image from elsewhere (e.g. pasted from another app) over the selected layer, in the middle of the part of
|
|
902
|
+
* the canvas in view, and select the Move tool to place, scale and rotate it, like `paste`. An image larger than the
|
|
903
|
+
* canvas is shrunk to fit it. Does nothing during a stroke.
|
|
904
|
+
*
|
|
905
|
+
* @example
|
|
906
|
+
* canvas.pasteImage(await createImageBitmap(file));
|
|
907
|
+
*/
|
|
908
|
+
pasteImage(source) {
|
|
909
|
+
if (this.strokeEngine.drawing) return;
|
|
910
|
+
const scale = Math.min(1, this.canvasWidth / source.width, this.canvasHeight / source.height);
|
|
911
|
+
const width = Math.max(1, Math.round(source.width * scale));
|
|
912
|
+
const height = Math.max(1, Math.round(source.height * scale));
|
|
913
|
+
const image = this.createCanvas(width, height);
|
|
914
|
+
image.getContext("2d").drawImage(source, 0, 0, width, height);
|
|
915
|
+
const view = this.view.visibleRect();
|
|
916
|
+
const area = {
|
|
917
|
+
x: Math.round(view.x + (view.width - width) / 2),
|
|
918
|
+
y: Math.round(view.y + (view.height - height) / 2),
|
|
919
|
+
width,
|
|
920
|
+
height
|
|
921
|
+
};
|
|
922
|
+
if (this.transformer.pending) this.commitTransform();
|
|
923
|
+
this.selectTool(Tool.Move);
|
|
924
|
+
this.transformer.float(this.layerStack.active, image, area);
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
833
927
|
* Apply a finished lasso action to the mask: a shape is added, subtracted or replaces it; a plain click deselects.
|
|
834
928
|
*/
|
|
835
929
|
applyLasso(result) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encode a canvas as a PNG on the page's own thread (which blocks the page while it runs: seconds on phones for a large
|
|
3
|
+
* canvas).
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* const png = await encodePngHere(layer.canvas);
|
|
7
|
+
*/
|
|
8
|
+
export declare const encodePngHere: (canvas: OffscreenCanvas) => Promise<Blob>;
|
|
9
|
+
/** The worker's code: draws the image it's sent onto its own canvas and encodes that as a PNG */
|
|
10
|
+
export declare const PNG_WORKER_SOURCE = "self.onmessage = async (event) => {\n const { id, bitmap } = event.data;\n try {\n const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);\n canvas.getContext(\"2d\").drawImage(bitmap, 0, 0);\n bitmap.close();\n self.postMessage({ id, blob: await canvas.convertToBlob({ type: \"image/png\" }) });\n } catch (error) {\n self.postMessage({ id, error: String(error) });\n }\n};";
|
|
11
|
+
/** Makes the worker, or gives null where workers can't be used */
|
|
12
|
+
export type WorkerFactory = () => Worker | null;
|
|
13
|
+
/**
|
|
14
|
+
* Make the PNG worker from its code (no separate file, so any bundler works), or null where there are no workers or
|
|
15
|
+
* `createImageBitmap`, or the page doesn't allow workers from `blob:` addresses.
|
|
16
|
+
*/
|
|
17
|
+
export declare const createPngWorker: WorkerFactory;
|
|
18
|
+
/**
|
|
19
|
+
* Encodes canvases as PNGs in a background worker, so the page keeps responding while large layers are saved. The
|
|
20
|
+
* canvas is copied (`createImageBitmap`) and sent to the worker, which encodes the copy. Where a worker can't be used,
|
|
21
|
+
* or it fails, it encodes on the page's own thread instead (see `encodePngHere`).
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const encoder = new PngEncoder();
|
|
25
|
+
* const png = await encoder.encode(layer.canvas);
|
|
26
|
+
*/
|
|
27
|
+
export default class PngEncoder {
|
|
28
|
+
private readonly createWorker;
|
|
29
|
+
/** The worker, once made (null when it can't be used) */
|
|
30
|
+
private worker?;
|
|
31
|
+
private nextId;
|
|
32
|
+
/** Encodings waiting for the worker, by ID */
|
|
33
|
+
private readonly waiting;
|
|
34
|
+
constructor(createWorker?: WorkerFactory);
|
|
35
|
+
/**
|
|
36
|
+
* Encode a canvas as a PNG, in the worker where it can be used.
|
|
37
|
+
*/
|
|
38
|
+
encode: (canvas: OffscreenCanvas) => Promise<Blob>;
|
|
39
|
+
/** The worker, made on first use */
|
|
40
|
+
private getWorker;
|
|
41
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// src/lib/classes/PngEncoder/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* Encode a canvas as a PNG on the page's own thread (which blocks the page while it runs: seconds on phones for a large
|
|
4
|
+
* canvas).
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* const png = await encodePngHere(layer.canvas);
|
|
8
|
+
*/
|
|
9
|
+
var encodePngHere = (canvas) => {
|
|
10
|
+
canvas.getContext("2d");
|
|
11
|
+
return canvas.convertToBlob({ type: "image/png" });
|
|
12
|
+
};
|
|
13
|
+
/** The worker's code: draws the image it's sent onto its own canvas and encodes that as a PNG */
|
|
14
|
+
var PNG_WORKER_SOURCE = `self.onmessage = async (event) => {
|
|
15
|
+
const { id, bitmap } = event.data;
|
|
16
|
+
try {
|
|
17
|
+
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
|
|
18
|
+
canvas.getContext("2d").drawImage(bitmap, 0, 0);
|
|
19
|
+
bitmap.close();
|
|
20
|
+
self.postMessage({ id, blob: await canvas.convertToBlob({ type: "image/png" }) });
|
|
21
|
+
} catch (error) {
|
|
22
|
+
self.postMessage({ id, error: String(error) });
|
|
23
|
+
}
|
|
24
|
+
};`;
|
|
25
|
+
/**
|
|
26
|
+
* Make the PNG worker from its code (no separate file, so any bundler works), or null where there are no workers or
|
|
27
|
+
* `createImageBitmap`, or the page doesn't allow workers from `blob:` addresses.
|
|
28
|
+
*/
|
|
29
|
+
var createPngWorker = () => {
|
|
30
|
+
if (typeof Worker === "undefined" || typeof createImageBitmap === "undefined") return null;
|
|
31
|
+
try {
|
|
32
|
+
const url = URL.createObjectURL(new Blob([PNG_WORKER_SOURCE], { type: "text/javascript" }));
|
|
33
|
+
return new Worker(url);
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Encodes canvases as PNGs in a background worker, so the page keeps responding while large layers are saved. The
|
|
40
|
+
* canvas is copied (`createImageBitmap`) and sent to the worker, which encodes the copy. Where a worker can't be used,
|
|
41
|
+
* or it fails, it encodes on the page's own thread instead (see `encodePngHere`).
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* const encoder = new PngEncoder();
|
|
45
|
+
* const png = await encoder.encode(layer.canvas);
|
|
46
|
+
*/
|
|
47
|
+
var PngEncoder = class {
|
|
48
|
+
createWorker;
|
|
49
|
+
/** The worker, once made (null when it can't be used) */
|
|
50
|
+
worker;
|
|
51
|
+
nextId = 0;
|
|
52
|
+
/** Encodings waiting for the worker, by ID */
|
|
53
|
+
waiting = /* @__PURE__ */ new Map();
|
|
54
|
+
constructor(createWorker = createPngWorker) {
|
|
55
|
+
this.createWorker = createWorker;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Encode a canvas as a PNG, in the worker where it can be used.
|
|
59
|
+
*/
|
|
60
|
+
encode = async (canvas) => {
|
|
61
|
+
const worker = this.getWorker();
|
|
62
|
+
if (!worker) return encodePngHere(canvas);
|
|
63
|
+
try {
|
|
64
|
+
canvas.getContext("2d");
|
|
65
|
+
const bitmap = await createImageBitmap(canvas);
|
|
66
|
+
const id = this.nextId++;
|
|
67
|
+
return await new Promise((resolve, reject) => {
|
|
68
|
+
this.waiting.set(id, {
|
|
69
|
+
resolve,
|
|
70
|
+
reject
|
|
71
|
+
});
|
|
72
|
+
worker.postMessage({
|
|
73
|
+
id,
|
|
74
|
+
bitmap
|
|
75
|
+
}, [bitmap]);
|
|
76
|
+
});
|
|
77
|
+
} catch {
|
|
78
|
+
return encodePngHere(canvas);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
/** The worker, made on first use */
|
|
82
|
+
getWorker() {
|
|
83
|
+
if (this.worker !== void 0) return this.worker;
|
|
84
|
+
const worker = this.createWorker();
|
|
85
|
+
this.worker = worker;
|
|
86
|
+
if (!worker) return null;
|
|
87
|
+
worker.addEventListener("message", (event) => {
|
|
88
|
+
const { id, blob, error } = event.data;
|
|
89
|
+
const waiting = this.waiting.get(id);
|
|
90
|
+
if (!waiting) return;
|
|
91
|
+
this.waiting.delete(id);
|
|
92
|
+
if (blob) waiting.resolve(blob);
|
|
93
|
+
else waiting.reject(new Error(error));
|
|
94
|
+
});
|
|
95
|
+
worker.addEventListener("error", () => {
|
|
96
|
+
worker.terminate();
|
|
97
|
+
this.worker = null;
|
|
98
|
+
for (const { reject } of this.waiting.values()) reject(/* @__PURE__ */ new Error("PNG worker failed"));
|
|
99
|
+
this.waiting.clear();
|
|
100
|
+
});
|
|
101
|
+
return worker;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
export { PNG_WORKER_SOURCE, createPngWorker, PngEncoder as default, encodePngHere };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type ProfileGraph, type ProfilePoint } from "../../profiles";
|
|
2
|
+
/**
|
|
3
|
+
* Keep only valid points from an untrusted value, in order from left to right: each on the grid, with a known
|
|
4
|
+
* interpolation. Undefined if there aren't 2 to `MAX_PROFILE_POINTS` of them.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* sanitisePoints([{ x: 10, y: 0, i: "linear" }, { x: 0, y: -10, i: "linear" }]); // sorted by x
|
|
8
|
+
*/
|
|
9
|
+
export declare const sanitisePoints: (input: unknown) => ProfilePoint[] | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Keep only valid profile graphs from an untrusted value (e.g. stored or imported): each with an ID and name and valid
|
|
12
|
+
* points (see `sanitisePoints`), each ID once and none of the presets', up to `MAX_SAVED_PROFILES`. Undefined if the
|
|
13
|
+
* value isn't an array.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* sanitiseProfiles([{ id: "a", name: "Soft", points: [...] }, { id: "a", name: "Again", points: [...] }]); // the first
|
|
17
|
+
*/
|
|
18
|
+
export declare const sanitiseProfiles: (input: unknown) => ProfileGraph[] | undefined;
|
|
19
|
+
/** What a profile graph is saved from: its name and points, and its ID when replacing one */
|
|
20
|
+
export interface ProfileGraphDraft {
|
|
21
|
+
id?: string;
|
|
22
|
+
name: string;
|
|
23
|
+
points: readonly ProfilePoint[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The user's own profile graphs, alongside the ones PaintCanvas comes with (`PROFILE_PRESETS`). Kept in memory; the app
|
|
27
|
+
* settings save and restore them (see `set` and the `change` event). Each gets a UUID when it's first saved.
|
|
28
|
+
*
|
|
29
|
+
* Dispatches a `change` event when the user's graphs change.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const profiles = new ProfileGraphs();
|
|
33
|
+
* const soft = profiles.save({ name: "Soft", points: PROFILE_PRESETS[1].points });
|
|
34
|
+
* profiles.get(soft.id)?.name; // "Soft"
|
|
35
|
+
*/
|
|
36
|
+
export default class ProfileGraphs extends EventTarget {
|
|
37
|
+
private values;
|
|
38
|
+
/** The user's own graphs, in the order they were first saved */
|
|
39
|
+
get graphs(): readonly ProfileGraph[];
|
|
40
|
+
/** Every graph: the presets, then the user's own */
|
|
41
|
+
get all(): readonly ProfileGraph[];
|
|
42
|
+
/**
|
|
43
|
+
* A graph by its ID: a preset or one of the user's own.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* profiles.get("overweight")?.name; // "Overweight"
|
|
47
|
+
*/
|
|
48
|
+
get(id: string | undefined): ProfileGraph | undefined;
|
|
49
|
+
/** Whether a graph is one PaintCanvas comes with (so it can't be renamed, replaced or deleted) */
|
|
50
|
+
isPreset(id: string | undefined): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* One of the user's own graphs with a name, ignoring case and spaces around it.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* profiles.findByName(" soft "); // the graph called "Soft"
|
|
56
|
+
*/
|
|
57
|
+
findByName(name: string): ProfileGraph | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Save a graph as one of the user's own: as a new one (with a new UUID), or in place of the one with its ID. Returns
|
|
60
|
+
* the saved graph, or undefined if it can't be saved (no name, invalid points, a preset's ID, or no room).
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* profiles.save({ name: "Soft", points }); // { id: "1b9d…", name: "Soft", points }
|
|
64
|
+
*/
|
|
65
|
+
save(draft: ProfileGraphDraft): ProfileGraph | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Rename one of the user's own graphs. Returns whether it was renamed.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* profiles.rename(id, "Softer");
|
|
71
|
+
*/
|
|
72
|
+
rename(id: string, name: string): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Delete one of the user's own graphs. Returns whether it was deleted.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* profiles.remove(id);
|
|
78
|
+
*/
|
|
79
|
+
remove(id: string): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Replace all the user's graphs (e.g. when restoring saved ones), keeping only valid ones (see `sanitiseProfiles`).
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* profiles.set(settings.profiles);
|
|
85
|
+
*/
|
|
86
|
+
set(graphs: unknown): void;
|
|
87
|
+
}
|