fix(desktop): Dateidialog folgt dem Dunkelmodus (GTK prefer-dark via set_theme)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -385,10 +385,18 @@ fn server_host(url: Option<&str>) -> Option<String> {
|
||||
/// `is_permission_granted.toml`, `request_permission.toml`). Eine unbekannte
|
||||
/// Kennung wuerde in `add_capability` ebenfalls in einen Panic laufen; darum
|
||||
/// pinnt ein Test die exakte Menge samt Reihenfolge.
|
||||
const SERVER_NOTIFICATION_PERMISSIONS: [&str; 3] = [
|
||||
///
|
||||
/// Dazu `core:window:allow-set-theme` (quick-261002, Dunkelmodus): die
|
||||
/// Web-Oberflaeche meldet ihr helles/dunkles Erscheinungsbild an das Fenster.
|
||||
/// Unter Linux setzt tao damit `gtk-application-prefer-dark-theme`, sodass
|
||||
/// der GTK-Dateidialog eines `<input type="file">` dem Dunkelmodus folgt;
|
||||
/// unter Windows folgt die Titelleiste. Erlaubt nur `set_theme`, keine
|
||||
/// anderen Fensterbefehle.
|
||||
const SERVER_NOTIFICATION_PERMISSIONS: [&str; 4] = [
|
||||
"notification:allow-notify",
|
||||
"notification:allow-is-permission-granted",
|
||||
"notification:allow-request-permission",
|
||||
"core:window:allow-set-theme",
|
||||
];
|
||||
|
||||
/// Bereits berechtigte Herkunfts-Muster. `add_capability` haengt bei jedem
|
||||
@@ -1859,7 +1867,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_origin_berechtigungen_sind_genau_diese_drei() {
|
||||
fn server_origin_berechtigungen_sind_genau_diese_vier() {
|
||||
// Pinnt die Menge (T-IF2-03): ein zusaetzliches `notification:default`
|
||||
// oder eine Platzhalter-Berechtigung laesst den Test rot werden.
|
||||
assert_eq!(
|
||||
@@ -1868,6 +1876,7 @@ mod tests {
|
||||
"notification:allow-notify",
|
||||
"notification:allow-is-permission-granted",
|
||||
"notification:allow-request-permission",
|
||||
"core:window:allow-set-theme",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getLocale, getMessages } from 'next-intl/server';
|
||||
import { ThemeProvider } from 'next-themes';
|
||||
import { DesktopContextMenuGuard } from '@/components/desktop/desktop-context-menu-guard';
|
||||
import { DesktopExternalLinks } from '@/components/desktop/desktop-external-links';
|
||||
import { DesktopThemeSync } from '@/components/desktop/desktop-theme-sync';
|
||||
import './globals.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -27,6 +28,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
<DesktopContextMenuGuard />
|
||||
<DesktopExternalLinks />
|
||||
<DesktopThemeSync />
|
||||
{children}
|
||||
</NextIntlClientProvider>
|
||||
</ThemeProvider>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { act, cleanup, render } from '@testing-library/react';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { DESKTOP_COOKIE_NAME } from '@/lib/desktop-client';
|
||||
import { DesktopThemeSync } from './desktop-theme-sync';
|
||||
|
||||
/**
|
||||
* desktop-theme-sync.test (quick-261002) — meldet hell/dunkel per
|
||||
* `plugin:window|set_theme` an das Desktop-Fenster, im Browser nichts.
|
||||
*/
|
||||
const theme = vi.hoisted(() => ({ resolvedTheme: 'dark' as string | undefined }));
|
||||
vi.mock('next-themes', () => ({ useTheme: () => theme }));
|
||||
|
||||
const invoke = vi.fn(() => Promise.resolve());
|
||||
|
||||
function setCookie() {
|
||||
document.cookie = `${DESKTOP_COOKIE_NAME}=1; path=/`;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
invoke.mockClear();
|
||||
(window as unknown as Record<string, unknown>).__TAURI_INTERNALS__ = { invoke };
|
||||
theme.resolvedTheme = 'dark';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
document.cookie = `${DESKTOP_COOKIE_NAME}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
|
||||
delete (window as unknown as Record<string, unknown>).__TAURI_INTERNALS__;
|
||||
});
|
||||
|
||||
describe('DesktopThemeSync', () => {
|
||||
it('meldet im Desktop-Client das dunkle Erscheinungsbild', async () => {
|
||||
setCookie();
|
||||
await act(async () => {
|
||||
render(<DesktopThemeSync />);
|
||||
});
|
||||
expect(invoke).toHaveBeenCalledWith('plugin:window|set_theme', { value: 'dark' });
|
||||
});
|
||||
|
||||
it('meldet einen Wechsel auf hell erneut', async () => {
|
||||
setCookie();
|
||||
const view = await act(async () => render(<DesktopThemeSync />));
|
||||
theme.resolvedTheme = 'light';
|
||||
await act(async () => {
|
||||
view.rerender(<DesktopThemeSync />);
|
||||
});
|
||||
expect(invoke).toHaveBeenLastCalledWith('plugin:window|set_theme', { value: 'light' });
|
||||
});
|
||||
|
||||
it('ruft im Browser nichts auf', async () => {
|
||||
await act(async () => {
|
||||
render(<DesktopThemeSync />);
|
||||
});
|
||||
expect(invoke).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('wartet, solange das Erscheinungsbild noch nicht feststeht', async () => {
|
||||
setCookie();
|
||||
theme.resolvedTheme = undefined;
|
||||
await act(async () => {
|
||||
render(<DesktopThemeSync />);
|
||||
});
|
||||
expect(invoke).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
'use client';
|
||||
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useEffect } from 'react';
|
||||
import { useIsDesktopClient } from '@/lib/desktop-client';
|
||||
|
||||
type TauriInvoke = (cmd: string, args?: Record<string, unknown>) => Promise<unknown>;
|
||||
|
||||
/**
|
||||
* DesktopThemeSync (quick-261002) — meldet dem Desktop-Fenster, ob Tessera
|
||||
* hell oder dunkel dargestellt wird. Unter Linux schaltet das GTK auf die
|
||||
* dunkle Variante, damit z. B. der Dateidialog eines Uploads nicht grell weiss
|
||||
* aufgeht; unter Windows folgt die Titelleiste. Im Browser ohne Wirkung.
|
||||
* Fehler (aeltere Desktop-App ohne Berechtigung) werden still ignoriert.
|
||||
*/
|
||||
export function DesktopThemeSync() {
|
||||
const isDesktop = useIsDesktopClient();
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDesktop || (resolvedTheme !== 'dark' && resolvedTheme !== 'light')) return;
|
||||
const internals = (window as unknown as Record<string, unknown>).__TAURI_INTERNALS__ as
|
||||
| { invoke?: TauriInvoke }
|
||||
| undefined;
|
||||
if (typeof internals?.invoke !== 'function') return;
|
||||
internals.invoke('plugin:window|set_theme', { value: resolvedTheme }).catch(() => {});
|
||||
}, [isDesktop, resolvedTheme]);
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user