Initial commit

This commit is contained in:
Jeremy Tuloup 2020-12-04 15:15:41 +02:00
commit ddcf687a78
60 changed files with 16011 additions and 0 deletions

22
.eslintignore Normal file
View File

@ -0,0 +1,22 @@
lint-staged.config.js
.eslintrc.js
node_modules
**/build
**/lib
**/node_modules
**/mock_packages
**/static
**/typings
**/schemas
**/themes
coverage
*.map.js
*.bundle.js
# jetbrains IDE stuff
.idea/
# ms IDE stuff
.history/
.vscode/

56
.eslintrc.js Normal file
View File

@ -0,0 +1,56 @@
module.exports = {
env: {
browser: true,
es6: true,
commonjs: true,
node: true,
'jest/globals': true
},
root: true,
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'plugin:react/recommended',
'plugin:jest/recommended'
],
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.eslint.json',
sourceType: 'module'
},
plugins: ['@typescript-eslint', 'jest'],
rules: {
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'interface',
format: ['PascalCase'],
custom: {
regex: '^I[A-Z]',
match: true
}
}
],
'@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/quotes': [
'error',
'single',
{ avoidEscape: true, allowTemplateLiterals: false }
],
curly: ['error', 'all'],
eqeqeq: 'error',
'prefer-arrow-callback': 'error'
},
settings: {
react: {
version: 'detect'
}
}
};

116
.gitignore vendored Normal file
View File

@ -0,0 +1,116 @@
*.bundle.*
lib/
node_modules/
*.egg-info/
.ipynb_checkpoints
*.tsbuildinfo
# Created by https://www.gitignore.io/api/python
# Edit at https://www.gitignore.io/?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# OS X stuff
*.DS_Store
# End of https://www.gitignore.io/api/python
_temp_extension
junit.xml
[uU]ntitled*
jupyterlab_classic/static

5
.prettierignore Normal file
View File

@ -0,0 +1,5 @@
node_modules
**/node_modules
**/lib
**/package.json
**/static

3
.prettierrc Normal file
View File

@ -0,0 +1,3 @@
{
"singleQuote": true
}

29
LICENSE Normal file
View File

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2020, Jeremy Tuloup
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

21
MANIFEST.in Normal file
View File

@ -0,0 +1,21 @@
include LICENSE
include README.md
include pyproject.toml
include jupyter-config/jupyterlab-classic.json
include package.json
include install.json
include ts*.json
# Javascript files
graft src
graft style
prune **/node_modules
prune lib
# Patterns to exclude from any directory
global-exclude *~
global-exclude *.pyc
global-exclude *.pyo
global-exclude .git
global-exclude .ipynb_checkpoints

60
README.md Normal file
View File

@ -0,0 +1,60 @@
# jupyterlab-classic
The next gen old-school Notebook UI.
## Install
With `pip`:
```bash
pip install jupyterlab-classic
```
With `conda`:
```bash
conda install -c conda-forge jupyterlab-classic
```
## Usage
`jupyterlab-classic` can be started as a standalone app with:
```bash
python -m jupyterlab_classic
```
Existing federated JupyterLab extensions listed via:
```bash
jupyter labextension list
```
Should also be available when starting `jupyterlab-classic`.
## Motivation
JupyterLab is the next-gen UI for Project Jupyter. Approaching version 3.0, it is becoming more mature and provides and advanced computation environment, that can sometimes be compared to what traditional IDEs offer.
However in some cases, having a leaner, simpler, and more focused interface to work on a notebook is really useful.
The single document mode as currently implemented in JupyterLab helps address this issue, but still displays a couple of visual cues to the users that can be distracting.
The goal of the `jupyterlab-classic` project is to look as close to the classic notebook UI as possible, while leveraging the efforts put in the development of JupyterLab itself and its extension system.
Technically speaking, `jupyterlab-classic` reuses **many** of the existing plugins for JupyterLab (notebook, toolbar), and also supports pre-built (federated) third-party extensions using the new distribution system added in 3.0. That way, extensions built for JupyterLab should also be compatible with `jupyterlab-classic`, as long as they can be added to the application.
## Prior art
This project is mostly a reboot of the two previous attempts at making something similar:
- [simplest-notebook](https://github.com/yuvipanda/simplest-notebook)
- [jupyterlab-clarity-mode](https://github.com/jupytercalpoly/jupyterlab-clarity-mode)
These projects real expressed the need for a stripped down, minimal version of the Jupyter Notebook UI.
`jupyterlab-classic` contributes to that space With the added:
- Support for existing federated (prebuilt) JupyterLab extensions
- Repo structure, similar to JupyterLab
- Reusing as much as possible from upstream JupyterLab

154
builder/index.js Normal file
View File

@ -0,0 +1,154 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { PageConfig, URLExt } from '@jupyterlab/coreutils';
// Promise.allSettled polyfill, until our supported browsers implement it
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
if (Promise.allSettled === undefined) {
Promise.allSettled = promises =>
Promise.all(
promises.map(promise =>
promise.then(
value => ({
status: 'fulfilled',
value
}),
reason => ({
status: 'rejected',
reason
})
)
)
);
}
require('./style.css');
function loadScript(url) {
return new Promise((resolve, reject) => {
const newScript = document.createElement('script');
newScript.onerror = reject;
newScript.onload = resolve;
newScript.async = true;
document.head.appendChild(newScript);
newScript.src = url;
});
}
async function loadComponent(url, scope) {
await loadScript(url);
// From MIT-licensed https://github.com/module-federation/module-federation-examples/blob/af043acd6be1718ee195b2511adf6011fba4233c/advanced-api/dynamic-remotes/app1/src/App.js#L6-L12
// eslint-disable-next-line no-undef
await __webpack_init_sharing__('default');
const container = window._JUPYTERLAB[scope];
// Initialize the container, it may provide shared modules and may need ours
// eslint-disable-next-line no-undef
await container.init(__webpack_share_scopes__.default);
}
async function createModule(scope, module) {
try {
const factory = await window._JUPYTERLAB[scope].get(module);
return factory();
} catch (e) {
console.warn(
`Failed to create module: package: ${scope}; module: ${module}`
);
throw e;
}
}
/**
* The main function
*/
async function main() {
const App = require('@jupyterlab-classic/application').App;
const app = new App();
const mods = [
require('@jupyterlab-classic/application-extension'),
require('@jupyterlab/apputils-extension').default.filter(({ id }) =>
['@jupyterlab/apputils-extension:settings'].includes(id)
),
require('@jupyterlab/codemirror-extension').default.filter(({ id }) =>
['@jupyterlab/codemirror-extension:services'].includes(id)
),
require('@jupyterlab/mainmenu-extension'),
require('@jupyterlab/rendermime-extension'),
require('@jupyterlab/notebook-extension').default.filter(({ id }) =>
[
'@jupyterlab/notebook-extension:factory',
'@jupyterlab/notebook-extension:widget-factory',
'@jupyterlab/notebook-extension:tracker'
].includes(id)
)
];
const extension_data = JSON.parse(
PageConfig.getOption('federated_extensions')
);
const federatedExtensionPromises = [];
const federatedMimeExtensionPromises = [];
const federatedStylePromises = [];
const extensions = await Promise.allSettled(
extension_data.map(async data => {
await loadComponent(
`${URLExt.join(
PageConfig.getOption('fullLabextensionsUrl'),
data.name,
data.load
)}`,
data.name
);
return data;
})
);
extensions.forEach(p => {
if (p.status === 'rejected') {
// There was an error loading the component
console.error(p.reason);
return;
}
const data = p.value;
if (data.extension) {
federatedExtensionPromises.push(createModule(data.name, data.extension));
}
if (data.mimeExtension) {
federatedMimeExtensionPromises.push(
createModule(data.name, data.mimeExtension)
);
}
if (data.style) {
federatedStylePromises.push(createModule(data.name, data.style));
}
});
// Add the federated extensions.
const federatedExtensions = await Promise.allSettled(
federatedExtensionPromises
);
federatedExtensions.forEach(p => {
if (p.status === 'fulfilled') {
mods.push(p.value);
} else {
console.error(p.reason);
}
});
// Load all federated component styles and log errors for any that do not
(await Promise.allSettled(federatedStylePromises))
.filter(({ status }) => status === 'rejected')
.forEach(({ reason }) => {
console.error(reason);
});
app.registerPluginModules(mods);
await app.start();
}
window.addEventListener('load', main);

207
builder/package.json Normal file
View File

@ -0,0 +1,207 @@
{
"name": "@jupyterlab-classic/builder",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "webpack",
"build:prod": "webpack --mode=production",
"clean": "rimraf build",
"prepublishOnly": "yarn run build",
"watch": "webpack --watch"
},
"dependencies": {
"@jupyterlab-classic/application": "^0.1.0",
"@jupyterlab-classic/application-extension": "^0.1.0",
"@jupyterlab-classic/ui-components": "^0.1.0",
"@jupyterlab/apputils-extension": "^3.0.0-rc.10",
"@jupyterlab/codemirror-extension": "^3.0.0-rc.10",
"@jupyterlab/mainmenu-extension": "^3.0.0-rc.10",
"@jupyterlab/notebook-extension": "^3.0.0-rc.10",
"@jupyterlab/rendermime-extension": "^3.0.0-rc.10",
"@jupyterlab/theme-light-extension": "^3.0.0-rc.10",
"@jupyterlab/coreutils": "~5.0.0-rc.10"
},
"devDependencies": {
"@jupyterlab/builder": "^3.0.0-rc.10",
"css-loader": "~3.2.0",
"file-loader": "~5.0.2",
"fs-extra": "^8.1.0",
"glob": "~7.1.6",
"mini-css-extract-plugin": "~0.9.0",
"npm-run-all": "^4.1.5",
"raw-loader": "~4.0.0",
"rimraf": "~3.0.2",
"style-loader": "~1.0.1",
"svg-url-loader": "~6.0.0",
"url-loader": "~4.1.1",
"watch": "~1.0.2",
"webpack": "^5.7.0",
"webpack-bundle-analyzer": "^4.1.0",
"webpack-cli": "^4.2.0",
"webpack-merge": "^5.1.2",
"whatwg-fetch": "^3.0.0"
},
"resolutions": {
"@jupyterlab/application": "~3.0.0-rc.10",
"@jupyterlab/application-extension": "~3.0.0-rc.10",
"@jupyterlab/apputils": "~3.0.0-rc.10",
"@jupyterlab/apputils-extension": "~3.0.0-rc.10",
"@jupyterlab/attachments": "~3.0.0-rc.10",
"@jupyterlab/cells": "~3.0.0-rc.10",
"@jupyterlab/celltags": "~3.0.0-rc.10",
"@jupyterlab/celltags-extension": "~3.0.0-rc.10",
"@jupyterlab/codeeditor": "~3.0.0-rc.10",
"@jupyterlab/codemirror": "~3.0.0-rc.10",
"@jupyterlab/codemirror-extension": "~3.0.0-rc.10",
"@jupyterlab/completer": "~3.0.0-rc.10",
"@jupyterlab/completer-extension": "~3.0.0-rc.10",
"@jupyterlab/console": "~3.0.0-rc.10",
"@jupyterlab/console-extension": "~3.0.0-rc.10",
"@jupyterlab/coreutils": "~5.0.0-rc.10",
"@jupyterlab/csvviewer": "~3.0.0-rc.10",
"@jupyterlab/csvviewer-extension": "~3.0.0-rc.10",
"@jupyterlab/debugger": "~3.0.0-rc.10",
"@jupyterlab/debugger-extension": "~3.0.0-rc.10",
"@jupyterlab/docmanager": "~3.0.0-rc.10",
"@jupyterlab/docmanager-extension": "~3.0.0-rc.10",
"@jupyterlab/docregistry": "~3.0.0-rc.10",
"@jupyterlab/documentsearch": "~3.0.0-rc.10",
"@jupyterlab/documentsearch-extension": "~3.0.0-rc.10",
"@jupyterlab/extensionmanager": "^3.0.0-rc.10",
"@jupyterlab/extensionmanager-extension": "~3.0.0-rc.10",
"@jupyterlab/filebrowser": "~3.0.0-rc.10",
"@jupyterlab/filebrowser-extension": "~3.0.0-rc.10",
"@jupyterlab/fileeditor": "~3.0.0-rc.10",
"@jupyterlab/fileeditor-extension": "~3.0.0-rc.10",
"@jupyterlab/help-extension": "~3.0.0-rc.10",
"@jupyterlab/htmlviewer": "~3.0.0-rc.10",
"@jupyterlab/htmlviewer-extension": "~3.0.0-rc.10",
"@jupyterlab/hub-extension": "~3.0.0-rc.10",
"@jupyterlab/imageviewer": "~3.0.0-rc.10",
"@jupyterlab/imageviewer-extension": "~3.0.0-rc.10",
"@jupyterlab/inspector": "~3.0.0-rc.10",
"@jupyterlab/inspector-extension": "~3.0.0-rc.10",
"@jupyterlab/javascript-extension": "~3.0.0-rc.10",
"@jupyterlab/json-extension": "~3.0.0-rc.10",
"@jupyterlab/launcher": "~3.0.0-rc.10",
"@jupyterlab/launcher-extension": "~3.0.0-rc.10",
"@jupyterlab/logconsole": "~3.0.0-rc.10",
"@jupyterlab/logconsole-extension": "~3.0.0-rc.10",
"@jupyterlab/mainmenu": "~3.0.0-rc.10",
"@jupyterlab/mainmenu-extension": "~3.0.0-rc.10",
"@jupyterlab/markdownviewer-extension": "~3.0.0-rc.10",
"@jupyterlab/mathjax2": "~3.0.0-rc.10",
"@jupyterlab/mathjax2-extension": "~3.0.0-rc.10",
"@jupyterlab/metapackage": "~3.0.0-rc.10",
"@jupyterlab/nbconvert-css": "~3.0.0-rc.10",
"@jupyterlab/nbformat": "~3.0.0-rc.10",
"@jupyterlab/notebook": "~3.0.0-rc.10",
"@jupyterlab/notebook-extension": "~3.0.0-rc.10",
"@jupyterlab/observables": "~4.0.0-rc.10",
"@jupyterlab/outputarea": "~3.0.0-rc.10",
"@jupyterlab/pdf-extension": "~3.0.0-rc.10",
"@jupyterlab/property-inspector": "~3.0.0-rc.10",
"@jupyterlab/rendermime": "~3.0.0-rc.10",
"@jupyterlab/rendermime-extension": "~3.0.0-rc.10",
"@jupyterlab/rendermime-interfaces": "~3.0.0-rc.10",
"@jupyterlab/running": "~3.0.0-rc.10",
"@jupyterlab/running-extension": "~3.0.0-rc.10",
"@jupyterlab/services": "~6.0.0-rc.10",
"@jupyterlab/settingeditor": "~3.0.0-rc.10",
"@jupyterlab/settingeditor-extension": "~3.0.0-rc.10",
"@jupyterlab/settingregistry": "~3.0.0-rc.10",
"@jupyterlab/shortcuts-extension": "~3.0.0-rc.10",
"@jupyterlab/statedb": "~3.0.0-rc.10",
"@jupyterlab/statusbar": "~3.0.0-rc.10",
"@jupyterlab/statusbar-extension": "~3.0.0-rc.10",
"@jupyterlab/terminal": "~3.0.0-rc.10",
"@jupyterlab/terminal-extension": "~3.0.0-rc.10",
"@jupyterlab/theme-dark-extension": "~3.0.0-rc.10",
"@jupyterlab/theme-light-extension": "~3.0.0-rc.10",
"@jupyterlab/toc": "~5.0.0-rc.10",
"@jupyterlab/toc-extension": "~5.0.0-rc.10",
"@jupyterlab/tooltip": "~3.0.0-rc.10",
"@jupyterlab/tooltip-extension": "~3.0.0-rc.10",
"@jupyterlab/translation": "~3.0.0-rc.10",
"@jupyterlab/translation-extension": "~3.0.0-rc.10",
"@jupyterlab/ui-components": "~3.0.0-rc.10",
"@jupyterlab/ui-components-extension": "~3.0.0-rc.10",
"@jupyterlab/vdom": "~3.0.0-rc.10",
"@jupyterlab/vdom-extension": "~3.0.0-rc.10",
"@jupyterlab/vega5-extension": "~3.0.0-rc.10",
"@lumino/algorithm": "^1.2.3",
"@lumino/application": "^1.8.4",
"@lumino/commands": "^1.10.1",
"@lumino/coreutils": "^1.4.3",
"@lumino/disposable": "^1.3.5",
"@lumino/domutils": "^1.1.7",
"@lumino/dragdrop": "^1.5.1",
"@lumino/messaging": "^1.3.3",
"@lumino/properties": "^1.1.6",
"@lumino/signaling": "^1.3.5",
"@lumino/virtualdom": "^1.6.1",
"@lumino/widgets": "^1.14.0",
"react": "^17.0.1",
"react-dom": "^17.0.1"
},
"jupyterlab": {
"name": "JupyterLab Classic",
"version": "0.1.0",
"extensions": {
"@jupyterlab-classic/application-extension": ""
},
"buildDir": "./static",
"outputDir": ".",
"singletonPackages": [
"@jupyterlab/application",
"@jupyterlab/apputils",
"@jupyterlab/codeeditor",
"@jupyterlab/codemirror",
"@jupyterlab/completer",
"@jupyterlab/console",
"@jupyterlab/coreutils",
"@jupyterlab/debugger",
"@jupyterlab/docmanager",
"@jupyterlab/documentsearch",
"@jupyterlab/extensionmanager",
"@jupyterlab/filebrowser",
"@jupyterlab/fileeditor",
"@jupyterlab/imageviewer",
"@jupyterlab/inspector",
"@jupyterlab/launcher",
"@jupyterlab/logconsole",
"@jupyterlab/mainmenu",
"@jupyterlab/markdownviewer",
"@jupyterlab/notebook",
"@jupyterlab/rendermime",
"@jupyterlab/rendermime-interfaces",
"@jupyterlab/services",
"@jupyterlab/settingeditor",
"@jupyterlab/settingregistry",
"@jupyterlab/statedb",
"@jupyterlab/statusbar",
"@jupyterlab/terminal",
"@jupyterlab/tooltip",
"@jupyterlab/translation",
"@jupyterlab/ui-components",
"@lumino/algorithm",
"@lumino/application",
"@lumino/commands",
"@lumino/coreutils",
"@lumino/disposable",
"@lumino/domutils",
"@lumino/dragdrop",
"@lumino/messaging",
"@lumino/properties",
"@lumino/signaling",
"@lumino/virtualdom",
"@lumino/widgets",
"react",
"react-dom"
],
"linkedPackages": {
"@jupyterlab-classic/application": "../packages/application",
"@jupyterlab-classic/application-extension": "../packages/application-extension"
}
}
}

37
builder/publicpath.js Normal file
View File

@ -0,0 +1,37 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
// We dynamically set the webpack public path based on the page config
// settings from the JupyterLab app. We copy some of the pageconfig parsing
// logic in @jupyterlab/coreutils below, since this must run before any other
// files are loaded (including @jupyterlab/coreutils).
/**
* Get global configuration data for the Jupyter application.
*
* @param name - The name of the configuration option.
*
* @returns The config value or an empty string if not found.
*
* #### Notes
* All values are treated as strings.
* For browser based applications, it is assumed that the page HTML
* includes a script tag with the id `jupyter-config-data` containing the
* configuration as valid JSON. In order to support the classic Notebook,
* we fall back on checking for `body` data of the given `name`.
*/
function getOption(name) {
let configData = Object.create(null);
// Use script tag if available.
if (typeof document !== 'undefined' && document) {
const el = document.getElementById('jupyter-config-data');
if (el) {
configData = JSON.parse(el.textContent || '{}');
}
}
return configData[name] || '';
}
// eslint-disable-next-line no-undef
__webpack_public_path__ = getOption('fullStaticUrl') + '/';

7
builder/style.css Normal file
View File

@ -0,0 +1,7 @@
@import url('~@jupyterlab-classic/application-extension/style/index.css');
@import url('~@jupyterlab-classic/ui-components/style/index.css');
@import url('~@jupyterlab/codemirror-extension/style/index.css');
@import url('~@jupyterlab/rendermime-extension/style/index.css');
@import url('~@jupyterlab/notebook-extension/style/index.css');
@import url('~@jupyterlab/theme-light-extension/style/index.css');

88
builder/webpack.config.js Normal file
View File

@ -0,0 +1,88 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
// Heavily inspired (and slightly tweaked) from:
// https://github.com/jupyterlab/jupyterlab/blob/master/examples/federated/core_package/webpack.config.js
const fs = require('fs-extra');
const path = require('path');
const webpack = require('webpack');
const merge = require('webpack-merge').default;
const { ModuleFederationPlugin } = webpack.container;
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
.BundleAnalyzerPlugin;
const Build = require('@jupyterlab/builder').Build;
const baseConfig = require('@jupyterlab/builder/lib/webpack.config.base');
const data = require('./package.json');
const names = Object.keys(data.dependencies).filter(name => {
const packageData = require(path.join(name, 'package.json'));
return packageData.jupyterlab !== undefined;
});
// Ensure a clear build directory.
const buildDir = path.resolve(__dirname, 'build');
if (fs.existsSync(buildDir)) {
fs.removeSync(buildDir);
}
fs.ensureDirSync(buildDir);
// Copy extra files
const index = path.resolve(__dirname, 'index.js');
const cssImports = path.resolve(__dirname, 'style.css');
fs.copySync(index, path.resolve(buildDir, 'index.js'));
fs.copySync(cssImports, path.resolve(buildDir, 'style.css'));
const extras = Build.ensureAssets({
packageNames: names,
output: buildDir
});
const singletons = {};
data.jupyterlab.singletonPackages.forEach(element => {
singletons[element] = { singleton: true };
});
// Make a bootstrap entrypoint
const entryPoint = path.join(buildDir, 'bootstrap.js');
const bootstrap = 'import("./index.js");';
fs.writeFileSync(entryPoint, bootstrap);
if (process.env.NODE_ENV === 'production') {
baseConfig.mode = 'production';
}
if (process.argv.includes('--analyze')) {
extras.push(new BundleAnalyzerPlugin());
}
module.exports = [
merge(baseConfig, {
mode: 'development',
entry: ['./publicpath.js', './' + path.relative(__dirname, entryPoint)],
output: {
path: path.resolve(__dirname, '..', 'jupyterlab_classic/static/'),
library: {
type: 'var',
name: ['_JUPYTERLAB', 'CORE_OUTPUT']
},
filename: 'bundle.js'
},
plugins: [
new ModuleFederationPlugin({
library: {
type: 'var',
name: ['_JUPYTERLAB', 'CORE_LIBRARY_FEDERATION']
},
name: 'CORE_FEDERATION',
shared: {
...data.resolutions,
...singletons
}
})
]
})
].concat(extras);

View File

@ -0,0 +1,7 @@
{
"ServerApp": {
"jpserver_extensions": {
"jupyterlab_classic": true
}
}
}

View File

@ -0,0 +1,15 @@
from ._version import __version__
from .app import ClassicApp
from .serverextension import load_jupyter_server_extension
def _jupyter_server_extension_paths():
return [
{
'module': 'jupyterlab'
}
]
def _jupyter_server_extension_points():
return [{"module": "jupyterlab_classic", "app": ClassicApp}]

View File

@ -0,0 +1,4 @@
from jupyterlab_classic.app import main
import sys
sys.exit(main())

View File

@ -0,0 +1 @@
__version__ = "0.1.0"

58
jupyterlab_classic/app.py Normal file
View File

@ -0,0 +1,58 @@
import os
from jupyter_server.base.handlers import JupyterHandler
from jupyter_server.extension.handler import (
ExtensionHandlerMixin,
ExtensionHandlerJinjaMixin,
)
from jupyterlab import LabApp
from jupyter_server.utils import url_path_join as ujoin
from tornado import web
from ._version import __version__
HERE = os.path.dirname(__file__)
version = __version__
class ClassicHandler(ExtensionHandlerJinjaMixin, ExtensionHandlerMixin, JupyterHandler):
@web.authenticated
def get(self):
config_data = {
"appVersion": version,
"baseUrl": self.base_url,
"token": self.settings["token"],
"fullStaticUrl": ujoin(self.base_url, "static", self.name),
"frontendUrl": ujoin(self.base_url, "classic/"),
}
return self.write(
self.render_template(
"index.html",
static=self.static_url,
base_url=self.base_url,
token=self.settings["token"],
page_config=config_data,
)
)
class ClassicApp(LabApp):
extension_url = '/classic'
app_url = "/classic"
load_other_extensions = True
name = __name__
app_name = 'JupyterLab Classic'
static_dir = os.path.join(HERE, 'static')
templates_dir = os.path.join(HERE, 'templates')
app_version = version
def initialize_handlers(self):
super().initialize_handlers()
self.handlers.append(('/classic', ClassicHandler))
main = launch_new_instance = ClassicApp.launch_instance
if __name__ == '__main__':
main()

View File

@ -0,0 +1,10 @@
from .app import ClassicApp
def load_jupyter_server_extension(serverapp):
extension = ClassicApp()
extension.serverapp = serverapp
extension.load_config_file()
extension.update_config(serverapp.config)
extension.parse_command_line(serverapp.extra_args)
extension.initialize()

View File

@ -0,0 +1,59 @@
<!DOCTYPE html>
<!--
Copyright (c) Jupyter Development Team.
Distributed under the terms of the Modified BSD License.
-->
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}{{page_title | e}}{% endblock %}</title>
{% block favicon %}<link rel="shortcut icon" type="image/x-icon" href="/static/favicons/favicon.ico">{% endblock %}
</head>
<body>
{% block stylesheet %}
<style type="text/css">
/* disable initial hide */
div#header, div#site {
display: block;
}
</style>
{% endblock %}
{% block site %}
<div class="error">
{% block h1_error %}
<h1>{{status_code | e}} : {{status_message | e}}</h1>
{% endblock h1_error %}
{% block error_detail %}
{% if message %}
<p>The error was:</p>
<div class="traceback-wrapper">
<pre class="traceback">{{message | e}}</pre>
</div>
{% endif %}
{% endblock %}
</header>
{% endblock %}
{% block script %}
<script type='text/javascript'>
window.onload = function () {
var tb = document.getElementsByClassName('traceback')[0];
tb.scrollTop = tb.scrollHeight;
{% if message %}
console.error("{{message | e}}")
{% endif %}
};
</script>
{% endblock script %}
</body>
</html>

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{page_config['appName'] | e}}</title>
</head>
<body>
{# Copy so we do not modify the page_config with updates. #}
{% set page_config_full = page_config.copy() %}
{# Set a dummy variable - we just want the side effect of the update. #}
{% set _ = page_config_full.update(baseUrl=base_url, wsUrl=ws_url) %}
<script id="jupyter-config-data" type="application/json">
{{ page_config_full | tojson }}
</script>
<script src="{{page_config['fullStaticUrl'] | e}}/bundle.js" main="index"></script>
<script type="text/javascript">
/* Remove token from URL. */
(function () {
var parsedUrl = new URL(window.location.href);
if (parsedUrl.searchParams.get('token')) {
parsedUrl.searchParams.delete('token');
window.history.replaceState({ }, '', parsedUrl.href);
}
})();
</script>
</body>
</html>

5
lerna.json Normal file
View File

@ -0,0 +1,5 @@
{
"npmClient": "yarn",
"version": "2.0.0",
"useWorkspaces": true
}

27
lint-staged.config.js Normal file
View File

@ -0,0 +1,27 @@
const escape = require('shell-quote').quote;
const fs = require('fs');
const isWin = process.platform === 'win32';
const escapeFileNames = filenames =>
filenames
.filter(filename => fs.existsSync(filename))
.map(filename => `"${isWin ? filename : escape([filename])}"`)
.join(' ');
module.exports = {
'**/*{.css,.json,.md}': filenames => {
const escapedFileNames = escapeFileNames(filenames);
return [
`prettier --write ${escapedFileNames}`,
`git add -f ${escapedFileNames}`
];
},
'**/*{.ts,.tsx,.js,.jsx}': filenames => {
const escapedFileNames = escapeFileNames(filenames);
return [
`prettier --write ${escapedFileNames}`,
`eslint --fix ${escapedFileNames}`,
`git add -f ${escapedFileNames}`
];
}
};

161
package.json Normal file
View File

@ -0,0 +1,161 @@
{
"private": true,
"homepage": "https://github.com/jtpio/jupyterlab-classic",
"repository": {
"type": "git",
"url": "https://github.com/jtpio/jupyterlab-classic"
},
"bugs": {
"url": "https://github.com/jtpio/jupyterlab-classic/issues"
},
"license": "BSD-3-Clause",
"author": "Jeremy Tuloup",
"workspaces": {
"packages": [
"builder",
"packages/*"
]
},
"scripts": {
"build": "lerna run build",
"build:prod": "lerna run build:prod",
"build:test": "lerna run build:test",
"clean": "lerna run clean",
"install": "lerna bootstrap",
"eslint": "eslint . --ext .ts,.tsx --fix",
"eslint:check": "eslint . --ext .ts,.tsx",
"prettier": "prettier --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
"prettier:check": "prettier --list-different \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"",
"publish": "yarn run clean && yarn run build && lerna publish",
"test": "lerna run test"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.2.0",
"@typescript-eslint/parser": "^4.2.0",
"eslint": "^7.10.0",
"eslint-plugin-jest": "^24.1.3",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.21.5",
"husky": "^3",
"jest": "^26.4.2",
"jest-junit": "^11.1.0",
"jest-raw-loader": "^1.0.1",
"jest-summary-reporter": "^0.0.2",
"lerna": "^3.22.1",
"lint-staged": "^10.4.0",
"npm-run-all": "^4.1.5",
"prettier": "^1.19.0",
"rimraf": "^3.0.2",
"shell-quote": "^1.7.2",
"typescript": "~4.0.3"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"resolutions": {
"@jupyterlab/application": "3.0.0-rc.10",
"@jupyterlab/application-extension": "3.0.0-rc.10",
"@jupyterlab/apputils": "3.0.0-rc.10",
"@jupyterlab/apputils-extension": "3.0.0-rc.10",
"@jupyterlab/attachments": "3.0.0-rc.10",
"@jupyterlab/cells": "3.0.0-rc.10",
"@jupyterlab/celltags": "3.0.0-rc.10",
"@jupyterlab/celltags-extension": "3.0.0-rc.10",
"@jupyterlab/codeeditor": "3.0.0-rc.10",
"@jupyterlab/codemirror": "3.0.0-rc.10",
"@jupyterlab/codemirror-extension": "3.0.0-rc.10",
"@jupyterlab/completer": "3.0.0-rc.10",
"@jupyterlab/completer-extension": "3.0.0-rc.10",
"@jupyterlab/console": "3.0.0-rc.10",
"@jupyterlab/console-extension": "3.0.0-rc.10",
"@jupyterlab/coreutils": "5.0.0-rc.10",
"@jupyterlab/csvviewer": "3.0.0-rc.10",
"@jupyterlab/csvviewer-extension": "3.0.0-rc.10",
"@jupyterlab/debugger": "3.0.0-rc.10",
"@jupyterlab/debugger-extension": "3.0.0-rc.10",
"@jupyterlab/docmanager": "3.0.0-rc.10",
"@jupyterlab/docmanager-extension": "3.0.0-rc.10",
"@jupyterlab/docregistry": "3.0.0-rc.10",
"@jupyterlab/documentsearch": "3.0.0-rc.10",
"@jupyterlab/documentsearch-extension": "3.0.0-rc.10",
"@jupyterlab/extensionmanager": "3.0.0-rc.10",
"@jupyterlab/extensionmanager-extension": "3.0.0-rc.10",
"@jupyterlab/filebrowser": "3.0.0-rc.10",
"@jupyterlab/filebrowser-extension": "3.0.0-rc.10",
"@jupyterlab/fileeditor": "3.0.0-rc.10",
"@jupyterlab/fileeditor-extension": "3.0.0-rc.10",
"@jupyterlab/help-extension": "3.0.0-rc.10",
"@jupyterlab/htmlviewer": "3.0.0-rc.10",
"@jupyterlab/htmlviewer-extension": "3.0.0-rc.10",
"@jupyterlab/hub-extension": "3.0.0-rc.10",
"@jupyterlab/imageviewer": "3.0.0-rc.10",
"@jupyterlab/imageviewer-extension": "3.0.0-rc.10",
"@jupyterlab/inspector": "3.0.0-rc.10",
"@jupyterlab/inspector-extension": "3.0.0-rc.10",
"@jupyterlab/javascript-extension": "3.0.0-rc.10",
"@jupyterlab/json-extension": "3.0.0-rc.10",
"@jupyterlab/launcher": "3.0.0-rc.10",
"@jupyterlab/launcher-extension": "3.0.0-rc.10",
"@jupyterlab/logconsole": "3.0.0-rc.10",
"@jupyterlab/logconsole-extension": "3.0.0-rc.10",
"@jupyterlab/mainmenu": "3.0.0-rc.10",
"@jupyterlab/mainmenu-extension": "3.0.0-rc.10",
"@jupyterlab/markdownviewer-extension": "3.0.0-rc.10",
"@jupyterlab/mathjax2": "3.0.0-rc.10",
"@jupyterlab/mathjax2-extension": "3.0.0-rc.10",
"@jupyterlab/metapackage": "3.0.0-rc.10",
"@jupyterlab/nbconvert-css": "3.0.0-rc.10",
"@jupyterlab/nbformat": "3.0.0-rc.10",
"@jupyterlab/notebook": "3.0.0-rc.10",
"@jupyterlab/notebook-extension": "3.0.0-rc.10",
"@jupyterlab/observables": "4.0.0-rc.10",
"@jupyterlab/outputarea": "3.0.0-rc.10",
"@jupyterlab/pdf-extension": "3.0.0-rc.10",
"@jupyterlab/property-inspector": "3.0.0-rc.10",
"@jupyterlab/rendermime": "3.0.0-rc.10",
"@jupyterlab/rendermime-extension": "3.0.0-rc.10",
"@jupyterlab/rendermime-interfaces": "3.0.0-rc.10",
"@jupyterlab/running": "3.0.0-rc.10",
"@jupyterlab/running-extension": "3.0.0-rc.10",
"@jupyterlab/services": "6.0.0-rc.10",
"@jupyterlab/settingeditor": "3.0.0-rc.10",
"@jupyterlab/settingeditor-extension": "3.0.0-rc.10",
"@jupyterlab/settingregistry": "3.0.0-rc.10",
"@jupyterlab/shortcuts-extension": "3.0.0-rc.10",
"@jupyterlab/statedb": "3.0.0-rc.10",
"@jupyterlab/statusbar": "3.0.0-rc.10",
"@jupyterlab/statusbar-extension": "3.0.0-rc.10",
"@jupyterlab/terminal": "3.0.0-rc.10",
"@jupyterlab/terminal-extension": "3.0.0-rc.10",
"@jupyterlab/theme-dark-extension": "3.0.0-rc.10",
"@jupyterlab/theme-light-extension": "3.0.0-rc.10",
"@jupyterlab/toc": "5.0.0-rc.10",
"@jupyterlab/toc-extension": "5.0.0-rc.10",
"@jupyterlab/tooltip": "3.0.0-rc.10",
"@jupyterlab/tooltip-extension": "3.0.0-rc.10",
"@jupyterlab/translation": "3.0.0-rc.10",
"@jupyterlab/translation-extension": "3.0.0-rc.10",
"@jupyterlab/ui-components": "3.0.0-rc.10",
"@jupyterlab/ui-components-extension": "3.0.0-rc.10",
"@jupyterlab/vdom": "3.0.0-rc.10",
"@jupyterlab/vdom-extension": "3.0.0-rc.10",
"@jupyterlab/vega5-extension": "3.0.0-rc.10",
"@lumino/algorithm": "^1.2.3",
"@lumino/application": "^1.8.4",
"@lumino/commands": "^1.10.1",
"@lumino/coreutils": "^1.4.3",
"@lumino/disposable": "^1.3.5",
"@lumino/domutils": "^1.1.7",
"@lumino/dragdrop": "^1.5.1",
"@lumino/messaging": "^1.3.3",
"@lumino/properties": "^1.1.6",
"@lumino/signaling": "^1.3.5",
"@lumino/virtualdom": "^1.6.1",
"@lumino/widgets": "^1.14.0",
"react": "^17.0.1",
"react-dom": "^17.0.1"
}
}

View File

@ -0,0 +1,61 @@
{
"name": "@jupyterlab-classic/application-extension",
"version": "0.1.0",
"description": "JupyterLab Classic - Application Extension",
"homepage": "https://github.com/jtpio/jupyterlab-classic",
"bugs": {
"url": "https://github.com/jtpio/jupyterlab-classic/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/jtpio/jupyterlab-classic.git"
},
"license": "BSD-3-Clause",
"author": "Project Jupyter",
"sideEffects": [
"style/**/*.css"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
"style": "style/index.css",
"directories": {
"lib": "lib/"
},
"files": [
"lib/*.d.ts",
"lib/*.js.map",
"lib/*.js",
"schema/*.json",
"style/**/*.css"
],
"scripts": {
"build": "tsc -b",
"clean": "rimraf lib && rimraf tsconfig.tsbuildinfo",
"docs": "typedoc src",
"prepublishOnly": "npm run build",
"watch": "tsc -b --watch"
},
"dependencies": {
"@jupyterlab-classic/application": "0.1.0",
"@jupyterlab-classic/ui-components": "0.1.0",
"@jupyterlab/application": "^3.0.0-rc.10",
"@jupyterlab/apputils": "^3.0.0-rc.10",
"@jupyterlab/codeeditor": "^3.0.0-rc.10",
"@jupyterlab/codemirror": "^3.0.0-rc.10",
"@jupyterlab/docregistry": "^3.0.0-rc.10",
"@jupyterlab/docmanager": "^3.0.0-rc.10",
"@jupyterlab/notebook": "^3.0.0-rc.10",
"@jupyterlab/translation": "^3.0.0-rc.10",
"@lumino/widgets": "^1.14.0"
},
"devDependencies": {
"rimraf": "~3.0.0",
"typescript": "~4.0.2"
},
"publishConfig": {
"access": "public"
},
"jupyterlab": {
"extension": true
}
}

View File

@ -0,0 +1,367 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import {
IRouter,
JupyterFrontEnd,
JupyterFrontEndPlugin,
Router
} from '@jupyterlab/application';
import {
sessionContextDialogs,
ISessionContextDialogs,
DOMUtils
} from '@jupyterlab/apputils';
import { PageConfig } from '@jupyterlab/coreutils';
import {
DocumentManager,
IDocumentManager,
renameDialog
} from '@jupyterlab/docmanager';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import { NotebookPanel } from '@jupyterlab/notebook';
import { ITranslator, TranslationManager } from '@jupyterlab/translation';
import {
App,
ClassicShell,
IClassicShell
} from '@jupyterlab-classic/application';
import { jupyterIcon } from '@jupyterlab-classic/ui-components';
import { Widget } from '@lumino/widgets';
/**
* The default notebook factory.
*/
const NOTEBOOK_FACTORY = 'Notebook';
/**
* The command IDs used by the application plugin.
*/
namespace CommandIDs {
export const open = 'open';
}
/**
* A minimal document manager plugin.
* TODO: move to @jupyterlab-classic/docmanager-extension
*/
const doc: JupyterFrontEndPlugin<IDocumentManager> = {
id: '@jupyterlab-classic/application-extension:docmanager',
provides: IDocumentManager,
autoStart: true,
activate: (app: JupyterFrontEnd) => {
const opener = {
open: (widget: Widget, options: DocumentRegistry.IOpenOptions) => {
app.shell.add(widget, 'main', options);
}
};
const docManager = new DocumentManager({
registry: app.docRegistry,
manager: app.serviceManager,
opener
});
app.commands.addCommand(CommandIDs.open, {
label: 'Open a document',
execute: (args: any) => {
const path = args['path'] as string;
const factory = args['factory'] as string;
const options = args['options'] as DocumentRegistry.IOpenOptions;
const closable = args['closable'] as boolean;
const widget = docManager.open(path, factory, undefined, options);
if (widget) {
widget.title.closable = closable ?? true;
}
}
});
return docManager;
}
};
/**
* The kernel logo plugin.
*/
const kernelLogo: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:kernelLogo',
autoStart: true,
requires: [IClassicShell],
activate: (app: JupyterFrontEnd, shell: IClassicShell) => {
const { serviceManager } = app;
const baseUrl = PageConfig.getBaseUrl();
let widget: Widget;
// TODO: this signal might not be needed if we assume there is always only
// one notebook in the main area
shell.currentChanged.connect(async () => {
if (widget) {
widget.dispose();
widget.parent = null;
}
const current = shell.currentWidget;
if (!(current instanceof NotebookPanel)) {
return;
}
await current.sessionContext.ready;
const name = current.sessionContext.session?.kernel?.name ?? '';
const spec = serviceManager.kernelspecs?.specs?.kernelspecs[name];
if (!spec) {
return;
}
let kernelIconUrl = spec.resources['logo-64x64'];
if (!kernelIconUrl) {
return;
}
const index = kernelIconUrl.indexOf('kernelspecs');
kernelIconUrl = baseUrl + kernelIconUrl.slice(index);
const node = document.createElement('div');
const img = document.createElement('img');
img.src = kernelIconUrl;
img.title = spec.display_name;
node.appendChild(img);
widget = new Widget({ node });
widget.addClass('jp-ClassicKernelLogo');
app.shell.add(widget, 'top', { rank: 10_010 });
});
}
};
/**
* The logo plugin.
*/
const logo: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:logo',
autoStart: true,
activate: (app: JupyterFrontEnd) => {
const logo = new Widget();
jupyterIcon.element({
container: logo.node,
elementPosition: 'center',
padding: '2px 2px 2px 8px',
height: '28px',
width: 'auto'
});
logo.id = 'jp-ClassicLogo';
app.shell.add(logo, 'top', { rank: 0 });
}
};
/**
* The main plugin.
*/
const main: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:main',
autoStart: true,
activate: () => {
console.log(main.id, 'activated');
}
};
/**
* The default paths for a JupyterLab Classic app.
*/
const paths: JupyterFrontEndPlugin<JupyterFrontEnd.IPaths> = {
id: '@jupyterlab-classic/application-extension:paths',
activate: (app: JupyterFrontEnd): JupyterFrontEnd.IPaths => {
if (!(app instanceof App)) {
throw new Error(`${paths.id} must be activated in JupyterLab Classic.`);
}
return app.paths;
},
autoStart: true,
provides: JupyterFrontEnd.IPaths
};
/**
* The default URL router provider.
*/
const router: JupyterFrontEndPlugin<IRouter> = {
id: '@jupyterlab-classic/application-extension:router',
requires: [JupyterFrontEnd.IPaths],
activate: (app: JupyterFrontEnd, paths: JupyterFrontEnd.IPaths) => {
const { commands } = app;
const base = paths.urls.base;
const router = new Router({ base, commands });
void app.started.then(() => {
// Route the very first request on load.
void router.route();
// Route all pop state events.
window.addEventListener('popstate', () => {
void router.route();
});
});
return router;
},
autoStart: true,
provides: IRouter
};
/**
* The default session dialogs plugin
*/
const sessionDialogs: JupyterFrontEndPlugin<ISessionContextDialogs> = {
id: '@jupyterlab-classic/application-extension:sessionDialogs',
provides: ISessionContextDialogs,
autoStart: true,
activate: () => sessionContextDialogs
};
/**
* The default JupyterLab Classic application shell.
*/
const shell: JupyterFrontEndPlugin<IClassicShell> = {
id: '@jupyterlab-classic/application-extension:shell',
activate: (app: JupyterFrontEnd) => {
if (!(app.shell instanceof ClassicShell)) {
throw new Error(`${shell.id} did not find a ClassicShell instance.`);
}
return app.shell;
},
autoStart: true,
provides: IClassicShell
};
/**
* A plugin to provide a spacer at rank 10000 for flex panels
*/
const spacer: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:spacer',
autoStart: true,
activate: (app: JupyterFrontEnd) => {
const spacer = new Widget();
spacer.id = DOMUtils.createDomID();
spacer.addClass('jp-ClassicSpacer');
app.shell.add(spacer, 'top', { rank: 10000 });
}
};
/**
* A plugin to display the title of the notebook
*/
const title: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:title',
autoStart: true,
requires: [IClassicShell],
optional: [IDocumentManager, IRouter],
activate: (
app: JupyterFrontEnd,
shell: IClassicShell,
docManager: IDocumentManager | null,
router: IRouter | null
) => {
// TODO: this signal might not be needed if we assume there is always only
// one notebook in the main area
const widget = new Widget();
widget.id = 'jp-title';
app.shell.add(widget, 'top', { rank: 10 });
shell.currentChanged.connect(async () => {
const current = shell.currentWidget;
if (!(current instanceof NotebookPanel)) {
return;
}
const h = document.createElement('h1');
h.textContent = current.title.label;
widget.node.appendChild(h);
widget.node.style.marginLeft = '10px';
if (docManager) {
widget.node.onclick = async () => {
const result = await renameDialog(
docManager,
current.sessionContext.path
);
if (result) {
h.textContent = result.path;
if (router) {
// TODO: better handle this
router.navigate(`/classic/tree/${result.path}`, {
skipRouting: true
});
}
}
};
}
});
}
};
/**
* A simplified Translator
*/
const translator: JupyterFrontEndPlugin<ITranslator> = {
id: '@jupyterlab-classic/application-extension:translator',
activate: (app: JupyterFrontEnd<JupyterFrontEnd.IShell>): ITranslator => {
const translationManager = new TranslationManager();
return translationManager;
},
autoStart: true,
provides: ITranslator
};
/**
* The default tree route resolver plugin.
*/
const tree: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab-classic/application-extension:tree-resolver',
autoStart: true,
requires: [IRouter],
activate: (app: JupyterFrontEnd, router: IRouter): void => {
const { commands } = app;
const treePattern = new RegExp('/tree/(.*)');
const command = 'router:tree';
commands.addCommand(command, {
execute: (args: any) => {
const parsed = args as IRouter.ILocation;
const matches = parsed.path.match(treePattern);
if (!matches) {
return;
}
const [, path] = matches;
app.restored.then(() => {
commands.execute(CommandIDs.open, {
path,
factory: NOTEBOOK_FACTORY
});
});
}
});
router.register({ command, pattern: treePattern });
}
};
/**
* Export the plugins as default.
*/
const plugins: JupyterFrontEndPlugin<any>[] = [
doc,
kernelLogo,
logo,
main,
paths,
router,
sessionDialogs,
shell,
spacer,
title,
translator,
tree
];
export default plugins;

View File

@ -0,0 +1,38 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
|
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
.jp-ClassicSpacer {
flex-grow: 1;
flex-shrink: 1;
}
.jp-ClassicKernelLogo {
flex: 0 0 auto;
display: flex;
align-items: center;
text-align: center;
margin-right: 8px;
}
.jp-ClassicKernelLogo img {
max-width: 28px;
max-height: 28px;
display: flex;
}
#jp-title h1 {
cursor: pointer;
font-size: 18px;
margin: 0;
font-weight: normal;
color: var(--jp-ui-font-color0);
font-family: var(--jp-ui-font-family);
line-height: calc(1.5 * var(--jp-private-title-panel-height));
}
#jp-title h1:hover {
background: var(--jp-layout-color2);
}

View File

@ -0,0 +1,4 @@
@import url('~@jupyterlab-classic/application/style/index.css');
@import url('~@lumino/widgets/style/index.css');
@import url('./base.css');

View File

@ -0,0 +1,13 @@
{
"extends": "../../tsconfigbase",
"compilerOptions": {
"outDir": "lib",
"rootDir": "src"
},
"include": ["src/*"],
"references": [
{
"path": "../application"
}
]
}

View File

@ -0,0 +1 @@
module.exports = require('@jupyterlab/testutils/lib/babel.config');

View File

@ -0,0 +1,22 @@
const func = require('@jupyterlab/testutils/lib/jest-config');
const upstream = func(__dirname);
let local = {
preset: 'ts-jest/presets/js-with-babel',
transformIgnorePatterns: ['/node_modules/(?!(@jupyterlab/.*)/)'],
globals: {
'ts-jest': {
tsconfig: './tsconfig.test.json'
}
},
transform: {
'\\.(ts|tsx)?$': 'ts-jest',
'\\.svg$': 'jest-raw-loader'
}
};
Object.keys(local).forEach(option => {
upstream[option] = local[option];
});
module.exports = upstream;

View File

@ -0,0 +1,67 @@
{
"name": "@jupyterlab-classic/application",
"version": "0.1.0",
"description": "JupyterLab Classic - Application",
"homepage": "https://github.com/jtpio/jupyterlab-classic",
"bugs": {
"url": "https://github.com/jtpio/jupyterlab-classic/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/jtpio/jupyterlab-classic.git"
},
"license": "BSD-3-Clause",
"author": "Project Jupyter",
"sideEffects": [
"style/*.css"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
"style": "style/index.css",
"directories": {
"lib": "lib/"
},
"files": [
"lib/*.d.ts",
"lib/*.js.map",
"lib/*.js",
"style/*.css"
],
"scripts": {
"build": "tsc -b",
"build:test": "tsc --build tsconfig.test.json",
"clean": "rimraf lib && rimraf tsconfig.tsbuildinfo",
"docs": "typedoc src",
"prepublishOnly": "npm run build",
"test": "jest",
"test:cov": "jest --collect-coverage",
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
"test:debug:watch": "node --inspect-brk node_modules/.bin/jest --runInBand --watch",
"watch": "tsc -b --watch"
},
"dependencies": {
"@jupyterlab/application": "^3.0.0-rc.10",
"@jupyterlab/docregistry": "^3.0.0-rc.10",
"@jupyterlab/coreutils": "^5.0.0-rc.10",
"@jupyterlab/ui-components": "^3.0.0-rc.10",
"@lumino/algorithm": "^1.3.3",
"@lumino/coreutils": "^1.5.3",
"@lumino/messaging": "^1.4.3",
"@lumino/signaling": "^1.4.3",
"@lumino/widgets": "^1.14.0",
"es6-promise": "~4.2.8"
},
"devDependencies": {
"@babel/core": "^7.11.6",
"@babel/preset-env": "^7.12.1",
"@jupyterlab/testutils": "^3.0.0-rc.10",
"@types/jest": "^26.0.10",
"jest": "^26.4.2",
"rimraf": "~3.0.0",
"ts-jest": "^26.3.0",
"typescript": "~4.0.3"
},
"publishConfig": {
"access": "public"
}
}

View File

@ -0,0 +1,129 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import {
JupyterFrontEnd,
JupyterFrontEndPlugin
} from '@jupyterlab/application';
import { PageConfig } from '@jupyterlab/coreutils';
import { IClassicShell, ClassicShell } from './shell';
/**
* App is the main application class. It is instantiated once and shared.
*/
export class App extends JupyterFrontEnd<IClassicShell> {
/**
* Construct a new App object.
*
* @param options The instantiation options for an application.
*/
constructor(options: App.IOptions = { shell: new ClassicShell() }) {
super({
shell: options.shell
});
}
/**
* The name of the application.
*/
readonly name = 'JupyterLab Custom App';
/**
* A namespace/prefix plugins may use to denote their provenance.
*/
readonly namespace = this.name;
/**
* The version of the application.
*/
readonly version = 'unknown';
/**
* The JupyterLab application paths dictionary.
*/
get paths(): JupyterFrontEnd.IPaths {
return {
urls: {
base: PageConfig.getOption('baseUrl'),
notFound: PageConfig.getOption('notFoundUrl'),
app: PageConfig.getOption('appUrl'),
static: PageConfig.getOption('staticUrl'),
settings: PageConfig.getOption('settingsUrl'),
themes: PageConfig.getOption('themesUrl'),
doc: PageConfig.getOption('docUrl'),
translations: PageConfig.getOption('translationsApiUrl'),
hubHost: PageConfig.getOption('hubHost') || undefined,
hubPrefix: PageConfig.getOption('hubPrefix') || undefined,
hubUser: PageConfig.getOption('hubUser') || undefined,
hubServerName: PageConfig.getOption('hubServerName') || undefined
},
directories: {
appSettings: PageConfig.getOption('appSettingsDir'),
schemas: PageConfig.getOption('schemasDir'),
static: PageConfig.getOption('staticDir'),
templates: PageConfig.getOption('templatesDir'),
themes: PageConfig.getOption('themesDir'),
userSettings: PageConfig.getOption('userSettingsDir'),
serverRoot: PageConfig.getOption('serverRoot'),
workspaces: PageConfig.getOption('workspacesDir')
}
};
}
/**
* Register plugins from a plugin module.
*
* @param mod - The plugin module to register.
*/
registerPluginModule(mod: App.IPluginModule): void {
let data = mod.default;
// Handle commonjs exports.
if (!Object.prototype.hasOwnProperty.call(mod, '__esModule')) {
data = mod as any;
}
if (!Array.isArray(data)) {
data = [data];
}
data.forEach(item => {
try {
this.registerPlugin(item);
} catch (error) {
console.error(error);
}
});
}
/**
* Register the plugins from multiple plugin modules.
*
* @param mods - The plugin modules to register.
*/
registerPluginModules(mods: App.IPluginModule[]): void {
mods.forEach(mod => {
this.registerPluginModule(mod);
});
}
}
/**
* A namespace for App statics.
*/
export namespace App {
/**
* The instantiation options for an App application.
*/
export type IOptions = JupyterFrontEnd.IOptions<IClassicShell>;
/**
* The interface for a module that exports a plugin or plugins as
* the default value.
*/
export interface IPluginModule {
/**
* The default export.
*/
default: JupyterFrontEndPlugin<any> | JupyterFrontEndPlugin<any>[];
}
}

View File

@ -0,0 +1,5 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
export * from './app';
export * from './shell';

View File

@ -0,0 +1,237 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { JupyterFrontEnd } from '@jupyterlab/application';
import { DocumentRegistry } from '@jupyterlab/docregistry';
import { ArrayExt, IIterator, iter } from '@lumino/algorithm';
import { Token } from '@lumino/coreutils';
import { Message, MessageLoop, IMessageHandler } from '@lumino/messaging';
import { ISignal, Signal } from '@lumino/signaling';
import { Panel, Widget, BoxLayout } from '@lumino/widgets';
/**
* The JupyterLab Classic application shell token.
*/
export const IClassicShell = new Token<IClassicShell>(
'@jupyterlab-classic/application:IClassicShell'
);
/**
* The JupyterLab Classic application shell interface.
*/
export interface IClassicShell extends ClassicShell {}
/**
* The default rank for ranked panels.
*/
const DEFAULT_RANK = 900;
/**
* The application shell.
*/
export class ClassicShell extends Widget implements JupyterFrontEnd.IShell {
constructor() {
super();
this.id = 'main';
const rootLayout = new BoxLayout();
this._topHandler = new Private.PanelHandler();
this._menu = new Panel();
this._main = new Panel();
this._topHandler.panel.id = 'top-panel';
this._menu.id = 'menu-panel';
this._main.id = 'main-panel';
BoxLayout.setStretch(this._topHandler.panel, 0);
BoxLayout.setStretch(this._menu, 0);
BoxLayout.setStretch(this._main, 1);
const spacer = new Widget();
spacer.node.style.minHeight = '16px';
rootLayout.spacing = 0;
rootLayout.addWidget(this._topHandler.panel);
rootLayout.addWidget(this._menu);
rootLayout.addWidget(spacer);
rootLayout.addWidget(this._main);
this.layout = rootLayout;
}
activateById(id: string): void {
// no-op
}
/**
* Add a widget to the application shell.
*
* @param widget - The widget being added.
*
* @param area - Optional region in the shell into which the widget should
* be added.
*
* @param options - Optional open options.
*
*/
add(
widget: Widget,
area?: Shell.Area,
options?: DocumentRegistry.IOpenOptions
): void {
if (area === 'top') {
const rank = options?.rank ?? DEFAULT_RANK;
return this._topHandler.addWidget(widget, rank);
}
if (area === 'menu') {
return this._menu.addWidget(widget);
}
this._main.widgets.forEach(w => {
w.close();
});
this._main.addWidget(widget);
this._main.update();
this._currentChanged.emit(void 0);
}
/**
* A signal emitted when the current widget changes.
*/
get currentChanged(): ISignal<ClassicShell, void> {
return this._currentChanged;
}
/**
* The current widget in the shell's main area.
*/
get currentWidget(): Widget {
return this._main.widgets[0];
}
/**
* Return the list of widgets for the given area.
*
* @param area The area
*/
widgets(area: Shell.Area): IIterator<Widget> {
if (area === 'top') {
return iter(this._topHandler.panel.widgets);
}
if (area === 'menu') {
return iter(this._menu.widgets);
}
return iter(this._main.widgets);
}
private _topHandler: Private.PanelHandler;
private _menu: Panel;
private _main: Panel;
private _currentChanged = new Signal<this, void>(this);
}
/**
* A namespace for Shell statics
*/
export namespace Shell {
/**
* The areas of the application shell where widgets can reside.
*/
export type Area = 'main' | 'top' | 'menu';
}
/**
* A namespace for private module data.
*/
namespace Private {
/**
* An object which holds a widget and its sort rank.
*/
export interface IRankItem {
/**
* The widget for the item.
*/
widget: Widget;
/**
* The sort rank of the widget.
*/
rank: number;
}
/**
* A less-than comparison function for side bar rank items.
*/
export function itemCmp(first: IRankItem, second: IRankItem): number {
return first.rank - second.rank;
}
/**
* A class which manages a panel and sorts its widgets by rank.
*/
export class PanelHandler {
constructor() {
MessageLoop.installMessageHook(this._panel, this._panelChildHook);
}
/**
* Get the panel managed by the handler.
*/
get panel(): Panel {
return this._panel;
}
/**
* Add a widget to the panel.
*
* If the widget is already added, it will be moved.
*/
addWidget(widget: Widget, rank: number): void {
widget.parent = null;
const item = { widget, rank };
const index = ArrayExt.upperBound(this._items, item, Private.itemCmp);
ArrayExt.insert(this._items, index, item);
this._panel.insertWidget(index, widget);
}
/**
* A message hook for child add/remove messages on the main area dock panel.
*/
private _panelChildHook = (
handler: IMessageHandler,
msg: Message
): boolean => {
switch (msg.type) {
case 'child-added':
{
const widget = (msg as Widget.ChildMessage).child;
// If we already know about this widget, we're done
if (this._items.find(v => v.widget === widget)) {
break;
}
// Otherwise, add to the end by default
const rank = this._items[this._items.length - 1].rank;
this._items.push({ widget, rank });
}
break;
case 'child-removed':
{
const widget = (msg as Widget.ChildMessage).child;
ArrayExt.removeFirstWhere(this._items, v => v.widget === widget);
}
break;
default:
break;
}
return true;
};
private _items = new Array<Private.IRankItem>();
private _panel = new Panel();
}
}

View File

@ -0,0 +1,40 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
body {
margin: 0;
padding: 0;
background: var(--jp-layout-color2);
}
#main {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin-left: auto;
margin-right: auto;
max-width: 1200px;
}
#top-panel {
display: flex;
border-bottom: var(--jp-border-width) solid var(--jp-border-color0);
background: var(--jp-layout-color1);
min-height: calc(1.5 * var(--jp-private-menubar-height));
}
#menu-panel {
display: flex;
min-height: var(--jp-private-menu-panel-height);
background: var(--jp-layout-color1);
border-bottom: var(--jp-border-width) solid var(--jp-border-color0);
box-shadow: var(--jp-elevation-z1);
}
#main-panel {
box-shadow: var(--jp-elevation-z4);
}

View File

@ -0,0 +1,10 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
@import url('~@jupyterlab/application/style/index.css');
@import url('~@jupyterlab/mainmenu/style/index.css');
@import url('~@jupyterlab/ui-components/style/index.css');
@import url('./base.css');

View File

@ -0,0 +1,13 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
import { Shell } from '@jupyterlab-classic/application';
describe('Shell', () => {
describe('#constructor()', () => {
it('should create a LabShell instance', () => {
const shell = new Shell();
expect(shell).toBeInstanceOf(Shell);
});
});
});

View File

@ -0,0 +1,8 @@
{
"extends": "../../tsconfigbase",
"compilerOptions": {
"outDir": "lib",
"rootDir": "src"
},
"include": ["src/**/*"]
}

View File

@ -0,0 +1,9 @@
{
"extends": "../../tsconfigbase.test",
"include": ["src/**/*", "test/**/*"],
"references": [
{
"path": "."
}
]
}

View File

@ -0,0 +1 @@
module.exports = require('@jupyterlab/testutils/lib/babel.config');

View File

@ -0,0 +1,2 @@
const func = require('@jupyterlab/testutils/lib/jest-config');
module.exports = func(__dirname);

View File

@ -0,0 +1,60 @@
{
"name": "@jupyterlab-classic/ui-components",
"version": "0.1.0",
"description": "JupyterLab Classic - UI components",
"homepage": "https://github.com/jtpio/jupyterlab-classic",
"bugs": {
"url": "https://github.com/jtpio/jupyterlab-classic/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/jtpio/jupyterlab-classic.git"
},
"license": "BSD-3-Clause",
"author": "Project Jupyter",
"sideEffects": [
"style/**/*"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
"style": "style/index.css",
"directories": {
"lib": "lib/"
},
"files": [
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
"style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
],
"scripts": {
"build": "tsc -b",
"build:test": "tsc --build tsconfig.test.json",
"clean": "rimraf lib && rimraf tsconfig.tsbuildinfo",
"cleansvg": "svgo --config svgo.yaml",
"docs": "typedoc src",
"docs:init": "bash docs/build.sh",
"prepublishOnly": "npm run build",
"storybook": "start-storybook -p 9001 -c .storybook",
"test": "jest",
"test:cov": "jest --collect-coverage",
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
"test:debug:watch": "node --inspect-brk node_modules/.bin/jest --runInBand --watch",
"watch": "tsc -b --watch"
},
"dependencies": {
"@jupyterlab/ui-components": "^3.0.0-rc.10"
},
"devDependencies": {
"@babel/core": "^7.10.2",
"@babel/preset-env": "^7.10.2",
"@jupyterlab/testutils": "^3.0.0-rc.10",
"@types/jest": "^26.0.10",
"babel-loader": "^8.0.6",
"jest": "^26.4.2",
"rimraf": "~3.0.0",
"ts-jest": "^26.3.0",
"typescript": "~4.0.2"
},
"publishConfig": {
"access": "public"
}
}

View File

@ -0,0 +1,13 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
import { LabIcon } from '@jupyterlab/ui-components';
import jupyterSvgstr from '../../style/icons/jupyter.svg';
export const jupyterIcon = new LabIcon({
name: 'classic-ui-components:jupyter',
svgstr: jupyterSvgstr
});

View File

@ -0,0 +1,4 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
export * from './iconimports';

View File

@ -0,0 +1,4 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
export * from './icon';

7
packages/ui-components/src/svg.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
declare module '*.svg' {
const value: string;
export default value;
}

View File

View File

@ -0,0 +1,88 @@
<svg width="189" height="51" viewBox="0 0 189 51" version="2.0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:figma="http://www.figma.com/figma/ns">
<title>logo.svg</title>
<desc>Created using Figma 0.90</desc>
<g id="Canvas" transform="translate(-1638 -2093)" figma:type="canvas">
<g id="logo" style="mix-blend-mode:normal;" figma:type="group">
<g id="Group" style="mix-blend-mode:normal;" figma:type="group">
<g id="g" style="mix-blend-mode:normal;" figma:type="group">
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path0 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path0_fill" transform="translate(1688.87 2106.23)" fill="#4E4E4E" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path1 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path1_fill" transform="translate(1705.38 2106.19)" fill="#4E4E4E" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path2 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path2_fill" transform="translate(1730.18 2105.67)" fill="#4E4E4E" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path3 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path3_fill" transform="translate(1752.94 2106.21)" fill="#4E4E4E" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path4 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path4_fill" transform="translate(1775.8 2100.04)" fill="#4E4E4E" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path5 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path5_fill" transform="translate(1791.75 2105.71)" fill="#4E4E4E" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path6 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path6_fill" transform="translate(1815.77 2105.72)" fill="#4E4E4E" style="mix-blend-mode:normal;"/>
</g>
</g>
</g>
</g>
<g id="g" style="mix-blend-mode:normal;" figma:type="group">
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path7 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path7_fill" transform="translate(1669.3 2093.31)" fill="#767677" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path8 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path8_fill" transform="translate(1639.74 2123.98)" fill="#F37726" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path9 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path9_fill" transform="translate(1639.73 2097.48)" fill="#F37726" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path10 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path10_fill" transform="translate(1639.8 2135.81)" fill="#989798" style="mix-blend-mode:normal;"/>
</g>
</g>
<g id="path" style="mix-blend-mode:normal;" figma:type="group">
<g id="path11 fill" style="mix-blend-mode:normal;" figma:type="vector">
<use xlink:href="#path11_fill" transform="translate(1638.36 2098.06)" fill="#6F7070" style="mix-blend-mode:normal;"/>
</g>
</g>
</g>
</g>
</g>
<defs>
<path id="path0_fill" d="M 5.62592 17.9276C 5.62592 23.0737 5.23342 24.7539 4.18673 25.9942C 3.04416 27.0501 1.54971 27.6341 0 27.6304L 0.392506 30.6916C 2.79452 30.7249 5.12302 29.8564 6.92556 28.255C 8.80086 26.3021 9.45504 23.6015 9.45504 19.4583L 9.45504 0L 5.6172 0L 5.6172 17.954L 5.62592 17.9276Z"/>
<path id="path1_fill" d="M 17.7413 15.6229C 17.7413 17.8397 17.7413 19.7925 17.9157 21.4727L 14.5576 21.4727L 14.3396 17.954L 14.2523 17.954C 13.5454 19.1838 12.5262 20.2013 11.2997 20.9017C 10.0732 21.6022 8.68377 21.9602 7.27445 21.9389C 3.95995 21.9389 0 20.074 0 12.5441L 0 0L 3.83784 0L 3.83784 11.9019C 3.83784 15.9836 5.05897 18.7281 8.53919 18.7281C 9.63092 18.708 10.6925 18.3634 11.5908 17.7374C 12.4892 17.1115 13.1844 16.2321 13.5894 15.2095C 13.8222 14.57 13.9404 13.8938 13.9383 13.2126L 13.9383 0.0175934L 17.7762 0.0175934L 17.7762 15.6229L 17.7413 15.6229Z"/>
<path id="path2_fill" d="M 0.174447 7.53632C 0.174447 4.79175 0.0872236 2.57499 0 0.498968L 3.44533 0.498968L 3.61978 4.17598L 3.707 4.17598C 4.46074 2.85853 5.55705 1.77379 6.87754 1.03893C 8.19802 0.304077 9.69248 -0.0529711 11.1995 0.0063534C 16.2934 0.0063534 20.1312 4.4047 20.1312 10.9142C 20.1312 18.6289 15.5171 22.4379 10.5366 22.4379C 9.25812 22.492 7.98766 22.2098 6.84994 21.6192C 5.71222 21.0285 4.74636 20.1496 4.04718 19.0688L 3.95995 19.0688L 3.95995 30.7244L 0.165725 30.7244L 0.165725 7.50113L 0.174447 7.53632ZM 3.96868 13.2542C 3.97869 13.7891 4.03708 14.3221 4.14312 14.8464C 4.45574 16.1467 5.19222 17.3035 6.23449 18.1313C 7.27677 18.9592 8.56446 19.4101 9.89116 19.4118C 13.9471 19.4118 16.2934 16.0427 16.2934 11.1254C 16.2934 6.82378 14.0692 3.14677 10.022 3.14677C 8.66089 3.18518 7.35158 3.68134 6.30219 4.55638C 5.25279 5.43143 4.52354 6.63513 4.23035 7.97615C 4.07662 8.49357 3.98869 9.02858 3.96868 9.56835L 3.96868 13.2454L 3.96868 13.2542Z"/>
<path id="path3_fill" d="M 4.16057 0L 8.7747 12.676C 9.25443 14.0923 9.77777 15.7813 10.1267 17.0744L 10.2139 17.0744C 10.6064 15.7901 11.0425 14.1451 11.5659 12.5969L 15.7526 0.00879669L 19.8085 0.00879669L 14.0604 15.3062C 11.3129 22.6603 9.44632 26.434 6.82961 28.7388C 5.50738 29.9791 3.88672 30.8494 2.12826 31.2634L 1.1688 27.9823C 2.39912 27.5689 3.53918 26.9208 4.52691 26.0734C 5.92259 24.8972 7.02752 23.4094 7.75418 21.7278C 7.90932 21.4374 8.01266 21.1218 8.05946 20.7954C 8.02501 20.4436 7.93674 20.0994 7.79779 19.7749L 0 0.00879669L 4.18673 0.00879669L 4.16057 0Z"/>
<path id="path4_fill" d="M 7.0215 0L 7.0215 6.15768L 12.5079 6.15768L 12.5079 9.13096L 7.0215 9.13096L 7.0215 20.6898C 7.0215 23.3288 7.7629 24.8594 9.89988 24.8594C 10.6496 24.8712 11.3975 24.7824 12.1241 24.5955L 12.2985 27.5248C 11.207 27.9125 10.0534 28.0915 8.89681 28.0526C 8.1298 28.1 7.36177 27.9782 6.64622 27.6956C 5.93068 27.413 5.28484 26.9765 4.75369 26.4164C 3.66339 25.2641 3.27089 23.3552 3.27089 20.8306L 3.27089 9.13096L 0 9.13096L 0 6.15768L 3.27089 6.15768L 3.27089 1.01162L 7.0215 0Z"/>
<path id="path5_fill" d="M 3.6285 11.9283C 3.71573 17.2063 7.03022 19.3791 10.8593 19.3791C 12.8612 19.4425 14.8527 19.0642 16.6946 18.2707L 17.3488 21.0593C 15.1419 21.994 12.7638 22.4467 10.3709 22.3876C 3.88145 22.3876 0 18.042 0 11.5676C 0 5.09328 3.75062 0 9.89116 0C 16.7731 0 18.6135 6.15768 18.6135 10.1074C 18.607 10.7165 18.5634 11.3246 18.4827 11.9283L 3.65467 11.9283L 3.6285 11.9283ZM 14.8716 9.13976C 14.9152 6.65909 13.8686 2.79735 9.55971 2.79735C 5.67826 2.79735 3.98612 6.43038 3.68084 9.13976L 14.8803 9.13976L 14.8716 9.13976Z"/>
<path id="path6_fill" d="M 0.174447 7.17854C 0.174447 4.65389 0.130835 2.48111 0 0.484261L 3.35811 0.484261L 3.48894 4.69787L 3.66339 4.69787C 4.62285 1.81256 6.93428 0.00044283 9.49865 0.00044283C 9.8663 -0.0049786 10.233 0.0394012 10.5889 0.132393L 10.5889 3.80941C 10.1593 3.71494 9.72029 3.67067 9.28059 3.67746C 6.57666 3.67746 4.66646 5.76227 4.14312 8.68277C 4.03516 9.28384 3.97681 9.89289 3.96867 10.5037L 3.96867 21.9394L 0.174447 21.9394L 0.174447 7.17854Z"/>
<path id="path7_fill" d="M 5.89353 2.844C 5.91889 3.43165 5.77085 4.01367 5.46815 4.51645C 5.16545 5.01922 4.72168 5.42015 4.19299 5.66851C 3.6643 5.91688 3.07444 6.00151 2.49805 5.91171C 1.92166 5.8219 1.38463 5.5617 0.954898 5.16401C 0.52517 4.76633 0.222056 4.24903 0.0839037 3.67757C -0.0542483 3.10611 -0.02123 2.50617 0.178781 1.95364C 0.378793 1.4011 0.736809 0.920817 1.20754 0.573538C 1.67826 0.226259 2.24055 0.0275919 2.82326 0.00267229C 3.60389 -0.0307115 4.36573 0.249789 4.94142 0.782551C 5.51711 1.31531 5.85956 2.05676 5.89353 2.844Z"/>
<path id="path8_fill" d="M 18.2646 7.13411C 10.4145 7.13411 3.55872 4.2576 0 0C 1.32539 3.8204 3.79556 7.13081 7.0686 9.47303C 10.3417 11.8152 14.2557 13.0734 18.269 13.0734C 22.2823 13.0734 26.1963 11.8152 29.4694 9.47303C 32.7424 7.13081 35.2126 3.8204 36.538 0C 32.9705 4.2576 26.1148 7.13411 18.2646 7.13411Z"/>
<path id="path9_fill" d="M 18.2733 5.93931C 26.1235 5.93931 32.9793 8.81583 36.538 13.0734C 35.2126 9.25303 32.7424 5.94262 29.4694 3.6004C 26.1963 1.25818 22.2823 0 18.269 0C 14.2557 0 10.3417 1.25818 7.0686 3.6004C 3.79556 5.94262 1.32539 9.25303 0 13.0734C 3.56745 8.82463 10.4232 5.93931 18.2733 5.93931Z"/>
<path id="path10_fill" d="M 7.42789 3.58338C 7.46008 4.3243 7.27355 5.05819 6.89193 5.69213C 6.51031 6.32607 5.95075 6.83156 5.28411 7.1446C 4.61747 7.45763 3.87371 7.56414 3.14702 7.45063C 2.42032 7.33712 1.74336 7.0087 1.20184 6.50695C 0.660328 6.0052 0.27861 5.35268 0.105017 4.63202C -0.0685757 3.91135 -0.0262361 3.15494 0.226675 2.45856C 0.479587 1.76217 0.931697 1.15713 1.52576 0.720033C 2.11983 0.282935 2.82914 0.0334395 3.56389 0.00313344C 4.54667 -0.0374033 5.50529 0.316706 6.22961 0.987835C 6.95393 1.65896 7.38484 2.59235 7.42789 3.58338L 7.42789 3.58338Z"/>
<path id="path11_fill" d="M 2.27471 4.39629C 1.84363 4.41508 1.41671 4.30445 1.04799 4.07843C 0.679268 3.8524 0.385328 3.52114 0.203371 3.12656C 0.0214136 2.73198 -0.0403798 2.29183 0.0258116 1.86181C 0.0920031 1.4318 0.283204 1.03126 0.575213 0.710883C 0.867222 0.39051 1.24691 0.164708 1.66622 0.0620592C 2.08553 -0.0405897 2.52561 -0.0154714 2.93076 0.134235C 3.33591 0.283941 3.68792 0.551505 3.94222 0.90306C 4.19652 1.25462 4.34169 1.67436 4.35935 2.10916C 4.38299 2.69107 4.17678 3.25869 3.78597 3.68746C 3.39516 4.11624 2.85166 4.37116 2.27471 4.39629L 2.27471 4.39629Z"/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -0,0 +1,6 @@
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/
@import url('./base.css');

View File

@ -0,0 +1,10 @@
// Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License.
describe('foo', () => {
describe('bar', () => {
it('should pass', () => {
expect(true).toBe(true);
});
});
});

View File

@ -0,0 +1,8 @@
{
"extends": "../../tsconfigbase",
"compilerOptions": {
"outDir": "lib",
"rootDir": "src"
},
"include": ["src/**/*"]
}

View File

@ -0,0 +1,9 @@
{
"extends": "../../tsconfigbase.test",
"include": ["src/**/*", "test/**/*"],
"references": [
{
"path": "."
}
]
}

3
pyproject.toml Normal file
View File

@ -0,0 +1,3 @@
[build-system]
requires = ["jupyter_packaging~=0.7.0", "jupyterlab>=3.0.0rc10,==3.*", "setuptools>=40.8.0", "wheel"]
build-backend = "setuptools.build_meta"

59
setup.py Normal file
View File

@ -0,0 +1,59 @@
"""
jupyterlab-classic setup
"""
import os
from jupyter_packaging import get_version
import setuptools
HERE = os.path.abspath(os.path.dirname(__file__))
# The name of the project
NAME = "jupyterlab-classic"
PACKAGE_NAME = NAME.replace("-", "_")
# Get our version
version = get_version(os.path.join(PACKAGE_NAME, "_version.py"))
with open("README.md", "r") as fh:
long_description = fh.read()
setup_args = dict(
name=NAME,
version=version,
url="https://github.com/jtpio/jupyterlab-classic",
author="Jeremy Tuloup",
description="The next gen old-school Notebook UI",
long_description=long_description,
long_description_content_type="text/markdown",
data_files=[
(
"etc/jupyter/jupyter_server_config.d",
["jupyter-config/jupyter_server_config.d/jupyterlab_classic.json"],
),
],
packages=setuptools.find_packages(),
install_requires=[
"jupyterlab>=3.0.0rc10,==3.*",
],
zip_safe=False,
include_package_data=True,
python_requires=">=3.6",
license="BSD-3-Clause",
platforms="Linux, Mac OS X, Windows",
keywords=["Jupyter", "JupyterLab", "Notebook"],
classifiers=[
"License :: OSI Approved :: BSD License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Framework :: Jupyter",
],
)
if __name__ == "__main__":
setuptools.setup(**setup_args)

5
tsconfig.eslint.json Normal file
View File

@ -0,0 +1,5 @@
{
"extends": "./tsconfigbase",
"include": ["packages/**/*", "builder/**/*"],
"types": ["jest"]
}

22
tsconfig.test.json Normal file
View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"declaration": true,
"noEmitOnError": true,
"noUnusedLocals": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "es2015",
"lib": [
"es2015",
"es2015.collection",
"dom",
"es2015.iterable",
"es2017.object"
],
"types": ["jest"],
"jsx": "react",
"resolveJsonModule": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}

22
tsconfigbase.json Normal file
View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"composite": true,
"declaration": true,
"esModuleInterop": true,
"incremental": true,
"jsx": "react",
"module": "esnext",
"moduleResolution": "node",
"noEmitOnError": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"preserveWatchOutput": true,
"resolveJsonModule": true,
"strict": true,
"skipLibCheck": true,
"strictNullChecks": true,
"target": "es2017",
"types": []
}
}

25
tsconfigbase.test.json Normal file
View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"declaration": true,
"noImplicitAny": true,
"noEmitOnError": true,
"noUnusedLocals": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "es2015",
"outDir": "lib",
"lib": [
"es2015",
"es2015.collection",
"dom",
"es2015.iterable",
"es2017.object"
],
"types": ["jest", "node"],
"jsx": "react",
"resolveJsonModule": true,
"esModuleInterop": true,
"strictNullChecks": true,
"skipLibCheck": true
}
}

13454
yarn.lock Normal file

File diff suppressed because it is too large Load Diff