Improve settings storage
- add error reporting - refactor
This commit is contained in:
@@ -2,13 +2,16 @@ import * as React from 'react'
|
||||
import ConnectionSetup from './components/ConnectionSetup/ConnectionSetup'
|
||||
import CssBaseline from '@material-ui/core/CssBaseline'
|
||||
import ErrorBoundary from './ErrorBoundary'
|
||||
import Notification from './components/Notification'
|
||||
import Sidebar from './components/Sidebar/Sidebar'
|
||||
import TitleBar from './components/TitleBar'
|
||||
import Tree from './components/Tree/Tree'
|
||||
import UpdateNotifier from './UpdateNotifier'
|
||||
import { AppState } from './reducers'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { connect } from 'react-redux'
|
||||
import { default as SplitPane } from 'react-split-pane'
|
||||
import { globalActions } from './actions'
|
||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||
|
||||
const Settings = React.lazy(() => import('./components/Settings'))
|
||||
@@ -18,6 +21,8 @@ interface Props {
|
||||
connectionId: string
|
||||
classes: any
|
||||
settingsVisible: boolean
|
||||
error?: string
|
||||
actions: any
|
||||
}
|
||||
|
||||
class App extends React.PureComponent<Props, {}> {
|
||||
@@ -26,6 +31,18 @@ class App extends React.PureComponent<Props, {}> {
|
||||
this.state = { }
|
||||
}
|
||||
|
||||
private renderError() {
|
||||
if (this.props.error) {
|
||||
const error = typeof this.props.error === 'string' ? this.props.error : JSON.stringify(this.props.error)
|
||||
return (
|
||||
<Notification
|
||||
message={error}
|
||||
onClose={() => { this.props.actions.showError(undefined) }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { settingsVisible } = this.props
|
||||
const { content, contentShift, centerContent, paneDefaults, heightProperty } = this.props.classes
|
||||
@@ -34,6 +51,7 @@ class App extends React.PureComponent<Props, {}> {
|
||||
<div className={centerContent}>
|
||||
<CssBaseline />
|
||||
<ErrorBoundary>
|
||||
{this.renderError()}
|
||||
<React.Suspense fallback={<div>Loading...</div>}>
|
||||
<Settings />
|
||||
</React.Suspense>
|
||||
@@ -75,6 +93,7 @@ const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
settingsVisible: state.settings.visible,
|
||||
connectionId: state.connection.connectionId,
|
||||
error: state.globalState.error,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,4 +139,10 @@ const styles = (theme: Theme) => {
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(connect(mapStateToProps)(App))
|
||||
const mapDispatchToProps = (dispatch: any) => {
|
||||
return {
|
||||
actions: bindActionCreators(globalActions, dispatch),
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(App))
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import * as React from 'react'
|
||||
import PersistantStorage from './PersistantStorage'
|
||||
import SentimentDissatisfied from '@material-ui/icons/SentimentDissatisfied'
|
||||
import Warning from '@material-ui/icons/Warning'
|
||||
import { electronRendererTelementry } from 'electron-telemetry'
|
||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
@@ -8,11 +12,6 @@ import {
|
||||
Typography,
|
||||
} from '@material-ui/core'
|
||||
|
||||
import Warning from '@material-ui/icons/Warning'
|
||||
import SentimentDissatisfied from '@material-ui/icons/SentimentDissatisfied'
|
||||
|
||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||
|
||||
interface State {
|
||||
error?: Error
|
||||
}
|
||||
@@ -41,7 +40,7 @@ class ErrorBoundary extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
private clearStorage = () => {
|
||||
localStorage.clear()
|
||||
PersistantStorage.clear()
|
||||
window.location = window.location
|
||||
}
|
||||
|
||||
|
||||
@@ -30,8 +30,13 @@ class RemoteStorage implements PersistantStorage {
|
||||
private expectAck(transactionId: string): Promise<void> {
|
||||
const ack = makeStorageAcknoledgementEvent(transactionId)
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const callback = () => {
|
||||
resolve()
|
||||
const callback = (msg: any) => {
|
||||
console.log(msg)
|
||||
if (msg && msg.error) {
|
||||
reject(msg.error)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
rendererEvents.unsubscribe(ack, callback)
|
||||
}
|
||||
rendererEvents.subscribe(ack, callback)
|
||||
@@ -52,8 +57,13 @@ class RemoteStorage implements PersistantStorage {
|
||||
|
||||
const promise = new Promise<Model>((resolve, reject) => {
|
||||
const callback = (msg: any) => {
|
||||
const data = msg.data && JSON.parse(msg.data)
|
||||
resolve(data)
|
||||
console.log(msg)
|
||||
|
||||
if (msg.error) {
|
||||
reject(msg.error)
|
||||
} else {
|
||||
resolve(msg.data)
|
||||
}
|
||||
rendererEvents.unsubscribe(responseEvent, callback)
|
||||
}
|
||||
rendererEvents.subscribe(responseEvent, callback)
|
||||
|
||||
@@ -115,7 +115,6 @@ class UpdateNotifier extends React.Component<Props, State> {
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}
|
||||
console.log(this.state.newerVersions)
|
||||
|
||||
return (
|
||||
<Snackbar
|
||||
@@ -266,8 +265,8 @@ const styles = (theme: Theme) => ({
|
||||
|
||||
const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
showUpdateNotification: state.tooBigReducer.showUpdateNotification,
|
||||
showUpdateDetails: state.tooBigReducer.showUpdateDetails,
|
||||
showUpdateNotification: state.globalState.showUpdateNotification,
|
||||
showUpdateDetails: state.globalState.showUpdateDetails,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Dispatch } from 'redux'
|
||||
import { MqttOptions } from '../../../backend/src/DataSource'
|
||||
import { showTree } from './Tree'
|
||||
import { TopicViewModel } from '../TopicViewModel'
|
||||
import { showError } from './Global'
|
||||
|
||||
export const connect = (options: MqttOptions, connectionId: string) => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
dispatch(connecting(connectionId))
|
||||
@@ -44,11 +45,6 @@ export const connecting: (connectionId: string) => Action = (connectionId: strin
|
||||
type: ActionTypes.CONNECTION_SET_CONNECTING,
|
||||
})
|
||||
|
||||
export const showError = (error?: string) => ({
|
||||
error,
|
||||
type: ActionTypes.CONNECTION_SET_SHOW_ERROR,
|
||||
})
|
||||
|
||||
export const disconnect = () => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
const { connectionId, tree } = getState().connection
|
||||
rendererEvents.emit(removeConnection, connectionId)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { AppState } from '../reducers'
|
||||
import { clearLegacyConnectionOptions, loadLegacyConnectionOptions } from '../model/LegacyConnectionSettings'
|
||||
import { ConnectionOptions, createEmptyConnection, makeDefaultConnections } from '../model/ConnectionOptions'
|
||||
import { default as persistantStorage, StorageIdentifier } from '../PersistantStorage'
|
||||
import { Dispatch } from 'redux'
|
||||
import { loadLegacyConnectionOptions, clearLegacyConnectionOptions } from '../model/LegacyConnectionSettings'
|
||||
import { showError } from './Global'
|
||||
import {
|
||||
ActionTypes,
|
||||
Action,
|
||||
@@ -13,8 +14,13 @@ const storedConnectionsIdentifier: StorageIdentifier<{[s: string]: ConnectionOpt
|
||||
}
|
||||
|
||||
export const loadConnectionSettings = () => async (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
await ensureConnectionsHaveBeenInitialized()
|
||||
const connections = await persistantStorage.load(storedConnectionsIdentifier)
|
||||
let connections
|
||||
try {
|
||||
await ensureConnectionsHaveBeenInitialized()
|
||||
connections = await persistantStorage.load(storedConnectionsIdentifier)
|
||||
} catch (error) {
|
||||
dispatch(showError(error))
|
||||
}
|
||||
|
||||
if (!connections) {
|
||||
return
|
||||
@@ -27,8 +33,12 @@ export const loadConnectionSettings = () => async (dispatch: Dispatch<any>, getS
|
||||
}
|
||||
}
|
||||
|
||||
export const saveConnectionSettings = () => (_dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
persistantStorage.store(storedConnectionsIdentifier, getState().connectionManager.connections)
|
||||
export const saveConnectionSettings = () => async (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
try {
|
||||
await persistantStorage.store(storedConnectionsIdentifier, getState().connectionManager.connections)
|
||||
} catch (error) {
|
||||
dispatch(showError(error))
|
||||
}
|
||||
}
|
||||
|
||||
export const updateConnection = (connectionId: string, changeSet: any): Action => ({
|
||||
|
||||
6
app/src/actions/Global.ts
Normal file
6
app/src/actions/Global.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { ActionTypes } from '../reducers'
|
||||
|
||||
export const showError = (error?: string) => ({
|
||||
error,
|
||||
type: ActionTypes.showError,
|
||||
})
|
||||
@@ -5,5 +5,6 @@ import * as updateNotifierActions from './UpdateNotifier'
|
||||
import * as connectionActions from './Connection'
|
||||
import * as sidebarActons from './Sidebar'
|
||||
import * as connectionManagerActions from './ConnectionManager'
|
||||
import * as globalActions from './Global'
|
||||
|
||||
export { settingsActions, treeActions, publishActions, updateNotifierActions, connectionActions, sidebarActons, connectionManagerActions }
|
||||
export { settingsActions, treeActions, publishActions, updateNotifierActions, connectionActions, sidebarActons, connectionManagerActions, globalActions }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react'
|
||||
import Delete from '@material-ui/icons/Delete'
|
||||
import Settings from '@material-ui/icons/Settings'
|
||||
import Notification from './Notification'
|
||||
import PowerSettingsNew from '@material-ui/icons/PowerSettingsNew'
|
||||
import Save from '@material-ui/icons/Save'
|
||||
import Visibility from '@material-ui/icons/Visibility'
|
||||
@@ -35,7 +34,6 @@ interface Props {
|
||||
managerActions: typeof connectionManagerActions
|
||||
connected: boolean
|
||||
connecting: boolean
|
||||
error?: string
|
||||
}
|
||||
|
||||
const protocols = [
|
||||
@@ -106,19 +104,8 @@ class ConnectionSettings extends React.Component<Props, State> {
|
||||
</InputAdornment>
|
||||
)
|
||||
|
||||
let renderError = null
|
||||
if (this.props.error) {
|
||||
renderError = (
|
||||
<Notification
|
||||
message={this.props.error}
|
||||
onClose={() => { this.props.actions.showError(undefined) }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{renderError}
|
||||
<form className={classes.container} noValidate={true} autoComplete="off">
|
||||
<Grid container={true} spacing={3}>
|
||||
<Grid item={true} xs={5}>
|
||||
@@ -324,7 +311,6 @@ const mapStateToProps = (state: AppState) => {
|
||||
return {
|
||||
connected: state.connection.connected,
|
||||
connecting: state.connection.connecting,
|
||||
error: state.connection.error,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -147,7 +147,6 @@ class Sidebar extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
private valueRenderWidthChange = (width: number) => {
|
||||
console.log(width)
|
||||
this.setState({ valueRenderWidth: width })
|
||||
}
|
||||
|
||||
|
||||
@@ -10,16 +10,18 @@ import { ConnectionManagerState, connectionManagerReducer } from './ConnectionMa
|
||||
export enum ActionTypes {
|
||||
showUpdateNotification = 'SHOW_UPDATE_NOTIFICATION',
|
||||
showUpdateDetails = 'SHOW_UPDATE_DETAILS',
|
||||
showError = 'SHOW_ERROR',
|
||||
}
|
||||
|
||||
export interface CustomAction extends Action {
|
||||
type: ActionTypes,
|
||||
showUpdateNotification?: boolean
|
||||
showUpdateDetails?: boolean
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface AppState {
|
||||
tooBigReducer: TooBigOfState
|
||||
globalState: GlobalState
|
||||
tree: TreeState
|
||||
settings: SettingsState,
|
||||
publish: PublishState
|
||||
@@ -27,16 +29,17 @@ export interface AppState {
|
||||
connectionManager: ConnectionManagerState
|
||||
}
|
||||
|
||||
export interface TooBigOfState {
|
||||
export interface GlobalState {
|
||||
showUpdateNotification?: boolean
|
||||
showUpdateDetails: boolean
|
||||
error?: string
|
||||
}
|
||||
|
||||
const initialBigState: TooBigOfState = {
|
||||
const initialBigState: GlobalState = {
|
||||
showUpdateDetails: false,
|
||||
}
|
||||
|
||||
const tooBigReducer: Reducer<TooBigOfState | undefined, CustomAction> = (state = initialBigState, action) => {
|
||||
const globalState: Reducer<GlobalState | undefined, CustomAction> = (state = initialBigState, action) => {
|
||||
if (!state) {
|
||||
throw Error('No initial state')
|
||||
}
|
||||
@@ -49,6 +52,12 @@ const tooBigReducer: Reducer<TooBigOfState | undefined, CustomAction> = (state =
|
||||
showUpdateNotification: action.showUpdateNotification,
|
||||
}
|
||||
|
||||
case ActionTypes.showError:
|
||||
return {
|
||||
...state,
|
||||
error: action.error,
|
||||
}
|
||||
|
||||
case ActionTypes.showUpdateDetails:
|
||||
if (action.showUpdateDetails === undefined) {
|
||||
return state
|
||||
@@ -64,7 +73,7 @@ const tooBigReducer: Reducer<TooBigOfState | undefined, CustomAction> = (state =
|
||||
}
|
||||
|
||||
const reducer = combineReducers({
|
||||
tooBigReducer,
|
||||
globalState,
|
||||
publish: publishReducer,
|
||||
connection: connectionReducer,
|
||||
settings: settingsReducer,
|
||||
|
||||
@@ -10,32 +10,58 @@ import {
|
||||
} from '../../events/StorageEvents'
|
||||
|
||||
export default class ConfigStorage {
|
||||
private adapter: any
|
||||
private file: string
|
||||
private database: any
|
||||
constructor(file: string) {
|
||||
this.adapter = new FileAsync(file)
|
||||
this.file = file
|
||||
}
|
||||
|
||||
private async getDb() {
|
||||
const adapter = new FileAsync(this.file)
|
||||
if (!this.database) {
|
||||
this.database = await lowdb(adapter)
|
||||
}
|
||||
|
||||
return this.database
|
||||
}
|
||||
|
||||
public async init() {
|
||||
const database: lowdb.LoDashExplicitAsyncWrapper<any> = await lowdb(this.adapter)
|
||||
backendEvents.subscribe(storageStoreEvent, async (event) => {
|
||||
await database.set(event.store, event.data).write()
|
||||
backendEvents.emit(makeStorageAcknoledgementEvent(event.transactionId), undefined)
|
||||
const ack = makeStorageAcknoledgementEvent(event.transactionId)
|
||||
try {
|
||||
const db = await this.getDb()
|
||||
await db.set(event.store, event.data).write()
|
||||
backendEvents.emit(ack, undefined)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
backendEvents.emit(ack, { error, transactionId: event.transactionId, store: event.store })
|
||||
}
|
||||
})
|
||||
|
||||
backendEvents.subscribe(storageLoadEvent, async (event) => {
|
||||
const responseEvent = makeStorageResponseEvent(event.transactionId)
|
||||
try {
|
||||
const data = await database.get(event.store).value()
|
||||
backendEvents.emit(responseEvent, { data, transactionId: event.transactionId })
|
||||
const db = await this.getDb()
|
||||
const data = await db.get(event.store).value()
|
||||
backendEvents.emit(responseEvent, { data, transactionId: event.transactionId, store: event.store })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
backendEvents.emit(responseEvent, { transactionId: event.transactionId })
|
||||
backendEvents.emit(responseEvent, { error, transactionId: event.transactionId, store: event.store })
|
||||
}
|
||||
})
|
||||
|
||||
backendEvents.subscribe(storageClearEvent, async (event) => {
|
||||
await database.drop()
|
||||
backendEvents.emit(makeStorageAcknoledgementEvent(event.transactionId), undefined)
|
||||
try {
|
||||
const db = await this.getDb()
|
||||
const keys = await db.keys().value()
|
||||
for (const key of keys) {
|
||||
await db.unset(key).write()
|
||||
}
|
||||
backendEvents.emit(makeStorageAcknoledgementEvent(event.transactionId), undefined)
|
||||
} catch (error) {
|
||||
backendEvents.emit(makeStorageAcknoledgementEvent(event.transactionId), { error, transactionId: event.transactionId })
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,3 @@ class UpdateNotifier {
|
||||
}
|
||||
|
||||
export const updateNotifier = new UpdateNotifier()
|
||||
|
||||
const configStorage = new ConfigStorage('blah.json')
|
||||
configStorage.init()
|
||||
|
||||
@@ -25,7 +25,6 @@ class IpcMainEventBus implements EventBusInterface {
|
||||
console.log('subscribing', subscribeEvent.topic)
|
||||
this.ipc.on(subscribeEvent.topic, (event: any, arg: any) => {
|
||||
this.client = event.sender
|
||||
console.log(subscribeEvent.topic, arg)
|
||||
callback(arg)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ interface StorageEvent {
|
||||
}
|
||||
|
||||
export interface StoreCommand extends StorageEvent {
|
||||
store: string,
|
||||
data: any
|
||||
store?: string,
|
||||
data?: any
|
||||
error?: any
|
||||
}
|
||||
|
||||
export interface LoadCommand extends StorageEvent {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { UpdateInfo } from '../events'
|
||||
import { BrowserWindow, app, Menu } from 'electron'
|
||||
import * as path from 'path'
|
||||
import { menuTemplate } from './MenuTemplate'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import * as log from 'electron-log'
|
||||
import * as path from 'path'
|
||||
import ConfigStorage from '../backend/src/ConfigStorage'
|
||||
import { app, BrowserWindow, Menu } from 'electron'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { ConnectionManager, updateNotifier } from '../backend/src/index'
|
||||
import { electronTelemetryFactory } from 'electron-telemetry'
|
||||
import { menuTemplate } from './MenuTemplate'
|
||||
import { UpdateInfo } from '../events'
|
||||
const isDev = require('electron-is-dev')
|
||||
|
||||
let electronTelemetry: any
|
||||
@@ -24,6 +25,9 @@ log.info('App starting...')
|
||||
const connectionManager = new ConnectionManager()
|
||||
connectionManager.manageConnections()
|
||||
|
||||
const configStorage = new ConfigStorage(path.join(app.getPath('appData'), app.getName(), 'settings.json'))
|
||||
configStorage.init()
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let mainWindow: BrowserWindow | undefined
|
||||
|
||||
Reference in New Issue
Block a user