annotate-image 2.0.0-beta.2 → 2.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core.js +55 -3
- package/dist/core.min.js +1 -1
- package/dist/css/annotate.min.css +1 -1
- package/dist/jquery.js +55 -3
- package/dist/jquery.min.js +1 -1
- package/dist/react.js +55 -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 +55 -3
- package/package.json +12 -12
- 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;
|
|
@@ -270,7 +309,13 @@ var AnnotateView = class {
|
|
|
270
309
|
}
|
|
271
310
|
/** Show the tooltip and apply hover styling. */
|
|
272
311
|
show() {
|
|
312
|
+
this.tooltip.style.visibility = "hidden";
|
|
273
313
|
this.tooltip.style.display = "block";
|
|
314
|
+
const noteRect = this.tooltip.getBoundingClientRect();
|
|
315
|
+
const areaRect = this.area.getBoundingClientRect();
|
|
316
|
+
const left = computeNoteLeft(noteRect.width, areaRect.left, areaRect.width, window.innerWidth);
|
|
317
|
+
this.tooltip.style.left = left + "px";
|
|
318
|
+
this.tooltip.style.visibility = "";
|
|
274
319
|
if (!this.editable) {
|
|
275
320
|
this.area.classList.add("image-annotate-area-hover");
|
|
276
321
|
} else {
|
|
@@ -368,17 +413,19 @@ function destroyDraggable(el) {
|
|
|
368
413
|
var MIN_SIZE = 10;
|
|
369
414
|
var CORNERS = ["nw", "ne", "sw", "se"];
|
|
370
415
|
function computeResize(corner, startLeft, startTop, startWidth, startHeight, dx, dy) {
|
|
371
|
-
let left
|
|
416
|
+
let left, top, width, height;
|
|
372
417
|
if (corner === "nw" || corner === "sw") {
|
|
373
418
|
left = startLeft + dx;
|
|
374
419
|
width = startWidth - dx;
|
|
375
420
|
} else {
|
|
421
|
+
left = startLeft;
|
|
376
422
|
width = startWidth + dx;
|
|
377
423
|
}
|
|
378
424
|
if (corner === "nw" || corner === "ne") {
|
|
379
425
|
top = startTop + dy;
|
|
380
426
|
height = startHeight - dy;
|
|
381
427
|
} else {
|
|
428
|
+
top = startTop;
|
|
382
429
|
height = startHeight + dy;
|
|
383
430
|
}
|
|
384
431
|
if (width < MIN_SIZE) {
|
|
@@ -554,6 +601,9 @@ var AnnotateImage = class {
|
|
|
554
601
|
this.originalNextSibling = img.nextSibling;
|
|
555
602
|
this.canvas = document.createElement("div");
|
|
556
603
|
this.canvas.className = "image-annotate-canvas";
|
|
604
|
+
if (options.theme) {
|
|
605
|
+
this.canvas.dataset.theme = options.theme;
|
|
606
|
+
}
|
|
557
607
|
this.viewOverlay = document.createElement("div");
|
|
558
608
|
this.viewOverlay.className = "image-annotate-view";
|
|
559
609
|
this.editOverlay = document.createElement("div");
|
|
@@ -663,6 +713,7 @@ var AnnotateImage = class {
|
|
|
663
713
|
/** Rebuild annotation views from the current notes array. */
|
|
664
714
|
load() {
|
|
665
715
|
this.destroyViews();
|
|
716
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
666
717
|
this.createViews();
|
|
667
718
|
this.notifyLoad();
|
|
668
719
|
}
|
|
@@ -771,6 +822,7 @@ var AnnotateImage = class {
|
|
|
771
822
|
this.api.load().then((notes) => {
|
|
772
823
|
this.destroyViews();
|
|
773
824
|
this.notes = notes;
|
|
825
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
774
826
|
this.createViews();
|
|
775
827
|
this.notifyLoad();
|
|
776
828
|
}).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,r=t+o+e;r>i&&(o-=r-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 r=this.image.options.labels?.placeholder??"";r&&(this.textarea.placeholder=r),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 l=document.createElement("div");l.className="image-annotate-edit-buttons",this.form.appendChild(l),this.addSaveButton(l,i),i&&this.addDeleteButton(l,i),this.addCancelButton(l)}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=()=>{this.image.setMode("view"),n?n.resetPosition(this,o):(this.note.editable=!0,new D(this.image,this.note).resetPosition(this,o),this.image.notes.push(this.note)),this.image.notifySave(C(this.note)),this.destroy(),this.image.flushPendingRescale()},r=z(this.area),s=k(this.area),d=this.image.toNatural({top:r.top,left:r.left,width:s.width,height:s.height});this.note.top=d.top,this.note.left=d.left,this.note.width=d.width,this.note.height=d.height,this.note.text=o,this.image.api.save?(this.busy=!0,this.image.api.save(C(this.note)).then(l=>{l.annotation_id!=null&&(this.note.id=l.annotation_id),h()}).catch(l=>{this.busy=!1;let a=l instanceof Error?l:new Error(String(l));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 r=h instanceof Error?h:new Error(String(h));this.image.reportError({type:"delete",error:r,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,r=parseFloat(e.style.left)||0,s=parseFloat(e.style.top)||0,d=parseFloat(e.style.width)||e.offsetWidth,l=parseFloat(e.style.height)||e.offsetHeight,a=-1/0,y=-1/0,N=1/0,E=1/0;if(t.containment){let p=t.containment.getBoundingClientRect(),v=e.getBoundingClientRect(),g=r-(v.left-p.left),c=s-(v.top-p.top);a=g,y=c,N=p.width-d+g,E=p.height-l+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(r+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(r+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,r){let s,d,l,a;return e==="nw"||e==="sw"?(s=t+h,l=i-h):(s=t,l=i+h),e==="nw"||e==="ne"?(d=n+r,a=o-r):(d=n,a=o+r),l<b&&((e==="nw"||e==="sw")&&(s=t+i-b),l=b),a<b&&((e==="nw"||e==="ne")&&(d=n+o-b),a=b),{left:s,top:d,width:l,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(r){if(r.button!==0)return;r.preventDefault(),r.stopPropagation(),o.setPointerCapture&&o.setPointerCapture(r.pointerId);let s=r.clientX,d=r.clientY,l=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=l-(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,l,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,l,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 r=document.createElement("div");if(r.className="image-annotate-edit-area",this.editOverlay.appendChild(r),!t.parentNode)throw new Error("image-annotate: image must be in the DOM before initialization");t.parentNode.insertBefore(this.canvas,t),this.canvas.appendChild(t),this.canvas.appendChild(this.viewOverlay),this.canvas.appendChild(this.editOverlay),this.api=this.options.api?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:l,height:a}=d.contentRect;l===0||a===0||this.rescale(l,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 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;
|
|
@@ -270,7 +309,13 @@ var AnnotateView = class {
|
|
|
270
309
|
}
|
|
271
310
|
/** Show the tooltip and apply hover styling. */
|
|
272
311
|
show() {
|
|
312
|
+
this.tooltip.style.visibility = "hidden";
|
|
273
313
|
this.tooltip.style.display = "block";
|
|
314
|
+
const noteRect = this.tooltip.getBoundingClientRect();
|
|
315
|
+
const areaRect = this.area.getBoundingClientRect();
|
|
316
|
+
const left = computeNoteLeft(noteRect.width, areaRect.left, areaRect.width, window.innerWidth);
|
|
317
|
+
this.tooltip.style.left = left + "px";
|
|
318
|
+
this.tooltip.style.visibility = "";
|
|
274
319
|
if (!this.editable) {
|
|
275
320
|
this.area.classList.add("image-annotate-area-hover");
|
|
276
321
|
} else {
|
|
@@ -368,17 +413,19 @@ function destroyDraggable(el) {
|
|
|
368
413
|
var MIN_SIZE = 10;
|
|
369
414
|
var CORNERS = ["nw", "ne", "sw", "se"];
|
|
370
415
|
function computeResize(corner, startLeft, startTop, startWidth, startHeight, dx, dy) {
|
|
371
|
-
let left
|
|
416
|
+
let left, top, width, height;
|
|
372
417
|
if (corner === "nw" || corner === "sw") {
|
|
373
418
|
left = startLeft + dx;
|
|
374
419
|
width = startWidth - dx;
|
|
375
420
|
} else {
|
|
421
|
+
left = startLeft;
|
|
376
422
|
width = startWidth + dx;
|
|
377
423
|
}
|
|
378
424
|
if (corner === "nw" || corner === "ne") {
|
|
379
425
|
top = startTop + dy;
|
|
380
426
|
height = startHeight - dy;
|
|
381
427
|
} else {
|
|
428
|
+
top = startTop;
|
|
382
429
|
height = startHeight + dy;
|
|
383
430
|
}
|
|
384
431
|
if (width < MIN_SIZE) {
|
|
@@ -554,6 +601,9 @@ var AnnotateImage = class {
|
|
|
554
601
|
this.originalNextSibling = img.nextSibling;
|
|
555
602
|
this.canvas = document.createElement("div");
|
|
556
603
|
this.canvas.className = "image-annotate-canvas";
|
|
604
|
+
if (options.theme) {
|
|
605
|
+
this.canvas.dataset.theme = options.theme;
|
|
606
|
+
}
|
|
557
607
|
this.viewOverlay = document.createElement("div");
|
|
558
608
|
this.viewOverlay.className = "image-annotate-view";
|
|
559
609
|
this.editOverlay = document.createElement("div");
|
|
@@ -663,6 +713,7 @@ var AnnotateImage = class {
|
|
|
663
713
|
/** Rebuild annotation views from the current notes array. */
|
|
664
714
|
load() {
|
|
665
715
|
this.destroyViews();
|
|
716
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
666
717
|
this.createViews();
|
|
667
718
|
this.notifyLoad();
|
|
668
719
|
}
|
|
@@ -771,6 +822,7 @@ var AnnotateImage = class {
|
|
|
771
822
|
this.api.load().then((notes) => {
|
|
772
823
|
this.destroyViews();
|
|
773
824
|
this.notes = notes;
|
|
825
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
774
826
|
this.createViews();
|
|
775
827
|
this.notifyLoad();
|
|
776
828
|
}).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),l=Math.max(0,Math.min(e.left,t-n));return{top:Math.max(0,Math.min(e.top,i-o)),left:l,width:n,height:o}}function I(e,t,i){for(let n of e)Object.assign(n,X(n,t,i))}function T(e,t,i,n){let o=(i-e)/2,r=t+o+e;r>n&&(o-=r-n);let s=t+o;return s<0&&(o-=s),o}var Y=30,_=30,W=30,U=30,R=class{constructor(t,i,n){this.busy=!1;this.image=t,this.handlers=t.handlers,i?this.note=i:this.note={id:"new",top:Y,left:_,width:W,height:U,text:"",editable:!0},this.area=t.editOverlay.querySelector(".image-annotate-edit-area");let o=t.toRendered(this.note);this.area.style.height=o.height+"px",this.area.style.width=o.width+"px",this.area.style.left=o.left+"px",this.area.style.top=o.top+"px",this.form=document.createElement("div"),this.form.className="image-annotate-edit-form";let l=document.createElement("form");this.textarea=document.createElement("textarea"),this.textarea.name="text",this.textarea.rows=3,this.textarea.cols=30,this.textarea.value=this.note.text;let r=this.image.options.labels?.placeholder??"";r&&(this.textarea.placeholder=r),l.appendChild(this.textarea),this.form.appendChild(l),this.area.appendChild(this.form),this.positionForm(),this.textarea.focus(),this.form.addEventListener("pointerdown",a=>a.stopPropagation());let s=this.area,d=a=>{s.style.left=a.left+"px",s.style.top=a.top+"px",s.style.width=a.width+"px",s.style.height=a.height+"px"};this.handlers.makeResizable(s,{containment:t.canvas,onResize:d,onStop:a=>{d(a),this.positionForm()}}),this.handlers.makeDraggable(s,{containment:t.canvas,onDrag:a=>{s.style.left=a.left+"px",s.style.top=a.top+"px"},onStop:a=>{s.style.left=a.left+"px",s.style.top=a.top+"px",this.positionForm()}}),this.form.addEventListener("keydown",a=>{if(a.key==="Escape"){let y=this.form.querySelector(".image-annotate-edit-close");y&&y.click()}});let h=document.createElement("div");h.className="image-annotate-edit-buttons",this.form.appendChild(h),this.addSaveButton(h,n),n&&this.addDeleteButton(h,n),this.addCancelButton(h)}positionForm(){let t=this.form.getBoundingClientRect(),i=this.area.getBoundingClientRect(),n=T(t.width,i.left,i.width,window.innerWidth);this.form.style.left=n+"px"}destroy(){this.image.activeEdit=null,this.handlers.destroyResizable(this.area),this.handlers.destroyDraggable(this.area),this.area.style.height="",this.area.style.width="",this.area.style.left="",this.area.style.top="",this.form.remove()}addSaveButton(t,i){let n=document.createElement("button");n.className="image-annotate-edit-ok",n.textContent=this.image.options.labels?.save??"OK",n.type="button",n.addEventListener("click",()=>{if(this.busy)return;let o=this.textarea.value,l=()=>{this.image.setMode("view"),i?i.resetPosition(this,o):(this.note.editable=!0,new D(this.image,this.note).resetPosition(this,o),this.image.notes.push(this.note)),this.image.notifySave(C(this.note)),this.destroy(),this.image.flushPendingRescale()},r=S(this.area),s=z(this.area),d=this.image.toNatural({top:r.top,left:r.left,width:s.width,height:s.height});this.note.top=d.top,this.note.left=d.left,this.note.width=d.width,this.note.height=d.height,this.note.text=o,this.image.api.save?(this.busy=!0,this.image.api.save(C(this.note)).then(h=>{h.annotation_id!=null&&(this.note.id=h.annotation_id),l()}).catch(h=>{this.busy=!1;let a=h instanceof Error?h:new Error(String(h));this.image.reportError({type:"save",error:a,note:this.note})})):l()}),t.appendChild(n)}addDeleteButton(t,i){let n=document.createElement("button");n.className="image-annotate-edit-delete",n.textContent=this.image.options.labels?.delete??"Delete",n.type="button",n.addEventListener("click",()=>{if(this.busy)return;let o=()=>{this.image.setMode("view"),this.destroy(),i.destroy();let l=this.image.notes.indexOf(this.note);l!==-1&&this.image.notes.splice(l,1),this.image.notifyDelete(C(this.note)),this.image.flushPendingRescale()};this.image.api.delete?(this.busy=!0,this.image.api.delete(C(this.note)).then(()=>{o()}).catch(l=>{this.busy=!1;let r=l instanceof Error?l:new Error(String(l));this.image.reportError({type:"delete",error:r,note:this.note})})):o()}),t.appendChild(n)}addCancelButton(t){let i=document.createElement("button");i.className="image-annotate-edit-close",i.textContent=this.image.options.labels?.cancel??"Cancel",i.type="button",i.addEventListener("click",()=>{this.image.cancelEdit()}),t.appendChild(i)}};function S(e){return{left:parseInt(e.style.left)||0,top:parseInt(e.style.top)||0}}function z(e){return{width:parseInt(e.style.width)||e.offsetWidth,height:parseInt(e.style.height)||e.offsetHeight}}var D=class{constructor(t,i){this.image=t,this.note=i,this.editable=!!(i.editable&&t.options.editable),this.area=document.createElement("div"),this.area.className="image-annotate-area"+(this.editable?" image-annotate-area-editable":"");let n=document.createElement("div");this.area.appendChild(n),t.viewOverlay.insertBefore(this.area,t.viewOverlay.firstChild),this.tooltip=document.createElement("div"),this.tooltip.className="image-annotate-note",this.tooltip.textContent=i.text,this.tooltip.style.display="none",this.area.appendChild(this.tooltip),this.setPosition(),this.area.addEventListener("mouseenter",()=>this.show()),this.area.addEventListener("mouseleave",()=>this.hide()),this.editable&&(this.area.setAttribute("tabindex","0"),this.area.setAttribute("role","button"),this.area.addEventListener("click",()=>this.edit()),this.area.addEventListener("keydown",o=>{(o.key==="Enter"||o.key===" ")&&(o.preventDefault(),this.edit())}))}setPosition(){let t=this.image.toRendered(this.note),i=this.area.firstElementChild;i.style.height=t.height+"px",i.style.width=t.width+"px",this.area.style.left=t.left+"px",this.area.style.top=t.top+"px"}resetPosition(t,i){this.tooltip.textContent=i,this.tooltip.style.display="none";let n=this.image.toRendered(t.note),o=this.area.firstElementChild;o.style.height=n.height+"px",o.style.width=n.width+"px",this.area.style.left=n.left+"px",this.area.style.top=n.top+"px",this.note.top=t.note.top,this.note.left=t.note.left,this.note.height=t.note.height,this.note.width=t.note.width,this.note.text=i,this.note.id=t.note.id,this.editable=!0}show(){this.tooltip.style.visibility="hidden",this.tooltip.style.display="block";let t=this.tooltip.getBoundingClientRect(),i=this.area.getBoundingClientRect(),n=T(t.width,i.left,i.width,window.innerWidth);this.tooltip.style.left=n+"px",this.tooltip.style.visibility="",this.editable?this.area.classList.add("image-annotate-area-editable-hover"):this.area.classList.add("image-annotate-area-hover")}hide(){this.tooltip.style.display="none",this.area.classList.remove("image-annotate-area-hover"),this.area.classList.remove("image-annotate-area-editable-hover")}destroy(){this.area.remove(),this.tooltip.remove()}edit(){this.image.mode==="view"&&(this.image.setMode("edit"),this.image.activeEdit=new R(this.image,this.note,this))}};var P=new WeakMap,M=new WeakMap;function j(e,t){B(e);function i(n){if(n.button!==0)return;n.preventDefault(),e.setPointerCapture&&e.setPointerCapture(n.pointerId);let o=n.clientX,l=n.clientY,r=parseFloat(e.style.left)||0,s=parseFloat(e.style.top)||0,d=parseFloat(e.style.width)||e.offsetWidth,h=parseFloat(e.style.height)||e.offsetHeight,a=-1/0,y=-1/0,N=1/0,E=1/0;if(t.containment){let p=t.containment.getBoundingClientRect(),v=e.getBoundingClientRect(),g=r-(v.left-p.left),c=s-(v.top-p.top);a=g,y=c,N=p.width-d+g,E=p.height-h+c}function w(p,v,g){return Math.max(v,Math.min(g,p))}function L(p){let v=p.clientX-o,g=p.clientY-l,c=w(r+v,a,N),m=w(s+g,y,E);t.onDrag&&t.onDrag({left:c,top:m})}function x(p){e.releasePointerCapture&&e.releasePointerCapture(p.pointerId),e.removeEventListener("pointermove",L),e.removeEventListener("pointerup",x);let v=p.clientX-o,g=p.clientY-l,c=w(r+v,a,N),m=w(s+g,y,E);t.onStop&&t.onStop({left:c,top:m})}e.addEventListener("pointermove",L),e.addEventListener("pointerup",x)}e.addEventListener("pointerdown",i),P.set(e,()=>{e.removeEventListener("pointerdown",i)})}function B(e){let t=P.get(e);t&&(t(),P.delete(e))}var b=10,q=["nw","ne","sw","se"];function k(e,t,i,n,o,l,r){let s,d,h,a;return e==="nw"||e==="sw"?(s=t+l,h=n-l):(s=t,h=n+l),e==="nw"||e==="ne"?(d=i+r,a=o-r):(d=i,a=o+r),h<b&&((e==="nw"||e==="sw")&&(s=t+n-b),h=b),a<b&&((e==="nw"||e==="ne")&&(d=i+o-b),a=b),{left:s,top:d,width:h,height:a}}function K(e,t){F(e);let i=[];for(let n of q){let o=document.createElement("div");o.className=`image-annotate-resize-handle image-annotate-resize-handle-${n}`,e.appendChild(o),i.push(o),o.addEventListener("pointerdown",function(r){if(r.button!==0)return;r.preventDefault(),r.stopPropagation(),o.setPointerCapture&&o.setPointerCapture(r.pointerId);let s=r.clientX,d=r.clientY,h=parseFloat(e.style.left)||0,a=parseFloat(e.style.top)||0,y=parseFloat(e.style.width)||0,N=parseFloat(e.style.height)||0,E=1/0,w=1/0,L=-1/0,x=-1/0;if(t.containment){let c=t.containment.getBoundingClientRect(),m=e.getBoundingClientRect(),u=h-(m.left-c.left),f=a-(m.top-c.top);L=u,x=f,E=c.width+u,w=c.height+f}function p(c){let{left:m,top:u,width:f,height:A}=c;return m<L&&(f-=L-m,m=L),u<x&&(A-=x-u,u=x),m+f>E&&(f=E-m),u+A>w&&(A=w-u),f<b&&(f=b),A<b&&(A=b),{left:m,top:u,width:f,height:A}}function v(c){let m=c.clientX-s,u=c.clientY-d,f=p(k(n,h,a,y,N,m,u));t.onResize?.(f)}function g(c){o.releasePointerCapture&&o.releasePointerCapture(c.pointerId),o.removeEventListener("pointermove",v),o.removeEventListener("pointerup",g);let m=c.clientX-s,u=c.clientY-d,f=p(k(n,h,a,y,N,m,u));t.onStop&&t.onStop(f)}o.addEventListener("pointermove",v),o.addEventListener("pointerup",g)})}M.set(e,()=>{for(let n of i)n.remove()})}function F(e){let t=M.get(e);t&&(t(),M.delete(e))}function V(){return{makeDraggable:j,makeResizable:K,destroyDraggable:B,destroyResizable:F}}function C(e){let{view:t,editable:i,...n}=e;return n}function J(e){return{load:typeof e.load=="string"?G(e.load):e.load,save:typeof e.save=="string"?Q(e.save):e.save,delete:typeof e.delete=="string"?Z(e.delete):e.delete}}function G(e){return()=>fetch(e).then(t=>{if(!t.ok)throw new Error(`Load failed (HTTP ${t.status})`);return t.json()})}function Q(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(i=>{if(!i.ok)throw new Error(`Save failed (HTTP ${i.status})`);return i.json()})}function Z(e){return t=>fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)}).then(i=>{if(!i.ok)throw new Error(`Delete failed (HTTP ${i.status})`)})}var H=class{constructor(t,i){this._mode="view";this.activeEdit=null;this.destroyed=!1;this.pendingRescale=!1;this.originalParent=null;this.originalNextSibling=null;this.options=i,this.handlers=V(),this.img=t,this.naturalWidth=t.naturalWidth||t.width,this.naturalHeight=t.naturalHeight||t.height;let n=t.getBoundingClientRect(),o=n.width||t.width,l=n.height||t.height;if(this.naturalWidth===0||this.naturalHeight===0)throw new Error("image-annotate: image must have non-zero dimensions (is the image loaded?)");this.scaleX=o/this.naturalWidth,this.scaleY=l/this.naturalHeight,this.notes=i.notes.map(s=>({...s})),this.originalParent=t.parentNode,this.originalNextSibling=t.nextSibling,this.canvas=document.createElement("div"),this.canvas.className="image-annotate-canvas",i.theme&&(this.canvas.dataset.theme=i.theme),this.viewOverlay=document.createElement("div"),this.viewOverlay.className="image-annotate-view",this.editOverlay=document.createElement("div"),this.editOverlay.className="image-annotate-edit",this.editOverlay.style.display="none";let r=document.createElement("div");if(r.className="image-annotate-edit-area",this.editOverlay.appendChild(r),!t.parentNode)throw new Error("image-annotate: image must be in the DOM before initialization");t.parentNode.insertBefore(this.canvas,t),this.canvas.appendChild(t),this.canvas.appendChild(this.viewOverlay),this.canvas.appendChild(this.editOverlay),this.api=this.options.api?J(this.options.api):{},this.api.load?this.loadFromApi():this.load(),this.options.editable&&this.createButton(),i.autoResize!==!1&&typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(s=>{let d=s[0];if(!d)return;let{width:h,height:a}=d.contentRect;h===0||a===0||this.rescale(h,a)}),this.resizeObserver.observe(this.canvas))}toRendered(t){return{top:t.top*this.scaleY,left:t.left*this.scaleX,width:t.width*this.scaleX,height:t.height*this.scaleY}}toNatural(t){let i={top:t.top/this.scaleY,left:t.left/this.scaleX,width:t.width/this.scaleX,height:t.height/this.scaleY};if(!isFinite(i.top)||!isFinite(i.left)||!isFinite(i.width)||!isFinite(i.height))throw new Error("image-annotate: scale conversion produced non-finite coordinates");return i}get mode(){return this._mode}setMode(t){this._mode=t,t==="edit"?(this.canvas.classList.add("image-annotate-editing"),this.editOverlay.style.display="block"):(this.canvas.classList.remove("image-annotate-editing"),this.editOverlay.style.display="none")}getNotes(){return this.notes.map(C)}notifyChange(){this.options.onChange?.(this.getNotes())}notifySave(t){this.options.onSave?.(t),this.notifyChange()}notifyDelete(t){this.options.onDelete?.(t),this.notifyChange()}notifyLoad(){this.options.onLoad?.(this.getNotes()),this.notifyChange()}destroyViews(){this.cancelEdit();for(let t of this.notes)t.view?.destroy()}createViews(){for(let t of this.notes)t.view=new D(this,t)}load(){this.destroyViews(),I(this.notes,this.naturalWidth,this.naturalHeight),this.createViews(),this.notifyLoad()}clear(){this.destroyViews(),this.notes=[],this.notifyChange()}destroy(){if(!this.destroyed){if(this.destroyed=!0,this.destroyViews(),this.notes=[],this.button&&this.button.remove(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=void 0),this.originalParent&&this.originalParent.isConnected){let t=this.originalNextSibling?.parentNode===this.originalParent?this.originalNextSibling:null;this.originalParent.insertBefore(this.img,t)}this.canvas.remove()}}cancelEdit(){this.activeEdit&&(this.activeEdit.destroy(),this.setMode("view")),this.flushPendingRescale()}rescale(t,i){if(this.mode==="edit"){this.pendingRescale=!0;return}this.applyRescale(t,i)}applyRescale(t,i){let n=t/this.naturalWidth,o=i/this.naturalHeight;n===this.scaleX&&o===this.scaleY||(this.scaleX=n,this.scaleY=o,this.destroyViews(),this.createViews())}flushPendingRescale(){if(!this.pendingRescale)return;this.pendingRescale=!1;let t=this.canvas.getBoundingClientRect();t.width>0&&t.height>0&&this.applyRescale(t.width,t.height)}setNotes(t){this.destroyed||(this.destroyViews(),this.notes=t.map(i=>({...i})),this.createViews())}setEditable(t){this.destroyed||this.options.editable!==t&&(this.options.editable=t,t&&!this.button?this.createButton():!t&&this.button&&(this.button.remove(),this.button=void 0),this.destroyViews(),this.createViews())}createButton(){this.button=document.createElement("button"),this.button.className="image-annotate-add",this.button.title=this.options.labels?.addNote??"Add Note",this.button.type="button",this.button.addEventListener("click",()=>{this.add()}),this.canvas.appendChild(this.button)}reportError(t){this.options.onError?this.options.onError(t):console.error(`image-annotate: ${t.type} failed`,t.error)}loadFromApi(){this.api.load&&this.api.load().then(t=>{this.destroyViews(),this.notes=t,I(this.notes,this.naturalWidth,this.naturalHeight),this.createViews(),this.notifyLoad()}).catch(t=>{let i=t instanceof Error?t:new Error(String(t));this.reportError({type:"load",error:i})})}add(){return this.mode==="view"?(this.setMode("edit"),this.activeEdit=new R(this),!0):!1}};var O={addNote:"Add Note",save:"OK",delete:"Delete",cancel:"Cancel",placeholder:""};var tt={editable:!0,notes:[],labels:{...O}};$.fn.annotateImage=function(e){if(e==="destroy"){let l=this.data("annotateImage");return l&&(l.destroy(),this.removeData("annotateImage")),this}let t=e,i={...tt,...t,labels:{...O,...t?.labels}},n=this[0],o=new H(n,i);return this.data("annotateImage",o),this};})();
|
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;
|
|
@@ -279,7 +318,13 @@ var AnnotateView = class {
|
|
|
279
318
|
}
|
|
280
319
|
/** Show the tooltip and apply hover styling. */
|
|
281
320
|
show() {
|
|
321
|
+
this.tooltip.style.visibility = "hidden";
|
|
282
322
|
this.tooltip.style.display = "block";
|
|
323
|
+
const noteRect = this.tooltip.getBoundingClientRect();
|
|
324
|
+
const areaRect = this.area.getBoundingClientRect();
|
|
325
|
+
const left = computeNoteLeft(noteRect.width, areaRect.left, areaRect.width, window.innerWidth);
|
|
326
|
+
this.tooltip.style.left = left + "px";
|
|
327
|
+
this.tooltip.style.visibility = "";
|
|
283
328
|
if (!this.editable) {
|
|
284
329
|
this.area.classList.add("image-annotate-area-hover");
|
|
285
330
|
} else {
|
|
@@ -377,17 +422,19 @@ function destroyDraggable(el) {
|
|
|
377
422
|
var MIN_SIZE = 10;
|
|
378
423
|
var CORNERS = ["nw", "ne", "sw", "se"];
|
|
379
424
|
function computeResize(corner, startLeft, startTop, startWidth, startHeight, dx, dy) {
|
|
380
|
-
let left
|
|
425
|
+
let left, top, width, height;
|
|
381
426
|
if (corner === "nw" || corner === "sw") {
|
|
382
427
|
left = startLeft + dx;
|
|
383
428
|
width = startWidth - dx;
|
|
384
429
|
} else {
|
|
430
|
+
left = startLeft;
|
|
385
431
|
width = startWidth + dx;
|
|
386
432
|
}
|
|
387
433
|
if (corner === "nw" || corner === "ne") {
|
|
388
434
|
top = startTop + dy;
|
|
389
435
|
height = startHeight - dy;
|
|
390
436
|
} else {
|
|
437
|
+
top = startTop;
|
|
391
438
|
height = startHeight + dy;
|
|
392
439
|
}
|
|
393
440
|
if (width < MIN_SIZE) {
|
|
@@ -563,6 +610,9 @@ var AnnotateImage = class {
|
|
|
563
610
|
this.originalNextSibling = img.nextSibling;
|
|
564
611
|
this.canvas = document.createElement("div");
|
|
565
612
|
this.canvas.className = "image-annotate-canvas";
|
|
613
|
+
if (options.theme) {
|
|
614
|
+
this.canvas.dataset.theme = options.theme;
|
|
615
|
+
}
|
|
566
616
|
this.viewOverlay = document.createElement("div");
|
|
567
617
|
this.viewOverlay.className = "image-annotate-view";
|
|
568
618
|
this.editOverlay = document.createElement("div");
|
|
@@ -672,6 +722,7 @@ var AnnotateImage = class {
|
|
|
672
722
|
/** Rebuild annotation views from the current notes array. */
|
|
673
723
|
load() {
|
|
674
724
|
this.destroyViews();
|
|
725
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
675
726
|
this.createViews();
|
|
676
727
|
this.notifyLoad();
|
|
677
728
|
}
|
|
@@ -780,6 +831,7 @@ var AnnotateImage = class {
|
|
|
780
831
|
this.api.load().then((notes) => {
|
|
781
832
|
this.destroyViews();
|
|
782
833
|
this.notes = notes;
|
|
834
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
783
835
|
this.createViews();
|
|
784
836
|
this.notifyLoad();
|
|
785
837
|
}).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;
|
|
@@ -273,7 +312,13 @@ var AnnotateView = class {
|
|
|
273
312
|
}
|
|
274
313
|
/** Show the tooltip and apply hover styling. */
|
|
275
314
|
show() {
|
|
315
|
+
this.tooltip.style.visibility = "hidden";
|
|
276
316
|
this.tooltip.style.display = "block";
|
|
317
|
+
const noteRect = this.tooltip.getBoundingClientRect();
|
|
318
|
+
const areaRect = this.area.getBoundingClientRect();
|
|
319
|
+
const left = computeNoteLeft(noteRect.width, areaRect.left, areaRect.width, window.innerWidth);
|
|
320
|
+
this.tooltip.style.left = left + "px";
|
|
321
|
+
this.tooltip.style.visibility = "";
|
|
277
322
|
if (!this.editable) {
|
|
278
323
|
this.area.classList.add("image-annotate-area-hover");
|
|
279
324
|
} else {
|
|
@@ -371,17 +416,19 @@ function destroyDraggable(el) {
|
|
|
371
416
|
var MIN_SIZE = 10;
|
|
372
417
|
var CORNERS = ["nw", "ne", "sw", "se"];
|
|
373
418
|
function computeResize(corner, startLeft, startTop, startWidth, startHeight, dx, dy) {
|
|
374
|
-
let left
|
|
419
|
+
let left, top, width, height;
|
|
375
420
|
if (corner === "nw" || corner === "sw") {
|
|
376
421
|
left = startLeft + dx;
|
|
377
422
|
width = startWidth - dx;
|
|
378
423
|
} else {
|
|
424
|
+
left = startLeft;
|
|
379
425
|
width = startWidth + dx;
|
|
380
426
|
}
|
|
381
427
|
if (corner === "nw" || corner === "ne") {
|
|
382
428
|
top = startTop + dy;
|
|
383
429
|
height = startHeight - dy;
|
|
384
430
|
} else {
|
|
431
|
+
top = startTop;
|
|
385
432
|
height = startHeight + dy;
|
|
386
433
|
}
|
|
387
434
|
if (width < MIN_SIZE) {
|
|
@@ -557,6 +604,9 @@ var AnnotateImage = class {
|
|
|
557
604
|
this.originalNextSibling = img.nextSibling;
|
|
558
605
|
this.canvas = document.createElement("div");
|
|
559
606
|
this.canvas.className = "image-annotate-canvas";
|
|
607
|
+
if (options.theme) {
|
|
608
|
+
this.canvas.dataset.theme = options.theme;
|
|
609
|
+
}
|
|
560
610
|
this.viewOverlay = document.createElement("div");
|
|
561
611
|
this.viewOverlay.className = "image-annotate-view";
|
|
562
612
|
this.editOverlay = document.createElement("div");
|
|
@@ -666,6 +716,7 @@ var AnnotateImage = class {
|
|
|
666
716
|
/** Rebuild annotation views from the current notes array. */
|
|
667
717
|
load() {
|
|
668
718
|
this.destroyViews();
|
|
719
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
669
720
|
this.createViews();
|
|
670
721
|
this.notifyLoad();
|
|
671
722
|
}
|
|
@@ -774,6 +825,7 @@ var AnnotateImage = class {
|
|
|
774
825
|
this.api.load().then((notes) => {
|
|
775
826
|
this.destroyViews();
|
|
776
827
|
this.notes = notes;
|
|
828
|
+
clampNotes(this.notes, this.naturalWidth, this.naturalHeight);
|
|
777
829
|
this.createViews();
|
|
778
830
|
this.notifyLoad();
|
|
779
831
|
}).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.3",
|
|
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
|
+
"browser-sync": "^3.0.4",
|
|
80
81
|
"concurrently": "^9.2.1",
|
|
81
|
-
"esbuild": "^0.
|
|
82
|
-
"eslint": "^9.39.
|
|
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
|
|