Skip to content

Commit 86139b3

Browse files
committed
Update frontend to togle and display different notebooks
1 parent 18d7b3f commit 86139b3

File tree

10 files changed

+151
-58
lines changed

10 files changed

+151
-58
lines changed

src/frontend/components/Cell/index.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import { cleanErrorMessage } from "../../lib/helpers/utils"
1313

1414
const NoteBookCell = ({ cell }: { cell: NbCell }) => {
1515
const dispatch = useDispatch();
16-
const { cells, interpreterMode } = useSelector((state: { app: AppState }) => state.app)
16+
const { notebooks, activeNotebookName, interpreterMode } = useSelector((state: { app: AppState }) => state.app)
17+
const notebook = notebooks[activeNotebookName]
18+
const { cells } = notebook
19+
1720

1821
const [cellIsRunning, setCellIsRunning] = useState(false)
1922
const [output, setOutput] = useState("")
@@ -30,15 +33,15 @@ const NoteBookCell = ({ cell }: { cell: NbCell }) => {
3033

3134
const newCurrCell = { ...cell, output: "", outputError: fullErrorMessage }
3235
const newCells = { ...cells, [cell.id]: newCurrCell }
33-
dispatch(updateCells(newCells))
36+
dispatch(updateCells({ newCells, activeNotebookName }))
3437

3538
} else {
3639
setHasError(false)
3740
setOutput(accumulatedResult as string)
3841

3942
const newCurrCell = { ...cell, output: accumulatedResult as string, outputError: "" }
4043
const newCells = { ...cells, [cell.id]: newCurrCell }
41-
dispatch(updateCells(newCells))
44+
dispatch(updateCells({ newCells, activeNotebookName }))
4245

4346
}
4447

@@ -58,7 +61,7 @@ const NoteBookCell = ({ cell }: { cell: NbCell }) => {
5861
setOutputError("")
5962

6063
if (interpreterMode === 'node') {
61-
NodejsInterpreter.exec(content, language, cellRunCallback)
64+
NodejsInterpreter.exec({ content, language, callback: cellRunCallback, activeNotebookName })
6265
.catch((error) => {
6366
setOutputError({
6467
...error,
@@ -103,14 +106,10 @@ const NoteBookCell = ({ cell }: { cell: NbCell }) => {
103106
</div>
104107

105108
<div className="col-span-11"
106-
onKeyPress={handleKeyPress}
109+
onKeyPress={handleKeyPress}
107110
>
108111
<CellEditor cell={cell} />
109-
<CellOutputRenderer
110-
hasError={hasError}
111-
output={output}
112-
outputError={outputError}
113-
/>
112+
<CellOutputRenderer cell={cell} />
114113
</div>
115114

116115
</section>

src/frontend/components/CellEditor/index.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,20 @@ const AceEditor = dynamic(
2323

2424

2525
const Editor = ({ cell }: { cell: NbCell }) => {
26-
const { config, cells } = useSelector((state: { app: AppState }) => state.app)
27-
2826
const dispatch = useDispatch();
27+
28+
const { notebooks, activeNotebookName, config } = useSelector((state: { app: AppState }) => state.app)
29+
const notebook = notebooks[activeNotebookName]
30+
const { cells } = notebook
31+
2932
const [code, updateCode] = useState(cell?.content);
3033

3134
const handleCodeChange = (newCode: any) => {
3235
updateCode(newCode);
3336
const newCurrCell = { ...cell, content: newCode }
3437

3538
const newCells = { ...cells, [cell.id]: newCurrCell }
36-
dispatch(updateCells(newCells))
39+
dispatch(updateCells({ newCells, activeNotebookName }))
3740
}
3841

3942
return (

src/frontend/components/CellOutputRenderer/index.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1+
import { NbCell } from "../../lib/typings/types";
12

23

3-
const cellOutputRenderer = ({ output, hasError, outputError }: { output: string, hasError: boolean, outputError: string }) => {
4+
const cellOutputRenderer = ({ cell }: { cell: NbCell }) => {
45
return (
56
<div>
6-
{hasError ? (
7-
<div className="text-red-400 p-3" dangerouslySetInnerHTML={{ __html: outputError }}></div>
7+
{cell.outputError !== "" ? (
8+
<div className="text-red-400 p-3" dangerouslySetInnerHTML={{ __html: cell.outputError }}></div>
89
) : (
910
<div>
10-
<div className="p-3" dangerouslySetInnerHTML={{ __html: output }}></div>
11+
<div className="p-3" dangerouslySetInnerHTML={{ __html: cell.output }}></div>
1112
</div>
1213
)}
1314
</div>

src/frontend/components/MenuBar/cellOptions.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ import { useState } from "react";
1919

2020
export default function CellOptions({ cell }: { cell: NbCell }) {
2121
const dispatch = useDispatch();
22-
const { cells, cellIds } = useSelector((state: { app: AppState }) => state.app)
22+
const { notebooks, activeNotebookName } = useSelector((state: { app: AppState }) => state.app)
23+
const notebook = notebooks[activeNotebookName]
24+
const { cellIds, cells } = notebook
25+
2326
const languages = Object.keys(cellLanguages) as LangaugeOption[];
2427

2528
const [copied, setCopied] = useState(false);
@@ -30,7 +33,7 @@ export default function CellOptions({ cell }: { cell: NbCell }) {
3033
const language = e.target.value as LangaugeOption
3134
const newCurrCell = { ...cell, mode: language }
3235
const newCells = { ...cells, [cell.id]: newCurrCell }
33-
dispatch(updateCells(newCells))
36+
dispatch(updateCells({ newCells, activeNotebookName }))
3437
}
3538

3639
const handleAddNewCellBelowCurrentCell = () => {
@@ -47,8 +50,8 @@ export default function CellOptions({ cell }: { cell: NbCell }) {
4750
newCellIds.splice(topCellIdIndex + 1, 0, newId)
4851

4952
const newCells = { ...cells, [newId]: newCell }
50-
dispatch(updateCells(newCells))
51-
dispatch(updateCellIds(newCellIds))
53+
dispatch(updateCells({ newCells, activeNotebookName }))
54+
dispatch(updateCellIds({ newCellIds, activeNotebookName }))
5255
}
5356

5457
const handleMoveCurrentCellUp = () => {
@@ -58,7 +61,7 @@ export default function CellOptions({ cell }: { cell: NbCell }) {
5861
newCellIds.splice(currentCellIdIndex - 1, 0, cell.id)
5962
newCellIds.splice(currentCellIdIndex + 1, 1)
6063

61-
dispatch(updateCellIds(newCellIds))
64+
dispatch(updateCellIds({ newCellIds, activeNotebookName }))
6265
}
6366

6467
const handleMoveCurrentCellDown = () => {
@@ -71,15 +74,15 @@ export default function CellOptions({ cell }: { cell: NbCell }) {
7174
newCellIds.splice(currentCellIdIndex, 1, downCellId)
7275
newCellIds.splice(downCellIdIndex, 1, cell.id)
7376

74-
dispatch(updateCellIds(newCellIds))
77+
dispatch(updateCellIds({ newCellIds, activeNotebookName }))
7578
}
7679

7780
const handleDeleteCurrentCell = () => {
7881
const newCellIds = [...cellIds]
7982
const currentCellIdIndex = cellIds.indexOf(cell.id)
8083
newCellIds.splice(currentCellIdIndex, 1)
8184

82-
dispatch(updateCellIds(newCellIds))
85+
dispatch(updateCellIds({ newCellIds, activeNotebookName }))
8386
}
8487

8588
const handleCopyCurrentCellContentToClipBoard = async () => {
@@ -134,6 +137,7 @@ export default function CellOptions({ cell }: { cell: NbCell }) {
134137
<IconButton
135138
aria-label="delete"
136139
color="warning"
140+
disabled={currentCellIdIndex === 0}
137141
onClick={handleDeleteCurrentCell}
138142
>
139143
<DeleteIcon />
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import NoteBookCell from '../Cell';
2+
import { useSelector } from "react-redux";
3+
import { AppState, NbCell } from '../../lib/typings/types';
4+
5+
const NotebookTab = () => {
6+
const { notebooks, activeNotebookName } = useSelector((state: { app: AppState }) => state.app)
7+
const notebook = notebooks[activeNotebookName]
8+
const { cellIds, cells } = notebook
9+
10+
return (
11+
<div className='col-span-12 border-l-2 ml-52'>
12+
{
13+
cellIds.map((cellId: string, i: number) => {
14+
const cell: NbCell = cells[cellId]
15+
return <div key={cellId}><NoteBookCell cell={cell} /></div>
16+
})
17+
}
18+
</div>
19+
)
20+
}
21+
22+
export default NotebookTab
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { useState } from 'react';
2+
import Tabs from '@mui/material/Tabs';
3+
import Tab from '@mui/material/Tab';
4+
import Box from '@mui/material/Box';
5+
import { AppState } from '../../lib/typings/types';
6+
import { useSelector, useDispatch } from 'react-redux';
7+
import { updateActiveNotebookName } from '../../lib/state/reducer';
8+
9+
export default function NotebookTabPanel() {
10+
const dispatch = useDispatch();
11+
const { notebooks } = useSelector((state: { app: AppState }) => state.app)
12+
const tabNames = Object.keys(notebooks)
13+
const [value, setValue] = useState(0);
14+
15+
const handleChange = (event: any, newValue: number) => {
16+
const currentNotebookTab = notebooks[event.target.innerText]
17+
dispatch(updateActiveNotebookName(currentNotebookTab.name));
18+
setValue(newValue);
19+
};
20+
21+
return (
22+
<div className='col-span-12 border-l-2 ml-52'>
23+
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
24+
<Tabs value={value} onChange={handleChange} aria-label="basic tabs example">
25+
{
26+
tabNames.map((name, i) => {
27+
return <Tab style={{ textTransform: "none" }} key={i} label={name} />
28+
})
29+
}
30+
</Tabs>
31+
</Box>
32+
</div>
33+
);
34+
}

src/frontend/lib/interpreter/server.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ import { marked } from "marked"
33
import { formatErrorMessage } from "../helpers/utils"
44

55
const SERVER_URL = process.env.NEXT_PUBLIC_CODE_SERVER_URL
6+
7+
type ExecProps = {
8+
content: string,
9+
language: string,
10+
activeNotebookName: string,
11+
callback: (accumulatedResult: string | outputError, hasErrors: boolean) => void
12+
}
613
class ServerAPI {
714
/**
815
* Executes the code in the given language via the server. Intermediary results like those in
@@ -13,9 +20,9 @@ class ServerAPI {
1320
* @returns A promise that resolves when the execution is finished.
1421
* @throws Error if the language is not supported.
1522
*/
16-
async exec(content: string, language: string, callback: (accumulatedResult: string | outputError, hasErrors: boolean) => void) {
23+
async exec({ content, language, callback, activeNotebookName }: ExecProps) {
1724
if (["typescript", "javascript", "bash", "sh", "powershell", "process"].includes(language)) {
18-
return this.executeInNodeJs(content, language, callback);
25+
return this.executeInNodeJs({ content, language, callback, activeNotebookName });
1926

2027
} else if (language === "markdown") {
2128
try {
@@ -61,19 +68,17 @@ class ServerAPI {
6168
* @param callback A callback that is called with the result/intermediate result of the execution.
6269
* @returns A promise that resolves when the execution is finished.
6370
* */
64-
async executeInNodeJs(
65-
code: string,
66-
language: string,
67-
callback: (accumulatedResult: string | outputError, hasErrors: boolean) => void) {
71+
async executeInNodeJs({ content, language, callback, activeNotebookName }: ExecProps) {
6872

6973
fetch(`${SERVER_URL}/nodejs/run`, {
7074
method: 'POST',
7175
headers: {
7276
'Content-Type': 'application/json'
7377
},
7478
body: JSON.stringify({
75-
code,
76-
language
79+
content,
80+
language,
81+
activeNotebookName
7782
}),
7883
})
7984
.then(response => response.body)

src/frontend/lib/state/reducer.ts

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,40 @@ import { createSlice } from "@reduxjs/toolkit";
22
import { AppState } from "../typings/types";
33
import { v4 as uuid_v4 } from "uuid";
44

5-
const initialId = uuid_v4()
5+
const initialNotebookId = uuid_v4()
6+
const initialCellId1 = uuid_v4()
7+
const initialCellId2 = uuid_v4()
8+
69

710
const initialState: AppState = {
811
interpreterMode: "node",
9-
cellIds: [initialId],
10-
cells: {
11-
[initialId]: {
12-
id: initialId,
13-
content: "",
14-
mode: "javascript",
15-
output: ""
12+
activeNotebookName: "note1.dnb",
13+
notebooks: {
14+
"note1.dnb": {
15+
notebookId: initialNotebookId,
16+
name: "note1.dnb",
17+
cellIds: [initialCellId1],
18+
cells: {
19+
[initialCellId1]: {
20+
id: initialCellId1,
21+
content: "",
22+
mode: "javascript",
23+
output: ""
24+
},
25+
},
26+
},
27+
"note2.dnb": {
28+
notebookId: uuid_v4(),
29+
name: "note2.dnb",
30+
cellIds: [initialCellId2],
31+
cells: {
32+
[initialCellId2]: {
33+
id: initialCellId2,
34+
content: "",
35+
mode: "markdown",
36+
output: ""
37+
},
38+
},
1639
},
1740
},
1841
config: {
@@ -37,14 +60,19 @@ const appReducer = createSlice({
3760
state.interpreterMode = action.payload;
3861
},
3962
updateCellIds: (state, action) => {
40-
state.cellIds = action.payload;
63+
const { newCellIds, activeNotebookName } = action.payload;
64+
state.notebooks[activeNotebookName].cellIds = newCellIds;
4165
},
4266
updateCells: (state, action) => {
43-
state.cells = action.payload;
67+
const { newCells, activeNotebookName } = action.payload;
68+
state.notebooks[activeNotebookName].cells = newCells;
4469
},
4570
updateConfig: (state, action) => {
4671
state.config = action.payload;
4772
},
73+
updateActiveNotebookName: (state, action) => {
74+
state.activeNotebookName = action.payload;
75+
},
4876
}
4977
});
5078

@@ -53,6 +81,7 @@ export const {
5381
updateCellIds,
5482
updateCells,
5583
updateConfig,
84+
updateActiveNotebookName,
5685
} = appReducer.actions;
5786

5887
export default appReducer.reducer;

src/frontend/lib/typings/types.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ export type NbCell = {
1919
}
2020

2121
export type Notebook = {
22+
notebookId: string;
23+
name: string;
2224
cellIds: string[];
2325
cells: {
2426
[key: string]: NbCell
2527
}
26-
config: Partial<NotebookConfig>;
2728
}
2829

2930
export type NotebookConfig = {
@@ -50,11 +51,12 @@ export type CellLanguages = {
5051
}
5152
}
5253

54+
5355
export type AppState = {
5456
interpreterMode: string;
55-
cellIds: string[];
56-
cells: {
57-
[key: string]: NbCell
57+
activeNotebookName: string;
58+
notebooks: {
59+
[key: string]: Notebook
5860
}
5961
config: Partial<NotebookConfig>;
6062
}

0 commit comments

Comments
 (0)