js-draw 0.0.1
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 +57 -0
- package/.husky/pre-commit +4 -0
- package/LICENSE +21 -0
- package/README.md +74 -0
- package/__mocks__/coloris.ts +8 -0
- package/__mocks__/styleMock.js +1 -0
- package/dist/__mocks__/coloris.d.ts +2 -0
- package/dist/__mocks__/coloris.js +5 -0
- package/dist/build_tools/BundledFile.d.ts +12 -0
- package/dist/build_tools/BundledFile.js +153 -0
- package/dist/scripts/bundle.d.ts +1 -0
- package/dist/scripts/bundle.js +19 -0
- package/dist/scripts/watchBundle.d.ts +1 -0
- package/dist/scripts/watchBundle.js +9 -0
- package/dist/src/Color4.d.ts +23 -0
- package/dist/src/Color4.js +102 -0
- package/dist/src/Display.d.ts +22 -0
- package/dist/src/Display.js +93 -0
- package/dist/src/Editor.d.ts +55 -0
- package/dist/src/Editor.js +366 -0
- package/dist/src/EditorImage.d.ts +44 -0
- package/dist/src/EditorImage.js +243 -0
- package/dist/src/EventDispatcher.d.ts +11 -0
- package/dist/src/EventDispatcher.js +39 -0
- package/dist/src/Pointer.d.ts +22 -0
- package/dist/src/Pointer.js +57 -0
- package/dist/src/SVGLoader.d.ts +21 -0
- package/dist/src/SVGLoader.js +204 -0
- package/dist/src/StrokeBuilder.d.ts +35 -0
- package/dist/src/StrokeBuilder.js +275 -0
- package/dist/src/UndoRedoHistory.d.ts +17 -0
- package/dist/src/UndoRedoHistory.js +46 -0
- package/dist/src/Viewport.d.ts +39 -0
- package/dist/src/Viewport.js +134 -0
- package/dist/src/commands/Command.d.ts +15 -0
- package/dist/src/commands/Command.js +29 -0
- package/dist/src/commands/Erase.d.ts +11 -0
- package/dist/src/commands/Erase.js +37 -0
- package/dist/src/commands/localization.d.ts +19 -0
- package/dist/src/commands/localization.js +17 -0
- package/dist/src/components/AbstractComponent.d.ts +19 -0
- package/dist/src/components/AbstractComponent.js +46 -0
- package/dist/src/components/Stroke.d.ts +16 -0
- package/dist/src/components/Stroke.js +79 -0
- package/dist/src/components/UnknownSVGObject.d.ts +15 -0
- package/dist/src/components/UnknownSVGObject.js +25 -0
- package/dist/src/components/localization.d.ts +5 -0
- package/dist/src/components/localization.js +4 -0
- package/dist/src/geometry/LineSegment2.d.ts +19 -0
- package/dist/src/geometry/LineSegment2.js +100 -0
- package/dist/src/geometry/Mat33.d.ts +31 -0
- package/dist/src/geometry/Mat33.js +187 -0
- package/dist/src/geometry/Path.d.ts +55 -0
- package/dist/src/geometry/Path.js +364 -0
- package/dist/src/geometry/Rect2.d.ts +47 -0
- package/dist/src/geometry/Rect2.js +148 -0
- package/dist/src/geometry/Vec2.d.ts +13 -0
- package/dist/src/geometry/Vec2.js +13 -0
- package/dist/src/geometry/Vec3.d.ts +32 -0
- package/dist/src/geometry/Vec3.js +98 -0
- package/dist/src/localization.d.ts +12 -0
- package/dist/src/localization.js +5 -0
- package/dist/src/main.d.ts +3 -0
- package/dist/src/main.js +4 -0
- package/dist/src/rendering/AbstractRenderer.d.ts +38 -0
- package/dist/src/rendering/AbstractRenderer.js +108 -0
- package/dist/src/rendering/CanvasRenderer.d.ts +23 -0
- package/dist/src/rendering/CanvasRenderer.js +108 -0
- package/dist/src/rendering/DummyRenderer.d.ts +25 -0
- package/dist/src/rendering/DummyRenderer.js +65 -0
- package/dist/src/rendering/SVGRenderer.d.ts +27 -0
- package/dist/src/rendering/SVGRenderer.js +122 -0
- package/dist/src/testing/loadExpectExtensions.d.ts +17 -0
- package/dist/src/testing/loadExpectExtensions.js +27 -0
- package/dist/src/toolbar/HTMLToolbar.d.ts +12 -0
- package/dist/src/toolbar/HTMLToolbar.js +444 -0
- package/dist/src/toolbar/types.d.ts +17 -0
- package/dist/src/toolbar/types.js +5 -0
- package/dist/src/tools/BaseTool.d.ts +20 -0
- package/dist/src/tools/BaseTool.js +44 -0
- package/dist/src/tools/Eraser.d.ts +16 -0
- package/dist/src/tools/Eraser.js +53 -0
- package/dist/src/tools/PanZoom.d.ts +40 -0
- package/dist/src/tools/PanZoom.js +191 -0
- package/dist/src/tools/Pen.d.ts +25 -0
- package/dist/src/tools/Pen.js +97 -0
- package/dist/src/tools/SelectionTool.d.ts +49 -0
- package/dist/src/tools/SelectionTool.js +437 -0
- package/dist/src/tools/ToolController.d.ts +18 -0
- package/dist/src/tools/ToolController.js +110 -0
- package/dist/src/tools/ToolEnabledGroup.d.ts +6 -0
- package/dist/src/tools/ToolEnabledGroup.js +11 -0
- package/dist/src/tools/localization.d.ts +10 -0
- package/dist/src/tools/localization.js +9 -0
- package/dist/src/types.d.ts +88 -0
- package/dist/src/types.js +20 -0
- package/jest.config.js +22 -0
- package/lint-staged.config.js +6 -0
- package/package.json +82 -0
- package/src/Color4.test.ts +12 -0
- package/src/Color4.ts +122 -0
- package/src/Display.ts +118 -0
- package/src/Editor.css +58 -0
- package/src/Editor.ts +469 -0
- package/src/EditorImage.test.ts +90 -0
- package/src/EditorImage.ts +297 -0
- package/src/EventDispatcher.test.ts +123 -0
- package/src/EventDispatcher.ts +53 -0
- package/src/Pointer.ts +93 -0
- package/src/SVGLoader.ts +230 -0
- package/src/StrokeBuilder.ts +362 -0
- package/src/UndoRedoHistory.ts +61 -0
- package/src/Viewport.ts +168 -0
- package/src/commands/Command.ts +43 -0
- package/src/commands/Erase.ts +52 -0
- package/src/commands/localization.ts +38 -0
- package/src/components/AbstractComponent.ts +73 -0
- package/src/components/Stroke.test.ts +18 -0
- package/src/components/Stroke.ts +102 -0
- package/src/components/UnknownSVGObject.ts +36 -0
- package/src/components/localization.ts +9 -0
- package/src/editorStyles.js +3 -0
- package/src/geometry/LineSegment2.test.ts +77 -0
- package/src/geometry/LineSegment2.ts +127 -0
- package/src/geometry/Mat33.test.ts +144 -0
- package/src/geometry/Mat33.ts +268 -0
- package/src/geometry/Path.fromString.test.ts +146 -0
- package/src/geometry/Path.test.ts +96 -0
- package/src/geometry/Path.toString.test.ts +31 -0
- package/src/geometry/Path.ts +456 -0
- package/src/geometry/Rect2.test.ts +121 -0
- package/src/geometry/Rect2.ts +215 -0
- package/src/geometry/Vec2.test.ts +32 -0
- package/src/geometry/Vec2.ts +18 -0
- package/src/geometry/Vec3.test.ts +29 -0
- package/src/geometry/Vec3.ts +133 -0
- package/src/localization.ts +27 -0
- package/src/rendering/AbstractRenderer.ts +164 -0
- package/src/rendering/CanvasRenderer.ts +141 -0
- package/src/rendering/DummyRenderer.ts +80 -0
- package/src/rendering/SVGRenderer.ts +159 -0
- package/src/testing/loadExpectExtensions.ts +43 -0
- package/src/toolbar/HTMLToolbar.ts +551 -0
- package/src/toolbar/toolbar.css +110 -0
- package/src/toolbar/types.ts +20 -0
- package/src/tools/BaseTool.ts +58 -0
- package/src/tools/Eraser.ts +67 -0
- package/src/tools/PanZoom.ts +253 -0
- package/src/tools/Pen.ts +121 -0
- package/src/tools/SelectionTool.test.ts +85 -0
- package/src/tools/SelectionTool.ts +545 -0
- package/src/tools/ToolController.ts +126 -0
- package/src/tools/ToolEnabledGroup.ts +14 -0
- package/src/tools/localization.ts +22 -0
- package/src/types.ts +133 -0
- package/tsconfig.json +28 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
import EditorImage from './EditorImage';
|
2
|
+
import ToolController from './tools/ToolController';
|
3
|
+
import { InputEvtType, EditorNotifier, ImageLoader } from './types';
|
4
|
+
import Command from './commands/Command';
|
5
|
+
import UndoRedoHistory from './UndoRedoHistory';
|
6
|
+
import Viewport from './Viewport';
|
7
|
+
import { Point2 } from './geometry/Vec2';
|
8
|
+
import HTMLToolbar from './toolbar/HTMLToolbar';
|
9
|
+
import { RenderablePathSpec } from './rendering/AbstractRenderer';
|
10
|
+
import Display, { RenderingMode } from './Display';
|
11
|
+
import Pointer from './Pointer';
|
12
|
+
import Rect2 from './geometry/Rect2';
|
13
|
+
import { EditorLocalization } from './localization';
|
14
|
+
export declare class Editor {
|
15
|
+
private container;
|
16
|
+
private renderingRegion;
|
17
|
+
history: UndoRedoHistory;
|
18
|
+
display: Display;
|
19
|
+
image: EditorImage;
|
20
|
+
private importExportViewport;
|
21
|
+
localization: EditorLocalization;
|
22
|
+
viewport: Viewport;
|
23
|
+
toolController: ToolController;
|
24
|
+
notifier: EditorNotifier;
|
25
|
+
private loadingWarning;
|
26
|
+
private accessibilityAnnounceArea;
|
27
|
+
constructor(parent: HTMLElement, renderingMode?: RenderingMode, localization?: Partial<EditorLocalization>);
|
28
|
+
showLoadingWarning(fractionLoaded: number): void;
|
29
|
+
hideLoadingWarning(): void;
|
30
|
+
announceForAccessibility(message: string): void;
|
31
|
+
addToolbar(): HTMLToolbar;
|
32
|
+
private registerListeners;
|
33
|
+
dispatch(command: Command, addToHistory?: boolean): void;
|
34
|
+
private asyncApplyOrUnapplyCommands;
|
35
|
+
asyncApplyCommands(commands: Command[], chunkSize: number): Promise<void>;
|
36
|
+
asyncUnapplyCommands(commands: Command[], chunkSize: number): Promise<void>;
|
37
|
+
private announceUndoCallback;
|
38
|
+
private announceRedoCallback;
|
39
|
+
private rerenderQueued;
|
40
|
+
queueRerender(): void;
|
41
|
+
rerender(showImageBounds?: boolean): void;
|
42
|
+
drawWetInk(...path: RenderablePathSpec[]): void;
|
43
|
+
clearWetInk(): void;
|
44
|
+
createHTMLOverlay(overlay: HTMLElement): {
|
45
|
+
remove: () => void;
|
46
|
+
};
|
47
|
+
addStyleSheet(content: string): HTMLStyleElement;
|
48
|
+
sendPenEvent(eventType: InputEvtType.PointerDownEvt | InputEvtType.PointerMoveEvt | InputEvtType.PointerUpEvt, point: Point2, allPointers?: Pointer[]): void;
|
49
|
+
toSVG(): SVGElement;
|
50
|
+
loadFrom(loader: ImageLoader): Promise<void>;
|
51
|
+
getImportExportRect(): Rect2;
|
52
|
+
setImportExportRect(imageRect: Rect2): Command;
|
53
|
+
loadFromSVG(svgData: string): Promise<void>;
|
54
|
+
}
|
55
|
+
export default Editor;
|
@@ -0,0 +1,366 @@
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
8
|
+
});
|
9
|
+
};
|
10
|
+
import EditorImage from './EditorImage';
|
11
|
+
import ToolController from './tools/ToolController';
|
12
|
+
import { InputEvtType, EditorEventType } from './types';
|
13
|
+
import UndoRedoHistory from './UndoRedoHistory';
|
14
|
+
import Viewport from './Viewport';
|
15
|
+
import EventDispatcher from './EventDispatcher';
|
16
|
+
import { Vec2 } from './geometry/Vec2';
|
17
|
+
import Vec3 from './geometry/Vec3';
|
18
|
+
import HTMLToolbar from './toolbar/HTMLToolbar';
|
19
|
+
import Display, { RenderingMode } from './Display';
|
20
|
+
import SVGRenderer from './rendering/SVGRenderer';
|
21
|
+
import Color4 from './Color4';
|
22
|
+
import SVGLoader from './SVGLoader';
|
23
|
+
import Pointer from './Pointer';
|
24
|
+
import Mat33 from './geometry/Mat33';
|
25
|
+
import { defaultEditorLocalization } from './localization';
|
26
|
+
export class Editor {
|
27
|
+
constructor(parent, renderingMode = RenderingMode.CanvasRenderer,
|
28
|
+
// Uses a default English localization if a translation is not given.
|
29
|
+
localization) {
|
30
|
+
this.localization = defaultEditorLocalization;
|
31
|
+
this.announceUndoCallback = (command) => {
|
32
|
+
this.announceForAccessibility(this.localization.undoAnnouncement(command.description(this.localization)));
|
33
|
+
};
|
34
|
+
this.announceRedoCallback = (command) => {
|
35
|
+
this.announceForAccessibility(this.localization.redoAnnouncement(command.description(this.localization)));
|
36
|
+
};
|
37
|
+
this.rerenderQueued = false;
|
38
|
+
this.container = document.createElement('div');
|
39
|
+
this.renderingRegion = document.createElement('div');
|
40
|
+
this.container.appendChild(this.renderingRegion);
|
41
|
+
this.container.className = 'imageEditorContainer';
|
42
|
+
this.localization = Object.assign(Object.assign({}, this.localization), localization);
|
43
|
+
this.loadingWarning = document.createElement('div');
|
44
|
+
this.loadingWarning.classList.add('loadingMessage');
|
45
|
+
this.loadingWarning.ariaLive = 'polite';
|
46
|
+
this.container.appendChild(this.loadingWarning);
|
47
|
+
this.accessibilityAnnounceArea = document.createElement('div');
|
48
|
+
this.accessibilityAnnounceArea.ariaLive = 'assertive';
|
49
|
+
this.accessibilityAnnounceArea.className = 'accessibilityAnnouncement';
|
50
|
+
this.container.appendChild(this.accessibilityAnnounceArea);
|
51
|
+
this.renderingRegion.style.touchAction = 'none';
|
52
|
+
this.renderingRegion.className = 'imageEditorRenderArea';
|
53
|
+
this.renderingRegion.setAttribute('tabIndex', '0');
|
54
|
+
this.renderingRegion.ariaLabel = this.localization.imageEditor;
|
55
|
+
this.notifier = new EventDispatcher();
|
56
|
+
this.importExportViewport = new Viewport(this.notifier);
|
57
|
+
this.viewport = new Viewport(this.notifier);
|
58
|
+
this.display = new Display(this, renderingMode, this.renderingRegion);
|
59
|
+
this.image = new EditorImage();
|
60
|
+
this.history = new UndoRedoHistory(this, this.announceRedoCallback, this.announceUndoCallback);
|
61
|
+
this.toolController = new ToolController(this, this.localization);
|
62
|
+
parent.appendChild(this.container);
|
63
|
+
// Default to a 500x500 image
|
64
|
+
this.importExportViewport.updateScreenSize(Vec2.of(500, 500));
|
65
|
+
this.viewport.updateScreenSize(Vec2.of(this.display.width, this.display.height));
|
66
|
+
this.registerListeners();
|
67
|
+
this.rerender();
|
68
|
+
this.hideLoadingWarning();
|
69
|
+
}
|
70
|
+
// [fractionLoaded] should be a number from 0 to 1, where 1 represents completely loaded.
|
71
|
+
showLoadingWarning(fractionLoaded) {
|
72
|
+
const loadingPercent = Math.round(fractionLoaded * 100);
|
73
|
+
this.loadingWarning.innerText = this.localization.loading(loadingPercent);
|
74
|
+
this.loadingWarning.style.display = 'block';
|
75
|
+
}
|
76
|
+
hideLoadingWarning() {
|
77
|
+
this.loadingWarning.style.display = 'none';
|
78
|
+
this.announceForAccessibility(this.localization.doneLoading);
|
79
|
+
}
|
80
|
+
announceForAccessibility(message) {
|
81
|
+
this.accessibilityAnnounceArea.innerText = message;
|
82
|
+
}
|
83
|
+
addToolbar() {
|
84
|
+
return new HTMLToolbar(this, this.container, this.localization);
|
85
|
+
}
|
86
|
+
registerListeners() {
|
87
|
+
const pointers = {};
|
88
|
+
const getPointerList = () => {
|
89
|
+
const nowTime = (new Date()).getTime();
|
90
|
+
const res = [];
|
91
|
+
for (const id in pointers) {
|
92
|
+
const maxUnupdatedTime = 2000; // Maximum time without a pointer update (ms)
|
93
|
+
if (pointers[id] && (nowTime - pointers[id].timeStamp) < maxUnupdatedTime) {
|
94
|
+
res.push(pointers[id]);
|
95
|
+
}
|
96
|
+
}
|
97
|
+
return res;
|
98
|
+
};
|
99
|
+
// May be required to prevent text selection on iOS/Safari:
|
100
|
+
// See https://stackoverflow.com/a/70992717/17055750
|
101
|
+
this.renderingRegion.addEventListener('touchstart', evt => evt.preventDefault());
|
102
|
+
this.renderingRegion.addEventListener('pointerdown', evt => {
|
103
|
+
const pointer = Pointer.ofEvent(evt, true, this.viewport);
|
104
|
+
pointers[pointer.id] = pointer;
|
105
|
+
this.renderingRegion.setPointerCapture(pointer.id);
|
106
|
+
const event = {
|
107
|
+
kind: InputEvtType.PointerDownEvt,
|
108
|
+
current: pointer,
|
109
|
+
allPointers: getPointerList(),
|
110
|
+
};
|
111
|
+
this.toolController.dispatchInputEvent(event);
|
112
|
+
return true;
|
113
|
+
});
|
114
|
+
this.renderingRegion.addEventListener('pointermove', evt => {
|
115
|
+
var _a, _b;
|
116
|
+
const pointer = Pointer.ofEvent(evt, (_b = (_a = pointers[evt.pointerId]) === null || _a === void 0 ? void 0 : _a.down) !== null && _b !== void 0 ? _b : false, this.viewport);
|
117
|
+
if (pointer.down) {
|
118
|
+
pointers[pointer.id] = pointer;
|
119
|
+
if (this.toolController.dispatchInputEvent({
|
120
|
+
kind: InputEvtType.PointerMoveEvt,
|
121
|
+
current: pointer,
|
122
|
+
allPointers: getPointerList(),
|
123
|
+
})) {
|
124
|
+
evt.preventDefault();
|
125
|
+
}
|
126
|
+
}
|
127
|
+
});
|
128
|
+
const pointerEnd = (evt) => {
|
129
|
+
const pointer = Pointer.ofEvent(evt, false, this.viewport);
|
130
|
+
if (!pointers[pointer.id]) {
|
131
|
+
return;
|
132
|
+
}
|
133
|
+
pointers[pointer.id] = pointer;
|
134
|
+
this.renderingRegion.releasePointerCapture(pointer.id);
|
135
|
+
if (this.toolController.dispatchInputEvent({
|
136
|
+
kind: InputEvtType.PointerUpEvt,
|
137
|
+
current: pointer,
|
138
|
+
allPointers: getPointerList(),
|
139
|
+
})) {
|
140
|
+
evt.preventDefault();
|
141
|
+
}
|
142
|
+
delete pointers[pointer.id];
|
143
|
+
};
|
144
|
+
this.renderingRegion.addEventListener('pointerup', evt => {
|
145
|
+
pointerEnd(evt);
|
146
|
+
});
|
147
|
+
this.renderingRegion.addEventListener('pointercancel', evt => {
|
148
|
+
pointerEnd(evt);
|
149
|
+
});
|
150
|
+
this.renderingRegion.addEventListener('keydown', evt => {
|
151
|
+
if (this.toolController.dispatchInputEvent({
|
152
|
+
kind: InputEvtType.KeyPressEvent,
|
153
|
+
key: evt.key,
|
154
|
+
})) {
|
155
|
+
evt.preventDefault();
|
156
|
+
}
|
157
|
+
});
|
158
|
+
this.container.addEventListener('wheel', evt => {
|
159
|
+
let delta = Vec3.of(evt.deltaX, evt.deltaY, evt.deltaZ);
|
160
|
+
if (evt.deltaMode === WheelEvent.DOM_DELTA_LINE) {
|
161
|
+
delta = delta.times(15);
|
162
|
+
}
|
163
|
+
else if (evt.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
|
164
|
+
delta = delta.times(100);
|
165
|
+
}
|
166
|
+
if (evt.ctrlKey) {
|
167
|
+
delta = Vec3.of(0, 0, evt.deltaY);
|
168
|
+
}
|
169
|
+
const pos = Vec2.of(evt.clientX, evt.clientY);
|
170
|
+
if (this.toolController.dispatchInputEvent({
|
171
|
+
kind: InputEvtType.WheelEvt,
|
172
|
+
delta,
|
173
|
+
screenPos: pos,
|
174
|
+
})) {
|
175
|
+
evt.preventDefault();
|
176
|
+
return true;
|
177
|
+
}
|
178
|
+
return false;
|
179
|
+
});
|
180
|
+
this.notifier.on(EditorEventType.DisplayResized, _event => {
|
181
|
+
this.viewport.updateScreenSize(Vec2.of(this.display.width, this.display.height));
|
182
|
+
});
|
183
|
+
window.addEventListener('resize', () => {
|
184
|
+
this.notifier.dispatch(EditorEventType.DisplayResized, {
|
185
|
+
kind: EditorEventType.DisplayResized,
|
186
|
+
newSize: Vec2.of(this.display.width, this.display.height),
|
187
|
+
});
|
188
|
+
this.queueRerender();
|
189
|
+
});
|
190
|
+
}
|
191
|
+
dispatch(command, addToHistory = true) {
|
192
|
+
if (addToHistory) {
|
193
|
+
// .push applies [command] to this
|
194
|
+
this.history.push(command);
|
195
|
+
}
|
196
|
+
else {
|
197
|
+
command.apply(this);
|
198
|
+
}
|
199
|
+
this.announceForAccessibility(command.description(this.localization));
|
200
|
+
}
|
201
|
+
// Apply a large transformation in chunks.
|
202
|
+
// If [apply] is false, the commands are unapplied.
|
203
|
+
// Triggers a re-render after each [updateChunkSize]-sized group of commands
|
204
|
+
// has been applied.
|
205
|
+
asyncApplyOrUnapplyCommands(commands, apply, updateChunkSize) {
|
206
|
+
return __awaiter(this, void 0, void 0, function* () {
|
207
|
+
for (let i = 0; i < commands.length; i += updateChunkSize) {
|
208
|
+
this.showLoadingWarning(i / commands.length);
|
209
|
+
for (let j = i; j < commands.length && j < i + updateChunkSize; j++) {
|
210
|
+
const cmd = commands[j];
|
211
|
+
if (apply) {
|
212
|
+
cmd.apply(this);
|
213
|
+
}
|
214
|
+
else {
|
215
|
+
cmd.unapply(this);
|
216
|
+
}
|
217
|
+
}
|
218
|
+
// Re-render to show progress, but only if we're not done.
|
219
|
+
if (i + updateChunkSize < commands.length) {
|
220
|
+
yield new Promise(resolve => {
|
221
|
+
this.rerender();
|
222
|
+
requestAnimationFrame(resolve);
|
223
|
+
});
|
224
|
+
}
|
225
|
+
}
|
226
|
+
this.hideLoadingWarning();
|
227
|
+
});
|
228
|
+
}
|
229
|
+
asyncApplyCommands(commands, chunkSize) {
|
230
|
+
return this.asyncApplyOrUnapplyCommands(commands, true, chunkSize);
|
231
|
+
}
|
232
|
+
asyncUnapplyCommands(commands, chunkSize) {
|
233
|
+
return this.asyncApplyOrUnapplyCommands(commands, false, chunkSize);
|
234
|
+
}
|
235
|
+
queueRerender() {
|
236
|
+
if (!this.rerenderQueued) {
|
237
|
+
this.rerenderQueued = true;
|
238
|
+
requestAnimationFrame(() => {
|
239
|
+
this.rerender();
|
240
|
+
this.rerenderQueued = false;
|
241
|
+
});
|
242
|
+
}
|
243
|
+
}
|
244
|
+
rerender(showImageBounds = true) {
|
245
|
+
this.display.startRerender();
|
246
|
+
// Draw a rectangle around the region that will be visible on save
|
247
|
+
const renderer = this.display.getDryInkRenderer();
|
248
|
+
if (showImageBounds) {
|
249
|
+
const exportRectFill = { fill: Color4.fromHex('#44444455') };
|
250
|
+
const exportRectStrokeWidth = 12;
|
251
|
+
renderer.drawRect(this.importExportViewport.visibleRect, exportRectStrokeWidth, exportRectFill);
|
252
|
+
}
|
253
|
+
this.image.render(renderer, this.viewport);
|
254
|
+
this.rerenderQueued = false;
|
255
|
+
}
|
256
|
+
drawWetInk(...path) {
|
257
|
+
for (const part of path) {
|
258
|
+
this.display.getWetInkRenderer().drawPath(part);
|
259
|
+
}
|
260
|
+
}
|
261
|
+
clearWetInk() {
|
262
|
+
this.display.getWetInkRenderer().clear();
|
263
|
+
}
|
264
|
+
createHTMLOverlay(overlay) {
|
265
|
+
overlay.classList.add('overlay');
|
266
|
+
this.container.appendChild(overlay);
|
267
|
+
return {
|
268
|
+
remove: () => overlay.remove(),
|
269
|
+
};
|
270
|
+
}
|
271
|
+
addStyleSheet(content) {
|
272
|
+
const styleSheet = document.createElement('style');
|
273
|
+
styleSheet.innerText = content;
|
274
|
+
this.container.appendChild(styleSheet);
|
275
|
+
return styleSheet;
|
276
|
+
}
|
277
|
+
// Dispatch a pen event to the currently selected tool.
|
278
|
+
// Intented for unit tests.
|
279
|
+
sendPenEvent(eventType, point, allPointers) {
|
280
|
+
const mainPointer = Pointer.ofCanvasPoint(point, eventType !== InputEvtType.PointerUpEvt, this.viewport);
|
281
|
+
this.toolController.dispatchInputEvent({
|
282
|
+
kind: eventType,
|
283
|
+
allPointers: allPointers !== null && allPointers !== void 0 ? allPointers : [
|
284
|
+
mainPointer,
|
285
|
+
],
|
286
|
+
current: mainPointer,
|
287
|
+
});
|
288
|
+
}
|
289
|
+
toSVG() {
|
290
|
+
const importExportViewport = this.importExportViewport;
|
291
|
+
const svgNameSpace = 'http://www.w3.org/2000/svg';
|
292
|
+
const result = document.createElementNS(svgNameSpace, 'svg');
|
293
|
+
const renderer = new SVGRenderer(result, importExportViewport);
|
294
|
+
const origTransform = importExportViewport.canvasToScreenTransform;
|
295
|
+
// Reset the transform to ensure that (0, 0) is (0, 0)
|
296
|
+
importExportViewport.resetTransform(Mat33.identity);
|
297
|
+
// Render **all** elements.
|
298
|
+
this.image.renderAll(renderer);
|
299
|
+
importExportViewport.resetTransform(origTransform);
|
300
|
+
// Just show the main region
|
301
|
+
const rect = importExportViewport.visibleRect;
|
302
|
+
result.setAttribute('viewBox', `${rect.x} ${rect.y} ${rect.w} ${rect.h}`);
|
303
|
+
result.setAttribute('width', `${rect.w}`);
|
304
|
+
result.setAttribute('height', `${rect.h}`);
|
305
|
+
// Ensure the image can be identified as an SVG if downloaded.
|
306
|
+
// See https://jwatt.org/svg/authoring/
|
307
|
+
result.setAttribute('version', '1.1');
|
308
|
+
result.setAttribute('baseProfile', 'full');
|
309
|
+
result.setAttribute('xmlns', svgNameSpace);
|
310
|
+
return result;
|
311
|
+
}
|
312
|
+
loadFrom(loader) {
|
313
|
+
return __awaiter(this, void 0, void 0, function* () {
|
314
|
+
this.showLoadingWarning(0);
|
315
|
+
const imageRect = yield loader.start((component) => {
|
316
|
+
(new EditorImage.AddElementCommand(component)).apply(this);
|
317
|
+
}, (countProcessed, totalToProcess) => {
|
318
|
+
if (countProcessed % 100 === 0) {
|
319
|
+
this.showLoadingWarning(countProcessed / totalToProcess);
|
320
|
+
this.rerender(false);
|
321
|
+
return new Promise(resolve => {
|
322
|
+
requestAnimationFrame(() => resolve());
|
323
|
+
});
|
324
|
+
}
|
325
|
+
return null;
|
326
|
+
});
|
327
|
+
this.hideLoadingWarning();
|
328
|
+
this.setImportExportRect(imageRect).apply(this);
|
329
|
+
});
|
330
|
+
}
|
331
|
+
// Returns the size of the visible region of the output SVG
|
332
|
+
getImportExportRect() {
|
333
|
+
return this.importExportViewport.visibleRect;
|
334
|
+
}
|
335
|
+
// Resize the output SVG
|
336
|
+
setImportExportRect(imageRect) {
|
337
|
+
const origSize = this.importExportViewport.visibleRect.size;
|
338
|
+
const origTransform = this.importExportViewport.canvasToScreenTransform;
|
339
|
+
return {
|
340
|
+
apply(editor) {
|
341
|
+
const viewport = editor.importExportViewport;
|
342
|
+
viewport.updateScreenSize(imageRect.size);
|
343
|
+
viewport.resetTransform(Mat33.translation(imageRect.topLeft.times(-1)));
|
344
|
+
editor.queueRerender();
|
345
|
+
},
|
346
|
+
unapply(editor) {
|
347
|
+
const viewport = editor.importExportViewport;
|
348
|
+
viewport.updateScreenSize(origSize);
|
349
|
+
viewport.resetTransform(origTransform);
|
350
|
+
editor.queueRerender();
|
351
|
+
},
|
352
|
+
description(localizationTable) {
|
353
|
+
return localizationTable.resizeOutputCommand(imageRect);
|
354
|
+
},
|
355
|
+
};
|
356
|
+
}
|
357
|
+
// Alias for loadFrom(SVGLoader.fromString).
|
358
|
+
// This is particularly useful when accessing a bundled version of the editor.
|
359
|
+
loadFromSVG(svgData) {
|
360
|
+
return __awaiter(this, void 0, void 0, function* () {
|
361
|
+
const loader = SVGLoader.fromString(svgData);
|
362
|
+
yield this.loadFrom(loader);
|
363
|
+
});
|
364
|
+
}
|
365
|
+
}
|
366
|
+
export default Editor;
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import Editor from './Editor';
|
2
|
+
import AbstractRenderer from './rendering/AbstractRenderer';
|
3
|
+
import Viewport from './Viewport';
|
4
|
+
import AbstractComponent from './components/AbstractComponent';
|
5
|
+
import Rect2 from './geometry/Rect2';
|
6
|
+
import { EditorLocalization } from './localization';
|
7
|
+
export default class EditorImage {
|
8
|
+
private root;
|
9
|
+
constructor();
|
10
|
+
private addElement;
|
11
|
+
findParent(elem: AbstractComponent): ImageNode | null;
|
12
|
+
private sortLeaves;
|
13
|
+
render(renderer: AbstractRenderer, viewport: Viewport, minFraction?: number): void;
|
14
|
+
renderAll(renderer: AbstractRenderer): void;
|
15
|
+
getElementsIntersectingRegion(region: Rect2): AbstractComponent[];
|
16
|
+
static AddElementCommand: {
|
17
|
+
new (element: AbstractComponent, applyByFlattening?: boolean): {
|
18
|
+
readonly "__#1@#element": AbstractComponent;
|
19
|
+
"__#1@#applyByFlattening": boolean;
|
20
|
+
apply(editor: Editor): void;
|
21
|
+
unapply(editor: Editor): void;
|
22
|
+
description(localization: EditorLocalization): string;
|
23
|
+
};
|
24
|
+
};
|
25
|
+
}
|
26
|
+
export declare type AddElementCommand = typeof EditorImage.AddElementCommand.prototype;
|
27
|
+
export declare class ImageNode {
|
28
|
+
private parent;
|
29
|
+
private content;
|
30
|
+
private bbox;
|
31
|
+
private children;
|
32
|
+
private targetChildCount;
|
33
|
+
constructor(parent?: ImageNode | null);
|
34
|
+
getContent(): AbstractComponent | null;
|
35
|
+
getParent(): ImageNode | null;
|
36
|
+
private getChildrenInRegion;
|
37
|
+
getLeavesInRegion(region: Rect2, minFractionOfRegion?: number): ImageNode[];
|
38
|
+
getLeaves(): ImageNode[];
|
39
|
+
addLeaf(leaf: AbstractComponent): ImageNode;
|
40
|
+
getBBox(): Rect2;
|
41
|
+
recomputeBBox(bubbleUp: boolean): void;
|
42
|
+
private rebalance;
|
43
|
+
remove(): void;
|
44
|
+
}
|