forked from akai/readest
refactor: use useFileSelector hook for all file selection (#1905)
This commit is contained in:
@@ -18,7 +18,7 @@ import { getFilename } from '@/utils/path';
|
||||
import { parseOpenWithFiles } from '@/helpers/openWith';
|
||||
import { isTauriAppPlatform, isWebAppPlatform } from '@/services/environment';
|
||||
import { checkForAppUpdates, checkAppReleaseNotes } from '@/helpers/updater';
|
||||
import { BOOK_ACCEPT_FORMATS, SUPPORTED_BOOK_EXTS } from '@/services/constants';
|
||||
import { BOOK_ACCEPT_FORMATS } from '@/services/constants';
|
||||
import { impactFeedback } from '@tauri-apps/plugin-haptics';
|
||||
import { getCurrentWebview } from '@tauri-apps/api/webview';
|
||||
|
||||
@@ -35,6 +35,7 @@ import { useDemoBooks } from './hooks/useDemoBooks';
|
||||
import { useBooksSync } from './hooks/useBooksSync';
|
||||
import { useScreenWakeLock } from '@/hooks/useScreenWakeLock';
|
||||
import { useOpenWithBooks } from '@/hooks/useOpenWithBooks';
|
||||
import { FILE_SELECTION_PRESETS, SelectedFile, useFileSelector } from '@/hooks/useFileSelector';
|
||||
import { lockScreenOrientation } from '@/utils/bridge';
|
||||
import {
|
||||
tauriHandleSetAlwaysOnTop,
|
||||
@@ -73,6 +74,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
setCheckLastOpenBooks,
|
||||
} = useLibraryStore();
|
||||
const _ = useTranslation();
|
||||
const { selectFiles } = useFileSelector(appService, _);
|
||||
const { safeAreaInsets: insets } = useThemeStore();
|
||||
const { settings, setSettings, saveSettings } = useSettingsStore();
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -164,7 +166,14 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
impactFeedback('medium');
|
||||
}
|
||||
|
||||
await importBooks(supportedFiles);
|
||||
const selectedFiles = supportedFiles.map(
|
||||
(file) =>
|
||||
({
|
||||
file: typeof file === 'string' ? undefined : file,
|
||||
path: typeof file === 'string' ? file : undefined,
|
||||
}) as SelectedFile,
|
||||
);
|
||||
await importBooks(selectedFiles);
|
||||
};
|
||||
|
||||
const handleDragOver = (event: React.DragEvent<HTMLDivElement> | DragEvent) => {
|
||||
@@ -392,7 +401,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [demoBooks, libraryLoaded]);
|
||||
|
||||
const importBooks = async (files: (string | File)[]) => {
|
||||
const importBooks = async (files: SelectedFile[]) => {
|
||||
setLoading(true);
|
||||
const failedFiles = [];
|
||||
const errorMap: [string, string][] = [
|
||||
@@ -401,7 +410,9 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
['Unsupported format.', _('This book format is not supported.')],
|
||||
];
|
||||
const { library } = useLibraryStore.getState();
|
||||
for (const file of files) {
|
||||
for (const selectedFile of files) {
|
||||
const file = selectedFile.file || selectedFile.path;
|
||||
if (!file) continue;
|
||||
try {
|
||||
const book = await appService?.importBook(file, library);
|
||||
setLibrary([...library]);
|
||||
@@ -431,32 +442,6 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const selectFilesTauri = async () => {
|
||||
const exts = appService?.isIOSApp ? [] : SUPPORTED_BOOK_EXTS;
|
||||
const files = (await appService?.selectFiles(_('Select Books'), exts)) || [];
|
||||
if (appService?.isIOSApp) {
|
||||
return files.filter((file) => {
|
||||
const fileExt = file.split('.').pop()?.toLowerCase() || 'unknown';
|
||||
return SUPPORTED_BOOK_EXTS.includes(fileExt);
|
||||
});
|
||||
}
|
||||
return files;
|
||||
};
|
||||
|
||||
const selectFilesWeb = () => {
|
||||
return new Promise((resolve) => {
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = BOOK_ACCEPT_FORMATS;
|
||||
fileInput.multiple = true;
|
||||
fileInput.click();
|
||||
|
||||
fileInput.onchange = () => {
|
||||
resolve(fileInput.files);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const updateBookTransferProgress = throttle((bookHash: string, progress: ProgressPayload) => {
|
||||
if (progress.total === 0) return;
|
||||
const progressPct = (progress.progress / progress.total) * 100;
|
||||
@@ -605,14 +590,10 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
const handleImportBooks = async () => {
|
||||
setIsSelectMode(false);
|
||||
console.log('Importing books...');
|
||||
let files;
|
||||
|
||||
if (isTauriAppPlatform()) {
|
||||
files = (await selectFilesTauri()) as string[];
|
||||
} else {
|
||||
files = (await selectFilesWeb()) as File[];
|
||||
}
|
||||
importBooks(files);
|
||||
selectFiles({ ...FILE_SELECTION_PRESETS.books, multiple: true }).then((result) => {
|
||||
if (result.files.length === 0 || result.error) return;
|
||||
importBooks(result.files);
|
||||
});
|
||||
};
|
||||
|
||||
const handleSetSelectMode = (selectMode: boolean) => {
|
||||
|
||||
@@ -8,8 +8,7 @@ import { useEnv } from '@/context/EnvContext';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { flattenContributors, formatAuthors, formatPublisher, formatTitle } from '@/utils/book';
|
||||
import { FormField } from './FormField';
|
||||
import { IMAGE_ACCEPT_FORMATS, SUPPORTED_IMAGE_EXTS } from '@/services/constants';
|
||||
import { isTauriAppPlatform } from '@/services/environment';
|
||||
import { FILE_SELECTION_PRESETS, useFileSelector } from '@/hooks/useFileSelector';
|
||||
import BookCover from '@/components/BookCover';
|
||||
|
||||
interface BookDetailEditProps {
|
||||
@@ -47,6 +46,7 @@ const BookDetailEdit: React.FC<BookDetailEditProps> = ({
|
||||
}) => {
|
||||
const _ = useTranslation();
|
||||
const { appService } = useEnv();
|
||||
const { selectFiles } = useFileSelector(appService, _);
|
||||
|
||||
const hasLockedFields = Object.values(lockedFields).some((locked) => locked);
|
||||
const allFieldsLocked = Object.values(lockedFields).every((locked) => locked);
|
||||
@@ -148,51 +148,23 @@ const BookDetailEdit: React.FC<BookDetailEditProps> = ({
|
||||
},
|
||||
];
|
||||
|
||||
const selectImageFileWeb = () => {
|
||||
return new Promise((resolve) => {
|
||||
const fileInput = document.createElement('input');
|
||||
fileInput.type = 'file';
|
||||
fileInput.accept = IMAGE_ACCEPT_FORMATS;
|
||||
fileInput.multiple = false;
|
||||
fileInput.click();
|
||||
|
||||
fileInput.onchange = () => {
|
||||
resolve(fileInput.files);
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const selectImageFileTauri = async () => {
|
||||
const exts = appService?.isMobileApp ? [] : SUPPORTED_IMAGE_EXTS;
|
||||
const files = (await appService?.selectFiles(_('Select Cover Image'), exts)) || [];
|
||||
if (appService?.isIOSApp) {
|
||||
return files.filter((file) => {
|
||||
const fileExt = file.split('.').pop()?.toLowerCase() || 'unknown';
|
||||
return SUPPORTED_IMAGE_EXTS.includes(fileExt);
|
||||
});
|
||||
}
|
||||
return files;
|
||||
};
|
||||
|
||||
const handleSelectLocalImage = async () => {
|
||||
let files;
|
||||
if (isTauriAppPlatform()) {
|
||||
files = (await selectImageFileTauri()) as string[];
|
||||
if (appService && files.length > 0) {
|
||||
metadata.coverImageFile = files[0]!;
|
||||
selectFiles({ ...FILE_SELECTION_PRESETS.covers, multiple: false }).then(async (result) => {
|
||||
if (result.error || result.files.length === 0) return;
|
||||
const selectedFile = result.files[0]!;
|
||||
if (selectedFile.path && appService) {
|
||||
const filePath = selectedFile.path;
|
||||
metadata.coverImageFile = filePath;
|
||||
const tempName = `cover-${Date.now()}.png`;
|
||||
const cachePrefix = await appService.fs.getPrefix('Cache');
|
||||
await appService.fs.copyFile(files[0]!, tempName, 'Cache');
|
||||
await appService.fs.copyFile(filePath, tempName, 'Cache');
|
||||
metadata.coverImageUrl = await appService.fs.getURL(`${cachePrefix}/${tempName}`);
|
||||
setNewCoverImageUrl(metadata.coverImageUrl!);
|
||||
}
|
||||
} else {
|
||||
files = (await selectImageFileWeb()) as File[];
|
||||
if (files.length > 0) {
|
||||
metadata.coverImageBlobUrl = URL.createObjectURL(files[0]!);
|
||||
} else if (selectedFile.file) {
|
||||
metadata.coverImageBlobUrl = URL.createObjectURL(selectedFile.file);
|
||||
setNewCoverImageUrl(metadata.coverImageBlobUrl!);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -71,7 +71,7 @@ const processTauriFiles = (files: string[]): SelectedFile[] => {
|
||||
export const useFileSelector = (appService: AppService | null, _: (key: string) => string) => {
|
||||
const selectFiles = async (options: FileSelectorOptions = {}) => {
|
||||
if (!appService) {
|
||||
return { files: [], error: 'App service is not available' };
|
||||
return { files: [] as SelectedFile[], error: 'App service is not available' };
|
||||
}
|
||||
try {
|
||||
if (isTauriAppPlatform()) {
|
||||
@@ -121,4 +121,9 @@ export const FILE_SELECTION_PRESETS = {
|
||||
extensions: ['ttf', 'otf', 'woff', 'woff2'],
|
||||
dialogTitle: _('Select Fonts'),
|
||||
},
|
||||
covers: {
|
||||
accept: '.png, .jpg, .jpeg, .gif',
|
||||
extensions: ['png', 'jpg', 'jpeg', 'gif'],
|
||||
dialogTitle: _('Select Image'),
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user