mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-10-20 06:28:19 +02:00
feat: profile start pages
This commit is contained in:
1 parent
3f07a41fac
commit
4044c845c7
25 files changed
+755
-54
No files matched your search
@@ -61,6 +61,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -74,6 +75,7 @@ import { useVpnEvents } from "@/hooks/use-vpn-events";
|
||||
import { getOSDisplayName, getOSIcon } from "@/lib/browser-utils";
|
||||
import { DNS_BLOCKLIST_LEVELS } from "@/lib/dns-blocklist-levels";
|
||||
import { MOTION_EASE_OUT } from "@/lib/motion";
|
||||
import { startPagesFromText } from "@/lib/start-pages";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { BrowserReleaseTypes, WayfernConfig, WayfernOS } from "@/types";
|
||||
|
||||
@@ -113,6 +115,7 @@ interface CreateProfileDialogProps {
|
||||
ephemeral?: boolean;
|
||||
dnsBlocklist?: string;
|
||||
dnsServer?: string;
|
||||
startUrls?: string[];
|
||||
launchHook?: string;
|
||||
password?: string;
|
||||
}) => Promise<void>;
|
||||
@@ -146,6 +149,7 @@ export function CreateProfileDialog({
|
||||
const [dnsBlocklist, setDnsBlocklist] = useState("");
|
||||
const [dnsServer, setDnsServer] = useState("");
|
||||
const [dnsServerValid, setDnsServerValid] = useState(true);
|
||||
const [startPages, setStartPages] = useState("");
|
||||
const [launchHook, setLaunchHook] = useState("");
|
||||
const [wayfernConfig, setWayfernConfig] =
|
||||
useState<WayfernConfig>(defaultWayfernConfig);
|
||||
@@ -306,6 +310,7 @@ export function CreateProfileDialog({
|
||||
setShowProxyForm(false);
|
||||
setDnsBlocklist("");
|
||||
setDnsServer("");
|
||||
setStartPages("");
|
||||
setLaunchHook("");
|
||||
setSelectedExtensionGroupId(undefined);
|
||||
setReleaseTypes({});
|
||||
@@ -351,6 +356,7 @@ export function CreateProfileDialog({
|
||||
ephemeral,
|
||||
dnsBlocklist: dnsBlocklist || undefined,
|
||||
dnsServer: dnsServer.trim() || undefined,
|
||||
startUrls: startPagesFromText(startPages),
|
||||
launchHook: launchHook.trim() || undefined,
|
||||
password: enablePassword && !ephemeral ? password : undefined,
|
||||
});
|
||||
@@ -1006,6 +1012,28 @@ export function CreateProfileDialog({
|
||||
disabled={isCreating}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label
|
||||
htmlFor="profile-start-pages"
|
||||
className={FIELD_LABEL_CLASS}
|
||||
>
|
||||
{t("profileInfo.sections.startPages")}
|
||||
</Label>
|
||||
<Textarea
|
||||
id="profile-start-pages"
|
||||
variant="soft"
|
||||
value={startPages}
|
||||
onChange={(event) => setStartPages(event.target.value)}
|
||||
placeholder={t("startPages.placeholder")}
|
||||
rows={3}
|
||||
spellCheck={false}
|
||||
disabled={isCreating}
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("startPages.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-4 pt-1">
|
||||
{creatableVersion && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
LuFingerprint,
|
||||
LuGlobe,
|
||||
LuGroup,
|
||||
LuHouse,
|
||||
LuKey,
|
||||
LuLink,
|
||||
LuLock,
|
||||
@@ -84,6 +85,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { LaunchArgumentsEditor } from "@/components/wayfern-config-form";
|
||||
import { WindowDragArea } from "@/components/window-drag-area";
|
||||
import { useInputModality } from "@/hooks/use-input-modality";
|
||||
@@ -94,6 +96,7 @@ import { formatRelativeTime } from "@/lib/flag-utils";
|
||||
import { MOTION_EASE_OUT } from "@/lib/motion";
|
||||
import { rotateProxyIps } from "@/lib/proxy-rotation";
|
||||
import type { RemoteHandoffState } from "@/lib/remote-sessions";
|
||||
import { startPagesFromText } from "@/lib/start-pages";
|
||||
import { showErrorToast, showSuccessToast } from "@/lib/toast-utils";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type {
|
||||
@@ -961,6 +964,7 @@ type ProfileSection =
|
||||
| "overview"
|
||||
| "fingerprint"
|
||||
| "network"
|
||||
| "startPages"
|
||||
| "cookies"
|
||||
| "extensions"
|
||||
| "sync"
|
||||
@@ -1081,6 +1085,14 @@ function ProfileInfoLayout({
|
||||
label: t("profileInfo.sections.network"),
|
||||
badge: profile.proxy_id || profile.vpn_id ? networkLabel : undefined,
|
||||
},
|
||||
{
|
||||
id: "startPages",
|
||||
icon: <LuHouse className="size-3.5" />,
|
||||
label: t("profileInfo.sections.startPages"),
|
||||
badge: profile.start_urls?.length
|
||||
? String(profile.start_urls.length)
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
id: "cookies",
|
||||
icon: <LuCookie className="size-3.5" />,
|
||||
@@ -1393,6 +1405,10 @@ function ProfileInfoLayout({
|
||||
/>
|
||||
)}
|
||||
|
||||
{section === "startPages" && (
|
||||
<StartPagesEditor profile={profile} isDisabled={isDisabled} t={t} />
|
||||
)}
|
||||
|
||||
{section === "cookies" &&
|
||||
(isEphemeral ? (
|
||||
<EphemeralSectionNotice
|
||||
@@ -1647,6 +1663,89 @@ function LaunchHookEditor({
|
||||
);
|
||||
}
|
||||
|
||||
function StartPagesEditor({
|
||||
profile,
|
||||
isDisabled,
|
||||
t,
|
||||
}: {
|
||||
profile: BrowserProfile;
|
||||
isDisabled: boolean;
|
||||
t: (key: string, options?: Record<string, unknown>) => string;
|
||||
}) {
|
||||
const { t: tFn } = useTranslation();
|
||||
const initial = (profile.start_urls ?? []).join("\n");
|
||||
const [value, setValue] = React.useState(initial);
|
||||
const [isSaving, setIsSaving] = React.useState(false);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
const dirty = value.trim() !== initial;
|
||||
|
||||
React.useEffect(() => {
|
||||
setValue(initial);
|
||||
}, [initial]);
|
||||
|
||||
const onSave = async () => {
|
||||
setIsSaving(true);
|
||||
setError(null);
|
||||
try {
|
||||
const saved = await invoke<BrowserProfile>("update_profile_start_urls", {
|
||||
profileId: profile.id,
|
||||
startUrls: startPagesFromText(value),
|
||||
});
|
||||
setValue((saved.start_urls ?? []).join("\n"));
|
||||
} catch (e) {
|
||||
setError(translateBackendError(tFn, e));
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2 text-sm font-semibold">
|
||||
<LuHouse className="size-4" />
|
||||
{t("profileInfo.sections.startPages")}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("startPages.description")}
|
||||
</p>
|
||||
<Textarea
|
||||
data-slot="profile-start-pages-input"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder={t("startPages.placeholder")}
|
||||
rows={5}
|
||||
spellCheck={false}
|
||||
disabled={isDisabled || isSaving}
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
{error && <p className="text-xs text-destructive-text">{error}</p>}
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
className="h-7 text-xs"
|
||||
disabled={!dirty || isSaving || isDisabled}
|
||||
onClick={() => void onSave()}
|
||||
>
|
||||
{isSaving ? t("common.buttons.saving") : t("common.buttons.save")}
|
||||
</Button>
|
||||
{dirty && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-7 text-xs"
|
||||
onClick={() => {
|
||||
setValue(initial);
|
||||
setError(null);
|
||||
}}
|
||||
>
|
||||
{t("common.buttons.cancel")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SyncSectionInline({
|
||||
profile,
|
||||
syncMode,
|
||||
|
||||
Reference in new issue
Block a user