add partially working file browser
This commit is contained in:
parent
c765b34019
commit
0d874a7cf1
38
index.html
38
index.html
|
|
@ -1,24 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Miller code editor</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>Miller code editor - welcome!
|
||||
<button id="addEditor">Add Editor</button>
|
||||
<button id="openFolder">Open Folder</button>
|
||||
<strong id="currentFolder">N/A</strong>
|
||||
</header>
|
||||
<aside>
|
||||
<nav>
|
||||
<p>Files go here</p>
|
||||
<p>foo</p>
|
||||
<p>bar</p>
|
||||
</nav>
|
||||
</aside>
|
||||
<main id="editorGrid">
|
||||
</main>
|
||||
<script type="module" src="/src/app/renderer.ts"></script>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Miller code editor</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
Miller code editor - welcome!
|
||||
<button id="addEditor">Add Editor</button>
|
||||
</header>
|
||||
<aside>
|
||||
<nav></nav>
|
||||
</aside>
|
||||
<main id="editorGrid"></main>
|
||||
<script type="module" src="/src/app/renderer.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -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<FolderTree | null>(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 <details>
|
||||
// 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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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<FolderTree[]> {
|
||||
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<FolderTree | null> {
|
||||
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 [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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<FolderTree | null>,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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<string>;
|
||||
// Add other methods as needed
|
||||
};
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: {
|
||||
openFolder: () => Promise<FolderTree | null>;
|
||||
// Add other methods as needed
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export { FolderTree };
|
||||
|
|
|
|||
|
|
@ -11,5 +11,9 @@
|
|||
"outDir": "dist",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src/types/global.d.ts",
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue