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.
- package/dist/Annotation/ui/tools/Line.js +1 -1
- package/dist/Annotation/ui/tools/Polygon.js +1 -1
- package/dist/Canvas/Canvas.js +1 -1
- package/dist/Sia.js +1 -1
- package/dist/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.d.ts +1 -1
- package/dist/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.js +1 -1
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
- package/src/Annotation/ui/tools/Line.tsx +8 -0
- package/src/Annotation/ui/tools/Polygon.tsx +11 -0
- package/src/Canvas/Canvas.tsx +37 -18
- package/src/Sia.tsx +72 -54
- package/src/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.tsx +6 -6
- package/src/Toolbar/ToolbarItems/ImageTools.tsx +2 -0
- package/src/types.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsxs as $,jsx as
|
|
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
|
|
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};
|
package/dist/Canvas/Canvas.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{jsxs as
|
|
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
|
|
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
|
|
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
package/package.json
CHANGED
|
@@ -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 &&
|
package/src/Canvas/Canvas.tsx
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
//
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
const annoListIndex: number =
|
|
155
|
+
const currentAnnotations = annotationsRef.current
|
|
156
|
+
const annoListIndex: number = currentAnnotations.findIndex(
|
|
142
157
|
(anno) => anno.internalId === internalId,
|
|
143
158
|
)
|
|
144
159
|
|
|
145
|
-
|
|
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[] = [...
|
|
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:
|
|
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
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
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
|
-
|
|
555
|
-
// update annotation list
|
|
556
|
-
const _annotations: Annotation[] = [...prev]
|
|
567
|
+
const _annotations: Annotation[] = [...annotationsRef.current]
|
|
557
568
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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=
|
|
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])
|