refactor: reduce table re-renders

This commit is contained in:
zhom
2025-08-17 11:31:24 +04:00
parent 2e6bb2498b
commit f7e116f345
7 changed files with 684 additions and 498 deletions
+60 -101
View File
@@ -3,7 +3,7 @@
import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { getCurrent } from "@tauri-apps/plugin-deep-link";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { CamoufoxConfigDialog } from "@/components/camoufox-config-dialog";
import { CreateProfileDialog } from "@/components/create-profile-dialog";
import { DeleteConfirmationDialog } from "@/components/delete-confirmation-dialog";
@@ -76,7 +76,9 @@ export default function Home() {
const [isBulkDeleting, setIsBulkDeleting] = useState(false);
const { isMicrophoneAccessGranted, isCameraAccessGranted, isInitialized } =
usePermissions();
const [runningProfiles, setRunningProfiles] = useState<Set<string>>(
new Set(),
);
const handleSelectGroup = useCallback((groupId: string) => {
setSelectedGroupId(groupId);
setSelectedProfiles([]);
@@ -423,20 +425,17 @@ export default function Home() {
setError(null);
try {
const _profile = await invoke<BrowserProfile>(
"create_browser_profile_new",
{
name: profileData.name,
browserStr: profileData.browserStr,
version: profileData.version,
releaseType: profileData.releaseType,
proxyId: profileData.proxyId,
camoufoxConfig: profileData.camoufoxConfig,
groupId:
profileData.groupId ||
(selectedGroupId !== "default" ? selectedGroupId : undefined),
},
);
await invoke<BrowserProfile>("create_browser_profile_new", {
name: profileData.name,
browserStr: profileData.browserStr,
version: profileData.version,
releaseType: profileData.releaseType,
proxyId: profileData.proxyId,
camoufoxConfig: profileData.camoufoxConfig,
groupId:
profileData.groupId ||
(selectedGroupId !== "default" ? selectedGroupId : undefined),
});
await loadProfiles();
await loadGroups();
@@ -453,77 +452,48 @@ export default function Home() {
[loadProfiles, loadGroups, selectedGroupId],
);
const [runningProfiles, setRunningProfiles] = useState<Set<string>>(
new Set(),
);
const runningProfilesRef = useRef<Set<string>>(new Set());
const checkBrowserStatus = useCallback(async (profile: BrowserProfile) => {
try {
const isRunning = await invoke<boolean>("check_browser_status", {
profile,
});
const currentRunning = runningProfilesRef.current.has(profile.name);
if (isRunning !== currentRunning) {
console.log(
`Profile ${profile.name} (${profile.browser}) status changed: ${currentRunning} -> ${isRunning}`,
);
setRunningProfiles((prev) => {
const next = new Set(prev);
if (isRunning) {
next.add(profile.name);
} else {
next.delete(profile.name);
}
runningProfilesRef.current = next;
return next;
});
}
} catch (err) {
console.error("Failed to check browser status:", err);
}
}, []);
const launchProfile = useCallback(
async (profile: BrowserProfile) => {
setError(null);
// Check if browser is disabled due to ongoing update
useEffect(() => {
let unlisten: (() => void) | undefined;
(async () => {
try {
const isDisabled = await invoke<boolean>(
"is_browser_disabled_for_update",
{
browser: profile.browser,
unlisten = await listen<{ id: string; is_running: boolean }>(
"profile-running-changed",
(event) => {
const { id, is_running } = event.payload;
setRunningProfiles((prev) => {
const next = new Set(prev);
if (is_running) next.add(id);
else next.delete(id);
return next;
});
},
);
if (isDisabled || isUpdating(profile.browser)) {
setError(
`${profile.browser} is currently being updated. Please wait for the update to complete.`,
);
return;
}
} catch (err) {
console.error("Failed to check browser update status:", err);
} catch {
// best-effort listener
}
})();
return () => {
if (unlisten) unlisten();
};
}, []);
try {
const updatedProfile = await invoke<BrowserProfile>(
"launch_browser_profile",
{ profile },
);
await loadProfiles();
await checkBrowserStatus(updatedProfile);
} catch (err: unknown) {
console.error("Failed to launch browser:", err);
setError(`Failed to launch browser: ${JSON.stringify(err)}`);
}
},
[loadProfiles, checkBrowserStatus, isUpdating],
);
const launchProfile = useCallback(async (profile: BrowserProfile) => {
setError(null);
console.log("Starting launch for profile:", profile.name);
try {
const result = await invoke<BrowserProfile>("launch_browser_profile", {
profile,
});
console.log("Successfully launched profile:", result.name);
} catch (err: unknown) {
console.error("Failed to launch browser:", err);
const errorMessage = err instanceof Error ? err.message : String(err);
setError(`Failed to launch browser: ${errorMessage}`);
// Re-throw the error so the table component can handle loading state cleanup
throw err;
}
}, []);
const handleDeleteProfile = useCallback(
async (profile: BrowserProfile) => {
@@ -582,12 +552,19 @@ export default function Home() {
const handleKillProfile = useCallback(
async (profile: BrowserProfile) => {
setError(null);
console.log("Starting kill for profile:", profile.name);
try {
await invoke("kill_browser_profile", { profile });
await loadProfiles();
console.log("Successfully killed profile:", profile.name);
// Don't reload profiles here - let the backend events handle UI updates
} catch (err: unknown) {
console.error("Failed to kill browser:", err);
setError(`Failed to kill browser: ${JSON.stringify(err)}`);
const errorMessage = err instanceof Error ? err.message : String(err);
setError(`Failed to kill browser: ${errorMessage}`);
// Re-throw the error so the table component can handle loading state cleanup
throw err;
}
},
[loadProfiles],
@@ -732,24 +709,6 @@ export default function Home() {
}
}, [profiles]);
useEffect(() => {
if (profiles.length === 0) return;
const interval = setInterval(() => {
for (const profile of profiles) {
void checkBrowserStatus(profile);
}
}, 500);
return () => {
clearInterval(interval);
};
}, [profiles, checkBrowserStatus]);
useEffect(() => {
runningProfilesRef.current = runningProfiles;
}, [runningProfiles]);
useEffect(() => {
if (error) {
showErrorToast(error);
File diff suppressed because it is too large Load Diff
+14 -9
View File
@@ -97,7 +97,7 @@ export function ProfileSelectorDialog({
if (profileList.length > 0) {
// First, try to find a running profile that can be used for opening links
const runningAvailableProfile = profileList.find((profile) => {
const isRunning = runningProfiles.has(profile.name);
const isRunning = runningProfiles.has(profile.id);
// Simple check without browserState dependency
return (
isRunning &&
@@ -128,7 +128,10 @@ export function ProfileSelectorDialog({
if (!selectedProfile || !url) return;
setIsLaunching(true);
setLaunchingProfiles((prev) => new Set(prev).add(selectedProfile));
const selected = profiles.find((p) => p.name === selectedProfile);
if (selected) {
setLaunchingProfiles((prev) => new Set(prev).add(selected.id));
}
try {
await invoke("open_url_with_profile", {
profileName: selectedProfile,
@@ -139,13 +142,15 @@ export function ProfileSelectorDialog({
console.error("Failed to open URL with profile:", error);
} finally {
setIsLaunching(false);
setLaunchingProfiles((prev) => {
const next = new Set(prev);
next.delete(selectedProfile);
return next;
});
if (selected) {
setLaunchingProfiles((prev) => {
const next = new Set(prev);
next.delete(selected.id);
return next;
});
}
}
}, [selectedProfile, url, onClose]);
}, [selectedProfile, url, onClose, profiles]);
const handleCancel = useCallback(() => {
setSelectedProfile(null);
@@ -238,7 +243,7 @@ export function ProfileSelectorDialog({
</SelectTrigger>
<SelectContent>
{profiles.map((profile) => {
const isRunning = runningProfiles.has(profile.name);
const isRunning = runningProfiles.has(profile.id);
const canUseForLinks =
browserState.canUseProfileForLinks(profile);
const tooltipContent = getProfileTooltipContent(profile);
+23 -23
View File
@@ -35,7 +35,7 @@ export function useBrowserState(
(browserType: string): boolean => {
if (!isClient) return false;
return profiles.some(
(p) => p.browser === browserType && runningProfiles.has(p.name),
(p) => p.browser === browserType && runningProfiles.has(p.id),
);
},
[profiles, runningProfiles, isClient],
@@ -48,10 +48,10 @@ export function useBrowserState(
(profile: BrowserProfile): boolean => {
if (!isClient) return false;
const isRunning = runningProfiles.has(profile.name);
const isLaunching = launchingProfiles?.has(profile.name) ?? false;
const isStopping = stoppingProfiles?.has(profile.name) ?? false;
const isBrowserUpdating = isUpdating?.(profile.browser) ?? false;
const isRunning = runningProfiles.has(profile.id);
const isLaunching = launchingProfiles.has(profile.id);
const isStopping = stoppingProfiles.has(profile.id);
const isBrowserUpdating = isUpdating(profile.browser);
// If the profile is launching or stopping, disable the button
if (isLaunching || isStopping) {
@@ -92,10 +92,10 @@ export function useBrowserState(
(profile: BrowserProfile): boolean => {
if (!isClient) return false;
const isRunning = runningProfiles.has(profile.name);
const isLaunching = launchingProfiles?.has(profile.name) ?? false;
const isStopping = stoppingProfiles?.has(profile.name) ?? false;
const isBrowserUpdating = isUpdating?.(profile.browser) ?? false;
const isRunning = runningProfiles.has(profile.id);
const isLaunching = launchingProfiles.has(profile.id);
const isStopping = stoppingProfiles.has(profile.id);
const isBrowserUpdating = isUpdating(profile.browser);
// If this specific browser is updating, downloading, launching, or stopping, block it
if (isBrowserUpdating || isLaunching || isStopping) {
@@ -105,7 +105,7 @@ export function useBrowserState(
// For single-instance browsers (Tor and Mullvad)
if (isSingleInstanceBrowser(profile.browser)) {
const runningInstancesOfType = profiles.filter(
(p) => p.browser === profile.browser && runningProfiles.has(p.name),
(p) => p.browser === profile.browser && runningProfiles.has(p.id),
);
// If no instances are running, any profile of this type can be used
@@ -142,10 +142,10 @@ export function useBrowserState(
(profile: BrowserProfile): boolean => {
if (!isClient) return false;
const isRunning = runningProfiles.has(profile.name);
const isLaunching = launchingProfiles?.has(profile.name) ?? false;
const isStopping = stoppingProfiles?.has(profile.name) ?? false;
const isBrowserUpdating = isUpdating?.(profile.browser) ?? false;
const isRunning = runningProfiles.has(profile.id);
const isLaunching = launchingProfiles.has(profile.id);
const isStopping = stoppingProfiles.has(profile.id);
const isBrowserUpdating = isUpdating(profile.browser);
// If profile is running, launching, stopping, or browser is updating, block selection
if (isRunning || isLaunching || isStopping || isBrowserUpdating) {
@@ -170,10 +170,10 @@ export function useBrowserState(
(profile: BrowserProfile): string => {
if (!isClient) return "Loading...";
const isRunning = runningProfiles.has(profile.name);
const isLaunching = launchingProfiles?.has(profile.name) ?? false;
const isStopping = stoppingProfiles?.has(profile.name) ?? false;
const isBrowserUpdating = isUpdating?.(profile.browser) ?? false;
const isRunning = runningProfiles.has(profile.id);
const isLaunching = launchingProfiles.has(profile.id);
const isStopping = stoppingProfiles.has(profile.id);
const isBrowserUpdating = isUpdating(profile.browser);
if (isLaunching) {
return "Launching browser...";
@@ -224,10 +224,10 @@ export function useBrowserState(
if (canUseForLinks) return null;
const isRunning = runningProfiles.has(profile.name);
const isLaunching = launchingProfiles?.has(profile.name) ?? false;
const isStopping = stoppingProfiles?.has(profile.name) ?? false;
const isBrowserUpdating = isUpdating?.(profile.browser) ?? false;
const isRunning = runningProfiles.has(profile.id);
const isLaunching = launchingProfiles.has(profile.id);
const isStopping = stoppingProfiles.has(profile.id);
const isBrowserUpdating = isUpdating(profile.browser);
if (isLaunching) {
return "Profile is currently launching. Please wait.";
@@ -245,7 +245,7 @@ export function useBrowserState(
const browserDisplayName =
profile.browser === "tor-browser" ? "TOR" : "Mullvad";
const runningInstancesOfType = profiles.filter(
(p) => p.browser === profile.browser && runningProfiles.has(p.name),
(p) => p.browser === profile.browser && runningProfiles.has(p.id),
);
if (runningInstancesOfType.length > 0) {