feat: support deleting only the cloud backup of a book (#1546)
* feat: support delete the cloud storage of a book * feat: merge to delete function * fix: change a logic
This commit is contained in:
@@ -127,7 +127,9 @@
|
||||
"Upload Book": "上传书籍",
|
||||
"Auto Upload Books to Cloud": "自动上传书籍到云端",
|
||||
"Book deleted: {{title}}": "书籍已删除:{{title}}",
|
||||
"Book's cloud store deleted: {{title}}": "书籍的云端储存已删除:{{title}}",
|
||||
"Failed to delete book: {{title}}": "删除书籍失败:{{title}}",
|
||||
"Failed to delete book's cloud store: {{title}}": "删除书籍的云端储存失败:{{title}}",
|
||||
"Check Updates on Start": "启动时检查更新",
|
||||
"Insufficient storage quota": "云存储空间不足",
|
||||
"Font Weight": "字重",
|
||||
@@ -281,6 +283,7 @@
|
||||
"Previous Page": "上一页",
|
||||
"Are you sure to delete {{count}} selected book(s)?_other": "您确定要删除 {{count}} 本选中的书籍吗?",
|
||||
"Are you sure to delete the selected book?": "您确定要删除选中的书籍吗?",
|
||||
"Are you sure to delete the selected book's cloud store?": "您确定要删除选中书籍的云端储存吗?",
|
||||
"Deselect": "取消选择",
|
||||
"Select All": "全选",
|
||||
"No translation available.": "暂无可用的翻译",
|
||||
|
||||
@@ -508,7 +508,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
|
||||
const handleBookDelete = async (book: Book) => {
|
||||
try {
|
||||
await appService?.deleteBook(book, !!book.uploadedAt);
|
||||
await appService?.deleteBook(book, !!book.uploadedAt, true);
|
||||
await updateBook(envConfig, book);
|
||||
pushLibrary();
|
||||
eventDispatcher.dispatch('toast', {
|
||||
@@ -530,6 +530,31 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
}
|
||||
};
|
||||
|
||||
const handleBookDeleteCloudStore = async (book: Book) => {
|
||||
try {
|
||||
await appService?.deleteBook(book, !!book.uploadedAt, false);
|
||||
await updateBook(envConfig, book);
|
||||
pushLibrary();
|
||||
eventDispatcher.dispatch('toast', {
|
||||
type: 'info',
|
||||
timeout: 2000,
|
||||
message: _('Book\'s cloud store deleted: {{title}}', {
|
||||
title: book.title,
|
||||
}),
|
||||
});
|
||||
return true;
|
||||
} catch (e){
|
||||
console.error(e);
|
||||
eventDispatcher.dispatch('toast', {
|
||||
message: _('Failed to delete book\'s cloud store: {{title}}', {
|
||||
title: book.title,
|
||||
}),
|
||||
type: 'error',
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleImportBooks = async () => {
|
||||
setIsSelectMode(false);
|
||||
console.log('Importing books...');
|
||||
@@ -659,6 +684,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
|
||||
handleBookUpload={handleBookUpload}
|
||||
handleBookDownload={handleBookDownload}
|
||||
handleBookDelete={handleBookDelete}
|
||||
handleBookDeleteCloudStore={handleBookDeleteCloudStore}
|
||||
/>
|
||||
)}
|
||||
<AboutWindow />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { MdDelete, MdCloudDownload, MdCloudUpload } from 'react-icons/md';
|
||||
import { MdOutlineDelete, MdOutlineCloudDownload, MdOutlineCloudUpload, MdOutlineCloudOff } from 'react-icons/md';
|
||||
|
||||
import { Book } from '@/types/book';
|
||||
import { BookDoc } from '@/libs/document';
|
||||
@@ -28,6 +28,7 @@ interface BookDetailModalProps {
|
||||
handleBookDownload?: (book: Book) => void;
|
||||
handleBookUpload?: (book: Book) => void;
|
||||
handleBookDelete?: (book: Book) => void;
|
||||
handleBookDeleteCloudStore?: (book: Book) => void;
|
||||
}
|
||||
|
||||
const BookDetailModal = ({
|
||||
@@ -37,10 +38,12 @@ const BookDetailModal = ({
|
||||
handleBookDownload,
|
||||
handleBookUpload,
|
||||
handleBookDelete,
|
||||
handleBookDeleteCloudStore,
|
||||
}: BookDetailModalProps) => {
|
||||
const _ = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [showDeleteAlert, setShowDeleteAlert] = useState(false);
|
||||
const [showDeleteCloudStoreAlert, setShowDeleteCloudStoreAlert] = useState(false);
|
||||
const [bookMeta, setBookMeta] = useState<BookDoc['metadata'] | null>(null);
|
||||
const [fileSize, setFileSize] = useState<number | null>(null);
|
||||
const { envConfig } = useEnv();
|
||||
@@ -73,6 +76,10 @@ const BookDetailModal = ({
|
||||
setShowDeleteAlert(true);
|
||||
};
|
||||
|
||||
const handleDeleteCloudStore = () => {
|
||||
setShowDeleteCloudStoreAlert(true);
|
||||
};
|
||||
|
||||
const confirmDelete = async () => {
|
||||
handleClose();
|
||||
setShowDeleteAlert(false);
|
||||
@@ -81,6 +88,14 @@ const BookDetailModal = ({
|
||||
}
|
||||
};
|
||||
|
||||
const confirmDeleteCloudStore = async () => {
|
||||
handleClose();
|
||||
setShowDeleteCloudStoreAlert(false);
|
||||
if (handleBookDeleteCloudStore) {
|
||||
handleBookDeleteCloudStore(book);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRedownload = async () => {
|
||||
handleClose();
|
||||
if (handleBookDownload) {
|
||||
@@ -132,17 +147,22 @@ const BookDetailModal = ({
|
||||
<div className='flex flex-wrap items-center gap-x-4'>
|
||||
{handleBookDelete && (
|
||||
<button onClick={handleDelete}>
|
||||
<MdDelete className='fill-red-500' />
|
||||
<MdOutlineDelete className='fill-red-500' />
|
||||
</button>
|
||||
)}
|
||||
{book.uploadedAt && handleBookDeleteCloudStore && (
|
||||
<button onClick={handleDeleteCloudStore}>
|
||||
<MdOutlineCloudOff className='fill-red-500' />
|
||||
</button>
|
||||
)}
|
||||
{book.uploadedAt && handleBookDownload && (
|
||||
<button onClick={handleRedownload}>
|
||||
<MdCloudDownload className='fill-base-content' />
|
||||
<MdOutlineCloudDownload className='fill-base-content' />
|
||||
</button>
|
||||
)}
|
||||
{book.downloadedAt && handleBookUpload && (
|
||||
<button onClick={handleReupload}>
|
||||
<MdCloudUpload className='fill-base-content' />
|
||||
<MdOutlineCloudUpload className='fill-base-content' />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -221,6 +241,23 @@ const BookDetailModal = ({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{showDeleteCloudStoreAlert && (
|
||||
<div
|
||||
className={clsx(
|
||||
'fixed bottom-0 left-0 right-0 z-50 flex justify-center',
|
||||
'pb-[calc(env(safe-area-inset-bottom)+16px)]',
|
||||
)}
|
||||
>
|
||||
<Alert
|
||||
title={_('Confirm Deletion')}
|
||||
message={_('Are you sure to delete the selected book\'s cloud store?')}
|
||||
onCancel={() => {
|
||||
setShowDeleteAlert(false);
|
||||
}}
|
||||
onConfirm={confirmDeleteCloudStore}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -272,16 +272,18 @@ export abstract class BaseAppService implements AppService {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteBook(book: Book, includingUploaded = false): Promise<void> {
|
||||
const fps = [getRemoteBookFilename(book), getCoverFilename(book)];
|
||||
const localDeleteFps = [getLocalBookFilename(book), getCoverFilename(book)];
|
||||
for (const fp of localDeleteFps) {
|
||||
if (await this.fs.exists(fp, 'Books')) {
|
||||
await this.fs.removeFile(fp, 'Books');
|
||||
async deleteBook(book: Book, includingUploaded = false, includingLocal = true): Promise<void> {
|
||||
if (includingLocal) {
|
||||
const localDeleteFps = [getLocalBookFilename(book), getCoverFilename(book)];
|
||||
for (const fp of localDeleteFps) {
|
||||
if (await this.fs.exists(fp, 'Books')) {
|
||||
await this.fs.removeFile(fp, 'Books');
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const fp of fps) {
|
||||
if (includingUploaded) {
|
||||
if (includingUploaded) {
|
||||
const fps = [getRemoteBookFilename(book), getCoverFilename(book)];
|
||||
for (const fp of fps) {
|
||||
console.log('Deleting uploaded file:', fp);
|
||||
const cfp = `${CLOUD_BOOKS_SUBDIR}/${fp}`;
|
||||
try {
|
||||
@@ -291,8 +293,11 @@ export abstract class BaseAppService implements AppService {
|
||||
}
|
||||
}
|
||||
}
|
||||
book.deletedAt = Date.now();
|
||||
book.downloadedAt = null;
|
||||
|
||||
if (includingLocal) {
|
||||
book.deletedAt = Date.now();
|
||||
book.downloadedAt = null;
|
||||
}
|
||||
if (includingUploaded) {
|
||||
book.uploadedAt = null;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export interface AppService {
|
||||
overwrite?: boolean,
|
||||
transient?: boolean,
|
||||
): Promise<Book | null>;
|
||||
deleteBook(book: Book, includingUploaded?: boolean): Promise<void>;
|
||||
deleteBook(book: Book, includingUploaded?: boolean, includingLocal?: boolean): Promise<void>;
|
||||
uploadBook(book: Book, onProgress?: ProgressHandler): Promise<void>;
|
||||
downloadBook(book: Book, onlyCover?: boolean, onProgress?: ProgressHandler): Promise<void>;
|
||||
isBookAvailable(book: Book): Promise<boolean>;
|
||||
|
||||
Reference in New Issue
Block a user