未验证 提交 fbcf7586 编写于 作者: A Aleksey Alekseev 提交者: GitHub

86 improve uiux on create task page (#7)

* Update label form

Remove Done button
Add closing by Esc
Add continuing by Enter

* disable autocomplete on label form

* Update submit behavior on create task

Change one submit button to two: "submit and open" and "submit and continue"

* Update submit behavior on create project

Change one submit button to two: "submit and open" and "submit and continue"

* fix eslint error

* change tests

* Add changes in changelog

* update version cvat-ui

* move of empty name handler logic when create label

* change handler errors on create project

* change text of buttons

* revert change yarn.lock

* Fixed eslint pipeline

* fix eslint error on test

* fix several tests

* fix several tests

* fix several tests

* fix several tests
Co-authored-by: NBoris <sekachev.bs@gmail.com>
Co-authored-by: Nkirill-sizov <sizow.k.d@gmail.com>
Co-authored-by: NNikita Manovich <nikita@cvat.ai>
上级 b7e1ebeb
......@@ -26,7 +26,7 @@ jobs:
done
if [[ ! -z $CHANGED_FILES ]]; then
yarn install --frozen-lockfile
yarn install --frozen-lockfile && cd tests && yarn install --frozen-lockfile && cd ..
yarn add eslint-detailed-reporter -D -W
mkdir -p eslint_report
......
......@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Bumped nuclio version to 1.8.14
- Simplified running REST API tests. Extended CI-nightly workflow
- REST API tests are partially moved to Python SDK (`users`, `projects`, `tasks`)
- cvat-ui: Improve UI/UX on label, create task and create project forms (<https://github.com/cvat-ai/cvat/pull/7>)
- Removed link to OpenVINO documentation (<https://github.com/cvat-ai/cvat/pull/35>)
- Clarified meaning of chunking for videos
......
{
"name": "cvat-ui",
"version": "1.39.1",
"version": "1.40.0",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
......
......@@ -89,7 +89,7 @@ export function getProjectTasksAsync(tasksQuery: Partial<TasksQuery> = {}): Thun
getState().projects.gettingQuery,
tasksQuery,
));
const query: Partial<TasksQuery> = {
const query: TasksQuery = {
...state.projects.tasksGettingQuery,
...tasksQuery,
};
......@@ -149,8 +149,10 @@ export function createProjectAsync(data: any): ThunkAction {
try {
const savedProject = await projectInstance.save();
dispatch(projectActions.createProjectSuccess(savedProject.id));
return savedProject;
} catch (error) {
dispatch(projectActions.createProjectFailed(error));
throw error;
}
};
}
......
......@@ -345,7 +345,7 @@ function createTaskUpdateStatus(status: string): AnyAction {
}
export function createTaskAsync(data: any): ThunkAction<Promise<void>, {}, {}, AnyAction> {
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
return async (dispatch: ActionCreator<Dispatch>): Promise<any> => {
const description: any = {
name: data.basic.name,
labels: data.labels,
......@@ -417,8 +417,10 @@ export function createTaskAsync(data: any): ThunkAction<Promise<void>, {}, {}, A
dispatch(createTaskUpdateStatus(status + (progress !== null ? ` ${Math.floor(progress * 100)}%` : '')));
});
dispatch(createTaskSuccess(savedTask.id));
return savedTask;
} catch (error) {
dispatch(createTaskFailed(error));
throw error;
}
};
}
......
......@@ -3,9 +3,9 @@
// SPDX-License-Identifier: MIT
import React, {
RefObject, useContext, useEffect, useRef, useState,
RefObject, useContext, useRef, useState, useEffect,
} from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router';
import Switch from 'antd/lib/switch';
import Select from 'antd/lib/select';
......@@ -17,14 +17,16 @@ import Input from 'antd/lib/input';
import notification from 'antd/lib/notification';
import patterns from 'utils/validation-patterns';
import { CombinedState } from 'reducers/interfaces';
import LabelsEditor from 'components/labels-editor/labels-editor';
import { createProjectAsync } from 'actions/projects-actions';
import CreateProjectContext from './create-project.context';
const { Option } = Select;
function NameConfigurationForm({ formRef }: { formRef: RefObject<FormInstance> }): JSX.Element {
function NameConfigurationForm(
{ formRef, inputRef }:
{ formRef: RefObject<FormInstance>, inputRef: RefObject<Input> },
):JSX.Element {
return (
<Form layout='vertical' ref={formRef}>
<Form.Item
......@@ -38,7 +40,7 @@ function NameConfigurationForm({ formRef }: { formRef: RefObject<FormInstance> }
},
]}
>
<Input />
<Input ref={inputRef} />
</Form.Item>
</Form>
);
......@@ -50,7 +52,7 @@ function AdaptiveAutoAnnotationForm({ formRef }: { formRef: RefObject<FormInstan
return (
<Form layout='vertical' ref={formRef}>
<Form.Item name='project_class' hasFeedback label='Class'>
<Select value={projectClass.value} onChange={(v) => projectClass.set(v)}>
<Select value={projectClass.value} onChange={(v) => projectClass.set?.(v)}>
<Option value=''>--Not Selected--</Option>
<Option value='OD'>Detection</Option>
</Select>
......@@ -60,7 +62,7 @@ function AdaptiveAutoAnnotationForm({ formRef }: { formRef: RefObject<FormInstan
<Switch
disabled={!projectClassesForTraining.includes(projectClass.value)}
checked={trainingEnabled.value}
onClick={() => trainingEnabled.set(!trainingEnabled.value)}
onClick={() => trainingEnabled.set?.(!trainingEnabled.value)}
/>
</Form.Item>
......@@ -125,64 +127,79 @@ function AdvancedConfigurationForm({ formRef }: { formRef: RefObject<FormInstanc
export default function CreateProjectContent(): JSX.Element {
const [projectLabels, setProjectLabels] = useState<any[]>([]);
const shouldShowNotification = useRef(false);
const nameFormRef = useRef<FormInstance>(null);
const nameInputRef = useRef<Input>(null);
const adaptiveAutoAnnotationFormRef = useRef<FormInstance>(null);
const advancedFormRef = useRef<FormInstance>(null);
const dispatch = useDispatch();
const history = useHistory();
const newProjectId = useSelector((state: CombinedState) => state.projects.activities.creates.id);
const { isTrainingActive } = useContext(CreateProjectContext);
useEffect(() => {
if (Number.isInteger(newProjectId) && shouldShowNotification.current) {
const btn = <Button onClick={() => history.push(`/projects/${newProjectId}`)}>Open project</Button>;
const resetForm = (): void => {
if (nameFormRef.current) nameFormRef.current.resetFields();
if (advancedFormRef.current) advancedFormRef.current.resetFields();
setProjectLabels([]);
};
const focusForm = (): void => {
nameInputRef.current?.focus();
};
const sumbit = async (): Promise<any> => {
try {
let projectData: Record<string, any> = {};
if (nameFormRef.current && advancedFormRef.current) {
const basicValues = await nameFormRef.current.validateFields();
const advancedValues = await advancedFormRef.current.validateFields();
const adaptiveAutoAnnotationValues = await adaptiveAutoAnnotationFormRef.current?.validateFields();
projectData = {
...projectData,
...advancedValues,
name: basicValues.name,
};
if (adaptiveAutoAnnotationValues) {
projectData.training_project = { ...adaptiveAutoAnnotationValues };
}
}
projectData.labels = projectLabels;
const createdProject = await dispatch(createProjectAsync(projectData));
return createdProject;
} catch {
return false;
}
};
// Clear new project forms
if (nameFormRef.current) nameFormRef.current.resetFields();
if (advancedFormRef.current) advancedFormRef.current.resetFields();
setProjectLabels([]);
const onSubmitAndOpen = async (): Promise<void> => {
const createdProject = await sumbit();
if (createdProject) {
history.push(`/projects/${createdProject.id}`);
}
};
const onSubmitAndContinue = async (): Promise<void> => {
const res = await sumbit();
if (res) {
resetForm();
notification.info({
message: 'The project has been created',
btn,
className: 'cvat-notification-create-project-success',
});
focusForm();
}
shouldShowNotification.current = true;
}, [newProjectId]);
const onSumbit = async (): Promise<void> => {
let projectData: Record<string, any> = {};
if (nameFormRef.current && advancedFormRef.current) {
const basicValues = await nameFormRef.current.validateFields();
const advancedValues = await advancedFormRef.current.validateFields();
const adaptiveAutoAnnotationValues = await adaptiveAutoAnnotationFormRef.current?.validateFields();
projectData = {
...projectData,
...advancedValues,
name: basicValues.name,
};
if (adaptiveAutoAnnotationValues) {
projectData.training_project = { ...adaptiveAutoAnnotationValues };
}
}
projectData.labels = projectLabels;
if (!projectData.name) return;
dispatch(createProjectAsync(projectData));
};
useEffect(() => {
focusForm();
}, []);
return (
<Row justify='start' align='middle' className='cvat-create-project-content'>
<Col span={24}>
<NameConfigurationForm formRef={nameFormRef} />
<NameConfigurationForm formRef={nameFormRef} inputRef={nameInputRef} />
</Col>
{isTrainingActive.value && (
<Col span={24}>
......@@ -202,9 +219,18 @@ export default function CreateProjectContent(): JSX.Element {
<AdvancedConfigurationForm formRef={advancedFormRef} />
</Col>
<Col span={24}>
<Button type='primary' onClick={onSumbit}>
Submit
</Button>
<Row justify='end' gutter={5}>
<Col>
<Button type='primary' onClick={onSubmitAndOpen}>
Submit & Open
</Button>
</Col>
<Col>
<Button type='primary' onClick={onSubmitAndContinue}>
Submit & Continue
</Button>
</Col>
</Row>
</Col>
</Row>
);
......
......@@ -17,10 +17,12 @@ interface Props {
export default class BasicConfigurationForm extends React.PureComponent<Props> {
private formRef: RefObject<FormInstance>;
private inputRef: RefObject<Input>;
public constructor(props: Props) {
super(props);
this.formRef = React.createRef<FormInstance>();
this.inputRef = React.createRef<Input>();
}
public submit(): Promise<void> {
......@@ -41,6 +43,12 @@ export default class BasicConfigurationForm extends React.PureComponent<Props> {
}
}
public focus(): void {
if (this.inputRef.current) {
this.inputRef.current.focus();
}
}
public render(): JSX.Element {
return (
<Form ref={this.formRef} layout='vertical'>
......@@ -55,7 +63,7 @@ export default class BasicConfigurationForm extends React.PureComponent<Props> {
},
]}
>
<Input />
<Input ref={this.inputRef} />
</Form.Item>
</Form>
);
......
......@@ -85,31 +85,21 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
if (projectId) {
this.handleProjectIdChange(projectId);
}
}
public componentDidUpdate(prevProps: Props): void {
const { status, history, taskId } = this.props;
if (status === 'CREATED' && prevProps.status !== 'CREATED') {
const btn = <Button onClick={() => history.push(`/tasks/${taskId}`)}>Open task</Button>;
notification.info({
message: 'The task has been created',
btn,
className: 'cvat-notification-create-task-success',
});
this.focusToForm();
}
this.basicConfigurationComponent.current?.resetFields();
this.advancedConfigurationComponent.current?.resetFields();
private resetState = (): void => {
this.basicConfigurationComponent.current?.resetFields();
this.advancedConfigurationComponent.current?.resetFields();
this.fileManagerContainer.reset();
this.fileManagerContainer.reset();
this.setState((state) => ({
...defaultState,
projectId: state.projectId,
}));
}
}
this.setState((state) => ({
...defaultState,
projectId: state.projectId,
}));
};
private validateLabelsOrProject = (): boolean => {
const { projectId, labels } = this.state;
......@@ -170,13 +160,42 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
});
};
private handleSubmitClick = (): void => {
private focusToForm = (): void => {
this.basicConfigurationComponent.current?.focus();
};
private handleSubmitAndOpen = (): void => {
const { history } = this.props;
this.handleSubmit()
.then((createdTask) => {
const { id } = createdTask;
history.push(`/tasks/${id}`);
})
.catch(() => {});
};
private handleSubmitAndContinue = (): void => {
this.handleSubmit()
.then(() => {
notification.info({
message: 'The task has been created',
className: 'cvat-notification-create-task-success',
});
})
.then(this.resetState)
.then(this.focusToForm)
.catch(() => {});
};
private handleSubmit = (): Promise<any> => new Promise((resolve, reject) => {
if (!this.validateLabelsOrProject()) {
notification.error({
message: 'Could not create a task',
description: 'A task must contain at least one label or belong to some project',
className: 'cvat-notification-create-task-fail',
});
reject();
return;
}
......@@ -186,35 +205,43 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
description: 'A task must contain at least one file',
className: 'cvat-notification-create-task-fail',
});
reject();
return;
}
if (this.basicConfigurationComponent.current) {
this.basicConfigurationComponent.current
.submit()
.then(() => {
if (this.advancedConfigurationComponent.current) {
return this.advancedConfigurationComponent.current.submit();
}
return Promise.resolve();
})
.then((): void => {
const { onCreate } = this.props;
onCreate(this.state);
})
.catch((error: Error | ValidateErrorEntity): void => {
notification.error({
message: 'Could not create a task',
description: (error as ValidateErrorEntity).errorFields ?
(error as ValidateErrorEntity).errorFields
.map((field) => `${field.name} : ${field.errors.join(';')}`)
.map((text: string): JSX.Element => <div>{text}</div>) :
error.toString(),
className: 'cvat-notification-create-task-fail',
});
});
if (!this.basicConfigurationComponent.current) {
reject();
return;
}
};
this.basicConfigurationComponent.current
.submit()
.then(() => {
if (this.advancedConfigurationComponent.current) {
return this.advancedConfigurationComponent.current.submit();
}
return Promise.resolve();
})
.then((): void => {
const { onCreate } = this.props;
return onCreate(this.state);
})
.then((cratedTask) => {
resolve(cratedTask);
})
.catch((error: Error | ValidateErrorEntity): void => {
notification.error({
message: 'Could not create a task',
description: (error as ValidateErrorEntity).errorFields ?
(error as ValidateErrorEntity).errorFields
.map((field) => `${field.name} : ${field.errors.join(';')}`)
.map((text: string): JSX.Element => <div>{text}</div>) :
error.toString(),
className: 'cvat-notification-create-task-fail',
});
reject(error);
});
});
private renderBasicBlock(): JSX.Element {
return (
......@@ -332,6 +359,23 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
);
}
private renderActions(): JSX.Element {
return (
<Row justify='end' gutter={5}>
<Col>
<Button type='primary' onClick={this.handleSubmitAndOpen}>
Submit & Open
</Button>
</Col>
<Col>
<Button type='primary' onClick={this.handleSubmitAndContinue}>
Submit & Continue
</Button>
</Col>
</Row>
);
}
public render(): JSX.Element {
const { status } = this.props;
const loading = !!status && status !== 'CREATED' && status !== 'FAILED';
......@@ -349,11 +393,8 @@ class CreateTaskContent extends React.PureComponent<Props & RouteComponentProps,
{this.renderFilesBlock()}
{this.renderAdvancedBlock()}
<Col span={18}>{loading ? <Alert message={status} /> : null}</Col>
<Col span={6} className='cvat-create-task-submit-section'>
<Button loading={loading} disabled={loading} type='primary' onClick={this.handleSubmitClick}>
Submit
</Button>
<Col span={24} className='cvat-create-task-content-footer'>
{loading ? <Alert message={status} /> : this.renderActions()}
</Col>
</Row>
);
......
......@@ -30,11 +30,6 @@
margin-top: 10px;
}
.cvat-create-task-submit-section > button {
float: right;
width: 120px;
}
.cvat-project-search-field {
width: 100%;
}
......
......@@ -9,7 +9,8 @@ import { Label } from './common';
interface Props {
labelNames: string[];
onCreate: (label: Label | null) => void;
onCreate: (label: Label) => void;
onCancel: () => void;
}
function compareProps(prevProps: Props, nextProps: Props): boolean {
......@@ -30,10 +31,10 @@ function compareProps(prevProps: Props, nextProps: Props): boolean {
}
function ConstructorCreator(props: Props): JSX.Element {
const { onCreate, labelNames } = props;
const { onCreate, onCancel, labelNames } = props;
return (
<div className='cvat-label-constructor-creator'>
<LabelForm label={null} onSubmit={onCreate} labelNames={labelNames} />
<LabelForm label={null} onSubmit={onCreate} labelNames={labelNames} onCancel={onCancel} />
</div>
);
}
......
......@@ -9,15 +9,16 @@ import { Label } from './common';
interface Props {
label: Label;
onUpdate: (label: Label | null) => void;
onUpdate: (label: Label) => void;
onCancel: () => void;
}
export default function ConstructorUpdater(props: Props): JSX.Element {
const { label, onUpdate } = props;
const { label, onUpdate, onCancel } = props;
return (
<div className='cvat-label-constructor-updater'>
<LabelForm label={label} onSubmit={onUpdate} />
<LabelForm label={label} onSubmit={onUpdate} onCancel={onCancel} />
</div>
);
}
......@@ -17,18 +17,20 @@ interface ConstructorViewerProps {
}
export default function ConstructorViewer(props: ConstructorViewerProps): JSX.Element {
const { onCreate } = props;
const {
onCreate, labels, onUpdate, onDelete,
} = props;
const list = [
<Button key='create' type='ghost' onClick={onCreate} className='cvat-constructor-viewer-new-item'>
Add label
<PlusCircleOutlined />
</Button>,
];
for (const label of props.labels) {
for (const label of labels) {
list.push(
<ConstructorViewerItem
onUpdate={props.onUpdate}
onDelete={props.onDelete}
onUpdate={onUpdate}
onDelete={onDelete}
label={label}
key={label.id}
color={label.color}
......
......@@ -33,21 +33,33 @@ export enum AttributeType {
interface Props {
label: Label | null;
labelNames?: string[];
onSubmit: (label: Label | null) => void;
onSubmit: (label: Label) => void;
onCancel: () => void;
}
export default class LabelForm extends React.Component<Props> {
private continueAfterSubmit: boolean;
private formRef: RefObject<FormInstance>;
private inputNameRef: RefObject<Input>;
constructor(props: Props) {
super(props);
this.continueAfterSubmit = false;
this.formRef = React.createRef<FormInstance>();
this.inputNameRef = React.createRef<Input>();
}
private focus = (): void => {
this.inputNameRef.current?.focus({
cursor: 'end',
});
};
private handleSubmit = (values: Store): void => {
const { label, onSubmit } = this.props;
const { label, onSubmit, onCancel } = this.props;
if (!values.name) {
onCancel();
return;
}
onSubmit({
name: values.name,
......@@ -76,10 +88,10 @@ export default class LabelForm extends React.Component<Props> {
// resetFields does not remove existed attributes
this.formRef.current.setFieldsValue({ attributes: undefined });
this.formRef.current.resetFields();
}
if (!this.continueAfterSubmit) {
onSubmit(null);
if (!label) {
this.focus();
}
}
};
......@@ -380,7 +392,7 @@ export default class LabelForm extends React.Component<Props> {
};
private renderLabelNameInput(): JSX.Element {
const { label, labelNames } = this.props;
const { label, labelNames, onCancel } = this.props;
const value = label ? label.name : '';
return (
......@@ -390,7 +402,7 @@ export default class LabelForm extends React.Component<Props> {
initialValue={value}
rules={[
{
required: true,
required: !!label,
message: 'Please specify a name',
},
{
......@@ -407,7 +419,16 @@ export default class LabelForm extends React.Component<Props> {
},
]}
>
<Input placeholder='Label name' />
<Input
ref={this.inputNameRef}
placeholder='Label name'
onKeyUp={(event): void => {
if (event.key === 'Escape' || event.key === 'Esc' || event.keyCode === 27) {
onCancel();
}
}}
autoComplete='off'
/>
</Form.Item>
);
}
......@@ -423,45 +444,26 @@ export default class LabelForm extends React.Component<Props> {
);
}
private renderDoneButton(): JSX.Element {
return (
<CVATTooltip title='Save the label and return'>
<Button
style={{ width: '150px' }}
type='primary'
htmlType='submit'
onClick={(): void => {
this.continueAfterSubmit = false;
}}
>
Done
</Button>
</CVATTooltip>
);
}
private renderContinueButton(): JSX.Element | null {
private renderSaveButton(): JSX.Element {
const { label } = this.props;
const tooltipTitle = label ? 'Save the label and return' : 'Save the label and create one more';
const buttonText = label ? 'Done' : 'Continue';
if (label) return null;
return (
<CVATTooltip title='Save the label and create one more'>
<CVATTooltip title={tooltipTitle}>
<Button
style={{ width: '150px' }}
type='primary'
htmlType='submit'
onClick={(): void => {
this.continueAfterSubmit = true;
}}
>
Continue
{buttonText}
</Button>
</CVATTooltip>
);
}
private renderCancelButton(): JSX.Element {
const { onSubmit } = this.props;
const { onCancel } = this.props;
return (
<CVATTooltip title='Do not save the label and return'>
......@@ -470,7 +472,7 @@ export default class LabelForm extends React.Component<Props> {
danger
style={{ width: '150px' }}
onClick={(): void => {
onSubmit(null);
onCancel();
}}
>
Cancel
......@@ -526,6 +528,8 @@ export default class LabelForm extends React.Component<Props> {
this.formRef.current.setFieldsValue({ attributes: convertedAttributes });
}
this.focus();
}
public render(): JSX.Element {
......@@ -546,8 +550,7 @@ export default class LabelForm extends React.Component<Props> {
</Col>
</Row>
<Row justify='start' align='middle'>
<Col>{this.renderDoneButton()}</Col>
<Col offset={1}>{this.renderContinueButton()}</Col>
<Col>{this.renderSaveButton()}</Col>
<Col offset={1}>{this.renderCancelButton()}</Col>
</Row>
</Form>
......
......@@ -98,48 +98,45 @@ export default class LabelsEditor extends React.PureComponent<LabelsEditorProps,
this.handleSubmit(savedLabels, unsavedLabels);
};
private handleCreate = (label: Label | null): void => {
if (label === null) {
this.setState({ constructorMode: ConstructorMode.SHOW });
} else {
const { unsavedLabels, savedLabels } = this.state;
const newUnsavedLabels = [
...unsavedLabels,
{
...label,
id: idGenerator(),
},
];
private handleCreate = (label: Label): void => {
const { unsavedLabels, savedLabels } = this.state;
const newUnsavedLabels = [
...unsavedLabels,
{
...label,
id: idGenerator(),
},
];
this.setState({ unsavedLabels: newUnsavedLabels });
this.handleSubmit(savedLabels, newUnsavedLabels);
}
this.setState({ unsavedLabels: newUnsavedLabels });
this.handleSubmit(savedLabels, newUnsavedLabels);
};
private handleUpdate = (label: Label | null): void => {
private handleUpdate = (label: Label): void => {
const { savedLabels, unsavedLabels } = this.state;
if (label) {
const filteredSavedLabels = savedLabels.filter((_label: Label) => _label.id !== label.id);
const filteredUnsavedLabels = unsavedLabels.filter((_label: Label) => _label.id !== label.id);
if (label.id >= 0) {
filteredSavedLabels.push(label);
this.setState({
savedLabels: filteredSavedLabels,
constructorMode: ConstructorMode.SHOW,
});
} else {
filteredUnsavedLabels.push(label);
this.setState({
unsavedLabels: filteredUnsavedLabels,
constructorMode: ConstructorMode.SHOW,
});
}
this.handleSubmit(filteredSavedLabels, filteredUnsavedLabels);
const filteredSavedLabels = savedLabels.filter((_label: Label) => _label.id !== label.id);
const filteredUnsavedLabels = unsavedLabels.filter((_label: Label) => _label.id !== label.id);
if (label.id >= 0) {
filteredSavedLabels.push(label);
this.setState({
savedLabels: filteredSavedLabels,
constructorMode: ConstructorMode.SHOW,
});
} else {
this.setState({ constructorMode: ConstructorMode.SHOW });
filteredUnsavedLabels.push(label);
this.setState({
unsavedLabels: filteredUnsavedLabels,
constructorMode: ConstructorMode.SHOW,
});
}
this.handleSubmit(filteredSavedLabels, filteredUnsavedLabels);
this.setState({ constructorMode: ConstructorMode.SHOW });
};
private handlerCancel = (): void => {
this.setState({ constructorMode: ConstructorMode.SHOW });
};
private handleDelete = (label: Label): void => {
......@@ -198,6 +195,7 @@ export default class LabelsEditor extends React.PureComponent<LabelsEditorProps,
const {
savedLabels, unsavedLabels, constructorMode, labelForUpdate,
} = this.state;
const savedAndUnsavedLabels = [...savedLabels, ...unsavedLabels];
return (
<Tabs
......@@ -214,7 +212,7 @@ export default class LabelsEditor extends React.PureComponent<LabelsEditorProps,
)}
key='1'
>
<RawViewer labels={[...savedLabels, ...unsavedLabels]} onSubmit={this.handleRawSubmit} />
<RawViewer labels={savedAndUnsavedLabels} onSubmit={this.handleRawSubmit} />
</Tabs.TabPane>
<Tabs.TabPane
......@@ -228,7 +226,7 @@ export default class LabelsEditor extends React.PureComponent<LabelsEditorProps,
>
{constructorMode === ConstructorMode.SHOW && (
<ConstructorViewer
labels={[...savedLabels, ...unsavedLabels]}
labels={savedAndUnsavedLabels}
onUpdate={(label: Label): void => {
this.setState({
constructorMode: ConstructorMode.UPDATE,
......@@ -244,10 +242,18 @@ export default class LabelsEditor extends React.PureComponent<LabelsEditorProps,
/>
)}
{constructorMode === ConstructorMode.UPDATE && labelForUpdate !== null && (
<ConstructorUpdater label={labelForUpdate} onUpdate={this.handleUpdate} />
<ConstructorUpdater
label={labelForUpdate}
onUpdate={this.handleUpdate}
onCancel={this.handlerCancel}
/>
)}
{constructorMode === ConstructorMode.CREATE && (
<ConstructorCreator labelNames={labels.map((l) => l.name)} onCreate={this.handleCreate} />
<ConstructorCreator
labelNames={labels.map((l) => l.name)}
onCreate={this.handleCreate}
onCancel={this.handlerCancel}
/>
)}
</Tabs.TabPane>
</Tabs>
......
......@@ -23,7 +23,7 @@ interface DispatchToProps {
function mapDispatchToProps(dispatch: any): DispatchToProps {
return {
onCreate: (data: CreateTaskData): void => dispatch(createTaskAsync(data)),
onCreate: (data: CreateTaskData): Promise<any> => dispatch(createTaskAsync(data)),
};
}
......
......@@ -71,7 +71,7 @@ context('Creating a project by inserting labels from a task.', { browser: '!fire
cy.contains('button', 'Done').click();
cy.contains('[role="tab"]', 'Constructor').click();
cy.contains('.cvat-constructor-viewer-item', task.label).should('exist');
cy.contains('button', 'Submit').click();
cy.contains('button', 'Submit & Continue').click();
cy.get('.cvat-notification-create-project-success').should('exist').find('[data-icon="close"]').click();
cy.goToProjectsList();
cy.openProject(projectName);
......
......@@ -31,7 +31,7 @@ context('Create more than one task per time when create from project.', () => {
});
cy.get('.cvat-constructor-viewer-new-item').should('not.exist');
cy.get('input[type="file"]').attachFile(archiveName, { subjectType: 'drag-n-drop' });
cy.contains('button', 'Submit').click();
cy.contains('button', 'Submit & Continue').click();
cy.get('.cvat-notification-create-task-success').should('exist');
cy.get('.cvat-notification-create-task-fail').should('not.exist');
}
......
......@@ -44,7 +44,7 @@ context('Create a task with set an issue tracker.', () => {
cy.contains('Advanced configuration').click();
cy.get('#bugTracker').type(incorrectBugTrackerUrl);
cy.contains('URL is not a valid URL').should('exist');
cy.get('.cvat-create-task-submit-section').click();
cy.contains('button', 'Submit & Continue').click();
cy.get('.cvat-notification-create-task-fail').should('exist').and('be.visible');
cy.closeNotification('.cvat-notification-create-task-fail');
});
......@@ -52,7 +52,7 @@ context('Create a task with set an issue tracker.', () => {
it('Set correct issue tracker URL. The task created.', () => {
cy.get('#bugTracker').clear().type(dummyBugTrackerUrl);
cy.contains('URL is not a valid URL').should('not.exist');
cy.get('.cvat-create-task-submit-section').click();
cy.contains('button', 'Submit & Continue').click();
cy.get('.cvat-notification-create-task-fail').should('not.exist');
cy.get('.cvat-notification-create-task-success').should('exist').and('be.visible');
});
......
......@@ -35,8 +35,7 @@ context('Button "Continue" in label editor.', () => {
cy.get('.cvat-constructor-viewer-new-item').click();
cy.get('.cvat-label-constructor-creator').within(() => {
cy.contains('button', 'Continue').click();
cy.contains('[role="alert"]', 'Please specify a name').should('be.visible');
cy.contains('button', 'Cancel').click();
cy.get('.cvat-label-constructor-creator').should('not.exist');
});
});
});
......
......@@ -36,28 +36,28 @@ context('Try to create a task without necessary arguments.', () => {
describe(`Testing "${labelName}"`, () => {
it('Try to create a task without any fields. A task is not created.', () => {
cy.get('.cvat-create-task-submit-section').click();
cy.contains('button', 'Submit & Continue').click();
cy.get('.cvat-notification-create-task-fail').should('exist');
cy.closeNotification('.cvat-notification-create-task-fail');
});
it('Input a task name. A task is not created.', () => {
cy.get('[id="name"]').type(taskName);
cy.get('.cvat-create-task-submit-section').click();
cy.contains('button', 'Submit & Continue').click();
cy.get('.cvat-notification-create-task-fail').should('exist');
cy.closeNotification('.cvat-notification-create-task-fail');
});
it('Input task labels. A task is not created.', () => {
cy.addNewLabel(labelName);
cy.get('.cvat-create-task-submit-section').click();
cy.contains('button', 'Submit & Continue').click();
cy.get('.cvat-notification-create-task-fail').should('exist');
cy.closeNotification('.cvat-notification-create-task-fail');
});
it('Add some files. A task created.', () => {
cy.get('input[type="file"]').attachFile(archiveName, { subjectType: 'drag-n-drop' });
cy.get('.cvat-create-task-submit-section').click();
cy.contains('button', 'Submit & Continue').click();
cy.get('.cvat-notification-create-task-fail').should('not.exist');
cy.get('.cvat-notification-create-task-success').should('exist');
// Check that the interface is prepared for creating the next task.
......
......@@ -45,7 +45,8 @@ context('Add/delete labels and attributes.', () => {
cy.get('.cvat-attribute-type-input').click();
cy.get('.cvat-attribute-type-input-text').click();
cy.get('.cvat-attribute-values-input').type(textDefaultValue);
cy.contains('[type="submit"]', 'Done').click();
cy.contains('[type="submit"]', 'Continue').click();
cy.contains('[type="button"]', 'Cancel').click();
cy.get('.cvat-constructor-viewer-item').should('exist');
});
......
......@@ -17,15 +17,18 @@ context('Changing a label name via label constructor.', () => {
});
describe(`Testing case "${caseId}"`, () => {
it('Set empty label name. Press "Done" button. Alert exist.', () => {
it('Set empty label name. Press "Continue" button. Label name is not created. Label constructor is closed.', () => {
cy.get('.cvat-constructor-viewer-new-item').click(); // Open label constructor
cy.contains('[type="submit"]', 'Done').click();
cy.contains('[role="alert"]', 'Please specify a name').should('exist').and('be.visible');
cy.contains('[type="submit"]', 'Continue').click();
cy.get('.cvat-label-constructor-creator').should('not.exist');
cy.get('.cvat-constructor-viewer').should('be.visible');
});
it('Change label name to any other correct value. Press "Done" button. The label created.', () => {
it('Change label name to any other correct value. Press "Continue" button. The label created.', () => {
cy.get('.cvat-constructor-viewer-new-item').click(); // Open label constructor
cy.get('[placeholder="Label name"]').type(firstLabelName);
cy.contains('[type="submit"]', 'Done').click({ force: true });
cy.contains('[type="submit"]', 'Continue').click({ force: true });
cy.contains('[type="button"]', 'Cancel').click(); // Close label constructor
cy.get('.cvat-constructor-viewer-item').should('exist').and('have.text', firstLabelName);
});
......
......@@ -45,7 +45,7 @@ context('Try to create a task with an incorrect dataset repository.', () => {
it('Set dummy dataset repository.', () => {
cy.get('#repository').type(incorrectDatasetRepoUrlHttps);
cy.get('.cvat-create-task-submit-section').click();
cy.contains('button', 'Submit & Continue').click();
cy.get('.cvat-notification-notice-create-task-failed').should('exist');
cy.closeNotification('.cvat-notification-notice-create-task-failed');
cy.get('#repository').clear();
......@@ -53,7 +53,7 @@ context('Try to create a task with an incorrect dataset repository.', () => {
it('Set repository with missing access.', () => {
cy.get('#repository').type(repositoryWithMissingAccess);
cy.get('.cvat-create-task-submit-section').click();
cy.contains('button', 'Submit & Continue').click();
cy.get('.cvat-notification-notice-create-task-failed').should('exist');
cy.get('.cvat-create-task-clone-repository-fail').should('exist');
});
......
......@@ -34,9 +34,7 @@ context('Connected file share.', () => {
});
});
});
cy.contains('button', 'Submit').click();
cy.get('.cvat-notification-create-task-success').should('exist').find('button').click();
cy.get('.cvat-notification-create-task-success').should('exist').find('[data-icon="close"]').click();
cy.contains('button', 'Submit & Open').click();
cy.get('.cvat-task-details').should('exist');
}
......
......@@ -30,14 +30,14 @@ context('Create a task with files from remote sources.', () => {
cy.addNewLabel(labelName);
cy.contains('Remote sources').click();
cy.get('.cvat-file-selector-remote').type(wrongUrl);
cy.get('.cvat-create-task-submit-section').click();
cy.contains('button', 'Submit & Continue').click();
cy.get('.cvat-notification-notice-create-task-failed').should('exist');
cy.closeNotification('.cvat-notification-notice-create-task-failed');
});
it('Set correct URL to remote file. The task is created.', () => {
cy.get('.cvat-file-selector-remote').clear().type(correctUrl);
cy.get('.cvat-create-task-submit-section').click();
cy.contains('button', 'Submit & Continue').click();
cy.get('.cvat-notification-create-task-success').should('exist');
cy.goToTaskList();
cy.contains('.cvat-item-task-name', taskName).should('exist');
......
......@@ -197,7 +197,7 @@ Cypress.Commands.add(
if (multiAttrParams) {
cy.updateAttributes(multiAttrParams);
}
cy.contains('button', 'Done').click();
cy.contains('button', 'Continue').click();
} else {
if (attachToProject) {
cy.get('.cvat-project-search-field').click();
......@@ -217,7 +217,7 @@ Cypress.Commands.add(
if (advancedConfigurationParams) {
cy.advancedConfiguration(advancedConfigurationParams);
}
cy.contains('button', 'Submit').click();
cy.contains('button', 'Submit & Continue').click();
if (expectedResult === 'success') {
cy.get('.cvat-notification-create-task-success').should('exist').find('[data-icon="close"]').click();
}
......@@ -699,7 +699,8 @@ Cypress.Commands.add('addNewLabel', (newLabelName, additionalAttrs, labelColor)
cy.updateAttributes(additionalAttrs[i]);
}
}
cy.contains('button', 'Done').click();
cy.contains('button', 'Continue').click();
cy.contains('button', 'Cancel').click();
cy.get('.cvat-spinner').should('not.exist');
cy.get('.cvat-constructor-viewer').should('be.visible');
cy.contains('.cvat-constructor-viewer-item', new RegExp(`^${newLabelName}$`)).should('exist');
......@@ -711,12 +712,9 @@ Cypress.Commands.add('addNewLabelViaContinueButton', (additionalLabels) => {
cy.get('.cvat-constructor-viewer-new-item').click();
for (let j = 0; j < additionalLabels.length; j++) {
cy.get('[placeholder="Label name"]').type(additionalLabels[j]);
if (j !== additionalLabels.length - 1) {
cy.contains('button', 'Continue').click();
} else {
cy.contains('button', 'Done').click();
}
cy.contains('button', 'Continue').click();
}
cy.contains('button', 'Cancel').click();
}
});
});
......
......@@ -26,9 +26,9 @@ Cypress.Commands.add(
if (multiAttrParams) {
cy.updateAttributes(multiAttrParams);
}
cy.contains('button', 'Done').click();
cy.contains('button', 'Continue').click();
cy.get('.cvat-create-project-content').within(() => {
cy.contains('Submit').click();
cy.contains('button', 'Submit & Continue').click();
});
if (expectedResult === 'success') {
cy.get('.cvat-notification-create-project-success').should('exist').find('[data-icon="close"]').click();
......@@ -184,7 +184,6 @@ Cypress.Commands.add('deleteProjectViaActions', (projectName) => {
Cypress.Commands.add('assignProjectToUser', (user) => {
cy.get('.cvat-project-details').within(() => {
cy.get('.cvat-user-search-field').click().type(user);
cy.wait(300);
});
cy.get('.ant-select-dropdown')
.not('.ant-select-dropdown-hidden')
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册