js-draw 1.17.0 → 1.19.1
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +70 -10
- package/dist/Editor.css +35 -3
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +38 -21
- package/dist/cjs/Editor.js +11 -2
- package/dist/cjs/{SVGLoader.d.ts → SVGLoader/index.d.ts} +1 -1
- package/dist/cjs/{SVGLoader.js → SVGLoader/index.js} +12 -29
- package/dist/cjs/SVGLoader/utils/determineFontSize.d.ts +3 -0
- package/dist/cjs/SVGLoader/utils/determineFontSize.js +27 -0
- package/dist/cjs/Viewport.d.ts +33 -1
- package/dist/cjs/components/AbstractComponent.d.ts +17 -5
- package/dist/cjs/components/AbstractComponent.js +15 -15
- package/dist/cjs/components/Stroke.d.ts +4 -1
- package/dist/cjs/components/Stroke.js +158 -2
- package/dist/cjs/components/TextComponent.js +3 -1
- package/dist/cjs/components/builders/PolylineBuilder.d.ts +1 -1
- package/dist/cjs/components/builders/PolylineBuilder.js +9 -2
- package/dist/cjs/components/builders/PressureSensitiveFreehandLineBuilder.d.ts +1 -1
- package/dist/cjs/components/builders/PressureSensitiveFreehandLineBuilder.js +44 -51
- package/dist/cjs/image/EditorImage.js +1 -1
- package/dist/cjs/localizations/de.js +1 -1
- package/dist/cjs/localizations/es.js +1 -1
- package/dist/cjs/rendering/caching/RenderingCacheNode.js +20 -15
- package/dist/cjs/testing/createEditor.d.ts +2 -2
- package/dist/cjs/testing/createEditor.js +2 -2
- package/dist/cjs/testing/findNodeWithText.d.ts +3 -0
- package/dist/cjs/testing/findNodeWithText.js +16 -0
- package/dist/cjs/testing/firstElementAncestorOfNode.d.ts +3 -0
- package/dist/cjs/testing/firstElementAncestorOfNode.js +13 -0
- package/dist/cjs/testing/sendKeyPressRelease.d.ts +3 -0
- package/dist/cjs/testing/sendKeyPressRelease.js +8 -0
- package/dist/cjs/testing/sendPenEvent.d.ts +2 -2
- package/dist/cjs/testing/sendPenEvent.js +26 -3
- package/dist/cjs/toolbar/IconProvider.d.ts +3 -1
- package/dist/cjs/toolbar/IconProvider.js +15 -3
- package/dist/cjs/toolbar/localization.d.ts +8 -1
- package/dist/cjs/toolbar/localization.js +9 -2
- package/dist/cjs/toolbar/widgets/BaseWidget.d.ts +1 -0
- package/dist/cjs/toolbar/widgets/BaseWidget.js +1 -0
- package/dist/cjs/toolbar/widgets/EraserToolWidget.d.ts +6 -1
- package/dist/cjs/toolbar/widgets/EraserToolWidget.js +45 -5
- package/dist/cjs/toolbar/widgets/InsertImageWidget/ImageWrapper.d.ts +22 -0
- package/dist/cjs/toolbar/widgets/InsertImageWidget/ImageWrapper.js +58 -0
- package/dist/cjs/toolbar/widgets/InsertImageWidget/fileToImages.d.ts +3 -0
- package/dist/cjs/toolbar/widgets/InsertImageWidget/fileToImages.js +21 -0
- package/dist/cjs/toolbar/widgets/InsertImageWidget/index.d.ts +37 -0
- package/dist/cjs/toolbar/widgets/InsertImageWidget/index.js +281 -0
- package/dist/cjs/toolbar/widgets/PenToolWidget.js +10 -3
- package/dist/cjs/toolbar/widgets/PenToolWidget.test.d.ts +1 -0
- package/dist/cjs/toolbar/widgets/TextToolWidget.js +5 -3
- package/dist/cjs/toolbar/widgets/TextToolWidget.test.d.ts +1 -0
- package/dist/cjs/toolbar/widgets/components/makeFileInput.d.ts +12 -2
- package/dist/cjs/toolbar/widgets/components/makeFileInput.js +102 -45
- package/dist/cjs/toolbar/widgets/components/makeFileInput.test.d.ts +1 -0
- package/dist/cjs/toolbar/widgets/components/makeSnappedList.d.ts +15 -0
- package/dist/cjs/toolbar/widgets/components/makeSnappedList.js +103 -0
- package/dist/cjs/toolbar/widgets/keybindings.js +1 -1
- package/dist/cjs/tools/Eraser.d.ts +31 -6
- package/dist/cjs/tools/Eraser.js +161 -21
- package/dist/cjs/tools/PasteHandler.js +0 -1
- package/dist/cjs/tools/SelectionTool/Selection.d.ts +2 -2
- package/dist/cjs/tools/SelectionTool/Selection.js +20 -20
- package/dist/cjs/tools/SelectionTool/SelectionHandle.d.ts +8 -2
- package/dist/cjs/tools/SelectionTool/SelectionHandle.js +6 -0
- package/dist/cjs/tools/SelectionTool/SelectionTool.js +1 -1
- package/dist/cjs/tools/SelectionTool/types.d.ts +19 -0
- package/dist/cjs/tools/TextTool.js +2 -1
- package/dist/cjs/tools/TextTool.test.d.ts +1 -0
- package/dist/cjs/tools/ToolController.d.ts +2 -0
- package/dist/cjs/tools/ToolController.js +10 -1
- package/dist/cjs/tools/lib.d.ts +1 -4
- package/dist/cjs/tools/lib.js +2 -4
- package/dist/cjs/util/ReactiveValue.d.ts +2 -0
- package/dist/cjs/util/ReactiveValue.js +11 -0
- package/dist/cjs/util/bytesToSizeString.d.ts +8 -0
- package/dist/cjs/util/bytesToSizeString.js +26 -0
- package/dist/cjs/util/bytesToSizeString.test.d.ts +1 -0
- package/dist/cjs/util/stopPropagationOfScrollingWheelEvents.js +10 -6
- package/dist/cjs/util/waitForAll.d.ts +2 -0
- package/dist/cjs/util/waitForAll.js +2 -0
- package/dist/cjs/util/waitForImageLoaded.js +3 -0
- package/dist/cjs/util/waitForTimeout.d.ts +1 -0
- package/dist/cjs/util/waitForTimeout.js +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/mjs/Editor.d.ts +38 -21
- package/dist/mjs/Editor.mjs +11 -2
- package/dist/mjs/{SVGLoader.d.ts → SVGLoader/index.d.ts} +1 -1
- package/dist/mjs/{SVGLoader.mjs → SVGLoader/index.mjs} +12 -29
- package/dist/mjs/SVGLoader/index.test.d.ts +1 -0
- package/dist/mjs/SVGLoader/utils/determineFontSize.d.ts +3 -0
- package/dist/mjs/SVGLoader/utils/determineFontSize.mjs +25 -0
- package/dist/mjs/Viewport.d.ts +33 -1
- package/dist/mjs/components/AbstractComponent.d.ts +17 -5
- package/dist/mjs/components/AbstractComponent.mjs +15 -15
- package/dist/mjs/components/Stroke.d.ts +4 -1
- package/dist/mjs/components/Stroke.mjs +159 -3
- package/dist/mjs/components/TextComponent.mjs +3 -1
- package/dist/mjs/components/builders/PolylineBuilder.d.ts +1 -1
- package/dist/mjs/components/builders/PolylineBuilder.mjs +10 -3
- package/dist/mjs/components/builders/PressureSensitiveFreehandLineBuilder.d.ts +1 -1
- package/dist/mjs/components/builders/PressureSensitiveFreehandLineBuilder.mjs +45 -52
- package/dist/mjs/image/EditorImage.mjs +1 -1
- package/dist/mjs/localizations/de.mjs +1 -1
- package/dist/mjs/localizations/es.mjs +1 -1
- package/dist/mjs/rendering/caching/RenderingCacheNode.mjs +20 -15
- package/dist/mjs/testing/createEditor.d.ts +2 -2
- package/dist/mjs/testing/createEditor.mjs +2 -2
- package/dist/mjs/testing/findNodeWithText.d.ts +3 -0
- package/dist/mjs/testing/findNodeWithText.mjs +14 -0
- package/dist/mjs/testing/firstElementAncestorOfNode.d.ts +3 -0
- package/dist/mjs/testing/firstElementAncestorOfNode.mjs +11 -0
- package/dist/mjs/testing/sendKeyPressRelease.d.ts +3 -0
- package/dist/mjs/testing/sendKeyPressRelease.mjs +6 -0
- package/dist/mjs/testing/sendPenEvent.d.ts +2 -2
- package/dist/mjs/testing/sendPenEvent.mjs +3 -3
- package/dist/mjs/toolbar/IconProvider.d.ts +3 -1
- package/dist/mjs/toolbar/IconProvider.mjs +15 -3
- package/dist/mjs/toolbar/localization.d.ts +8 -1
- package/dist/mjs/toolbar/localization.mjs +9 -2
- package/dist/mjs/toolbar/widgets/BaseWidget.d.ts +1 -0
- package/dist/mjs/toolbar/widgets/BaseWidget.mjs +1 -0
- package/dist/mjs/toolbar/widgets/EraserToolWidget.d.ts +6 -1
- package/dist/mjs/toolbar/widgets/EraserToolWidget.mjs +47 -6
- package/dist/mjs/toolbar/widgets/InsertImageWidget/ImageWrapper.d.ts +22 -0
- package/dist/mjs/toolbar/widgets/InsertImageWidget/ImageWrapper.mjs +54 -0
- package/dist/mjs/toolbar/widgets/InsertImageWidget/fileToImages.d.ts +3 -0
- package/dist/mjs/toolbar/widgets/InsertImageWidget/fileToImages.mjs +16 -0
- package/dist/mjs/toolbar/widgets/InsertImageWidget/index.d.ts +37 -0
- package/dist/mjs/toolbar/widgets/InsertImageWidget/index.mjs +276 -0
- package/dist/mjs/toolbar/widgets/InsertImageWidget/index.test.d.ts +1 -0
- package/dist/mjs/toolbar/widgets/PenToolWidget.mjs +10 -3
- package/dist/mjs/toolbar/widgets/PenToolWidget.test.d.ts +1 -0
- package/dist/mjs/toolbar/widgets/TextToolWidget.mjs +5 -3
- package/dist/mjs/toolbar/widgets/TextToolWidget.test.d.ts +1 -0
- package/dist/mjs/toolbar/widgets/components/makeFileInput.d.ts +12 -2
- package/dist/mjs/toolbar/widgets/components/makeFileInput.mjs +102 -45
- package/dist/mjs/toolbar/widgets/components/makeFileInput.test.d.ts +1 -0
- package/dist/mjs/toolbar/widgets/components/makeSnappedList.d.ts +15 -0
- package/dist/mjs/toolbar/widgets/components/makeSnappedList.mjs +98 -0
- package/dist/mjs/toolbar/widgets/keybindings.mjs +1 -1
- package/dist/mjs/tools/Eraser.d.ts +31 -6
- package/dist/mjs/tools/Eraser.mjs +161 -22
- package/dist/mjs/tools/PasteHandler.mjs +0 -1
- package/dist/mjs/tools/SelectionTool/Selection.d.ts +2 -2
- package/dist/mjs/tools/SelectionTool/Selection.mjs +20 -20
- package/dist/mjs/tools/SelectionTool/SelectionHandle.d.ts +8 -2
- package/dist/mjs/tools/SelectionTool/SelectionHandle.mjs +6 -0
- package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +1 -1
- package/dist/mjs/tools/SelectionTool/types.d.ts +19 -0
- package/dist/mjs/tools/TextTool.mjs +2 -1
- package/dist/mjs/tools/TextTool.test.d.ts +1 -0
- package/dist/mjs/tools/ToolController.d.ts +2 -0
- package/dist/mjs/tools/ToolController.mjs +10 -1
- package/dist/mjs/tools/lib.d.ts +1 -4
- package/dist/mjs/tools/lib.mjs +1 -4
- package/dist/mjs/util/ReactiveValue.d.ts +2 -0
- package/dist/mjs/util/ReactiveValue.mjs +11 -0
- package/dist/mjs/util/bytesToSizeString.d.ts +8 -0
- package/dist/mjs/util/bytesToSizeString.mjs +24 -0
- package/dist/mjs/util/bytesToSizeString.test.d.ts +1 -0
- package/dist/mjs/util/stopPropagationOfScrollingWheelEvents.mjs +10 -6
- package/dist/mjs/util/waitForAll.d.ts +2 -0
- package/dist/mjs/util/waitForAll.mjs +2 -0
- package/dist/mjs/util/waitForImageLoaded.mjs +3 -0
- package/dist/mjs/util/waitForTimeout.d.ts +1 -0
- package/dist/mjs/util/waitForTimeout.mjs +1 -1
- package/dist/mjs/version.mjs +1 -1
- package/package.json +4 -4
- package/src/toolbar/toolbar.scss +1 -7
- package/src/toolbar/widgets/{InsertImageWidget.scss → InsertImageWidget/index.scss} +3 -2
- package/src/toolbar/widgets/components/components.scss +2 -1
- package/src/toolbar/widgets/components/makeFileInput.scss +14 -1
- package/src/toolbar/widgets/components/makeSnappedList.scss +28 -0
- package/src/toolbar/widgets/widgets.scss +7 -0
- package/dist/cjs/toolbar/widgets/InsertImageWidget.d.ts +0 -22
- package/dist/cjs/toolbar/widgets/InsertImageWidget.js +0 -269
- package/dist/mjs/toolbar/widgets/InsertImageWidget.d.ts +0 -22
- package/dist/mjs/toolbar/widgets/InsertImageWidget.mjs +0 -264
- /package/dist/cjs/{SVGLoader.test.d.ts → SVGLoader/index.test.d.ts} +0 -0
- /package/dist/{mjs/SVGLoader.test.d.ts → cjs/toolbar/widgets/InsertImageWidget/index.test.d.ts} +0 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
4
|
+
};
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
6
|
+
const stopPropagationOfScrollingWheelEvents_1 = __importDefault(require("../../../util/stopPropagationOfScrollingWheelEvents"));
|
7
|
+
const ReactiveValue_1 = require("../../../util/ReactiveValue");
|
8
|
+
/**
|
9
|
+
* Creates a list that snaps to each item and reports the selected item.
|
10
|
+
*/
|
11
|
+
const makeSnappedList = (itemsValue) => {
|
12
|
+
const container = document.createElement('div');
|
13
|
+
container.classList.add('toolbar-snapped-scroll-list');
|
14
|
+
const visibleIndex = ReactiveValue_1.MutableReactiveValue.fromInitialValue(0);
|
15
|
+
let observer = null;
|
16
|
+
const createObserver = () => {
|
17
|
+
observer = new IntersectionObserver((entries) => {
|
18
|
+
for (const entry of entries) {
|
19
|
+
if (entry.isIntersecting && entry.intersectionRatio > 0.7) {
|
20
|
+
const indexString = entry.target.getAttribute('data-item-index');
|
21
|
+
if (indexString === null)
|
22
|
+
throw new Error('Could not find attribute data-item-index');
|
23
|
+
const index = Number(indexString);
|
24
|
+
visibleIndex.set(index);
|
25
|
+
break;
|
26
|
+
}
|
27
|
+
}
|
28
|
+
}, {
|
29
|
+
// Element to use as the boudning box with which to intersect.
|
30
|
+
// See https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
|
31
|
+
root: container,
|
32
|
+
// Fraction of an element that must be visible to trigger the callback:
|
33
|
+
threshold: 0.9,
|
34
|
+
});
|
35
|
+
};
|
36
|
+
const destroyObserver = () => {
|
37
|
+
if (observer) {
|
38
|
+
observer.disconnect();
|
39
|
+
visibleIndex.set(0);
|
40
|
+
observer = null;
|
41
|
+
}
|
42
|
+
};
|
43
|
+
const wrappedItems = ReactiveValue_1.ReactiveValue.map(itemsValue, items => {
|
44
|
+
return items.map((item, index) => {
|
45
|
+
const wrapper = document.createElement('div');
|
46
|
+
if (item.element.parentElement)
|
47
|
+
item.element.remove();
|
48
|
+
wrapper.appendChild(item.element);
|
49
|
+
wrapper.classList.add('item');
|
50
|
+
wrapper.setAttribute('data-item-index', `${index}`);
|
51
|
+
return {
|
52
|
+
element: wrapper,
|
53
|
+
data: item.data,
|
54
|
+
};
|
55
|
+
});
|
56
|
+
});
|
57
|
+
const lastItems = [];
|
58
|
+
wrappedItems.onUpdateAndNow(items => {
|
59
|
+
visibleIndex.set(-1);
|
60
|
+
for (const item of lastItems) {
|
61
|
+
observer?.unobserve(item.element);
|
62
|
+
}
|
63
|
+
container.replaceChildren();
|
64
|
+
// An observer is only necessary if there are multiple items to scroll through.
|
65
|
+
if (items.length > 1) {
|
66
|
+
createObserver();
|
67
|
+
}
|
68
|
+
else {
|
69
|
+
destroyObserver();
|
70
|
+
}
|
71
|
+
// Different styling is applied when empty
|
72
|
+
if (items.length === 0) {
|
73
|
+
container.classList.add('-empty');
|
74
|
+
}
|
75
|
+
else {
|
76
|
+
container.classList.remove('-empty');
|
77
|
+
}
|
78
|
+
for (const item of items) {
|
79
|
+
container.appendChild(item.element);
|
80
|
+
}
|
81
|
+
visibleIndex.set(0);
|
82
|
+
if (observer) {
|
83
|
+
for (const item of items) {
|
84
|
+
observer.observe(item.element);
|
85
|
+
}
|
86
|
+
}
|
87
|
+
});
|
88
|
+
const visibleItem = ReactiveValue_1.ReactiveValue.map(visibleIndex, index => {
|
89
|
+
const values = itemsValue.get();
|
90
|
+
if (0 <= index && index < values.length) {
|
91
|
+
return values[index].data;
|
92
|
+
}
|
93
|
+
return null;
|
94
|
+
});
|
95
|
+
// makeSnappedList is generally shown within the toolbar. This allows users to
|
96
|
+
// scroll it with a touchpad.
|
97
|
+
(0, stopPropagationOfScrollingWheelEvents_1.default)(container);
|
98
|
+
return {
|
99
|
+
container,
|
100
|
+
visibleItem,
|
101
|
+
};
|
102
|
+
};
|
103
|
+
exports.default = makeSnappedList;
|
@@ -9,7 +9,7 @@ const KeyboardShortcutManager_1 = __importDefault(require("../../shortcuts/Keybo
|
|
9
9
|
exports.resizeImageToSelectionKeyboardShortcut = 'jsdraw.toolbar.SelectionTool.resizeImageToSelection';
|
10
10
|
KeyboardShortcutManager_1.default.registerDefaultKeyboardShortcut(exports.resizeImageToSelectionKeyboardShortcut, ['ctrlOrMeta+r'], 'Resize image to selection');
|
11
11
|
// Pen tool
|
12
|
-
exports.selectStrokeTypeKeyboardShortcutIds = [1, 2, 3, 4, 5, 6, 7].map(id => `jsdraw.toolbar.PenTool.select-pen-${id}`);
|
12
|
+
exports.selectStrokeTypeKeyboardShortcutIds = [1, 2, 3, 4, 5, 6, 7, 8, 9].map(id => `jsdraw.toolbar.PenTool.select-pen-${id}`);
|
13
13
|
for (let i = 0; i < exports.selectStrokeTypeKeyboardShortcutIds.length; i++) {
|
14
14
|
const id = exports.selectStrokeTypeKeyboardShortcutIds[i];
|
15
15
|
KeyboardShortcutManager_1.default.registerDefaultKeyboardShortcut(id, [`CtrlOrMeta+Digit${(i + 1)}`], 'Select pen style ' + (i + 1));
|
@@ -1,31 +1,56 @@
|
|
1
|
-
import { KeyPressEvent, PointerEvt } from '../inputEvents';
|
1
|
+
import { GestureCancelEvt, KeyPressEvent, PointerEvt } from '../inputEvents';
|
2
2
|
import BaseTool from './BaseTool';
|
3
3
|
import Editor from '../Editor';
|
4
4
|
import { MutableReactiveValue } from '../util/ReactiveValue';
|
5
|
+
export declare enum EraserMode {
|
6
|
+
PartialStroke = "partial-stroke",
|
7
|
+
FullStroke = "full-stroke"
|
8
|
+
}
|
9
|
+
export interface InitialEraserOptions {
|
10
|
+
thickness?: number;
|
11
|
+
mode?: EraserMode;
|
12
|
+
}
|
5
13
|
export default class Eraser extends BaseTool {
|
6
14
|
private editor;
|
7
15
|
private lastPoint;
|
8
16
|
private isFirstEraseEvt;
|
9
|
-
private toRemove;
|
10
17
|
private thickness;
|
11
18
|
private thicknessValue;
|
12
|
-
private
|
13
|
-
|
19
|
+
private modeValue;
|
20
|
+
private toRemove;
|
21
|
+
private toAdd;
|
22
|
+
private eraseCommands;
|
23
|
+
private addCommands;
|
24
|
+
constructor(editor: Editor, description: string, options?: InitialEraserOptions);
|
25
|
+
/**
|
26
|
+
* @returns a tool that briefly enables the eraser when a physical eraser is used.
|
27
|
+
* This tool should be added to the tool list after the primary tools.
|
28
|
+
*/
|
29
|
+
makeEraserSwitcherTool(): BaseTool;
|
14
30
|
private clearPreview;
|
15
31
|
private getSizeOnCanvas;
|
16
32
|
private drawPreviewAt;
|
33
|
+
/**
|
34
|
+
* @returns the eraser rectangle in canvas coordinates.
|
35
|
+
*
|
36
|
+
* For now, all erasers are rectangles or points.
|
37
|
+
*/
|
17
38
|
private getEraserRect;
|
39
|
+
/** Erases in a line from the last point to the current. */
|
18
40
|
private eraseTo;
|
19
41
|
onPointerDown(event: PointerEvt): boolean;
|
20
42
|
onPointerMove(event: PointerEvt): void;
|
21
43
|
onPointerUp(event: PointerEvt): void;
|
22
|
-
onGestureCancel(): void;
|
44
|
+
onGestureCancel(_event: GestureCancelEvt): void;
|
23
45
|
onKeyPress(event: KeyPressEvent): boolean;
|
46
|
+
/** Returns the side-length of the tip of this eraser. */
|
24
47
|
getThickness(): number;
|
48
|
+
/** Sets the side-length of this' tip. */
|
49
|
+
setThickness(thickness: number): void;
|
25
50
|
/**
|
26
51
|
* Returns a {@link MutableReactiveValue} that can be used to watch
|
27
52
|
* this tool's thickness.
|
28
53
|
*/
|
29
54
|
getThicknessValue(): MutableReactiveValue<number>;
|
30
|
-
|
55
|
+
getModeValue(): MutableReactiveValue<EraserMode>;
|
31
56
|
}
|
package/dist/cjs/tools/Eraser.js
CHANGED
@@ -3,6 +3,7 @@ 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.EraserMode = void 0;
|
6
7
|
const types_1 = require("../types");
|
7
8
|
const BaseTool_1 = __importDefault(require("./BaseTool"));
|
8
9
|
const math_1 = require("@js-draw/math");
|
@@ -10,15 +11,70 @@ const Erase_1 = __importDefault(require("../commands/Erase"));
|
|
10
11
|
const Pointer_1 = require("../Pointer");
|
11
12
|
const keybindings_1 = require("./keybindings");
|
12
13
|
const ReactiveValue_1 = require("../util/ReactiveValue");
|
14
|
+
const EditorImage_1 = __importDefault(require("../image/EditorImage"));
|
15
|
+
const uniteCommands_1 = __importDefault(require("../commands/uniteCommands"));
|
16
|
+
const RenderablePathSpec_1 = require("../rendering/RenderablePathSpec");
|
17
|
+
var EraserMode;
|
18
|
+
(function (EraserMode) {
|
19
|
+
EraserMode["PartialStroke"] = "partial-stroke";
|
20
|
+
EraserMode["FullStroke"] = "full-stroke";
|
21
|
+
})(EraserMode || (exports.EraserMode = EraserMode = {}));
|
22
|
+
/** Handles switching from other primary tools to the eraser and back */
|
23
|
+
class EraserSwitcher extends BaseTool_1.default {
|
24
|
+
constructor(editor, eraser) {
|
25
|
+
super(editor.notifier, editor.localization.changeTool);
|
26
|
+
this.editor = editor;
|
27
|
+
this.eraser = eraser;
|
28
|
+
}
|
29
|
+
onPointerDown(event) {
|
30
|
+
if (event.allPointers.length === 1 && event.current.device === Pointer_1.PointerDevice.Eraser) {
|
31
|
+
const toolController = this.editor.toolController;
|
32
|
+
const enabledPrimaryTools = toolController.getPrimaryTools().filter(tool => tool.isEnabled());
|
33
|
+
if (enabledPrimaryTools.length) {
|
34
|
+
this.previousEnabledTool = enabledPrimaryTools[0];
|
35
|
+
}
|
36
|
+
else {
|
37
|
+
this.previousEnabledTool = null;
|
38
|
+
}
|
39
|
+
this.previousEraserEnabledState = this.eraser.isEnabled();
|
40
|
+
this.eraser.setEnabled(true);
|
41
|
+
if (this.eraser.onPointerDown(event)) {
|
42
|
+
return true;
|
43
|
+
}
|
44
|
+
else {
|
45
|
+
this.restoreOriginalTool();
|
46
|
+
}
|
47
|
+
}
|
48
|
+
return false;
|
49
|
+
}
|
50
|
+
onPointerMove(event) {
|
51
|
+
this.eraser.onPointerMove(event);
|
52
|
+
}
|
53
|
+
restoreOriginalTool() {
|
54
|
+
this.eraser.setEnabled(this.previousEraserEnabledState);
|
55
|
+
if (this.previousEnabledTool) {
|
56
|
+
this.previousEnabledTool.setEnabled(true);
|
57
|
+
}
|
58
|
+
}
|
59
|
+
onPointerUp(event) {
|
60
|
+
this.eraser.onPointerUp(event);
|
61
|
+
this.restoreOriginalTool();
|
62
|
+
}
|
63
|
+
onGestureCancel(event) {
|
64
|
+
this.eraser.onGestureCancel(event);
|
65
|
+
this.restoreOriginalTool();
|
66
|
+
}
|
67
|
+
}
|
13
68
|
class Eraser extends BaseTool_1.default {
|
14
|
-
constructor(editor, description) {
|
69
|
+
constructor(editor, description, options) {
|
15
70
|
super(editor.notifier, description);
|
16
71
|
this.editor = editor;
|
17
72
|
this.lastPoint = null;
|
18
73
|
this.isFirstEraseEvt = true;
|
19
|
-
this.thickness = 10;
|
20
74
|
// Commands that each remove one element
|
21
|
-
this.
|
75
|
+
this.eraseCommands = [];
|
76
|
+
this.addCommands = [];
|
77
|
+
this.thickness = options?.thickness ?? 10;
|
22
78
|
this.thicknessValue = ReactiveValue_1.ReactiveValue.fromInitialValue(this.thickness);
|
23
79
|
this.thicknessValue.onUpdate(value => {
|
24
80
|
this.thickness = value;
|
@@ -27,6 +83,20 @@ class Eraser extends BaseTool_1.default {
|
|
27
83
|
tool: this,
|
28
84
|
});
|
29
85
|
});
|
86
|
+
this.modeValue = ReactiveValue_1.ReactiveValue.fromInitialValue(options?.mode ?? EraserMode.FullStroke);
|
87
|
+
this.modeValue.onUpdate(_value => {
|
88
|
+
this.editor.notifier.dispatch(types_1.EditorEventType.ToolUpdated, {
|
89
|
+
kind: types_1.EditorEventType.ToolUpdated,
|
90
|
+
tool: this,
|
91
|
+
});
|
92
|
+
});
|
93
|
+
}
|
94
|
+
/**
|
95
|
+
* @returns a tool that briefly enables the eraser when a physical eraser is used.
|
96
|
+
* This tool should be added to the tool list after the primary tools.
|
97
|
+
*/
|
98
|
+
makeEraserSwitcherTool() {
|
99
|
+
return new EraserSwitcher(this.editor, this);
|
30
100
|
}
|
31
101
|
clearPreview() {
|
32
102
|
this.editor.clearWetInk();
|
@@ -39,16 +109,24 @@ class Eraser extends BaseTool_1.default {
|
|
39
109
|
const size = this.getSizeOnCanvas();
|
40
110
|
const renderer = this.editor.display.getWetInkRenderer();
|
41
111
|
const rect = this.getEraserRect(point);
|
112
|
+
const rect2 = this.getEraserRect(this.lastPoint ?? point);
|
42
113
|
const fill = {
|
43
|
-
fill: math_1.Color4.
|
114
|
+
fill: math_1.Color4.transparent,
|
115
|
+
stroke: { width: size / 10, color: math_1.Color4.gray },
|
44
116
|
};
|
45
|
-
renderer.
|
117
|
+
renderer.drawPath((0, RenderablePathSpec_1.pathToRenderable)(math_1.Path.fromConvexHullOf([...rect.corners, ...rect2.corners]), fill));
|
46
118
|
}
|
119
|
+
/**
|
120
|
+
* @returns the eraser rectangle in canvas coordinates.
|
121
|
+
*
|
122
|
+
* For now, all erasers are rectangles or points.
|
123
|
+
*/
|
47
124
|
getEraserRect(centerPoint) {
|
48
125
|
const size = this.getSizeOnCanvas();
|
49
126
|
const halfSize = math_1.Vec2.of(size / 2, size / 2);
|
50
127
|
return math_1.Rect2.fromCorners(centerPoint.minus(halfSize), centerPoint.plus(halfSize));
|
51
128
|
}
|
129
|
+
/** Erases in a line from the last point to the current. */
|
52
130
|
eraseTo(currentPoint) {
|
53
131
|
if (!this.isFirstEraseEvt && currentPoint.distanceTo(this.lastPoint) === 0) {
|
54
132
|
return;
|
@@ -65,13 +143,55 @@ class Eraser extends BaseTool_1.default {
|
|
65
143
|
});
|
66
144
|
// Only erase components that could be selected (and thus interacted with)
|
67
145
|
// by the user.
|
68
|
-
const
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
146
|
+
const eraseableElems = intersectingElems.filter(elem => elem.isSelectable());
|
147
|
+
if (this.modeValue.get() === EraserMode.FullStroke) {
|
148
|
+
// Remove any intersecting elements.
|
149
|
+
this.toRemove.push(...eraseableElems);
|
150
|
+
// Create new Erase commands for the now-to-be-erased elements and apply them.
|
151
|
+
const newPartialCommands = eraseableElems.map(elem => new Erase_1.default([elem]));
|
152
|
+
newPartialCommands.forEach(cmd => cmd.apply(this.editor));
|
153
|
+
this.eraseCommands.push(...newPartialCommands);
|
154
|
+
}
|
155
|
+
else {
|
156
|
+
const toErase = [];
|
157
|
+
const toAdd = [];
|
158
|
+
for (const targetElem of eraseableElems) {
|
159
|
+
toErase.push(targetElem);
|
160
|
+
// Completely delete items that can't be divided.
|
161
|
+
if (!targetElem.withRegionErased) {
|
162
|
+
continue;
|
163
|
+
}
|
164
|
+
// Completely delete items that are completely or almost completely
|
165
|
+
// contained within the eraser.
|
166
|
+
const grownRect = eraserRect.grownBy(eraserRect.maxDimension / 3);
|
167
|
+
if (grownRect.containsRect(targetElem.getExactBBox())) {
|
168
|
+
continue;
|
169
|
+
}
|
170
|
+
// Join the current and previous rectangles so that points between events are also
|
171
|
+
// erased.
|
172
|
+
const erasePath = math_1.Path.fromConvexHullOf([
|
173
|
+
...eraserRect.corners, ...this.getEraserRect(this.lastPoint ?? currentPoint).corners
|
174
|
+
].map(p => this.editor.viewport.roundPoint(p)));
|
175
|
+
toAdd.push(...targetElem.withRegionErased(erasePath, this.editor.viewport));
|
176
|
+
}
|
177
|
+
const eraseCommand = new Erase_1.default(toErase);
|
178
|
+
const newAddCommands = toAdd.map(elem => EditorImage_1.default.addElement(elem));
|
179
|
+
eraseCommand.apply(this.editor);
|
180
|
+
newAddCommands.forEach(command => command.apply(this.editor));
|
181
|
+
const finalToErase = [];
|
182
|
+
for (const item of toErase) {
|
183
|
+
if (this.toAdd.includes(item)) {
|
184
|
+
this.toAdd = this.toAdd.filter(i => i !== item);
|
185
|
+
}
|
186
|
+
else {
|
187
|
+
finalToErase.push(item);
|
188
|
+
}
|
189
|
+
}
|
190
|
+
this.toRemove.push(...finalToErase);
|
191
|
+
this.toAdd.push(...toAdd);
|
192
|
+
this.eraseCommands.push(new Erase_1.default(finalToErase));
|
193
|
+
this.addCommands.push(...newAddCommands);
|
194
|
+
}
|
75
195
|
this.drawPreviewAt(currentPoint);
|
76
196
|
this.lastPoint = currentPoint;
|
77
197
|
}
|
@@ -79,6 +199,7 @@ class Eraser extends BaseTool_1.default {
|
|
79
199
|
if (event.allPointers.length === 1 || event.current.device === Pointer_1.PointerDevice.Eraser) {
|
80
200
|
this.lastPoint = event.current.canvasPos;
|
81
201
|
this.toRemove = [];
|
202
|
+
this.toAdd = [];
|
82
203
|
this.isFirstEraseEvt = true;
|
83
204
|
this.drawPreviewAt(event.current.canvasPos);
|
84
205
|
return true;
|
@@ -91,18 +212,32 @@ class Eraser extends BaseTool_1.default {
|
|
91
212
|
}
|
92
213
|
onPointerUp(event) {
|
93
214
|
this.eraseTo(event.current.canvasPos);
|
94
|
-
|
215
|
+
const commands = [];
|
216
|
+
if (this.addCommands.length > 0) {
|
217
|
+
this.addCommands.forEach(cmd => cmd.unapply(this.editor));
|
218
|
+
commands.push(...this.toAdd.map(a => EditorImage_1.default.addElement(a)));
|
219
|
+
this.addCommands = [];
|
220
|
+
}
|
221
|
+
if (this.eraseCommands.length > 0) {
|
95
222
|
// Undo commands for each individual component and unite into a single command.
|
96
|
-
this.
|
97
|
-
this.
|
223
|
+
this.eraseCommands.forEach(cmd => cmd.unapply(this.editor));
|
224
|
+
this.eraseCommands = [];
|
98
225
|
const command = new Erase_1.default(this.toRemove);
|
99
|
-
|
226
|
+
commands.push(command);
|
227
|
+
}
|
228
|
+
if (commands.length === 1) {
|
229
|
+
this.editor.dispatch(commands[0]); // dispatch: Makes undo-able.
|
230
|
+
}
|
231
|
+
else {
|
232
|
+
this.editor.dispatch((0, uniteCommands_1.default)(commands));
|
100
233
|
}
|
101
234
|
this.clearPreview();
|
102
235
|
}
|
103
|
-
onGestureCancel() {
|
104
|
-
this.
|
105
|
-
this.
|
236
|
+
onGestureCancel(_event) {
|
237
|
+
this.addCommands.forEach(cmd => cmd.unapply(this.editor));
|
238
|
+
this.eraseCommands.forEach(cmd => cmd.unapply(this.editor));
|
239
|
+
this.eraseCommands = [];
|
240
|
+
this.addCommands = [];
|
106
241
|
this.clearPreview();
|
107
242
|
}
|
108
243
|
onKeyPress(event) {
|
@@ -121,9 +256,14 @@ class Eraser extends BaseTool_1.default {
|
|
121
256
|
}
|
122
257
|
return false;
|
123
258
|
}
|
259
|
+
/** Returns the side-length of the tip of this eraser. */
|
124
260
|
getThickness() {
|
125
261
|
return this.thickness;
|
126
262
|
}
|
263
|
+
/** Sets the side-length of this' tip. */
|
264
|
+
setThickness(thickness) {
|
265
|
+
this.thicknessValue.set(thickness);
|
266
|
+
}
|
127
267
|
/**
|
128
268
|
* Returns a {@link MutableReactiveValue} that can be used to watch
|
129
269
|
* this tool's thickness.
|
@@ -131,8 +271,8 @@ class Eraser extends BaseTool_1.default {
|
|
131
271
|
getThicknessValue() {
|
132
272
|
return this.thicknessValue;
|
133
273
|
}
|
134
|
-
|
135
|
-
this.
|
274
|
+
getModeValue() {
|
275
|
+
return this.modeValue;
|
136
276
|
}
|
137
277
|
}
|
138
278
|
exports.default = Eraser;
|
@@ -10,7 +10,7 @@ import AbstractComponent from '../../components/AbstractComponent';
|
|
10
10
|
import Command from '../../commands/Command';
|
11
11
|
export default class Selection {
|
12
12
|
private editor;
|
13
|
-
private
|
13
|
+
private childwidgets;
|
14
14
|
private originalRegion;
|
15
15
|
private selectionTightBoundingBox;
|
16
16
|
private transformers;
|
@@ -52,7 +52,7 @@ export default class Selection {
|
|
52
52
|
private removedFromImage;
|
53
53
|
private addRemoveSelectionFromImage;
|
54
54
|
private removeDeletedElemsFromSelection;
|
55
|
-
private
|
55
|
+
private activeHandle;
|
56
56
|
private backgroundDragging;
|
57
57
|
onDragStart(pointer: Pointer): boolean;
|
58
58
|
onDragUpdate(pointer: Pointer): void;
|
@@ -57,7 +57,7 @@ class Selection {
|
|
57
57
|
this.hasParent = true;
|
58
58
|
// Maps IDs to whether we removed the component from the image
|
59
59
|
this.removedFromImage = {};
|
60
|
-
this.
|
60
|
+
this.activeHandle = null;
|
61
61
|
this.backgroundDragging = false;
|
62
62
|
this.selectionDuplicatedAnimationTimeout = null;
|
63
63
|
this.originalRegion = new math_1.Rect2(startPoint.x, startPoint.y, 0, 0);
|
@@ -99,14 +99,14 @@ class Selection {
|
|
99
99
|
side: math_1.Vec2.of(0.5, 0),
|
100
100
|
icon: this.editor.icons.makeRotateIcon(),
|
101
101
|
}, this, this.editor.viewport, (startPoint) => this.transformers.rotate.onDragStart(startPoint), (currentPoint) => this.transformers.rotate.onDragUpdate(currentPoint), () => this.transformers.rotate.onDragEnd());
|
102
|
-
this.
|
102
|
+
this.childwidgets = [
|
103
103
|
resizeBothHandle,
|
104
104
|
...resizeHorizontalHandles,
|
105
105
|
resizeVerticalHandle,
|
106
106
|
rotationHandle,
|
107
107
|
];
|
108
|
-
for (const
|
109
|
-
|
108
|
+
for (const widget of this.childwidgets) {
|
109
|
+
widget.addTo(this.backgroundElem);
|
110
110
|
}
|
111
111
|
this.updateUI();
|
112
112
|
}
|
@@ -321,8 +321,8 @@ class Selection {
|
|
321
321
|
else {
|
322
322
|
this.innerContainer.classList.remove('-empty');
|
323
323
|
}
|
324
|
-
for (const
|
325
|
-
|
324
|
+
for (const widget of this.childwidgets) {
|
325
|
+
widget.updatePosition(this.getScreenRegion());
|
326
326
|
}
|
327
327
|
}
|
328
328
|
// Add/remove the contents of this seleciton from the editor.
|
@@ -376,16 +376,16 @@ class Selection {
|
|
376
376
|
onDragStart(pointer) {
|
377
377
|
// Clear the HTML selection (prevent HTML drag and drop being triggered by this drag)
|
378
378
|
document.getSelection()?.removeAllRanges();
|
379
|
-
this.
|
379
|
+
this.activeHandle = null;
|
380
380
|
let result = false;
|
381
381
|
this.backgroundDragging = false;
|
382
382
|
if (this.region.containsPoint(pointer.canvasPos)) {
|
383
383
|
this.backgroundDragging = true;
|
384
384
|
result = true;
|
385
385
|
}
|
386
|
-
for (const
|
387
|
-
if (
|
388
|
-
this.
|
386
|
+
for (const widget of this.childwidgets) {
|
387
|
+
if (widget.containsPoint(pointer.canvasPos)) {
|
388
|
+
this.activeHandle = widget;
|
389
389
|
this.backgroundDragging = false;
|
390
390
|
result = true;
|
391
391
|
}
|
@@ -394,8 +394,8 @@ class Selection {
|
|
394
394
|
this.removeDeletedElemsFromSelection();
|
395
395
|
this.addRemoveSelectionFromImage(false);
|
396
396
|
}
|
397
|
-
if (this.
|
398
|
-
this.
|
397
|
+
if (this.activeHandle) {
|
398
|
+
this.activeHandle.handleDragStart(pointer);
|
399
399
|
}
|
400
400
|
if (this.backgroundDragging) {
|
401
401
|
this.transformers.drag.onDragStart(pointer.canvasPos);
|
@@ -406,25 +406,25 @@ class Selection {
|
|
406
406
|
if (this.backgroundDragging) {
|
407
407
|
this.transformers.drag.onDragUpdate(pointer.canvasPos);
|
408
408
|
}
|
409
|
-
if (this.
|
410
|
-
this.
|
409
|
+
if (this.activeHandle) {
|
410
|
+
this.activeHandle.handleDragUpdate(pointer);
|
411
411
|
}
|
412
412
|
}
|
413
413
|
onDragEnd() {
|
414
414
|
if (this.backgroundDragging) {
|
415
415
|
this.transformers.drag.onDragEnd();
|
416
416
|
}
|
417
|
-
else if (this.
|
418
|
-
this.
|
417
|
+
else if (this.activeHandle) {
|
418
|
+
this.activeHandle.handleDragEnd();
|
419
419
|
}
|
420
420
|
this.addRemoveSelectionFromImage(true);
|
421
421
|
this.backgroundDragging = false;
|
422
|
-
this.
|
422
|
+
this.activeHandle = null;
|
423
423
|
this.updateUI();
|
424
424
|
}
|
425
425
|
onDragCancel() {
|
426
426
|
this.backgroundDragging = false;
|
427
|
-
this.
|
427
|
+
this.activeHandle = null;
|
428
428
|
this.setTransform(math_1.Mat33.identity);
|
429
429
|
this.addRemoveSelectionFromImage(true);
|
430
430
|
this.updateUI();
|
@@ -452,7 +452,7 @@ class Selection {
|
|
452
452
|
return false;
|
453
453
|
}
|
454
454
|
deleteSelectedObjects() {
|
455
|
-
if (this.backgroundDragging || this.
|
455
|
+
if (this.backgroundDragging || this.activeHandle) {
|
456
456
|
this.onDragEnd();
|
457
457
|
}
|
458
458
|
return new Erase_1.default(this.selectedElems);
|
@@ -469,7 +469,7 @@ class Selection {
|
|
469
469
|
}, animationDuration);
|
470
470
|
}
|
471
471
|
async duplicateSelectedObjects() {
|
472
|
-
const wasTransforming = this.backgroundDragging || this.
|
472
|
+
const wasTransforming = this.backgroundDragging || this.activeHandle;
|
473
473
|
let tmpApplyCommand = null;
|
474
474
|
if (!wasTransforming) {
|
475
475
|
this.runSelectionDuplicatedAnimation();
|
@@ -2,6 +2,7 @@ import { Point2, Vec2 } from '@js-draw/math';
|
|
2
2
|
import Selection from './Selection';
|
3
3
|
import Pointer from '../../Pointer';
|
4
4
|
import Viewport from '../../Viewport';
|
5
|
+
import { SelectionBoxChild } from './types';
|
5
6
|
export declare enum HandleAction {
|
6
7
|
ResizeXY = "resize-xy",
|
7
8
|
Rotate = "rotate",
|
@@ -17,7 +18,7 @@ export declare const handleSize = 30;
|
|
17
18
|
export type DragStartCallback = (startPoint: Point2) => void;
|
18
19
|
export type DragUpdateCallback = (canvasPoint: Point2) => void;
|
19
20
|
export type DragEndCallback = () => Promise<void> | void;
|
20
|
-
export default class SelectionHandle {
|
21
|
+
export default class SelectionHandle implements SelectionBoxChild {
|
21
22
|
readonly presentation: HandlePresentation;
|
22
23
|
private readonly parent;
|
23
24
|
private readonly viewport;
|
@@ -34,6 +35,11 @@ export default class SelectionHandle {
|
|
34
35
|
* element visible on the screen.
|
35
36
|
*/
|
36
37
|
addTo(container: HTMLElement): void;
|
38
|
+
/**
|
39
|
+
* Removes this element from its container. Should only be called
|
40
|
+
* after {@link addTo}.
|
41
|
+
*/
|
42
|
+
remove(): void;
|
37
43
|
/**
|
38
44
|
* Returns this handle's bounding box relative to the top left of the
|
39
45
|
* selection box.
|
@@ -48,7 +54,7 @@ export default class SelectionHandle {
|
|
48
54
|
/** @returns true iff `point` (in editor **canvas** coordinates) is in this. */
|
49
55
|
containsPoint(point: Point2): boolean;
|
50
56
|
private dragLastPos;
|
51
|
-
handleDragStart(pointer: Pointer):
|
57
|
+
handleDragStart(pointer: Pointer): boolean;
|
52
58
|
handleDragUpdate(pointer: Pointer): void;
|
53
59
|
handleDragEnd(): void | Promise<void>;
|
54
60
|
setSnapToGrid(snap: boolean): void;
|
@@ -64,6 +64,11 @@ class SelectionHandle {
|
|
64
64
|
addTo(container) {
|
65
65
|
container.appendChild(this.element);
|
66
66
|
}
|
67
|
+
/**
|
68
|
+
* Removes this element from its container. Should only be called
|
69
|
+
* after {@link addTo}.
|
70
|
+
*/
|
71
|
+
remove() { this.element.remove(); }
|
67
72
|
/**
|
68
73
|
* Returns this handle's bounding box relative to the top left of the
|
69
74
|
* selection box.
|
@@ -112,6 +117,7 @@ class SelectionHandle {
|
|
112
117
|
handleDragStart(pointer) {
|
113
118
|
this.onDragStart(pointer.canvasPos);
|
114
119
|
this.dragLastPos = pointer.canvasPos;
|
120
|
+
return true;
|
115
121
|
}
|
116
122
|
handleDragUpdate(pointer) {
|
117
123
|
if (!this.dragLastPos) {
|