-
Notifications
You must be signed in to change notification settings - Fork 4
Implement view-only notebook using custom factory #75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2811951
96f5686
2a98d24
b5c3a4c
3c6cc5a
a9d295c
d3251ed
221ac5c
4ef8a80
7b6217c
44c7baa
7fe19d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,26 @@ | ||
| import { JupyterFrontEnd, JupyterFrontEndPlugin } from '@jupyterlab/application'; | ||
| import { INotebookTracker } from '@jupyterlab/notebook'; | ||
| import { INotebookContent } from '@jupyterlab/nbformat'; | ||
| import { SidebarIcon } from '../ui-components/SidebarIcon'; | ||
| import { EverywhereIcons } from '../icons'; | ||
| import { ToolbarButton, IToolbarWidgetRegistry } from '@jupyterlab/apputils'; | ||
| import { DownloadDropdownButton } from '../ui-components/DownloadDropdownButton'; | ||
| import { Commands } from '../commands'; | ||
| import { SharingService } from '../sharing-service'; | ||
| import { INotebookContent } from '@jupyterlab/nbformat'; | ||
| import { VIEW_ONLY_NOTEBOOK_FACTORY, IViewOnlyNotebookTracker } from '../view-only'; | ||
|
|
||
| export const notebookPlugin: JupyterFrontEndPlugin<void> = { | ||
| id: 'jupytereverywhere:notebook', | ||
| autoStart: true, | ||
| requires: [INotebookTracker, IToolbarWidgetRegistry], | ||
| requires: [INotebookTracker, IViewOnlyNotebookTracker, IToolbarWidgetRegistry], | ||
| activate: ( | ||
| app: JupyterFrontEnd, | ||
| tracker: INotebookTracker, | ||
| readonlyTracker: IViewOnlyNotebookTracker, | ||
| toolbarRegistry: IToolbarWidgetRegistry | ||
| ) => { | ||
| const { commands, shell } = app; | ||
| const contents = app.serviceManager.contents; | ||
| const { commands, shell, serviceManager } = app; | ||
| const { contents } = serviceManager; | ||
|
|
||
| const params = new URLSearchParams(window.location.search); | ||
| let notebookId = params.get('notebook'); | ||
|
|
@@ -41,12 +43,15 @@ export const notebookPlugin: JupyterFrontEndPlugin<void> = { | |
| console.log('Retrieving notebook from API...'); | ||
|
|
||
| const notebookResponse = await sharingService.retrieve(id); | ||
| console.log('API Response received:', notebookResponse); // debug | ||
| console.log('API Response received:', notebookResponse); | ||
|
|
||
| const content: INotebookContent = notebookResponse.content; | ||
| const { content }: { content: INotebookContent } = notebookResponse; | ||
|
|
||
| // We make all cells read-only by setting editable: false | ||
| // by iterating over each cell in the notebook content. | ||
| // We make all cells read-only by setting editable: false. | ||
| // This is still required with a custom widget factory as | ||
| // it is not trivial to coerce the cells to respect the `readOnly` | ||
| // property otherwise (Mike tried swapping `Notebook.ContentFactory` | ||
| // and it does not work without further hacks). | ||
| if (content.cells) { | ||
| content.cells.forEach(cell => { | ||
| cell.metadata = { | ||
|
|
@@ -56,27 +61,31 @@ export const notebookPlugin: JupyterFrontEndPlugin<void> = { | |
| }); | ||
| } | ||
|
|
||
| const { id: responseId, readable_id, domain_id } = notebookResponse; | ||
| content.metadata = { | ||
| ...content.metadata, | ||
| isSharedNotebook: true, | ||
| sharedId: notebookResponse.id, | ||
| readableId: notebookResponse.readable_id, | ||
| domainId: notebookResponse.domain_id | ||
| sharedId: responseId, | ||
| readableId: readable_id, | ||
| domainId: domain_id | ||
| }; | ||
|
|
||
| // Generate a meaningful filename for the shared notebook | ||
| const filename = `Shared_${notebookResponse.readable_id || notebookResponse.id}.ipynb`; | ||
| const filename = `Shared_${readable_id || responseId}.ipynb`; | ||
|
|
||
| await contents.save(filename, { | ||
| content, | ||
| format: 'json', | ||
| type: 'notebook', | ||
| // Even though we have a custom view-only factory, we still | ||
| // want to indicate that notebook is read-only to avoid | ||
| // error on Ctrl + S and instead get a nice notification that | ||
| // the notebook cannot be saved unless using save-as. | ||
|
Comment on lines
+79
to
+82
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One follow-up here after this PR could be to style that notification as well. The default design is pretty neat, so maybe all we need to do is to change the orange-ish colour to something like
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, we could adjust the styling. Maybe we could track that in a follow-up issue - would you like to open one? Though orange/yellow is meant to symbolize danger so maybe not a bad choice? But surely there is some rounding and borders we could adjust. |
||
| writable: false | ||
| }); | ||
|
|
||
| await commands.execute('docmanager:open', { | ||
| path: filename, | ||
| factory: 'Notebook' | ||
| factory: VIEW_ONLY_NOTEBOOK_FACTORY | ||
| }); | ||
|
|
||
| console.log(`Successfully loaded shared notebook: ${filename}`); | ||
|
|
@@ -125,8 +134,11 @@ export const notebookPlugin: JupyterFrontEndPlugin<void> = { | |
| label: 'Notebook', | ||
| icon: EverywhereIcons.notebook, | ||
| execute: () => { | ||
| if (readonlyTracker.currentWidget) { | ||
| return shell.activateById(readonlyTracker.currentWidget.id); | ||
| } | ||
| if (tracker.currentWidget) { | ||
| shell.activateById(tracker.currentWidget.id); | ||
| return shell.activateById(tracker.currentWidget.id); | ||
| } | ||
| } | ||
| }); | ||
|
|
@@ -135,24 +147,26 @@ export const notebookPlugin: JupyterFrontEndPlugin<void> = { | |
| app.shell.activateById(sidebarItem.id); | ||
| app.restored.then(() => app.shell.activateById(sidebarItem.id)); | ||
|
|
||
| toolbarRegistry.addFactory( | ||
| 'Notebook', | ||
| 'downloadDropdown', | ||
| () => new DownloadDropdownButton(commands) | ||
| ); | ||
|
|
||
| toolbarRegistry.addFactory( | ||
| 'Notebook', | ||
| 'share', | ||
| () => | ||
| new ToolbarButton({ | ||
| label: 'Share', | ||
| icon: EverywhereIcons.link, | ||
| tooltip: 'Share this notebook', | ||
| onClick: () => { | ||
| void commands.execute(Commands.shareNotebookCommand); | ||
| } | ||
| }) | ||
| ); | ||
| for (const toolbarName of ['Notebook', 'ViewOnlyNotebook']) { | ||
| toolbarRegistry.addFactory( | ||
| toolbarName, | ||
| 'downloadDropdown', | ||
| () => new DownloadDropdownButton(commands) | ||
| ); | ||
|
|
||
| toolbarRegistry.addFactory( | ||
| toolbarName, | ||
| 'share', | ||
| () => | ||
| new ToolbarButton({ | ||
| label: 'Share', | ||
| icon: EverywhereIcons.link, | ||
| tooltip: 'Share this notebook', | ||
| onClick: () => { | ||
| void commands.execute(Commands.shareNotebookCommand); | ||
| } | ||
| }) | ||
| ); | ||
| } | ||
| } | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: does this remove the "notebook is read-only" bar as well, as you're not manipulating the DOM to do so and instead adding back the buttons for
ViewOnlyNotebook?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that item gets removed too, but in a different way, here:
jupyterlite-extension/src/view-only.ts
Lines 74 to 79 in 7fe19d4
jupyterlite-extension/src/view-only.ts
Lines 50 to 63 in 7fe19d4