Skip to content

Commit dfc500e

Browse files
g-barbilal meddah
andauthored
User notebooks (#30)
Implement user notebooks. --------- Co-authored-by: bilal meddah <[email protected]>
1 parent ece30ab commit dfc500e

File tree

18 files changed

+1838
-317
lines changed

18 files changed

+1838
-317
lines changed

package-lock.json

Lines changed: 983 additions & 80 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
"react-dom": "18.3.1",
8989
"react-error-boundary": "^4.0.4",
9090
"react-intersection-observer": "^9.5.2",
91+
"react-ipynb-renderer": "^2.2.4",
9192
"react-markdown": "^9.0.3",
9293
"react-pdf": "^7.7.0",
9394
"react-plotly.js": "^2.6.0",

src/app/api/github/download-notebook/route.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NextResponse } from 'next/server';
2-
import { downloadZippedFolder } from '@/util/virtual-lab/github';
2+
// import { downloadZippedNotebook } from '@/util/virtual-lab/github';
33
import { assertErrorMessage } from '@/util/utils';
44

55
export async function GET(request: Request): Promise<NextResponse> {
@@ -11,9 +11,9 @@ export async function GET(request: Request): Promise<NextResponse> {
1111
}
1212

1313
try {
14-
const zipContent = await downloadZippedFolder(folder);
14+
// const zipContent = await downloadZippedFolder(folder);
1515

16-
return new NextResponse(zipContent, {
16+
return new NextResponse(null, {
1717
headers: {
1818
'Content-Type': 'application/zip',
1919
'Content-Disposition': `attachment; filename="${folder}.zip"`,

src/app/api/github/fetch-file/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NextResponse } from 'next/server';
2-
import { fetchFile } from '@/util/virtual-lab/github';
2+
import { fetchGithubFile } from '@/util/virtual-lab/github';
33

44
export async function GET(request: Request): Promise<NextResponse> {
55
const { searchParams } = new URL(request.url);
@@ -9,7 +9,7 @@ export async function GET(request: Request): Promise<NextResponse> {
99
return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 });
1010
}
1111

12-
const file = await fetchFile(path);
12+
const file = await fetchGithubFile(path);
1313

1414
return new NextResponse(file, {
1515
headers: {

src/app/virtual-lab/lab/[virtualLabId]/project/[projectId]/(pages)/notebooks/ContentModal.tsx

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,75 @@
11
import { useEffect, useState } from 'react';
22
import { Modal } from 'antd/lib';
33
import ReactMarkdown from 'react-markdown';
4-
import { basePath, notebookRepository } from '@/config';
4+
import { IpynbRenderer } from 'react-ipynb-renderer';
5+
import { Notebook } from '@/util/virtual-lab/github';
6+
import { basePath } from '@/config';
7+
8+
import 'react-ipynb-renderer/dist/styles/monokai.css';
59

610
import 'github-markdown-css';
11+
import { notification } from '@/api/notifications';
712

813
export default function ContentModal({
9-
file,
14+
notebook,
1015
onCancel,
16+
display,
1117
}: {
12-
file: { path: string; type: 'notebook' | 'text' } | null;
18+
notebook: Notebook | null;
19+
display: 'notebook' | 'readme' | null;
1320
onCancel: () => void;
1421
}) {
1522
const [content, setContent] = useState<string | null>(null);
1623

1724
useEffect(() => {
25+
const controller = new AbortController();
26+
1827
async function fetchFile() {
19-
if (!file) return;
20-
const res = await fetch(
21-
`${basePath}/api/github/fetch-file?path=${encodeURIComponent(file.path)}`
22-
);
23-
24-
if (!res.ok) {
25-
setContent('Cannot display the contents, ensure the repository is public');
26-
} else {
27-
setContent(await res.text());
28+
if (!notebook || !display) return;
29+
30+
try {
31+
const res = await fetch(
32+
`${basePath}/api/github/fetch-file?path=${encodeURIComponent(display === 'notebook' ? notebook.notebookUrl : notebook.readmeUrl)}`,
33+
{ signal: controller.signal }
34+
);
35+
36+
if (!res.ok) {
37+
notification.error('Cannot display the contents, ensure the repository is public');
38+
} else {
39+
setContent(await res.text());
40+
}
41+
} catch (error) {
42+
if (error instanceof Error && error.name !== 'AbortError') {
43+
notification.error('An error occurred while fetching the file');
44+
}
2845
}
2946
}
3047

3148
fetchFile();
32-
}, [file]);
49+
50+
return () => controller.abort();
51+
}, [notebook, display]);
3352

3453
return (
35-
<Modal open={!!file && !!content} onCancel={onCancel} footer={false} width="70%">
54+
<Modal
55+
open={!!notebook && !!content}
56+
onCancel={() => {
57+
setContent(null);
58+
onCancel();
59+
}}
60+
footer={false}
61+
width="70%"
62+
>
3663
<div>
37-
{file?.type === 'text' && (
64+
{display === 'readme' && !!content && (
3865
<div className="markdown-body">
3966
<ReactMarkdown>{content}</ReactMarkdown>
4067
</div>
4168
)}
4269

43-
{file?.type === 'notebook' && content && (
44-
<div className="h-[80vh] w-full">
45-
<iframe
46-
title={file.path}
47-
src={`https://nbviewer.org/github/${notebookRepository.user}/${notebookRepository.repository}/blob/main/${encodeURIComponent(file.path)}`}
48-
width="100%"
49-
height="100%"
50-
/>
70+
{display === 'notebook' && !!notebook && !!content && (
71+
<div className="h-[80vh] w-full overflow-y-scroll">
72+
<IpynbRenderer ipynb={JSON.parse(content)} />
5173
</div>
5274
)}
5375
</div>

0 commit comments

Comments
 (0)