js-draw 1.9.1 → 1.11.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/dist/Editor.css +48 -1
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +41 -0
- package/dist/cjs/Editor.js +9 -0
- package/dist/cjs/Pointer.js +1 -1
- package/dist/cjs/commands/Erase.d.ts +22 -2
- package/dist/cjs/commands/Erase.js +22 -2
- package/dist/cjs/commands/invertCommand.js +5 -0
- package/dist/cjs/commands/uniteCommands.d.ts +36 -0
- package/dist/cjs/commands/uniteCommands.js +36 -0
- package/dist/cjs/components/AbstractComponent.d.ts +8 -0
- package/dist/cjs/components/AbstractComponent.js +28 -8
- package/dist/cjs/components/ImageComponent.d.ts +12 -0
- package/dist/cjs/components/ImageComponent.js +16 -9
- package/dist/cjs/components/Stroke.d.ts +16 -2
- package/dist/cjs/components/Stroke.js +17 -1
- package/dist/cjs/components/builders/ArrowBuilder.js +3 -3
- package/dist/cjs/components/builders/CircleBuilder.js +3 -3
- package/dist/cjs/components/builders/FreehandLineBuilder.js +3 -3
- package/dist/cjs/components/builders/LineBuilder.js +3 -3
- package/dist/cjs/components/builders/PressureSensitiveFreehandLineBuilder.js +3 -3
- package/dist/cjs/components/builders/RectangleBuilder.js +5 -6
- package/dist/cjs/components/builders/autocorrect/makeShapeFitAutocorrect.d.ts +3 -0
- package/dist/cjs/components/builders/autocorrect/makeShapeFitAutocorrect.js +168 -0
- package/dist/cjs/components/builders/autocorrect/makeSnapToGridAutocorrect.d.ts +3 -0
- package/dist/cjs/components/builders/autocorrect/makeSnapToGridAutocorrect.js +46 -0
- package/dist/cjs/components/builders/types.d.ts +12 -0
- package/dist/cjs/image/EditorImage.d.ts +32 -1
- package/dist/cjs/image/EditorImage.js +32 -1
- package/dist/cjs/rendering/RenderablePathSpec.d.ts +5 -1
- package/dist/cjs/rendering/RenderablePathSpec.js +4 -0
- package/dist/cjs/toolbar/AbstractToolbar.d.ts +18 -2
- package/dist/cjs/toolbar/AbstractToolbar.js +46 -30
- package/dist/cjs/toolbar/IconProvider.d.ts +2 -0
- package/dist/cjs/toolbar/IconProvider.js +17 -0
- package/dist/cjs/toolbar/localization.d.ts +3 -0
- package/dist/cjs/toolbar/localization.js +4 -1
- package/dist/cjs/toolbar/widgets/BaseWidget.js +1 -1
- package/dist/cjs/toolbar/widgets/ExitActionWidget.d.ts +12 -0
- package/dist/cjs/toolbar/widgets/ExitActionWidget.js +32 -0
- package/dist/cjs/toolbar/widgets/HandToolWidget.d.ts +4 -3
- package/dist/cjs/toolbar/widgets/HandToolWidget.js +24 -13
- package/dist/cjs/toolbar/widgets/InsertImageWidget.d.ts +2 -1
- package/dist/cjs/toolbar/widgets/InsertImageWidget.js +102 -22
- package/dist/cjs/toolbar/widgets/PenToolWidget.d.ts +1 -2
- package/dist/cjs/toolbar/widgets/PenToolWidget.js +50 -20
- package/dist/cjs/toolbar/widgets/keybindings.d.ts +1 -0
- package/dist/cjs/toolbar/widgets/keybindings.js +4 -1
- package/dist/cjs/toolbar/widgets/layout/types.d.ts +1 -1
- package/dist/cjs/tools/Pen.d.ts +9 -0
- package/dist/cjs/tools/Pen.js +82 -3
- package/dist/cjs/tools/SelectionTool/Selection.d.ts +4 -0
- package/dist/cjs/tools/SelectionTool/Selection.js +56 -12
- package/dist/cjs/tools/SelectionTool/SelectionTool.d.ts +1 -0
- package/dist/cjs/tools/SelectionTool/SelectionTool.js +19 -1
- package/dist/cjs/tools/TextTool.js +5 -1
- package/dist/cjs/tools/ToolSwitcherShortcut.d.ts +0 -1
- package/dist/cjs/tools/ToolSwitcherShortcut.js +0 -1
- package/dist/cjs/tools/keybindings.d.ts +1 -0
- package/dist/cjs/tools/keybindings.js +3 -1
- package/dist/cjs/tools/util/StationaryPenDetector.d.ts +22 -0
- package/dist/cjs/tools/util/StationaryPenDetector.js +95 -0
- package/dist/cjs/util/ReactiveValue.d.ts +2 -0
- package/dist/cjs/util/ReactiveValue.js +2 -0
- package/dist/cjs/util/lib.d.ts +1 -0
- package/dist/cjs/util/lib.js +4 -1
- package/dist/cjs/util/waitForImageLoaded.d.ts +2 -0
- package/dist/cjs/util/waitForImageLoaded.js +12 -0
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.d.ts +41 -0
- package/dist/mjs/Editor.mjs +9 -0
- package/dist/mjs/Pointer.mjs +1 -1
- package/dist/mjs/commands/Erase.d.ts +22 -2
- package/dist/mjs/commands/Erase.mjs +22 -2
- package/dist/mjs/commands/invertCommand.mjs +5 -0
- package/dist/mjs/commands/uniteCommands.d.ts +36 -0
- package/dist/mjs/commands/uniteCommands.mjs +36 -0
- package/dist/mjs/components/AbstractComponent.d.ts +8 -0
- package/dist/mjs/components/AbstractComponent.mjs +28 -8
- package/dist/mjs/components/ImageComponent.d.ts +12 -0
- package/dist/mjs/components/ImageComponent.mjs +16 -9
- package/dist/mjs/components/Stroke.d.ts +16 -2
- package/dist/mjs/components/Stroke.mjs +17 -1
- package/dist/mjs/components/builders/ArrowBuilder.mjs +3 -2
- package/dist/mjs/components/builders/CircleBuilder.mjs +3 -2
- package/dist/mjs/components/builders/FreehandLineBuilder.mjs +3 -2
- package/dist/mjs/components/builders/LineBuilder.mjs +3 -2
- package/dist/mjs/components/builders/PressureSensitiveFreehandLineBuilder.mjs +3 -2
- package/dist/mjs/components/builders/RectangleBuilder.mjs +5 -4
- package/dist/mjs/components/builders/autocorrect/makeShapeFitAutocorrect.d.ts +3 -0
- package/dist/mjs/components/builders/autocorrect/makeShapeFitAutocorrect.mjs +166 -0
- package/dist/mjs/components/builders/autocorrect/makeSnapToGridAutocorrect.d.ts +3 -0
- package/dist/mjs/components/builders/autocorrect/makeSnapToGridAutocorrect.mjs +44 -0
- package/dist/mjs/components/builders/types.d.ts +12 -0
- package/dist/mjs/image/EditorImage.d.ts +32 -1
- package/dist/mjs/image/EditorImage.mjs +32 -1
- package/dist/mjs/rendering/RenderablePathSpec.d.ts +5 -1
- package/dist/mjs/rendering/RenderablePathSpec.mjs +4 -0
- package/dist/mjs/toolbar/AbstractToolbar.d.ts +18 -2
- package/dist/mjs/toolbar/AbstractToolbar.mjs +46 -30
- package/dist/mjs/toolbar/IconProvider.d.ts +2 -0
- package/dist/mjs/toolbar/IconProvider.mjs +17 -0
- package/dist/mjs/toolbar/localization.d.ts +3 -0
- package/dist/mjs/toolbar/localization.mjs +4 -1
- package/dist/mjs/toolbar/widgets/BaseWidget.mjs +1 -1
- package/dist/mjs/toolbar/widgets/ExitActionWidget.d.ts +12 -0
- package/dist/mjs/toolbar/widgets/ExitActionWidget.mjs +27 -0
- package/dist/mjs/toolbar/widgets/HandToolWidget.d.ts +4 -3
- package/dist/mjs/toolbar/widgets/HandToolWidget.mjs +24 -13
- package/dist/mjs/toolbar/widgets/InsertImageWidget.d.ts +2 -1
- package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +102 -22
- package/dist/mjs/toolbar/widgets/PenToolWidget.d.ts +1 -2
- package/dist/mjs/toolbar/widgets/PenToolWidget.mjs +50 -20
- package/dist/mjs/toolbar/widgets/keybindings.d.ts +1 -0
- package/dist/mjs/toolbar/widgets/keybindings.mjs +3 -0
- package/dist/mjs/toolbar/widgets/layout/types.d.ts +1 -1
- package/dist/mjs/tools/Pen.d.ts +9 -0
- package/dist/mjs/tools/Pen.mjs +82 -3
- package/dist/mjs/tools/SelectionTool/Selection.d.ts +4 -0
- package/dist/mjs/tools/SelectionTool/Selection.mjs +56 -12
- package/dist/mjs/tools/SelectionTool/SelectionTool.d.ts +1 -0
- package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +20 -2
- package/dist/mjs/tools/TextTool.mjs +5 -1
- package/dist/mjs/tools/ToolSwitcherShortcut.d.ts +0 -1
- package/dist/mjs/tools/ToolSwitcherShortcut.mjs +0 -1
- package/dist/mjs/tools/keybindings.d.ts +1 -0
- package/dist/mjs/tools/keybindings.mjs +2 -0
- package/dist/mjs/tools/util/StationaryPenDetector.d.ts +22 -0
- package/dist/mjs/tools/util/StationaryPenDetector.mjs +92 -0
- package/dist/mjs/util/ReactiveValue.d.ts +2 -0
- package/dist/mjs/util/ReactiveValue.mjs +2 -0
- package/dist/mjs/util/lib.d.ts +1 -0
- package/dist/mjs/util/lib.mjs +1 -0
- package/dist/mjs/util/waitForImageLoaded.d.ts +2 -0
- package/dist/mjs/util/waitForImageLoaded.mjs +10 -0
- package/dist/mjs/version.mjs +1 -1
- package/package.json +3 -3
- package/src/Editor.scss +7 -0
- package/src/toolbar/AbstractToolbar.scss +20 -0
- package/src/toolbar/toolbar.scss +1 -1
- package/src/toolbar/widgets/InsertImageWidget.scss +6 -1
- package/src/toolbar/widgets/PenToolWidget.scss +33 -0
- package/src/tools/SelectionTool/SelectionTool.scss +6 -0
- package/src/toolbar/widgets/PenToolWidget.css +0 -2
@@ -0,0 +1,22 @@
|
|
1
|
+
import Pointer from '../../Pointer';
|
2
|
+
interface Config {
|
3
|
+
maxSpeed: number;
|
4
|
+
minTimeSeconds: number;
|
5
|
+
maxRadius: number;
|
6
|
+
}
|
7
|
+
type OnStationaryCallback = (lastPointer: Pointer) => void;
|
8
|
+
export default class StationaryPenDetector {
|
9
|
+
private config;
|
10
|
+
private onStationary;
|
11
|
+
private stationaryStartPointer;
|
12
|
+
private lastPointer;
|
13
|
+
private averageVelocity;
|
14
|
+
private timeout;
|
15
|
+
constructor(startPointer: Pointer, config: Config, onStationary: OnStationaryCallback);
|
16
|
+
onPointerMove(currentPointer: Pointer): boolean | undefined;
|
17
|
+
onPointerUp(pointer: Pointer): void;
|
18
|
+
destroy(): void;
|
19
|
+
private cancelStationaryTimeout;
|
20
|
+
private setStationaryTimeout;
|
21
|
+
}
|
22
|
+
export {};
|
@@ -0,0 +1,95 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const math_1 = require("@js-draw/math");
|
4
|
+
class StationaryPenDetector {
|
5
|
+
// Only handles one pen. As such, `startPointer` should be the same device/finger
|
6
|
+
// as `updatedPointer` in `onPointerMove`.
|
7
|
+
//
|
8
|
+
// A new `StationaryPenDetector` should be created for each gesture.
|
9
|
+
constructor(startPointer, config, onStationary) {
|
10
|
+
this.config = config;
|
11
|
+
this.onStationary = onStationary;
|
12
|
+
this.timeout = null;
|
13
|
+
this.stationaryStartPointer = startPointer;
|
14
|
+
this.lastPointer = startPointer;
|
15
|
+
this.averageVelocity = math_1.Vec2.zero;
|
16
|
+
}
|
17
|
+
// Returns true if stationary
|
18
|
+
onPointerMove(currentPointer) {
|
19
|
+
if (!this.stationaryStartPointer) {
|
20
|
+
// Destoroyed
|
21
|
+
return;
|
22
|
+
}
|
23
|
+
if (currentPointer.id !== this.stationaryStartPointer.id) {
|
24
|
+
return false;
|
25
|
+
}
|
26
|
+
// dx: "Δx" Displacement from last.
|
27
|
+
const dxFromLast = currentPointer.screenPos.minus(this.lastPointer.screenPos);
|
28
|
+
const dxFromStationaryStart = currentPointer.screenPos.minus(this.stationaryStartPointer.screenPos);
|
29
|
+
// dt: Delta time:
|
30
|
+
// /1000: Convert to s.
|
31
|
+
let dtFromLast = (currentPointer.timeStamp - this.lastPointer.timeStamp) / 1000; // s
|
32
|
+
// Don't divide by zero
|
33
|
+
if (dtFromLast === 0) {
|
34
|
+
dtFromLast = 1;
|
35
|
+
}
|
36
|
+
const currentVelocity = dxFromLast.times(1 / dtFromLast); // px/s
|
37
|
+
// Slight smoothing of the velocity to prevent input jitter from affecting the
|
38
|
+
// velocity too significantly.
|
39
|
+
this.averageVelocity = this.averageVelocity.lerp(currentVelocity, 0.5); // px/s
|
40
|
+
const dtFromStart = currentPointer.timeStamp - this.stationaryStartPointer.timeStamp; // ms
|
41
|
+
// If not stationary
|
42
|
+
if (dxFromStationaryStart.length() > this.config.maxRadius
|
43
|
+
|| this.averageVelocity.length() > this.config.maxSpeed
|
44
|
+
|| dtFromStart < this.config.minTimeSeconds) {
|
45
|
+
this.stationaryStartPointer = currentPointer;
|
46
|
+
this.lastPointer = currentPointer;
|
47
|
+
this.setStationaryTimeout(this.config.minTimeSeconds * 1000);
|
48
|
+
return false;
|
49
|
+
}
|
50
|
+
const stationaryTimeoutMs = this.config.minTimeSeconds * 1000 - dtFromStart;
|
51
|
+
this.lastPointer = currentPointer;
|
52
|
+
return stationaryTimeoutMs <= 0;
|
53
|
+
}
|
54
|
+
onPointerUp(pointer) {
|
55
|
+
if (pointer.id !== this.stationaryStartPointer?.id) {
|
56
|
+
this.cancelStationaryTimeout();
|
57
|
+
}
|
58
|
+
}
|
59
|
+
destroy() {
|
60
|
+
this.cancelStationaryTimeout();
|
61
|
+
this.stationaryStartPointer = null;
|
62
|
+
}
|
63
|
+
cancelStationaryTimeout() {
|
64
|
+
if (this.timeout !== null) {
|
65
|
+
clearTimeout(this.timeout);
|
66
|
+
this.timeout = null;
|
67
|
+
}
|
68
|
+
}
|
69
|
+
setStationaryTimeout(timeoutMs) {
|
70
|
+
if (this.timeout !== null) {
|
71
|
+
return;
|
72
|
+
}
|
73
|
+
if (timeoutMs <= 0) {
|
74
|
+
this.onStationary(this.lastPointer);
|
75
|
+
}
|
76
|
+
else {
|
77
|
+
this.timeout = setTimeout(() => {
|
78
|
+
this.timeout = null;
|
79
|
+
if (!this.stationaryStartPointer) {
|
80
|
+
// Destroyed
|
81
|
+
return;
|
82
|
+
}
|
83
|
+
const timeSinceStationaryStart = performance.now() - this.stationaryStartPointer.timeStamp;
|
84
|
+
const timeRemaining = this.config.minTimeSeconds * 1000 - timeSinceStationaryStart;
|
85
|
+
if (timeRemaining <= 0) {
|
86
|
+
this.onStationary(this.lastPointer);
|
87
|
+
}
|
88
|
+
else {
|
89
|
+
this.setStationaryTimeout(timeRemaining);
|
90
|
+
}
|
91
|
+
}, timeoutMs);
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
exports.default = StationaryPenDetector;
|
@@ -13,6 +13,8 @@ type UpdateCallback<T> = (value: T) => void;
|
|
13
13
|
*
|
14
14
|
* Static methods in the `ReactiveValue` and `MutableReactiveValue` classes are
|
15
15
|
* constructors (e.g. `fromImmutable`).
|
16
|
+
*
|
17
|
+
* Avoid extending this class from an external library, as that may not be stable.
|
16
18
|
*/
|
17
19
|
export declare abstract class ReactiveValue<T> {
|
18
20
|
/**
|
@@ -34,6 +34,8 @@ const noOpSetUpdateListener = () => {
|
|
34
34
|
*
|
35
35
|
* Static methods in the `ReactiveValue` and `MutableReactiveValue` classes are
|
36
36
|
* constructors (e.g. `fromImmutable`).
|
37
|
+
*
|
38
|
+
* Avoid extending this class from an external library, as that may not be stable.
|
37
39
|
*/
|
38
40
|
class ReactiveValue {
|
39
41
|
/** Creates a `ReactiveValue` with an initial value, `initialValue`. */
|
package/dist/cjs/util/lib.d.ts
CHANGED
package/dist/cjs/util/lib.js
CHANGED
@@ -3,6 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
-
exports.adjustEditorThemeForContrast = void 0;
|
6
|
+
exports.MutableReactiveValue = exports.ReactiveValue = exports.adjustEditorThemeForContrast = void 0;
|
7
7
|
var adjustEditorThemeForContrast_1 = require("./adjustEditorThemeForContrast");
|
8
8
|
Object.defineProperty(exports, "adjustEditorThemeForContrast", { enumerable: true, get: function () { return __importDefault(adjustEditorThemeForContrast_1).default; } });
|
9
|
+
var ReactiveValue_1 = require("./ReactiveValue");
|
10
|
+
Object.defineProperty(exports, "ReactiveValue", { enumerable: true, get: function () { return ReactiveValue_1.ReactiveValue; } });
|
11
|
+
Object.defineProperty(exports, "MutableReactiveValue", { enumerable: true, get: function () { return ReactiveValue_1.MutableReactiveValue; } });
|
@@ -0,0 +1,12 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const waitForImageLoad = async (image) => {
|
4
|
+
if (!image.complete) {
|
5
|
+
await new Promise((resolve, reject) => {
|
6
|
+
image.onload = event => resolve(event);
|
7
|
+
image.onerror = event => reject(event);
|
8
|
+
image.onabort = event => reject(event);
|
9
|
+
});
|
10
|
+
}
|
11
|
+
};
|
12
|
+
exports.default = waitForImageLoad;
|
package/dist/cjs/version.js
CHANGED
package/dist/mjs/Editor.d.ts
CHANGED
@@ -156,6 +156,38 @@ export declare class Editor {
|
|
156
156
|
readonly toolController: ToolController;
|
157
157
|
/**
|
158
158
|
* Global event dispatcher/subscriber.
|
159
|
+
*
|
160
|
+
* @example
|
161
|
+
*
|
162
|
+
* ```ts,runnable
|
163
|
+
* import { Editor, EditorEventType, SerializableCommand } from 'js-draw';
|
164
|
+
*
|
165
|
+
* // Create a minimal editor
|
166
|
+
* const editor = new Editor(document.body);
|
167
|
+
* editor.addToolbar();
|
168
|
+
*
|
169
|
+
* // Create a place to show text output
|
170
|
+
* const log = document.createElement('textarea');
|
171
|
+
* document.body.appendChild(log);
|
172
|
+
* log.style.width = '100%';
|
173
|
+
* log.style.height = '200px';
|
174
|
+
*
|
175
|
+
* // Listen for CommandDone events (there's also a CommandUndone)
|
176
|
+
* editor.notifier.on(EditorEventType.CommandDone, event => {
|
177
|
+
* // Type narrowing for TypeScript -- event will always be of kind CommandDone,
|
178
|
+
* // but TypeScript doesn't know this.
|
179
|
+
* if (event.kind !== EditorEventType.CommandDone) return;
|
180
|
+
*
|
181
|
+
* log.value = `Command done ${event.command.description(editor, editor.localization)}\n`;
|
182
|
+
*
|
183
|
+
* if (event.command instanceof SerializableCommand) {
|
184
|
+
* log.value += `serializes to: ${JSON.stringify(event.command.serialize())}`;
|
185
|
+
* }
|
186
|
+
* });
|
187
|
+
*
|
188
|
+
* // Dispatch an initial command to trigger the event listener for the first time
|
189
|
+
* editor.dispatch(editor.image.setAutoresizeEnabled(true));
|
190
|
+
* ```
|
159
191
|
*/
|
160
192
|
readonly notifier: EditorNotifier;
|
161
193
|
private loadingWarning;
|
@@ -395,6 +427,12 @@ export declare class Editor {
|
|
395
427
|
* @see {@link sendPenEvent} {@link sendTouchEvent}
|
396
428
|
*/
|
397
429
|
sendPenEvent(eventType: InputEvtType.PointerDownEvt | InputEvtType.PointerMoveEvt | InputEvtType.PointerUpEvt, point: Point2, allPointers?: Pointer[]): void;
|
430
|
+
/**
|
431
|
+
* Adds all components in `components` such that they are in the center of the screen.
|
432
|
+
* This is a convenience method that creates **and applies** a single command.
|
433
|
+
*
|
434
|
+
* If `selectComponents` is true (the default), the components are selected.
|
435
|
+
*/
|
398
436
|
addAndCenterComponents(components: AbstractComponent[], selectComponents?: boolean): Promise<void>;
|
399
437
|
/**
|
400
438
|
* Get a data URL (e.g. as produced by `HTMLCanvasElement::toDataURL`).
|
@@ -403,6 +441,9 @@ export declare class Editor {
|
|
403
441
|
*
|
404
442
|
* The export resolution is the same as the size of the drawing canvas, unless `outputSize`
|
405
443
|
* is given.
|
444
|
+
*
|
445
|
+
* **Example**:
|
446
|
+
* [[include:doc-pages/inline-examples/adding-an-image-and-data-urls.md]]
|
406
447
|
*/
|
407
448
|
toDataURL(format?: 'image/png' | 'image/jpeg' | 'image/webp', outputSize?: Vec2): string;
|
408
449
|
/**
|
package/dist/mjs/Editor.mjs
CHANGED
@@ -886,6 +886,12 @@ export class Editor {
|
|
886
886
|
allPointers) {
|
887
887
|
sendPenEvent(this, eventType, point, allPointers);
|
888
888
|
}
|
889
|
+
/**
|
890
|
+
* Adds all components in `components` such that they are in the center of the screen.
|
891
|
+
* This is a convenience method that creates **and applies** a single command.
|
892
|
+
*
|
893
|
+
* If `selectComponents` is true (the default), the components are selected.
|
894
|
+
*/
|
889
895
|
async addAndCenterComponents(components, selectComponents = true) {
|
890
896
|
let bbox = null;
|
891
897
|
for (const component of components) {
|
@@ -932,6 +938,9 @@ export class Editor {
|
|
932
938
|
*
|
933
939
|
* The export resolution is the same as the size of the drawing canvas, unless `outputSize`
|
934
940
|
* is given.
|
941
|
+
*
|
942
|
+
* **Example**:
|
943
|
+
* [[include:doc-pages/inline-examples/adding-an-image-and-data-urls.md]]
|
935
944
|
*/
|
936
945
|
toDataURL(format = 'image/png', outputSize) {
|
937
946
|
const canvas = document.createElement('canvas');
|
package/dist/mjs/Pointer.mjs
CHANGED
@@ -112,7 +112,7 @@ export default class Pointer {
|
|
112
112
|
// Intended for unit tests.
|
113
113
|
static ofCanvasPoint(canvasPos, isDown, viewport, id = 0, device = PointerDevice.Pen, isPrimary = true, pressure = null) {
|
114
114
|
const screenPos = viewport.canvasToScreen(canvasPos);
|
115
|
-
const timeStamp =
|
115
|
+
const timeStamp = performance.now();
|
116
116
|
return new Pointer(screenPos, canvasPos, pressure, isPrimary, isDown, device, id, timeStamp);
|
117
117
|
}
|
118
118
|
}
|
@@ -5,8 +5,28 @@ import SerializableCommand from './SerializableCommand';
|
|
5
5
|
/**
|
6
6
|
* Removes the given {@link AbstractComponent}s from the image.
|
7
7
|
*
|
8
|
-
*
|
9
|
-
* ```ts
|
8
|
+
* **Example**:
|
9
|
+
* ```ts,runnable
|
10
|
+
* import { Editor, Erase, uniteCommands, Color4, Path, Stroke, Rect2, pathToRenderable } from 'js-draw';
|
11
|
+
*
|
12
|
+
* const editor = new Editor(document.body);
|
13
|
+
* editor.addToolbar();
|
14
|
+
*
|
15
|
+
* // Add a large number of strokes
|
16
|
+
* const commands = [];
|
17
|
+
* for (let x = -20; x < 20; x++) {
|
18
|
+
* for (let y = 0; y < 60; y++) {
|
19
|
+
* const stroke = new Stroke([
|
20
|
+
* pathToRenderable(
|
21
|
+
* Path.fromString(`m${x * 5},${y * 5}l1,1`),
|
22
|
+
* { fill: Color4.transparent, stroke: {width: 2, color: Color4.ofRGB(x / 10, y / 10, 0.5)}} )
|
23
|
+
* ]);
|
24
|
+
* commands.push(editor.image.addElement(stroke));
|
25
|
+
* }
|
26
|
+
* }
|
27
|
+
* await editor.dispatch(uniteCommands(commands, 100));
|
28
|
+
*
|
29
|
+
* ---visible---
|
10
30
|
* // Given some editor...
|
11
31
|
*
|
12
32
|
* // Find all elements intersecting the rectangle with top left (-10,-30) and
|
@@ -5,8 +5,28 @@ import SerializableCommand from './SerializableCommand.mjs';
|
|
5
5
|
/**
|
6
6
|
* Removes the given {@link AbstractComponent}s from the image.
|
7
7
|
*
|
8
|
-
*
|
9
|
-
* ```ts
|
8
|
+
* **Example**:
|
9
|
+
* ```ts,runnable
|
10
|
+
* import { Editor, Erase, uniteCommands, Color4, Path, Stroke, Rect2, pathToRenderable } from 'js-draw';
|
11
|
+
*
|
12
|
+
* const editor = new Editor(document.body);
|
13
|
+
* editor.addToolbar();
|
14
|
+
*
|
15
|
+
* // Add a large number of strokes
|
16
|
+
* const commands = [];
|
17
|
+
* for (let x = -20; x < 20; x++) {
|
18
|
+
* for (let y = 0; y < 60; y++) {
|
19
|
+
* const stroke = new Stroke([
|
20
|
+
* pathToRenderable(
|
21
|
+
* Path.fromString(`m${x * 5},${y * 5}l1,1`),
|
22
|
+
* { fill: Color4.transparent, stroke: {width: 2, color: Color4.ofRGB(x / 10, y / 10, 0.5)}} )
|
23
|
+
* ]);
|
24
|
+
* commands.push(editor.image.addElement(stroke));
|
25
|
+
* }
|
26
|
+
* }
|
27
|
+
* await editor.dispatch(uniteCommands(commands, 100));
|
28
|
+
*
|
29
|
+
* ---visible---
|
10
30
|
* // Given some editor...
|
11
31
|
*
|
12
32
|
* // Find all elements intersecting the rectangle with top left (-10,-30) and
|
@@ -6,6 +6,11 @@ const invertCommand = (command) => {
|
|
6
6
|
if (command instanceof SerializableCommand) {
|
7
7
|
// SerializableCommand that does the inverse of [command]
|
8
8
|
return new class extends SerializableCommand {
|
9
|
+
constructor() {
|
10
|
+
super(...arguments);
|
11
|
+
// For debugging
|
12
|
+
this._command = command;
|
13
|
+
}
|
9
14
|
serializeToJSON() {
|
10
15
|
return command.serialize();
|
11
16
|
}
|
@@ -1,4 +1,40 @@
|
|
1
1
|
import Command from './Command';
|
2
2
|
import SerializableCommand from './SerializableCommand';
|
3
|
+
/**
|
4
|
+
* Creates a single command from `commands`. This is useful when undoing should undo *all* commands
|
5
|
+
* in `commands` at once, rather than one at a time.
|
6
|
+
*
|
7
|
+
* @example
|
8
|
+
*
|
9
|
+
* ```ts,runnable
|
10
|
+
* import { Editor, pathToRenderable, Stroke, uniteCommands } from 'js-draw';
|
11
|
+
* import { Path, Color4 } from '@js-draw/math';
|
12
|
+
*
|
13
|
+
* const editor = new Editor(document.body);
|
14
|
+
* editor.addToolbar();
|
15
|
+
*
|
16
|
+
* // Create strokes!
|
17
|
+
* const strokes = [];
|
18
|
+
* for (let i = 0; i < 10; i++) {
|
19
|
+
* const renderablePath = pathToRenderable(
|
20
|
+
* Path.fromString(`M0,${i * 10} L100,100 L300,30 z`),
|
21
|
+
* { fill: Color4.transparent, stroke: { color: Color4.red, width: 1, } }
|
22
|
+
* );
|
23
|
+
* strokes.push(new Stroke([ renderablePath ]));
|
24
|
+
* }
|
25
|
+
*
|
26
|
+
* // Convert to commands
|
27
|
+
* const addStrokesCommands = strokes.map(stroke => editor.image.addElement(stroke));
|
28
|
+
*
|
29
|
+
* // Apply all as a single undoable command (try applying each in a loop instead!)
|
30
|
+
* await editor.dispatch(uniteCommands(addStrokesCommands));
|
31
|
+
*
|
32
|
+
* // The second parameter to uniteCommands is for very large numbers of commands, when
|
33
|
+
* // applying them shouldn't be done all at once (which would block the UI).
|
34
|
+
*
|
35
|
+
* // The second parameter to uniteCommands is for very large numbers of commands, when
|
36
|
+
* // applying them shouldn't be done all at once (which would block the UI).
|
37
|
+
* ```
|
38
|
+
*/
|
3
39
|
declare const uniteCommands: <T extends Command>(commands: T[], applyChunkSize?: number) => T extends SerializableCommand ? SerializableCommand : Command;
|
4
40
|
export default uniteCommands;
|
@@ -84,6 +84,42 @@ class SerializableUnion extends SerializableCommand {
|
|
84
84
|
return this.nonserializableCommand.description(editor, localizationTable);
|
85
85
|
}
|
86
86
|
}
|
87
|
+
/**
|
88
|
+
* Creates a single command from `commands`. This is useful when undoing should undo *all* commands
|
89
|
+
* in `commands` at once, rather than one at a time.
|
90
|
+
*
|
91
|
+
* @example
|
92
|
+
*
|
93
|
+
* ```ts,runnable
|
94
|
+
* import { Editor, pathToRenderable, Stroke, uniteCommands } from 'js-draw';
|
95
|
+
* import { Path, Color4 } from '@js-draw/math';
|
96
|
+
*
|
97
|
+
* const editor = new Editor(document.body);
|
98
|
+
* editor.addToolbar();
|
99
|
+
*
|
100
|
+
* // Create strokes!
|
101
|
+
* const strokes = [];
|
102
|
+
* for (let i = 0; i < 10; i++) {
|
103
|
+
* const renderablePath = pathToRenderable(
|
104
|
+
* Path.fromString(`M0,${i * 10} L100,100 L300,30 z`),
|
105
|
+
* { fill: Color4.transparent, stroke: { color: Color4.red, width: 1, } }
|
106
|
+
* );
|
107
|
+
* strokes.push(new Stroke([ renderablePath ]));
|
108
|
+
* }
|
109
|
+
*
|
110
|
+
* // Convert to commands
|
111
|
+
* const addStrokesCommands = strokes.map(stroke => editor.image.addElement(stroke));
|
112
|
+
*
|
113
|
+
* // Apply all as a single undoable command (try applying each in a loop instead!)
|
114
|
+
* await editor.dispatch(uniteCommands(addStrokesCommands));
|
115
|
+
*
|
116
|
+
* // The second parameter to uniteCommands is for very large numbers of commands, when
|
117
|
+
* // applying them shouldn't be done all at once (which would block the UI).
|
118
|
+
*
|
119
|
+
* // The second parameter to uniteCommands is for very large numbers of commands, when
|
120
|
+
* // applying them shouldn't be done all at once (which would block the UI).
|
121
|
+
* ```
|
122
|
+
*/
|
87
123
|
const uniteCommands = (commands, applyChunkSize) => {
|
88
124
|
let allSerializable = true;
|
89
125
|
for (const command of commands) {
|
@@ -116,6 +116,14 @@ export default abstract class AbstractComponent {
|
|
116
116
|
protected abstract applyTransformation(affineTransfm: Mat33): void;
|
117
117
|
transformBy(affineTransfm: Mat33): SerializableCommand;
|
118
118
|
setZIndex(newZIndex: number): SerializableCommand;
|
119
|
+
/**
|
120
|
+
* Combines {@link transformBy} and {@link setZIndex} into a single command.
|
121
|
+
*
|
122
|
+
* @param newZIndex - The z-index this component should have after applying this command.
|
123
|
+
* @param originalZIndex - @internal The z-index the component should revert to after unapplying
|
124
|
+
* this command.
|
125
|
+
*/
|
126
|
+
setZIndexAndTransformBy(affineTransfm: Mat33, newZIndex: number, originalZIndex?: number): SerializableCommand;
|
119
127
|
isSelectable(): boolean;
|
120
128
|
isBackground(): boolean;
|
121
129
|
getProportionalRenderingTime(): number;
|
@@ -138,13 +138,25 @@ class AbstractComponent {
|
|
138
138
|
}
|
139
139
|
// Returns a command that, when applied, transforms this by [affineTransfm] and
|
140
140
|
// updates the editor.
|
141
|
-
//
|
141
|
+
//
|
142
|
+
// The transformed component is also moved to the top (use {@link setZIndexAndTransformBy} to
|
143
|
+
// avoid this behavior).
|
142
144
|
transformBy(affineTransfm) {
|
143
145
|
return new AbstractComponent.TransformElementCommand(affineTransfm, this.getId(), this);
|
144
146
|
}
|
145
147
|
// Returns a command that updates this component's z-index.
|
146
148
|
setZIndex(newZIndex) {
|
147
|
-
return new AbstractComponent.TransformElementCommand(Mat33.identity, this.getId(), this, newZIndex
|
149
|
+
return new AbstractComponent.TransformElementCommand(Mat33.identity, this.getId(), this, newZIndex);
|
150
|
+
}
|
151
|
+
/**
|
152
|
+
* Combines {@link transformBy} and {@link setZIndex} into a single command.
|
153
|
+
*
|
154
|
+
* @param newZIndex - The z-index this component should have after applying this command.
|
155
|
+
* @param originalZIndex - @internal The z-index the component should revert to after unapplying
|
156
|
+
* this command.
|
157
|
+
*/
|
158
|
+
setZIndexAndTransformBy(affineTransfm, newZIndex, originalZIndex) {
|
159
|
+
return new AbstractComponent.TransformElementCommand(affineTransfm, this.getId(), this, newZIndex, originalZIndex);
|
148
160
|
}
|
149
161
|
// @returns true iff this component can be selected (e.g. by the selection tool.)
|
150
162
|
isSelectable() {
|
@@ -215,8 +227,12 @@ class AbstractComponent {
|
|
215
227
|
throw new Error(`Element with data ${json} cannot be deserialized.`);
|
216
228
|
}
|
217
229
|
const instance = this.deserializationCallbacks[json.name](json.data);
|
218
|
-
instance.zIndex = json.zIndex;
|
219
230
|
instance.id = json.id;
|
231
|
+
if (isFinite(json.zIndex)) {
|
232
|
+
instance.zIndex = json.zIndex;
|
233
|
+
// Ensure that new components will be added on top.
|
234
|
+
AbstractComponent.zIndexCounter = Math.max(AbstractComponent.zIndexCounter, instance.zIndex + 1);
|
235
|
+
}
|
220
236
|
// TODO: What should we do with json.loadSaveData?
|
221
237
|
// If we attach it to [instance], we create a potential security risk — loadSaveData
|
222
238
|
// is often used to store unrecognised attributes so they can be preserved on output.
|
@@ -225,6 +241,7 @@ class AbstractComponent {
|
|
225
241
|
}
|
226
242
|
}
|
227
243
|
// Topmost z-index
|
244
|
+
// TODO: Should be a property of the EditorImage.
|
228
245
|
AbstractComponent.zIndexCounter = 0;
|
229
246
|
AbstractComponent.deserializationCallbacks = {};
|
230
247
|
AbstractComponent.transformElementCommandId = 'transform-element';
|
@@ -252,7 +269,7 @@ AbstractComponent.TransformElementCommand = (_a = class extends UnresolvedSerial
|
|
252
269
|
super.resolveComponent(image);
|
253
270
|
this.origZIndex ??= this.component.getZIndex();
|
254
271
|
}
|
255
|
-
updateTransform(editor, newTransfm) {
|
272
|
+
updateTransform(editor, newTransfm, targetZIndex) {
|
256
273
|
if (!this.component) {
|
257
274
|
throw new Error('this.component is undefined or null!');
|
258
275
|
}
|
@@ -264,7 +281,12 @@ AbstractComponent.TransformElementCommand = (_a = class extends UnresolvedSerial
|
|
264
281
|
hadParent = true;
|
265
282
|
}
|
266
283
|
this.component.applyTransformation(newTransfm);
|
284
|
+
this.component.zIndex = targetZIndex;
|
267
285
|
this.component.lastChangedTime = (new Date()).getTime();
|
286
|
+
// Ensure that new components are automatically drawn above the current component.
|
287
|
+
if (targetZIndex >= AbstractComponent.zIndexCounter) {
|
288
|
+
AbstractComponent.zIndexCounter = targetZIndex + 1;
|
289
|
+
}
|
268
290
|
// Add the element back to the document.
|
269
291
|
if (hadParent) {
|
270
292
|
EditorImage.addElement(this.component).apply(editor);
|
@@ -272,14 +294,12 @@ AbstractComponent.TransformElementCommand = (_a = class extends UnresolvedSerial
|
|
272
294
|
}
|
273
295
|
apply(editor) {
|
274
296
|
this.resolveComponent(editor.image);
|
275
|
-
this.
|
276
|
-
this.updateTransform(editor, this.affineTransfm);
|
297
|
+
this.updateTransform(editor, this.affineTransfm, this.targetZIndex);
|
277
298
|
editor.queueRerender();
|
278
299
|
}
|
279
300
|
unapply(editor) {
|
280
301
|
this.resolveComponent(editor.image);
|
281
|
-
this.
|
282
|
-
this.updateTransform(editor, this.affineTransfm.inverse());
|
302
|
+
this.updateTransform(editor, this.affineTransfm.inverse(), this.origZIndex);
|
283
303
|
editor.queueRerender();
|
284
304
|
}
|
285
305
|
description(_editor, localizationTable) {
|
@@ -2,12 +2,24 @@ import { Mat33Array, Rect2, Mat33, LineSegment2 } from '@js-draw/math';
|
|
2
2
|
import AbstractRenderer, { RenderableImage } from '../rendering/renderers/AbstractRenderer';
|
3
3
|
import AbstractComponent from './AbstractComponent';
|
4
4
|
import { ImageComponentLocalization } from './localization';
|
5
|
+
/**
|
6
|
+
* Represents a raster image.
|
7
|
+
*
|
8
|
+
* **Example: Adding images**:
|
9
|
+
* [[include:doc-pages/inline-examples/adding-an-image-and-data-urls.md]]
|
10
|
+
*/
|
5
11
|
export default class ImageComponent extends AbstractComponent {
|
6
12
|
protected contentBBox: Rect2;
|
7
13
|
private image;
|
8
14
|
constructor(image: RenderableImage);
|
9
15
|
private getImageRect;
|
10
16
|
private recomputeBBox;
|
17
|
+
/**
|
18
|
+
* Load from an image. Waits for the image to load if incomplete.
|
19
|
+
*
|
20
|
+
* The image, `elem`, must not [taint](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image#security_and_tainted_canvases)
|
21
|
+
* an HTMLCanvasElement when rendered.
|
22
|
+
*/
|
11
23
|
static fromImage(elem: HTMLImageElement, transform: Mat33): Promise<ImageComponent>;
|
12
24
|
render(canvas: AbstractRenderer, _visibleRect?: Rect2): void;
|
13
25
|
getProportionalRenderingTime(): number;
|
@@ -1,7 +1,13 @@
|
|
1
1
|
import { Rect2, Mat33 } from '@js-draw/math';
|
2
2
|
import { assertIsNumber, assertIsNumberArray } from '../util/assertions.mjs';
|
3
3
|
import AbstractComponent from './AbstractComponent.mjs';
|
4
|
-
|
4
|
+
import waitForImageLoaded from '../util/waitForImageLoaded.mjs';
|
5
|
+
/**
|
6
|
+
* Represents a raster image.
|
7
|
+
*
|
8
|
+
* **Example: Adding images**:
|
9
|
+
* [[include:doc-pages/inline-examples/adding-an-image-and-data-urls.md]]
|
10
|
+
*/
|
5
11
|
export default class ImageComponent extends AbstractComponent {
|
6
12
|
constructor(image) {
|
7
13
|
super('image-component');
|
@@ -24,15 +30,14 @@ export default class ImageComponent extends AbstractComponent {
|
|
24
30
|
this.contentBBox = this.getImageRect();
|
25
31
|
this.contentBBox = this.contentBBox.transformedBoundingBox(this.image.transform);
|
26
32
|
}
|
27
|
-
|
33
|
+
/**
|
34
|
+
* Load from an image. Waits for the image to load if incomplete.
|
35
|
+
*
|
36
|
+
* The image, `elem`, must not [taint](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image#security_and_tainted_canvases)
|
37
|
+
* an HTMLCanvasElement when rendered.
|
38
|
+
*/
|
28
39
|
static async fromImage(elem, transform) {
|
29
|
-
|
30
|
-
await new Promise((resolve, reject) => {
|
31
|
-
elem.onload = resolve;
|
32
|
-
elem.onerror = reject;
|
33
|
-
elem.onabort = reject;
|
34
|
-
});
|
35
|
-
}
|
40
|
+
await waitForImageLoaded(elem);
|
36
41
|
let width, height;
|
37
42
|
if (typeof elem.width === 'number' && typeof elem.height === 'number'
|
38
43
|
&& elem.width !== 0 && elem.height !== 0) {
|
@@ -74,6 +79,7 @@ export default class ImageComponent extends AbstractComponent {
|
|
74
79
|
canvas.drawImage(this.image);
|
75
80
|
canvas.endObject(this.getLoadSaveData());
|
76
81
|
}
|
82
|
+
// A *very* rough estimate of how long it takes to render this component
|
77
83
|
getProportionalRenderingTime() {
|
78
84
|
// Estimate: Equivalent to a stroke with 10 segments.
|
79
85
|
return 10;
|
@@ -98,6 +104,7 @@ export default class ImageComponent extends AbstractComponent {
|
|
98
104
|
getAltText() {
|
99
105
|
return this.image.label;
|
100
106
|
}
|
107
|
+
// The base64 image URL of this image.
|
101
108
|
getURL() {
|
102
109
|
return this.image.base64Url;
|
103
110
|
}
|
@@ -5,7 +5,7 @@ import AbstractRenderer from '../rendering/renderers/AbstractRenderer';
|
|
5
5
|
import AbstractComponent from './AbstractComponent';
|
6
6
|
import { ImageComponentLocalization } from './localization';
|
7
7
|
import RestyleableComponent, { ComponentStyle } from './RestylableComponent';
|
8
|
-
import RenderablePathSpec from '../rendering/RenderablePathSpec';
|
8
|
+
import RenderablePathSpec, { RenderablePathSpecWithPath } from '../rendering/RenderablePathSpec';
|
9
9
|
/**
|
10
10
|
* Represents an {@link AbstractComponent} made up of one or more {@link Path}s.
|
11
11
|
*
|
@@ -21,6 +21,9 @@ import RenderablePathSpec from '../rendering/RenderablePathSpec';
|
|
21
21
|
* ```ts
|
22
22
|
* editor.dispatch(stroke.transformBy(Mat33.translation(Vec2.of(10, 0))));
|
23
23
|
* ```
|
24
|
+
*
|
25
|
+
* **Adding**:
|
26
|
+
* [[include:doc-pages/inline-examples/adding-a-stroke.md]]
|
24
27
|
*/
|
25
28
|
export default class Stroke extends AbstractComponent implements RestyleableComponent {
|
26
29
|
private parts;
|
@@ -39,7 +42,7 @@ export default class Stroke extends AbstractComponent implements RestyleableComp
|
|
39
42
|
*
|
40
43
|
* const stroke = new Stroke([
|
41
44
|
* // Fill with red
|
42
|
-
* pathToRenderable({ fill: Color4.red })
|
45
|
+
* pathToRenderable(path, { fill: Color4.red })
|
43
46
|
* ]);
|
44
47
|
* ```
|
45
48
|
*/
|
@@ -57,6 +60,17 @@ export default class Stroke extends AbstractComponent implements RestyleableComp
|
|
57
60
|
private bboxForPart;
|
58
61
|
getExactBBox(): Rect2;
|
59
62
|
protected applyTransformation(affineTransfm: Mat33): void;
|
63
|
+
/**
|
64
|
+
* @returns A list of the parts that make up this path. Many paths only have one part.
|
65
|
+
*
|
66
|
+
* Each part (a {@link RenderablePathSpec}) contains information about the style and geometry
|
67
|
+
* of that part of the stroke. Use the `.path` property to do collision detection and other
|
68
|
+
* operations involving the stroke's geometry.
|
69
|
+
*
|
70
|
+
* Note that many of {@link Path}'s methods (e.g. {@link Path.intersection}) take a
|
71
|
+
* `strokeWidth` parameter that can be gotten from {@link RenderablePathSpec.style} `.stroke.width`.
|
72
|
+
*/
|
73
|
+
getParts(): Readonly<RenderablePathSpecWithPath>[];
|
60
74
|
/**
|
61
75
|
* @returns the {@link Path.union} of all paths that make up this stroke.
|
62
76
|
*/
|