From 8baacd6920f376046aa4df5fb575e677f19e0577 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Mon, 17 Feb 2020 16:04:14 +0300 Subject: [PATCH 01/18] Added settings actions --- cvat-ui/src/actions/settings-actions.ts | 100 ++++++++++++++++++ .../settings-page/player-settings.tsx | 54 +++++++++- .../settings-page/workspace-settings.tsx | 27 ++++- cvat-ui/src/components/task-page/job-list.tsx | 8 +- .../settings-page/player-settings.tsx | 30 +++--- .../settings-page/workspace-settings.tsx | 25 +++-- cvat-ui/src/reducers/settings-reducer.ts | 90 ++++++++++++++++ 7 files changed, 296 insertions(+), 38 deletions(-) diff --git a/cvat-ui/src/actions/settings-actions.ts b/cvat-ui/src/actions/settings-actions.ts index e1614cdfb421..2b6b0cdacb12 100644 --- a/cvat-ui/src/actions/settings-actions.ts +++ b/cvat-ui/src/actions/settings-actions.ts @@ -14,6 +14,16 @@ export enum SettingsActionTypes { CHANGE_SELECTED_SHAPES_OPACITY = 'CHANGE_SELECTED_SHAPES_OPACITY', CHANGE_SHAPES_COLOR_BY = 'CHANGE_SHAPES_COLOR_BY', CHANGE_SHAPES_BLACK_BORDERS = 'CHANGE_SHAPES_BLACK_BORDERS', + CHANGE_FRAME_STEP = 'CHANGE_FRAME_STEP', + CHANGE_FRAME_SPEED = 'CHANGE_FRAME_SPEED', + SWITCH_RESET_ZOOM = 'SWITCH_RESET_ZOOM', + CHANGE_BRIGHTNESS_LEVEL = 'CHANGE_BRIGHTNESS_LEVEL', + CHANGE_CONTRAST_LEVEL = 'CHANGE_CONTRAST_LEVEL', + CHANGE_SATURATION_LEVEL = 'CHANGE_SATURATION_LEVEL', + SWITCH_AUTO_SAVE = 'SWITCH_AUTO_SAVE', + CHANGE_AUTO_SAVE_INTERVAL = 'CHANGE_AUTO_SAVE_INTERVAL', + CHANGE_AAM_ZOOM_MARGIN = 'CHANGE_AAM_ZOOM_MARGIN', + SWITCH_SHOWNIG_INTERPOLATED_TRACKS = 'SWITCH_SHOWNIG_INTERPOLATED_TRACKS', } export function changeShapesOpacity(opacity: number): AnyAction { @@ -96,3 +106,93 @@ export function changeGridOpacity(gridOpacity: number): AnyAction { }, }; } + +export function changeFrameStep(frameStep: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_FRAME_STEP, + payload: { + frameStep, + }, + }; +} + +export function changeFrameSpeed(frameSpeed: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_FRAME_SPEED, + payload: { + frameSpeed, + }, + }; +} + +export function switchResetZoom(resetZoom: boolean): AnyAction { + return { + type: SettingsActionTypes.SWITCH_RESET_ZOOM, + payload: { + resetZoom, + }, + }; +} + +export function changeBrightnessLevel(level: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_BRIGHTNESS_LEVEL, + payload: { + level, + }, + }; +} + +export function changeContrastLevel(level: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_CONTRAST_LEVEL, + payload: { + level, + }, + }; +} + +export function changeSaturationLevel(level: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_SATURATION_LEVEL, + payload: { + level, + }, + }; +} + +export function switchAutoSave(autoSave: boolean): AnyAction { + return { + type: SettingsActionTypes.SWITCH_AUTO_SAVE, + payload: { + autoSave, + }, + }; +} + +export function changeAutoSaveInterval(autoSaveInterval: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_AUTO_SAVE_INTERVAL, + payload: { + autoSaveInterval, + }, + }; +} + +export function changeAAMZoomMargin(aamZoomMargin: number): AnyAction { + return { + type: SettingsActionTypes.CHANGE_AAM_ZOOM_MARGIN, + payload: { + aamZoomMargin, + }, + }; +} + +export function switchShowingInterpolatedTracks(showAllInterpolationTracks: boolean): AnyAction { + return { + type: SettingsActionTypes.SWITCH_SHOWNIG_INTERPOLATED_TRACKS, + payload: { + showAllInterpolationTracks, + }, + }; +} \ No newline at end of file diff --git a/cvat-ui/src/components/settings-page/player-settings.tsx b/cvat-ui/src/components/settings-page/player-settings.tsx index 1e419679a11a..2f5e5c55fda8 100644 --- a/cvat-ui/src/components/settings-page/player-settings.tsx +++ b/cvat-ui/src/components/settings-page/player-settings.tsx @@ -61,11 +61,17 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { brightnessLevel, contrastLevel, saturationLevel, + onChangeFrameStep, + onChangeFrameSpeed, + onSwitchResetZoom, onSwitchRotateAll, onSwitchGrid, onChangeGridSize, onChangeGridColor, onChangeGridOpacity, + onChangeBrightnessLevel, + onChangeContrastLevel, + onChangeSaturationLevel, } = props; return ( @@ -73,7 +79,16 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { Player step - + { + if (value) { + onChangeFrameStep(value); + } + }} + /> @@ -87,7 +102,12 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { Player speed - { + onChangeFrameSpeed(speed); + }} + > Fastest Fast Usual @@ -160,6 +180,9 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { { + onSwitchResetZoom(event.target.checked); + }} > Reset zoom @@ -193,7 +216,14 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { Brightness - + { + onChangeBrightnessLevel(value as number); + }} + /> @@ -201,7 +231,14 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { Contrast - + { + onChangeContrastLevel(value as number); + }} + /> @@ -209,7 +246,14 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { Saturation - + { + onChangeSaturationLevel(value as number); + }} + /> diff --git a/cvat-ui/src/components/settings-page/workspace-settings.tsx b/cvat-ui/src/components/settings-page/workspace-settings.tsx index 3fd6d10be6a4..263c4ae3fd60 100644 --- a/cvat-ui/src/components/settings-page/workspace-settings.tsx +++ b/cvat-ui/src/components/settings-page/workspace-settings.tsx @@ -8,6 +8,7 @@ import { } from 'antd'; import Text from 'antd/lib/typography/Text'; +import { CheckboxChangeEvent } from 'antd/lib/checkbox'; interface Props { autoSave: boolean; @@ -26,6 +27,10 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { autoSaveInterval, aamZoomMargin, showAllInterpolationTracks, + onSwitchAutoSave, + onChangeAutoSaveInterval, + onChangeAAMZoomMargin, + onSwitchShowingInterpolatedTracks, } = props; return ( @@ -35,6 +40,9 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { { + onSwitchAutoSave(event.target.checked); + }} > Enable auto save @@ -48,6 +56,11 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { max={60} step={1} value={Math.round(autoSaveInterval / (60 * 1000))} + onChange={(value: number | undefined): void => { + if (value) { + onChangeAutoSaveInterval(value as number * 60 * 1000); + } + }} /> minutes @@ -57,6 +70,9 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { { + onSwitchShowingInterpolatedTracks(event.target.checked); + }} > Show all interpolation tracks @@ -68,7 +84,16 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { Attribute annotation mode (AAM) zoom margin - + { + if (value) { + onChangeAAMZoomMargin(value); + } + }} + /> diff --git a/cvat-ui/src/components/task-page/job-list.tsx b/cvat-ui/src/components/task-page/job-list.tsx index fb29cca56d77..f3d10ccc68c0 100644 --- a/cvat-ui/src/components/task-page/job-list.tsx +++ b/cvat-ui/src/components/task-page/job-list.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { Link } from 'react-router-dom'; + import { Row, Col, @@ -39,7 +41,11 @@ export default function JobListComponent(props: Props): JSX.Element { title: 'Job', dataIndex: 'job', key: 'job', - render: (id: number): JSX.Element => ({ `Job #${id}` }), + render: (id: number): JSX.Element => (
+ {`Job #${id}`} + {" | "} + Old +
), }, { title: 'Frames', dataIndex: 'frames', diff --git a/cvat-ui/src/containers/settings-page/player-settings.tsx b/cvat-ui/src/containers/settings-page/player-settings.tsx index 73193b8f4278..ee0d56f0166b 100644 --- a/cvat-ui/src/containers/settings-page/player-settings.tsx +++ b/cvat-ui/src/containers/settings-page/player-settings.tsx @@ -4,11 +4,17 @@ import { connect } from 'react-redux'; import PlayerSettingsComponent from 'components/settings-page/player-settings'; import { + changeFrameStep, + changeFrameSpeed, + switchResetZoom, switchRotateAll, switchGrid, changeGridSize, changeGridColor, changeGridOpacity, + changeBrightnessLevel, + changeContrastLevel, + changeSaturationLevel, } from 'actions/settings-actions'; import { @@ -54,20 +60,14 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - // will be implemented - // eslint-disable-next-line onChangeFrameStep(step: number): void { - + dispatch(changeFrameStep(step)); }, - // will be implemented - // eslint-disable-next-line onChangeFrameSpeed(speed: FrameSpeed): void { - + dispatch(changeFrameSpeed(speed)); }, - // will be implemented - // eslint-disable-next-line onSwitchResetZoom(enabled: boolean): void { - + dispatch(switchResetZoom(enabled)); }, onSwitchRotateAll(rotateAll: boolean): void { dispatch(switchRotateAll(rotateAll)); @@ -84,20 +84,14 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { onChangeGridOpacity(gridOpacity: number): void { dispatch(changeGridOpacity(gridOpacity)); }, - // will be implemented - // eslint-disable-next-line onChangeBrightnessLevel(level: number): void { - + dispatch(changeBrightnessLevel(level)); }, - // will be implemented - // eslint-disable-next-line onChangeContrastLevel(level: number): void { - + dispatch(changeContrastLevel(level)); }, - // will be implemented - // eslint-disable-next-line onChangeSaturationLevel(level: number): void { - + dispatch(changeSaturationLevel(level)); }, }; } diff --git a/cvat-ui/src/containers/settings-page/workspace-settings.tsx b/cvat-ui/src/containers/settings-page/workspace-settings.tsx index 0c5a597efbfe..c1253c469877 100644 --- a/cvat-ui/src/containers/settings-page/workspace-settings.tsx +++ b/cvat-ui/src/containers/settings-page/workspace-settings.tsx @@ -1,6 +1,13 @@ import React from 'react'; import { connect } from 'react-redux'; +import { + switchAutoSave, + changeAutoSaveInterval, + changeAAMZoomMargin, + switchShowingInterpolatedTracks, +} from 'actions/settings-actions'; + import { CombinedState, } from 'reducers/interfaces'; @@ -38,27 +45,19 @@ function mapStateToProps(state: CombinedState): StateToProps { }; } -function mapDispatchToProps(): DispatchToProps { +function mapDispatchToProps(dispatch: any): DispatchToProps { return { - // will be implemented - // eslint-disable-next-line onSwitchAutoSave(enabled: boolean): void { - + dispatch(switchAutoSave(enabled)); }, - // will be implemented - // eslint-disable-next-line onChangeAutoSaveInterval(interval: number): void { - + dispatch(changeAutoSaveInterval(interval)); }, - // will be implemented - // eslint-disable-next-line onChangeAAMZoomMargin(margin: number): void { - + dispatch(changeAAMZoomMargin(margin)); }, - // will be implemented - // eslint-disable-next-line onSwitchShowingInterpolatedTracks(enabled: boolean): void { - + dispatch(switchShowingInterpolatedTracks(enabled)); }, }; } diff --git a/cvat-ui/src/reducers/settings-reducer.ts b/cvat-ui/src/reducers/settings-reducer.ts index b35dd37df43d..a0274e322566 100644 --- a/cvat-ui/src/reducers/settings-reducer.ts +++ b/cvat-ui/src/reducers/settings-reducer.ts @@ -119,6 +119,96 @@ export default (state = defaultState, action: AnyAction): SettingsState => { }, }; } + case SettingsActionTypes.CHANGE_FRAME_STEP: { + return { + ...state, + player: { + ...state.player, + frameStep: action.payload.frameStep, + } + } + } + case SettingsActionTypes.CHANGE_FRAME_SPEED: { + return { + ...state, + player: { + ...state.player, + frameSpeed: action.payload.frameSpeed, + } + } + } + case SettingsActionTypes.SWITCH_RESET_ZOOM: { + return { + ...state, + player: { + ...state.player, + resetZoom: action.payload.resetZoom, + } + } + } + case SettingsActionTypes.CHANGE_BRIGHTNESS_LEVEL: { + return { + ...state, + player: { + ...state.player, + brightnessLevel: action.payload.level, + } + } + } + case SettingsActionTypes.CHANGE_CONTRAST_LEVEL: { + return { + ...state, + player: { + ...state.player, + contrastLevel: action.payload.level, + } + } + } + case SettingsActionTypes.CHANGE_SATURATION_LEVEL: { + return { + ...state, + player: { + ...state.player, + saturationLevel: action.payload.level, + } + } + } + case SettingsActionTypes.SWITCH_AUTO_SAVE: { + return { + ...state, + workspace: { + ...state.workspace, + autoSave: action.payload.autoSave, + } + } + } + case SettingsActionTypes.CHANGE_AUTO_SAVE_INTERVAL: { + return { + ...state, + workspace: { + ...state.workspace, + autoSaveInterval: action.payload.autoSaveInterval, + } + } + } + case SettingsActionTypes.CHANGE_AAM_ZOOM_MARGIN: { + return { + ...state, + workspace: { + ...state.workspace, + aamZoomMargin: action.payload.aamZoomMargin, + } + } + } + case SettingsActionTypes.SWITCH_SHOWNIG_INTERPOLATED_TRACKS: { + return { + ...state, + workspace: { + ...state.workspace, + showAllInterpolationTracks: action.payload.showAllInterpolationTracks, + } + } + } default: { return state; } From 1d5699862445eb1db4f7ef693743d0d1e493dfdc Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Tue, 18 Feb 2020 14:22:07 +0300 Subject: [PATCH 02/18] Added image filters for background and autosaving --- .../standard-workspace/canvas-wrapper.tsx | 12 +++++++ .../settings-page/player-settings.tsx | 3 ++ .../standard-workspace/canvas-wrapper.tsx | 9 ++++++ .../annotation-page/top-bar/top-bar.tsx | 31 ++++++++++++++++++- 4 files changed, 54 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 6c9cc997d357..590cfa5892ea 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 @@ -39,6 +39,9 @@ interface Props { gridOpacity: number; activeLabelID: number; activeObjectType: ObjectType; + brightnessLevel: number; + contrastLevel: number; + saturationLevel: number; onSetupCanvas: () => void; onDragCanvas: (enabled: boolean) => void; onZoomCanvas: (enabled: boolean) => void; @@ -325,6 +328,9 @@ export default class CanvasWrapperComponent extends React.PureComponent { onActivateObject, onUpdateContextMenu, onEditShape, + brightnessLevel, + contrastLevel, + saturationLevel, } = this.props; // Size @@ -343,6 +349,12 @@ export default class CanvasWrapperComponent extends React.PureComponent { } canvasInstance.grid(gridSize, gridSize); + // Filters + const backgroundElement = window.document.getElementById('cvat_canvas_background'); + if (backgroundElement) { + backgroundElement.style.filter = `brightness(${brightnessLevel/50}) contrast(${contrastLevel/50}) saturate(${saturationLevel/50})`; + } + // Events canvasInstance.html().addEventListener('mousedown', (e: MouseEvent): void => { const { diff --git a/cvat-ui/src/components/settings-page/player-settings.tsx b/cvat-ui/src/components/settings-page/player-settings.tsx index 2f5e5c55fda8..8447f10e54de 100644 --- a/cvat-ui/src/components/settings-page/player-settings.tsx +++ b/cvat-ui/src/components/settings-page/player-settings.tsx @@ -138,6 +138,7 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { max={1000} step={1} value={gridSize} + disabled={!grid} onChange={(value: number | undefined): void => { if (value) { onChangeGridSize(value); @@ -149,6 +150,7 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { Grid color { onChangeFrameSpeed(speed); @@ -220,8 +220,8 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { { onChangeBrightnessLevel(value as number); @@ -235,12 +235,12 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { { onChangeContrastLevel(value as number); - }} + }} />
@@ -251,7 +251,7 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element { { onChangeSaturationLevel(value as number); diff --git a/cvat-ui/src/components/task-page/job-list.tsx b/cvat-ui/src/components/task-page/job-list.tsx index f3d10ccc68c0..ab4edfddc151 100644 --- a/cvat-ui/src/components/task-page/job-list.tsx +++ b/cvat-ui/src/components/task-page/job-list.tsx @@ -36,16 +36,18 @@ export default function JobListComponent(props: Props): JSX.Element { onJobUpdate, } = props; - const { jobs } = taskInstance; + const { jobs, id: taskId } = taskInstance; const columns = [{ title: 'Job', dataIndex: 'job', key: 'job', - render: (id: number): JSX.Element => (
- {`Job #${id}`} - {" | "} + render: (id: number): JSX.Element => ( +
+ {`Job #${id}`} + {' | '} Old -
), +
+ ), }, { title: 'Frames', dataIndex: 'frames', 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 69f895239b17..705859b121b3 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 @@ -27,7 +27,7 @@ interface StateToProps { frameChangeTime: number | null; playing: boolean; saving: boolean; - unsaved: boolean, + unsaved: boolean; canvasInstance: Canvas; canvasIsReady: boolean; savingStatuses: string[]; @@ -82,7 +82,7 @@ function mapStateToProps(state: CombinedState): StateToProps { workspace: { autoSave, autoSaveInterval, - } + }, }, } = state; @@ -132,8 +132,29 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { type Props = StateToProps & DispatchToProps; class AnnotationTopBarContainer extends React.PureComponent { + private static beforeUnloadCallback(event: BeforeUnloadEvent): any { + const confirmationMessage = 'You have unsaved changes, please confirm leaving this page.'; + // eslint-disable-next-line no-param-reassign + (event || window.event).returnValue = confirmationMessage; + return confirmationMessage; + } + private autoSaveInterval: number | undefined; + componentDidMount(): void { + const { + autoSave, + autoSaveInterval, + saving, + } = this.props; + + this.autoSaveInterval = window.setInterval((): void => { + if (autoSave && !saving) { + this.onSaveAnnotation(); + } + }, autoSaveInterval); + } + public componentDidUpdate(): void { const { jobInstance, @@ -143,12 +164,15 @@ class AnnotationTopBarContainer extends React.PureComponent { playing, canvasIsReady, onSwitchPlay, + unsaved, } = this.props; - + if (playing && canvasIsReady) { if (frameNumber < jobInstance.stopFrame) { - const delay: number = frameChangeTime ? Math.max(0,~~(1000/frameSpeed) - new Date().getTime() + frameChangeTime) : 0; + const delay: number = frameChangeTime + ? Math.max(0, Math.round(1000 / frameSpeed) + - new Date().getTime() + frameChangeTime) : 0; setTimeout(() => { const { playing: stillPlaying } = this.props; if (stillPlaying) { @@ -159,40 +183,21 @@ class AnnotationTopBarContainer extends React.PureComponent { onSwitchPlay(false); } } - } - - componentDidMount(): void { - const { - autoSave, - autoSaveInterval, - saving, - unsaved, - } = this.props; - - this.autoSaveInterval = window.setInterval((autoSave: boolean, saving: boolean): void => { - if (autoSave && !saving) { - this.onSaveAnnotation(); - } - }, autoSaveInterval, autoSave, saving); if (unsaved) { - window.addEventListener('beforeunload', this.beforeUnloadCallback); + window.addEventListener('beforeunload', AnnotationTopBarContainer.beforeUnloadCallback); + } else { + window.removeEventListener('beforeunload', AnnotationTopBarContainer.beforeUnloadCallback); } } - public componentWillUnmount() : void { + public componentWillUnmount(): void { window.clearInterval(this.autoSaveInterval); - window.removeEventListener('beforeunload', this.beforeUnloadCallback); - } - - private beforeUnloadCallback(event: BeforeUnloadEvent): any { - const confirmationMessage = 'You have unsaved changes, please confirm leaving this page.'; - (event || window.event).returnValue = confirmationMessage; - return confirmationMessage; + window.removeEventListener('beforeunload', AnnotationTopBarContainer.beforeUnloadCallback); } private onChangeFrame(newFrame: number, time: number | null = null): void { - const { + const { canvasInstance, canvasIsReady, onChangeFrame, diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 21889c1a6e13..f7ba149125d8 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -325,7 +325,7 @@ export interface AnnotationState { uploading: boolean; statuses: string[]; }; - unsaved: boolean, + unsaved: boolean; }; propagate: { objectState: any | null; diff --git a/cvat-ui/src/reducers/settings-reducer.ts b/cvat-ui/src/reducers/settings-reducer.ts index a0274e322566..9879e2b34228 100644 --- a/cvat-ui/src/reducers/settings-reducer.ts +++ b/cvat-ui/src/reducers/settings-reducer.ts @@ -30,9 +30,9 @@ const defaultState: SettingsState = { gridSize: 100, gridColor: GridColor.White, gridOpacity: 0, - brightnessLevel: 50, - contrastLevel: 50, - saturationLevel: 50, + brightnessLevel: 100, + contrastLevel: 100, + saturationLevel: 100, }, }; @@ -125,8 +125,8 @@ export default (state = defaultState, action: AnyAction): SettingsState => { player: { ...state.player, frameStep: action.payload.frameStep, - } - } + }, + }; } case SettingsActionTypes.CHANGE_FRAME_SPEED: { return { @@ -134,8 +134,8 @@ export default (state = defaultState, action: AnyAction): SettingsState => { player: { ...state.player, frameSpeed: action.payload.frameSpeed, - } - } + }, + }; } case SettingsActionTypes.SWITCH_RESET_ZOOM: { return { @@ -143,8 +143,8 @@ export default (state = defaultState, action: AnyAction): SettingsState => { player: { ...state.player, resetZoom: action.payload.resetZoom, - } - } + }, + }; } case SettingsActionTypes.CHANGE_BRIGHTNESS_LEVEL: { return { @@ -152,8 +152,8 @@ export default (state = defaultState, action: AnyAction): SettingsState => { player: { ...state.player, brightnessLevel: action.payload.level, - } - } + }, + }; } case SettingsActionTypes.CHANGE_CONTRAST_LEVEL: { return { @@ -161,8 +161,8 @@ export default (state = defaultState, action: AnyAction): SettingsState => { player: { ...state.player, contrastLevel: action.payload.level, - } - } + }, + }; } case SettingsActionTypes.CHANGE_SATURATION_LEVEL: { return { @@ -170,8 +170,8 @@ export default (state = defaultState, action: AnyAction): SettingsState => { player: { ...state.player, saturationLevel: action.payload.level, - } - } + }, + }; } case SettingsActionTypes.SWITCH_AUTO_SAVE: { return { @@ -179,8 +179,8 @@ export default (state = defaultState, action: AnyAction): SettingsState => { workspace: { ...state.workspace, autoSave: action.payload.autoSave, - } - } + }, + }; } case SettingsActionTypes.CHANGE_AUTO_SAVE_INTERVAL: { return { @@ -188,8 +188,8 @@ export default (state = defaultState, action: AnyAction): SettingsState => { workspace: { ...state.workspace, autoSaveInterval: action.payload.autoSaveInterval, - } - } + }, + }; } case SettingsActionTypes.CHANGE_AAM_ZOOM_MARGIN: { return { @@ -197,8 +197,8 @@ export default (state = defaultState, action: AnyAction): SettingsState => { workspace: { ...state.workspace, aamZoomMargin: action.payload.aamZoomMargin, - } - } + }, + }; } case SettingsActionTypes.SWITCH_SHOWNIG_INTERPOLATED_TRACKS: { return { @@ -206,8 +206,8 @@ export default (state = defaultState, action: AnyAction): SettingsState => { workspace: { ...state.workspace, showAllInterpolationTracks: action.payload.showAllInterpolationTracks, - } - } + }, + }; } default: { return state; From 4972f0acf2a438efc669a4cb474d568abbb450d8 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 20 Feb 2020 15:15:10 +0300 Subject: [PATCH 06/18] PR fixes --- cvat-ui/src/actions/annotation-actions.ts | 14 ++++-- .../settings-page/workspace-settings.tsx | 2 +- cvat-ui/src/components/task-page/job-list.tsx | 2 +- .../annotation-page/top-bar/top-bar.tsx | 46 +++++++++---------- cvat-ui/src/reducers/annotation-reducer.ts | 9 ++-- cvat-ui/src/reducers/interfaces.ts | 1 + 6 files changed, 42 insertions(+), 32 deletions(-) diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index efd882826b2b..caa86ba31c2b 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -545,7 +545,7 @@ export function switchPlay(playing: boolean): AnyAction { }; } -export function changeFrameAsync(toFrame: number, frameChangeTime: number | null): +export function changeFrameAsync(toFrame: number): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { const state: CombinedState = getStore().getState(); @@ -558,13 +558,17 @@ ThunkAction, {}, {}, AnyAction> { } if (toFrame === frame) { + const currentTime = new Date().getTime(); + const delay = Math.max(0, Math.round(1000 / state.settings.player.frameSpeed) + - currentTime + (state.annotation.player.frame.changeTime as number)); dispatch({ type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS, payload: { number: state.annotation.player.frame.number, data: state.annotation.player.frame.data, states: state.annotation.annotations.states, - frameChangeTime, + changeTime: currentTime + delay, + delay, }, }); @@ -579,13 +583,17 @@ ThunkAction, {}, {}, AnyAction> { const data = await job.frames.get(toFrame); const states = await job.annotations.get(toFrame, showAllInterpolationTracks, filters); + const currentTime = new Date().getTime(); + const delay = Math.max(0, Math.round(1000 / state.settings.player.frameSpeed) + - currentTime + (state.annotation.player.frame.changeTime as number)); dispatch({ type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS, payload: { number: toFrame, data, states, - frameChangeTime, + changeTime: currentTime + delay, + delay, }, }); } catch (error) { diff --git a/cvat-ui/src/components/settings-page/workspace-settings.tsx b/cvat-ui/src/components/settings-page/workspace-settings.tsx index 263c4ae3fd60..c704f8fa0626 100644 --- a/cvat-ui/src/components/settings-page/workspace-settings.tsx +++ b/cvat-ui/src/components/settings-page/workspace-settings.tsx @@ -58,7 +58,7 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element { value={Math.round(autoSaveInterval / (60 * 1000))} onChange={(value: number | undefined): void => { if (value) { - onChangeAutoSaveInterval(value as number * 60 * 1000); + onChangeAutoSaveInterval(value * 60 * 1000); } }} /> diff --git a/cvat-ui/src/components/task-page/job-list.tsx b/cvat-ui/src/components/task-page/job-list.tsx index ab4edfddc151..658ea16c7306 100644 --- a/cvat-ui/src/components/task-page/job-list.tsx +++ b/cvat-ui/src/components/task-page/job-list.tsx @@ -45,7 +45,7 @@ export default function JobListComponent(props: Props): JSX.Element {
{`Job #${id}`} {' | '} - Old + Legacy UI
), }, { 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 705859b121b3..4b545dd7a942 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 @@ -24,7 +24,7 @@ interface StateToProps { frameNumber: number; frameStep: number; frameSpeed: FrameSpeed; - frameChangeTime: number | null; + frameDelay: number; playing: boolean; saving: boolean; unsaved: boolean; @@ -39,7 +39,7 @@ interface StateToProps { } interface DispatchToProps { - onChangeFrame(frame: number, time: number | null): void; + onChangeFrame(frame: number): void; onSwitchPlay(playing: boolean): void; onSaveAnnotation(sessionInstance: any): void; showStatistics(sessionInstance: any): void; @@ -54,7 +54,7 @@ function mapStateToProps(state: CombinedState): StateToProps { playing, frame: { number: frameNumber, - changeTime: frameChangeTime, + delay: frameDelay, }, }, annotations: { @@ -89,7 +89,7 @@ function mapStateToProps(state: CombinedState): StateToProps { return { frameStep, frameSpeed, - frameChangeTime, + frameDelay, playing, canvasInstance, canvasIsReady, @@ -108,8 +108,8 @@ function mapStateToProps(state: CombinedState): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { - onChangeFrame(frame: number, time: number | null): void { - dispatch(changeFrameAsync(frame, time)); + onChangeFrame(frame: number): void { + dispatch(changeFrameAsync(frame)); }, onSwitchPlay(playing: boolean): void { dispatch(switchPlay(playing)); @@ -159,36 +159,33 @@ class AnnotationTopBarContainer extends React.PureComponent { const { jobInstance, frameNumber, - frameChangeTime, - frameSpeed, + frameDelay, playing, canvasIsReady, onSwitchPlay, - unsaved, } = this.props; if (playing && canvasIsReady) { if (frameNumber < jobInstance.stopFrame) { - const delay: number = frameChangeTime - ? Math.max(0, Math.round(1000 / frameSpeed) - - new Date().getTime() + frameChangeTime) : 0; setTimeout(() => { const { playing: stillPlaying } = this.props; if (stillPlaying) { - this.onChangeFrame(frameNumber + 1, new Date().getTime()); + this.onChangeFrame(frameNumber + 1); } - }, delay); + }, frameDelay); } else { onSwitchPlay(false); } } - if (unsaved) { - window.addEventListener('beforeunload', AnnotationTopBarContainer.beforeUnloadCallback); - } else { - window.removeEventListener('beforeunload', AnnotationTopBarContainer.beforeUnloadCallback); - } + jobInstance.annotations.hasUnsavedChanges().then((unsaved: boolean) => { + if (unsaved) { + window.addEventListener('beforeunload', AnnotationTopBarContainer.beforeUnloadCallback); + } else { + window.removeEventListener('beforeunload', AnnotationTopBarContainer.beforeUnloadCallback); + } + }); } public componentWillUnmount(): void { @@ -196,18 +193,19 @@ class AnnotationTopBarContainer extends React.PureComponent { window.removeEventListener('beforeunload', AnnotationTopBarContainer.beforeUnloadCallback); } - private onChangeFrame(newFrame: number, time: number | null = null): void { + private onChangeFrame(newFrame: number): void { const { canvasInstance, - canvasIsReady, onChangeFrame, resetZoom, } = this.props; - onChangeFrame(newFrame, time); + onChangeFrame(newFrame); - if (canvasIsReady && resetZoom) { - canvasInstance.fit(); + if (resetZoom) { + canvasInstance.html().addEventListener('canvas.setup', () => { + canvasInstance.fit(); + }, { once: true }); } } diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index 7b42cd298005..c0bb8124612d 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -36,7 +36,8 @@ const defaultState: AnnotationState = { number: 0, data: null, fetching: false, - changeTime: null + delay: 0, + changeTime: null, }, playing: false, }, @@ -162,7 +163,8 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { number, data, states, - frameChangeTime + delay, + changeTime, } = action.payload; const activatedStateID = states @@ -177,7 +179,8 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { data, number, fetching: false, - changeTime: frameChangeTime, + changeTime, + delay, }, }, annotations: { diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index f7ba149125d8..3cf7fc73292c 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -300,6 +300,7 @@ export interface AnnotationState { number: number; data: any | null; fetching: boolean; + delay: number; changeTime: number | null; }; playing: boolean; From 8c2fe43459c8e79f26ed99466ed440d41da4d61c Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 20 Feb 2020 15:18:34 +0300 Subject: [PATCH 07/18] Another PR fixes --- cvat-ui/src/containers/annotation-page/top-bar/top-bar.tsx | 3 --- cvat-ui/src/reducers/annotation-reducer.ts | 3 --- cvat-ui/src/reducers/interfaces.ts | 1 - 3 files changed, 7 deletions(-) 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 4b545dd7a942..107ad868417a 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 @@ -27,7 +27,6 @@ interface StateToProps { frameDelay: number; playing: boolean; saving: boolean; - unsaved: boolean; canvasInstance: Canvas; canvasIsReady: boolean; savingStatuses: string[]; @@ -63,7 +62,6 @@ function mapStateToProps(state: CombinedState): StateToProps { statuses: savingStatuses, }, history, - unsaved, }, job: { instance: jobInstance, @@ -95,7 +93,6 @@ function mapStateToProps(state: CombinedState): StateToProps { canvasIsReady, saving, savingStatuses, - unsaved, frameNumber, jobInstance, undoAction: history.undo[history.undo.length - 1], diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index c0bb8124612d..f4a125b88d82 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -60,7 +60,6 @@ const defaultState: AnnotationState = { undo: [], redo: [], }, - unsaved: false, }, propagate: { objectState: null, @@ -224,7 +223,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { ...state.annotations.saving, uploading: false, }, - unsaved: false, }, }; } @@ -361,7 +359,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { annotations: { ...state.annotations, activatedStateID: null, - unsaved: true, }, canvas: { ...state.canvas, diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 3cf7fc73292c..cea83459767a 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -326,7 +326,6 @@ export interface AnnotationState { uploading: boolean; statuses: string[]; }; - unsaved: boolean; }; propagate: { objectState: any | null; From 60f44776dc5a82fb48245f11ac87a9bdeff3a95d Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Fri, 21 Feb 2020 17:44:28 +0300 Subject: [PATCH 08/18] Fixed link to new job and unsaved changes message --- cvat-ui/src/actions/annotation-actions.ts | 7 +++ .../annotation-page/top-bar/top-bar.tsx | 2 + cvat-ui/src/components/task-page/job-list.tsx | 30 ++++++++-- .../objects-side-bar/object-item.tsx | 2 +- .../annotation-page/top-bar/top-bar.tsx | 60 +++++++++++++++---- cvat-ui/src/containers/task-page/job-list.tsx | 9 +++ cvat-ui/src/reducers/annotation-reducer.ts | 9 +++ 7 files changed, 101 insertions(+), 18 deletions(-) diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index caa86ba31c2b..38efcbbe3694 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -48,6 +48,7 @@ export enum AnnotationActionTypes { GET_JOB = 'GET_JOB', GET_JOB_SUCCESS = 'GET_JOB_SUCCESS', GET_JOB_FAILED = 'GET_JOB_FAILED', + REMOVE_JOB = 'REMOVE_JOB', CHANGE_FRAME = 'CHANGE_FRAME', CHANGE_FRAME_SUCCESS = 'CHANGE_FRAME_SUCCESS', CHANGE_FRAME_FAILED = 'CHANGE_FRAME_FAILED', @@ -702,6 +703,12 @@ export function getJobAsync( }; } +export function removeJob(): AnyAction { + return { + type: AnnotationActionTypes.REMOVE_JOB, + }; +} + export function saveAnnotationsAsync(sessionInstance: any): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { 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 06a1654171a3..eb61cde1868e 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 @@ -20,6 +20,7 @@ interface Props { frameNumber: number; startFrame: number; stopFrame: number; + annotations: any; undoAction?: string; redoAction?: string; showStatistics(): void; @@ -48,6 +49,7 @@ function AnnotationTopBarComponent(props: Props): JSX.Element { frameNumber, startFrame, stopFrame, + annotations, showStatistics, onSwitchPlay, onSaveAnnotation, diff --git a/cvat-ui/src/components/task-page/job-list.tsx b/cvat-ui/src/components/task-page/job-list.tsx index 658ea16c7306..1eaa527e545c 100644 --- a/cvat-ui/src/components/task-page/job-list.tsx +++ b/cvat-ui/src/components/task-page/job-list.tsx @@ -1,6 +1,7 @@ import React from 'react'; -import { Link } from 'react-router-dom'; +import { RouteComponentProps } from 'react-router'; +import { withRouter } from 'react-router-dom'; import { Row, @@ -26,14 +27,21 @@ const baseURL = core.config.backendAPI.slice(0, -7); interface Props { taskInstance: any; registeredUsers: any[]; + currentJobId: number | null; onJobUpdate(jobInstance: any): void; + onJobremove(): void; } -export default function JobListComponent(props: Props): JSX.Element { +function JobListComponent(props: Props & RouteComponentProps): JSX.Element { const { taskInstance, registeredUsers, onJobUpdate, + onJobremove, + currentJobId, + history: { + push, + }, } = props; const { jobs, id: taskId } = taskInstance; @@ -43,9 +51,19 @@ export default function JobListComponent(props: Props): JSX.Element { key: 'job', render: (id: number): JSX.Element => (
- {`Job #${id}`} - {' | '} - Legacy UI + + | +
), }, { @@ -176,3 +194,5 @@ export default function JobListComponent(props: Props): JSX.Element { ); } + +export default withRouter(JobListComponent); 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 7c299d16c3b2..f4baecdbec30 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 @@ -99,7 +99,7 @@ function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps { function mapDispatchToProps(dispatch: any): DispatchToProps { return { changeFrame(frame: number): void { - dispatch(changeFrameAsync(frame, null)); + dispatch(changeFrameAsync(frame)); }, updateState(sessionInstance: any, frameNumber: number, state: any): void { dispatch(updateAnnotationsAsync(sessionInstance, frameNumber, [state])); 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 107ad868417a..d239461ddccc 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 @@ -2,6 +2,9 @@ import React from 'react'; import copy from 'copy-to-clipboard'; import { connect } from 'react-redux'; +import { withRouter } from 'react-router'; +import { RouteComponentProps } from 'react-router-dom'; + import { Canvas } from 'cvat-canvas'; import { SliderValue } from 'antd/lib/slider'; @@ -127,7 +130,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { }; } -type Props = StateToProps & DispatchToProps; +type Props = StateToProps & DispatchToProps & RouteComponentProps; class AnnotationTopBarContainer extends React.PureComponent { private static beforeUnloadCallback(event: BeforeUnloadEvent): any { const confirmationMessage = 'You have unsaved changes, please confirm leaving this page.'; @@ -137,6 +140,7 @@ class AnnotationTopBarContainer extends React.PureComponent { } private autoSaveInterval: number | undefined; + private unblock: any; componentDidMount(): void { const { @@ -150,6 +154,8 @@ class AnnotationTopBarContainer extends React.PureComponent { this.onSaveAnnotation(); } }, autoSaveInterval); + + this.checkUnsavedChanges(); } public componentDidUpdate(): void { @@ -176,18 +182,16 @@ class AnnotationTopBarContainer extends React.PureComponent { } } - jobInstance.annotations.hasUnsavedChanges().then((unsaved: boolean) => { - if (unsaved) { - window.addEventListener('beforeunload', AnnotationTopBarContainer.beforeUnloadCallback); - } else { - window.removeEventListener('beforeunload', AnnotationTopBarContainer.beforeUnloadCallback); - } - }); + this.checkUnsavedChanges(); } public componentWillUnmount(): void { window.clearInterval(this.autoSaveInterval); window.removeEventListener('beforeunload', AnnotationTopBarContainer.beforeUnloadCallback); + if (typeof this.unblock === 'function') { + this.unblock(); + this.unblock = undefined; + } } private onChangeFrame(newFrame: number): void { @@ -403,6 +407,34 @@ class AnnotationTopBarContainer extends React.PureComponent { copy(url); }; + private checkUnsavedChanges(): void { + const { + jobInstance, + history, + } = this.props; + + jobInstance.annotations.hasUnsavedChanges().then((unsaved: boolean) => { + if (unsaved) { + window.addEventListener('beforeunload', AnnotationTopBarContainer.beforeUnloadCallback); + if (this.unblock === undefined) { + this.unblock = history.block((location) => { + if (location.pathname !== '/settings' && location.pathname + !== `/tasks/${jobInstance.task.id}/jobs/${jobInstance.id}`) { + return 'You have unsaved changes, please confirm leaving this page.'; + } + return undefined; + }); + } + } else { + window.removeEventListener('beforeunload', AnnotationTopBarContainer.beforeUnloadCallback); + if (typeof this.unblock === 'function') { + this.unblock(); + this.unblock = undefined; + } + } + }); + } + public render(): JSX.Element { const { playing, @@ -411,6 +443,7 @@ class AnnotationTopBarContainer extends React.PureComponent { jobInstance: { startFrame, stopFrame, + annotations, }, frameNumber, undoAction, @@ -437,6 +470,7 @@ class AnnotationTopBarContainer extends React.PureComponent { startFrame={startFrame} stopFrame={stopFrame} frameNumber={frameNumber} + annotations={annotations} undoAction={undoAction} redoAction={redoAction} onUndoClick={this.undo} @@ -446,7 +480,9 @@ class AnnotationTopBarContainer extends React.PureComponent { } } -export default connect( - mapStateToProps, - mapDispatchToProps, -)(AnnotationTopBarContainer); +export default withRouter( + connect( + mapStateToProps, + mapDispatchToProps, + )(AnnotationTopBarContainer), +); diff --git a/cvat-ui/src/containers/task-page/job-list.tsx b/cvat-ui/src/containers/task-page/job-list.tsx index 04ca7ecdc093..4734c66c8273 100644 --- a/cvat-ui/src/containers/task-page/job-list.tsx +++ b/cvat-ui/src/containers/task-page/job-list.tsx @@ -3,6 +3,7 @@ import { connect } from 'react-redux'; import JobListComponent from 'components/task-page/job-list'; import { updateJobAsync } from 'actions/tasks-actions'; +import { removeJob } from 'actions/annotation-actions'; import { Task, CombinedState, @@ -14,21 +15,25 @@ interface OwnProps { interface StateToProps { registeredUsers: any[]; + currentJobId: number | null; } interface DispatchToProps { onJobUpdate(jobInstance: any): void; + onJobRemove(): void; } function mapStateToProps(state: CombinedState): StateToProps { return { registeredUsers: state.users.users, + currentJobId: state.annotation.job.instance ? state.annotation.job.instance.id : null, }; } function mapDispatchToProps(dispatch: any): DispatchToProps { return { onJobUpdate: (jobInstance: any): void => dispatch(updateJobAsync(jobInstance)), + onJobRemove: (): void => dispatch(removeJob()), }; } @@ -37,13 +42,17 @@ function TaskPageContainer(props: StateToProps & DispatchToProps & OwnProps): JS task, registeredUsers, onJobUpdate, + currentJobId, + onJobRemove, } = props; return ( ); } diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index f4a125b88d82..47e4e2c9d880 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -141,6 +141,15 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } + case AnnotationActionTypes.REMOVE_JOB: { + return { + ...defaultState, + canvas: { + ...defaultState.canvas, + instance: new Canvas(), + }, + }; + } case AnnotationActionTypes.CHANGE_FRAME: { return { ...state, From 22b68216aaf74a5090a017a036ae47125f37eea6 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Tue, 25 Feb 2020 19:47:01 +0300 Subject: [PATCH 09/18] Fixed PR --- cvat-ui/src/actions/annotation-actions.ts | 40 +++++++----- .../annotation-page/annotation-page.tsx | 16 ++++- .../standard-workspace/canvas-wrapper.tsx | 8 +++ .../annotation-page/top-bar/top-bar.tsx | 2 - cvat-ui/src/components/task-page/job-list.tsx | 7 --- .../annotation-page/annotation-page.tsx | 2 +- .../standard-workspace/canvas-wrapper.tsx | 3 + .../annotation-page/top-bar/top-bar.tsx | 61 +++++++++---------- cvat-ui/src/containers/task-page/job-list.tsx | 9 --- cvat-ui/src/reducers/annotation-reducer.ts | 2 +- 10 files changed, 81 insertions(+), 69 deletions(-) diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 38efcbbe3694..c17ec81c600e 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -12,6 +12,7 @@ import { ShapeType, ObjectType, Task, + FrameSpeed, } from 'reducers/interfaces'; import getCore from 'cvat-core'; @@ -48,7 +49,7 @@ export enum AnnotationActionTypes { GET_JOB = 'GET_JOB', GET_JOB_SUCCESS = 'GET_JOB_SUCCESS', GET_JOB_FAILED = 'GET_JOB_FAILED', - REMOVE_JOB = 'REMOVE_JOB', + CLOSE_JOB = 'CLOSE_JOB', CHANGE_FRAME = 'CHANGE_FRAME', CHANGE_FRAME_SUCCESS = 'CHANGE_FRAME_SUCCESS', CHANGE_FRAME_FAILED = 'CHANGE_FRAME_FAILED', @@ -112,6 +113,7 @@ export enum AnnotationActionTypes { CHANGE_ANNOTATIONS_FILTERS = 'CHANGE_ANNOTATIONS_FILTERS', FETCH_ANNOTATIONS_SUCCESS = 'FETCH_ANNOTATIONS_SUCCESS', FETCH_ANNOTATIONS_FAILED = 'FETCH_ANNOTATIONS_FAILED', + ROTATE_FRAME = 'ROTATE_FRAME', } export function fetchAnnotationsAsync(sessionInstance: any): @@ -559,17 +561,12 @@ ThunkAction, {}, {}, AnyAction> { } if (toFrame === frame) { - const currentTime = new Date().getTime(); - const delay = Math.max(0, Math.round(1000 / state.settings.player.frameSpeed) - - currentTime + (state.annotation.player.frame.changeTime as number)); dispatch({ type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS, payload: { number: state.annotation.player.frame.number, data: state.annotation.player.frame.data, states: state.annotation.annotations.states, - changeTime: currentTime + delay, - delay, }, }); @@ -585,7 +582,21 @@ ThunkAction, {}, {}, AnyAction> { const data = await job.frames.get(toFrame); const states = await job.annotations.get(toFrame, showAllInterpolationTracks, filters); const currentTime = new Date().getTime(); - const delay = Math.max(0, Math.round(1000 / state.settings.player.frameSpeed) + let frameSpeed; + switch (state.settings.player.frameSpeed) { + case (FrameSpeed.Fast): { + frameSpeed = (FrameSpeed.Fast as number) / 2; + break; + } + case (FrameSpeed.Fastest): { + frameSpeed = (FrameSpeed.Fastest as number) / 3; + break; + } + default: { + frameSpeed = state.settings.player.frameSpeed as number; + } + } + const delay = Math.max(0, Math.round(1000 / frameSpeed) - currentTime + (state.annotation.player.frame.changeTime as number)); dispatch({ type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS, @@ -658,7 +669,14 @@ export function getJobAsync( const filters = initialFilters; const { showAllInterpolationTracks } = state.settings.workspace; - // First check state if the task is already there + // Check if already loaded job is different from asking one + if (state.annotation.job.instance && state.annotation.job.instance.id !== jid) { + dispatch({ + type: AnnotationActionTypes.CLOSE_JOB, + }); + } + + // Check state if the task is already there let task = state.tasks.current .filter((_task: Task) => _task.instance.id === tid) .map((_task: Task) => _task.instance)[0]; @@ -703,12 +721,6 @@ export function getJobAsync( }; } -export function removeJob(): AnyAction { - return { - type: AnnotationActionTypes.REMOVE_JOB, - }; -} - export function saveAnnotationsAsync(sessionInstance: any): ThunkAction, {}, {}, AnyAction> { return async (dispatch: ActionCreator): Promise => { diff --git a/cvat-ui/src/components/annotation-page/annotation-page.tsx b/cvat-ui/src/components/annotation-page/annotation-page.tsx index 82c5bcff8353..e5788c2acb86 100644 --- a/cvat-ui/src/components/annotation-page/annotation-page.tsx +++ b/cvat-ui/src/components/annotation-page/annotation-page.tsx @@ -1,6 +1,8 @@ import './styles.scss'; import React from 'react'; +import { RouteComponentProps } from 'react-router'; + import { Layout, Spin, @@ -17,14 +19,24 @@ interface Props { getJob(): void; } -export default function AnnotationPageComponent(props: Props): JSX.Element { +type RoutePros = RouteComponentProps<{ + tid: string; + jid: string; +}>; + +export default function AnnotationPageComponent(props: Props & RoutePros): JSX.Element { const { job, fetching, getJob, + match: { + params, + }, } = props; - if (job === null) { + const jid = +params.jid; + + if (job === null || (job !== undefined && job.id !== jid)) { if (!fetching) { getJob(); } 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 eca33c622efe..032cbc39002d 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 @@ -42,6 +42,7 @@ interface Props { brightnessLevel: number; contrastLevel: number; saturationLevel: number; + resetZoom: boolean; onSetupCanvas: () => void; onDragCanvas: (enabled: boolean) => void; onZoomCanvas: (enabled: boolean) => void; @@ -92,6 +93,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance, sidebarCollapsed, activatedStateID, + resetZoom, } = this.props; if (prevProps.sidebarCollapsed !== sidebarCollapsed) { @@ -146,6 +148,12 @@ export default class CanvasWrapperComponent extends React.PureComponent { this.updateShapesView(); } + if (prevProps.frame !== frameData.number && resetZoom) { + canvasInstance.html().addEventListener('canvas.setup', () => { + canvasInstance.fit(); + }, { once: true }); + } + this.activateOnCanvas(); } 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 eb61cde1868e..06a1654171a3 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 @@ -20,7 +20,6 @@ interface Props { frameNumber: number; startFrame: number; stopFrame: number; - annotations: any; undoAction?: string; redoAction?: string; showStatistics(): void; @@ -49,7 +48,6 @@ function AnnotationTopBarComponent(props: Props): JSX.Element { frameNumber, startFrame, stopFrame, - annotations, showStatistics, onSwitchPlay, onSaveAnnotation, diff --git a/cvat-ui/src/components/task-page/job-list.tsx b/cvat-ui/src/components/task-page/job-list.tsx index 1eaa527e545c..1388111bccb8 100644 --- a/cvat-ui/src/components/task-page/job-list.tsx +++ b/cvat-ui/src/components/task-page/job-list.tsx @@ -27,9 +27,7 @@ const baseURL = core.config.backendAPI.slice(0, -7); interface Props { taskInstance: any; registeredUsers: any[]; - currentJobId: number | null; onJobUpdate(jobInstance: any): void; - onJobremove(): void; } function JobListComponent(props: Props & RouteComponentProps): JSX.Element { @@ -37,8 +35,6 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element { taskInstance, registeredUsers, onJobUpdate, - onJobremove, - currentJobId, history: { push, }, @@ -54,9 +50,6 @@ function JobListComponent(props: Props & RouteComponentProps): JSX.Element { + | - + + + ), }, { From e6b6d29c5fff6be73c8a6a74d5104b608120185e Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 27 Feb 2020 15:31:47 +0300 Subject: [PATCH 15/18] Improved frames rotation --- cvat-canvas/src/typescript/canvasModel.ts | 21 +++++------ cvat-ui/src/actions/annotation-actions.ts | 27 ++++++++++++++ .../standard-workspace/canvas-wrapper.tsx | 12 +++++-- .../controls-side-bar/controls-side-bar.tsx | 6 ++-- .../controls-side-bar/rotate-control.tsx | 13 +++---- .../standard-workspace/canvas-wrapper.tsx | 3 ++ .../controls-side-bar/rotate-control.tsx | 35 +++++++++++++++++++ cvat-ui/src/reducers/annotation-reducer.ts | 13 +++++++ cvat-ui/src/reducers/interfaces.ts | 1 + 9 files changed, 106 insertions(+), 25 deletions(-) create mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 05e39139b2fd..46a8e8cc56ad 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -130,9 +130,9 @@ export interface CanvasModel { zoom(x: number, y: number, direction: number): void; move(topOffset: number, leftOffset: number): void; - setup(frameData: any, objectStates: any[]): void; + setup(frameData: any, objectStates: any[], frameAngle: number): void; activate(clientID: number | null, attributeID: number | null): void; - rotate(rotation: Rotation, remember: boolean): void; + rotate(rotation: Rotation): void; focus(clientID: number, padding: number): void; fit(): void; grid(stepX: number, stepY: number): void; @@ -163,7 +163,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { gridSize: Size; left: number; objects: any[]; - rememberAngle: boolean; scale: number; top: number; zLayer: number | null; @@ -205,7 +204,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { }, left: 0, objects: [], - rememberAngle: false, scale: 1, top: 0, zLayer: null, @@ -301,9 +299,13 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.notify(UpdateReasons.ZOOM_CANVAS); } - public setup(frameData: any, objectStates: any[]): void { + public setup(frameData: any, objectStates: any[], frameAngle: number): void { if (frameData.number === this.data.imageID) { this.data.objects = objectStates; + if (this.data.angle !== frameAngle) { + this.data.angle = frameAngle; + this.fit(); + } this.notify(UpdateReasons.OBJECTS_UPDATED); return; } @@ -320,10 +322,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { return; } - if (!this.data.rememberAngle) { - this.data.angle = 0; + if (this.data.angle !== frameAngle) { + this.data.angle = frameAngle; + this.fit(); } - this.data.imageSize = { height: (frameData.height as number), width: (frameData.width as number), @@ -352,7 +354,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.notify(UpdateReasons.SHAPE_ACTIVATED); } - public rotate(rotation: Rotation, remember: boolean = false): void { + public rotate(rotation: Rotation): void { if (rotation === Rotation.CLOCKWISE90) { this.data.angle += 90; } else { @@ -360,7 +362,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { } this.data.angle %= 360; - this.data.rememberAngle = remember; this.fit(); } diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 2279da3d6344..41e1a3779730 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -17,6 +17,7 @@ import { import getCore from 'cvat-core'; import { getCVATStore } from 'cvat-store'; +import { Rotation } from '../../../cvat-canvas/src/typescript/canvas'; const cvat = getCore(); let store: null | Store = null; @@ -124,6 +125,7 @@ export enum AnnotationActionTypes { CHANGE_ANNOTATIONS_FILTERS = 'CHANGE_ANNOTATIONS_FILTERS', FETCH_ANNOTATIONS_SUCCESS = 'FETCH_ANNOTATIONS_SUCCESS', FETCH_ANNOTATIONS_FAILED = 'FETCH_ANNOTATIONS_FAILED', + ROTATE_FRAME = 'ROTATE_FRAME', SWITCH_Z_LAYER = 'SWITCH_Z_LAYER', ADD_Z_LAYER = 'ADD_Z_LAYER', } @@ -661,6 +663,31 @@ ThunkAction, {}, {}, AnyAction> { }; } + +export function rotateCurrentFrame(angle: Rotation): AnyAction { + const state: CombinedState = getStore().getState(); + const { number: frameNumber } = state.annotation.player.frame; + const { rotateAll } = state.settings.player; + const previousFrameAngle = state.annotation.player.frameAngles[frameNumber + - state.annotation.job.instance.startFrame]; + let frameAngle = previousFrameAngle; + if (angle === Rotation.CLOCKWISE90) { + frameAngle += 90; + } else { + frameAngle += 270; + } + frameAngle %= 360; + + return { + type: AnnotationActionTypes.ROTATE_FRAME, + payload: { + frame: frameNumber - state.annotation.job.instance.startFrame, + angle: frameAngle, + rotateAll, + }, + }; +} + export function dragCanvas(enabled: boolean): AnyAction { return { type: AnnotationActionTypes.DRAG_CANVAS, 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 ae69dd1744cd..cb2cb2771bf5 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 @@ -33,6 +33,7 @@ interface Props { selectedStatesID: number[]; annotations: any[]; frameData: any; + frameAngles: number[]; frame: number; opacity: number; colorBy: ColorBy; @@ -101,6 +102,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { gridColor, gridOpacity, frameData, + frameAngles, annotations, canvasInstance, sidebarCollapsed, @@ -152,7 +154,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { } } - if (prevProps.annotations !== annotations || prevProps.frameData !== frameData) { + if (prevProps.annotations !== annotations || prevProps.frameData !== frameData + || prevProps.frameAngles !== frameAngles) { this.updateCanvas(); } @@ -326,12 +329,17 @@ export default class CanvasWrapperComponent extends React.PureComponent { private updateCanvas(): void { const { annotations, + frame, frameData, + frameAngles, canvasInstance, + jobInstance, } = this.props; + const frameAngle = frameAngles[frame - jobInstance.startFrame]; + if (frameData !== null) { - canvasInstance.setup(frameData, annotations); + canvasInstance.setup(frameData, annotations, frameAngle); } } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx index 0870b53f3e2e..6d865b06c033 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -18,9 +18,9 @@ import { Canvas, } from 'cvat-canvas'; +import RotateControl from 'containers/annotation-page/standard-workspace/controls-side-bar/rotate-control'; import CursorControl from './cursor-control'; import MoveControl from './move-control'; -import RotateControl from './rotate-control'; import FitControl from './fit-control'; import ResizeControl from './resize-control'; import DrawRectangleControl from './draw-rectangle-control'; @@ -33,7 +33,6 @@ import SplitControl from './split-control'; interface Props { canvasInstance: Canvas; - rotateAll: boolean; activeControl: ActiveControl; mergeObjects(enabled: boolean): void; @@ -45,7 +44,6 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { const { canvasInstance, activeControl, - rotateAll, mergeObjects, groupObjects, @@ -60,7 +58,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { > - +
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx index 0d94eafa5982..737e8717594b 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx @@ -12,18 +12,15 @@ import { import { Rotation, - Canvas, } from 'cvat-canvas'; interface Props { - canvasInstance: Canvas; - rotateAll: boolean; + rotateFrame(angle: Rotation): void; } function RotateControl(props: Props): JSX.Element { const { - rotateAll, - canvasInstance, + rotateFrame, } = props; return ( @@ -35,16 +32,14 @@ function RotateControl(props: Props): JSX.Element { canvasInstance - .rotate(Rotation.ANTICLOCKWISE90, rotateAll)} + onClick={(): void => rotateFrame(Rotation.ANTICLOCKWISE90)} component={RotateIcon} /> canvasInstance - .rotate(Rotation.CLOCKWISE90, rotateAll)} + onClick={(): void => rotateFrame(Rotation.CLOCKWISE90)} component={RotateIcon} /> 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 ea0dd7c1e306..8dd79a32d337 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 @@ -41,6 +41,7 @@ interface StateToProps { selectedStatesID: number[]; annotations: any[]; frameData: any; + frameAngles: number[]; frame: number; opacity: number; colorBy: ColorBy; @@ -101,6 +102,7 @@ function mapStateToProps(state: CombinedState): StateToProps { data: frameData, number: frame, }, + frameAngles, }, annotations: { states: annotations, @@ -139,6 +141,7 @@ function mapStateToProps(state: CombinedState): StateToProps { canvasInstance, jobInstance, frameData, + frameAngles, frame, activatedStateID, selectedStatesID, diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx new file mode 100644 index 000000000000..65cdca2a8bc8 --- /dev/null +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { connect } from 'react-redux'; + +import { rotateCurrentFrame } from 'actions/annotation-actions'; + +import RotateControlComponent from 'components/annotation-page/standard-workspace/controls-side-bar/rotate-control'; + +import { + Rotation, +} from 'cvat-canvas'; + +interface DispatchToProps { + rotateFrame(angle: Rotation): void; +} + +function mapDispatchToProps(dispatch: any): DispatchToProps { + return { + rotateFrame(angle: Rotation): void { + dispatch(rotateCurrentFrame(angle)); + }, + }; +} + +class RotateControlContainer extends React.PureComponent { + public render(): JSX.Element { + return ( + + ); + } +} + +export default connect( + null, + mapDispatchToProps, +)(RotateControlContainer); diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index c3d642047258..d8ad1c09b1c0 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -40,6 +40,7 @@ const defaultState: AnnotationState = { changeTime: null, }, playing: false, + frameAngles: [], }, drawing: { activeShapeType: ShapeType.RECTANGLE, @@ -134,6 +135,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { number, data, }, + frameAngles: Array(job.stopFrame - job.startFrame).fill(0), }, drawing: { ...state.drawing, @@ -229,6 +231,17 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } + case AnnotationActionTypes.ROTATE_FRAME: { + const { frame, angle, rotateAll } = action.payload; + return { + ...state, + player: { + ...state.player, + frameAngles: state.player.frameAngles.map((_angle: number, idx: number) => ( + rotateAll || frame === idx ? angle : _angle)), + }, + }; + } case AnnotationActionTypes.SAVE_ANNOTATIONS: { return { ...state, diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index ee0b80c8a3e8..31dbfb1f121e 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -309,6 +309,7 @@ export interface AnnotationState { changeTime: number | null; }; playing: boolean; + frameAngles: number[]; }; drawing: { activeShapeType: ShapeType; From 93405499e23386be09070729029cc537af2ef4e3 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 27 Feb 2020 15:33:08 +0300 Subject: [PATCH 16/18] Improved frames rotation --- cvat-canvas/src/typescript/canvas.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cvat-canvas/src/typescript/canvas.ts b/cvat-canvas/src/typescript/canvas.ts index 94d5c226f0e7..45d9b49f41dc 100644 --- a/cvat-canvas/src/typescript/canvas.ts +++ b/cvat-canvas/src/typescript/canvas.ts @@ -35,9 +35,9 @@ const CanvasVersion = pjson.version; interface Canvas { html(): HTMLDivElement; setZLayer(zLayer: number | null): void; - setup(frameData: any, objectStates: any[]): void; + setup(frameData: any, objectStates: any[], frameAngle: number): void; activate(clientID: number | null, attributeID?: number): void; - rotate(rotation: Rotation, remember?: boolean): void; + rotate(rotation: Rotation): void; focus(clientID: number, padding?: number): void; fit(): void; grid(stepX: number, stepY: number): void; @@ -74,8 +74,8 @@ class CanvasImpl implements Canvas { this.model.setZLayer(zLayer); } - public setup(frameData: any, objectStates: any[]): void { - this.model.setup(frameData, objectStates); + public setup(frameData: any, objectStates: any[], frameAngle: number): void { + this.model.setup(frameData, objectStates, frameAngle); } public fitCanvas(): void { @@ -97,8 +97,8 @@ class CanvasImpl implements Canvas { this.model.activate(clientID, attributeID); } - public rotate(rotation: Rotation, remember: boolean = false): void { - this.model.rotate(rotation, remember); + public rotate(rotation: Rotation): void { + this.model.rotate(rotation); } public focus(clientID: number, padding: number = 0): void { From 80e8e54a37a9e7e5f372eb05c3233172cf18c84d Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 27 Feb 2020 17:30:20 +0300 Subject: [PATCH 17/18] Fixed PR --- cvat-canvas/src/typescript/canvas.ts | 14 ++++---- cvat-canvas/src/typescript/canvasModel.ts | 31 ++++------------ cvat-ui/src/actions/annotation-actions.ts | 20 +++++------ .../standard-workspace/canvas-wrapper.tsx | 24 +++++++++---- .../controls-side-bar/controls-side-bar.tsx | 7 ++-- .../controls-side-bar/rotate-control.tsx | 4 +-- .../controls-side-bar/controls-side-bar.tsx | 6 ++++ .../controls-side-bar/rotate-control.tsx | 35 ------------------- cvat-ui/src/cvat-canvas.ts | 2 -- cvat-ui/src/reducers/annotation-reducer.ts | 4 +-- cvat-ui/src/reducers/interfaces.ts | 5 +++ 11 files changed, 59 insertions(+), 93 deletions(-) delete mode 100644 cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx diff --git a/cvat-canvas/src/typescript/canvas.ts b/cvat-canvas/src/typescript/canvas.ts index 45d9b49f41dc..9f3911ce77ed 100644 --- a/cvat-canvas/src/typescript/canvas.ts +++ b/cvat-canvas/src/typescript/canvas.ts @@ -4,7 +4,6 @@ */ import { - Rotation, DrawData, MergeData, SplitData, @@ -35,9 +34,9 @@ const CanvasVersion = pjson.version; interface Canvas { html(): HTMLDivElement; setZLayer(zLayer: number | null): void; - setup(frameData: any, objectStates: any[], frameAngle: number): void; + setup(frameData: any, objectStates: any[]): void; activate(clientID: number | null, attributeID?: number): void; - rotate(rotation: Rotation): void; + rotate(rotationAngle: number): void; focus(clientID: number, padding?: number): void; fit(): void; grid(stepX: number, stepY: number): void; @@ -74,8 +73,8 @@ class CanvasImpl implements Canvas { this.model.setZLayer(zLayer); } - public setup(frameData: any, objectStates: any[], frameAngle: number): void { - this.model.setup(frameData, objectStates, frameAngle); + public setup(frameData: any, objectStates: any[]): void { + this.model.setup(frameData, objectStates); } public fitCanvas(): void { @@ -97,8 +96,8 @@ class CanvasImpl implements Canvas { this.model.activate(clientID, attributeID); } - public rotate(rotation: Rotation): void { - this.model.rotate(rotation); + public rotate(rotationAngle: number): void { + this.model.rotate(rotationAngle); } public focus(clientID: number, padding: number = 0): void { @@ -140,6 +139,5 @@ class CanvasImpl implements Canvas { export { CanvasImpl as Canvas, - Rotation, CanvasVersion, }; diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 46a8e8cc56ad..a8efa0040d1e 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -69,11 +69,6 @@ export enum FrameZoom { MAX = 10, } -export enum Rotation { - ANTICLOCKWISE90, - CLOCKWISE90, -} - export enum UpdateReasons { IMAGE_CHANGED = 'image_changed', IMAGE_ZOOMED = 'image_zoomed', @@ -130,9 +125,9 @@ export interface CanvasModel { zoom(x: number, y: number, direction: number): void; move(topOffset: number, leftOffset: number): void; - setup(frameData: any, objectStates: any[], frameAngle: number): void; + setup(frameData: any, objectStates: any[]): void; activate(clientID: number | null, attributeID: number | null): void; - rotate(rotation: Rotation): void; + rotate(rotationAngle: number): void; focus(clientID: number, padding: number): void; fit(): void; grid(stepX: number, stepY: number): void; @@ -299,13 +294,9 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.notify(UpdateReasons.ZOOM_CANVAS); } - public setup(frameData: any, objectStates: any[], frameAngle: number): void { + public setup(frameData: any, objectStates: any[]): void { if (frameData.number === this.data.imageID) { this.data.objects = objectStates; - if (this.data.angle !== frameAngle) { - this.data.angle = frameAngle; - this.fit(); - } this.notify(UpdateReasons.OBJECTS_UPDATED); return; } @@ -322,10 +313,6 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { return; } - if (this.data.angle !== frameAngle) { - this.data.angle = frameAngle; - this.fit(); - } this.data.imageSize = { height: (frameData.height as number), width: (frameData.width as number), @@ -354,15 +341,11 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.notify(UpdateReasons.SHAPE_ACTIVATED); } - public rotate(rotation: Rotation): void { - if (rotation === Rotation.CLOCKWISE90) { - this.data.angle += 90; - } else { - this.data.angle -= 90; + public rotate(rotationAngle: number): void { + if (this.data.angle !== rotationAngle) { + this.data.angle = rotationAngle; + this.fit(); } - - this.data.angle %= 360; - this.fit(); } public focus(clientID: number, padding: number): void { diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 41e1a3779730..02e54784f8e2 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -13,11 +13,11 @@ import { ObjectType, Task, FrameSpeed, + Rotation, } from 'reducers/interfaces'; import getCore from 'cvat-core'; import { getCVATStore } from 'cvat-store'; -import { Rotation } from '../../../cvat-canvas/src/typescript/canvas'; const cvat = getCore(); let store: null | Store = null; @@ -664,24 +664,20 @@ ThunkAction, {}, {}, AnyAction> { } -export function rotateCurrentFrame(angle: Rotation): AnyAction { +export function rotateCurrentFrame(rotation: Rotation): AnyAction { const state: CombinedState = getStore().getState(); const { number: frameNumber } = state.annotation.player.frame; + const { startFrame } = state.annotation.job.instance; + const { frameAngles } = state.annotation.player; const { rotateAll } = state.settings.player; - const previousFrameAngle = state.annotation.player.frameAngles[frameNumber - - state.annotation.job.instance.startFrame]; - let frameAngle = previousFrameAngle; - if (angle === Rotation.CLOCKWISE90) { - frameAngle += 90; - } else { - frameAngle += 270; - } - frameAngle %= 360; + + const frameAngle = (frameAngles[frameNumber - startFrame] + + (rotation === Rotation.CLOCKWISE90 ? 90 : 270)) % 360; return { type: AnnotationActionTypes.ROTATE_FRAME, payload: { - frame: frameNumber - state.annotation.job.instance.startFrame, + offset: frameNumber - state.annotation.job.instance.startFrame, angle: frameAngle, rotateAll, }, 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 cb2cb2771bf5..bc61c5ca50e9 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 @@ -154,8 +154,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { } } - if (prevProps.annotations !== annotations || prevProps.frameData !== frameData - || prevProps.frameAngles !== frameAngles) { + if (prevProps.annotations !== annotations || prevProps.frameData !== frameData) { this.updateCanvas(); } @@ -174,6 +173,10 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.setZLayer(curZLayer); } + if (prevProps.frameAngles !== frameAngles) { + this.rotate(); + } + this.activateOnCanvas(); } @@ -329,8 +332,19 @@ export default class CanvasWrapperComponent extends React.PureComponent { private updateCanvas(): void { const { annotations, - frame, frameData, + canvasInstance, + } = this.props; + + if (frameData !== null) { + canvasInstance.setup(frameData, annotations); + this.rotate(); + } + } + + private rotate(): void { + const { + frame, frameAngles, canvasInstance, jobInstance, @@ -338,9 +352,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { const frameAngle = frameAngles[frame - jobInstance.startFrame]; - if (frameData !== null) { - canvasInstance.setup(frameData, annotations, frameAngle); - } + canvasInstance.rotate(frameAngle); } private initialSetup(): void { diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx index 6d865b06c033..45aed3337579 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -8,6 +8,7 @@ import { import { ActiveControl, + Rotation } from 'reducers/interfaces'; import { @@ -18,7 +19,7 @@ import { Canvas, } from 'cvat-canvas'; -import RotateControl from 'containers/annotation-page/standard-workspace/controls-side-bar/rotate-control'; +import RotateControl from './rotate-control'; import CursorControl from './cursor-control'; import MoveControl from './move-control'; import FitControl from './fit-control'; @@ -38,6 +39,7 @@ interface Props { mergeObjects(enabled: boolean): void; groupObjects(enabled: boolean): void; splitTrack(enabled: boolean): void; + rotateFrame(rotation: Rotation): void; } export default function ControlsSideBarComponent(props: Props): JSX.Element { @@ -48,6 +50,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { mergeObjects, groupObjects, splitTrack, + rotateFrame, } = props; return ( @@ -58,7 +61,7 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { > - +
diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx index 737e8717594b..e84ec047bc7e 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx @@ -12,10 +12,10 @@ import { import { Rotation, -} from 'cvat-canvas'; +} from 'reducers/interfaces'; interface Props { - rotateFrame(angle: Rotation): void; + rotateFrame(rotation: Rotation): void; } function RotateControl(props: Props): JSX.Element { diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx index b59375e495b9..4792af67843d 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -7,11 +7,13 @@ import { mergeObjects, groupObjects, splitTrack, + rotateCurrentFrame, } from 'actions/annotation-actions'; import ControlsSideBarComponent from 'components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar'; import { ActiveControl, CombinedState, + Rotation, } from 'reducers/interfaces'; interface StateToProps { @@ -24,6 +26,7 @@ interface DispatchToProps { mergeObjects(enabled: boolean): void; groupObjects(enabled: boolean): void; splitTrack(enabled: boolean): void; + rotateFrame(angle: Rotation): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -59,6 +62,9 @@ function dispatchToProps(dispatch: any): DispatchToProps { splitTrack(enabled: boolean): void { dispatch(splitTrack(enabled)); }, + rotateFrame(rotation: Rotation): void { + dispatch(rotateCurrentFrame(rotation)); + }, }; } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx deleted file mode 100644 index 65cdca2a8bc8..000000000000 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/rotate-control.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; - -import { rotateCurrentFrame } from 'actions/annotation-actions'; - -import RotateControlComponent from 'components/annotation-page/standard-workspace/controls-side-bar/rotate-control'; - -import { - Rotation, -} from 'cvat-canvas'; - -interface DispatchToProps { - rotateFrame(angle: Rotation): void; -} - -function mapDispatchToProps(dispatch: any): DispatchToProps { - return { - rotateFrame(angle: Rotation): void { - dispatch(rotateCurrentFrame(angle)); - }, - }; -} - -class RotateControlContainer extends React.PureComponent { - public render(): JSX.Element { - return ( - - ); - } -} - -export default connect( - null, - mapDispatchToProps, -)(RotateControlContainer); diff --git a/cvat-ui/src/cvat-canvas.ts b/cvat-ui/src/cvat-canvas.ts index f7cfacdb7518..987df6d43fcf 100644 --- a/cvat-ui/src/cvat-canvas.ts +++ b/cvat-ui/src/cvat-canvas.ts @@ -1,11 +1,9 @@ import { Canvas, - Rotation, CanvasVersion, } from '../../cvat-canvas/src/typescript/canvas'; export { Canvas, - Rotation, CanvasVersion, }; diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index d8ad1c09b1c0..a201787c9d07 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -232,13 +232,13 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }; } case AnnotationActionTypes.ROTATE_FRAME: { - const { frame, angle, rotateAll } = action.payload; + const { offset, angle, rotateAll } = action.payload; return { ...state, player: { ...state.player, frameAngles: state.player.frameAngles.map((_angle: number, idx: number) => ( - rotateAll || frame === idx ? angle : _angle)), + rotateAll || offset === idx ? angle : _angle)), }, }; } diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 31dbfb1f121e..eade676bb67e 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -276,6 +276,11 @@ export enum ContextMenuType { CANVAS_SHAPE = 'canvas_shape', } +export enum Rotation { + ANTICLOCKWISE90, + CLOCKWISE90, +} + export interface AnnotationState { activities: { loads: { From 28cc0286a5505283f232b45e9e6ebd720e143cbd Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Fri, 28 Feb 2020 18:23:46 +0300 Subject: [PATCH 18/18] Minor fixes and README update --- cvat-canvas/README.md | 9 ++----- cvat-canvas/src/typescript/canvasModel.ts | 2 +- .../standard-workspace/canvas-wrapper.tsx | 24 +++++-------------- .../standard-workspace/canvas-wrapper.tsx | 4 ++-- cvat-ui/src/reducers/annotation-reducer.ts | 2 +- 5 files changed, 12 insertions(+), 29 deletions(-) diff --git a/cvat-canvas/README.md b/cvat-canvas/README.md index a52744332652..32ebf5cc61bc 100644 --- a/cvat-canvas/README.md +++ b/cvat-canvas/README.md @@ -32,11 +32,6 @@ Canvas itself handles: ### API Methods ```ts - enum Rotation { - ANTICLOCKWISE90, - CLOCKWISE90, - } - interface DrawData { enabled: boolean; shapeType?: string; @@ -74,7 +69,7 @@ Canvas itself handles: setZLayer(zLayer: number | null): void; setup(frameData: any, objectStates: any[]): void; activate(clientID: number, attributeID?: number): void; - rotate(rotation: Rotation, remember?: boolean): void; + rotate(frameAngle: number): void; focus(clientID: number, padding?: number): void; fit(): void; grid(stepX: number, stepY: number): void; @@ -142,7 +137,7 @@ Standard JS events are used. canvas.fitCanvas(); // Next you can use its API methods. For example: - canvas.rotate(window.Canvas.Rotation.CLOCKWISE90); + canvas.rotate(270); canvas.draw({ enabled: true, shapeType: 'rectangle', diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index a8efa0040d1e..2f133d5c2f3e 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -343,7 +343,7 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { public rotate(rotationAngle: number): void { if (this.data.angle !== rotationAngle) { - this.data.angle = rotationAngle; + this.data.angle = (360 + Math.floor((rotationAngle) / 90) * 90) % 360; this.fit(); } } 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 bc61c5ca50e9..36a94dc014a4 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 @@ -33,7 +33,7 @@ interface Props { selectedStatesID: number[]; annotations: any[]; frameData: any; - frameAngles: number[]; + frameAngle: number; frame: number; opacity: number; colorBy: ColorBy; @@ -102,7 +102,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { gridColor, gridOpacity, frameData, - frameAngles, + frameAngle, annotations, canvasInstance, sidebarCollapsed, @@ -173,8 +173,8 @@ export default class CanvasWrapperComponent extends React.PureComponent { canvasInstance.setZLayer(curZLayer); } - if (prevProps.frameAngles !== frameAngles) { - this.rotate(); + if (prevProps.frameAngle !== frameAngle) { + canvasInstance.rotate(frameAngle); } this.activateOnCanvas(); @@ -333,28 +333,16 @@ export default class CanvasWrapperComponent extends React.PureComponent { const { annotations, frameData, + frameAngle, canvasInstance, } = this.props; if (frameData !== null) { canvasInstance.setup(frameData, annotations); - this.rotate(); + canvasInstance.rotate(frameAngle); } } - private rotate(): void { - const { - frame, - frameAngles, - canvasInstance, - jobInstance, - } = this.props; - - const frameAngle = frameAngles[frame - jobInstance.startFrame]; - - canvasInstance.rotate(frameAngle); - } - private initialSetup(): void { const { grid, 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 8dd79a32d337..d3f07602b20d 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 @@ -41,7 +41,7 @@ interface StateToProps { selectedStatesID: number[]; annotations: any[]; frameData: any; - frameAngles: number[]; + frameAngle: number; frame: number; opacity: number; colorBy: ColorBy; @@ -141,7 +141,7 @@ function mapStateToProps(state: CombinedState): StateToProps { canvasInstance, jobInstance, frameData, - frameAngles, + frameAngle: frameAngles[frame - jobInstance.startFrame], frame, activatedStateID, selectedStatesID, diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index a201787c9d07..93149ee3a539 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -135,7 +135,7 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { number, data, }, - frameAngles: Array(job.stopFrame - job.startFrame).fill(0), + frameAngles: Array(job.stopFrame - job.startFrame + 1).fill(0), }, drawing: { ...state.drawing,