You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
442 lines
15 KiB
TypeScript
442 lines
15 KiB
TypeScript
// Copyright (C) 2020 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
import React from 'react';
|
|
|
|
import {
|
|
Row,
|
|
Col,
|
|
Tag,
|
|
Icon,
|
|
Modal,
|
|
Button,
|
|
notification,
|
|
} from 'antd';
|
|
|
|
import Text from 'antd/lib/typography/Text';
|
|
import Title from 'antd/lib/typography/Title';
|
|
|
|
import moment from 'moment';
|
|
|
|
import getCore from 'cvat-core';
|
|
import patterns from 'utils/validation-patterns';
|
|
import { getReposData, syncRepos } from 'utils/git-utils';
|
|
import UserSelector from './user-selector';
|
|
import LabelsEditorComponent from '../labels-editor/labels-editor';
|
|
|
|
const core = getCore();
|
|
|
|
interface Props {
|
|
previewImage: string;
|
|
taskInstance: any;
|
|
installedGit: boolean; // change to git repos url
|
|
registeredUsers: any[];
|
|
onTaskUpdate: (taskInstance: any) => void;
|
|
}
|
|
|
|
interface State {
|
|
name: string;
|
|
bugTracker: string;
|
|
repository: string;
|
|
repositoryStatus: string;
|
|
}
|
|
|
|
export default class DetailsComponent extends React.PureComponent<Props, State> {
|
|
private mounted: boolean;
|
|
private previewImageElement: HTMLImageElement;
|
|
private previewWrapperRef: React.RefObject<HTMLDivElement>;
|
|
|
|
constructor(props: Props) {
|
|
super(props);
|
|
|
|
const { taskInstance } = props;
|
|
|
|
this.mounted = false;
|
|
this.previewImageElement = new Image();
|
|
this.previewWrapperRef = React.createRef<HTMLDivElement>();
|
|
this.state = {
|
|
name: taskInstance.name,
|
|
bugTracker: taskInstance.bugTracker,
|
|
repository: '',
|
|
repositoryStatus: '',
|
|
};
|
|
}
|
|
|
|
public componentDidMount(): void {
|
|
const { taskInstance, previewImage } = this.props;
|
|
const { previewImageElement, previewWrapperRef } = this;
|
|
this.mounted = true;
|
|
|
|
previewImageElement.onload = () => {
|
|
const { height, width } = previewImageElement;
|
|
if (width > height) {
|
|
previewImageElement.style.width = '100%';
|
|
} else {
|
|
previewImageElement.style.height = '100%';
|
|
}
|
|
};
|
|
|
|
previewImageElement.src = previewImage;
|
|
previewImageElement.alt = 'Preview';
|
|
if (previewWrapperRef.current) {
|
|
previewWrapperRef.current.appendChild(previewImageElement);
|
|
}
|
|
|
|
getReposData(taskInstance.id)
|
|
.then((data): void => {
|
|
if (data !== null && this.mounted) {
|
|
if (data.status.error) {
|
|
notification.error({
|
|
message: 'Could not receive repository status',
|
|
description: data.status.error,
|
|
});
|
|
} else {
|
|
this.setState({
|
|
repositoryStatus: data.status.value,
|
|
});
|
|
}
|
|
|
|
this.setState({
|
|
repository: data.url,
|
|
});
|
|
}
|
|
}).catch((error): void => {
|
|
if (this.mounted) {
|
|
notification.error({
|
|
message: 'Could not receive repository status',
|
|
description: error.toString(),
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
public componentDidUpdate(prevProps: Props): void {
|
|
const { taskInstance } = this.props;
|
|
|
|
if (prevProps !== this.props) {
|
|
this.setState({
|
|
name: taskInstance.name,
|
|
bugTracker: taskInstance.bugTracker,
|
|
});
|
|
}
|
|
}
|
|
|
|
public componentWillUnmount(): void {
|
|
this.mounted = false;
|
|
}
|
|
|
|
private renderTaskName(): JSX.Element {
|
|
const { name } = this.state;
|
|
const {
|
|
taskInstance,
|
|
onTaskUpdate,
|
|
} = this.props;
|
|
|
|
return (
|
|
<Title
|
|
level={4}
|
|
editable={{
|
|
onChange: (value: string): void => {
|
|
this.setState({
|
|
name: value,
|
|
});
|
|
|
|
taskInstance.name = value;
|
|
onTaskUpdate(taskInstance);
|
|
},
|
|
}}
|
|
className='cvat-text-color'
|
|
>
|
|
{name}
|
|
</Title>
|
|
);
|
|
}
|
|
|
|
private renderPreview(): JSX.Element {
|
|
const { previewWrapperRef } = this;
|
|
|
|
// Add image on mount after get its width and height to fit it into wrapper
|
|
return (
|
|
<div ref={previewWrapperRef} className='cvat-task-preview-wrapper' />
|
|
);
|
|
}
|
|
|
|
private renderParameters(): JSX.Element {
|
|
const { taskInstance } = this.props;
|
|
const { overlap } = taskInstance;
|
|
const { segmentSize } = taskInstance;
|
|
const { imageQuality } = taskInstance;
|
|
const zOrder = taskInstance.zOrder.toString();
|
|
|
|
return (
|
|
<>
|
|
<Row type='flex' justify='start' align='middle'>
|
|
<Col span={12}>
|
|
<Text strong className='cvat-text-color'>Overlap size</Text>
|
|
<br />
|
|
<Text className='cvat-text-color'>{overlap}</Text>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Text strong className='cvat-text-color'>Segment size</Text>
|
|
<br />
|
|
<Text className='cvat-text-color'>{segmentSize}</Text>
|
|
</Col>
|
|
</Row>
|
|
<Row type='flex' justify='space-between' align='middle'>
|
|
<Col span={12}>
|
|
<Text strong className='cvat-text-color'>Image quality</Text>
|
|
<br />
|
|
<Text className='cvat-text-color'>{imageQuality}</Text>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Text strong className='cvat-text-color'>Z-order</Text>
|
|
<br />
|
|
<Text className='cvat-text-color'>{zOrder}</Text>
|
|
</Col>
|
|
</Row>
|
|
</>
|
|
);
|
|
}
|
|
|
|
private renderUsers(): JSX.Element {
|
|
const {
|
|
taskInstance,
|
|
registeredUsers,
|
|
onTaskUpdate,
|
|
} = this.props;
|
|
const owner = taskInstance.owner ? taskInstance.owner.username : null;
|
|
const assignee = taskInstance.assignee ? taskInstance.assignee.username : null;
|
|
const created = moment(taskInstance.createdDate).format('MMMM Do YYYY');
|
|
const assigneeSelect = (
|
|
<UserSelector
|
|
users={registeredUsers}
|
|
value={assignee}
|
|
onChange={
|
|
(value: string): void => {
|
|
let [userInstance] = registeredUsers
|
|
.filter((user: any) => user.username === value);
|
|
|
|
if (userInstance === undefined) {
|
|
userInstance = null;
|
|
}
|
|
|
|
taskInstance.assignee = userInstance;
|
|
onTaskUpdate(taskInstance);
|
|
}
|
|
}
|
|
/>
|
|
);
|
|
|
|
return (
|
|
<Row type='flex' justify='space-between' align='middle'>
|
|
<Col span={12}>
|
|
{ owner && (
|
|
<Text type='secondary'>
|
|
{`Created by ${owner} on ${created}`}
|
|
</Text>
|
|
)}
|
|
</Col>
|
|
<Col span={10}>
|
|
<Text type='secondary'>
|
|
Assigned to
|
|
{ assigneeSelect }
|
|
</Text>
|
|
</Col>
|
|
</Row>
|
|
);
|
|
}
|
|
|
|
private renderDatasetRepository(): JSX.Element | boolean {
|
|
const { taskInstance } = this.props;
|
|
const {
|
|
repository,
|
|
repositoryStatus,
|
|
} = this.state;
|
|
|
|
return (
|
|
!!repository
|
|
&& (
|
|
<Row>
|
|
<Col className='cvat-dataset-repository-url'>
|
|
<Text strong className='cvat-text-color'>Dataset Repository</Text>
|
|
<br />
|
|
<a href={repository} rel='noopener noreferrer' target='_blank'>{repository}</a>
|
|
{repositoryStatus === 'sync'
|
|
&& (
|
|
<Tag color='blue'>
|
|
<Icon type='check-circle' />
|
|
Synchronized
|
|
</Tag>
|
|
)}
|
|
{repositoryStatus === 'merged'
|
|
&& (
|
|
<Tag color='green'>
|
|
<Icon type='check-circle' />
|
|
Merged
|
|
</Tag>
|
|
)}
|
|
{repositoryStatus === 'syncing'
|
|
&& (
|
|
<Tag color='purple'>
|
|
<Icon type='loading' />
|
|
Syncing
|
|
</Tag>
|
|
)}
|
|
{repositoryStatus === '!sync'
|
|
&& (
|
|
<Tag
|
|
color='red'
|
|
onClick={(): void => {
|
|
this.setState({
|
|
repositoryStatus: 'syncing',
|
|
});
|
|
|
|
syncRepos(taskInstance.id).then((): void => {
|
|
if (this.mounted) {
|
|
this.setState({
|
|
repositoryStatus: 'sync',
|
|
});
|
|
}
|
|
}).catch((): void => {
|
|
if (this.mounted) {
|
|
this.setState({
|
|
repositoryStatus: '!sync',
|
|
});
|
|
}
|
|
});
|
|
}}
|
|
>
|
|
<Icon type='warning' />
|
|
Synchronize
|
|
</Tag>
|
|
)}
|
|
</Col>
|
|
</Row>
|
|
)
|
|
);
|
|
}
|
|
|
|
private renderBugTracker(): JSX.Element {
|
|
const {
|
|
taskInstance,
|
|
onTaskUpdate,
|
|
} = this.props;
|
|
const { bugTracker } = this.state;
|
|
|
|
let shown = false;
|
|
const onChangeValue = (value: string): void => {
|
|
if (value && !patterns.validateURL.pattern.test(value)) {
|
|
if (!shown) {
|
|
Modal.error({
|
|
title: `Could not update the task ${taskInstance.id}`,
|
|
content: 'Issue tracker is expected to be URL',
|
|
onOk: (() => {
|
|
shown = false;
|
|
}),
|
|
});
|
|
shown = true;
|
|
}
|
|
} else {
|
|
this.setState({
|
|
bugTracker: value,
|
|
});
|
|
|
|
taskInstance.bugTracker = value;
|
|
onTaskUpdate(taskInstance);
|
|
}
|
|
};
|
|
|
|
if (bugTracker) {
|
|
return (
|
|
<Row>
|
|
<Col>
|
|
<Text strong className='cvat-text-color'>Issue Tracker</Text>
|
|
<br />
|
|
<Text editable={{ onChange: onChangeValue }}>{bugTracker}</Text>
|
|
<Button
|
|
type='ghost'
|
|
size='small'
|
|
onClick={(): void => {
|
|
// false positive
|
|
// eslint-disable-next-line
|
|
window.open(bugTracker, '_blank');
|
|
}}
|
|
className='cvat-open-bug-tracker-button'
|
|
>
|
|
Open the issue
|
|
</Button>
|
|
</Col>
|
|
</Row>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Row>
|
|
<Col>
|
|
<Text strong className='cvat-text-color'>Issue Tracker</Text>
|
|
<br />
|
|
<Text editable={{ onChange: onChangeValue }}>Not specified</Text>
|
|
</Col>
|
|
</Row>
|
|
);
|
|
}
|
|
|
|
private renderLabelsEditor(): JSX.Element {
|
|
const {
|
|
taskInstance,
|
|
onTaskUpdate,
|
|
} = this.props;
|
|
|
|
return (
|
|
<Row>
|
|
<Col>
|
|
<LabelsEditorComponent
|
|
labels={taskInstance.labels.map(
|
|
(label: any): string => label.toJSON(),
|
|
)}
|
|
onSubmit={(labels: any[]): void => {
|
|
taskInstance.labels = labels
|
|
.map((labelData): any => new core.classes.Label(labelData));
|
|
onTaskUpdate(taskInstance);
|
|
}}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
);
|
|
}
|
|
|
|
public render(): JSX.Element {
|
|
return (
|
|
<div className='cvat-task-details'>
|
|
<Row type='flex' justify='start' align='middle'>
|
|
<Col>
|
|
{ this.renderTaskName() }
|
|
</Col>
|
|
</Row>
|
|
<Row type='flex' justify='space-between' align='top'>
|
|
<Col md={8} lg={7} xl={7} xxl={6}>
|
|
<Row type='flex' justify='start' align='middle'>
|
|
<Col span={24}>
|
|
{ this.renderPreview() }
|
|
</Col>
|
|
</Row>
|
|
<Row>
|
|
<Col>
|
|
{ this.renderParameters() }
|
|
</Col>
|
|
</Row>
|
|
</Col>
|
|
<Col md={16} lg={17} xl={17} xxl={18}>
|
|
{ this.renderUsers() }
|
|
{ this.renderBugTracker() }
|
|
{ this.renderDatasetRepository() }
|
|
{ this.renderLabelsEditor() }
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
);
|
|
}
|
|
}
|