diff --git a/index.html b/index.html index eea3bd7..5edd686 100644 --- a/index.html +++ b/index.html @@ -1,24 +1,18 @@ - + - - - Miller code editor - - -
Miller code editor - welcome! - - - N/A -
- -
-
- - + + + Miller code editor + + +
+ Miller code editor - welcome! + +
+ +
+ + diff --git a/src/app/foldernav.ts b/src/app/foldernav.ts index efbbd4a..7effbad 100644 --- a/src/app/foldernav.ts +++ b/src/app/foldernav.ts @@ -1,7 +1,94 @@ import van from "vanjs-core"; const v = van.tags; +import type { FolderTree } from "../types/global"; -document.getElementById("openFolder").addEventListener("click", async () => { - const folderPath = await window.electronAPI.openFolder(); - document.getElementById("currentFolder").innerText = folderPath; -}); +const folderTreeState = van.state(null); + +async function openFolder() { + const folderTree = await window.electronAPI.openFolder(); + if (!folderTree) return; + folderTreeState.val = folderTree; +} + +const FolderTreeView = () => { + if (!folderTreeState.val) { + return v.div( + { style: "text-align: center; margin-top: 25px;" }, + v.p("No folder selected!"), + v.p( + v.button( + { + onclick: async () => { + const folderTree = + await window.electronAPI.openFolder(); + if (!folderTree) return; + folderTreeState.val = folderTree; + }, + }, + "Open Folder", + ), + ), + ); + } + return v.div( + v.span( + { style: "font-weight: bold; margin-right: 1em;" }, + folderTreeState.val?.name ?? "No folder", + ), + v.button( + { + onclick: openFolder, + title: "Refresh current folder", + style: "margin-right: 0.5em;", + }, + "⟳", + ), + v.button( + { + onclick: openFolder, + title: "Open another folder", + style: "margin-right: 0.5em;", + }, + "📁", + ), + folderTreeState.val.children?.map(FsItemView) || [], + ); +}; + +const FsItemView = (tree: FolderTree): HTMLElement | string => { + if (tree.type === "file") return v.p(tree.name); + return v.details(v.summary(tree.name), tree.children?.map(FsItemView)); + + // if (tree.type === "file") return v.div(" ", tree.name); + // // Use a state to track open/close instead of
+ // const isOpen = van.state(0); + // return v.div( + // { + // style: "cursor: pointer; user-select: none; font-weight: bold;", + // onclick: () => isOpen.val++, + // }, + // () => (isOpen.val ? "▼ " : "▶ "), + // tree.name, + // ); + // // return v.div( + // // v.span( + // // { + // // style: "cursor: pointer; user-select: none; font-weight: bold;", + // // onclick: () => { + // // console.log("opening"); + // // isOpen.val++; + // // }, + // // }, + // // () => (isOpen.val ? "▼ " : "▶ "), + // // tree.name, + // // ), + // // " ", + // // isOpen.val, + // // ); +}; + +// Mount the folder tree view reactively to the nav +const nav = document.querySelector("aside nav"); +if (nav) { + van.add(nav, () => FolderTreeView()); +} diff --git a/src/app/index.css b/src/app/index.css index a9ccef8..3465153 100644 --- a/src/app/index.css +++ b/src/app/index.css @@ -33,7 +33,6 @@ aside { } /* Editor grid stuff */ - #editorGrid { display: grid; height: minmax(0, 100%); @@ -52,3 +51,16 @@ aside { } /* Navbar styling */ +nav details { + margin-bottom: 0px; +} +nav details[open] summary, +nav details summary, +nav div p { + margin-bottom: 5px; + line-height: 1.2; +} +nav details[open] :not(summary) { + margin-left: 16px; +} +/* closed detail summary */ diff --git a/src/main/fileOperations.ts b/src/main/fileOperations.ts index f3b1cb3..491db0c 100644 --- a/src/main/fileOperations.ts +++ b/src/main/fileOperations.ts @@ -3,26 +3,57 @@ import { dialog, BrowserWindow } from "electron"; import fs from "fs"; +const fsp = fs.promises; import path from "path"; -export async function handleOpenFolder(mainWindow: BrowserWindow) { +type FolderTree = { + name: string; + path: string; + type: "directory" | "file"; + children?: FolderTree[]; +}; + +async function readTree(dirPath: string): Promise { + const stats = await fsp.stat(dirPath); + if (!stats.isDirectory()) return []; + const names = await fsp.readdir(dirPath); + const children = await Promise.all( + names.map(async (name) => { + const fullPath = path.join(dirPath, name); + const stat = await fsp.stat(fullPath); + if (stat.isDirectory()) { + return { + name, + path: fullPath, + type: "directory" as const, + children: await readTree(fullPath), + }; + } else { + return { + name, + path: fullPath, + type: "file" as const, + }; + } + }) + ); + return children; +} + +export async function handleOpenFolder( + mainWindow: BrowserWindow, +): Promise { const result = await dialog.showOpenDialog(mainWindow, { properties: ["openDirectory"], }); if (!result.canceled && result.filePaths.length > 0) { const folderPath = result.filePaths[0]; - return folderPath; + return { + name: path.basename(folderPath), + path: folderPath, + type: "directory", + children: await readTree(folderPath), + }; } return null; } - -export function readFolderContents(folderPath: string): string[] { - try { - return fs - .readdirSync(folderPath) - .map((file) => path.join(folderPath, file)); - } catch (err) { - console.error("Error reading folder:", err); - return []; - } -} diff --git a/src/main/main.ts b/src/main/main.ts index d7710ec..0420e0c 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,5 +1,5 @@ import { app, BrowserWindow, ipcMain } from "electron"; -import { handleOpenFolder, readFolderContents } from "./fileOperations"; +import { handleOpenFolder } from "./fileOperations"; import path from "node:path"; import started from "electron-squirrel-startup"; @@ -45,13 +45,13 @@ app.whenReady().then(() => { return await handleOpenFolder(senderWindow); }); createWindow(); - app.on("activate", function () { - if (BrowserWindow.getAllWindows().length === 0) createWindow(); - }); + if (process.platform === "darwin") { + app.on("activate", function () { + if (BrowserWindow.getAllWindows().length === 0) createWindow(); + }); + } }); -app.on("ready", createWindow); - // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. diff --git a/src/preload.ts b/src/preload.ts index 4ce1923..97a4d39 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -2,7 +2,9 @@ // https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts import { contextBridge, ipcRenderer } from "electron"; +import type { FolderTree } from "./types/global"; contextBridge.exposeInMainWorld("electronAPI", { - openFolder: () => ipcRenderer.invoke("dialog:openFolder"), + openFolder: () => + ipcRenderer.invoke("dialog:openFolder") as Promise, }); diff --git a/src/types/global.d.ts b/src/types/global.d.ts index 76fe0c9..0940b40 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -1,9 +1,21 @@ // src/types/global.d.ts +// FolderTree type for folder structure +type FolderTree = { + name: string; + path: string; + type: "directory" | "file"; + children?: FolderTree[]; +}; + // Extend the Window interface to include electronAPI -interface Window { - electronAPI: { - openFolder: () => Promise; - // Add other methods as needed - }; +declare global { + interface Window { + electronAPI: { + openFolder: () => Promise; + // Add other methods as needed + }; + } } + +export { FolderTree }; diff --git a/tsconfig.json b/tsconfig.json index 74434b2..4de3018 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,5 +11,9 @@ "outDir": "dist", "moduleResolution": "node", "resolveJsonModule": true - } + }, + "include": [ + "src/types/global.d.ts", + "src/**/*.ts" + ] }