@visns-studio/visns-components 5.0.19 → 5.0.21

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 (38) hide show
  1. package/package.json +6 -6
  2. package/src/components/crm/MultiSelect.jsx +46 -67
  3. package/src/components/crm/auth/styles/Profile.module.scss +6 -7
  4. package/src/components/crm/generic/GenericDashboard.jsx +495 -29
  5. package/src/components/crm/generic/GenericFormBuilder.jsx +96 -32
  6. package/src/components/crm/generic/styles/AuditLog.module.scss +3 -3
  7. package/src/components/crm/generic/styles/AuditLogs.module.scss +1 -1
  8. package/src/components/crm/generic/styles/GenericDashboard.module.scss +364 -0
  9. package/src/components/crm/generic/styles/GenericDynamic.module.scss +11 -11
  10. package/src/components/crm/generic/styles/GenericFormBuilder.module.scss +327 -317
  11. package/src/components/crm/generic/styles/GenericIndex.module.scss +8 -8
  12. package/src/components/crm/generic/styles/GenericSort.module.scss +3 -3
  13. package/src/components/crm/generic/styles/NotificationList.module.scss +2 -2
  14. package/src/components/crm/sketch/SketchField.jsx +395 -0
  15. package/src/components/crm/sketch/arrow.jsx +108 -0
  16. package/src/components/crm/sketch/circle.jsx +75 -0
  17. package/src/components/crm/sketch/default-tool.jsx +16 -0
  18. package/src/components/crm/sketch/fabrictool.jsx +22 -0
  19. package/src/components/crm/sketch/history.jsx +144 -0
  20. package/src/components/crm/sketch/json/config.json +14 -0
  21. package/src/components/crm/sketch/line.jsx +64 -0
  22. package/src/components/crm/sketch/pan.jsx +48 -0
  23. package/src/components/crm/sketch/pencil.jsx +36 -0
  24. package/src/components/crm/sketch/rectangle-label-object.jsx +69 -0
  25. package/src/components/crm/sketch/rectangle-label.jsx +93 -0
  26. package/src/components/crm/sketch/rectangle.jsx +76 -0
  27. package/src/components/crm/sketch/select.jsx +16 -0
  28. package/src/components/crm/sketch/tools.jsx +11 -0
  29. package/src/components/crm/sketch/utils.jsx +67 -0
  30. package/src/components/crm/sorting/Item.jsx +2 -2
  31. package/src/components/crm/sorting/styles/Item.module.scss +12 -65
  32. package/src/components/crm/sorting/styles/List.module.scss +6 -20
  33. package/src/components/crm/styles/Form.module.scss +4 -6
  34. package/src/components/crm/styles/MultiSelect.module.scss +4 -0
  35. package/src/components/crm/styles/Navigation.module.scss +13 -6
  36. package/src/components/crm/styles/QrCode.module.scss +2 -2
  37. package/src/components/styles/global.css +8 -0
  38. package/src/index.js +2 -0
@@ -17,7 +17,7 @@
17
17
  position: relative;
18
18
  padding: 20px;
19
19
  margin: 8px 0;
20
- box-shadow: 0 4px 0 rgba(var(--primary-color-rgb), 0.05);
20
+ box-shadow: 0 4px 0 rgba(var(--primary-rgb), 0.05);
21
21
  }
22
22
 
23
23
  .grid__half {
@@ -43,7 +43,7 @@
43
43
  position: relative;
44
44
  padding: 2px;
45
45
  margin: 8px 0;
46
- box-shadow: 0 4px 0 rgba(var(--primary-color-rgb), 0.05);
46
+ box-shadow: 0 4px 0 rgba(var(--primary-rgb), 0.05);
47
47
  }
48
48
 
49
49
  .grid__dashfull {
@@ -56,7 +56,7 @@
56
56
  position: relative;
57
57
  padding: 20px;
58
58
  margin: 8px 0;
59
- box-shadow: 0 4px 0 rgba(var(--primary-color-rgb), 0.05);
59
+ box-shadow: 0 4px 0 rgba(var(--primary-rgb), 0.05);
60
60
  }
61
61
 
62
62
  .grid__subrow {
@@ -75,7 +75,7 @@
75
75
  padding: 5px;
76
76
  margin: 8px 0;
77
77
  height: auto;
78
- box-shadow: 0 4px 0 rgba(var(--primary-color-rgb), 0.05);
78
+ box-shadow: 0 4px 0 rgba(var(--primary-rgb), 0.05);
79
79
  }
80
80
 
81
81
  .grid__subnav > ul {
@@ -126,7 +126,7 @@
126
126
  padding: 5px;
127
127
  margin: 8px 0;
128
128
  height: auto;
129
- box-shadow: 0 4px 0 rgba(var(--primary-color-rgb), 0.05);
129
+ box-shadow: 0 4px 0 rgba(var(--primary-rgb), 0.05);
130
130
  }
131
131
 
132
132
  .grid__subnav__alternate > ul {
@@ -186,7 +186,7 @@
186
186
  margin: 8px 0;
187
187
  height: auto;
188
188
  position: relative;
189
- box-shadow: 0 4px 0 rgba(var(--primary-color-rgb), 0.05);
189
+ box-shadow: 0 4px 0 rgba(var(--primary-rgb), 0.05);
190
190
  }
191
191
 
192
192
  .grid__subcontent h2 {
@@ -260,7 +260,7 @@
260
260
  }
261
261
 
262
262
  svg {
263
- color: rgba(var(--primary-color-rgb), 0.5);
263
+ color: rgba(var(--primary-rgb), 0.5);
264
264
  position: relative;
265
265
  top: 2px;
266
266
  margin: 0 0.25rem;
@@ -270,7 +270,7 @@
270
270
  .titleInfo {
271
271
  width: max-content;
272
272
  }
273
-
273
+
274
274
  .titleInfo span {
275
275
  font-size: 0.875em;
276
276
  color: rgba(var(--paragraph-color-rgb), 0.65);
@@ -26,7 +26,7 @@
26
26
  position: relative;
27
27
  padding: 2px;
28
28
  margin: 8px 0;
29
- box-shadow: 0 4px 0 rgba(var(--primary-color-rgb), 0.05);
29
+ box-shadow: 0 4px 0 rgba(var(--primary-rgb), 0.05);
30
30
  }
31
31
 
32
32
  .dragitem {
@@ -36,8 +36,8 @@
36
36
  align-items: center;
37
37
  flex-wrap: wrap;
38
38
  border-radius: 5px;
39
- border: 1px solid rgba(var(--primary-color-rgb), 0.2);
40
- box-shadow: 0px 0px 20px rgba(var(--primary-color-rgb), 0.25);
39
+ border: 1px solid rgba(var(--primary-rgb), 0.2);
40
+ box-shadow: 0px 0px 20px rgba(var(--primary-rgb), 0.25);
41
41
  padding: 1.25rem;
42
42
  box-sizing: border-box;
43
43
  cursor: grab;
@@ -26,5 +26,5 @@
26
26
  position: relative;
27
27
  padding: 2px;
28
28
  margin: 8px 0;
29
- box-shadow: 0 4px 0 rgba(var(--primary-color-rgb), 0.05);
30
- }
29
+ box-shadow: 0 4px 0 rgba(var(--primary-rgb), 0.05);
30
+ }
@@ -0,0 +1,395 @@
1
+ import React, { PureComponent } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import History from './history';
4
+ import Select from './select';
5
+ import Pencil from './pencil';
6
+ import Line from './line';
7
+ import Arrow from './arrow';
8
+ import Rectangle from './rectangle';
9
+ import Circle from './circle';
10
+ import Pan from './pan';
11
+ import Tool from './tools';
12
+ import RectangleLabel from './rectangle-label';
13
+ import DefaultTool from './default-tool';
14
+ import * as fabric from 'fabric'; // v6
15
+ import { toast } from 'react-toastify'; // Import toast
16
+
17
+ // Debounce function
18
+ function debounce(func, delay) {
19
+ let timeout;
20
+ return (...args) => {
21
+ clearTimeout(timeout);
22
+ timeout = setTimeout(() => func.apply(this, args), delay);
23
+ };
24
+ }
25
+
26
+ class SketchField extends PureComponent {
27
+ static propTypes = {
28
+ lineColor: PropTypes.string,
29
+ lineWidth: PropTypes.number,
30
+ lineRadius: PropTypes.number,
31
+ fillColor: PropTypes.string,
32
+ backgroundColor: PropTypes.string,
33
+ backgroundImage: PropTypes.string,
34
+ opacity: PropTypes.number,
35
+ undoSteps: PropTypes.number,
36
+ tool: PropTypes.string,
37
+ imageFormat: PropTypes.string,
38
+ value: PropTypes.object,
39
+ forceValue: PropTypes.bool,
40
+ widthCorrection: PropTypes.number,
41
+ heightCorrection: PropTypes.number,
42
+ onChange: PropTypes.func,
43
+ defaultValue: PropTypes.object,
44
+ width: PropTypes.number,
45
+ height: PropTypes.number,
46
+ onObjectAdded: PropTypes.func,
47
+ onObjectModified: PropTypes.func,
48
+ onObjectRemoved: PropTypes.func,
49
+ className: PropTypes.string,
50
+ style: PropTypes.object,
51
+ };
52
+
53
+ static defaultProps = {
54
+ lineColor: 'black',
55
+ lineWidth: 10,
56
+ lineRadius: 10,
57
+ fillColor: 'transparent',
58
+ backgroundColor: 'transparent',
59
+ opacity: 1.0,
60
+ undoSteps: 25,
61
+ tool: null,
62
+ widthCorrection: 0,
63
+ heightCorrection: 0,
64
+ forceValue: false,
65
+ onObjectAdded: () => null,
66
+ onObjectModified: () => null,
67
+ onObjectRemoved: () => null,
68
+ };
69
+
70
+ _initTools = (fabricCanvas) => {
71
+ this._tools = {
72
+ [Tool.Select]: new Select(fabricCanvas),
73
+ [Tool.Pencil]: new Pencil(fabricCanvas),
74
+ [Tool.Line]: new Line(fabricCanvas),
75
+ [Tool.Arrow]: new Arrow(fabricCanvas),
76
+ [Tool.Rectangle]: new Rectangle(fabricCanvas),
77
+ [Tool.RectangleLabel]: new RectangleLabel(fabricCanvas),
78
+ [Tool.Circle]: new Circle(fabricCanvas),
79
+ [Tool.Pan]: new Pan(fabricCanvas),
80
+ [Tool.DefaultTool]: new DefaultTool(fabricCanvas),
81
+ };
82
+ };
83
+
84
+ setBackgroundImage = async (imageUrl) => {
85
+ try {
86
+ const img = await fabric.Image.fromURL(imageUrl, {
87
+ crossOrigin: 'anonymous', // Enable CORS
88
+ });
89
+
90
+ const imgWidth = img.width;
91
+ const imgHeight = img.height;
92
+
93
+ // Get the aspect ratio of the image
94
+ const aspectRatio = imgWidth / imgHeight;
95
+
96
+ // Calculate the new canvas dimensions based on aspect ratio
97
+ const { width, height } = this.props; // Default width and height
98
+
99
+ let newWidth, newHeight;
100
+ if (aspectRatio > 1) {
101
+ // Landscape orientation: Set width to max and adjust height
102
+ newWidth = width;
103
+ newHeight = width / aspectRatio;
104
+ } else {
105
+ // Portrait orientation: Set height to max and adjust width
106
+ newHeight = height;
107
+ newWidth = height * aspectRatio;
108
+ }
109
+
110
+ // Set the new canvas dimensions
111
+ this._fc.setWidth(newWidth);
112
+ this._fc.setHeight(newHeight);
113
+
114
+ // Scale the image to fit the canvas dimensions
115
+ img.scaleToWidth(newWidth);
116
+ img.scaleToHeight(newHeight);
117
+
118
+ // Center the image both horizontally and vertically
119
+ img.set({
120
+ left: (newWidth - img.getScaledWidth()) / 2,
121
+ top: (newHeight - img.getScaledHeight()) / 2,
122
+ selectable: false,
123
+ evented: false,
124
+ });
125
+
126
+ // Set the image as the background directly
127
+ this._fc.backgroundImage = img;
128
+
129
+ // Render the canvas to apply changes
130
+ this._fc.requestRenderAll();
131
+ } catch (error) {
132
+ console.error('Failed to load background image:', error);
133
+ }
134
+ };
135
+
136
+ saveCanvasState = () => {
137
+ // Return the current state of the canvas as JSON
138
+ const state = this._fc.toJSON();
139
+ const backgroundImage = this._fc.backgroundImage
140
+ ? {
141
+ src: this._fc.backgroundImage._element.src,
142
+ left: this._fc.backgroundImage.left,
143
+ top: this._fc.backgroundImage.top,
144
+ scaleX: this._fc.backgroundImage.scaleX,
145
+ scaleY: this._fc.backgroundImage.scaleY,
146
+ }
147
+ : null;
148
+
149
+ return {
150
+ state,
151
+ backgroundImage,
152
+ };
153
+ };
154
+
155
+ exportCanvasImage = async (format = 'png') => {
156
+ // Ensure all images are loaded with CORS
157
+ const imageObjects = this._fc.getObjects('image');
158
+ for (const imgObj of imageObjects) {
159
+ if (imgObj.crossOrigin !== 'anonymous') {
160
+ // Set crossOrigin for images that are not set
161
+ imgObj.set({ crossOrigin: 'anonymous' });
162
+ }
163
+ }
164
+
165
+ // Get the Base64 representation of the canvas image
166
+ const dataURL = this._fc.toDataURL({
167
+ format: format,
168
+ quality: 0.8,
169
+ });
170
+
171
+ // Return the Base64 string
172
+ return dataURL; // This returns a promise
173
+ };
174
+
175
+ clearCanvas = () => {
176
+ // Clear all objects from the canvas while keeping the background image
177
+ const backgroundImage = this._fc.backgroundImage;
178
+ this._fc.clear();
179
+
180
+ // Restore the background image after clearing
181
+ if (backgroundImage) {
182
+ this._fc.backgroundImage = backgroundImage;
183
+ }
184
+ this._fc.renderAll(); // Re-render to show the background image
185
+ };
186
+
187
+ _resize = (e) => {
188
+ const { widthCorrection, heightCorrection } = this.props;
189
+ const canvas = this._fc;
190
+
191
+ const { offsetWidth, clientHeight } = this._container;
192
+ const wfactor = (offsetWidth - widthCorrection) / canvas.width;
193
+ const hfactor = (clientHeight - heightCorrection) / canvas.height;
194
+
195
+ canvas.setWidth(offsetWidth - widthCorrection);
196
+ canvas.setHeight(clientHeight - heightCorrection);
197
+
198
+ canvas.getObjects().forEach((obj) => {
199
+ obj.scaleX *= wfactor;
200
+ obj.scaleY *= hfactor;
201
+ obj.left *= wfactor;
202
+ obj.top *= hfactor;
203
+ obj.setCoords();
204
+ });
205
+
206
+ this.setState({ parentWidth: offsetWidth });
207
+ canvas.requestRenderAll();
208
+ };
209
+
210
+ _backgroundColor = (color) => {
211
+ if (this._fc && color) {
212
+ this._fc.backgroundColor = color;
213
+ this._fc.requestRenderAll();
214
+ }
215
+ };
216
+
217
+ undo = () => {
218
+ if (this._history && this._history.canUndo()) {
219
+ const previousState = this._history.undo();
220
+ if (previousState) {
221
+ // Restore the previous state
222
+ this.fromJSON(previousState.state);
223
+ // Reapply the background image after undoing
224
+ if (previousState.backgroundImage) {
225
+ this.setBackgroundImage(previousState.backgroundImage.src);
226
+ }
227
+ } else {
228
+ this._fc.clear(); // If no previous state, clear the canvas
229
+
230
+ this.setBackgroundImage(this.props.backgroundImage);
231
+ }
232
+ this._fc.renderAll(); // Re-render canvas
233
+ this.props.onChange && this.props.onChange();
234
+ }
235
+ };
236
+
237
+ redo = () => {
238
+ if (this._history && this._history.canRedo()) {
239
+ const nextState = this._history.redo();
240
+ if (nextState) {
241
+ this.fromJSON(nextState.state);
242
+ // Reapply the background image after redoing
243
+ if (nextState.backgroundImage) {
244
+ this.setBackgroundImage(nextState.backgroundImage.src);
245
+ }
246
+ this._fc.renderAll(); // Re-render canvas
247
+ this.props.onChange && this.props.onChange();
248
+ }
249
+ }
250
+ };
251
+
252
+ fromJSON = (json) => {
253
+ if (!json) return;
254
+ this._fc.loadFromJSON(json, () => {
255
+ this._fc.requestRenderAll();
256
+ this.props.onChange && this.props.onChange();
257
+ });
258
+ };
259
+
260
+ componentDidMount = () => {
261
+ const {
262
+ tool,
263
+ undoSteps,
264
+ backgroundColor,
265
+ width = 800,
266
+ height = 600,
267
+ backgroundImage,
268
+ defaultValue,
269
+ } = this.props;
270
+
271
+ this._fc = new fabric.Canvas(this._canvas);
272
+ this._fc.setWidth(width);
273
+ this._fc.setHeight(height);
274
+
275
+ this._initTools(this._fc);
276
+ this._backgroundColor(backgroundColor);
277
+
278
+ const selectedTool = this._tools[tool];
279
+ if (selectedTool) {
280
+ selectedTool.configureCanvas(this.props);
281
+ selectedTool.attachListeners();
282
+ }
283
+ this._selectedTool = selectedTool;
284
+
285
+ window.addEventListener('resize', this._resize, false);
286
+
287
+ this._history = new History(undoSteps);
288
+
289
+ this._fc.on('object:added', this._captureState);
290
+ this._fc.on('object:modified', this._captureState);
291
+ this._fc.on('object:removed', this._captureState);
292
+
293
+ this.setState({ canvasReady: true }, () => {
294
+ // Load the defaultValue to set the initial state of the canvas
295
+ if (defaultValue) {
296
+ // Show loading toast
297
+ setTimeout(() => {
298
+ this.fromJSON(defaultValue);
299
+ }, 1000);
300
+ } else {
301
+ if (backgroundImage) {
302
+ this.setBackgroundImage(backgroundImage);
303
+ }
304
+ }
305
+ });
306
+
307
+ // Load the defaultValue to set the initial state of the canvas
308
+ if (defaultValue) {
309
+ // Show loading toast
310
+ const toastId = toast.loading('Loading Canvas...');
311
+ setTimeout(() => {
312
+ this.fromJSON(defaultValue);
313
+ // Update toast after loading is done
314
+ toast.update(toastId, {
315
+ render: 'Saved Canvas loaded!',
316
+ type: 'success',
317
+ isLoading: false,
318
+ autoClose: 2000,
319
+ });
320
+ }, 1000);
321
+ } else {
322
+ if (backgroundImage) {
323
+ this.setBackgroundImage(backgroundImage);
324
+ }
325
+ }
326
+ };
327
+
328
+ _captureState = debounce(() => {
329
+ if (this._history && this._fc) {
330
+ const state = this._fc.toJSON();
331
+ const backgroundImage = this._fc.backgroundImage
332
+ ? {
333
+ src: this._fc.backgroundImage._element.src,
334
+ left: this._fc.backgroundImage.left,
335
+ top: this._fc.backgroundImage.top,
336
+ scaleX: this._fc.backgroundImage.scaleX,
337
+ scaleY: this._fc.backgroundImage.scaleY,
338
+ }
339
+ : null;
340
+
341
+ this._history.keep({
342
+ state,
343
+ backgroundImage,
344
+ });
345
+ }
346
+ }, 300);
347
+
348
+ componentWillUnmount = () => {
349
+ window.removeEventListener('resize', this._resize);
350
+ };
351
+
352
+ componentDidUpdate = (prevProps) => {
353
+ if (this.props.tool !== prevProps.tool) {
354
+ if (this._selectedTool) {
355
+ this._selectedTool.detachListeners();
356
+ }
357
+
358
+ this._selectedTool = this._tools[this.props.tool];
359
+ this._selectedTool.configureCanvas(this.props);
360
+ this._selectedTool.attachListeners();
361
+ }
362
+
363
+ if (this.props.backgroundColor !== prevProps.backgroundColor) {
364
+ this._backgroundColor(this.props.backgroundColor);
365
+ }
366
+
367
+ if (this.props.defaultValue !== prevProps.defaultValue) {
368
+ this.fromJSON(this.props.defaultValue);
369
+ } else {
370
+ if (this.props.backgroundImage !== prevProps.backgroundImage) {
371
+ this.setBackgroundImage(this.props.backgroundImage);
372
+ }
373
+ }
374
+ };
375
+
376
+ render() {
377
+ const { className, style, width = 800, height = 600 } = this.props;
378
+ const canvasDivStyle = { ...style, width, height };
379
+
380
+ return (
381
+ <div
382
+ className={className}
383
+ ref={(c) => (this._container = c)}
384
+ style={canvasDivStyle}
385
+ >
386
+ <canvas ref={(c) => (this._canvas = c)}>
387
+ Sorry, Canvas HTML5 element is not supported by your
388
+ browser.
389
+ </canvas>
390
+ </div>
391
+ );
392
+ }
393
+ }
394
+
395
+ export default SketchField;
@@ -0,0 +1,108 @@
1
+ import FabricCanvasTool from './fabrictool';
2
+ import {
3
+ Line as FabricLine,
4
+ Triangle as FabricTriangle,
5
+ Group as FabricGroup,
6
+ } from 'fabric'; // Import specific classes
7
+
8
+ class Arrow extends FabricCanvasTool {
9
+ configureCanvas(props) {
10
+ const canvas = this._canvas;
11
+
12
+ // Disable selection and set up drawing mode
13
+ canvas.isDrawingMode = false;
14
+ canvas.selection = false;
15
+ canvas.forEachObject((o) => {
16
+ o.selectable = false;
17
+ o.evented = false;
18
+ });
19
+
20
+ // Set arrow line properties
21
+ this._width = props.lineWidth || 1;
22
+ this._color = props.lineColor || 'black';
23
+ }
24
+
25
+ doMouseDown(o) {
26
+ this.isDown = true;
27
+ const canvas = this._canvas;
28
+
29
+ // Get initial mouse pointer position
30
+ const pointer = canvas.getPointer(o.e);
31
+ const points = [pointer.x, pointer.y, pointer.x, pointer.y];
32
+
33
+ // Create the main line of the arrow
34
+ this.line = new FabricLine(points, {
35
+ strokeWidth: this._width,
36
+ fill: this._color,
37
+ stroke: this._color,
38
+ originX: 'center',
39
+ originY: 'center',
40
+ selectable: false,
41
+ evented: false,
42
+ });
43
+
44
+ // Create the arrowhead as a triangle
45
+ this.head = new FabricTriangle({
46
+ fill: this._color,
47
+ left: pointer.x,
48
+ top: pointer.y,
49
+ originX: 'center',
50
+ originY: 'center',
51
+ height: 3 * this._width,
52
+ width: 3 * this._width,
53
+ selectable: false,
54
+ evented: false,
55
+ angle: 90,
56
+ });
57
+
58
+ // Add both line and head to the canvas
59
+ canvas.add(this.line);
60
+ canvas.add(this.head);
61
+ }
62
+
63
+ doMouseMove(o) {
64
+ if (!this.isDown) return;
65
+ const canvas = this._canvas;
66
+ const pointer = canvas.getPointer(o.e);
67
+
68
+ // Update the line endpoint as mouse moves
69
+ this.line.set({ x2: pointer.x, y2: pointer.y });
70
+ this.line.setCoords();
71
+
72
+ // Calculate angle for the arrowhead to point along the line
73
+ const xDelta = pointer.x - this.line.x1;
74
+ const yDelta = pointer.y - this.line.y1;
75
+ this.head.set({
76
+ left: pointer.x,
77
+ top: pointer.y,
78
+ angle: 90 + (Math.atan2(yDelta, xDelta) * 180) / Math.PI,
79
+ });
80
+
81
+ // Render changes
82
+ canvas.requestRenderAll();
83
+ }
84
+
85
+ doMouseUp() {
86
+ this.isDown = false;
87
+ const canvas = this._canvas;
88
+
89
+ // Remove separate line and head and group them into an arrow
90
+ canvas.remove(this.line);
91
+ canvas.remove(this.head);
92
+
93
+ const arrow = new FabricGroup([this.line, this.head], {
94
+ selectable: true,
95
+ evented: true,
96
+ });
97
+
98
+ // Add the grouped arrow back to the canvas
99
+ canvas.add(arrow);
100
+ canvas.requestRenderAll();
101
+ }
102
+
103
+ doMouseOut() {
104
+ this.isDown = false;
105
+ }
106
+ }
107
+
108
+ export default Arrow;
@@ -0,0 +1,75 @@
1
+ import FabricCanvasTool from './fabrictool';
2
+ import { Circle as FabricCircle } from 'fabric';
3
+
4
+ class Circle extends FabricCanvasTool {
5
+ constructor(canvas) {
6
+ super(canvas);
7
+ // Define event handlers as arrow functions for consistent references
8
+ this.doMouseDown = (o) => this._doMouseDown(o);
9
+ this.doMouseMove = (o) => this._doMouseMove(o);
10
+ this.doMouseUp = () => this._doMouseUp();
11
+ }
12
+
13
+ configureCanvas(props) {
14
+ const canvas = this._canvas;
15
+ canvas.isDrawingMode = false; // Disable drawing mode for custom drawing
16
+ canvas.selection = false;
17
+ this._width = props.lineWidth || 2;
18
+ this._color = props.lineColor || 'black';
19
+ this._fill = props.fillColor || 'transparent';
20
+ }
21
+
22
+ attachListeners() {
23
+ this._canvas.on('mouse:down', this.doMouseDown);
24
+ this._canvas.on('mouse:move', this.doMouseMove);
25
+ this._canvas.on('mouse:up', this.doMouseUp);
26
+ }
27
+
28
+ detachListeners() {
29
+ this._canvas.off('mouse:down', this.doMouseDown);
30
+ this._canvas.off('mouse:move', this.doMouseMove);
31
+ this._canvas.off('mouse:up', this.doMouseUp);
32
+ }
33
+
34
+ _doMouseDown(o) {
35
+ const canvas = this._canvas;
36
+ this.isDown = true;
37
+ const pointer = canvas.getPointer(o.e);
38
+ this.startX = pointer.x;
39
+ this.startY = pointer.y;
40
+
41
+ this.circle = new FabricCircle({
42
+ left: this.startX,
43
+ top: this.startY,
44
+ originX: 'center',
45
+ originY: 'center',
46
+ strokeWidth: this._width,
47
+ stroke: this._color,
48
+ fill: this._fill,
49
+ radius: 1, // Start with a minimal radius, updated on `doMouseMove`
50
+ selectable: false,
51
+ evented: false,
52
+ });
53
+ canvas.add(this.circle);
54
+ }
55
+
56
+ _doMouseMove(o) {
57
+ if (!this.isDown) return;
58
+ const canvas = this._canvas;
59
+ const pointer = canvas.getPointer(o.e);
60
+ const radius = Math.sqrt(
61
+ Math.pow(pointer.x - this.startX, 2) +
62
+ Math.pow(pointer.y - this.startY, 2)
63
+ );
64
+
65
+ this.circle.set({ radius });
66
+ this.circle.setCoords();
67
+ canvas.renderAll();
68
+ }
69
+
70
+ _doMouseUp() {
71
+ this.isDown = false;
72
+ }
73
+ }
74
+
75
+ export default Circle;
@@ -0,0 +1,16 @@
1
+ /*eslint no-unused-vars: 0*/
2
+
3
+ import FabricCanvasTool from './fabrictool';
4
+
5
+ class DefaultTool extends FabricCanvasTool {
6
+ configureCanvas(props) {
7
+ let canvas = this._canvas;
8
+ canvas.isDrawingMode = canvas.selection = false;
9
+ canvas.forEachObject((o) => (o.selectable = o.evented = false));
10
+ canvas.discardActiveObject();
11
+ canvas.defaultCursor = 'pointer';
12
+ canvas.renderAll();
13
+ }
14
+ }
15
+
16
+ export default DefaultTool;