diff --git a/src/visualizers/panels/InteractiveEditor/InteractiveEditorControl.js b/src/visualizers/panels/InteractiveEditor/InteractiveEditorControl.js new file mode 100644 index 000000000..35152239d --- /dev/null +++ b/src/visualizers/panels/InteractiveEditor/InteractiveEditorControl.js @@ -0,0 +1,218 @@ +/*globals define, WebGMEGlobal*/ + +define([ + 'deepforge/viz/ConfigDialog', + 'js/Constants', +], function ( + ConfigDialog, + CONSTANTS, +) { + + 'use strict'; + + class InteractiveEditorControl { + constructor(options) { + this._logger = options.logger.fork('Control'); + this.client = options.client; + this._embedded = options.embedded; + this._widget = options.widget; + this.initializeWidgetHandlers(this._widget); + this.territoryEventFilters = []; + + this._currentNodeId = null; + + this._logger.debug('ctor finished'); + } + + initializeWidgetHandlers (widget) { + const features = widget.getCapabilities(); + if (features.save) { + widget.save = () => this.save(); + } + widget.getConfigDialog = () => new ConfigDialog(this.client); + } + + selectedObjectChanged (nodeId) { + const desc = this.getObjectDescriptor(nodeId); + + this._logger.debug('activeObject nodeId \'' + nodeId + '\''); + + if (this._currentNodeId) { + this.client.removeUI(this._territoryId); + } + + this._currentNodeId = nodeId; + + if (typeof this._currentNodeId === 'string') { + const territory = this.getTerritory(nodeId); + this._widget.setTitle(desc.name.toUpperCase()); + + this._territoryId = this.client + .addUI(this, events => this._eventCallback(events)); + + this.client.updateTerritory(this._territoryId, territory); + } + } + + getTerritory(nodeId) { + const territory = {}; + territory[nodeId] = {children: 0}; + return territory; + } + + getMetaNode(name) { + const metanodes = this.client.getAllMetaNodes(); + return metanodes + .find(node => { + const namespace = node.getNamespace(); + const fullName = namespace ? namespace + '.' + node.getAttribute('name') : + node.getAttribute('name'); + + return fullName === name; + }); + } + + createNode(desc, parentId) { + if (!parentId) { + parentId = this._currentNodeId; + } + desc.pointers = desc.pointers || {}; + desc.attributes = desc.attributes || {}; + + const base = this.getMetaNode(desc.type) || this.client.getNode(desc.pointers.base); + const nodeId = this.client.createNode({ + parentId: parentId, + baseId: base.getId() + }); + + const attributes = Object.entries(desc.attributes); + attributes.forEach(entry => { + const [name, value] = entry; + this.client.setAttribute(nodeId, name, value); + }); + + const pointers = Object.entries(desc.pointers); + pointers.forEach(entry => { + const [name, id] = entry; + this.client.setPointer(nodeId, name, id); + }); + + return nodeId; + } + + save() { + this.client.startTransaction(); + const dataId = this.createNode(this._widget.getSnapshot()); + const implicitOpId = this.createNode(this._widget.getEditorState(), dataId); + this.client.setPointer(dataId, 'provenance', implicitOpId); + const operationId = this.createNode(this._widget.getOperation(), implicitOpId); + this.client.setPointer(implicitOpId, 'operation', operationId); + this.client.completeTransaction(); + } + + getObjectDescriptor (nodeId) { + const node = this.client.getNode(nodeId); + + if (node) { + return { + id: node.getId(), + name: node.getAttribute('name'), + childrenIds: node.getChildrenIds(), + parentId: node.getParentId(), + }; + } + } + + /* * * * * * * * Node Event Handling * * * * * * * */ + _eventCallback (events=[]) { + this._logger.debug('_eventCallback \'' + events.length + '\' items'); + + events + .filter(event => this.isRelevantEvent(event)) + .forEach(event => { + switch (event.etype) { + + case CONSTANTS.TERRITORY_EVENT_LOAD: + this.onNodeLoad(event.eid); + break; + case CONSTANTS.TERRITORY_EVENT_UPDATE: + this.onNodeUpdate(event.eid); + break; + case CONSTANTS.TERRITORY_EVENT_UNLOAD: + this.onNodeUnload(event.eid); + break; + default: + break; + } + }); + + this._logger.debug('_eventCallback \'' + events.length + '\' items - DONE'); + } + + onNodeLoad (gmeId) { + const description = this.getObjectDescriptor(gmeId); + this._widget.addNode(description); + } + + onNodeUpdate (gmeId) { + const description = this.getObjectDescriptor(gmeId); + this._widget.updateNode(description); + } + + onNodeUnload (gmeId) { + this._widget.removeNode(gmeId); + } + + isRelevantEvent (event) { + return this.territoryEventFilters + .reduce((keep, fn) => keep && fn(event), true); + } + + _stateActiveObjectChanged (model, activeObjectId) { + if (this._currentNodeId === activeObjectId) { + // The same node selected as before - do not trigger + } else { + this.selectedObjectChanged(activeObjectId); + } + } + + /* * * * * * * * Visualizer life cycle callbacks * * * * * * * */ + destroy () { + this._detachClientEventListeners(); + } + + _attachClientEventListeners () { + this._detachClientEventListeners(); + if (!this._embedded) { + WebGMEGlobal.State.on( + 'change:' + CONSTANTS.STATE_ACTIVE_OBJECT, + this._stateActiveObjectChanged, + this + ); + } + } + + _detachClientEventListeners () { + if (!this._embedded) { + WebGMEGlobal.State.off( + 'change:' + CONSTANTS.STATE_ACTIVE_OBJECT, + this._stateActiveObjectChanged + ); + } + } + + onActivate () { + this._attachClientEventListeners(); + + if (typeof this._currentNodeId === 'string') { + WebGMEGlobal.State.registerActiveObject(this._currentNodeId, {suppressVisualizerFromNode: true}); + } + } + + onDeactivate () { + this._detachClientEventListeners(); + } + } + + return InteractiveEditorControl; +}); diff --git a/src/visualizers/panels/InteractiveEditor/InteractiveEditorPanel.js b/src/visualizers/panels/InteractiveEditor/InteractiveEditorPanel.js new file mode 100644 index 000000000..b7b4d7e65 --- /dev/null +++ b/src/visualizers/panels/InteractiveEditor/InteractiveEditorPanel.js @@ -0,0 +1,97 @@ +/*globals define, _, WebGMEGlobal*/ + +define([ + 'js/PanelBase/PanelBaseWithHeader', + 'js/PanelManager/IActivePanel', + 'widgets/InteractiveEditor/InteractiveEditorWidget', + './InteractiveEditorControl' +], function ( + PanelBaseWithHeader, + IActivePanel, + InteractiveEditorWidget, + InteractiveEditorControl +) { + 'use strict'; + + function InteractiveEditorPanel(layoutManager, params) { + var options = {}; + //set properties from options + options[PanelBaseWithHeader.OPTIONS.LOGGER_INSTANCE_NAME] = 'InteractiveEditorPanel'; + options[PanelBaseWithHeader.OPTIONS.FLOATING_TITLE] = true; + + //call parent's constructor + PanelBaseWithHeader.apply(this, [options, layoutManager]); + + this._client = params.client; + this._embedded = params.embedded; + + this.initialize(); + + this.logger.debug('ctor finished'); + } + + //inherit from PanelBaseWithHeader + _.extend(InteractiveEditorPanel.prototype, PanelBaseWithHeader.prototype); + _.extend(InteractiveEditorPanel.prototype, IActivePanel.prototype); + + InteractiveEditorPanel.prototype.initialize = function () { + var self = this; + + //set Widget title + this.setTitle(''); + + this.widget = new InteractiveEditorWidget(this.logger, this.$el); + + this.widget.setTitle = function (title) { + self.setTitle(title); + }; + + this.control = new InteractiveEditorControl({ + logger: this.logger, + client: this._client, + embedded: this._embedded, + widget: this.widget + }); + + this.onActivate(); + }; + + /* OVERRIDE FROM WIDGET-WITH-HEADER */ + /* METHOD CALLED WHEN THE WIDGET'S READ-ONLY PROPERTY CHANGES */ + InteractiveEditorPanel.prototype.onReadOnlyChanged = function (isReadOnly) { + //apply parent's onReadOnlyChanged + PanelBaseWithHeader.prototype.onReadOnlyChanged.call(this, isReadOnly); + + }; + + InteractiveEditorPanel.prototype.onResize = function (width, height) { + this.logger.debug('onResize --> width: ' + width + ', height: ' + height); + this.widget.onWidgetContainerResize(width, height); + }; + + /* * * * * * * * Visualizer life cycle callbacks * * * * * * * */ + InteractiveEditorPanel.prototype.destroy = function () { + this.control.destroy(); + this.widget.destroy(); + + PanelBaseWithHeader.prototype.destroy.call(this); + WebGMEGlobal.KeyboardManager.setListener(undefined); + WebGMEGlobal.Toolbar.refresh(); + }; + + InteractiveEditorPanel.prototype.onActivate = function () { + this.widget.onActivate(); + this.control.onActivate(); + WebGMEGlobal.KeyboardManager.setListener(this.widget); + WebGMEGlobal.Toolbar.refresh(); + }; + + InteractiveEditorPanel.prototype.onDeactivate = function () { + this.widget.onDeactivate(); + this.control.onDeactivate(); + WebGMEGlobal.KeyboardManager.setListener(undefined); + WebGMEGlobal.Toolbar.refresh(); + }; + + return InteractiveEditorPanel; +}); diff --git a/src/visualizers/panels/InteractiveExplorer/InteractiveExplorerControl.js b/src/visualizers/panels/InteractiveExplorer/InteractiveExplorerControl.js new file mode 100644 index 000000000..bca1aad31 --- /dev/null +++ b/src/visualizers/panels/InteractiveExplorer/InteractiveExplorerControl.js @@ -0,0 +1,44 @@ +/*globals define */ + +define([ + 'panels/InteractiveEditor/InteractiveEditorControl', +], function ( + InteractiveEditorControl, +) { + + 'use strict'; + + class InteractiveExplorerControl extends InteractiveEditorControl { + ensureValidSnapshot(desc) { + const metadata = this.getMetaNode('pipeline.Metadata'); + const type = this.getMetaNode(desc.type); + + if (!type) { + throw new Error(`Invalid metadata type: ${type}`); + } + + if (!type.isTypeOf(metadata.getId())) { + throw new Error('Explorer can only create artifact metadata'); + } + } + + save() { + const snapshotDesc = this._widget.getSnapshot(); + this.ensureValidSnapshot(snapshotDesc); + + const features = this._widget.getCapabilities(); + this.client.startTransaction(); + const data = this.createNode(snapshotDesc); + if (features.provenance) { + const implicitOp = this.createNode(this._widget.getEditorState(), data); + this.client.setPointer(data.getId(), 'provenance', implicitOp.getId()); + const operation = this.createNode(this._widget.getOperation(), implicitOp); + this.client.setPointer(implicitOp.getId(), 'operation', operation.getId()); + } + this.client.completeTransaction(); + } + + } + + return InteractiveExplorerControl; +}); diff --git a/src/visualizers/panels/InteractiveExplorer/InteractiveExplorerPanel.js b/src/visualizers/panels/InteractiveExplorer/InteractiveExplorerPanel.js new file mode 100644 index 000000000..4f91b50f7 --- /dev/null +++ b/src/visualizers/panels/InteractiveExplorer/InteractiveExplorerPanel.js @@ -0,0 +1,33 @@ +/*globals define */ + +define([ + 'panels/InteractiveEditor/InteractiveEditorPanel', + 'widgets/InteractiveExplorer/InteractiveExplorerWidget', + './InteractiveExplorerControl', +], function ( + InteractiveEditorPanel, + InteractiveExplorerWidget, + InteractiveExplorerControl, +) { + 'use strict'; + + class InteractiveExplorerPanel extends InteractiveEditorPanel { + + initialize() { + this.setTitle(''); + this.widget = new InteractiveExplorerWidget(this.logger, this.$el); + this.widget.setTitle = title => this.setTitle(title); + + this.control = new InteractiveExplorerControl({ + logger: this.logger, + client: this._client, + embedded: this._embedded, + widget: this.widget + }); + + this.onActivate(); + } + } + + return InteractiveExplorerPanel; +}); diff --git a/src/visualizers/widgets/InteractiveEditor/InteractiveEditorWidget.js b/src/visualizers/widgets/InteractiveEditor/InteractiveEditorWidget.js new file mode 100644 index 000000000..e2fbca2ae --- /dev/null +++ b/src/visualizers/widgets/InteractiveEditor/InteractiveEditorWidget.js @@ -0,0 +1,99 @@ +/* globals define, $ */ +define([ + 'deepforge/compute/interactive/session-with-queue', + 'deepforge/viz/ConfigDialog', + 'deepforge/viz/InformDialog', + 'deepforge/compute/index', + 'deepforge/globals', + 'css!./styles/InteractiveEditorWidget.css', +], function( + Session, + ConfigDialog, + InformDialog, + Compute, + DeepForge, +) { + const COMPUTE_MESSAGE = 'Compute Required. Click to configure.'; + class InteractiveEditorWidget { + constructor(container) { + this.showComputeShield(container); + } + + showComputeShield(container) { + const overlay = $('