js-draw 1.16.1 → 1.18.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +70 -10
- package/dist/bundle.js +2 -2
- package/dist/bundledStyles.js +1 -1
- package/dist/cjs/Editor.d.ts +75 -7
- package/dist/cjs/Editor.js +93 -90
- package/dist/cjs/Pointer.d.ts +2 -1
- package/dist/cjs/Pointer.js +9 -2
- package/dist/cjs/commands/localization.d.ts +1 -0
- package/dist/cjs/commands/localization.js +1 -0
- package/dist/cjs/commands/uniteCommands.d.ts +5 -1
- package/dist/cjs/commands/uniteCommands.js +33 -7
- 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.d.ts +36 -1
- package/dist/cjs/components/TextComponent.js +39 -1
- package/dist/cjs/components/builders/ArrowBuilder.js +1 -1
- package/dist/cjs/components/builders/PolylineBuilder.d.ts +35 -0
- package/dist/cjs/components/builders/PolylineBuilder.js +122 -0
- package/dist/cjs/components/builders/PressureSensitiveFreehandLineBuilder.d.ts +1 -1
- package/dist/cjs/components/builders/PressureSensitiveFreehandLineBuilder.js +44 -51
- package/dist/cjs/components/builders/autocorrect/makeShapeFitAutocorrect.js +1 -1
- package/dist/cjs/components/lib.d.ts +1 -0
- package/dist/cjs/components/lib.js +3 -1
- package/dist/cjs/components/util/StrokeSmoother.js +4 -4
- package/dist/cjs/image/EditorImage.d.ts +4 -1
- package/dist/cjs/image/EditorImage.js +5 -2
- package/dist/cjs/inputEvents.d.ts +11 -1
- package/dist/cjs/localizations/comments.d.ts +3 -0
- package/dist/cjs/localizations/comments.js +3 -0
- package/dist/cjs/localizations/de.js +1 -3
- package/dist/cjs/localizations/es.js +3 -3
- package/dist/cjs/rendering/renderers/CanvasRenderer.d.ts +7 -0
- package/dist/cjs/rendering/renderers/CanvasRenderer.js +16 -0
- package/dist/cjs/rendering/renderers/SVGRenderer.js +1 -1
- package/dist/cjs/testing/createEditor.d.ts +2 -2
- package/dist/cjs/testing/createEditor.js +2 -2
- package/dist/cjs/toolbar/IconProvider.d.ts +9 -4
- package/dist/cjs/toolbar/IconProvider.js +21 -7
- package/dist/cjs/toolbar/localization.d.ts +6 -1
- package/dist/cjs/toolbar/localization.js +7 -2
- package/dist/cjs/toolbar/widgets/DocumentPropertiesWidget.js +24 -1
- package/dist/cjs/toolbar/widgets/EraserToolWidget.d.ts +6 -1
- package/dist/cjs/toolbar/widgets/EraserToolWidget.js +45 -5
- package/dist/cjs/toolbar/widgets/PenToolWidget.d.ts +1 -1
- package/dist/cjs/toolbar/widgets/PenToolWidget.js +17 -4
- package/dist/cjs/toolbar/widgets/PenToolWidget.test.d.ts +1 -0
- package/dist/cjs/toolbar/widgets/keybindings.js +1 -1
- package/dist/cjs/tools/Eraser.d.ts +24 -4
- package/dist/cjs/tools/Eraser.js +108 -21
- package/dist/cjs/tools/InputFilter/InputStabilizer.js +3 -3
- package/dist/cjs/tools/PasteHandler.js +35 -10
- package/dist/cjs/tools/Pen.js +2 -2
- package/dist/cjs/tools/SelectionTool/SelectionTool.js +23 -4
- package/dist/cjs/tools/SelectionTool/ToPointerAutoscroller.js +1 -1
- package/dist/cjs/tools/ToolController.d.ts +17 -1
- package/dist/cjs/tools/ToolController.js +21 -8
- package/dist/cjs/tools/lib.d.ts +1 -4
- package/dist/cjs/tools/lib.js +2 -4
- package/dist/cjs/tools/localization.d.ts +2 -2
- package/dist/cjs/tools/localization.js +2 -2
- package/dist/cjs/util/ClipboardHandler.d.ts +27 -0
- package/dist/cjs/util/ClipboardHandler.js +205 -0
- package/dist/cjs/util/ClipboardHandler.test.d.ts +1 -0
- package/dist/cjs/version.d.ts +5 -0
- package/dist/cjs/version.js +6 -1
- package/dist/mjs/Editor.d.ts +75 -7
- package/dist/mjs/Editor.mjs +93 -90
- package/dist/mjs/Pointer.d.ts +2 -1
- package/dist/mjs/Pointer.mjs +9 -2
- package/dist/mjs/commands/localization.d.ts +1 -0
- package/dist/mjs/commands/localization.mjs +1 -0
- package/dist/mjs/commands/uniteCommands.d.ts +5 -1
- package/dist/mjs/commands/uniteCommands.mjs +33 -7
- 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.d.ts +36 -1
- package/dist/mjs/components/TextComponent.mjs +40 -2
- package/dist/mjs/components/builders/ArrowBuilder.mjs +1 -1
- package/dist/mjs/components/builders/PolylineBuilder.d.ts +35 -0
- package/dist/mjs/components/builders/PolylineBuilder.mjs +115 -0
- package/dist/mjs/components/builders/PressureSensitiveFreehandLineBuilder.d.ts +1 -1
- package/dist/mjs/components/builders/PressureSensitiveFreehandLineBuilder.mjs +45 -52
- package/dist/mjs/components/builders/autocorrect/makeShapeFitAutocorrect.mjs +1 -1
- package/dist/mjs/components/lib.d.ts +1 -0
- package/dist/mjs/components/lib.mjs +1 -0
- package/dist/mjs/components/util/StrokeSmoother.mjs +4 -4
- package/dist/mjs/image/EditorImage.d.ts +4 -1
- package/dist/mjs/image/EditorImage.mjs +5 -2
- package/dist/mjs/inputEvents.d.ts +11 -1
- package/dist/mjs/localizations/comments.d.ts +3 -0
- package/dist/mjs/localizations/comments.mjs +3 -0
- package/dist/mjs/localizations/de.mjs +1 -3
- package/dist/mjs/localizations/es.mjs +3 -3
- package/dist/mjs/rendering/renderers/CanvasRenderer.d.ts +7 -0
- package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +16 -0
- package/dist/mjs/rendering/renderers/SVGRenderer.mjs +1 -1
- package/dist/mjs/testing/createEditor.d.ts +2 -2
- package/dist/mjs/testing/createEditor.mjs +2 -2
- package/dist/mjs/toolbar/IconProvider.d.ts +9 -4
- package/dist/mjs/toolbar/IconProvider.mjs +21 -7
- package/dist/mjs/toolbar/localization.d.ts +6 -1
- package/dist/mjs/toolbar/localization.mjs +7 -2
- package/dist/mjs/toolbar/widgets/DocumentPropertiesWidget.mjs +24 -1
- package/dist/mjs/toolbar/widgets/EraserToolWidget.d.ts +6 -1
- package/dist/mjs/toolbar/widgets/EraserToolWidget.mjs +47 -6
- package/dist/mjs/toolbar/widgets/PenToolWidget.d.ts +1 -1
- package/dist/mjs/toolbar/widgets/PenToolWidget.mjs +17 -4
- package/dist/mjs/toolbar/widgets/PenToolWidget.test.d.ts +1 -0
- package/dist/mjs/toolbar/widgets/keybindings.mjs +1 -1
- package/dist/mjs/tools/Eraser.d.ts +24 -4
- package/dist/mjs/tools/Eraser.mjs +108 -22
- package/dist/mjs/tools/InputFilter/InputStabilizer.mjs +3 -3
- package/dist/mjs/tools/PasteHandler.mjs +35 -10
- package/dist/mjs/tools/Pen.mjs +2 -2
- package/dist/mjs/tools/SelectionTool/SelectionTool.mjs +23 -4
- package/dist/mjs/tools/SelectionTool/ToPointerAutoscroller.mjs +1 -1
- package/dist/mjs/tools/ToolController.d.ts +17 -1
- package/dist/mjs/tools/ToolController.mjs +21 -8
- package/dist/mjs/tools/lib.d.ts +1 -4
- package/dist/mjs/tools/lib.mjs +1 -4
- package/dist/mjs/tools/localization.d.ts +2 -2
- package/dist/mjs/tools/localization.mjs +2 -2
- package/dist/mjs/util/ClipboardHandler.d.ts +27 -0
- package/dist/mjs/util/ClipboardHandler.mjs +200 -0
- package/dist/mjs/util/ClipboardHandler.test.d.ts +1 -0
- package/dist/mjs/version.d.ts +5 -0
- package/dist/mjs/version.mjs +6 -1
- package/package.json +6 -6
@@ -30,7 +30,8 @@ exports.defaultToolbarLocalization = {
|
|
30
30
|
save: 'Save',
|
31
31
|
undo: 'Undo',
|
32
32
|
redo: 'Redo',
|
33
|
-
|
33
|
+
fullStrokeEraser: 'Full stroke eraser',
|
34
|
+
selectPenType: 'Pen type',
|
34
35
|
selectShape: 'Shape',
|
35
36
|
pickColorFromScreen: 'Pick color from screen',
|
36
37
|
clickToPickColorAnnouncement: 'Click on the screen to pick a color',
|
@@ -48,6 +49,7 @@ exports.defaultToolbarLocalization = {
|
|
48
49
|
strokeAutocorrect: 'Autocorrect',
|
49
50
|
touchPanning: 'Touchscreen panning',
|
50
51
|
roundedTipPen: 'Round',
|
52
|
+
roundedTipPen2: 'Polyline',
|
51
53
|
flatTipPen: 'Flat',
|
52
54
|
arrowPen: 'Arrow',
|
53
55
|
linePen: 'Line',
|
@@ -62,7 +64,7 @@ exports.defaultToolbarLocalization = {
|
|
62
64
|
penDropdown__baseHelpText: 'This tool draws shapes or freehand lines.',
|
63
65
|
penDropdown__colorHelpText: 'Changes the pen\'s color',
|
64
66
|
penDropdown__thicknessHelpText: 'Changes the thickness of strokes drawn by the pen.',
|
65
|
-
penDropdown__penTypeHelpText: 'Changes the pen style.\n\nEither a “pen
|
67
|
+
penDropdown__penTypeHelpText: 'Changes the pen style.\n\nEither a “pen” style or “shape” can be chosen. Choosing a “pen” style draws freehand lines. Choosing a “shape” draws shapes.',
|
66
68
|
penDropdown__autocorrectHelpText: 'Converts approximate freehand lines and rectangles to perfect ones.\n\nThe pen must be held stationary at the end of a stroke to trigger a correction.',
|
67
69
|
penDropdown__stabilizationHelpText: 'Draws smoother strokes.\n\nThis also adds a short delay between the mouse/stylus and the stroke.',
|
68
70
|
handDropdown__baseHelpText: 'This tool is responsible for scrolling, rotating, and zooming the editor.',
|
@@ -72,6 +74,9 @@ exports.defaultToolbarLocalization = {
|
|
72
74
|
handDropdown__zoomDisplayHelpText: 'Shows the current zoom level. 100% shows the image at its actual size.',
|
73
75
|
handDropdown__touchPanningHelpText: 'When enabled, touch gestures move the image rather than select or draw.',
|
74
76
|
handDropdown__lockRotationHelpText: 'When enabled, prevents touch gestures from rotating the screen.',
|
77
|
+
eraserDropdown__baseHelpText: 'This tool removes strokes, images, and text under the cursor.',
|
78
|
+
eraserDropdown__thicknessHelpText: 'Changes the size of the eraser.',
|
79
|
+
eraserDropdown__fullStrokeEraserHelpText: 'When in full-stroke mode, entire shapes are erased.\n\nWhen not in full-stroke mode, shapes can be partially erased.',
|
75
80
|
selectionDropdown__baseHelpText: 'Selects content and manipulates the selection',
|
76
81
|
selectionDropdown__resizeToHelpText: 'Crops the drawing to the size of what\'s currently selected.\n\nIf auto-resize is enabled, it will be disabled.',
|
77
82
|
selectionDropdown__deleteHelpText: 'Erases selected items.',
|
@@ -194,7 +194,30 @@ class DocumentPropertiesWidget extends BaseWidget_1.default {
|
|
194
194
|
row.replaceChildren(label, input);
|
195
195
|
return {
|
196
196
|
setValue: (value) => {
|
197
|
-
|
197
|
+
// Slightly improve the case where the user tries to change the
|
198
|
+
// first digit of a dimension like 600.
|
199
|
+
//
|
200
|
+
// As changing the value also gives the image zero size (which is unsupported,
|
201
|
+
// .setValue is called immediately). We work around this by trying to select
|
202
|
+
// the added/changed digits.
|
203
|
+
//
|
204
|
+
// See https://github.com/personalizedrefrigerator/js-draw/issues/58.
|
205
|
+
if (document.activeElement === input && input.value.match(/^0*$/)) {
|
206
|
+
// We need to switch to type="text" and back to type="number" because
|
207
|
+
// number inputs don't support selection.
|
208
|
+
//
|
209
|
+
// See https://stackoverflow.com/q/22381837
|
210
|
+
const originalValue = input.value;
|
211
|
+
input.type = 'text';
|
212
|
+
input.value = value.toString();
|
213
|
+
// Select the added digits
|
214
|
+
const lengthToSelect = Math.max(1, input.value.length - originalValue.length);
|
215
|
+
input.setSelectionRange(0, lengthToSelect);
|
216
|
+
input.type = 'number';
|
217
|
+
}
|
218
|
+
else {
|
219
|
+
input.value = value.toString();
|
220
|
+
}
|
198
221
|
},
|
199
222
|
setIsAutomaticSize: (automatic) => {
|
200
223
|
input.disabled = automatic;
|
@@ -1,15 +1,20 @@
|
|
1
1
|
import Editor from '../../Editor';
|
2
2
|
import Eraser from '../../tools/Eraser';
|
3
3
|
import { ToolbarLocalization } from '../localization';
|
4
|
+
import HelpDisplay from '../utils/HelpDisplay';
|
4
5
|
import BaseToolWidget from './BaseToolWidget';
|
5
6
|
import { SavedToolbuttonState } from './BaseWidget';
|
6
7
|
export default class EraserToolWidget extends BaseToolWidget {
|
7
8
|
private tool;
|
8
9
|
private updateInputs;
|
9
10
|
constructor(editor: Editor, tool: Eraser, localizationTable?: ToolbarLocalization);
|
11
|
+
protected getHelpText(): string;
|
10
12
|
protected getTitle(): string;
|
13
|
+
private makeIconForType;
|
11
14
|
protected createIcon(): Element;
|
12
|
-
|
15
|
+
private static idCounter;
|
16
|
+
private makeEraserTypeSelector;
|
17
|
+
protected fillDropdown(dropdown: HTMLElement, helpDisplay?: HelpDisplay): boolean;
|
13
18
|
serializeState(): SavedToolbuttonState;
|
14
19
|
deserializeFrom(state: SavedToolbuttonState): void;
|
15
20
|
}
|
@@ -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
|
+
const Eraser_1 = require("../../tools/Eraser");
|
6
7
|
const types_1 = require("../../types");
|
7
8
|
const constants_1 = require("../constants");
|
8
9
|
const BaseToolWidget_1 = __importDefault(require("./BaseToolWidget"));
|
@@ -19,26 +20,57 @@ class EraserToolWidget extends BaseToolWidget_1.default {
|
|
19
20
|
}
|
20
21
|
});
|
21
22
|
}
|
23
|
+
getHelpText() {
|
24
|
+
return this.localizationTable.eraserDropdown__baseHelpText;
|
25
|
+
}
|
22
26
|
getTitle() {
|
23
27
|
return this.localizationTable.eraser;
|
24
28
|
}
|
29
|
+
makeIconForType(mode) {
|
30
|
+
return this.editor.icons.makeEraserIcon(this.tool.getThickness(), mode);
|
31
|
+
}
|
25
32
|
createIcon() {
|
26
|
-
return this.
|
33
|
+
return this.makeIconForType(this.tool.getModeValue().get());
|
34
|
+
}
|
35
|
+
makeEraserTypeSelector(helpDisplay) {
|
36
|
+
const container = document.createElement('div');
|
37
|
+
const labelElement = document.createElement('label');
|
38
|
+
const checkboxElement = document.createElement('input');
|
39
|
+
checkboxElement.id = `${constants_1.toolbarCSSPrefix}eraserToolWidget-${EraserToolWidget.idCounter++}`;
|
40
|
+
labelElement.htmlFor = checkboxElement.id;
|
41
|
+
labelElement.innerText = this.localizationTable.fullStrokeEraser;
|
42
|
+
checkboxElement.type = 'checkbox';
|
43
|
+
checkboxElement.oninput = () => {
|
44
|
+
this.tool.getModeValue().set(checkboxElement.checked ? Eraser_1.EraserMode.FullStroke : Eraser_1.EraserMode.PartialStroke);
|
45
|
+
};
|
46
|
+
const updateValue = () => {
|
47
|
+
checkboxElement.checked = this.tool.getModeValue().get() === Eraser_1.EraserMode.FullStroke;
|
48
|
+
};
|
49
|
+
container.replaceChildren(labelElement, checkboxElement);
|
50
|
+
helpDisplay?.registerTextHelpForElement(container, this.localizationTable.eraserDropdown__fullStrokeEraserHelpText);
|
51
|
+
return {
|
52
|
+
addTo: (parent) => {
|
53
|
+
parent.appendChild(container);
|
54
|
+
},
|
55
|
+
updateValue,
|
56
|
+
};
|
27
57
|
}
|
28
|
-
fillDropdown(dropdown) {
|
58
|
+
fillDropdown(dropdown, helpDisplay) {
|
29
59
|
const container = document.createElement('div');
|
30
60
|
container.classList.add(`${constants_1.toolbarCSSPrefix}spacedList`, `${constants_1.toolbarCSSPrefix}nonbutton-controls-main-list`);
|
31
61
|
const thicknessSlider = (0, makeThicknessSlider_1.default)(this.editor, thickness => {
|
32
62
|
this.tool.setThickness(thickness);
|
33
63
|
});
|
34
64
|
thicknessSlider.setBounds(10, 55);
|
65
|
+
helpDisplay?.registerTextHelpForElement(thicknessSlider.container, this.localizationTable.eraserDropdown__thicknessHelpText);
|
66
|
+
const modeSelector = this.makeEraserTypeSelector(helpDisplay);
|
35
67
|
this.updateInputs = () => {
|
36
68
|
thicknessSlider.setValue(this.tool.getThickness());
|
69
|
+
modeSelector.updateValue();
|
37
70
|
};
|
38
71
|
this.updateInputs();
|
39
|
-
|
40
|
-
|
41
|
-
container.replaceChildren(thicknessSlider.container, spacer);
|
72
|
+
container.replaceChildren(thicknessSlider.container);
|
73
|
+
modeSelector.addTo(container);
|
42
74
|
dropdown.replaceChildren(container);
|
43
75
|
return true;
|
44
76
|
}
|
@@ -46,6 +78,7 @@ class EraserToolWidget extends BaseToolWidget_1.default {
|
|
46
78
|
return {
|
47
79
|
...super.serializeState(),
|
48
80
|
thickness: this.tool.getThickness(),
|
81
|
+
mode: this.tool.getModeValue().get(),
|
49
82
|
};
|
50
83
|
}
|
51
84
|
deserializeFrom(state) {
|
@@ -57,6 +90,13 @@ class EraserToolWidget extends BaseToolWidget_1.default {
|
|
57
90
|
}
|
58
91
|
this.tool.setThickness(parsedThickness);
|
59
92
|
}
|
93
|
+
if (state.mode) {
|
94
|
+
const mode = state.mode;
|
95
|
+
if (Object.values(Eraser_1.EraserMode).includes(mode)) {
|
96
|
+
this.tool.getModeValue().set(mode);
|
97
|
+
}
|
98
|
+
}
|
60
99
|
}
|
61
100
|
}
|
101
|
+
EraserToolWidget.idCounter = 0;
|
62
102
|
exports.default = EraserToolWidget;
|
@@ -15,7 +15,7 @@ export interface PenTypeRecord {
|
|
15
15
|
export default class PenToolWidget extends BaseToolWidget {
|
16
16
|
private tool;
|
17
17
|
private updateInputs;
|
18
|
-
protected penTypes: PenTypeRecord[];
|
18
|
+
protected penTypes: Readonly<PenTypeRecord>[];
|
19
19
|
protected shapelikeIDs: string[];
|
20
20
|
private static idCounter;
|
21
21
|
constructor(editor: Editor, tool: Pen, localization?: ToolbarLocalization);
|
@@ -17,6 +17,7 @@ const keybindings_1 = require("./keybindings");
|
|
17
17
|
const constants_1 = require("../constants");
|
18
18
|
const makeThicknessSlider_1 = __importDefault(require("./components/makeThicknessSlider"));
|
19
19
|
const makeGridSelector_1 = __importDefault(require("./components/makeGridSelector"));
|
20
|
+
const PolylineBuilder_1 = require("../../components/builders/PolylineBuilder");
|
20
21
|
class PenToolWidget extends BaseToolWidget_1.default {
|
21
22
|
constructor(editor, tool, localization) {
|
22
23
|
super(editor, tool, 'pen', localization);
|
@@ -24,8 +25,12 @@ class PenToolWidget extends BaseToolWidget_1.default {
|
|
24
25
|
this.updateInputs = () => { };
|
25
26
|
// Pen types that correspond to
|
26
27
|
this.shapelikeIDs = ['pressure-sensitive-pen', 'freehand-pen'];
|
28
|
+
// Additional client-specified pens.
|
29
|
+
const additionalPens = editor.getCurrentSettings().pens?.additionalPenTypes ?? [];
|
30
|
+
const filterPens = editor.getCurrentSettings().pens?.filterPenTypes ?? (() => true);
|
27
31
|
// Default pen types
|
28
32
|
this.penTypes = [
|
33
|
+
// Non-shape pens
|
29
34
|
{
|
30
35
|
name: this.localizationTable.flatTipPen,
|
31
36
|
id: 'pressure-sensitive-pen',
|
@@ -36,6 +41,13 @@ class PenToolWidget extends BaseToolWidget_1.default {
|
|
36
41
|
id: 'freehand-pen',
|
37
42
|
factory: FreehandLineBuilder_1.makeFreehandLineBuilder,
|
38
43
|
},
|
44
|
+
{
|
45
|
+
name: this.localizationTable.roundedTipPen2,
|
46
|
+
id: 'polyline-pen',
|
47
|
+
factory: PolylineBuilder_1.makePolylineBuilder,
|
48
|
+
},
|
49
|
+
...(additionalPens.filter(pen => !pen.isShapeBuilder)),
|
50
|
+
// Shape pens
|
39
51
|
{
|
40
52
|
name: this.localizationTable.arrowPen,
|
41
53
|
id: 'arrow',
|
@@ -65,8 +77,9 @@ class PenToolWidget extends BaseToolWidget_1.default {
|
|
65
77
|
id: 'outlined-circle',
|
66
78
|
isShapeBuilder: true,
|
67
79
|
factory: CircleBuilder_1.makeOutlinedCircleBuilder,
|
68
|
-
}
|
69
|
-
|
80
|
+
},
|
81
|
+
...(additionalPens.filter(pen => pen.isShapeBuilder)),
|
82
|
+
].filter(filterPens);
|
70
83
|
this.editor.notifier.on(types_1.EditorEventType.ToolUpdated, toolEvt => {
|
71
84
|
if (toolEvt.kind !== types_1.EditorEventType.ToolUpdated) {
|
72
85
|
throw new Error('Invalid event type!');
|
@@ -110,7 +123,7 @@ class PenToolWidget extends BaseToolWidget_1.default {
|
|
110
123
|
style.factory = record.factory;
|
111
124
|
}
|
112
125
|
const strokeFactory = record?.factory;
|
113
|
-
if (!strokeFactory || strokeFactory === FreehandLineBuilder_1.makeFreehandLineBuilder || strokeFactory === PressureSensitiveFreehandLineBuilder_1.makePressureSensitiveFreehandLineBuilder) {
|
126
|
+
if (!strokeFactory || strokeFactory === FreehandLineBuilder_1.makeFreehandLineBuilder || strokeFactory === PressureSensitiveFreehandLineBuilder_1.makePressureSensitiveFreehandLineBuilder || strokeFactory === PolylineBuilder_1.makePolylineBuilder) {
|
114
127
|
return this.editor.icons.makePenIcon(style);
|
115
128
|
}
|
116
129
|
else {
|
@@ -130,7 +143,7 @@ class PenToolWidget extends BaseToolWidget_1.default {
|
|
130
143
|
isShapeBuilder: penType.isShapeBuilder ?? false,
|
131
144
|
};
|
132
145
|
});
|
133
|
-
const penSelector = (0, makeGridSelector_1.default)(this.localizationTable.
|
146
|
+
const penSelector = (0, makeGridSelector_1.default)(this.localizationTable.selectPenType, this.getCurrentPenTypeIdx(), allChoices.filter(choice => !choice.isShapeBuilder));
|
134
147
|
const shapeSelector = (0, makeGridSelector_1.default)(this.localizationTable.selectShape, this.getCurrentPenTypeIdx(), allChoices.filter(choice => choice.isShapeBuilder));
|
135
148
|
const onSelectorUpdate = (newPenTypeIndex) => {
|
136
149
|
this.tool.setStrokeFactory(this.penTypes[newPenTypeIndex].factory);
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -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));
|
@@ -2,30 +2,50 @@ import { 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);
|
14
25
|
private clearPreview;
|
15
26
|
private getSizeOnCanvas;
|
16
27
|
private drawPreviewAt;
|
28
|
+
/**
|
29
|
+
* @returns the eraser rectangle in canvas coordinates.
|
30
|
+
*
|
31
|
+
* For now, all erasers are rectangles or points.
|
32
|
+
*/
|
17
33
|
private getEraserRect;
|
34
|
+
/** Erases in a line from the last point to the current. */
|
18
35
|
private eraseTo;
|
19
36
|
onPointerDown(event: PointerEvt): boolean;
|
20
37
|
onPointerMove(event: PointerEvt): void;
|
21
38
|
onPointerUp(event: PointerEvt): void;
|
22
39
|
onGestureCancel(): void;
|
23
40
|
onKeyPress(event: KeyPressEvent): boolean;
|
41
|
+
/** Returns the side-length of the tip of this eraser. */
|
24
42
|
getThickness(): number;
|
43
|
+
/** Sets the side-length of this' tip. */
|
44
|
+
setThickness(thickness: number): void;
|
25
45
|
/**
|
26
46
|
* Returns a {@link MutableReactiveValue} that can be used to watch
|
27
47
|
* this tool's thickness.
|
28
48
|
*/
|
29
49
|
getThicknessValue(): MutableReactiveValue<number>;
|
30
|
-
|
50
|
+
getModeValue(): MutableReactiveValue<EraserMode>;
|
31
51
|
}
|
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,24 @@ 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 = {}));
|
13
22
|
class Eraser extends BaseTool_1.default {
|
14
|
-
constructor(editor, description) {
|
23
|
+
constructor(editor, description, options) {
|
15
24
|
super(editor.notifier, description);
|
16
25
|
this.editor = editor;
|
17
26
|
this.lastPoint = null;
|
18
27
|
this.isFirstEraseEvt = true;
|
19
|
-
this.thickness = 10;
|
20
28
|
// Commands that each remove one element
|
21
|
-
this.
|
29
|
+
this.eraseCommands = [];
|
30
|
+
this.addCommands = [];
|
31
|
+
this.thickness = options?.thickness ?? 10;
|
22
32
|
this.thicknessValue = ReactiveValue_1.ReactiveValue.fromInitialValue(this.thickness);
|
23
33
|
this.thicknessValue.onUpdate(value => {
|
24
34
|
this.thickness = value;
|
@@ -27,6 +37,13 @@ class Eraser extends BaseTool_1.default {
|
|
27
37
|
tool: this,
|
28
38
|
});
|
29
39
|
});
|
40
|
+
this.modeValue = ReactiveValue_1.ReactiveValue.fromInitialValue(options?.mode ?? EraserMode.FullStroke);
|
41
|
+
this.modeValue.onUpdate(_value => {
|
42
|
+
this.editor.notifier.dispatch(types_1.EditorEventType.ToolUpdated, {
|
43
|
+
kind: types_1.EditorEventType.ToolUpdated,
|
44
|
+
tool: this,
|
45
|
+
});
|
46
|
+
});
|
30
47
|
}
|
31
48
|
clearPreview() {
|
32
49
|
this.editor.clearWetInk();
|
@@ -39,18 +56,26 @@ class Eraser extends BaseTool_1.default {
|
|
39
56
|
const size = this.getSizeOnCanvas();
|
40
57
|
const renderer = this.editor.display.getWetInkRenderer();
|
41
58
|
const rect = this.getEraserRect(point);
|
59
|
+
const rect2 = this.getEraserRect(this.lastPoint ?? point);
|
42
60
|
const fill = {
|
43
|
-
fill: math_1.Color4.
|
61
|
+
fill: math_1.Color4.transparent,
|
62
|
+
stroke: { width: size / 10, color: math_1.Color4.gray },
|
44
63
|
};
|
45
|
-
renderer.
|
64
|
+
renderer.drawPath((0, RenderablePathSpec_1.pathToRenderable)(math_1.Path.fromConvexHullOf([...rect.corners, ...rect2.corners]), fill));
|
46
65
|
}
|
66
|
+
/**
|
67
|
+
* @returns the eraser rectangle in canvas coordinates.
|
68
|
+
*
|
69
|
+
* For now, all erasers are rectangles or points.
|
70
|
+
*/
|
47
71
|
getEraserRect(centerPoint) {
|
48
72
|
const size = this.getSizeOnCanvas();
|
49
73
|
const halfSize = math_1.Vec2.of(size / 2, size / 2);
|
50
74
|
return math_1.Rect2.fromCorners(centerPoint.minus(halfSize), centerPoint.plus(halfSize));
|
51
75
|
}
|
76
|
+
/** Erases in a line from the last point to the current. */
|
52
77
|
eraseTo(currentPoint) {
|
53
|
-
if (!this.isFirstEraseEvt && currentPoint.
|
78
|
+
if (!this.isFirstEraseEvt && currentPoint.distanceTo(this.lastPoint) === 0) {
|
54
79
|
return;
|
55
80
|
}
|
56
81
|
this.isFirstEraseEvt = false;
|
@@ -65,13 +90,55 @@ class Eraser extends BaseTool_1.default {
|
|
65
90
|
});
|
66
91
|
// Only erase components that could be selected (and thus interacted with)
|
67
92
|
// by the user.
|
68
|
-
const
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
93
|
+
const eraseableElems = intersectingElems.filter(elem => elem.isSelectable());
|
94
|
+
if (this.modeValue.get() === EraserMode.FullStroke) {
|
95
|
+
// Remove any intersecting elements.
|
96
|
+
this.toRemove.push(...eraseableElems);
|
97
|
+
// Create new Erase commands for the now-to-be-erased elements and apply them.
|
98
|
+
const newPartialCommands = eraseableElems.map(elem => new Erase_1.default([elem]));
|
99
|
+
newPartialCommands.forEach(cmd => cmd.apply(this.editor));
|
100
|
+
this.eraseCommands.push(...newPartialCommands);
|
101
|
+
}
|
102
|
+
else {
|
103
|
+
const toErase = [];
|
104
|
+
const toAdd = [];
|
105
|
+
for (const targetElem of eraseableElems) {
|
106
|
+
toErase.push(targetElem);
|
107
|
+
// Completely delete items that can't be divided.
|
108
|
+
if (!targetElem.withRegionErased) {
|
109
|
+
continue;
|
110
|
+
}
|
111
|
+
// Completely delete items that are completely or almost completely
|
112
|
+
// contained within the eraser.
|
113
|
+
const grownRect = eraserRect.grownBy(eraserRect.maxDimension / 3);
|
114
|
+
if (grownRect.containsRect(targetElem.getExactBBox())) {
|
115
|
+
continue;
|
116
|
+
}
|
117
|
+
// Join the current and previous rectangles so that points between events are also
|
118
|
+
// erased.
|
119
|
+
const erasePath = math_1.Path.fromConvexHullOf([
|
120
|
+
...eraserRect.corners, ...this.getEraserRect(this.lastPoint ?? currentPoint).corners
|
121
|
+
].map(p => this.editor.viewport.roundPoint(p)));
|
122
|
+
toAdd.push(...targetElem.withRegionErased(erasePath, this.editor.viewport));
|
123
|
+
}
|
124
|
+
const eraseCommand = new Erase_1.default(toErase);
|
125
|
+
const newAddCommands = toAdd.map(elem => EditorImage_1.default.addElement(elem));
|
126
|
+
eraseCommand.apply(this.editor);
|
127
|
+
newAddCommands.forEach(command => command.apply(this.editor));
|
128
|
+
const finalToErase = [];
|
129
|
+
for (const item of toErase) {
|
130
|
+
if (this.toAdd.includes(item)) {
|
131
|
+
this.toAdd = this.toAdd.filter(i => i !== item);
|
132
|
+
}
|
133
|
+
else {
|
134
|
+
finalToErase.push(item);
|
135
|
+
}
|
136
|
+
}
|
137
|
+
this.toRemove.push(...finalToErase);
|
138
|
+
this.toAdd.push(...toAdd);
|
139
|
+
this.eraseCommands.push(new Erase_1.default(finalToErase));
|
140
|
+
this.addCommands.push(...newAddCommands);
|
141
|
+
}
|
75
142
|
this.drawPreviewAt(currentPoint);
|
76
143
|
this.lastPoint = currentPoint;
|
77
144
|
}
|
@@ -79,6 +146,7 @@ class Eraser extends BaseTool_1.default {
|
|
79
146
|
if (event.allPointers.length === 1 || event.current.device === Pointer_1.PointerDevice.Eraser) {
|
80
147
|
this.lastPoint = event.current.canvasPos;
|
81
148
|
this.toRemove = [];
|
149
|
+
this.toAdd = [];
|
82
150
|
this.isFirstEraseEvt = true;
|
83
151
|
this.drawPreviewAt(event.current.canvasPos);
|
84
152
|
return true;
|
@@ -91,18 +159,32 @@ class Eraser extends BaseTool_1.default {
|
|
91
159
|
}
|
92
160
|
onPointerUp(event) {
|
93
161
|
this.eraseTo(event.current.canvasPos);
|
94
|
-
|
162
|
+
const commands = [];
|
163
|
+
if (this.addCommands.length > 0) {
|
164
|
+
this.addCommands.forEach(cmd => cmd.unapply(this.editor));
|
165
|
+
commands.push(...this.toAdd.map(a => EditorImage_1.default.addElement(a)));
|
166
|
+
this.addCommands = [];
|
167
|
+
}
|
168
|
+
if (this.eraseCommands.length > 0) {
|
95
169
|
// Undo commands for each individual component and unite into a single command.
|
96
|
-
this.
|
97
|
-
this.
|
170
|
+
this.eraseCommands.forEach(cmd => cmd.unapply(this.editor));
|
171
|
+
this.eraseCommands = [];
|
98
172
|
const command = new Erase_1.default(this.toRemove);
|
99
|
-
|
173
|
+
commands.push(command);
|
174
|
+
}
|
175
|
+
if (commands.length === 1) {
|
176
|
+
this.editor.dispatch(commands[0]); // dispatch: Makes undo-able.
|
177
|
+
}
|
178
|
+
else {
|
179
|
+
this.editor.dispatch((0, uniteCommands_1.default)(commands));
|
100
180
|
}
|
101
181
|
this.clearPreview();
|
102
182
|
}
|
103
183
|
onGestureCancel() {
|
104
|
-
this.
|
105
|
-
this.
|
184
|
+
this.addCommands.forEach(cmd => cmd.unapply(this.editor));
|
185
|
+
this.eraseCommands.forEach(cmd => cmd.unapply(this.editor));
|
186
|
+
this.eraseCommands = [];
|
187
|
+
this.addCommands = [];
|
106
188
|
this.clearPreview();
|
107
189
|
}
|
108
190
|
onKeyPress(event) {
|
@@ -121,9 +203,14 @@ class Eraser extends BaseTool_1.default {
|
|
121
203
|
}
|
122
204
|
return false;
|
123
205
|
}
|
206
|
+
/** Returns the side-length of the tip of this eraser. */
|
124
207
|
getThickness() {
|
125
208
|
return this.thickness;
|
126
209
|
}
|
210
|
+
/** Sets the side-length of this' tip. */
|
211
|
+
setThickness(thickness) {
|
212
|
+
this.thicknessValue.set(thickness);
|
213
|
+
}
|
127
214
|
/**
|
128
215
|
* Returns a {@link MutableReactiveValue} that can be used to watch
|
129
216
|
* this tool's thickness.
|
@@ -131,8 +218,8 @@ class Eraser extends BaseTool_1.default {
|
|
131
218
|
getThicknessValue() {
|
132
219
|
return this.thicknessValue;
|
133
220
|
}
|
134
|
-
|
135
|
-
this.
|
221
|
+
getModeValue() {
|
222
|
+
return this.modeValue;
|
136
223
|
}
|
137
224
|
}
|
138
225
|
exports.default = Eraser;
|
@@ -13,10 +13,10 @@ var StabilizerType;
|
|
13
13
|
})(StabilizerType || (StabilizerType = {}));
|
14
14
|
const defaultOptions = {
|
15
15
|
kind: StabilizerType.IntertialStabilizer,
|
16
|
-
mass: 0.4,
|
17
|
-
springConstant: 100.0,
|
16
|
+
mass: 0.4, // kg
|
17
|
+
springConstant: 100.0, // N/m
|
18
18
|
frictionCoefficient: 0.28,
|
19
|
-
maxPointDist: 10,
|
19
|
+
maxPointDist: 10, // screen units
|
20
20
|
inertiaFraction: 0.75,
|
21
21
|
minSimilarityToFinalize: 0.0,
|
22
22
|
velocityDecayFactor: 0.1,
|
@@ -28,8 +28,28 @@ class PasteHandler extends BaseTool_1.default {
|
|
28
28
|
// @internal
|
29
29
|
onPaste(event) {
|
30
30
|
const mime = event.mime.toLowerCase();
|
31
|
-
|
32
|
-
|
31
|
+
const svgData = (() => {
|
32
|
+
if (mime === 'image/svg+xml') {
|
33
|
+
return event.data;
|
34
|
+
}
|
35
|
+
if (mime !== 'text/html') {
|
36
|
+
return false;
|
37
|
+
}
|
38
|
+
// text/html is sometimes handlable SVG data. Use a hueristic
|
39
|
+
// to determine if this is the case:
|
40
|
+
// We use [^] and not . so that newlines are included.
|
41
|
+
const match = event.data.match(/^[^]{0,200}<svg.*/i); // [^]{0,200} <- Allow for metadata near start
|
42
|
+
if (!match) {
|
43
|
+
return false;
|
44
|
+
}
|
45
|
+
// Extract the SVG element from the pasted data
|
46
|
+
let svgEnd = event.data.toLowerCase().lastIndexOf('</svg>');
|
47
|
+
if (svgEnd === -1)
|
48
|
+
svgEnd = event.data.length;
|
49
|
+
return event.data.substring(event.data.search(/<svg/i), svgEnd);
|
50
|
+
})();
|
51
|
+
if (svgData) {
|
52
|
+
void this.doSVGPaste(svgData);
|
33
53
|
return true;
|
34
54
|
}
|
35
55
|
else if (mime === 'text/plain') {
|
@@ -43,16 +63,21 @@ class PasteHandler extends BaseTool_1.default {
|
|
43
63
|
return false;
|
44
64
|
}
|
45
65
|
async addComponentsFromPaste(components) {
|
46
|
-
await this.editor.addAndCenterComponents(components);
|
66
|
+
await this.editor.addAndCenterComponents(components, true, this.editor.localization.pasted(components.length));
|
47
67
|
}
|
48
68
|
async doSVGPaste(data) {
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
69
|
+
this.editor.showLoadingWarning(0);
|
70
|
+
try {
|
71
|
+
const loader = SVGLoader_1.default.fromString(data, true);
|
72
|
+
const components = [];
|
73
|
+
await loader.start((component) => {
|
74
|
+
components.push(component);
|
75
|
+
}, (_countProcessed, _totalToProcess) => null);
|
76
|
+
await this.addComponentsFromPaste(components);
|
77
|
+
}
|
78
|
+
finally {
|
79
|
+
this.editor.hideLoadingWarning();
|
80
|
+
}
|
56
81
|
}
|
57
82
|
async doTextPaste(text) {
|
58
83
|
const textTools = this.editor.toolController.getMatchingTools(TextTool_1.default);
|
package/dist/cjs/tools/Pen.js
CHANGED
@@ -102,8 +102,8 @@ class Pen extends BaseTool_1.default {
|
|
102
102
|
this.currentDeviceType = current.device;
|
103
103
|
if (this.shapeAutocompletionEnabled) {
|
104
104
|
const stationaryDetectionConfig = {
|
105
|
-
maxSpeed: 8.5,
|
106
|
-
maxRadius: 11,
|
105
|
+
maxSpeed: 8.5, // screenPx/s
|
106
|
+
maxRadius: 11, // screenPx
|
107
107
|
minTimeSeconds: 0.5, // s
|
108
108
|
};
|
109
109
|
this.stationaryDetector = new StationaryPenDetector_1.default(current, stationaryDetectionConfig, pointer => this.autocorrectShape(pointer));
|