@saltcorn/builder 1.1.1-beta.2 → 1.1.1-beta.3

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.
@@ -0,0 +1,244 @@
1
+ /**
2
+ * @category saltcorn-builder
3
+ * @module components/elements/utils
4
+ * @subcategory components / elements
5
+ */
6
+ /* globals $, _sc_globalCsrf*/
7
+ import React, { Fragment, useState, useEffect } from "react";
8
+ import optionsCtx from "../context";
9
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
10
+ import {
11
+ faChevronDown,
12
+ faChevronRight,
13
+ faInfoCircle,
14
+ faQuestionCircle,
15
+ faBold,
16
+ faItalic,
17
+ faFont,
18
+ faPlus,
19
+ faCommentSlash,
20
+ faUnderline,
21
+ faAngleDoubleLeft,
22
+ faAngleDoubleRight,
23
+ faTerminal,
24
+ faTrashAlt,
25
+ } from "@fortawesome/free-solid-svg-icons";
26
+ import { useNode, Element, useEditor } from "@craftjs/core";
27
+ import FontIconPicker from "@fonticonpicker/react-fonticonpicker";
28
+ import Tippy from "@tippyjs/react";
29
+ import { RelationType } from "@saltcorn/common-code";
30
+ import Select from "react-select";
31
+ import { recursivelyCloneToElems } from "./Clone";
32
+ import { ConfigField } from "./utils";
33
+ import { Empty } from "./Empty";
34
+ import { Column } from "./Column";
35
+ import storageCtx from "../storage_context";
36
+
37
+ export const DynamicFontAwesomeIcon = ({ icon, className }) => {
38
+ if (!icon) return null;
39
+ return <i className={`${icon} ${className || ""}`}></i>;
40
+ };
41
+
42
+ const ntimes = (n, f) => {
43
+ var res = [];
44
+ for (let index = 0; index < n; index++) {
45
+ res.push(f(index));
46
+ }
47
+ return res;
48
+ };
49
+
50
+ export const ArrayManager = ({
51
+ node,
52
+ setProp,
53
+ countProp,
54
+ currentProp,
55
+ managedArrays,
56
+ manageContents,
57
+ initialAddProps,
58
+ }) => {
59
+ const { actions, query, connectors } = useEditor((state, query) => {
60
+ return {};
61
+ });
62
+ const { craftToSaltcorn, layoutToNodes } = React.useContext(storageCtx);
63
+ const options = React.useContext(optionsCtx);
64
+
65
+ const fullNode = query.node(node.id).get();
66
+ const parentId = fullNode.data.parent;
67
+ const siblings = query.node(parentId).childNodes();
68
+ const sibIx = siblings.findIndex((sib) => sib === node.id);
69
+
70
+ const deleteElem = () => {
71
+ if (manageContents) {
72
+ const rmIx = node[currentProp];
73
+ const { layout } = craftToSaltcorn(
74
+ JSON.parse(query.serialize()),
75
+ node.id,
76
+ options
77
+ );
78
+ layout.contents.splice(rmIx, 1);
79
+
80
+ managedArrays.forEach((arrNm) => {
81
+ layout[arrNm].splice(rmIx, 1);
82
+ });
83
+ layout[countProp] = node[countProp] - 1;
84
+ layout[currentProp] = node[currentProp] - 1;
85
+ actions.delete(node.id);
86
+ layoutToNodes(layout, query, actions, parentId, options, sibIx);
87
+ } else {
88
+ setProp((prop) => {
89
+ const rmIx = prop[currentProp];
90
+
91
+ managedArrays.forEach((arrNm) => {
92
+ if (arrNm.includes(".")) {
93
+ const vars = arrNm.split(".");
94
+ let arr = prop;
95
+ for (const tvar of vars) arr = arr[tvar];
96
+ if (arr) arr.splice(rmIx, 1);
97
+ } else if (prop[arrNm]) prop[arrNm].splice(rmIx, 1);
98
+ });
99
+ prop[countProp] = node[countProp] - 1;
100
+ prop[currentProp] = node[currentProp] - 1;
101
+ });
102
+ }
103
+ };
104
+
105
+ const move = (delta) => {
106
+ const swapElements = (arr, i, j) => {
107
+ const tmp = arr[i];
108
+ arr[i] = arr[j];
109
+ arr[j] = tmp;
110
+ };
111
+ if (manageContents) {
112
+ const curIx = node[currentProp];
113
+
114
+ const { layout } = craftToSaltcorn(
115
+ JSON.parse(query.serialize()),
116
+ node.id,
117
+ options
118
+ );
119
+
120
+ swapElements(layout.contents, curIx, curIx + delta);
121
+
122
+ managedArrays.forEach((arrNm) => {
123
+ if (arrNm.includes(".")) {
124
+ let arr = layout;
125
+ for (const tvar of arrNm.split(".")) arr = arr[tvar];
126
+ if (arr) swapElements(arr, curIx, curIx + delta);
127
+ } else if (layout[arrNm])
128
+ swapElements(layout[arrNm], curIx, curIx + delta);
129
+ });
130
+ layout[currentProp] = node[currentProp] + delta;
131
+ actions.delete(node.id);
132
+ layoutToNodes(layout, query, actions, parentId, options, sibIx);
133
+ } else
134
+ setProp((prop) => {
135
+ const curIx = prop[currentProp];
136
+ if (curIx + delta < 0 || curIx + delta >= prop[countProp]) return;
137
+
138
+ managedArrays.forEach((arrNm) => {
139
+ if (arrNm.includes(".")) {
140
+ let arr = prop;
141
+ for (const tvar of arrNm.split(".")) arr = arr[tvar];
142
+ if (arr) swapElements(arr, curIx, curIx + delta);
143
+ } else if (prop[arrNm])
144
+ swapElements(prop[arrNm], curIx, curIx + delta);
145
+ });
146
+ prop[currentProp] = prop[currentProp] + delta;
147
+ });
148
+ };
149
+ const add = () => {
150
+ if (manageContents) {
151
+ const { layout } = craftToSaltcorn(
152
+ JSON.parse(query.serialize()),
153
+ node.id,
154
+ options
155
+ );
156
+
157
+ layout.contents.push(null);
158
+ managedArrays.forEach((arrNm) => {
159
+ if (initialAddProps?.[arrNm])
160
+ layout[arrNm][node[countProp]] = initialAddProps?.[arrNm];
161
+ });
162
+ layout[currentProp] = node[countProp];
163
+ layout[countProp] = node[countProp] + 1;
164
+
165
+ actions.delete(node.id);
166
+ layoutToNodes(layout, query, actions, parentId, options, sibIx);
167
+ } else
168
+ setProp((prop) => {
169
+ prop[countProp] = node[countProp] + 1;
170
+ prop[currentProp] = node[countProp];
171
+ managedArrays.forEach((arrNm) => {
172
+ if (initialAddProps?.[arrNm])
173
+ if (arrNm.includes(".")) {
174
+ let arr = prop;
175
+ for (const tvar of arrNm.split(".")) arr = arr[tvar];
176
+ if (arr) arr[node[countProp]] = initialAddProps?.[arrNm];
177
+ } else if (prop[arrNm])
178
+ prop[arrNm][node[countProp]] = initialAddProps?.[arrNm];
179
+ });
180
+ });
181
+ };
182
+
183
+ //console.log("arrayman", { node });
184
+
185
+ return (
186
+ <Fragment>
187
+ <ConfigField
188
+ field={{
189
+ name: currentProp,
190
+ label: "Number of things",
191
+ type: "btn_select",
192
+ options: ntimes(node[countProp], (i) => ({
193
+ value: i,
194
+ title: `${i + 1}`,
195
+ label: `${i + 1}`,
196
+ })),
197
+ }}
198
+ node={node}
199
+ setProp={setProp}
200
+ props={node}
201
+ ></ConfigField>
202
+ <div className="btn-group w-100" role="group">
203
+ <button
204
+ title="Move left"
205
+ type="button"
206
+ style={{ width: "25%" }}
207
+ className="btn btn-sm"
208
+ onClick={() => move(-1)}
209
+ disabled={node[currentProp] === 0}
210
+ >
211
+ <FontAwesomeIcon icon={faAngleDoubleLeft} />
212
+ </button>
213
+ <button
214
+ title="Add"
215
+ type="button"
216
+ style={{ width: "25%" }}
217
+ className="btn btn-sm"
218
+ onClick={() => add()}
219
+ >
220
+ <FontAwesomeIcon icon={faPlus} />
221
+ </button>
222
+ <button
223
+ title="Delete"
224
+ type="button"
225
+ style={{ width: "25%" }}
226
+ className="btn btn-sm"
227
+ onClick={() => deleteElem()}
228
+ >
229
+ <FontAwesomeIcon icon={faTrashAlt} />
230
+ </button>
231
+ <button
232
+ title="Move right"
233
+ type="button"
234
+ disabled={node[currentProp] === node[countProp] - 1}
235
+ style={{ width: "25%" }}
236
+ className="btn btn-sm"
237
+ onClick={() => move(1)}
238
+ >
239
+ <FontAwesomeIcon icon={faAngleDoubleRight} />
240
+ </button>
241
+ </div>
242
+ </Fragment>
243
+ );
244
+ };
@@ -1,9 +1,11 @@
1
1
  import React, { Fragment, useState, useEffect } from "react";
2
2
  import { ListColumn } from "./ListColumn";
3
3
  import { Columns, ntimes } from "./Columns";
4
- import { rand_ident } from "./utils";
5
4
  import { Element } from "@craftjs/core";
6
5
 
6
+ const rand_ident = () =>
7
+ Math.floor(Math.random() * 16777215).toString(16);
8
+
7
9
  export const recursivelyCloneToElems = (query) => (nodeId, ix) => {
8
10
  const { data } = query.node(nodeId).get();
9
11
  const { type, props, nodes } = data;
@@ -98,6 +98,7 @@ const Container = ({
98
98
  setTextColor,
99
99
  textColor,
100
100
  customClass,
101
+ customId,
101
102
  customCSS,
102
103
  margin,
103
104
  padding,
@@ -119,6 +120,7 @@ const Container = ({
119
120
  htmlElement,
120
121
  {
121
122
  ref: (dom) => connect(drag(dom)),
123
+ id: customId || "",
122
124
  className: `${customClass || ""} kontainer canvas text-${hAlign} ${
123
125
  vAlign === "middle" ? "d-flex align-items-center" : ""
124
126
  } ${
@@ -202,6 +204,7 @@ const ContainerSettings = () => {
202
204
  showForRole: node.data.props.showForRole,
203
205
  textColor: node.data.props.textColor,
204
206
  customClass: node.data.props.customClass,
207
+ customId: node.data.props.customId,
205
208
  customCSS: node.data.props.customCSS,
206
209
  minScreenWidth: node.data.props.minScreenWidth,
207
210
  maxScreenWidth: node.data.props.maxScreenWidth,
@@ -220,6 +223,10 @@ const ContainerSettings = () => {
220
223
  transform: node.data.props.transform,
221
224
  imgResponsiveWidths: node.data.props.imgResponsiveWidths,
222
225
  click_action: node.data.props.click_action,
226
+ animateName: node.data.props.animateName,
227
+ animateDelay: node.data.props.animateDelay,
228
+ animateDuration: node.data.props.animateDuration,
229
+ animateInitialHide: node.data.props.animateInitialHide,
223
230
  }));
224
231
  const {
225
232
  actions: { setProp },
@@ -234,6 +241,7 @@ const ContainerSettings = () => {
234
241
  isFormula,
235
242
  showForRole,
236
243
  customClass,
244
+ customId,
237
245
  customCSS,
238
246
  minScreenWidth,
239
247
  maxScreenWidth,
@@ -848,6 +856,39 @@ const ContainerSettings = () => {
848
856
  )}
849
857
  </tbody>
850
858
  </table>
859
+ <table className="w-100" accordiontitle="Animate">
860
+ <tbody>
861
+ <SettingsRow
862
+ field={{
863
+ name: "animateName",
864
+ label: "Animation",
865
+ type: "select",
866
+ options: ["None", ...options.keyframes || []],
867
+ }}
868
+ node={node}
869
+ setProp={setProp}
870
+ />
871
+ <SettingsRow
872
+ field={{
873
+ name: "animateDuration",
874
+ label: "Duration (s)",
875
+ type: "Float",
876
+ }}
877
+ node={node}
878
+ setProp={setProp}
879
+ />
880
+ <SettingsRow
881
+ field={{ name: "animateDelay", label: "Delay (s)", type: "Float" }}
882
+ node={node}
883
+ setProp={setProp}
884
+ />
885
+ <SettingsRow
886
+ field={{ name: "animateInitialHide", label: "Initially hidden", type: "Bool" }}
887
+ node={node}
888
+ setProp={setProp}
889
+ />
890
+ </tbody>
891
+ </table>
851
892
  <table className="w-100" accordiontitle="Show if...">
852
893
  <tbody>
853
894
  {["show", "edit", "filter"].includes(options.mode) && (
@@ -997,7 +1038,18 @@ const ContainerSettings = () => {
997
1038
  </select>
998
1039
  </div>
999
1040
 
1000
- <div accordiontitle="Custom class/CSS">
1041
+ <div accordiontitle="Class, ID and CSS">
1042
+ <div>
1043
+ <label>ID</label>
1044
+ </div>
1045
+ <OrFormula nodekey="customId" {...{ setProp, isFormula, node }}>
1046
+ <input
1047
+ type="text"
1048
+ className="form-control text-to-display"
1049
+ value={customId}
1050
+ onChange={setAProp("customId")}
1051
+ />
1052
+ </OrFormula>
1001
1053
  <div>
1002
1054
  <label>Custom class</label>
1003
1055
  </div>
@@ -9,6 +9,7 @@ import { ntimes } from "./Columns";
9
9
  import { Column } from "./Column";
10
10
  import optionsCtx from "../context";
11
11
  import { setAPropGen, buildOptions, ConfigField } from "./utils";
12
+ import { ArrayManager } from "./ArrayManager";
12
13
 
13
14
  import { Element, useNode } from "@craftjs/core";
14
15
 
@@ -186,6 +187,7 @@ const TabsSettings = () => {
186
187
  showif: node.data.props.showif,
187
188
  field: node.data.props.field,
188
189
  acc_init_opens: node.data.props.acc_init_opens,
190
+ contents: node.data.props.contents,
189
191
  }));
190
192
  const {
191
193
  actions: { setProp },
@@ -374,39 +376,17 @@ const TabsSettings = () => {
374
376
  </td>
375
377
  </tr>
376
378
  ) : null}
377
- <tr>
378
- <th>
379
- <label>Number of sections</label>
380
- </th>
381
- <td>
382
- <input
383
- type="number"
384
- className="form-control"
385
- value={ntabs}
386
- step="1"
387
- min="1"
388
- max="20"
389
- onChange={setAProp("ntabs")}
390
- />
391
- </td>
392
- </tr>
393
379
  <tr>
394
380
  <td colSpan={2}>
395
- <ConfigField
396
- field={{
397
- name: "setting_tab_n",
398
- label: "Tab number",
399
- type: "btn_select",
400
- options: ntimes(ntabs, (i) => ({
401
- value: i,
402
- title: `${i + 1}`,
403
- label: `${i + 1}`,
404
- })),
405
- }}
381
+ <ArrayManager
406
382
  node={node}
407
383
  setProp={setProp}
408
- props={node}
409
- ></ConfigField>
384
+ countProp={"ntabs"}
385
+ currentProp={"setting_tab_n"}
386
+ managedArrays={["titles", "acc_init_opens"]}
387
+ manageContents={true}
388
+ initialAddProps={{ titles: "New Tab" }}
389
+ ></ArrayManager>
410
390
  </td>
411
391
  </tr>
412
392
  <tr>
@@ -15,11 +15,15 @@ import {
15
15
  faBold,
16
16
  faItalic,
17
17
  faFont,
18
+ faPlus,
18
19
  faCommentSlash,
19
20
  faUnderline,
21
+ faAngleDoubleLeft,
22
+ faAngleDoubleRight,
20
23
  faTerminal,
24
+ faTrashAlt,
21
25
  } from "@fortawesome/free-solid-svg-icons";
22
- import { useNode, Element } from "@craftjs/core";
26
+ import { useNode, Element, useEditor } from "@craftjs/core";
23
27
  import FontIconPicker from "@fonticonpicker/react-fonticonpicker";
24
28
  import Tippy from "@tippyjs/react";
25
29
  import { RelationType } from "@saltcorn/common-code";
@@ -30,6 +34,14 @@ export const DynamicFontAwesomeIcon = ({ icon, className }) => {
30
34
  return <i className={`${icon} ${className || ""}`}></i>;
31
35
  };
32
36
 
37
+ const ntimes = (n, f) => {
38
+ var res = [];
39
+ for (let index = 0; index < n; index++) {
40
+ res.push(f(index));
41
+ }
42
+ return res;
43
+ };
44
+
33
45
  export /**
34
46
  * @param {boolean} is_block
35
47
  * @returns {object}
@@ -92,7 +92,14 @@ export /**
92
92
  * @subcategory components
93
93
  * @namespace
94
94
  */
95
- const layoutToNodes = (layout, query, actions, parent = "ROOT", options) => {
95
+ const layoutToNodes = (
96
+ layout,
97
+ query,
98
+ actions,
99
+ parent = "ROOT",
100
+ options,
101
+ index = 0
102
+ ) => {
96
103
  //console.log("layoutToNodes", JSON.stringify(layout));
97
104
  /**
98
105
  * @param {object} segment
@@ -211,7 +218,12 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT", options) => {
211
218
  gradEndColor={segment.gradEndColor}
212
219
  gradDirection={segment.gradDirection}
213
220
  rotate={segment.rotate || 0}
221
+ animateName={segment.animateName}
222
+ animateDuration={segment.animateDuration}
223
+ animateDelay={segment.animateDelay}
224
+ animateInitialHide={segment.animateInitialHide}
214
225
  customClass={segment.customClass}
226
+ customId={segment.customId}
215
227
  customCSS={segment.customCSS}
216
228
  overflow={segment.overflow}
217
229
  margin={segment.margin || [0, 0, 0, 0]}
@@ -277,6 +289,7 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT", options) => {
277
289
  titles={segment.titles}
278
290
  showif={segment.showif}
279
291
  ntabs={segment.ntabs}
292
+ setting_tab_n={segment.setting_tab_n}
280
293
  independent={segment.independent}
281
294
  startClosed={segment.startClosed}
282
295
  deeplink={segment.deeplink}
@@ -358,7 +371,7 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT", options) => {
358
371
  * @param {object} parent
359
372
  * @returns {void}
360
373
  */
361
- function go(segment, parent) {
374
+ function go(segment, parent, ix) {
362
375
  if (!segment) return;
363
376
  if (segment.above) {
364
377
  segment.above.forEach((child) => {
@@ -383,25 +396,25 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT", options) => {
383
396
  />
384
397
  )
385
398
  .toNodeTree();
386
- actions.addNodeTree(node, parent);
399
+ actions.addNodeTree(node, parent, ix);
387
400
  } else {
388
401
  const tag = toTag(segment);
389
402
  if (Array.isArray(tag)) {
390
403
  tag.forEach((t) => {
391
404
  const node = query.parseReactElement(t).toNodeTree();
392
405
  //console.log("other", node);
393
- actions.addNodeTree(node, parent);
406
+ actions.addNodeTree(node, parent, ix);
394
407
  });
395
408
  } else if (tag) {
396
409
  const node = query.parseReactElement(tag).toNodeTree();
397
410
  //console.log("other", node);
398
- actions.addNodeTree(node, parent);
411
+ actions.addNodeTree(node, parent, ix);
399
412
  }
400
413
  }
401
414
  }
402
415
  //const node1 = query.createNode(toTag(layout));
403
416
  //actions.add(node1, );
404
- go(layout, parent);
417
+ go(layout, parent, index);
405
418
  };
406
419
 
407
420
  /**
@@ -507,6 +520,11 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
507
520
  type: "container",
508
521
  customCSS: node.props.customCSS,
509
522
  customClass: node.props.customClass,
523
+ customId: node.props.customId,
524
+ animateName: node.props.animateName,
525
+ animateDelay: node.props.animateDelay,
526
+ animateDuration: node.props.animateDuration,
527
+ animateInitialHide: node.props.animateInitialHide,
510
528
  minHeight: node.props.minHeight,
511
529
  height: node.props.height,
512
530
  width: node.props.width,
@@ -628,6 +646,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT", options) => {
628
646
  serverRendered: node.props.serverRendered,
629
647
  tabId: node.props.tabId,
630
648
  ntabs: node.props.ntabs,
649
+ setting_tab_n: node.props.setting_tab_n,
631
650
  ...customProps,
632
651
  };
633
652
  }
@@ -0,0 +1,3 @@
1
+ import { createContext } from "react";
2
+
3
+ export default createContext({});
package/webpack.config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const path = require("path");
2
- const webpack = require('webpack')
2
+ const webpack = require("webpack");
3
3
  module.exports = {
4
4
  module: {
5
5
  rules: [
@@ -18,10 +18,13 @@ module.exports = {
18
18
  //libraryTarget: 'window',
19
19
  //libraryExport: 'default'
20
20
  },
21
+ resolve: {
22
+ fallback: { "process/browser": require.resolve("process/browser") },
23
+ },
21
24
  plugins: [
22
25
  // fix "process is not defined" error:
23
26
  new webpack.ProvidePlugin({
24
- process: 'process/browser',
27
+ process: "process/browser",
25
28
  }),
26
- ]
29
+ ],
27
30
  };