@saltcorn/builder 0.0.1-beta.1

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 (38) hide show
  1. package/.babelrc +3 -0
  2. package/CHANGELOG.md +8 -0
  3. package/dist/builder_bundle.js +80 -0
  4. package/package.json +47 -0
  5. package/src/components/Builder.js +477 -0
  6. package/src/components/Library.js +224 -0
  7. package/src/components/RenderNode.js +203 -0
  8. package/src/components/Toolbox.js +688 -0
  9. package/src/components/context.js +9 -0
  10. package/src/components/elements/Action.js +204 -0
  11. package/src/components/elements/Aggregation.js +179 -0
  12. package/src/components/elements/BoxModelEditor.js +398 -0
  13. package/src/components/elements/Card.js +152 -0
  14. package/src/components/elements/Column.js +63 -0
  15. package/src/components/elements/Columns.js +201 -0
  16. package/src/components/elements/Container.js +947 -0
  17. package/src/components/elements/DropDownFilter.js +154 -0
  18. package/src/components/elements/DropMenu.js +156 -0
  19. package/src/components/elements/Empty.js +30 -0
  20. package/src/components/elements/Field.js +239 -0
  21. package/src/components/elements/HTMLCode.js +61 -0
  22. package/src/components/elements/Image.js +320 -0
  23. package/src/components/elements/JoinField.js +206 -0
  24. package/src/components/elements/LineBreak.js +46 -0
  25. package/src/components/elements/Link.js +305 -0
  26. package/src/components/elements/SearchBar.js +141 -0
  27. package/src/components/elements/Tabs.js +347 -0
  28. package/src/components/elements/Text.js +330 -0
  29. package/src/components/elements/ToggleFilter.js +243 -0
  30. package/src/components/elements/View.js +189 -0
  31. package/src/components/elements/ViewLink.js +225 -0
  32. package/src/components/elements/boxmodel.html +253 -0
  33. package/src/components/elements/faicons.js +1643 -0
  34. package/src/components/elements/utils.js +1217 -0
  35. package/src/components/preview_context.js +9 -0
  36. package/src/components/storage.js +506 -0
  37. package/src/index.js +73 -0
  38. package/webpack.config.js +21 -0
@@ -0,0 +1,347 @@
1
+ /**
2
+ * @category saltcorn-builder
3
+ * @module components/elements/Tabs
4
+ * @subcategory components / elements
5
+ */
6
+
7
+ import React, { Fragment, useState, useContext, useEffect } from "react";
8
+ import { ntimes } from "./Columns";
9
+ import { Column } from "./Column";
10
+ import optionsCtx from "../context";
11
+
12
+ import { Element, useNode } from "@craftjs/core";
13
+
14
+ export /**
15
+ * @param {object} props
16
+ * @param {string[]} props.contents
17
+ * @param {string[]} props.titles
18
+ * @param {string} props.tabsStyle
19
+ * @param {number} props.ntabs
20
+ * @returns {div}
21
+ * @namespace
22
+ * @category saltcorn-builder
23
+ * @subcategory components
24
+ */
25
+ const Tabs = ({ contents, titles, tabsStyle, ntabs, independent, field }) => {
26
+ const {
27
+ selected,
28
+ connectors: { connect, drag },
29
+ } = useNode((node) => ({ selected: node.events.selected }));
30
+ const [showTab, setShowTab] = useState(0);
31
+ const [showTabs, setShowTabs] = useState([true]);
32
+ if (tabsStyle === "Accordion")
33
+ return (
34
+ <div className="accordion">
35
+ {ntimes(ntabs, (ix) => (
36
+ <div key={ix} className="card">
37
+ <div className="card-header">
38
+ <h2 className="mb-0">
39
+ <button
40
+ className="btn btn-link btn-block text-left"
41
+ type="button"
42
+ onClick={() => {
43
+ setShowTab(ix);
44
+ if (!independent) {
45
+ let newArr = [];
46
+ newArr[ix] = true;
47
+ setShowTabs(newArr);
48
+ } else {
49
+ let newArr = [...showTabs];
50
+ newArr[ix] = !newArr[ix];
51
+ setShowTabs(newArr);
52
+ }
53
+ }}
54
+ >
55
+ {titles[ix]}
56
+ </button>
57
+ </h2>
58
+ </div>
59
+
60
+ <div
61
+ id={`collapse${ix}`}
62
+ className={`collapse ${
63
+ (independent && showTabs[ix]) || (!independent && showTab == ix)
64
+ ? "show"
65
+ : ""
66
+ }`}
67
+ aria-labelledby="headingOne"
68
+ data-parent="#accordionExample"
69
+ >
70
+ <div className="card-body">
71
+ <Element canvas id={`Tab${ix}`} is={Column}>
72
+ {contents[ix]}
73
+ </Element>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ ))}
78
+ </div>
79
+ );
80
+ else
81
+ return (
82
+ <Fragment>
83
+ <ul
84
+ id="myTab"
85
+ role="tablist"
86
+ className={`nav ${
87
+ tabsStyle === "Tabs" ? "nav-tabs" : "nav-pills"
88
+ } builder ${selected ? "selected-node" : ""}`}
89
+ ref={(dom) => connect(drag(dom))}
90
+ >
91
+ {ntimes(ntabs, (ix) => {
92
+ if (!titles[ix]) return null;
93
+ const targetIx =
94
+ typeof titles[ix].value === "undefined" ? ix : titles[ix].value;
95
+ return (
96
+ <li key={ix} className="nav-item" role="presentation">
97
+ <a
98
+ className={`nav-link ${targetIx === showTab ? `active` : ""}`}
99
+ onClick={() => setShowTab(targetIx)}
100
+ >
101
+ {titles[ix] &&
102
+ (typeof titles[ix].label === "undefined"
103
+ ? titles[ix]
104
+ : titles[ix].label === ""
105
+ ? "(empty)"
106
+ : titles[ix].label)}
107
+ </a>
108
+ </li>
109
+ );
110
+ })}
111
+ </ul>
112
+ <div className="tab-content" id="myTabContent">
113
+ {ntimes(ntabs, (ix) => {
114
+ if (!titles[ix]) return null;
115
+
116
+ const useIx =
117
+ typeof titles[ix].value === "undefined" ? ix : titles[ix].value;
118
+
119
+ if (useIx !== showTab)
120
+ return (
121
+ <div className="d-none" key={ix}>
122
+ <Element canvas id={`Tab${useIx}`} is={Column}>
123
+ {contents[useIx]}
124
+ </Element>
125
+ </div>
126
+ );
127
+ //d-none display of useIx is bug workaround? needed
128
+ else
129
+ return (
130
+ <div
131
+ key={ix}
132
+ className={`tab-pane fade ${
133
+ useIx === showTab ? `show active` : ""
134
+ }`}
135
+ role="tabpanel"
136
+ aria-labelledby="home-tab"
137
+ >
138
+ <div className="d-none">{useIx}</div>
139
+ <Element canvas id={`Tab${useIx}`} is={Column}>
140
+ {contents[useIx]}
141
+ </Element>
142
+ </div>
143
+ );
144
+ })}
145
+ </div>
146
+ </Fragment>
147
+ );
148
+ };
149
+
150
+ export /**
151
+ * @returns {table}
152
+ * @namespace
153
+ * @category saltcorn-builder
154
+ * @subcategory components
155
+ */
156
+ const TabsSettings = () => {
157
+ const node = useNode((node) => ({
158
+ tabsStyle: node.data.props.tabsStyle,
159
+ ntabs: node.data.props.ntabs,
160
+ independent: node.data.props.independent,
161
+ deeplink: node.data.props.deeplink,
162
+ titles: node.data.props.titles,
163
+ field: node.data.props.field,
164
+ }));
165
+ const {
166
+ actions: { setProp },
167
+ titles,
168
+ tabsStyle,
169
+ deeplink,
170
+ independent,
171
+ ntabs,
172
+ field,
173
+ } = node;
174
+ const options = useContext(optionsCtx);
175
+ useEffect(() => {
176
+ if (field)
177
+ fetch(`/api/${options.tableName}/distinct/${field}`, {
178
+ method: "GET",
179
+ headers: {
180
+ "Content-Type": "application/json",
181
+ "CSRF-Token": options.csrfToken,
182
+ },
183
+ })
184
+ .then(function (response) {
185
+ if (response.status < 399) return response.json();
186
+ else return "";
187
+ })
188
+ .then(function (data) {
189
+ if (data.success) {
190
+ const len = data.success.length;
191
+
192
+ setProp((prop) => (prop.ntabs = len));
193
+ setProp((prop) => (prop.titles = data.success));
194
+ }
195
+ });
196
+ }, [field]);
197
+ return (
198
+ <table className="w-100" accordiontitle="Placement">
199
+ <tbody>
200
+ <tr>
201
+ <th>
202
+ <label>Style</label>
203
+ </th>
204
+ <td>
205
+ <select
206
+ value={tabsStyle}
207
+ className="form-control form-select"
208
+ onChange={(e) =>
209
+ setProp((prop) => {
210
+ prop.tabsStyle = e.target.value;
211
+ })
212
+ }
213
+ >
214
+ <option>Tabs</option>
215
+ <option>Pills</option>
216
+ <option>Accordion</option>
217
+ {["show", "edit"].includes(options.mode) && (
218
+ <option>Value switch</option>
219
+ )}
220
+ </select>
221
+ </td>
222
+ </tr>
223
+ {tabsStyle === "Value switch" ? (
224
+ <tr>
225
+ <td>
226
+ <label>Field</label>
227
+ </td>
228
+ <td>
229
+ <select
230
+ value={field}
231
+ className="form-control form-select"
232
+ onChange={(e) => {
233
+ setProp((prop) => (prop.field = e.target.value));
234
+ }}
235
+ >
236
+ {options.fields.map((f, ix) => (
237
+ <option key={ix} value={f.name}>
238
+ {f.label}
239
+ </option>
240
+ ))}
241
+ </select>
242
+ </td>
243
+ </tr>
244
+ ) : (
245
+ <Fragment>
246
+ <tr>
247
+ <th>
248
+ <label>Number of sections</label>
249
+ </th>
250
+ <td>
251
+ <input
252
+ type="number"
253
+ className="form-control"
254
+ value={ntabs}
255
+ step="1"
256
+ min="0"
257
+ max="20"
258
+ onChange={(e) =>
259
+ setProp((prop) => (prop.ntabs = e.target.value))
260
+ }
261
+ />
262
+ </td>
263
+ </tr>
264
+ <tr>
265
+ <th colSpan="2">Titles</th>
266
+ </tr>
267
+ {ntimes(ntabs, (ix) => (
268
+ <tr key={ix}>
269
+ <th>{ix + 1}</th>
270
+ <td>
271
+ <input
272
+ type="text"
273
+ className="form-control text-to-display"
274
+ value={titles[ix]}
275
+ onChange={(e) =>
276
+ setProp((prop) => (prop.titles[ix] = e.target.value))
277
+ }
278
+ />
279
+ </td>
280
+ </tr>
281
+ ))}
282
+ {tabsStyle === "Accordion" ? (
283
+ <tr>
284
+ <td colSpan="2">
285
+ <div className="form-check">
286
+ <input
287
+ className="form-check-input"
288
+ name="block"
289
+ type="checkbox"
290
+ checked={independent}
291
+ onChange={(e) => {
292
+ if (e.target) {
293
+ setProp(
294
+ (prop) => (prop.independent = e.target.checked)
295
+ );
296
+ }
297
+ }}
298
+ />
299
+ <label className="form-check-label">
300
+ Open independently
301
+ </label>
302
+ </div>
303
+ </td>
304
+ </tr>
305
+ ) : (
306
+ <tr>
307
+ <td colSpan="2">
308
+ <div className="form-check">
309
+ <input
310
+ className="form-check-input"
311
+ name="block"
312
+ type="checkbox"
313
+ checked={deeplink}
314
+ onChange={(e) => {
315
+ if (e.target) {
316
+ setProp((prop) => (prop.deeplink = e.target.checked));
317
+ }
318
+ }}
319
+ />
320
+ <label className="form-check-label">Deep link</label>
321
+ </div>
322
+ </td>
323
+ </tr>
324
+ )}
325
+ </Fragment>
326
+ )}
327
+ </tbody>
328
+ </table>
329
+ );
330
+ };
331
+
332
+ /**
333
+ * @type {object}
334
+ */
335
+ Tabs.craft = {
336
+ props: {
337
+ titles: ["Tab1", "Tab2"],
338
+ ntabs: 2,
339
+ tabsStyle: "Tabs",
340
+ independent: false,
341
+ deeplink: true,
342
+ },
343
+ displayName: "Tabs",
344
+ related: {
345
+ settings: TabsSettings,
346
+ },
347
+ };
@@ -0,0 +1,330 @@
1
+ /**
2
+ * @category saltcorn-builder
3
+ * @module components/elements/Text
4
+ * @subcategory components / elements
5
+ */
6
+
7
+ import React, { useState, useContext, useEffect, Fragment } from "react";
8
+ import { useNode } from "@craftjs/core";
9
+ import {
10
+ blockProps,
11
+ BlockOrInlineSetting,
12
+ TextStyleSetting,
13
+ OrFormula,
14
+ ErrorBoundary,
15
+ TextStyleRow,
16
+ DynamicFontAwesomeIcon,
17
+ isBlock,
18
+ reactifyStyles,
19
+ SettingsRow,
20
+ } from "./utils";
21
+ import ContentEditable from "react-contenteditable";
22
+ import optionsCtx from "../context";
23
+ import CKEditor from "ckeditor4-react";
24
+ import FontIconPicker from "@fonticonpicker/react-fonticonpicker";
25
+ import faIcons from "./faicons";
26
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
27
+ import fas from "@fortawesome/free-solid-svg-icons";
28
+ import far from "@fortawesome/free-regular-svg-icons";
29
+ const ckConfig = {
30
+ toolbarGroups: [
31
+ { name: "document", groups: ["mode", "document", "doctools"] },
32
+ { name: "clipboard", groups: ["clipboard", "undo"] },
33
+ { name: "forms", groups: ["forms"] },
34
+ { name: "basicstyles", groups: ["basicstyles", "cleanup"] },
35
+ {
36
+ name: "editing",
37
+ groups: ["find", "selection", "spellchecker", "editing"],
38
+ },
39
+ {
40
+ name: "paragraph",
41
+ groups: ["list", "indent", "blocks", "align", "bidi", "paragraph"],
42
+ },
43
+ { name: "links", groups: ["links"] },
44
+ "/",
45
+ { name: "insert", groups: ["insert"] },
46
+ { name: "styles", groups: ["styles"] },
47
+ { name: "colors", groups: ["colors"] },
48
+ { name: "tools", groups: ["tools"] },
49
+ { name: "others", groups: ["others"] },
50
+ { name: "about", groups: ["about"] },
51
+ ],
52
+ autoParagraph: false,
53
+ fillEmptyBlocks: false,
54
+ removeButtons:
55
+ "Source,Save,NewPage,ExportPdf,Print,Preview,Templates,Cut,Copy,Paste,PasteText,PasteFromWord,Find,Replace,SelectAll,Form,Checkbox,Radio,TextField,Textarea,Select,Button,ImageButton,HiddenField,CopyFormatting,CreateDiv,BidiLtr,BidiRtl,Language,Anchor,Flash,Iframe,PageBreak,Maximize,ShowBlocks,About,Undo,Redo,Image",
56
+ };
57
+
58
+ /**
59
+ * @param {string} str
60
+ * @returns {string}
61
+ */
62
+ function escape_tags(str) {
63
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
64
+ }
65
+
66
+ export /**
67
+ * @param {object} props
68
+ * @param {string} props.text
69
+ * @param {boolean} props.block
70
+ * @param {object} props.isFormula
71
+ * @param {string} props.textStyle
72
+ * @param {string} [props.icon]
73
+ * @param {string} [props.font]
74
+ * @returns {div}
75
+ * @namespace
76
+ * @category saltcorn-builder
77
+ * @subcategory components
78
+ */
79
+ const Text = ({
80
+ text,
81
+ block,
82
+ inline,
83
+ isFormula,
84
+ textStyle,
85
+ icon,
86
+ font,
87
+ style,
88
+ }) => {
89
+ const {
90
+ connectors: { connect, drag },
91
+ selected,
92
+ actions: { setProp },
93
+ } = useNode((state) => ({
94
+ selected: state.events.selected,
95
+ dragged: state.events.dragged,
96
+ }));
97
+ const [editable, setEditable] = useState(false);
98
+
99
+ useEffect(() => {
100
+ !selected && setEditable(false);
101
+ }, [selected]);
102
+ return (
103
+ <div
104
+ className={`${
105
+ isBlock(block, inline, textStyle) ? "d-block" : "d-inline-block"
106
+ } ${textStyle} is-text ${isFormula.text ? "font-monospace" : ""} ${
107
+ selected ? "selected-node" : ""
108
+ }`}
109
+ ref={(dom) => connect(drag(dom))}
110
+ onClick={(e) => selected && setEditable(true)}
111
+ style={{
112
+ ...(font ? { fontFamily: font } : {}),
113
+ ...reactifyStyles(style || {}),
114
+ }}
115
+ >
116
+ <DynamicFontAwesomeIcon icon={icon} className="me-1" />
117
+ {isFormula.text ? (
118
+ <Fragment>
119
+ =
120
+ <ContentEditable
121
+ html={text}
122
+ style={{ display: "inline" }}
123
+ disabled={!editable}
124
+ onChange={(e) => setProp((props) => (props.text = e.target.value))}
125
+ />
126
+ </Fragment>
127
+ ) : editable ? (
128
+ <ErrorBoundary>
129
+ <CKEditor
130
+ data={text}
131
+ style={{ display: "inline" }}
132
+ onChange={(e) =>
133
+ setProp((props) => (props.text = e.editor.getData()))
134
+ }
135
+ config={ckConfig}
136
+ type="inline"
137
+ />
138
+ </ErrorBoundary>
139
+ ) : (
140
+ <div className="d-inline" dangerouslySetInnerHTML={{ __html: text }} />
141
+ )}
142
+ </div>
143
+ );
144
+ };
145
+ //<div dangerouslySetInnerHTML={{ __html: text }} />
146
+
147
+ export /**
148
+ * @returns {div}
149
+ * @namespace
150
+ * @category saltcorn-builder
151
+ * @subcategory components
152
+ */
153
+ const TextSettings = () => {
154
+ const node = useNode((node) => ({
155
+ text: node.data.props.text,
156
+ block: node.data.props.block,
157
+ inline: node.data.props.inline,
158
+ isFormula: node.data.props.isFormula,
159
+ textStyle: node.data.props.textStyle,
160
+ labelFor: node.data.props.labelFor,
161
+ icon: node.data.props.icon,
162
+ font: node.data.props.font,
163
+ style: node.data.props.style,
164
+ }));
165
+ const {
166
+ actions: { setProp },
167
+ text,
168
+ block,
169
+ inline,
170
+ textStyle,
171
+ isFormula,
172
+ labelFor,
173
+ icon,
174
+ font,
175
+ style,
176
+ } = node;
177
+ const { mode, fields } = useContext(optionsCtx);
178
+ const setAProp = (key) => (e) => {
179
+ if (e.target) {
180
+ const target_value = e.target.value;
181
+ setProp((prop) => (prop[key] = target_value));
182
+ }
183
+ };
184
+ return (
185
+ <div>
186
+ {mode === "show" && (
187
+ <div className="form-check">
188
+ <input
189
+ type="checkbox"
190
+ className="form-check-input"
191
+ checked={isFormula.text}
192
+ onChange={(e) =>
193
+ setProp((prop) => (prop.isFormula.text = e.target.checked))
194
+ }
195
+ />
196
+ <label className="form-check-label">Formula?</label>
197
+ </div>
198
+ )}
199
+ <label>Text to display</label>
200
+ {mode === "show" && isFormula.text ? (
201
+ <input
202
+ type="text"
203
+ className="text-to-display form-control"
204
+ value={text}
205
+ onChange={setAProp("text")}
206
+ />
207
+ ) : (
208
+ <ErrorBoundary>
209
+ <div className="border">
210
+ <CKEditor
211
+ data={text}
212
+ onChange={(e) => {
213
+ if (e.editor) {
214
+ const text = e.editor.getData();
215
+ setProp((props) => (props.text = text));
216
+ }
217
+ }}
218
+ config={ckConfig}
219
+ type="inline"
220
+ />
221
+ </div>
222
+ </ErrorBoundary>
223
+ )}
224
+ {mode === "edit" && (
225
+ <Fragment>
226
+ <label>Label for Field</label>
227
+ <select
228
+ value={labelFor}
229
+ onChange={setAProp("labelFor")}
230
+ className="form-control form-select"
231
+ >
232
+ <option value={""}></option>
233
+ {fields.map((f, ix) => (
234
+ <option key={ix} value={f.name}>
235
+ {f.label}
236
+ </option>
237
+ ))}
238
+ </select>
239
+ </Fragment>
240
+ )}
241
+ <table className="w-100 mt-2">
242
+ <tbody>
243
+ <TextStyleRow textStyle={textStyle} setProp={setProp} />
244
+ <tr>
245
+ <td>
246
+ <label>Icon</label>
247
+ </td>
248
+ <td>
249
+ <FontIconPicker
250
+ className="w-100"
251
+ value={icon}
252
+ icons={faIcons}
253
+ onChange={(value) => setProp((prop) => (prop.icon = value))}
254
+ isMulti={false}
255
+ />
256
+ </td>
257
+ </tr>
258
+ <SettingsRow
259
+ field={{
260
+ name: "font",
261
+ label: "Font family",
262
+ type: "Font",
263
+ }}
264
+ node={node}
265
+ setProp={setProp}
266
+ />
267
+ <SettingsRow
268
+ field={{
269
+ name: "font-size",
270
+ label: "Font size",
271
+ type: "DimUnits",
272
+ }}
273
+ node={node}
274
+ setProp={setProp}
275
+ isStyle={true}
276
+ />
277
+ <SettingsRow
278
+ field={{
279
+ name: "font-weight",
280
+ label: "Weight",
281
+ type: "Integer",
282
+ min: 100,
283
+ max: 900,
284
+ step: 100,
285
+ }}
286
+ node={node}
287
+ setProp={setProp}
288
+ isStyle={true}
289
+ />
290
+ <SettingsRow
291
+ field={{
292
+ name: "line-height",
293
+ label: "Line height",
294
+ type: "DimUnits",
295
+ }}
296
+ node={node}
297
+ setProp={setProp}
298
+ isStyle={true}
299
+ />
300
+ </tbody>
301
+ </table>
302
+ <BlockOrInlineSetting
303
+ block={block}
304
+ inline={inline}
305
+ textStyle={textStyle}
306
+ setProp={setProp}
307
+ />
308
+ </div>
309
+ );
310
+ };
311
+
312
+ /**
313
+ * @type {object}
314
+ */
315
+ Text.craft = {
316
+ defaultProps: {
317
+ text: "Click here",
318
+ block: false,
319
+ inline: false,
320
+ isFormula: {},
321
+ textStyle: "",
322
+ labelFor: "",
323
+ font: "",
324
+ style: {},
325
+ },
326
+ displayName: "Text",
327
+ related: {
328
+ settings: TextSettings,
329
+ },
330
+ };