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

// 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}
</>
);
}