lost-sia 3.1.2 → 3.2.0-alpha0

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.
Files changed (50) hide show
  1. package/dist/Annotation/ui/tools/Line.js +1 -1
  2. package/dist/Annotation/ui/tools/Polygon.js +1 -1
  3. package/dist/Canvas/Canvas.js +1 -1
  4. package/dist/Sia.js +1 -1
  5. package/dist/SiaViewer.d.ts +19 -0
  6. package/dist/SiaViewer.js +1 -0
  7. package/dist/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.d.ts +1 -1
  8. package/dist/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.js +1 -1
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.js +1 -1
  11. package/dist/stories/Canvas/Canvas.stories.d.ts +3 -3
  12. package/dist/stories/Canvas/CanvasWithOffset.stories.d.ts +6 -6
  13. package/dist/stories/SIA/SiaViewer.stories.d.ts +52 -0
  14. package/dist/types.d.ts +1 -0
  15. package/package.json +3 -4
  16. package/src/Annotation/ui/tools/Line.tsx +8 -0
  17. package/src/Annotation/ui/tools/Polygon.tsx +11 -0
  18. package/src/Canvas/Canvas.tsx +42 -19
  19. package/src/Sia.tsx +1 -1
  20. package/src/SiaViewer.tsx +125 -0
  21. package/src/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.tsx +7 -8
  22. package/src/Toolbar/ToolbarItems/ImageTools.tsx +2 -0
  23. package/src/index.ts +1 -1
  24. package/src/stories/SIA/SiaViewer.stories.tsx +105 -0
  25. package/src/stories/exampleData/exampleExternalAnnotations.ts +22 -22
  26. package/src/types.ts +1 -0
  27. package/dist/assets/brand-icons-Cu_C0hZ4.svg +0 -1008
  28. package/dist/assets/brand-icons-F3SPCeH1.woff +0 -0
  29. package/dist/assets/brand-icons-XL9sxUpA.woff2 +0 -0
  30. package/dist/assets/brand-icons-sqJ2Pg7a.eot +0 -0
  31. package/dist/assets/brand-icons-ubhWoxly.ttf +0 -0
  32. package/dist/assets/flags-DOLqOU7Y.png +0 -0
  33. package/dist/assets/icons-BOCtAERH.woff +0 -0
  34. package/dist/assets/icons-CHzK1VD9.eot +0 -0
  35. package/dist/assets/icons-D29ZQHHw.ttf +0 -0
  36. package/dist/assets/icons-Du6TOHnR.woff2 +0 -0
  37. package/dist/assets/icons-RwhydX30.svg +0 -1518
  38. package/dist/assets/node_modules/semantic-ui-css/semantic.min-09YPtVE6.css +0 -1
  39. package/dist/assets/outline-icons-BfdLr8tr.svg +0 -366
  40. package/dist/assets/outline-icons-DD8jm0uy.ttf +0 -0
  41. package/dist/assets/outline-icons-DInHoiqI.woff2 +0 -0
  42. package/dist/assets/outline-icons-LX8adJ4n.eot +0 -0
  43. package/dist/assets/outline-icons-aQ88nltS.woff +0 -0
  44. package/src/AnnoExampleViewer.jsx +0 -69
  45. package/src/InfoBoxes/AnnoDetails.jsx +0 -165
  46. package/src/InfoBoxes/AnnoStats.jsx +0 -106
  47. package/src/InfoBoxes/InfoBox.jsx +0 -76
  48. package/src/InfoBoxes/InfoBoxArea.jsx +0 -152
  49. package/src/InfoBoxes/LabelInfo.jsx +0 -107
  50. package/src/SIASettingButton.jsx +0 -126
@@ -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 F,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 N 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 b 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:w,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:R,toolbarHeight:G=0,uiConfig:V,onAnnoCreated:Ie,onAnnoCreationFinished:ne,onAnnoChanged:oe,onAnnoEditing:Te=c=>{},onNotification:se=c=>{},onRequestNewAnnoId:P,onSelectAnnotation:I,onSetIsImageJunk:Me,onSetSelectedTool:Oe=c=>{},onShouldDeleteAnno:re,onTraverseAnnotationHistory:ie})=>{const[c,y]=g(r.VIEW),[be,we]=g(),[ae,De]=g(q),[k,Se]=g({x:-1,y:-1}),[ce,le]=g(0),L={x:k.x,y:k.y},[u,de]=g({x:-1,y:-1}),[d,H]=g({x:-1,y:-1}),[m,fe]=g({x:-1,y:-1}),[f,W]=g(1),[l,D]=g({x:0,y:0}),$={x:l.x+ce,y:l.y},[j,B]=g(),[J,S]=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(V.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);R===O.BBox&&t.push(t[0]);const n=P(),o=new xe(n,R,t);if(we(performance.now()),ae!==void 0&&(o.labelIds=[ae]),Ie(o),R===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:N.CREATE,status:b.CREATING,internalId:P(),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=P(),t.externalId="",ne(t,!0),I(t)},Ee=e=>{const t=A.getMostLeftPoints(e),n=A.getTopPoint(t)[0];return A.convertStageToPage(n,L,f,l)},Be=e=>{switch(e){case a.EDIT_LABEL:if(i){const t=A.convertPercentagedCoordinatesToStage(i.coordinates,u,m);B(Ee(t)),S(!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:_(20*f,0);break;case a.CAM_MOVE_RIGHT:_(-20*f,0);break;case a.CAM_MOVE_UP:_(0,20*f);break;case a.CAM_MOVE_DOWN:_(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}},_=(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),D({x:n,y:o})},K=(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})}W(1),D({x:0,y:0}),B(void 0),S(!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,V]),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=P(),n=new xe(t,e.type,A.convertPercentagedCoordinatesToStage(e.coordinates,u,m),N.VIEW,b.CREATED);e.labelIds!==void 0&&(n.labelIds=e.labelIds),Q(n)})},[z]);const Q=e=>{y(r.VIEW);const t={...e,mode:N.VIEW};if(e.type!==O.Point){const s=at.getRoundedDuration(be,performance.now());t.annoTime=s}const n=A.convertStageCoordinatesToPercentaged(e.coordinates,C,u);t.coordinates=n,oe(t);const o=R===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(!w.canCreate||c===r.ADD||c===r.CREATE)return;const t=Ce.getAntiScaledMouseStagePosition(e,L,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&&_(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,L,f,l),E=f/o,M={x:E*(s.x+l.x)-s.x,y:E*(s.y+l.y)-s.y};o<1?(W(1),(l.x!=0||l.y!=0)&&D({x:0,y:0})):o>200?(W(200),D(M)):(W(o),D(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.canLabel&&B(Ee(e.coordinates))},me=e=>{const t=A.convertStageCoordinatesToPercentaged(e.coordinates,C,u),n={...e,coordinates:t};n.status===b.LOADED&&(n.status=b.CHANGED),oe(n)},je=()=>{if(c===r.CAMERA_MOVE)return p(qe,{});const t=[r.CREATE,r.ADD,r.MOVE].includes(c),n=K.map(o=>{const s=o.internalId===i?.internalId;return t&&!s?p("g",{},`annotationComponent_${o.internalId}`):p(nt,{scaledAnnotation:o,annotationSettings:w,possibleLabels:ee,svgScale:f,svgTranslation:$,pageToStageOffset:L,nodeRadius:V.nodeRadius,strokeWidth:V.strokeWidth,isSelected:s,isDisabled:Y&&s,onFinishAnnoCreate:Q,onLabelIconClicked:()=>S(!0),onAction:$e,onAnnoChanged:me,onAnnotationModeChange:E=>{E===N.MOVE&&y(r.MOVE),c===r.MOVE&&E===N.VIEW&&y(r.VIEW)},onNotification:se},`annotationComponent_${o.internalId}`)});if(i){const o=K.find(E=>E.internalId===i?.internalId),s=K.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:()=>{S(!1)}}),pe={x:k.x+d.x/2,y:k.y+d.y/2};return F("div",{ref:x,style:{flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"},children:[w.canLabel&&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:w.canHaveMultipleLabels,onLabelSelect:e=>{if(setTimeout(()=>S(!1),0),e.length>0){const s=e.filter(E=>!i.labelIds.includes(E));s.length>0&&De(s[0])}const t=K.find(s=>s.internalId===i.internalId);if(!t)return;const n=t.status===b.LOADED?b.CHANGED:t.status,o={...i,coordinates:t.coordinates,labelIds:[...e],status:n};me(o)}})}),U&&F("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"})]}),F("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:[F("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:()=>{B(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 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:1040,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};
@@ -0,0 +1,19 @@
1
+ import { default as Annotation } from './Annotation/logic/Annotation';
2
+ import { ExternalAnnotation, Label, UiConfig } from './types';
3
+ type SiaViewerProps = {
4
+ image: string;
5
+ annotations?: ExternalAnnotation[];
6
+ possibleLabels: Label[];
7
+ isJunk?: boolean;
8
+ uiConfig?: Partial<UiConfig>;
9
+ enableZoom?: boolean;
10
+ canSelect?: boolean;
11
+ onSelectAnnotation?: (annotation?: Annotation) => void;
12
+ };
13
+ /**
14
+ * View-only SIA component. Renders image + annotations without a toolbar and
15
+ * blocks all mutations (create / edit / delete / label). Zoom and selection
16
+ * can be toggled via props.
17
+ */
18
+ declare const SiaViewer: ({ image, annotations: propAnnotations, possibleLabels, isJunk, uiConfig: propUiConfig, enableZoom, canSelect, onSelectAnnotation, }: SiaViewerProps) => import("react/jsx-runtime").JSX.Element;
19
+ export default SiaViewer;
@@ -0,0 +1 @@
1
+ import{jsx as r}from"react/jsx-runtime";import{useState as s,useEffect as c}from"react";import p from"./Canvas/Canvas.js";import N from"./models/AnnotationMode.js";import h from"./models/AnnotationTool.js";const E={canCreate:!1,canEdit:!1,canLabel:!1,canHaveMultipleLabels:!1},f={nodeRadius:4,strokeWidth:4,imageCentered:!1},y={flex:"1 1 auto",minHeight:0,display:"flex",flexDirection:"column"},H=({image:n,annotations:e,possibleLabels:u,isJunk:m=!1,uiConfig:a,enableZoom:A=!0,canSelect:l=!0,onSelectAnnotation:I=()=>{}})=>{const[S,d]=s([]),[T,t]=s(),[C,g]=s(f);return c(()=>{if(n===void 0||e===void 0){d([]),t(void 0);return}let o=0;const v=e.map(i=>({...i,internalId:o++,mode:N.VIEW,selectedNode:1,status:i.status,annoTime:i.annoTime??0}));d(v),t(void 0)},[e,n]),c(()=>{g({...f,...a})},[a]),r("div",{style:y,onWheelCapture:o=>{A||o.stopPropagation()},children:r(p,{annotations:S,annotationSettings:E,image:n,isImageJunk:m,isPolygonSelectionMode:!1,possibleLabels:u,selectedAnnotation:l?T:void 0,selectedAnnoTool:h.Point,uiConfig:C,onAnnoCreated:()=>{},onAnnoChanged:()=>{},onAnnoCreationFinished:()=>{},onAnnoEditing:()=>{},onNotification:()=>{},onRequestNewAnnoId:()=>0,onSelectAnnotation:l?o=>{t(o),I(o)}:()=>{},onSetIsImageJunk:()=>{},onSetSelectedTool:()=>{},onShouldDeleteAnno:()=>{},onTraverseAnnotationHistory:()=>{}})})};export{H 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/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './types';
2
2
  export { default as IconButton } from './IconButton';
3
3
  export { default as Sia } from './Sia';
4
+ export { default as SiaViewer } from './SiaViewer';
4
5
  export { default as transform } from './utils/transform';
5
6
  export { default as TagLabel } from './Toolbar/ToolbarItems/ImageToolItems/TagLabel';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- /* empty css *//* empty css */import{default as e}from"./IconButton.js";import{default as m}from"./Sia.js";import{default as l}from"./utils/transform.js";import{default as u}from"./Toolbar/ToolbarItems/ImageToolItems/TagLabel.js";export{e as IconButton,m as Sia,u as TagLabel,l as transform};
1
+ /* empty css */import{default as t}from"./IconButton.js";import{default as f}from"./Sia.js";import{default as l}from"./SiaViewer.js";import{default as s}from"./utils/transform.js";import{default as d}from"./Toolbar/ToolbarItems/ImageToolItems/TagLabel.js";export{t as IconButton,f as Sia,l as SiaViewer,d as TagLabel,s as transform};
@@ -2,9 +2,9 @@ import { StoryObj } from '@storybook/react';
2
2
  import { default as AnnotationTool } from '../../models/AnnotationTool';
3
3
  import { AnnotationSettings, UiConfig } from '../../types';
4
4
  export declare const ActionsData: {
5
- onAnnoEvent: any;
6
- onKeyDown: any;
7
- onKeyUp: any;
5
+ onAnnoEvent: import('@vitest/spy').Mock<(...args: any[]) => any>;
6
+ onKeyDown: import('@vitest/spy').Mock<(...args: any[]) => any>;
7
+ onKeyUp: import('@vitest/spy').Mock<(...args: any[]) => any>;
8
8
  };
9
9
  declare const meta: {
10
10
  title: string;
@@ -2,9 +2,9 @@ import { StoryObj } from '@storybook/react';
2
2
  import { default as AnnotationTool } from '../../models/AnnotationTool';
3
3
  import { UiConfig } from '../../types';
4
4
  export declare const ActionsData: {
5
- onAnnoEvent: any;
6
- onKeyDown: any;
7
- onKeyUp: any;
5
+ onAnnoEvent: import('@vitest/spy').Mock<(...args: any[]) => any>;
6
+ onKeyDown: import('@vitest/spy').Mock<(...args: any[]) => any>;
7
+ onKeyUp: import('@vitest/spy').Mock<(...args: any[]) => any>;
8
8
  };
9
9
  declare const meta: {
10
10
  title: string;
@@ -22,9 +22,9 @@ declare const meta: {
22
22
  tags: string[];
23
23
  excludeStories: RegExp;
24
24
  args: {
25
- onAnnoEvent: any;
26
- onKeyDown: any;
27
- onKeyUp: any;
25
+ onAnnoEvent: import('@vitest/spy').Mock<(...args: any[]) => any>;
26
+ onKeyDown: import('@vitest/spy').Mock<(...args: any[]) => any>;
27
+ onKeyUp: import('@vitest/spy').Mock<(...args: any[]) => any>;
28
28
  };
29
29
  };
30
30
  export default meta;
@@ -0,0 +1,52 @@
1
+ import { StoryObj } from '@storybook/react';
2
+ export declare const ActionsData: {};
3
+ declare const meta: {
4
+ title: string;
5
+ component: ({ image, annotations: propAnnotations, possibleLabels, isJunk, uiConfig: propUiConfig, enableZoom, canSelect, onSelectAnnotation, }: {
6
+ image: string;
7
+ annotations?: import('../..').ExternalAnnotation[];
8
+ possibleLabels: import('../..').Label[];
9
+ isJunk?: boolean;
10
+ uiConfig?: Partial<import('../..').UiConfig>;
11
+ enableZoom?: boolean;
12
+ canSelect?: boolean;
13
+ onSelectAnnotation?: (annotation?: import('../../models').Annotation) => void;
14
+ }) => import("react/jsx-runtime").JSX.Element;
15
+ parameters: {
16
+ layout: string;
17
+ };
18
+ tags: string[];
19
+ excludeStories: RegExp;
20
+ args: {};
21
+ };
22
+ export default meta;
23
+ type Story = StoryObj<typeof meta>;
24
+ /**
25
+ * SiaViewer with point annotations. No toolbar, no editing.
26
+ * Try clicking annotations to select them and scrolling to zoom.
27
+ */
28
+ export declare const WithPointAnnotations: Story;
29
+ /**
30
+ * SiaViewer with bbox annotations.
31
+ */
32
+ export declare const WithBBoxAnnotations: Story;
33
+ /**
34
+ * SiaViewer with line annotations.
35
+ */
36
+ export declare const WithLineAnnotations: Story;
37
+ /**
38
+ * SiaViewer with polygon annotations.
39
+ */
40
+ export declare const WithPolygonAnnotations: Story;
41
+ /**
42
+ * SiaViewer with zoom disabled.
43
+ */
44
+ export declare const ZoomDisabled: Story;
45
+ /**
46
+ * SiaViewer with selection disabled. Annotations cannot be clicked.
47
+ */
48
+ export declare const SelectionDisabled: Story;
49
+ /**
50
+ * SiaViewer marked as junk.
51
+ */
52
+ export declare const Junk: Story;
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.2.0-alpha0",
4
4
  "description": "Single Image Annotation Tool",
5
5
  "license": "MIT",
6
6
  "repository": "l3p-cv/lost-sia",
@@ -63,9 +63,7 @@
63
63
  "react": "^19.2.1",
64
64
  "react-dom": "^19.2.1",
65
65
  "react-draggable": "^4.5.0",
66
- "sass": "^1.95.0",
67
- "semantic-ui-css": "2.5.0",
68
- "semantic-ui-react": "^2.0.3"
66
+ "sass": "^1.95.0"
69
67
  },
70
68
  "peerDependencies": {
71
69
  "@coreui/react": "^5.9.1",
@@ -81,6 +79,7 @@
81
79
  "@storybook/addon-links": "^10.1.6",
82
80
  "@storybook/react": "^10.1.6",
83
81
  "@storybook/react-vite": "^10.1.6",
82
+ "@storybook/test": "^8.6.15",
84
83
  "@types/react": "^19.2.7",
85
84
  "@typescript-eslint/eslint-plugin": "^8.46.1",
86
85
  "@typescript-eslint/parser": "^8.46.1",
@@ -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
  }
@@ -761,7 +780,9 @@ const Canvas = ({
761
780
  onSelectAnnotation(percentagedAnnotation)
762
781
 
763
782
  // get top left point of annotation
764
- setLabelInputPosition(getAnnoTopLeftPagePosition(annotation.coordinates))
783
+ if (annotationSettings.canLabel) {
784
+ setLabelInputPosition(getAnnoTopLeftPagePosition(annotation.coordinates))
785
+ }
765
786
  }
766
787
 
767
788
  const handleOnAnnoChanged = (annotation: Annotation) => {
@@ -900,6 +921,7 @@ const Canvas = ({
900
921
  flexDirection: 'column',
901
922
  }}
902
923
  >
924
+ {annotationSettings.canLabel && (
903
925
  <div
904
926
  style={{
905
927
  position: 'absolute',
@@ -954,6 +976,7 @@ const Canvas = ({
954
976
  }}
955
977
  />
956
978
  </div>
979
+ )}
957
980
 
958
981
  {isImageJunk && (
959
982
  <div
package/src/Sia.tsx CHANGED
@@ -410,7 +410,7 @@ const Sia = ({
410
410
  position: 'fixed',
411
411
  top: 0,
412
412
  left: 0,
413
- zIndex: 6000,
413
+ zIndex: 1040,
414
414
  backgroundColor: '#ffff',
415
415
  width: '100%',
416
416
  height: '100%',
@@ -0,0 +1,125 @@
1
+ import { CSSProperties, useEffect, useState, WheelEvent } from 'react'
2
+ import Canvas from './Canvas/Canvas'
3
+ import Annotation from './Annotation/logic/Annotation'
4
+ import AnnotationMode from './models/AnnotationMode'
5
+ import AnnotationTool from './models/AnnotationTool'
6
+ import { AnnotationSettings, ExternalAnnotation, Label, UiConfig } from './types'
7
+
8
+ type SiaViewerProps = {
9
+ image: string
10
+ annotations?: ExternalAnnotation[]
11
+ possibleLabels: Label[]
12
+ isJunk?: boolean
13
+ uiConfig?: Partial<UiConfig>
14
+ enableZoom?: boolean
15
+ canSelect?: boolean
16
+ onSelectAnnotation?: (annotation?: Annotation) => void
17
+ }
18
+
19
+ const VIEW_ANNOTATION_SETTINGS: AnnotationSettings = {
20
+ canCreate: false,
21
+ canEdit: false,
22
+ canLabel: false,
23
+ canHaveMultipleLabels: false,
24
+ }
25
+
26
+ const DEFAULT_UI_CONFIG: UiConfig = {
27
+ nodeRadius: 4,
28
+ strokeWidth: 4,
29
+ imageCentered: false,
30
+ }
31
+
32
+ const containerStyle: CSSProperties = {
33
+ flex: '1 1 auto',
34
+ minHeight: 0,
35
+ display: 'flex',
36
+ flexDirection: 'column',
37
+ }
38
+
39
+ /**
40
+ * View-only SIA component. Renders image + annotations without a toolbar and
41
+ * blocks all mutations (create / edit / delete / label). Zoom and selection
42
+ * can be toggled via props.
43
+ */
44
+ const SiaViewer = ({
45
+ image,
46
+ annotations: propAnnotations,
47
+ possibleLabels,
48
+ isJunk = false,
49
+ uiConfig: propUiConfig,
50
+ enableZoom = true,
51
+ canSelect = true,
52
+ onSelectAnnotation = () => {},
53
+ }: SiaViewerProps) => {
54
+ const [annotations, setAnnotations] = useState<Annotation[]>([])
55
+ const [selectedAnnotation, setSelectedAnnotation] = useState<
56
+ Annotation | undefined
57
+ >()
58
+ const [uiConfig, setUiConfig] = useState<UiConfig>(DEFAULT_UI_CONFIG)
59
+
60
+ // (re)initialize annotations whenever the annotations or image prop changes
61
+ useEffect(() => {
62
+ if (image === undefined || propAnnotations === undefined) {
63
+ setAnnotations([])
64
+ setSelectedAnnotation(undefined)
65
+ return
66
+ }
67
+
68
+ let internalId = 0
69
+ const next: Annotation[] = propAnnotations.map((externalAnno) => ({
70
+ ...externalAnno,
71
+ internalId: internalId++,
72
+ mode: AnnotationMode.VIEW,
73
+ selectedNode: 1,
74
+ status: externalAnno.status,
75
+ annoTime: externalAnno.annoTime ?? 0,
76
+ }))
77
+
78
+ setAnnotations(next)
79
+ setSelectedAnnotation(undefined)
80
+ }, [propAnnotations, image])
81
+
82
+ useEffect(() => {
83
+ setUiConfig({ ...DEFAULT_UI_CONFIG, ...propUiConfig })
84
+ }, [propUiConfig])
85
+
86
+ const stopZoom = (e: WheelEvent) => {
87
+ if (!enableZoom) e.stopPropagation()
88
+ }
89
+
90
+ return (
91
+ <div style={containerStyle} onWheelCapture={stopZoom}>
92
+ <Canvas
93
+ annotations={annotations}
94
+ annotationSettings={VIEW_ANNOTATION_SETTINGS}
95
+ image={image}
96
+ isImageJunk={isJunk}
97
+ isPolygonSelectionMode={false}
98
+ possibleLabels={possibleLabels}
99
+ selectedAnnotation={canSelect ? selectedAnnotation : undefined}
100
+ selectedAnnoTool={AnnotationTool.Point}
101
+ uiConfig={uiConfig}
102
+ onAnnoCreated={() => {}}
103
+ onAnnoChanged={() => {}}
104
+ onAnnoCreationFinished={() => {}}
105
+ onAnnoEditing={() => {}}
106
+ onNotification={() => {}}
107
+ onRequestNewAnnoId={() => 0}
108
+ onSelectAnnotation={
109
+ canSelect
110
+ ? (annotation) => {
111
+ setSelectedAnnotation(annotation)
112
+ onSelectAnnotation(annotation)
113
+ }
114
+ : () => {}
115
+ }
116
+ onSetIsImageJunk={() => {}}
117
+ onSetSelectedTool={() => {}}
118
+ onShouldDeleteAnno={() => {}}
119
+ onTraverseAnnotationHistory={() => {}}
120
+ />
121
+ </div>
122
+ )
123
+ }
124
+
125
+ export default SiaViewer