@lvl99dotcom/paintcanvas 0.0.0-stage → 0.2.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/LICENSE +201 -0
- package/README.md +131 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/lib/brushes/BrushTipCache/index.d.ts +32 -0
- package/dist/lib/brushes/BrushTipCache/index.js +60 -0
- package/dist/lib/brushes/StrokeEngine/index.d.ts +171 -0
- package/dist/lib/brushes/StrokeEngine/index.js +324 -0
- package/dist/lib/brushes/StrokePath/index.d.ts +53 -0
- package/dist/lib/brushes/StrokePath/index.js +161 -0
- package/dist/lib/brushes/StrokeSampler/index.d.ts +55 -0
- package/dist/lib/brushes/StrokeSampler/index.js +75 -0
- package/dist/lib/brushes/authors/index.d.ts +55 -0
- package/dist/lib/brushes/authors/index.js +60 -0
- package/dist/lib/brushes/configRanges/index.d.ts +52 -0
- package/dist/lib/brushes/configRanges/index.js +206 -0
- package/dist/lib/brushes/index.d.ts +5 -0
- package/dist/lib/brushes/index.js +19 -0
- package/dist/lib/brushes/params/index.d.ts +108 -0
- package/dist/lib/brushes/params/index.js +139 -0
- package/dist/lib/brushes/presetFile/index.d.ts +32 -0
- package/dist/lib/brushes/presetFile/index.js +55 -0
- package/dist/lib/brushes/presets/drawing/brush-pen.js +3 -0
- package/dist/lib/brushes/presets/drawing/felt-pen.js +3 -0
- package/dist/lib/brushes/presets/drawing/index.d.ts +13 -0
- package/dist/lib/brushes/presets/drawing/index.js +88 -0
- package/dist/lib/brushes/presets/drawing/pencil-2b.js +3 -0
- package/dist/lib/brushes/presets/drawing/pencil-hb.js +3 -0
- package/dist/lib/brushes/presets/painting/grass.js +3 -0
- package/dist/lib/brushes/presets/painting/hair.js +3 -0
- package/dist/lib/brushes/presets/painting/index.d.ts +18 -0
- package/dist/lib/brushes/presets/painting/index.js +118 -0
- package/dist/lib/brushes/presets/painting/splatter.js +3 -0
- package/dist/lib/brushes/presets/painting/textured-paint.js +3 -0
- package/dist/lib/brushes/presets/painting/watery-paint.js +3 -0
- package/dist/lib/brushes/presets/round/index.d.ts +9 -0
- package/dist/lib/brushes/presets/round/index.js +44 -0
- package/dist/lib/brushes/presets/round/round.js +3 -0
- package/dist/lib/brushes/presets/round/soft-round.js +3 -0
- package/dist/lib/brushes/presets/sized/index.d.ts +20 -0
- package/dist/lib/brushes/presets/sized/index.js +30 -0
- package/dist/lib/brushes/presets/sketching/index.d.ts +14 -0
- package/dist/lib/brushes/presets/sketching/index.js +107 -0
- package/dist/lib/brushes/presets/square/index.d.ts +7 -0
- package/dist/lib/brushes/presets/square/index.js +22 -0
- package/dist/lib/brushes/presets/square/square.js +3 -0
- package/dist/lib/brushes/shapeImage/index.d.ts +40 -0
- package/dist/lib/brushes/shapeImage/index.js +61 -0
- package/dist/lib/brushes/types/index.d.ts +243 -0
- package/dist/lib/brushes/types/index.js +20 -0
- package/dist/lib/classes/BackgroundWork/index.d.ts +55 -0
- package/dist/lib/classes/BackgroundWork/index.js +97 -0
- package/dist/lib/classes/BrushSettings/index.d.ts +91 -0
- package/dist/lib/classes/BrushSettings/index.js +152 -0
- package/dist/lib/classes/CustomPresets/index.d.ts +53 -0
- package/dist/lib/classes/CustomPresets/index.js +71 -0
- package/dist/lib/classes/ExternalSheets/index.d.ts +64 -0
- package/dist/lib/classes/ExternalSheets/index.js +112 -0
- package/dist/lib/classes/GestureTracker/index.d.ts +62 -0
- package/dist/lib/classes/GestureTracker/index.js +122 -0
- package/dist/lib/classes/History/index.d.ts +83 -0
- package/dist/lib/classes/History/index.js +126 -0
- package/dist/lib/classes/HistoryGuard/index.d.ts +58 -0
- package/dist/lib/classes/HistoryGuard/index.js +97 -0
- package/dist/lib/classes/InputSettings/index.d.ts +74 -0
- package/dist/lib/classes/InputSettings/index.js +101 -0
- package/dist/lib/classes/KeyboardShortcuts/index.d.ts +48 -0
- package/dist/lib/classes/KeyboardShortcuts/index.js +137 -0
- package/dist/lib/classes/Layer/index.d.ts +86 -0
- package/dist/lib/classes/Layer/index.js +111 -0
- package/dist/lib/classes/LayerAutosave/index.d.ts +180 -0
- package/dist/lib/classes/LayerAutosave/index.js +346 -0
- package/dist/lib/classes/LayerStack/index.d.ts +177 -0
- package/dist/lib/classes/LayerStack/index.js +394 -0
- package/dist/lib/classes/PaintCanvas/index.d.ts +537 -0
- package/dist/lib/classes/PaintCanvas/index.js +965 -0
- package/dist/lib/classes/StrokeLog/index.d.ts +149 -0
- package/dist/lib/classes/StrokeLog/index.js +257 -0
- package/dist/lib/classes/Viewport/index.d.ts +108 -0
- package/dist/lib/classes/Viewport/index.js +194 -0
- package/dist/lib/document/index.d.ts +17 -0
- package/dist/lib/document/index.js +25 -0
- package/dist/lib/document/schema.d.ts +132 -0
- package/dist/lib/document/schema.js +110 -0
- package/dist/lib/i18n/index.d.ts +94 -0
- package/dist/lib/i18n/index.js +182 -0
- package/dist/lib/image/index.d.ts +90 -0
- package/dist/lib/image/index.js +147 -0
- package/dist/lib/messages/PatchSender/index.d.ts +75 -0
- package/dist/lib/messages/PatchSender/index.js +125 -0
- package/dist/lib/messages/index.d.ts +26 -0
- package/dist/lib/messages/index.js +59 -0
- package/dist/lib/messages/schema.d.ts +1775 -0
- package/dist/lib/messages/schema.js +192 -0
- package/dist/lib/presets/index.d.ts +32 -0
- package/dist/lib/presets/index.js +35 -0
- package/dist/lib/presets/schema.d.ts +753 -0
- package/dist/lib/presets/schema.js +181 -0
- package/dist/lib/rendering/BrushTip/index.d.ts +63 -0
- package/dist/lib/rendering/BrushTip/index.js +112 -0
- package/dist/lib/rendering/checkerboard/index.d.ts +22 -0
- package/dist/lib/rendering/checkerboard/index.js +31 -0
- package/dist/lib/rendering/compositing/index.d.ts +64 -0
- package/dist/lib/rendering/compositing/index.js +124 -0
- package/dist/lib/rendering/cursor/index.d.ts +14 -0
- package/dist/lib/rendering/cursor/index.js +39 -0
- package/dist/lib/rendering/cursors/index.d.ts +18 -0
- package/dist/lib/rendering/cursors/index.js +23 -0
- package/dist/lib/rendering/maskOutline/index.d.ts +22 -0
- package/dist/lib/rendering/maskOutline/index.js +78 -0
- package/dist/lib/rendering/outline/index.d.ts +36 -0
- package/dist/lib/rendering/outline/index.js +120 -0
- package/dist/lib/rendering/sample/index.d.ts +55 -0
- package/dist/lib/rendering/sample/index.js +83 -0
- package/dist/lib/rendering/screenshot/index.d.ts +67 -0
- package/dist/lib/rendering/screenshot/index.js +96 -0
- package/dist/lib/rendering/transformBox/index.d.ts +10 -0
- package/dist/lib/rendering/transformBox/index.js +57 -0
- package/dist/lib/selection/Lasso/index.d.ts +70 -0
- package/dist/lib/selection/Lasso/index.js +156 -0
- package/dist/lib/selection/Selection/index.d.ts +86 -0
- package/dist/lib/selection/Selection/index.js +228 -0
- package/dist/lib/selection/geometry/index.d.ts +37 -0
- package/dist/lib/selection/geometry/index.js +57 -0
- package/dist/lib/storage/index.d.ts +63 -0
- package/dist/lib/storage/index.js +114 -0
- package/dist/lib/transform/Transformer/index.d.ts +124 -0
- package/dist/lib/transform/Transformer/index.js +286 -0
- package/dist/lib/transform/geometry/index.d.ts +95 -0
- package/dist/lib/transform/geometry/index.js +258 -0
- package/dist/lib/utils/array/index.d.ts +10 -0
- package/dist/lib/utils/array/index.js +21 -0
- package/dist/lib/utils/gestures/index.d.ts +35 -0
- package/dist/lib/utils/gestures/index.js +81 -0
- package/dist/lib/utils/navigation/index.d.ts +51 -0
- package/dist/lib/utils/navigation/index.js +59 -0
- package/dist/lib/utils/random/index.d.ts +18 -0
- package/dist/lib/utils/random/index.js +27 -0
- package/dist/lib/utils/shortcuts/index.d.ts +74 -0
- package/dist/lib/utils/shortcuts/index.js +189 -0
- package/dist/lib/validation/validators.generated.d.ts +27 -0
- package/dist/lib/validation/validators.generated.js +19826 -0
- package/dist/locales/en-GB.js +188 -0
- package/dist/locales/en-US.js +188 -0
- package/dist/locales/fr.js +188 -0
- package/dist/locales/ja.js +188 -0
- package/dist/locales/ko.js +188 -0
- package/dist/locales/zh-Hans.js +188 -0
- package/dist/locales/zh-Hant.js +188 -0
- package/dist/package.js +3 -0
- package/dist/testing/app.d.ts +7 -0
- package/dist/testing/app.js +16 -0
- package/dist/testing/elements.d.ts +42 -0
- package/dist/testing/elements.js +58 -0
- package/dist/testing/fakeCanvas.d.ts +21 -0
- package/dist/testing/fakeCanvas.js +26 -0
- package/dist/testing/layerStore.d.ts +26 -0
- package/dist/testing/layerStore.js +43 -0
- package/dist/testing/locks.d.ts +15 -0
- package/dist/testing/locks.js +41 -0
- package/dist/testing/recordingCanvas.d.ts +14 -0
- package/dist/testing/recordingCanvas.js +28 -0
- package/dist/testing/recordingContext.d.ts +10 -0
- package/dist/testing/recordingContext.js +19 -0
- package/dist/ui/ChangeController/index.d.ts +40 -0
- package/dist/ui/ChangeController/index.js +62 -0
- package/dist/ui/base/index.d.ts +69 -0
- package/dist/ui/base/index.js +94 -0
- package/dist/ui/elements/PaintCanvas/index.d.ts +249 -0
- package/dist/ui/elements/PaintCanvas/index.js +584 -0
- package/dist/ui/elements/PaintCanvas/style.js +3 -0
- package/dist/ui/elements/PcBrushConfig/index.d.ts +130 -0
- package/dist/ui/elements/PcBrushConfig/index.js +463 -0
- package/dist/ui/elements/PcBrushConfig/style.js +3 -0
- package/dist/ui/elements/PcBrushMenu/index.d.ts +121 -0
- package/dist/ui/elements/PcBrushMenu/index.js +362 -0
- package/dist/ui/elements/PcBrushMenu/style.js +3 -0
- package/dist/ui/elements/PcBrushTest/index.d.ts +71 -0
- package/dist/ui/elements/PcBrushTest/index.js +136 -0
- package/dist/ui/elements/PcBrushTest/style.js +3 -0
- package/dist/ui/elements/PcButton/index.d.ts +57 -0
- package/dist/ui/elements/PcButton/index.js +124 -0
- package/dist/ui/elements/PcButtonGroup/index.d.ts +44 -0
- package/dist/ui/elements/PcButtonGroup/index.js +93 -0
- package/dist/ui/elements/PcCanvas/index.d.ts +356 -0
- package/dist/ui/elements/PcCanvas/index.js +1004 -0
- package/dist/ui/elements/PcCanvas/style.js +3 -0
- package/dist/ui/elements/PcCanvasMap/index.d.ts +96 -0
- package/dist/ui/elements/PcCanvasMap/index.js +187 -0
- package/dist/ui/elements/PcCanvasMap/style.js +3 -0
- package/dist/ui/elements/PcContextMenu/index.d.ts +72 -0
- package/dist/ui/elements/PcContextMenu/index.js +148 -0
- package/dist/ui/elements/PcDialog/index.d.ts +63 -0
- package/dist/ui/elements/PcDialog/index.js +114 -0
- package/dist/ui/elements/PcIcon/index.d.ts +36 -0
- package/dist/ui/elements/PcIcon/index.js +43 -0
- package/dist/ui/elements/PcIconToggle/index.d.ts +46 -0
- package/dist/ui/elements/PcIconToggle/index.js +87 -0
- package/dist/ui/elements/PcInfoTip/index.d.ts +40 -0
- package/dist/ui/elements/PcInfoTip/index.js +95 -0
- package/dist/ui/elements/PcInputSettings/index.d.ts +69 -0
- package/dist/ui/elements/PcInputSettings/index.js +150 -0
- package/dist/ui/elements/PcLayerItem/index.d.ts +221 -0
- package/dist/ui/elements/PcLayerItem/index.js +561 -0
- package/dist/ui/elements/PcLayerItem/style.js +3 -0
- package/dist/ui/elements/PcLayersMenu/index.d.ts +108 -0
- package/dist/ui/elements/PcLayersMenu/index.js +208 -0
- package/dist/ui/elements/PcLayersMenu/style.js +3 -0
- package/dist/ui/elements/PcMatch/index.d.ts +31 -0
- package/dist/ui/elements/PcMatch/index.js +58 -0
- package/dist/ui/elements/PcPanel/index.d.ts +43 -0
- package/dist/ui/elements/PcPanel/index.js +51 -0
- package/dist/ui/elements/PcPopover/index.d.ts +48 -0
- package/dist/ui/elements/PcPopover/index.js +86 -0
- package/dist/ui/elements/PcSlider/index.d.ts +57 -0
- package/dist/ui/elements/PcSlider/index.js +88 -0
- package/dist/ui/elements/PcStats/index.d.ts +54 -0
- package/dist/ui/elements/PcStats/index.js +141 -0
- package/dist/ui/elements/PcThemePicker/index.d.ts +56 -0
- package/dist/ui/elements/PcThemePicker/index.js +134 -0
- package/dist/ui/elements/PcToast/index.d.ts +58 -0
- package/dist/ui/elements/PcToast/index.js +110 -0
- package/dist/ui/elements/PcToggleCheckbox/index.d.ts +37 -0
- package/dist/ui/elements/PcToggleCheckbox/index.js +105 -0
- package/dist/ui/elements/PcToolSettings/index.d.ts +128 -0
- package/dist/ui/elements/PcToolSettings/index.js +543 -0
- package/dist/ui/elements/PcToolSettings/style.js +3 -0
- package/dist/ui/elements/PcToolbar/index.d.ts +71 -0
- package/dist/ui/elements/PcToolbar/index.js +190 -0
- package/dist/ui/elements/PcToolbar/style.js +3 -0
- package/dist/ui/icons/images/brush.js +3 -0
- package/dist/ui/icons/images/circle-mask.js +3 -0
- package/dist/ui/icons/images/clipboard.js +3 -0
- package/dist/ui/icons/images/cursor.js +3 -0
- package/dist/ui/icons/images/eraser.js +3 -0
- package/dist/ui/icons/images/eyedropper.js +3 -0
- package/dist/ui/icons/images/flip-horizontal.js +3 -0
- package/dist/ui/icons/images/flip-vertical.js +3 -0
- package/dist/ui/icons/images/focus-pen.js +3 -0
- package/dist/ui/icons/images/freehand-mask.js +3 -0
- package/dist/ui/icons/images/hidden.js +3 -0
- package/dist/ui/icons/images/layers.js +3 -0
- package/dist/ui/icons/images/linked.js +3 -0
- package/dist/ui/icons/images/locked.js +3 -0
- package/dist/ui/icons/images/moon.js +3 -0
- package/dist/ui/icons/images/pan.js +3 -0
- package/dist/ui/icons/images/polygon-mask.js +3 -0
- package/dist/ui/icons/images/pressure.js +3 -0
- package/dist/ui/icons/images/randomise.js +3 -0
- package/dist/ui/icons/images/rectangle-mask.js +3 -0
- package/dist/ui/icons/images/redo.js +3 -0
- package/dist/ui/icons/images/rotate-90-left.js +3 -0
- package/dist/ui/icons/images/rotate-90-right.js +3 -0
- package/dist/ui/icons/images/smoothing-bezier-cubic.js +3 -0
- package/dist/ui/icons/images/smoothing-bezier-quad.js +3 -0
- package/dist/ui/icons/images/smoothing-linear.js +3 -0
- package/dist/ui/icons/images/star.js +3 -0
- package/dist/ui/icons/images/sun.js +3 -0
- package/dist/ui/icons/images/tilt.js +3 -0
- package/dist/ui/icons/images/undo.js +3 -0
- package/dist/ui/icons/images/unlinked.js +3 -0
- package/dist/ui/icons/images/unlocked.js +3 -0
- package/dist/ui/icons/images/visible.js +3 -0
- package/dist/ui/icons/images/zoom-in.js +3 -0
- package/dist/ui/icons/images/zoom-out.js +3 -0
- package/dist/ui/icons/images/zoom.js +3 -0
- package/dist/ui/icons/index.d.ts +42 -0
- package/dist/ui/icons/index.js +130 -0
- package/dist/ui/index.d.ts +21 -0
- package/dist/ui/index.js +22 -0
- package/dist/ui/layout/index.d.ts +88 -0
- package/dist/ui/layout/index.js +96 -0
- package/dist/ui/styles/index.d.ts +13 -0
- package/dist/ui/styles/index.js +195 -0
- package/dist/ui/tokens/index.d.ts +29 -0
- package/dist/ui/tokens/index.js +114 -0
- package/locales/en-GB.json +186 -0
- package/locales/en-US.json +186 -0
- package/locales/fr.json +186 -0
- package/locales/ja.json +186 -0
- package/locales/ko.json +186 -0
- package/locales/zh-Hans.json +186 -0
- package/locales/zh-Hant.json +186 -0
- package/package.json +97 -3
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/** Default number of log entries kept */
|
|
2
|
+
export declare const DEFAULT_STROKE_LOG_LIMIT = 200;
|
|
3
|
+
/**
|
|
4
|
+
* What a stroke log entry is about:
|
|
5
|
+
* - `start`: a stroke started
|
|
6
|
+
* - `end`: a stroke finished, with its measurements
|
|
7
|
+
* - `cancel`: a stroke in progress was abandoned, with its measurements so far
|
|
8
|
+
* - `ignore`: a pointer press didn't start a stroke
|
|
9
|
+
* - `gesture`: a touch gesture did an action (e.g. undo)
|
|
10
|
+
* - `work`: other work that kept the page busy (e.g. syncing the drawing)
|
|
11
|
+
*/
|
|
12
|
+
export type StrokeLogKind = "start" | "end" | "cancel" | "ignore" | "gesture" | "work";
|
|
13
|
+
/** A logged event */
|
|
14
|
+
export interface StrokeLogEntry {
|
|
15
|
+
/** Time the entry was logged (Unix epoch milliseconds) */
|
|
16
|
+
at: number;
|
|
17
|
+
kind: StrokeLogKind;
|
|
18
|
+
/** What happened, e.g. the pointer type, why a stroke was cancelled, or the work done */
|
|
19
|
+
detail: string;
|
|
20
|
+
/** Measurements by name: times in milliseconds, otherwise counts */
|
|
21
|
+
measures: Record<string, number>;
|
|
22
|
+
}
|
|
23
|
+
/** What was on screen when a stroke started */
|
|
24
|
+
export interface StrokeScene {
|
|
25
|
+
/** Layers drawn (visible, including other people's drawings) */
|
|
26
|
+
layers: number;
|
|
27
|
+
/** Zoom as a percentage */
|
|
28
|
+
zoomPct: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Records when brush and eraser strokes start, end, are cancelled or are ignored, with timings for each stroke, plus
|
|
32
|
+
* other work done meanwhile (see `BackgroundWork`), for the debug panel. Times are in the same clock as `event.timeStamp` and
|
|
33
|
+
* `performance.now()`. Keeps only the most recent entries.
|
|
34
|
+
*
|
|
35
|
+
* Each finished or cancelled stroke measures (in ms unless noted):
|
|
36
|
+
* - `gap`: time since the previous stroke ended
|
|
37
|
+
* - `wait`: how long the press waited before it was handled (the page was busy)
|
|
38
|
+
* - `begin`: time taken to start the stroke
|
|
39
|
+
* - `moves`: pointer events handled (count), `maxMoveWait`: longest one waited, `moveMs`: total time handling them
|
|
40
|
+
* - `frames`: frames drawn (count), `maxFrame`: longest frame to draw, `maxFrameGap`: longest time between frames
|
|
41
|
+
* (including after the last one), `overlays`: times the brush cursor was drawn (count)
|
|
42
|
+
* - `end`: time taken to write the stroke into its layer
|
|
43
|
+
* - `duration`: from press to release
|
|
44
|
+
* - `layers`: layers drawn (count) and `zoomPct`: the zoom as a percentage, when the stroke started (if given)
|
|
45
|
+
*
|
|
46
|
+
* Records nothing while switched off (`enabled`); work given to `measure` still runs.
|
|
47
|
+
*
|
|
48
|
+
* Dispatches a `change` event when entries are added or cleared.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* const log = new StrokeLog();
|
|
52
|
+
* log.begin("pen", event.timeStamp, 2);
|
|
53
|
+
* log.move(event.timeStamp, 0.5);
|
|
54
|
+
* log.end(event.timeStamp, 3); // entry with kind "end" and its measures
|
|
55
|
+
*/
|
|
56
|
+
export default class StrokeLog extends EventTarget {
|
|
57
|
+
/** Maximum number of entries kept */
|
|
58
|
+
readonly limit: number;
|
|
59
|
+
/** Current time in milliseconds (replaceable for tests) */
|
|
60
|
+
now: () => number;
|
|
61
|
+
/** Whether anything is recorded */
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
private items;
|
|
64
|
+
private stroke?;
|
|
65
|
+
/** When the last stroke ended or was cancelled (event time) */
|
|
66
|
+
private lastEnd?;
|
|
67
|
+
constructor(limit?: number);
|
|
68
|
+
/** Logged entries, oldest first */
|
|
69
|
+
get entries(): readonly StrokeLogEntry[];
|
|
70
|
+
/** Whether a stroke is being measured */
|
|
71
|
+
get drawing(): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* A stroke started: the pointer type, when it pressed (event time), how long starting the stroke took, and what was on
|
|
74
|
+
* screen.
|
|
75
|
+
*/
|
|
76
|
+
begin(pointerType: string, eventTime: number, beginMs: number, scene?: StrokeScene): void;
|
|
77
|
+
/**
|
|
78
|
+
* The stroke continued: when the pointer moved (event time) and how long handling it took.
|
|
79
|
+
*/
|
|
80
|
+
move(eventTime: number, moveMs: number): void;
|
|
81
|
+
/**
|
|
82
|
+
* A frame was drawn, taking `ms`; counted towards the stroke in progress, if any.
|
|
83
|
+
*/
|
|
84
|
+
frame(ms: number): void;
|
|
85
|
+
/**
|
|
86
|
+
* The brush cursor was drawn; counted towards the stroke in progress, if any.
|
|
87
|
+
*/
|
|
88
|
+
overlay(): void;
|
|
89
|
+
/**
|
|
90
|
+
* The stroke finished: when the pointer released (event time) and how long writing it into the layer took.
|
|
91
|
+
*/
|
|
92
|
+
end(eventTime: number, endMs: number): void;
|
|
93
|
+
/**
|
|
94
|
+
* The stroke in progress was abandoned, and why.
|
|
95
|
+
*/
|
|
96
|
+
cancel(reason: string): void;
|
|
97
|
+
/**
|
|
98
|
+
* A pointer press didn't start a stroke, and why.
|
|
99
|
+
*/
|
|
100
|
+
ignore(pointerType: string, reason: string): void;
|
|
101
|
+
/**
|
|
102
|
+
* A touch gesture did an action.
|
|
103
|
+
*/
|
|
104
|
+
gesture(gesture: string, action: string): void;
|
|
105
|
+
/**
|
|
106
|
+
* Other work was done, with its measurements: `ms` for how long it kept the page busy, and `total` for how long work
|
|
107
|
+
* that finishes later took altogether (which can include waiting that doesn't keep the page busy).
|
|
108
|
+
*/
|
|
109
|
+
work(name: string, measures: {
|
|
110
|
+
ms?: number;
|
|
111
|
+
total?: number;
|
|
112
|
+
}): void;
|
|
113
|
+
/**
|
|
114
|
+
* Run some work and return its result, logging how long it kept the page busy, or for work that finishes later (a
|
|
115
|
+
* promise), how long it took altogether once it settles.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* log.measure("map", () => drawMap()); // logs "map" with how long it took
|
|
119
|
+
* log.measure("save", () => save()); // logs "save" with how long it took once saved (or failed)
|
|
120
|
+
*/
|
|
121
|
+
measure<T>(name: string, run: () => T): T;
|
|
122
|
+
/**
|
|
123
|
+
* Remove all entries.
|
|
124
|
+
*/
|
|
125
|
+
clear(): void;
|
|
126
|
+
/** Log a finished or cancelled stroke with its measurements, and stop measuring it */
|
|
127
|
+
private finish;
|
|
128
|
+
/** Add an entry, rounding its measurements (unless switched off) */
|
|
129
|
+
private add;
|
|
130
|
+
}
|
|
131
|
+
/** The stroke log's CSV columns after time, event and detail: every measurement, in order */
|
|
132
|
+
export declare const STROKE_LOG_COLUMNS: readonly ["gap", "wait", "begin", "moves", "maxMoveWait", "moveMs", "frames", "maxFrame", "maxFrameGap", "overlays", "end", "duration", "layers", "zoomPct", "ms", "total"];
|
|
133
|
+
/**
|
|
134
|
+
* A CSV field, quoted if it holds a comma, quote or line break.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* csvField('a "b", c'); // '"a ""b"", c"'
|
|
138
|
+
*/
|
|
139
|
+
export declare const csvField: (value: string) => string;
|
|
140
|
+
/**
|
|
141
|
+
* Stroke log entries as CSV, oldest first, to paste somewhere for analysis: `#` lines describing the context first (e.g.
|
|
142
|
+
* device and canvas), then a header row and one row per entry. Times are ISO 8601; measurements are in milliseconds
|
|
143
|
+
* except counts (`moves`, `frames`, `layers`) and `zoomPct`; empty fields don't apply to that entry.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* strokeLogCsv(log.entries, { version: "1.13.0" });
|
|
147
|
+
* // "# version: 1.13.0\ntime,event,detail,gap,…\n2026-10-31T20:37:55.545Z,start,mouse,6.7,0.2,…"
|
|
148
|
+
*/
|
|
149
|
+
export declare const strokeLogCsv: (entries: readonly StrokeLogEntry[], context?: Record<string, string | number>) => string;
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// src/lib/classes/StrokeLog/index.ts
|
|
2
|
+
/** Default number of log entries kept */
|
|
3
|
+
var DEFAULT_STROKE_LOG_LIMIT = 200;
|
|
4
|
+
/** Round a measurement to 0.1 */
|
|
5
|
+
var round = (value) => Math.round(value * 10) / 10;
|
|
6
|
+
/**
|
|
7
|
+
* Records when brush and eraser strokes start, end, are cancelled or are ignored, with timings for each stroke, plus
|
|
8
|
+
* other work done meanwhile (see `BackgroundWork`), for the debug panel. Times are in the same clock as `event.timeStamp` and
|
|
9
|
+
* `performance.now()`. Keeps only the most recent entries.
|
|
10
|
+
*
|
|
11
|
+
* Each finished or cancelled stroke measures (in ms unless noted):
|
|
12
|
+
* - `gap`: time since the previous stroke ended
|
|
13
|
+
* - `wait`: how long the press waited before it was handled (the page was busy)
|
|
14
|
+
* - `begin`: time taken to start the stroke
|
|
15
|
+
* - `moves`: pointer events handled (count), `maxMoveWait`: longest one waited, `moveMs`: total time handling them
|
|
16
|
+
* - `frames`: frames drawn (count), `maxFrame`: longest frame to draw, `maxFrameGap`: longest time between frames
|
|
17
|
+
* (including after the last one), `overlays`: times the brush cursor was drawn (count)
|
|
18
|
+
* - `end`: time taken to write the stroke into its layer
|
|
19
|
+
* - `duration`: from press to release
|
|
20
|
+
* - `layers`: layers drawn (count) and `zoomPct`: the zoom as a percentage, when the stroke started (if given)
|
|
21
|
+
*
|
|
22
|
+
* Records nothing while switched off (`enabled`); work given to `measure` still runs.
|
|
23
|
+
*
|
|
24
|
+
* Dispatches a `change` event when entries are added or cleared.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* const log = new StrokeLog();
|
|
28
|
+
* log.begin("pen", event.timeStamp, 2);
|
|
29
|
+
* log.move(event.timeStamp, 0.5);
|
|
30
|
+
* log.end(event.timeStamp, 3); // entry with kind "end" and its measures
|
|
31
|
+
*/
|
|
32
|
+
var StrokeLog = class extends EventTarget {
|
|
33
|
+
/** Maximum number of entries kept */
|
|
34
|
+
limit;
|
|
35
|
+
/** Current time in milliseconds (replaceable for tests) */
|
|
36
|
+
now = () => performance.now();
|
|
37
|
+
/** Whether anything is recorded */
|
|
38
|
+
enabled = true;
|
|
39
|
+
items = [];
|
|
40
|
+
stroke;
|
|
41
|
+
/** When the last stroke ended or was cancelled (event time) */
|
|
42
|
+
lastEnd;
|
|
43
|
+
constructor(limit = 200) {
|
|
44
|
+
super();
|
|
45
|
+
this.limit = limit;
|
|
46
|
+
}
|
|
47
|
+
/** Logged entries, oldest first */
|
|
48
|
+
get entries() {
|
|
49
|
+
return this.items;
|
|
50
|
+
}
|
|
51
|
+
/** Whether a stroke is being measured */
|
|
52
|
+
get drawing() {
|
|
53
|
+
return this.stroke !== void 0;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A stroke started: the pointer type, when it pressed (event time), how long starting the stroke took, and what was on
|
|
57
|
+
* screen.
|
|
58
|
+
*/
|
|
59
|
+
begin(pointerType, eventTime, beginMs, scene) {
|
|
60
|
+
if (!this.enabled) return;
|
|
61
|
+
const handled = this.now() - beginMs;
|
|
62
|
+
this.stroke = {
|
|
63
|
+
scene,
|
|
64
|
+
pointerType,
|
|
65
|
+
startTime: eventTime,
|
|
66
|
+
startWait: Math.max(0, handled - eventTime),
|
|
67
|
+
beginMs,
|
|
68
|
+
moves: 0,
|
|
69
|
+
maxMoveWait: 0,
|
|
70
|
+
moveMs: 0,
|
|
71
|
+
frames: 0,
|
|
72
|
+
maxFrameMs: 0,
|
|
73
|
+
lastFrameAt: handled,
|
|
74
|
+
maxFrameGap: 0,
|
|
75
|
+
overlays: 0,
|
|
76
|
+
gap: this.lastEnd === void 0 ? void 0 : eventTime - this.lastEnd
|
|
77
|
+
};
|
|
78
|
+
this.add("start", pointerType, {
|
|
79
|
+
wait: this.stroke.startWait,
|
|
80
|
+
...this.stroke.gap === void 0 ? {} : { gap: this.stroke.gap },
|
|
81
|
+
...scene
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The stroke continued: when the pointer moved (event time) and how long handling it took.
|
|
86
|
+
*/
|
|
87
|
+
move(eventTime, moveMs) {
|
|
88
|
+
const stroke = this.stroke;
|
|
89
|
+
if (!stroke) return;
|
|
90
|
+
const handled = this.now() - moveMs;
|
|
91
|
+
stroke.moves++;
|
|
92
|
+
stroke.moveMs += moveMs;
|
|
93
|
+
stroke.maxMoveWait = Math.max(stroke.maxMoveWait, handled - eventTime);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A frame was drawn, taking `ms`; counted towards the stroke in progress, if any.
|
|
97
|
+
*/
|
|
98
|
+
frame(ms) {
|
|
99
|
+
const stroke = this.stroke;
|
|
100
|
+
if (!stroke) return;
|
|
101
|
+
const now = this.now();
|
|
102
|
+
stroke.frames++;
|
|
103
|
+
stroke.maxFrameMs = Math.max(stroke.maxFrameMs, ms);
|
|
104
|
+
stroke.maxFrameGap = Math.max(stroke.maxFrameGap, now - stroke.lastFrameAt);
|
|
105
|
+
stroke.lastFrameAt = now;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The brush cursor was drawn; counted towards the stroke in progress, if any.
|
|
109
|
+
*/
|
|
110
|
+
overlay() {
|
|
111
|
+
if (this.stroke) this.stroke.overlays++;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The stroke finished: when the pointer released (event time) and how long writing it into the layer took.
|
|
115
|
+
*/
|
|
116
|
+
end(eventTime, endMs) {
|
|
117
|
+
const stroke = this.stroke;
|
|
118
|
+
if (!stroke) return;
|
|
119
|
+
this.finish("end", stroke.pointerType, eventTime, { end: endMs });
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The stroke in progress was abandoned, and why.
|
|
123
|
+
*/
|
|
124
|
+
cancel(reason) {
|
|
125
|
+
const stroke = this.stroke;
|
|
126
|
+
if (!stroke) return;
|
|
127
|
+
this.finish("cancel", `${stroke.pointerType}: ${reason}`, this.now(), {});
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* A pointer press didn't start a stroke, and why.
|
|
131
|
+
*/
|
|
132
|
+
ignore(pointerType, reason) {
|
|
133
|
+
this.add("ignore", `${pointerType}: ${reason}`, {});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* A touch gesture did an action.
|
|
137
|
+
*/
|
|
138
|
+
gesture(gesture, action) {
|
|
139
|
+
this.add("gesture", `${gesture}: ${action}`, {});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Other work was done, with its measurements: `ms` for how long it kept the page busy, and `total` for how long work
|
|
143
|
+
* that finishes later took altogether (which can include waiting that doesn't keep the page busy).
|
|
144
|
+
*/
|
|
145
|
+
work(name, measures) {
|
|
146
|
+
this.add("work", name, measures);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Run some work and return its result, logging how long it kept the page busy, or for work that finishes later (a
|
|
150
|
+
* promise), how long it took altogether once it settles.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* log.measure("map", () => drawMap()); // logs "map" with how long it took
|
|
154
|
+
* log.measure("save", () => save()); // logs "save" with how long it took once saved (or failed)
|
|
155
|
+
*/
|
|
156
|
+
measure(name, run) {
|
|
157
|
+
if (!this.enabled) return run();
|
|
158
|
+
const start = this.now();
|
|
159
|
+
const result = run();
|
|
160
|
+
if (result instanceof Promise) {
|
|
161
|
+
const done = () => this.work(name, { total: this.now() - start });
|
|
162
|
+
result.then(done, done);
|
|
163
|
+
} else this.work(name, { ms: this.now() - start });
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Remove all entries.
|
|
168
|
+
*/
|
|
169
|
+
clear() {
|
|
170
|
+
this.items = [];
|
|
171
|
+
this.dispatchEvent(new Event("change"));
|
|
172
|
+
}
|
|
173
|
+
/** Log a finished or cancelled stroke with its measurements, and stop measuring it */
|
|
174
|
+
finish(kind, detail, eventTime, extra) {
|
|
175
|
+
const stroke = this.stroke;
|
|
176
|
+
this.stroke = void 0;
|
|
177
|
+
this.lastEnd = eventTime;
|
|
178
|
+
this.add(kind, detail, {
|
|
179
|
+
...stroke.gap === void 0 ? {} : { gap: stroke.gap },
|
|
180
|
+
wait: stroke.startWait,
|
|
181
|
+
begin: stroke.beginMs,
|
|
182
|
+
moves: stroke.moves,
|
|
183
|
+
maxMoveWait: stroke.maxMoveWait,
|
|
184
|
+
moveMs: stroke.moveMs,
|
|
185
|
+
frames: stroke.frames,
|
|
186
|
+
maxFrame: stroke.maxFrameMs,
|
|
187
|
+
maxFrameGap: Math.max(stroke.maxFrameGap, this.now() - stroke.lastFrameAt),
|
|
188
|
+
overlays: stroke.overlays,
|
|
189
|
+
...extra,
|
|
190
|
+
duration: eventTime - stroke.startTime,
|
|
191
|
+
...stroke.scene
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/** Add an entry, rounding its measurements (unless switched off) */
|
|
195
|
+
add(kind, detail, measures) {
|
|
196
|
+
if (!this.enabled) return;
|
|
197
|
+
const rounded = Object.fromEntries(Object.entries(measures).map(([name, value]) => [name, round(value)]));
|
|
198
|
+
this.items = [...this.items, {
|
|
199
|
+
at: Date.now(),
|
|
200
|
+
kind,
|
|
201
|
+
detail,
|
|
202
|
+
measures: rounded
|
|
203
|
+
}].slice(-this.limit);
|
|
204
|
+
this.dispatchEvent(new Event("change"));
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
/** The stroke log's CSV columns after time, event and detail: every measurement, in order */
|
|
208
|
+
var STROKE_LOG_COLUMNS = [
|
|
209
|
+
"gap",
|
|
210
|
+
"wait",
|
|
211
|
+
"begin",
|
|
212
|
+
"moves",
|
|
213
|
+
"maxMoveWait",
|
|
214
|
+
"moveMs",
|
|
215
|
+
"frames",
|
|
216
|
+
"maxFrame",
|
|
217
|
+
"maxFrameGap",
|
|
218
|
+
"overlays",
|
|
219
|
+
"end",
|
|
220
|
+
"duration",
|
|
221
|
+
"layers",
|
|
222
|
+
"zoomPct",
|
|
223
|
+
"ms",
|
|
224
|
+
"total"
|
|
225
|
+
];
|
|
226
|
+
/**
|
|
227
|
+
* A CSV field, quoted if it holds a comma, quote or line break.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* csvField('a "b", c'); // '"a ""b"", c"'
|
|
231
|
+
*/
|
|
232
|
+
var csvField = (value) => /[",\n\r]/.test(value) ? `"${value.replaceAll("\"", "\"\"")}"` : value;
|
|
233
|
+
/**
|
|
234
|
+
* Stroke log entries as CSV, oldest first, to paste somewhere for analysis: `#` lines describing the context first (e.g.
|
|
235
|
+
* device and canvas), then a header row and one row per entry. Times are ISO 8601; measurements are in milliseconds
|
|
236
|
+
* except counts (`moves`, `frames`, `layers`) and `zoomPct`; empty fields don't apply to that entry.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* strokeLogCsv(log.entries, { version: "1.13.0" });
|
|
240
|
+
* // "# version: 1.13.0\ntime,event,detail,gap,…\n2026-10-31T20:37:55.545Z,start,mouse,6.7,0.2,…"
|
|
241
|
+
*/
|
|
242
|
+
var strokeLogCsv = (entries, context = {}) => [
|
|
243
|
+
...Object.entries(context).map(([name, value]) => `# ${name}: ${value}`),
|
|
244
|
+
[
|
|
245
|
+
"time",
|
|
246
|
+
"event",
|
|
247
|
+
"detail",
|
|
248
|
+
...STROKE_LOG_COLUMNS
|
|
249
|
+
].join(","),
|
|
250
|
+
...entries.map(({ at, kind, detail, measures }) => [
|
|
251
|
+
new Date(at).toISOString(),
|
|
252
|
+
kind,
|
|
253
|
+
csvField(detail),
|
|
254
|
+
...STROKE_LOG_COLUMNS.map((name) => name in measures ? String(measures[name]) : "")
|
|
255
|
+
].join(","))
|
|
256
|
+
].join("\n");
|
|
257
|
+
export { DEFAULT_STROKE_LOG_LIMIT, STROKE_LOG_COLUMNS, csvField, StrokeLog as default, strokeLogCsv };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/** A point in screen (CSS pixel) or canvas (image pixel) coordinates */
|
|
2
|
+
export interface Point {
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
}
|
|
6
|
+
/** A rectangle in canvas pixels */
|
|
7
|
+
export interface Rect {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
/** Space reserved on each side of the screen by the interface (e.g. toolbar and panels), in CSS pixels */
|
|
14
|
+
export interface Insets {
|
|
15
|
+
top: number;
|
|
16
|
+
right: number;
|
|
17
|
+
bottom: number;
|
|
18
|
+
left: number;
|
|
19
|
+
}
|
|
20
|
+
/** Zoom steps used by the zoom tool, from 1/16 to 32× */
|
|
21
|
+
export declare const ZOOM_LEVELS: readonly [0.0625, 0.125, 0.25, number, 0.5, number, 1, 1.5, 2, 3, 4, 6, 8, 12, 16, 24, 32];
|
|
22
|
+
/** Smallest zoom */
|
|
23
|
+
export declare const MIN_ZOOM: number;
|
|
24
|
+
/** Largest zoom */
|
|
25
|
+
export declare const MAX_ZOOM: number;
|
|
26
|
+
/** Screen space (CSS pixels) of canvas that must stay visible so it can't be panned out of reach */
|
|
27
|
+
export declare const MIN_VISIBLE = 64;
|
|
28
|
+
/** Space left around the canvas when fitting it to the screen, in CSS pixels */
|
|
29
|
+
export declare const FIT_PADDING = 24;
|
|
30
|
+
/**
|
|
31
|
+
* The user's view of the canvas: zoom level and position. Converts between screen and canvas coordinates.
|
|
32
|
+
*
|
|
33
|
+
* The canvas pixel at `(0, 0)` is drawn at screen position `(offsetX, offsetY)`, and one canvas pixel is `zoom` CSS pixels wide.
|
|
34
|
+
*
|
|
35
|
+
* Dispatches a `change` event whenever the view moves or zooms.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const view = new Viewport(2000, 2000);
|
|
39
|
+
* view.resize(1280, 800);
|
|
40
|
+
* view.fit();
|
|
41
|
+
* view.zoomAt({ x: 640, y: 400 }, 2);
|
|
42
|
+
* view.screenToCanvas({ x: 640, y: 400 }); // canvas point under the screen centre
|
|
43
|
+
*/
|
|
44
|
+
export default class Viewport extends EventTarget {
|
|
45
|
+
/** Canvas width in pixels */
|
|
46
|
+
readonly canvasWidth: number;
|
|
47
|
+
/** Canvas height in pixels */
|
|
48
|
+
readonly canvasHeight: number;
|
|
49
|
+
/** Screen width in CSS pixels */
|
|
50
|
+
width: number;
|
|
51
|
+
/** Screen height in CSS pixels */
|
|
52
|
+
height: number;
|
|
53
|
+
/** CSS pixels per canvas pixel */
|
|
54
|
+
zoom: number;
|
|
55
|
+
/** Screen X of the canvas's left edge */
|
|
56
|
+
offsetX: number;
|
|
57
|
+
/** Screen Y of the canvas's top edge */
|
|
58
|
+
offsetY: number;
|
|
59
|
+
constructor(canvasWidth: number, canvasHeight: number);
|
|
60
|
+
/**
|
|
61
|
+
* Convert a screen point to canvas pixels.
|
|
62
|
+
*/
|
|
63
|
+
screenToCanvas({ x, y }: Point): Point;
|
|
64
|
+
/**
|
|
65
|
+
* Convert a canvas point to screen (CSS) pixels.
|
|
66
|
+
*/
|
|
67
|
+
canvasToScreen({ x, y }: Point): Point;
|
|
68
|
+
/**
|
|
69
|
+
* The part of the canvas currently visible on screen, clipped to the canvas.
|
|
70
|
+
*/
|
|
71
|
+
visibleRect(): Rect;
|
|
72
|
+
/**
|
|
73
|
+
* Set the screen size (e.g. when the window resizes). Keeps the view in reach.
|
|
74
|
+
*/
|
|
75
|
+
resize(width: number, height: number): void;
|
|
76
|
+
/**
|
|
77
|
+
* Zoom and centre so the whole canvas fits in the space not covered by the interface (never zooming in past 100%).
|
|
78
|
+
*/
|
|
79
|
+
fit(insets?: Insets): void;
|
|
80
|
+
/**
|
|
81
|
+
* Move the view so a canvas point is in the middle of the space not covered by the interface (e.g. clicking the canvas map).
|
|
82
|
+
*/
|
|
83
|
+
centreOn({ x, y }: Point, insets?: Insets): void;
|
|
84
|
+
/**
|
|
85
|
+
* Move the view by a distance in screen pixels (e.g. while dragging with the pan tool).
|
|
86
|
+
*/
|
|
87
|
+
panBy(dx: number, dy: number): void;
|
|
88
|
+
/**
|
|
89
|
+
* Zoom to a level, keeping the canvas point under `anchor` (a screen point) in place.
|
|
90
|
+
*/
|
|
91
|
+
zoomAt(anchor: Point, zoom: number): void;
|
|
92
|
+
/**
|
|
93
|
+
* Zoom in one step around a screen point (zoom tool click).
|
|
94
|
+
*/
|
|
95
|
+
zoomIn(anchor: Point): void;
|
|
96
|
+
/**
|
|
97
|
+
* Zoom out one step around a screen point (zoom tool Alt+click).
|
|
98
|
+
*/
|
|
99
|
+
zoomOut(anchor: Point): void;
|
|
100
|
+
/**
|
|
101
|
+
* Keep at least `MIN_VISIBLE` pixels of the canvas on screen in each direction.
|
|
102
|
+
*/
|
|
103
|
+
private constrain;
|
|
104
|
+
/**
|
|
105
|
+
* Dispatch a `change` event.
|
|
106
|
+
*/
|
|
107
|
+
private notify;
|
|
108
|
+
}
|