Skip to content

Commit

Permalink
86 improve uiux on create task page (cvat-ai#7)
Browse files Browse the repository at this point in the history
* 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: Boris <[email protected]>
Co-authored-by: kirill-sizov <[email protected]>
Co-authored-by: Nikita Manovich <[email protected]>
  • Loading branch information
4 people authored Jul 23, 2022
1 parent b7e1ebe commit fbcf758
Show file tree
Hide file tree
Showing 27 changed files with 318 additions and 232 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/eslint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion cvat-ui/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cvat-ui",
"version": "1.39.1",
"version": "1.40.0",
"description": "CVAT single-page application",
"main": "src/index.tsx",
"scripts": {
Expand Down
4 changes: 3 additions & 1 deletion cvat-ui/src/actions/projects-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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;
}
};
}
Expand Down
4 changes: 3 additions & 1 deletion cvat-ui/src/actions/tasks-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
};
}
Expand Down
122 changes: 74 additions & 48 deletions cvat-ui/src/components/create-project-page/create-project-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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
Expand All @@ -38,7 +40,7 @@ function NameConfigurationForm({ formRef }: { formRef: RefObject<FormInstance> }
},
]}
>
<Input />
<Input ref={inputRef} />
</Form.Item>
</Form>
);
Expand All @@ -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>
Expand All @@ -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>

Expand Down Expand Up @@ -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}>
Expand All @@ -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>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -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'>
Expand All @@ -55,7 +63,7 @@ export default class BasicConfigurationForm extends React.PureComponent<Props> {
},
]}
>
<Input />
<Input ref={this.inputRef} />
</Form.Item>
</Form>
);
Expand Down
Loading

0 comments on commit fbcf758

Please sign in to comment.