js-draw 0.1.6 → 0.1.9
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/.eslintrc.js +1 -0
- package/CHANGELOG.md +16 -0
- package/README.md +2 -2
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.js +6 -2
- package/dist/src/Editor.d.ts +1 -0
- package/dist/src/Editor.js +23 -8
- package/dist/src/EditorImage.d.ts +8 -13
- package/dist/src/EditorImage.js +51 -29
- package/dist/src/Viewport.d.ts +9 -1
- package/dist/src/Viewport.js +3 -1
- package/dist/src/commands/Command.d.ts +9 -8
- package/dist/src/commands/Command.js +15 -14
- package/dist/src/commands/Duplicate.d.ts +14 -0
- package/dist/src/commands/Duplicate.js +34 -0
- package/dist/src/commands/Erase.d.ts +5 -2
- package/dist/src/commands/Erase.js +28 -9
- package/dist/src/commands/SerializableCommand.d.ts +13 -0
- package/dist/src/commands/SerializableCommand.js +28 -0
- package/dist/src/commands/localization.d.ts +2 -0
- package/dist/src/commands/localization.js +2 -0
- package/dist/src/components/AbstractComponent.d.ts +15 -2
- package/dist/src/components/AbstractComponent.js +122 -26
- package/dist/src/components/SVGGlobalAttributesObject.d.ts +6 -1
- package/dist/src/components/SVGGlobalAttributesObject.js +23 -1
- package/dist/src/components/Stroke.d.ts +5 -0
- package/dist/src/components/Stroke.js +32 -1
- package/dist/src/components/Text.d.ts +11 -4
- package/dist/src/components/Text.js +57 -3
- package/dist/src/components/UnknownSVGObject.d.ts +2 -0
- package/dist/src/components/UnknownSVGObject.js +12 -1
- package/dist/src/components/builders/RectangleBuilder.d.ts +3 -1
- package/dist/src/components/builders/RectangleBuilder.js +17 -8
- package/dist/src/components/util/describeComponentList.d.ts +4 -0
- package/dist/src/components/util/describeComponentList.js +14 -0
- package/dist/src/geometry/Path.d.ts +4 -1
- package/dist/src/geometry/Path.js +4 -0
- package/dist/src/rendering/Display.d.ts +3 -0
- package/dist/src/rendering/Display.js +13 -0
- package/dist/src/rendering/RenderingStyle.d.ts +24 -0
- package/dist/src/rendering/RenderingStyle.js +32 -0
- package/dist/src/rendering/caching/RenderingCacheNode.js +5 -1
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +1 -8
- package/dist/src/rendering/renderers/AbstractRenderer.js +1 -6
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/DummyRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/TextOnlyRenderer.d.ts +2 -1
- package/dist/src/toolbar/HTMLToolbar.d.ts +2 -1
- package/dist/src/toolbar/HTMLToolbar.js +57 -535
- package/dist/src/toolbar/icons.d.ts +5 -0
- package/dist/src/toolbar/icons.js +186 -13
- package/dist/src/toolbar/localization.d.ts +4 -0
- package/dist/src/toolbar/localization.js +4 -0
- package/dist/src/toolbar/makeColorInput.d.ts +5 -0
- package/dist/src/toolbar/makeColorInput.js +95 -0
- package/dist/src/toolbar/widgets/BaseToolWidget.d.ts +12 -0
- package/dist/src/toolbar/widgets/BaseToolWidget.js +44 -0
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +32 -0
- package/dist/src/toolbar/widgets/BaseWidget.js +148 -0
- package/dist/src/toolbar/widgets/EraserWidget.d.ts +6 -0
- package/dist/src/toolbar/widgets/EraserWidget.js +14 -0
- package/dist/src/toolbar/widgets/HandToolWidget.d.ts +13 -0
- package/dist/src/toolbar/widgets/HandToolWidget.js +133 -0
- package/dist/src/toolbar/widgets/PenWidget.d.ts +20 -0
- package/dist/src/toolbar/widgets/PenWidget.js +131 -0
- package/dist/src/toolbar/widgets/SelectionWidget.d.ts +11 -0
- package/dist/src/toolbar/widgets/SelectionWidget.js +56 -0
- package/dist/src/toolbar/widgets/TextToolWidget.d.ts +13 -0
- package/dist/src/toolbar/widgets/TextToolWidget.js +72 -0
- package/dist/src/tools/Pen.js +1 -1
- package/dist/src/tools/PipetteTool.d.ts +20 -0
- package/dist/src/tools/PipetteTool.js +40 -0
- package/dist/src/tools/SelectionTool.d.ts +2 -0
- package/dist/src/tools/SelectionTool.js +41 -23
- package/dist/src/tools/TextTool.js +1 -1
- package/dist/src/tools/ToolController.d.ts +3 -1
- package/dist/src/tools/ToolController.js +4 -0
- package/dist/src/tools/localization.d.ts +2 -1
- package/dist/src/tools/localization.js +3 -2
- package/dist/src/types.d.ts +7 -2
- package/dist/src/types.js +1 -0
- package/jest.config.js +2 -0
- package/package.json +6 -6
- package/src/Color4.ts +9 -3
- package/src/Editor.ts +28 -11
- package/src/EditorImage.test.ts +5 -5
- package/src/EditorImage.ts +61 -20
- package/src/SVGLoader.ts +2 -1
- package/src/Viewport.ts +2 -1
- package/src/commands/Command.ts +21 -19
- package/src/commands/Duplicate.ts +49 -0
- package/src/commands/Erase.ts +34 -13
- package/src/commands/SerializableCommand.ts +41 -0
- package/src/commands/localization.ts +5 -0
- package/src/components/AbstractComponent.ts +168 -26
- package/src/components/SVGGlobalAttributesObject.ts +34 -2
- package/src/components/Stroke.test.ts +53 -0
- package/src/components/Stroke.ts +37 -2
- package/src/components/Text.test.ts +38 -0
- package/src/components/Text.ts +80 -5
- package/src/components/UnknownSVGObject.test.ts +10 -0
- package/src/components/UnknownSVGObject.ts +15 -1
- package/src/components/builders/FreehandLineBuilder.ts +2 -1
- package/src/components/builders/RectangleBuilder.ts +23 -8
- package/src/components/util/describeComponentList.ts +18 -0
- package/src/geometry/Path.ts +8 -1
- package/src/rendering/Display.ts +17 -1
- package/src/rendering/RenderingStyle.test.ts +68 -0
- package/src/rendering/RenderingStyle.ts +46 -0
- package/src/rendering/caching/RenderingCache.test.ts +1 -1
- package/src/rendering/caching/RenderingCacheNode.ts +6 -1
- package/src/rendering/renderers/AbstractRenderer.ts +1 -15
- package/src/rendering/renderers/CanvasRenderer.ts +2 -1
- package/src/rendering/renderers/DummyRenderer.ts +2 -1
- package/src/rendering/renderers/SVGRenderer.ts +2 -1
- package/src/rendering/renderers/TextOnlyRenderer.ts +2 -1
- package/src/toolbar/HTMLToolbar.ts +64 -661
- package/src/toolbar/icons.ts +205 -13
- package/src/toolbar/localization.ts +10 -2
- package/src/toolbar/makeColorInput.ts +120 -0
- package/src/toolbar/toolbar.css +116 -78
- package/src/toolbar/widgets/BaseToolWidget.ts +53 -0
- package/src/toolbar/widgets/BaseWidget.ts +175 -0
- package/src/toolbar/widgets/EraserWidget.ts +16 -0
- package/src/toolbar/widgets/HandToolWidget.ts +186 -0
- package/src/toolbar/widgets/PenWidget.ts +165 -0
- package/src/toolbar/widgets/SelectionWidget.ts +72 -0
- package/src/toolbar/widgets/TextToolWidget.ts +90 -0
- package/src/tools/Pen.ts +1 -1
- package/src/tools/PipetteTool.ts +56 -0
- package/src/tools/SelectionTool.test.ts +2 -4
- package/src/tools/SelectionTool.ts +47 -27
- package/src/tools/TextTool.ts +1 -1
- package/src/tools/ToolController.ts +10 -6
- package/src/tools/UndoRedoShortcut.test.ts +1 -1
- package/src/tools/localization.ts +6 -3
- package/src/types.ts +12 -1
@@ -0,0 +1,133 @@
|
|
1
|
+
import Mat33 from '../../geometry/Mat33';
|
2
|
+
import { PanZoomMode } from '../../tools/PanZoom';
|
3
|
+
import { EditorEventType } from '../../types';
|
4
|
+
import Viewport from '../../Viewport';
|
5
|
+
import { toolbarCSSPrefix } from '../HTMLToolbar';
|
6
|
+
import { makeAllDevicePanningIcon, makeHandToolIcon, makeTouchPanningIcon, makeZoomIcon } from '../icons';
|
7
|
+
import BaseToolWidget from './BaseToolWidget';
|
8
|
+
import BaseWidget from './BaseWidget';
|
9
|
+
const makeZoomControl = (localizationTable, editor) => {
|
10
|
+
const zoomLevelRow = document.createElement('div');
|
11
|
+
const increaseButton = document.createElement('button');
|
12
|
+
const decreaseButton = document.createElement('button');
|
13
|
+
const zoomLevelDisplay = document.createElement('span');
|
14
|
+
increaseButton.innerText = '+';
|
15
|
+
decreaseButton.innerText = '-';
|
16
|
+
zoomLevelRow.replaceChildren(zoomLevelDisplay, increaseButton, decreaseButton);
|
17
|
+
zoomLevelRow.classList.add(`${toolbarCSSPrefix}zoomLevelEditor`);
|
18
|
+
zoomLevelDisplay.classList.add('zoomDisplay');
|
19
|
+
let lastZoom;
|
20
|
+
const updateZoomDisplay = () => {
|
21
|
+
let zoomLevel = editor.viewport.getScaleFactor() * 100;
|
22
|
+
if (zoomLevel > 0.1) {
|
23
|
+
zoomLevel = Math.round(zoomLevel * 10) / 10;
|
24
|
+
}
|
25
|
+
else {
|
26
|
+
zoomLevel = Math.round(zoomLevel * 1000) / 1000;
|
27
|
+
}
|
28
|
+
if (zoomLevel !== lastZoom) {
|
29
|
+
zoomLevelDisplay.innerText = localizationTable.zoomLevel(zoomLevel);
|
30
|
+
lastZoom = zoomLevel;
|
31
|
+
}
|
32
|
+
};
|
33
|
+
updateZoomDisplay();
|
34
|
+
editor.notifier.on(EditorEventType.ViewportChanged, (event) => {
|
35
|
+
if (event.kind === EditorEventType.ViewportChanged) {
|
36
|
+
updateZoomDisplay();
|
37
|
+
}
|
38
|
+
});
|
39
|
+
const zoomBy = (factor) => {
|
40
|
+
const screenCenter = editor.viewport.visibleRect.center;
|
41
|
+
const transformUpdate = Mat33.scaling2D(factor, screenCenter);
|
42
|
+
editor.dispatch(new Viewport.ViewportTransform(transformUpdate), false);
|
43
|
+
};
|
44
|
+
increaseButton.onclick = () => {
|
45
|
+
zoomBy(5.0 / 4);
|
46
|
+
};
|
47
|
+
decreaseButton.onclick = () => {
|
48
|
+
zoomBy(4.0 / 5);
|
49
|
+
};
|
50
|
+
return zoomLevelRow;
|
51
|
+
};
|
52
|
+
class ZoomWidget extends BaseWidget {
|
53
|
+
constructor(editor, localizationTable) {
|
54
|
+
super(editor, localizationTable);
|
55
|
+
// Make it possible to open the dropdown, even if this widget isn't selected.
|
56
|
+
this.container.classList.add('dropdownShowable');
|
57
|
+
}
|
58
|
+
getTitle() {
|
59
|
+
return this.localizationTable.zoom;
|
60
|
+
}
|
61
|
+
createIcon() {
|
62
|
+
return makeZoomIcon();
|
63
|
+
}
|
64
|
+
handleClick() {
|
65
|
+
this.setDropdownVisible(!this.isDropdownVisible());
|
66
|
+
}
|
67
|
+
fillDropdown(dropdown) {
|
68
|
+
dropdown.appendChild(makeZoomControl(this.localizationTable, this.editor));
|
69
|
+
return true;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
class HandModeWidget extends BaseWidget {
|
73
|
+
constructor(editor, localizationTable, tool, flag, makeIcon, title) {
|
74
|
+
super(editor, localizationTable);
|
75
|
+
this.tool = tool;
|
76
|
+
this.flag = flag;
|
77
|
+
this.makeIcon = makeIcon;
|
78
|
+
this.title = title;
|
79
|
+
editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
|
80
|
+
if (toolEvt.kind === EditorEventType.ToolUpdated && toolEvt.tool === tool) {
|
81
|
+
const allEnabled = !!(tool.getMode() & PanZoomMode.SinglePointerGestures);
|
82
|
+
this.setSelected(!!(tool.getMode() & flag) || allEnabled);
|
83
|
+
// Unless this widget toggles all single pointer gestures, toggling while
|
84
|
+
// single pointer gestures are enabled should have no effect
|
85
|
+
this.setDisabled(allEnabled && flag !== PanZoomMode.SinglePointerGestures);
|
86
|
+
}
|
87
|
+
});
|
88
|
+
this.setSelected(false);
|
89
|
+
}
|
90
|
+
setModeFlag(enabled) {
|
91
|
+
const mode = this.tool.getMode();
|
92
|
+
if (enabled) {
|
93
|
+
this.tool.setMode(mode | this.flag);
|
94
|
+
}
|
95
|
+
else {
|
96
|
+
this.tool.setMode(mode & ~this.flag);
|
97
|
+
}
|
98
|
+
}
|
99
|
+
handleClick() {
|
100
|
+
this.setModeFlag(!this.isSelected());
|
101
|
+
}
|
102
|
+
getTitle() {
|
103
|
+
return this.title;
|
104
|
+
}
|
105
|
+
createIcon() {
|
106
|
+
return this.makeIcon();
|
107
|
+
}
|
108
|
+
fillDropdown(_dropdown) {
|
109
|
+
return false;
|
110
|
+
}
|
111
|
+
}
|
112
|
+
export default class HandToolWidget extends BaseToolWidget {
|
113
|
+
constructor(editor, tool, localizationTable) {
|
114
|
+
super(editor, tool, localizationTable);
|
115
|
+
this.tool = tool;
|
116
|
+
this.container.classList.add('dropdownShowable');
|
117
|
+
this.touchPanningWidget = new HandModeWidget(editor, localizationTable, tool, PanZoomMode.OneFingerTouchGestures, makeTouchPanningIcon, localizationTable.touchPanning);
|
118
|
+
this.addSubWidget(this.touchPanningWidget);
|
119
|
+
this.addSubWidget(new HandModeWidget(editor, localizationTable, tool, PanZoomMode.SinglePointerGestures, makeAllDevicePanningIcon, localizationTable.anyDevicePanning));
|
120
|
+
this.addSubWidget(new ZoomWidget(editor, localizationTable));
|
121
|
+
}
|
122
|
+
getTitle() {
|
123
|
+
return this.localizationTable.handTool;
|
124
|
+
}
|
125
|
+
createIcon() {
|
126
|
+
return makeHandToolIcon();
|
127
|
+
}
|
128
|
+
setSelected(_selected) {
|
129
|
+
}
|
130
|
+
handleClick() {
|
131
|
+
this.setDropdownVisible(!this.isDropdownVisible());
|
132
|
+
}
|
133
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { ComponentBuilderFactory } from '../../components/builders/types';
|
2
|
+
import Editor from '../../Editor';
|
3
|
+
import Pen from '../../tools/Pen';
|
4
|
+
import { ToolbarLocalization } from '../localization';
|
5
|
+
import BaseToolWidget from './BaseToolWidget';
|
6
|
+
interface PenTypeRecord {
|
7
|
+
name: string;
|
8
|
+
factory: ComponentBuilderFactory;
|
9
|
+
}
|
10
|
+
export default class PenWidget extends BaseToolWidget {
|
11
|
+
private tool;
|
12
|
+
private updateInputs;
|
13
|
+
protected penTypes: PenTypeRecord[];
|
14
|
+
constructor(editor: Editor, tool: Pen, localization: ToolbarLocalization);
|
15
|
+
protected getTitle(): string;
|
16
|
+
protected createIcon(): Element;
|
17
|
+
private static idCounter;
|
18
|
+
protected fillDropdown(dropdown: HTMLElement): boolean;
|
19
|
+
}
|
20
|
+
export {};
|
@@ -0,0 +1,131 @@
|
|
1
|
+
import { makeArrowBuilder } from '../../components/builders/ArrowBuilder';
|
2
|
+
import { makeFreehandLineBuilder } from '../../components/builders/FreehandLineBuilder';
|
3
|
+
import { makeLineBuilder } from '../../components/builders/LineBuilder';
|
4
|
+
import { makeFilledRectangleBuilder, makeOutlinedRectangleBuilder } from '../../components/builders/RectangleBuilder';
|
5
|
+
import { EditorEventType } from '../../types';
|
6
|
+
import { toolbarCSSPrefix } from '../HTMLToolbar';
|
7
|
+
import { makeIconFromFactory, makePenIcon } from '../icons';
|
8
|
+
import makeColorInput from '../makeColorInput';
|
9
|
+
import BaseToolWidget from './BaseToolWidget';
|
10
|
+
export default class PenWidget extends BaseToolWidget {
|
11
|
+
constructor(editor, tool, localization) {
|
12
|
+
super(editor, tool, localization);
|
13
|
+
this.tool = tool;
|
14
|
+
this.updateInputs = () => { };
|
15
|
+
// Default pen types
|
16
|
+
this.penTypes = [
|
17
|
+
{
|
18
|
+
name: localization.freehandPen,
|
19
|
+
factory: makeFreehandLineBuilder,
|
20
|
+
},
|
21
|
+
{
|
22
|
+
name: localization.arrowPen,
|
23
|
+
factory: makeArrowBuilder,
|
24
|
+
},
|
25
|
+
{
|
26
|
+
name: localization.linePen,
|
27
|
+
factory: makeLineBuilder,
|
28
|
+
},
|
29
|
+
{
|
30
|
+
name: localization.filledRectanglePen,
|
31
|
+
factory: makeFilledRectangleBuilder,
|
32
|
+
},
|
33
|
+
{
|
34
|
+
name: localization.outlinedRectanglePen,
|
35
|
+
factory: makeOutlinedRectangleBuilder,
|
36
|
+
},
|
37
|
+
];
|
38
|
+
this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
|
39
|
+
if (toolEvt.kind !== EditorEventType.ToolUpdated) {
|
40
|
+
throw new Error('Invalid event type!');
|
41
|
+
}
|
42
|
+
// The button icon may depend on tool properties.
|
43
|
+
if (toolEvt.tool === this.tool) {
|
44
|
+
this.updateIcon();
|
45
|
+
this.updateInputs();
|
46
|
+
}
|
47
|
+
});
|
48
|
+
}
|
49
|
+
getTitle() {
|
50
|
+
return this.targetTool.description;
|
51
|
+
}
|
52
|
+
createIcon() {
|
53
|
+
const strokeFactory = this.tool.getStrokeFactory();
|
54
|
+
if (strokeFactory === makeFreehandLineBuilder) {
|
55
|
+
// Use a square-root scale to prevent the pen's tip from overflowing.
|
56
|
+
const scale = Math.round(Math.sqrt(this.tool.getThickness()) * 4);
|
57
|
+
const color = this.tool.getColor();
|
58
|
+
return makePenIcon(scale, color.toHexString());
|
59
|
+
}
|
60
|
+
else {
|
61
|
+
const strokeFactory = this.tool.getStrokeFactory();
|
62
|
+
return makeIconFromFactory(this.tool, strokeFactory);
|
63
|
+
}
|
64
|
+
}
|
65
|
+
fillDropdown(dropdown) {
|
66
|
+
const container = document.createElement('div');
|
67
|
+
const thicknessRow = document.createElement('div');
|
68
|
+
const objectTypeRow = document.createElement('div');
|
69
|
+
// Thickness: Value of the input is squared to allow for finer control/larger values.
|
70
|
+
const thicknessLabel = document.createElement('label');
|
71
|
+
const thicknessInput = document.createElement('input');
|
72
|
+
const objectSelectLabel = document.createElement('label');
|
73
|
+
const objectTypeSelect = document.createElement('select');
|
74
|
+
// Give inputs IDs so we can label them with a <label for=...>Label text</label>
|
75
|
+
thicknessInput.id = `${toolbarCSSPrefix}thicknessInput${PenWidget.idCounter++}`;
|
76
|
+
objectTypeSelect.id = `${toolbarCSSPrefix}builderSelect${PenWidget.idCounter++}`;
|
77
|
+
thicknessLabel.innerText = this.localizationTable.thicknessLabel;
|
78
|
+
thicknessLabel.setAttribute('for', thicknessInput.id);
|
79
|
+
objectSelectLabel.innerText = this.localizationTable.selectObjectType;
|
80
|
+
objectSelectLabel.setAttribute('for', objectTypeSelect.id);
|
81
|
+
thicknessInput.type = 'range';
|
82
|
+
thicknessInput.min = '1';
|
83
|
+
thicknessInput.max = '20';
|
84
|
+
thicknessInput.step = '1';
|
85
|
+
thicknessInput.oninput = () => {
|
86
|
+
this.tool.setThickness(Math.pow(parseFloat(thicknessInput.value), 2));
|
87
|
+
};
|
88
|
+
thicknessRow.appendChild(thicknessLabel);
|
89
|
+
thicknessRow.appendChild(thicknessInput);
|
90
|
+
objectTypeSelect.oninput = () => {
|
91
|
+
const penTypeIdx = parseInt(objectTypeSelect.value);
|
92
|
+
if (penTypeIdx < 0 || penTypeIdx >= this.penTypes.length) {
|
93
|
+
console.error('Invalid pen type index', penTypeIdx);
|
94
|
+
return;
|
95
|
+
}
|
96
|
+
this.tool.setStrokeFactory(this.penTypes[penTypeIdx].factory);
|
97
|
+
};
|
98
|
+
objectTypeRow.appendChild(objectSelectLabel);
|
99
|
+
objectTypeRow.appendChild(objectTypeSelect);
|
100
|
+
const colorRow = document.createElement('div');
|
101
|
+
const colorLabel = document.createElement('label');
|
102
|
+
const [colorInput, colorInputContainer] = makeColorInput(this.editor, color => {
|
103
|
+
this.tool.setColor(color);
|
104
|
+
});
|
105
|
+
colorInput.id = `${toolbarCSSPrefix}colorInput${PenWidget.idCounter++}`;
|
106
|
+
colorLabel.innerText = this.localizationTable.colorLabel;
|
107
|
+
colorLabel.setAttribute('for', colorInput.id);
|
108
|
+
colorRow.appendChild(colorLabel);
|
109
|
+
colorRow.appendChild(colorInputContainer);
|
110
|
+
this.updateInputs = () => {
|
111
|
+
colorInput.value = this.tool.getColor().toHexString();
|
112
|
+
thicknessInput.value = Math.sqrt(this.tool.getThickness()).toString();
|
113
|
+
objectTypeSelect.replaceChildren();
|
114
|
+
for (let i = 0; i < this.penTypes.length; i++) {
|
115
|
+
const penType = this.penTypes[i];
|
116
|
+
const option = document.createElement('option');
|
117
|
+
option.value = i.toString();
|
118
|
+
option.innerText = penType.name;
|
119
|
+
objectTypeSelect.appendChild(option);
|
120
|
+
if (penType.factory === this.tool.getStrokeFactory()) {
|
121
|
+
objectTypeSelect.value = i.toString();
|
122
|
+
}
|
123
|
+
}
|
124
|
+
};
|
125
|
+
this.updateInputs();
|
126
|
+
container.replaceChildren(colorRow, thicknessRow, objectTypeRow);
|
127
|
+
dropdown.replaceChildren(container);
|
128
|
+
return true;
|
129
|
+
}
|
130
|
+
}
|
131
|
+
PenWidget.idCounter = 0;
|
@@ -0,0 +1,11 @@
|
|
1
|
+
import Editor from '../../Editor';
|
2
|
+
import SelectionTool from '../../tools/SelectionTool';
|
3
|
+
import { ToolbarLocalization } from '../localization';
|
4
|
+
import BaseToolWidget from './BaseToolWidget';
|
5
|
+
export declare class SelectionWidget extends BaseToolWidget {
|
6
|
+
private tool;
|
7
|
+
constructor(editor: Editor, tool: SelectionTool, localization: ToolbarLocalization);
|
8
|
+
protected getTitle(): string;
|
9
|
+
protected createIcon(): Element;
|
10
|
+
protected fillDropdown(dropdown: HTMLElement): boolean;
|
11
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import { EditorEventType } from '../../types';
|
2
|
+
import { makeSelectionIcon } from '../icons';
|
3
|
+
import BaseToolWidget from './BaseToolWidget';
|
4
|
+
export class SelectionWidget extends BaseToolWidget {
|
5
|
+
constructor(editor, tool, localization) {
|
6
|
+
super(editor, tool, localization);
|
7
|
+
this.tool = tool;
|
8
|
+
}
|
9
|
+
getTitle() {
|
10
|
+
return this.localizationTable.select;
|
11
|
+
}
|
12
|
+
createIcon() {
|
13
|
+
return makeSelectionIcon();
|
14
|
+
}
|
15
|
+
fillDropdown(dropdown) {
|
16
|
+
const container = document.createElement('div');
|
17
|
+
const resizeButton = document.createElement('button');
|
18
|
+
const duplicateButton = document.createElement('button');
|
19
|
+
const deleteButton = document.createElement('button');
|
20
|
+
resizeButton.innerText = this.localizationTable.resizeImageToSelection;
|
21
|
+
resizeButton.disabled = true;
|
22
|
+
deleteButton.innerText = this.localizationTable.deleteSelection;
|
23
|
+
deleteButton.disabled = true;
|
24
|
+
duplicateButton.innerText = this.localizationTable.duplicateSelection;
|
25
|
+
duplicateButton.disabled = true;
|
26
|
+
resizeButton.onclick = () => {
|
27
|
+
const selection = this.tool.getSelection();
|
28
|
+
this.editor.dispatch(this.editor.setImportExportRect(selection.region));
|
29
|
+
};
|
30
|
+
deleteButton.onclick = () => {
|
31
|
+
const selection = this.tool.getSelection();
|
32
|
+
this.editor.dispatch(selection.deleteSelectedObjects());
|
33
|
+
this.tool.clearSelection();
|
34
|
+
};
|
35
|
+
duplicateButton.onclick = () => {
|
36
|
+
const selection = this.tool.getSelection();
|
37
|
+
this.editor.dispatch(selection.duplicateSelectedObjects());
|
38
|
+
};
|
39
|
+
// Enable/disable actions based on whether items are selected
|
40
|
+
this.editor.notifier.on(EditorEventType.ToolUpdated, toolEvt => {
|
41
|
+
if (toolEvt.kind !== EditorEventType.ToolUpdated) {
|
42
|
+
throw new Error('Invalid event type!');
|
43
|
+
}
|
44
|
+
if (toolEvt.tool === this.tool) {
|
45
|
+
const selection = this.tool.getSelection();
|
46
|
+
const hasSelection = selection && selection.region.area > 0;
|
47
|
+
resizeButton.disabled = !hasSelection;
|
48
|
+
deleteButton.disabled = resizeButton.disabled;
|
49
|
+
duplicateButton.disabled = resizeButton.disabled;
|
50
|
+
}
|
51
|
+
});
|
52
|
+
container.replaceChildren(resizeButton, duplicateButton, deleteButton);
|
53
|
+
dropdown.appendChild(container);
|
54
|
+
return true;
|
55
|
+
}
|
56
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import Editor from '../../Editor';
|
2
|
+
import TextTool from '../../tools/TextTool';
|
3
|
+
import { ToolbarLocalization } from '../localization';
|
4
|
+
import BaseToolWidget from './BaseToolWidget';
|
5
|
+
export default class TextToolWidget extends BaseToolWidget {
|
6
|
+
private tool;
|
7
|
+
private updateDropdownInputs;
|
8
|
+
constructor(editor: Editor, tool: TextTool, localization: ToolbarLocalization);
|
9
|
+
protected getTitle(): string;
|
10
|
+
protected createIcon(): Element;
|
11
|
+
private static idCounter;
|
12
|
+
protected fillDropdown(dropdown: HTMLElement): boolean;
|
13
|
+
}
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import { EditorEventType } from '../../types';
|
2
|
+
import { toolbarCSSPrefix } from '../HTMLToolbar';
|
3
|
+
import { makeTextIcon } from '../icons';
|
4
|
+
import makeColorInput from '../makeColorInput';
|
5
|
+
import BaseToolWidget from './BaseToolWidget';
|
6
|
+
export default class TextToolWidget extends BaseToolWidget {
|
7
|
+
constructor(editor, tool, localization) {
|
8
|
+
super(editor, tool, localization);
|
9
|
+
this.tool = tool;
|
10
|
+
this.updateDropdownInputs = null;
|
11
|
+
editor.notifier.on(EditorEventType.ToolUpdated, evt => {
|
12
|
+
var _a;
|
13
|
+
if (evt.kind === EditorEventType.ToolUpdated && evt.tool === tool) {
|
14
|
+
this.updateIcon();
|
15
|
+
(_a = this.updateDropdownInputs) === null || _a === void 0 ? void 0 : _a.call(this);
|
16
|
+
}
|
17
|
+
});
|
18
|
+
}
|
19
|
+
getTitle() {
|
20
|
+
return this.targetTool.description;
|
21
|
+
}
|
22
|
+
createIcon() {
|
23
|
+
const textStyle = this.tool.getTextStyle();
|
24
|
+
return makeTextIcon(textStyle);
|
25
|
+
}
|
26
|
+
fillDropdown(dropdown) {
|
27
|
+
const fontRow = document.createElement('div');
|
28
|
+
const colorRow = document.createElement('div');
|
29
|
+
const fontInput = document.createElement('select');
|
30
|
+
const fontLabel = document.createElement('label');
|
31
|
+
const [colorInput, colorInputContainer] = makeColorInput(this.editor, color => {
|
32
|
+
this.tool.setColor(color);
|
33
|
+
});
|
34
|
+
const colorLabel = document.createElement('label');
|
35
|
+
const fontsInInput = new Set();
|
36
|
+
const addFontToInput = (fontName) => {
|
37
|
+
const option = document.createElement('option');
|
38
|
+
option.value = fontName;
|
39
|
+
option.textContent = fontName;
|
40
|
+
fontInput.appendChild(option);
|
41
|
+
fontsInInput.add(fontName);
|
42
|
+
};
|
43
|
+
fontLabel.innerText = this.localizationTable.fontLabel;
|
44
|
+
colorLabel.innerText = this.localizationTable.colorLabel;
|
45
|
+
colorInput.id = `${toolbarCSSPrefix}-text-color-input-${TextToolWidget.idCounter++}`;
|
46
|
+
colorLabel.setAttribute('for', colorInput.id);
|
47
|
+
addFontToInput('monospace');
|
48
|
+
addFontToInput('serif');
|
49
|
+
addFontToInput('sans-serif');
|
50
|
+
fontInput.id = `${toolbarCSSPrefix}-text-font-input-${TextToolWidget.idCounter++}`;
|
51
|
+
fontLabel.setAttribute('for', fontInput.id);
|
52
|
+
fontInput.onchange = () => {
|
53
|
+
this.tool.setFontFamily(fontInput.value);
|
54
|
+
};
|
55
|
+
colorRow.appendChild(colorLabel);
|
56
|
+
colorRow.appendChild(colorInputContainer);
|
57
|
+
fontRow.appendChild(fontLabel);
|
58
|
+
fontRow.appendChild(fontInput);
|
59
|
+
this.updateDropdownInputs = () => {
|
60
|
+
const style = this.tool.getTextStyle();
|
61
|
+
colorInput.value = style.renderingStyle.fill.toHexString();
|
62
|
+
if (!fontsInInput.has(style.fontFamily)) {
|
63
|
+
addFontToInput(style.fontFamily);
|
64
|
+
}
|
65
|
+
fontInput.value = style.fontFamily;
|
66
|
+
};
|
67
|
+
this.updateDropdownInputs();
|
68
|
+
dropdown.replaceChildren(colorRow, fontRow);
|
69
|
+
return true;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
TextToolWidget.idCounter = 0;
|
package/dist/src/tools/Pen.js
CHANGED
@@ -68,7 +68,7 @@ export default class Pen extends BaseTool {
|
|
68
68
|
this.previewStroke();
|
69
69
|
if (stroke.getBBox().area > 0) {
|
70
70
|
const canFlatten = true;
|
71
|
-
const action =
|
71
|
+
const action = EditorImage.addElement(stroke, canFlatten);
|
72
72
|
this.editor.dispatch(action);
|
73
73
|
}
|
74
74
|
else {
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import Color4 from '../Color4';
|
2
|
+
import Editor from '../Editor';
|
3
|
+
import { PointerEvt } from '../types';
|
4
|
+
import BaseTool from './BaseTool';
|
5
|
+
import { ToolType } from './ToolController';
|
6
|
+
declare type ColorListener = (color: Color4 | null) => void;
|
7
|
+
export default class PipetteTool extends BaseTool {
|
8
|
+
private editor;
|
9
|
+
kind: ToolType;
|
10
|
+
private colorPreviewListener;
|
11
|
+
private colorSelectListener;
|
12
|
+
constructor(editor: Editor, description: string);
|
13
|
+
setColorListener(colorPreviewListener: ColorListener, colorSelectListener: ColorListener): void;
|
14
|
+
clearColorListener(): void;
|
15
|
+
onPointerDown({ current, allPointers }: PointerEvt): boolean;
|
16
|
+
onPointerMove({ current }: PointerEvt): void;
|
17
|
+
onPointerUp({ current }: PointerEvt): void;
|
18
|
+
onGestureCancel(): void;
|
19
|
+
}
|
20
|
+
export {};
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import BaseTool from './BaseTool';
|
2
|
+
import { ToolType } from './ToolController';
|
3
|
+
export default class PipetteTool extends BaseTool {
|
4
|
+
constructor(editor, description) {
|
5
|
+
super(editor.notifier, description);
|
6
|
+
this.editor = editor;
|
7
|
+
this.kind = ToolType.Pipette;
|
8
|
+
this.colorPreviewListener = null;
|
9
|
+
this.colorSelectListener = null;
|
10
|
+
}
|
11
|
+
setColorListener(colorPreviewListener,
|
12
|
+
// Called when the gesture ends -- when the user has selected a color.
|
13
|
+
colorSelectListener) {
|
14
|
+
this.colorPreviewListener = colorPreviewListener;
|
15
|
+
this.colorSelectListener = colorSelectListener;
|
16
|
+
}
|
17
|
+
clearColorListener() {
|
18
|
+
this.colorPreviewListener = null;
|
19
|
+
this.colorSelectListener = null;
|
20
|
+
}
|
21
|
+
onPointerDown({ current, allPointers }) {
|
22
|
+
if (this.colorPreviewListener && allPointers.length === 1) {
|
23
|
+
this.colorPreviewListener(this.editor.display.getColorAt(current.screenPos));
|
24
|
+
return true;
|
25
|
+
}
|
26
|
+
return false;
|
27
|
+
}
|
28
|
+
onPointerMove({ current }) {
|
29
|
+
var _a;
|
30
|
+
(_a = this.colorPreviewListener) === null || _a === void 0 ? void 0 : _a.call(this, this.editor.display.getColorAt(current.screenPos));
|
31
|
+
}
|
32
|
+
onPointerUp({ current }) {
|
33
|
+
var _a;
|
34
|
+
(_a = this.colorSelectListener) === null || _a === void 0 ? void 0 : _a.call(this, this.editor.display.getColorAt(current.screenPos));
|
35
|
+
}
|
36
|
+
onGestureCancel() {
|
37
|
+
var _a;
|
38
|
+
(_a = this.colorSelectListener) === null || _a === void 0 ? void 0 : _a.call(this, null);
|
39
|
+
}
|
40
|
+
}
|
@@ -21,6 +21,7 @@ declare class Selection {
|
|
21
21
|
handleRotateCircleDrag(offset: Vec2): void;
|
22
22
|
private computeTransformCommands;
|
23
23
|
finishDragging(): void;
|
24
|
+
private static ApplyTransformationCommand;
|
24
25
|
private previewTransformCmds;
|
25
26
|
appendBackgroundBoxTo(elem: HTMLElement): void;
|
26
27
|
setToPoint(point: Point2): void;
|
@@ -32,6 +33,7 @@ declare class Selection {
|
|
32
33
|
getSelectedItemCount(): number;
|
33
34
|
updateUI(): void;
|
34
35
|
deleteSelectedObjects(): Command;
|
36
|
+
duplicateSelectedObjects(): Command;
|
35
37
|
}
|
36
38
|
export default class SelectionTool extends BaseTool {
|
37
39
|
private editor;
|
@@ -7,6 +7,8 @@ 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 Command from '../commands/Command';
|
11
|
+
import Duplicate from '../commands/Duplicate';
|
10
12
|
import Erase from '../commands/Erase';
|
11
13
|
import Mat33 from '../geometry/Mat33';
|
12
14
|
// import Mat33 from "../geometry/Mat33";
|
@@ -219,28 +221,7 @@ class Selection {
|
|
219
221
|
this.transform = Mat33.identity;
|
220
222
|
this.region = this.region.transformedBoundingBox(inverseTransform);
|
221
223
|
// Make the commands undo-able
|
222
|
-
this.editor.dispatch(
|
223
|
-
apply: (editor) => __awaiter(this, void 0, void 0, function* () {
|
224
|
-
// Approximate the new selection
|
225
|
-
this.region = this.region.transformedBoundingBox(fullTransform);
|
226
|
-
this.boxRotation += deltaBoxRotation;
|
227
|
-
this.updateUI();
|
228
|
-
yield editor.asyncApplyCommands(currentTransfmCommands, updateChunkSize);
|
229
|
-
this.recomputeRegion();
|
230
|
-
this.updateUI();
|
231
|
-
}),
|
232
|
-
unapply: (editor) => __awaiter(this, void 0, void 0, function* () {
|
233
|
-
this.region = this.region.transformedBoundingBox(inverseTransform);
|
234
|
-
this.boxRotation -= deltaBoxRotation;
|
235
|
-
this.updateUI();
|
236
|
-
yield editor.asyncUnapplyCommands(currentTransfmCommands, updateChunkSize);
|
237
|
-
this.recomputeRegion();
|
238
|
-
this.updateUI();
|
239
|
-
}),
|
240
|
-
description(localizationTable) {
|
241
|
-
return localizationTable.transformedElements(currentTransfmCommands.length);
|
242
|
-
},
|
243
|
-
});
|
224
|
+
this.editor.dispatch(new Selection.ApplyTransformationCommand(this, currentTransfmCommands, fullTransform, inverseTransform, deltaBoxRotation));
|
244
225
|
}
|
245
226
|
// Preview the effects of the current transformation on the selection
|
246
227
|
previewTransformCmds() {
|
@@ -350,7 +331,44 @@ class Selection {
|
|
350
331
|
deleteSelectedObjects() {
|
351
332
|
return new Erase(this.selectedElems);
|
352
333
|
}
|
334
|
+
duplicateSelectedObjects() {
|
335
|
+
return new Duplicate(this.selectedElems);
|
336
|
+
}
|
353
337
|
}
|
338
|
+
Selection.ApplyTransformationCommand = class extends Command {
|
339
|
+
constructor(selection, currentTransfmCommands, fullTransform, inverseTransform, deltaBoxRotation) {
|
340
|
+
super();
|
341
|
+
this.selection = selection;
|
342
|
+
this.currentTransfmCommands = currentTransfmCommands;
|
343
|
+
this.fullTransform = fullTransform;
|
344
|
+
this.inverseTransform = inverseTransform;
|
345
|
+
this.deltaBoxRotation = deltaBoxRotation;
|
346
|
+
}
|
347
|
+
apply(editor) {
|
348
|
+
return __awaiter(this, void 0, void 0, function* () {
|
349
|
+
// Approximate the new selection
|
350
|
+
this.selection.region = this.selection.region.transformedBoundingBox(this.fullTransform);
|
351
|
+
this.selection.boxRotation += this.deltaBoxRotation;
|
352
|
+
this.selection.updateUI();
|
353
|
+
yield editor.asyncApplyCommands(this.currentTransfmCommands, updateChunkSize);
|
354
|
+
this.selection.recomputeRegion();
|
355
|
+
this.selection.updateUI();
|
356
|
+
});
|
357
|
+
}
|
358
|
+
unapply(editor) {
|
359
|
+
return __awaiter(this, void 0, void 0, function* () {
|
360
|
+
this.selection.region = this.selection.region.transformedBoundingBox(this.inverseTransform);
|
361
|
+
this.selection.boxRotation -= this.deltaBoxRotation;
|
362
|
+
this.selection.updateUI();
|
363
|
+
yield editor.asyncUnapplyCommands(this.currentTransfmCommands, updateChunkSize);
|
364
|
+
this.selection.recomputeRegion();
|
365
|
+
this.selection.updateUI();
|
366
|
+
});
|
367
|
+
}
|
368
|
+
description(localizationTable) {
|
369
|
+
return localizationTable.transformedElements(this.currentTransfmCommands.length);
|
370
|
+
}
|
371
|
+
};
|
354
372
|
export default class SelectionTool extends BaseTool {
|
355
373
|
constructor(editor, description) {
|
356
374
|
super(editor.notifier, description);
|
@@ -396,7 +414,7 @@ export default class SelectionTool extends BaseTool {
|
|
396
414
|
if (hasSelection) {
|
397
415
|
this.editor.announceForAccessibility(this.editor.localization.selectedElements(this.selectionBox.getSelectedItemCount()));
|
398
416
|
const selectionRect = this.selectionBox.region;
|
399
|
-
this.editor.viewport.zoomTo(selectionRect, false)
|
417
|
+
this.editor.dispatchNoAnnounce(this.editor.viewport.zoomTo(selectionRect, false), false);
|
400
418
|
}
|
401
419
|
}
|
402
420
|
onPointerUp(event) {
|
@@ -60,7 +60,7 @@ export default class TextTool extends BaseTool {
|
|
60
60
|
}
|
61
61
|
const textTransform = Mat33.translation(this.textTargetPosition).rightMul(Mat33.scaling2D(this.editor.viewport.getSizeOfPixelOnCanvas())).rightMul(Mat33.zRotation(this.textRotation));
|
62
62
|
const textComponent = new Text([content], textTransform, this.textStyle);
|
63
|
-
const action =
|
63
|
+
const action = EditorImage.addElement(textComponent);
|
64
64
|
this.editor.dispatch(action);
|
65
65
|
}
|
66
66
|
}
|
@@ -7,6 +7,7 @@ import SelectionTool from './SelectionTool';
|
|
7
7
|
import Color4 from '../Color4';
|
8
8
|
import UndoRedoShortcut from './UndoRedoShortcut';
|
9
9
|
import TextTool from './TextTool';
|
10
|
+
import PipetteTool from './PipetteTool';
|
10
11
|
export var ToolType;
|
11
12
|
(function (ToolType) {
|
12
13
|
ToolType[ToolType["Pen"] = 0] = "Pen";
|
@@ -15,6 +16,8 @@ export var ToolType;
|
|
15
16
|
ToolType[ToolType["PanZoom"] = 3] = "PanZoom";
|
16
17
|
ToolType[ToolType["Text"] = 4] = "Text";
|
17
18
|
ToolType[ToolType["UndoRedoShortcut"] = 5] = "UndoRedoShortcut";
|
19
|
+
ToolType[ToolType["Pipette"] = 6] = "Pipette";
|
20
|
+
ToolType[ToolType["Other"] = 7] = "Other";
|
18
21
|
})(ToolType || (ToolType = {}));
|
19
22
|
export default class ToolController {
|
20
23
|
constructor(editor, localization) {
|
@@ -32,6 +35,7 @@ export default class ToolController {
|
|
32
35
|
new TextTool(editor, localization.textTool, localization),
|
33
36
|
];
|
34
37
|
this.tools = [
|
38
|
+
new PipetteTool(editor, localization.pipetteTool),
|
35
39
|
panZoomTool,
|
36
40
|
...primaryTools,
|
37
41
|
new UndoRedoShortcut(editor),
|