mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-05-01 08:07:55 +02:00
chore: linting
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
* ```
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
LuCheckCheck,
|
||||
LuDownload,
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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
@@ -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,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,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,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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user