lost-sia 3.0.0 → 3.1.0
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/logic/Annotation.js +1 -1
- package/dist/Annotation/ui/AnnotationComponent.js +1 -1
- package/dist/Annotation/ui/atoms/AnnoBar.js +1 -1
- package/dist/Annotation/ui/tools/Point.js +1 -1
- package/dist/Canvas/Canvas.d.ts +2 -1
- package/dist/Canvas/Canvas.js +1 -1
- package/dist/Canvas/LabelInput.js +1 -1
- package/dist/IconButton.js +1 -1
- package/dist/Sia.d.ts +4 -3
- package/dist/Sia.js +1 -1
- package/dist/assets/node_modules/semantic-ui-css/semantic.min-09YPtVE6.css +1 -0
- package/dist/stories/AnnotationTools.stories.d.ts +2 -1
- package/dist/stories/Canvas/Canvas.stories.d.ts +5 -4
- package/dist/stories/Canvas/CanvasWithOffset.stories.d.ts +6 -6
- package/dist/stories/MinimalSia.stories.d.ts +3 -1
- package/dist/stories/SIA/SIA.stories.d.ts +3 -1
- package/dist/types.d.ts +5 -0
- package/dist/utils/KeyMapper.js +1 -1
- package/package.json +20 -22
- package/src/Annotation/ui/atoms/AnnoBar.tsx +3 -1
- package/src/Canvas/Canvas.tsx +4 -2
- package/src/Canvas/LabelInput.tsx +1 -1
- package/src/Sia.tsx +116 -2
- package/src/types.ts +6 -0
- package/dist/assets/node_modules/semantic-ui-css/semantic.min-Bvulf91l.css +0 -346
|
@@ -3,7 +3,7 @@ import { AllowedTools } from '../types';
|
|
|
3
3
|
export declare const ActionsData: {};
|
|
4
4
|
declare const meta: {
|
|
5
5
|
title: string;
|
|
6
|
-
component: ({ additionalButtons, allowedTools: propAllowedTools, polygonOperationResult, annotationSettings: propAnnotationSettings, uiConfig: propUiConfig, defaultAnnotationTool, defaultLabelId, image, isLoading, isPolygonSelectionMode, initialAnnotations, initialImageLabelIds, initialIsImageJunk, possibleLabels, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoDeleted, onImageLabelsChanged, onIsImageJunk, onNotification, onSelectAnnotation, }: {
|
|
6
|
+
component: ({ additionalButtons, allowedTools: propAllowedTools, polygonOperationResult, annotationSettings: propAnnotationSettings, uiConfig: propUiConfig, defaultAnnotationTool, defaultLabelId, image, isLoading, isPolygonSelectionMode, initialAnnotations, initialImageLabelIds, initialIsImageJunk, possibleLabels, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoDeleted, onImageLabelsChanged, onIsImageJunk, onNotification, onSelectAnnotation, onTimeTravel, }: {
|
|
7
7
|
additionalButtons?: import('react').ReactElement;
|
|
8
8
|
allowedTools?: AllowedTools;
|
|
9
9
|
polygonOperationResult?: import('..').PolygonOperationResult;
|
|
@@ -26,6 +26,7 @@ declare const meta: {
|
|
|
26
26
|
onIsImageJunk?: (isJunk: boolean) => void;
|
|
27
27
|
onNotification?: (notification: import('..').SIANotification) => void;
|
|
28
28
|
onSelectAnnotation?: (annotation: import('../models').Annotation) => void;
|
|
29
|
+
onTimeTravel?: (timeTravelAction: import('..').TimeTravelChanges) => void;
|
|
29
30
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
30
31
|
parameters: {
|
|
31
32
|
layout: string;
|
|
@@ -2,13 +2,13 @@ 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:
|
|
6
|
-
onKeyDown:
|
|
7
|
-
onKeyUp:
|
|
5
|
+
onAnnoEvent: any;
|
|
6
|
+
onKeyDown: any;
|
|
7
|
+
onKeyUp: any;
|
|
8
8
|
};
|
|
9
9
|
declare const meta: {
|
|
10
10
|
title: string;
|
|
11
|
-
component: ({ annotations, annotationSettings, defaultLabelId, image, isFullscreen, isImageJunk, isPolygonSelectionMode, polygonOperationResult, possibleLabels, preventScrolling, selectedAnnotation, selectedAnnoTool, toolbarHeight, uiConfig, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoEditing, onNotification, onRequestNewAnnoId, onSelectAnnotation, onSetIsImageJunk, onSetSelectedTool, onShouldDeleteAnno, }: {
|
|
11
|
+
component: ({ annotations, annotationSettings, defaultLabelId, image, isFullscreen, isImageJunk, isPolygonSelectionMode, polygonOperationResult, possibleLabels, preventScrolling, selectedAnnotation, selectedAnnoTool, toolbarHeight, uiConfig, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoEditing, onNotification, onRequestNewAnnoId, onSelectAnnotation, onSetIsImageJunk, onSetSelectedTool, onShouldDeleteAnno, onTraverseAnnotationHistory, }: {
|
|
12
12
|
annotations?: import('../../models').Annotation[];
|
|
13
13
|
annotationSettings: AnnotationSettings;
|
|
14
14
|
defaultLabelId?: number;
|
|
@@ -33,6 +33,7 @@ declare const meta: {
|
|
|
33
33
|
onSetIsImageJunk: (newJunkState: boolean) => void;
|
|
34
34
|
onSetSelectedTool: (tool: AnnotationTool) => void;
|
|
35
35
|
onShouldDeleteAnno: (internalAnnoId: number) => void;
|
|
36
|
+
onTraverseAnnotationHistory: (isUndo: boolean) => void;
|
|
36
37
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
37
38
|
parameters: {
|
|
38
39
|
layout: 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:
|
|
6
|
-
onKeyDown:
|
|
7
|
-
onKeyUp:
|
|
5
|
+
onAnnoEvent: any;
|
|
6
|
+
onKeyDown: any;
|
|
7
|
+
onKeyUp: 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:
|
|
26
|
-
onKeyDown:
|
|
27
|
-
onKeyUp:
|
|
25
|
+
onAnnoEvent: any;
|
|
26
|
+
onKeyDown: any;
|
|
27
|
+
onKeyUp: any;
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
30
|
export default meta;
|
|
@@ -3,7 +3,7 @@ import { Label } from '../types';
|
|
|
3
3
|
export declare const ActionsData: {};
|
|
4
4
|
declare const meta: {
|
|
5
5
|
title: string;
|
|
6
|
-
component: ({ additionalButtons, allowedTools: propAllowedTools, polygonOperationResult, annotationSettings: propAnnotationSettings, uiConfig: propUiConfig, defaultAnnotationTool, defaultLabelId, image, isLoading, isPolygonSelectionMode, initialAnnotations, initialImageLabelIds, initialIsImageJunk, possibleLabels, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoDeleted, onImageLabelsChanged, onIsImageJunk, onNotification, onSelectAnnotation, }: {
|
|
6
|
+
component: ({ additionalButtons, allowedTools: propAllowedTools, polygonOperationResult, annotationSettings: propAnnotationSettings, uiConfig: propUiConfig, defaultAnnotationTool, defaultLabelId, image, isLoading, isPolygonSelectionMode, initialAnnotations, initialImageLabelIds, initialIsImageJunk, possibleLabels, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoDeleted, onImageLabelsChanged, onIsImageJunk, onNotification, onSelectAnnotation, onTimeTravel, }: {
|
|
7
7
|
additionalButtons?: import('react').ReactElement;
|
|
8
8
|
allowedTools?: import('..').AllowedTools;
|
|
9
9
|
polygonOperationResult?: import('..').PolygonOperationResult;
|
|
@@ -26,6 +26,7 @@ declare const meta: {
|
|
|
26
26
|
onIsImageJunk?: (isJunk: boolean) => void;
|
|
27
27
|
onNotification?: (notification: import('..').SIANotification) => void;
|
|
28
28
|
onSelectAnnotation?: (annotation: import('../models').Annotation) => void;
|
|
29
|
+
onTimeTravel?: (timeTravelAction: import('..').TimeTravelChanges) => void;
|
|
29
30
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
30
31
|
parameters: {
|
|
31
32
|
layout: string;
|
|
@@ -56,6 +57,7 @@ declare const meta: {
|
|
|
56
57
|
onIsImageJunk?: (isJunk: boolean) => void;
|
|
57
58
|
onNotification?: (notification: import('..').SIANotification) => void;
|
|
58
59
|
onSelectAnnotation?: (annotation: import('../models').Annotation) => void;
|
|
60
|
+
onTimeTravel?: (timeTravelAction: import('..').TimeTravelChanges) => void;
|
|
59
61
|
}>) => import("react/jsx-runtime").JSX.Element)[];
|
|
60
62
|
};
|
|
61
63
|
export default meta;
|
|
@@ -3,7 +3,7 @@ import { UiConfig } from '../../types';
|
|
|
3
3
|
export declare const ActionsData: {};
|
|
4
4
|
declare const meta: {
|
|
5
5
|
title: string;
|
|
6
|
-
component: ({ additionalButtons, allowedTools: propAllowedTools, polygonOperationResult, annotationSettings: propAnnotationSettings, uiConfig: propUiConfig, defaultAnnotationTool, defaultLabelId, image, isLoading, isPolygonSelectionMode, initialAnnotations, initialImageLabelIds, initialIsImageJunk, possibleLabels, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoDeleted, onImageLabelsChanged, onIsImageJunk, onNotification, onSelectAnnotation, }: {
|
|
6
|
+
component: ({ additionalButtons, allowedTools: propAllowedTools, polygonOperationResult, annotationSettings: propAnnotationSettings, uiConfig: propUiConfig, defaultAnnotationTool, defaultLabelId, image, isLoading, isPolygonSelectionMode, initialAnnotations, initialImageLabelIds, initialIsImageJunk, possibleLabels, onAnnoCreated, onAnnoCreationFinished, onAnnoChanged, onAnnoDeleted, onImageLabelsChanged, onIsImageJunk, onNotification, onSelectAnnotation, onTimeTravel, }: {
|
|
7
7
|
additionalButtons?: import('react').ReactElement;
|
|
8
8
|
allowedTools?: import('../..').AllowedTools;
|
|
9
9
|
polygonOperationResult?: import('../..').PolygonOperationResult;
|
|
@@ -26,6 +26,7 @@ declare const meta: {
|
|
|
26
26
|
onIsImageJunk?: (isJunk: boolean) => void;
|
|
27
27
|
onNotification?: (notification: import('../..').SIANotification) => void;
|
|
28
28
|
onSelectAnnotation?: (annotation: import('../../models').Annotation) => void;
|
|
29
|
+
onTimeTravel?: (timeTravelAction: import('../..').TimeTravelChanges) => void;
|
|
29
30
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
30
31
|
parameters: {
|
|
31
32
|
layout: string;
|
|
@@ -56,6 +57,7 @@ declare const meta: {
|
|
|
56
57
|
onIsImageJunk?: (isJunk: boolean) => void;
|
|
57
58
|
onNotification?: (notification: import('../..').SIANotification) => void;
|
|
58
59
|
onSelectAnnotation?: (annotation: import('../../models').Annotation) => void;
|
|
60
|
+
onTimeTravel?: (timeTravelAction: import('../..').TimeTravelChanges) => void;
|
|
59
61
|
}>) => import("react/jsx-runtime").JSX.Element)[];
|
|
60
62
|
};
|
|
61
63
|
export default meta;
|
package/dist/types.d.ts
CHANGED
package/dist/utils/KeyMapper.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import e from"../models/KeyAction.js";class c{isControlDown=!1;keyActionHandler;constructor(t=void 0){this.keyActionHandler=t}keyDown(t,r=!1,i=!1){switch(t){case"Enter":this.triggerKeyAction(e.EDIT_LABEL);break;case"Delete":this.triggerKeyAction(e.DELETE_ANNO);break;case"Backspace":this.triggerKeyAction(e.DELETE_ANNO);break;case"z":i&&this.triggerKeyAction(e.UNDO);break;case"y":i&&this.triggerKeyAction(e.REDO);break;case"Tab":r?this.triggerKeyAction(e.TRAVERSE_ANNOS_BACKWARDS):this.triggerKeyAction(e.TRAVERSE_ANNOS);break;case"w":this.triggerKeyAction(e.CAM_MOVE_UP);break;case"s":this.triggerKeyAction(e.CAM_MOVE_DOWN);break;case"a":this.triggerKeyAction(e.CAM_MOVE_LEFT);break;case"d":this.triggerKeyAction(e.CAM_MOVE_RIGHT);break;case"e":this.triggerKeyAction(e.RECREATE_ANNO);break;case"j":this.triggerKeyAction(e.TOGGLE_IMAGE_JUNK);break;case"c":i?this.triggerKeyAction(e.COPY_ANNOTATION):this.triggerKeyAction(e.TOGGLE_ANNO_COMMENT_INPUT);break;case"v":i&&this.triggerKeyAction(e.PASTE_ANNOTATION);break;case"Escape":this.triggerKeyAction(e.DELETE_ANNO_IN_CREATION);break;default:return!1}return!0}triggerKeyAction(t){this.keyActionHandler&&this.keyActionHandler(t)}}export{c as default};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lost-sia",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Single Image Annotation Tool",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "l3p-cv/lost-sia",
|
|
@@ -57,36 +57,34 @@
|
|
|
57
57
|
"pretty": "prettier --write \"./**/*.{js,jsx,mjs,cjs,ts,tsx,json}\""
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@fortawesome/free-regular-svg-icons": "^
|
|
61
|
-
"@fortawesome/free-solid-svg-icons": "^
|
|
62
|
-
"@fortawesome/react-fontawesome": "^
|
|
63
|
-
"react": "^
|
|
64
|
-
"react-dom": "^
|
|
65
|
-
"react-draggable": "^4.
|
|
66
|
-
"sass": "^1.
|
|
60
|
+
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
|
61
|
+
"@fortawesome/free-solid-svg-icons": "^7.1.0",
|
|
62
|
+
"@fortawesome/react-fontawesome": "^3.1.1",
|
|
63
|
+
"react": "^19.2.1",
|
|
64
|
+
"react-dom": "^19.2.1",
|
|
65
|
+
"react-draggable": "^4.5.0",
|
|
66
|
+
"sass": "^1.95.0",
|
|
67
67
|
"semantic-ui-css": "2.5.0",
|
|
68
68
|
"semantic-ui-react": "^2.0.3"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"@coreui/react": "^5.9.1",
|
|
72
72
|
"prop-types": "^15.5.4",
|
|
73
|
-
"react": "^
|
|
74
|
-
"react-dom": "^
|
|
73
|
+
"react": "^19.2.1",
|
|
74
|
+
"react-dom": "^19.2.1"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
77
|
"@eslint/eslintrc": "^3.3.1",
|
|
78
78
|
"@eslint/js": "^9.39.1",
|
|
79
79
|
"@microsoft/api-extractor": "^7.52.13",
|
|
80
|
-
"@storybook/addon-docs": "^
|
|
81
|
-
"@storybook/addon-links": "^
|
|
82
|
-
"@storybook/react": "^
|
|
83
|
-
"@storybook/react-vite": "^
|
|
84
|
-
"@
|
|
85
|
-
"@types/react": "^19.1.16",
|
|
80
|
+
"@storybook/addon-docs": "^10.1.6",
|
|
81
|
+
"@storybook/addon-links": "^10.1.6",
|
|
82
|
+
"@storybook/react": "^10.1.6",
|
|
83
|
+
"@storybook/react-vite": "^10.1.6",
|
|
84
|
+
"@types/react": "^19.2.7",
|
|
86
85
|
"@typescript-eslint/eslint-plugin": "^8.46.1",
|
|
87
86
|
"@typescript-eslint/parser": "^8.46.1",
|
|
88
|
-
"@vitejs/plugin-react": "^
|
|
89
|
-
"cross-env": "^7.0.3",
|
|
87
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
90
88
|
"eslint": "^9.39.1",
|
|
91
89
|
"eslint-config-standard": "^17.1.0",
|
|
92
90
|
"eslint-config-standard-react": "^13.0.0",
|
|
@@ -96,15 +94,15 @@
|
|
|
96
94
|
"eslint-plugin-react": "^7.37.5",
|
|
97
95
|
"eslint-plugin-standard": "^5.0.0",
|
|
98
96
|
"eslint-plugin-storybook": "^10.0.7",
|
|
99
|
-
"glob": "^
|
|
97
|
+
"glob": "^13.0.0",
|
|
100
98
|
"lodash-es": "^4.17.21",
|
|
101
99
|
"prettier": "^3.3.3",
|
|
102
100
|
"react-redux": "^9.1.2",
|
|
103
101
|
"redux": "^5.0.1",
|
|
104
|
-
"storybook": "^
|
|
102
|
+
"storybook": "^10.1.6",
|
|
105
103
|
"unplugin-dts": "^1.0.0-beta.6",
|
|
106
|
-
"vite": "^
|
|
107
|
-
"vitest": "^
|
|
104
|
+
"vite": "^7.2.7",
|
|
105
|
+
"vitest": "^4.0.15"
|
|
108
106
|
},
|
|
109
107
|
"eslintConfig": {
|
|
110
108
|
"extends": [
|
|
@@ -48,7 +48,9 @@ const AnnoBar = ({
|
|
|
48
48
|
useEffect(() => {
|
|
49
49
|
// get the most top left point from the annotation
|
|
50
50
|
const topPoints: Point[] = transform.getTopPoint(annotationCoordinates)
|
|
51
|
-
const
|
|
51
|
+
const newTopLeftPoints: Point[] = transform.getMostLeftPoints(topPoints)
|
|
52
|
+
const newTopLeftPoint: Point =
|
|
53
|
+
newTopLeftPoints.length > 0 ? newTopLeftPoints[0] : { x: 0, y: 0 }
|
|
52
54
|
|
|
53
55
|
setTopLeftPoint(newTopLeftPoint)
|
|
54
56
|
|
package/src/Canvas/Canvas.tsx
CHANGED
|
@@ -63,6 +63,7 @@ type CanvasProps = {
|
|
|
63
63
|
onSetIsImageJunk: (newJunkState: boolean) => void
|
|
64
64
|
onSetSelectedTool: (tool: AnnotationTool) => void
|
|
65
65
|
onShouldDeleteAnno: (internalAnnoId: number) => void
|
|
66
|
+
onTraverseAnnotationHistory: (isUndo: boolean) => void
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
const Canvas = ({
|
|
@@ -92,6 +93,7 @@ const Canvas = ({
|
|
|
92
93
|
onSetIsImageJunk,
|
|
93
94
|
onSetSelectedTool = (_) => {},
|
|
94
95
|
onShouldDeleteAnno,
|
|
96
|
+
onTraverseAnnotationHistory,
|
|
95
97
|
}: CanvasProps) => {
|
|
96
98
|
const [editorMode, setEditorMode] = useState<EditorModes>(EditorModes.VIEW)
|
|
97
99
|
const [annoTimestamp, setAnnoTimestamp] = useState<number | undefined>()
|
|
@@ -360,10 +362,10 @@ const Canvas = ({
|
|
|
360
362
|
console.log('KeyAction TODO: LEAVE_ANNO_ADD_MODE')
|
|
361
363
|
break
|
|
362
364
|
case KeyAction.UNDO:
|
|
363
|
-
|
|
365
|
+
onTraverseAnnotationHistory(true)
|
|
364
366
|
break
|
|
365
367
|
case KeyAction.REDO:
|
|
366
|
-
|
|
368
|
+
onTraverseAnnotationHistory(false)
|
|
367
369
|
break
|
|
368
370
|
case KeyAction.TRAVERSE_ANNOS:
|
|
369
371
|
traverseAnnos()
|
|
@@ -67,7 +67,7 @@ const LabelInput = ({
|
|
|
67
67
|
</CPopover>
|
|
68
68
|
|
|
69
69
|
<CDropdown visible={isVisible} autoClose={false} style={{ marginTop: -25 }}>
|
|
70
|
-
{/* this invisible toggle has to be here,
|
|
70
|
+
{/* this invisible toggle has to be here, otherwise the menu is not showing as intended */}
|
|
71
71
|
<CDropdownToggle style={{ display: 'none' }} />
|
|
72
72
|
<CDropdownMenu>
|
|
73
73
|
<div className="px-3 py-2">
|
package/src/Sia.tsx
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
Label,
|
|
14
14
|
PolygonOperationResult,
|
|
15
15
|
SIANotification,
|
|
16
|
+
TimeTravelChanges,
|
|
16
17
|
UiConfig,
|
|
17
18
|
} from './types'
|
|
18
19
|
|
|
@@ -39,12 +40,13 @@ type SiaProps = {
|
|
|
39
40
|
onIsImageJunk?: (isJunk: boolean) => void
|
|
40
41
|
onNotification?: (notification: SIANotification) => void
|
|
41
42
|
onSelectAnnotation?: (annotation: Annotation) => void
|
|
43
|
+
onTimeTravel?: (timeTravelAction: TimeTravelChanges) => void
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
/**
|
|
45
47
|
* Main SIA component
|
|
46
48
|
*/
|
|
47
|
-
const
|
|
49
|
+
const Sia = ({
|
|
48
50
|
additionalButtons,
|
|
49
51
|
allowedTools: propAllowedTools,
|
|
50
52
|
polygonOperationResult = { annotationsToDelete: [], polygonsToCreate: [] },
|
|
@@ -67,6 +69,7 @@ const Sia2 = ({
|
|
|
67
69
|
onIsImageJunk = () => {},
|
|
68
70
|
onNotification = (_) => {},
|
|
69
71
|
onSelectAnnotation = (_) => {},
|
|
72
|
+
onTimeTravel = (_) => {},
|
|
70
73
|
}: SiaProps) => {
|
|
71
74
|
const marginBetweenToolbarAndContainerPixels: number = 10
|
|
72
75
|
|
|
@@ -78,6 +81,12 @@ const Sia2 = ({
|
|
|
78
81
|
const [annotations, setAnnotations] = useState<Annotation[]>([])
|
|
79
82
|
const [annotationSettings, setAnnotationSettings] = useState<AnnotationSettings>()
|
|
80
83
|
|
|
84
|
+
// tracks how far we went back in the history
|
|
85
|
+
const [annotationHistoryIndex, setAnnotationHistoryIndex] = useState<
|
|
86
|
+
number | undefined
|
|
87
|
+
>()
|
|
88
|
+
const [annotationHistory, setAnnotationHistory] = useState<Annotation[][]>([])
|
|
89
|
+
|
|
81
90
|
const [uiConfig, setUiConfig] = useState<UiConfig>()
|
|
82
91
|
|
|
83
92
|
const [selectedAnnotation, setSelectedAnnotation] = useState<Annotation>()
|
|
@@ -86,6 +95,26 @@ const Sia2 = ({
|
|
|
86
95
|
defaultAnnotationTool ?? AnnotationTool.Point,
|
|
87
96
|
)
|
|
88
97
|
|
|
98
|
+
const updateAnnotationHistory = (annotations: Annotation[]) => {
|
|
99
|
+
const _annotations = [...annotations]
|
|
100
|
+
const _annotationHistory = [...annotationHistory]
|
|
101
|
+
|
|
102
|
+
// user did some changes from within the past
|
|
103
|
+
// time to create an alternative timeline and delete the original one
|
|
104
|
+
if (annotationHistoryIndex !== undefined) {
|
|
105
|
+
// remove everything after the state the user is
|
|
106
|
+
_annotationHistory.splice(annotationHistoryIndex + 1)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// update the list with out latest change (it is always living in the present)
|
|
110
|
+
_annotationHistory.push(_annotations)
|
|
111
|
+
|
|
112
|
+
// keep history index marker in the present
|
|
113
|
+
setAnnotationHistoryIndex(undefined)
|
|
114
|
+
|
|
115
|
+
setAnnotationHistory(_annotationHistory)
|
|
116
|
+
}
|
|
117
|
+
|
|
89
118
|
// for adjusting the container/canvas size
|
|
90
119
|
// const [toolbarHeight, setToolbarHeight] = useState<number>(-1);
|
|
91
120
|
|
|
@@ -121,6 +150,7 @@ const Sia2 = ({
|
|
|
121
150
|
|
|
122
151
|
setAnnotations(_annotations)
|
|
123
152
|
setSelectedAnnotation(undefined)
|
|
153
|
+
updateAnnotationHistory(_annotations)
|
|
124
154
|
|
|
125
155
|
// inform the outside world about our changes
|
|
126
156
|
onAnnoDeleted(removedAnno, _annotations)
|
|
@@ -159,6 +189,7 @@ const Sia2 = ({
|
|
|
159
189
|
setUsedInternalIds([...new Array(internalAnnoId).keys()])
|
|
160
190
|
|
|
161
191
|
setAnnotations(_annotations)
|
|
192
|
+
updateAnnotationHistory(_annotations)
|
|
162
193
|
}
|
|
163
194
|
|
|
164
195
|
const createNewInternalAnnotationId = (): number => {
|
|
@@ -206,11 +237,86 @@ const Sia2 = ({
|
|
|
206
237
|
onIsImageJunk(newJunkState)
|
|
207
238
|
}
|
|
208
239
|
|
|
240
|
+
const handleTraverseAnnotationHistory = (isUndo: boolean) => {
|
|
241
|
+
// undefined -> last element
|
|
242
|
+
const _annotationHistoryIndex = annotationHistoryIndex ?? annotationHistory.length - 1
|
|
243
|
+
|
|
244
|
+
const isPresent = _annotationHistoryIndex == annotationHistory.length - 1
|
|
245
|
+
const isFirst = _annotationHistoryIndex == 0
|
|
246
|
+
|
|
247
|
+
// we cannot go into the future (yet) or past the past
|
|
248
|
+
if ((isPresent && !isUndo) || (isFirst && isUndo)) return
|
|
249
|
+
|
|
250
|
+
// request time travel using state update
|
|
251
|
+
const newHistoryIndex = _annotationHistoryIndex + (isUndo ? -1 : 1)
|
|
252
|
+
setAnnotationHistoryIndex(newHistoryIndex)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const getTimeTravelInductedChanges = (
|
|
256
|
+
timeTravelledAnnotations: Annotation[],
|
|
257
|
+
): TimeTravelChanges => {
|
|
258
|
+
const addedAnnotations: Annotation[] = []
|
|
259
|
+
const removedAnnotations: Annotation[] = []
|
|
260
|
+
const changedAnnotations: Annotation[] = []
|
|
261
|
+
|
|
262
|
+
// check which items have been added or changed
|
|
263
|
+
for (const travelledAnno of timeTravelledAnnotations) {
|
|
264
|
+
const currentAnno = annotations.find(
|
|
265
|
+
(anno) => anno.internalId === travelledAnno.internalId,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if (!currentAnno) {
|
|
269
|
+
addedAnnotations.push(travelledAnno)
|
|
270
|
+
} else if (JSON.stringify(currentAnno) !== JSON.stringify(travelledAnno)) {
|
|
271
|
+
changedAnnotations.push(travelledAnno)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// check which items have been deleted
|
|
276
|
+
for (const anno of annotations) {
|
|
277
|
+
const travelledAnno = timeTravelledAnnotations.find(
|
|
278
|
+
(tAnno) => tAnno.internalId === anno.internalId,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
if (!travelledAnno) {
|
|
282
|
+
removedAnnotations.push(anno)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return { addedAnnotations, removedAnnotations, changedAnnotations }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
if (
|
|
291
|
+
annotationHistoryIndex == undefined ||
|
|
292
|
+
annotationHistoryIndex < 0 ||
|
|
293
|
+
annotationHistoryIndex > annotationHistory.length - 1
|
|
294
|
+
)
|
|
295
|
+
return
|
|
296
|
+
|
|
297
|
+
// update the shown annotations when we travel in time using the history index
|
|
298
|
+
const refSelectedAnnotationsFromHistory = annotationHistory[annotationHistoryIndex]
|
|
299
|
+
const selectedAnnotationsFromHistory: Annotation[] = [
|
|
300
|
+
...refSelectedAnnotationsFromHistory,
|
|
301
|
+
]
|
|
302
|
+
|
|
303
|
+
setAnnotations(selectedAnnotationsFromHistory)
|
|
304
|
+
|
|
305
|
+
const timeTravelInductedChanges: TimeTravelChanges = getTimeTravelInductedChanges(
|
|
306
|
+
selectedAnnotationsFromHistory,
|
|
307
|
+
)
|
|
308
|
+
onTimeTravel(timeTravelInductedChanges)
|
|
309
|
+
}, [annotationHistoryIndex])
|
|
310
|
+
|
|
209
311
|
useEffect(() => {
|
|
210
312
|
// remove current annotations when the image changes
|
|
211
313
|
if (image === undefined) {
|
|
212
314
|
setAnnotations([])
|
|
213
315
|
setSelectedAnnotation(undefined)
|
|
316
|
+
|
|
317
|
+
// reset time machine
|
|
318
|
+
setAnnotationHistory([])
|
|
319
|
+
setAnnotationHistoryIndex(undefined)
|
|
214
320
|
}
|
|
215
321
|
}, [image])
|
|
216
322
|
|
|
@@ -397,6 +503,7 @@ const Sia2 = ({
|
|
|
397
503
|
setAnnotations(_annotations)
|
|
398
504
|
setSelectedAnnotation(annotation)
|
|
399
505
|
onAnnoCreated(annotation, _annotations)
|
|
506
|
+
// dont update history here - we dont have a finished anno at this point
|
|
400
507
|
}}
|
|
401
508
|
onAnnoChanged={(changedAnno: Annotation) => {
|
|
402
509
|
// update annotation list
|
|
@@ -411,6 +518,11 @@ const Sia2 = ({
|
|
|
411
518
|
_annotations[annoListIndex] = changedAnno
|
|
412
519
|
setAnnotations(_annotations)
|
|
413
520
|
|
|
521
|
+
// only update history for full/finished annotations
|
|
522
|
+
if (changedAnno.status !== AnnotationStatus.CREATING) {
|
|
523
|
+
updateAnnotationHistory(_annotations)
|
|
524
|
+
}
|
|
525
|
+
|
|
414
526
|
// inform the outside world about our change
|
|
415
527
|
onAnnoChanged(changedAnno, _annotations)
|
|
416
528
|
}}
|
|
@@ -457,6 +569,7 @@ const Sia2 = ({
|
|
|
457
569
|
}
|
|
458
570
|
|
|
459
571
|
setAnnotations(_annotations)
|
|
572
|
+
updateAnnotationHistory(_annotations)
|
|
460
573
|
|
|
461
574
|
// mark annotation as fully created
|
|
462
575
|
changedAnno.status = AnnotationStatus.CREATED
|
|
@@ -474,6 +587,7 @@ const Sia2 = ({
|
|
|
474
587
|
}}
|
|
475
588
|
onSetSelectedTool={setSelectedAnnoTool}
|
|
476
589
|
onShouldDeleteAnno={deleteAnnotationByInternalId}
|
|
590
|
+
onTraverseAnnotationHistory={handleTraverseAnnotationHistory}
|
|
477
591
|
/>
|
|
478
592
|
)}
|
|
479
593
|
</div>
|
|
@@ -481,4 +595,4 @@ const Sia2 = ({
|
|
|
481
595
|
)
|
|
482
596
|
}
|
|
483
597
|
|
|
484
|
-
export default
|
|
598
|
+
export default Sia
|