@abidibo/react-cam-roi 0.0.8 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +335 -39
- package/dist/Components/BoolField/BoolField.module.css +60 -0
- package/dist/Components/BoolField/index.d.ts +5 -0
- package/dist/Components/BoolField/index.js +13 -0
- package/dist/Components/Button/Button.module.css +27 -0
- package/dist/Components/Button/index.d.ts +8 -0
- package/dist/Components/Button/index.js +15 -0
- package/dist/Components/EnumField/EnumField.module.css +61 -0
- package/dist/Components/EnumField/index.d.ts +10 -0
- package/dist/Components/EnumField/index.js +16 -0
- package/dist/Components/IconButton/IconButton.module.css +8 -3
- package/dist/Components/IconButton/index.d.ts +1 -0
- package/dist/Components/IconButton/index.js +3 -3
- package/dist/Components/Modal/Modal.module.css +7 -0
- package/dist/Components/Modal/index.d.ts +2 -1
- package/dist/Components/Modal/index.js +3 -3
- package/dist/Components/NumberField/NumberField.module.css +60 -0
- package/dist/Components/NumberField/index.d.ts +3 -0
- package/dist/Components/NumberField/index.js +13 -0
- package/dist/Components/RoiEditor/Canvas.d.ts +2 -0
- package/dist/Components/RoiEditor/Canvas.js +20 -8
- package/dist/Components/RoiEditor/Hooks.d.ts +13 -0
- package/dist/Components/RoiEditor/Hooks.js +84 -5
- package/dist/Components/RoiEditor/ParameterField.d.ts +9 -0
- package/dist/Components/RoiEditor/ParameterField.js +27 -0
- package/dist/Components/RoiEditor/ParametersModalForm/ParametersModalForm.module.css +5 -0
- package/dist/Components/RoiEditor/ParametersModalForm/index.d.ts +10 -0
- package/dist/Components/RoiEditor/ParametersModalForm/index.js +31 -0
- package/dist/Components/RoiEditor/ShapesList.js +19 -6
- package/dist/Components/RoiEditor/Toolbar.js +13 -3
- package/dist/Components/RoiEditor/Toolbar.module.css +13 -1
- package/dist/Components/RoiEditor/Types.d.ts +52 -2
- package/dist/Components/RoiEditor/Utils.d.ts +18 -1
- package/dist/Components/RoiEditor/Utils.js +106 -0
- package/dist/Components/RoiEditor/index.d.ts +3 -1
- package/dist/Components/RoiEditor/index.js +44 -6
- package/dist/Components/TextField/TextField.module.css +61 -0
- package/dist/Components/TextField/index.d.ts +6 -0
- package/dist/Components/TextField/index.js +13 -0
- package/dist/Components/Typography/index.d.ts +1 -0
- package/dist/Components/Typography/index.js +2 -2
- package/dist/Icons/SaveIcon.d.ts +6 -0
- package/dist/Icons/SaveIcon.js +5 -0
- package/dist/Providers/EditorProvider.d.ts +11 -2
- package/dist/Providers/EditorProvider.js +16 -2
- package/dist/Providers/UiProvider.d.ts +24 -1
- package/dist/Providers/UiProvider.js +30 -1
- package/dist/Types.d.ts +10 -0
- package/dist/Types.js +1 -0
- package/dist/Utils/index.d.ts +1 -1
- package/dist/Utils/index.js +1 -1
- package/package.json +7 -2
- package/dist/Components/RoiEditor/ParametersModalForm.d.ts +0 -5
- package/dist/Components/RoiEditor/ParametersModalForm.js +0 -8
@@ -0,0 +1,61 @@
|
|
1
|
+
/*
|
2
|
+
.enum-field-wrapper {
|
3
|
+
margin-bottom: 2rem;
|
4
|
+
}
|
5
|
+
.enum-field-wrapper-light {
|
6
|
+
}
|
7
|
+
.enum-field-wrapper-dark {
|
8
|
+
}
|
9
|
+
*/
|
10
|
+
|
11
|
+
.enum-field {
|
12
|
+
border-radius: 0.25rem;
|
13
|
+
box-sizing: border-box;
|
14
|
+
padding: 0.5rem;
|
15
|
+
width: 100%;
|
16
|
+
}
|
17
|
+
.enum-field:focus-visible {
|
18
|
+
outline: none;
|
19
|
+
border: 1px solid #1976d2;
|
20
|
+
}
|
21
|
+
.enum-field-light {
|
22
|
+
background-color: #fff;
|
23
|
+
color: #333;
|
24
|
+
border: 1px solid #ccc;
|
25
|
+
}
|
26
|
+
.enum-field-dark {
|
27
|
+
background-color: #333;
|
28
|
+
border: 1px solid #666;
|
29
|
+
color: #fff;
|
30
|
+
}
|
31
|
+
.enum-field-error {
|
32
|
+
border: 1px solid #d32f2f;
|
33
|
+
}
|
34
|
+
.enum-field-label {
|
35
|
+
font-weight: bold;
|
36
|
+
display: block;
|
37
|
+
margin: 0 0 1rem 0;
|
38
|
+
}
|
39
|
+
/*
|
40
|
+
.text-fiel-label-light {
|
41
|
+
}
|
42
|
+
.enum-field-label-dark {
|
43
|
+
}
|
44
|
+
*/
|
45
|
+
.enum-field-label-error {
|
46
|
+
color: #d32f2f;
|
47
|
+
}
|
48
|
+
.enum-field-helper-text {
|
49
|
+
font-style: italic;
|
50
|
+
font-size: 0.9rem;
|
51
|
+
margin-top: 0.5rem;
|
52
|
+
}
|
53
|
+
/*
|
54
|
+
.enum-field-helper-text-light {
|
55
|
+
}
|
56
|
+
.enum-field-helper-text-dark {
|
57
|
+
}
|
58
|
+
*/
|
59
|
+
.enum-field-helper-text-error {
|
60
|
+
color: #d32f2f;
|
61
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { FieldProps } from '../../Types';
|
2
|
+
export type EnumOption = {
|
3
|
+
value: string | number;
|
4
|
+
label: string;
|
5
|
+
};
|
6
|
+
declare const EnumField: ({ onChange, value, label, helperText, error, options, disabled, required, multiple }: Omit<FieldProps<string | number | (string | number)[]>, "readOnly"> & {
|
7
|
+
options: EnumOption[];
|
8
|
+
multiple?: boolean;
|
9
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
10
|
+
export default EnumField;
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
2
|
+
import { useContext } from 'react';
|
3
|
+
import { UiContext } from '../../Providers/UiProvider';
|
4
|
+
import { css } from '../../Utils';
|
5
|
+
import styles from './EnumField.module.css';
|
6
|
+
const EnumField = ({ onChange, value, label, helperText, error, options, disabled = false, required = false, multiple = false }) => {
|
7
|
+
const { themeMode, Typography } = useContext(UiContext);
|
8
|
+
const handleChange = (e) => {
|
9
|
+
const selectedValues = Array.from(e.target.selectedOptions, (option) => {
|
10
|
+
return isNaN(Number(option.value)) ? option.value : Number(option.value);
|
11
|
+
});
|
12
|
+
onChange(multiple ? selectedValues : selectedValues[0]);
|
13
|
+
};
|
14
|
+
return (_jsxs("div", { className: css('enum-field-wrapper', styles, themeMode), children: [_jsx("label", { className: `${css('enum-field-label', styles, themeMode)} ${error ? css('enum-field-label-error', styles, null) : ''}`, children: _jsxs(Typography, { children: [label, required && ' *'] }) }), _jsxs("select", { className: `${css('enum-field', styles, themeMode)} ${error ? css('enum-field-error', styles, null) : ''}`, onChange: handleChange, value: value, disabled: disabled, multiple: multiple, children: [!required && _jsx("option", { value: '' }), options.map((option) => (_jsx("option", { value: option.value, children: option.label }, option.value)))] }), helperText && (_jsx(Typography, { component: 'div', className: `${css('enum-field-helper-text', styles, themeMode)} ${error ? css('enum-field-helper-text-error', styles, null) : ''}`, children: helperText }))] }));
|
15
|
+
};
|
16
|
+
export default EnumField;
|
@@ -1,4 +1,4 @@
|
|
1
|
-
.
|
1
|
+
.icon-button {
|
2
2
|
display: inline-block;
|
3
3
|
border-radius: 50%;
|
4
4
|
line-height: 0;
|
@@ -6,10 +6,15 @@
|
|
6
6
|
cursor: pointer;
|
7
7
|
}
|
8
8
|
|
9
|
-
.
|
9
|
+
.icon-button-light:hover {
|
10
10
|
background-color: rgba(0, 0, 0, 0.1);
|
11
11
|
}
|
12
12
|
|
13
|
-
.
|
13
|
+
.icon-button-dark:hover {
|
14
14
|
background-color: rgba(255, 255, 255, 0.1);
|
15
15
|
}
|
16
|
+
|
17
|
+
.icon-button-disabled {
|
18
|
+
cursor: not-allowed;
|
19
|
+
opacity: 0.5;
|
20
|
+
}
|
@@ -1,10 +1,10 @@
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
2
2
|
import { useContext } from 'react';
|
3
|
+
import { UiContext } from '../../Providers/UiProvider';
|
3
4
|
import { css } from '../../Utils';
|
4
5
|
import styles from './IconButton.module.css';
|
5
|
-
|
6
|
-
const IconButton = ({ children, onClick }) => {
|
6
|
+
const IconButton = ({ children, disabled, onClick }) => {
|
7
7
|
const { themeMode } = useContext(UiContext);
|
8
|
-
return (_jsx("div", { className: css('
|
8
|
+
return (_jsx("div", { className: `${css('icon-button', styles, themeMode)} ${disabled ? css('icon-button-disabled', styles, themeMode) : ''}`, onClick: disabled ? undefined : onClick, children: children }));
|
9
9
|
};
|
10
10
|
export default IconButton;
|
@@ -3,7 +3,8 @@ export type ModalProps = {
|
|
3
3
|
isOpen: boolean;
|
4
4
|
onClose: () => void;
|
5
5
|
title: string;
|
6
|
-
|
6
|
+
maxWidth: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
7
|
+
onSubmit?: () => void;
|
7
8
|
};
|
8
9
|
declare const Modal: React.FC<PropsWithChildren<ModalProps>>;
|
9
10
|
export default Modal;
|
@@ -5,12 +5,12 @@ import CloseIcon from '../../Icons/CloseIcon';
|
|
5
5
|
import { UiContext } from '../../Providers/UiProvider';
|
6
6
|
import { css } from '../../Utils';
|
7
7
|
import styles from './Modal.module.css';
|
8
|
-
const Modal = ({ isOpen, onClose, children, title,
|
9
|
-
const { themeMode, IconButton, Typography } = useContext(UiContext);
|
8
|
+
const Modal = ({ isOpen, onClose, children, title, maxWidth, onSubmit }) => {
|
9
|
+
const { themeMode, IconButton, Typography, Button, strings } = useContext(UiContext);
|
10
10
|
const iconColor = themeMode === 'light' ? 'black' : 'white';
|
11
11
|
if (!isOpen) {
|
12
12
|
return null;
|
13
13
|
}
|
14
|
-
return createPortal(_jsx("div", { className: css('modal-overlay', styles, themeMode), children: _jsxs("div", { className: `${css('modal', styles, themeMode)} ${css(`modal-${
|
14
|
+
return createPortal(_jsx("div", { className: css('modal-overlay', styles, themeMode), children: _jsxs("div", { className: `${css('modal', styles, themeMode)} ${css(`modal-${maxWidth}`, styles, themeMode)}`, children: [_jsxs("div", { className: css('modal-header', styles, themeMode), children: [_jsx(Typography, { component: 'h6', className: css('modal-title', styles, themeMode), children: title }), _jsx(IconButton, { onClick: onClose, children: _jsx(CloseIcon, { color: iconColor }) })] }), children, _jsxs("div", { className: css('modal-footer', styles, themeMode), children: [_jsx(Button, { onClick: onClose, children: strings.cancel }), onSubmit && _jsx(Button, { primary: true, onClick: onSubmit, children: strings.save })] })] }) }), document.body);
|
15
15
|
};
|
16
16
|
export default Modal;
|
@@ -0,0 +1,60 @@
|
|
1
|
+
/*
|
2
|
+
.number-field-wrapper {
|
3
|
+
}
|
4
|
+
.number-field-wrapper-light {
|
5
|
+
}
|
6
|
+
.number-field-wrapper-dark {
|
7
|
+
}
|
8
|
+
*/
|
9
|
+
|
10
|
+
.number-field {
|
11
|
+
border-radius: 0.25rem;
|
12
|
+
box-sizing: border-box;
|
13
|
+
padding: 0.5rem;
|
14
|
+
width: 100%;
|
15
|
+
}
|
16
|
+
.number-field:focus-visible {
|
17
|
+
outline: none;
|
18
|
+
border: 1px solid #1976d2;
|
19
|
+
}
|
20
|
+
.number-field-light {
|
21
|
+
background-color: #fff;
|
22
|
+
color: #333;
|
23
|
+
border: 1px solid #ccc;
|
24
|
+
}
|
25
|
+
.number-field-dark {
|
26
|
+
background-color: #333;
|
27
|
+
border: 1px solid #666;
|
28
|
+
color: #fff;
|
29
|
+
}
|
30
|
+
.number-field-error {
|
31
|
+
border: 1px solid #d32f2f;
|
32
|
+
}
|
33
|
+
.number-field-label {
|
34
|
+
font-weight: bold;
|
35
|
+
display: block;
|
36
|
+
margin: 0 0 1rem 0;
|
37
|
+
}
|
38
|
+
/*
|
39
|
+
.text-fiel-label-light {
|
40
|
+
}
|
41
|
+
.number-field-label-dark {
|
42
|
+
}
|
43
|
+
*/
|
44
|
+
.number-field-label-error {
|
45
|
+
color: #d32f2f;
|
46
|
+
}
|
47
|
+
.number-field-helper-text {
|
48
|
+
font-style: italic;
|
49
|
+
font-size: 0.9rem;
|
50
|
+
padding-top: 0.5rem;
|
51
|
+
}
|
52
|
+
/*
|
53
|
+
.number-field-helper-text-light {
|
54
|
+
}
|
55
|
+
.number-field-helper-text-dark {
|
56
|
+
}
|
57
|
+
*/
|
58
|
+
.number-field-helper-text-error {
|
59
|
+
color: #d32f2f;
|
60
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
2
|
+
import { useContext } from 'react';
|
3
|
+
import { UiContext } from '../../Providers/UiProvider';
|
4
|
+
import { css } from '../../Utils';
|
5
|
+
import styles from './NumberField.module.css';
|
6
|
+
const NumberField = ({ onChange, value, label, required, helperText, error }) => {
|
7
|
+
const { themeMode, Typography } = useContext(UiContext);
|
8
|
+
const handleChange = (e) => {
|
9
|
+
onChange(parseFloat(e.target.value));
|
10
|
+
};
|
11
|
+
return (_jsxs("div", { className: css('number-field-wrapper', styles, themeMode), children: [_jsx("label", { className: `${css('number-field-label', styles, themeMode)} ${error ? css('number-field-label-error', styles, null) : ''}`, children: _jsxs(Typography, { children: [label, required && ' *'] }) }), _jsx("input", { type: 'number', className: `${css('number-field', styles, themeMode)} ${error ? css('number-field-error', styles, null) : ''}`, onChange: handleChange, value: value !== null && value !== void 0 ? value : '' }), helperText && (_jsx(Typography, { component: 'div', className: `${css('number-field-helper-text', styles, themeMode)} ${error ? css('number-field-helper-text-error', styles, null) : ''}`, children: helperText }))] }));
|
12
|
+
};
|
13
|
+
export default NumberField;
|
@@ -1,16 +1,28 @@
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
2
|
-
import { useRef, useEffect } from 'react';
|
3
2
|
import * as fabric from 'fabric';
|
4
|
-
import {
|
5
|
-
|
3
|
+
import { useRef, useEffect, useState, useContext } from 'react';
|
4
|
+
import { useEditorContext } from '../../Providers/EditorProvider';
|
5
|
+
import { initCanvasData, useDispatcherEvents, useTool } from './Hooks';
|
6
|
+
import { UiContext } from '../../Providers/UiProvider';
|
7
|
+
const Canvas = ({ canvasSize, initialData }) => {
|
8
|
+
const { metadata, setMetadata, addShapes } = useEditorContext();
|
9
|
+
const { enableLogs } = useContext(UiContext);
|
10
|
+
const [initialized, setInitialized] = useState(false);
|
6
11
|
const canvasRef = useRef(null);
|
7
12
|
useTool(canvasRef.current);
|
8
13
|
useDispatcherEvents(canvasRef.current);
|
9
14
|
useEffect(() => {
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
+
if (canvasSize.width !== 0 && canvasSize.height !== 0 && !initialized) {
|
16
|
+
canvasRef.current = new fabric.Canvas('react-cam-roi-canvas');
|
17
|
+
canvasRef.current.setDimensions({ width: canvasSize.width, height: canvasSize.height });
|
18
|
+
initCanvasData(canvasRef, addShapes, metadata, setMetadata, initialData, enableLogs);
|
19
|
+
setInitialized(true);
|
20
|
+
return () => {
|
21
|
+
var _a;
|
22
|
+
(_a = canvasRef.current) === null || _a === void 0 ? void 0 : _a.dispose();
|
23
|
+
};
|
24
|
+
}
|
25
|
+
}, [canvasSize.width, canvasSize.height]); // eslint-disable-line
|
26
|
+
return (_jsx("canvas", { id: "react-cam-roi-canvas", style: { width: `${canvasSize.width}px`, height: `${canvasSize.height}px` } }));
|
15
27
|
};
|
16
28
|
export default Canvas;
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import * as fabric from 'fabric';
|
2
|
+
import { Metadata, Output, OutputParameter, Shape, ShapeType } from './Types';
|
2
3
|
export declare const useImageSize: (imageUrl: string) => {
|
3
4
|
imageSize: {
|
4
5
|
width: number;
|
@@ -18,5 +19,17 @@ export declare const useCanvasSize: (imageUrl: string) => {
|
|
18
19
|
wrapperRef: import("react").RefObject<HTMLDivElement>;
|
19
20
|
isReady: boolean;
|
20
21
|
};
|
22
|
+
export declare const initCanvasData: (canvasRef: React.MutableRefObject<fabric.Canvas | null>, addShapes: (shapes: {
|
23
|
+
id: string;
|
24
|
+
type: ShapeType;
|
25
|
+
shape: Shape;
|
26
|
+
}[]) => void, metadata: Metadata, setMetadata: (v: Metadata) => void, initialData?: Output, enableLogs?: boolean) => void;
|
21
27
|
export declare const useTool: (canvas: fabric.Canvas | null) => void;
|
22
28
|
export declare const useDispatcherEvents: (canvas: fabric.Canvas | null) => void;
|
29
|
+
export declare const useParametersForm: (parameters: OutputParameter[]) => {
|
30
|
+
fields: Record<string, unknown>;
|
31
|
+
setField: <T>(key: string) => (value: T) => void;
|
32
|
+
setFields: import("react").Dispatch<import("react").SetStateAction<Record<string, unknown>>>;
|
33
|
+
errors: Record<string, string>;
|
34
|
+
setErrors: import("react").Dispatch<import("react").SetStateAction<Record<string, string>>>;
|
35
|
+
};
|
@@ -1,6 +1,9 @@
|
|
1
|
+
import * as fabric from 'fabric';
|
1
2
|
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
2
4
|
import { useEditorContext } from '../../Providers/EditorProvider';
|
3
5
|
import { UiContext } from '../../Providers/UiProvider';
|
6
|
+
import { log } from '../../Utils';
|
4
7
|
import Dispatcher from '../../Utils/Dispatcher';
|
5
8
|
import { copyPolygon, handleDoubleClickPolygon, handleMouseDownPolygon, handleMouseMovePolygon } from './Polygon';
|
6
9
|
import { copyPolyline, handleDoubleClickPolyline, handleMouseDownPolyline, handleMouseMovePolyline } from './Polyline';
|
@@ -40,16 +43,78 @@ export const useCanvasSize = (imageUrl) => {
|
|
40
43
|
};
|
41
44
|
if (imageSize.width > 0 && wrapperRef.current) {
|
42
45
|
update();
|
43
|
-
// observe ref for resizing event and in case update canvas dimensions
|
44
|
-
const resizeObserver = new ResizeObserver(() => {
|
45
|
-
update();
|
46
|
-
});
|
47
|
-
resizeObserver.observe(wrapperRef.current);
|
48
46
|
}
|
49
47
|
}
|
50
48
|
}, [imageSize, wrapperRef.current]); // eslint-disable-line
|
51
49
|
return { imageSize, canvasSize, wrapperRef, isReady };
|
52
50
|
};
|
51
|
+
export const initCanvasData = (canvasRef, addShapes, metadata, setMetadata, initialData, enableLogs) => {
|
52
|
+
log('info', enableLogs !== null && enableLogs !== void 0 ? enableLogs : false, 'Loading initial shapes data', initialData, canvasRef.current);
|
53
|
+
if (initialData === null || initialData === void 0 ? void 0 : initialData.rois) {
|
54
|
+
const m = [];
|
55
|
+
const s = [];
|
56
|
+
initialData.rois.forEach((r) => {
|
57
|
+
var _a, _b, _c;
|
58
|
+
log('info', enableLogs !== null && enableLogs !== void 0 ? enableLogs : false, 'Loading initial shape', r);
|
59
|
+
const id = uuidv4();
|
60
|
+
let shape;
|
61
|
+
switch (r.type) {
|
62
|
+
case "rect" /* ToolEnum.Rectangle */:
|
63
|
+
shape = new fabric.Rect({
|
64
|
+
left: r.shape.left,
|
65
|
+
top: r.shape.top,
|
66
|
+
originX: 'left',
|
67
|
+
originY: 'top',
|
68
|
+
width: r.shape.width,
|
69
|
+
height: r.shape.height,
|
70
|
+
fill: 'transparent',
|
71
|
+
stroke: r.shape.color,
|
72
|
+
strokeWidth: 2,
|
73
|
+
strokeUniform: true,
|
74
|
+
selectable: false,
|
75
|
+
hasControls: true,
|
76
|
+
hoverCursor: 'default',
|
77
|
+
id,
|
78
|
+
});
|
79
|
+
(_a = canvasRef.current) === null || _a === void 0 ? void 0 : _a.add(shape);
|
80
|
+
break;
|
81
|
+
case "polygon" /* ToolEnum.Polygon */:
|
82
|
+
shape = new fabric.Polygon(r.shape.points, {
|
83
|
+
top: r.shape.top + 10,
|
84
|
+
left: r.shape.left + 10,
|
85
|
+
fill: 'transparent',
|
86
|
+
stroke: r.shape.color,
|
87
|
+
strokeWidth: 2,
|
88
|
+
selectable: false,
|
89
|
+
hasControls: true,
|
90
|
+
hoverCursor: 'default',
|
91
|
+
// @ts-expect-error id is not included in types but the property is added and it works
|
92
|
+
id,
|
93
|
+
});
|
94
|
+
(_b = canvasRef.current) === null || _b === void 0 ? void 0 : _b.add(shape);
|
95
|
+
break;
|
96
|
+
case "polyline" /* ToolEnum.Polyline */:
|
97
|
+
shape = new fabric.Polyline(r.shape.points, {
|
98
|
+
top: r.shape.top + 10,
|
99
|
+
left: r.shape.left + 10,
|
100
|
+
fill: 'transparent',
|
101
|
+
stroke: r.shape.color,
|
102
|
+
strokeWidth: 2,
|
103
|
+
selectable: false,
|
104
|
+
hasControls: true,
|
105
|
+
hoverCursor: 'default',
|
106
|
+
id,
|
107
|
+
});
|
108
|
+
(_c = canvasRef.current) === null || _c === void 0 ? void 0 : _c.add(shape);
|
109
|
+
break;
|
110
|
+
}
|
111
|
+
m.push({ id, parameters: r.parameters });
|
112
|
+
s.push({ id, type: r.type, shape });
|
113
|
+
});
|
114
|
+
addShapes(s);
|
115
|
+
setMetadata(Object.assign(Object.assign({}, metadata), { rois: m }));
|
116
|
+
}
|
117
|
+
};
|
53
118
|
export const useTool = (canvas) => {
|
54
119
|
const { configuration, activeTool, activeColor, shapes, addShape } = useEditorContext();
|
55
120
|
const { notify, strings } = useContext(UiContext);
|
@@ -239,3 +304,17 @@ export const useDispatcherEvents = (canvas) => {
|
|
239
304
|
};
|
240
305
|
}, [setActiveTool, canvas, addShape, configuration, shapes, notify, strings]);
|
241
306
|
};
|
307
|
+
export const useParametersForm = (parameters) => {
|
308
|
+
const [errors, setErrors] = useState({});
|
309
|
+
const [fields, setFields] = useState(parameters.reduce((acc, p) => (Object.assign(Object.assign({}, acc), { [p.codename]: p.value })), {}));
|
310
|
+
const setField = (key) => (value) => {
|
311
|
+
setFields(Object.assign(Object.assign({}, fields), { [key]: value }));
|
312
|
+
};
|
313
|
+
return {
|
314
|
+
fields,
|
315
|
+
setField,
|
316
|
+
setFields,
|
317
|
+
errors,
|
318
|
+
setErrors,
|
319
|
+
};
|
320
|
+
};
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { ConfigurationParameter } from './Types';
|
2
|
+
export type ParameterFieldProps<T> = {
|
3
|
+
value: T;
|
4
|
+
onChange: (value: T) => void;
|
5
|
+
parameter: ConfigurationParameter;
|
6
|
+
errors: Record<string, string>;
|
7
|
+
};
|
8
|
+
declare const ParameterField: <T>({ value, onChange, parameter, errors }: ParameterFieldProps<T>) => import("react/jsx-runtime").JSX.Element | null;
|
9
|
+
export default ParameterField;
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
2
|
+
import { useContext } from 'react';
|
3
|
+
import { UiContext } from '../../Providers/UiProvider';
|
4
|
+
const ParameterField = ({ value, onChange, parameter, errors }) => {
|
5
|
+
var _a, _b;
|
6
|
+
const { TextField, NumberField, BoolField, EnumField, strings } = useContext(UiContext);
|
7
|
+
const props = {
|
8
|
+
required: parameter.required,
|
9
|
+
label: `${parameter.label}${parameter.unit ? ` (${parameter.unit})` : ''}`,
|
10
|
+
error: !!errors[parameter.codename],
|
11
|
+
helperText: errors[parameter.codename]
|
12
|
+
? strings[errors[parameter.codename]]
|
13
|
+
: parameter.description,
|
14
|
+
};
|
15
|
+
switch (parameter.type) {
|
16
|
+
case 'string':
|
17
|
+
return ((_a = parameter.options) === null || _a === void 0 ? void 0 : _a.length) ? (_jsx(EnumField, Object.assign({ value: value, onChange: (v) => onChange(v), options: parameter.options, multiple: parameter.multiple }, props))) : (_jsx(TextField, Object.assign({ type: "text", value: value, onChange: (v) => onChange(v) }, props)));
|
18
|
+
case 'int':
|
19
|
+
case 'float':
|
20
|
+
return ((_b = parameter.options) === null || _b === void 0 ? void 0 : _b.length) ? (_jsx(EnumField, Object.assign({ value: value, onChange: (v) => onChange(v), options: parameter.options, multiple: parameter.multiple }, props))) : (_jsx(NumberField, Object.assign({ value: value, onChange: (v) => onChange(v) }, props)));
|
21
|
+
case 'bool':
|
22
|
+
return _jsx(BoolField, Object.assign({ value: value, onChange: (v) => onChange(v) }, props));
|
23
|
+
default:
|
24
|
+
return null;
|
25
|
+
}
|
26
|
+
};
|
27
|
+
export default ParameterField;
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import { ConfigurationParameter, OutputParameter } from '../Types';
|
2
|
+
export type ParametersModalFormProps = {
|
3
|
+
onClose: () => void;
|
4
|
+
title: string;
|
5
|
+
parameters: ConfigurationParameter[];
|
6
|
+
data: OutputParameter[];
|
7
|
+
onSubmit: (data: OutputParameter[]) => void;
|
8
|
+
};
|
9
|
+
declare const ParametersModalForm: React.FC<ParametersModalFormProps>;
|
10
|
+
export default ParametersModalForm;
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
2
|
+
import { useContext } from 'react';
|
3
|
+
import { UiContext } from '../../../Providers/UiProvider';
|
4
|
+
import { css } from '../../../Utils';
|
5
|
+
import { useParametersForm } from '../Hooks';
|
6
|
+
import ParameterField from '../ParameterField';
|
7
|
+
import styles from './ParametersModalForm.module.css';
|
8
|
+
import { validateParametersForm } from '../Utils';
|
9
|
+
const ParametersModalForm = ({ title, onClose, parameters, data, onSubmit }) => {
|
10
|
+
const { Modal } = useContext(UiContext);
|
11
|
+
const { fields, setField, errors, setErrors } = useParametersForm(data);
|
12
|
+
const handleSubmit = () => {
|
13
|
+
if (validateParametersForm(parameters, fields, setErrors)) {
|
14
|
+
onSubmit([...parameters.map((p) => ({ codename: p.codename, value: fields[p.codename] }))]);
|
15
|
+
}
|
16
|
+
};
|
17
|
+
return (_jsx(Modal, { onClose: onClose, title: title, isOpen: true, maxWidth: "sm", onSubmit: handleSubmit, children: _jsx("div", { className: css('form', styles, null), children: parameters.map((parameter) => {
|
18
|
+
switch (parameter.type) {
|
19
|
+
case 'string':
|
20
|
+
return (_jsx(ParameterField, { value: String(fields[parameter.codename]), onChange: setField(parameter.codename), parameter: parameter, errors: errors }, parameter.codename));
|
21
|
+
case 'int':
|
22
|
+
case 'float':
|
23
|
+
return (_jsx(ParameterField, { value: fields[parameter.codename], onChange: setField(parameter.codename), parameter: parameter, errors: errors }, parameter.codename));
|
24
|
+
case 'bool':
|
25
|
+
return (_jsx(ParameterField, { value: fields[parameter.codename], onChange: setField(parameter.codename), parameter: parameter, errors: errors }, parameter.codename));
|
26
|
+
default:
|
27
|
+
return null;
|
28
|
+
}
|
29
|
+
}) }) }));
|
30
|
+
};
|
31
|
+
export default ParametersModalForm;
|
@@ -1,14 +1,17 @@
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
2
2
|
import { useContext, useEffect, useState } from 'react';
|
3
3
|
import { useEditorContext } from '../../Providers/EditorProvider';
|
4
4
|
import { UiContext } from '../../Providers/UiProvider';
|
5
5
|
import { css } from '../../Utils';
|
6
6
|
import Dispatcher from '../../Utils/Dispatcher';
|
7
|
+
import ParametersModalForm from './ParametersModalForm';
|
7
8
|
import styles from './ShapesList.module.css';
|
8
9
|
const ShapesList = () => {
|
9
|
-
|
10
|
-
const {
|
10
|
+
var _a, _b, _c, _d, _e, _f;
|
11
|
+
const { strings, Typography, IconButton, DeleteIcon, AnnotateIcon, SelectIcon, CopyIcon, themeMode } = useContext(UiContext);
|
12
|
+
const { shapes, removeShape, configuration, metadata, setMetadata } = useEditorContext();
|
11
13
|
const [selected, setSelected] = useState([]);
|
14
|
+
const [form, setForm] = useState({ isOpen: false, shapeId: '' });
|
12
15
|
useEffect(() => {
|
13
16
|
const setSelectedShapes = (_, event) => { var _a; return setSelected((_a = event === null || event === void 0 ? void 0 : event.map((s) => s.id)) !== null && _a !== void 0 ? _a : []); };
|
14
17
|
Dispatcher.register('canvas:shapeSelected', setSelectedShapes);
|
@@ -26,9 +29,19 @@ const ShapesList = () => {
|
|
26
29
|
const handleSelectShape = (id) => () => {
|
27
30
|
Dispatcher.emit('canvas:selectShape', id);
|
28
31
|
};
|
32
|
+
const handleEditShapeMetadata = (id) => () => {
|
33
|
+
setForm({ isOpen: true, shapeId: id });
|
34
|
+
};
|
35
|
+
const handleSubmitMetadata = (shapeId) => (data) => {
|
36
|
+
setMetadata(Object.assign(Object.assign({}, metadata), { rois: [
|
37
|
+
...metadata.rois.filter((r) => r.id !== shapeId),
|
38
|
+
{ id: shapeId, parameters: data },
|
39
|
+
] }));
|
40
|
+
setForm({ isOpen: false, shapeId: '' });
|
41
|
+
};
|
29
42
|
const iconColor = themeMode === 'light' ? 'black' : 'white';
|
30
|
-
return (_jsxs("table", { className: css('shapes-table', styles, themeMode), children: [Object.keys(shapes).length > 0 && (_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: _jsx(Typography, { children: strings.id }) }), _jsx("th", { children: _jsx(Typography, { children: strings.type }) }), _jsx("th", {})] }) })), _jsx("tbody", { children: Object.keys(shapes).map((id) => {
|
31
|
-
|
32
|
-
|
43
|
+
return (_jsxs(_Fragment, { children: [_jsxs("table", { className: css('shapes-table', styles, themeMode), children: [Object.keys(shapes).length > 0 && (_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: _jsx(Typography, { style: { fontWeight: 'bold' }, children: strings.id }) }), _jsx("th", { children: _jsx(Typography, { style: { fontWeight: 'bold' }, children: strings.type }) }), _jsx("th", {})] }) })), _jsx("tbody", { children: Object.keys(shapes).map((id) => {
|
44
|
+
return (_jsxs("tr", { className: selected.indexOf(id) > -1 ? css('shapes-row-selected', styles, themeMode) : '', children: [_jsx("td", { children: _jsx("div", { children: _jsx(Typography, { children: id.substring(0, 6) }) }) }), _jsx("td", { children: _jsx(Typography, { children: strings[shapes[id].type] }) }), _jsxs("td", { children: [_jsx(IconButton, { onClick: handleSelectShape(id), children: _jsx(SelectIcon, { color: iconColor }) }), _jsx(IconButton, { onClick: handleCopyShape(id), children: _jsx(CopyIcon, { color: iconColor }) }), _jsx(IconButton, { onClick: handleEditShapeMetadata(id), disabled: !configuration.rois.find((roi) => roi.type === shapes[id].type), children: _jsx(AnnotateIcon, { color: iconColor }) }), _jsx(IconButton, { onClick: handleRemoveShape(id), children: _jsx(DeleteIcon, { color: iconColor }) })] })] }, id));
|
45
|
+
}) })] }), form.isOpen && (_jsx(ParametersModalForm, { parameters: (_b = (_a = configuration.rois.find((roi) => roi.type === shapes[form.shapeId].type)) === null || _a === void 0 ? void 0 : _a.parameters) !== null && _b !== void 0 ? _b : [], data: (_f = (_d = (_c = metadata.rois.find((roi) => roi.id === form.shapeId)) === null || _c === void 0 ? void 0 : _c.parameters) !== null && _d !== void 0 ? _d : (_e = configuration.rois.find((roi) => roi.type === shapes[form.shapeId].type)) === null || _e === void 0 ? void 0 : _e.parameters) !== null && _f !== void 0 ? _f : [], title: strings.mainParametersMetadata, onClose: () => setForm({ isOpen: false, shapeId: '' }), onSubmit: handleSubmitMetadata(form.shapeId) }))] }));
|
33
46
|
};
|
34
47
|
export default ShapesList;
|
@@ -5,6 +5,7 @@ import PointerIcon from '../../Icons/PointerIcon';
|
|
5
5
|
import PolygonIcon from '../../Icons/PolygonIcon';
|
6
6
|
import PolylineIcon from '../../Icons/PolylineIcon';
|
7
7
|
import RectangleIcon from '../../Icons/RectangleIcon';
|
8
|
+
import SaveIcon from '../../Icons/SaveIcon';
|
8
9
|
import { useEditorContext } from '../../Providers/EditorProvider';
|
9
10
|
import { UiContext } from '../../Providers/UiProvider';
|
10
11
|
import { css } from '../../Utils';
|
@@ -13,11 +14,20 @@ import ParametersModalForm from './ParametersModalForm';
|
|
13
14
|
import styles from './Toolbar.module.css';
|
14
15
|
import { enableMainMetadata, enableRois } from './Utils';
|
15
16
|
const Toolbar = () => {
|
17
|
+
var _a, _b;
|
16
18
|
const { IconButton, themeMode, primaryColor, Typography, strings } = useContext(UiContext);
|
17
|
-
const { activeTool, setActiveTool, configuration } = useEditorContext();
|
18
|
-
const [form, setForm] = useState({ isOpen: false
|
19
|
+
const { activeTool, setActiveTool, configuration, metadata, setMetadata, onSubmit } = useEditorContext();
|
20
|
+
const [form, setForm] = useState({ isOpen: false });
|
19
21
|
const iconColor = (tool) => (tool === activeTool ? primaryColor : themeMode === 'light' ? 'black' : 'white');
|
20
22
|
const setTool = (tool) => () => setActiveTool(tool);
|
21
|
-
|
23
|
+
const handleSubmitMetadata = (data) => {
|
24
|
+
setMetadata(Object.assign(Object.assign({}, metadata), { parameters: data }));
|
25
|
+
setForm({ isOpen: false });
|
26
|
+
};
|
27
|
+
const hideForbiddenTools = (_a = configuration.options) === null || _a === void 0 ? void 0 : _a.hideForbiddenTools;
|
28
|
+
const polylineEnabled = configuration.rois.find((r) => r.type === "polyline" /* ToolEnum.Polyline */);
|
29
|
+
const polygonEnabled = configuration.rois.find((r) => r.type === "polygon" /* ToolEnum.Polygon */);
|
30
|
+
const rectangleEnabled = configuration.rois.find((r) => r.type === "rect" /* ToolEnum.Rectangle */);
|
31
|
+
return (_jsxs(_Fragment, { children: [((_b = configuration.options) === null || _b === void 0 ? void 0 : _b.description) && (_jsx("div", { className: css('toolbar-info', styles, themeMode), children: _jsx(Typography, { children: configuration.options.description }) })), _jsxs("div", { className: css('toolbar', styles, themeMode), children: [enableRois(configuration) && (_jsxs(_Fragment, { children: [_jsx(IconButton, { onClick: setTool("pointer" /* ToolEnum.Pointer */), children: _jsx(PointerIcon, { color: iconColor("pointer" /* ToolEnum.Pointer */) }) }), (!hideForbiddenTools || polylineEnabled) && (_jsx(IconButton, { onClick: setTool("polyline" /* ToolEnum.Polyline */), disabled: !polylineEnabled, children: _jsx(PolylineIcon, { color: iconColor("polyline" /* ToolEnum.Polyline */) }) })), (!hideForbiddenTools || polygonEnabled) && (_jsx(IconButton, { onClick: setTool("polygon" /* ToolEnum.Polygon */), disabled: !polygonEnabled, children: _jsx(PolygonIcon, { color: iconColor("polygon" /* ToolEnum.Polygon */) }) })), (!hideForbiddenTools || rectangleEnabled) && (_jsx(IconButton, { onClick: setTool("rect" /* ToolEnum.Rectangle */), disabled: !rectangleEnabled, children: _jsx(RectangleIcon, { color: iconColor("rect" /* ToolEnum.Rectangle */) }) })), _jsx(ColorPicker, { style: { marginLeft: 'auto', marginRight: '.5rem' } })] })), enableRois(configuration) && enableMainMetadata(configuration) && (_jsx("div", { className: css('toolbar-spacer', styles, themeMode) })), enableMainMetadata(configuration) && (_jsx(IconButton, { onClick: () => setForm({ isOpen: true }), children: _jsx(AnnotateIcon, { color: iconColor("rect" /* ToolEnum.Rectangle */) }) })), _jsx("div", { className: css('toolbar-spacer', styles, themeMode) }), _jsx(IconButton, { onClick: onSubmit, children: _jsx(SaveIcon, { color: iconColor("rect" /* ToolEnum.Rectangle */) }) })] }), enableRois(configuration) && (_jsx("div", { className: css('toolbar-helper', styles, themeMode), children: _jsxs(Typography, { children: [strings[activeTool], ": ", strings[`${activeTool}HelpText`]] }) })), form.isOpen && (_jsx(ParametersModalForm, { parameters: configuration.parameters, data: metadata.parameters, title: strings.mainParametersMetadata, onClose: () => setForm({ isOpen: false }), onSubmit: handleSubmitMetadata }))] }));
|
22
32
|
};
|
23
33
|
export default Toolbar;
|