@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,224 @@
1
+ /**
2
+ * @category saltcorn-builder
3
+ * @module components/Library
4
+ * @subcategory components
5
+ */
6
+
7
+ import React, { useEffect, useContext, useState, Fragment } from "react";
8
+ import { useEditor, useNode } from "@craftjs/core";
9
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
10
+ import { faPlus, faTimes } from "@fortawesome/free-solid-svg-icons";
11
+ import FontIconPicker from "@fonticonpicker/react-fonticonpicker";
12
+ import faIcons from "./elements/faicons";
13
+ import { craftToSaltcorn, layoutToNodes } from "./storage";
14
+ import optionsCtx from "./context";
15
+ import { WrapElem } from "./Toolbox";
16
+
17
+ /**
18
+ *
19
+ * @param {object[]} xs
20
+ * @returns {object[]}
21
+ */
22
+ const twoByTwos = (xs) => {
23
+ if (xs.length <= 2) return [xs];
24
+ const [x, y, ...rest] = xs;
25
+ return [[x, y], ...twoByTwos(rest)];
26
+ };
27
+
28
+ export /**
29
+ * @param {object} props
30
+ * @param {*} props.name
31
+ * @param {*} props.layout
32
+ * @returns {Fraggment}
33
+ * @category saltcorn-builder
34
+ * @subcategory components
35
+ * @namespace
36
+ */
37
+ const LibraryElem = ({ name, layout }) => {
38
+ const {
39
+ selected,
40
+ connectors: { connect, drag },
41
+ } = useNode((node) => ({ selected: node.events.selected }));
42
+ return (
43
+ <Fragment>
44
+ <span
45
+ className={selected ? "selected-node" : ""}
46
+ ref={(dom) => connect(drag(dom))}
47
+ >
48
+ LibElem
49
+ </span>
50
+ <br />
51
+ </Fragment>
52
+ );
53
+ };
54
+
55
+ /**
56
+ * @type {object}
57
+ */
58
+ LibraryElem.craft = {
59
+ displayName: "LibraryElem",
60
+ };
61
+
62
+ export /**
63
+ * @param {object} props
64
+ * @param {object} props.nodekeys
65
+ * @returns {object[]}
66
+ * @category saltcorn-builder
67
+ * @subcategory components
68
+ * @namespace
69
+ */
70
+ const InitNewElement = ({ nodekeys }) => {
71
+ const { actions, query, connectors } = useEditor((state, query) => {
72
+ return {};
73
+ });
74
+ const onNodesChange = (arg, arg1) => {
75
+ const nodes = arg.getSerializedNodes();
76
+ const newNodeIds = [];
77
+ Object.keys(nodes).forEach((id) => {
78
+ if (!nodekeys.current.includes(id)) {
79
+ newNodeIds.push(id);
80
+ }
81
+ });
82
+ nodekeys.current = Object.keys(nodes);
83
+ if (newNodeIds.length === 1) {
84
+ const id = newNodeIds[0];
85
+ const node = nodes[id];
86
+ if (node.displayName === "LibraryElem") {
87
+ const layout = node.props.layout;
88
+ layoutToNodes(
89
+ layout.layout ? layout.layout : layout,
90
+ query,
91
+ actions,
92
+ node.parent
93
+ );
94
+ setTimeout(() => {
95
+ actions.delete(id);
96
+ }, 0);
97
+ } else if (node.displayName !== "Column") {
98
+ actions.selectNode(id);
99
+ }
100
+ }
101
+ };
102
+ useEffect(() => {
103
+ const nodes = query.getSerializedNodes();
104
+ nodekeys.current = Object.keys(nodes);
105
+ actions.setOptions((options) => {
106
+ const oldf = options.onNodesChange(
107
+ (options.onNodesChange = oldf
108
+ ? (q) => {
109
+ oldf(q);
110
+ onNodesChange(q);
111
+ }
112
+ : onNodesChange)
113
+ );
114
+ });
115
+ }, []);
116
+
117
+ return [];
118
+ };
119
+
120
+ export /**
121
+ * @category saltcorn-builder
122
+ * @returns {div}
123
+ * @subcategory components
124
+ * @namespace
125
+ */
126
+ const Library = () => {
127
+ const { actions, selected, query, connectors } = useEditor((state, query) => {
128
+ return {
129
+ selected: state.events.selected,
130
+ };
131
+ });
132
+ const options = useContext(optionsCtx);
133
+ const [adding, setAdding] = useState(false);
134
+ const [newName, setNewName] = useState("");
135
+ const [icon, setIcon] = useState();
136
+ const [recent, setRecent] = useState([]);
137
+
138
+ /**
139
+ * @returns {void}
140
+ */
141
+ const addSelected = () => {
142
+ const layout = craftToSaltcorn(JSON.parse(query.serialize()), selected);
143
+ const data = { layout, icon, name: newName };
144
+ fetch(`/library/savefrombuilder`, {
145
+ method: "POST", // or 'PUT'
146
+ headers: {
147
+ "Content-Type": "application/json",
148
+ "CSRF-Token": options.csrfToken,
149
+ },
150
+ body: JSON.stringify(data),
151
+ });
152
+ setAdding(false);
153
+ setIcon();
154
+ setNewName("");
155
+ setRecent([...recent, data]);
156
+ };
157
+
158
+ const elemRows = twoByTwos([...(options.library || []), ...recent]);
159
+ return (
160
+ <div className="builder-library">
161
+ <div className="dropdown">
162
+ <button
163
+ className="btn btn-sm btn-secondary dropdown-toggle mt-2"
164
+ type="button"
165
+ id="dropdownMenuButton"
166
+ aria-haspopup="true"
167
+ aria-expanded="false"
168
+ disabled={!selected}
169
+ onClick={() => setAdding(!adding)}
170
+ >
171
+ <FontAwesomeIcon icon={faPlus} className="me-1" />
172
+ Add
173
+ </button>
174
+ <div
175
+ className={`dropdown-menu py-3 px-4 ${adding ? "show" : ""}`}
176
+ aria-labelledby="dropdownMenuButton"
177
+ >
178
+ <label>Name</label>
179
+ <input
180
+ type="text"
181
+ className="form-control"
182
+ value={newName}
183
+ onChange={(e) => setNewName(e.target.value)}
184
+ />
185
+ <br />
186
+ <label>Icon</label>
187
+ <FontIconPicker
188
+ className="w-100"
189
+ value={icon}
190
+ icons={faIcons}
191
+ onChange={setIcon}
192
+ isMulti={false}
193
+ />
194
+ <button className={`btn btn-primary mt-3`} onClick={addSelected}>
195
+ <FontAwesomeIcon icon={faPlus} className="me-1" />
196
+ Add
197
+ </button>
198
+ <button
199
+ className={`btn btn-outline-secondary ms-2 mt-3`}
200
+ onClick={() => setAdding(false)}
201
+ >
202
+ <FontAwesomeIcon icon={faTimes} />
203
+ </button>
204
+ </div>
205
+ </div>
206
+ <div className="card mt-2">
207
+ {elemRows.map((els, ix) => (
208
+ <div className="toolbar-row" key={ix}>
209
+ {els.map((l, ix) => (
210
+ <WrapElem
211
+ key={ix}
212
+ connectors={connectors}
213
+ icon={l.icon}
214
+ label={l.name}
215
+ >
216
+ <LibraryElem name={l.name} layout={l.layout}></LibraryElem>
217
+ </WrapElem>
218
+ ))}
219
+ </div>
220
+ ))}
221
+ </div>
222
+ </div>
223
+ );
224
+ };
@@ -0,0 +1,203 @@
1
+ /**
2
+ * @category saltcorn-builder
3
+ * @module components/RenderNode
4
+ * @subcategory components
5
+ */
6
+
7
+ import { useNode, useEditor } from "@craftjs/core";
8
+ //import { ROOT_NODE } from "@craftjs/utils";
9
+ import React, { useEffect, useRef, useCallback, Fragment } from "react";
10
+ import ReactDOM from "react-dom";
11
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
12
+ import {
13
+ faCopy,
14
+ faUndo,
15
+ faRedo,
16
+ faTrashAlt,
17
+ faArrowUp,
18
+ faArrowsAlt,
19
+ } from "@fortawesome/free-solid-svg-icons";
20
+ import { recursivelyCloneToElems } from "./elements/utils";
21
+ /*
22
+ Contains code copied from craft.js landing page example
23
+ Copyright (c) 2020 Previnash Wong Sze Chuan
24
+ */
25
+
26
+ export /**
27
+ * @param {object} props
28
+ * @param {string} props.render
29
+ * @category saltcorn-builder
30
+ * @subcategory components
31
+ * @namespace
32
+ */
33
+ const RenderNode = ({ render }) => {
34
+ const { id } = useNode();
35
+ const { actions, query, isActive } = useEditor((state) => ({
36
+ isActive: state.nodes[id].events.selected,
37
+ }));
38
+
39
+ const {
40
+ isHover,
41
+ dom,
42
+ name,
43
+ moveable,
44
+ deletable,
45
+ connectors: { drag },
46
+ parent,
47
+ } = useNode((node) => ({
48
+ isHover: node.events.hovered,
49
+ dom: node.dom,
50
+ name: node.data.custom.displayName || node.data.displayName,
51
+ moveable: query.node(node.id).isDraggable(),
52
+ deletable: query.node(node.id).isDeletable(),
53
+ parent: node.data.parent,
54
+ props: node.data.props,
55
+ }));
56
+
57
+ const currentRef = useRef();
58
+
59
+ const getPos = useCallback((dom) => {
60
+ const { top, left, bottom, height, width, right } = dom
61
+ ? dom.getBoundingClientRect()
62
+ : { top: 0, left: 0, bottom: 0, right: 0, height: 0, width: 0 };
63
+ return {
64
+ top: `${top > 0 ? top : bottom}px`,
65
+ left: `${left}px`,
66
+ topn: top,
67
+ leftn: left,
68
+ height,
69
+ width,
70
+ right,
71
+ bottom,
72
+ };
73
+ }, []);
74
+
75
+ const scroll = useCallback(() => {
76
+ const { current: currentDOM } = currentRef;
77
+ if (!currentDOM) return;
78
+ const { top, left } = getPos(dom);
79
+ currentDOM.style.top = top;
80
+ currentDOM.style.left = left;
81
+ }, [dom, getPos]);
82
+
83
+ useEffect(() => {
84
+ document
85
+ .getElementById("builder-main-canvas")
86
+ .addEventListener("scroll", scroll);
87
+ document.addEventListener("scroll", scroll);
88
+
89
+ return () => {
90
+ document
91
+ .getElementById("builder-main-canvas")
92
+ .removeEventListener("scroll", scroll);
93
+ document.removeEventListener("scroll", scroll);
94
+ };
95
+ }, [scroll]);
96
+
97
+ /**
98
+ * @returns {void}
99
+ */
100
+ const duplicate = () => {
101
+ const {
102
+ data: { parent },
103
+ } = query.node(id).get();
104
+ const siblings = query.node(parent).childNodes();
105
+ const sibIx = siblings.findIndex((sib) => sib === id);
106
+ const elem = recursivelyCloneToElems(query)(id);
107
+ actions.addNodeTree(
108
+ query.parseReactElement(elem).toNodeTree(),
109
+ parent || "ROOT",
110
+ sibIx + 1
111
+ );
112
+ };
113
+ return (
114
+ <>
115
+ {(isActive || isHover) &&
116
+ id !== "ROOT" &&
117
+ !(name === "Column" && !isActive)
118
+ ? ReactDOM.createPortal(
119
+ <div
120
+ ref={currentRef}
121
+ className={`selected-indicator ${
122
+ isActive ? "activeind" : "hoverind"
123
+ } px-1 text-white`}
124
+ style={{
125
+ left: getPos(dom).left,
126
+ top: getPos(dom).top,
127
+ zIndex: 9999,
128
+ }}
129
+ >
130
+ <div className="dispname me-3">{name}</div>{" "}
131
+ {moveable && isActive && (
132
+ <button
133
+ className="btn btn-link btn-builder-move p-0"
134
+ ref={drag}
135
+ >
136
+ <FontAwesomeIcon icon={faArrowsAlt} className="me-2" />
137
+ </button>
138
+ )}
139
+ {isActive && parent && parent !== "ROOT" ? (
140
+ <FontAwesomeIcon
141
+ icon={faArrowUp}
142
+ className="me-2"
143
+ onClick={() => {
144
+ actions.selectNode(parent);
145
+ }}
146
+ />
147
+ ) : null}
148
+ {deletable && isActive
149
+ ? [
150
+ <FontAwesomeIcon
151
+ key={1}
152
+ icon={faCopy}
153
+ onClick={duplicate}
154
+ className="me-2"
155
+ />,
156
+ <FontAwesomeIcon
157
+ key={2}
158
+ icon={faTrashAlt}
159
+ className="me-2"
160
+ onMouseDown={(e) => {
161
+ e.stopPropagation();
162
+ actions.delete(id);
163
+ setTimeout(() => actions.selectNode(parent), 0);
164
+ }}
165
+ />,
166
+ ]
167
+ : null}
168
+ </div>,
169
+ document.querySelector("#builder-main-canvas")
170
+ )
171
+ : null}
172
+ {render}
173
+ </>
174
+ );
175
+ };
176
+ /*
177
+ {moveable ? (
178
+ <Btn className="me-2 cursor-move" ref={drag}>
179
+ <Move />
180
+ </Btn>
181
+ ) : null}
182
+ {id !== ROOT_NODE && (
183
+ <Btn
184
+ className="me-2 cursor-pointer"
185
+ onClick={() => {
186
+ actions.selectNode(parent);
187
+ }}
188
+ >
189
+ <ArrowUp />
190
+ </Btn>
191
+ )}
192
+ {deletable ? (
193
+ <Btn
194
+ className="cursor-pointer"
195
+ onMouseDown={(e) => {
196
+ e.stopPropagation();
197
+ actions.delete(id);
198
+ }}
199
+ >
200
+ <Delete />
201
+ </Btn>
202
+ ) : null}
203
+ */