feat: per-profile window color with id-derived default

This commit is contained in:
zhom
2026-07-08 01:19:52 +04:00
parent 78803ab289
commit 63a1f4c92a
18 changed files with 224 additions and 9 deletions
+80
View File
@@ -4,6 +4,7 @@ import { invoke } from "@tauri-apps/api/core";
import { listen } from "@tauri-apps/api/event";
import { save } from "@tauri-apps/plugin-dialog";
import { writeTextFile } from "@tauri-apps/plugin-fs";
import Color from "color";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { FaApple, FaLinux, FaWindows } from "react-icons/fa";
@@ -34,6 +35,14 @@ import {
} from "react-icons/lu";
import { SharedCamoufoxConfigForm } from "@/components/shared-camoufox-config-form";
import { Button } from "@/components/ui/button";
import {
ColorPicker,
ColorPickerEyeDropper,
ColorPickerFormat,
ColorPickerHue,
ColorPickerOutput,
ColorPickerSelection,
} from "@/components/ui/color-picker";
import {
Dialog,
DialogContent,
@@ -49,6 +58,11 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Select,
@@ -183,6 +197,71 @@ function LocalDataTransferCard({
);
}
// Shown only for legacy profiles that predate the feature and have no stored
// color yet (new profiles get a backend-derived one at creation/launch).
const DEFAULT_SWATCH_COLOR = "#94a3b8";
function WindowColorSwatch({ profile }: { profile: BrowserProfile }) {
const { t } = useTranslation();
const [color, setColor] = React.useState(profile.window_color);
React.useEffect(() => {
setColor(profile.window_color);
}, [profile.window_color]);
const persist = React.useCallback(
async (hex: string) => {
try {
await invoke("update_profile_window_color", {
profileId: profile.id,
windowColor: hex,
});
} catch {
setColor(profile.window_color);
}
},
[profile.id, profile.window_color],
);
return (
<Popover
onOpenChange={(open) => {
if (!open && color && color !== profile.window_color)
void persist(color);
}}
>
<PopoverTrigger asChild>
<button
type="button"
aria-label={t("profileInfo.fields.windowColor")}
title={t("profileInfo.fields.windowColor")}
className="size-9 shrink-0 cursor-pointer rounded-lg border shadow-sm ring-offset-background transition-transform hover:scale-105 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
style={{ backgroundColor: color ?? DEFAULT_SWATCH_COLOR }}
/>
</PopoverTrigger>
<PopoverContent align="end" sideOffset={6} className="w-[264px] p-3">
<ColorPicker
className="rounded-md border bg-background p-3 shadow-sm"
value={color ?? DEFAULT_SWATCH_COLOR}
onColorChange={([r, g, b]) => {
setColor(Color({ r, g, b }).hex().toLowerCase());
}}
>
<ColorPickerSelection className="h-32 rounded" />
<div className="mt-3 flex items-center gap-3">
<ColorPickerEyeDropper />
<ColorPickerHue className="flex-1" />
</div>
<div className="mt-3 flex items-center gap-2">
<ColorPickerOutput />
<ColorPickerFormat />
</div>
</ColorPicker>
</PopoverContent>
</Popover>
);
}
export function ProfileInfoDialog({
isOpen,
onClose,
@@ -820,6 +899,7 @@ function ProfileInfoLayout({
</span>
</div>
</div>
<WindowColorSwatch profile={profile} />
</div>
{/* ID */}