From 69023b7b71b6a0898a2a3b82e6523f35ea26cb7e Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Mon, 26 Oct 2020 15:58:43 -0500 Subject: [PATCH 01/13] Add initial commit of EagerOperation --- src/visualizers/Visualizers.json | 8 +++++- .../EagerOperation/EagerOperationControl.js | 15 ++++++++++ .../EagerOperation/EagerOperationPanel.js | 28 +++++++++++++++++++ .../InteractiveEditors.json | 7 +++++ .../EagerOperation/EagerOperationWidget.js | 19 +++++++++++++ .../styles/EagerOperationWidget.css | 10 +++++++ .../styles/EagerOperationWidget.scss | 7 +++++ webgme-setup.json | 7 +++++ 8 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/visualizers/panels/EagerOperation/EagerOperationControl.js create mode 100644 src/visualizers/panels/EagerOperation/EagerOperationPanel.js create mode 100644 src/visualizers/widgets/EagerOperation/EagerOperationWidget.js create mode 100644 src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.css create mode 100644 src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.scss diff --git a/src/visualizers/Visualizers.json b/src/visualizers/Visualizers.json index 490a55a67..62a7a91df 100644 --- a/src/visualizers/Visualizers.json +++ b/src/visualizers/Visualizers.json @@ -172,5 +172,11 @@ "title": "InteractiveWorkspace", "panel": "panels/InteractiveWorkspace/InteractiveWorkspacePanel", "DEBUG_ONLY": false + }, + { + "id": "EagerOperation", + "title": "EagerOperation", + "panel": "panels/EagerOperation/EagerOperationPanel", + "DEBUG_ONLY": false } -] +] \ No newline at end of file diff --git a/src/visualizers/panels/EagerOperation/EagerOperationControl.js b/src/visualizers/panels/EagerOperation/EagerOperationControl.js new file mode 100644 index 000000000..e73b31acf --- /dev/null +++ b/src/visualizers/panels/EagerOperation/EagerOperationControl.js @@ -0,0 +1,15 @@ +/*globals define */ + +define([ + 'panels/InteractiveExplorer/InteractiveExplorerControl', +], function ( + InteractiveExplorerControl, +) { + + 'use strict'; + + class EagerOperationControl extends InteractiveExplorerControl { + } + + return EagerOperationControl; +}); diff --git a/src/visualizers/panels/EagerOperation/EagerOperationPanel.js b/src/visualizers/panels/EagerOperation/EagerOperationPanel.js new file mode 100644 index 000000000..3ad591b6d --- /dev/null +++ b/src/visualizers/panels/EagerOperation/EagerOperationPanel.js @@ -0,0 +1,28 @@ +/*globals define, */ + +define([ + 'panels/InteractiveEditor/InteractiveEditorPanel', + 'widgets/EagerOperation/EagerOperationWidget', + './EagerOperationControl' +], function ( + InteractiveEditorPanel, + EagerOperationWidget, + EagerOperationControl +) { + 'use strict'; + + class EagerOperationPanel extends InteractiveEditorPanel { + constructor(layoutManager, params) { + const config = { + name: 'EagerOperation', + Control: EagerOperationControl, + Widget: EagerOperationWidget, + }; + super(config, params); + + this.logger.debug('ctor finished'); + } + } + + return EagerOperationPanel; +}); diff --git a/src/visualizers/panels/InteractiveWorkspace/InteractiveEditors.json b/src/visualizers/panels/InteractiveWorkspace/InteractiveEditors.json index d07ecca26..a3e32f24a 100644 --- a/src/visualizers/panels/InteractiveWorkspace/InteractiveEditors.json +++ b/src/visualizers/panels/InteractiveWorkspace/InteractiveEditors.json @@ -1,4 +1,11 @@ [ + { + "id": "EagerOperation", + "title": "Operation Creator", + "panel": "panels/EagerOperation/EagerOperationPanel", + "dependencies": [], + "DEBUG_ONLY": false + }, { "id": "TrainKeras", "title": "Neural Network Trainer", diff --git a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js new file mode 100644 index 000000000..fde697e1f --- /dev/null +++ b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js @@ -0,0 +1,19 @@ +/*globals define */ + +define([ + 'widgets/InteractiveEditor/InteractiveEditorWidget', +], function ( + InteractiveEditor, +) { + 'use strict'; + + const WIDGET_CLASS = 'eager-operation'; + class EagerOperationWidget extends InteractiveEditor { + constructor(logger, container) { + container.addClass(WIDGET_CLASS); + super(container); + } + } + + return EagerOperationWidget; +}); diff --git a/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.css b/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.css new file mode 100644 index 000000000..3161debf9 --- /dev/null +++ b/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.css @@ -0,0 +1,10 @@ +/** + * This file is for any css that you may want for this visualizer. + * + * Ideally, you would use the scss file also provided in this directory + * and then generate this file automatically from that. However, you can + * simply write css if you prefer + */ + +.eager-operation { + outline: none; } diff --git a/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.scss b/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.scss new file mode 100644 index 000000000..3fccb6119 --- /dev/null +++ b/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.scss @@ -0,0 +1,7 @@ +/** + * This file is for any scss that you may want for this visualizer. + */ + +.eager-operation { + outline: none; +} diff --git a/webgme-setup.json b/webgme-setup.json index 981698510..bede9c795 100644 --- a/webgme-setup.json +++ b/webgme-setup.json @@ -309,6 +309,13 @@ "panel": "src/visualizers/panels/InteractiveWorkspace", "secondary": false, "widget": "src/visualizers/widgets/InteractiveWorkspace" + }, + "EagerOperation": { + "src": "panels/EagerOperation/EagerOperationPanel", + "title": "EagerOperation", + "panel": "src/visualizers/panels/EagerOperation", + "secondary": false, + "widget": "src/visualizers/widgets/EagerOperation" } }, "addons": {}, From e245df2c3e28b49b5537f2242348eec69c50f33c Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Mon, 26 Oct 2020 22:08:35 -0500 Subject: [PATCH 02/13] add debug comment --- src/visualizers/widgets/EagerOperation/EagerOperationWidget.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js index fde697e1f..f44f809e7 100644 --- a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js +++ b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js @@ -13,6 +13,7 @@ define([ container.addClass(WIDGET_CLASS); super(container); } + // TODO: embed another widget } return EagerOperationWidget; From 988be4f6f0cd5de0ad4c070294f6c8c0f8b77512 Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Wed, 28 Oct 2020 16:13:40 -0500 Subject: [PATCH 03/13] minor css improvements --- .../EagerOperation/styles/EagerOperationWidget.css | 11 ++++------- .../EagerOperation/styles/EagerOperationWidget.scss | 2 ++ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.css b/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.css index 3161debf9..841028599 100644 --- a/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.css +++ b/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.css @@ -1,10 +1,7 @@ /** - * This file is for any css that you may want for this visualizer. - * - * Ideally, you would use the scss file also provided in this directory - * and then generate this file automatically from that. However, you can - * simply write css if you prefer + * This file is for any scss that you may want for this visualizer. */ - .eager-operation { - outline: none; } + outline: none; + padding: 0; + height: 100%; } diff --git a/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.scss b/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.scss index 3fccb6119..4caa16aa0 100644 --- a/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.scss +++ b/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.scss @@ -4,4 +4,6 @@ .eager-operation { outline: none; + padding: 0; + height: 100%; } From 880e1b2910806963fec2ce5d5f745368d25801a5 Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Mon, 2 Nov 2020 11:43:08 -0600 Subject: [PATCH 04/13] Add tabs, run action, initial operation --- .../EagerOperation/EagerOperationControl.js | 37 +++++ .../EagerOperation/EagerOperationWidget.js | 128 +++++++++++++++++- .../styles/EagerOperationWidget.css | 6 +- .../styles/EagerOperationWidget.scss | 5 + 4 files changed, 173 insertions(+), 3 deletions(-) diff --git a/src/visualizers/panels/EagerOperation/EagerOperationControl.js b/src/visualizers/panels/EagerOperation/EagerOperationControl.js index e73b31acf..92ef71b16 100644 --- a/src/visualizers/panels/EagerOperation/EagerOperationControl.js +++ b/src/visualizers/panels/EagerOperation/EagerOperationControl.js @@ -2,13 +2,50 @@ define([ 'panels/InteractiveExplorer/InteractiveExplorerControl', + 'underscore', + 'text!deepforge/NewOperationCode.ejs', ], function ( InteractiveExplorerControl, + _, + NewOperationCodeTxt, ) { 'use strict'; + const GetOperationCode = _.template(NewOperationCodeTxt); class EagerOperationControl extends InteractiveExplorerControl { + + constructor() { + super(...arguments); + const operation = this.getInitialOperation(); + this._widget.setOperation(operation); + } + + getInitialOperation() { + const basename = 'NewOperation'; + let name = basename; + let i = '2'; + const metanodes = Object.values(this.client.getAllMetaNodes()); + while (metanodes.find(node => node.getAttribute('name') === name)) { + name = name + i++; + } + + const code = GetOperationCode({name}); + + return { + name: name, + inputs: [], + outputs: [], + references: [], + code, + env: '' + }; + } + + async onComputeInitialized(session) { + await super.onComputeInitialized(session); + this._widget.registerActions(); + } } return EagerOperationControl; diff --git a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js index f44f809e7..858f07e31 100644 --- a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js +++ b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js @@ -1,9 +1,20 @@ -/*globals define */ +/*globals define, $, WebGMEGlobal*/ define([ + 'deepforge/globals', 'widgets/InteractiveEditor/InteractiveEditorWidget', + 'widgets/OperationCodeEditor/OperationCodeEditorWidget', + 'widgets/TabbedTextEditor/TabbedTextEditorWidget', + 'widgets/TextEditor/TextEditorWidget', + 'widgets/OperationInterfaceEditor/OperationInterfaceEditorWidget', + 'css!./styles/EagerOperationWidget.css', ], function ( + DeepForge, InteractiveEditor, + OperationCodeEditor, + TabbedTextEditorWidget, + TextEditorWidget, + OperationInterfaceEditorWidget, ) { 'use strict'; @@ -12,8 +23,121 @@ define([ constructor(logger, container) { container.addClass(WIDGET_CLASS); super(container); + this.width = 0; + this.height = 0; + + const $leftPane = $('
', {class: 'pane'}); + container.append($leftPane); + this.codeEditor = new OperationCodeEditor(logger, $leftPane); + + const $rightPane = $('
', {class: 'pane'}); + container.append($rightPane); + this.secondaryEditor = this.initializeSecondaryEditor(logger, $rightPane); + } + + initializeSecondaryEditor(logger, $el) { + const config = { + canCreateTabs: false, + message: { + new: '', + empty: '', + rename: '', + }, + }; + const widget = new TabbedTextEditorWidget(logger, $el, config); + this.tabs = []; + + function newTab(name, $el, editor) { + return { + id: name, + name: name, + supportedActions: { + delete: false, + rename: false, + }, + editor, + $el, + }; + } + $el = $('
'); + let editor = new OperationInterfaceEditorWidget(logger, $el); + this.tabs.push(newTab('Operation Interface', $el, editor)); + + $el = $('
'); + editor = new TextEditorWidget( + logger, + $el, + {language: 'yaml', displayMiniMap: false} + ); + this.tabs.push(newTab('Environment', $el, editor)); + + $el = $('
'); + editor = new TextEditorWidget( + logger, + $el, + {language: 'plaintext'} + ); + this.tabs.push(newTab('Console', $el, editor)); + + widget.onTabSelected = id => { + const tab = this.tabs.find(tab => tab.id === id); + this.onTabSelected(widget, tab); + }; + this.tabs.forEach(tab => widget.addTab(tab)); + + return widget; + } + + setOperation(operation) { + this.codeEditor.addNode({ + name: operation.name, + text: operation.code, + }); + const [interfaceTab, envTab] = this.tabs; + // TODO: update the interface editor + + envTab.editor.addNode({name: operation.name, text: operation.env}); + } + + registerActions() { + // TODO: use the operation name + DeepForge.registerAction('Run operation', 'play_arrow', 10, () => this.runOperation()); + } + + runOperation() { + console.log('running operation!'); // TODO + } + // TODO: add interface editor + // TODO: - add save button to outputs + // TODO: - add input operations (and outputs?) + // TODO: add conda environment editor + // TODO: add console output tab + // TODO: add graphical output (controller?) + // TODO: add "save" button for the operation definition + + onTabSelected(widget, tab) { + widget.$tabContent.empty(); + widget.$tabContent.append(tab.$el); + const isDisplayed = this.width && this.height; + if (isDisplayed) { + this.onWidgetContainerResize(this.width, this.height); + } + } + + onActivate() { + this.codeEditor.onActivate(); + this.secondaryEditor.onActivate(); + } + + onWidgetContainerResize(width, height) { + this.width = width; + this.height = height; + } + + destroy() { + super.destroy(); + DeepForge.unregisterAction('Run operation', 'play_arrow', 10, () => this.runOperation()); } - // TODO: embed another widget } return EagerOperationWidget; diff --git a/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.css b/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.css index 841028599..c23f1cb7f 100644 --- a/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.css +++ b/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.css @@ -4,4 +4,8 @@ .eager-operation { outline: none; padding: 0; - height: 100%; } + height: 100%; + display: flex; } + .eager-operation .pane { + height: 100%; + width: 50%; } diff --git a/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.scss b/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.scss index 4caa16aa0..b2f7fc433 100644 --- a/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.scss +++ b/src/visualizers/widgets/EagerOperation/styles/EagerOperationWidget.scss @@ -6,4 +6,9 @@ outline: none; padding: 0; height: 100%; + display: flex; + .pane { + height: 100%; + width: 50%; + } } From 1353bc79edb51cd5da336b79dc6a992d761db5d3 Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Tue, 3 Nov 2020 15:53:14 -0600 Subject: [PATCH 05/13] Ensure tabs are activated. Add run operation button --- .../EagerOperation/EagerOperationControl.js | 10 +++++ .../EagerOperation/EagerOperationWidget.js | 41 +++++++++++++++---- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/visualizers/panels/EagerOperation/EagerOperationControl.js b/src/visualizers/panels/EagerOperation/EagerOperationControl.js index 92ef71b16..2c1c320c5 100644 --- a/src/visualizers/panels/EagerOperation/EagerOperationControl.js +++ b/src/visualizers/panels/EagerOperation/EagerOperationControl.js @@ -21,6 +21,15 @@ define([ this._widget.setOperation(operation); } + initializeWidgetHandlers (widget) { + super.initializeWidgetHandlers(widget); + widget.runOperation = operation => this.runOperation(operation); + } + + runOperation(operation) { + // TODO: + } + getInitialOperation() { const basename = 'NewOperation'; let name = basename; @@ -34,6 +43,7 @@ define([ return { name: name, + attributes: {}, inputs: [], outputs: [], references: [], diff --git a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js index 858f07e31..b97c03f02 100644 --- a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js +++ b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js @@ -2,6 +2,7 @@ define([ 'deepforge/globals', + 'deepforge/Constants', 'widgets/InteractiveEditor/InteractiveEditorWidget', 'widgets/OperationCodeEditor/OperationCodeEditorWidget', 'widgets/TabbedTextEditor/TabbedTextEditorWidget', @@ -10,6 +11,7 @@ define([ 'css!./styles/EagerOperationWidget.css', ], function ( DeepForge, + Constants, InteractiveEditor, OperationCodeEditor, TabbedTextEditorWidget, @@ -29,10 +31,12 @@ define([ const $leftPane = $('
', {class: 'pane'}); container.append($leftPane); this.codeEditor = new OperationCodeEditor(logger, $leftPane); + window.codeEditor = this.codeEditor; const $rightPane = $('
', {class: 'pane'}); container.append($rightPane); this.secondaryEditor = this.initializeSecondaryEditor(logger, $rightPane); + this.tabs.forEach(tab => this.secondaryEditor.addTab(tab)); } initializeSecondaryEditor(logger, $el) { @@ -77,13 +81,13 @@ define([ $el, {language: 'plaintext'} ); + editor.setReadOnly(true); this.tabs.push(newTab('Console', $el, editor)); widget.onTabSelected = id => { const tab = this.tabs.find(tab => tab.id === id); this.onTabSelected(widget, tab); }; - this.tabs.forEach(tab => widget.addTab(tab)); return widget; } @@ -94,19 +98,36 @@ define([ text: operation.code, }); const [interfaceTab, envTab] = this.tabs; - // TODO: update the interface editor + const interfaceNodes = this.getOperationInterfaceNodes(operation); + console.log('about to add', interfaceNodes); + interfaceNodes.forEach(node => interfaceTab.editor.addNode(node)); // FIXME: this is overly simplistic... envTab.editor.addNode({name: operation.name, text: operation.env}); } + getOperationInterfaceNodes(operation) { + //const displayColor = desc.attributes[CONSTANTS.OPERATION.DISPLAY_COLOR]; + //desc.displayColor = displayColor && displayColor.value; + // TODO: Get the attributes and such + // TODO: create the interface nodes + console.log(operation); + const Decorator = WebGMEGlobal.Client.decoratorManager.getDecoratorForWidget('OpIntDecorator', 'EasyDAG'); + const centralNode = operation; + centralNode.Decorator = Decorator; + //const + return [centralNode]; + } + registerActions() { // TODO: use the operation name - DeepForge.registerAction('Run operation', 'play_arrow', 10, () => this.runOperation()); + DeepForge.registerAction('Run operation', 'play_arrow', 10, () => this.onRunClicked()); } - runOperation() { - console.log('running operation!'); // TODO + onRunClicked() { + // TODO: get the operation info + this.runOperation(this.operation); } + // TODO: add interface editor // TODO: - add save button to outputs // TODO: - add input operations (and outputs?) @@ -118,20 +139,22 @@ define([ onTabSelected(widget, tab) { widget.$tabContent.empty(); widget.$tabContent.append(tab.$el); - const isDisplayed = this.width && this.height; - if (isDisplayed) { - this.onWidgetContainerResize(this.width, this.height); - } + this.onWidgetContainerResize(this.width, this.height); + //tab.editor.onWidgetContainerResize(); } onActivate() { this.codeEditor.onActivate(); this.secondaryEditor.onActivate(); + this.tabs.forEach(tab => tab.editor.onActivate()); } onWidgetContainerResize(width, height) { this.width = width; this.height = height; + // TODO: get the right width/heights + this.codeEditor.onWidgetContainerResize(); + this.secondaryEditor.onWidgetContainerResize(); } destroy() { From 52639fdee3686b8576bbcde6a998948c1be9f297 Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Tue, 3 Nov 2020 16:25:06 -0600 Subject: [PATCH 06/13] Add references --- .../panels/EagerOperation/EagerOperationControl.js | 14 ++++++++++++++ .../widgets/EagerOperation/EagerOperationWidget.js | 2 ++ 2 files changed, 16 insertions(+) diff --git a/src/visualizers/panels/EagerOperation/EagerOperationControl.js b/src/visualizers/panels/EagerOperation/EagerOperationControl.js index 2c1c320c5..af6571390 100644 --- a/src/visualizers/panels/EagerOperation/EagerOperationControl.js +++ b/src/visualizers/panels/EagerOperation/EagerOperationControl.js @@ -2,10 +2,12 @@ define([ 'panels/InteractiveExplorer/InteractiveExplorerControl', + 'panels/OperationInterfaceEditor/OperationInterfaceEditorControl', 'underscore', 'text!deepforge/NewOperationCode.ejs', ], function ( InteractiveExplorerControl, + OperationInterfaceControl, _, NewOperationCodeTxt, ) { @@ -17,6 +19,7 @@ define([ constructor() { super(...arguments); + this._client = this.client; const operation = this.getInitialOperation(); this._widget.setOperation(operation); } @@ -24,6 +27,17 @@ define([ initializeWidgetHandlers (widget) { super.initializeWidgetHandlers(widget); widget.runOperation = operation => this.runOperation(operation); + widget.operationInterface.allValidReferences = () => this.allValidReferences(); + } + + getResourcesNodeTypes() { + return OperationInterfaceControl.prototype.getResourcesNodeTypes.call(this); + } + + allValidReferences() { + return this.getResourcesNodeTypes().map(node => ({ + node: OperationInterfaceControl.prototype._getObjectDescriptor.call(this, node.getId()) + })); } runOperation(operation) { diff --git a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js index b97c03f02..0d5c581fa 100644 --- a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js +++ b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js @@ -66,6 +66,8 @@ define([ $el = $('
'); let editor = new OperationInterfaceEditorWidget(logger, $el); this.tabs.push(newTab('Operation Interface', $el, editor)); + editor.isValidTerminalNode = () => true; + this.operationInterface = editor; $el = $('
'); editor = new TextEditorWidget( From 025bbbbe404681c303d79bbde9282dbcb910a42e Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Tue, 3 Nov 2020 16:28:10 -0600 Subject: [PATCH 07/13] resize tab on open --- src/visualizers/widgets/EagerOperation/EagerOperationWidget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js index 0d5c581fa..9ecfbafc5 100644 --- a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js +++ b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js @@ -142,7 +142,7 @@ define([ widget.$tabContent.empty(); widget.$tabContent.append(tab.$el); this.onWidgetContainerResize(this.width, this.height); - //tab.editor.onWidgetContainerResize(); + tab.editor.onWidgetContainerResize(); } onActivate() { From 968ead73ff5664293d3fad81e0716965b74bcf61 Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Tue, 3 Nov 2020 17:06:25 -0600 Subject: [PATCH 08/13] Add some support for adding references --- .../EagerOperation/EagerOperationControl.js | 107 ++++++++++++++++-- 1 file changed, 95 insertions(+), 12 deletions(-) diff --git a/src/visualizers/panels/EagerOperation/EagerOperationControl.js b/src/visualizers/panels/EagerOperation/EagerOperationControl.js index af6571390..24140e240 100644 --- a/src/visualizers/panels/EagerOperation/EagerOperationControl.js +++ b/src/visualizers/panels/EagerOperation/EagerOperationControl.js @@ -14,30 +14,27 @@ define([ 'use strict'; + let counter = (function() { + let c = 1; + return () => c++; + })(); const GetOperationCode = _.template(NewOperationCodeTxt); class EagerOperationControl extends InteractiveExplorerControl { constructor() { super(...arguments); this._client = this.client; - const operation = this.getInitialOperation(); - this._widget.setOperation(operation); + this.operation = this.getInitialOperation(); + this._widget.setOperation(this.operation); + this.DEFAULT_DECORATOR = 'EllipseDecorator'; } initializeWidgetHandlers (widget) { super.initializeWidgetHandlers(widget); widget.runOperation = operation => this.runOperation(operation); widget.operationInterface.allValidReferences = () => this.allValidReferences(); - } - - getResourcesNodeTypes() { - return OperationInterfaceControl.prototype.getResourcesNodeTypes.call(this); - } - - allValidReferences() { - return this.getResourcesNodeTypes().map(node => ({ - node: OperationInterfaceControl.prototype._getObjectDescriptor.call(this, node.getId()) - })); + widget.operationInterface.addRefTo = this.addRefTo.bind(this); + widget.operationInterface.removePtr = this.removePtr.bind(this); } runOperation(operation) { @@ -56,7 +53,9 @@ define([ const code = GetOperationCode({name}); return { + id: `operation_${counter()}`, name: name, + baseName: 'Operation', attributes: {}, inputs: [], outputs: [], @@ -70,6 +69,90 @@ define([ await super.onComputeInitialized(session); this._widget.registerActions(); } + + // Operation interface functions + getResourcesNodeTypes() { + return OperationInterfaceControl.prototype.getResourcesNodeTypes.call(this); + } + + allValidReferences() { + return this.getResourcesNodeTypes().map(node => ({ + node: OperationInterfaceControl.prototype._getObjectDescriptor.call(this, node.getId()) + })); + } + + _getNodeDecorator() { // FIXME: this shouldn't be here... A bit of a code smell + return OperationInterfaceControl.prototype._getNodeDecorator.call(this, ...arguments); + } + + containedInCurrent() { // FIXME: this shouldn't be here... A bit of a code smell + return OperationInterfaceControl.prototype.containedInCurrent.call(this, ...arguments); + } + + hasMetaName() { // FIXME: this shouldn't be here... A bit of a code smell + return OperationInterfaceControl.prototype.hasMetaName.call(this, ...arguments); + } + + addRefTo(refId) { + const node = this.client.getNode(refId); + const nodeName = node.getAttribute('name'); + const name = uniqueName( + nodeName, + this.operation.references.map(ref => ref.name) + ); + const desc = { + baseName: nodeName, + name: name.toLowerCase(), + Decorator: this._getNodeDecorator(node), + id: `ptr_${nodeName}_${counter()}`, + isPointer: true, + attributes: {}, + isUnknown: false, + connId: `conn_${counter()}`, + }; + this.operation.references.push(desc); + this._widget.operationInterface.addNode(desc); + this.createConnection(desc); + } + + createConnection(desc) { + const conn = {}; + conn.id = desc.connId; + + if (desc.container === 'outputs') { + conn.src = this.operation.id; + conn.dst = desc.id; + } else { + conn.src = desc.id; + conn.dst = this.operation.id; + } + this._widget.operationInterface.addConnection(conn); + + return conn; + } + + removePtr(name) { + const index = this.operation.references.findIndex(ref => ref.name === name); + if (index > -1) { + const [ptr] = this.operation.references.splice(index, 1); + this._widget.operationInterface.removeNode(ptr.id); + + // and connection + this._widget.operationInterface.removeNode(ptr.connId); + + } else { + throw new Error(`Could not find reference: ${name}`); + } + } + } + + function uniqueName(basename, names) { + let counter = 1; + let name = basename; + while (names.includes(name)) { + name = `${name}_${counter++}`; + } + return name; } return EagerOperationControl; From 2b3fc16e28432b01e9d5110caad3a7b9d419aa12 Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Wed, 4 Nov 2020 10:35:03 -0600 Subject: [PATCH 09/13] Add support for adding/deleting inputs/outputs/refs --- .../EagerOperation/EagerOperationControl.js | 184 +++++++++++++++--- .../EagerOperation/EagerOperationWidget.js | 2 - 2 files changed, 161 insertions(+), 25 deletions(-) diff --git a/src/visualizers/panels/EagerOperation/EagerOperationControl.js b/src/visualizers/panels/EagerOperation/EagerOperationControl.js index 24140e240..45bf8346b 100644 --- a/src/visualizers/panels/EagerOperation/EagerOperationControl.js +++ b/src/visualizers/panels/EagerOperation/EagerOperationControl.js @@ -3,11 +3,15 @@ define([ 'panels/InteractiveExplorer/InteractiveExplorerControl', 'panels/OperationInterfaceEditor/OperationInterfaceEditorControl', + 'deepforge/viz/OperationControl', + 'panels/EasyDAG/EasyDAGControl', 'underscore', 'text!deepforge/NewOperationCode.ejs', ], function ( InteractiveExplorerControl, OperationInterfaceControl, + OperationControl, + EasyDAGControl, _, NewOperationCodeTxt, ) { @@ -26,7 +30,7 @@ define([ this._client = this.client; this.operation = this.getInitialOperation(); this._widget.setOperation(this.operation); - this.DEFAULT_DECORATOR = 'EllipseDecorator'; + this.DEFAULT_DECORATOR = 'OpIntDecorator'; } initializeWidgetHandlers (widget) { @@ -35,6 +39,11 @@ define([ widget.operationInterface.allValidReferences = () => this.allValidReferences(); widget.operationInterface.addRefTo = this.addRefTo.bind(this); widget.operationInterface.removePtr = this.removePtr.bind(this); + widget.operationInterface.getValidSuccessors = this.getValidSuccessors.bind(this); + widget.operationInterface.createConnectedNode = this.createConnectedNode.bind(this); + widget.operationInterface.deleteNode = this.deleteNode.bind(this); + widget.operationInterface.saveAttributeForNode = this.saveAttributeForNode.bind(this); + widget.operationInterface.getValidAttributeNames = this.getValidAttributeNames.bind(this); } runOperation(operation) { @@ -70,6 +79,14 @@ define([ this._widget.registerActions(); } + onOperationInterfaceUpdate() { + // TODO: Update the + } + + setOperationCode(newCode) { + // TODO: Update the operation inputs, outputs, etc + } + // Operation interface functions getResourcesNodeTypes() { return OperationInterfaceControl.prototype.getResourcesNodeTypes.call(this); @@ -93,6 +110,18 @@ define([ return OperationInterfaceControl.prototype.hasMetaName.call(this, ...arguments); } + getDescColor() { // FIXME: this shouldn't be here... A bit of a code smell + return OperationInterfaceControl.prototype.getDescColor.call(this, ...arguments); + } + + isUsedInput() { // FIXME: this shouldn't be here... A bit of a code smell + return true; + } + + isUsedOutput() { // FIXME: this shouldn't be here... A bit of a code smell + return true; + } + addRefTo(refId) { const node = this.client.getNode(refId); const nodeName = node.getAttribute('name'); @@ -100,35 +129,25 @@ define([ nodeName, this.operation.references.map(ref => ref.name) ); + const id = `ptr_${nodeName}_${counter()}`; const desc = { baseName: nodeName, name: name.toLowerCase(), Decorator: this._getNodeDecorator(node), - id: `ptr_${nodeName}_${counter()}`, + id: id, isPointer: true, attributes: {}, isUnknown: false, - connId: `conn_${counter()}`, + conn: { + id: `conn_${counter()}`, + src: id, + dst: this.operation.id, + } }; this.operation.references.push(desc); this._widget.operationInterface.addNode(desc); - this.createConnection(desc); - } - - createConnection(desc) { - const conn = {}; - conn.id = desc.connId; - - if (desc.container === 'outputs') { - conn.src = this.operation.id; - conn.dst = desc.id; - } else { - conn.src = desc.id; - conn.dst = this.operation.id; - } - this._widget.operationInterface.addConnection(conn); - - return conn; + this._widget.operationInterface.addConnection(desc.conn); + this.onOperationInterfaceUpdate(); } removePtr(name) { @@ -136,14 +155,131 @@ define([ if (index > -1) { const [ptr] = this.operation.references.splice(index, 1); this._widget.operationInterface.removeNode(ptr.id); + this._widget.operationInterface.removeNode(ptr.conn.id); + this.onOperationInterfaceUpdate(); + } else { + throw new Error(`Could not find reference: ${name}`); + } + } + + getValidSuccessors(id) { + if (id !== this.operation.id) { + return []; + } + + const nodeId = this.getDataTypeId(); + return [{ + node: this._getObjectDescriptor(nodeId) + }]; + } + + _getObjectDescriptor(gmeId) { + const desc = EasyDAGControl.prototype._getObjectDescriptor.call(this, gmeId); + if (desc.id !== this._currentNodeId && this.containedInCurrent(gmeId)) { + var cntrType = this._client.getNode(desc.parentId).getMetaTypeId(); + var cntr = this._client.getNode(cntrType).getAttribute('name'); + + desc.container = cntr.toLowerCase(); + desc.isInput = desc.container === 'inputs'; + desc.attributes = {}; + desc.pointers = {}; - // and connection - this._widget.operationInterface.removeNode(ptr.connId); + } else if (desc.id === this._currentNodeId) { + desc.pointers = {}; + // Remove DeepForge hidden attributes + const displayColor = desc.attributes[CONSTANTS.OPERATION.DISPLAY_COLOR]; + desc.displayColor = displayColor && displayColor.value; + + CONSTANTS.OPERATION.RESERVED_ATTRS + .filter(attrName => attrName !== 'name') + .forEach(name => delete desc.attributes[name]); + } + + // Extra decoration for data + if (this.hasMetaName(desc.id, 'Data', true)) { + desc.used = true; + desc.color = this.getDescColor(gmeId); + } + return desc; + } + + createConnectedNode(typeId, isInput) { + const node = this.client.getNode(typeId); + const nodes = isInput ? this.operation.inputs : this.operation.outputs; + const name = uniqueName( + 'data', + nodes.map(d => d.name) + ); + const id = `data_${counter()}`; + const dataDesc = { + id, + name, + Decorator: this._getNodeDecorator(node), + attributes: {}, + pointers: {}, + baseName: 'Data', + container: isInput ? 'inputs' : 'outputs', + isConnection: false, + conn: { + id: `conn_${counter()}`, + src: null, + dst: null, + } + }; + if (isInput) { + dataDesc.conn.src = id; + dataDesc.conn.dst = this.operation.id; + this.operation.inputs.push(dataDesc); } else { - throw new Error(`Could not find reference: ${name}`); + dataDesc.conn.src = this.operation.id; + dataDesc.conn.dst = id; + this.operation.outputs.push(dataDesc); + } + // FIXME: move this to the widget? + this._widget.operationInterface.addNode(dataDesc); + this._widget.operationInterface.addConnection(dataDesc.conn); + this.onOperationInterfaceUpdate(); + return dataDesc.id; + } + + deleteNode(id) { + const nodes = this.operation.inputs.find(desc => desc.id === id) ? + this.operation.inputs : this.operation.outputs; + const index = nodes.findIndex(desc => desc.id === id); + if (index > -1) { + const [desc] = nodes.splice(index, 1); + this._widget.operationInterface.removeNode(desc.id); + this._widget.operationInterface.removeNode(desc.conn.id); + this.onOperationInterfaceUpdate(); + } else { + throw new Error(`Could not find input/output node: ${id}`); } } + + saveAttributeForNode(id, attr, value) { + const desc = _.clone([ + ...this.operation.inputs, + ...this.operation.outputs, + ...this.operation.references, + this.operation + ].find(desc => desc.id === id)); + if (attr === 'name') { + desc.name = value; + } + + desc.attributes[attr] = value; + this._widget.operationInterface.updateNode(desc); + this.onOperationInterfaceUpdate(); + } + + getValidAttributeNames() { + console.log('getValidAttributeNames', arguments); + } + } + + class InMemoryOperationInterfaceControl { + // TODO: Use this? } function uniqueName(basename, names) { @@ -155,5 +291,7 @@ define([ return name; } + _.extend(EagerOperationControl.prototype, OperationControl.prototype); + return EagerOperationControl; }); diff --git a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js index 9ecfbafc5..b836b79e6 100644 --- a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js +++ b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js @@ -112,11 +112,9 @@ define([ //desc.displayColor = displayColor && displayColor.value; // TODO: Get the attributes and such // TODO: create the interface nodes - console.log(operation); const Decorator = WebGMEGlobal.Client.decoratorManager.getDecoratorForWidget('OpIntDecorator', 'EasyDAG'); const centralNode = operation; centralNode.Decorator = Decorator; - //const return [centralNode]; } From 3312216db261af92d494b743a7d69a3fe55195de Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Wed, 4 Nov 2020 11:47:30 -0600 Subject: [PATCH 10/13] Update code on interface edit --- src/common/viz/OperationControl.js | 18 ++- .../EagerOperation/EagerOperationControl.js | 122 ++++++++++++++---- 2 files changed, 108 insertions(+), 32 deletions(-) diff --git a/src/common/viz/OperationControl.js b/src/common/viz/OperationControl.js index d738ee9ee..b439d1c0b 100644 --- a/src/common/viz/OperationControl.js +++ b/src/common/viz/OperationControl.js @@ -212,21 +212,25 @@ define([ OperationControl.prototype.updateCode = function(fn, nodeId) { const node = this._client.getNode(nodeId || this._currentNodeId); const code = node.getAttribute('code'); - const operation = OperationCode.findOperation(code); - const opCode = operation.getCode(); - const offset = code.indexOf(opCode); - const preCode = code.substring(0, offset); - const postCode = code.substring(offset+opCode.length); try { - fn(operation); - const entireCode = preCode + operation.getCode() + postCode; + const entireCode = this.getUpdatedCode(code, fn); this._client.setAttribute(nodeId || this._currentNodeId, 'code', entireCode); } catch(e) { this.logger.debug(`could not update the code - invalid python!: ${e}`); } }; + OperationControl.prototype.getUpdatedCode = function(code, fn) { + const operation = OperationCode.findOperation(code); + const opCode = operation.getCode(); + const offset = code.indexOf(opCode); + const preCode = code.substring(0, offset); + const postCode = code.substring(offset+opCode.length); + fn(operation); + return preCode + operation.getCode() + postCode; + }; + OperationControl.prototype.getInputNodes = function(nodeId) { nodeId = nodeId || this._currentNodeId; var node = this._client.getNode(nodeId); diff --git a/src/visualizers/panels/EagerOperation/EagerOperationControl.js b/src/visualizers/panels/EagerOperation/EagerOperationControl.js index 45bf8346b..3c2a513b1 100644 --- a/src/visualizers/panels/EagerOperation/EagerOperationControl.js +++ b/src/visualizers/panels/EagerOperation/EagerOperationControl.js @@ -4,6 +4,7 @@ define([ 'panels/InteractiveExplorer/InteractiveExplorerControl', 'panels/OperationInterfaceEditor/OperationInterfaceEditorControl', 'deepforge/viz/OperationControl', + 'deepforge/OperationCode', 'panels/EasyDAG/EasyDAGControl', 'underscore', 'text!deepforge/NewOperationCode.ejs', @@ -11,6 +12,7 @@ define([ InteractiveExplorerControl, OperationInterfaceControl, OperationControl, + OperationCode, EasyDAGControl, _, NewOperationCodeTxt, @@ -44,6 +46,8 @@ define([ widget.operationInterface.deleteNode = this.deleteNode.bind(this); widget.operationInterface.saveAttributeForNode = this.saveAttributeForNode.bind(this); widget.operationInterface.getValidAttributeNames = this.getValidAttributeNames.bind(this); + widget.operationInterface.setAttributeMeta = this.setAttributeMeta.bind(this); + widget.operationInterface.deleteAttribute = this.deleteAttribute.bind(this); } runOperation(operation) { @@ -66,6 +70,7 @@ define([ name: name, baseName: 'Operation', attributes: {}, + attribute_meta: {}, inputs: [], outputs: [], references: [], @@ -79,8 +84,16 @@ define([ this._widget.registerActions(); } - onOperationInterfaceUpdate() { - // TODO: Update the + updateCode(fn) { + this.operation.code = this.getUpdatedCode( + this.operation.code, + fn + ); + // TODO: update the code + this._widget.codeEditor.addNode({ + name: this.operation.name, + text: this.operation.code, + }); } setOperationCode(newCode) { @@ -126,17 +139,18 @@ define([ const node = this.client.getNode(refId); const nodeName = node.getAttribute('name'); const name = uniqueName( - nodeName, + nodeName.toLowerCase(), this.operation.references.map(ref => ref.name) ); const id = `ptr_${nodeName}_${counter()}`; const desc = { baseName: nodeName, - name: name.toLowerCase(), + name: name, Decorator: this._getNodeDecorator(node), id: id, isPointer: true, attributes: {}, + attribute_meta: {}, isUnknown: false, conn: { id: `conn_${counter()}`, @@ -145,9 +159,9 @@ define([ } }; this.operation.references.push(desc); - this._widget.operationInterface.addNode(desc); + this.addInterfaceNode(desc); this._widget.operationInterface.addConnection(desc.conn); - this.onOperationInterfaceUpdate(); + this.updateCode(operation => operation.addReference(name)); } removePtr(name) { @@ -156,7 +170,7 @@ define([ const [ptr] = this.operation.references.splice(index, 1); this._widget.operationInterface.removeNode(ptr.id); this._widget.operationInterface.removeNode(ptr.conn.id); - this.onOperationInterfaceUpdate(); + this.updateCode(operation => operation.removeReference(name)); } else { throw new Error(`Could not find reference: ${name}`); } @@ -217,6 +231,7 @@ define([ name, Decorator: this._getNodeDecorator(node), attributes: {}, + attribute_meta: {}, pointers: {}, baseName: 'Data', container: isInput ? 'inputs' : 'outputs', @@ -231,50 +246,100 @@ define([ dataDesc.conn.src = id; dataDesc.conn.dst = this.operation.id; this.operation.inputs.push(dataDesc); + this.updateCode(operation => operation.addInput(name)); } else { dataDesc.conn.src = this.operation.id; dataDesc.conn.dst = id; this.operation.outputs.push(dataDesc); + this.updateCode(operation => operation.addOutput(name)); } - // FIXME: move this to the widget? - this._widget.operationInterface.addNode(dataDesc); + this.addInterfaceNode(dataDesc); this._widget.operationInterface.addConnection(dataDesc.conn); - this.onOperationInterfaceUpdate(); return dataDesc.id; } deleteNode(id) { - const nodes = this.operation.inputs.find(desc => desc.id === id) ? - this.operation.inputs : this.operation.outputs; + const isInput = this.operation.inputs.find(desc => desc.id === id); + const nodes = isInput ? this.operation.inputs : + this.operation.outputs; const index = nodes.findIndex(desc => desc.id === id); if (index > -1) { const [desc] = nodes.splice(index, 1); this._widget.operationInterface.removeNode(desc.id); this._widget.operationInterface.removeNode(desc.conn.id); - this.onOperationInterfaceUpdate(); + if (isInput) { + this.updateCode(operation => operation.removeInput(desc.name)); + } else { + this.updateCode(operation => operation.removeOutput(desc.name)); + } } else { throw new Error(`Could not find input/output node: ${id}`); } } saveAttributeForNode(id, attr, value) { - const desc = _.clone([ - ...this.operation.inputs, - ...this.operation.outputs, - ...this.operation.references, - this.operation - ].find(desc => desc.id === id)); + const desc = this.getDesc(id); if (attr === 'name') { + const isEditingOperation = id === this.operation.id; + const isRenamingRef = this.operation.references.includes(desc); + if (isEditingOperation) { + this.updateCode(operation => operation.setName(value)); + } else if (isRenamingRef) { + this.updateCode(operation => + operation.renameIn(OperationCode.CTOR_FN, desc.name, value)); + } else { + this.updateCode(operation => operation.rename(desc.name, value)); + } desc.name = value; + } else { + desc.attributes[attr].value = value; + this.updateCode(operation => operation.setAttributeDefault(attr, value)); } - desc.attributes[attr] = value; - this._widget.operationInterface.updateNode(desc); - this.onOperationInterfaceUpdate(); + this.updateInterfaceNode(desc); + } + + getValidAttributeNames(id) { + const desc = this.getDesc(id); + return Object.keys(desc.attribute_meta); + } + + setAttributeMeta(id, _name, schema) { + const {name} = schema; + const desc = this.getDesc(id); + desc.attribute_meta[name] = schema; + desc.attributes[name] = { + name, + type: schema.type, + values: schema.enumValues, + value: schema.defaultValue, + }; + this.updateInterfaceNode(desc); } - getValidAttributeNames() { - console.log('getValidAttributeNames', arguments); + deleteAttribute(id, name) { + const desc = this.getDesc(id); + delete desc.attribute_meta[name]; + delete desc.attributes[name]; + this.updateInterfaceNode(desc); + } + + addInterfaceNode(desc) { + this._widget.operationInterface.addNode(deepCopy(desc)); + } + + updateInterfaceNode(desc) { + this._widget.operationInterface.updateNode(deepCopy(desc)); + } + + getDesc(id) { + const desc = [ + ...this.operation.inputs, + ...this.operation.outputs, + ...this.operation.references, + this.operation + ].find(desc => desc.id === id); + return desc; } } @@ -291,7 +356,14 @@ define([ return name; } - _.extend(EagerOperationControl.prototype, OperationControl.prototype); + function deepCopy(data) { + if (typeof data !== 'object') { + return data; + } + return _.mapObject(data, deepCopy); + } + + _.extend(EagerOperationControl.prototype, _.omit(OperationControl.prototype, 'updateCode')); return EagerOperationControl; }); From 950aaad257f26a34d39cecf1abb251e526b418f4 Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Wed, 4 Nov 2020 15:29:36 -0600 Subject: [PATCH 11/13] Add code-sync support --- .../EagerOperation/EagerOperationControl.js | 145 ++++++++++++++++-- .../EagerOperation/EagerOperationWidget.js | 3 +- 2 files changed, 131 insertions(+), 17 deletions(-) diff --git a/src/visualizers/panels/EagerOperation/EagerOperationControl.js b/src/visualizers/panels/EagerOperation/EagerOperationControl.js index 3c2a513b1..a19c25e8e 100644 --- a/src/visualizers/panels/EagerOperation/EagerOperationControl.js +++ b/src/visualizers/panels/EagerOperation/EagerOperationControl.js @@ -48,6 +48,7 @@ define([ widget.operationInterface.getValidAttributeNames = this.getValidAttributeNames.bind(this); widget.operationInterface.setAttributeMeta = this.setAttributeMeta.bind(this); widget.operationInterface.deleteAttribute = this.deleteAttribute.bind(this); + widget.codeEditor.saveTextFor = this.saveTextFor.bind(this); } runOperation(operation) { @@ -91,6 +92,7 @@ define([ ); // TODO: update the code this._widget.codeEditor.addNode({ + id: this.operation.id, name: this.operation.name, text: this.operation.code, }); @@ -165,12 +167,16 @@ define([ } removePtr(name) { + this.removeInterfaceReference(name); + this.updateCode(operation => operation.removeReference(name)); + } + + removeInterfaceReference(name) { const index = this.operation.references.findIndex(ref => ref.name === name); if (index > -1) { const [ptr] = this.operation.references.splice(index, 1); this._widget.operationInterface.removeNode(ptr.id); this._widget.operationInterface.removeNode(ptr.conn.id); - this.updateCode(operation => operation.removeReference(name)); } else { throw new Error(`Could not find reference: ${name}`); } @@ -218,18 +224,22 @@ define([ return desc; } - createConnectedNode(typeId, isInput) { - const node = this.client.getNode(typeId); + newDataDesc(isInput, name) { + const dataNode = this.client.getAllMetaNodes() + .find(node => node.getAttribute('name') === 'Data'); + const Decorator = this._getNodeDecorator(dataNode); + const nodes = isInput ? this.operation.inputs : this.operation.outputs; - const name = uniqueName( - 'data', + name = uniqueName( + name || 'data', nodes.map(d => d.name) ); + const id = `data_${counter()}`; const dataDesc = { id, name, - Decorator: this._getNodeDecorator(node), + Decorator, attributes: {}, attribute_meta: {}, pointers: {}, @@ -238,24 +248,48 @@ define([ isConnection: false, conn: { id: `conn_${counter()}`, - src: null, - dst: null, + src: isInput ? id : this.operation.id, + dst: isInput ? this.operation.id : id, } }; + return dataDesc; + } + + createConnectedNode(typeId, isInput) { + const {id, name} = this.addDataInterfaceNode(isInput); if (isInput) { - dataDesc.conn.src = id; - dataDesc.conn.dst = this.operation.id; - this.operation.inputs.push(dataDesc); this.updateCode(operation => operation.addInput(name)); } else { - dataDesc.conn.src = this.operation.id; - dataDesc.conn.dst = id; - this.operation.outputs.push(dataDesc); this.updateCode(operation => operation.addOutput(name)); } + return id; + } + + addDataInterfaceNode(isInput, name) { + const dataDesc = this.newDataDesc(isInput, name); + + if (isInput) { + this.operation.inputs.push(dataDesc); + } else { + this.operation.outputs.push(dataDesc); + } this.addInterfaceNode(dataDesc); this._widget.operationInterface.addConnection(dataDesc.conn); - return dataDesc.id; + return dataDesc; + } + + deleteDataInterfaceNode(name) { + const isInput = this.operation.inputs.find(desc => desc.name === name); + const nodes = isInput ? this.operation.inputs : + this.operation.outputs; + const index = nodes.findIndex(desc => desc.name === name); + if (index > -1) { + const [desc] = nodes.splice(index, 1); + this._widget.operationInterface.removeNode(desc.id); + this._widget.operationInterface.removeNode(desc.conn.id); + } else { + throw new Error(`Could not find input/output node: ${name}`); + } } deleteNode(id) { @@ -333,14 +367,93 @@ define([ } getDesc(id) { + return this.getDescWith(desc => desc.id === id) + } + + getDescWith(fn) { const desc = [ ...this.operation.inputs, ...this.operation.outputs, ...this.operation.references, this.operation - ].find(desc => desc.id === id); + ].find(fn); return desc; } + + // Operation code editor + saveTextFor(_id, code) { + this.operation.code = code; + const operation = OperationCode.findOperation(code); + const refs = this.operation.references.map(desc => desc.name); + + this.operation.name = operation.getName(); + + // update the attributes + // check if the attributes have changed + const allAttrs = operation.getAttributes(); + const removedAttrs = Object.values(this.operation.attributes) + .filter(oldAttr => !allAttrs.find(attr => attr.name === oldAttr.name)); + + const addAttrs = allAttrs.filter(attr => { + const oldAttr = this.operation.attributes[attr.name]; + const isNewAttribute = !oldAttr; + if (isNewAttribute) { + const isReference = refs.includes(attr.name); + return !isReference; + } + return false; + }); + + const changedAttrs = allAttrs.filter(attr => { + const oldAttr = this.operation.attributes[attr.name]; + const isNewAttribute = !oldAttr; + return !isNewAttribute && attr.value !== oldAttr.value; + }); + + // update the references (removal only) + const rmRefs = _.difference(refs, allAttrs.map(attr => attr.name)); + + const [addInputs, rmInputs] = this.listdiff( + operation.getInputs().map(input => input.name), + this.operation.inputs.map(input => input.name) + ); + + const [addOutputs, rmOutputs] = this.listdiff( + operation.getOutputs().map(input => input.name), + this.operation.outputs.map(input => input.name) + ); + + addAttrs.forEach(attr => { + this.operation.attributes[attr.name].value = attr.value; + console.log('adding', attr, 'what is the default value?'); + this.operation.attribute_meta[attr.name] = { + name: attr.name, + type: 'string', + defaultValue: attr.value, + }; + }); + changedAttrs.forEach(attr => + this.operation.attributes[attr.name].value = attr.value + ); + removedAttrs.forEach(attr => { + delete this.operation.attribute_meta[attr.name]; + delete this.operation.attributes[attr.name]; + }); + + rmRefs.forEach(name => this.removeInterfaceReference(name)); + + addInputs.forEach(input => this.addDataInterfaceNode(true, input)); + addOutputs.forEach(name => this.addDataInterfaceNode(false, name)); + rmInputs.concat(rmOutputs) + .forEach(name => this.deleteDataInterfaceNode(name)); + this.updateInterfaceNode(this.operation); + } + + listdiff(l1, l2) { + const newElements = _.difference(l1, l2); + const oldElements = _.difference(l2, l1); + return [newElements, oldElements]; + } } class InMemoryOperationInterfaceControl { diff --git a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js index b836b79e6..208b61d0b 100644 --- a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js +++ b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js @@ -96,6 +96,7 @@ define([ setOperation(operation) { this.codeEditor.addNode({ + id: operation.id, name: operation.name, text: operation.code, }); @@ -113,7 +114,7 @@ define([ // TODO: Get the attributes and such // TODO: create the interface nodes const Decorator = WebGMEGlobal.Client.decoratorManager.getDecoratorForWidget('OpIntDecorator', 'EasyDAG'); - const centralNode = operation; + const centralNode = _.clone(operation); centralNode.Decorator = Decorator; return [centralNode]; } From 07f6f66708ef060373ec16442a3036766dc95dcb Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Mon, 9 Nov 2020 09:50:31 -0600 Subject: [PATCH 12/13] Starting to add the execution logic --- .../EagerOperation/EagerOperationControl.js | 85 ++++++++++++++++++- .../EagerOperation/EagerOperationWidget.js | 6 +- 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/visualizers/panels/EagerOperation/EagerOperationControl.js b/src/visualizers/panels/EagerOperation/EagerOperationControl.js index a19c25e8e..0d738f346 100644 --- a/src/visualizers/panels/EagerOperation/EagerOperationControl.js +++ b/src/visualizers/panels/EagerOperation/EagerOperationControl.js @@ -33,6 +33,7 @@ define([ this.operation = this.getInitialOperation(); this._widget.setOperation(this.operation); this.DEFAULT_DECORATOR = 'OpIntDecorator'; + this.currentTrainTask = trainTask; } initializeWidgetHandlers (widget) { @@ -51,8 +52,88 @@ define([ widget.codeEditor.saveTextFor = this.saveTextFor.bind(this); } - runOperation(operation) { - // TODO: + async runOperation(operation) { + await this.createOperationCode(operation); + // TODO: run the operation + const self = this; + return PromiseEvents.new(async function(resolve) { + this.emit('update', 'Generating Code'); + await self.initTrainingCode(modelInfo); + this.emit('update', 'Training...'); + const trainTask = self.session.spawn('python start_train.py'); + self.currentTrainTask = trainTask; + self.currentTrainTask.on(Message.STDOUT, data => { + let line = data.toString(); + if (line.startsWith(CONSTANTS.START_CMD)) { + line = line.substring(CONSTANTS.START_CMD.length + 1); + const splitIndex = line.indexOf(' '); + const cmd = line.substring(0, splitIndex); + const content = JSON.parse(line.substring(splitIndex + 1)); + if (cmd === 'PLOT') { + this.emit('plot', content); + } else { + console.error('Unrecognized command:', cmd); + } + } + }); + let stderr = ''; + self.currentTrainTask.on(Message.STDERR, data => stderr += data.toString()); + self.currentTrainTask.on(Message.COMPLETE, exitCode => { + if (exitCode) { + this.emit('error', stderr); + } else { + this.emit('end'); + } + if (self.currentTrainTask === trainTask) { + self.currentTrainTask = null; + } + resolve(); + }); + }); + // TODO: initCode + // TODO: load input data + // TODO: upload data afterwards? + const mainCode = ``; + await this.session.addFile('run_operation.py', mainCode); + + const trainTask = this.session.spawn('python start_train.py'); + this.currentTrainTask = trainTask; + this.currentTrainTask.on(Message.STDOUT, data => { + let line = data.toString(); + if (line.startsWith(CONSTANTS.START_CMD)) { + line = line.substring(CONSTANTS.START_CMD.length + 1); + const splitIndex = line.indexOf(' '); + const cmd = line.substring(0, splitIndex); + const content = JSON.parse(line.substring(splitIndex + 1)); + if (cmd === 'PLOT') { + this.emit('plot', content); + } else { + console.error('Unrecognized command:', cmd); + } + } + }); + let stderr = ''; + this.currentTrainTask.on(Message.STDERR, data => stderr += data.toString()); + this.currentTrainTask.on(Message.COMPLETE, exitCode => { + if (exitCode) { + this.emit('error', stderr); + } else { + this.emit('end'); + } + if (this.currentTrainTask === trainTask) { + this.currentTrainTask = null; + } + resolve(); + }); + } + + async createOperationCode(operation) { + const {name, code} = operation; + const filename = ; + // TODO: Can I reuse some code from the operation plugin? + const initCode = `from operations.${filename} import ${name}`; + await this.session.addFile('operations/__init__.py', initCode); + await this.session.addFile(`operations/${filename}.py`, code); } getInitialOperation() { diff --git a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js index 208b61d0b..7a5cd115f 100644 --- a/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js +++ b/src/visualizers/widgets/EagerOperation/EagerOperationWidget.js @@ -105,7 +105,11 @@ define([ console.log('about to add', interfaceNodes); interfaceNodes.forEach(node => interfaceTab.editor.addNode(node)); // FIXME: this is overly simplistic... - envTab.editor.addNode({name: operation.name, text: operation.env}); + envTab.editor.addNode({ + id: `env-${operation.id}`, + name: operation.name, + text: operation.env, + }); } getOperationInterfaceNodes(operation) { From a500ee21d2abdf8633f356b31b2080a7b468dcb2 Mon Sep 17 00:00:00 2001 From: Brian Broll Date: Tue, 10 Nov 2020 10:34:44 -0600 Subject: [PATCH 13/13] WIP --- .../panels/EagerOperation/EagerOperationControl.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/visualizers/panels/EagerOperation/EagerOperationControl.js b/src/visualizers/panels/EagerOperation/EagerOperationControl.js index 0d738f346..c35a701d3 100644 --- a/src/visualizers/panels/EagerOperation/EagerOperationControl.js +++ b/src/visualizers/panels/EagerOperation/EagerOperationControl.js @@ -53,7 +53,7 @@ define([ } async runOperation(operation) { - await this.createOperationCode(operation); + await this.addOperationCode(operation, this.session); // TODO: run the operation const self = this; return PromiseEvents.new(async function(resolve) { @@ -94,7 +94,6 @@ define([ // TODO: load input data // TODO: upload data afterwards? const mainCode = ``; - await this.session.addFile('run_operation.py', mainCode); const trainTask = this.session.spawn('python start_train.py'); this.currentTrainTask = trainTask; @@ -127,7 +126,12 @@ define([ }); } - async createOperationCode(operation) { + async addOperationCode(operation, session) { + // TODO: create a new branch + // TODO: save the operation + // TODO: generate code from the operation + // TODO: copy the generated files into the session + // TODO: copy the artifacts into the session const {name, code} = operation; const filename = ; // TODO: Can I reuse some code from the operation plugin?