@annotorious/annotorious 3.3.5 → 3.4.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/dist/annotation/editors/polygon/MidpointHandle.svelte.d.ts +1 -0
- package/dist/annotation/utils/index.d.ts +2 -0
- package/dist/annotation/utils/svg.d.ts +7 -0
- package/dist/annotorious.css +1 -1
- package/dist/annotorious.es.js +3917 -3121
- package/dist/annotorious.es.js.map +1 -1
- package/dist/annotorious.js +2 -2
- package/dist/annotorious.js.map +1 -1
- package/dist/state/ImageAnnotationStore.d.ts +3 -4
- package/dist/state/spatialTree.d.ts +2 -2
- package/package.json +6 -6
- package/src/Annotorious.css +4 -0
- package/src/annotation/SVGAnnotationLayer.svelte +4 -1
- package/src/annotation/editors/Editor.svelte +17 -1
- package/src/annotation/editors/EditorMount.svelte +7 -1
- package/src/annotation/editors/Handle.svelte +61 -33
- package/src/annotation/editors/multipolygon/MultiPolygonEditor.svelte +27 -3
- package/src/annotation/editors/polygon/MidpointHandle.svelte +76 -0
- package/src/annotation/editors/polygon/PolygonEditor.svelte +274 -9
- package/src/annotation/editors/rectangle/RectangleEditor.svelte +26 -2
- package/src/annotation/tools/polygon/RubberbandPolygon.svelte +49 -17
- package/src/annotation/tools/rectangle/RubberbandRectangle.svelte +36 -2
- package/src/annotation/utils/index.ts +2 -0
- package/src/annotation/utils/svg.ts +11 -0
- package/src/state/ImageAnnotationStore.ts +3 -4
- package/src/state/ImageAnnotatorState.ts +16 -4
- package/src/state/spatialTree.ts +6 -3
- package/src/themes/light/index.css +2 -2
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { Annotation, Store, SvelteAnnotatorState, SvelteStore } from '@annotorious/core';
|
|
2
|
-
import { ImageAnnotation } from '../model';
|
|
1
|
+
import { Annotation, Filter, Store, SvelteAnnotatorState, SvelteStore } from '@annotorious/core';
|
|
3
2
|
export type ImageAnnotationStore<I extends Annotation> = Store<I> & {
|
|
4
|
-
getAt(x: number, y: number):
|
|
5
|
-
getIntersecting(x: number, y: number, width: number, height: number):
|
|
3
|
+
getAt(x: number, y: number, filter?: Filter<I>): I | undefined;
|
|
4
|
+
getIntersecting(x: number, y: number, width: number, height: number): I[];
|
|
6
5
|
};
|
|
7
6
|
export type SvelteImageAnnotationStore<I extends Annotation = Annotation> = SvelteStore<I> & ImageAnnotationStore<I>;
|
|
8
7
|
export type SvelteImageAnnotatorState<I extends Annotation = Annotation, E extends unknown = Annotation> = SvelteAnnotatorState<I, E> & {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ImageAnnotationTarget } from '../model';
|
|
2
|
-
import { AnnotationTarget } from '@annotorious/core';
|
|
2
|
+
import { Annotation, AnnotationTarget, Filter } from '@annotorious/core';
|
|
3
3
|
interface IndexedTarget {
|
|
4
4
|
minX: number;
|
|
5
5
|
minY: number;
|
|
@@ -10,7 +10,7 @@ interface IndexedTarget {
|
|
|
10
10
|
export declare const createSpatialTree: () => {
|
|
11
11
|
all: () => IndexedTarget[];
|
|
12
12
|
clear: () => void;
|
|
13
|
-
getAt: (x: number, y: number) => ImageAnnotationTarget
|
|
13
|
+
getAt: (x: number, y: number, filter?: Filter<Annotation>) => ImageAnnotationTarget[];
|
|
14
14
|
getIntersecting: (x: number, y: number, width: number, height: number) => ImageAnnotationTarget[];
|
|
15
15
|
insert: (target: AnnotationTarget) => void;
|
|
16
16
|
remove: (target: AnnotationTarget) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@annotorious/annotorious",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
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",
|
|
@@ -41,15 +41,15 @@
|
|
|
41
41
|
"@tsconfig/svelte": "^5.0.4",
|
|
42
42
|
"@types/rbush": "^4.0.0",
|
|
43
43
|
"jsdom": "^26.1.0",
|
|
44
|
-
"svelte": "^4.2.
|
|
44
|
+
"svelte": "^4.2.20",
|
|
45
45
|
"svelte-preprocess": "^6.0.3",
|
|
46
46
|
"typescript": "5.8.3",
|
|
47
|
-
"vite": "^5.4.
|
|
48
|
-
"vite-plugin-dts": "^4.5.
|
|
49
|
-
"vitest": "^3.1.
|
|
47
|
+
"vite": "^5.4.19",
|
|
48
|
+
"vite-plugin-dts": "^4.5.4",
|
|
49
|
+
"vitest": "^3.1.4"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@annotorious/core": "3.
|
|
52
|
+
"@annotorious/core": "3.4.0",
|
|
53
53
|
"rbush": "^4.0.1",
|
|
54
54
|
"svg-pathdata": "^7.2.0",
|
|
55
55
|
"uuid": "^11.1.0"
|
package/src/Annotorious.css
CHANGED
|
@@ -143,16 +143,19 @@
|
|
|
143
143
|
const getEditor = (shape: Shape): typeof SvelteComponent => _getEditor(shape)!;
|
|
144
144
|
</script>
|
|
145
145
|
|
|
146
|
+
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
|
146
147
|
<svg
|
|
147
148
|
bind:this={svgEl}
|
|
149
|
+
role="application"
|
|
150
|
+
tabindex={0}
|
|
148
151
|
class="a9s-annotationlayer"
|
|
149
152
|
class:drawing={tool}
|
|
153
|
+
class:editing={editableAnnotations}
|
|
150
154
|
class:hidden={!visible}
|
|
151
155
|
class:hover={$hover}
|
|
152
156
|
on:pointerup={onPointerUp}
|
|
153
157
|
on:pointerdown={onPointerDown}
|
|
154
158
|
on:pointermove={onPointerMove}>
|
|
155
|
-
|
|
156
159
|
<g>
|
|
157
160
|
{#each $store.filter(a => isImageAnnotation(a)) as annotation}
|
|
158
161
|
{#if isImageAnnotation(annotation) && !isEditable(annotation)}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
export let shape: Shape;
|
|
10
10
|
export let editor: (shape: Shape, handle: string, delta: [number, number]) => Shape;
|
|
11
11
|
export let transform: Transform;
|
|
12
|
+
export let svgEl: SVGSVGElement;
|
|
12
13
|
|
|
13
14
|
let grabbedHandle: string | undefined;
|
|
14
15
|
|
|
@@ -18,7 +19,22 @@
|
|
|
18
19
|
|
|
19
20
|
const onGrab = (handle: string) => (evt: PointerEvent) => {
|
|
20
21
|
grabbedHandle = handle;
|
|
21
|
-
|
|
22
|
+
|
|
23
|
+
// For legacy compatibility: offsetX and offsetY are not
|
|
24
|
+
// available on synthetic events, currently used by the
|
|
25
|
+
// new PolygonEditor. Old versions of Annotorious, however,
|
|
26
|
+
// won't yet forward the svgEl prop!
|
|
27
|
+
if (svgEl) {
|
|
28
|
+
const { left, top } = svgEl.getBoundingClientRect();
|
|
29
|
+
const offsetX = evt.clientX - left;
|
|
30
|
+
const offsetY = evt.clientY - top;
|
|
31
|
+
|
|
32
|
+
origin = transform.elementToImage(offsetX, offsetY);
|
|
33
|
+
} else {
|
|
34
|
+
const { offsetX, offsetY } = evt;
|
|
35
|
+
origin = transform.elementToImage(offsetX, offsetY);
|
|
36
|
+
}
|
|
37
|
+
|
|
22
38
|
initialShape = shape;
|
|
23
39
|
|
|
24
40
|
const target = evt.target as Element;
|
|
@@ -27,7 +27,13 @@
|
|
|
27
27
|
onMount(() => {
|
|
28
28
|
editorComponent = new editor({
|
|
29
29
|
target,
|
|
30
|
-
props: {
|
|
30
|
+
props: {
|
|
31
|
+
shape: annotation.target.selector,
|
|
32
|
+
computedStyle,
|
|
33
|
+
transform,
|
|
34
|
+
viewportScale,
|
|
35
|
+
svgEl: target.closest('svg')
|
|
36
|
+
}
|
|
31
37
|
});
|
|
32
38
|
|
|
33
39
|
editorComponent.$on('change', event => {
|
|
@@ -5,62 +5,90 @@
|
|
|
5
5
|
export let x: number;
|
|
6
6
|
export let y: number;
|
|
7
7
|
export let scale: number;
|
|
8
|
-
export let
|
|
8
|
+
export let selected: Boolean | undefined = undefined;
|
|
9
9
|
|
|
10
10
|
let touched = false;
|
|
11
11
|
|
|
12
|
-
const onPointerDown = (
|
|
13
|
-
if (
|
|
12
|
+
const onPointerDown = (evt: PointerEvent) => {
|
|
13
|
+
if (evt.pointerType === 'touch')
|
|
14
14
|
touched = true;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const onPointerUp = () =>
|
|
18
18
|
touched = false;
|
|
19
19
|
|
|
20
|
-
$:
|
|
20
|
+
$: handleRadius = 4 / scale;
|
|
21
21
|
</script>
|
|
22
22
|
|
|
23
23
|
{#if isTouch}
|
|
24
|
-
<
|
|
24
|
+
<circle
|
|
25
|
+
cx={x}
|
|
26
|
+
cy={y}
|
|
27
|
+
r={2 * handleRadius} />
|
|
28
|
+
{:else}
|
|
29
|
+
<g class={`a9s-handle ${$$props.class || ''}`.trim()}>
|
|
25
30
|
<circle
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
class="a9s-handle-buffer"
|
|
32
|
+
cx={x}
|
|
33
|
+
cy={y}
|
|
34
|
+
r={handleRadius + (6 / scale)}
|
|
35
|
+
on:pointerenter
|
|
36
|
+
on:pointerleave
|
|
31
37
|
on:pointerdown
|
|
32
|
-
on:pointerdown={onPointerDown}
|
|
38
|
+
on:pointerdown={onPointerDown}
|
|
39
|
+
on:pointerup
|
|
33
40
|
on:pointerup={onPointerUp} />
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
{#if selected}
|
|
43
|
+
<circle
|
|
44
|
+
class="a9s-handle-selected"
|
|
45
|
+
cx={x}
|
|
46
|
+
cy={y}
|
|
47
|
+
r={handleRadius + (6 / scale)} />
|
|
48
|
+
{/if}
|
|
49
|
+
|
|
50
|
+
<circle
|
|
51
|
+
class={`a9s-handle-dot${selected ? ' selected': ''}`}
|
|
52
|
+
cx={x}
|
|
53
|
+
cy={y}
|
|
54
|
+
r={handleRadius} />
|
|
44
55
|
</g>
|
|
45
|
-
{:else}
|
|
46
|
-
<rect
|
|
47
|
-
class={`a9s-handle ${$$props.class || ''}`.trim()}
|
|
48
|
-
x={x - handleSize / 2}
|
|
49
|
-
y={y - handleSize / 2}
|
|
50
|
-
width={handleSize}
|
|
51
|
-
height={handleSize}
|
|
52
|
-
on:pointerdown />
|
|
53
56
|
{/if}
|
|
54
57
|
|
|
55
58
|
<style>
|
|
56
|
-
.a9s-
|
|
59
|
+
.a9s-handle-buffer {
|
|
57
60
|
fill: transparent;
|
|
58
|
-
stroke-width: 0;
|
|
59
61
|
}
|
|
60
62
|
|
|
61
|
-
.a9s-
|
|
62
|
-
fill:
|
|
63
|
+
.a9s-handle-dot {
|
|
64
|
+
fill: #fff;
|
|
65
|
+
pointer-events: none;
|
|
66
|
+
stroke: rgba(0, 0, 0, 0.35);
|
|
67
|
+
stroke-width: 1px;
|
|
68
|
+
vector-effect: non-scaling-stroke;
|
|
63
69
|
}
|
|
64
70
|
|
|
71
|
+
.a9s-handle-dot.selected {
|
|
72
|
+
fill: #1a1a1a;
|
|
73
|
+
stroke: none;
|
|
74
|
+
}
|
|
65
75
|
|
|
66
|
-
|
|
76
|
+
.a9s-handle-selected {
|
|
77
|
+
animation: dash-rotate 350ms linear infinite reverse;
|
|
78
|
+
fill: rgba(255, 255, 255, 0.25);
|
|
79
|
+
stroke: rgba(0, 0, 0, 0.9);
|
|
80
|
+
stroke-dasharray: 2 2;
|
|
81
|
+
stroke-width: 1px;
|
|
82
|
+
pointer-events: none;
|
|
83
|
+
vector-effect: non-scaling-stroke;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@keyframes dash-rotate {
|
|
87
|
+
0% {
|
|
88
|
+
stroke-dashoffset: 0;
|
|
89
|
+
}
|
|
90
|
+
100% {
|
|
91
|
+
stroke-dashoffset: 4; /* Sum of dash + gap */
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
</style>
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
multipolygonElementToPath
|
|
12
12
|
} from '../../../model';
|
|
13
13
|
import type { Transform } from '../../Transform';
|
|
14
|
+
import { getMaskDimensions } from '../../utils';
|
|
14
15
|
import { Editor, Handle } from '..';
|
|
15
16
|
|
|
16
17
|
/** Props */
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
export let computedStyle: string | undefined;
|
|
19
20
|
export let transform: Transform;
|
|
20
21
|
export let viewportScale: number = 1;
|
|
22
|
+
export let svgEl: SVGSVGElement;
|
|
21
23
|
|
|
22
24
|
$: geom = shape.geometry;
|
|
23
25
|
|
|
@@ -76,22 +78,33 @@
|
|
|
76
78
|
}
|
|
77
79
|
} as MultiPolygon;
|
|
78
80
|
}
|
|
81
|
+
|
|
82
|
+
$: mask = getMaskDimensions(geom.bounds, 2 / viewportScale);
|
|
83
|
+
|
|
84
|
+
const maskId = `multipoly-mask-${Math.random().toString(36).substring(2, 12)}`;
|
|
79
85
|
</script>
|
|
80
86
|
|
|
81
87
|
<Editor
|
|
82
88
|
shape={shape}
|
|
83
89
|
transform={transform}
|
|
84
90
|
editor={editor}
|
|
91
|
+
svgEl={svgEl}
|
|
85
92
|
on:change
|
|
86
93
|
on:grab
|
|
87
94
|
on:release
|
|
88
95
|
let:grab={grab}>
|
|
89
|
-
|
|
90
96
|
{#each geom.polygons as element, elementIdx}
|
|
91
97
|
<g>
|
|
98
|
+
<defs>
|
|
99
|
+
<mask id={`${maskId}-${elementIdx}`} class="a9s-multipolygon-editor-mask">
|
|
100
|
+
<rect x={mask.x} y={mask.y} width={mask.w} height={mask.h} />
|
|
101
|
+
<path d={multipolygonElementToPath(element)} />
|
|
102
|
+
</mask>
|
|
103
|
+
</defs>
|
|
104
|
+
|
|
92
105
|
<path
|
|
93
106
|
class="a9s-outer"
|
|
94
|
-
|
|
107
|
+
mask={`url(#${maskId}-${elementIdx})`}
|
|
95
108
|
fill-rule="evenodd"
|
|
96
109
|
on:pointerdown={grab('SHAPE')}
|
|
97
110
|
d={multipolygonElementToPath(element)} />
|
|
@@ -106,6 +119,7 @@
|
|
|
106
119
|
{#each element.rings as ring, ringIdx}
|
|
107
120
|
{#each ring.points as point, pointIdx}
|
|
108
121
|
<Handle
|
|
122
|
+
class="a9s-corner-handle"
|
|
109
123
|
on:pointerdown={grab(`HANDLE-${elementIdx}-${ringIdx}-${pointIdx}`)}
|
|
110
124
|
x={point[0]} y={point[1]}
|
|
111
125
|
scale={viewportScale} />
|
|
@@ -113,4 +127,14 @@
|
|
|
113
127
|
{/each}
|
|
114
128
|
</g>
|
|
115
129
|
{/each}
|
|
116
|
-
</Editor>
|
|
130
|
+
</Editor>
|
|
131
|
+
|
|
132
|
+
<style>
|
|
133
|
+
mask.a9s-multipolygon-editor-mask > rect {
|
|
134
|
+
fill: #fff;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
mask.a9s-multipolygon-editor-mask > path {
|
|
138
|
+
fill: #000;
|
|
139
|
+
}
|
|
140
|
+
</style>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { isTouch } from '../../utils';
|
|
3
|
+
|
|
4
|
+
/** props **/
|
|
5
|
+
export let x: number;
|
|
6
|
+
export let y: number;
|
|
7
|
+
export let scale: number;
|
|
8
|
+
|
|
9
|
+
let touched = false;
|
|
10
|
+
|
|
11
|
+
const onPointerDown = (evt: PointerEvent) => {
|
|
12
|
+
if (evt.pointerType === 'touch')
|
|
13
|
+
touched = true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const onPointerUp = () =>
|
|
17
|
+
touched = false;
|
|
18
|
+
|
|
19
|
+
$: handleRadius = 4 / scale;
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
{#if isTouch}
|
|
23
|
+
<circle
|
|
24
|
+
cx={x}
|
|
25
|
+
cy={y}
|
|
26
|
+
r={handleRadius} />
|
|
27
|
+
{:else}
|
|
28
|
+
<g class="a9s-polygon-midpoint">
|
|
29
|
+
<circle
|
|
30
|
+
class="a9s-polygon-midpoint-buffer"
|
|
31
|
+
cx={x}
|
|
32
|
+
cy={y}
|
|
33
|
+
r={1.75 * handleRadius}
|
|
34
|
+
on:pointerdown
|
|
35
|
+
on:pointerdown={onPointerDown} />
|
|
36
|
+
|
|
37
|
+
<circle
|
|
38
|
+
class="a9s-polygon-midpoint-outer"
|
|
39
|
+
cx={x}
|
|
40
|
+
cy={y}
|
|
41
|
+
r={handleRadius} />
|
|
42
|
+
|
|
43
|
+
<circle
|
|
44
|
+
class="a9s-polygon-midpoint-inner"
|
|
45
|
+
cx={x}
|
|
46
|
+
cy={y}
|
|
47
|
+
r={handleRadius} />
|
|
48
|
+
</g>
|
|
49
|
+
{/if}
|
|
50
|
+
|
|
51
|
+
<style>
|
|
52
|
+
.a9s-polygon-midpoint {
|
|
53
|
+
cursor: crosshair;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.a9s-polygon-midpoint-buffer {
|
|
57
|
+
fill: transparent;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.a9s-polygon-midpoint-outer {
|
|
61
|
+
display: none;
|
|
62
|
+
fill: transparent;
|
|
63
|
+
pointer-events: none;
|
|
64
|
+
stroke: rgba(0, 0, 0, 0.35);
|
|
65
|
+
stroke-width: 1.5px;
|
|
66
|
+
vector-effect: non-scaling-stroke;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.a9s-polygon-midpoint-inner {
|
|
70
|
+
fill: rgba(0, 0, 0, 0.25);
|
|
71
|
+
pointer-events: none;
|
|
72
|
+
stroke: #fff;
|
|
73
|
+
stroke-width: 1px;
|
|
74
|
+
vector-effect: non-scaling-stroke;
|
|
75
|
+
}
|
|
76
|
+
</style>
|