Skip to content

Commit 8a46a25

Browse files
trungleducgithub-actions[bot]jtpio
authored
Create tree plugin (#1374)
* Create tree plugin * Update style * Add classic tree setting * Update UI test * Update Playwright Snapshots * Apply suggestions from code review Co-authored-by: Jeremy Tuloup <[email protected]> * Update test * Pin pyzmq * Update snapshot * [skip ci] Update voila/configuration.py Co-authored-by: Jeremy Tuloup <[email protected]> * Fix leakage contents --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Jeremy Tuloup <[email protected]>
1 parent 3ac569a commit 8a46a25

27 files changed

+492
-70
lines changed

.github/workflows/main.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ jobs:
8484

8585
- name: Create the conda environment
8686
shell: bash -l {0}
87-
run: mamba install -q python=${{ matrix.python_version }} pip jupyterlab_pygments==0.1.0 pytest-cov pytest-rerunfailures nodejs=18 yarn ipywidgets matplotlib xeus-cling "traitlets>=5.0.3,<6" ipykernel
87+
# TODO unpin pyzmq
88+
run: mamba install -q python=${{ matrix.python_version }} pip jupyterlab_pygments==0.1.0 pytest-cov pytest-rerunfailures nodejs=18 yarn ipywidgets matplotlib xeus-cling "traitlets>=5.0.3,<6" ipykernel pyzmq==25.1.0
8889

8990
- name: Install dependencies
9091
shell: bash -l {0}
@@ -136,6 +137,6 @@ jobs:
136137
- name: Run test
137138
run: |
138139
set VOILA_TEST_DEBUG=1
139-
py.test tests/app --async-test-timeout=240 --reruns 2 --reruns-delay 1
140-
py.test tests/server --async-test-timeout=240 --reruns 2 --reruns-delay 1 --trace
141-
py.test tests/execute_output_test.py
140+
py.test tests/app --async-test-timeout=240 --reruns 2 --reruns-delay 1 -x
141+
py.test tests/server --async-test-timeout=240 --reruns 2 --reruns-delay 1 --trace -x
142+
py.test tests/execute_output_test.py -x

packages/voila/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"@jupyterlab/mainmenu": "^4.0.0",
2323
"@jupyterlab/markdownviewer-extension": "^4.0.0",
2424
"@jupyterlab/markedparser-extension": "^4.0.0",
25-
"@jupyterlab/mathjax2-extension": "^4.0.0-alpha.21",
25+
"@jupyterlab/mathjax2-extension": "^4.0.0",
2626
"@jupyterlab/nbformat": "^4.0.0",
2727
"@jupyterlab/notebook": "^4.0.0",
2828
"@jupyterlab/outputarea": "^4.0.0",
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { FileBrowser, DirListing } from '@jupyterlab/filebrowser';
2+
import { VoilaDirListing } from './listing';
3+
4+
export class VoilaFileBrowser extends FileBrowser {
5+
/**
6+
* Create the underlying DirListing instance.
7+
*
8+
* @param options - The DirListing constructor options.
9+
*
10+
* @returns The created DirListing instance.
11+
*/
12+
protected createDirListing(options: DirListing.IOptions): DirListing {
13+
return new VoilaDirListing(options);
14+
}
15+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/***************************************************************************
2+
* Copyright (c) 2023, Voilà contributors *
3+
* Copyright (c) 2023, QuantStack *
4+
* *
5+
* Distributed under the terms of the BSD 3-Clause License. *
6+
* *
7+
* The full license is in the file LICENSE, distributed with this software. *
8+
****************************************************************************/
9+
import {
10+
JupyterFrontEnd,
11+
JupyterFrontEndPlugin
12+
} from '@jupyterlab/application';
13+
import { DocumentManager } from '@jupyterlab/docmanager';
14+
import { DocumentRegistry } from '@jupyterlab/docregistry';
15+
import { FilterFileBrowserModel } from '@jupyterlab/filebrowser';
16+
17+
import { VoilaFileBrowser } from './browser';
18+
import { Widget } from '@lumino/widgets';
19+
20+
/**
21+
* The voila file browser provider.
22+
*/
23+
export const treeWidgetPlugin: JupyterFrontEndPlugin<void> = {
24+
id: '@voila-dashboards/voila:tree-widget',
25+
description: 'Provides the file browser.',
26+
activate: (app: JupyterFrontEnd): void => {
27+
const docRegistry = new DocumentRegistry();
28+
const docManager = new DocumentManager({
29+
registry: docRegistry,
30+
manager: app.serviceManager,
31+
opener
32+
});
33+
const fbModel = new FilterFileBrowserModel({
34+
manager: docManager,
35+
refreshInterval: 2147483646
36+
});
37+
const fb = new VoilaFileBrowser({
38+
id: 'filebrowser',
39+
model: fbModel
40+
});
41+
42+
fb.addClass('voila-FileBrowser');
43+
fb.showFileCheckboxes = false;
44+
fb.showLastModifiedColumn = false;
45+
46+
const title = new Widget();
47+
title.node.innerText = 'Select items to open with Voilà.';
48+
fb.toolbar.addItem('title', title);
49+
50+
const spacerTop = new Widget();
51+
spacerTop.addClass('spacer-top-widget');
52+
app.shell.add(spacerTop, 'main');
53+
54+
app.shell.add(fb, 'main');
55+
56+
const spacerBottom = new Widget();
57+
spacerBottom.addClass('spacer-bottom-widget');
58+
app.shell.add(spacerBottom, 'main');
59+
},
60+
61+
autoStart: true
62+
};
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { DirListing } from '@jupyterlab/filebrowser';
2+
import { Contents } from '@jupyterlab/services';
3+
import { showErrorMessage } from '@jupyterlab/apputils';
4+
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
5+
6+
export class VoilaDirListing extends DirListing {
7+
/**
8+
* Handle the opening of an item.
9+
*/
10+
protected handleOpen(item: Contents.IModel): void {
11+
if (item.type === 'directory') {
12+
const localPath = this.model.manager.services.contents.localPath(
13+
item.path
14+
);
15+
this.model
16+
.cd(`/${localPath}`)
17+
.catch((error) => showErrorMessage('Open directory', error));
18+
} else {
19+
const path = item.path;
20+
const baseUrl = PageConfig.getBaseUrl();
21+
const frontend = PageConfig.getOption('frontend');
22+
const query = PageConfig.getOption('query');
23+
const url = URLExt.join(baseUrl, frontend, 'render', path) + `?${query}`;
24+
window.open(url, '_blank');
25+
}
26+
}
27+
28+
handleEvent(event: Event): void {
29+
if (event.type === 'click') {
30+
this.evtDblClick(event as MouseEvent);
31+
}
32+
}
33+
}

packages/voila/src/services/servicemanager.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ServiceManager } from '@jupyterlab/services';
22
import { VoilaEventManager } from './event';
33
import { VoilaUserManager } from './user';
44
import { VoilaKernelSpecManager } from './kernelspec';
5+
import { ContentsManager } from '@jupyterlab/services';
56

67
const alwaysTrue = () => true;
78

@@ -18,7 +19,8 @@ export class VoilaServiceManager extends ServiceManager {
1819
standby: options?.standby ?? alwaysTrue,
1920
kernelspecs: options?.kernelspecs ?? new VoilaKernelSpecManager({}),
2021
events: options?.events ?? new VoilaEventManager(),
21-
user: options?.user ?? new VoilaUserManager({})
22+
user: options?.user ?? new VoilaUserManager({}),
23+
contents: options?.contents ?? new ContentsManager()
2224
});
2325
}
2426
}

packages/voila/src/tree.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,24 @@
88
* Copyright (c) Jupyter Development Team. *
99
* Distributed under the terms of the Modified BSD License. *
1010
****************************************************************************/
11+
import '../style/index.js';
12+
import '@jupyterlab/filebrowser/style/index.js';
1113

1214
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
15+
import { ContentsManager, Drive } from '@jupyterlab/services';
1316

1417
import { VoilaApp } from './app';
18+
import { treeWidgetPlugin } from './plugins/tree';
19+
import { VoilaServiceManager } from './services/servicemanager';
20+
import { VoilaShell } from './shell';
21+
import { activePlugins, createModule, loadComponent } from './tools';
1522
import {
1623
pathsPlugin,
1724
themePlugin,
1825
themesManagerPlugin,
19-
translatorPlugin
26+
translatorPlugin,
27+
widgetManager
2028
} from './voilaplugins';
21-
import { VoilaServiceManager } from './services/servicemanager';
22-
import { VoilaShell } from './shell';
23-
import { activePlugins, createModule, loadComponent } from './tools';
2429

2530
const disabled = [
2631
'@jupyter-widgets/jupyterlab-manager:plugin',
@@ -37,10 +42,13 @@ async function main() {
3742
const mods = [
3843
require('@jupyterlab/theme-light-extension'),
3944
require('@jupyterlab/theme-dark-extension'),
45+
require('@jupyterlab/rendermime-extension'),
4046
pathsPlugin,
4147
translatorPlugin,
4248
themePlugin,
43-
themesManagerPlugin
49+
widgetManager,
50+
themesManagerPlugin,
51+
treeWidgetPlugin
4452
];
4553

4654
const mimeExtensions: any[] = [];
@@ -69,7 +77,6 @@ async function main() {
6977

7078
extensions.forEach((p) => {
7179
if (p.status === 'rejected') {
72-
// There was an error loading the component
7380
console.error(p.reason);
7481
return;
7582
}
@@ -122,11 +129,12 @@ async function main() {
122129
.forEach((p) => {
123130
console.error((p as PromiseRejectedResult).reason);
124131
});
125-
132+
const drive = new Drive({ apiEndpoint: 'voila/api/contents' });
133+
const cm = new ContentsManager({ defaultDrive: drive });
126134
const app = new VoilaApp({
127135
mimeExtensions,
128136
shell: new VoilaShell(),
129-
serviceManager: new VoilaServiceManager()
137+
serviceManager: new VoilaServiceManager({ contents: cm })
130138
});
131139
app.registerPluginModules(mods);
132140
app.started.then(() => {

packages/voila/style/base.css

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ body {
33
}
44
div#main {
55
height: 100vh;
6+
background-color: var(--jp-layout-color2);
7+
}
8+
div#rendered_cells {
9+
background-color: var(--jp-layout-color1);
610
}
711
div#voila-top-panel {
812
min-height: var(--jp-private-menubar-height);
@@ -16,3 +20,27 @@ div#rendered_cells {
1620
padding: var(--jp-notebook-padding);
1721
overflow: auto;
1822
}
23+
24+
.voila-FileBrowser {
25+
max-width: 1000px;
26+
box-shadow: var(--jp-elevation-z4);
27+
}
28+
29+
.voila-FileBrowser .jp-DirListing-item {
30+
border-bottom-style: solid;
31+
border-bottom-width: var(--jp-border-width);
32+
border-bottom-color: var(--jp-border-color0);
33+
padding: 10px 12px;
34+
}
35+
36+
.voila-FileBrowser .jp-DirListing-itemText:focus {
37+
outline-style: none;
38+
}
39+
40+
.spacer-top-widget {
41+
max-height: 50px;
42+
}
43+
44+
.spacer-bottom-widget {
45+
max-height: 50px;
46+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
{% extends "page.html" %}
2+
3+
{% block title %}{{ page_title }}{% endblock %}
4+
5+
{% block stylesheets %}
6+
{{ super() }}
7+
8+
<style>
9+
body {
10+
background-color: var(--jp-layout-color0);
11+
}
12+
13+
.list-header {
14+
width: 80%;
15+
margin-top: 50px;
16+
margin-left: auto;
17+
margin-right: auto;
18+
padding: 0px;
19+
border-style: solid;
20+
border-width: var(--jp-border-width);
21+
border-color: var(--jp-border-color2);
22+
border-bottom: none;
23+
background-color: var(--jp-layout-color2)
24+
}
25+
26+
.list-header-text {
27+
color: var(--jp-ui-font-color0);
28+
font-size: var(--jp-ui-font-size1);
29+
padding: 10px
30+
}
31+
32+
.voila-notebooks {
33+
background-color: var(--jp-layout-color1);
34+
width: 80%;
35+
margin: auto;
36+
padding: 0px;
37+
border-style: solid;
38+
border-width: var(--jp-border-width);
39+
border-color: var(--jp-border-color0);
40+
border-radius: var(--jp-border-radius);
41+
}
42+
43+
.voila-notebooks > li {
44+
color: var(--jp-ui-font-color1);
45+
list-style: none;
46+
border-bottom-style: solid;
47+
border-bottom-width: var(--jp-border-width);
48+
border-bottom-color: var(--jp-border-color0);
49+
}
50+
51+
.voila-notebooks > li:hover {
52+
background-color: var(--jp-layout-color2);
53+
}
54+
55+
.voila-notebooks > li:last-child {
56+
border: none
57+
}
58+
59+
.voila-notebooks > li > a {
60+
display: block;
61+
width: 100%;
62+
height: 100%;
63+
padding: 10px;
64+
}
65+
66+
.voila-notebooks > li > a > i {
67+
padding: 0 10px
68+
}
69+
</style>
70+
{% endblock %}
71+
72+
{% block body %}
73+
74+
{% set openInNewTab = 'target=_blank' %}
75+
<script id="jupyter-config-data" type="application/json">
76+
{{ page_config | tojson }}
77+
</script>
78+
79+
<script src="{{ page_config['fullStaticUrl'] | e }}/treepage.js"></script>
80+
{% set mainStyle = 'style="display: None;"' %}
81+
82+
<div id="voila-tree-main" {{mainStyle | safe}}>
83+
84+
{% endblock %}

share/jupyter/voila/templates/lab/tree.html

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,8 @@
7575
<script id="jupyter-config-data" type="application/json">
7676
{{ page_config | tojson }}
7777
</script>
78-
{% if (theme != 'dark' and theme != 'light') %}
79-
<script src="{{ page_config['fullStaticUrl'] | e }}/treepage.js"></script>
80-
<div id="voila-tree-main" style="display: None;">
81-
{% set mainStyle = 'style="display: None;"' %}
82-
{% else %}
83-
{% set mainStyle = '' %}
84-
{% endif %}
8578

86-
87-
<div id="voila-tree-main" {{mainStyle}}>
79+
<div id="voila-tree-main" {{mainStyle | safe}}>
8880
<div class="list-header">
8981
<div class="list-header-text">
9082
Select items to open with {{ "Voilà" if frontend == "voila" else frontend.capitalize() }}.
@@ -105,5 +97,4 @@
10597
{% endif %}
10698
{% endfor %}
10799
</ul>
108-
</div>
109100
{% endblock %}

0 commit comments

Comments
 (0)