feat(settings): start minimized to tray on cold launch (#1271)

This commit is contained in:
cucadmuh
2026-07-11 01:41:44 +03:00
committed by GitHub
parent 51140c613a
commit 1625f4a8be
33 changed files with 315 additions and 18 deletions
+14
View File
@@ -13,12 +13,18 @@ vi.mock('@/lib/themes/startupThemeAppearance', () => ({
applyStartupSplashThemeFromStorage: vi.fn(() => 'mocha'),
}));
vi.mock('@/lib/settings/readStartMinimizedToTray', () => ({
shouldDeferMainWindowReveal: vi.fn(() => false),
}));
vi.mock('@tauri-apps/api/webviewWindow', () => ({
getCurrentWebviewWindow: vi.fn(() => ({ show: vi.fn(() => Promise.resolve()) })),
}));
import { getWindowKind } from './windowKind';
import { applyStartupSplashThemeFromStorage } from '@/lib/themes/startupThemeAppearance';
import { shouldDeferMainWindowReveal } from '@/lib/settings/readStartMinimizedToTray';
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
describe('startupSplash', () => {
beforeEach(() => {
@@ -42,6 +48,14 @@ describe('startupSplash', () => {
expect(applyStartupSplashThemeFromStorage).toHaveBeenCalled();
});
it('skips reveal when start minimized to tray is enabled', () => {
vi.mocked(shouldDeferMainWindowReveal).mockReturnValue(true);
const show = vi.fn(() => Promise.resolve());
vi.mocked(getCurrentWebviewWindow).mockReturnValue({ show } as never);
configureStartupSplash();
expect(show).not.toHaveBeenCalled();
});
it('fades out and removes splash', () => {
vi.useFakeTimers();
dismissStartupSplash();
+2
View File
@@ -1,4 +1,5 @@
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
import { shouldDeferMainWindowReveal } from '@/lib/settings/readStartMinimizedToTray';
import { applyStartupSplashThemeFromStorage } from '@/lib/themes/startupThemeAppearance';
import { getWindowKind } from './windowKind';
@@ -7,6 +8,7 @@ export const STARTUP_SPLASH_ID = 'app-startup-splash';
/** Ensure the native shell is visible once the webview bundle is alive. */
export function revealStartupWindow(): void {
if (getWindowKind() === 'mini') return;
if (shouldDeferMainWindowReveal()) return;
void getCurrentWebviewWindow().show().catch(() => {});
}
+1
View File
@@ -185,6 +185,7 @@ const CONTRIBUTOR_ENTRIES = [
'Queue — resolve thin-state rows off the visible range so off-window items stop rendering as "…" placeholders (desktop panel, mobile drawer, fullscreen up-next) (PR #1236)',
'Artists browse — case-insensitive Cyrillic/non-ASCII name search when local index is enabled (PR #1237)',
'CLI — relative volume via signed `volume` argument (+/ percent delta); suppress WebKit NVIDIA stderr notes on CLI argv; faster Linux CLI forward before WebKit init (PR #1238)',
'Settings — start minimized to tray on cold launch; session-gated startup hide; tray icon coupling (PR #1271)',
'Multi-library filter — priority-ordered multi-select scope across browse/search/detail, sargable library_id + FTS-first SQL, rebuildable library-cluster.db identity keys, locale-aware name normalization (PR #1241)',
'Genres — full catalog via indexed SQL when All libraries is selected; no longer samples first album page on large libraries (PR #1242)',
'Offline browse — on-disk-only Artists/Albums/Tracks/Genres (pins, favorites-auto, hot-cache); reactive sidebar gates and sync-idle reload; local credit mode and genre scope (PR #1243)',
@@ -161,6 +161,18 @@ export function SystemTab() {
checked={auth.minimizeToTray}
onChange={auth.setMinimizeToTray}
/>
<div className="settings-section-divider" />
<SettingsToggle
label={t('settings.startMinimizedToTray')}
desc={
auth.showTrayIcon
? t('settings.startMinimizedToTrayDesc')
: t('settings.startMinimizedToTrayRequiresTray')
}
checked={auth.startMinimizedToTray}
disabled={!auth.showTrayIcon}
onChange={auth.setStartMinimizedToTray}
/>
</SettingsGroup>
{IS_LINUX && (
@@ -71,7 +71,7 @@ export const SETTINGS_INDEX: SearchIndexEntry[] = [
{ tab: 'input', titleKey: 'settings.inputKeybindingsTitle', keywords: 'keybindings shortcuts hotkeys keyboard' },
{ tab: 'input', titleKey: 'settings.globalShortcutsTitle', keywords: 'global shortcuts hotkeys system-wide media keys' },
{ tab: 'system', titleKey: 'settings.language', keywords: 'language locale translation i18n' },
{ tab: 'system', titleKey: 'settings.behavior', keywords: 'behavior tray minimize close start smooth scroll linux' },
{ tab: 'system', titleKey: 'settings.behavior', keywords: 'behavior tray minimize close start minimized launch hidden smooth scroll linux' },
{ tab: 'system', titleKey: 'settings.backupTitle', keywords: 'backup export import settings restore' },
{ tab: 'system', titleKey: 'settings.loggingTitle', keywords: 'log logs diagnostic debug verbose' },
{ tab: 'system', titleKey: 'settings.aboutTitle', keywords: 'about version update changelog release notes' },
@@ -0,0 +1,64 @@
import { beforeEach, describe, expect, it } from 'vitest';
import {
STARTUP_TRAY_HANDLED_KEY,
isStartupTrayHandledThisSession,
markStartupTrayHandledThisSession,
readStartMinimizedToTray,
shouldDeferMainWindowReveal,
shouldDeferMainWindowRevealThisSession,
} from './readStartMinimizedToTray';
describe('readStartMinimizedToTray', () => {
beforeEach(() => {
localStorage.clear();
sessionStorage.clear();
delete window.__psyStartMinimizedToTray;
});
it('returns false when unset', () => {
expect(readStartMinimizedToTray()).toBe(false);
});
it('reads persisted value from psysonic-auth', () => {
localStorage.setItem(
'psysonic-auth',
JSON.stringify({ state: { startMinimizedToTray: true } }),
);
expect(readStartMinimizedToTray()).toBe(true);
});
it('returns false when tray icon is disabled', () => {
localStorage.setItem(
'psysonic-auth',
JSON.stringify({ state: { startMinimizedToTray: true, showTrayIcon: false } }),
);
expect(readStartMinimizedToTray()).toBe(false);
});
});
describe('startup tray session gate', () => {
beforeEach(() => {
localStorage.clear();
sessionStorage.clear();
delete window.__psyStartMinimizedToTray;
});
it('defers reveal only on the first load when the setting is on', () => {
localStorage.setItem(
'psysonic-auth',
JSON.stringify({ state: { startMinimizedToTray: true } }),
);
expect(shouldDeferMainWindowRevealThisSession()).toBe(true);
markStartupTrayHandledThisSession();
expect(shouldDeferMainWindowRevealThisSession()).toBe(false);
expect(isStartupTrayHandledThisSession()).toBe(true);
expect(sessionStorage.getItem(STARTUP_TRAY_HANDLED_KEY)).toBe('1');
});
it('prefers the preflight flag when present', () => {
window.__psyStartMinimizedToTray = true;
expect(shouldDeferMainWindowReveal()).toBe(true);
window.__psyStartMinimizedToTray = false;
expect(shouldDeferMainWindowReveal()).toBe(false);
});
});
@@ -0,0 +1,51 @@
const AUTH_STORAGE_KEY = 'psysonic-auth';
/** Keep in sync with `public/startup-splash-preflight.js` and Rust `eval_startup_main_window_visibility`. */
export const STARTUP_TRAY_HANDLED_KEY = 'psy-startup-tray-handled';
/** Read persisted "start minimized to tray" before Zustand rehydrates. */
export function readStartMinimizedToTray(): boolean {
try {
const raw = localStorage.getItem(AUTH_STORAGE_KEY);
if (!raw) return false;
const parsed = JSON.parse(raw) as {
state?: { startMinimizedToTray?: boolean; showTrayIcon?: boolean };
};
const state = parsed.state;
if (!state?.startMinimizedToTray) return false;
// Tray icon must be available to restore the window after a hidden start.
return state.showTrayIcon !== false;
} catch {
return false;
}
}
/** Whether startup-to-tray already ran (or was skipped) this app process session. */
export function isStartupTrayHandledThisSession(): boolean {
try {
return sessionStorage.getItem(STARTUP_TRAY_HANDLED_KEY) === '1';
} catch {
return false;
}
}
/** True only on the first document load of a process when the setting is on. */
export function shouldDeferMainWindowRevealThisSession(): boolean {
return readStartMinimizedToTray() && !isStartupTrayHandledThisSession();
}
/** Prefer the preflight flag when set; otherwise compute from storage + session. */
export function shouldDeferMainWindowReveal(): boolean {
if (typeof window.__psyStartMinimizedToTray === 'boolean') {
return window.__psyStartMinimizedToTray;
}
return shouldDeferMainWindowRevealThisSession();
}
export function markStartupTrayHandledThisSession(): void {
try {
sessionStorage.setItem(STARTUP_TRAY_HANDLED_KEY, '1');
} catch {
// Non-fatal — worst case we re-apply pause once on reload.
}
}
+3
View File
@@ -255,6 +255,9 @@ export const settings = {
showTrayIconDesc: 'Показва иконата на Psysonic в областта за системни известия / менюто.',
minimizeToTray: 'Минимизирай в системната лента',
minimizeToTrayDesc: 'При затваряне на прозореца, запази Psysonic да работи в системната лента, вместо да излиза.',
startMinimizedToTray: 'Стартирай минимизиран в трея',
startMinimizedToTrayDesc: 'Стартирай Psysonic със скрит главен прозорец; покажи го чрез иконата в трея.',
startMinimizedToTrayRequiresTray: 'Първо включи „Показвай икона в системната лента“ — без трей не можеш да отвориш прозореца след скрит старт.',
clockFormat: 'Формат на часовника',
clockFormatDesc: 'Показване на часа, използвано от прогнозното време на опашката и предварителния преглед на таймера за заспиване.',
clockFormatAuto: 'Автоматично (системен регион)',
+3
View File
@@ -252,6 +252,9 @@ export const settings = {
showTrayIconDesc: 'Psysonic-Icon im System-Tray / in der Menüleiste anzeigen.',
minimizeToTray: 'Im Tray minimieren',
minimizeToTrayDesc: 'Beim Schließen des Fensters läuft Psysonic weiter im System-Tray statt zu beenden.',
startMinimizedToTray: 'Im Tray starten',
startMinimizedToTrayDesc: 'Psysonic mit verstecktem Hauptfenster starten; über das Tray-Icon wieder anzeigen.',
startMinimizedToTrayRequiresTray: 'Zuerst „Tray-Icon anzeigen“ aktivieren — ohne Tray lässt sich das Fenster nach verstecktem Start nicht öffnen.',
clockFormat: 'Uhrzeitformat',
clockFormatDesc: 'Format für die Queue-ETA und die Vorschau des Sleep-Timers.',
clockFormatAuto: 'Automatisch (Systemsprache)',
+3
View File
@@ -255,6 +255,9 @@ export const settings = {
showTrayIconDesc: 'Display the Psysonic icon in the system notification area / menu bar.',
minimizeToTray: 'Minimize to Tray',
minimizeToTrayDesc: 'When closing the window, keep Psysonic running in the system tray instead of quitting.',
startMinimizedToTray: 'Start Minimized to Tray',
startMinimizedToTrayDesc: 'Launch Psysonic with the main window hidden; use the tray icon to show it.',
startMinimizedToTrayRequiresTray: 'Enable "Show Tray Icon" first — you need the tray to open the window after a hidden start.',
clockFormat: 'Clock Format',
clockFormatDesc: 'Wall-clock display used by the queue ETA and the sleep-timer preview.',
clockFormatAuto: 'Auto (system locale)',
+3
View File
@@ -251,6 +251,9 @@ export const settings = {
showTrayIconDesc: 'Muestra el icono de Psysonic en el área de notificación / barra de menú.',
minimizeToTray: 'Minimizar a Bandeja',
minimizeToTrayDesc: 'Al cerrar la ventana, mantener Psysonic ejecutándose en la bandeja del sistema en lugar de salir.',
startMinimizedToTray: 'Iniciar minimizado en la bandeja',
startMinimizedToTrayDesc: 'Iniciar Psysonic con la ventana principal oculta; usa el icono de la bandeja para mostrarla.',
startMinimizedToTrayRequiresTray: 'Activa primero «Mostrar icono en bandeja»; sin él no podrás abrir la ventana tras un inicio oculto.',
clockFormat: 'Formato de hora',
clockFormatDesc: 'Formato del reloj utilizado por la ETA de la cola y la vista previa del temporizador de suspensión.',
clockFormatAuto: 'Automático (idioma del sistema)',
+3
View File
@@ -251,6 +251,9 @@ export const settings = {
showTrayIconDesc: 'Affiche l\'icône Psysonic dans la zone de notification / barre des menus.',
minimizeToTray: 'Réduire dans la barre système',
minimizeToTrayDesc: 'Lors de la fermeture, Psysonic continue de fonctionner dans la barre système au lieu de se fermer.',
startMinimizedToTray: 'Démarrer réduit dans la barre système',
startMinimizedToTrayDesc: 'Lance Psysonic avec la fenêtre principale masquée ; utilise l\'icône de la barre système pour l\'afficher.',
startMinimizedToTrayRequiresTray: 'Activez d\'abord « Afficher l\'icône dans la barre système » — sans elle, la fenêtre ne s\'ouvre pas après un démarrage masqué.',
clockFormat: 'Format de l\'heure',
clockFormatDesc: 'Format de l\'horloge utilisé par l\'ETA de la file d\'attente et l\'aperçu du minuteur de mise en veille.',
clockFormatAuto: 'Automatique (langue du système)',
+3
View File
@@ -255,6 +255,9 @@ export const settings = {
showTrayIconDesc: 'A Psysonic ikon megjelenítése a rendszer értesítési területén / menüsorban.',
minimizeToTray: 'Kis méretre a tálcára',
minimizeToTrayDesc: 'Az ablak bezárásakor a Psysonic kilépés helyett a rendszer tálcáján fut tovább.',
startMinimizedToTray: 'Indítás a tálcán',
startMinimizedToTrayDesc: 'A Psysonic rejtett főablakkal indul; a megjelenítéshez használd a tálca ikont.',
startMinimizedToTrayRequiresTray: 'Először kapcsold be a „Tálcaikon megjelenítése” beállítást — rejtett indítás után csak így nyitható meg az ablak.',
clockFormat: 'Óraformátum',
clockFormatDesc: 'A sor várható befejezési idejéhez és az alvásidőzítő előnézetéhez használt óramegjelenítés.',
clockFormatAuto: 'Automatikus (rendszer beállítása)',
+3
View File
@@ -255,6 +255,9 @@ export const settings = {
showTrayIconDesc: 'Mostra l\'icona di Psysonic nell\'area di notifica di sistema / barra dei menu.',
minimizeToTray: 'Riduci nell\'area di notifica',
minimizeToTrayDesc: 'Alla chiusura della finestra, mantieni Psysonic in esecuzione nell\'area di notifica invece di chiuderlo.',
startMinimizedToTray: 'Avvia ridotto nell\'area di notifica',
startMinimizedToTrayDesc: 'Avvia Psysonic con la finestra principale nascosta; usa l\'icona nell\'area di notifica per mostrarla.',
startMinimizedToTrayRequiresTray: 'Abilita prima «Mostra icona nell\'area di notifica» — senza tray non puoi aprire la finestra dopo un avvio nascosto.',
clockFormat: 'Formato orario',
clockFormatDesc: 'Formato dell\'orario usato nella stima della coda e nell\'anteprima del timer di spegnimento.',
clockFormatAuto: 'Automatico (lingua di sistema)',
+3
View File
@@ -255,6 +255,9 @@ export const settings = {
showTrayIconDesc: 'システム通知領域 / メニューバーに Psysonic アイコンを表示します。',
minimizeToTray: 'トレイへ最小化',
minimizeToTrayDesc: 'ウィンドウを閉じたとき、終了せずにシステムトレイで Psysonic を実行し続けます。',
startMinimizedToTray: 'トレイで起動',
startMinimizedToTrayDesc: 'メインウィンドウを非表示のまま起動します。表示するにはトレイアイコンを使います。',
startMinimizedToTrayRequiresTray: '先に「トレイアイコンを表示」を有効にしてください。非表示起動後はトレイから開けます。',
clockFormat: '時計形式',
clockFormatDesc: 'キューの終了予定時刻とスリープタイマーのプレビューに使う時計表示です。',
clockFormatAuto: '自動 (システムロケール)',
+3
View File
@@ -250,6 +250,9 @@ export const settings = {
showOrbitTriggerDesc: 'Knappen i toppen for å starte eller bli med i en delt lytteøkt. Skjul den hvis du ikke bruker Orbit — du kan slå den på igjen her.',
minimizeToTray: 'Minimer til oppgavelinjen',
minimizeToTrayDesc: 'Når vinduet lukkes, vil Psysonic bli kjørende i oppgavelinjen fremfor å bli avsluttet.',
startMinimizedToTray: 'Start minimert til oppgavelinjen',
startMinimizedToTrayDesc: 'Start Psysonic med hovedvinduet skjult; bruk oppgavelinjeikonet for å vise det.',
startMinimizedToTrayRequiresTray: 'Aktiver «Vis systemstatusikon» først — uten oppgavelinjen kan du ikke åpne vinduet etter skjult oppstart.',
clockFormat: 'Klokkeformat',
clockFormatDesc: 'Klokkevisning brukt av kø-ETA og forhåndsvisningen av hviletimeren.',
clockFormatAuto: 'Automatisk (systemspråk)',
+3
View File
@@ -251,6 +251,9 @@ export const settings = {
showTrayIconDesc: 'Toont het Psysonic-pictogram in het systeemvak / de menubalk.',
minimizeToTray: 'Minimaliseren naar systeemvak',
minimizeToTrayDesc: 'Bij het sluiten van het venster blijft Psysonic actief in het systeemvak in plaats van af te sluiten.',
startMinimizedToTray: 'Start geminimaliseerd in systeemvak',
startMinimizedToTrayDesc: 'Start Psysonic met het hoofdvenster verborgen; gebruik het tray-pictogram om het te tonen.',
startMinimizedToTrayRequiresTray: 'Schakel eerst „Tray-pictogram weergeven” in — zonder tray kun je het venster na een verborgen start niet openen.',
clockFormat: 'Tijdformaat',
clockFormatDesc: 'Tijdsweergave voor de wachtrij-ETA en het voorbeeld van de slaaptimer.',
clockFormatAuto: 'Automatisch (systeemtaal)',
+3
View File
@@ -255,6 +255,9 @@ export const settings = {
showTrayIconDesc: 'Wyświetl ikonę Psysonic w obszarze powiadomień systemu/pasku menu.',
minimizeToTray: 'Minimalizuj do zasobnika systemowego',
minimizeToTrayDesc: 'Podczas zamykania okna, zamiast wyłączać Psysonic, pozostaw go uruchomionym w zasobniku systemowym.',
startMinimizedToTray: 'Uruchamiaj zminimalizowany w zasobniku',
startMinimizedToTrayDesc: 'Uruchamiaj Psysonic z ukrytym oknem głównym; pokaż je przez ikonę w zasobniku.',
startMinimizedToTrayRequiresTray: 'Najpierw włącz „Pokaż ikonę zasobnika” — bez niej nie otworzysz okna po ukrytym starcie.',
clockFormat: 'Format zegara',
clockFormatDesc: 'Wyświetlacz zegara ściennego używany przez kolejkę ETA i podgląd timera uśpienia.',
clockFormatAuto: 'Auto (ustawienia regionalne systemu)',
+3
View File
@@ -254,6 +254,9 @@ export const settings = {
showTrayIconDesc: 'Afișează iconița Psysonic în zona notificărilor de sistem / bara de meniu.',
minimizeToTray: 'Minimizează în Tavă',
minimizeToTrayDesc: 'La închiderea ferestrei, continuă rularea Psysonic în tava de sistem în loc de ieșire',
startMinimizedToTray: 'Pornește minimizat în tavă',
startMinimizedToTrayDesc: 'Lansează Psysonic cu fereastra principală ascunsă; folosește iconița din tavă pentru a o afișa.',
startMinimizedToTrayRequiresTray: 'Activează mai întâi „Afișează iconița tăvii” — fără tavă nu poți deschide fereastra după un start ascuns.',
clockFormat: 'Format Oră',
clockFormatDesc: 'Format al ceasului folosit pentru ETA-ul cozii și previzualizarea cronometrului de somn.',
clockFormatAuto: 'Automat (limba sistemului)',
+3
View File
@@ -256,6 +256,9 @@ export const settings = {
showTrayIconDesc: 'Показывать Psysonic в области уведомлений / строке меню.',
minimizeToTray: 'Сворачивать в трей',
minimizeToTrayDesc: 'При закрытии окна не выходить из приложения, а оставаться в трее.',
startMinimizedToTray: 'Запускать свёрнутым в трей',
startMinimizedToTrayDesc: 'При запуске скрывать главное окно; открыть приложение можно через иконку в трее.',
startMinimizedToTrayRequiresTray: 'Сначала включите «Иконка в трее» — без неё окно не открыть после скрытого запуска.',
clockFormat: 'Формат времени',
clockFormatDesc: 'Формат времени для ETA очереди и предпросмотра таймера сна.',
clockFormatAuto: 'Авто (системный)',
+3
View File
@@ -251,6 +251,9 @@ export const settings = {
showTrayIconDesc: '在系统通知区域 / 菜单栏显示 Psysonic 图标。',
minimizeToTray: '最小化到托盘',
minimizeToTrayDesc: '关闭窗口时,Psysonic 将继续在系统托盘中运行,而不是退出。',
startMinimizedToTray: '启动时最小化到托盘',
startMinimizedToTrayDesc: '启动时隐藏主窗口;通过托盘图标显示窗口。',
startMinimizedToTrayRequiresTray: '请先启用“显示托盘图标”——隐藏启动后需要通过托盘打开窗口。',
clockFormat: '时间格式',
clockFormatDesc: '队列预计结束时间和睡眠定时器预览使用的时间格式。',
clockFormatAuto: '自动(跟随系统)',
+19
View File
@@ -54,6 +54,7 @@ describe('trivial pass-through setters', () => {
['setArtistBrowseCreditMode', 'artistBrowseCreditMode', 'track'],
['setShowTrayIcon', 'showTrayIcon', false],
['setMinimizeToTray', 'minimizeToTray', true],
['setStartMinimizedToTray', 'startMinimizedToTray', true],
['setClockFormat', 'clockFormat', '24h'],
['setShowOrbitTrigger', 'showOrbitTrigger', false],
['setDiscordRichPresence', 'discordRichPresence', true],
@@ -313,6 +314,24 @@ describe('lyrics source setters', () => {
});
});
describe('tray startup settings coupling', () => {
it('setStartMinimizedToTray enables showTrayIcon when it was off', () => {
useAuthStore.getState().setShowTrayIcon(false);
useAuthStore.getState().setStartMinimizedToTray(true);
const s = useAuthStore.getState();
expect(s.startMinimizedToTray).toBe(true);
expect(s.showTrayIcon).toBe(true);
});
it('setShowTrayIcon(false) clears startMinimizedToTray', () => {
useAuthStore.getState().setStartMinimizedToTray(true);
useAuthStore.getState().setShowTrayIcon(false);
const s = useAuthStore.getState();
expect(s.showTrayIcon).toBe(false);
expect(s.startMinimizedToTray).toBe(false);
});
});
describe('mix filter setters — clamp to allowed range', () => {
it('setMixMinRatingSong / Album / Artist clamp out-of-range stars', () => {
useAuthStore.getState().setMixMinRatingSong(10);
+1
View File
@@ -72,6 +72,7 @@ export const useAuthStore = create<AuthState>()(
libraryGridMaxColumns: DEFAULT_LIBRARY_GRID_MAX_COLUMNS,
showTrayIcon: true,
minimizeToTray: false,
startMinimizedToTray: false,
clockFormat: 'auto',
showOrbitTrigger: true,
discordRichPresence: false,
+10
View File
@@ -75,4 +75,14 @@ describe('computeAuthStoreRehydration — lyrics', () => {
{ id: 'netease', enabled: false },
]);
});
it('clears startMinimizedToTray when tray icon is off', () => {
const base = useAuthStore.getState();
const patch = computeAuthStoreRehydration({
...base,
startMinimizedToTray: true,
showTrayIcon: false,
});
expect(patch.startMinimizedToTray).toBe(false);
});
});
+3
View File
@@ -252,6 +252,9 @@ export function computeAuthStoreRehydration(state: AuthState): Partial<AuthState
return {
...mediaDirMigrated,
...musicNetworkMigrated,
...(state.startMinimizedToTray && state.showTrayIcon === false
? { startMinimizedToTray: false as const }
: {}),
mixMinRatingSong: clampMixFilterMinStars(state.mixMinRatingSong as number),
mixMinRatingAlbum: clampMixFilterMinStars(state.mixMinRatingAlbum as number),
mixMinRatingArtist: clampMixFilterMinStars(state.mixMinRatingArtist as number),
+3
View File
@@ -188,6 +188,8 @@ export interface AuthState {
libraryGridMaxColumns: number;
showTrayIcon: boolean;
minimizeToTray: boolean;
/** Cold start: keep the main window hidden and run in the system tray. */
startMinimizedToTray: boolean;
clockFormat: ClockFormat;
/** Whether the "Orbit" topbar trigger is rendered. Users who never
* touch Orbit can hide it so the header stays uncluttered. */
@@ -416,6 +418,7 @@ export interface AuthState {
setLibraryGridMaxColumns: (v: number) => void;
setShowTrayIcon: (v: boolean) => void;
setMinimizeToTray: (v: boolean) => void;
setStartMinimizedToTray: (v: boolean) => void;
setClockFormat: (v: ClockFormat) => void;
setShowOrbitTrigger: (v: boolean) => void;
setDiscordRichPresence: (v: boolean) => void;
+9 -1
View File
@@ -17,6 +17,7 @@ export function createUiAppearanceActions(set: SetState): Pick<
| 'setLibraryGridMaxColumns'
| 'setShowTrayIcon'
| 'setMinimizeToTray'
| 'setStartMinimizedToTray'
| 'setClockFormat'
| 'setShowOrbitTrigger'
| 'setUseCustomTitlebar'
@@ -45,8 +46,15 @@ export function createUiAppearanceActions(set: SetState): Pick<
setShowArtistImages: (v) => set({ showArtistImages: v }),
setArtistBrowseCreditMode: (v) => set({ artistBrowseCreditMode: v === 'track' ? 'track' : 'album' }),
setLibraryGridMaxColumns: (v) => set({ libraryGridMaxColumns: clampLibraryGridMaxColumns(v) }),
setShowTrayIcon: (v) => set({ showTrayIcon: v }),
setShowTrayIcon: (v) => set({
showTrayIcon: v,
...(v ? {} : { startMinimizedToTray: false }),
}),
setMinimizeToTray: (v) => set({ minimizeToTray: v }),
setStartMinimizedToTray: (v) => set((state) => ({
startMinimizedToTray: v,
...(v && !state.showTrayIcon ? { showTrayIcon: true } : {}),
})),
setClockFormat: (v) => set({ clockFormat: v }),
setShowOrbitTrigger: (v) => set({ showOrbitTrigger: v }),
setUseCustomTitlebar: (v) => set({ useCustomTitlebar: v }),
+1
View File
@@ -4,6 +4,7 @@ declare global {
interface Window {
__psyHidden?: boolean;
__psyBlurred?: boolean;
__psyStartMinimizedToTray?: boolean;
}
}