Make selected indicator more distinguishable in proxy summary list
ref #575
This commit is contained in:
parent
459de99875
commit
20c8dad10d
8 changed files with 53 additions and 25 deletions
|
@ -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": [
|
||||||
|
|
|
@ -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'] {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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' : ''}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 };
|
||||||
|
}
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
Loading…
Reference in a new issue