forked from akai/readest
@@ -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 =
|
||||
|
||||
@@ -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'
|
||||
>
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,6 +27,7 @@ export interface SystemSettings {
|
||||
keepLogin: boolean;
|
||||
autoUpload: boolean;
|
||||
alwaysOnTop: boolean;
|
||||
openBookInNewWindow: boolean;
|
||||
autoCheckUpdates: boolean;
|
||||
screenWakeLock: boolean;
|
||||
alwaysShowStatusBar: boolean;
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user