feat: 系统终端

This commit is contained in:
bietiaop
2025-02-01 20:35:01 +08:00
parent 5120786708
commit 4157746478
15 changed files with 349 additions and 259 deletions

View File

@@ -1,4 +1,11 @@
import { DndContext, DragEndEvent, closestCenter } from '@dnd-kit/core'
import {
DndContext,
DragEndEvent,
PointerSensor,
closestCenter,
useSensor,
useSensors
} from '@dnd-kit/core'
import {
SortableContext,
arrayMove,
@@ -9,10 +16,11 @@ import { useEffect, useState } from 'react'
import toast from 'react-hot-toast'
import { IoAdd, IoClose } from 'react-icons/io5'
import { SortableTab } from '@/components/sortable_tab'
import { TabList, TabPanel, Tabs } from '@/components/tabs'
import { SortableTab } from '@/components/tabs/sortable_tab.tsx'
import { TerminalInstance } from '@/components/terminal/terminal-instance'
import terminalManager from '@/controllers/terminal_manager'
import WebUIManager from '@/controllers/webui_manager'
interface TerminalTab {
@@ -29,9 +37,9 @@ export default function TerminalPage() {
WebUIManager.getTerminalList().then((terminals) => {
if (terminals.length === 0) return
const newTabs = terminals.map((terminal, index) => ({
const newTabs = terminals.map((terminal) => ({
id: terminal.id,
title: `Terminal ${index + 1}`
title: terminal.id
}))
setTabs(newTabs)
@@ -44,7 +52,7 @@ export default function TerminalPage() {
const { id } = await WebUIManager.createTerminal(80, 24)
const newTab = {
id,
title: `Terminal ${tabs.length + 1}`
title: id
}
setTabs((prev) => [...prev, newTab])
@@ -58,10 +66,16 @@ export default function TerminalPage() {
const closeTerminal = async (id: string) => {
try {
await WebUIManager.closeTerminal(id)
setTabs((prev) => prev.filter((tab) => tab.id !== id))
terminalManager.removeTerminal(id)
if (selectedTab === id) {
setSelectedTab(tabs[0]?.id || '')
const previousIndex = tabs.findIndex((tab) => tab.id === id) - 1
if (previousIndex >= 0) {
setSelectedTab(tabs[previousIndex].id)
} else {
setSelectedTab(tabs[0]?.id || '')
}
}
setTabs((prev) => prev.filter((tab) => tab.id !== id))
} catch (error) {
toast.error('关闭终端失败')
}
@@ -78,25 +92,49 @@ export default function TerminalPage() {
}
}
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 8
}
})
)
return (
<div className="flex flex-col h-full gap-2 p-4">
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<Tabs activeKey={selectedTab} onChange={setSelectedTab}>
<div className="flex items-center gap-2">
<TabList className="flex-1">
<div className="flex flex-col gap-2 p-4">
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<Tabs
activeKey={selectedTab}
onChange={setSelectedTab}
className="h-full overflow-hidden"
>
<div className="flex items-center gap-2 flex-shrink-0 flex-grow-0">
<TabList className="flex-1 !overflow-x-auto hide-scrollbar">
<SortableContext
items={tabs}
strategy={horizontalListSortingStrategy}
>
{tabs.map((tab) => (
<SortableTab key={tab.id} id={tab.id} value={tab.id}>
<SortableTab
key={tab.id}
id={tab.id}
value={tab.id}
isSelected={selectedTab === tab.id}
className="flex gap-2 items-center"
>
{tab.title}
<Button
isIconOnly
radius="full"
variant="flat"
size="sm"
className="ml-2"
className="min-w-0 w-4 h-4"
onPress={() => closeTerminal(tab.id)}
color={selectedTab === tab.id ? 'danger' : 'default'}
>
<IoClose />
</Button>
@@ -106,15 +144,21 @@ export default function TerminalPage() {
</TabList>
<Button
isIconOnly
color="danger"
size="sm"
variant="flat"
onPress={createNewTerminal}
startContent={<IoAdd />}
className="text-xl"
/>
</div>
{tabs.map((tab) => (
<TabPanel key={tab.id} value={tab.id} className="flex-1">
<TerminalInstance id={tab.id} />
</TabPanel>
))}
<div className="flex-grow overflow-hidden">
{tabs.map((tab) => (
<TabPanel key={tab.id} value={tab.id} className="h-full">
<TerminalInstance id={tab.id} />
</TabPanel>
))}
</div>
</Tabs>
</DndContext>
</div>