web based infinite canvas
1import { invoke } from "@tauri-apps/api/core";
2import { open, save } from "@tauri-apps/plugin-dialog";
3import { readTextFile, writeTextFile } from "@tauri-apps/plugin-fs";
4import { load } from "@tauri-apps/plugin-store";
5import type { DesktopFileOps, DirectoryEntry, FileHandle } from "inkfinite-core";
6
7export type { DesktopFileOps };
8
9const STORE_NAME = "inkfinite-desktop.json";
10const RECENT_FILES_KEY = "recentFiles";
11const WORKSPACE_DIR_KEY = "workspaceDir";
12const MAX_RECENT_FILES = 10;
13
14type FileEntry = { path: string; name: string; is_dir: boolean };
15
16/**
17 * Create desktop file operations using Tauri APIs
18 */
19export function createDesktopFileOps(): DesktopFileOps {
20 let storePromise: Promise<Awaited<ReturnType<typeof load>>> | null = null;
21
22 async function getStore() {
23 if (!storePromise) {
24 storePromise = load(STORE_NAME);
25 }
26 return storePromise;
27 }
28
29 async function showOpenDialog(): Promise<string | null> {
30 const result = await open({
31 multiple: false,
32 directory: false,
33 filters: [{ name: "Inkfinite Files", extensions: ["inkfinite.json", "json"] }],
34 });
35
36 return result;
37 }
38
39 async function showSaveDialog(defaultName?: string): Promise<string | null> {
40 const result = await save({
41 defaultPath: defaultName || "Untitled.inkfinite.json",
42 filters: [{ name: "Inkfinite Files", extensions: ["inkfinite.json"] }],
43 });
44
45 return result;
46 }
47
48 async function readFile(path: string): Promise<string> {
49 return readTextFile(path);
50 }
51
52 async function writeFile(path: string, content: string): Promise<void> {
53 await writeTextFile(path, content);
54 }
55
56 async function getRecentFiles(): Promise<FileHandle[]> {
57 const store = await getStore();
58 const recent = (await store.get<FileHandle[]>(RECENT_FILES_KEY)) || [];
59 return recent;
60 }
61
62 async function addRecentFile(handle: FileHandle): Promise<void> {
63 const store = await getStore();
64 const recent = (await store.get<FileHandle[]>(RECENT_FILES_KEY)) || [];
65 const filtered = recent.filter((f) => f.path !== handle.path);
66 const updated = [handle, ...filtered].slice(0, MAX_RECENT_FILES);
67 await store.set(RECENT_FILES_KEY, updated);
68 await store.save();
69 }
70
71 async function removeRecentFile(path: string): Promise<void> {
72 const store = await getStore();
73 const recent = (await store.get<FileHandle[]>(RECENT_FILES_KEY)) || [];
74 const filtered = recent.filter((f) => f.path !== path);
75 await store.set(RECENT_FILES_KEY, filtered);
76 await store.save();
77 }
78
79 async function clearRecentFiles(): Promise<void> {
80 const store = await getStore();
81 await store.set(RECENT_FILES_KEY, []);
82 await store.save();
83 }
84
85 async function getWorkspaceDir(): Promise<string | null> {
86 const store = await getStore();
87 const workspace = (await store.get<string | null>(WORKSPACE_DIR_KEY)) || null;
88 return workspace;
89 }
90
91 async function setWorkspaceDir(path: string | null): Promise<void> {
92 const store = await getStore();
93 await store.set(WORKSPACE_DIR_KEY, path);
94 await store.save();
95 }
96
97 async function pickWorkspaceDir(): Promise<string | null> {
98 const result = await invoke<string | null>("pick_workspace_directory");
99 if (result) {
100 await setWorkspaceDir(result);
101 }
102 return result;
103 }
104
105 async function readDirectory(directory: string, pattern?: string): Promise<DirectoryEntry[]> {
106 const entries = await invoke<FileEntry[]>("read_directory", { directory, pattern: pattern || "*.inkfinite.json" });
107
108 return entries.map((e) => ({ path: e.path, name: e.name, isDir: e.is_dir }));
109 }
110
111 async function renameFile(oldPath: string, newPath: string): Promise<void> {
112 await invoke("rename_file", { oldPath, newPath });
113 }
114
115 async function deleteFile(path: string): Promise<void> {
116 await invoke("delete_file", { filePath: path });
117 }
118
119 return {
120 showOpenDialog,
121 showSaveDialog,
122 readFile,
123 writeFile,
124 getRecentFiles,
125 addRecentFile,
126 removeRecentFile,
127 clearRecentFiles,
128 getWorkspaceDir,
129 setWorkspaceDir,
130 pickWorkspaceDir,
131 readDirectory,
132 renameFile,
133 deleteFile,
134 };
135}