lost-sia 3.2.0-alpha0 → 3.2.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/Canvas/Canvas.js +1 -1
- package/dist/Canvas/LabelInput.js +1 -1
- package/dist/Sia.js +1 -1
- package/dist/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.d.ts +2 -1
- package/dist/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.js +1 -1
- package/dist/Toolbar/ToolbarItems/ImageToolItems/TagLabel.d.ts +2 -1
- package/dist/Toolbar/ToolbarItems/ImageToolItems/TagLabel.js +1 -1
- package/dist/Toolbar/ToolbarItems/ImageTools.js +1 -1
- package/dist/assets/SIA-CoOqcT1a.css +1 -0
- package/dist/assets/brand-icons-Cu_C0hZ4.svg +1008 -0
- package/dist/assets/brand-icons-F3SPCeH1.woff +0 -0
- package/dist/assets/brand-icons-XL9sxUpA.woff2 +0 -0
- package/dist/assets/brand-icons-sqJ2Pg7a.eot +0 -0
- package/dist/assets/brand-icons-ubhWoxly.ttf +0 -0
- package/dist/assets/flags-DOLqOU7Y.png +0 -0
- package/dist/assets/icons-BOCtAERH.woff +0 -0
- package/dist/assets/icons-CHzK1VD9.eot +0 -0
- package/dist/assets/icons-D29ZQHHw.ttf +0 -0
- package/dist/assets/icons-Du6TOHnR.woff2 +0 -0
- package/dist/assets/icons-RwhydX30.svg +1518 -0
- package/dist/assets/node_modules/semantic-ui-css/semantic.min-09YPtVE6.css +1 -0
- package/dist/assets/outline-icons-BfdLr8tr.svg +366 -0
- package/dist/assets/outline-icons-DD8jm0uy.ttf +0 -0
- package/dist/assets/outline-icons-DInHoiqI.woff2 +0 -0
- package/dist/assets/outline-icons-LX8adJ4n.eot +0 -0
- package/dist/assets/outline-icons-aQ88nltS.woff +0 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +1 -1
- package/dist/stories/Canvas/Canvas.stories.d.ts +3 -3
- package/dist/stories/Canvas/CanvasWithOffset.stories.d.ts +6 -6
- package/dist/stories/Toolbar/ImageTools/TagLabel.stories.d.ts +2 -1
- package/package.json +4 -3
- package/src/AnnoExampleViewer.jsx +69 -0
- package/src/Canvas/Canvas.tsx +45 -17
- package/src/Canvas/LabelInput.tsx +11 -9
- package/src/InfoBoxes/AnnoDetails.jsx +165 -0
- package/src/InfoBoxes/AnnoStats.jsx +106 -0
- package/src/InfoBoxes/InfoBox.jsx +76 -0
- package/src/InfoBoxes/InfoBoxArea.jsx +152 -0
- package/src/InfoBoxes/LabelInfo.jsx +107 -0
- package/src/SIA.scss +13 -0
- package/src/SIASettingButton.jsx +126 -0
- package/src/Sia.tsx +70 -54
- package/src/Toolbar/ToolbarItems/ImageToolItems/ImageLabelInput.tsx +74 -24
- package/src/Toolbar/ToolbarItems/ImageToolItems/TagLabel.tsx +33 -0
- package/src/Toolbar/ToolbarItems/ImageTools.tsx +1 -0
- package/src/index.ts +1 -1
- package/src/stories/exampleData/exampleExternalAnnotations.ts +22 -22
- package/dist/SiaViewer.d.ts +0 -19
- package/dist/SiaViewer.js +0 -1
- package/dist/assets/SIA-BV1tYu3P.css +0 -1
- package/dist/stories/SIA/SiaViewer.stories.d.ts +0 -52
- package/src/SiaViewer.tsx +0 -125
- package/src/stories/SIA/SiaViewer.stories.tsx +0 -105
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Component } from 'react'
|
|
2
|
+
import AnnoDetails from './AnnoDetails'
|
|
3
|
+
import AnnoStats from './AnnoStats'
|
|
4
|
+
import LabelInfo from './LabelInfo'
|
|
5
|
+
|
|
6
|
+
class InfoBoxes extends Component {
|
|
7
|
+
constructor(props) {
|
|
8
|
+
super(props)
|
|
9
|
+
this.state = {
|
|
10
|
+
position: {
|
|
11
|
+
top: 0,
|
|
12
|
+
left: 0,
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
componentDidMount() {
|
|
18
|
+
this.updateLayout()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
componentDidUpdate(prevProps) {
|
|
22
|
+
if (this.props.layoutUpdate !== prevProps.layoutUpdate) {
|
|
23
|
+
this.updateLayout()
|
|
24
|
+
}
|
|
25
|
+
if (this.props.commentInputTrigger !== prevProps.commentInputTrigger) {
|
|
26
|
+
if (!this.props.uiConfig.annoDetails.visible) {
|
|
27
|
+
this.showAnnoDetails(true)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
updateLayout() {
|
|
33
|
+
if (this.props.container.current) {
|
|
34
|
+
const container = this.props.container.current.getBoundingClientRect()
|
|
35
|
+
this.setState({
|
|
36
|
+
position: {
|
|
37
|
+
...this.state.position,
|
|
38
|
+
left: container.right - 250,
|
|
39
|
+
top: container.top,
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
showAnnoDetails(show = true) {
|
|
46
|
+
this.props.onUiConfigUpdate({
|
|
47
|
+
...this.props.uiConfig,
|
|
48
|
+
annoDetails: {
|
|
49
|
+
...this.props.uiConfig.annoDetails,
|
|
50
|
+
visible: show,
|
|
51
|
+
},
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
onDismiss(type) {
|
|
56
|
+
if (this.props.onUiConfigUpdate) {
|
|
57
|
+
switch (type) {
|
|
58
|
+
case 'AnnoDetails':
|
|
59
|
+
this.showAnnoDetails(false)
|
|
60
|
+
break
|
|
61
|
+
case 'LabelInfo':
|
|
62
|
+
this.props.onUiConfigUpdate({
|
|
63
|
+
...this.props.uiConfig,
|
|
64
|
+
labelInfo: {
|
|
65
|
+
...this.props.uiConfig.labelInfo,
|
|
66
|
+
visible: false,
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
break
|
|
70
|
+
case 'AnnoStats':
|
|
71
|
+
this.props.onUiConfigUpdate({
|
|
72
|
+
...this.props.uiConfig,
|
|
73
|
+
annoStats: {
|
|
74
|
+
...this.props.uiConfig.annoStats,
|
|
75
|
+
visible: false,
|
|
76
|
+
},
|
|
77
|
+
})
|
|
78
|
+
break
|
|
79
|
+
default:
|
|
80
|
+
break
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
onCommentUpdate(comment) {
|
|
86
|
+
if (this.props.onCommentUpdate) {
|
|
87
|
+
this.props.onCommentUpdate(comment)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
onMarkExample(anno) {
|
|
92
|
+
if (this.props.onMarkExample) {
|
|
93
|
+
this.props.onMarkExample(anno)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
onHideLbl(lbl, hide) {
|
|
98
|
+
if (this.props.onHideLbl) {
|
|
99
|
+
this.props.onHideLbl(lbl, hide)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
render() {
|
|
104
|
+
if (!this.props.annos) return null
|
|
105
|
+
// if (!this.props.selectedAnno) return null
|
|
106
|
+
return (
|
|
107
|
+
<div>
|
|
108
|
+
<LabelInfo
|
|
109
|
+
selectedAnno={this.props.selectedAnno}
|
|
110
|
+
possibleLabels={this.props.possibleLabels}
|
|
111
|
+
defaultPos={this.state.position}
|
|
112
|
+
onDismiss={() => this.onDismiss('LabelInfo')}
|
|
113
|
+
visible={this.props.uiConfig.labelInfo.visible}
|
|
114
|
+
onGetAnnoExample={(exampleArgs) =>
|
|
115
|
+
this.props.onGetAnnoExample ? this.props.onGetAnnoExample(exampleArgs) : {}
|
|
116
|
+
}
|
|
117
|
+
exampleImg={this.props.exampleImg}
|
|
118
|
+
/>
|
|
119
|
+
<AnnoDetails
|
|
120
|
+
anno={this.props.selectedAnno}
|
|
121
|
+
possibleLabels={this.props.possibleLabels}
|
|
122
|
+
defaultPos={{
|
|
123
|
+
left: this.state.position.left - 300,
|
|
124
|
+
top: this.state.position.top,
|
|
125
|
+
}}
|
|
126
|
+
onDismiss={() => this.onDismiss('AnnoDetails')}
|
|
127
|
+
onCommentUpdate={(comment) => this.onCommentUpdate(comment)}
|
|
128
|
+
onMarkExample={(anno) => this.onMarkExample(anno)}
|
|
129
|
+
allowedToMarkExample={this.props.allowedToMarkExample}
|
|
130
|
+
commentInputTrigger={this.props.commentInputTrigger}
|
|
131
|
+
visible={this.props.uiConfig.annoDetails.visible}
|
|
132
|
+
/>
|
|
133
|
+
<AnnoStats
|
|
134
|
+
selectedAnno={this.props.selectedAnno}
|
|
135
|
+
annos={this.props.annos}
|
|
136
|
+
possibleLabels={this.props.possibleLabels}
|
|
137
|
+
defaultPos={{
|
|
138
|
+
left: this.state.position.left,
|
|
139
|
+
top: this.state.position.top + 400,
|
|
140
|
+
}}
|
|
141
|
+
// defaultPos={this.state.position}
|
|
142
|
+
onDismiss={() => this.onDismiss('AnnoStats')}
|
|
143
|
+
onHideLbl={(lbl, hide) => this.onHideLbl(lbl, hide)}
|
|
144
|
+
visible={this.props.uiConfig.annoStats.visible}
|
|
145
|
+
imgLoadCount={this.props.imgLoadCount}
|
|
146
|
+
/>
|
|
147
|
+
</div>
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default InfoBoxes
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import InfoBox from './InfoBox'
|
|
3
|
+
import SiaPopup from '../SiaPopup'
|
|
4
|
+
import AnnoExampleViewer from '../AnnoExampleViewer'
|
|
5
|
+
const LabelInfo = (props) => {
|
|
6
|
+
const [showExampleViewer, setShowExampleViewer] = useState(false)
|
|
7
|
+
const [myLbl, setMyLbl] = useState(undefined)
|
|
8
|
+
// const { data: exampleImg, mutate: getAnnoExample } = exampleApi.useGetAnnoExampleImg({})
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (props.selectedAnno) {
|
|
11
|
+
const selectedLabelIds = props.selectedAnno.labelIds
|
|
12
|
+
if (selectedLabelIds) {
|
|
13
|
+
const lbl = props.possibleLabels.find((e) => {
|
|
14
|
+
return selectedLabelIds[0] === e.id
|
|
15
|
+
})
|
|
16
|
+
if (lbl) {
|
|
17
|
+
if (lbl !== myLbl) {
|
|
18
|
+
setMyLbl(lbl)
|
|
19
|
+
if (props.visible)
|
|
20
|
+
// getAnnoExample({llId:lbl.id, type:'annoBased', drawAnno: true, addContext:0.05})
|
|
21
|
+
requestImg(lbl, props.selectedAnno)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}, [props.selectedAnno])
|
|
27
|
+
const onDismiss = () => {
|
|
28
|
+
if (props.onDismiss) {
|
|
29
|
+
props.onDismiss()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const requestImg = (lbl, anno) => {
|
|
34
|
+
if (props.onGetAnnoExample) {
|
|
35
|
+
props.onGetAnnoExample({ lbl: lbl, anno: anno })
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const handleImgClick = () => {
|
|
40
|
+
// setShowExampleViewer(true)
|
|
41
|
+
// requestImg(myLbl, props.selectedAnno)
|
|
42
|
+
|
|
43
|
+
setShowExampleViewer(true)
|
|
44
|
+
// getAnnoExample({llId:myLbl.id, type:'annoBased', drawAnno: true, addContext:0.05})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const renderExampleImg = () => {
|
|
48
|
+
if (!props.exampleImg) return null
|
|
49
|
+
return (
|
|
50
|
+
<div>
|
|
51
|
+
<h4 onClick={() => handleImgClick()}>Example:</h4>
|
|
52
|
+
<SiaPopup
|
|
53
|
+
trigger={
|
|
54
|
+
<img
|
|
55
|
+
src={props.exampleImg.img}
|
|
56
|
+
onClick={() => handleImgClick()}
|
|
57
|
+
style={{ borderRadius: 25, width: '100%' }}
|
|
58
|
+
/>
|
|
59
|
+
}
|
|
60
|
+
content={'Click on image to view more examples'}
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const renderDescription = () => {
|
|
67
|
+
// if (props.selectedAnno){
|
|
68
|
+
// if (myLbl){
|
|
69
|
+
// const selectedLabelIds = props.selectedAnno.labelIds
|
|
70
|
+
// if (!selectedLabelIds) return 'No Label'
|
|
71
|
+
// const lbl = props.possibleLabels.find( e => {
|
|
72
|
+
// return selectedLabelIds[0] === e.id
|
|
73
|
+
// })
|
|
74
|
+
if (!myLbl) return 'No Label'
|
|
75
|
+
return (
|
|
76
|
+
<div>
|
|
77
|
+
<h4>{myLbl.label}</h4>
|
|
78
|
+
<div dangerouslySetInnerHTML={{ __html: myLbl.description }} />
|
|
79
|
+
{renderExampleImg()}
|
|
80
|
+
<AnnoExampleViewer
|
|
81
|
+
onRequestExample={() => requestImg(myLbl, props.selectedAnno)}
|
|
82
|
+
onClose={() => {
|
|
83
|
+
setShowExampleViewer(false)
|
|
84
|
+
}}
|
|
85
|
+
active={showExampleViewer}
|
|
86
|
+
lbl={myLbl}
|
|
87
|
+
exampleImg={props.exampleImg}
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
)
|
|
91
|
+
// } else {
|
|
92
|
+
// return 'No Label'
|
|
93
|
+
// }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<InfoBox
|
|
98
|
+
header="Label Info"
|
|
99
|
+
content={renderDescription()}
|
|
100
|
+
visible={props.visible}
|
|
101
|
+
defaultPos={props.defaultPos}
|
|
102
|
+
onDismiss={() => onDismiss()}
|
|
103
|
+
/>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export default LabelInfo
|
package/src/SIA.scss
CHANGED
|
@@ -7,3 +7,16 @@
|
|
|
7
7
|
height: 100%;
|
|
8
8
|
background-color: #ffff;
|
|
9
9
|
}
|
|
10
|
+
|
|
11
|
+
.tag-scroll::-webkit-scrollbar {
|
|
12
|
+
height: 4px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.tag-scroll::-webkit-scrollbar-track {
|
|
16
|
+
background: transparent;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.tag-scroll::-webkit-scrollbar-thumb {
|
|
20
|
+
background-color: rgba(0, 0, 0, 0.25);
|
|
21
|
+
border-radius: 2px;
|
|
22
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React, { Component } from 'react'
|
|
2
|
+
|
|
3
|
+
import * as tbe from './types/toolbarEvents'
|
|
4
|
+
import { faCog } from '@fortawesome/free-solid-svg-icons'
|
|
5
|
+
import { CFormSwitch, CPopover } from '@coreui/react'
|
|
6
|
+
import ToolbarItem from './ToolbarItem'
|
|
7
|
+
class SIASettingButton extends Component {
|
|
8
|
+
constructor(props) {
|
|
9
|
+
super(props)
|
|
10
|
+
this.state = {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
triggerEvent(e, data) {
|
|
14
|
+
if (this.props.onSettingEvent) {
|
|
15
|
+
this.props.onSettingEvent(e, data)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
toggleAnnoDetails() {
|
|
19
|
+
this.triggerEvent(tbe.SHOW_ANNO_DETAILS)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
toggleLabelInfo() {
|
|
23
|
+
this.triggerEvent(tbe.SHOW_LABEL_INFO)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
toggleAnnoStats() {
|
|
27
|
+
this.triggerEvent(tbe.SHOW_ANNO_STATS)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
handleStrokeWidthChange(e) {
|
|
31
|
+
this.triggerEvent(tbe.EDIT_STROKE_WIDTH, parseInt(e.target.value))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
handleNodeRadiusChange(e) {
|
|
35
|
+
this.triggerEvent(tbe.EDIT_NODE_RADIUS, parseInt(e.target.value))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
renderInfoBoxContent() {
|
|
39
|
+
return (
|
|
40
|
+
<div>
|
|
41
|
+
<h4>Info Boxes</h4>
|
|
42
|
+
<CFormSwitch
|
|
43
|
+
checked={this.props.uiConfig.annoDetails.visible}
|
|
44
|
+
label="Annotation Details"
|
|
45
|
+
size="xl"
|
|
46
|
+
onClick={() => this.toggleAnnoDetails()}
|
|
47
|
+
/>
|
|
48
|
+
<CFormSwitch
|
|
49
|
+
checked={this.props.uiConfig.labelInfo.visible}
|
|
50
|
+
label="Label Info"
|
|
51
|
+
size="xl"
|
|
52
|
+
onClick={() => this.toggleLabelInfo()}
|
|
53
|
+
/>
|
|
54
|
+
<CFormSwitch
|
|
55
|
+
checked={this.props.uiConfig.annoStats.visible}
|
|
56
|
+
label="Anno Stats"
|
|
57
|
+
size="xl"
|
|
58
|
+
onClick={() => this.toggleAnnoStats()}
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
renderInfoBoxes() {
|
|
64
|
+
if (!this.props.enabled) return null
|
|
65
|
+
if (this.props.enabled === true) {
|
|
66
|
+
return this.renderInfoBoxContent()
|
|
67
|
+
} else {
|
|
68
|
+
if (this.props.enabled.infoBoxes) {
|
|
69
|
+
return this.renderInfoBoxContent()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
renderAnnoStyle() {
|
|
75
|
+
if (!this.props.enabled) return null
|
|
76
|
+
if (this.props.enabled === true) {
|
|
77
|
+
return this.renderAnnoStyleContent()
|
|
78
|
+
} else {
|
|
79
|
+
if (this.props.enabled.annoStyle) {
|
|
80
|
+
return this.renderAnnoStyleContent()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
renderAnnoStyleContent() {
|
|
85
|
+
return (
|
|
86
|
+
<div>
|
|
87
|
+
<h4 style={{ marginTop: 25 }}>Anno Appearance</h4>
|
|
88
|
+
<div>Stroke width: {this.props.uiConfig.strokeWidth}</div>
|
|
89
|
+
<input
|
|
90
|
+
type="range"
|
|
91
|
+
min={1}
|
|
92
|
+
max={10}
|
|
93
|
+
value={this.props.uiConfig.strokeWidth}
|
|
94
|
+
onChange={(e) => this.handleStrokeWidthChange(e)}
|
|
95
|
+
/>
|
|
96
|
+
<div>Node radius: {this.props.uiConfig.nodeRadius}</div>
|
|
97
|
+
<input
|
|
98
|
+
type="range"
|
|
99
|
+
min={1}
|
|
100
|
+
max={10}
|
|
101
|
+
value={this.props.uiConfig.nodeRadius}
|
|
102
|
+
onChange={(e) => this.handleNodeRadiusChange(e)}
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
render() {
|
|
109
|
+
if (!this.props.uiConfig) return null
|
|
110
|
+
const popupContent = (
|
|
111
|
+
<div>
|
|
112
|
+
{this.renderInfoBoxes()}
|
|
113
|
+
{this.renderAnnoStyle()}
|
|
114
|
+
</div>
|
|
115
|
+
)
|
|
116
|
+
return (
|
|
117
|
+
<CPopover content={popupContent} placement="right">
|
|
118
|
+
<span>
|
|
119
|
+
<ToolbarItem faIcon={faCog} />
|
|
120
|
+
</span>
|
|
121
|
+
</CPopover>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export default SIASettingButton
|
package/src/Sia.tsx
CHANGED
|
@@ -95,24 +95,38 @@ 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
|
-
if (annotationHistoryIndex !== undefined) {
|
|
105
|
-
// remove everything after the state the user is
|
|
106
|
-
_annotationHistory.splice(annotationHistoryIndex + 1)
|
|
107
|
-
}
|
|
112
|
+
setAnnotationHistory((prevHistory) => {
|
|
113
|
+
const _annotationHistory = [...prevHistory]
|
|
108
114
|
|
|
109
|
-
|
|
110
|
-
|
|
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
|
+
}
|
|
121
|
+
|
|
122
|
+
// update the list with out latest change (it is always living in the present)
|
|
123
|
+
_annotationHistory.push(_annotations)
|
|
124
|
+
return _annotationHistory
|
|
125
|
+
})
|
|
111
126
|
|
|
112
127
|
// keep history index marker in the present
|
|
128
|
+
annotationHistoryIndexRef.current = undefined
|
|
113
129
|
setAnnotationHistoryIndex(undefined)
|
|
114
|
-
|
|
115
|
-
setAnnotationHistory(_annotationHistory)
|
|
116
130
|
}
|
|
117
131
|
|
|
118
132
|
// for adjusting the container/canvas size
|
|
@@ -137,17 +151,19 @@ const Sia = ({
|
|
|
137
151
|
const [usedInternalIds, setUsedInternalIds] = useState<number[]>([])
|
|
138
152
|
|
|
139
153
|
const deleteAnnotationByInternalId = (internalId: number) => {
|
|
140
|
-
|
|
141
|
-
const annoListIndex: number =
|
|
154
|
+
const currentAnnotations = annotationsRef.current
|
|
155
|
+
const annoListIndex: number = currentAnnotations.findIndex(
|
|
142
156
|
(anno) => anno.internalId === internalId,
|
|
143
157
|
)
|
|
144
158
|
|
|
145
|
-
|
|
146
|
-
|
|
159
|
+
if (annoListIndex === -1) return
|
|
160
|
+
|
|
161
|
+
const _annotations: Annotation[] = [...currentAnnotations]
|
|
147
162
|
|
|
148
163
|
// remove annotation
|
|
149
164
|
const removedAnno: Annotation = _annotations.splice(annoListIndex, 1)[0]
|
|
150
165
|
|
|
166
|
+
annotationsRef.current = _annotations
|
|
151
167
|
setAnnotations(_annotations)
|
|
152
168
|
setSelectedAnnotation(undefined)
|
|
153
169
|
updateAnnotationHistory(_annotations)
|
|
@@ -206,7 +222,7 @@ const Sia = ({
|
|
|
206
222
|
}
|
|
207
223
|
|
|
208
224
|
const handleAnnoEditing = (annotation: Annotation) => {
|
|
209
|
-
const _annotations: Annotation[] = [...
|
|
225
|
+
const _annotations: Annotation[] = [...annotationsRef.current]
|
|
210
226
|
|
|
211
227
|
// annotation is being edited - remove it from current annotations for this time
|
|
212
228
|
const selectedAnnotationIndex: number = _annotations.findIndex(
|
|
@@ -498,34 +514,29 @@ const Sia = ({
|
|
|
498
514
|
possibleLabels={possibleLabels}
|
|
499
515
|
uiConfig={uiConfig}
|
|
500
516
|
onAnnoCreated={(annotation: Annotation) => {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
})
|
|
517
|
+
const _annotations = [...annotationsRef.current, annotation]
|
|
518
|
+
annotationsRef.current = _annotations
|
|
519
|
+
setAnnotations(_annotations)
|
|
520
|
+
onAnnoCreated(annotation, _annotations)
|
|
506
521
|
setSelectedAnnotation(annotation)
|
|
507
522
|
// dont update history here - we dont have a finished anno at this point
|
|
508
523
|
}}
|
|
509
524
|
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
|
|
525
|
+
const annoListIndex: number = annotationsRef.current.findIndex(
|
|
526
|
+
(anno) => anno.internalId === changedAnno.internalId,
|
|
527
|
+
)
|
|
518
528
|
|
|
519
|
-
|
|
529
|
+
if (annoListIndex !== -1) {
|
|
530
|
+
const _annotations: Annotation[] = [...annotationsRef.current]
|
|
520
531
|
_annotations[annoListIndex] = changedAnno
|
|
532
|
+
annotationsRef.current = _annotations
|
|
533
|
+
setAnnotations(_annotations)
|
|
521
534
|
|
|
522
535
|
// only update history for full/finished annotations
|
|
523
536
|
if (changedAnno.status !== AnnotationStatus.CREATING) {
|
|
524
537
|
updateAnnotationHistory(_annotations)
|
|
525
538
|
}
|
|
526
|
-
|
|
527
|
-
return _annotations
|
|
528
|
-
})
|
|
539
|
+
}
|
|
529
540
|
|
|
530
541
|
// inform the outside world about our change
|
|
531
542
|
// (kept outside the updater — side effects must not run inside setState)
|
|
@@ -551,30 +562,35 @@ const Sia = ({
|
|
|
551
562
|
]
|
|
552
563
|
: []
|
|
553
564
|
|
|
554
|
-
|
|
555
|
-
// update annotation list
|
|
556
|
-
const _annotations: Annotation[] = [...prev]
|
|
557
|
-
|
|
558
|
-
// remove the source annotations that were consumed by the polygon operation
|
|
559
|
-
for (const annotation of annosToDelete) {
|
|
560
|
-
const annoListIndex: number = _annotations.findIndex(
|
|
561
|
-
(anno) => anno.internalId === annotation.internalId,
|
|
562
|
-
)
|
|
563
|
-
if (annoListIndex !== -1) _annotations.splice(annoListIndex, 1)
|
|
564
|
-
}
|
|
565
|
+
const _annotations: Annotation[] = [...annotationsRef.current]
|
|
565
566
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
567
|
+
// remove annotations consumed by polygon operation
|
|
568
|
+
for (const annotation of annosToDelete) {
|
|
569
|
+
const annoListIndex: number = _annotations.findIndex(
|
|
570
|
+
(anno) => anno.internalId === annotation.internalId,
|
|
571
|
+
)
|
|
572
|
+
if (annoListIndex !== -1) _annotations.splice(annoListIndex, 1)
|
|
573
|
+
}
|
|
573
574
|
|
|
574
|
-
|
|
575
|
+
if (hasAnnoJustBeenCreated) {
|
|
576
|
+
// replace the CREATING entry with the finished CREATED version
|
|
577
|
+
const existingIdx = _annotations.findIndex(
|
|
578
|
+
(a) => a.internalId === finishedAnno.internalId,
|
|
579
|
+
)
|
|
580
|
+
if (existingIdx !== -1) _annotations.splice(existingIdx, 1)
|
|
581
|
+
_annotations.push(finishedAnno)
|
|
582
|
+
} else {
|
|
583
|
+
const annoListIndex: number = _annotations.findIndex(
|
|
584
|
+
(anno) => anno.internalId === finishedAnno.internalId,
|
|
585
|
+
)
|
|
586
|
+
if (annoListIndex !== -1) _annotations[annoListIndex] = finishedAnno
|
|
587
|
+
}
|
|
575
588
|
|
|
576
|
-
|
|
577
|
-
|
|
589
|
+
annotationsRef.current = _annotations
|
|
590
|
+
setAnnotations(_annotations)
|
|
591
|
+
updateAnnotationHistory(_annotations)
|
|
592
|
+
setSelectedAnnotation(finishedAnno)
|
|
593
|
+
onSelectAnnotation(finishedAnno)
|
|
578
594
|
|
|
579
595
|
// Notify the server about the deletions (outside of the updater/setState!!!)
|
|
580
596
|
for (const annotation of annosToDelete) {
|