@visns-studio/visns-components 4.10.47 → 5.0.0
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.
- package/package.json +28 -30
- package/src/components/cms/Field.jsx +39 -63
- package/src/components/crm/Breadcrumb.jsx +2 -18
- package/src/components/crm/DataGrid.jsx +23 -379
- package/src/components/crm/Field.jsx +16 -215
- package/src/components/crm/Form.jsx +81 -79
- package/src/components/crm/MultiSelect.jsx +25 -71
- package/src/components/crm/Navigation.jsx +7 -11
- package/src/components/crm/QrCode.jsx +8 -12
- package/src/components/crm/QuickAction.jsx +1 -0
- package/src/components/crm/generic/GenericAuth.jsx +2 -6
- package/src/components/crm/generic/GenericDashboard.jsx +30 -340
- package/src/components/crm/generic/GenericDetail.jsx +37 -80
- package/src/components/crm/generic/GenericDynamic.jsx +76 -94
- package/src/components/crm/generic/GenericFormBuilder.jsx +19 -79
- package/src/components/crm/generic/GenericIndex.jsx +1 -2
- package/src/components/crm/generic/styles/GenericDetail.module.scss +0 -51
- package/src/components/crm/generic/styles/GenericFormBuilder.module.scss +0 -5
- package/src/components/crm/styles/DataGrid.module.scss +25 -0
- package/src/components/crm/styles/Field.module.scss +0 -210
- package/src/components/crm/styles/Form.module.scss +0 -78
- package/src/components/crm/styles/Navigation.module.scss +4 -10
- package/src/components/crm/styles/QrCode.module.scss +0 -18
- package/src/index.js +0 -8
- package/src/components/crm/generic/styles/GenericDashboard.module.scss +0 -325
- package/src/components/crm/sketch/SketchField.jsx +0 -395
- package/src/components/crm/sketch/arrow.jsx +0 -108
- package/src/components/crm/sketch/circle.jsx +0 -75
- package/src/components/crm/sketch/default-tool.jsx +0 -16
- package/src/components/crm/sketch/fabrictool.jsx +0 -22
- package/src/components/crm/sketch/history.jsx +0 -144
- package/src/components/crm/sketch/json/config.json +0 -14
- package/src/components/crm/sketch/line.jsx +0 -64
- package/src/components/crm/sketch/pan.jsx +0 -48
- package/src/components/crm/sketch/pencil.jsx +0 -36
- package/src/components/crm/sketch/rectangle-label-object.jsx +0 -69
- package/src/components/crm/sketch/rectangle-label.jsx +0 -93
- package/src/components/crm/sketch/rectangle.jsx +0 -76
- package/src/components/crm/sketch/select.jsx +0 -16
- package/src/components/crm/sketch/tools.jsx +0 -11
- package/src/components/crm/sketch/utils.jsx +0 -67
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
body {
|
|
2
|
-
min-height: 100vh;
|
|
3
|
-
background: var(--bg);
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
.grid {
|
|
7
|
-
display: flex;
|
|
8
|
-
flex-direction: column;
|
|
9
|
-
gap: 2rem;
|
|
10
|
-
padding: 2rem;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
.filterContainer {
|
|
14
|
-
background: #ffffff; // White background for contrast
|
|
15
|
-
border: 1px solid #dadce0; // Subtle border for separation
|
|
16
|
-
border-radius: 8px; // Rounded corners
|
|
17
|
-
padding: 1.5rem;
|
|
18
|
-
margin-bottom: 1.5rem; // Spacing below the filter section
|
|
19
|
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); // Subtle shadow for depth
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
.filterHeader {
|
|
23
|
-
text-align: left;
|
|
24
|
-
margin-bottom: 1rem;
|
|
25
|
-
|
|
26
|
-
h3 {
|
|
27
|
-
font-size: 1.25rem;
|
|
28
|
-
font-weight: 600;
|
|
29
|
-
color: #333;
|
|
30
|
-
margin: 0;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.filterFields {
|
|
35
|
-
display: flex;
|
|
36
|
-
flex-wrap: wrap;
|
|
37
|
-
gap: 1rem; // Gap between fields
|
|
38
|
-
align-items: flex-end; // Align fields vertically
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.filterField {
|
|
42
|
-
flex: 1; // Ensures equal spacing for each field
|
|
43
|
-
display: flex;
|
|
44
|
-
flex-direction: column;
|
|
45
|
-
min-width: 200px; // Sets a minimum width for smaller screens
|
|
46
|
-
|
|
47
|
-
label {
|
|
48
|
-
font-size: 0.875rem;
|
|
49
|
-
font-weight: 600;
|
|
50
|
-
margin-bottom: 0.5rem;
|
|
51
|
-
color: #555;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
input,
|
|
55
|
-
select {
|
|
56
|
-
border: 1px solid #dadce0;
|
|
57
|
-
border-radius: 5px;
|
|
58
|
-
padding: 0.5rem 0.75rem;
|
|
59
|
-
font-size: 0.875rem;
|
|
60
|
-
color: #333;
|
|
61
|
-
outline: none;
|
|
62
|
-
background-color: #f9f9f9;
|
|
63
|
-
width: 100%; // Ensures full width within the container
|
|
64
|
-
height: 51px;
|
|
65
|
-
|
|
66
|
-
&:focus {
|
|
67
|
-
border-color: #007bff;
|
|
68
|
-
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); // Focus effect
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.filterActions {
|
|
74
|
-
display: flex;
|
|
75
|
-
gap: 0.75rem; // Gap between buttons
|
|
76
|
-
|
|
77
|
-
.btn {
|
|
78
|
-
padding: 0.5rem 1rem;
|
|
79
|
-
border-radius: 5px;
|
|
80
|
-
font-size: 0.875rem;
|
|
81
|
-
font-weight: 600;
|
|
82
|
-
text-transform: uppercase;
|
|
83
|
-
cursor: pointer;
|
|
84
|
-
transition: background-color 0.2s;
|
|
85
|
-
height: 51px;
|
|
86
|
-
|
|
87
|
-
&.btn-primary {
|
|
88
|
-
background-color: #007bff;
|
|
89
|
-
color: #fff;
|
|
90
|
-
border: none;
|
|
91
|
-
|
|
92
|
-
&:hover {
|
|
93
|
-
background-color: #0056b3;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
&.btn-secondary {
|
|
98
|
-
background-color: #f8f9fa;
|
|
99
|
-
color: #333;
|
|
100
|
-
border: 1px solid #ccc;
|
|
101
|
-
|
|
102
|
-
&:hover {
|
|
103
|
-
background-color: #e2e6ea;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.widgetTitle {
|
|
110
|
-
width: 100%;
|
|
111
|
-
height: auto;
|
|
112
|
-
padding: 0 0 1em 0;
|
|
113
|
-
margin-bottom: 1em;
|
|
114
|
-
border-bottom: 1px dashed #dadce0;
|
|
115
|
-
|
|
116
|
-
h2 {
|
|
117
|
-
font-size: 1.375em;
|
|
118
|
-
font-weight: 700;
|
|
119
|
-
margin: 0;
|
|
120
|
-
padding: 0;
|
|
121
|
-
line-height: 1;
|
|
122
|
-
text-transform: capitalize;
|
|
123
|
-
text-align: left;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.dashList {
|
|
128
|
-
width: 100%;
|
|
129
|
-
display: flex;
|
|
130
|
-
flex-wrap: wrap;
|
|
131
|
-
list-style: none;
|
|
132
|
-
padding: 0;
|
|
133
|
-
margin: 0;
|
|
134
|
-
|
|
135
|
-
li {
|
|
136
|
-
width: 100%;
|
|
137
|
-
padding: 0;
|
|
138
|
-
margin: 0;
|
|
139
|
-
|
|
140
|
-
&:first-child {
|
|
141
|
-
button {
|
|
142
|
-
border-top-left-radius: var(--br);
|
|
143
|
-
border-top-right-radius: var(--br);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
&:last-child {
|
|
148
|
-
margin-bottom: 0;
|
|
149
|
-
|
|
150
|
-
button {
|
|
151
|
-
border-bottom-left-radius: var(--br);
|
|
152
|
-
border-bottom-right-radius: var(--br);
|
|
153
|
-
border-bottom: 1px solid #dadce0;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
a {
|
|
158
|
-
text-decoration: none;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
button {
|
|
162
|
-
width: 100%;
|
|
163
|
-
display: block;
|
|
164
|
-
background: white;
|
|
165
|
-
border: 1px solid #dadce0;
|
|
166
|
-
border-bottom: none;
|
|
167
|
-
outline: none;
|
|
168
|
-
padding: 0.5rem 1rem;
|
|
169
|
-
color: var(--paragraph-color);
|
|
170
|
-
text-align: left;
|
|
171
|
-
transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
172
|
-
letter-spacing: 0.018em;
|
|
173
|
-
|
|
174
|
-
&:hover {
|
|
175
|
-
background: rgba(var(--secondary-color), 0.05);
|
|
176
|
-
transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
strong {
|
|
180
|
-
color: var(--paragraph-color);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
span {
|
|
184
|
-
color: rgba(var(--secondary-color-rgb), 1.1);
|
|
185
|
-
float: right;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.grid__row {
|
|
192
|
-
width: 100%;
|
|
193
|
-
display: flex;
|
|
194
|
-
align-items: flex-start;
|
|
195
|
-
flex-wrap: nowrap;
|
|
196
|
-
height: auto;
|
|
197
|
-
gap: 1rem;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
.grid__dashfull {
|
|
201
|
-
width: 100%;
|
|
202
|
-
background: var(--item-color);
|
|
203
|
-
border-radius: var(--br);
|
|
204
|
-
border: 1px solid rgba(var(--item-color-rgb), 1.15);
|
|
205
|
-
box-sizing: border-box;
|
|
206
|
-
padding: 20px;
|
|
207
|
-
margin: 8px 0;
|
|
208
|
-
box-shadow: 0 4px 0 rgba(var(--primary-color-rgb), 0.05);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
.tableContainer {
|
|
212
|
-
margin-top: 20px;
|
|
213
|
-
border: 1px solid #dadce0;
|
|
214
|
-
border-radius: 8px;
|
|
215
|
-
overflow: hidden;
|
|
216
|
-
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.tableContainer table {
|
|
220
|
-
width: 100%;
|
|
221
|
-
border-collapse: collapse;
|
|
222
|
-
text-align: left;
|
|
223
|
-
background-color: #ffffff;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
.tableContainer th {
|
|
227
|
-
background-color: #f6f8fa;
|
|
228
|
-
color: #333;
|
|
229
|
-
font-weight: bold;
|
|
230
|
-
padding: 12px 15px;
|
|
231
|
-
border-bottom: 1px solid #dadce0;
|
|
232
|
-
text-transform: capitalize;
|
|
233
|
-
font-size: 14px;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.tableContainer td {
|
|
237
|
-
padding: 10px 15px;
|
|
238
|
-
border-bottom: 1px solid #eaeaea;
|
|
239
|
-
font-size: 13px;
|
|
240
|
-
color: #555;
|
|
241
|
-
text-align: center;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.tableContainer tbody tr:hover {
|
|
245
|
-
background-color: #f9f9f9;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.tableContainer tbody tr:nth-child(even) {
|
|
249
|
-
background-color: #f7f7f7;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.tableContainer th:first-child,
|
|
253
|
-
.tableContainer td:first-child {
|
|
254
|
-
text-align: left;
|
|
255
|
-
font-weight: bold;
|
|
256
|
-
padding-left: 20px;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
.tableContainer td:last-child {
|
|
260
|
-
border-right: none;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
.tableContainer th:last-child {
|
|
264
|
-
border-right: none;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
.dashwidget {
|
|
268
|
-
background: var(--primary-color) !important;
|
|
269
|
-
border-top: 1px solid transparent !important;
|
|
270
|
-
border-left: 1px solid transparent !important;
|
|
271
|
-
border-right: 1px solid transparent !important;
|
|
272
|
-
border-bottom: 4px solid rgba(var(--primary-color-rgb), 0.9) !important;
|
|
273
|
-
|
|
274
|
-
h2 {
|
|
275
|
-
color: var(--highlight-color) !important;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
.widgetTitle {
|
|
279
|
-
border-bottom: 1px dashed rgba(#dadce0, 0.15);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
.grid__contentlist {
|
|
284
|
-
li {
|
|
285
|
-
position: relative;
|
|
286
|
-
|
|
287
|
-
&:hover {
|
|
288
|
-
.edititem {
|
|
289
|
-
opacity: 1;
|
|
290
|
-
transition: opacity 0.15s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
.edititem {
|
|
295
|
-
position: absolute;
|
|
296
|
-
right: 0;
|
|
297
|
-
text-decoration: none;
|
|
298
|
-
opacity: 0;
|
|
299
|
-
transition: opacity 0.15s cubic-bezier(0.25, 0.8, 0.25, 1);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
.react-datepicker__month-select,
|
|
305
|
-
.react-datepicker__year-select {
|
|
306
|
-
padding: 0.5em 0.85em;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
.react-confirm-alert-overlay {
|
|
310
|
-
z-index: 999 !important;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
.polActions {
|
|
314
|
-
position: fixed;
|
|
315
|
-
bottom: 15px;
|
|
316
|
-
right: 20px;
|
|
317
|
-
width: auto;
|
|
318
|
-
|
|
319
|
-
button {
|
|
320
|
-
display: block;
|
|
321
|
-
float: left;
|
|
322
|
-
padding: 0.35em 1em;
|
|
323
|
-
margin: 0 0.15em;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
@@ -1,395 +0,0 @@
|
|
|
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;
|