@viamrobotics/test-widgets 0.0.1 → 0.1.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.
Files changed (50) hide show
  1. package/dist/components/index.d.ts +2 -4
  2. package/dist/components/index.js +2 -4
  3. package/dist/components/maplibre/controls/center.svelte +13 -10
  4. package/dist/components/maplibre/controls/follow.svelte +9 -7
  5. package/dist/components/maplibre/controls/navigation.svelte +6 -4
  6. package/dist/components/maplibre/controls/satellite.svelte +6 -4
  7. package/dist/components/maplibre/{hooks.d.ts → hooks.svelte.d.ts} +9 -14
  8. package/dist/components/maplibre/{hooks.js → hooks.svelte.js} +31 -16
  9. package/dist/components/maplibre/index.d.ts +2 -2
  10. package/dist/components/maplibre/index.js +2 -2
  11. package/dist/components/maplibre/index.svelte +9 -2
  12. package/dist/components/maplibre/marker.svelte +5 -3
  13. package/dist/components/maplibre/plugins/{raycast.js → raycast.svelte.js} +8 -7
  14. package/dist/components/maplibre/plugins/three.svelte.js +8 -6
  15. package/dist/components/navigation-map/components/draw-tool.svelte +15 -8
  16. package/dist/components/navigation-map/components/nav/index.svelte +1 -1
  17. package/dist/components/navigation-map/components/nav/key.svelte +1 -1
  18. package/dist/components/navigation-map/components/nav/obstacles-legend.svelte +1 -1
  19. package/dist/components/navigation-map/components/nav/obstacles.svelte +5 -3
  20. package/dist/components/navigation-map/components/nav/waypoints.svelte +4 -2
  21. package/dist/components/navigation-map/components/obstacle.svelte +18 -12
  22. package/dist/components/navigation-map/plugins/interactivity.js +4 -2
  23. package/dist/components/widgets/arm/move-to-joint-positions.svelte +1 -0
  24. package/dist/components/widgets/arm/move-to-position.svelte +1 -0
  25. package/dist/components/widgets/do-command/do-command.svelte +14 -4
  26. package/dist/components/widgets/navigation/obstacles/scene.svelte +4 -2
  27. package/dist/components/widgets/switch/position-view.svelte +6 -6
  28. package/dist/components/widgets/vision-service/context.svelte.js +6 -6
  29. package/package.json +116 -116
  30. package/dist/components/image-annotations/annotation-edit-utils.d.ts +0 -21
  31. package/dist/components/image-annotations/annotation-edit-utils.js +0 -183
  32. package/dist/components/image-annotations/bounding-box-labeler.svelte +0 -521
  33. package/dist/components/image-annotations/bounding-box-labeler.svelte.d.ts +0 -51
  34. package/dist/components/image-annotations/bounding-box-types.d.ts +0 -29
  35. package/dist/components/image-annotations/bounding-box-types.js +0 -17
  36. package/dist/components/image-annotations/bounding-box.svelte +0 -230
  37. package/dist/components/image-annotations/bounding-box.svelte.d.ts +0 -44
  38. package/dist/components/image-annotations/get-image-size.d.ts +0 -7
  39. package/dist/components/image-annotations/get-image-size.js +0 -29
  40. package/dist/components/image-annotations/labeler-constants.d.ts +0 -4
  41. package/dist/components/image-annotations/labeler-constants.js +0 -4
  42. package/dist/components/image-annotations/resize-corner.svelte +0 -47
  43. package/dist/components/image-annotations/resize-corner.svelte.d.ts +0 -9
  44. package/dist/components/image-annotations/resize-edge.svelte +0 -51
  45. package/dist/components/image-annotations/resize-edge.svelte.d.ts +0 -9
  46. package/dist/components/image-annotations/resize-handle-props.d.ts +0 -60
  47. package/dist/components/image-annotations/resize-handle-props.js +0 -66
  48. package/dist/components/image-annotations/resize-handles.svelte +0 -53
  49. package/dist/components/image-annotations/resize-handles.svelte.d.ts +0 -8
  50. /package/dist/components/maplibre/plugins/{raycast.d.ts → raycast.svelte.d.ts} +0 -0
@@ -1,183 +0,0 @@
1
- import { BoxModifier, ResizeHandleLocation, } from './bounding-box-types';
2
- export const getBoundingRect = (original, dragDistance, containerWidth, containerHeight, corner) => {
3
- let transformation = BoxModifier.Scaled;
4
- if (dragDistance) {
5
- transformation = corner ? BoxModifier.Resized : BoxModifier.Translated;
6
- }
7
- // scale annotation size to container
8
- const scaledCopy = { ...original };
9
- scaledCopy.xMinNormalized *= containerWidth;
10
- scaledCopy.xMaxNormalized *= containerWidth;
11
- scaledCopy.yMinNormalized *= containerHeight;
12
- scaledCopy.yMaxNormalized *= containerHeight;
13
- if (transformation === BoxModifier.Scaled || !dragDistance) {
14
- return scaledCopy;
15
- }
16
- // apply transformation
17
- const unboundedRect = transformation === BoxModifier.Resized && corner
18
- ? getResizedRect(scaledCopy, dragDistance, corner)
19
- : getTranslatedRect(scaledCopy, dragDistance, containerWidth, containerHeight);
20
- // ensure transformation does not exceed boundaries of container
21
- return {
22
- xMinNormalized: Math.max(unboundedRect.xMinNormalized, 0),
23
- xMaxNormalized: Math.min(unboundedRect.xMaxNormalized, containerWidth),
24
- yMinNormalized: Math.max(unboundedRect.yMinNormalized, 0),
25
- yMaxNormalized: Math.min(unboundedRect.yMaxNormalized, containerHeight),
26
- };
27
- };
28
- const getTranslatedRect = (original, dragDistance, containerWidth, containerHeight) => {
29
- const modifiedRect = {
30
- xMinNormalized: original.xMinNormalized + dragDistance.x,
31
- xMaxNormalized: original.xMaxNormalized + dragDistance.x,
32
- yMinNormalized: original.yMinNormalized + dragDistance.y,
33
- yMaxNormalized: original.yMaxNormalized + dragDistance.y,
34
- };
35
- // maintains box size if user attempts to drag a box beyond the bounds of the image
36
- if (modifiedRect.xMinNormalized <= 0) {
37
- modifiedRect.xMaxNormalized -= modifiedRect.xMinNormalized;
38
- modifiedRect.xMinNormalized = 0;
39
- }
40
- if (modifiedRect.yMinNormalized <= 0) {
41
- modifiedRect.yMaxNormalized -= modifiedRect.yMinNormalized;
42
- modifiedRect.yMinNormalized = 0;
43
- }
44
- if (modifiedRect.xMaxNormalized >= containerWidth) {
45
- modifiedRect.xMinNormalized =
46
- containerWidth - (modifiedRect.xMaxNormalized - modifiedRect.xMinNormalized);
47
- modifiedRect.xMaxNormalized = containerWidth;
48
- }
49
- if (modifiedRect.yMaxNormalized >= containerHeight) {
50
- modifiedRect.yMinNormalized =
51
- containerHeight - (modifiedRect.yMaxNormalized - modifiedRect.yMinNormalized);
52
- modifiedRect.yMaxNormalized = containerHeight;
53
- }
54
- return modifiedRect;
55
- };
56
- const getResizedRect = (original, dragDistance, corner) => {
57
- let variableX = undefined;
58
- let variableY = undefined;
59
- switch (corner) {
60
- case ResizeHandleLocation.T: {
61
- variableY = 'yMinNormalized';
62
- break;
63
- }
64
- case ResizeHandleLocation.R: {
65
- variableX = 'xMaxNormalized';
66
- break;
67
- }
68
- case ResizeHandleLocation.B: {
69
- variableY = 'yMaxNormalized';
70
- break;
71
- }
72
- case ResizeHandleLocation.L: {
73
- variableX = 'xMinNormalized';
74
- break;
75
- }
76
- case ResizeHandleLocation.TL: {
77
- variableX = 'xMinNormalized';
78
- variableY = 'yMinNormalized';
79
- break;
80
- }
81
- case ResizeHandleLocation.TR: {
82
- variableX = 'xMaxNormalized';
83
- variableY = 'yMinNormalized';
84
- break;
85
- }
86
- case ResizeHandleLocation.BL: {
87
- variableX = 'xMinNormalized';
88
- variableY = 'yMaxNormalized';
89
- break;
90
- }
91
- case ResizeHandleLocation.BR: {
92
- variableX = 'xMaxNormalized';
93
- variableY = 'yMaxNormalized';
94
- break;
95
- }
96
- }
97
- const modifiedRect = {
98
- ...original,
99
- };
100
- if (variableX) {
101
- modifiedRect[variableX] = original[variableX] + dragDistance.x;
102
- }
103
- if (variableY) {
104
- modifiedRect[variableY] = original[variableY] + dragDistance.y;
105
- }
106
- if (modifiedRect.xMinNormalized > modifiedRect.xMaxNormalized) {
107
- const temp = modifiedRect.xMaxNormalized;
108
- modifiedRect.xMaxNormalized = modifiedRect.xMinNormalized;
109
- modifiedRect.xMinNormalized = temp;
110
- }
111
- if (modifiedRect.yMinNormalized > modifiedRect.yMaxNormalized) {
112
- const temp = modifiedRect.yMaxNormalized;
113
- modifiedRect.yMaxNormalized = modifiedRect.yMinNormalized;
114
- modifiedRect.yMinNormalized = temp;
115
- }
116
- return modifiedRect;
117
- };
118
- export const shouldDisplayBoxFill = (annotationID, inEditMode, focusedID, editTargetID, hoveredAnnotations) => {
119
- return (editTargetID === annotationID ||
120
- ((focusedID ? focusedID === annotationID : Boolean(hoveredAnnotations[annotationID])) &&
121
- !inEditMode));
122
- };
123
- export const createNormalizedTempBox = (label, size, firstPoint, finalPoint) => {
124
- // coordinates start in center, must translate all coordinates by one quadrant (i.e. half the height and width of container)
125
- const xMinNormalized = Math.min(firstPoint.x, finalPoint.x) / size.width;
126
- const xMaxNormalized = Math.max(firstPoint.x, finalPoint.x) / size.width;
127
- const yMinNormalized = Math.min(firstPoint.y, finalPoint.y) / size.height;
128
- const yMaxNormalized = Math.max(firstPoint.y, finalPoint.y) / size.height;
129
- return xMaxNormalized - xMinNormalized > 0.01 && yMaxNormalized - yMinNormalized > 0.01
130
- ? {
131
- // if normalized values exceed bounds of image, lock to edge by setting to min/max value (0 and 1 respectively)
132
- xMinNormalized: Math.max(0, xMinNormalized),
133
- xMaxNormalized: Math.min(1, xMaxNormalized),
134
- yMinNormalized: Math.max(0, yMinNormalized),
135
- yMaxNormalized: Math.min(1, yMaxNormalized),
136
- label,
137
- id: '__NEW_TEMP_BBOX_ID__',
138
- }
139
- : undefined;
140
- };
141
- export const getModifyKeyForOS = () => {
142
- const isMac = navigator.userAgent.includes('Mac');
143
- return isMac ? 'metaKey' : 'ctrlKey';
144
- };
145
- export const calculatePastedBox = (mousePosition, copyTarget, containerWidth, containerHeight) => {
146
- const boxWidth = copyTarget.xMaxNormalized - copyTarget.xMinNormalized;
147
- const boxHeight = copyTarget.yMaxNormalized - copyTarget.yMinNormalized;
148
- // creates box centered on current mouse position
149
- const tempBox = {
150
- xMin: mousePosition.x / containerWidth - boxWidth / 2,
151
- xMax: mousePosition.x / containerWidth + boxWidth / 2,
152
- yMin: mousePosition.y / containerHeight - boxHeight / 2,
153
- yMax: mousePosition.y / containerHeight + boxHeight / 2,
154
- };
155
- /**
156
- * if side exceeds boundary of image, set exceeding side equal to boundary
157
- * then translate parallel side by the same distance
158
- */
159
- if (tempBox.xMin < 0) {
160
- tempBox.xMax -= tempBox.xMin;
161
- tempBox.xMin = 0;
162
- }
163
- if (tempBox.yMin < 0) {
164
- tempBox.yMax -= tempBox.yMin;
165
- tempBox.yMin = 0;
166
- }
167
- if (tempBox.xMax > 1) {
168
- tempBox.xMin -= tempBox.xMax - 1;
169
- tempBox.xMax = 1;
170
- }
171
- if (tempBox.yMax > 1) {
172
- tempBox.yMin -= tempBox.yMax - 1;
173
- tempBox.yMax = 1;
174
- }
175
- return {
176
- id: '',
177
- label: copyTarget.label,
178
- xMinNormalized: tempBox.xMin,
179
- xMaxNormalized: tempBox.xMax,
180
- yMinNormalized: tempBox.yMin,
181
- yMaxNormalized: tempBox.yMax,
182
- };
183
- };
@@ -1,521 +0,0 @@
1
- <script lang="ts">
2
- import { resize } from '@svelte-put/resize'
3
- import { clickOutside, Progress } from '@viamrobotics/prime-core'
4
- import { onMount, untrack } from 'svelte'
5
-
6
- import { getImageSize } from '../widgets/vision-service/get-image-size'
7
-
8
- import type {
9
- BoundingBox as BoundingBoxData,
10
- BoundingCoords,
11
- Coordinates,
12
- ResizeHandleLocation,
13
- } from './bounding-box-types'
14
-
15
- import {
16
- calculatePastedBox,
17
- createNormalizedTempBox,
18
- getBoundingRect,
19
- getModifyKeyForOS,
20
- } from './annotation-edit-utils'
21
- import BoundingBox from './bounding-box.svelte'
22
- import { getImageSize as getImageSizeDynamic, type Size } from './get-image-size'
23
- import {
24
- INVERT_X_FLOAT_DISTANCE,
25
- INVERT_Y_FLOAT_DISTANCE,
26
- ZOOM_MAX,
27
- ZOOM_MIN,
28
- } from './labeler-constants'
29
- import { ResizeHandleClassMap } from './resize-handle-props'
30
-
31
- const MODIFY_KEY = getModifyKeyForOS()
32
-
33
- interface Props {
34
- /** whether to allow drawing of new bounding boxes */
35
- allowDrawing: boolean
36
- /** inference candidates displayed using readonly bounding boxes */
37
- candidateBoundingBoxes?: BoundingBoxData[]
38
- /** currently selected box */
39
- editTarget?: BoundingBoxData | undefined
40
- /** map of annotations displayed in their hovered states */
41
- hoveredAnnotations: Record<string, boolean>
42
- /** whether image is loading */
43
- imageLoading: boolean
44
- /** URI of image */
45
- imageSrc: string
46
- /** used to set the max size of the display container, used for bounding boxes */
47
- maxSize?: { maxHeight: number; maxWidth: number }
48
- /** whether to allow any interactivity (i.e. drawing, editing, deleting) */
49
- readonly?: boolean
50
- /** bounding boxes saved on the image */
51
- savedBoundingBoxes: BoundingBoxData[]
52
- /** user selected label to apply to drawn boxes */
53
- selectedLabel?: string
54
- /** whether to display candidates and saved boxes or just candidates */
55
- showSavedBoundingBoxes?: boolean
56
- /** how much image is zoomed */
57
- zoom?: number
58
- /** accept the selected inference candidate box */
59
- acceptCandidate?: (id: string) => void
60
- /** saves drawn bounding box to image */
61
- createBoundingBox?: (newBox: BoundingBoxData) => void
62
- /** deletes saved bounding box */
63
- deleteBoundingBox?: (annotationID: string, targetLabel: string) => void
64
- /** save changes to existing box */
65
- editBoundingBox?: (id: string, newBox: BoundingBoxData) => void
66
- /** invoked when displayed image loaded */
67
- onImageLoad?: VoidFunction
68
- /** reject the selected inference candidate box */
69
- rejectCandidate?: (id: string) => void
70
- /** set which box is being edited */
71
- setEditTarget?: (target: BoundingBoxData | undefined) => void
72
- /** used to change which boxes are hovered */
73
- setHoveredAnnotations?: (newHovered: Record<string, boolean>) => void
74
- /** sets zoom */
75
- setZoom?: (value: number) => void
76
- }
77
-
78
- const {
79
- allowDrawing,
80
- candidateBoundingBoxes,
81
- editTarget,
82
- hoveredAnnotations,
83
- imageLoading,
84
- imageSrc,
85
- maxSize,
86
- readonly = false,
87
- savedBoundingBoxes,
88
- selectedLabel,
89
- showSavedBoundingBoxes = true,
90
- zoom = 1,
91
- acceptCandidate,
92
- createBoundingBox,
93
- deleteBoundingBox,
94
- editBoundingBox,
95
- onImageLoad,
96
- rejectCandidate,
97
- setEditTarget,
98
- setHoveredAnnotations,
99
- setZoom,
100
- }: Props = $props()
101
-
102
- let dataURI = $state<string>()
103
- const img = $state(document.createElement('img'))
104
- let container = $state<HTMLElement>()
105
- let size = $state<Size>()
106
- let firstClickedPoint = $state<Coordinates>()
107
- let normalizedDrawnBox = $state<BoundingBoxData>()
108
- let drawingContainer = $state<HTMLDivElement>()
109
- let floatingCoords = $state<HTMLDivElement>()
110
- let currentMousePosition = $state<Coordinates>({ x: 0, y: 0 })
111
- let activeResizeHandle = $state<ResizeHandleLocation>()
112
- let inEditMode = $state(false)
113
- let showMouseCoords = $state(false)
114
- let copiedBox = $state<BoundingBoxData>()
115
- let allowPaste = $state(true)
116
-
117
- const dragDistance = $derived(
118
- firstClickedPoint
119
- ? {
120
- x: currentMousePosition.x - firstClickedPoint.x,
121
- y: currentMousePosition.y - firstClickedPoint.y,
122
- }
123
- : undefined
124
- )
125
-
126
- const getOffset = (el: HTMLElement | undefined) => {
127
- if (el) {
128
- const { height, width } = el.getBoundingClientRect()
129
- return { x: width, y: height }
130
- }
131
- return undefined
132
- }
133
-
134
- let zoomOffset = $derived(getOffset(drawingContainer))
135
-
136
- const setSize = () => {
137
- if (maxSize) {
138
- size = getImageSize(img, maxSize)
139
- } else if (container) {
140
- size = getImageSizeDynamic(img, container)
141
- }
142
-
143
- if (drawingContainer) {
144
- zoomOffset = getOffset(drawingContainer)
145
- drawingContainer?.scrollIntoView()
146
- }
147
- }
148
-
149
- const backgroundImageLoad = () => {
150
- setSize()
151
-
152
- // gets image binary from img element
153
- const cvs = document.createElement('canvas')
154
- cvs.width = img.width
155
- cvs.height = img.height
156
- cvs.getContext('2d')?.drawImage(img, 0, 0)
157
- dataURI = cvs.toDataURL()
158
- }
159
-
160
- const resetDrawing = (clearTarget: boolean | undefined = true) => {
161
- if (clearTarget) {
162
- setEditTarget?.(undefined)
163
- }
164
- firstClickedPoint = undefined
165
- normalizedDrawnBox = undefined
166
- activeResizeHandle = undefined
167
- inEditMode = false
168
- }
169
-
170
- onMount(() => {
171
- img.addEventListener('load', backgroundImageLoad)
172
- globalThis.addEventListener('mousemove', handleOverlayMove)
173
- globalThis.addEventListener('mouseup', handleMouseUp)
174
- globalThis.addEventListener('keydown', handleKeyDown)
175
- return () => {
176
- img.removeEventListener('load', backgroundImageLoad)
177
- globalThis.removeEventListener('mouseup', handleMouseUp)
178
- globalThis.removeEventListener('keydown', handleKeyDown)
179
- globalThis.removeEventListener('mousemove', handleOverlayMove)
180
- }
181
- })
182
-
183
- $effect.pre(() => {
184
- if (imageSrc) {
185
- img.src = imageSrc
186
- }
187
- img.crossOrigin = 'anonymous'
188
- })
189
-
190
- $effect.pre(() => {
191
- void container
192
- setSize()
193
- })
194
-
195
- $effect.pre(() => {
196
- if (allowDrawing) {
197
- setHoveredAnnotations?.({})
198
- } else {
199
- resetDrawing()
200
- }
201
- })
202
-
203
- const getRelativeMousePosition = (event: MouseEvent): Coordinates => {
204
- if (drawingContainer) {
205
- const boundingRect = drawingContainer.getBoundingClientRect()
206
- const { clientX, clientY } = event
207
-
208
- const { left, top } = boundingRect
209
- const x = (clientX - left) / zoom
210
- const y = (clientY - top) / zoom
211
-
212
- return { x, y }
213
- }
214
-
215
- return { x: 0, y: 0 }
216
- }
217
-
218
- const sendEditAnnotationRequest = (annotation: BoundingBoxData) => {
219
- editBoundingBox?.(annotation.id, annotation)
220
- }
221
-
222
- const findAndSetTarget = (id: string | undefined) => {
223
- setEditTarget?.(savedBoundingBoxes.find((ann) => ann.id === id))
224
- }
225
-
226
- const handleMouseDown = (event: MouseEvent) => {
227
- if (readonly) {
228
- return
229
- }
230
-
231
- if (editTarget) {
232
- setEditTarget?.(undefined)
233
- } else if (allowDrawing) {
234
- firstClickedPoint = getRelativeMousePosition(event)
235
- }
236
- }
237
-
238
- const handleMouseUp = () => {
239
- if (readonly) {
240
- return
241
- }
242
-
243
- if (editTarget && inEditMode && size) {
244
- if (dragDistance) {
245
- // in edit mode
246
- const boundingRect = getBoundingRect(
247
- editTarget,
248
- dragDistance,
249
- size.width,
250
- size.height,
251
- activeResizeHandle
252
- )
253
-
254
- const normalized: BoundingCoords = {
255
- xMinNormalized: boundingRect.xMinNormalized / size.width,
256
- xMaxNormalized: boundingRect.xMaxNormalized / size.width,
257
- yMinNormalized: boundingRect.yMinNormalized / size.height,
258
- yMaxNormalized: boundingRect.yMaxNormalized / size.height,
259
- }
260
-
261
- const editedBox = {
262
- ...editTarget,
263
- ...normalized,
264
- }
265
-
266
- resetDrawing(false)
267
- sendEditAnnotationRequest(editedBox)
268
- } else {
269
- resetDrawing(false)
270
- }
271
- } else {
272
- // in create mode
273
- if (normalizedDrawnBox) {
274
- createBoundingBox?.(normalizedDrawnBox)
275
- }
276
- resetDrawing(true)
277
- }
278
- }
279
-
280
- // show coordinates and allow paste only when over image
281
- const handleMove = () => {
282
- if (readonly) {
283
- return
284
- }
285
-
286
- showMouseCoords = true
287
- allowPaste = true
288
- }
289
-
290
- // track mouse movement over entire screen
291
- const handleOverlayMove = (event: MouseEvent) => {
292
- const coords = getRelativeMousePosition(event)
293
-
294
- currentMousePosition = coords
295
-
296
- if (firstClickedPoint && size && !editTarget) {
297
- // prevent mouse from highlighting when dragging over text outside of image, but only if a point on the image has been clicked first
298
- event.preventDefault()
299
- normalizedDrawnBox = createNormalizedTempBox(
300
- selectedLabel ?? '',
301
- size,
302
- firstClickedPoint,
303
- coords
304
- )
305
- }
306
- }
307
-
308
- const onMouseDownAnnotation = (
309
- event: MouseEvent,
310
- annotationID: string,
311
- corner?: ResizeHandleLocation
312
- ) => {
313
- event.preventDefault()
314
- if (readonly) {
315
- return
316
- }
317
-
318
- if (annotationID !== editTarget?.id) {
319
- findAndSetTarget(annotationID)
320
- }
321
- inEditMode = true
322
- firstClickedPoint = getRelativeMousePosition(event)
323
- activeResizeHandle = corner
324
- }
325
-
326
- // sorting by distance from top so that index can represent a top-to-bottom tab order
327
- const sortedCandidatesByTop = $derived(
328
- candidateBoundingBoxes
329
- ? candidateBoundingBoxes.toSorted((boxA, boxB) => boxA.yMinNormalized - boxB.yMinNormalized)
330
- : []
331
- )
332
-
333
- const addToHovered = (id: string) => {
334
- setHoveredAnnotations?.({ ...hoveredAnnotations, [id]: true })
335
- }
336
-
337
- const removeFromHovered = (id: string) => {
338
- setHoveredAnnotations?.({ ...hoveredAnnotations, [id]: false })
339
- }
340
-
341
- const handleWheel = (event: WheelEvent) => {
342
- if (event.metaKey && zoom <= ZOOM_MAX && zoom >= ZOOM_MIN) {
343
- showMouseCoords = false
344
- const { deltaY } = event
345
- event.preventDefault()
346
- const newZoom = zoom + deltaY / 100
347
-
348
- if (newZoom > ZOOM_MAX) {
349
- setZoom?.(ZOOM_MAX)
350
- return
351
- }
352
-
353
- if (newZoom < ZOOM_MIN) {
354
- setZoom?.(ZOOM_MIN)
355
- return
356
- }
357
-
358
- setZoom?.(newZoom)
359
- }
360
- }
361
-
362
- $effect(() => {
363
- void zoom
364
- untrack(() => {
365
- container?.scrollTo({
366
- left: currentMousePosition.x * (zoom - 1),
367
- top: currentMousePosition.y * (zoom - 1),
368
- behavior: 'instant',
369
- })
370
- })
371
- })
372
-
373
- const handleLeave = () => {
374
- showMouseCoords = false
375
- allowPaste = false
376
- }
377
-
378
- const handleKeyDown = (event: KeyboardEvent) => {
379
- if (event[MODIFY_KEY] && event.code === 'KeyV' && size && copiedBox && allowPaste) {
380
- const pastedBox = calculatePastedBox(currentMousePosition, copiedBox, size.width, size.height)
381
-
382
- createBoundingBox?.(pastedBox)
383
- }
384
- }
385
-
386
- const copyBoundingBox = () => {
387
- copiedBox = editTarget
388
- }
389
-
390
- const floatingHeight = $derived(floatingCoords?.getBoundingClientRect().height ?? 0)
391
- </script>
392
-
393
- <div
394
- class={[
395
- 'flex h-full min-h-0 w-full min-w-0 grow flex-col items-center justify-center',
396
- { 'overflow-x-auto overflow-y-auto': !maxSize },
397
- ]}
398
- bind:this={container}
399
- use:resize
400
- onwheel={handleWheel}
401
- onresized={setSize}
402
- style:cursor={activeResizeHandle ? ResizeHandleClassMap[activeResizeHandle].cursor : undefined}
403
- >
404
- {#if size && dataURI}
405
- <!-- svelte-ignore a11y_no_static_element_interactions -->
406
- <div
407
- class="relative h-fit w-fit"
408
- style:left={zoom > 1 && zoomOffset ? `${(zoomOffset.x * (zoom - 1)) / 2}px` : ''}
409
- style:top={zoom > 1 && zoomOffset ? `${(zoomOffset.y * (zoom - 1)) / 2}px` : ''}
410
- bind:this={drawingContainer}
411
- onmousedown={handleMouseDown}
412
- onmousemove={handleMove}
413
- onmouseleave={handleLeave}
414
- use:clickOutside={() => resetDrawing()}
415
- style:scale={zoom}
416
- >
417
- <div
418
- bind:this={floatingCoords}
419
- class={[
420
- 'z-max absolute origin-bottom-left rounded-md bg-black px-1.5',
421
- {
422
- hidden: !showMouseCoords,
423
- 'origin-bottom-left': currentMousePosition.y > INVERT_Y_FLOAT_DISTANCE / zoom,
424
- 'origin-top-left': currentMousePosition.y <= INVERT_Y_FLOAT_DISTANCE / zoom,
425
- },
426
- ]}
427
- style:left={`${currentMousePosition.x + (size.width - currentMousePosition.x < INVERT_X_FLOAT_DISTANCE / zoom ? -95 : 4) / zoom}px`}
428
- style:top={`${currentMousePosition.y + (currentMousePosition.y > INVERT_Y_FLOAT_DISTANCE / zoom ? -1 * (floatingHeight + 5 / zoom) : 5 / zoom)}px`}
429
- style:scale={1 / zoom}
430
- >
431
- <span class="font-roboto-mono text-xs whitespace-nowrap text-white"
432
- >{`{${Math.round(currentMousePosition.x)},${Math.round(currentMousePosition.y)}}`}</span
433
- >
434
- </div>
435
- <img
436
- class={['z-0 select-none', { invisible: imageLoading }]}
437
- height={size.height}
438
- width={size.width}
439
- src={dataURI}
440
- alt={imageSrc}
441
- crossorigin="anonymous"
442
- onload={onImageLoad}
443
- />
444
- {#if imageLoading}
445
- <div class="absolute top-0 flex h-full w-full items-center justify-center">
446
- <Progress size="large" />
447
- </div>
448
- {:else}
449
- <div
450
- style={`height: ${size.height}px; width: ${size.width}px;`}
451
- class="absolute top-0 z-10 overflow-hidden"
452
- >
453
- {#if normalizedDrawnBox}
454
- <BoundingBox
455
- active={true}
456
- annotation={normalizedDrawnBox}
457
- allowInteractivity={false}
458
- containerHeight={size.height}
459
- containerWidth={size.width}
460
- readonly={true}
461
- selected={false}
462
- isPending={false}
463
- {zoom}
464
- onEscapePress={resetDrawing}
465
- />
466
- {/if}
467
- {#each sortedCandidatesByTop as candidate, index (candidate.id)}
468
- <BoundingBox
469
- active={Boolean(hoveredAnnotations[candidate.id])}
470
- allowInteractivity={true}
471
- annotation={candidate}
472
- containerHeight={size.height}
473
- containerWidth={size.width}
474
- {dragDistance}
475
- isPending={true}
476
- {readonly}
477
- tabIndex={index + 1}
478
- {zoom}
479
- {addToHovered}
480
- onEscapePress={resetDrawing}
481
- onBackspacePress={rejectCandidate}
482
- onEnterPress={acceptCandidate}
483
- {removeFromHovered}
484
- />
485
- {/each}
486
- {#if showSavedBoundingBoxes}
487
- {#each savedBoundingBoxes as boundingBox, index (boundingBox.id)}
488
- <BoundingBox
489
- active={Boolean(hoveredAnnotations[boundingBox.id])}
490
- {activeResizeHandle}
491
- allowInteractivity={!allowDrawing}
492
- annotation={boundingBox}
493
- containerHeight={size.height}
494
- containerWidth={size.width}
495
- {dragDistance}
496
- isPending={false}
497
- {readonly}
498
- selected={editTarget?.id === boundingBox.id}
499
- tabIndex={index + 1}
500
- {zoom}
501
- {addToHovered}
502
- onBackspacePress={deleteBoundingBox}
503
- onCopyPress={copyBoundingBox}
504
- onEscapePress={resetDrawing}
505
- onMouseDown={onMouseDownAnnotation}
506
- {removeFromHovered}
507
- />
508
- {/each}
509
- {/if}
510
- </div>
511
- {/if}
512
- </div>
513
- {:else}
514
- <div
515
- class="flex w-full items-center justify-center"
516
- style:height={maxSize?.maxHeight ? `${maxSize?.maxHeight}px` : '250px'}
517
- >
518
- <Progress size="large" />
519
- </div>
520
- {/if}
521
- </div>