Skip to content

Commit ab3f6d7

Browse files
committed
添加终端功能
1 parent ec265d8 commit ab3f6d7

8 files changed

Lines changed: 199 additions & 52 deletions

File tree

bun.lock

Lines changed: 20 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
"wrangler": "^4.50.0"
2828
},
2929
"dependencies": {
30+
"@dnd-kit/core": "^6.3.1",
31+
"@dnd-kit/modifiers": "^9.0.0",
3032
"@hookform/resolvers": "^5.2.2",
3133
"@radix-ui/react-accordion": "^1.2.12",
3234
"@radix-ui/react-alert-dialog": "^1.1.15",
@@ -60,6 +62,7 @@
6062
"@tauri-apps/api": "^2.9.0",
6163
"@tauri-apps/plugin-opener": "^2.5.2",
6264
"@tauri-apps/plugin-os": "^2.3.2",
65+
"@xterm/xterm": "^5.5.0",
6366
"class-variance-authority": "^0.7.1",
6467
"clsx": "^2.1.1",
6568
"cmdk": "^1.1.1",

src/render.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { routeTree } from "./routeTree.gen";
1010
const Router = createRouter({
1111
routeTree,
1212
history: createMemoryHistory({
13-
initialEntries: ["/app"],
13+
initialEntries: ["/boot"],
1414
}),
1515
});
1616
declare module "@tanstack/react-router" {

src/routes/boot.tsx

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import "@xterm/xterm/css/xterm.css";
2+
import { DndContext, useDraggable } from "@dnd-kit/core";
3+
import { CSS } from "@dnd-kit/utilities";
4+
import { useEffect, useRef, useState } from "react";
5+
import { Terminal as XTerm } from "@xterm/xterm";
6+
import { restrictToWindowEdges } from "@dnd-kit/modifiers";
7+
import { createFileRoute, Outlet, useNavigate } from "@tanstack/react-router";
8+
import { Loading } from "@/components/loading";
9+
import { AppStore } from "./boot/app";
10+
11+
export const Route = createFileRoute("/boot")({
12+
component: Component,
13+
pendingComponent: () => {
14+
return <Loading hint_text="正在引导应用程序" mode="screen" />;
15+
},
16+
});
17+
function Component() {
18+
const navigate = useNavigate();
19+
const [position, set_position] = useState<{ x: number; y: number }>({
20+
x: 4,
21+
y: 4,
22+
});
23+
const [is_open, set_is_open] = useState(false);
24+
useEffect(() => {
25+
const update = (e: KeyboardEvent) => {
26+
if (e.key === "`") {
27+
set_is_open((v) => !v);
28+
}
29+
};
30+
addEventListener("keydown", update);
31+
return () => {
32+
removeEventListener("keydown", update);
33+
};
34+
}, []);
35+
//导航到应用
36+
useEffect(() => {
37+
navigate({ to: "/boot/app" });
38+
}, []);
39+
return (
40+
<>
41+
<Outlet />
42+
<DndContext
43+
modifiers={[restrictToWindowEdges]}
44+
onDragEnd={(e) =>
45+
set_position((v) => ({
46+
x: v.x + e.delta.x,
47+
y: v.y + e.delta.y,
48+
}))
49+
}
50+
>
51+
{is_open && <Window position={position} />}
52+
</DndContext>
53+
</>
54+
);
55+
}
56+
57+
function Window(props: { position: { x: number; y: number } }) {
58+
const { setNodeRef, transform, attributes, listeners } = useDraggable({
59+
id: "terminal",
60+
});
61+
return (
62+
<div
63+
ref={setNodeRef}
64+
className="absolute"
65+
style={{
66+
top: props.position.y,
67+
left: props.position.x,
68+
transform: CSS.Translate.toString(transform),
69+
}}
70+
>
71+
<div {...attributes} {...listeners} className="h-6 bg-blue-500" />
72+
<Terminal />
73+
</div>
74+
);
75+
}
76+
77+
function Terminal() {
78+
const self_ref = useRef(null);
79+
useEffect(() => {
80+
if (self_ref.current) {
81+
const xterm = new XTerm({ fontSize: 12 });
82+
xterm.open(self_ref.current);
83+
const prompt = ">";
84+
let input_buffer = "";
85+
xterm.writeln(
86+
`开发者终端
87+
\r使用"?"命令获取帮助`,
88+
);
89+
xterm.write(prompt);
90+
xterm.onData(async (str) => {
91+
switch (str) {
92+
case "\r":
93+
xterm.write("\r\n");
94+
await execute_command(xterm, input_buffer as any);
95+
input_buffer = "";
96+
xterm.write(prompt);
97+
break;
98+
case "\x7F":
99+
case "\b":
100+
if (!input_buffer) return;
101+
input_buffer = input_buffer.slice(0, -1);
102+
xterm.write("\b \b");
103+
break;
104+
default:
105+
input_buffer += str;
106+
xterm.write(str);
107+
break;
108+
}
109+
});
110+
return () => xterm.dispose();
111+
}
112+
}, []);
113+
return <div ref={self_ref} />;
114+
}
115+
116+
type Command = "?" | "db_close";
117+
async function execute_command(xterm: XTerm, command: Command) {
118+
switch (command) {
119+
case "?":
120+
xterm.writeln(`db_close:关闭数据库连接`);
121+
break;
122+
case "db_close":
123+
await AppStore.getState().db.close();
124+
break;
125+
default:
126+
xterm.writeln(`未知命令:${command}`);
127+
break;
128+
}
129+
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ export const AppStore = createStore(
7979
on_resets: new Array<() => void | Promise<void>>(),
8080
})),
8181
);
82-
export const Route = createFileRoute("/app")({
82+
export const Route = createFileRoute("/boot/app")({
8383
component: Component,
8484
pendingComponent: () => {
8585
return <Loading hint_text="我现在正在初始化这个程序" mode="screen" />;
@@ -116,7 +116,7 @@ function Component() {
116116
>();
117117
//导航到子路由
118118
useEffect(() => {
119-
navigate({ to: "/app/login" });
119+
navigate({ to: "/boot/app/login" });
120120
}, []);
121121
//窗口配置
122122
useEffect(() => {
@@ -266,7 +266,7 @@ function Component() {
266266
AppPath.DatabaseFile,
267267
);
268268
router.clearCache();
269-
await navigate({ to: "/app/login" });
269+
await navigate({ to: "/boot/app/login" });
270270
for (const callback of AppStore.getState().on_resets) {
271271
await callback();
272272
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export const HomeStore = createStore(
6262
connections: new Map<string, Connection>(),
6363
})),
6464
);
65-
export const Route = createFileRoute("/app/home/$user_id")({
65+
export const Route = createFileRoute("/boot/app/home/$user_id")({
6666
component: Component,
6767
pendingComponent: () => <Loading hint_text="正在初始化主界面" />,
6868
errorComponent: () => <Errored hint_text="emmm...出错了呢..." />,
@@ -334,7 +334,7 @@ function Component() {
334334
{friend_virtualizer.getVirtualItems().map((value) => (
335335
<Item key={value.key} className="rounded-none" asChild>
336336
<Link
337-
to="/app/home/$user_id/chat/$friend_id"
337+
to="/boot/app/home/$user_id/chat/$friend_id"
338338
params={{
339339
...params,
340340
friend_id: friends[value.index].id,

0 commit comments

Comments
 (0)