feat: support multiple reader windows on desktop, closes #259 (#1596)

This commit is contained in:
Huang Xin
2025-07-17 16:21:37 +08:00
committed by GitHub
parent cde055dcb7
commit 6da6b85530
39 changed files with 203 additions and 55 deletions
+2
View File
@@ -38,3 +38,5 @@ next-env.d.ts
# Rust build
target
fastlane/report.xml
@@ -465,5 +465,6 @@
"Google Books": "كتب جوجل",
"Open Library": "المكتبة المفتوحة",
"Fiction, Science, History": "خيال، علوم، تاريخ",
"Select Cover Image": "اختر صورة الغلاف"
"Select Cover Image": "اختر صورة الغلاف",
"Open Book in New Window": "فتح الكتاب في نافذة جديدة"
}
@@ -449,5 +449,6 @@
"Google Books": "Google Books",
"Open Library": "Open Library",
"Fiction, Science, History": "Belletristik, Wissenschaft, Geschichte",
"Select Cover Image": "Coverbild auswählen"
"Select Cover Image": "Coverbild auswählen",
"Open Book in New Window": "Buch in neuem Fenster öffnen"
}
@@ -449,5 +449,6 @@
"Google Books": "Google Books",
"Open Library": "Open Library",
"Fiction, Science, History": "Μυθοπλασία, Επιστήμη, Ιστορία",
"Select Cover Image": "Επιλέξτε εικόνα εξωφύλλου"
"Select Cover Image": "Επιλέξτε εικόνα εξωφύλλου",
"Open Book in New Window": "Άνοιγμα βιβλίου σε νέο παράθυρο"
}
@@ -453,5 +453,6 @@
"Google Books": "Google Books",
"Open Library": "Open Library",
"Fiction, Science, History": "Ficción, Ciencia, Historia",
"Select Cover Image": "Seleccionar imagen de portada"
"Select Cover Image": "Seleccionar imagen de portada",
"Open Book in New Window": "Abre el libro en una nueva ventana"
}
@@ -453,5 +453,6 @@
"Google Books": "Google Books",
"Open Library": "Open Library",
"Fiction, Science, History": "Fiction, Science, Histoire",
"Select Cover Image": "Sélectionner l'image de couverture"
"Select Cover Image": "Sélectionner l'image de couverture",
"Open Book in New Window": "Ouvrir dans une nouvelle fenêtre"
}
@@ -449,5 +449,6 @@
"Google Books": "गूगल बुक्स",
"Open Library": "ओपन लाइब्रेरी",
"Fiction, Science, History": "काल्पनिक, विज्ञान, इतिहास",
"Select Cover Image": "कवर इमेज चुनें"
"Select Cover Image": "कवर इमेज चुनें",
"Open Book in New Window": "नई विंडो में पुस्तक खोलें"
}
@@ -445,5 +445,6 @@
"Google Books": "Google Books",
"Open Library": "Open Library",
"Fiction, Science, History": "Fiksi, Sains, Sejarah",
"Select Cover Image": "Pilihan Gambar Sampul"
"Select Cover Image": "Pilihan Gambar Sampul",
"Open Book in New Window": "Buka Buku di Jendela Baru"
}
@@ -453,5 +453,6 @@
"Google Books": "Google Books",
"Open Library": "Open Library",
"Fiction, Science, History": "Narrativa, Scienza, Storia",
"Select Cover Image": "Seleziona immagine di copertina"
"Select Cover Image": "Seleziona immagine di copertina",
"Open Book in New Window": "Apri libro in una nuova finestra"
}
@@ -445,5 +445,6 @@
"Google Books": "Google Books",
"Open Library": "Open Library",
"Fiction, Science, History": "小説、科学、歴史",
"Select Cover Image": "表紙画像を選択"
"Select Cover Image": "表紙画像を選択",
"Open Book in New Window": "新しいウィンドウで本を開く"
}
@@ -445,5 +445,6 @@
"Google Books": "구글 북스",
"Open Library": "오픈 라이브러리",
"Fiction, Science, History": "소설, 과학, 역사",
"Select Cover Image": "표지 이미지 선택"
"Select Cover Image": "표지 이미지 선택",
"Open Book in New Window": "새 창에서 책 열기"
}
@@ -449,5 +449,6 @@
"Google Books": "Google Books",
"Open Library": "Open Library",
"Fiction, Science, History": "Fictie, Wetenschap, Geschiedenis",
"Select Cover Image": "Selecteer omslagafbeelding"
"Select Cover Image": "Selecteer omslagafbeelding",
"Open Book in New Window": "Open boek in nieuw venster"
}
@@ -457,5 +457,6 @@
"Google Books": "Google Books",
"Open Library": "Open Library",
"Fiction, Science, History": "Beletrystyka, Nauka, Historia",
"Select Cover Image": "Zaznacz obraz okładki"
"Select Cover Image": "Zaznacz obraz okładki",
"Open Book in New Window": "Otwórz książkę w nowym oknie"
}
@@ -453,5 +453,6 @@
"Google Books": "Google Books",
"Open Library": "Open Library",
"Fiction, Science, History": "Ficção, Ciência, História",
"Select Cover Image": "Selecionar imagem da capa"
"Select Cover Image": "Selecionar imagem da capa",
"Open Book in New Window": "Abrir livro em nova janela"
}
@@ -457,5 +457,6 @@
"Google Books": "Google Книги",
"Open Library": "Open Library",
"Fiction, Science, History": "Художественная литература, Наука, История",
"Select Cover Image": "Выбрать изображение обложки"
"Select Cover Image": "Выбрать изображение обложки",
"Open Book in New Window": "Открыть книгу в новом окне"
}
@@ -445,5 +445,6 @@
"Google Books": "Google Books",
"Open Library": "Open Library",
"Fiction, Science, History": "นิยาย, วิทยาศาสตร์, ประวัติศาสตร์",
"Select Cover Image": "เลือกภาพปก"
"Select Cover Image": "เลือกภาพปก",
"Open Book in New Window": "เปิดหนังสือในหน้าต่างใหม่"
}
@@ -449,5 +449,6 @@
"Google Books": "Google Books",
"Open Library": "Open Library",
"Fiction, Science, History": "Kurgu, Bilim, Tarih",
"Select Cover Image": "Kapak Resmi Seç"
"Select Cover Image": "Kapak Resmi Seç",
"Open Book in New Window": "Kitabı Yeni Pencerede Aç"
}
@@ -457,5 +457,6 @@
"Google Books": "Google Книги",
"Open Library": "Open Library",
"Fiction, Science, History": "Художня література, Наукова фантастика, Історія",
"Select Cover Image": "Оберіть зображення обкладинки"
"Select Cover Image": "Оберіть зображення обкладинки",
"Open Book in New Window": "Відкрити книгу в новому вікні"
}
@@ -445,5 +445,6 @@
"Google Books": "Google Books",
"Open Library": "Open Library",
"Fiction, Science, History": "Tiểu thuyết, Khoa học, Lịch sử",
"Select Cover Image": "Chọn ảnh bìa"
"Select Cover Image": "Chọn ảnh bìa",
"Open Book in New Window": "Open Sách trong Cửa sổ Mới"
}
@@ -445,5 +445,6 @@
"Google Books": "谷歌图书",
"Open Library": "开放图书馆",
"Fiction, Science, History": "小说,科学,历史",
"Select Cover Image": "选择封面图片"
"Select Cover Image": "选择封面图片",
"Open Book in New Window": "在新窗口中打开书籍"
}
@@ -445,5 +445,6 @@
"Google Books": "Google 圖書",
"Open Library": "開放圖書館",
"Fiction, Science, History": "小說,科學,歷史",
"Select Cover Image": "選擇封面圖片"
"Select Cover Image": "選擇封面圖片",
"Open Book in New Window": "在新視窗中打開書籍"
}
@@ -2,7 +2,7 @@
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"windows": ["main", "updater"],
"windows": ["main", "updater", "reader-*"],
"permissions": [
"core:default",
"fs:default",
@@ -91,6 +91,7 @@
"core:window:allow-toggle-maximize",
"core:window:allow-set-fullscreen",
"core:window:allow-set-always-on-top",
"core:window:allow-destroy",
"core:webview:allow-create-webview-window",
"core:path:allow-resolve-directory",
"log:default",
@@ -144,7 +144,8 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
.expect("NS window should exist on state to handle resize")
as id;
if state.window.label() == "main" {
if state.window.label() == "main" || state.window.label().starts_with("reader")
{
position_traffic_lights(
UnsafeWindowHandle(id as *mut std::ffi::c_void),
TRAFFIC_LIGHTS_VISIBLE,
@@ -278,7 +279,8 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
.expect("Failed to emit event");
let id = state.window.ns_window().expect("Failed to emit event") as id;
if state.window.label() == "main" {
if state.window.label() == "main" || state.window.label().starts_with("reader")
{
position_traffic_lights(
UnsafeWindowHandle(id as *mut std::ffi::c_void),
TRAFFIC_LIGHTS_VISIBLE,
@@ -11,7 +11,7 @@ import { useEnv } from '@/context/EnvContext';
import { useSettingsStore } from '@/store/settingsStore';
import { useLibraryStore } from '@/store/libraryStore';
import { useTranslation } from '@/hooks/useTranslation';
import { navigateToLibrary, navigateToReader } from '@/utils/nav';
import { navigateToLibrary, navigateToReader, showReaderWindow } from '@/utils/nav';
import { formatAuthors, formatTitle } from '@/utils/book';
import { isMd5 } from '@/utils/md5';
@@ -169,8 +169,13 @@ const Bookshelf: React.FC<BookshelfProps> = ({
};
const openSelectedBooks = () => {
setTimeout(() => setLoading(true), 200);
navigateToReader(router, selectedBooks);
handleSetSelectMode(false);
if (appService?.hasWindow && settings.openBookInNewWindow) {
showReaderWindow(appService, selectedBooks);
} else {
setTimeout(() => setLoading(true), 200);
navigateToReader(router, selectedBooks);
}
};
const openBookDetails = () => {
@@ -1,6 +1,6 @@
import clsx from 'clsx';
import { useRouter, useSearchParams } from 'next/navigation';
import { navigateToLibrary, navigateToReader } from '@/utils/nav';
import { navigateToLibrary, navigateToReader, showReaderWindow } from '@/utils/nav';
import { useEnv } from '@/context/EnvContext';
import { useLibraryStore } from '@/store/libraryStore';
import { useSettingsStore } from '@/store/settingsStore';
@@ -117,8 +117,9 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
const makeBookAvailable = async (book: Book) => {
if (book.uploadedAt && !book.downloadedAt) {
if (await appService?.isBookAvailable(book)) {
if (!book.downloadedAt) {
if (!book.downloadedAt || !book.coverDownloadedAt) {
book.downloadedAt = Date.now();
book.coverDownloadedAt = Date.now();
updateBook(envConfig, book);
}
return true;
@@ -142,7 +143,11 @@ const BookshelfItem: React.FC<BookshelfItemProps> = ({
toggleSelection(book.hash);
} else {
if (!(await makeBookAvailable(book))) return;
navigateToReader(router, [book.hash]);
if (appService?.hasWindow && settings.openBookInNewWindow) {
showReaderWindow(appService, [book.hash]);
} else {
navigateToReader(router, [book.hash]);
}
}
};
@@ -83,6 +83,13 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
setIsDropdownOpen?.(false);
};
const toggleOpenInNewWindow = () => {
settings.openBookInNewWindow = !settings.openBookInNewWindow;
setSettings(settings);
saveSettings(envConfig, settings);
setIsDropdownOpen?.(false);
};
const toggleAlwaysOnTop = () => {
settings.alwaysOnTop = !settings.alwaysOnTop;
setSettings(settings);
@@ -219,6 +226,13 @@ const SettingsMenu: React.FC<SettingsMenuProps> = ({ setIsDropdownOpen }) => {
/>
)}
<hr className='border-base-200 my-1' />
{appService?.hasWindow && (
<MenuItem
label={_('Open Book in New Window')}
Icon={settings.openBookInNewWindow ? MdCheck : undefined}
onClick={toggleOpenInNewWindow}
/>
)}
{appService?.hasWindow && <MenuItem label={_('Fullscreen')} onClick={handleFullScreen} />}
{appService?.hasWindow && (
<MenuItem
@@ -70,7 +70,7 @@ export const useBooksSync = ({ onSyncStart, onSyncEnd }: UseBooksSyncProps) => {
const processOldBook = async (oldBook: Book) => {
const matchingBook = syncedBooks.find((newBook) => newBook.hash === oldBook.hash);
if (matchingBook) {
if (!matchingBook.deletedAt && matchingBook.uploadedAt && !oldBook.downloadedAt) {
if (!matchingBook.deletedAt && matchingBook.uploadedAt && !oldBook.coverDownloadedAt) {
await appService?.downloadBook(oldBook, true);
}
const mergedBook =
+24 -2
View File
@@ -2,7 +2,7 @@
import clsx from 'clsx';
import * as React from 'react';
import { useState, useRef, useEffect, Suspense } from 'react';
import { useState, useRef, useEffect, Suspense, useCallback } from 'react';
import { ReadonlyURLSearchParams, useRouter, useSearchParams } from 'next/navigation';
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from 'overlayscrollbars-react';
import 'overlayscrollbars/overlayscrollbars.css';
@@ -194,6 +194,28 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
}
};
const handleRefreshLibrary = useCallback(async () => {
const appService = await envConfig.getAppService();
const settings = await appService.loadSettings();
const library = await appService.loadLibraryBooks();
setSettings(settings);
setLibrary(library);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [envConfig, appService]);
useEffect(() => {
if (appService?.hasWindow) {
const currentWebview = getCurrentWebview();
const unlisten = currentWebview.listen('close-reader-window', async () => {
handleRefreshLibrary();
});
return () => {
unlisten.then((fn) => fn());
};
}
return;
}, [appService, handleRefreshLibrary]);
useEffect(() => {
const libraryPage = document.querySelector('.library-page');
if (!appService?.isMobile) {
@@ -294,7 +316,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
navigateToReader(router, bookIds);
}
}
}, [pendingNavigationBookIds, router]);
}, [pendingNavigationBookIds, appService, router]);
useEffect(() => {
if (isInitiating.current) return;
@@ -62,19 +62,18 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
initializeTrafficLightStore(appService);
initializeTrafficLightListeners();
setTrafficLightVisibility(true);
return () => {
cleanupTrafficLightListeners();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [appService]);
useEffect(() => {
if (!appService?.hasTrafficLight) return;
setTrafficLightVisibility(isSideBarVisible);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSideBarVisible]);
}, [appService, isSideBarVisible]);
const isVisible = hoveredBookKey === bookKey || isDropdownOpen;
@@ -86,7 +85,7 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
}}
>
<div
className={clsx('absolute top-0 z-10 hidden h-11 w-full sm:flex')}
className={clsx('absolute top-0 z-10 h-11 w-full')}
onMouseEnter={() => !appService?.isMobile && setHoveredBookKey(bookKey)}
onTouchStart={() => !appService?.isMobile && setHoveredBookKey(bookKey)}
/>
@@ -5,14 +5,16 @@ import * as React from 'react';
import { useState, useRef, useEffect } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { Book } from '@/types/book';
import { useEnv } from '@/context/EnvContext';
import { useSettingsStore } from '@/store/settingsStore';
import { useBookDataStore } from '@/store/bookDataStore';
import { useReaderStore } from '@/store/readerStore';
import { useSidebarStore } from '@/store/sidebarStore';
import { Book } from '@/types/book';
import { SystemSettings } from '@/types/settings';
import { parseOpenWithFiles } from '@/helpers/openWith';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { UnlistenFn } from '@tauri-apps/api/event';
import { tauriHandleClose, tauriHandleOnCloseWindow } from '@/utils/window';
import { isTauriAppPlatform } from '@/services/environment';
import { uniqueId } from '@/utils/misc';
@@ -83,7 +85,10 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
saveSettings(envConfig, settings);
}
if (isTauriAppPlatform()) tauriHandleOnCloseWindow(handleCloseBooks);
let unlistenOnCloseWindow: Promise<UnlistenFn>;
if (isTauriAppPlatform()) {
unlistenOnCloseWindow = tauriHandleOnCloseWindow(handleCloseBooks);
}
window.addEventListener('beforeunload', handleCloseBooks);
eventDispatcher.on('beforereload', handleCloseBooks);
eventDispatcher.on('quit-app', handleCloseBooks);
@@ -91,6 +96,7 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
window.removeEventListener('beforeunload', handleCloseBooks);
eventDispatcher.off('beforereload', handleCloseBooks);
eventDispatcher.off('quit-app', handleCloseBooks);
unlistenOnCloseWindow?.then((fn) => fn());
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bookKeys]);
@@ -128,7 +134,7 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
const settings = useSettingsStore.getState().settings;
await Promise.all(bookKeys.map((key) => saveConfigAndCloseBook(key)));
await saveSettings(envConfig, settings);
}, 500);
}, 200);
const handleCloseBooksToLibrary = () => {
handleCloseBooks();
@@ -143,11 +149,16 @@ const ReaderContent: React.FC<{ ids?: string; settings: SystemSettings }> = ({ i
dismissBook(bookKey);
if (bookKeys.filter((key) => key !== bookKey).length == 0) {
const openWithFiles = (await parseOpenWithFiles()) || [];
if (openWithFiles.length > 0 && !appService?.isMobile) {
tauriHandleClose();
} else {
saveSettingsAndGoToLibrary();
if (appService?.hasWindow) {
if (openWithFiles.length > 0) {
return await tauriHandleClose();
}
const currentWindow = getCurrentWindow();
if (currentWindow.label.startsWith('reader')) {
return await currentWindow.close();
}
}
saveSettingsAndGoToLibrary();
}
};
@@ -27,7 +27,7 @@ const SidebarHeader: React.FC<{
<div
className={clsx(
'sidebar-header flex h-11 items-center justify-between pe-2',
isTrafficLightVisible ? 'pl-20' : 'ps-1.5',
isTrafficLightVisible ? 'ps-1.5 sm:ps-20' : 'ps-1.5',
)}
dir='ltr'
>
+4 -1
View File
@@ -13,7 +13,10 @@ interface CliArgument {
}
const parseWindowOpenWithFiles = () => {
return window.OPEN_WITH_FILES;
const params = new URLSearchParams(window.location.search);
const filesParams = params.get('files') || '';
const files = filesParams ? filesParams.split(',').map(decodeURIComponent) : [];
return files.length > 0 ? files : window.OPEN_WITH_FILES;
};
const parseCLIOpenWithFiles = async () => {
+15 -7
View File
@@ -1,10 +1,12 @@
import { useEffect, useRef } from 'react';
import { useRouter } from 'next/navigation';
import { useEnv } from '@/context/EnvContext';
import { useLibraryStore } from '@/store/libraryStore';
import { useSettingsStore } from '@/store/settingsStore';
import { onOpenUrl } from '@tauri-apps/plugin-deep-link';
import { getCurrentWindow } from '@tauri-apps/api/window';
import { isTauriAppPlatform } from '@/services/environment';
import { useLibraryStore } from '@/store/libraryStore';
import { navigateToLibrary } from '@/utils/nav';
import { navigateToLibrary, showLibraryWindow } from '@/utils/nav';
interface SingleInstancePayload {
args: string[];
@@ -13,6 +15,7 @@ interface SingleInstancePayload {
export function useOpenWithBooks() {
const router = useRouter();
const { appService } = useEnv();
const { setCheckOpenWithBooks } = useLibraryStore();
const listenedOpenWithBooks = useRef(false);
@@ -23,14 +26,19 @@ export function useOpenWithBooks() {
filePath = decodeURI(filePath.replace('file://', ''));
}
if (!/^(https?:|data:|blob:)/i.test(filePath)) {
window.OPEN_WITH_FILES = [filePath];
setCheckOpenWithBooks(true);
navigateToLibrary(router, `reload=${Date.now()}`);
const settings = useSettingsStore.getState().settings;
if (appService?.hasWindow && settings.openBookInNewWindow) {
showLibraryWindow(appService, [filePath]);
} else {
window.OPEN_WITH_FILES = [filePath];
setCheckOpenWithBooks(true);
navigateToLibrary(router, `reload=${Date.now()}`);
}
}
};
useEffect(() => {
if (!isTauriAppPlatform()) return;
if (!isTauriAppPlatform() || !appService) return;
if (listenedOpenWithBooks.current) return;
listenedOpenWithBooks.current = true;
@@ -54,5 +62,5 @@ export function useOpenWithBooks() {
unlistenOpenUrl.then((f) => f());
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [appService]);
}
@@ -298,6 +298,7 @@ export abstract class BaseAppService implements AppService {
if (includingLocal) {
book.deletedAt = Date.now();
book.downloadedAt = null;
book.coverDownloadedAt = null;
}
if (includingUploaded) {
book.uploadedAt = null;
@@ -357,6 +358,7 @@ export abstract class BaseAppService implements AppService {
book.updatedAt = Date.now();
book.uploadedAt = Date.now();
book.downloadedAt = Date.now();
book.coverDownloadedAt = Date.now();
} else {
throw new Error('Book file not uploaded');
}
@@ -379,6 +381,7 @@ export abstract class BaseAppService implements AppService {
async downloadBook(book: Book, onlyCover = false, onProgress?: ProgressHandler): Promise<void> {
let bookDownloaded = false;
let bookCoverDownloaded = false;
const completedFiles = { count: 0 };
let toDownloadFpCount = 0;
const needDownCover = !(await this.fs.exists(getCoverFilename(book), 'Books'));
@@ -402,6 +405,7 @@ export abstract class BaseAppService implements AppService {
const cfp = `${CLOUD_BOOKS_SUBDIR}/${lfp}`;
await this.downloadCloudFile(lfp, cfp, handleProgress);
completedFiles.count++;
bookCoverDownloaded = true;
}
} catch (error) {
// don't throw error here since some books may not have cover images at all
@@ -420,6 +424,9 @@ export abstract class BaseAppService implements AppService {
if (bookDownloaded || (!onlyCover && !needDownBook)) {
book.downloadedAt = Date.now();
}
if ((bookCoverDownloaded || !needDownCover) && !book.coverDownloadedAt) {
book.coverDownloadedAt = Date.now();
}
}
async isBookAvailable(book: Book): Promise<boolean> {
@@ -40,6 +40,7 @@ export const DEFAULT_SYSTEM_SETTINGS: Partial<SystemSettings> = {
keepLogin: false,
autoUpload: true,
alwaysOnTop: false,
openBookInNewWindow: false,
alwaysShowStatusBar: false,
autoCheckUpdates: true,
screenWakeLock: false,
+1
View File
@@ -26,6 +26,7 @@ export interface Book {
uploadedAt?: number | null;
downloadedAt?: number | null;
coverDownloadedAt?: number | null;
lastUpdated?: number; // deprecated in favor of updatedAt
progress?: [number, number]; // Add progress field: [current, total], 1-based page number
+1
View File
@@ -27,6 +27,7 @@ export interface SystemSettings {
keepLogin: boolean;
autoUpload: boolean;
alwaysOnTop: boolean;
openBookInNewWindow: boolean;
autoCheckUpdates: boolean;
screenWakeLock: boolean;
alwaysShowStatusBar: boolean;
+42
View File
@@ -1,6 +1,48 @@
import { useRouter, redirect } from 'next/navigation';
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
import { isPWA, isWebAppPlatform } from '@/services/environment';
import { BOOK_IDS_SEPARATOR } from '@/services/constants';
import { AppService } from '@/types/system';
let readerWindowsCount = 0;
const createReaderWindow = (appService: AppService, url: string) => {
const win = new WebviewWindow(`reader-${readerWindowsCount}`, {
url,
width: 800,
height: 600,
center: true,
resizable: true,
title: appService.isMacOSApp ? '' : 'Readest',
decorations: appService.isMacOSApp ? true : false,
transparent: appService.isMacOSApp ? false : true,
shadow: appService.isMacOSApp ? undefined : true,
titleBarStyle: appService.isMacOSApp ? 'overlay' : undefined,
});
win.once('tauri://created', () => {
console.log('new window created');
readerWindowsCount += 1;
});
win.once('tauri://error', (e) => {
console.error('error creating window', e);
});
win.once('tauri://destroyed', () => {
readerWindowsCount -= 1;
});
};
export const showReaderWindow = (appService: AppService, bookIds: string[]) => {
const ids = bookIds.join(BOOK_IDS_SEPARATOR);
const params = new URLSearchParams('');
params.set('ids', ids);
const url = `/reader?${params.toString()}`;
createReaderWindow(appService, url);
};
export const showLibraryWindow = (appService: AppService, filenames: string[]) => {
const files = filenames.map((file) => `${encodeURIComponent(file)}`).join(',');
const url = `/library?files=${files}`;
createReaderWindow(appService, url);
};
export const navigateToReader = (
router: ReturnType<typeof useRouter>,
+7 -3
View File
@@ -1,5 +1,5 @@
import { getCurrentWindow } from '@tauri-apps/api/window';
import { TauriEvent } from '@tauri-apps/api/event';
import { emitTo, TauriEvent } from '@tauri-apps/api/event';
import { exit } from '@tauri-apps/plugin-process';
import { eventDispatcher } from './event';
@@ -33,8 +33,12 @@ export const tauriHandleOnCloseWindow = async (callback: () => void) => {
const currentWindow = getCurrentWindow();
return currentWindow.listen(TauriEvent.WINDOW_CLOSE_REQUESTED, async () => {
await callback();
console.log('exit app');
await exit(0);
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();
}
});
};