@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.
- package/.babelrc +3 -0
- package/CHANGELOG.md +8 -0
- package/dist/builder_bundle.js +80 -0
- package/package.json +47 -0
- package/src/components/Builder.js +477 -0
- package/src/components/Library.js +224 -0
- package/src/components/RenderNode.js +203 -0
- package/src/components/Toolbox.js +688 -0
- package/src/components/context.js +9 -0
- package/src/components/elements/Action.js +204 -0
- package/src/components/elements/Aggregation.js +179 -0
- package/src/components/elements/BoxModelEditor.js +398 -0
- package/src/components/elements/Card.js +152 -0
- package/src/components/elements/Column.js +63 -0
- package/src/components/elements/Columns.js +201 -0
- package/src/components/elements/Container.js +947 -0
- package/src/components/elements/DropDownFilter.js +154 -0
- package/src/components/elements/DropMenu.js +156 -0
- package/src/components/elements/Empty.js +30 -0
- package/src/components/elements/Field.js +239 -0
- package/src/components/elements/HTMLCode.js +61 -0
- package/src/components/elements/Image.js +320 -0
- package/src/components/elements/JoinField.js +206 -0
- package/src/components/elements/LineBreak.js +46 -0
- package/src/components/elements/Link.js +305 -0
- package/src/components/elements/SearchBar.js +141 -0
- package/src/components/elements/Tabs.js +347 -0
- package/src/components/elements/Text.js +330 -0
- package/src/components/elements/ToggleFilter.js +243 -0
- package/src/components/elements/View.js +189 -0
- package/src/components/elements/ViewLink.js +225 -0
- package/src/components/elements/boxmodel.html +253 -0
- package/src/components/elements/faicons.js +1643 -0
- package/src/components/elements/utils.js +1217 -0
- package/src/components/preview_context.js +9 -0
- package/src/components/storage.js +506 -0
- package/src/index.js +73 -0
- 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
|
+
*/
|