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
65 changes: 62 additions & 3 deletions schema/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,65 @@
"title": "jupytereverywhere",
"description": "jupytereverywhere settings.",
"type": "object",
"properties": {},
"additionalProperties": false,
"jupyter.lab.toolbars": {
"Notebook": [
"ViewOnlyNotebook": [
{
"name": "save",
"disabled": true
},
{
"name": "cut",
"command": "notebook:cut-cell",
"disabled": true
},
{
"name": "copy",
"command": "notebook:copy-cell",
"disabled": true
},
{
"name": "paste",
"command": "notebook:paste-cell-below",
"disabled": true
},
{
"name": "run",
"caption": "Run cell",
"disabled": true
},
{
"name": "interrupt",
"caption": "Interrupt notebook",
"disabled": true
},
{
"name": "restart",
"caption": "Restart notebook",
"disabled": true
},
{
"name": "restart-and-run",
"caption": "Restart and run all cells",
"disabled": true
},
{
"name": "cellType",
"command": "",
"disabled": true,
"disabled": true
},
{
"name": "share",
"rank": 35
},
{
"name": "downloadDropdown",
"rank": 36
}
],
"Notebook": [
{
"name": "save",
"rank": 10
},
{
Expand Down Expand Up @@ -106,5 +157,13 @@
"disabled": true
}
]
},
"jupyter.lab.transform": true,
"properties": {
"toolbar": {
"title": "View-only notebook panel toolbar items",
"type": "array",
"default": []
}
Comment on lines +161 to +167
Copy link
Member

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?

Copy link
Collaborator Author

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:

super({
...options,
toolbar: new FilteredToolbar({
itemsToFilterOut: new Set(['read-only-indicator'])
})
});

class FilteredToolbar extends ReactiveToolbar {
constructor(options: FilteredToolbar.IOptions) {
super(options);
this._itemsToFilterOut = options.itemsToFilterOut;
}
insertItem(index: number, name: string, widget: Widget): boolean {
if (this._itemsToFilterOut?.has(name)) {
return false;
}
return super.insertItem(index, name, widget);
}
// This can be undefined during the super() call in constructor
private _itemsToFilterOut: Set<string> | undefined;
}

}
}
10 changes: 9 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Commands } from './commands';
import { competitions } from './pages/competitions';
import { notebookPlugin } from './pages/notebook';
import { generateDefaultNotebookName } from './notebook-name';
import { viewOnlyNotebookFactoryPlugin } from './view-only';

/**
* Generate a shareable URL for the currently active notebook.
Expand Down Expand Up @@ -236,4 +237,11 @@ const plugin: JupyterFrontEndPlugin<void> = {
}
};

export default [plugin, notebookPlugin, files, competitions, customSidebar];
export default [
viewOnlyNotebookFactoryPlugin,
plugin,
notebookPlugin,
files,
competitions,
customSidebar
];
82 changes: 48 additions & 34 deletions src/pages/notebook.tsx
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');
Expand All @@ -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 = {
Expand All @@ -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
Copy link
Member

Choose a reason for hiding this comment

The 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 --je-slate-blue.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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}`);
Expand Down Expand Up @@ -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);
}
}
});
Expand All @@ -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);
}
})
);
}
}
};
1 change: 0 additions & 1 deletion src/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export const customSidebar: JupyterFrontEndPlugin<void> = {
const newWidget = args.currentTitle
? leftHandler._findWidgetByTitle(args.currentTitle)
: null;
console.log(newWidget);
if (newWidget && newWidget instanceof SidebarIcon) {
const cancel = newWidget.execute();
if (cancel) {
Expand Down
Loading
Loading