164 lines
4.1 KiB
TypeScript
164 lines
4.1 KiB
TypeScript
import { create } from 'zustand';
|
|
|
|
export interface WorkspaceFile {
|
|
id: string;
|
|
name: string;
|
|
content: string;
|
|
modified: boolean;
|
|
}
|
|
|
|
const MAIN_ID = 'main-sketch';
|
|
|
|
const DEFAULT_FILE: WorkspaceFile = {
|
|
id: MAIN_ID,
|
|
name: 'sketch.ino',
|
|
content: `// Arduino Blink Example
|
|
void setup() {
|
|
pinMode(LED_BUILTIN, OUTPUT);
|
|
}
|
|
|
|
void loop() {
|
|
digitalWrite(LED_BUILTIN, HIGH);
|
|
delay(1000);
|
|
digitalWrite(LED_BUILTIN, LOW);
|
|
delay(1000);
|
|
}`,
|
|
modified: false,
|
|
};
|
|
|
|
interface EditorState {
|
|
files: WorkspaceFile[];
|
|
activeFileId: string;
|
|
openFileIds: string[];
|
|
theme: 'vs-dark' | 'light';
|
|
fontSize: number;
|
|
|
|
// File operations
|
|
createFile: (name: string) => string;
|
|
deleteFile: (id: string) => void;
|
|
renameFile: (id: string, newName: string) => void;
|
|
setFileContent: (id: string, content: string) => void;
|
|
markFileSaved: (id: string) => void;
|
|
openFile: (id: string) => void;
|
|
closeFile: (id: string) => void;
|
|
setActiveFile: (id: string) => void;
|
|
// Load a full set of files (e.g. when loading a saved project)
|
|
loadFiles: (files: { name: string; content: string }[]) => void;
|
|
|
|
// Settings
|
|
setTheme: (theme: 'vs-dark' | 'light') => void;
|
|
setFontSize: (size: number) => void;
|
|
|
|
// Legacy compat — sets content of the active file
|
|
setCode: (code: string) => void;
|
|
}
|
|
|
|
export const useEditorStore = create<EditorState>((set, get) => ({
|
|
files: [DEFAULT_FILE],
|
|
activeFileId: MAIN_ID,
|
|
openFileIds: [MAIN_ID],
|
|
theme: 'vs-dark',
|
|
fontSize: 14,
|
|
|
|
createFile: (name: string) => {
|
|
const id = crypto.randomUUID();
|
|
const newFile: WorkspaceFile = { id, name, content: '', modified: false };
|
|
set((s) => ({
|
|
files: [...s.files, newFile],
|
|
openFileIds: [...s.openFileIds, id],
|
|
activeFileId: id,
|
|
}));
|
|
return id;
|
|
},
|
|
|
|
deleteFile: (id: string) => {
|
|
set((s) => {
|
|
const files = s.files.filter((f) => f.id !== id);
|
|
const openFileIds = s.openFileIds.filter((fid) => fid !== id);
|
|
let activeFileId = s.activeFileId;
|
|
if (activeFileId === id) {
|
|
const idx = s.openFileIds.indexOf(id);
|
|
activeFileId =
|
|
openFileIds[idx] ??
|
|
openFileIds[idx - 1] ??
|
|
openFileIds[0] ??
|
|
files[0]?.id ??
|
|
'';
|
|
}
|
|
return { files, openFileIds, activeFileId };
|
|
});
|
|
},
|
|
|
|
renameFile: (id: string, newName: string) => {
|
|
set((s) => ({
|
|
files: s.files.map((f) =>
|
|
f.id === id ? { ...f, name: newName, modified: true } : f
|
|
),
|
|
}));
|
|
},
|
|
|
|
setFileContent: (id: string, content: string) => {
|
|
set((s) => ({
|
|
files: s.files.map((f) =>
|
|
f.id === id ? { ...f, content, modified: true } : f
|
|
),
|
|
}));
|
|
},
|
|
|
|
markFileSaved: (id: string) => {
|
|
set((s) => ({
|
|
files: s.files.map((f) =>
|
|
f.id === id ? { ...f, modified: false } : f
|
|
),
|
|
}));
|
|
},
|
|
|
|
openFile: (id: string) => {
|
|
set((s) => ({
|
|
openFileIds: s.openFileIds.includes(id)
|
|
? s.openFileIds
|
|
: [...s.openFileIds, id],
|
|
activeFileId: id,
|
|
}));
|
|
},
|
|
|
|
closeFile: (id: string) => {
|
|
set((s) => {
|
|
const openFileIds = s.openFileIds.filter((fid) => fid !== id);
|
|
let activeFileId = s.activeFileId;
|
|
if (activeFileId === id) {
|
|
const idx = s.openFileIds.indexOf(id);
|
|
activeFileId =
|
|
openFileIds[idx] ?? openFileIds[idx - 1] ?? openFileIds[0] ?? '';
|
|
}
|
|
return { openFileIds, activeFileId };
|
|
});
|
|
},
|
|
|
|
setActiveFile: (id: string) => set({ activeFileId: id }),
|
|
|
|
loadFiles: (incoming: { name: string; content: string }[]) => {
|
|
const files: WorkspaceFile[] = incoming.map((f, i) => ({
|
|
id: i === 0 ? MAIN_ID : crypto.randomUUID(),
|
|
name: f.name,
|
|
content: f.content,
|
|
modified: false,
|
|
}));
|
|
const firstId = files[0]?.id ?? MAIN_ID;
|
|
set({
|
|
files,
|
|
activeFileId: firstId,
|
|
openFileIds: [firstId],
|
|
});
|
|
},
|
|
|
|
setTheme: (theme) => set({ theme }),
|
|
setFontSize: (fontSize) => set({ fontSize }),
|
|
|
|
// Legacy: sets content of active file
|
|
setCode: (code: string) => {
|
|
const { activeFileId, setFileContent } = get();
|
|
if (activeFileId) setFileContent(activeFileId, code);
|
|
},
|
|
}));
|