Add deprioritized device group
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, {
|
||||
import {
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
@@ -233,16 +233,12 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
|
||||
setAlias(value);
|
||||
}
|
||||
|
||||
async function togglePin(device: Device) {
|
||||
if (!device.pinned || window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||
await updateDevice(device, { pinned: !device.pinned });
|
||||
return;
|
||||
}
|
||||
async function collapsePinned(device: Device, patch: Record<string, unknown>) {
|
||||
setPinCollapses((current) => ({
|
||||
...current,
|
||||
[device.id]: { animationDone: false, result: 'pending' },
|
||||
}));
|
||||
const saved = await updateDevice(device, { pinned: false });
|
||||
const saved = await updateDevice(device, patch);
|
||||
setPinCollapses((current) => {
|
||||
const phase = current[device.id];
|
||||
if (!phase) return current;
|
||||
@@ -255,6 +251,23 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
|
||||
});
|
||||
}
|
||||
|
||||
async function togglePin(device: Device) {
|
||||
if (!device.pinned || window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||
await updateDevice(device, { pinned: !device.pinned });
|
||||
return;
|
||||
}
|
||||
await collapsePinned(device, { pinned: false });
|
||||
}
|
||||
|
||||
async function toggleDeprioritized(device: Device) {
|
||||
const deprioritized = device.deprioritized === true;
|
||||
if (!device.pinned || deprioritized || window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||
await updateDevice(device, { deprioritized: !deprioritized });
|
||||
return;
|
||||
}
|
||||
await collapsePinned(device, { deprioritized: true });
|
||||
}
|
||||
|
||||
function finishPinCollapse(deviceId: string) {
|
||||
setPinCollapses((current) => {
|
||||
const phase = current[deviceId];
|
||||
@@ -365,10 +378,21 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
|
||||
{copyAnnouncement?.message || ''}
|
||||
</div>
|
||||
<div className="client-devices-list" aria-live="polite" aria-busy={status === 'loading' || status === 'refreshing'}>
|
||||
{devices.map((device) => {
|
||||
{devices.map((device, index) => {
|
||||
const collapsing = Boolean(pinCollapses[device.id]);
|
||||
const deprioritized = device.deprioritized === true;
|
||||
const compact = deprioritized && !collapsing;
|
||||
const expanded = device.pinned && !collapsing;
|
||||
const showDetails = device.pinned || collapsing;
|
||||
const showDetails = !compact && (device.pinned || collapsing);
|
||||
const group = device.pinned ? 'pinned' : deprioritized ? 'deprioritized' : 'default';
|
||||
const previous = devices[index - 1];
|
||||
const previousGroup = previous
|
||||
? previous.pinned ? 'pinned' : previous.deprioritized === true ? 'deprioritized' : 'default'
|
||||
: '';
|
||||
const groupLabel = group === 'pinned'
|
||||
? 'Закреплённые'
|
||||
: group === 'deprioritized' ? 'Убраны вниз' : 'Остальные';
|
||||
const groupStart = group !== previousGroup;
|
||||
const hasName = Boolean(device.alias || device.hostname);
|
||||
const title = device.alias || device.hostname || device.ip || 'Неизвестное устройство';
|
||||
const editing = editingId === device.id;
|
||||
@@ -406,14 +430,20 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
|
||||
? 'Полностью обходит sing-box. Нажмите, чтобы вернуть обработку Gateway'
|
||||
: 'Проходит через sing-box и правила Gateway. Нажмите, чтобы пустить полностью напрямую';
|
||||
return <article
|
||||
key={device.id}
|
||||
ref={(node) => {
|
||||
if (node) deviceNodes.current.set(device.id, node);
|
||||
else deviceNodes.current.delete(device.id);
|
||||
}}
|
||||
className={`client-device is-${device.status}${expanded ? ' is-pinned' : ''}${collapsing ? ' is-collapsing' : ''}`}
|
||||
key={device.id}
|
||||
className={`client-device is-${device.status}${expanded ? ' is-pinned' : ''}${collapsing ? ' is-collapsing' : ''}${compact ? ' is-deprioritized' : ''}${groupStart ? ' is-group-start' : ''}`}
|
||||
>
|
||||
<span className="client-device-pin-wrap client-tooltip-anchor">
|
||||
<span
|
||||
className="client-device-group-heading"
|
||||
role={groupStart ? 'heading' : undefined}
|
||||
aria-level={groupStart ? 3 : undefined}
|
||||
aria-hidden={!groupStart}
|
||||
>{groupStart ? groupLabel : ''}</span>
|
||||
{!compact && <span className="client-device-pin-wrap client-tooltip-anchor">
|
||||
<button
|
||||
className="client-device-pin"
|
||||
type="button"
|
||||
@@ -427,10 +457,10 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
|
||||
</svg>
|
||||
</button>
|
||||
<Tooltip>{device.pinned ? 'Открепить' : 'Закрепить'}</Tooltip>
|
||||
</span>
|
||||
</span>}
|
||||
|
||||
<div className="client-device-main">
|
||||
<h3 className={`client-device-name-heading${hasName ? '' : ' is-address-only'}${editing ? ' is-editing' : ''}`}>
|
||||
<h4 className={`client-device-name-heading${hasName ? '' : ' is-address-only'}${editing ? ' is-editing' : ''}`}>
|
||||
{editing ? (
|
||||
<input
|
||||
className="client-device-alias-input"
|
||||
@@ -460,7 +490,7 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
|
||||
aria-label={`Скопировать IP ${device.ip} устройства ${title}`}
|
||||
onClick={() => copyDeviceIp(device)}
|
||||
>{device.ip}</button> : !hasName && !editing && <span>Неизвестное устройство</span>}
|
||||
</h3>
|
||||
</h4>
|
||||
{!hasName && !editing && <span className="client-device-edit-wrap client-tooltip-anchor">
|
||||
<button
|
||||
className={`client-device-edit${pencilAnimationId === device.id ? ' is-writing' : ''}`}
|
||||
@@ -490,7 +520,7 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span
|
||||
{!compact && <span
|
||||
className="client-device-traffic"
|
||||
role="group"
|
||||
tabIndex={0}
|
||||
@@ -503,9 +533,9 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
|
||||
<span><b>Gateway</b><TrafficValue value={gatewayTraffic} delta={trafficDelta.gateway} /></span>
|
||||
{hasProxyTraffic && <span className="is-proxy"><b>Прокси</b><TrafficValue value={proxyTraffic} delta={trafficDelta.proxy} /></span>}
|
||||
</span>
|
||||
</span>
|
||||
</span>}
|
||||
|
||||
<span className="client-device-policy-wrap client-tooltip-anchor">
|
||||
{!compact && <span className="client-device-policy-wrap client-tooltip-anchor">
|
||||
<button
|
||||
className={`client-device-policy is-${displayPolicy}${policyFailed ? ' is-failed' : ''}${policyPending ? ' is-pending' : ''}`}
|
||||
type="button"
|
||||
@@ -523,8 +553,25 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
|
||||
</svg>}
|
||||
</button>
|
||||
<Tooltip>{policyTooltip}</Tooltip>
|
||||
</span>}
|
||||
<span className="client-device-deprioritize-wrap client-tooltip-anchor">
|
||||
<button
|
||||
className="client-device-deprioritize"
|
||||
type="button"
|
||||
aria-pressed={deprioritized}
|
||||
aria-label={deprioritized ? `Вернуть ${title} в основной список` : `Убрать ${title} вниз`}
|
||||
disabled={saving}
|
||||
onClick={() => toggleDeprioritized(device)}
|
||||
>
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
{deprioritized
|
||||
? <path d="M12 20V8M7 13l5-5 5 5M5 4h14" />
|
||||
: <path d="M12 4v12M7 11l5 5 5-5M5 20h14" />}
|
||||
</svg>
|
||||
</button>
|
||||
<Tooltip>{deprioritized ? 'Вернуть в список' : 'Убрать вниз'}</Tooltip>
|
||||
</span>
|
||||
<TrafficChart
|
||||
{!compact && <TrafficChart
|
||||
samples={device.trafficHistory || []}
|
||||
scale={trafficScale}
|
||||
capacity={snapshot?.trafficHistoryCapacity || device.trafficHistory?.length || 1}
|
||||
@@ -532,7 +579,7 @@ export function DevicesPanel({ feature }: { feature: DevicesFeature }) {
|
||||
pinned={showDetails}
|
||||
collapsing={collapsing}
|
||||
onCollapseEnd={() => finishPinCollapse(device.id)}
|
||||
/>
|
||||
/>}
|
||||
</article>;
|
||||
})}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user