@abidibo/react-cam-roi 0.0.7 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
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;