@annotorious/annotorious 3.0.0-rc.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.
Files changed (183) hide show
  1. package/README.md +6 -0
  2. package/dist/Annotorious.d.ts +15 -0
  3. package/dist/Annotorious.d.ts.map +1 -0
  4. package/dist/AnnotoriousOpts.d.ts +14 -0
  5. package/dist/AnnotoriousOpts.d.ts.map +1 -0
  6. package/dist/annotation/SVGAnnotationLayer.svelte.d.ts +1 -0
  7. package/dist/annotation/SVGAnnotationLayerPointerEvent.d.ts +11 -0
  8. package/dist/annotation/SVGAnnotationLayerPointerEvent.d.ts.map +1 -0
  9. package/dist/annotation/Transform.d.ts +6 -0
  10. package/dist/annotation/Transform.d.ts.map +1 -0
  11. package/dist/annotation/editors/Editor.svelte.d.ts +1 -0
  12. package/dist/annotation/editors/EditorMount.svelte.d.ts +1 -0
  13. package/dist/annotation/editors/Handle.d.ts +14 -0
  14. package/dist/annotation/editors/Handle.d.ts.map +1 -0
  15. package/dist/annotation/editors/editorsRegistry.d.ts +5 -0
  16. package/dist/annotation/editors/editorsRegistry.d.ts.map +1 -0
  17. package/dist/annotation/editors/index.d.ts +7 -0
  18. package/dist/annotation/editors/index.d.ts.map +1 -0
  19. package/dist/annotation/editors/polygon/PolygonEditor.svelte.d.ts +1 -0
  20. package/dist/annotation/editors/polygon/index.d.ts +2 -0
  21. package/dist/annotation/editors/polygon/index.d.ts.map +1 -0
  22. package/dist/annotation/editors/rectangle/RectangleEditor.svelte.d.ts +1 -0
  23. package/dist/annotation/editors/rectangle/index.d.ts +2 -0
  24. package/dist/annotation/editors/rectangle/index.d.ts.map +1 -0
  25. package/dist/annotation/index.d.ts +7 -0
  26. package/dist/annotation/index.d.ts.map +1 -0
  27. package/dist/annotation/shapes/Ellipse.svelte.d.ts +1 -0
  28. package/dist/annotation/shapes/Polygon.svelte.d.ts +1 -0
  29. package/dist/annotation/shapes/Rectangle.svelte.d.ts +1 -0
  30. package/dist/annotation/shapes/index.d.ts +4 -0
  31. package/dist/annotation/shapes/index.d.ts.map +1 -0
  32. package/dist/annotation/tools/DrawingToolConfig.d.ts +8 -0
  33. package/dist/annotation/tools/DrawingToolConfig.d.ts.map +1 -0
  34. package/dist/annotation/tools/ToolMount.svelte.d.ts +1 -0
  35. package/dist/annotation/tools/drawingToolsRegistry.d.ts +17 -0
  36. package/dist/annotation/tools/drawingToolsRegistry.d.ts.map +1 -0
  37. package/dist/annotation/tools/index.d.ts +5 -0
  38. package/dist/annotation/tools/index.d.ts.map +1 -0
  39. package/dist/annotation/tools/polygon/RubberbandPolygon.svelte.d.ts +1 -0
  40. package/dist/annotation/tools/polygon/index.d.ts +2 -0
  41. package/dist/annotation/tools/polygon/index.d.ts.map +1 -0
  42. package/dist/annotation/tools/rectangle/RubberbandRectangle.svelte.d.ts +1 -0
  43. package/dist/annotation/tools/rectangle/index.d.ts +2 -0
  44. package/dist/annotation/tools/rectangle/index.d.ts.map +1 -0
  45. package/dist/annotation/utils/index.d.ts +4 -0
  46. package/dist/annotation/utils/index.d.ts.map +1 -0
  47. package/dist/annotation/utils/math.d.ts +2 -0
  48. package/dist/annotation/utils/math.d.ts.map +1 -0
  49. package/dist/annotation/utils/responsive.d.ts +5 -0
  50. package/dist/annotation/utils/responsive.d.ts.map +1 -0
  51. package/dist/annotation/utils/styling.d.ts +4 -0
  52. package/dist/annotation/utils/styling.d.ts.map +1 -0
  53. package/dist/annotation/utils/touch.d.ts +2 -0
  54. package/dist/annotation/utils/touch.d.ts.map +1 -0
  55. package/dist/annotorious.css +1 -0
  56. package/dist/annotorious.es.js +3890 -0
  57. package/dist/annotorious.es.js.map +1 -0
  58. package/dist/annotorious.js +2 -0
  59. package/dist/annotorious.js.map +1 -0
  60. package/dist/index.d.ts +12 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/model/core/ImageAnnotation.d.ts +9 -0
  63. package/dist/model/core/ImageAnnotation.d.ts.map +1 -0
  64. package/dist/model/core/Shape.d.ts +20 -0
  65. package/dist/model/core/Shape.d.ts.map +1 -0
  66. package/dist/model/core/ellipse/Ellipse.d.ts +12 -0
  67. package/dist/model/core/ellipse/Ellipse.d.ts.map +1 -0
  68. package/dist/model/core/ellipse/ellipseUtils.d.ts +2 -0
  69. package/dist/model/core/ellipse/ellipseUtils.d.ts.map +1 -0
  70. package/dist/model/core/ellipse/index.d.ts +3 -0
  71. package/dist/model/core/ellipse/index.d.ts.map +1 -0
  72. package/dist/model/core/index.d.ts +7 -0
  73. package/dist/model/core/index.d.ts.map +1 -0
  74. package/dist/model/core/polygon/Polygon.d.ts +9 -0
  75. package/dist/model/core/polygon/Polygon.d.ts.map +1 -0
  76. package/dist/model/core/polygon/index.d.ts +3 -0
  77. package/dist/model/core/polygon/index.d.ts.map +1 -0
  78. package/dist/model/core/polygon/polygonUtils.d.ts +2 -0
  79. package/dist/model/core/polygon/polygonUtils.d.ts.map +1 -0
  80. package/dist/model/core/rectangle/Rectangle.d.ts +12 -0
  81. package/dist/model/core/rectangle/Rectangle.d.ts.map +1 -0
  82. package/dist/model/core/rectangle/index.d.ts +3 -0
  83. package/dist/model/core/rectangle/index.d.ts.map +1 -0
  84. package/dist/model/core/rectangle/rectangleUtils.d.ts +4 -0
  85. package/dist/model/core/rectangle/rectangleUtils.d.ts.map +1 -0
  86. package/dist/model/core/shapeUtils.d.ts +35 -0
  87. package/dist/model/core/shapeUtils.d.ts.map +1 -0
  88. package/dist/model/index.d.ts +3 -0
  89. package/dist/model/index.d.ts.map +1 -0
  90. package/dist/model/w3c/W3CImageFormatAdapter.d.ts +7 -0
  91. package/dist/model/w3c/W3CImageFormatAdapter.d.ts.map +1 -0
  92. package/dist/model/w3c/fragment/FragmentSelector.d.ts +10 -0
  93. package/dist/model/w3c/fragment/FragmentSelector.d.ts.map +1 -0
  94. package/dist/model/w3c/fragment/index.d.ts +2 -0
  95. package/dist/model/w3c/fragment/index.d.ts.map +1 -0
  96. package/dist/model/w3c/index.d.ts +4 -0
  97. package/dist/model/w3c/index.d.ts.map +1 -0
  98. package/dist/model/w3c/svg/SVG.d.ts +5 -0
  99. package/dist/model/w3c/svg/SVG.d.ts.map +1 -0
  100. package/dist/model/w3c/svg/SVGSelector.d.ts +9 -0
  101. package/dist/model/w3c/svg/SVGSelector.d.ts.map +1 -0
  102. package/dist/model/w3c/svg/index.d.ts +2 -0
  103. package/dist/model/w3c/svg/index.d.ts.map +1 -0
  104. package/dist/state/ImageAnnotationStore.d.ts +11 -0
  105. package/dist/state/ImageAnnotationStore.d.ts.map +1 -0
  106. package/dist/state/ImageAnnotatorState.d.ts +12 -0
  107. package/dist/state/ImageAnnotatorState.d.ts.map +1 -0
  108. package/dist/state/index.d.ts +3 -0
  109. package/dist/state/index.d.ts.map +1 -0
  110. package/dist/state/spatialTree.d.ts +21 -0
  111. package/dist/state/spatialTree.d.ts.map +1 -0
  112. package/dist/themes/index.d.ts +2 -0
  113. package/dist/themes/index.d.ts.map +1 -0
  114. package/dist/themes/smart/index.d.ts +2 -0
  115. package/dist/themes/smart/index.d.ts.map +1 -0
  116. package/dist/themes/smart/setTheme.d.ts +3 -0
  117. package/dist/themes/smart/setTheme.d.ts.map +1 -0
  118. package/package.json +55 -0
  119. package/src/Annotorious.css +74 -0
  120. package/src/Annotorious.ts +158 -0
  121. package/src/AnnotoriousOpts.ts +40 -0
  122. package/src/annotation/SVGAnnotationLayer.svelte +169 -0
  123. package/src/annotation/SVGAnnotationLayerPointerEvent.ts +55 -0
  124. package/src/annotation/Transform.ts +24 -0
  125. package/src/annotation/editors/Editor.svelte +61 -0
  126. package/src/annotation/editors/EditorMount.svelte +44 -0
  127. package/src/annotation/editors/Handle.ts +21 -0
  128. package/src/annotation/editors/editorsRegistry.ts +14 -0
  129. package/src/annotation/editors/index.ts +7 -0
  130. package/src/annotation/editors/polygon/PolygonEditor.svelte +64 -0
  131. package/src/annotation/editors/polygon/index.ts +1 -0
  132. package/src/annotation/editors/rectangle/RectangleEditor.svelte +143 -0
  133. package/src/annotation/editors/rectangle/index.ts +1 -0
  134. package/src/annotation/index.ts +7 -0
  135. package/src/annotation/shapes/Ellipse.svelte +32 -0
  136. package/src/annotation/shapes/Polygon.svelte +26 -0
  137. package/src/annotation/shapes/Rectangle.svelte +32 -0
  138. package/src/annotation/shapes/index.ts +3 -0
  139. package/src/annotation/tools/DrawingToolConfig.ts +9 -0
  140. package/src/annotation/tools/ToolMount.svelte +49 -0
  141. package/src/annotation/tools/drawingToolsRegistry.ts +26 -0
  142. package/src/annotation/tools/index.ts +4 -0
  143. package/src/annotation/tools/polygon/RubberbandPolygon.svelte +165 -0
  144. package/src/annotation/tools/polygon/index.ts +1 -0
  145. package/src/annotation/tools/rectangle/RubberbandRectangle.svelte +131 -0
  146. package/src/annotation/tools/rectangle/index.ts +1 -0
  147. package/src/annotation/utils/index.ts +3 -0
  148. package/src/annotation/utils/math.ts +6 -0
  149. package/src/annotation/utils/responsive.ts +56 -0
  150. package/src/annotation/utils/styling.ts +19 -0
  151. package/src/annotation/utils/touch.ts +1 -0
  152. package/src/index.ts +20 -0
  153. package/src/model/core/ImageAnnotation.ts +14 -0
  154. package/src/model/core/Shape.ts +37 -0
  155. package/src/model/core/ellipse/Ellipse.ts +21 -0
  156. package/src/model/core/ellipse/ellipseUtils.ts +28 -0
  157. package/src/model/core/ellipse/index.ts +2 -0
  158. package/src/model/core/index.ts +6 -0
  159. package/src/model/core/polygon/Polygon.ts +15 -0
  160. package/src/model/core/polygon/index.ts +2 -0
  161. package/src/model/core/polygon/polygonUtils.ts +43 -0
  162. package/src/model/core/rectangle/Rectangle.ts +21 -0
  163. package/src/model/core/rectangle/index.ts +2 -0
  164. package/src/model/core/rectangle/rectangleUtils.ts +17 -0
  165. package/src/model/core/shapeUtils.ts +57 -0
  166. package/src/model/index.ts +2 -0
  167. package/src/model/w3c/W3CImageFormatAdapter.ts +83 -0
  168. package/src/model/w3c/fragment/FragmentSelector.ts +59 -0
  169. package/src/model/w3c/fragment/index.ts +1 -0
  170. package/src/model/w3c/index.ts +3 -0
  171. package/src/model/w3c/svg/SVG.ts +36 -0
  172. package/src/model/w3c/svg/SVGSelector.ts +99 -0
  173. package/src/model/w3c/svg/index.ts +1 -0
  174. package/src/state/ImageAnnotationStore.ts +18 -0
  175. package/src/state/ImageAnnotatorState.ts +88 -0
  176. package/src/state/index.ts +2 -0
  177. package/src/state/spatialTree.ts +108 -0
  178. package/src/themes/dark/index.css +24 -0
  179. package/src/themes/index.ts +1 -0
  180. package/src/themes/light/index.css +30 -0
  181. package/src/themes/smart/index.ts +1 -0
  182. package/src/themes/smart/setTheme.ts +46 -0
  183. package/src/vite-env.d.ts +2 -0
@@ -0,0 +1,158 @@
1
+ import type { SvelteComponent } from 'svelte';
2
+ import type { Annotator, DrawingStyle, User } from '@annotorious/core';
3
+ import { createAnonymousGuest, createBaseAnnotator, createLifecyleObserver } from '@annotorious/core';
4
+ import { registerEditor } from './annotation/editors';
5
+ import { getTool, registerTool, listDrawingTools, type DrawingTool } from './annotation/tools';
6
+ import { SVGAnnotationLayer } from './annotation';
7
+ import type { DrawingToolOpts, SVGAnnotationLayerPointerEvent } from './annotation';
8
+ import type { ImageAnnotation, ShapeType } from './model';
9
+ import { createSvelteImageAnnotatorState } from './state';
10
+ import { setTheme } from './themes';
11
+ import { fillDefaults } from './AnnotoriousOpts';
12
+ import type { AnnotoriousOpts } from './AnnotoriousOpts';
13
+
14
+ import './Annotorious.css';
15
+ import './themes/dark/index.css';
16
+ import './themes/light/index.css';
17
+
18
+ export interface ImageAnnotator<E extends unknown = ImageAnnotation> extends Annotator<ImageAnnotation, E> {
19
+
20
+ listDrawingTools(): string[];
21
+
22
+ registerDrawingTool(name: string, tool: typeof SvelteComponent, opts?: DrawingToolOpts): void;
23
+
24
+ registerShapeEditor(shapeType: ShapeType, editor: typeof SvelteComponent): void;
25
+
26
+ setDrawingTool(tool: DrawingTool): void;
27
+
28
+ setDrawingEnabled(enabled: boolean): void;
29
+
30
+ }
31
+
32
+ export const createImageAnnotator = <E extends unknown = ImageAnnotation>(
33
+ image: string | HTMLImageElement | HTMLCanvasElement,
34
+ options: AnnotoriousOpts<ImageAnnotation, E> = {}
35
+ ): ImageAnnotator<E> => {
36
+
37
+ if (!image)
38
+ throw 'Missing argument: image';
39
+
40
+ const img = (typeof image === 'string' ?
41
+ document.getElementById(image) : image) as HTMLImageElement | HTMLCanvasElement;
42
+
43
+ const opts = fillDefaults<ImageAnnotation, E>(options);
44
+
45
+ const state = createSvelteImageAnnotatorState(opts);
46
+
47
+ const { hover, selection, store } = state;
48
+
49
+ const lifecycle = createLifecyleObserver<ImageAnnotation, E>(
50
+ store, selection, hover, undefined, opts.adapter, opts.autoSave);
51
+
52
+ let currentUser: User = createAnonymousGuest();
53
+
54
+ let style = opts.style;
55
+
56
+ // We'll wrap the image in a container DIV.
57
+ const container = document.createElement('DIV');
58
+ container.style.position = 'relative';
59
+ container.style.display = 'inline-block';
60
+
61
+ // Wrapper div has unwanted margin at the bottom otherwise!
62
+ img.style.display = 'block';
63
+
64
+ img.parentNode.insertBefore(container, img);
65
+ container.appendChild(img);
66
+
67
+ setTheme(img, container);
68
+
69
+ const annotationLayer = new SVGAnnotationLayer({
70
+ target: container,
71
+ props: {
72
+ drawingEnabled: opts.drawingEnabled,
73
+ image: img,
74
+ preferredDrawingMode: opts.drawingMode,
75
+ state,
76
+ style,
77
+ user: currentUser
78
+ }
79
+ });
80
+
81
+ annotationLayer.$on('click', (evt: CustomEvent<SVGAnnotationLayerPointerEvent>) => {
82
+ const { originalEvent, annotation } = evt.detail;
83
+ if (annotation)
84
+ selection.clickSelect(annotation.id, originalEvent);
85
+ else if (!selection.isEmpty())
86
+ selection.clear();
87
+ });
88
+
89
+ /*************************/
90
+ /* External API */
91
+ /******++++++*************/
92
+
93
+ // Most of the external API functions are covered in the base annotator
94
+ const base = createBaseAnnotator<ImageAnnotation, E>(store, opts.adapter);
95
+
96
+ const setStyle = (drawingStyle: DrawingStyle | ((annotation: ImageAnnotation) => DrawingStyle) | undefined) => {
97
+ style = drawingStyle;
98
+ annotationLayer.$set({ style });
99
+ }
100
+
101
+ const destroy = () => {
102
+ // Destroy Svelte annotation layer
103
+ annotationLayer.$destroy();
104
+
105
+ // Unwrap the image
106
+ container.parentNode.insertBefore(img, container);
107
+ container.parentNode.removeChild(container);
108
+ }
109
+
110
+ const getUser = () => currentUser;
111
+
112
+ const registerDrawingTool = (name: string, tool: typeof SvelteComponent, opts?: DrawingToolOpts) =>
113
+ registerTool(name, tool, opts);
114
+
115
+ const registerShapeEditor = (shapeType: ShapeType, editor: typeof SvelteComponent) =>
116
+ registerEditor(shapeType, editor);
117
+
118
+ const setDrawingTool = (t: DrawingTool) => {
119
+ const { tool, opts } = getTool(t);
120
+ // @ts-ignore
121
+ annotationLayer.$set({ tool, opts })
122
+ }
123
+
124
+ const setDrawingEnabled = (enabled: boolean) =>
125
+ annotationLayer.$set({ drawingEnabled: enabled });
126
+
127
+ const setSelected = (arg?: string | string[]) => {
128
+ if (arg) {
129
+ selection.setSelected(arg);
130
+ } else {
131
+ selection.clear();
132
+ }
133
+ }
134
+
135
+ const setUser = (user: User) => {
136
+ currentUser = user;
137
+ annotationLayer.$set({ user });
138
+ }
139
+
140
+ return {
141
+ ...base,
142
+ get style() { return style },
143
+ set style(s: DrawingStyle | ((annotation: ImageAnnotation) => DrawingStyle) | undefined) { setStyle(s) },
144
+ destroy,
145
+ getUser,
146
+ listDrawingTools,
147
+ on: lifecycle.on,
148
+ off: lifecycle.off,
149
+ registerDrawingTool,
150
+ registerShapeEditor,
151
+ setDrawingEnabled,
152
+ setDrawingTool,
153
+ setSelected,
154
+ setUser,
155
+ state
156
+ }
157
+
158
+ }
@@ -0,0 +1,40 @@
1
+ import { PointerSelectAction } from '@annotorious/core';
2
+ import type { Annotation, DrawingStyle, FormatAdapter } from '@annotorious/core';
3
+ import type { ImageAnnotation } from './model';
4
+
5
+ export interface AnnotoriousOpts<I extends Annotation = ImageAnnotation, E extends unknown = ImageAnnotation> {
6
+
7
+ adapter?: FormatAdapter<I, E>;
8
+
9
+ autoSave?: boolean;
10
+
11
+ drawingEnabled?: boolean;
12
+
13
+ // 'click': starts on single click, user cannot select unless drawingEnabled = false
14
+ // 'drag': starts drawing on drag, single click always selects
15
+ drawingMode?: DrawingMode;
16
+
17
+ pointerSelectAction?: PointerSelectAction | ((a: I) => PointerSelectAction);
18
+
19
+ style?: DrawingStyle | ((annotation: I) => DrawingStyle);
20
+
21
+ }
22
+
23
+ export type DrawingMode = 'click' | 'drag';
24
+
25
+ export const fillDefaults = <I extends ImageAnnotation = ImageAnnotation, E extends unknown = ImageAnnotation> (
26
+ opts: AnnotoriousOpts<I, E>
27
+ ): AnnotoriousOpts<I, E> => {
28
+
29
+ return {
30
+ ...opts,
31
+ drawingEnabled: opts.drawingEnabled === undefined ? true : opts.drawingEnabled,
32
+ drawingMode: opts.drawingMode || 'drag',
33
+ pointerSelectAction: opts.pointerSelectAction || PointerSelectAction.EDIT
34
+ };
35
+
36
+ };
37
+
38
+
39
+
40
+
@@ -0,0 +1,169 @@
1
+ <script type="ts">
2
+ import { onMount, type SvelteComponent } from 'svelte';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ import type { DrawingStyle, StoreChangeEvent, User } from '@annotorious/core';
5
+ import { ShapeType } from '../model';
6
+ import type { ImageAnnotation, Shape} from '../model';
7
+ import { getEditor, EditorMount } from './editors';
8
+ import { Ellipse, Polygon, Rectangle} from './shapes';
9
+ import { getTool, ToolMount } from './tools';
10
+ import { enableResponsive } from './utils';
11
+ import { createSVGTransform } from './Transform';
12
+ import { addEventListeners } from './SVGAnnotationLayerPointerEvent';
13
+ import type { SvelteImageAnnotatorState } from 'src/state';
14
+ import type { DrawingMode } from 'src/AnnotoriousOpts';
15
+
16
+ /** Props **/
17
+ export let drawingEnabled: boolean;
18
+ export let image: HTMLImageElement | HTMLCanvasElement;
19
+ export let preferredDrawingMode: DrawingMode;
20
+ export let state: SvelteImageAnnotatorState;
21
+ export let style: DrawingStyle | ((annotation: ImageAnnotation) => DrawingStyle) = undefined;
22
+ export let { tool, opts } = getTool('rectangle');
23
+ export let user: User;
24
+
25
+ $: drawingMode = opts?.drawingMode || preferredDrawingMode;
26
+
27
+ /** Drawing tool layer **/
28
+ let drawingEl: SVGGElement;
29
+
30
+ /** Responsive scaling **/
31
+ let svgEl: SVGSVGElement;
32
+
33
+ let scale: ReturnType<typeof enableResponsive>;
34
+
35
+ onMount(() => scale = enableResponsive(image, svgEl));
36
+
37
+ $: transform = createSVGTransform(svgEl);
38
+
39
+ /** Selection tracking */
40
+ const { selection, store } = state;
41
+
42
+ $: ({ onPointerDown, onPointerUp } = addEventListeners(svgEl, store));
43
+
44
+ let storeObserver = null;
45
+
46
+ let editableAnnotations: ImageAnnotation[] = null;
47
+
48
+ $: isEditable = (a: ImageAnnotation) => $selection.selected.find(s => s.id === a.id && s.editable);
49
+
50
+ $: trackSelection($selection.selected);
51
+
52
+ const trackSelection = (selected: { id: string, editable?: boolean }[]) => {
53
+ store.unobserve(storeObserver);
54
+
55
+ // Track only editable annotations
56
+ const editableIds =
57
+ selected.filter(({ editable }) => editable).map(({ id }) => id);
58
+
59
+ if (editableIds.length > 0) {
60
+ // Resolve selected IDs from the store
61
+ editableAnnotations = editableIds.map(id => store.getAnnotation(id));
62
+
63
+ // Track updates on the editable annotations
64
+ storeObserver = (event: StoreChangeEvent<ImageAnnotation>) => {
65
+ const { updated } = event.changes;
66
+ editableAnnotations = updated.map(change => change.newValue);
67
+ }
68
+
69
+ store.observe(storeObserver, { annotations: editableIds });
70
+ } else {
71
+ editableAnnotations = null;
72
+ }
73
+ }
74
+
75
+ const onSelectionCreated = <T extends Shape>(evt: CustomEvent<T>) => {
76
+ const id = uuidv4();
77
+
78
+ const annotation: ImageAnnotation = {
79
+ id,
80
+ bodies: [],
81
+ target: {
82
+ annotation: id,
83
+ selector: evt.detail,
84
+ creator: user,
85
+ created: new Date()
86
+ }
87
+ };
88
+
89
+ store.addAnnotation(annotation);
90
+
91
+ selection.setSelected(annotation.id);
92
+ }
93
+
94
+ const onChangeSelected = (annotation: ImageAnnotation) => (event: CustomEvent<Shape>) => {
95
+ const { target } = annotation;
96
+
97
+ // We don't consider a shape edit an 'update' if it happens within 10mins
98
+ const GRACE_PERIOD = 10 * 60 * 1000;
99
+
100
+ const isUpdate =
101
+ target.creator?.id !== user.id ||
102
+ !target.created ||
103
+ new Date().getTime() - target.created.getTime() > GRACE_PERIOD;
104
+
105
+ store.updateTarget({
106
+ ...target,
107
+ selector: event.detail,
108
+ created: isUpdate ? target.created : new Date(),
109
+ updated: isUpdate ? new Date() : null,
110
+ updatedBy: isUpdate ? user : null
111
+ });
112
+ }
113
+ </script>
114
+
115
+ <svg
116
+ bind:this={svgEl}
117
+ class="a9s-annotationlayer"
118
+ class:drawing={tool}
119
+ on:pointerup={onPointerUp}
120
+ on:pointerdown={onPointerDown}>
121
+
122
+ <g>
123
+ {#each $store as annotation}
124
+ {#if !isEditable(annotation)}
125
+ {@const selector = annotation.target.selector}
126
+ {#key annotation.id}
127
+ {#if (selector.type === ShapeType.ELLIPSE)}
128
+ <Ellipse annotation={annotation} geom={selector.geometry} style={style} />
129
+ {:else if (selector.type === ShapeType.RECTANGLE)}
130
+ <Rectangle annotation={annotation} geom={selector.geometry} style={style} />
131
+ {:else if (selector.type === ShapeType.POLYGON)}
132
+ <Polygon annotation={annotation} geom={selector.geometry} style={style} />
133
+ {/if}
134
+ {/key}
135
+ {/if}
136
+ {/each}
137
+ </g>
138
+
139
+ <g
140
+ bind:this={drawingEl}
141
+ class="drawing" >
142
+ {#if drawingEl}
143
+ {#if editableAnnotations}
144
+ {#each editableAnnotations as editable}
145
+ {#key editable.id}
146
+ <EditorMount
147
+ target={drawingEl}
148
+ editor={getEditor(editable.target.selector)}
149
+ annotation={editable}
150
+ style={style}
151
+ transform={transform}
152
+ viewportScale={$scale}
153
+ on:change={onChangeSelected(editable)} />
154
+ {/key}
155
+ {/each}
156
+ {:else if (tool && drawingEnabled)}
157
+ {#key tool}
158
+ <ToolMount
159
+ target={drawingEl}
160
+ tool={tool}
161
+ drawingMode={drawingMode}
162
+ transform={transform}
163
+ viewportScale={$scale}
164
+ on:create={onSelectionCreated} />
165
+ {/key}
166
+ {/if}
167
+ {/if}
168
+ </g>
169
+ </svg>
@@ -0,0 +1,55 @@
1
+ import { createEventDispatcher } from 'svelte';
2
+ import type { SvelteImageAnnotationStore } from '../state';
3
+ import type { ImageAnnotation } from '../model';
4
+ import { isTouch } from './utils';
5
+
6
+ export interface SVGAnnotationLayerPointerEvent {
7
+
8
+ originalEvent: PointerEvent;
9
+
10
+ annotation?: ImageAnnotation;
11
+
12
+ }
13
+
14
+ // Maximum amount of ms between pointer down and up to make it a click
15
+ const MAX_CLICK_DURATION = 250;
16
+
17
+ export const addEventListeners = (svg: SVGSVGElement, store: SvelteImageAnnotationStore) => {
18
+ const dispatch = createEventDispatcher<{ click: SVGAnnotationLayerPointerEvent}>();
19
+
20
+ let lastPointerDown: number;
21
+
22
+ const onPointerDown = () =>
23
+ lastPointerDown = performance.now();
24
+
25
+ const onPointerUp = (evt: PointerEvent) => {
26
+ const duration = performance.now() - lastPointerDown;
27
+
28
+ if (duration < MAX_CLICK_DURATION) {
29
+ const { x, y } = getSVGPoint(evt, svg);
30
+
31
+ const annotation = store.getAt(x, y);
32
+
33
+ if (annotation)
34
+ dispatch('click', { originalEvent: evt, annotation });
35
+ else
36
+ dispatch('click', { originalEvent: evt });
37
+ }
38
+ }
39
+
40
+ return { onPointerDown, onPointerUp };
41
+ }
42
+
43
+ const getSVGPoint = (evt: PointerEvent, svg: SVGSVGElement) => {
44
+ const pt = svg.createSVGPoint();
45
+ const bbox = svg.getBoundingClientRect();
46
+
47
+ const x = evt.clientX - bbox.x;
48
+ const y = evt.clientY - bbox.y;
49
+
50
+ const { left, top } = svg.getBoundingClientRect();
51
+ pt.x = x + left;
52
+ pt.y = y + top;
53
+
54
+ return pt.matrixTransform(svg.getScreenCTM().inverse());
55
+ }
@@ -0,0 +1,24 @@
1
+ export interface Transform {
2
+
3
+ elementToImage: (offsetX: number, offsetY: number) => [ number, number ]
4
+
5
+ }
6
+
7
+ export const IdentityTransform: Transform = {
8
+
9
+ elementToImage: (offsetX: number, offsetY: number) => ([ offsetX, offsetY ])
10
+
11
+ }
12
+
13
+ export const createSVGTransform = (svg: SVGSVGElement): Transform => ({
14
+
15
+ elementToImage: (offsetX: number, offsetY: number) => {
16
+ const pt = svg.createSVGPoint();
17
+ pt.x = offsetX;
18
+ pt.y = offsetY;
19
+
20
+ const { x, y } = pt.matrixTransform(svg.getCTM().inverse());
21
+ return [x, y];
22
+ }
23
+
24
+ });
@@ -0,0 +1,61 @@
1
+ <script type="ts">
2
+ import { createEventDispatcher } from 'svelte';
3
+ import type { Shape } from '../../model';
4
+ import type { Handle } from './Handle';
5
+ import type { Transform } from '../Transform';
6
+
7
+ const dispatch = createEventDispatcher<{ grab: undefined, release: undefined, change: Shape }>();
8
+
9
+ /** Props */
10
+ export let shape: Shape;
11
+ export let editor: (shape: Shape, handle: Handle, delta: [number, number]) => Shape;
12
+ export let transform: Transform;
13
+
14
+ let grabbedHandle: Handle = null;
15
+
16
+ let origin: [number, number];
17
+
18
+ let initialShape: Shape = null;
19
+
20
+ const onGrab = (handle: Handle) => (evt: PointerEvent) => {
21
+ grabbedHandle = handle;
22
+ origin = transform.elementToImage(evt.offsetX, evt.offsetY);
23
+ initialShape = shape;
24
+
25
+ const target = evt.target as Element;
26
+ target.setPointerCapture(evt.pointerId);
27
+
28
+ dispatch('grab');
29
+ }
30
+
31
+ const onPointerMove = (evt: PointerEvent) => {
32
+ if (grabbedHandle) {
33
+ const [x, y] = transform.elementToImage(evt.offsetX, evt.offsetY);
34
+
35
+ const delta: [number, number] = [x - origin[0], y - origin[1]];
36
+
37
+ shape = editor(initialShape, grabbedHandle, delta);
38
+
39
+ dispatch('change', shape);
40
+ }
41
+ }
42
+
43
+ const onRelease = (evt: PointerEvent) => {
44
+ const target = evt.target as Element;
45
+ target.releasePointerCapture(evt.pointerId);
46
+
47
+ grabbedHandle = null;
48
+
49
+ initialShape = shape;
50
+
51
+ dispatch('release');
52
+ }
53
+ </script>
54
+
55
+ <g
56
+ class="a9s-annotation selected"
57
+ on:pointerup={onRelease}
58
+ on:pointermove={onPointerMove}>
59
+
60
+ <slot grab={onGrab} />
61
+ </g>
@@ -0,0 +1,44 @@
1
+ <script lang="ts">
2
+ import type { DrawingStyle } from '@annotorious/core';
3
+ import { createEventDispatcher, onMount, type SvelteComponent } from 'svelte';
4
+ import type { ImageAnnotation, Shape } from '../../model';
5
+ import { computeStyle } from '../utils/styling';
6
+ import type { Transform } from '../Transform';
7
+
8
+ const dispatch = createEventDispatcher<{ grab: undefined, release: undefined, change: Shape }>();
9
+
10
+ /** Props */
11
+ export let annotation: ImageAnnotation;
12
+ export let editor: typeof SvelteComponent;
13
+ export let style: DrawingStyle | ((annotation: ImageAnnotation) => DrawingStyle) = undefined;
14
+ export let target: SVGGElement;
15
+ export let transform: Transform;
16
+ export let viewportScale: number;
17
+
18
+ let editorComponent: SvelteComponent;
19
+
20
+ $: computedStyle = computeStyle(annotation, style);
21
+
22
+ $: if (editorComponent) editorComponent.$set({ transform });
23
+
24
+ $: if (editorComponent) editorComponent.$set({ viewportScale });
25
+
26
+ onMount(() => {
27
+ editorComponent = new editor({
28
+ target,
29
+ props: { shape: annotation.target.selector, computedStyle, transform, viewportScale }
30
+ });
31
+
32
+ editorComponent.$on('change', event => {
33
+ editorComponent.$$set({ shape: event.detail });
34
+ dispatch('change', event.detail);
35
+ });
36
+
37
+ editorComponent.$on('grab', event => dispatch('grab', event.detail));
38
+ editorComponent.$on('release', event => dispatch('release', event.detail));
39
+
40
+ return () => {
41
+ editorComponent.$destroy();
42
+ }
43
+ });
44
+ </script>
@@ -0,0 +1,21 @@
1
+ export type Handle = string;
2
+
3
+ export const Handle = (value: string | number) => `HANDLE-${value}`;
4
+
5
+ Handle.SHAPE = 'SHAPE';
6
+
7
+ Handle.TOP = 'TOP';
8
+
9
+ Handle.RIGHT = 'RIGHT';
10
+
11
+ Handle.BOTTOM = 'BOTTOM';
12
+
13
+ Handle.LEFT = 'LEFT';
14
+
15
+ Handle.TOP_LEFT = 'TOP_LEFT';
16
+
17
+ Handle.TOP_RIGHT = 'TOP_RIGHT';
18
+
19
+ Handle.BOTTOM_RIGHT = 'BOTTOM_RIGHT';
20
+
21
+ Handle.BOTTOM_LEFT = 'BOTTOM_LEFT';
@@ -0,0 +1,14 @@
1
+ import { ShapeType, type Shape } from '../../model';
2
+ import type { SvelteComponent } from 'svelte';
3
+ import { PolygonEditor } from './polygon';
4
+ import { RectangleEditor } from './rectangle';
5
+
6
+ const REGISTERED = new Map<ShapeType, typeof SvelteComponent>([
7
+ [ShapeType.RECTANGLE, RectangleEditor],
8
+ [ShapeType.POLYGON, PolygonEditor]
9
+ ]);
10
+
11
+ export const getEditor = (shape: Shape) => REGISTERED.get(shape.type);
12
+
13
+ export const registerEditor = (shapeType: ShapeType, editor: typeof SvelteComponent) =>
14
+ REGISTERED.set(shapeType, editor);
@@ -0,0 +1,7 @@
1
+ export * from './polygon';
2
+ export * from './rectangle';
3
+ export * from './editorsRegistry';
4
+ export * from './Handle';
5
+
6
+ export { default as Editor } from './Editor.svelte';
7
+ export { default as EditorMount } from './EditorMount.svelte';
@@ -0,0 +1,64 @@
1
+ <script type="ts">
2
+ import { boundsFromPoints } from '../../../model';
3
+ import type { Polygon } from '../../../model';
4
+ import type { Transform } from '../../Transform';
5
+ import { Editor, Handle } from '..';
6
+
7
+ /** Props */
8
+ export let shape: Polygon;
9
+ export let computedStyle: string = undefined;
10
+ export let transform: Transform;
11
+ export let viewportScale: number = 1;
12
+
13
+ $: geom = shape.geometry;
14
+
15
+ $: handleSize = 10 / viewportScale;
16
+
17
+ const editor = (polygon: Polygon, handle: Handle, delta: [number, number]) => {
18
+ let points: [number, number][];
19
+
20
+ if (handle === Handle.SHAPE) {
21
+ points = polygon.geometry.points.map(([x, y]) => [x + delta[0], y + delta[1]]);
22
+ } else {
23
+ points = polygon.geometry.points.map(([x, y], idx) =>
24
+ handle === Handle(idx) ? [x + delta[0], y + delta[1]] : [x, y]
25
+ );
26
+ }
27
+
28
+ const bounds = boundsFromPoints(points);
29
+
30
+ return {
31
+ ...polygon,
32
+ geometry: { points, bounds }
33
+ }
34
+ }
35
+ </script>
36
+
37
+ <Editor
38
+ shape={shape}
39
+ transform={transform}
40
+ editor={editor}
41
+ on:change
42
+ on:grab
43
+ on:release
44
+ let:grab={grab}>
45
+
46
+ <polygon
47
+ class="a9s-outer"
48
+ style={computedStyle ? 'display:none;' : undefined}
49
+ on:pointerdown={grab(Handle.SHAPE)}
50
+ points={geom.points.map(xy => xy.join(',')).join(' ')} />
51
+
52
+ <polygon
53
+ class="a9s-inner a9s-shape-handle"
54
+ style={computedStyle}
55
+ on:pointerdown={grab(Handle.SHAPE)}
56
+ points={geom.points.map(xy => xy.join(',')).join(' ')} />
57
+
58
+ {#each geom.points as point, idx}
59
+ <rect
60
+ class="a9s-corner-handle"
61
+ on:pointerdown={grab(Handle(idx))}
62
+ x={point[0] - handleSize / 2} y={point[1] - handleSize / 2} height={handleSize} width={handleSize} />
63
+ {/each}
64
+ </Editor>
@@ -0,0 +1 @@
1
+ export { default as PolygonEditor } from './PolygonEditor.svelte';