Browse Source

Add a filebrowser example

Steven Silvester 9 years ago
parent
commit
c93bbd007c

+ 3 - 6
.gitignore

@@ -2,11 +2,8 @@
 *.user
 .DS_Store
 node_modules
-npm-debug.log
-test/build
-test/coverage
-lib/
-example/build
+coverage
+lib
+build
 docs
-
 .ipynb_checkpoints

+ 241 - 0
examples/filebrowser/index.css

@@ -0,0 +1,241 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) Jupyter Development Team.
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+.p-SplitPanel {
+  height: 400px;
+}
+
+
+.p-DockTabPanel {
+  padding-right: 2px;
+  padding-bottom: 2px;
+}
+
+
+.p-DockTabPanel > .p-StackedPanel {
+  padding: 10px;
+  background: white;
+  border: 1px solid #C0C0C0;
+  border-top: none;
+  box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
+}
+
+
+.p-DockPanel-overlay {
+  background: rgba(255, 255, 255, 0.6);
+  border: 1px dashed black;
+}
+
+
+.p-DockPanel-overlay.p-mod-root-top,
+.p-DockPanel-overlay.p-mod-root-left,
+.p-DockPanel-overlay.p-mod-root-right,
+.p-DockPanel-overlay.p-mod-root-bottom,
+.p-DockPanel-overlay.p-mod-root-center {
+  border-width: 2px;
+}
+
+
+.p-TabBar {
+  min-height: 24px;
+  max-height: 24px;
+}
+
+
+.p-TabBar-header {
+  display: none;
+}
+
+
+.p-TabBar-footer {
+  flex: 0 0 1px;
+  background: #C0C0C0;
+}
+
+
+.p-TabBar-content {
+  min-width: 0;
+  align-items: flex-end;
+}
+
+
+.p-TabBar-tab {
+  flex: 0 1 125px;
+  min-height: 20px;
+  max-height: 20px;
+  min-width: 35px;
+  margin-left: -1px;
+  border: 1px solid #C0C0C0;
+  border-bottom: none;
+  padding: 0px 10px;
+  background: #E5E5E5;
+  font: 12px Helvetica, Arial, sans-serif;
+}
+
+
+.p-TabBar-tab:first-child {
+  margin-left: 0;
+}
+
+
+.p-TabBar-tab.p-mod-current {
+  min-height: 23px;
+  max-height: 23px;
+  background: white;
+  transform: translateY(1px);
+}
+
+
+.p-TabBar-tab:hover:not(.p-mod-current) {
+  background: #F0F0F0;
+}
+
+
+.p-TabBar-tabIcon,
+.p-TabBar-tabText,
+.p-TabBar-tabCloseIcon {
+  line-height: 20px;
+}
+
+
+.p-TabBar-tab.p-mod-closable > .p-TabBar-tabCloseIcon {
+  margin-left: 4px;
+}
+
+
+.p-TabBar-tab.p-mod-closable > .p-TabBar-tabCloseIcon:before {
+  content: '\f00d';
+  font-family: FontAwesome;
+}
+
+
+.p-TabBar-tab.p-mod-drag-image {
+  min-height: 23px;
+  max-height: 23px;
+  min-width: 125px;
+  border: none;
+  box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
+  transform: translateX(-40%) translateY(-58%);
+}
+
+.p-MenuBar {
+  padding-left: 5px;
+  background: #FAFAFA;
+  color: rgba(0, 0, 0, 0.87);
+  border-bottom: 1px solid #DDDDDD;
+  font: 13px Helvetica, Arial, sans-serif;
+}
+
+
+.p-MenuBar-menu {
+  transform: translateY(-1px);
+}
+
+
+.p-MenuBar-item {
+  padding: 4px 8px;
+  border-left: 1px solid transparent;
+  border-right: 1px solid transparent;
+}
+
+
+.p-MenuBar-item.p-mod-active {
+  background: #E5E5E5;
+}
+
+
+.p-MenuBar-item.p-mod-disabled {
+  color: rgba(0, 0, 0, 0.26);
+}
+
+
+.p-MenuBar-item.p-type-separator {
+  margin: 2px;
+  padding: 0;
+  border: none;
+  border-left: 1px solid #DDDDDD;
+}
+
+
+.p-MenuBar.p-mod-active .p-MenuBar-item.p-mod-active {
+  z-index: 10001;
+  background: white;
+  border-left: 1px solid #C0C0C0;
+  border-right: 1px solid #C0C0C0;
+  box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.2);
+}
+
+
+.p-Menu {
+  z-index: 10000;
+  padding: 3px 0px;
+  background: white;
+  color: rgba(0, 0, 0, 0.87);
+  border: 1px solid #C0C0C0;
+  font: 12px Helvetica, Arial, sans-serif;
+  box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.2);
+}
+
+
+.p-Menu-item.p-mod-active {
+  background: #E5E5E5;
+}
+
+
+.p-Menu-item.p-mod-disabled {
+  color: rgba(0, 0, 0, 0.26);
+}
+
+
+.p-Menu-itemIcon {
+  width: 21px;
+  padding: 4px 2px;
+}
+
+
+.p-Menu-itemText {
+  padding: 4px 35px 4px 2px;
+}
+
+
+.p-Menu-itemShortcut {
+  padding: 4px 0px;
+}
+
+
+.p-Menu-itemSubmenuIcon {
+  width: 16px;
+  padding: 4px 0px;
+}
+
+
+.p-Menu-item.p-type-separator > span {
+  padding: 0;
+  height: 9px;
+}
+
+
+.p-Menu-item.p-type-separator > span::after {
+  content: '';
+  display: block;
+  position: relative;
+  top: 4px;
+  border-top: 1px solid #DDDDDD;
+}
+
+
+.p-Menu-itemIcon::before,
+.p-Menu-itemSubmenuIcon::before {
+  font-family: FontAwesome;
+}
+
+
+.p-Menu-item.p-type-check.p-mod-checked > .p-Menu-itemIcon::before {
+  content: '\f00c';
+}
+
+
+.p-Menu-item.p-type-submenu > .p-Menu-itemSubmenuIcon::before {
+  content: '\f0da';
+}

+ 12 - 0
examples/filebrowser/index.html

@@ -0,0 +1,12 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <title>FileBrowser Demo</title>
+    <link href="http://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
+    <link href="index.css" rel="stylesheet">
+  </head>
+  <body>
+    <script id='jupyter-config-data' type="application/json">{ "baseUrl": "{{base_url}}" }</script>
+    <script src="build/bundle.js"></script>
+  </body>
+</html>

+ 82 - 0
examples/filebrowser/main.py

@@ -0,0 +1,82 @@
+"""
+Copyright (c) Jupyter Development Team.
+Distributed under the terms of the Modified BSD License.
+"""
+import re
+import subprocess
+import sys
+import threading
+
+import tornado.web
+
+PORT = 8765
+
+
+class MainPageHandler(tornado.web.RequestHandler):
+
+    def initialize(self, base_url):
+        self.base_url = base_url
+
+    def get(self):
+        return self.render("index.html", static=self.static_url,
+                           base_url=self.base_url)
+
+
+def main(argv):
+
+    url = "http://localhost:%s" % PORT
+
+    nb_command = [sys.executable, '-m', 'notebook', '--no-browser', '--debug',
+                  '--NotebookApp.allow_origin="%s"' % url]
+    nb_server = subprocess.Popen(nb_command, stderr=subprocess.STDOUT,
+                                 stdout=subprocess.PIPE)
+
+    # wait for notebook server to start up
+    while 1:
+        line = nb_server.stdout.readline().decode('utf-8').strip()
+        if not line:
+            continue
+        print(line)
+        if 'Jupyter Notebook is running at:' in line:
+            base_url = re.search('(http.*?)$', line).groups()[0]
+            break
+
+    while 1:
+        line = nb_server.stdout.readline().decode('utf-8').strip()
+        if not line:
+            continue
+        print(line)
+        if 'Control-C' in line:
+            break
+
+    def print_thread():
+        while 1:
+            line = nb_server.stdout.readline().decode('utf-8').strip()
+            if not line:
+                continue
+            print(line)
+    thread = threading.Thread(target=print_thread)
+    thread.setDaemon(True)
+    thread.start()
+
+    handlers = [
+        (r"/", MainPageHandler, {'base_url': base_url}),
+        (r'/(.*)', tornado.web.StaticFileHandler, {'path': '.'}),
+    ]
+
+    app = tornado.web.Application(handlers, static_path='build',
+                                  template_path='.')
+
+    app.listen(PORT, 'localhost')
+    loop = tornado.ioloop.IOLoop.instance()
+    print('Browse to http://localhost:%s' % PORT)
+    try:
+        loop.start()
+    except KeyboardInterrupt:
+        print(" Shutting down on SIGINT")
+    finally:
+        nb_server.kill()
+        loop.close()
+
+if __name__ == '__main__':
+    main(sys.argv)

+ 25 - 0
examples/filebrowser/package.json

@@ -0,0 +1,25 @@
+{
+  "private": true,
+  "name": "filebrowser-example",
+  "dependencies": {
+    "jupyter-js-ui": "file:../..",
+    "jupyter-js-services": "^0.5.1",
+    "jupyter-js-utils": "^0.3.1",
+    "phosphor-arrays": "^1.0.6",
+    "phosphor-codemirror": "0.0.1",
+    "phosphor-dockpanel": "^0.9.6",
+    "phosphor-keymap": "^0.7.1",
+    "phosphor-menus": "^1.0.0-rc.1",
+    "phosphor-splitpanel": "^1.0.0-rc.1",
+    "phosphor-widget": "^1.0.0-rc.1"
+  },
+  "scripts": {
+    "build": "rm -rf node_modules/jupyter-js-ui && npm install && tsc --project src && webpack --config webpack.conf.js",
+    "postinstall": "npm dedupe"
+  },
+  "devDependencies": {
+    "css-loader": "^0.23.1",
+    "style-loader": "^0.13.0",
+    "webpack": "^1.12.14"
+  }
+}

+ 203 - 0
examples/filebrowser/src/index.ts

@@ -0,0 +1,203 @@
+/*-----------------------------------------------------------------------------
+| Copyright (c) 2014-2015, Jupyter Development Team.
+|
+| Distributed under the terms of the Modified BSD License.
+|----------------------------------------------------------------------------*/
+'use strict';
+
+import {
+  ContentsManager, ISessionOptions, NotebookSessionManager
+} from 'jupyter-js-services';
+
+import {
+  FileHandler
+} from 'jupyter-js-ui/lib/docmanager';
+
+import {
+  FileBrowserWidget, FileBrowserModel
+} from 'jupyter-js-ui/lib/filebrowser';
+
+import {
+  getConfigOption
+} from 'jupyter-js-utils';
+
+import * as arrays
+ from 'phosphor-arrays';
+
+import {
+  CodeMirrorWidget
+} from 'phosphor-codemirror';
+
+import {
+  DockPanel
+} from 'phosphor-dockpanel';
+
+import {
+  KeymapManager
+} from 'phosphor-keymap';
+
+import {
+  Menu, MenuBar, MenuItem
+} from 'phosphor-menus';
+
+import {
+  SplitPanel
+} from 'phosphor-splitpanel';
+
+import {
+  Widget
+} from 'phosphor-widget';
+
+
+import 'jupyter-js-ui/lib/index.css';
+import 'jupyter-js-ui/lib/theme.css';
+
+
+function main(): void {
+
+  let baseUrl = getConfigOption('baseUrl');
+  let contentsManager = new ContentsManager(baseUrl);
+  let sessionsManager = new NotebookSessionManager({ baseUrl: baseUrl });
+
+  let fbModel = new FileBrowserModel(contentsManager, sessionsManager);
+  let fbWidget = new FileBrowserWidget(fbModel)
+  fbWidget.widgetFactory = model => {
+    return handler.open(model);
+  };
+  let handler = new FileHandler(contentsManager);
+
+  let panel = new SplitPanel();
+  panel.addChild(fbWidget);
+  let dock = new DockPanel();
+  panel.addChild(dock);
+  dock.spacing = 8;
+
+  fbModel.openRequested.connect((fbModel, model) => {
+    let editor = handler.open(model);
+    dock.insertTabAfter(editor);
+  });
+
+  let keymapManager = new KeymapManager();
+  keymapManager.add([{
+    sequence: ['Enter'],
+    selector: '.jp-DirListing',
+    handler: () => {
+      fbWidget.open();
+      return true;
+    }
+  }, {
+    sequence: ['Ctrl N'], // Add emacs keybinding for select next.
+    selector: '.jp-DirListing',
+    handler: () => {
+      fbWidget.selectNext();
+      return true;
+    }
+  }, {
+    sequence: ['Ctrl P'], // Add emacs keybinding for select previous.
+    selector: '.jp-DirListing',
+    handler: () => {
+      fbWidget.selectPrevious();
+      return true;
+    }
+  }, {
+    sequence: ['Accel S'],
+    selector: '.jp-CodeMirrorWidget',
+    handler: () => {
+      handler.save();
+      return true;
+    }
+  }, {
+    sequence: ['Accel R'],
+    selector: '.jp-CodeMirrorWidget',
+    handler: () => {
+      handler.revert();
+      return true;
+    }
+  }, {
+    sequence: ['Ctrl W'],
+    selector: '.jp-CodeMirrorWidget',
+    handler: () => {
+      handler.close();
+      return true;
+    }
+  }]);
+
+  window.addEventListener('keydown', (event) => {
+    keymapManager.processKeydownEvent(event);
+  });
+
+  let contextMenu = new Menu([
+    new MenuItem({
+      text: '&Open',
+      icon: 'fa fa-folder-open-o',
+      shortcut: 'Ctrl+O',
+      handler: () => { fbWidget.open(); }
+    }),
+    new MenuItem({
+      text: '&Rename',
+      icon: 'fa fa-edit',
+      shortcut: 'Ctrl+R',
+      handler: () => { fbWidget.rename(); }
+    }),
+    new MenuItem({
+      text: '&Delete',
+      icon: 'fa fa-remove',
+      shortcut: 'Ctrl+D',
+      handler: () => { fbWidget.delete(); }
+    }),
+    new MenuItem({
+      text: 'Duplicate',
+      icon: 'fa fa-copy',
+      handler: () => { fbWidget.duplicate(); }
+    }),
+    new MenuItem({
+      text: 'Cut',
+      icon: 'fa fa-cut',
+      shortcut: 'Ctrl+X',
+      handler: () => { fbWidget.cut(); }
+    }),
+    new MenuItem({
+      text: '&Copy',
+      icon: 'fa fa-copy',
+      shortcut: 'Ctrl+C',
+      handler: () => { fbWidget.copy(); }
+    }),
+    new MenuItem({
+      text: '&Paste',
+      icon: 'fa fa-paste',
+      shortcut: 'Ctrl+V',
+      handler: () => { fbWidget.paste(); }
+    }),
+    new MenuItem({
+      text: 'Download',
+      icon: 'fa fa-download',
+      handler: () => { fbWidget.download(); }
+    }),
+    new MenuItem({
+      text: 'Shutdown Kernel',
+      icon: 'fa fa-stop-circle-o',
+      handler: () => { fbWidget.shutdownKernels(); }
+    }),
+  ])
+
+  // Start a default session.
+  contentsManager.newUntitled('', { type: 'notebook' }).then(contents => {
+    sessionsManager.startNew({ notebookPath: contents.path }).then(() => {
+      panel.attach(document.body);
+    });
+  });
+
+  // Add a context menu to the dir listing.
+  let node = fbWidget.node.getElementsByClassName('jp-DirListing-content')[0];
+  node.addEventListener('contextmenu', (event: MouseEvent) => {
+    event.preventDefault();
+    let x = event.clientX;
+    let y = event.clientY;
+    contextMenu.popup(x, y);
+  });
+
+  window.onresize = () => panel.update();
+}
+
+
+window.onload = main;

+ 15 - 0
examples/filebrowser/src/tsconfig.json

@@ -0,0 +1,15 @@
+{
+  "compilerOptions": {
+    "noImplicitAny": true,
+    "noEmitOnError": true,
+    "module": "commonjs",
+    "moduleResolution": "node",
+    "target": "ES5",
+    "outDir": "../build"
+  },
+  "files": [
+    "../../../src/typings/codemirror/codemirror.d.ts",
+    "../../../src/typings/es6-promise.d.ts",
+    "index.ts"
+  ]
+}

+ 26 - 0
examples/filebrowser/webpack.conf.js

@@ -0,0 +1,26 @@
+
+var ContextReplacementPlugin = require("webpack/lib/ContextReplacementPlugin");
+
+module.exports = {
+  entry: './build/index.js',
+  output: {
+    path: './build',
+    filename: 'bundle.js'
+  },
+  node: {
+    fs: "empty"
+  },
+  bail: true,
+  debug: true,
+  module: {
+    loaders: [
+      { test: /\.css$/, loader: 'style-loader!css-loader' },
+    ]
+  },
+  plugins: [
+    new ContextReplacementPlugin(
+      /codemirror\/mode.*$/,
+      /codemirror\/mode.*\.js$/
+    )
+  ]
+}