annotate-image 2.0.0-beta.1 → 2.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core.js +173 -41
- package/dist/core.min.js +1 -1
- package/dist/css/annotate.min.css +1 -1
- package/dist/jquery.js +172 -41
- package/dist/jquery.min.js +1 -1
- package/dist/react.js +174 -42
- package/dist/types/annotate-edit.d.ts +2 -0
- package/dist/types/annotate-image.d.ts +42 -0
- package/dist/types/annotate-view.d.ts +1 -1
- package/dist/types/positioning.d.ts +31 -0
- package/dist/types/react.d.ts +2 -0
- package/dist/types/types.d.ts +4 -0
- package/dist/types/vue.d.ts +11 -0
- package/dist/vue.js +176 -42
- package/package.json +12 -12
- package/readme.md +111 -2
package/dist/jquery.js
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
|
+
// src/positioning.ts
|
|
2
|
+
function clampNote(note, naturalWidth, naturalHeight) {
|
|
3
|
+
const width = Math.min(note.width, naturalWidth);
|
|
4
|
+
const height = Math.min(note.height, naturalHeight);
|
|
5
|
+
const left = Math.max(0, Math.min(note.left, naturalWidth - width));
|
|
6
|
+
const top = Math.max(0, Math.min(note.top, naturalHeight - height));
|
|
7
|
+
return { top, left, width, height };
|
|
8
|
+
}
|
|
9
|
+
function clampNotes(notes, naturalWidth, naturalHeight) {
|
|
10
|
+
for (const note of notes) {
|
|
11
|
+
Object.assign(note, clampNote(note, naturalWidth, naturalHeight));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function computeNoteLeft(noteWidth, areaLeftInViewport, areaWidth, viewportWidth) {
|
|
15
|
+
let left = (areaWidth - noteWidth) / 2;
|
|
16
|
+
const noteLeftInViewport = areaLeftInViewport + left;
|
|
17
|
+
const noteRightInViewport = noteLeftInViewport + noteWidth;
|
|
18
|
+
if (noteRightInViewport > viewportWidth) {
|
|
19
|
+
left -= noteRightInViewport - viewportWidth;
|
|
20
|
+
}
|
|
21
|
+
const adjustedNoteLeft = areaLeftInViewport + left;
|
|
22
|
+
if (adjustedNoteLeft < 0) {
|
|
23
|
+
left -= adjustedNoteLeft;
|
|
24
|
+
}
|
|
25
|
+
return left;
|
|
26
|
+
}
|
|
27
|
+
|
|
1
28
|
// src/annotate-edit.ts
|
|
2
29
|
var DEFAULT_NOTE_TOP = 30;
|
|
3
30
|
var DEFAULT_NOTE_LEFT = 30;
|
|
@@ -27,10 +54,11 @@ var AnnotateEdit = class {
|
|
|
27
54
|
};
|
|
28
55
|
}
|
|
29
56
|
this.area = image.editOverlay.querySelector(".image-annotate-edit-area");
|
|
30
|
-
|
|
31
|
-
this.area.style.
|
|
32
|
-
this.area.style.
|
|
33
|
-
this.area.style.
|
|
57
|
+
const rendered = image.toRendered(this.note);
|
|
58
|
+
this.area.style.height = rendered.height + "px";
|
|
59
|
+
this.area.style.width = rendered.width + "px";
|
|
60
|
+
this.area.style.left = rendered.left + "px";
|
|
61
|
+
this.area.style.top = rendered.top + "px";
|
|
34
62
|
this.form = document.createElement("div");
|
|
35
63
|
this.form.className = "image-annotate-edit-form";
|
|
36
64
|
const formEl = document.createElement("form");
|
|
@@ -46,6 +74,8 @@ var AnnotateEdit = class {
|
|
|
46
74
|
formEl.appendChild(this.textarea);
|
|
47
75
|
this.form.appendChild(formEl);
|
|
48
76
|
this.area.appendChild(this.form);
|
|
77
|
+
this.positionForm();
|
|
78
|
+
this.textarea.focus();
|
|
49
79
|
this.form.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
50
80
|
const area = this.area;
|
|
51
81
|
const applyRect = (rect) => {
|
|
@@ -57,7 +87,10 @@ var AnnotateEdit = class {
|
|
|
57
87
|
this.handlers.makeResizable(area, {
|
|
58
88
|
containment: image.canvas,
|
|
59
89
|
onResize: applyRect,
|
|
60
|
-
onStop:
|
|
90
|
+
onStop: (rect) => {
|
|
91
|
+
applyRect(rect);
|
|
92
|
+
this.positionForm();
|
|
93
|
+
}
|
|
61
94
|
});
|
|
62
95
|
this.handlers.makeDraggable(area, {
|
|
63
96
|
containment: image.canvas,
|
|
@@ -68,9 +101,9 @@ var AnnotateEdit = class {
|
|
|
68
101
|
onStop: (pos) => {
|
|
69
102
|
area.style.left = pos.left + "px";
|
|
70
103
|
area.style.top = pos.top + "px";
|
|
104
|
+
this.positionForm();
|
|
71
105
|
}
|
|
72
106
|
});
|
|
73
|
-
this.textarea.focus();
|
|
74
107
|
this.form.addEventListener("keydown", (e) => {
|
|
75
108
|
if (e.key === "Escape") {
|
|
76
109
|
const cancelBtn = this.form.querySelector(".image-annotate-edit-close");
|
|
@@ -86,6 +119,13 @@ var AnnotateEdit = class {
|
|
|
86
119
|
}
|
|
87
120
|
this.addCancelButton(buttonRow);
|
|
88
121
|
}
|
|
122
|
+
/** Recompute the form's horizontal position relative to the area. */
|
|
123
|
+
positionForm() {
|
|
124
|
+
const formRect = this.form.getBoundingClientRect();
|
|
125
|
+
const areaRect = this.area.getBoundingClientRect();
|
|
126
|
+
const left = computeNoteLeft(formRect.width, areaRect.left, areaRect.width, window.innerWidth);
|
|
127
|
+
this.form.style.left = left + "px";
|
|
128
|
+
}
|
|
89
129
|
/** Tear down the edit form and interaction handlers. */
|
|
90
130
|
destroy() {
|
|
91
131
|
this.image.activeEdit = null;
|
|
@@ -117,13 +157,20 @@ var AnnotateEdit = class {
|
|
|
117
157
|
}
|
|
118
158
|
this.image.notifySave(stripInternals(this.note));
|
|
119
159
|
this.destroy();
|
|
160
|
+
this.image.flushPendingRescale();
|
|
120
161
|
};
|
|
121
162
|
const pos = readInlinePosition(this.area);
|
|
122
163
|
const size = readInlineSize(this.area);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
164
|
+
const natural = this.image.toNatural({
|
|
165
|
+
top: pos.top,
|
|
166
|
+
left: pos.left,
|
|
167
|
+
width: size.width,
|
|
168
|
+
height: size.height
|
|
169
|
+
});
|
|
170
|
+
this.note.top = natural.top;
|
|
171
|
+
this.note.left = natural.left;
|
|
172
|
+
this.note.width = natural.width;
|
|
173
|
+
this.note.height = natural.height;
|
|
127
174
|
this.note.text = text;
|
|
128
175
|
if (this.image.api.save) {
|
|
129
176
|
this.busy = true;
|
|
@@ -157,6 +204,7 @@ var AnnotateEdit = class {
|
|
|
157
204
|
const idx = this.image.notes.indexOf(this.note);
|
|
158
205
|
if (idx !== -1) this.image.notes.splice(idx, 1);
|
|
159
206
|
this.image.notifyDelete(stripInternals(this.note));
|
|
207
|
+
this.image.flushPendingRescale();
|
|
160
208
|
};
|
|
161
209
|
if (this.image.api.delete) {
|
|
162
210
|
this.busy = true;
|
|
@@ -232,36 +280,42 @@ var AnnotateView = class {
|
|
|
232
280
|
});
|
|
233
281
|
}
|
|
234
282
|
}
|
|
235
|
-
/** Apply the note's position and dimensions to the area element. */
|
|
283
|
+
/** Apply the note's position and dimensions to the area element, scaled to rendered size. */
|
|
236
284
|
setPosition() {
|
|
285
|
+
const rendered = this.image.toRendered(this.note);
|
|
237
286
|
const innerDiv = this.area.firstElementChild;
|
|
238
|
-
innerDiv.style.height =
|
|
239
|
-
innerDiv.style.width =
|
|
240
|
-
this.area.style.left =
|
|
241
|
-
this.area.style.top =
|
|
287
|
+
innerDiv.style.height = rendered.height + "px";
|
|
288
|
+
innerDiv.style.width = rendered.width + "px";
|
|
289
|
+
this.area.style.left = rendered.left + "px";
|
|
290
|
+
this.area.style.top = rendered.top + "px";
|
|
242
291
|
}
|
|
243
292
|
/** Update the view's position, size, and text from the edit area after a save. */
|
|
244
293
|
resetPosition(editable, text) {
|
|
245
294
|
this.tooltip.textContent = text;
|
|
246
295
|
this.tooltip.style.display = "none";
|
|
247
|
-
const
|
|
248
|
-
const areaSize = readInlineSize(editable.area);
|
|
296
|
+
const rendered = this.image.toRendered(editable.note);
|
|
249
297
|
const innerDiv = this.area.firstElementChild;
|
|
250
|
-
innerDiv.style.height =
|
|
251
|
-
innerDiv.style.width =
|
|
252
|
-
this.area.style.left =
|
|
253
|
-
this.area.style.top =
|
|
254
|
-
this.note.top =
|
|
255
|
-
this.note.left =
|
|
256
|
-
this.note.height =
|
|
257
|
-
this.note.width =
|
|
298
|
+
innerDiv.style.height = rendered.height + "px";
|
|
299
|
+
innerDiv.style.width = rendered.width + "px";
|
|
300
|
+
this.area.style.left = rendered.left + "px";
|
|
301
|
+
this.area.style.top = rendered.top + "px";
|
|
302
|
+
this.note.top = editable.note.top;
|
|
303
|
+
this.note.left = editable.note.left;
|
|
304
|
+
this.note.height = editable.note.height;
|
|
305
|
+
this.note.width = editable.note.width;
|
|
258
306
|
this.note.text = text;
|
|
259
307
|
this.note.id = editable.note.id;
|
|
260
308
|
this.editable = true;
|
|
261
309
|
}
|
|
262
310
|
/** Show the tooltip and apply hover styling. */
|
|
263
311
|
show() {
|
|
312
|
+
this.tooltip.style.visibility = "hidden";
|
|
264
313
|
this.tooltip.style.display = "block";
|
|
314
|
+
const noteRect = this.tooltip.getBoundingClientRect();
|
|
315
|
+
const areaRect = this.area.getBoundingClientRect();
|
|
316
|
+
const left = computeNoteLeft(noteRect.width, areaRect.left, areaRect.width, window.innerWidth);
|
|
317
|
+
this.tooltip.style.left = left + "px";
|
|
318
|
+
this.tooltip.style.visibility = "";
|
|
265
319
|
if (!this.editable) {
|
|
266
320
|
this.area.classList.add("image-annotate-area-hover");
|
|
267
321
|
} else {
|
|
@@ -359,17 +413,19 @@ function destroyDraggable(el) {
|
|
|
359
413
|
var MIN_SIZE = 10;
|
|
360
414
|
var CORNERS = ["nw", "ne", "sw", "se"];
|
|
361
415
|
function computeResize(corner, startLeft, startTop, startWidth, startHeight, dx, dy) {
|
|
362
|
-
let left
|
|
416
|
+
let left, top, width, height;
|
|
363
417
|
if (corner === "nw" || corner === "sw") {
|
|
364
418
|
left = startLeft + dx;
|
|
365
419
|
width = startWidth - dx;
|
|
366
420
|
} else {
|
|
421
|
+
left = startLeft;
|
|
367
422
|
width = startWidth + dx;
|
|
368
423
|
}
|
|
369
424
|
if (corner === "nw" || corner === "ne") {
|
|
370
425
|
top = startTop + dy;
|
|
371
426
|
height = startHeight - dy;
|
|
372
427
|
} else {
|
|
428
|
+
top = startTop;
|
|
373
429
|
height = startHeight + dy;
|
|
374
430
|
}
|
|
375
431
|
if (width < MIN_SIZE) {
|
|
@@ -524,17 +580,30 @@ var AnnotateImage = class {
|
|
|
524
580
|
this._mode = "view";
|
|
525
581
|
this.activeEdit = null;
|
|
526
582
|
this.destroyed = false;
|
|
583
|
+
this.pendingRescale = false;
|
|
584
|
+
this.originalParent = null;
|
|
585
|
+
this.originalNextSibling = null;
|
|
527
586
|
this.options = options;
|
|
528
587
|
this.handlers = createDefaultHandlers();
|
|
529
588
|
this.img = img;
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
589
|
+
this.naturalWidth = img.naturalWidth || img.width;
|
|
590
|
+
this.naturalHeight = img.naturalHeight || img.height;
|
|
591
|
+
const rendered = img.getBoundingClientRect();
|
|
592
|
+
const renderedWidth = rendered.width || img.width;
|
|
593
|
+
const renderedHeight = rendered.height || img.height;
|
|
594
|
+
if (this.naturalWidth === 0 || this.naturalHeight === 0) {
|
|
533
595
|
throw new Error("image-annotate: image must have non-zero dimensions (is the image loaded?)");
|
|
534
596
|
}
|
|
597
|
+
this.scaleX = renderedWidth / this.naturalWidth;
|
|
598
|
+
this.scaleY = renderedHeight / this.naturalHeight;
|
|
535
599
|
this.notes = options.notes.map((n) => ({ ...n }));
|
|
600
|
+
this.originalParent = img.parentNode;
|
|
601
|
+
this.originalNextSibling = img.nextSibling;
|
|
536
602
|
this.canvas = document.createElement("div");
|
|
537
603
|
this.canvas.className = "image-annotate-canvas";
|
|
604
|
+
if (options.theme) {
|
|
605
|
+
this.canvas.dataset.theme = options.theme;
|
|
606
|
+
}
|
|
538
607
|
this.viewOverlay = document.createElement("div");
|
|
539
608
|
this.viewOverlay.className = "image-annotate-view";
|
|
540
609
|
this.editOverlay = document.createElement("div");
|
|
@@ -543,19 +612,13 @@ var AnnotateImage = class {
|
|
|
543
612
|
const editArea = document.createElement("div");
|
|
544
613
|
editArea.className = "image-annotate-edit-area";
|
|
545
614
|
this.editOverlay.appendChild(editArea);
|
|
546
|
-
this.canvas.appendChild(this.viewOverlay);
|
|
547
|
-
this.canvas.appendChild(this.editOverlay);
|
|
548
615
|
if (!img.parentNode) {
|
|
549
616
|
throw new Error("image-annotate: image must be in the DOM before initialization");
|
|
550
617
|
}
|
|
551
|
-
img.parentNode.insertBefore(this.canvas, img
|
|
552
|
-
this.canvas.
|
|
553
|
-
this.canvas.
|
|
554
|
-
this.canvas.
|
|
555
|
-
this.viewOverlay.style.height = height + "px";
|
|
556
|
-
this.viewOverlay.style.width = width + "px";
|
|
557
|
-
this.editOverlay.style.height = height + "px";
|
|
558
|
-
this.editOverlay.style.width = width + "px";
|
|
618
|
+
img.parentNode.insertBefore(this.canvas, img);
|
|
619
|
+
this.canvas.appendChild(img);
|
|
620
|
+
this.canvas.appendChild(this.viewOverlay);
|
|
621
|
+
this.canvas.appendChild(this.editOverlay);
|
|
559
622
|
this.api = this.options.api ? normalizeApi(this.options.api) : {};
|
|
560
623
|
if (this.api.load) {
|
|
561
624
|
this.loadFromApi();
|
|
@@ -565,7 +628,38 @@ var AnnotateImage = class {
|
|
|
565
628
|
if (this.options.editable) {
|
|
566
629
|
this.createButton();
|
|
567
630
|
}
|
|
568
|
-
|
|
631
|
+
if (options.autoResize !== false && typeof ResizeObserver !== "undefined") {
|
|
632
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
633
|
+
const entry = entries[0];
|
|
634
|
+
if (!entry) return;
|
|
635
|
+
const { width, height } = entry.contentRect;
|
|
636
|
+
if (width === 0 || height === 0) return;
|
|
637
|
+
this.rescale(width, height);
|
|
638
|
+
});
|
|
639
|
+
this.resizeObserver.observe(this.canvas);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
/** Convert a rect from natural image coordinates to rendered (scaled) coordinates. */
|
|
643
|
+
toRendered(rect) {
|
|
644
|
+
return {
|
|
645
|
+
top: rect.top * this.scaleY,
|
|
646
|
+
left: rect.left * this.scaleX,
|
|
647
|
+
width: rect.width * this.scaleX,
|
|
648
|
+
height: rect.height * this.scaleY
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
/** Convert a rect from rendered (scaled) coordinates to natural image coordinates. */
|
|
652
|
+
toNatural(rect) {
|
|
653
|
+
const result = {
|
|
654
|
+
top: rect.top / this.scaleY,
|
|
655
|
+
left: rect.left / this.scaleX,
|
|
656
|
+
width: rect.width / this.scaleX,
|
|
657
|
+
height: rect.height / this.scaleY
|
|
658
|
+
};
|
|
659
|
+
if (!isFinite(result.top) || !isFinite(result.left) || !isFinite(result.width) || !isFinite(result.height)) {
|
|
660
|
+
throw new Error("image-annotate: scale conversion produced non-finite coordinates");
|
|
661
|
+
}
|
|
662
|
+
return result;
|
|
569
663
|
}
|
|
570
664
|
/** Current interaction mode — 'view' for browsing, 'edit' when an annotation is being created or modified. */
|
|
571
665
|
get mode() {
|
|
@@ -619,6 +713,7 @@ var AnnotateImage = class {
|
|
|
619
713
|
/** Rebuild annotation views from the current notes array. */
|
|
620
714
|
load() {
|
|
621
715
|
this.destroyViews();
|
|
716
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
622
717
|
this.createViews();
|
|
623
718
|
this.notifyLoad();
|
|
624
719
|
}
|
|
@@ -637,8 +732,15 @@ var AnnotateImage = class {
|
|
|
637
732
|
if (this.button) {
|
|
638
733
|
this.button.remove();
|
|
639
734
|
}
|
|
735
|
+
if (this.resizeObserver) {
|
|
736
|
+
this.resizeObserver.disconnect();
|
|
737
|
+
this.resizeObserver = void 0;
|
|
738
|
+
}
|
|
739
|
+
if (this.originalParent && this.originalParent.isConnected) {
|
|
740
|
+
const ref = this.originalNextSibling?.parentNode === this.originalParent ? this.originalNextSibling : null;
|
|
741
|
+
this.originalParent.insertBefore(this.img, ref);
|
|
742
|
+
}
|
|
640
743
|
this.canvas.remove();
|
|
641
|
-
this.img.style.display = "";
|
|
642
744
|
}
|
|
643
745
|
/** Cancel the active edit (if any) and return to view mode. */
|
|
644
746
|
cancelEdit() {
|
|
@@ -646,6 +748,34 @@ var AnnotateImage = class {
|
|
|
646
748
|
this.activeEdit.destroy();
|
|
647
749
|
this.setMode("view");
|
|
648
750
|
}
|
|
751
|
+
this.flushPendingRescale();
|
|
752
|
+
}
|
|
753
|
+
/** Recompute scale factors, deferring if an edit is active. */
|
|
754
|
+
rescale(renderedWidth, renderedHeight) {
|
|
755
|
+
if (this.mode === "edit") {
|
|
756
|
+
this.pendingRescale = true;
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
this.applyRescale(renderedWidth, renderedHeight);
|
|
760
|
+
}
|
|
761
|
+
/** Apply new scale factors and re-render all views. */
|
|
762
|
+
applyRescale(renderedWidth, renderedHeight) {
|
|
763
|
+
const newScaleX = renderedWidth / this.naturalWidth;
|
|
764
|
+
const newScaleY = renderedHeight / this.naturalHeight;
|
|
765
|
+
if (newScaleX === this.scaleX && newScaleY === this.scaleY) return;
|
|
766
|
+
this.scaleX = newScaleX;
|
|
767
|
+
this.scaleY = newScaleY;
|
|
768
|
+
this.destroyViews();
|
|
769
|
+
this.createViews();
|
|
770
|
+
}
|
|
771
|
+
/** @internal Flush any deferred rescale after an edit completes. */
|
|
772
|
+
flushPendingRescale() {
|
|
773
|
+
if (!this.pendingRescale) return;
|
|
774
|
+
this.pendingRescale = false;
|
|
775
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
776
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
777
|
+
this.applyRescale(rect.width, rect.height);
|
|
778
|
+
}
|
|
649
779
|
}
|
|
650
780
|
/** Replace all annotations with new data. Does not fire lifecycle callbacks. */
|
|
651
781
|
setNotes(notes) {
|
|
@@ -692,6 +822,7 @@ var AnnotateImage = class {
|
|
|
692
822
|
this.api.load().then((notes) => {
|
|
693
823
|
this.destroyViews();
|
|
694
824
|
this.notes = notes;
|
|
825
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
695
826
|
this.createViews();
|
|
696
827
|
this.notifyLoad();
|
|
697
828
|
}).catch((err) => {
|
package/dist/jquery.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";(()=>{var _=30,F=30,V=30,X=30,D=class{constructor(t,i,n){this.busy=!1;this.image=t,this.handlers=t.handlers,i?this.note=i:this.note={id:"new",top:_,left:F,width:V,height:X,text:"",editable:!0},this.area=t.editOverlay.querySelector(".image-annotate-edit-area"),this.area.style.height=this.note.height+"px",this.area.style.width=this.note.width+"px",this.area.style.left=this.note.left+"px",this.area.style.top=this.note.top+"px",this.form=document.createElement("div"),this.form.className="image-annotate-edit-form";let o=document.createElement("form");this.textarea=document.createElement("textarea"),this.textarea.name="text",this.textarea.rows=3,this.textarea.cols=30,this.textarea.value=this.note.text;let s=this.image.options.labels?.placeholder??"";s&&(this.textarea.placeholder=s),o.appendChild(this.textarea),this.form.appendChild(o),this.area.appendChild(this.form),this.form.addEventListener("pointerdown",r=>r.stopPropagation());let a=this.area,c=r=>{a.style.left=r.left+"px",a.style.top=r.top+"px",a.style.width=r.width+"px",a.style.height=r.height+"px"};this.handlers.makeResizable(a,{containment:t.canvas,onResize:c,onStop:c}),this.handlers.makeDraggable(a,{containment:t.canvas,onDrag:r=>{a.style.left=r.left+"px",a.style.top=r.top+"px"},onStop:r=>{a.style.left=r.left+"px",a.style.top=r.top+"px"}}),this.textarea.focus(),this.form.addEventListener("keydown",r=>{if(r.key==="Escape"){let p=this.form.querySelector(".image-annotate-edit-close");p&&p.click()}});let l=document.createElement("div");l.className="image-annotate-edit-buttons",this.form.appendChild(l),this.addSaveButton(l,n),n&&this.addDeleteButton(l,n),this.addCancelButton(l)}destroy(){this.image.activeEdit=null,this.handlers.destroyResizable(this.area),this.handlers.destroyDraggable(this.area),this.area.style.height="",this.area.style.width="",this.area.style.left="",this.area.style.top="",this.form.remove()}addSaveButton(t,i){let n=document.createElement("button");n.className="image-annotate-edit-ok",n.textContent=this.image.options.labels?.save??"OK",n.type="button",n.addEventListener("click",()=>{if(this.busy)return;let o=this.textarea.value,s=()=>{this.image.setMode("view"),i?i.resetPosition(this,o):(this.note.editable=!0,new T(this.image,this.note).resetPosition(this,o),this.image.notes.push(this.note)),this.image.notifySave(C(this.note)),this.destroy()},a=H(this.area),c=M(this.area);this.note.top=a.top,this.note.left=a.left,this.note.width=c.width,this.note.height=c.height,this.note.text=o,this.image.api.save?(this.busy=!0,this.image.api.save(C(this.note)).then(l=>{l.annotation_id!=null&&(this.note.id=l.annotation_id),s()}).catch(l=>{this.busy=!1;let r=l instanceof Error?l:new Error(String(l));this.image.reportError({type:"save",error:r,note:this.note})})):s()}),t.appendChild(n)}addDeleteButton(t,i){let n=document.createElement("button");n.className="image-annotate-edit-delete",n.textContent=this.image.options.labels?.delete??"Delete",n.type="button",n.addEventListener("click",()=>{if(this.busy)return;let o=()=>{this.image.setMode("view"),this.destroy(),i.destroy();let s=this.image.notes.indexOf(this.note);s!==-1&&this.image.notes.splice(s,1),this.image.notifyDelete(C(this.note))};this.image.api.delete?(this.busy=!0,this.image.api.delete(C(this.note)).then(()=>{o()}).catch(s=>{this.busy=!1;let a=s instanceof Error?s:new Error(String(s));this.image.reportError({type:"delete",error:a,note:this.note})})):o()}),t.appendChild(n)}addCancelButton(t){let i=document.createElement("button");i.className="image-annotate-edit-close",i.textContent=this.image.options.labels?.cancel??"Cancel",i.type="button",i.addEventListener("click",()=>{this.image.cancelEdit()}),t.appendChild(i)}};function H(e){return{left:parseInt(e.style.left)||0,top:parseInt(e.style.top)||0}}function M(e){return{width:parseInt(e.style.width)||e.offsetWidth,height:parseInt(e.style.height)||e.offsetHeight}}var T=class{constructor(t,i){this.image=t,this.note=i,this.editable=!!(i.editable&&t.options.editable),this.area=document.createElement("div"),this.area.className="image-annotate-area"+(this.editable?" image-annotate-area-editable":"");let n=document.createElement("div");this.area.appendChild(n),t.viewOverlay.insertBefore(this.area,t.viewOverlay.firstChild),this.tooltip=document.createElement("div"),this.tooltip.className="image-annotate-note",this.tooltip.textContent=i.text,this.tooltip.style.display="none",this.area.appendChild(this.tooltip),this.setPosition(),this.area.addEventListener("mouseenter",()=>this.show()),this.area.addEventListener("mouseleave",()=>this.hide()),this.editable&&(this.area.setAttribute("tabindex","0"),this.area.setAttribute("role","button"),this.area.addEventListener("click",()=>this.edit()),this.area.addEventListener("keydown",o=>{(o.key==="Enter"||o.key===" ")&&(o.preventDefault(),this.edit())}))}setPosition(){let t=this.area.firstElementChild;t.style.height=this.note.height+"px",t.style.width=this.note.width+"px",this.area.style.left=this.note.left+"px",this.area.style.top=this.note.top+"px"}resetPosition(t,i){this.tooltip.textContent=i,this.tooltip.style.display="none";let n=H(t.area),o=M(t.area),s=this.area.firstElementChild;s.style.height=o.height+"px",s.style.width=o.width+"px",this.area.style.left=n.left+"px",this.area.style.top=n.top+"px",this.note.top=n.top,this.note.left=n.left,this.note.height=o.height,this.note.width=o.width,this.note.text=i,this.note.id=t.note.id,this.editable=!0}show(){this.tooltip.style.display="block",this.editable?this.area.classList.add("image-annotate-area-editable-hover"):this.area.classList.add("image-annotate-area-hover")}hide(){this.tooltip.style.display="none",this.area.classList.remove("image-annotate-area-hover"),this.area.classList.remove("image-annotate-area-editable-hover")}destroy(){this.area.remove(),this.tooltip.remove()}edit(){this.image.mode==="view"&&(this.image.setMode("edit"),this.image.activeEdit=new D(this.image,this.note,this))}};var P=new WeakMap,O=new WeakMap;function Y(e,t){R(e);function i(n){if(n.button!==0)return;n.preventDefault(),e.setPointerCapture&&e.setPointerCapture(n.pointerId);let o=n.clientX,s=n.clientY,a=parseFloat(e.style.left)||0,c=parseFloat(e.style.top)||0,l=parseFloat(e.style.width)||e.offsetWidth,r=parseFloat(e.style.height)||e.offsetHeight,p=-1/0,x=-1/0,A=1/0,E=1/0;if(t.containment){let m=t.containment.getBoundingClientRect(),u=e.getBoundingClientRect(),y=a-(u.left-m.left),d=c-(u.top-m.top);p=y,x=d,A=m.width-l+y,E=m.height-r+d}function b(m,u,y){return Math.max(u,Math.min(y,m))}function w(m){let u=m.clientX-o,y=m.clientY-s,d=b(a+u,p,A),h=b(c+y,x,E);t.onDrag&&t.onDrag({left:d,top:h})}function L(m){e.releasePointerCapture&&e.releasePointerCapture(m.pointerId),e.removeEventListener("pointermove",w),e.removeEventListener("pointerup",L);let u=m.clientX-o,y=m.clientY-s,d=b(a+u,p,A),h=b(c+y,x,E);t.onStop&&t.onStop({left:d,top:h})}e.addEventListener("pointermove",w),e.addEventListener("pointerup",L)}e.addEventListener("pointerdown",i),P.set(e,()=>{e.removeEventListener("pointerdown",i)})}function R(e){let t=P.get(e);t&&(t(),P.delete(e))}var g=10,U=["nw","ne","sw","se"];function k(e,t,i,n,o,s,a){let c=t,l=i,r=n,p=o;return e==="nw"||e==="sw"?(c=t+s,r=n-s):r=n+s,e==="nw"||e==="ne"?(l=i+a,p=o-a):p=o+a,r<g&&((e==="nw"||e==="sw")&&(c=t+n-g),r=g),p<g&&((e==="nw"||e==="ne")&&(l=i+o-g),p=g),{left:c,top:l,width:r,height:p}}function j(e,t){z(e);let i=[];for(let n of U){let o=document.createElement("div");o.className=`image-annotate-resize-handle image-annotate-resize-handle-${n}`,e.appendChild(o),i.push(o),o.addEventListener("pointerdown",function(a){if(a.button!==0)return;a.preventDefault(),a.stopPropagation(),o.setPointerCapture&&o.setPointerCapture(a.pointerId);let c=a.clientX,l=a.clientY,r=parseFloat(e.style.left)||0,p=parseFloat(e.style.top)||0,x=parseFloat(e.style.width)||0,A=parseFloat(e.style.height)||0,E=1/0,b=1/0,w=-1/0,L=-1/0;if(t.containment){let d=t.containment.getBoundingClientRect(),h=e.getBoundingClientRect(),f=r-(h.left-d.left),v=p-(h.top-d.top);w=f,L=v,E=d.width+f,b=d.height+v}function m(d){let{left:h,top:f,width:v,height:N}=d;return h<w&&(v-=w-h,h=w),f<L&&(N-=L-f,f=L),h+v>E&&(v=E-h),f+N>b&&(N=b-f),v<g&&(v=g),N<g&&(N=g),{left:h,top:f,width:v,height:N}}function u(d){let h=d.clientX-c,f=d.clientY-l,v=m(k(n,r,p,x,A,h,f));t.onResize?.(v)}function y(d){o.releasePointerCapture&&o.releasePointerCapture(d.pointerId),o.removeEventListener("pointermove",u),o.removeEventListener("pointerup",y);let h=d.clientX-c,f=d.clientY-l,v=m(k(n,r,p,x,A,h,f));t.onStop&&t.onStop(v)}o.addEventListener("pointermove",u),o.addEventListener("pointerup",y)})}O.set(e,()=>{for(let n of i)n.remove()})}function z(e){let t=O.get(e);t&&(t(),O.delete(e))}function B(){return{makeDraggable:Y,makeResizable:j,destroyDraggable:R,destroyResizable:z}}function C(e){let{view:t,editable:i,...n}=e;return n}function J(e){return{load:typeof e.load=="string"?K(e.load):e.load,save:typeof e.save=="string"?W(e.save):e.save,delete:typeof e.delete=="string"?q(e.delete):e.delete}}function K(e){return()=>fetch(e).then(t=>{if(!t.ok)throw new Error(`Load failed (HTTP ${t.status})`);return t.json()})}function W(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(i=>{if(!i.ok)throw new Error(`Save failed (HTTP ${i.status})`);return i.json()})}function q(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(i=>{if(!i.ok)throw new Error(`Delete failed (HTTP ${i.status})`)})}var I=class{constructor(t,i){this._mode="view";this.activeEdit=null;this.destroyed=!1;this.options=i,this.handlers=B(),this.img=t;let n=t.width,o=t.height;if(n===0||o===0)throw new Error("image-annotate: image must have non-zero dimensions (is the image loaded?)");this.notes=i.notes.map(a=>({...a})),this.canvas=document.createElement("div"),this.canvas.className="image-annotate-canvas",this.viewOverlay=document.createElement("div"),this.viewOverlay.className="image-annotate-view",this.editOverlay=document.createElement("div"),this.editOverlay.className="image-annotate-edit",this.editOverlay.style.display="none";let s=document.createElement("div");if(s.className="image-annotate-edit-area",this.editOverlay.appendChild(s),this.canvas.appendChild(this.viewOverlay),this.canvas.appendChild(this.editOverlay),!t.parentNode)throw new Error("image-annotate: image must be in the DOM before initialization");t.parentNode.insertBefore(this.canvas,t.nextSibling),this.canvas.style.height=o+"px",this.canvas.style.width=n+"px",this.canvas.style.backgroundImage='url("'+t.src+'")',this.viewOverlay.style.height=o+"px",this.viewOverlay.style.width=n+"px",this.editOverlay.style.height=o+"px",this.editOverlay.style.width=n+"px",this.api=this.options.api?J(this.options.api):{},this.api.load?this.loadFromApi():this.load(),this.options.editable&&this.createButton(),t.style.display="none"}get mode(){return this._mode}setMode(t){this._mode=t,t==="edit"?(this.canvas.classList.add("image-annotate-editing"),this.editOverlay.style.display="block"):(this.canvas.classList.remove("image-annotate-editing"),this.editOverlay.style.display="none")}getNotes(){return this.notes.map(C)}notifyChange(){this.options.onChange?.(this.getNotes())}notifySave(t){this.options.onSave?.(t),this.notifyChange()}notifyDelete(t){this.options.onDelete?.(t),this.notifyChange()}notifyLoad(){this.options.onLoad?.(this.getNotes()),this.notifyChange()}destroyViews(){this.cancelEdit();for(let t of this.notes)t.view?.destroy()}createViews(){for(let t of this.notes)t.view=new T(this,t)}load(){this.destroyViews(),this.createViews(),this.notifyLoad()}clear(){this.destroyViews(),this.notes=[],this.notifyChange()}destroy(){this.destroyed||(this.destroyed=!0,this.destroyViews(),this.notes=[],this.button&&this.button.remove(),this.canvas.remove(),this.img.style.display="")}cancelEdit(){this.activeEdit&&(this.activeEdit.destroy(),this.setMode("view"))}setNotes(t){this.destroyed||(this.destroyViews(),this.notes=t.map(i=>({...i})),this.createViews())}setEditable(t){this.destroyed||this.options.editable!==t&&(this.options.editable=t,t&&!this.button?this.createButton():!t&&this.button&&(this.button.remove(),this.button=void 0),this.destroyViews(),this.createViews())}createButton(){this.button=document.createElement("button"),this.button.className="image-annotate-add",this.button.title=this.options.labels?.addNote??"Add Note",this.button.type="button",this.button.addEventListener("click",()=>{this.add()}),this.canvas.appendChild(this.button)}reportError(t){this.options.onError?this.options.onError(t):console.error(`image-annotate: ${t.type} failed`,t.error)}loadFromApi(){this.api.load&&this.api.load().then(t=>{this.destroyViews(),this.notes=t,this.createViews(),this.notifyLoad()}).catch(t=>{let i=t instanceof Error?t:new Error(String(t));this.reportError({type:"load",error:i})})}add(){return this.mode==="view"?(this.setMode("edit"),this.activeEdit=new D(this),!0):!1}};var S={addNote:"Add Note",save:"OK",delete:"Delete",cancel:"Cancel",placeholder:""};var Q={editable:!0,notes:[],labels:{...S}};$.fn.annotateImage=function(e){if(e==="destroy"){let s=this.data("annotateImage");return s&&(s.destroy(),this.removeData("annotateImage")),this}let t=e,i={...Q,...t,labels:{...S,...t?.labels}},n=this[0],o=new I(n,i);return this.data("annotateImage",o),this};})();
|
|
1
|
+
"use strict";(()=>{function X(e,t,i){let n=Math.min(e.width,t),o=Math.min(e.height,i),l=Math.max(0,Math.min(e.left,t-n));return{top:Math.max(0,Math.min(e.top,i-o)),left:l,width:n,height:o}}function I(e,t,i){for(let n of e)Object.assign(n,X(n,t,i))}function T(e,t,i,n){let o=(i-e)/2,r=t+o+e;r>n&&(o-=r-n);let s=t+o;return s<0&&(o-=s),o}var Y=30,_=30,W=30,U=30,R=class{constructor(t,i,n){this.busy=!1;this.image=t,this.handlers=t.handlers,i?this.note=i:this.note={id:"new",top:Y,left:_,width:W,height:U,text:"",editable:!0},this.area=t.editOverlay.querySelector(".image-annotate-edit-area");let o=t.toRendered(this.note);this.area.style.height=o.height+"px",this.area.style.width=o.width+"px",this.area.style.left=o.left+"px",this.area.style.top=o.top+"px",this.form=document.createElement("div"),this.form.className="image-annotate-edit-form";let l=document.createElement("form");this.textarea=document.createElement("textarea"),this.textarea.name="text",this.textarea.rows=3,this.textarea.cols=30,this.textarea.value=this.note.text;let r=this.image.options.labels?.placeholder??"";r&&(this.textarea.placeholder=r),l.appendChild(this.textarea),this.form.appendChild(l),this.area.appendChild(this.form),this.positionForm(),this.textarea.focus(),this.form.addEventListener("pointerdown",a=>a.stopPropagation());let s=this.area,d=a=>{s.style.left=a.left+"px",s.style.top=a.top+"px",s.style.width=a.width+"px",s.style.height=a.height+"px"};this.handlers.makeResizable(s,{containment:t.canvas,onResize:d,onStop:a=>{d(a),this.positionForm()}}),this.handlers.makeDraggable(s,{containment:t.canvas,onDrag:a=>{s.style.left=a.left+"px",s.style.top=a.top+"px"},onStop:a=>{s.style.left=a.left+"px",s.style.top=a.top+"px",this.positionForm()}}),this.form.addEventListener("keydown",a=>{if(a.key==="Escape"){let y=this.form.querySelector(".image-annotate-edit-close");y&&y.click()}});let h=document.createElement("div");h.className="image-annotate-edit-buttons",this.form.appendChild(h),this.addSaveButton(h,n),n&&this.addDeleteButton(h,n),this.addCancelButton(h)}positionForm(){let t=this.form.getBoundingClientRect(),i=this.area.getBoundingClientRect(),n=T(t.width,i.left,i.width,window.innerWidth);this.form.style.left=n+"px"}destroy(){this.image.activeEdit=null,this.handlers.destroyResizable(this.area),this.handlers.destroyDraggable(this.area),this.area.style.height="",this.area.style.width="",this.area.style.left="",this.area.style.top="",this.form.remove()}addSaveButton(t,i){let n=document.createElement("button");n.className="image-annotate-edit-ok",n.textContent=this.image.options.labels?.save??"OK",n.type="button",n.addEventListener("click",()=>{if(this.busy)return;let o=this.textarea.value,l=()=>{this.image.setMode("view"),i?i.resetPosition(this,o):(this.note.editable=!0,new D(this.image,this.note).resetPosition(this,o),this.image.notes.push(this.note)),this.image.notifySave(C(this.note)),this.destroy(),this.image.flushPendingRescale()},r=S(this.area),s=z(this.area),d=this.image.toNatural({top:r.top,left:r.left,width:s.width,height:s.height});this.note.top=d.top,this.note.left=d.left,this.note.width=d.width,this.note.height=d.height,this.note.text=o,this.image.api.save?(this.busy=!0,this.image.api.save(C(this.note)).then(h=>{h.annotation_id!=null&&(this.note.id=h.annotation_id),l()}).catch(h=>{this.busy=!1;let a=h instanceof Error?h:new Error(String(h));this.image.reportError({type:"save",error:a,note:this.note})})):l()}),t.appendChild(n)}addDeleteButton(t,i){let n=document.createElement("button");n.className="image-annotate-edit-delete",n.textContent=this.image.options.labels?.delete??"Delete",n.type="button",n.addEventListener("click",()=>{if(this.busy)return;let o=()=>{this.image.setMode("view"),this.destroy(),i.destroy();let l=this.image.notes.indexOf(this.note);l!==-1&&this.image.notes.splice(l,1),this.image.notifyDelete(C(this.note)),this.image.flushPendingRescale()};this.image.api.delete?(this.busy=!0,this.image.api.delete(C(this.note)).then(()=>{o()}).catch(l=>{this.busy=!1;let r=l instanceof Error?l:new Error(String(l));this.image.reportError({type:"delete",error:r,note:this.note})})):o()}),t.appendChild(n)}addCancelButton(t){let i=document.createElement("button");i.className="image-annotate-edit-close",i.textContent=this.image.options.labels?.cancel??"Cancel",i.type="button",i.addEventListener("click",()=>{this.image.cancelEdit()}),t.appendChild(i)}};function S(e){return{left:parseInt(e.style.left)||0,top:parseInt(e.style.top)||0}}function z(e){return{width:parseInt(e.style.width)||e.offsetWidth,height:parseInt(e.style.height)||e.offsetHeight}}var D=class{constructor(t,i){this.image=t,this.note=i,this.editable=!!(i.editable&&t.options.editable),this.area=document.createElement("div"),this.area.className="image-annotate-area"+(this.editable?" image-annotate-area-editable":"");let n=document.createElement("div");this.area.appendChild(n),t.viewOverlay.insertBefore(this.area,t.viewOverlay.firstChild),this.tooltip=document.createElement("div"),this.tooltip.className="image-annotate-note",this.tooltip.textContent=i.text,this.tooltip.style.display="none",this.area.appendChild(this.tooltip),this.setPosition(),this.area.addEventListener("mouseenter",()=>this.show()),this.area.addEventListener("mouseleave",()=>this.hide()),this.editable&&(this.area.setAttribute("tabindex","0"),this.area.setAttribute("role","button"),this.area.addEventListener("click",()=>this.edit()),this.area.addEventListener("keydown",o=>{(o.key==="Enter"||o.key===" ")&&(o.preventDefault(),this.edit())}))}setPosition(){let t=this.image.toRendered(this.note),i=this.area.firstElementChild;i.style.height=t.height+"px",i.style.width=t.width+"px",this.area.style.left=t.left+"px",this.area.style.top=t.top+"px"}resetPosition(t,i){this.tooltip.textContent=i,this.tooltip.style.display="none";let n=this.image.toRendered(t.note),o=this.area.firstElementChild;o.style.height=n.height+"px",o.style.width=n.width+"px",this.area.style.left=n.left+"px",this.area.style.top=n.top+"px",this.note.top=t.note.top,this.note.left=t.note.left,this.note.height=t.note.height,this.note.width=t.note.width,this.note.text=i,this.note.id=t.note.id,this.editable=!0}show(){this.tooltip.style.visibility="hidden",this.tooltip.style.display="block";let t=this.tooltip.getBoundingClientRect(),i=this.area.getBoundingClientRect(),n=T(t.width,i.left,i.width,window.innerWidth);this.tooltip.style.left=n+"px",this.tooltip.style.visibility="",this.editable?this.area.classList.add("image-annotate-area-editable-hover"):this.area.classList.add("image-annotate-area-hover")}hide(){this.tooltip.style.display="none",this.area.classList.remove("image-annotate-area-hover"),this.area.classList.remove("image-annotate-area-editable-hover")}destroy(){this.area.remove(),this.tooltip.remove()}edit(){this.image.mode==="view"&&(this.image.setMode("edit"),this.image.activeEdit=new R(this.image,this.note,this))}};var P=new WeakMap,M=new WeakMap;function j(e,t){B(e);function i(n){if(n.button!==0)return;n.preventDefault(),e.setPointerCapture&&e.setPointerCapture(n.pointerId);let o=n.clientX,l=n.clientY,r=parseFloat(e.style.left)||0,s=parseFloat(e.style.top)||0,d=parseFloat(e.style.width)||e.offsetWidth,h=parseFloat(e.style.height)||e.offsetHeight,a=-1/0,y=-1/0,N=1/0,E=1/0;if(t.containment){let p=t.containment.getBoundingClientRect(),v=e.getBoundingClientRect(),g=r-(v.left-p.left),c=s-(v.top-p.top);a=g,y=c,N=p.width-d+g,E=p.height-h+c}function w(p,v,g){return Math.max(v,Math.min(g,p))}function L(p){let v=p.clientX-o,g=p.clientY-l,c=w(r+v,a,N),m=w(s+g,y,E);t.onDrag&&t.onDrag({left:c,top:m})}function x(p){e.releasePointerCapture&&e.releasePointerCapture(p.pointerId),e.removeEventListener("pointermove",L),e.removeEventListener("pointerup",x);let v=p.clientX-o,g=p.clientY-l,c=w(r+v,a,N),m=w(s+g,y,E);t.onStop&&t.onStop({left:c,top:m})}e.addEventListener("pointermove",L),e.addEventListener("pointerup",x)}e.addEventListener("pointerdown",i),P.set(e,()=>{e.removeEventListener("pointerdown",i)})}function B(e){let t=P.get(e);t&&(t(),P.delete(e))}var b=10,q=["nw","ne","sw","se"];function k(e,t,i,n,o,l,r){let s,d,h,a;return e==="nw"||e==="sw"?(s=t+l,h=n-l):(s=t,h=n+l),e==="nw"||e==="ne"?(d=i+r,a=o-r):(d=i,a=o+r),h<b&&((e==="nw"||e==="sw")&&(s=t+n-b),h=b),a<b&&((e==="nw"||e==="ne")&&(d=i+o-b),a=b),{left:s,top:d,width:h,height:a}}function K(e,t){F(e);let i=[];for(let n of q){let o=document.createElement("div");o.className=`image-annotate-resize-handle image-annotate-resize-handle-${n}`,e.appendChild(o),i.push(o),o.addEventListener("pointerdown",function(r){if(r.button!==0)return;r.preventDefault(),r.stopPropagation(),o.setPointerCapture&&o.setPointerCapture(r.pointerId);let s=r.clientX,d=r.clientY,h=parseFloat(e.style.left)||0,a=parseFloat(e.style.top)||0,y=parseFloat(e.style.width)||0,N=parseFloat(e.style.height)||0,E=1/0,w=1/0,L=-1/0,x=-1/0;if(t.containment){let c=t.containment.getBoundingClientRect(),m=e.getBoundingClientRect(),u=h-(m.left-c.left),f=a-(m.top-c.top);L=u,x=f,E=c.width+u,w=c.height+f}function p(c){let{left:m,top:u,width:f,height:A}=c;return m<L&&(f-=L-m,m=L),u<x&&(A-=x-u,u=x),m+f>E&&(f=E-m),u+A>w&&(A=w-u),f<b&&(f=b),A<b&&(A=b),{left:m,top:u,width:f,height:A}}function v(c){let m=c.clientX-s,u=c.clientY-d,f=p(k(n,h,a,y,N,m,u));t.onResize?.(f)}function g(c){o.releasePointerCapture&&o.releasePointerCapture(c.pointerId),o.removeEventListener("pointermove",v),o.removeEventListener("pointerup",g);let m=c.clientX-s,u=c.clientY-d,f=p(k(n,h,a,y,N,m,u));t.onStop&&t.onStop(f)}o.addEventListener("pointermove",v),o.addEventListener("pointerup",g)})}M.set(e,()=>{for(let n of i)n.remove()})}function F(e){let t=M.get(e);t&&(t(),M.delete(e))}function V(){return{makeDraggable:j,makeResizable:K,destroyDraggable:B,destroyResizable:F}}function C(e){let{view:t,editable:i,...n}=e;return n}function J(e){return{load:typeof e.load=="string"?G(e.load):e.load,save:typeof e.save=="string"?Q(e.save):e.save,delete:typeof e.delete=="string"?Z(e.delete):e.delete}}function G(e){return()=>fetch(e).then(t=>{if(!t.ok)throw new Error(`Load failed (HTTP ${t.status})`);return t.json()})}function Q(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(i=>{if(!i.ok)throw new Error(`Save failed (HTTP ${i.status})`);return i.json()})}function Z(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(i=>{if(!i.ok)throw new Error(`Delete failed (HTTP ${i.status})`)})}var H=class{constructor(t,i){this._mode="view";this.activeEdit=null;this.destroyed=!1;this.pendingRescale=!1;this.originalParent=null;this.originalNextSibling=null;this.options=i,this.handlers=V(),this.img=t,this.naturalWidth=t.naturalWidth||t.width,this.naturalHeight=t.naturalHeight||t.height;let n=t.getBoundingClientRect(),o=n.width||t.width,l=n.height||t.height;if(this.naturalWidth===0||this.naturalHeight===0)throw new Error("image-annotate: image must have non-zero dimensions (is the image loaded?)");this.scaleX=o/this.naturalWidth,this.scaleY=l/this.naturalHeight,this.notes=i.notes.map(s=>({...s})),this.originalParent=t.parentNode,this.originalNextSibling=t.nextSibling,this.canvas=document.createElement("div"),this.canvas.className="image-annotate-canvas",i.theme&&(this.canvas.dataset.theme=i.theme),this.viewOverlay=document.createElement("div"),this.viewOverlay.className="image-annotate-view",this.editOverlay=document.createElement("div"),this.editOverlay.className="image-annotate-edit",this.editOverlay.style.display="none";let r=document.createElement("div");if(r.className="image-annotate-edit-area",this.editOverlay.appendChild(r),!t.parentNode)throw new Error("image-annotate: image must be in the DOM before initialization");t.parentNode.insertBefore(this.canvas,t),this.canvas.appendChild(t),this.canvas.appendChild(this.viewOverlay),this.canvas.appendChild(this.editOverlay),this.api=this.options.api?J(this.options.api):{},this.api.load?this.loadFromApi():this.load(),this.options.editable&&this.createButton(),i.autoResize!==!1&&typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(s=>{let d=s[0];if(!d)return;let{width:h,height:a}=d.contentRect;h===0||a===0||this.rescale(h,a)}),this.resizeObserver.observe(this.canvas))}toRendered(t){return{top:t.top*this.scaleY,left:t.left*this.scaleX,width:t.width*this.scaleX,height:t.height*this.scaleY}}toNatural(t){let i={top:t.top/this.scaleY,left:t.left/this.scaleX,width:t.width/this.scaleX,height:t.height/this.scaleY};if(!isFinite(i.top)||!isFinite(i.left)||!isFinite(i.width)||!isFinite(i.height))throw new Error("image-annotate: scale conversion produced non-finite coordinates");return i}get mode(){return this._mode}setMode(t){this._mode=t,t==="edit"?(this.canvas.classList.add("image-annotate-editing"),this.editOverlay.style.display="block"):(this.canvas.classList.remove("image-annotate-editing"),this.editOverlay.style.display="none")}getNotes(){return this.notes.map(C)}notifyChange(){this.options.onChange?.(this.getNotes())}notifySave(t){this.options.onSave?.(t),this.notifyChange()}notifyDelete(t){this.options.onDelete?.(t),this.notifyChange()}notifyLoad(){this.options.onLoad?.(this.getNotes()),this.notifyChange()}destroyViews(){this.cancelEdit();for(let t of this.notes)t.view?.destroy()}createViews(){for(let t of this.notes)t.view=new D(this,t)}load(){this.destroyViews(),I(this.notes,this.naturalWidth,this.naturalHeight),this.createViews(),this.notifyLoad()}clear(){this.destroyViews(),this.notes=[],this.notifyChange()}destroy(){if(!this.destroyed){if(this.destroyed=!0,this.destroyViews(),this.notes=[],this.button&&this.button.remove(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=void 0),this.originalParent&&this.originalParent.isConnected){let t=this.originalNextSibling?.parentNode===this.originalParent?this.originalNextSibling:null;this.originalParent.insertBefore(this.img,t)}this.canvas.remove()}}cancelEdit(){this.activeEdit&&(this.activeEdit.destroy(),this.setMode("view")),this.flushPendingRescale()}rescale(t,i){if(this.mode==="edit"){this.pendingRescale=!0;return}this.applyRescale(t,i)}applyRescale(t,i){let n=t/this.naturalWidth,o=i/this.naturalHeight;n===this.scaleX&&o===this.scaleY||(this.scaleX=n,this.scaleY=o,this.destroyViews(),this.createViews())}flushPendingRescale(){if(!this.pendingRescale)return;this.pendingRescale=!1;let t=this.canvas.getBoundingClientRect();t.width>0&&t.height>0&&this.applyRescale(t.width,t.height)}setNotes(t){this.destroyed||(this.destroyViews(),this.notes=t.map(i=>({...i})),this.createViews())}setEditable(t){this.destroyed||this.options.editable!==t&&(this.options.editable=t,t&&!this.button?this.createButton():!t&&this.button&&(this.button.remove(),this.button=void 0),this.destroyViews(),this.createViews())}createButton(){this.button=document.createElement("button"),this.button.className="image-annotate-add",this.button.title=this.options.labels?.addNote??"Add Note",this.button.type="button",this.button.addEventListener("click",()=>{this.add()}),this.canvas.appendChild(this.button)}reportError(t){this.options.onError?this.options.onError(t):console.error(`image-annotate: ${t.type} failed`,t.error)}loadFromApi(){this.api.load&&this.api.load().then(t=>{this.destroyViews(),this.notes=t,I(this.notes,this.naturalWidth,this.naturalHeight),this.createViews(),this.notifyLoad()}).catch(t=>{let i=t instanceof Error?t:new Error(String(t));this.reportError({type:"load",error:i})})}add(){return this.mode==="view"?(this.setMode("edit"),this.activeEdit=new R(this),!0):!1}};var O={addNote:"Add Note",save:"OK",delete:"Delete",cancel:"Cancel",placeholder:""};var tt={editable:!0,notes:[],labels:{...O}};$.fn.annotateImage=function(e){if(e==="destroy"){let l=this.data("annotateImage");return l&&(l.destroy(),this.removeData("annotateImage")),this}let t=e,i={...tt,...t,labels:{...O,...t?.labels}},n=this[0],o=new H(n,i);return this.data("annotateImage",o),this};})();
|