@saltcorn/builder 1.6.0-alpha.0 → 1.6.0-alpha.10

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 +4126 -2
  2. package/dist/builder_bundle.js.LICENSE.txt +18 -51
  3. package/package.json +31 -27
  4. package/src/components/Builder.js +334 -122
  5. package/src/components/Library.js +25 -13
  6. package/src/components/RenderNode.js +19 -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 +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 +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 +27 -129
  39. package/src/hooks/useTranslation.js +11 -0
  40. package/src/index.js +6 -3
@@ -11,6 +11,7 @@ import React, {
11
11
  useEffect,
12
12
  useState,
13
13
  } from "react";
14
+ import useTranslation from "../../hooks/useTranslation";
14
15
 
15
16
  import { Element, useNode, useEditor } from "@craftjs/core";
16
17
  import { setAPropGen, SettingsFromFields } from "./utils";
@@ -38,6 +39,7 @@ const ListColumn = ({
38
39
  col_width,
39
40
  col_width_units,
40
41
  }) => {
42
+ const { t } = useTranslation();
41
43
  const {
42
44
  selected,
43
45
  id,
@@ -75,16 +77,18 @@ const ListColumn = ({
75
77
  <div className={`list-column flex-50 p-2`}>
76
78
  <div className="d-flex justify-content-between h-100">
77
79
  <div className="">
78
- Column {childIx}
80
+ {t("Column")} {childIx}
79
81
  {header_label ? `: ${header_label}` : ""}
80
82
  <br />
81
83
  {showif ? (
82
- <span className="badge bg-secondary me-2">showif</span>
84
+ <span className="badge bg-secondary me-2">{t("showif")}</span>
83
85
  ) : (
84
86
  ""
85
87
  )}
86
88
  {alignment && alignment !== "Default" ? (
87
- <span className="badge bg-secondary me-2">Align {alignment}</span>
89
+ <span className="badge bg-secondary me-2">
90
+ {t("Align")} {alignment}
91
+ </span>
88
92
  ) : (
89
93
  ""
90
94
  )}
@@ -53,7 +53,10 @@ ListColumns.craft = {
53
53
  canDrag: () => false,
54
54
  canDrop: () => false,
55
55
  canMoveIn: (incoming) => {
56
- return incoming?.data?.displayName === "ListColumn";
56
+ const incomingNodes = Array.isArray(incoming) ? incoming : [incoming];
57
+ return incomingNodes.every(
58
+ (node) => node?.data?.displayName === "ListColumn"
59
+ );
57
60
  },
58
61
  },
59
62
  related: {
@@ -68,7 +68,7 @@ export const SingleLineEditor = React.forwardRef(
68
68
  }
69
69
  );
70
70
 
71
- export const MultiLineCodeEditor = ({ setProp, value, onChange }) => {
71
+ export const MultiLineCodeEditor = ({ setProp, value, onChange, isModalEditor = false }) => {
72
72
  const options = React.useContext(optionsCtx);
73
73
 
74
74
  const handleEditorWillMount = (monaco) => {
@@ -77,7 +77,7 @@ export const MultiLineCodeEditor = ({ setProp, value, onChange }) => {
77
77
  return (
78
78
  <div className="form-control p-0 pt-2">
79
79
  <Editor
80
- height="150px"
80
+ height={isModalEditor ? "100%" : "150px"}
81
81
  value={value}
82
82
  onChange={onChange}
83
83
  defaultLanguage="typescript"
@@ -86,6 +86,7 @@ export const MultiLineCodeEditor = ({ setProp, value, onChange }) => {
86
86
  options={multiLineEditorOptions}
87
87
  //theme="myCoolTheme"
88
88
  beforeMount={handleEditorWillMount}
89
+ className={isModalEditor ? 'code-modal-form' : ''}
89
90
  />
90
91
  </div>
91
92
  );
@@ -93,6 +94,7 @@ export const MultiLineCodeEditor = ({ setProp, value, onChange }) => {
93
94
 
94
95
  const multiLineEditorOptions = {
95
96
  fontSize: "14px",
97
+ minHeight:"80vh",
96
98
  fontWeight: "normal",
97
99
  wordWrap: "off",
98
100
  lineNumbers: "off",
@@ -1,10 +1,12 @@
1
- import React, { Fragment, useContext, useEffect } from "react";
1
+ import React, { Fragment, useState, useContext, useEffect } from "react";
2
+ import useTranslation from "../../hooks/useTranslation";
2
3
  import { useNode } from "@craftjs/core";
3
4
  import optionsCtx from "../context";
4
5
  import previewCtx from "../preview_context";
5
6
  import { fetchPagePreview, setAPropGen } from "./utils";
6
7
 
7
8
  export const Page = ({ page }) => {
9
+ const { t } = useTranslation();
8
10
  const {
9
11
  selected,
10
12
  node_id,
@@ -35,13 +37,14 @@ export const Page = ({ page }) => {
35
37
  dangerouslySetInnerHTML={{ __html: myPreview }}
36
38
  ></div>
37
39
  ) : (
38
- `Page: ${page}`
40
+ `${t("Page")}: ${page}`
39
41
  )}
40
42
  </div>
41
43
  );
42
44
  };
43
45
 
44
46
  export const PageSettings = () => {
47
+ const { t } = useTranslation();
45
48
  const node = useNode((node) => ({
46
49
  page: node.data.props.page,
47
50
  node_id: node.id,
@@ -58,7 +61,7 @@ export const PageSettings = () => {
58
61
  <div>
59
62
  <Fragment>
60
63
  <div>
61
- <label>Page to embed</label>
64
+ <label>{t("Page to embed")}</label>
62
65
  <select
63
66
  value={page}
64
67
  className="form-control form-select"
@@ -83,7 +86,7 @@ export const PageSettings = () => {
83
86
  target="_blank"
84
87
  href={`/pageedit/edit/${page}`}
85
88
  >
86
- Edit this page
89
+ {t("Edit this page")}
87
90
  </a>
88
91
  ) : null}
89
92
  </div>
@@ -1,4 +1,6 @@
1
- import React from "react";
1
+ import React, { Fragment, useState, useContext } from "react";
2
+ import useTranslation from "../../hooks/useTranslation";
3
+ import optionsCtx from "../context";
2
4
  import { removeWhitespaces } from "./utils";
3
5
  import {
4
6
  parseLegacyRelation,
@@ -6,14 +8,14 @@ import {
6
8
  Relation,
7
9
  } from "@saltcorn/common-code";
8
10
 
9
- const buildBadgeCfgs = (sourceTblName, type, path, caches, relString) => {
11
+ const buildBadgeCfgs = (sourceTblName, type, path, caches, relString, t) => {
10
12
  if (type === RelationType.OWN)
11
- return [{ table: `${sourceTblName} (same table)` }];
13
+ return [{ table: `${sourceTblName} ${t("(same table)")}` }];
12
14
  else if (type === RelationType.INDEPENDENT)
13
- return [{ table: "None (no relation)" }];
14
- else if (path.length === 0) return [{ table: "invalid relation" }];
15
+ return [{ table: t("None (no relation)") }];
16
+ else if (path.length === 0) return [{ table: t("invalid relation") }];
15
17
  else if (relString === Relation.fixedUserRelation)
16
- return [{ table: "logged in user" }];
18
+ return [{ table: t("logged in user") }];
17
19
  else {
18
20
  const result = [];
19
21
  let currentCfg = null;
@@ -82,6 +84,7 @@ const buildBadge = ({ up, table, down }, index) => {
82
84
  };
83
85
 
84
86
  export const RelationBadges = ({ view, relation, parentTbl, caches }) => {
87
+ const { t } = useTranslation();
85
88
  if (relation) {
86
89
  return (
87
90
  <div className="overflow-scroll">
@@ -90,16 +93,17 @@ export const RelationBadges = ({ view, relation, parentTbl, caches }) => {
90
93
  relation.type,
91
94
  relation.path,
92
95
  caches,
93
- relation.relationString
96
+ relation.relationString,
97
+ t
94
98
  ).map(buildBadge)}
95
99
  </div>
96
100
  );
97
101
  } else {
98
- if (!view) return buildBadge({ table: "invalid relation" }, 0);
102
+ if (!view) return buildBadge({ table: t("invalid relation") }, 0);
99
103
  const [prefix, rest] = view.split(":");
100
- if (!rest) return buildBadge({ table: "invalid relation" }, 0);
104
+ if (!rest) return buildBadge({ table: t("invalid relation") }, 0);
101
105
  const { type, path } = parseLegacyRelation(prefix, rest, parentTbl);
102
- if (path.length === 0) return buildBadge({ table: "invalid relation" }, 0);
106
+ if (path.length === 0) return buildBadge({ table: t("invalid relation") }, 0);
103
107
  else if (path.length === 1 && (type === "Independent" || type === "Own"))
104
108
  return (
105
109
  <div className="overflow-scroll">
@@ -114,7 +118,8 @@ export const RelationBadges = ({ view, relation, parentTbl, caches }) => {
114
118
  type,
115
119
  path,
116
120
  caches,
117
- relation.relationString
121
+ relation.relationString,
122
+ t
118
123
  ).map(buildBadge)}
119
124
  </div>
120
125
  );
@@ -1,8 +1,17 @@
1
+ /* globals $ */
2
+
1
3
  import React from "react";
4
+ import { createRoot } from "react-dom/client";
2
5
  import { removeWhitespaces, rand_ident } from "./utils";
3
- import ReactDOM from "react-dom";
6
+ import useTranslation from "../../hooks/useTranslation";
4
7
 
5
8
  const maxLevelDefault = 10;
9
+ const renderInto = (container, node) => {
10
+ if (!container) return;
11
+ const root = container.__scRoot || createRoot(container);
12
+ container.__scRoot = root;
13
+ root.render(node);
14
+ };
6
15
 
7
16
  const keyLabel = (key, type) =>
8
17
  type === "fk" ? `${key.name}` : `${key.name} (from ${key.table})`;
@@ -78,15 +87,11 @@ const Relation = ({ cfg }) => {
78
87
  maxLevel,
79
88
  setMaxLevel,
80
89
  };
81
- ReactDOM.render(
82
- <RelationLayer cfg={layerCfg} />,
83
- document.getElementById(nextDropId),
84
- () => {
85
- toggleLayers(level, maxLevel, [`#${toggleId}`]);
86
- setActiveClasses(level, maxLevel, itemId);
87
- if (level > maxLevel) setMaxLevel(level);
88
- }
89
- );
90
+ const container = document.getElementById(nextDropId);
91
+ renderInto(container, <RelationLayer cfg={layerCfg} />);
92
+ toggleLayers(level, maxLevel, [`#${toggleId}`]);
93
+ setActiveClasses(level, maxLevel, itemId);
94
+ if (level > maxLevel) setMaxLevel(level);
90
95
  }}
91
96
  >
92
97
  {keyLabel(relation, type)}
@@ -178,6 +183,7 @@ const RelationLayer = ({ cfg }) => {
178
183
  * @returns
179
184
  */
180
185
  export const RelationOnDemandPicker = ({ relations, update }) => {
186
+ const { t } = useTranslation();
181
187
  const [maxLevel, setMaxLevel] = React.useState(maxLevelDefault);
182
188
  const toggleId = "_relation_picker_toggle_";
183
189
  const layerCfg = {
@@ -189,7 +195,7 @@ export const RelationOnDemandPicker = ({ relations, update }) => {
189
195
  };
190
196
  return (
191
197
  <div>
192
- <label>Relation</label>
198
+ <label>{t("Relation")}</label>
193
199
  <div style={{ zIndex: 10000 }} className="dropstart">
194
200
  <button
195
201
  id={toggleId}
@@ -201,7 +207,7 @@ export const RelationOnDemandPicker = ({ relations, update }) => {
201
207
  setMaxLevel(maxLevelDefault);
202
208
  }}
203
209
  >
204
- Select
210
+ {t("Select")}
205
211
  </button>
206
212
  <div className="dropdown-menu">
207
213
  <RelationLayer cfg={layerCfg} />
@@ -4,8 +4,10 @@
4
4
  * @subcategory components / elements
5
5
  */
6
6
 
7
- import React, { Fragment, useState } from "react";
8
- import { Element, useNode } from "@craftjs/core";
7
+ import React, { Fragment, useState, useEffect, useContext } from "react";
8
+ import useTranslation from "../../hooks/useTranslation";
9
+ import optionsCtx from "../context";
10
+ import { useNode, Element } from "@craftjs/core";
9
11
  import { Column } from "./Column";
10
12
  import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
11
13
  import { faCaretDown } from "@fortawesome/free-solid-svg-icons";
@@ -21,13 +23,23 @@ export /**
21
23
  * @category saltcorn-builder
22
24
  * @subcategory components
23
25
  */
24
- const SearchBar = ({ has_dropdown, children, show_badges }) => {
26
+ const SearchBar = ({ has_dropdown, children, contents, show_badges }) => {
27
+ const { t } = useTranslation();
25
28
  const {
26
29
  selected,
27
30
  connectors: { connect, drag },
28
31
  } = useNode((node) => ({ selected: node.events.selected }));
29
32
  const [showDropdown, setDropdown] = useState(false);
30
33
  const [dropWidth, setDropWidth] = useState(200);
34
+
35
+ const renderContents = () => {
36
+ const actualChildren = contents || children;
37
+ if (!actualChildren) return null;
38
+ if (React.isValidElement(actualChildren)) return actualChildren;
39
+ if (Array.isArray(actualChildren)) return actualChildren;
40
+ return actualChildren;
41
+ };
42
+
31
43
  return (
32
44
  <div
33
45
  className={`input-group ${selected ? "selected-node" : ""}`}
@@ -47,7 +59,7 @@ const SearchBar = ({ has_dropdown, children, show_badges }) => {
47
59
  <input
48
60
  type="text"
49
61
  className="form-control bg-light"
50
- placeholder="Search..."
62
+ placeholder={t("Search...")}
51
63
  readOnly={true}
52
64
  />
53
65
 
@@ -70,10 +82,19 @@ const SearchBar = ({ has_dropdown, children, show_badges }) => {
70
82
  }`}
71
83
  style={{ width: dropWidth, left: 0 }}
72
84
  >
73
- <div className="canvas">{children}</div>
85
+ <Element canvas id="searchbar-contents" is={Column}>
86
+ {renderContents()}
87
+ </Element>
74
88
  </div>
75
89
  </Fragment>
76
90
  )}
91
+ {!has_dropdown && (
92
+ <div style={{ display: "none" }}>
93
+ <Element canvas id="searchbar-contents" is={Column}>
94
+ {renderContents()}
95
+ </Element>
96
+ </div>
97
+ )}
77
98
  </div>
78
99
  );
79
100
  };
@@ -85,6 +106,7 @@ export /**
85
106
  * @subcategory components
86
107
  */
87
108
  const SearchBarSettings = () => {
109
+ const { t } = useTranslation();
88
110
  const {
89
111
  actions: { setProp },
90
112
  has_dropdown,
@@ -107,7 +129,7 @@ const SearchBarSettings = () => {
107
129
  checked={has_dropdown}
108
130
  onChange={setAProp("has_dropdown", { checked: true })}
109
131
  />
110
- <label className="form-check-label">Has Dropdown</label>
132
+ <label className="form-check-label">{t("Has Dropdown")}</label>
111
133
  </div>
112
134
  <div className="form-check">
113
135
  <input
@@ -117,7 +139,7 @@ const SearchBarSettings = () => {
117
139
  checked={show_badges}
118
140
  onChange={setAProp("show_badges", { checked: true })}
119
141
  />
120
- <label className="form-check-label">Show current state badges</label>
142
+ <label className="form-check-label">{t("Show current state badges")}</label>
121
143
  </div>
122
144
  <div className="form-check">
123
145
  <input
@@ -127,7 +149,7 @@ const SearchBarSettings = () => {
127
149
  checked={autofocus}
128
150
  onChange={setAProp("autofocus", { checked: true })}
129
151
  />
130
- <label className="form-check-label">Autofocus</label>
152
+ <label className="form-check-label">{t("Autofocus")}</label>
131
153
  </div>
132
154
  </div>
133
155
  );
@@ -142,11 +164,16 @@ SearchBar.craft = {
142
164
  has_dropdown: false,
143
165
  show_badges: false,
144
166
  autofocus: false,
167
+ contents: [],
145
168
  },
146
169
  related: {
147
170
  settings: SearchBarSettings,
148
171
  segment_type: "search_bar",
149
- hasContents: true,
150
- fields: ["has_dropdown", "show_badges", "autofocus"],
172
+ fields: [
173
+ { name: "has_dropdown" },
174
+ { name: "show_badges" },
175
+ { name: "autofocus" },
176
+ { label: "Contents", name: "contents", type: "Nodes", nodeID: "searchbar-contents" },
177
+ ],
151
178
  },
152
179
  };
@@ -7,6 +7,7 @@
7
7
  import React, { Fragment, useState, useContext, useEffect } from "react";
8
8
  import { ntimes } from "./Columns";
9
9
  import { Column } from "./Column";
10
+ import useTranslation from "../../hooks/useTranslation";
10
11
  import optionsCtx from "../context";
11
12
  import { setAPropGen, SettingsFromFields } from "./utils";
12
13
 
@@ -67,60 +68,76 @@ const Table = ({
67
68
  );
68
69
  };
69
70
 
70
- const fields = [
71
- {
72
- label: "Rows",
73
- name: "rows",
74
- type: "Integer",
75
- attributes: { min: 0 },
76
- },
77
- {
78
- label: "Columns",
79
- name: "columns",
80
- type: "Integer",
81
- attributes: { min: 0 },
82
- },
83
- {
84
- name: "customClass",
85
- label: "Custom class",
86
- type: "String",
87
- },
88
- {
89
- label: "Bootstrap style",
90
- name: "bs_style",
91
- type: "Bool",
92
- },
93
- {
94
- label: "Small",
95
- name: "bs_small",
96
- type: "Bool",
97
- showIf: { bs_style: true },
98
- },
99
- {
100
- label: "Striped",
101
- name: "bs_striped",
102
- type: "Bool",
103
- showIf: { bs_style: true },
104
- },
105
- {
106
- label: "Bordered",
107
- name: "bs_bordered",
108
- type: "Bool",
109
- showIf: { bs_style: true },
110
- },
111
- {
112
- label: "Borderless",
113
- name: "bs_borderless",
114
- type: "Bool",
115
- showIf: { bs_style: true },
116
- },
117
- {
118
- label: "Auto width",
119
- name: "bs_wauto",
120
- type: "Bool",
121
- showIf: { bs_style: true },
122
- },
123
- ];
71
+ const TableSettings = () => {
72
+ const { t } = useTranslation();
73
+ const fields = [
74
+ {
75
+ label: t("Rows"),
76
+ name: "rows",
77
+ type: "Integer",
78
+ attributes: { min: 0 },
79
+ },
80
+ {
81
+ label: t("Columns"),
82
+ name: "columns",
83
+ type: "Integer",
84
+ attributes: { min: 0 },
85
+ },
86
+ {
87
+ name: "customClass",
88
+ label: t("Custom class"),
89
+ type: "String",
90
+ },
91
+ {
92
+ label: t("Bootstrap style"),
93
+ name: "bs_style",
94
+ type: "Bool",
95
+ },
96
+ {
97
+ label: t("Small"),
98
+ name: "bs_small",
99
+ type: "Bool",
100
+ showIf: { bs_style: true },
101
+ },
102
+ {
103
+ label: t("Striped"),
104
+ name: "bs_striped",
105
+ type: "Bool",
106
+ showIf: { bs_style: true },
107
+ },
108
+ {
109
+ label: t("Bordered"),
110
+ name: "bs_bordered",
111
+ type: "Bool",
112
+ showIf: { bs_style: true },
113
+ },
114
+ {
115
+ label: t("Borderless"),
116
+ name: "bs_borderless",
117
+ type: "Bool",
118
+ showIf: { bs_style: true },
119
+ },
120
+ {
121
+ label: t("Auto width"),
122
+ name: "bs_wauto",
123
+ type: "Bool",
124
+ showIf: { bs_style: true },
125
+ },
126
+ ];
127
+ return (
128
+ <SettingsFromFields
129
+ fields={fields}
130
+ onChange={(fnm, v, setProp) => {
131
+ if (fnm === "rows")
132
+ setProp((prop) => {
133
+ ntimes(v, (i) => {
134
+ if (!prop.contents[i]) prop.contents[i] = [];
135
+ });
136
+ });
137
+ }}
138
+ />
139
+ );
140
+ };
124
141
 
125
142
  /**
126
143
  * @type {object}
@@ -128,16 +145,6 @@ const fields = [
128
145
  Table.craft = {
129
146
  displayName: "Table",
130
147
  related: {
131
- settings: SettingsFromFields(fields, {
132
- onChange(fnm, v, setProp) {
133
- if (fnm === "rows")
134
- setProp((prop) => {
135
- ntimes(v, (i) => {
136
- if (!prop.contents[i]) prop.contents[i] = [];
137
- });
138
- });
139
- },
140
- }),
141
- fields,
148
+ settings: TableSettings,
142
149
  },
143
150
  };