mirror of
https://github.com/aleksey-hoffman/sigma-file-manager.git
synced 2026-10-19 01:31:05 -05:00
Fix: apply selected theme in all windows
This commit is contained in:
1 parent
f470369f03
commit
06dd790e27
2 files changed
+198
No files matched your search
@@ -0,0 +1,152 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// License: GNU GPLv3 or later. See the license file in the project root for more information.
|
||||
// Copyright © 2021 - present Aleksey Hoffman. All rights reserved.
|
||||
|
||||
import { nextTick } from 'vue';
|
||||
import {
|
||||
beforeEach, describe, expect, it, vi,
|
||||
} from 'vitest';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import { USER_SETTINGS_SCHEMA_VERSION } from '@/stores/schemas/user-settings';
|
||||
import {
|
||||
USER_SETTINGS_THEME_CHANGED_EVENT,
|
||||
useUserSettingsStore,
|
||||
} from '@/stores/storage/user-settings';
|
||||
import type { StartupStorageFileBootstrap } from '@/stores/storage/utils/startup-storage-bootstrap';
|
||||
import type { Theme } from '@/types/user-settings';
|
||||
|
||||
type ThemeEventCallback = (event: { payload: { theme: Theme } }) => void;
|
||||
|
||||
const {
|
||||
emitMock,
|
||||
lazyStoreSaveMock,
|
||||
lazyStoreSetMock,
|
||||
listenMock,
|
||||
themeEventCallbacks,
|
||||
webviewSetZoomMock,
|
||||
} = vi.hoisted(() => ({
|
||||
emitMock: vi.fn(),
|
||||
lazyStoreSaveMock: vi.fn(),
|
||||
lazyStoreSetMock: vi.fn(),
|
||||
listenMock: vi.fn(),
|
||||
themeEventCallbacks: new Map<string, ThemeEventCallback>(),
|
||||
webviewSetZoomMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@tauri-apps/plugin-store', () => ({
|
||||
LazyStore: class {
|
||||
async save(): Promise<void> {
|
||||
await lazyStoreSaveMock();
|
||||
}
|
||||
|
||||
async set(key: string, value: unknown): Promise<void> {
|
||||
await lazyStoreSetMock(key, value);
|
||||
}
|
||||
|
||||
async entries(): Promise<[string, unknown][]> {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@tauri-apps/api/event', () => ({
|
||||
emit: emitMock,
|
||||
listen: listenMock,
|
||||
}));
|
||||
|
||||
vi.mock('@tauri-apps/api/webview', () => ({
|
||||
getCurrentWebview: () => ({
|
||||
setZoom: webviewSetZoomMock,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@tauri-apps/api/core', () => ({
|
||||
invoke: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@tauri-apps/api/path', () => ({
|
||||
appDataDir: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@/stores/storage/user-paths', () => ({
|
||||
useUserPathsStore: () => ({
|
||||
customPaths: {
|
||||
appUserDataSettingsPath: '/tmp/user-data/user-settings.json',
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
function createUserSettingsBootstrap(theme: Theme): StartupStorageFileBootstrap {
|
||||
return {
|
||||
path: '/tmp/user-data/user-settings.json',
|
||||
status: 'ready',
|
||||
data: {
|
||||
__schemaVersion: USER_SETTINGS_SCHEMA_VERSION,
|
||||
theme,
|
||||
},
|
||||
schemaVersion: USER_SETTINGS_SCHEMA_VERSION,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
describe('user settings theme sync', () => {
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
document.documentElement.className = '';
|
||||
document.documentElement.style.cssText = '';
|
||||
emitMock.mockReset().mockResolvedValue(undefined);
|
||||
lazyStoreSaveMock.mockReset();
|
||||
lazyStoreSetMock.mockReset();
|
||||
webviewSetZoomMock.mockReset();
|
||||
themeEventCallbacks.clear();
|
||||
listenMock.mockReset().mockImplementation(async (
|
||||
eventName: string,
|
||||
callback: ThemeEventCallback,
|
||||
) => {
|
||||
themeEventCallbacks.set(eventName, callback);
|
||||
|
||||
return vi.fn();
|
||||
});
|
||||
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: vi.fn(() => ({
|
||||
matches: false,
|
||||
addEventListener: vi.fn(),
|
||||
})),
|
||||
});
|
||||
});
|
||||
|
||||
it('broadcasts theme changes to secondary windows', async () => {
|
||||
const userSettingsStore = useUserSettingsStore();
|
||||
|
||||
await userSettingsStore.init(createUserSettingsBootstrap('dark'));
|
||||
emitMock.mockClear();
|
||||
|
||||
await userSettingsStore.set('theme', 'light');
|
||||
|
||||
expect(emitMock).toHaveBeenCalledWith(USER_SETTINGS_THEME_CHANGED_EVENT, { theme: 'light' });
|
||||
});
|
||||
|
||||
it('applies theme changes from other windows without writing them back', async () => {
|
||||
const userSettingsStore = useUserSettingsStore();
|
||||
|
||||
await userSettingsStore.init(createUserSettingsBootstrap('dark'));
|
||||
emitMock.mockClear();
|
||||
lazyStoreSaveMock.mockClear();
|
||||
lazyStoreSetMock.mockClear();
|
||||
|
||||
themeEventCallbacks.get(USER_SETTINGS_THEME_CHANGED_EVENT)?.({
|
||||
payload: {
|
||||
theme: 'light',
|
||||
},
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
expect(userSettingsStore.userSettings.theme).toBe('light');
|
||||
expect(lazyStoreSetMock).not.toHaveBeenCalled();
|
||||
expect(lazyStoreSaveMock).not.toHaveBeenCalled();
|
||||
expect(emitMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
UserSettingsValue,
|
||||
InfusionPageSettings,
|
||||
VisualFiltersSettings,
|
||||
Theme,
|
||||
} from '@/types/user-settings';
|
||||
import {
|
||||
backgroundMedia,
|
||||
@@ -26,6 +27,7 @@ import { useUserPathsStore } from './user-paths';
|
||||
import { useExtensionsStorageStore } from './extensions';
|
||||
import { i18n } from '@/localization';
|
||||
import { getCurrentWebview } from '@tauri-apps/api/webview';
|
||||
import { emit, listen, type UnlistenFn } from '@tauri-apps/api/event';
|
||||
import {
|
||||
buildAllowedUserSettingsStorageKeys,
|
||||
migrateUserSettingsStorage,
|
||||
@@ -41,6 +43,12 @@ import {
|
||||
} from './utils/startup-storage-bootstrap';
|
||||
import { BUILTIN_NAVIGATOR_ICON_THEME_IDS } from '@/types/icon-theme';
|
||||
|
||||
export const USER_SETTINGS_THEME_CHANGED_EVENT = 'user-settings:theme-changed';
|
||||
|
||||
type ThemeChangedEventPayload = {
|
||||
theme: Theme;
|
||||
};
|
||||
|
||||
export const useUserSettingsStore = defineStore('userSettings', () => {
|
||||
const userPathsStore = useUserPathsStore();
|
||||
const extensionsStorageStore = useExtensionsStorageStore();
|
||||
@@ -49,6 +57,7 @@ export const useUserSettingsStore = defineStore('userSettings', () => {
|
||||
const userSettingsDefault = ref<UserSettings | null>(null);
|
||||
const themeTransitionOrigin = ref<ThemeTransitionOrigin | null>(null);
|
||||
const themeTransitionsEnabled = ref(false);
|
||||
const themeChangeEventUnlisten = ref<UnlistenFn | null>(null);
|
||||
const allowedUserSettingsStorageKeys = ref<Set<string>>(new Set());
|
||||
const userSettings = ref<UserSettings>({
|
||||
language: {
|
||||
@@ -377,6 +386,37 @@ export const useUserSettingsStore = defineStore('userSettings', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function broadcastThemeChange(theme: Theme) {
|
||||
try {
|
||||
await emit(USER_SETTINGS_THEME_CHANGED_EVENT, { theme } satisfies ThemeChangedEventPayload);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to broadcast theme change:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureThemeChangeListener() {
|
||||
if (themeChangeEventUnlisten.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
themeChangeEventUnlisten.value = await listen<ThemeChangedEventPayload>(
|
||||
USER_SETTINGS_THEME_CHANGED_EVENT,
|
||||
(event) => {
|
||||
if (event.payload.theme === userSettings.value.theme) {
|
||||
return;
|
||||
}
|
||||
|
||||
userSettings.value.theme = event.payload.theme;
|
||||
},
|
||||
);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to listen for theme changes:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function setLanguage(newLanguage: LocalizationLanguage) {
|
||||
userSettings.value.language = newLanguage;
|
||||
await setUserSettingsStorage('language', newLanguage);
|
||||
@@ -414,6 +454,11 @@ export const useUserSettingsStore = defineStore('userSettings', () => {
|
||||
}
|
||||
|
||||
current[keys[keys.length - 1]] = value;
|
||||
|
||||
if (key === 'theme') {
|
||||
await broadcastThemeChange(value as Theme);
|
||||
}
|
||||
|
||||
await setUserSettingsStorage(key, value);
|
||||
}
|
||||
|
||||
@@ -451,6 +496,7 @@ export const useUserSettingsStore = defineStore('userSettings', () => {
|
||||
|
||||
initTheme();
|
||||
themeTransitionsEnabled.value = true;
|
||||
await ensureThemeChangeListener();
|
||||
initLanguage();
|
||||
await initZoom();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user