mirror of
https://github.com/woodchen-ink/clash-and-dashboard.git
synced 2025-07-18 14:01:56 +08:00
Upgrade: react 18 && connection table scroll perf
This commit is contained in:
parent
fa98e692bd
commit
2866bafc6a
18
package.json
18
package.json
@ -33,10 +33,10 @@
|
||||
"@types/react-table": "^7.7.10",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.16.0",
|
||||
"@typescript-eslint/parser": "^5.16.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.17.0",
|
||||
"@typescript-eslint/parser": "^5.17.0",
|
||||
"@vitejs/plugin-react": "^1.2.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint": "^8.12.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^16.1.4",
|
||||
"eslint-config-standard-with-typescript": "^21.0.1",
|
||||
@ -44,18 +44,19 @@
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-react-hooks": "^4.4.0",
|
||||
"sass": "^1.49.9",
|
||||
"type-fest": "^2.12.1",
|
||||
"typescript": "^4.6.3",
|
||||
"vite": "^2.8.6",
|
||||
"vite": "^2.9.0",
|
||||
"vite-plugin-pwa": "^0.11.13",
|
||||
"vite-plugin-windicss": "^1.8.3",
|
||||
"vite-tsconfig-paths": "^3.4.1",
|
||||
"windicss": "^3.5.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/react-table": "^8.0.0-alpha.8",
|
||||
"@react-hookz/web": "^13.1.0",
|
||||
"@tanstack/react-table": "^8.0.0-alpha.11",
|
||||
"axios": "^0.26.1",
|
||||
"classnames": "^2.3.1",
|
||||
"dayjs": "^1.11.0",
|
||||
@ -64,10 +65,9 @@
|
||||
"jotai": "^1.6.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"neverthrow": "^4.3.1",
|
||||
"react": "^18.0.0-rc.3",
|
||||
"react-dom": "^18.0.0-rc.3",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-router-dom": "^6.2.2",
|
||||
"react-use": "^17.3.2",
|
||||
"react-virtualized-auto-sizer": "^1.0.6",
|
||||
"react-window": "^1.8.6",
|
||||
"swr": "^1.2.2",
|
||||
|
721
pnpm-lock.yaml
generated
721
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,9 @@
|
||||
import { columnFilterRowsFn, createTable, sortRowsFn } from '@tanstack/react-table'
|
||||
import { type ColumnSort } from '@tanstack/react-table/build/types/features/Sorting'
|
||||
import { useIntersectionObserver, useSyncedRef } from '@react-hookz/web/esm'
|
||||
import { useTable, columnFilterRowsFn, createTable, sortRowsFn } from '@tanstack/react-table'
|
||||
import classnames from 'classnames'
|
||||
import produce from 'immer'
|
||||
import { groupBy } from 'lodash-es'
|
||||
import { useMemo, useLayoutEffect, useRef, useState, useEffect } from 'react'
|
||||
import { useLatest, useScroll } from 'react-use'
|
||||
|
||||
import { Header, Checkbox, Modal, Icon, Drawer, Card, Button } from '@components'
|
||||
import { fromNow } from '@lib/date'
|
||||
@ -47,7 +46,7 @@ function formatSpeed (upload: number, download: number) {
|
||||
}
|
||||
}
|
||||
|
||||
const table = createTable().RowType<FormatConnection>()
|
||||
const table = createTable<FormatConnection>()
|
||||
|
||||
export default function Connections () {
|
||||
const { translation, lang } = useI18n()
|
||||
@ -95,8 +94,8 @@ export default function Connections () {
|
||||
}, [connections])
|
||||
|
||||
// table
|
||||
const tableRef = useRef<HTMLDivElement>(null)
|
||||
const { x: scrollX } = useScroll(tableRef)
|
||||
const pinRef = useRef<HTMLTableCellElement>(null)
|
||||
const intersection = useIntersectionObserver(pinRef, { threshold: [1] })
|
||||
const columns = useMemo(
|
||||
() => table.createColumns([
|
||||
table.createDataColumn(Columns.Host, { minWidth: 260, width: 260, header: t(`columns.${Columns.Host}`) }),
|
||||
@ -113,28 +112,28 @@ export default function Connections () {
|
||||
width: 200,
|
||||
sortDescFirst: true,
|
||||
sortType (rowA, rowB) {
|
||||
const speedA = rowA.original.speed
|
||||
const speedB = rowB.original.speed
|
||||
const speedA = rowA.original?.speed ?? { upload: 0, download: 0 }
|
||||
const speedB = rowB.original?.speed ?? { upload: 0, download: 0 }
|
||||
return speedA.download === speedB.download
|
||||
? speedA.upload - speedB.upload
|
||||
: speedA.download - speedB.download
|
||||
},
|
||||
cell: cell => formatSpeed(cell.value[0], cell.value[1]),
|
||||
cell: (cell: { value: [number, number] }) => formatSpeed(cell.value[0], cell.value[1]),
|
||||
},
|
||||
),
|
||||
table.createDataColumn(Columns.Upload, { minWidth: 100, width: 100, header: t(`columns.${Columns.Upload}`), cell: cell => formatTraffic(cell.value) }),
|
||||
table.createDataColumn(Columns.Download, { minWidth: 100, width: 100, header: t(`columns.${Columns.Download}`), cell: cell => formatTraffic(cell.value) }),
|
||||
table.createDataColumn(Columns.Upload, { minWidth: 100, width: 100, header: t(`columns.${Columns.Upload}`), cell: cell => formatTraffic(cell.value as number) }),
|
||||
table.createDataColumn(Columns.Download, { minWidth: 100, width: 100, header: t(`columns.${Columns.Download}`), cell: cell => formatTraffic(cell.value as number) }),
|
||||
table.createDataColumn(Columns.SourceIP, { minWidth: 140, width: 140, header: t(`columns.${Columns.SourceIP}`), filterType: 'equals' }),
|
||||
table.createDataColumn(
|
||||
Columns.Time,
|
||||
{
|
||||
minWidth:
|
||||
120,
|
||||
minWidth: 120,
|
||||
width: 120,
|
||||
header: t(`columns.${Columns.Time}`),
|
||||
cell: cell => fromNow(new Date(cell.value), lang),
|
||||
sortType: (rowA, rowB) => rowB.original.time - rowA.original.time,
|
||||
}),
|
||||
cell: cell => fromNow(new Date(cell.value as string), lang),
|
||||
sortType: (rowA, rowB) => (rowB.original as FormatConnection).time - (rowA.original as FormatConnection).time,
|
||||
},
|
||||
),
|
||||
]),
|
||||
[lang, t],
|
||||
)
|
||||
@ -158,7 +157,7 @@ export default function Connections () {
|
||||
}
|
||||
}, [connStreamReader, feed, setTraffic])
|
||||
|
||||
const instance = table.useTable({
|
||||
const instance = useTable(table, {
|
||||
data,
|
||||
columns,
|
||||
sortRowsFn,
|
||||
@ -191,7 +190,7 @@ export default function Connections () {
|
||||
setDrawerState(d => { d.connection.completed = true })
|
||||
client.closeConnection(drawerState.selectedID)
|
||||
}
|
||||
const latestConntion = useLatest(drawerState.connection)
|
||||
const latestConntion = useSyncedRef(drawerState.connection)
|
||||
useEffect(() => {
|
||||
const conn = data.find(c => c.id === drawerState.selectedID)?.original
|
||||
if (conn) {
|
||||
@ -206,9 +205,9 @@ export default function Connections () {
|
||||
}
|
||||
}, [data, drawerState.selectedID, latestConntion, setDrawerState])
|
||||
|
||||
const scrolled = useMemo(() => scrollX > 0, [scrollX])
|
||||
const scrolled = useMemo(() => (intersection?.intersectionRatio ?? 0) < 1, [intersection])
|
||||
const headers = headerGroup.headers.map((header, idx) => {
|
||||
const column = header.column // as unknown as TableColumn<FormatConnection>
|
||||
const column = header.column
|
||||
const id = column.id
|
||||
return (
|
||||
<th
|
||||
@ -223,6 +222,7 @@ export default function Connections () {
|
||||
props.style.width = header.getWidth()
|
||||
}),
|
||||
)}
|
||||
ref={column.id === Columns.Host ? pinRef : undefined}
|
||||
key={id}>
|
||||
<div {...column.getToggleSortingProps()}>
|
||||
{header.renderHeader()}
|
||||
@ -233,13 +233,13 @@ export default function Connections () {
|
||||
}
|
||||
</div>
|
||||
{ idx !== headerGroup.headers.length - 1 &&
|
||||
<div {...column.getResizerProps()} className="connections-resizer" />
|
||||
<div {...header.getResizerProps()} className="connections-resizer" />
|
||||
}
|
||||
</th>
|
||||
)
|
||||
})
|
||||
|
||||
const content = instance.getRows().map(row => {
|
||||
const content = instance.getRowModel().rows.map(row => {
|
||||
return (
|
||||
<tr
|
||||
{...row.getRowProps()}
|
||||
@ -253,7 +253,7 @@ export default function Connections () {
|
||||
{ 'text-center': shouldCenter.has(cell.column.id), completed: row.original?.completed },
|
||||
{
|
||||
fixed: cell.column.id === Columns.Host,
|
||||
shadow: scrollX > 0 && cell.column.id === Columns.Host,
|
||||
shadow: scrolled && cell.column.id === Columns.Host,
|
||||
},
|
||||
)
|
||||
return (
|
||||
@ -286,7 +286,7 @@ export default function Connections () {
|
||||
</Header>
|
||||
{ devices.length > 1 && <Devices devices={devices} selected={device} onChange={handleDeviceSelected} /> }
|
||||
<Card ref={cardRef} className="connections-card relative">
|
||||
<div className="overflow-auto min-h-full" ref={tableRef}>
|
||||
<div className="overflow-auto min-h-full">
|
||||
<table {...instance.getTableProps()} className="flex-1">
|
||||
<thead>
|
||||
<tr {...headerGroup.getHeaderGroupProps()} className="connections-header">
|
||||
|
@ -28,7 +28,7 @@
|
||||
|
||||
&.fixed {
|
||||
position: sticky !important;
|
||||
left: 0;
|
||||
left: -0.1px;
|
||||
z-index: 99;
|
||||
&.shadow {
|
||||
box-shadow: inset -9px 0 8px -14px $color-black;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Suspense } from 'react'
|
||||
import { Suspense, StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { HashRouter } from 'react-router-dom'
|
||||
|
||||
@ -9,11 +9,13 @@ import 'virtual:windi.css'
|
||||
export default function renderApp () {
|
||||
const rootEl = document.getElementById('root')
|
||||
const AppInstance = (
|
||||
<StrictMode>
|
||||
<HashRouter>
|
||||
<Suspense fallback={<Loading visible />}>
|
||||
<App />
|
||||
</Suspense>
|
||||
</HashRouter>
|
||||
</StrictMode>
|
||||
)
|
||||
|
||||
const root = createRoot(rootEl!)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { atom, useAtom, useAtomValue } from 'jotai'
|
||||
import { atomWithStorage } from 'jotai/utils'
|
||||
import { useLocation } from 'react-use'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { isClashX, jsBridge } from '@lib/jsBridge'
|
||||
import { Client } from '@lib/request'
|
||||
|
@ -1,5 +1,5 @@
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from 'vite'
|
||||
import { defineConfig, splitVendorChunkPlugin } from 'vite'
|
||||
import { VitePWA } from 'vite-plugin-pwa'
|
||||
import windiCSS from 'vite-plugin-windicss'
|
||||
import tsConfigPath from 'vite-tsconfig-paths'
|
||||
@ -24,6 +24,7 @@ export default defineConfig(
|
||||
name: 'Clash Dashboard',
|
||||
},
|
||||
}),
|
||||
splitVendorChunkPlugin(),
|
||||
],
|
||||
base: './',
|
||||
css: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user