chore: linting

This commit is contained in:
zhom
2025-06-22 06:04:02 +04:00
parent e090881917
commit 3d3a3b3816
26 changed files with 607 additions and 574 deletions
+2 -3
View File
@@ -1,10 +1,9 @@
"use client";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import React from "react";
import { FaDownload, FaTimes } from "react-icons/fa";
import { LuRefreshCw } from "react-icons/lu";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
interface AppUpdateInfo {
current_version: string;
+19 -19
View File
@@ -1,5 +1,8 @@
"use client";
import { invoke } from "@tauri-apps/api/core";
import { useCallback, useEffect, useState } from "react";
import { LuTriangleAlert } from "react-icons/lu";
import { LoadingButton } from "@/components/loading-button";
import { ReleaseTypeSelector } from "@/components/release-type-selector";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
@@ -16,9 +19,6 @@ import { Label } from "@/components/ui/label";
import { useBrowserDownload } from "@/hooks/use-browser-download";
import { getBrowserDisplayName } from "@/lib/browser-utils";
import type { BrowserProfile, BrowserReleaseTypes } from "@/types";
import { invoke } from "@tauri-apps/api/core";
import { useEffect, useState } from "react";
import { LuTriangleAlert } from "react-icons/lu";
interface ChangeVersionDialogProps {
isOpen: boolean;
@@ -50,17 +50,7 @@ export function ChangeVersionDialog({
isVersionDownloaded,
} = useBrowserDownload();
useEffect(() => {
if (isOpen && profile) {
// Set current release type based on profile
setSelectedReleaseType(profile.release_type as "stable" | "nightly");
setAcknowledgeDowngrade(false);
void loadReleaseTypes(profile.browser);
void loadDownloadedVersions(profile.browser);
}
}, [isOpen, profile, loadDownloadedVersions]);
const loadReleaseTypes = async (browser: string) => {
const loadReleaseTypes = useCallback(async (browser: string) => {
setIsLoadingReleaseTypes(true);
try {
const releaseTypes = await invoke<BrowserReleaseTypes>(
@@ -73,7 +63,7 @@ export function ChangeVersionDialog({
} finally {
setIsLoadingReleaseTypes(false);
}
};
}, []);
useEffect(() => {
if (
@@ -93,7 +83,7 @@ export function ChangeVersionDialog({
}
}, [selectedReleaseType, profile]);
const handleDownload = async () => {
const handleDownload = useCallback(async () => {
if (!profile || !selectedReleaseType) return;
const version =
@@ -103,9 +93,9 @@ export function ChangeVersionDialog({
if (!version) return;
await downloadBrowser(profile.browser, version);
};
}, [profile, selectedReleaseType, downloadBrowser, releaseTypes]);
const handleVersionChange = async () => {
const handleVersionChange = useCallback(async () => {
if (!profile || !selectedReleaseType) return;
const version =
@@ -127,7 +117,7 @@ export function ChangeVersionDialog({
} finally {
setIsUpdating(false);
}
};
}, [profile, selectedReleaseType, releaseTypes, onVersionChanged, onClose]);
const selectedVersion =
selectedReleaseType === "stable"
@@ -142,6 +132,16 @@ export function ChangeVersionDialog({
isVersionDownloaded(selectedVersion) &&
(!showDowngradeWarning || acknowledgeDowngrade);
useEffect(() => {
if (isOpen && profile) {
// Set current release type based on profile
setSelectedReleaseType(profile.release_type as "stable" | "nightly");
setAcknowledgeDowngrade(false);
void loadReleaseTypes(profile.browser);
void loadDownloadedVersions(profile.browser);
}
}, [isOpen, profile, loadDownloadedVersions, loadReleaseTypes]);
if (!profile) return null;
return (
+59 -40
View File
@@ -1,5 +1,8 @@
"use client";
import { invoke } from "@tauri-apps/api/core";
import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";
import { LoadingButton } from "@/components/loading-button";
import { ReleaseTypeSelector } from "@/components/release-type-selector";
import { Button } from "@/components/ui/button";
@@ -33,9 +36,6 @@ import type {
BrowserReleaseTypes,
ProxySettings,
} from "@/types";
import { invoke } from "@tauri-apps/api/core";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { Alert, AlertDescription } from "./ui/alert";
type BrowserTypeString =
@@ -102,12 +102,6 @@ export function CreateProfileDialog({
isBrowserSupported,
} = useBrowserSupport();
useEffect(() => {
if (isOpen) {
void loadExistingProfiles();
}
}, [isOpen]);
useEffect(() => {
if (supportedBrowsers.length > 0) {
// Set default browser to first supported browser
@@ -119,15 +113,6 @@ export function CreateProfileDialog({
}
}, [supportedBrowsers]);
useEffect(() => {
if (isOpen && selectedBrowser) {
// Reset selected release type when browser changes
setSelectedReleaseType(null);
void loadReleaseTypes(selectedBrowser);
void loadDownloadedVersions(selectedBrowser);
}
}, [isOpen, selectedBrowser, loadDownloadedVersions]);
// Set default release type when release types are loaded
useEffect(() => {
if (!selectedReleaseType && Object.keys(releaseTypes).length > 0) {
@@ -142,16 +127,16 @@ export function CreateProfileDialog({
}
}, [releaseTypes, selectedReleaseType, selectedBrowser]);
const loadExistingProfiles = async () => {
const loadExistingProfiles = useCallback(async () => {
try {
const profiles = await invoke<BrowserProfile[]>("list_browser_profiles");
setExistingProfiles(profiles);
} catch (error) {
console.error("Failed to load existing profiles:", error);
}
};
}, []);
const loadReleaseTypes = async (browser: string) => {
const loadReleaseTypes = useCallback(async (browser: string) => {
try {
setIsLoadingReleaseTypes(true);
const types = await invoke<BrowserReleaseTypes>(
@@ -167,9 +152,9 @@ export function CreateProfileDialog({
} finally {
setIsLoadingReleaseTypes(false);
}
};
}, []);
const handleDownload = async () => {
const handleDownload = useCallback(async () => {
if (!selectedBrowser || !selectedReleaseType) return;
const version =
@@ -179,26 +164,29 @@ export function CreateProfileDialog({
if (!version) return;
await downloadBrowser(selectedBrowser, version);
};
}, [selectedBrowser, selectedReleaseType, downloadBrowser, releaseTypes]);
const validateProfileName = (name: string): string | null => {
const trimmedName = name.trim();
const validateProfileName = useCallback(
(name: string): string | null => {
const trimmedName = name.trim();
if (!trimmedName) {
return "Profile name cannot be empty";
}
if (!trimmedName) {
return "Profile name cannot be empty";
}
// Check for duplicate names (case insensitive)
const isDuplicate = existingProfiles.some(
(profile) => profile.name.toLowerCase() === trimmedName.toLowerCase(),
);
// Check for duplicate names (case insensitive)
const isDuplicate = existingProfiles.some(
(profile) => profile.name.toLowerCase() === trimmedName.toLowerCase(),
);
if (isDuplicate) {
return "A profile with this name already exists";
}
if (isDuplicate) {
return "A profile with this name already exists";
}
return null;
};
return null;
},
[existingProfiles],
);
// Helper to determine if proxy should be disabled for the selected browser
const isProxyDisabled = selectedBrowser === "tor-browser";
@@ -210,7 +198,7 @@ export function CreateProfileDialog({
}
}, [selectedBrowser, proxyEnabled]);
const handleCreate = async () => {
const handleCreate = useCallback(async () => {
if (!profileName.trim() || !selectedBrowser || !selectedReleaseType) return;
// Validate profile name
@@ -265,7 +253,23 @@ export function CreateProfileDialog({
} finally {
setIsCreating(false);
}
};
}, [
profileName,
selectedBrowser,
selectedReleaseType,
onCreateProfile,
proxyEnabled,
isProxyDisabled,
onClose,
proxyHost,
proxyPassword,
proxyPort,
proxyType,
proxyUsername,
releaseTypes.nightly,
releaseTypes.stable,
validateProfileName,
]);
const nameError = profileName.trim()
? validateProfileName(profileName)
@@ -285,6 +289,21 @@ export function CreateProfileDialog({
(!proxyEnabled || isProxyDisabled || (proxyHost && proxyPort)) &&
!nameError;
useEffect(() => {
if (isOpen) {
void loadExistingProfiles();
}
}, [isOpen, loadExistingProfiles]);
useEffect(() => {
if (isOpen && selectedBrowser) {
// Reset selected release type when browser changes
setSelectedReleaseType(null);
void loadReleaseTypes(selectedBrowser);
void loadDownloadedVersions(selectedBrowser);
}
}, [isOpen, selectedBrowser, loadDownloadedVersions, loadReleaseTypes]);
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-md max-h-[80vh] my-8 flex flex-col">
-1
View File
@@ -48,7 +48,6 @@
* ```
*/
import React from "react";
import {
LuCheckCheck,
LuDownload,
+29 -17
View File
@@ -1,5 +1,10 @@
"use client";
import { invoke } from "@tauri-apps/api/core";
import { open } from "@tauri-apps/plugin-dialog";
import { useCallback, useEffect, useState } from "react";
import { FaFolder } from "react-icons/fa";
import { toast } from "sonner";
import { LoadingButton } from "@/components/loading-button";
import { Button } from "@/components/ui/button";
import {
@@ -21,11 +26,6 @@ import {
import { useBrowserSupport } from "@/hooks/use-browser-support";
import { getBrowserDisplayName, getBrowserIcon } from "@/lib/browser-utils";
import type { DetectedProfile } from "@/types";
import { invoke } from "@tauri-apps/api/core";
import { open } from "@tauri-apps/plugin-dialog";
import { useEffect, useState } from "react";
import { FaFolder } from "react-icons/fa";
import { toast } from "sonner";
interface ImportProfileDialogProps {
isOpen: boolean;
@@ -63,13 +63,7 @@ export function ImportProfileDialog({
const { supportedBrowsers, isLoading: isLoadingSupport } =
useBrowserSupport();
useEffect(() => {
if (isOpen) {
void loadDetectedProfiles();
}
}, [isOpen]);
const loadDetectedProfiles = async () => {
const loadDetectedProfiles = useCallback(async () => {
setIsLoading(true);
try {
const profiles = await invoke<DetectedProfile[]>(
@@ -96,7 +90,7 @@ export function ImportProfileDialog({
} finally {
setIsLoading(false);
}
};
}, []);
const handleBrowseFolder = async () => {
try {
@@ -115,7 +109,7 @@ export function ImportProfileDialog({
}
};
const handleAutoDetectImport = async () => {
const handleAutoDetectImport = useCallback(async () => {
if (!selectedDetectedProfile || !autoDetectProfileName.trim()) {
toast.error("Please select a profile and provide a name");
return;
@@ -152,9 +146,15 @@ export function ImportProfileDialog({
} finally {
setIsImporting(false);
}
};
}, [
selectedDetectedProfile,
autoDetectProfileName,
detectedProfiles,
onImportComplete,
onClose,
]);
const handleManualImport = async () => {
const handleManualImport = useCallback(async () => {
if (
!manualBrowserType ||
!manualProfilePath.trim() ||
@@ -187,7 +187,13 @@ export function ImportProfileDialog({
} finally {
setIsImporting(false);
}
};
}, [
manualBrowserType,
manualProfilePath,
manualProfileName,
onImportComplete,
onClose,
]);
const handleClose = () => {
setSelectedDetectedProfile(null);
@@ -222,6 +228,12 @@ export function ImportProfileDialog({
(p) => p.path === selectedDetectedProfile,
);
useEffect(() => {
if (isOpen) {
void loadDetectedProfiles();
}
}, [isOpen, loadDetectedProfiles]);
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-2xl max-h-[80vh] my-8 flex flex-col">
+1
View File
@@ -1,5 +1,6 @@
import { LuLoaderCircle } from "react-icons/lu";
import { type ButtonProps, Button as UIButton } from "./ui/button";
type Props = ButtonProps & {
isLoading: boolean;
"aria-label"?: string;
+3 -3
View File
@@ -1,5 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { BsCamera, BsMic } from "react-icons/bs";
import { LoadingButton } from "@/components/loading-button";
import { Button } from "@/components/ui/button";
import {
@@ -10,11 +12,9 @@ import {
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { usePermissions } from "@/hooks/use-permissions";
import type { PermissionType } from "@/hooks/use-permissions";
import { usePermissions } from "@/hooks/use-permissions";
import { showErrorToast, showSuccessToast } from "@/lib/toast-utils";
import { useEffect, useState } from "react";
import { BsCamera, BsMic } from "react-icons/bs";
interface PermissionDialogProps {
isOpen: boolean;
+12 -12
View File
@@ -1,5 +1,17 @@
"use client";
import {
type ColumnDef,
flexRender,
getCoreRowModel,
getSortedRowModel,
type SortingState,
useReactTable,
} from "@tanstack/react-table";
import * as React from "react";
import { CiCircleCheck } from "react-icons/ci";
import { IoEllipsisHorizontal } from "react-icons/io5";
import { LuChevronDown, LuChevronUp } from "react-icons/lu";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -33,18 +45,6 @@ import {
import { useTableSorting } from "@/hooks/use-table-sorting";
import { getBrowserDisplayName, getBrowserIcon } from "@/lib/browser-utils";
import type { BrowserProfile } from "@/types";
import {
type ColumnDef,
type SortingState,
flexRender,
getCoreRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import * as React from "react";
import { CiCircleCheck } from "react-icons/ci";
import { IoEllipsisHorizontal } from "react-icons/io5";
import { LuChevronDown, LuChevronUp } from "react-icons/lu";
import { Input } from "./ui/input";
import { Label } from "./ui/label";
+125 -124
View File
@@ -1,5 +1,9 @@
"use client";
import { invoke } from "@tauri-apps/api/core";
import { useCallback, useEffect, useState } from "react";
import { LuCopy } from "react-icons/lu";
import { toast } from "sonner";
import { LoadingButton } from "@/components/loading-button";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -25,10 +29,6 @@ import {
} from "@/components/ui/tooltip";
import { getBrowserDisplayName, getBrowserIcon } from "@/lib/browser-utils";
import type { BrowserProfile } from "@/types";
import { invoke } from "@tauri-apps/api/core";
import { useEffect, useState } from "react";
import { LuCopy } from "react-icons/lu";
import { toast } from "sonner";
interface ProfileSelectorDialogProps {
isOpen: boolean;
@@ -48,13 +48,42 @@ export function ProfileSelectorDialog({
const [isLoading, setIsLoading] = useState(false);
const [isLaunching, setIsLaunching] = useState(false);
useEffect(() => {
if (isOpen) {
void loadProfiles();
}
}, [isOpen]);
// Helper function to determine if a profile can be used for opening links
const canUseProfileForLinks = useCallback(
(
profile: BrowserProfile,
allProfiles: BrowserProfile[],
runningProfiles: Set<string>,
): boolean => {
const isRunning = runningProfiles.has(profile.name);
const loadProfiles = async () => {
// For TOR browser: Check if any TOR browser is running
if (profile.browser === "tor-browser") {
const runningTorProfiles = allProfiles.filter(
(p) => p.browser === "tor-browser" && runningProfiles.has(p.name),
);
// If no TOR browser is running, allow any TOR profile
if (runningTorProfiles.length === 0) {
return true;
}
// If TOR browser(s) are running, only allow the running one(s)
return isRunning;
}
// For Mullvad browser: never allow if running
if (profile.browser === "mullvad-browser" && isRunning) {
return false;
}
// For other browsers: always allow
return true;
},
[],
);
const loadProfiles = useCallback(async () => {
setIsLoading(true);
try {
const profileList = await invoke<BrowserProfile[]>(
@@ -99,39 +128,7 @@ export function ProfileSelectorDialog({
} finally {
setIsLoading(false);
}
};
// Helper function to determine if a profile can be used for opening links
const canUseProfileForLinks = (
profile: BrowserProfile,
allProfiles: BrowserProfile[],
runningProfiles: Set<string>,
): boolean => {
const isRunning = runningProfiles.has(profile.name);
// For TOR browser: Check if any TOR browser is running
if (profile.browser === "tor-browser") {
const runningTorProfiles = allProfiles.filter(
(p) => p.browser === "tor-browser" && runningProfiles.has(p.name),
);
// If no TOR browser is running, allow any TOR profile
if (runningTorProfiles.length === 0) {
return true;
}
// If TOR browser(s) are running, only allow the running one(s)
return isRunning;
}
// For Mullvad browser: never allow if running
if (profile.browser === "mullvad-browser" && isRunning) {
return false;
}
// For other browsers: always allow
return true;
};
}, [runningProfiles, canUseProfileForLinks]);
// Helper function to get tooltip content for profiles
const getProfileTooltipContent = (profile: BrowserProfile): string => {
@@ -156,7 +153,7 @@ export function ProfileSelectorDialog({
return "";
};
const handleOpenUrl = async () => {
const handleOpenUrl = useCallback(async () => {
if (!selectedProfile || !url) return;
setIsLaunching(true);
@@ -171,14 +168,14 @@ export function ProfileSelectorDialog({
} finally {
setIsLaunching(false);
}
};
}, [selectedProfile, url, onClose]);
const handleCancel = () => {
const handleCancel = useCallback(() => {
setSelectedProfile(null);
onClose();
};
}, [onClose]);
const handleCopyUrl = async () => {
const handleCopyUrl = useCallback(async () => {
if (!url) return;
try {
@@ -188,7 +185,7 @@ export function ProfileSelectorDialog({
console.error("Failed to copy URL:", error);
toast.error("Failed to copy URL to clipboard");
}
};
}, [url]);
const selectedProfileData = profiles.find((p) => p.name === selectedProfile);
@@ -208,6 +205,12 @@ export function ProfileSelectorDialog({
return getProfileTooltipContent(selectedProfileData);
};
useEffect(() => {
if (isOpen) {
void loadProfiles();
}
}, [isOpen, loadProfiles]);
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-md">
@@ -253,86 +256,84 @@ export function ProfileSelectorDialog({
</div>
</div>
) : (
<>
<Select
value={selectedProfile ?? undefined}
onValueChange={setSelectedProfile}
>
<SelectTrigger>
<SelectValue placeholder="Choose a profile" />
</SelectTrigger>
<SelectContent>
{profiles.map((profile) => {
const isRunning = runningProfiles.has(profile.name);
const canUseForLinks = canUseProfileForLinks(
profile,
profiles,
runningProfiles,
);
const tooltipContent = getProfileTooltipContent(profile);
<Select
value={selectedProfile ?? undefined}
onValueChange={setSelectedProfile}
>
<SelectTrigger>
<SelectValue placeholder="Choose a profile" />
</SelectTrigger>
<SelectContent>
{profiles.map((profile) => {
const isRunning = runningProfiles.has(profile.name);
const canUseForLinks = canUseProfileForLinks(
profile,
profiles,
runningProfiles,
);
const tooltipContent = getProfileTooltipContent(profile);
return (
<Tooltip key={profile.name}>
<TooltipTrigger asChild>
<SelectItem
value={profile.name}
disabled={!canUseForLinks}
return (
<Tooltip key={profile.name}>
<TooltipTrigger asChild>
<SelectItem
value={profile.name}
disabled={!canUseForLinks}
>
<div
className={`flex items-center gap-2 ${
!canUseForLinks ? "opacity-50" : ""
}`}
>
<div
className={`flex items-center gap-2 ${
!canUseForLinks ? "opacity-50" : ""
}`}
>
<div className="flex gap-3 items-center px-2 py-1 rounded-lg cursor-pointer hover:bg-accent">
<div className="flex gap-2 items-center">
{(() => {
const IconComponent = getBrowserIcon(
profile.browser,
);
return IconComponent ? (
<IconComponent className="w-4 h-4" />
) : null;
})()}
</div>
<div className="flex-1 text-right">
<div className="font-medium">
{profile.name}
</div>
<div className="flex gap-3 items-center px-2 py-1 rounded-lg cursor-pointer hover:bg-accent">
<div className="flex gap-2 items-center">
{(() => {
const IconComponent = getBrowserIcon(
profile.browser,
);
return IconComponent ? (
<IconComponent className="w-4 h-4" />
) : null;
})()}
</div>
<div className="flex-1 text-right">
<div className="font-medium">
{profile.name}
</div>
</div>
<Badge variant="secondary" className="text-xs">
{getBrowserDisplayName(profile.browser)}
</Badge>
{profile.proxy?.enabled && (
<Badge variant="outline" className="text-xs">
Proxy
</Badge>
)}
{isRunning && (
<Badge variant="default" className="text-xs">
Running
</Badge>
)}
{!canUseForLinks && (
<Badge
variant="destructive"
className="text-xs"
>
Unavailable
</Badge>
)}
</div>
</SelectItem>
</TooltipTrigger>
{tooltipContent && (
<TooltipContent>{tooltipContent}</TooltipContent>
)}
</Tooltip>
);
})}
</SelectContent>
</Select>
</>
<Badge variant="secondary" className="text-xs">
{getBrowserDisplayName(profile.browser)}
</Badge>
{profile.proxy?.enabled && (
<Badge variant="outline" className="text-xs">
Proxy
</Badge>
)}
{isRunning && (
<Badge variant="default" className="text-xs">
Running
</Badge>
)}
{!canUseForLinks && (
<Badge
variant="destructive"
className="text-xs"
>
Unavailable
</Badge>
)}
</div>
</SelectItem>
</TooltipTrigger>
{tooltipContent && (
<TooltipContent>{tooltipContent}</TooltipContent>
)}
</Tooltip>
);
})}
</SelectContent>
</Select>
)}
</div>
</div>
+1 -1
View File
@@ -1,5 +1,6 @@
"use client";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import {
@@ -23,7 +24,6 @@ import {
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useEffect, useState } from "react";
interface ProxySettings {
enabled: boolean;
+2 -3
View File
@@ -1,5 +1,7 @@
"use client";
import { useState } from "react";
import { LuCheck, LuChevronsUpDown, LuDownload } from "react-icons/lu";
import { LoadingButton } from "@/components/loading-button";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -17,9 +19,6 @@ import {
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
import type { BrowserReleaseTypes } from "@/types";
import { useState } from "react";
import { LuDownload } from "react-icons/lu";
import { LuCheck, LuChevronsUpDown } from "react-icons/lu";
interface ReleaseTypeSelectorProps {
selectedReleaseType: "stable" | "nightly" | null;
+125 -116
View File
@@ -1,5 +1,9 @@
"use client";
import { invoke } from "@tauri-apps/api/core";
import { useTheme } from "next-themes";
import { useCallback, useEffect, useState } from "react";
import { BsCamera, BsMic } from "react-icons/bs";
import { LoadingButton } from "@/components/loading-button";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
@@ -19,13 +23,9 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { usePermissions } from "@/hooks/use-permissions";
import type { PermissionType } from "@/hooks/use-permissions";
import { usePermissions } from "@/hooks/use-permissions";
import { showErrorToast, showSuccessToast } from "@/lib/toast-utils";
import { invoke } from "@tauri-apps/api/core";
import { useTheme } from "next-themes";
import { useCallback, useEffect, useState } from "react";
import { BsCamera, BsMic } from "react-icons/bs";
interface AppSettings {
set_as_default_browser: boolean;
@@ -73,6 +73,35 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
isCameraAccessGranted,
} = usePermissions();
const getPermissionIcon = useCallback((type: PermissionType) => {
switch (type) {
case "microphone":
return <BsMic className="w-4 h-4" />;
case "camera":
return <BsCamera className="w-4 h-4" />;
}
}, []);
const getPermissionDisplayName = useCallback((type: PermissionType) => {
switch (type) {
case "microphone":
return "Microphone";
case "camera":
return "Camera";
}
}, []);
const getStatusBadge = useCallback((isGranted: boolean) => {
if (isGranted) {
return (
<Badge variant="default" className="text-green-800 bg-green-100">
Granted
</Badge>
);
}
return <Badge variant="secondary">Not Granted</Badge>;
}, []);
const getPermissionDescription = useCallback((type: PermissionType) => {
switch (type) {
case "microphone":
@@ -81,60 +110,7 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
return "Access to camera for browser applications";
}
}, []);
useEffect(() => {
if (isOpen) {
loadSettings().catch(console.error);
checkDefaultBrowserStatus().catch(console.error);
// Check if we're on macOS
const userAgent = navigator.userAgent;
const isMac = userAgent.includes("Mac");
setIsMacOS(isMac);
if (isMac) {
loadPermissions().catch(console.error);
}
// Set up interval to check default browser status
const intervalId = setInterval(() => {
checkDefaultBrowserStatus().catch(console.error);
}, 500); // Check every 500ms
// Cleanup interval on component unmount or dialog close
return () => {
clearInterval(intervalId);
};
}
}, [isOpen]);
// Update permissions when the permission states change
useEffect(() => {
if (isMacOS) {
const permissionList: PermissionInfo[] = [
{
permission_type: "microphone",
isGranted: isMicrophoneAccessGranted,
description: getPermissionDescription("microphone"),
},
{
permission_type: "camera",
isGranted: isCameraAccessGranted,
description: getPermissionDescription("camera"),
},
];
setPermissions(permissionList);
} else {
setPermissions([]);
}
}, [
isMacOS,
isMicrophoneAccessGranted,
isCameraAccessGranted,
getPermissionDescription,
]);
const loadSettings = async () => {
const loadSettings = useCallback(async () => {
setIsLoading(true);
try {
const appSettings = await invoke<AppSettings>("get_app_settings");
@@ -145,9 +121,9 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
} finally {
setIsLoading(false);
}
};
}, []);
const loadPermissions = async () => {
const loadPermissions = useCallback(async () => {
setIsLoadingPermissions(true);
try {
if (!isMacOS) {
@@ -175,18 +151,23 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
} finally {
setIsLoadingPermissions(false);
}
};
}, [
getPermissionDescription,
isCameraAccessGranted,
isMacOS,
isMicrophoneAccessGranted,
]);
const checkDefaultBrowserStatus = async () => {
const checkDefaultBrowserStatus = useCallback(async () => {
try {
const isDefault = await invoke<boolean>("is_default_browser");
setIsDefaultBrowser(isDefault);
} catch (error) {
console.error("Failed to check default browser status:", error);
}
};
}, []);
const handleSetDefaultBrowser = async () => {
const handleSetDefaultBrowser = useCallback(async () => {
setIsSettingDefault(true);
try {
await invoke("set_as_default_browser");
@@ -196,9 +177,9 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
} finally {
setIsSettingDefault(false);
}
};
}, [checkDefaultBrowserStatus]);
const handleClearCache = async () => {
const handleClearCache = useCallback(async () => {
setIsClearingCache(true);
try {
await invoke("clear_all_version_cache_and_refetch");
@@ -217,52 +198,25 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
} finally {
setIsClearingCache(false);
}
};
}, []);
const handleRequestPermission = async (permissionType: PermissionType) => {
setRequestingPermission(permissionType);
try {
await requestPermission(permissionType);
showSuccessToast(
`${getPermissionDisplayName(permissionType)} access requested`,
);
} catch (error) {
console.error("Failed to request permission:", error);
} finally {
setRequestingPermission(null);
}
};
const getPermissionIcon = (type: PermissionType) => {
switch (type) {
case "microphone":
return <BsMic className="w-4 h-4" />;
case "camera":
return <BsCamera className="w-4 h-4" />;
}
};
const getPermissionDisplayName = (type: PermissionType) => {
switch (type) {
case "microphone":
return "Microphone";
case "camera":
return "Camera";
}
};
const getStatusBadge = (isGranted: boolean) => {
if (isGranted) {
return (
<Badge variant="default" className="text-green-800 bg-green-100">
Granted
</Badge>
);
}
return <Badge variant="secondary">Not Granted</Badge>;
};
const handleSave = async () => {
const handleRequestPermission = useCallback(
async (permissionType: PermissionType) => {
setRequestingPermission(permissionType);
try {
await requestPermission(permissionType);
showSuccessToast(
`${getPermissionDisplayName(permissionType)} access requested`,
);
} catch (error) {
console.error("Failed to request permission:", error);
} finally {
setRequestingPermission(null);
}
},
[getPermissionDisplayName, requestPermission],
);
const handleSave = useCallback(async () => {
setIsSaving(true);
try {
await invoke("save_app_settings", { settings });
@@ -274,11 +228,66 @@ export function SettingsDialog({ isOpen, onClose }: SettingsDialogProps) {
} finally {
setIsSaving(false);
}
};
}, [onClose, setTheme, settings]);
const updateSetting = (key: keyof AppSettings, value: boolean | string) => {
setSettings((prev) => ({ ...prev, [key]: value }));
};
const updateSetting = useCallback(
(key: keyof AppSettings, value: boolean | string) => {
setSettings((prev) => ({ ...prev, [key]: value }));
},
[],
);
useEffect(() => {
if (isOpen) {
loadSettings().catch(console.error);
checkDefaultBrowserStatus().catch(console.error);
// Check if we're on macOS
const userAgent = navigator.userAgent;
const isMac = userAgent.includes("Mac");
setIsMacOS(isMac);
if (isMac) {
loadPermissions().catch(console.error);
}
// Set up interval to check default browser status
const intervalId = setInterval(() => {
checkDefaultBrowserStatus().catch(console.error);
}, 500); // Check every 500ms
// Cleanup interval on component unmount or dialog close
return () => {
clearInterval(intervalId);
};
}
}, [isOpen, loadPermissions, checkDefaultBrowserStatus, loadSettings]);
// Update permissions when the permission states change
useEffect(() => {
if (isMacOS) {
const permissionList: PermissionInfo[] = [
{
permission_type: "microphone",
isGranted: isMicrophoneAccessGranted,
description: getPermissionDescription("microphone"),
},
{
permission_type: "camera",
isGranted: isCameraAccessGranted,
description: getPermissionDescription("camera"),
},
];
setPermissions(permissionList);
} else {
setPermissions([]);
}
}, [
isMacOS,
isMicrophoneAccessGranted,
isCameraAccessGranted,
getPermissionDescription,
]);
// Check if settings have changed (excluding default browser setting)
const hasChanges =
+1 -1
View File
@@ -1,4 +1,4 @@
import { type VariantProps, cva } from "class-variance-authority";
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";
+1 -1
View File
@@ -1,5 +1,5 @@
import { Slot } from "@radix-ui/react-slot";
import { type VariantProps, cva } from "class-variance-authority";
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";
+1 -1
View File
@@ -1,5 +1,5 @@
import { Slot } from "@radix-ui/react-slot";
import { type VariantProps, cva } from "class-variance-authority";
import { cva, type VariantProps } from "class-variance-authority";
import type * as React from "react";
import { cn } from "@/lib/utils";
+3 -2
View File
@@ -39,8 +39,9 @@ export function WindowDragArea() {
}
return (
<div
className="fixed top-0 right-0 left-0 h-10 z-9999"
<button
type="button"
className="fixed top-0 right-0 left-0 h-10 bg-transparent border-0 pointer-events-none z-9999"
style={{
// Ensure it's above all other content
zIndex: 9999,