@saltcorn/builder 1.6.0-alpha.7 → 1.6.0-alpha.9
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/dist/builder_bundle.js +104014 -2
- package/dist/builder_bundle.js.LICENSE.txt +65 -0
- package/package.json +32 -28
- package/src/components/Builder.js +334 -122
- package/src/components/Library.js +25 -13
- package/src/components/RenderNode.js +6 -6
- package/src/components/Toolbox.js +333 -269
- package/src/components/elements/Action.js +144 -29
- package/src/components/elements/Aggregation.js +20 -23
- package/src/components/elements/ArrayManager.js +7 -5
- package/src/components/elements/BoxModelEditor.js +19 -17
- package/src/components/elements/Card.js +47 -34
- package/src/components/elements/Clone.js +74 -2
- package/src/components/elements/Column.js +1 -1
- package/src/components/elements/Columns.js +27 -25
- package/src/components/elements/Container.js +170 -90
- package/src/components/elements/DropDownFilter.js +10 -8
- package/src/components/elements/DropMenu.js +8 -5
- package/src/components/elements/Field.js +9 -7
- package/src/components/elements/HTMLCode.js +3 -1
- package/src/components/elements/Image.js +20 -15
- package/src/components/elements/JoinField.js +15 -11
- package/src/components/elements/Link.js +18 -16
- package/src/components/elements/ListColumn.js +7 -3
- package/src/components/elements/ListColumns.js +4 -1
- package/src/components/elements/MonacoEditor.js +4 -2
- package/src/components/elements/Page.js +7 -4
- package/src/components/elements/RelationBadges.js +16 -11
- package/src/components/elements/RelationOnDemandPicker.js +18 -12
- package/src/components/elements/SearchBar.js +10 -6
- package/src/components/elements/Table.js +72 -65
- package/src/components/elements/Tabs.js +18 -15
- package/src/components/elements/Text.js +19 -14
- package/src/components/elements/ToggleFilter.js +28 -25
- package/src/components/elements/View.js +36 -18
- package/src/components/elements/ViewLink.js +15 -11
- package/src/components/elements/utils.js +224 -55
- package/src/components/storage.js +27 -129
- package/src/hooks/useTranslation.js +11 -0
- package/src/index.js +6 -3
|
@@ -10,8 +10,11 @@ import React, {
|
|
|
10
10
|
useState,
|
|
11
11
|
Fragment,
|
|
12
12
|
useRef,
|
|
13
|
+
memo,
|
|
13
14
|
} from "react";
|
|
14
|
-
import
|
|
15
|
+
import useTranslation from "../hooks/useTranslation";
|
|
16
|
+
import { Editor, Frame, Element, Selector, useEditor, DefaultEventHandlers } from "@craftjs/core";
|
|
17
|
+
import { Layers, useLayer } from "@craftjs/layers"
|
|
15
18
|
import { Text } from "./elements/Text";
|
|
16
19
|
import { Field } from "./elements/Field";
|
|
17
20
|
import { JoinField } from "./elements/JoinField";
|
|
@@ -46,7 +49,7 @@ import { Link } from "./elements/Link";
|
|
|
46
49
|
import { View } from "./elements/View";
|
|
47
50
|
import { Container } from "./elements/Container";
|
|
48
51
|
import { Column } from "./elements/Column";
|
|
49
|
-
import { Layers } from "saltcorn-craft-layers-noeye";
|
|
52
|
+
// import { Layers } from "saltcorn-craft-layers-noeye";
|
|
50
53
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
51
54
|
import {
|
|
52
55
|
faCopy,
|
|
@@ -56,13 +59,15 @@ import {
|
|
|
56
59
|
faSave,
|
|
57
60
|
faExclamationTriangle,
|
|
58
61
|
faPlus,
|
|
62
|
+
faChevronDown,
|
|
63
|
+
faChevronUp
|
|
59
64
|
} from "@fortawesome/free-solid-svg-icons";
|
|
60
65
|
import {
|
|
61
66
|
faCaretSquareLeft,
|
|
62
67
|
faCaretSquareRight,
|
|
63
68
|
} from "@fortawesome/free-regular-svg-icons";
|
|
64
69
|
import { Accordion, ErrorBoundary } from "./elements/utils";
|
|
65
|
-
import { InitNewElement, Library } from "./Library";
|
|
70
|
+
import { InitNewElement, Library, LibraryElem } from "./Library";
|
|
66
71
|
import { RenderNode } from "./RenderNode";
|
|
67
72
|
import { ListColumn } from "./elements/ListColumn";
|
|
68
73
|
import { ListColumns } from "./elements/ListColumns";
|
|
@@ -70,6 +75,28 @@ import { recursivelyCloneToElems } from "./elements/Clone";
|
|
|
70
75
|
|
|
71
76
|
const { Provider } = optionsCtx;
|
|
72
77
|
|
|
78
|
+
const getSelectedNodes = (selected) => {
|
|
79
|
+
if (!selected) return [];
|
|
80
|
+
if (typeof selected.all === "function") {
|
|
81
|
+
return selected.all();
|
|
82
|
+
}
|
|
83
|
+
if (Array.isArray(selected.all)) {
|
|
84
|
+
return selected.all;
|
|
85
|
+
}
|
|
86
|
+
if (typeof selected.values === "function") {
|
|
87
|
+
return Array.from(selected.values());
|
|
88
|
+
}
|
|
89
|
+
if (typeof selected.has === "function") {
|
|
90
|
+
return [...selected];
|
|
91
|
+
}
|
|
92
|
+
return [selected];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const getFirstSelected = (selected) => {
|
|
96
|
+
const nodes = getSelectedNodes(selected);
|
|
97
|
+
return nodes.length > 0 ? nodes[0] : null;
|
|
98
|
+
};
|
|
99
|
+
|
|
73
100
|
/**
|
|
74
101
|
*
|
|
75
102
|
* @returns {div}
|
|
@@ -78,10 +105,12 @@ const { Provider } = optionsCtx;
|
|
|
78
105
|
* @namespace
|
|
79
106
|
*/
|
|
80
107
|
const SettingsPanel = () => {
|
|
108
|
+
const { t } = useTranslation();
|
|
81
109
|
const options = useContext(optionsCtx);
|
|
82
110
|
|
|
83
|
-
const { actions, selected, query } = useEditor((state, query) => {
|
|
84
|
-
const
|
|
111
|
+
const { actions, selected, selectedCount, query } = useEditor((state, query) => {
|
|
112
|
+
const selectedNodes = getSelectedNodes(state.events.selected);
|
|
113
|
+
const currentNodeId = selectedNodes.length === 1 ? selectedNodes[0] : null;
|
|
85
114
|
let selected;
|
|
86
115
|
|
|
87
116
|
if (currentNodeId) {
|
|
@@ -104,6 +133,7 @@ const SettingsPanel = () => {
|
|
|
104
133
|
|
|
105
134
|
return {
|
|
106
135
|
selected,
|
|
136
|
+
selectedCount: selectedNodes.length,
|
|
107
137
|
};
|
|
108
138
|
});
|
|
109
139
|
|
|
@@ -128,74 +158,126 @@ const SettingsPanel = () => {
|
|
|
128
158
|
const handleUserKeyPress = (event) => {
|
|
129
159
|
const { keyCode, target } = event;
|
|
130
160
|
const tagName = target.tagName.toLowerCase();
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
161
|
+
const hasSelection = selectedCount > 0;
|
|
162
|
+
if ((tagName === "body" || tagName === "button") && hasSelection) {
|
|
163
|
+
if (selected) {
|
|
164
|
+
if ((keyCode === 8 || keyCode === 46) && selected.id === "ROOT") {
|
|
165
|
+
deleteChildren();
|
|
166
|
+
}
|
|
167
|
+
if (keyCode === 8) {
|
|
168
|
+
//backspace
|
|
169
|
+
const prevSib = otherSibling(-1);
|
|
170
|
+
const parent = selected.parent;
|
|
171
|
+
deleteThis();
|
|
172
|
+
if (prevSib) actions.selectNode(prevSib);
|
|
173
|
+
else actions.selectNode(parent);
|
|
174
|
+
}
|
|
175
|
+
if (keyCode === 46) {
|
|
176
|
+
//del
|
|
177
|
+
const nextSib = otherSibling(1);
|
|
178
|
+
deleteThis();
|
|
179
|
+
if (nextSib) actions.selectNode(nextSib);
|
|
180
|
+
}
|
|
181
|
+
if (keyCode === 37 && selected.parent)
|
|
182
|
+
//left
|
|
183
|
+
actions.selectNode(selected.parent);
|
|
184
|
+
|
|
185
|
+
if (keyCode === 39) {
|
|
186
|
+
//right
|
|
187
|
+
if (selected.children && selected.children.length > 0) {
|
|
188
|
+
actions.selectNode(selected.children[0]);
|
|
189
|
+
} else if (selected.displayName === "Columns") {
|
|
190
|
+
const node = query.node(selected.id).get();
|
|
191
|
+
const child = node?.data?.linkedNodes?.Col0;
|
|
192
|
+
if (child) actions.selectNode(child);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (keyCode === 38 && selected.parent) {
|
|
196
|
+
//up
|
|
197
|
+
const prevSib = otherSibling(-1);
|
|
198
|
+
if (prevSib) actions.selectNode(prevSib);
|
|
199
|
+
event.preventDefault();
|
|
200
|
+
}
|
|
201
|
+
if (keyCode === 40 && selected.parent) {
|
|
202
|
+
//down
|
|
203
|
+
const nextSib = otherSibling(1);
|
|
204
|
+
if (nextSib) actions.selectNode(nextSib);
|
|
205
|
+
event.preventDefault();
|
|
162
206
|
}
|
|
163
207
|
}
|
|
164
|
-
if (
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
208
|
+
if ((event.ctrlKey || event.metaKey) && event.keyCode == 67) {
|
|
209
|
+
const serialized = JSON.parse(query.serialize());
|
|
210
|
+
const serializedIds = new Set(Object.keys(serialized));
|
|
211
|
+
const currentSelected = query.getEvent("selected");
|
|
212
|
+
const rawSelected = getSelectedNodes(currentSelected);
|
|
213
|
+
if (rawSelected.length === 0 && selected?.id) rawSelected.push(selected.id);
|
|
214
|
+
const selectedNodes = rawSelected
|
|
215
|
+
.map((nodeId) => (typeof nodeId === "string" ? nodeId : nodeId?.id))
|
|
216
|
+
.filter(
|
|
217
|
+
(nodeId) =>
|
|
218
|
+
nodeId && nodeId !== "ROOT" && serializedIds.has(nodeId)
|
|
219
|
+
);
|
|
220
|
+
if (selectedNodes.length === 0) return;
|
|
221
|
+
|
|
222
|
+
if (selectedNodes.length === 1) {
|
|
223
|
+
const { layout } = craftToSaltcorn(
|
|
224
|
+
serialized,
|
|
225
|
+
selectedNodes[0],
|
|
226
|
+
options
|
|
227
|
+
);
|
|
228
|
+
navigator.clipboard.writeText(JSON.stringify(layout, null, 2));
|
|
229
|
+
} else {
|
|
230
|
+
const layouts = selectedNodes.map((nodeId) => {
|
|
231
|
+
const { layout } = craftToSaltcorn(
|
|
232
|
+
serialized,
|
|
233
|
+
nodeId,
|
|
234
|
+
options
|
|
235
|
+
);
|
|
236
|
+
return layout;
|
|
237
|
+
});
|
|
238
|
+
navigator.clipboard.writeText(
|
|
239
|
+
JSON.stringify({ above: layouts }, null, 2)
|
|
240
|
+
);
|
|
241
|
+
}
|
|
185
242
|
}
|
|
186
|
-
if ((event.ctrlKey || event.metaKey) && event.keyCode == 88
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
243
|
+
if ((event.ctrlKey || event.metaKey) && event.keyCode == 88) {
|
|
244
|
+
const serialized = JSON.parse(query.serialize());
|
|
245
|
+
const serializedIds = new Set(Object.keys(serialized));
|
|
246
|
+
const currentSelected = query.getEvent("selected");
|
|
247
|
+
const rawSelected = getSelectedNodes(currentSelected);
|
|
248
|
+
if (rawSelected.length === 0 && selected?.id) rawSelected.push(selected.id);
|
|
249
|
+
const selectedNodes = rawSelected
|
|
250
|
+
.map((nodeId) => (typeof nodeId === "string" ? nodeId : nodeId?.id))
|
|
251
|
+
.filter(
|
|
252
|
+
(nodeId) =>
|
|
253
|
+
nodeId && nodeId !== "ROOT" && serializedIds.has(nodeId)
|
|
254
|
+
);
|
|
255
|
+
if (selectedNodes.length === 0) return;
|
|
256
|
+
|
|
257
|
+
if (selectedNodes.length === 1) {
|
|
258
|
+
const { layout } = craftToSaltcorn(
|
|
259
|
+
serialized,
|
|
260
|
+
selectedNodes[0],
|
|
261
|
+
options
|
|
262
|
+
);
|
|
263
|
+
navigator.clipboard.writeText(JSON.stringify(layout, null, 2));
|
|
264
|
+
actions.delete(selectedNodes[0]);
|
|
265
|
+
} else {
|
|
266
|
+
const layouts = selectedNodes.map((nodeId) => {
|
|
267
|
+
const { layout } = craftToSaltcorn(
|
|
268
|
+
serialized,
|
|
269
|
+
nodeId,
|
|
270
|
+
options
|
|
271
|
+
);
|
|
272
|
+
return layout;
|
|
273
|
+
});
|
|
274
|
+
navigator.clipboard.writeText(
|
|
275
|
+
JSON.stringify({ above: layouts }, null, 2)
|
|
276
|
+
);
|
|
277
|
+
selectedNodes.forEach((nodeId) => actions.delete(nodeId));
|
|
278
|
+
}
|
|
195
279
|
}
|
|
196
280
|
if ((event.ctrlKey || event.metaKey) && event.keyCode == 86) {
|
|
197
|
-
// paste elem from clipboard into container element
|
|
198
|
-
|
|
199
281
|
navigator.clipboard.readText().then((clipText) => {
|
|
200
282
|
const layout = JSON.parse(clipText);
|
|
201
283
|
layoutToNodes(
|
|
@@ -245,7 +327,6 @@ const SettingsPanel = () => {
|
|
|
245
327
|
const siblings = query.node(selected.parent).childNodes();
|
|
246
328
|
const sibIx = siblings.findIndex((sib) => sib === selected.id);
|
|
247
329
|
const elem = recursivelyCloneToElems(query)(selected.id);
|
|
248
|
-
//console.log(elem);
|
|
249
330
|
actions.addNodeTree(
|
|
250
331
|
query.parseReactElement(elem).toNodeTree(),
|
|
251
332
|
parent || "ROOT",
|
|
@@ -261,11 +342,16 @@ const SettingsPanel = () => {
|
|
|
261
342
|
<b>{selected.displayName}</b> settings
|
|
262
343
|
</Fragment>
|
|
263
344
|
) : (
|
|
264
|
-
"Settings"
|
|
345
|
+
t("Settings")
|
|
265
346
|
)}
|
|
266
347
|
</div>
|
|
267
348
|
<div className="card-body p-2">
|
|
268
|
-
{
|
|
349
|
+
{selectedCount > 1 ? (
|
|
350
|
+
<div>
|
|
351
|
+
<p><strong>{selectedCount} {t("elements selected")}</strong></p>
|
|
352
|
+
<p className="text-muted small">{t("Multi-selection active. Use Shift+Click to add/remove elements.")}</p>
|
|
353
|
+
</div>
|
|
354
|
+
) : selected ? (
|
|
269
355
|
<Fragment>
|
|
270
356
|
{selected.isDeletable && (
|
|
271
357
|
<button
|
|
@@ -273,7 +359,7 @@ const SettingsPanel = () => {
|
|
|
273
359
|
onClick={deleteThis}
|
|
274
360
|
>
|
|
275
361
|
<FontAwesomeIcon icon={faTrashAlt} className="me-1" />
|
|
276
|
-
Delete
|
|
362
|
+
{t("Delete")}
|
|
277
363
|
</button>
|
|
278
364
|
)}
|
|
279
365
|
{hasChildren && !selected.isDeletable ? (
|
|
@@ -282,23 +368,23 @@ const SettingsPanel = () => {
|
|
|
282
368
|
onClick={deleteChildren}
|
|
283
369
|
>
|
|
284
370
|
<FontAwesomeIcon icon={faTrashAlt} className="me-1" />
|
|
285
|
-
Delete contents
|
|
371
|
+
{t("Delete contents")}
|
|
286
372
|
</button>
|
|
287
373
|
) : (
|
|
288
374
|
<button
|
|
289
|
-
title="Duplicate element with its children"
|
|
375
|
+
title={t("Duplicate element with its children")}
|
|
290
376
|
className="btn btn-sm btn-secondary ms-2 duplicate-element-builder"
|
|
291
377
|
onClick={duplicate}
|
|
292
378
|
>
|
|
293
379
|
<FontAwesomeIcon icon={faCopy} className="me-1" />
|
|
294
|
-
Clone
|
|
380
|
+
{t("Clone")}
|
|
295
381
|
</button>
|
|
296
382
|
)}
|
|
297
383
|
<hr className="my-2" />
|
|
298
384
|
{selected.settings && React.createElement(selected.settings)}
|
|
299
385
|
</Fragment>
|
|
300
386
|
) : (
|
|
301
|
-
"No element selected"
|
|
387
|
+
t("No element selected")
|
|
302
388
|
)}
|
|
303
389
|
</div>
|
|
304
390
|
</div>
|
|
@@ -331,7 +417,115 @@ function useWindowDimensions() {
|
|
|
331
417
|
return windowDimensions;
|
|
332
418
|
}
|
|
333
419
|
|
|
420
|
+
/**
|
|
421
|
+
* Custom Layer Component for Craft.js Layers panel
|
|
422
|
+
* Must be defined outside Builder component and memoized to prevent infinite re-renders
|
|
423
|
+
* Added defensive checks for layer properties
|
|
424
|
+
* @category saltcorn-builder
|
|
425
|
+
* @subcategory components
|
|
426
|
+
* @namespace
|
|
427
|
+
*/
|
|
428
|
+
|
|
429
|
+
const hiddenColumnParents = new Set(["Card", "Container", "Tabs", "Table"]);
|
|
430
|
+
|
|
431
|
+
const CustomLayerComponent = memo(({ children }) => {
|
|
432
|
+
const {
|
|
433
|
+
id,
|
|
434
|
+
depth,
|
|
435
|
+
expanded,
|
|
436
|
+
hovered,
|
|
437
|
+
actions: { toggleLayer, setExpandedState },
|
|
438
|
+
connectors: { layer, drag },
|
|
439
|
+
} = useLayer((layer) => {
|
|
440
|
+
return {
|
|
441
|
+
hovered: layer?.event?.hovered,
|
|
442
|
+
expanded: layer?.expanded,
|
|
443
|
+
};
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const { displayName, hasNodes, isHiddenColumn } = useEditor((state) => {
|
|
447
|
+
const node = state.nodes[id];
|
|
448
|
+
const data = node?.data;
|
|
449
|
+
|
|
450
|
+
let name = data?.displayName || data?.name || id;
|
|
451
|
+
if (name === "ROOT" || name === "Canvas") {
|
|
452
|
+
name = data?.name || name;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const nodes = data?.nodes;
|
|
456
|
+
const linkedNodes = data?.linkedNodes;
|
|
457
|
+
const hasChildren = (nodes && nodes.length > 0) || (linkedNodes && Object.keys(linkedNodes).length > 0);
|
|
458
|
+
|
|
459
|
+
// Check if this Column is a linked node of a Card/Container/Tabs/Table
|
|
460
|
+
let shouldHide = false;
|
|
461
|
+
if (name === "Column" && data?.parent) {
|
|
462
|
+
const parentNode = state.nodes[data.parent];
|
|
463
|
+
const parentName = parentNode?.data?.displayName || parentNode?.data?.name;
|
|
464
|
+
if (hiddenColumnParents.has(parentName)) {
|
|
465
|
+
const parentLinked = parentNode?.data?.linkedNodes;
|
|
466
|
+
if (parentLinked && Object.values(parentLinked).includes(id)) {
|
|
467
|
+
shouldHide = true;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return {
|
|
473
|
+
displayName: name,
|
|
474
|
+
hasNodes: hasChildren,
|
|
475
|
+
isHiddenColumn: shouldHide
|
|
476
|
+
};
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
// Auto-expand hidden linked-node Columns so their children are always
|
|
480
|
+
// visible through the transparent wrapper. Uses setExpandedState(true)
|
|
481
|
+
// instead of toggleLayer() — it's idempotent (no-op when already true),
|
|
482
|
+
// so it won't conflict with craft.js internals or cause toggle loops.
|
|
483
|
+
useEffect(() => {
|
|
484
|
+
if (isHiddenColumn && !expanded) {
|
|
485
|
+
setExpandedState(true);
|
|
486
|
+
}
|
|
487
|
+
}, [isHiddenColumn, expanded, setExpandedState]);
|
|
488
|
+
|
|
489
|
+
if (isHiddenColumn) {
|
|
490
|
+
return (
|
|
491
|
+
<div
|
|
492
|
+
ref={(dom) => { layer(dom); drag(dom); }}
|
|
493
|
+
style={{ marginLeft: "-20px" }}
|
|
494
|
+
>
|
|
495
|
+
{children}
|
|
496
|
+
</div>
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return (
|
|
501
|
+
<div>
|
|
502
|
+
<div
|
|
503
|
+
ref={(dom) => { layer(dom); drag(dom); }}
|
|
504
|
+
className={`builder-layer-node ${hovered ? "hovered" : ""}`}
|
|
505
|
+
style={{
|
|
506
|
+
paddingLeft: `${depth * 20 + 10}px`,
|
|
507
|
+
}}
|
|
508
|
+
>
|
|
509
|
+
<span className="layer-name" style={{ flexGrow: 1 }}>{displayName}</span>
|
|
510
|
+
|
|
511
|
+
{hasNodes && (
|
|
512
|
+
<span
|
|
513
|
+
onClick={(e) => {
|
|
514
|
+
e.stopPropagation();
|
|
515
|
+
toggleLayer();
|
|
516
|
+
}}
|
|
517
|
+
>
|
|
518
|
+
<FontAwesomeIcon icon={expanded ? faChevronUp : faChevronDown} fontSize={14} className="float-end fa-lg" />
|
|
519
|
+
</span>
|
|
520
|
+
)}
|
|
521
|
+
</div>
|
|
522
|
+
{children}
|
|
523
|
+
</div>
|
|
524
|
+
);
|
|
525
|
+
});
|
|
526
|
+
|
|
334
527
|
const AddColumnButton = () => {
|
|
528
|
+
const { t } = useTranslation();
|
|
335
529
|
const { query, actions } = useEditor(() => {});
|
|
336
530
|
const options = useContext(optionsCtx);
|
|
337
531
|
const addColumn = () => {
|
|
@@ -346,7 +540,7 @@ const AddColumnButton = () => {
|
|
|
346
540
|
onClick={addColumn}
|
|
347
541
|
>
|
|
348
542
|
<FontAwesomeIcon icon={faPlus} className="me-2" />
|
|
349
|
-
Add column
|
|
543
|
+
{t("Add column")}
|
|
350
544
|
</button>
|
|
351
545
|
);
|
|
352
546
|
};
|
|
@@ -358,6 +552,7 @@ const AddColumnButton = () => {
|
|
|
358
552
|
* @namespace
|
|
359
553
|
*/
|
|
360
554
|
const HistoryPanel = () => {
|
|
555
|
+
const { t } = useTranslation();
|
|
361
556
|
const { canUndo, canRedo, actions } = useEditor((state, query) => ({
|
|
362
557
|
canUndo: query.history.canUndo(),
|
|
363
558
|
canRedo: query.history.canRedo(),
|
|
@@ -368,7 +563,7 @@ const HistoryPanel = () => {
|
|
|
368
563
|
{canUndo && (
|
|
369
564
|
<button
|
|
370
565
|
className="btn btn-sm btn-secondary ms-2 me-2 undo-builder"
|
|
371
|
-
title="Undo"
|
|
566
|
+
title={t("Undo")}
|
|
372
567
|
onClick={() => actions.history.undo()}
|
|
373
568
|
>
|
|
374
569
|
<FontAwesomeIcon icon={faUndo} />
|
|
@@ -377,7 +572,7 @@ const HistoryPanel = () => {
|
|
|
377
572
|
{canRedo && (
|
|
378
573
|
<button
|
|
379
574
|
className="btn btn-sm btn-secondary redo-builder"
|
|
380
|
-
title="Redo"
|
|
575
|
+
title={t("Redo")}
|
|
381
576
|
onClick={() => actions.history.redo()}
|
|
382
577
|
>
|
|
383
578
|
<FontAwesomeIcon icon={faRedo} />
|
|
@@ -437,7 +632,10 @@ const NextButton = ({ layout }) => {
|
|
|
437
632
|
* @subcategory components
|
|
438
633
|
* @namespace
|
|
439
634
|
*/
|
|
635
|
+
|
|
636
|
+
|
|
440
637
|
const Builder = ({ options, layout, mode }) => {
|
|
638
|
+
const { t } = useTranslation();
|
|
441
639
|
const [showLayers, setShowLayers] = useState(true);
|
|
442
640
|
const [previews, setPreviews] = useState({});
|
|
443
641
|
const [uploadedFiles, setUploadedFiles] = useState([]);
|
|
@@ -453,18 +651,56 @@ const Builder = ({ options, layout, mode }) => {
|
|
|
453
651
|
|
|
454
652
|
const ref = useRef(null);
|
|
455
653
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
654
|
+
useEffect(() => {
|
|
655
|
+
if (!ref.current) return;
|
|
656
|
+
setBuilderHeight(ref.current.clientHeight);
|
|
657
|
+
const rect = ref.current.getBoundingClientRect();
|
|
658
|
+
setBuilderTop(rect.top);
|
|
659
|
+
});
|
|
462
660
|
|
|
463
661
|
const canvasHeight =
|
|
464
662
|
Math.max(windowHeight - builderTop, builderHeight, 600) - 10;
|
|
465
663
|
return (
|
|
466
664
|
<ErrorBoundary>
|
|
467
|
-
<Editor
|
|
665
|
+
<Editor
|
|
666
|
+
onRender={RenderNode}
|
|
667
|
+
indicator={{
|
|
668
|
+
success: "#28a745",
|
|
669
|
+
thickness: 2,
|
|
670
|
+
className: "builder-drop-indicator",
|
|
671
|
+
}}
|
|
672
|
+
handlers={(store) => new DefaultEventHandlers({
|
|
673
|
+
store,
|
|
674
|
+
isMultiSelectEnabled: (e) => e?.shiftKey || false
|
|
675
|
+
})}
|
|
676
|
+
resolver={{
|
|
677
|
+
Text,
|
|
678
|
+
Empty,
|
|
679
|
+
Columns,
|
|
680
|
+
JoinField,
|
|
681
|
+
Field,
|
|
682
|
+
ViewLink,
|
|
683
|
+
Action,
|
|
684
|
+
HTMLCode,
|
|
685
|
+
LineBreak,
|
|
686
|
+
Aggregation,
|
|
687
|
+
Card,
|
|
688
|
+
Image,
|
|
689
|
+
Link,
|
|
690
|
+
View,
|
|
691
|
+
SearchBar,
|
|
692
|
+
Container,
|
|
693
|
+
Column,
|
|
694
|
+
DropDownFilter,
|
|
695
|
+
DropMenu,
|
|
696
|
+
Tabs,
|
|
697
|
+
Table,
|
|
698
|
+
ToggleFilter,
|
|
699
|
+
ListColumn,
|
|
700
|
+
ListColumns,
|
|
701
|
+
LibraryElem,
|
|
702
|
+
}}
|
|
703
|
+
>
|
|
468
704
|
<Provider value={options}>
|
|
469
705
|
<PreviewCtx.Provider
|
|
470
706
|
value={{ previews, setPreviews, uploadedFiles, setUploadedFiles }}
|
|
@@ -496,16 +732,16 @@ const Builder = ({ options, layout, mode }) => {
|
|
|
496
732
|
savingState={savingState}
|
|
497
733
|
/>
|
|
498
734
|
<Accordion>
|
|
499
|
-
<div className="card mt-1" accordiontitle="Components">
|
|
735
|
+
<div className="card mt-1" accordiontitle={t("Components")}>
|
|
500
736
|
{{
|
|
501
737
|
show: <ToolboxShow expanded={isLeftEnlarged} />,
|
|
502
738
|
list: <ToolboxList expanded={isLeftEnlarged} />,
|
|
503
739
|
edit: <ToolboxEdit expanded={isLeftEnlarged} />,
|
|
504
740
|
page: <ToolboxPage expanded={isLeftEnlarged} />,
|
|
505
741
|
filter: <ToolboxFilter expanded={isLeftEnlarged} />,
|
|
506
|
-
}[mode] || <div>Missing mode</div>}
|
|
742
|
+
}[mode] || <div>{t("Missing mode")}</div>}
|
|
507
743
|
</div>
|
|
508
|
-
<div accordiontitle="Library">
|
|
744
|
+
<div accordiontitle={t("Library")}>
|
|
509
745
|
<Library expanded={isLeftEnlarged} />
|
|
510
746
|
</div>
|
|
511
747
|
</Accordion>
|
|
@@ -515,7 +751,7 @@ const Builder = ({ options, layout, mode }) => {
|
|
|
515
751
|
style={isLeftEnlarged ? { width: "13.4rem" } : {}}
|
|
516
752
|
>
|
|
517
753
|
<div className="card-header p-2 d-flex justify-content-between">
|
|
518
|
-
<div>Layers</div>
|
|
754
|
+
<div>{t("Layers")}</div>
|
|
519
755
|
<FontAwesomeIcon
|
|
520
756
|
icon={
|
|
521
757
|
isLeftEnlarged
|
|
@@ -526,12 +762,15 @@ const Builder = ({ options, layout, mode }) => {
|
|
|
526
762
|
"float-end fa-lg builder-expand-toggle-left"
|
|
527
763
|
}
|
|
528
764
|
onClick={() => setIsLeftEnlarged(!isLeftEnlarged)}
|
|
529
|
-
title={isLeftEnlarged ? "Shrink" : "Enlarge"}
|
|
765
|
+
title={isLeftEnlarged ? t("Shrink") : t("Enlarge")}
|
|
530
766
|
/>
|
|
531
767
|
</div>
|
|
532
768
|
{showLayers && (
|
|
533
769
|
<div className="card-body p-0 builder-layers">
|
|
534
|
-
<Layers
|
|
770
|
+
<Layers
|
|
771
|
+
expandRootOnLoad={true}
|
|
772
|
+
renderLayer={CustomLayerComponent}
|
|
773
|
+
/>
|
|
535
774
|
</div>
|
|
536
775
|
)}
|
|
537
776
|
</div>
|
|
@@ -544,34 +783,7 @@ const Builder = ({ options, layout, mode }) => {
|
|
|
544
783
|
}`}
|
|
545
784
|
>
|
|
546
785
|
<div>
|
|
547
|
-
<Frame
|
|
548
|
-
resolver={{
|
|
549
|
-
Text,
|
|
550
|
-
Empty,
|
|
551
|
-
Columns,
|
|
552
|
-
JoinField,
|
|
553
|
-
Field,
|
|
554
|
-
ViewLink,
|
|
555
|
-
Action,
|
|
556
|
-
HTMLCode,
|
|
557
|
-
LineBreak,
|
|
558
|
-
Aggregation,
|
|
559
|
-
Card,
|
|
560
|
-
Image,
|
|
561
|
-
Link,
|
|
562
|
-
View,
|
|
563
|
-
SearchBar,
|
|
564
|
-
Container,
|
|
565
|
-
Column,
|
|
566
|
-
DropDownFilter,
|
|
567
|
-
DropMenu,
|
|
568
|
-
Tabs,
|
|
569
|
-
Table,
|
|
570
|
-
ToggleFilter,
|
|
571
|
-
ListColumn,
|
|
572
|
-
ListColumns,
|
|
573
|
-
}}
|
|
574
|
-
>
|
|
786
|
+
<Frame>
|
|
575
787
|
{options.mode === "list" ? (
|
|
576
788
|
<Element canvas is={ListColumns}></Element>
|
|
577
789
|
) : (
|
|
@@ -602,14 +814,14 @@ const Builder = ({ options, layout, mode }) => {
|
|
|
602
814
|
"float-end me-2 mt-1 fa-lg builder-expand-toggle-right"
|
|
603
815
|
}
|
|
604
816
|
onClick={() => setIsEnlarged(!isEnlarged)}
|
|
605
|
-
title={isEnlarged ? "Shrink" : "Enlarge"}
|
|
817
|
+
title={isEnlarged ? t("Shrink") : t("Enlarge")}
|
|
606
818
|
/>
|
|
607
819
|
<div
|
|
608
820
|
className={` ${
|
|
609
821
|
savingState.error ? "d-block" : "d-none"
|
|
610
822
|
} my-2 fw-bold`}
|
|
611
823
|
>
|
|
612
|
-
your work is not being saved
|
|
824
|
+
{t("your work is not being saved")}
|
|
613
825
|
</div>
|
|
614
826
|
<SettingsPanel />
|
|
615
827
|
</div>
|