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.
168 lines
6.5 KiB
TypeScript
168 lines
6.5 KiB
TypeScript
// Copyright (C) 2020 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
import './styles.scss';
|
|
import React, { useState, useEffect } from 'react';
|
|
import { useSelector, useDispatch } from 'react-redux';
|
|
|
|
import { CombinedState } from 'reducers/interfaces';
|
|
import { Canvas } from 'cvat-canvas/src/typescript/canvas';
|
|
|
|
import { commentIssueAsync, resolveIssueAsync, reopenIssueAsync } from 'actions/review-actions';
|
|
|
|
import CreateIssueDialog from './create-issue-dialog';
|
|
import HiddenIssueLabel from './hidden-issue-label';
|
|
import IssueDialog from './issue-dialog';
|
|
|
|
const scaleHandler = (canvasInstance: Canvas): void => {
|
|
const { geometry } = canvasInstance;
|
|
const createDialogs = window.document.getElementsByClassName('cvat-create-issue-dialog');
|
|
const hiddenIssues = window.document.getElementsByClassName('cvat-hidden-issue-label');
|
|
const issues = window.document.getElementsByClassName('cvat-issue-dialog');
|
|
for (const element of [...Array.from(createDialogs), ...Array.from(hiddenIssues), ...Array.from(issues)]) {
|
|
(element as HTMLSpanElement).style.transform = `scale(${1 / geometry.scale}) rotate(${-geometry.angle}deg)`;
|
|
}
|
|
};
|
|
|
|
export default function IssueAggregatorComponent(): JSX.Element | null {
|
|
const dispatch = useDispatch();
|
|
const [expandedIssue, setExpandedIssue] = useState<number | null>(null);
|
|
const frameIssues = useSelector((state: CombinedState): any[] => state.review.frameIssues);
|
|
const canvasInstance = useSelector((state: CombinedState): Canvas => state.annotation.canvas.instance);
|
|
const canvasIsReady = useSelector((state: CombinedState): boolean => state.annotation.canvas.ready);
|
|
const newIssuePosition = useSelector((state: CombinedState): number[] | null => state.review.newIssuePosition);
|
|
const issuesHidden = useSelector((state: CombinedState): any => state.review.issuesHidden);
|
|
const issueFetching = useSelector((state: CombinedState): number | null => state.review.fetching.issueId);
|
|
const issueLabels: JSX.Element[] = [];
|
|
const issueDialogs: JSX.Element[] = [];
|
|
|
|
useEffect(() => {
|
|
scaleHandler(canvasInstance);
|
|
});
|
|
|
|
useEffect(() => {
|
|
const regions = frameIssues.reduce((acc: Record<number, number[]>, issue: any): Record<number, number[]> => {
|
|
acc[issue.id] = issue.position;
|
|
return acc;
|
|
}, {});
|
|
|
|
if (newIssuePosition) {
|
|
regions[0] = newIssuePosition;
|
|
}
|
|
|
|
canvasInstance.setupIssueRegions(regions);
|
|
|
|
if (newIssuePosition) {
|
|
setExpandedIssue(null);
|
|
const element = window.document.getElementById('cvat_canvas_issue_region_0');
|
|
if (element) {
|
|
element.style.display = 'block';
|
|
}
|
|
}
|
|
}, [newIssuePosition]);
|
|
|
|
useEffect(() => {
|
|
const listener = (): void => scaleHandler(canvasInstance);
|
|
|
|
canvasInstance.html().addEventListener('canvas.zoom', listener);
|
|
canvasInstance.html().addEventListener('canvas.fit', listener);
|
|
|
|
return () => {
|
|
canvasInstance.html().removeEventListener('canvas.zoom', listener);
|
|
canvasInstance.html().removeEventListener('canvas.fit', listener);
|
|
};
|
|
}, []);
|
|
|
|
if (!canvasIsReady) {
|
|
return null;
|
|
}
|
|
|
|
const { geometry } = canvasInstance;
|
|
for (const issue of frameIssues) {
|
|
if (issuesHidden) break;
|
|
const issueResolved = !!issue.resolver;
|
|
const offset = 15;
|
|
const translated = issue.position.map((coord: number): number => coord + geometry.offset);
|
|
const minX = Math.min(...translated.filter((_: number, idx: number): boolean => idx % 2 === 0)) + offset;
|
|
const minY = Math.min(...translated.filter((_: number, idx: number): boolean => idx % 2 !== 0)) + offset;
|
|
const { id } = issue;
|
|
const highlight = (): void => {
|
|
const element = window.document.getElementById(`cvat_canvas_issue_region_${id}`);
|
|
if (element) {
|
|
element.style.display = 'block';
|
|
}
|
|
};
|
|
|
|
const blur = (): void => {
|
|
if (issueResolved) {
|
|
const element = window.document.getElementById(`cvat_canvas_issue_region_${id}`);
|
|
if (element) {
|
|
element.style.display = '';
|
|
}
|
|
}
|
|
};
|
|
|
|
if (expandedIssue === id) {
|
|
issueDialogs.push(
|
|
<IssueDialog
|
|
key={issue.id}
|
|
id={issue.id}
|
|
top={minY}
|
|
left={minX}
|
|
isFetching={issueFetching !== null}
|
|
comments={issue.comments}
|
|
resolved={issueResolved}
|
|
highlight={highlight}
|
|
blur={blur}
|
|
collapse={() => {
|
|
setExpandedIssue(null);
|
|
}}
|
|
resolve={() => {
|
|
dispatch(resolveIssueAsync(issue.id));
|
|
setExpandedIssue(null);
|
|
}}
|
|
reopen={() => {
|
|
dispatch(reopenIssueAsync(issue.id));
|
|
}}
|
|
comment={(message: string) => {
|
|
dispatch(commentIssueAsync(issue.id, message));
|
|
}}
|
|
/>,
|
|
);
|
|
} else if (issue.comments.length) {
|
|
issueLabels.push(
|
|
<HiddenIssueLabel
|
|
key={issue.id}
|
|
id={issue.id}
|
|
top={minY}
|
|
left={minX}
|
|
resolved={issueResolved}
|
|
message={issue.comments[issue.comments.length - 1].message}
|
|
highlight={highlight}
|
|
blur={blur}
|
|
onClick={() => {
|
|
setExpandedIssue(id);
|
|
}}
|
|
/>,
|
|
);
|
|
}
|
|
}
|
|
|
|
const translated = newIssuePosition ? newIssuePosition.map((coord: number): number => coord + geometry.offset) : [];
|
|
const createLeft = translated.length ?
|
|
Math.max(...translated.filter((_: number, idx: number): boolean => idx % 2 === 0)) :
|
|
null;
|
|
const createTop = translated.length ?
|
|
Math.min(...translated.filter((_: number, idx: number): boolean => idx % 2 !== 0)) :
|
|
null;
|
|
|
|
return (
|
|
<>
|
|
{createLeft !== null && createTop !== null && <CreateIssueDialog top={createTop} left={createLeft} />}
|
|
{issueDialogs}
|
|
{issueLabels}
|
|
</>
|
|
);
|
|
}
|