Make selected indicator more distinguishable in proxy summary list

ref #575
This commit is contained in:
Haishan 2022-06-11 22:31:33 +08:00
parent 459de99875
commit 20c8dad10d
8 changed files with 53 additions and 25 deletions

View file

@ -12,7 +12,7 @@
"start": "vite", "start": "vite",
"build": "vite build", "build": "vite build",
"serve": "vite preview", "serve": "vite preview",
"pretty": "prettier --single-quote --write 'src/**/*.{js,scss,ts,tsx,md}'", "pretty": "prettier --write 'src/**/*.{js,scss,ts,tsx,md}'",
"fmt": "pnpm lint && pnpm pretty" "fmt": "pnpm lint && pnpm pretty"
}, },
"browserslist": [ "browserslist": [

View file

@ -96,6 +96,7 @@ body {
--bc-tooltip: #555; --bc-tooltip: #555;
--select-border-color: #040404; --select-border-color: #040404;
--select-bg-hover: url(data:image/svg+xml,%0A%20%20%20%20%3Csvg%20width%3D%228%22%20height%3D%2224%22%20viewBox%3D%220%200%208%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%207L7%2011H1L4%207Z%22%20fill%3D%22%23ffffff%22%20%2F%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%2017L1%2013L7%2013L4%2017Z%22%20fill%3D%22%23ffffff%22%20%2F%3E%0A%20%20%20%20%3C%2Fsvg%3E%0A%20%20); --select-bg-hover: url(data:image/svg+xml,%0A%20%20%20%20%3Csvg%20width%3D%228%22%20height%3D%2224%22%20viewBox%3D%220%200%208%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%207L7%2011H1L4%207Z%22%20fill%3D%22%23ffffff%22%20%2F%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%2017L1%2013L7%2013L4%2017Z%22%20fill%3D%22%23ffffff%22%20%2F%3E%0A%20%20%20%20%3C%2Fsvg%3E%0A%20%20);
--color-proxy-dot-selected-ind-bo: transparent;
} }
@mixin light { @mixin light {
--color-background: #eee; --color-background: #eee;
@ -124,6 +125,7 @@ body {
--bc-tooltip: #ccc; --bc-tooltip: #ccc;
--select-border-color: #999999; --select-border-color: #999999;
--select-bg-hover: url(data:image/svg+xml,%0A%20%20%20%20%3Csvg%20width%3D%228%22%20height%3D%2224%22%20viewBox%3D%220%200%208%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%207L7%2011H1L4%207Z%22%20fill%3D%22%23222222%22%20%2F%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%2017L1%2013L7%2013L4%2017Z%22%20fill%3D%22%23222222%22%20%2F%3E%0A%20%20%20%20%3C%2Fsvg%3E%0A%20%20); --select-bg-hover: url(data:image/svg+xml,%0A%20%20%20%20%3Csvg%20width%3D%228%22%20height%3D%2224%22%20viewBox%3D%220%200%208%2024%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%207L7%2011H1L4%207Z%22%20fill%3D%22%23222222%22%20%2F%3E%0A%20%20%20%20%20%20%3Cpath%20d%3D%22M4%2017L1%2013L7%2013L4%2017Z%22%20fill%3D%22%23222222%22%20%2F%3E%0A%20%20%20%20%3C%2Fsvg%3E%0A%20%20);
--color-proxy-dot-selected-ind-bo: #888;
} }
:root[data-theme='auto'] { :root[data-theme='auto'] {

View file

@ -64,13 +64,31 @@
} }
.proxySmall { .proxySmall {
width: 13px; --size: 13px;
height: 13px; width: var(--size);
height: var(--size);
border-radius: 50%; border-radius: 50%;
border: 1px solid var(--color-background); position: relative;
&.now { &.now {
border-color: var(--color-text-secondary); --size: 15px;
&:before {
--size-dot: 7px;
content: '';
position: absolute;
width: var(--size-dot);
height: var(--size-dot);
background-color: #fff;
// For non-primitive proxy type like "Selector", "LoadBalance", "DIRECT", etc. we are using a transparent
// background, and this selected indicator has a white background. In "light" them mode, the constrast
// between the bg of the indicator and the "background" is too small. In that case we want to add a
// border around this indicator so it's more distinguishable.
border: 1px solid var(--color-proxy-dot-selected-ind-bo);
border-radius: 4px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
} }
&.selectable { &.selectable {

View file

@ -1,7 +1,6 @@
import { TooltipPopup, useTooltip } from '@reach/tooltip'; import { TooltipPopup, useTooltip } from '@reach/tooltip';
import cx from 'clsx'; import cx from 'clsx';
import * as React from 'react'; import * as React from 'react';
import { keyCodes } from 'src/misc/keycode';
import { State } from '$src/store/types'; import { State } from '$src/store/types';
@ -40,16 +39,17 @@ function getLabelColor({
return colorMap.na; return colorMap.na;
} }
function getProxyDotBackgroundColor( function getProxyDotStyle(
latency: { latency: {
number?: number; number?: number;
}, },
proxyType: string proxyType: string
) { ) {
if (NonProxyTypes.indexOf(proxyType) > -1) { if (NonProxyTypes.indexOf(proxyType) > -1) {
return 'linear-gradient(135deg, white 15%, #999 15% 30%, white 30% 45%, #999 45% 60%, white 60% 75%, #999 75% 90%, white 90% 100%)'; return { border: '1px dotted #777' };
} }
return getLabelColor(latency); const bg = getLabelColor(latency);
return { background: bg };
} }
type ProxyProps = { type ProxyProps = {
@ -62,7 +62,7 @@ type ProxyProps = {
}; };
function ProxySmallImpl({ now, name, proxy, latency, isSelectable, onClick }: ProxyProps) { function ProxySmallImpl({ now, name, proxy, latency, isSelectable, onClick }: ProxyProps) {
const color = useMemo(() => getProxyDotBackgroundColor(latency, proxy.type), [latency, proxy]); const style = useMemo(() => getProxyDotStyle(latency, proxy.type), [latency, proxy]);
const title = useMemo(() => { const title = useMemo(() => {
let ret = name; let ret = name;
if (latency && typeof latency.number === 'number') { if (latency && typeof latency.number === 'number') {
@ -84,9 +84,7 @@ function ProxySmallImpl({ now, name, proxy, latency, isSelectable, onClick }: Pr
const handleKeyDown = React.useCallback( const handleKeyDown = React.useCallback(
(e: React.KeyboardEvent) => { (e: React.KeyboardEvent) => {
if (e.keyCode === keyCodes.Enter) { if (e.key === 'Enter') doSelect();
doSelect();
}
}, },
[doSelect] [doSelect]
); );
@ -95,7 +93,7 @@ function ProxySmallImpl({ now, name, proxy, latency, isSelectable, onClick }: Pr
<div <div
title={title} title={title}
className={className} className={className}
style={{ background: color }} style={style}
onClick={doSelect} onClick={doSelect}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
role={isSelectable ? 'menuitem' : ''} role={isSelectable ? 'menuitem' : ''}

View file

@ -10,4 +10,5 @@
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, 13px); grid-template-columns: repeat(auto-fill, 13px);
grid-gap: 10px; grid-gap: 10px;
place-items: center;
} }

View file

@ -16,11 +16,13 @@ import {
import { getDelay, healthcheckProviderByName } from 'src/store/proxies'; import { getDelay, healthcheckProviderByName } from 'src/store/proxies';
import { DelayMapping, State } from 'src/store/types'; import { DelayMapping, State } from 'src/store/types';
import { useState2 } from '$src/hooks/basic';
import { useFilteredAndSorted } from './hooks'; import { useFilteredAndSorted } from './hooks';
import { ProxyList, ProxyListSummaryView } from './ProxyList'; import { ProxyList, ProxyListSummaryView } from './ProxyList';
import s from './ProxyProvider.module.scss'; import s from './ProxyProvider.module.scss';
const { useState, useCallback } = React; const { useCallback } = React;
type Props = { type Props = {
name: string; name: string;
@ -49,15 +51,15 @@ function ProxyProviderImpl({
apiConfig, apiConfig,
}: Props) { }: Props) {
const proxies = useFilteredAndSorted(all, delay, hideUnavailableProxies, proxySortBy); const proxies = useFilteredAndSorted(all, delay, hideUnavailableProxies, proxySortBy);
const [isHealthcheckLoading, setIsHealthcheckLoading] = useState(false); const checkingHealth = useState2(false);
const updateProvider = useUpdateProviderItem({ dispatch, apiConfig, name }); const updateProvider = useUpdateProviderItem({ dispatch, apiConfig, name });
const healthcheckProvider = useCallback(async () => { const healthcheckProvider = useCallback(() => {
setIsHealthcheckLoading(true); checkingHealth.set(true);
await dispatch(healthcheckProviderByName(apiConfig, name)); const stop = () => checkingHealth.set(false);
setIsHealthcheckLoading(false); dispatch(healthcheckProviderByName(apiConfig, name)).then(stop, stop);
}, [apiConfig, dispatch, name, setIsHealthcheckLoading]); }, [apiConfig, dispatch, name, checkingHealth]);
const { const {
app: { updateCollapsibleIsOpen }, app: { updateCollapsibleIsOpen },
@ -77,7 +79,9 @@ function ProxyProviderImpl({
isOpen={isOpen} isOpen={isOpen}
qty={proxies.length} qty={proxies.length}
/> />
<div className={s.updatedAt}><small>Updated {timeAgo} ago</small></div> <div className={s.updatedAt}>
<small>Updated {timeAgo} ago</small>
</div>
<Collapsible isOpen={isOpen}> <Collapsible isOpen={isOpen}>
<ProxyList all={proxies} /> <ProxyList all={proxies} />
<div className={s.actionFooter}> <div className={s.actionFooter}>
@ -86,7 +90,7 @@ function ProxyProviderImpl({
text="Health Check" text="Health Check"
start={<Zap size={16} />} start={<Zap size={16} />}
onClick={healthcheckProvider} onClick={healthcheckProvider}
isLoading={isHealthcheckLoading} isLoading={checkingHealth.value}
/> />
</div> </div>
</Collapsible> </Collapsible>

View file

@ -1,4 +1,4 @@
import React from 'react'; import * as React from 'react';
const { useState, useCallback } = React; const { useState, useCallback } = React;
@ -7,3 +7,8 @@ export function useToggle(initialValue = false): [boolean, () => void] {
const toggle = useCallback(() => setState((x) => !x), []); const toggle = useCallback(() => setState((x) => !x), []);
return [isOn, toggle]; return [isOn, toggle];
} }
export function useState2<T>(v: T) {
const [value, set] = useState(v);
return { value, set };
}

View file

@ -5,4 +5,4 @@ export type ClashAPIConfig = {
export type LogsAPIConfig = ClashAPIConfig & { logLevel: string }; export type LogsAPIConfig = ClashAPIConfig & { logLevel: string };
export type RuleType = { id?: number; type?: string; payload?: string; proxy?: string; } export type RuleType = { id?: number; type?: string; payload?: string; proxy?: string };