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 {
|
||||
Icon,
|
||||
Tabs,
|
||||
Layout,
|
||||
Collapse,
|
||||
} 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 {
|
||||
onSidebarFoldUnfold(): void;
|
||||
sidebarCollapsed: boolean;
|
||||
appearanceCollapsed: boolean;
|
||||
collapseSidebar(): void;
|
||||
collapseAppearance(): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
collapsed: boolean;
|
||||
}
|
||||
const ObjectsSideBar = React.memo((props: Props): JSX.Element => {
|
||||
const {
|
||||
sidebarCollapsed,
|
||||
appearanceCollapsed,
|
||||
collapseSidebar,
|
||||
collapseAppearance,
|
||||
} = props;
|
||||
|
||||
export default class StandardWorkspaceComponent extends React.PureComponent<Props, State> {
|
||||
public constructor(props: any) {
|
||||
super(props);
|
||||
this.state = {
|
||||
collapsed: true,
|
||||
};
|
||||
}
|
||||
return (
|
||||
<Layout.Sider
|
||||
className='cvat-objects-sidebar'
|
||||
theme='light'
|
||||
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 {
|
||||
const { collapsed } = this.state;
|
||||
const { onSidebarFoldUnfold } = this.props;
|
||||
<Tabs type='card' defaultActiveKey='objects' className='cvat-objects-sidebar-tabs'>
|
||||
<Tabs.TabPane
|
||||
tab={<Text strong>Objects</Text>}
|
||||
key='objects'
|
||||
>
|
||||
<ObjectsListContainer />
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
tab={<Text strong>Labels</Text>}
|
||||
key='labels'
|
||||
>
|
||||
<LabelsListContainer />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
|
||||
return (
|
||||
<Layout.Sider
|
||||
className='cvat-annotation-page-objects-sidebar'
|
||||
theme='light'
|
||||
width={300}
|
||||
collapsedWidth={0}
|
||||
reverseArrow
|
||||
collapsible
|
||||
trigger={null}
|
||||
collapsed={collapsed}
|
||||
<Collapse
|
||||
onChange={collapseAppearance}
|
||||
activeKey={appearanceCollapsed ? [] : ['appearance']}
|
||||
className='cvat-objects-appearance-collapse'
|
||||
>
|
||||
{/* eslint-disable-next-line */}
|
||||
<span
|
||||
className={`cvat-annotation-page-objects-sidebar
|
||||
ant-layout-sider-zero-width-trigger
|
||||
ant-layout-sider-zero-width-trigger-left`}
|
||||
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 });
|
||||
}}
|
||||
<Collapse.Panel
|
||||
header={
|
||||
<Text strong>Appearance</Text>
|
||||
}
|
||||
key='appearance'
|
||||
>
|
||||
{collapsed ? <Icon type='menu-fold' title='Show' />
|
||||
: <Icon type='menu-unfold' title='Hide' />}
|
||||
</span>
|
||||
|
||||
Right sidebar
|
||||
</Layout.Sider>
|
||||
);
|
||||
}
|
||||
}
|
||||
</Collapse.Panel>
|
||||
</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);
|
||||