[CVAT-UI]: Appearance settings in AAM, keyframe navigation and other buttons in AAM (#1820)

main
Boris Sekachev 6 years ago committed by GitHub
parent 76280be4ad
commit 757f0ade17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -45,6 +45,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Ability to change orientation for poylgons/polylines in context menu (<https://github.com/opencv/cvat/pull/1571>)
- Ability to set the first point for polygons in points context menu (<https://github.com/opencv/cvat/pull/1571>)
- Added new tag annotation workspace (<https://github.com/opencv/cvat/pull/1570>)
- Appearance block in attribute annotation mode (<https://github.com/opencv/cvat/pull/1820>)
- Keyframe navigations and some switchers in attribute annotation mode (<https://github.com/opencv/cvat/pull/1820>)
### Changed
- Removed information about e-mail from the basic user information (<https://github.com/opencv/cvat/pull/1627>)

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.5.1",
"version": "1.6.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.5.1",
"version": "1.6.0",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {

@ -8,7 +8,7 @@ import {
ActionCreator,
Store,
} from 'redux';
import { ThunkAction } from 'redux-thunk';
import { ThunkAction } from 'utils/redux';
import {
CombinedState,
@ -191,8 +191,7 @@ export enum AnnotationActionTypes {
SAVE_LOGS_FAILED = 'SAVE_LOGS_FAILED',
}
export function saveLogsAsync():
ThunkAction<Promise<void>, {}, {}, AnyAction> {
export function saveLogsAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>) => {
try {
await logger.save();
@ -236,7 +235,7 @@ export function switchZLayer(cur: number): AnyAction {
};
}
export function fetchAnnotationsAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> {
export function fetchAnnotationsAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
const {
@ -308,8 +307,7 @@ export function updateCanvasContextMenu(
};
}
export function removeAnnotationsAsync(sessionInstance: any):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
export function removeAnnotationsAsync(sessionInstance: any): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
await sessionInstance.annotations.clear();
@ -333,8 +331,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
};
}
export function uploadJobAnnotationsAsync(job: any, loader: any, file: File):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
export function uploadJobAnnotationsAsync(job: any, loader: any, file: File): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
const state: CombinedState = getStore().getState();
@ -404,8 +401,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
};
}
export function changeJobStatusAsync(jobInstance: any, status: string):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
export function changeJobStatusAsync(jobInstance: any, status: string): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const oldStatus = jobInstance.status;
try {
@ -435,8 +431,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
};
}
export function collectStatisticsAsync(sessionInstance: any):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
export function collectStatisticsAsync(sessionInstance: any): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
dispatch({
@ -477,7 +472,7 @@ export function propagateObjectAsync(
objectState: any,
from: number,
to: number,
): ThunkAction<Promise<void>, {}, {}, AnyAction> {
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
const copy = {
@ -542,7 +537,7 @@ export function changePropagateFrames(frames: number): AnyAction {
}
export function removeObjectAsync(sessionInstance: any, objectState: any, force: boolean):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
await sessionInstance.logger.log(LogType.deleteObject, { count: 1 });
@ -659,7 +654,7 @@ export function switchPlay(playing: boolean): AnyAction {
}
export function changeFrameAsync(toFrame: number, fillBuffer?: boolean, frameStep?: number):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const state: CombinedState = getStore().getState();
const { instance: job } = state.annotation.job;
@ -751,7 +746,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
}
export function undoActionAsync(sessionInstance: any, frame: number):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
const state = getStore().getState();
@ -794,7 +789,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
}
export function redoActionAsync(sessionInstance: any, frame: number):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
const state = getStore().getState();
@ -906,7 +901,7 @@ export function confirmCanvasReady(): AnyAction {
};
}
export function closeJob(): ThunkAction<Promise<void>, {}, {}, AnyAction> {
export function closeJob(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { jobInstance } = receiveAnnotationsParameters();
if (jobInstance) {
@ -924,7 +919,7 @@ export function getJobAsync(
jid: number,
initialFrame: number,
initialFilters: string[],
): ThunkAction<Promise<void>, {}, {}, AnyAction> {
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
const state: CombinedState = getStore().getState();
@ -1001,7 +996,7 @@ export function getJobAsync(
}
export function saveAnnotationsAsync(sessionInstance: any):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters();
@ -1118,8 +1113,7 @@ export function splitTrack(enabled: boolean): AnyAction {
};
}
export function updateAnnotationsAsync(statesToUpdate: any[]):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
export function updateAnnotationsAsync(statesToUpdate: any[]): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const {
jobInstance,
@ -1164,7 +1158,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
}
export function createAnnotationsAsync(sessionInstance: any, frame: number, statesToCreate: any[]):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
@ -1192,7 +1186,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
}
export function mergeAnnotationsAsync(sessionInstance: any, frame: number, statesToMerge: any[]):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
@ -1230,7 +1224,7 @@ export function groupAnnotationsAsync(
sessionInstance: any,
frame: number,
statesToGroup: any[],
): ThunkAction<Promise<void>, {}, {}, AnyAction> {
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
@ -1266,7 +1260,7 @@ export function groupAnnotationsAsync(
}
export function splitAnnotationsAsync(sessionInstance: any, frame: number, stateToSplit: any):
ThunkAction<Promise<void>, {}, {}, AnyAction> {
ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters();
try {
@ -1296,7 +1290,7 @@ ThunkAction<Promise<void>, {}, {}, AnyAction> {
export function changeLabelColorAsync(
label: any,
color: string,
): ThunkAction<Promise<void>, {}, {}, AnyAction> {
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
const {
@ -1334,7 +1328,7 @@ export function changeLabelColorAsync(
export function changeGroupColorAsync(
group: number,
color: string,
): ThunkAction<Promise<void>, {}, {}, AnyAction> {
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const state: CombinedState = getStore().getState();
const groupStates = state.annotation.annotations.states
@ -1352,7 +1346,7 @@ export function searchAnnotationsAsync(
sessionInstance: any,
frameFrom: number,
frameTo: number,
): ThunkAction<Promise<void>, {}, {}, AnyAction> {
): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
try {
const { filters } = receiveAnnotationsParameters();
@ -1371,7 +1365,7 @@ export function searchAnnotationsAsync(
};
}
export function pasteShapeAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> {
export function pasteShapeAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const {
canvas: {
@ -1430,7 +1424,7 @@ export function pasteShapeAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction>
};
}
export function repeatDrawShapeAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> {
export function repeatDrawShapeAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const {
canvas: {
@ -1494,7 +1488,7 @@ export function repeatDrawShapeAsync(): ThunkAction<Promise<void>, {}, {}, AnyAc
};
}
export function redrawShapeAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> {
export function redrawShapeAsync(): ThunkAction {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
const {
annotations: {

@ -0,0 +1,214 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React, { Dispatch } from 'react';
import { AnyAction } from 'redux';
import { connect } from 'react-redux';
import Text from 'antd/lib/typography/Text';
import Radio, { RadioChangeEvent } from 'antd/lib/radio';
import Slider, { SliderValue } from 'antd/lib/slider';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
import Collapse from 'antd/lib/collapse';
import { ColorBy, CombinedState } from 'reducers/interfaces';
import {
collapseAppearance as collapseAppearanceAction,
updateTabContentHeight as updateTabContentHeightAction,
} from 'actions/annotation-actions';
import {
changeShapesColorBy as changeShapesColorByAction,
changeShapesOpacity as changeShapesOpacityAction,
changeSelectedShapesOpacity as changeSelectedShapesOpacityAction,
changeShapesBlackBorders as changeShapesBlackBordersAction,
changeShowBitmap as changeShowBitmapAction,
changeShowProjections as changeShowProjectionsAction,
} from 'actions/settings-actions';
interface StateToProps {
appearanceCollapsed: boolean;
colorBy: ColorBy;
opacity: number;
selectedOpacity: number;
blackBorders: boolean;
showBitmap: boolean;
showProjections: boolean;
}
interface DispatchToProps {
collapseAppearance(): void;
changeShapesColorBy(event: RadioChangeEvent): void;
changeShapesOpacity(event: SliderValue): void;
changeSelectedShapesOpacity(event: SliderValue): void;
changeShapesBlackBorders(event: CheckboxChangeEvent): void;
changeShowBitmap(event: CheckboxChangeEvent): void;
changeShowProjections(event: CheckboxChangeEvent): void;
}
export 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 mapStateToProps(state: CombinedState): StateToProps {
const {
annotation: {
appearanceCollapsed,
},
settings: {
shapes: {
colorBy,
opacity,
selectedOpacity,
blackBorders,
showBitmap,
showProjections,
},
},
} = state;
return {
appearanceCollapsed,
colorBy,
opacity,
selectedOpacity,
blackBorders,
showBitmap,
showProjections,
};
}
function mapDispatchToProps(dispatch: Dispatch<AnyAction>): DispatchToProps {
return {
collapseAppearance(): void {
dispatch(collapseAppearanceAction());
const [collapser] = window.document
.getElementsByClassName('cvat-objects-appearance-collapse');
if (collapser) {
const listener = (event: Event): void => {
if ((event as TransitionEvent).propertyName === 'height') {
const height = computeHeight();
dispatch(updateTabContentHeightAction(height));
collapser.removeEventListener('transitionend', listener);
}
};
collapser.addEventListener('transitionend', listener);
}
},
changeShapesColorBy(event: RadioChangeEvent): void {
dispatch(changeShapesColorByAction(event.target.value));
},
changeShapesOpacity(value: SliderValue): void {
dispatch(changeShapesOpacityAction(value as number));
},
changeSelectedShapesOpacity(value: SliderValue): void {
dispatch(changeSelectedShapesOpacityAction(value as number));
},
changeShapesBlackBorders(event: CheckboxChangeEvent): void {
dispatch(changeShapesBlackBordersAction(event.target.checked));
},
changeShowBitmap(event: CheckboxChangeEvent): void {
dispatch(changeShowBitmapAction(event.target.checked));
},
changeShowProjections(event: CheckboxChangeEvent): void {
dispatch(changeShowProjectionsAction(event.target.checked));
},
};
}
type Props = StateToProps & DispatchToProps;
function AppearanceBlock(props: Props): JSX.Element {
const {
appearanceCollapsed,
colorBy,
opacity,
selectedOpacity,
blackBorders,
showBitmap,
showProjections,
collapseAppearance,
changeShapesColorBy,
changeShapesOpacity,
changeSelectedShapesOpacity,
changeShapesBlackBorders,
changeShowBitmap,
changeShowProjections,
} = props;
return (
<Collapse
onChange={collapseAppearance}
activeKey={appearanceCollapsed ? [] : ['appearance']}
className='cvat-objects-appearance-collapse'
>
<Collapse.Panel
header={
<Text strong>Appearance</Text>
}
key='appearance'
>
<div className='cvat-objects-appearance-content'>
<Text type='secondary'>Color by</Text>
<Radio.Group value={colorBy} onChange={changeShapesColorBy}>
<Radio.Button value={ColorBy.INSTANCE}>{ColorBy.INSTANCE}</Radio.Button>
<Radio.Button value={ColorBy.GROUP}>{ColorBy.GROUP}</Radio.Button>
<Radio.Button value={ColorBy.LABEL}>{ColorBy.LABEL}</Radio.Button>
</Radio.Group>
<Text type='secondary'>Opacity</Text>
<Slider
onChange={changeShapesOpacity}
value={opacity}
min={0}
max={100}
/>
<Text type='secondary'>Selected opacity</Text>
<Slider
onChange={changeSelectedShapesOpacity}
value={selectedOpacity}
min={0}
max={100}
/>
<Checkbox
onChange={changeShapesBlackBorders}
checked={blackBorders}
>
Black borders
</Checkbox>
<Checkbox
onChange={changeShowBitmap}
checked={showBitmap}
>
Show bitmap
</Checkbox>
<Checkbox
onChange={changeShowProjections}
checked={showProjections}
>
Show projections
</Checkbox>
</div>
</Collapse.Panel>
</Collapse>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(React.memo(AppearanceBlock));

@ -5,23 +5,25 @@
import React, { useState, useEffect } from 'react';
import { GlobalHotKeys, ExtendedKeyMapOptions } from 'react-hotkeys';
import { connect } from 'react-redux';
import { Action } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import Layout, { SiderProps } from 'antd/lib/layout';
import { SelectValue } from 'antd/lib/select';
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
import { Row, Col } from 'antd/lib/grid';
import Text from 'antd/lib/typography/Text';
import Icon from 'antd/lib/icon';
import { ThunkDispatch } from 'utils/redux';
import { Canvas } from 'cvat-canvas-wrapper';
import { LogType } from 'cvat-logger';
import {
activateObject as activateObjectAction,
updateAnnotationsAsync,
changeFrameAsync,
} from 'actions/annotation-actions';
import { CombinedState } from 'reducers/interfaces';
import { CombinedState, ObjectType } from 'reducers/interfaces';
import AnnotationsFiltersInput from 'components/annotation-page/annotations-filters-input';
import AppearanceBlock from 'components/annotation-page/appearance-block';
import ObjectButtonsContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-buttons';
import ObjectSwitcher from './object-switcher';
import AttributeSwitcher from './attribute-switcher';
import ObjectBasicsEditor from './object-basics-edtior';
@ -43,6 +45,7 @@ interface StateToProps {
interface DispatchToProps {
activateObject(clientID: number | null, attrID: number | null): void;
updateAnnotations(statesToUpdate: any[]): void;
changeFrame(frame: number): void;
}
interface LabelAttrMap {
@ -85,7 +88,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
};
}
function mapDispatchToProps(dispatch: ThunkDispatch<CombinedState, {}, Action>): DispatchToProps {
function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps {
return {
activateObject(clientID: number, attrID: number): void {
dispatch(activateObjectAction(clientID, attrID));
@ -93,6 +96,9 @@ function mapDispatchToProps(dispatch: ThunkDispatch<CombinedState, {}, Action>):
updateAnnotations(states): void {
dispatch(updateAnnotationsAsync(states));
},
changeFrame(frame: number): void {
dispatch(changeFrameAsync(frame));
},
};
}
@ -104,6 +110,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
activatedAttributeID,
jobInstance,
updateAnnotations,
changeFrame,
activateObject,
keyMap,
normalizedKeyMap,
@ -111,6 +118,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
canvasIsReady,
} = props;
const filteredStates = states.filter((state) => !state.outside && !state.hidden);
const [labelAttrMap, setLabelAttrMap] = useState(
labels.reduce((acc, label): LabelAttrMap => {
acc[label.id] = label.attributes.length ? label.attributes[0] : null;
@ -133,10 +141,11 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
setSidebarCollapsed(!sidebarCollapsed);
};
const [activeObjectState] = activatedStateID === null
? [null] : states.filter((objectState: any): boolean => (
objectState.clientID === activatedStateID
));
const indexes = filteredStates.map((state) => state.clientID);
const activatedIndex = indexes.indexOf(activatedStateID);
const activeObjectState = activatedStateID === null || activatedIndex === -1
? null : filteredStates[activatedIndex];
const activeAttribute = activeObjectState
? labelAttrMap[activeObjectState.label.id]
: null;
@ -147,24 +156,24 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
if (attribute && attribute.id !== activatedAttributeID) {
activateObject(activatedStateID, attribute ? attribute.id : null);
}
} else if (states.length) {
const attribute = labelAttrMap[states[0].label.id];
activateObject(states[0].clientID, attribute ? attribute.id : null);
} else if (filteredStates.length) {
const attribute = labelAttrMap[filteredStates[0].label.id];
activateObject(filteredStates[0].clientID, attribute ? attribute.id : null);
}
}
const nextObject = (step: number): void => {
if (states.length) {
const index = states.indexOf(activeObjectState);
if (filteredStates.length) {
const index = filteredStates.indexOf(activeObjectState);
let nextIndex = index + step;
if (nextIndex > states.length - 1) {
if (nextIndex > filteredStates.length - 1) {
nextIndex = 0;
} else if (nextIndex < 0) {
nextIndex = states.length - 1;
nextIndex = filteredStates.length - 1;
}
if (nextIndex !== index) {
const attribute = labelAttrMap[states[nextIndex].label.id];
activateObject(states[nextIndex].clientID, attribute ? attribute.id : null);
const attribute = labelAttrMap[filteredStates[nextIndex].label.id];
activateObject(filteredStates[nextIndex].clientID, attribute ? attribute.id : null);
}
}
};
@ -207,42 +216,74 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
collapsed: sidebarCollapsed,
};
const preventDefault = (event: KeyboardEvent | undefined): void => {
if (event) {
event.preventDefault();
}
};
const subKeyMap = {
NEXT_ATTRIBUTE: keyMap.NEXT_ATTRIBUTE,
PREVIOUS_ATTRIBUTE: keyMap.PREVIOUS_ATTRIBUTE,
NEXT_OBJECT: keyMap.NEXT_OBJECT,
PREVIOUS_OBJECT: keyMap.PREVIOUS_OBJECT,
SWITCH_LOCK: keyMap.SWITCH_LOCK,
SWITCH_OCCLUDED: keyMap.SWITCH_OCCLUDED,
NEXT_KEY_FRAME: keyMap.NEXT_KEY_FRAME,
PREV_KEY_FRAME: keyMap.PREV_KEY_FRAME,
};
const handlers = {
NEXT_ATTRIBUTE: (event: KeyboardEvent | undefined) => {
if (event) {
event.preventDefault();
}
preventDefault(event);
nextAttribute(1);
},
PREVIOUS_ATTRIBUTE: (event: KeyboardEvent | undefined) => {
if (event) {
event.preventDefault();
}
preventDefault(event);
nextAttribute(-1);
},
NEXT_OBJECT: (event: KeyboardEvent | undefined) => {
if (event) {
event.preventDefault();
}
preventDefault(event);
nextObject(1);
},
PREVIOUS_OBJECT: (event: KeyboardEvent | undefined) => {
if (event) {
event.preventDefault();
}
preventDefault(event);
nextObject(-1);
},
SWITCH_LOCK: (event: KeyboardEvent | undefined) => {
preventDefault(event);
if (activeObjectState) {
activeObjectState.lock = !activeObjectState.lock;
updateAnnotations([activeObjectState]);
}
},
SWITCH_OCCLUDED: (event: KeyboardEvent | undefined) => {
preventDefault(event);
if (activeObjectState && activeObjectState.objectType !== ObjectType.TAG) {
activeObjectState.occluded = !activeObjectState.occluded;
updateAnnotations([activeObjectState]);
}
},
NEXT_KEY_FRAME: (event: KeyboardEvent | undefined) => {
preventDefault(event);
if (activeObjectState && activeObjectState.objectType === ObjectType.TRACK) {
const frame = typeof (activeObjectState.keyframes.next) === 'number'
? activeObjectState.keyframes.next : null;
if (frame !== null && canvasInstance.isAbleToChangeFrame()) {
changeFrame(frame);
}
}
},
PREV_KEY_FRAME: (event: KeyboardEvent | undefined) => {
preventDefault(event);
if (activeObjectState && activeObjectState.objectType === ObjectType.TRACK) {
const frame = typeof (activeObjectState.keyframes.prev) === 'number'
? activeObjectState.keyframes.prev : null;
if (frame !== null && canvasInstance.isAbleToChangeFrame()) {
changeFrame(frame);
}
}
},
};
if (activeObjectState) {
@ -268,15 +309,14 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
currentLabel={activeObjectState.label.name}
clientID={activeObjectState.clientID}
occluded={activeObjectState.occluded}
objectsCount={states.length}
currentIndex={states.indexOf(activeObjectState)}
objectsCount={filteredStates.length}
currentIndex={filteredStates.indexOf(activeObjectState)}
normalizedKeyMap={normalizedKeyMap}
nextObject={nextObject}
/>
<ObjectBasicsEditor
currentLabel={activeObjectState.label.name}
labels={labels}
occluded={activeObjectState.occluded}
changeLabel={(value: SelectValue): void => {
const labelName = value as string;
const [newLabel] = labels
@ -284,10 +324,12 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
activeObjectState.label = newLabel;
updateAnnotations([activeObjectState]);
}}
setOccluded={(event: CheckboxChangeEvent): void => {
activeObjectState.occluded = event.target.checked;
updateAnnotations([activeObjectState]);
}}
/>
<ObjectButtonsContainer
clientID={activeObjectState.clientID}
outsideDisabled
hiddenDisabled
keyframeDisabled
/>
{
activeAttribute
@ -302,6 +344,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
nextAttribute={nextAttribute}
/>
<AttributeEditor
clientID={activeObjectState.clientID}
attribute={activeAttribute}
currentValue={activeObjectState.attributes[activeAttribute.id]}
onChange={(value: string) => {
@ -326,12 +369,29 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
</div>
)
}
{ !sidebarCollapsed && <AppearanceBlock /> }
</Layout.Sider>
);
}
return (
<Layout.Sider {...siderProps}>
{/* eslint-disable-next-line */}
<span
className={`cvat-objects-sidebar-sider
ant-layout-sider-zero-width-trigger
ant-layout-sider-zero-width-trigger-left`}
onClick={collapse}
>
{sidebarCollapsed ? <Icon type='menu-fold' title='Show' />
: <Icon type='menu-unfold' title='Hide' />}
</span>
<Row className='cvat-objects-sidebar-filter-input'>
<Col>
<AnnotationsFiltersInput />
</Col>
</Row>
<div className='attribute-annotations-sidebar-not-found-wrapper'>
<Text strong>No objects found</Text>
</div>

@ -13,6 +13,7 @@ import Input from 'antd/lib/input';
import consts from 'consts';
interface InputElementParameters {
clientID: number;
attrID: number;
inputType: string;
values: string[];
@ -24,6 +25,7 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element {
const {
inputType,
attrID,
clientID,
values,
currentValue,
onChange,
@ -103,7 +105,7 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element {
<div className='attribute-annotation-sidebar-attr-elem-wrapper'>
<Input
autoFocus
key={attrID}
key={`${clientID}:${attrID}`}
defaultValue={currentValue}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.target;
@ -257,13 +259,19 @@ function renderList(parameters: ListParameters): JSX.Element | null {
}
interface Props {
clientID: number;
attribute: any;
currentValue: string;
onChange(value: string): void;
}
function AttributeEditor(props: Props): JSX.Element {
const { attribute, currentValue, onChange } = props;
const {
attribute,
currentValue,
onChange,
clientID,
} = props;
const { inputType, values, id: attrID } = attribute;
return (
@ -271,6 +279,7 @@ function AttributeEditor(props: Props): JSX.Element {
{renderList({ values, inputType, onChange })}
<hr />
{renderInputElement({
clientID,
attrID,
inputType,
currentValue,

@ -4,24 +4,15 @@
import React from 'react';
import Select, { SelectValue } from 'antd/lib/select';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
interface Props {
currentLabel: string;
labels: any[];
occluded: boolean;
setOccluded(event: CheckboxChangeEvent): void;
changeLabel(value: SelectValue): void;
}
function ObjectBasicsEditor(props: Props): JSX.Element {
const {
currentLabel,
occluded,
labels,
setOccluded,
changeLabel,
} = props;
const { currentLabel, labels, changeLabel } = props;
return (
<div className='attribute-annotation-sidebar-basics-editor'>
@ -35,7 +26,6 @@ function ObjectBasicsEditor(props: Props): JSX.Element {
</Select.Option>
))}
</Select>
<Checkbox checked={occluded} onChange={setOccluded}>Occluded</Checkbox>
</div>
);
}

@ -1,107 +0,0 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import Text from 'antd/lib/typography/Text';
import Radio, { RadioChangeEvent } from 'antd/lib/radio';
import Slider, { SliderValue } from 'antd/lib/slider';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
import Collapse from 'antd/lib/collapse';
import { ColorBy } from 'reducers/interfaces';
interface Props {
appearanceCollapsed: boolean;
colorBy: ColorBy;
opacity: number;
selectedOpacity: number;
blackBorders: boolean;
showBitmap: boolean;
showProjections: boolean;
collapseAppearance(): void;
changeShapesColorBy(event: RadioChangeEvent): void;
changeShapesOpacity(event: SliderValue): void;
changeSelectedShapesOpacity(event: SliderValue): void;
changeShapesBlackBorders(event: CheckboxChangeEvent): void;
changeShowBitmap(event: CheckboxChangeEvent): void;
changeShowProjections(event: CheckboxChangeEvent): void;
}
function AppearanceBlock(props: Props): JSX.Element {
const {
appearanceCollapsed,
colorBy,
opacity,
selectedOpacity,
blackBorders,
showBitmap,
showProjections,
collapseAppearance,
changeShapesColorBy,
changeShapesOpacity,
changeSelectedShapesOpacity,
changeShapesBlackBorders,
changeShowBitmap,
changeShowProjections,
} = props;
return (
<Collapse
onChange={collapseAppearance}
activeKey={appearanceCollapsed ? [] : ['appearance']}
className='cvat-objects-appearance-collapse'
>
<Collapse.Panel
header={
<Text strong>Appearance</Text>
}
key='appearance'
>
<div className='cvat-objects-appearance-content'>
<Text type='secondary'>Color by</Text>
<Radio.Group value={colorBy} onChange={changeShapesColorBy}>
<Radio.Button value={ColorBy.INSTANCE}>{ColorBy.INSTANCE}</Radio.Button>
<Radio.Button value={ColorBy.GROUP}>{ColorBy.GROUP}</Radio.Button>
<Radio.Button value={ColorBy.LABEL}>{ColorBy.LABEL}</Radio.Button>
</Radio.Group>
<Text type='secondary'>Opacity</Text>
<Slider
onChange={changeShapesOpacity}
value={opacity}
min={0}
max={100}
/>
<Text type='secondary'>Selected opacity</Text>
<Slider
onChange={changeSelectedShapesOpacity}
value={selectedOpacity}
min={0}
max={100}
/>
<Checkbox
onChange={changeShapesBlackBorders}
checked={blackBorders}
>
Black borders
</Checkbox>
<Checkbox
onChange={changeShowBitmap}
checked={showBitmap}
>
Show bitmap
</Checkbox>
<Checkbox
onChange={changeShowProjections}
checked={showProjections}
>
Show projections
</Checkbox>
</div>
</Collapse.Panel>
</Collapse>
);
}
export default React.memo(AppearanceBlock);

@ -0,0 +1,175 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { Col } from 'antd/lib/grid';
import Select from 'antd/lib/select';
import Radio, { RadioChangeEvent } from 'antd/lib/radio';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
import Input from 'antd/lib/input';
import InputNumber from 'antd/lib/input-number';
import Text from 'antd/lib/typography/Text';
import consts from 'consts';
import { clamp } from 'utils/math';
interface Props {
attrInputType: string;
attrValues: string[];
attrValue: string;
attrName: string;
attrID: number;
changeAttribute(attrID: number, value: string): void;
}
function attrIsTheSame(
prevProps: Props,
nextProps: Props,
): 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);
}
function ItemAttributeComponent(props: Props): 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'>
{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
size='small'
value={attrValue}
onChange={(event: RadioChangeEvent): void => {
changeAttribute(attrID, event.target.value);
}}
>
{ attrValues.map((value: string): JSX.Element => (
<Radio key={value} value={value}>
{value === consts.UNDEFINED_ATTRIBUTE_VALUE
? consts.NO_BREAK_SPACE : value}
</Radio>
)) }
</Radio.Group>
</fieldset>
</Col>
);
}
if (attrInputType === 'select') {
return (
<>
<Col span={24}>
<Text strong className='cvat-text'>
{attrName}
</Text>
</Col>
<Col span={24}>
<Select
size='small'
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 === consts.UNDEFINED_ATTRIBUTE_VALUE
? consts.NO_BREAK_SPACE : value}
</Select.Option>
)) }
</Select>
</Col>
</>
);
}
if (attrInputType === 'number') {
const [min, max, step] = attrValues.map((value: string): number => +value);
return (
<>
<Col span={24}>
<Text strong className='cvat-text'>
{attrName}
</Text>
</Col>
<Col span={24}>
<InputNumber
size='small'
onChange={(value: number | undefined): void => {
if (typeof (value) === 'number') {
changeAttribute(
attrID, `${clamp(value, min, max)}`,
);
}
}}
value={+attrValue}
className='cvat-object-item-number-attribute'
min={min}
max={max}
step={step}
/>
</Col>
</>
);
}
return (
<>
<Col span={24}>
<Text strong className='cvat-text'>
{attrName}
</Text>
</Col>
<Col span={24}>
<Input
size='small'
onChange={(event: React.ChangeEvent<HTMLInputElement>): void => {
changeAttribute(attrID, event.target.value);
}}
value={attrValue}
className='cvat-object-item-text-attribute'
/>
</Col>
</>
);
}
export default React.memo(ItemAttributeComponent, attrIsTheSame);

@ -0,0 +1,131 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Select, { OptionProps } from 'antd/lib/select';
import Dropdown from 'antd/lib/dropdown';
import Text from 'antd/lib/typography/Text';
import Tooltip from 'antd/lib/tooltip';
import { ObjectType, ShapeType } from 'reducers/interfaces';
import ItemMenu from './object-item-menu';
interface Props {
clientID: number;
serverID: number | undefined;
labelID: number;
labels: any[];
shapeType: ShapeType;
objectType: ObjectType;
type: string;
locked: boolean;
copyShortcut: string;
pasteShortcut: string;
propagateShortcut: string;
toBackgroundShortcut: string;
toForegroundShortcut: string;
removeShortcut: string;
changeLabel(labelID: string): void;
copy(): void;
remove(): void;
propagate(): void;
createURL(): void;
switchOrientation(): void;
toBackground(): void;
toForeground(): void;
resetCuboidPerspective(): void;
}
function ItemTopComponent(props: Props): JSX.Element {
const {
clientID,
serverID,
labelID,
labels,
shapeType,
objectType,
type,
locked,
copyShortcut,
pasteShortcut,
propagateShortcut,
toBackgroundShortcut,
toForegroundShortcut,
removeShortcut,
changeLabel,
copy,
remove,
propagate,
createURL,
switchOrientation,
toBackground,
toForeground,
resetCuboidPerspective,
} = props;
return (
<Row type='flex' align='middle'>
<Col span={10}>
<Text style={{ fontSize: 12 }}>{clientID}</Text>
<br />
<Text type='secondary' style={{ fontSize: 10 }}>{type}</Text>
</Col>
<Col span={12}>
<Tooltip title='Change current label'>
<Select
size='small'
value={`${labelID}`}
onChange={changeLabel}
showSearch
filterOption={(input: string, option: React.ReactElement<OptionProps>) => {
const { children } = option.props;
if (typeof (children) === 'string') {
return children.toLowerCase().includes(input.toLowerCase());
}
return false;
}}
>
{ labels.map((label: any): JSX.Element => (
<Select.Option key={label.id} value={`${label.id}`}>
{label.name}
</Select.Option>
))}
</Select>
</Tooltip>
</Col>
<Col span={2}>
<Dropdown
placement='bottomLeft'
overlay={ItemMenu({
serverID,
locked,
shapeType,
objectType,
copyShortcut,
pasteShortcut,
propagateShortcut,
toBackgroundShortcut,
toForegroundShortcut,
removeShortcut,
copy,
remove,
propagate,
createURL,
switchOrientation,
toBackground,
toForeground,
resetCuboidPerspective,
})}
>
<Icon type='more' />
</Dropdown>
</Col>
</Row>
);
}
export default React.memo(ItemTopComponent);

@ -0,0 +1,262 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Tooltip from 'antd/lib/tooltip';
import {
ObjectOutsideIcon,
FirstIcon,
LastIcon,
PreviousIcon,
NextIcon,
} from 'icons';
import { ObjectType, ShapeType } from 'reducers/interfaces';
interface Props {
objectType: ObjectType;
shapeType: ShapeType;
occluded: boolean;
outside: boolean | undefined;
locked: boolean;
pinned: boolean;
hidden: boolean;
keyframe: boolean | undefined;
outsideDisabled: boolean;
hiddenDisabled: boolean;
keyframeDisabled: boolean;
switchOccludedShortcut: string;
switchOutsideShortcut: string;
switchLockShortcut: string;
switchHiddenShortcut: string;
switchKeyFrameShortcut: string;
nextKeyFrameShortcut: string;
prevKeyFrameShortcut: string;
navigateFirstKeyframe: null | (() => void);
navigatePrevKeyframe: null | (() => void);
navigateNextKeyframe: null | (() => void);
navigateLastKeyframe: null | (() => void);
setOccluded(): void;
unsetOccluded(): void;
setOutside(): void;
unsetOutside(): void;
setKeyframe(): void;
unsetKeyframe(): void;
lock(): void;
unlock(): void;
pin(): void;
unpin(): void;
hide(): void;
show(): void;
}
function ItemButtonsComponent(props: Props): JSX.Element {
const {
objectType,
shapeType,
occluded,
outside,
locked,
pinned,
hidden,
keyframe,
outsideDisabled,
hiddenDisabled,
keyframeDisabled,
switchOccludedShortcut,
switchOutsideShortcut,
switchLockShortcut,
switchHiddenShortcut,
switchKeyFrameShortcut,
nextKeyFrameShortcut,
prevKeyFrameShortcut,
navigateFirstKeyframe,
navigatePrevKeyframe,
navigateNextKeyframe,
navigateLastKeyframe,
setOccluded,
unsetOccluded,
setOutside,
unsetOutside,
setKeyframe,
unsetKeyframe,
lock,
unlock,
pin,
unpin,
hide,
show,
} = props;
const outsideStyle = outsideDisabled ? { opacity: 0.5, pointerEvents: 'none' as 'none' } : {};
const hiddenStyle = hiddenDisabled ? { opacity: 0.5, pointerEvents: 'none' as 'none' } : {};
const keyframeStyle = keyframeDisabled ? { opacity: 0.5, pointerEvents: 'none' as 'none' } : {};
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>
{ navigateFirstKeyframe
? <Icon component={FirstIcon} onClick={navigateFirstKeyframe} />
: <Icon component={FirstIcon} style={{ opacity: 0.5, pointerEvents: 'none' }} />}
</Col>
<Col>
{ navigatePrevKeyframe
? (
<Tooltip title={`Go to previous keyframe ${prevKeyFrameShortcut}`}>
<Icon
component={PreviousIcon}
onClick={navigatePrevKeyframe}
/>
</Tooltip>
)
: <Icon component={PreviousIcon} style={{ opacity: 0.5, pointerEvents: 'none' }} />}
</Col>
<Col>
{ navigateNextKeyframe
? (
<Tooltip title={`Go to next keyframe ${nextKeyFrameShortcut}`}>
<Icon
component={NextIcon}
onClick={navigateNextKeyframe}
/>
</Tooltip>
)
: <Icon component={NextIcon} style={{ opacity: 0.5, pointerEvents: 'none' }} />}
</Col>
<Col>
{ navigateLastKeyframe
? <Icon component={LastIcon} onClick={navigateLastKeyframe} />
: <Icon component={LastIcon} style={{ opacity: 0.5, pointerEvents: 'none' }} />}
</Col>
</Row>
<Row type='flex' justify='space-around'>
<Col>
<Tooltip title={`Switch outside property ${switchOutsideShortcut}`}>
{ outside
? (
<Icon
component={ObjectOutsideIcon}
onClick={unsetOutside}
style={outsideStyle}
/>
)
: <Icon type='select' onClick={setOutside} style={outsideStyle} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch lock property ${switchLockShortcut}`}>
{ locked
? <Icon type='lock' theme='filled' onClick={unlock} />
: <Icon type='unlock' onClick={lock} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch occluded property ${switchOccludedShortcut}`}>
{ occluded
? <Icon type='team' onClick={unsetOccluded} />
: <Icon type='user' onClick={setOccluded} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch hidden property ${switchHiddenShortcut}`}>
{ hidden
? <Icon type='eye-invisible' theme='filled' onClick={show} style={hiddenStyle} />
: <Icon type='eye' onClick={hide} style={hiddenStyle} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch keyframe property ${switchKeyFrameShortcut}`}>
{ keyframe
? <Icon type='star' theme='filled' onClick={unsetKeyframe} style={keyframeStyle} />
: <Icon type='star' onClick={setKeyframe} style={keyframeStyle} />}
</Tooltip>
</Col>
{
shapeType !== ShapeType.POINTS && (
<Col>
<Tooltip title='Switch pinned property'>
{ pinned
? <Icon type='pushpin' theme='filled' onClick={unpin} />
: <Icon type='pushpin' onClick={pin} />}
</Tooltip>
</Col>
)
}
</Row>
</Col>
</Row>
);
}
if (objectType === ObjectType.TAG) {
return (
<Row type='flex' align='middle' justify='space-around'>
<Col span={20} style={{ textAlign: 'center' }}>
<Row type='flex' justify='space-around'>
<Col>
<Tooltip title={`Switch lock property ${switchLockShortcut}`}>
{ locked
? <Icon type='lock' onClick={unlock} theme='filled' />
: <Icon type='unlock' onClick={lock} />}
</Tooltip>
</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>
<Tooltip title={`Switch lock property ${switchLockShortcut}`}>
{ locked
? <Icon type='lock' onClick={unlock} theme='filled' />
: <Icon type='unlock' onClick={lock} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch occluded property ${switchOccludedShortcut}`}>
{ occluded
? <Icon type='team' onClick={unsetOccluded} />
: <Icon type='user' onClick={setOccluded} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch hidden property ${switchHiddenShortcut}`}>
{ hidden
? <Icon type='eye-invisible' onClick={show} />
: <Icon type='eye' onClick={hide} />}
</Tooltip>
</Col>
{
shapeType !== ShapeType.POINTS && (
<Col>
<Tooltip title='Switch pinned property'>
{ pinned
? <Icon type='pushpin' theme='filled' onClick={unpin} />
: <Icon type='pushpin' onClick={pin} />}
</Tooltip>
</Col>
)
}
</Row>
</Col>
</Row>
);
}
export default React.memo(ItemButtonsComponent);

@ -0,0 +1,86 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { Row } from 'antd/lib/grid';
import Collapse from 'antd/lib/collapse';
import ItemAttribute from './object-item-attribute';
interface Props {
collapsed: boolean;
attributes: any[];
values: Record<number, string>;
changeAttribute(attrID: number, value: string): void;
collapse(): void;
}
export 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: Props,
nextProps: Props,
): boolean {
return nextProps.collapsed === prevProps.collapsed
&& nextProps.attributes === prevProps.attributes
&& attrValuesAreEqual(nextProps.values, prevProps.values);
}
function ItemAttributesComponent(props: Props): 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={<span style={{ fontSize: '11px' }}>Details</span>}
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>
);
}
export default React.memo(ItemAttributesComponent, attrAreTheSame);

@ -0,0 +1,139 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import Icon from 'antd/lib/icon';
import Menu from 'antd/lib/menu';
import Button from 'antd/lib/button';
import Modal from 'antd/lib/modal';
import Tooltip from 'antd/lib/tooltip';
import { BackgroundIcon, ForegroundIcon, ResetPerspectiveIcon } from 'icons';
import { ObjectType, ShapeType } from 'reducers/interfaces';
interface Props {
serverID: number | undefined;
locked: boolean;
shapeType: ShapeType;
objectType: ObjectType;
copyShortcut: string;
pasteShortcut: string;
propagateShortcut: string;
toBackgroundShortcut: string;
toForegroundShortcut: string;
removeShortcut: string;
copy: (() => void);
remove: (() => void);
propagate: (() => void);
createURL: (() => void);
switchOrientation: (() => void);
toBackground: (() => void);
toForeground: (() => void);
resetCuboidPerspective: (() => void);
}
export default function ItemMenu(props: Props): JSX.Element {
const {
serverID,
locked,
shapeType,
objectType,
copyShortcut,
pasteShortcut,
propagateShortcut,
toBackgroundShortcut,
toForegroundShortcut,
removeShortcut,
copy,
remove,
propagate,
createURL,
switchOrientation,
toBackground,
toForeground,
resetCuboidPerspective,
} = props;
return (
<Menu className='cvat-object-item-menu'>
<Menu.Item>
<Button disabled={serverID === undefined} type='link' icon='link' onClick={createURL}>
Create object URL
</Button>
</Menu.Item>
<Menu.Item>
<Tooltip title={`${copyShortcut} and ${pasteShortcut}`}>
<Button type='link' icon='copy' onClick={copy}>
Make a copy
</Button>
</Tooltip>
</Menu.Item>
<Menu.Item>
<Tooltip title={`${propagateShortcut}`}>
<Button type='link' icon='block' onClick={propagate}>
Propagate
</Button>
</Tooltip>
</Menu.Item>
{ [ShapeType.POLYGON, ShapeType.POLYLINE, ShapeType.CUBOID].includes(shapeType) && (
<Menu.Item>
<Button type='link' icon='retweet' onClick={switchOrientation}>
Switch orientation
</Button>
</Menu.Item>
)}
{shapeType === ShapeType.CUBOID && (
<Menu.Item>
<Button type='link' onClick={resetCuboidPerspective}>
<Icon component={ResetPerspectiveIcon} />
Reset perspective
</Button>
</Menu.Item>
)}
{objectType !== ObjectType.TAG && (
<Menu.Item>
<Tooltip title={`${toBackgroundShortcut}`}>
<Button type='link' onClick={toBackground}>
<Icon component={BackgroundIcon} />
To background
</Button>
</Tooltip>
</Menu.Item>
)}
{objectType !== ObjectType.TAG && (
<Menu.Item>
<Tooltip title={`${toForegroundShortcut}`}>
<Button type='link' onClick={toForeground}>
<Icon component={ForegroundIcon} />
To foreground
</Button>
</Tooltip>
</Menu.Item>
)}
<Menu.Item>
<Tooltip title={`${removeShortcut}`}>
<Button
type='link'
icon='delete'
onClick={(): void => {
if (locked) {
Modal.confirm({
title: 'Object is locked',
content: 'Are you sure you want to remove it?',
onOk() {
remove();
},
});
} else {
remove();
}
}}
>
Remove
</Button>
</Tooltip>
</Menu.Item>
</Menu>
);
}

@ -3,722 +3,15 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Select, { OptionProps } from 'antd/lib/select';
import Radio, { RadioChangeEvent } from 'antd/lib/radio';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
import Input from 'antd/lib/input';
import InputNumber from 'antd/lib/input-number';
import Collapse from 'antd/lib/collapse';
import Dropdown from 'antd/lib/dropdown';
import Menu from 'antd/lib/menu';
import Button from 'antd/lib/button';
import Modal from 'antd/lib/modal';
import Popover from 'antd/lib/popover';
import Text from 'antd/lib/typography/Text';
import Tooltip from 'antd/lib/tooltip';
import ObjectButtonsContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-buttons';
import ColorChanger from 'components/annotation-page/standard-workspace/objects-side-bar/color-changer';
import consts from 'consts';
import {
ObjectOutsideIcon,
FirstIcon,
LastIcon,
PreviousIcon,
NextIcon,
BackgroundIcon,
ForegroundIcon,
ResetPerspectiveIcon,
} from 'icons';
import { ObjectType, ShapeType } from 'reducers/interfaces';
import { clamp } from 'utils/math';
import ItemDetails, { attrValuesAreEqual } from './object-item-details';
import ItemBasics from './object-item-basics';
function ItemMenu(
serverID: number | undefined,
locked: boolean,
shapeType: ShapeType,
objectType: ObjectType,
copyShortcut: string,
pasteShortcut: string,
propagateShortcut: string,
toBackgroundShortcut: string,
toForegroundShortcut: string,
removeShortcut: string,
copy: (() => void),
remove: (() => void),
propagate: (() => void),
createURL: (() => void),
switchOrientation: (() => void),
toBackground: (() => void),
toForeground: (() => void),
resetCuboidPerspective: (() => void),
): JSX.Element {
return (
<Menu className='cvat-object-item-menu'>
<Menu.Item>
<Button disabled={serverID === undefined} type='link' icon='link' onClick={createURL}>
Create object URL
</Button>
</Menu.Item>
<Menu.Item>
<Tooltip title={`${copyShortcut} and ${pasteShortcut}`}>
<Button type='link' icon='copy' onClick={copy}>
Make a copy
</Button>
</Tooltip>
</Menu.Item>
<Menu.Item>
<Tooltip title={`${propagateShortcut}`}>
<Button type='link' icon='block' onClick={propagate}>
Propagate
</Button>
</Tooltip>
</Menu.Item>
{ [ShapeType.POLYGON, ShapeType.POLYLINE, ShapeType.CUBOID].includes(shapeType) && (
<Menu.Item>
<Button type='link' icon='retweet' onClick={switchOrientation}>
Switch orientation
</Button>
</Menu.Item>
)}
{shapeType === ShapeType.CUBOID && (
<Menu.Item>
<Button type='link' onClick={resetCuboidPerspective}>
<Icon component={ResetPerspectiveIcon} />
Reset perspective
</Button>
</Menu.Item>
)}
{objectType !== ObjectType.TAG && (
<Menu.Item>
<Tooltip title={`${toBackgroundShortcut}`}>
<Button type='link' onClick={toBackground}>
<Icon component={BackgroundIcon} />
To background
</Button>
</Tooltip>
</Menu.Item>
)}
{objectType !== ObjectType.TAG && (
<Menu.Item>
<Tooltip title={`${toForegroundShortcut}`}>
<Button type='link' onClick={toForeground}>
<Icon component={ForegroundIcon} />
To foreground
</Button>
</Tooltip>
</Menu.Item>
)}
<Menu.Item>
<Tooltip title={`${removeShortcut}`}>
<Button
type='link'
icon='delete'
onClick={(): void => {
if (locked) {
Modal.confirm({
title: 'Object is locked',
content: 'Are you sure you want to remove it?',
onOk() {
remove();
},
});
} else {
remove();
}
}}
>
Remove
</Button>
</Tooltip>
</Menu.Item>
</Menu>
);
}
interface ItemTopComponentProps {
clientID: number;
serverID: number | undefined;
labelID: number;
labels: any[];
shapeType: ShapeType;
objectType: ObjectType;
type: string;
locked: boolean;
copyShortcut: string;
pasteShortcut: string;
propagateShortcut: string;
toBackgroundShortcut: string;
toForegroundShortcut: string;
removeShortcut: string;
changeLabel(labelID: string): void;
copy(): void;
remove(): void;
propagate(): void;
createURL(): void;
switchOrientation(): void;
toBackground(): void;
toForeground(): void;
resetCuboidPerspective(): void;
}
function ItemTopComponent(props: ItemTopComponentProps): JSX.Element {
const {
clientID,
serverID,
labelID,
labels,
shapeType,
objectType,
type,
locked,
copyShortcut,
pasteShortcut,
propagateShortcut,
toBackgroundShortcut,
toForegroundShortcut,
removeShortcut,
changeLabel,
copy,
remove,
propagate,
createURL,
switchOrientation,
toBackground,
toForeground,
resetCuboidPerspective,
} = props;
return (
<Row type='flex' align='middle'>
<Col span={10}>
<Text style={{ fontSize: 12 }}>{clientID}</Text>
<br />
<Text type='secondary' style={{ fontSize: 10 }}>{type}</Text>
</Col>
<Col span={12}>
<Tooltip title='Change current label'>
<Select
size='small'
value={`${labelID}`}
onChange={changeLabel}
showSearch
filterOption={(input: string, option: React.ReactElement<OptionProps>) => {
const { children } = option.props;
if (typeof (children) === 'string') {
return children.toLowerCase().includes(input.toLowerCase());
}
return false;
}}
>
{ labels.map((label: any): JSX.Element => (
<Select.Option key={label.id} value={`${label.id}`}>
{label.name}
</Select.Option>
))}
</Select>
</Tooltip>
</Col>
<Col span={2}>
<Dropdown
placement='bottomLeft'
overlay={ItemMenu(
serverID,
locked,
shapeType,
objectType,
copyShortcut,
pasteShortcut,
propagateShortcut,
toBackgroundShortcut,
toForegroundShortcut,
removeShortcut,
copy,
remove,
propagate,
createURL,
switchOrientation,
toBackground,
toForeground,
resetCuboidPerspective,
)}
>
<Icon type='more' />
</Dropdown>
</Col>
</Row>
);
}
const ItemTop = React.memo(ItemTopComponent);
interface ItemButtonsComponentProps {
objectType: ObjectType;
shapeType: ShapeType;
occluded: boolean;
outside: boolean | undefined;
locked: boolean;
pinned: boolean;
hidden: boolean;
keyframe: boolean | undefined;
switchOccludedShortcut: string;
switchOutsideShortcut: string;
switchLockShortcut: string;
switchHiddenShortcut: string;
switchKeyFrameShortcut: string;
nextKeyFrameShortcut: string;
prevKeyFrameShortcut: string;
navigateFirstKeyframe: null | (() => void);
navigatePrevKeyframe: null | (() => void);
navigateNextKeyframe: null | (() => void);
navigateLastKeyframe: null | (() => void);
setOccluded(): void;
unsetOccluded(): void;
setOutside(): void;
unsetOutside(): void;
setKeyframe(): void;
unsetKeyframe(): void;
lock(): void;
unlock(): void;
pin(): void;
unpin(): void;
hide(): void;
show(): void;
}
function ItemButtonsComponent(props: ItemButtonsComponentProps): JSX.Element {
const {
objectType,
shapeType,
occluded,
outside,
locked,
pinned,
hidden,
keyframe,
switchOccludedShortcut,
switchOutsideShortcut,
switchLockShortcut,
switchHiddenShortcut,
switchKeyFrameShortcut,
nextKeyFrameShortcut,
prevKeyFrameShortcut,
navigateFirstKeyframe,
navigatePrevKeyframe,
navigateNextKeyframe,
navigateLastKeyframe,
setOccluded,
unsetOccluded,
setOutside,
unsetOutside,
setKeyframe,
unsetKeyframe,
lock,
unlock,
pin,
unpin,
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>
{ navigateFirstKeyframe
? <Icon component={FirstIcon} onClick={navigateFirstKeyframe} />
: <Icon component={FirstIcon} style={{ opacity: 0.5, pointerEvents: 'none' }} />}
</Col>
<Col>
{ navigatePrevKeyframe
? (
<Tooltip title={`Go to previous keyframe ${prevKeyFrameShortcut}`}>
<Icon
component={PreviousIcon}
onClick={navigatePrevKeyframe}
/>
</Tooltip>
)
: <Icon component={PreviousIcon} style={{ opacity: 0.5, pointerEvents: 'none' }} />}
</Col>
<Col>
{ navigateNextKeyframe
? (
<Tooltip title={`Go to next keyframe ${nextKeyFrameShortcut}`}>
<Icon
component={NextIcon}
onClick={navigateNextKeyframe}
/>
</Tooltip>
)
: <Icon component={NextIcon} style={{ opacity: 0.5, pointerEvents: 'none' }} />}
</Col>
<Col>
{ navigateLastKeyframe
? <Icon component={LastIcon} onClick={navigateLastKeyframe} />
: <Icon component={LastIcon} style={{ opacity: 0.5, pointerEvents: 'none' }} />}
</Col>
</Row>
<Row type='flex' justify='space-around'>
<Col>
<Tooltip title={`Switch outside property ${switchOutsideShortcut}`}>
{ outside
? <Icon component={ObjectOutsideIcon} onClick={unsetOutside} />
: <Icon type='select' onClick={setOutside} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch lock property ${switchLockShortcut}`}>
{ locked
? <Icon type='lock' theme='filled' onClick={unlock} />
: <Icon type='unlock' onClick={lock} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch occluded property ${switchOccludedShortcut}`}>
{ occluded
? <Icon type='team' onClick={unsetOccluded} />
: <Icon type='user' onClick={setOccluded} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch hidden property ${switchHiddenShortcut}`}>
{ hidden
? <Icon type='eye-invisible' theme='filled' onClick={show} />
: <Icon type='eye' onClick={hide} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch keyframe property ${switchKeyFrameShortcut}`}>
{ keyframe
? <Icon type='star' theme='filled' onClick={unsetKeyframe} />
: <Icon type='star' onClick={setKeyframe} />}
</Tooltip>
</Col>
{
shapeType !== ShapeType.POINTS && (
<Col>
<Tooltip title='Switch pinned property'>
{ pinned
? <Icon type='pushpin' theme='filled' onClick={unpin} />
: <Icon type='pushpin' onClick={pin} />}
</Tooltip>
</Col>
)
}
</Row>
</Col>
</Row>
);
}
if (objectType === ObjectType.TAG) {
return (
<Row type='flex' align='middle' justify='space-around'>
<Col span={20} style={{ textAlign: 'center' }}>
<Row type='flex' justify='space-around'>
<Col>
<Tooltip title={`Switch lock property ${switchLockShortcut}`}>
{ locked
? <Icon type='lock' onClick={unlock} theme='filled' />
: <Icon type='unlock' onClick={lock} />}
</Tooltip>
</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>
<Tooltip title={`Switch lock property ${switchLockShortcut}`}>
{ locked
? <Icon type='lock' onClick={unlock} theme='filled' />
: <Icon type='unlock' onClick={lock} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch occluded property ${switchOccludedShortcut}`}>
{ occluded
? <Icon type='team' onClick={unsetOccluded} />
: <Icon type='user' onClick={setOccluded} />}
</Tooltip>
</Col>
<Col>
<Tooltip title={`Switch hidden property ${switchHiddenShortcut}`}>
{ hidden
? <Icon type='eye-invisible' onClick={show} />
: <Icon type='eye' onClick={hide} />}
</Tooltip>
</Col>
{
shapeType !== ShapeType.POINTS && (
<Col>
<Tooltip title='Switch pinned property'>
{ pinned
? <Icon type='pushpin' theme='filled' onClick={unpin} />
: <Icon type='pushpin' onClick={pin} />}
</Tooltip>
</Col>
)
}
</Row>
</Col>
</Row>
);
}
const ItemButtons = React.memo(ItemButtonsComponent);
interface ItemAttributeComponentProps {
attrInputType: string;
attrValues: string[];
attrValue: string;
attrName: string;
attrID: number;
changeAttribute(attrID: number, value: string): void;
}
function attrIsTheSame(
prevProps: ItemAttributeComponentProps,
nextProps: ItemAttributeComponentProps,
): 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);
}
function ItemAttributeComponent(props: ItemAttributeComponentProps): 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'>
{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
size='small'
value={attrValue}
onChange={(event: RadioChangeEvent): void => {
changeAttribute(attrID, event.target.value);
}}
>
{ attrValues.map((value: string): JSX.Element => (
<Radio key={value} value={value}>
{value === consts.UNDEFINED_ATTRIBUTE_VALUE
? consts.NO_BREAK_SPACE : value}
</Radio>
)) }
</Radio.Group>
</fieldset>
</Col>
);
}
if (attrInputType === 'select') {
return (
<>
<Col span={24}>
<Text strong className='cvat-text'>
{attrName}
</Text>
</Col>
<Col span={24}>
<Select
size='small'
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 === consts.UNDEFINED_ATTRIBUTE_VALUE
? consts.NO_BREAK_SPACE : value}
</Select.Option>
)) }
</Select>
</Col>
</>
);
}
if (attrInputType === 'number') {
const [min, max, step] = attrValues.map((value: string): number => +value);
return (
<>
<Col span={24}>
<Text strong className='cvat-text'>
{attrName}
</Text>
</Col>
<Col span={24}>
<InputNumber
size='small'
onChange={(value: number | undefined): void => {
if (typeof (value) === 'number') {
changeAttribute(
attrID, `${clamp(value, min, max)}`,
);
}
}}
value={+attrValue}
className='cvat-object-item-number-attribute'
min={min}
max={max}
step={step}
/>
</Col>
</>
);
}
return (
<>
<Col span={24}>
<Text strong className='cvat-text'>
{attrName}
</Text>
</Col>
<Col span={24}>
<Input
size='small'
onChange={(event: React.ChangeEvent<HTMLInputElement>): void => {
changeAttribute(attrID, event.target.value);
}}
value={attrValue}
className='cvat-object-item-text-attribute'
/>
</Col>
</>
);
}
const ItemAttribute = React.memo(ItemAttributeComponent, attrIsTheSame);
interface ItemAttributesComponentProps {
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: ItemAttributesComponentProps,
nextProps: ItemAttributesComponentProps,
): boolean {
return nextProps.collapsed === prevProps.collapsed
&& nextProps.attributes === prevProps.attributes
&& attrValuesAreEqual(nextProps.values, prevProps.values);
}
function ItemAttributesComponent(props: ItemAttributesComponentProps): 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={<span style={{ fontSize: '11px' }}>Details</span>}
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>
);
}
const ItemAttributes = React.memo(ItemAttributesComponent, attrAreTheSame);
interface Props {
normalizedKeyMap: Record<string, string>;
activated: boolean;
@ -727,12 +20,7 @@ interface Props {
clientID: number;
serverID: number | undefined;
labelID: number;
occluded: boolean;
outside: boolean | undefined;
locked: boolean;
pinned: boolean;
hidden: boolean;
keyframe: boolean | undefined;
attrValues: Record<number, string>;
color: string;
colors: string[];
@ -740,10 +28,6 @@ interface Props {
labels: any[];
attributes: any[];
collapsed: boolean;
navigateFirstKeyframe: null | (() => void);
navigatePrevKeyframe: null | (() => void);
navigateNextKeyframe: null | (() => void);
navigateLastKeyframe: null | (() => void);
activate(): void;
copy(): void;
@ -753,18 +37,6 @@ interface Props {
toBackground(): void;
toForeground(): void;
remove(): void;
setOccluded(): void;
unsetOccluded(): void;
setOutside(): void;
unsetOutside(): void;
setKeyframe(): void;
unsetKeyframe(): void;
lock(): void;
unlock(): void;
pin(): void;
unpin(): void;
hide(): void;
show(): void;
changeLabel(labelID: string): void;
changeAttribute(attrID: number, value: string): void;
changeColor(color: string): void;
@ -775,11 +47,6 @@ interface Props {
function objectItemsAreEqual(prevProps: Props, nextProps: Props): boolean {
return nextProps.activated === prevProps.activated
&& nextProps.locked === prevProps.locked
&& nextProps.pinned === prevProps.pinned
&& nextProps.occluded === prevProps.occluded
&& nextProps.outside === prevProps.outside
&& nextProps.hidden === prevProps.hidden
&& nextProps.keyframe === prevProps.keyframe
&& nextProps.labelID === prevProps.labelID
&& nextProps.color === prevProps.color
&& nextProps.clientID === prevProps.clientID
@ -790,10 +57,6 @@ function objectItemsAreEqual(prevProps: Props, nextProps: Props): boolean {
&& nextProps.labels === prevProps.labels
&& nextProps.attributes === prevProps.attributes
&& nextProps.normalizedKeyMap === prevProps.normalizedKeyMap
&& nextProps.navigateFirstKeyframe === prevProps.navigateFirstKeyframe
&& nextProps.navigatePrevKeyframe === prevProps.navigatePrevKeyframe
&& nextProps.navigateNextKeyframe === prevProps.navigateNextKeyframe
&& nextProps.navigateLastKeyframe === prevProps.navigateLastKeyframe
&& attrValuesAreEqual(nextProps.attrValues, prevProps.attrValues);
}
@ -804,12 +67,7 @@ function ObjectItemComponent(props: Props): JSX.Element {
shapeType,
clientID,
serverID,
occluded,
outside,
locked,
pinned,
hidden,
keyframe,
attrValues,
labelID,
color,
@ -819,10 +77,6 @@ function ObjectItemComponent(props: Props): JSX.Element {
labels,
collapsed,
normalizedKeyMap,
navigateFirstKeyframe,
navigatePrevKeyframe,
navigateNextKeyframe,
navigateLastKeyframe,
activate,
copy,
@ -832,18 +86,6 @@ function ObjectItemComponent(props: Props): JSX.Element {
toBackground,
toForeground,
remove,
setOccluded,
unsetOccluded,
setOutside,
unsetOutside,
setKeyframe,
unsetKeyframe,
lock,
unlock,
pin,
unpin,
hide,
show,
changeLabel,
changeAttribute,
changeColor,
@ -881,7 +123,7 @@ function ObjectItemComponent(props: Props): JSX.Element {
className={className}
style={{ backgroundColor: `${color}88` }}
>
<ItemTop
<ItemBasics
serverID={serverID}
clientID={clientID}
labelID={labelID}
@ -906,42 +148,12 @@ function ObjectItemComponent(props: Props): JSX.Element {
toForeground={toForeground}
resetCuboidPerspective={resetCuboidPerspective}
/>
<ItemButtons
shapeType={shapeType}
objectType={objectType}
occluded={occluded}
outside={outside}
locked={locked}
pinned={pinned}
hidden={hidden}
keyframe={keyframe}
switchOccludedShortcut={normalizedKeyMap.SWITCH_OCCLUDED}
switchOutsideShortcut={normalizedKeyMap.SWITCH_OUTSIDE}
switchLockShortcut={normalizedKeyMap.SWITCH_LOCK}
switchHiddenShortcut={normalizedKeyMap.SWITCH_HIDDEN}
switchKeyFrameShortcut={normalizedKeyMap.SWITCH_KEYFRAME}
nextKeyFrameShortcut={normalizedKeyMap.NEXT_KEY_FRAME}
prevKeyFrameShortcut={normalizedKeyMap.PREV_KEY_FRAME}
navigateFirstKeyframe={navigateFirstKeyframe}
navigatePrevKeyframe={navigatePrevKeyframe}
navigateNextKeyframe={navigateNextKeyframe}
navigateLastKeyframe={navigateLastKeyframe}
setOccluded={setOccluded}
unsetOccluded={unsetOccluded}
setOutside={setOutside}
unsetOutside={unsetOutside}
setKeyframe={setKeyframe}
unsetKeyframe={unsetKeyframe}
lock={lock}
unlock={unlock}
pin={pin}
unpin={unpin}
hide={hide}
show={show}
<ObjectButtonsContainer
clientID={clientID}
/>
{ !!attributes.length
&& (
<ItemAttributes
<ItemDetails
collapsed={collapsed}
attributes={attributes}
values={attrValues}

@ -3,82 +3,84 @@
// SPDX-License-Identifier: MIT
import './styles.scss';
import React, { useEffect } from 'react';
import React, { Dispatch, useEffect } from 'react';
import { AnyAction } from 'redux';
import { connect } from 'react-redux';
import Text from 'antd/lib/typography/Text';
import Icon from 'antd/lib/icon';
import Tabs from 'antd/lib/tabs';
import Layout from 'antd/lib/layout';
import { RadioChangeEvent } from 'antd/lib/radio';
import { SliderValue } from 'antd/lib/slider';
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
import { Canvas } from 'cvat-canvas-wrapper';
import { ColorBy } from 'reducers/interfaces';
import { Canvas } from 'cvat-canvas-wrapper';
import { CombinedState } from 'reducers/interfaces';
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';
import AppearanceBlock from './appearance-block';
import {
collapseSidebar as collapseSidebarAction,
updateTabContentHeight as updateTabContentHeightAction,
} from 'actions/annotation-actions';
import AppearanceBlock, { computeHeight } from 'components/annotation-page/appearance-block';
interface Props {
interface StateToProps {
sidebarCollapsed: boolean;
appearanceCollapsed: boolean;
colorBy: ColorBy;
opacity: number;
selectedOpacity: number;
blackBorders: boolean;
showBitmap: boolean;
showProjections: boolean;
canvasInstance: Canvas;
}
interface DispatchToProps {
collapseSidebar(): void;
collapseAppearance(): void;
changeShapesColorBy(event: RadioChangeEvent): void;
changeShapesOpacity(event: SliderValue): void;
changeSelectedShapesOpacity(event: SliderValue): void;
changeShapesBlackBorders(event: CheckboxChangeEvent): void;
changeShowBitmap(event: CheckboxChangeEvent): void;
changeShowProjections(event: CheckboxChangeEvent): void;
updateTabContentHeight(): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const {
annotation: {
sidebarCollapsed,
canvas: {
instance: canvasInstance,
},
},
} = state;
return {
sidebarCollapsed,
canvasInstance,
};
}
function mapDispatchToProps(dispatch: Dispatch<AnyAction>): DispatchToProps {
return {
collapseSidebar(): void {
dispatch(collapseSidebarAction());
},
updateTabContentHeight(): void {
const height = computeHeight();
dispatch(updateTabContentHeightAction(height));
},
};
}
function ObjectsSideBar(props: Props): JSX.Element {
function ObjectsSideBar(props: StateToProps & DispatchToProps): JSX.Element {
const {
sidebarCollapsed,
appearanceCollapsed,
colorBy,
opacity,
selectedOpacity,
blackBorders,
showBitmap,
showProjections,
canvasInstance,
collapseSidebar,
collapseAppearance,
changeShapesColorBy,
changeShapesOpacity,
changeSelectedShapesOpacity,
changeShapesBlackBorders,
changeShowBitmap,
changeShowProjections,
updateTabContentHeight,
} = props;
const appearanceProps = {
collapseAppearance,
appearanceCollapsed,
colorBy,
opacity,
selectedOpacity,
blackBorders,
showBitmap,
showProjections,
changeShapesColorBy,
changeShapesOpacity,
changeSelectedShapesOpacity,
changeShapesBlackBorders,
changeShowBitmap,
changeShowProjections,
};
useEffect(() => {
const alignTabHeight = (): void => {
if (!sidebarCollapsed) {
updateTabContentHeight();
}
};
window.addEventListener('resize', alignTabHeight);
alignTabHeight();
return () => {
window.removeEventListener('resize', alignTabHeight);
};
}, []);
useEffect(() => {
const listener = (event: Event): void => {
@ -134,9 +136,12 @@ function ObjectsSideBar(props: Props): JSX.Element {
</Tabs.TabPane>
</Tabs>
{ !sidebarCollapsed && <AppearanceBlock {...appearanceProps} /> }
{ !sidebarCollapsed && <AppearanceBlock /> }
</Layout.Sider>
);
}
export default React.memo(ObjectsSideBar);
export default connect(
mapStateToProps,
mapDispatchToProps,
)(React.memo(ObjectsSideBar));

@ -8,9 +8,9 @@ import Layout from 'antd/lib/layout';
import CanvasWrapperContainer from 'containers/annotation-page/standard-workspace/canvas-wrapper';
import ControlsSideBarContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar';
import ObjectSideBarContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-side-bar';
import PropagateConfirmContainer from 'containers/annotation-page/standard-workspace/propagate-confirm';
import CanvasContextMenuContainer from 'containers/annotation-page/standard-workspace/canvas-context-menu';
import ObjectSideBarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar';
import CanvasPointContextMenuComponent from 'components/annotation-page/standard-workspace/canvas-point-context-menu';
export default function StandardWorkspaceComponent(): JSX.Element {
@ -18,7 +18,7 @@ export default function StandardWorkspaceComponent(): JSX.Element {
<Layout hasSider className='cvat-standard-workspace'>
<ControlsSideBarContainer />
<CanvasWrapperContainer />
<ObjectSideBarContainer />
<ObjectSideBarComponent />
<PropagateConfirmContainer />
<CanvasContextMenuContainer />
<CanvasPointContextMenuComponent />

@ -5,8 +5,6 @@
import './styles.scss';
import React from 'react';
import { connect } from 'react-redux';
import { Action } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import Result from 'antd/lib/result';
import Text from 'antd/lib/typography/Text';
import Paragraph from 'antd/lib/typography/Paragraph';
@ -16,6 +14,7 @@ import Tooltip from 'antd/lib/tooltip';
import copy from 'copy-to-clipboard';
import ErrorStackParser from 'error-stack-parser';
import { ThunkDispatch } from 'utils/redux';
import { resetAfterErrorAsync } from 'actions/boundaries-actions';
import { CombinedState } from 'reducers/interfaces';
import logger, { LogType } from 'cvat-logger';
@ -60,7 +59,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
};
}
function mapDispatchToProps(dispatch: ThunkDispatch<CombinedState, {}, Action>): DispatchToProps {
function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps {
return {
restore(): void {
dispatch(resetAfterErrorAsync());

@ -0,0 +1,296 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { connect } from 'react-redux';
import { LogType } from 'cvat-logger';
import { Canvas } from 'cvat-canvas-wrapper';
import { ThunkDispatch } from 'utils/redux';
import { updateAnnotationsAsync, changeFrameAsync } from 'actions/annotation-actions';
import { CombinedState } from 'reducers/interfaces';
import ItemButtonsComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item-buttons';
interface OwnProps {
clientID: number;
outsideDisabled?: boolean;
hiddenDisabled?: boolean;
keyframeDisabled?: boolean;
}
interface StateToProps {
objectState: any;
jobInstance: any;
frameNumber: number;
normalizedKeyMap: Record<string, string>;
canvasInstance: Canvas;
outsideDisabled: boolean;
hiddenDisabled: boolean;
keyframeDisabled: boolean;
}
interface DispatchToProps {
updateAnnotations(statesToUpdate: any[]): void;
changeFrame(frame: number): void;
}
function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
const {
annotation: {
annotations: {
states,
},
job: {
instance: jobInstance,
},
player: {
frame: {
number: frameNumber,
},
},
canvas: {
instance: canvasInstance,
},
},
shortcuts: {
normalizedKeyMap,
},
} = state;
const {
clientID,
outsideDisabled,
hiddenDisabled,
keyframeDisabled,
} = own;
const [objectState] = states
.filter((_objectState): boolean => _objectState.clientID === clientID);
return {
objectState,
normalizedKeyMap,
frameNumber,
jobInstance,
canvasInstance,
outsideDisabled: typeof (outsideDisabled) === 'undefined' ? false : outsideDisabled,
hiddenDisabled: typeof (hiddenDisabled) === 'undefined' ? false : hiddenDisabled,
keyframeDisabled: typeof (keyframeDisabled) === 'undefined' ? false : keyframeDisabled,
};
}
function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps {
return {
updateAnnotations(states: any[]) {
dispatch(updateAnnotationsAsync(states));
},
changeFrame(frame: number): void {
dispatch(changeFrameAsync(frame));
},
};
}
class ItemButtonsWrapper extends React.PureComponent<StateToProps & DispatchToProps> {
private navigateFirstKeyframe = (): void => {
const { objectState, frameNumber } = this.props;
const { first } = objectState.keyframes;
if (first !== frameNumber) {
this.changeFrame(first);
}
};
private navigatePrevKeyframe = (): void => {
const { objectState, frameNumber } = this.props;
const { prev } = objectState.keyframes;
if (prev !== null && prev !== frameNumber) {
this.changeFrame(prev);
}
};
private navigateNextKeyframe = (): void => {
const { objectState, frameNumber } = this.props;
const { next } = objectState.keyframes;
if (next !== null && next !== frameNumber) {
this.changeFrame(next);
}
};
private navigateLastKeyframe = (): void => {
const { objectState, frameNumber } = this.props;
const { last } = objectState.keyframes;
if (last !== frameNumber) {
this.changeFrame(last);
}
};
private lock = (): void => {
const { objectState, jobInstance } = this.props;
jobInstance.logger.log(LogType.lockObject, { locked: true });
objectState.lock = true;
this.commit();
};
private unlock = (): void => {
const { objectState, jobInstance } = this.props;
jobInstance.logger.log(LogType.lockObject, { locked: false });
objectState.lock = false;
this.commit();
};
private pin = (): void => {
const { objectState } = this.props;
objectState.pinned = true;
this.commit();
};
private unpin = (): void => {
const { objectState } = this.props;
objectState.pinned = 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 commit(): void {
const {
objectState,
updateAnnotations,
} = this.props;
updateAnnotations([objectState]);
}
private changeFrame(frame: number): void {
const { changeFrame, canvasInstance } = this.props;
if (canvasInstance.isAbleToChangeFrame()) {
changeFrame(frame);
}
}
public render(): JSX.Element {
const {
objectState,
normalizedKeyMap,
frameNumber,
outsideDisabled,
hiddenDisabled,
keyframeDisabled,
} = this.props;
const {
first,
prev,
next,
last,
} = objectState.keyframes || {
first: null, // shapes don't have keyframes, so we use null
prev: null,
next: null,
last: null,
};
return (
<ItemButtonsComponent
objectType={objectState.objectType}
shapeType={objectState.shapeType}
occluded={objectState.occluded}
outside={objectState.outside}
locked={objectState.lock}
pinned={objectState.pinned}
hidden={objectState.hidden}
keyframe={objectState.keyframe}
switchOccludedShortcut={normalizedKeyMap.SWITCH_OCCLUDED}
switchOutsideShortcut={normalizedKeyMap.SWITCH_OUTSIDE}
switchLockShortcut={normalizedKeyMap.SWITCH_LOCK}
switchHiddenShortcut={normalizedKeyMap.SWITCH_HIDDEN}
switchKeyFrameShortcut={normalizedKeyMap.SWITCH_KEYFRAME}
nextKeyFrameShortcut={normalizedKeyMap.NEXT_KEY_FRAME}
prevKeyFrameShortcut={normalizedKeyMap.PREV_KEY_FRAME}
outsideDisabled={outsideDisabled}
hiddenDisabled={hiddenDisabled}
keyframeDisabled={keyframeDisabled}
navigateFirstKeyframe={
first >= frameNumber || first === null
? null : this.navigateFirstKeyframe
}
navigatePrevKeyframe={
prev === frameNumber || prev === null
? null : this.navigatePrevKeyframe
}
navigateNextKeyframe={
next === frameNumber || next === null
? null : this.navigateNextKeyframe
}
navigateLastKeyframe={
last <= frameNumber || last === null
? null : this.navigateLastKeyframe
}
setOccluded={this.setOccluded}
unsetOccluded={this.unsetOccluded}
setOutside={this.setOutside}
unsetOutside={this.unsetOutside}
setKeyframe={this.setKeyframe}
unsetKeyframe={this.unsetKeyframe}
lock={this.lock}
unlock={this.unlock}
pin={this.pin}
unpin={this.unpin}
hide={this.hide}
show={this.show}
/>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ItemButtonsWrapper);

@ -7,7 +7,6 @@ import copy from 'copy-to-clipboard';
import { connect } from 'react-redux';
import { LogType } from 'cvat-logger';
import { Canvas } from 'cvat-canvas-wrapper';
import {
ActiveControl,
CombinedState,
@ -17,7 +16,6 @@ import {
import {
collapseObjectItems,
changeLabelColorAsync,
createAnnotationsAsync,
updateAnnotationsAsync,
changeFrameAsync,
removeObjectAsync,
@ -50,13 +48,11 @@ interface StateToProps {
minZLayer: number;
maxZLayer: number;
normalizedKeyMap: Record<string, string>;
canvasInstance: Canvas;
}
interface DispatchToProps {
changeFrame(frame: number): void;
updateState(objectState: any): void;
createAnnotations(sessionInstance: any, frameNumber: number, state: any): void;
collapseOrExpand(objectStates: any[], collapsed: boolean): void;
activateObject: (activatedStateID: number | null) => void;
removeObject: (sessionInstance: any, objectState: any) => void;
@ -91,7 +87,6 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
canvas: {
ready,
activeControl,
instance: canvasInstance,
},
colors,
},
@ -127,7 +122,6 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
minZLayer,
maxZLayer,
normalizedKeyMap,
canvasInstance,
};
}
@ -139,9 +133,6 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
updateState(state: any): void {
dispatch(updateAnnotationsAsync([state]));
},
createAnnotations(sessionInstance: any, frameNumber: number, state: any): void {
dispatch(createAnnotationsAsync(sessionInstance, frameNumber, state));
},
collapseOrExpand(objectStates: any[], collapsed: boolean): void {
dispatch(collapseObjectItems(objectStates, collapsed));
},
@ -172,38 +163,6 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
type Props = StateToProps & DispatchToProps;
class ObjectItemContainer extends React.PureComponent<Props> {
private navigateFirstKeyframe = (): void => {
const { objectState, frameNumber } = this.props;
const { first } = objectState.keyframes;
if (first !== frameNumber) {
this.changeFrame(first);
}
};
private navigatePrevKeyframe = (): void => {
const { objectState, frameNumber } = this.props;
const { prev } = objectState.keyframes;
if (prev !== null && prev !== frameNumber) {
this.changeFrame(prev);
}
};
private navigateNextKeyframe = (): void => {
const { objectState, frameNumber } = this.props;
const { next } = objectState.keyframes;
if (next !== null && next !== frameNumber) {
this.changeFrame(next);
}
};
private navigateLastKeyframe = (): void => {
const { objectState, frameNumber } = this.props;
const { last } = objectState.keyframes;
if (last !== frameNumber) {
this.changeFrame(last);
}
};
private copy = (): void => {
const { objectState, copyShape } = this.props;
copyShape(objectState);
@ -300,80 +259,6 @@ class ObjectItemContainer extends React.PureComponent<Props> {
}
};
private lock = (): void => {
const { objectState, jobInstance } = this.props;
jobInstance.logger.log(LogType.lockObject, { locked: true });
objectState.lock = true;
this.commit();
};
private unlock = (): void => {
const { objectState, jobInstance } = this.props;
jobInstance.logger.log(LogType.lockObject, { locked: false });
objectState.lock = false;
this.commit();
};
private pin = (): void => {
const { objectState } = this.props;
objectState.pinned = true;
this.commit();
};
private unpin = (): void => {
const { objectState } = this.props;
objectState.pinned = 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,
@ -473,13 +358,6 @@ class ObjectItemContainer extends React.PureComponent<Props> {
if (commit) this.commit();
};
private changeFrame(frame: number): void {
const { changeFrame, canvasInstance } = this.props;
if (canvasInstance.isAbleToChangeFrame()) {
changeFrame(frame);
}
}
private commit(): void {
const {
objectState,
@ -495,25 +373,12 @@ class ObjectItemContainer extends React.PureComponent<Props> {
collapsed,
labels,
attributes,
frameNumber,
activated,
colorBy,
colors,
normalizedKeyMap,
} = this.props;
const {
first,
prev,
next,
last,
} = objectState.keyframes || {
first: null, // shapes don't have keyframes, so we use null
prev: null,
next: null,
last: null,
};
let stateColor = '';
if (colorBy === ColorBy.INSTANCE) {
stateColor = objectState.color;
@ -530,12 +395,7 @@ class ObjectItemContainer extends React.PureComponent<Props> {
shapeType={objectState.shapeType}
clientID={objectState.clientID}
serverID={objectState.serverID}
occluded={objectState.occluded}
outside={objectState.outside}
locked={objectState.lock}
pinned={objectState.pinned}
hidden={objectState.hidden}
keyframe={objectState.keyframe}
attrValues={{ ...objectState.attributes }}
labelID={objectState.label.id}
color={stateColor}
@ -544,22 +404,6 @@ class ObjectItemContainer extends React.PureComponent<Props> {
normalizedKeyMap={normalizedKeyMap}
labels={labels}
collapsed={collapsed}
navigateFirstKeyframe={
first >= frameNumber || first === null
? null : this.navigateFirstKeyframe
}
navigatePrevKeyframe={
prev === frameNumber || prev === null
? null : this.navigatePrevKeyframe
}
navigateNextKeyframe={
next === frameNumber || next === null
? null : this.navigateNextKeyframe
}
navigateLastKeyframe={
last <= frameNumber || last === null
? null : this.navigateLastKeyframe
}
activate={this.activate}
remove={this.remove}
copy={this.copy}
@ -568,18 +412,6 @@ class ObjectItemContainer extends React.PureComponent<Props> {
switchOrientation={this.switchOrientation}
toBackground={this.toBackground}
toForeground={this.toForeground}
setOccluded={this.setOccluded}
unsetOccluded={this.unsetOccluded}
setOutside={this.setOutside}
unsetOutside={this.unsetOutside}
setKeyframe={this.setKeyframe}
unsetKeyframe={this.unsetKeyframe}
lock={this.lock}
unlock={this.unlock}
pin={this.pin}
unpin={this.unpin}
hide={this.hide}
show={this.show}
changeColor={this.changeColor}
changeLabel={this.changeLabel}
changeAttribute={this.changeAttribute}

@ -1,254 +0,0 @@
// Copyright (C) 2020 Intel Corporation
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { connect } from 'react-redux';
import { RadioChangeEvent } from 'antd/lib/radio';
import { SliderValue } from 'antd/lib/slider';
import { CheckboxChangeEvent } from 'antd/lib/checkbox';
import ObjectsSidebarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar';
import {
CombinedState,
ColorBy,
} from 'reducers/interfaces';
import {
collapseSidebar as collapseSidebarAction,
collapseAppearance as collapseAppearanceAction,
updateTabContentHeight as updateTabContentHeightAction,
} from 'actions/annotation-actions';
import {
changeShapesColorBy as changeShapesColorByAction,
changeShapesOpacity as changeShapesOpacityAction,
changeSelectedShapesOpacity as changeSelectedShapesOpacityAction,
changeShapesBlackBorders as changeShapesBlackBordersAction,
changeShowBitmap as changeShowUnlabeledRegionsAction,
changeShowProjections as changeShowProjectionsAction,
} from 'actions/settings-actions';
import { Canvas } from 'cvat-canvas-wrapper';
interface StateToProps {
sidebarCollapsed: boolean;
appearanceCollapsed: boolean;
colorBy: ColorBy;
opacity: number;
selectedOpacity: number;
blackBorders: boolean;
showBitmap: boolean;
showProjections: boolean;
canvasInstance: Canvas;
}
interface DispatchToProps {
collapseSidebar(): void;
collapseAppearance(): void;
updateTabContentHeight(): void;
changeShapesColorBy(colorBy: ColorBy): void;
changeShapesOpacity(shapesOpacity: number): void;
changeSelectedShapesOpacity(selectedShapesOpacity: number): void;
changeShapesBlackBorders(blackBorders: boolean): void;
changeShowBitmap(showBitmap: boolean): void;
changeShowProjections(showProjections: boolean): void;
}
function mapStateToProps(state: CombinedState): StateToProps {
const {
annotation: {
sidebarCollapsed,
appearanceCollapsed,
canvas: {
instance: canvasInstance,
},
},
settings: {
shapes: {
colorBy,
opacity,
selectedOpacity,
blackBorders,
showBitmap,
showProjections,
},
},
} = state;
return {
sidebarCollapsed,
appearanceCollapsed,
colorBy,
opacity,
selectedOpacity,
blackBorders,
showBitmap,
showProjections,
canvasInstance,
};
}
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(),
),
);
},
changeShapesColorBy(colorBy: ColorBy): void {
dispatch(changeShapesColorByAction(colorBy));
},
changeShapesOpacity(shapesOpacity: number): void {
dispatch(changeShapesOpacityAction(shapesOpacity));
},
changeSelectedShapesOpacity(selectedShapesOpacity: number): void {
dispatch(changeSelectedShapesOpacityAction(selectedShapesOpacity));
},
changeShapesBlackBorders(blackBorders: boolean): void {
dispatch(changeShapesBlackBordersAction(blackBorders));
},
changeShowBitmap(showBitmap: boolean) {
dispatch(changeShowUnlabeledRegionsAction(showBitmap));
},
changeShowProjections(showProjections: boolean) {
dispatch(changeShowProjectionsAction(showProjections));
},
};
}
type Props = StateToProps & DispatchToProps;
class ObjectsSideBarContainer extends React.PureComponent<Props> {
public componentDidMount(): void {
window.addEventListener('resize', this.alignTabHeight);
this.alignTabHeight();
}
public componentWillUnmount(): void {
window.removeEventListener('resize', this.alignTabHeight);
}
private alignTabHeight = (): void => {
const {
sidebarCollapsed,
updateTabContentHeight,
} = this.props;
if (!sidebarCollapsed) {
updateTabContentHeight();
}
};
private changeShapesColorBy = (event: RadioChangeEvent): void => {
const { changeShapesColorBy } = this.props;
changeShapesColorBy(event.target.value);
};
private changeShapesOpacity = (value: SliderValue): void => {
const { changeShapesOpacity } = this.props;
changeShapesOpacity(value as number);
};
private changeSelectedShapesOpacity = (value: SliderValue): void => {
const { changeSelectedShapesOpacity } = this.props;
changeSelectedShapesOpacity(value as number);
};
private changeShapesBlackBorders = (event: CheckboxChangeEvent): void => {
const { changeShapesBlackBorders } = this.props;
changeShapesBlackBorders(event.target.checked);
};
private changeShowBitmap = (event: CheckboxChangeEvent): void => {
const { changeShowBitmap } = this.props;
changeShowBitmap(event.target.checked);
};
private changeShowProjections = (event: CheckboxChangeEvent): void => {
const { changeShowProjections } = this.props;
changeShowProjections(event.target.checked);
};
public render(): JSX.Element {
const {
sidebarCollapsed,
appearanceCollapsed,
colorBy,
opacity,
selectedOpacity,
blackBorders,
showBitmap,
showProjections,
canvasInstance,
collapseSidebar,
collapseAppearance,
} = this.props;
return (
<ObjectsSidebarComponent
sidebarCollapsed={sidebarCollapsed}
appearanceCollapsed={appearanceCollapsed}
colorBy={colorBy}
opacity={opacity}
selectedOpacity={selectedOpacity}
blackBorders={blackBorders}
showBitmap={showBitmap}
showProjections={showProjections}
canvasInstance={canvasInstance}
collapseSidebar={collapseSidebar}
collapseAppearance={collapseAppearance}
changeShapesColorBy={this.changeShapesColorBy}
changeShapesOpacity={this.changeShapesOpacity}
changeSelectedShapesOpacity={this.changeSelectedShapesOpacity}
changeShapesBlackBorders={this.changeShapesBlackBorders}
changeShowBitmap={this.changeShowBitmap}
changeShowProjections={this.changeShowProjections}
/>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ObjectsSideBarContainer);

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { connect } from 'react-redux';
import CreateModelPageComponent from 'components/create-model-page/create-model-page';
@ -38,13 +37,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
};
}
function CreateModelPageContainer(props: StateToProps & DispatchToProps): JSX.Element {
return (
<CreateModelPageComponent {...props} />
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(CreateModelPageContainer);
)(CreateModelPageComponent);

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { connect } from 'react-redux';
import { CombinedState } from 'reducers/interfaces';
@ -34,13 +33,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
};
}
function CreateTaskPageContainer(props: StateToProps & DispatchToProps): JSX.Element {
return (
<CreateTaskComponent {...props} />
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(CreateTaskPageContainer);
)(CreateTaskComponent);

@ -3,7 +3,6 @@
// SPDX-License-Identifier: MIT
import { connect } from 'react-redux';
import { ExtendedKeyMapOptions } from 'react-hotkeys';
import getCore from 'cvat-core-wrapper';
import HeaderComponent from 'components/header/header';

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { connect } from 'react-redux';
import ModelRunnerModalComponent from 'components/model-runner-modal/model-runner-modal';
@ -75,14 +74,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
});
}
function ModelRunnerModalContainer(props: StateToProps & DispatchToProps): JSX.Element {
return (
<ModelRunnerModalComponent {...props} />
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ModelRunnerModalContainer);
)(ModelRunnerModalComponent);

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { RouteComponentProps } from 'react-router';
@ -74,13 +73,7 @@ function mapDispatchToProps(dispatch: any, own: Props): DispatchToProps {
};
}
function TaskPageContainer(props: StateToProps & DispatchToProps): JSX.Element {
return (
<TaskPageComponent {...props} />
);
}
export default withRouter(connect(
mapStateToProps,
mapDispatchToProps,
)(TaskPageContainer));
)(TaskPageComponent));

@ -2,7 +2,6 @@
//
// SPDX-License-Identifier: MIT
import React from 'react';
import { connect } from 'react-redux';
import {
@ -55,15 +54,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps {
};
}
type TasksPageContainerProps = StateToProps & DispatchToProps;
function TasksPageContainer(props: TasksPageContainerProps): JSX.Element {
return (
<TasksPageComponent {...props} />
);
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(TasksPageContainer);
)(TasksPageComponent);

@ -23,5 +23,5 @@ export type ActionUnion<A extends ActionCreatorsMapObject> = ReturnType<A[keyof
export type ThunkAction<R = void, A extends Action = AnyAction>
= _ThunkAction<R, CombinedState, {}, A>;
export type ThunkDispatch<E = void, A extends Action = AnyAction>
export type ThunkDispatch<E = {}, A extends Action = AnyAction>
= _ThunkDispatch<CombinedState, E, A>;

Loading…
Cancel
Save