Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions cvat-ui/src/components/annotation-page/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,16 @@
display: block;
padding: $grid-unit-size * 2 $grid-unit-size * 3 $grid-unit-size $grid-unit-size * 2;
}

.cvat-filters-modal-navigation-checkbox {
padding: $grid-unit-size * 2 $grid-unit-size * 3;
margin-top: $grid-unit-size * 2;
border-top: 1px solid $border-color-1;

.ant-checkbox-wrapper {
font-size: 14px;
}
}
}
}

Expand Down
56 changes: 39 additions & 17 deletions cvat-ui/src/components/annotation-page/top-bar/filters-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import {
Builder, Config, ImmutableTree, JsonLogicTree, Query, Utils as QbUtils, AntdConfig, AntdWidgets,
Builder,
Config,
ImmutableTree,
JsonLogicTree,
Query,
Utils as QbUtils,
AntdConfig,
AntdWidgets,
} from '@react-awesome-query-builder/antd';

import { omit } from 'lodash';
Expand All @@ -15,9 +22,15 @@ import Popover from 'antd/lib/popover';
import Menu from 'antd/lib/menu';
import Button from 'antd/lib/button';
import Modal from 'antd/lib/modal';
import { CombinedState } from 'reducers';
import Checkbox from 'antd/lib/checkbox';
import { CombinedState, NavigationType } from 'reducers';
import { Label } from 'cvat-core-wrapper';
import { changeAnnotationsFilters, fetchAnnotationsAsync, showFilters } from 'actions/annotation-actions';
import {
changeAnnotationsFilters,
fetchAnnotationsAsync,
showFilters,
setNavigationType,
} from 'actions/annotation-actions';

const { FieldDropdown } = AntdWidgets;

Expand Down Expand Up @@ -74,11 +87,12 @@ const getAttributesSubfields = (labels: Label[]): Record<string, any> => {
};

function FiltersModalComponent(): JSX.Element {
const { labels, activeFilters, visible } = useSelector(
const { labels, activeFilters, visible, navigationType } = useSelector(
(state: CombinedState) => ({
labels: state.annotation.job.labels,
activeFilters: state.annotation.annotations.filters,
visible: state.annotation.filtersPanelVisible,
navigationType: state.annotation.player.navigationType,
}),
shallowEqual,
);
Expand All @@ -95,7 +109,7 @@ function FiltersModalComponent(): JSX.Element {
label: {
label: 'Label',
type: 'select',
valueSources: ['value'] as ('value')[],
valueSources: ['value'] as 'value'[],
fieldSettings: {
listValues: labels.map((label: any) => ({
value: label.name,
Expand Down Expand Up @@ -214,6 +228,10 @@ function FiltersModalComponent(): JSX.Element {
dispatch(showFilters(false));
};

const handleNavigationTypeChange = (checked: boolean): void => {
dispatch(setNavigationType(checked ? NavigationType.FILTERED : NavigationType.REGULAR));
};

const confirmModal = (): void => {
const currentFilter: StoredFilter = {
id: QbUtils.uuid(),
Expand All @@ -226,10 +244,9 @@ function FiltersModalComponent(): JSX.Element {
applyFilters([currentFilter.logic]);
};

const isModalConfirmable = (): boolean => (
(QbUtils.queryString(immutableTree, config) || '')
.trim().length > 0 && QbUtils.isValidTree(immutableTree, config)
);
const isModalConfirmable = (): boolean =>
(QbUtils.queryString(immutableTree, config) || '').trim().length > 0 &&
QbUtils.isValidTree(immutableTree, config);

const renderBuilder = (builderProps: any): JSX.Element => (
<div className='query-builder-container'>
Expand Down Expand Up @@ -319,24 +336,29 @@ function FiltersModalComponent(): JSX.Element {
overlayClassName='cvat-recently-used-filters-dropdown'
content={menu}
>
<Button
type='text'
className='cvat-filters-modal-recently-used-button'
>
Recently used
{' '}
<DownOutlined />
<Button type='text' className='cvat-filters-modal-recently-used-button'>
Recently used <DownOutlined />
</Button>
</Popover>
</div>
{ !!config.fields && (
{!!config.fields && (
<Query
{...config}
value={immutableTree as ImmutableTree}
onChange={onChange}
renderBuilder={renderBuilder}
/>
)}
{isModalConfirmable() && (
<div className='cvat-filters-modal-navigation-checkbox'>
<Checkbox
checked={navigationType === NavigationType.FILTERED}
onChange={(e) => handleNavigationTypeChange(e.target.checked)}
>
Filter frames only (navigate through filtered frames)
</Checkbox>
</div>
)}
</Modal>
);
}
Expand Down
104 changes: 62 additions & 42 deletions cvat-ui/src/components/annotation-page/top-bar/player-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ const componentShortcuts = {
sequences: ['space'],
scope: ShortcutScope.ANNOTATION_PAGE,
},
TOGGLE_FILTERED_NAV: {
name: 'Toggle filtered navigation',
description: 'Toggle between regular and filtered frame navigation',
sequences: ['shift+f'],
scope: ShortcutScope.ANNOTATION_PAGE,
},
};

registerComponentShortcuts(componentShortcuts);
Expand Down Expand Up @@ -142,7 +148,7 @@ function PlayerButtons(props: Props): JSX.Element {
onSelectChapter,
} = props;

const handlers: Partial<Record<keyof typeof componentShortcuts, ((event?: KeyboardEvent) => void)>> = {
const handlers: Partial<Record<keyof typeof componentShortcuts, (event?: KeyboardEvent) => void>> = {
NEXT_FRAME: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
onNextFrame();
Expand All @@ -151,36 +157,44 @@ function PlayerButtons(props: Props): JSX.Element {
event?.preventDefault();
onPrevFrame();
},
...(workspace !== Workspace.SINGLE_SHAPE ? {
FORWARD_FRAME: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
onForward();
},
BACKWARD_FRAME: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
onBackward();
},
SEARCH_FORWARD: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
onSearchAnnotations('forward');
},
SEARCH_BACKWARD: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
onSearchAnnotations('backward');
},
CHAPTER_BACKWARD: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
onSearchChapters('backward');
},
CHAPTER_FORWARD: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
onSearchChapters('forward');
},
PLAY_PAUSE: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
onSwitchPlay();
},
} : {}),
...(workspace !== Workspace.SINGLE_SHAPE
? {
FORWARD_FRAME: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
onForward();
},
BACKWARD_FRAME: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
onBackward();
},
SEARCH_FORWARD: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
onSearchAnnotations('forward');
},
SEARCH_BACKWARD: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
onSearchAnnotations('backward');
},
CHAPTER_BACKWARD: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
onSearchChapters('backward');
},
CHAPTER_FORWARD: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
onSearchChapters('forward');
},
PLAY_PAUSE: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
onSwitchPlay();
},
TOGGLE_FILTERED_NAV: (event: KeyboardEvent | undefined) => {
event?.preventDefault();
setNavigationType(
navigationType === NavigationType.FILTERED ? NavigationType.REGULAR : NavigationType.FILTERED,
);
},
}
: {}),
};

const prevRegularText = 'Go back';
Expand Down Expand Up @@ -210,7 +224,11 @@ function PlayerButtons(props: Props): JSX.Element {
prevButtonTooltipMessage = prevEmptyText;
} else if (navigationType === NavigationType.CHAPTER) {
prevButton = (
<Icon className='cvat-player-previous-button-chapter' component={PreviousChapterIcon} onClick={onPrevFrame} />
<Icon
className='cvat-player-previous-button-chapter'
component={PreviousChapterIcon}
onClick={onPrevFrame}
/>
);
prevButtonTooltipMessage = prevChapterText;
}
Expand All @@ -232,21 +250,23 @@ function PlayerButtons(props: Props): JSX.Element {
nextButtonTooltipMessage = nextChapterText;
}

const navIconStyle: CSSProperties = workspace === Workspace.SINGLE_SHAPE ? {
pointerEvents: 'none',
opacity: 0.5,
} : {};
const navIconStyle: CSSProperties =
workspace === Workspace.SINGLE_SHAPE
? {
pointerEvents: 'none',
opacity: 0.5,
}
: {};

return (
<Col className='cvat-player-buttons'>
<GlobalHotKeys keyMap={subKeyMap(componentShortcuts, keyMap)} handlers={handlers} />
{ (chapters.length > 0) && (
{chapters.length > 0 && (
<ChapterMenu
chapters={chapters}
onSelectChapter={onSelectChapter}
onHoveredChapter={onHoveredChapter}
/>

)}
<CVATTooltip title='Go to the first frame'>
<Icon
Expand All @@ -267,7 +287,7 @@ function PlayerButtons(props: Props): JSX.Element {
<Popover
trigger='contextMenu'
placement='bottom'
content={(
content={
<>
<CVATTooltip title={`${prevRegularText}`}>
<Icon
Expand Down Expand Up @@ -298,7 +318,7 @@ function PlayerButtons(props: Props): JSX.Element {
/>
</CVATTooltip>
</>
)}
}
>
<CVATTooltip placement='top' title={`${prevButtonTooltipMessage} ${previousFrameShortcut}`}>
{prevButton}
Expand Down Expand Up @@ -328,7 +348,7 @@ function PlayerButtons(props: Props): JSX.Element {
<Popover
trigger='contextMenu'
placement='bottom'
content={(
content={
<>
<CVATTooltip title={`${nextRegularText}`}>
<Icon
Expand Down Expand Up @@ -359,7 +379,7 @@ function PlayerButtons(props: Props): JSX.Element {
/>
</CVATTooltip>
</>
)}
}
>
<CVATTooltip placement='top' title={`${nextButtonTooltipMessage} ${nextFrameShortcut}`}>
{nextButton}
Expand Down