UI Enhancements (#985)

* Single import of basic styles
* A little bit redesigned header
* Specified min resolution 1280x768
* Getting a job instance
* Improved handling when task doesn't exist
main
Boris Sekachev 6 years ago committed by Nikita Manovich
parent 216416703a
commit f57586a03c

@ -15,27 +15,3 @@ $danger-icon-color: #FF4136;
$info-icon-color: #0074D9; $info-icon-color: #0074D9;
$monospaced-fonts-stack: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace; $monospaced-fonts-stack: Consolas,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New, monospace;
hr {
border: none;
border-top: 1px solid $border-color-1;
height: 1px;
}
.cvat-text-color {
color: $text-color;
}
.cvat-title {
font-weight: 400;
font-size: 21px;
color: $text-color;
padding-top: 5px;
}
#root {
width: 100%;
height: 100%;
min-height: 100%;
display: grid;
}

@ -3,12 +3,45 @@ import React from 'react';
import { import {
Layout, Layout,
Spin,
Result,
} from 'antd'; } from 'antd';
import AnnotationTopBarComponent from './top-bar/top-bar'; import AnnotationTopBarComponent from './top-bar/top-bar';
import StandardWorkspaceComponent from './standard-workspace/standard-workspace'; import StandardWorkspaceComponent from './standard-workspace/standard-workspace';
export default function AnnotationPageComponent(): JSX.Element { interface Props {
jobInstance: any | null | undefined;
fetching: boolean;
getJob(): void;
}
export default function AnnotationPageComponent(props: Props): JSX.Element {
const {
jobInstance,
fetching,
getJob,
} = props;
if (jobInstance === null) {
if (!fetching) {
getJob();
}
return <Spin size='large' className='cvat-spinner' />;
}
if (typeof (jobInstance) === 'undefined') {
return (
<Result
className='cvat-not-found'
status='404'
title='Sorry, but this job was not found'
subTitle='Please, be sure information you tried to get exist and you have access'
/>
);
}
return ( return (
<Layout className='cvat-annotation-page'> <Layout className='cvat-annotation-page'>
<AnnotationTopBarComponent /> <AnnotationTopBarComponent />

@ -14,46 +14,48 @@
.cvat-annotation-header-left-group { .cvat-annotation-header-left-group {
height: 100%; height: 100%;
> div { > div:first-child {
padding: 0px; filter: invert(0.9);
width: 54px; background: $background-color-1;
height: 54px; border-radius: 0px;
float: left; width: 70px;
text-align: center; }
}
> span { .cvat-annotation-header-button {
font-size: 10px; padding: 0px;
} width: 54px;
height: 54px;
float: left;
text-align: center;
user-select: none;
> i { > span {
transform: scale(0.8); font-size: 10px;
padding: 3px; }
}
&:hover > i { > i {
transform: scale(0.9); transform: scale(0.8);
} padding: 3px;
}
&:active > i { &:hover > i {
transform: scale(0.8); transform: scale(0.9);
} }
> * { &:active > i {
display: block; transform: scale(0.8);
line-height: 0px;
}
} }
> div:first-child { > * {
filter: invert(0.9); display: block;
background: $background-color-1; line-height: 0px;
border-radius: 0px;
width: 70px;
} }
} }
.cvat-annotation-header-player-group > div { .cvat-annotation-header-player-group > div {
height: 54px; height: 54px;
line-height: 0px;
} }
.cvat-annotation-header-player-buttons { .cvat-annotation-header-player-buttons {
@ -96,14 +98,16 @@
} }
.cvat-annotation-header-filename-wrapper { .cvat-annotation-header-filename-wrapper {
max-width: 180px; max-width: 300px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
user-select: none;
} }
.cvat-annotation-header-frame-selector { .cvat-annotation-header-frame-selector {
width: 5em; width: 5em;
margin-right: 0.5em; padding-right: 5px;
margin-left: 5px;
} }
.cvat-annotation-header-right-group { .cvat-annotation-header-right-group {

@ -34,25 +34,25 @@ export default function AnnotationPageComponent(): JSX.Element {
<Layout.Header className='cvat-annotation-page-header'> <Layout.Header className='cvat-annotation-page-header'>
<Row type='flex' justify='space-between'> <Row type='flex' justify='space-between'>
<Col className='cvat-annotation-header-left-group'> <Col className='cvat-annotation-header-left-group'>
<div> <div className='cvat-annotation-header-button'>
<Icon component={MainMenuIcon} /> <Icon component={MainMenuIcon} />
<span>Menu</span> <span>Menu</span>
</div> </div>
<div> <div className='cvat-annotation-header-button'>
<Icon component={SaveIcon} /> <Icon component={SaveIcon} />
<span>Save</span> <span>Save</span>
</div> </div>
<div> <div className='cvat-annotation-header-button'>
<Icon component={UndoIcon} /> <Icon component={UndoIcon} />
<span>Undo</span> <span>Undo</span>
</div> </div>
<div> <div className='cvat-annotation-header-button'>
<Icon component={RedoIcon} /> <Icon component={RedoIcon} />
<span>Redo</span> <span>Redo</span>
</div> </div>
</Col> </Col>
<Col className='cvat-annotation-header-player-group'> <Col className='cvat-annotation-header-player-group'>
<Row type='flex'> <Row type='flex' align='middle'>
<Col className='cvat-annotation-header-player-buttons'> <Col className='cvat-annotation-header-player-buttons'>
<Icon component={PlaycontrolFirstIcon} /> <Icon component={PlaycontrolFirstIcon} />
<Icon component={PlaycontrolBackJumpIcon} /> <Icon component={PlaycontrolBackJumpIcon} />
@ -68,26 +68,25 @@ export default function AnnotationPageComponent(): JSX.Element {
<Slider className='cvat-annotation-header-player-slider' tipFormatter={null} /> <Slider className='cvat-annotation-header-player-slider' tipFormatter={null} />
</Col> </Col>
</Row> </Row>
<Row type='flex' justify='space-between'> <Row type='flex' justify='space-around'>
<Col className='cvat-annotation-header-filename-wrapper'> <Col className='cvat-annotation-header-filename-wrapper'>
<Tooltip overlay='filename.png'> <Tooltip overlay='filename.png'>
<Text type='secondary'>filename.png</Text> <Text type='secondary'>filename.png</Text>
</Tooltip> </Tooltip>
</Col> </Col>
<Col>
<Input className='cvat-annotation-header-frame-selector' type='number' size='small' />
<Text type='secondary'>of 200</Text>
</Col>
</Row> </Row>
</Col> </Col>
<Col>
<Input className='cvat-annotation-header-frame-selector' type='number' />
</Col>
</Row> </Row>
</Col> </Col>
<Col className='cvat-annotation-header-right-group'> <Col className='cvat-annotation-header-right-group'>
<div> <div className='cvat-annotation-header-button'>
<Icon component={FullscreenIcon} /> <Icon component={FullscreenIcon} />
<span>Fullscreen</span> <span>Fullscreen</span>
</div> </div>
<div> <div className='cvat-annotation-header-button'>
<Icon component={InfoIcon} /> <Icon component={InfoIcon} />
<span>Info</span> <span>Info</span>
</div> </div>

@ -1,5 +1,5 @@
import 'antd/dist/antd.less'; import 'antd/dist/antd.less';
import '../base.scss'; import '../styles.scss';
import React from 'react'; import React from 'react';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import { import {
@ -262,7 +262,7 @@ export default class CVATApplication extends React.PureComponent<CVATAppProps> {
<Route exact path='/tasks' component={TasksPageContainer} /> <Route exact path='/tasks' component={TasksPageContainer} />
<Route exact path='/tasks/create' component={CreateTaskPageContainer} /> <Route exact path='/tasks/create' component={CreateTaskPageContainer} />
<Route exact path='/tasks/:id' component={TaskPageContainer} /> <Route exact path='/tasks/:id' component={TaskPageContainer} />
<Route exact path='/tasks/:id/jobs/:id' component={AnnotationPageContainer} /> <Route exact path='/tasks/:tid/jobs/:jid' component={AnnotationPageContainer} />
{ withModels { withModels
&& <Route exact path='/models' component={ModelsPageContainer} /> } && <Route exact path='/models' component={ModelsPageContainer} /> }
{ installedAutoAnnotation { installedAutoAnnotation
@ -289,7 +289,7 @@ export default class CVATApplication extends React.PureComponent<CVATAppProps> {
} }
return ( return (
<Spin size='large' style={{ margin: '25% 50%' }} /> <Spin size='large' className='cvat-spinner' />
); );
} }
} }

@ -407,7 +407,7 @@ export default class ModelRunnerModalComponent extends React.PureComponent<Props
visible visible
> >
{!modelsInitialized {!modelsInitialized
&& <Spin size='large' style={{ margin: '25% 50%' }} />} && <Spin size='large' className='cvat-spinner' />}
{modelsInitialized && this.renderContent()} {modelsInitialized && this.renderContent()}
</Modal> </Modal>
) )

@ -42,7 +42,7 @@ export default function ModelsPageComponent(props: Props): JSX.Element {
props.getModels(); props.getModels();
} }
return ( return (
<Spin size='large' style={{ margin: '25% 45%' }} /> <Spin size='large' className='cvat-spinner' />
); );
} }

@ -7,7 +7,7 @@ import {
Col, Col,
Row, Row,
Spin, Spin,
notification, Result,
} from 'antd'; } from 'antd';
import TopBarComponent from './top-bar'; import TopBarComponent from './top-bar';
@ -17,11 +17,11 @@ import ModelRunnerModalContainer from '../../containers/model-runner-dialog/mode
import { Task } from '../../reducers/interfaces'; import { Task } from '../../reducers/interfaces';
interface TaskPageComponentProps { interface TaskPageComponentProps {
task: Task | null; task: Task | null | undefined;
fetching: boolean; fetching: boolean;
deleteActivity: boolean | null; deleteActivity: boolean | null;
installedGit: boolean; installedGit: boolean;
onFetchTask: (tid: number) => void; getTask: () => void;
} }
type Props = TaskPageComponentProps & RouteComponentProps<{id: string}>; type Props = TaskPageComponentProps & RouteComponentProps<{id: string}>;
@ -38,41 +38,33 @@ class TaskPageComponent extends React.PureComponent<Props> {
if (deleteActivity) { if (deleteActivity) {
history.replace('/tasks'); history.replace('/tasks');
} }
if (this.attempts === 2) {
notification.warning({
message: 'Something wrong with the task. It cannot be fetched from the server',
});
}
} }
public render(): JSX.Element { public render(): JSX.Element {
const { const {
match,
task, task,
fetching, fetching,
onFetchTask, getTask,
} = this.props; } = this.props;
const { id } = match.params;
const fetchTask = !task;
if (fetchTask) { if (task === null) {
if (!fetching) { if (!fetching) {
if (!this.attempts) { getTask();
this.attempts++;
onFetchTask(+id);
} else {
this.attempts++;
}
} }
return ( return (
<Spin size='large' style={{ margin: '25% 50%' }} /> <Spin size='large' className='cvat-spinner' />
); );
} }
if (typeof (task) === 'undefined') { if (typeof (task) === 'undefined') {
return ( return (
<div> </div> <Result
className='cvat-not-found'
status='404'
title='Sorry, but this task was not found'
subTitle='Please, be sure information you tried to get exist and you have access'
/>
); );
} }

@ -207,7 +207,7 @@ class TasksPageComponent extends React.PureComponent<TasksPageProps & RouteCompo
if (tasksFetching) { if (tasksFetching) {
return ( return (
<Spin size='large' style={{ margin: '25% 45%' }} /> <Spin size='large' className='cvat-spinner' />
); );
} }

@ -1,9 +1,83 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { RouteComponentProps } from 'react-router';
import AnnotationPageComponent from '../../components/annotation-page/annotation-page'; import AnnotationPageComponent from '../../components/annotation-page/annotation-page';
import { getTasksAsync } from '../../actions/tasks-actions';
export default function AnnotationPageContainer() { import {
CombinedState,
Task,
} from '../../reducers/interfaces';
type OwnProps = RouteComponentProps<{
tid: string;
jid: string;
}>;
interface StateToProps {
jobInstance: any | null | undefined;
fetching: boolean;
}
interface DispatchToProps {
getJob(): void;
}
function mapStateToProps(state: CombinedState, own: OwnProps): StateToProps {
const { tasks } = state;
const {
gettingQuery,
current,
} = tasks;
const { params } = own.match;
const taskID = +params.tid;
const jobID = +params.jid;
const filteredTasks = current
.filter((_task: Task) => _task.instance.id === taskID);
const task = filteredTasks[0] || (gettingQuery.id === taskID || Number.isNaN(taskID)
? undefined : null);
const job = task ? task.instance.jobs
.filter((_job: any) => _job.id === jobID)[0] : task;
return {
jobInstance: job,
fetching: tasks.fetching,
};
}
function mapDispatchToProps(dispatch: any, own: OwnProps): DispatchToProps {
const { params } = own.match;
const taskID = +params.tid;
return {
getJob(): void {
dispatch(getTasksAsync({
id: taskID,
page: 1,
search: null,
owner: null,
assignee: null,
name: null,
status: null,
mode: null,
}));
},
};
}
function AnnotationPageContainer(props: StateToProps & DispatchToProps): JSX.Element {
return ( return (
<AnnotationPageComponent/> <AnnotationPageComponent {...props} />
); );
} }
export default withRouter(
connect(
mapStateToProps,
mapDispatchToProps,
)(AnnotationPageContainer),
);

@ -14,23 +14,29 @@ import {
type Props = RouteComponentProps<{id: string}>; type Props = RouteComponentProps<{id: string}>;
interface StateToProps { interface StateToProps {
task: Task | null; task: Task | null | undefined;
fetching: boolean; fetching: boolean;
deleteActivity: boolean | null; deleteActivity: boolean | null;
installedGit: boolean; installedGit: boolean;
} }
interface DispatchToProps { interface DispatchToProps {
onFetchTask: (tid: number) => void; getTask: () => void;
} }
function mapStateToProps(state: CombinedState, own: Props): StateToProps { function mapStateToProps(state: CombinedState, own: Props): StateToProps {
const { plugins } = state.plugins; const { plugins } = state.plugins;
const { deletes } = state.tasks.activities; const { tasks } = state;
const { gettingQuery } = tasks;
const { deletes } = tasks.activities;
const id = +own.match.params.id; const id = +own.match.params.id;
const filtered = state.tasks.current.filter((task) => task.instance.id === id); const filteredTasks = state.tasks.current
const task = filtered[0] || null; .filter((task) => task.instance.id === id);
const task = filteredTasks[0] || (gettingQuery.id === id || Number.isNaN(id)
? undefined : null);
let deleteActivity = null; let deleteActivity = null;
if (task && id in deletes.byTask) { if (task && id in deletes.byTask) {
@ -45,11 +51,13 @@ function mapStateToProps(state: CombinedState, own: Props): StateToProps {
}; };
} }
function mapDispatchToProps(dispatch: any): DispatchToProps { function mapDispatchToProps(dispatch: any, own: Props): DispatchToProps {
const id = +own.match.params.id;
return { return {
onFetchTask: (tid: number): void => { getTask: (): void => {
dispatch(getTasksAsync({ dispatch(getTasksAsync({
id: tid, id,
page: 1, page: 1,
search: null, search: null,
owner: null, owner: null,

@ -0,0 +1,34 @@
@import './base.scss';
hr {
border: none;
border-top: 1px solid $border-color-1;
height: 1px;
}
.cvat-spinner {
margin: 25% 50%;
}
.cvat-not-found {
margin: 10% 25%;
}
.cvat-text-color {
color: $text-color;
}
.cvat-title {
font-weight: 400;
font-size: 21px;
color: $text-color;
padding-top: 5px;
}
#root {
width: 100%;
height: 100%;
display: grid;
min-width: 1280px;
min-height: 768px;
}
Loading…
Cancel
Save