/
dispatchers.thunks.ts
145 lines (132 loc) · 5.71 KB
/
dispatchers.thunks.ts
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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import { MaybePromise, Undefinable } from 'tsdef';
import { FileActionData, FileActionState } from '../../types/action-handler.types';
import { FileAction } from '../../types/action.types';
import { ChonkyDispatch, ChonkyThunk } from '../../types/redux.types';
import { Logger } from '../../util/logger';
import { reduxActions } from '../reducers';
import {
selectContextMenuTriggerFile, selectExternalFileActionHandler, selectFileActionMap,
selectInstanceId, selectSelectedFiles
} from '../selectors';
import { thunkActivateSortAction, thunkApplySelectionTransform } from './file-actions.thunks';
/**
* Thunk that dispatches actions to the external (user-provided) action handler.
*/
export const thunkDispatchFileAction = (data: FileActionData<FileAction>): ChonkyThunk => (_dispatch, getState) => {
Logger.debug(`FILE ACTION DISPATCH: [${data.id}]`, 'data:', data);
const state = getState();
const action = selectFileActionMap(state)[data.id];
const externalFileActionHandler = selectExternalFileActionHandler(state);
if (action) {
if (externalFileActionHandler) {
Promise.resolve(externalFileActionHandler(data)).catch(error =>
Logger.error(`User-defined file action handler threw an error: ${error.message}`)
);
}
} else {
Logger.warn(
`Internal components dispatched the "${data.id}" file action, but such ` + `action was not registered.`
);
}
};
/**
* Thunk that is used by internal components (and potentially the user) to "request"
* actions. When action is requested, Chonky "prepares" the action data by extracting it
* from Redux state. Once action data is ready, Chonky executes some side effect and/or
* dispatches the action to the external action handler.
*/
export const thunkRequestFileAction = <Action extends FileAction>(
action: Action,
payload: Action['__payloadType']
): ChonkyThunk => (dispatch, getState) => {
Logger.debug(`FILE ACTION REQUEST: [${action.id}]`, 'action:', action, 'payload:', payload);
const state = getState();
const instanceId = selectInstanceId(state);
if (!selectFileActionMap(state)[action.id]) {
Logger.warn(
`The action "${action.id}" was requested, but it is not registered. The ` +
`action will still be dispatched, but this might indicate a bug in ` +
`the code. Please register your actions by passing them to ` +
`"fileActions" prop.`
);
}
// Determine files for the action if action requires selection
const selectedFiles = selectSelectedFiles(state);
const selectedFilesForAction = action.fileFilter ? selectedFiles.filter(action.fileFilter) : selectedFiles;
if (action.requiresSelection && selectedFilesForAction.length === 0) {
Logger.warn(
`Internal components requested the "${action.id}" file ` +
`action, but the selection for this action was empty. This ` +
`might a bug in the code of the presentational components.`
);
return;
}
const contextMenuTriggerFile = selectContextMenuTriggerFile(state);
const actionState: FileActionState<{}> = {
instanceId,
selectedFiles,
selectedFilesForAction,
contextMenuTriggerFile,
};
// === Update sort state if necessary
const sortKeySelector = action.sortKeySelector;
if (sortKeySelector) dispatch(thunkActivateSortAction(action.id));
// === Update file view state if necessary
const fileViewConfig = action.fileViewConfig;
if (fileViewConfig) dispatch(reduxActions.setFileViewConfig(fileViewConfig));
// === Update option state if necessary
const option = action.option;
if (option) dispatch(reduxActions.toggleOption(option.id));
// === Apply selection transform if necessary
const selectionTransform = action.selectionTransform;
if (selectionTransform) dispatch(thunkApplySelectionTransform(action));
// Apply the effect
const effect = action.effect;
let maybeEffectPromise: MaybePromise<boolean | undefined> = undefined;
if (effect) {
try {
maybeEffectPromise = effect({
action,
payload,
state: actionState,
reduxDispatch: dispatch,
getReduxState: getState,
}) as MaybePromise<boolean | undefined>;
} catch (error) {
Logger.error(`User-defined effect function for action ${action.id} threw an ` + `error: ${error.message}`);
}
}
// Dispatch the action to user code. Deliberately call it after all other
// operations are over.
return Promise.resolve(maybeEffectPromise)
.then(effectResult => {
const data: FileActionData<Action> = {
id: action.id,
action,
payload,
state: actionState,
};
triggerDispatchAfterEffect(dispatch, data, effectResult);
})
.catch(error => {
Logger.error(
`User-defined effect function for action ${action.id} returned a ` +
`promise that was rejected: ${error.message}`
);
const data: FileActionData<Action> = {
id: action.id,
action,
payload,
state: actionState,
};
triggerDispatchAfterEffect(dispatch, data, undefined);
});
};
export const triggerDispatchAfterEffect = <Action extends FileAction>(
dispatch: ChonkyDispatch,
data: FileActionData<Action>,
effectResult: Undefinable<boolean>
) => {
const preventDispatch = effectResult === true;
if (!preventDispatch) dispatch(thunkDispatchFileAction(data));
};