@saltcorn/builder 1.6.0-alpha.7 → 1.6.0-alpha.9

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.
Files changed (40) hide show
  1. package/dist/builder_bundle.js +104014 -2
  2. package/dist/builder_bundle.js.LICENSE.txt +65 -0
  3. package/package.json +32 -28
  4. package/src/components/Builder.js +334 -122
  5. package/src/components/Library.js +25 -13
  6. package/src/components/RenderNode.js +6 -6
  7. package/src/components/Toolbox.js +333 -269
  8. package/src/components/elements/Action.js +144 -29
  9. package/src/components/elements/Aggregation.js +20 -23
  10. package/src/components/elements/ArrayManager.js +7 -5
  11. package/src/components/elements/BoxModelEditor.js +19 -17
  12. package/src/components/elements/Card.js +47 -34
  13. package/src/components/elements/Clone.js +74 -2
  14. package/src/components/elements/Column.js +1 -1
  15. package/src/components/elements/Columns.js +27 -25
  16. package/src/components/elements/Container.js +170 -90
  17. package/src/components/elements/DropDownFilter.js +10 -8
  18. package/src/components/elements/DropMenu.js +8 -5
  19. package/src/components/elements/Field.js +9 -7
  20. package/src/components/elements/HTMLCode.js +3 -1
  21. package/src/components/elements/Image.js +20 -15
  22. package/src/components/elements/JoinField.js +15 -11
  23. package/src/components/elements/Link.js +18 -16
  24. package/src/components/elements/ListColumn.js +7 -3
  25. package/src/components/elements/ListColumns.js +4 -1
  26. package/src/components/elements/MonacoEditor.js +4 -2
  27. package/src/components/elements/Page.js +7 -4
  28. package/src/components/elements/RelationBadges.js +16 -11
  29. package/src/components/elements/RelationOnDemandPicker.js +18 -12
  30. package/src/components/elements/SearchBar.js +10 -6
  31. package/src/components/elements/Table.js +72 -65
  32. package/src/components/elements/Tabs.js +18 -15
  33. package/src/components/elements/Text.js +19 -14
  34. package/src/components/elements/ToggleFilter.js +28 -25
  35. package/src/components/elements/View.js +36 -18
  36. package/src/components/elements/ViewLink.js +15 -11
  37. package/src/components/elements/utils.js +224 -55
  38. package/src/components/storage.js +27 -129
  39. package/src/hooks/useTranslation.js +11 -0
  40. package/src/index.js +6 -3
@@ -21,12 +21,14 @@ import {
21
21
  } from "./utils";
22
22
  import ContentEditable from "react-contenteditable";
23
23
  import optionsCtx from "../context";
24
- import CKEditor from "ckeditor4-react";
24
+ import { CKEditor } from "ckeditor4-react";
25
25
  import FontIconPicker from "@fonticonpicker/react-fonticonpicker";
26
26
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
27
27
  import fas from "@fortawesome/free-solid-svg-icons";
28
28
  import far from "@fortawesome/free-regular-svg-icons";
29
29
  import { SingleLineEditor } from "./MonacoEditor";
30
+ import useTranslation from "../../hooks/useTranslation";
31
+
30
32
  const ckConfig = {
31
33
  toolbarGroups: [
32
34
  { name: "document", groups: ["mode", "document", "doctools"] },
@@ -109,7 +111,7 @@ const Text = ({
109
111
  isFormula.text ? "font-monospace" : ""
110
112
  } ${selected ? "selected-node" : ""}`}
111
113
  ref={(dom) => connect(drag(dom))}
112
- onClick={(e) => selected && setEditable(true)}
114
+ onDoubleClick={(e) => selected && setEditable(true)}
113
115
  style={{
114
116
  ...(font ? { fontFamily: font } : {}),
115
117
  ...reactifyStyles(style || {}),
@@ -131,7 +133,7 @@ const Text = ({
131
133
  ) : editable ? (
132
134
  <ErrorBoundary>
133
135
  <CKEditor
134
- data={text}
136
+ initData={text || ""}
135
137
  style={{ display: "inline" }}
136
138
  onChange={(e) =>
137
139
  setProp((props) => (props.text = e.editor.getData()))
@@ -155,7 +157,9 @@ export /**
155
157
  * @subcategory components
156
158
  */
157
159
  const TextSettings = () => {
160
+ const { t } = useTranslation();
158
161
  const node = useNode((node) => ({
162
+ id: node.id,
159
163
  text: node.data.props.text,
160
164
  block: node.data.props.block,
161
165
  inline: node.data.props.inline,
@@ -198,17 +202,18 @@ const TextSettings = () => {
198
202
  setProp((prop) => (prop.isFormula.text = checked));
199
203
  }}
200
204
  />
201
- <label className="form-check-label">Formula?</label>
205
+ <label className="form-check-label">{t("Formula?")}</label>
202
206
  </div>
203
207
  )}
204
- <label>Text to display</label>
208
+ <label>{t("Text to display")}</label>
205
209
  {allowFormula && isFormula.text ? (
206
210
  <SingleLineEditor setProp={setProp} value={text} propKey="text" />
207
211
  ) : (
208
212
  <ErrorBoundary>
209
213
  <div className="border">
210
214
  <CKEditor
211
- data={text}
215
+ key={node.id}
216
+ initData={text || ""}
212
217
  onChange={(e) => {
213
218
  if (e.editor) {
214
219
  const text = e.editor.getData();
@@ -223,7 +228,7 @@ const TextSettings = () => {
223
228
  )}
224
229
  {mode === "edit" && (
225
230
  <Fragment>
226
- <label>Label for Field</label>
231
+ <label>{t("Label for Field")}</label>
227
232
  <select
228
233
  value={labelFor}
229
234
  onChange={setAProp("labelFor")}
@@ -243,7 +248,7 @@ const TextSettings = () => {
243
248
  <TextStyleRow textStyle={textStyle} setProp={setProp} />
244
249
  <tr>
245
250
  <td>
246
- <label>Icon</label>
251
+ <label>{t("Icon")}</label>
247
252
  </td>
248
253
  <td>
249
254
  <FontIconPicker
@@ -258,7 +263,7 @@ const TextSettings = () => {
258
263
  <SettingsRow
259
264
  field={{
260
265
  name: "font",
261
- label: "Font family",
266
+ label: t("Font family"),
262
267
  type: "Font",
263
268
  }}
264
269
  node={node}
@@ -267,7 +272,7 @@ const TextSettings = () => {
267
272
  <SettingsRow
268
273
  field={{
269
274
  name: "font-size",
270
- label: "Font size",
275
+ label: t("Font size"),
271
276
  type: "DimUnits",
272
277
  }}
273
278
  node={node}
@@ -277,7 +282,7 @@ const TextSettings = () => {
277
282
  <SettingsRow
278
283
  field={{
279
284
  name: "font-weight",
280
- label: "Weight",
285
+ label: t("Weight"),
281
286
  type: "Integer",
282
287
  min: 100,
283
288
  max: 900,
@@ -290,7 +295,7 @@ const TextSettings = () => {
290
295
  <SettingsRow
291
296
  field={{
292
297
  name: "line-height",
293
- label: "Line height",
298
+ label: t("Line height"),
294
299
  type: "DimUnits",
295
300
  }}
296
301
  node={node}
@@ -298,7 +303,7 @@ const TextSettings = () => {
298
303
  isStyle={true}
299
304
  />
300
305
  <tr>
301
- <td>Class</td>
306
+ <td>{t("Class")}</td>
302
307
  <td>
303
308
  <input
304
309
  type="text"
@@ -312,7 +317,7 @@ const TextSettings = () => {
312
317
  <SettingsRow
313
318
  field={{
314
319
  name: "color",
315
- label: "Color",
320
+ label: t("Color"),
316
321
  type: "Color",
317
322
  }}
318
323
  node={node}
@@ -4,8 +4,9 @@
4
4
  * @subcategory components / elements
5
5
  */
6
6
 
7
- import React, { useContext, Fragment } from "react";
7
+ import React, { Fragment, useState, useContext, useEffect } from "react";
8
8
  import { useNode } from "@craftjs/core";
9
+ import useTranslation from "../../hooks/useTranslation";
9
10
  import optionsCtx from "../context";
10
11
  import { blockProps, BlockSetting, setAPropGen, buildOptions } from "./utils";
11
12
 
@@ -32,6 +33,7 @@ const ToggleFilter = ({
32
33
  size,
33
34
  style,
34
35
  }) => {
36
+ const { t } = useTranslation();
35
37
  const {
36
38
  selected,
37
39
  connectors: { connect, drag },
@@ -43,7 +45,7 @@ const ToggleFilter = ({
43
45
  ref={(dom) => connect(drag(dom))}
44
46
  >
45
47
  <button className={`btn btn-outline-${style || "primary"} ${size}`}>
46
- {label || value || preset_value || "Set label"}
48
+ {label || value || preset_value || t("Set label")}
47
49
  </button>
48
50
  </span>
49
51
  );
@@ -56,6 +58,7 @@ export /**
56
58
  * @subcategory components
57
59
  */
58
60
  const ToggleFilterSettings = () => {
61
+ const { t } = useTranslation();
59
62
  const {
60
63
  actions: { setProp },
61
64
  name,
@@ -85,7 +88,7 @@ const ToggleFilterSettings = () => {
85
88
  <tbody>
86
89
  <tr>
87
90
  <td>
88
- <label>Field</label>
91
+ <label>{t("Field")}</label>
89
92
  </td>
90
93
  <td>
91
94
  <select
@@ -115,7 +118,7 @@ const ToggleFilterSettings = () => {
115
118
  </tr>
116
119
  <tr>
117
120
  <td>
118
- <label>Value</label>
121
+ <label>{t("Value")}</label>
119
122
  </td>
120
123
  <td>
121
124
  {isBool ? (
@@ -124,9 +127,9 @@ const ToggleFilterSettings = () => {
124
127
  className="w-100 form-select"
125
128
  onChange={setAProp("value")}
126
129
  >
127
- <option value="on">True</option>
128
- <option value="off">False</option>
129
- <option value="?">Both</option>
130
+ <option value="on">{t("True")}</option>
131
+ <option value="off">{t("False")}</option>
132
+ <option value="?">{t("Both")}</option>
130
133
  </select>
131
134
  ) : (
132
135
  <input
@@ -140,7 +143,7 @@ const ToggleFilterSettings = () => {
140
143
  {preset_options && preset_options.length > 0 ? (
141
144
  <tr>
142
145
  <td>
143
- <label>Preset</label>
146
+ <label>{t("Preset")}</label>
144
147
  </td>
145
148
  <td>
146
149
  <select
@@ -160,7 +163,7 @@ const ToggleFilterSettings = () => {
160
163
  ) : null}
161
164
  <tr>
162
165
  <td>
163
- <label>Label</label>
166
+ <label>{t("Label")}</label>
164
167
  </td>
165
168
  <td>
166
169
  <input
@@ -172,7 +175,7 @@ const ToggleFilterSettings = () => {
172
175
  </tr>
173
176
  <tr>
174
177
  <td>
175
- <label>Button size</label>
178
+ <label>{t("Button size")}</label>
176
179
  </td>
177
180
  <td>
178
181
  <select
@@ -180,18 +183,18 @@ const ToggleFilterSettings = () => {
180
183
  value={size}
181
184
  onChange={setAProp("size")}
182
185
  >
183
- <option value="">Standard</option>
184
- <option value="btn-lg">Large</option>
185
- <option value="btn-sm">Small</option>
186
- <option value="btn-block">Block</option>
187
- <option value="btn-block btn-lg">Large block</option>
188
- <option value="btn-block btn-sm">Small block</option>
186
+ <option value="">{t("Standard")}</option>
187
+ <option value="btn-lg">{t("Large")}</option>
188
+ <option value="btn-sm">{t("Small")}</option>
189
+ <option value="btn-block">{t("Block")}</option>
190
+ <option value="btn-block btn-lg">{t("Large block")}</option>
191
+ <option value="btn-block btn-sm">{t("Small block")}</option>
189
192
  </select>
190
193
  </td>
191
194
  </tr>
192
195
  <tr>
193
196
  <td>
194
- <label>Button style</label>
197
+ <label>{t("Button style")}</label>
195
198
  </td>
196
199
  <td>
197
200
  <select
@@ -201,14 +204,14 @@ const ToggleFilterSettings = () => {
201
204
  >
202
205
  {buildOptions(
203
206
  [
204
- "primary",
205
- "secondary",
206
- "success",
207
- "danger",
208
- "warning",
209
- "info",
210
- "light",
211
- "dark",
207
+ t("primary"),
208
+ t("secondary"),
209
+ t("success"),
210
+ t("danger"),
211
+ t("warning"),
212
+ t("info"),
213
+ t("light"),
214
+ t("dark"),
212
215
  ],
213
216
  { valAttr: true, capitalize: true }
214
217
  )}
@@ -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 useTranslation from "../../hooks/useTranslation";
11
12
  import relationsCtx from "../relations_context";
12
13
  import Select from "react-select";
13
14
 
@@ -19,6 +20,8 @@ import {
19
20
  HelpTopicLink,
20
21
  initialRelation,
21
22
  buildLayers,
23
+ reactSelectStyles,
24
+ builderSelectClassName,
22
25
  } from "./utils";
23
26
 
24
27
  import { RelationBadges } from "./RelationBadges";
@@ -47,6 +50,7 @@ const View = ({ name, view, configuration, state }) => {
47
50
  node_id,
48
51
  connectors: { connect, drag },
49
52
  } = useNode((node) => ({ selected: node.events.selected, node_id: node.id }));
53
+ const { t } = useTranslation();
50
54
  const options = React.useContext(optionsCtx);
51
55
 
52
56
  let viewname = view;
@@ -81,7 +85,7 @@ const View = ({ name, view, configuration, state }) => {
81
85
  dangerouslySetInnerHTML={{ __html: myPreview }}
82
86
  ></div>
83
87
  ) : (
84
- `View: ${label}`
88
+ `${t("View")}: ${label}`
85
89
  )}
86
90
  </div>
87
91
  );
@@ -94,6 +98,7 @@ export /**
94
98
  * @namespace
95
99
  */
96
100
  const ViewSettings = () => {
101
+ const { t } = useTranslation();
97
102
  const node = useNode((node) => ({
98
103
  name: node.data.props.name,
99
104
  view: node.data.props.view,
@@ -157,6 +162,7 @@ const ViewSettings = () => {
157
162
  }
158
163
  if (viewname && viewname.includes(".")) viewname = viewname.split(".")[0];
159
164
 
165
+ let cacheWasPopulated = false;
160
166
  if (
161
167
  finder &&
162
168
  !(relationsCache[tableName] && relationsCache[tableName][viewname])
@@ -173,8 +179,13 @@ const ViewSettings = () => {
173
179
  );
174
180
  relationsCache[tableName] = relationsCache[tableName] || {};
175
181
  relationsCache[tableName][viewname] = { relations, layers };
176
- setRelationsCache({ ...relationsCache });
182
+ cacheWasPopulated = true;
177
183
  }
184
+ useEffect(() => {
185
+ if (cacheWasPopulated) {
186
+ setRelationsCache({ ...relationsCache });
187
+ }
188
+ });
178
189
  const [relationsData, setRelationsData] = finder
179
190
  ? React.useState(relationsCache[tableName][viewname])
180
191
  : [undefined, undefined];
@@ -188,18 +199,23 @@ const ViewSettings = () => {
188
199
  subView.display_type
189
200
  );
190
201
  }
191
- if (
202
+ const needsInitialRelation =
192
203
  options.mode !== "filter" &&
193
204
  subView?.table_id &&
194
205
  !safeRelation &&
195
206
  !hasLegacyRelation &&
196
- relationsData?.relations.length > 0
197
- ) {
207
+ relationsData?.relations.length > 0;
208
+ if (needsInitialRelation) {
198
209
  safeRelation = initialRelation(relationsData.relations);
199
- setProp((prop) => {
200
- prop.relation = safeRelation.relationString;
201
- });
202
210
  }
211
+ useEffect(() => {
212
+ if (needsInitialRelation) {
213
+ const rel = initialRelation(relationsData.relations);
214
+ setProp((prop) => {
215
+ prop.relation = rel.relationString;
216
+ });
217
+ }
218
+ }, [needsInitialRelation]);
203
219
  const helpContext = { view_name: viewname };
204
220
  if (options.tableName) helpContext.srcTable = options.tableName;
205
221
  const set_view_name = (e) => {
@@ -235,7 +251,7 @@ const ViewSettings = () => {
235
251
  } else
236
252
  window.notifyAlert({
237
253
  type: "warning",
238
- text: `${target_value} has no relations`,
254
+ text: `${target_value} ${t("has no relations")}`,
239
255
  });
240
256
  }
241
257
  }
@@ -262,11 +278,12 @@ const ViewSettings = () => {
262
278
  <Select
263
279
  options={viewOptions}
264
280
  value={selectedView}
265
- className="react-select view-selector"
281
+ className={builderSelectClassName("react-select view-selector")}
282
+ classNamePrefix="builder-select"
266
283
  onChange={set_view_name}
267
284
  onBlur={set_view_name}
268
285
  menuPortalTarget={document.body}
269
- styles={{ menuPortal: (base) => ({ ...base, zIndex: 19999 }) }}
286
+ styles={reactSelectStyles()}
270
287
  ></Select>
271
288
  )}
272
289
  </div>
@@ -304,7 +321,8 @@ const ViewSettings = () => {
304
321
  <Select
305
322
  options={viewOptions}
306
323
  value={selectedView}
307
- className="react-select view-selector"
324
+ className={builderSelectClassName("react-select view-selector")}
325
+ classNamePrefix="builder-select"
308
326
  onChange={(e) => {
309
327
  const target_value = e?.target?.value || e?.value;
310
328
  setProp((prop) => {
@@ -312,7 +330,7 @@ const ViewSettings = () => {
312
330
  });
313
331
  }}
314
332
  menuPortalTarget={document.body}
315
- styles={{ menuPortal: (base) => ({ ...base, zIndex: 19999 }) }}
333
+ styles={reactSelectStyles()}
316
334
  ></Select>
317
335
  )}
318
336
  </div>
@@ -322,7 +340,7 @@ const ViewSettings = () => {
322
340
  theview?.viewtemplate === "Edit" &&
323
341
  targetTable ? (
324
342
  <div>
325
- <label>Order field</label>
343
+ <label>{t("Order field")}</label>
326
344
  <select
327
345
  value={order_field}
328
346
  className="form-control form-select"
@@ -341,7 +359,7 @@ const ViewSettings = () => {
341
359
  {options.mode !== "edit" && (
342
360
  <Fragment>
343
361
  <div>
344
- <label>State</label>
362
+ <label>{t("State")}</label>
345
363
  <select
346
364
  value={state}
347
365
  className="form-control form-select"
@@ -365,7 +383,7 @@ const ViewSettings = () => {
365
383
  fixed_state_fields &&
366
384
  fixed_state_fields.length > 0 && (
367
385
  <Fragment>
368
- <h6>View state fields</h6>
386
+ <h6>{t("View state fields")}</h6>
369
387
  <ConfigForm
370
388
  fields={fixed_state_fields}
371
389
  configuration={configuration || {}}
@@ -379,7 +397,7 @@ const ViewSettings = () => {
379
397
  {
380
398
  <Fragment>
381
399
  <label>
382
- Extra state Formula
400
+ {t("Extra state Formula")}
383
401
  <HelpTopicLink topic="Extra state formula" {...helpContext} />
384
402
  </label>
385
403
  <SingleLineEditor
@@ -401,7 +419,7 @@ const ViewSettings = () => {
401
419
  target="_blank"
402
420
  href={`/viewedit/config/${viewname}`}
403
421
  >
404
- Configure this view
422
+ {t("Configure this view")}
405
423
  </a>
406
424
  ) : null}
407
425
  </div>
@@ -6,6 +6,7 @@
6
6
 
7
7
  import React, { useMemo } from "react";
8
8
  import { useNode } from "@craftjs/core";
9
+ import useTranslation from "../../hooks/useTranslation";
9
10
  import optionsCtx from "../context";
10
11
  import relationsCtx from "../relations_context";
11
12
 
@@ -20,6 +21,8 @@ import {
20
21
  HelpTopicLink,
21
22
  initialRelation,
22
23
  buildLayers,
24
+ reactSelectStyles,
25
+ builderSelectClassName,
23
26
  } from "./utils";
24
27
 
25
28
  import { RelationBadges } from "./RelationBadges";
@@ -102,6 +105,7 @@ export /**
102
105
  * @namespace
103
106
  */
104
107
  const ViewLinkSettings = () => {
108
+ const { t } = useTranslation();
105
109
  const node = useNode((node) => ({
106
110
  name: node.data.props.name,
107
111
  relation: node.data.props.relation,
@@ -233,7 +237,7 @@ const ViewLinkSettings = () => {
233
237
  } else
234
238
  window.notifyAlert({
235
239
  type: "warning",
236
- text: `${target_value} has no relations`,
240
+ text: `${target_value} ${t("has no relations")}`,
237
241
  });
238
242
  }
239
243
  }
@@ -245,24 +249,24 @@ const ViewLinkSettings = () => {
245
249
  value: name,
246
250
  }));
247
251
  const selectedView = viewOptions.find((v) => v.value === use_view_name);
252
+
248
253
  return (
249
254
  <div>
250
255
  <table className="w-100">
251
256
  <tbody>
252
257
  <tr>
253
258
  <td colSpan="2">
254
- <label>View to link to</label>
259
+ <label>{t("View to link to")}</label>
255
260
  {options.inJestTestingMode ? null : (
256
261
  <Select
257
262
  options={viewOptions}
258
- className="react-select viewlink-selector"
263
+ className={builderSelectClassName("react-select viewlink-selector")}
264
+ classNamePrefix="builder-select"
259
265
  value={selectedView}
260
266
  onChange={set_view_name}
261
267
  onBlur={set_view_name}
262
268
  menuPortalTarget={document.body}
263
- styles={{
264
- menuPortal: (base) => ({ ...base, zIndex: 19999 }),
265
- }}
269
+ styles={reactSelectStyles()}
266
270
  ></Select>
267
271
  )}
268
272
  </td>
@@ -295,7 +299,7 @@ const ViewLinkSettings = () => {
295
299
  </tr>
296
300
  <tr>
297
301
  <td colSpan="2">
298
- <label>Label (leave blank for default)</label>
302
+ <label>{t("Label (leave blank for default)")}</label>
299
303
  <OrFormula nodekey="label" {...{ setProp, isFormula, node }}>
300
304
  <input
301
305
  type="text"
@@ -309,7 +313,7 @@ const ViewLinkSettings = () => {
309
313
  <tr>
310
314
  <td colSpan="2">
311
315
  <label>
312
- Extra state Formula
316
+ {t("Extra state Formula")}
313
317
  <HelpTopicLink topic="Extra state formula" {...helpContext} />
314
318
  </label>
315
319
  <SingleLineEditor
@@ -346,7 +350,7 @@ const ViewLinkSettings = () => {
346
350
  checked={link_target_blank}
347
351
  onChange={setAProp("link_target_blank", { checked: true })}
348
352
  />
349
- <label className="form-check-label">Open in new tab</label>
353
+ <label className="form-check-label">{t("Open in new tab")}</label>
350
354
  </div>
351
355
  <div className="form-check">
352
356
  <input
@@ -356,7 +360,7 @@ const ViewLinkSettings = () => {
356
360
  checked={inModal}
357
361
  onChange={setAProp("inModal", { checked: true })}
358
362
  />
359
- <label className="form-check-label">Open in popup modal?</label>
363
+ <label className="form-check-label">{t("Open in popup modal?")}</label>
360
364
  </div>
361
365
  <BlockSetting block={block} setProp={setProp} />
362
366
  <TextStyleSetting textStyle={textStyle} setProp={setProp} />
@@ -370,7 +374,7 @@ const ViewLinkSettings = () => {
370
374
  target="_blank"
371
375
  href={`/viewedit/config/${use_view_name}`}
372
376
  >
373
- Configure this view
377
+ {t("Configure this view")}
374
378
  </a>
375
379
  </td>
376
380
  </tr>