mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-12 05:32:09 +08:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c450e830de | ||
|
|
d88fe8dec2 | ||
|
|
dc641bf1f4 | ||
|
|
73b18a28ed | ||
|
|
f84be8df72 |
@@ -48,6 +48,7 @@
|
||||
|
||||
- 优化连接、规则和日志筛选:切换页面后保留过滤条件
|
||||
- 优化全局模式代理列表:仅显示 `GLOBAL` 节点,选中其中的策略组时才显示该组
|
||||
- 优化连接和规则页面的资源占用,减少连接列表更新及排序时的卡顿
|
||||
|
||||
**🍎 macOS**
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ let timerId: number | null = null
|
||||
const listeners = new Set<RelativeTimeListener>()
|
||||
|
||||
const startTimer = () => {
|
||||
if (timerId !== null) return
|
||||
if (timerId !== null || document.hidden || listeners.size === 0) return
|
||||
|
||||
currentTime = Date.now()
|
||||
timerId = window.setInterval(() => {
|
||||
@@ -18,19 +18,34 @@ const startTimer = () => {
|
||||
}
|
||||
|
||||
const stopTimer = () => {
|
||||
if (listeners.size > 0 || timerId === null) return
|
||||
if (timerId === null) return
|
||||
|
||||
window.clearInterval(timerId)
|
||||
timerId = null
|
||||
}
|
||||
|
||||
const handleVisibilityChange = () => {
|
||||
if (document.hidden) {
|
||||
stopTimer()
|
||||
} else {
|
||||
startTimer()
|
||||
listeners.forEach((listener) => listener())
|
||||
}
|
||||
}
|
||||
|
||||
const subscribeRelativeTime = (listener: RelativeTimeListener) => {
|
||||
listeners.add(listener)
|
||||
if (listeners.size === 1) {
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange)
|
||||
}
|
||||
startTimer()
|
||||
|
||||
return () => {
|
||||
listeners.delete(listener)
|
||||
stopTimer()
|
||||
if (listeners.size === 0) {
|
||||
stopTimer()
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -223,6 +223,16 @@ const compareConnectionCellValue = (
|
||||
right: IConnectionsItem,
|
||||
getSnapshot: (row: IConnectionsItem) => TableRowSnapshot,
|
||||
) => {
|
||||
switch (field) {
|
||||
case 'download':
|
||||
return (left.download ?? 0) - (right.download ?? 0)
|
||||
case 'upload':
|
||||
return (left.upload ?? 0) - (right.upload ?? 0)
|
||||
case 'dlSpeed':
|
||||
return (left.curDownload ?? 0) - (right.curDownload ?? 0)
|
||||
case 'ulSpeed':
|
||||
return (left.curUpload ?? 0) - (right.curUpload ?? 0)
|
||||
}
|
||||
const leftValue = getConnectionCellValue(field, getSnapshot(left))
|
||||
const rightValue = getConnectionCellValue(field, getSnapshot(right))
|
||||
|
||||
@@ -491,6 +501,8 @@ export const ConnectionTable = (props: Props) => {
|
||||
const [sorting, setSorting] = useState<SortingState | null>(null)
|
||||
const [viewport, setViewport] = useState({ scrollTop: 0, height: 0 })
|
||||
const scrollContainerRef = useRef<HTMLDivElement | null>(null)
|
||||
const resizeCleanupRef = useRef<(() => void) | null>(null)
|
||||
useEffect(() => () => resizeCleanupRef.current?.(), [])
|
||||
const rowSnapshotCacheRef = useRef(new Map<string, TableRowSnapshot>())
|
||||
const getRowSnapshot = useCallback((row: IConnectionsItem) => {
|
||||
const cache = rowSnapshotCacheRef.current
|
||||
@@ -664,6 +676,7 @@ export const ConnectionTable = (props: Props) => {
|
||||
minWidth: number,
|
||||
maxWidth: number | undefined,
|
||||
) => {
|
||||
resizeCleanupRef.current?.()
|
||||
const handleMove = (clientX: number) => {
|
||||
const nextWidth = Math.max(
|
||||
minWidth,
|
||||
@@ -686,8 +699,10 @@ export const ConnectionTable = (props: Props) => {
|
||||
window.removeEventListener('touchmove', handleTouchMove)
|
||||
window.removeEventListener('touchend', cleanup)
|
||||
window.removeEventListener('touchcancel', cleanup)
|
||||
resizeCleanupRef.current = null
|
||||
}
|
||||
|
||||
resizeCleanupRef.current = cleanup
|
||||
window.addEventListener('mousemove', handleMouseMove)
|
||||
window.addEventListener('mouseup', cleanup)
|
||||
window.addEventListener('touchmove', handleTouchMove, { passive: true })
|
||||
|
||||
@@ -16,7 +16,8 @@ const COLOR = [
|
||||
]
|
||||
|
||||
interface Props {
|
||||
value: Rule & { lineNo: number }
|
||||
value: Rule
|
||||
lineNo: number
|
||||
}
|
||||
|
||||
const parseColor = (text: string) => {
|
||||
@@ -31,7 +32,7 @@ const parseColor = (text: string) => {
|
||||
}
|
||||
|
||||
const RuleItem = (props: Props) => {
|
||||
const { value } = props
|
||||
const { value, lineNo } = props
|
||||
|
||||
return (
|
||||
<Item sx={{ borderBottom: '1px solid var(--divider-color)' }}>
|
||||
@@ -40,7 +41,7 @@ const RuleItem = (props: Props) => {
|
||||
variant="body2"
|
||||
sx={{ lineHeight: 2, minWidth: 30, mr: 2.25, textAlign: 'center' }}
|
||||
>
|
||||
{value.lineNo}
|
||||
{lineNo}
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ userSelect: 'text' }}>
|
||||
|
||||
@@ -150,25 +150,49 @@ const mergeConnectionSnapshot = (
|
||||
const nextConnections = payload.connections ?? []
|
||||
const previousActive = previous.activeConnections ?? []
|
||||
const previousClosed = previous.closedConnections ?? []
|
||||
const previousActiveById = new Map<string, IConnectionsItem>()
|
||||
const sameOrder =
|
||||
nextConnections.length === previousActive.length &&
|
||||
nextConnections.every(
|
||||
(connection, i) => connection.id === previousActive[i].id,
|
||||
)
|
||||
let previousActiveById: Map<string, IConnectionsItem> | undefined
|
||||
|
||||
for (let i = 0; i < previousActive.length; i++) {
|
||||
const previousConnection = previousActive[i]
|
||||
previousActiveById.set(previousConnection.id, previousConnection)
|
||||
if (!sameOrder) {
|
||||
previousActiveById = new Map()
|
||||
for (let i = 0; i < previousActive.length; i++) {
|
||||
const previousConnection = previousActive[i]
|
||||
previousActiveById.set(previousConnection.id, previousConnection)
|
||||
}
|
||||
}
|
||||
|
||||
const activeConnections: IConnectionsItem[] = []
|
||||
let activeConnections = sameOrder ? previousActive : []
|
||||
for (let i = 0; i < nextConnections.length; i++) {
|
||||
const connection = nextConnections[i]
|
||||
const previousConnection = previousActiveById.get(connection.id)
|
||||
if (previousConnection) previousActiveById.delete(connection.id)
|
||||
activeConnections.push(normalizeConnection(connection, previousConnection))
|
||||
const previousConnection = sameOrder
|
||||
? previousActive[i]
|
||||
: previousActiveById?.get(connection.id)
|
||||
previousActiveById?.delete(connection.id)
|
||||
const normalized = normalizeConnection(connection, previousConnection)
|
||||
if (activeConnections[i] === normalized) continue
|
||||
if (activeConnections === previousActive) {
|
||||
activeConnections = previousActive.slice()
|
||||
}
|
||||
activeConnections[i] = normalized
|
||||
}
|
||||
|
||||
if (previousActiveById.size === 0) {
|
||||
const uploadTotal = payload.uploadTotal ?? 0
|
||||
const downloadTotal = payload.downloadTotal ?? 0
|
||||
if (!previousActiveById?.size) {
|
||||
if (
|
||||
activeConnections === previousActive &&
|
||||
uploadTotal === previous.uploadTotal &&
|
||||
downloadTotal === previous.downloadTotal
|
||||
) {
|
||||
return previous
|
||||
}
|
||||
return {
|
||||
uploadTotal: payload.uploadTotal ?? 0,
|
||||
downloadTotal: payload.downloadTotal ?? 0,
|
||||
uploadTotal,
|
||||
downloadTotal,
|
||||
activeConnections,
|
||||
closedConnections: previousClosed,
|
||||
}
|
||||
@@ -198,8 +222,8 @@ const mergeConnectionSnapshot = (
|
||||
}
|
||||
|
||||
return {
|
||||
uploadTotal: payload.uploadTotal ?? 0,
|
||||
downloadTotal: payload.downloadTotal ?? 0,
|
||||
uploadTotal,
|
||||
downloadTotal,
|
||||
activeConnections,
|
||||
closedConnections,
|
||||
}
|
||||
@@ -227,7 +251,9 @@ const flushPendingMessage = () => {
|
||||
|
||||
lastFlushAt = Date.now()
|
||||
|
||||
connectionData = mergeConnectionSnapshot(payload, connectionData)
|
||||
const nextData = mergeConnectionSnapshot(payload, connectionData)
|
||||
if (nextData === connectionData) return
|
||||
connectionData = nextData
|
||||
notifyConnectionListeners()
|
||||
}
|
||||
|
||||
|
||||
+11
-8
@@ -34,13 +34,11 @@ const RulesPage = () => {
|
||||
}, [refreshRules, refreshRuleProviders, pageVisible])
|
||||
|
||||
const filteredRules = useMemo(() => {
|
||||
const rulesWithLineNo = rules.map((item, index) => ({
|
||||
...item,
|
||||
// UI-only derived data; keep app context/SWR data immutable
|
||||
lineNo: index + 1,
|
||||
}))
|
||||
|
||||
return rulesWithLineNo.filter((item) => match(item.payload ?? ''))
|
||||
const indices: number[] = []
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
if (match(rules[i].payload ?? '')) indices.push(i)
|
||||
}
|
||||
return indices
|
||||
}, [rules, match])
|
||||
|
||||
const handleScroll = useCallback((e: Event) => {
|
||||
@@ -90,7 +88,12 @@ const RulesPage = () => {
|
||||
ref={virtuosoRef}
|
||||
count={filteredRules.length}
|
||||
estimateSize={40}
|
||||
renderItem={(i) => <RuleItem value={filteredRules[i]} />}
|
||||
renderItem={(i) => (
|
||||
<RuleItem
|
||||
value={rules[filteredRules[i]]}
|
||||
lineNo={filteredRules[i] + 1}
|
||||
/>
|
||||
)}
|
||||
style={{ flex: 1 }}
|
||||
onScroll={handleScroll}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user