mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-06-06 23:13:58 +02:00
chore: linting
This commit is contained in:
@@ -197,6 +197,7 @@ These are frequently overlooked issues that make UI look unprofessional:
|
||||
Before delivering UI code, verify these items:
|
||||
|
||||
### Visual Quality
|
||||
|
||||
- [ ] No emojis used as icons (use SVG instead)
|
||||
- [ ] All icons from consistent icon set (Heroicons/Lucide)
|
||||
- [ ] Brand logos are correct (verified from Simple Icons)
|
||||
@@ -204,24 +205,28 @@ Before delivering UI code, verify these items:
|
||||
- [ ] Use theme colors directly (bg-primary) not var() wrapper
|
||||
|
||||
### Interaction
|
||||
|
||||
- [ ] All clickable elements have `cursor-pointer`
|
||||
- [ ] Hover states provide clear visual feedback
|
||||
- [ ] Transitions are smooth (150-300ms)
|
||||
- [ ] Focus states visible for keyboard navigation
|
||||
|
||||
### Light/Dark Mode
|
||||
|
||||
- [ ] Light mode text has sufficient contrast (4.5:1 minimum)
|
||||
- [ ] Glass/transparent elements visible in light mode
|
||||
- [ ] Borders visible in both modes
|
||||
- [ ] Test both modes before delivery
|
||||
|
||||
### Layout
|
||||
|
||||
- [ ] Floating elements have proper spacing from edges
|
||||
- [ ] No content hidden behind fixed navbars
|
||||
- [ ] Responsive at 320px, 768px, 1024px, 1440px
|
||||
- [ ] No horizontal scroll on mobile
|
||||
|
||||
### Accessibility
|
||||
|
||||
- [ ] All images have alt text
|
||||
- [ ] Form inputs have labels
|
||||
- [ ] Color is not the only indicator
|
||||
|
||||
@@ -27,6 +27,7 @@ Or enter the dev shell: `nix develop`
|
||||
### Manual Setup
|
||||
|
||||
Requirements:
|
||||
|
||||
- Node.js (see `.node-version`)
|
||||
- pnpm
|
||||
- Rust + Cargo (latest stable)
|
||||
@@ -47,6 +48,7 @@ pnpm format && pnpm lint && pnpm test
|
||||
```
|
||||
|
||||
This runs:
|
||||
|
||||
- **Biome** — JS/TS linting and formatting
|
||||
- **Clippy + rustfmt** — Rust linting and formatting
|
||||
- **typos** — Spellcheck (allowlist in `_typos.toml`)
|
||||
|
||||
+9
-11
@@ -2,8 +2,6 @@
|
||||
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
|
||||
</p>
|
||||
|
||||
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
||||
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
||||
|
||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
||||
<p align="center">
|
||||
@@ -28,33 +26,33 @@
|
||||
## Project setup
|
||||
|
||||
```bash
|
||||
$ pnpm install
|
||||
pnpm install
|
||||
```
|
||||
|
||||
## Compile and run the project
|
||||
|
||||
```bash
|
||||
# development
|
||||
$ pnpm run start
|
||||
pnpm run start
|
||||
|
||||
# watch mode
|
||||
$ pnpm run start:dev
|
||||
pnpm run start:dev
|
||||
|
||||
# production mode
|
||||
$ pnpm run start:prod
|
||||
pnpm run start:prod
|
||||
```
|
||||
|
||||
## Run tests
|
||||
|
||||
```bash
|
||||
# unit tests
|
||||
$ pnpm run test
|
||||
pnpm run test
|
||||
|
||||
# e2e tests
|
||||
$ pnpm run test:e2e
|
||||
pnpm run test:e2e
|
||||
|
||||
# test coverage
|
||||
$ pnpm run test:cov
|
||||
pnpm run test:cov
|
||||
```
|
||||
|
||||
## Deployment
|
||||
@@ -64,8 +62,8 @@ When you're ready to deploy your NestJS application to production, there are som
|
||||
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
|
||||
|
||||
```bash
|
||||
$ pnpm install -g @nestjs/mau
|
||||
$ mau deploy
|
||||
pnpm install -g @nestjs/mau
|
||||
mau deploy
|
||||
```
|
||||
|
||||
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
|
||||
|
||||
@@ -38,7 +38,7 @@ export class AuthGuard implements CanActivate {
|
||||
// Try SYNC_TOKEN first (self-hosted mode)
|
||||
const expectedToken = this.configService.get<string>("SYNC_TOKEN");
|
||||
if (expectedToken && token === expectedToken) {
|
||||
(request as any).user = {
|
||||
(request as unknown as Record<string, unknown>).user = {
|
||||
mode: "self-hosted",
|
||||
prefix: "",
|
||||
teamPrefix: null,
|
||||
@@ -55,7 +55,7 @@ export class AuthGuard implements CanActivate {
|
||||
algorithms: ["RS256"],
|
||||
}) as jwt.JwtPayload;
|
||||
|
||||
(request as any).user = {
|
||||
(request as unknown as Record<string, unknown>).user = {
|
||||
mode: "cloud",
|
||||
prefix: decoded.prefix || `users/${decoded.sub}/`,
|
||||
teamPrefix: decoded.teamPrefix || null,
|
||||
|
||||
@@ -39,7 +39,7 @@ export class SyncController {
|
||||
constructor(private readonly syncService: SyncService) {}
|
||||
|
||||
private getUserContext(req: Request): UserContext {
|
||||
return (req as any).user as UserContext;
|
||||
return (req as unknown as Record<string, unknown>).user as UserContext;
|
||||
}
|
||||
|
||||
@Post("stat")
|
||||
|
||||
+5
-5
@@ -324,7 +324,7 @@ export default function Home() {
|
||||
const currentUrl = await getCurrent();
|
||||
if (currentUrl && currentUrl.length > 0) {
|
||||
console.log("Startup URL detected:", currentUrl[0]);
|
||||
void handleUrlOpen(currentUrl[0]);
|
||||
handleUrlOpen(currentUrl[0]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to check current URL:", error);
|
||||
@@ -413,13 +413,13 @@ export default function Home() {
|
||||
// Listen for URL open events from the deep link handler (when app is already running)
|
||||
await listen<string>("url-open-request", (event) => {
|
||||
console.log("Received URL open request:", event.payload);
|
||||
void handleUrlOpen(event.payload);
|
||||
handleUrlOpen(event.payload);
|
||||
});
|
||||
|
||||
// Listen for show profile selector events
|
||||
await listen<string>("show-profile-selector", (event) => {
|
||||
console.log("Received show profile selector request:", event.payload);
|
||||
void handleUrlOpen(event.payload);
|
||||
handleUrlOpen(event.payload);
|
||||
});
|
||||
|
||||
// Listen for show create profile dialog events
|
||||
@@ -437,7 +437,7 @@ export default function Home() {
|
||||
// Listen for custom logo click events
|
||||
const handleLogoUrlEvent = (event: CustomEvent) => {
|
||||
console.log("Received logo URL event:", event.detail);
|
||||
void handleUrlOpen(event.detail);
|
||||
handleUrlOpen(event.detail);
|
||||
};
|
||||
|
||||
window.addEventListener(
|
||||
@@ -995,7 +995,7 @@ export default function Home() {
|
||||
// Check permissions when they are initialized
|
||||
useEffect(() => {
|
||||
if (isInitialized) {
|
||||
void checkAllPermissions();
|
||||
checkAllPermissions();
|
||||
}
|
||||
}, [isInitialized, checkAllPermissions]);
|
||||
|
||||
|
||||
@@ -50,12 +50,13 @@ interface CookieCopyDialogProps {
|
||||
onCopyComplete?: () => void;
|
||||
}
|
||||
|
||||
interface SelectionState {
|
||||
[domain: string]: {
|
||||
type SelectionState = Record<
|
||||
string,
|
||||
{
|
||||
allSelected: boolean;
|
||||
cookies: Set<string>;
|
||||
};
|
||||
}
|
||||
}
|
||||
>;
|
||||
|
||||
export function CookieCopyDialog({
|
||||
isOpen,
|
||||
@@ -148,7 +149,7 @@ export function CookieCopyDialog({
|
||||
(domain: string, cookies: UnifiedCookie[]) => {
|
||||
setSelection((prev) => {
|
||||
const current = prev[domain];
|
||||
const allSelected = current.allSelected || false;
|
||||
const allSelected = current.allSelected;
|
||||
|
||||
if (allSelected) {
|
||||
const newSelection = { ...prev };
|
||||
@@ -171,7 +172,7 @@ export function CookieCopyDialog({
|
||||
const toggleCookie = useCallback(
|
||||
(domain: string, cookieName: string, totalCookies: number) => {
|
||||
setSelection((prev) => {
|
||||
const current = prev[domain] || {
|
||||
const current = prev[domain] ?? {
|
||||
allSelected: false,
|
||||
cookies: new Set<string>(),
|
||||
};
|
||||
@@ -503,8 +504,8 @@ function DomainRow({
|
||||
onToggleExpand,
|
||||
}: DomainRowProps) {
|
||||
const domainSelection = selection[domain.domain];
|
||||
const isAllSelected = domainSelection.allSelected || false;
|
||||
const selectedCount = domainSelection.cookies.size || 0;
|
||||
const isAllSelected = domainSelection.allSelected;
|
||||
const selectedCount = domainSelection.cookies.size;
|
||||
const isPartial =
|
||||
selectedCount > 0 && selectedCount < domain.cookie_count && !isAllSelected;
|
||||
|
||||
@@ -539,8 +540,7 @@ function DomainRow({
|
||||
{isExpanded && (
|
||||
<div className="ml-8 pl-2 border-l space-y-1">
|
||||
{domain.cookies.map((cookie) => {
|
||||
const isSelected =
|
||||
domainSelection.cookies.has(cookie.name) || false;
|
||||
const isSelected = domainSelection.cookies.has(cookie.name);
|
||||
return (
|
||||
<div
|
||||
key={`${domain.domain}-${cookie.name}`}
|
||||
|
||||
@@ -45,12 +45,13 @@ interface CookieManagementDialogProps {
|
||||
initialTab?: "import" | "export";
|
||||
}
|
||||
|
||||
interface SelectionState {
|
||||
[domain: string]: {
|
||||
type SelectionState = Record<
|
||||
string,
|
||||
{
|
||||
allSelected: boolean;
|
||||
cookies: Set<string>;
|
||||
};
|
||||
}
|
||||
}
|
||||
>;
|
||||
|
||||
const countCookies = (content: string): number => {
|
||||
const trimmed = content.trim();
|
||||
@@ -329,7 +330,7 @@ export function CookieManagementDialog({
|
||||
const toggleCookie = useCallback(
|
||||
(domain: string, cookieName: string, totalCookies: number) => {
|
||||
setExportSelection((prev) => {
|
||||
const current = prev[domain] || {
|
||||
const current = prev[domain] ?? {
|
||||
allSelected: false,
|
||||
cookies: new Set<string>(),
|
||||
};
|
||||
@@ -591,8 +592,8 @@ function ExportDomainRow({
|
||||
onToggleExpand,
|
||||
}: ExportDomainRowProps) {
|
||||
const domainSelection = selection[domain.domain];
|
||||
const isAllSelected = domainSelection.allSelected || false;
|
||||
const selectedCount = domainSelection.cookies.size || 0;
|
||||
const isAllSelected = domainSelection.allSelected;
|
||||
const selectedCount = domainSelection.cookies.size;
|
||||
const isPartial =
|
||||
selectedCount > 0 && selectedCount < domain.cookie_count && !isAllSelected;
|
||||
|
||||
@@ -627,8 +628,7 @@ function ExportDomainRow({
|
||||
{isExpanded && (
|
||||
<div className="ml-7 pl-2 border-l space-y-0.5">
|
||||
{domain.cookies.map((cookie) => {
|
||||
const isSelected =
|
||||
domainSelection.cookies.has(cookie.name) || false;
|
||||
const isSelected = domainSelection.cookies.has(cookie.name);
|
||||
return (
|
||||
<div
|
||||
key={`${domain.domain}-${cookie.name}`}
|
||||
|
||||
@@ -672,7 +672,7 @@ export function CreateProfileDialog({
|
||||
!isCreateDisabled &&
|
||||
!isCreating
|
||||
) {
|
||||
handleCreate();
|
||||
void handleCreate();
|
||||
}
|
||||
}}
|
||||
placeholder="Enter profile name"
|
||||
@@ -754,7 +754,9 @@ export function CreateProfileDialog({
|
||||
})()}
|
||||
</p>
|
||||
<LoadingButton
|
||||
onClick={() => handleDownload("wayfern")}
|
||||
onClick={() => {
|
||||
void handleDownload("wayfern");
|
||||
}}
|
||||
isLoading={isBrowserCurrentlyDownloading(
|
||||
"wayfern",
|
||||
)}
|
||||
@@ -856,7 +858,9 @@ export function CreateProfileDialog({
|
||||
})()}
|
||||
</p>
|
||||
<LoadingButton
|
||||
onClick={() => handleDownload("camoufox")}
|
||||
onClick={() => {
|
||||
void handleDownload("camoufox");
|
||||
}}
|
||||
isLoading={isBrowserCurrentlyDownloading(
|
||||
"camoufox",
|
||||
)}
|
||||
@@ -963,9 +967,9 @@ export function CreateProfileDialog({
|
||||
})()}
|
||||
</p>
|
||||
<LoadingButton
|
||||
onClick={() =>
|
||||
handleDownload(selectedBrowser)
|
||||
}
|
||||
onClick={() => {
|
||||
void handleDownload(selectedBrowser);
|
||||
}}
|
||||
isLoading={isBrowserCurrentlyDownloading(
|
||||
selectedBrowser,
|
||||
)}
|
||||
@@ -1163,7 +1167,7 @@ export function CreateProfileDialog({
|
||||
<div className="space-y-2">
|
||||
<Label>{t("extensions.extensionGroup")}</Label>
|
||||
<Select
|
||||
value={selectedExtensionGroupId || "none"}
|
||||
value={selectedExtensionGroupId ?? "none"}
|
||||
onValueChange={(val) => {
|
||||
setSelectedExtensionGroupId(
|
||||
val === "none" ? undefined : val,
|
||||
@@ -1209,7 +1213,7 @@ export function CreateProfileDialog({
|
||||
!isCreateDisabled &&
|
||||
!isCreating
|
||||
) {
|
||||
handleCreate();
|
||||
void handleCreate();
|
||||
}
|
||||
}}
|
||||
placeholder="Enter profile name"
|
||||
@@ -1263,9 +1267,9 @@ export function CreateProfileDialog({
|
||||
})()}
|
||||
</p>
|
||||
<LoadingButton
|
||||
onClick={() =>
|
||||
handleDownload(selectedBrowser)
|
||||
}
|
||||
onClick={() => {
|
||||
void handleDownload(selectedBrowser);
|
||||
}}
|
||||
isLoading={isBrowserCurrentlyDownloading(
|
||||
selectedBrowser,
|
||||
)}
|
||||
|
||||
@@ -363,15 +363,17 @@ export function UnifiedToast(props: ToastProps) {
|
||||
</>
|
||||
)}
|
||||
{action &&
|
||||
"onClick" in (action as any) &&
|
||||
"label" in (action as any) && (
|
||||
"onClick" in (action as { onClick?: () => void; label?: string }) &&
|
||||
"label" in (action as { onClick?: () => void; label?: string }) && (
|
||||
<div className="mt-2 w-full">
|
||||
<RippleButton
|
||||
size="sm"
|
||||
className="ml-auto"
|
||||
onClick={(action as any).onClick}
|
||||
onClick={
|
||||
(action as { onClick: () => void; label: string }).onClick
|
||||
}
|
||||
>
|
||||
{(action as any).label}
|
||||
{(action as { onClick: () => void; label: string }).label}
|
||||
</RippleButton>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -46,7 +46,7 @@ function DataTableActionBar<TData>({
|
||||
}, [table]);
|
||||
|
||||
const portalContainer =
|
||||
portalContainerProp ?? (mounted ? globalThis.document?.body : null);
|
||||
portalContainerProp ?? (mounted ? globalThis.document.body : null);
|
||||
|
||||
if (!portalContainer) return null;
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ export function GroupAssignmentDialog({
|
||||
</div>
|
||||
) : (
|
||||
<Select
|
||||
value={selectedGroupId || "default"}
|
||||
value={selectedGroupId ?? "default"}
|
||||
onValueChange={(value) => {
|
||||
setSelectedGroupId(value === "default" ? null : value);
|
||||
}}
|
||||
|
||||
@@ -19,9 +19,7 @@ export interface Option {
|
||||
/** Group the options by providing key. */
|
||||
[key: string]: string | boolean | undefined;
|
||||
}
|
||||
interface GroupOption {
|
||||
[key: string]: Option[];
|
||||
}
|
||||
type GroupOption = Record<string, Option[]>;
|
||||
|
||||
interface MultipleSelectorProps {
|
||||
value?: Option[];
|
||||
@@ -259,7 +257,7 @@ const MultipleSelector = React.forwardRef<
|
||||
if (!arrayOptions || onSearch) {
|
||||
return;
|
||||
}
|
||||
const newOption = transToGroupOption(arrayOptions || [], groupBy);
|
||||
const newOption = transToGroupOption(arrayOptions, groupBy);
|
||||
if (JSON.stringify(newOption) !== JSON.stringify(options)) {
|
||||
setOptions(newOption);
|
||||
}
|
||||
|
||||
@@ -218,12 +218,12 @@ interface TableMeta {
|
||||
onLaunchWithSync: (profile: BrowserProfile) => void;
|
||||
}
|
||||
|
||||
type SyncStatusDot = {
|
||||
interface SyncStatusDot {
|
||||
color: string;
|
||||
tooltip: string;
|
||||
animate: boolean;
|
||||
encrypted: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
function getProfileSyncStatusDot(
|
||||
profile: BrowserProfile,
|
||||
@@ -1215,7 +1215,7 @@ export function ProfilesDataTable({
|
||||
React.useEffect(() => {
|
||||
if (!browserState.isClient) return;
|
||||
let unlisten: (() => void) | undefined;
|
||||
(async () => {
|
||||
void (async () => {
|
||||
try {
|
||||
unlisten = await listen<{ id: string; is_running: boolean }>(
|
||||
"profile-running-changed",
|
||||
@@ -1540,7 +1540,11 @@ export function ProfilesDataTable({
|
||||
|
||||
// Overflow actions
|
||||
onAssignProfilesToGroup,
|
||||
onCloneProfile,
|
||||
onCloneProfile: onCloneProfile
|
||||
? (profile: BrowserProfile) => {
|
||||
void onCloneProfile(profile);
|
||||
}
|
||||
: undefined,
|
||||
onConfigureCamoufox,
|
||||
onCopyCookiesToProfile,
|
||||
onOpenCookieManagement,
|
||||
@@ -1572,7 +1576,11 @@ export function ProfilesDataTable({
|
||||
|
||||
// Synchronizer
|
||||
getProfileSyncInfo: getProfileSyncInfo ?? (() => undefined),
|
||||
onLaunchWithSync: onLaunchWithSync ?? (() => {}),
|
||||
onLaunchWithSync:
|
||||
onLaunchWithSync ??
|
||||
(() => {
|
||||
/* empty */
|
||||
}),
|
||||
}),
|
||||
[
|
||||
t,
|
||||
|
||||
@@ -147,7 +147,7 @@ export function ProfileInfoDialog({
|
||||
setExtensionGroupName(null);
|
||||
return;
|
||||
}
|
||||
(async () => {
|
||||
void (async () => {
|
||||
try {
|
||||
const group = await invoke<{ name: string } | null>(
|
||||
"get_extension_group_for_profile",
|
||||
|
||||
@@ -242,9 +242,9 @@ export function SettingsDialog({
|
||||
|
||||
const clearCustomTheme = useCallback(() => {
|
||||
const root = document.documentElement;
|
||||
THEME_VARIABLES.forEach(({ key }) =>
|
||||
root.style.removeProperty(key as string),
|
||||
);
|
||||
THEME_VARIABLES.forEach(({ key }) => {
|
||||
root.style.removeProperty(key as string);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const loadPermissions = useCallback(() => {
|
||||
@@ -378,16 +378,13 @@ export function SettingsDialog({
|
||||
|
||||
// Apply or clear custom variables only on Save
|
||||
if (settings.theme === "custom") {
|
||||
if (
|
||||
customThemeState.colors &&
|
||||
Object.keys(customThemeState.colors).length > 0
|
||||
) {
|
||||
if (Object.keys(customThemeState.colors).length > 0) {
|
||||
try {
|
||||
const root = document.documentElement;
|
||||
// Clear any previous custom vars first
|
||||
THEME_VARIABLES.forEach(({ key }) =>
|
||||
root.style.removeProperty(key as string),
|
||||
);
|
||||
THEME_VARIABLES.forEach(({ key }) => {
|
||||
root.style.removeProperty(key as string);
|
||||
});
|
||||
Object.entries(customThemeState.colors).forEach(([k, v]) => {
|
||||
root.style.setProperty(k, v, "important");
|
||||
});
|
||||
@@ -398,9 +395,9 @@ export function SettingsDialog({
|
||||
} else {
|
||||
try {
|
||||
const root = document.documentElement;
|
||||
THEME_VARIABLES.forEach(({ key }) =>
|
||||
root.style.removeProperty(key as string),
|
||||
);
|
||||
THEME_VARIABLES.forEach(({ key }) => {
|
||||
root.style.removeProperty(key as string);
|
||||
});
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ function ObjectEditor({
|
||||
const [jsonString, setJsonString] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
setJsonString(JSON.stringify(value || {}, null, 2));
|
||||
setJsonString(JSON.stringify(value ?? {}, null, 2));
|
||||
}, [value]);
|
||||
|
||||
const handleChange = (newValue: string) => {
|
||||
@@ -144,7 +144,7 @@ export function SharedCamoufoxConfigForm({
|
||||
|
||||
const handleGenerateFingerprint = async () => {
|
||||
if (!profileVersion) return;
|
||||
const browser = profileBrowser || browserType || "camoufox";
|
||||
const browser = profileBrowser ?? browserType ?? "camoufox";
|
||||
setIsGeneratingFingerprint(true);
|
||||
try {
|
||||
const configJson = JSON.stringify(config);
|
||||
@@ -917,7 +917,7 @@ export function SharedCamoufoxConfigForm({
|
||||
(fingerprintConfig["webGl:parameters"] as Record<
|
||||
string,
|
||||
unknown
|
||||
>) || {}
|
||||
>) ?? {}
|
||||
}
|
||||
onChange={(value) => {
|
||||
updateFingerprintConfig("webGl:parameters", value);
|
||||
@@ -934,7 +934,7 @@ export function SharedCamoufoxConfigForm({
|
||||
(fingerprintConfig["webGl2:parameters"] as Record<
|
||||
string,
|
||||
unknown
|
||||
>) || {}
|
||||
>) ?? {}
|
||||
}
|
||||
onChange={(value) => {
|
||||
updateFingerprintConfig("webGl2:parameters", value);
|
||||
@@ -951,7 +951,7 @@ export function SharedCamoufoxConfigForm({
|
||||
(fingerprintConfig["webGl:shaderPrecisionFormats"] as Record<
|
||||
string,
|
||||
unknown
|
||||
>) || {}
|
||||
>) ?? {}
|
||||
}
|
||||
onChange={(value) => {
|
||||
updateFingerprintConfig("webGl:shaderPrecisionFormats", value);
|
||||
@@ -968,7 +968,7 @@ export function SharedCamoufoxConfigForm({
|
||||
(fingerprintConfig["webGl2:shaderPrecisionFormats"] as Record<
|
||||
string,
|
||||
unknown
|
||||
>) || {}
|
||||
>) ?? {}
|
||||
}
|
||||
onChange={(value) => {
|
||||
updateFingerprintConfig("webGl2:shaderPrecisionFormats", value);
|
||||
|
||||
@@ -162,7 +162,9 @@ export function SyncFollowerDialog({
|
||||
!selectedIds.has(profile.id),
|
||||
);
|
||||
}}
|
||||
onKeyDown={() => {}}
|
||||
onKeyDown={() => {
|
||||
/* empty */
|
||||
}}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
|
||||
@@ -173,7 +173,9 @@ export function TrafficDetailsDialog({
|
||||
};
|
||||
|
||||
void fetchStats();
|
||||
const interval = setInterval(fetchStats, 2000);
|
||||
const interval = setInterval(() => {
|
||||
void fetchStats();
|
||||
}, 2000);
|
||||
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
|
||||
@@ -363,7 +363,7 @@ export type ColorPickerOutputProps = ComponentProps<typeof SelectTrigger>;
|
||||
const formats = ["hex", "rgb", "css", "hsl"];
|
||||
|
||||
export const ColorPickerOutput = ({
|
||||
className,
|
||||
className: _className,
|
||||
...props
|
||||
}: ColorPickerOutputProps) => {
|
||||
const { mode, setMode } = useColorPicker();
|
||||
|
||||
@@ -43,7 +43,7 @@ interface HighlightContextType<T extends string> {
|
||||
}
|
||||
|
||||
const HighlightContext = React.createContext<
|
||||
HighlightContextType<any> | undefined
|
||||
HighlightContextType<string> | undefined
|
||||
>(undefined);
|
||||
|
||||
function useHighlight<T extends string>(): HighlightContextType<T> {
|
||||
@@ -419,7 +419,7 @@ function HighlightItem<T extends React.ElementType>({
|
||||
const Component = as ?? "div";
|
||||
const element = children as React.ReactElement<ExtendedChildProps>;
|
||||
const childValue =
|
||||
id ?? value ?? element.props?.["data-value"] ?? element.props?.id ?? itemId;
|
||||
id ?? value ?? element.props["data-value"] ?? element.props.id ?? itemId;
|
||||
const isActive = activeValue === childValue;
|
||||
const isDisabled = disabled === undefined ? contextDisabled : disabled;
|
||||
const itemTransition = transition ?? contextTransition;
|
||||
|
||||
@@ -84,7 +84,7 @@ export function WayfernConfigForm({
|
||||
try {
|
||||
const configJson = JSON.stringify(config);
|
||||
const result = await invoke<string>("generate_sample_fingerprint", {
|
||||
browser: profileBrowser || "wayfern",
|
||||
browser: profileBrowser ?? "wayfern",
|
||||
version: profileVersion,
|
||||
configJson,
|
||||
});
|
||||
|
||||
@@ -22,18 +22,15 @@ export function useAutoHeight<T extends HTMLElement = HTMLDivElement>(
|
||||
const el = ref.current;
|
||||
if (!el) return 0;
|
||||
|
||||
const base = el.getBoundingClientRect().height ?? 0;
|
||||
const base = el.getBoundingClientRect().height;
|
||||
|
||||
let extra = 0;
|
||||
|
||||
if (options.includeParentBox && el.parentElement) {
|
||||
const cs = getComputedStyle(el.parentElement);
|
||||
const paddingY =
|
||||
(parseFloat(cs.paddingTop ?? "0") ?? 0) +
|
||||
(parseFloat(cs.paddingBottom ?? "0") ?? 0);
|
||||
const paddingY = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);
|
||||
const borderY =
|
||||
(parseFloat(cs.borderTopWidth ?? "0") ?? 0) +
|
||||
(parseFloat(cs.borderBottomWidth ?? "0") ?? 0);
|
||||
parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth);
|
||||
const isBorderBox = cs.boxSizing === "border-box";
|
||||
if (isBorderBox) {
|
||||
extra += paddingY + borderY;
|
||||
@@ -42,20 +39,16 @@ export function useAutoHeight<T extends HTMLElement = HTMLDivElement>(
|
||||
|
||||
if (options.includeSelfBox) {
|
||||
const cs = getComputedStyle(el);
|
||||
const paddingY =
|
||||
(parseFloat(cs.paddingTop ?? "0") ?? 0) +
|
||||
(parseFloat(cs.paddingBottom ?? "0") ?? 0);
|
||||
const paddingY = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);
|
||||
const borderY =
|
||||
(parseFloat(cs.borderTopWidth ?? "0") ?? 0) +
|
||||
(parseFloat(cs.borderBottomWidth ?? "0") ?? 0);
|
||||
parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth);
|
||||
const isBorderBox = cs.boxSizing === "border-box";
|
||||
if (isBorderBox) {
|
||||
extra += paddingY + borderY;
|
||||
}
|
||||
}
|
||||
|
||||
const dpr =
|
||||
typeof window !== "undefined" ? (window.devicePixelRatio ?? 1) : 1;
|
||||
const dpr = typeof window !== "undefined" ? window.devicePixelRatio : 1;
|
||||
const total = Math.ceil((base + extra) * dpr) / dpr;
|
||||
|
||||
return total;
|
||||
|
||||
@@ -367,7 +367,9 @@ export function useBrowserDownload() {
|
||||
? loadDownloadedVersions("camoufox")
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
} catch {}
|
||||
} catch {
|
||||
/* empty */
|
||||
}
|
||||
showDownloadToast(browserName, progress.version, "completed");
|
||||
setDownloadProgress(null);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export function useCloudAuth(): UseCloudAuthReturn {
|
||||
};
|
||||
}, [loadUser]);
|
||||
|
||||
const requestOtp = useCallback(async (email: string): Promise<string> => {
|
||||
const requestOtp = useCallback((email: string): Promise<string> => {
|
||||
return invoke<string>("cloud_request_otp", { email });
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ interface CommonControlledStateProps<T> {
|
||||
defaultValue?: T;
|
||||
}
|
||||
|
||||
export function useControlledState<T, Rest extends any[] = []>(
|
||||
export function useControlledState<T, Rest extends unknown[] = []>(
|
||||
props: CommonControlledStateProps<T> & {
|
||||
onChange?: (value: T, ...args: Rest) => void;
|
||||
},
|
||||
|
||||
@@ -17,7 +17,7 @@ export function useProxyEvents() {
|
||||
// Load proxy usage (how many profiles are using each proxy)
|
||||
const loadProxyUsage = useCallback(async () => {
|
||||
try {
|
||||
const profiles = await invoke<Array<{ proxy_id?: string }>>(
|
||||
const profiles = await invoke<{ proxy_id?: string }[]>(
|
||||
"list_browser_profiles",
|
||||
);
|
||||
const counts: Record<string, number> = {};
|
||||
|
||||
@@ -16,7 +16,7 @@ export function useVpnEvents() {
|
||||
|
||||
const loadVpnUsage = useCallback(async () => {
|
||||
try {
|
||||
const profiles = await invoke<Array<{ vpn_id?: string }>>(
|
||||
const profiles = await invoke<{ vpn_id?: string }[]>(
|
||||
"list_browser_profiles",
|
||||
);
|
||||
const counts: Record<string, number> = {};
|
||||
|
||||
+23
-25
@@ -524,7 +524,29 @@
|
||||
"selectedCount_plural": "{{count}} cookies selected"
|
||||
},
|
||||
"success": "Cookies copied successfully",
|
||||
"error": "Failed to copy cookies"
|
||||
"error": "Failed to copy cookies",
|
||||
"management": {
|
||||
"title": "Cookie Management",
|
||||
"menuItem": "Cookie Management"
|
||||
},
|
||||
"import": {
|
||||
"title": "Import Cookies",
|
||||
"description": "Import cookies from a Netscape or JSON format file.",
|
||||
"selectFile": "Choose File",
|
||||
"preview": "{{count}} cookies found",
|
||||
"success": "Successfully imported {{imported}} cookies ({{replaced}} replaced)",
|
||||
"error": "Failed to import cookies",
|
||||
"proFeature": "Cookie import is a Pro feature"
|
||||
},
|
||||
"export": {
|
||||
"title": "Export Cookies",
|
||||
"description": "Export cookies from this profile.",
|
||||
"formatLabel": "Format",
|
||||
"netscape": "Netscape TXT",
|
||||
"json": "JSON",
|
||||
"success": "Cookies exported successfully",
|
||||
"error": "Failed to export cookies"
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
"success": {
|
||||
@@ -724,30 +746,6 @@
|
||||
"cannotLaunch": "This profile was created on {{os}} and is not supported on this system",
|
||||
"cannotModify": "Cannot modify sync settings for a cross-OS profile"
|
||||
},
|
||||
"cookies": {
|
||||
"management": {
|
||||
"title": "Cookie Management",
|
||||
"menuItem": "Cookie Management"
|
||||
},
|
||||
"import": {
|
||||
"title": "Import Cookies",
|
||||
"description": "Import cookies from a Netscape or JSON format file.",
|
||||
"selectFile": "Choose File",
|
||||
"preview": "{{count}} cookies found",
|
||||
"success": "Successfully imported {{imported}} cookies ({{replaced}} replaced)",
|
||||
"error": "Failed to import cookies",
|
||||
"proFeature": "Cookie import is a Pro feature"
|
||||
},
|
||||
"export": {
|
||||
"title": "Export Cookies",
|
||||
"description": "Export cookies from this profile.",
|
||||
"formatLabel": "Format",
|
||||
"netscape": "Netscape TXT",
|
||||
"json": "JSON",
|
||||
"success": "Cookies exported successfully",
|
||||
"error": "Failed to export cookies"
|
||||
}
|
||||
},
|
||||
"profileInfo": {
|
||||
"title": "Profile Details",
|
||||
"tabs": {
|
||||
|
||||
+23
-25
@@ -524,7 +524,29 @@
|
||||
"selectedCount_plural": "{{count}} cookies seleccionadas"
|
||||
},
|
||||
"success": "Cookies copiadas exitosamente",
|
||||
"error": "Error al copiar cookies"
|
||||
"error": "Error al copiar cookies",
|
||||
"management": {
|
||||
"title": "Gestión de Cookies",
|
||||
"menuItem": "Gestión de Cookies"
|
||||
},
|
||||
"import": {
|
||||
"title": "Importar Cookies",
|
||||
"description": "Importar cookies desde un archivo en formato Netscape o JSON.",
|
||||
"selectFile": "Elegir Archivo",
|
||||
"preview": "{{count}} cookies encontradas",
|
||||
"success": "Se importaron {{imported}} cookies exitosamente ({{replaced}} reemplazadas)",
|
||||
"error": "Error al importar cookies",
|
||||
"proFeature": "La importación de cookies es una función Pro"
|
||||
},
|
||||
"export": {
|
||||
"title": "Exportar Cookies",
|
||||
"description": "Exportar cookies de este perfil.",
|
||||
"formatLabel": "Formato",
|
||||
"netscape": "Netscape TXT",
|
||||
"json": "JSON",
|
||||
"success": "Cookies exportadas exitosamente",
|
||||
"error": "Error al exportar cookies"
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
"success": {
|
||||
@@ -724,30 +746,6 @@
|
||||
"cannotLaunch": "Este perfil fue creado en {{os}} y no es compatible con este sistema",
|
||||
"cannotModify": "No se pueden modificar los ajustes de sincronización de un perfil de otro sistema operativo"
|
||||
},
|
||||
"cookies": {
|
||||
"management": {
|
||||
"title": "Gestión de Cookies",
|
||||
"menuItem": "Gestión de Cookies"
|
||||
},
|
||||
"import": {
|
||||
"title": "Importar Cookies",
|
||||
"description": "Importar cookies desde un archivo en formato Netscape o JSON.",
|
||||
"selectFile": "Elegir Archivo",
|
||||
"preview": "{{count}} cookies encontradas",
|
||||
"success": "Se importaron {{imported}} cookies exitosamente ({{replaced}} reemplazadas)",
|
||||
"error": "Error al importar cookies",
|
||||
"proFeature": "La importación de cookies es una función Pro"
|
||||
},
|
||||
"export": {
|
||||
"title": "Exportar Cookies",
|
||||
"description": "Exportar cookies de este perfil.",
|
||||
"formatLabel": "Formato",
|
||||
"netscape": "Netscape TXT",
|
||||
"json": "JSON",
|
||||
"success": "Cookies exportadas exitosamente",
|
||||
"error": "Error al exportar cookies"
|
||||
}
|
||||
},
|
||||
"profileInfo": {
|
||||
"title": "Detalles del Perfil",
|
||||
"tabs": {
|
||||
|
||||
+23
-25
@@ -524,7 +524,29 @@
|
||||
"selectedCount_plural": "{{count}} cookies sélectionnés"
|
||||
},
|
||||
"success": "Cookies copiés avec succès",
|
||||
"error": "Échec de la copie des cookies"
|
||||
"error": "Échec de la copie des cookies",
|
||||
"management": {
|
||||
"title": "Gestion des Cookies",
|
||||
"menuItem": "Gestion des Cookies"
|
||||
},
|
||||
"import": {
|
||||
"title": "Importer des Cookies",
|
||||
"description": "Importer des cookies depuis un fichier au format Netscape ou JSON.",
|
||||
"selectFile": "Choisir un Fichier",
|
||||
"preview": "{{count}} cookies trouvés",
|
||||
"success": "{{imported}} cookies importés avec succès ({{replaced}} remplacés)",
|
||||
"error": "Échec de l'importation des cookies",
|
||||
"proFeature": "L'importation de cookies est une fonctionnalité Pro"
|
||||
},
|
||||
"export": {
|
||||
"title": "Exporter les Cookies",
|
||||
"description": "Exporter les cookies de ce profil.",
|
||||
"formatLabel": "Format",
|
||||
"netscape": "Netscape TXT",
|
||||
"json": "JSON",
|
||||
"success": "Cookies exportés avec succès",
|
||||
"error": "Échec de l'exportation des cookies"
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
"success": {
|
||||
@@ -724,30 +746,6 @@
|
||||
"cannotLaunch": "Ce profil a été créé sur {{os}} et n'est pas pris en charge sur ce système",
|
||||
"cannotModify": "Impossible de modifier les paramètres de synchronisation d'un profil d'un autre système d'exploitation"
|
||||
},
|
||||
"cookies": {
|
||||
"management": {
|
||||
"title": "Gestion des Cookies",
|
||||
"menuItem": "Gestion des Cookies"
|
||||
},
|
||||
"import": {
|
||||
"title": "Importer des Cookies",
|
||||
"description": "Importer des cookies depuis un fichier au format Netscape ou JSON.",
|
||||
"selectFile": "Choisir un Fichier",
|
||||
"preview": "{{count}} cookies trouvés",
|
||||
"success": "{{imported}} cookies importés avec succès ({{replaced}} remplacés)",
|
||||
"error": "Échec de l'importation des cookies",
|
||||
"proFeature": "L'importation de cookies est une fonctionnalité Pro"
|
||||
},
|
||||
"export": {
|
||||
"title": "Exporter les Cookies",
|
||||
"description": "Exporter les cookies de ce profil.",
|
||||
"formatLabel": "Format",
|
||||
"netscape": "Netscape TXT",
|
||||
"json": "JSON",
|
||||
"success": "Cookies exportés avec succès",
|
||||
"error": "Échec de l'exportation des cookies"
|
||||
}
|
||||
},
|
||||
"profileInfo": {
|
||||
"title": "Détails du Profil",
|
||||
"tabs": {
|
||||
|
||||
+23
-25
@@ -524,7 +524,29 @@
|
||||
"selectedCount_plural": "{{count}} 個のCookieを選択"
|
||||
},
|
||||
"success": "Cookieが正常にコピーされました",
|
||||
"error": "Cookieのコピーに失敗しました"
|
||||
"error": "Cookieのコピーに失敗しました",
|
||||
"management": {
|
||||
"title": "Cookie管理",
|
||||
"menuItem": "Cookie管理"
|
||||
},
|
||||
"import": {
|
||||
"title": "Cookieのインポート",
|
||||
"description": "NetscapeまたはJSON形式のファイルからCookieをインポートします。",
|
||||
"selectFile": "ファイルを選択",
|
||||
"preview": "{{count}}件のCookieが見つかりました",
|
||||
"success": "{{imported}}件のCookieをインポートしました({{replaced}}件を置換)",
|
||||
"error": "Cookieのインポートに失敗しました",
|
||||
"proFeature": "Cookieのインポートはプロ機能です"
|
||||
},
|
||||
"export": {
|
||||
"title": "Cookieのエクスポート",
|
||||
"description": "このプロファイルからCookieをエクスポートします。",
|
||||
"formatLabel": "形式",
|
||||
"netscape": "Netscape TXT",
|
||||
"json": "JSON",
|
||||
"success": "Cookieのエクスポートに成功しました",
|
||||
"error": "Cookieのエクスポートに失敗しました"
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
"success": {
|
||||
@@ -724,30 +746,6 @@
|
||||
"cannotLaunch": "このプロファイルは{{os}}で作成されたもので、このシステムではサポートされていません",
|
||||
"cannotModify": "他のOSのプロファイルの同期設定は変更できません"
|
||||
},
|
||||
"cookies": {
|
||||
"management": {
|
||||
"title": "Cookie管理",
|
||||
"menuItem": "Cookie管理"
|
||||
},
|
||||
"import": {
|
||||
"title": "Cookieのインポート",
|
||||
"description": "NetscapeまたはJSON形式のファイルからCookieをインポートします。",
|
||||
"selectFile": "ファイルを選択",
|
||||
"preview": "{{count}}件のCookieが見つかりました",
|
||||
"success": "{{imported}}件のCookieをインポートしました({{replaced}}件を置換)",
|
||||
"error": "Cookieのインポートに失敗しました",
|
||||
"proFeature": "Cookieのインポートはプロ機能です"
|
||||
},
|
||||
"export": {
|
||||
"title": "Cookieのエクスポート",
|
||||
"description": "このプロファイルからCookieをエクスポートします。",
|
||||
"formatLabel": "形式",
|
||||
"netscape": "Netscape TXT",
|
||||
"json": "JSON",
|
||||
"success": "Cookieのエクスポートに成功しました",
|
||||
"error": "Cookieのエクスポートに失敗しました"
|
||||
}
|
||||
},
|
||||
"profileInfo": {
|
||||
"title": "プロフィール詳細",
|
||||
"tabs": {
|
||||
|
||||
+23
-25
@@ -524,7 +524,29 @@
|
||||
"selectedCount_plural": "{{count}} cookies selecionados"
|
||||
},
|
||||
"success": "Cookies copiados com sucesso",
|
||||
"error": "Falha ao copiar cookies"
|
||||
"error": "Falha ao copiar cookies",
|
||||
"management": {
|
||||
"title": "Gerenciamento de Cookies",
|
||||
"menuItem": "Gerenciamento de Cookies"
|
||||
},
|
||||
"import": {
|
||||
"title": "Importar Cookies",
|
||||
"description": "Importar cookies de um arquivo no formato Netscape ou JSON.",
|
||||
"selectFile": "Escolher Arquivo",
|
||||
"preview": "{{count}} cookies encontrados",
|
||||
"success": "{{imported}} cookies importados com sucesso ({{replaced}} substituídos)",
|
||||
"error": "Falha ao importar cookies",
|
||||
"proFeature": "A importação de cookies é um recurso Pro"
|
||||
},
|
||||
"export": {
|
||||
"title": "Exportar Cookies",
|
||||
"description": "Exportar cookies deste perfil.",
|
||||
"formatLabel": "Formato",
|
||||
"netscape": "Netscape TXT",
|
||||
"json": "JSON",
|
||||
"success": "Cookies exportados com sucesso",
|
||||
"error": "Falha ao exportar cookies"
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
"success": {
|
||||
@@ -724,30 +746,6 @@
|
||||
"cannotLaunch": "Este perfil foi criado em {{os}} e não é compatível com este sistema",
|
||||
"cannotModify": "Não é possível modificar as configurações de sincronização de um perfil de outro sistema operacional"
|
||||
},
|
||||
"cookies": {
|
||||
"management": {
|
||||
"title": "Gerenciamento de Cookies",
|
||||
"menuItem": "Gerenciamento de Cookies"
|
||||
},
|
||||
"import": {
|
||||
"title": "Importar Cookies",
|
||||
"description": "Importar cookies de um arquivo no formato Netscape ou JSON.",
|
||||
"selectFile": "Escolher Arquivo",
|
||||
"preview": "{{count}} cookies encontrados",
|
||||
"success": "{{imported}} cookies importados com sucesso ({{replaced}} substituídos)",
|
||||
"error": "Falha ao importar cookies",
|
||||
"proFeature": "A importação de cookies é um recurso Pro"
|
||||
},
|
||||
"export": {
|
||||
"title": "Exportar Cookies",
|
||||
"description": "Exportar cookies deste perfil.",
|
||||
"formatLabel": "Formato",
|
||||
"netscape": "Netscape TXT",
|
||||
"json": "JSON",
|
||||
"success": "Cookies exportados com sucesso",
|
||||
"error": "Falha ao exportar cookies"
|
||||
}
|
||||
},
|
||||
"profileInfo": {
|
||||
"title": "Detalhes do Perfil",
|
||||
"tabs": {
|
||||
|
||||
+23
-25
@@ -524,7 +524,29 @@
|
||||
"selectedCount_plural": "Выбрано {{count}} cookie"
|
||||
},
|
||||
"success": "Cookie успешно скопированы",
|
||||
"error": "Ошибка копирования cookie"
|
||||
"error": "Ошибка копирования cookie",
|
||||
"management": {
|
||||
"title": "Управление Cookies",
|
||||
"menuItem": "Управление Cookies"
|
||||
},
|
||||
"import": {
|
||||
"title": "Импорт Cookies",
|
||||
"description": "Импорт cookies из файла в формате Netscape или JSON.",
|
||||
"selectFile": "Выбрать файл",
|
||||
"preview": "Найдено {{count}} cookies",
|
||||
"success": "Успешно импортировано {{imported}} cookies ({{replaced}} заменено)",
|
||||
"error": "Ошибка импорта cookies",
|
||||
"proFeature": "Импорт cookies — функция Pro"
|
||||
},
|
||||
"export": {
|
||||
"title": "Экспорт Cookies",
|
||||
"description": "Экспорт cookies из этого профиля.",
|
||||
"formatLabel": "Формат",
|
||||
"netscape": "Netscape TXT",
|
||||
"json": "JSON",
|
||||
"success": "Cookies успешно экспортированы",
|
||||
"error": "Ошибка экспорта cookies"
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
"success": {
|
||||
@@ -724,30 +746,6 @@
|
||||
"cannotLaunch": "Этот профиль был создан на {{os}} и не поддерживается в этой системе",
|
||||
"cannotModify": "Невозможно изменить настройки синхронизации профиля другой ОС"
|
||||
},
|
||||
"cookies": {
|
||||
"management": {
|
||||
"title": "Управление Cookies",
|
||||
"menuItem": "Управление Cookies"
|
||||
},
|
||||
"import": {
|
||||
"title": "Импорт Cookies",
|
||||
"description": "Импорт cookies из файла в формате Netscape или JSON.",
|
||||
"selectFile": "Выбрать файл",
|
||||
"preview": "Найдено {{count}} cookies",
|
||||
"success": "Успешно импортировано {{imported}} cookies ({{replaced}} заменено)",
|
||||
"error": "Ошибка импорта cookies",
|
||||
"proFeature": "Импорт cookies — функция Pro"
|
||||
},
|
||||
"export": {
|
||||
"title": "Экспорт Cookies",
|
||||
"description": "Экспорт cookies из этого профиля.",
|
||||
"formatLabel": "Формат",
|
||||
"netscape": "Netscape TXT",
|
||||
"json": "JSON",
|
||||
"success": "Cookies успешно экспортированы",
|
||||
"error": "Ошибка экспорта cookies"
|
||||
}
|
||||
},
|
||||
"profileInfo": {
|
||||
"title": "Детали профиля",
|
||||
"tabs": {
|
||||
|
||||
+23
-25
@@ -524,7 +524,29 @@
|
||||
"selectedCount_plural": "已选择 {{count}} 个 cookies"
|
||||
},
|
||||
"success": "Cookies 复制成功",
|
||||
"error": "复制 cookies 失败"
|
||||
"error": "复制 cookies 失败",
|
||||
"management": {
|
||||
"title": "Cookie 管理",
|
||||
"menuItem": "Cookie 管理"
|
||||
},
|
||||
"import": {
|
||||
"title": "导入 Cookies",
|
||||
"description": "从 Netscape 或 JSON 格式文件导入 Cookies。",
|
||||
"selectFile": "选择文件",
|
||||
"preview": "找到 {{count}} 个 Cookies",
|
||||
"success": "成功导入 {{imported}} 个 Cookies(替换了 {{replaced}} 个)",
|
||||
"error": "导入 Cookies 失败",
|
||||
"proFeature": "导入 Cookies 是 Pro 功能"
|
||||
},
|
||||
"export": {
|
||||
"title": "导出 Cookies",
|
||||
"description": "从此配置文件导出 Cookies。",
|
||||
"formatLabel": "格式",
|
||||
"netscape": "Netscape TXT",
|
||||
"json": "JSON",
|
||||
"success": "Cookies 导出成功",
|
||||
"error": "导出 Cookies 失败"
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
"success": {
|
||||
@@ -724,30 +746,6 @@
|
||||
"cannotLaunch": "此配置文件在 {{os}} 上创建,不受此系统支持",
|
||||
"cannotModify": "无法修改跨操作系统配置文件的同步设置"
|
||||
},
|
||||
"cookies": {
|
||||
"management": {
|
||||
"title": "Cookie 管理",
|
||||
"menuItem": "Cookie 管理"
|
||||
},
|
||||
"import": {
|
||||
"title": "导入 Cookies",
|
||||
"description": "从 Netscape 或 JSON 格式文件导入 Cookies。",
|
||||
"selectFile": "选择文件",
|
||||
"preview": "找到 {{count}} 个 Cookies",
|
||||
"success": "成功导入 {{imported}} 个 Cookies(替换了 {{replaced}} 个)",
|
||||
"error": "导入 Cookies 失败",
|
||||
"proFeature": "导入 Cookies 是 Pro 功能"
|
||||
},
|
||||
"export": {
|
||||
"title": "导出 Cookies",
|
||||
"description": "从此配置文件导出 Cookies。",
|
||||
"formatLabel": "格式",
|
||||
"netscape": "Netscape TXT",
|
||||
"json": "JSON",
|
||||
"success": "Cookies 导出成功",
|
||||
"error": "导出 Cookies 失败"
|
||||
}
|
||||
},
|
||||
"profileInfo": {
|
||||
"title": "配置文件详情",
|
||||
"tabs": {
|
||||
|
||||
+2
-2
@@ -341,13 +341,13 @@ export interface CamoufoxFingerprintConfig {
|
||||
"canvas:aaCapOffset"?: boolean;
|
||||
|
||||
// Voices
|
||||
voices?: Array<{
|
||||
voices?: {
|
||||
isLocalService?: boolean;
|
||||
isDefault?: boolean;
|
||||
voiceURI?: string;
|
||||
name?: string;
|
||||
lang?: string;
|
||||
}>;
|
||||
}[];
|
||||
"voices:blockIfNotDefined"?: boolean;
|
||||
"voices:fakeCompletion"?: boolean;
|
||||
"voices:fakeCompletion:charsPerSecond"?: number;
|
||||
|
||||
Reference in New Issue
Block a user