js-draw 0.11.2 → 0.11.3
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/workflows/github-pages.yml +2 -0
- package/CHANGELOG.md +6 -0
- package/dist/bundle.js +1 -1
- package/dist/src/EditorImage.js +1 -11
- package/dist/src/math/Rect2.d.ts +1 -0
- package/dist/src/math/Rect2.js +20 -0
- package/dist/src/toolbar/IconProvider.js +5 -3
- package/dist/src/tools/TextTool.js +17 -6
- package/dist/src/tools/ToolController.js +3 -3
- package/package.json +1 -1
- package/src/EditorImage.ts +1 -11
- package/src/math/Rect2.test.ts +22 -0
- package/src/math/Rect2.ts +26 -0
- package/src/toolbar/IconProvider.ts +5 -3
- package/src/tools/TextTool.ts +18 -6
- package/src/tools/ToolController.ts +3 -3
package/dist/src/EditorImage.js
CHANGED
@@ -244,17 +244,7 @@ export class ImageNode {
|
|
244
244
|
this.bbox = this.content.getBBox();
|
245
245
|
}
|
246
246
|
else {
|
247
|
-
this.bbox = Rect2.
|
248
|
-
let isFirst = true;
|
249
|
-
for (const child of this.children) {
|
250
|
-
if (isFirst) {
|
251
|
-
this.bbox = child.getBBox();
|
252
|
-
isFirst = false;
|
253
|
-
}
|
254
|
-
else {
|
255
|
-
this.bbox = this.bbox.union(child.getBBox());
|
256
|
-
}
|
257
|
-
}
|
247
|
+
this.bbox = Rect2.union(...this.children.map(child => child.getBBox()));
|
258
248
|
}
|
259
249
|
if (bubbleUp && !oldBBox.eq(this.bbox)) {
|
260
250
|
(_a = this.parent) === null || _a === void 0 ? void 0 : _a.recomputeBBox(true);
|
package/dist/src/math/Rect2.d.ts
CHANGED
@@ -45,6 +45,7 @@ export default class Rect2 {
|
|
45
45
|
toString(): string;
|
46
46
|
static fromCorners(corner1: Point2, corner2: Point2): Rect2;
|
47
47
|
static bboxOf(points: Point2[], margin?: number): Rect2;
|
48
|
+
static union(...rects: Rect2[]): Rect2;
|
48
49
|
static of(template: RectTemplate): Rect2;
|
49
50
|
static empty: Rect2;
|
50
51
|
static unitSquare: Rect2;
|
package/dist/src/math/Rect2.js
CHANGED
@@ -197,6 +197,26 @@ export default class Rect2 {
|
|
197
197
|
}
|
198
198
|
return Rect2.fromCorners(Vec2.of(minX - margin, minY - margin), Vec2.of(maxX + margin, maxY + margin));
|
199
199
|
}
|
200
|
+
// @returns a rectangle that contains all of the given rectangles, the bounding box
|
201
|
+
// of the given rectangles.
|
202
|
+
static union(...rects) {
|
203
|
+
if (rects.length === 0) {
|
204
|
+
return Rect2.empty;
|
205
|
+
}
|
206
|
+
const firstRect = rects[0];
|
207
|
+
let minX = firstRect.topLeft.x;
|
208
|
+
let minY = firstRect.topLeft.y;
|
209
|
+
let maxX = firstRect.bottomRight.x;
|
210
|
+
let maxY = firstRect.bottomRight.y;
|
211
|
+
for (let i = 1; i < rects.length; i++) {
|
212
|
+
const rect = rects[i];
|
213
|
+
minX = Math.min(minX, rect.topLeft.x);
|
214
|
+
minY = Math.min(minY, rect.topLeft.y);
|
215
|
+
maxX = Math.max(maxX, rect.bottomRight.x);
|
216
|
+
maxY = Math.max(maxY, rect.bottomRight.y);
|
217
|
+
}
|
218
|
+
return new Rect2(minX, minY, maxX - minX, maxY - minY);
|
219
|
+
}
|
200
220
|
static of(template) {
|
201
221
|
var _a, _b, _c, _d;
|
202
222
|
const width = (_b = (_a = template.width) !== null && _a !== void 0 ? _a : template.w) !== null && _b !== void 0 ? _b : 0;
|
@@ -353,17 +353,19 @@ export default class IconProvider {
|
|
353
353
|
return icon;
|
354
354
|
}
|
355
355
|
makeIconFromFactory(pen, factory) {
|
356
|
-
|
356
|
+
// Increase the thickness we use to generate the icon less with larger actual thicknesses.
|
357
|
+
// We want the icon to be recognisable with a large range of thicknesses.
|
358
|
+
const thickness = Math.sqrt(pen.getThickness()) * 3;
|
357
359
|
const nowTime = (new Date()).getTime();
|
358
360
|
const startPoint = {
|
359
361
|
pos: Vec2.of(10, 10),
|
360
|
-
width:
|
362
|
+
width: thickness,
|
361
363
|
color: pen.getColor(),
|
362
364
|
time: nowTime - 100,
|
363
365
|
};
|
364
366
|
const endPoint = {
|
365
367
|
pos: Vec2.of(90, 90),
|
366
|
-
width:
|
368
|
+
width: thickness,
|
367
369
|
color: pen.getColor(),
|
368
370
|
time: nowTime,
|
369
371
|
};
|
@@ -63,11 +63,19 @@ export default class TextTool extends BaseTool {
|
|
63
63
|
// Estimate
|
64
64
|
return style.size * 2 / 3;
|
65
65
|
}
|
66
|
-
|
66
|
+
// Take input from this' textInputElem and add it to the EditorImage.
|
67
|
+
// If [removeInput], the HTML input element is removed. Otherwise, its value
|
68
|
+
// is cleared.
|
69
|
+
flushInput(removeInput = true) {
|
67
70
|
if (this.textInputElem && this.textTargetPosition) {
|
68
71
|
const content = this.textInputElem.value.trimEnd();
|
69
|
-
|
70
|
-
|
72
|
+
if (removeInput) {
|
73
|
+
this.textInputElem.remove();
|
74
|
+
this.textInputElem = null;
|
75
|
+
}
|
76
|
+
else {
|
77
|
+
this.textInputElem.value = '';
|
78
|
+
}
|
71
79
|
if (content === '') {
|
72
80
|
return;
|
73
81
|
}
|
@@ -134,9 +142,12 @@ export default class TextTool extends BaseTool {
|
|
134
142
|
}
|
135
143
|
};
|
136
144
|
this.textInputElem.onblur = () => {
|
137
|
-
//
|
138
|
-
//
|
139
|
-
|
145
|
+
// Delay removing the input -- flushInput may be called within a blur()
|
146
|
+
// event handler
|
147
|
+
const removeInput = false;
|
148
|
+
this.flushInput(removeInput);
|
149
|
+
const input = this.textInputElem;
|
150
|
+
setTimeout(() => input === null || input === void 0 ? void 0 : input.remove(), 0);
|
140
151
|
};
|
141
152
|
this.textInputElem.onkeyup = (evt) => {
|
142
153
|
var _a, _b;
|
@@ -21,13 +21,13 @@ export default class ToolController {
|
|
21
21
|
this.primaryToolGroup = primaryToolGroup;
|
22
22
|
const panZoomTool = new PanZoom(editor, PanZoomMode.TwoFingerTouchGestures | PanZoomMode.RightClickDrags, localization.touchPanTool);
|
23
23
|
const keyboardPanZoomTool = new PanZoom(editor, PanZoomMode.Keyboard, localization.keyboardPanZoom);
|
24
|
-
const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness:
|
24
|
+
const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 8 });
|
25
25
|
const primaryTools = [
|
26
26
|
// Three pens
|
27
27
|
primaryPenTool,
|
28
28
|
new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 4 }),
|
29
|
-
// Highlighter-like pen with width=
|
30
|
-
new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness:
|
29
|
+
// Highlighter-like pen with width=40
|
30
|
+
new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 40 }, makePressureSensitiveFreehandLineBuilder),
|
31
31
|
new Eraser(editor, localization.eraserTool),
|
32
32
|
new SelectionTool(editor, localization.selectionTool),
|
33
33
|
new TextTool(editor, localization.textTool, localization),
|
package/package.json
CHANGED
package/src/EditorImage.ts
CHANGED
@@ -320,17 +320,7 @@ export class ImageNode {
|
|
320
320
|
if (this.content !== null) {
|
321
321
|
this.bbox = this.content.getBBox();
|
322
322
|
} else {
|
323
|
-
this.bbox = Rect2.
|
324
|
-
let isFirst = true;
|
325
|
-
|
326
|
-
for (const child of this.children) {
|
327
|
-
if (isFirst) {
|
328
|
-
this.bbox = child.getBBox();
|
329
|
-
isFirst = false;
|
330
|
-
} else {
|
331
|
-
this.bbox = this.bbox.union(child.getBBox());
|
332
|
-
}
|
333
|
-
}
|
323
|
+
this.bbox = Rect2.union(...this.children.map(child => child.getBBox()));
|
334
324
|
}
|
335
325
|
|
336
326
|
if (bubbleUp && !oldBBox.eq(this.bbox)) {
|
package/src/math/Rect2.test.ts
CHANGED
@@ -46,6 +46,28 @@ describe('Rect2', () => {
|
|
46
46
|
expect(Rect2.empty.union(Rect2.empty)).objEq(Rect2.empty);
|
47
47
|
});
|
48
48
|
|
49
|
+
it('should handle empty unions', () => {
|
50
|
+
expect(Rect2.union()).toStrictEqual(Rect2.empty);
|
51
|
+
});
|
52
|
+
|
53
|
+
it('should correctly union multiple rectangles', () => {
|
54
|
+
expect(Rect2.union(new Rect2(0, 0, 1, 1), new Rect2(1, 1, 2, 2))).objEq(
|
55
|
+
new Rect2(0, 0, 3, 3)
|
56
|
+
);
|
57
|
+
|
58
|
+
expect(
|
59
|
+
Rect2.union(new Rect2(-1, 0, 1, 1), new Rect2(1, 1, 2, 2), new Rect2(1, 10, 1, 0.1))
|
60
|
+
).objEq(
|
61
|
+
new Rect2(-1, 0, 4, 10.1)
|
62
|
+
);
|
63
|
+
|
64
|
+
expect(
|
65
|
+
Rect2.union(new Rect2(-1, 0, 1, 1), new Rect2(1, -11.1, 2, 2), new Rect2(1, 10, 1, 0.1))
|
66
|
+
).objEq(
|
67
|
+
new Rect2(-1, -11.1, 4, 21.2)
|
68
|
+
);
|
69
|
+
});
|
70
|
+
|
49
71
|
it('should contain points that are within a rectangle', () => {
|
50
72
|
expect(new Rect2(-1, -1, 2, 2).containsPoint(Vec2.zero)).toBe(true);
|
51
73
|
expect(new Rect2(-1, -1, 0, 0).containsPoint(Vec2.zero)).toBe(false);
|
package/src/math/Rect2.ts
CHANGED
@@ -278,6 +278,32 @@ export default class Rect2 {
|
|
278
278
|
);
|
279
279
|
}
|
280
280
|
|
281
|
+
// @returns a rectangle that contains all of the given rectangles, the bounding box
|
282
|
+
// of the given rectangles.
|
283
|
+
public static union(...rects: Rect2[]): Rect2 {
|
284
|
+
if (rects.length === 0) {
|
285
|
+
return Rect2.empty;
|
286
|
+
}
|
287
|
+
|
288
|
+
const firstRect = rects[0];
|
289
|
+
let minX: number = firstRect.topLeft.x;
|
290
|
+
let minY: number = firstRect.topLeft.y;
|
291
|
+
let maxX: number = firstRect.bottomRight.x;
|
292
|
+
let maxY: number = firstRect.bottomRight.y;
|
293
|
+
|
294
|
+
for (let i = 1; i < rects.length; i++) {
|
295
|
+
const rect = rects[i];
|
296
|
+
minX = Math.min(minX, rect.topLeft.x);
|
297
|
+
minY = Math.min(minY, rect.topLeft.y);
|
298
|
+
maxX = Math.max(maxX, rect.bottomRight.x);
|
299
|
+
maxY = Math.max(maxY, rect.bottomRight.y);
|
300
|
+
}
|
301
|
+
|
302
|
+
return new Rect2(
|
303
|
+
minX, minY, maxX - minX, maxY - minY,
|
304
|
+
);
|
305
|
+
}
|
306
|
+
|
281
307
|
public static of(template: RectTemplate) {
|
282
308
|
const width = template.width ?? template.w ?? 0;
|
283
309
|
const height = template.height ?? template.h ?? 0;
|
@@ -407,18 +407,20 @@ export default class IconProvider {
|
|
407
407
|
}
|
408
408
|
|
409
409
|
public makeIconFromFactory(pen: Pen, factory: ComponentBuilderFactory): IconType {
|
410
|
-
|
410
|
+
// Increase the thickness we use to generate the icon less with larger actual thicknesses.
|
411
|
+
// We want the icon to be recognisable with a large range of thicknesses.
|
412
|
+
const thickness = Math.sqrt(pen.getThickness()) * 3;
|
411
413
|
|
412
414
|
const nowTime = (new Date()).getTime();
|
413
415
|
const startPoint: StrokeDataPoint = {
|
414
416
|
pos: Vec2.of(10, 10),
|
415
|
-
width:
|
417
|
+
width: thickness,
|
416
418
|
color: pen.getColor(),
|
417
419
|
time: nowTime - 100,
|
418
420
|
};
|
419
421
|
const endPoint: StrokeDataPoint = {
|
420
422
|
pos: Vec2.of(90, 90),
|
421
|
-
width:
|
423
|
+
width: thickness,
|
422
424
|
color: pen.getColor(),
|
423
425
|
time: nowTime,
|
424
426
|
};
|
package/src/tools/TextTool.ts
CHANGED
@@ -73,11 +73,19 @@ export default class TextTool extends BaseTool {
|
|
73
73
|
return style.size * 2 / 3;
|
74
74
|
}
|
75
75
|
|
76
|
-
|
76
|
+
// Take input from this' textInputElem and add it to the EditorImage.
|
77
|
+
// If [removeInput], the HTML input element is removed. Otherwise, its value
|
78
|
+
// is cleared.
|
79
|
+
private flushInput(removeInput: boolean = true) {
|
77
80
|
if (this.textInputElem && this.textTargetPosition) {
|
78
81
|
const content = this.textInputElem.value.trimEnd();
|
79
|
-
|
80
|
-
|
82
|
+
|
83
|
+
if (removeInput) {
|
84
|
+
this.textInputElem.remove();
|
85
|
+
this.textInputElem = null;
|
86
|
+
} else {
|
87
|
+
this.textInputElem.value = '';
|
88
|
+
}
|
81
89
|
|
82
90
|
if (content === '') {
|
83
91
|
return;
|
@@ -166,9 +174,13 @@ export default class TextTool extends BaseTool {
|
|
166
174
|
}
|
167
175
|
};
|
168
176
|
this.textInputElem.onblur = () => {
|
169
|
-
//
|
170
|
-
//
|
171
|
-
|
177
|
+
// Delay removing the input -- flushInput may be called within a blur()
|
178
|
+
// event handler
|
179
|
+
const removeInput = false;
|
180
|
+
this.flushInput(removeInput);
|
181
|
+
|
182
|
+
const input = this.textInputElem;
|
183
|
+
setTimeout(() => input?.remove(), 0);
|
172
184
|
};
|
173
185
|
this.textInputElem.onkeyup = (evt) => {
|
174
186
|
if (evt.key === 'Enter' && !evt.shiftKey) {
|
@@ -29,14 +29,14 @@ export default class ToolController {
|
|
29
29
|
|
30
30
|
const panZoomTool = new PanZoom(editor, PanZoomMode.TwoFingerTouchGestures | PanZoomMode.RightClickDrags, localization.touchPanTool);
|
31
31
|
const keyboardPanZoomTool = new PanZoom(editor, PanZoomMode.Keyboard, localization.keyboardPanZoom);
|
32
|
-
const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness:
|
32
|
+
const primaryPenTool = new Pen(editor, localization.penTool(1), { color: Color4.purple, thickness: 8 });
|
33
33
|
const primaryTools = [
|
34
34
|
// Three pens
|
35
35
|
primaryPenTool,
|
36
36
|
new Pen(editor, localization.penTool(2), { color: Color4.clay, thickness: 4 }),
|
37
37
|
|
38
|
-
// Highlighter-like pen with width=
|
39
|
-
new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness:
|
38
|
+
// Highlighter-like pen with width=40
|
39
|
+
new Pen(editor, localization.penTool(3), { color: Color4.ofRGBA(1, 1, 0, 0.5), thickness: 40 }, makePressureSensitiveFreehandLineBuilder),
|
40
40
|
|
41
41
|
new Eraser(editor, localization.eraserTool),
|
42
42
|
new SelectionTool(editor, localization.selectionTool),
|