perf(home): render proxy dropdown options on demand

This commit is contained in:
wonfen
2026-09-18 00:41:54 +08:00
parent 65ed8d3cf2
commit f6fdb34745
2 changed files with 185 additions and 139 deletions
+184 -139
View File
@@ -1,6 +1,7 @@
/* eslint-disable @eslint-react/set-state-in-effect */
import {
AccessTimeRounded,
ArrowDropDown,
ChevronRight,
NetworkCheckRounded,
WifiOff as SignalError,
@@ -15,24 +16,22 @@ import {
import {
Box,
Button,
ButtonBase,
Chip,
ClickAwayListener,
FormControl,
IconButton,
InputLabel,
MenuItem,
MenuList,
Paper,
Popper,
type PopperProps,
Select,
Tooltip,
Typography,
alpha,
useTheme,
} from '@mui/material'
import { useLockFn } from 'ahooks'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router'
@@ -223,34 +222,28 @@ const sortProxyOptions = (
interface PersistentSelectProps {
label: string
labelId: string
listboxId: string
value: string
selectedName: string
displayValue: React.ReactNode
open: boolean
disabled: boolean
keepOpenRef: React.RefObject<HTMLElement | null>
onOpen: () => void
onClose: () => void
renderValue?: () => React.ReactNode
children: React.ReactNode
onOpenChange: (open: boolean) => void
renderOptions: () => React.ReactNode
}
const PersistentSelect = ({
label,
labelId,
listboxId,
value,
selectedName,
displayValue,
open,
disabled,
keepOpenRef,
onOpen,
onClose,
renderValue,
children,
onOpenChange,
renderOptions,
}: PersistentSelectProps) => {
const anchorRef = useRef<HTMLDivElement>(null)
const id = useId()
const labelId = `${id}-label`
const valueId = `${id}-value`
const listboxId = `${id}-listbox`
const anchorRef = useRef<HTMLButtonElement>(null)
const listRef = useRef<HTMLUListElement>(null)
const popperOptions = useMemo<PopperProps['popperOptions']>(
@@ -283,17 +276,15 @@ const PersistentSelect = ({
const closeFromKeyboard = (event: KeyboardEvent) => {
if (event.key === 'Tab') {
onClose()
onOpenChange(false)
} else if (event.key === 'Escape') {
onClose()
anchorRef.current
?.querySelector<HTMLElement>('[role="combobox"]')
?.focus()
onOpenChange(false)
anchorRef.current?.focus({ preventScroll: true })
}
}
document.addEventListener('keydown', closeFromKeyboard)
return () => document.removeEventListener('keydown', closeFromKeyboard)
}, [onClose, open])
}, [onOpenChange, open])
return (
<ClickAwayListener
@@ -303,31 +294,92 @@ const PersistentSelect = ({
if (target instanceof Node && keepOpenRef.current?.contains(target)) {
return
}
onClose()
onOpenChange(false)
}}
>
<Box>
<FormControl ref={anchorRef} fullWidth variant="outlined" size="small">
<InputLabel id={labelId}>{label}</InputLabel>
<Select
labelId={labelId}
value={value}
open={false}
onOpen={(event) => {
if (open && event.type === 'mousedown') onClose()
else onOpen()
}}
label={label}
<Box
sx={{
position: 'relative',
borderRadius: 1,
'& fieldset': {
borderColor: (theme) =>
alpha(theme.palette.text.primary, disabled ? 0.12 : 0.23),
},
...(!disabled && {
'&:hover fieldset': { borderColor: 'text.primary' },
'&:focus-within fieldset': {
borderColor: 'primary.main',
borderWidth: 2,
},
'&:focus-within legend': { color: 'primary.main' },
}),
}}
>
<ButtonBase
ref={anchorRef}
type="button"
role="combobox"
aria-labelledby={`${labelId} ${valueId}`}
aria-haspopup="listbox"
aria-controls={open ? listboxId : undefined}
aria-expanded={open}
disabled={disabled}
renderValue={renderValue}
SelectDisplayProps={{
'aria-controls': open ? listboxId : undefined,
'aria-expanded': open ? 'true' : 'false',
onClick={() => onOpenChange(!open)}
onKeyDown={(event) => {
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
event.preventDefault()
onOpenChange(true)
}
}}
sx={{
width: '100%',
height: 40,
px: 1.75,
borderRadius: 'inherit',
justifyContent: 'space-between',
textAlign: 'left',
color: disabled ? 'text.disabled' : 'text.primary',
}}
>
<MenuItem value={value}>{selectedName}</MenuItem>
</Select>
</FormControl>
<Box id={valueId} sx={{ minWidth: 0, flex: 1 }}>
{displayValue}
</Box>
<ArrowDropDown
sx={{
ml: 1,
color: disabled ? 'action.disabled' : 'action.active',
transform: open ? 'rotate(180deg)' : undefined,
}}
/>
</ButtonBase>
<Box
component="fieldset"
sx={{
position: 'absolute',
inset: '-5px 0 0',
m: 0,
px: 1,
minWidth: 0,
border: '1px solid',
borderRadius: 'inherit',
pointerEvents: 'none',
}}
>
<Box
component="legend"
id={labelId}
sx={{
px: 0.5,
typography: 'caption',
lineHeight: '11px',
color: disabled ? 'text.disabled' : 'text.secondary',
}}
>
{label}
</Box>
</Box>
</Box>
<Popper
open={open}
@@ -354,7 +406,7 @@ const PersistentSelect = ({
aria-labelledby={labelId}
variant="selectedMenu"
>
{children}
{open && renderOptions()}
</MenuList>
</Paper>
</Popper>
@@ -364,10 +416,9 @@ const PersistentSelect = ({
}
interface PersistentProxySelectProps
extends Omit<
PersistentSelectProps,
'labelId' | 'listboxId' | 'renderValue' | 'children'
> {
extends Omit<PersistentSelectProps, 'displayValue' | 'renderOptions'> {
value: string
selectedName: string
groupName: string
fixed?: string
selectedDelay: number
@@ -390,11 +441,7 @@ const PersistentProxySelect = ({
return (
<PersistentSelect
{...props}
labelId="proxy-select-label"
listboxId="current-proxy-node-listbox"
value={value}
selectedName={selectedName}
renderValue={() => (
displayValue={
<Box
sx={{
display: 'flex',
@@ -421,67 +468,68 @@ const PersistentProxySelect = ({
</span>
)}
</Box>
)}
>
{options.map((option) => {
const selected = option.value === value
const isFixed = option.name === fixed
const delay = option.disabled
? -1
: delayManager.getDelayFix(option.member, groupName)
}
renderOptions={() =>
options.map((option) => {
const selected = option.value === value
const isFixed = option.name === fixed
const delay = option.disabled
? -1
: delayManager.getDelayFix(option.member, groupName)
return (
<MenuItem
key={option.value}
role="option"
aria-selected={selected}
selected={selected}
disabled={option.disabled}
onClick={() => {
if (!option.disabled) onChange(option.value)
}}
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
pr: 1,
}}
>
{isFixed && (
<span
style={{
position: 'absolute',
fontSize: '12px',
top: '-5px',
right: '5px',
...(!fixedProxyInUsed && {
filter: 'grayscale(1)',
}),
}}
>
📌
</span>
)}
<Typography noWrap sx={{ flex: 1, mr: 1 }}>
{option.name}
</Typography>
{!option.disabled && (
<Chip
size="small"
label={delayManager.formatDelay(delay)}
color={convertDelayColor(delay)}
sx={{
minWidth: '60px',
height: '22px',
flexShrink: 0,
}}
/>
)}
</MenuItem>
)
})}
</PersistentSelect>
return (
<MenuItem
key={option.value}
role="option"
aria-selected={selected}
selected={selected}
disabled={option.disabled}
onClick={() => {
if (!option.disabled) onChange(option.value)
}}
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
pr: 1,
}}
>
{isFixed && (
<span
style={{
position: 'absolute',
fontSize: '12px',
top: '-5px',
right: '5px',
...(!fixedProxyInUsed && {
filter: 'grayscale(1)',
}),
}}
>
📌
</span>
)}
<Typography noWrap sx={{ flex: 1, mr: 1 }}>
{option.name}
</Typography>
{!option.disabled && (
<Chip
size="small"
label={delayManager.formatDelay(delay)}
color={convertDelayColor(delay)}
sx={{
minWidth: '60px',
height: '22px',
flexShrink: 0,
}}
/>
)}
</MenuItem>
)
})
}
/>
)
}
@@ -863,7 +911,7 @@ export const CurrentProxyCard = () => {
const proxyOptions = useMemo(
() =>
isDirectMode
isDirectMode || openSelect !== 'proxy'
? []
: sortProxyOptions(
unsortedProxyOptions,
@@ -874,6 +922,7 @@ export const CurrentProxyCard = () => {
[
delays,
isDirectMode,
openSelect,
unsortedProxyOptions,
sortType,
defaultLatencyTimeout,
@@ -1039,28 +1088,25 @@ export const CurrentProxyCard = () => {
<Box sx={{ mb: 1.5 }}>
<PersistentSelect
label={t('home.components.currentProxy.labels.group')}
labelId="proxy-group-select-label"
listboxId="current-proxy-group-listbox"
value={selectedGroupName}
selectedName={selectedGroupName}
displayValue={<Typography noWrap>{selectedGroupName}</Typography>}
open={openSelect === 'group'}
disabled={isGlobalMode || isDirectMode}
keepOpenRef={delayButtonRef}
onOpen={() => setOpenSelect('group')}
onClose={() => setOpenSelect(null)}
>
{selectableGroups.map((group) => (
<MenuItem
key={group.name}
role="option"
aria-selected={group.name === selectedGroupName}
selected={group.name === selectedGroupName}
onClick={() => handleGroupChange(group.name)}
>
<Typography noWrap>{group.name}</Typography>
</MenuItem>
))}
</PersistentSelect>
onOpenChange={(open) => setOpenSelect(open ? 'group' : null)}
renderOptions={() =>
selectableGroups.map((group) => (
<MenuItem
key={group.name}
role="option"
aria-selected={group.name === selectedGroupName}
selected={group.name === selectedGroupName}
onClick={() => handleGroupChange(group.name)}
>
<Typography noWrap>{group.name}</Typography>
</MenuItem>
))
}
/>
</Box>
<PersistentProxySelect
@@ -1074,12 +1120,11 @@ export const CurrentProxyCard = () => {
}
selectedName={selectedProxyName}
selectedDelay={currentDelay}
options={isDirectMode ? [] : proxyOptions}
options={proxyOptions}
open={openSelect === 'proxy'}
disabled={isDirectMode}
keepOpenRef={delayButtonRef}
onOpen={() => setOpenSelect('proxy')}
onClose={() => setOpenSelect(null)}
onOpenChange={(open) => setOpenSelect(open ? 'proxy' : null)}
onChange={handleProxyChange}
/>
</Box>