js-draw 0.0.5 → 0.0.8
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 +11 -0
- package/README.md +13 -0
- package/dist/build_tools/bundle.js +2 -2
- package/dist/bundle.js +1 -1
- package/dist/src/Editor.js +9 -1
- package/dist/src/EditorImage.d.ts +2 -2
- package/dist/src/SVGLoader.d.ts +2 -0
- package/dist/src/SVGLoader.js +15 -1
- package/dist/src/UndoRedoHistory.d.ts +2 -0
- package/dist/src/Viewport.d.ts +1 -1
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +15 -0
- package/dist/src/components/SVGGlobalAttributesObject.js +29 -0
- package/dist/src/components/builders/FreehandLineBuilder.js +15 -3
- package/dist/src/geometry/Path.js +11 -0
- package/dist/src/htmlUtil.d.ts +1 -0
- package/dist/src/rendering/SVGRenderer.d.ts +2 -0
- package/dist/src/rendering/SVGRenderer.js +25 -0
- package/dist/src/tools/BaseTool.d.ts +4 -4
- package/dist/src/tools/SelectionTool.js +0 -1
- package/dist/src/tools/ToolController.d.ts +2 -1
- package/dist/src/tools/UndoRedoShortcut.d.ts +10 -0
- package/dist/src/tools/localization.d.ts +1 -0
- package/dist/src/types.d.ts +1 -0
- package/package.json +2 -2
- package/src/Editor.ts +12 -1
- package/src/EditorImage.test.ts +1 -4
- package/src/SVGLoader.ts +18 -1
- package/src/UndoRedoHistory.ts +8 -0
- package/src/components/SVGGlobalAttributesObject.ts +39 -0
- package/src/components/builders/FreehandLineBuilder.ts +23 -4
- package/src/geometry/Path.fromString.test.ts +11 -24
- package/src/geometry/Path.ts +13 -0
- package/src/rendering/SVGRenderer.ts +27 -0
- package/src/testing/createEditor.ts +4 -0
- package/src/tools/BaseTool.ts +5 -4
- 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/src/geometry/Path.ts
CHANGED
@@ -367,16 +367,28 @@ export default class Path {
|
|
367
367
|
|
368
368
|
let lastPos: Point2 = Vec2.zero;
|
369
369
|
let firstPos: Point2|null = null;
|
370
|
+
let isFirstCommand: boolean = true;
|
370
371
|
const commands: PathCommand[] = [];
|
371
372
|
|
372
373
|
|
373
374
|
const moveTo = (point: Point2) => {
|
375
|
+
// The first moveTo/lineTo is already handled by the [startPoint] parameter of the Path constructor.
|
376
|
+
if (isFirstCommand) {
|
377
|
+
isFirstCommand = false;
|
378
|
+
return;
|
379
|
+
}
|
380
|
+
|
374
381
|
commands.push({
|
375
382
|
kind: PathCommandType.MoveTo,
|
376
383
|
point,
|
377
384
|
});
|
378
385
|
};
|
379
386
|
const lineTo = (point: Point2) => {
|
387
|
+
if (isFirstCommand) {
|
388
|
+
isFirstCommand = false;
|
389
|
+
return;
|
390
|
+
}
|
391
|
+
|
380
392
|
commands.push({
|
381
393
|
kind: PathCommandType.LineTo,
|
382
394
|
point,
|
@@ -492,6 +504,7 @@ export default class Path {
|
|
492
504
|
if (args.length > 0) {
|
493
505
|
firstPos ??= args[0];
|
494
506
|
}
|
507
|
+
isFirstCommand = false;
|
495
508
|
}
|
496
509
|
|
497
510
|
return new Path(firstPos ?? Vec2.zero, commands);
|
@@ -15,12 +15,27 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
15
15
|
private lastPathStart: Point2|null;
|
16
16
|
|
17
17
|
private mainGroup: SVGGElement;
|
18
|
+
private overwrittenAttrs: Record<string, string|null> = {};
|
18
19
|
|
19
20
|
public constructor(private elem: SVGSVGElement, viewport: Viewport) {
|
20
21
|
super(viewport);
|
21
22
|
this.clear();
|
22
23
|
}
|
23
24
|
|
25
|
+
// Sets an attribute on the root SVG element.
|
26
|
+
public setRootSVGAttribute(name: string, value: string|null) {
|
27
|
+
// Make the original value of the attribute restorable on clear
|
28
|
+
if (!(name in this.overwrittenAttrs)) {
|
29
|
+
this.overwrittenAttrs[name] = this.elem.getAttribute(name);
|
30
|
+
}
|
31
|
+
|
32
|
+
if (value !== null) {
|
33
|
+
this.elem.setAttribute(name, value);
|
34
|
+
} else {
|
35
|
+
this.elem.removeAttribute(name);
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
24
39
|
public displaySize(): Vec2 {
|
25
40
|
return Vec2.of(this.elem.clientWidth, this.elem.clientHeight);
|
26
41
|
}
|
@@ -28,6 +43,18 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
28
43
|
public clear() {
|
29
44
|
this.mainGroup = document.createElementNS(svgNameSpace, 'g');
|
30
45
|
|
46
|
+
// Restore all alltributes
|
47
|
+
for (const attrName in this.overwrittenAttrs) {
|
48
|
+
const value = this.overwrittenAttrs[attrName];
|
49
|
+
|
50
|
+
if (value) {
|
51
|
+
this.elem.setAttribute(attrName, value);
|
52
|
+
} else {
|
53
|
+
this.elem.removeAttribute(attrName);
|
54
|
+
}
|
55
|
+
}
|
56
|
+
this.overwrittenAttrs = {};
|
57
|
+
|
31
58
|
// Remove all children
|
32
59
|
this.elem.replaceChildren(this.mainGroup);
|
33
60
|
}
|
package/src/tools/BaseTool.ts
CHANGED
@@ -6,10 +6,11 @@ export default abstract class BaseTool implements PointerEvtListener {
|
|
6
6
|
private enabled: boolean = true;
|
7
7
|
private group: ToolEnabledGroup|null = null;
|
8
8
|
|
9
|
-
public
|
10
|
-
public
|
11
|
-
public
|
12
|
-
public
|
9
|
+
public onPointerDown(_event: PointerEvt): boolean { return false; }
|
10
|
+
public onPointerMove(_event: PointerEvt) { }
|
11
|
+
public onPointerUp(_event: PointerEvt) { }
|
12
|
+
public onGestureCancel() { }
|
13
|
+
|
13
14
|
public abstract readonly kind: ToolType;
|
14
15
|
|
15
16
|
protected constructor(private notifier: EditorNotifier, public readonly description: string) {
|
@@ -8,6 +8,7 @@ import Eraser from './Eraser';
|
|
8
8
|
import SelectionTool from './SelectionTool';
|
9
9
|
import Color4 from '../Color4';
|
10
10
|
import { ToolLocalization } from './localization';
|
11
|
+
import UndoRedoShortcut from './UndoRedoShortcut';
|
11
12
|
|
12
13
|
export enum ToolType {
|
13
14
|
TouchPanZoom,
|
@@ -15,6 +16,7 @@ export enum ToolType {
|
|
15
16
|
Selection,
|
16
17
|
Eraser,
|
17
18
|
PanZoom,
|
19
|
+
UndoRedoShortcut,
|
18
20
|
}
|
19
21
|
|
20
22
|
export default class ToolController {
|
@@ -40,6 +42,7 @@ export default class ToolController {
|
|
40
42
|
touchPanZoom,
|
41
43
|
...primaryTools,
|
42
44
|
new PanZoom(editor, PanZoomMode.TwoFingerGestures | PanZoomMode.AnyDevice, localization.twoFingerPanZoomTool),
|
45
|
+
new UndoRedoShortcut(editor),
|
43
46
|
];
|
44
47
|
primaryTools.forEach(tool => tool.setToolGroup(primaryToolEnabledGroup));
|
45
48
|
touchPanZoom.setEnabled(false);
|
@@ -0,0 +1,53 @@
|
|
1
|
+
/* @jest-environment jsdom */
|
2
|
+
|
3
|
+
import Color4 from '../Color4';
|
4
|
+
import Stroke from '../components/Stroke';
|
5
|
+
import EditorImage from '../EditorImage';
|
6
|
+
import Path from '../geometry/Path';
|
7
|
+
import createEditor from '../testing/createEditor';
|
8
|
+
import { InputEvtType } from '../types';
|
9
|
+
|
10
|
+
describe('UndoRedoShortcut', () => {
|
11
|
+
const testStroke = new Stroke([Path.fromString('M0,0L10,10').toRenderable({ fill: Color4.red })]);
|
12
|
+
const addTestStrokeCommand = new EditorImage.AddElementCommand(testStroke);
|
13
|
+
|
14
|
+
it('ctrl+z should undo', () => {
|
15
|
+
const editor = createEditor();
|
16
|
+
editor.dispatch(addTestStrokeCommand);
|
17
|
+
expect(editor.history.undoStackSize).toBe(1);
|
18
|
+
|
19
|
+
editor.toolController.dispatchInputEvent({
|
20
|
+
kind: InputEvtType.KeyPressEvent,
|
21
|
+
ctrlKey: true,
|
22
|
+
key: 'z',
|
23
|
+
});
|
24
|
+
|
25
|
+
expect(editor.history.undoStackSize).toBe(0);
|
26
|
+
expect(editor.history.redoStackSize).toBe(1);
|
27
|
+
});
|
28
|
+
|
29
|
+
it('ctrl+shift+Z should re-do', () => {
|
30
|
+
const editor = createEditor();
|
31
|
+
editor.dispatch(addTestStrokeCommand);
|
32
|
+
editor.dispatch(addTestStrokeCommand);
|
33
|
+
expect(editor.history.undoStackSize).toBe(2);
|
34
|
+
|
35
|
+
editor.toolController.dispatchInputEvent({
|
36
|
+
kind: InputEvtType.KeyPressEvent,
|
37
|
+
ctrlKey: true,
|
38
|
+
key: 'z',
|
39
|
+
});
|
40
|
+
|
41
|
+
expect(editor.history.undoStackSize).toBe(1);
|
42
|
+
expect(editor.history.redoStackSize).toBe(1);
|
43
|
+
|
44
|
+
editor.toolController.dispatchInputEvent({
|
45
|
+
kind: InputEvtType.KeyPressEvent,
|
46
|
+
ctrlKey: true,
|
47
|
+
key: 'Z',
|
48
|
+
});
|
49
|
+
|
50
|
+
expect(editor.history.undoStackSize).toBe(2);
|
51
|
+
expect(editor.history.redoStackSize).toBe(0);
|
52
|
+
});
|
53
|
+
});
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import Editor from '../Editor';
|
2
|
+
import { KeyPressEvent } from '../types';
|
3
|
+
import BaseTool from './BaseTool';
|
4
|
+
import { ToolType } from './ToolController';
|
5
|
+
|
6
|
+
|
7
|
+
export default class UndoRedoShortcut extends BaseTool {
|
8
|
+
public kind: ToolType.UndoRedoShortcut = ToolType.UndoRedoShortcut;
|
9
|
+
|
10
|
+
public constructor(private editor: Editor) {
|
11
|
+
super(editor.notifier, editor.localization.undoRedoTool);
|
12
|
+
}
|
13
|
+
|
14
|
+
// Activate undo/redo
|
15
|
+
public onKeyPress({ key, ctrlKey }: KeyPressEvent): boolean {
|
16
|
+
if (ctrlKey) {
|
17
|
+
if (key === 'z') {
|
18
|
+
this.editor.history.undo();
|
19
|
+
return true;
|
20
|
+
} else if (key === 'Z') {
|
21
|
+
this.editor.history.redo();
|
22
|
+
return true;
|
23
|
+
}
|
24
|
+
}
|
25
|
+
|
26
|
+
return false;
|
27
|
+
}
|
28
|
+
}
|
@@ -5,6 +5,7 @@ export interface ToolLocalization {
|
|
5
5
|
eraserTool: string;
|
6
6
|
touchPanTool: string;
|
7
7
|
twoFingerPanZoomTool: string;
|
8
|
+
undoRedoTool: string;
|
8
9
|
|
9
10
|
toolEnabledAnnouncement: (toolName: string) => string;
|
10
11
|
toolDisabledAnnouncement: (toolName: string) => string;
|
@@ -16,6 +17,7 @@ export const defaultToolLocalization: ToolLocalization = {
|
|
16
17
|
eraserTool: 'Eraser',
|
17
18
|
touchPanTool: 'Touch Panning',
|
18
19
|
twoFingerPanZoomTool: 'Panning and Zooming',
|
20
|
+
undoRedoTool: 'Undo/Redo',
|
19
21
|
|
20
22
|
toolEnabledAnnouncement: (toolName) => `${toolName} enabled`,
|
21
23
|
toolDisabledAnnouncement: (toolName) => `${toolName} disabled`,
|
package/src/types.ts
CHANGED