Skip to content

Commit db67b2d

Browse files
committed
Rewrite gist reducer with RTK
1 parent 9fbb986 commit db67b2d

File tree

7 files changed

+111
-130
lines changed

7 files changed

+111
-130
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/gist.ts',
6869
'websocketMiddleware.ts',
6970
],
7071
extends: ['prettier'],

ui/frontend/.prettierignore

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

ui/frontend/Header.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import ToolsMenu from './ToolsMenu';
1515
import * as actions from './actions';
1616
import * as selectors from './selectors';
1717
import { useAppDispatch } from './configureStore';
18+
import { performGistSave } from './reducers/output/gist';
1819

1920
import styles from './Header.module.css';
2021

@@ -132,7 +133,7 @@ const AdvancedOptionsMenuButton: React.FC = () => {
132133

133134
const ShareButton: React.FC = () => {
134135
const dispatch = useAppDispatch();
135-
const gistSave = useCallback(() => dispatch(actions.performGistSave()), [dispatch]);
136+
const gistSave = useCallback(() => dispatch(performGistSave()), [dispatch]);
136137

137138
return (
138139
<SegmentedButton title="Create shareable links to this code" onClick={gistSave}>

ui/frontend/actions.ts

Lines changed: 5 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
getCrateType,
1010
runAsTest,
1111
useWebsocketSelector,
12-
baseUrlSelector,
1312
} from './selectors';
1413
import State from './state';
1514
import {
@@ -35,7 +34,9 @@ import {
3534
Crate,
3635
} from './types';
3736

38-
const routes = {
37+
import { performGistLoad } from './reducers/output/gist';
38+
39+
export const routes = {
3940
compile: '/compile',
4041
execute: '/execute',
4142
format: '/format',
@@ -118,12 +119,6 @@ export enum ActionType {
118119
RequestMacroExpansion = 'REQUEST_MACRO_EXPANSION',
119120
MacroExpansionSucceeded = 'MACRO_EXPANSION_SUCCEEDED',
120121
MacroExpansionFailed = 'MACRO_EXPANSION_FAILED',
121-
RequestGistLoad = 'REQUEST_GIST_LOAD',
122-
GistLoadSucceeded = 'GIST_LOAD_SUCCEEDED',
123-
GistLoadFailed = 'GIST_LOAD_FAILED',
124-
RequestGistSave = 'REQUEST_GIST_SAVE',
125-
GistSaveSucceeded = 'GIST_SAVE_SUCCEEDED',
126-
GistSaveFailed = 'GIST_SAVE_FAILED',
127122
RequestCratesLoad = 'REQUEST_CRATES_LOAD',
128123
CratesLoadSucceeded = 'CRATES_LOAD_SUCCEEDED',
129124
RequestVersionsLoad = 'REQUEST_VERSIONS_LOAD',
@@ -235,13 +230,13 @@ const receiveExecuteFailure = ({ error }: { error?: string }) =>
235230

236231
type FetchArg = Parameters<typeof fetch>[0];
237232

238-
function jsonGet(url: FetchArg) {
233+
export function jsonGet(url: FetchArg) {
239234
return fetchJson(url, {
240235
method: 'get',
241236
});
242237
}
243238

244-
function jsonPost<T>(url: FetchArg, body: Record<string, any>): Promise<T> {
239+
export function jsonPost<T>(url: FetchArg, body: Record<string, any>): Promise<T> {
245240
return fetchJson(url, {
246241
method: 'post',
247242
body: JSON.stringify(body),
@@ -733,83 +728,6 @@ export function performMacroExpansion(): ThunkAction {
733728
};
734729
}
735730

736-
interface GistSuccessProps {
737-
id: string;
738-
url: string;
739-
code: string;
740-
stdout: string;
741-
stderr: string;
742-
channel: Channel;
743-
mode: Mode;
744-
edition: Edition;
745-
}
746-
747-
const requestGistLoad = () =>
748-
createAction(ActionType.RequestGistLoad);
749-
750-
const receiveGistLoadSuccess = (props: GistSuccessProps) =>
751-
createAction(ActionType.GistLoadSucceeded, props);
752-
753-
const receiveGistLoadFailure = () => // eslint-disable-line no-unused-vars
754-
createAction(ActionType.GistLoadFailed);
755-
756-
type PerformGistLoadProps =
757-
Pick<GistSuccessProps, Exclude<keyof GistSuccessProps, 'url' | 'code' | 'stdout' | 'stderr'>>;
758-
759-
export function performGistLoad({ id, channel, mode, edition }: PerformGistLoadProps): ThunkAction {
760-
return function(dispatch, getState) {
761-
dispatch(requestGistLoad());
762-
763-
const state = getState();
764-
const baseUrl = baseUrlSelector(state);
765-
const gistUrl = new URL(routes.meta.gistLoad, baseUrl);
766-
const u = new URL(id, gistUrl);
767-
768-
jsonGet(u)
769-
.then(gist => dispatch(receiveGistLoadSuccess({ channel, mode, edition, ...gist })));
770-
// TODO: Failure case
771-
};
772-
}
773-
774-
const requestGistSave = () =>
775-
createAction(ActionType.RequestGistSave);
776-
777-
const receiveGistSaveSuccess = (props: GistSuccessProps) =>
778-
createAction(ActionType.GistSaveSucceeded, props);
779-
780-
const receiveGistSaveFailure = ({ error }: CompileFailure) => // eslint-disable-line no-unused-vars
781-
createAction(ActionType.GistSaveFailed, { error });
782-
783-
interface GistResponseBody {
784-
id: string;
785-
url: string;
786-
code: string;
787-
}
788-
789-
export function performGistSave(): ThunkAction {
790-
return function(dispatch, getState) {
791-
dispatch(requestGistSave());
792-
793-
const state = getState();
794-
const code = codeSelector(state);
795-
const {
796-
configuration: {
797-
channel, mode, edition,
798-
},
799-
output: {
800-
execute: {
801-
stdout = '',
802-
stderr = '',
803-
},
804-
},
805-
} = state;
806-
807-
return jsonPost<GistResponseBody>(routes.meta.gistSave, { code })
808-
.then(json => dispatch(receiveGistSaveSuccess({ ...json, code, stdout, stderr, channel, mode, edition })));
809-
// TODO: Failure case
810-
};
811-
}
812-
813731
const requestCratesLoad = () =>
814732
createAction(ActionType.RequestCratesLoad);
815733

@@ -1018,12 +936,6 @@ export type Action =
1018936
| ReturnType<typeof requestMacroExpansion>
1019937
| ReturnType<typeof receiveMacroExpansionSuccess>
1020938
| ReturnType<typeof receiveMacroExpansionFailure>
1021-
| ReturnType<typeof requestGistLoad>
1022-
| ReturnType<typeof receiveGistLoadSuccess>
1023-
| ReturnType<typeof receiveGistLoadFailure>
1024-
| ReturnType<typeof requestGistSave>
1025-
| ReturnType<typeof receiveGistSaveSuccess>
1026-
| ReturnType<typeof receiveGistSaveFailure>
1027939
| ReturnType<typeof requestCratesLoad>
1028940
| ReturnType<typeof receiveCratesLoadSuccess>
1029941
| ReturnType<typeof requestVersionsLoad>

ui/frontend/reducers/code.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Action, ActionType } from '../actions';
2+
import { performGistLoad } from './output/gist'
23

34
const DEFAULT: State = `fn main() {
45
println!("Hello, world!");
@@ -8,11 +9,6 @@ export type State = string;
89

910
export default function code(state = DEFAULT, action: Action): State {
1011
switch (action.type) {
11-
case ActionType.RequestGistLoad:
12-
return '';
13-
case ActionType.GistLoadSucceeded:
14-
return action.code;
15-
1612
case ActionType.EditCode:
1713
return action.code;
1814

@@ -28,7 +24,14 @@ export default function code(state = DEFAULT, action: Action): State {
2824
case ActionType.FormatSucceeded:
2925
return action.code;
3026

31-
default:
32-
return state;
27+
default: {
28+
if (performGistLoad.pending.match(action)) {
29+
return '';
30+
} else if (performGistLoad.fulfilled.match(action)) {
31+
return action.payload.code;
32+
} else {
33+
return state;
34+
}
35+
}
3336
}
3437
}

ui/frontend/reducers/output/gist.ts

Lines changed: 84 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
import { Action, ActionType } from '../../actions';
1+
import { Draft, PayloadAction, createAsyncThunk, createSlice } from '@reduxjs/toolkit';
2+
3+
import { jsonGet, jsonPost, routes } from '../../actions';
4+
import { baseUrlSelector, codeSelector } from '../../selectors';
5+
import RootState from '../../state';
26
import { Channel, Edition, Mode } from '../../types';
3-
import { finish, RequestsInProgress, start } from './sharedStateManagement';
7+
import { RequestsInProgress } from './sharedStateManagement';
8+
9+
const sliceName = 'output/gist';
410

5-
const DEFAULT: State = {
11+
const initialState: State = {
612
requestsInProgress: 0,
713
};
814

@@ -15,26 +21,81 @@ interface State extends RequestsInProgress {
1521
channel?: Channel;
1622
mode?: Mode;
1723
edition?: Edition;
18-
error?: string;
1924
}
2025

21-
export default function gist(state = DEFAULT, action: Action): State {
22-
switch (action.type) {
23-
case ActionType.RequestGistLoad:
24-
case ActionType.RequestGistSave:
25-
return start(DEFAULT, state);
26-
27-
case ActionType.GistLoadSucceeded:
28-
case ActionType.GistSaveSucceeded: {
29-
const { id, url, code, stdout, stderr, channel, mode, edition } = action;
30-
return finish(state, { id, url, code, stdout, stderr, channel, mode, edition });
31-
}
32-
33-
case ActionType.GistLoadFailed:
34-
case ActionType.GistSaveFailed:
35-
return finish(state, { error: 'Some kind of error' });
36-
37-
default:
38-
return state;
39-
}
26+
interface SuccessProps {
27+
id: string;
28+
url: string;
29+
code: string;
30+
stdout: string;
31+
stderr: string;
32+
channel: Channel;
33+
mode: Mode;
34+
edition: Edition;
35+
}
36+
37+
type PerformGistLoadProps = Pick<
38+
SuccessProps,
39+
Exclude<keyof SuccessProps, 'url' | 'code' | 'stdout' | 'stderr'>
40+
>;
41+
42+
interface GistResponseBody {
43+
id: string;
44+
url: string;
45+
code: string;
4046
}
47+
48+
export const performGistLoad = createAsyncThunk<
49+
SuccessProps,
50+
PerformGistLoadProps,
51+
{ state: RootState }
52+
>(`${sliceName}/load`, async ({ id, channel, mode, edition }, { getState }) => {
53+
const state = getState();
54+
const baseUrl = baseUrlSelector(state);
55+
const gistUrl = new URL(routes.meta.gistLoad, baseUrl);
56+
const u = new URL(id, gistUrl);
57+
58+
const gist = await jsonGet(u);
59+
return { channel, mode, edition, ...gist };
60+
});
61+
62+
export const performGistSave = createAsyncThunk<SuccessProps, void, { state: RootState }>(
63+
`${sliceName}/save`,
64+
async (_arg, { getState }) => {
65+
const state = getState();
66+
const code = codeSelector(state);
67+
const {
68+
configuration: { channel, mode, edition },
69+
output: {
70+
execute: { stdout = '', stderr = '' },
71+
},
72+
} = state;
73+
74+
const json = await jsonPost<GistResponseBody>(routes.meta.gistSave, { code });
75+
return { ...json, code, stdout, stderr, channel, mode, edition };
76+
},
77+
);
78+
79+
const pending = (state: Draft<State>) => {
80+
state.requestsInProgress += 1;
81+
};
82+
83+
const fulfilled = (state: Draft<State>, action: PayloadAction<SuccessProps>) => {
84+
state.requestsInProgress -= 1;
85+
Object.assign(state, action.payload);
86+
};
87+
88+
const slice = createSlice({
89+
name: sliceName,
90+
initialState,
91+
reducers: {},
92+
extraReducers: (builder) => {
93+
builder
94+
.addCase(performGistLoad.pending, pending)
95+
.addCase(performGistLoad.fulfilled, fulfilled)
96+
.addCase(performGistSave.pending, pending)
97+
.addCase(performGistSave.fulfilled, fulfilled);
98+
},
99+
});
100+
101+
export default slice.reducer;

ui/frontend/reducers/output/meta.ts

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

45
const DEFAULT: State = {
56
};
@@ -46,11 +47,12 @@ export default function meta(state = DEFAULT, action: Action) {
4647
case ActionType.FormatSucceeded:
4748
return { ...state, focus: undefined };
4849

49-
case ActionType.RequestGistLoad:
50-
case ActionType.RequestGistSave:
51-
return { ...state, focus: Focus.Gist };
52-
53-
default:
54-
return state;
50+
default: {
51+
if (performGistLoad.pending.match(action) || performGistSave.pending.match(action)) {
52+
return { ...state, focus: Focus.Gist };
53+
} else {
54+
return state;
55+
}
56+
}
5557
}
5658
}

0 commit comments

Comments
 (0)