annotate-image 2.0.0-beta.2 → 2.0.0-beta.4
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 +57 -3
- package/dist/core.min.js +1 -1
- package/dist/css/annotate.min.css +1 -1
- package/dist/jquery.js +57 -3
- package/dist/jquery.min.js +1 -1
- package/dist/react.js +57 -3
- package/dist/types/annotate-edit.d.ts +2 -0
- package/dist/types/positioning.d.ts +31 -0
- package/dist/types/types.d.ts +2 -0
- package/dist/vue.js +57 -3
- package/package.json +13 -13
- package/readme.md +86 -2
package/dist/core.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;
|
|
@@ -47,6 +74,8 @@ var AnnotateEdit = class {
|
|
|
47
74
|
formEl.appendChild(this.textarea);
|
|
48
75
|
this.form.appendChild(formEl);
|
|
49
76
|
this.area.appendChild(this.form);
|
|
77
|
+
this.positionForm();
|
|
78
|
+
this.textarea.focus();
|
|
50
79
|
this.form.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
51
80
|
const area = this.area;
|
|
52
81
|
const applyRect = (rect) => {
|
|
@@ -58,7 +87,10 @@ var AnnotateEdit = class {
|
|
|
58
87
|
this.handlers.makeResizable(area, {
|
|
59
88
|
containment: image.canvas,
|
|
60
89
|
onResize: applyRect,
|
|
61
|
-
onStop:
|
|
90
|
+
onStop: (rect) => {
|
|
91
|
+
applyRect(rect);
|
|
92
|
+
this.positionForm();
|
|
93
|
+
}
|
|
62
94
|
});
|
|
63
95
|
this.handlers.makeDraggable(area, {
|
|
64
96
|
containment: image.canvas,
|
|
@@ -69,9 +101,9 @@ var AnnotateEdit = class {
|
|
|
69
101
|
onStop: (pos) => {
|
|
70
102
|
area.style.left = pos.left + "px";
|
|
71
103
|
area.style.top = pos.top + "px";
|
|
104
|
+
this.positionForm();
|
|
72
105
|
}
|
|
73
106
|
});
|
|
74
|
-
this.textarea.focus();
|
|
75
107
|
this.form.addEventListener("keydown", (e) => {
|
|
76
108
|
if (e.key === "Escape") {
|
|
77
109
|
const cancelBtn = this.form.querySelector(".image-annotate-edit-close");
|
|
@@ -87,6 +119,13 @@ var AnnotateEdit = class {
|
|
|
87
119
|
}
|
|
88
120
|
this.addCancelButton(buttonRow);
|
|
89
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
|
+
}
|
|
90
129
|
/** Tear down the edit form and interaction handlers. */
|
|
91
130
|
destroy() {
|
|
92
131
|
this.image.activeEdit = null;
|
|
@@ -113,6 +152,7 @@ var AnnotateEdit = class {
|
|
|
113
152
|
} else {
|
|
114
153
|
this.note.editable = true;
|
|
115
154
|
const view = new AnnotateView(this.image, this.note);
|
|
155
|
+
this.note.view = view;
|
|
116
156
|
view.resetPosition(this, text);
|
|
117
157
|
this.image.notes.push(this.note);
|
|
118
158
|
}
|
|
@@ -270,7 +310,13 @@ var AnnotateView = class {
|
|
|
270
310
|
}
|
|
271
311
|
/** Show the tooltip and apply hover styling. */
|
|
272
312
|
show() {
|
|
313
|
+
this.tooltip.style.visibility = "hidden";
|
|
273
314
|
this.tooltip.style.display = "block";
|
|
315
|
+
const noteRect = this.tooltip.getBoundingClientRect();
|
|
316
|
+
const areaRect = this.area.getBoundingClientRect();
|
|
317
|
+
const left = computeNoteLeft(noteRect.width, areaRect.left, areaRect.width, window.innerWidth);
|
|
318
|
+
this.tooltip.style.left = left + "px";
|
|
319
|
+
this.tooltip.style.visibility = "";
|
|
274
320
|
if (!this.editable) {
|
|
275
321
|
this.area.classList.add("image-annotate-area-hover");
|
|
276
322
|
} else {
|
|
@@ -368,17 +414,19 @@ function destroyDraggable(el) {
|
|
|
368
414
|
var MIN_SIZE = 10;
|
|
369
415
|
var CORNERS = ["nw", "ne", "sw", "se"];
|
|
370
416
|
function computeResize(corner, startLeft, startTop, startWidth, startHeight, dx, dy) {
|
|
371
|
-
let left
|
|
417
|
+
let left, top, width, height;
|
|
372
418
|
if (corner === "nw" || corner === "sw") {
|
|
373
419
|
left = startLeft + dx;
|
|
374
420
|
width = startWidth - dx;
|
|
375
421
|
} else {
|
|
422
|
+
left = startLeft;
|
|
376
423
|
width = startWidth + dx;
|
|
377
424
|
}
|
|
378
425
|
if (corner === "nw" || corner === "ne") {
|
|
379
426
|
top = startTop + dy;
|
|
380
427
|
height = startHeight - dy;
|
|
381
428
|
} else {
|
|
429
|
+
top = startTop;
|
|
382
430
|
height = startHeight + dy;
|
|
383
431
|
}
|
|
384
432
|
if (width < MIN_SIZE) {
|
|
@@ -554,6 +602,9 @@ var AnnotateImage = class {
|
|
|
554
602
|
this.originalNextSibling = img.nextSibling;
|
|
555
603
|
this.canvas = document.createElement("div");
|
|
556
604
|
this.canvas.className = "image-annotate-canvas";
|
|
605
|
+
if (options.theme) {
|
|
606
|
+
this.canvas.dataset.theme = options.theme;
|
|
607
|
+
}
|
|
557
608
|
this.viewOverlay = document.createElement("div");
|
|
558
609
|
this.viewOverlay.className = "image-annotate-view";
|
|
559
610
|
this.editOverlay = document.createElement("div");
|
|
@@ -654,6 +705,7 @@ var AnnotateImage = class {
|
|
|
654
705
|
for (const note of this.notes) {
|
|
655
706
|
note.view?.destroy();
|
|
656
707
|
}
|
|
708
|
+
this.viewOverlay.replaceChildren();
|
|
657
709
|
}
|
|
658
710
|
createViews() {
|
|
659
711
|
for (const note of this.notes) {
|
|
@@ -663,6 +715,7 @@ var AnnotateImage = class {
|
|
|
663
715
|
/** Rebuild annotation views from the current notes array. */
|
|
664
716
|
load() {
|
|
665
717
|
this.destroyViews();
|
|
718
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
666
719
|
this.createViews();
|
|
667
720
|
this.notifyLoad();
|
|
668
721
|
}
|
|
@@ -771,6 +824,7 @@ var AnnotateImage = class {
|
|
|
771
824
|
this.api.load().then((notes) => {
|
|
772
825
|
this.destroyViews();
|
|
773
826
|
this.notes = notes;
|
|
827
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
774
828
|
this.createViews();
|
|
775
829
|
this.notifyLoad();
|
|
776
830
|
}).catch((err) => {
|
package/dist/core.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var AnnotateImage=(()=>{var P=Object.defineProperty;var X=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var _=Object.prototype.hasOwnProperty;var V=(e,t)=>{for(var n in t)P(e,n,{get:t[n],enumerable:!0})},W=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Y(t))!_.call(e,o)&&o!==n&&P(e,o,{get:()=>t[o],enumerable:!(i=X(t,o))||i.enumerable});return e};var U=e=>W(P({},"__esModule",{value:!0}),e);var at={};V(at,{AnnotateImage:()=>R,DEFAULT_LABELS:()=>H,annotate:()=>ot});var $=30,j=30,K=30,q=30,D=class{constructor(t,n,i){this.busy=!1;this.image=t,this.handlers=t.handlers,n?this.note=n:this.note={id:"new",top:$,left:j,width:K,height:q,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 d=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 l=this.image.options.labels?.placeholder??"";l&&(this.textarea.placeholder=l),d.appendChild(this.textarea),this.form.appendChild(d),this.area.appendChild(this.form),this.form.addEventListener("pointerdown",a=>a.stopPropagation());let s=this.area,c=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:c,onStop:c}),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.textarea.focus(),this.form.addEventListener("keydown",a=>{if(a.key==="Escape"){let b=this.form.querySelector(".image-annotate-edit-close");b&&b.click()}});let r=document.createElement("div");r.className="image-annotate-edit-buttons",this.form.appendChild(r),this.addSaveButton(r,i),i&&this.addDeleteButton(r,i),this.addCancelButton(r)}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,n){let i=document.createElement("button");i.className="image-annotate-edit-ok",i.textContent=this.image.options.labels?.save??"OK",i.type="button",i.addEventListener("click",()=>{if(this.busy)return;let o=this.textarea.value,d=()=>{this.image.setMode("view"),n?n.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(),this.image.flushPendingRescale()},l=O(this.area),s=S(this.area),c=this.image.toNatural({top:l.top,left:l.left,width:s.width,height:s.height});this.note.top=c.top,this.note.left=c.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(r=>{r.annotation_id!=null&&(this.note.id=r.annotation_id),d()}).catch(r=>{this.busy=!1;let a=r instanceof Error?r:new Error(String(r));this.image.reportError({type:"save",error:a,note:this.note})})):d()}),t.appendChild(i)}addDeleteButton(t,n){let i=document.createElement("button");i.className="image-annotate-edit-delete",i.textContent=this.image.options.labels?.delete??"Delete",i.type="button",i.addEventListener("click",()=>{if(this.busy)return;let o=()=>{this.image.setMode("view"),this.destroy(),n.destroy();let d=this.image.notes.indexOf(this.note);d!==-1&&this.image.notes.splice(d,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(d=>{this.busy=!1;let l=d instanceof Error?d:new Error(String(d));this.image.reportError({type:"delete",error:l,note:this.note})})):o()}),t.appendChild(i)}addCancelButton(t){let n=document.createElement("button");n.className="image-annotate-edit-close",n.textContent=this.image.options.labels?.cancel??"Cancel",n.type="button",n.addEventListener("click",()=>{this.image.cancelEdit()}),t.appendChild(n)}};function O(e){return{left:parseInt(e.style.left)||0,top:parseInt(e.style.top)||0}}function S(e){return{width:parseInt(e.style.width)||e.offsetWidth,height:parseInt(e.style.height)||e.offsetHeight}}var T=class{constructor(t,n){this.image=t,this.note=n,this.editable=!!(n.editable&&t.options.editable),this.area=document.createElement("div"),this.area.className="image-annotate-area"+(this.editable?" image-annotate-area-editable":"");let i=document.createElement("div");this.area.appendChild(i),t.viewOverlay.insertBefore(this.area,t.viewOverlay.firstChild),this.tooltip=document.createElement("div"),this.tooltip.className="image-annotate-note",this.tooltip.textContent=n.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),n=this.area.firstElementChild;n.style.height=t.height+"px",n.style.width=t.width+"px",this.area.style.left=t.left+"px",this.area.style.top=t.top+"px"}resetPosition(t,n){this.tooltip.textContent=n,this.tooltip.style.display="none";let i=this.image.toRendered(t.note),o=this.area.firstElementChild;o.style.height=i.height+"px",o.style.width=i.width+"px",this.area.style.left=i.left+"px",this.area.style.top=i.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=n,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 I=new WeakMap,M=new WeakMap;function J(e,t){k(e);function n(i){if(i.button!==0)return;i.preventDefault(),e.setPointerCapture&&e.setPointerCapture(i.pointerId);let o=i.clientX,d=i.clientY,l=parseFloat(e.style.left)||0,s=parseFloat(e.style.top)||0,c=parseFloat(e.style.width)||e.offsetWidth,r=parseFloat(e.style.height)||e.offsetHeight,a=-1/0,b=-1/0,N=1/0,w=1/0;if(t.containment){let m=t.containment.getBoundingClientRect(),v=e.getBoundingClientRect(),g=l-(v.left-m.left),h=s-(v.top-m.top);a=g,b=h,N=m.width-c+g,w=m.height-r+h}function E(m,v,g){return Math.max(v,Math.min(g,m))}function L(m){let v=m.clientX-o,g=m.clientY-d,h=E(l+v,a,N),p=E(s+g,b,w);t.onDrag&&t.onDrag({left:h,top:p})}function x(m){e.releasePointerCapture&&e.releasePointerCapture(m.pointerId),e.removeEventListener("pointermove",L),e.removeEventListener("pointerup",x);let v=m.clientX-o,g=m.clientY-d,h=E(l+v,a,N),p=E(s+g,b,w);t.onStop&&t.onStop({left:h,top:p})}e.addEventListener("pointermove",L),e.addEventListener("pointerup",x)}e.addEventListener("pointerdown",n),I.set(e,()=>{e.removeEventListener("pointerdown",n)})}function k(e){let t=I.get(e);t&&(t(),I.delete(e))}var y=10,G=["nw","ne","sw","se"];function z(e,t,n,i,o,d,l){let s=t,c=n,r=i,a=o;return e==="nw"||e==="sw"?(s=t+d,r=i-d):r=i+d,e==="nw"||e==="ne"?(c=n+l,a=o-l):a=o+l,r<y&&((e==="nw"||e==="sw")&&(s=t+i-y),r=y),a<y&&((e==="nw"||e==="ne")&&(c=n+o-y),a=y),{left:s,top:c,width:r,height:a}}function Z(e,t){B(e);let n=[];for(let i of G){let o=document.createElement("div");o.className=`image-annotate-resize-handle image-annotate-resize-handle-${i}`,e.appendChild(o),n.push(o),o.addEventListener("pointerdown",function(l){if(l.button!==0)return;l.preventDefault(),l.stopPropagation(),o.setPointerCapture&&o.setPointerCapture(l.pointerId);let s=l.clientX,c=l.clientY,r=parseFloat(e.style.left)||0,a=parseFloat(e.style.top)||0,b=parseFloat(e.style.width)||0,N=parseFloat(e.style.height)||0,w=1/0,E=1/0,L=-1/0,x=-1/0;if(t.containment){let h=t.containment.getBoundingClientRect(),p=e.getBoundingClientRect(),f=r-(p.left-h.left),u=a-(p.top-h.top);L=f,x=u,w=h.width+f,E=h.height+u}function m(h){let{left:p,top:f,width:u,height:A}=h;return p<L&&(u-=L-p,p=L),f<x&&(A-=x-f,f=x),p+u>w&&(u=w-p),f+A>E&&(A=E-f),u<y&&(u=y),A<y&&(A=y),{left:p,top:f,width:u,height:A}}function v(h){let p=h.clientX-s,f=h.clientY-c,u=m(z(i,r,a,b,N,p,f));t.onResize?.(u)}function g(h){o.releasePointerCapture&&o.releasePointerCapture(h.pointerId),o.removeEventListener("pointermove",v),o.removeEventListener("pointerup",g);let p=h.clientX-s,f=h.clientY-c,u=m(z(i,r,a,b,N,p,f));t.onStop&&t.onStop(u)}o.addEventListener("pointermove",v),o.addEventListener("pointerup",g)})}M.set(e,()=>{for(let i of n)i.remove()})}function B(e){let t=M.get(e);t&&(t(),M.delete(e))}function F(){return{makeDraggable:J,makeResizable:Z,destroyDraggable:k,destroyResizable:B}}function C(e){let{view:t,editable:n,...i}=e;return i}function Q(e){return{load:typeof e.load=="string"?tt(e.load):e.load,save:typeof e.save=="string"?et(e.save):e.save,delete:typeof e.delete=="string"?nt(e.delete):e.delete}}function tt(e){return()=>fetch(e).then(t=>{if(!t.ok)throw new Error(`Load failed (HTTP ${t.status})`);return t.json()})}function et(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(n=>{if(!n.ok)throw new Error(`Save failed (HTTP ${n.status})`);return n.json()})}function nt(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(n=>{if(!n.ok)throw new Error(`Delete failed (HTTP ${n.status})`)})}var R=class{constructor(t,n){this._mode="view";this.activeEdit=null;this.destroyed=!1;this.pendingRescale=!1;this.originalParent=null;this.originalNextSibling=null;this.options=n,this.handlers=F(),this.img=t,this.naturalWidth=t.naturalWidth||t.width,this.naturalHeight=t.naturalHeight||t.height;let i=t.getBoundingClientRect(),o=i.width||t.width,d=i.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=d/this.naturalHeight,this.notes=n.notes.map(s=>({...s})),this.originalParent=t.parentNode,this.originalNextSibling=t.nextSibling,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 l=document.createElement("div");if(l.className="image-annotate-edit-area",this.editOverlay.appendChild(l),!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?Q(this.options.api):{},this.api.load?this.loadFromApi():this.load(),this.options.editable&&this.createButton(),n.autoResize!==!1&&typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(s=>{let c=s[0];if(!c)return;let{width:r,height:a}=c.contentRect;r===0||a===0||this.rescale(r,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 n={top:t.top/this.scaleY,left:t.left/this.scaleX,width:t.width/this.scaleX,height:t.height/this.scaleY};if(!isFinite(n.top)||!isFinite(n.left)||!isFinite(n.width)||!isFinite(n.height))throw new Error("image-annotate: scale conversion produced non-finite coordinates");return n}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(){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,n){if(this.mode==="edit"){this.pendingRescale=!0;return}this.applyRescale(t,n)}applyRescale(t,n){let i=t/this.naturalWidth,o=n/this.naturalHeight;i===this.scaleX&&o===this.scaleY||(this.scaleX=i,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(n=>({...n})),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 n=t instanceof Error?t:new Error(String(t));this.reportError({type:"load",error:n})})}add(){return this.mode==="view"?(this.setMode("edit"),this.activeEdit=new D(this),!0):!1}};var H={addNote:"Add Note",save:"OK",delete:"Delete",cancel:"Cancel",placeholder:""};var it={editable:!0,notes:[],autoResize:!0,labels:{...H}};function ot(e,t){let n={...it,...t,labels:{...H,...t?.labels}};return new R(e,n)}return U(at);})();
|
|
1
|
+
"use strict";var AnnotateImage=(()=>{var M=Object.defineProperty;var Y=Object.getOwnPropertyDescriptor;var _=Object.getOwnPropertyNames;var W=Object.prototype.hasOwnProperty;var U=(e,t)=>{for(var n in t)M(e,n,{get:t[n],enumerable:!0})},j=(e,t,n,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of _(t))!W.call(e,o)&&o!==n&&M(e,o,{get:()=>t[o],enumerable:!(i=Y(t,o))||i.enumerable});return e};var $=e=>j(M({},"__esModule",{value:!0}),e);var lt={};U(lt,{AnnotateImage:()=>T,DEFAULT_LABELS:()=>P,annotate:()=>rt});function K(e,t,n){let i=Math.min(e.width,t),o=Math.min(e.height,n),h=Math.max(0,Math.min(e.left,t-i));return{top:Math.max(0,Math.min(e.top,n-o)),left:h,width:i,height:o}}function I(e,t,n){for(let i of e)Object.assign(i,K(i,t,n))}function H(e,t,n,i){let o=(n-e)/2,l=t+o+e;l>i&&(o-=l-i);let s=t+o;return s<0&&(o-=s),o}var q=30,J=30,G=30,Z=30,R=class{constructor(t,n,i){this.busy=!1;this.image=t,this.handlers=t.handlers,n?this.note=n:this.note={id:"new",top:q,left:J,width:G,height:Z,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 h=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 l=this.image.options.labels?.placeholder??"";l&&(this.textarea.placeholder=l),h.appendChild(this.textarea),this.form.appendChild(h),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 r=document.createElement("div");r.className="image-annotate-edit-buttons",this.form.appendChild(r),this.addSaveButton(r,i),i&&this.addDeleteButton(r,i),this.addCancelButton(r)}positionForm(){let t=this.form.getBoundingClientRect(),n=this.area.getBoundingClientRect(),i=H(t.width,n.left,n.width,window.innerWidth);this.form.style.left=i+"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,n){let i=document.createElement("button");i.className="image-annotate-edit-ok",i.textContent=this.image.options.labels?.save??"OK",i.type="button",i.addEventListener("click",()=>{if(this.busy)return;let o=this.textarea.value,h=()=>{if(this.image.setMode("view"),n)n.resetPosition(this,o);else{this.note.editable=!0;let r=new D(this.image,this.note);this.note.view=r,r.resetPosition(this,o),this.image.notes.push(this.note)}this.image.notifySave(C(this.note)),this.destroy(),this.image.flushPendingRescale()},l=z(this.area),s=k(this.area),d=this.image.toNatural({top:l.top,left:l.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(r=>{r.annotation_id!=null&&(this.note.id=r.annotation_id),h()}).catch(r=>{this.busy=!1;let a=r instanceof Error?r:new Error(String(r));this.image.reportError({type:"save",error:a,note:this.note})})):h()}),t.appendChild(i)}addDeleteButton(t,n){let i=document.createElement("button");i.className="image-annotate-edit-delete",i.textContent=this.image.options.labels?.delete??"Delete",i.type="button",i.addEventListener("click",()=>{if(this.busy)return;let o=()=>{this.image.setMode("view"),this.destroy(),n.destroy();let h=this.image.notes.indexOf(this.note);h!==-1&&this.image.notes.splice(h,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(h=>{this.busy=!1;let l=h instanceof Error?h:new Error(String(h));this.image.reportError({type:"delete",error:l,note:this.note})})):o()}),t.appendChild(i)}addCancelButton(t){let n=document.createElement("button");n.className="image-annotate-edit-close",n.textContent=this.image.options.labels?.cancel??"Cancel",n.type="button",n.addEventListener("click",()=>{this.image.cancelEdit()}),t.appendChild(n)}};function z(e){return{left:parseInt(e.style.left)||0,top:parseInt(e.style.top)||0}}function k(e){return{width:parseInt(e.style.width)||e.offsetWidth,height:parseInt(e.style.height)||e.offsetHeight}}var D=class{constructor(t,n){this.image=t,this.note=n,this.editable=!!(n.editable&&t.options.editable),this.area=document.createElement("div"),this.area.className="image-annotate-area"+(this.editable?" image-annotate-area-editable":"");let i=document.createElement("div");this.area.appendChild(i),t.viewOverlay.insertBefore(this.area,t.viewOverlay.firstChild),this.tooltip=document.createElement("div"),this.tooltip.className="image-annotate-note",this.tooltip.textContent=n.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),n=this.area.firstElementChild;n.style.height=t.height+"px",n.style.width=t.width+"px",this.area.style.left=t.left+"px",this.area.style.top=t.top+"px"}resetPosition(t,n){this.tooltip.textContent=n,this.tooltip.style.display="none";let i=this.image.toRendered(t.note),o=this.area.firstElementChild;o.style.height=i.height+"px",o.style.width=i.width+"px",this.area.style.left=i.left+"px",this.area.style.top=i.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=n,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(),n=this.area.getBoundingClientRect(),i=H(t.width,n.left,n.width,window.innerWidth);this.tooltip.style.left=i+"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 O=new WeakMap,S=new WeakMap;function Q(e,t){F(e);function n(i){if(i.button!==0)return;i.preventDefault(),e.setPointerCapture&&e.setPointerCapture(i.pointerId);let o=i.clientX,h=i.clientY,l=parseFloat(e.style.left)||0,s=parseFloat(e.style.top)||0,d=parseFloat(e.style.width)||e.offsetWidth,r=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=l-(v.left-p.left),c=s-(v.top-p.top);a=g,y=c,N=p.width-d+g,E=p.height-r+c}function w(p,v,g){return Math.max(v,Math.min(g,p))}function x(p){let v=p.clientX-o,g=p.clientY-h,c=w(l+v,a,N),m=w(s+g,y,E);t.onDrag&&t.onDrag({left:c,top:m})}function L(p){e.releasePointerCapture&&e.releasePointerCapture(p.pointerId),e.removeEventListener("pointermove",x),e.removeEventListener("pointerup",L);let v=p.clientX-o,g=p.clientY-h,c=w(l+v,a,N),m=w(s+g,y,E);t.onStop&&t.onStop({left:c,top:m})}e.addEventListener("pointermove",x),e.addEventListener("pointerup",L)}e.addEventListener("pointerdown",n),O.set(e,()=>{e.removeEventListener("pointerdown",n)})}function F(e){let t=O.get(e);t&&(t(),O.delete(e))}var b=10,tt=["nw","ne","sw","se"];function B(e,t,n,i,o,h,l){let s,d,r,a;return e==="nw"||e==="sw"?(s=t+h,r=i-h):(s=t,r=i+h),e==="nw"||e==="ne"?(d=n+l,a=o-l):(d=n,a=o+l),r<b&&((e==="nw"||e==="sw")&&(s=t+i-b),r=b),a<b&&((e==="nw"||e==="ne")&&(d=n+o-b),a=b),{left:s,top:d,width:r,height:a}}function et(e,t){V(e);let n=[];for(let i of tt){let o=document.createElement("div");o.className=`image-annotate-resize-handle image-annotate-resize-handle-${i}`,e.appendChild(o),n.push(o),o.addEventListener("pointerdown",function(l){if(l.button!==0)return;l.preventDefault(),l.stopPropagation(),o.setPointerCapture&&o.setPointerCapture(l.pointerId);let s=l.clientX,d=l.clientY,r=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,x=-1/0,L=-1/0;if(t.containment){let c=t.containment.getBoundingClientRect(),m=e.getBoundingClientRect(),u=r-(m.left-c.left),f=a-(m.top-c.top);x=u,L=f,E=c.width+u,w=c.height+f}function p(c){let{left:m,top:u,width:f,height:A}=c;return m<x&&(f-=x-m,m=x),u<L&&(A-=L-u,u=L),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(B(i,r,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(B(i,r,a,y,N,m,u));t.onStop&&t.onStop(f)}o.addEventListener("pointermove",v),o.addEventListener("pointerup",g)})}S.set(e,()=>{for(let i of n)i.remove()})}function V(e){let t=S.get(e);t&&(t(),S.delete(e))}function X(){return{makeDraggable:Q,makeResizable:et,destroyDraggable:F,destroyResizable:V}}function C(e){let{view:t,editable:n,...i}=e;return i}function nt(e){return{load:typeof e.load=="string"?it(e.load):e.load,save:typeof e.save=="string"?ot(e.save):e.save,delete:typeof e.delete=="string"?at(e.delete):e.delete}}function it(e){return()=>fetch(e).then(t=>{if(!t.ok)throw new Error(`Load failed (HTTP ${t.status})`);return t.json()})}function ot(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(n=>{if(!n.ok)throw new Error(`Save failed (HTTP ${n.status})`);return n.json()})}function at(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(n=>{if(!n.ok)throw new Error(`Delete failed (HTTP ${n.status})`)})}var T=class{constructor(t,n){this._mode="view";this.activeEdit=null;this.destroyed=!1;this.pendingRescale=!1;this.originalParent=null;this.originalNextSibling=null;this.options=n,this.handlers=X(),this.img=t,this.naturalWidth=t.naturalWidth||t.width,this.naturalHeight=t.naturalHeight||t.height;let i=t.getBoundingClientRect(),o=i.width||t.width,h=i.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=h/this.naturalHeight,this.notes=n.notes.map(s=>({...s})),this.originalParent=t.parentNode,this.originalNextSibling=t.nextSibling,this.canvas=document.createElement("div"),this.canvas.className="image-annotate-canvas",n.theme&&(this.canvas.dataset.theme=n.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 l=document.createElement("div");if(l.className="image-annotate-edit-area",this.editOverlay.appendChild(l),!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?nt(this.options.api):{},this.api.load?this.loadFromApi():this.load(),this.options.editable&&this.createButton(),n.autoResize!==!1&&typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(s=>{let d=s[0];if(!d)return;let{width:r,height:a}=d.contentRect;r===0||a===0||this.rescale(r,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 n={top:t.top/this.scaleY,left:t.left/this.scaleX,width:t.width/this.scaleX,height:t.height/this.scaleY};if(!isFinite(n.top)||!isFinite(n.left)||!isFinite(n.width)||!isFinite(n.height))throw new Error("image-annotate: scale conversion produced non-finite coordinates");return n}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();this.viewOverlay.replaceChildren()}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,n){if(this.mode==="edit"){this.pendingRescale=!0;return}this.applyRescale(t,n)}applyRescale(t,n){let i=t/this.naturalWidth,o=n/this.naturalHeight;i===this.scaleX&&o===this.scaleY||(this.scaleX=i,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(n=>({...n})),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 n=t instanceof Error?t:new Error(String(t));this.reportError({type:"load",error:n})})}add(){return this.mode==="view"?(this.setMode("edit"),this.activeEdit=new R(this),!0):!1}};var P={addNote:"Add Note",save:"OK",delete:"Delete",cancel:"Cancel",placeholder:""};var st={editable:!0,notes:[],autoResize:!0,labels:{...P}};function rt(e,t){let n={...st,...t,labels:{...P,...t?.labels}};return new T(e,n)}return $(lt);})();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.image-annotate-add{--image-annotate-icon-add: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='%23fff'%3E%3Cpath d='M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z'/%3E%3C/svg%3E");appearance:none;background-color
|
|
1
|
+
.image-annotate-add{--image-annotate-icon-add: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='%23fff'%3E%3Cpath d='M224,128a8,8,0,0,1-8,8H136v80a8,8,0,0,1-16,0V136H40a8,8,0,0,1,0-16h80V40a8,8,0,0,1,16,0v80h80A8,8,0,0,1,224,128Z'/%3E%3C/svg%3E");appearance:none;background-color:var(--image-annotate-add-bg);background-image:var(--image-annotate-icon-add);background-repeat:no-repeat;background-position:center;background-size:20px 20px;border:var(--image-annotate-add-border);border-radius:var(--image-annotate-add-radius);bottom:8px;color:#fff;cursor:pointer;height:32px;opacity:0;padding:0;position:absolute;right:8px;transition:opacity .2s,background-color .2s;width:32px;z-index:1}.image-annotate-add:hover{background-color:var(--image-annotate-add-bg-hover)}.image-annotate-add:focus{opacity:1;outline:2px solid var(--image-annotate-hover-editable-color);outline-offset:2px}.image-annotate-canvas{--image-annotate-font-family: Verdana, sans-serif;--image-annotate-font-size: 12px;--image-annotate-area-border: #000;--image-annotate-area-inner-border: #fff;--image-annotate-hover-color: yellow;--image-annotate-hover-editable-color: #00ad00;--image-annotate-note-bg: #e7ffe7;--image-annotate-note-border: #397f39;--image-annotate-note-text: #000;--image-annotate-edit-bg: #fffee3;--image-annotate-edit-border: #000;--image-annotate-button-bg: #fff;--image-annotate-button-bg-hover: #eee;--image-annotate-button-border: #ccc;--image-annotate-button-text: #000;--image-annotate-icon-save: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='%23fff'%3E%3Cpath d='M229.66,77.66l-128,128a8,8,0,0,1-11.32,0l-56-56a8,8,0,0,1,11.32-11.32L96,188.69,218.34,66.34a8,8,0,0,1,11.32,11.32Z'/%3E%3C/svg%3E");--image-annotate-icon-cancel: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='%23fff'%3E%3Cpath d='M205.66,194.34a8,8,0,0,1-11.32,11.32L128,139.31,61.66,205.66a8,8,0,0,1-11.32-11.32L116.69,128,50.34,61.66A8,8,0,0,1,61.66,50.34L128,116.69l66.34-66.35a8,8,0,0,1,11.32,11.32L139.31,128Z'/%3E%3C/svg%3E");--image-annotate-icon-delete: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256' fill='%23fff'%3E%3Cpath d='M216,48H176V40a24,24,0,0,0-24-24H104A24,24,0,0,0,80,40v8H40a8,8,0,0,0,0,16h8V208a16,16,0,0,0,16,16H192a16,16,0,0,0,16-16V64h8a8,8,0,0,0,0-16ZM96,40a8,8,0,0,1,8-8h48a8,8,0,0,1,8,8v8H96Zm96,168H64V64H192ZM112,104v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Zm48,0v64a8,8,0,0,1-16,0V104a8,8,0,0,1,16,0Z'/%3E%3C/svg%3E");--image-annotate-canvas-border: none;--image-annotate-area-bg: transparent;--image-annotate-area-border-width: 1px;--image-annotate-area-border-style: solid;--image-annotate-area-radius: 0;--image-annotate-hover-bg: transparent;--image-annotate-note-radius: 0;--image-annotate-note-shadow: none;--image-annotate-edit-radius: 0;--image-annotate-edit-shadow: none;--image-annotate-note-max-width: 300px;--image-annotate-edit-max-width: 300px;--image-annotate-add-bg: rgba(0, 0, 0, .4);--image-annotate-add-bg-hover: rgba(0, 0, 0, .6);--image-annotate-add-border: 1px solid rgba(255, 255, 255, .5);--image-annotate-add-radius: 4px;border:var(--image-annotate-canvas-border);display:inline-block;margin:0;max-width:100%;position:relative}.image-annotate-canvas>img{display:block;height:auto;max-width:100%}.image-annotate-view{display:none;inset:0;position:absolute}@media(hover:hover){.image-annotate-canvas:hover:not(.image-annotate-editing) .image-annotate-view{display:block}}@media(hover:none){.image-annotate-canvas:not(.image-annotate-editing) .image-annotate-view{display:block}}@media(hover:hover){.image-annotate-canvas:hover:not(.image-annotate-editing) .image-annotate-add{opacity:1}}@media(hover:none){.image-annotate-canvas:not(.image-annotate-editing) .image-annotate-add{opacity:1}}.image-annotate-editing .image-annotate-add{display:none}.image-annotate-area{background-color:var(--image-annotate-area-bg);border:var(--image-annotate-area-border-width) var(--image-annotate-area-border-style) var(--image-annotate-area-border);border-radius:var(--image-annotate-area-radius);position:absolute}.image-annotate-area div{border:1px solid var(--image-annotate-area-inner-border);border-radius:var(--image-annotate-area-radius);box-sizing:border-box;display:block}.image-annotate-area-hover div{border-color:var(--image-annotate-hover-color)}.image-annotate-area-hover{background-color:var(--image-annotate-hover-bg);z-index:1}.image-annotate-area-editable{cursor:pointer}.image-annotate-area-editable-hover{z-index:1}.image-annotate-area-editable-hover div{border-color:var(--image-annotate-hover-editable-color)}.image-annotate-note{background-color:var(--image-annotate-note-bg);border:solid 1px var(--image-annotate-note-border);border-radius:var(--image-annotate-note-radius);box-shadow:var(--image-annotate-note-shadow);color:var(--image-annotate-note-text);display:none;font-family:var(--image-annotate-font-family);font-size:var(--image-annotate-font-size);left:0;max-width:var(--image-annotate-note-max-width);padding:3px 7px;position:absolute;top:calc(100% + 7px);width:max-content}.image-annotate-note .actions{display:block;font-size:80%}.image-annotate-edit{display:none;inset:0;position:absolute}.image-annotate-edit-form{background-color:var(--image-annotate-edit-bg);border:1px solid var(--image-annotate-edit-border);border-radius:var(--image-annotate-edit-radius);box-shadow:var(--image-annotate-edit-shadow);box-sizing:border-box;cursor:default;display:flex;flex-direction:column;gap:7px;left:0;max-width:var(--image-annotate-edit-max-width);min-width:250px;padding:7px;position:absolute;top:calc(100% + 7px);width:max-content}.image-annotate-edit-form form{margin:0;padding:0}.image-annotate-edit-form textarea{box-sizing:border-box;font-family:var(--image-annotate-font-family);font-size:var(--image-annotate-font-size);height:50px;width:100%}.image-annotate-edit-buttons{display:flex;flex-wrap:nowrap;gap:6px}.image-annotate-edit-form button{appearance:none;background-color:var(--image-annotate-button-bg);border:solid 1px var(--image-annotate-button-border);color:var(--image-annotate-button-text);cursor:pointer;font-family:var(--image-annotate-font-family);font-size:var(--image-annotate-font-size);line-height:18px;min-height:22px;padding:2px 6px 2px 24px;position:relative;white-space:nowrap}.image-annotate-edit-form button:before{background-color:var(--image-annotate-button-text);content:"";height:16px;left:3px;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:16px 16px;mask-size:16px 16px;position:absolute;top:50%;transform:translateY(-50%);width:16px}.image-annotate-edit-form button:empty{min-width:24px;padding:2px 4px}.image-annotate-edit-form button:empty:before{left:50%;transform:translate(-50%,-50%)}.image-annotate-edit-form button:hover{background-color:var(--image-annotate-button-bg-hover)}.image-annotate-edit-area{border:1px solid var(--image-annotate-area-border);cursor:move;display:block;height:60px;left:10px;margin:0;padding:0;position:absolute;top:10px;width:60px}.image-annotate-resize-handle{background-color:var(--image-annotate-area-border);border:1px solid var(--image-annotate-area-inner-border);box-sizing:border-box;height:8px;position:absolute;width:8px}.image-annotate-resize-handle-nw{cursor:nw-resize;left:-4px;top:-4px}.image-annotate-resize-handle-ne{cursor:ne-resize;right:-4px;top:-4px}.image-annotate-resize-handle-sw{cursor:sw-resize;left:-4px;bottom:-4px}.image-annotate-resize-handle-se{cursor:se-resize;right:-4px;bottom:-4px}.image-annotate-edit-ok:before{-webkit-mask-image:var(--image-annotate-icon-save);mask-image:var(--image-annotate-icon-save)}.image-annotate-edit-delete:before{-webkit-mask-image:var(--image-annotate-icon-delete);mask-image:var(--image-annotate-icon-delete)}.image-annotate-edit-close:before{-webkit-mask-image:var(--image-annotate-icon-cancel);mask-image:var(--image-annotate-icon-cancel)}.image-annotate-area-editable:focus{outline:2px solid var(--image-annotate-hover-editable-color);outline-offset:2px}.image-annotate-edit-form button:focus{outline:2px solid var(--image-annotate-hover-editable-color);outline-offset:1px}
|
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;
|
|
@@ -47,6 +74,8 @@ var AnnotateEdit = class {
|
|
|
47
74
|
formEl.appendChild(this.textarea);
|
|
48
75
|
this.form.appendChild(formEl);
|
|
49
76
|
this.area.appendChild(this.form);
|
|
77
|
+
this.positionForm();
|
|
78
|
+
this.textarea.focus();
|
|
50
79
|
this.form.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
51
80
|
const area = this.area;
|
|
52
81
|
const applyRect = (rect) => {
|
|
@@ -58,7 +87,10 @@ var AnnotateEdit = class {
|
|
|
58
87
|
this.handlers.makeResizable(area, {
|
|
59
88
|
containment: image.canvas,
|
|
60
89
|
onResize: applyRect,
|
|
61
|
-
onStop:
|
|
90
|
+
onStop: (rect) => {
|
|
91
|
+
applyRect(rect);
|
|
92
|
+
this.positionForm();
|
|
93
|
+
}
|
|
62
94
|
});
|
|
63
95
|
this.handlers.makeDraggable(area, {
|
|
64
96
|
containment: image.canvas,
|
|
@@ -69,9 +101,9 @@ var AnnotateEdit = class {
|
|
|
69
101
|
onStop: (pos) => {
|
|
70
102
|
area.style.left = pos.left + "px";
|
|
71
103
|
area.style.top = pos.top + "px";
|
|
104
|
+
this.positionForm();
|
|
72
105
|
}
|
|
73
106
|
});
|
|
74
|
-
this.textarea.focus();
|
|
75
107
|
this.form.addEventListener("keydown", (e) => {
|
|
76
108
|
if (e.key === "Escape") {
|
|
77
109
|
const cancelBtn = this.form.querySelector(".image-annotate-edit-close");
|
|
@@ -87,6 +119,13 @@ var AnnotateEdit = class {
|
|
|
87
119
|
}
|
|
88
120
|
this.addCancelButton(buttonRow);
|
|
89
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
|
+
}
|
|
90
129
|
/** Tear down the edit form and interaction handlers. */
|
|
91
130
|
destroy() {
|
|
92
131
|
this.image.activeEdit = null;
|
|
@@ -113,6 +152,7 @@ var AnnotateEdit = class {
|
|
|
113
152
|
} else {
|
|
114
153
|
this.note.editable = true;
|
|
115
154
|
const view = new AnnotateView(this.image, this.note);
|
|
155
|
+
this.note.view = view;
|
|
116
156
|
view.resetPosition(this, text);
|
|
117
157
|
this.image.notes.push(this.note);
|
|
118
158
|
}
|
|
@@ -270,7 +310,13 @@ var AnnotateView = class {
|
|
|
270
310
|
}
|
|
271
311
|
/** Show the tooltip and apply hover styling. */
|
|
272
312
|
show() {
|
|
313
|
+
this.tooltip.style.visibility = "hidden";
|
|
273
314
|
this.tooltip.style.display = "block";
|
|
315
|
+
const noteRect = this.tooltip.getBoundingClientRect();
|
|
316
|
+
const areaRect = this.area.getBoundingClientRect();
|
|
317
|
+
const left = computeNoteLeft(noteRect.width, areaRect.left, areaRect.width, window.innerWidth);
|
|
318
|
+
this.tooltip.style.left = left + "px";
|
|
319
|
+
this.tooltip.style.visibility = "";
|
|
274
320
|
if (!this.editable) {
|
|
275
321
|
this.area.classList.add("image-annotate-area-hover");
|
|
276
322
|
} else {
|
|
@@ -368,17 +414,19 @@ function destroyDraggable(el) {
|
|
|
368
414
|
var MIN_SIZE = 10;
|
|
369
415
|
var CORNERS = ["nw", "ne", "sw", "se"];
|
|
370
416
|
function computeResize(corner, startLeft, startTop, startWidth, startHeight, dx, dy) {
|
|
371
|
-
let left
|
|
417
|
+
let left, top, width, height;
|
|
372
418
|
if (corner === "nw" || corner === "sw") {
|
|
373
419
|
left = startLeft + dx;
|
|
374
420
|
width = startWidth - dx;
|
|
375
421
|
} else {
|
|
422
|
+
left = startLeft;
|
|
376
423
|
width = startWidth + dx;
|
|
377
424
|
}
|
|
378
425
|
if (corner === "nw" || corner === "ne") {
|
|
379
426
|
top = startTop + dy;
|
|
380
427
|
height = startHeight - dy;
|
|
381
428
|
} else {
|
|
429
|
+
top = startTop;
|
|
382
430
|
height = startHeight + dy;
|
|
383
431
|
}
|
|
384
432
|
if (width < MIN_SIZE) {
|
|
@@ -554,6 +602,9 @@ var AnnotateImage = class {
|
|
|
554
602
|
this.originalNextSibling = img.nextSibling;
|
|
555
603
|
this.canvas = document.createElement("div");
|
|
556
604
|
this.canvas.className = "image-annotate-canvas";
|
|
605
|
+
if (options.theme) {
|
|
606
|
+
this.canvas.dataset.theme = options.theme;
|
|
607
|
+
}
|
|
557
608
|
this.viewOverlay = document.createElement("div");
|
|
558
609
|
this.viewOverlay.className = "image-annotate-view";
|
|
559
610
|
this.editOverlay = document.createElement("div");
|
|
@@ -654,6 +705,7 @@ var AnnotateImage = class {
|
|
|
654
705
|
for (const note of this.notes) {
|
|
655
706
|
note.view?.destroy();
|
|
656
707
|
}
|
|
708
|
+
this.viewOverlay.replaceChildren();
|
|
657
709
|
}
|
|
658
710
|
createViews() {
|
|
659
711
|
for (const note of this.notes) {
|
|
@@ -663,6 +715,7 @@ var AnnotateImage = class {
|
|
|
663
715
|
/** Rebuild annotation views from the current notes array. */
|
|
664
716
|
load() {
|
|
665
717
|
this.destroyViews();
|
|
718
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
666
719
|
this.createViews();
|
|
667
720
|
this.notifyLoad();
|
|
668
721
|
}
|
|
@@ -771,6 +824,7 @@ var AnnotateImage = class {
|
|
|
771
824
|
this.api.load().then((notes) => {
|
|
772
825
|
this.destroyViews();
|
|
773
826
|
this.notes = notes;
|
|
827
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
774
828
|
this.createViews();
|
|
775
829
|
this.notifyLoad();
|
|
776
830
|
}).catch((err) => {
|
package/dist/jquery.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";(()=>{var F=30,X=30,Y=30,_=30,D=class{constructor(t,n,i){this.busy=!1;this.image=t,this.handlers=t.handlers,n?this.note=n:this.note={id:"new",top:F,left:X,width:Y,height:_,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 d=this.image.options.labels?.placeholder??"";d&&(this.textarea.placeholder=d),l.appendChild(this.textarea),this.form.appendChild(l),this.area.appendChild(this.form),this.form.addEventListener("pointerdown",a=>a.stopPropagation());let s=this.area,c=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:c,onStop:c}),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.textarea.focus(),this.form.addEventListener("keydown",a=>{if(a.key==="Escape"){let b=this.form.querySelector(".image-annotate-edit-close");b&&b.click()}});let r=document.createElement("div");r.className="image-annotate-edit-buttons",this.form.appendChild(r),this.addSaveButton(r,i),i&&this.addDeleteButton(r,i),this.addCancelButton(r)}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,n){let i=document.createElement("button");i.className="image-annotate-edit-ok",i.textContent=this.image.options.labels?.save??"OK",i.type="button",i.addEventListener("click",()=>{if(this.busy)return;let o=this.textarea.value,l=()=>{this.image.setMode("view"),n?n.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(),this.image.flushPendingRescale()},d=O(this.area),s=M(this.area),c=this.image.toNatural({top:d.top,left:d.left,width:s.width,height:s.height});this.note.top=c.top,this.note.left=c.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(r=>{r.annotation_id!=null&&(this.note.id=r.annotation_id),l()}).catch(r=>{this.busy=!1;let a=r instanceof Error?r:new Error(String(r));this.image.reportError({type:"save",error:a,note:this.note})})):l()}),t.appendChild(i)}addDeleteButton(t,n){let i=document.createElement("button");i.className="image-annotate-edit-delete",i.textContent=this.image.options.labels?.delete??"Delete",i.type="button",i.addEventListener("click",()=>{if(this.busy)return;let o=()=>{this.image.setMode("view"),this.destroy(),n.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 d=l instanceof Error?l:new Error(String(l));this.image.reportError({type:"delete",error:d,note:this.note})})):o()}),t.appendChild(i)}addCancelButton(t){let n=document.createElement("button");n.className="image-annotate-edit-close",n.textContent=this.image.options.labels?.cancel??"Cancel",n.type="button",n.addEventListener("click",()=>{this.image.cancelEdit()}),t.appendChild(n)}};function O(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,n){this.image=t,this.note=n,this.editable=!!(n.editable&&t.options.editable),this.area=document.createElement("div"),this.area.className="image-annotate-area"+(this.editable?" image-annotate-area-editable":"");let i=document.createElement("div");this.area.appendChild(i),t.viewOverlay.insertBefore(this.area,t.viewOverlay.firstChild),this.tooltip=document.createElement("div"),this.tooltip.className="image-annotate-note",this.tooltip.textContent=n.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),n=this.area.firstElementChild;n.style.height=t.height+"px",n.style.width=t.width+"px",this.area.style.left=t.left+"px",this.area.style.top=t.top+"px"}resetPosition(t,n){this.tooltip.textContent=n,this.tooltip.style.display="none";let i=this.image.toRendered(t.note),o=this.area.firstElementChild;o.style.height=i.height+"px",o.style.width=i.width+"px",this.area.style.left=i.left+"px",this.area.style.top=i.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=n,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 I=new WeakMap,P=new WeakMap;function V(e,t){z(e);function n(i){if(i.button!==0)return;i.preventDefault(),e.setPointerCapture&&e.setPointerCapture(i.pointerId);let o=i.clientX,l=i.clientY,d=parseFloat(e.style.left)||0,s=parseFloat(e.style.top)||0,c=parseFloat(e.style.width)||e.offsetWidth,r=parseFloat(e.style.height)||e.offsetHeight,a=-1/0,b=-1/0,N=1/0,E=1/0;if(t.containment){let m=t.containment.getBoundingClientRect(),v=e.getBoundingClientRect(),g=d-(v.left-m.left),h=s-(v.top-m.top);a=g,b=h,N=m.width-c+g,E=m.height-r+h}function w(m,v,g){return Math.max(v,Math.min(g,m))}function L(m){let v=m.clientX-o,g=m.clientY-l,h=w(d+v,a,N),p=w(s+g,b,E);t.onDrag&&t.onDrag({left:h,top:p})}function x(m){e.releasePointerCapture&&e.releasePointerCapture(m.pointerId),e.removeEventListener("pointermove",L),e.removeEventListener("pointerup",x);let v=m.clientX-o,g=m.clientY-l,h=w(d+v,a,N),p=w(s+g,b,E);t.onStop&&t.onStop({left:h,top:p})}e.addEventListener("pointermove",L),e.addEventListener("pointerup",x)}e.addEventListener("pointerdown",n),I.set(e,()=>{e.removeEventListener("pointerdown",n)})}function z(e){let t=I.get(e);t&&(t(),I.delete(e))}var y=10,W=["nw","ne","sw","se"];function S(e,t,n,i,o,l,d){let s=t,c=n,r=i,a=o;return e==="nw"||e==="sw"?(s=t+l,r=i-l):r=i+l,e==="nw"||e==="ne"?(c=n+d,a=o-d):a=o+d,r<y&&((e==="nw"||e==="sw")&&(s=t+i-y),r=y),a<y&&((e==="nw"||e==="ne")&&(c=n+o-y),a=y),{left:s,top:c,width:r,height:a}}function U(e,t){k(e);let n=[];for(let i of W){let o=document.createElement("div");o.className=`image-annotate-resize-handle image-annotate-resize-handle-${i}`,e.appendChild(o),n.push(o),o.addEventListener("pointerdown",function(d){if(d.button!==0)return;d.preventDefault(),d.stopPropagation(),o.setPointerCapture&&o.setPointerCapture(d.pointerId);let s=d.clientX,c=d.clientY,r=parseFloat(e.style.left)||0,a=parseFloat(e.style.top)||0,b=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 h=t.containment.getBoundingClientRect(),p=e.getBoundingClientRect(),f=r-(p.left-h.left),u=a-(p.top-h.top);L=f,x=u,E=h.width+f,w=h.height+u}function m(h){let{left:p,top:f,width:u,height:A}=h;return p<L&&(u-=L-p,p=L),f<x&&(A-=x-f,f=x),p+u>E&&(u=E-p),f+A>w&&(A=w-f),u<y&&(u=y),A<y&&(A=y),{left:p,top:f,width:u,height:A}}function v(h){let p=h.clientX-s,f=h.clientY-c,u=m(S(i,r,a,b,N,p,f));t.onResize?.(u)}function g(h){o.releasePointerCapture&&o.releasePointerCapture(h.pointerId),o.removeEventListener("pointermove",v),o.removeEventListener("pointerup",g);let p=h.clientX-s,f=h.clientY-c,u=m(S(i,r,a,b,N,p,f));t.onStop&&t.onStop(u)}o.addEventListener("pointermove",v),o.addEventListener("pointerup",g)})}P.set(e,()=>{for(let i of n)i.remove()})}function k(e){let t=P.get(e);t&&(t(),P.delete(e))}function B(){return{makeDraggable:V,makeResizable:U,destroyDraggable:z,destroyResizable:k}}function C(e){let{view:t,editable:n,...i}=e;return i}function j(e){return{load:typeof e.load=="string"?J(e.load):e.load,save:typeof e.save=="string"?K(e.save):e.save,delete:typeof e.delete=="string"?q(e.delete):e.delete}}function J(e){return()=>fetch(e).then(t=>{if(!t.ok)throw new Error(`Load failed (HTTP ${t.status})`);return t.json()})}function K(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(n=>{if(!n.ok)throw new Error(`Save failed (HTTP ${n.status})`);return n.json()})}function q(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(n=>{if(!n.ok)throw new Error(`Delete failed (HTTP ${n.status})`)})}var H=class{constructor(t,n){this._mode="view";this.activeEdit=null;this.destroyed=!1;this.pendingRescale=!1;this.originalParent=null;this.originalNextSibling=null;this.options=n,this.handlers=B(),this.img=t,this.naturalWidth=t.naturalWidth||t.width,this.naturalHeight=t.naturalHeight||t.height;let i=t.getBoundingClientRect(),o=i.width||t.width,l=i.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=n.notes.map(s=>({...s})),this.originalParent=t.parentNode,this.originalNextSibling=t.nextSibling,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 d=document.createElement("div");if(d.className="image-annotate-edit-area",this.editOverlay.appendChild(d),!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(),n.autoResize!==!1&&typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(s=>{let c=s[0];if(!c)return;let{width:r,height:a}=c.contentRect;r===0||a===0||this.rescale(r,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 n={top:t.top/this.scaleY,left:t.left/this.scaleX,width:t.width/this.scaleX,height:t.height/this.scaleY};if(!isFinite(n.top)||!isFinite(n.left)||!isFinite(n.width)||!isFinite(n.height))throw new Error("image-annotate: scale conversion produced non-finite coordinates");return n}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(){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,n){if(this.mode==="edit"){this.pendingRescale=!0;return}this.applyRescale(t,n)}applyRescale(t,n){let i=t/this.naturalWidth,o=n/this.naturalHeight;i===this.scaleX&&o===this.scaleY||(this.scaleX=i,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(n=>({...n})),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 n=t instanceof Error?t:new Error(String(t));this.reportError({type:"load",error:n})})}add(){return this.mode==="view"?(this.setMode("edit"),this.activeEdit=new D(this),!0):!1}};var R={addNote:"Add Note",save:"OK",delete:"Delete",cancel:"Cancel",placeholder:""};var Q={editable:!0,notes:[],labels:{...R}};$.fn.annotateImage=function(e){if(e==="destroy"){let l=this.data("annotateImage");return l&&(l.destroy(),this.removeData("annotateImage")),this}let t=e,n={...Q,...t,labels:{...R,...t?.labels}},i=this[0],o=new H(i,n);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),h=Math.max(0,Math.min(e.left,t-n));return{top:Math.max(0,Math.min(e.top,i-o)),left:h,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,l=t+o+e;l>n&&(o-=l-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 h=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 l=this.image.options.labels?.placeholder??"";l&&(this.textarea.placeholder=l),h.appendChild(this.textarea),this.form.appendChild(h),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 r=document.createElement("div");r.className="image-annotate-edit-buttons",this.form.appendChild(r),this.addSaveButton(r,n),n&&this.addDeleteButton(r,n),this.addCancelButton(r)}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,h=()=>{if(this.image.setMode("view"),i)i.resetPosition(this,o);else{this.note.editable=!0;let r=new D(this.image,this.note);this.note.view=r,r.resetPosition(this,o),this.image.notes.push(this.note)}this.image.notifySave(C(this.note)),this.destroy(),this.image.flushPendingRescale()},l=S(this.area),s=z(this.area),d=this.image.toNatural({top:l.top,left:l.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(r=>{r.annotation_id!=null&&(this.note.id=r.annotation_id),h()}).catch(r=>{this.busy=!1;let a=r instanceof Error?r:new Error(String(r));this.image.reportError({type:"save",error:a,note:this.note})})):h()}),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 h=this.image.notes.indexOf(this.note);h!==-1&&this.image.notes.splice(h,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(h=>{this.busy=!1;let l=h instanceof Error?h:new Error(String(h));this.image.reportError({type:"delete",error:l,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,h=n.clientY,l=parseFloat(e.style.left)||0,s=parseFloat(e.style.top)||0,d=parseFloat(e.style.width)||e.offsetWidth,r=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=l-(v.left-p.left),c=s-(v.top-p.top);a=g,y=c,N=p.width-d+g,E=p.height-r+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-h,c=w(l+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-h,c=w(l+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,h,l){let s,d,r,a;return e==="nw"||e==="sw"?(s=t+h,r=n-h):(s=t,r=n+h),e==="nw"||e==="ne"?(d=i+l,a=o-l):(d=i,a=o+l),r<b&&((e==="nw"||e==="sw")&&(s=t+n-b),r=b),a<b&&((e==="nw"||e==="ne")&&(d=i+o-b),a=b),{left:s,top:d,width:r,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(l){if(l.button!==0)return;l.preventDefault(),l.stopPropagation(),o.setPointerCapture&&o.setPointerCapture(l.pointerId);let s=l.clientX,d=l.clientY,r=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=r-(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,r,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,r,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,h=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=h/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 l=document.createElement("div");if(l.className="image-annotate-edit-area",this.editOverlay.appendChild(l),!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:r,height:a}=d.contentRect;r===0||a===0||this.rescale(r,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();this.viewOverlay.replaceChildren()}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 h=this.data("annotateImage");return h&&(h.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};})();
|
package/dist/react.js
CHANGED
|
@@ -7,6 +7,33 @@ import {
|
|
|
7
7
|
useRef
|
|
8
8
|
} from "react";
|
|
9
9
|
|
|
10
|
+
// src/positioning.ts
|
|
11
|
+
function clampNote(note, naturalWidth, naturalHeight) {
|
|
12
|
+
const width = Math.min(note.width, naturalWidth);
|
|
13
|
+
const height = Math.min(note.height, naturalHeight);
|
|
14
|
+
const left = Math.max(0, Math.min(note.left, naturalWidth - width));
|
|
15
|
+
const top = Math.max(0, Math.min(note.top, naturalHeight - height));
|
|
16
|
+
return { top, left, width, height };
|
|
17
|
+
}
|
|
18
|
+
function clampNotes(notes, naturalWidth, naturalHeight) {
|
|
19
|
+
for (const note of notes) {
|
|
20
|
+
Object.assign(note, clampNote(note, naturalWidth, naturalHeight));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function computeNoteLeft(noteWidth, areaLeftInViewport, areaWidth, viewportWidth) {
|
|
24
|
+
let left = (areaWidth - noteWidth) / 2;
|
|
25
|
+
const noteLeftInViewport = areaLeftInViewport + left;
|
|
26
|
+
const noteRightInViewport = noteLeftInViewport + noteWidth;
|
|
27
|
+
if (noteRightInViewport > viewportWidth) {
|
|
28
|
+
left -= noteRightInViewport - viewportWidth;
|
|
29
|
+
}
|
|
30
|
+
const adjustedNoteLeft = areaLeftInViewport + left;
|
|
31
|
+
if (adjustedNoteLeft < 0) {
|
|
32
|
+
left -= adjustedNoteLeft;
|
|
33
|
+
}
|
|
34
|
+
return left;
|
|
35
|
+
}
|
|
36
|
+
|
|
10
37
|
// src/annotate-edit.ts
|
|
11
38
|
var DEFAULT_NOTE_TOP = 30;
|
|
12
39
|
var DEFAULT_NOTE_LEFT = 30;
|
|
@@ -56,6 +83,8 @@ var AnnotateEdit = class {
|
|
|
56
83
|
formEl.appendChild(this.textarea);
|
|
57
84
|
this.form.appendChild(formEl);
|
|
58
85
|
this.area.appendChild(this.form);
|
|
86
|
+
this.positionForm();
|
|
87
|
+
this.textarea.focus();
|
|
59
88
|
this.form.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
60
89
|
const area = this.area;
|
|
61
90
|
const applyRect = (rect) => {
|
|
@@ -67,7 +96,10 @@ var AnnotateEdit = class {
|
|
|
67
96
|
this.handlers.makeResizable(area, {
|
|
68
97
|
containment: image.canvas,
|
|
69
98
|
onResize: applyRect,
|
|
70
|
-
onStop:
|
|
99
|
+
onStop: (rect) => {
|
|
100
|
+
applyRect(rect);
|
|
101
|
+
this.positionForm();
|
|
102
|
+
}
|
|
71
103
|
});
|
|
72
104
|
this.handlers.makeDraggable(area, {
|
|
73
105
|
containment: image.canvas,
|
|
@@ -78,9 +110,9 @@ var AnnotateEdit = class {
|
|
|
78
110
|
onStop: (pos) => {
|
|
79
111
|
area.style.left = pos.left + "px";
|
|
80
112
|
area.style.top = pos.top + "px";
|
|
113
|
+
this.positionForm();
|
|
81
114
|
}
|
|
82
115
|
});
|
|
83
|
-
this.textarea.focus();
|
|
84
116
|
this.form.addEventListener("keydown", (e) => {
|
|
85
117
|
if (e.key === "Escape") {
|
|
86
118
|
const cancelBtn = this.form.querySelector(".image-annotate-edit-close");
|
|
@@ -96,6 +128,13 @@ var AnnotateEdit = class {
|
|
|
96
128
|
}
|
|
97
129
|
this.addCancelButton(buttonRow);
|
|
98
130
|
}
|
|
131
|
+
/** Recompute the form's horizontal position relative to the area. */
|
|
132
|
+
positionForm() {
|
|
133
|
+
const formRect = this.form.getBoundingClientRect();
|
|
134
|
+
const areaRect = this.area.getBoundingClientRect();
|
|
135
|
+
const left = computeNoteLeft(formRect.width, areaRect.left, areaRect.width, window.innerWidth);
|
|
136
|
+
this.form.style.left = left + "px";
|
|
137
|
+
}
|
|
99
138
|
/** Tear down the edit form and interaction handlers. */
|
|
100
139
|
destroy() {
|
|
101
140
|
this.image.activeEdit = null;
|
|
@@ -122,6 +161,7 @@ var AnnotateEdit = class {
|
|
|
122
161
|
} else {
|
|
123
162
|
this.note.editable = true;
|
|
124
163
|
const view = new AnnotateView(this.image, this.note);
|
|
164
|
+
this.note.view = view;
|
|
125
165
|
view.resetPosition(this, text);
|
|
126
166
|
this.image.notes.push(this.note);
|
|
127
167
|
}
|
|
@@ -279,7 +319,13 @@ var AnnotateView = class {
|
|
|
279
319
|
}
|
|
280
320
|
/** Show the tooltip and apply hover styling. */
|
|
281
321
|
show() {
|
|
322
|
+
this.tooltip.style.visibility = "hidden";
|
|
282
323
|
this.tooltip.style.display = "block";
|
|
324
|
+
const noteRect = this.tooltip.getBoundingClientRect();
|
|
325
|
+
const areaRect = this.area.getBoundingClientRect();
|
|
326
|
+
const left = computeNoteLeft(noteRect.width, areaRect.left, areaRect.width, window.innerWidth);
|
|
327
|
+
this.tooltip.style.left = left + "px";
|
|
328
|
+
this.tooltip.style.visibility = "";
|
|
283
329
|
if (!this.editable) {
|
|
284
330
|
this.area.classList.add("image-annotate-area-hover");
|
|
285
331
|
} else {
|
|
@@ -377,17 +423,19 @@ function destroyDraggable(el) {
|
|
|
377
423
|
var MIN_SIZE = 10;
|
|
378
424
|
var CORNERS = ["nw", "ne", "sw", "se"];
|
|
379
425
|
function computeResize(corner, startLeft, startTop, startWidth, startHeight, dx, dy) {
|
|
380
|
-
let left
|
|
426
|
+
let left, top, width, height;
|
|
381
427
|
if (corner === "nw" || corner === "sw") {
|
|
382
428
|
left = startLeft + dx;
|
|
383
429
|
width = startWidth - dx;
|
|
384
430
|
} else {
|
|
431
|
+
left = startLeft;
|
|
385
432
|
width = startWidth + dx;
|
|
386
433
|
}
|
|
387
434
|
if (corner === "nw" || corner === "ne") {
|
|
388
435
|
top = startTop + dy;
|
|
389
436
|
height = startHeight - dy;
|
|
390
437
|
} else {
|
|
438
|
+
top = startTop;
|
|
391
439
|
height = startHeight + dy;
|
|
392
440
|
}
|
|
393
441
|
if (width < MIN_SIZE) {
|
|
@@ -563,6 +611,9 @@ var AnnotateImage = class {
|
|
|
563
611
|
this.originalNextSibling = img.nextSibling;
|
|
564
612
|
this.canvas = document.createElement("div");
|
|
565
613
|
this.canvas.className = "image-annotate-canvas";
|
|
614
|
+
if (options.theme) {
|
|
615
|
+
this.canvas.dataset.theme = options.theme;
|
|
616
|
+
}
|
|
566
617
|
this.viewOverlay = document.createElement("div");
|
|
567
618
|
this.viewOverlay.className = "image-annotate-view";
|
|
568
619
|
this.editOverlay = document.createElement("div");
|
|
@@ -663,6 +714,7 @@ var AnnotateImage = class {
|
|
|
663
714
|
for (const note of this.notes) {
|
|
664
715
|
note.view?.destroy();
|
|
665
716
|
}
|
|
717
|
+
this.viewOverlay.replaceChildren();
|
|
666
718
|
}
|
|
667
719
|
createViews() {
|
|
668
720
|
for (const note of this.notes) {
|
|
@@ -672,6 +724,7 @@ var AnnotateImage = class {
|
|
|
672
724
|
/** Rebuild annotation views from the current notes array. */
|
|
673
725
|
load() {
|
|
674
726
|
this.destroyViews();
|
|
727
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
675
728
|
this.createViews();
|
|
676
729
|
this.notifyLoad();
|
|
677
730
|
}
|
|
@@ -780,6 +833,7 @@ var AnnotateImage = class {
|
|
|
780
833
|
this.api.load().then((notes) => {
|
|
781
834
|
this.destroyViews();
|
|
782
835
|
this.notes = notes;
|
|
836
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
783
837
|
this.createViews();
|
|
784
838
|
this.notifyLoad();
|
|
785
839
|
}).catch((err) => {
|
|
@@ -21,6 +21,8 @@ export declare class AnnotateEdit {
|
|
|
21
21
|
* @param existingView - The view being edited (for updates); omit for new annotations.
|
|
22
22
|
*/
|
|
23
23
|
constructor(image: AnnotateImage, note?: AnnotationNote, existingView?: AnnotateView);
|
|
24
|
+
/** Recompute the form's horizontal position relative to the area. */
|
|
25
|
+
private positionForm;
|
|
24
26
|
/** Tear down the edit form and interaction handlers. */
|
|
25
27
|
destroy(): void;
|
|
26
28
|
private addSaveButton;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Clamp a note's size and position to fit within the given natural image dimensions. */
|
|
2
|
+
export declare function clampNote(note: {
|
|
3
|
+
top: number;
|
|
4
|
+
left: number;
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
}, naturalWidth: number, naturalHeight: number): {
|
|
8
|
+
top: number;
|
|
9
|
+
left: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
};
|
|
13
|
+
/** Clamp all notes in the array in place, constraining geometry to image bounds. */
|
|
14
|
+
export declare function clampNotes(notes: {
|
|
15
|
+
top: number;
|
|
16
|
+
left: number;
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}[], naturalWidth: number, naturalHeight: number): void;
|
|
20
|
+
/**
|
|
21
|
+
* Compute the horizontal `left` value for a note/form element
|
|
22
|
+
* relative to its parent annotation area, centering it under the area
|
|
23
|
+
* and clamping to viewport edges.
|
|
24
|
+
*
|
|
25
|
+
* @param noteWidth - Measured width of the note/form element
|
|
26
|
+
* @param areaLeftInViewport - The area's left edge in viewport coordinates
|
|
27
|
+
* @param areaWidth - Width of the annotation area
|
|
28
|
+
* @param viewportWidth - Browser viewport width (window.innerWidth)
|
|
29
|
+
* @returns CSS `left` value in pixels, relative to the area element
|
|
30
|
+
*/
|
|
31
|
+
export declare function computeNoteLeft(noteWidth: number, areaLeftInViewport: number, areaWidth: number, viewportWidth: number): number;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -86,6 +86,8 @@ export interface AnnotateImageOptions {
|
|
|
86
86
|
labels?: Labels;
|
|
87
87
|
/** Attach a ResizeObserver to rescale annotations when the image resizes. Default: true. */
|
|
88
88
|
autoResize?: boolean;
|
|
89
|
+
/** CSS theme name. When set, adds `data-theme="<value>"` to the canvas element for CSS variable scoping. */
|
|
90
|
+
theme?: string;
|
|
89
91
|
}
|
|
90
92
|
export interface DragCallbacks {
|
|
91
93
|
containment?: HTMLElement;
|
package/dist/vue.js
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
1
|
// src/vue.ts
|
|
2
2
|
import { defineComponent, ref, shallowRef, computed, onMounted, onBeforeUnmount, watch, h } from "vue";
|
|
3
3
|
|
|
4
|
+
// src/positioning.ts
|
|
5
|
+
function clampNote(note, naturalWidth, naturalHeight) {
|
|
6
|
+
const width = Math.min(note.width, naturalWidth);
|
|
7
|
+
const height = Math.min(note.height, naturalHeight);
|
|
8
|
+
const left = Math.max(0, Math.min(note.left, naturalWidth - width));
|
|
9
|
+
const top = Math.max(0, Math.min(note.top, naturalHeight - height));
|
|
10
|
+
return { top, left, width, height };
|
|
11
|
+
}
|
|
12
|
+
function clampNotes(notes, naturalWidth, naturalHeight) {
|
|
13
|
+
for (const note of notes) {
|
|
14
|
+
Object.assign(note, clampNote(note, naturalWidth, naturalHeight));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function computeNoteLeft(noteWidth, areaLeftInViewport, areaWidth, viewportWidth) {
|
|
18
|
+
let left = (areaWidth - noteWidth) / 2;
|
|
19
|
+
const noteLeftInViewport = areaLeftInViewport + left;
|
|
20
|
+
const noteRightInViewport = noteLeftInViewport + noteWidth;
|
|
21
|
+
if (noteRightInViewport > viewportWidth) {
|
|
22
|
+
left -= noteRightInViewport - viewportWidth;
|
|
23
|
+
}
|
|
24
|
+
const adjustedNoteLeft = areaLeftInViewport + left;
|
|
25
|
+
if (adjustedNoteLeft < 0) {
|
|
26
|
+
left -= adjustedNoteLeft;
|
|
27
|
+
}
|
|
28
|
+
return left;
|
|
29
|
+
}
|
|
30
|
+
|
|
4
31
|
// src/annotate-edit.ts
|
|
5
32
|
var DEFAULT_NOTE_TOP = 30;
|
|
6
33
|
var DEFAULT_NOTE_LEFT = 30;
|
|
@@ -50,6 +77,8 @@ var AnnotateEdit = class {
|
|
|
50
77
|
formEl.appendChild(this.textarea);
|
|
51
78
|
this.form.appendChild(formEl);
|
|
52
79
|
this.area.appendChild(this.form);
|
|
80
|
+
this.positionForm();
|
|
81
|
+
this.textarea.focus();
|
|
53
82
|
this.form.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
54
83
|
const area = this.area;
|
|
55
84
|
const applyRect = (rect) => {
|
|
@@ -61,7 +90,10 @@ var AnnotateEdit = class {
|
|
|
61
90
|
this.handlers.makeResizable(area, {
|
|
62
91
|
containment: image.canvas,
|
|
63
92
|
onResize: applyRect,
|
|
64
|
-
onStop:
|
|
93
|
+
onStop: (rect) => {
|
|
94
|
+
applyRect(rect);
|
|
95
|
+
this.positionForm();
|
|
96
|
+
}
|
|
65
97
|
});
|
|
66
98
|
this.handlers.makeDraggable(area, {
|
|
67
99
|
containment: image.canvas,
|
|
@@ -72,9 +104,9 @@ var AnnotateEdit = class {
|
|
|
72
104
|
onStop: (pos) => {
|
|
73
105
|
area.style.left = pos.left + "px";
|
|
74
106
|
area.style.top = pos.top + "px";
|
|
107
|
+
this.positionForm();
|
|
75
108
|
}
|
|
76
109
|
});
|
|
77
|
-
this.textarea.focus();
|
|
78
110
|
this.form.addEventListener("keydown", (e) => {
|
|
79
111
|
if (e.key === "Escape") {
|
|
80
112
|
const cancelBtn = this.form.querySelector(".image-annotate-edit-close");
|
|
@@ -90,6 +122,13 @@ var AnnotateEdit = class {
|
|
|
90
122
|
}
|
|
91
123
|
this.addCancelButton(buttonRow);
|
|
92
124
|
}
|
|
125
|
+
/** Recompute the form's horizontal position relative to the area. */
|
|
126
|
+
positionForm() {
|
|
127
|
+
const formRect = this.form.getBoundingClientRect();
|
|
128
|
+
const areaRect = this.area.getBoundingClientRect();
|
|
129
|
+
const left = computeNoteLeft(formRect.width, areaRect.left, areaRect.width, window.innerWidth);
|
|
130
|
+
this.form.style.left = left + "px";
|
|
131
|
+
}
|
|
93
132
|
/** Tear down the edit form and interaction handlers. */
|
|
94
133
|
destroy() {
|
|
95
134
|
this.image.activeEdit = null;
|
|
@@ -116,6 +155,7 @@ var AnnotateEdit = class {
|
|
|
116
155
|
} else {
|
|
117
156
|
this.note.editable = true;
|
|
118
157
|
const view = new AnnotateView(this.image, this.note);
|
|
158
|
+
this.note.view = view;
|
|
119
159
|
view.resetPosition(this, text);
|
|
120
160
|
this.image.notes.push(this.note);
|
|
121
161
|
}
|
|
@@ -273,7 +313,13 @@ var AnnotateView = class {
|
|
|
273
313
|
}
|
|
274
314
|
/** Show the tooltip and apply hover styling. */
|
|
275
315
|
show() {
|
|
316
|
+
this.tooltip.style.visibility = "hidden";
|
|
276
317
|
this.tooltip.style.display = "block";
|
|
318
|
+
const noteRect = this.tooltip.getBoundingClientRect();
|
|
319
|
+
const areaRect = this.area.getBoundingClientRect();
|
|
320
|
+
const left = computeNoteLeft(noteRect.width, areaRect.left, areaRect.width, window.innerWidth);
|
|
321
|
+
this.tooltip.style.left = left + "px";
|
|
322
|
+
this.tooltip.style.visibility = "";
|
|
277
323
|
if (!this.editable) {
|
|
278
324
|
this.area.classList.add("image-annotate-area-hover");
|
|
279
325
|
} else {
|
|
@@ -371,17 +417,19 @@ function destroyDraggable(el) {
|
|
|
371
417
|
var MIN_SIZE = 10;
|
|
372
418
|
var CORNERS = ["nw", "ne", "sw", "se"];
|
|
373
419
|
function computeResize(corner, startLeft, startTop, startWidth, startHeight, dx, dy) {
|
|
374
|
-
let left
|
|
420
|
+
let left, top, width, height;
|
|
375
421
|
if (corner === "nw" || corner === "sw") {
|
|
376
422
|
left = startLeft + dx;
|
|
377
423
|
width = startWidth - dx;
|
|
378
424
|
} else {
|
|
425
|
+
left = startLeft;
|
|
379
426
|
width = startWidth + dx;
|
|
380
427
|
}
|
|
381
428
|
if (corner === "nw" || corner === "ne") {
|
|
382
429
|
top = startTop + dy;
|
|
383
430
|
height = startHeight - dy;
|
|
384
431
|
} else {
|
|
432
|
+
top = startTop;
|
|
385
433
|
height = startHeight + dy;
|
|
386
434
|
}
|
|
387
435
|
if (width < MIN_SIZE) {
|
|
@@ -557,6 +605,9 @@ var AnnotateImage = class {
|
|
|
557
605
|
this.originalNextSibling = img.nextSibling;
|
|
558
606
|
this.canvas = document.createElement("div");
|
|
559
607
|
this.canvas.className = "image-annotate-canvas";
|
|
608
|
+
if (options.theme) {
|
|
609
|
+
this.canvas.dataset.theme = options.theme;
|
|
610
|
+
}
|
|
560
611
|
this.viewOverlay = document.createElement("div");
|
|
561
612
|
this.viewOverlay.className = "image-annotate-view";
|
|
562
613
|
this.editOverlay = document.createElement("div");
|
|
@@ -657,6 +708,7 @@ var AnnotateImage = class {
|
|
|
657
708
|
for (const note of this.notes) {
|
|
658
709
|
note.view?.destroy();
|
|
659
710
|
}
|
|
711
|
+
this.viewOverlay.replaceChildren();
|
|
660
712
|
}
|
|
661
713
|
createViews() {
|
|
662
714
|
for (const note of this.notes) {
|
|
@@ -666,6 +718,7 @@ var AnnotateImage = class {
|
|
|
666
718
|
/** Rebuild annotation views from the current notes array. */
|
|
667
719
|
load() {
|
|
668
720
|
this.destroyViews();
|
|
721
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
669
722
|
this.createViews();
|
|
670
723
|
this.notifyLoad();
|
|
671
724
|
}
|
|
@@ -774,6 +827,7 @@ var AnnotateImage = class {
|
|
|
774
827
|
this.api.load().then((notes) => {
|
|
775
828
|
this.destroyViews();
|
|
776
829
|
this.notes = notes;
|
|
830
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
777
831
|
this.createViews();
|
|
778
832
|
this.notifyLoad();
|
|
779
833
|
}).catch((err) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "annotate-image",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.4",
|
|
4
4
|
"description": "Create Flickr-like comment annotations on images — draw rectangles, add notes, save via AJAX or static data",
|
|
5
5
|
"license": "GPL-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -64,35 +64,35 @@
|
|
|
64
64
|
"demo:watch:react": "esbuild src/react.tsx --bundle --format=esm --external:react --external:react-dom --outfile=dist/react.js --watch",
|
|
65
65
|
"demo:watch:vue": "esbuild src/vue.ts --bundle --format=esm --external:vue --outfile=dist/vue.js --watch",
|
|
66
66
|
"demo:watch:css": "esbuild src/annotation.css --minify --outfile=dist/css/annotate.min.css --watch",
|
|
67
|
-
"demo:serve": "
|
|
67
|
+
"demo:serve": "browser-sync start --server --port 8080 --startPath demo/index.html --files 'dist/**/*' 'demo/**/*'",
|
|
68
68
|
"demo:ci": "npm run build && concurrently -k \"npm:demo:watch:*\" \"npm:demo:serve:ci\"",
|
|
69
|
-
"demo:serve:ci": "
|
|
69
|
+
"demo:serve:ci": "browser-sync start --server --port 8080 --no-open --files 'dist/**/*' 'demo/**/*'"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@eslint/js": "^9.39.
|
|
72
|
+
"@eslint/js": "^9.39.4",
|
|
73
73
|
"@playwright/test": "^1.58.2",
|
|
74
74
|
"@testing-library/dom": "^10.4.1",
|
|
75
75
|
"@testing-library/react": "^16.3.2",
|
|
76
|
-
"@types/jquery": "^3.5.
|
|
76
|
+
"@types/jquery": "^3.5.34",
|
|
77
77
|
"@types/react": "^18.3.28",
|
|
78
78
|
"@types/react-dom": "^18.3.7",
|
|
79
79
|
"@vue/test-utils": "^2.4.6",
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
80
|
+
"browser-sync": "^3.0.4",
|
|
81
|
+
"concurrently": "^10.0.3",
|
|
82
|
+
"esbuild": "^0.28.1",
|
|
83
|
+
"eslint": "^9.39.4",
|
|
83
84
|
"eslint-config-prettier": "^10.1.8",
|
|
84
85
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
85
86
|
"eslint-plugin-vue": "^10.8.0",
|
|
86
87
|
"jquery": "^3.7.1",
|
|
87
|
-
"jsdom": "^28.
|
|
88
|
-
"live-server": "^1.2.2",
|
|
88
|
+
"jsdom": "^28.1.0",
|
|
89
89
|
"prettier": "^3.8.1",
|
|
90
90
|
"react": "^18.3.1",
|
|
91
91
|
"react-dom": "^18.3.1",
|
|
92
92
|
"typescript": "^5.9.3",
|
|
93
|
-
"typescript-eslint": "^8.
|
|
94
|
-
"vitest": "^4.0
|
|
95
|
-
"vue": "^3.5.
|
|
93
|
+
"typescript-eslint": "^8.57.0",
|
|
94
|
+
"vitest": "^4.1.0",
|
|
95
|
+
"vue": "^3.5.30"
|
|
96
96
|
},
|
|
97
97
|
"peerDependencies": {
|
|
98
98
|
"jquery": ">=3.7.0",
|
package/readme.md
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
# Annotate Image
|
|
2
2
|
|
|
3
|
+
<p align="center">
|
|
4
|
+
<a href="https://pullpatchpush.com/annotate-image"><img src="assets/image-annotate-preview.jpg" alt="Annotate Image preview" width="387"></a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/annotate-image"><img src="https://img.shields.io/npm/v/annotate-image" alt="npm version"></a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/annotate-image"><img src="https://img.shields.io/npm/dm/annotate-image" alt="npm downloads"></a>
|
|
10
|
+
<a href="https://bundlephobia.com/package/annotate-image"><img src="https://img.shields.io/bundlephobia/minzip/annotate-image" alt="bundle size"></a>
|
|
11
|
+
<img src="https://img.shields.io/badge/TypeScript-strict-blue" alt="TypeScript strict">
|
|
12
|
+
<a href="https://www.npmjs.com/package/annotate-image"><img src="https://img.shields.io/npm/l/annotate-image" alt="license"></a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
3
15
|
A JavaScript image annotation plugin that creates Flickr-like comment annotations on images. Users can draw rectangular regions on images, add text notes, and persist annotations via callbacks or AJAX.
|
|
4
16
|
|
|
5
17
|
Works standalone (vanilla JS), or with jQuery, React, or Vue. Framework adapters are tree-shakeable — only the one you import gets bundled.
|
|
6
18
|
|
|
19
|
+
**[Documentation & Live Demo](https://pullpatchpush.com/annotate-image)**
|
|
20
|
+
|
|
7
21
|
## Installation
|
|
8
22
|
|
|
9
23
|
```sh
|
|
@@ -172,6 +186,7 @@ Creates an annotation layer on an image element. Returns an `AnnotateImage` inst
|
|
|
172
186
|
| `onLoad` | `(notes: NoteData[]) => void` | — | Called after notes are loaded |
|
|
173
187
|
| `onError` | `(ctx: AnnotateErrorContext) => void` | — | Called on API errors (defaults to `console.error`) |
|
|
174
188
|
| `autoResize` | `boolean` | `true` | Re-scale annotations when the container resizes |
|
|
189
|
+
| `theme` | `string` | — | CSS theme name; sets `data-theme` on the canvas for variable scoping |
|
|
175
190
|
|
|
176
191
|
#### `AnnotateApi`
|
|
177
192
|
|
|
@@ -295,6 +310,75 @@ annotate(document.getElementById('myImage'), {
|
|
|
295
310
|
});
|
|
296
311
|
```
|
|
297
312
|
|
|
313
|
+
## Theming
|
|
314
|
+
|
|
315
|
+
The plugin uses CSS custom properties for all visual styling. Override these variables in your CSS to create custom themes.
|
|
316
|
+
|
|
317
|
+
### Using the `theme` option
|
|
318
|
+
|
|
319
|
+
Set `theme` in options to add a `data-theme` attribute to the canvas element, enabling CSS scoping:
|
|
320
|
+
|
|
321
|
+
```js
|
|
322
|
+
annotate(document.getElementById('myImage'), {
|
|
323
|
+
theme: 'dark',
|
|
324
|
+
notes: [/* ... */],
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
```css
|
|
329
|
+
.image-annotate-canvas[data-theme="dark"] {
|
|
330
|
+
--image-annotate-area-border: rgba(255, 255, 255, 0.7);
|
|
331
|
+
--image-annotate-area-bg: rgba(100, 149, 237, 0.15);
|
|
332
|
+
--image-annotate-note-bg: #2a2a3e;
|
|
333
|
+
--image-annotate-note-text: #e0e0f0;
|
|
334
|
+
--image-annotate-note-radius: 4px;
|
|
335
|
+
--image-annotate-note-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
|
|
336
|
+
/* ... see demo for full example */
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Multiple instances on the same page can use different themes.
|
|
341
|
+
|
|
342
|
+
Button icons (save, delete, cancel) use CSS `mask-image`, so their color automatically follows `--image-annotate-button-text`. Dark themes just need to set the text color — no icon overrides required.
|
|
343
|
+
|
|
344
|
+
### Available CSS variables
|
|
345
|
+
|
|
346
|
+
Set on `.image-annotate-canvas`:
|
|
347
|
+
|
|
348
|
+
| Variable | Default | Description |
|
|
349
|
+
|---|---|---|
|
|
350
|
+
| `--image-annotate-canvas-border` | `none` | Border around the canvas |
|
|
351
|
+
| `--image-annotate-font-family` | `Verdana, sans-serif` | Font for notes and buttons |
|
|
352
|
+
| `--image-annotate-font-size` | `12px` | Font size for notes and buttons |
|
|
353
|
+
| `--image-annotate-area-border` | `#000` | Annotation rectangle border color |
|
|
354
|
+
| `--image-annotate-area-inner-border` | `#fff` | Annotation inner border color |
|
|
355
|
+
| `--image-annotate-area-bg` | `transparent` | Annotation rectangle fill |
|
|
356
|
+
| `--image-annotate-area-border-width` | `1px` | Annotation border thickness |
|
|
357
|
+
| `--image-annotate-area-border-style` | `solid` | Annotation border style |
|
|
358
|
+
| `--image-annotate-area-radius` | `0` | Annotation border-radius |
|
|
359
|
+
| `--image-annotate-hover-color` | `yellow` | Hover border color (read-only) |
|
|
360
|
+
| `--image-annotate-hover-editable-color` | `#00ad00` | Hover border color (editable) |
|
|
361
|
+
| `--image-annotate-hover-bg` | `transparent` | Hover background fill |
|
|
362
|
+
| `--image-annotate-note-bg` | `#e7ffe7` | Tooltip background |
|
|
363
|
+
| `--image-annotate-note-border` | `#397f39` | Tooltip border |
|
|
364
|
+
| `--image-annotate-note-text` | `#000` | Tooltip text color |
|
|
365
|
+
| `--image-annotate-note-radius` | `0` | Tooltip border-radius |
|
|
366
|
+
| `--image-annotate-note-shadow` | `none` | Tooltip box-shadow |
|
|
367
|
+
| `--image-annotate-note-max-width` | `300px` | Tooltip max width |
|
|
368
|
+
| `--image-annotate-edit-bg` | `#fffee3` | Edit form background |
|
|
369
|
+
| `--image-annotate-edit-border` | `#000` | Edit form border |
|
|
370
|
+
| `--image-annotate-edit-radius` | `0` | Edit form border-radius |
|
|
371
|
+
| `--image-annotate-edit-shadow` | `none` | Edit form box-shadow |
|
|
372
|
+
| `--image-annotate-edit-max-width` | `300px` | Edit form max width |
|
|
373
|
+
| `--image-annotate-button-bg` | `#fff` | Button background |
|
|
374
|
+
| `--image-annotate-button-bg-hover` | `#eee` | Button hover background |
|
|
375
|
+
| `--image-annotate-button-border` | `#ccc` | Button border |
|
|
376
|
+
| `--image-annotate-button-text` | `#000` | Button text and icon color |
|
|
377
|
+
| `--image-annotate-add-bg` | `rgba(0,0,0,0.4)` | Add Note button background |
|
|
378
|
+
| `--image-annotate-add-bg-hover` | `rgba(0,0,0,0.6)` | Add Note button hover background |
|
|
379
|
+
| `--image-annotate-add-border` | `1px solid rgba(255,255,255,0.5)` | Add Note button border |
|
|
380
|
+
| `--image-annotate-add-radius` | `4px` | Add Note button border-radius |
|
|
381
|
+
|
|
298
382
|
## Tree Shaking
|
|
299
383
|
|
|
300
384
|
Each entry point (`annotate-image`, `annotate-image/jquery`, `annotate-image/react`, `annotate-image/vue`) is a separate bundle. Importing one does not pull in the others. Unused framework adapters are excluded automatically by bundlers that support package `exports`.
|
|
@@ -326,13 +410,13 @@ The plugin supports keyboard navigation:
|
|
|
326
410
|
|
|
327
411
|
## Demos
|
|
328
412
|
|
|
329
|
-
|
|
413
|
+
Try the [live examples](https://pullpatchpush.com/annotate-image/examples) online, or run the demo server locally:
|
|
330
414
|
|
|
331
415
|
```sh
|
|
332
416
|
npm run demo
|
|
333
417
|
```
|
|
334
418
|
|
|
335
|
-
This opens a browser at `http://localhost:8080/demo/index.html` with links to demos including static annotations, AJAX loading, vanilla JS, React, Vue, and
|
|
419
|
+
This opens a browser at `http://localhost:8080/demo/index.html` with links to demos including static annotations, AJAX loading, vanilla JS, React, Vue, multiple instances, and CSS theming.
|
|
336
420
|
|
|
337
421
|
## Build
|
|
338
422
|
|