From 8879c9e16504059daa3c63a835e09f5505067483 Mon Sep 17 00:00:00 2001 From: DtHnAme <32587943+DtHnAme@users.noreply.github.com> Date: Mon, 23 Sep 2024 22:45:45 +0800 Subject: [PATCH] add closed connections sub page (#180) --- package.json | 1 + pnpm-lock.yaml | 3 + .../connections/connection-item.tsx | 12 +- src/renderer/src/pages/connections.tsx | 130 +++++++++++++++--- src/shared/types.d.ts | 1 + 5 files changed, 119 insertions(+), 28 deletions(-) diff --git a/package.json b/package.json index d6346c8..aff43f8 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "chokidar": "^4.0.0", "dayjs": "^1.11.13", "express": "^4.21.0", + "lodash": "^4.17.21", "recharts": "^2.12.7", "webdav": "^5.7.1", "ws": "^8.18.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ab861e..338730d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: express: specifier: ^4.21.0 version: 4.21.0 + lodash: + specifier: ^4.17.21 + version: 4.17.21 recharts: specifier: ^2.12.7 version: 2.12.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) diff --git a/src/renderer/src/components/connections/connection-item.tsx b/src/renderer/src/components/connections/connection-item.tsx index 7552e6d..d46920b 100644 --- a/src/renderer/src/components/connections/connection-item.tsx +++ b/src/renderer/src/components/connections/connection-item.tsx @@ -2,7 +2,7 @@ import { Button, Card, CardFooter, CardHeader, Chip } from '@nextui-org/react' import { calcTraffic } from '@renderer/utils/calc' import dayjs from 'dayjs' import React, { useEffect } from 'react' -import { CgClose } from 'react-icons/cg' +import { CgClose, CgTrash } from 'react-icons/cg' interface Props { index: number @@ -10,7 +10,7 @@ interface Props { selected: IMihomoConnectionDetail | undefined setSelected: React.Dispatch> setIsDetailModalOpen: React.Dispatch> - close: (id: string) => Promise + close: (id: string) => void } const ConnectionItem: React.FC = (props) => { @@ -35,7 +35,7 @@ const ConnectionItem: React.FC = (props) => {
- + {info.metadata.type}({info.metadata.network.toUpperCase()})
@@ -54,7 +54,7 @@ const ConnectionItem: React.FC = (props) => { onWheel={(e) => { e.currentTarget.scrollLeft += e.deltaY }} - className="overscroll-contain pt-1 flex justify-start gap-1 overflow-x-auto no-scrollbar" + className="overscroll-contain pt-2 flex justify-start gap-1 overflow-x-auto no-scrollbar" > = (props) => {
diff --git a/src/renderer/src/pages/connections.tsx b/src/renderer/src/pages/connections.tsx index 44de7f4..ef18eb6 100644 --- a/src/renderer/src/pages/connections.tsx +++ b/src/renderer/src/pages/connections.tsx @@ -1,29 +1,34 @@ import BasePage from '@renderer/components/base/base-page' import { mihomoCloseAllConnections, mihomoCloseConnection } from '@renderer/utils/ipc' -import { useEffect, useMemo, useState } from 'react' -import { Badge, Button, Divider, Input, Select, SelectItem } from '@nextui-org/react' +import { Key, useEffect, useMemo, useState } from 'react' +import { Badge, Button, Divider, Input, Select, SelectItem, Tab, Tabs } from '@nextui-org/react' import { calcTraffic } from '@renderer/utils/calc' import ConnectionItem from '@renderer/components/connections/connection-item' import { Virtuoso } from 'react-virtuoso' import dayjs from 'dayjs' import ConnectionDetailModal from '@renderer/components/connections/connection-detail-modal' -import { CgClose } from 'react-icons/cg' +import { CgClose, CgTrash } from 'react-icons/cg' import { useAppConfig } from '@renderer/hooks/use-app-config' import { HiSortAscending, HiSortDescending } from 'react-icons/hi' import { includesIgnoreCase } from '@renderer/utils/includes' +import { differenceWith, unionWith } from 'lodash' -let preData: IMihomoConnectionDetail[] = [] +let cachedConnections: IMihomoConnectionDetail[] = [] const Connections: React.FC = () => { const [filter, setFilter] = useState('') const { appConfig, patchAppConfig } = useAppConfig() const { connectionDirection = 'asc', connectionOrderBy = 'time' } = appConfig || {} const [connectionsInfo, setConnectionsInfo] = useState() - const [connections, setConnections] = useState([]) + const [allConnections, setAllConnections] = useState(cachedConnections) + const [activeConnections, setActiveConnections] = useState([]) + const [closedConnections, setClosedConnections] = useState([]) const [isDetailModalOpen, setIsDetailModalOpen] = useState(false) const [selected, setSelected] = useState() + const [tab, setTab] = useState('active') const filteredConnections = useMemo(() => { + const connections = tab === 'active' ? activeConnections : closedConnections if (connectionOrderBy) { connections.sort((a, b) => { if (connectionDirection === 'asc') { @@ -60,29 +65,69 @@ const Connections: React.FC = () => { const raw = JSON.stringify(connection) return includesIgnoreCase(raw, filter) }) - }, [connections, filter, connectionDirection, connectionOrderBy]) + }, [activeConnections, closedConnections, filter, connectionDirection, connectionOrderBy]) + + const closeAllConnections = () => { + tab === 'active' ? mihomoCloseAllConnections() : trashAllClosedConnection() + } + + const closeConnection = (id: string) => { + tab === 'active' ? mihomoCloseConnection(id) : trashClosedConnection(id) + } + + const trashAllClosedConnection = () => { + const trashIds = closedConnections.map((conn) => conn.id) + setAllConnections((allConns) => allConns.filter((conn) => !trashIds.includes(conn.id))) + setClosedConnections([]) + + cachedConnections = allConnections + } + + const trashClosedConnection = (id: string) => { + setAllConnections((allConns) => allConns.filter((conn) => conn.id != id)) + setClosedConnections((closedConns) => closedConns.filter((conn) => conn.id != id)) + + cachedConnections = allConnections + } useEffect(() => { window.electron.ipcRenderer.on('mihomoConnections', (_e, info: IMihomoConnectionsInfo) => { setConnectionsInfo(info) - const newConns: IMihomoConnectionDetail[] = [] - for (const conn of info.connections ?? []) { - const preConn = preData?.find((c) => c.id === conn.id) - if (preConn) { - conn.downloadSpeed = conn.download - preConn.download - conn.uploadSpeed = conn.upload - preConn.upload + if (!info.connections) return + const allConns = unionWith(allConnections, activeConnections, (a, b) => a.id === b.id) + + const activeConns = info.connections.map((conn) => { + const preConn = activeConnections.find((c) => c.id === conn.id) + const downloadSpeed = preConn ? conn.download - preConn.download : 0 + const uploadSpeed = preConn ? conn.upload - preConn.upload : 0 + return { + ...conn, + isActive: true, + downloadSpeed: downloadSpeed, + uploadSpeed: uploadSpeed, } - newConns.push(conn) - } - setConnections(newConns) - preData = newConns + }) + const closedConns = differenceWith(allConns, activeConns, (a, b) => a.id === b.id).map((conn) => { + return { + ...conn, + isActive: false, + downloadSpeed: 0, + uploadSpeed: 0, + } + }) + + setActiveConnections(activeConns) + setClosedConnections(closedConns) + setAllConnections(allConns.slice(-(activeConns.length + 200))) + + cachedConnections = allConnections }) return (): void => { window.electron.ipcRenderer.removeAllListeners('mihomoConnections') } - }, []) + }, [allConnections, activeConnections, closedConnections]) return ( { variant="light" onPress={() => { if (filter === '') { - mihomoCloseAllConnections() + closeAllConnections() } else { filteredConnections.forEach((conn) => { - mihomoCloseConnection(conn.id) + closeConnection(conn.id) }) } }} > - + {tab === 'active' ? () : ()}
@@ -131,6 +176,47 @@ const Connections: React.FC = () => { )}
+ { + setTab(key as string) + }} + > + + 活动中 + + } + /> + + 已关闭 + + } + /> + {