Task moving between projects (#3164)
parent
e0f10d7b7f
commit
a2df499f50
@ -0,0 +1,84 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { Col, Row } from 'antd/lib/grid';
|
||||||
|
import Tag from 'antd/lib/tag';
|
||||||
|
import Select from 'antd/lib/select';
|
||||||
|
import Checkbox from 'antd/lib/checkbox';
|
||||||
|
import { ArrowRightOutlined } from '@ant-design/icons';
|
||||||
|
import CVATTooltip from 'components/common/cvat-tooltip';
|
||||||
|
|
||||||
|
export interface LabelMapperItemValue {
|
||||||
|
labelId: number;
|
||||||
|
newLabelName: string | null;
|
||||||
|
clearAtrributes: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LabelMapperItemProps {
|
||||||
|
label: any;
|
||||||
|
projectLabels?: any[];
|
||||||
|
value: LabelMapperItemValue;
|
||||||
|
labelMappers: LabelMapperItemValue[];
|
||||||
|
onChange: (value: LabelMapperItemValue) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LabelMapperItem(props: LabelMapperItemProps): JSX.Element {
|
||||||
|
const {
|
||||||
|
label, value, onChange, projectLabels, labelMappers,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const labelNames = labelMappers.map((mapper) => mapper.newLabelName).filter((el) => el);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row className='cvat-move-task-label-mapper-item' align='middle'>
|
||||||
|
<Col span={6}>
|
||||||
|
{label.name.length > 12 ? (
|
||||||
|
<CVATTooltip overlay={label.name}>
|
||||||
|
<Tag color={label.color}>
|
||||||
|
{`${label.name.slice(0, 12)}...`}
|
||||||
|
</Tag>
|
||||||
|
</CVATTooltip>
|
||||||
|
) : (
|
||||||
|
<Tag color={label.color}>
|
||||||
|
{label.name}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
<ArrowRightOutlined />
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Select
|
||||||
|
disabled={typeof projectLabels === 'undefined'}
|
||||||
|
value={value.newLabelName || ''}
|
||||||
|
onChange={(_value) =>
|
||||||
|
onChange({
|
||||||
|
...value,
|
||||||
|
newLabelName: _value as string,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{projectLabels?.filter((_label) => (
|
||||||
|
!labelNames.includes(_label.name)
|
||||||
|
)).map((_label) => (
|
||||||
|
<Select.Option key={_label.id} value={_label.name}>
|
||||||
|
{_label.name}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</Col>
|
||||||
|
<Col>
|
||||||
|
<Checkbox
|
||||||
|
disabled
|
||||||
|
checked={value.clearAtrributes}
|
||||||
|
onChange={(_value) =>
|
||||||
|
onChange({
|
||||||
|
...value,
|
||||||
|
clearAtrributes: _value.target.checked,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Clear attributes
|
||||||
|
</Checkbox>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,163 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import './styles.scss';
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import Modal from 'antd/lib/modal';
|
||||||
|
import { Row, Col } from 'antd/lib/grid';
|
||||||
|
import Divider from 'antd/lib/divider';
|
||||||
|
import notification from 'antd/lib/notification';
|
||||||
|
import { QuestionCircleFilled } from '@ant-design/icons';
|
||||||
|
|
||||||
|
import ProjectSearch from 'components/create-task-page/project-search-field';
|
||||||
|
import CVATTooltip from 'components/common/cvat-tooltip';
|
||||||
|
import { CombinedState } from 'reducers/interfaces';
|
||||||
|
import { switchMoveTaskModalVisible, moveTaskToProjectAsync } from 'actions/tasks-actions';
|
||||||
|
import getCore from 'cvat-core-wrapper';
|
||||||
|
import LabelMapperItem, { LabelMapperItemValue } from './label-mapper-item';
|
||||||
|
|
||||||
|
const core = getCore();
|
||||||
|
|
||||||
|
export default function MoveTaskModal(): JSX.Element {
|
||||||
|
const visible = useSelector((state: CombinedState) => state.tasks.moveTask.modalVisible);
|
||||||
|
const task = useSelector((state: CombinedState) => {
|
||||||
|
const [taskInstance] = state.tasks.current.filter((_task) => _task.instance.id === state.tasks.moveTask.taskId);
|
||||||
|
return taskInstance?.instance;
|
||||||
|
});
|
||||||
|
const taskUpdating = useSelector((state: CombinedState) => state.tasks.updating);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const [projectId, setProjectId] = useState<number | null>(null);
|
||||||
|
const [project, setProject] = useState<any>(null);
|
||||||
|
const [labelMap, setLabelMap] = useState<{ [key: string]: LabelMapperItemValue }>({});
|
||||||
|
|
||||||
|
const initValues = (): void => {
|
||||||
|
if (task) {
|
||||||
|
const labelValues: { [key: string]: LabelMapperItemValue } = {};
|
||||||
|
task.labels.forEach((label: any) => {
|
||||||
|
labelValues[label.id] = {
|
||||||
|
labelId: label.id,
|
||||||
|
newLabelName: null,
|
||||||
|
clearAtrributes: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setLabelMap(labelValues);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onCancel = (): void => {
|
||||||
|
dispatch(switchMoveTaskModalVisible(false));
|
||||||
|
initValues();
|
||||||
|
setProject(null);
|
||||||
|
setProjectId(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitMove = async (): Promise<void> => {
|
||||||
|
if (!projectId) {
|
||||||
|
notification.error({
|
||||||
|
message: 'Project not selected',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Object.values(labelMap).every((map) => map.newLabelName !== null)) {
|
||||||
|
notification.error({
|
||||||
|
message: 'Not all labels mapped',
|
||||||
|
description: 'Please choose any action to not mapped labels first',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(
|
||||||
|
moveTaskToProjectAsync(
|
||||||
|
task,
|
||||||
|
projectId,
|
||||||
|
Object.values(labelMap).map((map) => ({
|
||||||
|
label_id: map.labelId,
|
||||||
|
new_label_name: map.newLabelName,
|
||||||
|
clear_attributes: map.clearAtrributes,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
onCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (projectId) {
|
||||||
|
core.projects.get({ id: projectId }).then((_project: any) => {
|
||||||
|
if (projectId) {
|
||||||
|
setProject(_project[0]);
|
||||||
|
const { labels } = _project[0];
|
||||||
|
const labelValues: { [key: string]: LabelMapperItemValue } = {};
|
||||||
|
Object.entries(labelMap).forEach(([id, label]) => {
|
||||||
|
const taskLabelName = task.labels.filter(
|
||||||
|
(_label: any) => (_label.id === label.labelId),
|
||||||
|
)[0].name;
|
||||||
|
const [autoNewLabel] = labels.filter((_label: any) => (
|
||||||
|
_label.name === taskLabelName
|
||||||
|
));
|
||||||
|
labelValues[id] = {
|
||||||
|
labelId: label.labelId,
|
||||||
|
newLabelName: autoNewLabel ? autoNewLabel.name : null,
|
||||||
|
clearAtrributes: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setLabelMap(labelValues);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setProject(null);
|
||||||
|
}
|
||||||
|
}, [projectId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initValues();
|
||||||
|
}, [task?.id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
onCancel={onCancel}
|
||||||
|
onOk={submitMove}
|
||||||
|
okButtonProps={{ disabled: taskUpdating }}
|
||||||
|
title={(
|
||||||
|
<span>
|
||||||
|
{`Move task ${task?.id} to project`}
|
||||||
|
{/* TODO: replace placeholder */}
|
||||||
|
<CVATTooltip title='Some moving proccess description here'>
|
||||||
|
<QuestionCircleFilled className='ant-typography-secondary' />
|
||||||
|
</CVATTooltip>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
className='cvat-task-move-modal'
|
||||||
|
>
|
||||||
|
<Row align='middle'>
|
||||||
|
<Col>Project:</Col>
|
||||||
|
<Col>
|
||||||
|
<ProjectSearch
|
||||||
|
value={projectId}
|
||||||
|
onSelect={setProjectId}
|
||||||
|
filter={(_project) => _project.id !== task?.projectId}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Divider orientation='left'>Label mapping</Divider>
|
||||||
|
{!!Object.keys(labelMap).length && !taskUpdating &&
|
||||||
|
task?.labels.map((label: any) => (
|
||||||
|
<LabelMapperItem
|
||||||
|
label={label}
|
||||||
|
key={label.id}
|
||||||
|
projectLabels={project?.labels}
|
||||||
|
value={labelMap[label.id]}
|
||||||
|
labelMappers={Object.values(labelMap)}
|
||||||
|
onChange={(value) => {
|
||||||
|
setLabelMap({
|
||||||
|
...labelMap,
|
||||||
|
[value.labelId]: value,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (C) 2021 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
@import 'base.scss';
|
||||||
|
|
||||||
|
.cvat-task-move-modal > .ant-modal-content {
|
||||||
|
> .ant-modal-body {
|
||||||
|
> div:nth-child(1) {
|
||||||
|
margin-bottom: $grid-unit-size * 2;
|
||||||
|
|
||||||
|
> div:nth-child(1) {
|
||||||
|
padding-right: $grid-unit-size * 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .ant-modal-header .anticon {
|
||||||
|
margin-left: $grid-unit-size;
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
color: $text-color-secondary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select {
|
||||||
|
margin: 0 $grid-unit-size;
|
||||||
|
width: $grid-unit-size * 25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cvat-move-task-label-mapper-item {
|
||||||
|
margin: $grid-unit-size * 2 0;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue