Skip to content

Commit

Permalink
Create tree plugin (#1374)
Browse files Browse the repository at this point in the history
* 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 <jeremy.tuloup@gmail.com>

* Update test

* Pin pyzmq

* Update snapshot

* [skip ci] Update voila/configuration.py

Co-authored-by: Jeremy Tuloup <jeremy.tuloup@gmail.com>

* Fix leakage contents

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jeremy Tuloup <jeremy.tuloup@gmail.com>
  • Loading branch information
3 people committed Aug 10, 2023
1 parent 3ac569a commit 8a46a25
Show file tree
Hide file tree
Showing 27 changed files with 492 additions and 70 deletions.
9 changes: 5 additions & 4 deletions .github/workflows/main.yml
Expand Up @@ -84,7 +84,8 @@ jobs:

- name: Create the conda environment
shell: bash -l {0}
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
# TODO unpin pyzmq
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

- name: Install dependencies
shell: bash -l {0}
Expand Down Expand Up @@ -136,6 +137,6 @@ jobs:
- name: Run test
run: |
set VOILA_TEST_DEBUG=1
py.test tests/app --async-test-timeout=240 --reruns 2 --reruns-delay 1
py.test tests/server --async-test-timeout=240 --reruns 2 --reruns-delay 1 --trace
py.test tests/execute_output_test.py
py.test tests/app --async-test-timeout=240 --reruns 2 --reruns-delay 1 -x
py.test tests/server --async-test-timeout=240 --reruns 2 --reruns-delay 1 --trace -x
py.test tests/execute_output_test.py -x
2 changes: 1 addition & 1 deletion packages/voila/package.json
Expand Up @@ -22,7 +22,7 @@
"@jupyterlab/mainmenu": "^4.0.0",
"@jupyterlab/markdownviewer-extension": "^4.0.0",
"@jupyterlab/markedparser-extension": "^4.0.0",
"@jupyterlab/mathjax2-extension": "^4.0.0-alpha.21",
"@jupyterlab/mathjax2-extension": "^4.0.0",
"@jupyterlab/nbformat": "^4.0.0",
"@jupyterlab/notebook": "^4.0.0",
"@jupyterlab/outputarea": "^4.0.0",
Expand Down
15 changes: 15 additions & 0 deletions packages/voila/src/plugins/tree/browser.ts
@@ -0,0 +1,15 @@
import { FileBrowser, DirListing } from '@jupyterlab/filebrowser';
import { VoilaDirListing } from './listing';

export class VoilaFileBrowser extends FileBrowser {
/**
* Create the underlying DirListing instance.
*
* @param options - The DirListing constructor options.
*
* @returns The created DirListing instance.
*/
protected createDirListing(options: DirListing.IOptions): DirListing {
return new VoilaDirListing(options);
}
}
62 changes: 62 additions & 0 deletions packages/voila/src/plugins/tree/index.ts
@@ -0,0 +1,62 @@
/***************************************************************************
* Copyright (c) 2023, Voilà contributors *
* Copyright (c) 2023, QuantStack *
* *
* Distributed under the terms of the BSD 3-Clause License. *
* *
* The full license is in the file LICENSE, distributed with this software. *
****************************************************************************/
import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { DocumentManager } from '@jupyterlab/docmanager';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import { FilterFileBrowserModel } from '@jupyterlab/filebrowser';

import { VoilaFileBrowser } from './browser';
import { Widget } from '@lumino/widgets';

/**
* The voila file browser provider.
*/
export const treeWidgetPlugin: JupyterFrontEndPlugin<void> = {
id: '@voila-dashboards/voila:tree-widget',
description: 'Provides the file browser.',
activate: (app: JupyterFrontEnd): void => {
const docRegistry = new DocumentRegistry();
const docManager = new DocumentManager({
registry: docRegistry,
manager: app.serviceManager,
opener
});
const fbModel = new FilterFileBrowserModel({
manager: docManager,
refreshInterval: 2147483646
});
const fb = new VoilaFileBrowser({
id: 'filebrowser',
model: fbModel
});

fb.addClass('voila-FileBrowser');
fb.showFileCheckboxes = false;
fb.showLastModifiedColumn = false;

const title = new Widget();
title.node.innerText = 'Select items to open with Voilà.';
fb.toolbar.addItem('title', title);

const spacerTop = new Widget();
spacerTop.addClass('spacer-top-widget');
app.shell.add(spacerTop, 'main');

app.shell.add(fb, 'main');

const spacerBottom = new Widget();
spacerBottom.addClass('spacer-bottom-widget');
app.shell.add(spacerBottom, 'main');
},

autoStart: true
};
33 changes: 33 additions & 0 deletions packages/voila/src/plugins/tree/listing.ts
@@ -0,0 +1,33 @@
import { DirListing } from '@jupyterlab/filebrowser';
import { Contents } from '@jupyterlab/services';
import { showErrorMessage } from '@jupyterlab/apputils';
import { PageConfig, URLExt } from '@jupyterlab/coreutils';

export class VoilaDirListing extends DirListing {
/**
* Handle the opening of an item.
*/
protected handleOpen(item: Contents.IModel): void {
if (item.type === 'directory') {
const localPath = this.model.manager.services.contents.localPath(
item.path
);
this.model
.cd(`/${localPath}`)
.catch((error) => showErrorMessage('Open directory', error));
} else {
const path = item.path;
const baseUrl = PageConfig.getBaseUrl();
const frontend = PageConfig.getOption('frontend');
const query = PageConfig.getOption('query');
const url = URLExt.join(baseUrl, frontend, 'render', path) + `?${query}`;
window.open(url, '_blank');
}
}

handleEvent(event: Event): void {
if (event.type === 'click') {
this.evtDblClick(event as MouseEvent);
}
}
}
4 changes: 3 additions & 1 deletion packages/voila/src/services/servicemanager.ts
Expand Up @@ -2,6 +2,7 @@ import { ServiceManager } from '@jupyterlab/services';
import { VoilaEventManager } from './event';
import { VoilaUserManager } from './user';
import { VoilaKernelSpecManager } from './kernelspec';
import { ContentsManager } from '@jupyterlab/services';

const alwaysTrue = () => true;

Expand All @@ -18,7 +19,8 @@ export class VoilaServiceManager extends ServiceManager {
standby: options?.standby ?? alwaysTrue,
kernelspecs: options?.kernelspecs ?? new VoilaKernelSpecManager({}),
events: options?.events ?? new VoilaEventManager(),
user: options?.user ?? new VoilaUserManager({})
user: options?.user ?? new VoilaUserManager({}),
contents: options?.contents ?? new ContentsManager()
});
}
}
24 changes: 16 additions & 8 deletions packages/voila/src/tree.ts
Expand Up @@ -8,19 +8,24 @@
* Copyright (c) Jupyter Development Team. *
* Distributed under the terms of the Modified BSD License. *
****************************************************************************/
import '../style/index.js';
import '@jupyterlab/filebrowser/style/index.js';

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

import { VoilaApp } from './app';
import { treeWidgetPlugin } from './plugins/tree';
import { VoilaServiceManager } from './services/servicemanager';
import { VoilaShell } from './shell';
import { activePlugins, createModule, loadComponent } from './tools';
import {
pathsPlugin,
themePlugin,
themesManagerPlugin,
translatorPlugin
translatorPlugin,
widgetManager
} from './voilaplugins';
import { VoilaServiceManager } from './services/servicemanager';
import { VoilaShell } from './shell';
import { activePlugins, createModule, loadComponent } from './tools';

const disabled = [
'@jupyter-widgets/jupyterlab-manager:plugin',
Expand All @@ -37,10 +42,13 @@ async function main() {
const mods = [
require('@jupyterlab/theme-light-extension'),
require('@jupyterlab/theme-dark-extension'),
require('@jupyterlab/rendermime-extension'),
pathsPlugin,
translatorPlugin,
themePlugin,
themesManagerPlugin
widgetManager,
themesManagerPlugin,
treeWidgetPlugin
];

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

extensions.forEach((p) => {
if (p.status === 'rejected') {
// There was an error loading the component
console.error(p.reason);
return;
}
Expand Down Expand Up @@ -122,11 +129,12 @@ async function main() {
.forEach((p) => {
console.error((p as PromiseRejectedResult).reason);
});

const drive = new Drive({ apiEndpoint: 'voila/api/contents' });
const cm = new ContentsManager({ defaultDrive: drive });
const app = new VoilaApp({
mimeExtensions,
shell: new VoilaShell(),
serviceManager: new VoilaServiceManager()
serviceManager: new VoilaServiceManager({ contents: cm })
});
app.registerPluginModules(mods);
app.started.then(() => {
Expand Down
28 changes: 28 additions & 0 deletions packages/voila/style/base.css
Expand Up @@ -3,6 +3,10 @@ body {
}
div#main {
height: 100vh;
background-color: var(--jp-layout-color2);
}
div#rendered_cells {
background-color: var(--jp-layout-color1);
}
div#voila-top-panel {
min-height: var(--jp-private-menubar-height);
Expand All @@ -16,3 +20,27 @@ div#rendered_cells {
padding: var(--jp-notebook-padding);
overflow: auto;
}

.voila-FileBrowser {
max-width: 1000px;
box-shadow: var(--jp-elevation-z4);
}

.voila-FileBrowser .jp-DirListing-item {
border-bottom-style: solid;
border-bottom-width: var(--jp-border-width);
border-bottom-color: var(--jp-border-color0);
padding: 10px 12px;
}

.voila-FileBrowser .jp-DirListing-itemText:focus {
outline-style: none;
}

.spacer-top-widget {
max-height: 50px;
}

.spacer-bottom-widget {
max-height: 50px;
}
84 changes: 84 additions & 0 deletions share/jupyter/voila/templates/lab/tree-lab.html
@@ -0,0 +1,84 @@
{% extends "page.html" %}

{% block title %}{{ page_title }}{% endblock %}

{% block stylesheets %}
{{ super() }}

<style>
body {
background-color: var(--jp-layout-color0);
}

.list-header {
width: 80%;
margin-top: 50px;
margin-left: auto;
margin-right: auto;
padding: 0px;
border-style: solid;
border-width: var(--jp-border-width);
border-color: var(--jp-border-color2);
border-bottom: none;
background-color: var(--jp-layout-color2)
}

.list-header-text {
color: var(--jp-ui-font-color0);
font-size: var(--jp-ui-font-size1);
padding: 10px
}

.voila-notebooks {
background-color: var(--jp-layout-color1);
width: 80%;
margin: auto;
padding: 0px;
border-style: solid;
border-width: var(--jp-border-width);
border-color: var(--jp-border-color0);
border-radius: var(--jp-border-radius);
}

.voila-notebooks > li {
color: var(--jp-ui-font-color1);
list-style: none;
border-bottom-style: solid;
border-bottom-width: var(--jp-border-width);
border-bottom-color: var(--jp-border-color0);
}

.voila-notebooks > li:hover {
background-color: var(--jp-layout-color2);
}

.voila-notebooks > li:last-child {
border: none
}

.voila-notebooks > li > a {
display: block;
width: 100%;
height: 100%;
padding: 10px;
}

.voila-notebooks > li > a > i {
padding: 0 10px
}
</style>
{% endblock %}

{% block body %}

{% set openInNewTab = 'target=_blank' %}
<script id="jupyter-config-data" type="application/json">
{{ page_config | tojson }}
</script>

<script src="{{ page_config['fullStaticUrl'] | e }}/treepage.js"></script>
{% set mainStyle = 'style="display: None;"' %}

<div id="voila-tree-main" {{mainStyle | safe}}>

{% endblock %}
11 changes: 1 addition & 10 deletions share/jupyter/voila/templates/lab/tree.html
Expand Up @@ -75,16 +75,8 @@
<script id="jupyter-config-data" type="application/json">
{{ page_config | tojson }}
</script>
{% if (theme != 'dark' and theme != 'light') %}
<script src="{{ page_config['fullStaticUrl'] | e }}/treepage.js"></script>
<div id="voila-tree-main" style="display: None;">
{% set mainStyle = 'style="display: None;"' %}
{% else %}
{% set mainStyle = '' %}
{% endif %}


<div id="voila-tree-main" {{mainStyle}}>
<div id="voila-tree-main" {{mainStyle | safe}}>
<div class="list-header">
<div class="list-header-text">
Select items to open with {{ "Voilà" if frontend == "voila" else frontend.capitalize() }}.
Expand All @@ -105,5 +97,4 @@
{% endif %}
{% endfor %}
</ul>
</div>
{% endblock %}

0 comments on commit 8a46a25

Please sign in to comment.