Skip to content

Commit b586c50

Browse files
committed
feat: add page settings panel and enhance preview mode with responsive layout options
1 parent 8a1862b commit b586c50

20 files changed

+2542
-398
lines changed

frontend/components.json

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema.json",
3+
"style": "new-york",
4+
"rsc": false,
5+
"tsx": true,
6+
"tailwind": {
7+
"config": "tailwind.config.js",
8+
"css": "src/index.css",
9+
"baseColor": "neutral",
10+
"cssVariables": true,
11+
"prefix": ""
12+
},
13+
"aliases": {
14+
"components": "@/components",
15+
"utils": "@/lib/utils",
16+
"ui": "@/components/ui",
17+
"lib": "@/lib",
18+
"hooks": "@/hooks"
19+
},
20+
"iconLibrary": "lucide"
21+
}

frontend/package-lock.json

+1,607-296
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

+14-4
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,29 @@
1010
"typescript": "tsc --noEmit"
1111
},
1212
"dependencies": {
13+
"@radix-ui/react-label": "^2.1.0",
14+
"@radix-ui/react-select": "^2.1.2",
15+
"@radix-ui/react-slot": "^1.1.0",
16+
"@radix-ui/react-switch": "^1.1.1",
17+
"class-variance-authority": "^0.7.0",
18+
"clsx": "^2.1.1",
1319
"lodash": "^4.17.21",
1420
"lucide-react": "^0.460.0",
1521
"react": "^18.2.0",
16-
"react-dom": "^18.2.0"
22+
"react-dom": "^18.2.0",
23+
"tailwind-merge": "^2.5.4",
24+
"tailwindcss-animate": "^1.0.7"
1725
},
1826
"devDependencies": {
27+
"@shadcn/ui": "^0.0.4",
1928
"@types/lodash": "^4.17.13",
29+
"@types/node": "^22.9.3",
2030
"@types/react": "^18.2.0",
2131
"@types/react-dom": "^18.2.0",
2232
"@vitejs/plugin-react": "^4.0.0",
23-
"autoprefixer": "^10.4.13",
24-
"postcss": "^8.4.21",
25-
"tailwindcss": "^3.2.4",
33+
"autoprefixer": "^10.4.20",
34+
"postcss": "^8.4.49",
35+
"tailwindcss": "^3.4.15",
2636
"typescript": "^5.0.2",
2737
"vite": "^4.0.0"
2838
}

frontend/src/components/Editor.tsx

+74-22
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,28 @@ import {useElementInteraction} from '../hooks/useElementInteraction';
33
import {Toolbar} from './Toolbar';
44
import {ComponentsSidebar} from './ComponentsSidebar';
55
import {PropertiesPanel} from './PropertiesPanel';
6+
import PageSettingsPanel from './PageSettingsPanel';
67
import {ElementRenderer} from './ElementRenderer';
78
import PreviewMode from './PreviewMode';
89
import {componentsData} from '../constants/components';
9-
import {ComponentProps} from '../types';
10+
import {ComponentProps, PageSettings} from '../types';
1011
import {useEffect, useState} from 'react';
1112
import {XCircle} from 'lucide-react';
1213

14+
const defaultPageSettings: PageSettings = {
15+
responsive: true,
16+
width: 1200,
17+
height: 800,
18+
maxWidth: 'none',
19+
bgColor: '#ffffff'
20+
};
21+
1322
export default function Editor() {
1423
const [isSaving, setIsSaving] = useState(false);
1524
const [isLoading, setIsLoading] = useState(true);
1625
const [error, setError] = useState<string | null>(null);
26+
const [showPageSettings, setShowPageSettings] = useState(false);
27+
const [pageSettings, setPageSettings] = useState<PageSettings>(defaultPageSettings);
1728

1829
const {
1930
isPreview,
@@ -50,9 +61,10 @@ export default function Editor() {
5061
throw new Error(data.error || 'Failed to load page content');
5162
}
5263

53-
if (data.data && Array.isArray(data.data.elements)) {
54-
setElements(data.data.elements);
55-
resetHistory(data.data.elements);
64+
if (data.data) {
65+
setElements(data.data.elements || []);
66+
setPageSettings(data.data.settings || defaultPageSettings);
67+
resetHistory(data.data.elements || []);
5668
}
5769
} catch (error) {
5870
setError(error instanceof Error ? error.message : 'Failed to load page content');
@@ -117,7 +129,8 @@ export default function Editor() {
117129
elements: elements.map(el => ({
118130
...el,
119131
id: Number(el.id)
120-
}))
132+
})),
133+
settings: pageSettings
121134
})
122135
});
123136

@@ -133,20 +146,9 @@ export default function Editor() {
133146
}
134147
};
135148

136-
if (isLoading) {
137-
return (
138-
<div className="h-screen flex items-center justify-center bg-gray-50">
139-
<div className="flex flex-col items-center space-y-4">
140-
<div className="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"/>
141-
<div className="text-gray-600">Loading editor content...</div>
142-
</div>
143-
</div>
144-
);
145-
}
146-
147-
if (isPreview) {
148-
return <PreviewMode elements={elements} onExitPreview={() => setIsPreview(false)}/>;
149-
}
149+
const handleUpdatePageSettings = (newSettings: PageSettings) => {
150+
setPageSettings(newSettings);
151+
};
150152

151153
const updateElementPosition = (axis: 'x' | 'y', value: number) => {
152154
const newElements = elements.map(el =>
@@ -182,13 +184,52 @@ export default function Editor() {
182184
addToHistory(newElements);
183185
};
184186

187+
const getPageStyle = () => {
188+
if (pageSettings.responsive) {
189+
return {
190+
backgroundColor: pageSettings.bgColor,
191+
minHeight: '500px',
192+
...(pageSettings.maxWidth !== 'none' && {maxWidth: pageSettings.maxWidth})
193+
};
194+
}
195+
196+
return {
197+
width: `${pageSettings.width}px`,
198+
height: `${pageSettings.height}px`,
199+
backgroundColor: pageSettings.bgColor
200+
};
201+
};
202+
203+
if (isLoading) {
204+
return (
205+
<div className="h-screen flex items-center justify-center bg-gray-50">
206+
<div className="flex flex-col items-center space-y-4">
207+
<div className="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin"/>
208+
<div className="text-gray-600">Loading editor content...</div>
209+
</div>
210+
</div>
211+
);
212+
}
213+
214+
if (isPreview) {
215+
return (
216+
<PreviewMode
217+
elements={elements}
218+
pageSettings={pageSettings}
219+
onExitPreview={() => setIsPreview(false)}
220+
/>
221+
);
222+
}
223+
185224
return (
186225
<div className="h-screen flex">
187226
<Toolbar
188227
showSidebar={showSidebar}
189228
setShowSidebar={setShowSidebar}
190229
showProperties={showProperties}
191230
setShowProperties={setShowProperties}
231+
showPageSettings={showPageSettings}
232+
setShowPageSettings={setShowPageSettings}
192233
isPreview={isPreview}
193234
setIsPreview={setIsPreview}
194235
canUndo={historyIndex > 0}
@@ -220,9 +261,12 @@ export default function Editor() {
220261
/>
221262
)}
222263

223-
<div className="flex-1 bg-gray-50">
224-
<div className="max-w-4xl mx-auto p-8">
225-
<div className="relative min-h-[500px] bg-white rounded-lg shadow-sm border">
264+
<div className="flex-1 bg-gray-50 overflow-auto">
265+
<div className={`mx-auto p-8 ${pageSettings.responsive ? pageSettings.maxWidth : ''}`}>
266+
<div
267+
className="relative rounded-lg shadow-sm border"
268+
style={getPageStyle()}
269+
>
226270
{elements.map(element => (
227271
<ElementRenderer
228272
key={element.id}
@@ -251,6 +295,14 @@ export default function Editor() {
251295
onUpdateSize={updateElementSize}
252296
/>
253297
)}
298+
299+
{showPageSettings && (
300+
<PageSettingsPanel
301+
settings={pageSettings}
302+
onUpdate={handleUpdatePageSettings}
303+
onClose={() => setShowPageSettings(false)}
304+
/>
305+
)}
254306
</div>
255307
</div>
256308
);

frontend/src/components/ElementRenderer.tsx

+52-16
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,34 @@ interface ElementRendererProps {
1111
onDragStart: (e: React.MouseEvent) => void;
1212
}
1313

14+
interface TextProps {
15+
text: string;
16+
fontSize: number;
17+
color: string;
18+
backgroundColor: string;
19+
}
20+
21+
interface ImageProps {
22+
src: string;
23+
alt: string;
24+
width: number;
25+
height: number;
26+
}
27+
28+
interface ButtonProps {
29+
text: string;
30+
bgColor: string;
31+
textColor: string;
32+
width: number;
33+
height: number;
34+
}
35+
36+
interface ListProps {
37+
items: string[];
38+
listStyle: string;
39+
spacing: number;
40+
}
41+
1442
export function ElementRenderer({
1543
element,
1644
isSelected,
@@ -33,28 +61,31 @@ export function ElementRenderer({
3361

3462
const renderContent = () => {
3563
switch (element.type) {
36-
case 'text':
64+
case 'text': {
65+
const props = element.props as TextProps;
3766
return (
3867
<div
3968
style={{
40-
fontSize: `${element.props.fontSize}px`,
41-
color: element.props.color,
42-
backgroundColor: element.props.backgroundColor,
69+
fontSize: `${props.fontSize}px`,
70+
color: props.color,
71+
backgroundColor: props.backgroundColor,
4372
width: '100%',
4473
height: '100%',
4574
padding: '8px',
4675
pointerEvents: isPreview ? 'auto' : 'none',
4776
userSelect: 'none',
4877
}}
4978
>
50-
{element.props.text}
79+
{props.text}
5180
</div>
5281
);
53-
case 'image':
82+
}
83+
case 'image': {
84+
const props = element.props as ImageProps;
5485
return (
5586
<img
56-
src={element.props.src}
57-
alt={element.props.alt}
87+
src={props.src}
88+
alt={props.alt}
5889
style={{
5990
width: '100%',
6091
height: '100%',
@@ -66,12 +97,14 @@ export function ElementRenderer({
6697
onDragStart={(e) => e.preventDefault()}
6798
/>
6899
);
69-
case 'button':
100+
}
101+
case 'button': {
102+
const props = element.props as ButtonProps;
70103
return (
71104
<button
72105
style={{
73-
backgroundColor: element.props.bgColor,
74-
color: element.props.textColor,
106+
backgroundColor: props.bgColor,
107+
color: props.textColor,
75108
width: '100%',
76109
height: '100%',
77110
border: 'none',
@@ -80,28 +113,31 @@ export function ElementRenderer({
80113
userSelect: 'none',
81114
}}
82115
>
83-
{element.props.text}
116+
{props.text}
84117
</button>
85118
);
86-
case 'list':
119+
}
120+
case 'list': {
121+
const props = element.props as ListProps;
87122
return (
88123
<ul
89124
style={{
90-
listStyleType: element.props.listStyle,
125+
listStyleType: props.listStyle,
91126
padding: '8px 24px',
92127
height: '100%',
93128
overflowY: 'auto',
94129
pointerEvents: isPreview ? 'auto' : 'none',
95130
userSelect: 'none',
96131
}}
97132
>
98-
{element.props.items?.map((item, index) => (
99-
<li key={index} style={{marginBottom: `${element.props.spacing}px`}}>
133+
{props.items.map((item: string, index: number) => (
134+
<li key={index} style={{marginBottom: `${props.spacing}px`}}>
100135
{item}
101136
</li>
102137
))}
103138
</ul>
104139
);
140+
}
105141
default:
106142
return <div>Unknown Component</div>;
107143
}

0 commit comments

Comments
 (0)