Basic dashboard components (#548)

* Update `cvat.js`
* Update metadata
* Minor refactoring
* Using sass
* Dashboard components structure
* Add pagination
main
Artyom Zankevich 7 years ago committed by Nikita Manovich
parent 021b77ccee
commit 59cd9260c3

@ -14,6 +14,7 @@
"customize-cra": "^0.2.12",
"less": "^3.9.0",
"less-loader": "^5.0.0",
"node-sass": "^4.12.0",
"react": "^16.8.6",
"react-app-rewired": "^2.1.3",
"react-dom": "^16.8.6",

File diff suppressed because one or more lines are too long

@ -19,7 +19,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>CVAT</title>
<script src="./cvat.min.js"></script>
</head>

@ -1,6 +1,6 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"short_name": "CVAT",
"name": "Computer Vision Annotation Tool",
"icons": [
{
"src": "favicon.ico",

@ -1,6 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import App from './app';
it('renders without crashing', () => {
const div = document.createElement('div');

@ -1,17 +1,42 @@
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Redirect } from 'react-router-dom';
import Dashboard from '../dashboard/Dashboard';
import Dashboard from '../dashboard/dashboard';
import './App.css';
import './app.scss';
declare const window: any;
interface AppState {
isLoggedIn: boolean;
}
class App extends Component<any, AppState> {
constructor(props: any) {
super(props);
this.state = {
isLoggedIn: false
};
}
componentDidMount() {
window.cvat.server.login('admin', 'admin').then(
(_response: any) => {
this.setState({ isLoggedIn: true });
},
(_error: any) => {
this.setState({ isLoggedIn: false });
}
);
}
class App extends Component {
render() {
return(
<Router>
<div>
<Redirect from="/" to="dashboard" />
<Route path="/dashboard" component={Dashboard} />
<Route path="/dashboard" component={ Dashboard } />
</div>
</Router>
);

@ -1,6 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Dashboard from './Dashboard';
import Dashboard from './dashboard';
it('renders without crashing', () => {
const div = document.createElement('div');

@ -1,26 +1,58 @@
import React, { Component } from 'react';
import './Dashboard.css';
import { Layout } from 'antd';
declare const window: any;
import DashboardHeader from './header/dashboard-header';
import DashboardContent from './content/dashboard-content';
import DashboardFooter from './footer/dashboard-footer';
class Dashboard extends Component {
import './dashboard.scss';
interface DashboardState {
tasks: [];
tasksCount: number;
}
class Dashboard extends Component<any, DashboardState> {
constructor(props: any) {
super(props);
this.state = { tasks: [], tasksCount: 0 };
}
componentDidMount() {
this.getTasks();
}
render() {
return (
<Layout>
<DashboardHeader onSearch={ this.getTasks } />
<DashboardContent tasks={ this.state.tasks } deleteTask={ this.deleteTask } />
<DashboardFooter tasksCount={ this.state.tasksCount } onPageChange={ this.onPageChange } />
</Layout>
);
}
componentWillMount() {
window.cvat.server.login('admin', 'admin').then(
(response: any) => {
console.log(response);
private getTasks = (query?: string) => {
const queryObject = {
search: query
};
(window as any).cvat.tasks.get(query ? queryObject : {}).then(
(tasks: any) => {
this.setState({ tasks, tasksCount: tasks.count });
},
(error: any) => {
console.log(error);
}
);
}
window.cvat.tasks.get().then(
(response: any) => {
console.log(response);
private onPageChange = (page: number) => {
(window as any).cvat.tasks.get({ page }).then(
(tasks: any) => {
this.setState({ tasks });
},
(error: any) => {
console.log(error);
@ -28,11 +60,20 @@ class Dashboard extends Component {
);
}
render() {
return(
<div className="Dashboard">
private deleteTask = (task: any) => {
task.delete().then(
(_deleted: any) => {
setTimeout(() => {
this.getTasks();
}, 1000);
// const tasks = this.state.tasks.filter((taskToDelete: any) => taskToDelete.id !== task.id) as any;
</div>
// this.setState({ tasks, tasksCount: this.state.tasksCount - 1 });
},
(error: any) => {
console.log(error);
}
);
}
}

@ -0,0 +1,23 @@
.dashboard-content-сard {
&__header {
}
&__content {
.card-cover {
img {
max-width: 300px;
}
}
.card-actions {
}
.card-jobs {
}
}
}

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import DashboardContent from './dashboard-content';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<DashboardContent />, div);
ReactDOM.unmountComponentAtNode(div);
});

@ -0,0 +1,129 @@
import React, { Component } from 'react';
import { Layout, Empty, Button, Col, Row } from 'antd';
import './dashboard-content.scss';
const { Content } = Layout;
interface DashboardContentAction {
id: number,
name: string,
trigger: Function,
}
class DashboardContent extends Component<any, any> {
hostUrl: string;
apiUrl: string;
actions: DashboardContentAction[];
constructor(props: any) {
super(props);
this.state = {};
this.hostUrl = 'http://localhost:7000';
this.apiUrl = 'http://localhost:7000/api/v1';
this.actions = [
// {
// id: 1,
// name: 'Dump annotation',
// trigger: () => {},
// },
// {
// id: 2,
// name: 'Upload annotation',
// trigger: () => {},
// },
// {
// id: 3,
// name: 'Update task',
// trigger: () => {},
// },
{
id: 4,
name: 'Delete task',
trigger: (task: any) => {
this.props.deleteTask(task);
},
},
];
}
render() {
return(
<>
{ this.props.tasks.length ? this.renderTasks() : this.renderPlaceholder() }
</>
);
}
private renderPlaceholder() {
return (
<Empty
description={
<span>
No tasks in this workspace yet...
</span>
}
>
<Button type="primary">Create a new task</Button>
</Empty>
)
}
private renderTasks() {
return(
<Content>
{
this.props.tasks.map(
(task: any) => (
<div className="dashboard-content-сard" key={ task.id }>
<Row className="dashboard-content-сard__header" type="flex">
<Col span={24}>
<h2>{ `${task.name}: ${task.mode}` }</h2>
</Col>
</Row>
<Row className="dashboard-content-сard__content" type="flex">
<Col className="card-cover" span={8}>
<img alt="Task cover" src={ `${this.apiUrl}/tasks/${task.id}/frames/0` } />
</Col>
<Col className="сard-actions" span={8}>
{
this.actions.map(
(action: DashboardContentAction) => (
<Row type="flex" key={ action.id }>
<Button type="primary" onClick={ () => action.trigger(task) }>
{ action.name }
</Button>
</Row>
)
)
}
</Col>
<Col className="сard-jobs" span={8}>
Jobs
{
task.jobs.map(
(job: any) => (
<Row type="flex" key={ job.id }>
<a href={`${this.hostUrl}?id=${job.id}`}>{`${this.hostUrl}?id=${job.id}`}</a>
</Row>
)
)
}
</Col>
</Row>
</div>
)
)
}
</Content>
);
}
}
export default DashboardContent;

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import DashboardFooter from './dashboard-footer';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<DashboardFooter />, div);
ReactDOM.unmountComponentAtNode(div);
});

@ -0,0 +1,25 @@
import React, { Component } from 'react';
import { Layout, Pagination } from 'antd';
import './dashboard-footer.scss';
const { Footer } = Layout;
class DashboardFooter extends Component<any, any> {
constructor(props: any) {
super(props);
this.state = {};
}
render() {
return(
<Footer>
<Pagination onChange={ this.props.onPageChange } total={ this.props.tasksCount } />
</Footer>
);
}
}
export default DashboardFooter;

@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import DashboardHeader from './dashboard-header';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<DashboardHeader />, div);
ReactDOM.unmountComponentAtNode(div);
});

@ -0,0 +1,72 @@
import React, { Component } from 'react';
import { Layout, Row, Col, Button, Input } from 'antd';
import './dashboard-header.scss';
const { Header } = Layout;
const { Search } = Input;
interface DashboardHeaderAction {
id: number,
name: string,
trigger: any,
}
class DashboardHeader extends Component<any, any> {
actions: DashboardHeaderAction[];
hostUrl: string;
constructor(props: any) {
super(props);
this.state = {};
this.hostUrl = 'http://localhost:7000';
this.actions = [
// {
// id: 1,
// name: 'Create task',
// trigger: this.props.onSearch,
// },
{
id: 2,
name: 'User guide',
trigger: this.openUserGuide,
},
];
}
render() {
return(
<Header>
<Row type="flex">
<Col span={8}>
Tasks
</Col>
<Col span={8}>
<Search placeholder="Search for tasks" onSearch={ query => this.props.onSearch(query) } enterButton />
</Col>
<Col span={8}>
{
this.actions.map(
(action: DashboardHeaderAction) => (
<Button type="primary" key={ action.id } onClick={ () => action.trigger() }>
{ action.name }
</Button>
)
)
}
</Col>
</Row>
</Header>
);
}
private openUserGuide = () => {
window.open(`${this.hostUrl}/documentation/user_guide.html`, '_blank')
}
}
export default DashboardHeader;

@ -3,13 +3,10 @@ import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
import './index.css';
import App from './components/app/App';
import './index.scss';
import App from './components/app/app';
ReactDOM.render(
<App />,
document.getElementById('root')
);
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.

Loading…
Cancel
Save