forked from akai/readest
This commit is contained in:
@@ -164,9 +164,11 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
|
||||
useEffect(() => {
|
||||
const libraryPage = document.querySelector('.library-page');
|
||||
libraryPage?.addEventListener('dragover', handleDragOver as unknown as EventListener);
|
||||
libraryPage?.addEventListener('dragleave', handleDragLeave as unknown as EventListener);
|
||||
libraryPage?.addEventListener('drop', handleDrop as unknown as EventListener);
|
||||
if (!appService?.isMobile) {
|
||||
libraryPage?.addEventListener('dragover', handleDragOver as unknown as EventListener);
|
||||
libraryPage?.addEventListener('dragleave', handleDragLeave as unknown as EventListener);
|
||||
libraryPage?.addEventListener('drop', handleDrop as unknown as EventListener);
|
||||
}
|
||||
|
||||
if (isTauriAppPlatform()) {
|
||||
const unlisten = getCurrentWebview().onDragDropEvent((event) => {
|
||||
@@ -185,9 +187,11 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
}
|
||||
|
||||
return () => {
|
||||
libraryPage?.removeEventListener('dragover', handleDragOver as unknown as EventListener);
|
||||
libraryPage?.removeEventListener('dragleave', handleDragLeave as unknown as EventListener);
|
||||
libraryPage?.removeEventListener('drop', handleDrop as unknown as EventListener);
|
||||
if (!appService?.isMobile) {
|
||||
libraryPage?.removeEventListener('dragover', handleDragOver as unknown as EventListener);
|
||||
libraryPage?.removeEventListener('dragleave', handleDragLeave as unknown as EventListener);
|
||||
libraryPage?.removeEventListener('drop', handleDrop as unknown as EventListener);
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [pageRef.current]);
|
||||
@@ -337,7 +341,8 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
const selectFilesTauri = async () => {
|
||||
const exts = appService?.isAndroidApp ? [] : SUPPORTED_FILE_EXTS;
|
||||
const files = (await appService?.selectFiles(_('Select Books'), exts)) || [];
|
||||
return files.filter((file) => SUPPORTED_FILE_EXTS.some((ext) => file.endsWith(`.${ext}`)));
|
||||
// Cannot filter out files on Android since some content providers may not return the file name
|
||||
return files;
|
||||
};
|
||||
|
||||
const selectFilesWeb = () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AppPlatform, AppService, ToastType } from '@/types/system';
|
||||
import { AppPlatform, AppService } from '@/types/system';
|
||||
|
||||
import { SystemSettings } from '@/types/settings';
|
||||
import { FileSystem, BaseDir } from '@/types/system';
|
||||
@@ -62,13 +62,9 @@ export abstract class BaseAppService implements AppService {
|
||||
abstract getCoverImageUrl(book: Book): string;
|
||||
abstract getCoverImageBlobUrl(book: Book): Promise<string>;
|
||||
abstract getInitBooksDir(): Promise<string>;
|
||||
abstract getCacheDir(): Promise<string>;
|
||||
abstract selectDirectory(): Promise<string>;
|
||||
abstract selectFiles(name: string, extensions: string[]): Promise<string[]>;
|
||||
abstract showMessage(
|
||||
msg: string,
|
||||
kind?: ToastType,
|
||||
title?: string,
|
||||
okLabel?: string,
|
||||
): Promise<void>;
|
||||
|
||||
async loadSettings(): Promise<SystemSettings> {
|
||||
let settings: SystemSettings;
|
||||
@@ -118,9 +114,12 @@ export abstract class BaseAppService implements AppService {
|
||||
}
|
||||
|
||||
this.localBooksDir = settings.localBooksDir;
|
||||
const cacheDir = await this.getCacheDir();
|
||||
this.fs.getPrefix = (baseDir: BaseDir) => {
|
||||
if (baseDir === 'Books') {
|
||||
return this.localBooksDir;
|
||||
} else if (baseDir === 'Cache') {
|
||||
return cacheDir;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -13,12 +13,12 @@ import {
|
||||
WriteFileOptions,
|
||||
} from '@tauri-apps/plugin-fs';
|
||||
import { convertFileSrc } from '@tauri-apps/api/core';
|
||||
import { open as openDialog, message } from '@tauri-apps/plugin-dialog';
|
||||
import { join, appDataDir } from '@tauri-apps/api/path';
|
||||
import { open as openDialog } from '@tauri-apps/plugin-dialog';
|
||||
import { join, appDataDir, appCacheDir } from '@tauri-apps/api/path';
|
||||
import { type as osType } from '@tauri-apps/plugin-os';
|
||||
|
||||
import { Book } from '@/types/book';
|
||||
import { ToastType, FileSystem, BaseDir, AppPlatform } from '@/types/system';
|
||||
import { FileSystem, BaseDir, AppPlatform } from '@/types/system';
|
||||
import { isContentURI, isValidURL } from '@/utils/misc';
|
||||
import { getCoverFilename, getFilename } from '@/utils/book';
|
||||
import { copyURIToPath } from '@/utils/bridge';
|
||||
@@ -80,7 +80,22 @@ export const nativeFileSystem: FileSystem = {
|
||||
if (isValidURL(path)) {
|
||||
return await new RemoteFile(path, name).open();
|
||||
} else if (isContentURI(path)) {
|
||||
return await new NativeFile(fp, fname, base ? baseDir : null).open();
|
||||
if (path.includes('com.android.externalstorage')) {
|
||||
// If the URI is from shared internal storage (like /storage/emulated/0),
|
||||
// we can access it directly using the path — no need to copy.
|
||||
return await new NativeFile(fp, fname, base ? baseDir : null).open();
|
||||
} else {
|
||||
// Otherwise, for content:// URIs (e.g. from MediaStore, Drive, or third-party apps),
|
||||
// we cannot access the file directly — so we copy it to a temporary cache location.
|
||||
const prefix = this.getPrefix('Cache');
|
||||
const dst = `${prefix}/${fname}`;
|
||||
const res = await copyURIToPath({ uri: path, dst });
|
||||
if (!res.success) {
|
||||
console.error('Failed to open file:', res);
|
||||
throw new Error('Failed to open file');
|
||||
}
|
||||
return await new NativeFile(dst, fname, base ? baseDir : null).open();
|
||||
}
|
||||
} else {
|
||||
const prefix = this.getPrefix(base);
|
||||
if (prefix && OS_TYPE !== 'android') {
|
||||
@@ -213,6 +228,18 @@ export class NativeAppService extends BaseAppService {
|
||||
return join(await appDataDir(), LOCAL_BOOKS_SUBDIR);
|
||||
}
|
||||
|
||||
async getCacheDir(): Promise<string> {
|
||||
return await appCacheDir();
|
||||
}
|
||||
|
||||
async selectDirectory(): Promise<string> {
|
||||
const selected = await openDialog({
|
||||
directory: true,
|
||||
multiple: false,
|
||||
});
|
||||
return selected as string;
|
||||
}
|
||||
|
||||
async selectFiles(name: string, extensions: string[]): Promise<string[]> {
|
||||
const selected = await openDialog({
|
||||
multiple: true,
|
||||
@@ -221,15 +248,6 @@ export class NativeAppService extends BaseAppService {
|
||||
return Array.isArray(selected) ? selected : selected ? [selected] : [];
|
||||
}
|
||||
|
||||
async showMessage(
|
||||
msg: string,
|
||||
kind: ToastType = 'info',
|
||||
title?: string,
|
||||
okLabel?: string,
|
||||
): Promise<void> {
|
||||
await message(msg, { kind, title, okLabel });
|
||||
}
|
||||
|
||||
getCoverImageUrl = (book: Book): string => {
|
||||
return this.fs.getURL(`${this.localBooksDir}/${getCoverFilename(book)}`);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Book } from '@/types/book';
|
||||
import { ToastType, FileSystem, BaseDir, AppPlatform } from '@/types/system';
|
||||
import { FileSystem, BaseDir, AppPlatform } from '@/types/system';
|
||||
import { getCoverFilename } from '@/utils/book';
|
||||
import { getOSPlatform, isValidURL } from '@/utils/misc';
|
||||
import { RemoteFile } from '@/utils/file';
|
||||
@@ -212,6 +212,10 @@ export class WebAppService extends BaseAppService {
|
||||
return LOCAL_BOOKS_SUBDIR;
|
||||
}
|
||||
|
||||
async getCacheDir(): Promise<string> {
|
||||
return 'Cache';
|
||||
}
|
||||
|
||||
async selectDirectory(): Promise<string> {
|
||||
throw new Error('selectDirectory is not supported in browser');
|
||||
}
|
||||
@@ -220,10 +224,6 @@ export class WebAppService extends BaseAppService {
|
||||
throw new Error('selectFiles is not supported in browser');
|
||||
}
|
||||
|
||||
async showMessage(msg: string, kind: ToastType = 'info'): Promise<void> {
|
||||
alert(`${kind.toUpperCase()}: ${msg}`);
|
||||
}
|
||||
|
||||
getCoverImageUrl = (book: Book): string => {
|
||||
return this.fs.getURL(`${LOCAL_BOOKS_SUBDIR}/${getCoverFilename(book)}`);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ import { ProgressHandler } from '@/utils/transfer';
|
||||
|
||||
export type AppPlatform = 'web' | 'tauri';
|
||||
export type BaseDir = 'Books' | 'Settings' | 'Data' | 'Log' | 'Cache' | 'None';
|
||||
export type ToastType = 'info' | 'warning' | 'error';
|
||||
|
||||
export interface FileSystem {
|
||||
getURL(path: string): string;
|
||||
@@ -39,9 +38,8 @@ export interface AppService {
|
||||
isAndroidApp: boolean;
|
||||
isIOSApp: boolean;
|
||||
|
||||
selectDirectory(): Promise<string>;
|
||||
selectFiles(name: string, extensions: string[]): Promise<string[]>;
|
||||
showMessage(msg: string, kind?: ToastType, title?: string, okLabel?: string): Promise<void>;
|
||||
|
||||
loadSettings(): Promise<SystemSettings>;
|
||||
saveSettings(settings: SystemSettings): Promise<void>;
|
||||
importBook(
|
||||
|
||||
Reference in New Issue
Block a user