incognitolm commited on
Commit
93a8ee2
·
1 Parent(s): 70d86da
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 options.blockGeneratedCode || activeFile.content || '// JavaScript\n';
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 options.blockGeneratedCode || activeFile.content || generateDefaultElectronMain(options);
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 options.blockGeneratedCode || activeFile.content || generateDefaultExpress(options);
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', // Blockly's Scratch-like renderer
341
  theme: Blockly.Themes.Zelos,
342
  sounds: false,
343
  });
344
 
345
  workspaceRef.current = ws;
346
 
347
- // Restore saved blocks
348
- const savedXml = useEditorStore.getState().blocksXml;
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().setBlocksXml(xml);
363
- try {
364
- const code = javascriptGenerator.workspaceToCode(ws);
365
- useEditorStore.getState().setBlockGeneratedCode(code || '');
366
- } catch (e) {
367
- // code generation failed
 
 
 
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, Check, FileCode, RefreshCw,
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, blockGeneratedCode } = useEditorStore();
20
 
21
  const compiledCode = useMemo(() => {
22
  if (!currentProject) return '';
23
 
24
- // If there's block-generated code, show it directly
25
- if (blockGeneratedCode) {
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, blockGeneratedCode]);
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, blocksXml, setBlocksXml
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
- setBlocksXml(currentProject.data.blocksXml);
 
 
 
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: blocksXml || undefined,
123
  };
124
  await updateProject(id, { data });
125
  } finally {
126
  setSaving(false);
127
  }
128
- }, [currentProject, id, fileTree, visualElements, activeFileId, updateProject, blocksXml]);
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
- setBlocksXml: (xml: string) => void;
41
- setBlockGeneratedCode: (code: string) => void;
 
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) => set({ activeFileId: 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
- setBlocksXml: (xml) => set({ blocksXml: xml }),
142
- setBlockGeneratedCode: (code) => set({ blockGeneratedCode: code }),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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?: string;
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) {