@saltcorn/builder 0.9.3-beta.8 → 0.9.3-rc.1

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/builder",
3
- "version": "0.9.3-beta.8",
3
+ "version": "0.9.3-rc.1",
4
4
  "description": "Drag and drop view builder for Saltcorn, open-source no-code platform",
5
5
  "main": "index.js",
6
6
  "homepage": "https://saltcorn.com",
@@ -31,6 +31,7 @@ import { DropMenu } from "./elements/DropMenu";
31
31
  import { ToggleFilter } from "./elements/ToggleFilter";
32
32
  import optionsCtx from "./context";
33
33
  import PreviewCtx from "./preview_context";
34
+ import RelationsCtx from "./relations_context";
34
35
  import {
35
36
  ToolboxShow,
36
37
  ToolboxEdit,
@@ -390,6 +391,7 @@ const Builder = ({ options, layout, mode }) => {
390
391
  const [uploadedFiles, setUploadedFiles] = useState([]);
391
392
  const nodekeys = useRef([]);
392
393
  const [isSaving, setIsSaving] = useState(false);
394
+ const [relationsCache, setRelationsCache] = useState({});
393
395
 
394
396
  return (
395
397
  <ErrorBoundary>
@@ -398,83 +400,90 @@ const Builder = ({ options, layout, mode }) => {
398
400
  <PreviewCtx.Provider
399
401
  value={{ previews, setPreviews, uploadedFiles, setUploadedFiles }}
400
402
  >
401
- <div className="row" style={{ marginTop: "-5px" }}>
402
- <div className="col-sm-auto left-builder-col">
403
- <div className="componets-and-library-accordion toolbox-card">
404
- <InitNewElement
405
- nodekeys={nodekeys}
406
- setIsSaving={setIsSaving}
407
- />
408
- <Accordion>
409
- <div className="card mt-1" accordiontitle="Components">
410
- {{
411
- show: <ToolboxShow />,
412
- edit: <ToolboxEdit />,
413
- page: <ToolboxPage />,
414
- filter: <ToolboxFilter />,
415
- }[mode] || <div>Missing mode</div>}
416
- </div>
417
- <div accordiontitle="Library">
418
- <Library />
419
- </div>
420
- </Accordion>
403
+ <RelationsCtx.Provider
404
+ value={{
405
+ relationsCache,
406
+ setRelationsCache,
407
+ }}
408
+ >
409
+ <div className="row" style={{ marginTop: "-5px" }}>
410
+ <div className="col-sm-auto left-builder-col">
411
+ <div className="componets-and-library-accordion toolbox-card">
412
+ <InitNewElement
413
+ nodekeys={nodekeys}
414
+ setIsSaving={setIsSaving}
415
+ />
416
+ <Accordion>
417
+ <div className="card mt-1" accordiontitle="Components">
418
+ {{
419
+ show: <ToolboxShow />,
420
+ edit: <ToolboxEdit />,
421
+ page: <ToolboxPage />,
422
+ filter: <ToolboxFilter />,
423
+ }[mode] || <div>Missing mode</div>}
424
+ </div>
425
+ <div accordiontitle="Library">
426
+ <Library />
427
+ </div>
428
+ </Accordion>
429
+ </div>
430
+ <div className="card toolbox-card pe-0">
431
+ <div className="card-header">Layers</div>
432
+ {showLayers && (
433
+ <div className="card-body p-0 builder-layers">
434
+ <Layers expandRootOnLoad={true} />
435
+ </div>
436
+ )}
437
+ </div>
421
438
  </div>
422
- <div className="card toolbox-card pe-0">
423
- <div className="card-header">Layers</div>
424
- {showLayers && (
425
- <div className="card-body p-0 builder-layers">
426
- <Layers expandRootOnLoad={true} />
427
- </div>
428
- )}
439
+ <div
440
+ id="builder-main-canvas"
441
+ className={`col builder-mode-${options.mode}`}
442
+ >
443
+ <div>
444
+ <Frame
445
+ resolver={{
446
+ Text,
447
+ Empty,
448
+ Columns,
449
+ JoinField,
450
+ Field,
451
+ ViewLink,
452
+ Action,
453
+ HTMLCode,
454
+ LineBreak,
455
+ Aggregation,
456
+ Card,
457
+ Image,
458
+ Link,
459
+ View,
460
+ SearchBar,
461
+ Container,
462
+ Column,
463
+ DropDownFilter,
464
+ DropMenu,
465
+ Tabs,
466
+ Table,
467
+ ToggleFilter,
468
+ }}
469
+ >
470
+ <Element canvas is={Column}></Element>
471
+ </Frame>
472
+ </div>
429
473
  </div>
430
- </div>
431
- <div
432
- id="builder-main-canvas"
433
- className={`col builder-mode-${options.mode}`}
434
- >
435
- <div>
436
- <Frame
437
- resolver={{
438
- Text,
439
- Empty,
440
- Columns,
441
- JoinField,
442
- Field,
443
- ViewLink,
444
- Action,
445
- HTMLCode,
446
- LineBreak,
447
- Aggregation,
448
- Card,
449
- Image,
450
- Link,
451
- View,
452
- SearchBar,
453
- Container,
454
- Column,
455
- DropDownFilter,
456
- DropMenu,
457
- Tabs,
458
- Table,
459
- ToggleFilter,
460
- }}
461
- >
462
- <Element canvas is={Column}></Element>
463
- </Frame>
464
- </div>
465
- </div>
466
- <div className="col-sm-auto builder-sidebar">
467
- <div style={{ width: "16rem" }}>
468
- <NextButton layout={layout} />
469
- <HistoryPanel />
470
- <FontAwesomeIcon
471
- icon={faSave}
472
- className={isSaving ? "d-inline" : "d-none"}
473
- />
474
- <SettingsPanel />
474
+ <div className="col-sm-auto builder-sidebar">
475
+ <div style={{ width: "16rem" }}>
476
+ <NextButton layout={layout} />
477
+ <HistoryPanel />
478
+ <FontAwesomeIcon
479
+ icon={faSave}
480
+ className={isSaving ? "d-inline" : "d-none"}
481
+ />
482
+ <SettingsPanel />
483
+ </div>
475
484
  </div>
476
485
  </div>
477
- </div>
486
+ </RelationsCtx.Provider>
478
487
  </PreviewCtx.Provider>
479
488
  </Provider>
480
489
  <div className="d-none preview-scratchpad"></div>
@@ -10,6 +10,7 @@ import React, {
10
10
  useState,
11
11
  Fragment,
12
12
  useRef,
13
+ useMemo,
13
14
  } from "react";
14
15
  import { useEditor, useNode } from "@craftjs/core";
15
16
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -19,7 +20,7 @@ import faIcons from "./elements/faicons";
19
20
  import { craftToSaltcorn, layoutToNodes } from "./storage";
20
21
  import optionsCtx from "./context";
21
22
  import { WrapElem } from "./Toolbox";
22
- import { isEqual } from "lodash";
23
+ import { isEqual, throttle } from "lodash";
23
24
 
24
25
  /**
25
26
  *
@@ -66,6 +67,25 @@ LibraryElem.craft = {
66
67
  displayName: "LibraryElem",
67
68
  };
68
69
 
70
+ // https://www.developerway.com/posts/debouncing-in-react
71
+ const useThrottle = (callback) => {
72
+ const ref = useRef();
73
+
74
+ useEffect(() => {
75
+ ref.current = callback;
76
+ }, [callback]);
77
+
78
+ const debouncedCallback = useMemo(() => {
79
+ const func = () => {
80
+ ref.current?.();
81
+ };
82
+
83
+ return throttle(func, 3000);
84
+ }, []);
85
+
86
+ return debouncedCallback;
87
+ };
88
+
69
89
  export /**
70
90
  * @param {object} props
71
91
  * @param {object} props.nodekeys
@@ -89,11 +109,12 @@ const InitNewElement = ({ nodekeys, setIsSaving }) => {
89
109
  if (savedData.current === false) {
90
110
  //do not save on first call
91
111
  savedData.current = JSON.stringify(data.layout);
92
- setIsSaving(false);
112
+
93
113
  return;
94
114
  }
95
115
  if (isEqual(savedData.current, JSON.stringify(data.layout))) return;
96
116
  savedData.current = JSON.stringify(data.layout);
117
+ setIsSaving(true);
97
118
 
98
119
  fetch(`/${urlroot}/savebuilder/${options.page_id || options.view_id}`, {
99
120
  method: "POST", // or 'PUT'
@@ -106,6 +127,9 @@ const InitNewElement = ({ nodekeys, setIsSaving }) => {
106
127
  setIsSaving(false);
107
128
  });
108
129
  };
130
+ const throttledSave = useThrottle(() => {
131
+ doSave(query);
132
+ });
109
133
  const onNodesChange = (arg, arg1) => {
110
134
  const nodes = arg.getSerializedNodes();
111
135
  const newNodeIds = [];
@@ -133,14 +157,8 @@ const InitNewElement = ({ nodekeys, setIsSaving }) => {
133
157
  actions.selectNode(id);
134
158
  }
135
159
  }
136
- if (saveTimeout) clearTimeout(saveTimeout);
137
- setIsSaving(true);
138
- setSaveTimeout(
139
- setTimeout(() => {
140
- doSave(query);
141
- setSaveTimeout(false);
142
- }, 500)
143
- );
160
+
161
+ throttledSave();
144
162
  };
145
163
  useEffect(() => {
146
164
  const nodes = query.getSerializedNodes();
@@ -140,12 +140,12 @@ const AggregationSettings = () => {
140
140
  { valAttr: true }
141
141
  )}
142
142
  {options.fields
143
- .filter((f) => f.type.name === "Date")
143
+ .filter((f) => f.type === "Date" || f.type.name === "Date")
144
144
  .map((f) => (
145
145
  <option value={`Latest ${f.name}`}>Latest {f.name}</option>
146
146
  ))}
147
147
  {options.fields
148
- .filter((f) => f.type.name === "Date")
148
+ .filter((f) => f.type === "Date" || f.type.name === "Date")
149
149
  .map((f) => (
150
150
  <option value={`Earliest ${f.name}`}>
151
151
  Earliest {f.name}
@@ -4,7 +4,7 @@
4
4
  * @subcategory components / elements
5
5
  */
6
6
 
7
- import React, { useContext, useEffect, Fragment } from "react";
7
+ import React, { useContext, useEffect, useState, Fragment } from "react";
8
8
  import { useNode } from "@craftjs/core";
9
9
  import optionsCtx from "../context";
10
10
  import previewCtx from "../preview_context";
@@ -115,9 +115,26 @@ const FieldSettings = () => {
115
115
  const fvs = options.field_view_options[name];
116
116
  const handlesTextStyle = (options.handlesTextStyle || {})[name];
117
117
  const blockDisplay = (options.blockDisplay || {})[name];
118
- const getCfgFields = (fv) =>
119
- ((options.fieldViewConfigForms || {})[name] || {})[fv];
120
- const cfgFields = getCfgFields(fieldview);
118
+
119
+ const [fetchedCfgFields, setFetchedCfgFields] = useState([]);
120
+ const cfgFields = fetchedCfgFields;
121
+ useEffect(() => {
122
+ fetch(`/field/fieldviewcfgform/${options.tableName}?accept=json`, {
123
+ method: "POST",
124
+ headers: {
125
+ "Content-Type": "application/json",
126
+ "CSRF-Token": options.csrfToken,
127
+ "X-Requested-With": "XMLHttpRequest",
128
+ },
129
+ body: JSON.stringify({ field_name: name, fieldview, type: "Field" }),
130
+ })
131
+ .then(function (response) {
132
+ if (response.status < 399) return response.json();
133
+ else return [];
134
+ })
135
+ .then(setFetchedCfgFields);
136
+ }, [name, fieldview]);
137
+
121
138
  const refetchPreview = fetchFieldPreview({
122
139
  options,
123
140
  name,
@@ -189,7 +206,7 @@ const FieldSettings = () => {
189
206
  const value = e.target.value;
190
207
 
191
208
  setProp((prop) => (prop.fieldview = value));
192
- setInitialConfig(setProp, value, getCfgFields(value));
209
+ //setInitialConfig(setProp, value, getCfgFields(value));
193
210
  refetchPreview({ fieldview: value });
194
211
  }}
195
212
  >