@visns-studio/visns-components 4.10.14 → 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.14",
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
@@ -1700,6 +1755,7 @@ function Field({
1700
1755
  'address',
1701
1756
  'async-dropdown-ajax',
1702
1757
  'checkbox',
1758
+ 'canvas',
1703
1759
  'colour',
1704
1760
  'copytoclipboard',
1705
1761
  'date',
@@ -1897,6 +1953,126 @@ function Field({
1897
1953
  updateForm={setChildForm}
1898
1954
  />
1899
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>
1900
2076
  </div>
1901
2077
  );
1902
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
 
@@ -550,27 +583,29 @@ const GenericDynamic = ({
550
583
  });
551
584
  };
552
585
 
553
- const handleSaveForm = async () => {
586
+ const handleSaveForm = async (validate = true) => {
554
587
  try {
555
588
  let error = '';
556
589
 
557
- const excludedTypes = [
558
- 'dynamicdata',
559
- 'plaintext',
560
- 'plaintextheading',
561
- 'section',
562
- 'table_radio',
563
- ];
590
+ if (validate) {
591
+ const excludedTypes = [
592
+ 'dynamicdata',
593
+ 'plaintext',
594
+ 'plaintextheading',
595
+ 'section',
596
+ 'table_radio',
597
+ ];
564
598
 
565
- Object.entries(activeForm).forEach(([key, field]) => {
566
- if (
567
- field.required === true &&
568
- !excludedTypes.includes(field.type) &&
569
- (field.value === null || field.value === '')
570
- ) {
571
- error += `<p>${field.label} is required</p>`;
572
- }
573
- });
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
+ }
574
609
 
575
610
  if (error === '') {
576
611
  const res = await CustomFetch(setting.save, 'POST', {
@@ -836,6 +871,9 @@ const GenericDynamic = ({
836
871
  onChange={
837
872
  handleChange
838
873
  }
874
+ onChangeCanvas={
875
+ handleChangeCanvas
876
+ }
839
877
  onChangeCheckbox={
840
878
  handleChangeCheckbox
841
879
  }
@@ -884,6 +922,9 @@ const GenericDynamic = ({
884
922
  originalData
885
923
  )}
886
924
  onChange={handleChange}
925
+ onChangeCanvas={
926
+ handleChangeCanvas
927
+ }
887
928
  onChangeCheckbox={
888
929
  handleChangeCheckbox
889
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}>