Skip to content

Commit

Permalink
Merge pull request #4 from LonelyFellas/main
Browse files Browse the repository at this point in the history
✨Feature: 选择工程文件夹
  • Loading branch information
MikeGrateful authored Jan 21, 2025
2 parents 1118164 + 650ebcd commit 26419d0
Show file tree
Hide file tree
Showing 46 changed files with 798 additions and 102 deletions.
Binary file removed .DS_Store
Binary file not shown.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,9 @@ release/
.DS_Store
.vite/

# monaco-editor
src/renderer/public/monaco-editor

# mac
.DS_Store

8 changes: 0 additions & 8 deletions .vite/deps/_metadata.json

This file was deleted.

3 changes: 0 additions & 3 deletions .vite/deps/package.json

This file was deleted.

5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"cSpell.words": [
"iconfont"
]
}
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "vite",
"build": "tsc && vite build && electron-builder"
"build": "tsc && vite build --emptyOutDir && electron-builder"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -39,9 +39,16 @@
"vite": "^6.0.7",
"vite-plugin-electron": "^0.29.0",
"vite-plugin-electron-renderer": "^0.14.6",
"vite-plugin-monaco-editor": "^1.1.0",
"vitest": "^3.0.1"
},
"dependencies": {
"react-split-pane": "^0.1.92"
"@monaco-editor/react": "^4.6.0",
"allotment": "^1.20.2",
"clsx": "^2.1.1",
"electron-log": "^5.2.4",
"monaco-editor": "^0.52.2",
"tailwind-merge": "^2.6.0",
"zustand": "^5.0.3"
}
}
Binary file removed src/.DS_Store
Binary file not shown.
13 changes: 13 additions & 0 deletions src/far.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
declare global {
interface FileInfo {
name: string;
parentPath: string;
path: string;
isDir: boolean;
isEmpty: boolean;
isActive: boolean;
id: string;
files: FileInfo[];
}
}
export {};
22 changes: 19 additions & 3 deletions src/far/electron-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare namespace NodeJS {
interface ProcessEnv {
VSCODE_DEBUG?: 'true'
VSCODE_DEBUG?: "true";
/**
* The built directory structure
*
Expand All @@ -16,8 +16,24 @@ declare namespace NodeJS {
* │ └── index.html > Electron-Renderer
* ```
*/
APP_ROOT: string
APP_ROOT: string;
/** /dist/ or /public/ */
VITE_PUBLIC: string
VITE_PUBLIC: string;
}
}

declare namespace Electron {
interface IpcMain {
handle: <T extends keyof InvokeChannelMap>(
channel: T,
listener: (
event: Electron.IpcMainEvent,
...args: InvokeChannelMap[T][0]
) => Promise<InvokeChannelMap[T][1]> | InvokeChannelMap[T][1]
) => Promise<any>;
}
}

interface Window {
ipcRenderer: IpcRenderer;
}
8 changes: 8 additions & 0 deletions src/far/main/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"window": {
"width": 1024,
"height": 768
},
"layout": {
}
}
46 changes: 25 additions & 21 deletions src/far/main/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { app, BrowserWindow, shell, ipcMain } from "electron";
import { app, BrowserWindow, shell, ipcMain, session } from "electron";
import { createRequire } from "node:module";
import { fileURLToPath } from "node:url";
import path from "node:path";
import os from "node:os";
import "./listeners";
// import { update } from "./update";

const require = createRequire(import.meta.url);
Expand All @@ -20,8 +21,8 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
//
process.env.APP_ROOT = path.join(__dirname, "../..");

export const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron");
export const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist");
export const MAIN_DIST = path.join(process.env.APP_ROOT, "main");
export const RENDERER_DIST = path.join(process.env.APP_ROOT, "renderer");
export const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL;

process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL
Expand Down Expand Up @@ -49,8 +50,11 @@ async function createWindow() {
icon: path.join(process.env.VITE_PUBLIC, "favicon.ico"),
frame: false,
titleBarStyle: "hiddenInset",
width: 1024,
height: 768,
webPreferences: {
preload,
webSecurity: false,
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
// nodeIntegration: true,

Expand All @@ -60,8 +64,8 @@ async function createWindow() {
},
});

if (VITE_DEV_SERVER_URL) {
win.loadURL(VITE_DEV_SERVER_URL);
if (!app.isPackaged) {
win.loadURL(VITE_DEV_SERVER_URL!);
// Open devTool if the app is not packaged
win.webContents.openDevTools();
} else {
Expand Down Expand Up @@ -107,19 +111,19 @@ app.on("activate", () => {
}
});

// New window example arg: new windows url
ipcMain.handle("open-win", (_, arg) => {
const childWindow = new BrowserWindow({
webPreferences: {
preload,
nodeIntegration: true,
contextIsolation: false,
},
});

if (VITE_DEV_SERVER_URL) {
childWindow.loadURL(`${VITE_DEV_SERVER_URL}#${arg}`);
} else {
childWindow.loadFile(indexHtml, { hash: arg });
}
});
// // New window example arg: new windows url
// ipcMain.handle("open-win", (_, arg) => {
// const childWindow = new BrowserWindow({
// webPreferences: {
// preload,
// nodeIntegration: true,
// contextIsolation: false,
// },
// });

// if (VITE_DEV_SERVER_URL) {
// childWindow.loadURL(`${VITE_DEV_SERVER_URL}#${arg}`);
// } else {
// childWindow.loadFile(indexHtml, { hash: arg });
// }
// });
85 changes: 85 additions & 0 deletions src/far/main/listeners.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { dialog, ipcMain } from "electron";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
import { isEmptyDir } from "./utils";
import { randomUUID } from "node:crypto";

function getFileInfo(rootName = ""): FileInfo["files"] {
// 查看当前根目录的文件
const filesWithType = fs.readdirSync(rootName, { withFileTypes: true });
// 判断每一个子文件是否是文件夹,如果是文件夹,则判断是否是空文件夹
const dirFiles: FileInfo[] = [];
const fileFiles: FileInfo[] = [];
filesWithType.forEach((file) => {
const isDir = file.isDirectory();
(isDir ? dirFiles : fileFiles).push({
name: file.name,
parentPath: rootName,
path: path.join(rootName, file.name),
isDir: isDir,
isEmpty: isDir ? isEmptyDir(path.join(rootName, file.name)) : false,
isActive: false,
files: [],
id: randomUUID() as string,
});
});
// 对dirFiles进行排序
dirFiles.sort((a, b) => a.name.localeCompare(b.name));
// 对fileFiles进行排序
fileFiles.sort((a, b) => a.name.localeCompare(b.name));
return [...dirFiles, ...fileFiles];
}
/**
* 打开文件
*/
ipcMain.handle("open-file", () => {
const root = dialog.showOpenDialogSync({
// 用户目录
defaultPath: path.join(os.homedir(), "Dev/far-editor"),
// 只能选择文件夹
properties: ["openDirectory"],
});

if (root && Array.isArray(root) && root.length > 0) {
const files = getFileInfo(root[0]);

// 获取根目录名称
const name = path.basename(root[0]);

return {
name,
parentPath: "",
path: root[0],
isDir: true,
isEmpty: false,
isActive: true,
files,
id: randomUUID(),
};
}
return {
name: "",
parentPath: "",
path: "",
isDir: false,
isEmpty: false,
isActive: true,
files: [] as FileInfo[],
id: randomUUID(),
};
});

/**
* 展开或折叠文件夹
*/
ipcMain.handle("expand-or-collapse-file", (_, rootName: string) => {
return getFileInfo(rootName);
});

/**
* 读取文件内容
*/
ipcMain.handle("read-file", (_, fileName: string) => {
return fs.readFileSync(fileName, "utf-8");
});
15 changes: 15 additions & 0 deletions src/far/main/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Logger from "electron-log";
import fs from "node:fs";
/**
* 判断一个文件夹是否为空
* @param dir 文件夹路径
* @returns 是否为空
*/
export function isEmptyDir(dir: string) {
try {
return fs.readdirSync(dir).length === 0;
} catch (error) {
Logger.error(error);
return false;
}
}
Binary file removed src/renderer/.DS_Store
Binary file not shown.
1 change: 1 addition & 0 deletions src/renderer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />

<title>Electron + Vite + React</title>
</head>
<body>
Expand Down
32 changes: 19 additions & 13 deletions src/renderer/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import SplitPane, { Pane } from "react-split-pane";
import Tabbar from "./components/tabbar";
import { Tabbar, MainEmpty } from "@common";
import { useProject } from "./store";
import { Allotment } from "allotment";
import "allotment/dist/style.css";
import "./assets/iconfont.js";
import Editor from "./layout/editor/index";
import Slider from "./layout/slider";

export default function App() {
const isEmptyProject =
useProject((state) => state.projectInfo.files).length === 0;

return (
<div className="bg-primary h-screen flex flex-col">
<Tabbar />
<div className="flex-1 overflow-hidden relative">
{/* @ts-ignore */}
<SplitPane
split="vertical"
style={{ height: "100%" }}
resizerClassName="w-[2px]"
resizerStyle={{ backgroundColor: "yellow" }}
>
<Pane className="h-full bg-red-500">1</Pane>
<Pane className="h-full bg-blue-500">2</Pane>
</SplitPane>
<div className="flex-1">
<Allotment defaultSizes={[250, 500]}>
<Allotment.Pane minSize={160} visible={!isEmptyProject}>
<Slider />
</Allotment.Pane>
<Allotment.Pane minSize={220}>
{isEmptyProject ? <MainEmpty /> : <Editor />}
</Allotment.Pane>
</Allotment>
</div>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions src/renderer/src/assets/iconfont.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions src/renderer/src/common/components/icon-font/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { cn } from "@common";
interface IconFontProps {
name: string;
className?: string;
size?: number;
style?: React.CSSProperties;
onClick?: () => void;
}
export default function IconFont(props: IconFontProps) {
const handleClick = () => {};
return (
<svg
onClick={handleClick}
className={cn(
"custom-icon-font text-18px",
props?.className,
props?.size ? `text-${props.size}px` : ""
)}
aria-hidden="true"
style={props?.style}
>
<use xlinkHref={`#icon_${props.name}`}></use>
</svg>
);
}
3 changes: 3 additions & 0 deletions src/renderer/src/common/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as IconFont } from "./icon-font/index";
export { default as Tabbar } from "./tabbar";
export { default as MainEmpty } from "./main-empty";
Loading

0 comments on commit 26419d0

Please sign in to comment.