@abidibo/react-cam-roi 0.0.7 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +588 -29
  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 +92 -0
  15. package/dist/Components/Modal/index.d.ts +10 -0
  16. package/dist/Components/Modal/index.js +16 -0
  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 +21 -11
  22. package/dist/Components/RoiEditor/Hooks.d.ts +16 -4
  23. package/dist/Components/RoiEditor/Hooks.js +148 -21
  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/Polygon.d.ts +3 -2
  30. package/dist/Components/RoiEditor/Polygon.js +18 -0
  31. package/dist/Components/RoiEditor/Polyline.d.ts +12 -1
  32. package/dist/Components/RoiEditor/Polyline.js +18 -0
  33. package/dist/Components/RoiEditor/Rectangle.d.ts +17 -1
  34. package/dist/Components/RoiEditor/Rectangle.js +23 -1
  35. package/dist/Components/RoiEditor/ShapesList.d.ts +2 -0
  36. package/dist/Components/RoiEditor/ShapesList.js +47 -0
  37. package/dist/Components/RoiEditor/ShapesList.module.css +36 -0
  38. package/dist/Components/RoiEditor/Toolbar.js +20 -6
  39. package/dist/Components/RoiEditor/Toolbar.module.css +40 -0
  40. package/dist/Components/RoiEditor/Types.d.ts +97 -1
  41. package/dist/Components/RoiEditor/Types.js +15 -1
  42. package/dist/Components/RoiEditor/Utils.d.ts +22 -0
  43. package/dist/Components/RoiEditor/Utils.js +143 -0
  44. package/dist/Components/RoiEditor/index.d.ts +4 -0
  45. package/dist/Components/RoiEditor/index.js +44 -7
  46. package/dist/Components/TextField/TextField.module.css +61 -0
  47. package/dist/Components/TextField/index.d.ts +6 -0
  48. package/dist/Components/TextField/index.js +13 -0
  49. package/dist/Components/Typography/index.d.ts +4 -0
  50. package/dist/Components/Typography/index.js +3 -2
  51. package/dist/Icons/AnnotateIcon.d.ts +6 -0
  52. package/dist/Icons/AnnotateIcon.js +5 -0
  53. package/dist/Icons/CloseIcon.d.ts +6 -0
  54. package/dist/Icons/CloseIcon.js +5 -0
  55. package/dist/Icons/CopyIcon.d.ts +6 -0
  56. package/dist/Icons/CopyIcon.js +5 -0
  57. package/dist/Icons/SaveIcon.d.ts +6 -0
  58. package/dist/Icons/SaveIcon.js +5 -0
  59. package/dist/Providers/EditorProvider.d.ts +12 -2
  60. package/dist/Providers/EditorProvider.js +16 -2
  61. package/dist/Providers/UiProvider.d.ts +44 -2
  62. package/dist/Providers/UiProvider.js +55 -2
  63. package/dist/Types.d.ts +10 -0
  64. package/dist/Types.js +1 -0
  65. package/dist/Utils/index.d.ts +1 -1
  66. package/dist/Utils/index.js +1 -1
  67. package/dist/index.d.ts +2 -1
  68. package/dist/index.js +2 -1
  69. package/package.json +13 -4
  70. package/dist/Components/RoiEditor/Metadata.d.ts +0 -2
  71. package/dist/Components/RoiEditor/Metadata.js +0 -31
  72. package/dist/Components/RoiEditor/Metadata.module.css +0 -34
@@ -1,8 +1,14 @@
1
- import { useCallback, useEffect, useRef, useState } from 'react';
1
+ import * as fabric from 'fabric';
2
+ import { useCallback, useContext, useEffect, useRef, useState } from 'react';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ import { useEditorContext } from '../../Providers/EditorProvider';
5
+ import { UiContext } from '../../Providers/UiProvider';
6
+ import { log } from '../../Utils';
2
7
  import Dispatcher from '../../Utils/Dispatcher';
3
- import { handleDoubleClickPolygon, handleMouseDownPolygon, handleMouseMovePolygon } from './Polygon';
4
- import { handleDoubleClickPolyline, handleMouseDownPolyline, handleMouseMovePolyline } from './Polyline';
5
- import { handleMouseDownRect, handleMouseMoveRect, handleMouseUpRect } from './Rectangle';
8
+ import { copyPolygon, handleDoubleClickPolygon, handleMouseDownPolygon, handleMouseMovePolygon } from './Polygon';
9
+ import { copyPolyline, handleDoubleClickPolyline, handleMouseDownPolyline, handleMouseMovePolyline } from './Polyline';
10
+ import { copyRectangle, handleMouseDownRect, handleMouseMoveRect, handleMouseUpRect } from './Rectangle';
11
+ import { canDrawShape } from './Utils';
6
12
  export const useImageSize = (imageUrl) => {
7
13
  const [imageSize, setImageSize] = useState({ width: 0, height: 0 });
8
14
  useEffect(() => {
@@ -37,17 +43,81 @@ export const useCanvasSize = (imageUrl) => {
37
43
  };
38
44
  if (imageSize.width > 0 && wrapperRef.current) {
39
45
  update();
40
- // observe ref for resizing event and in case update canvas dimensions
41
- const resizeObserver = new ResizeObserver(() => {
42
- update();
43
- });
44
- resizeObserver.observe(wrapperRef.current);
45
46
  }
46
47
  }
47
48
  }, [imageSize, wrapperRef.current]); // eslint-disable-line
48
49
  return { imageSize, canvasSize, wrapperRef, isReady };
49
50
  };
50
- export const useTool = (tool, activeColor, addShape, canvas) => {
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
+ };
118
+ export const useTool = (canvas) => {
119
+ const { configuration, activeTool, activeColor, shapes, addShape } = useEditorContext();
120
+ const { notify, strings } = useContext(UiContext);
51
121
  const [isDrawing, setIsDrawing] = useState(false);
52
122
  const [shape, setShape] = useState(null);
53
123
  const [originX, setOriginX] = useState(0);
@@ -68,7 +138,7 @@ export const useTool = (tool, activeColor, addShape, canvas) => {
68
138
  if (!canvas) {
69
139
  return;
70
140
  }
71
- if (tool === "pointer" /* ToolEnum.Pointer */) {
141
+ if (activeTool === "pointer" /* ToolEnum.Pointer */) {
72
142
  // enable selection
73
143
  canvas.selection = true;
74
144
  canvas.getObjects().forEach((object) => {
@@ -84,14 +154,20 @@ export const useTool = (tool, activeColor, addShape, canvas) => {
84
154
  canvas.renderAll();
85
155
  }
86
156
  const handleMouseDown = (event) => {
87
- switch (tool) {
88
- case "rectangle" /* ToolEnum.Rectangle */:
157
+ switch (activeTool) {
158
+ case "rect" /* ToolEnum.Rectangle */:
159
+ if (!canDrawShape(configuration, "rect" /* ToolEnum.Rectangle */, shapes, notify, strings.cannotDrawMoreRectangles))
160
+ return;
89
161
  handleMouseDownRect(event, canvas, activeColor, setOriginX, setOriginY, setShape, setIsDrawing);
90
162
  break;
91
163
  case "polygon" /* ToolEnum.Polygon */:
164
+ if (!canDrawShape(configuration, "polygon" /* ToolEnum.Polygon */, shapes, notify, strings.cannotDrawMorePolygons))
165
+ return;
92
166
  handleMouseDownPolygon(event, canvas, activeColor, setIsDrawing, points, setPoints, lines, setLines);
93
167
  break;
94
168
  case "polyline" /* ToolEnum.Polyline */:
169
+ if (!canDrawShape(configuration, "polyline" /* ToolEnum.Polyline */, shapes, notify, strings.cannotDrawMorePolylines))
170
+ return;
95
171
  handleMouseDownPolyline(event, canvas, activeColor, setIsDrawing, points, setPoints, lines, setLines);
96
172
  break;
97
173
  default:
@@ -99,8 +175,8 @@ export const useTool = (tool, activeColor, addShape, canvas) => {
99
175
  }
100
176
  };
101
177
  const handleMouseMove = (event) => {
102
- switch (tool) {
103
- case "rectangle" /* ToolEnum.Rectangle */:
178
+ switch (activeTool) {
179
+ case "rect" /* ToolEnum.Rectangle */:
104
180
  handleMouseMoveRect(event, canvas, originX, originY, shape, isDrawing);
105
181
  break;
106
182
  case "polygon" /* ToolEnum.Polygon */:
@@ -114,8 +190,8 @@ export const useTool = (tool, activeColor, addShape, canvas) => {
114
190
  }
115
191
  };
116
192
  const handleMouseUp = () => {
117
- switch (tool) {
118
- case "rectangle" /* ToolEnum.Rectangle */:
193
+ switch (activeTool) {
194
+ case "rect" /* ToolEnum.Rectangle */:
119
195
  handleMouseUpRect(canvas, setIsDrawing, shape, setShape, addShape);
120
196
  break;
121
197
  default:
@@ -123,7 +199,7 @@ export const useTool = (tool, activeColor, addShape, canvas) => {
123
199
  }
124
200
  };
125
201
  const handleDoubleClick = () => {
126
- switch (tool) {
202
+ switch (activeTool) {
127
203
  case "polygon" /* ToolEnum.Polygon */:
128
204
  handleDoubleClickPolygon(canvas, activeColor, setIsDrawing, points, setPoints, lines, setLines, addShape);
129
205
  break;
@@ -151,7 +227,7 @@ export const useTool = (tool, activeColor, addShape, canvas) => {
151
227
  canvas.off('selection:cleared', handleSelectionCleared);
152
228
  };
153
229
  }, [
154
- tool,
230
+ activeTool,
155
231
  activeColor,
156
232
  isDrawing,
157
233
  shape,
@@ -164,9 +240,15 @@ export const useTool = (tool, activeColor, addShape, canvas) => {
164
240
  addShape,
165
241
  handleObjectSelected,
166
242
  handleSelectionCleared,
243
+ configuration,
244
+ notify,
245
+ strings,
246
+ shapes,
167
247
  ]);
168
248
  };
169
- export const useDispatcherEvents = (canvas, setActiveTool) => {
249
+ export const useDispatcherEvents = (canvas) => {
250
+ const { configuration, shapes, addShape, setActiveTool } = useEditorContext();
251
+ const { notify, strings } = useContext(UiContext);
170
252
  useEffect(() => {
171
253
  const removeShape = (_, id) => {
172
254
  const obj = canvas === null || canvas === void 0 ? void 0 : canvas.getObjects().find((s) => s.id === id);
@@ -174,6 +256,35 @@ export const useDispatcherEvents = (canvas, setActiveTool) => {
174
256
  canvas === null || canvas === void 0 ? void 0 : canvas.remove(obj);
175
257
  }
176
258
  };
259
+ const copyShape = (_, id) => {
260
+ const obj = canvas === null || canvas === void 0 ? void 0 : canvas.getObjects().find((s) => s.id === id);
261
+ let copy;
262
+ switch (obj === null || obj === void 0 ? void 0 : obj.type) {
263
+ case "polygon" /* ToolEnum.Polygon */:
264
+ if (!canDrawShape(configuration, "polygon" /* ToolEnum.Polygon */, shapes, notify, strings.cannotDrawMorePolygons))
265
+ return;
266
+ copy = copyPolygon(canvas, obj, addShape);
267
+ // @ts-expect-error id exists but his stupid ts does not know
268
+ Dispatcher.emit('canvas:selectShape', copy.id);
269
+ break;
270
+ case "polyline" /* ToolEnum.Polyline */:
271
+ if (!canDrawShape(configuration, "polyline" /* ToolEnum.Polyline */, shapes, notify, strings.cannotDrawMorePolylines))
272
+ return;
273
+ copy = copyPolyline(canvas, obj, addShape);
274
+ // @ts-expect-error id exists but his stupid ts does not know
275
+ Dispatcher.emit('canvas:selectShape', copy.id);
276
+ break;
277
+ case "rect" /* ToolEnum.Rectangle */:
278
+ if (!canDrawShape(configuration, "rect" /* ToolEnum.Rectangle */, shapes, notify, strings.cannotDrawMoreRectangles))
279
+ return;
280
+ copy = copyRectangle(canvas, obj, addShape);
281
+ // @ts-expect-error id exists but his stupid ts does not know
282
+ Dispatcher.emit('canvas:selectShape', copy.id);
283
+ break;
284
+ default:
285
+ break;
286
+ }
287
+ };
177
288
  const selectShape = (_, id) => {
178
289
  const obj = canvas === null || canvas === void 0 ? void 0 : canvas.getObjects().find((s) => s.id === id);
179
290
  if (obj) {
@@ -184,10 +295,26 @@ export const useDispatcherEvents = (canvas, setActiveTool) => {
184
295
  }
185
296
  };
186
297
  Dispatcher.register('canvas:removeShape', removeShape);
298
+ Dispatcher.register('canvas:copyShape', copyShape);
187
299
  Dispatcher.register('canvas:selectShape', selectShape);
188
300
  return () => {
189
301
  Dispatcher.unregister('canvas:removeShape', removeShape);
302
+ Dispatcher.unregister('canvas:copyShape', copyShape);
190
303
  Dispatcher.unregister('canvas:selectShape', selectShape);
191
304
  };
192
- }, [setActiveTool, canvas]);
305
+ }, [setActiveTool, canvas, addShape, configuration, shapes, notify, strings]);
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
+ };
193
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,5 +1,5 @@
1
1
  import * as fabric from 'fabric';
2
- import { FabricEvent, ShapeType } from './Types';
2
+ import { FabricEvent, IAddShape } from './Types';
3
3
  export declare const handleMouseDownPolygon: (event: FabricEvent, canvas: fabric.Canvas, activeColor: string, setIsDrawing: (v: boolean) => void, points: {
4
4
  x: number;
5
5
  y: number;
@@ -14,4 +14,5 @@ export declare const handleDoubleClickPolygon: (canvas: fabric.Canvas, activeCol
14
14
  }[], setPoints: (v: {
15
15
  x: number;
16
16
  y: number;
17
- }[]) => void, lines: fabric.Line[], setLines: (v: fabric.Line[]) => void, addShape: (id: string, type: ShapeType, shape: fabric.Polygon) => void) => void;
17
+ }[]) => void, lines: fabric.Line[], setLines: (v: fabric.Line[]) => void, addShape: IAddShape) => void;
18
+ export declare const copyPolygon: (canvas: fabric.Canvas, polygon: fabric.Polygon, addShape: IAddShape) => fabric.Polygon;
@@ -56,3 +56,21 @@ export const handleDoubleClickPolygon = (canvas, activeColor, setIsDrawing, poin
56
56
  setIsDrawing(false);
57
57
  }
58
58
  };
59
+ export const copyPolygon = (canvas, polygon, addShape) => {
60
+ const id = uuidv4();
61
+ const copy = new fabric.Polygon(polygon.points, {
62
+ top: polygon.top + 10,
63
+ left: polygon.left + 10,
64
+ fill: 'transparent',
65
+ stroke: polygon.stroke,
66
+ strokeWidth: polygon.strokeWidth,
67
+ selectable: false,
68
+ hasControls: true,
69
+ hoverCursor: 'default',
70
+ // @ts-expect-error id is not included in types but the property is added and it works
71
+ id,
72
+ });
73
+ canvas.add(copy);
74
+ addShape(id, "polygon" /* ToolEnum.Polygon */, copy);
75
+ return copy;
76
+ };
@@ -1,5 +1,5 @@
1
1
  import * as fabric from 'fabric';
2
- import { FabricEvent, ShapeType } from './Types';
2
+ import { FabricEvent, IAddShape, ShapeType } from './Types';
3
3
  export declare const handleMouseDownPolyline: (event: FabricEvent, canvas: fabric.Canvas, activeColor: string, setIsDrawing: (v: boolean) => void, points: {
4
4
  x: number;
5
5
  y: number;
@@ -15,3 +15,14 @@ export declare const handleDoubleClickPolyline: (canvas: fabric.Canvas, activeCo
15
15
  x: number;
16
16
  y: number;
17
17
  }[]) => void, lines: fabric.Line[], setLines: (v: fabric.Line[]) => void, addShape: (id: string, type: ShapeType, shape: fabric.Polyline) => void) => void;
18
+ export declare const copyPolyline: (canvas: fabric.Canvas, polyline: fabric.Polyline, addShape: IAddShape) => fabric.Polyline<{
19
+ top: number;
20
+ left: number;
21
+ fill: string;
22
+ stroke: string | fabric.TFiller | null;
23
+ strokeWidth: number;
24
+ selectable: false;
25
+ hasControls: true;
26
+ hoverCursor: string;
27
+ id: string;
28
+ }, fabric.SerializedPolylineProps, fabric.ObjectEvents>;
@@ -37,6 +37,7 @@ export const handleDoubleClickPolyline = (canvas, activeColor, setIsDrawing, poi
37
37
  if (points.length > 2) {
38
38
  const id = uuidv4();
39
39
  const polyline = new fabric.Polyline(points, {
40
+ fill: 'transparent',
40
41
  stroke: activeColor,
41
42
  strokeWidth: 2,
42
43
  selectable: false,
@@ -54,3 +55,20 @@ export const handleDoubleClickPolyline = (canvas, activeColor, setIsDrawing, poi
54
55
  setIsDrawing(false);
55
56
  }
56
57
  };
58
+ export const copyPolyline = (canvas, polyline, addShape) => {
59
+ const id = uuidv4();
60
+ const copy = new fabric.Polyline(polyline.points, {
61
+ top: polyline.top + 10,
62
+ left: polyline.left + 10,
63
+ fill: 'transparent',
64
+ stroke: polyline.stroke,
65
+ strokeWidth: polyline.strokeWidth,
66
+ selectable: false,
67
+ hasControls: true,
68
+ hoverCursor: 'default',
69
+ id,
70
+ });
71
+ canvas.add(copy);
72
+ addShape(id, "polyline" /* ToolEnum.Polyline */, copy);
73
+ return copy;
74
+ };
@@ -1,5 +1,21 @@
1
1
  import * as fabric from 'fabric';
2
- import { FabricEvent, Shape, ShapeType } from './Types';
2
+ import { FabricEvent, IAddShape, Shape, ShapeType } from './Types';
3
3
  export declare const handleMouseDownRect: (event: FabricEvent, canvas: fabric.Canvas, activeColor: string, setOriginX: (v: number) => void, setOriginY: (v: number) => void, setShape: (v: Shape) => void, setIsDrawing: (v: boolean) => void) => void;
4
4
  export declare const handleMouseMoveRect: (event: FabricEvent, canvas: fabric.Canvas, originX: number, originY: number, shape: Shape, isDrawing: boolean) => void;
5
5
  export declare const handleMouseUpRect: (canvas: fabric.Canvas, setIsDrawing: (v: boolean) => void, shape: Shape, setShape: (v: Shape | null) => void, addShape: (id: string, type: ShapeType, shape: Shape) => void) => void;
6
+ export declare const copyRectangle: (canvas: fabric.Canvas, rectangle: fabric.Rect, addShape: IAddShape) => fabric.Rect<{
7
+ left: number;
8
+ top: number;
9
+ originX: "left";
10
+ originY: "top";
11
+ width: number;
12
+ height: number;
13
+ fill: string;
14
+ stroke: string | fabric.TFiller | null;
15
+ strokeWidth: number;
16
+ strokeUniform: true;
17
+ selectable: false;
18
+ hasControls: true;
19
+ hoverCursor: string;
20
+ id: string;
21
+ }, fabric.SerializedRectProps, fabric.ObjectEvents>;
@@ -44,7 +44,29 @@ export const handleMouseMoveRect = (event, canvas, originX, originY, shape, isDr
44
44
  export const handleMouseUpRect = (canvas, setIsDrawing, shape, setShape, addShape) => {
45
45
  setIsDrawing(false);
46
46
  shape.setCoords();
47
- addShape(shape.id, "rectangle" /* ToolEnum.Rectangle */, shape);
47
+ addShape(shape.id, "rect" /* ToolEnum.Rectangle */, shape);
48
48
  setShape(null);
49
49
  canvas.defaultCursor = 'default';
50
50
  };
51
+ export const copyRectangle = (canvas, rectangle, addShape) => {
52
+ const id = uuidv4();
53
+ const copy = new fabric.Rect({
54
+ left: rectangle.left + 10,
55
+ top: rectangle.top + 10,
56
+ originX: 'left',
57
+ originY: 'top',
58
+ width: rectangle.width,
59
+ height: rectangle.height,
60
+ fill: 'transparent',
61
+ stroke: rectangle.stroke,
62
+ strokeWidth: rectangle.strokeWidth,
63
+ strokeUniform: true,
64
+ selectable: false,
65
+ hasControls: true,
66
+ hoverCursor: 'default',
67
+ id,
68
+ });
69
+ canvas.add(copy);
70
+ addShape(id, "rect" /* ToolEnum.Rectangle */, copy);
71
+ return copy;
72
+ };
@@ -0,0 +1,2 @@
1
+ declare const ShapesList: React.FC;
2
+ export default ShapesList;
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useContext, useEffect, useState } from 'react';
3
+ import { useEditorContext } from '../../Providers/EditorProvider';
4
+ import { UiContext } from '../../Providers/UiProvider';
5
+ import { css } from '../../Utils';
6
+ import Dispatcher from '../../Utils/Dispatcher';
7
+ import ParametersModalForm from './ParametersModalForm';
8
+ import styles from './ShapesList.module.css';
9
+ const ShapesList = () => {
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();
13
+ const [selected, setSelected] = useState([]);
14
+ const [form, setForm] = useState({ isOpen: false, shapeId: '' });
15
+ useEffect(() => {
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 : []); };
17
+ Dispatcher.register('canvas:shapeSelected', setSelectedShapes);
18
+ return () => {
19
+ Dispatcher.unregister('canvas:shapeSelected', setSelectedShapes);
20
+ };
21
+ }, [shapes]);
22
+ const handleCopyShape = (id) => () => {
23
+ Dispatcher.emit('canvas:copyShape', id);
24
+ };
25
+ const handleRemoveShape = (id) => () => {
26
+ Dispatcher.emit('canvas:removeShape', id);
27
+ removeShape(id);
28
+ };
29
+ const handleSelectShape = (id) => () => {
30
+ Dispatcher.emit('canvas:selectShape', id);
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
+ };
42
+ const iconColor = themeMode === 'light' ? 'black' : 'white';
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) }))] }));
46
+ };
47
+ export default ShapesList;
@@ -0,0 +1,36 @@
1
+ .shapes-table {
2
+ border-collapse: collapse;
3
+ width: 100%;
4
+ }
5
+
6
+ .shapes-table th {
7
+ padding: .8rem .3rem .3rem;
8
+ text-align: left;
9
+ }
10
+
11
+ .shapes-table td {
12
+ padding: 0 .3rem;
13
+ }
14
+
15
+ .shapes-table tr td:last-child,
16
+ .shapes-table tr th:last-child {
17
+ text-align: right;
18
+ }
19
+
20
+ .shapes-table-light {
21
+ background-color: #eee;
22
+ color: #000;
23
+ }
24
+
25
+ .shapes-table-dark {
26
+ background-color: #333;
27
+ color: #fff;
28
+ }
29
+
30
+ .shapes-row-selected-light {
31
+ background-color: #ccc;
32
+ }
33
+
34
+ .shapes-row-selected-dark {
35
+ background-color: #666;
36
+ }
@@ -1,19 +1,33 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useContext } from 'react';
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useContext, useState } from 'react';
3
+ import AnnotateIcon from '../../Icons/AnnotateIcon';
3
4
  import PointerIcon from '../../Icons/PointerIcon';
4
5
  import PolygonIcon from '../../Icons/PolygonIcon';
5
6
  import PolylineIcon from '../../Icons/PolylineIcon';
6
7
  import RectangleIcon from '../../Icons/RectangleIcon';
8
+ import SaveIcon from '../../Icons/SaveIcon';
7
9
  import { useEditorContext } from '../../Providers/EditorProvider';
8
10
  import { UiContext } from '../../Providers/UiProvider';
9
11
  import { css } from '../../Utils';
10
- import styles from './Toolbar.module.css';
11
12
  import ColorPicker from './ColorPicker';
13
+ import ParametersModalForm from './ParametersModalForm';
14
+ import styles from './Toolbar.module.css';
15
+ import { enableMainMetadata, enableRois } from './Utils';
12
16
  const Toolbar = () => {
13
- const { IconButton, themeMode, primaryColor } = useContext(UiContext);
14
- const { activeTool, setActiveTool } = useEditorContext();
17
+ var _a, _b;
18
+ const { IconButton, themeMode, primaryColor, Typography, strings } = useContext(UiContext);
19
+ const { activeTool, setActiveTool, configuration, metadata, setMetadata, onSubmit } = useEditorContext();
20
+ const [form, setForm] = useState({ isOpen: false });
15
21
  const iconColor = (tool) => (tool === activeTool ? primaryColor : themeMode === 'light' ? 'black' : 'white');
16
22
  const setTool = (tool) => () => setActiveTool(tool);
17
- return (_jsxs("div", { className: css('toolbar', styles, themeMode), 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("rectangle" /* ToolEnum.Rectangle */), children: _jsx(RectangleIcon, { color: iconColor("rectangle" /* ToolEnum.Rectangle */) }) }), _jsx(ColorPicker, { style: { marginLeft: 'auto' } })] }));
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 }))] }));
18
32
  };
19
33
  export default Toolbar;