@saltcorn/builder 1.6.0-alpha.12 → 1.6.0-alpha.13
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 +4 -4
- package/package.json +2 -2
- package/src/components/Builder.js +121 -9
- package/src/components/Toolbox.js +78 -1
- package/src/components/elements/BoxModelEditor.js +24 -23
- package/src/components/elements/Card.js +26 -1
- package/src/components/elements/Columns.js +51 -10
- package/src/components/elements/Container.js +27 -5
- package/src/components/elements/Prompt.js +285 -0
- package/src/components/elements/Text.js +58 -14
- package/src/components/elements/utils.js +3 -1
- package/src/components/storage.js +24 -0
- package/src/utils/responsive_utils.js +139 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saltcorn/builder",
|
|
3
|
-
"version": "1.6.0-alpha.
|
|
3
|
+
"version": "1.6.0-alpha.13",
|
|
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",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"@fortawesome/free-solid-svg-icons": "5.15.2",
|
|
31
31
|
"@fortawesome/react-fontawesome": "0.1.14",
|
|
32
32
|
"@monaco-editor/react": "4.7.0",
|
|
33
|
-
"@saltcorn/common-code": "1.6.0-alpha.
|
|
33
|
+
"@saltcorn/common-code": "1.6.0-alpha.13",
|
|
34
34
|
"@tippyjs/react": "4.2.6",
|
|
35
35
|
"babel-jest": "^29.7.0",
|
|
36
36
|
"babel-loader": "9.2.1",
|
|
@@ -73,6 +73,7 @@ import { InitNewElement, Library, LibraryElem } from "./Library";
|
|
|
73
73
|
import { RenderNode } from "./RenderNode";
|
|
74
74
|
import { ListColumn } from "./elements/ListColumn";
|
|
75
75
|
import { ListColumns } from "./elements/ListColumns";
|
|
76
|
+
import { Prompt } from "./elements/Prompt";
|
|
76
77
|
import { recursivelyCloneToElems } from "./elements/Clone";
|
|
77
78
|
|
|
78
79
|
const { Provider } = optionsCtx;
|
|
@@ -353,6 +354,113 @@ const SettingsPanel = ({ isEnlarged, setIsEnlarged }) => {
|
|
|
353
354
|
);
|
|
354
355
|
};
|
|
355
356
|
|
|
357
|
+
const [generating, setGenerating] = useState(false);
|
|
358
|
+
const [generateError, setGenerateError] = useState(null);
|
|
359
|
+
|
|
360
|
+
// Find prompt nodes: check children of selected, or siblings if selected is a Prompt
|
|
361
|
+
const findPromptContext = () => {
|
|
362
|
+
if (!selected) return { promptNodes: [], targetParent: null };
|
|
363
|
+
const isSelectedPrompt = selected.displayName === "Prompt";
|
|
364
|
+
|
|
365
|
+
if (isSelectedPrompt && selected.parent) {
|
|
366
|
+
// Selected node is a Prompt — find all Prompt siblings in same parent
|
|
367
|
+
try {
|
|
368
|
+
const siblingIds = query.node(selected.parent).childNodes();
|
|
369
|
+
const promptIds = siblingIds.filter((id) => {
|
|
370
|
+
const n = query.node(id).get();
|
|
371
|
+
return n?.data?.displayName === "Prompt";
|
|
372
|
+
});
|
|
373
|
+
return { promptNodes: promptIds, targetParent: selected.parent };
|
|
374
|
+
} catch {
|
|
375
|
+
return { promptNodes: [], targetParent: null };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Selected node is a container — check its direct children
|
|
380
|
+
if (selected.children && selected.children.length > 0) {
|
|
381
|
+
const promptIds = selected.children.filter((id) => {
|
|
382
|
+
try {
|
|
383
|
+
const n = query.node(id).get();
|
|
384
|
+
return n?.data?.displayName === "Prompt";
|
|
385
|
+
} catch {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
if (promptIds.length > 0) {
|
|
390
|
+
return { promptNodes: promptIds, targetParent: selected.id };
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Check linked nodes (e.g. Card's inner Column)
|
|
395
|
+
try {
|
|
396
|
+
const nodeData = query.node(selected.id).get();
|
|
397
|
+
const linkedNodes = nodeData?.data?.linkedNodes;
|
|
398
|
+
if (linkedNodes) {
|
|
399
|
+
for (const linkedId of Object.values(linkedNodes)) {
|
|
400
|
+
const linkedChildIds = query.node(linkedId).childNodes();
|
|
401
|
+
const promptIds = linkedChildIds.filter((id) => {
|
|
402
|
+
try {
|
|
403
|
+
const n = query.node(id).get();
|
|
404
|
+
return n?.data?.displayName === "Prompt";
|
|
405
|
+
} catch {
|
|
406
|
+
return false;
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
if (promptIds.length > 0) {
|
|
410
|
+
return { promptNodes: promptIds, targetParent: linkedId };
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
} catch {
|
|
415
|
+
// ignore
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return { promptNodes: [], targetParent: null };
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const { promptNodes, targetParent } = selected
|
|
422
|
+
? findPromptContext()
|
|
423
|
+
: { promptNodes: [], targetParent: null };
|
|
424
|
+
const hasPromptNodes = promptNodes.length > 0;
|
|
425
|
+
|
|
426
|
+
const handleGenerate = async () => {
|
|
427
|
+
setGenerating(true);
|
|
428
|
+
setGenerateError(null);
|
|
429
|
+
try {
|
|
430
|
+
const prompts = promptNodes.map((childId) => {
|
|
431
|
+
const n = query.node(childId).get();
|
|
432
|
+
const { promptType, promptText } = n.data.props;
|
|
433
|
+
return `[${promptType}]: ${promptText}`;
|
|
434
|
+
});
|
|
435
|
+
const combinedPrompt = prompts.join("\n");
|
|
436
|
+
|
|
437
|
+
const res = await fetch("/viewedit/copilot-generate-layout", {
|
|
438
|
+
method: "POST",
|
|
439
|
+
headers: {
|
|
440
|
+
"Content-Type": "application/json",
|
|
441
|
+
"CSRF-Token": options.csrfToken,
|
|
442
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
443
|
+
},
|
|
444
|
+
body: JSON.stringify({
|
|
445
|
+
prompt: combinedPrompt,
|
|
446
|
+
mode: options.mode,
|
|
447
|
+
table: options.tableName,
|
|
448
|
+
}),
|
|
449
|
+
});
|
|
450
|
+
const data = await res.json();
|
|
451
|
+
if (data.error) {
|
|
452
|
+
setGenerateError(data.error);
|
|
453
|
+
} else if (data.layout) {
|
|
454
|
+
promptNodes.forEach((id) => actions.delete(id));
|
|
455
|
+
layoutToNodes(data.layout, query, actions, targetParent, options);
|
|
456
|
+
}
|
|
457
|
+
} catch (err) {
|
|
458
|
+
setGenerateError(err.message || "Generation failed");
|
|
459
|
+
} finally {
|
|
460
|
+
setGenerating(false);
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
|
|
356
464
|
return (
|
|
357
465
|
<div className="settings-panel card mt-1">
|
|
358
466
|
<div className="card-header px-2 py-1 d-flex justify-content-between align-items-center">
|
|
@@ -409,8 +517,9 @@ const SettingsPanel = ({ isEnlarged, setIsEnlarged }) => {
|
|
|
409
517
|
{t("Clone")}
|
|
410
518
|
</button>
|
|
411
519
|
)}
|
|
412
|
-
<
|
|
413
|
-
|
|
520
|
+
<div className="mt-2">
|
|
521
|
+
{selected.settings && React.createElement(selected.settings)}
|
|
522
|
+
</div>
|
|
414
523
|
</Fragment>
|
|
415
524
|
) : (
|
|
416
525
|
t("No element selected")
|
|
@@ -582,7 +691,7 @@ const AddColumnButton = () => {
|
|
|
582
691
|
const DEVICE_WIDTHS = {
|
|
583
692
|
desktop: null,
|
|
584
693
|
tablet: 768,
|
|
585
|
-
mobile:
|
|
694
|
+
mobile: 576,
|
|
586
695
|
};
|
|
587
696
|
|
|
588
697
|
const DevicePreviewToolbar = ({ previewDevice, setPreviewDevice }) => {
|
|
@@ -766,6 +875,7 @@ const Builder = ({ options, layout, mode }) => {
|
|
|
766
875
|
ListColumn,
|
|
767
876
|
ListColumns,
|
|
768
877
|
LibraryElem,
|
|
878
|
+
Prompt,
|
|
769
879
|
}}
|
|
770
880
|
>
|
|
771
881
|
<Provider value={options}>
|
|
@@ -852,10 +962,10 @@ const Builder = ({ options, layout, mode }) => {
|
|
|
852
962
|
<div className="device-preview-scroll-area">
|
|
853
963
|
<div
|
|
854
964
|
className={`device-preview-canvas-wrapper ${
|
|
855
|
-
previewDevice !== "desktop" ? "device-preview-constrained" : ""
|
|
965
|
+
previewDevice !== "desktop" && options.mode !== "list" ? "device-preview-constrained" : ""
|
|
856
966
|
}`}
|
|
857
967
|
style={{
|
|
858
|
-
maxWidth: DEVICE_WIDTHS[previewDevice]
|
|
968
|
+
maxWidth: options.mode !== "list" && DEVICE_WIDTHS[previewDevice]
|
|
859
969
|
? `${DEVICE_WIDTHS[previewDevice]}px`
|
|
860
970
|
: "none",
|
|
861
971
|
}}
|
|
@@ -873,10 +983,6 @@ const Builder = ({ options, layout, mode }) => {
|
|
|
873
983
|
</div>
|
|
874
984
|
<div className="col-sm-auto builder-sidebar">
|
|
875
985
|
<div style={{ width: isEnlarged ? "28rem" : "16rem" }}>
|
|
876
|
-
<DevicePreviewToolbar
|
|
877
|
-
previewDevice={previewDevice}
|
|
878
|
-
setPreviewDevice={setPreviewDevice}
|
|
879
|
-
/>
|
|
880
986
|
{document.getElementById("builder-header-actions") &&
|
|
881
987
|
createPortal(
|
|
882
988
|
<Fragment>
|
|
@@ -890,6 +996,12 @@ const Builder = ({ options, layout, mode }) => {
|
|
|
890
996
|
className={savingState.error ? "d-inline" : "d-none"}
|
|
891
997
|
/>
|
|
892
998
|
<HistoryPanel />
|
|
999
|
+
{options.mode !== "list" && (
|
|
1000
|
+
<DevicePreviewToolbar
|
|
1001
|
+
previewDevice={previewDevice}
|
|
1002
|
+
setPreviewDevice={setPreviewDevice}
|
|
1003
|
+
/>
|
|
1004
|
+
)}
|
|
893
1005
|
<NextButton layout={layout} />
|
|
894
1006
|
</Fragment>,
|
|
895
1007
|
document.getElementById("builder-header-actions")
|
|
@@ -29,6 +29,7 @@ import { View } from "./elements/View";
|
|
|
29
29
|
import { SearchBar } from "./elements/SearchBar";
|
|
30
30
|
import { Link } from "./elements/Link";
|
|
31
31
|
import { Page } from "./elements/Page";
|
|
32
|
+
import { Prompt } from "./elements/Prompt";
|
|
32
33
|
import optionsCtx from "./context";
|
|
33
34
|
import {
|
|
34
35
|
BoundingBox,
|
|
@@ -581,8 +582,64 @@ const TableElem = ({ connectors }) => {
|
|
|
581
582
|
);
|
|
582
583
|
};
|
|
583
584
|
|
|
585
|
+
const PromptContainerElem = ({ connectors }) => {
|
|
586
|
+
const { t } = useTranslation();
|
|
587
|
+
return (
|
|
588
|
+
<WrapElem
|
|
589
|
+
connectors={connectors}
|
|
590
|
+
icon="fas fa-robot"
|
|
591
|
+
title={t("Generate with AI")}
|
|
592
|
+
label={t("Generate")}
|
|
593
|
+
>
|
|
594
|
+
<Prompt promptType="container" promptText="" />
|
|
595
|
+
</WrapElem>
|
|
596
|
+
);
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const PromptViewElem = ({ connectors }) => {
|
|
600
|
+
const { t } = useTranslation();
|
|
601
|
+
return (
|
|
602
|
+
<WrapElem
|
|
603
|
+
connectors={connectors}
|
|
604
|
+
icon="fas fa-eye"
|
|
605
|
+
title={t("Prompt View")}
|
|
606
|
+
label={t("Prompt View")}
|
|
607
|
+
>
|
|
608
|
+
<Prompt promptType="view" promptText="" />
|
|
609
|
+
</WrapElem>
|
|
610
|
+
);
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
const PromptFieldElem = ({ connectors }) => {
|
|
614
|
+
const { t } = useTranslation();
|
|
615
|
+
return (
|
|
616
|
+
<WrapElem
|
|
617
|
+
connectors={connectors}
|
|
618
|
+
icon="fas fa-i-cursor"
|
|
619
|
+
title={t("Prompt Field")}
|
|
620
|
+
label={t("Prompt Field")}
|
|
621
|
+
>
|
|
622
|
+
<Prompt promptType="field" promptText="" />
|
|
623
|
+
</WrapElem>
|
|
624
|
+
);
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
const PromptActionElem = ({ connectors }) => {
|
|
628
|
+
const { t } = useTranslation();
|
|
629
|
+
return (
|
|
630
|
+
<WrapElem
|
|
631
|
+
connectors={connectors}
|
|
632
|
+
icon="fas fa-bolt"
|
|
633
|
+
title={t("Prompt Action")}
|
|
634
|
+
label={t("Prompt Action")}
|
|
635
|
+
>
|
|
636
|
+
<Prompt promptType="action" promptText="" />
|
|
637
|
+
</WrapElem>
|
|
638
|
+
);
|
|
639
|
+
};
|
|
640
|
+
|
|
584
641
|
const chunkToolBox = (elems, expanded) => {
|
|
585
|
-
const chunks = chunk(elems, expanded ? 3 : 2);
|
|
642
|
+
const chunks = chunk(elems.filter(Boolean), expanded ? 3 : 2);
|
|
586
643
|
return chunks.map((es, ix) => (
|
|
587
644
|
<div className="toolbar-row" key={ix}>
|
|
588
645
|
{es.map((e, j) => (
|
|
@@ -638,6 +695,10 @@ const ToolboxShow = ({ expanded }) => {
|
|
|
638
695
|
<DropMenuElem connectors={connectors} />,
|
|
639
696
|
<TableElem connectors={connectors} />,
|
|
640
697
|
<PageElem connectors={connectors} pages={pages} />,
|
|
698
|
+
options.has_copilot_generate && <PromptContainerElem connectors={connectors} />,
|
|
699
|
+
// <PromptViewElem connectors={connectors} />,
|
|
700
|
+
// <PromptFieldElem connectors={connectors} />,
|
|
701
|
+
// <PromptActionElem connectors={connectors} />,
|
|
641
702
|
],
|
|
642
703
|
expanded
|
|
643
704
|
);
|
|
@@ -696,6 +757,10 @@ const ToolboxList = ({ expanded }) => {
|
|
|
696
757
|
<DropMenuElem connectors={connectors} />
|
|
697
758
|
),
|
|
698
759
|
// <TableElem connectors={connectors} />,
|
|
760
|
+
options.has_copilot_generate && <PromptContainerElem connectors={connectors} />,
|
|
761
|
+
// <PromptViewElem connectors={connectors} />,
|
|
762
|
+
// <PromptFieldElem connectors={connectors} />,
|
|
763
|
+
// <PromptActionElem connectors={connectors} />,
|
|
699
764
|
options.allowMultipleElementsPerColumn &&
|
|
700
765
|
!disable_toolbox?.line_break && (
|
|
701
766
|
<LineBreakElem connectors={connectors} />
|
|
@@ -750,6 +815,10 @@ const ToolboxFilter = ({ expanded }) => {
|
|
|
750
815
|
<TableElem connectors={connectors} />,
|
|
751
816
|
<DropMenuElem connectors={connectors} />,
|
|
752
817
|
<PageElem connectors={connectors} pages={pages} />,
|
|
818
|
+
options.has_copilot_generate && <PromptContainerElem connectors={connectors} />,
|
|
819
|
+
// <PromptViewElem connectors={connectors} />,
|
|
820
|
+
// <PromptFieldElem connectors={connectors} />,
|
|
821
|
+
// <PromptActionElem connectors={connectors} />,
|
|
753
822
|
],
|
|
754
823
|
expanded
|
|
755
824
|
);
|
|
@@ -787,6 +856,10 @@ const ToolboxEdit = ({ expanded }) => {
|
|
|
787
856
|
<DropMenuElem connectors={connectors} />,
|
|
788
857
|
<TableElem connectors={connectors} />,
|
|
789
858
|
<ViewLinkElem connectors={connectors} options={options} />,
|
|
859
|
+
options.has_copilot_generate && <PromptContainerElem connectors={connectors} />,
|
|
860
|
+
// <PromptViewElem connectors={connectors} />,
|
|
861
|
+
// <PromptFieldElem connectors={connectors} />,
|
|
862
|
+
// <PromptActionElem connectors={connectors} />,
|
|
790
863
|
],
|
|
791
864
|
expanded
|
|
792
865
|
);
|
|
@@ -819,6 +892,10 @@ const ToolboxPage = ({ expanded }) => {
|
|
|
819
892
|
<DropMenuElem connectors={connectors} />,
|
|
820
893
|
<PageElem connectors={connectors} pages={pages} />,
|
|
821
894
|
<TableElem connectors={connectors} />,
|
|
895
|
+
options.has_copilot_generate && <PromptContainerElem connectors={connectors} />,
|
|
896
|
+
// <PromptViewElem connectors={connectors} />,
|
|
897
|
+
// <PromptFieldElem connectors={connectors} />,
|
|
898
|
+
// <PromptActionElem connectors={connectors} />,
|
|
822
899
|
],
|
|
823
900
|
expanded
|
|
824
901
|
);
|
|
@@ -7,6 +7,12 @@
|
|
|
7
7
|
import React, { useContext, Fragment, useState } from "react";
|
|
8
8
|
import useTranslation from "../../hooks/useTranslation";
|
|
9
9
|
import { SettingsRow, SettingsSectionHeaderRow, bstyleopt } from "./utils";
|
|
10
|
+
import PreviewCtx from "../preview_context";
|
|
11
|
+
import {
|
|
12
|
+
getDeviceSizeNode,
|
|
13
|
+
getDeviceSizeSetProp,
|
|
14
|
+
getDisplaySize,
|
|
15
|
+
} from "../../utils/responsive_utils";
|
|
10
16
|
/*
|
|
11
17
|
Contains code from https://github.com/tpaksu/boxmodel
|
|
12
18
|
Copyright (c) 2017 Taha Paksu
|
|
@@ -24,6 +30,7 @@ export /**
|
|
|
24
30
|
*/
|
|
25
31
|
const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
|
|
26
32
|
const { t } = useTranslation();
|
|
33
|
+
const { previewDevice } = useContext(PreviewCtx);
|
|
27
34
|
const [selectedCategory, setSelectedCategory] = useState(false);
|
|
28
35
|
const [selectedDirection, setSelectedDirection] = useState(false);
|
|
29
36
|
const selectedProperty = !selectedCategory
|
|
@@ -35,7 +42,13 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
|
|
|
35
42
|
setSelectedCategory(c);
|
|
36
43
|
setSelectedDirection(d);
|
|
37
44
|
};
|
|
38
|
-
|
|
45
|
+
const isDesktop = !previewDevice || previewDevice === "desktop";
|
|
46
|
+
const deviceLabel = previewDevice === "mobile"
|
|
47
|
+
? ` (${t("mobile")})`
|
|
48
|
+
: previewDevice === "tablet"
|
|
49
|
+
? ` (${t("tablet")})`
|
|
50
|
+
: "";
|
|
51
|
+
|
|
39
52
|
const style = node.style;
|
|
40
53
|
return (
|
|
41
54
|
<Fragment>
|
|
@@ -147,13 +160,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
|
|
|
147
160
|
autoComplete="off"
|
|
148
161
|
name="boxmodel-ex-1_width"
|
|
149
162
|
size="3"
|
|
150
|
-
value={
|
|
151
|
-
sizeWithStyle
|
|
152
|
-
? style["width"]
|
|
153
|
-
: node.width
|
|
154
|
-
? `${node.width}${node.widthUnit || "px"}`
|
|
155
|
-
: ""
|
|
156
|
-
}
|
|
163
|
+
value={getDisplaySize(node, "width", previewDevice, sizeWithStyle)}
|
|
157
164
|
/>
|
|
158
165
|
x
|
|
159
166
|
<input
|
|
@@ -162,13 +169,7 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
|
|
|
162
169
|
autoComplete="off"
|
|
163
170
|
name="boxmodel-ex-1_height"
|
|
164
171
|
size="3"
|
|
165
|
-
value={
|
|
166
|
-
sizeWithStyle
|
|
167
|
-
? style["height"]
|
|
168
|
-
: node.height
|
|
169
|
-
? `${node.height}${node.heightUnit || "px"}`
|
|
170
|
-
: ""
|
|
171
|
-
}
|
|
172
|
+
value={getDisplaySize(node, "height", previewDevice, sizeWithStyle)}
|
|
172
173
|
/>
|
|
173
174
|
</div>
|
|
174
175
|
<span
|
|
@@ -276,13 +277,13 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
|
|
|
276
277
|
<SettingsRow
|
|
277
278
|
field={{
|
|
278
279
|
name: "width",
|
|
279
|
-
label: t("width"),
|
|
280
|
+
label: t("width") + deviceLabel,
|
|
280
281
|
type: "DimUnits",
|
|
281
282
|
horiz: true,
|
|
282
283
|
}}
|
|
283
|
-
node={node}
|
|
284
|
-
setProp={setProp}
|
|
285
|
-
isStyle={!!sizeWithStyle}
|
|
284
|
+
node={isDesktop ? node : getDeviceSizeNode(node, "width", previewDevice)}
|
|
285
|
+
setProp={isDesktop ? setProp : getDeviceSizeSetProp(setProp, "width", previewDevice)}
|
|
286
|
+
isStyle={isDesktop ? !!sizeWithStyle : true}
|
|
286
287
|
/>
|
|
287
288
|
<SettingsRow
|
|
288
289
|
field={{
|
|
@@ -307,10 +308,10 @@ const BoxModelEditor = ({ setProp, node, sizeWithStyle }) => {
|
|
|
307
308
|
isStyle={true}
|
|
308
309
|
/>
|
|
309
310
|
<SettingsRow
|
|
310
|
-
field={{ name: "height", label: t("height"), type: "DimUnits" }}
|
|
311
|
-
node={node}
|
|
312
|
-
setProp={setProp}
|
|
313
|
-
isStyle={!!sizeWithStyle}
|
|
311
|
+
field={{ name: "height", label: t("height") + deviceLabel, type: "DimUnits" }}
|
|
312
|
+
node={isDesktop ? node : getDeviceSizeNode(node, "height", previewDevice)}
|
|
313
|
+
setProp={isDesktop ? setProp : getDeviceSizeSetProp(setProp, "height", previewDevice)}
|
|
314
|
+
isStyle={isDesktop ? !!sizeWithStyle : true}
|
|
314
315
|
/>
|
|
315
316
|
<SettingsRow
|
|
316
317
|
field={{
|
|
@@ -75,7 +75,27 @@ const Card = ({
|
|
|
75
75
|
const {
|
|
76
76
|
selected,
|
|
77
77
|
connectors: { connect, drag },
|
|
78
|
-
|
|
78
|
+
mobileWidth,
|
|
79
|
+
tabletWidth,
|
|
80
|
+
mobileHeight,
|
|
81
|
+
tabletHeight,
|
|
82
|
+
} = useNode((node) => ({
|
|
83
|
+
selected: node.events.selected,
|
|
84
|
+
_style: node.data.props.style,
|
|
85
|
+
mobileWidth: node.data.props.mobileWidth,
|
|
86
|
+
tabletWidth: node.data.props.tabletWidth,
|
|
87
|
+
mobileHeight: node.data.props.mobileHeight,
|
|
88
|
+
tabletHeight: node.data.props.tabletHeight,
|
|
89
|
+
}));
|
|
90
|
+
const { previewDevice } = useContext(previewCtx);
|
|
91
|
+
const deviceSizeOverrides = {};
|
|
92
|
+
if (previewDevice === "mobile") {
|
|
93
|
+
if (mobileWidth) deviceSizeOverrides.width = mobileWidth;
|
|
94
|
+
if (mobileHeight) deviceSizeOverrides.height = mobileHeight;
|
|
95
|
+
} else if (previewDevice === "tablet") {
|
|
96
|
+
if (tabletWidth) deviceSizeOverrides.width = tabletWidth;
|
|
97
|
+
if (tabletHeight) deviceSizeOverrides.height = tabletHeight;
|
|
98
|
+
}
|
|
79
99
|
|
|
80
100
|
return (
|
|
81
101
|
<div
|
|
@@ -105,6 +125,7 @@ const Card = ({
|
|
|
105
125
|
}deg, ${gradStartColor}, ${gradEndColor})`,
|
|
106
126
|
}
|
|
107
127
|
: {}),
|
|
128
|
+
...deviceSizeOverrides,
|
|
108
129
|
}}
|
|
109
130
|
ref={(dom) => connect(drag(dom))}
|
|
110
131
|
>
|
|
@@ -486,6 +507,10 @@ const fields = [
|
|
|
486
507
|
{ label: "Title Right", name: "titleRight", type: "Nodes", nodeID: "titleRight" },
|
|
487
508
|
{ label: "Footer", name: "footer", type: "Nodes", nodeID: "cardfooter" },
|
|
488
509
|
{ name: "style", default: {} },
|
|
510
|
+
{ name: "mobileWidth" },
|
|
511
|
+
{ name: "tabletWidth" },
|
|
512
|
+
{ name: "mobileHeight" },
|
|
513
|
+
{ name: "tabletHeight" },
|
|
489
514
|
{ label: "Class", name: "class", type: "String", canBeFormula: true },
|
|
490
515
|
{ name: "hAlign" },
|
|
491
516
|
{ name: "bgType" },
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from "./utils";
|
|
21
21
|
import { BoxModelEditor } from "./BoxModelEditor";
|
|
22
22
|
import { ArrayManager } from "./ArrayManager";
|
|
23
|
+
import { getAlignClass } from "../../utils/responsive_utils";
|
|
23
24
|
import {
|
|
24
25
|
AlignTop,
|
|
25
26
|
AlignMiddle,
|
|
@@ -81,7 +82,7 @@ const BREAKPOINT_MIN_WIDTH = {
|
|
|
81
82
|
const PREVIEW_DEVICE_WIDTH = {
|
|
82
83
|
desktop: Infinity,
|
|
83
84
|
tablet: 768,
|
|
84
|
-
mobile:
|
|
85
|
+
mobile: 576,
|
|
85
86
|
};
|
|
86
87
|
|
|
87
88
|
const getColClass = (width, breakpoint, previewDevice) => {
|
|
@@ -114,6 +115,8 @@ const Columns = ({
|
|
|
114
115
|
gx,
|
|
115
116
|
gy,
|
|
116
117
|
aligns,
|
|
118
|
+
mobileAligns,
|
|
119
|
+
tabletAligns,
|
|
117
120
|
vAligns,
|
|
118
121
|
colClasses,
|
|
119
122
|
colStyles,
|
|
@@ -123,15 +126,34 @@ const Columns = ({
|
|
|
123
126
|
const {
|
|
124
127
|
selected,
|
|
125
128
|
connectors: { connect, drag },
|
|
126
|
-
|
|
129
|
+
mobileWidth,
|
|
130
|
+
tabletWidth,
|
|
131
|
+
mobileHeight,
|
|
132
|
+
tabletHeight,
|
|
133
|
+
} = useNode((node) => ({
|
|
134
|
+
selected: node.events.selected,
|
|
135
|
+
_style: node.data.props.style,
|
|
136
|
+
mobileWidth: node.data.props.mobileWidth,
|
|
137
|
+
tabletWidth: node.data.props.tabletWidth,
|
|
138
|
+
mobileHeight: node.data.props.mobileHeight,
|
|
139
|
+
tabletHeight: node.data.props.tabletHeight,
|
|
140
|
+
}));
|
|
127
141
|
const { previewDevice } = useContext(PreviewCtx);
|
|
142
|
+
const canvasStyle = { ...reactifyStyles(style || {}) };
|
|
143
|
+
if (previewDevice === "mobile") {
|
|
144
|
+
if (mobileWidth) canvasStyle.width = mobileWidth;
|
|
145
|
+
if (mobileHeight) canvasStyle.height = mobileHeight;
|
|
146
|
+
} else if (previewDevice === "tablet") {
|
|
147
|
+
if (tabletWidth) canvasStyle.width = tabletWidth;
|
|
148
|
+
if (tabletHeight) canvasStyle.height = tabletHeight;
|
|
149
|
+
}
|
|
128
150
|
return (
|
|
129
151
|
<div
|
|
130
152
|
className={`row builder-columns ${customClass || ""} ${selected ? "selected-node" : ""} ${
|
|
131
153
|
typeof gx !== "undefined" && gx !== null ? `gx-${gx}` : ""
|
|
132
154
|
} ${typeof gy !== "undefined" && gy !== null ? `gy-${gy}` : ""}`}
|
|
133
155
|
ref={(dom) => connect(drag(dom))}
|
|
134
|
-
style={
|
|
156
|
+
style={canvasStyle}
|
|
135
157
|
>
|
|
136
158
|
{ntimes(ncols, (ix) => (
|
|
137
159
|
<div
|
|
@@ -140,8 +162,8 @@ const Columns = ({
|
|
|
140
162
|
getWidth(widths, ix),
|
|
141
163
|
breakpoints?.[ix],
|
|
142
164
|
previewDevice
|
|
143
|
-
)}
|
|
144
|
-
aligns
|
|
165
|
+
)} ${
|
|
166
|
+
getAlignClass(aligns, mobileAligns, tabletAligns, ix, previewDevice)
|
|
145
167
|
} align-items-${vAligns?.[ix]} ${colClasses?.[ix] || ""}`}
|
|
146
168
|
style={parseStyles(colStyles?.[ix] || "")}
|
|
147
169
|
>
|
|
@@ -162,16 +184,23 @@ export /**
|
|
|
162
184
|
*/
|
|
163
185
|
const ColumnsSettings = () => {
|
|
164
186
|
const { t } = useTranslation();
|
|
187
|
+
const { previewDevice } = useContext(PreviewCtx);
|
|
165
188
|
const node = useNode((node) => ({
|
|
166
189
|
widths: node.data.props.widths,
|
|
167
190
|
ncols: node.data.props.ncols,
|
|
168
191
|
breakpoints: node.data.props.breakpoints,
|
|
169
192
|
style: node.data.props.style,
|
|
193
|
+
mobileWidth: node.data.props.mobileWidth,
|
|
194
|
+
tabletWidth: node.data.props.tabletWidth,
|
|
195
|
+
mobileHeight: node.data.props.mobileHeight,
|
|
196
|
+
tabletHeight: node.data.props.tabletHeight,
|
|
170
197
|
setting_col_n: node.data.props.setting_col_n,
|
|
171
198
|
gx: node.data.props.gx,
|
|
172
199
|
gy: node.data.props.gy,
|
|
173
200
|
vAligns: node.data.props.vAligns,
|
|
174
201
|
aligns: node.data.props.aligns,
|
|
202
|
+
mobileAligns: node.data.props.mobileAligns,
|
|
203
|
+
tabletAligns: node.data.props.tabletAligns,
|
|
175
204
|
colClasses: node.data.props.colClasses,
|
|
176
205
|
colStyles: node.data.props.colStyles,
|
|
177
206
|
customClass: node.data.props.customClass,
|
|
@@ -186,14 +215,24 @@ const ColumnsSettings = () => {
|
|
|
186
215
|
setting_col_n,
|
|
187
216
|
vAligns,
|
|
188
217
|
aligns,
|
|
218
|
+
mobileAligns,
|
|
219
|
+
tabletAligns,
|
|
189
220
|
colClasses,
|
|
190
221
|
colStyles,
|
|
191
222
|
customClass,
|
|
192
223
|
currentSettingsTab,
|
|
193
224
|
} = node;
|
|
225
|
+
|
|
226
|
+
const activeAlignProp =
|
|
227
|
+
previewDevice === "mobile" ? "mobileAligns" :
|
|
228
|
+
previewDevice === "tablet" ? "tabletAligns" : "aligns";
|
|
229
|
+
const activeAligns =
|
|
230
|
+
previewDevice === "mobile" ? mobileAligns :
|
|
231
|
+
previewDevice === "tablet" ? tabletAligns : aligns;
|
|
232
|
+
|
|
194
233
|
const colSetsNode = {
|
|
195
234
|
vAlign: vAligns?.[setting_col_n],
|
|
196
|
-
hAlign:
|
|
235
|
+
hAlign: activeAligns?.[setting_col_n],
|
|
197
236
|
colClass: colClasses?.[setting_col_n] || "",
|
|
198
237
|
colStyle: colStyles?.[setting_col_n] || "",
|
|
199
238
|
};
|
|
@@ -208,7 +247,7 @@ const ColumnsSettings = () => {
|
|
|
208
247
|
setProp={setProp}
|
|
209
248
|
countProp={"ncols"}
|
|
210
249
|
currentProp={"setting_col_n"}
|
|
211
|
-
managedArrays={["widths", "breakpoints", "aligns", "vAligns", "colClasses", "colStyles"]}
|
|
250
|
+
managedArrays={["widths", "breakpoints", "aligns", "mobileAligns", "tabletAligns", "vAligns", "colClasses", "colStyles"]}
|
|
212
251
|
manageContents={true}
|
|
213
252
|
contentsKey={"besides"}
|
|
214
253
|
initialAddProps={{
|
|
@@ -293,7 +332,9 @@ const ColumnsSettings = () => {
|
|
|
293
332
|
<SettingsRow
|
|
294
333
|
field={{
|
|
295
334
|
name: "hAlign",
|
|
296
|
-
label:
|
|
335
|
+
label: previewDevice !== "desktop"
|
|
336
|
+
? `${t("Horizontal")} (${previewDevice})`
|
|
337
|
+
: t("Horizontal"),
|
|
297
338
|
type: "btn_select",
|
|
298
339
|
options: [
|
|
299
340
|
{ value: "start", title: t("Left"), label: <AlignStart /> },
|
|
@@ -305,8 +346,8 @@ const ColumnsSettings = () => {
|
|
|
305
346
|
setProp={setProp}
|
|
306
347
|
onChange={(k, v) =>
|
|
307
348
|
setProp((prop) => {
|
|
308
|
-
if (!prop
|
|
309
|
-
|
|
349
|
+
if (!prop[activeAlignProp]) prop[activeAlignProp] = [];
|
|
350
|
+
prop[activeAlignProp][setting_col_n] = v;
|
|
310
351
|
})
|
|
311
352
|
}
|
|
312
353
|
/>
|