@saltcorn/builder 1.6.0-alpha.1 → 1.6.0-alpha.11

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 +85 -1
  2. package/dist/builder_bundle.js.LICENSE.txt +18 -51
  3. package/package.json +31 -27
  4. package/src/components/Builder.js +445 -155
  5. package/src/components/Library.js +25 -13
  6. package/src/components/RenderNode.js +26 -8
  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 +17 -10
  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 +130 -121
  16. package/src/components/elements/Container.js +185 -92
  17. package/src/components/elements/DropDownFilter.js +10 -8
  18. package/src/components/elements/DropMenu.js +18 -9
  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 +37 -10
  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 +33 -134
  39. package/src/hooks/useTranslation.js +11 -0
  40. package/src/index.js +6 -3
@@ -5,6 +5,7 @@
5
5
  */
6
6
 
7
7
  import React, { useContext, Fragment, useState } from "react";
8
+ import useTranslation from "../../hooks/useTranslation";
8
9
  import { SettingsRow, SettingsSectionHeaderRow, bstyleopt } from "./utils";
9
10
  /*
10
11
  Contains code from https://github.com/tpaksu/boxmodel
@@ -22,6 +23,7 @@ export /**
22
23
  * @namespace
23
24
  */
24
25
  const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
26
+ const { t } = useTranslation();
25
27
  const [selectedCategory, setSelectedCategory] = useState(false);
26
28
  const [selectedDirection, setSelectedDirection] = useState(false);
27
29
  const selectedProperty = !selectedCategory
@@ -45,7 +47,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
45
47
  className="boxmodel-text boxmodel-header"
46
48
  onClick={() => setCatAndDir("margin", null)}
47
49
  >
48
- Margin
50
+ {t("Margin")}
49
51
  </span>
50
52
  <span
51
53
  className="boxmodel-input-container boxmodel-input-direction-left"
@@ -74,7 +76,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
74
76
  className="boxmodel-text boxmodel-header"
75
77
  onClick={() => setCatAndDir("border", null)}
76
78
  >
77
- Border
79
+ {t("Border")}
78
80
  </span>
79
81
  <span
80
82
  className="boxmodel-input-container boxmodel-input-direction-left"
@@ -109,7 +111,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
109
111
  className="boxmodel-text boxmodel-header"
110
112
  onClick={() => setCatAndDir("padding", null)}
111
113
  >
112
- Padding
114
+ {t("Padding")}
113
115
  </span>
114
116
  <span
115
117
  className="boxmodel-input-container boxmodel-input-direction-left"
@@ -274,7 +276,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
274
276
  <SettingsRow
275
277
  field={{
276
278
  name: "width",
277
- label: "width",
279
+ label: t("width"),
278
280
  type: "DimUnits",
279
281
  horiz: true,
280
282
  }}
@@ -285,7 +287,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
285
287
  <SettingsRow
286
288
  field={{
287
289
  name: "min-width",
288
- label: "min width",
290
+ label: t("min width"),
289
291
  type: "DimUnits",
290
292
  horiz: true,
291
293
  }}
@@ -296,7 +298,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
296
298
  <SettingsRow
297
299
  field={{
298
300
  name: "max-width",
299
- label: "max width",
301
+ label: t("max width"),
300
302
  type: "DimUnits",
301
303
  horiz: true,
302
304
  }}
@@ -305,7 +307,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
305
307
  isStyle={true}
306
308
  />
307
309
  <SettingsRow
308
- field={{ name: "height", label: "height", type: "DimUnits" }}
310
+ field={{ name: "height", label: t("height"), type: "DimUnits" }}
309
311
  node={node}
310
312
  setProp={setProp}
311
313
  isStyle={!!sizeWithStyle}
@@ -313,7 +315,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
313
315
  <SettingsRow
314
316
  field={{
315
317
  name: sizeWithStyle ? "min-height" : "minHeight",
316
- label: "min height",
318
+ label: t("min height"),
317
319
  type: "DimUnits",
318
320
  }}
319
321
  node={node}
@@ -323,7 +325,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
323
325
  <SettingsRow
324
326
  field={{
325
327
  name: "max-height",
326
- label: "max height",
328
+ label: t("max height"),
327
329
  type: "DimUnits",
328
330
  }}
329
331
  node={node}
@@ -338,7 +340,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
338
340
  <SettingsRow
339
341
  field={{
340
342
  name: selectedProperty + "-width",
341
- label: "width",
343
+ label: t("width"),
342
344
  type: "DimUnits",
343
345
  }}
344
346
  node={node}
@@ -348,7 +350,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
348
350
  <SettingsRow
349
351
  field={{
350
352
  name: selectedProperty + "-style",
351
- label: "style",
353
+ label: t("style"),
352
354
  type: "btn_select",
353
355
  btnClass: "btnstylesel",
354
356
  options: [
@@ -369,7 +371,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
369
371
  <SettingsRow
370
372
  field={{
371
373
  name: selectedProperty + "-color",
372
- label: "color",
374
+ label: t("color"),
373
375
  type: "Color",
374
376
  }}
375
377
  node={node}
@@ -380,7 +382,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
380
382
  <SettingsRow
381
383
  field={{
382
384
  name: selectedProperty + "-radius",
383
- label: "radius",
385
+ label: t("radius"),
384
386
  type: "DimUnits",
385
387
  }}
386
388
  node={node}
@@ -407,17 +409,17 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
407
409
  {!selectedProperty && (
408
410
  <tr>
409
411
  <td colSpan={2}>
410
- <div>Click above to select a property to adjust.</div>
412
+ <div>{t("Click above to select a property to adjust.")}</div>
411
413
  <div>
412
414
  <small>
413
- Click a label (margin, border, padding) to adjust all edges.
415
+ {t("Click a label (margin, border, padding) to adjust all edges.")}
414
416
  </small>
415
417
  </div>
416
418
  <div>
417
- <small>Click an edge to adjust that edge.</small>
419
+ <small>{t("Click an edge to adjust that edge.")}</small>
418
420
  </div>
419
421
  <div>
420
- <small>Click center to adjust height and width</small>
422
+ <small>{t("Click center to adjust height and width")}</small>
421
423
  </div>
422
424
  </td>
423
425
  </tr>
@@ -16,9 +16,10 @@ import {
16
16
  buildOptions,
17
17
  } from "./utils";
18
18
  import { Column } from "./Column";
19
-
20
19
  import { Element, useNode } from "@craftjs/core";
21
20
  import { BoxModelEditor } from "./BoxModelEditor";
21
+ import useTranslation from "../../hooks/useTranslation";
22
+
22
23
  import {
23
24
  AlignTop,
24
25
  AlignMiddle,
@@ -40,7 +41,7 @@ import { bstyleopt } from "./utils";
40
41
 
41
42
  export /**
42
43
  * @param {object} props
43
- * @param {string} props.children
44
+ * @param {string} props.contents - Card body contents
44
45
  * @param {object} props.isFormula
45
46
  * @param {string} [props.title]
46
47
  * @param {string} props.shadow
@@ -52,7 +53,7 @@ export /**
52
53
  * @namespace
53
54
  */
54
55
  const Card = ({
55
- children,
56
+ contents,
56
57
  isFormula,
57
58
  title,
58
59
  shadow,
@@ -69,6 +70,7 @@ const Card = ({
69
70
  gradStartColor,
70
71
  gradEndColor,
71
72
  gradDirection,
73
+ titleRight,
72
74
  }) => {
73
75
  const {
74
76
  selected,
@@ -110,12 +112,17 @@ const Card = ({
110
112
  <img src={`/files/serve/${bgFileId}`} className="card-img-top" />
111
113
  ) : null}
112
114
  {title && title.length > 0 && (
113
- <div className="card-header">
115
+ <div className="card-header right-section">
114
116
  {isFormula?.title ? (
115
117
  <span className="font-monospace">={title}</span>
116
118
  ) : (
117
119
  title
118
120
  )}
121
+ <div className='title-right'>
122
+ <Element canvas id="titleRight" is={Column}>
123
+ {titleRight}
124
+ </Element>
125
+ </div>
119
126
  </div>
120
127
  )}
121
128
  <div
@@ -132,7 +139,9 @@ const Card = ({
132
139
  : {}
133
140
  }
134
141
  >
135
- <div className="canvas">{children}</div>
142
+ <Element canvas id="cardbody" is={Column}>
143
+ {contents}
144
+ </Element>
136
145
  </div>
137
146
  {hasFooter ? (
138
147
  <div className={`card-footer ${noPadding ? "p-0" : ""}`}>
@@ -152,6 +161,7 @@ export /**
152
161
  * @namespace
153
162
  */
154
163
  const CardSettings = () => {
164
+ const { t } = useTranslation();
155
165
  const node = useNode((node) => {
156
166
  const ps = {
157
167
  currentSettingsTab: node.data.props.currentSettingsTab,
@@ -186,11 +196,11 @@ const CardSettings = () => {
186
196
  value={currentSettingsTab}
187
197
  onChange={(ix) => setProp((prop) => (prop.currentSettingsTab = ix))}
188
198
  >
189
- <table className="w-100" accordiontitle="Card properties">
199
+ <table className="w-100" accordiontitle={t("Card properties")}>
190
200
  <tbody>
191
201
  <SettingsRow
192
202
  field={{
193
- label: "Card title",
203
+ label: t("Card title"),
194
204
  name: "title",
195
205
  type: "String",
196
206
  canBeFormula: true,
@@ -200,7 +210,7 @@ const CardSettings = () => {
200
210
  />
201
211
  <SettingsRow
202
212
  field={{
203
- label: "Click URL",
213
+ label: t("Click URL"),
204
214
  name: "url",
205
215
  type: "String",
206
216
  canBeFormula: true,
@@ -210,7 +220,7 @@ const CardSettings = () => {
210
220
  />
211
221
  <SettingsRow
212
222
  field={{
213
- label: "Class",
223
+ label: t("Class"),
214
224
  name: "class",
215
225
  type: "String",
216
226
  canBeFormula: true,
@@ -219,18 +229,18 @@ const CardSettings = () => {
219
229
  setProp={setProp}
220
230
  />
221
231
  <SettingsRow
222
- field={{ label: "Card footer", name: "hasFooter", type: "Bool" }}
232
+ field={{ label: t("Card footer"), name: "hasFooter", type: "Bool" }}
223
233
  node={node}
224
234
  setProp={setProp}
225
235
  />
226
236
  <SettingsRow
227
- field={{ label: "Shadow", name: "shadow", type: "Bool" }}
237
+ field={{ label: t("Shadow"), name: "shadow", type: "Bool" }}
228
238
  node={node}
229
239
  setProp={setProp}
230
240
  />
231
241
  <SettingsRow
232
242
  field={{
233
- label: "Save indicator",
243
+ label: t("Save indicator"),
234
244
  name: "titleAjaxIndicator",
235
245
  type: "Bool",
236
246
  }}
@@ -238,37 +248,37 @@ const CardSettings = () => {
238
248
  setProp={setProp}
239
249
  />
240
250
  <SettingsRow
241
- field={{ label: "No padding", name: "noPadding", type: "Bool" }}
251
+ field={{ label: t("No padding"), name: "noPadding", type: "Bool" }}
242
252
  node={node}
243
253
  setProp={setProp}
244
254
  />
245
255
  </tbody>
246
256
  </table>
247
- <div accordiontitle="Box" className="w-100">
257
+ <div accordiontitle={t("Box")} className="w-100">
248
258
  <BoxModelEditor setProp={setProp} node={node} sizeWithStyle={true} />
249
259
  </div>
250
- <table className="w-100" accordiontitle="Contents">
260
+ <table className="w-100" accordiontitle={t("Contents")}>
251
261
  <tbody>
252
- <SettingsSectionHeaderRow title="Align" />
262
+ <SettingsSectionHeaderRow title={t("Align")} />
253
263
  <SettingsRow
254
264
  field={{
255
265
  name: "hAlign",
256
- label: "Horizontal",
266
+ label: t("Horizontal"),
257
267
  type: "btn_select",
258
268
  options: [
259
- { value: "start", title: "Left", label: <AlignStart /> },
260
- { value: "center", title: "Center", label: <AlignCenter /> },
261
- { value: "end", title: "Right", label: <AlignEnd /> },
269
+ { value: "start", title: t("Left"), label: <AlignStart /> },
270
+ { value: "center", title: t("Center"), label: <AlignCenter /> },
271
+ { value: "end", title: t("Right"), label: <AlignEnd /> },
262
272
  ],
263
273
  }}
264
274
  node={node}
265
275
  setProp={setProp}
266
276
  />
267
- <SettingsSectionHeaderRow title="Background" />
277
+ <SettingsSectionHeaderRow title={t("Background")} />
268
278
  <SettingsRow
269
279
  field={{
270
280
  name: "bgType",
271
- label: "Type",
281
+ label: t("Type"),
272
282
  type: "btn_select",
273
283
  options: [
274
284
  { value: "None", label: <SlashCircle /> },
@@ -294,7 +304,7 @@ const CardSettings = () => {
294
304
  {bgType === "Gradient" && (
295
305
  <Fragment>
296
306
  <tr>
297
- <td>Start</td>
307
+ <td>{t("Start")}</td>
298
308
  <td>
299
309
  <OrFormula
300
310
  nodekey="gradStartColor"
@@ -310,7 +320,7 @@ const CardSettings = () => {
310
320
  </td>
311
321
  </tr>
312
322
  <tr>
313
- <td>End</td>
323
+ <td>{t("End")}</td>
314
324
  <td>
315
325
  <OrFormula
316
326
  nodekey="gradEndColor"
@@ -326,7 +336,7 @@ const CardSettings = () => {
326
336
  </td>
327
337
  </tr>
328
338
  <tr>
329
- <td>Direction (&deg;)</td>
339
+ <td>{t("Direction (&deg;)")}</td>
330
340
  <td>
331
341
  <OrFormula
332
342
  nodekey="gradDirection"
@@ -348,7 +358,7 @@ const CardSettings = () => {
348
358
  {bgType === "Image" && (
349
359
  <tr>
350
360
  <td>
351
- <label>File</label>
361
+ <label>{t("File")}</label>
352
362
  </td>
353
363
  <td>
354
364
  <select
@@ -373,7 +383,7 @@ const CardSettings = () => {
373
383
  {bgType === "Image Field" && (
374
384
  <tr>
375
385
  <td>
376
- <label>File field</label>
386
+ <label>{t("File field")}</label>
377
387
  </td>
378
388
  <td>
379
389
  <select
@@ -401,7 +411,7 @@ const CardSettings = () => {
401
411
  <Fragment>
402
412
  <tr>
403
413
  <td>
404
- <label>Location</label>
414
+ <label>{t("Location")}</label>
405
415
  </td>
406
416
 
407
417
  <td>
@@ -410,9 +420,9 @@ const CardSettings = () => {
410
420
  className="form-control-sm form-select"
411
421
  onChange={setAProp("imageLocation")}
412
422
  >
413
- <option>Card</option>
414
- <option>Body</option>
415
- <option>Top</option>
423
+ <option>{t("Card")}</option>
424
+ <option>{t("Body")}</option>
425
+ <option>{t("Top")}</option>
416
426
  </select>
417
427
  </td>
418
428
  </tr>
@@ -423,7 +433,7 @@ const CardSettings = () => {
423
433
  <Fragment>
424
434
  <tr>
425
435
  <td>
426
- <label>Size</label>
436
+ <label>{t("Size")}</label>
427
437
  </td>
428
438
 
429
439
  <td>
@@ -440,7 +450,7 @@ const CardSettings = () => {
440
450
  )}
441
451
  {bgType === "Color" && (
442
452
  <tr>
443
- <td>Color</td>
453
+ <td>{t("Color")}</td>
444
454
  <td>
445
455
  <OrFormula nodekey="bgColor" {...{ setProp, isFormula, node }}>
446
456
  <input
@@ -472,6 +482,8 @@ const fields = [
472
482
  { label: "Card footer", name: "hasFooter", type: "Bool" },
473
483
  { label: "Save indicator", name: "titleAjaxIndicator", type: "Bool" },
474
484
  { label: "No padding", name: "noPadding", type: "Bool" },
485
+ { label: "Contents", name: "contents", type: "Nodes", nodeID: "cardbody" },
486
+ { label: "Title Right", name: "titleRight", type: "Nodes", nodeID: "titleRight" },
475
487
  { label: "Footer", name: "footer", type: "Nodes", nodeID: "cardfooter" },
476
488
  { name: "style", default: {} },
477
489
  { label: "Class", name: "class", type: "String", canBeFormula: true },
@@ -498,13 +510,14 @@ Card.craft = {
498
510
  shadow: true,
499
511
  isFormula: {},
500
512
  style: {},
513
+ contents: [],
501
514
  footer: [],
515
+ titleRight: [],
502
516
  },
503
517
  displayName: "Card",
504
518
  related: {
505
519
  settings: CardSettings,
506
520
  segment_type: "card",
507
- hasContents: true,
508
521
  fields,
509
522
  },
510
523
  };
@@ -1,11 +1,26 @@
1
1
  import React, { Fragment, useState, useEffect } from "react";
2
2
  import { ListColumn } from "./ListColumn";
3
3
  import { Columns, ntimes } from "./Columns";
4
+ import { Card } from "./Card";
5
+ import { Container } from "./Container";
6
+ import { Tabs } from "./Tabs";
7
+ import { Table } from "./Table";
4
8
  import { Element } from "@craftjs/core";
5
9
 
6
10
  const rand_ident = () =>
7
11
  Math.floor(Math.random() * 16777215).toString(16);
8
12
 
13
+ /**
14
+ * Clone the children of a linked node (not the linked node wrapper itself).
15
+ * The parent component's render recreates the wrapper via <Element canvas>.
16
+ */
17
+ const cloneLinkedNodeChildren = (query, linkedNodeId) => {
18
+ if (!linkedNodeId) return undefined;
19
+ const linkedNodeData = query.node(linkedNodeId).get().data;
20
+ const childNodes = linkedNodeData.nodes || [];
21
+ return childNodes.map(recursivelyCloneToElems(query));
22
+ };
23
+
9
24
  export const recursivelyCloneToElems = (query) => (nodeId, ix) => {
10
25
  const { data } = query.node(nodeId).get();
11
26
  const { type, props, nodes } = data;
@@ -14,8 +29,6 @@ export const recursivelyCloneToElems = (query) => (nodeId, ix) => {
14
29
  newProps.rndid = rand_ident();
15
30
  }
16
31
 
17
- // console.log("cloning", data.displayName, data.linkedNodes["listcol"]);
18
-
19
32
  const children = (nodes || []).map(recursivelyCloneToElems(query));
20
33
  if (data.displayName === "Columns") {
21
34
  const cols = ntimes(data.props.ncols, (ix) =>
@@ -37,6 +50,65 @@ export const recursivelyCloneToElems = (query) => (nodeId, ix) => {
37
50
  });
38
51
  }
39
52
 
53
+ if (data.displayName === "Card") {
54
+ const clonedContents = cloneLinkedNodeChildren(
55
+ query,
56
+ data.linkedNodes["cardbody"]
57
+ );
58
+ const clonedFooter = cloneLinkedNodeChildren(
59
+ query,
60
+ data.linkedNodes["cardfooter"]
61
+ );
62
+ return React.createElement(Card, {
63
+ ...newProps,
64
+ ...(typeof ix !== "undefined" ? { key: ix } : {}),
65
+ contents: clonedContents,
66
+ footer: clonedFooter,
67
+ });
68
+ }
69
+
70
+ if (data.displayName === "Container") {
71
+ const clonedContents = cloneLinkedNodeChildren(
72
+ query,
73
+ data.linkedNodes["container-canvas"]
74
+ );
75
+ return React.createElement(Container, {
76
+ ...newProps,
77
+ ...(typeof ix !== "undefined" ? { key: ix } : {}),
78
+ contents: clonedContents,
79
+ });
80
+ }
81
+
82
+ if (data.displayName === "Tabs") {
83
+ const cols = ntimes(data.props.ntabs, (ix) =>
84
+ data.linkedNodes["Tab" + ix]
85
+ ? cloneLinkedNodeChildren(query, data.linkedNodes["Tab" + ix])
86
+ : undefined
87
+ );
88
+ return React.createElement(Tabs, {
89
+ ...newProps,
90
+ ...(typeof ix !== "undefined" ? { key: ix } : {}),
91
+ contents: cols,
92
+ });
93
+ }
94
+
95
+ if (data.displayName === "Table") {
96
+ const rows = data.props.rows || 0;
97
+ const columns = data.props.columns || 0;
98
+ const clonedContents = ntimes(rows, (ri) =>
99
+ ntimes(columns, (ci) =>
100
+ data.linkedNodes[`cell_${ri}_${ci}`]
101
+ ? cloneLinkedNodeChildren(query, data.linkedNodes[`cell_${ri}_${ci}`])
102
+ : undefined
103
+ )
104
+ );
105
+ return React.createElement(Table, {
106
+ ...newProps,
107
+ ...(typeof ix !== "undefined" ? { key: ix } : {}),
108
+ contents: clonedContents,
109
+ });
110
+ }
111
+
40
112
  if (data.isCanvas)
41
113
  return React.createElement(
42
114
  Element,
@@ -64,7 +64,7 @@ Column.craft = {
64
64
  props: {},
65
65
  rules: {
66
66
  canDrag: () => true,
67
- canMoveIn: (incomming, current) => {
67
+ canMoveIn: (incoming, current) => {
68
68
  if (current?.data?.props?.singleOccupancy && current.data.nodes?.length)
69
69
  return false;
70
70