Merge pull request #2536 from openvinotoolkit/bs/antd4_migration

Migration from Antd 3.* to Antd 4.9
main
Boris Sekachev 5 years ago committed by GitHub
commit 8ae83c7b02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- PATCH requests from cvat-core submit only changed fields (<https://github.com/openvinotoolkit/cvat/pull/2445>)
- Migrated to Antd 4.9 (<https://github.com/openvinotoolkit/cvat/pull/2536>)
### Deprecated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.12.1",
"version": "1.13.0",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
@ -47,6 +47,7 @@
"worker-loader": "^2.0.0"
},
"dependencies": {
"@ant-design/icons": "^4.3.0",
"@types/lodash": "^4.14.165",
"@types/platform": "^1.3.3",
"@types/react": "^16.9.56",
@ -57,7 +58,7 @@
"@types/react-router-dom": "^5.1.6",
"@types/react-share": "^3.0.3",
"@types/redux-logger": "^3.0.8",
"antd": "^3.26.20",
"antd": "^4.9.1",
"copy-to-clipboard": "^3.3.1",
"cvat-canvas": "file:../cvat-canvas",
"cvat-core": "file:../cvat-core",

@ -18,7 +18,7 @@ $completed-progress-color: #61c200;
$inprogress-progress-color: #1890ff;
$pending-progress-color: #c1c1c1;
$border-color-1: #c3c3c3;
$border-color-2: #d9d9d9;
$border-color-2: rgb(240, 240, 240);
$border-color-3: #242424;
$border-color-hover: #40a9ff;
$background-color-1: white;

@ -4,9 +4,10 @@
import './styles.scss';
import React from 'react';
import Menu, { ClickParam } from 'antd/lib/menu';
import Menu from 'antd/lib/menu';
import Modal from 'antd/lib/modal';
// eslint-disable-next-line import/no-extraneous-dependencies
import { MenuInfo } from 'rc-menu/lib/interface';
import DumpSubmenu from './dump-submenu';
import LoadSubmenu from './load-submenu';
import ExportSubmenu from './export-submenu';
@ -22,7 +23,7 @@ interface Props {
exportActivities: string[] | null;
inferenceIsActive: boolean;
onClickMenu: (params: ClickParam, file?: File) => void;
onClickMenu: (params: MenuInfo, file?: File) => void;
}
export enum Actions {
@ -48,8 +49,8 @@ export default function ActionsMenuComponent(props: Props): JSX.Element {
loadActivity,
} = props;
let latestParams: ClickParam | null = null;
function onClickMenuWrapper(params: ClickParam | null, file?: File): void {
let latestParams: MenuInfo | null = null;
function onClickMenuWrapper(params: MenuInfo | null, file?: File): void {
const copyParams = params || latestParams;
if (!copyParams) {
return;
@ -67,7 +68,8 @@ export default function ActionsMenuComponent(props: Props): JSX.Element {
onClickMenu(copyParams, file);
},
okButtonProps: {
type: 'danger',
type: 'primary',
danger: true,
},
okText: 'Update',
});
@ -83,7 +85,8 @@ export default function ActionsMenuComponent(props: Props): JSX.Element {
onClickMenu(copyParams);
},
okButtonProps: {
type: 'danger',
type: 'primary',
danger: true,
},
okText: 'Delete',
});

@ -4,7 +4,7 @@
import React from 'react';
import Menu from 'antd/lib/menu';
import Icon from 'antd/lib/icon';
import { DownloadOutlined, LoadingOutlined } from '@ant-design/icons';
import Text from 'antd/lib/typography/Text';
function isDefaultFormat(dumperName: string, taskMode: string): boolean {
@ -35,11 +35,11 @@ export default function DumpSubmenu(props: Props): JSX.Element {
const isDefault = isDefaultFormat(dumper.name, taskMode);
return (
<Menu.Item key={dumper.name} disabled={disabled} className='cvat-menu-dump-submenu-item'>
<Icon type='download' />
<DownloadOutlined />
<Text strong={isDefault} disabled={disabled}>
{dumper.name}
</Text>
{pending && <Icon style={{ marginLeft: 10 }} type='loading' />}
{pending && <LoadingOutlined style={{ marginLeft: 10 }} />}
</Menu.Item>
);
},

@ -4,8 +4,8 @@
import React from 'react';
import Menu from 'antd/lib/menu';
import Icon from 'antd/lib/icon';
import Text from 'antd/lib/typography/Text';
import { ExportOutlined, LoadingOutlined } from '@ant-design/icons';
interface Props {
menuKey: string;
@ -30,9 +30,9 @@ export default function ExportSubmenu(props: Props): JSX.Element {
disabled={disabled}
className='cvat-menu-export-submenu-item'
>
<Icon type='export' />
<ExportOutlined />
<Text disabled={disabled}>{exporter.name}</Text>
{pending && <Icon style={{ marginLeft: 10 }} type='loading' />}
{pending && <LoadingOutlined style={{ marginLeft: 10 }} />}
</Menu.Item>
);
},

@ -4,10 +4,10 @@
import React from 'react';
import Menu from 'antd/lib/menu';
import Icon from 'antd/lib/icon';
import Upload from 'antd/lib/upload';
import Button from 'antd/lib/button';
import Text from 'antd/lib/typography/Text';
import { UploadOutlined, LoadingOutlined } from '@ant-design/icons';
interface Props {
menuKey: string;
@ -17,7 +17,9 @@ interface Props {
}
export default function LoadSubmenu(props: Props): JSX.Element {
const { menuKey, loaders, loadActivity, onFileUpload } = props;
const {
menuKey, loaders, loadActivity, onFileUpload,
} = props;
return (
<Menu.SubMenu key={menuKey} title='Upload annotations'>
@ -43,9 +45,9 @@ export default function LoadSubmenu(props: Props): JSX.Element {
}}
>
<Button block type='link' disabled={disabled}>
<Icon type='upload' />
<UploadOutlined />
<Text>{loader.name}</Text>
{pending && <Icon style={{ marginLeft: 10 }} type='loading' />}
{pending && <LoadingOutlined style={{ marginLeft: 10 }} />}
</Button>
</Upload>
</Menu.Item>

@ -20,7 +20,7 @@
.cvat-menu-load-submenu-item,
.cvat-menu-dump-submenu-item,
.cvat-menu-export-submenu-item {
> i {
> span[role='img'] {
color: $info-icon-color;
}

@ -10,7 +10,7 @@ import Text from 'antd/lib/typography/Text';
import Paragraph from 'antd/lib/typography/Paragraph';
import Tooltip from 'antd/lib/tooltip';
import Modal from 'antd/lib/modal';
import Icon from 'antd/lib/icon';
import { FilterOutlined } from '@ant-design/icons';
import {
changeAnnotationsFilters as changeAnnotationsFiltersAction,
@ -133,8 +133,7 @@ function AnnotationsFiltersInput(props: StateToProps & DispatchToProps): JSX.Ele
underCursor ? (
<>
<Tooltip title='Click to open help' mouseLeaveDelay={0}>
<Icon
type='filter'
<FilterOutlined
onClick={(e: React.MouseEvent) => {
e.stopPropagation();
Modal.info({
@ -148,7 +147,7 @@ function AnnotationsFiltersInput(props: StateToProps & DispatchToProps): JSX.Ele
</>
) : (
<>
<Icon style={{ transform: 'scale(0.9)' }} type='filter' />
<FilterOutlined style={{ transform: 'scale(0.9)' }} />
<span style={{ marginLeft: 5 }}>Annotations filters</span>
</>
)

@ -7,7 +7,7 @@ import { AnyAction } from 'redux';
import { connect } from 'react-redux';
import Text from 'antd/lib/typography/Text';
import Radio, { RadioChangeEvent } from 'antd/lib/radio';
import Slider, { SliderValue } from 'antd/lib/slider';
import Slider from 'antd/lib/slider';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
import Collapse from 'antd/lib/collapse';
@ -42,8 +42,8 @@ interface StateToProps {
interface DispatchToProps {
collapseAppearance(): void;
changeShapesColorBy(event: RadioChangeEvent): void;
changeShapesOpacity(event: SliderValue): void;
changeSelectedShapesOpacity(event: SliderValue): void;
changeShapesOpacity(value: number): void;
changeSelectedShapesOpacity(value: number): void;
changeShapesOutlinedBorders(outlined: boolean, color: string): void;
changeShowBitmap(event: CheckboxChangeEvent): void;
changeShowProjections(event: CheckboxChangeEvent): void;
@ -52,7 +52,7 @@ interface DispatchToProps {
export function computeHeight(): number {
const [sidebar] = window.document.getElementsByClassName('cvat-objects-sidebar');
const [appearance] = window.document.getElementsByClassName('cvat-objects-appearance-collapse');
const [tabs] = Array.from(window.document.querySelectorAll('.cvat-objects-sidebar-tabs > .ant-tabs-card-bar'));
const [tabs] = Array.from(window.document.querySelectorAll('.cvat-objects-sidebar-tabs > .ant-tabs-nav'));
if (sidebar && appearance && tabs) {
const maxHeight = sidebar ? sidebar.clientHeight : 0;
@ -107,11 +107,11 @@ function mapDispatchToProps(dispatch: Dispatch<AnyAction>): DispatchToProps {
changeShapesColorBy(event: RadioChangeEvent): void {
dispatch(changeShapesColorByAction(event.target.value));
},
changeShapesOpacity(value: SliderValue): void {
dispatch(changeShapesOpacityAction(value as number));
changeShapesOpacity(value: number): void {
dispatch(changeShapesOpacityAction(value));
},
changeSelectedShapesOpacity(value: SliderValue): void {
dispatch(changeSelectedShapesOpacityAction(value as number));
changeSelectedShapesOpacity(value: number): void {
dispatch(changeSelectedShapesOpacityAction(value));
},
changeShapesOutlinedBorders(outlined: boolean, color: string): void {
dispatch(changeShapesOutlinedBordersAction(outlined, color));

@ -9,7 +9,7 @@ import Layout, { SiderProps } from 'antd/lib/layout';
import { SelectValue } from 'antd/lib/select';
import { Row, Col } from 'antd/lib/grid';
import Text from 'antd/lib/typography/Text';
import Icon from 'antd/lib/icon';
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
import { ThunkDispatch } from 'utils/redux';
import { Canvas } from 'cvat-canvas-wrapper';
@ -291,15 +291,11 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
ant-layout-sider-zero-width-trigger-left`}
onClick={collapse}
>
{sidebarCollapsed ? (
<Icon type='menu-fold' title='Show' />
) : (
<Icon type='menu-unfold' title='Hide' />
)}
{sidebarCollapsed ? <MenuFoldOutlined title='Show' /> : <MenuUnfoldOutlined title='Hide' />}
</span>
<GlobalHotKeys keyMap={subKeyMap} handlers={handlers} allowChanges />
<Row className='cvat-objects-sidebar-filter-input'>
<Col>
<Row>
<Col span={24}>
<AnnotationsFiltersInput />
</Col>
</Row>
@ -323,6 +319,7 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
}}
/>
<ObjectButtonsContainer
readonly={false}
clientID={activeObjectState.clientID}
outsideDisabled
hiddenDisabled
@ -374,10 +371,10 @@ function AttributeAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.
ant-layout-sider-zero-width-trigger-left`}
onClick={collapse}
>
{sidebarCollapsed ? <Icon type='menu-fold' title='Show' /> : <Icon type='menu-unfold' title='Hide' />}
{sidebarCollapsed ? <MenuFoldOutlined title='Show' /> : <MenuUnfoldOutlined title='Hide' />}
</span>
<Row className='cvat-objects-sidebar-filter-input'>
<Col>
<Row>
<Col span={24}>
<AnnotationsFiltersInput />
</Col>
</Row>

@ -22,7 +22,9 @@ interface InputElementParameters {
}
function renderInputElement(parameters: InputElementParameters): JSX.Element {
const { inputType, attrID, clientID, values, currentValue, onChange } = parameters;
const {
inputType, attrID, clientID, values, currentValue, onChange,
} = parameters;
const renderCheckbox = (): JSX.Element => (
<>
@ -246,7 +248,9 @@ interface Props {
}
function AttributeEditor(props: Props): JSX.Element {
const { attribute, currentValue, onChange, clientID } = props;
const {
attribute, currentValue, onChange, clientID,
} = props;
const { inputType, values, id: attrID } = attribute;
return (

@ -3,10 +3,10 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import Icon from 'antd/lib/icon';
import Text from 'antd/lib/typography/Text';
import Tooltip from 'antd/lib/tooltip';
import Button from 'antd/lib/button';
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
interface Props {
currentAttribute: string;
@ -17,14 +17,16 @@ interface Props {
}
function AttributeSwitcher(props: Props): JSX.Element {
const { currentAttribute, currentIndex, attributesCount, nextAttribute, normalizedKeyMap } = props;
const {
currentAttribute, currentIndex, attributesCount, nextAttribute, normalizedKeyMap,
} = props;
const title = `${currentAttribute} [${currentIndex + 1}/${attributesCount}]`;
return (
<div className='attribute-annotation-sidebar-attribute-switcher'>
<Tooltip title={`Previous attribute ${normalizedKeyMap.PREVIOUS_ATTRIBUTE}`} mouseLeaveDelay={0}>
<Button disabled={attributesCount <= 1} onClick={() => nextAttribute(-1)}>
<Icon type='left' />
<LeftOutlined />
</Button>
</Tooltip>
<Tooltip title={title} mouseLeaveDelay={0}>
@ -33,7 +35,7 @@ function AttributeSwitcher(props: Props): JSX.Element {
</Tooltip>
<Tooltip title={`Next attribute ${normalizedKeyMap.NEXT_ATTRIBUTE}`} mouseLeaveDelay={0}>
<Button disabled={attributesCount <= 1} onClick={() => nextAttribute(1)}>
<Icon type='right' />
<RightOutlined />
</Button>
</Tooltip>
</div>

@ -3,10 +3,10 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import Icon from 'antd/lib/icon';
import Text from 'antd/lib/typography/Text';
import Tooltip from 'antd/lib/tooltip';
import Button from 'antd/lib/button';
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
interface Props {
currentLabel: string;
@ -19,14 +19,16 @@ interface Props {
}
function ObjectSwitcher(props: Props): JSX.Element {
const { currentLabel, clientID, objectsCount, currentIndex, nextObject, normalizedKeyMap } = props;
const {
currentLabel, clientID, objectsCount, currentIndex, nextObject, normalizedKeyMap,
} = props;
const title = `${currentLabel} ${clientID} [${currentIndex + 1}/${objectsCount}]`;
return (
<div className='attribute-annotation-sidebar-object-switcher'>
<Tooltip title={`Previous object ${normalizedKeyMap.PREVIOUS_OBJECT}`} mouseLeaveDelay={0}>
<Button disabled={objectsCount <= 1} onClick={() => nextObject(-1)}>
<Icon type='left' />
<LeftOutlined />
</Button>
</Tooltip>
<Tooltip title={title} mouseLeaveDelay={0}>
@ -36,7 +38,7 @@ function ObjectSwitcher(props: Props): JSX.Element {
</Tooltip>
<Tooltip title={`Next object ${normalizedKeyMap.NEXT_OBJECT}`} mouseLeaveDelay={0}>
<Button disabled={objectsCount <= 1} onClick={() => nextObject(1)}>
<Icon type='right' />
<RightOutlined />
</Button>
</Tooltip>
</div>

@ -27,7 +27,7 @@
overflow: hidden;
}
> button > i {
> button > span[role='img'] {
color: $objects-bar-icons-color;
}
}

@ -4,7 +4,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Menu, { ClickParam } from 'antd/lib/menu';
import Menu from 'antd/lib/menu';
// eslint-disable-next-line import/no-extraneous-dependencies
import { MenuInfo } from 'rc-menu/lib/interface';
import ObjectItemContainer from 'containers/annotation-page/standard-workspace/objects-side-bar/object-item';
import { Workspace } from 'reducers/interfaces';
@ -27,7 +29,7 @@ interface ReviewContextMenuProps {
top: number;
left: number;
latestComments: string[];
onClick: (param: ClickParam) => void;
onClick: (param: MenuInfo) => void;
}
enum ReviewContextMenuKeys {
@ -95,7 +97,7 @@ export default function CanvasContextMenu(props: Props): JSX.Element | null {
top={top}
left={left}
latestComments={latestComments}
onClick={(param: ClickParam) => {
onClick={(param: MenuInfo) => {
const [state] = objectStates.filter(
(_state: any): boolean => _state.clientID === contextMenuClientID,
);

@ -6,6 +6,7 @@ import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import Button from 'antd/lib/button';
import Tooltip from 'antd/lib/tooltip';
import { DeleteOutlined, EnvironmentOutlined } from '@ant-design/icons';
import { connect } from 'react-redux';
import { CombinedState, ContextMenuType } from 'reducers/interfaces';
@ -103,12 +104,12 @@ function CanvasPointContextMenu(props: Props): React.ReactPortal | null {
ReactDOM.createPortal(
<div className='cvat-canvas-point-context-menu' style={{ top, left }}>
<Tooltip title='Delete point [Alt + dblclick]' mouseLeaveDelay={0}>
<Button type='link' icon='delete' onClick={onPointDelete}>
<Button type='link' icon={<DeleteOutlined />} onClick={onPointDelete}>
Delete point
</Button>
</Tooltip>
{contextMenuFor && contextMenuFor.shapeType === 'polygon' && (
<Button type='link' icon='environment' onClick={onSetStartPoint}>
<Button type='link' icon={<EnvironmentOutlined />} onClick={onSetStartPoint}>
Set start point
</Button>
)}

@ -6,9 +6,8 @@ import React from 'react';
import { GlobalHotKeys, ExtendedKeyMapOptions } from 'react-hotkeys';
import Tooltip from 'antd/lib/tooltip';
import Icon from 'antd/lib/icon';
import Layout from 'antd/lib/layout/layout';
import Slider, { SliderValue } from 'antd/lib/slider';
import Layout from 'antd/lib/layout';
import Slider from 'antd/lib/slider';
import {
ColorBy, GridColor, ObjectType, ContextMenuType, Workspace, ShapeType,
@ -17,6 +16,7 @@ import { LogType } from 'cvat-logger';
import { Canvas } from 'cvat-canvas-wrapper';
import getCore from 'cvat-core-wrapper';
import consts from 'consts';
import { PlusCircleOutlined } from '@ant-design/icons';
const cvat = getCore();
@ -906,10 +906,10 @@ export default class CanvasWrapperComponent extends React.PureComponent<Props> {
vertical
reverse
defaultValue={0}
onChange={(value: SliderValue): void => onSwitchZLayer(value as number)}
onChange={(value: number): void => onSwitchZLayer(value as number)}
/>
<Tooltip title={`Add new layer ${maxZLayer + 1} and switch to it`} mouseLeaveDelay={0}>
<Icon type='plus-circle' onClick={onAddZLayer} />
<PlusCircleOutlined onClick={onAddZLayer} />
</Tooltip>
</div>
</Layout.Content>

@ -43,12 +43,12 @@ export default function RequestReviewModal(): JSX.Element | null {
onOk={submitAnnotations}
okText='Submit'
>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col>
<Title level={4}>Assign a user who is responsible for review</Title>
</Col>
</Row>
<Row align='middle' type='flex' justify='start'>
<Row align='middle' justify='start'>
<Col>
<Text type='secondary'>Reviewer: </Text>
</Col>
@ -56,7 +56,7 @@ export default function RequestReviewModal(): JSX.Element | null {
<UserSelector value={reviewer} onSelect={setReviewer} />
</Col>
</Row>
<Row type='flex' justify='start'>
<Row justify='start'>
<Text type='secondary'>You might not be able to change the job after this action. Continue?</Text>
</Row>
</Modal>

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import Tooltip from 'antd/lib/tooltip';
import { ActiveControl } from 'reducers/interfaces';

@ -5,47 +5,36 @@
import React, { ReactPortal } from 'react';
import ReactDOM from 'react-dom';
import { useDispatch } from 'react-redux';
import Form, { FormComponentProps } from 'antd/lib/form';
import Form from 'antd/lib/form';
import Input from 'antd/lib/input';
import Button from 'antd/lib/button';
import { Row, Col } from 'antd/lib/grid';
import { reviewActions, finishIssueAsync } from 'actions/review-actions';
import { Store } from 'antd/lib/form/interface';
type FormProps = {
interface FormProps {
top: number;
left: number;
submit(message: string): void;
cancel(): void;
} & FormComponentProps;
}
function MessageForm(props: FormProps): JSX.Element {
const {
form: { getFieldDecorator },
form,
top,
left,
submit,
cancel,
top, left, submit, cancel,
} = props;
function handleSubmit(e: React.FormEvent): void {
e.preventDefault();
form.validateFields((error, values): void => {
if (!error) {
submit(values.issue_description);
}
});
function handleSubmit(values: Store): void {
submit(values.issue_description);
}
return (
<Form className='cvat-create-issue-dialog' style={{ top, left }} onSubmit={handleSubmit}>
<Form.Item>
{getFieldDecorator('issue_description', {
rules: [{ required: true, message: 'Please, fill out the field' }],
})(<Input autoComplete='off' placeholder='Please, describe the issue' />)}
<Form className='cvat-create-issue-dialog' style={{ top, left }} onFinish={handleSubmit}>
<Form.Item name='issue_description' rules={[{ required: true, message: 'Please, fill out the field' }]}>
<Input autoComplete='off' placeholder='Please, describe the issue' />
</Form.Item>
<Row type='flex' justify='space-between'>
<Row justify='space-between'>
<Col>
<Button onClick={cancel} type='ghost'>
Cancel
@ -61,8 +50,6 @@ function MessageForm(props: FormProps): JSX.Element {
);
}
const WrappedMessageForm = Form.create<FormProps>()(MessageForm);
interface Props {
top: number;
left: number;
@ -73,7 +60,7 @@ export default function CreateIssueDialog(props: Props): ReactPortal {
const { top, left } = props;
return ReactDOM.createPortal(
<WrappedMessageForm
<MessageForm
top={top}
left={left}
submit={(message: string) => {

@ -5,8 +5,8 @@
import React, { ReactPortal, useEffect } from 'react';
import ReactDOM from 'react-dom';
import Tag from 'antd/lib/tag';
import Icon from 'antd/lib/icon';
import Tooltip from 'antd/lib/tooltip';
import { CheckOutlined, CloseCircleOutlined } from '@ant-design/icons';
interface Props {
id: number;
@ -44,9 +44,9 @@ export default function HiddenIssueLabel(props: Props): ReactPortal {
className='cvat-hidden-issue-label'
>
{resolved ? (
<Icon className='cvat-hidden-issue-resolved-indicator' type='check' />
<CheckOutlined className='cvat-hidden-issue-resolved-indicator' />
) : (
<Icon className='cvat-hidden-issue-unsolved-indicator' type='close-circle' />
<CloseCircleOutlined className='cvat-hidden-issue-unsolved-indicator' />
)}
{message}
</Tag>

@ -5,13 +5,13 @@
import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
import { Row, Col } from 'antd/lib/grid';
import { CloseOutlined } from '@ant-design/icons';
import Comment from 'antd/lib/comment';
import Text from 'antd/lib/typography/Text';
import Title from 'antd/lib/typography/Title';
import Tooltip from 'antd/lib/tooltip';
import Button from 'antd/lib/button';
import Input from 'antd/lib/input';
import Icon from 'antd/lib/icon';
import moment from 'moment';
interface Props {
@ -88,20 +88,20 @@ export default function IssueDialog(props: Props): JSX.Element {
return ReactDOM.createPortal(
<div style={{ top, left }} ref={ref} className='cvat-issue-dialog'>
<Row className='cvat-issue-dialog-header' type='flex' justify='space-between'>
<Row className='cvat-issue-dialog-header' justify='space-between'>
<Col>
<Title level={4}>{id >= 0 ? `Issue #${id}` : 'Issue'}</Title>
</Col>
<Col>
<Tooltip title='Collapse the chat'>
<Icon type='close' onClick={collapse} />
<CloseOutlined onClick={collapse} />
</Tooltip>
</Col>
</Row>
<Row className='cvat-issue-dialog-chat' type='flex' justify='start'>
<Row className='cvat-issue-dialog-chat' justify='start'>
<Col style={{ display: 'block' }}>{lines}</Col>
</Row>
<Row className='cvat-issue-dialog-input' type='flex' justify='start'>
<Row className='cvat-issue-dialog-input' justify='start'>
<Col span={24}>
<Input
placeholder='Print a comment here..'
@ -118,7 +118,7 @@ export default function IssueDialog(props: Props): JSX.Element {
/>
</Col>
</Row>
<Row className='cvat-issue-dialog-footer' type='flex' justify='end'>
<Row className='cvat-issue-dialog-footer' justify='end'>
<Col>
{currentText.length ? (
<Button

@ -86,12 +86,12 @@ export default function SubmitReviewModal(): JSX.Element | null {
okText='Submit'
width={650}
>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col>
<Title level={4}>Submitting your review</Title>
</Col>
</Row>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col span={12}>
<Description title='Review summary' layout='horizontal' column={1} size='small' bordered>
<Description.Item label='Estimated quality: '>{estimatedQuality}</Description.Item>
@ -119,16 +119,16 @@ export default function SubmitReviewModal(): JSX.Element | null {
<RadioButton value={ReviewStatus.REJECTED}>Reject</RadioButton>
</Radio.Group>
{reviewStatus === ReviewStatus.REVIEW_FURTHER && (
<Row align='middle' type='flex' justify='start'>
<Col>
<Row align='middle' justify='start'>
<Col span={7}>
<Text type='secondary'>Reviewer: </Text>
</Col>
<Col offset={1}>
<Col span={16} offset={1}>
<UserSelector value={reviewer} onSelect={setReviewer} />
</Col>
</Row>
)}
<Row type='flex' justify='center' align='middle'>
<Row justify='center' align='middle'>
<Col>
<Rate
value={Math.round(estimatedQuality)}

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import Tooltip from 'antd/lib/tooltip';
import { CursorIcon } from 'icons';
@ -24,9 +24,9 @@ function CursorControl(props: Props): JSX.Element {
<Icon
component={CursorIcon}
className={
activeControl === ActiveControl.CURSOR
? 'cvat-active-canvas-control cvat-cursor-control'
: 'cvat-cursor-control'
activeControl === ActiveControl.CURSOR ?
'cvat-active-canvas-control cvat-cursor-control' :
'cvat-cursor-control'
}
onClick={activeControl !== ActiveControl.CURSOR ? (): void => canvasInstance.cancel() : undefined}
/>

@ -4,7 +4,7 @@
import React from 'react';
import Popover from 'antd/lib/popover';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { ShapeType } from 'reducers/interfaces';
@ -21,22 +21,24 @@ interface Props {
function DrawPolygonControl(props: Props): JSX.Element {
const { canvasInstance, isDrawing } = props;
const dynamcPopoverPros = isDrawing
? {
overlayStyle: {
display: 'none',
},
}
: {};
const dynamicIconProps = isDrawing
? {
className: 'cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.draw({ enabled: false });
},
}
: {};
const dynamcPopoverPros = isDrawing ?
{
overlayStyle: {
display: 'none',
},
} :
{};
const dynamicIconProps = isDrawing ?
{
className: 'cvat-draw-cuboid-control cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.draw({ enabled: false });
},
} :
{
className: 'cvat-draw-cuboid-control',
};
return (
<Popover
@ -45,7 +47,7 @@ function DrawPolygonControl(props: Props): JSX.Element {
placement='right'
content={<DrawShapePopoverContainer shapeType={ShapeType.CUBOID} />}
>
<Icon className='cvat-draw-cuboid-control' {...dynamicIconProps} component={CubeIcon} />
<Icon {...dynamicIconProps} component={CubeIcon} />
</Popover>
);
}

@ -4,7 +4,7 @@
import React from 'react';
import Popover from 'antd/lib/popover';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { PointIcon } from 'icons';
@ -20,22 +20,24 @@ interface Props {
function DrawPointsControl(props: Props): JSX.Element {
const { canvasInstance, isDrawing } = props;
const dynamcPopoverPros = isDrawing
? {
overlayStyle: {
display: 'none',
},
}
: {};
const dynamicIconProps = isDrawing
? {
className: 'cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.draw({ enabled: false });
},
}
: {};
const dynamcPopoverPros = isDrawing ?
{
overlayStyle: {
display: 'none',
},
} :
{};
const dynamicIconProps = isDrawing ?
{
className: 'cvat-draw-points-control cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.draw({ enabled: false });
},
} :
{
className: 'cvat-draw-points-control',
};
return (
<Popover
@ -44,7 +46,7 @@ function DrawPointsControl(props: Props): JSX.Element {
placement='right'
content={<DrawShapePopoverContainer shapeType={ShapeType.POINTS} />}
>
<Icon className='cvat-draw-points-control' {...dynamicIconProps} component={PointIcon} />
<Icon {...dynamicIconProps} component={PointIcon} />
</Popover>
);
}

@ -4,7 +4,7 @@
import React from 'react';
import Popover from 'antd/lib/popover';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { PolygonIcon } from 'icons';
@ -20,22 +20,24 @@ interface Props {
function DrawPolygonControl(props: Props): JSX.Element {
const { canvasInstance, isDrawing } = props;
const dynamcPopoverPros = isDrawing
? {
overlayStyle: {
display: 'none',
},
}
: {};
const dynamicIconProps = isDrawing
? {
className: 'cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.draw({ enabled: false });
},
}
: {};
const dynamcPopoverPros = isDrawing ?
{
overlayStyle: {
display: 'none',
},
} :
{};
const dynamicIconProps = isDrawing ?
{
className: 'cvat-draw-polygon-control cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.draw({ enabled: false });
},
} :
{
className: 'cvat-draw-polygon-control',
};
return (
<Popover
@ -44,7 +46,7 @@ function DrawPolygonControl(props: Props): JSX.Element {
placement='right'
content={<DrawShapePopoverContainer shapeType={ShapeType.POLYGON} />}
>
<Icon className='cvat-draw-polygon-control' {...dynamicIconProps} component={PolygonIcon} />
<Icon {...dynamicIconProps} component={PolygonIcon} />
</Popover>
);
}

@ -4,7 +4,7 @@
import React from 'react';
import Popover from 'antd/lib/popover';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { PolylineIcon } from 'icons';
@ -20,22 +20,24 @@ interface Props {
function DrawPolylineControl(props: Props): JSX.Element {
const { canvasInstance, isDrawing } = props;
const dynamcPopoverPros = isDrawing
? {
overlayStyle: {
display: 'none',
},
}
: {};
const dynamicIconProps = isDrawing
? {
className: 'cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.draw({ enabled: false });
},
}
: {};
const dynamcPopoverPros = isDrawing ?
{
overlayStyle: {
display: 'none',
},
} :
{};
const dynamicIconProps = isDrawing ?
{
className: 'cvat-draw-polyline-control cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.draw({ enabled: false });
},
} :
{
className: 'cvat-draw-polyline-control',
};
return (
<Popover
@ -44,7 +46,7 @@ function DrawPolylineControl(props: Props): JSX.Element {
placement='right'
content={<DrawShapePopoverContainer shapeType={ShapeType.POLYLINE} />}
>
<Icon className='cvat-draw-polyline-control' {...dynamicIconProps} component={PolylineIcon} />
<Icon {...dynamicIconProps} component={PolylineIcon} />
</Popover>
);
}

@ -4,7 +4,7 @@
import React from 'react';
import Popover from 'antd/lib/popover';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { RectangleIcon } from 'icons';
@ -20,22 +20,24 @@ interface Props {
function DrawRectangleControl(props: Props): JSX.Element {
const { canvasInstance, isDrawing } = props;
const dynamcPopoverPros = isDrawing
? {
overlayStyle: {
display: 'none',
},
}
: {};
const dynamicIconProps = isDrawing
? {
className: 'cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.draw({ enabled: false });
},
}
: {};
const dynamcPopoverPros = isDrawing ?
{
overlayStyle: {
display: 'none',
},
} :
{};
const dynamicIconProps = isDrawing ?
{
className: 'cvat-draw-rectangle-control cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.draw({ enabled: false });
},
} :
{
className: 'cvat-draw-rectangle-control',
};
return (
<Popover
@ -44,7 +46,7 @@ function DrawRectangleControl(props: Props): JSX.Element {
placement='right'
content={<DrawShapePopoverContainer shapeType={ShapeType.RECTANGLE} />}
>
<Icon className='cvat-draw-rectangle-control' {...dynamicIconProps} component={RectangleIcon} />
<Icon {...dynamicIconProps} component={RectangleIcon} />
</Popover>
);
}

@ -52,17 +52,17 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
return (
<div className='cvat-draw-shape-popover-content'>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col>
<Text className='cvat-text-color' strong>{`Draw new ${shapeType}`}</Text>
</Col>
</Row>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col>
<Text className='cvat-text-color'>Label</Text>
</Col>
</Row>
<Row type='flex' justify='center'>
<Row justify='center'>
<Col span={24}>
<LabelSelector
style={{ width: '100%' }}
@ -79,7 +79,7 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
<Text className='cvat-text-color'> Drawing method </Text>
</Col>
</Row>
<Row type='flex' justify='space-around'>
<Row justify='space-around'>
<Col>
<Radio.Group
style={{ display: 'flex' }}
@ -104,7 +104,7 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
<Text className='cvat-text-color'> Drawing method </Text>
</Col>
</Row>
<Row type='flex' justify='space-around'>
<Row justify='space-around'>
<Col>
<Radio.Group
style={{ display: 'flex' }}
@ -123,15 +123,15 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
</>
)}
{shapeType !== ShapeType.RECTANGLE && shapeType !== ShapeType.CUBOID && (
<Row type='flex' justify='space-around' align='middle'>
<Row justify='space-around' align='middle'>
<Col span={14}>
<Text className='cvat-text-color'> Number of points: </Text>
</Col>
<Col span={10}>
<InputNumber
onChange={(value: number | undefined) => {
if (typeof value === 'number') {
onChangePoints(Math.floor(clamp(value, minimumPoints, Number.MAX_SAFE_INTEGER)));
onChange={(value: number | undefined | string) => {
if (typeof value !== 'undefined') {
onChangePoints(Math.floor(clamp(+value, minimumPoints, Number.MAX_SAFE_INTEGER)));
} else if (!value) {
onChangePoints(undefined);
}
@ -144,7 +144,7 @@ function DrawShapePopoverComponent(props: Props): JSX.Element {
</Col>
</Row>
)}
<Row type='flex' justify='space-around'>
<Row justify='space-around'>
<Col span={12}>
<Tooltip title={`Press ${repeatShapeShortcut} to draw again`} mouseLeaveDelay={0}>
<Button onClick={onDrawShape}>Shape</Button>

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import Tooltip from 'antd/lib/tooltip';
import { FitIcon } from 'icons';

@ -4,7 +4,7 @@
import React from 'react';
import Tooltip from 'antd/lib/tooltip';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import { GroupIcon } from 'icons';
import { Canvas } from 'cvat-canvas-wrapper';
@ -22,22 +22,20 @@ function GroupControl(props: Props): JSX.Element {
const { switchGroupShortcut, resetGroupShortcut, activeControl, canvasInstance, groupObjects } = props;
const dynamicIconProps =
activeControl === ActiveControl.GROUP
? {
className: 'cvat-group-control cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.group({ enabled: false });
groupObjects(false);
},
}
: {
className: 'cvat-group-control',
onClick: (): void => {
canvasInstance.cancel();
canvasInstance.group({ enabled: true });
groupObjects(true);
},
};
activeControl === ActiveControl.GROUP ? {
className: 'cvat-group-control cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.group({ enabled: false });
groupObjects(false);
},
} : {
className: 'cvat-group-control',
onClick: (): void => {
canvasInstance.cancel();
canvasInstance.group({ enabled: true });
groupObjects(true);
},
};
const title =
`Group shapes/tracks ${switchGroupShortcut}.` + ` Select and press ${resetGroupShortcut} to reset a group`;

@ -4,7 +4,7 @@
import React from 'react';
import Tooltip from 'antd/lib/tooltip';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import { MergeIcon } from 'icons';
import { Canvas } from 'cvat-canvas-wrapper';
@ -21,22 +21,20 @@ function MergeControl(props: Props): JSX.Element {
const { switchMergeShortcut, activeControl, canvasInstance, mergeObjects } = props;
const dynamicIconProps =
activeControl === ActiveControl.MERGE
? {
className: 'cvat-merge-control cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.merge({ enabled: false });
mergeObjects(false);
},
}
: {
className: 'cvat-merge-control',
onClick: (): void => {
canvasInstance.cancel();
canvasInstance.merge({ enabled: true });
mergeObjects(true);
},
};
activeControl === ActiveControl.MERGE ? {
className: 'cvat-merge-control cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.merge({ enabled: false });
mergeObjects(false);
},
} : {
className: 'cvat-merge-control',
onClick: (): void => {
canvasInstance.cancel();
canvasInstance.merge({ enabled: true });
mergeObjects(true);
},
};
return (
<Tooltip title={`Merge shapes/tracks ${switchMergeShortcut}`} placement='right' mouseLeaveDelay={0}>

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import Tooltip from 'antd/lib/tooltip';
import { MoveIcon } from 'icons';
@ -23,9 +23,9 @@ function MoveControl(props: Props): JSX.Element {
<Icon
component={MoveIcon}
className={
activeControl === ActiveControl.DRAG_CANVAS
? 'cvat-move-control cvat-active-canvas-control'
: 'cvat-move-control'
activeControl === ActiveControl.DRAG_CANVAS ?
'cvat-move-control cvat-active-canvas-control' :
'cvat-move-control'
}
onClick={(): void => {
if (activeControl === ActiveControl.DRAG_CANVAS) {

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import Tooltip from 'antd/lib/tooltip';
import { ZoomIcon } from 'icons';
@ -23,9 +23,9 @@ function ResizeControl(props: Props): JSX.Element {
<Icon
component={ZoomIcon}
className={
activeControl === ActiveControl.ZOOM_CANVAS
? 'cvat-resize-control cvat-active-canvas-control'
: 'cvat-resize-control'
activeControl === ActiveControl.ZOOM_CANVAS ?
'cvat-resize-control cvat-active-canvas-control' :
'cvat-resize-control'
}
onClick={(): void => {
if (activeControl === ActiveControl.ZOOM_CANVAS) {

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import Tooltip from 'antd/lib/tooltip';
import Popover from 'antd/lib/popover';
@ -23,7 +23,7 @@ function RotateControl(props: Props): JSX.Element {
<Popover
overlayClassName='cvat-rotate-canvas-controls'
placement='right'
content={
content={(
<>
<Tooltip
title={`Rotate the image anticlockwise ${anticlockwiseShortcut}`}
@ -48,7 +48,7 @@ function RotateControl(props: Props): JSX.Element {
/>
</Tooltip>
</>
}
)}
trigger='hover'
>
<Icon className='cvat-rotate-canvas-control' component={RotateIcon} />

@ -4,7 +4,7 @@
import React from 'react';
import Popover from 'antd/lib/popover';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import { Canvas } from 'cvat-canvas-wrapper';
import { TagIcon } from 'icons';
@ -19,13 +19,11 @@ interface Props {
function SetupTagControl(props: Props): JSX.Element {
const { isDrawing } = props;
const dynamcPopoverPros = isDrawing
? {
overlayStyle: {
display: 'none',
},
}
: {};
const dynamcPopoverPros = isDrawing ? {
overlayStyle: {
display: 'none',
},
} : {};
return (
<Popover

@ -24,19 +24,19 @@ function SetupTagPopover(props: Props): JSX.Element {
return (
<div className='cvat-draw-shape-popover-content'>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col>
<Text className='cvat-text-color' strong>
Setup tag
</Text>
</Col>
</Row>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col>
<Text className='cvat-text-color'>Label</Text>
</Col>
</Row>
<Row type='flex' justify='center'>
<Row justify='center'>
<Col span={24}>
<LabelSelector
style={{ width: '100%' }}
@ -46,7 +46,7 @@ function SetupTagPopover(props: Props): JSX.Element {
/>
</Col>
</Row>
<Row type='flex' justify='space-around'>
<Row justify='space-around'>
<Col span={24}>
<Tooltip title={`Press ${repeatShapeShortcut} to add a tag again`} mouseLeaveDelay={0}>
<Button onClick={() => onSetup(selectedLabelID)}>Tag</Button>

@ -4,7 +4,7 @@
import React from 'react';
import Tooltip from 'antd/lib/tooltip';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import { SplitIcon } from 'icons';
import { Canvas } from 'cvat-canvas-wrapper';
@ -18,25 +18,27 @@ interface Props {
}
function SplitControl(props: Props): JSX.Element {
const { switchSplitShortcut, activeControl, canvasInstance, splitTrack } = props;
const {
switchSplitShortcut, activeControl, canvasInstance, splitTrack,
} = props;
const dynamicIconProps =
activeControl === ActiveControl.SPLIT
? {
className: 'cvat-split-track-control cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.split({ enabled: false });
splitTrack(false);
},
}
: {
className: 'cvat-split-track-control',
onClick: (): void => {
canvasInstance.cancel();
canvasInstance.split({ enabled: true });
splitTrack(true);
},
};
activeControl === ActiveControl.SPLIT ?
{
className: 'cvat-split-track-control cvat-active-canvas-control',
onClick: (): void => {
canvasInstance.split({ enabled: false });
splitTrack(false);
},
} :
{
className: 'cvat-split-track-control',
onClick: (): void => {
canvasInstance.cancel();
canvasInstance.split({ enabled: true });
splitTrack(true);
},
};
return (
<Tooltip title={`Split a track ${switchSplitShortcut}`} placement='right' mouseLeaveDelay={0}>

@ -4,7 +4,7 @@
import React, { MutableRefObject } from 'react';
import { connect } from 'react-redux';
import Icon from 'antd/lib/icon';
import Icon, { LoadingOutlined } from '@ant-design/icons';
import Popover from 'antd/lib/popover';
import Select from 'antd/lib/select';
import Button from 'antd/lib/button';
@ -375,17 +375,17 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
}
};
private setActiveInteractor = (key: string): void => {
private setActiveInteractor = (value: string): void => {
const { interactors } = this.props;
this.setState({
activeInteractor: interactors.filter((interactor: Model) => interactor.id === key)[0],
activeInteractor: interactors.filter((interactor: Model) => interactor.id === value)[0],
});
};
private setActiveTracker = (key: string): void => {
private setActiveTracker = (value: string): void => {
const { trackers } = this.props;
this.setState({
activeTracker: trackers.filter((tracker: Model) => tracker.id === key)[0],
activeTracker: trackers.filter((tracker: Model) => tracker.id === value)[0],
});
};
@ -457,12 +457,12 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
const { activeLabelID } = this.state;
return (
<>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col>
<Text className='cvat-text-color'>Label</Text>
</Col>
</Row>
<Row type='flex' justify='center'>
<Row justify='center'>
<Col span={24}>
<LabelSelector
style={{ width: '100%' }}
@ -486,7 +486,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
if (!trackers.length) {
return (
<Row type='flex' justify='center' align='middle' style={{ marginTop: '5px' }}>
<Row justify='center' align='middle' style={{ marginTop: '5px' }}>
<Col>
<Text type='warning' className='cvat-text-color'>
No available trackers found
@ -498,12 +498,12 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
return (
<>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col>
<Text className='cvat-text-color'>Tracker</Text>
</Col>
</Row>
<Row type='flex' align='middle' justify='center'>
<Row align='middle' justify='center'>
<Col span={24}>
<Select
style={{ width: '100%' }}
@ -512,7 +512,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
>
{trackers.map(
(tracker: Model): JSX.Element => (
<Select.Option title={tracker.description} key={tracker.id}>
<Select.Option value={tracker.id} title={tracker.description} key={tracker.id}>
{tracker.name}
</Select.Option>
),
@ -520,7 +520,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
</Select>
</Col>
</Row>
<Row type='flex' align='middle' justify='start' style={{ marginTop: '5px' }}>
<Row align='middle' justify='start' style={{ marginTop: '5px' }}>
<Col>
<Text>Tracking frames</Text>
</Col>
@ -531,17 +531,17 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
min={1}
precision={0}
max={jobInstance.stopFrame - frame}
onChange={(value: number | undefined): void => {
onChange={(value: number | undefined | string): void => {
if (typeof value !== 'undefined') {
this.setState({
trackingFrames: value,
trackingFrames: +value,
});
}
}}
/>
</Col>
</Row>
<Row type='flex' align='middle' justify='end'>
<Row align='middle' justify='end'>
<Col>
<Button
type='primary'
@ -578,7 +578,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
if (!interactors.length) {
return (
<Row type='flex' justify='center' align='middle' style={{ marginTop: '5px' }}>
<Row justify='center' align='middle' style={{ marginTop: '5px' }}>
<Col>
<Text type='warning' className='cvat-text-color'>
No available interactors found
@ -590,12 +590,12 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
return (
<>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col>
<Text className='cvat-text-color'>Interactor</Text>
</Col>
</Row>
<Row type='flex' align='middle' justify='center'>
<Row align='middle' justify='center'>
<Col span={24}>
<Select
style={{ width: '100%' }}
@ -604,7 +604,11 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
>
{interactors.map(
(interactor: Model): JSX.Element => (
<Select.Option title={interactor.description} key={interactor.id}>
<Select.Option
value={interactor.id}
title={interactor.description}
key={interactor.id}
>
{interactor.name}
</Select.Option>
),
@ -612,7 +616,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
</Select>
</Col>
</Row>
<Row type='flex' align='middle' justify='end'>
<Row align='middle' justify='end'>
<Col>
<Button
type='primary'
@ -651,7 +655,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
if (!detectors.length) {
return (
<Row type='flex' justify='center' align='middle' style={{ marginTop: '5px' }}>
<Row justify='center' align='middle' style={{ marginTop: '5px' }}>
<Col>
<Text type='warning' className='cvat-text-color'>
No available detectors found
@ -679,17 +683,18 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
});
const states = result.map(
(data: any): any => new core.classes.ObjectState({
shapeType: data.type,
label: task.labels.filter((label: any): boolean => label.name === data.label)[0],
points: data.points,
objectType: ObjectType.SHAPE,
frame,
occluded: false,
source: 'auto',
attributes: {},
zOrder: curZOrder,
}),
(data: any): any =>
new core.classes.ObjectState({
shapeType: data.type,
label: task.labels.filter((label: any): boolean => label.name === data.label)[0],
points: data.points,
objectType: ObjectType.SHAPE,
frame,
occluded: false,
source: 'auto',
attributes: {},
zOrder: curZOrder,
}),
);
await jobInstance.annotations.put(states);
@ -710,7 +715,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
private renderPopoverContent(): JSX.Element {
return (
<div className='cvat-tools-control-popover-content'>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col>
<Text className='cvat-text-color' strong>
AI Tools
@ -771,7 +776,7 @@ export class ToolsControlComponent extends React.PureComponent<Props, State> {
footer={[]}
>
<Text>Waiting for a server response..</Text>
<Icon style={{ marginLeft: '10px' }} type='loading' />
<LoadingOutlined style={{ marginLeft: '10px' }} />
{trackingProgress !== null && (
<Progress percent={+(trackingProgress * 100).toFixed(0)} status='active' />
)}

@ -3,7 +3,8 @@
// SPDX-License-Identifier: MIT
import React, { useState } from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import { CloseOutlined } from '@ant-design/icons';
import Button from 'antd/lib/button';
import Popover from 'antd/lib/popover';
import Text from 'antd/lib/typography/Text';
@ -102,7 +103,7 @@ function ColorPicker(props: Props, ref: React.Ref<any>): JSX.Element {
</>
)}
title={(
<Row type='flex' justify='space-between' align='middle'>
<Row justify='space-between' align='middle'>
<Col span={12}>
<Text strong>Select color</Text>
</Col>
@ -114,7 +115,7 @@ function ColorPicker(props: Props, ref: React.Ref<any>): JSX.Element {
changeVisible(false);
}}
>
<Icon type='close' />
<CloseOutlined />
</Button>
</Tooltip>
</Col>

@ -5,7 +5,9 @@
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { CombinedState } from 'reducers/interfaces';
import Icon, { IconProps } from 'antd/lib/icon';
import {
LeftOutlined, RightOutlined, EyeInvisibleFilled, EyeOutlined,
} from '@ant-design/icons';
import Tooltip from 'antd/lib/tooltip';
import Alert from 'antd/lib/alert';
import { Row, Col } from 'antd/lib/grid';
@ -24,7 +26,7 @@ export default function LabelsListComponent(): JSX.Element {
const combinedIssues = activeReview ? issues.concat(activeReview.issues) : issues;
const frames = combinedIssues.map((issue: any): number => issue.frame).sort((a: number, b: number) => +a - +b);
const nearestLeft = frames.filter((_frame: number): boolean => _frame < frame).reverse()[0];
const dinamicLeftProps: IconProps = Number.isInteger(nearestLeft) ?
const dinamicLeftProps: any = Number.isInteger(nearestLeft) ?
{
onClick: () => dispatch(changeFrameAsync(nearestLeft)),
} :
@ -36,7 +38,7 @@ export default function LabelsListComponent(): JSX.Element {
};
const nearestRight = frames.filter((_frame: number): boolean => _frame > frame)[0];
const dinamicRightProps: IconProps = Number.isInteger(nearestRight) ?
const dinamicRightProps: any = Number.isInteger(nearestRight) ?
{
onClick: () => dispatch(changeFrameAsync(nearestRight)),
} :
@ -47,33 +49,29 @@ export default function LabelsListComponent(): JSX.Element {
},
};
const dinamicShowHideProps: IconProps = issuesHidden ?
{
onClick: () => dispatch(reviewActions.switchIssuesHiddenFlag(false)),
type: 'eye-invisible',
} :
{
onClick: () => dispatch(reviewActions.switchIssuesHiddenFlag(true)),
type: 'eye',
};
return (
<div style={{ height: tabContentHeight }}>
<div className='cvat-objects-sidebar-issues-list-header'>
<Row type='flex' justify='start' align='middle'>
<Row justify='start' align='middle'>
<Col>
<Tooltip title='Find the previous frame with issues'>
<Icon type='left' {...dinamicLeftProps} />
<LeftOutlined {...dinamicLeftProps} />
</Tooltip>
</Col>
<Col offset={1}>
<Tooltip title='Find the next frame with issues'>
<Icon type='right' {...dinamicRightProps} />
<RightOutlined {...dinamicRightProps} />
</Tooltip>
</Col>
<Col offset={3}>
<Tooltip title='Show/hide all the issues'>
<Icon {...dinamicShowHideProps} />
{issuesHidden ? (
<EyeInvisibleFilled
onClick={() => dispatch(reviewActions.switchIssuesHiddenFlag(false))}
/>
) : (
<EyeOutlined onClick={() => dispatch(reviewActions.switchIssuesHiddenFlag(true))} />
)}
</Tooltip>
</Col>
</Row>

@ -4,9 +4,11 @@
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Button from 'antd/lib/button';
import Text from 'antd/lib/typography/Text';
import {
LockFilled, UnlockOutlined, EyeInvisibleFilled, EyeOutlined,
} from '@ant-design/icons';
interface Props {
labelName: string;
@ -46,14 +48,15 @@ function LabelItemComponent(props: Props): JSX.Element {
return (
<Row
type='flex'
align='middle'
justify='space-around'
className='cvat-objects-sidebar-label-item'
style={{ display: visible ? 'flex' : 'none' }}
>
<Col span={4}>
<Button style={{ background: labelColor }} className='cvat-label-item-color-button' />
<Button style={{ background: labelColor }} className='cvat-label-item-color-button'>
{' '}
</Button>
</Col>
<Col span={14}>
<Text strong className='cvat-text'>
@ -62,16 +65,16 @@ function LabelItemComponent(props: Props): JSX.Element {
</Col>
<Col span={3}>
{statesLocked ? (
<Icon {...classes.lock.enabled} type='lock' onClick={unlockStates} />
<LockFilled {...classes.lock.enabled} onClick={unlockStates} />
) : (
<Icon {...classes.lock.disabled} type='unlock' onClick={lockStates} />
<UnlockOutlined {...classes.lock.disabled} onClick={lockStates} />
)}
</Col>
<Col span={3}>
{statesHidden ? (
<Icon {...classes.hidden.enabled} type='eye-invisible' onClick={showStates} />
<EyeInvisibleFilled {...classes.hidden.enabled} onClick={showStates} />
) : (
<Icon {...classes.hidden.disabled} type='eye' onClick={hideStates} />
<EyeOutlined {...classes.hidden.disabled} onClick={hideStates} />
)}
</Col>
</Row>

@ -134,9 +134,9 @@ function ItemAttributeComponent(props: Props): JSX.Element {
<InputNumber
disabled={readonly}
size='small'
onChange={(value: number | undefined): void => {
if (typeof value === 'number') {
changeAttribute(attrID, `${clamp(value, min, max)}`);
onChange={(value: number | undefined | string): void => {
if (typeof value !== 'undefined') {
changeAttribute(attrID, `${clamp(+value, min, max)}`);
}
}}
value={+attrValue}

@ -4,7 +4,7 @@
import React, { useState } from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import { MoreOutlined } from '@ant-design/icons';
import Dropdown from 'antd/lib/dropdown';
import Text from 'antd/lib/typography/Text';
import Tooltip from 'antd/lib/tooltip';
@ -94,7 +94,7 @@ function ItemTopComponent(props: Props): JSX.Element {
};
return (
<Row type='flex' align='middle'>
<Row align='middle'>
<Col span={10}>
<Text style={{ fontSize: 12 }}>{clientID}</Text>
<br />
@ -104,7 +104,13 @@ function ItemTopComponent(props: Props): JSX.Element {
</Col>
<Col span={12}>
<Tooltip title='Change current label' mouseLeaveDelay={0}>
<LabelSelector disabled={readonly} size='small' labels={labels} value={labelID} onChange={changeLabel} />
<LabelSelector
disabled={readonly}
size='small'
labels={labels}
value={labelID}
onChange={changeLabel}
/>
</Tooltip>
</Col>
<Col span={2}>
@ -141,7 +147,7 @@ function ItemTopComponent(props: Props): JSX.Element {
activateTracking,
})}
>
<Icon type='more' />
<MoreOutlined />
</Dropdown>
</Col>
</Row>

@ -4,7 +4,19 @@
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Icon, {
UnlockOutlined,
LockFilled,
TeamOutlined,
UserOutlined,
PushpinFilled,
PushpinOutlined,
EyeInvisibleFilled,
StarFilled,
SelectOutlined,
StarOutlined,
EyeOutlined,
} from '@ant-design/icons';
import Tooltip from 'antd/lib/tooltip';
import { ObjectType, ShapeType } from 'reducers/interfaces';
@ -130,9 +142,9 @@ function SwitchLock(props: Props): JSX.Element {
return (
<Tooltip title={`Switch lock property ${switchLockShortcut}`} mouseLeaveDelay={0}>
{locked ? (
<Icon {...classes.lock.enabled} type='lock' theme='filled' onClick={unlock} />
<LockFilled {...classes.lock.enabled} onClick={unlock} />
) : (
<Icon {...classes.lock.disabled} type='unlock' onClick={lock} />
<UnlockOutlined {...classes.lock.disabled} onClick={lock} />
)}
</Tooltip>
);
@ -145,9 +157,9 @@ function SwitchOccluded(props: Props): JSX.Element {
return (
<Tooltip title={`Switch occluded property ${switchOccludedShortcut}`} mouseLeaveDelay={0}>
{occluded ? (
<Icon {...classes.occluded.enabled} type='team' onClick={unsetOccluded} />
<TeamOutlined {...classes.occluded.enabled} onClick={unsetOccluded} />
) : (
<Icon {...classes.occluded.disabled} type='user' onClick={setOccluded} />
<UserOutlined {...classes.occluded.disabled} onClick={setOccluded} />
)}
</Tooltip>
);
@ -158,9 +170,9 @@ function SwitchPinned(props: Props): JSX.Element {
return (
<Tooltip title='Switch pinned property' mouseLeaveDelay={0}>
{pinned ? (
<Icon {...classes.pinned.enabled} type='pushpin' theme='filled' onClick={unpin} />
<PushpinFilled {...classes.pinned.enabled} onClick={unpin} />
) : (
<Icon {...classes.pinned.disabled} type='pushpin' onClick={pin} />
<PushpinOutlined {...classes.pinned.disabled} onClick={pin} />
)}
</Tooltip>
);
@ -174,15 +186,9 @@ function SwitchHidden(props: Props): JSX.Element {
return (
<Tooltip title={`Switch hidden property ${switchHiddenShortcut}`} mouseLeaveDelay={0}>
{hidden ? (
<Icon
{...classes.hidden.enabled}
type='eye-invisible'
theme='filled'
onClick={show}
style={hiddenStyle}
/>
<EyeInvisibleFilled {...classes.hidden.enabled} onClick={show} style={hiddenStyle} />
) : (
<Icon {...classes.hidden.disabled} type='eye' onClick={hide} style={hiddenStyle} />
<EyeOutlined {...classes.hidden.disabled} onClick={hide} style={hiddenStyle} />
)}
</Tooltip>
);
@ -203,7 +209,7 @@ function SwitchOutside(props: Props): JSX.Element {
style={outsideStyle}
/>
) : (
<Icon {...classes.outside.disabled} type='select' onClick={setOutside} style={outsideStyle} />
<SelectOutlined {...classes.outside.disabled} onClick={setOutside} style={outsideStyle} />
)}
</Tooltip>
);
@ -217,15 +223,9 @@ function SwitchKeyframe(props: Props): JSX.Element {
return (
<Tooltip title={`Switch keyframe property ${switchKeyFrameShortcut}`} mouseLeaveDelay={0}>
{keyframe ? (
<Icon
{...classes.keyframe.enabled}
type='star'
theme='filled'
onClick={unsetKeyframe}
style={keyframeStyle}
/>
<StarFilled {...classes.keyframe.enabled} onClick={unsetKeyframe} style={keyframeStyle} />
) : (
<Icon {...classes.keyframe.disabled} type='star' onClick={setKeyframe} style={keyframeStyle} />
<StarOutlined {...classes.keyframe.disabled} onClick={setKeyframe} style={keyframeStyle} />
)}
</Tooltip>
);
@ -236,9 +236,9 @@ function ItemButtonsComponent(props: Props): JSX.Element {
if (objectType === ObjectType.TRACK) {
return (
<Row type='flex' align='middle' justify='space-around'>
<Row align='middle' justify='space-around'>
<Col span={20} style={{ textAlign: 'center' }}>
<Row type='flex' justify='space-around'>
<Row justify='space-around'>
<Col>
<NavigateFirstKeyframe {...props} />
</Col>
@ -253,7 +253,7 @@ function ItemButtonsComponent(props: Props): JSX.Element {
</Col>
</Row>
{!readonly && (
<Row type='flex' justify='space-around'>
<Row justify='space-around'>
<Col>
<SwitchOutside {...props} />
</Col>
@ -287,9 +287,9 @@ function ItemButtonsComponent(props: Props): JSX.Element {
if (objectType === ObjectType.TAG) {
return (
<Row type='flex' align='middle' justify='space-around'>
<Row align='middle' justify='space-around'>
<Col span={20} style={{ textAlign: 'center' }}>
<Row type='flex' justify='space-around'>
<Row justify='space-around'>
<Col>
<SwitchLock {...props} />
</Col>
@ -300,9 +300,9 @@ function ItemButtonsComponent(props: Props): JSX.Element {
}
return (
<Row type='flex' align='middle' justify='space-around'>
<Row align='middle' justify='space-around'>
<Col span={20} style={{ textAlign: 'center' }}>
<Row type='flex' justify='space-around'>
<Row justify='space-around'>
<Col>
<SwitchLock {...props} />
</Col>

@ -55,7 +55,6 @@ function ItemAttributesComponent(props: Props): JSX.Element {
(attribute: any): JSX.Element => (
<Row
key={attribute.id}
type='flex'
align='middle'
justify='start'
className='cvat-object-item-attribute-wrapper'

@ -3,12 +3,18 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import Icon from 'antd/lib/icon';
import Menu from 'antd/lib/menu';
import Button from 'antd/lib/button';
import Modal from 'antd/lib/modal';
import Tooltip from 'antd/lib/tooltip';
import Icon, {
LinkOutlined,
CopyOutlined,
BlockOutlined,
GatewayOutlined,
RetweetOutlined,
DeleteOutlined,
} from '@ant-design/icons';
import {
BackgroundIcon, ForegroundIcon, ResetPerspectiveIcon, ColorizeIcon,
} from 'icons';
@ -53,7 +59,7 @@ function CreateURLItem(props: ItemProps): JSX.Element {
const { serverID, createURL } = toolProps;
return (
<Menu.Item {...rest}>
<Button disabled={serverID === undefined} type='link' icon='link' onClick={createURL}>
<Button disabled={serverID === undefined} type='link' icon={<LinkOutlined />} onClick={createURL}>
Create object URL
</Button>
</Menu.Item>
@ -66,7 +72,7 @@ function MakeCopyItem(props: ItemProps): JSX.Element {
return (
<Menu.Item {...rest}>
<Tooltip title={`${copyShortcut} and ${pasteShortcut}`} mouseLeaveDelay={0}>
<Button type='link' icon='copy' onClick={copy}>
<Button type='link' icon={<CopyOutlined />} onClick={copy}>
Make a copy
</Button>
</Tooltip>
@ -80,7 +86,7 @@ function PropagateItem(props: ItemProps): JSX.Element {
return (
<Menu.Item {...rest}>
<Tooltip title={`${propagateShortcut}`} mouseLeaveDelay={0}>
<Button type='link' icon='block' onClick={propagate}>
<Button type='link' icon={<BlockOutlined />} onClick={propagate}>
Propagate
</Button>
</Tooltip>
@ -94,8 +100,7 @@ function TrackingItem(props: ItemProps): JSX.Element {
return (
<Menu.Item {...rest}>
<Tooltip title='Run tracking with the active tracker' mouseLeaveDelay={0}>
<Button type='link' onClick={activateTracking}>
<Icon type='gateway' />
<Button type='link' icon={<GatewayOutlined />} onClick={activateTracking}>
Track
</Button>
</Tooltip>
@ -108,7 +113,7 @@ function SwitchOrientationItem(props: ItemProps): JSX.Element {
const { switchOrientation } = toolProps;
return (
<Menu.Item {...rest}>
<Button type='link' icon='retweet' onClick={switchOrientation}>
<Button type='link' icon={<RetweetOutlined />} onClick={switchOrientation}>
Switch orientation
</Button>
</Menu.Item>
@ -196,11 +201,11 @@ function RemoveItem(props: ItemProps): JSX.Element {
<Tooltip title={`${removeShortcut}`} mouseLeaveDelay={0}>
<Button
type='link'
icon='delete'
icon={<DeleteOutlined />}
onClick={(): void => {
if (locked) {
Modal.confirm({
className: 'cvat-modal-confirm',
className: 'cvat-modal-confirm',
title: 'Object is locked',
content: 'Are you sure you want to remove it?',
onOk() {

@ -4,7 +4,14 @@
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import {
LockFilled,
UnlockOutlined,
EyeInvisibleFilled,
EyeOutlined,
CaretDownOutlined,
CaretUpFilled,
} from '@ant-design/icons';
import Tooltip from 'antd/lib/tooltip';
import AnnotationsFiltersInput from 'components/annotation-page/annotations-filters-input';
@ -35,11 +42,7 @@ function LockAllSwitcher(props: Props): JSX.Element {
return (
<Col span={2}>
<Tooltip title={`Switch lock property for all ${switchLockAllShortcut}`} mouseLeaveDelay={0}>
{statesLocked ? (
<Icon type='lock' onClick={unlockAllStates} theme='filled' />
) : (
<Icon type='unlock' onClick={lockAllStates} />
)}
{statesLocked ? <LockFilled onClick={unlockAllStates} /> : <UnlockOutlined onClick={lockAllStates} />}
</Tooltip>
</Col>
);
@ -53,9 +56,9 @@ function HideAllSwitcher(props: Props): JSX.Element {
<Col span={2}>
<Tooltip title={`Switch hidden property for all ${switchHiddenAllShortcut}`} mouseLeaveDelay={0}>
{statesHidden ? (
<Icon type='eye-invisible' onClick={showAllStates} />
<EyeInvisibleFilled onClick={showAllStates} />
) : (
<Icon type='eye' onClick={hideAllStates} />
<EyeOutlined onClick={hideAllStates} />
)}
</Tooltip>
</Col>
@ -68,9 +71,9 @@ function CollapseAllSwitcher(props: Props): JSX.Element {
<Col span={2}>
<Tooltip title='Expand/collapse all' mouseLeaveDelay={0}>
{statesCollapsed ? (
<Icon type='caret-down' onClick={expandAllStates} />
<CaretDownOutlined onClick={expandAllStates} />
) : (
<Icon type='caret-up' onClick={collapseAllStates} />
<CaretUpFilled onClick={collapseAllStates} />
)}
</Tooltip>
</Col>
@ -83,11 +86,11 @@ function ObjectListHeader(props: Props): JSX.Element {
return (
<div className='cvat-objects-sidebar-states-header'>
<Row>
<Col>
<Col span={24}>
<AnnotationsFiltersInput />
</Col>
</Row>
<Row type='flex' justify='space-between' align='middle'>
<Row justify='space-between' align='middle'>
{!readonly && (
<>
<LockAllSwitcher {...props} />

@ -6,8 +6,8 @@ import './styles.scss';
import React, { Dispatch, useEffect } from 'react';
import { AnyAction } from 'redux';
import { connect } from 'react-redux';
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
import Text from 'antd/lib/typography/Text';
import Icon from 'antd/lib/icon';
import Tabs from 'antd/lib/tabs';
import Layout from 'antd/lib/layout';
@ -118,7 +118,7 @@ function ObjectsSideBar(props: StateToProps & DispatchToProps & OwnProps): JSX.E
ant-layout-sider-zero-width-trigger-left`}
onClick={collapseSidebar}
>
{sidebarCollapsed ? <Icon type='menu-fold' title='Show' /> : <Icon type='menu-unfold' title='Hide' />}
{sidebarCollapsed ? <MenuFoldOutlined title='Show' /> : <MenuUnfoldOutlined title='Hide' />}
</span>
<Tabs type='card' defaultActiveKey='objects' className='cvat-objects-sidebar-tabs'>

@ -22,6 +22,7 @@ function StatesOrderingSelectorComponent(props: StatesOrderingSelectorComponentP
<Text strong>Sort by</Text>
<Select
className='cvat-objects-sidebar-ordering-selector'
dropdownClassName='cvat-objects-sidebar-ordering-dropdown'
value={statesOrdering}
onChange={changeStatesOrdering}
>

@ -19,6 +19,10 @@
background: $header-color;
border-radius: 0;
height: 25px;
.ant-collapse-arrow {
top: $grid-unit-size;
}
}
> .ant-collapse-content {
@ -48,7 +52,11 @@
box-sizing: border-box;
border: none;
.ant-tabs-card-bar {
.ant-tabs-nav {
&::before {
content: none;
}
border: none;
margin-bottom: 0;
padding-top: 25px;
@ -75,7 +83,7 @@
box-sizing: border-box;
> div > div {
> i {
> span[role='img'] {
font-size: 16px;
color: $objects-bar-icons-color;
@ -133,7 +141,7 @@
text-align: center;
margin: 0 2px;
> i {
> span[role='img'] {
@extend .cvat-object-sidebar-icon;
}
@ -182,7 +190,7 @@
line-height: 12px;
}
> div:nth-child(3) > i {
> div:nth-child(3) > span {
@extend .cvat-object-sidebar-icon;
font-size: 25px;
@ -198,7 +206,7 @@
margin-top: 5px;
}
i {
span[role='img'] {
@extend .cvat-object-sidebar-icon;
}
}
@ -216,6 +224,10 @@
background: inherit;
padding-top: 2px;
padding-bottom: 2px;
.ant-collapse-arrow {
top: $grid-unit-size;
}
}
> .ant-collapse-content {
@ -281,10 +293,10 @@
.cvat-objects-sidebar-label-item {
height: 2.5em;
border-bottom: 1px solid $border-color-2;
border-bottom: 1px solid $border-color-1;
padding: 5px;
i {
span {
@extend .cvat-object-sidebar-icon;
}

@ -52,10 +52,10 @@ export default function PropagateConfirmComponent(props: Props): JSX.Element {
size='small'
min={minPropagateFrames}
value={propagateFrames}
onChange={(value: number | undefined) => {
if (typeof value === 'number') {
onChange={(value: number | undefined | string) => {
if (typeof value !== 'undefined') {
changePropagateFrames(
Math.floor(clamp(value, minPropagateFrames, Number.MAX_SAFE_INTEGER)),
Math.floor(clamp(+value, minPropagateFrames, Number.MAX_SAFE_INTEGER)),
);
}
}}
@ -67,9 +67,9 @@ export default function PropagateConfirmComponent(props: Props): JSX.Element {
value={propagateUpToFrame}
min={frameNumber + 1}
max={stopFrame}
onChange={(value: number | undefined) => {
if (typeof value === 'number') {
changeUpToFrame(Math.floor(clamp(value, frameNumber + 1, stopFrame)));
onChange={(value: number | undefined | string) => {
if (typeof value !== 'undefined') {
changeUpToFrame(Math.floor(clamp(+value, frameNumber + 1, stopFrame)));
}
}}
/>

@ -8,10 +8,6 @@
height: 100%;
}
.cvat-objects-sidebar-filter-input {
width: calc(100% - 35px);
}
.cvat-objects-sidebar-sider {
top: 0;
right: 0;
@ -34,26 +30,39 @@
.cvat-canvas-controls-sidebar {
background-color: $background-color-2;
border-right: 1px solid $border-color-1;
}
> div {
> i {
border-radius: 3.3px;
transform: scale(0.65);
padding: 2px;
&:hover {
background: $header-color;
transform: scale(0.75);
}
.cvat-cursor-control,
.cvat-move-control,
.cvat-rotate-canvas-control,
.cvat-fit-control,
.cvat-resize-control,
.cvat-draw-rectangle-control,
.cvat-draw-polygon-control,
.cvat-draw-polyline-control,
.cvat-draw-points-control,
.cvat-draw-cuboid-control,
.cvat-setup-tag-control,
.cvat-merge-control,
.cvat-group-control,
.cvat-split-track-control,
.cvat-issue-control,
.cvat-tools-control {
border-radius: 3.3px;
transform: scale(0.65);
padding: 2px;
&:active {
transform: scale(0.65);
}
&:hover {
background: $header-color;
transform: scale(0.75);
}
> svg {
transform: scale(0.8);
}
}
&:active {
transform: scale(0.65);
}
> svg {
transform: scale(0.8);
}
}
@ -76,13 +85,15 @@
}
}
.cvat-rotate-canvas-controls > .ant-popover-content > .ant-popover-inner > div > .ant-popover-inner-content {
padding: 0;
.cvat-rotate-canvas-controls {
.ant-popover-inner-content {
padding: 0;
}
}
.cvat-draw-shape-popover,
.cvat-tools-control-popover {
> .ant-popover-content > .ant-popover-inner > div > .ant-popover-inner-content {
.ant-popover-inner-content {
padding: 0;
}
}

@ -6,6 +6,7 @@
.cvat-annotation-page.ant-layout {
height: 100%;
overflow: hidden;
}
.ant-layout-header.cvat-annotation-header {
@ -37,21 +38,21 @@
align-items: center;
margin: 0 3px;
> span {
> span:not([role='img']) {
margin-left: 0;
font-size: 10px;
}
> i {
> span[role='img'] {
transform: scale(0.8);
padding: 3px;
}
&:hover > i {
&:hover > span[role='img'] {
transform: scale(0.85);
}
&:active > i {
&:active > span[role='img'] {
transform: scale(0.8);
}
@ -80,7 +81,7 @@
height: 100%;
margin-right: 10px;
> i {
> span {
font-size: 25px;
margin: 0 7px;
color: $player-buttons-color;
@ -96,18 +97,17 @@
}
.cvat-player-controls {
position: relative;
height: 100%;
line-height: 27px;
> div {
position: relative;
height: 50%;
}
}
.cvat-player-slider {
width: 350px;
margin: 0;
> .ant-slider-rail {
background-color: $player-slider-color;
@ -266,7 +266,7 @@
}
}
> i {
> span[role='img'] {
opacity: 0.7;
color: $objects-bar-icons-color;

@ -12,7 +12,7 @@
background: $background-color-2;
padding: 5px;
> div > .ant-row-flex > .ant-col > .ant-tag {
> div > .ant-row > .ant-col > .ant-tag {
margin: 4px;
}
}

@ -98,7 +98,6 @@ const ShortcutsSelect = (props: Props): JSX.Element => {
onChange={(value: string) => {
onChangeShortcutLabel(value, Number.parseInt(id, 10));
}}
size='default'
style={{ width: 200 }}
className='cvat-tag-annotation-label-select'
>

@ -8,11 +8,11 @@ import { GlobalHotKeys, ExtendedKeyMapOptions } from 'react-hotkeys';
import { Action } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import { Row, Col } from 'antd/lib/grid';
import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons';
import Layout, { SiderProps } from 'antd/lib/layout';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox/Checkbox';
import Button from 'antd/lib/button/button';
import Icon from 'antd/lib/icon';
import Text from 'antd/lib/typography/Text';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox/Checkbox';
import Tag from 'antd/lib/tag';
import {
@ -207,19 +207,15 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen
ant-layout-sider-zero-width-trigger-left`}
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
>
{sidebarCollapsed ? (
<Icon type='menu-fold' title='Show' />
) : (
<Icon type='menu-unfold' title='Hide' />
)}
{sidebarCollapsed ? <MenuFoldOutlined title='Show' /> : <MenuUnfoldOutlined title='Hide' />}
</span>
<Row type='flex' justify='start' className='cvat-tag-annotation-sidebar-label-select'>
<Row justify='start' className='cvat-tag-annotation-sidebar-label-select'>
<Col>
<Text strong>Tag label</Text>
<LabelSelector labels={labels} value={selectedLabelID} onChange={onChangeLabel} />
</Col>
</Row>
<Row type='flex' justify='space-around' className='cvat-tag-annotation-sidebar-buttons'>
<Row justify='space-around' className='cvat-tag-annotation-sidebar-buttons'>
<Col span={8}>
<Button onClick={() => onAddTag(selectedLabelID)}>Add tag</Button>
</Col>
@ -227,7 +223,7 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen
<Button onClick={onChangeFrame}>Skip frame</Button>
</Col>
</Row>
<Row type='flex' className='cvat-tag-annotation-sidebar-checkbox-skip-frame'>
<Row className='cvat-tag-annotation-sidebar-checkbox-skip-frame'>
<Col>
<Checkbox
checked={skipFrame}
@ -239,12 +235,12 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen
</Checkbox>
</Col>
</Row>
<Row type='flex' justify='start' className='cvat-tag-annotation-sidebar-frame-tags'>
<Row justify='start' className='cvat-tag-annotation-sidebar-frame-tags'>
<Col>
<Text strong>Frame tags:&nbsp;</Text>
{frameTags.map((tag: any) => (
<Tag
className={'cvat-tag-annotation-sidebar-frame-tag-label'}
className='cvat-tag-annotation-sidebar-frame-tag-label'
color={tag.label.color}
onClose={() => {
onRemoveState(tag);
@ -262,7 +258,7 @@ function TagAnnotationSidebar(props: StateToProps & DispatchToProps): JSX.Elemen
<ShortcutsSelect onAddTag={onAddTag} />
</Col>
</Row>
<Row type='flex' justify='center' className='cvat-tag-annotation-sidebar-shortcut-help'>
<Row justify='center' className='cvat-tag-annotation-sidebar-shortcut-help'>
<Col>
<Text>
Use&nbsp;

@ -3,8 +3,10 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import Menu, { ClickParam } from 'antd/lib/menu';
import Menu from 'antd/lib/menu';
import Modal from 'antd/lib/modal';
// eslint-disable-next-line import/no-extraneous-dependencies
import { MenuInfo } from 'rc-menu/lib/interface';
import DumpSubmenu from 'components/actions-menu/dump-submenu';
import LoadSubmenu from 'components/actions-menu/load-submenu';
@ -19,7 +21,7 @@ interface Props {
exportActivities: string[] | null;
isReviewer: boolean;
jobInstance: any;
onClickMenu(params: ClickParam, file?: File): void;
onClickMenu(params: MenuInfo, file?: File): void;
setForceExitAnnotationFlag(forceExit: boolean): void;
saveAnnotations(jobInstance: any, afterSave?: () => void): void;
}
@ -54,15 +56,15 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element {
const jobStatus = jobInstance.status;
const taskID = jobInstance.task.id;
let latestParams: ClickParam | null = null;
function onClickMenuWrapper(params: ClickParam | null, file?: File): void {
let latestParams: MenuInfo | null = null;
function onClickMenuWrapper(params: MenuInfo | null, file?: File): void {
const copyParams = params || latestParams;
if (!copyParams) {
return;
}
latestParams = params;
function checkUnsavedChanges(_copyParams: ClickParam): void {
function checkUnsavedChanges(_copyParams: MenuInfo): void {
if (jobInstance.annotations.hasUnsavedChanges()) {
Modal.confirm({
title: 'The job has unsaved annotations',
@ -100,7 +102,8 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element {
onClickMenu(copyParams, file);
},
okButtonProps: {
type: 'danger',
type: 'primary',
danger: true,
},
okText: 'Update',
});
@ -118,7 +121,8 @@ export default function AnnotationMenuComponent(props: Props): JSX.Element {
onClickMenu(copyParams);
},
okButtonProps: {
type: 'danger',
type: 'primary',
danger: true,
},
okText: 'Delete',
});

@ -4,7 +4,7 @@
import React from 'react';
import { Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import Modal from 'antd/lib/modal';
import Button from 'antd/lib/button';
import Timeline from 'antd/lib/timeline';

@ -5,7 +5,7 @@
import React from 'react';
import { Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import Tooltip from 'antd/lib/tooltip';
import Popover from 'antd/lib/popover';

@ -5,10 +5,11 @@
import React, { useState, useEffect } from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Slider, { SliderValue } from 'antd/lib/slider';
import { LinkOutlined } from '@ant-design/icons';
import Slider from 'antd/lib/slider';
import Tooltip from 'antd/lib/tooltip';
import InputNumber from 'antd/lib/input-number';
import Input from 'antd/lib/input';
import Text from 'antd/lib/typography/Text';
import { clamp } from 'utils/math';
@ -19,8 +20,8 @@ interface Props {
frameNumber: number;
frameFilename: string;
focusFrameInputShortcut: string;
inputFrameRef: React.RefObject<InputNumber>;
onSliderChange(value: SliderValue): void;
inputFrameRef: React.RefObject<Input>;
onSliderChange(value: number): void;
onInputChange(value: number): void;
onURLIconClick(): void;
}
@ -49,7 +50,7 @@ function PlayerNavigation(props: Props): JSX.Element {
return (
<>
<Col className='cvat-player-controls'>
<Row type='flex'>
<Row align='bottom'>
<Col>
<Slider
className='cvat-player-slider'
@ -60,7 +61,7 @@ function PlayerNavigation(props: Props): JSX.Element {
/>
</Col>
</Row>
<Row type='flex' justify='center'>
<Row justify='center'>
<Col className='cvat-player-filename-wrapper'>
<Tooltip title={frameFilename} mouseLeaveDelay={0}>
<Text type='secondary'>{frameFilename}</Text>
@ -68,7 +69,7 @@ function PlayerNavigation(props: Props): JSX.Element {
</Col>
<Col offset={1}>
<Tooltip title='Create frame URL' mouseLeaveDelay={0}>
<Icon className='cvat-player-frame-url-icon' type='link' onClick={onURLIconClick} />
<LinkOutlined className='cvat-player-frame-url-icon' onClick={onURLIconClick} />
</Tooltip>
</Col>
</Row>
@ -76,12 +77,13 @@ function PlayerNavigation(props: Props): JSX.Element {
<Col>
<Tooltip title={`Press ${focusFrameInputShortcut} to focus here`} mouseLeaveDelay={0}>
<InputNumber
ref={inputFrameRef}
className='cvat-player-frame-selector'
type='number'
value={frameInputValue}
onChange={(value: number | undefined) => {
if (typeof value === 'number') {
setFrameInputValue(Math.floor(clamp(value, startFrame, stopFrame)));
onChange={(value: number | undefined | string) => {
if (typeof value !== 'undefined') {
setFrameInputValue(Math.floor(clamp(+value, startFrame, stopFrame)));
}
}}
onBlur={() => {
@ -90,7 +92,6 @@ function PlayerNavigation(props: Props): JSX.Element {
onPressEnter={() => {
onInputChange(frameInputValue);
}}
ref={inputFrameRef}
/>
</Tooltip>
</Col>

@ -4,7 +4,7 @@
import React from 'react';
import { Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import Select from 'antd/lib/select';
import Button from 'antd/lib/button';
@ -43,7 +43,12 @@ function RightGroup(props: Props): JSX.Element {
Info
</Button>
<div>
<Select className='cvat-workspace-selector' onChange={changeWorkspace} value={workspace}>
<Select
dropdownClassName='cvat-workspace-selector-dropdown'
className='cvat-workspace-selector'
onChange={changeWorkspace}
value={workspace}
>
{Object.values(Workspace).map((ws) => (
<Select.Option key={ws} value={ws}>
{ws}

@ -4,11 +4,11 @@
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import { QuestionCircleOutlined } from '@ant-design/icons';
import Tooltip from 'antd/lib/tooltip';
import Table from 'antd/lib/table';
import Modal from 'antd/lib/modal';
import Spin from 'antd/lib/spin';
import Icon from 'antd/lib/icon';
import Text from 'antd/lib/typography/Text';
interface Props {
@ -78,7 +78,7 @@ export default function StatisticsModalComponent(props: Props): JSX.Element {
<Text strong style={{ marginRight: 5 }}>
{title}
</Text>
<Icon className='cvat-info-circle-icon' type='question-circle' />
<QuestionCircleOutlined className='cvat-info-circle-icon' />
</Tooltip>
);
@ -133,12 +133,12 @@ export default function StatisticsModalComponent(props: Props): JSX.Element {
return (
<Modal {...baseProps}>
<div className='cvat-job-info-modal-window'>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col>
<Text className='cvat-text'>Overview</Text>
</Col>
</Row>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col span={4}>
<Text strong className='cvat-text'>
Assignee
@ -171,7 +171,7 @@ export default function StatisticsModalComponent(props: Props): JSX.Element {
</Col>
</Row>
{!!bugTracker && (
<Row type='flex' justify='start' className='cvat-job-info-bug-tracker'>
<Row justify='start' className='cvat-job-info-bug-tracker'>
<Col>
<Text strong className='cvat-text'>
Bug tracker
@ -180,7 +180,7 @@ export default function StatisticsModalComponent(props: Props): JSX.Element {
</Col>
</Row>
)}
<Row type='flex' justify='space-around' className='cvat-job-info-statistics'>
<Row justify='space-around' className='cvat-job-info-statistics'>
<Col span={24}>
<Text className='cvat-text'>Annotations statistics</Text>
<Table scroll={{ y: 400 }} bordered pagination={false} columns={columns} dataSource={rows} />

@ -5,8 +5,7 @@
import React from 'react';
import { Row, Col } from 'antd/lib/grid';
import InputNumber from 'antd/lib/input-number';
import { SliderValue } from 'antd/lib/slider';
import Input from 'antd/lib/input';
import { Workspace } from 'reducers/interfaces';
import LeftGroup from './left-group';
@ -20,7 +19,7 @@ interface Props {
savingStatuses: string[];
frameNumber: number;
frameFilename: string;
inputFrameRef: React.RefObject<InputNumber>;
inputFrameRef: React.RefObject<Input>;
startFrame: number;
stopFrame: number;
undoAction?: string;
@ -49,7 +48,7 @@ interface Props {
onLastFrame(): void;
setPrevButtonType(type: 'regular' | 'filtered' | 'empty'): void;
setNextButtonType(type: 'regular' | 'filtered' | 'empty'): void;
onSliderChange(value: SliderValue): void;
onSliderChange(value: number): void;
onInputChange(value: number): void;
onURLIconClick(): void;
onUndoClick(): void;
@ -100,7 +99,7 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element {
} = props;
return (
<Row type='flex' justify='space-between'>
<Row justify='space-between'>
<LeftGroup
saving={saving}
savingStatuses={savingStatuses}
@ -114,7 +113,7 @@ export default function AnnotationTopBarComponent(props: Props): JSX.Element {
onRedoClick={onRedoClick}
/>
<Col className='cvat-annotation-header-player-group'>
<Row type='flex' align='middle'>
<Row align='middle'>
<PlayerButtons
playing={playing}
playPauseShortcut={playPauseShortcut}

@ -3,12 +3,12 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import Form, { FormComponentProps } from 'antd/lib/form/Form';
import Form from 'antd/lib/form';
import { LockOutlined } from '@ant-design/icons';
import Button from 'antd/lib/button';
import Icon from 'antd/lib/icon';
import Input from 'antd/lib/input';
import patterns from 'utils/validation-patterns';
import { validateConfirmation, validatePassword } from 'components/register-page/register-form';
export interface ChangePasswordData {
oldPassword: string;
@ -16,159 +16,79 @@ export interface ChangePasswordData {
newPassword2: string;
}
type ChangePasswordFormProps = {
interface Props {
fetching: boolean;
onSubmit(loginData: ChangePasswordData): void;
} & FormComponentProps;
class ChangePasswordFormComponent extends React.PureComponent<ChangePasswordFormProps> {
private validateConfirmation = (_: any, value: string, callback: Function): void => {
const { form } = this.props;
if (value && value !== form.getFieldValue('newPassword1')) {
callback('Two passwords that you enter is inconsistent!');
} else {
callback();
}
};
private validatePassword = (_: any, value: string, callback: Function): void => {
const { form } = this.props;
if (!patterns.validatePasswordLength.pattern.test(value)) {
callback(patterns.validatePasswordLength.message);
}
if (!patterns.passwordContainsNumericCharacters.pattern.test(value)) {
callback(patterns.passwordContainsNumericCharacters.message);
}
if (!patterns.passwordContainsUpperCaseCharacter.pattern.test(value)) {
callback(patterns.passwordContainsUpperCaseCharacter.message);
}
if (!patterns.passwordContainsLowerCaseCharacter.pattern.test(value)) {
callback(patterns.passwordContainsLowerCaseCharacter.message);
}
if (value) {
form.validateFields(['newPassword2'], { force: true });
}
callback();
};
private handleSubmit = (e: React.FormEvent): void => {
e.preventDefault();
const { form, onSubmit } = this.props;
form.validateFields((error, values): void => {
if (!error) {
const validatedFields = {
...values,
confirmations: [],
};
onSubmit(validatedFields);
}
});
};
private renderOldPasswordField(): JSX.Element {
const { form } = this.props;
}
return (
<Form.Item hasFeedback>
{form.getFieldDecorator('oldPassword', {
rules: [
{
required: true,
message: 'Please input your current password!',
},
],
})(
<Input.Password
autoComplete='new-password'
prefix={<Icon type='lock' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Current password'
/>,
)}
function ChangePasswordFormComponent({ fetching, onSubmit }: Props): JSX.Element {
return (
<Form onFinish={onSubmit} className='change-password-form'>
<Form.Item
hasFeedback
name='oldPassword'
rules={[
{
required: true,
message: 'Please input your current password!',
},
]}
>
<Input.Password
autoComplete='current-password'
prefix={<LockOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Current password'
/>
</Form.Item>
);
}
private renderNewPasswordField(): JSX.Element {
const { form } = this.props;
return (
<Form.Item hasFeedback>
{form.getFieldDecorator('newPassword1', {
rules: [
{
required: true,
message: 'Please input new password!',
},
{
validator: this.validatePassword,
},
],
})(
<Input.Password
autoComplete='new-password'
prefix={<Icon type='lock' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='New password'
/>,
)}
<Form.Item
hasFeedback
name='newPassword1'
rules={[
{
required: true,
message: 'Please input new password!',
}, validatePassword,
]}
>
<Input.Password
autoComplete='new-password'
prefix={<LockOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='New password'
/>
</Form.Item>
);
}
private renderNewPasswordConfirmationField(): JSX.Element {
const { form } = this.props;
return (
<Form.Item hasFeedback>
{form.getFieldDecorator('newPassword2', {
rules: [
{
required: true,
message: 'Please confirm your new password!',
},
{
validator: this.validateConfirmation,
},
],
})(
<Input.Password
autoComplete='new-password'
prefix={<Icon type='lock' style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Confirm new password'
/>,
)}
<Form.Item
hasFeedback
name='newPassword2'
dependencies={['newPassword1']}
rules={[
{
required: true,
message: 'Please confirm your new password!',
}, validateConfirmation('newPassword1'),
]}
>
<Input.Password
autoComplete='new-password'
prefix={<LockOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Confirm new password'
/>
</Form.Item>
);
}
public render(): JSX.Element {
const { fetching } = this.props;
return (
<Form onSubmit={this.handleSubmit} className='change-password-form'>
{this.renderOldPasswordField()}
{this.renderNewPasswordField()}
{this.renderNewPasswordConfirmationField()}
<Form.Item>
<Button
type='primary'
htmlType='submit'
className='change-password-form-button'
loading={fetching}
disabled={fetching}
>
Submit
</Button>
</Form.Item>
</Form>
);
}
<Form.Item>
<Button
type='primary'
htmlType='submit'
className='change-password-form-button'
loading={fetching}
disabled={fetching}
>
Submit
</Button>
</Form.Item>
</Form>
);
}
export default Form.create<ChangePasswordFormProps>()(ChangePasswordFormComponent);
export default React.memo(ChangePasswordFormComponent);

@ -3,13 +3,13 @@
// SPDX-License-Identifier: MIT
import React, {
useState, useRef, useEffect, Component,
useState, useRef, useEffect, RefObject,
} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory } from 'react-router';
import { Col, Row } from 'antd/lib/grid';
import Text from 'antd/lib/typography/Text';
import Form, { FormComponentProps, WrappedFormUtils } from 'antd/lib/form/Form';
import Form, { FormInstance } from 'antd/lib/form';
import Button from 'antd/lib/button';
import Input from 'antd/lib/input';
import notification from 'antd/lib/notification';
@ -19,66 +19,57 @@ import { CombinedState } from 'reducers/interfaces';
import LabelsEditor from 'components/labels-editor/labels-editor';
import { createProjectAsync } from 'actions/projects-actions';
type FormRefType = Component<FormComponentProps<any>, any, any> & WrappedFormUtils;
const ProjectNameEditor = Form.create<FormComponentProps>()(
(props: FormComponentProps): JSX.Element => {
const { form } = props;
const { getFieldDecorator } = form;
return (
<Form onSubmit={(e): void => e.preventDefault()}>
<Form.Item hasFeedback label={<span>Name</span>}>
{getFieldDecorator('name', {
rules: [
{
required: true,
message: 'Please, specify a name',
},
],
})(<Input />)}
</Form.Item>
</Form>
);
},
);
const AdvanvedConfigurationForm = Form.create<FormComponentProps>()(
(props: FormComponentProps): JSX.Element => {
const { form } = props;
const { getFieldDecorator } = form;
return (
<Form onSubmit={(e): void => e.preventDefault()}>
<Form.Item
label={<span>Issue tracker</span>}
extra='Attach issue tracker where the project is described'
hasFeedback
>
{getFieldDecorator('bug_tracker', {
rules: [
{
validator: (_, value, callback): void => {
if (value && !patterns.validateURL.pattern.test(value)) {
callback('Issue tracker must be URL');
} else {
callback();
}
},
},
],
})(<Input />)}
</Form.Item>
</Form>
);
},
);
function NameConfigurationForm({ formRef }: { formRef: RefObject<FormInstance> }): JSX.Element {
return (
<Form layout='vertical' ref={formRef}>
<Form.Item
name='name'
hasFeedback
label='Name'
rules={[
{
required: true,
message: 'Please, specify a name',
},
]}
>
<Input />
</Form.Item>
</Form>
);
}
function AdvanvedConfigurationForm({ formRef }: { formRef: RefObject<FormInstance> }): JSX.Element {
return (
<Form layout='vertical' ref={formRef}>
<Form.Item
name='bug_tracker'
label='Issue tracker'
extra='Attach issue tracker where the project is described'
hasFeedback
rules={[
{
validator: (_, value, callback): void => {
if (value && !patterns.validateURL.pattern.test(value)) {
callback('Issue tracker must be URL');
} else {
callback();
}
},
},
]}
>
<Input />
</Form.Item>
</Form>
);
}
export default function CreateProjectContent(): JSX.Element {
const [projectLabels, setProjectLabels] = useState<any[]>([]);
const shouldShowNotification = useRef(false);
const nameFormRef = useRef<FormRefType>(null);
const advancedFormRef = useRef<FormRefType>(null);
const nameFormRef = useRef<FormInstance>(null);
const advancedFormRef = useRef<FormInstance>(null);
const dispatch = useDispatch();
const history = useHistory();
@ -102,28 +93,19 @@ export default function CreateProjectContent(): JSX.Element {
shouldShowNotification.current = true;
}, [newProjectId]);
const onSumbit = (): void => {
const onSumbit = async (): Promise<void> => {
interface Project {
[key: string]: any;
}
const projectData: Project = {};
if (nameFormRef.current !== null) {
nameFormRef.current.validateFields((error, value) => {
if (!error) {
projectData.name = value.name;
}
});
}
if (advancedFormRef.current !== null) {
advancedFormRef.current.validateFields((error, values) => {
if (!error) {
for (const [field, value] of Object.entries(values)) {
projectData[field] = value;
}
}
});
if (nameFormRef.current && advancedFormRef.current) {
const basicValues = await nameFormRef.current.validateFields();
const advancedValues = await advancedFormRef.current.validateFields();
projectData.name = basicValues.name;
for (const [field, value] of Object.entries(advancedValues)) {
projectData[field] = value;
}
}
projectData.labels = projectLabels;
@ -134,9 +116,9 @@ export default function CreateProjectContent(): JSX.Element {
};
return (
<Row type='flex' justify='start' align='middle' className='cvat-create-project-content'>
<Row justify='start' align='middle' className='cvat-create-project-content'>
<Col span={24}>
<ProjectNameEditor ref={nameFormRef} />
<NameConfigurationForm formRef={nameFormRef} />
</Col>
<Col span={24}>
<Text className='cvat-text-color'>Labels:</Text>
@ -148,7 +130,7 @@ export default function CreateProjectContent(): JSX.Element {
/>
</Col>
<Col span={24}>
<AdvanvedConfigurationForm ref={advancedFormRef} />
<AdvanvedConfigurationForm formRef={advancedFormRef} />
</Col>
<Col span={24}>
<Button type='primary' onClick={onSumbit}>

@ -11,7 +11,7 @@ import CreateProjectContent from './create-project-content';
export default function CreateProjectPageComponent(): JSX.Element {
return (
<Row type='flex' justify='center' align='top' className='cvat-create-task-form-wrapper'>
<Row justify='center' align='top' className='cvat-create-task-form-wrapper'>
<Col md={20} lg={16} xl={14} xxl={9}>
<Text className='cvat-title'>Create a new project</Text>
<CreateProjectContent />

@ -2,16 +2,17 @@
//
// SPDX-License-Identifier: MIT
import React from 'react';
import React, { RefObject } from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import { PercentageOutlined } from '@ant-design/icons';
import Input from 'antd/lib/input';
import Checkbox from 'antd/lib/checkbox';
import Tooltip from 'antd/lib/tooltip';
import Form, { FormComponentProps } from 'antd/lib/form/Form';
import Form, { FormInstance, RuleObject, RuleRender } from 'antd/lib/form';
import Text from 'antd/lib/typography/Text';
import patterns from 'utils/validation-patterns';
import { Store } from 'antd/lib/form/interface';
export interface AdvancedConfiguration {
bugTracker?: string;
@ -29,273 +30,248 @@ export interface AdvancedConfiguration {
copyData?: boolean;
}
type Props = FormComponentProps & {
const initialValues: AdvancedConfiguration = {
imageQuality: 70,
lfs: false,
useZipChunks: true,
useCache: true,
copyData: false,
};
interface Props {
onSubmit(values: AdvancedConfiguration): void;
installedGit: boolean;
activeFileManagerTab: string;
};
function isPositiveInteger(_: any, value: any, callback: any): void {
if (!value) {
callback();
return;
}
}
const intValue = +value;
if (Number.isNaN(intValue) || !Number.isInteger(intValue) || intValue < 1) {
callback('Value must be a positive integer');
function validateURL(_: RuleObject, value: string): Promise<void> {
if (value && !patterns.validateURL.pattern.test(value)) {
return Promise.reject(new Error('URL is not a valid URL'));
}
callback();
return Promise.resolve();
}
function isNonNegativeInteger(_: any, value: any, callback: any): void {
if (!value) {
callback();
return;
function validateRepositoryPath(_: RuleObject, value: string): Promise<void> {
if (value && !patterns.validatePath.pattern.test(value)) {
return Promise.reject(new Error('Repository path is not a valid path'));
}
const intValue = +value;
if (Number.isNaN(intValue) || intValue < 0) {
callback('Value must be a non negative integer');
return Promise.resolve();
}
function validateRepository(_: RuleObject, value: string): Promise<[void, void]> | Promise<void> {
if (value) {
const [url, path] = value.split(/\s+/);
return Promise.all([validateURL(_, url), validateRepositoryPath(_, path)]);
}
callback();
return Promise.resolve();
}
function isIntegerRange(min: number, max: number, _: any, value: any, callback: any): void {
if (!value) {
callback();
return;
const isInteger = ({ min, max }: { min?: number; max?: number }) => (
_: RuleObject,
value?: number | string,
): Promise<void> => {
if (typeof value === 'undefined' || value === '') {
return Promise.resolve();
}
const intValue = +value;
if (Number.isNaN(intValue) || !Number.isInteger(intValue) || intValue < min || intValue > max) {
callback(`Value must be an integer [${min}, ${max}]`);
if (Number.isNaN(intValue) || !Number.isInteger(intValue)) {
return Promise.reject(new Error('Value must be a positive integer'));
}
callback();
}
if (typeof min !== 'undefined' && intValue < min) {
return Promise.reject(new Error(`Value must be more than ${min}`));
}
if (typeof max !== 'undefined' && intValue > max) {
return Promise.reject(new Error(`Value must be less than ${max}`));
}
return Promise.resolve();
};
const validateOverlapSize: RuleRender = ({ getFieldValue }): RuleObject => ({
validator(_: RuleObject, value?: string | number): Promise<void> {
if (typeof value !== 'undefined' && value !== '') {
const segmentSize = getFieldValue('segmentSize');
if (typeof segmentSize !== 'undefined' && segmentSize !== '') {
if (+segmentSize <= +value) {
return Promise.reject(new Error('Segment size must be more than overlap size'));
}
}
}
return Promise.resolve();
},
});
const validateStopFrame: RuleRender = ({ getFieldValue }): RuleObject => ({
validator(_: RuleObject, value?: string | number): Promise<void> {
if (typeof value !== 'undefined' && value !== '') {
const startFrame = getFieldValue('startFrame');
if (typeof startFrame !== 'undefined' && startFrame !== '') {
if (+startFrame > +value) {
return Promise.reject(new Error('Start frame must not be more than stop frame'));
}
}
}
return Promise.resolve();
},
});
class AdvancedConfigurationForm extends React.PureComponent<Props> {
private formRef: RefObject<FormInstance>;
public constructor(props: Props) {
super(props);
this.formRef = React.createRef<FormInstance>();
}
public submit(): Promise<void> {
return new Promise((resolve, reject) => {
const { form, onSubmit } = this.props;
form.validateFields((error, values): void => {
if (!error) {
const filteredValues = { ...values };
delete filteredValues.frameStep;
if (values.overlapSize && +values.segmentSize <= +values.overlapSize) {
reject(new Error('Segment size must be more than overlap size'));
}
if (
typeof values.startFrame !== 'undefined' &&
typeof values.stopFrame !== 'undefined' &&
+values.stopFrame < +values.startFrame
) {
reject(new Error('Stop frame must be more or equal start frame'));
}
const { onSubmit } = this.props;
if (this.formRef.current) {
return this.formRef.current.validateFields().then(
(values: Store): Promise<void> => {
const frameFilter = values.frameStep ? `step=${values.frameStep}` : undefined;
const entries = Object.entries(values).filter(
(entry: [string, unknown]): boolean => entry[0] !== frameFilter,
);
onSubmit({
...values,
frameFilter: values.frameStep ? `step=${values.frameStep}` : undefined,
...((Object.fromEntries(entries) as any) as AdvancedConfiguration),
frameFilter,
});
resolve();
} else {
reject();
}
});
});
return Promise.resolve();
},
);
}
return Promise.reject(new Error('Form ref is empty'));
}
public resetFields(): void {
const { form } = this.props;
form.resetFields();
if (this.formRef.current) {
this.formRef.current.resetFields();
}
}
renderCopyDataChechbox(): JSX.Element {
const { form } = this.props;
/* eslint-disable class-methods-use-this */
private renderCopyDataChechbox(): JSX.Element {
return (
<Row>
<Col>
<Form.Item help='If you have a low data transfer rate over the network you can copy data into CVAT to speed up work'>
{form.getFieldDecorator('copyData', {
initialValue: false,
valuePropName: 'checked',
})(
<Checkbox>
<Text className='cvat-text-color'>Copy data into CVAT</Text>
</Checkbox>,
)}
</Form.Item>
</Col>
</Row>
<Form.Item
help='If you have a low data transfer rate over the network you can copy data into CVAT to speed up work'
name='copyData'
valuePropName='checked'
>
<Checkbox>
<Text className='cvat-text-color'>Copy data into CVAT</Text>
</Checkbox>
</Form.Item>
);
}
private renderImageQuality(): JSX.Element {
const { form } = this.props;
return (
<Form.Item label={<span>Image quality</span>}>
<Tooltip title='Defines image quality level' mouseLeaveDelay={0}>
{form.getFieldDecorator('imageQuality', {
initialValue: 70,
rules: [
{
required: true,
message: 'The field is required.',
},
{
validator: isIntegerRange.bind(null, 5, 100),
},
],
})(<Input size='large' type='number' suffix={<Icon type='percentage' />} />)}
</Tooltip>
</Form.Item>
<Tooltip title='Defines images compression level' mouseLeaveDelay={0}>
<Form.Item
label='Image quality'
name='imageQuality'
rules={[
{
required: true,
message: 'The field is required.',
},
{ validator: isInteger({ min: 5, max: 100 }) },
]}
>
<Input size='large' type='number' min={5} max={100} suffix={<PercentageOutlined />} />
</Form.Item>
</Tooltip>
);
}
private renderOverlap(): JSX.Element {
const { form } = this.props;
return (
<Form.Item label={<span>Overlap size</span>}>
<Tooltip title='Defines a number of intersected frames between different segments' mouseLeaveDelay={0}>
{form.getFieldDecorator('overlapSize', {
rules: [
{
validator: isNonNegativeInteger,
},
],
})(<Input size='large' type='number' />)}
</Tooltip>
</Form.Item>
<Tooltip title='Defines a number of intersected frames between different segments' mouseLeaveDelay={0}>
<Form.Item
label='Overlap size'
name='overlapSize'
dependencies={['segmentSize']}
rules={[{ validator: isInteger({ min: 0 }) }, validateOverlapSize]}
>
<Input size='large' type='number' min={0} />
</Form.Item>
</Tooltip>
);
}
private renderSegmentSize(): JSX.Element {
const { form } = this.props;
return (
<Form.Item label={<span>Segment size</span>}>
<Tooltip title='Defines a number of frames in a segment' mouseLeaveDelay={0}>
{form.getFieldDecorator('segmentSize', {
rules: [
{
validator: isPositiveInteger,
},
],
})(<Input size='large' type='number' />)}
</Tooltip>
</Form.Item>
<Tooltip title='Defines a number of frames in a segment' mouseLeaveDelay={0}>
<Form.Item label='Segment size' name='segmentSize' rules={[{ validator: isInteger({ min: 1 }) }]}>
<Input size='large' type='number' min={1} />
</Form.Item>
</Tooltip>
);
}
private renderStartFrame(): JSX.Element {
const { form } = this.props;
return (
<Form.Item label={<span>Start frame</span>}>
{form.getFieldDecorator('startFrame', {
rules: [
{
validator: isNonNegativeInteger,
},
],
})(<Input size='large' type='number' min={0} step={1} />)}
<Form.Item label='Start frame' name='startFrame' rules={[{ validator: isInteger({ min: 0 }) }]}>
<Input size='large' type='number' min={0} step={1} />
</Form.Item>
);
}
private renderStopFrame(): JSX.Element {
const { form } = this.props;
return (
<Form.Item label={<span>Stop frame</span>}>
{form.getFieldDecorator('stopFrame', {
rules: [
{
validator: isNonNegativeInteger,
},
],
})(<Input size='large' type='number' min={0} step={1} />)}
<Form.Item
label='Stop frame'
name='stopFrame'
dependencies={['startFrame']}
rules={[{ validator: isInteger({ min: 0 }) }, validateStopFrame]}
>
<Input size='large' type='number' min={0} step={1} />
</Form.Item>
);
}
private renderFrameStep(): JSX.Element {
const { form } = this.props;
return (
<Form.Item label={<span>Frame step</span>}>
{form.getFieldDecorator('frameStep', {
rules: [
{
validator: isPositiveInteger,
},
],
})(<Input size='large' type='number' min={1} step={1} />)}
<Form.Item label='Frame step' name='frameStep' rules={[{ validator: isInteger({ min: 1 }) }]}>
<Input size='large' type='number' min={1} step={1} />
</Form.Item>
);
}
private renderGitLFSBox(): JSX.Element {
const { form } = this.props;
return (
<Form.Item help='If annotation files are large, you can use git LFS feature'>
{form.getFieldDecorator('lfs', {
valuePropName: 'checked',
initialValue: false,
})(
<Checkbox>
<Text className='cvat-text-color'>Use LFS (Large File Support):</Text>
</Checkbox>,
)}
<Form.Item
help='If annotation files are large, you can use git LFS feature'
name='lfs'
valuePropName='checked'
>
<Checkbox>
<Text className='cvat-text-color'>Use LFS (Large File Support):</Text>
</Checkbox>
</Form.Item>
);
}
private renderGitRepositoryURL(): JSX.Element {
const { form } = this.props;
return (
<Form.Item
hasFeedback
label={<span>Dataset repository URL</span>}
name='repository'
label='Dataset repository URL'
extra='Attach a repository to store annotations there'
rules={[{ validator: validateRepository }]}
>
{form.getFieldDecorator('repository', {
rules: [
{
validator: (_, value, callback): void => {
if (!value) {
callback();
} else {
const [url, path] = value.split(/\s+/);
if (!patterns.validateURL.pattern.test(url)) {
callback('Git URL is not a valid');
}
if (path && !patterns.validatePath.pattern.test(path)) {
callback('Git path is not a valid');
}
callback();
}
},
},
],
})(
<Input
size='large'
placeholder='e.g. https//github.com/user/repos [annotation/<anno_file_name>.zip]'
/>,
)}
<Input size='large' placeholder='e.g. https//github.com/user/repos [annotation/<anno_file_name>.zip]' />
</Form.Item>
);
}
@ -304,125 +280,97 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
return (
<>
<Row>
<Col>{this.renderGitRepositoryURL()}</Col>
<Col span={24}>{this.renderGitRepositoryURL()}</Col>
</Row>
<Row>
<Col>{this.renderGitLFSBox()}</Col>
<Col span={24}>{this.renderGitLFSBox()}</Col>
</Row>
</>
);
}
private renderBugTracker(): JSX.Element {
const { form } = this.props;
return (
<Form.Item
hasFeedback
label={<span>Issue tracker</span>}
name='bugTracker'
label='Issue tracker'
extra='Attach issue tracker where the task is described'
rules={[{ validator: validateURL }]}
>
{form.getFieldDecorator('bugTracker', {
rules: [
{
validator: (_, value, callback): void => {
if (value && !patterns.validateURL.pattern.test(value)) {
callback('Issue tracker must be URL');
} else {
callback();
}
},
},
],
})(<Input size='large' />)}
<Input size='large' />
</Form.Item>
);
}
private renderUzeZipChunks(): JSX.Element {
const { form } = this.props;
return (
<Form.Item help='Force to use zip chunks as compressed data. Actual for videos only.'>
{form.getFieldDecorator('useZipChunks', {
initialValue: true,
valuePropName: 'checked',
})(
<Checkbox>
<Text className='cvat-text-color'>Use zip chunks</Text>
</Checkbox>,
)}
<Form.Item
help='Force to use zip chunks as compressed data. Actual for videos only.'
name='useZipChunks'
valuePropName='checked'
>
<Checkbox>
<Text className='cvat-text-color'>Use zip chunks</Text>
</Checkbox>
</Form.Item>
);
}
private renderCreateTaskMethod(): JSX.Element {
const { form } = this.props;
return (
<Form.Item help='Using cache to store data.'>
{form.getFieldDecorator('useCache', {
initialValue: true,
valuePropName: 'checked',
})(
<Checkbox>
<Text className='cvat-text-color'>Use cache</Text>
</Checkbox>,
)}
<Form.Item help='Using cache to store data.' name='useCache' valuePropName='checked'>
<Checkbox>
<Text className='cvat-text-color'>Use cache</Text>
</Checkbox>
</Form.Item>
);
}
private renderChunkSize(): JSX.Element {
const { form } = this.props;
return (
<Form.Item label={<span>Chunk size</span>}>
<Tooltip
title={
<>
Defines a number of frames to be packed in a chunk when send from client to server. Server
defines automatically if empty.
<br />
Recommended values:
<br />
1080p or less: 36
<br />
2k or less: 8 - 16
<br />
4k or less: 4 - 8
<br />
More: 1 - 4
</>
}
mouseLeaveDelay={0}
>
{form.getFieldDecorator('dataChunkSize', {
rules: [
{
validator: isPositiveInteger,
},
],
})(<Input size='large' type='number' />)}
</Tooltip>
</Form.Item>
<Tooltip
title={(
<>
Defines a number of frames to be packed in a chunk when send from client to server. Server
defines automatically if empty.
<br />
Recommended values:
<br />
1080p or less: 36
<br />
2k or less: 8 - 16
<br />
4k or less: 4 - 8
<br />
More: 1 - 4
</>
)}
mouseLeaveDelay={0}
>
<Form.Item label='Chunk size' name='dataChunkSize' rules={[{ validator: isInteger({ min: 1 }) }]}>
<Input size='large' type='number' />
</Form.Item>
</Tooltip>
);
}
public render(): JSX.Element {
const { installedGit, activeFileManagerTab } = this.props;
return (
<Form>
{activeFileManagerTab === 'share' ? this.renderCopyDataChechbox() : null}
<Form initialValues={initialValues} ref={this.formRef} layout='vertical'>
{activeFileManagerTab === 'share' ? (
<Row>
<Col>{this.renderCopyDataChechbox()}</Col>
</Row>
) : null}
<Row>
<Col>{this.renderUzeZipChunks()}</Col>
</Row>
<Row>
<Col>{this.renderCreateTaskMethod()}</Col>
</Row>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col span={7}>{this.renderImageQuality()}</Col>
<Col span={7} offset={1}>
{this.renderOverlap()}
@ -432,7 +380,7 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
</Col>
</Row>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col span={7}>{this.renderStartFrame()}</Col>
<Col span={7} offset={1}>
{this.renderStopFrame()}
@ -442,18 +390,18 @@ class AdvancedConfigurationForm extends React.PureComponent<Props> {
</Col>
</Row>
<Row type='flex' justify='start'>
<Row justify='start'>
<Col span={7}>{this.renderChunkSize()}</Col>
</Row>
{installedGit ? this.renderGit() : null}
<Row>
<Col>{this.renderBugTracker()}</Col>
<Col span={24}>{this.renderBugTracker()}</Col>
</Row>
</Form>
);
}
}
export default Form.create<Props>()(AdvancedConfigurationForm);
export default AdvancedConfigurationForm;

@ -2,60 +2,62 @@
//
// SPDX-License-Identifier: MIT
import React from 'react';
import React, { RefObject } from 'react';
import Input from 'antd/lib/input';
import Form, { FormComponentProps } from 'antd/lib/form/Form';
import Form, { FormInstance } from 'antd/lib/form';
import { Store } from 'antd/lib/form/interface';
export interface BaseConfiguration {
name: string;
}
type Props = FormComponentProps & {
interface Props {
onSubmit(values: BaseConfiguration): void;
};
}
export default class BasicConfigurationForm extends React.PureComponent<Props> {
private formRef: RefObject<FormInstance>;
public constructor(props: Props) {
super(props);
this.formRef = React.createRef<FormInstance>();
}
class BasicConfigurationForm extends React.PureComponent<Props> {
public submit(): Promise<void> {
return new Promise((resolve, reject) => {
const { form, onSubmit } = this.props;
form.validateFields((error, values): void => {
if (!error) {
onSubmit({
name: values.name,
});
resolve();
} else {
reject();
}
const { onSubmit } = this.props;
if (this.formRef.current) {
return this.formRef.current.validateFields().then((values: Store): Promise<void> => {
onSubmit({ name: values.name });
return Promise.resolve();
});
});
}
return Promise.reject(new Error('Form ref is empty'));
}
public resetFields(): void {
const { form } = this.props;
form.resetFields();
if (this.formRef.current) {
this.formRef.current.resetFields();
}
}
public render(): JSX.Element {
const { form } = this.props;
const { getFieldDecorator } = form;
return (
<Form onSubmit={(e: React.FormEvent): void => e.preventDefault()}>
<Form.Item hasFeedback label={<span>Name</span>}>
{getFieldDecorator('name', {
rules: [
{
required: true,
message: 'Please, specify a name',
},
],
})(<Input />)}
<Form ref={this.formRef} layout='vertical'>
<Form.Item
hasFeedback
name='name'
label={<span>Name</span>}
rules={[
{
required: true,
message: 'Task name cannot be empty',
},
]}
>
<Input />
</Form.Item>
</Form>
);
}
}
export default Form.create<Props>()(BasicConfigurationForm);

@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: MIT
import React from 'react';
import React, { RefObject } from 'react';
import { RouteComponentProps } from 'react-router';
import { withRouter } from 'react-router-dom';
import { Row, Col } from 'antd/lib/grid';
@ -58,15 +58,15 @@ const defaultState = {
};
class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps, State> {
private basicConfigurationComponent: any;
private advancedConfigurationComponent: any;
private basicConfigurationComponent: RefObject<BasicConfigurationForm>;
private advancedConfigurationComponent: RefObject<AdvancedConfigurationForm>;
private fileManagerContainer: any;
public constructor(props: Props & RouteComponentProps) {
super(props);
this.state = { ...defaultState };
this.basicConfigurationComponent = React.createRef<BasicConfigurationForm>();
this.advancedConfigurationComponent = React.createRef<AdvancedConfigurationForm>();
}
public componentDidMount(): void {
@ -88,9 +88,11 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
btn,
});
this.basicConfigurationComponent.resetFields();
if (this.advancedConfigurationComponent) {
this.advancedConfigurationComponent.resetFields();
if (this.basicConfigurationComponent.current) {
this.basicConfigurationComponent.current.resetFields();
}
if (this.advancedConfigurationComponent.current) {
this.advancedConfigurationComponent.current.resetFields();
}
this.fileManagerContainer.reset();
@ -138,7 +140,7 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
const values = this.state;
this.setState({
...values,
activeFileManagerTab: key
activeFileManagerTab: key,
});
};
@ -159,36 +161,34 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
return;
}
this.basicConfigurationComponent
.submit()
.then(() => {
if (this.advancedConfigurationComponent) {
return this.advancedConfigurationComponent.submit();
}
return new Promise((resolve): void => {
resolve();
if (this.basicConfigurationComponent.current) {
this.basicConfigurationComponent.current.submit()
.then(() => {
if (this.advancedConfigurationComponent.current) {
return this.advancedConfigurationComponent.current.submit();
}
return new Promise((resolve): void => {
resolve();
});
}).then((): void => {
const { onCreate } = this.props;
onCreate(this.state);
})
.catch((error: Error): void => {
notification.error({
message: 'Could not create a task',
description: error.toString(),
});
});
})
.then((): void => {
const { onCreate } = this.props;
onCreate(this.state);
})
.catch((error: Error): void => {
notification.error({
message: 'Could not create a task',
description: error.toString(),
});
});
}
};
private renderBasicBlock(): JSX.Element {
return (
<Col span={24}>
<BasicConfigurationForm
wrappedComponentRef={(component: any): void => {
this.basicConfigurationComponent = component;
}}
ref={this.basicConfigurationComponent}
onSubmit={this.handleSubmitBasicConfiguration}
/>
</Col>
@ -260,16 +260,15 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
private renderAdvancedBlock(): JSX.Element {
const { installedGit } = this.props;
const { activeFileManagerTab } = this.state;
return (
<Col span={24}>
<Collapse>
<Collapse.Panel key='1' header={<Text className='cvat-title'>Advanced configuration</Text>}>
<AdvancedConfigurationForm
installedGit={installedGit}
activeFileManagerTab={this.state.activeFileManagerTab}
wrappedComponentRef={(component: any): void => {
this.advancedConfigurationComponent = component;
}}
activeFileManagerTab={activeFileManagerTab}
ref={this.advancedConfigurationComponent}
onSubmit={this.handleSubmitAdvancedConfiguration}
/>
</Collapse.Panel>
@ -283,7 +282,7 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
const loading = !!status && status !== 'CREATED' && status !== 'FAILED';
return (
<Row type='flex' justify='start' align='middle' className='cvat-create-task-content'>
<Row justify='start' align='middle' className='cvat-create-task-content'>
<Col span={24}>
<Text className='cvat-title'>Basic configuration</Text>
</Col>

@ -69,7 +69,7 @@ export default function CreateTaskPage(props: Props): JSX.Element {
}, [error]);
return (
<Row type='flex' justify='center' align='top' className='cvat-create-task-form-wrapper'>
<Row justify='center' align='top' className='cvat-create-task-form-wrapper'>
<Col md={20} lg={16} xl={14} xxl={9}>
<Text className='cvat-title'>Create a new task</Text>
<CreateTaskContent

@ -62,10 +62,13 @@ export default function ProjectSearchField(props: Props): JSX.Element {
if (value && !projects.filter((project) => project.id === value).length) {
core.projects.get({ id: value }).then((result: Project[]) => {
const [project] = result;
setProjects([...projects, {
id: project.id,
name: project.name,
}]);
setProjects([
...projects,
{
id: project.id,
name: project.name,
},
]);
setSearchPhrase(project.name);
onSelect(project.id);
});
@ -80,12 +83,10 @@ export default function ProjectSearchField(props: Props): JSX.Element {
onSelect={handleSelect}
className='cvat-project-search-field'
onDropdownVisibleChange={handleFocus}
dataSource={
projects.map((proj) => ({
value: proj.id.toString(),
text: proj.name,
}))
}
options={projects.map((proj) => ({
value: proj.id.toString(),
label: proj.name,
}))}
/>
);
}

@ -5,9 +5,11 @@
import './styles.scss';
import React from 'react';
import Button from 'antd/lib/button';
import Icon from 'antd/lib/icon';
import Popover from 'antd/lib/popover';
import Text from 'antd/lib/typography/Text';
import {
StarOutlined, LikeOutlined, CloseCircleOutlined, MessageOutlined,
} from '@ant-design/icons';
import {
FacebookShareButton,
LinkedinShareButton,
@ -34,7 +36,7 @@ function renderContent(): JSX.Element {
return (
<>
<Icon type='star' />
<StarOutlined />
<Text style={{ marginLeft: '10px' }}>
Star us on
<a target='_blank' rel='noopener noreferrer' href={GITHUB_URL}>
@ -43,7 +45,7 @@ function renderContent(): JSX.Element {
</a>
</Text>
<br />
<Icon type='like' />
<LikeOutlined />
<Text style={{ marginLeft: '10px' }}>
Leave a
<a target='_blank' rel='noopener noreferrer' href={GITTER_PUBLIC_URL}>
@ -107,14 +109,14 @@ export default function Feedback(): JSX.Element {
visible={visible}
>
<Button
style={{ color: '#ff4d4f' }}
style={visible ? { color: '#ff4d4f' } : {}}
className='cvat-feedback-button'
type='link'
onClick={(): void => {
setVisible(!visible);
}}
>
{visible ? <Icon type='close-circle' theme='filled' /> : <Icon type='message' theme='twoTone' />}
{visible ? <CloseCircleOutlined /> : <MessageOutlined />}
</Button>
</Popover>
</>

@ -9,7 +9,7 @@
padding: 0;
height: auto;
> i {
> span[role='img'] {
font-size: 40px;
}
}

@ -3,15 +3,17 @@
// SPDX-License-Identifier: MIT
import './styles.scss';
import React from 'react';
import React, { ReactText } from 'react';
import Tabs from 'antd/lib/tabs';
import Icon from 'antd/lib/icon';
import Input from 'antd/lib/input';
import Text from 'antd/lib/typography/Text';
import Paragraph from 'antd/lib/typography/Paragraph';
import Upload, { RcFile } from 'antd/lib/upload';
import Empty from 'antd/lib/empty';
import Tree, { AntTreeNode, TreeNodeNormal } from 'antd/lib/tree/Tree';
import Tree, { TreeNodeNormal } from 'antd/lib/tree/Tree';
// eslint-disable-next-line import/no-extraneous-dependencies
import { EventDataNode } from 'rc-tree/lib/interface';
import { InboxOutlined } from '@ant-design/icons';
import consts from 'consts';
@ -106,7 +108,7 @@ export default class FileManager extends React.PureComponent<Props, State> {
}}
>
<p className='ant-upload-drag-icon'>
<Icon type='inbox' />
<InboxOutlined />
</p>
<p className='ant-upload-text'>Click or drag files to this area</p>
<p className='ant-upload-hint'>Support for a bulk images or a single video</p>
@ -124,17 +126,18 @@ export default class FileManager extends React.PureComponent<Props, State> {
private renderShareSelector(): JSX.Element {
function renderTreeNodes(data: TreeNodeNormal[]): JSX.Element[] {
// sort alphabetically
data.sort((a: TreeNodeNormal, b: TreeNodeNormal): number => a.key.localeCompare(b.key));
data.sort((a: TreeNodeNormal, b: TreeNodeNormal): number =>
a.key.toLocaleString().localeCompare(b.key.toLocaleString()));
return data.map((item: TreeNodeNormal) => {
if (item.children) {
return (
<Tree.TreeNode title={item.title} key={item.key} dataRef={item} isLeaf={item.isLeaf}>
<Tree.TreeNode title={item.title} key={item.key} data={item} isLeaf={item.isLeaf}>
{renderTreeNodes(item.children)}
</Tree.TreeNode>
);
}
return <Tree.TreeNode key={item.key} {...item} dataRef={item} />;
return <Tree.TreeNode {...item} key={item.key} data={item} />;
});
}
@ -149,24 +152,26 @@ export default class FileManager extends React.PureComponent<Props, State> {
className='cvat-share-tree'
checkable
showLine
height={256}
checkStrictly={false}
expandedKeys={expandedKeys}
checkedKeys={files.share}
loadData={(node: AntTreeNode): Promise<void> => this.loadData(node.props.dataRef.key)}
onExpand={(newExpandedKeys: string[]): void => {
loadData={(event: EventDataNode): Promise<void> => this.loadData(event.key.toLocaleString())}
onExpand={(newExpandedKeys: ReactText[]): void => {
this.setState({
expandedKeys: newExpandedKeys,
expandedKeys: newExpandedKeys.map((text: ReactText): string => text.toLocaleString()),
});
}}
onCheck={(
checkedKeys:
| string[]
| {
checked: string[];
halfChecked: string[];
},
| ReactText[]
| {
checked: ReactText[];
halfChecked: ReactText[];
},
): void => {
const keys = checkedKeys as string[];
const keys = (checkedKeys as ReactText[]).map((text: ReactText): string =>
text.toLocaleString());
this.setState({
files: {
...files,

@ -5,10 +5,7 @@
@import '../../base.scss';
.cvat-share-tree {
height: fit-content;
min-height: 10em;
max-height: 20em;
overflow: auto;
height: 32 * $grid-unit-size;
}
.cvat-empty-share-tree {

@ -100,7 +100,9 @@ class GlobalErrorBoundary extends React.PureComponent<Props, State> {
}
public render(): React.ReactNode {
const { restore, job, serverVersion, coreVersion, canvasVersion, uiVersion } = this.props;
const {
restore, job, serverVersion, coreVersion, canvasVersion, uiVersion,
} = this.props;
const { hasError, error } = this.state;
@ -152,7 +154,8 @@ class GlobalErrorBoundary extends React.PureComponent<Props, State> {
}}
>
{' '}
Copy{' '}
Copy
{' '}
</a>
</Tooltip>
the error message to clipboard

@ -7,8 +7,18 @@ import React from 'react';
import { connect } from 'react-redux';
import { useHistory } from 'react-router';
import { Row, Col } from 'antd/lib/grid';
import Icon, {
SettingOutlined,
InfoCircleOutlined,
EditOutlined,
LoadingOutlined,
LogoutOutlined,
GithubOutlined,
QuestionCircleOutlined,
CaretDownOutlined,
ControlOutlined,
} from '@ant-design/icons';
import Layout from 'antd/lib/layout';
import Icon from 'antd/lib/icon';
import Button from 'antd/lib/button';
import Menu from 'antd/lib/menu';
import Dropdown from 'antd/lib/dropdown';
@ -165,7 +175,7 @@ function HeaderContainer(props: Props): JSX.Element {
<Text strong>UI version:</Text>
<Text type='secondary'>{` ${tool.ui.version}`}</Text>
</p>
<Row type='flex' justify='space-around'>
<Row justify='space-around'>
<Col>
<a href={CHANGELOG_URL} target='_blank' rel='noopener noreferrer'>
What&apos;s new?
@ -208,28 +218,28 @@ function HeaderContainer(props: Props): JSX.Element {
window.open(`${tool.server.host}/admin`, '_blank');
}}
>
<Icon type='control' />
<ControlOutlined />
Admin page
</Menu.Item>
)}
<Menu.Item title={`Press ${switchSettingsShortcut} to switch`} onClick={() => switchSettingsDialog(true)}>
<Icon type='setting' />
<SettingOutlined />
Settings
</Menu.Item>
<Menu.Item onClick={showAboutModal}>
<Icon type='info-circle' />
<InfoCircleOutlined />
About
</Menu.Item>
{renderChangePasswordItem && (
<Menu.Item onClick={(): void => switchChangePasswordDialog(true)} disabled={changePasswordFetching}>
{changePasswordFetching ? <Icon type='loading' /> : <Icon type='edit' />}
{changePasswordFetching ? <LoadingOutlined /> : <EditOutlined />}
Change password
</Menu.Item>
)}
<Menu.Item onClick={onLogout} disabled={logoutFetching}>
{logoutFetching ? <Icon type='loading' /> : <Icon type='logout' />}
{logoutFetching ? <LoadingOutlined /> : <LogoutOutlined />}
Logout
</Menu.Item>
</Menu>
@ -239,7 +249,6 @@ function HeaderContainer(props: Props): JSX.Element {
<Layout.Header className='cvat-header'>
<div className='cvat-left-header'>
<Icon className='cvat-logo-icon' component={CVATLogo} />
<Button
className='cvat-header-button'
type='link'
@ -305,7 +314,7 @@ function HeaderContainer(props: Props): JSX.Element {
window.open(GITHUB_URL, '_blank');
}}
>
<Icon type='github' />
<GithubOutlined />
<Text className='cvat-text-color'>GitHub</Text>
</Button>
<Button
@ -319,7 +328,7 @@ function HeaderContainer(props: Props): JSX.Element {
window.open(`${tool.server.host}/documentation/user_guide.html`, '_blank');
}}
>
<Icon type='question-circle' />
<QuestionCircleOutlined />
Help
</Button>
<Dropdown overlay={menu} className='cvat-header-menu-dropdown'>
@ -328,7 +337,7 @@ function HeaderContainer(props: Props): JSX.Element {
<Text strong>
{user.username.length > 14 ? `${user.username.slice(0, 10)} ...` : user.username}
</Text>
<Icon className='cvat-header-menu-icon' type='caret-down' />
<CaretDownOutlined className='cvat-header-menu-icon' />
</span>
</Dropdown>
</div>

@ -11,7 +11,7 @@ import Slider from 'antd/lib/slider';
import Select from 'antd/lib/select';
import Popover from 'antd/lib/popover';
import InputNumber from 'antd/lib/input-number';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import Text from 'antd/lib/typography/Text';
import { CompactPicker } from 'react-color';
@ -82,16 +82,16 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
return (
<div className='cvat-player-settings'>
<Row type='flex' align='bottom' className='cvat-player-settings-step'>
<Row align='bottom' className='cvat-player-settings-step'>
<Col>
<Text className='cvat-text-color'> Player step </Text>
<InputNumber
min={minFrameStep}
max={maxFrameStep}
value={frameStep}
onChange={(value: number | undefined): void => {
if (typeof value === 'number') {
onChangeFrameStep(Math.floor(clamp(value, minFrameStep, maxFrameStep)));
onChange={(value: number | undefined | string): void => {
if (typeof value !== 'undefined') {
onChangeFrameStep(Math.floor(clamp(+value, minFrameStep, maxFrameStep)));
}
}}
/>
@ -105,7 +105,7 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
</Text>
</Col>
</Row>
<Row type='flex' align='middle' className='cvat-player-settings-speed'>
<Row align='middle' className='cvat-player-settings-speed'>
<Col>
<Text className='cvat-text-color'> Player speed </Text>
<Select
@ -135,7 +135,7 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
</Select>
</Col>
</Row>
<Row type='flex' className='cvat-player-settings-canvas-background'>
<Row className='cvat-player-settings-canvas-background'>
<Col>
<Popover
content={(
@ -152,7 +152,7 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
</Popover>
</Col>
</Row>
<Row type='flex'>
<Row>
<Col>
<Checkbox
className='cvat-text-color cvat-player-settings-grid'
@ -165,7 +165,7 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
</Checkbox>
</Col>
</Row>
<Row type='flex' justify='space-between'>
<Row justify='space-between'>
<Col span={8} className='cvat-player-settings-grid-size'>
<Text className='cvat-text-color'> Grid size </Text>
<InputNumber
@ -174,9 +174,9 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
max={maxGridSize}
value={gridSize}
disabled={!grid}
onChange={(value: number | undefined): void => {
if (typeof value === 'number') {
onChangeGridSize(Math.floor(clamp(value, minGridSize, maxGridSize)));
onChange={(value: number | undefined | string): void => {
if (typeof value !== 'undefined') {
onChangeGridSize(Math.floor(clamp(+value, minGridSize, maxGridSize)));
}
}}
/>
@ -223,10 +223,10 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
<Text className='cvat-text-color'>{`${gridOpacity} %`}</Text>
</Col>
</Row>
<Row type='flex' justify='start'>
<Col>
<Row justify='start'>
<Col span={7}>
<Row className='cvat-player-settings-reset-zoom'>
<Col className='cvat-player-settings-reset-zoom-checkbox'>
<Col span={24} className='cvat-player-settings-reset-zoom-checkbox'>
<Checkbox
className='cvat-text-color'
checked={resetZoom}
@ -237,14 +237,14 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
Reset zoom
</Checkbox>
</Col>
<Col>
<Col span={24}>
<Text type='secondary'> Fit image after changing frame </Text>
</Col>
</Row>
</Col>
<Col offset={5}>
<Col span={7} offset={5}>
<Row className='cvat-player-settings-rotate-all'>
<Col className='cvat-player-settings-rotate-all-checkbox'>
<Col span={24} className='cvat-player-settings-rotate-all-checkbox'>
<Checkbox
className='cvat-text-color'
checked={rotateAll}
@ -255,7 +255,7 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
Rotate all images
</Checkbox>
</Col>
<Col>
<Col span={24}>
<Text type='secondary'> Rotate all images simultaneously </Text>
</Col>
</Row>
@ -264,8 +264,10 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
<Row>
<Col span={12}>
<Row className='cvat-player-settings-brightness'>
<Col className='cvat-text-color'>Brightness</Col>
<Col>
<Col span={24} className='cvat-text-color'>
Brightness
</Col>
<Col span={24}>
<Slider
min={50}
max={200}
@ -277,8 +279,10 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
</Col>
</Row>
<Row className='cvat-player-settings-contrast'>
<Col className='cvat-text-color'>Contrast</Col>
<Col>
<Col span={24} className='cvat-text-color'>
Contrast
</Col>
<Col span={24}>
<Slider
min={50}
max={200}
@ -290,8 +294,10 @@ export default function PlayerSettingsComponent(props: Props): JSX.Element {
</Col>
</Row>
<Row className='cvat-player-settings-saturation'>
<Col className='cvat-text-color'>Saturation</Col>
<Col>
<Col span={24} className='cvat-text-color'>
Saturation
</Col>
<Col span={24}>
<Slider
min={0}
max={300}

@ -5,9 +5,9 @@
import './styles.scss';
import React from 'react';
import Tabs from 'antd/lib/tabs';
import Icon from 'antd/lib/icon';
import Text from 'antd/lib/typography/Text';
import Modal from 'antd/lib/modal/Modal';
import { PlayCircleOutlined, LaptopOutlined } from '@ant-design/icons';
import WorkspaceSettingsContainer from 'containers/header/settings-modal/workspace-settings';
import PlayerSettingsContainer from 'containers/header/settings-modal/player-settings';
@ -28,32 +28,32 @@ const SettingsModal = (props: SettingsModalProps): JSX.Element => {
onCancel={onClose}
width={800}
className='cvat-settings-modal'
footer={
footer={(
<Button type='primary' onClick={onClose}>
Close
</Button>
}
)}
>
<div className='cvat-settings-tabs'>
<Tabs type='card' tabBarStyle={{ marginBottom: '0px', marginLeft: '-1px' }}>
<Tabs.TabPane
tab={
tab={(
<span>
<Icon type='play-circle' />
<PlayCircleOutlined />
<Text>Player</Text>
</span>
}
)}
key='player'
>
<PlayerSettingsContainer />
</Tabs.TabPane>
<Tabs.TabPane
tab={
tab={(
<span>
<Icon type='laptop' />
<LaptopOutlined />
<Text>Workspace</Text>
</span>
}
)}
key='workspace'
>
<WorkspaceSettingsContainer />

@ -55,6 +55,10 @@
justify-items: start;
}
.cvat-player-settings-grid-size-input {
height: fit-content;
}
.cvat-player-settings-grid-color {
> .ant-select {
width: 150px;
@ -75,7 +79,7 @@
}
}
.cvat-player-settings-step > div > span > i {
.cvat-player-settings-step > div > span > span[role='img'] {
margin: 0 5px;
font-size: 10px;
}

@ -49,7 +49,7 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
return (
<div className='cvat-workspace-settings'>
<Row type='flex'>
<Row>
<Col>
<Checkbox
className='cvat-text-color cvat-workspace-settings-auto-save'
@ -62,7 +62,7 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
</Checkbox>
</Col>
</Row>
<Row type='flex'>
<Row>
<Col className='cvat-workspace-settings-auto-save-interval'>
<Text type='secondary'> Auto save every </Text>
<InputNumber
@ -70,10 +70,10 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
max={maxAutoSaveInterval}
step={1}
value={Math.round(autoSaveInterval / (60 * 1000))}
onChange={(value: number | undefined): void => {
if (typeof value === 'number') {
onChange={(value: number | undefined | string): void => {
if (typeof value !== 'undefined') {
onChangeAutoSaveInterval(
Math.floor(clamp(value, minAutoSaveInterval, maxAutoSaveInterval)) * 60 * 1000,
Math.floor(clamp(+value, minAutoSaveInterval, maxAutoSaveInterval)) * 60 * 1000,
);
}
}}
@ -82,7 +82,7 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
</Col>
</Row>
<Row className='cvat-workspace-settings-show-interpolated'>
<Col>
<Col span={24}>
<Checkbox
className='cvat-text-color'
checked={showAllInterpolationTracks}
@ -93,12 +93,12 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
Show all interpolation tracks
</Checkbox>
</Col>
<Col>
<Col span={24}>
<Text type='secondary'> Show hidden interpolated objects in the side panel </Text>
</Col>
</Row>
<Row className='cvat-workspace-settings-show-text-always'>
<Col>
<Col span={24}>
<Checkbox
className='cvat-text-color'
checked={showObjectsTextAlways}
@ -109,15 +109,16 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
Always show object details
</Checkbox>
</Col>
<Col>
<Col span={24}>
<Text type='secondary'>
{' '}
Show text for an object on the canvas not only when the object is activated{' '}
Show text for an object on the canvas not only when the object is activated
{' '}
</Text>
</Col>
</Row>
<Row className='cvat-workspace-settings-autoborders'>
<Col>
<Col span={24}>
<Checkbox
className='cvat-text-color'
checked={automaticBordering}
@ -128,10 +129,11 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
Automatic bordering
</Checkbox>
</Col>
<Col>
<Col span={24}>
<Text type='secondary'>
{' '}
Enable automatic bordering for polygons and polylines during drawing/editing{' '}
Enable automatic bordering for polygons and polylines during drawing/editing
{' '}
</Text>
</Col>
</Row>
@ -142,9 +144,9 @@ export default function WorkspaceSettingsComponent(props: Props): JSX.Element {
min={minAAMMargin}
max={maxAAMMargin}
value={aamZoomMargin}
onChange={(value: number | undefined): void => {
if (typeof value === 'number') {
onChangeAAMZoomMargin(Math.floor(clamp(value, minAAMMargin, maxAAMMargin)));
onChange={(value: number | undefined | string): void => {
if (typeof value !== 'undefined') {
onChangeAAMZoomMargin(Math.floor(clamp(+value, minAAMMargin, maxAAMMargin)));
}
}}
/>

@ -3,9 +3,11 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import Select, { OptionProps, SelectProps } from 'antd/lib/select';
import Select, { SelectProps } from 'antd/lib/select';
// eslint-disable-next-line import/no-extraneous-dependencies
import { OptionData, OptionGroupData } from 'rc-select/lib/interface';
interface Props extends SelectProps {
interface Props extends SelectProps<string> {
labels: any[];
value: any | number | null;
onChange: (label: any) => void;
@ -23,13 +25,16 @@ export default function LabelSelector(props: Props): JSX.Element {
return (
<Select
virtual={false}
{...rest}
{...dinamicProps}
showSearch
filterOption={(input: string, option: React.ReactElement<OptionProps>) => {
const { children } = option.props;
if (typeof children === 'string') {
return children.toLowerCase().includes(input.toLowerCase());
filterOption={(input: string, option?: OptionData | OptionGroupData) => {
if (option) {
const { children } = option;
if (typeof children === 'string') {
return children.toLowerCase().includes(input.toLowerCase());
}
}
return false;

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import Icon from 'antd/lib/icon';
import { EditOutlined, CloseOutlined } from '@ant-design/icons';
import Tooltip from 'antd/lib/tooltip';
import Text from 'antd/lib/typography/Text';
@ -18,7 +18,9 @@ interface ConstructorViewerItemProps {
}
export default function ConstructorViewerItem(props: ConstructorViewerItemProps): JSX.Element {
const { color, label, onUpdate, onDelete } = props;
const {
color, label, onUpdate, onDelete,
} = props;
return (
<div style={{ background: color || consts.NEW_LABEL_COLOR }} className='cvat-constructor-viewer-item'>
@ -30,7 +32,7 @@ export default function ConstructorViewerItem(props: ConstructorViewerItemProps)
onClick={(): void => onUpdate(label)}
onKeyPress={(): boolean => false}
>
<Icon theme='filled' type='edit' />
<EditOutlined />
</span>
</Tooltip>
{label.id < 0 && (
@ -41,7 +43,7 @@ export default function ConstructorViewerItem(props: ConstructorViewerItemProps)
onClick={(): void => onDelete(label)}
onKeyPress={(): boolean => false}
>
<Icon type='close' />
<CloseOutlined />
</span>
</Tooltip>
)}

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import Icon from 'antd/lib/icon';
import { PlusCircleOutlined } from '@ant-design/icons';
import Button from 'antd/lib/button';
import ConstructorViewerItem from './constructor-viewer-item';
@ -21,7 +21,7 @@ export default function ConstructorViewer(props: ConstructorViewerProps): JSX.El
const list = [
<Button key='create' type='ghost' onClick={onCreate} className='cvat-constructor-viewer-new-item'>
Add label
<Icon type='plus-circle' />
<PlusCircleOutlined />
</Button>,
];
for (const label of props.labels) {

@ -2,19 +2,19 @@
//
// SPDX-License-Identifier: MIT
import React from 'react';
import React, { RefObject } from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Icon, { CloseCircleOutlined, PlusOutlined } from '@ant-design/icons';
import Input from 'antd/lib/input';
import Button from 'antd/lib/button';
import Checkbox from 'antd/lib/checkbox';
import Tooltip from 'antd/lib/tooltip';
import Select from 'antd/lib/select';
import Form, { FormComponentProps } from 'antd/lib/form/Form';
import Text from 'antd/lib/typography/Text';
import Form, { FormInstance } from 'antd/lib/form';
import Badge from 'antd/lib/badge';
import ColorPicker from 'components/annotation-page/standard-workspace/objects-side-bar/color-picker';
import { Store } from 'antd/lib/form/interface';
import ColorPicker from 'components/annotation-page/standard-workspace/objects-side-bar/color-picker';
import { ColorizeIcon } from 'icons';
import patterns from 'utils/validation-patterns';
import consts from 'consts';
@ -30,136 +30,126 @@ export enum AttributeType {
NUMBER = 'NUMBER',
}
type Props = FormComponentProps & {
interface Props {
label: Label | null;
labelNames?: string[];
onSubmit: (label: Label | null) => void;
};
}
class LabelForm extends React.PureComponent<Props, {}> {
export default class LabelForm extends React.Component<Props> {
private continueAfterSubmit: boolean;
private formRef: RefObject<FormInstance>;
constructor(props: Props) {
super(props);
this.continueAfterSubmit = false;
this.formRef = React.createRef<FormInstance>();
}
private handleSubmit = (e: React.FormEvent): void => {
const { form, label, onSubmit } = this.props;
e.preventDefault();
form.validateFields((error, formValues): void => {
if (!error) {
onSubmit({
name: formValues.labelName,
id: label ? label.id : idGenerator(),
color: formValues.labelColor,
attributes: formValues.keys.map(
(key: number, index: number): Attribute => {
let attrValues = formValues.values[key];
if (!Array.isArray(attrValues)) {
if (formValues.type[key] === AttributeType.NUMBER) {
attrValues = attrValues.split(';');
} else {
attrValues = [attrValues];
}
}
attrValues = attrValues.map((value: string) => value.trim());
return {
name: formValues.attrName[key],
input_type: formValues.type[key],
mutable: formValues.mutable[key],
id: label && index < label.attributes.length ? label.attributes[index].id : key,
values: attrValues,
};
},
),
});
form.resetFields();
if (!this.continueAfterSubmit) {
onSubmit(null);
private handleSubmit = (values: Store): void => {
const { label, onSubmit } = this.props;
onSubmit({
name: values.name,
id: label ? label.id : idGenerator(),
color: values.color,
attributes: values.attributes.map((attribute: Store) => {
let attrValues: string | string[] = attribute.values;
if (!Array.isArray(attrValues)) {
if (attribute.type === AttributeType.NUMBER) {
attrValues = attrValues.split(';');
} else {
attrValues = [attrValues];
}
}
}
attrValues = attrValues.map((value: string) => value.trim());
return {
...attribute,
values: attrValues,
input_type: attribute.type.toLowerCase(),
};
}),
});
if (this.formRef.current) {
this.formRef.current.resetFields();
this.formRef.current.setFieldsValue({ attributes: [] });
}
if (!this.continueAfterSubmit) {
onSubmit(null);
}
};
private addAttribute = (): void => {
const { form } = this.props;
const keys = form.getFieldValue('keys');
const nextKeys = keys.concat(idGenerator());
form.setFieldsValue({
keys: nextKeys,
});
if (this.formRef.current) {
const attributes = this.formRef.current.getFieldValue('attributes');
this.formRef.current.setFieldsValue({ attributes: [...attributes, { id: idGenerator() }] });
}
};
private removeAttribute = (key: number): void => {
const { form } = this.props;
const keys = form.getFieldValue('keys');
form.setFieldsValue({
keys: keys.filter((_key: number) => _key !== key),
});
if (this.formRef.current) {
const attributes = this.formRef.current.getFieldValue('attributes');
this.formRef.current.setFieldsValue({
attributes: attributes.filter((_: any, id: number) => id !== key),
});
}
};
private renderAttributeNameInput(key: number, attr: Attribute | null): JSX.Element {
/* eslint-disable class-methods-use-this */
private renderAttributeNameInput(fieldInstance: any, attr: Attribute | null): JSX.Element {
const { key } = fieldInstance;
const locked = attr ? attr.id >= 0 : false;
const value = attr ? attr.name : '';
const { form } = this.props;
return (
<Col span={5}>
<Form.Item hasFeedback>
{form.getFieldDecorator(`attrName[${key}]`, {
initialValue: value,
rules: [
{
required: true,
message: 'Please specify a name',
},
{
pattern: patterns.validateAttributeName.pattern,
message: patterns.validateAttributeName.message,
},
],
})(<Input className='cvat-attribute-name-input' disabled={locked} placeholder='Name' />)}
</Form.Item>
</Col>
<Form.Item
hasFeedback
name={[key, 'name']}
fieldKey={[fieldInstance.fieldKey, 'name']}
initialValue={value}
rules={[
{
required: true,
message: 'Please specify a name',
},
{
pattern: patterns.validateAttributeName.pattern,
message: patterns.validateAttributeName.message,
},
]}
>
<Input className='cvat-attribute-name-input' disabled={locked} placeholder='Name' />
</Form.Item>
);
}
private renderAttributeTypeInput(key: number, attr: Attribute | null): JSX.Element {
private renderAttributeTypeInput(fieldInstance: any, attr: Attribute | null): JSX.Element {
const { key } = fieldInstance;
const locked = attr ? attr.id >= 0 : false;
const type = attr ? attr.input_type.toUpperCase() : AttributeType.SELECT;
const { form } = this.props;
return (
<Col span={4}>
<Form.Item>
<Tooltip title='An HTML element representing the attribute' mouseLeaveDelay={0}>
{form.getFieldDecorator(`type[${key}]`, {
initialValue: type,
})(
<Select className='cvat-attribute-type-input' disabled={locked}>
<Select.Option value={AttributeType.SELECT}>Select</Select.Option>
<Select.Option value={AttributeType.RADIO}>Radio</Select.Option>
<Select.Option value={AttributeType.CHECKBOX}>Checkbox</Select.Option>
<Select.Option value={AttributeType.TEXT}>Text</Select.Option>
<Select.Option value={AttributeType.NUMBER}>Number</Select.Option>
</Select>,
)}
</Tooltip>
<Tooltip title='An HTML element representing the attribute' mouseLeaveDelay={0}>
<Form.Item name={[key, 'type']} fieldKey={[fieldInstance.fieldKey, 'type']} initialValue={type}>
<Select className='cvat-attribute-type-input' disabled={locked}>
<Select.Option value={AttributeType.SELECT}>Select</Select.Option>
<Select.Option value={AttributeType.RADIO}>Radio</Select.Option>
<Select.Option value={AttributeType.CHECKBOX}>Checkbox</Select.Option>
<Select.Option value={AttributeType.TEXT}>Text</Select.Option>
<Select.Option value={AttributeType.NUMBER}>Number</Select.Option>
</Select>
</Form.Item>
</Col>
</Tooltip>
);
}
private renderAttributeValuesInput(key: number, attr: Attribute | null): JSX.Element {
private renderAttributeValuesInput(fieldInstance: any, attr: Attribute | null): JSX.Element {
const { key } = fieldInstance;
const locked = attr ? attr.id >= 0 : false;
const existedValues = attr ? attr.values : [];
const { form } = this.props;
const validator = (_: any, values: string[], callback: any): void => {
if (locked && existedValues) {
@ -179,55 +169,51 @@ class LabelForm extends React.PureComponent<Props, {}> {
return (
<Tooltip title='Press enter to add a new value' mouseLeaveDelay={0}>
<Form.Item>
{form.getFieldDecorator(`values[${key}]`, {
initialValue: existedValues,
rules: [
{
required: true,
message: 'Please specify values',
},
{
validator,
},
],
})(
<Select
className='cvat-attribute-values-input'
mode='tags'
dropdownMenuStyle={{ display: 'none' }}
placeholder='Attribute values'
/>,
)}
<Form.Item
name={[key, 'values']}
fieldKey={[fieldInstance.fieldKey, 'values']}
initialValue={existedValues}
rules={[
{
required: true,
message: 'Please specify values',
},
{
validator,
},
]}
>
<Select
className='cvat-attribute-values-input'
mode='tags'
placeholder='Attribute values'
dropdownStyle={{ display: 'none' }}
/>
</Form.Item>
</Tooltip>
);
}
private renderBooleanValueInput(key: number, attr: Attribute | null): JSX.Element {
private renderBooleanValueInput(fieldInstance: any, attr: Attribute | null): JSX.Element {
const { key } = fieldInstance;
const value = attr ? attr.values[0] : 'false';
const { form } = this.props;
return (
<Tooltip title='Specify a default value' mouseLeaveDelay={0}>
<Form.Item>
{form.getFieldDecorator(`values[${key}]`, {
initialValue: value,
})(
<Select className='cvat-attribute-values-input'>
<Select.Option value='false'> False </Select.Option>
<Select.Option value='true'> True </Select.Option>
</Select>,
)}
<Form.Item name={[key, 'values']} fieldKey={[fieldInstance.fieldKey, 'values']} initialValue={value}>
<Select className='cvat-attribute-values-input'>
<Select.Option value='false'>False</Select.Option>
<Select.Option value='true'>True</Select.Option>
</Select>
</Form.Item>
</Tooltip>
);
}
private renderNumberRangeInput(key: number, attr: Attribute | null): JSX.Element {
private renderNumberRangeInput(fieldInstance: any, attr: Attribute | null): JSX.Element {
const { key } = fieldInstance;
const locked = attr ? attr.id >= 0 : false;
const value = attr ? attr.values.join(';') : '';
const { form } = this.props;
const validator = (_: any, strNumbers: string, callback: any): void => {
const numbers = strNumbers.split(';').map((number): number => Number.parseFloat(number));
@ -259,65 +245,64 @@ class LabelForm extends React.PureComponent<Props, {}> {
};
return (
<Form.Item>
{form.getFieldDecorator(`values[${key}]`, {
initialValue: value,
rules: [
{
required: true,
message: 'Please set a range',
},
{
validator,
},
],
})(<Input className='cvat-attribute-values-input' disabled={locked} placeholder='min;max;step' />)}
<Form.Item
name={[key, 'values']}
fieldKey={[fieldInstance.fieldKey, 'values']}
initialValue={value}
rules={[
{
required: true,
message: 'Please set a range',
},
{
validator,
},
]}
>
<Input className='cvat-attribute-values-input' disabled={locked} placeholder='min;max;step' />
</Form.Item>
);
}
private renderDefaultValueInput(key: number, attr: Attribute | null): JSX.Element {
private renderDefaultValueInput(fieldInstance: any, attr: Attribute | null): JSX.Element {
const { key } = fieldInstance;
const value = attr ? attr.values[0] : '';
const { form } = this.props;
return (
<Form.Item>
{form.getFieldDecorator(`values[${key}]`, {
initialValue: value,
})(<Input className='cvat-attribute-values-input' placeholder='Default value' />)}
<Form.Item name={[key, 'values']} fieldKey={[fieldInstance.fieldKey, 'values']} initialValue={value}>
<Input className='cvat-attribute-values-input' placeholder='Default value' />
</Form.Item>
);
}
private renderMutableAttributeInput(key: number, attr: Attribute | null): JSX.Element {
private renderMutableAttributeInput(fieldInstance: any, attr: Attribute | null): JSX.Element {
const { key } = fieldInstance;
const locked = attr ? attr.id >= 0 : false;
const value = attr ? attr.mutable : false;
const { form } = this.props;
return (
<Form.Item>
<Tooltip title='Can this attribute be changed frame to frame?' mouseLeaveDelay={0}>
{form.getFieldDecorator(`mutable[${key}]`, {
initialValue: value,
valuePropName: 'checked',
})(
<Checkbox className='cvat-attribute-mutable-checkbox' disabled={locked}>
{' '}
Mutable
{' '}
</Checkbox>,
)}
</Tooltip>
</Form.Item>
<Tooltip title='Can this attribute be changed frame to frame?' mouseLeaveDelay={0}>
<Form.Item
name={[key, 'mutable']}
fieldKey={[fieldInstance.fieldKey, 'mutable']}
initialValue={value}
valuePropName='checked'
>
<Checkbox className='cvat-attribute-mutable-checkbox' disabled={locked}>
Mutable
</Checkbox>
</Form.Item>
</Tooltip>
);
}
private renderDeleteAttributeButton(key: number, attr: Attribute | null): JSX.Element {
private renderDeleteAttributeButton(fieldInstance: any, attr: Attribute | null): JSX.Element {
const { key } = fieldInstance;
const locked = attr ? attr.id >= 0 : false;
return (
<Form.Item>
<Tooltip title='Delete the attribute' mouseLeaveDelay={0}>
<Tooltip title='Delete the attribute' mouseLeaveDelay={0}>
<Form.Item>
<Button
type='link'
className='cvat-delete-attribute-button'
@ -326,137 +311,134 @@ class LabelForm extends React.PureComponent<Props, {}> {
this.removeAttribute(key);
}}
>
<Icon type='close-circle' />
<CloseCircleOutlined />
</Button>
</Tooltip>
</Form.Item>
</Form.Item>
</Tooltip>
);
}
private renderAttribute = (key: number): JSX.Element => {
const { label, form } = this.props;
const attr = label ? label.attributes.filter((_attr: any): boolean => _attr.id === key)[0] : null;
private renderAttribute = (fieldInstance: any): JSX.Element => {
const { label } = this.props;
const { key } = fieldInstance;
const fieldValue = this.formRef.current?.getFieldValue('attributes')[key];
const attr = label ? label.attributes.filter((_attr: any): boolean => _attr.id === fieldValue.id)[0] : null;
return (
<Form.Item key={key}>
<Row
type='flex'
justify='space-between'
align='middle'
cvat-attribute-id={key}
className='cvat-attribute-inputs-wrapper'
>
{this.renderAttributeNameInput(key, attr)}
{this.renderAttributeTypeInput(key, attr)}
<Col span={6}>
{((): JSX.Element => {
const type = form.getFieldValue(`type[${key}]`);
let element = null;
if ([AttributeType.SELECT, AttributeType.RADIO].includes(type)) {
element = this.renderAttributeValuesInput(key, attr);
} else if (type === AttributeType.CHECKBOX) {
element = this.renderBooleanValueInput(key, attr);
} else if (type === AttributeType.NUMBER) {
element = this.renderNumberRangeInput(key, attr);
} else {
element = this.renderDefaultValueInput(key, attr);
}
<Form.Item noStyle key={key} shouldUpdate>
{() => (
<Row
justify='space-between'
align='middle'
cvat-attribute-id={fieldValue.id}
className='cvat-attribute-inputs-wrapper'
>
<Col span={5}>{this.renderAttributeNameInput(fieldInstance, attr)}</Col>
<Col span={4}>{this.renderAttributeTypeInput(fieldInstance, attr)}</Col>
<Col span={6}>
{((): JSX.Element => {
const currentFieldValue = this.formRef.current?.getFieldValue('attributes')[key];
const type = currentFieldValue.type || AttributeType.SELECT;
let element = null;
if ([AttributeType.SELECT, AttributeType.RADIO].includes(type)) {
element = this.renderAttributeValuesInput(fieldInstance, attr);
} else if (type === AttributeType.CHECKBOX) {
element = this.renderBooleanValueInput(fieldInstance, attr);
} else if (type === AttributeType.NUMBER) {
element = this.renderNumberRangeInput(fieldInstance, attr);
} else {
element = this.renderDefaultValueInput(fieldInstance, attr);
}
return element;
})()}
</Col>
<Col span={5}>{this.renderMutableAttributeInput(key, attr)}</Col>
<Col span={2}>{this.renderDeleteAttributeButton(key, attr)}</Col>
</Row>
return element;
})()}
</Col>
<Col span={5}>{this.renderMutableAttributeInput(fieldInstance, attr)}</Col>
<Col span={2}>{this.renderDeleteAttributeButton(fieldInstance, attr)}</Col>
</Row>
)}
</Form.Item>
);
};
private renderLabelNameInput(): JSX.Element {
const { label, form, labelNames } = this.props;
const { label, labelNames } = this.props;
const value = label ? label.name : '';
const locked = label ? label.id >= 0 : false;
return (
<Col span={10}>
<Form.Item hasFeedback>
{form.getFieldDecorator('labelName', {
initialValue: value,
rules: [
{
required: true,
message: 'Please specify a name',
},
{
pattern: patterns.validateAttributeName.pattern,
message: patterns.validateAttributeName.message,
},
{
validator: async (_rule: any, labelName: string, callback: Function) => {
if (labelNames && labelNames.includes(labelName)) {
callback('Label name must be unique for the task');
}
},
},
],
})(<Input disabled={locked} placeholder='Label name' />)}
</Form.Item>
</Col>
<Form.Item
hasFeedback
name='name'
initialValue={value}
rules={[
{
required: true,
message: 'Please specify a name',
},
{
pattern: patterns.validateAttributeName.pattern,
message: patterns.validateAttributeName.message,
},
{
validator: async (_rule: any, labelName: string, callback: Function) => {
if (labelNames && labelNames.includes(labelName)) {
callback('Label name must be unique for the task');
}
},
},
]}
>
<Input disabled={locked} placeholder='Label name' />
</Form.Item>
);
}
private renderNewAttributeButton(): JSX.Element {
return (
<Col span={6}>
<Form.Item>
<Button type='ghost' onClick={this.addAttribute} className='cvat-new-attribute-button'>
Add an attribute
<Icon type='plus' />
</Button>
</Form.Item>
</Col>
<Form.Item>
<Button type='ghost' onClick={this.addAttribute} className='cvat-new-attribute-button'>
Add an attribute
<PlusOutlined />
</Button>
</Form.Item>
);
}
private renderDoneButton(): JSX.Element {
return (
<Col>
<Tooltip title='Save the label and return' mouseLeaveDelay={0}>
<Button
style={{ width: '150px' }}
type='primary'
htmlType='submit'
onClick={(): void => {
this.continueAfterSubmit = false;
}}
>
Done
</Button>
</Tooltip>
</Col>
<Tooltip title='Save the label and return' mouseLeaveDelay={0}>
<Button
style={{ width: '150px' }}
type='primary'
htmlType='submit'
onClick={(): void => {
this.continueAfterSubmit = false;
}}
>
Done
</Button>
</Tooltip>
);
}
private renderContinueButton(): JSX.Element {
private renderContinueButton(): JSX.Element | null {
const { label } = this.props;
return label ? (
<div />
) : (
<Col offset={1}>
<Tooltip title='Save the label and create one more' mouseLeaveDelay={0}>
<Button
style={{ width: '150px' }}
type='primary'
htmlType='submit'
onClick={(): void => {
this.continueAfterSubmit = true;
}}
>
Continue
</Button>
</Tooltip>
</Col>
if (label) return null;
return (
<Tooltip title='Save the label and create one more' mouseLeaveDelay={0}>
<Button
style={{ width: '150px' }}
type='primary'
htmlType='submit'
onClick={(): void => {
this.continueAfterSubmit = true;
}}
>
Continue
</Button>
</Tooltip>
);
}
@ -464,83 +446,93 @@ class LabelForm extends React.PureComponent<Props, {}> {
const { onSubmit } = this.props;
return (
<Col offset={1}>
<Tooltip title='Do not save the label and return' mouseLeaveDelay={0}>
<Button
style={{ width: '150px' }}
type='danger'
onClick={(): void => {
onSubmit(null);
}}
>
Cancel
</Button>
</Tooltip>
</Col>
<Tooltip title='Do not save the label and return' mouseLeaveDelay={0}>
<Button
type='primary'
danger
style={{ width: '150px' }}
onClick={(): void => {
onSubmit(null);
}}
>
Cancel
</Button>
</Tooltip>
);
}
private renderChangeColorButton(): JSX.Element {
const { label, form } = this.props;
const { label } = this.props;
return (
<Col span={3}>
<Form.Item>
{form.getFieldDecorator('labelColor', {
initialValue: label && label.color ? label.color : undefined,
})(
<Form.Item noStyle shouldUpdate>
{() => (
<Form.Item name='color' initialValue={label ? label?.color : undefined}>
<ColorPicker placement='bottom'>
<Tooltip title='Change color of the label'>
<Button type='default' className='cvat-change-task-label-color-button'>
<Badge
className='cvat-change-task-label-color-badge'
color={form.getFieldValue('labelColor') || consts.NEW_LABEL_COLOR}
color={this.formRef.current?.getFieldValue('color') || consts.NEW_LABEL_COLOR}
text={<Icon component={ColorizeIcon} />}
/>
</Button>
</Tooltip>
</ColorPicker>,
)}
</Form.Item>
</Col>
</ColorPicker>
</Form.Item>
)}
</Form.Item>
);
}
public render(): JSX.Element {
const { label, form } = this.props;
private renderAttributes() {
return (fieldInstances: any[]): JSX.Element[] => fieldInstances.map(this.renderAttribute);
}
form.getFieldDecorator('keys', {
initialValue: label ? label.attributes.map((attr: Attribute): number => attr.id) : [],
});
// eslint-disable-next-line react/sort-comp
public componentDidMount(): void {
const { label } = this.props;
if (this.formRef.current) {
const convertedAttributes = label ?
label.attributes.map(
(attribute: Attribute): Store => ({
...attribute,
type: attribute.input_type.toUpperCase(),
}),
) :
[];
for (const attr of convertedAttributes) {
delete attr.input_type;
}
const keys = form.getFieldValue('keys');
const attributeItems = keys.map(this.renderAttribute);
this.formRef.current.setFieldsValue({ attributes: convertedAttributes });
}
}
public render(): JSX.Element {
return (
<Form onSubmit={this.handleSubmit}>
<Row type='flex' justify='start' align='middle'>
{this.renderLabelNameInput()}
<Col span={1} />
{this.renderChangeColorButton()}
<Col span={1} />
{this.renderNewAttributeButton()}
<Form onFinish={this.handleSubmit} layout='vertical' ref={this.formRef}>
<Row justify='start' align='middle'>
<Col span={10}>{this.renderLabelNameInput()}</Col>
<Col span={3} offset={1}>
{this.renderChangeColorButton()}
</Col>
<Col span={6} offset={1}>
{this.renderNewAttributeButton()}
</Col>
</Row>
{attributeItems.length > 0 && (
<Row type='flex' justify='start' align='middle'>
<Col>
<Text>Attributes</Text>
</Col>
</Row>
)}
{attributeItems.reverse()}
<Row type='flex' justify='start' align='middle'>
{this.renderDoneButton()}
{this.renderContinueButton()}
{this.renderCancelButton()}
<Row justify='start' align='middle'>
<Col span={24}>
<Form.List name='attributes'>{this.renderAttributes()}</Form.List>
</Col>
</Row>
<Row justify='start' align='middle'>
<Col>{this.renderDoneButton()}</Col>
<Col offset={1}>{this.renderContinueButton()}</Col>
<Col offset={1}>{this.renderCancelButton()}</Col>
</Row>
</Form>
);
}
}
export default Form.create<Props>()(LabelForm);

@ -5,12 +5,12 @@
import './styles.scss';
import React from 'react';
import Tabs from 'antd/lib/tabs';
import Icon from 'antd/lib/icon';
import Button from 'antd/lib/button';
import Tooltip from 'antd/lib/tooltip';
import notification from 'antd/lib/notification';
import Text from 'antd/lib/typography/Text';
import copy from 'copy-to-clipboard';
import { CopyOutlined, EditOutlined, BuildOutlined } from '@ant-design/icons';
import RawViewer from './raw-viewer';
import ConstructorViewer from './constructor-viewer';
@ -185,61 +185,61 @@ export default class LabelsEditor extends React.PureComponent<LabelsEditortProps
public render(): JSX.Element {
const { labels } = this.props;
const { savedLabels, unsavedLabels, constructorMode, labelForUpdate } = this.state;
const {
savedLabels, unsavedLabels, constructorMode, labelForUpdate,
} = this.state;
return (
<Tabs
defaultActiveKey='2'
type='card'
tabBarStyle={{ marginBottom: '0px' }}
tabBarExtraContent={
<>
<Tooltip title='Copied to clipboard!' trigger='click' mouseLeaveDelay={0}>
<Button
type='link'
icon='copy'
onClick={(): void => {
copy(
JSON.stringify(
savedLabels.concat(unsavedLabels).map((label): any => ({
...label,
tabBarExtraContent={(
<Tooltip title='Copied to clipboard!' trigger='click' mouseLeaveDelay={0}>
<Button
type='link'
icon={<CopyOutlined />}
onClick={(): void => {
copy(
JSON.stringify(
savedLabels.concat(unsavedLabels).map((label): any => ({
...label,
id: undefined,
attributes: label.attributes.map((attribute): any => ({
...attribute,
id: undefined,
attributes: label.attributes.map((attribute): any => ({
...attribute,
id: undefined,
})),
})),
null,
4,
),
);
}}
>
Copy
</Button>
</Tooltip>
</>
}
})),
null,
4,
),
);
}}
>
Copy
</Button>
</Tooltip>
)}
>
<Tabs.TabPane
tab={
tab={(
<span>
<Icon type='edit' />
<EditOutlined />
<Text>Raw</Text>
</span>
}
)}
key='1'
>
<RawViewer labels={[...savedLabels, ...unsavedLabels]} onSubmit={this.handleRawSubmit} />
</Tabs.TabPane>
<Tabs.TabPane
tab={
tab={(
<span>
<Icon type='build' />
<BuildOutlined />
<Text>Constructor</Text>
</span>
}
)}
key='2'
>
{constructorMode === ConstructorMode.SHOW && (

@ -2,62 +2,66 @@
//
// SPDX-License-Identifier: MIT
import React from 'react';
import React, { RefObject } from 'react';
import { Row, Col } from 'antd/lib/grid';
import Input from 'antd/lib/input';
import Button from 'antd/lib/button';
import Tooltip from 'antd/lib/tooltip';
import Form, { FormComponentProps } from 'antd/lib/form/Form';
import Form, { FormInstance, RuleObject } from 'antd/lib/form';
import { Store } from 'antd/lib/form/interface';
import { Label, Attribute, validateParsedLabel, idGenerator } from './common';
import {
Label, Attribute, validateParsedLabel, idGenerator,
} from './common';
type Props = FormComponentProps & {
labels: Label[];
onSubmit: (labels: Label[]) => void;
};
class RawViewer extends React.PureComponent<Props> {
private validateLabels = (_: any, value: string, callback: any): void => {
try {
const parsed = JSON.parse(value);
if (!Array.isArray(parsed)) {
callback('Field is expected to be a JSON array');
}
const labelNames = parsed.map((label: Label) => label.name);
if (new Set(labelNames).size !== labelNames.length) {
callback('Label names must be unique for the task');
}
function validateLabels(_: RuleObject, value: string): Promise<void> {
try {
const parsed = JSON.parse(value);
if (!Array.isArray(parsed)) {
return Promise.reject(new Error('Field is expected to be a JSON array'));
}
const labelNames = parsed.map((label: Label) => label.name);
if (new Set(labelNames).size !== labelNames.length) {
return Promise.reject(new Error('Label names must be unique for the task'));
}
for (const label of parsed) {
try {
validateParsedLabel(label);
} catch (error) {
callback(error.toString());
}
for (const label of parsed) {
try {
validateParsedLabel(label);
} catch (error) {
return Promise.reject(error);
}
} catch (error) {
callback(error.toString());
}
} catch (error) {
return Promise.reject(error);
}
callback();
};
return Promise.resolve();
}
private handleSubmit = (e: React.FormEvent): void => {
const { form, onSubmit } = this.props;
interface Props {
labels: Label[];
onSubmit: (labels: Label[]) => void;
}
e.preventDefault();
form.validateFields((error, values): void => {
if (!error) {
const parsed = JSON.parse(values.labels);
for (const label of parsed) {
label.id = label.id || idGenerator();
for (const attr of label.attributes) {
attr.id = attr.id || idGenerator();
}
}
onSubmit(parsed);
export default class RawViewer extends React.PureComponent<Props> {
private formRef: RefObject<FormInstance>;
public constructor(props: Props) {
super(props);
this.formRef = React.createRef<FormInstance>();
}
private handleSubmit = (values: Store): void => {
const { onSubmit } = this.props;
const parsed = JSON.parse(values.labels);
for (const label of parsed) {
label.id = label.id || idGenerator();
for (const attr of label.attributes) {
attr.id = attr.id || idGenerator();
}
});
}
onSubmit(parsed);
};
public render(): JSX.Element {
@ -76,21 +80,12 @@ class RawViewer extends React.PureComponent<Props> {
);
const textLabels = JSON.stringify(convertedLabels, null, 2);
const { form } = this.props;
return (
<Form onSubmit={this.handleSubmit}>
<Form.Item>
{form.getFieldDecorator('labels', {
initialValue: textLabels,
rules: [
{
validator: this.validateLabels,
},
],
})(<Input.TextArea rows={5} className='cvat-raw-labels-viewer' />)}
<Form layout='vertical' onFinish={this.handleSubmit} ref={this.formRef}>
<Form.Item name='labels' initialValue={textLabels} rules={[{ validator: validateLabels }]}>
<Input.TextArea rows={5} className='cvat-raw-labels-viewer' />
</Form.Item>
<Row type='flex' justify='start' align='middle'>
<Row justify='start' align='middle'>
<Col>
<Tooltip title='Save labels and return' mouseLeaveDelay={0}>
<Button style={{ width: '150px' }} type='primary' htmlType='submit'>
@ -101,10 +96,13 @@ class RawViewer extends React.PureComponent<Props> {
<Col offset={1}>
<Tooltip title='Do not save the label and return' mouseLeaveDelay={0}>
<Button
type='primary'
danger
style={{ width: '150px' }}
type='danger'
onClick={(): void => {
form.resetFields();
if (this.formRef.current) {
this.formRef.current.resetFields();
}
}}
>
Reset
@ -116,5 +114,3 @@ class RawViewer extends React.PureComponent<Props> {
);
}
}
export default Form.create<Props>()(RawViewer);

@ -51,7 +51,7 @@ textarea.ant-input.cvat-raw-labels-viewer {
margin-left: 5px;
color: white;
> i:hover {
> span[role='img']:hover {
filter: invert(1);
}
}
@ -84,7 +84,7 @@ textarea.ant-input.cvat-raw-labels-viewer {
display: contents;
}
.cvat-delete-attribute-button:hover > i {
.cvat-delete-attribute-button:hover > span[role='img'] {
color: $danger-icon-color;
}

@ -3,103 +3,73 @@
// SPDX-License-Identifier: MIT
import React from 'react';
import Form, { FormComponentProps } from 'antd/lib/form/Form';
import Form from 'antd/lib/form';
import Button from 'antd/lib/button';
import Icon from 'antd/lib/icon';
import Input from 'antd/lib/input';
import { UserOutlined, LockOutlined } from '@ant-design/icons';
export interface LoginData {
username: string;
password: string;
}
type LoginFormProps = {
interface Props {
fetching: boolean;
onSubmit(loginData: LoginData): void;
} & FormComponentProps;
class LoginFormComponent extends React.PureComponent<LoginFormProps> {
private handleSubmit = (e: React.FormEvent): void => {
e.preventDefault();
const { form, onSubmit } = this.props;
form.validateFields((error, values): void => {
if (!error) {
onSubmit(values);
}
});
};
private renderUsernameField(): JSX.Element {
const { form } = this.props;
const { getFieldDecorator } = form;
}
return (
<Form.Item hasFeedback>
{getFieldDecorator('username', {
rules: [
{
required: true,
message: 'Please specify a username',
},
],
})(
<Input
autoComplete='username'
prefix={<Icon type='user' style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder='Username'
/>,
)}
function LoginFormComponent(props: Props): JSX.Element {
const { fetching, onSubmit } = props;
return (
<Form onFinish={onSubmit} className='login-form'>
<Form.Item
hasFeedback
name='username'
rules={[
{
required: true,
message: 'Please specify a username',
},
]}
>
<Input
autoComplete='username'
prefix={<UserOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Username'
/>
</Form.Item>
);
}
private renderPasswordField(): JSX.Element {
const { form } = this.props;
const { getFieldDecorator } = form;
return (
<Form.Item hasFeedback>
{getFieldDecorator('password', {
rules: [
{
required: true,
message: 'Please specify a password',
},
],
})(
<Input
autoComplete='current-password'
prefix={<Icon type='lock' style={{ color: 'rgba(0,0,0,.25)' }} />}
placeholder='Password'
type='password'
/>,
)}
<Form.Item
hasFeedback
name='password'
rules={[
{
required: true,
message: 'Please specify a password',
},
]}
>
<Input
autoComplete='current-password'
prefix={<LockOutlined style={{ color: 'rgba(0, 0, 0, 0.25)' }} />}
placeholder='Password'
type='password'
/>
</Form.Item>
);
}
public render(): JSX.Element {
const { fetching } = this.props;
return (
<Form onSubmit={this.handleSubmit} className='login-form'>
{this.renderUsernameField()}
{this.renderPasswordField()}
<Form.Item>
<Button
type='primary'
loading={fetching}
disabled={fetching}
htmlType='submit'
className='login-form-button'
>
Sign in
</Button>
</Form.Item>
</Form>
);
}
<Form.Item>
<Button
type='primary'
loading={fetching}
disabled={fetching}
htmlType='submit'
className='login-form-button'
>
Sign in
</Button>
</Form.Item>
</Form>
);
}
export default Form.create<LoginFormProps>()(LoginFormComponent);
export default React.memo(LoginFormComponent);

@ -31,7 +31,7 @@ function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps
return (
<>
<Row type='flex' justify='center' align='middle'>
<Row justify='center' align='middle'>
<Col {...sizes}>
<Title level={2}> Login </Title>
<LoginForm
@ -40,7 +40,7 @@ function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps
onLogin(loginData.username, loginData.password);
}}
/>
<Row type='flex' justify='start' align='top'>
<Row justify='start' align='top'>
<Col>
<Text strong>
New to CVAT? Create
@ -49,7 +49,7 @@ function LoginPageComponent(props: LoginPageComponentProps & RouteComponentProps
</Col>
</Row>
{renderResetPassword && (
<Row type='flex' justify='start' align='top'>
<Row justify='start' align='top'>
<Col>
<Text strong>
<Link to='/auth/password/reset'>Forgot your password?</Link>

@ -5,8 +5,8 @@
import './styles.scss';
import React, { useState } from 'react';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Select, { OptionProps } from 'antd/lib/select';
import { CloseCircleOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import Select from 'antd/lib/select';
import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox';
import Tooltip from 'antd/lib/tooltip';
import Tag from 'antd/lib/tag';
@ -14,9 +14,12 @@ import Text from 'antd/lib/typography/Text';
import InputNumber from 'antd/lib/input-number';
import Button from 'antd/lib/button';
import notification from 'antd/lib/notification';
// eslint-disable-next-line import/no-extraneous-dependencies
import { OptionData, OptionGroupData } from 'rc-select/lib/interface';
import { Model, StringObject } from 'reducers/interfaces';
import { clamp } from 'utils/math';
import consts from 'consts';
interface Props {
@ -27,7 +30,9 @@ interface Props {
}
function DetectorRunner(props: Props): JSX.Element {
const { task, models, withCleanup, runInference } = props;
const {
task, models, withCleanup, runInference,
} = props;
const [modelID, setModelID] = useState<string | null>(null);
const [mapping, setMapping] = useState<StringObject>({});
@ -93,10 +98,12 @@ function DetectorRunner(props: Props): JSX.Element {
onChange={onChange}
style={{ width: '100%' }}
showSearch
filterOption={(input: string, option: React.ReactElement<OptionProps>) => {
const { children } = option.props;
if (typeof children === 'string') {
return children.toLowerCase().includes(input.toLowerCase());
filterOption={(input: string, option?: OptionData | OptionGroupData) => {
if (option) {
const { children } = option.props;
if (typeof children === 'string') {
return children.toLowerCase().includes(input.toLowerCase());
}
}
return false;
@ -104,7 +111,9 @@ function DetectorRunner(props: Props): JSX.Element {
>
{labels.map(
(label: string): JSX.Element => (
<Select.Option key={label}>{label}</Select.Option>
<Select.Option value={label} key={label}>
{label}
</Select.Option>
),
)}
</Select>
@ -114,7 +123,7 @@ function DetectorRunner(props: Props): JSX.Element {
return (
<div className='cvat-run-model-content'>
<Row type='flex' align='middle'>
<Row align='middle'>
<Col span={4}>Model:</Col>
<Col span={20}>
<Select
@ -136,7 +145,9 @@ function DetectorRunner(props: Props): JSX.Element {
>
{models.map(
(_model: Model): JSX.Element => (
<Select.Option key={_model.id}>{_model.name}</Select.Option>
<Select.Option value={_model.id} key={_model.id}>
{_model.name}
</Select.Option>
),
)}
</Select>
@ -148,7 +159,7 @@ function DetectorRunner(props: Props): JSX.Element {
const label = task.labels.filter((_label: any): boolean => _label.name === mapping[modelLabel])[0];
const color = label ? label.color : consts.NEW_LABEL_COLOR;
return (
<Row key={modelLabel} type='flex' justify='start' align='middle'>
<Row key={modelLabel} justify='start' align='middle'>
<Col span={10}>
<Tag color={color}>{modelLabel}</Tag>
</Col>
@ -157,9 +168,8 @@ function DetectorRunner(props: Props): JSX.Element {
</Col>
<Col offset={1}>
<Tooltip title='Remove the mapped values' mouseLeaveDelay={0}>
<Icon
<CloseCircleOutlined
className='cvat-danger-circle-icon'
type='close-circle'
onClick={(): void => {
const newmapping = { ...mapping };
delete newmapping[modelLabel];
@ -173,23 +183,21 @@ function DetectorRunner(props: Props): JSX.Element {
})}
{isDetector && !!taskLabels.length && !!modelLabels.length && (
<>
<Row type='flex' justify='start' align='middle'>
<Row justify='start' align='middle'>
<Col span={10}>
{renderSelector(match.model || '', 'Model labels', modelLabels, (modelLabel: string) =>
updateMatch(modelLabel, null),
)}
updateMatch(modelLabel, null))}
</Col>
<Col span={10} offset={1}>
{renderSelector(match.task || '', 'Task labels', taskLabels, (taskLabel: string) =>
updateMatch(null, taskLabel),
)}
updateMatch(null, taskLabel))}
</Col>
<Col span={1} offset={1}>
<Tooltip
title='Specify a label mapping between model labels and task labels'
mouseLeaveDelay={0}
>
<Icon className='cvat-info-circle-icon' type='question-circle' />
<QuestionCircleOutlined className='cvat-info-circle-icon' />
</Tooltip>
</Col>
</Row>
@ -207,7 +215,7 @@ function DetectorRunner(props: Props): JSX.Element {
)}
{isReId && (
<div>
<Row type='flex' align='middle' justify='start'>
<Row align='middle' justify='start'>
<Col>
<Text>Threshold</Text>
</Col>
@ -218,16 +226,16 @@ function DetectorRunner(props: Props): JSX.Element {
step={0.01}
max={1}
value={threshold}
onChange={(value: number | undefined) => {
if (typeof value === 'number') {
setThreshold(value);
onChange={(value: number | undefined | string) => {
if (typeof value !== 'undefined') {
setThreshold(clamp(+value, 0.01, 1));
}
}}
/>
</Tooltip>
</Col>
</Row>
<Row type='flex' align='middle' justify='start'>
<Row align='middle' justify='start'>
<Col>
<Text>Maximum distance</Text>
</Col>
@ -237,9 +245,9 @@ function DetectorRunner(props: Props): JSX.Element {
placeholder='Threshold'
min={1}
value={distance}
onChange={(value: number | undefined) => {
if (typeof value === 'number') {
setDistance(value);
onChange={(value: number | undefined | string) => {
if (typeof value !== 'undefined') {
setDistance(+value);
}
}}
/>
@ -248,7 +256,7 @@ function DetectorRunner(props: Props): JSX.Element {
</Row>
</div>
)}
<Row type='flex' align='middle' justify='end'>
<Row align='middle' justify='end'>
<Col>
<Button
disabled={!buttonEnabled}
@ -257,12 +265,12 @@ function DetectorRunner(props: Props): JSX.Element {
runInference(
task,
model,
model.type === 'detector'
? { mapping, cleanup }
: {
threshold,
max_distance: distance,
},
model.type === 'detector' ?
{ mapping, cleanup } :
{
threshold,
max_distance: distance,
},
);
}}
>

@ -48,7 +48,9 @@ function mapDispatchToProps(dispatch: ThunkDispatch): DispatchToProps {
}
function ModelRunnerDialog(props: StateToProps & DispatchToProps): JSX.Element {
const { reid, detectors, task, visible, runInference, closeDialog } = props;
const {
reid, detectors, task, visible, runInference, closeDialog,
} = props;
const models = [...reid, ...detectors];

@ -17,7 +17,7 @@ export default function DeployedModelItem(props: Props): JSX.Element {
const { model } = props;
return (
<Row className='cvat-models-list-item' type='flex'>
<Row className='cvat-models-list-item'>
<Col span={3}>
<Tag color='purple'>{model.framework}</Tag>
</Col>
@ -34,7 +34,9 @@ export default function DeployedModelItem(props: Props): JSX.Element {
<Select showSearch placeholder='Supported labels' style={{ width: '90%' }} value='Supported labels'>
{model.labels.map(
(label): JSX.Element => (
<Select.Option key={label}>{label}</Select.Option>
<Select.Option value={label} key={label}>
{label}
</Select.Option>
),
)}
</Select>

@ -20,9 +20,9 @@ export default function DeployedModelsListComponent(props: Props): JSX.Element {
return (
<>
<Row type='flex' justify='center' align='middle'>
<Row justify='center' align='middle'>
<Col md={22} lg={18} xl={16} xxl={14} className='cvat-models-list'>
<Row type='flex' align='middle' style={{ padding: '10px' }}>
<Row align='middle' style={{ padding: '10px' }}>
<Col span={3}>
<Text strong>Framework</Text>
</Col>

@ -5,7 +5,7 @@
import React from 'react';
import Text from 'antd/lib/typography/Text';
import { Row, Col } from 'antd/lib/grid';
import Icon from 'antd/lib/icon';
import Icon from '@ant-design/icons';
import consts from 'consts';
import { EmptyTasksIcon as EmptyModelsIcon } from 'icons';
@ -13,22 +13,22 @@ import { EmptyTasksIcon as EmptyModelsIcon } from 'icons';
export default function EmptyListComponent(): JSX.Element {
return (
<div className='cvat-empty-models-list'>
<Row type='flex' justify='center' align='middle'>
<Row justify='center' align='middle'>
<Col>
<Icon className='cvat-empty-models-icon' component={EmptyModelsIcon} />
</Col>
</Row>
<Row type='flex' justify='center' align='middle'>
<Row justify='center' align='middle'>
<Col>
<Text strong>No models deployed yet...</Text>
</Col>
</Row>
<Row type='flex' justify='center' align='middle'>
<Row justify='center' align='middle'>
<Col>
<Text type='secondary'>To annotate your tasks automatically</Text>
</Col>
</Row>
<Row type='flex' justify='center' align='middle'>
<Row justify='center' align='middle'>
<Col>
<Text type='secondary'>deploy a model with </Text>
<a href={`${consts.NUCLIO_GUIDE}`}>nuclio</a>

@ -8,7 +8,7 @@ import Text from 'antd/lib/typography/Text';
export default function TopBarComponent(): JSX.Element {
return (
<Row type='flex' justify='center' align='middle'>
<Row justify='center' align='middle'>
<Col md={22} lg={20} xl={16} xxl={14}>
<Text className='cvat-title'>Models</Text>
</Col>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save