Skip to content

Commit eb1565b

Browse files
committed
hack in a multieditor
1 parent b79dd27 commit eb1565b

File tree

7 files changed

+161
-5
lines changed

7 files changed

+161
-5
lines changed

ui/frontend/MultiEditor.tsx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React, { useCallback } from 'react';
2+
import { useSelector } from 'react-redux';
3+
import { noop } from 'lodash';
4+
5+
import * as selectors from './selectors';
6+
import { Entries } from './types';
7+
import * as actions from './actions';
8+
import { createSelector } from 'reselect';
9+
10+
import SimpleEditor from './editor/SimpleEditor';
11+
import { useAppDispatch } from './configureStore';
12+
13+
const entriesToSaveSelector = createSelector(
14+
selectors.entriesSelector,
15+
(entries) => {
16+
const names = Object.keys(entries);
17+
const toBeSaved: Entries = {};
18+
for (const name of names) {
19+
const entry = entries[name];
20+
if (!entry.lastSaved || entry.lastEdited > entry.lastSaved) {
21+
toBeSaved[name] = entry;
22+
}
23+
}
24+
return toBeSaved;
25+
}
26+
);
27+
28+
const entryNamesSelector = createSelector(selectors.entriesSelector, Object.keys);
29+
30+
const doSave = (): actions.ThunkAction => (dispatch, getState) => {
31+
const state = getState();
32+
const toSave = entriesToSaveSelector(state);
33+
dispatch(actions.saveFiles(toSave));
34+
};
35+
36+
const MultiEditor: React.FC = () => {
37+
const entryNames = useSelector(entryNamesSelector);
38+
const entry = useSelector(selectors.currentEntrySelector);
39+
40+
const toSave = useSelector(entriesToSaveSelector);
41+
const toSaveNames = Object.keys(toSave);
42+
43+
const dispatch = useAppDispatch()
44+
45+
const clickSave = useCallback(() => dispatch(doSave()), [dispatch]);
46+
47+
return (
48+
<>
49+
<div>I am the multieditor</div>
50+
{ toSaveNames.map(n => <b key={n}>{n}</b>) }
51+
<button onClick={clickSave}>Go Save Now</button>
52+
53+
{ entryNames.map((name) => (
54+
<button key={name} onClick={() => dispatch(actions.selectFile(name))}>{name}</button>
55+
)) }
56+
<SimpleEditor
57+
code={entry.code}
58+
execute={noop}
59+
onEditCode={newCode => dispatch(actions.editFile(entry.name, newCode))}
60+
position={entry.position}
61+
selection={entry.selection}
62+
crates={[]} />
63+
</>
64+
);
65+
};
66+
67+
export default MultiEditor;

ui/frontend/Playground.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useDispatch, useSelector } from 'react-redux';
33
import Split from 'split-grid';
44

55
import Editor from './editor/Editor';
6+
import MultiEditor from './MultiEditor';
67
import Header from './Header';
78
import Notifications from './Notifications';
89
import Output from './Output';
@@ -76,7 +77,7 @@ const ResizableArea: React.FC = () => {
7677

7778
return (
7879
<div ref={grid} className={gridStyle}>
79-
<div className={styles.editor}><Editor /></div>
80+
<div className={styles.editor}><MultiEditor /></div>
8081
{ isFocused &&
8182
<div ref={dragHandle} className={handleOuterStyle}>
8283
<span className={handleInnerStyle}></span>

ui/frontend/actions.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
DemangleAssembly,
2020
Edition,
2121
Editor,
22+
Entries,
2223
Focus,
2324
Mode,
2425
Notification,
@@ -105,6 +106,9 @@ export enum ActionType {
105106
EnableFeatureGate = 'ENABLE_FEATURE_GATE',
106107
GotoPosition = 'GOTO_POSITION',
107108
SelectText = 'SELECT_TEXT',
109+
EditFile = 'EDIT_FILE',
110+
SelectFile = 'SELECT_FILE',
111+
SaveFiles = 'SAVE_FILES',
108112
RequestFormat = 'REQUEST_FORMAT',
109113
FormatSucceeded = 'FORMAT_SUCCEEDED',
110114
FormatFailed = 'FORMAT_FAILED',
@@ -573,6 +577,13 @@ export const gotoPosition = (line: string | number, column: string | number) =>
573577
export const selectText = (start: Position, end: Position) =>
574578
createAction(ActionType.SelectText, { start, end });
575579

580+
export const editFile = (name: string, code: string) =>
581+
createAction(ActionType.EditFile, { name, code });
582+
583+
export const selectFile = (name: string) => createAction(ActionType.SelectFile, { name });
584+
585+
export const saveFiles = (entries: Entries) => createAction(ActionType.SaveFiles, { entries });
586+
576587
const requestFormat = () =>
577588
createAction(ActionType.RequestFormat);
578589

@@ -1002,6 +1013,9 @@ export type Action =
10021013
| ReturnType<typeof enableFeatureGate>
10031014
| ReturnType<typeof gotoPosition>
10041015
| ReturnType<typeof selectText>
1016+
| ReturnType<typeof editFile>
1017+
| ReturnType<typeof selectFile>
1018+
| ReturnType<typeof saveFiles>
10051019
| ReturnType<typeof requestFormat>
10061020
| ReturnType<typeof receiveFormatSuccess>
10071021
| ReturnType<typeof receiveFormatFailure>

ui/frontend/reducers/files.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,32 @@ const DEFAULT_MAIN_RS = `fn main() {
55
println!("Hello, world!");
66
}`;
77

8+
const DEFAULT_CARGO_TOML = `[package]
9+
name = "playground"
10+
version = "0.0.1"
11+
authors = ["The Rust Playground"]
12+
resolver = "2"
13+
14+
[profile.dev]
15+
codegen-units = 1
16+
incremental = false
17+
18+
[profile.dev.build-override]
19+
codegen-units = 1
20+
21+
[profile.release]
22+
codegen-units = 1
23+
incremental = false
24+
25+
[profile.release.build-override]
26+
codegen-units = 1
27+
28+
[dependencies.itertools]
29+
package = "itertools"
30+
version = "=0.10.5"
31+
features = ["use_alloc", "use_std"]
32+
`
33+
834
const makeFile = (code: string): File => ({
935
code,
1036
position: makePosition(0, 0),
@@ -19,6 +45,14 @@ export const makeState = (code: string): State => ({
1945
entries: { [SINGLE_FILE_FILENAME]: makeFile(code) },
2046
});
2147

48+
// const DEFAULT: State = {
49+
// current: 'main.rs',
50+
// entries: {
51+
// 'main.rs': makeFile(DEFAULT_MAIN_RS),
52+
// 'Cargo.toml': makeFile(DEFAULT_CARGO_TOML),
53+
// },
54+
// };
55+
2256
const DEFAULT: State = makeState(DEFAULT_MAIN_RS);
2357

2458
export type State = {
@@ -79,6 +113,33 @@ export default function files(state = DEFAULT, action: Action): State {
79113
});
80114
}
81115

116+
// These are actions for the multi-file mode
117+
118+
case ActionType.EditFile: {
119+
const { name, code } = action;
120+
let entries = state.entries;
121+
let entry = entries[name];
122+
entry = { ...entry, code, lastEdited: new Date() };
123+
entries = { ...entries, [name]: entry };
124+
return { ...state, entries };
125+
}
126+
127+
case ActionType.SelectFile: {
128+
return { ...state, current: action.name };
129+
}
130+
131+
case ActionType.SaveFiles: {
132+
const { entries: toSave } = action;
133+
let entries = state.entries;
134+
const lastSaved = new Date();
135+
for (const name of Object.keys(toSave)) {
136+
let entry = entries[name];
137+
entry = { ...entry, lastSaved };
138+
entries = { ...entries, [name]: entry };
139+
}
140+
return { ...state, entries };
141+
}
142+
82143
default:
83144
return state;
84145
}

ui/frontend/selectors/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ import {
1414
Version,
1515
} from '../types';
1616

17-
const currentEntryNameSelector = (state: State) => state.files.current;
18-
const entriesSelector = (state: State) => state.files.entries;
17+
export const currentEntryNameSelector = (state: State) => state.files.current;
18+
export const entriesSelector = (state: State) => state.files.entries;
1919

20-
const currentEntrySelector = createSelector(
20+
export const currentEntrySelector = createSelector(
2121
currentEntryNameSelector,
2222
entriesSelector,
2323
(name, entries) => ({ ...entries[name], name }));

ui/frontend/websocketMiddleware.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,5 @@ export const websocketMiddleware = (window: Window): Middleware => store => {
113113
}
114114

115115
const sendActionOnWebsocket = (action: any): boolean =>
116-
action.type === ActionType.WSExecuteRequest;
116+
action.type === ActionType.WSExecuteRequest
117+
|| action.type === ActionType.SaveFiles;

ui/src/server_axum/websocket.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::{
88
use axum::extract::ws::{Message, WebSocket};
99
use snafu::prelude::*;
1010
use std::{
11+
collections::BTreeMap,
1112
convert::{TryFrom, TryInto},
1213
time::Instant,
1314
};
@@ -18,6 +19,14 @@ use tokio::{sync::mpsc, task::JoinSet};
1819
enum WSMessageRequest {
1920
#[serde(rename = "WS_EXECUTE_REQUEST")]
2021
WSExecuteRequest(WSExecuteRequest),
22+
23+
#[serde(rename = "SAVE_FILES")]
24+
SaveFiles { entries: BTreeMap<String, File> },
25+
}
26+
27+
#[derive(Debug, serde::Deserialize)]
28+
struct File {
29+
code: String,
2130
}
2231

2332
#[derive(serde::Deserialize)]
@@ -203,6 +212,9 @@ async fn handle_msg(
203212
Ok(())
204213
});
205214
}
215+
Ok(SaveFiles { entries }) => {
216+
dbg!(entries);
217+
}
206218
Err(e) => {
207219
let resp = Err(e);
208220
tx.send(resp).await.ok(/* We don't care if the channel is closed */);

0 commit comments

Comments
 (0)