@developer_tribe/react-builder 1.0.2 → 1.0.4
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/AttributesEditor.d.ts +3 -1
- package/dist/RenderPage.d.ts +2 -1
- package/dist/android.svg +43 -0
- package/dist/apple.svg +16 -0
- package/dist/attributes-editor/Field.d.ts +4 -2
- package/dist/attributes-editor/SizeField.d.ts +9 -0
- package/dist/attributes-editor/SpecialCategorySection.d.ts +2 -1
- package/dist/build-components/BackgroundImage/BackgroundImage.d.ts +5 -0
- package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +45 -0
- package/dist/build-components/Button/ButtonProps.generated.d.ts +8 -0
- package/dist/build-components/Carousel/CarouselProps.generated.d.ts +8 -0
- package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +8 -0
- package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +8 -0
- package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +8 -0
- package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +8 -0
- package/dist/build-components/Image/ImageProps.generated.d.ts +8 -0
- package/dist/build-components/Onboard/OnboardProps.generated.d.ts +8 -0
- package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +8 -1
- package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +8 -0
- package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +9 -3
- package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +8 -0
- package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +9 -1
- package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +8 -0
- package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +8 -1
- package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +8 -0
- package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +8 -0
- package/dist/build-components/Text/TextProps.generated.d.ts +8 -0
- package/dist/build-components/View/ViewProps.generated.d.ts +8 -0
- package/dist/build-components/index.d.ts +2 -1
- package/dist/build-components/patterns.generated.d.ts +1612 -46
- package/dist/components/AttributesEditorPanel.d.ts +3 -4
- package/dist/components/Builder.d.ts +2 -1
- package/dist/components/BuilderButton.d.ts +9 -0
- package/dist/components/JsonTextEditor.d.ts +9 -0
- package/dist/index.cjs.js +5 -5
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.esm.js +5 -5
- package/dist/index.esm.js.map +1 -1
- package/dist/modals/ColorModal.d.ts +3 -1
- package/dist/pages/ProjectPage.d.ts +3 -3
- package/dist/pages/tabs/BuilderPanel.d.ts +8 -0
- package/dist/pages/tabs/SideTool.d.ts +8 -0
- package/dist/store.d.ts +9 -1
- package/dist/styles.css +1 -1
- package/dist/types/Project.d.ts +11 -0
- package/dist/utils/analyseNode.d.ts +1 -0
- package/dist/utils/extractImageStyle.d.ts +2 -1
- package/dist/utils/extractTextStyle.d.ts +8 -1
- package/dist/utils/extractViewStyle.d.ts +7 -1
- package/dist/utils/parseColor.d.ts +7 -0
- package/dist/utils/selection.d.ts +7 -0
- package/dist/utils/useMergedStyle.d.ts +2 -0
- package/package.json +2 -5
- package/src/.DS_Store +0 -0
- package/src/AttributesEditor.tsx +83 -16
- package/src/RenderPage.tsx +86 -4
- package/src/attributes-editor/Field.tsx +60 -165
- package/src/attributes-editor/SizeField.tsx +184 -0
- package/src/attributes-editor/SpecialCategorySection.tsx +12 -4
- package/src/build-components/BackgroundImage/BackgroundImage.tsx +77 -0
- package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +61 -0
- package/src/build-components/BackgroundImage/pattern.json +45 -0
- package/src/build-components/Button/Button.tsx +29 -4
- package/src/build-components/Button/ButtonProps.generated.ts +8 -0
- package/src/build-components/Carousel/Carousel.tsx +25 -3
- package/src/build-components/Carousel/CarouselProps.generated.ts +8 -0
- package/src/build-components/CarouselButtons/CarouselButtons.tsx +19 -4
- package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +8 -0
- package/src/build-components/CarouselDots/CarouselDots.tsx +13 -4
- package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +8 -0
- package/src/build-components/CarouselItem/CarouselItem.tsx +20 -4
- package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +8 -0
- package/src/build-components/CarouselProvider/CarouselProvider.tsx +14 -3
- package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +8 -0
- package/src/build-components/Image/Image.tsx +27 -9
- package/src/build-components/Image/ImageProps.generated.ts +8 -0
- package/src/build-components/Image/pattern.json +1 -9
- package/src/build-components/Onboard/Onboard.tsx +2 -2
- package/src/build-components/Onboard/OnboardProps.generated.ts +8 -0
- package/src/build-components/OnboardButton/OnboardButton.tsx +11 -7
- package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +8 -1
- package/src/build-components/OnboardButtons/OnboardButtons.tsx +17 -5
- package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +8 -0
- package/src/build-components/OnboardDot/OnboardDot.tsx +68 -39
- package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +9 -3
- package/src/build-components/OnboardDot/pattern.json +3 -19
- package/src/build-components/OnboardFooter/OnboardFooter.tsx +37 -14
- package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +8 -0
- package/src/build-components/OnboardImage/OnboardImage.tsx +28 -6
- package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +9 -1
- package/src/build-components/OnboardItem/OnboardItem.tsx +15 -14
- package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +8 -0
- package/src/build-components/OnboardProvider/OnboardProvider.tsx +35 -20
- package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +8 -1
- package/src/build-components/OnboardProvider/pattern.json +0 -8
- package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +8 -0
- package/src/build-components/OnboardSubtitle/pattern.json +1 -1
- package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +8 -0
- package/src/build-components/OnboardTitle/pattern.json +1 -1
- package/src/build-components/RenderNode.generated.tsx +3 -0
- package/src/build-components/Text/Text.tsx +28 -10
- package/src/build-components/Text/TextProps.generated.ts +8 -0
- package/src/build-components/View/View.tsx +25 -3
- package/src/build-components/View/ViewProps.generated.ts +8 -0
- package/src/build-components/View/pattern.json +67 -1
- package/src/build-components/index.ts +5 -0
- package/src/build-components/patterns.generated.ts +1620 -46
- package/src/components/AttributesEditorPanel.tsx +13 -6
- package/src/components/Builder.tsx +200 -56
- package/src/components/BuilderButton.tsx +127 -0
- package/src/components/DeviceNavigationBar.tsx +0 -1
- package/src/components/EditorHeader.tsx +11 -1
- package/src/components/JsonTextEditor.tsx +185 -0
- package/src/index.ts +2 -2
- package/src/mockOS/components/MockOSRouter.tsx +17 -3
- package/src/mockOS/context/MockOSContext.tsx +0 -5
- package/src/mockOS/managers/mockPermissionManager.ts +0 -4
- package/src/mockOS/managers/navigationManager.ts +1 -6
- package/src/modals/ColorModal.tsx +306 -71
- package/src/modals/LocalicationModal.tsx +4 -5
- package/src/modals/Modal.tsx +8 -1
- package/src/pages/ProjectPage.tsx +299 -55
- package/src/pages/tabs/{BuilderTab.tsx → BuilderPanel.tsx} +13 -9
- package/src/pages/tabs/SideTool.tsx +260 -0
- package/src/size-matters/index.ts +6 -0
- package/src/store.ts +18 -1
- package/src/styles/base/_global.scss +163 -7
- package/src/styles/components/_attributes-editor.scss +12 -0
- package/src/styles/components/_editor-shell.scss +25 -0
- package/src/styles/foundation/_sizes.scss +1 -1
- package/src/styles/layout/_builder.scss +66 -10
- package/src/styles/modals/_color-modal.scss +59 -1
- package/src/styles/utilities/_carousel.scss +9 -8
- package/src/types/Project.ts +14 -0
- package/src/utils/analyseNode.ts +98 -0
- package/src/utils/extractImageStyle.ts +3 -6
- package/src/utils/extractTextStyle.ts +19 -82
- package/src/utils/extractViewStyle.ts +41 -12
- package/src/utils/parseColor.ts +43 -0
- package/src/utils/selection.ts +24 -0
- package/src/utils/useMergedStyle.ts +16 -0
- package/dist/pages/tabs/BuilderTab.d.ts +0 -9
- package/dist/pages/tabs/DebugTab.d.ts +0 -7
- package/dist/pages/tabs/PreviewTab.d.ts +0 -3
- package/src/pages/tabs/DebugTab.tsx +0 -64
- package/src/pages/tabs/PreviewTab.tsx +0 -206
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
import { AttributesEditor, Node, NodeData } from '..';
|
|
1
|
+
import { AttributesEditor, Node, NodeData, ProjectColors } from '..';
|
|
2
2
|
import { useLogRender } from '../utils/useLogRender';
|
|
3
|
+
import { useRenderStore } from '../store';
|
|
3
4
|
|
|
4
5
|
interface AttributesEditorPanelProps {
|
|
5
|
-
current: Node;
|
|
6
6
|
attributes: any;
|
|
7
7
|
onChange: (data: Node) => void;
|
|
8
|
-
|
|
8
|
+
projectColors?: ProjectColors;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function AttributesEditorPanel({
|
|
12
|
-
current,
|
|
13
12
|
attributes,
|
|
14
13
|
onChange,
|
|
15
|
-
|
|
14
|
+
projectColors,
|
|
16
15
|
}: AttributesEditorPanelProps) {
|
|
17
16
|
useLogRender('AttributesEditorPanel');
|
|
17
|
+
const { current, setCurrent } = useRenderStore((s) => ({
|
|
18
|
+
current: s.current,
|
|
19
|
+
setCurrent: s.setCurrent,
|
|
20
|
+
}));
|
|
18
21
|
if (!current) return null;
|
|
19
22
|
|
|
20
23
|
function replaceNode(root: Node, target: Node, next: Node): Node {
|
|
@@ -52,7 +55,11 @@ export function AttributesEditorPanel({
|
|
|
52
55
|
|
|
53
56
|
return (
|
|
54
57
|
<div style={{ padding: 12 }}>
|
|
55
|
-
<AttributesEditor
|
|
58
|
+
<AttributesEditor
|
|
59
|
+
node={current}
|
|
60
|
+
onChange={handleAttributesChange}
|
|
61
|
+
projectColors={projectColors}
|
|
62
|
+
/>
|
|
56
63
|
</div>
|
|
57
64
|
);
|
|
58
65
|
}
|
|
@@ -12,56 +12,35 @@ import { Breadcrumb } from './Breadcrumb';
|
|
|
12
12
|
import { useLogRender } from '../utils/useLogRender';
|
|
13
13
|
import { getDefaultsForType, getPatternByType } from '../utils/patterns';
|
|
14
14
|
import { AddComponentModal } from '../modals/AddComponentModal';
|
|
15
|
+
import { BuilderButton } from './BuilderButton';
|
|
15
16
|
|
|
16
17
|
type BuilderEditorProps = {
|
|
17
18
|
data: Node;
|
|
18
19
|
setData: (data: Node) => void;
|
|
19
20
|
current: Node;
|
|
20
21
|
setCurrent: (current: Node) => void;
|
|
22
|
+
onDeleteNode: (node: Node) => void;
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
interface BuilderEditorComponentProps {
|
|
24
26
|
node: Node;
|
|
25
27
|
onClick: (node: Node) => void;
|
|
26
28
|
onAdd?: () => void;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return <div className="builder__placeholder">Null or undefined</div>;
|
|
32
|
-
}
|
|
33
|
-
if (isNodeString(node)) {
|
|
34
|
-
return <div className="builder__text">{node as string}</div>;
|
|
35
|
-
}
|
|
36
|
-
const nodeData = node as NodeData<NodeDefaultAttribute>;
|
|
37
|
-
|
|
38
|
-
let extra = '';
|
|
39
|
-
if (nodeData.attributes?.condition) {
|
|
40
|
-
extra = ` (${nodeData.attributes.condition} ${nodeData.attributes.conditionVariable})`;
|
|
41
|
-
}
|
|
42
|
-
const patternLabel = getPatternByType(nodeData.type)?.meta?.label?.trim();
|
|
43
|
-
const baseLabel =
|
|
44
|
-
patternLabel && patternLabel.length > 0 ? patternLabel : nodeData.type;
|
|
45
|
-
const conditionLabel = extra.trim() ? extra : '';
|
|
46
|
-
const fullLabel = `${baseLabel}${conditionLabel}`.trim();
|
|
47
|
-
return (
|
|
48
|
-
<a onClick={onClick} className="builder__button">
|
|
49
|
-
<span className="builder__button-label">{baseLabel}</span> <br />
|
|
50
|
-
{conditionLabel && (
|
|
51
|
-
<span className="builder__button-condition">{conditionLabel}</span>
|
|
52
|
-
)}
|
|
53
|
-
</a>
|
|
54
|
-
);
|
|
29
|
+
onDelete?: (node: Node) => void;
|
|
30
|
+
onReorder?: (prev: Node[], next: Node[]) => void;
|
|
31
|
+
onMoveChildUp?: (parent: Node, childIndex: number) => void;
|
|
32
|
+
onMoveChildDown?: (parent: Node, childIndex: number) => void;
|
|
55
33
|
}
|
|
56
34
|
|
|
57
35
|
function BuilderComponent({
|
|
58
36
|
node,
|
|
59
37
|
onClick,
|
|
60
38
|
onAdd,
|
|
39
|
+
onDelete,
|
|
40
|
+
onReorder,
|
|
41
|
+
onMoveChildUp,
|
|
42
|
+
onMoveChildDown,
|
|
61
43
|
}: BuilderEditorComponentProps) {
|
|
62
|
-
if (isNodeNullOrUndefined(node)) {
|
|
63
|
-
return <div className="builder__placeholder">Null or undefined</div>;
|
|
64
|
-
}
|
|
65
44
|
if (isNodeString(node)) {
|
|
66
45
|
return (
|
|
67
46
|
<div className="builder__text">
|
|
@@ -84,16 +63,38 @@ function BuilderComponent({
|
|
|
84
63
|
);
|
|
85
64
|
|
|
86
65
|
if (isNodeArray(node)) {
|
|
66
|
+
const list = node as Node[];
|
|
67
|
+
|
|
68
|
+
const moveItem = (index: number, direction: -1 | 1) => {
|
|
69
|
+
if (!onReorder) return;
|
|
70
|
+
const targetIndex = index + direction;
|
|
71
|
+
if (targetIndex < 0 || targetIndex >= list.length) return;
|
|
72
|
+
const updated = [...list];
|
|
73
|
+
const [moved] = updated.splice(index, 1);
|
|
74
|
+
updated.splice(targetIndex, 0, moved);
|
|
75
|
+
onReorder(list, updated);
|
|
76
|
+
};
|
|
77
|
+
|
|
87
78
|
return (
|
|
88
79
|
<div className="builder__list">
|
|
89
|
-
{
|
|
90
|
-
<
|
|
91
|
-
|
|
92
|
-
onClick(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
80
|
+
{list.map((item, index) => (
|
|
81
|
+
<div key={index} className="builder__list-item">
|
|
82
|
+
<BuilderButton
|
|
83
|
+
onClick={() => {
|
|
84
|
+
onClick(item);
|
|
85
|
+
}}
|
|
86
|
+
node={item}
|
|
87
|
+
onDelete={onDelete}
|
|
88
|
+
onMoveUp={
|
|
89
|
+
onReorder && index > 0 ? () => moveItem(index, -1) : undefined
|
|
90
|
+
}
|
|
91
|
+
onMoveDown={
|
|
92
|
+
onReorder && index < list.length - 1
|
|
93
|
+
? () => moveItem(index, 1)
|
|
94
|
+
: undefined
|
|
95
|
+
}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
97
98
|
))}
|
|
98
99
|
{addButton}
|
|
99
100
|
</div>
|
|
@@ -101,15 +102,24 @@ function BuilderComponent({
|
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
const nodeData = node as NodeData<NodeDefaultAttribute>;
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
const rawChildren = nodeData?.children;
|
|
106
|
+
const hasArrayChildren = isNodeArray(rawChildren);
|
|
107
|
+
const children = rawChildren
|
|
108
|
+
? hasArrayChildren
|
|
109
|
+
? (rawChildren as Node[])
|
|
110
|
+
: [rawChildren]
|
|
108
111
|
: null;
|
|
109
112
|
|
|
110
113
|
return (
|
|
111
114
|
<div className="builder__node">
|
|
112
115
|
<div className="builder__children">
|
|
116
|
+
<BuilderButton
|
|
117
|
+
onClick={() => {
|
|
118
|
+
onClick(node);
|
|
119
|
+
}}
|
|
120
|
+
node={node}
|
|
121
|
+
onDelete={onDelete}
|
|
122
|
+
/>
|
|
113
123
|
{children &&
|
|
114
124
|
children.map((child, index) => (
|
|
115
125
|
<BuilderButton
|
|
@@ -118,6 +128,19 @@ function BuilderComponent({
|
|
|
118
128
|
}}
|
|
119
129
|
key={index}
|
|
120
130
|
node={child}
|
|
131
|
+
onDelete={onDelete}
|
|
132
|
+
onMoveUp={
|
|
133
|
+
onMoveChildUp && hasArrayChildren && index > 0
|
|
134
|
+
? () => onMoveChildUp(node, index)
|
|
135
|
+
: undefined
|
|
136
|
+
}
|
|
137
|
+
onMoveDown={
|
|
138
|
+
onMoveChildDown &&
|
|
139
|
+
hasArrayChildren &&
|
|
140
|
+
index < (children as Node[]).length - 1
|
|
141
|
+
? () => onMoveChildDown(node, index)
|
|
142
|
+
: undefined
|
|
143
|
+
}
|
|
121
144
|
/>
|
|
122
145
|
))}
|
|
123
146
|
</div>
|
|
@@ -131,6 +154,7 @@ export function Builder({
|
|
|
131
154
|
setData,
|
|
132
155
|
current,
|
|
133
156
|
setCurrent,
|
|
157
|
+
onDeleteNode,
|
|
134
158
|
}: BuilderEditorProps) {
|
|
135
159
|
useLogRender('Builder');
|
|
136
160
|
const [isAddModalOpen, setIsAddModalOpen] = useState(false);
|
|
@@ -168,16 +192,55 @@ export function Builder({
|
|
|
168
192
|
|
|
169
193
|
const handleAddChild = useCallback(
|
|
170
194
|
(type: string) => {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
195
|
+
const nextChild = createDefaultNode(type);
|
|
196
|
+
|
|
197
|
+
// Root (or selection) can be empty/null-ish: allow creating the first node.
|
|
198
|
+
if (isNodeNullOrUndefined(current)) {
|
|
199
|
+
// If the project itself is empty (or a placeholder string), replace it with the first node.
|
|
200
|
+
if (isNodeNullOrUndefined(data) || isNodeString(data)) {
|
|
201
|
+
setData(nextChild);
|
|
202
|
+
setCurrent(nextChild);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// If the project root is a list, append into it.
|
|
207
|
+
if (Array.isArray(data)) {
|
|
208
|
+
const nextList = [...data, nextChild];
|
|
209
|
+
setData(nextList);
|
|
210
|
+
setCurrent(nextList);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Otherwise default to adding into the root node.
|
|
215
|
+
const parent = data as NodeData<NodeDefaultAttribute>;
|
|
216
|
+
const updatedParent: NodeData<NodeDefaultAttribute> = {
|
|
217
|
+
...parent,
|
|
218
|
+
children: appendChild(parent.children, nextChild),
|
|
219
|
+
};
|
|
220
|
+
setData(updatedParent);
|
|
221
|
+
setCurrent(updatedParent);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// If the root is a list, allow adding to it directly.
|
|
226
|
+
if (isNodeArray(current)) {
|
|
227
|
+
const nextList = [...(current as Node[]), nextChild];
|
|
228
|
+
const updatedRoot = replaceNode(data, current, nextList);
|
|
229
|
+
setData(updatedRoot);
|
|
230
|
+
setCurrent(nextList);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// If the root is a placeholder string, allow replacing it with the first node.
|
|
235
|
+
if (isNodeString(current)) {
|
|
236
|
+
if (current === data) {
|
|
237
|
+
setData(nextChild);
|
|
238
|
+
setCurrent(nextChild);
|
|
239
|
+
}
|
|
176
240
|
return;
|
|
177
241
|
}
|
|
178
242
|
|
|
179
243
|
const parent = current as NodeData<NodeDefaultAttribute>;
|
|
180
|
-
const nextChild = createDefaultNode(type);
|
|
181
244
|
const updatedParent: NodeData<NodeDefaultAttribute> = {
|
|
182
245
|
...parent,
|
|
183
246
|
children: appendChild(parent.children, nextChild),
|
|
@@ -203,7 +266,11 @@ export function Builder({
|
|
|
203
266
|
}
|
|
204
267
|
return (current as NodeData<NodeDefaultAttribute>).type ?? null;
|
|
205
268
|
}, [current]);
|
|
206
|
-
const canAddChild =
|
|
269
|
+
const canAddChild =
|
|
270
|
+
allowedChildTypes.length > 0 ||
|
|
271
|
+
data === undefined ||
|
|
272
|
+
data === null ||
|
|
273
|
+
(Array.isArray(data) && data.length === 0);
|
|
207
274
|
|
|
208
275
|
const handleOpenAddModal = useCallback(() => {
|
|
209
276
|
if (!canAddChild) return;
|
|
@@ -222,6 +289,75 @@ export function Builder({
|
|
|
222
289
|
[handleAddChild],
|
|
223
290
|
);
|
|
224
291
|
|
|
292
|
+
const handleReorder = useCallback(
|
|
293
|
+
(prev: Node[], next: Node[]) => {
|
|
294
|
+
const updatedRoot = replaceNode(data, prev, next);
|
|
295
|
+
setData(updatedRoot);
|
|
296
|
+
if (current === prev) {
|
|
297
|
+
setCurrent(next);
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
[current, data, setCurrent, setData],
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
const handleMoveChildUp = useCallback(
|
|
304
|
+
(parentNode: Node, childIndex: number) => {
|
|
305
|
+
if (
|
|
306
|
+
isNodeNullOrUndefined(parentNode) ||
|
|
307
|
+
isNodeString(parentNode) ||
|
|
308
|
+
isNodeArray(parentNode)
|
|
309
|
+
) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const parentData = parentNode as NodeData<NodeDefaultAttribute>;
|
|
313
|
+
const children = parentData.children;
|
|
314
|
+
if (!Array.isArray(children)) return;
|
|
315
|
+
if (childIndex <= 0 || childIndex >= children.length) return;
|
|
316
|
+
const updatedChildren = [...children];
|
|
317
|
+
const [movedChild] = updatedChildren.splice(childIndex, 1);
|
|
318
|
+
updatedChildren.splice(childIndex - 1, 0, movedChild);
|
|
319
|
+
const updatedParent: NodeData<NodeDefaultAttribute> = {
|
|
320
|
+
...parentData,
|
|
321
|
+
children: updatedChildren,
|
|
322
|
+
};
|
|
323
|
+
const updatedRoot = replaceNode(data, parentNode, updatedParent);
|
|
324
|
+
setData(updatedRoot);
|
|
325
|
+
if (current === parentNode) {
|
|
326
|
+
setCurrent(updatedParent);
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
[current, data, setCurrent, setData],
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const handleMoveChildDown = useCallback(
|
|
333
|
+
(parentNode: Node, childIndex: number) => {
|
|
334
|
+
if (
|
|
335
|
+
isNodeNullOrUndefined(parentNode) ||
|
|
336
|
+
isNodeString(parentNode) ||
|
|
337
|
+
isNodeArray(parentNode)
|
|
338
|
+
) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const parentData = parentNode as NodeData<NodeDefaultAttribute>;
|
|
342
|
+
const children = parentData.children;
|
|
343
|
+
if (!Array.isArray(children)) return;
|
|
344
|
+
if (childIndex < 0 || childIndex >= children.length - 1) return;
|
|
345
|
+
const updatedChildren = [...children];
|
|
346
|
+
const [movedChild] = updatedChildren.splice(childIndex, 1);
|
|
347
|
+
updatedChildren.splice(childIndex + 1, 0, movedChild);
|
|
348
|
+
const updatedParent: NodeData<NodeDefaultAttribute> = {
|
|
349
|
+
...parentData,
|
|
350
|
+
children: updatedChildren,
|
|
351
|
+
};
|
|
352
|
+
const updatedRoot = replaceNode(data, parentNode, updatedParent);
|
|
353
|
+
setData(updatedRoot);
|
|
354
|
+
if (current === parentNode) {
|
|
355
|
+
setCurrent(updatedParent);
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
[current, data, setCurrent, setData],
|
|
359
|
+
);
|
|
360
|
+
|
|
225
361
|
function replaceNode(root: Node, target: Node, next: Node): Node {
|
|
226
362
|
if (root === target) return next;
|
|
227
363
|
if (root === null || root === undefined) return root;
|
|
@@ -277,12 +413,16 @@ export function Builder({
|
|
|
277
413
|
}
|
|
278
414
|
|
|
279
415
|
function getAllowedChildTypes(parent: Node): string[] {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
416
|
+
// Treat non-node containers (root empty, root arrays) as "View-like" containers:
|
|
417
|
+
// allow inserting any component type as a child.
|
|
418
|
+
if (isNodeNullOrUndefined(parent)) return [...allcomponentNames];
|
|
419
|
+
if (isNodeArray(parent) && (parent as Node[]).length === 0)
|
|
420
|
+
return [...allcomponentNames];
|
|
421
|
+
if (isNodeString(parent)) {
|
|
422
|
+
// Only allow adding when the string is the root placeholder.
|
|
423
|
+
return parent === data ? [...allcomponentNames] : [];
|
|
424
|
+
}
|
|
425
|
+
|
|
286
426
|
const parentData = parent as NodeData;
|
|
287
427
|
const parentType = parentData.type;
|
|
288
428
|
// Special rule: limit OnboardButtons to OnboardButton only
|
|
@@ -312,6 +452,10 @@ export function Builder({
|
|
|
312
452
|
<BuilderComponent
|
|
313
453
|
onClick={handleNodeSelect}
|
|
314
454
|
onAdd={canAddChild ? handleOpenAddModal : undefined}
|
|
455
|
+
onDelete={onDeleteNode}
|
|
456
|
+
onReorder={handleReorder}
|
|
457
|
+
onMoveChildUp={handleMoveChildUp}
|
|
458
|
+
onMoveChildDown={handleMoveChildDown}
|
|
315
459
|
node={current}
|
|
316
460
|
/>
|
|
317
461
|
{isAddModalOpen && (
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
isNodeNullOrUndefined,
|
|
4
|
+
isNodeString,
|
|
5
|
+
Node,
|
|
6
|
+
NodeData,
|
|
7
|
+
NodeDefaultAttribute,
|
|
8
|
+
} from '..';
|
|
9
|
+
import { getPatternByType } from '../utils/patterns';
|
|
10
|
+
|
|
11
|
+
export type BuilderButtonProps = {
|
|
12
|
+
node: Node;
|
|
13
|
+
onClick: () => void;
|
|
14
|
+
onDelete?: (node: Node) => void;
|
|
15
|
+
onMoveUp?: () => void;
|
|
16
|
+
onMoveDown?: () => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function BuilderButton({
|
|
20
|
+
node,
|
|
21
|
+
onClick,
|
|
22
|
+
onDelete,
|
|
23
|
+
onMoveUp,
|
|
24
|
+
onMoveDown,
|
|
25
|
+
}: BuilderButtonProps) {
|
|
26
|
+
if (isNodeNullOrUndefined(node)) {
|
|
27
|
+
return <div className="builder__placeholder">Null or undefined</div>;
|
|
28
|
+
}
|
|
29
|
+
if (isNodeString(node)) {
|
|
30
|
+
return <div className="builder__text">{node as string}</div>;
|
|
31
|
+
}
|
|
32
|
+
const nodeData = node as NodeData<NodeDefaultAttribute>;
|
|
33
|
+
|
|
34
|
+
const longPressTimeoutRef = useRef<number | null>(null);
|
|
35
|
+
const longPressTriggeredRef = useRef(false);
|
|
36
|
+
|
|
37
|
+
const handleDelete = () => {
|
|
38
|
+
if (onDelete) {
|
|
39
|
+
onDelete(node);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const clearLongPress = () => {
|
|
44
|
+
if (longPressTimeoutRef.current !== null) {
|
|
45
|
+
window.clearTimeout(longPressTimeoutRef.current);
|
|
46
|
+
longPressTimeoutRef.current = null;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handlePressStart = () => {
|
|
51
|
+
longPressTriggeredRef.current = false;
|
|
52
|
+
longPressTimeoutRef.current = window.setTimeout(() => {
|
|
53
|
+
longPressTriggeredRef.current = true;
|
|
54
|
+
const shouldDelete = window.confirm('Do you want to delete');
|
|
55
|
+
if (shouldDelete) {
|
|
56
|
+
handleDelete();
|
|
57
|
+
}
|
|
58
|
+
}, 600);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const handlePressEnd = () => {
|
|
62
|
+
if (!longPressTriggeredRef.current) {
|
|
63
|
+
onClick();
|
|
64
|
+
}
|
|
65
|
+
clearLongPress();
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const handlePressCancel = () => {
|
|
69
|
+
clearLongPress();
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
let extra = '';
|
|
73
|
+
if (nodeData.attributes?.condition) {
|
|
74
|
+
extra = ` (${nodeData.attributes.condition} ${nodeData.attributes.conditionVariable})`;
|
|
75
|
+
}
|
|
76
|
+
const patternLabel = getPatternByType(nodeData.type)?.meta?.label?.trim();
|
|
77
|
+
const baseLabel =
|
|
78
|
+
patternLabel && patternLabel.length > 0 ? patternLabel : nodeData.type;
|
|
79
|
+
const conditionLabel = extra.trim() ? extra : '';
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div className="builder__button">
|
|
83
|
+
{(onMoveUp || onMoveDown) && (
|
|
84
|
+
<div className="builder__sort-controls">
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
className="builder__sort-button"
|
|
88
|
+
onClick={(event) => {
|
|
89
|
+
event.stopPropagation();
|
|
90
|
+
onMoveUp?.();
|
|
91
|
+
}}
|
|
92
|
+
disabled={!onMoveUp}
|
|
93
|
+
aria-label="Move up"
|
|
94
|
+
>
|
|
95
|
+
↑
|
|
96
|
+
</button>
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
className="builder__sort-button"
|
|
100
|
+
onClick={(event) => {
|
|
101
|
+
event.stopPropagation();
|
|
102
|
+
onMoveDown?.();
|
|
103
|
+
}}
|
|
104
|
+
disabled={!onMoveDown}
|
|
105
|
+
aria-label="Move down"
|
|
106
|
+
>
|
|
107
|
+
↓
|
|
108
|
+
</button>
|
|
109
|
+
</div>
|
|
110
|
+
)}
|
|
111
|
+
<a
|
|
112
|
+
className="builder__button-link"
|
|
113
|
+
onMouseDown={handlePressStart}
|
|
114
|
+
onMouseUp={handlePressEnd}
|
|
115
|
+
onMouseLeave={handlePressCancel}
|
|
116
|
+
onTouchStart={handlePressStart}
|
|
117
|
+
onTouchEnd={handlePressEnd}
|
|
118
|
+
onTouchCancel={handlePressCancel}
|
|
119
|
+
>
|
|
120
|
+
{baseLabel}
|
|
121
|
+
</a>
|
|
122
|
+
{conditionLabel && (
|
|
123
|
+
<span className="builder__button-condition">{conditionLabel}</span>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
@@ -24,9 +24,14 @@ export function EditorHeader({
|
|
|
24
24
|
useLogRender('EditorHeader');
|
|
25
25
|
const [isDevicesModalOpen, setIsDevicesModalOpen] = useState(false);
|
|
26
26
|
const copiedNode = useRenderStore((s) => s.copiedNode);
|
|
27
|
-
const {
|
|
27
|
+
const {
|
|
28
|
+
device: selectedDevice,
|
|
29
|
+
setDevice,
|
|
30
|
+
setCurrent,
|
|
31
|
+
} = useRenderStore((s) => ({
|
|
28
32
|
device: s.device,
|
|
29
33
|
setDevice: s.setDevice,
|
|
34
|
+
setCurrent: s.setCurrent,
|
|
30
35
|
}));
|
|
31
36
|
|
|
32
37
|
function replaceNode(root: Node, target: Node, next: Node): Node {
|
|
@@ -67,6 +72,11 @@ export function EditorHeader({
|
|
|
67
72
|
copiedNode: null,
|
|
68
73
|
});
|
|
69
74
|
setEditorData(updated);
|
|
75
|
+
//TODO: current and editor must be sync!! and tested more
|
|
76
|
+
// Important: selection is stored by reference. After replacing `current` in the tree,
|
|
77
|
+
// we must point selection to the new (cloned) node reference to keep "current node"
|
|
78
|
+
// in sync with what’s rendered/edited.
|
|
79
|
+
setCurrent(cloned);
|
|
70
80
|
};
|
|
71
81
|
return (
|
|
72
82
|
<div
|