diff --git a/src/components/main.tsx b/src/components/main.tsx index 073c71d..be645d4 100644 --- a/src/components/main.tsx +++ b/src/components/main.tsx @@ -35,6 +35,7 @@ import { RenameDialog } from './rename-dialog'; import { UploadDialog } from './upload-dialog'; import { RecordDialog } from './record-dialog'; import { ErrorDialog } from './error-dialog'; +import { PanicDialog } from './panic-dialog'; import { ConvertDialog } from './convert-dialog'; import { AboutDialog } from './about-dialog'; import { DumpDialog } from './dump-dialog'; @@ -379,6 +380,7 @@ export const Main = (props: {}) => { + ); }; diff --git a/src/components/panic-dialog.tsx b/src/components/panic-dialog.tsx new file mode 100644 index 0000000..9a04ecd --- /dev/null +++ b/src/components/panic-dialog.tsx @@ -0,0 +1,68 @@ +import React, { useCallback } from 'react'; +import { useDispatch } from 'react-redux'; +import { useShallowEqualSelector } from '../utils'; +import { actions as panicDialogActions } from '../redux/panic-dialog-feature'; + +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import Slide from '@material-ui/core/Slide'; +import Button from '@material-ui/core/Button'; +import { TransitionProps } from '@material-ui/core/transitions'; +import { Typography } from '@material-ui/core'; + +const Transition = React.forwardRef(function Transition( + props: TransitionProps & { children?: React.ReactElement }, + ref: React.Ref +) { + return ; +}); + +export const PanicDialog = (props: {}) => { + const dispatch = useDispatch(); + + let { visible, dismissed } = useShallowEqualSelector(state => state.panicDialog); + + const handleReloadApp = useCallback(() => { + window.location.reload(); + }, []); + + const handleIgnore = useCallback(() => { + dispatch(panicDialogActions.dismiss()); + }, [dispatch]); + + return ( + + Oops… Something unexpected happened. + + + Try to restart the app. If the error persists, try the followings: +
    +
  1. Use your browser in incognito mode.
  2. +
  3. Use a blank MiniDisc.
  4. +
  5. Try to use Web MiniDisc on another computer.
  6. +
+ If this does not solve the error, your unit might not be supported yet or you have encountered a bug. The full error is + reported in the JS console. +
+
+ + + + +
+ ); +}; diff --git a/src/redux/panic-dialog-feature.ts b/src/redux/panic-dialog-feature.ts new file mode 100644 index 0000000..83bbc02 --- /dev/null +++ b/src/redux/panic-dialog-feature.ts @@ -0,0 +1,24 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { enableBatching } from 'redux-batched-actions'; + +export const initialState = { + visible: false, + dismissed: false, // This will prevent showing the dialog during the same session +}; + +const slice = createSlice({ + name: 'panicDialog', + initialState, + reducers: { + setVisible: (state, action: PayloadAction) => { + state.visible = action.payload; + }, + dismiss: (state, action: PayloadAction) => { + state.visible = false; + state.dismissed = true; + }, + }, +}); + +export const { actions, reducer } = slice; +export default enableBatching(reducer); diff --git a/src/redux/store.ts b/src/redux/store.ts index dd08b0d..b29ed15 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -1,26 +1,51 @@ -import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'; +import { configureStore, getDefaultMiddleware, Middleware, combineReducers } from '@reduxjs/toolkit'; import uploadDialog from './upload-dialog-feature'; import renameDialog from './rename-dialog-feature'; import errorDialog from './error-dialog-feature'; +import panicDialog, { actions as panicDialogActions } from './panic-dialog-feature'; import convertDialog from './convert-dialog-feature'; import dumpDialog from './dump-dialog-feature'; import recordDialog from './record-dialog-feature'; -import appState from './app-feature'; +import appState, { actions as appActions } from './app-feature'; import main from './main-feature'; -export const store = configureStore({ - reducer: { - renameDialog, - uploadDialog, - errorDialog, - convertDialog, - dumpDialog, - recordDialog, - appState, - main, - }, - middleware: [...getDefaultMiddleware()], +const errorCatcher: Middleware = store => next => async action => { + try { + await next(action); + } catch (e) { + console.error(e); + next(panicDialogActions.setVisible(true)); + } +}; + +let reducer = combineReducers({ + renameDialog, + uploadDialog, + errorDialog, + panicDialog, + convertDialog, + dumpDialog, + recordDialog, + appState, + main, }); +const resetStateAction = appActions.setState.toString(); +const resetStatePayoload = 'WELCOME'; +const resetStateReducer: typeof reducer = function(...args) { + const [state, action] = args; + if (action.type === resetStateAction && action.payload === resetStatePayoload) { + return initialState; + } + return reducer(...args); +}; + +export const store = configureStore({ + reducer: resetStateReducer, + middleware: [errorCatcher, ...getDefaultMiddleware()], +}); + +const initialState = Object.freeze(store.getState()); + export type RootState = ReturnType; export type AppDispatch = typeof store.dispatch;