Rename client app to Harbor Connect and refresh state animations
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 14s
Build and Deploy Gateway / deploy (push) Successful in 1s

This commit is contained in:
2026-07-11 16:24:28 +03:00
parent edad26d978
commit b53cd08dcc
4 changed files with 70 additions and 23 deletions

View File

@@ -78,6 +78,12 @@ function DurationPart({ name, children }) {
);
}
function AnimatedSeconds({ value, padded = true }) {
return String(value).padStart(padded ? 2 : 1, '0').split('').map((digit, index) => (
<span className="client-duration-second-digit" key={`${index}-${digit}`}>{digit}</span>
));
}
function HarborBrand({ isGateway, gatewayAvailable, gatewayDirect, busy, onSetGatewayAuto }) {
const [modeAnimating, setModeAnimating] = useState(false);
const stopModeAnimationRef = useRef(false);
@@ -213,6 +219,9 @@ export function ClientOverviewPage({
const proxyUrls = localProxyUrls(state?.proxyPort, gatewayAddress);
const usage = subscriptionUsage(state?.userInfo);
const duration = connectionDurationParts(state?.singboxStartedAt, now);
const connectionTitle = connected
? gatewayDirect ? 'Gateway подключён' : 'VPN включён'
: 'Подключение выключено';
const [displayedUsed, setDisplayedUsed] = useState(usage.used);
const hasUsage = Boolean(
state?.userInfo && ['upload', 'download', 'total', 'expire'].some((key) => key in state.userInfo),
@@ -513,10 +522,10 @@ export function ClientOverviewPage({
</svg>
</button>
<div className="client-state-copy" aria-live="polite">
<h2 key={connected ? 'connected' : 'disconnected'} id="connection-title">
{connected
? gatewayDirect ? 'Gateway подключён' : 'VPN включён'
: 'Подключение выключено'}
<h2 id="connection-title" className="client-connection-title" aria-label={connectionTitle}>
<span className={!connected ? 'is-active' : ''} aria-hidden="true">Подключение выключено</span>
<span className={connected && !gatewayDirect ? 'is-active' : ''} aria-hidden="true">VPN включён</span>
<span className={connected && gatewayDirect ? 'is-active' : ''} aria-hidden="true">Gateway подключён</span>
</h2>
<div className="client-state-detail">
{connected ? (
@@ -534,7 +543,7 @@ export function ClientOverviewPage({
>
<DurationPart name="hours-value">{String(duration.totalHours).padStart(2, '0')}</DurationPart>
:<DurationPart name="minutes-value">{String(duration.minutes.value).padStart(2, '0')}</DurationPart>
:<DurationPart name="seconds-value" key={`digital-${duration.seconds.value}`}>{String(duration.seconds.value).padStart(2, '0')}</DurationPart>
:<DurationPart name="seconds-value"><AnimatedSeconds value={duration.seconds.value} /></DurationPart>
</time>
<time
className={`client-duration${durationMode === 'words' ? ' is-active' : ''}`}
@@ -549,7 +558,9 @@ export function ClientOverviewPage({
.filter(([name, part]) => part.value || name === 'seconds')
.map(([name, part]) => (
<span className="client-duration-unit" data-unit={name} key={name}>
<DurationPart name={`${name}-value`} key={`${name}-${name === 'seconds' ? part.value : 'value'}`}>{part.value}</DurationPart>{' '}
<DurationPart name={`${name}-value`}>{name === 'seconds'
? <AnimatedSeconds value={part.value} padded={false} />
: part.value}</DurationPart>{' '}
<DurationPart name={`${name}-label`}>{part.label}</DurationPart>
</span>
))}