Repository navigation
Expand file tree
/
Copy pathmulticursorAlertMiddleware.ts
More file actions
96 lines (89 loc) · 5.97 KB
/
Copy pathmulticursorAlertMiddleware.ts
File metadata and controls
96 lines (89 loc) · 5.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import _ from 'lodash'
import { ThunkMiddleware } from 'redux-thunk'
import Dispatch from '../@types/Dispatch'
import State from '../@types/State'
import { alertActionCreator } from '../actions/alert'
import { toggleDropdownActionCreator as toggleDropdown } from '../actions/toggleDropdown'
import { isTouch } from '../browser'
import { AlertType } from '../constants'
/** Throttled dispatch for alert actions. */
const throttledAlert = _.throttle(
(dispatch: Dispatch, ...args: Parameters<typeof alertActionCreator>) => dispatch(alertActionCreator(...args)),
50,
{ leading: false, trailing: true },
)
/** A middleware that manages multicursor alerts and shows/hides the Command Center on mobile. This is done so that the Alert and Command Center are updated regardless of which action the multiselect is triggered from. Note that this only works in one direction: Multiselect -> Alert/CommandCenter. If the Command Center is closed somewhere else (e.g. toggleDropdown) it will need to clear the multicursors itself. */
const multicursorAlertMiddleware: ThunkMiddleware<State> = ({ getState, dispatch }) => {
return next => action => {
const prevNumMulticursors = Object.keys(getState().multicursors).length
next(action)
const state = getState()
const numMulticursors = Object.keys(state.multicursors).length
// On mobile, show the Command Center when multicursor is active, and hide it when inactive.
if (isTouch) {
// Do not react while a multicursor command is executing. The command loop sets the cursor to each selected
// thought in turn, which empties state.multicursors, and then restores them one at a time (see
// executeCommandWithMulticursor), so the count churns through 0 and back up again. That is internal
// bookkeeping rather than the user changing the selection, and reacting to it re-opens the Command Center over
// an editing session (space is bound to indent, so it runs the whole loop on every space typed after Clear
// Thought). The closing setIsMulticursorExecuting({ value: false }) is dispatched once the command completes,
// and this middleware evaluates it against the settled multicursors. setCursor suppresses its own cursorCleared
// reset over the same window and for the same reason.
if (state.isMulticursorExecuting) return
if (numMulticursors === 0 && state.showCommandCenter) {
dispatch(toggleDropdown({ dropDownType: 'commandCenter', value: false }))
} else if (
numMulticursors > 0 &&
!state.showCommandCenter &&
// Do not open the Command Center while the Undo Slider session is active.
// Otherwise undoing/redoing a multicursor command (e.g. delete from the Command Center) restores the
// multicursor, which would re-open the Command Center and dismiss the Undo Slider being used.
!state.showUndoSlider &&
// Do not re-open the Command Center while the keyboard is open, i.e. while the multiselection is being edited
// (Clear Thought). The sheet would cover the editing session, and on iOS any focus that arrives while the
// Command Center is shown is actively dismissed (see onFocus in Editable), so the keyboard could never open.
// When the keyboard closes (blur or exiting the cleared state), the multicursors are still active and this
// branch re-opens the Command Center.
// Starting a multiselection from none is exempt, since selecting a thought while the keyboard is open is how
// the Command Center is opened in the first place (Open Command Center adds the cursor thought when there is no
// multiselection yet). Checking prevNumMulticursors rather than a plain count-change keeps a multiselection that
// is already being edited (Clear Thought) from re-opening the Command Center over the editing session should
// its multicursor count fluctuate for some unrelated reason while typing.
// This only governs multiselection changes. An explicit Open/Close Command Center gesture is never subject to
// it: both commands set showCommandCenter themselves so that the user can always reach the Command Center they
// can see (see openCommandCenter.ts and closeCommandCenter.ts).
(!state.isKeyboardOpen || prevNumMulticursors === 0)
) {
dispatch(toggleDropdown({ dropDownType: 'commandCenter', value: true }))
}
}
// on desktop, show a persistent alert
else {
// A single throttle carries both the count alert and its clear, and only the last call's arguments survive to the
// trailing invocation. So a selection that drops to zero must schedule only the clear: scheduling the count alert
// as well would replace the pending clear with a "0 thoughts selected" alert that overwrites whatever alert is
// showing by then (e.g. an AI request's rate-limit message) and is cleared only on the next action.
if (!numMulticursors) {
// clear multicursor alert. The clear is throttled, so by the time it fires another alert may have replaced
// the multicursor one; scoping it to the multicursor alertType makes the action creator drop it in that case
// rather than dismiss an unrelated alert.
if (state.alert?.alertType === AlertType.MulticursorActive) {
throttledAlert(dispatch, null, { alertType: AlertType.MulticursorActive })
}
} else if (numMulticursors !== prevNumMulticursors) {
// show or update multicursor alert
throttledAlert(
dispatch,
numMulticursors === 1 ? '1 thought selected' : `${numMulticursors} thoughts selected`,
{
alertType: AlertType.MulticursorActive,
// Prevent auto-dismiss: the multiselect indicator must remain visible while a selection is active.
// It is cleared explicitly when the selection reaches zero (see above) or via the Cancel button.
clearDelay: null,
},
)
}
}
}
}
export default multicursorAlertMiddleware