|
@@ -1,14 +1,13 @@
|
|
|
// Copyright (c) Jupyter Development Team.
|
|
|
// Distributed under the terms of the Modified BSD License.
|
|
|
|
|
|
-import {
|
|
|
- Kernel
|
|
|
-} from '@jupyterlab/services';
|
|
|
-
|
|
|
import {
|
|
|
Message
|
|
|
} from 'phosphor/lib/core/messaging';
|
|
|
|
|
|
+import {
|
|
|
+ disconnectReceiver
|
|
|
+} from 'phosphor/lib/core/signaling';
|
|
|
|
|
|
import {
|
|
|
PanelLayout
|
|
@@ -23,10 +22,12 @@ import {
|
|
|
} from '../docregistry';
|
|
|
|
|
|
import {
|
|
|
- HTML_COMMON_CLASS
|
|
|
-} from '../renderers/widget';
|
|
|
+ CSVModel, CSVTable
|
|
|
+} from './table';
|
|
|
|
|
|
-import * as d3Dsv from 'd3-dsv';
|
|
|
+import {
|
|
|
+ CSVToolbar
|
|
|
+} from './toolbar';
|
|
|
|
|
|
|
|
|
/**
|
|
@@ -34,81 +35,56 @@ import * as d3Dsv from 'd3-dsv';
|
|
|
*/
|
|
|
const CSV_CLASS = 'jp-CSVWidget';
|
|
|
|
|
|
-/**
|
|
|
- * The class name added to a csv toolbar widget.
|
|
|
- */
|
|
|
-const CSV_TOOLBAR_CLASS = 'jp-CSVWidget-toolbar';
|
|
|
-
|
|
|
-/**
|
|
|
- * The class name added to a csv toolbar's dropdown element.
|
|
|
- */
|
|
|
-const CSV_TOOLBAR_DROPDOWN_CLASS = 'jp-CSVWidget-toolbarDropdown';
|
|
|
-
|
|
|
-/**
|
|
|
- * The class name added to a csv table widget.
|
|
|
- */
|
|
|
-const CSV_TABLE_CLASS = 'jp-CSVWidget-table';
|
|
|
-
|
|
|
/**
|
|
|
* The class name added to a csv warning widget.
|
|
|
*/
|
|
|
const CSV_WARNING_CLASS = 'jp-CSVWidget-warning';
|
|
|
|
|
|
-/**
|
|
|
- * The hard limit on the number of rows to display.
|
|
|
- */
|
|
|
-const DISPLAY_LIMIT = 1000;
|
|
|
-
|
|
|
|
|
|
/**
|
|
|
- * A widget for csv tables.
|
|
|
+ * A widget for CSV tables.
|
|
|
*/
|
|
|
export
|
|
|
class CSVWidget extends Widget {
|
|
|
/**
|
|
|
* Construct a new csv table widget.
|
|
|
*/
|
|
|
- constructor(context: DocumentRegistry.IContext<DocumentRegistry.IModel>) {
|
|
|
+ constructor(options: CSVWidget.IOptions) {
|
|
|
super();
|
|
|
- this._context = context;
|
|
|
- this.node.tabIndex = -1;
|
|
|
+
|
|
|
+ let context = options.context;
|
|
|
+ let layout = this.layout = new PanelLayout();
|
|
|
+
|
|
|
this.addClass(CSV_CLASS);
|
|
|
+ this.title.label = context.path.split('/').pop();
|
|
|
|
|
|
- this.layout = new PanelLayout();
|
|
|
- this._toolbar = new Widget({ node: createDelimiterSwitcherNode() });
|
|
|
- this._toolbar.addClass(CSV_TOOLBAR_CLASS);
|
|
|
- this._table = new Widget();
|
|
|
- this._table.addClass(CSV_TABLE_CLASS);
|
|
|
- this._table.addClass(HTML_COMMON_CLASS);
|
|
|
this._warning = new Widget();
|
|
|
this._warning.addClass(CSV_WARNING_CLASS);
|
|
|
|
|
|
- let layout = this.layout as PanelLayout;
|
|
|
+ this._table = new CSVTable();
|
|
|
+ this._table.model = new CSVModel({ content: context.model.toString() });
|
|
|
+ this._table.model.maxExceeded.connect((sender, overflow) => {
|
|
|
+ let { available, maximum } = overflow;
|
|
|
+ let message = `Table is too long to render,
|
|
|
+ rendering ${maximum} of ${available} rows`;
|
|
|
+ this._warning.node.textContent = message;
|
|
|
+ }, this);
|
|
|
+
|
|
|
+ this._toolbar = new CSVToolbar();
|
|
|
+ this._toolbar.delimiterChanged.connect((sender, delimiter) => {
|
|
|
+ this._table.model.delimiter = delimiter;
|
|
|
+ }, this);
|
|
|
+
|
|
|
layout.addWidget(this._toolbar);
|
|
|
layout.addWidget(this._table);
|
|
|
layout.addWidget(this._warning);
|
|
|
|
|
|
- let select = this._toolbar.node.getElementsByClassName(
|
|
|
- CSV_TOOLBAR_DROPDOWN_CLASS)[0] as HTMLSelectElement;
|
|
|
-
|
|
|
- if (context.model.toString()) {
|
|
|
- this.update();
|
|
|
- }
|
|
|
- context.pathChanged.connect(() => {
|
|
|
- this.update();
|
|
|
- });
|
|
|
+ context.pathChanged.connect((c, path) => {
|
|
|
+ this.title.label = path.split('/').pop();
|
|
|
+ }, this);
|
|
|
context.model.contentChanged.connect(() => {
|
|
|
- this.update();
|
|
|
- });
|
|
|
- context.fileChanged.connect(() => {
|
|
|
- this.update();
|
|
|
- });
|
|
|
-
|
|
|
- // Change delimiter on a change in the dropdown.
|
|
|
- select.addEventListener('change', event => {
|
|
|
- this.delimiter = select.value;
|
|
|
- this.update();
|
|
|
- });
|
|
|
+ this._table.model.content = context.model.toString();
|
|
|
+ }, this);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -118,57 +94,12 @@ class CSVWidget extends Widget {
|
|
|
if (this.isDisposed) {
|
|
|
return;
|
|
|
}
|
|
|
- this._context = null;
|
|
|
super.dispose();
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Handle `update-request` messages for the widget.
|
|
|
- */
|
|
|
- protected onUpdateRequest(msg: Message): void {
|
|
|
- this.title.label = this._context.path.split('/').pop();
|
|
|
- let cm = this._context.contentsModel;
|
|
|
- if (cm === null) {
|
|
|
- return;
|
|
|
- }
|
|
|
- let content = this._context.model.toString();
|
|
|
- let delimiter = this.delimiter as string;
|
|
|
- this.renderTable(content, delimiter);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Render an html table from a csv string.
|
|
|
- */
|
|
|
- renderTable(content: string, delimiter: string) {
|
|
|
- let parsed = d3Dsv.dsvFormat(delimiter).parse(content);
|
|
|
- let table = document.createElement('table');
|
|
|
- let header = document.createElement('thead');
|
|
|
- let body = document.createElement('tbody');
|
|
|
- for (let name of parsed.columns) {
|
|
|
- let th = document.createElement('th');
|
|
|
- th.textContent = name;
|
|
|
- header.appendChild(th);
|
|
|
- }
|
|
|
- for (let row of parsed.slice(0, DISPLAY_LIMIT)) {
|
|
|
- let tr = document.createElement('tr');
|
|
|
- for (let col of parsed.columns) {
|
|
|
- let td = document.createElement('td');
|
|
|
- td.textContent = row[col];
|
|
|
- tr.appendChild(td);
|
|
|
- }
|
|
|
- body.appendChild(tr);
|
|
|
- }
|
|
|
- let msg = `Table is too long to render, rendering ${DISPLAY_LIMIT} of ` +
|
|
|
- `${parsed.length} rows`;
|
|
|
- if (parsed.length > DISPLAY_LIMIT) {
|
|
|
- this._warning.node.textContent = msg;
|
|
|
- } else {
|
|
|
- this._warning.node.textContent = '';
|
|
|
- }
|
|
|
- table.appendChild(header);
|
|
|
- table.appendChild(body);
|
|
|
- this._table.node.textContent = '';
|
|
|
- this._table.node.appendChild(table);
|
|
|
+ this._table.model.dispose();
|
|
|
+ this._table.dispose();
|
|
|
+ this._toolbar.dispose();
|
|
|
+ this._warning.dispose();
|
|
|
+ disconnectReceiver(this);
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -178,41 +109,29 @@ class CSVWidget extends Widget {
|
|
|
this.node.focus();
|
|
|
}
|
|
|
|
|
|
- private _context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
|
|
|
- private delimiter: string = ',';
|
|
|
- private _toolbar: Widget = null;
|
|
|
- private _table: Widget = null;
|
|
|
+ private _toolbar: CSVToolbar = null;
|
|
|
+ private _table: CSVTable = null;
|
|
|
private _warning: Widget = null;
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
- * Create the node for the delimiter switcher.
|
|
|
+ * A namespace for `CSVWidget` statics.
|
|
|
*/
|
|
|
-function createDelimiterSwitcherNode(): HTMLElement {
|
|
|
- let div = document.createElement('div');
|
|
|
- let label = document.createElement('span');
|
|
|
- label.textContent = 'Delimiter:';
|
|
|
- let select = document.createElement('select');
|
|
|
- for (let delim of [',', ';', '\t']) {
|
|
|
- let option = document.createElement('option');
|
|
|
- option.value = delim;
|
|
|
- if (delim === '\t') {
|
|
|
- option.textContent = '\\t';
|
|
|
- } else {
|
|
|
- option.textContent = delim;
|
|
|
- }
|
|
|
- select.appendChild(option);
|
|
|
+export
|
|
|
+namespace CSVWidget {
|
|
|
+ /**
|
|
|
+ * Instantiation options for CSV widgets.
|
|
|
+ */
|
|
|
+ export
|
|
|
+ interface IOptions {
|
|
|
+ context: DocumentRegistry.IContext<DocumentRegistry.IModel>;
|
|
|
}
|
|
|
- select.className = CSV_TOOLBAR_DROPDOWN_CLASS;
|
|
|
- div.appendChild(label);
|
|
|
- div.appendChild(select);
|
|
|
- return div;
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
- * A widget factory for csv tables.
|
|
|
+ * A widget factory for CSV widgets.
|
|
|
*/
|
|
|
export
|
|
|
class CSVWidgetFactory extends ABCWidgetFactory<CSVWidget, DocumentRegistry.IModel> {
|
|
@@ -241,6 +160,6 @@ class CSVWidgetFactory extends ABCWidgetFactory<CSVWidget, DocumentRegistry.IMod
|
|
|
* Create a new widget given a context.
|
|
|
*/
|
|
|
protected createNewWidget(context: DocumentRegistry.IContext<DocumentRegistry.IModel>): CSVWidget {
|
|
|
- return new CSVWidget(context);
|
|
|
+ return new CSVWidget({ context });
|
|
|
}
|
|
|
}
|