@visns-studio/visns-components 4.10.13 → 4.10.15

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 CHANGED
@@ -16,6 +16,7 @@
16
16
  "awesome-debounce-promise": "^2.1.0",
17
17
  "axios": "^1.7.7",
18
18
  "dayjs": "^1.11.13",
19
+ "fabric": "^6.4.3",
19
20
  "file-saver": "^2.0.5",
20
21
  "framer-motion": "^11.11.9",
21
22
  "html-react-parser": "^5.1.18",
@@ -75,7 +76,7 @@
75
76
  "react-dom": "^17.0.0 || ^18.0.0"
76
77
  },
77
78
  "name": "@visns-studio/visns-components",
78
- "version": "4.10.13",
79
+ "version": "4.10.15",
79
80
  "description": "Various packages to assist in the development of our Custom Applications.",
80
81
  "main": "src/index.js",
81
82
  "files": [
@@ -96,5 +97,5 @@
96
97
  "url": "https://github.com/visnsstudio/visns-components/issues"
97
98
  },
98
99
  "homepage": "https://github.com/visnsstudio/visns-components#readme",
99
- "packageManager": "yarn@4.4.0"
100
+ "packageManager": "yarn@4.5.1"
100
101
  }
@@ -11,7 +11,17 @@ import { BlockPicker } from 'react-color';
11
11
  import { CopyToClipboard } from 'react-copy-to-clipboard';
12
12
  import { NumericFormat, PatternFormat } from 'react-number-format';
13
13
  import { Editor } from '@tinymce/tinymce-react';
14
- import { CirclePlus, CloudDownload, Copy, TrashCan } from 'akar-icons';
14
+ import {
15
+ Edit,
16
+ ChevronRight,
17
+ Circle,
18
+ CircleFill,
19
+ CloudDownload,
20
+ Minus,
21
+ ArrowBack,
22
+ TrashCan,
23
+ CircleX,
24
+ } from 'akar-icons';
15
25
  import { toast } from 'react-toastify';
16
26
  import styles from './styles/Field.module.scss';
17
27
 
@@ -19,13 +29,15 @@ import CustomFetch from './Fetch';
19
29
  import Form from './Form';
20
30
  import MultiSelect from './MultiSelect';
21
31
  import Select from './Select';
32
+ import SketchConfig from './sketch/json/config.json';
33
+ import SketchField from './sketch/SketchField';
34
+ import Tools from './sketch/tools';
22
35
  import VisnsAsyncSelect from './AsyncSelect';
23
36
  import VisnsAutocomplete from './Autocomplete';
24
37
  import VisnsDropZone from '../cms/DropZone';
25
38
 
26
39
  import 'react-toggle/style.css';
27
40
  import 'react-datepicker/dist/react-datepicker.css';
28
- import Item from '../cms/sorting/Item';
29
41
 
30
42
  function Field({
31
43
  api,
@@ -38,6 +50,7 @@ function Field({
38
50
  inputClass,
39
51
  mapbox,
40
52
  onChange,
53
+ onChangeCanvas,
41
54
  onChangeCheckbox,
42
55
  onChangeCheckboxManual,
43
56
  onChangeColour,
@@ -65,6 +78,30 @@ function Field({
65
78
  const [formItemClass, setFormItemClass] = useState('');
66
79
  const [options, setOptions] = useState([]);
67
80
 
81
+ /** Canvas States */
82
+ const [canvasPopupOpen, setCanvasPopupOpen] = useState(false);
83
+ const [canvasUrl, setCanvasUrl] = useState('');
84
+ const [sketchTool, setSketchTool] = useState(Tools.Pencil);
85
+ const [toolClass, setToolClass] = useState('pencil');
86
+ const [lineWidth, setLineWidth] = useState(2);
87
+ const [lineColor, setLineColor] = useState('rgba(0, 0, 0, 1)');
88
+ const sketchRef = useRef(null);
89
+
90
+ /** Canvas Functions */
91
+ const handleCanvasSave = async () => {
92
+ // Save the current canvas state
93
+ const canvasState = sketchRef.current.saveCanvasState();
94
+
95
+ // Export the canvas image data as a Base64 string
96
+ const imageData = await sketchRef.current.exportCanvasImage(); // Use await here
97
+
98
+ // Call the onChangeCanvas function with both values
99
+ onChangeCanvas(settings, canvasState, imageData);
100
+
101
+ // Close the canvas popup
102
+ setCanvasPopupOpen(false);
103
+ };
104
+
68
105
  const cover = {
69
106
  position: 'fixed',
70
107
  top: '0px',
@@ -154,6 +191,13 @@ function Field({
154
191
  setFormItemClass(`${styles.formItem}`);
155
192
  break;
156
193
  }
194
+
195
+ if (settings.type === 'canvas') {
196
+ const canvasType = SketchConfig.canvasTypes.find(
197
+ (a) => a.id === settings.canvasType
198
+ );
199
+ setCanvasUrl(canvasType.url);
200
+ }
157
201
  }, [settings, counter]);
158
202
 
159
203
  const fetchChildDropdownData = (s, v) => {
@@ -258,6 +302,7 @@ function Field({
258
302
  'address',
259
303
  'async-dropdown-ajax',
260
304
  'checkbox',
305
+ 'canvas',
261
306
  'colour',
262
307
  'copytoclipboard',
263
308
  'date',
@@ -386,6 +431,16 @@ function Field({
386
431
  dataOptions={dataOptions}
387
432
  />
388
433
  );
434
+ case 'canvas':
435
+ return canvasUrl ? (
436
+ <div
437
+ onClick={() => setCanvasPopupOpen(true)}
438
+ style={{ cursor: 'pointer' }}
439
+ >
440
+ <img src={canvasUrl} width="200" alt="Canvas Preview" />
441
+ </div>
442
+ ) : null;
443
+
389
444
  case 'checkbox':
390
445
  return (
391
446
  <div
@@ -1469,13 +1524,7 @@ function Field({
1469
1524
  </td>
1470
1525
  {tableRadioColumns.map((cell, cellKey) => (
1471
1526
  <td
1472
- key={`table-radio-cell-${
1473
- settings.id
1474
- }-${row.id}-${
1475
- cell.id
1476
- }-${Math.random()
1477
- .toString(36)
1478
- .substr(2, 9)}`}
1527
+ key={`table-radio-cell-${settings.id}-${row.id}-${cell.id}`}
1479
1528
  style={{
1480
1529
  textAlign:
1481
1530
  tableRadioColumns[cellKey]
@@ -1706,6 +1755,7 @@ function Field({
1706
1755
  'address',
1707
1756
  'async-dropdown-ajax',
1708
1757
  'checkbox',
1758
+ 'canvas',
1709
1759
  'colour',
1710
1760
  'copytoclipboard',
1711
1761
  'date',
@@ -1903,6 +1953,126 @@ function Field({
1903
1953
  updateForm={setChildForm}
1904
1954
  />
1905
1955
  </Popup>
1956
+ <Popup
1957
+ open={canvasPopupOpen}
1958
+ onClose={() => setCanvasPopupOpen(false)}
1959
+ closeOnDocumentClick={false}
1960
+ >
1961
+ <div className={`${styles.modalwrap} ${styles['top--modal']}`}>
1962
+ <div className={styles.modal}>
1963
+ <div className={styles.modal__header}>
1964
+ <h1>Edit Canvas</h1>
1965
+ <button
1966
+ className={styles.modal__close}
1967
+ onClick={() => setCanvasPopupOpen(false)}
1968
+ >
1969
+ <CircleX size={24} color="#FFF" />
1970
+ </button>
1971
+ </div>
1972
+ <div
1973
+ className={`${styles.modal__content} ${styles.drawDiv}`}
1974
+ >
1975
+ {/* Sketch Tools */}
1976
+ <ul className={styles.drawactions}>
1977
+ <li>
1978
+ <button
1979
+ onClick={() => {
1980
+ handleCanvasSave();
1981
+ }}
1982
+ className={styles['drawactions--save']}
1983
+ >
1984
+ <CloudDownload size={16} /> Save
1985
+ </button>
1986
+ </li>
1987
+ <li>
1988
+ <button
1989
+ onClick={() => {
1990
+ setSketchTool(Tools.Pencil);
1991
+ setToolClass('pencil');
1992
+ }}
1993
+ className={`${
1994
+ styles['drawaction-pencil']
1995
+ } ${
1996
+ toolClass === 'pencil'
1997
+ ? styles['drawactive']
1998
+ : ''
1999
+ }`}
2000
+ >
2001
+ <Edit size={16} /> Pencil
2002
+ </button>
2003
+ </li>
2004
+ <li>
2005
+ <button
2006
+ onClick={() => {
2007
+ setSketchTool(Tools.Line);
2008
+
2009
+ setToolClass('line');
2010
+ }}
2011
+ className={`${
2012
+ styles['drawaction-line']
2013
+ } ${
2014
+ toolClass === 'line'
2015
+ ? styles['drawactive']
2016
+ : ''
2017
+ }`}
2018
+ >
2019
+ <Minus size={16} /> Line
2020
+ </button>
2021
+ </li>
2022
+ <li>
2023
+ <button
2024
+ onClick={() => {
2025
+ setSketchTool(Tools.Circle);
2026
+
2027
+ setToolClass('circle');
2028
+ }}
2029
+ className={`${
2030
+ styles['drawaction-circle']
2031
+ } ${
2032
+ toolClass === 'circle'
2033
+ ? styles['drawactive']
2034
+ : ''
2035
+ }`}
2036
+ >
2037
+ <Circle size={16} /> Circle
2038
+ </button>
2039
+ </li>
2040
+ <li>
2041
+ <button
2042
+ onClick={() => sketchRef.current.undo()}
2043
+ className={styles['drawaction-undo']}
2044
+ >
2045
+ <ArrowBack size={16} /> Undo
2046
+ </button>
2047
+ </li>
2048
+ <li>
2049
+ <button
2050
+ onClick={() =>
2051
+ sketchRef.current.clearCanvas()
2052
+ }
2053
+ className={styles['drawactions--clear']}
2054
+ >
2055
+ <TrashCan size={16} /> Clear
2056
+ </button>
2057
+ </li>
2058
+ </ul>
2059
+ {(canvasUrl || inputValue.state) && (
2060
+ <SketchField
2061
+ ref={sketchRef}
2062
+ width={740}
2063
+ height={800}
2064
+ tool={sketchTool}
2065
+ lineColor={lineColor}
2066
+ lineWidth={lineWidth}
2067
+ className={styles.drawcontainer}
2068
+ backgroundImage={canvasUrl}
2069
+ defaultValue={inputValue.state || null}
2070
+ />
2071
+ )}
2072
+ </div>
2073
+ </div>
2074
+ </div>
2075
+ </Popup>
1906
2076
  </div>
1907
2077
  );
1908
2078
  }
@@ -151,6 +151,8 @@ function Form({
151
151
  }
152
152
  };
153
153
 
154
+ const handleChangeCanvas = (id, value) => {};
155
+
154
156
  const handleChangeCheckbox = (e) => {
155
157
  const { checked, name, value } = e.target;
156
158
 
@@ -1367,15 +1369,18 @@ function Form({
1367
1369
  );
1368
1370
  } else {
1369
1371
  return (
1370
- <div className="modalwrap top--modal">
1371
- <div className="modal">
1372
- <div className="modal__header">
1372
+ <div className={`${styles.modalwrap} ${styles['top--modal']}`}>
1373
+ <div className={styles.modal}>
1374
+ <div className={styles.modal__header}>
1373
1375
  <h1>{formSettings[formType]?.title}</h1>
1374
- <button className="modal__close" onClick={closeModal}>
1376
+ <button
1377
+ className={styles.modal__close}
1378
+ onClick={closeModal}
1379
+ >
1375
1380
  <CircleX strokeWidth={1} size={24} />
1376
1381
  </button>
1377
1382
  </div>
1378
- <div className="modal__content">
1383
+ <div className={styles.modal__content}>
1379
1384
  <div className={styles.formcontainer}>
1380
1385
  <form className={styles.modalForm}>
1381
1386
  {formSettings?.fields?.map((item, index) => {
@@ -1426,6 +1431,7 @@ function Form({
1426
1431
  }
1427
1432
  mapbox={mapbox}
1428
1433
  onChange={handleChange}
1434
+ onChangeCanvas={handleChangeCanvas}
1429
1435
  onChangeCheckbox={
1430
1436
  handleChangeCheckbox
1431
1437
  }
@@ -31,6 +31,8 @@ const updateField = (fields, name, value) => {
31
31
 
32
32
  const renderValue = (field, data) => {
33
33
  switch (field.type) {
34
+ case 'canvas':
35
+ return field.value || '';
34
36
  case 'datetime':
35
37
  case 'date':
36
38
  return field.value && moment(field.value).isValid()
@@ -103,6 +105,7 @@ const GenericDynamic = ({
103
105
  }) => {
104
106
  const [activeForm, setActiveForm] = useState({});
105
107
  const [activeFormKey, setActiveFormKey] = useState(0);
108
+ const [canvasSaveCounter, setCanvasSaveCounter] = useState(0);
106
109
 
107
110
  const handleChange = (e) => {
108
111
  const {
@@ -174,6 +177,36 @@ const GenericDynamic = ({
174
177
  }
175
178
  };
176
179
 
180
+ const handleChangeCanvas = (fieldSetting, value, imageData) => {
181
+ // Check if activeForm has keys
182
+ if (activeForm && typeof activeForm === 'object') {
183
+ // Iterate over the keys in activeForm to find the matching id
184
+ for (const key in activeForm) {
185
+ if (activeForm[key].id === fieldSetting.id) {
186
+ // If a matching id is found, update the corresponding field
187
+ setActiveForm((prevState) => ({
188
+ ...prevState,
189
+ [key]: {
190
+ ...prevState[key],
191
+ value, // Update the value for the matching id
192
+ imageData,
193
+ },
194
+ }));
195
+
196
+ setCanvasSaveCounter((prevState) => prevState + 1);
197
+ return; // Exit after updating to prevent further iterations
198
+ }
199
+ }
200
+ }
201
+ };
202
+
203
+ useEffect(() => {
204
+ if (canvasSaveCounter > 0) {
205
+ console.info(activeForm);
206
+ handleSaveForm(false);
207
+ }
208
+ }, [canvasSaveCounter]);
209
+
177
210
  const handleChangeCheckbox = (e) => {
178
211
  const { checked, name, value } = e.target;
179
212
 
@@ -247,33 +280,20 @@ const GenericDynamic = ({
247
280
  return prevActiveForm;
248
281
  }
249
282
 
250
- // Create a new rows array with updated value properties based on input type
283
+ // Create a shallow copy of rows to target only the necessary updates
251
284
  const updatedRows = prevActiveForm[formKey].rows.map((row) => {
252
285
  if (row.id === rowId) {
253
- // Determine new value based on input type
254
- let newValue;
255
- if (type === 'checkbox') {
256
- newValue = { ...row.value, [columnId]: checked };
257
- } else if (
258
- type === 'date' ||
259
- type === 'time' ||
260
- type === 'text'
261
- ) {
262
- newValue = { ...row.value, [columnId]: value };
263
- } else {
264
- // For other input types, use the value directly
265
- newValue = { ...row.value, [columnId]: value };
266
- }
267
-
268
- return {
269
- ...row,
270
- value: newValue,
286
+ // Update only the specific cell in the target row
287
+ const newValue = {
288
+ ...row.value,
289
+ [columnId]: type === 'checkbox' ? checked : value,
271
290
  };
291
+ return { ...row, value: newValue }; // Return the updated row
272
292
  }
273
- return row;
293
+ return row; // Return other rows unchanged
274
294
  });
275
295
 
276
- // Return the updated activeForm state
296
+ // Update only the formKey section of activeForm
277
297
  return {
278
298
  ...prevActiveForm,
279
299
  [formKey]: {
@@ -563,27 +583,29 @@ const GenericDynamic = ({
563
583
  });
564
584
  };
565
585
 
566
- const handleSaveForm = async () => {
586
+ const handleSaveForm = async (validate = true) => {
567
587
  try {
568
588
  let error = '';
569
589
 
570
- const excludedTypes = [
571
- 'dynamicdata',
572
- 'plaintext',
573
- 'plaintextheading',
574
- 'section',
575
- 'table_radio',
576
- ];
590
+ if (validate) {
591
+ const excludedTypes = [
592
+ 'dynamicdata',
593
+ 'plaintext',
594
+ 'plaintextheading',
595
+ 'section',
596
+ 'table_radio',
597
+ ];
577
598
 
578
- Object.entries(activeForm).forEach(([key, field]) => {
579
- if (
580
- field.required === true &&
581
- !excludedTypes.includes(field.type) &&
582
- (field.value === null || field.value === '')
583
- ) {
584
- error += `<p>${field.label} is required</p>`;
585
- }
586
- });
599
+ Object.entries(activeForm).forEach(([key, field]) => {
600
+ if (
601
+ field.required === true &&
602
+ !excludedTypes.includes(field.type) &&
603
+ (field.value === null || field.value === '')
604
+ ) {
605
+ error += `<p>${field.label} is required</p>`;
606
+ }
607
+ });
608
+ }
587
609
 
588
610
  if (error === '') {
589
611
  const res = await CustomFetch(setting.save, 'POST', {
@@ -849,6 +871,9 @@ const GenericDynamic = ({
849
871
  onChange={
850
872
  handleChange
851
873
  }
874
+ onChangeCanvas={
875
+ handleChangeCanvas
876
+ }
852
877
  onChangeCheckbox={
853
878
  handleChangeCheckbox
854
879
  }
@@ -897,6 +922,9 @@ const GenericDynamic = ({
897
922
  originalData
898
923
  )}
899
924
  onChange={handleChange}
925
+ onChangeCanvas={
926
+ handleChangeCanvas
927
+ }
900
928
  onChangeCheckbox={
901
929
  handleChangeCheckbox
902
930
  }
@@ -8,7 +8,7 @@ import Toggle from 'react-toggle';
8
8
  import { toast } from 'react-toastify';
9
9
  import { v4 as uuidv4 } from 'uuid';
10
10
  import { Editor } from '@tinymce/tinymce-react';
11
- import { CircleChevronRight, CirclePlus, CircleX, TrashCan } from 'akar-icons';
11
+ import { CirclePlus, CircleX, TrashCan } from 'akar-icons';
12
12
 
13
13
  import 'react-toggle/style.css';
14
14
  import 'react-datepicker/dist/react-datepicker.css';
@@ -17,6 +17,7 @@ import 'react-confirm-alert/src/react-confirm-alert.css';
17
17
  import Breadcrumb from '../Breadcrumb';
18
18
  import CustomFetch from '../Fetch';
19
19
  import SortableList from '../../../components/cms/sorting/List';
20
+ import SketchConfig from '../sketch/json/config.json';
20
21
 
21
22
  import styles from './styles/GenericFormBuilder.module.scss'; // Import the CSS module
22
23
 
@@ -44,7 +45,8 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
44
45
  label: '',
45
46
  type: '',
46
47
  });
47
- const [fieldTypes, setFieldTypes] = useState([
48
+ const [fieldTypes] = useState([
49
+ { value: 'canvas', label: 'Canvas' },
48
50
  { value: 'checkbox', label: 'Checkbox' },
49
51
  { value: 'html5_date', label: 'Date' },
50
52
  { value: 'html5_datetime', label: 'Date & Time' },
@@ -78,6 +80,7 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
78
80
  });
79
81
  const [modalFormShow, setModalFormShow] = useState(false);
80
82
  const [roles, setRoles] = useState([]);
83
+ const canvasTypes = SketchConfig.canvasTypes;
81
84
 
82
85
  /** Data Option Functionalities */
83
86
  const handleDataOption = (e) => {
@@ -428,6 +431,21 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
428
431
 
429
432
  const renderField = (field) => {
430
433
  switch (field.type) {
434
+ case 'canvas':
435
+ const canvasType = canvasTypes.find(
436
+ (item) => item.id === field.canvasType
437
+ );
438
+ return (
439
+ <label className={`${styles.fi__label}`}>
440
+ <span
441
+ className={`${styles.fi__span} ${styles.prefocus}`}
442
+ >
443
+ {field.label}{' '}
444
+ {field.required === 'yes' ? '*' : null}
445
+ </span>
446
+ <img src={canvasType.url} width="200" />
447
+ </label>
448
+ );
431
449
  case 'checkbox':
432
450
  return (
433
451
  <label className={styles.fi__label}>
@@ -1679,6 +1697,46 @@ function GenericFormBuilder({ setting, urlParam, userProfile }) {
1679
1697
  </div>
1680
1698
  ) : null}
1681
1699
 
1700
+ {['canvas'].includes(dataField.type) ? (
1701
+ <div className={styles.formItem}>
1702
+ <label className={styles.fi__label}>
1703
+ <select
1704
+ name="canvasType"
1705
+ value={dataField.canvasType}
1706
+ onChange={handleChangeForm}
1707
+ >
1708
+ <option>
1709
+ Please select a type
1710
+ </option>
1711
+ {canvasTypes.map(
1712
+ (
1713
+ canvasType,
1714
+ canvasTypeKey
1715
+ ) => (
1716
+ <option
1717
+ value={
1718
+ canvasType.id
1719
+ }
1720
+ key={
1721
+ canvasTypeKey
1722
+ }
1723
+ >
1724
+ {
1725
+ canvasType.label
1726
+ }
1727
+ </option>
1728
+ )
1729
+ )}
1730
+ </select>
1731
+ <span
1732
+ className={styles.fi__span}
1733
+ >
1734
+ Canvas Type
1735
+ </span>
1736
+ </label>
1737
+ </div>
1738
+ ) : null}
1739
+
1682
1740
  {['signature'].includes(dataField.type) ? (
1683
1741
  <div className={styles.formItem}>
1684
1742
  <label className={styles.fi__label}>