js-draw 0.15.1 → 0.16.0
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/.github/ISSUE_TEMPLATE/translation.yml +56 -0
- package/CHANGELOG.md +13 -0
- package/dist/bundle.js +1 -1
- package/dist/src/Color4.d.ts +1 -1
- package/dist/src/Color4.js +5 -1
- package/dist/src/Editor.d.ts +11 -2
- package/dist/src/Editor.js +66 -33
- package/dist/src/EditorImage.d.ts +28 -3
- package/dist/src/EditorImage.js +109 -18
- package/dist/src/EventDispatcher.d.ts +4 -3
- package/dist/src/SVGLoader.d.ts +1 -0
- package/dist/src/SVGLoader.js +15 -1
- package/dist/src/Viewport.d.ts +8 -3
- package/dist/src/Viewport.js +15 -8
- package/dist/src/components/AbstractComponent.d.ts +6 -1
- package/dist/src/components/AbstractComponent.js +15 -2
- package/dist/src/components/ImageBackground.d.ts +42 -0
- package/dist/src/components/ImageBackground.js +139 -0
- package/dist/src/components/ImageComponent.js +2 -0
- package/dist/src/components/builders/ArrowBuilder.d.ts +3 -1
- package/dist/src/components/builders/ArrowBuilder.js +43 -40
- package/dist/src/components/builders/LineBuilder.d.ts +3 -1
- package/dist/src/components/builders/LineBuilder.js +25 -28
- package/dist/src/components/builders/RectangleBuilder.js +1 -1
- package/dist/src/components/lib.d.ts +2 -1
- package/dist/src/components/lib.js +2 -1
- package/dist/src/components/localization.d.ts +2 -0
- package/dist/src/components/localization.js +2 -0
- package/dist/src/localizations/es.js +1 -1
- package/dist/src/math/Mat33.js +43 -5
- package/dist/src/math/Path.d.ts +5 -0
- package/dist/src/math/Path.js +80 -28
- package/dist/src/math/Vec3.js +1 -1
- package/dist/src/rendering/Display.js +1 -1
- package/dist/src/rendering/renderers/AbstractRenderer.d.ts +13 -1
- package/dist/src/rendering/renderers/AbstractRenderer.js +18 -3
- package/dist/src/rendering/renderers/CanvasRenderer.d.ts +2 -1
- package/dist/src/rendering/renderers/CanvasRenderer.js +12 -2
- package/dist/src/rendering/renderers/SVGRenderer.d.ts +1 -1
- package/dist/src/rendering/renderers/SVGRenderer.js +8 -2
- package/dist/src/testing/sendTouchEvent.d.ts +6 -0
- package/dist/src/testing/sendTouchEvent.js +26 -0
- package/dist/src/toolbar/HTMLToolbar.d.ts +25 -2
- package/dist/src/toolbar/HTMLToolbar.js +127 -15
- package/dist/src/toolbar/IconProvider.d.ts +2 -0
- package/dist/src/toolbar/IconProvider.js +45 -2
- package/dist/src/toolbar/localization.d.ts +5 -0
- package/dist/src/toolbar/localization.js +5 -0
- package/dist/src/toolbar/widgets/ActionButtonWidget.d.ts +3 -1
- package/dist/src/toolbar/widgets/ActionButtonWidget.js +5 -1
- package/dist/src/toolbar/widgets/BaseToolWidget.d.ts +1 -1
- package/dist/src/toolbar/widgets/BaseToolWidget.js +2 -1
- package/dist/src/toolbar/widgets/BaseWidget.d.ts +7 -2
- package/dist/src/toolbar/widgets/BaseWidget.js +23 -1
- package/dist/src/toolbar/widgets/DocumentPropertiesWidget.d.ts +19 -0
- package/dist/src/toolbar/widgets/DocumentPropertiesWidget.js +135 -0
- package/dist/src/toolbar/widgets/HandToolWidget.js +1 -1
- package/dist/src/toolbar/widgets/OverflowWidget.d.ts +25 -0
- package/dist/src/toolbar/widgets/OverflowWidget.js +65 -0
- package/dist/src/toolbar/widgets/lib.d.ts +1 -0
- package/dist/src/toolbar/widgets/lib.js +1 -0
- package/dist/src/tools/Eraser.js +5 -2
- package/dist/src/tools/PanZoom.js +12 -0
- package/dist/src/tools/PasteHandler.js +2 -2
- package/dist/src/tools/SelectionTool/Selection.d.ts +2 -1
- package/dist/src/tools/SelectionTool/Selection.js +3 -2
- package/dist/src/tools/SelectionTool/SelectionTool.js +5 -1
- package/package.json +1 -1
- package/src/Color4.test.ts +6 -0
- package/src/Color4.ts +6 -1
- package/src/Editor.loadFrom.test.ts +24 -0
- package/src/Editor.ts +73 -39
- package/src/EditorImage.ts +136 -21
- package/src/EventDispatcher.ts +4 -1
- package/src/SVGLoader.ts +12 -1
- package/src/Viewport.ts +17 -7
- package/src/components/AbstractComponent.ts +17 -1
- package/src/components/ImageBackground.test.ts +35 -0
- package/src/components/ImageBackground.ts +176 -0
- package/src/components/ImageComponent.ts +2 -0
- package/src/components/builders/ArrowBuilder.ts +44 -41
- package/src/components/builders/LineBuilder.ts +26 -28
- package/src/components/builders/RectangleBuilder.ts +1 -1
- package/src/components/lib.ts +2 -0
- package/src/components/localization.ts +4 -0
- package/src/localizations/es.ts +8 -0
- package/src/math/Mat33.test.ts +47 -3
- package/src/math/Mat33.ts +47 -5
- package/src/math/Path.ts +87 -28
- package/src/math/Vec3.test.ts +4 -0
- package/src/math/Vec3.ts +1 -1
- package/src/rendering/Display.ts +1 -1
- package/src/rendering/renderers/AbstractRenderer.ts +20 -3
- package/src/rendering/renderers/CanvasRenderer.ts +17 -4
- package/src/rendering/renderers/DummyRenderer.test.ts +1 -2
- package/src/rendering/renderers/SVGRenderer.ts +8 -1
- package/src/testing/sendTouchEvent.ts +43 -0
- package/src/toolbar/HTMLToolbar.ts +164 -16
- package/src/toolbar/IconProvider.ts +47 -2
- package/src/toolbar/localization.ts +10 -0
- package/src/toolbar/toolbar.css +2 -0
- package/src/toolbar/widgets/ActionButtonWidget.ts +5 -0
- package/src/toolbar/widgets/BaseToolWidget.ts +3 -1
- package/src/toolbar/widgets/BaseWidget.ts +34 -2
- package/src/toolbar/widgets/DocumentPropertiesWidget.ts +185 -0
- package/src/toolbar/widgets/HandToolWidget.ts +1 -1
- package/src/toolbar/widgets/OverflowWidget.css +9 -0
- package/src/toolbar/widgets/OverflowWidget.ts +83 -0
- package/src/toolbar/widgets/lib.ts +2 -1
- package/src/tools/Eraser.test.ts +24 -1
- package/src/tools/Eraser.ts +6 -2
- package/src/tools/PanZoom.test.ts +267 -23
- package/src/tools/PanZoom.ts +15 -1
- package/src/tools/PasteHandler.ts +3 -2
- package/src/tools/SelectionTool/Selection.ts +3 -2
- package/src/tools/SelectionTool/SelectionTool.ts +6 -1
- package/src/types.ts +1 -0
package/dist/src/math/Path.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import { Bezier } from 'bezier-js';
|
2
2
|
import { toRoundedString, toStringOfSamePrecision } from './rounding';
|
3
3
|
import LineSegment2 from './LineSegment2';
|
4
|
+
import Mat33 from './Mat33';
|
4
5
|
import Rect2 from './Rect2';
|
5
6
|
import { Vec2 } from './Vec2';
|
6
7
|
export var PathCommandType;
|
@@ -143,38 +144,39 @@ export default class Path {
|
|
143
144
|
}
|
144
145
|
return result;
|
145
146
|
}
|
147
|
+
static mapPathCommand(part, mapping) {
|
148
|
+
switch (part.kind) {
|
149
|
+
case PathCommandType.MoveTo:
|
150
|
+
case PathCommandType.LineTo:
|
151
|
+
return {
|
152
|
+
kind: part.kind,
|
153
|
+
point: mapping(part.point),
|
154
|
+
};
|
155
|
+
break;
|
156
|
+
case PathCommandType.CubicBezierTo:
|
157
|
+
return {
|
158
|
+
kind: part.kind,
|
159
|
+
controlPoint1: mapping(part.controlPoint1),
|
160
|
+
controlPoint2: mapping(part.controlPoint2),
|
161
|
+
endPoint: mapping(part.endPoint),
|
162
|
+
};
|
163
|
+
break;
|
164
|
+
case PathCommandType.QuadraticBezierTo:
|
165
|
+
return {
|
166
|
+
kind: part.kind,
|
167
|
+
controlPoint: mapping(part.controlPoint),
|
168
|
+
endPoint: mapping(part.endPoint),
|
169
|
+
};
|
170
|
+
break;
|
171
|
+
}
|
172
|
+
const exhaustivenessCheck = part;
|
173
|
+
return exhaustivenessCheck;
|
174
|
+
}
|
146
175
|
mapPoints(mapping) {
|
147
176
|
const startPoint = mapping(this.startPoint);
|
148
177
|
const newParts = [];
|
149
|
-
let exhaustivenessCheck;
|
150
178
|
for (const part of this.parts) {
|
151
|
-
|
152
|
-
case PathCommandType.MoveTo:
|
153
|
-
case PathCommandType.LineTo:
|
154
|
-
newParts.push({
|
155
|
-
kind: part.kind,
|
156
|
-
point: mapping(part.point),
|
157
|
-
});
|
158
|
-
break;
|
159
|
-
case PathCommandType.CubicBezierTo:
|
160
|
-
newParts.push({
|
161
|
-
kind: part.kind,
|
162
|
-
controlPoint1: mapping(part.controlPoint1),
|
163
|
-
controlPoint2: mapping(part.controlPoint2),
|
164
|
-
endPoint: mapping(part.endPoint),
|
165
|
-
});
|
166
|
-
break;
|
167
|
-
case PathCommandType.QuadraticBezierTo:
|
168
|
-
newParts.push({
|
169
|
-
kind: part.kind,
|
170
|
-
controlPoint: mapping(part.controlPoint),
|
171
|
-
endPoint: mapping(part.endPoint),
|
172
|
-
});
|
173
|
-
break;
|
174
|
-
default:
|
175
|
-
exhaustivenessCheck = part;
|
176
|
-
return exhaustivenessCheck;
|
177
|
-
}
|
179
|
+
newParts.push(Path.mapPathCommand(part, mapping));
|
178
180
|
}
|
179
181
|
return new Path(startPoint, newParts);
|
180
182
|
}
|
@@ -329,6 +331,56 @@ export default class Path {
|
|
329
331
|
path: this,
|
330
332
|
};
|
331
333
|
}
|
334
|
+
/**
|
335
|
+
* @returns a Path that, when rendered, looks roughly equivalent to the given path.
|
336
|
+
*/
|
337
|
+
static visualEquivalent(renderablePath, visibleRect) {
|
338
|
+
var _a, _b;
|
339
|
+
const path = Path.fromRenderable(renderablePath);
|
340
|
+
const strokeWidth = (_b = (_a = renderablePath.style.stroke) === null || _a === void 0 ? void 0 : _a.width) !== null && _b !== void 0 ? _b : 0;
|
341
|
+
const onlyStroked = strokeWidth > 0 && renderablePath.style.fill.a === 0;
|
342
|
+
// Scale the expanded rect --- the visual equivalent is only close for huge strokes.
|
343
|
+
const expandedRect = visibleRect.grownBy(strokeWidth)
|
344
|
+
.transformedBoundingBox(Mat33.scaling2D(2, visibleRect.center));
|
345
|
+
// TODO: Handle simplifying very small paths.
|
346
|
+
if (expandedRect.containsRect(path.bbox.grownBy(strokeWidth))) {
|
347
|
+
return renderablePath;
|
348
|
+
}
|
349
|
+
const parts = [];
|
350
|
+
let startPoint = path.startPoint;
|
351
|
+
for (const part of path.parts) {
|
352
|
+
const partBBox = Path.computeBBoxForSegment(startPoint, part).grownBy(strokeWidth);
|
353
|
+
let endPoint;
|
354
|
+
if (part.kind === PathCommandType.LineTo || part.kind === PathCommandType.MoveTo) {
|
355
|
+
endPoint = part.point;
|
356
|
+
}
|
357
|
+
else {
|
358
|
+
endPoint = part.endPoint;
|
359
|
+
}
|
360
|
+
const intersectsVisible = partBBox.intersects(visibleRect);
|
361
|
+
if (intersectsVisible) {
|
362
|
+
// TODO: Can we trim parts of paths that intersect the visible rectangle?
|
363
|
+
parts.push(part);
|
364
|
+
}
|
365
|
+
else if (onlyStroked || part.kind === PathCommandType.MoveTo) {
|
366
|
+
// We're stroking (not filling) and the path doesn't intersect the bounding box.
|
367
|
+
// Don't draw it, but preserve the endpoints.
|
368
|
+
parts.push({
|
369
|
+
kind: PathCommandType.MoveTo,
|
370
|
+
point: endPoint,
|
371
|
+
});
|
372
|
+
}
|
373
|
+
else {
|
374
|
+
// Otherwise, we may be filling. Try to roughly preserve the filled region.
|
375
|
+
parts.push({
|
376
|
+
kind: PathCommandType.LineTo,
|
377
|
+
point: endPoint,
|
378
|
+
});
|
379
|
+
}
|
380
|
+
startPoint = endPoint;
|
381
|
+
}
|
382
|
+
return new Path(path.startPoint, parts).toRenderable(renderablePath.style);
|
383
|
+
}
|
332
384
|
toString(useNonAbsCommands) {
|
333
385
|
if (this.cachedStringVersion) {
|
334
386
|
return this.cachedStringVersion;
|
package/dist/src/math/Vec3.js
CHANGED
@@ -65,7 +65,7 @@ export default class Vec3 {
|
|
65
65
|
return Vec3.of(this.x + v.x, this.y + v.y, this.z + v.z);
|
66
66
|
}
|
67
67
|
minus(v) {
|
68
|
-
return this.
|
68
|
+
return Vec3.of(this.x - v.x, this.y - v.y, this.z - v.z);
|
69
69
|
}
|
70
70
|
dot(other) {
|
71
71
|
return this.x * other.x + this.y * other.y + this.z * other.z;
|
@@ -70,7 +70,7 @@ export default class Display {
|
|
70
70
|
},
|
71
71
|
blockResolution: cacheBlockResolution,
|
72
72
|
cacheSize: 600 * 600 * 4 * 90,
|
73
|
-
maxScale: 1.
|
73
|
+
maxScale: 1.3,
|
74
74
|
// Require about 20 strokes with 4 parts each to cache an image in one of the
|
75
75
|
// parts of the cache grid.
|
76
76
|
minProportionalRenderTimePerCache: 20 * 4,
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import Color4 from '../../Color4';
|
1
2
|
import { LoadSaveDataTable } from '../../components/AbstractComponent';
|
2
3
|
import Mat33 from '../../math/Mat33';
|
3
4
|
import Path, { PathCommand } from '../../math/Path';
|
@@ -41,8 +42,19 @@ export default abstract class AbstractRenderer {
|
|
41
42
|
private flushPath;
|
42
43
|
drawPath(path: RenderablePathSpec): void;
|
43
44
|
drawRect(rect: Rect2, lineWidth: number, lineFill: RenderingStyle): void;
|
45
|
+
fillRect(rect: Rect2, fill: Color4): void;
|
44
46
|
startObject(_boundingBox: Rect2, _clip?: boolean): void;
|
45
|
-
|
47
|
+
/**
|
48
|
+
* Notes the end of an object.
|
49
|
+
* @param _loaderData - a map from strings to JSON-ifyable objects
|
50
|
+
* and contains properties attached to the object by whatever loader loaded the image. This
|
51
|
+
* is used to preserve attributes not supported by js-draw when loading/saving an image.
|
52
|
+
* Renderers may ignore this.
|
53
|
+
*
|
54
|
+
* @param _objectTags - a list of labels (e.g. `className`s) to be attached to the object.
|
55
|
+
* Renderers may ignore this.
|
56
|
+
*/
|
57
|
+
endObject(_loaderData?: LoadSaveDataTable, _objectTags?: string[]): void;
|
46
58
|
protected getNestingLevel(): number;
|
47
59
|
abstract drawPoints(...points: Point2[]): void;
|
48
60
|
canRenderFromWithoutDataLoss(_other: AbstractRenderer): boolean;
|
@@ -64,19 +64,34 @@ export default class AbstractRenderer {
|
|
64
64
|
this.currentPaths.push(path);
|
65
65
|
}
|
66
66
|
}
|
67
|
-
//
|
67
|
+
// Strokes a rectangle. Boundary lines have width [lineWidth] and are filled with [lineFill].
|
68
68
|
// This is equivalent to `drawPath(Path.fromRect(...).toRenderable(...))`.
|
69
69
|
drawRect(rect, lineWidth, lineFill) {
|
70
70
|
const path = Path.fromRect(rect, lineWidth);
|
71
71
|
this.drawPath(path.toRenderable(lineFill));
|
72
72
|
}
|
73
|
-
//
|
73
|
+
// Fills a rectangle.
|
74
|
+
fillRect(rect, fill) {
|
75
|
+
const path = Path.fromRect(rect);
|
76
|
+
this.drawPath(path.toRenderable({ fill }));
|
77
|
+
}
|
78
|
+
// Note the start of an object with the given bounding box.
|
74
79
|
// Renderers are not required to support [clip]
|
75
80
|
startObject(_boundingBox, _clip) {
|
76
81
|
this.currentPaths = [];
|
77
82
|
this.objectLevel++;
|
78
83
|
}
|
79
|
-
|
84
|
+
/**
|
85
|
+
* Notes the end of an object.
|
86
|
+
* @param _loaderData - a map from strings to JSON-ifyable objects
|
87
|
+
* and contains properties attached to the object by whatever loader loaded the image. This
|
88
|
+
* is used to preserve attributes not supported by js-draw when loading/saving an image.
|
89
|
+
* Renderers may ignore this.
|
90
|
+
*
|
91
|
+
* @param _objectTags - a list of labels (e.g. `className`s) to be attached to the object.
|
92
|
+
* Renderers may ignore this.
|
93
|
+
*/
|
94
|
+
endObject(_loaderData, _objectTags) {
|
80
95
|
// Render the paths all at once
|
81
96
|
this.flushPath();
|
82
97
|
this.currentPaths = null;
|
@@ -10,6 +10,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
10
10
|
private ctx;
|
11
11
|
private ignoreObjectsAboveLevel;
|
12
12
|
private ignoringObject;
|
13
|
+
private currentObjectBBox;
|
13
14
|
private minSquareCurveApproxDist;
|
14
15
|
private minRenderSizeAnyDimen;
|
15
16
|
private minRenderSizeBothDimens;
|
@@ -30,7 +31,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
30
31
|
drawText(text: string, transform: Mat33, style: TextStyle): void;
|
31
32
|
drawImage(image: RenderableImage): void;
|
32
33
|
private clipLevels;
|
33
|
-
startObject(boundingBox: Rect2, clip
|
34
|
+
startObject(boundingBox: Rect2, clip?: boolean): void;
|
34
35
|
endObject(): void;
|
35
36
|
drawPoints(...points: Point2[]): void;
|
36
37
|
isTooSmallToRender(rect: Rect2): boolean;
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import Color4 from '../../Color4';
|
2
2
|
import TextComponent from '../../components/TextComponent';
|
3
|
+
import Path from '../../math/Path';
|
3
4
|
import { Vec2 } from '../../math/Vec2';
|
4
5
|
import AbstractRenderer from './AbstractRenderer';
|
5
6
|
export default class CanvasRenderer extends AbstractRenderer {
|
@@ -8,6 +9,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
8
9
|
this.ctx = ctx;
|
9
10
|
this.ignoreObjectsAboveLevel = null;
|
10
11
|
this.ignoringObject = false;
|
12
|
+
this.currentObjectBBox = null;
|
11
13
|
this.clipLevels = [];
|
12
14
|
this.setDraftMode(false);
|
13
15
|
}
|
@@ -43,8 +45,8 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
43
45
|
}
|
44
46
|
else {
|
45
47
|
this.minSquareCurveApproxDist = 0.5;
|
46
|
-
this.minRenderSizeBothDimens = 0.
|
47
|
-
this.minRenderSizeAnyDimen = 1e-
|
48
|
+
this.minRenderSizeBothDimens = 0.2;
|
49
|
+
this.minRenderSizeAnyDimen = 1e-6;
|
48
50
|
}
|
49
51
|
}
|
50
52
|
displaySize() {
|
@@ -106,9 +108,15 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
106
108
|
}
|
107
109
|
}
|
108
110
|
drawPath(path) {
|
111
|
+
var _a;
|
109
112
|
if (this.ignoringObject) {
|
110
113
|
return;
|
111
114
|
}
|
115
|
+
// If part of a huge object, it might be worth trimming the path
|
116
|
+
if ((_a = this.currentObjectBBox) === null || _a === void 0 ? void 0 : _a.containsRect(this.getViewport().visibleRect)) {
|
117
|
+
// Try to trim/remove parts of the path outside of the bounding box.
|
118
|
+
path = Path.visualEquivalent(path, this.getViewport().visibleRect);
|
119
|
+
}
|
112
120
|
super.drawPath(path);
|
113
121
|
}
|
114
122
|
drawText(text, transform, style) {
|
@@ -140,6 +148,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
140
148
|
this.ignoringObject = true;
|
141
149
|
}
|
142
150
|
super.startObject(boundingBox);
|
151
|
+
this.currentObjectBBox = boundingBox;
|
143
152
|
if (!this.ignoringObject && clip) {
|
144
153
|
this.clipLevels.push(this.objectLevel);
|
145
154
|
this.ctx.save();
|
@@ -158,6 +167,7 @@ export default class CanvasRenderer extends AbstractRenderer {
|
|
158
167
|
this.clipLevels.pop();
|
159
168
|
}
|
160
169
|
}
|
170
|
+
this.currentObjectBBox = null;
|
161
171
|
super.endObject();
|
162
172
|
// If exiting an object with a too-small-to-draw bounding box,
|
163
173
|
if (this.ignoreObjectsAboveLevel !== null && this.getNestingLevel() <= this.ignoreObjectsAboveLevel) {
|
@@ -28,7 +28,7 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
28
28
|
drawText(text: string, transform: Mat33, style: TextStyle): void;
|
29
29
|
drawImage(image: RenderableImage): void;
|
30
30
|
startObject(boundingBox: Rect2): void;
|
31
|
-
endObject(loaderData?: LoadSaveDataTable): void;
|
31
|
+
endObject(loaderData?: LoadSaveDataTable, elemClassNames?: string[]): void;
|
32
32
|
private unimplementedMessage;
|
33
33
|
protected beginPath(_startPoint: Point2): void;
|
34
34
|
protected endPath(_style: RenderingStyle): void;
|
@@ -215,8 +215,8 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
215
215
|
this.textParentStyle = null;
|
216
216
|
this.objectElems = [];
|
217
217
|
}
|
218
|
-
endObject(loaderData) {
|
219
|
-
var _a;
|
218
|
+
endObject(loaderData, elemClassNames) {
|
219
|
+
var _a, _b;
|
220
220
|
super.endObject(loaderData);
|
221
221
|
// Don't extend paths across objects
|
222
222
|
this.addPathToSVG();
|
@@ -237,6 +237,12 @@ export default class SVGRenderer extends AbstractRenderer {
|
|
237
237
|
}
|
238
238
|
}
|
239
239
|
}
|
240
|
+
// Add class names to the object, if given.
|
241
|
+
if (elemClassNames) {
|
242
|
+
for (const elem of (_b = this.objectElems) !== null && _b !== void 0 ? _b : []) {
|
243
|
+
elem.classList.add(...elemClassNames);
|
244
|
+
}
|
245
|
+
}
|
240
246
|
}
|
241
247
|
// Not implemented -- use drawPath instead.
|
242
248
|
unimplementedMessage() { throw new Error('Not implemenented!'); }
|
@@ -0,0 +1,6 @@
|
|
1
|
+
import Editor from '../Editor';
|
2
|
+
import { Vec2 } from '../math/Vec2';
|
3
|
+
import Pointer from '../Pointer';
|
4
|
+
import { InputEvtType } from '../types';
|
5
|
+
declare const sendTouchEvent: (editor: Editor, eventType: InputEvtType.PointerDownEvt | InputEvtType.PointerMoveEvt | InputEvtType.PointerUpEvt, screenPos: Vec2, allOtherPointers?: Pointer[]) => Pointer;
|
6
|
+
export default sendTouchEvent;
|
@@ -0,0 +1,26 @@
|
|
1
|
+
import Pointer, { PointerDevice } from '../Pointer';
|
2
|
+
import { InputEvtType } from '../types';
|
3
|
+
const sendTouchEvent = (editor, eventType, screenPos, allOtherPointers) => {
|
4
|
+
const canvasPos = editor.viewport.screenToCanvas(screenPos);
|
5
|
+
let ptrId = 0;
|
6
|
+
let maxPtrId = 0;
|
7
|
+
// Get a unique ID for the main pointer
|
8
|
+
// (try to use id=0, but don't use it if it's already in use).
|
9
|
+
for (const pointer of allOtherPointers !== null && allOtherPointers !== void 0 ? allOtherPointers : []) {
|
10
|
+
maxPtrId = Math.max(pointer.id, maxPtrId);
|
11
|
+
if (pointer.id === ptrId) {
|
12
|
+
ptrId = maxPtrId + 1;
|
13
|
+
}
|
14
|
+
}
|
15
|
+
const mainPointer = Pointer.ofCanvasPoint(canvasPos, eventType !== InputEvtType.PointerUpEvt, editor.viewport, ptrId, PointerDevice.Touch);
|
16
|
+
editor.toolController.dispatchInputEvent({
|
17
|
+
kind: eventType,
|
18
|
+
allPointers: [
|
19
|
+
...(allOtherPointers !== null && allOtherPointers !== void 0 ? allOtherPointers : []),
|
20
|
+
mainPointer,
|
21
|
+
],
|
22
|
+
current: mainPointer,
|
23
|
+
});
|
24
|
+
return mainPointer;
|
25
|
+
};
|
26
|
+
export default sendTouchEvent;
|
@@ -12,12 +12,19 @@ export default class HTMLToolbar {
|
|
12
12
|
private editor;
|
13
13
|
private localizationTable;
|
14
14
|
private container;
|
15
|
-
private
|
15
|
+
private resizeObserver;
|
16
|
+
private listeners;
|
17
|
+
private widgetsById;
|
18
|
+
private widgetList;
|
19
|
+
private overflowWidget;
|
16
20
|
private static colorisStarted;
|
17
21
|
private updateColoris;
|
18
22
|
/** @internal */
|
19
23
|
constructor(editor: Editor, parent: HTMLElement, localizationTable?: ToolbarLocalization);
|
20
24
|
setupColorPickers(): void;
|
25
|
+
private reLayoutQueued;
|
26
|
+
private queueReLayout;
|
27
|
+
private reLayout;
|
21
28
|
/**
|
22
29
|
* Adds an `ActionButtonWidget` or `BaseToolWidget`. The widget should not have already have a parent
|
23
30
|
* (i.e. its `addTo` method should not have been called).
|
@@ -63,17 +70,33 @@ export default class HTMLToolbar {
|
|
63
70
|
*
|
64
71
|
* @return The added button.
|
65
72
|
*/
|
66
|
-
addActionButton(title: string | ActionButtonIcon, command: () => void): BaseWidget;
|
73
|
+
addActionButton(title: string | ActionButtonIcon, command: () => void, mustBeToplevel?: boolean): BaseWidget;
|
67
74
|
addUndoRedoButtons(): void;
|
68
75
|
addDefaultToolWidgets(): void;
|
69
76
|
addDefaultActionButtons(): void;
|
77
|
+
/**
|
78
|
+
* Adds a widget that toggles the overflow menu. Call `addOverflowWidget` to ensure
|
79
|
+
* that this widget is in the correct space (if shown).
|
80
|
+
*
|
81
|
+
* @example
|
82
|
+
* ```ts
|
83
|
+
* toolbar.addDefaultToolWidgets();
|
84
|
+
* toolbar.addOverflowWidget();
|
85
|
+
* toolbar.addDefaultActionButtons();
|
86
|
+
* ```
|
87
|
+
* shows the overflow widget between the default tool widgets and the default action buttons,
|
88
|
+
* if shown.
|
89
|
+
*/
|
90
|
+
addOverflowWidget(): void;
|
70
91
|
/**
|
71
92
|
* Adds both the default tool widgets and action buttons. Equivalent to
|
72
93
|
* ```ts
|
73
94
|
* toolbar.addDefaultToolWidgets();
|
95
|
+
* toolbar.addOverflowWidget();
|
74
96
|
* toolbar.addDefaultActionButtons();
|
75
97
|
* ```
|
76
98
|
*/
|
77
99
|
addDefaults(): void;
|
100
|
+
remove(): void;
|
78
101
|
}
|
79
102
|
export {};
|
@@ -14,14 +14,21 @@ import TextToolWidget from './widgets/TextToolWidget';
|
|
14
14
|
import HandToolWidget from './widgets/HandToolWidget';
|
15
15
|
import ActionButtonWidget from './widgets/ActionButtonWidget';
|
16
16
|
import InsertImageWidget from './widgets/InsertImageWidget';
|
17
|
+
import DocumentPropertiesWidget from './widgets/DocumentPropertiesWidget';
|
18
|
+
import OverflowWidget from './widgets/OverflowWidget';
|
17
19
|
export const toolbarCSSPrefix = 'toolbar-';
|
18
20
|
export default class HTMLToolbar {
|
19
21
|
/** @internal */
|
20
22
|
constructor(editor, parent, localizationTable = defaultToolbarLocalization) {
|
21
23
|
this.editor = editor;
|
22
24
|
this.localizationTable = localizationTable;
|
23
|
-
this.
|
25
|
+
this.listeners = [];
|
26
|
+
this.widgetsById = {};
|
27
|
+
this.widgetList = [];
|
28
|
+
// Widget to toggle overflow menu.
|
29
|
+
this.overflowWidget = null;
|
24
30
|
this.updateColoris = null;
|
31
|
+
this.reLayoutQueued = false;
|
25
32
|
this.container = document.createElement('div');
|
26
33
|
this.container.classList.add(`${toolbarCSSPrefix}root`);
|
27
34
|
this.container.setAttribute('role', 'toolbar');
|
@@ -31,6 +38,15 @@ export default class HTMLToolbar {
|
|
31
38
|
HTMLToolbar.colorisStarted = true;
|
32
39
|
}
|
33
40
|
this.setupColorPickers();
|
41
|
+
if ('ResizeObserver' in window) {
|
42
|
+
this.resizeObserver = new ResizeObserver((_entries) => {
|
43
|
+
this.reLayout();
|
44
|
+
});
|
45
|
+
this.resizeObserver.observe(this.container);
|
46
|
+
}
|
47
|
+
else {
|
48
|
+
console.warn('ResizeObserver not supported. Toolbar will not resize.');
|
49
|
+
}
|
34
50
|
}
|
35
51
|
// @internal
|
36
52
|
setupColorPickers() {
|
@@ -80,20 +96,84 @@ export default class HTMLToolbar {
|
|
80
96
|
initColoris();
|
81
97
|
}
|
82
98
|
};
|
83
|
-
this.editor.notifier.on(EditorEventType.ColorPickerToggled, event => {
|
99
|
+
this.listeners.push(this.editor.notifier.on(EditorEventType.ColorPickerToggled, event => {
|
84
100
|
if (event.kind !== EditorEventType.ColorPickerToggled) {
|
85
101
|
return;
|
86
102
|
}
|
87
103
|
// Show/hide the overlay. Making the overlay visible gives users a surface to click
|
88
104
|
// on that shows/hides the color picker.
|
89
105
|
closePickerOverlay.style.display = event.open ? 'block' : 'none';
|
90
|
-
});
|
106
|
+
}));
|
91
107
|
// Add newly-selected colors to the swatch.
|
92
|
-
this.editor.notifier.on(EditorEventType.ColorPickerColorSelected, event => {
|
108
|
+
this.listeners.push(this.editor.notifier.on(EditorEventType.ColorPickerColorSelected, event => {
|
93
109
|
if (event.kind === EditorEventType.ColorPickerColorSelected) {
|
94
110
|
addColorToSwatch(event.color.toHexString());
|
95
111
|
}
|
96
|
-
});
|
112
|
+
}));
|
113
|
+
}
|
114
|
+
queueReLayout() {
|
115
|
+
if (!this.reLayoutQueued) {
|
116
|
+
this.reLayoutQueued = true;
|
117
|
+
requestAnimationFrame(() => this.reLayout());
|
118
|
+
}
|
119
|
+
}
|
120
|
+
reLayout() {
|
121
|
+
this.reLayoutQueued = false;
|
122
|
+
if (!this.overflowWidget) {
|
123
|
+
return;
|
124
|
+
}
|
125
|
+
const getTotalWidth = (widgetList) => {
|
126
|
+
let totalWidth = 0;
|
127
|
+
for (const widget of widgetList) {
|
128
|
+
if (!widget.isHidden()) {
|
129
|
+
totalWidth += widget.getButtonWidth();
|
130
|
+
}
|
131
|
+
}
|
132
|
+
return totalWidth;
|
133
|
+
};
|
134
|
+
let overflowWidgetsWidth = getTotalWidth(this.overflowWidget.getChildWidgets());
|
135
|
+
let shownWidgetWidth = getTotalWidth(this.widgetList) - overflowWidgetsWidth;
|
136
|
+
let availableWidth = this.container.clientWidth * 0.87;
|
137
|
+
// If on a device that has enough vertical space, allow
|
138
|
+
// showing two rows of buttons.
|
139
|
+
// TODO: Fix magic numbers
|
140
|
+
if (window.innerHeight > availableWidth * 1.75) {
|
141
|
+
availableWidth *= 1.75;
|
142
|
+
}
|
143
|
+
let updatedChildren = false;
|
144
|
+
if (shownWidgetWidth + overflowWidgetsWidth <= availableWidth) {
|
145
|
+
// Move widgets to the main menu.
|
146
|
+
const overflowChildren = this.overflowWidget.clearChildren();
|
147
|
+
for (const child of overflowChildren) {
|
148
|
+
child.addTo(this.container);
|
149
|
+
child.setIsToplevel(true);
|
150
|
+
if (!child.isHidden()) {
|
151
|
+
shownWidgetWidth += child.getButtonWidth();
|
152
|
+
}
|
153
|
+
}
|
154
|
+
this.overflowWidget.setHidden(true);
|
155
|
+
overflowWidgetsWidth = 0;
|
156
|
+
updatedChildren = true;
|
157
|
+
}
|
158
|
+
if (shownWidgetWidth >= availableWidth) {
|
159
|
+
// Move widgets to the overflow menu.
|
160
|
+
this.overflowWidget.setHidden(false);
|
161
|
+
// Start with the rightmost widget, move to the leftmost
|
162
|
+
for (let i = this.widgetList.length - 1; i >= 0 && shownWidgetWidth >= availableWidth; i--) {
|
163
|
+
const child = this.widgetList[i];
|
164
|
+
if (this.overflowWidget.hasAsChild(child)) {
|
165
|
+
continue;
|
166
|
+
}
|
167
|
+
if (child.canBeInOverflowMenu()) {
|
168
|
+
shownWidgetWidth -= child.getButtonWidth();
|
169
|
+
this.overflowWidget.addToOverflow(child);
|
170
|
+
}
|
171
|
+
}
|
172
|
+
updatedChildren = true;
|
173
|
+
}
|
174
|
+
if (updatedChildren) {
|
175
|
+
this.setupColorPickers();
|
176
|
+
}
|
97
177
|
}
|
98
178
|
/**
|
99
179
|
* Adds an `ActionButtonWidget` or `BaseToolWidget`. The widget should not have already have a parent
|
@@ -108,12 +188,17 @@ export default class HTMLToolbar {
|
|
108
188
|
*/
|
109
189
|
addWidget(widget) {
|
110
190
|
// Prevent name collisions
|
111
|
-
const id = widget.getUniqueIdIn(this.
|
191
|
+
const id = widget.getUniqueIdIn(this.widgetsById);
|
112
192
|
// Add the widget
|
113
|
-
this.
|
193
|
+
this.widgetsById[id] = widget;
|
194
|
+
this.widgetList.push(widget);
|
114
195
|
// Add HTML elements.
|
115
|
-
widget.addTo(this.container);
|
196
|
+
const container = widget.addTo(this.container);
|
116
197
|
this.setupColorPickers();
|
198
|
+
// Ensure that the widget gets displayed in the correct
|
199
|
+
// place in the toolbar, even if it's removed and re-added.
|
200
|
+
container.style.order = `${this.widgetList.length}`;
|
201
|
+
this.queueReLayout();
|
117
202
|
}
|
118
203
|
/**
|
119
204
|
* Adds a spacer.
|
@@ -152,8 +237,8 @@ export default class HTMLToolbar {
|
|
152
237
|
}
|
153
238
|
serializeState() {
|
154
239
|
const result = {};
|
155
|
-
for (const widgetId in this.
|
156
|
-
result[widgetId] = this.
|
240
|
+
for (const widgetId in this.widgetsById) {
|
241
|
+
result[widgetId] = this.widgetsById[widgetId].serializeState();
|
157
242
|
}
|
158
243
|
return JSON.stringify(result);
|
159
244
|
}
|
@@ -164,10 +249,10 @@ export default class HTMLToolbar {
|
|
164
249
|
deserializeState(state) {
|
165
250
|
const data = JSON.parse(state);
|
166
251
|
for (const widgetId in data) {
|
167
|
-
if (!(widgetId in this.
|
252
|
+
if (!(widgetId in this.widgetsById)) {
|
168
253
|
console.warn(`Unable to deserialize widget ${widgetId} — no such widget.`);
|
169
254
|
}
|
170
|
-
this.
|
255
|
+
this.widgetsById[widgetId].deserializeFrom(data[widgetId]);
|
171
256
|
}
|
172
257
|
}
|
173
258
|
/**
|
@@ -175,7 +260,7 @@ export default class HTMLToolbar {
|
|
175
260
|
*
|
176
261
|
* @return The added button.
|
177
262
|
*/
|
178
|
-
addActionButton(title, command) {
|
263
|
+
addActionButton(title, command, mustBeToplevel = true) {
|
179
264
|
const titleString = typeof title === 'string' ? title : title.label;
|
180
265
|
const widgetId = 'action-button';
|
181
266
|
const makeIcon = () => {
|
@@ -184,7 +269,7 @@ export default class HTMLToolbar {
|
|
184
269
|
}
|
185
270
|
return title.icon;
|
186
271
|
};
|
187
|
-
const widget = new ActionButtonWidget(this.editor, widgetId, makeIcon, titleString, command, this.editor.localization);
|
272
|
+
const widget = new ActionButtonWidget(this.editor, widgetId, makeIcon, titleString, command, this.editor.localization, mustBeToplevel);
|
188
273
|
this.addWidget(widget);
|
189
274
|
return widget;
|
190
275
|
}
|
@@ -226,25 +311,52 @@ export default class HTMLToolbar {
|
|
226
311
|
for (const tool of toolController.getMatchingTools(TextTool)) {
|
227
312
|
this.addWidget(new TextToolWidget(this.editor, tool, this.localizationTable));
|
228
313
|
}
|
229
|
-
this.addWidget(new InsertImageWidget(this.editor, this.localizationTable));
|
230
314
|
const panZoomTool = toolController.getMatchingTools(PanZoomTool)[0];
|
231
315
|
if (panZoomTool) {
|
232
316
|
this.addWidget(new HandToolWidget(this.editor, panZoomTool, this.localizationTable));
|
233
317
|
}
|
318
|
+
this.addWidget(new InsertImageWidget(this.editor, this.localizationTable));
|
234
319
|
}
|
235
320
|
addDefaultActionButtons() {
|
321
|
+
this.addWidget(new DocumentPropertiesWidget(this.editor, this.localizationTable));
|
236
322
|
this.addUndoRedoButtons();
|
237
323
|
}
|
324
|
+
/**
|
325
|
+
* Adds a widget that toggles the overflow menu. Call `addOverflowWidget` to ensure
|
326
|
+
* that this widget is in the correct space (if shown).
|
327
|
+
*
|
328
|
+
* @example
|
329
|
+
* ```ts
|
330
|
+
* toolbar.addDefaultToolWidgets();
|
331
|
+
* toolbar.addOverflowWidget();
|
332
|
+
* toolbar.addDefaultActionButtons();
|
333
|
+
* ```
|
334
|
+
* shows the overflow widget between the default tool widgets and the default action buttons,
|
335
|
+
* if shown.
|
336
|
+
*/
|
337
|
+
addOverflowWidget() {
|
338
|
+
this.overflowWidget = new OverflowWidget(this.editor, this.localizationTable);
|
339
|
+
this.addWidget(this.overflowWidget);
|
340
|
+
}
|
238
341
|
/**
|
239
342
|
* Adds both the default tool widgets and action buttons. Equivalent to
|
240
343
|
* ```ts
|
241
344
|
* toolbar.addDefaultToolWidgets();
|
345
|
+
* toolbar.addOverflowWidget();
|
242
346
|
* toolbar.addDefaultActionButtons();
|
243
347
|
* ```
|
244
348
|
*/
|
245
349
|
addDefaults() {
|
246
350
|
this.addDefaultToolWidgets();
|
351
|
+
this.addOverflowWidget();
|
247
352
|
this.addDefaultActionButtons();
|
248
353
|
}
|
354
|
+
remove() {
|
355
|
+
this.container.remove();
|
356
|
+
this.resizeObserver.disconnect();
|
357
|
+
for (const listener of this.listeners) {
|
358
|
+
listener.remove();
|
359
|
+
}
|
360
|
+
}
|
249
361
|
}
|
250
362
|
HTMLToolbar.colorisStarted = false;
|