Skip to content

Commit

Permalink
feat: added support for remove datasource books
Browse files Browse the repository at this point in the history
  • Loading branch information
mbret committed Mar 16, 2024
1 parent 3afa7a6 commit 08b6937
Show file tree
Hide file tree
Showing 37 changed files with 516 additions and 464 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { useObservers } from "./rxdb/sync/useObservers"
import { PreloadQueries } from "./PreloadQueries"
import { SplashScreen } from "./SplashScreen"
import { FirstTimeExperienceTours } from "./firstTimeExperience/FirstTimeExperienceTours"
import { DialogProvider } from "./common/dialog"
import { BlurContainer } from "./books/BlurContainer"
import "./i18n"
import { ErrorBoundary } from "@sentry/react"
Expand All @@ -32,6 +31,7 @@ import { AuthorizeActionDialog } from "./auth/AuthorizeActionDialog"
import { profileStorageSignal } from "./profile/storage"
import { authSignalStorageAdapter } from "./auth/storage"
import { authStateSignal } from "./auth/authState"
import { DialogProvider } from "./common/dialogs/DialogProvider"

declare module "@mui/styles/defaultTheme" {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
Expand Down
5 changes: 4 additions & 1 deletion packages/web/src/auth/AuthorizeActionDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { Controller, useForm } from "react-hook-form"
import { errorToHelperText } from "../common/forms/errorToHelperText"
import { signal, useSignalValue } from "reactjrx"
import { Observable, from, mergeMap } from "rxjs"
import { CancelError } from "../errors"
import { CancelError } from "../common/errors/errors"

const FORM_ID = "LockActionBehindUserPasswordDialog"

Expand All @@ -31,6 +31,9 @@ export const authorizeAction = (action: () => void, onCancel?: () => void) =>
onCancel
})

/**
* add check if user has pass code or not and if not just ignore
*/
export function withAuthorization<T>(stream: Observable<T>) {
return stream.pipe(
mergeMap(() =>
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/auth/LoginScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Alert } from "@mui/material"
import { useTranslation } from "react-i18next"
import { Google } from "@mui/icons-material"
import { useSignIn } from "./useSignIn"
import { ErrorMessage, isCancelError } from "../errors"
import { ErrorMessage, isCancelError } from "../common/errors/errors"
import { OrDivider } from "../common/OrDivider"
import { links } from "@oboku/shared"
import { useMutation } from "reactjrx"
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/auth/useSignIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useCallback } from "react"
import { catchError, finalize, from, of, switchMap, tap } from "rxjs"
import { lock, unlock } from "../common/BlockingBackdrop"
import { API_URI } from "../constants"
import { CancelError } from "../errors"
import { CancelError } from "../common/errors/errors"
import { useReCreateDb } from "../rxdb"
import { authStateSignal } from "./authState"
import { httpClient } from "../http/httpClient"
Expand Down
5 changes: 2 additions & 3 deletions packages/web/src/books/details/DataSourceSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,16 @@ import { MoreVertRounded } from "@mui/icons-material"
import { FC, useState } from "react"
import { useDataSourcePlugin } from "../../dataSources/helpers"
import { Report } from "../../debug/report.shared"
import { useDialogManager } from "../../common/dialog"
import { useBookLinksState } from "../states"
import { useCreateRequestPopupDialog } from "../../plugins/useCreateRequestPopupDialog"
import { upsertBookLink } from "../triggers"
import { useTagsByIds } from "../../tags/helpers"
import { createDialog } from "../../common/dialogs/createDialog"

export const DataSourceSection: FC<{ bookId: string }> = ({ bookId }) => {
const link = useBookLinksState({ bookId, tags: useTagsByIds().data })[0]
const dataSourcePlugin = useDataSourcePlugin(link?.type)
const [isSelectItemOpened, setIsSelectItemOpened] = useState(false)
const dialog = useDialogManager()
const createRequestPopupDialog = useCreateRequestPopupDialog()

return (
Expand All @@ -48,7 +47,7 @@ export const DataSourceSection: FC<{ bookId: string }> = ({ bookId }) => {
}}
onClick={() => {
if (!dataSourcePlugin?.SelectItemComponent) {
dialog({ preset: "NOT_IMPLEMENTED" })
createDialog({ preset: "NOT_IMPLEMENTED" })
} else {
setIsSelectItemOpened(true)
}
Expand Down
15 changes: 5 additions & 10 deletions packages/web/src/books/drawer/BookActionsDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,11 @@ export const BookActionsDrawer = memo(() => {
bookId
)

const { mutate: onRemovePress, ...rest } = useRemoveHandler({
onError: () => {
handleClose()
},
onSuccess: ({ isDeleted }) => {
if (isDeleted) {
handleClose(() => {
onDeleteBook?.()
})
}
const { mutate: onRemovePress } = useRemoveHandler({
onSuccess: () => {
handleClose(() => {
onDeleteBook?.()
})
}
})

Expand Down
145 changes: 57 additions & 88 deletions packages/web/src/books/drawer/useRemoveHandler.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import { getBookById, useRemoveBook } from "../helpers"
import { useDialogManager } from "../../common/dialog"
import { useMutation } from "reactjrx"
import { getLatestDatabase } from "../../rxdb/useCreateDatabase"
import { catchError, from, map, mergeMap } from "rxjs"
import { combineLatest, from, map, mergeMap, of } from "rxjs"
import { isRemovableFromDataSource } from "../../links/isRemovableFromDataSource"
import { getDataSourcePlugin } from "../../dataSources/getDataSourcePlugin"
import { getLinkById } from "../../links/helpers"

type Return = {
isDeleted: boolean
}
import { createDialog } from "../../common/dialogs/createDialog"
import { withUnknownErrorDialog } from "../../common/errors/withUnknownErrorDialog"
import { withOfflineErrorDialog } from "../../common/network/withOfflineErrorDialog"

export const useRemoveHandler = (
options: { onSuccess?: (data: Return) => void; onError?: () => void } = {}
options: { onSuccess?: () => void; onError?: () => void } = {}
) => {
const removeBook = useRemoveBook()
const dialog = useDialogManager()
const { mutateAsync: removeBook } = useRemoveBook()

return useMutation({
mutationFn: ({ bookId }: { bookId: string }) => {
return getLatestDatabase().pipe(
const mutation$ = getLatestDatabase().pipe(
mergeMap((database) => {
return getBookById({ database, id: bookId }).pipe(
mergeMap((book) => {
Expand All @@ -28,31 +25,21 @@ export const useRemoveHandler = (
const linkId = book.links[0]

if (!book?.isAttachedToDataSource || !linkId) {
return from(
new Promise<Return>((resolve, reject) => {
dialog({
preset: "CONFIRM",
title: "Delete a book",
content: `You are about to delete a book, are you sure ?`,
onConfirm: () => {
removeBook({ id: book._id })
.then(() => resolve({ isDeleted: true }))
.catch(reject)
},
onCancel: () => {
resolve({ isDeleted: false })
}
})
})
)
return combineLatest([
of(book),
createDialog({
preset: "CONFIRM",
title: "Delete a book",
content: `You are about to delete a book, are you sure ?`,
onConfirm: () => ({ deleteFromDataSource: false })
}).$
])
}

return getLinkById(database, linkId).pipe(
mergeMap((firstLink) => {
if (!firstLink) {
return from(removeBook({ id: book._id })).pipe(
map(() => ({ isDeleted: true }))
)
return of({ deleteFromDataSource: false })
}

const plugin = getDataSourcePlugin(firstLink?.type)
Expand All @@ -61,71 +48,53 @@ export const useRemoveHandler = (
book?.isAttachedToDataSource &&
!isRemovableFromDataSource({ link: firstLink })
) {
return from(
new Promise<Return>((resolve, reject) => {
dialog({
preset: "CONFIRM",
title: "Delete a book",
content: `This book has been synchronized with one of your ${plugin?.name} data source. Oboku does not support deletion from ${plugin?.name} directly so consider deleting it there manually if you don't want the book to be synced again`,
onConfirm: () => {
removeBook({ id: book._id })
.then(() => resolve({ isDeleted: true }))
.catch(reject)
},
onCancel: () => {
resolve({ isDeleted: false })
}
})
})
)
return createDialog({
preset: "CONFIRM",
title: "Delete a book",
content: `This book has been synchronized with one of your ${plugin?.name} data source. Oboku does not support deletion from ${plugin?.name} directly so consider deleting it there manually if you don't want the book to be synced again`,
onConfirm: () => ({ deleteFromDataSource: false })
}).$
} else {
return from(
new Promise<Return>((resolve, reject) => {
dialog({
preset: "CONFIRM",
title: "Delete a book",
content: `This book has been synchronized with one of your ${plugin?.name} data source. You can delete it from both oboku and ${plugin?.name} which will prevent the book to be synced again`,
actions: [
{
type: "confirm",
title: "both",
onClick: () => {
removeBook({
id: book._id,
deleteFromDataSource: true
})
.then(() => resolve({ isDeleted: true }))
.catch(reject)
}
},
{
type: "confirm",
title: "only oboku",
onClick: () => {
removeBook({ id: book._id })
.then(() => resolve({ isDeleted: true }))
.catch(reject)
}
}
],
onCancel: () => {
resolve({ isDeleted: false })
}
})
})
)
return createDialog({
preset: "CONFIRM",
title: "Delete a book",
content: `This book has been synchronized with one of your ${plugin?.name} data source. You can delete it from both oboku and ${plugin?.name} which will prevent the book to be synced again`,
actions: [
{
type: "confirm",
title: "both",
onConfirm: () => ({ deleteFromDataSource: true })
},
{
type: "confirm",
title: "only oboku",
onConfirm: () => ({ deleteFromDataSource: false })
}
]
}).$
}
}),
map(
({ deleteFromDataSource }) =>
[book, { deleteFromDataSource }] as const
)
)
}),
mergeMap(([book, { deleteFromDataSource }]) =>
from(
removeBook({
id: book._id,
deleteFromDataSource: deleteFromDataSource
})
)
})
)
)
}),
catchError((e) => {
console.error(e)

throw e
})
withOfflineErrorDialog(),
withUnknownErrorDialog()
)

return mutation$
},
...options
})
Expand Down
54 changes: 20 additions & 34 deletions packages/web/src/books/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,65 +10,51 @@ import { useCallback, useMemo } from "react"
import { PromiseReturnType } from "../types"
import { BookQueryResult, useBooksDic } from "./states"
import { AtomicUpdateFunction } from "rxdb"
import { useLock } from "../common/BlockingBackdrop"
import { useNetworkState } from "react-use"
import { useDialogManager } from "../common/dialog"
import { from } from "rxjs"
import { useRemoveBookFromDataSource } from "../plugins/useRemoveBookFromDataSource"
import { useMutation } from "reactjrx"
import { isPluginError } from "../plugins/plugin-front"
import { getMetadataFromBook } from "./getMetadataFromBook"
import { useRefreshBookMetadata } from "./useRefreshBookMetadata"
import { CancelError, OfflineError } from "../common/errors/errors"

export const useRemoveBook = () => {
const removeDownload = useRemoveDownloadFile()
const { db } = useDatabase()
const dialog = useDialogManager()
const [lock] = useLock()
const removeBookFromDataSource = useRemoveBookFromDataSource()
const network = useNetworkState()

return useCallback(
async ({
return useMutation({
mutationFn: async ({
id,
deleteFromDataSource
}: {
id: string
deleteFromDataSource?: boolean
}) => {
let unlock = () => {}
if (deleteFromDataSource) {
if (!network.online) {
throw new OfflineError()
}

try {
if (deleteFromDataSource) {
if (!network.online) {
return dialog({ preset: "OFFLINE" })
try {
await removeBookFromDataSource(id)
} catch (e) {
if (isPluginError(e) && e.code === "cancelled") {
throw new CancelError()
}

try {
await removeBookFromDataSource(id)
} catch (e) {
if (isPluginError(e) && e.code === "cancelled") {
return
}

Report.error(e)

return dialog({ preset: "UNKNOWN_ERROR" })
} finally {
unlock()
}
throw e
}

await Promise.all([
removeDownload(id),
db?.book.findOne({ selector: { _id: id } }).remove()
])
} catch (e) {
Report.error(e)
}
},
[removeDownload, removeBookFromDataSource, network, dialog, db]
)

await Promise.all([
removeDownload(id),
db?.book.findOne({ selector: { _id: id } }).remove()
])
}
})
}

export const useRemoveTagFromBook = () => {
Expand Down
Loading

0 comments on commit 08b6937

Please sign in to comment.