Skip to content

Commit 936406b

Browse files
committed
Navigationstiefe erweitert
1 parent 6d6eebe commit 936406b

13 files changed

Lines changed: 1220 additions & 257 deletions

src/components/EditorArea/EditorArea.test.tsx

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,34 @@ import { render, screen, fireEvent } from '@testing-library/react';
33
import EditorArea from './EditorArea';
44

55
// Mocks für die abhängigen Komponenten und Kontexte
6-
jest.mock('../../context/EditorContext', () => ({
7-
useEditorContext: jest.fn().mockImplementation(() => ({
8-
state: {
9-
selectedPageId: 'test-page-1',
10-
selectedElementId: null,
11-
currentFlow: {
12-
pages_edit: [
13-
{
14-
id: 'test-page-1',
15-
pattern_type: 'Page',
16-
title: { de: 'Testseite 1' },
17-
elements: []
18-
}
19-
]
20-
}
21-
},
22-
dispatch: jest.fn()
23-
})),
24-
useEditor: jest.fn().mockImplementation(() => ({
25-
state: {
26-
selectedPageId: 'test-page-1',
27-
selectedElementId: null,
28-
currentFlow: {
29-
pages_edit: [
30-
{
31-
id: 'test-page-1',
32-
pattern_type: 'Page',
33-
title: { de: 'Testseite 1' },
34-
elements: []
35-
}
36-
]
37-
}
6+
jest.mock('../../context/EditorContext', () => {
7+
const mockDispatch = jest.fn();
8+
const mockState = {
9+
selectedPageId: 'test-page-1',
10+
selectedElementId: null,
11+
currentFlow: {
12+
pages_edit: [
13+
{
14+
id: 'test-page-1',
15+
pattern_type: 'Page',
16+
title: { de: 'Testseite 1' },
17+
elements: []
18+
}
19+
]
3820
},
39-
dispatch: jest.fn()
40-
}))
41-
}));
21+
dialogs: { elementType: false } // Standardmäßig geschlossen
22+
};
23+
return {
24+
useEditorContext: jest.fn().mockImplementation(() => ({
25+
state: mockState,
26+
dispatch: mockDispatch
27+
})),
28+
useEditor: jest.fn().mockImplementation(() => ({
29+
state: mockState,
30+
dispatch: mockDispatch
31+
}))
32+
};
33+
});
4234

4335
// Mock für DnD
4436
jest.mock('react-dnd', () => ({
@@ -118,11 +110,13 @@ jest.mock('@mui/icons-material', () => ({
118110
describe('EditorArea', () => {
119111
it('sollte eine leere Area rendern, wenn keine Seite ausgewählt ist', () => {
120112
// Überschreibe den Mock für diesen speziellen Test
121-
require('../../context/EditorContext').useEditorContext.mockImplementation(() => ({
113+
const { useEditor } = require('../../context/EditorContext');
114+
useEditor.mockImplementation(() => ({
122115
state: {
123116
selectedPageId: null,
124117
selectedElementId: null,
125-
currentFlow: null
118+
currentFlow: null,
119+
dialogs: { elementType: false }
126120
},
127121
dispatch: jest.fn()
128122
}));
@@ -142,7 +136,8 @@ describe('EditorArea', () => {
142136

143137
it('sollte eine leere Seite mit "Element hinzufügen" Button rendern', () => {
144138
// Setze den Standard-Mock wieder her
145-
require('../../context/EditorContext').useEditorContext.mockImplementation(() => ({
139+
const { useEditor } = require('../../context/EditorContext');
140+
useEditor.mockImplementation(() => ({
146141
state: {
147142
selectedPageId: 'test-page-1',
148143
selectedElementId: null,
@@ -155,7 +150,8 @@ describe('EditorArea', () => {
155150
elements: []
156151
}
157152
]
158-
}
153+
},
154+
dialogs: { elementType: false }
159155
},
160156
dispatch: jest.fn()
161157
}));
@@ -173,8 +169,9 @@ describe('EditorArea', () => {
173169
});
174170

175171
it('sollte den ElementTypeDialog öffnen, wenn auf "Element hinzufügen" geklickt wird', () => {
172+
const { useEditor } = require('../../context/EditorContext');
176173
const mockDispatch = jest.fn();
177-
require('../../context/EditorContext').useEditorContext.mockImplementation(() => ({
174+
useEditor.mockImplementation(() => ({
178175
state: {
179176
selectedPageId: 'test-page-1',
180177
selectedElementId: null,
@@ -188,7 +185,7 @@ describe('EditorArea', () => {
188185
}
189186
]
190187
},
191-
dialogOpen: { elementType: false }
188+
dialogs: { elementType: false }
192189
},
193190
dispatch: mockDispatch
194191
}));
@@ -214,7 +211,8 @@ describe('EditorArea', () => {
214211

215212
it('sollte ElementTypeDialog mit korrekten Props rendern', () => {
216213
// Mock mit geöffnetem Dialog
217-
require('../../context/EditorContext').useEditorContext.mockImplementation(() => ({
214+
const { useEditor } = require('../../context/EditorContext');
215+
useEditor.mockImplementation(() => ({
218216
state: {
219217
selectedPageId: 'test-page-1',
220218
selectedElementId: null,
@@ -228,7 +226,7 @@ describe('EditorArea', () => {
228226
}
229227
]
230228
},
231-
dialogOpen: { elementType: true }
229+
dialogs: { elementType: true } // Dialog ist geöffnet
232230
},
233231
dispatch: jest.fn()
234232
}));
@@ -248,8 +246,9 @@ describe('EditorArea', () => {
248246
});
249247

250248
it('sollte den Dialog schließen, wenn auf "Schließen" geklickt wird', () => {
249+
const { useEditor } = require('../../context/EditorContext');
251250
const mockDispatch = jest.fn();
252-
require('../../context/EditorContext').useEditorContext.mockImplementation(() => ({
251+
useEditor.mockImplementation(() => ({
253252
state: {
254253
selectedPageId: 'test-page-1',
255254
selectedElementId: null,
@@ -263,7 +262,7 @@ describe('EditorArea', () => {
263262
}
264263
]
265264
},
266-
dialogOpen: { elementType: true }
265+
dialogs: { elementType: true } // Dialog ist geöffnet
267266
},
268267
dispatch: mockDispatch
269268
}));
@@ -288,8 +287,9 @@ describe('EditorArea', () => {
288287
});
289288

290289
it('sollte ein Element hinzufügen, wenn es im Dialog ausgewählt wird', () => {
290+
const { useEditor } = require('../../context/EditorContext');
291291
const mockDispatch = jest.fn();
292-
require('../../context/EditorContext').useEditorContext.mockImplementation(() => ({
292+
useEditor.mockImplementation(() => ({
293293
state: {
294294
selectedPageId: 'test-page-1',
295295
selectedElementId: null,
@@ -303,7 +303,7 @@ describe('EditorArea', () => {
303303
}
304304
]
305305
},
306-
dialogOpen: { elementType: true }
306+
dialogs: { elementType: true } // Dialog ist geöffnet
307307
},
308308
dispatch: mockDispatch
309309
}));

src/components/EditorArea/EditorArea.tsx

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
} from '@mui/icons-material';
2929
import { PatternLibraryElement } from '../../models/listingFlow';
3030
import { UIElement, GroupUIElement } from '../../models/uiElements';
31-
import { getElementByPath } from '../../context/EditorContext';
31+
import { useEditor, getElementByPath } from '../../context/EditorContext'; // useEditor importieren
3232
import { useFieldValues } from '../../context/FieldValuesContext';
3333
import { evaluateVisibilityCondition } from '../../utils/visibilityUtils';
3434
// Import der react-dnd Hooks
@@ -88,7 +88,8 @@ const ChildrenContainer = styled(Box)<{ depth: number }>`
8888
margin-top: 1rem;
8989
`;
9090

91-
const EmptyState = styled(Box)<{ isOver?: boolean }>`
91+
// Filter transient props for EmptyState
92+
const EmptyState = styled(({ $isOver, ...props }) => <Box {...props} />)<{ $isOver?: boolean }>`
9293
display: flex;
9394
flex-direction: column;
9495
align-items: center;
@@ -97,20 +98,21 @@ const EmptyState = styled(Box)<{ isOver?: boolean }>`
9798
text-align: center;
9899
color: #343951;
99100
padding: 2rem;
100-
background-color: ${props => props.isOver ? 'rgba(0, 159, 100, 0.05)' : 'rgba(0, 159, 100, 0.02)'};
101+
background-color: ${props => props.$isOver ? 'rgba(0, 159, 100, 0.05)' : 'rgba(0, 159, 100, 0.02)'};
101102
border-radius: 8px;
102-
border: 2px dashed ${props => props.isOver ? '#009F64' : 'rgba(0, 159, 100, 0.2)'};
103+
border: 2px dashed ${props => props.$isOver ? '#009F64' : 'rgba(0, 159, 100, 0.2)'};
103104
transition: all 0.2s ease;
104105
`;
105106

106-
const DropZone = styled(Box)<{ isOver?: boolean }>`
107-
border: 2px dashed ${props => props.isOver ? '#009F64' : 'rgba(0, 159, 100, 0.3)'};
107+
// Filter transient props for DropZone
108+
const DropZone = styled(({ $isOver, ...props }) => <Box {...props} />)<{ $isOver?: boolean }>`
109+
border: 2px dashed ${props => props.$isOver ? '#009F64' : 'rgba(0, 159, 100, 0.3)'};
108110
border-radius: 8px;
109111
padding: 1rem;
110112
margin: 0.5rem 0;
111113
text-align: center;
112114
color: #343951;
113-
background-color: ${props => props.isOver ? 'rgba(0, 159, 100, 0.05)' : 'transparent'};
115+
background-color: ${props => props.$isOver ? 'rgba(0, 159, 100, 0.05)' : 'transparent'};
114116
transition: all 0.2s ease;
115117
`;
116118

@@ -417,7 +419,7 @@ const ElementRenderer: React.FC<{
417419
onDuplicateElement: (path: number[]) => void;
418420
onAddSubElement?: (parentPath: number[], type: string) => void;
419421
onMoveElement?: (sourceIndex: number, targetIndex: number, parentPath?: number[], sourcePath?: number[]) => void;
420-
setShowElementTypeModal: React.Dispatch<React.SetStateAction<boolean>>;
422+
setShowElementTypeModal: (open: boolean) => void; // Typ angepasst
421423
setTargetParentPath: React.Dispatch<React.SetStateAction<number[] | undefined>>;
422424
}> = ({
423425
element,
@@ -508,7 +510,7 @@ const ElementRenderer: React.FC<{
508510

509511
{isCollapsible && (
510512
<IconButton
511-
onClick={(e) => {
513+
onClick={(e: React.MouseEvent) => { // Typ hinzugefügt
512514
e.stopPropagation();
513515
setIsExpanded(!isExpanded);
514516
}}
@@ -541,7 +543,7 @@ const ElementRenderer: React.FC<{
541543
<Tooltip title="Duplizieren">
542544
<IconButton
543545
size="small"
544-
onClick={(e) => {
546+
onClick={(e: React.MouseEvent) => { // Typ hinzugefügt
545547
e.stopPropagation();
546548
onDuplicateElement(path);
547549
}}
@@ -552,7 +554,7 @@ const ElementRenderer: React.FC<{
552554
<Tooltip title="Löschen">
553555
<IconButton
554556
size="small"
555-
onClick={(e) => {
557+
onClick={(e: React.MouseEvent) => { // Typ hinzugefügt
556558
e.stopPropagation();
557559
onRemoveElement(path);
558560
}}
@@ -715,10 +717,15 @@ const EditorArea: React.FC<EditorAreaProps> = ({
715717
onDropElement,
716718
onMoveElement
717719
}) => {
718-
const [showElementTypeModal, setShowElementTypeModal] = useState(false);
720+
const { state: editorState, dispatch: editorDispatch } = useEditor(); // EditorContext verwenden
719721
const [targetParentPath, setTargetParentPath] = useState<number[] | undefined>(undefined);
720722

721-
const handleDrop = (e: React.DragEvent) => {
723+
const showElementTypeModal = editorState.dialogs?.elementType || false;
724+
const setShowElementTypeModal = (open: boolean) => {
725+
editorDispatch({ type: 'TOGGLE_DIALOG', payload: { dialog: 'elementType', open } });
726+
};
727+
728+
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
722729
e.preventDefault();
723730
const elementType = e.dataTransfer.getData('element_type');
724731
if (elementType && onDropElement) {
@@ -728,7 +735,7 @@ const EditorArea: React.FC<EditorAreaProps> = ({
728735

729736
const [isDropZoneActive, setIsDropZoneActive] = useState(false);
730737

731-
const handleDragOver = (e: React.DragEvent) => {
738+
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
732739
e.preventDefault();
733740
setIsDropZoneActive(true);
734741
};
@@ -741,6 +748,8 @@ const EditorArea: React.FC<EditorAreaProps> = ({
741748
const handleSelectElementType = (type: string) => {
742749
if (targetParentPath && onAddSubElement) {
743750
onAddSubElement(targetParentPath, type);
751+
} else if (!targetParentPath && onAddSubElement) { // Hinzufügen auf Root-Ebene
752+
onAddSubElement([], type);
744753
}
745754
// Dialog schließen
746755
setShowElementTypeModal(false);
@@ -757,26 +766,41 @@ const EditorArea: React.FC<EditorAreaProps> = ({
757766
open={showElementTypeModal}
758767
onClose={() => setShowElementTypeModal(false)}
759768
onSelectElementType={handleSelectElementType}
760-
parentElementType={targetParentPath ?
761-
elements[targetParentPath[0]]?.element.pattern_type : undefined}
769+
parentElementType={
770+
targetParentPath && targetParentPath.length > 0 && elements[targetParentPath[0]]
771+
? elements[targetParentPath[0]].element.pattern_type
772+
: undefined
773+
}
762774
/>
763775

764776
{elements.length === 0 ? (
765777
<EmptyState
766-
onDrop={(e) => {
778+
onDrop={(e: React.DragEvent<HTMLDivElement>) => { // Typ hinzugefügt
767779
handleDrop(e);
768780
setIsDropZoneActive(false);
769781
}}
770782
onDragOver={handleDragOver}
771783
onDragLeave={handleDragLeave}
772-
isOver={isDropZoneActive}
784+
$isOver={isDropZoneActive}
773785
>
774786
<Typography variant="body1" gutterBottom>
775787
Ziehen Sie Elemente aus der Palette hierher
776788
</Typography>
777-
<Typography variant="body2">
789+
<Typography variant="body2" sx={{ mb: 1 }}>
778790
Oder klicken Sie, um ein Element hinzuzufügen
779791
</Typography>
792+
{/* Add IconButton to trigger dialog */}
793+
<IconButton
794+
data-testid="icon-add-circle"
795+
color="primary"
796+
onClick={() => {
797+
setTargetParentPath(undefined); // Set parent path for root level
798+
setShowElementTypeModal(true);
799+
}}
800+
sx={{ mt: 1 }}
801+
>
802+
<AddIcon fontSize="large" />
803+
</IconButton>
780804
</EmptyState>
781805
) : (
782806
<Box>
@@ -819,13 +843,13 @@ const EditorArea: React.FC<EditorAreaProps> = ({
819843
)}
820844

821845
<DropZone
822-
onDrop={(e) => {
846+
onDrop={(e: React.DragEvent<HTMLDivElement>) => { // Typ hinzugefügt
823847
handleDrop(e);
824848
setIsDropZoneActive(false);
825849
}}
826850
onDragOver={handleDragOver}
827851
onDragLeave={handleDragLeave}
828-
isOver={isDropZoneActive}
852+
$isOver={isDropZoneActive}
829853
>
830854
<Typography>
831855
Element hier ablegen
@@ -837,4 +861,5 @@ const EditorArea: React.FC<EditorAreaProps> = ({
837861
);
838862
};
839863

840-
export default EditorArea;
864+
const ExportedEditorArea: React.FC<EditorAreaProps> = EditorArea;
865+
export default ExportedEditorArea;

src/components/HybridEditor/ElementContextView.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -353,10 +353,11 @@ const ElementContextView: React.FC<ElementContextViewProps> = ({
353353

354354
// Handle SubFlow objects
355355
if (!elementType && (element.element as any).type) {
356-
console.log('hasChildren - SubFlow - checking elements');
356+
console.log('hasChildren - SubFlow - checking elements and sub_elements');
357357
const hasElements = (element.element as any).elements?.length > 0;
358-
console.log('hasChildren - SubFlow - hasElements:', hasElements);
359-
return hasElements;
358+
const hasSubElements = (element.element as any).sub_elements?.length > 0;
359+
console.log('hasChildren - SubFlow - hasElements:', hasElements, 'hasSubElements:', hasSubElements);
360+
return hasElements || hasSubElements;
360361
}
361362

362363
if (elementType === 'GroupUIElement') {

0 commit comments

Comments
 (0)