Spaces:
Running
Running
incognitolm commited on
Commit ·
93a8ee2
1
Parent(s): 70d86da
- client/src/compilers/web.ts +3 -4
- client/src/components/BlockEditor/BlockEditor.tsx +27 -22
- client/src/components/CodeView/CodeView.tsx +5 -8
- client/src/components/ProjectEditor/ProjectEditor.tsx +18 -4
- client/src/store/editorStore.ts +50 -10
- client/src/types/index.ts +1 -1
- server/src/routes/projects.ts +1 -0
client/src/compilers/web.ts
CHANGED
|
@@ -6,7 +6,6 @@ interface CompileOptions {
|
|
| 6 |
visualElements: VisualElement[];
|
| 7 |
activeFile?: ProjectFile;
|
| 8 |
projectName: string;
|
| 9 |
-
blockGeneratedCode?: string;
|
| 10 |
}
|
| 11 |
|
| 12 |
export function compileWeb(options: CompileOptions): string {
|
|
@@ -23,7 +22,7 @@ export function compileWeb(options: CompileOptions): string {
|
|
| 23 |
case 'css':
|
| 24 |
return activeFile.content || '/* Styles */\n';
|
| 25 |
case 'js':
|
| 26 |
-
return
|
| 27 |
default:
|
| 28 |
return activeFile.content || '';
|
| 29 |
}
|
|
@@ -88,7 +87,7 @@ export function compileElectron(options: CompileOptions): string {
|
|
| 88 |
|
| 89 |
switch (activeFile.type) {
|
| 90 |
case 'typescript':
|
| 91 |
-
return
|
| 92 |
case 'html':
|
| 93 |
return activeFile.content || generateDefaultHTML(options);
|
| 94 |
case 'css':
|
|
@@ -199,7 +198,7 @@ export function compileNodeJS(options: CompileOptions): string {
|
|
| 199 |
|
| 200 |
switch (activeFile.type) {
|
| 201 |
case 'js':
|
| 202 |
-
return
|
| 203 |
case 'json':
|
| 204 |
return activeFile.content || generateDefaultPackageJson(options);
|
| 205 |
case 'env':
|
|
|
|
| 6 |
visualElements: VisualElement[];
|
| 7 |
activeFile?: ProjectFile;
|
| 8 |
projectName: string;
|
|
|
|
| 9 |
}
|
| 10 |
|
| 11 |
export function compileWeb(options: CompileOptions): string {
|
|
|
|
| 22 |
case 'css':
|
| 23 |
return activeFile.content || '/* Styles */\n';
|
| 24 |
case 'js':
|
| 25 |
+
return activeFile.content || '// JavaScript\n';
|
| 26 |
default:
|
| 27 |
return activeFile.content || '';
|
| 28 |
}
|
|
|
|
| 87 |
|
| 88 |
switch (activeFile.type) {
|
| 89 |
case 'typescript':
|
| 90 |
+
return activeFile.content || generateDefaultElectronMain(options);
|
| 91 |
case 'html':
|
| 92 |
return activeFile.content || generateDefaultHTML(options);
|
| 93 |
case 'css':
|
|
|
|
| 198 |
|
| 199 |
switch (activeFile.type) {
|
| 200 |
case 'js':
|
| 201 |
+
return activeFile.content || generateDefaultExpress(options);
|
| 202 |
case 'json':
|
| 203 |
return activeFile.content || generateDefaultPackageJson(options);
|
| 204 |
case 'env':
|
client/src/components/BlockEditor/BlockEditor.tsx
CHANGED
|
@@ -293,10 +293,15 @@ export default function BlockEditor() {
|
|
| 293 |
const workspaceRef = useRef<Blockly.WorkspaceSvg | null>(null);
|
| 294 |
const { currentProject } = useProjectStore();
|
| 295 |
const framework = currentProject?.framework || 'web';
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
|
| 297 |
// Initialize Blockly workspace
|
| 298 |
useEffect(() => {
|
| 299 |
if (!blocklyDiv.current) return;
|
|
|
|
| 300 |
|
| 301 |
// Register all blocks for the current framework
|
| 302 |
const blocks = getBlocksForFramework(framework as any);
|
|
@@ -337,15 +342,15 @@ export default function BlockEditor() {
|
|
| 337 |
},
|
| 338 |
trashcan: true,
|
| 339 |
|
| 340 |
-
renderer: 'zelos',
|
| 341 |
theme: Blockly.Themes.Zelos,
|
| 342 |
sounds: false,
|
| 343 |
});
|
| 344 |
|
| 345 |
workspaceRef.current = ws;
|
| 346 |
|
| 347 |
-
// Restore saved blocks
|
| 348 |
-
const savedXml =
|
| 349 |
if (savedXml) {
|
| 350 |
try {
|
| 351 |
const dom = Blockly.utils.xml.textToDom(savedXml);
|
|
@@ -355,16 +360,19 @@ export default function BlockEditor() {
|
|
| 355 |
}
|
| 356 |
}
|
| 357 |
|
| 358 |
-
// Auto-save blocks on change
|
| 359 |
const saveBlocks = () => {
|
| 360 |
const dom = Blockly.Xml.workspaceToDom(ws);
|
| 361 |
const xml = Blockly.Xml.domToText(dom);
|
| 362 |
-
useEditorStore.getState().
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
|
|
|
|
|
|
|
|
|
| 368 |
}
|
| 369 |
};
|
| 370 |
ws.addChangeListener(saveBlocks);
|
|
@@ -374,18 +382,7 @@ export default function BlockEditor() {
|
|
| 374 |
ws.dispose();
|
| 375 |
workspaceRef.current = null;
|
| 376 |
};
|
| 377 |
-
}, [framework]);
|
| 378 |
-
|
| 379 |
-
// Generate code from workspace
|
| 380 |
-
const generateCode = useCallback(() => {
|
| 381 |
-
if (!workspaceRef.current) return '';
|
| 382 |
-
return javascriptGenerator.workspaceToCode(workspaceRef.current);
|
| 383 |
-
}, []);
|
| 384 |
-
|
| 385 |
-
// Expose code generator to window for debugging
|
| 386 |
-
useEffect(() => {
|
| 387 |
-
(window as any).generateBlocksCode = generateCode;
|
| 388 |
-
}, [generateCode]);
|
| 389 |
|
| 390 |
const fitView = useCallback(() => {
|
| 391 |
if (workspaceRef.current) {
|
|
@@ -400,6 +397,14 @@ export default function BlockEditor() {
|
|
| 400 |
}
|
| 401 |
}, []);
|
| 402 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 403 |
return (
|
| 404 |
<div className="flex flex-col h-full bg-[#1a1f2e]">
|
| 405 |
{/* Toolbar */}
|
|
|
|
| 293 |
const workspaceRef = useRef<Blockly.WorkspaceSvg | null>(null);
|
| 294 |
const { currentProject } = useProjectStore();
|
| 295 |
const framework = currentProject?.framework || 'web';
|
| 296 |
+
const activeFileId = useEditorStore((s) => s.activeFileId);
|
| 297 |
+
const getBlocksXml = useEditorStore((s) => s.getBlocksXml);
|
| 298 |
+
const setBlocksXml = useEditorStore((s) => s.setBlocksXml);
|
| 299 |
+
const saveGeneratedCodeToFile = useEditorStore((s) => s.saveGeneratedCodeToFile);
|
| 300 |
|
| 301 |
// Initialize Blockly workspace
|
| 302 |
useEffect(() => {
|
| 303 |
if (!blocklyDiv.current) return;
|
| 304 |
+
if (!activeFileId) return;
|
| 305 |
|
| 306 |
// Register all blocks for the current framework
|
| 307 |
const blocks = getBlocksForFramework(framework as any);
|
|
|
|
| 342 |
},
|
| 343 |
trashcan: true,
|
| 344 |
|
| 345 |
+
renderer: 'zelos',
|
| 346 |
theme: Blockly.Themes.Zelos,
|
| 347 |
sounds: false,
|
| 348 |
});
|
| 349 |
|
| 350 |
workspaceRef.current = ws;
|
| 351 |
|
| 352 |
+
// Restore saved blocks for this specific file
|
| 353 |
+
const savedXml = getBlocksXml(activeFileId);
|
| 354 |
if (savedXml) {
|
| 355 |
try {
|
| 356 |
const dom = Blockly.utils.xml.textToDom(savedXml);
|
|
|
|
| 360 |
}
|
| 361 |
}
|
| 362 |
|
| 363 |
+
// Auto-save blocks on change - per-file
|
| 364 |
const saveBlocks = () => {
|
| 365 |
const dom = Blockly.Xml.workspaceToDom(ws);
|
| 366 |
const xml = Blockly.Xml.domToText(dom);
|
| 367 |
+
const currentFileId = useEditorStore.getState().activeFileId;
|
| 368 |
+
if (currentFileId) {
|
| 369 |
+
setBlocksXml(currentFileId, xml);
|
| 370 |
+
try {
|
| 371 |
+
const code = javascriptGenerator.workspaceToCode(ws);
|
| 372 |
+
saveGeneratedCodeToFile(currentFileId, code || '');
|
| 373 |
+
} catch (e) {
|
| 374 |
+
// code generation failed
|
| 375 |
+
}
|
| 376 |
}
|
| 377 |
};
|
| 378 |
ws.addChangeListener(saveBlocks);
|
|
|
|
| 382 |
ws.dispose();
|
| 383 |
workspaceRef.current = null;
|
| 384 |
};
|
| 385 |
+
}, [framework, activeFileId]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 386 |
|
| 387 |
const fitView = useCallback(() => {
|
| 388 |
if (workspaceRef.current) {
|
|
|
|
| 397 |
}
|
| 398 |
}, []);
|
| 399 |
|
| 400 |
+
if (!activeFileId) {
|
| 401 |
+
return (
|
| 402 |
+
<div className="flex items-center justify-center h-full bg-[#1a1f2e] text-surface-400 text-sm">
|
| 403 |
+
Select a file to edit its blocks
|
| 404 |
+
</div>
|
| 405 |
+
);
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
return (
|
| 409 |
<div className="flex flex-col h-full bg-[#1a1f2e]">
|
| 410 |
{/* Toolbar */}
|
client/src/components/CodeView/CodeView.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import { compileMaui } from '../../compilers/maui';
|
|
| 8 |
import { compileNodeJS } from '../../compilers/nodejs';
|
| 9 |
import { SyntaxHighlight } from './SyntaxHighlight';
|
| 10 |
import {
|
| 11 |
-
Download, Copy,
|
| 12 |
Eye, Code2, SplitSquareHorizontal
|
| 13 |
} from 'lucide-react';
|
| 14 |
import { saveAs } from 'file-saver';
|
|
@@ -16,18 +16,15 @@ import JSZip from 'jszip';
|
|
| 16 |
|
| 17 |
export default function CodeView() {
|
| 18 |
const { currentProject } = useProjectStore();
|
| 19 |
-
const { activeFileId, fileTree, viewMode, setViewMode, visualElements
|
| 20 |
|
| 21 |
const compiledCode = useMemo(() => {
|
| 22 |
if (!currentProject) return '';
|
| 23 |
|
| 24 |
-
//
|
| 25 |
-
|
| 26 |
-
return blockGeneratedCode;
|
| 27 |
-
}
|
| 28 |
|
| 29 |
const blocks = getBlocksForFramework(currentProject.framework);
|
| 30 |
-
const activeFile = fileTree.find(f => f.id === activeFileId);
|
| 31 |
|
| 32 |
const compilerOptions = {
|
| 33 |
blocks,
|
|
@@ -49,7 +46,7 @@ export default function CodeView() {
|
|
| 49 |
default:
|
| 50 |
return '// Select a framework to generate code';
|
| 51 |
}
|
| 52 |
-
}, [currentProject, activeFileId, fileTree, visualElements
|
| 53 |
|
| 54 |
const handleDownload = useCallback(async () => {
|
| 55 |
if (!currentProject) return;
|
|
|
|
| 8 |
import { compileNodeJS } from '../../compilers/nodejs';
|
| 9 |
import { SyntaxHighlight } from './SyntaxHighlight';
|
| 10 |
import {
|
| 11 |
+
Download, Copy, FileCode,
|
| 12 |
Eye, Code2, SplitSquareHorizontal
|
| 13 |
} from 'lucide-react';
|
| 14 |
import { saveAs } from 'file-saver';
|
|
|
|
| 16 |
|
| 17 |
export default function CodeView() {
|
| 18 |
const { currentProject } = useProjectStore();
|
| 19 |
+
const { activeFileId, fileTree, viewMode, setViewMode, visualElements } = useEditorStore();
|
| 20 |
|
| 21 |
const compiledCode = useMemo(() => {
|
| 22 |
if (!currentProject) return '';
|
| 23 |
|
| 24 |
+
// Show the active file's content directly (blocks compile to file content on change)
|
| 25 |
+
const activeFile = fileTree.find(f => f.id === activeFileId);
|
|
|
|
|
|
|
| 26 |
|
| 27 |
const blocks = getBlocksForFramework(currentProject.framework);
|
|
|
|
| 28 |
|
| 29 |
const compilerOptions = {
|
| 30 |
blocks,
|
|
|
|
| 46 |
default:
|
| 47 |
return '// Select a framework to generate code';
|
| 48 |
}
|
| 49 |
+
}, [currentProject, activeFileId, fileTree, visualElements]);
|
| 50 |
|
| 51 |
const handleDownload = useCallback(async () => {
|
| 52 |
if (!currentProject) return;
|
client/src/components/ProjectEditor/ProjectEditor.tsx
CHANGED
|
@@ -41,7 +41,7 @@ export default function ProjectEditor() {
|
|
| 41 |
editorMode, setEditorMode, viewMode, setViewMode,
|
| 42 |
activeFileId, setActiveFile, fileTree, setFileTree,
|
| 43 |
visualElements, setVisualElements, snapToGrid, toggleSnapToGrid,
|
| 44 |
-
showMiniMap, toggleMiniMap,
|
| 45 |
} = useEditorStore();
|
| 46 |
const [showSidebar, setShowSidebar] = useState(true);
|
| 47 |
const [saving, setSaving] = useState(false);
|
|
@@ -56,8 +56,12 @@ export default function ProjectEditor() {
|
|
| 56 |
setVisualElements(
|
| 57 |
currentProject.data.visual?.[activeFileId || ''] || []
|
| 58 |
);
|
|
|
|
| 59 |
if (currentProject.data.blocksXml) {
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
| 61 |
}
|
| 62 |
if (!activeFileId && currentProject.data.files?.length > 0) {
|
| 63 |
const firstFile = currentProject.data.files[0];
|
|
@@ -71,6 +75,15 @@ export default function ProjectEditor() {
|
|
| 71 |
}
|
| 72 |
}, [currentProject, setFileTree, setVisualElements, setActiveFile, activeFileId, setEditorMode, setBlocksXml]);
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
const activeFile = useMemo(
|
| 75 |
() => (activeFileId ? findFileById(fileTree, activeFileId) : null),
|
| 76 |
[activeFileId, fileTree]
|
|
@@ -112,6 +125,7 @@ export default function ProjectEditor() {
|
|
| 112 |
if (!currentProject || !id) return;
|
| 113 |
setSaving(true);
|
| 114 |
try {
|
|
|
|
| 115 |
const data = {
|
| 116 |
...currentProject.data,
|
| 117 |
files: fileTree,
|
|
@@ -119,13 +133,13 @@ export default function ProjectEditor() {
|
|
| 119 |
...currentProject.data.visual,
|
| 120 |
[activeFileId || '']: visualElements,
|
| 121 |
},
|
| 122 |
-
blocksXml:
|
| 123 |
};
|
| 124 |
await updateProject(id, { data });
|
| 125 |
} finally {
|
| 126 |
setSaving(false);
|
| 127 |
}
|
| 128 |
-
}, [currentProject, id, fileTree, visualElements, activeFileId, updateProject
|
| 129 |
|
| 130 |
useEffect(() => {
|
| 131 |
const interval = setInterval(() => {
|
|
|
|
| 41 |
editorMode, setEditorMode, viewMode, setViewMode,
|
| 42 |
activeFileId, setActiveFile, fileTree, setFileTree,
|
| 43 |
visualElements, setVisualElements, snapToGrid, toggleSnapToGrid,
|
| 44 |
+
showMiniMap, toggleMiniMap, setBlocksXml
|
| 45 |
} = useEditorStore();
|
| 46 |
const [showSidebar, setShowSidebar] = useState(true);
|
| 47 |
const [saving, setSaving] = useState(false);
|
|
|
|
| 56 |
setVisualElements(
|
| 57 |
currentProject.data.visual?.[activeFileId || ''] || []
|
| 58 |
);
|
| 59 |
+
// Restore per-file blocksXml metadata
|
| 60 |
if (currentProject.data.blocksXml) {
|
| 61 |
+
const xmlMap = currentProject.data.blocksXml;
|
| 62 |
+
Object.entries(xmlMap).forEach(([fid, xml]) => {
|
| 63 |
+
setBlocksXml(fid, xml);
|
| 64 |
+
});
|
| 65 |
}
|
| 66 |
if (!activeFileId && currentProject.data.files?.length > 0) {
|
| 67 |
const firstFile = currentProject.data.files[0];
|
|
|
|
| 75 |
}
|
| 76 |
}, [currentProject, setFileTree, setVisualElements, setActiveFile, activeFileId, setEditorMode, setBlocksXml]);
|
| 77 |
|
| 78 |
+
// When activeFileId changes, load visual elements for that file
|
| 79 |
+
useEffect(() => {
|
| 80 |
+
if (currentProject?.data && activeFileId) {
|
| 81 |
+
setVisualElements(
|
| 82 |
+
currentProject.data.visual?.[activeFileId] || []
|
| 83 |
+
);
|
| 84 |
+
}
|
| 85 |
+
}, [activeFileId, currentProject, setVisualElements]);
|
| 86 |
+
|
| 87 |
const activeFile = useMemo(
|
| 88 |
() => (activeFileId ? findFileById(fileTree, activeFileId) : null),
|
| 89 |
[activeFileId, fileTree]
|
|
|
|
| 125 |
if (!currentProject || !id) return;
|
| 126 |
setSaving(true);
|
| 127 |
try {
|
| 128 |
+
const { blocksXml: currentBlocksXml } = useEditorStore.getState();
|
| 129 |
const data = {
|
| 130 |
...currentProject.data,
|
| 131 |
files: fileTree,
|
|
|
|
| 133 |
...currentProject.data.visual,
|
| 134 |
[activeFileId || '']: visualElements,
|
| 135 |
},
|
| 136 |
+
blocksXml: currentBlocksXml,
|
| 137 |
};
|
| 138 |
await updateProject(id, { data });
|
| 139 |
} finally {
|
| 140 |
setSaving(false);
|
| 141 |
}
|
| 142 |
+
}, [currentProject, id, fileTree, visualElements, activeFileId, updateProject]);
|
| 143 |
|
| 144 |
useEffect(() => {
|
| 145 |
const interval = setInterval(() => {
|
client/src/store/editorStore.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
| 1 |
import { create } from 'zustand';
|
| 2 |
-
import { FrameworkType } from '../types';
|
| 3 |
import { ProjectFile, VisualElement } from '../types/blocks';
|
| 4 |
|
| 5 |
type EditorMode = 'blocks' | 'visual' | 'code' | 'files';
|
|
@@ -17,8 +16,7 @@ interface EditorStore {
|
|
| 17 |
showMiniMap: boolean;
|
| 18 |
visualElements: VisualElement[];
|
| 19 |
fileTree: ProjectFile[];
|
| 20 |
-
blocksXml: string;
|
| 21 |
-
blockGeneratedCode: string;
|
| 22 |
|
| 23 |
setActiveFile: (fileId: string | null) => void;
|
| 24 |
setActiveFileContent: (content: string) => void;
|
|
@@ -37,8 +35,9 @@ interface EditorStore {
|
|
| 37 |
addFile: (file: ProjectFile, parentId?: string) => void;
|
| 38 |
removeFile: (id: string) => void;
|
| 39 |
updateFile: (id: string, updates: Partial<ProjectFile>) => void;
|
| 40 |
-
|
| 41 |
-
|
|
|
|
| 42 |
}
|
| 43 |
|
| 44 |
export const useEditorStore = create<EditorStore>((set, get) => ({
|
|
@@ -53,10 +52,21 @@ export const useEditorStore = create<EditorStore>((set, get) => ({
|
|
| 53 |
showMiniMap: true,
|
| 54 |
visualElements: [],
|
| 55 |
fileTree: [],
|
| 56 |
-
blocksXml:
|
| 57 |
-
blockGeneratedCode: '',
|
| 58 |
|
| 59 |
-
setActiveFile: (fileId) =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
setActiveFileContent: (content) => set({ activeFileContent: content }),
|
| 61 |
setEditorMode: (mode) => set({ editorMode: mode }),
|
| 62 |
setViewMode: (mode) => set({ viewMode: mode }),
|
|
@@ -138,8 +148,27 @@ export const useEditorStore = create<EditorStore>((set, get) => ({
|
|
| 138 |
activeFileId: state.activeFileId === id ? null : state.activeFileId,
|
| 139 |
};
|
| 140 |
}),
|
| 141 |
-
|
| 142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
updateFile: (id, updates) =>
|
| 144 |
set((state) => {
|
| 145 |
const files = state.fileTree || [];
|
|
@@ -152,3 +181,14 @@ export const useEditorStore = create<EditorStore>((set, get) => ({
|
|
| 152 |
return { fileTree: updateRecursive(files) };
|
| 153 |
}),
|
| 154 |
}));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import { create } from 'zustand';
|
|
|
|
| 2 |
import { ProjectFile, VisualElement } from '../types/blocks';
|
| 3 |
|
| 4 |
type EditorMode = 'blocks' | 'visual' | 'code' | 'files';
|
|
|
|
| 16 |
showMiniMap: boolean;
|
| 17 |
visualElements: VisualElement[];
|
| 18 |
fileTree: ProjectFile[];
|
| 19 |
+
blocksXml: Record<string, string>;
|
|
|
|
| 20 |
|
| 21 |
setActiveFile: (fileId: string | null) => void;
|
| 22 |
setActiveFileContent: (content: string) => void;
|
|
|
|
| 35 |
addFile: (file: ProjectFile, parentId?: string) => void;
|
| 36 |
removeFile: (id: string) => void;
|
| 37 |
updateFile: (id: string, updates: Partial<ProjectFile>) => void;
|
| 38 |
+
getBlocksXml: (fileId: string) => string;
|
| 39 |
+
setBlocksXml: (fileId: string, xml: string) => void;
|
| 40 |
+
saveGeneratedCodeToFile: (fileId: string, code: string) => void;
|
| 41 |
}
|
| 42 |
|
| 43 |
export const useEditorStore = create<EditorStore>((set, get) => ({
|
|
|
|
| 52 |
showMiniMap: true,
|
| 53 |
visualElements: [],
|
| 54 |
fileTree: [],
|
| 55 |
+
blocksXml: {},
|
|
|
|
| 56 |
|
| 57 |
+
setActiveFile: (fileId) => {
|
| 58 |
+
const state = get();
|
| 59 |
+
if (fileId && fileId !== state.activeFileId) {
|
| 60 |
+
const file = findFileById(state.fileTree, fileId);
|
| 61 |
+
set({
|
| 62 |
+
activeFileId: fileId,
|
| 63 |
+
activeFileContent: file?.content || '',
|
| 64 |
+
visualElements: [],
|
| 65 |
+
});
|
| 66 |
+
} else {
|
| 67 |
+
set({ activeFileId: fileId });
|
| 68 |
+
}
|
| 69 |
+
},
|
| 70 |
setActiveFileContent: (content) => set({ activeFileContent: content }),
|
| 71 |
setEditorMode: (mode) => set({ editorMode: mode }),
|
| 72 |
setViewMode: (mode) => set({ viewMode: mode }),
|
|
|
|
| 148 |
activeFileId: state.activeFileId === id ? null : state.activeFileId,
|
| 149 |
};
|
| 150 |
}),
|
| 151 |
+
getBlocksXml: (fileId: string) => {
|
| 152 |
+
return get().blocksXml[fileId] || '';
|
| 153 |
+
},
|
| 154 |
+
setBlocksXml: (fileId: string, xml: string) =>
|
| 155 |
+
set((state) => ({
|
| 156 |
+
blocksXml: { ...state.blocksXml, [fileId]: xml },
|
| 157 |
+
})),
|
| 158 |
+
saveGeneratedCodeToFile: (fileId: string, code: string) =>
|
| 159 |
+
set((state) => {
|
| 160 |
+
const files = state.fileTree || [];
|
| 161 |
+
const updateRecursive = (entries: ProjectFile[]): ProjectFile[] =>
|
| 162 |
+
(entries || []).map((f) => {
|
| 163 |
+
if (f.id === fileId) return { ...f, content: code };
|
| 164 |
+
if (f.children) return { ...f, children: updateRecursive(f.children) };
|
| 165 |
+
return f;
|
| 166 |
+
});
|
| 167 |
+
return {
|
| 168 |
+
fileTree: updateRecursive(files),
|
| 169 |
+
activeFileContent: fileId === state.activeFileId ? code : state.activeFileContent,
|
| 170 |
+
};
|
| 171 |
+
}),
|
| 172 |
updateFile: (id, updates) =>
|
| 173 |
set((state) => {
|
| 174 |
const files = state.fileTree || [];
|
|
|
|
| 181 |
return { fileTree: updateRecursive(files) };
|
| 182 |
}),
|
| 183 |
}));
|
| 184 |
+
|
| 185 |
+
function findFileById(files: ProjectFile[], id: string): ProjectFile | null {
|
| 186 |
+
for (const f of files) {
|
| 187 |
+
if (f.id === id) return f;
|
| 188 |
+
if (f.children) {
|
| 189 |
+
const found = findFileById(f.children, id);
|
| 190 |
+
if (found) return found;
|
| 191 |
+
}
|
| 192 |
+
}
|
| 193 |
+
return null;
|
| 194 |
+
}
|
client/src/types/index.ts
CHANGED
|
@@ -39,7 +39,7 @@ export interface ProjectData {
|
|
| 39 |
files: ProjectFile[];
|
| 40 |
blocks: Record<string, BlockNode[]>;
|
| 41 |
visual: Record<string, VisualElement[]>;
|
| 42 |
-
blocksXml
|
| 43 |
}
|
| 44 |
|
| 45 |
export interface ProjectFile {
|
|
|
|
| 39 |
files: ProjectFile[];
|
| 40 |
blocks: Record<string, BlockNode[]>;
|
| 41 |
visual: Record<string, VisualElement[]>;
|
| 42 |
+
blocksXml: Record<string, string>;
|
| 43 |
}
|
| 44 |
|
| 45 |
export interface ProjectFile {
|
server/src/routes/projects.ts
CHANGED
|
@@ -191,6 +191,7 @@ function getDefaultProjectData(framework: string): any {
|
|
| 191 |
files: [] as any[],
|
| 192 |
blocks: {} as Record<string, any>,
|
| 193 |
visual: {} as Record<string, any>,
|
|
|
|
| 194 |
};
|
| 195 |
|
| 196 |
switch (framework) {
|
|
|
|
| 191 |
files: [] as any[],
|
| 192 |
blocks: {} as Record<string, any>,
|
| 193 |
visual: {} as Record<string, any>,
|
| 194 |
+
blocksXml: {} as Record<string, string>,
|
| 195 |
};
|
| 196 |
|
| 197 |
switch (framework) {
|