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