React UI: Sidebar with objects and optimizations for annotation view (#1089)
* Basic layout for objects panel * Objects header * A little name refactoring * Side panel base layout * Firefox specific exceptions * Some minor fixes * React & canvas optimizations * Icons refactoring * Little style refactoring * Some style fixes * Improved side panel with objects * Actual attribute values * Actual icons * Hidden > visible * hidden -> __internal * Fixed hidden in ui * Fixed some issues in canvas * Fixed list height * Color picker for labels * A bit fixed design * Actual header icons * Changing attributes and switchable buttons * Removed react memo (will reoptimize better) * Sorting methods, removed cache from cvat-core (a lot of bugs related with it) * Label switchers * Fixed bug with update timestamp for shapes * Annotation state refactoring * Removed old resetCache calls * Optimized top & left panels. Number of renders significantly decreased * Optimized some extra renders * Accelerated performance * Fixed two minor issues * Canvas improvements * Minor fixes * Removed extra codemain
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" height="1em" width="1em" viewBox="64 64 896 896">
|
||||||
|
<g style="transform: scale(25)">
|
||||||
|
<path d="M33.632 2.58a2.058 2.058 0 0 1 2.863 0l1.912 1.868c.79.772.79 2.022.003 2.795L25.42 20l12.99 12.757a1.947 1.947 0 0 1-.003 2.795l-1.912 1.868a2.058 2.058 0 0 1-2.863 0L17.24 21.4a1.947 1.947 0 0 1 0-2.798zm-15.647 0a2.058 2.058 0 0 1 2.863 0l1.912 1.868c.79.772.79 2.022.003 2.795L9.773 20l12.99 12.757a1.947 1.947 0 0 1-.003 2.795l-1.912 1.868a2.058 2.058 0 0 1-2.863 0L1.593 21.4a1.947 1.947 0 0 1 0-2.798z" fill-rule="nonzero"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 631 B |
@ -1 +0,0 @@
|
|||||||
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><path d="M36.889 5.188H29.5L27.389 3H22.11C20.945 3 20 3.98 20 5.188v10.937c0 1.208.945 2.188 2.111 2.188H36.89c1.166 0 2.111-.98 2.111-2.188v-8.75c0-1.208-.945-2.188-2.111-2.188zm0 19.687H29.5l-2.111-2.188H22.11c-1.166 0-2.111.98-2.111 2.188v10.938C20 37.02 20.945 38 22.111 38H36.89C38.055 38 39 37.02 39 35.812v-8.75c0-1.208-.945-2.187-2.111-2.187zM5.222 4.094C5.222 3.49 4.75 3 4.167 3H2.056C1.473 3 1 3.49 1 4.094v27.343c0 1.209.945 2.188 2.111 2.188H17.89V29.25H5.222V13.937H17.89V9.564H5.222v-5.47z" fill="#000" fill-rule="nonzero"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 609 B |
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" height="1em" width="1em" viewBox="64 64 896 896">
|
||||||
|
<g style="transform: scale(25)">
|
||||||
|
<path d="M13.762 2v14.774L32 2v36L13.762 23.225V38H7V2h6.762z" fill-rule="nonzero"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 275 B |
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" height="1em" width="1em" viewBox="64 64 896 896">
|
||||||
|
<g style="transform: scale(25)">
|
||||||
|
<path d="M6.368 2.58a2.058 2.058 0 0 0-2.863 0L1.593 4.448a1.947 1.947 0 0 0-.003 2.795L14.58 20 1.59 32.757a1.947 1.947 0 0 0 .003 2.795l1.912 1.868c.79.773 2.072.773 2.863 0L22.76 21.4c.79-.773.79-2.025 0-2.798zm15.647 0a2.058 2.058 0 0 0-2.863 0L17.24 4.448a1.947 1.947 0 0 0-.003 2.795L30.227 20l-12.99 12.757a1.947 1.947 0 0 0 .003 2.795l1.912 1.868c.79.773 2.072.773 2.863 0L38.407 21.4c.79-.773.79-2.025 0-2.798z" fill-rule="nonzero"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 633 B |
@ -1 +0,0 @@
|
|||||||
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><g transform="translate(3 7)" fill="#000" fill-rule="evenodd"><rect x="5.75" y="4.5" width="22.5" height="15.75" rx="2.25"/><path stroke="#F1F1F1" stroke-width="2.25" d="M-1.39 2.72l2.451-3.773 33.967 22.057-2.451 3.774z"/></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 296 B |
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" height="1em" width="1em" viewBox="64 64 896 896">
|
||||||
|
<g style="transform: scale(25)">
|
||||||
|
<path d="M25.238 2v14.774L7 2v36l18.238-14.775V38H32V2h-6.762z" fill-rule="nonzero"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 276 B |
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" height="1em" width="1em" viewBox="64 64 896 896">
|
||||||
|
<g style="transform: scale(25)">
|
||||||
|
<path d="M30.76 21.399L14.368 37.42a2.058 2.058 0 0 1-2.863 0l-1.912-1.868a1.947 1.947 0 0 1-.003-2.795L22.58 20 9.59 7.243a1.947 1.947 0 0 1 .003-2.795l1.912-1.868a2.058 2.058 0 0 1 2.863 0L30.76 18.6c.79.773.79 2.025 0 2.798z" fill-rule="nonzero"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 441 B |
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" height="1em" width="1em" viewBox="64 64 896 896" style="transform: scale(1.5)">
|
||||||
|
<g style="transform: scale(25)">
|
||||||
|
<g transform="translate(3 7)" fill-rule="evenodd">
|
||||||
|
<rect x="5.75" y="4.5" width="22.5" height="15.75" rx="2.25"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 359 B |
@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" height="1em" width="1em" viewBox="64 64 896 896" style="transform: scale(1.5)">
|
||||||
|
<g style="transform: scale(25)">
|
||||||
|
<g transform="translate(3 7)" fill-rule="evenodd">
|
||||||
|
<rect x="5.75" y="4.5" width="22.5" height="15.75" rx="2.25"/>
|
||||||
|
<path stroke="#F1F1F1" stroke-width="1" d="M-1.39 2.72l2.451-3.773 33.967 22.057-2.451 3.774z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 468 B |
@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" height="1em" width="1em" viewBox="64 64 896 896">
|
||||||
|
<g style="transform: scale(25)">
|
||||||
|
<g transform="scale(0.07812)" transform-origin="bottom">
|
||||||
|
<path d="m201.350937,473.371796l0,-434.86451c0,-8.10006 -6.528412,-14.628468 -14.749344,-14.628468l-86.561874,0c-8.220955,0 -14.749359,6.528408 -14.749359,14.628468l0,434.86454c0,8.100037 6.528404,14.749359 14.749359,14.749359l86.561874,0c8.220932,0 14.749344,-6.528412 14.749344,-14.74939z" />
|
||||||
|
<path d="m423.967224,473.371796l0,-434.86451c0,-8.10006 -6.528381,-14.628468 -14.749329,-14.628468l-86.56189,0c-8.220947,0 -14.749329,6.528408 -14.749329,14.628468l0,434.86454c0,8.100037 6.528381,14.749359 14.749329,14.749359l86.56189,0c8.220947,0 14.749329,-6.528412 14.749329,-14.74939z" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 872 B |
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" height="1em" width="1em" viewBox="64 64 896 896">
|
||||||
|
<g style="transform: scale(25)">
|
||||||
|
<path fill-rule="nonzero" d="M35.5 20l-30 19.5V.5z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 244 B |
@ -1 +0,0 @@
|
|||||||
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><path d="M33.632 2.58a2.058 2.058 0 0 1 2.863 0l1.912 1.868c.79.772.79 2.022.003 2.795L25.42 20l12.99 12.757a1.947 1.947 0 0 1-.003 2.795l-1.912 1.868a2.058 2.058 0 0 1-2.863 0L17.24 21.4a1.947 1.947 0 0 1 0-2.798zm-15.647 0a2.058 2.058 0 0 1 2.863 0l1.912 1.868c.79.772.79 2.022.003 2.795L9.773 20l12.99 12.757a1.947 1.947 0 0 1-.003 2.795l-1.912 1.868a2.058 2.058 0 0 1-2.863 0L1.593 21.4a1.947 1.947 0 0 1 0-2.798z" fill="#000" fill-rule="nonzero"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 521 B |
@ -1 +0,0 @@
|
|||||||
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><path d="M13.762 2v14.774L32 2v36L13.762 23.225V38H7V2h6.762z" fill="#000" fill-rule="nonzero"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 165 B |
@ -1 +0,0 @@
|
|||||||
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><path d="M6.368 2.58a2.058 2.058 0 0 0-2.863 0L1.593 4.448a1.947 1.947 0 0 0-.003 2.795L14.58 20 1.59 32.757a1.947 1.947 0 0 0 .003 2.795l1.912 1.868c.79.773 2.072.773 2.863 0L22.76 21.4c.79-.773.79-2.025 0-2.798zm15.647 0a2.058 2.058 0 0 0-2.863 0L17.24 4.448a1.947 1.947 0 0 0-.003 2.795L30.227 20l-12.99 12.757a1.947 1.947 0 0 0 .003 2.795l1.912 1.868c.79.773 2.072.773 2.863 0L38.407 21.4c.79-.773.79-2.025 0-2.798z" fill="#000" fill-rule="nonzero"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 523 B |
@ -1 +0,0 @@
|
|||||||
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><path d="M25.238 2v14.774L7 2v36l18.238-14.775V38H32V2h-6.762z" fill="#000" fill-rule="nonzero"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 166 B |
@ -1 +0,0 @@
|
|||||||
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><path d="M30.76 21.399L14.368 37.42a2.058 2.058 0 0 1-2.863 0l-1.912-1.868a1.947 1.947 0 0 1-.003-2.795L22.58 20 9.59 7.243a1.947 1.947 0 0 1 .003-2.795l1.912-1.868a2.058 2.058 0 0 1 2.863 0L30.76 18.6c.79.773.79 2.025 0 2.798z" fill="#000" fill-rule="nonzero"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 331 B |
@ -1,7 +0,0 @@
|
|||||||
<!-- Drawn in https://www.iconfinder.com/editor/ -->
|
|
||||||
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g transform="scale(0.07812)" transform-origin="bottom">
|
|
||||||
<path d="m201.350937,473.371796l0,-434.86451c0,-8.10006 -6.528412,-14.628468 -14.749344,-14.628468l-86.561874,0c-8.220955,0 -14.749359,6.528408 -14.749359,14.628468l0,434.86454c0,8.100037 6.528404,14.749359 14.749359,14.749359l86.561874,0c8.220932,0 14.749344,-6.528412 14.749344,-14.74939z" />
|
|
||||||
<path d="m423.967224,473.371796l0,-434.86451c0,-8.10006 -6.528381,-14.628468 -14.749329,-14.628468l-86.56189,0c-8.220947,0 -14.749329,6.528408 -14.749329,14.628468l0,434.86454c0,8.100037 6.528381,14.749359 14.749329,14.749359l86.56189,0c8.220947,0 14.749329,-6.528412 14.749329,-14.74939z" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 798 B |
@ -1 +0,0 @@
|
|||||||
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><path fill="#000" fill-rule="nonzero" d="M35.5 20l-30 19.5V.5z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 134 B |
@ -1 +0,0 @@
|
|||||||
<svg width="40" height="40" xmlns="http://www.w3.org/2000/svg"><path d="M9.593 21.399L25.985 37.42c.79.773 2.073.773 2.863 0l1.912-1.868c.79-.772.79-2.022.003-2.795L17.773 20l12.99-12.757a1.947 1.947 0 0 0-.003-2.795L28.848 2.58a2.058 2.058 0 0 0-2.863 0L9.593 18.6a1.947 1.947 0 0 0 0 2.798z" fill="#000" fill-rule="nonzero"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 333 B |
@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" fill="currentColor" height="1em" width="1em" viewBox="64 64 896 896">
|
||||||
|
<g style="transform: scale(25)">
|
||||||
|
<path d="M9.593 21.399L25.985 37.42c.79.773 2.073.773 2.863 0l1.912-1.868c.79-.772.79-2.022.003-2.795L17.773 20l12.99-12.757a1.947 1.947 0 0 0-.003-2.795L28.848 2.58a2.058 2.058 0 0 0-2.863 0L9.593 18.6a1.947 1.947 0 0 0 0 2.798z" fill-rule="nonzero"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 443 B |
@ -0,0 +1,130 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Icon,
|
||||||
|
Popover,
|
||||||
|
Button,
|
||||||
|
} from 'antd';
|
||||||
|
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
|
||||||
|
interface PopoverContentProps {
|
||||||
|
colors: string[];
|
||||||
|
changeColor(color: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PopoverContent(props: PopoverContentProps): JSX.Element {
|
||||||
|
const {
|
||||||
|
colors,
|
||||||
|
changeColor,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const cols = 6;
|
||||||
|
const rows = Math.ceil(colors.length / cols);
|
||||||
|
|
||||||
|
const antdRows = [];
|
||||||
|
for (let row = 0; row < rows; row++) {
|
||||||
|
const antdCols = [];
|
||||||
|
for (let col = 0; col < cols; col++) {
|
||||||
|
const idx = row * cols + col;
|
||||||
|
if (idx >= colors.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const color = colors[idx];
|
||||||
|
antdCols.push(
|
||||||
|
<Col key={col} span={4}>
|
||||||
|
<Button
|
||||||
|
onClick={(): void => changeColor(color)}
|
||||||
|
style={{ background: color }}
|
||||||
|
className='cvat-label-item-color-button'
|
||||||
|
/>
|
||||||
|
</Col>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
antdRows.push(
|
||||||
|
// eslint-disable-next-line react/no-children-prop
|
||||||
|
<Row key={row} children={antdCols} />,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{antdRows}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
labelName: string;
|
||||||
|
labelColor: string;
|
||||||
|
labelColors: string[];
|
||||||
|
visible: boolean;
|
||||||
|
statesHidden: boolean;
|
||||||
|
statesLocked: boolean;
|
||||||
|
hideStates(): void;
|
||||||
|
showStates(): void;
|
||||||
|
lockStates(): void;
|
||||||
|
unlockStates(): void;
|
||||||
|
changeColor(color: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LabelItemComponent = React.memo((props: Props): JSX.Element => {
|
||||||
|
const {
|
||||||
|
labelName,
|
||||||
|
labelColor,
|
||||||
|
labelColors,
|
||||||
|
visible,
|
||||||
|
statesHidden,
|
||||||
|
statesLocked,
|
||||||
|
hideStates,
|
||||||
|
showStates,
|
||||||
|
lockStates,
|
||||||
|
unlockStates,
|
||||||
|
changeColor,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row
|
||||||
|
type='flex'
|
||||||
|
align='middle'
|
||||||
|
justify='space-around'
|
||||||
|
className='cvat-objects-sidebar-label-item'
|
||||||
|
style={{ display: visible ? 'flex' : 'none' }}
|
||||||
|
>
|
||||||
|
<Col span={4}>
|
||||||
|
<Popover
|
||||||
|
placement='left'
|
||||||
|
trigger='click'
|
||||||
|
content={(
|
||||||
|
<PopoverContent
|
||||||
|
changeColor={changeColor}
|
||||||
|
colors={labelColors}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Button style={{ background: labelColor }} className='cvat-label-item-color-button' />
|
||||||
|
</Popover>
|
||||||
|
</Col>
|
||||||
|
<Col span={14}>
|
||||||
|
<Text strong className='cvat-text'>{labelName}</Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={3}>
|
||||||
|
{ statesLocked
|
||||||
|
? <Icon type='lock' onClick={unlockStates} />
|
||||||
|
: <Icon type='unlock' onClick={lockStates} />
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
<Col span={3}>
|
||||||
|
{ statesHidden
|
||||||
|
? <Icon type='eye-invisible' onClick={showStates} />
|
||||||
|
: <Icon type='eye' onClick={hideStates} />
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default LabelItemComponent;
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import LabelItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/label-item';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
labelIDs: number[];
|
||||||
|
listHeight: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LabelsListComponent(props: Props): JSX.Element {
|
||||||
|
const {
|
||||||
|
listHeight,
|
||||||
|
labelIDs,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ height: listHeight }} className='cvat-objects-sidebar-labels-list'>
|
||||||
|
{
|
||||||
|
labelIDs.map((labelID: number): JSX.Element => (
|
||||||
|
<LabelItemContainer key={labelID} labelID={labelID} />
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,538 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Icon,
|
||||||
|
Select,
|
||||||
|
Radio,
|
||||||
|
Input,
|
||||||
|
Collapse,
|
||||||
|
Checkbox,
|
||||||
|
InputNumber,
|
||||||
|
} from 'antd';
|
||||||
|
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
|
||||||
|
import { RadioChangeEvent } from 'antd/lib/radio';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ObjectOutsideIcon,
|
||||||
|
FirstIcon,
|
||||||
|
LastIcon,
|
||||||
|
PreviousIcon,
|
||||||
|
NextIcon,
|
||||||
|
} from 'icons';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ObjectType, ShapeType,
|
||||||
|
} from 'reducers/interfaces';
|
||||||
|
|
||||||
|
interface ItemTopProps {
|
||||||
|
clientID: number;
|
||||||
|
labelID: number;
|
||||||
|
labels: any[];
|
||||||
|
type: string;
|
||||||
|
changeLabel(labelID: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ItemTop = React.memo((props: ItemTopProps): JSX.Element => {
|
||||||
|
const {
|
||||||
|
clientID,
|
||||||
|
labelID,
|
||||||
|
labels,
|
||||||
|
type,
|
||||||
|
changeLabel,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row type='flex' align='middle'>
|
||||||
|
<Col span={10}>
|
||||||
|
<Text style={{ fontSize: 16 }}>{clientID}</Text>
|
||||||
|
<br />
|
||||||
|
<Text style={{ fontSize: 10 }}>{type}</Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={12}>
|
||||||
|
<Select value={`${labelID}`} onChange={changeLabel}>
|
||||||
|
{ labels.map((label: any): JSX.Element => (
|
||||||
|
<Select.Option key={label.id} value={`${label.id}`}>
|
||||||
|
{label.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
<Col span={2}>
|
||||||
|
<Icon type='more' />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
interface ItemButtonsProps {
|
||||||
|
objectType: ObjectType;
|
||||||
|
occluded: boolean;
|
||||||
|
outside: boolean | undefined;
|
||||||
|
locked: boolean;
|
||||||
|
hidden: boolean;
|
||||||
|
keyframe: boolean | undefined;
|
||||||
|
|
||||||
|
setOccluded(): void;
|
||||||
|
unsetOccluded(): void;
|
||||||
|
setOutside(): void;
|
||||||
|
unsetOutside(): void;
|
||||||
|
setKeyframe(): void;
|
||||||
|
unsetKeyframe(): void;
|
||||||
|
lock(): void;
|
||||||
|
unlock(): void;
|
||||||
|
hide(): void;
|
||||||
|
show(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ItemButtons = React.memo((props: ItemButtonsProps): JSX.Element => {
|
||||||
|
const {
|
||||||
|
objectType,
|
||||||
|
occluded,
|
||||||
|
outside,
|
||||||
|
locked,
|
||||||
|
hidden,
|
||||||
|
keyframe,
|
||||||
|
setOccluded,
|
||||||
|
unsetOccluded,
|
||||||
|
setOutside,
|
||||||
|
unsetOutside,
|
||||||
|
setKeyframe,
|
||||||
|
unsetKeyframe,
|
||||||
|
lock,
|
||||||
|
unlock,
|
||||||
|
hide,
|
||||||
|
show,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
if (objectType === ObjectType.TRACK) {
|
||||||
|
return (
|
||||||
|
<Row type='flex' align='middle' justify='space-around'>
|
||||||
|
<Col span={20} style={{ textAlign: 'center' }}>
|
||||||
|
<Row type='flex' justify='space-around'>
|
||||||
|
<Col span={6}>
|
||||||
|
<Icon component={FirstIcon} />
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Icon component={PreviousIcon} />
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Icon component={NextIcon} />
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<Icon component={LastIcon} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row type='flex' justify='space-around'>
|
||||||
|
<Col span={4}>
|
||||||
|
{ outside
|
||||||
|
? <Icon component={ObjectOutsideIcon} onClick={unsetOutside} />
|
||||||
|
: <Icon type='select' onClick={setOutside} />
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
{ locked
|
||||||
|
? <Icon type='lock' onClick={unlock} />
|
||||||
|
: <Icon type='unlock' onClick={lock} />
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
{ occluded
|
||||||
|
? <Icon type='team' onClick={unsetOccluded} />
|
||||||
|
: <Icon type='user' onClick={setOccluded} />
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
{ hidden
|
||||||
|
? <Icon type='eye-invisible' onClick={show} />
|
||||||
|
: <Icon type='eye' onClick={hide} />
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
{ keyframe
|
||||||
|
? <Icon type='star' theme='filled' onClick={unsetKeyframe} />
|
||||||
|
: <Icon type='star' onClick={setKeyframe} />
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row type='flex' align='middle' justify='space-around'>
|
||||||
|
<Col span={20} style={{ textAlign: 'center' }}>
|
||||||
|
<Row type='flex' justify='space-around'>
|
||||||
|
<Col span={8}>
|
||||||
|
{ locked
|
||||||
|
? <Icon type='lock' onClick={unlock} />
|
||||||
|
: <Icon type='unlock' onClick={lock} />
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
{ occluded
|
||||||
|
? <Icon type='team' onClick={unsetOccluded} />
|
||||||
|
: <Icon type='user' onClick={setOccluded} />
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
{ hidden
|
||||||
|
? <Icon type='eye-invisible' onClick={show} />
|
||||||
|
: <Icon type='eye' onClick={hide} />
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
interface ItemAttributeProps {
|
||||||
|
attrInputType: string;
|
||||||
|
attrValues: string[];
|
||||||
|
attrValue: string;
|
||||||
|
attrName: string;
|
||||||
|
attrID: number;
|
||||||
|
changeAttribute(attrID: number, value: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function attrIsTheSame(prevProps: ItemAttributeProps, nextProps: ItemAttributeProps): boolean {
|
||||||
|
return nextProps.attrID === prevProps.attrID
|
||||||
|
&& nextProps.attrValue === prevProps.attrValue
|
||||||
|
&& nextProps.attrName === prevProps.attrName
|
||||||
|
&& nextProps.attrInputType === prevProps.attrInputType
|
||||||
|
&& nextProps.attrValues
|
||||||
|
.map((value: string, id: number): boolean => prevProps.attrValues[id] === value)
|
||||||
|
.every((value: boolean): boolean => value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ItemAttribute = React.memo((props: ItemAttributeProps): JSX.Element => {
|
||||||
|
const {
|
||||||
|
attrInputType,
|
||||||
|
attrValues,
|
||||||
|
attrValue,
|
||||||
|
attrName,
|
||||||
|
attrID,
|
||||||
|
changeAttribute,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
if (attrInputType === 'checkbox') {
|
||||||
|
return (
|
||||||
|
<Col span={24}>
|
||||||
|
<Checkbox
|
||||||
|
className='cvat-object-item-checkbox-attribute'
|
||||||
|
checked={attrValue === 'true'}
|
||||||
|
onChange={(event: CheckboxChangeEvent): void => {
|
||||||
|
const value = event.target.checked ? 'true' : 'false';
|
||||||
|
changeAttribute(attrID, value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text strong className='cvat-text' style={{ fontSize: '1.2em' }}>
|
||||||
|
{attrName}
|
||||||
|
</Text>
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrInputType === 'radio') {
|
||||||
|
return (
|
||||||
|
<Col span={24}>
|
||||||
|
<fieldset className='cvat-object-item-radio-attribute'>
|
||||||
|
<legend>
|
||||||
|
<Text strong className='cvat-text'>{attrName}</Text>
|
||||||
|
</legend>
|
||||||
|
<Radio.Group
|
||||||
|
value={attrValue}
|
||||||
|
onChange={(event: RadioChangeEvent): void => {
|
||||||
|
changeAttribute(attrID, event.target.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ attrValues.map((value: string): JSX.Element => (
|
||||||
|
<Radio key={value} value={value}>{value}</Radio>
|
||||||
|
)) }
|
||||||
|
</Radio.Group>
|
||||||
|
</fieldset>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrInputType === 'select') {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Col span={24}>
|
||||||
|
<Text strong className='cvat-text' style={{ fontSize: '1.2em' }}>
|
||||||
|
{attrName}
|
||||||
|
</Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Select
|
||||||
|
onChange={(value: string): void => {
|
||||||
|
changeAttribute(attrID, value);
|
||||||
|
}}
|
||||||
|
value={attrValue}
|
||||||
|
className='cvat-object-item-select-attribute'
|
||||||
|
>
|
||||||
|
{ attrValues.map((value: string): JSX.Element => (
|
||||||
|
<Select.Option key={value} value={value}>{value}</Select.Option>
|
||||||
|
)) }
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrInputType === 'number') {
|
||||||
|
const [min, max, step] = attrValues;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Col span={24}>
|
||||||
|
<Text strong className='cvat-text' style={{ fontSize: '1.2em' }}>
|
||||||
|
{attrName}
|
||||||
|
</Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<InputNumber
|
||||||
|
onChange={(value: number | undefined): void => {
|
||||||
|
if (typeof (value) !== 'undefined') {
|
||||||
|
changeAttribute(attrID, `${value}`);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
value={+attrValue}
|
||||||
|
className='cvat-object-item-number-attribute'
|
||||||
|
min={+min}
|
||||||
|
max={+max}
|
||||||
|
step={+step}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Col span={24}>
|
||||||
|
<Text strong className='cvat-text' style={{ fontSize: '1.2em' }}>
|
||||||
|
{attrName}
|
||||||
|
</Text>
|
||||||
|
</Col>
|
||||||
|
<Col span={24}>
|
||||||
|
<Input
|
||||||
|
onChange={(event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
|
changeAttribute(attrID, event.target.value);
|
||||||
|
}}
|
||||||
|
value={attrValue}
|
||||||
|
className='cvat-object-item-text-attribute'
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}, attrIsTheSame);
|
||||||
|
|
||||||
|
|
||||||
|
interface ItemAttributesProps {
|
||||||
|
collapsed: boolean;
|
||||||
|
attributes: any[];
|
||||||
|
values: Record<number, string>;
|
||||||
|
changeAttribute(attrID: number, value: string): void;
|
||||||
|
collapse(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function attrValuesAreEqual(next: Record<number, string>, prev: Record<number, string>): boolean {
|
||||||
|
const prevKeys = Object.keys(prev);
|
||||||
|
const nextKeys = Object.keys(next);
|
||||||
|
|
||||||
|
return nextKeys.length === prevKeys.length
|
||||||
|
&& nextKeys.map((key: string): boolean => prev[+key] === next[+key])
|
||||||
|
.every((value: boolean) => value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function attrAreTheSame(prevProps: ItemAttributesProps, nextProps: ItemAttributesProps): boolean {
|
||||||
|
return nextProps.collapsed === prevProps.collapsed
|
||||||
|
&& nextProps.attributes === prevProps.attributes
|
||||||
|
&& attrValuesAreEqual(nextProps.values, prevProps.values);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ItemAttributes = React.memo((props: ItemAttributesProps): JSX.Element => {
|
||||||
|
const {
|
||||||
|
collapsed,
|
||||||
|
attributes,
|
||||||
|
values,
|
||||||
|
changeAttribute,
|
||||||
|
collapse,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const sorted = [...attributes]
|
||||||
|
.sort((a: any, b: any): number => a.inputType.localeCompare(b.inputType));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Collapse
|
||||||
|
className='cvat-objects-sidebar-state-item-collapse'
|
||||||
|
activeKey={collapsed ? [] : ['details']}
|
||||||
|
onChange={collapse}
|
||||||
|
>
|
||||||
|
<Collapse.Panel
|
||||||
|
header='Details'
|
||||||
|
key='details'
|
||||||
|
>
|
||||||
|
{ sorted.map((attribute: any): JSX.Element => (
|
||||||
|
<Row
|
||||||
|
key={attribute.id}
|
||||||
|
type='flex'
|
||||||
|
align='middle'
|
||||||
|
justify='start'
|
||||||
|
className='cvat-object-item-attribute-wrapper'
|
||||||
|
>
|
||||||
|
<ItemAttribute
|
||||||
|
attrValue={values[attribute.id]}
|
||||||
|
attrInputType={attribute.inputType}
|
||||||
|
attrName={attribute.name}
|
||||||
|
attrID={attribute.id}
|
||||||
|
attrValues={attribute.values}
|
||||||
|
changeAttribute={changeAttribute}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
))}
|
||||||
|
</Collapse.Panel>
|
||||||
|
</Collapse>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}, attrAreTheSame);
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
objectType: ObjectType;
|
||||||
|
shapeType: ShapeType;
|
||||||
|
clientID: number;
|
||||||
|
labelID: number;
|
||||||
|
occluded: boolean;
|
||||||
|
outside: boolean | undefined;
|
||||||
|
locked: boolean;
|
||||||
|
hidden: boolean;
|
||||||
|
keyframe: boolean | undefined;
|
||||||
|
attrValues: Record<number, string>;
|
||||||
|
color: string;
|
||||||
|
|
||||||
|
labels: any[];
|
||||||
|
attributes: any[];
|
||||||
|
collapsed: boolean;
|
||||||
|
|
||||||
|
setOccluded(): void;
|
||||||
|
unsetOccluded(): void;
|
||||||
|
setOutside(): void;
|
||||||
|
unsetOutside(): void;
|
||||||
|
setKeyframe(): void;
|
||||||
|
unsetKeyframe(): void;
|
||||||
|
lock(): void;
|
||||||
|
unlock(): void;
|
||||||
|
hide(): void;
|
||||||
|
show(): void;
|
||||||
|
changeLabel(labelID: string): void;
|
||||||
|
changeAttribute(attrID: number, value: string): void;
|
||||||
|
collapse(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function objectItemsAreEqual(prevProps: Props, nextProps: Props): boolean {
|
||||||
|
return nextProps.locked === prevProps.locked
|
||||||
|
&& nextProps.occluded === prevProps.occluded
|
||||||
|
&& nextProps.outside === prevProps.outside
|
||||||
|
&& nextProps.hidden === prevProps.hidden
|
||||||
|
&& nextProps.keyframe === prevProps.keyframe
|
||||||
|
&& nextProps.label === prevProps.label
|
||||||
|
&& nextProps.color === prevProps.color
|
||||||
|
&& nextProps.clientID === prevProps.clientID
|
||||||
|
&& nextProps.objectType === prevProps.objectType
|
||||||
|
&& nextProps.shapeType === prevProps.shapeType
|
||||||
|
&& nextProps.collapsed === prevProps.collapsed
|
||||||
|
&& nextProps.labels === prevProps.labels
|
||||||
|
&& nextProps.attributes === prevProps.attributes
|
||||||
|
&& attrValuesAreEqual(nextProps.attrValues, prevProps.attrValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ObjectItem = React.memo((props: Props): JSX.Element => {
|
||||||
|
const {
|
||||||
|
objectType,
|
||||||
|
shapeType,
|
||||||
|
clientID,
|
||||||
|
occluded,
|
||||||
|
outside,
|
||||||
|
locked,
|
||||||
|
hidden,
|
||||||
|
keyframe,
|
||||||
|
attrValues,
|
||||||
|
labelID,
|
||||||
|
color,
|
||||||
|
|
||||||
|
attributes,
|
||||||
|
labels,
|
||||||
|
collapsed,
|
||||||
|
|
||||||
|
setOccluded,
|
||||||
|
unsetOccluded,
|
||||||
|
setOutside,
|
||||||
|
unsetOutside,
|
||||||
|
setKeyframe,
|
||||||
|
unsetKeyframe,
|
||||||
|
lock,
|
||||||
|
unlock,
|
||||||
|
hide,
|
||||||
|
show,
|
||||||
|
changeLabel,
|
||||||
|
changeAttribute,
|
||||||
|
collapse,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const type = objectType === ObjectType.TAG ? ObjectType.TAG.toUpperCase()
|
||||||
|
: `${shapeType.toUpperCase()} ${objectType.toUpperCase()}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className='cvat-objects-sidebar-state-item'
|
||||||
|
style={{ borderLeftStyle: 'solid', borderColor: ` ${color}` }}
|
||||||
|
>
|
||||||
|
<ItemTop
|
||||||
|
clientID={clientID}
|
||||||
|
labelID={labelID}
|
||||||
|
labels={labels}
|
||||||
|
type={type}
|
||||||
|
changeLabel={changeLabel}
|
||||||
|
/>
|
||||||
|
<ItemButtons
|
||||||
|
objectType={objectType}
|
||||||
|
occluded={occluded}
|
||||||
|
outside={outside}
|
||||||
|
locked={locked}
|
||||||
|
hidden={hidden}
|
||||||
|
keyframe={keyframe}
|
||||||
|
setOccluded={setOccluded}
|
||||||
|
unsetOccluded={unsetOccluded}
|
||||||
|
setOutside={setOutside}
|
||||||
|
unsetOutside={unsetOutside}
|
||||||
|
setKeyframe={setKeyframe}
|
||||||
|
unsetKeyframe={unsetKeyframe}
|
||||||
|
lock={lock}
|
||||||
|
unlock={unlock}
|
||||||
|
hide={hide}
|
||||||
|
show={show}
|
||||||
|
/>
|
||||||
|
{ !!attributes.length
|
||||||
|
&& (
|
||||||
|
<ItemAttributes
|
||||||
|
collapsed={collapsed}
|
||||||
|
attributes={attributes}
|
||||||
|
values={attrValues}
|
||||||
|
collapse={collapse}
|
||||||
|
changeAttribute={changeAttribute}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}, objectItemsAreEqual);
|
||||||
|
|
||||||
|
export default ObjectItem;
|
||||||
@ -0,0 +1,121 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Icon,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
} from 'antd';
|
||||||
|
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
|
||||||
|
import { StatesOrdering } from 'reducers/interfaces';
|
||||||
|
|
||||||
|
|
||||||
|
interface StatesOrderingSelectorProps {
|
||||||
|
statesOrdering: StatesOrdering;
|
||||||
|
changeStatesOrdering(value: StatesOrdering): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StatesOrderingSelector = React.memo((props: StatesOrderingSelectorProps): JSX.Element => {
|
||||||
|
const {
|
||||||
|
statesOrdering,
|
||||||
|
changeStatesOrdering,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col span={16}>
|
||||||
|
<Text strong>Sort by</Text>
|
||||||
|
<Select value={statesOrdering} onChange={changeStatesOrdering}>
|
||||||
|
<Select.Option
|
||||||
|
key={StatesOrdering.ID_DESCENT}
|
||||||
|
value={StatesOrdering.ID_DESCENT}
|
||||||
|
>
|
||||||
|
{StatesOrdering.ID_DESCENT}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option
|
||||||
|
key={StatesOrdering.ID_ASCENT}
|
||||||
|
value={StatesOrdering.ID_ASCENT}
|
||||||
|
>
|
||||||
|
{StatesOrdering.ID_ASCENT}
|
||||||
|
</Select.Option>
|
||||||
|
<Select.Option
|
||||||
|
key={StatesOrdering.UPDATED}
|
||||||
|
value={StatesOrdering.UPDATED}
|
||||||
|
>
|
||||||
|
{StatesOrdering.UPDATED}
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
statesHidden: boolean;
|
||||||
|
statesLocked: boolean;
|
||||||
|
statesCollapsed: boolean;
|
||||||
|
statesOrdering: StatesOrdering;
|
||||||
|
changeStatesOrdering(value: StatesOrdering): void;
|
||||||
|
lockAllStates(): void;
|
||||||
|
unlockAllStates(): void;
|
||||||
|
collapseAllStates(): void;
|
||||||
|
expandAllStates(): void;
|
||||||
|
hideAllStates(): void;
|
||||||
|
showAllStates(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Header = React.memo((props: Props): JSX.Element => {
|
||||||
|
const {
|
||||||
|
statesHidden,
|
||||||
|
statesLocked,
|
||||||
|
statesCollapsed,
|
||||||
|
statesOrdering,
|
||||||
|
changeStatesOrdering,
|
||||||
|
lockAllStates,
|
||||||
|
unlockAllStates,
|
||||||
|
collapseAllStates,
|
||||||
|
expandAllStates,
|
||||||
|
hideAllStates,
|
||||||
|
showAllStates,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='cvat-objects-sidebar-states-header'>
|
||||||
|
<Row>
|
||||||
|
<Col>
|
||||||
|
<Input
|
||||||
|
placeholder='Filter e.g. car[attr/model="mazda"]'
|
||||||
|
prefix={<Icon type='filter' />}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row type='flex' justify='space-between' align='middle'>
|
||||||
|
<Col span={2}>
|
||||||
|
{ statesLocked
|
||||||
|
? <Icon type='lock' onClick={unlockAllStates} />
|
||||||
|
: <Icon type='unlock' onClick={lockAllStates} />
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
<Col span={2}>
|
||||||
|
{ statesHidden
|
||||||
|
? <Icon type='eye-invisible' onClick={showAllStates} />
|
||||||
|
: <Icon type='eye' onClick={hideAllStates} />
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
<Col span={2}>
|
||||||
|
{ statesCollapsed
|
||||||
|
? <Icon type='caret-down' onClick={expandAllStates} />
|
||||||
|
: <Icon type='caret-up' onClick={collapseAllStates} />
|
||||||
|
}
|
||||||
|
</Col>
|
||||||
|
<StatesOrderingSelector
|
||||||
|
statesOrdering={statesOrdering}
|
||||||
|
changeStatesOrdering={changeStatesOrdering}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Header;
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { StatesOrdering } from 'reducers/interfaces';
|
||||||
|
|
||||||
|
import ObjectItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-item';
|
||||||
|
import Header from './objects-list-header';
|
||||||
|
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
listHeight: number;
|
||||||
|
statesHidden: boolean;
|
||||||
|
statesLocked: boolean;
|
||||||
|
statesCollapsed: boolean;
|
||||||
|
statesOrdering: StatesOrdering;
|
||||||
|
sortedStatesID: number[];
|
||||||
|
changeStatesOrdering(value: StatesOrdering): void;
|
||||||
|
lockAllStates(): void;
|
||||||
|
unlockAllStates(): void;
|
||||||
|
collapseAllStates(): void;
|
||||||
|
expandAllStates(): void;
|
||||||
|
hideAllStates(): void;
|
||||||
|
showAllStates(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ObjectListComponent = React.memo((props: Props): JSX.Element => {
|
||||||
|
const {
|
||||||
|
listHeight,
|
||||||
|
statesHidden,
|
||||||
|
statesLocked,
|
||||||
|
statesCollapsed,
|
||||||
|
statesOrdering,
|
||||||
|
sortedStatesID,
|
||||||
|
changeStatesOrdering,
|
||||||
|
lockAllStates,
|
||||||
|
unlockAllStates,
|
||||||
|
collapseAllStates,
|
||||||
|
expandAllStates,
|
||||||
|
hideAllStates,
|
||||||
|
showAllStates,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ height: listHeight }}>
|
||||||
|
<Header
|
||||||
|
statesHidden={statesHidden}
|
||||||
|
statesLocked={statesLocked}
|
||||||
|
statesCollapsed={statesCollapsed}
|
||||||
|
statesOrdering={statesOrdering}
|
||||||
|
changeStatesOrdering={changeStatesOrdering}
|
||||||
|
lockAllStates={lockAllStates}
|
||||||
|
unlockAllStates={unlockAllStates}
|
||||||
|
collapseAllStates={collapseAllStates}
|
||||||
|
expandAllStates={expandAllStates}
|
||||||
|
hideAllStates={hideAllStates}
|
||||||
|
showAllStates={showAllStates}
|
||||||
|
/>
|
||||||
|
<div className='cvat-objects-sidebar-states-list'>
|
||||||
|
{ sortedStatesID.map((id: number): JSX.Element => (
|
||||||
|
<ObjectItemContainer key={id} clientID={id} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ObjectListComponent;
|
||||||
@ -1,66 +1,86 @@
|
|||||||
|
import './styles.scss';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Icon,
|
Icon,
|
||||||
|
Tabs,
|
||||||
Layout,
|
Layout,
|
||||||
|
Collapse,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
|
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
|
||||||
|
import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list';
|
||||||
|
import LabelsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/labels-list';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onSidebarFoldUnfold(): void;
|
sidebarCollapsed: boolean;
|
||||||
|
appearanceCollapsed: boolean;
|
||||||
|
collapseSidebar(): void;
|
||||||
|
collapseAppearance(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
const ObjectsSideBar = React.memo((props: Props): JSX.Element => {
|
||||||
collapsed: boolean;
|
const {
|
||||||
}
|
sidebarCollapsed,
|
||||||
|
appearanceCollapsed,
|
||||||
|
collapseSidebar,
|
||||||
|
collapseAppearance,
|
||||||
|
} = props;
|
||||||
|
|
||||||
export default class StandardWorkspaceComponent extends React.PureComponent<Props, State> {
|
return (
|
||||||
public constructor(props: any) {
|
<Layout.Sider
|
||||||
super(props);
|
className='cvat-objects-sidebar'
|
||||||
this.state = {
|
theme='light'
|
||||||
collapsed: true,
|
width={300}
|
||||||
};
|
collapsedWidth={0}
|
||||||
}
|
reverseArrow
|
||||||
|
collapsible
|
||||||
|
trigger={null}
|
||||||
|
collapsed={sidebarCollapsed}
|
||||||
|
>
|
||||||
|
{/* eslint-disable-next-line */}
|
||||||
|
<span
|
||||||
|
className={`cvat-objects-sidebar-sider
|
||||||
|
ant-layout-sider-zero-width-trigger
|
||||||
|
ant-layout-sider-zero-width-trigger-left`}
|
||||||
|
onClick={collapseSidebar}
|
||||||
|
>
|
||||||
|
{sidebarCollapsed ? <Icon type='menu-fold' title='Show' />
|
||||||
|
: <Icon type='menu-unfold' title='Hide' />}
|
||||||
|
</span>
|
||||||
|
|
||||||
public render(): JSX.Element {
|
<Tabs type='card' defaultActiveKey='objects' className='cvat-objects-sidebar-tabs'>
|
||||||
const { collapsed } = this.state;
|
<Tabs.TabPane
|
||||||
const { onSidebarFoldUnfold } = this.props;
|
tab={<Text strong>Objects</Text>}
|
||||||
|
key='objects'
|
||||||
|
>
|
||||||
|
<ObjectsListContainer />
|
||||||
|
</Tabs.TabPane>
|
||||||
|
<Tabs.TabPane
|
||||||
|
tab={<Text strong>Labels</Text>}
|
||||||
|
key='labels'
|
||||||
|
>
|
||||||
|
<LabelsListContainer />
|
||||||
|
</Tabs.TabPane>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
return (
|
<Collapse
|
||||||
<Layout.Sider
|
onChange={collapseAppearance}
|
||||||
className='cvat-annotation-page-objects-sidebar'
|
activeKey={appearanceCollapsed ? [] : ['appearance']}
|
||||||
theme='light'
|
className='cvat-objects-appearance-collapse'
|
||||||
width={300}
|
|
||||||
collapsedWidth={0}
|
|
||||||
reverseArrow
|
|
||||||
collapsible
|
|
||||||
trigger={null}
|
|
||||||
collapsed={collapsed}
|
|
||||||
>
|
>
|
||||||
{/* eslint-disable-next-line */}
|
<Collapse.Panel
|
||||||
<span
|
header={
|
||||||
className={`cvat-annotation-page-objects-sidebar
|
<Text strong>Appearance</Text>
|
||||||
ant-layout-sider-zero-width-trigger
|
}
|
||||||
ant-layout-sider-zero-width-trigger-left`}
|
key='appearance'
|
||||||
onClick={(): void => {
|
|
||||||
this.setState(
|
|
||||||
(prevState: State): State => ({
|
|
||||||
collapsed: !prevState.collapsed,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const [sidebar] = window.document
|
|
||||||
.getElementsByClassName('cvat-annotation-page-objects-sidebar');
|
|
||||||
sidebar.addEventListener('transitionend', () => {
|
|
||||||
onSidebarFoldUnfold();
|
|
||||||
}, { once: true });
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{collapsed ? <Icon type='menu-fold' title='Show' />
|
|
||||||
: <Icon type='menu-unfold' title='Hide' />}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
Right sidebar
|
</Collapse.Panel>
|
||||||
</Layout.Sider>
|
</Collapse>
|
||||||
);
|
</Layout.Sider>
|
||||||
}
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
export default ObjectsSideBar;
|
||||||
|
|||||||
@ -0,0 +1,248 @@
|
|||||||
|
@import 'base.scss';
|
||||||
|
|
||||||
|
.cvat-objects-appearance-collapse.ant-collapse {
|
||||||
|
width: 100%;
|
||||||
|
bottom: 0px;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 0px;
|
||||||
|
|
||||||
|
> .ant-collapse-item {
|
||||||
|
border: none;
|
||||||
|
> .ant-collapse-header {
|
||||||
|
padding-top: 2.5px;
|
||||||
|
padding-bottom: 2.5px;
|
||||||
|
background: $header-color;
|
||||||
|
border-radius: 0px;
|
||||||
|
height: 25px;
|
||||||
|
}
|
||||||
|
> .ant-collapse-content {
|
||||||
|
background: $background-color-2;
|
||||||
|
border-bottom: none;
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-object-sidebar-icon {
|
||||||
|
fill: $objects-bar-icons-color;
|
||||||
|
color: $objects-bar-icons-color;
|
||||||
|
|
||||||
|
font-size: 21px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-objects-sidebar-tabs.ant-tabs.ant-tabs-card {
|
||||||
|
background: $header-color;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
.ant-tabs-card-bar {
|
||||||
|
border: none;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
padding-top: 25px;
|
||||||
|
|
||||||
|
.ant-tabs-tab {
|
||||||
|
background: $transparent-color;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&:nth-child(1) {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-tabs-tab.ant-tabs-tab-active {
|
||||||
|
background: $objects-bar-tabs-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-objects-sidebar-states-header {
|
||||||
|
background: $objects-bar-tabs-color;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
> div:nth-child(1) > div > span {
|
||||||
|
> input {
|
||||||
|
text-indent: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
@extend .cvat-object-sidebar-icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:nth-child(2) {
|
||||||
|
margin-top: 5px;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
text-align: center;
|
||||||
|
margin: 0px 2px;
|
||||||
|
|
||||||
|
> i {
|
||||||
|
@extend .cvat-object-sidebar-icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(4) {
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
> .ant-select {
|
||||||
|
margin-left: 5px;
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-objects-sidebar-states-header {
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-objects-sidebar-states-list {
|
||||||
|
background-color: $background-color-2;
|
||||||
|
height: calc(100% - 80px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-objects-sidebar-state-active-item {
|
||||||
|
background: $active-object-item-background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-objects-sidebar-state-item {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px 5px 5px 5px;
|
||||||
|
border-left-width: 5px;
|
||||||
|
border-bottom: 1px dashed;
|
||||||
|
|
||||||
|
> div:nth-child(1) {
|
||||||
|
> div:nth-child(3) > i {
|
||||||
|
@extend .cvat-object-sidebar-icon;
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:nth-child(2) > .ant-select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:nth-child(2) {
|
||||||
|
> div > div {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
@extend .cvat-object-sidebar-icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:nth-child(3) {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@extend .cvat-objects-sidebar-state-active-item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-objects-sidebar-state-item-collapse {
|
||||||
|
border: 0px;
|
||||||
|
background: inherit;
|
||||||
|
|
||||||
|
> .ant-collapse-item {
|
||||||
|
background: inherit;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
> .ant-collapse-header {
|
||||||
|
background: inherit;
|
||||||
|
padding-top: 2px;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .ant-collapse-content {
|
||||||
|
background: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-object-item-attribute-wrapper {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-object-item-select-attribute {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-object-item-number-attribute {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-object-item-text-attribute {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-object-item-radio-attribute {
|
||||||
|
border: 1px double $border-color-hover;
|
||||||
|
border-radius: 7px 7px 7px 7px;
|
||||||
|
|
||||||
|
> legend {
|
||||||
|
text-align: center;
|
||||||
|
width: unset;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 80%;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 1.2em;
|
||||||
|
font-size: 1.2em;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .ant-radio-group {
|
||||||
|
display: grid;
|
||||||
|
padding: 5px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-objects-sidebar-labels-list {
|
||||||
|
background-color: $background-color-2;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-objects-sidebar-label-active-item {
|
||||||
|
background: $active-object-item-background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-objects-sidebar-label-item {
|
||||||
|
height: 2.5em;
|
||||||
|
border-bottom: 1px solid $border-color-2;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
@extend .cvat-object-sidebar-icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:nth-child(2) {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 1.5em;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@extend .cvat-objects-sidebar-label-active-item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-label-item-color-button {
|
||||||
|
width: 30px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Col,
|
||||||
|
Icon,
|
||||||
|
Modal,
|
||||||
|
Timeline,
|
||||||
|
} from 'antd';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MainMenuIcon,
|
||||||
|
SaveIcon,
|
||||||
|
UndoIcon,
|
||||||
|
RedoIcon,
|
||||||
|
} from '../../../icons';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
saving: boolean;
|
||||||
|
savingStatuses: string[];
|
||||||
|
onSaveAnnotation(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const LeftGroup = React.memo((props: Props): JSX.Element => {
|
||||||
|
const {
|
||||||
|
saving,
|
||||||
|
savingStatuses,
|
||||||
|
onSaveAnnotation,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col className='cvat-annotation-header-left-group'>
|
||||||
|
<div className='cvat-annotation-header-button'>
|
||||||
|
<Icon component={MainMenuIcon} />
|
||||||
|
<span>Menu</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={saving
|
||||||
|
? 'cvat-annotation-disabled-header-button'
|
||||||
|
: 'cvat-annotation-header-button'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Icon component={SaveIcon} onClick={onSaveAnnotation} />
|
||||||
|
<span>
|
||||||
|
{ saving ? 'Saving...' : 'Save' }
|
||||||
|
</span>
|
||||||
|
<Modal
|
||||||
|
title='Saving changes on the server'
|
||||||
|
visible={saving}
|
||||||
|
footer={[]}
|
||||||
|
closable={false}
|
||||||
|
>
|
||||||
|
<Timeline pending={savingStatuses[savingStatuses.length - 1] || 'Pending..'}>
|
||||||
|
{
|
||||||
|
savingStatuses.slice(0, -1)
|
||||||
|
.map((
|
||||||
|
status: string,
|
||||||
|
id: number,
|
||||||
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
|
) => <Timeline.Item key={id}>{status}</Timeline.Item>)
|
||||||
|
}
|
||||||
|
</Timeline>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
<div className='cvat-annotation-header-button'>
|
||||||
|
<Icon component={UndoIcon} />
|
||||||
|
<span>Undo</span>
|
||||||
|
</div>
|
||||||
|
<div className='cvat-annotation-header-button'>
|
||||||
|
<Icon component={RedoIcon} />
|
||||||
|
<span>Redo</span>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default LeftGroup;
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Col,
|
||||||
|
Icon,
|
||||||
|
Tooltip,
|
||||||
|
} from 'antd';
|
||||||
|
|
||||||
|
import {
|
||||||
|
FirstIcon,
|
||||||
|
BackJumpIcon,
|
||||||
|
PreviousIcon,
|
||||||
|
PlayIcon,
|
||||||
|
PauseIcon,
|
||||||
|
NextIcon,
|
||||||
|
ForwardJumpIcon,
|
||||||
|
LastIcon,
|
||||||
|
} from '../../../icons';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
playing: boolean;
|
||||||
|
onSwitchPlay(): void;
|
||||||
|
onPrevFrame(): void;
|
||||||
|
onNextFrame(): void;
|
||||||
|
onForward(): void;
|
||||||
|
onBackward(): void;
|
||||||
|
onFirstFrame(): void;
|
||||||
|
onLastFrame(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlayerButtons = React.memo((props: Props): JSX.Element => {
|
||||||
|
const {
|
||||||
|
playing,
|
||||||
|
onSwitchPlay,
|
||||||
|
onPrevFrame,
|
||||||
|
onNextFrame,
|
||||||
|
onForward,
|
||||||
|
onBackward,
|
||||||
|
onFirstFrame,
|
||||||
|
onLastFrame,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Col className='cvat-player-buttons'>
|
||||||
|
<Tooltip overlay='Go to the first frame'>
|
||||||
|
<Icon component={FirstIcon} onClick={onFirstFrame} />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip overlay='Go back with a step'>
|
||||||
|
<Icon component={BackJumpIcon} onClick={onBackward} />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip overlay='Go back'>
|
||||||
|
<Icon component={PreviousIcon} onClick={onPrevFrame} />
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
{!playing
|
||||||
|
? (
|
||||||
|
<Tooltip overlay='Play'>
|
||||||
|
<Icon
|
||||||
|
component={PlayIcon}
|
||||||
|
onClick={onSwitchPlay}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<Tooltip overlay='Pause'>
|
||||||
|
<Icon
|
||||||
|
component={PauseIcon}
|
||||||
|
onClick={onSwitchPlay}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<Tooltip overlay='Go next'>
|
||||||
|
<Icon component={NextIcon} onClick={onNextFrame} />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip overlay='Go next with a step'>
|
||||||
|
<Icon component={ForwardJumpIcon} onClick={onForward} />
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip overlay='Go to the last frame'>
|
||||||
|
<Icon component={LastIcon} onClick={onLastFrame} />
|
||||||
|
</Tooltip>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default PlayerButtons;
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Slider,
|
||||||
|
Tooltip,
|
||||||
|
InputNumber,
|
||||||
|
} from 'antd';
|
||||||
|
|
||||||
|
import { SliderValue } from 'antd/lib/slider';
|
||||||
|
import Text from 'antd/lib/typography/Text';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
startFrame: number;
|
||||||
|
stopFrame: number;
|
||||||
|
frameNumber: number;
|
||||||
|
onSliderChange(value: SliderValue): void;
|
||||||
|
onInputChange(value: number | undefined): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlayerNavigation = React.memo((props: Props): JSX.Element => {
|
||||||
|
const {
|
||||||
|
startFrame,
|
||||||
|
stopFrame,
|
||||||
|
frameNumber,
|
||||||
|
onSliderChange,
|
||||||
|
onInputChange,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Col className='cvat-player-controls'>
|
||||||
|
<Row type='flex'>
|
||||||
|
<Col>
|
||||||
|
<Slider
|
||||||
|
className='cvat-player-slider'
|
||||||
|
min={startFrame}
|
||||||
|
max={stopFrame}
|
||||||
|
value={frameNumber || 0}
|
||||||
|
onChange={onSliderChange}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row type='flex' justify='space-around'>
|
||||||
|
<Col className='cvat-player-filename-wrapper'>
|
||||||
|
<Tooltip overlay='filename.png'>
|
||||||
|
<Text type='secondary'>filename.png</Text>
|
||||||
|
</Tooltip>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<InputNumber
|
||||||
|
className='cvat-player-frame-selector'
|
||||||
|
type='number'
|
||||||
|
value={frameNumber || 0}
|
||||||
|
// https://stackoverflow.com/questions/38256332/in-react-whats-the-difference-between-onchange-and-oninput
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default PlayerNavigation;
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Col,
|
||||||
|
Icon,
|
||||||
|
Select,
|
||||||
|
} from 'antd';
|
||||||
|
|
||||||
|
import {
|
||||||
|
InfoIcon,
|
||||||
|
FullscreenIcon,
|
||||||
|
} from '../../../icons';
|
||||||
|
|
||||||
|
const RightGroup = React.memo((): JSX.Element => (
|
||||||
|
<Col className='cvat-annotation-header-right-group'>
|
||||||
|
<div className='cvat-annotation-header-button'>
|
||||||
|
<Icon component={FullscreenIcon} />
|
||||||
|
<span>Fullscreen</span>
|
||||||
|
</div>
|
||||||
|
<div className='cvat-annotation-header-button'>
|
||||||
|
<Icon component={InfoIcon} />
|
||||||
|
<span>Info</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Select className='cvat-workspace-selector' defaultValue='standard'>
|
||||||
|
<Select.Option key='standard' value='standard'>Standard</Select.Option>
|
||||||
|
<Select.Option key='aam' value='aam'>Attribute annotation</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
));
|
||||||
|
|
||||||
|
export default RightGroup;
|
||||||
@ -0,0 +1,211 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
changeLabelColor as changeLabelColorAction,
|
||||||
|
updateAnnotationsAsync,
|
||||||
|
} from 'actions/annotation-actions';
|
||||||
|
|
||||||
|
import LabelItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/label-item';
|
||||||
|
import { CombinedState } from 'reducers/interfaces';
|
||||||
|
|
||||||
|
|
||||||
|
interface OwnProps {
|
||||||
|
labelID: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateToProps {
|
||||||
|
label: any;
|
||||||
|
labelName: string;
|
||||||
|
labelColor: string;
|
||||||
|
labelColors: string[];
|
||||||
|
objectStates: any[];
|
||||||
|
jobInstance: any;
|
||||||
|
frameNumber: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DispatchToProps {
|
||||||
|
updateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void;
|
||||||
|
changeLabelColor(label: any, color: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
|
||||||
|
const {
|
||||||
|
annotation: {
|
||||||
|
annotations: {
|
||||||
|
states: objectStates,
|
||||||
|
},
|
||||||
|
job: {
|
||||||
|
instance: jobInstance,
|
||||||
|
labels,
|
||||||
|
},
|
||||||
|
player: {
|
||||||
|
frame: {
|
||||||
|
number: frameNumber,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
colors: labelColors,
|
||||||
|
},
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
const [label] = labels.filter((_label: any) => _label.id === own.labelID);
|
||||||
|
|
||||||
|
return {
|
||||||
|
label,
|
||||||
|
labelColor: label.color,
|
||||||
|
labelName: label.name,
|
||||||
|
labelColors,
|
||||||
|
objectStates,
|
||||||
|
jobInstance,
|
||||||
|
frameNumber,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||||
|
return {
|
||||||
|
updateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void {
|
||||||
|
dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, states));
|
||||||
|
},
|
||||||
|
changeLabelColor(label: any, color: string): void {
|
||||||
|
dispatch(changeLabelColorAction(label, color));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = StateToProps & DispatchToProps & OwnProps;
|
||||||
|
interface State {
|
||||||
|
objectStates: any[];
|
||||||
|
ownObjectStates: any[];
|
||||||
|
visible: boolean;
|
||||||
|
statesHidden: boolean;
|
||||||
|
statesLocked: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LabelItemContainer extends React.PureComponent<Props, State> {
|
||||||
|
public constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
objectStates: [],
|
||||||
|
ownObjectStates: [],
|
||||||
|
visible: true,
|
||||||
|
statesHidden: false,
|
||||||
|
statesLocked: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props: Props, state: State): State | null {
|
||||||
|
if (props.objectStates === state.objectStates) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ownObjectStates = props.objectStates
|
||||||
|
.filter((ownObjectState: any): boolean => ownObjectState.label.id === props.labelID);
|
||||||
|
const visible = !!ownObjectStates.length;
|
||||||
|
let statesHidden = true;
|
||||||
|
let statesLocked = true;
|
||||||
|
|
||||||
|
ownObjectStates.forEach((objectState: any) => {
|
||||||
|
statesHidden = statesHidden && objectState.hidden;
|
||||||
|
statesLocked = statesLocked && objectState.lock;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
objectStates: props.objectStates,
|
||||||
|
ownObjectStates,
|
||||||
|
statesHidden,
|
||||||
|
statesLocked,
|
||||||
|
visible,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private hideStates = (): void => {
|
||||||
|
this.switchHidden(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
private showStates = (): void => {
|
||||||
|
this.switchHidden(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
private lockStates = (): void => {
|
||||||
|
this.switchLock(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
private unlockStates = (): void => {
|
||||||
|
this.switchLock(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
private changeColor = (color: string): void => {
|
||||||
|
const {
|
||||||
|
changeLabelColor,
|
||||||
|
label,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
changeLabelColor(label, color);
|
||||||
|
};
|
||||||
|
|
||||||
|
private switchHidden(value: boolean): void {
|
||||||
|
const {
|
||||||
|
updateAnnotations,
|
||||||
|
jobInstance,
|
||||||
|
frameNumber,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { ownObjectStates } = this.state;
|
||||||
|
for (const state of ownObjectStates) {
|
||||||
|
state.hidden = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAnnotations(jobInstance, frameNumber, ownObjectStates);
|
||||||
|
}
|
||||||
|
|
||||||
|
private switchLock(value: boolean): void {
|
||||||
|
const {
|
||||||
|
updateAnnotations,
|
||||||
|
jobInstance,
|
||||||
|
frameNumber,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { ownObjectStates } = this.state;
|
||||||
|
for (const state of ownObjectStates) {
|
||||||
|
state.lock = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAnnotations(jobInstance, frameNumber, ownObjectStates);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
const {
|
||||||
|
visible,
|
||||||
|
statesHidden,
|
||||||
|
statesLocked,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
const {
|
||||||
|
labelName,
|
||||||
|
labelColor,
|
||||||
|
labelColors,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LabelItemComponent
|
||||||
|
labelName={labelName}
|
||||||
|
labelColor={labelColor}
|
||||||
|
labelColors={labelColors}
|
||||||
|
visible={visible}
|
||||||
|
statesHidden={statesHidden}
|
||||||
|
statesLocked={statesLocked}
|
||||||
|
hideStates={this.hideStates}
|
||||||
|
showStates={this.showStates}
|
||||||
|
lockStates={this.lockStates}
|
||||||
|
unlockStates={this.unlockStates}
|
||||||
|
changeColor={this.changeColor}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect<StateToProps, DispatchToProps, OwnProps, CombinedState>(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(LabelItemContainer);
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import LabelsListComponent from 'components/annotation-page/standard-workspace/objects-side-bar/labels-list';
|
||||||
|
import { CombinedState } from 'reducers/interfaces';
|
||||||
|
|
||||||
|
interface StateToProps {
|
||||||
|
labelIDs: number[];
|
||||||
|
listHeight: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state: CombinedState): StateToProps {
|
||||||
|
const {
|
||||||
|
annotation: {
|
||||||
|
job: {
|
||||||
|
labels,
|
||||||
|
},
|
||||||
|
tabContentHeight: listHeight,
|
||||||
|
},
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
return {
|
||||||
|
labelIDs: labels.map((label: any): number => label.id),
|
||||||
|
listHeight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function LabelsListContainer(props: StateToProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<LabelsListComponent {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
)(LabelsListContainer);
|
||||||
@ -0,0 +1,226 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
CombinedState,
|
||||||
|
} from 'reducers/interfaces';
|
||||||
|
import {
|
||||||
|
collapseObjectItems,
|
||||||
|
updateAnnotationsAsync,
|
||||||
|
} from 'actions/annotation-actions';
|
||||||
|
|
||||||
|
import ObjectStateItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item';
|
||||||
|
|
||||||
|
interface OwnProps {
|
||||||
|
clientID: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StateToProps {
|
||||||
|
objectState: any;
|
||||||
|
collapsed: boolean;
|
||||||
|
labels: any[];
|
||||||
|
attributes: any[];
|
||||||
|
jobInstance: any;
|
||||||
|
frameNumber: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DispatchToProps {
|
||||||
|
updateState(sessionInstance: any, frameNumber: number, objectState: any): void;
|
||||||
|
collapseOrExpand(objectStates: any[], collapsed: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
|
||||||
|
const {
|
||||||
|
annotation: {
|
||||||
|
annotations: {
|
||||||
|
states,
|
||||||
|
collapsed: statesCollapsed,
|
||||||
|
},
|
||||||
|
job: {
|
||||||
|
labels,
|
||||||
|
attributes: jobAttributes,
|
||||||
|
instance: jobInstance,
|
||||||
|
},
|
||||||
|
player: {
|
||||||
|
frame: {
|
||||||
|
number: frameNumber,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
const index = states
|
||||||
|
.map((_state: any): number => _state.clientID)
|
||||||
|
.indexOf(own.clientID);
|
||||||
|
|
||||||
|
const collapsedState = typeof (statesCollapsed[own.clientID]) === 'undefined'
|
||||||
|
? true : statesCollapsed[own.clientID];
|
||||||
|
|
||||||
|
return {
|
||||||
|
objectState: states[index],
|
||||||
|
collapsed: collapsedState,
|
||||||
|
attributes: jobAttributes[states[index].label.id],
|
||||||
|
labels,
|
||||||
|
jobInstance,
|
||||||
|
frameNumber,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||||
|
return {
|
||||||
|
updateState(sessionInstance: any, frameNumber: number, state: any): void {
|
||||||
|
dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, [state]));
|
||||||
|
},
|
||||||
|
collapseOrExpand(objectStates: any[], collapsed: boolean): void {
|
||||||
|
dispatch(collapseObjectItems(objectStates, collapsed));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = StateToProps & DispatchToProps;
|
||||||
|
class ObjectItemContainer extends React.PureComponent<Props> {
|
||||||
|
private lock = (): void => {
|
||||||
|
const { objectState } = this.props;
|
||||||
|
objectState.lock = true;
|
||||||
|
this.commit();
|
||||||
|
};
|
||||||
|
|
||||||
|
private unlock = (): void => {
|
||||||
|
const { objectState } = this.props;
|
||||||
|
objectState.lock = false;
|
||||||
|
this.commit();
|
||||||
|
};
|
||||||
|
|
||||||
|
private show = (): void => {
|
||||||
|
const { objectState } = this.props;
|
||||||
|
objectState.hidden = false;
|
||||||
|
this.commit();
|
||||||
|
};
|
||||||
|
|
||||||
|
private hide = (): void => {
|
||||||
|
const { objectState } = this.props;
|
||||||
|
objectState.hidden = true;
|
||||||
|
this.commit();
|
||||||
|
};
|
||||||
|
|
||||||
|
private setOccluded = (): void => {
|
||||||
|
const { objectState } = this.props;
|
||||||
|
objectState.occluded = true;
|
||||||
|
this.commit();
|
||||||
|
};
|
||||||
|
|
||||||
|
private unsetOccluded = (): void => {
|
||||||
|
const { objectState } = this.props;
|
||||||
|
objectState.occluded = false;
|
||||||
|
this.commit();
|
||||||
|
};
|
||||||
|
|
||||||
|
private setOutside = (): void => {
|
||||||
|
const { objectState } = this.props;
|
||||||
|
objectState.outside = true;
|
||||||
|
this.commit();
|
||||||
|
};
|
||||||
|
|
||||||
|
private unsetOutside = (): void => {
|
||||||
|
const { objectState } = this.props;
|
||||||
|
objectState.outside = false;
|
||||||
|
this.commit();
|
||||||
|
};
|
||||||
|
|
||||||
|
private setKeyframe = (): void => {
|
||||||
|
const { objectState } = this.props;
|
||||||
|
objectState.keyframe = true;
|
||||||
|
this.commit();
|
||||||
|
};
|
||||||
|
|
||||||
|
private unsetKeyframe = (): void => {
|
||||||
|
const { objectState } = this.props;
|
||||||
|
objectState.keyframe = false;
|
||||||
|
this.commit();
|
||||||
|
};
|
||||||
|
|
||||||
|
private collapse = (): void => {
|
||||||
|
const {
|
||||||
|
collapseOrExpand,
|
||||||
|
objectState,
|
||||||
|
collapsed,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
collapseOrExpand([objectState], !collapsed);
|
||||||
|
};
|
||||||
|
|
||||||
|
private changeLabel = (labelID: string): void => {
|
||||||
|
const {
|
||||||
|
objectState,
|
||||||
|
labels,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const [label] = labels.filter((_label: any): boolean => _label.id === +labelID);
|
||||||
|
objectState.label = label;
|
||||||
|
this.commit();
|
||||||
|
};
|
||||||
|
|
||||||
|
private changeAttribute = (id: number, value: string): void => {
|
||||||
|
const { objectState } = this.props;
|
||||||
|
const attr: Record<number, string> = {};
|
||||||
|
attr[id] = value;
|
||||||
|
objectState.attributes = attr;
|
||||||
|
this.commit();
|
||||||
|
};
|
||||||
|
|
||||||
|
private commit(): void {
|
||||||
|
const {
|
||||||
|
objectState,
|
||||||
|
updateState,
|
||||||
|
jobInstance,
|
||||||
|
frameNumber,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
updateState(jobInstance, frameNumber, objectState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
const {
|
||||||
|
objectState,
|
||||||
|
collapsed,
|
||||||
|
labels,
|
||||||
|
attributes,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ObjectStateItemComponent
|
||||||
|
objectType={objectState.objectType}
|
||||||
|
shapeType={objectState.shapeType}
|
||||||
|
clientID={objectState.clientID}
|
||||||
|
occluded={objectState.occluded}
|
||||||
|
outside={objectState.outside}
|
||||||
|
locked={objectState.lock}
|
||||||
|
hidden={objectState.hidden}
|
||||||
|
keyframe={objectState.keyframe}
|
||||||
|
attrValues={{ ...objectState.attributes }}
|
||||||
|
labelID={objectState.label.id}
|
||||||
|
color={objectState.color}
|
||||||
|
attributes={attributes}
|
||||||
|
labels={labels}
|
||||||
|
collapsed={collapsed}
|
||||||
|
setOccluded={this.setOccluded}
|
||||||
|
unsetOccluded={this.unsetOccluded}
|
||||||
|
setOutside={this.setOutside}
|
||||||
|
unsetOutside={this.unsetOutside}
|
||||||
|
setKeyframe={this.setKeyframe}
|
||||||
|
unsetKeyframe={this.unsetKeyframe}
|
||||||
|
lock={this.lock}
|
||||||
|
unlock={this.unlock}
|
||||||
|
hide={this.hide}
|
||||||
|
show={this.show}
|
||||||
|
changeLabel={this.changeLabel}
|
||||||
|
changeAttribute={this.changeAttribute}
|
||||||
|
collapse={this.collapse}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect<StateToProps, DispatchToProps, OwnProps, CombinedState>(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(ObjectItemContainer);
|
||||||
@ -0,0 +1,246 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import ObjectsListComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-list';
|
||||||
|
import {
|
||||||
|
updateAnnotationsAsync,
|
||||||
|
collapseObjectItems,
|
||||||
|
} from 'actions/annotation-actions';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CombinedState,
|
||||||
|
StatesOrdering,
|
||||||
|
} from 'reducers/interfaces';
|
||||||
|
|
||||||
|
interface StateToProps {
|
||||||
|
jobInstance: any;
|
||||||
|
frameNumber: any;
|
||||||
|
listHeight: number;
|
||||||
|
statesHidden: boolean;
|
||||||
|
statesLocked: boolean;
|
||||||
|
statesCollapsed: boolean;
|
||||||
|
objectStates: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DispatchToProps {
|
||||||
|
onUpdateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void;
|
||||||
|
onCollapseStates(states: any[], value: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state: CombinedState): StateToProps {
|
||||||
|
const {
|
||||||
|
annotation: {
|
||||||
|
annotations: {
|
||||||
|
states: objectStates,
|
||||||
|
collapsed,
|
||||||
|
},
|
||||||
|
job: {
|
||||||
|
instance: jobInstance,
|
||||||
|
},
|
||||||
|
player: {
|
||||||
|
frame: {
|
||||||
|
number: frameNumber,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tabContentHeight: listHeight,
|
||||||
|
},
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
let statesHidden = true;
|
||||||
|
let statesLocked = true;
|
||||||
|
let statesCollapsed = true;
|
||||||
|
|
||||||
|
objectStates.forEach((objectState: any) => {
|
||||||
|
const { clientID } = objectState;
|
||||||
|
statesHidden = statesHidden && objectState.hidden;
|
||||||
|
statesLocked = statesLocked && objectState.lock;
|
||||||
|
const stateCollapsed = clientID in collapsed ? collapsed[clientID] : true;
|
||||||
|
statesCollapsed = statesCollapsed && stateCollapsed;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
listHeight,
|
||||||
|
statesHidden,
|
||||||
|
statesLocked,
|
||||||
|
statesCollapsed,
|
||||||
|
objectStates,
|
||||||
|
frameNumber,
|
||||||
|
jobInstance,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||||
|
return {
|
||||||
|
onUpdateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void {
|
||||||
|
dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, states));
|
||||||
|
},
|
||||||
|
onCollapseStates(states: any[], collapsed: boolean): void {
|
||||||
|
dispatch(collapseObjectItems(states, collapsed));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortAndMap(objectStates: any[], ordering: StatesOrdering): number[] {
|
||||||
|
let sorted = [];
|
||||||
|
if (ordering === StatesOrdering.ID_ASCENT) {
|
||||||
|
sorted = [...objectStates].sort((a: any, b: any): number => a.clientID - b.clientID);
|
||||||
|
} else if (ordering === StatesOrdering.ID_DESCENT) {
|
||||||
|
sorted = [...objectStates].sort((a: any, b: any): number => b.clientID - a.clientID);
|
||||||
|
} else {
|
||||||
|
sorted = [...objectStates].sort((a: any, b: any): number => b.updated - a.updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sorted.map((state: any) => state.clientID);
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = StateToProps & DispatchToProps;
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
statesOrdering: StatesOrdering;
|
||||||
|
objectStates: any[];
|
||||||
|
sortedStatesID: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
class ObjectsListContainer extends React.Component<Props, State> {
|
||||||
|
public constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
statesOrdering: StatesOrdering.ID_ASCENT,
|
||||||
|
objectStates: [],
|
||||||
|
sortedStatesID: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props: Props, state: State): State | null {
|
||||||
|
if (props.objectStates === state.objectStates) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
objectStates: props.objectStates,
|
||||||
|
sortedStatesID: sortAndMap(props.objectStates, state.statesOrdering),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public shouldComponentUpdate(nextProps: Props, nextState: State): boolean {
|
||||||
|
const {
|
||||||
|
objectStates,
|
||||||
|
listHeight,
|
||||||
|
statesHidden,
|
||||||
|
statesLocked,
|
||||||
|
statesCollapsed,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const { statesOrdering } = this.state;
|
||||||
|
|
||||||
|
return nextProps.objectStates.length !== objectStates.length
|
||||||
|
|| nextProps.listHeight !== listHeight
|
||||||
|
|| nextProps.statesHidden !== statesHidden
|
||||||
|
|| nextProps.statesLocked !== statesLocked
|
||||||
|
|| nextProps.statesCollapsed !== statesCollapsed
|
||||||
|
|| nextState.statesOrdering !== statesOrdering
|
||||||
|
|| (statesOrdering === StatesOrdering.UPDATED
|
||||||
|
? nextProps.objectStates !== objectStates
|
||||||
|
: nextProps.objectStates.map((nextObjectState: any, id: number): boolean => (
|
||||||
|
nextObjectState.clientID !== objectStates[id].clientID
|
||||||
|
)).some((value: boolean) => value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onChangeStatesOrdering = (statesOrdering: StatesOrdering): void => {
|
||||||
|
const { objectStates } = this.props;
|
||||||
|
this.setState({
|
||||||
|
statesOrdering,
|
||||||
|
sortedStatesID: sortAndMap(objectStates, statesOrdering),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private onLockAllStates = (): void => {
|
||||||
|
this.lockAllStates(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onUnlockAllStates = (): void => {
|
||||||
|
this.lockAllStates(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onCollapseAllStates = (): void => {
|
||||||
|
this.collapseAllStates(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onExpandAllStates = (): void => {
|
||||||
|
this.collapseAllStates(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onHideAllStates = (): void => {
|
||||||
|
this.hideAllStates(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onShowAllStates = (): void => {
|
||||||
|
this.hideAllStates(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
private lockAllStates(locked: boolean): void {
|
||||||
|
const {
|
||||||
|
objectStates,
|
||||||
|
onUpdateAnnotations,
|
||||||
|
jobInstance,
|
||||||
|
frameNumber,
|
||||||
|
} = this.props;
|
||||||
|
for (const objectState of objectStates) {
|
||||||
|
objectState.lock = locked;
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdateAnnotations(jobInstance, frameNumber, objectStates);
|
||||||
|
}
|
||||||
|
|
||||||
|
private hideAllStates(hidden: boolean): void {
|
||||||
|
const {
|
||||||
|
objectStates,
|
||||||
|
onUpdateAnnotations,
|
||||||
|
jobInstance,
|
||||||
|
frameNumber,
|
||||||
|
} = this.props;
|
||||||
|
for (const objectState of objectStates) {
|
||||||
|
objectState.hidden = hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdateAnnotations(jobInstance, frameNumber, objectStates);
|
||||||
|
}
|
||||||
|
|
||||||
|
private collapseAllStates(collapsed: boolean): void {
|
||||||
|
const {
|
||||||
|
objectStates,
|
||||||
|
onCollapseStates,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
onCollapseStates(objectStates, collapsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
const {
|
||||||
|
sortedStatesID,
|
||||||
|
statesOrdering,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ObjectsListComponent
|
||||||
|
{...this.props}
|
||||||
|
statesOrdering={statesOrdering}
|
||||||
|
sortedStatesID={sortedStatesID}
|
||||||
|
changeStatesOrdering={this.onChangeStatesOrdering}
|
||||||
|
lockAllStates={this.onLockAllStates}
|
||||||
|
unlockAllStates={this.onUnlockAllStates}
|
||||||
|
collapseAllStates={this.onCollapseAllStates}
|
||||||
|
expandAllStates={this.onExpandAllStates}
|
||||||
|
hideAllStates={this.onHideAllStates}
|
||||||
|
showAllStates={this.onShowAllStates}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(ObjectsListContainer);
|
||||||
@ -0,0 +1,103 @@
|
|||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import ObjectsSidebarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar';
|
||||||
|
import { CombinedState } from 'reducers/interfaces';
|
||||||
|
import {
|
||||||
|
collapseSidebar as collapseSidebarAction,
|
||||||
|
collapseAppearance as collapseAppearanceAction,
|
||||||
|
updateTabContentHeight as updateTabContentHeightAction,
|
||||||
|
} from 'actions/annotation-actions';
|
||||||
|
|
||||||
|
interface StateToProps {
|
||||||
|
sidebarCollapsed: boolean;
|
||||||
|
appearanceCollapsed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DispatchToProps {
|
||||||
|
collapseSidebar(): void;
|
||||||
|
collapseAppearance(): void;
|
||||||
|
updateTabContentHeight(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state: CombinedState): StateToProps {
|
||||||
|
const {
|
||||||
|
annotation: {
|
||||||
|
sidebarCollapsed,
|
||||||
|
appearanceCollapsed,
|
||||||
|
},
|
||||||
|
} = state;
|
||||||
|
|
||||||
|
return {
|
||||||
|
sidebarCollapsed,
|
||||||
|
appearanceCollapsed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeHeight(): number {
|
||||||
|
const [sidebar] = window.document.getElementsByClassName('cvat-objects-sidebar');
|
||||||
|
const [appearance] = window.document.getElementsByClassName('cvat-objects-appearance-collapse');
|
||||||
|
const [tabs] = Array.from(
|
||||||
|
window.document.querySelectorAll('.cvat-objects-sidebar-tabs > .ant-tabs-card-bar'),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sidebar && appearance && tabs) {
|
||||||
|
const maxHeight = sidebar ? sidebar.clientHeight : 0;
|
||||||
|
const appearanceHeight = appearance ? appearance.clientHeight : 0;
|
||||||
|
const tabsHeight = tabs ? tabs.clientHeight : 0;
|
||||||
|
return maxHeight - appearanceHeight - tabsHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||||
|
return {
|
||||||
|
collapseSidebar(): void {
|
||||||
|
dispatch(collapseSidebarAction());
|
||||||
|
},
|
||||||
|
collapseAppearance(): void {
|
||||||
|
dispatch(collapseAppearanceAction());
|
||||||
|
|
||||||
|
const [collapser] = window.document
|
||||||
|
.getElementsByClassName('cvat-objects-appearance-collapse');
|
||||||
|
|
||||||
|
if (collapser) {
|
||||||
|
collapser.addEventListener('transitionend', () => {
|
||||||
|
dispatch(
|
||||||
|
updateTabContentHeightAction(
|
||||||
|
computeHeight(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}, { once: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateTabContentHeight(): void {
|
||||||
|
dispatch(
|
||||||
|
updateTabContentHeightAction(
|
||||||
|
computeHeight(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = StateToProps & DispatchToProps;
|
||||||
|
class ObjectsSideBarContainer extends React.PureComponent<Props> {
|
||||||
|
public componentDidMount(): void {
|
||||||
|
const { updateTabContentHeight } = this.props;
|
||||||
|
updateTabContentHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): JSX.Element {
|
||||||
|
return (
|
||||||
|
<ObjectsSidebarComponent {...this.props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps,
|
||||||
|
)(ObjectsSideBarContainer);
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import { Canvas } from 'cvat-canvas';
|
|
||||||
|
|
||||||
import StandardWorkspaceComponent from 'components/annotation-page/standard-workspace/standard-workspace';
|
|
||||||
import { CombinedState } from 'reducers/interfaces';
|
|
||||||
|
|
||||||
|
|
||||||
interface StateToProps {
|
|
||||||
canvasInstance: Canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapStateToProps(state: CombinedState): StateToProps {
|
|
||||||
const { annotation } = state;
|
|
||||||
return {
|
|
||||||
canvasInstance: annotation.canvasInstance,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function StandardWorkspaceContainer(props: StateToProps): JSX.Element {
|
|
||||||
const {
|
|
||||||
canvasInstance,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StandardWorkspaceComponent
|
|
||||||
canvasInstance={canvasInstance}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
)(StandardWorkspaceContainer);
|
|
||||||