add all project files
This commit is contained in:
268
src/lib/store.ts
Normal file
268
src/lib/store.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
import { useSyncExternalStore, useCallback } from "react";
|
||||
|
||||
export interface EditorTab {
|
||||
path: string;
|
||||
name: string;
|
||||
content: string;
|
||||
savedContent: string;
|
||||
language: string;
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
filePath: string;
|
||||
fileName: string;
|
||||
lineNumber: number;
|
||||
lineContent: string;
|
||||
matchStart: number;
|
||||
matchEnd: number;
|
||||
}
|
||||
|
||||
export interface EditorSettings {
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
tabSize: number;
|
||||
wordWrap: boolean;
|
||||
autoSave: boolean;
|
||||
theme: "dark" | "light";
|
||||
customCssPath: string | null;
|
||||
}
|
||||
|
||||
interface EditorStore {
|
||||
tabs: EditorTab[];
|
||||
activeTabPath: string | null;
|
||||
workspacePath: string | null;
|
||||
sidebarVisible: boolean;
|
||||
terminalVisible: boolean;
|
||||
sidebarView: "files" | "search";
|
||||
searchQuery: string;
|
||||
searchResults: SearchResult[];
|
||||
searchCaseSensitive: boolean;
|
||||
searchRegex: boolean;
|
||||
splitEditorPath: string | null;
|
||||
recentWorkspaces: string[];
|
||||
fileTreeFilter: string;
|
||||
cursorLine: number;
|
||||
cursorCol: number;
|
||||
settings: EditorSettings;
|
||||
}
|
||||
|
||||
const defaultSettings: EditorSettings = {
|
||||
fontSize: 14,
|
||||
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
|
||||
tabSize: 2,
|
||||
wordWrap: false,
|
||||
autoSave: false,
|
||||
theme: "dark",
|
||||
customCssPath: null,
|
||||
};
|
||||
|
||||
function loadSettings(): EditorSettings {
|
||||
try {
|
||||
const stored = localStorage.getItem("editor-settings");
|
||||
if (stored) return { ...defaultSettings, ...JSON.parse(stored) };
|
||||
} catch { /* ignore */ }
|
||||
return defaultSettings;
|
||||
}
|
||||
|
||||
function loadRecentWorkspaces(): string[] {
|
||||
try {
|
||||
const stored = localStorage.getItem("recent-workspaces");
|
||||
if (stored) return JSON.parse(stored);
|
||||
} catch { /* ignore */ }
|
||||
return [];
|
||||
}
|
||||
|
||||
let state: EditorStore = {
|
||||
tabs: [],
|
||||
activeTabPath: null,
|
||||
workspacePath: null,
|
||||
sidebarVisible: true,
|
||||
terminalVisible: false,
|
||||
sidebarView: "files",
|
||||
searchQuery: "",
|
||||
searchResults: [],
|
||||
searchCaseSensitive: false,
|
||||
searchRegex: false,
|
||||
splitEditorPath: null,
|
||||
recentWorkspaces: loadRecentWorkspaces(),
|
||||
fileTreeFilter: "",
|
||||
cursorLine: 1,
|
||||
cursorCol: 1,
|
||||
settings: loadSettings(),
|
||||
};
|
||||
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
function emit() {
|
||||
listeners.forEach((l) => l());
|
||||
}
|
||||
|
||||
function getSnapshot() {
|
||||
return state;
|
||||
}
|
||||
|
||||
function subscribe(listener: () => void) {
|
||||
listeners.add(listener);
|
||||
return () => listeners.delete(listener);
|
||||
}
|
||||
|
||||
export function useStore() {
|
||||
return useSyncExternalStore(subscribe, getSnapshot);
|
||||
}
|
||||
|
||||
export function useStoreSelector<T>(selector: (s: EditorStore) => T): T {
|
||||
const select = useCallback(() => selector(getSnapshot()), [selector]);
|
||||
return useSyncExternalStore(subscribe, select);
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
openFile(path: string, name: string, content: string, language: string) {
|
||||
const existing = state.tabs.find((t) => t.path === path);
|
||||
if (existing) {
|
||||
state = { ...state, activeTabPath: path };
|
||||
} else {
|
||||
state = {
|
||||
...state,
|
||||
tabs: [...state.tabs, { path, name, content, savedContent: content, language }],
|
||||
activeTabPath: path,
|
||||
};
|
||||
}
|
||||
emit();
|
||||
},
|
||||
|
||||
closeTab(path: string) {
|
||||
const tabs = state.tabs.filter((t) => t.path !== path);
|
||||
let activeTabPath = state.activeTabPath;
|
||||
if (activeTabPath === path) {
|
||||
const idx = state.tabs.findIndex((t) => t.path === path);
|
||||
activeTabPath = tabs[Math.min(idx, tabs.length - 1)]?.path ?? null;
|
||||
}
|
||||
// Also close split if it was showing this file
|
||||
let splitEditorPath = state.splitEditorPath;
|
||||
if (splitEditorPath === path) {
|
||||
splitEditorPath = null;
|
||||
}
|
||||
state = { ...state, tabs, activeTabPath, splitEditorPath };
|
||||
emit();
|
||||
},
|
||||
|
||||
setActiveTab(path: string) {
|
||||
state = { ...state, activeTabPath: path };
|
||||
emit();
|
||||
},
|
||||
|
||||
updateContent(path: string, content: string) {
|
||||
state = {
|
||||
...state,
|
||||
tabs: state.tabs.map((t) => (t.path === path ? { ...t, content } : t)),
|
||||
};
|
||||
emit();
|
||||
},
|
||||
|
||||
markSaved(path: string) {
|
||||
state = {
|
||||
...state,
|
||||
tabs: state.tabs.map((t) =>
|
||||
t.path === path ? { ...t, savedContent: t.content } : t
|
||||
),
|
||||
};
|
||||
emit();
|
||||
},
|
||||
|
||||
setWorkspace(path: string | null) {
|
||||
state = { ...state, workspacePath: path };
|
||||
if (path) {
|
||||
const recents = [path, ...state.recentWorkspaces.filter((w) => w !== path)].slice(0, 10);
|
||||
state = { ...state, recentWorkspaces: recents };
|
||||
localStorage.setItem("recent-workspaces", JSON.stringify(recents));
|
||||
}
|
||||
emit();
|
||||
},
|
||||
|
||||
toggleSidebar() {
|
||||
state = { ...state, sidebarVisible: !state.sidebarVisible };
|
||||
emit();
|
||||
},
|
||||
|
||||
toggleTerminal() {
|
||||
state = { ...state, terminalVisible: !state.terminalVisible };
|
||||
emit();
|
||||
},
|
||||
|
||||
setSidebarView(view: "files" | "search") {
|
||||
state = { ...state, sidebarView: view, sidebarVisible: true };
|
||||
emit();
|
||||
},
|
||||
|
||||
setSearchQuery(query: string) {
|
||||
state = { ...state, searchQuery: query };
|
||||
emit();
|
||||
},
|
||||
|
||||
setSearchResults(results: SearchResult[]) {
|
||||
state = { ...state, searchResults: results };
|
||||
emit();
|
||||
},
|
||||
|
||||
setSearchCaseSensitive(value: boolean) {
|
||||
state = { ...state, searchCaseSensitive: value };
|
||||
emit();
|
||||
},
|
||||
|
||||
setSearchRegex(value: boolean) {
|
||||
state = { ...state, searchRegex: value };
|
||||
emit();
|
||||
},
|
||||
|
||||
setSplitEditor(path: string | null) {
|
||||
state = { ...state, splitEditorPath: path };
|
||||
emit();
|
||||
},
|
||||
|
||||
setFileTreeFilter(filter: string) {
|
||||
state = { ...state, fileTreeFilter: filter };
|
||||
emit();
|
||||
},
|
||||
|
||||
reorderTabs(fromIndex: number, toIndex: number) {
|
||||
const tabs = [...state.tabs];
|
||||
const [moved] = tabs.splice(fromIndex, 1);
|
||||
tabs.splice(toIndex, 0, moved);
|
||||
state = { ...state, tabs };
|
||||
emit();
|
||||
},
|
||||
|
||||
reloadFileContent(path: string, content: string) {
|
||||
state = {
|
||||
...state,
|
||||
tabs: state.tabs.map((t) =>
|
||||
t.path === path ? { ...t, content, savedContent: content } : t
|
||||
),
|
||||
};
|
||||
emit();
|
||||
},
|
||||
|
||||
setCursor(line: number, col: number) {
|
||||
state = { ...state, cursorLine: line, cursorCol: col };
|
||||
emit();
|
||||
},
|
||||
|
||||
updateTabLanguage(path: string, language: string) {
|
||||
state = {
|
||||
...state,
|
||||
tabs: state.tabs.map((t) => (t.path === path ? { ...t, language } : t)),
|
||||
};
|
||||
emit();
|
||||
},
|
||||
|
||||
updateSettings(partial: Partial<EditorSettings>) {
|
||||
const settings = { ...state.settings, ...partial };
|
||||
state = { ...state, settings };
|
||||
localStorage.setItem("editor-settings", JSON.stringify(settings));
|
||||
emit();
|
||||
},
|
||||
|
||||
getState() {
|
||||
return state;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user