Add confirmation dialog
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { ActionTypes } from '../reducers/Global'
|
import { ActionTypes, ConfirmationRequest } from '../reducers/Global'
|
||||||
import { Dispatch } from 'redux'
|
import { Dispatch } from 'redux'
|
||||||
|
|
||||||
export const showError = (error?: string) => ({
|
export const showError = (error?: string) => ({
|
||||||
@@ -20,3 +20,30 @@ export const toggleSettingsVisibility = () => (dispatch: Dispatch<any>) => {
|
|||||||
type: ActionTypes.toggleSettingsVisibility,
|
type: ActionTypes.toggleSettingsVisibility,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const requestConfirmation = (title: string, inquiry: string) => (dispatch: Dispatch<any>) => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const confirmationRequest = {
|
||||||
|
title,
|
||||||
|
inquiry,
|
||||||
|
callback: (confirmed: boolean) => {
|
||||||
|
resolve(confirmed)
|
||||||
|
dispatch(removeConfirmationRequest(confirmationRequest))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
confirmationRequest,
|
||||||
|
type: ActionTypes.requestConfirmation,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const removeConfirmationRequest = (confirmationRequest: ConfirmationRequest) => (dispatch: Dispatch<any>) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
dispatch({
|
||||||
|
confirmationRequest,
|
||||||
|
type: ActionTypes.removeConfirmationRequest,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,11 +3,28 @@ import { AppState } from '../reducers'
|
|||||||
import { Dispatch } from 'redux'
|
import { Dispatch } from 'redux'
|
||||||
import { makePublishEvent, rendererEvents } from '../../../events'
|
import { makePublishEvent, rendererEvents } from '../../../events'
|
||||||
import { moveSelectionUpOrDownwards } from './visibleTreeTraversal'
|
import { moveSelectionUpOrDownwards } from './visibleTreeTraversal'
|
||||||
|
import { globalActions } from '.'
|
||||||
|
|
||||||
export const clearTopic = (topic: q.TreeNode<any>, recursive: boolean, subtopicClearLimit = 50) => (
|
export const clearTopic = (topic: q.TreeNode<any>, recursive: boolean, subtopicClearLimit = 50) => async (
|
||||||
dispatch: Dispatch<any>,
|
dispatch: Dispatch<any>,
|
||||||
getState: () => AppState
|
getState: () => AppState
|
||||||
) => {
|
) => {
|
||||||
|
if (recursive) {
|
||||||
|
const topicCount = topic.childTopicCount()
|
||||||
|
const deleteLimitMessage =
|
||||||
|
topicCount > subtopicClearLimit ? ` You can only delete ${subtopicClearLimit} child topics at once.` : ''
|
||||||
|
const childTopicsMessage = topicCount > 0 ? ` and ${topicCount} child ${topicCount === 1 ? 'topic' : 'topics'}` : ''
|
||||||
|
const confirmed = await dispatch(
|
||||||
|
globalActions.requestConfirmation(
|
||||||
|
'Confirm delete',
|
||||||
|
`Do you want to delete "${topic.path()}"${childTopicsMessage}?${deleteLimitMessage}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (!confirmed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(moveSelectionUpOrDownwards('next'))
|
dispatch(moveSelectionUpOrDownwards('next'))
|
||||||
|
|
||||||
const { connectionId } = getState().connection
|
const { connectionId } = getState().connection
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import * as React from 'react'
|
import ConfirmationDialog from './ConfirmationDialog'
|
||||||
import ConnectionSetup from './ConnectionSetup/ConnectionSetup'
|
import ConnectionSetup from './ConnectionSetup/ConnectionSetup'
|
||||||
import CssBaseline from '@material-ui/core/CssBaseline'
|
import CssBaseline from '@material-ui/core/CssBaseline'
|
||||||
import ErrorBoundary from './ErrorBoundary'
|
import ErrorBoundary from './ErrorBoundary'
|
||||||
import Notification from './Layout/Notification'
|
import Notification from './Layout/Notification'
|
||||||
|
import React from 'react'
|
||||||
import TitleBar from './Layout/TitleBar'
|
import TitleBar from './Layout/TitleBar'
|
||||||
import UpdateNotifier from './UpdateNotifier'
|
import UpdateNotifier from './UpdateNotifier'
|
||||||
import { AppState } from '../reducers'
|
import { AppState } from '../reducers'
|
||||||
import { bindActionCreators } from 'redux'
|
import { bindActionCreators } from 'redux'
|
||||||
|
import { ConfirmationRequest } from '../reducers/Global'
|
||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { globalActions, settingsActions } from '../actions'
|
import { globalActions, settingsActions } from '../actions'
|
||||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||||
@@ -23,6 +25,7 @@ interface Props {
|
|||||||
actions: typeof globalActions
|
actions: typeof globalActions
|
||||||
settingsActions: typeof settingsActions
|
settingsActions: typeof settingsActions
|
||||||
launching: boolean
|
launching: boolean
|
||||||
|
confirmationRequests: Array<ConfirmationRequest>
|
||||||
}
|
}
|
||||||
|
|
||||||
class App extends React.PureComponent<Props, {}> {
|
class App extends React.PureComponent<Props, {}> {
|
||||||
@@ -67,6 +70,7 @@ class App extends React.PureComponent<Props, {}> {
|
|||||||
<div className={centerContent}>
|
<div className={centerContent}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
<ConfirmationDialog confirmationRequests={this.props.confirmationRequests} />
|
||||||
{this.renderNotification()}
|
{this.renderNotification()}
|
||||||
<React.Suspense fallback={<div></div>}>
|
<React.Suspense fallback={<div></div>}>
|
||||||
<Settings />
|
<Settings />
|
||||||
@@ -149,6 +153,7 @@ const mapStateToProps = (state: AppState) => {
|
|||||||
notification: state.globalState.get('notification'),
|
notification: state.globalState.get('notification'),
|
||||||
highlightTopicUpdates: state.settings.get('highlightTopicUpdates'),
|
highlightTopicUpdates: state.settings.get('highlightTopicUpdates'),
|
||||||
launching: state.globalState.get('launching'),
|
launching: state.globalState.get('launching'),
|
||||||
|
confirmationRequests: state.globalState.get('confirmationRequests'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
59
app/src/components/ConfirmationDialog.tsx
Normal file
59
app/src/components/ConfirmationDialog.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import React, { useRef, useCallback, memo } from 'react'
|
||||||
|
import { ConfirmationRequest } from '../reducers/Global'
|
||||||
|
import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Button } from '@material-ui/core'
|
||||||
|
import { KeyCodes } from '../utils/KeyCodes'
|
||||||
|
|
||||||
|
function ConfirmationDialog(props: { confirmationRequests: Array<ConfirmationRequest> }) {
|
||||||
|
const request = props.confirmationRequests[0]
|
||||||
|
const yesRef = useRef<HTMLButtonElement>()
|
||||||
|
const noRef = useRef<HTMLButtonElement>()
|
||||||
|
const arrowKeyHandler = useCallback((event: KeyboardEvent) => {
|
||||||
|
const isArrowKey = event.keyCode === KeyCodes.arrow_left || event.keyCode === KeyCodes.arrow_right
|
||||||
|
if (!isArrowKey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.stopPropagation()
|
||||||
|
if (document.activeElement === noRef.current) {
|
||||||
|
yesRef.current && yesRef.current.focus()
|
||||||
|
} else {
|
||||||
|
noRef.current && noRef.current.focus()
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const confirm = React.useCallback(() => {
|
||||||
|
request && request.callback(true)
|
||||||
|
}, [request])
|
||||||
|
const reject = React.useCallback(() => {
|
||||||
|
request && request.callback(false)
|
||||||
|
}, [request])
|
||||||
|
|
||||||
|
if (!request) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={true}
|
||||||
|
onClose={reject}
|
||||||
|
aria-labelledby="alert-dialog-title"
|
||||||
|
aria-describedby="alert-dialog-description"
|
||||||
|
onKeyDown={arrowKeyHandler}
|
||||||
|
>
|
||||||
|
<DialogTitle id="alert-dialog-title">{request.title}</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText id="alert-dialog-description">{request.inquiry}</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button ref={yesRef} variant="contained" onClick={confirm} color="primary" autoFocus>
|
||||||
|
Yes
|
||||||
|
</Button>
|
||||||
|
<Button ref={noRef} variant="contained" onClick={reject} color="secondary">
|
||||||
|
No
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(ConfirmationDialog)
|
||||||
@@ -8,11 +8,16 @@ export const RecursiveTopicDeleteButton = (props: {
|
|||||||
node?: q.TreeNode<any>
|
node?: q.TreeNode<any>
|
||||||
deleteTopicAction: (node: q.TreeNode<any>, a: boolean, limit: number) => void
|
deleteTopicAction: (node: q.TreeNode<any>, a: boolean, limit: number) => void
|
||||||
}) => {
|
}) => {
|
||||||
const onClick = useCallback(() => {
|
const onClick = useCallback(
|
||||||
|
(event: React.MouseEvent) => {
|
||||||
if (props.node) {
|
if (props.node) {
|
||||||
|
event.stopPropagation()
|
||||||
|
event.preventDefault()
|
||||||
props.deleteTopicAction(props.node, true, deleteLimit)
|
props.deleteTopicAction(props.node, true, deleteLimit)
|
||||||
}
|
}
|
||||||
}, [props.node])
|
},
|
||||||
|
[props.node]
|
||||||
|
)
|
||||||
if (!props.node) {
|
if (!props.node) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ export enum ActionTypes {
|
|||||||
showNotification = 'SHOW_NOTIFICATION',
|
showNotification = 'SHOW_NOTIFICATION',
|
||||||
didLaunch = 'DID_LAUNCH',
|
didLaunch = 'DID_LAUNCH',
|
||||||
toggleSettingsVisibility = 'TOGGLE_SETTINGS_VISIBILITY',
|
toggleSettingsVisibility = 'TOGGLE_SETTINGS_VISIBILITY',
|
||||||
|
requestConfirmation = 'REQUEST_CONFIRMATION',
|
||||||
|
removeConfirmationRequest = 'REMOVE_CONFIRMATION_REQUEST',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfirmationRequest {
|
||||||
|
inquiry: string
|
||||||
|
title: string
|
||||||
|
callback: (confirmed: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GlobalAction extends Action {
|
export interface GlobalAction extends Action {
|
||||||
@@ -17,6 +25,7 @@ export interface GlobalAction extends Action {
|
|||||||
showUpdateDetails?: boolean
|
showUpdateDetails?: boolean
|
||||||
error?: string
|
error?: string
|
||||||
notification?: string
|
notification?: string
|
||||||
|
confirmationRequest?: ConfirmationRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GlobalStateInterface {
|
interface GlobalStateInterface {
|
||||||
@@ -26,6 +35,7 @@ interface GlobalStateInterface {
|
|||||||
notification?: string
|
notification?: string
|
||||||
launching: boolean
|
launching: boolean
|
||||||
settingsVisible: boolean
|
settingsVisible: boolean
|
||||||
|
confirmationRequests: Array<ConfirmationRequest>
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GlobalState = Record<GlobalStateInterface>
|
export type GlobalState = Record<GlobalStateInterface>
|
||||||
@@ -37,6 +47,7 @@ const initialStateFactory = Record<GlobalStateInterface>({
|
|||||||
notification: undefined,
|
notification: undefined,
|
||||||
launching: true,
|
launching: true,
|
||||||
settingsVisible: false,
|
settingsVisible: false,
|
||||||
|
confirmationRequests: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
export const globalState: Reducer<Record<GlobalStateInterface>, GlobalAction> = (
|
export const globalState: Reducer<Record<GlobalStateInterface>, GlobalAction> = (
|
||||||
@@ -67,6 +78,21 @@ export const globalState: Reducer<Record<GlobalStateInterface>, GlobalAction> =
|
|||||||
}
|
}
|
||||||
return state.set('showUpdateDetails', action.showUpdateDetails)
|
return state.set('showUpdateDetails', action.showUpdateDetails)
|
||||||
|
|
||||||
|
case ActionTypes.requestConfirmation:
|
||||||
|
if (action.confirmationRequest === undefined) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
return state.set('confirmationRequests', [...state.get('confirmationRequests'), action.confirmationRequest])
|
||||||
|
|
||||||
|
case ActionTypes.removeConfirmationRequest:
|
||||||
|
if (action.confirmationRequest === undefined) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
return state.set(
|
||||||
|
'confirmationRequests',
|
||||||
|
state.get('confirmationRequests').filter(a => a !== action.confirmationRequest)
|
||||||
|
)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user