mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-04 11:43:57 +08:00
fix(ui): reduce idle GPU usage for route status (#5767)
* fix(ui): reduce idle GPU usage for route status * fix(ui): disable heartbeat for reduced motion * fix(ui): preserve polling outside Tauri focus
This commit is contained in:
@@ -84,7 +84,7 @@ export function ClaudeDesktopRouteToggle({
|
||||
className={cn(
|
||||
"h-4 w-4 transition-colors",
|
||||
isRunning
|
||||
? "text-emerald-500 animate-pulse"
|
||||
? "text-emerald-500 status-heartbeat"
|
||||
: "text-muted-foreground",
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -72,7 +72,7 @@ export function FailoverToggle({ className, activeApp }: FailoverToggleProps) {
|
||||
className={cn(
|
||||
"h-4 w-4 transition-colors",
|
||||
isEnabled
|
||||
? "text-emerald-500 animate-pulse"
|
||||
? "text-emerald-500 status-heartbeat"
|
||||
: "text-muted-foreground",
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -75,7 +75,7 @@ export function ProxyToggle({ className, activeApp }: ProxyToggleProps) {
|
||||
className={cn(
|
||||
"h-4 w-4 transition-colors",
|
||||
takeoverEnabled
|
||||
? "text-emerald-500 animate-pulse"
|
||||
? "text-emerald-500 status-heartbeat"
|
||||
: "text-muted-foreground",
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -118,7 +118,7 @@ export function ProxyTabContent({
|
||||
className="gap-1.5 h-6 ml-auto mr-2"
|
||||
>
|
||||
<Activity
|
||||
className={`h-3 w-3 ${isRunning ? "animate-pulse" : ""}`}
|
||||
className={`h-3 w-3 ${isRunning ? "status-heartbeat" : ""}`}
|
||||
/>
|
||||
{isRunning
|
||||
? t("settings.advanced.proxy.running")
|
||||
|
||||
@@ -145,6 +145,35 @@ html.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
.status-heartbeat {
|
||||
opacity: 1;
|
||||
transition-property: color, opacity;
|
||||
transition-duration: 150ms, 300ms;
|
||||
transition-timing-function: ease, ease-out;
|
||||
}
|
||||
|
||||
html[data-window-active="true"][data-status-heartbeat="true"]
|
||||
.status-heartbeat {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
html[data-window-active="false"] .status-heartbeat {
|
||||
opacity: 1;
|
||||
transition-duration: 0s;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.status-heartbeat {
|
||||
opacity: 1;
|
||||
transition-property: color;
|
||||
}
|
||||
|
||||
html[data-window-active="true"][data-status-heartbeat="true"]
|
||||
.status-heartbeat {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { isTauri } from "@tauri-apps/api/core";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
|
||||
const HEARTBEAT_INTERVAL_MS = 3000;
|
||||
const HEARTBEAT_DIM_MS = 300;
|
||||
|
||||
let initialized = false;
|
||||
let heartbeatInterval: number | undefined;
|
||||
let heartbeatReset: number | undefined;
|
||||
|
||||
function stopHeartbeat() {
|
||||
if (heartbeatInterval !== undefined) {
|
||||
window.clearInterval(heartbeatInterval);
|
||||
heartbeatInterval = undefined;
|
||||
}
|
||||
if (heartbeatReset !== undefined) {
|
||||
window.clearTimeout(heartbeatReset);
|
||||
heartbeatReset = undefined;
|
||||
}
|
||||
delete document.documentElement.dataset.statusHeartbeat;
|
||||
}
|
||||
|
||||
function startHeartbeat() {
|
||||
stopHeartbeat();
|
||||
heartbeatInterval = window.setInterval(() => {
|
||||
document.documentElement.dataset.statusHeartbeat = "true";
|
||||
heartbeatReset = window.setTimeout(() => {
|
||||
delete document.documentElement.dataset.statusHeartbeat;
|
||||
heartbeatReset = undefined;
|
||||
}, HEARTBEAT_DIM_MS);
|
||||
}, HEARTBEAT_INTERVAL_MS);
|
||||
}
|
||||
|
||||
function setWindowActive(active: boolean) {
|
||||
document.documentElement.dataset.windowActive = String(active);
|
||||
|
||||
if (active) {
|
||||
startHeartbeat();
|
||||
} else {
|
||||
stopHeartbeat();
|
||||
}
|
||||
}
|
||||
|
||||
export function initializeWindowActivity() {
|
||||
if (initialized) return;
|
||||
initialized = true;
|
||||
|
||||
setWindowActive(document.hasFocus());
|
||||
|
||||
// Browser focus events are a fallback for non-Tauri renderer tests and dev mode.
|
||||
window.addEventListener("focus", () => setWindowActive(true));
|
||||
window.addEventListener("blur", () => setWindowActive(false));
|
||||
|
||||
if (isTauri()) {
|
||||
void getCurrentWindow()
|
||||
.onFocusChanged(({ payload }) => setWindowActive(payload))
|
||||
.catch((error) => {
|
||||
console.error("Failed to observe window focus changes", error);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
MODELS_DEV_SYNC_CONFIG_QUERY_KEY,
|
||||
syncModelsDevPricingOnStartup,
|
||||
} from "./lib/modelsDevAutoSync";
|
||||
import { initializeWindowActivity } from "@/lib/windowActivity";
|
||||
|
||||
installGlobalErrorHandlers();
|
||||
|
||||
@@ -114,6 +115,8 @@ async function bootstrap() {
|
||||
reportFrontendError("get_init_error", e);
|
||||
}
|
||||
|
||||
initializeWindowActivity();
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<FrontendErrorBoundary>
|
||||
|
||||
Reference in New Issue
Block a user