User interface with React and antd (#785)
* Dump & refactoring * Upload annotations, cvat-core from sources * Added download icon * Added iconmain
parent
4361bc548c
commit
5f511b7543
@ -1,5 +1,5 @@
|
||||
dist
|
||||
docs
|
||||
node_modules
|
||||
reports
|
||||
/dist
|
||||
/docs
|
||||
/node_modules
|
||||
/reports
|
||||
|
||||
|
||||
@ -1 +1 @@
|
||||
node_modules
|
||||
/node_modules
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
File diff suppressed because one or more lines are too long
@ -0,0 +1,43 @@
|
||||
import { AnyAction, Dispatch, ActionCreator } from 'redux';
|
||||
import { ThunkAction } from 'redux-thunk';
|
||||
|
||||
import getCore from '../core';
|
||||
|
||||
const cvat = getCore();
|
||||
|
||||
export enum FormatsActionTypes {
|
||||
GETTING_FORMATS_SUCCESS = 'GETTING_FORMATS_SUCCESS',
|
||||
GETTING_FORMATS_FAILED = 'GETTING_FORMATS_FAILED',
|
||||
}
|
||||
|
||||
export function gettingFormatsSuccess(formats: any): AnyAction {
|
||||
return {
|
||||
type: FormatsActionTypes.GETTING_FORMATS_SUCCESS,
|
||||
payload: {
|
||||
formats,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function gettingFormatsFailed(error: any): AnyAction {
|
||||
return {
|
||||
type: FormatsActionTypes.GETTING_FORMATS_FAILED,
|
||||
payload: {
|
||||
error,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function gettingFormatsAsync(): ThunkAction<Promise<void>, {}, {}, AnyAction> {
|
||||
return async (dispatch: ActionCreator<Dispatch>): Promise<void> => {
|
||||
let formats = null;
|
||||
try {
|
||||
formats = await cvat.server.formats();
|
||||
} catch (error) {
|
||||
dispatch(gettingFormatsFailed(error));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(gettingFormatsSuccess(formats));
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
|
||||
import Title from 'antd/lib/typography/Title';
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
import {
|
||||
Col,
|
||||
Row,
|
||||
Modal,
|
||||
} from 'antd';
|
||||
|
||||
import RegisterForm, { RegisterData } from '../../components/register-page/register-form';
|
||||
|
||||
interface RegisterPageComponentProps {
|
||||
registerError: string;
|
||||
onRegister: (username: string, firstName: string,
|
||||
lastName: string, email: string,
|
||||
password1: string, password2: string) => void;
|
||||
}
|
||||
|
||||
function RegisterPageComponent(props: RegisterPageComponentProps & RouteComponentProps) {
|
||||
const sizes = {
|
||||
xs: { span: 14 },
|
||||
sm: { span: 14 },
|
||||
md: { span: 10 },
|
||||
lg: { span: 4 },
|
||||
xl: { span: 4 },
|
||||
}
|
||||
|
||||
if (props.registerError) {
|
||||
Modal.error({
|
||||
title: 'Could not register',
|
||||
content: props.registerError,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Row type='flex' justify='center' align='middle'>
|
||||
<Col {...sizes}>
|
||||
<Title level={2}> Create an account </Title>
|
||||
<RegisterForm onSubmit={(registerData: RegisterData) => {
|
||||
props.onRegister(
|
||||
registerData.username,
|
||||
registerData.firstName,
|
||||
registerData.lastName,
|
||||
registerData.email,
|
||||
registerData.password1,
|
||||
registerData.password2,
|
||||
);
|
||||
}}/>
|
||||
<Row type='flex' justify='start' align='top'>
|
||||
<Col>
|
||||
<Text strong>
|
||||
Already have an account? <Link to="/auth/login"> Login </Link>
|
||||
</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(RegisterPageComponent);
|
||||
@ -0,0 +1,185 @@
|
||||
import React from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Spin,
|
||||
Modal,
|
||||
} from 'antd';
|
||||
|
||||
import {
|
||||
TasksQuery,
|
||||
} from '../../reducers/interfaces';
|
||||
|
||||
import TopBar from './top-bar';
|
||||
import EmptyListComponent from './empty-list';
|
||||
import TaskListContainer from '../../containers/tasks-page/tasks-list';
|
||||
|
||||
interface TasksPageProps {
|
||||
dumpingError: string;
|
||||
loadingError: string;
|
||||
tasksFetchingError: string;
|
||||
loadingDoneMessage: string;
|
||||
tasksAreBeingFetched: boolean;
|
||||
gettingQuery: TasksQuery;
|
||||
numberOfTasks: number;
|
||||
numberOfVisibleTasks: number;
|
||||
onGetTasks: (gettingQuery: TasksQuery) => void;
|
||||
}
|
||||
|
||||
class TasksPageComponent extends React.PureComponent<TasksPageProps & RouteComponentProps> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
private updateURL(gettingQuery: TasksQuery) {
|
||||
let queryString = '?';
|
||||
for (const field of Object.keys(gettingQuery)) {
|
||||
if (gettingQuery[field] !== null) {
|
||||
queryString += `${field}=${gettingQuery[field]}&`;
|
||||
}
|
||||
}
|
||||
this.props.history.replace({
|
||||
search: queryString.slice(0, -1),
|
||||
});
|
||||
}
|
||||
|
||||
private getSearchField(gettingQuery: TasksQuery): string {
|
||||
let searchString = '';
|
||||
for (const field of Object.keys(gettingQuery)) {
|
||||
if (gettingQuery[field] !== null && field !== 'page') {
|
||||
if (field === 'search') {
|
||||
return (gettingQuery[field] as any) as string;
|
||||
} else {
|
||||
if (typeof (gettingQuery[field] === 'number')) {
|
||||
searchString += `${field}:${gettingQuery[field]} AND `;
|
||||
} else {
|
||||
searchString += `${field}:"${gettingQuery[field]}" AND `;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return searchString.slice(0, -5);
|
||||
}
|
||||
|
||||
private handleSearch = (value: string): void => {
|
||||
const gettingQuery = { ...this.props.gettingQuery };
|
||||
const search = value.replace(/\s+/g, ' ').replace(/\s*:+\s*/g, ':').trim();
|
||||
|
||||
const fields = ['name', 'mode', 'owner', 'assignee', 'status', 'id'];
|
||||
for (const field of fields) {
|
||||
gettingQuery[field] = null;
|
||||
}
|
||||
gettingQuery.search = null;
|
||||
|
||||
let specificRequest = false;
|
||||
for (const param of search.split(/[\s]+and[\s]+|[\s]+AND[\s]+/)) {
|
||||
if (param.includes(':')) {
|
||||
const [name, value] = param.split(':');
|
||||
if (fields.includes(name) && !!value) {
|
||||
specificRequest = true;
|
||||
if (name === 'id') {
|
||||
if (Number.isInteger(+value)) {
|
||||
gettingQuery[name] = +value;
|
||||
}
|
||||
} else {
|
||||
gettingQuery[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gettingQuery.page = 1;
|
||||
if (!specificRequest && value) { // only id
|
||||
gettingQuery.search = value;
|
||||
}
|
||||
|
||||
this.updateURL(gettingQuery);
|
||||
this.props.onGetTasks(gettingQuery);
|
||||
}
|
||||
|
||||
private handlePagination = (page: number): void => {
|
||||
const gettingQuery = { ...this.props.gettingQuery };
|
||||
|
||||
gettingQuery.page = page;
|
||||
this.updateURL(gettingQuery);
|
||||
this.props.onGetTasks(gettingQuery);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const gettingQuery = { ...this.props.gettingQuery };
|
||||
const params = new URLSearchParams(this.props.location.search);
|
||||
|
||||
for (const field of Object.keys(gettingQuery)) {
|
||||
if (params.has(field)) {
|
||||
const value = params.get(field);
|
||||
if (value) {
|
||||
if (field === 'id' || field === 'page') {
|
||||
if (Number.isInteger(+value)) {
|
||||
gettingQuery[field] = +value;
|
||||
}
|
||||
} else {
|
||||
gettingQuery[field] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.updateURL(gettingQuery);
|
||||
this.props.onGetTasks(gettingQuery);
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
if (this.props.tasksFetchingError) {
|
||||
Modal.error({
|
||||
title: 'Could not receive tasks',
|
||||
content: this.props.tasksFetchingError,
|
||||
});
|
||||
}
|
||||
|
||||
if (this.props.dumpingError) {
|
||||
Modal.error({
|
||||
title: 'Could not dump annotations',
|
||||
content: this.props.dumpingError,
|
||||
});;
|
||||
}
|
||||
|
||||
if (this.props.loadingError) {
|
||||
Modal.error({
|
||||
title: 'Could not load annotations',
|
||||
content: this.props.loadingError,
|
||||
});;
|
||||
}
|
||||
|
||||
if (this.props.loadingDoneMessage) {
|
||||
Modal.info({
|
||||
title: 'Successful loading of annotations',
|
||||
content: this.props.loadingDoneMessage,
|
||||
});;
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (this.props.tasksAreBeingFetched) {
|
||||
return (
|
||||
<Spin size='large' style={{margin: '25% 50%'}}/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div className='tasks-page'>
|
||||
<TopBar
|
||||
onSearch={this.handleSearch}
|
||||
searchValue={this.getSearchField(this.props.gettingQuery)}
|
||||
/>
|
||||
{this.props.numberOfVisibleTasks ?
|
||||
<TaskListContainer
|
||||
onSwitchPage={this.handlePagination}
|
||||
/> : <EmptyListComponent/>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(TasksPageComponent);
|
||||
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Col,
|
||||
Row,
|
||||
Button,
|
||||
Input,
|
||||
} from 'antd';
|
||||
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
|
||||
interface VisibleTopBarProps {
|
||||
onSearch: (value: string) => void;
|
||||
searchValue: string;
|
||||
}
|
||||
|
||||
export default class TopBarComponent extends React.PureComponent<VisibleTopBarProps> {
|
||||
public render() {
|
||||
return (
|
||||
<>
|
||||
<Row type='flex' justify='center' align='middle'>
|
||||
<Col md={22} lg={18} xl={16} xxl={14}>
|
||||
<Text strong> Default project </Text>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row type='flex' justify='center' align='middle'>
|
||||
<Col md={11} lg={9} xl={8} xxl={7}>
|
||||
<Text className='cvat-title'> Tasks </Text>
|
||||
<Input.Search
|
||||
defaultValue={this.props.searchValue}
|
||||
onSearch={this.props.onSearch}
|
||||
size='large' placeholder='Search'
|
||||
/>
|
||||
</Col>
|
||||
<Col
|
||||
md={{span: 11}}
|
||||
lg={{span: 9}}
|
||||
xl={{span: 8}}
|
||||
xxl={{span: 7}}>
|
||||
<Button size='large' id='cvat-create-task-button' type='primary' onClick={
|
||||
() => window.open('/tasks/create', '_blank')
|
||||
}> Create new task </Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
export default function CreateTaskPage() {
|
||||
export default function CreateTaskPageContainer() {
|
||||
return (
|
||||
<div>
|
||||
"Create Task Page"
|
||||
@ -0,0 +1,44 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { logoutAsync } from '../../actions/auth-actions';
|
||||
import { CombinedState } from '../../reducers/root-reducer';
|
||||
|
||||
import HeaderComponent from '../../components/header/header';
|
||||
|
||||
interface StateToProps {
|
||||
username: string;
|
||||
logoutError: any;
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
logout(): void;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState): StateToProps {
|
||||
return {
|
||||
username: state.auth.user.username,
|
||||
logoutError: state.auth.logoutError,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||
return {
|
||||
logout: () => dispatch(logoutAsync()),
|
||||
}
|
||||
}
|
||||
|
||||
function HeaderContainer(props: StateToProps & DispatchToProps) {
|
||||
return (
|
||||
<HeaderComponent
|
||||
onLogout={props.logout}
|
||||
username={props.username}
|
||||
logoutError={props.logoutError ? props.logoutError.toString() : ''}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(HeaderContainer);
|
||||
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { loginAsync } from '../../actions/auth-actions';
|
||||
import { CombinedState } from '../../reducers/root-reducer';
|
||||
import LoginPageComponent from '../../components/login-page/login-page';
|
||||
|
||||
interface StateToProps {
|
||||
loginError: any;
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
login(username: string, password: string): void;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState): StateToProps {
|
||||
return {
|
||||
loginError: state.auth.loginError,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||
return {
|
||||
login: (...args) => dispatch(loginAsync(...args)),
|
||||
};
|
||||
}
|
||||
|
||||
function LoginPageContainer(props: StateToProps & DispatchToProps) {
|
||||
return (
|
||||
<LoginPageComponent
|
||||
onLogin={props.login}
|
||||
loginError={props.loginError ? props.loginError.toString() : ''}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(LoginPageContainer);
|
||||
@ -1,77 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
|
||||
import Title from 'antd/lib/typography/Title';
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
import {
|
||||
Col,
|
||||
Row,
|
||||
Modal,
|
||||
} from 'antd';
|
||||
|
||||
|
||||
import { registerAsync } from '../actions/auth-actions';
|
||||
import RegisterForm, { RegisterData } from '../components/register-form';
|
||||
import { AuthState } from '../reducers/interfaces';
|
||||
|
||||
interface StateToProps {
|
||||
auth: AuthState;
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
register: (registerData: RegisterData) => void;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: any): StateToProps {
|
||||
return {
|
||||
auth: state.auth,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||
return {
|
||||
register: (registerData: RegisterData) => dispatch(registerAsync(registerData))
|
||||
}
|
||||
}
|
||||
|
||||
type RegisterPageProps = StateToProps & DispatchToProps & RouteComponentProps;
|
||||
function RegisterPage(props: RegisterPageProps) {
|
||||
const { registerError } = props.auth;
|
||||
const sizes = {
|
||||
xs: { span: 14 },
|
||||
sm: { span: 14 },
|
||||
md: { span: 10 },
|
||||
lg: { span: 4 },
|
||||
xl: { span: 4 },
|
||||
}
|
||||
|
||||
if (registerError) {
|
||||
Modal.error({
|
||||
title: 'Could not login',
|
||||
content: `${registerError.toString()}`,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Row type='flex' justify='center' align='middle'>
|
||||
<Col {...sizes}>
|
||||
<Title level={2}> Create an account </Title>
|
||||
<RegisterForm onSubmit={props.register}/>
|
||||
<Row type='flex' justify='start' align='top'>
|
||||
<Col>
|
||||
<Text strong>
|
||||
Already have an account? <Link to="/auth/login"> Login </Link>
|
||||
</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(RegisterPage));
|
||||
@ -0,0 +1,42 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { registerAsync } from '../../actions/auth-actions';
|
||||
import { CombinedState } from '../../reducers/root-reducer';
|
||||
import RegisterPageComponent from '../../components/register-page/register-page';
|
||||
|
||||
interface StateToProps {
|
||||
registerError: any;
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
register: (username: string, firstName: string,
|
||||
lastName: string, email: string,
|
||||
password1: string, password2: string) => void;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState): StateToProps {
|
||||
return {
|
||||
registerError: state.auth.registerError,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||
return {
|
||||
register: (...args) => dispatch(registerAsync(...args))
|
||||
}
|
||||
}
|
||||
|
||||
type RegisterPageContainerProps = StateToProps & DispatchToProps;
|
||||
function RegisterPageContainer(props: RegisterPageContainerProps) {
|
||||
return (
|
||||
<RegisterPageComponent
|
||||
registerError={props.registerError ? props.registerError.toString() : ''}
|
||||
onRegister={props.register}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(RegisterPageContainer);
|
||||
@ -1,212 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
import {
|
||||
Col,
|
||||
Row,
|
||||
Button,
|
||||
Input,
|
||||
Spin,
|
||||
Modal,
|
||||
} from 'antd';
|
||||
|
||||
import { TasksState, TasksQuery } from '../reducers/interfaces';
|
||||
import EmptyList from '../components/tasks-page/empty-list';
|
||||
import TaskList from '../components/tasks-page/task-list';
|
||||
|
||||
import { getTasksAsync } from '../actions/tasks-actions';
|
||||
|
||||
interface StateToProps {
|
||||
tasks: TasksState;
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
getTasks: (query: TasksQuery) => void;
|
||||
}
|
||||
|
||||
interface TasksPageState {
|
||||
searchString: string;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: any): object {
|
||||
return {
|
||||
tasks: state.tasks,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||
return {
|
||||
getTasks: (query: TasksQuery) => {dispatch(getTasksAsync(query))}
|
||||
}
|
||||
}
|
||||
|
||||
type TasksPageProps = StateToProps & DispatchToProps
|
||||
& RouteComponentProps;
|
||||
|
||||
class TasksPage extends React.PureComponent<TasksPageProps, TasksPageState> {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
private updateURL(query: TasksQuery) {
|
||||
let queryString = '?';
|
||||
for (const field of Object.keys(query)) {
|
||||
if (query[field] != null && field !== 'page') {
|
||||
queryString += `${field}=${query[field]}&`;
|
||||
}
|
||||
}
|
||||
this.props.history.replace({
|
||||
search: queryString.slice(0, -1),
|
||||
});
|
||||
}
|
||||
|
||||
private computeSearchField(query: TasksQuery): string {
|
||||
let searchString = '';
|
||||
for (const field of Object.keys(query)) {
|
||||
|
||||
if (query[field] != null && field !== 'page') {
|
||||
if (typeof (query[field] === 'number')) {
|
||||
searchString += `${field}:${query[field]} AND `;
|
||||
} else {
|
||||
searchString += `${field}:"${query[field]}" AND `;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return searchString.slice(0, -5);
|
||||
}
|
||||
|
||||
private handlePagination(page: number): void {
|
||||
const query = { ...this.props.tasks.query };
|
||||
|
||||
query.page = page;
|
||||
this.updateURL(query);
|
||||
this.props.getTasks(query);
|
||||
}
|
||||
|
||||
private handleSearch(value: string): void {
|
||||
const query = { ...this.props.tasks.query };
|
||||
const search = value.replace(/\s+/g, ' ').replace(/\s*:+\s*/g, ':').trim();
|
||||
|
||||
const fields = ['name', 'mode', 'owner', 'assignee', 'status', 'id'];
|
||||
for (const field of fields) {
|
||||
query[field] = null;
|
||||
}
|
||||
query.search = null;
|
||||
|
||||
let specificRequest = false;
|
||||
for (const param of search.split(/[\s]+and[\s]+|[\s]+AND[\s]+/)) {
|
||||
if (param.includes(':')) {
|
||||
const [name, value] = param.split(':');
|
||||
if (fields.includes(name) && !!value) {
|
||||
specificRequest = true;
|
||||
if (name === 'id') {
|
||||
if (Number.isInteger(+value)) {
|
||||
query[name] = +value;
|
||||
}
|
||||
} else {
|
||||
query[name] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query.page = 1;
|
||||
if (!specificRequest && value) { // only id
|
||||
query.search = value;
|
||||
}
|
||||
|
||||
this.updateURL(query);
|
||||
this.props.getTasks(query);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const query = { ...this.props.tasks.query };
|
||||
const params = new URLSearchParams(this.props.location.search);
|
||||
|
||||
for (const field of Object.keys(query)) {
|
||||
if (params.has(field)) {
|
||||
const value = params.get(field);
|
||||
if (value) {
|
||||
if (field === 'id' || field === 'page') {
|
||||
if (Number.isInteger(+value)) {
|
||||
query[field] = +value;
|
||||
}
|
||||
} else {
|
||||
query[field] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.updateURL(query);
|
||||
this.props.getTasks(query);
|
||||
}
|
||||
|
||||
private renderTaskList() {
|
||||
const searchString = this.computeSearchField(this.props.tasks.query);
|
||||
|
||||
const List = this.props.tasks.array.length ? <TaskList
|
||||
tasks={this.props.tasks.array}
|
||||
previews={this.props.tasks.previews}
|
||||
page={this.props.tasks.query.page}
|
||||
count={this.props.tasks.count}
|
||||
goToPage={this.handlePagination.bind(this)}
|
||||
/> : <EmptyList/>
|
||||
|
||||
if (this.props.tasks.error) {
|
||||
Modal.error({
|
||||
title: 'Could not receive tasks',
|
||||
content: `${this.props.tasks.error.toString()}`,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='tasks-page'>
|
||||
<Row type='flex' justify='center' align='middle'>
|
||||
<Col md={22} lg={18} xl={16} xxl={14}>
|
||||
<Text strong> Default project </Text>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row type='flex' justify='center' align='middle'>
|
||||
<Col md={11} lg={9} xl={8} xxl={7}>
|
||||
<Text className='cvat-title'> Tasks </Text>
|
||||
<Input.Search
|
||||
defaultValue={searchString}
|
||||
onSearch={this.handleSearch.bind(this)}
|
||||
size='large' placeholder='Search'
|
||||
/>
|
||||
</Col>
|
||||
<Col
|
||||
md={{span: 11}}
|
||||
lg={{span: 9}}
|
||||
xl={{span: 8}}
|
||||
xxl={{span: 7}}>
|
||||
<Button size='large' id='cvat-create-task-button' type='primary' onClick={
|
||||
() => window.open('/tasks/create', '_blank')
|
||||
}> Create new task </Button>
|
||||
</Col>
|
||||
</Row>
|
||||
{List}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
public render() {
|
||||
if (this.props.tasks.initialized) {
|
||||
return this.renderTaskList();
|
||||
} else {
|
||||
return (
|
||||
<Spin size='large' style={{margin: '25% 50%'}}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(TasksPage));
|
||||
@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
TasksQuery,
|
||||
} from '../../reducers/interfaces';
|
||||
|
||||
import {
|
||||
CombinedState,
|
||||
} from '../../reducers/root-reducer';
|
||||
|
||||
import TaskItemComponent from '../../components/tasks-page/task-item'
|
||||
|
||||
import {
|
||||
getTasksAsync,
|
||||
dumpAnnotationsAsync,
|
||||
loadAnnotationsAsync,
|
||||
} from '../../actions/tasks-actions';
|
||||
|
||||
interface StateToProps {
|
||||
dumpActivities: string[] | null;
|
||||
loadActivity: string | null;
|
||||
previewImage: string;
|
||||
taskInstance: any;
|
||||
loaders: any[];
|
||||
dumpers: any[];
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
getTasks: (query: TasksQuery) => void;
|
||||
dump: (task: any, format: string) => void;
|
||||
load: (task: any, format: string, file: File) => void;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
idx: number;
|
||||
taskID: number;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
|
||||
const task = state.tasks.current[own.idx];
|
||||
const { formats } = state;
|
||||
const { dumps } = state.tasks.activities;
|
||||
const { loads } = state.tasks.activities;
|
||||
|
||||
return {
|
||||
dumpActivities: dumps.byTask[own.taskID] ? dumps.byTask[own.taskID] : null,
|
||||
loadActivity: loads.byTask[own.taskID] ? loads.byTask[own.taskID] : null,
|
||||
previewImage: task.preview,
|
||||
taskInstance: task.instance,
|
||||
loaders: formats.loaders,
|
||||
dumpers: formats.dumpers,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||
return {
|
||||
getTasks: (query: TasksQuery): void => {
|
||||
dispatch(getTasksAsync(query));
|
||||
},
|
||||
dump: (task: any, dumper: any): void => {
|
||||
dispatch(dumpAnnotationsAsync(task, dumper));
|
||||
},
|
||||
load: (task: any, loader: any, file: File): void => {
|
||||
dispatch(loadAnnotationsAsync(task, loader, file));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type TasksItemContainerProps = StateToProps & DispatchToProps & OwnProps;
|
||||
|
||||
function TaskItemContainer(props: TasksItemContainerProps) {
|
||||
return (
|
||||
<TaskItemComponent
|
||||
taskInstance={props.taskInstance}
|
||||
previewImage={props.previewImage}
|
||||
dumpActivities={props.dumpActivities}
|
||||
loadActivity={props.loadActivity}
|
||||
loaders={props.loaders}
|
||||
dumpers={props.dumpers}
|
||||
onLoadAnnotation={props.load}
|
||||
onDumpAnnotation={props.dump}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(TaskItemContainer);
|
||||
@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
TasksState,
|
||||
TasksQuery,
|
||||
} from '../../reducers/interfaces';
|
||||
|
||||
import {
|
||||
CombinedState,
|
||||
} from '../../reducers/root-reducer';
|
||||
|
||||
import TasksListComponent from '../../components/tasks-page/task-list';
|
||||
|
||||
import {
|
||||
getTasksAsync,
|
||||
} from '../../actions/tasks-actions';
|
||||
|
||||
interface StateToProps {
|
||||
tasks: TasksState;
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
getTasks: (query: TasksQuery) => void;
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
onSwitchPage: (page: number) => void;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState): StateToProps {
|
||||
return {
|
||||
tasks: state.tasks,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||
return {
|
||||
getTasks: (query: TasksQuery) => {dispatch(getTasksAsync(query))}
|
||||
}
|
||||
}
|
||||
|
||||
type TasksListContainerProps = StateToProps & DispatchToProps & OwnProps;
|
||||
|
||||
function TasksListContainer(props: TasksListContainerProps) {
|
||||
return (
|
||||
<TasksListComponent
|
||||
onSwitchPage={props.onSwitchPage}
|
||||
currentTasksIndexes={props.tasks.current.map((task) => task.instance.id)}
|
||||
currentPage={props.tasks.gettingQuery.page}
|
||||
numberOfTasks={props.tasks.count}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(TasksListContainer);
|
||||
@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
TasksQuery,
|
||||
} from '../../reducers/interfaces';
|
||||
import { CombinedState } from '../../reducers/root-reducer';
|
||||
|
||||
import TasksPageComponent from '../../components/tasks-page/tasks-page';
|
||||
|
||||
import { getTasksAsync } from '../../actions/tasks-actions';
|
||||
|
||||
interface StateToProps {
|
||||
dumpingError: any;
|
||||
loadingError: any;
|
||||
tasksFetchingError: any;
|
||||
loadingDoneMessage: string;
|
||||
tasksAreBeingFetched: boolean;
|
||||
gettingQuery: TasksQuery;
|
||||
numberOfTasks: number;
|
||||
numberOfVisibleTasks: number;
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
getTasks: (gettingQuery: TasksQuery) => void;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState): StateToProps {
|
||||
const { tasks } = state;
|
||||
const { activities } = tasks;
|
||||
const { dumps } = activities;
|
||||
const { loads } = activities;
|
||||
|
||||
return {
|
||||
dumpingError: dumps.dumpingError,
|
||||
loadingError: loads.loadingError,
|
||||
tasksFetchingError: tasks.tasksFetchingError,
|
||||
loadingDoneMessage: loads.loadingDoneMessage,
|
||||
tasksAreBeingFetched: !state.tasks.initialized,
|
||||
gettingQuery: tasks.gettingQuery,
|
||||
numberOfTasks: state.tasks.count,
|
||||
numberOfVisibleTasks: state.tasks.current.length,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||
return {
|
||||
getTasks: (query: TasksQuery) => {dispatch(getTasksAsync(query))}
|
||||
}
|
||||
}
|
||||
|
||||
type TasksPageContainerProps = StateToProps & DispatchToProps;
|
||||
|
||||
function TasksPageContainer(props: TasksPageContainerProps) {
|
||||
return (
|
||||
<TasksPageComponent
|
||||
dumpingError={props.dumpingError ? props.dumpingError.toString() : ''}
|
||||
loadingError={props.loadingError ? props.loadingError.toString() : ''}
|
||||
tasksFetchingError={props.tasksFetchingError ? props.tasksFetchingError.toString(): ''}
|
||||
loadingDoneMessage={props.loadingDoneMessage}
|
||||
tasksAreBeingFetched={props.tasksAreBeingFetched}
|
||||
gettingQuery={props.gettingQuery}
|
||||
numberOfTasks={props.numberOfTasks}
|
||||
numberOfVisibleTasks={props.numberOfVisibleTasks}
|
||||
onGetTasks={props.getTasks}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(TasksPageContainer);
|
||||
@ -0,0 +1,32 @@
|
||||
import { AnyAction } from 'redux';
|
||||
import { FormatsActionTypes } from '../actions/formats-actions';
|
||||
|
||||
import { FormatsState } from './interfaces';
|
||||
|
||||
const defaultState: FormatsState = {
|
||||
loaders: [],
|
||||
dumpers: [],
|
||||
gettingFormatsError: null,
|
||||
initialized: false,
|
||||
};
|
||||
|
||||
export default (state = defaultState, action: AnyAction): FormatsState => {
|
||||
switch (action.type) {
|
||||
case FormatsActionTypes.GETTING_FORMATS_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
initialized: true,
|
||||
gettingFormatsError: null,
|
||||
dumpers: action.payload.formats.map((format: any): any[] => format.dumpers).flat(),
|
||||
loaders: action.payload.formats.map((format: any): any[] => format.loaders).flat(),
|
||||
};
|
||||
case FormatsActionTypes.GETTING_FORMATS_FAILED:
|
||||
return {
|
||||
...state,
|
||||
initialized: true,
|
||||
gettingFormatsError: action.payload.error,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
@ -1,10 +1,24 @@
|
||||
import { combineReducers, Reducer } from 'redux';
|
||||
import authReducer from './auth-reducer';
|
||||
import tasksReducer from './tasks-reducer';
|
||||
import formatsReducer from './formats-reducer';
|
||||
|
||||
import {
|
||||
AuthState,
|
||||
TasksState,
|
||||
FormatsState,
|
||||
} from './interfaces';
|
||||
|
||||
export interface CombinedState {
|
||||
auth: AuthState;
|
||||
tasks: TasksState;
|
||||
formats: FormatsState;
|
||||
}
|
||||
|
||||
export default function createRootReducer(): Reducer {
|
||||
return combineReducers({
|
||||
auth: authReducer,
|
||||
tasks: tasksReducer,
|
||||
formats: formatsReducer,
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue