Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions plans/add-jupyter-notebook-preview.execplan.md

Large diffs are not rendered by default.

35 changes: 35 additions & 0 deletions src/app/core/learning-object-module/file/file.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { HttpClient } from "@angular/common/http";
import { FileService } from "./file.service";

describe("FileService previews", () => {
let service: FileService;
let openSpy: jest.SpyInstance;

beforeEach(() => {
service = new FileService({} as HttpClient);
openSpy = jest.spyOn(window, "open").mockImplementation(() => null);
});

afterEach(() => {
openSpy.mockRestore();
});

it.each(["lesson.ipynb", "LESSON.IPYNB"])(
"allows %s to be previewed",
(fileName) => {
expect(FileService.canPreview(fileName)).toBe(true);
},
);

it("opens notebooks in notebook mode in the shared preview route", async () => {
const url = "https://files.example.com/lesson.ipynb?token=signed-value";

await service.previewLearningObjectFile(url, "lesson.ipynb");

expect(openSpy).toHaveBeenCalledWith(
`/preview/code?url=${encodeURIComponent(url)}&type=notebook&filename=lesson.ipynb`,
"_blank",
"noopener,noreferrer",
);
});
});
10 changes: 8 additions & 2 deletions src/app/core/learning-object-module/file/file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export class FileService {
const extension = this.getFileExtension(filename);

const typeExtensions: { [type: string]: string[] } = {
notebook: [".ipynb"],
office: [
".docx",
".doc",
Expand Down Expand Up @@ -115,6 +116,13 @@ export class FileService {
const previewUrl = `/preview/code?url=${encodedUrl}&language=${language}&filename=${encodedFileName}`;
window.open(previewUrl, "_blank", "noopener,noreferrer");
},
// Render Jupyter notebooks as static HTML in the shared preview shell
notebook: (url: string, fileName: string) => {
const encodedUrl = encodeURIComponent(url);
const encodedFileName = encodeURIComponent(fileName);
const previewUrl = `/preview/code?url=${encodedUrl}&type=notebook&filename=${encodedFileName}`;
window.open(previewUrl, "_blank", "noopener,noreferrer");
},
};

/**
Expand Down Expand Up @@ -187,8 +195,6 @@ export class FileService {
const previewAction = FileService.PREVIEW_ACTIONS[fileType];

if (previewAction) {
console.log("URL: ", url);
console.log("FileName: ", fileName);
previewAction(url, fileName);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,77 @@
<div class="code-preview-container">
<header class="code-preview-header">
<h1 class="file-name" [textContent]="fileName"></h1>
<button
class="copy-button"
(click)="copyToClipboard()"
[disabled]="isLoading || hasError"
title="Copy to clipboard">
<i class="fas fa-copy"></i>
<span>Copy</span>
</button>
<div class="header-actions">
<button
*ngIf="!isNotebookPreview"
class="copy-button"
(click)="copyToClipboard()"
[disabled]="isLoading || hasError"
title="Copy to clipboard">
<i class="fas fa-copy"></i>
<span>Copy</span>
</button>
<a
*ngIf="isNotebookPreview && notebookSourceUrl && !hasError"
class="download-button"
[href]="notebookSourceUrl"
target="_blank"
rel="noopener noreferrer"
download>
<i class="fas fa-download"></i>
<span>Download notebook</span>
</a>
</div>
</header>

<div class="loading-state" *ngIf="isLoading">
<i class="fas fa-spinner fa-spin"></i>
<p>Loading file content...</p>
<p>
{{
isNotebookPreview
? "Loading notebook preview..."
: "Loading file content..."
}}
</p>
</div>

<div class="error-state" *ngIf="hasError">
<i class="fas fa-exclamation-triangle"></i>
<p [textContent]="errorMessage"></p>
<div
class="error-actions"
*ngIf="isNotebookPreview && notebookSourceUrl">
<button class="retry-button" (click)="retryNotebookPreview()">
Try again
</button>
<a
class="download-button"
[href]="notebookSourceUrl"
target="_blank"
rel="noopener noreferrer"
download>
<i class="fas fa-download"></i>
<span>Download notebook</span>
</a>
</div>
</div>

<div class="code-content" *ngIf="!isLoading && !hasError">
<div
class="notebook-content"
[class.notebook-content--loading]="isLoading"
*ngIf="isNotebookPreview && notebookViewerUrl && !hasError">

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<iframe
[src]="notebookViewerUrl"
[title]="'Notebook preview for ' + fileName"
sandbox="allow-downloads allow-popups allow-same-origin allow-scripts"
referrerpolicy="no-referrer"
(load)="onNotebookLoaded()"
(error)="onNotebookLoadError()"></iframe>
</div>

<div
class="code-content"
*ngIf="!isNotebookPreview && !isLoading && !hasError">
<markdown
[data]="markdownContent"
ngPreserveWhitespaces
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,16 @@
word-break: break-all;
}

.copy-button {
.header-actions,
.error-actions {
display: flex;
align-items: center;
gap: 0.75rem;
}

.copy-button,
.download-button,
.retry-button {
display: flex;
align-items: center;
gap: 0.5rem;
Expand All @@ -35,6 +44,7 @@
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s ease;
text-decoration: none;

&:hover:not(:disabled) {
background-color: #0056b3;
Expand All @@ -50,6 +60,10 @@
}
}

.error-actions {
margin-top: 1.5rem;
}

.loading-state,
.error-state {
display: flex;
Expand Down Expand Up @@ -94,6 +108,24 @@
overflow: auto;
}

.notebook-content {
flex: 1;
display: flex;
min-height: 0;

iframe {
flex: 1;
width: 100%;
min-height: calc(100vh - 82px);
border: 0;
}

&--loading {
visibility: hidden;
position: absolute;
}
}

@media (max-width: 768px) {
.code-preview-header {
flex-direction: column;
Expand All @@ -105,11 +137,19 @@
font-size: 1.25rem;
}

.copy-button {
.header-actions,
.copy-button,
.download-button,
.retry-button {
width: 100%;
justify-content: center;
}

.header-actions,
.error-actions {
flex-direction: column;
}

.code-content {
padding: 1rem;

Expand Down
Loading