@annotorious/annotorious 3.4.1 → 3.4.2
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/dist/annotation/editors/multipolygon/utils.d.ts +9 -0
- package/dist/annotorious.css +1 -1
- package/dist/annotorious.es.js +2562 -2194
- package/dist/annotorious.es.js.map +1 -1
- package/dist/annotorious.js +2 -2
- package/dist/annotorious.js.map +1 -1
- package/dist/model/core/multipolygon/multiPolygonUtils.d.ts +2 -1
- package/package.json +3 -2
- package/src/annotation/editors/{polygon/MidpointHandle.svelte → MidpointHandle.svelte} +1 -1
- package/src/annotation/editors/multipolygon/MultiPolygonEditor.svelte +290 -17
- package/src/annotation/editors/multipolygon/utils.ts +42 -0
- package/src/annotation/editors/polygon/PolygonEditor.svelte +2 -4
- package/src/model/core/multipolygon/multiPolygonUtils.ts +11 -1
- /package/dist/annotation/editors/{polygon/MidpointHandle.svelte.d.ts → MidpointHandle.svelte.d.ts} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import { MultiPolygonElement } from './MultiPolygon';
|
|
1
|
+
import { MultiPolygonElement, MultiPolygonGeometry } from './MultiPolygon';
|
|
2
2
|
export declare const boundsFromMultiPolygonElements: (elements: MultiPolygonElement[]) => import('../Shape').Bounds;
|
|
3
3
|
export declare const multipolygonElementToPath: (element: MultiPolygonElement) => string;
|
|
4
|
+
export declare const getAllCorners: (geom: MultiPolygonGeometry) => [number, number][];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@annotorious/annotorious",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.2",
|
|
4
4
|
"description": "Add image annotation functionality to any web page with a few lines of JavaScript",
|
|
5
5
|
"author": "Rainer Simon",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
"vitest": "^3.1.4"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@annotorious/core": "3.4.
|
|
52
|
+
"@annotorious/core": "3.4.2",
|
|
53
|
+
"dequal": "^2.0.3",
|
|
53
54
|
"rbush": "^4.0.1",
|
|
54
55
|
"svg-pathdata": "^7.2.0",
|
|
55
56
|
"uuid": "^11.1.0"
|
|
@@ -1,18 +1,30 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
import { createEventDispatcher, onMount, tick } from 'svelte';
|
|
3
|
+
import { dequal } from 'dequal/lite';
|
|
4
|
+
import type { MultiPolygon, MultiPolygonElement, MultiPolygonGeometry, Shape } from '../../../model';
|
|
5
|
+
import { getMaskDimensions, isTouch } from '../../utils';
|
|
6
|
+
import type { Transform } from '../../Transform';
|
|
7
|
+
import Editor from '../Editor.svelte';
|
|
8
|
+
import Handle from '../Handle.svelte';
|
|
9
|
+
import MidpointHandle from '../MidpointHandle.svelte';
|
|
10
|
+
import { computeMidpoints } from './utils';
|
|
8
11
|
import {
|
|
9
12
|
boundsFromMultiPolygonElements,
|
|
10
13
|
boundsFromPoints,
|
|
11
|
-
|
|
14
|
+
getAllCorners,
|
|
15
|
+
multipolygonElementToPath
|
|
12
16
|
} from '../../../model';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
|
|
18
|
+
const dispatch = createEventDispatcher<{ change: MultiPolygon }>();
|
|
19
|
+
|
|
20
|
+
/** Time difference (milliseconds) required for registering a click/tap **/
|
|
21
|
+
const CLICK_THRESHOLD = 250;
|
|
22
|
+
|
|
23
|
+
/** Minimum distance (px) to shape required for midpoints to show */
|
|
24
|
+
const MIN_HOVER_DISTANCE = 1000;
|
|
25
|
+
|
|
26
|
+
/** Needed for the <mask> element **/
|
|
27
|
+
const MIDPOINT_SIZE = 4.5;
|
|
16
28
|
|
|
17
29
|
/** Props */
|
|
18
30
|
export let shape: MultiPolygon;
|
|
@@ -21,9 +33,120 @@
|
|
|
21
33
|
export let viewportScale: number = 1;
|
|
22
34
|
export let svgEl: SVGSVGElement;
|
|
23
35
|
|
|
36
|
+
/** Drawing tool layer **/
|
|
37
|
+
let visibleMidpoint: number | undefined;
|
|
38
|
+
let isHandleHovered = false;
|
|
39
|
+
let lastHandleClick: number | undefined;
|
|
40
|
+
let selectedCorners: { polygon: number, ring: number, point: number }[] = [];
|
|
41
|
+
|
|
24
42
|
$: geom = shape.geometry;
|
|
25
43
|
|
|
44
|
+
// No support yet for adding or removing points in mobile!
|
|
45
|
+
$: midpoints = isTouch ? [] : computeMidpoints(geom, viewportScale);
|
|
46
|
+
|
|
47
|
+
/** Handle hover state **/
|
|
48
|
+
const onEnterHandle = () => isHandleHovered = true;
|
|
49
|
+
const onLeaveHandle = () => isHandleHovered = false;
|
|
50
|
+
|
|
51
|
+
/** Determine visible midpoint, if any **/
|
|
52
|
+
const onPointerMove = (evt: PointerEvent) => {
|
|
53
|
+
if (selectedCorners.length > 0) {
|
|
54
|
+
visibleMidpoint = undefined;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const [px, py] = transform.elementToImage(evt.offsetX, evt.offsetY);
|
|
59
|
+
|
|
60
|
+
const getDistSq = (pt: number[]) =>
|
|
61
|
+
Math.pow(pt[0] - px, 2) + Math.pow(pt[1] - py, 2);
|
|
62
|
+
|
|
63
|
+
const closestCorner = getAllCorners(geom).reduce((closest, corner) =>
|
|
64
|
+
getDistSq(corner) < getDistSq(closest) ? corner : closest);
|
|
65
|
+
|
|
66
|
+
const closestVisibleMidpoint = midpoints
|
|
67
|
+
.filter(m => m.visible)
|
|
68
|
+
.reduce((closest, midpoint) =>
|
|
69
|
+
getDistSq(midpoint.point) < getDistSq(closest.point) ? midpoint : closest);
|
|
70
|
+
|
|
71
|
+
// Show midpoint of the mouse is at least within THRESHOLD distance
|
|
72
|
+
// of the midpoint or the closest corner. (Basically a poor man's shape buffering).
|
|
73
|
+
const threshold = Math.pow(MIN_HOVER_DISTANCE / viewportScale, 2);
|
|
74
|
+
|
|
75
|
+
const shouldShow =
|
|
76
|
+
getDistSq(closestCorner) < threshold ||
|
|
77
|
+
getDistSq(closestVisibleMidpoint.point) < threshold;
|
|
78
|
+
|
|
79
|
+
if (shouldShow)
|
|
80
|
+
visibleMidpoint = midpoints.indexOf(closestVisibleMidpoint);
|
|
81
|
+
else
|
|
82
|
+
visibleMidpoint = undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* SVG element keeps loosing focus when interacting with
|
|
87
|
+
* shapes–this function refocuses.
|
|
88
|
+
*/
|
|
89
|
+
const reclaimFocus = () => {
|
|
90
|
+
if (document.activeElement !== svgEl)
|
|
91
|
+
svgEl.focus();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* De-selects all corners and reclaims focus.
|
|
96
|
+
*/
|
|
97
|
+
const onShapePointerUp = () => {
|
|
98
|
+
selectedCorners = [];
|
|
99
|
+
reclaimFocus();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Updates state, waiting for potential click.
|
|
104
|
+
*/
|
|
105
|
+
const onHandlePointerDown = (evt: PointerEvent) => {
|
|
106
|
+
isHandleHovered = true;
|
|
107
|
+
|
|
108
|
+
evt.preventDefault();
|
|
109
|
+
evt.stopPropagation();
|
|
110
|
+
|
|
111
|
+
lastHandleClick = performance.now();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Selection handling logic **/
|
|
115
|
+
const onHandlePointerUp = (polygon: number, ring: number, point: number) => (evt: PointerEvent) => {
|
|
116
|
+
if (!lastHandleClick || isTouch) return;
|
|
117
|
+
|
|
118
|
+
// Drag, not click
|
|
119
|
+
if (performance.now() - lastHandleClick > CLICK_THRESHOLD) return;
|
|
120
|
+
|
|
121
|
+
// Shorthand
|
|
122
|
+
const isMatch = (other: { polygon: number, ring: number, point: number }) =>
|
|
123
|
+
other.polygon === polygon && other.ring === ring && other.point === point;
|
|
124
|
+
|
|
125
|
+
const isSelected = selectedCorners.some(isMatch);
|
|
126
|
+
|
|
127
|
+
if (evt.metaKey || evt.ctrlKey || evt.shiftKey) {
|
|
128
|
+
// Add to or remove from selection
|
|
129
|
+
if (isSelected)
|
|
130
|
+
selectedCorners = selectedCorners.filter(other => !isMatch(other));
|
|
131
|
+
else
|
|
132
|
+
selectedCorners = [...selectedCorners, { polygon, ring, point }];
|
|
133
|
+
} else {
|
|
134
|
+
if (isSelected && selectedCorners.length > 1)
|
|
135
|
+
// Keep selected, de-select others
|
|
136
|
+
selectedCorners = [{ polygon, ring, point }]
|
|
137
|
+
else if (isSelected)
|
|
138
|
+
// De-select
|
|
139
|
+
selectedCorners = [];
|
|
140
|
+
else
|
|
141
|
+
selectedCorners = [{ polygon, ring, point }];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
reclaimFocus();
|
|
145
|
+
}
|
|
146
|
+
|
|
26
147
|
const editor = (shape: Shape, handle: string, delta: [number, number]) => {
|
|
148
|
+
reclaimFocus();
|
|
149
|
+
|
|
27
150
|
const elements = ((shape.geometry) as MultiPolygonGeometry).polygons;
|
|
28
151
|
|
|
29
152
|
let updated: MultiPolygonElement[];
|
|
@@ -79,9 +202,126 @@
|
|
|
79
202
|
} as MultiPolygon;
|
|
80
203
|
}
|
|
81
204
|
|
|
82
|
-
|
|
205
|
+
const onAddPoint = (midpointIdx: number) => async (evt: PointerEvent) => {
|
|
206
|
+
evt.stopPropagation();
|
|
83
207
|
|
|
84
|
-
|
|
208
|
+
const midpoint = midpoints[midpointIdx];
|
|
209
|
+
|
|
210
|
+
const updated = geom.polygons.map((element, elIdx) => {
|
|
211
|
+
if (elIdx === midpoint.elementIdx) {
|
|
212
|
+
const rings = element.rings.map((ring, ringIdx) => {
|
|
213
|
+
if (ringIdx === midpoint.ringIdx) {
|
|
214
|
+
const points = [
|
|
215
|
+
...ring.points.slice(0, midpoint.pointIdx + 1),
|
|
216
|
+
midpoint.point,
|
|
217
|
+
...ring.points.slice(midpoint.pointIdx + 1)
|
|
218
|
+
] as [number, number][];
|
|
219
|
+
|
|
220
|
+
return { points };
|
|
221
|
+
} else {
|
|
222
|
+
return ring;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const bounds = boundsFromPoints(rings[0].points as [number, number][]);
|
|
227
|
+
return { rings, bounds } as MultiPolygonElement;
|
|
228
|
+
} else {
|
|
229
|
+
return element;
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
dispatch('change', {
|
|
234
|
+
...shape,
|
|
235
|
+
geometry: {
|
|
236
|
+
polygons: updated,
|
|
237
|
+
bounds: boundsFromMultiPolygonElements(updated)
|
|
238
|
+
}
|
|
239
|
+
} as MultiPolygon);
|
|
240
|
+
|
|
241
|
+
await tick();
|
|
242
|
+
|
|
243
|
+
// Find the newly inserted handle and dispatch grab event
|
|
244
|
+
const newHandle = [...document.querySelectorAll(`.a9s-handle`)][midpointIdx + 1];
|
|
245
|
+
if (newHandle?.firstChild) {
|
|
246
|
+
const newEvent = new PointerEvent('pointerdown', {
|
|
247
|
+
bubbles: true,
|
|
248
|
+
cancelable: true,
|
|
249
|
+
clientX: evt.clientX,
|
|
250
|
+
clientY: evt.clientY,
|
|
251
|
+
pointerId: evt.pointerId,
|
|
252
|
+
pointerType: evt.pointerType,
|
|
253
|
+
isPrimary: evt.isPrimary,
|
|
254
|
+
buttons: evt.buttons
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
newHandle.firstChild.dispatchEvent(newEvent);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const onDeleteSelected = () => {
|
|
262
|
+
const updatedPolygons = geom.polygons.map((polygon, polygonIdx) => {
|
|
263
|
+
const hasSelected = selectedCorners.some(s => s.polygon === polygonIdx);
|
|
264
|
+
|
|
265
|
+
if (hasSelected) {
|
|
266
|
+
const updatedRings = polygon.rings.map((ring, ringIdx) => {
|
|
267
|
+
const hasSelected = selectedCorners.some(s => s.polygon === polygonIdx && s.ring === ringIdx);
|
|
268
|
+
|
|
269
|
+
// Rings needs 3 points min
|
|
270
|
+
if (hasSelected && ring.points.length > 3) {
|
|
271
|
+
const points = ring.points.filter((_, i) =>
|
|
272
|
+
!selectedCorners.some(s => s.polygon === polygonIdx && s.ring === ringIdx && s.point === i));
|
|
273
|
+
|
|
274
|
+
return { points };
|
|
275
|
+
} else {
|
|
276
|
+
// No points selected on this ring
|
|
277
|
+
return ring;
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const bounds = boundsFromPoints(updatedRings[0].points as [number, number][]);
|
|
282
|
+
return { rings: updatedRings, bounds } as MultiPolygonElement;
|
|
283
|
+
} else {
|
|
284
|
+
// No points selected on this polygon
|
|
285
|
+
return polygon;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const hasChanged = !dequal(geom.polygons, updatedPolygons);
|
|
290
|
+
if (hasChanged) {
|
|
291
|
+
dispatch('change', {
|
|
292
|
+
...shape,
|
|
293
|
+
geometry: {
|
|
294
|
+
polygons: updatedPolygons,
|
|
295
|
+
bounds: boundsFromMultiPolygonElements(updatedPolygons)
|
|
296
|
+
}
|
|
297
|
+
} as MultiPolygon);
|
|
298
|
+
|
|
299
|
+
selectedCorners = [];
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
onMount(() => {
|
|
304
|
+
if (isTouch) return;
|
|
305
|
+
|
|
306
|
+
const onKeydown = (evt: KeyboardEvent) => {
|
|
307
|
+
if (evt.key === 'Delete' || evt.key === 'Backspace') {
|
|
308
|
+
evt.preventDefault();
|
|
309
|
+
onDeleteSelected();
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
svgEl.addEventListener('pointermove', onPointerMove);
|
|
314
|
+
svgEl.addEventListener('keydown', onKeydown);
|
|
315
|
+
|
|
316
|
+
return () => {
|
|
317
|
+
svgEl.removeEventListener('pointermove', onPointerMove);
|
|
318
|
+
svgEl.removeEventListener('keydown', onKeydown);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
$: mask = getMaskDimensions(geom.bounds, MIDPOINT_SIZE / viewportScale);
|
|
323
|
+
|
|
324
|
+
const maskId = `polygon-mask-${Math.random().toString(36).substring(2, 12)}`;
|
|
85
325
|
</script>
|
|
86
326
|
|
|
87
327
|
<Editor
|
|
@@ -96,23 +336,39 @@
|
|
|
96
336
|
{#each geom.polygons as element, elementIdx}
|
|
97
337
|
<g>
|
|
98
338
|
<defs>
|
|
99
|
-
<mask id={`${maskId}-${elementIdx}`} class="a9s-multipolygon-editor-mask">
|
|
339
|
+
<mask id={`${maskId}-${elementIdx}-outer`} class="a9s-multipolygon-editor-mask">
|
|
100
340
|
<rect x={mask.x} y={mask.y} width={mask.w} height={mask.h} />
|
|
101
|
-
<path d={multipolygonElementToPath(element)} />
|
|
341
|
+
<path d={multipolygonElementToPath(element)} />
|
|
342
|
+
|
|
343
|
+
{#if (visibleMidpoint !== undefined && !isHandleHovered)}
|
|
344
|
+
{@const { point } = midpoints[visibleMidpoint]}
|
|
345
|
+
<circle cx={point[0]} cy={point[1]} r={MIDPOINT_SIZE / viewportScale} />
|
|
346
|
+
{/if}
|
|
102
347
|
</mask>
|
|
348
|
+
|
|
349
|
+
{#if (visibleMidpoint !== undefined && !isHandleHovered)}
|
|
350
|
+
{@const { point } = midpoints[visibleMidpoint]}
|
|
351
|
+
<mask id={`${maskId}-${elementIdx}-inner`} class="a9s-multipolygon-editor-mask">
|
|
352
|
+
<rect x={mask.x} y={mask.y} width={mask.w} height={mask.h} />
|
|
353
|
+
<circle cx={point[0]} cy={point[1]} r={MIDPOINT_SIZE / viewportScale} />
|
|
354
|
+
</mask>
|
|
355
|
+
{/if}
|
|
103
356
|
</defs>
|
|
104
357
|
|
|
105
358
|
<path
|
|
106
359
|
class="a9s-outer"
|
|
107
|
-
mask={`url(#${maskId}-${elementIdx})`}
|
|
360
|
+
mask={`url(#${maskId}-${elementIdx}-outer)`}
|
|
108
361
|
fill-rule="evenodd"
|
|
362
|
+
on:pointerup={onShapePointerUp}
|
|
109
363
|
on:pointerdown={grab('SHAPE')}
|
|
110
364
|
d={multipolygonElementToPath(element)} />
|
|
111
365
|
|
|
112
366
|
<path
|
|
113
367
|
class="a9s-inner"
|
|
368
|
+
mask={`url(#${maskId}-${elementIdx}-inner)`}
|
|
114
369
|
style={computedStyle}
|
|
115
370
|
fill-rule="evenodd"
|
|
371
|
+
on:pointerup={onShapePointerUp}
|
|
116
372
|
on:pointerdown={grab('SHAPE')}
|
|
117
373
|
d={multipolygonElementToPath(element)} />
|
|
118
374
|
|
|
@@ -120,13 +376,29 @@
|
|
|
120
376
|
{#each ring.points as point, pointIdx}
|
|
121
377
|
<Handle
|
|
122
378
|
class="a9s-corner-handle"
|
|
379
|
+
x={point[0]}
|
|
380
|
+
y={point[1]}
|
|
381
|
+
scale={viewportScale}
|
|
382
|
+
selected={selectedCorners.some(({ polygon, ring, point }) =>
|
|
383
|
+
polygon === elementIdx && ring === ringIdx && point === pointIdx)}
|
|
384
|
+
on:pointerenter={onEnterHandle}
|
|
385
|
+
on:pointerleave={onLeaveHandle}
|
|
386
|
+
on:pointerdown={onHandlePointerDown}
|
|
123
387
|
on:pointerdown={grab(`HANDLE-${elementIdx}-${ringIdx}-${pointIdx}`)}
|
|
124
|
-
|
|
125
|
-
scale={viewportScale} />
|
|
388
|
+
on:pointerup={onHandlePointerUp(elementIdx, ringIdx, pointIdx)} />
|
|
126
389
|
{/each}
|
|
127
390
|
{/each}
|
|
128
391
|
</g>
|
|
129
392
|
{/each}
|
|
393
|
+
|
|
394
|
+
{#if (visibleMidpoint !== undefined && !isHandleHovered)}
|
|
395
|
+
{@const { point } = midpoints[visibleMidpoint]}
|
|
396
|
+
<MidpointHandle
|
|
397
|
+
x={point[0]}
|
|
398
|
+
y={point[1]}
|
|
399
|
+
scale={viewportScale}
|
|
400
|
+
on:pointerdown={onAddPoint(visibleMidpoint)} />
|
|
401
|
+
{/if}
|
|
130
402
|
</Editor>
|
|
131
403
|
|
|
132
404
|
<style>
|
|
@@ -134,6 +406,7 @@
|
|
|
134
406
|
fill: #fff;
|
|
135
407
|
}
|
|
136
408
|
|
|
409
|
+
mask.a9s-multipolygon-editor-mask > circle,
|
|
137
410
|
mask.a9s-multipolygon-editor-mask > path {
|
|
138
411
|
fill: #000;
|
|
139
412
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { MultiPolygonGeometry } from '../../../model';
|
|
2
|
+
|
|
3
|
+
/** Minimum distance (px) between corners required for midpoints to show **/
|
|
4
|
+
const MIN_VISIBILITY_DISTANCE = 12;
|
|
5
|
+
|
|
6
|
+
export interface MultipolygonMidpoint {
|
|
7
|
+
|
|
8
|
+
point: [number, number];
|
|
9
|
+
|
|
10
|
+
visible: boolean;
|
|
11
|
+
|
|
12
|
+
elementIdx: number;
|
|
13
|
+
|
|
14
|
+
ringIdx: number;
|
|
15
|
+
|
|
16
|
+
pointIdx: number;
|
|
17
|
+
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const computeMidpoints = (geom: MultiPolygonGeometry, viewportScale: number) =>
|
|
21
|
+
geom.polygons.reduce<MultipolygonMidpoint[]>((all, element, elementIdx) => {
|
|
22
|
+
const forThisPolygon = element.rings.reduce<MultipolygonMidpoint[]>((forThisPolygon, ring, ringIdx) => {
|
|
23
|
+
const forThisRing: MultipolygonMidpoint[] = ring.points.map((thisPoint, pointIdx) => {
|
|
24
|
+
const nextPoint = pointIdx === ring.points.length - 1 ? ring.points[0] : ring.points[pointIdx + 1];
|
|
25
|
+
|
|
26
|
+
const x = (thisPoint[0] + nextPoint[0]) / 2;
|
|
27
|
+
const y = (thisPoint[1] + nextPoint[1]) / 2;
|
|
28
|
+
|
|
29
|
+
const dist = Math.sqrt(
|
|
30
|
+
Math.pow(nextPoint[0] - x, 2) + Math.pow(nextPoint[1] - y, 2));
|
|
31
|
+
|
|
32
|
+
// Don't show if the distance between the corners is too small
|
|
33
|
+
const visible = dist > MIN_VISIBILITY_DISTANCE / viewportScale;
|
|
34
|
+
|
|
35
|
+
return { point: [x, y], visible, elementIdx, ringIdx, pointIdx };
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return [...forThisPolygon, ...forThisRing];
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
return [...all, ...forThisPolygon];
|
|
42
|
+
}, []);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import type { Transform } from '../../Transform';
|
|
7
7
|
import Editor from '../Editor.svelte';
|
|
8
8
|
import Handle from '../Handle.svelte';
|
|
9
|
-
import
|
|
9
|
+
import MidpointHandle from '../MidpointHandle.svelte';
|
|
10
10
|
|
|
11
11
|
const dispatch = createEventDispatcher<{ change: Polygon }>();
|
|
12
12
|
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
export let svgEl: SVGSVGElement;
|
|
31
31
|
|
|
32
32
|
/** Drawing tool layer **/
|
|
33
|
-
let polygonEl: SVGGElement;
|
|
34
33
|
let visibleMidpoint: number | undefined;
|
|
35
34
|
let isHandleHovered = false;
|
|
36
35
|
let lastHandleClick: number | undefined;
|
|
@@ -287,7 +286,6 @@
|
|
|
287
286
|
points={geom.points.map(xy => xy.join(',')).join(' ')} />
|
|
288
287
|
|
|
289
288
|
<polygon
|
|
290
|
-
bind:this={polygonEl}
|
|
291
289
|
class="a9s-inner a9s-shape-handle"
|
|
292
290
|
mask={`url(#${maskId}-inner)`}
|
|
293
291
|
style={computedStyle}
|
|
@@ -312,7 +310,7 @@
|
|
|
312
310
|
|
|
313
311
|
{#if (visibleMidpoint !== undefined && !isHandleHovered)}
|
|
314
312
|
{@const { point } = midpoints[visibleMidpoint]}
|
|
315
|
-
<
|
|
313
|
+
<MidpointHandle
|
|
316
314
|
x={point[0]}
|
|
317
315
|
y={point[1]}
|
|
318
316
|
scale={viewportScale}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ShapeType } from '../Shape';
|
|
2
2
|
import type { ShapeUtil } from '../shapeUtils';
|
|
3
3
|
import { boundsFromPoints, computePolygonArea, isPointInPolygon, pointsToPath, registerShapeUtil } from '../shapeUtils';
|
|
4
|
-
import type { MultiPolygon, MultiPolygonElement } from './MultiPolygon';
|
|
4
|
+
import type { MultiPolygon, MultiPolygonElement, MultiPolygonGeometry } from './MultiPolygon';
|
|
5
5
|
|
|
6
6
|
const MultiPolygonUtil: ShapeUtil<MultiPolygon> = {
|
|
7
7
|
|
|
@@ -60,4 +60,14 @@ export const multipolygonElementToPath = (element: MultiPolygonElement) => {
|
|
|
60
60
|
return paths.join(' ');
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
export const getAllCorners = (geom: MultiPolygonGeometry) =>
|
|
64
|
+
geom.polygons.reduce<[number, number][]>((all, element) => (
|
|
65
|
+
[
|
|
66
|
+
...all,
|
|
67
|
+
...element.rings.reduce<[number, number][]>((onThisElement, ring) => (
|
|
68
|
+
[...onThisElement, ...ring.points]
|
|
69
|
+
), [])
|
|
70
|
+
]
|
|
71
|
+
), []);
|
|
72
|
+
|
|
63
73
|
registerShapeUtil(ShapeType.MULTIPOLYGLON, MultiPolygonUtil);
|
/package/dist/annotation/editors/{polygon/MidpointHandle.svelte.d.ts → MidpointHandle.svelte.d.ts}
RENAMED
|
File without changes
|