From bac55296212a412808e76feb0f51582a87e218d0 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Mon, 2 Mar 2020 18:07:41 +0300 Subject: [PATCH 01/26] Initial version of shortcuts --- cvat-ui/package-lock.json | 10 ++- cvat-ui/package.json | 1 + cvat-ui/src/actions/shortcuts-actions.ts | 13 ++++ .../annotation-page/top-bar/top-bar.tsx | 24 ++++++- cvat-ui/src/components/cvat-app.tsx | 66 +++++++++++++---- .../shortcuts-dialog/shortcuts-dialog.tsx | 71 +++++++++++++++++++ cvat-ui/src/index.tsx | 13 ++-- cvat-ui/src/reducers/interfaces.ts | 5 ++ cvat-ui/src/reducers/root-reducer.ts | 2 + cvat-ui/src/reducers/shortcuts-reducer.ts | 27 +++++++ 10 files changed, 209 insertions(+), 23 deletions(-) create mode 100644 cvat-ui/src/actions/shortcuts-actions.ts create mode 100644 cvat-ui/src/components/shortcuts-dialog/shortcuts-dialog.tsx create mode 100644 cvat-ui/src/reducers/shortcuts-reducer.ts diff --git a/cvat-ui/package-lock.json b/cvat-ui/package-lock.json index 3ff854fe28d..828118cfaaf 100644 --- a/cvat-ui/package-lock.json +++ b/cvat-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "0.1.0", + "version": "0.5.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -9734,6 +9734,14 @@ "scheduler": "^0.17.0" } }, + "react-hotkeys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-hotkeys/-/react-hotkeys-2.0.0.tgz", + "integrity": "sha512-3n3OU8vLX/pfcJrR3xJ1zlww6KS1kEJt0Whxc4FiGV+MJrQ1mYSYI3qS/11d2MJDFm8IhOXMTFQirfu6AVOF6Q==", + "requires": { + "prop-types": "^15.6.1" + } + }, "react-is": { "version": "16.11.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.11.0.tgz", diff --git a/cvat-ui/package.json b/cvat-ui/package.json index 9c25aa8f6d9..60f6aa30c61 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -61,6 +61,7 @@ "prop-types": "^15.7.2", "react": "^16.9.0", "react-dom": "^16.9.0", + "react-hotkeys": "^2.0.0", "react-redux": "^7.1.1", "react-router": "^5.1.0", "react-router-dom": "^5.1.0", diff --git a/cvat-ui/src/actions/shortcuts-actions.ts b/cvat-ui/src/actions/shortcuts-actions.ts new file mode 100644 index 00000000000..6ecd30d046f --- /dev/null +++ b/cvat-ui/src/actions/shortcuts-actions.ts @@ -0,0 +1,13 @@ +import { ActionUnion, createAction } from 'utils/redux'; + +export enum ShortcutsActionsTypes { + SHOW_SHORTCUTS_HELP = 'SHOW_SHORTCUTS_HELP', + HIDE_SHORTCUTS_HELP = 'HIDE_SHORTCUTS_HELP', +} + +export const shortcutsActions = { + showShortcutsHelp: () => createAction(ShortcutsActionsTypes.SHOW_SHORTCUTS_HELP), + hideShortcutsHelp: () => createAction(ShortcutsActionsTypes.HIDE_SHORTCUTS_HELP), +}; + +export type ShortcutsActions = ActionUnion; 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 2c51efb41ea..09d03a9ce99 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 @@ -3,6 +3,7 @@ // SPDX-License-Identifier: MIT import React from 'react'; +import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; import { Row, @@ -42,7 +43,7 @@ interface Props { onRedoClick(): void; } -function AnnotationTopBarComponent(props: Props): JSX.Element { +export default function AnnotationTopBarComponent(props: Props): JSX.Element { const { saving, savingStatuses, @@ -68,8 +69,27 @@ function AnnotationTopBarComponent(props: Props): JSX.Element { onRedoClick, } = props; + const keyMap = { + SAVE_JOB: { + name: 'Save the job', + description: 'Send all changes of annotations to the server', + sequence: 'ctrl+s', + action: 'keydown', + }, + }; + + const handlers = { + SAVE_JOB: (event: KeyboardEvent | undefined) => { + if (event) { + event.preventDefault(); + } + onSaveAnnotation(); + }, + }; + return ( + ); } - -export default React.memo(AnnotationTopBarComponent); diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index b696c048d90..0214e127681 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -5,6 +5,7 @@ import 'antd/dist/antd.less'; import '../styles.scss'; import React from 'react'; +import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; import { BrowserRouter } from 'react-router-dom'; import { Switch, @@ -17,6 +18,7 @@ import { notification, } from 'antd'; +import ShorcutsDialog from 'components/shortcuts-dialog/shortcuts-dialog'; import SettingsPageContainer from 'containers/settings-page/settings-page'; import TasksPageContainer from 'containers/tasks-page/tasks-page'; import CreateTaskPageContainer from 'containers/create-task-page/create-task-page'; @@ -38,6 +40,8 @@ type CVATAppProps = { initPlugins: () => void; resetErrors: () => void; resetMessages: () => void; + showShortcutsHelp: () => void; + hideShortcutsHelp: () => void; userInitialized: boolean; pluginsInitialized: boolean; pluginsFetching: boolean; @@ -191,6 +195,8 @@ export default class CVATApplication extends React.PureComponent { installedTFSegmentation, installedTFAnnotation, user, + showShortcutsHelp, + hideShortcutsHelp, } = this.props; const readyForRender = (userInitialized && user == null) @@ -200,6 +206,36 @@ export default class CVATApplication extends React.PureComponent { const withModels = installedAutoAnnotation || installedTFAnnotation || installedTFSegmentation; + const keyMap = { + SHOW_SHORTCUTS: { + name: 'Show shortcuts', + description: 'Open a list of available shortcuts', + sequence: 'f1', + action: 'keydown', + }, + HIDE_SHORTCUTS: { + name: 'Hide shortcuts', + description: 'Close the list of available shortcuts', + sequence: 'f1', + action: 'keyup', + }, + }; + + const handlers = { + SHOW_SHORTCUTS: (event: KeyboardEvent | undefined) => { + if (event) { + event.preventDefault(); + } + showShortcutsHelp(); + }, + HIDE_SHORTCUTS: (event: KeyboardEvent | undefined) => { + if (event) { + event.preventDefault(); + } + hideShortcutsHelp(); + }, + }; + if (readyForRender) { if (user) { return ( @@ -207,18 +243,24 @@ export default class CVATApplication extends React.PureComponent { - - - - - - - { withModels - && } - { installedAutoAnnotation - && } - - + + + + + + + + + { withModels + && } + { installedAutoAnnotation + && } + + + {/* eslint-disable-next-line */} diff --git a/cvat-ui/src/components/shortcuts-dialog/shortcuts-dialog.tsx b/cvat-ui/src/components/shortcuts-dialog/shortcuts-dialog.tsx new file mode 100644 index 00000000000..21fb7db342f --- /dev/null +++ b/cvat-ui/src/components/shortcuts-dialog/shortcuts-dialog.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { getApplicationKeyMap } from 'react-hotkeys'; +import { Modal, Table } from 'antd'; +import { connect } from 'react-redux'; +import { CombinedState } from 'reducers/interfaces'; + +interface StateToProps { + visible: boolean; +} + +function mapStateToProps(state: CombinedState): StateToProps { + const { + shortcuts: { + visibleShortcutsHelp: visible, + }, + } = state; + + return { + visible, + }; +} + +function ShorcutsDialog(props: StateToProps): JSX.Element | null { + const { visible } = props; + const keyMap = getApplicationKeyMap(); + + const columns = [{ + title: 'Name', + dataIndex: 'name', + key: 'name', + }, { + title: 'Shorcut', + dataIndex: 'shortcut', + key: 'shortcut', + }, { + title: 'Action', + dataIndex: 'action', + key: 'action', + }, { + title: 'Description', + dataIndex: 'description', + key: 'description', + }]; + + const dataSource = Object.keys(keyMap).map((key: string, id: number) => ({ + key: id, + name: keyMap[key].name || key, + description: keyMap[key].description || '', + shortcut: keyMap[key].sequences.map((value) => value.sequence) + .join('\n'), + action: keyMap[key].sequences.map((value) => value.action || 'keydown') + .join('\n'), + })); + + return ( + + + + ); +} + +export default connect( + mapStateToProps, +)(ShorcutsDialog); diff --git a/cvat-ui/src/index.tsx b/cvat-ui/src/index.tsx index 9dd8e219092..ee1bff04ab0 100644 --- a/cvat-ui/src/index.tsx +++ b/cvat-ui/src/index.tsx @@ -16,6 +16,7 @@ import { getFormatsAsync } from './actions/formats-actions'; import { checkPluginsAsync } from './actions/plugins-actions'; import { getUsersAsync } from './actions/users-actions'; import { getAboutAsync } from './actions/about-actions'; +import { shortcutsActions } from './actions/shortcuts-actions'; import { resetErrors, resetMessages, @@ -54,6 +55,8 @@ interface DispatchToProps { initPlugins: () => void; resetErrors: () => void; resetMessages: () => void; + showShortcutsHelp: () => void; + hideShortcutsHelp: () => void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -90,19 +93,15 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { loadAbout: (): void => dispatch(getAboutAsync()), resetErrors: (): void => dispatch(resetErrors()), resetMessages: (): void => dispatch(resetMessages()), + showShortcutsHelp: (): void => dispatch(shortcutsActions.showShortcutsHelp()), + hideShortcutsHelp: (): void => dispatch(shortcutsActions.hideShortcutsHelp()), }; } -function reduxAppWrapper(props: StateToProps & DispatchToProps): JSX.Element { - return ( - - ); -} - const ReduxAppWrapper = connect( mapStateToProps, mapDispatchToProps, -)(reduxAppWrapper); +)(CVATApplication); ReactDOM.render( ( diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 644d85d50c8..0880b519c16 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -414,6 +414,10 @@ export interface SettingsState { player: PlayerSettingsState; } +export interface ShortcutsState { + visibleShortcutsHelp: boolean; +} + export interface CombinedState { auth: AuthState; tasks: TasksState; @@ -426,4 +430,5 @@ export interface CombinedState { notifications: NotificationsState; annotation: AnnotationState; settings: SettingsState; + shortcuts: ShortcutsState; } diff --git a/cvat-ui/src/reducers/root-reducer.ts b/cvat-ui/src/reducers/root-reducer.ts index 6e65dcf44e9..337bfbc5345 100644 --- a/cvat-ui/src/reducers/root-reducer.ts +++ b/cvat-ui/src/reducers/root-reducer.ts @@ -14,6 +14,7 @@ import modelsReducer from './models-reducer'; import notificationsReducer from './notifications-reducer'; import annotationReducer from './annotation-reducer'; import settingsReducer from './settings-reducer'; +import shortcutsReducer from './shortcuts-reducer'; export default function createRootReducer(): Reducer { return combineReducers({ @@ -28,5 +29,6 @@ export default function createRootReducer(): Reducer { notifications: notificationsReducer, annotation: annotationReducer, settings: settingsReducer, + shortcuts: shortcutsReducer, }); } diff --git a/cvat-ui/src/reducers/shortcuts-reducer.ts b/cvat-ui/src/reducers/shortcuts-reducer.ts new file mode 100644 index 00000000000..d104e49d66d --- /dev/null +++ b/cvat-ui/src/reducers/shortcuts-reducer.ts @@ -0,0 +1,27 @@ + +import { ShortcutsActions, ShortcutsActionsTypes } from 'actions/shortcuts-actions'; +import { ShortcutsState } from './interfaces'; + +const defaultState: ShortcutsState = { + visibleShortcutsHelp: false, +}; + +export default (state = defaultState, action: ShortcutsActions): ShortcutsState => { + switch (action.type) { + case ShortcutsActionsTypes.SHOW_SHORTCUTS_HELP: { + return { + ...state, + visibleShortcutsHelp: true, + }; + } + case ShortcutsActionsTypes.HIDE_SHORTCUTS_HELP: { + return { + ...state, + visibleShortcutsHelp: false, + }; + } + default: { + return state; + } + } +}; From a6c4548cbb28d8018a91076b0791eb4c1cf2dc1a Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 3 Mar 2020 11:02:14 +0300 Subject: [PATCH 02/26] Added shortcut to open settings page --- cvat-ui/src/actions/shortcuts-actions.ts | 6 +- cvat-ui/src/components/cvat-app.tsx | 110 +++++++++++----------- cvat-ui/src/index.tsx | 11 ++- cvat-ui/src/reducers/shortcuts-reducer.ts | 10 +- 4 files changed, 67 insertions(+), 70 deletions(-) diff --git a/cvat-ui/src/actions/shortcuts-actions.ts b/cvat-ui/src/actions/shortcuts-actions.ts index 6ecd30d046f..86d83e6d707 100644 --- a/cvat-ui/src/actions/shortcuts-actions.ts +++ b/cvat-ui/src/actions/shortcuts-actions.ts @@ -1,13 +1,11 @@ import { ActionUnion, createAction } from 'utils/redux'; export enum ShortcutsActionsTypes { - SHOW_SHORTCUTS_HELP = 'SHOW_SHORTCUTS_HELP', - HIDE_SHORTCUTS_HELP = 'HIDE_SHORTCUTS_HELP', + SWITCH_SHORTCUT_DIALOG = 'SWITCH_SHORTCUT_DIALOG', } export const shortcutsActions = { - showShortcutsHelp: () => createAction(ShortcutsActionsTypes.SHOW_SHORTCUTS_HELP), - hideShortcutsHelp: () => createAction(ShortcutsActionsTypes.HIDE_SHORTCUTS_HELP), + switchShortcutsDialog: () => createAction(ShortcutsActionsTypes.SWITCH_SHORTCUT_DIALOG), }; export type ShortcutsActions = ActionUnion; diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index 0214e127681..fbbdb1ba1e2 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -6,11 +6,12 @@ import 'antd/dist/antd.less'; import '../styles.scss'; import React from 'react'; import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; -import { BrowserRouter } from 'react-router-dom'; import { Switch, Route, Redirect, + withRouter, + RouteComponentProps, } from 'react-router'; import { Spin, @@ -32,7 +33,7 @@ import HeaderContainer from 'containers/header/header'; import { NotificationsState } from 'reducers/interfaces'; -type CVATAppProps = { +interface CVATAppProps { loadFormats: () => void; loadUsers: () => void; loadAbout: () => void; @@ -40,8 +41,7 @@ type CVATAppProps = { initPlugins: () => void; resetErrors: () => void; resetMessages: () => void; - showShortcutsHelp: () => void; - hideShortcutsHelp: () => void; + switchShortcutsDialog: () => void; userInitialized: boolean; pluginsInitialized: boolean; pluginsFetching: boolean; @@ -56,9 +56,9 @@ type CVATAppProps = { installedTFSegmentation: boolean; notifications: NotificationsState; user: any; -}; +} -export default class CVATApplication extends React.PureComponent { +class CVATApplication extends React.PureComponent { public componentDidMount(): void { const { verifyAuthorized } = this.props; verifyAuthorized(); @@ -194,9 +194,9 @@ export default class CVATApplication extends React.PureComponent { installedAutoAnnotation, installedTFSegmentation, installedTFAnnotation, + switchShortcutsDialog, user, - showShortcutsHelp, - hideShortcutsHelp, + history, } = this.props; const readyForRender = (userInitialized && user == null) @@ -207,76 +207,78 @@ export default class CVATApplication extends React.PureComponent { || installedTFAnnotation || installedTFSegmentation; const keyMap = { - SHOW_SHORTCUTS: { + SWITCH_SHORTCUTS: { name: 'Show shortcuts', - description: 'Open a list of available shortcuts', + description: 'Open/hide the list of available shortcuts', sequence: 'f1', action: 'keydown', }, - HIDE_SHORTCUTS: { - name: 'Hide shortcuts', - description: 'Close the list of available shortcuts', - sequence: 'f1', - action: 'keyup', + OPEN_SETTINGS: { + name: 'Open settings', + description: 'Go to the settings page or go back', + sequence: 'f2', + action: 'keydown', }, }; const handlers = { - SHOW_SHORTCUTS: (event: KeyboardEvent | undefined) => { + SWITCH_SHORTCUTS: (event: KeyboardEvent | undefined) => { if (event) { event.preventDefault(); } - showShortcutsHelp(); + + switchShortcutsDialog(); }, - HIDE_SHORTCUTS: (event: KeyboardEvent | undefined) => { + OPEN_SETTINGS: (event: KeyboardEvent | undefined) => { if (event) { event.preventDefault(); } - hideShortcutsHelp(); + + if (history.location.pathname.endsWith('settings')) { + history.goBack(); + } else { + history.push('/settings'); + } }, }; if (readyForRender) { if (user) { return ( - - - - - - - - - - - - - { withModels - && } - { installedAutoAnnotation - && } - - - - {/* eslint-disable-next-line */} - - - - + + + + + + + + + + + + { withModels + && } + { installedAutoAnnotation + && } + + + + {/* eslint-disable-next-line */} + + + ); } return ( - - - - - - - + + + + + ); } @@ -285,3 +287,5 @@ export default class CVATApplication extends React.PureComponent { ); } } + +export default withRouter(CVATApplication); diff --git a/cvat-ui/src/index.tsx b/cvat-ui/src/index.tsx index ee1bff04ab0..468b53722de 100644 --- a/cvat-ui/src/index.tsx +++ b/cvat-ui/src/index.tsx @@ -5,6 +5,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { connect, Provider } from 'react-redux'; +import { BrowserRouter } from 'react-router-dom'; import CVATApplication from './components/cvat-app'; @@ -55,8 +56,7 @@ interface DispatchToProps { initPlugins: () => void; resetErrors: () => void; resetMessages: () => void; - showShortcutsHelp: () => void; - hideShortcutsHelp: () => void; + switchShortcutsDialog: () => void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -93,8 +93,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { loadAbout: (): void => dispatch(getAboutAsync()), resetErrors: (): void => dispatch(resetErrors()), resetMessages: (): void => dispatch(resetMessages()), - showShortcutsHelp: (): void => dispatch(shortcutsActions.showShortcutsHelp()), - hideShortcutsHelp: (): void => dispatch(shortcutsActions.hideShortcutsHelp()), + switchShortcutsDialog: (): void => dispatch(shortcutsActions.switchShortcutsDialog()), }; } @@ -106,7 +105,9 @@ const ReduxAppWrapper = connect( ReactDOM.render( ( - + + + ), document.getElementById('root'), diff --git a/cvat-ui/src/reducers/shortcuts-reducer.ts b/cvat-ui/src/reducers/shortcuts-reducer.ts index d104e49d66d..e30e44e6122 100644 --- a/cvat-ui/src/reducers/shortcuts-reducer.ts +++ b/cvat-ui/src/reducers/shortcuts-reducer.ts @@ -8,16 +8,10 @@ const defaultState: ShortcutsState = { export default (state = defaultState, action: ShortcutsActions): ShortcutsState => { switch (action.type) { - case ShortcutsActionsTypes.SHOW_SHORTCUTS_HELP: { + case ShortcutsActionsTypes.SWITCH_SHORTCUT_DIALOG: { return { ...state, - visibleShortcutsHelp: true, - }; - } - case ShortcutsActionsTypes.HIDE_SHORTCUTS_HELP: { - return { - ...state, - visibleShortcutsHelp: false, + visibleShortcutsHelp: !state.visibleShortcutsHelp, }; } default: { From b90c5c906a95baf4c610767a65e8b246f6330345 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 3 Mar 2020 11:23:47 +0300 Subject: [PATCH 03/26] Added hideAll, lockAll hotkeys --- cvat-ui/src/components/cvat-app.tsx | 5 +- .../objects-side-bar/objects-list.tsx | 65 ++++++++++++++----- 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index fbbdb1ba1e2..00369c4195e 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -249,10 +249,7 @@ class CVATApplication extends React.PureComponent - + 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 b1e2ed85cd8..e99adf388af 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 @@ -4,6 +4,7 @@ import React from 'react'; import { connect } from 'react-redux'; +import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; import { SelectValue } from 'antd/lib/select'; @@ -221,27 +222,61 @@ class ObjectsListContainer extends React.PureComponent { } public render(): JSX.Element { - const { annotationsFilters } = this.props; + const { annotationsFilters, statesHidden, statesLocked } = this.props; const { sortedStatesID, statesOrdering, } = this.state; + const keyMap = { + SWITCH_ALL_LOCK: { + name: 'Lock/unlock all objects', + description: 'Locking objects allows to prevent any updates', + sequence: 't+l', + action: 'keydown', + }, + SWITCH_ALL_HIDDEN: { + name: 'Hide/show all objects', + description: 'Hidden objects are invisible on the canvas', + sequence: 't+h', + action: 'keydown', + }, + }; + + const handlers = { + SWITCH_ALL_LOCK: (event: KeyboardEvent | undefined) => { + if (event) { + event.preventDefault(); + } + + this.lockAllStates(!statesLocked); + }, + SWITCH_ALL_HIDDEN: (event: KeyboardEvent | undefined) => { + if (event) { + event.preventDefault(); + } + + this.hideAllStates(!statesHidden); + }, + }; + return ( - + + + ); } } From 97283d9062bc5e8c434d5f91e90d5df81c8a01a1 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 3 Mar 2020 12:03:33 +0300 Subject: [PATCH 04/26] Added lock and hide an object shortcuts --- cvat-ui/src/components/cvat-app.tsx | 3 +- .../objects-side-bar/label-item.tsx | 7 +- .../objects-side-bar/object-item.tsx | 121 +++++++++--------- .../objects-side-bar/objects-list.tsx | 83 ++++++++++-- 4 files changed, 138 insertions(+), 76 deletions(-) diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index 00369c4195e..dc7aac8235d 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -5,7 +5,6 @@ import 'antd/dist/antd.less'; import '../styles.scss'; import React from 'react'; -import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; import { Switch, Route, @@ -13,6 +12,8 @@ import { withRouter, RouteComponentProps, } from 'react-router'; +import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; + import { Spin, Layout, 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 ef2783cb040..2e70ed1bb03 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 @@ -115,8 +115,11 @@ class LabelItemContainer extends React.PureComponent { let statesLocked = true; ownObjectStates.forEach((objectState: any) => { - statesHidden = statesHidden && objectState.hidden; - statesLocked = statesLocked && objectState.lock; + const { lock } = objectState; + if (!lock) { + statesHidden = statesHidden && objectState.hidden; + statesLocked = statesLocked && objectState.lock; + } }); return { 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 6ced57029d2..139a3184b34 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 @@ -23,6 +23,7 @@ import { } from 'actions/annotation-actions'; import ObjectStateItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item'; +import { GlobalHotKeys } from 'react-hotkeys'; interface OwnProps { clientID: number; @@ -454,65 +455,67 @@ class ObjectItemContainer extends React.PureComponent { } return ( -
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 e4dbada15d9..20681b5de35 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 @@ -12,6 +12,7 @@ import ObjectsListComponent from 'components/annotation-page/standard-workspace/ import { updateAnnotationsAsync, fetchAnnotationsAsync, + removeObjectAsync, changeAnnotationsFilters as changeAnnotationsFiltersAction, collapseObjectItems, } from 'actions/annotation-actions'; @@ -32,12 +33,15 @@ interface StateToProps { objectStates: any[]; annotationsFilters: string[]; activatedStateID: number | null; + minZLayer: number; + maxZLayer: number; } interface DispatchToProps { updateAnnotations(sessionInstance: any, frameNumber: number, states: any[]): void; changeAnnotationsFilters(sessionInstance: any, filters: string[]): void; collapseStates(states: any[], value: boolean): void; + removeObject: (sessionInstance: any, objectState: any, force: boolean) => void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -48,6 +52,10 @@ function mapStateToProps(state: CombinedState): StateToProps { filters: annotationsFilters, collapsed, activatedStateID, + zLayer: { + min: minZLayer, + max: maxZLayer, + }, }, job: { instance: jobInstance, @@ -85,6 +93,8 @@ function mapStateToProps(state: CombinedState): StateToProps { jobInstance, annotationsFilters, activatedStateID, + minZLayer, + maxZLayer, }; } @@ -103,6 +113,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { dispatch(changeAnnotationsFiltersAction(filters)); dispatch(fetchAnnotationsAsync(sessionInstance)); }, + removeObject(sessionInstance: any, objectState: any, force: boolean): void { + dispatch(removeObjectAsync(sessionInstance, objectState, force)); + }, }; } @@ -237,6 +250,9 @@ class ObjectsListContainer extends React.PureComponent { frameNumber, jobInstance, updateAnnotations, + removeObject, + maxZLayer, + minZLayer, } = this.props; const { sortedStatesID, @@ -277,13 +293,31 @@ class ObjectsListContainer extends React.PureComponent { SWITCH_KEYFRAME: { name: 'Switch keyframe', description: 'Change keyframe property for an active track', - sequences: ['k'], + sequence: 'k', action: 'keydown', }, SWITCH_OUTSIDE: { name: 'Switch outside', description: 'Change outside property for an active track', - sequences: ['o'], + sequence: 'o', + action: 'keydown', + }, + DELETE_OBJECT: { + name: 'Delete object', + description: 'Delete an active object. Use shift to force delete of locked objects', + sequences: ['del', 'shift+del'], + action: 'keydown', + }, + TO_BACKGROUND: { + name: 'To background', + description: 'Put an active object "farther" from the user (decrease z axis value)', + sequences: ['-', '_'], + action: 'keydown', + }, + TO_FOREGROUND: { + name: 'To foreground', + description: 'Put an active object "closer" to the user (increase z axis value)', + sequences: ['+', '='], action: 'keydown', }, }; @@ -356,6 +390,29 @@ class ObjectsListContainer extends React.PureComponent { updateAnnotations(jobInstance, frameNumber, [state]); } }, + DELETE_OBJECT: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const state = activatedStated(); + if (state) { + removeObject(jobInstance, state, event ? event.shiftKey : false); + } + }, + TO_BACKGROUND: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const state = activatedStated(); + if (state && state.objectType !== ObjectType.TAG) { + state.zOrder = minZLayer - 1; + updateAnnotations(jobInstance, frameNumber, [state]); + } + }, + TO_FOREGROUND: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const state = activatedStated(); + if (state && state.objectType !== ObjectType.TAG) { + state.zOrder = maxZLayer + 1; + updateAnnotations(jobInstance, frameNumber, [state]); + } + }, }; return ( From a5f81e637e07342c8cc4dbf63a0544cfc4eaae06 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 3 Mar 2020 17:59:20 +0300 Subject: [PATCH 08/26] Added shortcut to copy shape --- cvat-ui/src/actions/annotation-actions.ts | 8 +++ cvat-ui/src/components/cvat-app.tsx | 9 +-- .../objects-side-bar/object-item.tsx | 2 + .../objects-side-bar/objects-list.tsx | 19 ++++++ cvat-ui/src/reducers/annotation-reducer.ts | 58 ++++++++++++------- cvat-ui/src/reducers/interfaces.ts | 1 + cvat-ui/src/reducers/shortcuts-reducer.ts | 1 - 7 files changed, 68 insertions(+), 30 deletions(-) diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index b923e76ac62..5092da739ed 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -82,6 +82,7 @@ export enum AnnotationActionTypes { GROUP_OBJECTS = 'GROUP_OBJECTS', SPLIT_TRACK = 'SPLIT_TRACK', COPY_SHAPE = 'COPY_SHAPE', + PASTE_SHAPE = 'PASTE_SHAPE', EDIT_SHAPE = 'EDIT_SHAPE', DRAW_SHAPE = 'DRAW_SHAPE', SHAPE_DRAWN = 'SHAPE_DRAWN', @@ -524,6 +525,13 @@ export function editShape(enabled: boolean): AnyAction { }; } +export function pasteShape(): AnyAction { + return { + type: AnnotationActionTypes.PASTE_SHAPE, + payload: {}, + }; +} + export function copyShape(objectState: any): AnyAction { return { type: AnnotationActionTypes.COPY_SHAPE, diff --git a/cvat-ui/src/components/cvat-app.tsx b/cvat-ui/src/components/cvat-app.tsx index dc7aac8235d..f3241d063dd 100644 --- a/cvat-ui/src/components/cvat-app.tsx +++ b/cvat-ui/src/components/cvat-app.tsx @@ -5,13 +5,8 @@ import 'antd/dist/antd.less'; import '../styles.scss'; import React from 'react'; -import { - Switch, - Route, - Redirect, - withRouter, - RouteComponentProps, -} from 'react-router'; +import { Switch, Route, Redirect } from 'react-router'; +import { withRouter, RouteComponentProps } from 'react-router-dom'; import { GlobalHotKeys, KeyMap } from 'react-hotkeys'; import { 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 139a3184b34..c7d6e1df98b 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 @@ -20,6 +20,7 @@ import { copyShape as copyShapeAction, activateObject as activateObjectAction, propagateObject as propagateObjectAction, + pasteShape, } from 'actions/annotation-actions'; import ObjectStateItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item'; @@ -135,6 +136,7 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { }, copyShape(objectState: any): void { dispatch(copyShapeAction(objectState)); + dispatch(pasteShape()); }, propagateObject(objectState: any): void { dispatch(propagateObjectAction(objectState)); 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 20681b5de35..4cd9cdf75ca 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 @@ -15,6 +15,7 @@ import { removeObjectAsync, changeAnnotationsFilters as changeAnnotationsFiltersAction, collapseObjectItems, + copyShape as copyShapeAction, } from 'actions/annotation-actions'; import { @@ -42,6 +43,7 @@ interface DispatchToProps { changeAnnotationsFilters(sessionInstance: any, filters: string[]): void; collapseStates(states: any[], value: boolean): void; removeObject: (sessionInstance: any, objectState: any, force: boolean) => void; + copyShape: (objectState: any) => void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -116,6 +118,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { removeObject(sessionInstance: any, objectState: any, force: boolean): void { dispatch(removeObjectAsync(sessionInstance, objectState, force)); }, + copyShape(objectState: any): void { + dispatch(copyShapeAction(objectState)); + }, }; } @@ -251,6 +256,7 @@ class ObjectsListContainer extends React.PureComponent { jobInstance, updateAnnotations, removeObject, + copyShape, maxZLayer, minZLayer, } = this.props; @@ -320,6 +326,12 @@ class ObjectsListContainer extends React.PureComponent { sequences: ['+', '='], action: 'keydown', }, + COPY_SHAPE: { + name: 'Copy shape', + description: 'Copy shape to CVAT internal clipboard', + sequence: 'ctrl+c', + action: 'keydown', + }, }; const preventDefault = (event: KeyboardEvent | undefined): void => { @@ -413,6 +425,13 @@ class ObjectsListContainer extends React.PureComponent { updateAnnotations(jobInstance, frameNumber, [state]); } }, + COPY_SHAPE: (event: KeyboardEvent | undefined) => { + preventDefault(event); + const state = activatedStated(); + if (state && state.objectType !== ObjectType.TAG) { + copyShape(state); + } + }, }; return ( diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index b9615fe70be..23070d7b978 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -653,35 +653,49 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } + case AnnotationActionTypes.PASTE_SHAPE: { + const initialState = state.drawing.activeInitialState; + if (initialState) { + state.canvas.instance.cancel(); + state.canvas.instance.draw({ + enabled: true, + initialState, + }); + + let activeControl = ActiveControl.DRAW_RECTANGLE; + if (initialState.shapeType === ShapeType.POINTS) { + activeControl = ActiveControl.DRAW_POINTS; + } else if (initialState.shapeType === ShapeType.POLYGON) { + activeControl = ActiveControl.DRAW_POLYGON; + } else if (initialState.shapeType === ShapeType.POLYLINE) { + activeControl = ActiveControl.DRAW_POLYLINE; + } + + return { + ...state, + canvas: { + ...state.canvas, + activeControl, + }, + annotations: { + ...state.annotations, + activatedStateID: null, + }, + }; + } + + return state; + } case AnnotationActionTypes.COPY_SHAPE: { const { objectState, } = action.payload; - state.canvas.instance.cancel(); - state.canvas.instance.draw({ - enabled: true, - initialState: objectState, - }); - - let activeControl = ActiveControl.DRAW_RECTANGLE; - if (objectState.shapeType === ShapeType.POINTS) { - activeControl = ActiveControl.DRAW_POINTS; - } else if (objectState.shapeType === ShapeType.POLYGON) { - activeControl = ActiveControl.DRAW_POLYGON; - } else if (objectState.shapeType === ShapeType.POLYLINE) { - activeControl = ActiveControl.DRAW_POLYLINE; - } - return { ...state, - canvas: { - ...state.canvas, - activeControl, - }, - annotations: { - ...state.annotations, - activatedStateID: null, + drawing: { + ...state.drawing, + activeInitialState: objectState, }, }; } diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index 0880b519c16..811a0c1e305 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -321,6 +321,7 @@ export interface AnnotationState { activeNumOfPoints?: number; activeLabelID: number; activeObjectType: ObjectType; + activeInitialState?: any; }; annotations: { selectedStatesID: number[]; diff --git a/cvat-ui/src/reducers/shortcuts-reducer.ts b/cvat-ui/src/reducers/shortcuts-reducer.ts index e30e44e6122..92397b64b01 100644 --- a/cvat-ui/src/reducers/shortcuts-reducer.ts +++ b/cvat-ui/src/reducers/shortcuts-reducer.ts @@ -1,4 +1,3 @@ - import { ShortcutsActions, ShortcutsActionsTypes } from 'actions/shortcuts-actions'; import { ShortcutsState } from './interfaces'; From 0a11bfc6637954a44c1132a36c440d9da6c7393f Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Wed, 4 Mar 2020 10:05:58 +0300 Subject: [PATCH 09/26] Propagate shortcut --- .../objects-side-bar/object-item.tsx | 121 +++++++++--------- .../objects-side-bar/objects-list.tsx | 19 +++ 2 files changed, 78 insertions(+), 62 deletions(-) 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 c7d6e1df98b..ae97be6933c 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 @@ -24,7 +24,6 @@ import { } from 'actions/annotation-actions'; import ObjectStateItemComponent from 'components/annotation-page/standard-workspace/objects-side-bar/object-item'; -import { GlobalHotKeys } from 'react-hotkeys'; interface OwnProps { clientID: number; @@ -457,67 +456,65 @@ class ObjectItemContainer extends React.PureComponent { } return ( - - +