@saltcorn/builder 0.7.0-beta.0 → 0.7.0-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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/builder",
3
- "version": "0.7.0-beta.0",
3
+ "version": "0.7.0-beta.3",
4
4
  "description": "Drag and drop view builder for Saltcorn, open-source no-code platform",
5
5
  "main": "index.js",
6
6
  "homepage": "https://saltcorn.com",
@@ -26,6 +26,7 @@ import { Image } from "./elements/Image";
26
26
  import { Tabs } from "./elements/Tabs";
27
27
  import { Empty } from "./elements/Empty";
28
28
  import { DropDownFilter } from "./elements/DropDownFilter";
29
+ import { DropMenu } from "./elements/DropMenu";
29
30
  import { ToggleFilter } from "./elements/ToggleFilter";
30
31
  import optionsCtx from "./context";
31
32
  import PreviewCtx from "./preview_context";
@@ -446,6 +447,7 @@ const Builder = ({ options, layout, mode }) => {
446
447
  Container,
447
448
  Column,
448
449
  DropDownFilter,
450
+ DropMenu,
449
451
  Tabs,
450
452
  ToggleFilter,
451
453
  }}
@@ -16,6 +16,7 @@ import { ViewLink } from "./elements/ViewLink";
16
16
  import { Columns } from "./elements/Columns";
17
17
  import { Action } from "./elements/Action";
18
18
  import { DropDownFilter } from "./elements/DropDownFilter";
19
+ import { DropMenu } from "./elements/DropMenu";
19
20
  import { ToggleFilter } from "./elements/ToggleFilter";
20
21
  import { Empty } from "./elements/Empty";
21
22
  import { Card } from "./elements/Card";
@@ -336,7 +337,7 @@ const DropDownFilterElem = ({ connectors, fields }) => (
336
337
  connectors={connectors}
337
338
  icon="far fa-caret-square-down"
338
339
  title="Dropdown filter"
339
- label="Dropdown"
340
+ label="Select"
340
341
  >
341
342
  <DropDownFilter
342
343
  name={fields[0].name}
@@ -346,6 +347,18 @@ const DropDownFilterElem = ({ connectors, fields }) => (
346
347
  />
347
348
  </WrapElem>
348
349
  );
350
+
351
+ const DropMenuElem = ({ connectors }) => (
352
+ <WrapElem
353
+ connectors={connectors}
354
+ icon="far fa-caret-square-down"
355
+ title="Dropdown menu"
356
+ label="DropMenu"
357
+ >
358
+ <Element canvas is={DropMenu}></Element>
359
+ </WrapElem>
360
+ );
361
+
349
362
  /**
350
363
  * @param {object} props
351
364
  * @param {object} props.connectors
@@ -456,7 +469,7 @@ const AggregationElem = ({ connectors, child_field_list, agg_field_opts }) => (
456
469
  connectors={connectors}
457
470
  text="∑"
458
471
  title="Aggregation"
459
- label="Calc"
472
+ label="Aggreg8"
460
473
  bold
461
474
  fontSize="16px"
462
475
  disable={child_field_list.length === 0}
@@ -529,6 +542,7 @@ const ToolboxShow = () => {
529
542
  </div>
530
543
  <div className="toolbar-row">
531
544
  <HTMLElem connectors={connectors} />
545
+ <DropMenuElem connectors={connectors} />
532
546
  </div>
533
547
  </Fragment>
534
548
  );
@@ -624,6 +638,7 @@ const ToolboxEdit = () => {
624
638
  </div>
625
639
  <div className="toolbar-row">
626
640
  <JoinFieldElem connectors={connectors} options={options} />
641
+ <DropMenuElem connectors={connectors} />
627
642
  </div>
628
643
  </Fragment>
629
644
  );
@@ -665,6 +680,9 @@ const ToolboxPage = () => {
665
680
  <ContainerElem connectors={connectors} />
666
681
  <TabsElem connectors={connectors} />
667
682
  </div>
683
+ <div className="toolbar-row">
684
+ <DropMenuElem connectors={connectors} />
685
+ </div>
668
686
  </Fragment>
669
687
  );
670
688
  };
@@ -10,7 +10,7 @@ import optionsCtx from "../context";
10
10
  import { blockProps, BlockSetting, TextStyleRow } from "./utils";
11
11
 
12
12
  export /**
13
- * @param {object} props
13
+ * @param {object} props
14
14
  * @param {string} props.agg_relation
15
15
  * @param {string} props.agg_field
16
16
  * @param {string} props.stat
@@ -21,13 +21,7 @@ export /**
21
21
  * @subcategory components
22
22
  * @namespace
23
23
  */
24
- const Aggregation = ({
25
- agg_relation,
26
- agg_field,
27
- stat,
28
- block,
29
- textStyle,
30
- }) => {
24
+ const Aggregation = ({ agg_relation, agg_field, stat, block, textStyle }) => {
31
25
  const {
32
26
  selected,
33
27
  connectors: { connect, drag },
@@ -129,6 +123,7 @@ const AggregationSettings = () => {
129
123
  <option value={"Sum"}>Sum</option>
130
124
  <option value={"Max"}>Max</option>
131
125
  <option value={"Min"}>Min</option>
126
+ <option value={"Array_Agg"}>Array_Agg</option>
132
127
  {options.fields
133
128
  .filter((f) => f.type.name === "Date")
134
129
  .map((f) => (
@@ -163,8 +158,8 @@ const AggregationSettings = () => {
163
158
  );
164
159
  };
165
160
 
166
- /**
167
- * @type {object}
161
+ /**
162
+ * @type {object}
168
163
  */
169
164
  Aggregation.craft = {
170
165
  displayName: "Aggregation",
@@ -0,0 +1,156 @@
1
+ /**
2
+ * @category saltcorn-builder
3
+ * @module components/elements/DropMenu
4
+ * @subcategory components / elements
5
+ */
6
+
7
+ import React, { Fragment, useState } from "react";
8
+ import { Element, useNode } from "@craftjs/core";
9
+ import { Column } from "./Column";
10
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
11
+ import { faCaretDown } from "@fortawesome/free-solid-svg-icons";
12
+ import {
13
+ SettingsRow,
14
+ BlockSetting,
15
+ ButtonOrLinkSettingsRows,
16
+ DynamicFontAwesomeIcon,
17
+ } from "./utils";
18
+
19
+ export /**
20
+ * @param {object} props
21
+ * @param {boolean} props.has_dropdown
22
+ * @param {string} props.children
23
+ * @param {boolean} props.show_badges
24
+ * @returns {div}
25
+ * @namespace
26
+ * @category saltcorn-builder
27
+ * @subcategory components
28
+ */
29
+ const DropMenu = ({
30
+ children,
31
+ action_style,
32
+ action_size,
33
+ action_icon,
34
+ action_bgcol,
35
+ action_bordercol,
36
+ action_textcol,
37
+ block,
38
+ label,
39
+ }) => {
40
+ const {
41
+ selected,
42
+ connectors: { connect, drag },
43
+ } = useNode((node) => ({ selected: node.events.selected }));
44
+ const [showDropdown, setDropdown] = useState(false);
45
+ //const [dropWidth, setDropWidth] = useState(200);
46
+ return (
47
+ <div
48
+ className={`${selected ? "selected-node" : ""} ${block ? "d-block" : ""}`}
49
+ ref={(dom) => connect(drag(dom))}
50
+ >
51
+ <button
52
+ className={`btn ${action_style || "btn-primary"} ${action_size || ""} `}
53
+ style={
54
+ action_style === "btn-custom-color"
55
+ ? {
56
+ backgroundColor: action_bgcol || "#000000",
57
+ borderColor: action_bordercol || "#000000",
58
+ color: action_textcol || "#000000",
59
+ }
60
+ : {}
61
+ }
62
+ >
63
+ <DynamicFontAwesomeIcon icon={action_icon} className="me-1" />
64
+
65
+ {label}
66
+ <FontAwesomeIcon
67
+ icon={faCaretDown}
68
+ className="ms-1"
69
+ onClick={() => setDropdown(!showDropdown)}
70
+ />
71
+ </button>
72
+ <div
73
+ className={`dropdown-menu dropmenu-dropdown ${
74
+ showDropdown ? "show" : ""
75
+ }`}
76
+ >
77
+ <div className="canvas d-flex flex-column">{children}</div>
78
+ </div>
79
+ </div>
80
+ );
81
+ };
82
+
83
+ export /**
84
+ * @returns {div}
85
+ * @namespace
86
+ * @category saltcorn-builder
87
+ * @subcategory components
88
+ */
89
+ const DropMenuSettings = () => {
90
+ const node = useNode((node) => ({
91
+ label: node.data.props.label,
92
+ block: node.data.props.block,
93
+ action_style: node.data.props.action_style,
94
+ action_size: node.data.props.action_size,
95
+ action_icon: node.data.props.action_icon,
96
+ action_bgcol: node.data.props.action_bgcol,
97
+ action_bordercol: node.data.props.action_bordercol,
98
+ action_textcol: node.data.props.action_textcol,
99
+ }));
100
+ const {
101
+ actions: { setProp },
102
+ label,
103
+ block,
104
+ } = node;
105
+ return (
106
+ <table className="w-100">
107
+ <tbody>
108
+ <SettingsRow
109
+ field={{
110
+ label: "Label",
111
+ name: "label",
112
+ type: "String",
113
+ }}
114
+ node={node}
115
+ setProp={setProp}
116
+ />
117
+ <ButtonOrLinkSettingsRows
118
+ setProp={setProp}
119
+ keyPrefix="action_"
120
+ values={node}
121
+ />
122
+ <tr>
123
+ <td colSpan="2">
124
+ <BlockSetting block={block} setProp={setProp} />
125
+ </td>
126
+ </tr>
127
+ </tbody>
128
+ </table>
129
+ );
130
+ };
131
+
132
+ /**
133
+ * @type {object}
134
+ */
135
+ DropMenu.craft = {
136
+ displayName: "DropMenu",
137
+ props: {
138
+ label: "Menu",
139
+ block: false,
140
+ },
141
+ related: {
142
+ settings: DropMenuSettings,
143
+ segment_type: "dropdown_menu",
144
+ hasContents: true,
145
+ fields: [
146
+ "label",
147
+ "block",
148
+ "action_style",
149
+ "action_size",
150
+ "action_icon",
151
+ "action_bgcol",
152
+ "action_bordercol",
153
+ "action_textcol",
154
+ ],
155
+ },
156
+ };
@@ -4,9 +4,10 @@
4
4
  * @subcategory components / elements
5
5
  */
6
6
 
7
- import React, { Fragment, useState } from "react";
7
+ import React, { Fragment, useState, useContext, useEffect } from "react";
8
8
  import { ntimes } from "./Columns";
9
9
  import { Column } from "./Column";
10
+ import optionsCtx from "../context";
10
11
 
11
12
  import { Element, useNode } from "@craftjs/core";
12
13
 
@@ -21,14 +22,13 @@ export /**
21
22
  * @category saltcorn-builder
22
23
  * @subcategory components
23
24
  */
24
- const Tabs = ({ contents, titles, tabsStyle, ntabs, independent }) => {
25
+ const Tabs = ({ contents, titles, tabsStyle, ntabs, independent, field }) => {
25
26
  const {
26
27
  selected,
27
28
  connectors: { connect, drag },
28
29
  } = useNode((node) => ({ selected: node.events.selected }));
29
30
  const [showTab, setShowTab] = useState(0);
30
31
  const [showTabs, setShowTabs] = useState([true]);
31
-
32
32
  if (tabsStyle === "Accordion")
33
33
  return (
34
34
  <div className="accordion">
@@ -94,7 +94,12 @@ const Tabs = ({ contents, titles, tabsStyle, ntabs, independent }) => {
94
94
  className={`nav-link ${ix === showTab ? `active` : ""}`}
95
95
  onClick={() => setShowTab(ix)}
96
96
  >
97
- {titles[ix]}
97
+ {titles[ix] &&
98
+ (typeof titles[ix].label === "undefined"
99
+ ? titles[ix]
100
+ : titles[ix].label === ""
101
+ ? "(empty)"
102
+ : titles[ix].label)}
98
103
  </a>
99
104
  </li>
100
105
  ))}
@@ -130,6 +135,7 @@ const TabsSettings = () => {
130
135
  independent: node.data.props.independent,
131
136
  deeplink: node.data.props.deeplink,
132
137
  titles: node.data.props.titles,
138
+ field: node.data.props.field,
133
139
  }));
134
140
  const {
135
141
  actions: { setProp },
@@ -138,7 +144,30 @@ const TabsSettings = () => {
138
144
  deeplink,
139
145
  independent,
140
146
  ntabs,
147
+ field,
141
148
  } = node;
149
+ const options = useContext(optionsCtx);
150
+ useEffect(() => {
151
+ if (field)
152
+ fetch(`/api/${options.tableName}/distinct/${field}`, {
153
+ method: "GET",
154
+ headers: {
155
+ "Content-Type": "application/json",
156
+ "CSRF-Token": options.csrfToken,
157
+ },
158
+ })
159
+ .then(function (response) {
160
+ if (response.status < 399) return response.json();
161
+ else return "";
162
+ })
163
+ .then(function (data) {
164
+ if (data.success) {
165
+ const len = data.success.length;
166
+ setProp((prop) => (prop.ntabs = len));
167
+ setProp((prop) => (prop.titles = data.success));
168
+ }
169
+ });
170
+ }, [field]);
142
171
  return (
143
172
  <table className="w-100" accordiontitle="Placement">
144
173
  <tbody>
@@ -159,81 +188,115 @@ const TabsSettings = () => {
159
188
  <option>Tabs</option>
160
189
  <option>Pills</option>
161
190
  <option>Accordion</option>
191
+ {["show", "edit"].includes(options.mode) && (
192
+ <option>Value switch</option>
193
+ )}
162
194
  </select>
163
195
  </td>
164
196
  </tr>
165
- <tr>
166
- <th>
167
- <label>Number of sections</label>
168
- </th>
169
- <td>
170
- <input
171
- type="number"
172
- className="form-control"
173
- value={ntabs}
174
- step="1"
175
- min="0"
176
- max="20"
177
- onChange={(e) => setProp((prop) => (prop.ntabs = e.target.value))}
178
- />
179
- </td>
180
- </tr>
181
- <tr>
182
- <th colSpan="2">Titles</th>
183
- </tr>
184
- {ntimes(ntabs, (ix) => (
185
- <tr key={ix}>
186
- <th>{ix + 1}</th>
197
+ {tabsStyle === "Value switch" ? (
198
+ <tr>
187
199
  <td>
188
- <input
189
- type="text"
190
- className="form-control text-to-display"
191
- value={titles[ix]}
192
- onChange={(e) =>
193
- setProp((prop) => (prop.titles[ix] = e.target.value))
194
- }
195
- />
200
+ <label>Field</label>
196
201
  </td>
197
- </tr>
198
- ))}
199
- {tabsStyle === "Accordion" ? (
200
- <tr>
201
- <td colSpan="2">
202
- <div className="form-check">
203
- <input
204
- className="form-check-input"
205
- name="block"
206
- type="checkbox"
207
- checked={independent}
208
- onChange={(e) => {
209
- if (e.target) {
210
- setProp((prop) => (prop.independent = e.target.checked));
211
- }
212
- }}
213
- />
214
- <label className="form-check-label">Open independently</label>
215
- </div>
202
+ <td>
203
+ <select
204
+ value={field}
205
+ className="form-control form-select"
206
+ onChange={(e) => {
207
+ setProp((prop) => (prop.field = e.target.value));
208
+ }}
209
+ >
210
+ {options.fields.map((f, ix) => (
211
+ <option key={ix} value={f.name}>
212
+ {f.label}
213
+ </option>
214
+ ))}
215
+ </select>
216
216
  </td>
217
217
  </tr>
218
218
  ) : (
219
- <tr>
220
- <td colSpan="2">
221
- <div className="form-check">
219
+ <Fragment>
220
+ <tr>
221
+ <th>
222
+ <label>Number of sections</label>
223
+ </th>
224
+ <td>
222
225
  <input
223
- className="form-check-input"
224
- name="block"
225
- type="checkbox"
226
- checked={deeplink}
227
- onChange={(e) => {
228
- if (e.target) {
229
- setProp((prop) => (prop.deeplink = e.target.checked));
230
- }
231
- }}
226
+ type="number"
227
+ className="form-control"
228
+ value={ntabs}
229
+ step="1"
230
+ min="0"
231
+ max="20"
232
+ onChange={(e) =>
233
+ setProp((prop) => (prop.ntabs = e.target.value))
234
+ }
232
235
  />
233
- <label className="form-check-label">Deep link</label>
234
- </div>
235
- </td>
236
- </tr>
236
+ </td>
237
+ </tr>
238
+ <tr>
239
+ <th colSpan="2">Titles</th>
240
+ </tr>
241
+ {ntimes(ntabs, (ix) => (
242
+ <tr key={ix}>
243
+ <th>{ix + 1}</th>
244
+ <td>
245
+ <input
246
+ type="text"
247
+ className="form-control text-to-display"
248
+ value={titles[ix]}
249
+ onChange={(e) =>
250
+ setProp((prop) => (prop.titles[ix] = e.target.value))
251
+ }
252
+ />
253
+ </td>
254
+ </tr>
255
+ ))}
256
+ {tabsStyle === "Accordion" ? (
257
+ <tr>
258
+ <td colSpan="2">
259
+ <div className="form-check">
260
+ <input
261
+ className="form-check-input"
262
+ name="block"
263
+ type="checkbox"
264
+ checked={independent}
265
+ onChange={(e) => {
266
+ if (e.target) {
267
+ setProp(
268
+ (prop) => (prop.independent = e.target.checked)
269
+ );
270
+ }
271
+ }}
272
+ />
273
+ <label className="form-check-label">
274
+ Open independently
275
+ </label>
276
+ </div>
277
+ </td>
278
+ </tr>
279
+ ) : (
280
+ <tr>
281
+ <td colSpan="2">
282
+ <div className="form-check">
283
+ <input
284
+ className="form-check-input"
285
+ name="block"
286
+ type="checkbox"
287
+ checked={deeplink}
288
+ onChange={(e) => {
289
+ if (e.target) {
290
+ setProp((prop) => (prop.deeplink = e.target.checked));
291
+ }
292
+ }}
293
+ />
294
+ <label className="form-check-label">Deep link</label>
295
+ </div>
296
+ </td>
297
+ </tr>
298
+ )}
299
+ </Fragment>
237
300
  )}
238
301
  </tbody>
239
302
  </table>
@@ -252,7 +252,7 @@ const TextStyleSelect = ({ textStyle, setProp }) => {
252
252
  <option value="h5">Heading 5</option>
253
253
  <option value="h6">Heading 6</option>
254
254
  <option value="fw-bold">Bold</option>
255
- <option value="font-italic">Italics</option>
255
+ <option value="fst-italic">Italics</option>
256
256
  <option value="small">Small</option>
257
257
  <option value="text-muted">Muted</option>
258
258
  <option value="text-underline">Underline</option>
@@ -25,6 +25,7 @@ import { SearchBar } from "./elements/SearchBar";
25
25
  import { Container } from "./elements/Container";
26
26
  import { DropDownFilter } from "./elements/DropDownFilter";
27
27
  import { ToggleFilter } from "./elements/ToggleFilter";
28
+ import { DropMenu } from "./elements/DropMenu";
28
29
 
29
30
  /**
30
31
  * @param {object} segment
@@ -69,6 +70,7 @@ const allElements = [
69
70
  DropDownFilter,
70
71
  Tabs,
71
72
  ToggleFilter,
73
+ DropMenu,
72
74
  ];
73
75
 
74
76
  export /**
@@ -227,6 +229,7 @@ const layoutToNodes = (layout, query, actions, parent = "ROOT") => {
227
229
  ntabs={segment.ntabs}
228
230
  independent={segment.independent}
229
231
  deeplink={segment.deeplink}
232
+ field={segment.field}
230
233
  tabsStyle={segment.tabsStyle}
231
234
  contents={segment.contents.map(toTag)}
232
235
  />
@@ -419,6 +422,7 @@ const craftToSaltcorn = (nodes, startFrom = "ROOT") => {
419
422
  ),
420
423
  titles: node.props.titles,
421
424
  tabsStyle: node.props.tabsStyle,
425
+ field: node.props.field,
422
426
  independent: node.props.independent,
423
427
  deeplink: node.props.deeplink,
424
428
  ntabs: node.props.ntabs,