Files
readest/apps/readest-app/src/utils/window.ts
T
Huang Xin 6fcda66b60 fix(reader): close stuck reader window on book load failure, closes #3932 (#3980)
When a book's underlying file is missing, opening it in a dedicated
reader window showed an error toast then navigated that window to
/library, leaving a leftover library-in-reader-window the user had
to close manually. Route the recovery through a new
closeReaderWindowOrGoToLibrary() that closes the dedicated reader
window (after ensuring the main library window is visible) and only
falls back to /library navigation in the main window or on web.

Also fix a related macOS issue: the reader's CloseRequested handler
was running handleCloseBooks and calling currentWindow.destroy() on
the main window, which tore down the active book and bypassed the
Rust close-to-hide handler — making Cmd+W / traffic-light close
quit the app from the reader page (vs. correctly hiding from the
library page) and lose the active book even when the window did
hide. Skip both the cleanup and destroy on macOS for the main
window so the Rust handler hides it with the book intact, matching
the macOS minimize-to-dock convention.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 18:32:04 +02:00

106 lines
3.6 KiB
TypeScript

import { getAllWindows, getCurrentWindow } from '@tauri-apps/api/window';
import { emitTo, TauriEvent } from '@tauri-apps/api/event';
import { exit } from '@tauri-apps/plugin-process';
import { type as osType } from '@tauri-apps/plugin-os';
import { eventDispatcher } from './event';
export const tauriGetWindowLogicalPosition = async () => {
const currentWindow = getCurrentWindow();
const factor = await currentWindow.scaleFactor();
const physicalPos = await currentWindow.outerPosition();
return { x: physicalPos.x / factor, y: physicalPos.y / factor };
};
export const tauriHandleMinimize = async () => {
getCurrentWindow().minimize();
};
// workaround to reset transparent background when toggling fullscreen/maximize
const linuxWindowRestoreTransparentBg = async () => {
const currentSize = await getCurrentWindow().innerSize();
currentSize.width -= 1;
currentSize.height -= 1;
await getCurrentWindow().setSize(currentSize);
setTimeout(async () => {
const currentSize = await getCurrentWindow().innerSize();
currentSize.width += 1;
currentSize.height += 1;
await getCurrentWindow().setSize(currentSize);
}, 100);
};
export const tauriHandleToggleMaximize = async () => {
const currentWindow = getCurrentWindow();
const isFullscreen = await currentWindow.isFullscreen();
if (isFullscreen) {
await currentWindow.setFullscreen(false);
await currentWindow.unmaximize();
} else {
await currentWindow.toggleMaximize();
}
if ((await osType()) === 'linux') {
linuxWindowRestoreTransparentBg();
}
};
export const tauriHandleClose = async () => {
getCurrentWindow().close();
};
export const tauriHandleOnCloseWindow = async (callback: () => void) => {
const currentWindow = getCurrentWindow();
return await currentWindow.onCloseRequested(async (event) => {
event.preventDefault();
// On macOS, the main window's close is intercepted by the Rust backend
// to hide the window (close-to-hide), keeping the app in the dock. Skip
// the in-app cleanup — the user is just minimizing the window and
// expects the active book to still be there when the window reopens.
if (currentWindow.label === 'main' && (await osType()) === 'macos') {
return;
}
await callback();
if (currentWindow.label.startsWith('reader')) {
await emitTo('main', 'close-reader-window', { label: currentWindow.label });
setTimeout(() => currentWindow.destroy(), 300);
} else if (currentWindow.label === 'main') {
await currentWindow.destroy();
}
});
};
export const tauriHandleToggleFullScreen = async () => {
const currentWindow = getCurrentWindow();
const isFullscreen = await currentWindow.isFullscreen();
const isMaximized = await currentWindow.isMaximized();
if (isMaximized) {
await currentWindow.unmaximize();
} else {
await currentWindow.setFullscreen(!isFullscreen);
}
if ((await osType()) === 'linux') {
linuxWindowRestoreTransparentBg();
}
};
export const tauriHandleSetAlwaysOnTop = async (isAlwaysOnTop: boolean) => {
const windows = await getAllWindows();
await Promise.all(windows.map((w) => w.setAlwaysOnTop(isAlwaysOnTop)));
};
export const tauriGetAlwaysOnTop = async () => {
const currentWindow = getCurrentWindow();
return await currentWindow.isAlwaysOnTop();
};
export const tauriHandleOnWindowFocus = async (callback: () => void) => {
const currentWindow = getCurrentWindow();
return currentWindow.listen(TauriEvent.WINDOW_FOCUS, async () => {
await callback();
});
};
export const tauriQuitApp = async () => {
await eventDispatcher.dispatch('quit-app');
await exit(0);
};