js-draw 0.0.7 → 0.0.10
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/CHANGELOG.md +7 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Display.d.ts +1 -0
- package/dist/src/Display.js +3 -0
- package/dist/src/Editor.js +3 -1
- package/dist/src/UndoRedoHistory.d.ts +2 -0
- package/dist/src/UndoRedoHistory.js +6 -0
- package/dist/src/rendering/AbstractRenderer.d.ts +1 -0
- package/dist/src/rendering/AbstractRenderer.js +1 -0
- package/dist/src/rendering/CanvasRenderer.d.ts +4 -0
- package/dist/src/rendering/CanvasRenderer.js +22 -6
- package/dist/src/testing/createEditor.d.ts +3 -0
- package/dist/src/testing/createEditor.js +3 -0
- package/dist/src/toolbar/HTMLToolbar.js +10 -1
- package/dist/src/toolbar/localization.d.ts +1 -0
- package/dist/src/toolbar/localization.js +1 -0
- package/dist/src/tools/BaseTool.d.ts +4 -4
- package/dist/src/tools/BaseTool.js +4 -0
- package/dist/src/tools/PanZoom.js +3 -0
- package/dist/src/tools/SelectionTool.d.ts +3 -0
- package/dist/src/tools/SelectionTool.js +13 -0
- package/dist/src/tools/ToolController.d.ts +2 -1
- package/dist/src/tools/ToolController.js +3 -0
- package/dist/src/tools/UndoRedoShortcut.d.ts +10 -0
- package/dist/src/tools/UndoRedoShortcut.js +23 -0
- package/dist/src/tools/localization.d.ts +1 -0
- package/dist/src/tools/localization.js +1 -0
- package/dist/src/types.d.ts +1 -0
- package/package.json +2 -2
- package/src/Display.ts +4 -0
- package/src/Editor.ts +3 -1
- package/src/EditorImage.test.ts +1 -4
- package/src/UndoRedoHistory.ts +8 -0
- package/src/rendering/AbstractRenderer.ts +2 -0
- package/src/rendering/CanvasRenderer.ts +35 -6
- package/src/testing/createEditor.ts +4 -0
- package/src/toolbar/HTMLToolbar.ts +12 -1
- package/src/toolbar/localization.ts +2 -0
- package/src/tools/BaseTool.ts +5 -4
- package/src/tools/PanZoom.ts +3 -0
- package/src/tools/SelectionTool.ts +16 -0
- package/src/tools/ToolController.ts +3 -0
- package/src/tools/UndoRedoShortcut.test.ts +53 -0
- package/src/tools/UndoRedoShortcut.ts +28 -0
- package/src/tools/localization.ts +2 -0
- package/src/types.ts +1 -0
package/dist/src/Display.d.ts
CHANGED
@@ -16,6 +16,7 @@ export default class Display {
|
|
16
16
|
get height(): number;
|
17
17
|
private initializeCanvasRendering;
|
18
18
|
startRerender(): AbstractRenderer;
|
19
|
+
setDraftMode(draftMode: boolean): void;
|
19
20
|
getDryInkRenderer(): AbstractRenderer;
|
20
21
|
getWetInkRenderer(): AbstractRenderer;
|
21
22
|
flatten(): void;
|
package/dist/src/Display.js
CHANGED
package/dist/src/Editor.js
CHANGED
@@ -175,6 +175,7 @@ export class Editor {
|
|
175
175
|
if (this.toolController.dispatchInputEvent({
|
176
176
|
kind: InputEvtType.KeyPressEvent,
|
177
177
|
key: evt.key,
|
178
|
+
ctrlKey: evt.ctrlKey,
|
178
179
|
})) {
|
179
180
|
evt.preventDefault();
|
180
181
|
}
|
@@ -233,6 +234,7 @@ export class Editor {
|
|
233
234
|
// has been applied.
|
234
235
|
asyncApplyOrUnapplyCommands(commands, apply, updateChunkSize) {
|
235
236
|
return __awaiter(this, void 0, void 0, function* () {
|
237
|
+
this.display.setDraftMode(true);
|
236
238
|
for (let i = 0; i < commands.length; i += updateChunkSize) {
|
237
239
|
this.showLoadingWarning(i / commands.length);
|
238
240
|
for (let j = i; j < commands.length && j < i + updateChunkSize; j++) {
|
@@ -252,6 +254,7 @@ export class Editor {
|
|
252
254
|
});
|
253
255
|
}
|
254
256
|
}
|
257
|
+
this.display.setDraftMode(false);
|
255
258
|
this.hideLoadingWarning();
|
256
259
|
});
|
257
260
|
}
|
@@ -331,7 +334,6 @@ export class Editor {
|
|
331
334
|
result.setAttribute('viewBox', `${rect.x} ${rect.y} ${rect.w} ${rect.h}`);
|
332
335
|
result.setAttribute('width', `${rect.w}`);
|
333
336
|
result.setAttribute('height', `${rect.h}`);
|
334
|
-
console.log('res', result);
|
335
337
|
// Ensure the image can be identified as an SVG if downloaded.
|
336
338
|
// See https://jwatt.org/svg/authoring/
|
337
339
|
result.setAttribute('version', '1.1');
|
@@ -26,6 +26,7 @@ export default abstract class AbstractRenderer {
|
|
26
26
|
protected abstract moveTo(point: Point2): void;
|
27
27
|
protected abstract traceCubicBezierCurve(p1: Point2, p2: Point2, p3: Point2): void;
|
28
28
|
protected abstract traceQuadraticBezierCurve(controlPoint: Point2, endPoint: Point2): void;
|
29
|
+
setDraftMode(_draftMode: boolean): void;
|
29
30
|
private objectLevel;
|
30
31
|
private currentPaths;
|
31
32
|
private flushPath;
|
@@ -7,7 +7,11 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
7
7
|
private ctx;
|
8
8
|
private ignoreObjectsAboveLevel;
|
9
9
|
private ignoringObject;
|
10
|
+
private minSquareCurveApproxDist;
|
11
|
+
private minRenderSizeAnyDimen;
|
12
|
+
private minRenderSizeBothDimens;
|
10
13
|
constructor(ctx: CanvasRenderingContext2D, viewport: Viewport);
|
14
|
+
setDraftMode(draftMode: boolean): void;
|
11
15
|
displaySize(): Vec2;
|
12
16
|
clear(): void;
|
13
17
|
protected beginPath(startPoint: Point2): void;
|
@@ -1,13 +1,26 @@
|
|
1
1
|
import Color4 from '../Color4';
|
2
2
|
import { Vec2 } from '../geometry/Vec2';
|
3
3
|
import AbstractRenderer from './AbstractRenderer';
|
4
|
-
const minSquareCurveApproxDist = 25;
|
5
4
|
export default class CanvasRenderer extends AbstractRenderer {
|
6
5
|
constructor(ctx, viewport) {
|
7
6
|
super(viewport);
|
8
7
|
this.ctx = ctx;
|
9
8
|
this.ignoreObjectsAboveLevel = null;
|
10
9
|
this.ignoringObject = false;
|
10
|
+
this.setDraftMode(false);
|
11
|
+
}
|
12
|
+
// Set parameters for lower/higher quality rendering
|
13
|
+
setDraftMode(draftMode) {
|
14
|
+
if (draftMode) {
|
15
|
+
this.minSquareCurveApproxDist = 64;
|
16
|
+
this.minRenderSizeBothDimens = 8;
|
17
|
+
this.minRenderSizeAnyDimen = 2;
|
18
|
+
}
|
19
|
+
else {
|
20
|
+
this.minSquareCurveApproxDist = 1;
|
21
|
+
this.minRenderSizeBothDimens = 1;
|
22
|
+
this.minRenderSizeAnyDimen = 0;
|
23
|
+
}
|
11
24
|
}
|
12
25
|
displaySize() {
|
13
26
|
return Vec2.of(this.ctx.canvas.clientWidth, this.ctx.canvas.clientHeight);
|
@@ -45,8 +58,8 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
45
58
|
// Approximate the curve if small enough.
|
46
59
|
const delta1 = p2.minus(p1);
|
47
60
|
const delta2 = p3.minus(p2);
|
48
|
-
if (delta1.magnitudeSquared() < minSquareCurveApproxDist
|
49
|
-
&& delta2.magnitudeSquared() < minSquareCurveApproxDist) {
|
61
|
+
if (delta1.magnitudeSquared() < this.minSquareCurveApproxDist
|
62
|
+
&& delta2.magnitudeSquared() < this.minSquareCurveApproxDist) {
|
50
63
|
this.ctx.lineTo(p3.x, p3.y);
|
51
64
|
}
|
52
65
|
else {
|
@@ -58,7 +71,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
58
71
|
endPoint = this.viewport.canvasToScreen(endPoint);
|
59
72
|
// Approximate the curve with a line if small enough
|
60
73
|
const delta = controlPoint.minus(endPoint);
|
61
|
-
if (delta.magnitudeSquared() < minSquareCurveApproxDist) {
|
74
|
+
if (delta.magnitudeSquared() < this.minSquareCurveApproxDist) {
|
62
75
|
this.ctx.lineTo(endPoint.x, endPoint.y);
|
63
76
|
}
|
64
77
|
else {
|
@@ -74,8 +87,11 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
74
87
|
startObject(boundingBox) {
|
75
88
|
// Should we ignore all objects within this object's bbox?
|
76
89
|
const diagonal = this.viewport.canvasToScreenTransform.transformVec3(boundingBox.size);
|
77
|
-
const
|
78
|
-
|
90
|
+
const bothDimenMinSize = this.minRenderSizeBothDimens;
|
91
|
+
const bothTooSmall = Math.abs(diagonal.x) < bothDimenMinSize && Math.abs(diagonal.y) < bothDimenMinSize;
|
92
|
+
const anyDimenMinSize = this.minRenderSizeAnyDimen;
|
93
|
+
const anyTooSmall = Math.abs(diagonal.x) < anyDimenMinSize || Math.abs(diagonal.y) < anyDimenMinSize;
|
94
|
+
if (bothTooSmall || anyTooSmall) {
|
79
95
|
this.ignoreObjectsAboveLevel = this.getNestingLevel();
|
80
96
|
this.ignoringObject = true;
|
81
97
|
}
|
@@ -192,12 +192,20 @@ class SelectionWidget extends ToolbarWidget {
|
|
192
192
|
fillDropdown(dropdown) {
|
193
193
|
const container = document.createElement('div');
|
194
194
|
const resizeButton = document.createElement('button');
|
195
|
+
const deleteButton = document.createElement('button');
|
195
196
|
resizeButton.innerText = this.localizationTable.resizeImageToSelection;
|
196
197
|
resizeButton.disabled = true;
|
198
|
+
deleteButton.innerText = this.localizationTable.deleteSelection;
|
199
|
+
deleteButton.disabled = true;
|
197
200
|
resizeButton.onclick = () => {
|
198
201
|
const selection = this.tool.getSelection();
|
199
202
|
this.editor.dispatch(this.editor.setImportExportRect(selection.region));
|
200
203
|
};
|
204
|
+
deleteButton.onclick = () => {
|
205
|
+
const selection = this.tool.getSelection();
|
206
|
+
this.editor.dispatch(selection.deleteSelectedObjects());
|
207
|
+
this.tool.clearSelection();
|
208
|
+
};
|
201
209
|
// Enable/disable actions based on whether items are selected
|
202
210
|
this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
|
203
211
|
if (toolEvt.kind !== EditorEventType.ToolUpdated) {
|
@@ -207,9 +215,10 @@ class SelectionWidget extends ToolbarWidget {
|
|
207
215
|
const selection = this.tool.getSelection();
|
208
216
|
const hasSelection = selection && selection.region.area > 0;
|
209
217
|
resizeButton.disabled = !hasSelection;
|
218
|
+
deleteButton.disabled = resizeButton.disabled;
|
210
219
|
}
|
211
220
|
});
|
212
|
-
container.replaceChildren(resizeButton);
|
221
|
+
container.replaceChildren(resizeButton, deleteButton);
|
213
222
|
dropdown.appendChild(container);
|
214
223
|
return true;
|
215
224
|
}
|
@@ -6,10 +6,10 @@ export default abstract class BaseTool implements PointerEvtListener {
|
|
6
6
|
readonly description: string;
|
7
7
|
private enabled;
|
8
8
|
private group;
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
onPointerDown(_event: PointerEvt): boolean;
|
10
|
+
onPointerMove(_event: PointerEvt): void;
|
11
|
+
onPointerUp(_event: PointerEvt): void;
|
12
|
+
onGestureCancel(): void;
|
13
13
|
abstract readonly kind: ToolType;
|
14
14
|
protected constructor(notifier: EditorNotifier, description: string);
|
15
15
|
onWheel(_event: WheelEvt): boolean;
|
@@ -56,6 +56,7 @@ export default class PanZoom extends BaseTool {
|
|
56
56
|
}
|
57
57
|
if (handlingGesture) {
|
58
58
|
(_a = this.transform) !== null && _a !== void 0 ? _a : (this.transform = new Viewport.ViewportTransform(Mat33.identity));
|
59
|
+
this.editor.display.setDraftMode(true);
|
59
60
|
}
|
60
61
|
return handlingGesture;
|
61
62
|
}
|
@@ -100,11 +101,13 @@ export default class PanZoom extends BaseTool {
|
|
100
101
|
this.transform.unapply(this.editor);
|
101
102
|
this.editor.dispatch(this.transform, false);
|
102
103
|
}
|
104
|
+
this.editor.display.setDraftMode(false);
|
103
105
|
this.transform = null;
|
104
106
|
}
|
105
107
|
onGestureCancel() {
|
106
108
|
var _a;
|
107
109
|
(_a = this.transform) === null || _a === void 0 ? void 0 : _a.unapply(this.editor);
|
110
|
+
this.editor.display.setDraftMode(false);
|
108
111
|
this.transform = null;
|
109
112
|
}
|
110
113
|
// Applies [transformUpdate] to the editor. This stacks on top of the
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import Command from '../commands/Command';
|
1
2
|
import Editor from '../Editor';
|
2
3
|
import Rect2 from '../geometry/Rect2';
|
3
4
|
import { Point2, Vec2 } from '../geometry/Vec2';
|
@@ -30,6 +31,7 @@ declare class Selection {
|
|
30
31
|
private recomputeBoxRotation;
|
31
32
|
getSelectedItemCount(): number;
|
32
33
|
updateUI(): void;
|
34
|
+
deleteSelectedObjects(): Command;
|
33
35
|
}
|
34
36
|
export default class SelectionTool extends BaseTool {
|
35
37
|
private editor;
|
@@ -45,5 +47,6 @@ export default class SelectionTool extends BaseTool {
|
|
45
47
|
onGestureCancel(): void;
|
46
48
|
setEnabled(enabled: boolean): void;
|
47
49
|
getSelection(): Selection | null;
|
50
|
+
clearSelection(): void;
|
48
51
|
}
|
49
52
|
export {};
|
@@ -7,6 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
8
8
|
});
|
9
9
|
};
|
10
|
+
import Erase from '../commands/Erase';
|
10
11
|
import Mat33 from '../geometry/Mat33';
|
11
12
|
// import Mat33 from "../geometry/Mat33";
|
12
13
|
import Rect2 from '../geometry/Rect2';
|
@@ -344,6 +345,9 @@ class Selection {
|
|
344
345
|
this.backgroundBox.style.transform = `rotate(${rotationDeg}deg)`;
|
345
346
|
this.rotateCircle.style.transform = `rotate(${-rotationDeg}deg)`;
|
346
347
|
}
|
348
|
+
deleteSelectedObjects() {
|
349
|
+
return new Erase(this.selectedElems);
|
350
|
+
}
|
347
351
|
}
|
348
352
|
export default class SelectionTool extends BaseTool {
|
349
353
|
constructor(editor, description) {
|
@@ -434,4 +438,13 @@ export default class SelectionTool extends BaseTool {
|
|
434
438
|
getSelection() {
|
435
439
|
return this.selectionBox;
|
436
440
|
}
|
441
|
+
clearSelection() {
|
442
|
+
this.handleOverlay.replaceChildren();
|
443
|
+
this.prevSelectionBox = this.selectionBox;
|
444
|
+
this.selectionBox = null;
|
445
|
+
this.editor.notifier.dispatch(EditorEventType.ToolUpdated, {
|
446
|
+
kind: EditorEventType.ToolUpdated,
|
447
|
+
tool: this,
|
448
|
+
});
|
449
|
+
}
|
437
450
|
}
|
@@ -5,6 +5,7 @@ import ToolEnabledGroup from './ToolEnabledGroup';
|
|
5
5
|
import Eraser from './Eraser';
|
6
6
|
import SelectionTool from './SelectionTool';
|
7
7
|
import Color4 from '../Color4';
|
8
|
+
import UndoRedoShortcut from './UndoRedoShortcut';
|
8
9
|
export var ToolType;
|
9
10
|
(function (ToolType) {
|
10
11
|
ToolType[ToolType["TouchPanZoom"] = 0] = "TouchPanZoom";
|
@@ -12,6 +13,7 @@ export var ToolType;
|
|
12
13
|
ToolType[ToolType["Selection"] = 2] = "Selection";
|
13
14
|
ToolType[ToolType["Eraser"] = 3] = "Eraser";
|
14
15
|
ToolType[ToolType["PanZoom"] = 4] = "PanZoom";
|
16
|
+
ToolType[ToolType["UndoRedoShortcut"] = 5] = "UndoRedoShortcut";
|
15
17
|
})(ToolType || (ToolType = {}));
|
16
18
|
export default class ToolController {
|
17
19
|
constructor(editor, localization) {
|
@@ -31,6 +33,7 @@ export default class ToolController {
|
|
31
33
|
touchPanZoom,
|
32
34
|
...primaryTools,
|
33
35
|
new PanZoom(editor, PanZoomMode.TwoFingerGestures | PanZoomMode.AnyDevice, localization.twoFingerPanZoomTool),
|
36
|
+
new UndoRedoShortcut(editor),
|
34
37
|
];
|
35
38
|
primaryTools.forEach(tool => tool.setToolGroup(primaryToolEnabledGroup));
|
36
39
|
touchPanZoom.setEnabled(false);
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import Editor from '../Editor';
|
2
|
+
import { KeyPressEvent } from '../types';
|
3
|
+
import BaseTool from './BaseTool';
|
4
|
+
import { ToolType } from './ToolController';
|
5
|
+
export default class UndoRedoShortcut extends BaseTool {
|
6
|
+
private editor;
|
7
|
+
kind: ToolType.UndoRedoShortcut;
|
8
|
+
constructor(editor: Editor);
|
9
|
+
onKeyPress({ key, ctrlKey }: KeyPressEvent): boolean;
|
10
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import BaseTool from './BaseTool';
|
2
|
+
import { ToolType } from './ToolController';
|
3
|
+
export default class UndoRedoShortcut extends BaseTool {
|
4
|
+
constructor(editor) {
|
5
|
+
super(editor.notifier, editor.localization.undoRedoTool);
|
6
|
+
this.editor = editor;
|
7
|
+
this.kind = ToolType.UndoRedoShortcut;
|
8
|
+
}
|
9
|
+
// Activate undo/redo
|
10
|
+
onKeyPress({ key, ctrlKey }) {
|
11
|
+
if (ctrlKey) {
|
12
|
+
if (key === 'z') {
|
13
|
+
this.editor.history.undo();
|
14
|
+
return true;
|
15
|
+
}
|
16
|
+
else if (key === 'Z') {
|
17
|
+
this.editor.history.redo();
|
18
|
+
return true;
|
19
|
+
}
|
20
|
+
}
|
21
|
+
return false;
|
22
|
+
}
|
23
|
+
}
|
@@ -4,6 +4,7 @@ export const defaultToolLocalization = {
|
|
4
4
|
eraserTool: 'Eraser',
|
5
5
|
touchPanTool: 'Touch Panning',
|
6
6
|
twoFingerPanZoomTool: 'Panning and Zooming',
|
7
|
+
undoRedoTool: 'Undo/Redo',
|
7
8
|
toolEnabledAnnouncement: (toolName) => `${toolName} enabled`,
|
8
9
|
toolDisabledAnnouncement: (toolName) => `${toolName} disabled`,
|
9
10
|
};
|
package/dist/src/types.d.ts
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "js-draw",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.10",
|
4
4
|
"description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
|
5
5
|
"main": "dist/src/Editor.js",
|
6
6
|
"types": "dist/src/Editor.d.ts",
|
@@ -48,7 +48,7 @@
|
|
48
48
|
"linter-precommit": "eslint --fix --ext .js --ext .ts",
|
49
49
|
"lint-staged": "lint-staged",
|
50
50
|
"_postinstall": "husky install",
|
51
|
-
"prepack": "pinst --disable",
|
51
|
+
"prepack": "yarn build && pinst --disable",
|
52
52
|
"postpack": "pinst --enable"
|
53
53
|
},
|
54
54
|
"dependencies": {
|
package/src/Display.ts
CHANGED
@@ -105,6 +105,10 @@ export default class Display {
|
|
105
105
|
return this.dryInkRenderer;
|
106
106
|
}
|
107
107
|
|
108
|
+
public setDraftMode(draftMode: boolean) {
|
109
|
+
this.dryInkRenderer.setDraftMode(draftMode);
|
110
|
+
}
|
111
|
+
|
108
112
|
public getDryInkRenderer(): AbstractRenderer {
|
109
113
|
return this.dryInkRenderer;
|
110
114
|
}
|
package/src/Editor.ts
CHANGED
@@ -238,6 +238,7 @@ export class Editor {
|
|
238
238
|
if (this.toolController.dispatchInputEvent({
|
239
239
|
kind: InputEvtType.KeyPressEvent,
|
240
240
|
key: evt.key,
|
241
|
+
ctrlKey: evt.ctrlKey,
|
241
242
|
})) {
|
242
243
|
evt.preventDefault();
|
243
244
|
}
|
@@ -310,6 +311,7 @@ export class Editor {
|
|
310
311
|
private async asyncApplyOrUnapplyCommands(
|
311
312
|
commands: Command[], apply: boolean, updateChunkSize: number
|
312
313
|
) {
|
314
|
+
this.display.setDraftMode(true);
|
313
315
|
for (let i = 0; i < commands.length; i += updateChunkSize) {
|
314
316
|
this.showLoadingWarning(i / commands.length);
|
315
317
|
|
@@ -331,6 +333,7 @@ export class Editor {
|
|
331
333
|
});
|
332
334
|
}
|
333
335
|
}
|
336
|
+
this.display.setDraftMode(false);
|
334
337
|
this.hideLoadingWarning();
|
335
338
|
}
|
336
339
|
|
@@ -447,7 +450,6 @@ export class Editor {
|
|
447
450
|
result.setAttribute('viewBox', `${rect.x} ${rect.y} ${rect.w} ${rect.h}`);
|
448
451
|
result.setAttribute('width', `${rect.w}`);
|
449
452
|
result.setAttribute('height', `${rect.h}`);
|
450
|
-
console.log('res', result);
|
451
453
|
|
452
454
|
// Ensure the image can be identified as an SVG if downloaded.
|
453
455
|
// See https://jwatt.org/svg/authoring/
|
package/src/EditorImage.test.ts
CHANGED
@@ -5,12 +5,9 @@ import Stroke from './components/Stroke';
|
|
5
5
|
import { Vec2 } from './geometry/Vec2';
|
6
6
|
import Path, { PathCommandType } from './geometry/Path';
|
7
7
|
import Color4 from './Color4';
|
8
|
-
import Editor from './Editor';
|
9
|
-
import { RenderingMode } from './Display';
|
10
8
|
import DummyRenderer from './rendering/DummyRenderer';
|
11
9
|
import { RenderingStyle } from './rendering/AbstractRenderer';
|
12
|
-
|
13
|
-
const createEditor = () => new Editor(document.body, { renderingMode: RenderingMode.DummyRenderer });
|
10
|
+
import createEditor from './testing/createEditor';
|
14
11
|
|
15
12
|
describe('EditorImage', () => {
|
16
13
|
const testStroke = new Stroke([
|
package/src/UndoRedoHistory.ts
CHANGED
@@ -56,6 +56,14 @@ class UndoRedoHistory {
|
|
56
56
|
}
|
57
57
|
this.fireUpdateEvent();
|
58
58
|
}
|
59
|
+
|
60
|
+
public get undoStackSize(): number {
|
61
|
+
return this.undoStack.length;
|
62
|
+
}
|
63
|
+
|
64
|
+
public get redoStackSize(): number {
|
65
|
+
return this.redoStack.length;
|
66
|
+
}
|
59
67
|
}
|
60
68
|
|
61
69
|
export default UndoRedoHistory;
|
@@ -43,6 +43,8 @@ export default abstract class AbstractRenderer {
|
|
43
43
|
controlPoint: Point2, endPoint: Point2,
|
44
44
|
): void;
|
45
45
|
|
46
|
+
public setDraftMode(_draftMode: boolean) { }
|
47
|
+
|
46
48
|
private objectLevel: number = 0;
|
47
49
|
private currentPaths: RenderablePathSpec[]|null = null;
|
48
50
|
private flushPath() {
|
@@ -5,13 +5,37 @@ import Vec3 from '../geometry/Vec3';
|
|
5
5
|
import Viewport from '../Viewport';
|
6
6
|
import AbstractRenderer, { RenderablePathSpec, RenderingStyle } from './AbstractRenderer';
|
7
7
|
|
8
|
-
const minSquareCurveApproxDist = 25;
|
9
8
|
export default class CanvasRenderer extends AbstractRenderer {
|
10
9
|
private ignoreObjectsAboveLevel: number|null = null;
|
11
10
|
private ignoringObject: boolean = false;
|
12
11
|
|
12
|
+
// Minimum square distance of a control point from the line between the end points
|
13
|
+
// for the curve not to be drawn as a line.
|
14
|
+
// For example, if [minSquareCurveApproxDist] = 25 = 5², then a control point on a quadratic
|
15
|
+
// bezier curve needs to be at least 5 units away from the line between the curve's end points
|
16
|
+
// for the curve to be drawn as a Bezier curve (and not a line).
|
17
|
+
private minSquareCurveApproxDist: number;
|
18
|
+
|
19
|
+
// Minimum size of an object (in pixels) for it to be rendered.
|
20
|
+
private minRenderSizeAnyDimen: number;
|
21
|
+
private minRenderSizeBothDimens: number;
|
22
|
+
|
13
23
|
public constructor(private ctx: CanvasRenderingContext2D, viewport: Viewport) {
|
14
24
|
super(viewport);
|
25
|
+
this.setDraftMode(false);
|
26
|
+
}
|
27
|
+
|
28
|
+
// Set parameters for lower/higher quality rendering
|
29
|
+
public setDraftMode(draftMode: boolean) {
|
30
|
+
if (draftMode) {
|
31
|
+
this.minSquareCurveApproxDist = 64;
|
32
|
+
this.minRenderSizeBothDimens = 8;
|
33
|
+
this.minRenderSizeAnyDimen = 2;
|
34
|
+
} else {
|
35
|
+
this.minSquareCurveApproxDist = 1;
|
36
|
+
this.minRenderSizeBothDimens = 1;
|
37
|
+
this.minRenderSizeAnyDimen = 0;
|
38
|
+
}
|
15
39
|
}
|
16
40
|
|
17
41
|
public displaySize(): Vec2 {
|
@@ -63,8 +87,8 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
63
87
|
// Approximate the curve if small enough.
|
64
88
|
const delta1 = p2.minus(p1);
|
65
89
|
const delta2 = p3.minus(p2);
|
66
|
-
if (delta1.magnitudeSquared() < minSquareCurveApproxDist
|
67
|
-
&& delta2.magnitudeSquared() < minSquareCurveApproxDist) {
|
90
|
+
if (delta1.magnitudeSquared() < this.minSquareCurveApproxDist
|
91
|
+
&& delta2.magnitudeSquared() < this.minSquareCurveApproxDist) {
|
68
92
|
this.ctx.lineTo(p3.x, p3.y);
|
69
93
|
} else {
|
70
94
|
this.ctx.bezierCurveTo(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
|
@@ -77,7 +101,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
77
101
|
|
78
102
|
// Approximate the curve with a line if small enough
|
79
103
|
const delta = controlPoint.minus(endPoint);
|
80
|
-
if (delta.magnitudeSquared() < minSquareCurveApproxDist) {
|
104
|
+
if (delta.magnitudeSquared() < this.minSquareCurveApproxDist) {
|
81
105
|
this.ctx.lineTo(endPoint.x, endPoint.y);
|
82
106
|
} else {
|
83
107
|
this.ctx.quadraticCurveTo(
|
@@ -97,8 +121,13 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
97
121
|
public startObject(boundingBox: Rect2) {
|
98
122
|
// Should we ignore all objects within this object's bbox?
|
99
123
|
const diagonal = this.viewport.canvasToScreenTransform.transformVec3(boundingBox.size);
|
100
|
-
|
101
|
-
|
124
|
+
|
125
|
+
const bothDimenMinSize = this.minRenderSizeBothDimens;
|
126
|
+
const bothTooSmall = Math.abs(diagonal.x) < bothDimenMinSize && Math.abs(diagonal.y) < bothDimenMinSize;
|
127
|
+
const anyDimenMinSize = this.minRenderSizeAnyDimen;
|
128
|
+
const anyTooSmall = Math.abs(diagonal.x) < anyDimenMinSize || Math.abs(diagonal.y) < anyDimenMinSize;
|
129
|
+
|
130
|
+
if (bothTooSmall || anyTooSmall) {
|
102
131
|
this.ignoreObjectsAboveLevel = this.getNestingLevel();
|
103
132
|
this.ignoringObject = true;
|
104
133
|
}
|
@@ -245,15 +245,24 @@ class SelectionWidget extends ToolbarWidget {
|
|
245
245
|
protected fillDropdown(dropdown: HTMLElement): boolean {
|
246
246
|
const container = document.createElement('div');
|
247
247
|
const resizeButton = document.createElement('button');
|
248
|
+
const deleteButton = document.createElement('button');
|
248
249
|
|
249
250
|
resizeButton.innerText = this.localizationTable.resizeImageToSelection;
|
250
251
|
resizeButton.disabled = true;
|
252
|
+
deleteButton.innerText = this.localizationTable.deleteSelection;
|
253
|
+
deleteButton.disabled = true;
|
251
254
|
|
252
255
|
resizeButton.onclick = () => {
|
253
256
|
const selection = this.tool.getSelection();
|
254
257
|
this.editor.dispatch(this.editor.setImportExportRect(selection!.region));
|
255
258
|
};
|
256
259
|
|
260
|
+
deleteButton.onclick = () => {
|
261
|
+
const selection = this.tool.getSelection();
|
262
|
+
this.editor.dispatch(selection!.deleteSelectedObjects());
|
263
|
+
this.tool.clearSelection();
|
264
|
+
};
|
265
|
+
|
257
266
|
// Enable/disable actions based on whether items are selected
|
258
267
|
this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
|
259
268
|
if (toolEvt.kind !== EditorEventType.ToolUpdated) {
|
@@ -263,11 +272,13 @@ class SelectionWidget extends ToolbarWidget {
|
|
263
272
|
if (toolEvt.tool === this.tool) {
|
264
273
|
const selection = this.tool.getSelection();
|
265
274
|
const hasSelection = selection && selection.region.area > 0;
|
275
|
+
|
266
276
|
resizeButton.disabled = !hasSelection;
|
277
|
+
deleteButton.disabled = resizeButton.disabled;
|
267
278
|
}
|
268
279
|
});
|
269
280
|
|
270
|
-
container.replaceChildren(resizeButton);
|
281
|
+
container.replaceChildren(resizeButton, deleteButton);
|
271
282
|
dropdown.appendChild(container);
|
272
283
|
return true;
|
273
284
|
}
|