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 (
+
+ );
+};
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;