@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.
Files changed (54) hide show
  1. package/README.md +335 -39
  2. package/dist/Components/BoolField/BoolField.module.css +60 -0
  3. package/dist/Components/BoolField/index.d.ts +5 -0
  4. package/dist/Components/BoolField/index.js +13 -0
  5. package/dist/Components/Button/Button.module.css +27 -0
  6. package/dist/Components/Button/index.d.ts +8 -0
  7. package/dist/Components/Button/index.js +15 -0
  8. package/dist/Components/EnumField/EnumField.module.css +61 -0
  9. package/dist/Components/EnumField/index.d.ts +10 -0
  10. package/dist/Components/EnumField/index.js +16 -0
  11. package/dist/Components/IconButton/IconButton.module.css +8 -3
  12. package/dist/Components/IconButton/index.d.ts +1 -0
  13. package/dist/Components/IconButton/index.js +3 -3
  14. package/dist/Components/Modal/Modal.module.css +7 -0
  15. package/dist/Components/Modal/index.d.ts +2 -1
  16. package/dist/Components/Modal/index.js +3 -3
  17. package/dist/Components/NumberField/NumberField.module.css +60 -0
  18. package/dist/Components/NumberField/index.d.ts +3 -0
  19. package/dist/Components/NumberField/index.js +13 -0
  20. package/dist/Components/RoiEditor/Canvas.d.ts +2 -0
  21. package/dist/Components/RoiEditor/Canvas.js +20 -8
  22. package/dist/Components/RoiEditor/Hooks.d.ts +13 -0
  23. package/dist/Components/RoiEditor/Hooks.js +84 -5
  24. package/dist/Components/RoiEditor/ParameterField.d.ts +9 -0
  25. package/dist/Components/RoiEditor/ParameterField.js +27 -0
  26. package/dist/Components/RoiEditor/ParametersModalForm/ParametersModalForm.module.css +5 -0
  27. package/dist/Components/RoiEditor/ParametersModalForm/index.d.ts +10 -0
  28. package/dist/Components/RoiEditor/ParametersModalForm/index.js +31 -0
  29. package/dist/Components/RoiEditor/ShapesList.js +19 -6
  30. package/dist/Components/RoiEditor/Toolbar.js +13 -3
  31. package/dist/Components/RoiEditor/Toolbar.module.css +13 -1
  32. package/dist/Components/RoiEditor/Types.d.ts +52 -2
  33. package/dist/Components/RoiEditor/Utils.d.ts +18 -1
  34. package/dist/Components/RoiEditor/Utils.js +106 -0
  35. package/dist/Components/RoiEditor/index.d.ts +3 -1
  36. package/dist/Components/RoiEditor/index.js +44 -6
  37. package/dist/Components/TextField/TextField.module.css +61 -0
  38. package/dist/Components/TextField/index.d.ts +6 -0
  39. package/dist/Components/TextField/index.js +13 -0
  40. package/dist/Components/Typography/index.d.ts +1 -0
  41. package/dist/Components/Typography/index.js +2 -2
  42. package/dist/Icons/SaveIcon.d.ts +6 -0
  43. package/dist/Icons/SaveIcon.js +5 -0
  44. package/dist/Providers/EditorProvider.d.ts +11 -2
  45. package/dist/Providers/EditorProvider.js +16 -2
  46. package/dist/Providers/UiProvider.d.ts +24 -1
  47. package/dist/Providers/UiProvider.js +30 -1
  48. package/dist/Types.d.ts +10 -0
  49. package/dist/Types.js +1 -0
  50. package/dist/Utils/index.d.ts +1 -1
  51. package/dist/Utils/index.js +1 -1
  52. package/package.json +7 -2
  53. package/dist/Components/RoiEditor/ParametersModalForm.d.ts +0 -5
  54. 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
- .iconButton {
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
- .iconButton-light:hover {
9
+ .icon-button-light:hover {
10
10
  background-color: rgba(0, 0, 0, 0.1);
11
11
  }
12
12
 
13
- .iconButton-dark:hover {
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,5 +1,6 @@
1
1
  export type IconButtonProps = {
2
2
  children?: React.ReactNode;
3
+ disabled?: boolean;
3
4
  onClick?: (event: React.MouseEvent) => void;
4
5
  };
5
6
  declare const IconButton: React.FC<IconButtonProps>;
@@ -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
- import { UiContext } from '../../Providers/UiProvider';
6
- const IconButton = ({ children, onClick }) => {
6
+ const IconButton = ({ children, disabled, onClick }) => {
7
7
  const { themeMode } = useContext(UiContext);
8
- return (_jsx("div", { className: css('iconButton', styles, themeMode), onClick: onClick, children: children }));
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;
@@ -83,3 +83,10 @@
83
83
  color: #fff;
84
84
  }
85
85
 
86
+ .modal-footer {
87
+ display: flex;
88
+ justify-content: flex-end;
89
+ margin-top: 1rem;
90
+ gap: .5rem;
91
+ }
92
+
@@ -3,7 +3,8 @@ export type ModalProps = {
3
3
  isOpen: boolean;
4
4
  onClose: () => void;
5
5
  title: string;
6
- size: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
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, size }) => {
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-${size}`, 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] }) }), document.body);
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,3 @@
1
+ import { FieldProps } from '../../Types';
2
+ declare const NumberField: React.FC<FieldProps<number | null>>;
3
+ export default NumberField;
@@ -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,8 +1,10 @@
1
+ import { Output } from './Types';
1
2
  type CanvasProps = {
2
3
  canvasSize: {
3
4
  width: number;
4
5
  height: number;
5
6
  };
7
+ initialData?: Output;
6
8
  };
7
9
  declare const Canvas: React.FC<CanvasProps>;
8
10
  export default Canvas;
@@ -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 { useDispatcherEvents, useTool } from './Hooks';
5
- const Canvas = ({ canvasSize }) => {
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
- canvasRef.current = new fabric.Canvas('react-cam-roi-canvas');
11
- canvasRef.current.setDimensions({ width: canvasSize.width, height: canvasSize.height });
12
- return () => { var _a; (_a = canvasRef.current) === null || _a === void 0 ? void 0 : _a.dispose(); };
13
- }, [canvasSize.width, canvasSize.height]);
14
- return _jsx("canvas", { id: "react-cam-roi-canvas", style: { width: `${canvasSize.width}px`, height: `${canvasSize.height}px` } });
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,5 @@
1
+ .form {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 1rem;
5
+ }
@@ -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
- const { strings, Typography, IconButton, DeleteIcon, EditIcon, SelectIcon, CopyIcon, themeMode } = useContext(UiContext);
10
- const { shapes, removeShape } = useEditorContext();
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
- 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: handleRemoveShape(id), children: _jsx(EditIcon, { color: iconColor }) }), _jsx(IconButton, { onClick: handleRemoveShape(id), children: _jsx(DeleteIcon, { color: iconColor }) })] })] }, id));
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, data: [] });
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
- return (_jsxs(_Fragment, { children: [_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 */) }) }), _jsx(IconButton, { onClick: setTool("polyline" /* ToolEnum.Polyline */), children: _jsx(PolylineIcon, { color: iconColor("polyline" /* ToolEnum.Polyline */) }) }), _jsx(IconButton, { onClick: setTool("polygon" /* ToolEnum.Polygon */), children: _jsx(PolygonIcon, { color: iconColor("polygon" /* ToolEnum.Polygon */) }) }), _jsx(IconButton, { onClick: setTool("rect" /* ToolEnum.Rectangle */), children: _jsx(RectangleIcon, { color: iconColor("rect" /* ToolEnum.Rectangle */) }) }), _jsx(ColorPicker, { style: { marginLeft: 'auto' } })] })), enableRois(configuration) && enableMainMetadata(configuration) && (_jsx("div", { className: css('toolbar-spacer', styles, themeMode) })), enableMainMetadata(configuration) && (_jsx(IconButton, { onClick: () => setForm({ isOpen: true, data: configuration.parameters }), children: _jsx(AnnotateIcon, { color: iconColor("rect" /* ToolEnum.Rectangle */) }) }))] }), _jsx("div", { className: css('toolbar-helper', styles, themeMode), children: _jsxs(Typography, { children: [strings[activeTool], ": ", strings[`${activeTool}HelpText`]] }) }), form.isOpen && _jsx(ParametersModalForm, { onClose: () => setForm({ isOpen: false, data: [] }) })] }));
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;