Skip to content

Commit

Permalink
Merge pull request #101 from mbret/develop
Browse files Browse the repository at this point in the history
feat: fixed fullscreen switch
  • Loading branch information
mbret authored Mar 14, 2024
2 parents 692d486 + fb48bab commit c9193c1
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 71 deletions.
15 changes: 15 additions & 0 deletions packages/web/src/auth/AuthorizeActionDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { useValidateAppPassword } from "../settings/helpers"
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"

const FORM_ID = "LockActionBehindUserPasswordDialog"

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

export function withAuthorization<T>(stream: Observable<T>) {
return stream.pipe(
mergeMap(() =>
from(
new Promise<void>((resolve, reject) =>
authorizeAction(resolve, () => reject(new CancelError()))
)
)
)
)
}

export const AuthorizeActionDialog: FC<{}> = () => {
const { action, onCancel = () => {} } = useSignalValue(actionSignal) ?? {}
const open = !!action
Expand All @@ -37,6 +51,7 @@ export const AuthorizeActionDialog: FC<{}> = () => {
password: ""
}
})

const {
mutate: validatePassword,
reset: resetValidatePasswordMutation,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { catchError, from, map, of, switchMap, throwError } from "rxjs"
import { catchError, from, map, of, switchMap } from "rxjs"
import { usePluginRefreshMetadata } from "../plugins/usePluginRefreshMetadata"
import { useSyncReplicate } from "../rxdb/replication/useSyncReplicate"
import { useUpdateCollection } from "./useUpdateCollection"
Expand Down
56 changes: 35 additions & 21 deletions packages/web/src/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
useMemo,
useState
} from "react"
import { signal } from "reactjrx"
import { Subject, lastValueFrom, map, merge, of } from "rxjs"
import { CancelError } from "./errors"

type Preset = "NOT_IMPLEMENTED" | "OFFLINE" | "CONFIRM" | "UNKNOWN_ERROR"

Expand All @@ -34,17 +35,13 @@ type DialogType = {
onCancel?: () => void
}

const dialogUpdateSignal = signal<{ id: string; state: "closed" } | undefined>(
{}
)

const DialogContext = createContext<DialogType[]>([])

const ManageDialogContext = createContext({
remove: (id: string) => {},
add: (options: Omit<DialogType, "id">) => ({
id: "-1" as string,
promise: Promise.resolve()
$: of({})
})
})

Expand Down Expand Up @@ -109,13 +106,13 @@ const InnerDialog = () => {
}, [remove, currentDialog])

const onCancel = useCallback(() => {
handleClose()
currentDialog?.onCancel && currentDialog.onCancel()
handleClose()
}, [handleClose, currentDialog])

const onConfirm = useCallback(() => {
handleClose()
currentDialog?.onConfirm && currentDialog.onConfirm()
handleClose()
}, [handleClose, currentDialog])

const actions = currentDialog?.actions || [
Expand Down Expand Up @@ -174,27 +171,44 @@ export const DialogProvider: FC<{ children: ReactNode }> = ({ children }) => {

const remove = useCallback((id: string) => {
setDialogs((old) => old.filter((dialog) => id !== dialog.id))

dialogUpdateSignal.setValue({ id, state: "closed" })
}, [])

const add = useCallback((options: Omit<DialogType, "id">) => {
generatedId++

setDialogs((old) => [...old, { ...options, id: generatedId.toString() }])
const cancel = new Subject<void>()
const confirm = new Subject<void>()

const newDialog: DialogType = {
...options,
id: generatedId.toString(),
onCancel: () => {
cancel.next()
options.onCancel?.()
},
onConfirm: () => {
confirm.next()
options.onConfirm?.()
},
onClose: () => {
confirm.complete()
cancel.complete()
options.onClose?.()
}
}

const id = generatedId.toString()
setDialogs((old) => [...old, newDialog])

const promise = new Promise<void>((resolve) => {
const sub = dialogUpdateSignal.subject.subscribe((value) => {
if (value?.id === id && value.state === "closed") {
sub.unsubscribe()
resolve()
}
})
})
const $ = merge(
cancel.pipe(
map(() => {
throw new CancelError()
})
),
confirm.pipe(map(() => ({})))
)

return { id: generatedId.toString(), promise }
return { id: newDialog.id, $ }
}, [])

const controls = useMemo(
Expand Down
84 changes: 84 additions & 0 deletions packages/web/src/fullscreen/useFullscreenOnMount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useEffect } from "react"
import screenfull from "screenfull"
import { Report } from "../debug/report.shared"
import { useDialogManager } from "../dialog"
import {
EMPTY,
catchError,
defer,
endWith,
from,
mergeMap,
retry,
throwError,
timer
} from "rxjs"
import { useSubscribe } from "reactjrx"
import { CancelError } from "../errors"

const isPermissionCheckFailedError = (error: unknown): error is TypeError =>
error instanceof TypeError &&
// chrome
(error.message === "Permissions check failed" ||
// safari
error.message === "Type error" ||
// firefox
error.message === "Fullscreen request denied")

export const useFullscreenOnMount = ({ enabled }: { enabled: boolean }) => {
const dialog = useDialogManager()

useSubscribe(() => {
if (enabled && screenfull.isEnabled && !screenfull.isFullscreen) {
return defer(() => {
return from(screenfull.request(undefined, { navigationUI: "hide" }))
}).pipe(
retry({
count: 1,
delay: (error) => {
if (isPermissionCheckFailedError(error)) {
return timer(5).pipe(
mergeMap(() =>
// we avoid double dialog because of strict mode
screenfull.isFullscreen
? throwError(() => error)
: dialog({
title: "Fullscreen request",
content:
"Your browser does not allow automatic fullscreen without an interaction",
confirmTitle: "Fullscreen",
cancellable: true
}).$.pipe(endWith(true))
)
)
}

throw error
}
}),
catchError((error) => {
if (
isPermissionCheckFailedError(error) ||
error instanceof CancelError
) {
return EMPTY
}

Report.error(error)

return EMPTY
})
)
}

return EMPTY
}, [enabled, dialog])

useEffect(() => {
return () => {
if (screenfull.isEnabled && screenfull.isFullscreen) {
screenfull.exit().catch(Report.error)
}
}
}, [])
}
4 changes: 2 additions & 2 deletions packages/web/src/reader/ReaderScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { FC, useEffect } from "react"
import { useParams } from "react-router-dom"
import { AppTourReader } from "../firstTimeExperience/AppTourReader"
import { useWakeLock } from "../common/useWakeLock"
import { useFullScreenSwitch } from "./fullScreen"
import { useFullscreenAutoSwitch } from "./fullScreen"
import { Reader } from "./Reader"
import { MoreDialog } from "./MoreDialog"
import { useTrackBookBeingRead } from "../reading/useTrackBookBeingRead"
Expand All @@ -19,7 +19,7 @@ export const ReaderScreen: FC<{}> = () => {

useTrackBookBeingRead(bookId)
useWakeLock()
useFullScreenSwitch()
useFullscreenAutoSwitch()

useEffect(() => () => {
;[
Expand Down
32 changes: 8 additions & 24 deletions packages/web/src/reader/fullScreen.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,13 @@
import { useEffect } from "react"
import screenfull from "screenfull"
import { IS_MOBILE_DEVICE } from "../constants"
import { Report } from "../debug/report.shared"
import { useLocalSettings } from "../settings/states"
import { useFullscreenOnMount } from "../fullscreen/useFullscreenOnMount"

export const useFullScreenSwitch = () => {
const localSettings = useLocalSettings()
export const useFullscreenAutoSwitch = () => {
const { readingFullScreenSwitchMode } = useLocalSettings()

useEffect(() => {
if (
(localSettings.readingFullScreenSwitchMode === "always" ||
(localSettings.readingFullScreenSwitchMode === "automatic" &&
IS_MOBILE_DEVICE)) &&
screenfull.isEnabled &&
!screenfull.isFullscreen
) {
screenfull
.request(undefined, { navigationUI: "hide" })
.catch(Report.error)
}

return () => {
if (screenfull.isEnabled && screenfull.isFullscreen) {
screenfull.exit().catch(Report.error)
}
}
}, [localSettings])
useFullscreenOnMount({
enabled:
readingFullScreenSwitchMode === "always" ||
(readingFullScreenSwitchMode === "automatic" && IS_MOBILE_DEVICE)
})
}
33 changes: 10 additions & 23 deletions packages/web/src/settings/useRemoveAllContents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { catchError, combineLatest, from, map, mergeMap, of, tap } from "rxjs"
import { useSyncReplicate } from "../rxdb/replication/useSyncReplicate"
import { useLock } from "../common/BlockingBackdrop"
import { useDialogManager } from "../dialog"
import { authorizeAction } from "../auth/AuthorizeActionDialog"
import { withAuthorization } from "../auth/AuthorizeActionDialog"
import { Report } from "../debug/report.shared"
import { CancelError } from "../errors"

Expand Down Expand Up @@ -33,30 +33,16 @@ export const useRemoveAllContents = () => {
tagCount,
dataSourceCount
]) => {
const confirmed$ = from(
new Promise<void>((resolve, reject) => {
dialog({
title: "Account reset",
content: `This action will remove all of your content. Here is a breakdown of everything that will be removed:\n
${bookCount} books, ${collectionCount} collections, ${tagCount} tags and ${dataSourceCount} data sources. \n\nThis operation can take a long time and you NEED to be connected to internet`,
canEscape: true,
cancellable: true,
onConfirm: resolve,
onCancel() {
reject(new CancelError())
}
})
})
)
const confirmed$ = dialog({
title: "Account reset",
content: `This action will remove all of your content. Here is a breakdown of everything that will be removed:\n
${bookCount} books, ${collectionCount} collections, ${tagCount} tags and ${dataSourceCount} data sources. \n\nThis operation can take a long time and you NEED to be connected to internet`,
canEscape: true,
cancellable: true
}).$

return confirmed$.pipe(
mergeMap(() =>
from(
new Promise<void>((resolve, reject) =>
authorizeAction(resolve, () => reject(new CancelError()))
)
)
),
withAuthorization,
map(() => lock()),
mergeMap((unlock) =>
from(
Expand Down Expand Up @@ -102,6 +88,7 @@ export const useRemoveAllContents = () => {
content:
"Something went wrong during the process. No need to panic since you already wanted to destroy everything anyway. If everything is gone, you should not worry too much, if you still have contents, try to do it again"
})

throw e
})
)
Expand Down

0 comments on commit c9193c1

Please sign in to comment.