From b1f2e46e8e9e622595490a8fb37950a554d5bbe4 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 12 Mar 2020 14:32:34 +0300 Subject: [PATCH 01/17] Done main work --- cvat-canvas/src/typescript/canvasView.ts | 91 +++-- cvat-ui/src/actions/annotation-actions.ts | 76 ++++- .../annotation-page/annotation-page.tsx | 19 +- .../attribute-annotation-sidebar.tsx | 287 ++++++++++++++++ .../attribute-editor.tsx | 317 ++++++++++++++++++ .../attribute-switcher.tsx | 43 +++ .../object-basics-edtior.tsx | 43 +++ .../object-switcher.tsx | 48 +++ .../attribute-annotation-workspace.tsx | 19 ++ .../styles.scss | 65 ++++ .../standard-workspace/canvas-wrapper.tsx | 58 ++-- .../standard-workspace/standard-workspace.tsx | 2 +- .../standard-workspace/styles.scss | 63 +--- .../components/annotation-page/styles.scss | 61 ++++ .../annotation-page/top-bar/right-group.tsx | 30 +- .../annotation-page/top-bar/top-bar.tsx | 82 ++--- .../settings-page/workspace-settings.tsx | 2 +- .../annotation-page/annotation-page.tsx | 7 +- .../standard-workspace/canvas-wrapper.tsx | 20 +- .../objects-side-bar/label-item.tsx | 14 +- .../objects-side-bar/object-item.tsx | 25 +- .../objects-side-bar/objects-list.tsx | 29 +- .../annotation-page/top-bar/top-bar.tsx | 16 +- cvat-ui/src/reducers/annotation-reducer.ts | 48 ++- cvat-ui/src/reducers/interfaces.ts | 7 + 25 files changed, 1255 insertions(+), 217 deletions(-) create mode 100644 cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx create mode 100644 cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx create mode 100644 cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx create mode 100644 cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx create mode 100644 cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx create mode 100644 cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx create mode 100644 cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index d026c5246a7..da9b60e8d80 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -264,9 +264,10 @@ export class CanvasViewImpl implements CanvasView, Listener { y + height / 2, ]); + const canvasOffset = this.canvas.getBoundingClientRect(); const [cx, cy] = [ - this.canvas.clientWidth / 2 + this.canvas.offsetLeft, - this.canvas.clientHeight / 2 + this.canvas.offsetTop, + this.canvas.clientWidth / 2 + canvasOffset.left, + this.canvas.clientHeight / 2 + canvasOffset.top, ]; const dragged = { @@ -725,7 +726,7 @@ export class CanvasViewImpl implements CanvasView, Listener { if (object) { const bbox: SVG.BBox = object.bbox(); this.onFocusRegion(bbox.x - padding, bbox.y - padding, - bbox.width + padding, bbox.height + padding); + bbox.width + padding * 2, bbox.height + padding * 2); } } else if (reason === UpdateReasons.SHAPE_ACTIVATED) { this.activate(this.controller.activeElement); @@ -1014,7 +1015,26 @@ export class CanvasViewImpl implements CanvasView, Listener { this.content.prepend(...sorted.map((pair): SVGElement => pair[0])); } - private deactivate(): void { + private deactivateAttribute(): void { + const { clientID, attributeID } = this.activeElement; + if (clientID !== null && attributeID !== null) { + const text = this.svgTexts[clientID]; + if (text) { + const [span] = text.node + .querySelectorAll(`[attrID="${attributeID}"]`) as any as SVGTSpanElement[]; + if (span) { + span.style.fill = ''; + } + } + + this.activeElement = { + ...this.activeElement, + attributeID: null, + }; + } + } + + private deactivateShape(): void { if (this.activeElement.clientID !== null) { const { clientID } = this.activeElement; const drawnState = this.drawnStates[clientID]; @@ -1047,29 +1067,34 @@ export class CanvasViewImpl implements CanvasView, Listener { this.sortObjects(); this.activeElement = { + ...this.activeElement, clientID: null, - attributeID: null, }; } } - private activate(activeElement: ActiveElement): void { - // Check if other element have been already activated - if (this.activeElement.clientID !== null) { - // Check if it is the same element - if (this.activeElement.clientID === activeElement.clientID) { - return; - } + private deactivate(): void { + this.deactivateAttribute(); + this.deactivateShape(); + } - // Deactivate previous element - this.deactivate(); - } + private activateAttribute(clientID: number, attributeID: number): void { + const text = this.svgTexts[clientID]; + if (text) { + const [span] = text.node + .querySelectorAll(`[attrID="${attributeID}"]`) as any as SVGTSpanElement[]; + if (span) { + span.style.fill = 'red'; + } - const { clientID } = activeElement; - if (clientID === null) { - return; + this.activeElement = { + ...this.activeElement, + attributeID, + }; } + } + private activateShape(clientID: number): void { const [state] = this.controller.objects .filter((_state: any): boolean => _state.clientID === clientID); @@ -1082,7 +1107,6 @@ export class CanvasViewImpl implements CanvasView, Listener { return; } - this.activeElement = { ...activeElement }; const shape = this.svgShapes[clientID]; let text = this.svgTexts[clientID]; if (!text) { @@ -1188,6 +1212,11 @@ export class CanvasViewImpl implements CanvasView, Listener { } }); + this.activeElement = { + ...this.activeElement, + clientID, + }; + this.canvas.dispatchEvent(new CustomEvent('canvas.activated', { bubbles: false, cancelable: true, @@ -1197,6 +1226,30 @@ export class CanvasViewImpl implements CanvasView, Listener { })); } + private activate(activeElement: ActiveElement): void { + // Check if another element have been already activated + if (this.activeElement.clientID !== null) { + if (this.activeElement.clientID !== activeElement.clientID) { + // Deactivate previous shape and attribute + this.deactivate(); + } else if (this.activeElement.attributeID !== activeElement.attributeID) { + this.deactivateAttribute(); + } + } + + const { clientID, attributeID } = activeElement; + if (clientID !== null && this.activeElement.clientID !== clientID) { + this.activateShape(clientID); + } + + if (clientID !== null + && attributeID !== null + && this.activeElement.attributeID !== attributeID + ) { + this.activateAttribute(clientID, attributeID); + } + } + // Update text position after corresponding box has been moved, resized, etc. private updateTextPosition(text: SVG.Text, shape: SVG.Shape): void { let box = (shape.node as any).getBBox(); diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 44aeb752ceb..1689bcb2dac 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -18,12 +18,20 @@ import { Task, FrameSpeed, Rotation, + Workspace, } from 'reducers/interfaces'; import getCore from 'cvat-core'; import { RectDrawingMethod } from 'cvat-canvas'; import { getCVATStore } from 'cvat-store'; +interface AnnotationsParameters { + filters: string[]; + frame: number; + showAllInterpolationTracks: boolean; + jobInstance: any; +} + const cvat = getCore(); let store: null | Store = null; @@ -34,19 +42,37 @@ function getStore(): Store { return store; } -function receiveAnnotationsParameters(): -{ filters: string[]; frame: number; showAllInterpolationTracks: boolean } { +function receiveAnnotationsParameters(): AnnotationsParameters { if (store === null) { store = getCVATStore(); } const state: CombinedState = getStore().getState(); - const { filters } = state.annotation.annotations; - const frame = state.annotation.player.frame.number; - const { showAllInterpolationTracks } = state.settings.workspace; + const { + annotation: { + annotations: { + filters, + }, + player: { + frame: { + number: frame, + }, + }, + job: { + instance: jobInstance, + }, + }, + settings: { + workspace: { + showAllInterpolationTracks, + }, + }, + } = state; + return { filters, frame, + jobInstance, showAllInterpolationTracks, }; } @@ -138,11 +164,23 @@ export enum AnnotationActionTypes { SWITCH_Z_LAYER = 'SWITCH_Z_LAYER', ADD_Z_LAYER = 'ADD_Z_LAYER', SEARCH_ANNOTATIONS_FAILED = 'SEARCH_ANNOTATIONS_FAILED', + ACTIVATE_NEXT = 'ACTIVATE_NEXT', + CHANGE_WORKSPACE = 'CHANGE_WORKSPACE', +} + +export function changeWorkspace(workspace: Workspace): AnyAction { + return { + type: AnnotationActionTypes.CHANGE_WORKSPACE, + payload: { + workspace, + }, + }; } export function addZLayer(): AnyAction { return { type: AnnotationActionTypes.ADD_Z_LAYER, + payload: {}, }; } @@ -559,11 +597,15 @@ export function selectObjects(selectedStatesID: number[]): AnyAction { }; } -export function activateObject(activatedStateID: number | null): AnyAction { +export function activateObject( + activatedStateID: number | null, + activatedAttributeID: number | null, +): AnyAction { return { type: AnnotationActionTypes.ACTIVATE_OBJECT, payload: { activatedStateID, + activatedAttributeID, }, }; } @@ -906,19 +948,26 @@ export function splitTrack(enabled: boolean): AnyAction { }; } -export function updateAnnotationsAsync(sessionInstance: any, frame: number, statesToUpdate: any[]): +export function updateAnnotationsAsync(statesToUpdate: any[]): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { + const { + jobInstance, + filters, + frame, + showAllInterpolationTracks, + } = receiveAnnotationsParameters(); + try { if (statesToUpdate.some((state: any): boolean => state.updateFlags.zOrder)) { // deactivate object to visualize changes immediately (UX) - dispatch(activateObject(null)); + dispatch(activateObject(null, null)); } const promises = statesToUpdate .map((objectState: any): Promise => objectState.save()); const states = await Promise.all(promises); - const history = await sessionInstance.actions.get(); + const history = await jobInstance.actions.get(); const [minZ, maxZ] = computeZRange(states); dispatch({ @@ -931,8 +980,7 @@ ThunkAction, {}, {}, AnyAction> { }, }); } catch (error) { - const { filters, showAllInterpolationTracks } = receiveAnnotationsParameters(); - const states = await sessionInstance.annotations + const states = await jobInstance.annotations .get(frame, showAllInterpolationTracks, filters); dispatch({ type: AnnotationActionTypes.UPDATE_ANNOTATIONS_FAILED, @@ -1110,8 +1158,6 @@ export function changeLabelColorAsync( } export function changeGroupColorAsync( - sessionInstance: any, - frameNumber: number, group: number, color: string, ): ThunkAction, {}, {}, AnyAction> { @@ -1121,9 +1167,9 @@ export function changeGroupColorAsync( .filter((_state: any): boolean => _state.group.id === group); if (groupStates.length) { groupStates[0].group.color = color; - dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, groupStates)); + dispatch(updateAnnotationsAsync(groupStates)); } else { - dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, [])); + dispatch(updateAnnotationsAsync([])); } }; } diff --git a/cvat-ui/src/components/annotation-page/annotation-page.tsx b/cvat-ui/src/components/annotation-page/annotation-page.tsx index 3ea1c87c8cd..a096afd2fde 100644 --- a/cvat-ui/src/components/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/components/annotation-page/annotation-page.tsx @@ -11,14 +11,17 @@ import { Result, } from 'antd'; +import { Workspace } from 'reducers/interfaces'; import AnnotationTopBarContainer from 'containers/annotation-page/top-bar/top-bar'; import StatisticsModalContainer from 'containers/annotation-page/top-bar/statistics-modal'; import StandardWorkspaceComponent from './standard-workspace/standard-workspace'; +import AttributeAnnotationWorkspace from './attribute-annotation-workspace/attribute-annotation-workspace'; interface Props { job: any | null | undefined; fetching: boolean; getJob(): void; + workspace: Workspace; } @@ -27,9 +30,9 @@ export default function AnnotationPageComponent(props: Props): JSX.Element { job, fetching, getJob, + workspace, } = props; - if (job === null) { if (!fetching) { getJob(); @@ -51,8 +54,18 @@ export default function AnnotationPageComponent(props: Props): JSX.Element { return ( - - + + + + { workspace === Workspace.STANDARD ? ( + + + + ) : ( + + + + )} ); diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx new file mode 100644 index 00000000000..a0c745f5110 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -0,0 +1,287 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React, { useState } from 'react'; +import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; +import { connect } from 'react-redux'; +import Layout, { SiderProps } from 'antd/lib/layout'; +import { SelectValue } from 'antd/lib/select'; +import { CheckboxChangeEvent } from 'antd/lib/checkbox'; +import Text from 'antd/lib/typography/Text'; + +import { + activateObject as activateObjectAction, + updateAnnotationsAsync, +} from 'actions/annotation-actions'; +import { CombinedState } from 'reducers/interfaces'; +import ObjectSwitcher from './object-switcher'; +import AttributeSwitcher from './attribute-switcher'; +import ObjectBasicsEditor from './object-basics-edtior'; +import AttributeEditor from './attribute-editor'; + + +interface StateToProps { + activatedStateID: number | null; + activatedAttributeID: number | null; + states: any[]; + labels: any[]; +} + +interface DispatchToProps { + activateObject(clientID: number | null, attrID: number | null): void; + updateAnnotations(statesToUpdate: any[]): void; +} + +interface LabelAttrMap { + [index: number]: any; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + annotation: { + annotations: { + activatedStateID, + activatedAttributeID, + states, + }, + job: { + labels, + }, + }, + } = state; + + return { + labels, + activatedStateID, + activatedAttributeID, + states, + }; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + activateObject(clientID: number, attrID: number): void { + dispatch(activateObjectAction(clientID, attrID)); + }, + updateAnnotations(states): void { + dispatch(updateAnnotationsAsync(states)); + }, + }; +} + +function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Element { + const { + labels, + states, + activatedStateID, + activatedAttributeID, + updateAnnotations, + activateObject, + } = props; + + const [labelAttrMap, setLabelAttrMap] = useState( + labels.reduce((acc, label): LabelAttrMap => { + acc[label.id] = label.attributes.length ? label.attributes[0] : null; + return acc; + }, {}), + ); + + const [activeObjectState] = activatedStateID === null + ? [null] : states.filter((objectState: any): boolean => ( + objectState.clientID === activatedStateID + )); + const activeAttribute = activeObjectState + ? labelAttrMap[activeObjectState.label.id] + : null; + + if (activeObjectState) { + const attribute = labelAttrMap[activeObjectState.label.id]; + 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); + } + + const nextObject = (step: number): void => { + if (states.length) { + const index = states.indexOf(activeObjectState); + let nextIndex = index + step; + if (nextIndex > states.length - 1) { + nextIndex = 0; + } else if (nextIndex < 0) { + nextIndex = states.length - 1; + } + if (nextIndex !== index) { + const attribute = labelAttrMap[states[nextIndex].label.id]; + activateObject(states[nextIndex].clientID, attribute ? attribute.id : null); + } + } + }; + + const nextAttribute = (step: number): void => { + if (activeObjectState) { + const { label } = activeObjectState; + const { attributes } = label; + if (attributes.length) { + const index = attributes.indexOf(activeAttribute); + let nextIndex = index + step; + if (nextIndex > attributes.length - 1) { + nextIndex = 0; + } else if (nextIndex < 0) { + nextIndex = attributes.length - 1; + } + if (index !== nextIndex) { + const updatedLabelAttrMap = { ...labelAttrMap }; + updatedLabelAttrMap[label.id] = attributes[nextIndex]; + setLabelAttrMap(updatedLabelAttrMap); + } + } + } + }; + + const siderProps: SiderProps = { + className: 'attribute-annotation-sidebar', + theme: 'light', + width: 300, + collapsedWidth: 0, + reverseArrow: true, + collapsible: true, + trigger: null, + }; + + const keyMap = { + NEXT_ATTRIBUTE: { + name: 'Next object', + description: 'Go to the next object. Use Ctrl when inside an input field', + sequences: ['ArrowDown', 'Ctrl+ArrowDown'], + action: 'keydown', + }, + PREVIOUS_ATTRIBUTE: { + name: 'Previous object', + description: 'Go to the previous object. Use Ctrl when inside an input field', + sequences: ['ArrowUp', 'Ctrl+ArrowUp'], + action: 'keydown', + }, + NEXT_OBJECT: { + name: 'Next object', + description: 'Go to the next object. Use Ctrl when inside an input field', + sequences: ['Tab', 'Ctrl+Tab'], + action: 'keydown', + }, + PREVIOUS_OBJECT: { + name: 'Previous object', + description: 'Go to the previous object. Use Ctrl when inside an input field', + sequences: ['Shift+Tab', 'Ctrl+Shift+Tab'], + action: 'keydown', + }, + }; + + const handlers = { + NEXT_ATTRIBUTE: (event: KeyboardEvent | undefined) => { + if (event) { + event.preventDefault(); + } + + nextAttribute(1); + }, + PREVIOUS_ATTRIBUTE: (event: KeyboardEvent | undefined) => { + if (event) { + event.preventDefault(); + } + + nextAttribute(-1); + }, + NEXT_OBJECT: (event: KeyboardEvent | undefined) => { + if (event) { + event.preventDefault(); + } + + nextObject(1); + }, + PREVIOUS_OBJECT: (event: KeyboardEvent | undefined) => { + if (event) { + event.preventDefault(); + } + + nextObject(-1); + }, + }; + + if (activeObjectState) { + return ( + + + + { + const labelName = value as string; + const [newLabel] = labels + .filter((_label): boolean => _label.name === labelName); + activeObjectState.label = newLabel; + updateAnnotations([activeObjectState]); + }} + setOccluded={(event: CheckboxChangeEvent): void => { + activeObjectState.occluded = event.target.checked; + updateAnnotations([activeObjectState]); + }} + /> + { + activeAttribute + ? ( + <> + + { + const { attributes } = activeObjectState; + attributes[activeAttribute.id] = value; + activeObjectState.attributes = attributes; + updateAnnotations([activeObjectState]); + }} + /> + + + ) : ( +
+ No attributes found +
+ ) + } +
+ ); + } + + return ( + +
+ No objects found +
+
+ ); +} + + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(AttributeAnnotationSidebar); diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx new file mode 100644 index 00000000000..48402273fd1 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx @@ -0,0 +1,317 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React, { useEffect } from 'react'; +import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; +import Text from 'antd/lib/typography/Text'; +import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; +import Select, { SelectValue } from 'antd/lib/select'; +import Radio, { RadioChangeEvent } from 'antd/lib/radio'; +import Input from 'antd/lib/input'; +import InputNumber from 'antd/lib/input-number'; + +interface InputElementParameters { + inputType: string; + values: string[]; + currentValue: string; + onChange(value: string): void; + ref: React.RefObject; +} + +function renderInputElement(parameters: InputElementParameters): JSX.Element { + const { + inputType, + values, + currentValue, + onChange, + ref, + } = parameters; + + const renderCheckbox = (): JSX.Element => ( + <> + Checkbox: +
+ ( + onChange(event.target.checked ? 'true' : 'false') + )} + checked={currentValue === 'true'} + /> +
+ + ); + + const renderSelect = (): JSX.Element => ( + <> + Values: +
+ +
+ + ); + + const renderRadio = (): JSX.Element => ( + <> + Values: +
+ ( + onChange(event.target.value) + )} + > + {values.map((value: string): JSX.Element => ( + {value} + ))} + +
+ + ); + + // Hardly ever good solution with lockedChange is a workaround + // Antd InputNumber changes current value when press ArrowUp, ArrowDown + // And only after that it calls onKeyDown callback + // There aren't any API to prevent such behavour (call onKeyDown first or cancel changing value) + // This trick works as following: + // 1. User presses ArrowUp/ArrowDown. InputNumber changes it's internal state and calls onChange + // 2. onChange setups a setTimeout that work after current callstack is done + // 3. InputNumber next calls onKeyDown callback where we lock any changes by the flag + // callback also setups a setTimeout to unlock this flag and put it to event loop + // 4. then the first setTimeout callback works and do not change value because of locked flag + // 5. finally the second setTimeout callback works and reset the locked flag + let lockedChange = false; + const handleKeydown = (event: React.KeyboardEvent): void => { + if (['ArrowDown', 'ArrowUp', 'ArrowLeft', + 'ArrowRight', 'Tab', 'Shift', 'Control'] + .includes(event.key) + ) { + event.preventDefault(); + const copyEvent = new KeyboardEvent('keydown', event); + window.document.dispatchEvent(copyEvent); + lockedChange = true; + setTimeout(() => { + lockedChange = false; + }); + } + }; + + const renderNumber = (): JSX.Element => ( + <> + Number: +
+ } + autoFocus + min={+values[0]} + max={+values[1]} + step={+values[2]} + value={+currentValue} + onChange={(value: number | undefined) => { + setTimeout(() => { + if (typeof (value) !== 'undefined' && !lockedChange) { + onChange(`${value}`); + } + }); + }} + onKeyDown={handleKeydown} + /> +
+ + ); + + const renderText = (): JSX.Element => ( + <> + Text: +
+ ) => { + onChange(event.target.value); + }} + onKeyDown={handleKeydown} + ref={ref as React.RefObject} + /> +
+ + ); + + let element = null; + if (inputType === 'checkbox') { + element = renderCheckbox(); + } else if (inputType === 'select') { + element = renderSelect(); + } else if (inputType === 'radio') { + element = renderRadio(); + } else if (inputType === 'number') { + element = renderNumber(); + } else { + element = renderText(); + } + + return ( +
+ {element} +
+ ); +} + +interface ListParameters { + inputType: string; + values: string[]; + onChange(value: string): void; +} + +function renderList(parameters: ListParameters): JSX.Element | null { + const { inputType, values, onChange } = parameters; + + if (inputType === 'checkbox') { + const sortedValues = ['true', 'false']; + if (values[0].toLowerCase() !== 'true') { + sortedValues.reverse(); + } + + const keyMap: KeyMap = {}; + const handlers: { + [key: string]: (keyEvent?: KeyboardEvent) => void; + } = {}; + + sortedValues.forEach((value: string, index: number): void => { + const key = `SET_${index}_VALUE`; + keyMap[key] = { + name: `Set value "${value}"`, + description: `Change current value for the attribute to "${value}"`, + sequence: `${index}`, + action: 'keydown', + }; + + handlers[key] = (event: KeyboardEvent | undefined) => { + if (event) { + event.preventDefault(); + } + + onChange(value); + }; + }); + + return ( +
+ +
+ 0: + {` ${sortedValues[0]}`} +
+
+ 1: + {` ${sortedValues[1]}`} +
+
+ ); + } + + if (inputType === 'radio' || inputType === 'select') { + const keyMap: KeyMap = {}; + const handlers: { + [key: string]: (keyEvent?: KeyboardEvent) => void; + } = {}; + + values.forEach((value: string, index: number): void => { + const key = `SET_${index}_VALUE`; + keyMap[key] = { + name: `Set value "${value}"`, + description: `Change current value for the attribute to "${value}"`, + sequence: `${index}`, + action: 'keydown', + }; + + handlers[key] = (event: KeyboardEvent | undefined) => { + if (event) { + event.preventDefault(); + } + + onChange(value); + }; + }); + + return ( +
+ + {values.map((value: string, index: number): JSX.Element => ( +
+ {`${index}:`} + {` ${value}`} +
+ ))} +
+ ); + } + + if (inputType === 'number') { + return ( +
+
+ From: + {` ${values[0]}`} +
+
+ To: + {` ${values[1]}`} +
+
+ Step: + {` ${values[2]}`} +
+
+ ); + } + + return null; +} + +interface Props { + attribute: any; + currentValue: string; + onChange(value: string): void; +} + +function AttributeEditor(props: Props): JSX.Element { + const { attribute, currentValue, onChange } = props; + const { inputType, values } = attribute; + const ref = inputType === 'number' ? React.createRef() + : React.createRef(); + + useEffect(() => { + // if (ref.current) { + // ref.current.focus(); + // } else if (document.activeElement) { + // (document.activeElement as HTMLElement).blur(); + // } + }); + + return ( +
+ {renderList({ values, inputType, onChange })} +
+ {renderInputElement({ + ref, + inputType, + currentValue, + values, + onChange, + })} +
+ ); +} + +export default React.memo(AttributeEditor); diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx new file mode 100644 index 00000000000..5f0eea0c12a --- /dev/null +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx @@ -0,0 +1,43 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import Icon from 'antd/lib/icon'; +import Text from 'antd/lib/typography/Text'; +import Tooltip from 'antd/lib/tooltip'; +import Button from 'antd/lib/button'; + +interface Props { + currentAttribute: string; + currentIndex: number; + attributesCount: number; + nextAttribute(step: number): void; +} + +function AttributeSwitcher(props: Props): JSX.Element { + const { + currentAttribute, + currentIndex, + attributesCount, + nextAttribute, + } = props; + + const title = `${currentAttribute} [${currentIndex + 1}/${attributesCount}]`; + return ( +
+ + + {currentAttribute} + {` [${currentIndex + 1}/${attributesCount}]`} + + +
+ ); +} + +export default React.memo(AttributeSwitcher); diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx new file mode 100644 index 00000000000..17a689a997f --- /dev/null +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-basics-edtior.tsx @@ -0,0 +1,43 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +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; + + return ( +
+ + Occluded +
+ ); +} + +export default React.memo(ObjectBasicsEditor); diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx new file mode 100644 index 00000000000..e1cde7fdd65 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx @@ -0,0 +1,48 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import Icon from 'antd/lib/icon'; +import Text from 'antd/lib/typography/Text'; +import Tooltip from 'antd/lib/tooltip'; +import Button from 'antd/lib/button'; + +interface Props { + currentLabel: string; + clientID: number; + occluded: boolean; + objectsCount: number; + currentIndex: number; + nextObject(step: number): void; +} + +function ObjectSwitcher(props: Props): JSX.Element { + const { + currentLabel, + clientID, + objectsCount, + currentIndex, + nextObject, + } = props; + + + const title = `${currentLabel} ${clientID} [${currentIndex + 1}/${objectsCount}]`; + return ( +
+ + + {currentLabel} + {` ${clientID} `} + {`[${currentIndex + 1}/${objectsCount}]`} + + +
+ ); +} + +export default React.memo(ObjectSwitcher); diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx new file mode 100644 index 00000000000..8c89c4d9ba6 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx @@ -0,0 +1,19 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import './styles.scss'; +import React from 'react'; +import Layout from 'antd/lib/layout'; + +import CanvasWrapperContainer from 'containers/annotation-page/standard-workspace/canvas-wrapper'; +import ObjectSidebar from './attribute-annotation-sidebar/attribute-annotation-sidebar'; + +export default function StandardWorkspaceComponent(): JSX.Element { + return ( + + + + + ); +} diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss new file mode 100644 index 00000000000..f62dc6c5da5 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss @@ -0,0 +1,65 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +@import 'base.scss'; + +.attribute-annotation-workspace.ant-layout { + height: 100%; +} + +.attribute-annotation-sidebar { + background: $background-color-2; + padding: 5px; +} + +.attribute-annotation-sidebar-switcher { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 18px; + + > span { + max-width: 60%; + text-overflow: ellipsis; + overflow: hidden; + } + + > button > i { + color: $objects-bar-icons-color; + } +} + +.attribute-annotation-sidebar-basics-editor { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 18px; + margin: 10px 0px; +} + +.attribute-annotations-sidebar-not-found-wrapper { + margin-top: 20px; + text-align: center; +} + +.attribute-annotation-sidebar-attr-list-wrapper { + margin: 10px 0px 10px 10px; +} + + +.attribute-annotation-sidebar-attr-elem-wrapper { + display: inline-block; + width: 60%; +} + +.attribute-annotation-sidebar-number-list { + display: flex; + justify-content: space-around; +} + +.attribute-annotation-sidebar-attr-editor { + display: flex; + align-items: center; + justify-content: space-around; +} diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index 1b5d750820d..efe213ebe97 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -4,18 +4,19 @@ import React from 'react'; import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; +import Slider, { SliderValue } from 'antd/lib/slider'; +import Layout from 'antd/lib/layout'; +import Icon from 'antd/lib/icon'; +import Tooltip from 'antd/lib/tooltip'; -import { - Layout, - Slider, - Icon, - Tooltip, -} from 'antd'; - -import { SliderValue } from 'antd/lib//slider'; -import { ColorBy, GridColor, ObjectType } from 'reducers/interfaces'; import { Canvas } from 'cvat-canvas'; import getCore from 'cvat-core'; +import { + ColorBy, + GridColor, + ObjectType, + Workspace, +} from 'reducers/interfaces'; const cvat = getCore(); @@ -26,6 +27,7 @@ interface Props { canvasInstance: Canvas; jobInstance: any; activatedStateID: number | null; + activatedAttributeID: number | null; selectedStatesID: number[]; annotations: any[]; frameData: any; @@ -48,6 +50,8 @@ interface Props { contrastLevel: number; saturationLevel: number; resetZoom: boolean; + aamZoomMargin: number; + workspace: Workspace; onSetupCanvas: () => void; onDragCanvas: (enabled: boolean) => void; onZoomCanvas: (enabled: boolean) => void; @@ -57,7 +61,7 @@ interface Props { onEditShape: (enabled: boolean) => void; onShapeDrawn: () => void; onResetCanvas: () => void; - onUpdateAnnotations(sessionInstance: any, frame: number, states: any[]): void; + onUpdateAnnotations(states: any[]): void; onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void; onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void; onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void; @@ -226,8 +230,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { private onShapeEdited(event: any): void { const { - jobInstance, - frame, onEditShape, onUpdateAnnotations, } = this.props; @@ -239,7 +241,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { points, } = event.detail; state.points = points; - onUpdateAnnotations(jobInstance, frame, [state]); + onUpdateAnnotations([state]); } private onObjectsMerged(event: any): void { @@ -292,12 +294,18 @@ export default class CanvasWrapperComponent extends React.PureComponent { private activateOnCanvas(): void { const { activatedStateID, + activatedAttributeID, canvasInstance, selectedOpacity, + aamZoomMargin, + workspace, } = this.props; if (activatedStateID !== null) { - canvasInstance.activate(activatedStateID); + if (workspace === Workspace.ATTRIBUTE_ANNOTATION) { + canvasInstance.focus(activatedStateID, aamZoomMargin); + } + canvasInstance.activate(activatedStateID, activatedAttributeID); const el = window.document.getElementById(`cvat_canvas_shape_${activatedStateID}`); if (el) { (el as any as SVGElement).setAttribute('fill-opacity', `${selectedOpacity / 100}`); @@ -397,11 +405,14 @@ export default class CanvasWrapperComponent extends React.PureComponent { // Events canvasInstance.html().addEventListener('mousedown', (e: MouseEvent): void => { const { + workspace, activatedStateID, } = this.props; - if ((e.target as HTMLElement).tagName === 'svg' && activatedStateID !== null) { - onActivateObject(null); + if ((e.target as HTMLElement).tagName === 'svg') { + if (activatedStateID !== null && workspace !== Workspace.ATTRIBUTE_ANNOTATION) { + onActivateObject(null); + } } }); @@ -424,16 +435,17 @@ export default class CanvasWrapperComponent extends React.PureComponent { onEditShape(true); }); + // it is important to be called before the following listener + canvasInstance.html().addEventListener('canvas.setup', () => { + canvasInstance.fit(); + }, { once: true }); + canvasInstance.html().addEventListener('canvas.setup', (): void => { onSetupCanvas(); this.updateShapesView(); this.activateOnCanvas(); }); - canvasInstance.html().addEventListener('canvas.setup', () => { - canvasInstance.fit(); - }, { once: true }); - canvasInstance.html().addEventListener('canvas.canceled', () => { onResetCanvas(); }); @@ -476,7 +488,11 @@ export default class CanvasWrapperComponent extends React.PureComponent { }); canvasInstance.html().addEventListener('canvas.moved', async (event: any): Promise => { - const { activatedStateID } = this.props; + const { activatedStateID, workspace } = this.props; + if (workspace === Workspace.ATTRIBUTE_ANNOTATION) { + return; + } + const result = await jobInstance.annotations.select( event.detail.states, event.detail.x, diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx index 83e2c70d664..e9bfd6c4e00 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx @@ -17,7 +17,7 @@ import CanvasContextMenuContainer from 'containers/annotation-page/standard-work export default function StandardWorkspaceComponent(): JSX.Element { return ( - + diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss index ed07a9056f3..e731a27f46e 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -4,6 +4,10 @@ @import 'base.scss'; +.cvat-standard-workspace.ant-layout { + height: 100% +} + .cvat-canvas-container { background-color: $background-color-1; } @@ -115,62 +119,3 @@ margin: 0px 5px; } } - -.cvat-canvas-context-menu { - opacity: 0.6; - position: fixed; - width: 300px; - z-index: 10; - max-height: 50%; - overflow-y: auto; - - &:hover { - opacity: 1; - } -} - -.cvat-canvas-z-axis-wrapper { - position: absolute; - background: $background-color-2; - bottom: 10px; - right: 10px; - height: 150px; - z-index: 100; - border-radius: 6px; - opacity: 0.5; - border: 1px solid $border-color-3; - display: flex; - flex-direction: column; - justify-content: space-between; - padding: 3px; - - &:hover { - opacity: 1; - } - - > .ant-slider { - height: 75%; - margin: 5px 3px; - - > .ant-slider-rail { - background-color: #979797; - } - - > .ant-slider-handle { - transform: none !important; - } - } - - > i { - opacity: 0.7; - color: $objects-bar-icons-color; - - &:hover { - opacity: 1; - } - - &:active { - opacity: 0.7; - } - } -} diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss index 366253b4e28..1e58c2e6fcb 100644 --- a/cvat-ui/src/components/annotation-page/styles.scss +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -213,3 +213,64 @@ width: 15em; } } + + +// TODO: Move canvas from standard workspace and create its own .scss +.cvat-canvas-context-menu { + opacity: 0.6; + position: fixed; + width: 300px; + z-index: 10; + max-height: 50%; + overflow-y: auto; + + &:hover { + opacity: 1; + } +} + +.cvat-canvas-z-axis-wrapper { + position: absolute; + background: $background-color-2; + bottom: 10px; + right: 10px; + height: 150px; + z-index: 100; + border-radius: 6px; + opacity: 0.5; + border: 1px solid $border-color-3; + display: flex; + flex-direction: column; + justify-content: space-between; + padding: 3px; + + &:hover { + opacity: 1; + } + + > .ant-slider { + height: 75%; + margin: 5px 3px; + + > .ant-slider-rail { + background-color: #979797; + } + + > .ant-slider-handle { + transform: none !important; + } + } + + > i { + opacity: 0.7; + color: $objects-bar-icons-color; + + &:hover { + opacity: 1; + } + + &:active { + opacity: 0.7; + } + } +} diff --git a/cvat-ui/src/components/annotation-page/top-bar/right-group.tsx b/cvat-ui/src/components/annotation-page/top-bar/right-group.tsx index 7e39eff7be1..071279a2942 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/right-group.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/right-group.tsx @@ -11,17 +11,17 @@ import { Button, } from 'antd'; -import { - InfoIcon, - FullscreenIcon, -} from '../../../icons'; +import { Workspace } from 'reducers/interfaces'; +import { InfoIcon, FullscreenIcon } from '../../../icons'; interface Props { + workspace: Workspace; showStatistics(): void; + changeWorkspace(workspace: Workspace): void; } function RightGroup(props: Props): JSX.Element { - const { showStatistics } = props; + const { showStatistics, changeWorkspace, workspace } = props; return ( @@ -46,9 +46,23 @@ function RightGroup(props: Props): JSX.Element { Info
- + + {Workspace.STANDARD} + + + {Workspace.ATTRIBUTE_ANNOTATION} +
diff --git a/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx index a358720303f..370702fec02 100644 --- a/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/components/annotation-page/top-bar/top-bar.tsx @@ -7,12 +7,12 @@ import React from 'react'; import { Row, Col, - Layout, InputNumber, } from 'antd'; import { SliderValue } from 'antd/lib/slider'; +import { Workspace } from 'reducers/interfaces'; import LeftGroup from './left-group'; import RightGroup from './right-group'; import PlayerNavigation from './player-navigation'; @@ -28,6 +28,8 @@ interface Props { stopFrame: number; undoAction?: string; redoAction?: string; + workspace: Workspace; + changeWorkspace(workspace: Workspace): void; showStatistics(): void; onSwitchPlay(): void; onSaveAnnotation(): void; @@ -55,7 +57,9 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { inputFrameRef, startFrame, stopFrame, + workspace, showStatistics, + changeWorkspace, onSwitchPlay, onSaveAnnotation, onPrevFrame, @@ -72,42 +76,44 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element { } = props; return ( - - - - - - - - - - - - + + + + + + + + + + ); } diff --git a/cvat-ui/src/components/settings-page/workspace-settings.tsx b/cvat-ui/src/components/settings-page/workspace-settings.tsx index 6d265e4ab41..a7a5fd2cbb4 100644 --- a/cvat-ui/src/components/settings-page/workspace-settings.tsx +++ b/cvat-ui/src/components/settings-page/workspace-settings.tsx @@ -93,7 +93,7 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { max={1000} value={aamZoomMargin} onChange={(value: number | undefined): void => { - if (value) { + if (typeof (value) === 'number') { onChangeAAMZoomMargin(value); } }} diff --git a/cvat-ui/src/containers/annotation-page/annotation-page.tsx b/cvat-ui/src/containers/annotation-page/annotation-page.tsx index c66a93049a2..264d1d9450f 100644 --- a/cvat-ui/src/containers/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/containers/annotation-page/annotation-page.tsx @@ -9,9 +9,7 @@ import { RouteComponentProps } from 'react-router'; import AnnotationPageComponent from 'components/annotation-page/annotation-page'; import { getJobAsync } from 'actions/annotation-actions'; -import { - CombinedState, -} from 'reducers/interfaces'; +import { CombinedState, Workspace } from 'reducers/interfaces'; type OwnProps = RouteComponentProps<{ tid: string; @@ -21,6 +19,7 @@ type OwnProps = RouteComponentProps<{ interface StateToProps { job: any | null | undefined; fetching: boolean; + workspace: Workspace; } interface DispatchToProps { @@ -36,12 +35,14 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { instance: job, fetching, }, + workspace, }, } = state; return { job: !job || jobID === job.id ? job : null, fetching, + workspace, }; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx index 0625bece913..e9265bfae3c 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -39,6 +39,7 @@ import { GridColor, ObjectType, CombinedState, + Workspace, } from 'reducers/interfaces'; import { Canvas } from 'cvat-canvas'; @@ -48,6 +49,7 @@ interface StateToProps { canvasInstance: Canvas; jobInstance: any; activatedStateID: number | null; + activatedAttributeID: number | null; selectedStatesID: number[]; annotations: any[]; frameData: any; @@ -67,6 +69,8 @@ interface StateToProps { contrastLevel: number; saturationLevel: number; resetZoom: boolean; + aamZoomMargin: number; + workspace: Workspace; minZLayer: number; maxZLayer: number; curZLayer: number; @@ -82,7 +86,7 @@ interface DispatchToProps { onGroupObjects: (enabled: boolean) => void; onSplitTrack: (enabled: boolean) => void; onEditShape: (enabled: boolean) => void; - onUpdateAnnotations(sessionInstance: any, frame: number, states: any[]): void; + onUpdateAnnotations(states: any[]): void; onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void; onMergeAnnotations(sessionInstance: any, frame: number, states: any[]): void; onGroupAnnotations(sessionInstance: any, frame: number, states: any[]): void; @@ -123,6 +127,7 @@ function mapStateToProps(state: CombinedState): StateToProps { annotations: { states: annotations, activatedStateID, + activatedAttributeID, selectedStatesID, zLayer: { cur: curZLayer, @@ -131,6 +136,7 @@ function mapStateToProps(state: CombinedState): StateToProps { }, }, sidebarCollapsed, + workspace, }, settings: { player: { @@ -143,6 +149,9 @@ function mapStateToProps(state: CombinedState): StateToProps { saturationLevel, resetZoom, }, + workspace: { + aamZoomMargin, + }, shapes: { opacity, colorBy, @@ -160,6 +169,7 @@ function mapStateToProps(state: CombinedState): StateToProps { frameAngle: frameAngles[frame - jobInstance.startFrame], frame, activatedStateID, + activatedAttributeID, selectedStatesID, annotations, opacity, @@ -176,9 +186,11 @@ function mapStateToProps(state: CombinedState): StateToProps { contrastLevel, saturationLevel, resetZoom, + aamZoomMargin, curZLayer, minZLayer, maxZLayer, + workspace, }; } @@ -211,8 +223,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onEditShape(enabled: boolean): void { dispatch(editShape(enabled)); }, - onUpdateAnnotations(sessionInstance: any, frame: number, states: any[]): void { - dispatch(updateAnnotationsAsync(sessionInstance, frame, states)); + onUpdateAnnotations(states: any[]): void { + dispatch(updateAnnotationsAsync(states)); }, onCreateAnnotations(sessionInstance: any, frame: number, states: any[]): void { dispatch(createAnnotationsAsync(sessionInstance, frame, states)); @@ -231,7 +243,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { dispatch(updateCanvasContextMenu(false, 0, 0)); } - dispatch(activateObject(activatedStateID)); + dispatch(activateObject(activatedStateID, null)); }, onSelectObjects(selectedStatesID: number[]): void { dispatch(selectObjects(selectedStatesID)); diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx index 2e70ed1bb03..e795eb8d638 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/label-item.tsx @@ -29,7 +29,7 @@ interface StateToProps { } interface DispatchToProps { - updateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void; + updateAnnotations(states: any[]): void; changeLabelColor(sessionInstance: any, frameNumber: number, label: any, color: string): void; } @@ -68,8 +68,8 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - updateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void { - dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, states)); + updateAnnotations(states: any[]): void { + dispatch(updateAnnotationsAsync(states)); }, changeLabelColor( sessionInstance: any, @@ -162,8 +162,6 @@ class LabelItemContainer extends React.PureComponent { private switchHidden(value: boolean): void { const { updateAnnotations, - jobInstance, - frameNumber, } = this.props; const { ownObjectStates } = this.state; @@ -171,14 +169,12 @@ class LabelItemContainer extends React.PureComponent { state.hidden = value; } - updateAnnotations(jobInstance, frameNumber, ownObjectStates); + updateAnnotations(ownObjectStates); } private switchLock(value: boolean): void { const { updateAnnotations, - jobInstance, - frameNumber, } = this.props; const { ownObjectStates } = this.state; @@ -186,7 +182,7 @@ class LabelItemContainer extends React.PureComponent { state.lock = value; } - updateAnnotations(jobInstance, frameNumber, ownObjectStates); + updateAnnotations(ownObjectStates); } public render(): JSX.Element { diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index 9c65ee79343..419e437c15a 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx @@ -47,14 +47,14 @@ interface StateToProps { interface DispatchToProps { changeFrame(frame: number): void; - updateState(sessionInstance: any, frameNumber: number, objectState: any): void; + updateState(objectState: any): void; collapseOrExpand(objectStates: any[], collapsed: boolean): void; activateObject: (activatedStateID: number | null) => void; removeObject: (sessionInstance: any, objectState: any) => void; copyShape: (objectState: any) => void; propagateObject: (objectState: any) => void; changeLabelColor(sessionInstance: any, frameNumber: number, label: any, color: string): void; - changeGroupColor(sessionInstance: any, frameNumber: number, group: number, color: string): void; + changeGroupColor(group: number, color: string): void; } function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { @@ -121,14 +121,14 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { changeFrame(frame: number): void { dispatch(changeFrameAsync(frame)); }, - updateState(sessionInstance: any, frameNumber: number, state: any): void { - dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, [state])); + updateState(state: any): void { + dispatch(updateAnnotationsAsync([state])); }, collapseOrExpand(objectStates: any[], collapsed: boolean): void { dispatch(collapseObjectItems(objectStates, collapsed)); }, activateObject(activatedStateID: number | null): void { - dispatch(activateObjectAction(activatedStateID)); + dispatch(activateObjectAction(activatedStateID, null)); }, removeObject(sessionInstance: any, objectState: any): void { dispatch(removeObjectAsync(sessionInstance, objectState, true)); @@ -148,13 +148,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { ): void { dispatch(changeLabelColorAsync(sessionInstance, frameNumber, label, color)); }, - changeGroupColor( - sessionInstance: any, - frameNumber: number, - group: number, - color: string, - ): void { - dispatch(changeGroupColorAsync(sessionInstance, frameNumber, group, color)); + changeGroupColor(group: number, color: string): void { + dispatch(changeGroupColorAsync(group, color)); }, }; } @@ -386,7 +381,7 @@ class ObjectItemContainer extends React.PureComponent { objectState.color = color; this.commit(); } else if (colorBy === ColorBy.GROUP) { - changeGroupColor(jobInstance, frameNumber, objectState.group.id, color); + changeGroupColor(objectState.group.id, color); } else if (colorBy === ColorBy.LABEL) { changeLabelColor(jobInstance, frameNumber, objectState.label, color); } @@ -415,11 +410,9 @@ class ObjectItemContainer extends React.PureComponent { const { objectState, updateState, - jobInstance, - frameNumber, } = this.props; - updateState(jobInstance, frameNumber, objectState); + updateState(objectState); } public render(): JSX.Element { diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx index 8bc18e0f6d6..ea7670d0c55 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx @@ -42,7 +42,7 @@ interface StateToProps { } interface DispatchToProps { - updateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void; + updateAnnotations(states: any[]): void; changeAnnotationsFilters(sessionInstance: any, filters: string[]): void; collapseStates(states: any[], value: boolean): void; removeObject: (sessionInstance: any, objectState: any, force: boolean) => void; @@ -109,8 +109,8 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - updateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void { - dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, states)); + updateAnnotations(states: any[]): void { + dispatch(updateAnnotationsAsync(states)); }, collapseStates(states: any[], collapsed: boolean): void { dispatch(collapseObjectItems(states, collapsed)); @@ -225,28 +225,24 @@ class ObjectsListContainer extends React.PureComponent { const { objectStates, updateAnnotations, - jobInstance, - frameNumber, } = this.props; for (const objectState of objectStates) { objectState.lock = locked; } - updateAnnotations(jobInstance, frameNumber, objectStates); + updateAnnotations(objectStates); } private hideAllStates(hidden: boolean): void { const { objectStates, updateAnnotations, - jobInstance, - frameNumber, } = this.props; for (const objectState of objectStates) { objectState.hidden = hidden; } - updateAnnotations(jobInstance, frameNumber, objectStates); + updateAnnotations(objectStates); } private collapseAllStates(collapsed: boolean): void { @@ -265,7 +261,6 @@ class ObjectsListContainer extends React.PureComponent { statesLocked, activatedStateID, objectStates, - frameNumber, jobInstance, updateAnnotations, removeObject, @@ -397,7 +392,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state) { state.lock = !state.lock; - updateAnnotations(jobInstance, frameNumber, [state]); + updateAnnotations([state]); } }, SWITCH_ALL_HIDDEN: (event: KeyboardEvent | undefined) => { @@ -409,7 +404,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state) { state.hidden = !state.hidden; - updateAnnotations(jobInstance, frameNumber, [state]); + updateAnnotations([state]); } }, SWITCH_OCCLUDED: (event: KeyboardEvent | undefined) => { @@ -417,7 +412,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state && state.objectType !== ObjectType.TAG) { state.occluded = !state.occluded; - updateAnnotations(jobInstance, frameNumber, [state]); + updateAnnotations([state]); } }, SWITCH_KEYFRAME: (event: KeyboardEvent | undefined) => { @@ -425,7 +420,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state && state.objectType === ObjectType.TRACK) { state.keyframe = !state.keyframe; - updateAnnotations(jobInstance, frameNumber, [state]); + updateAnnotations([state]); } }, SWITCH_OUTSIDE: (event: KeyboardEvent | undefined) => { @@ -433,7 +428,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state && state.objectType === ObjectType.TRACK) { state.outside = !state.outside; - updateAnnotations(jobInstance, frameNumber, [state]); + updateAnnotations([state]); } }, DELETE_OBJECT: (event: KeyboardEvent | undefined) => { @@ -448,7 +443,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state && state.objectType !== ObjectType.TAG) { state.zOrder = minZLayer - 1; - updateAnnotations(jobInstance, frameNumber, [state]); + updateAnnotations([state]); } }, TO_FOREGROUND: (event: KeyboardEvent | undefined) => { @@ -456,7 +451,7 @@ class ObjectsListContainer extends React.PureComponent { const state = activatedStated(); if (state && state.objectType !== ObjectType.TAG) { state.zOrder = maxZLayer + 1; - updateAnnotations(jobInstance, frameNumber, [state]); + updateAnnotations([state]); } }, COPY_SHAPE: (event: KeyboardEvent | undefined) => { diff --git a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx index 8bcc1f59f87..96d76ab58ce 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx @@ -22,10 +22,11 @@ import { undoActionAsync, redoActionAsync, searchAnnotationsAsync, + changeWorkspace as changeWorkspaceAction, } from 'actions/annotation-actions'; import AnnotationTopBarComponent from 'components/annotation-page/top-bar/top-bar'; -import { CombinedState, FrameSpeed } from 'reducers/interfaces'; +import { CombinedState, FrameSpeed, Workspace } from 'reducers/interfaces'; interface StateToProps { jobInstance: any; @@ -41,6 +42,7 @@ interface StateToProps { redoAction?: string; autoSave: boolean; autoSaveInterval: number; + workspace: Workspace; } interface DispatchToProps { @@ -51,6 +53,7 @@ interface DispatchToProps { undo(sessionInstance: any, frameNumber: any): void; redo(sessionInstance: any, frameNumber: any): void; searchAnnotations(sessionInstance: any, frameFrom: any, frameTo: any): void; + changeWorkspace(workspace: Workspace): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -76,6 +79,7 @@ function mapStateToProps(state: CombinedState): StateToProps { canvas: { ready: canvasIsReady, }, + workspace, }, settings: { player: { @@ -103,6 +107,7 @@ function mapStateToProps(state: CombinedState): StateToProps { redoAction: history.redo[history.redo.length - 1], autoSave, autoSaveInterval, + workspace, }; } @@ -130,6 +135,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { searchAnnotations(sessionInstance: any, frameFrom: any, frameTo: any): void { dispatch(searchAnnotationsAsync(sessionInstance, frameFrom, frameTo)); }, + changeWorkspace(workspace: Workspace): void { + dispatch(changeWorkspaceAction(workspace)); + }, }; } @@ -442,8 +450,10 @@ class AnnotationTopBarContainer extends React.PureComponent { frameNumber, undoAction, redoAction, - searchAnnotations, + workspace, canvasIsReady, + searchAnnotations, + changeWorkspace, } = this.props; const preventDefault = (event: KeyboardEvent | undefined): void => { @@ -602,6 +612,8 @@ class AnnotationTopBarContainer extends React.PureComponent { onSliderChange={this.onChangePlayerSliderValue} onInputChange={this.onChangePlayerInputValue} onURLIconClick={this.onURLIconClick} + changeWorkspace={changeWorkspace} + workspace={workspace} playing={playing} saving={saving} savingStatuses={savingStatuses} diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index d5de5f6009c..c372b878ef6 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -12,6 +12,7 @@ import { ActiveControl, ShapeType, ObjectType, + Workspace, } from './interfaces'; const defaultState: AnnotationState = { @@ -54,6 +55,7 @@ const defaultState: AnnotationState = { annotations: { selectedStatesID: [], activatedStateID: null, + activatedAttributeID: null, saving: { uploading: false, statuses: [], @@ -88,6 +90,7 @@ const defaultState: AnnotationState = { sidebarCollapsed: false, appearanceCollapsed: false, tabContentHeight: 0, + workspace: Workspace.ATTRIBUTE_ANNOTATION, }; export default (state = defaultState, action: AnyAction): AnnotationState => { @@ -646,7 +649,11 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }; } case AnnotationActionTypes.ACTIVATE_OBJECT: { - const { activatedStateID } = action.payload; + const { + activatedStateID, + activatedAttributeID, + } = action.payload; + const { canvas: { activeControl, @@ -663,6 +670,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { annotations: { ...state.annotations, activatedStateID, + activatedAttributeID, }, }; } @@ -1041,6 +1049,44 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } + case AnnotationActionTypes.ACTIVATE_NEXT: { + const { step } = action.payload; + const { states } = state.annotations; + const currentActivated = state.annotations.activatedStateID; + + let newActivated = null; + if (currentActivated === null) { + if (states.length) { + newActivated = state.annotations.states[0].clientID; + } + } else { + const index = states + .map((objectState: any): number => objectState.clientID) + .indexOf(currentActivated); + let newIndex = index + step; + if (newIndex < 0) { + newIndex = states[states.length - 1].clientID; + } else if (newIndex >= states.length) { + newIndex = 0; + } + newActivated = states[newIndex].clientID; + } + + return { + ...state, + annotations: { + ...state.annotations, + activatedStateID: newActivated, + }, + }; + } + case AnnotationActionTypes.CHANGE_WORKSPACE: { + const { workspace } = action.payload; + return { + ...state, + workspace, + }; + } case AnnotationActionTypes.RESET_CANVAS: { return { ...state, diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 74e36d86c56..e0595b90b9f 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -335,6 +335,7 @@ export interface AnnotationState { annotations: { selectedStatesID: number[]; activatedStateID: number | null; + activatedAttributeID: number | null; collapsed: Record; states: any[]; filters: string[]; @@ -367,6 +368,12 @@ export interface AnnotationState { sidebarCollapsed: boolean; appearanceCollapsed: boolean; tabContentHeight: number; + workspace: Workspace; +} + +export enum Workspace { + STANDARD = 'Standard', + ATTRIBUTE_ANNOTATION = 'Attribute annotation', } export enum GridColor { From 9b144d14d6d26e9d3d7e06df3becb8c9f2199ef6 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 12 Mar 2020 14:59:56 +0300 Subject: [PATCH 02/17] Fixed mount/unmount for canvas wrapper --- .../standard-workspace/canvas-wrapper.tsx | 354 ++++++++++-------- 1 file changed, 197 insertions(+), 157 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index efe213ebe97..9608a531ee7 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -192,10 +192,34 @@ export default class CanvasWrapperComponent extends React.PureComponent { } public componentWillUnmount(): void { + const { canvasInstance } = this.props; + + canvasInstance.html().removeEventListener('mousedown', this.onCanvasMouseDown); + canvasInstance.html().removeEventListener('click', this.onCanvasClicked); + canvasInstance.html().removeEventListener('contextmenu', this.onCanvasContextMenu); + canvasInstance.html().removeEventListener('canvas.editstart', this.onCanvasEditStart); + canvasInstance.html().removeEventListener('canvas.edited', this.onCanvasEditDone); + canvasInstance.html().removeEventListener('canvas.dragstart', this.onCanvasDragStart); + canvasInstance.html().removeEventListener('canvas.dragstop', this.onCanvasDragDone); + canvasInstance.html().removeEventListener('canvas.zoomstart', this.onCanvasZoomStart); + canvasInstance.html().removeEventListener('canvas.zoomstop', this.onCanvasZoomDone); + + canvasInstance.html().removeEventListener('canvas.setup', this.onCanvasSetup); + canvasInstance.html().removeEventListener('canvas.canceled', this.onCanvasCancel); + canvasInstance.html().removeEventListener('canvas.find', this.onCanvasFindObject); + canvasInstance.html().removeEventListener('canvas.deactivated', this.onCanvasShapeDeactivated); + canvasInstance.html().removeEventListener('canvas.moved', this.onCanvasCursorMoved); + + canvasInstance.html().removeEventListener('canvas.clicked', this.onCanvasShapeClicked); + canvasInstance.html().removeEventListener('canvas.drawn', this.onCanvasShapeDrawn); + canvasInstance.html().removeEventListener('canvas.merged', this.onCanvasObjectsMerged); + canvasInstance.html().removeEventListener('canvas.groupped', this.onCanvasObjectsGroupped); + canvasInstance.html().addEventListener('canvas.splitted', this.onCanvasTrackSplitted); + window.removeEventListener('resize', this.fitCanvas); } - private onShapeDrawn(event: any): void { + private onCanvasShapeDrawn = (event: any): void => { const { jobInstance, activeLabelID, @@ -226,25 +250,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { state.frame = frame; const objectState = new cvat.classes.ObjectState(state); onCreateAnnotations(jobInstance, frame, [objectState]); - } - - private onShapeEdited(event: any): void { - const { - onEditShape, - onUpdateAnnotations, - } = this.props; - - onEditShape(false); - - const { - state, - points, - } = event.detail; - state.points = points; - onUpdateAnnotations([state]); - } + }; - private onObjectsMerged(event: any): void { + private onCanvasObjectsMerged = (event: any): void => { const { jobInstance, frame, @@ -256,9 +264,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { const { states } = event.detail; onMergeAnnotations(jobInstance, frame, states); - } + }; - private onObjectsGroupped(event: any): void { + private onCanvasObjectsGroupped = (event: any): void => { const { jobInstance, frame, @@ -270,9 +278,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { const { states } = event.detail; onGroupAnnotations(jobInstance, frame, states); - } + }; - private onTrackSplitted(event: any): void { + private onCanvasTrackSplitted = (event: any): void => { const { jobInstance, frame, @@ -284,13 +292,157 @@ export default class CanvasWrapperComponent extends React.PureComponent { const { state } = event.detail; onSplitAnnotations(jobInstance, frame, state); - } + }; private fitCanvas = (): void => { const { canvasInstance } = this.props; canvasInstance.fitCanvas(); }; + private onCanvasMouseDown = (e: MouseEvent): void => { + const { workspace, activatedStateID, onActivateObject } = this.props; + + if ((e.target as HTMLElement).tagName === 'svg') { + if (activatedStateID !== null && workspace !== Workspace.ATTRIBUTE_ANNOTATION) { + onActivateObject(null); + } + } + }; + + private onCanvasClicked = (): void => { + if (document.activeElement) { + (document.activeElement as HTMLElement).blur(); + } + }; + + private onCanvasContextMenu = (e: MouseEvent): void => { + const { activatedStateID, onUpdateContextMenu } = this.props; + onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY); + }; + + private onCanvasShapeClicked = (e: any): void => { + const { clientID } = e.detail.state; + const sidebarItem = window.document + .getElementById(`cvat-objects-sidebar-state-item-${clientID}`); + if (sidebarItem) { + sidebarItem.scrollIntoView(); + } + }; + + private onCanvasShapeDeactivated = (e: any): void => { + const { onActivateObject, activatedStateID } = this.props; + const { state } = e.detail; + + // when we activate element, canvas deactivates the previous + // and triggers this event + // in this case we do not need to update our state + if (state.clientID === activatedStateID) { + onActivateObject(null); + } + }; + + private onCanvasCursorMoved = async (event: any): Promise => { + const { + jobInstance, + activatedStateID, + workspace, + onActivateObject, + } = this.props; + + if (workspace === Workspace.ATTRIBUTE_ANNOTATION) { + return; + } + + const result = await jobInstance.annotations.select( + event.detail.states, + event.detail.x, + event.detail.y, + ); + + if (result && result.state) { + if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') { + if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) { + return; + } + } + + if (activatedStateID !== result.state.clientID) { + onActivateObject(result.state.clientID); + } + } + }; + + private onCanvasEditStart = (): void => { + const { onActivateObject, onEditShape } = this.props; + onActivateObject(null); + onEditShape(true); + }; + + private onCanvasEditDone = (event: any): void => { + const { + onEditShape, + onUpdateAnnotations, + } = this.props; + + onEditShape(false); + + const { + state, + points, + } = event.detail; + state.points = points; + onUpdateAnnotations([state]); + }; + + private onCanvasDragStart = (): void => { + const { onDragCanvas } = this.props; + onDragCanvas(true); + }; + + private onCanvasDragDone = (): void => { + const { onDragCanvas } = this.props; + onDragCanvas(false); + }; + + private onCanvasZoomStart = (): void => { + const { onZoomCanvas } = this.props; + onZoomCanvas(true); + }; + + private onCanvasZoomDone = (): void => { + const { onZoomCanvas } = this.props; + onZoomCanvas(false); + }; + + private onCanvasSetup = (): void => { + const { onSetupCanvas } = this.props; + onSetupCanvas(); + this.updateShapesView(); + this.activateOnCanvas(); + }; + + private onCanvasCancel = (): void => { + const { onResetCanvas } = this.props; + onResetCanvas(); + }; + + private onCanvasFindObject = async (e: any): Promise => { + const { jobInstance, canvasInstance } = this.props; + + const result = await jobInstance.annotations + .select(e.detail.states, e.detail.x, e.detail.y); + + if (result && result.state) { + if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') { + if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) { + return; + } + } + + canvasInstance.select(result.state); + } + }; + private activateOnCanvas(): void { const { activatedStateID, @@ -365,14 +517,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { gridColor, gridOpacity, canvasInstance, - jobInstance, - onSetupCanvas, - onDragCanvas, - onZoomCanvas, - onResetCanvas, - onActivateObject, - onUpdateContextMenu, - onEditShape, brightnessLevel, contrastLevel, saturationLevel, @@ -403,135 +547,31 @@ export default class CanvasWrapperComponent extends React.PureComponent { } // Events - canvasInstance.html().addEventListener('mousedown', (e: MouseEvent): void => { - const { - workspace, - activatedStateID, - } = this.props; - - if ((e.target as HTMLElement).tagName === 'svg') { - if (activatedStateID !== null && workspace !== Workspace.ATTRIBUTE_ANNOTATION) { - onActivateObject(null); - } - } - }); - - canvasInstance.html().addEventListener('click', (): void => { - if (document.activeElement) { - (document.activeElement as HTMLElement).blur(); - } - }); - - canvasInstance.html().addEventListener('contextmenu', (e: MouseEvent): void => { - const { - activatedStateID, - } = this.props; - - onUpdateContextMenu(activatedStateID !== null, e.clientX, e.clientY); - }); - - canvasInstance.html().addEventListener('canvas.editstart', (): void => { - onActivateObject(null); - onEditShape(true); - }); - - // it is important to be called before the following listener canvasInstance.html().addEventListener('canvas.setup', () => { canvasInstance.fit(); }, { once: true }); - canvasInstance.html().addEventListener('canvas.setup', (): void => { - onSetupCanvas(); - this.updateShapesView(); - this.activateOnCanvas(); - }); - - canvasInstance.html().addEventListener('canvas.canceled', () => { - onResetCanvas(); - }); - - canvasInstance.html().addEventListener('canvas.dragstart', () => { - onDragCanvas(true); - }); - - canvasInstance.html().addEventListener('canvas.dragstop', () => { - onDragCanvas(false); - }); - - canvasInstance.html().addEventListener('canvas.zoomstart', () => { - onZoomCanvas(true); - }); - - canvasInstance.html().addEventListener('canvas.zoomstop', () => { - onZoomCanvas(false); - }); - - canvasInstance.html().addEventListener('canvas.clicked', (e: any) => { - const { clientID } = e.detail.state; - const sidebarItem = window.document - .getElementById(`cvat-objects-sidebar-state-item-${clientID}`); - if (sidebarItem) { - sidebarItem.scrollIntoView(); - } - }); - - canvasInstance.html().addEventListener('canvas.deactivated', (e: any): void => { - const { activatedStateID } = this.props; - const { state } = e.detail; - - // when we activate element, canvas deactivates the previous - // and triggers this event - // in this case we do not need to update our state - if (state.clientID === activatedStateID) { - onActivateObject(null); - } - }); - - canvasInstance.html().addEventListener('canvas.moved', async (event: any): Promise => { - const { activatedStateID, workspace } = this.props; - if (workspace === Workspace.ATTRIBUTE_ANNOTATION) { - return; - } - - const result = await jobInstance.annotations.select( - event.detail.states, - event.detail.x, - event.detail.y, - ); - - if (result && result.state) { - if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') { - if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) { - return; - } - } - - if (activatedStateID !== result.state.clientID) { - onActivateObject(result.state.clientID); - } - } - }); - - canvasInstance.html().addEventListener('canvas.find', async (e: any) => { - const result = await jobInstance.annotations - .select(e.detail.states, e.detail.x, e.detail.y); - - if (result && result.state) { - if (result.state.shapeType === 'polyline' || result.state.shapeType === 'points') { - if (result.distance > MAX_DISTANCE_TO_OPEN_SHAPE) { - return; - } - } - - canvasInstance.select(result.state); - } - }); - - canvasInstance.html().addEventListener('canvas.edited', this.onShapeEdited.bind(this)); - canvasInstance.html().addEventListener('canvas.drawn', this.onShapeDrawn.bind(this)); - canvasInstance.html().addEventListener('canvas.merged', this.onObjectsMerged.bind(this)); - canvasInstance.html().addEventListener('canvas.groupped', this.onObjectsGroupped.bind(this)); - canvasInstance.html().addEventListener('canvas.splitted', this.onTrackSplitted.bind(this)); + canvasInstance.html().addEventListener('mousedown', this.onCanvasMouseDown); + canvasInstance.html().addEventListener('click', this.onCanvasClicked); + canvasInstance.html().addEventListener('contextmenu', this.onCanvasContextMenu); + canvasInstance.html().addEventListener('canvas.editstart', this.onCanvasEditStart); + canvasInstance.html().addEventListener('canvas.edited', this.onCanvasEditDone); + canvasInstance.html().addEventListener('canvas.dragstart', this.onCanvasDragStart); + canvasInstance.html().addEventListener('canvas.dragstop', this.onCanvasDragDone); + canvasInstance.html().addEventListener('canvas.zoomstart', this.onCanvasZoomStart); + canvasInstance.html().addEventListener('canvas.zoomstop', this.onCanvasZoomDone); + + canvasInstance.html().addEventListener('canvas.setup', this.onCanvasSetup); + canvasInstance.html().addEventListener('canvas.canceled', this.onCanvasCancel); + canvasInstance.html().addEventListener('canvas.find', this.onCanvasFindObject); + canvasInstance.html().addEventListener('canvas.deactivated', this.onCanvasShapeDeactivated); + canvasInstance.html().addEventListener('canvas.moved', this.onCanvasCursorMoved); + + canvasInstance.html().addEventListener('canvas.clicked', this.onCanvasShapeClicked); + canvasInstance.html().addEventListener('canvas.drawn', this.onCanvasShapeDrawn); + canvasInstance.html().addEventListener('canvas.merged', this.onCanvasObjectsMerged); + canvasInstance.html().addEventListener('canvas.groupped', this.onCanvasObjectsGroupped); + canvasInstance.html().addEventListener('canvas.splitted', this.onCanvasTrackSplitted); } public render(): JSX.Element { From 5fe65aa3f042c4d0e2c4e576a1fefdafd5ec02aa Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 12 Mar 2020 16:37:07 +0300 Subject: [PATCH 03/17] Refactoring, added filters --- cvat-ui/src/actions/annotation-actions.ts | 11 +++- .../attribute-annotation-sidebar.tsx | 7 +++ .../attribute-editor.tsx | 59 +++++++++++-------- .../attribute-annotation-workspace.tsx | 4 +- .../styles.scss | 1 + .../objects-side-bar/objects-list-header.tsx | 38 ++---------- .../objects-side-bar/objects-list.tsx | 10 ---- .../objects-side-bar/styles.scss | 10 ---- .../components/annotation-page/styles.scss | 11 ++++ .../objects-side-bar/objects-list.tsx | 26 -------- cvat-ui/src/reducers/annotation-reducer.ts | 2 +- 11 files changed, 71 insertions(+), 108 deletions(-) diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 1689bcb2dac..0ac2af63c96 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -193,12 +193,17 @@ export function switchZLayer(cur: number): AnyAction { }; } -export function fetchAnnotationsAsync(sessionInstance: any): +export function fetchAnnotationsAsync(): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { try { - const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters(); - const states = await sessionInstance.annotations + const { + filters, + frame, + showAllInterpolationTracks, + jobInstance, + } = receiveAnnotationsParameters(); + const states = await jobInstance.annotations .get(frame, showAllInterpolationTracks, filters); const [minZ, maxZ] = computeZRange(states); diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx index a0c745f5110..ea00650c1fa 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -8,6 +8,7 @@ import { connect } from 'react-redux'; 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 { @@ -15,6 +16,7 @@ import { updateAnnotationsAsync, } from 'actions/annotation-actions'; import { CombinedState } from 'reducers/interfaces'; +import AnnotationsFiltersInput from 'components/annotation-page/annotations-filters-input'; import ObjectSwitcher from './object-switcher'; import AttributeSwitcher from './attribute-switcher'; import ObjectBasicsEditor from './object-basics-edtior'; @@ -214,6 +216,11 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. return ( + + + + + ( - <> - Number: -
- } - autoFocus - min={+values[0]} - max={+values[1]} - step={+values[2]} - value={+currentValue} - onChange={(value: number | undefined) => { - setTimeout(() => { - if (typeof (value) !== 'undefined' && !lockedChange) { - onChange(`${value}`); - } - }); - }} - onKeyDown={handleKeydown} - /> -
- - ); + const renderNumber = (): JSX.Element => { + const numberProps = { + min: +values[0], + max: +values[1], + step: +values[2], + value: +currentValue, + autoFocus: true, + ref: ref as React.RefObject, + }; + + return ( + <> + Number: +
+ { + setTimeout(() => { + if (typeof (value) !== 'undefined') { + const isValid = typeof (+value) === 'number' + && value >= numberProps.min + && value <= numberProps.max + && !(value % numberProps.step); + + if (isValid && !lockedChange) { + onChange(`${+value}`); + } + } + }); + }} + onKeyDown={handleKeydown} + /> +
+ + ); + }; const renderText = (): JSX.Element => ( <> diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx index 8c89c4d9ba6..78b2dc5b426 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx @@ -7,13 +7,13 @@ import React from 'react'; import Layout from 'antd/lib/layout'; import CanvasWrapperContainer from 'containers/annotation-page/standard-workspace/canvas-wrapper'; -import ObjectSidebar from './attribute-annotation-sidebar/attribute-annotation-sidebar'; +import AttributeAnnotationSidebar from './attribute-annotation-sidebar/attribute-annotation-sidebar'; export default function StandardWorkspaceComponent(): JSX.Element { return ( - + ); } diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss index f62dc6c5da5..1f8de8412a3 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/styles.scss @@ -18,6 +18,7 @@ align-items: center; justify-content: space-between; font-size: 18px; + margin-top: 10px; > span { max-width: 60%; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx index d7406ccd8e1..56b29a26f27 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list-header.tsx @@ -3,20 +3,14 @@ // SPDX-License-Identifier: MIT import React from 'react'; - -import { - Row, - Col, - Icon, - Select, -} from 'antd'; - +import { Row, Col } from 'antd/lib/grid'; +import Icon from 'antd/lib/icon'; +import Select from 'antd/lib/select'; import Text from 'antd/lib/typography/Text'; -import { SelectValue } from 'antd/lib/select'; +import AnnotationsFiltersInput from 'components/annotation-page/annotations-filters-input'; import { StatesOrdering } from 'reducers/interfaces'; - interface StatesOrderingSelectorComponentProps { statesOrdering: StatesOrdering; changeStatesOrdering(value: StatesOrdering): void; @@ -62,10 +56,7 @@ interface Props { statesLocked: boolean; statesCollapsed: boolean; statesOrdering: StatesOrdering; - annotationsFilters: string[]; - annotationsFiltersHistory: string[]; changeStatesOrdering(value: StatesOrdering): void; - changeAnnotationsFilters(value: SelectValue): void; lockAllStates(): void; unlockAllStates(): void; collapseAllStates(): void; @@ -76,8 +67,6 @@ interface Props { function ObjectListHeader(props: Props): JSX.Element { const { - annotationsFilters, - annotationsFiltersHistory, statesHidden, statesLocked, statesCollapsed, @@ -89,30 +78,13 @@ function ObjectListHeader(props: Props): JSX.Element { expandAllStates, hideAllStates, showAllStates, - changeAnnotationsFilters, } = props; return (
- + diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx index e3af2e3cbd8..5bf4dc0b251 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx @@ -4,7 +4,6 @@ import React from 'react'; -import { SelectValue } from 'antd/lib/select'; import { StatesOrdering } from 'reducers/interfaces'; import ObjectItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-item'; import ObjectListHeader from './objects-list-header'; @@ -17,10 +16,7 @@ interface Props { statesCollapsed: boolean; statesOrdering: StatesOrdering; sortedStatesID: number[]; - annotationsFilters: string[]; - annotationsFiltersHistory: string[]; changeStatesOrdering(value: StatesOrdering): void; - changeAnnotationsFilters(value: SelectValue): void; lockAllStates(): void; unlockAllStates(): void; collapseAllStates(): void; @@ -37,10 +33,7 @@ function ObjectListComponent(props: Props): JSX.Element { statesCollapsed, statesOrdering, sortedStatesID, - annotationsFilters, - annotationsFiltersHistory, changeStatesOrdering, - changeAnnotationsFilters, lockAllStates, unlockAllStates, collapseAllStates, @@ -56,16 +49,13 @@ function ObjectListComponent(props: Props): JSX.Element { statesLocked={statesLocked} statesCollapsed={statesCollapsed} statesOrdering={statesOrdering} - annotationsFilters={annotationsFilters} changeStatesOrdering={changeStatesOrdering} - changeAnnotationsFilters={changeAnnotationsFilters} lockAllStates={lockAllStates} unlockAllStates={unlockAllStates} collapseAllStates={collapseAllStates} expandAllStates={expandAllStates} hideAllStates={hideAllStates} showAllStates={showAllStates} - annotationsFiltersHistory={annotationsFiltersHistory} />
{ sortedStatesID.map((id: number): JSX.Element => ( diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss index 81e6ba9c284..07f3c0f6eaf 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/styles.scss @@ -69,16 +69,6 @@ > div:nth-child(1) > div:nth-child(1) { height: 32px; - > .ant-select > div { - height: 32px; - > div { - height: 32px; - - ul { - display: flex; - } - } - } } > div:nth-child(2) { diff --git a/cvat-ui/src/components/annotation-page/styles.scss b/cvat-ui/src/components/annotation-page/styles.scss index 1e58c2e6fcb..ac89b303283 100644 --- a/cvat-ui/src/components/annotation-page/styles.scss +++ b/cvat-ui/src/components/annotation-page/styles.scss @@ -274,3 +274,14 @@ } } } + +.cvat-annotations-filters-input.ant-select > div { + height: 32px; + > div { + height: 32px; + + ul { + display: flex; + } + } +} diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx index ea7670d0c55..444c8e263d8 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-list.tsx @@ -6,15 +6,11 @@ import React from 'react'; import { connect } from 'react-redux'; import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; -import { SelectValue } from 'antd/lib/select'; - import ObjectsListComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-list'; import { updateAnnotationsAsync, - fetchAnnotationsAsync, removeObjectAsync, changeFrameAsync, - changeAnnotationsFilters as changeAnnotationsFiltersAction, collapseObjectItems, copyShape as copyShapeAction, propagateObject as propagateObjectAction, @@ -43,7 +39,6 @@ interface StateToProps { interface DispatchToProps { updateAnnotations(states: any[]): void; - changeAnnotationsFilters(sessionInstance: any, filters: string[]): void; collapseStates(states: any[], value: boolean): void; removeObject: (sessionInstance: any, objectState: any, force: boolean) => void; copyShape: (objectState: any) => void; @@ -115,13 +110,6 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { collapseStates(states: any[], collapsed: boolean): void { dispatch(collapseObjectItems(states, collapsed)); }, - changeAnnotationsFilters( - sessionInstance: any, - filters: string[], - ): void { - dispatch(changeAnnotationsFiltersAction(filters)); - dispatch(fetchAnnotationsAsync(sessionInstance)); - }, removeObject(sessionInstance: any, objectState: any, force: boolean): void { dispatch(removeObjectAsync(sessionInstance, objectState, force)); }, @@ -188,15 +176,6 @@ class ObjectsListContainer extends React.PureComponent { }); }; - private onChangeAnnotationsFilters = (value: SelectValue): void => { - const { - jobInstance, - changeAnnotationsFilters, - } = this.props; - const filters = value as string[]; - changeAnnotationsFilters(jobInstance, filters); - }; - private onLockAllStates = (): void => { this.lockAllStates(true); }; @@ -256,7 +235,6 @@ class ObjectsListContainer extends React.PureComponent { public render(): JSX.Element { const { - annotationsFilters, statesHidden, statesLocked, activatedStateID, @@ -269,7 +247,6 @@ class ObjectsListContainer extends React.PureComponent { changeFrame, maxZLayer, minZLayer, - annotationsFiltersHistory, } = this.props; const { sortedStatesID, @@ -499,10 +476,7 @@ class ObjectsListContainer extends React.PureComponent { {...this.props} statesOrdering={statesOrdering} sortedStatesID={sortedStatesID} - annotationsFilters={annotationsFilters} changeStatesOrdering={this.onChangeStatesOrdering} - changeAnnotationsFilters={this.onChangeAnnotationsFilters} - annotationsFiltersHistory={annotationsFiltersHistory} lockAllStates={this.onLockAllStates} unlockAllStates={this.onUnlockAllStates} collapseAllStates={this.onCollapseAllStates} diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index c372b878ef6..d4221a0591d 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -90,7 +90,7 @@ const defaultState: AnnotationState = { sidebarCollapsed: false, appearanceCollapsed: false, tabContentHeight: 0, - workspace: Workspace.ATTRIBUTE_ANNOTATION, + workspace: Workspace.STANDARD, }; export default (state = defaultState, action: AnyAction): AnnotationState => { From ae19fe5cae4d669d5175e39cd5d78f03e9acbf6a Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 12 Mar 2020 16:49:47 +0300 Subject: [PATCH 04/17] Added missed file --- .../annotations-filters-input.tsx | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 cvat-ui/src/components/annotation-page/annotations-filters-input.tsx diff --git a/cvat-ui/src/components/annotation-page/annotations-filters-input.tsx b/cvat-ui/src/components/annotation-page/annotations-filters-input.tsx new file mode 100644 index 00000000000..39ff37b6cf7 --- /dev/null +++ b/cvat-ui/src/components/annotation-page/annotations-filters-input.tsx @@ -0,0 +1,92 @@ +// Copyright (C) 2020 Intel Corporation +// +// SPDX-License-Identifier: MIT + +import React from 'react'; +import { connect } from 'react-redux'; +import Select, { SelectValue, LabeledValue } from 'antd/lib/select'; +import Icon from 'antd/lib/icon'; + +import { + changeAnnotationsFilters as changeAnnotationsFiltersAction, + fetchAnnotationsAsync, +} from 'actions/annotation-actions'; +import { CombinedState } from 'reducers/interfaces'; + +interface StateToProps { + annotationsFilters: string[]; + annotationsFiltersHistory: string[]; +} + +interface DispatchToProps { + changeAnnotationsFilters(value: SelectValue): void; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + annotation: { + annotations: { + filters: annotationsFilters, + filtersHistory: annotationsFiltersHistory, + }, + }, + } = state; + + return { + annotationsFilters, + annotationsFiltersHistory, + }; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + changeAnnotationsFilters(value: SelectValue) { + if (typeof (value) === 'string') { + dispatch(changeAnnotationsFiltersAction([value])); + dispatch(fetchAnnotationsAsync()); + } else if (Array.isArray(value) + && value.every((element: string | number | LabeledValue): boolean => ( + typeof (element) === 'string' + )) + ) { + dispatch(changeAnnotationsFiltersAction(value as string[])); + dispatch(fetchAnnotationsAsync()); + } + }, + }; +} + +function AnnotationsFiltersInput(props: StateToProps & DispatchToProps): JSX.Element { + const { + annotationsFilters, + annotationsFiltersHistory, + changeAnnotationsFilters, + } = props; + + return ( + + ); +} + + +export default connect( + mapStateToProps, + mapDispatchToProps, +)(AnnotationsFiltersInput); From 82ef105724d6781074dc4566225bd4cbe1d37124 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 12 Mar 2020 16:51:05 +0300 Subject: [PATCH 05/17] Removed unnecessary useEffect --- .../attribute-annotation-sidebar/attribute-editor.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx index 574b6961fa8..8f9d23473ab 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -import React, { useEffect } from 'react'; +import React from 'react'; import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; import Text from 'antd/lib/typography/Text'; import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; @@ -304,14 +304,6 @@ function AttributeEditor(props: Props): JSX.Element { const ref = inputType === 'number' ? React.createRef() : React.createRef(); - useEffect(() => { - // if (ref.current) { - // ref.current.focus(); - // } else if (document.activeElement) { - // (document.activeElement as HTMLElement).blur(); - // } - }); - return (
{renderList({ values, inputType, onChange })} From 3567caab08327934c12a0fb17ad575c511815793 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 13 Mar 2020 11:15:49 +0300 Subject: [PATCH 06/17] Removed extra code --- cvat-ui/src/actions/annotation-actions.ts | 1 - cvat-ui/src/reducers/annotation-reducer.ts | 31 ---------------------- 2 files changed, 32 deletions(-) diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 0ac2af63c96..ca89ff9e31e 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -164,7 +164,6 @@ export enum AnnotationActionTypes { SWITCH_Z_LAYER = 'SWITCH_Z_LAYER', ADD_Z_LAYER = 'ADD_Z_LAYER', SEARCH_ANNOTATIONS_FAILED = 'SEARCH_ANNOTATIONS_FAILED', - ACTIVATE_NEXT = 'ACTIVATE_NEXT', CHANGE_WORKSPACE = 'CHANGE_WORKSPACE', } diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index d4221a0591d..a22e0439c5f 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -1049,37 +1049,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } - case AnnotationActionTypes.ACTIVATE_NEXT: { - const { step } = action.payload; - const { states } = state.annotations; - const currentActivated = state.annotations.activatedStateID; - - let newActivated = null; - if (currentActivated === null) { - if (states.length) { - newActivated = state.annotations.states[0].clientID; - } - } else { - const index = states - .map((objectState: any): number => objectState.clientID) - .indexOf(currentActivated); - let newIndex = index + step; - if (newIndex < 0) { - newIndex = states[states.length - 1].clientID; - } else if (newIndex >= states.length) { - newIndex = 0; - } - newActivated = states[newIndex].clientID; - } - - return { - ...state, - annotations: { - ...state.annotations, - activatedStateID: newActivated, - }, - }; - } case AnnotationActionTypes.CHANGE_WORKSPACE: { const { workspace } = action.payload; return { From 90371709386bfe1604558aff06234453e9642cd6 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 13 Mar 2020 18:53:57 +0300 Subject: [PATCH 07/17] Max 9 attributes, inputNumber -> Input in aam --- .../attribute-editor.tsx | 75 +++++-------------- 1 file changed, 18 insertions(+), 57 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx index 8f9d23473ab..1a96309aacd 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx @@ -79,18 +79,6 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { ); - // Hardly ever good solution with lockedChange is a workaround - // Antd InputNumber changes current value when press ArrowUp, ArrowDown - // And only after that it calls onKeyDown callback - // There aren't any API to prevent such behavour (call onKeyDown first or cancel changing value) - // This trick works as following: - // 1. User presses ArrowUp/ArrowDown. InputNumber changes it's internal state and calls onChange - // 2. onChange setups a setTimeout that work after current callstack is done - // 3. InputNumber next calls onKeyDown callback where we lock any changes by the flag - // callback also setups a setTimeout to unlock this flag and put it to event loop - // 4. then the first setTimeout callback works and do not change value because of locked flag - // 5. finally the second setTimeout callback works and reset the locked flag - let lockedChange = false; const handleKeydown = (event: React.KeyboardEvent): void => { if (['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'Tab', 'Shift', 'Control'] @@ -99,50 +87,9 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { event.preventDefault(); const copyEvent = new KeyboardEvent('keydown', event); window.document.dispatchEvent(copyEvent); - lockedChange = true; - setTimeout(() => { - lockedChange = false; - }); } }; - const renderNumber = (): JSX.Element => { - const numberProps = { - min: +values[0], - max: +values[1], - step: +values[2], - value: +currentValue, - autoFocus: true, - ref: ref as React.RefObject, - }; - - return ( - <> - Number: -
- { - setTimeout(() => { - if (typeof (value) !== 'undefined') { - const isValid = typeof (+value) === 'number' - && value >= numberProps.min - && value <= numberProps.max - && !(value % numberProps.step); - - if (isValid && !lockedChange) { - onChange(`${+value}`); - } - } - }); - }} - onKeyDown={handleKeydown} - /> -
- - ); - }; - const renderText = (): JSX.Element => ( <> Text: @@ -151,7 +98,23 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { autoFocus value={currentValue} onChange={(event: React.ChangeEvent) => { - onChange(event.target.value); + const { value } = event.target; + if (inputType === 'number') { + const numberValue = +value; + if (!Number.isNaN(numberValue)) { + const isValid = numberValue >= +values[0] + && numberValue <= +values[1] + && !(numberValue % +values[2]); + if (isValid) { + onChange(value); + } + } + if (!Number.isNaN(+event.target.value)) { + onChange(value); + } + } else { + onChange(value); + } }} onKeyDown={handleKeydown} ref={ref as React.RefObject} @@ -167,8 +130,6 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { element = renderSelect(); } else if (inputType === 'radio') { element = renderRadio(); - } else if (inputType === 'number') { - element = renderNumber(); } else { element = renderText(); } @@ -239,7 +200,7 @@ function renderList(parameters: ListParameters): JSX.Element | null { [key: string]: (keyEvent?: KeyboardEvent) => void; } = {}; - values.forEach((value: string, index: number): void => { + values.slice(0, 9).forEach((value: string, index: number): void => { const key = `SET_${index}_VALUE`; keyMap[key] = { name: `Set value "${value}"`, From a8c7bd912d45bcca6c39ee34b64dee0a7a55a4db Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 13 Mar 2020 18:59:51 +0300 Subject: [PATCH 08/17] Added blur --- .../attribute-annotation-sidebar.tsx | 8 +++++++- .../annotation-page/standard-workspace/canvas-wrapper.tsx | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx index ea00650c1fa..6060cb4cb09 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: MIT -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; import { connect } from 'react-redux'; import Layout, { SiderProps } from 'antd/lib/layout'; @@ -144,6 +144,12 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. } }; + useEffect(() => { + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } + }, []); + const siderProps: SiderProps = { className: 'attribute-annotation-sidebar', theme: 'light', diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index 9608a531ee7..0c1e24dc87b 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -310,8 +310,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { }; private onCanvasClicked = (): void => { - if (document.activeElement) { - (document.activeElement as HTMLElement).blur(); + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); } }; From 569c7a277e9041f185e2b2f3e82c2185c8f6d613 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 13 Mar 2020 19:03:05 +0300 Subject: [PATCH 09/17] Renamed component --- .../attribute-annotation-workspace.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx index 78b2dc5b426..d4e5fd8de94 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-workspace.tsx @@ -9,7 +9,7 @@ import Layout from 'antd/lib/layout'; import CanvasWrapperContainer from 'containers/annotation-page/standard-workspace/canvas-wrapper'; import AttributeAnnotationSidebar from './attribute-annotation-sidebar/attribute-annotation-sidebar'; -export default function StandardWorkspaceComponent(): JSX.Element { +export default function AttributeAnnotationWorkspace(): JSX.Element { return ( From 1d33ce07c282c2308a7eaa66c98c4914fe4efb1d Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 13 Mar 2020 19:08:09 +0300 Subject: [PATCH 10/17] Fixed condition when validate number attribute --- .../attribute-annotation-sidebar/attribute-editor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx index 1a96309aacd..1561bce5133 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx @@ -104,7 +104,7 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { if (!Number.isNaN(numberValue)) { const isValid = numberValue >= +values[0] && numberValue <= +values[1] - && !(numberValue % +values[2]); + && !((numberValue - +values[0]) % +values[2]); if (isValid) { onChange(value); } From b5dafa877580edf59cb93d4116ab04977ee2d8fa Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 16 Mar 2020 16:25:39 +0300 Subject: [PATCH 11/17] Some minor fixes --- .../attribute-annotation-sidebar.tsx | 16 ++++++++-------- .../object-switcher.tsx | 4 ++-- .../standard-workspace/canvas-wrapper.tsx | 8 +++++++- .../annotation-page/top-bar/top-bar.tsx | 2 ++ 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx index 6060cb4cb09..4c962dc0c57 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -163,26 +163,26 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. const keyMap = { NEXT_ATTRIBUTE: { name: 'Next object', - description: 'Go to the next object. Use Ctrl when inside an input field', - sequences: ['ArrowDown', 'Ctrl+ArrowDown'], + description: 'Go to the next attribute', + sequence: 'ArrowDown', action: 'keydown', }, PREVIOUS_ATTRIBUTE: { name: 'Previous object', - description: 'Go to the previous object. Use Ctrl when inside an input field', - sequences: ['ArrowUp', 'Ctrl+ArrowUp'], + description: 'Go to the previous attribute', + sequences: 'ArrowUp', action: 'keydown', }, NEXT_OBJECT: { name: 'Next object', - description: 'Go to the next object. Use Ctrl when inside an input field', - sequences: ['Tab', 'Ctrl+Tab'], + description: 'Go to the next object', + sequence: 'Tab', action: 'keydown', }, PREVIOUS_OBJECT: { name: 'Previous object', - description: 'Go to the previous object. Use Ctrl when inside an input field', - sequences: ['Shift+Tab', 'Ctrl+Shift+Tab'], + description: 'Go to the previous object', + sequence: 'Shift+Tab', action: 'keydown', }, }; diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx index e1cde7fdd65..9341396feae 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/object-switcher.tsx @@ -30,7 +30,7 @@ function ObjectSwitcher(props: Props): JSX.Element { const title = `${currentLabel} ${clientID} [${currentIndex + 1}/${objectsCount}]`; return (
- @@ -38,7 +38,7 @@ function ObjectSwitcher(props: Props): JSX.Element { {` ${clientID} `} {`[${currentIndex + 1}/${objectsCount}]`} -
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index 0152ca31ce0..28c84152ed8 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -117,6 +117,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { brightnessLevel, contrastLevel, saturationLevel, + workspace, } = this.props; if (prevProps.sidebarCollapsed !== sidebarCollapsed) { @@ -169,7 +170,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { this.updateCanvas(); } - if (prevProps.frame !== frameData.number && resetZoom) { + if (prevProps.frame !== frameData.number + && resetZoom + && workspace !== Workspace.ATTRIBUTE_ANNOTATION + ) { canvasInstance.html().addEventListener('canvas.setup', () => { canvasInstance.fit(); }, { once: true }); @@ -549,7 +553,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { // Events canvasInstance.html().addEventListener('canvas.setup', () => { + const { activatedStateID, activatedAttributeID } = this.props; canvasInstance.fit(); + canvasInstance.activate(activatedStateID, activatedAttributeID); }, { once: true }); canvasInstance.html().addEventListener('mousedown', this.onCanvasMouseDown); diff --git a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx index 96d76ab58ce..48695dbed7b 100644 --- a/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx @@ -23,6 +23,7 @@ import { redoActionAsync, searchAnnotationsAsync, changeWorkspace as changeWorkspaceAction, + activateObject, } from 'actions/annotation-actions'; import AnnotationTopBarComponent from 'components/annotation-page/top-bar/top-bar'; @@ -136,6 +137,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { dispatch(searchAnnotationsAsync(sessionInstance, frameFrom, frameTo)); }, changeWorkspace(workspace: Workspace): void { + dispatch(activateObject(null, null)); dispatch(changeWorkspaceAction(workspace)); }, }; From e87e2b383ed825d81a3ef6c49bfadaad2b68a7e5 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 16 Mar 2020 16:29:35 +0300 Subject: [PATCH 12/17] Fixed hotkeys config --- .../attribute-annotation-sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx index 4c962dc0c57..3eab297d891 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -170,7 +170,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. PREVIOUS_ATTRIBUTE: { name: 'Previous object', description: 'Go to the previous attribute', - sequences: 'ArrowUp', + sequence: 'ArrowUp', action: 'keydown', }, NEXT_OBJECT: { From 11d6236d1cec2edbb765e2a49babf930b458a9f7 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 16 Mar 2020 16:46:20 +0300 Subject: [PATCH 13/17] Fixed canvas zoom --- .../annotation-page/standard-workspace/canvas-wrapper.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index 28c84152ed8..2b34c1314fd 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -166,6 +166,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { } } + if (prevProps.curZLayer !== curZLayer) { + canvasInstance.setZLayer(curZLayer); + } + if (prevProps.annotations !== annotations || prevProps.frameData !== frameData) { this.updateCanvas(); } @@ -184,10 +188,6 @@ export default class CanvasWrapperComponent extends React.PureComponent { this.updateShapesView(); } - if (prevProps.curZLayer !== curZLayer) { - canvasInstance.setZLayer(curZLayer); - } - if (prevProps.frameAngle !== frameAngle) { canvasInstance.rotate(frameAngle); } From 1e0e3d9e4f5fc981e897e743e795ba17c45a87ee Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 16 Mar 2020 17:04:26 +0300 Subject: [PATCH 14/17] Improved behaviour of number & text --- .../attribute-editor.tsx | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx index 1561bce5133..ecfca2b8e9c 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx @@ -12,6 +12,7 @@ import Input from 'antd/lib/input'; import InputNumber from 'antd/lib/input-number'; interface InputElementParameters { + attrID: number; inputType: string; values: string[]; currentValue: string; @@ -22,6 +23,7 @@ interface InputElementParameters { function renderInputElement(parameters: InputElementParameters): JSX.Element { const { inputType, + attrID, values, currentValue, onChange, @@ -92,26 +94,21 @@ function renderInputElement(parameters: InputElementParameters): JSX.Element { const renderText = (): JSX.Element => ( <> - Text: + {inputType === 'number' ? Number: : Text: }
) => { const { value } = event.target; if (inputType === 'number') { - const numberValue = +value; - if (!Number.isNaN(numberValue)) { - const isValid = numberValue >= +values[0] - && numberValue <= +values[1] - && !((numberValue - +values[0]) % +values[2]); - if (isValid) { - onChange(value); + if (value !== '') { + const numberValue = +value; + if (!Number.isNaN(numberValue)) { + onChange(`${numberValue}`); } } - if (!Number.isNaN(+event.target.value)) { - onChange(value); - } } else { onChange(value); } @@ -261,7 +258,7 @@ interface Props { function AttributeEditor(props: Props): JSX.Element { const { attribute, currentValue, onChange } = props; - const { inputType, values } = attribute; + const { inputType, values, id: attrID } = attribute; const ref = inputType === 'number' ? React.createRef() : React.createRef(); @@ -270,6 +267,7 @@ function AttributeEditor(props: Props): JSX.Element { {renderList({ values, inputType, onChange })}
{renderInputElement({ + attrID, ref, inputType, currentValue, From f5566a762c1db79955829dc5dedc78dbe91ea03d Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 16 Mar 2020 17:05:39 +0300 Subject: [PATCH 15/17] Fixed attributes switching order --- .../attribute-annotation-sidebar/attribute-switcher.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx index 5f0eea0c12a..2ad4a6d700a 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-switcher.tsx @@ -26,14 +26,14 @@ function AttributeSwitcher(props: Props): JSX.Element { const title = `${currentAttribute} [${currentIndex + 1}/${attributesCount}]`; return (
- {currentAttribute} {` [${currentIndex + 1}/${attributesCount}]`} -
From 95f8bc09e3c1ddb1e9e02622de254ba5ff6dc9dc Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 16 Mar 2020 17:20:12 +0300 Subject: [PATCH 16/17] Fix tags --- .../standard-workspace/canvas-wrapper.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index 2b34c1314fd..91a2e20e698 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -455,11 +455,18 @@ export default class CanvasWrapperComponent extends React.PureComponent { selectedOpacity, aamZoomMargin, workspace, + annotations, } = this.props; if (activatedStateID !== null) { if (workspace === Workspace.ATTRIBUTE_ANNOTATION) { - canvasInstance.focus(activatedStateID, aamZoomMargin); + const [activatedState] = annotations + .filter((state: any): boolean => state.clientID === activatedStateID); + if (activatedState.objectType !== ObjectType.TAG) { + canvasInstance.focus(activatedStateID, aamZoomMargin); + } else { + canvasInstance.fit(); + } } canvasInstance.activate(activatedStateID, activatedAttributeID); const el = window.document.getElementById(`cvat_canvas_shape_${activatedStateID}`); From 86b5cd3b276d9912abbfee3abe9d6a7fbbda9db8 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 16 Mar 2020 18:58:32 +0300 Subject: [PATCH 17/17] Fixed interval --- .../attribute-annotation-sidebar.tsx | 4 ++-- .../attribute-annotation-sidebar/attribute-editor.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx index 3eab297d891..d19b94ef9de 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-annotation-sidebar.tsx @@ -162,13 +162,13 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX. const keyMap = { NEXT_ATTRIBUTE: { - name: 'Next object', + name: 'Next attribute', description: 'Go to the next attribute', sequence: 'ArrowDown', action: 'keydown', }, PREVIOUS_ATTRIBUTE: { - name: 'Previous object', + name: 'Previous attribute', description: 'Go to the previous attribute', sequence: 'ArrowUp', action: 'keydown', diff --git a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx index ecfca2b8e9c..5b9150a209a 100644 --- a/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx +++ b/cvat-ui/src/components/annotation-page/attribute-annotation-workspace/attribute-annotation-sidebar/attribute-editor.tsx @@ -197,7 +197,7 @@ function renderList(parameters: ListParameters): JSX.Element | null { [key: string]: (keyEvent?: KeyboardEvent) => void; } = {}; - values.slice(0, 9).forEach((value: string, index: number): void => { + values.slice(0, 10).forEach((value: string, index: number): void => { const key = `SET_${index}_VALUE`; keyMap[key] = { name: `Set value "${value}"`,