@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.
@@ -8,6 +8,7 @@ import React, { Fragment, useEffect, useMemo } from "react";
8
8
  import { useNode } from "@craftjs/core";
9
9
  import optionsCtx from "../context";
10
10
  import previewCtx from "../preview_context";
11
+ import relationsCtx from "../relations_context";
11
12
 
12
13
  import {
13
14
  fetchViewPreview,
@@ -15,7 +16,9 @@ import {
15
16
  setAPropGen,
16
17
  buildOptions,
17
18
  HelpTopicLink,
19
+ initialRelation,
18
20
  prepCacheAndFinder,
21
+ updateRelationsCache,
19
22
  } from "./utils";
20
23
 
21
24
  import { RelationBadges } from "./RelationBadges";
@@ -113,6 +116,7 @@ const ViewSettings = () => {
113
116
  const fixed_state_fields =
114
117
  options.fixed_state_fields && options.fixed_state_fields[view];
115
118
  const { setPreviews } = React.useContext(previewCtx);
119
+ const { relationsCache, setRelationsCache } = React.useContext(relationsCtx);
116
120
 
117
121
  const setAProp = setAPropGen(setProp);
118
122
  let errorString = false;
@@ -131,18 +135,25 @@ const ViewSettings = () => {
131
135
  else viewname = rest;
132
136
  }
133
137
  if (viewname.includes(".")) viewname = viewname.split(".")[0];
138
+ if (finder)
139
+ updateRelationsCache(
140
+ relationsCache,
141
+ setRelationsCache,
142
+ options,
143
+ finder,
144
+ viewname
145
+ );
134
146
  const [relations, setRelations] = finder
135
- ? React.useState(
136
- finder.findRelations(
137
- options.tableName,
138
- viewname,
139
- options.excluded_subview_templates
140
- )
141
- )
147
+ ? React.useState(relationsCache[options.tableName][viewname])
142
148
  : [undefined, undefined];
143
149
  let safeRelation = relation;
144
- if (!safeRelation && !hasLegacyRelation && relations?.paths.length > 0) {
145
- safeRelation = relations.paths[0];
150
+ if (
151
+ options.mode !== "filter" &&
152
+ !safeRelation &&
153
+ !hasLegacyRelation &&
154
+ relations?.paths.length > 0
155
+ ) {
156
+ safeRelation = initialRelation(relations.paths, options.tableName);
146
157
  setProp((prop) => {
147
158
  prop.relation = safeRelation;
148
159
  });
@@ -153,17 +164,29 @@ const ViewSettings = () => {
153
164
  if (e.target) {
154
165
  const target_value = e.target.value;
155
166
  if (target_value !== viewname) {
156
- const newRelations = finder.findRelations(
157
- options.tableName,
158
- target_value,
159
- options.excluded_subview_templates
160
- );
161
- if (newRelations.paths.length > 0) {
167
+ if (options.mode === "filter") {
162
168
  setProp((prop) => {
163
169
  prop.view = target_value;
164
- prop.relation = newRelations.paths[0];
165
170
  });
166
- setRelations(newRelations);
171
+ } else {
172
+ updateRelationsCache(
173
+ relationsCache,
174
+ setRelationsCache,
175
+ options,
176
+ finder,
177
+ target_value
178
+ );
179
+ const newRelations = relationsCache[options.tableName][target_value];
180
+ if (newRelations.paths.length > 0) {
181
+ setProp((prop) => {
182
+ prop.view = target_value;
183
+ prop.relation = initialRelation(
184
+ newRelations.paths,
185
+ options.tableName
186
+ );
187
+ });
188
+ setRelations(newRelations);
189
+ }
167
190
  }
168
191
  }
169
192
  }
@@ -188,7 +211,7 @@ const ViewSettings = () => {
188
211
  ))}
189
212
  </select>
190
213
  </div>
191
- {
214
+ {options.mode !== "filter" && (
192
215
  <div>
193
216
  <RelationOnDemandPicker
194
217
  relations={relations.layers}
@@ -213,7 +236,7 @@ const ViewSettings = () => {
213
236
  tableNameCache={caches.tableNameCache}
214
237
  />
215
238
  </div>
216
- }
239
+ )}
217
240
  </Fragment>
218
241
  ) : (
219
242
  <div>
@@ -7,6 +7,8 @@
7
7
  import React, { useMemo } from "react";
8
8
  import { useNode } from "@craftjs/core";
9
9
  import optionsCtx from "../context";
10
+ import relationsCtx from "../relations_context";
11
+
10
12
  import {
11
13
  BlockSetting,
12
14
  MinRoleSettingRow,
@@ -17,6 +19,8 @@ import {
17
19
  FormulaTooltip,
18
20
  HelpTopicLink,
19
21
  prepCacheAndFinder,
22
+ initialRelation,
23
+ updateRelationsCache,
20
24
  } from "./utils";
21
25
 
22
26
  import { RelationBadges } from "./RelationBadges";
@@ -127,6 +131,7 @@ const ViewLinkSettings = () => {
127
131
  () => prepCacheAndFinder(options),
128
132
  [undefined]
129
133
  );
134
+ const { relationsCache, setRelationsCache } = React.useContext(relationsCtx);
130
135
  let errorString = false;
131
136
  try {
132
137
  Function("return " + extra_state_fml);
@@ -142,16 +147,19 @@ const ViewLinkSettings = () => {
142
147
  const safeViewName = use_view_name?.includes(".")
143
148
  ? use_view_name.split(".")[0]
144
149
  : use_view_name;
150
+ updateRelationsCache(
151
+ relationsCache,
152
+ setRelationsCache,
153
+ options,
154
+ finder,
155
+ safeViewName
156
+ );
145
157
  const [relations, setRelations] = React.useState(
146
- finder.findRelations(
147
- options.tableName,
148
- safeViewName,
149
- options.excluded_subview_templates
150
- )
158
+ relationsCache[options.tableName][safeViewName]
151
159
  );
152
160
  let safeRelation = relation;
153
161
  if (!safeRelation && !hasLegacyRelation && relations?.paths.length > 0) {
154
- safeRelation = relations.paths[0];
162
+ safeRelation = initialRelation(relations.paths, options.tableName);
155
163
  setProp((prop) => {
156
164
  prop.relation = safeRelation;
157
165
  });
@@ -160,15 +168,21 @@ const ViewLinkSettings = () => {
160
168
  if (e.target) {
161
169
  const target_value = e.target.value;
162
170
  if (target_value !== use_view_name) {
163
- const newRelations = finder.findRelations(
164
- options.tableName,
165
- target_value,
166
- options.excluded_subview_templates
171
+ updateRelationsCache(
172
+ relationsCache,
173
+ setRelationsCache,
174
+ options,
175
+ finder,
176
+ target_value
167
177
  );
178
+ const newRelations = relationsCache[options.tableName][target_value];
168
179
  if (newRelations.paths.length > 0) {
169
180
  setProp((prop) => {
170
181
  prop.name = target_value;
171
- prop.relation = newRelations.paths[0];
182
+ prop.relation = initialRelation(
183
+ newRelations.paths,
184
+ options.tableName
185
+ );
172
186
  });
173
187
  setRelations(newRelations);
174
188
  }
@@ -767,12 +767,16 @@ const ConfigField = ({
767
767
  field.default
768
768
  );
769
769
  if (field.input_type === "fromtype") field.input_type = null;
770
- if (field.type && field.type.name === "String" && field.attributes.options) {
770
+ if (
771
+ field.type &&
772
+ (field.type.name === "String" || field.type === "String") &&
773
+ field.attributes?.options
774
+ ) {
771
775
  field.input_type = "select";
772
776
  field.options =
773
- typeof field.attributes.options === "string"
774
- ? field.attributes.options.split(",").map((s) => s.trim())
775
- : field.attributes.options;
777
+ typeof field.attributes?.options === "string"
778
+ ? field.attributes?.options.split(",").map((s) => s.trim())
779
+ : field.attributes?.options;
776
780
  if (!field.required && field.options) field.options.unshift("");
777
781
  }
778
782
  const field_type = field.input_type || field.type.name || field.type;
@@ -781,7 +785,7 @@ const ConfigField = ({
781
785
  field_type === "select";
782
786
  const getOptions = () =>
783
787
  typeof field?.attributes?.options === "string"
784
- ? field.attributes.options.split(",").map((s) => s.trim())
788
+ ? field.attributes?.options.split(",").map((s) => s.trim())
785
789
  : field?.attributes?.options || field.options;
786
790
 
787
791
  if (hasSelect && typeof value === "undefined") {
@@ -890,6 +894,7 @@ const ConfigField = ({
890
894
  className="form-control"
891
895
  value={value}
892
896
  onChange={(e) => e.target && myOnChange(e.target.value)}
897
+ spellCheck={false}
893
898
  />
894
899
  ),
895
900
  select: () => (
@@ -1501,3 +1506,64 @@ export const prepCacheAndFinder = ({
1501
1506
  return { caches, finder };
1502
1507
  } else return { caches: null, finder: null };
1503
1508
  };
1509
+
1510
+ /**
1511
+ * @param {string[]} paths
1512
+ * @param {string} sourceTbl name of the topview table
1513
+ * @returns either a same table relation, a parent relation, a child relation, or the first relation
1514
+ */
1515
+ export const initialRelation = (paths, sourceTbl) => {
1516
+ let sameTblRel = null;
1517
+ let parentRel = null;
1518
+ let childRel = null;
1519
+ for (const path of paths) {
1520
+ if (!sameTblRel && path === `.${sourceTbl}`) sameTblRel = path;
1521
+ else {
1522
+ const tokens = path.split(".");
1523
+ if (
1524
+ !parentRel &&
1525
+ tokens.length === 3 &&
1526
+ tokens[1] === sourceTbl &&
1527
+ tokens[2].indexOf("$") === -1
1528
+ )
1529
+ parentRel = path;
1530
+ else {
1531
+ const lastToken = tokens[tokens.length - 1];
1532
+ if (
1533
+ lastToken.indexOf("$") > 0 &&
1534
+ (!childRel || childRel.split(".").length > tokens.length)
1535
+ )
1536
+ childRel = path;
1537
+ }
1538
+ }
1539
+ }
1540
+ return sameTblRel || parentRel || childRel || paths[0];
1541
+ };
1542
+
1543
+ /**
1544
+ * update the builder wide relations cache relations cache
1545
+ * if there is no entry for the given tableName and viewname
1546
+ * @param {any} relationsCache cache from the context
1547
+ * @param {Function} setRelationsCache set cache in context
1548
+ * @param {any} options builder options
1549
+ * @param {RelationsFinder} finder
1550
+ * @param {string} viewname subview name
1551
+ */
1552
+ export const updateRelationsCache = (
1553
+ relationsCache,
1554
+ setRelationsCache,
1555
+ options,
1556
+ finder,
1557
+ viewname
1558
+ ) => {
1559
+ if (!relationsCache[options.tableName])
1560
+ relationsCache[options.tableName] = {};
1561
+ if (!relationsCache[options.tableName][viewname]) {
1562
+ relationsCache[options.tableName][viewname] = finder.findRelations(
1563
+ options.tableName,
1564
+ viewname,
1565
+ options.excluded_subview_templates
1566
+ );
1567
+ setRelationsCache({ ...relationsCache });
1568
+ }
1569
+ };
@@ -0,0 +1,3 @@
1
+ import { createContext } from "react";
2
+
3
+ export default createContext({});
@@ -38,14 +38,22 @@ const doTest = (
38
38
  name: view || viewName,
39
39
  });
40
40
  require("@craftjs/core").useNode.mockImplementation(useNodeMock);
41
+ let relationsCache = {};
42
+ const setRelationsCache = (newVal) => {
43
+ relationsCache = newVal;
44
+ };
41
45
  // mock react context
42
46
  const useContextMock = (React.useContext = jest.fn());
43
47
  useContextMock.mockReturnValue({
48
+ // optionsCtx part
44
49
  tables: tables,
45
50
  views: views,
46
51
  tableName: tableName,
47
52
  roles: [],
48
53
  excluded_subview_templates: excludedTemplates,
54
+ // relationsCtx part
55
+ relationsCache: relationsCache,
56
+ setRelationsCache: setRelationsCache,
49
57
  });
50
58
  // spy on useState and extract the relations (first call)
51
59
  const spy = jest.spyOn(React, "useState");