2022-10-29 09:06:09 +00:00
import { captureException } from '@sentry/browser' ;
2023-11-13 07:02:03 +00:00
import { PropsWithChildren , useEffect , useState } from 'react' ;
2023-10-30 00:44:33 +00:00
import {
ErrorBoundary ,
ErrorBoundaryPropsWithComponent ,
FallbackProps
} from 'react-error-boundary' ;
2023-11-13 07:02:03 +00:00
import { useRouteError } from 'react-router' ;
2023-02-24 08:12:21 +00:00
import { useDebugState } from '@sd/client' ;
2023-08-18 16:43:41 +00:00
import { Button , Dialogs } from '@sd/ui' ;
2023-09-11 15:26:44 +00:00
2023-08-16 07:16:18 +00:00
import { showAlertDialog } from './components' ;
2023-06-16 07:53:21 +00:00
import { useOperatingSystem , useTheme } from './hooks' ;
2023-10-13 08:50:01 +00:00
import { usePlatform } from './util/Platform' ;
2023-05-30 09:24:05 +00:00
2023-10-30 00:44:33 +00:00
const RENDERING_ERROR_LOCAL_STORAGE_KEY = 'was-rendering-error' ;
2023-05-30 09:24:05 +00:00
export function RouterErrorBoundary() {
const error = useRouteError ( ) ;
2023-10-30 00:44:33 +00:00
const reloadBtn = ( ) = > {
location . reload ( ) ;
localStorage . setItem ( RENDERING_ERROR_LOCAL_STORAGE_KEY , 'true' ) ;
} ;
2023-05-30 09:24:05 +00:00
return (
< ErrorPage
message = { ( error as any ) . toString ( ) }
sendReportBtn = { ( ) = > {
captureException ( error ) ;
2023-10-30 00:44:33 +00:00
reloadBtn ( ) ;
2023-05-30 09:24:05 +00:00
} }
2023-10-30 00:44:33 +00:00
reloadBtn = { reloadBtn }
2023-05-30 09:24:05 +00:00
/ >
) ;
}
2022-06-18 08:35:51 +00:00
2023-02-28 05:29:48 +00:00
export default ( { error , resetErrorBoundary } : FallbackProps ) = > (
< ErrorPage
2023-05-30 09:24:05 +00:00
message = { ` Error: ${ error . message } ` }
2023-02-28 05:29:48 +00:00
sendReportBtn = { ( ) = > {
captureException ( error ) ;
resetErrorBoundary ( ) ;
} }
reloadBtn = { resetErrorBoundary }
/ >
) ;
2022-10-29 09:06:09 +00:00
2023-06-06 10:56:31 +00:00
// This is sketchy but these are all edge cases that will only be encountered by developers if everything works as expected so it's probs fine
const errorsThatRequireACoreReset = [
'failed to initialize config' ,
'failed to initialize library manager: failed to run library migrations' ,
2023-06-16 03:49:02 +00:00
'failed to initialize config: We detected a Spacedrive config from a super early version of the app!' ,
'failed to initialize library manager: failed to run library migrations: YourAppIsOutdated - the config file is for a newer version of the app. Please update to the latest version to load it!'
2023-06-06 10:56:31 +00:00
] ;
2023-02-25 13:50:22 +00:00
export function ErrorPage ( {
reloadBtn ,
sendReportBtn ,
2023-05-30 09:24:05 +00:00
message ,
submessage
2023-02-25 13:50:22 +00:00
} : {
reloadBtn ? : ( ) = > void ;
sendReportBtn ? : ( ) = > void ;
message : string ;
2023-05-30 09:24:05 +00:00
submessage? : string ;
2023-02-25 13:50:22 +00:00
} ) {
2023-06-16 07:53:21 +00:00
useTheme ( ) ;
2023-02-24 08:12:21 +00:00
const debug = useDebugState ( ) ;
2023-05-30 09:24:05 +00:00
const os = useOperatingSystem ( ) ;
2023-10-13 08:50:01 +00:00
const platform = usePlatform ( ) ;
2023-05-30 09:24:05 +00:00
const isMacOS = os === 'macOS' ;
2023-10-30 00:44:33 +00:00
const [ redirecting , _ ] = useState ( ( ) = >
localStorage . getItem ( RENDERING_ERROR_LOCAL_STORAGE_KEY )
) ;
// If the user is on a page and the user presses "Reset" on the error boundary, it may crash in rendering causing the user to get stuck on the error page.
// If it crashes again, we redirect them instead of infinitely crashing.
useEffect ( ( ) = > {
if ( localStorage . getItem ( RENDERING_ERROR_LOCAL_STORAGE_KEY ) !== null ) {
localStorage . removeItem ( RENDERING_ERROR_LOCAL_STORAGE_KEY ) ;
window . location . pathname = '/' ;
console . error (
'Hit error boundary after reloading. Redirecting to overview screen!' ,
redirecting
) ;
}
} ) ;
if ( redirecting ) return null ; // To stop flash of error boundary after `localStorage` is reset in the first render and the check above starts being `false`
2023-08-16 07:16:18 +00:00
const resetHandler = ( ) = > {
showAlertDialog ( {
title : 'Reset' ,
value : 'Are you sure you want to reset Spacedrive? Your database will be deleted.' ,
label : 'Confirm' ,
cancelBtn : true ,
onSubmit : ( ) = > {
localStorage . clear ( ) ;
// @ts-expect-error
window . __TAURI_INVOKE__ ( 'reset_spacedrive' ) ;
}
} ) ;
} ;
2023-05-30 09:24:05 +00:00
if ( ! submessage && debug . enabled )
submessage = 'Check the console (CMD/CTRL + OPTION + i) for stack trace.' ;
2023-02-24 08:12:21 +00:00
2022-06-18 08:35:51 +00:00
return (
< div
data - tauri - drag - region
role = "alert"
2023-05-30 09:24:05 +00:00
className = {
2023-10-24 07:51:58 +00:00
'flex h-screen w-screen flex-col items-center justify-center border border-app-divider bg-app p-4' +
2023-05-30 09:24:05 +00:00
( isMacOS ? ' rounded-lg' : '' )
}
2022-06-18 08:35:51 +00:00
>
2023-08-18 16:43:41 +00:00
< Dialogs / >
2023-04-04 05:39:07 +00:00
< p className = "m-3 text-sm font-bold text-ink-faint" > APP CRASHED < / p >
< h1 className = "text-2xl font-bold text-ink" > We ' re past the event horizon . . . < / h1 >
2023-06-06 10:56:31 +00:00
< pre className = "m-2 max-w-[650px] whitespace-normal text-center text-ink" >
{ message }
< / pre >
2023-05-30 09:24:05 +00:00
{ submessage && < pre className = "m-2 text-sm text-ink-dull" > { submessage } < / pre > }
2023-04-04 05:39:07 +00:00
< div className = "flex flex-row space-x-2 text-ink" >
2023-02-25 13:50:22 +00:00
{ reloadBtn && (
< Button variant = "accent" className = "mt-2" onClick = { reloadBtn } >
Reload
< / Button >
) }
2023-10-13 08:50:01 +00:00
< Button
variant = "gray"
className = "mt-2"
onClick = { ( ) = > ( sendReportBtn ? sendReportBtn ( ) : captureException ( message ) ) }
>
Send report
< / Button >
{ platform . openLogsDir && (
< Button variant = "gray" className = "mt-2" onClick = { platform . openLogsDir } >
Open Logs
2023-02-25 13:50:22 +00:00
< / Button >
) }
2023-10-13 08:50:01 +00:00
2023-06-16 07:53:21 +00:00
{ ( errorsThatRequireACoreReset . includes ( message ) ||
message . startsWith ( 'NodeError::FailedToInitializeConfig' ) ||
message . startsWith ( 'failed to initialize library manager' ) ) && (
2023-05-30 12:45:28 +00:00
< div className = "flex flex-col items-center pt-12" >
2023-05-31 09:35:38 +00:00
< p className = "text-md max-w-[650px] text-center" >
2023-05-30 12:45:28 +00:00
We detected you may have created your library with an older version of
Spacedrive . Please reset it to continue using the app !
2023-05-31 09:35:38 +00:00
< / p >
< p className = "mt-3 font-bold" >
{ ' ' }
2023-05-30 12:45:28 +00:00
YOU WILL LOSE ANY EXISTING SPACEDRIVE DATA !
< / p >
< Button
variant = "colored"
2023-08-16 07:16:18 +00:00
onClick = { resetHandler }
2023-08-18 16:43:41 +00:00
className = "mt-4 max-w-xs border-transparent bg-red-500"
2023-05-30 12:45:28 +00:00
>
2023-07-12 06:23:30 +00:00
Reset & Quit App
2023-05-30 12:45:28 +00:00
< / Button >
< / div >
) }
2022-06-18 08:35:51 +00:00
< / div >
< / div >
) ;
}
2023-10-30 00:44:33 +00:00
export const BetterErrorBoundary = ( {
children ,
FallbackComponent ,
. . . props
2023-11-13 07:02:03 +00:00
} : PropsWithChildren < ErrorBoundaryPropsWithComponent > ) = > {
2023-10-30 00:44:33 +00:00
useEffect ( ( ) = > {
const id = setTimeout (
( ) = > localStorage . removeItem ( RENDERING_ERROR_LOCAL_STORAGE_KEY ) ,
1000
) ;
return ( ) = > clearTimeout ( id ) ;
} , [ ] ) ;
return (
< ErrorBoundary FallbackComponent = { FallbackComponent } { ...props } >
{ children }
< / ErrorBoundary >
) ;
} ;