diff --git a/CHANGELOG.md b/CHANGELOG.md index 688e646938c6..fa95ae159170 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ from online detectors & interactors) ( - Added Webhooks () - Authentication with social accounts google & github (, , ) - REST API tests to export job datasets & annotations and validate their structure () +- Propagation backward on UI () ### Changed - `api/docs`, `api/swagger`, `api/schema`, `server/about` endpoints now allow unauthorized access (, ) diff --git a/cvat-ui/package.json b/cvat-ui/package.json index fa7b7eeed275..e87f985c9528 100644 --- a/cvat-ui/package.json +++ b/cvat-ui/package.json @@ -1,6 +1,6 @@ { "name": "cvat-ui", - "version": "1.44.1", + "version": "1.44.2", "description": "CVAT single-page application", "main": "src/index.tsx", "scripts": { diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 8816760344ec..24e941348c3f 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -160,10 +160,9 @@ export enum AnnotationActionTypes { REMOVE_OBJECT = 'REMOVE_OBJECT', REMOVE_OBJECT_SUCCESS = 'REMOVE_OBJECT_SUCCESS', REMOVE_OBJECT_FAILED = 'REMOVE_OBJECT_FAILED', - PROPAGATE_OBJECT = 'PROPAGATE_OBJECT', PROPAGATE_OBJECT_SUCCESS = 'PROPAGATE_OBJECT_SUCCESS', PROPAGATE_OBJECT_FAILED = 'PROPAGATE_OBJECT_FAILED', - CHANGE_PROPAGATE_FRAMES = 'CHANGE_PROPAGATE_FRAMES', + SWITCH_PROPAGATE_VISIBILITY = 'SWITCH_PROPAGATE_VISIBILITY', SWITCH_SHOWING_STATISTICS = 'SWITCH_SHOWING_STATISTICS', SWITCH_SHOWING_FILTERS = 'SWITCH_SHOWING_FILTERS', COLLECT_STATISTICS = 'COLLECT_STATISTICS', @@ -404,9 +403,32 @@ export function showFilters(visible: boolean): AnyAction { }; } -export function propagateObjectAsync(sessionInstance: any, objectState: any, from: number, to: number): ThunkAction { - return async (dispatch: ActionCreator): Promise => { +export function switchPropagateVisibility(visible: boolean): AnyAction { + return { + type: AnnotationActionTypes.SWITCH_PROPAGATE_VISIBILITY, + payload: { visible }, + }; +} + +export function propagateObjectAsync(from: number, to: number): ThunkAction { + return async (dispatch: ActionCreator, getState): Promise => { + const state = getState(); + const { + job: { + instance: sessionInstance, + }, + annotations: { + activatedStateID, + states: objectStates, + }, + } = state.annotation; + try { + const objectState = objectStates.find((_state: any) => _state.clientID === activatedStateID); + if (!objectState) { + throw new Error('There is not an activated object state to be propagated'); + } + const getCopyFromState = (_objectState: any): any => ({ attributes: _objectState.attributes, points: _objectState.shapeType === 'skeleton' ? null : _objectState.points, @@ -423,9 +445,10 @@ export function propagateObjectAsync(sessionInstance: any, objectState: any, fro }); const copy = getCopyFromState(objectState); - await sessionInstance.logger.log(LogType.propagateObject, { count: to - from + 1 }); + await sessionInstance.logger.log(LogType.propagateObject, { count: Math.abs(to - from) }); const states = []; - for (let frame = from; frame <= to; frame++) { + const sign = Math.sign(to - from); + for (let frame = from + sign; sign > 0 ? frame <= to : frame >= to; frame += sign) { copy.frame = frame; copy.elements.forEach((element: any) => { element.frame = frame; }); const newState = new cvat.classes.ObjectState(copy); @@ -437,40 +460,17 @@ export function propagateObjectAsync(sessionInstance: any, objectState: any, fro dispatch({ type: AnnotationActionTypes.PROPAGATE_OBJECT_SUCCESS, - payload: { - objectState, - history, - }, + payload: { history }, }); } catch (error) { dispatch({ type: AnnotationActionTypes.PROPAGATE_OBJECT_FAILED, - payload: { - error, - }, + payload: { error }, }); } }; } -export function propagateObject(objectState: any | null): AnyAction { - return { - type: AnnotationActionTypes.PROPAGATE_OBJECT, - payload: { - objectState, - }, - }; -} - -export function changePropagateFrames(frames: number): AnyAction { - return { - type: AnnotationActionTypes.CHANGE_PROPAGATE_FRAMES, - payload: { - frames, - }, - }; -} - export function removeObjectAsync(sessionInstance: any, objectState: any, force: boolean): ThunkAction { return async (dispatch: ActionCreator): Promise => { try { @@ -661,6 +661,13 @@ export function getPredictionsAsync(): ThunkAction { }; } +export function confirmCanvasReady(): AnyAction { + return { + type: AnnotationActionTypes.CONFIRM_CANVAS_READY, + payload: {}, + }; +} + export function changeFrameAsync( toFrame: number, fillBuffer?: boolean, @@ -670,6 +677,14 @@ export function changeFrameAsync( return async (dispatch: ActionCreator, getState: () => CombinedState): Promise => { const state: CombinedState = getState(); const { instance: job } = state.annotation.job; + const { + propagate: { + visible: propagateVisible, + }, + statistics: { + visible: statisticsVisible, + }, + } = state.annotation; const { filters, frame, showAllInterpolationTracks } = receiveAnnotationsParameters(); try { @@ -677,9 +692,9 @@ export function changeFrameAsync( throw Error(`Required frame ${toFrame} is out of the current job`); } - const abortAction = (): AnyAction => { + const abortAction = (): void => { const currentState = getState(); - return ({ + dispatch({ type: AnnotationActionTypes.CHANGE_FRAME_SUCCESS, payload: { number: currentState.annotation.player.frame.number, @@ -694,6 +709,8 @@ export function changeFrameAsync( curZ: currentState.annotation.annotations.zLayer.cur, }, }); + + dispatch(confirmCanvasReady()); }; dispatch({ @@ -702,17 +719,17 @@ export function changeFrameAsync( }); if (toFrame === frame && !forceUpdate) { - dispatch(abortAction()); + abortAction(); return; } const data = await job.frames.get(toFrame, fillBuffer, frameStep); const states = await job.annotations.get(toFrame, showAllInterpolationTracks, filters); - if (!isAbleToChangeFrame()) { + if (!isAbleToChangeFrame() || statisticsVisible || propagateVisible) { // while doing async actions above, canvas can become used by a user in another way // so, we need an additional check and if it is used, we do not update state - dispatch(abortAction()); + abortAction(); return; } @@ -933,13 +950,6 @@ export function resetCanvas(): AnyAction { }; } -export function confirmCanvasReady(): AnyAction { - return { - type: AnnotationActionTypes.CONFIRM_CANVAS_READY, - payload: {}, - }; -} - export function closeJob(): ThunkAction { return async (dispatch: ActionCreator): Promise => { const { jobInstance } = receiveAnnotationsParameters(); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx index 4d245fc7c66e..3d6f747285fa 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/propagate-confirm.tsx @@ -1,82 +1,155 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT -import React from 'react'; - +import React, { useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import Modal from 'antd/lib/modal'; import InputNumber from 'antd/lib/input-number'; import Text from 'antd/lib/typography/Text'; +import Radio, { RadioChangeEvent } from 'antd/lib/radio'; +import { Row, Col } from 'antd/lib/grid'; +import Slider from 'antd/lib/slider'; import { clamp } from 'utils/math'; +import { ArrowLeftOutlined, ArrowRightOutlined } from '@ant-design/icons'; +import { propagateObjectAsync, switchPropagateVisibility } from 'actions/annotation-actions'; +import { CombinedState } from 'reducers'; -interface Props { - visible: boolean; - propagateFrames: number; - propagateUpToFrame: number; - stopFrame: number; - frameNumber: number; - propagateObject(): void; - cancel(): void; - changePropagateFrames(value: number): void; - changeUpToFrame(value: number): void; +export enum PropagateDirection { + FORWARD = 'forward', + BACKWARD = 'backward', } -export default function PropagateConfirmComponent(props: Props): JSX.Element { - const { - visible, - propagateFrames, - propagateUpToFrame, - stopFrame, - frameNumber, - propagateObject, - changePropagateFrames, - changeUpToFrame, - cancel, - } = props; +function PropagateConfirmComponent(): JSX.Element { + const dispatch = useDispatch(); + const visible = useSelector((state: CombinedState) => state.annotation.propagate.visible); + const frameNumber = useSelector((state: CombinedState) => state.annotation.player.frame.number); + const startFrame = useSelector((state: CombinedState) => state.annotation.job.instance.startFrame); + const stopFrame = useSelector((state: CombinedState) => state.annotation.job.instance.stopFrame); + const [targetFrame, setTargetFrame] = useState(frameNumber); + + const propagateFrames = Math.abs(targetFrame - frameNumber); + const propagateDirection = targetFrame >= frameNumber ? PropagateDirection.FORWARD : PropagateDirection.BACKWARD; - const minPropagateFrames = 1; + useEffect(() => { + const propagateForwardAvailable = stopFrame - frameNumber >= 1; + const propagateBackwardAvailable = frameNumber - startFrame >= 1; + if (propagateForwardAvailable) { + setTargetFrame(stopFrame); + } else if (propagateBackwardAvailable) { + setTargetFrame(startFrame); + } + }, [visible]); + + const updateTargetFrame = (direction: PropagateDirection, _propagateFrames: number): void => { + if (direction === PropagateDirection.FORWARD) { + setTargetFrame(clamp(frameNumber + _propagateFrames, startFrame, stopFrame)); + } else { + setTargetFrame(clamp(frameNumber - _propagateFrames, startFrame, stopFrame)); + } + }; return ( { + dispatch(propagateObjectAsync(frameNumber, targetFrame)) + .then(() => dispatch(switchPropagateVisibility(false))); + }} + onCancel={() => dispatch(switchPropagateVisibility(false))} title='Confirm propagation' visible={visible} + destroyOnClose + okButtonProps={{ disabled: !propagateFrames }} >
- Do you want to make a copy of the object on - { - if (typeof value !== 'undefined') { - changePropagateFrames( - Math.floor(clamp(+value, minPropagateFrames, Number.MAX_SAFE_INTEGER)), - ); - } - }} - /> - {propagateFrames > 1 ? frames : frame } - up to the - { - if (typeof value !== 'undefined') { - changeUpToFrame(Math.floor(clamp(+value, frameNumber + 1, stopFrame))); - } - }} - /> - frame + + + Please, specify a direction + + + updateTargetFrame(e.target.value, propagateFrames)} + > + + + + + + + + + + + How many copies do you want to create? + + { + if (typeof value !== 'undefined') { + updateTargetFrame(propagateDirection, +value); + } + }} + /> + + +
+ + + Or specify a range where copies will be created + + + { + const value = value1 === frameNumber || value1 === targetFrame ? value2 : value1; + if (value < frameNumber) { + setTargetFrame(clamp(value, startFrame, frameNumber)); + } else { + setTargetFrame(clamp(value, frameNumber, stopFrame)); + } + }} + value={[frameNumber, targetFrame] as [number, number]} + /> + + + { + if (typeof value !== 'undefined') { + if (value > frameNumber) { + setTargetFrame(clamp(+value, frameNumber, stopFrame)); + } else if (value < frameNumber) { + setTargetFrame(clamp(+value, startFrame, frameNumber)); + } else { + setTargetFrame(frameNumber); + } + } + }} + /> + +
); } + +export default React.memo(PropagateConfirmComponent); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx index d62d226bd714..096d62fe5c0d 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/standard-workspace.tsx @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporations // // SPDX-License-Identifier: MIT @@ -8,13 +9,13 @@ import Layout from 'antd/lib/layout'; import CanvasWrapperContainer from 'containers/annotation-page/canvas/canvas-wrapper'; import ControlsSideBarContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/controls-side-bar'; -import PropagateConfirmContainer from 'containers/annotation-page/standard-workspace/propagate-confirm'; import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas-context-menu'; import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list'; import ObjectSideBarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; import CanvasPointContextMenuComponent from 'components/annotation-page/canvas/canvas-point-context-menu'; import IssueAggregatorComponent from 'components/annotation-page/review/issues-aggregator'; import RemoveConfirmComponent from 'components/annotation-page/standard-workspace/remove-confirm'; +import PropagateConfirmComponent from 'components/annotation-page/standard-workspace/propagate-confirm'; export default function StandardWorkspaceComponent(): JSX.Element { return ( @@ -22,7 +23,7 @@ export default function StandardWorkspaceComponent(): JSX.Element { } /> - + diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss index a6c23fccb0c1..ef788cf583cd 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss +++ b/cvat-ui/src/components/annotation-page/standard-workspace/styles.scss @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -333,9 +334,23 @@ } .cvat-propagate-confirm { - > .ant-input-number { - width: 70px; - margin: 0 5px; + > .ant-row { + align-items: flex-start; + margin-bottom: $grid-unit-size * 2; + } + + .ant-input-number { + margin-left: $grid-unit-size; + margin-right: $grid-unit-size; + width: $grid-unit-size * 7; + } + + .cvat-propagate-slider-wrapper { + height: $grid-unit-size; + } + + .cvat-propagate-up-to-wrapper > div:first-child { + margin-bottom: $grid-unit-size; } } diff --git a/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx b/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx index 3273bcfad37f..18f70140b0a2 100644 --- a/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx +++ b/cvat-ui/src/components/annotation-page/standard3D-workspace/standard3D-workspace.tsx @@ -10,10 +10,10 @@ import CanvasWrapperContainer from 'containers/annotation-page/canvas/canvas-wra import ControlsSideBarContainer from 'containers/annotation-page/standard3D-workspace/controls-side-bar/controls-side-bar'; import ObjectSideBarComponent from 'components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar'; import ObjectsListContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/objects-list'; -import PropagateConfirmContainer from 'containers/annotation-page/standard-workspace/propagate-confirm'; import CanvasContextMenuContainer from 'containers/annotation-page/canvas/canvas-context-menu'; import CanvasPointContextMenuComponent from 'components/annotation-page/canvas/canvas-point-context-menu'; import RemoveConfirmComponent from 'components/annotation-page/standard-workspace/remove-confirm'; +import PropagateConfirmComponent from 'components/annotation-page/standard-workspace/propagate-confirm'; export default function StandardWorkspace3DComponent(): JSX.Element { return ( @@ -21,7 +21,7 @@ export default function StandardWorkspace3DComponent(): JSX.Element { } /> - + diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/object-item.tsx index 4762ed7d355d..ebbf9a29315d 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 @@ -14,7 +14,7 @@ import { pasteShapeAsync, copyShape as copyShapeAction, activateObject as activateObjectAction, - propagateObject as propagateObjectAction, + switchPropagateVisibility as switchPropagateVisibilityAction, removeObject as removeObjectAction, } from 'actions/annotation-actions'; import { @@ -56,7 +56,7 @@ interface DispatchToProps { activateObject: (activatedStateID: number | null, activatedElementID: number | null) => void; removeObject: (objectState: any) => void; copyShape: (objectState: any) => void; - propagateObject: (objectState: any) => void; + switchPropagateVisibility: (visible: boolean) => void; changeGroupColor(group: number, color: string): void; } @@ -118,8 +118,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { dispatch(copyShapeAction(objectState)); dispatch(pasteShapeAsync()); }, - propagateObject(objectState: any): void { - dispatch(propagateObjectAction(objectState)); + switchPropagateVisibility(visible: boolean): void { + dispatch(switchPropagateVisibilityAction(visible)); }, changeGroupColor(group: number, color: string): void { dispatch(changeGroupColorAsync(group, color)); @@ -137,9 +137,9 @@ class ObjectItemContainer extends React.PureComponent { }; private propagate = (): void => { - const { objectState, readonly, propagateObject } = this.props; + const { switchPropagateVisibility, readonly } = this.props; if (!readonly) { - propagateObject(objectState); + switchPropagateVisibility(true); } }; 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 bd9b103f13d1..feb2c8788221 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 @@ -16,7 +16,7 @@ import { collapseObjectItems, changeGroupColorAsync, copyShape as copyShapeAction, - propagateObject as propagateObjectAction, + switchPropagateVisibility as switchPropagateVisibilityAction, removeObject as removeObjectAction, } from 'actions/annotation-actions'; import isAbleToChangeFrame from 'utils/is-able-to-change-frame'; @@ -53,7 +53,7 @@ interface DispatchToProps { collapseStates(states: any[], value: boolean): void; removeObject: (objectState: any, force: boolean) => void; copyShape: (objectState: any) => void; - propagateObject: (objectState: any) => void; + switchPropagateVisibility: (visible: boolean) => void; changeFrame(frame: number): void; changeGroupColor(group: number, color: string): void; } @@ -135,8 +135,8 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { copyShape(objectState: ObjectState): void { dispatch(copyShapeAction(objectState)); }, - propagateObject(objectState: ObjectState): void { - dispatch(propagateObjectAction(objectState)); + switchPropagateVisibility(visible: boolean): void { + dispatch(switchPropagateVisibilityAction(visible)); }, changeFrame(frame: number): void { dispatch(changeFrameAsync(frame)); @@ -280,7 +280,7 @@ class ObjectsListContainer extends React.PureComponent { changeGroupColor, removeObject, copyShape, - propagateObject, + switchPropagateVisibility, changeFrame, } = this.props; const { objectStates, sortedStatesID, statesOrdering } = this.state; @@ -448,7 +448,7 @@ class ObjectsListContainer extends React.PureComponent { preventDefault(event); const state = activatedState(); if (state && !readonly) { - propagateObject(state); + switchPropagateVisibility(true); } }, NEXT_KEY_FRAME: (event: KeyboardEvent | undefined) => { diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/propagate-confirm.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/propagate-confirm.tsx deleted file mode 100644 index 42617c299836..000000000000 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/propagate-confirm.tsx +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (C) 2020-2022 Intel Corporation -// -// SPDX-License-Identifier: MIT - -import React from 'react'; -import { connect } from 'react-redux'; - -import { - propagateObject as propagateObjectAction, - changePropagateFrames as changePropagateFramesAction, - propagateObjectAsync, -} from 'actions/annotation-actions'; - -import { CombinedState } from 'reducers'; -import PropagateConfirmComponent from 'components/annotation-page/standard-workspace/propagate-confirm'; - -interface StateToProps { - objectState: any | null; - frameNumber: number; - stopFrame: number; - propagateFrames: number; - jobInstance: any; -} - -interface DispatchToProps { - cancel(): void; - propagateObject(sessionInstance: any, objectState: any, from: number, to: number): void; - changePropagateFrames(frames: number): void; -} - -function mapStateToProps(state: CombinedState): StateToProps { - const { - annotation: { - propagate: { objectState, frames: propagateFrames }, - job: { - instance: { stopFrame }, - instance: jobInstance, - }, - player: { - frame: { number: frameNumber }, - }, - }, - } = state; - - return { - objectState, - frameNumber, - stopFrame, - propagateFrames, - jobInstance, - }; -} - -function mapDispatchToProps(dispatch: any): DispatchToProps { - return { - propagateObject(sessionInstance: any, objectState: any, from: number, to: number): void { - dispatch(propagateObjectAsync(sessionInstance, objectState, from, to)); - }, - changePropagateFrames(frames: number): void { - dispatch(changePropagateFramesAction(frames)); - }, - cancel(): void { - dispatch(propagateObjectAction(null)); - }, - }; -} - -type Props = StateToProps & DispatchToProps; -class PropagateConfirmContainer extends React.PureComponent { - private propagateObject = (): void => { - const { - propagateObject, objectState, propagateFrames, frameNumber, stopFrame, jobInstance, - } = this.props; - - const propagateUpToFrame = Math.min(frameNumber + propagateFrames, stopFrame); - propagateObject(jobInstance, objectState, frameNumber + 1, propagateUpToFrame); - }; - - private changePropagateFrames = (value: number): void => { - const { changePropagateFrames } = this.props; - changePropagateFrames(value); - }; - - private changeUpToFrame = (value: number): void => { - const { stopFrame, frameNumber, changePropagateFrames } = this.props; - - const propagateFrames = Math.max(0, Math.min(stopFrame, value)) - frameNumber; - changePropagateFrames(propagateFrames); - }; - - public render(): JSX.Element { - const { - frameNumber, stopFrame, propagateFrames, cancel, objectState, - } = this.props; - - const propagateUpToFrame = Math.min(frameNumber + propagateFrames, stopFrame); - - return ( - - ); - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(PropagateConfirmContainer); diff --git a/cvat-ui/src/reducers/annotation-reducer.ts b/cvat-ui/src/reducers/annotation-reducer.ts index e2d33d5f3a9c..e05625074f65 100644 --- a/cvat-ui/src/reducers/annotation-reducer.ts +++ b/cvat-ui/src/reducers/annotation-reducer.ts @@ -108,10 +108,6 @@ const defaultState: AnnotationState = { cur: 0, }, }, - propagate: { - objectState: null, - frames: 50, - }, remove: { objectState: null, force: false, @@ -121,6 +117,9 @@ const defaultState: AnnotationState = { collecting: false, data: null, }, + propagate: { + visible: false, + }, colors: [], sidebarCollapsed: false, appearanceCollapsed: false, @@ -830,16 +829,6 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { }, }; } - case AnnotationActionTypes.PROPAGATE_OBJECT: { - const { objectState } = action.payload; - return { - ...state, - propagate: { - ...state.propagate, - objectState, - }, - }; - } case AnnotationActionTypes.PROPAGATE_OBJECT_SUCCESS: { const { history } = action.payload; return { @@ -848,20 +837,14 @@ export default (state = defaultState, action: AnyAction): AnnotationState => { ...state.annotations, history, }, - propagate: { - ...state.propagate, - objectState: null, - }, }; } - case AnnotationActionTypes.CHANGE_PROPAGATE_FRAMES: { - const { frames } = action.payload; - + case AnnotationActionTypes.SWITCH_PROPAGATE_VISIBILITY: { + const { visible } = action.payload; return { ...state, propagate: { - ...state.propagate, - frames, + visible, }, }; } diff --git a/cvat-ui/src/reducers/index.ts b/cvat-ui/src/reducers/index.ts index fd69be0db3a2..e17ba9b47e67 100644 --- a/cvat-ui/src/reducers/index.ts +++ b/cvat-ui/src/reducers/index.ts @@ -720,10 +720,6 @@ export interface AnnotationState { cur: number; }; }; - propagate: { - objectState: any | null; - frames: number; - }; remove: { objectState: any; force: boolean; @@ -733,6 +729,9 @@ export interface AnnotationState { visible: boolean; data: any; }; + propagate: { + visible: boolean; + }; colors: any[]; filtersPanelVisible: boolean; sidebarCollapsed: boolean; diff --git a/tests/cypress/integration/actions_objects/case_53_object_propagate.js b/tests/cypress/integration/actions_objects/case_53_object_propagate.js index 4c9b6285c246..fd2dc8210ea4 100644 --- a/tests/cypress/integration/actions_objects/case_53_object_propagate.js +++ b/tests/cypress/integration/actions_objects/case_53_object_propagate.js @@ -1,4 +1,5 @@ // Copyright (C) 2021-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -17,8 +18,6 @@ context('Object propagate.', () => { secondX: 350, secondY: 450, }; - const propagateOnOneFrame = 1; - const propagateOnTwoFrames = 2; function startPropagation() { cy.get('#cvat-objects-sidebar-state-item-1').find('[aria-label="more"]').trigger('mouseover'); @@ -27,61 +26,88 @@ context('Object propagate.', () => { }); } + function setupUpToFrame(value) { + cy.get('.cvat-propagate-confirm-up-to-input') + .find('input') + .clear() + .type(value) + .blur() + .should('have.value', value); + } + + function setupPropagateFrames(value) { + cy.get('.cvat-propagate-confirm-object-on-frames') // Change value in the "copy of the object on frame" field + .find('input') + .clear() + .type(value) + .blur() + .should('have.value', value); + } + before(() => { cy.openTaskJob(taskName); + }); + + beforeEach(() => { + cy.removeAnnotations(); + cy.goCheckFrameNumber(0); cy.createCuboid(createCuboidShape2Points); }); describe(`Testing case "${caseId}"`, () => { it('On the 1st frame propagate object on 1 frame.', () => { + const FROM_FRAME = 0; + const PROPAGATE_FRAMES = 1; + startPropagation(); - cy.get('.cvat-propagate-confirm-object-on-frames') // Change value in the "copy of the object on frame" field - .find('input') - .clear() - .should('have.value', 1); - cy.get('.cvat-propagate-confirm-object-up-to-frame') // Value of "up to the frame" field should be same + setupPropagateFrames(PROPAGATE_FRAMES); + cy.get('.cvat-propagate-confirm-up-to-input') // Value of "up to the frame" field should be same .find('input') - .should('have.attr', 'value', propagateOnOneFrame); + .should('have.attr', 'value', FROM_FRAME + PROPAGATE_FRAMES); cy.contains('button', 'Yes').click(); - }); - it('On the 1st and 2nd frames, the number of objects is equal to 1. On the 3rd frame is 0.', () => { - cy.get('.cvat_canvas_shape_cuboid').then(($cuboidCountFirstFrame) => { - cy.goCheckFrameNumber(1); // Go to 2nd frame - cy.get('.cvat_canvas_shape_cuboid').then(($cuboidCountSecondFrame) => { - expect($cuboidCountFirstFrame.length).to.be.equal($cuboidCountSecondFrame.length); - }); - }); - cy.goCheckFrameNumber(2); // Go to 3rd frame + for (let i = FROM_FRAME; i <= FROM_FRAME + PROPAGATE_FRAMES; i++) { + cy.goCheckFrameNumber(i); + cy.get('.cvat_canvas_shape_cuboid').should('have.length', 1); + } + cy.goCheckFrameNumber(FROM_FRAME + PROPAGATE_FRAMES + 1); cy.get('.cvat_canvas_shape_cuboid').should('not.exist'); - cy.get('.cvat-player-first-button').click(); }); it('From the 1st frame propagate again on 2 frames.', () => { + const FROM_FRAME = 0; + const PROPAGATE_FRAMES = 2; + startPropagation(); - cy.get('.cvat-propagate-confirm-object-up-to-frame') // Change value in the "up to the frame" field - .find('input') - .clear() - .type(propagateOnTwoFrames) - .should('have.attr', 'value', propagateOnTwoFrames); + setupUpToFrame(FROM_FRAME + PROPAGATE_FRAMES); cy.get('.cvat-propagate-confirm-object-on-frames') // Value of "copy of the object on frames" field should be same .find('input') - .should('have.attr', 'value', propagateOnTwoFrames); + .should('have.attr', 'value', FROM_FRAME + PROPAGATE_FRAMES); cy.contains('button', 'Yes').click(); + + for (let i = FROM_FRAME; i <= FROM_FRAME + PROPAGATE_FRAMES; i++) { + cy.goCheckFrameNumber(i); + cy.get('.cvat_canvas_shape_cuboid').should('have.length', 1); + } + cy.goCheckFrameNumber(FROM_FRAME + PROPAGATE_FRAMES + 1); + cy.get('.cvat_canvas_shape_cuboid').should('not.exist'); }); - it('On the 1st and 3rd frames the number of objects is equal to 1. On the 2nd frame equal to 2. On the 4th frame equal to 0', () => { - cy.get('.cvat_canvas_shape_cuboid').then(($cuboidCountFirstFrame) => { - cy.goCheckFrameNumber(2); // Go to 3rd frame - cy.get('.cvat_canvas_shape_cuboid').then(($cuboidCountThirdFrame) => { - expect($cuboidCountFirstFrame.length).to.be.equal($cuboidCountThirdFrame.length); - }); - }); - cy.goCheckFrameNumber(1); // Go to 2nd frame - cy.get('.cvat_canvas_shape_cuboid').then(($cuboidCountSecondFrame) => { - expect($cuboidCountSecondFrame.length).to.be.equal(2); - }); - cy.goCheckFrameNumber(3); // Go to 4th frame + it('Testing propagate backward', () => { + cy.removeAnnotations(); + const FROM_FRAME = 4; + const UP_TO_FRAME = 1; + cy.goCheckFrameNumber(FROM_FRAME); + cy.createCuboid(createCuboidShape2Points); + startPropagation(); + setupUpToFrame(UP_TO_FRAME); + cy.contains('button', 'Yes').click(); + + for (let i = FROM_FRAME - 1; i >= UP_TO_FRAME; i--) { + cy.goCheckFrameNumber(i); + cy.get('.cvat_canvas_shape_cuboid').should('have.length', 1); + } + cy.goCheckFrameNumber(UP_TO_FRAME - 1); cy.get('.cvat_canvas_shape_cuboid').should('not.exist'); }); }); diff --git a/tests/cypress/integration/actions_tasks3/case_19_all_image_rotate_features.js b/tests/cypress/integration/actions_tasks3/case_19_all_image_rotate_features.js index 33b8de9b887b..7de491f522fa 100644 --- a/tests/cypress/integration/actions_tasks3/case_19_all_image_rotate_features.js +++ b/tests/cypress/integration/actions_tasks3/case_19_all_image_rotate_features.js @@ -14,11 +14,11 @@ context('Rotate all images feature.', () => { } function imageRotate(direction = 'anticlockwise', deg) { - cy.get('.cvat-rotate-canvas-control').trigger('mouseover'); + cy.get('.cvat-rotate-canvas-control').trigger('mouseover').should('be.visible'); if (direction === 'clockwise') { - cy.get('.cvat-rotate-canvas-controls-right').click(); + cy.get('.cvat-rotate-canvas-controls-right').should('be.visible').click(); } else { - cy.get('.cvat-rotate-canvas-controls-left').click(); + cy.get('.cvat-rotate-canvas-controls-left').should('be.visible').click(); } checkDegRotate(deg); } diff --git a/tests/cypress/integration/issues_prs2/issue_1785_propagation_latest_frame.js b/tests/cypress/integration/issues_prs2/issue_1785_propagation_latest_frame.js index 6cc1989cc9b4..847813466275 100644 --- a/tests/cypress/integration/issues_prs2/issue_1785_propagation_latest_frame.js +++ b/tests/cypress/integration/issues_prs2/issue_1785_propagation_latest_frame.js @@ -1,4 +1,5 @@ // Copyright (C) 2020-2022 Intel Corporation +// Copyright (C) 2022 CVAT.ai Corporation // // SPDX-License-Identifier: MIT @@ -11,7 +12,7 @@ context('Check propagation work from the latest frame', () => { const createRectangleShape2Points = { points: 'By 2 Points', type: 'Shape', - labelName: labelName, + labelName, firstX: 250, firstY: 350, secondX: 350, @@ -35,7 +36,9 @@ context('Check propagation work from the latest frame', () => { it('Try to propagate', () => { cy.get('#cvat_canvas_shape_1').trigger('mousemove'); cy.get('body').type('{ctrl}b'); - cy.get('.ant-modal-content').find('.ant-btn-primary').click(); + cy.get('.cvat-propagate-confirm-up-to-input').type(advancedConfigurationParams.segmentSize - 1); + cy.get('.ant-modal-content').find('.ant-btn-primary').should('be.disabled'); + cy.get('.ant-modal-content').find('.ant-btn-default').click(); cy.get('.ant-notification-notice').should('not.exist'); }); }); diff --git a/tests/cypress/integration/masks/masks_basics.js b/tests/cypress/integration/masks/masks_basics.js index 66081dc3cb26..fda94b6b9fa2 100644 --- a/tests/cypress/integration/masks/masks_basics.js +++ b/tests/cypress/integration/masks/masks_basics.js @@ -171,7 +171,7 @@ context('Manipulations with masks', { scrollBehavior: false }, () => { cy.get('.cvat-object-item-menu').within(() => { cy.contains('button', 'Propagate').click(); }); - cy.get('.cvat-propagate-confirm-object-up-to-frame').find('input') + cy.get('.cvat-propagate-confirm-up-to-input').find('input') .should('have.attr', 'value', serverFiles.length - 1); cy.contains('button', 'Yes').click(); for (let i = 1; i < serverFiles.length; i++) { diff --git a/tests/cypress/support/commands_organizations.js b/tests/cypress/support/commands_organizations.js index 7dcf6034a8b4..86c919126990 100644 --- a/tests/cypress/support/commands_organizations.js +++ b/tests/cypress/support/commands_organizations.js @@ -62,7 +62,9 @@ Cypress.Commands.add('activateOrganization', (organizationShortName) => { .find('[role="menuitem"]') .filter(':contains("Organization")') .trigger('mouseover'); - cy.contains('.cvat-header-menu-organization-item', organizationShortName).click(); + cy.contains('.cvat-header-menu-organization-item', organizationShortName) + .should('be.visible') + .click(); cy.get('.cvat-header-menu-user-dropdown').should('be.visible'); cy.get('.cvat-header-menu-user-dropdown-organization') .should('exist')