lost-sia 3.1.2 → 3.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- import{jsxs as $,jsx as d}from"react/jsx-runtime";import{useState as P,useRef as k,useEffect as x}from"react";import U from"../atoms/Node.js";import i from"../../../models/AnnotationMode.js";import q from"../atoms/Edge.js";import I from"../../../utils/mouse.js";const K=({annotationSettings:p,coordinates:n,isSelected:C,annotationMode:r,pageToStageOffset:l,svgScale:u,svgTranslation:f,style:R,onAddNode:v,onDeleteNode:L,onFinishAnnoCreate:X,onMoving:E,onMoved:w,onIsDraggingStateChanged:y})=>{const[c,h]=P(!1),[A,g]=P(!1),D=k(A);x(()=>{D.current=A},[A]);const M=t=>{if(p.canEdit!==!1&&(C&&r!==i.CREATE&&t.button===0&&h(!0),t.button===2&&r==i.CREATE)){const o=I.getAntiScaledMouseStagePosition(t,l,u,f),e=[...n];e.push(o),v(e)}},N=t=>{if(c){const o=n.map(e=>{const s=e.x+=t.movementX/u,m=e.y+=t.movementY/u;return{x:s,y:m}});(t.movementX!==0||t.movementY!==0)&&(g(!0),E(o))}if(r===i.CREATE){const o=I.getAntiScaledMouseStagePosition(t,l,u,f);let e=[...n];n.length>1&&(e=n.slice(0,-1)),e.push(o),E(e)}};x(()=>{if(y(c),!c)return;const t=()=>{h(!1),D.current&&w(),g(!1)};return globalThis.addEventListener("mouseup",t),()=>{globalThis.removeEventListener("mouseup",t)}},[c]);const Y=()=>d("circle",{cx:n[0].x,cy:n[0].y,r:"100%",style:{opacity:0},onMouseDown:M,onMouseMove:N,onContextMenu:t=>t.preventDefault()}),j=()=>n.map((o,e)=>d(U,{index:e,annotationSettings:p,coordinates:o,pageToStageOffset:l,svgScale:u,svgTranslation:f,style:R,onDeleteNode:()=>{const s=[...n];s.splice(e,1),L(s)},onMoving:(s,m)=>{const b=[...n];b[s]=m,E(b)},onMoved:()=>w(),onIsDraggingStateChanged:y},`node_${e}`)),T=()=>n.map((o,e)=>{if(!(e+1>=n.length))return d(q,{startCoordinate:o,endCoordinate:n[e+1],pageToStageOffset:l,svgScale:u,svgTranslation:f,style:R,onAddNode:s=>{const m=[...n];m.splice(e+1,0,s),v(m)},onDoubleClick:()=>r===i.CREATE&&X(),onMouseDown:M,onMouseMove:N},`edge_${e}`)}),_=C&&r!==i.CREATE;return $("g",{children:[(c||r===i.CREATE)&&Y(),T(),_&&j()]})};export{K as default};
1
+ import{jsxs as $,jsx as A}from"react/jsx-runtime";import{useState as P,useRef as k,useEffect as x}from"react";import U from"../atoms/Node.js";import u from"../../../models/AnnotationMode.js";import q from"../atoms/Edge.js";import I from"../../../utils/mouse.js";const K=({annotationSettings:C,coordinates:n,isSelected:d,annotationMode:s,pageToStageOffset:l,svgScale:m,svgTranslation:f,style:R,onAddNode:v,onDeleteNode:L,onFinishAnnoCreate:T,onMoving:E,onMoved:w,onIsDraggingStateChanged:y})=>{const[c,g]=P(!1),[p,h]=P(!1),D=k(p);x(()=>{D.current=p},[p]);const b=e=>{if(C.canEdit!==!1){if(e.button===1&&s===u.CREATE){e.preventDefault(),e.stopPropagation();return}if(d&&s!==u.CREATE&&e.button===0&&g(!0),e.button===2&&s==u.CREATE){const o=I.getAntiScaledMouseStagePosition(e,l,m,f),t=[...n];t.push(o),v(t)}}},N=e=>{if(c){const o=n.map(t=>{const r=t.x+=e.movementX/m,i=t.y+=e.movementY/m;return{x:r,y:i}});(e.movementX!==0||e.movementY!==0)&&(h(!0),E(o))}if(s===u.CREATE){const o=I.getAntiScaledMouseStagePosition(e,l,m,f);let t=[...n];n.length>1&&(t=n.slice(0,-1)),t.push(o),E(t)}};x(()=>{if(y(c),!c)return;const e=()=>{g(!1),D.current&&w(),h(!1)};return globalThis.addEventListener("mouseup",e),()=>{globalThis.removeEventListener("mouseup",e)}},[c]);const X=()=>A("circle",{cx:n[0].x,cy:n[0].y,r:"100%",style:{opacity:0},onMouseDown:b,onMouseMove:N,onContextMenu:e=>e.preventDefault()}),Y=()=>n.map((o,t)=>A(U,{index:t,annotationSettings:C,coordinates:o,pageToStageOffset:l,svgScale:m,svgTranslation:f,style:R,onDeleteNode:()=>{const r=[...n];r.splice(t,1),L(r)},onMoving:(r,i)=>{const M=[...n];M[r]=i,E(M)},onMoved:()=>w(),onIsDraggingStateChanged:y},`node_${t}`)),j=()=>n.map((o,t)=>{if(!(t+1>=n.length))return A(q,{startCoordinate:o,endCoordinate:n[t+1],pageToStageOffset:l,svgScale:m,svgTranslation:f,style:R,onAddNode:r=>{const i=[...n];i.splice(t+1,0,r),v(i)},onDoubleClick:()=>s===u.CREATE&&T(),onMouseDown:b,onMouseMove:N},`edge_${t}`)}),_=d&&s!==u.CREATE;return $("g",{children:[(c||s===u.CREATE)&&X(),j(),_&&Y()]})};export{K as default};
@@ -1 +1 @@
1
- import{jsxs as U,jsx as c}from"react/jsx-runtime";import{useState as T,useRef as a,useEffect as X}from"react";import q from"../atoms/Node.js";import z from"../atoms/PolygonArea.js";import s from"../../../models/AnnotationMode.js";import B from"../atoms/Edge.js";import Y from"../../../utils/mouse.js";import _ from"../../../models/NotificationType.js";const O=({annotationSettings:C,coordinates:e,isSelected:f,isDisabled:D=!1,annotationMode:o,pageToStageOffset:A,svgScale:m,svgTranslation:p,style:y,onAddNode:v,onDeleteNode:j,onFinishAnnoCreate:F,onIsDraggingStateChanged:P,onMoving:g,onMoved:w,onNotification:b=l=>{}})=>{const[l,N]=T(!1),[h,x]=T(!1),I=a(h),M=()=>{if(e.length<3)return b({message:"Polygons must have at least 3 nodes",title:"Polygon Error",type:_.ERROR});F()};X(()=>{I.current=h},[h]);const R=n=>{if(C.canEdit!==!1&&(f&&o!==s.CREATE&&o!==s.ADD&&n.button===0&&N(!0),n.button===2&&[s.CREATE,s.ADD].includes(o))){const r=Y.getAntiScaledMouseStagePosition(n,A,m,p),t=[...e];t.push(r),v(t)}},d=n=>{if(l){const r=e.map(t=>{const u=t.x+=n.movementX/m,i=t.y+=n.movementY/m;return{x:u,y:i}});(n.movementX!==0||n.movementY!==0)&&(x(!0),g(r))}if(o===s.CREATE){const r=Y.getAntiScaledMouseStagePosition(n,A,m,p);let t=[...e];e.length>1&&(t=e.slice(0,-1)),t.push(r),g(t)}};X(()=>{if(P(l),!l)return;const n=()=>{N(!1),I.current&&w(),x(!1)};return globalThis.addEventListener("mouseup",n),()=>{globalThis.removeEventListener("mouseup",n)}},[l]);const L=()=>e.map((r,t)=>c(q,{index:t,annotationSettings:C,coordinates:r,pageToStageOffset:A,svgScale:m,svgTranslation:p,style:y,onDeleteNode:()=>{if(e.length<4)return b({message:"Polygons must have at least 3 nodes",title:"Polygon Error",type:_.ERROR});const u=[...e];u.splice(t,1),j(u)},onMoving:(u,i)=>{const E=[...e];E[u]=i,g(E)},onMoved:()=>w(),onIsDraggingStateChanged:P},`node_${t}`)),$=()=>e.map((r,t)=>{const u=t+1<e.length?e[t+1]:e[0];return c(B,{startCoordinate:r,endCoordinate:u,isDisabled:D&&f,pageToStageOffset:A,svgScale:m,svgTranslation:p,style:y,onAddNode:i=>{const E=[...e];E.splice(t+1,0,i),v(E)},onDoubleClick:()=>o===s.CREATE&&M(),onMouseDown:R,onMouseMove:d},`edge_${t}`)}),k=()=>c("circle",{cx:e[0].x,cy:e[0].y,r:"100%",style:{opacity:0},onMouseDown:R,onMouseMove:d,onContextMenu:n=>n.preventDefault()});return U("g",{children:[(l||o===s.CREATE||o===s.ADD)&&k(),c(z,{coordinates:e,isSelected:f,isDisabled:D,annotationMode:o,style:y,onFinishAnnoCreate:M,onMouseDown:R,onMouseMove:d}),f&&C.canEdit&&$(),f&&o!==s.CREATE&&L()]})};export{O as default};
1
+ import{jsxs as k,jsx as c}from"react/jsx-runtime";import{useState as X,useRef as U,useEffect as Y}from"react";import q from"../atoms/Node.js";import z from"../atoms/PolygonArea.js";import r from"../../../models/AnnotationMode.js";import B from"../atoms/Edge.js";import _ from"../../../utils/mouse.js";import j from"../../../models/NotificationType.js";const O=({annotationSettings:C,coordinates:t,isSelected:f,isDisabled:d=!1,annotationMode:o,pageToStageOffset:A,svgScale:l,svgTranslation:p,style:g,onAddNode:v,onDeleteNode:F,onFinishAnnoCreate:L,onIsDraggingStateChanged:P,onMoving:y,onMoved:w,onNotification:b=m=>{}})=>{const[m,N]=X(!1),[D,T]=X(!1),x=U(D),I=()=>{if(t.length<3)return b({message:"Polygons must have at least 3 nodes",title:"Polygon Error",type:j.ERROR});L()};Y(()=>{x.current=D},[D]);const R=e=>{if(C.canEdit!==!1){if(e.button===1&&[r.CREATE,r.ADD].includes(o)){e.preventDefault(),e.stopPropagation();return}if(f&&o!==r.CREATE&&o!==r.ADD&&e.button===0&&N(!0),e.button===2&&[r.CREATE,r.ADD].includes(o)){const s=_.getAntiScaledMouseStagePosition(e,A,l,p),n=[...t];n.push(s),v(n)}}},h=e=>{if(m){const s=t.map(n=>{const u=n.x+=e.movementX/l,i=n.y+=e.movementY/l;return{x:u,y:i}});(e.movementX!==0||e.movementY!==0)&&(T(!0),y(s))}if(o===r.CREATE){const s=_.getAntiScaledMouseStagePosition(e,A,l,p);let n=[...t];t.length>1&&(n=t.slice(0,-1)),n.push(s),y(n)}};Y(()=>{if(P(m),!m)return;const e=()=>{N(!1),x.current&&w(),T(!1)};return globalThis.addEventListener("mouseup",e),()=>{globalThis.removeEventListener("mouseup",e)}},[m]);const M=()=>t.map((s,n)=>c(q,{index:n,annotationSettings:C,coordinates:s,pageToStageOffset:A,svgScale:l,svgTranslation:p,style:g,onDeleteNode:()=>{if(t.length<4)return b({message:"Polygons must have at least 3 nodes",title:"Polygon Error",type:j.ERROR});const u=[...t];u.splice(n,1),F(u)},onMoving:(u,i)=>{const E=[...t];E[u]=i,y(E)},onMoved:()=>w(),onIsDraggingStateChanged:P},`node_${n}`)),$=()=>t.map((s,n)=>{const u=n+1<t.length?t[n+1]:t[0];return c(B,{startCoordinate:s,endCoordinate:u,isDisabled:d&&f,pageToStageOffset:A,svgScale:l,svgTranslation:p,style:g,onAddNode:i=>{const E=[...t];E.splice(n+1,0,i),v(E)},onDoubleClick:()=>o===r.CREATE&&I(),onMouseDown:R,onMouseMove:h},`edge_${n}`)}),a=()=>c("circle",{cx:t[0].x,cy:t[0].y,r:"100%",style:{opacity:0},onMouseDown:R,onMouseMove:h,onContextMenu:e=>e.preventDefault()});return k("g",{children:[(m||o===r.CREATE||o===r.ADD)&&a(),c(z,{coordinates:t,isSelected:f,isDisabled:d,annotationMode:o,style:g,onFinishAnnoCreate:I,onMouseDown:R,onMouseMove:h}),f&&C.canEdit&&$(),f&&o!==r.CREATE&&M()]})};export{O as default};
@@ -1 +1 @@
1
- import{jsxs as B,jsx as E,Fragment as qe}from"react/jsx-runtime";import{useState as y,useRef as Z,useEffect as I}from"react";import M from"../models/AnnotationTool.js";import i from"../models/EditorModes.js";import et from"../utils/KeyMapper.js";import c from"../models/KeyAction.js";import he from"../Annotation/logic/Annotation.js";import tt from"../models/CanvasAction.js";import nt from"../Annotation/ui/AnnotationComponent.js";import xe from"../utils/mouse.js";import _ from"../models/AnnotationMode.js";import ot from"./LabelInput.js";import{FontAwesomeIcon as rt}from"@fortawesome/react-fontawesome";import{faBan as st}from"@fortawesome/free-solid-svg-icons";import w from"../models/AnnotationStatus.js";import A from"../utils/transform.js";import it from"../models/NotificationType.js";import at from"../utils/TimeUtils.js";import Ce from"../utils/windowViewport.js";const bt=({annotations:v=[],annotationSettings:K,defaultLabelId:q,image:ee,isFullscreen:ve=!1,isImageJunk:F=!1,isPolygonSelectionMode:X=!1,polygonOperationResult:z={annotationsToDelete:[],polygonsToCreate:[]},possibleLabels:te,preventScrolling:ne=!0,selectedAnnotation:s,selectedAnnoTool:N,toolbarHeight:U=0,uiConfig:Y,onAnnoCreated:Te,onAnnoCreationFinished:oe,onAnnoChanged:re,onAnnoEditing:Ie=g=>{},onNotification:se=g=>{},onRequestNewAnnoId:R,onSelectAnnotation:T,onSetIsImageJunk:Oe,onSetSelectedTool:Me=g=>{},onShouldDeleteAnno:ie,onTraverseAnnotationHistory:ae})=>{const[g,h]=y(i.VIEW),[we,Se]=y(),[ce,be]=y(q),[P,De]=y({x:-1,y:-1}),[le,de]=y(0),V={x:P.x,y:P.y},[u,G]=y({x:-1,y:-1}),[d,H]=y({x:-1,y:-1}),[m,fe]=y({x:-1,y:-1}),[l,k]=y(1),[a,S]=y({x:0,y:0}),$={x:a.x+le,y:a.y},[j,L]=y(),[J,b]=y(!1),ue=Z(null),x=Z(null),p=Z(null),C=((e,t)=>{if(e.x===0||e.y===0||t.x===0||t.y===0)return 0;const n=t.x/e.x,o=t.y/e.y;return Math.min(n,o)})(u,d),_e=()=>{if(p?.current===null)return{x:0,y:0};const e=u.x*C;if(Y.imageCentered&&d.x>e){const f=(d.x-e)/2;de(f)}else de(0);const{top:t,left:n}=x.current.getBoundingClientRect(),o={x:n+window.scrollX,y:t+window.scrollY};De(o)},Ne=new et(e=>Be(e)),Re=e=>{h(i.CREATE);const t=A.convertStageCoordinatesToPercentaged([e],C,u);N===M.BBox&&t.push(t[0]);const n=R(),o=new he(n,N,t);if(Se(performance.now()),ce!==void 0&&(o.labelIds=[ce]),Te(o),N===M.Point){const r={...o,coordinates:[e],annoTime:0};Q(r)}},Pe=()=>{if(s&&![M.Line,M.Polygon].includes(s.type))return;const e=v.find(n=>n.internalId===s?.internalId);if(e===void 0)return;h(i.CREATE),Me(e.type);const t={...e,mode:_.CREATE,status:w.CREATING,internalId:R(),selectedNode:e.coordinates.length-1};Ie(t)},Ve=()=>{const e=s?s.internalId:0,t=v.find(n=>n.internalId>e);if(t)return T(t);if(v.length>0)return T(v[0])},ke=()=>{const e=s?s.internalId:0,t=[...v];t.sort((o,r)=>r.internalId-o.internalId);const n=t.find(o=>o.internalId<e);if(n)return T(n);if(v.length>0)return T(v.at(-1))},Le=()=>{if(s){const e=JSON.stringify(s);localStorage.setItem("lostAnnotationClipboard",e);const t={title:"Success",message:"Annotation copied",type:it.SUCCESS};se(t)}},We=()=>{const e=localStorage.getItem("lostAnnotationClipboard");if(e==null)return;const t=JSON.parse(e);t.internalId=R(),t.externalId="",oe(t,!0),T(t)},ge=e=>{const t=A.getMostLeftPoints(e),n=A.getTopPoint(t)[0];return A.convertStageToPage(n,V,l,a)},Be=e=>{switch(e){case c.EDIT_LABEL:if(s){const t=A.convertPercentagedCoordinatesToStage(s.coordinates,u,m);L(ge(t)),b(!0)}break;case c.DELETE_ANNO:s&&ie(s.internalId);break;case c.DELETE_ANNO_IN_CREATION:g===i.CREATE&&(ie(s.internalId),h(i.VIEW));break;case c.ENTER_ANNO_ADD_MODE:console.log("KeyAction TODO: ENTER_ANNO_ADD_MODE");break;case c.LEAVE_ANNO_ADD_MODE:console.log("KeyAction TODO: LEAVE_ANNO_ADD_MODE");break;case c.UNDO:ae(!0);break;case c.REDO:ae(!1);break;case c.TRAVERSE_ANNOS:Ve();break;case c.TRAVERSE_ANNOS_BACKWARDS:ke();break;case c.CAM_MOVE_LEFT:D(20*l,0);break;case c.CAM_MOVE_RIGHT:D(-20*l,0);break;case c.CAM_MOVE_UP:D(0,20*l);break;case c.CAM_MOVE_DOWN:D(0,-20*l);break;case c.CAM_MOVE_STOP:console.log("KeyAction TODO: CAM_MOVE_STOP");break;case c.COPY_ANNOTATION:Le();break;case c.PASTE_ANNOTATION:We();break;case c.RECREATE_ANNO:console.log("KeyAction TODO: RECREATE_ANNO"),Pe();break;case c.TOGGLE_IMAGE_JUNK:if(g===i.ADD||g===i.CREATE)return;Oe(!F);break;default:console.log("Unknown KeyAction",e);break}},D=(e,t)=>{let n=a.x+e/l,o=a.y+t/l;const r=d.x*.45,f=d.x*.55,O=d.y*.45,Qe=d.y*.55,Ze={x:0,y:0},pe=Ce.getViewportCoordinates(a,d,l,Ze),Ae=Ce.getViewportCoordinates(a,d,l,d);pe.vX>=r?n=a.x-5:Ae.vX<=f?n=a.x+5:pe.vY>=O?o=a.y-5:Ae.vY<=Qe&&(o=a.y+5),S({x:n,y:o})},W=(e=>m.x<=0||m.y<=0||u.x<=0||u.y<=0?[]:v.map(n=>({...n,coordinates:A.convertPercentagedCoordinatesToStage(n.coordinates,u,m)})))(),Ke=()=>{if(h(i.VIEW),fe({x:-1,y:-1}),p.current!==null){const{width:e,height:t}=p.current.getBoundingClientRect();G({x:e,y:t})}k(1),S({x:0,y:0}),L(void 0),b(!1)};I(()=>{ue.current?.focus()},[]),I(()=>{if(x?.current!==void 0){const{width:e,height:t}=x.current.getBoundingClientRect(),n=t-U;H({x:e,y:n});const o=new ResizeObserver(()=>{const{width:r,height:f}=x.current.getBoundingClientRect(),O=f-U;H({x:r,y:O})});return o.observe(x.current),()=>o.disconnect()}Ke()},[ee,ve]),I(()=>{_e()},[p,a,d]),I(()=>{if(x.current===null)return;const{width:e,height:t}=x.current.getBoundingClientRect(),n=t-U;H({x:e,y:n})},[x]),I(()=>{if(p.current===null)return;const{width:e,height:t}=p.current.getBoundingClientRect();G({x:e,y:t});const n=new ResizeObserver(()=>{const{width:o,height:r}=p.current.getBoundingClientRect();G({x:o,y:r})});return n.observe(p.current),()=>n.disconnect()},[p]),I(()=>{if(C===0)return;const e={x:u.x*C,y:u.y*C};fe(e)},[C,u]),I(()=>{X&&z.polygonsToCreate!==void 0&&z.polygonsToCreate.forEach(e=>{const t=R(),n=new he(t,e.type,A.convertPercentagedCoordinatesToStage(e.coordinates,u,m),_.VIEW,w.CREATED);Q(n)})},[z]);const Q=e=>{h(i.VIEW);const t={...e,mode:_.VIEW};if(e.type!==M.Point){const r=at.getRoundedDuration(we,performance.now());t.annoTime=r}const n=A.convertStageCoordinatesToPercentaged(e.coordinates,C,u);t.coordinates=n,re(t);const o=N===M.Point||X;oe(t,o)},Fe=e=>{Ne.keyDown(e.key,e.shiftKey,e.ctrlKey)&&e.preventDefault()},Xe=e=>{e.preventDefault()},ze=e=>{if(e.button!==0){if(e.button===1)h(i.CAMERA_MOVE);else if(e.button===2){if(!K.canCreate||g===i.ADD||g===i.CREATE)return;const t=xe.getAntiScaledMouseStagePosition(e,V,l,a),n={x:t.x-le,y:t.y};Re(n)}}},Ue=()=>{ne&&(document.body.style.overflow="hidden")},Ye=e=>{e.button===1&&h(i.VIEW)},ye=(e,t)=>{g===i.CAMERA_MOVE&&D(e,t)},Ge=()=>{ne&&(document.body.style.overflow="")},He=e=>{const o=(e.deltaY<0?1:-1)>0?l*1.25:l/1.25,r=xe.getAntiScaledMouseStagePosition(e,V,l,a),f=l/o,O={x:f*(r.x+a.x)-r.x,y:f*(r.y+a.y)-r.y};o<1?(k(1),(a.x!=0||a.y!=0)&&S({x:0,y:0})):o>200?(k(200),S(O)):(k(o),S(O))},$e=(e,t)=>{if(t!==tt.ANNO_SELECTED){console.log("Unknown Canvas Action:",t);return}const n={...e,coordinates:A.convertStageCoordinatesToPercentaged([...e.coordinates],C,u)};T(n),L(ge(e.coordinates))},me=e=>{const t=A.convertStageCoordinatesToPercentaged(e.coordinates,C,u),n={...e,coordinates:t};n.status===w.LOADED&&(n.status=w.CHANGED),re(n)},je=()=>{if(g===i.CAMERA_MOVE)return E(qe,{});const t=[i.CREATE,i.ADD,i.MOVE].includes(g),n=W.map(o=>{const r=o.internalId===s?.internalId;return t&&!r?E("g",{},`annotationComponent_${o.internalId}`):E(nt,{scaledAnnotation:o,annotationSettings:K,possibleLabels:te,svgScale:l,svgTranslation:$,pageToStageOffset:V,nodeRadius:Y.nodeRadius,strokeWidth:Y.strokeWidth,isSelected:r,isDisabled:X&&r,onFinishAnnoCreate:Q,onLabelIconClicked:()=>b(!0),onAction:$e,onAnnoChanged:me,onAnnotationModeChange:f=>{f===_.MOVE&&h(i.MOVE),g===i.MOVE&&f===_.VIEW&&h(i.VIEW)},onNotification:se},`annotationComponent_${o.internalId}`)});if(s){const o=W.find(f=>f.internalId===s?.internalId),r=W.indexOf(o);n.push(n.splice(r,1)[0])}return E("g",{children:n})},Je=()=>E("circle",{cx:m.x/2,cy:m.y/2,r:"100%",style:{opacity:0},onContextMenu:e=>e.preventDefault(),onClick:()=>{b(!1)}}),Ee={x:P.x+d.x/2,y:P.y+d.y/2};return B("div",{ref:x,style:{flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"},children:[E("div",{style:{position:"absolute",left:j?.x??0,top:j?.y??0,display:j?.y===void 0?"none":"inherit",zIndex:J?7e3:-1},children:E(ot,{defaultLabelId:q,isVisible:J,selectedLabelsIds:s?.labelIds,possibleLabels:te,isMultilabel:K.canHaveMultipleLabels,onLabelSelect:e=>{if(setTimeout(()=>b(!1),0),e.length>0){const r=e.filter(f=>!s.labelIds.includes(f));r.length>0&&be(r[0])}const t=W.find(r=>r.internalId===s.internalId);if(!t)return;const n=t.status===w.LOADED?w.CHANGED:t.status,o={...s,coordinates:t.coordinates,labelIds:[...e],status:n};me(o)}})}),F&&B("div",{style:{position:"absolute",left:Ee.x,top:Ee.y,transform:"translate(-50%, -50%)",textAlign:"center",color:"white"},children:[E(rt,{icon:st,size:"5x",style:{marginBottom:15}}),E("h2",{children:"Marked as Junk"})]}),B("svg",{ref:ue,style:{flex:"1 1 auto",minHeight:0},onKeyDown:Fe,onKeyUp:Xe,onMouseMove:e=>ye(e.movementX,e.movementY),tabIndex:0,onMouseDown:e=>ze(e),children:[B("g",{transform:`scale(${l}) translate(${$.x}, ${$.y})`,onMouseOver:Ue,onMouseLeave:Ge,onMouseUp:Ye,onWheel:He,onMouseMove:e=>ye(e.movementX,e.movementY),onClick:()=>{T(void 0)},children:[E("image",{onContextMenu:e=>e.preventDefault(),href:ee,ref:p,width:m.x>0?m.x:void 0,height:m.y>0?m.y:void 0}),je()]}),J&&Je(),F&&E("rect",{x:"0",y:"0",width:d.x,height:d.y,style:{opacity:.8},onContextMenu:e=>e.preventDefault(),onClick:()=>{L(void 0)}})]})]})};export{bt as default};
1
+ import{jsxs as K,jsx as p,Fragment as qe}from"react/jsx-runtime";import{useState as g,useRef as Z,useEffect as v}from"react";import O from"../models/AnnotationTool.js";import r from"../models/EditorModes.js";import et from"../utils/KeyMapper.js";import a from"../models/KeyAction.js";import xe from"../Annotation/logic/Annotation.js";import tt from"../models/CanvasAction.js";import nt from"../Annotation/ui/AnnotationComponent.js";import Ce from"../utils/mouse.js";import _ from"../models/AnnotationMode.js";import ot from"./LabelInput.js";import{FontAwesomeIcon as st}from"@fortawesome/react-fontawesome";import{faBan as rt}from"@fortawesome/free-solid-svg-icons";import w from"../models/AnnotationStatus.js";import A from"../utils/transform.js";import it from"../models/NotificationType.js";import at from"../utils/TimeUtils.js";import he from"../utils/windowViewport.js";const Dt=({annotations:h=[],annotationSettings:F,defaultLabelId:q,image:X,isFullscreen:ve=!1,isImageJunk:U=!1,isPolygonSelectionMode:Y=!1,polygonOperationResult:z={annotationsToDelete:[],polygonsToCreate:[]},possibleLabels:ee,preventScrolling:te=!0,selectedAnnotation:i,selectedAnnoTool:N,toolbarHeight:G=0,uiConfig:R,onAnnoCreated:Ie,onAnnoCreationFinished:ne,onAnnoChanged:oe,onAnnoEditing:Te=c=>{},onNotification:se=c=>{},onRequestNewAnnoId:V,onSelectAnnotation:I,onSetIsImageJunk:Me,onSetSelectedTool:Oe=c=>{},onShouldDeleteAnno:re,onTraverseAnnotationHistory:ie})=>{const[c,y]=g(r.VIEW),[we,be]=g(),[ae,De]=g(q),[P,Se]=g({x:-1,y:-1}),[ce,le]=g(0),k={x:P.x,y:P.y},[u,de]=g({x:-1,y:-1}),[d,H]=g({x:-1,y:-1}),[m,fe]=g({x:-1,y:-1}),[f,L]=g(1),[l,b]=g({x:0,y:0}),$={x:l.x+ce,y:l.y},[j,W]=g(),[J,D]=g(!1),ue=Z(null),x=Z(null),T=Z(null),C=((e,t)=>{if(e.x===0||e.y===0||t.x===0||t.y===0)return 0;const n=t.x/e.x,o=t.y/e.y;return Math.min(n,o)})(u,d),_e=()=>{if(T?.current===null)return{x:0,y:0};const e=u.x*C;if(R.imageCentered&&d.x>e){const E=(d.x-e)/2;le(E)}else le(0);const{top:t,left:n}=x.current.getBoundingClientRect(),o={x:n+window.scrollX,y:t+window.scrollY};Se(o)},Ne=new et(e=>Be(e)),Re=e=>{y(r.CREATE);const t=A.convertStageCoordinatesToPercentaged([e],C,u);N===O.BBox&&t.push(t[0]);const n=V(),o=new xe(n,N,t);if(be(performance.now()),ae!==void 0&&(o.labelIds=[ae]),Ie(o),N===O.Point){const s={...o,coordinates:[e],annoTime:0};Q(s)}},Ve=()=>{if(i&&![O.Line,O.Polygon].includes(i.type))return;const e=h.find(n=>n.internalId===i?.internalId);if(e===void 0)return;y(r.CREATE),Oe(e.type);const t={...e,mode:_.CREATE,status:w.CREATING,internalId:V(),selectedNode:e.coordinates.length-1};Te(t)},Pe=()=>{const e=i?i.internalId:0,t=h.find(n=>n.internalId>e);if(t)return I(t);if(h.length>0)return I(h[0])},ke=()=>{const e=i?i.internalId:0,t=[...h];t.sort((o,s)=>s.internalId-o.internalId);const n=t.find(o=>o.internalId<e);if(n)return I(n);if(h.length>0)return I(h.at(-1))},Le=()=>{if(i){const e=JSON.stringify(i);localStorage.setItem("lostAnnotationClipboard",e);const t={title:"Success",message:"Annotation copied",type:it.SUCCESS};se(t)}},We=()=>{const e=localStorage.getItem("lostAnnotationClipboard");if(e==null)return;const t=JSON.parse(e);t.internalId=V(),t.externalId="",ne(t,!0),I(t)},Ee=e=>{const t=A.getMostLeftPoints(e),n=A.getTopPoint(t)[0];return A.convertStageToPage(n,k,f,l)},Be=e=>{switch(e){case a.EDIT_LABEL:if(i){const t=A.convertPercentagedCoordinatesToStage(i.coordinates,u,m);W(Ee(t)),D(!0)}break;case a.DELETE_ANNO:i&&re(i.internalId);break;case a.DELETE_ANNO_IN_CREATION:c===r.CREATE&&(re(i.internalId),y(r.VIEW));break;case a.ENTER_ANNO_ADD_MODE:console.log("KeyAction TODO: ENTER_ANNO_ADD_MODE");break;case a.LEAVE_ANNO_ADD_MODE:console.log("KeyAction TODO: LEAVE_ANNO_ADD_MODE");break;case a.UNDO:ie(!0);break;case a.REDO:ie(!1);break;case a.TRAVERSE_ANNOS:Pe();break;case a.TRAVERSE_ANNOS_BACKWARDS:ke();break;case a.CAM_MOVE_LEFT:S(20*f,0);break;case a.CAM_MOVE_RIGHT:S(-20*f,0);break;case a.CAM_MOVE_UP:S(0,20*f);break;case a.CAM_MOVE_DOWN:S(0,-20*f);break;case a.CAM_MOVE_STOP:console.log("KeyAction TODO: CAM_MOVE_STOP");break;case a.COPY_ANNOTATION:Le();break;case a.PASTE_ANNOTATION:We();break;case a.RECREATE_ANNO:console.log("KeyAction TODO: RECREATE_ANNO"),Ve();break;case a.TOGGLE_IMAGE_JUNK:if(c===r.ADD||c===r.CREATE)return;Me(!U);break;default:console.log("Unknown KeyAction",e);break}},S=(e,t)=>{let n=l.x+e/f,o=l.y+t/f;const s=d.x*.45,E=d.x*.55,M=d.y*.45,Qe=d.y*.55,Ze={x:0,y:0},ye=he.getViewportCoordinates(l,d,f,Ze),Ae=he.getViewportCoordinates(l,d,f,d);ye.vX>=s?n=l.x-5:Ae.vX<=E?n=l.x+5:ye.vY>=M?o=l.y-5:Ae.vY<=Qe&&(o=l.y+5),b({x:n,y:o})},B=(e=>m.x<=0||m.y<=0||u.x<=0||u.y<=0?[]:h.map(n=>({...n,coordinates:A.convertPercentagedCoordinatesToStage(n.coordinates,u,m)})))(),Ke=()=>{if(y(r.VIEW),fe({x:-1,y:-1}),T.current!==null){const{width:e,height:t}=T.current.getBoundingClientRect();de({x:e,y:t})}L(1),b({x:0,y:0}),W(void 0),D(!1)};v(()=>{ue.current?.focus()},[]),v(()=>{const e=t=>{t.button===1&&c===r.CAMERA_MOVE&&y(r.VIEW)};return window.addEventListener("mouseup",e),()=>window.removeEventListener("mouseup",e)},[c]),v(()=>{if(x?.current!==void 0){Ke();const{width:e,height:t}=x.current.getBoundingClientRect(),n=t-G;H({x:e,y:n});const o=new ResizeObserver(()=>{const{width:s,height:E}=x.current.getBoundingClientRect(),M=E-G;H({x:s,y:M})});return o.observe(x.current),()=>o.disconnect()}},[X,ve]),v(()=>{_e()},[d,u,R]),v(()=>{if(x.current===null)return;const{width:e,height:t}=x.current.getBoundingClientRect(),n=t-G;H({x:e,y:n})},[x]),v(()=>{if(T.current===null)return;const{width:e,height:t}=T.current.getBoundingClientRect();de({x:e,y:t})},[X,d]),v(()=>{if(C===0)return;const e={x:u.x*C,y:u.y*C};fe(e)},[C,u]),v(()=>{Y&&z.polygonsToCreate!==void 0&&z.polygonsToCreate.forEach(e=>{const t=V(),n=new xe(t,e.type,A.convertPercentagedCoordinatesToStage(e.coordinates,u,m),_.VIEW,w.CREATED);e.labelIds!==void 0&&(n.labelIds=e.labelIds),Q(n)})},[z]);const Q=e=>{y(r.VIEW);const t={...e,mode:_.VIEW};if(e.type!==O.Point){const s=at.getRoundedDuration(we,performance.now());t.annoTime=s}const n=A.convertStageCoordinatesToPercentaged(e.coordinates,C,u);t.coordinates=n,oe(t);const o=N===O.Point||Y;ne(t,o)},Fe=e=>{Ne.keyDown(e.key,e.shiftKey,e.ctrlKey)&&e.preventDefault()},Xe=e=>{e.preventDefault()},Ue=e=>{if(e.button!==0){if(e.button===1){if(c===r.CREATE||c===r.ADD){e.preventDefault();return}y(r.CAMERA_MOVE)}else if(e.button===2){if(!F.canCreate||c===r.ADD||c===r.CREATE)return;const t=Ce.getAntiScaledMouseStagePosition(e,k,f,l),n={x:t.x-ce,y:t.y};Re(n)}}},Ye=()=>{te&&(document.body.style.overflow="hidden")},ze=e=>{e.button===1&&c===r.CAMERA_MOVE&&y(r.VIEW)},ge=(e,t)=>{c===r.CAMERA_MOVE&&S(e,t)},Ge=()=>{te&&(document.body.style.overflow="")},He=e=>{const o=(e.deltaY<0?1:-1)>0?f*1.25:f/1.25,s=Ce.getAntiScaledMouseStagePosition(e,k,f,l),E=f/o,M={x:E*(s.x+l.x)-s.x,y:E*(s.y+l.y)-s.y};o<1?(L(1),(l.x!=0||l.y!=0)&&b({x:0,y:0})):o>200?(L(200),b(M)):(L(o),b(M))},$e=(e,t)=>{if(t!==tt.ANNO_SELECTED){console.log("Unknown Canvas Action:",t);return}const n={...e,coordinates:A.convertStageCoordinatesToPercentaged([...e.coordinates],C,u)};I(n),W(Ee(e.coordinates))},me=e=>{const t=A.convertStageCoordinatesToPercentaged(e.coordinates,C,u),n={...e,coordinates:t};n.status===w.LOADED&&(n.status=w.CHANGED),oe(n)},je=()=>{if(c===r.CAMERA_MOVE)return p(qe,{});const t=[r.CREATE,r.ADD,r.MOVE].includes(c),n=B.map(o=>{const s=o.internalId===i?.internalId;return t&&!s?p("g",{},`annotationComponent_${o.internalId}`):p(nt,{scaledAnnotation:o,annotationSettings:F,possibleLabels:ee,svgScale:f,svgTranslation:$,pageToStageOffset:k,nodeRadius:R.nodeRadius,strokeWidth:R.strokeWidth,isSelected:s,isDisabled:Y&&s,onFinishAnnoCreate:Q,onLabelIconClicked:()=>D(!0),onAction:$e,onAnnoChanged:me,onAnnotationModeChange:E=>{E===_.MOVE&&y(r.MOVE),c===r.MOVE&&E===_.VIEW&&y(r.VIEW)},onNotification:se},`annotationComponent_${o.internalId}`)});if(i){const o=B.find(E=>E.internalId===i?.internalId),s=B.indexOf(o);n.push(n.splice(s,1)[0])}return p("g",{children:n})},Je=()=>p("circle",{cx:m.x/2,cy:m.y/2,r:"100%",style:{opacity:0},onContextMenu:e=>e.preventDefault(),onClick:()=>{D(!1)}}),pe={x:P.x+d.x/2,y:P.y+d.y/2};return K("div",{ref:x,style:{flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"},children:[p("div",{style:{position:"absolute",left:j?.x??0,top:j?.y??0,display:j?.y===void 0?"none":"inherit",zIndex:J?7e3:-1},children:p(ot,{defaultLabelId:q,isVisible:J,selectedLabelsIds:i?.labelIds,possibleLabels:ee,isMultilabel:F.canHaveMultipleLabels,onLabelSelect:e=>{if(setTimeout(()=>D(!1),0),e.length>0){const s=e.filter(E=>!i.labelIds.includes(E));s.length>0&&De(s[0])}const t=B.find(s=>s.internalId===i.internalId);if(!t)return;const n=t.status===w.LOADED?w.CHANGED:t.status,o={...i,coordinates:t.coordinates,labelIds:[...e],status:n};me(o)}})}),U&&K("div",{style:{position:"absolute",left:pe.x,top:pe.y,transform:"translate(-50%, -50%)",textAlign:"center",color:"white"},children:[p(st,{icon:rt,size:"5x",style:{marginBottom:15}}),p("h2",{children:"Marked as Junk"})]}),K("svg",{ref:ue,style:{flex:"1 1 auto",minHeight:0},onKeyDown:Fe,onKeyUp:Xe,onMouseMove:e=>ge(e.movementX,e.movementY),tabIndex:0,onMouseDown:e=>Ue(e),children:[K("g",{transform:`scale(${f}) translate(${$.x}, ${$.y})`,onMouseOver:Ye,onMouseLeave:Ge,onMouseUp:ze,onWheel:He,onMouseMove:e=>ge(e.movementX,e.movementY),onClick:()=>{I(void 0)},children:[p("image",{onContextMenu:e=>e.preventDefault(),href:X,ref:T,width:m.x>0?m.x:void 0,height:m.y>0?m.y:void 0}),je()]}),J&&Je(),U&&p("rect",{x:"0",y:"0",width:d.x,height:d.y,style:{opacity:.8},onContextMenu:e=>e.preventDefault(),onClick:()=>{W(void 0)}})]})]})};export{Dt as default};
package/dist/Sia.js CHANGED
@@ -1 +1 @@
1
- import{jsx as u,jsxs as V}from"react/jsx-runtime";import{useRef as Tn,useState as a,useEffect as I}from"react";import{CSpinner as K}from"@coreui/react";import Cn from"./Canvas/Canvas.js";import _n from"./models/AnnotationTool.js";import pn from"./Toolbar/Toolbar.js";import Hn from"./models/AnnotationMode.js";import O from"./models/AnnotationStatus.js";const jn=({additionalButtons:Q,allowedTools:S,polygonOperationResult:T={annotationsToDelete:[],polygonsToCreate:[]},annotationSettings:b,uiConfig:k,defaultAnnotationTool:X,defaultLabelId:Y,image:A,isLoading:D=!1,isPolygonSelectionMode:E=!1,initialAnnotations:y=void 0,initialImageLabelIds:C=void 0,initialIsImageJunk:Z=!1,possibleLabels:F,onAnnoCreated:$=(l,p)=>{},onAnnoCreationFinished:nn=l=>{},onAnnoChanged:tn=l=>{},onAnnoDeleted:_=(l,p)=>{},onImageLabelsChanged:en=()=>{},onIsImageJunk:on=()=>{},onNotification:sn=l=>{},onSelectAnnotation:an=l=>{},onTimeTravel:rn=l=>{}})=>{const p=Tn(null),[N,J]=a(),[c,r]=a([]),[j,dn]=a(),[d,H]=a(),[h,B]=a([]),[ln,cn]=a(),[m,g]=a(),[L,R]=a(X??_n.Point),v=n=>{const t=[...n],e=[...h];d!==void 0&&e.splice(d+1),e.push(t),H(void 0),B(e)},[fn,P]=a(C),[U,W]=a(),[w,un]=a(!1),[q,z]=a([]),G=n=>{const t=c.findIndex(o=>o.internalId===n),e=[...c],s=e.splice(t,1)[0];r(e),g(void 0),v(e),_(s,e)},In=()=>{m!==void 0&&G(m.internalId)},mn=()=>{let n=0;const t=y.map(e=>({...e,internalId:n++,mode:Hn.VIEW,selectedNode:1,status:e.status,annoTime:e.annoTime??0}));z([...new Array(n).keys()]),r(t),v(t)},An=()=>{let n=0;for(;q.includes(n);)n++;const t=[...q];return t.push(n),z(t),n},hn=n=>{const t=[...c],e=t.findIndex(i=>i.internalId===m?.internalId);if(e===-1)return;const s=t.splice(e,1)[0];_(s,t);const o=[...t];o.push(n),r(o),g(n)},M=n=>{W(n),on(n)},gn=n=>{const t=d??h.length-1,e=t==h.length-1,s=t==0;if(e&&!n||s&&n)return;const o=t+(n?-1:1);H(o)},yn=n=>{const t=[],e=[],s=[];for(const o of n){const i=c.find(f=>f.internalId===o.internalId);i?JSON.stringify(i)!==JSON.stringify(o)&&s.push(o):t.push(o)}for(const o of c)n.find(f=>f.internalId===o.internalId)||e.push(o);return{addedAnnotations:t,removedAnnotations:e,changedAnnotations:s}};I(()=>{if(d==null||d<0||d>h.length-1)return;const t=[...h[d]];r(t);const e=yn(t);rn(e)},[d]),I(()=>{A===void 0&&(r([]),g(void 0),B([]),H(void 0))},[A]),I(()=>{W(Z),!(A!==void 0||y===void 0||y.length===0)&&mn()},[y]),I(()=>{P(C)},[C]),I(()=>{const t={...{canCreate:!0,canEdit:!0,canHaveMultipleLabels:!1,canLabel:!0,minimalArea:250},...b};dn(t)},[b]),I(()=>{const t={...{nodeRadius:4,strokeWidth:4,imageCentered:!1},...k};cn(t)},[k]),I(()=>{const n={bbox:!0,point:!0,line:!0,junk:!0,polygon:!0};if(S===void 0)return J(n);J(S)},[S]);const vn={position:"fixed",top:0,left:0,zIndex:6e3,backgroundColor:"#ffff",width:"100%",height:"100%",padding:15},xn={flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"};return N===void 0?u("div",{className:"d-flex justify-content-center",children:u(K,{color:"primary",style:{width:"5rem",height:"5rem"}})}):V("div",{style:{...w?vn:{},flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"},children:[u("div",{ref:p,style:{marginBottom:10},children:u(pn,{annotationSettings:j,allowedTools:N,additionalButtons:Q,isDisabled:D,isFullscreen:w,isImageJunk:U,imageLabelIds:fn,possibleLabels:F,selectedTool:L,onImageLabelsChanged:n=>{P(n),en(n)},onSetIsFullscreen:un,onSetIsImageJunk:M,onSetSelectedTool:R,onShouldDeleteSelectedAnnotation:In})}),V("div",{style:xn,children:[D&&u("div",{className:"d-flex justify-content-center",children:u(K,{color:"primary",style:{width:"5rem",height:"5rem",marginTop:200}})}),A&&c&&u(Cn,{annotations:c,annotationSettings:j,defaultLabelId:Y,image:A,isFullscreen:w,isImageJunk:U,isPolygonSelectionMode:E,selectedAnnotation:m,selectedAnnoTool:L,polygonOperationResult:T,possibleLabels:F,uiConfig:ln,onAnnoCreated:n=>{r(t=>{const e=[...t,n];return $(n,e),e}),g(n)},onAnnoChanged:n=>{r(t=>{const e=t.findIndex(o=>o.internalId===n.internalId);if(e===-1)return t;const s=[...t];return s[e]=n,n.status!==O.CREATING&&v(s),s}),tn(n)},onAnnoCreationFinished:(n,t)=>{const e={...n,status:O.CREATED},s=E&&T?.annotationsToDelete!==void 0?[...T.annotationsToDelete,...m!==void 0?[m]:[]]:[];r(o=>{const i=[...o];for(const f of s){const x=i.findIndex(Sn=>Sn.internalId===f.internalId);x!==-1&&i.splice(x,1)}if(t)i.push(e);else{const f=o.findIndex(x=>x.internalId===e.internalId);i[f]=e}return v(i),i});for(const o of s)_(o);nn(e)},onAnnoEditing:hn,onSetIsImageJunk:M,onNotification:sn,onRequestNewAnnoId:An,onSelectAnnotation:n=>{g(n),an(n)},onSetSelectedTool:R,onShouldDeleteAnno:G,onTraverseAnnotationHistory:gn})]})]})};export{jn as default};
1
+ import{jsx as m,jsxs as Q}from"react/jsx-runtime";import{useRef as k,useState as r,useEffect as l}from"react";import{CSpinner as X}from"@coreui/react";import Cn from"./Canvas/Canvas.js";import _n from"./models/AnnotationTool.js";import Hn from"./Toolbar/Toolbar.js";import wn from"./models/AnnotationMode.js";import Y from"./models/AnnotationStatus.js";const jn=({additionalButtons:Z,allowedTools:T,polygonOperationResult:p={annotationsToDelete:[],polygonsToCreate:[]},annotationSettings:D,uiConfig:E,defaultAnnotationTool:$,defaultLabelId:nn,image:g,isLoading:F=!1,isPolygonSelectionMode:N=!1,initialAnnotations:v=void 0,initialImageLabelIds:C=void 0,initialIsImageJunk:tn=!1,possibleLabels:J,onAnnoCreated:en=(u,H)=>{},onAnnoCreationFinished:on=u=>{},onAnnoChanged:sn=u=>{},onAnnoDeleted:_=(u,H)=>{},onImageLabelsChanged:rn=()=>{},onIsImageJunk:an=()=>{},onNotification:dn=u=>{},onSelectAnnotation:R=u=>{},onTimeTravel:cn=u=>{}})=>{const H=k(null),[j,B]=r(),[I,f]=r([]),[L,ln]=r(),[c,w]=r(),[x,P]=r([]),[fn,un]=r(),[A,h]=r(),[U,W]=r($??_n.Point),a=k(I);l(()=>{a.current=I},[I]);const y=k(c);l(()=>{y.current=c},[c]);const S=n=>{const t=[...n];P(e=>{const s=[...e];return y.current!==void 0&&s.splice(y.current+1),s.push(t),s}),y.current=void 0,w(void 0)},[In,q]=r(C),[z,G]=r(),[b,mn]=r(!1),[M,V]=r([]),K=n=>{const t=a.current,e=t.findIndex(i=>i.internalId===n);if(e===-1)return;const s=[...t],o=s.splice(e,1)[0];a.current=s,f(s),h(void 0),S(s),_(o,s)},An=()=>{A!==void 0&&K(A.internalId)},hn=()=>{let n=0;const t=v.map(e=>({...e,internalId:n++,mode:wn.VIEW,selectedNode:1,status:e.status,annoTime:e.annoTime??0}));V([...new Array(n).keys()]),f(t),S(t)},gn=()=>{let n=0;for(;M.includes(n);)n++;const t=[...M];return t.push(n),V(t),n},vn=n=>{const t=[...a.current],e=t.findIndex(i=>i.internalId===A?.internalId);if(e===-1)return;const s=t.splice(e,1)[0];_(s,t);const o=[...t];o.push(n),f(o),h(n)},O=n=>{G(n),an(n)},xn=n=>{const t=c??x.length-1,e=t==x.length-1,s=t==0;if(e&&!n||s&&n)return;const o=t+(n?-1:1);w(o)},yn=n=>{const t=[],e=[],s=[];for(const o of n){const i=I.find(d=>d.internalId===o.internalId);i?JSON.stringify(i)!==JSON.stringify(o)&&s.push(o):t.push(o)}for(const o of I)n.find(d=>d.internalId===o.internalId)||e.push(o);return{addedAnnotations:t,removedAnnotations:e,changedAnnotations:s}};l(()=>{if(c==null||c<0||c>x.length-1)return;const t=[...x[c]];f(t);const e=yn(t);cn(e)},[c]),l(()=>{g===void 0&&(f([]),h(void 0),P([]),w(void 0))},[g]),l(()=>{G(tn),!(g!==void 0||v===void 0||v.length===0)&&hn()},[v]),l(()=>{q(C)},[C]),l(()=>{const t={...{canCreate:!0,canEdit:!0,canHaveMultipleLabels:!1,canLabel:!0,minimalArea:250},...D};ln(t)},[D]),l(()=>{const t={...{nodeRadius:4,strokeWidth:4,imageCentered:!1},...E};un(t)},[E]),l(()=>{const n={bbox:!0,point:!0,line:!0,junk:!0,polygon:!0};if(T===void 0)return B(n);B(T)},[T]);const Sn={position:"fixed",top:0,left:0,zIndex:1040,backgroundColor:"#ffff",width:"100%",height:"100%",padding:15},Tn={flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"};return j===void 0?m("div",{className:"d-flex justify-content-center",children:m(X,{color:"primary",style:{width:"5rem",height:"5rem"}})}):Q("div",{style:{...b?Sn:{},flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"},children:[m("div",{ref:H,style:{marginBottom:10},children:m(Hn,{annotationSettings:L,allowedTools:j,additionalButtons:Z,isDisabled:F,isFullscreen:b,isImageJunk:z,imageLabelIds:In,possibleLabels:J,selectedTool:U,onImageLabelsChanged:n=>{q(n),rn(n)},onSetIsFullscreen:mn,onSetIsImageJunk:O,onSetSelectedTool:W,onShouldDeleteSelectedAnnotation:An})}),Q("div",{style:Tn,children:[F&&m("div",{className:"d-flex justify-content-center",children:m(X,{color:"primary",style:{width:"5rem",height:"5rem",marginTop:200}})}),g&&I&&m(Cn,{annotations:I,annotationSettings:L,defaultLabelId:nn,image:g,isFullscreen:b,isImageJunk:z,isPolygonSelectionMode:N,selectedAnnotation:A,selectedAnnoTool:U,polygonOperationResult:p,possibleLabels:J,uiConfig:fn,onAnnoCreated:n=>{const t=[...a.current,n];a.current=t,f(t),en(n,t),h(n)},onAnnoChanged:n=>{const t=a.current.findIndex(e=>e.internalId===n.internalId);if(t!==-1){const e=[...a.current];e[t]=n,a.current=e,f(e),n.status!==Y.CREATING&&S(e)}sn(n)},onAnnoCreationFinished:(n,t)=>{const e={...n,status:Y.CREATED},s=N&&p?.annotationsToDelete!==void 0?[...p.annotationsToDelete,...A!==void 0?[A]:[]]:[],o=[...a.current];for(const i of s){const d=o.findIndex(pn=>pn.internalId===i.internalId);d!==-1&&o.splice(d,1)}if(t){const i=o.findIndex(d=>d.internalId===e.internalId);i!==-1&&o.splice(i,1),o.push(e)}else{const i=o.findIndex(d=>d.internalId===e.internalId);i!==-1&&(o[i]=e)}a.current=o,f(o),S(o),h(e),R(e);for(const i of s)_(i);on(e)},onAnnoEditing:vn,onSetIsImageJunk:O,onNotification:dn,onRequestNewAnnoId:gn,onSelectAnnotation:n=>{h(n),R(n)},onSetSelectedTool:W,onShouldDeleteAnno:K,onTraverseAnnotationHistory:xn})]})]})};export{jn as default};
@@ -2,7 +2,7 @@ import { Label } from '../../../types';
2
2
  type ImageLabelInputProps = {
3
3
  isDisabled: boolean;
4
4
  isVisible: boolean;
5
- selectedLabelsIds: number[];
5
+ selectedLabelsIds: number[] | undefined;
6
6
  possibleLabels: Label[];
7
7
  isMultilabel?: boolean;
8
8
  onLabelSelect: (selectedLabelIds: number[]) => void;
@@ -1 +1 @@
1
- import{jsx as r,jsxs as s}from"react/jsx-runtime";import{useState as C}from"react";import{CTooltip as L,CDropdown as w,CDropdownToggle as v,CDropdownMenu as T,CFormInput as x,CDropdownDivider as y,CDropdownItem as d}from"@coreui/react";import{FontAwesomeIcon as D}from"@fortawesome/react-fontawesome";import{faTag as b}from"@fortawesome/free-solid-svg-icons";import F from"./TagLabel.js";const M=({isDisabled:c,isVisible:m,selectedLabelsIds:t,possibleLabels:n,isMultilabel:p=!1,onLabelSelect:u})=>{const[i,f]=C(""),a=n.filter(e=>e.name.toLowerCase().includes(i.toLowerCase())),g=e=>{let o=[];if(p){o=[...t];const l=t.indexOf(e.id);l===-1?o.push(e.id):o.splice(l,1)}else o=[e.id];u(o)},h=()=>n.filter(o=>t.includes(o.id));return r(L,{content:"Add Image Label",children:s(w,{visible:m,autoClose:!1,children:[r(v,{variant:"outline",caret:!1,color:c?"secondary":"primary",style:{paddingTop:0,paddingBottom:0},as:"div",children:t.length===0?r("div",{style:{marginTop:6},children:r(D,{icon:b})}):h().map(o=>r(F,{name:o.name,color:o.color,size:25,triangleSize:17,style:{marginLeft:1,marginTop:5}},o.name))}),s(T,{children:[r("div",{className:"px-3 py-2",children:r(x,{placeholder:"Filter label...",value:i,onChange:e=>f(e.target.value),autoFocus:!0})}),r(y,{}),a.length>0?a.map(e=>r(d,{onClick:()=>g(e),children:e.name},e.id)):r(d,{disabled:!0,children:"No results"})]})]})})};export{M as default};
1
+ import{jsx as r,jsxs as d}from"react/jsx-runtime";import{useState as C}from"react";import{CTooltip as w,CDropdown as L,CDropdownToggle as v,CDropdownMenu as T,CFormInput as x,CDropdownDivider as y,CDropdownItem as s}from"@coreui/react";import{FontAwesomeIcon as D}from"@fortawesome/react-fontawesome";import{faTag as F}from"@fortawesome/free-solid-svg-icons";import b from"./TagLabel.js";const M=({isDisabled:c,isVisible:m,selectedLabelsIds:t,possibleLabels:n,isMultilabel:p=!1,onLabelSelect:u})=>{const[i,f]=C(""),a=n.filter(e=>e.name.toLowerCase().includes(i.toLowerCase())),g=e=>{let o=[];if(p){o=[...t??[]];const l=(t??[]).indexOf(e.id);l===-1?o.push(e.id):o.splice(l,1)}else o=[e.id];u(o)},h=()=>n.filter(o=>t?.includes(o.id));return r(w,{content:"Add Image Label",children:d(L,{visible:m,autoClose:"outside",children:[r(v,{variant:"outline",caret:!1,color:c?"secondary":"primary",style:{paddingTop:0,paddingBottom:0},as:"div",children:!t||t.length===0?r("div",{style:{marginTop:6},children:r(D,{icon:F})}):h().map(o=>r(b,{name:o.name,color:o.color,size:25,triangleSize:17,style:{marginLeft:1,marginTop:5}},o.name))}),d(T,{children:[r("div",{className:"px-3 py-2",children:r(x,{placeholder:"Filter label...",value:i,onChange:e=>f(e.target.value),autoFocus:!0})}),r(y,{}),a.length>0?a.map(e=>r(s,{onClick:()=>g(e),children:e.name},e.id)):r(s,{disabled:!0,children:"No results"})]})]})})};export{M as default};
package/dist/types.d.ts CHANGED
@@ -45,6 +45,7 @@ export type SIANotification = {
45
45
  export type ToolCoordinates = {
46
46
  coordinates: Point[];
47
47
  type: AnnotationTool;
48
+ labelIds?: number[];
48
49
  };
49
50
  export type UiConfig = {
50
51
  strokeWidth: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lost-sia",
3
- "version": "3.1.2",
3
+ "version": "3.1.4",
4
4
  "description": "Single Image Annotation Tool",
5
5
  "license": "MIT",
6
6
  "repository": "l3p-cv/lost-sia",
@@ -54,6 +54,14 @@ const Line = ({
54
54
  const onMouseDown = (e: MouseEvent) => {
55
55
  if (annotationSettings.canEdit === false) return
56
56
 
57
+ // prevent middle-click from bubbling to the canvas during annotation creation
58
+ // also prevent default browser behavior (auto-scroll / paste on Linux)
59
+ if (e.button === 1 && annotationMode === AnnotationMode.CREATE) {
60
+ e.preventDefault()
61
+ e.stopPropagation()
62
+ return
63
+ }
64
+
57
65
  if (isSelected && annotationMode !== AnnotationMode.CREATE && e.button === 0)
58
66
  setIsAnnoDragging(true)
59
67
 
@@ -71,6 +71,17 @@ const Polygon = ({
71
71
  const onMouseDown = (e: MouseEvent) => {
72
72
  if (annotationSettings.canEdit === false) return
73
73
 
74
+ // prevent middle-click from bubbling to the canvas during annotation creation
75
+ // also prevent default browser behavior (auto-scroll / paste on Linux)
76
+ if (
77
+ e.button === 1 &&
78
+ [AnnotationMode.CREATE, AnnotationMode.ADD].includes(annotationMode)
79
+ ) {
80
+ e.preventDefault()
81
+ e.stopPropagation()
82
+ return
83
+ }
84
+
74
85
  if (
75
86
  isSelected &&
76
87
  annotationMode !== AnnotationMode.CREATE &&
@@ -343,7 +343,12 @@ const Canvas = ({
343
343
  const getAnnoTopLeftPagePosition = (stageCoords: Point[]): Point => {
344
344
  const leftPoints: Point[] = transform.getMostLeftPoints(stageCoords)
345
345
  const topLeftPoint: Point = transform.getTopPoint(leftPoints)[0]
346
- return transform.convertStageToPage(topLeftPoint, pageToStageOffset, svgScale, svgTranslation)
346
+ return transform.convertStageToPage(
347
+ topLeftPoint,
348
+ pageToStageOffset,
349
+ svgScale,
350
+ svgTranslation,
351
+ )
347
352
  }
348
353
 
349
354
  const handleKeyAction = (keyAction: KeyAction) => {
@@ -499,9 +504,24 @@ const Canvas = ({
499
504
  svgRef.current?.focus()
500
505
  }, [])
501
506
 
507
+ // reset CAMERA_MOVE mode on middle-mouse release, even if it happens outside the SVG boundary
508
+ useEffect(() => {
509
+ const handleWindowMouseUp = (e: MouseEvent) => {
510
+ if (e.button === 1 && editorMode === EditorModes.CAMERA_MOVE) {
511
+ setEditorMode(EditorModes.VIEW)
512
+ }
513
+ }
514
+ window.addEventListener('mouseup', handleWindowMouseUp)
515
+ return () => window.removeEventListener('mouseup', handleWindowMouseUp)
516
+ }, [editorMode])
517
+
502
518
  // image changed after init -> reset everything
503
519
  useEffect(() => {
504
520
  if (canvasRef?.current !== undefined) {
521
+ // clear stale sizing state only when the canvas ref is ready, to avoid
522
+ // leaving imgSize stuck at {x:-1, y:-1} on the early-exit path
523
+ resetCanvas()
524
+
505
525
  const { width, height } = canvasRef.current!.getBoundingClientRect()
506
526
 
507
527
  // for whatever reason the ref adds the toolbars height to the available space, leading to a container size reaching outside the bottom
@@ -522,13 +542,13 @@ const Canvas = ({
522
542
  // cleanup
523
543
  return () => resizeObserver.disconnect()
524
544
  }
525
-
526
- resetCanvas()
527
545
  }, [image, isFullscreen])
528
546
 
529
547
  useEffect(() => {
530
548
  calculatePageToCanvasOffset()
531
- }, [imageRef, svgTranslation, canvasSize])
549
+ // imgSize and uiConfig are read inside calculatePageToCanvasOffset for the imageCentered path;
550
+ // include them to avoid a stale closure when imgSize changes without canvasSize changing.
551
+ }, [canvasSize, imgSize, uiConfig])
532
552
 
533
553
  // notify component about available size
534
554
  useEffect(() => {
@@ -543,23 +563,14 @@ const Canvas = ({
543
563
  }, [canvasRef])
544
564
 
545
565
  // notify component about default image size
566
+ // read rendered size when image or canvas size changes — no ResizeObserver needed here
567
+ // since canvasRef's ResizeObserver already handles container resize events and updates canvasSize
546
568
  useEffect(() => {
547
569
  if (imageRef.current === null) return
548
570
 
549
571
  const { width, height } = imageRef.current.getBoundingClientRect()
550
-
551
572
  setImgSize({ x: width, y: height })
552
-
553
- // listen for size changes on div element
554
- const imgResizeObserver = new ResizeObserver(() => {
555
- const { width, height } = imageRef.current!.getBoundingClientRect()
556
-
557
- setImgSize({ x: width, y: height })
558
- })
559
- imgResizeObserver.observe(imageRef.current)
560
-
561
- return () => imgResizeObserver.disconnect()
562
- }, [imageRef])
573
+ }, [image, canvasSize])
563
574
 
564
575
  useEffect(() => {
565
576
  if (imageToStageFactor === 0) return
@@ -592,6 +603,10 @@ const Canvas = ({
592
603
  AnnotationStatus.CREATED,
593
604
  )
594
605
 
606
+ if (polygonToCreate.labelIds !== undefined) {
607
+ newAnnotation.labelIds = polygonToCreate.labelIds
608
+ }
609
+
595
610
  onFinishCreateAnno(newAnnotation)
596
611
  },
597
612
  )
@@ -649,7 +664,11 @@ const Canvas = ({
649
664
  if (e.button === 0) {
650
665
  // left click
651
666
  } else if (e.button === 1) {
652
- // click on mouse wheel
667
+ // click on mouse wheel - ignore during annotation creation to prevent abandoning in-progress annotations
668
+ if (editorMode === EditorModes.CREATE || editorMode === EditorModes.ADD) {
669
+ e.preventDefault()
670
+ return
671
+ }
653
672
  setEditorMode(EditorModes.CAMERA_MOVE)
654
673
  } else if (e.button === 2) {
655
674
  // check if annotation creation allowed in settings
@@ -686,7 +705,7 @@ const Canvas = ({
686
705
  }
687
706
 
688
707
  const onMouseUp = (e) => {
689
- if (e.button === 1) {
708
+ if (e.button === 1 && editorMode === EditorModes.CAMERA_MOVE) {
690
709
  setEditorMode(EditorModes.VIEW)
691
710
  }
692
711
  }
package/src/Sia.tsx CHANGED
@@ -95,24 +95,39 @@ const Sia = ({
95
95
  defaultAnnotationTool ?? AnnotationTool.Point,
96
96
  )
97
97
 
98
+ // refs to always have the latest state without stale closure issues
99
+ const annotationsRef = useRef<Annotation[]>(annotations)
100
+ useEffect(() => {
101
+ annotationsRef.current = annotations
102
+ }, [annotations])
103
+
104
+ const annotationHistoryIndexRef = useRef<number | undefined>(annotationHistoryIndex)
105
+ useEffect(() => {
106
+ annotationHistoryIndexRef.current = annotationHistoryIndex
107
+ }, [annotationHistoryIndex])
108
+
98
109
  const updateAnnotationHistory = (annotations: Annotation[]) => {
99
110
  const _annotations = [...annotations]
100
- const _annotationHistory = [...annotationHistory]
101
111
 
102
- // user did some changes from within the past
103
- // time to create an alternative timeline and delete the original one
104
- if (annotationHistoryIndex !== undefined) {
105
- // remove everything after the state the user is
106
- _annotationHistory.splice(annotationHistoryIndex + 1)
107
- }
112
+ setAnnotationHistory((prevHistory) => {
113
+ const _annotationHistory = [...prevHistory]
114
+
115
+ // user did some changes from within the past
116
+ // time to create an alternative timeline and delete the original one
117
+ if (annotationHistoryIndexRef.current !== undefined) {
118
+ // remove everything after the state the user is
119
+ _annotationHistory.splice(annotationHistoryIndexRef.current + 1)
120
+ }
108
121
 
109
122
  // update the list with out latest change (it is always living in the present)
110
- _annotationHistory.push(_annotations)
123
+ _annotationHistory.push(_annotations)
124
+ return _annotationHistory
125
+ })
126
+
111
127
 
112
128
  // keep history index marker in the present
129
+ annotationHistoryIndexRef.current = undefined
113
130
  setAnnotationHistoryIndex(undefined)
114
-
115
- setAnnotationHistory(_annotationHistory)
116
131
  }
117
132
 
118
133
  // for adjusting the container/canvas size
@@ -137,17 +152,19 @@ const Sia = ({
137
152
  const [usedInternalIds, setUsedInternalIds] = useState<number[]>([])
138
153
 
139
154
  const deleteAnnotationByInternalId = (internalId: number) => {
140
- // get index of selected annotation
141
- const annoListIndex: number = annotations.findIndex(
155
+ const currentAnnotations = annotationsRef.current
156
+ const annoListIndex: number = currentAnnotations.findIndex(
142
157
  (anno) => anno.internalId === internalId,
143
158
  )
144
159
 
145
- // dereference list to force state update
146
- const _annotations: Annotation[] = [...annotations]
160
+ if (annoListIndex === -1) return
147
161
 
162
+ const _annotations: Annotation[] = [...currentAnnotations]
163
+
148
164
  // remove annotation
149
165
  const removedAnno: Annotation = _annotations.splice(annoListIndex, 1)[0]
150
166
 
167
+ annotationsRef.current = _annotations
151
168
  setAnnotations(_annotations)
152
169
  setSelectedAnnotation(undefined)
153
170
  updateAnnotationHistory(_annotations)
@@ -206,7 +223,7 @@ const Sia = ({
206
223
  }
207
224
 
208
225
  const handleAnnoEditing = (annotation: Annotation) => {
209
- const _annotations: Annotation[] = [...annotations]
226
+ const _annotations: Annotation[] = [...annotationsRef.current]
210
227
 
211
228
  // annotation is being edited - remove it from current annotations for this time
212
229
  const selectedAnnotationIndex: number = _annotations.findIndex(
@@ -410,7 +427,7 @@ const Sia = ({
410
427
  position: 'fixed',
411
428
  top: 0,
412
429
  left: 0,
413
- zIndex: 6000,
430
+ zIndex: 1040,
414
431
  backgroundColor: '#ffff',
415
432
  width: '100%',
416
433
  height: '100%',
@@ -498,34 +515,30 @@ const Sia = ({
498
515
  possibleLabels={possibleLabels}
499
516
  uiConfig={uiConfig}
500
517
  onAnnoCreated={(annotation: Annotation) => {
501
- setAnnotations((prev) => {
502
- const _annotations = [...prev, annotation]
503
- onAnnoCreated(annotation, _annotations)
504
- return _annotations
505
- })
518
+ const _annotations = [...annotationsRef.current, annotation]
519
+ annotationsRef.current = _annotations
520
+ setAnnotations(_annotations)
521
+ onAnnoCreated(annotation, _annotations)
506
522
  setSelectedAnnotation(annotation)
507
523
  // dont update history here - we dont have a finished anno at this point
508
524
  }}
509
525
  onAnnoChanged={(changedAnno: Annotation) => {
510
- setAnnotations((prev) => {
511
- // update annotation list
512
- const annoListIndex: number = prev.findIndex(
513
- (anno) => anno.internalId === changedAnno.internalId,
514
- )
515
-
516
- // only fire event if item found
517
- if (annoListIndex === -1) return prev
526
+ const annoListIndex: number = annotationsRef.current.findIndex(
527
+ (anno) => anno.internalId === changedAnno.internalId,
528
+ )
518
529
 
519
- const _annotations: Annotation[] = [...prev]
530
+ if (annoListIndex !== -1) {
531
+ const _annotations: Annotation[] = [...annotationsRef.current]
520
532
  _annotations[annoListIndex] = changedAnno
533
+ annotationsRef.current = _annotations
534
+ setAnnotations(_annotations)
535
+
521
536
 
522
537
  // only update history for full/finished annotations
523
538
  if (changedAnno.status !== AnnotationStatus.CREATING) {
524
539
  updateAnnotationHistory(_annotations)
525
540
  }
526
-
527
- return _annotations
528
- })
541
+ }
529
542
 
530
543
  // inform the outside world about our change
531
544
  // (kept outside the updater — side effects must not run inside setState)
@@ -551,30 +564,35 @@ const Sia = ({
551
564
  ]
552
565
  : []
553
566
 
554
- setAnnotations((prev) => {
555
- // update annotation list
556
- const _annotations: Annotation[] = [...prev]
567
+ const _annotations: Annotation[] = [...annotationsRef.current]
557
568
 
558
- // remove the source annotations that were consumed by the polygon operation
559
- for (const annotation of annosToDelete) {
560
- const annoListIndex: number = _annotations.findIndex(
561
- (anno) => anno.internalId === annotation.internalId,
562
- )
563
- if (annoListIndex !== -1) _annotations.splice(annoListIndex, 1)
564
- }
565
-
566
- if (hasAnnoJustBeenCreated) _annotations.push(finishedAnno)
567
- else {
568
- const annoListIndex: number = prev.findIndex(
569
- (anno) => anno.internalId === finishedAnno.internalId,
570
- )
571
- _annotations[annoListIndex] = finishedAnno
572
- }
569
+ // remove annotations consumed by polygon operation
570
+ for (const annotation of annosToDelete) {
571
+ const annoListIndex: number = _annotations.findIndex(
572
+ (anno) => anno.internalId === annotation.internalId,
573
+ )
574
+ if (annoListIndex !== -1) _annotations.splice(annoListIndex, 1)
575
+ }
573
576
 
574
- updateAnnotationHistory(_annotations)
577
+ if (hasAnnoJustBeenCreated) {
578
+ // replace the CREATING entry with the finished CREATED version
579
+ const existingIdx = _annotations.findIndex(
580
+ (a) => a.internalId === finishedAnno.internalId,
581
+ )
582
+ if (existingIdx !== -1) _annotations.splice(existingIdx, 1)
583
+ _annotations.push(finishedAnno)
584
+ } else {
585
+ const annoListIndex: number = _annotations.findIndex(
586
+ (anno) => anno.internalId === finishedAnno.internalId,
587
+ )
588
+ if (annoListIndex !== -1) _annotations[annoListIndex] = finishedAnno
589
+ }
575
590
 
576
- return _annotations
577
- })
591
+ annotationsRef.current = _annotations
592
+ setAnnotations(_annotations)
593
+ updateAnnotationHistory(_annotations)
594
+ setSelectedAnnotation(finishedAnno)
595
+ onSelectAnnotation(finishedAnno)
578
596
 
579
597
  // Notify the server about the deletions (outside of the updater/setState!!!)
580
598
  for (const annotation of annosToDelete) {
@@ -17,7 +17,7 @@ import TagLabel from './TagLabel'
17
17
  type ImageLabelInputProps = {
18
18
  isDisabled: boolean
19
19
  isVisible: boolean
20
- selectedLabelsIds: number[]
20
+ selectedLabelsIds: number[] | undefined
21
21
  possibleLabels: Label[]
22
22
  isMultilabel?: boolean
23
23
  onLabelSelect: (selectedLabelIds: number[]) => void
@@ -41,9 +41,9 @@ const ImageLabelInput = ({
41
41
  let newLabelIds: number[] = []
42
42
 
43
43
  if (isMultilabel) {
44
- newLabelIds = [...selectedLabelsIds]
44
+ newLabelIds = [...(selectedLabelsIds ?? [])]
45
45
  // check if item in list (get its index if so)
46
- const foundIndex: number = selectedLabelsIds.indexOf(clickedLabel.id)
46
+ const foundIndex: number = (selectedLabelsIds ?? []).indexOf(clickedLabel.id)
47
47
  // add label if not in list, remove label if in list
48
48
  if (foundIndex === -1) {
49
49
  newLabelIds.push(clickedLabel.id)
@@ -59,14 +59,14 @@ const ImageLabelInput = ({
59
59
 
60
60
  const getSelectedLabels = () => {
61
61
  const selectedLabels: Label[] = possibleLabels.filter((label: Label) =>
62
- selectedLabelsIds.includes(label.id),
62
+ selectedLabelsIds?.includes(label.id),
63
63
  )
64
64
 
65
65
  return selectedLabels
66
66
  }
67
67
 
68
68
  const renderLabels = () => {
69
- if (selectedLabelsIds.length === 0)
69
+ if (!selectedLabelsIds || selectedLabelsIds.length === 0)
70
70
  return (
71
71
  <div style={{ marginTop: 6 }}>
72
72
  <FontAwesomeIcon icon={faTag as IconProps} />
@@ -88,7 +88,7 @@ const ImageLabelInput = ({
88
88
 
89
89
  return (
90
90
  <CTooltip content="Add Image Label">
91
- <CDropdown visible={isVisible} autoClose={false}>
91
+ <CDropdown visible={isVisible} autoClose="outside">
92
92
  {/* this invisible toggle has to be here, othervise the menu is not showing as intended */}
93
93
  <CDropdownToggle
94
94
  variant="outline"
@@ -29,6 +29,8 @@ const ImageTools = ({
29
29
  const [isLabelPopupVisible, setIsLabelPopupVisible] = useState<boolean>(false)
30
30
 
31
31
  // close modal when the fullscreen state changes
32
+ // NOTE: imageLabelIds intentionally excluded — adding it would close the dropdown on every
33
+ // label click in multi-label mode, before the user finishes selecting labels.
32
34
  useEffect(() => {
33
35
  setIsLabelPopupVisible(false)
34
36
  }, [isFullscreen])
package/src/types.ts CHANGED
@@ -53,6 +53,7 @@ export type SIANotification = {
53
53
  export type ToolCoordinates = {
54
54
  coordinates: Point[]
55
55
  type: AnnotationTool
56
+ labelIds?: number[]
56
57
  }
57
58
 
58
59
  export type UiConfig = {