From 2f048723e2ed13d356ae0e3b02ac90fac90631d3 Mon Sep 17 00:00:00 2001 From: Drew Logsdon Date: Fri, 27 May 2016 15:32:42 -0500 Subject: [PATCH] [Issue 243] Move to new metadata spec (c) Copyright IBM Corp. 2016 --- .../dashboard-view/dashboard-actions.js | 4 +- .../dashboard-metadata-compatibility.js | 100 +++++++ .../dashboard-view/dashboard-metadata.js | 267 +++++++++--------- .../notebook/dashboard-view/dashboard-view.js | 16 +- .../dashboard-view/layout/grid/layout.js | 37 +-- .../dashboard-view/layout/report/layout.js | 15 +- .../notebook/dashboard-view/object-util.js | 40 +++ .../nbextension/notebook/main.js | 22 +- system-test/utils/dashboard.js | 2 +- 9 files changed, 329 insertions(+), 174 deletions(-) create mode 100644 jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-metadata-compatibility.js create mode 100644 jupyter_dashboards/nbextension/notebook/dashboard-view/object-util.js diff --git a/jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-actions.js b/jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-actions.js index 970a5f0..6b88645 100644 --- a/jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-actions.js +++ b/jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-actions.js @@ -181,7 +181,7 @@ define([ var $el = $(this); var state = $el.attr('data-dashboard-state'); if ($el.parents('#jupyter-dashboard-layout-menu').length) { - Metadata.dashboardLayout = state; + Metadata.activeView = state; } setDashboardState(state); }) @@ -206,7 +206,7 @@ define([ updateAuthoringOptions(currentState); // also set the initial layout button default to whatever layout the // notebook currently has - updateAuthoringButtonState(Metadata.dashboardLayout); + updateAuthoringButtonState(Metadata.activeView); }; DashboardActions.prototype.switchToNotebook = function() { diff --git a/jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-metadata-compatibility.js b/jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-metadata-compatibility.js new file mode 100644 index 0000000..b5ddb46 --- /dev/null +++ b/jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-metadata-compatibility.js @@ -0,0 +1,100 @@ +/** + * Copyright (c) Jupyter Development Team. + * Distributed under the terms of the Modified BSD License. + */ +/** + * This module provides an API to manage compatibility between + * Jupyter Dashboards extension metadata. + * + * Jupyter Dashboard metadata structure: + * https://github.com/jupyter-incubator/dashboards/wiki/Dashboard-Metadata-and-Rendering + */ +define([ + 'jquery', + 'base/js/namespace', + './object-util' +], function( + $, + IPython, + ObjectUtil +) { + 'use strict'; + + var GRID_DEFAULT = 'grid_default'; + var REPORT_DEFAULT = 'report_default'; + + // Mapping of view type to view id (from dashboard-metadata.js) + // This will change when multiple views of the same type are supported. + var VIEW_TO_ID = { + grid: GRID_DEFAULT, + report: REPORT_DEFAULT + }; + + return { + /** + * Converts version 0.x metadata structure to version 1. + * Versions 0.x and 1 currently do not overlap. + * This must be run AFTER the version 1 metadata is initialized in + * `dashboard-metadata.js`. + */ + convert: function() { + var metadata = IPython.notebook.metadata; + var jupyter_dashboards = metadata.extensions.jupyter_dashboards; + + // 0.x metadata resides under "urth" + var oldNotebookMetadata = IPython.notebook.metadata.urth; + if (!oldNotebookMetadata) { return; } + + // notebook-level metadata + if (oldNotebookMetadata.dashboard) { + // set active view + if (oldNotebookMetadata.dashboard.layout) { + jupyter_dashboards.activeView = + VIEW_TO_ID[oldNotebookMetadata.dashboard.layout]; + } else { + jupyter_dashboards.activeView = GRID_DEFAULT; + } + + // copy dashboard properties + Object.keys(oldNotebookMetadata.dashboard).filter(function(key) { + return key !== 'layout'; + }).forEach(function(key) { + jupyter_dashboards.views[jupyter_dashboards.activeView][key] = + oldNotebookMetadata.dashboard[key]; + }); + } + + // cell-level metadata + IPython.notebook.get_cells().forEach(function(cell) { + if (ObjectUtil.has(cell, 'metadata.urth.dashboard')) { + var cellMetadata = cell.metadata; + var cellDashboard = cellMetadata.extensions.jupyter_dashboards; + + // copy hidden state + Object.keys(cellDashboard.views).forEach(function(view) { + cellDashboard.views[view].hidden = false; + }); + cellDashboard.views[jupyter_dashboards.activeView].hidden = + !!cellMetadata.urth.dashboard.hidden; + + // copy layout properties + if (cellMetadata.urth.dashboard.layout) { + var cellLayout = cellMetadata.urth.dashboard.layout; + + // only grid layout has properties + // so copy all layout properties into default grid view + Object.keys(cellLayout).forEach(function(key) { + cellDashboard.views[GRID_DEFAULT][key] = cellLayout[key]; + }); + } + + // clear old metadata + delete cellMetadata.urth; + } + }); + + // clear old metadata + delete IPython.notebook.metadata.urth; + } + }; +}); diff --git a/jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-metadata.js b/jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-metadata.js index 94455bb..602f0bc 100644 --- a/jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-metadata.js +++ b/jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-metadata.js @@ -3,174 +3,163 @@ * Distributed under the terms of the Modified BSD License. */ /** - * This module provides an API to manage notebook dashboard layout cell metadata. + * This module provides an API to manage Jupyter Dashboards extension metadata. * - * Dashboard metadata structure: + * Jupyter Dashboard metadata structure: * https://github.com/jupyter-incubator/dashboards/wiki/Dashboard-Metadata-and-Rendering */ define([ 'jquery', - 'base/js/namespace' + 'base/js/namespace', + './dashboard-metadata-compatibility', + './object-util' ], function( $, - IPython + IPython, + Compatibility, + ObjectUtil ) { 'use strict'; - var CELL_PROPERTIES = ['col','row','width','height']; + var SPEC_VERSION = 1; + var GRID_DEFAULT = 'grid_default'; + var REPORT_DEFAULT = 'report_default'; - // must match `dashboard-actions` auth states - var DASHBOARD_LAYOUT = Object.freeze({ + // must match `dashboard-actions.js` auth states + var DASHBOARD_VIEW = Object.freeze({ GRID: 'grid', REPORT: 'report' }); - // pull cell metadata from element data - function _getCellMetadata($elem) { + // Mapping of view type to view id + // This will change when multiple views of the same type are supported. + var VIEW_TO_ID = { + grid: GRID_DEFAULT, + report: REPORT_DEFAULT + }; + + function _getActiveView() { + var metadata = _getDashboardMetadata(); + if (metadata) { + return metadata.activeView; + } + } + + /** + * Returns view metadata from cell element data. + * @param {jQuery} $elem - notebook cell or child element of a notebook cell + * @param {string} [viewId] - View to get metadata for. + * Defaults to the active view if viewId isn't specified. + * @return {Object} view metadata + */ + function _getCellViewMetadata($elem, viewId) { + // use active view if not specified + viewId = viewId || _getActiveView(); if (!$elem.is('.cell')) { $elem = $elem.parents('.cell').first(); } if ($elem.length > 0) { var metadata = $elem.data('cell').metadata; - return metadata && - metadata.urth && - metadata.urth.dashboard; + return metadata.extensions.jupyter_dashboards.views[viewId]; } } - function _createEmptyUrthMetadata(cell, preserveExistingMetadata) { + function _createDashboardMetadata(cell) { var metadata = IPython.notebook.metadata; if (cell) { metadata = $(cell).data('cell').metadata; } - if (preserveExistingMetadata) { - metadata.urth = metadata.urth || {}; - metadata.urth.dashboard = metadata.urth.dashboard || {}; - } else { - metadata.urth = { - dashboard: {} - }; - } - return metadata; - } - function _getDashboardLayout() { - var metadata = _getDashboardMetadata(); - var layout = null; - if (metadata && metadata.layout) { - layout = metadata.layout; + // common metadata + ObjectUtil.ensure(metadata, 'extensions.jupyter_dashboards.views'); + metadata.extensions.jupyter_dashboards.version = SPEC_VERSION; + + var views = metadata.extensions.jupyter_dashboards.views; + ObjectUtil.ensure(views, GRID_DEFAULT); + ObjectUtil.ensure(views, REPORT_DEFAULT); + + // notebook-level only metadata + if (!cell) { + views[GRID_DEFAULT].type = DASHBOARD_VIEW.GRID; + views[REPORT_DEFAULT].type = DASHBOARD_VIEW.REPORT; + + // default names + views[GRID_DEFAULT].name = DASHBOARD_VIEW.GRID; + views[REPORT_DEFAULT].name = DASHBOARD_VIEW.REPORT; } - return layout; + + return metadata; } function _getDashboardMetadata() { var metadata = IPython.notebook.metadata; - if (metadata.hasOwnProperty('urth') && - metadata.urth.hasOwnProperty('dashboard')) { - return metadata.urth.dashboard; + if (ObjectUtil.has(metadata, 'extensions.jupyter_dashboards')) { + return metadata.extensions.jupyter_dashboards; } } - /** - * Sets default values in the metadata if not set. - * @param {Object} values - values to set - */ - function _setDefaultValues(values) { - values = values || {}; - if (typeof values === 'object') { - var metadata = _getDashboardMetadata(); - Object.keys(values).forEach(function(key) { + function _setDefaultGridProperties(props) { + props = props || {}; + if (typeof props === 'object') { + var metadata = _getDashboardMetadata().views[GRID_DEFAULT]; + Object.keys(props).forEach(function(key) { // only copy values that are not already set in metadata if (!metadata.hasOwnProperty(key)) { - metadata[key] = values[key]; + metadata[key] = props[key]; } }); } else { - throw new Error('Metadata values must be an object:', values); + throw new Error('Metadata properties must be an object:', props); } } // Ensures dashboard metadata exists and sets default notebook-level values. - // Will clear out existing metadata if preserveExistingMetadata == true. - function _initMetadata(opts, preserveExistingMetadata) { - if (arguments.length < 2) { - preserveExistingMetadata = true; - } - _createEmptyUrthMetadata(null, preserveExistingMetadata); - _setDefaultValues(opts); + function _initMetadata() { + _createDashboardMetadata(); $('.cell').each(function() { - _createEmptyUrthMetadata(this, preserveExistingMetadata); + _createDashboardMetadata(this); }); + Compatibility.convert(); } - function _removePosition() { + function _removeGridPositioning() { IPython.notebook.get_cells().forEach(function(cell, i) { - var layout = cell.metadata.urth.dashboard.layout; - if (layout) { - delete layout.col; - delete layout.row; + var view = cell.metadata.extensions.jupyter_dashboards.views[GRID_DEFAULT]; + if (view) { + delete view.col; + delete view.row; } }); } - // copy dashboard layout information into each cell's metadata + // copy grid layout information into each cell's view metadata function _saveGrid() { $('.grid-stack .cell').each(function (i) { var el = $(this); if (el.is('.grid-stack-item')) { - // add gridstack layout data to cell metadata + // add gridstack layout data to cell metadata and show cell var node = el.data('_gridstack_node'); - _updateCellMetadata(el, { + _updateCellMetadata({ col: node.x, row: node.y, width: node.width, height: node.height - }); + }, el, GRID_DEFAULT); } else { // add hidden metadata to cell - _updateCellMetadata(el, null); + _updateCellMetadata({ hidden: true }, el, GRID_DEFAULT); } }); IPython.notebook.set_dirty(true); } - function _showCell(cells) { - $(cells).each(function() { - var metadata = _getCellMetadata($(this)); - // add a layout object to indicate that this cell has explicitly - // been added to the layout either by some initialization routine - // that calculated the layout or the user - metadata.layout = {}; - delete metadata.hidden; - }); - } - - function _updateCellMetadata($cells, layout) { - if (arguments.length === 1) { - layout = $cells; // first argument is optional - $cells = $('.cell'); - } else { - $cells = $($cells); // force to jquery - } + function _updateCellMetadata(viewProps, $cells, viewId) { + // force cells to jquery & use all cells if not specified + $cells = $cells ? $($cells) : $('.cell'); + viewId = viewId || _getActiveView(); $cells.each(function(i, cell) { - var metadata = _getCellMetadata($(cell)); - - // only update the layout if not hidden - if (layout && !metadata.hidden) { - metadata.layout = metadata.layout || {}; - CELL_PROPERTIES.forEach(function(prop) { - if (layout.hasOwnProperty(prop)) { - metadata.layout[prop] = layout[prop]; - } - }); - } else { - // reset the layout object, but don't remove it completely - // since it serves as the indicator that this cell has been - // purposefully added to the layout in the past - metadata.layout = {}; - metadata.hidden = true; - } + $.extend(_getCellViewMetadata($(cell), viewId), viewProps); }); IPython.notebook.set_dirty(true); } @@ -182,26 +171,27 @@ define([ } return { - get DASHBOARD_LAYOUT() { return DASHBOARD_LAYOUT; }, + get DASHBOARD_VIEW() { return DASHBOARD_VIEW; }, /** - * @return {string} dashboard layout type (defaults to grid layout if not set) + * @return {string} dashboard view type *//** - * Sets the specified dashboard layout in the notebook metadata - * @param {string} layout - desired dashboard layout + * Sets the specified view in the notebook metadata + * @param {string} view - desired dashboard view */ - get dashboardLayout() { - return _getDashboardLayout(); + get activeView() { + // map default view id to view type + var activeView = _getActiveView(); + return Object.keys(VIEW_TO_ID).filter(function(key) { + return VIEW_TO_ID[key] === activeView; + })[0]; }, - set dashboardLayout(dbLayout) { - if (_validValue(DASHBOARD_LAYOUT, dbLayout)) { - var currentLayout = _getDashboardLayout(); - var preserveExistingMetadata = currentLayout === dbLayout || - currentLayout === null; - _initMetadata({}, preserveExistingMetadata); - _getDashboardMetadata().layout = dbLayout; + set activeView(dbView) { + if (_validValue(DASHBOARD_VIEW, dbView)) { + _initMetadata(); + _getDashboardMetadata().activeView = VIEW_TO_ID[dbView]; } else { - throw new Error('Invalid dashboard layout:', dbLayout); + throw new Error('Invalid dashboard view:', dbView); } }, @@ -212,20 +202,32 @@ define([ return _getDashboardMetadata(); }, + /** + * @return {Object} active view properties + */ + get viewProperties() { + return _getDashboardMetadata().views[_getActiveView()]; + }, + /** * @param {jQuery} $cell - notebook cell or element inside a notebook cell - * @return {Object} layout positioning for the specified cell + * @return {Object} view positioning for the specified cell */ getCellLayout: function($cell) { - var metadata = _getCellMetadata($cell); - return metadata && metadata.layout; + return _getCellViewMetadata($cell); }, /** - * Update a cell's metadata so it does not appear in the dashboard. - * @param {(DOM Element|DOM Element[]|jQuery)} $cell - one or more notebook cells to hide + * @return {boolean} true if cell has been rendered in the active dashboard view */ - hideCell: function($cell) { - _updateCellMetadata($cell, null); + hasCellBeenRendered: function($cell) { + return _getCellViewMetadata($cell).hasOwnProperty('hidden'); + }, + /** + * Hide's a cell in the active view. + * @param {(DOM Element|DOM Element[]|jQuery)} $cells - one or more notebook cells to hide + */ + hideCell: function($cells) { + _updateCellMetadata({ hidden: true }, $cells); }, /** * Populates the notebook metadata with empty dashboard metadata @@ -236,27 +238,36 @@ define([ * @return {boolean} true if the cell is visible, else false */ isCellVisible: function($cell) { - var metadata = _getCellMetadata($cell); - return metadata && !metadata.hidden; + return !_getCellViewMetadata($cell).hidden; }, /** * Copies layout data from the dashboard to the notebook metadata */ save: _saveGrid, /** - * Shows the specified cells. Adds an empty layout object. - * The layout can be subsequently populated by calling `updateCellLayout`. - * @type {(DOM Element|DOM Element[]|jQuery)} cells - one or more cells to show + * Mark a cell as being rendered. Some layouts may use this information + * for performing operations on new cells, e.g. auto-hide. + * @param {jQuery} $cell - cell to mark rendered */ - showCell: _showCell, + setCellRendered: function($cell) { + var view = _getCellViewMetadata($cell); + view.hidden = !!view.hidden; + }, /** - * Stacks the cells in notebook order. Keeps hidden cells hidden. + * Sets the specified grid view properties if they are not set + * @type {Object} props - grid view properties and values */ - stackCells: _removePosition, + setDefaultGridProperties: _setDefaultGridProperties, /** - * Update the specified cell's layout metadata - * @param {(DOM Element|DOM Element[]|jQuery)} $cell - one or more notebook cells to update + * Shows the specified cells in the active view. + * @type {(DOM Element|DOM Element[]|jQuery)} cells - one or more cells to show + */ + showCell: function($cells) { + _updateCellMetadata({ hidden: false }, $cells); + }, + /** + * Stacks the cells in notebook order. Keeps hidden cells hidden. */ - updateCellLayout: _updateCellMetadata + stackCells: _removeGridPositioning }; }); diff --git a/jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-view.js b/jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-view.js index 168f6d2..e2604c1 100644 --- a/jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-view.js +++ b/jupyter_dashboards/nbextension/notebook/dashboard-view/dashboard-view.js @@ -92,21 +92,21 @@ define([ $helpTemplate ) { var LAYOUT = {}; - LAYOUT[Metadata.DASHBOARD_LAYOUT.GRID] = getLayout(dbActions, GridLayout, { + LAYOUT[Metadata.DASHBOARD_VIEW.GRID] = getLayout(dbActions, GridLayout, { onResize: PolymerSupport.onResize }); - LAYOUT[Metadata.DASHBOARD_LAYOUT.REPORT] = getLayout(dbActions, ReportLayout); + LAYOUT[Metadata.DASHBOARD_VIEW.REPORT] = getLayout(dbActions, ReportLayout); if (actionState !== DashboardActions.STATE.NOTEBOOK && - !Metadata.dashboardLayout) { + !Metadata.activeView) { // set to grid by default if layout not set - Metadata.dashboardLayout = Metadata.DASHBOARD_LAYOUT.GRID; + Metadata.activeView = Metadata.DASHBOARD_VIEW.GRID; } - LAYOUT[DashboardActions.STATE.DASHBOARD_PREVIEW] = LAYOUT[Metadata.dashboardLayout]; + LAYOUT[DashboardActions.STATE.DASHBOARD_PREVIEW] = LAYOUT[Metadata.activeView]; var layout = LAYOUT[actionState]; if (dashboard) { - // when switching between two layouts, destroy the old one + // destroy the previous view dashboard.destroy(); } // create help area @@ -140,8 +140,8 @@ define([ PolymerSupport.notifyResizeAll(); } }); - // Metadata.dashboardLayout gets set by the layout module - $('body').attr('data-dashboard-layout', Metadata.dashboardLayout); + // Metadata.activeView gets set by the layout module + $('body').attr('data-dashboard-layout', Metadata.activeView); }); }, exitDashboardMode: function() { diff --git a/jupyter_dashboards/nbextension/notebook/dashboard-view/layout/grid/layout.js b/jupyter_dashboards/nbextension/notebook/dashboard-view/layout/grid/layout.js index f4b0245..5f9438f 100644 --- a/jupyter_dashboards/nbextension/notebook/dashboard-view/layout/grid/layout.js +++ b/jupyter_dashboards/nbextension/notebook/dashboard-view/layout/grid/layout.js @@ -48,15 +48,16 @@ define([ this._loaded = $.Deferred(); ErrorLog.enable(IPython); - Metadata.dashboardLayout = Metadata.DASHBOARD_LAYOUT.GRID; + Metadata.activeView = Metadata.DASHBOARD_VIEW.GRID; // read configurable values from dashboard metadata - var dm = Metadata.dashboardMetadata; - this.gridMargin = dm.cellMargin >= 0 ? dm.cellMargin : 10; - this.numCols = dm.maxColumns >= 0 ? dm.maxColumns : 12; - this.rowHeight = dm.defaultCellHeight >= 0 ? dm.defaultCellHeight : 20; + var viewProps = Metadata.viewProperties; + this.gridMargin = viewProps.cellMargin >= 0 ? viewProps.cellMargin : 10; + this.numCols = viewProps.maxColumns >= 0 ? viewProps.maxColumns : 12; + this.rowHeight = viewProps.defaultCellHeight >= 0 ? viewProps.defaultCellHeight : 20; - Metadata.initialize({ + Metadata.initialize(); + Metadata.setDefaultGridProperties({ cellMargin: this.gridMargin, maxColumns: this.numCols, defaultCellHeight: this.rowHeight @@ -111,23 +112,23 @@ define([ this.$container.addClass('grid-stack'); var self = this; this.$container.find('.cell').each(function(idx) { - // Gridstack expects horizontal margins to be handled within the cell. To accomplish - // that, we need to wrap the cell contents in an element. - // Also, we add a separate div to show the border, since we want to show that above + // Wrap the cell contents in an element because Gridstack expects + // horizontal margins to be handled within the cell. + // Add a separate div to show the border since we want to show it above // the cell contents. This is necessary since cell contents may overflow the cell // bounds, but we still want to show the user those boundaries. - var el = $(this); - if (el.find('> .dashboard-item-background').length === 0) { - el.prepend('
'); + var $cell = $(this); + if ($cell.find('> .dashboard-item-background').length === 0) { + $cell.prepend('
'); } - var layout = Metadata.getCellLayout(el); - if (Metadata.isCellVisible(el)) { - if (layout) { - self._initVisibleCell(el, layout); - } else { - nolayout.push($(this)); // cell doesn't have layout info; save for later + if (Metadata.hasCellBeenRendered($cell)) { + if (Metadata.isCellVisible($cell)) { + self._initVisibleCell($cell, Metadata.getCellLayout($cell)); } + } else { + // cell hasn't been laid out before so save for later + nolayout.push($(this)); } }); return nolayout; diff --git a/jupyter_dashboards/nbextension/notebook/dashboard-view/layout/report/layout.js b/jupyter_dashboards/nbextension/notebook/dashboard-view/layout/report/layout.js index 529bbde..b4461c2 100644 --- a/jupyter_dashboards/nbextension/notebook/dashboard-view/layout/report/layout.js +++ b/jupyter_dashboards/nbextension/notebook/dashboard-view/layout/report/layout.js @@ -29,7 +29,7 @@ define([ cssLoaded = linkCSS('./dashboard-view/layout/report/layout.css'); } - Metadata.dashboardLayout = Metadata.DASHBOARD_LAYOUT.REPORT; + Metadata.activeView = Metadata.DASHBOARD_VIEW.REPORT; $.when(cssLoaded).then(function() { // setup cells for report layout @@ -39,22 +39,17 @@ define([ .each(function() { var $cell = $(this); - // if there's no layout object in the cell metadata, assume - // this cell has never been seen by the layout before and - // compute a default visibility based on its height - if(!Metadata.getCellLayout($cell)) { - // hide cell if empty + // mark new & empty cells hidden + if (!Metadata.hasCellBeenRendered($cell)) { + // mark the cell as rendered by hiding or showing if ($cell.height() === 0) { Metadata.hideCell($cell); } else { - // otherwise, explicitly show the cell so that - // it gains layout metadata and its visibility is - // not automatically computed again in the future Metadata.showCell($cell); } } - // set hidden state + // set hidden state in UI $cell.toggleClass('dashboard-hidden dashboard-collapsed', !Metadata.isCellVisible($cell)); diff --git a/jupyter_dashboards/nbextension/notebook/dashboard-view/object-util.js b/jupyter_dashboards/nbextension/notebook/dashboard-view/object-util.js new file mode 100644 index 0000000..756d987 --- /dev/null +++ b/jupyter_dashboards/nbextension/notebook/dashboard-view/object-util.js @@ -0,0 +1,40 @@ +/** + * Copyright (c) Jupyter Development Team. + * Distributed under the terms of the Modified BSD License. + */ +define([], function() { + 'use strict'; + + return { + /** + * @param {Object} obj - Object to test + * @param {string} prop - Name of property to test. + * May be a '.'-delimited string to check nested objects + * @return {boolean} true if specified object has specified property + */ + has: function(obj, prop) { + var currentLevel = obj; + return prop.split('.').every(function(prop) { + var has = currentLevel.hasOwnProperty(prop); + currentLevel = currentLevel[prop]; + return has; + }); + }, + /** + * Ensures a property is present in an object. + * Sets the property to an empty object if not set. + * @param {Object} obj - Object to test + * @param {string} prop - Name of property to test. + * May be a '.'-delimited string to ensure nested objects + */ + ensure: function(obj, prop) { + var currentLevel = obj; + prop.split('.').forEach(function(p) { + if (!currentLevel.hasOwnProperty(p)) { + currentLevel[p] = {}; + } + currentLevel = currentLevel[p]; + }); + } + }; +}); diff --git a/jupyter_dashboards/nbextension/notebook/main.js b/jupyter_dashboards/nbextension/notebook/main.js index 29d686b..1583074 100644 --- a/jupyter_dashboards/nbextension/notebook/main.js +++ b/jupyter_dashboards/nbextension/notebook/main.js @@ -12,21 +12,29 @@ define([ Notebook ) { return { - load_ipython_extension: function() { + load_ipython_extension: function() { console.debug('jupyter_dashboards loaded'); if (Notebook.Notebook.prototype.copy_cell) { - Notebook.Notebook.prototype.__copy_cell = Notebook.Notebook.prototype.copy_cell; + Notebook.Notebook.prototype.__copy_cell = + Notebook.Notebook.prototype.copy_cell; Notebook.Notebook.prototype.copy_cell = function() { this.__copy_cell(); for (var i = 0; i < this.clipboard.length; i++) { var cell_json = this.clipboard[i]; - if (cell_json.metadata.urth !== undefined && cell_json.metadata.urth.dashboard !== undefined && cell_json.metadata.urth.dashboard.layout !== undefined) { - // other dashboard metadata may need to be preserved - delete cell_json.metadata.urth.dashboard.layout; - cell_json.metadata.urth.dashboard.hidden = true; + if (cell_json.metadata.extensions !== undefined && + cell_json.metadata.extensions.jupyter_dashboards !== undefined && + cell_json.metadata.extensions.jupyter_dashboards.views !== undefined) { + + // Clear out data for each view. + // Other dashboard metadata may need to be preserved. + var views = cell_json.metadata.extensions.jupyter_dashboards.views; + var viewIds = Object.keys(views); + for (var j = 0; j < viewIds.length; j++) { + views[viewIds[j]] = { hidden: true }; + } } } - } + }; } } }; diff --git a/system-test/utils/dashboard.js b/system-test/utils/dashboard.js index 3c8f280..1f71ebe 100644 --- a/system-test/utils/dashboard.js +++ b/system-test/utils/dashboard.js @@ -20,7 +20,7 @@ var DashboardElements = { "layoutViewHelpArea" : "#notebook_panel > .help-area", "jupyterHeaderContainer": "#header-container", "viewMenuButton" : "ul.nav > li:nth-child(3)", - "notebookViewMenuButton" : "#urth-notebook-view", + "notebookViewMenuButton" : "#jupyter-dashboard-notebook-view", "layoutViewMenuButton" : "#jupyter-dashboard-layout-menu", "layoutViewGridMenuButton" : "#jupyter-dashboard-auth-grid", "dashboardViewMenuButton" : "#jupyter-dashboard-view"