Add panic dialog
This dialog show a few hints to users who encounter unexpected errors
This commit is contained in:
parent
51812af06f
commit
9f9e043feb
|
@ -35,6 +35,7 @@ import { RenameDialog } from './rename-dialog';
|
||||||
import { UploadDialog } from './upload-dialog';
|
import { UploadDialog } from './upload-dialog';
|
||||||
import { RecordDialog } from './record-dialog';
|
import { RecordDialog } from './record-dialog';
|
||||||
import { ErrorDialog } from './error-dialog';
|
import { ErrorDialog } from './error-dialog';
|
||||||
|
import { PanicDialog } from './panic-dialog';
|
||||||
import { ConvertDialog } from './convert-dialog';
|
import { ConvertDialog } from './convert-dialog';
|
||||||
import { AboutDialog } from './about-dialog';
|
import { AboutDialog } from './about-dialog';
|
||||||
import { DumpDialog } from './dump-dialog';
|
import { DumpDialog } from './dump-dialog';
|
||||||
|
@ -379,6 +380,7 @@ export const Main = (props: {}) => {
|
||||||
<RecordDialog />
|
<RecordDialog />
|
||||||
<DumpDialog trackIndexes={selected} />
|
<DumpDialog trackIndexes={selected} />
|
||||||
<AboutDialog />
|
<AboutDialog />
|
||||||
|
<PanicDialog />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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<any, any> },
|
||||||
|
ref: React.Ref<unknown>
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Dialog
|
||||||
|
open={visible && !dismissed}
|
||||||
|
maxWidth={'sm'}
|
||||||
|
fullWidth={true}
|
||||||
|
scroll={'paper'}
|
||||||
|
TransitionComponent={Transition as any}
|
||||||
|
aria-labelledby="error-dialog-slide-title"
|
||||||
|
aria-describedby="error-dialog-slide-description"
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-slide-title">Oops… Something unexpected happened.</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Typography color="textSecondary" variant="body1" component="div">
|
||||||
|
Try to restart the app. If the error persists, try the followings:
|
||||||
|
<ol>
|
||||||
|
<li>Use your browser in incognito mode.</li>
|
||||||
|
<li>Use a blank MiniDisc.</li>
|
||||||
|
<li>Try to use Web MiniDisc on another computer.</li>
|
||||||
|
</ol>
|
||||||
|
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.
|
||||||
|
</Typography>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleIgnore} size="small">
|
||||||
|
Ignore and Continue
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleReloadApp} color="primary">
|
||||||
|
Restart the App
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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<boolean>) => {
|
||||||
|
state.visible = action.payload;
|
||||||
|
},
|
||||||
|
dismiss: (state, action: PayloadAction<void>) => {
|
||||||
|
state.visible = false;
|
||||||
|
state.dismissed = true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { actions, reducer } = slice;
|
||||||
|
export default enableBatching(reducer);
|
|
@ -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 uploadDialog from './upload-dialog-feature';
|
||||||
import renameDialog from './rename-dialog-feature';
|
import renameDialog from './rename-dialog-feature';
|
||||||
import errorDialog from './error-dialog-feature';
|
import errorDialog from './error-dialog-feature';
|
||||||
|
import panicDialog, { actions as panicDialogActions } from './panic-dialog-feature';
|
||||||
import convertDialog from './convert-dialog-feature';
|
import convertDialog from './convert-dialog-feature';
|
||||||
import dumpDialog from './dump-dialog-feature';
|
import dumpDialog from './dump-dialog-feature';
|
||||||
import recordDialog from './record-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';
|
import main from './main-feature';
|
||||||
|
|
||||||
export const store = configureStore({
|
const errorCatcher: Middleware = store => next => async action => {
|
||||||
reducer: {
|
try {
|
||||||
|
await next(action);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
next(panicDialogActions.setVisible(true));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let reducer = combineReducers({
|
||||||
renameDialog,
|
renameDialog,
|
||||||
uploadDialog,
|
uploadDialog,
|
||||||
errorDialog,
|
errorDialog,
|
||||||
|
panicDialog,
|
||||||
convertDialog,
|
convertDialog,
|
||||||
dumpDialog,
|
dumpDialog,
|
||||||
recordDialog,
|
recordDialog,
|
||||||
appState,
|
appState,
|
||||||
main,
|
main,
|
||||||
},
|
|
||||||
middleware: [...getDefaultMiddleware()],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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<typeof store.getState>;
|
export type RootState = ReturnType<typeof store.getState>;
|
||||||
export type AppDispatch = typeof store.dispatch;
|
export type AppDispatch = typeof store.dispatch;
|
||||||
|
|
Loading…
Reference in New Issue