Skip to content

Commit c2e0ec6

Browse files
committed
Rewrite format reducer with RTK
1 parent db67b2d commit c2e0ec6

File tree

7 files changed

+75
-72
lines changed

7 files changed

+75
-72
lines changed

ui/frontend/.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ module.exports = {
6565
'PopButton.tsx',
6666
'editor/AceEditor.tsx',
6767
'editor/SimpleEditor.tsx',
68+
'reducers/output/format.ts',
6869
'reducers/output/gist.ts',
6970
'websocketMiddleware.ts',
7071
],

ui/frontend/.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ node_modules
1414
!PopButton.tsx
1515
!editor/AceEditor.tsx
1616
!editor/SimpleEditor.tsx
17+
!reducers/output/format.ts
1718
!reducers/output/gist.ts
1819
!websocketMiddleware.ts

ui/frontend/ToolsMenu.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import MenuAside from './MenuAside';
88
import * as selectors from './selectors';
99
import * as actions from './actions';
1010
import { useAppDispatch } from './configureStore';
11+
import { performFormat } from './reducers/output/format';
1112

1213
interface ToolsMenuProps {
1314
close: () => void;
@@ -33,7 +34,7 @@ const ToolsMenu: React.FC<ToolsMenuProps> = props => {
3334
props.close();
3435
}, [dispatch, props]);
3536
const format = useCallback(() => {
36-
dispatch(actions.performFormat());
37+
dispatch(performFormat());
3738
props.close();
3839
}, [dispatch, props]);
3940
const expandMacros = useCallback(() => {

ui/frontend/actions.ts

Lines changed: 15 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { z } from 'zod';
55
import {
66
codeSelector,
77
clippyRequestSelector,
8-
formatRequestSelector,
98
getCrateType,
109
runAsTest,
1110
useWebsocketSelector,
@@ -107,9 +106,6 @@ export enum ActionType {
107106
EnableFeatureGate = 'ENABLE_FEATURE_GATE',
108107
GotoPosition = 'GOTO_POSITION',
109108
SelectText = 'SELECT_TEXT',
110-
RequestFormat = 'REQUEST_FORMAT',
111-
FormatSucceeded = 'FORMAT_SUCCEEDED',
112-
FormatFailed = 'FORMAT_FAILED',
113109
RequestClippy = 'REQUEST_CLIPPY',
114110
ClippySucceeded = 'CLIPPY_SUCCEEDED',
115111
ClippyFailed = 'CLIPPY_FAILED',
@@ -287,6 +283,21 @@ async function fetchJson(url: FetchArg, args: RequestInit) {
287283
}
288284
}
289285

286+
// We made some strange decisions with how the `fetchJson` function
287+
// communicates errors, so we untwist those here to fit better with
288+
// redux-toolkit's ideas.
289+
export const adaptFetchError = async <R>(cb: () => Promise<R>): Promise<R> => {
290+
try {
291+
return await cb();
292+
} catch (e) {
293+
if (e && typeof e === 'object' && 'error' in e && typeof e.error === 'string') {
294+
throw new Error(e.error);
295+
} else {
296+
throw new Error('An unknown error occurred');
297+
}
298+
}
299+
}
300+
290301
interface ExecuteRequestBody {
291302
channel: string;
292303
mode: string;
@@ -567,46 +578,6 @@ export const gotoPosition = (line: string | number, column: string | number) =>
567578
export const selectText = (start: Position, end: Position) =>
568579
createAction(ActionType.SelectText, { start, end });
569580

570-
const requestFormat = () =>
571-
createAction(ActionType.RequestFormat);
572-
573-
interface FormatRequestBody {
574-
code: string;
575-
edition: string;
576-
}
577-
578-
interface FormatResponseBody {
579-
success: boolean;
580-
code: string;
581-
stdout: string;
582-
stderr: string;
583-
}
584-
585-
const receiveFormatSuccess = (body: FormatResponseBody) =>
586-
createAction(ActionType.FormatSucceeded, body);
587-
588-
const receiveFormatFailure = (body: FormatResponseBody) =>
589-
createAction(ActionType.FormatFailed, body);
590-
591-
export function performFormat(): ThunkAction {
592-
// TODO: Check a cache
593-
return function(dispatch, getState) {
594-
dispatch(requestFormat());
595-
596-
const body: FormatRequestBody = formatRequestSelector(getState());
597-
598-
return jsonPost<FormatResponseBody>(routes.format, body)
599-
.then(json => {
600-
if (json.success) {
601-
dispatch(receiveFormatSuccess(json));
602-
} else {
603-
dispatch(receiveFormatFailure(json));
604-
}
605-
})
606-
.catch(json => dispatch(receiveFormatFailure(json)));
607-
};
608-
}
609-
610581
interface GeneralSuccess {
611582
stdout: string;
612583
stderr: string;
@@ -924,9 +895,6 @@ export type Action =
924895
| ReturnType<typeof enableFeatureGate>
925896
| ReturnType<typeof gotoPosition>
926897
| ReturnType<typeof selectText>
927-
| ReturnType<typeof requestFormat>
928-
| ReturnType<typeof receiveFormatSuccess>
929-
| ReturnType<typeof receiveFormatFailure>
930898
| ReturnType<typeof requestClippy>
931899
| ReturnType<typeof receiveClippySuccess>
932900
| ReturnType<typeof receiveClippyFailure>

ui/frontend/reducers/code.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Action, ActionType } from '../actions';
22
import { performGistLoad } from './output/gist'
3+
import { performFormat } from './output/format'
34

45
const DEFAULT: State = `fn main() {
56
println!("Hello, world!");
@@ -21,14 +22,13 @@ export default function code(state = DEFAULT, action: Action): State {
2122
case ActionType.EnableFeatureGate:
2223
return `#![feature(${action.featureGate})]\n${state}`;
2324

24-
case ActionType.FormatSucceeded:
25-
return action.code;
26-
2725
default: {
2826
if (performGistLoad.pending.match(action)) {
2927
return '';
3028
} else if (performGistLoad.fulfilled.match(action)) {
3129
return action.payload.code;
30+
} else if (performFormat.fulfilled.match(action)) {
31+
return action.payload.code;
3232
} else {
3333
return state;
3434
}

ui/frontend/reducers/output/format.ts

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import { Action, ActionType } from '../../actions';
2-
import { finish, start } from './sharedStateManagement';
1+
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
32

4-
const DEFAULT: State = {
3+
import { adaptFetchError, jsonPost, routes } from '../../actions';
4+
import { formatRequestSelector } from '../../selectors';
5+
import RootState from '../../state';
6+
7+
const sliceName = 'output/format';
8+
9+
const initialState: State = {
510
requestsInProgress: 0,
611
};
712

@@ -11,17 +16,44 @@ interface State {
1116
stderr?: string;
1217
}
1318

14-
export default function format(state = DEFAULT, action: Action): State {
15-
switch (action.type) {
16-
case ActionType.RequestFormat:
17-
return start(DEFAULT, state);
18-
case ActionType.FormatSucceeded:
19-
return finish(state);
20-
case ActionType.FormatFailed: {
21-
const { stdout = '', stderr = '' } = action;
22-
return finish(state, { stdout, stderr });
23-
}
24-
default:
25-
return state;
26-
}
19+
interface FormatRequestBody {
20+
code: string;
21+
edition: string;
22+
}
23+
24+
interface FormatResponseBody {
25+
success: boolean;
26+
code: string;
27+
stdout: string;
28+
stderr: string;
2729
}
30+
31+
export const performFormat = createAsyncThunk<FormatResponseBody, void, { state: RootState }>(
32+
sliceName,
33+
async (_arg: void, { getState }) => {
34+
const body: FormatRequestBody = formatRequestSelector(getState());
35+
36+
return adaptFetchError(() => jsonPost<FormatResponseBody>(routes.format, body));
37+
},
38+
);
39+
40+
const slice = createSlice({
41+
name: sliceName,
42+
initialState,
43+
reducers: {},
44+
extraReducers: (builder) => {
45+
builder
46+
.addCase(performFormat.pending, (state) => {
47+
state.requestsInProgress += 1;
48+
})
49+
.addCase(performFormat.fulfilled, (state, action) => {
50+
state.requestsInProgress -= 1;
51+
Object.assign(state, action.payload);
52+
})
53+
.addCase(performFormat.rejected, (state) => {
54+
state.requestsInProgress -= 1;
55+
});
56+
},
57+
});
58+
59+
export default slice.reducer;

ui/frontend/reducers/output/meta.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Action, ActionType } from '../../actions';
22
import { Focus } from '../../types';
33
import { performGistLoad, performGistSave } from './gist';
4+
import { performFormat } from './format';
45

56
const DEFAULT: State = {
67
};
@@ -42,14 +43,13 @@ export default function meta(state = DEFAULT, action: Action) {
4243
case ActionType.WSExecuteRequest:
4344
return { ...state, focus: Focus.Execute };
4445

45-
case ActionType.RequestFormat:
46-
return { ...state, focus: Focus.Format };
47-
case ActionType.FormatSucceeded:
48-
return { ...state, focus: undefined };
49-
5046
default: {
5147
if (performGistLoad.pending.match(action) || performGistSave.pending.match(action)) {
5248
return { ...state, focus: Focus.Gist };
49+
} else if (performFormat.pending.match(action)) {
50+
return { ...state, focus: Focus.Format };
51+
} else if (performFormat.fulfilled.match(action)) {
52+
return { ...state, focus: undefined };
5353
} else {
5454
return state;
5555
}

0 commit comments

Comments
 (0)