mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-29 18:09:55 +02:00
refactor: cleanup
This commit is contained in:
@@ -83,12 +83,7 @@ interface ErrorToastProps extends BaseToastProps {
|
||||
|
||||
interface DownloadToastProps extends BaseToastProps {
|
||||
type: "download";
|
||||
stage?:
|
||||
| "downloading"
|
||||
| "extracting"
|
||||
| "verifying"
|
||||
| "completed"
|
||||
| "downloading (twilight rolling release)";
|
||||
stage?: "downloading" | "extracting" | "verifying" | "completed";
|
||||
progress?: {
|
||||
percentage: number;
|
||||
speed?: string;
|
||||
@@ -111,12 +106,6 @@ interface FetchingToastProps extends BaseToastProps {
|
||||
browserName?: string;
|
||||
}
|
||||
|
||||
interface TwilightUpdateToastProps extends BaseToastProps {
|
||||
type: "twilight-update";
|
||||
browserName?: string;
|
||||
hasUpdate?: boolean;
|
||||
}
|
||||
|
||||
interface SyncProgressToastProps extends BaseToastProps {
|
||||
type: "sync-progress";
|
||||
progress?: {
|
||||
@@ -138,7 +127,6 @@ type ToastProps =
|
||||
| DownloadToastProps
|
||||
| VersionUpdateToastProps
|
||||
| FetchingToastProps
|
||||
| TwilightUpdateToastProps
|
||||
| SyncProgressToastProps;
|
||||
|
||||
function formatBytesCompact(bytes: number): string {
|
||||
@@ -191,10 +179,6 @@ function getToastIcon(type: ToastProps["type"], stage?: string) {
|
||||
return (
|
||||
<LuRefreshCw className="shrink-0 size-4 animate-spin text-foreground" />
|
||||
);
|
||||
case "twilight-update":
|
||||
return (
|
||||
<LuRefreshCw className="shrink-0 size-4 animate-spin text-foreground" />
|
||||
);
|
||||
case "sync-progress":
|
||||
return (
|
||||
<LuRefreshCw className="shrink-0 size-4 animate-spin text-foreground" />
|
||||
@@ -246,7 +230,8 @@ export function UnifiedToast(props: ToastProps) {
|
||||
<p className="flex-1 min-w-0 text-xs text-muted-foreground">
|
||||
{progress.percentage.toFixed(1)}%
|
||||
{progress.speed && ` • ${progress.speed} MB/s`}
|
||||
{progress.eta && ` • ${progress.eta} remaining`}
|
||||
{progress.eta &&
|
||||
` • ${t("toasts.progress.remaining", { time: progress.eta })}`}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full bg-muted rounded-full h-1.5">
|
||||
@@ -264,9 +249,10 @@ export function UnifiedToast(props: ToastProps) {
|
||||
"current_browser" in progress && (
|
||||
<div className="mt-2 space-y-1">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{progress.current_browser && (
|
||||
<>Looking for updates for {progress.current_browser}</>
|
||||
)}
|
||||
{progress.current_browser &&
|
||||
t("versionUpdater.toast.lookingForUpdates", {
|
||||
browser: progress.current_browser,
|
||||
})}
|
||||
</p>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="flex-1 bg-muted rounded-full h-1.5 min-w-0">
|
||||
@@ -293,7 +279,10 @@ export function UnifiedToast(props: ToastProps) {
|
||||
{progress.phase === "uploading"
|
||||
? t("appUpdate.toast.uploading")
|
||||
: t("appUpdate.toast.downloading")}{" "}
|
||||
{progress.completed_files}/{progress.total_files} files
|
||||
{t("toasts.progress.filesProgress", {
|
||||
completed: progress.completed_files,
|
||||
total: progress.total_files,
|
||||
})}
|
||||
{" \u2022 "}
|
||||
{formatBytesCompact(progress.completed_bytes)} /{" "}
|
||||
{formatBytesCompact(progress.total_bytes)}
|
||||
@@ -304,37 +293,21 @@ export function UnifiedToast(props: ToastProps) {
|
||||
</>
|
||||
)}
|
||||
{progress.eta_seconds > 0 &&
|
||||
progress.completed_files < progress.total_files && (
|
||||
<>
|
||||
{" \u2022 ~"}
|
||||
{formatEtaCompact(progress.eta_seconds)} remaining
|
||||
</>
|
||||
)}
|
||||
progress.completed_files < progress.total_files &&
|
||||
` \u2022 ${t("toasts.progress.remaining", {
|
||||
time: `~${formatEtaCompact(progress.eta_seconds)}`,
|
||||
})}`}
|
||||
</p>
|
||||
{progress.failed_count > 0 && (
|
||||
<p className="text-xs text-destructive mt-0.5">
|
||||
{progress.failed_count} file(s) failed
|
||||
{t("toasts.progress.filesFailed", {
|
||||
count: progress.failed_count,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Twilight update progress */}
|
||||
{type === "twilight-update" && (
|
||||
<div className="mt-2">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{"hasUpdate" in props && props.hasUpdate
|
||||
? "New twilight build available for download"
|
||||
: "Checking for twilight updates..."}
|
||||
</p>
|
||||
{props.browserName && (
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{props.browserName} • Rolling Release
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Description */}
|
||||
{description && (
|
||||
<p className="mt-1 text-xs leading-tight text-muted-foreground">
|
||||
@@ -355,11 +328,6 @@ export function UnifiedToast(props: ToastProps) {
|
||||
{t("browserDownload.toast.verifying")}
|
||||
</p>
|
||||
)}
|
||||
{stage === "downloading (twilight rolling release)" && (
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
{t("browserDownload.toast.downloadingRolling")}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{action &&
|
||||
|
||||
@@ -2039,12 +2039,12 @@ export function ProfilesDataTable({
|
||||
|
||||
if (isDisabled) {
|
||||
const tooltipMessage = isRunning
|
||||
? "Can't modify running profile"
|
||||
? t("profiles.table.cantModifyRunning")
|
||||
: isLaunching
|
||||
? "Can't modify profile while launching"
|
||||
? t("profiles.table.cantModifyLaunching")
|
||||
: isStopping
|
||||
? "Can't modify profile while stopping"
|
||||
: "Can't modify profile while browser is updating";
|
||||
? t("profiles.table.cantModifyStopping")
|
||||
: t("profiles.table.cantModifyUpdating");
|
||||
|
||||
return (
|
||||
<Tooltip>
|
||||
|
||||
@@ -263,9 +263,9 @@ export function ProfileInfoDialog({
|
||||
? vpnConfigs.find((v) => v.id === profile.vpn_id)?.name
|
||||
: null;
|
||||
const networkLabel = vpnName
|
||||
? `VPN: ${vpnName}`
|
||||
? t("profileInfo.network.vpnLabel", { name: vpnName })
|
||||
: proxyName
|
||||
? `Proxy: ${proxyName}`
|
||||
? t("profileInfo.network.proxyLabel", { name: proxyName })
|
||||
: t("profileInfo.values.none");
|
||||
|
||||
const syncStatus = syncStatuses[profile.id];
|
||||
@@ -299,6 +299,10 @@ export function ProfileInfoDialog({
|
||||
// `ProfileDnsBlocklistDialog` for the pattern). The settings tab is purely
|
||||
// a navigation hub.
|
||||
interface ActionItem {
|
||||
// Stable, language-independent key used to map sidebar sections to actions.
|
||||
// The sidebar must NOT match on `label` — labels are translated, so English
|
||||
// substring matching hides sections for every non-English user.
|
||||
id?: string;
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
@@ -311,6 +315,7 @@ export function ProfileInfoDialog({
|
||||
|
||||
const actions: ActionItem[] = [
|
||||
{
|
||||
id: "network",
|
||||
icon: <LuGlobe className="size-4" />,
|
||||
label: t("profiles.actions.viewNetwork"),
|
||||
onClick: () => {
|
||||
@@ -319,6 +324,7 @@ export function ProfileInfoDialog({
|
||||
disabled: isCrossOs,
|
||||
},
|
||||
{
|
||||
id: "sync",
|
||||
icon: <LuRefreshCw className="size-4" />,
|
||||
label: t("profiles.actions.syncSettings"),
|
||||
onClick: () => {
|
||||
@@ -337,6 +343,7 @@ export function ProfileInfoDialog({
|
||||
runningBadge: isRunning,
|
||||
},
|
||||
{
|
||||
id: "fingerprint",
|
||||
icon: <LuFingerprint className="size-4" />,
|
||||
label: t("profiles.actions.changeFingerprint"),
|
||||
onClick: () => {
|
||||
@@ -359,6 +366,7 @@ export function ProfileInfoDialog({
|
||||
hidden: profile.browser !== "wayfern" || !onLaunchWithSync,
|
||||
},
|
||||
{
|
||||
id: "cookiesCopy",
|
||||
icon: <LuCopy className="size-4" />,
|
||||
label: t("profiles.actions.copyCookiesToProfile"),
|
||||
onClick: () => {
|
||||
@@ -372,6 +380,7 @@ export function ProfileInfoDialog({
|
||||
!onCopyCookiesToProfile,
|
||||
},
|
||||
{
|
||||
id: "cookiesManage",
|
||||
icon: <LuCookie className="size-4" />,
|
||||
label: t("profileInfo.actions.manageCookies"),
|
||||
onClick: () => {
|
||||
@@ -395,6 +404,7 @@ export function ProfileInfoDialog({
|
||||
hidden: profile.ephemeral === true,
|
||||
},
|
||||
{
|
||||
id: "extension",
|
||||
icon: <LuPuzzle className="size-4" />,
|
||||
label: t("profileInfo.actions.assignExtensionGroup"),
|
||||
onClick: () => {
|
||||
@@ -419,6 +429,7 @@ export function ProfileInfoDialog({
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "hook",
|
||||
icon: <LuLink className="size-4" />,
|
||||
label: t("profiles.actions.launchHook"),
|
||||
onClick: () => {
|
||||
@@ -461,6 +472,7 @@ export function ProfileInfoDialog({
|
||||
destructive: true,
|
||||
},
|
||||
{
|
||||
id: "delete",
|
||||
icon: <LuTrash2 className="size-4" />,
|
||||
label: t("profiles.actions.delete"),
|
||||
onClick: () => {
|
||||
@@ -534,6 +546,7 @@ interface ProfileInfoLayoutProps {
|
||||
onCloneProfile?: (profile: BrowserProfile) => void;
|
||||
onKillProfile?: (profile: BrowserProfile) => void;
|
||||
visibleActions: {
|
||||
id?: string;
|
||||
icon: React.ReactNode;
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
@@ -579,22 +592,23 @@ function ProfileInfoLayout({
|
||||
}: ProfileInfoLayoutProps) {
|
||||
const [section, setSection] = React.useState<ProfileSection>("overview");
|
||||
|
||||
// Map sidebar items to existing action labels, so clicking a section
|
||||
// simply triggers the existing dialog handler.
|
||||
// Map sidebar items to existing actions by their stable, language-independent
|
||||
// `id`, so clicking a section triggers the existing dialog handler. Matching
|
||||
// on `label` would break for every non-English locale (the labels are
|
||||
// translated) and hide whole sections.
|
||||
const findAction = React.useCallback(
|
||||
(substr: string) =>
|
||||
visibleActions.find((a) => a.label.toLowerCase().includes(substr)),
|
||||
(id: string) => visibleActions.find((a) => a.id === id),
|
||||
[visibleActions],
|
||||
);
|
||||
|
||||
const deleteAction = findAction("delete");
|
||||
const fingerprintAction = findAction("fingerprint");
|
||||
const cookiesManageAction = findAction("manage cookies");
|
||||
const cookiesCopyAction = findAction("copy cookies");
|
||||
const cookiesManageAction = findAction("cookiesManage");
|
||||
const cookiesCopyAction = findAction("cookiesCopy");
|
||||
const cookiesAction = cookiesManageAction ?? cookiesCopyAction;
|
||||
const extensionAction = findAction("extension");
|
||||
const syncAction = findAction("sync");
|
||||
const _launchHookAction = findAction("hook") ?? findAction("launch hook");
|
||||
const _launchHookAction = findAction("hook");
|
||||
const _networkAction = findAction("network");
|
||||
// Password actions are no longer routed via the legacy action handlers —
|
||||
// SecuritySectionInline writes directly to the backend instead.
|
||||
@@ -1149,7 +1163,7 @@ function SyncSectionInline({
|
||||
syncMode: mode,
|
||||
});
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
setError(translateBackendError(t as never, e));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
@@ -1192,7 +1206,9 @@ function SyncSectionInline({
|
||||
<p className="text-[10px] uppercase tracking-wide text-muted-foreground">
|
||||
{t("profileInfo.fields.syncStatus")}
|
||||
</p>
|
||||
<p className="text-sm mt-0.5">{syncStatus.status}</p>
|
||||
<p className="text-sm mt-0.5">
|
||||
{t(`profileInfo.syncStatusValue.${syncStatus.status}`)}
|
||||
</p>
|
||||
{syncStatus.error && (
|
||||
<p className="text-xs text-destructive mt-1">{syncStatus.error}</p>
|
||||
)}
|
||||
@@ -1246,7 +1262,7 @@ function NetworkSectionInline({
|
||||
setProxyId(nextId);
|
||||
if (nextId !== null) setVpnId(null);
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
setError(translateBackendError(t as never, e));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
@@ -1264,7 +1280,7 @@ function NetworkSectionInline({
|
||||
setVpnId(nextId);
|
||||
if (nextId !== null) setProxyId(null);
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
setError(translateBackendError(t as never, e));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
@@ -1370,7 +1386,7 @@ function ExtensionsSectionInline({
|
||||
);
|
||||
if (mounted) setGroups(data);
|
||||
} catch (e) {
|
||||
if (mounted) setError(String(e));
|
||||
if (mounted) setError(translateBackendError(t as never, e));
|
||||
}
|
||||
};
|
||||
void load();
|
||||
@@ -1384,7 +1400,7 @@ function ExtensionsSectionInline({
|
||||
mounted = false;
|
||||
unlisten?.();
|
||||
};
|
||||
}, []);
|
||||
}, [t]);
|
||||
|
||||
const onChange = async (value: string) => {
|
||||
const next = value === "__none__" ? null : value;
|
||||
@@ -1397,7 +1413,7 @@ function ExtensionsSectionInline({
|
||||
});
|
||||
setGroupId(next);
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
setError(translateBackendError(t as never, e));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
@@ -1684,7 +1700,7 @@ function FingerprintSectionInline({
|
||||
// Close the dialog once the fingerprint is saved.
|
||||
onSaved();
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
setError(translateBackendError(t as never, e));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ export function ProfilePasswordDialog({
|
||||
<div className="flex flex-col gap-3">
|
||||
{(mode === "set" || mode === "change") && (
|
||||
<div className="rounded-md border border-warning/50 bg-warning/10 p-3 text-sm">
|
||||
<p className="font-medium text-warning-foreground">
|
||||
<p className="font-medium text-warning">
|
||||
{t("profilePassword.warnings.forgetWarningTitle")}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
|
||||
Reference in New Issue
Block a user