Skip to content

Commit 1a2485a

Browse files
Add notebook picker to toolbar
Signed-off-by: Andy Jakubowski <hello@andyjakubowski.com>
1 parent eb78032 commit 1a2485a

File tree

3 files changed

+98
-71
lines changed

3 files changed

+98
-71
lines changed

jupyterlab_deepnote/contents.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# deepnote_jupyter_extension/contents.py
22
from jupyter_server.services.contents.filemanager import FileContentsManager
33
from typing import cast
4-
import nbformat
54

65
import yaml
76
from nbformat.v4 import new_notebook, new_code_cell, new_markdown_cell
@@ -17,6 +16,7 @@ def yaml_to_ipynb(yaml_text: str):
1716
notebooks = (
1817
data.get("project", {}).get("notebooks", []) if isinstance(data, dict) else []
1918
)
19+
2020
if not notebooks:
2121
return new_notebook(cells=[])
2222

@@ -33,7 +33,9 @@ def yaml_to_ipynb(yaml_text: str):
3333
else:
3434
cells.append(new_markdown_cell(content))
3535

36-
return new_notebook(cells=cells, metadata={})
36+
notebook_names = [nb.get("name", "") for nb in notebooks]
37+
metadata = {"notebook_names": notebook_names}
38+
return new_notebook(cells=cells, metadata=metadata)
3739

3840

3941
def yaml_to_ipynb_dummy(yaml_text: str) -> dict:
@@ -43,9 +45,6 @@ def yaml_to_ipynb_dummy(yaml_text: str) -> dict:
4345
class DeepnoteContentsManager(FileContentsManager):
4446
def get(self, path, content=True, type=None, format=None, require_hash=False):
4547
if path.endswith(".deepnote") and (content == 1):
46-
self.log.info(
47-
"\n\n\n🌴🌴🌴 path %s, content: %s, type: %s", path, content, type
48-
)
4948
os_path = self._get_os_path(path)
5049

5150
# _read_file may return 2- or 3-tuple depending on raw flag in implementation hints
@@ -56,8 +55,7 @@ def get(self, path, content=True, type=None, format=None, require_hash=False):
5655
else:
5756
yaml_text = cast(str, _content)
5857

59-
nb_dict = yaml_to_ipynb(yaml_text)
60-
nb_node = nbformat.from_dict(nb_dict)
58+
nb_node = yaml_to_ipynb(yaml_text)
6159

6260
model = self._base_model(path)
6361
model["type"] = "notebook"

src/index.ts

Lines changed: 0 additions & 64 deletions
This file was deleted.

src/index.tsx

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import {
2+
JupyterFrontEnd,
3+
JupyterFrontEndPlugin
4+
} from '@jupyterlab/application';
5+
import React from 'react';
6+
import { IToolbarWidgetRegistry, ReactWidget } from '@jupyterlab/apputils';
7+
import {
8+
INotebookWidgetFactory,
9+
NotebookPanel,
10+
NotebookWidgetFactory
11+
} from '@jupyterlab/notebook';
12+
import { Widget } from '@lumino/widgets';
13+
import { HTMLSelect } from '@jupyterlab/ui-components';
14+
15+
const plugin: JupyterFrontEndPlugin<void> = {
16+
id: 'jupyterlab-deepnote:plugin',
17+
description: 'Open .deepnote files as notebooks.',
18+
autoStart: true,
19+
requires: [INotebookWidgetFactory, IToolbarWidgetRegistry],
20+
activate: (
21+
app: JupyterFrontEnd,
22+
notebookWidgetFactory: NotebookWidgetFactory,
23+
toolbarRegistry: IToolbarWidgetRegistry
24+
) => {
25+
app.docRegistry.addFileType(
26+
{
27+
name: 'deepnote',
28+
displayName: 'Deepnote Notebook',
29+
extensions: ['.deepnote'],
30+
mimeTypes: ['text/yaml', 'application/x-yaml'],
31+
fileFormat: 'text',
32+
contentType: 'file'
33+
},
34+
[notebookWidgetFactory.name]
35+
);
36+
37+
app.docRegistry.setDefaultWidgetFactory(
38+
'deepnote',
39+
notebookWidgetFactory.name
40+
);
41+
42+
toolbarRegistry.addFactory<NotebookPanel>(
43+
notebookWidgetFactory.name,
44+
'deepnote:switch-notebook',
45+
panel => {
46+
if (!panel.context.path.endsWith('.deepnote')) {
47+
return new Widget(); // don’t render for .ipynb or others
48+
}
49+
50+
return new NotebookPicker(panel);
51+
}
52+
);
53+
}
54+
};
55+
56+
class NotebookPicker extends ReactWidget {
57+
constructor(private panel: NotebookPanel) {
58+
super();
59+
// when the context becomes ready, trigger re-render
60+
void panel.context.ready.then(() => {
61+
this.update();
62+
});
63+
}
64+
65+
render(): JSX.Element {
66+
const metadataNames =
67+
this.panel.context.model.getMetadata('notebook_names');
68+
const names =
69+
Array.isArray(metadataNames) &&
70+
metadataNames.every(n => typeof n === 'string')
71+
? metadataNames
72+
: [];
73+
74+
return (
75+
<HTMLSelect
76+
value={names[0] ?? '-'}
77+
onChange={() => {}}
78+
onKeyDown={() => {}}
79+
aria-label="Select active notebook"
80+
title="Select active notebook"
81+
>
82+
<option value="-">-</option>
83+
{names.map(n => (
84+
<option key={n} value={n}>
85+
{n}
86+
</option>
87+
))}
88+
</HTMLSelect>
89+
);
90+
}
91+
}
92+
93+
export default plugin;

0 commit comments

Comments
 (0)