<commit_before>define(["dojo/_base/declare", "dijit/registry", "dojo/_base/lang", "dojo/dom-style",
        "../_PersistentObjectEditPane", "dijit/_TemplatedMixin", "dijit/_WidgetsInTemplateMixin",
        "dojo/text!./PersistentObjectButtonEditPane.html", "dojo/i18n!./nls/labels",
        "ppwcode/persistence/PersistentObject", "ppwcode/persistence/AuditableObject",
        "dijit/layout/BorderContainer", "dijit/layout/ContentPane",
        "ppwcode/persistence/ui/auditableInfoPane/AuditableInfoPane",
        "dijit/form/Button",
        "xstyle/css!./PersistentObjectButtonEditPane.css"],
    function(declare, registry, lang, domStyle,
             _PersistentObjectEditPane, _TemplatedMixin, _WidgetsInTemplateMixin,
             template, labels,
             PersistentObject, AuditableObject) {

      function setVisible(/*Button*/ button, /*Boolean*/ condition, /*Boolean*/ busy) {
        // IDEA use FX
        var displayStyle = condition ? "inline-block" : "none";
        domStyle.set(button.domNode, "display", displayStyle);
        button.set("disabled", !condition || busy);

        // TODO should listen to isEditable and isDeletable
      }

      return declare([_PersistentObjectEditPane, _TemplatedMixin, _WidgetsInTemplateMixin], {
        // summary:
        //    Widget that extends _PersistentObjectEditPane with an actual presentation.
        //    There are buttons to control the edit cycle on the bottom of the pane.
        //    The content will show a scrollbar if it is to high or wide.
        // description:
        //    This is a ./_PersistentObjectEditPane.
        //    The widget shows buttons to go the edit mode, cancel or save an edit,
        //    and delete the persistent object.
        //    The actual actions are done by a refresher, creator, saver, and remover
        //    function.
        //    Intended to wrap around a _SemanticObjectPane, which can be injected with
        //    set("contentPane")

        templateString: template,
        labels: labels,

        getTargetType: function() {
          // summary:
          //    PersistentObject if there is no persistentObjectDetail. The target type of
          //    the persistentObjectPane otherwise.
          return this.persistentObjectPane ? this.persistentObjectPane.getTargetType() : PersistentObject;
        },

        // persistentObjectPane: ppwcode/semantics/_SemanticObjectPane
        //    An _SemanticObjectPane that displays the target.
        persistentObjectPane: null,

        _borderContainer: null,
        _contentDiv: null,
        _errorDiv: null,
        _bottomDiv: null,
        _auditableInfo: null,
        _buttonDiv: null,
        _btnEdit: null,
        _btnCancel: null,
        _btnSave: null,
        _btnDelete: null,

        postCreate: function() {
          this._setButtonsStyles(this.NOTARGET);
          if (! this.get("target")) { // TODO is this really necessary? why? write a comment
            this.set("target", null);
          }
        },

        destroy: function() {
          this._beingDestroyed = true;
          this.set("target", null);
          // will also destroy _auditableInfo, if there still is one
          this.inherited(arguments);
        },

        _wrappedDetails: function() {
          var result = [];
          if (this.persistentObjectPane) {
            result.push(this.persistentObjectPane);
          }
          result.push(this._auditableInfo);
          return result;
        },

        _propagateTarget: function(/*PersistentObject*/ po) {
          // summary:
          //    Set the target on the persistentObjectPane and _auditableInfo
          if (this.get("persistentObjectPane")) {
            this.get("persistentObjectPane").set("target", po);
          }
          var ao = po && po.isInstanceOf(AuditableObject) ? po : null;
          // MUDO invisible is there is no target
          this._auditableInfo.set("target", ao);
        },

        _setPersistentObjectPaneAttr: function(poPane) {
          // summary:
          //    Set the persistentObjectPane. If it has a target, and we have no target,
          //    that becomes are new target.
          // description:
          //    Note that the old persistentObjectPane was not created by us,
          //    and thus also is not destroyed by us!

          // TODO poDetail target type is a PersistentObject

          // might be called implicity by constructor of WidgetBase
          var oldPoPane = this.get("persistentObjectPane");
          if (oldPoPane) {
            oldPoPane.set("target", null);
            // no destroy of oldPoDetail; we didn't create it; if we do, we have to!
            this._contentDiv.removeChild(oldPoPane);
            oldPoPane.destroyRecursive();
          }
          this._set("persistentObjectPane", poPane);
          if (poPane) {
            this._contentDiv.addChild(poPane);
            // new detail might already have a target
            var ourPo = this.get("target");
            var wrappedPo = poPane.get("target");
            if (ourPo) {
              poPane.set("target", ourPo);
            }
            else if (wrappedPo) {
              this.set("target", wrappedPo);
            }
            // else we both have null targets; ok
          }
          this._borderContainer.resize();
        },

        validate: function() {
          // MUDO: standard Widget function?
          if (this.get("presentationMode") === this.EDIT) {
            var poPane = this.get("persistentObjectPane");
            if (poPane && poPane.validate) {
              return poPane.validate();
            }
            else {
              return true;
            }
          }
          else {
            return true;
          }
        },

        _setButtonsStyles: function(stylePresentationMode) {
          // Set the button style.
          this._c_pre(function() { return stylePresentationMode && this.stylePresentationModes.indexOf(stylePresentationMode) >= 0; });

          var po = this.get("target");
          var busy = (stylePresentationMode === this.BUSY);
          setVisible(this._btnEdit, stylePresentationMode === this.VIEW && (po.isEditable() || po.isDeletable()), false);
          setVisible(this._btnCancel, this.isInEditMode(), busy);
          setVisible(this._btnDelete, this.isInEditMode() && po.isDeletable(), busy);
          setVisible(this._btnSave, this.isInEditMode() && po.isEditable(), busy);
        },

        _localPresentationModeChange: function(presentationMode) {
          this._setButtonsStyles(this.get("stylePresentationMode"));
        }

      });

    });
<commit_msg>inherit from _ContentPaneResizeMixin for sizing the widget correctly (mostly used in combination with the different layouts)<commit_after>define(["dojo/_base/declare", "dijit/registry", "dojo/_base/lang", "dojo/dom-style",
        "../_PersistentObjectEditPane", "dijit/_TemplatedMixin", "dijit/_WidgetsInTemplateMixin",
        "dojo/text!./PersistentObjectButtonEditPane.html", "dojo/i18n!./nls/labels",
        "ppwcode/persistence/PersistentObject", "ppwcode/persistence/AuditableObject",
        "dijit/layout/_ContentPaneResizeMixin", "dijit/layout/BorderContainer", "dijit/layout/ContentPane",
        "ppwcode/persistence/ui/auditableInfoPane/AuditableInfoPane",
        "dijit/form/Button",
        "xstyle/css!./PersistentObjectButtonEditPane.css"],
    function(declare, registry, lang, domStyle,
             _PersistentObjectEditPane, _TemplatedMixin, _WidgetsInTemplateMixin,
             template, labels,
             PersistentObject, AuditableObject,
             _ContentPaneResizeMixin) {

      function setVisible(/*Button*/ button, /*Boolean*/ condition, /*Boolean*/ busy) {
        // IDEA use FX
        var displayStyle = condition ? "inline-block" : "none";
        domStyle.set(button.domNode, "display", displayStyle);
        button.set("disabled", !condition || busy);

        // TODO should listen to isEditable and isDeletable
      }

      return declare([_PersistentObjectEditPane, _TemplatedMixin, _WidgetsInTemplateMixin, _ContentPaneResizeMixin], {
        // summary:
        //    Widget that extends _PersistentObjectEditPane with an actual presentation.
        //    There are buttons to control the edit cycle on the bottom of the pane.
        //    The content will show a scrollbar if it is to high or wide.
        // description:
        //    This is a ./_PersistentObjectEditPane.
        //    The widget shows buttons to go the edit mode, cancel or save an edit,
        //    and delete the persistent object.
        //    The actual actions are done by a refresher, creator, saver, and remover
        //    function.
        //    Intended to wrap around a _SemanticObjectPane, which can be injected with
        //    set("contentPane")

        templateString: template,
        labels: labels,

        getTargetType: function() {
          // summary:
          //    PersistentObject if there is no persistentObjectDetail. The target type of
          //    the persistentObjectPane otherwise.
          return this.persistentObjectPane ? this.persistentObjectPane.getTargetType() : PersistentObject;
        },

        // persistentObjectPane: ppwcode/semantics/_SemanticObjectPane
        //    An _SemanticObjectPane that displays the target.
        persistentObjectPane: null,

        _borderContainer: null,
        _contentDiv: null,
        _errorDiv: null,
        _bottomDiv: null,
        _auditableInfo: null,
        _buttonDiv: null,
        _btnEdit: null,
        _btnCancel: null,
        _btnSave: null,
        _btnDelete: null,

        postCreate: function() {
          this._setButtonsStyles(this.NOTARGET);
          if (! this.get("target")) { // TODO is this really necessary? why? write a comment
            this.set("target", null);
          }

          if(!this.containerNode){
            this.containerNode = this.domNode;
          }
        },

        destroy: function() {
          this._beingDestroyed = true;
          this.set("target", null);
          // will also destroy _auditableInfo, if there still is one
          this.inherited(arguments);
        },

        _wrappedDetails: function() {
          var result = [];
          if (this.persistentObjectPane) {
            result.push(this.persistentObjectPane);
          }
          result.push(this._auditableInfo);
          return result;
        },

        _propagateTarget: function(/*PersistentObject*/ po) {
          // summary:
          //    Set the target on the persistentObjectPane and _auditableInfo
          if (this.get("persistentObjectPane")) {
            this.get("persistentObjectPane").set("target", po);
          }
          var ao = po && po.isInstanceOf(AuditableObject) ? po : null;
          // MUDO invisible is there is no target
          this._auditableInfo.set("target", ao);
        },

        _setPersistentObjectPaneAttr: function(poPane) {
          // summary:
          //    Set the persistentObjectPane. If it has a target, and we have no target,
          //    that becomes are new target.
          // description:
          //    Note that the old persistentObjectPane was not created by us,
          //    and thus also is not destroyed by us!

          // TODO poDetail target type is a PersistentObject

          // might be called implicity by constructor of WidgetBase
          var oldPoPane = this.get("persistentObjectPane");
          if (oldPoPane) {
            oldPoPane.set("target", null);
            // no destroy of oldPoDetail; we didn't create it; if we do, we have to!
            this._contentDiv.removeChild(oldPoPane);
            oldPoPane.destroyRecursive();
          }
          this._set("persistentObjectPane", poPane);
          if (poPane) {
            this._contentDiv.addChild(poPane);
            // new detail might already have a target
            var ourPo = this.get("target");
            var wrappedPo = poPane.get("target");
            if (ourPo) {
              poPane.set("target", ourPo);
            }
            else if (wrappedPo) {
              this.set("target", wrappedPo);
            }
            // else we both have null targets; ok
          }
          this._borderContainer.resize();
        },

        validate: function() {
          // MUDO: standard Widget function?
          if (this.get("presentationMode") === this.EDIT) {
            var poPane = this.get("persistentObjectPane");
            if (poPane && poPane.validate) {
              return poPane.validate();
            }
            else {
              return true;
            }
          }
          else {
            return true;
          }
        },

        _setButtonsStyles: function(stylePresentationMode) {
          // Set the button style.
          this._c_pre(function() { return stylePresentationMode && this.stylePresentationModes.indexOf(stylePresentationMode) >= 0; });

          var po = this.get("target");
          var busy = (stylePresentationMode === this.BUSY);
          setVisible(this._btnEdit, stylePresentationMode === this.VIEW && (po.isEditable() || po.isDeletable()), false);
          setVisible(this._btnCancel, this.isInEditMode(), busy);
          setVisible(this._btnDelete, this.isInEditMode() && po.isDeletable(), busy);
          setVisible(this._btnSave, this.isInEditMode() && po.isEditable(), busy);
        },

        _localPresentationModeChange: function(presentationMode) {
          this._setButtonsStyles(this.get("stylePresentationMode"));
        }

      });

    });
<|endoftext|>


<commit_before>/**
 * An input widget to handle building a media set.
 * This has the option of loading/modifying an existing media.
 * @author Bill Riehl <wjriehl@lbl.gov>
 * @public
 */

(function( $, undefined ) {
    $.KBWidget({
        name: "kbaseMediaViewer",
        parent: "kbaseWidget",
        version: "1.0.0",
        options: {
            loadingImage: "../images/ajax-loader.gif",
            #fbaURL: "https://kbase.us/services/fba_model_services",
            fbaURL: "https://kbase.us/services/KBaseFBAModeling",
            metadata: null,
            media: null
        },

        init: function(options) {
            this._super(options);

            if (!this.options.media)
                return this.renderError();

            else
                return this.render();
        },

        render: function() {
            this.$elem.append($("
").append("New Media"));

            if (this.options.metadata)
                this.renderMetadata(this.options.metadata);

            this.renderMedia(this.options.media);
            return this;
        },

        /**
         * Renders a metadata block in some logical way.
         * Probably just a small table with a couple fields. name, date, etc.
         */
        renderMetadata: function(metadata) {
            var id = metadata[0];
            var timestamp = metadata[2];
            var owner = metadata[5];
            var workspace = metadata[7];
            var mediaMeta = metadata[10];
            var mediaName = mediaMeta['name'];
            var numCompounds = mediaMeta['number_compounds'];

            var tableRow = function(a, b) {
                return "
" + a + "	" + b + "
";
            };

            var $metaTable = $("
")
                             .addClass("table table-striped table-bordered")
                             .css({"margin-right": "auto", "margin-left": "auto"})
                             .append(tableRow("Name", mediaName))
                             .append(tableRow("Owner", owner))
                             .append(tableRow("Workspace", workspace))
                             .append(tableRow("Time", timestamp));

            this.$elem.append($metaTable);
            return this;
        },

        /**
         * Renders the Media typed object passed into it.
         * This is just a table with each compound with min/max fluxes and concentration
         * in each row.
         * Simple, eh?
         */
        renderMedia: function(media) {
            var $mediaTable = $("
")
                              .addClass("table table-striped table-bordered")
                              .css({"margin-right": "auto", "margin-left": "auto"})
                              .append($("
")
                                      .append($("")
                                              .append("Compound"))
                                      .append($("")
                                              .append("Concentration"))
                                      .append($("")
                                              .append("Min Flux"))
                                      .append($("")
                                              .append("Max Flux")));

            for (var i=0; i<media.media_compounds.length; i++) {
                var cpd = media.media_compounds[i];

                var $newRow = $("
")
                              .append($("")
                                      .append(cpd.name))
                              .append($("")
                                      .append(cpd.concentration))
                              .append($("")
                                      .append(cpd.min_flux))
                              .append($("")
                                      .append(cpd.max_flux));

                $mediaTable.append($newRow);
            }

            this.$elem.append($mediaTable);
            return this;
        },

        /**
         * Renders a simple error message.
         */
        renderError: function() {
            return this;
        },

        /**
         * Builds controls to fetch a media set and populate the current list.
         */
        buildFetchMediaDiv: function(mediaList) {
            var $fetchMediaDiv = $("
");
            $fetchMediaDiv.append("Select an existing media to modify (optional): ");

            var $mediaList = $("<select>");
            for (var i=0; i<mediaList.length; i++) {
                $mediaList.append($("<option>").append(mediaList[i][0]));
            }

            var $fetchButton = $("<button>")
                               .attr("type", "button")
                               .attr("value", "Fetch")
                               .addClass("btn btn-primary")
                               .append("Fetch")
                               .click($.proxy(
                                    function(event) {
                                        var mediaName = this.$elem.find("div > select").val();
                                        this.fbaClient.get_media_async({
                                                auth: this.authToken(),
                                                medias: [mediaName],
                                                workspaces: [this.wsId],
                                            },
                                            $.proxy(function(media) { this.updateMediaTable(media); }, this)
                                        );
                                    },
                                    this));

            $fetchMediaDiv.append($mediaList)
                          .append($fetchButton);

            return $fetchMediaDiv;
        },

        /**
         * name and pH
         */
        buildHeaderInputs: function() {
            var $headerInputDiv = $("
");
            $headerInputDiv.append($("")
                                   .append("Name (required): "))
                           .append($("<input>")
                                   .attr("type", "text")
                                   .attr("id", "media-name")
                                   .addClass("form-control"));
            $headerInputDiv.append($("")
                                   .append("pH (optional): "))
                           .append($("<input>")
                                   .attr("type", "text")
                                   .attr("id", "media-ph")
                                   .addClass("form-control"));

            return $headerInputDiv;
        },

        buildMediaTable: function() {
            var $mediaTable = $("
")
                              .addClass("table table-striped table-bordered")
                              .css({"margin-right": "auto", "margin-left": "auto"})
                              .append($("
")
                                      .append($("")
                                              .append("Compound"))
                                      .append($("")
                                              .append("Concentration"))
                                      .append($("")
                                              .append("Min Flux"))
                                      .append($("")
                                              .append("Max Flux"))
                                      .append($("")));

            return $mediaTable;
        },

        buildRowControlButton: function(trashOnly) {
            var self = this;

            var deleteRow = function(event) {
                $(event.currentTarget).closest("tr").remove();
            };

            var addRow = function(event) {
                self.addEmptyMediaRow();
                $(event.currentTarget).find("span")
                                      .addClass("glyphicon-trash")
                                      .removeClass("glyphicon-plus");

                $(event.currentTarget).off("click")
                                      .click(deleteRow);
            };

            var $button = $("<button>")
                          .addClass("form-control")
                          .append($("")
                                  .addClass("glyphicon")
                                  .addClass(trashOnly ? "glyphicon-trash" : "glyphicon-plus"));

            if (trashOnly)
                $button.click(deleteRow);
            else
                $button.click(addRow);

            return $button;
        },

        addEmptyMediaRow: function() {
            this.addMediaRow();
        },

        addMediaRow: function(cpd) {
            var self = this;
            var $newRowBtn = this.buildRowControlButton(cpd);

            // little hack to make the ternary operators below a little cleaner.
            if (!cpd) cpd = {};

            var $row = $("
")
                       .append($("")
                               .append($("<input>")
                                       .addClass("form-control")
                                       .attr("placeholder", "Add Compound")
                                       .attr("value", (cpd.name ? cpd.name : ""))))
                       .append($("")
                               .append($("<input>")
                                       .addClass("form-control")
                                       .attr("placeholder", "Add Concentration")
                                       .attr("value", (cpd.concentration ? cpd.concentration : ""))))
                       .append($("")
                               .append($("<input>")
                                       .addClass("form-control")
                                       .attr("placeholder", "Add Min Flux")
                                       .attr("value", (cpd.min_flux ? cpd.min_flux : ""))))
                       .append($("")
                               .append($("<input>")
                                       .addClass("form-control")
                                       .attr("placeholder", "Add Max Flux")
                                       .attr("value", (cpd.max_flux ? cpd.max_flux : ""))))
                       .append($("")
                               .append($newRowBtn));

            this.$mediaTable.append($row);
        },

        updateMediaTable: function(media) {
            media = media[0];
            this.$mediaTable.find("tr > td").closest("tr").remove();

            for (var i=0; i<media.media_compounds.length; i++) {
                this.addMediaRow(media.media_compounds[i]);
            }
            this.addEmptyMediaRow();

            this.$headerInputDiv.find("#media-name").val(media.name);
            this.$headerInputDiv.find("#media-ph").val(media.pH);
        },

        /**
         * This puts together and returns an array with a single JSON string
         * representing a new media set. This gets passed to the IPython back end
         * to be processed into a new medium.
         * @public
         * @return
         */
        getParameters: function() {
            var mediaName = this.$headerInputDiv.find("#media-name").val().trim().replace(/\s+/g, "_");
            if (!mediaName) {
                // stuuuuuuuuff...
            }

            var mediaParams = {
                // workspace and auth token will be handled by the IPython kernel.
                name : mediaName,
                media : mediaName,
                isDefined : 0,
                isMinimal : 0
            };

            var cpds = [],
                concs = [],
                minFluxes = [],
                maxFluxes = [];

            //$(cell.element).find("tr").each(function(a, b){ console.log($(b).find("td:nth-child(1) > input").val()); })

            // Find all
 in the table that has a  (e.g., NOT the header row - that has )
            // and iterate over those rows.
            this.$mediaTable.find("tr:has('td')").each(function(idx, row) {
                var cpd = $(row).find("td:nth-child(1) input").val().trim();
                var conc = $(row).find("td:nth-child(2) input").val().trim();
                var min = $(row).find("td:nth-child(3) input").val().trim();
                var max = $(row).find("td:nth-child(4) input").val().trim();

                if (cpd) {
                    cpds.push(cpd);
                    concs.push(conc);
                    minFluxes.push(min);
                    maxFluxes.push(max);
                }
            });

            mediaParams['compounds'] = cpds;
            mediaParams['concentrations'] = concs;
            mediaParams['maxflux'] = maxFluxes;
            mediaParams['minflux'] = minFluxes;

            return [ JSON.stringify(mediaParams) ];
        },

    });
})( jQuery );<commit_msg>   Fixed perl/python style comment that had crept into this JS file when FBA URL was changed.<commit_after>/**
 * An input widget to handle building a media set.
 * This has the option of loading/modifying an existing media.
 * @author Bill Riehl <wjriehl@lbl.gov>
 * @public
 */

(function( $, undefined ) {
    $.KBWidget({
        name: "kbaseMediaViewer",
        parent: "kbaseWidget",
        version: "1.0.0",
        options: {
            loadingImage: "../images/ajax-loader.gif",
            // fbaURL: "https://kbase.us/services/fba_model_services",
            fbaURL: "https://kbase.us/services/KBaseFBAModeling",
            metadata: null,
            media: null
        },

        init: function(options) {
            this._super(options);

            if (!this.options.media)
                return this.renderError();

            else
                return this.render();
        },

        render: function() {
            this.$elem.append($("
").append("New Media"));

            if (this.options.metadata)
                this.renderMetadata(this.options.metadata);

            this.renderMedia(this.options.media);
            return this;
        },

        /**
         * Renders a metadata block in some logical way.
         * Probably just a small table with a couple fields. name, date, etc.
         */
        renderMetadata: function(metadata) {
            var id = metadata[0];
            var timestamp = metadata[2];
            var owner = metadata[5];
            var workspace = metadata[7];
            var mediaMeta = metadata[10];
            var mediaName = mediaMeta['name'];
            var numCompounds = mediaMeta['number_compounds'];

            var tableRow = function(a, b) {
                return "
" + a + "	" + b + "
";
            };

            var $metaTable = $("
")
                             .addClass("table table-striped table-bordered")
                             .css({"margin-right": "auto", "margin-left": "auto"})
                             .append(tableRow("Name", mediaName))
                             .append(tableRow("Owner", owner))
                             .append(tableRow("Workspace", workspace))
                             .append(tableRow("Time", timestamp));

            this.$elem.append($metaTable);
            return this;
        },

        /**
         * Renders the Media typed object passed into it.
         * This is just a table with each compound with min/max fluxes and concentration
         * in each row.
         * Simple, eh?
         */
        renderMedia: function(media) {
            var $mediaTable = $("
")
                              .addClass("table table-striped table-bordered")
                              .css({"margin-right": "auto", "margin-left": "auto"})
                              .append($("
")
                                      .append($("")
                                              .append("Compound"))
                                      .append($("")
                                              .append("Concentration"))
                                      .append($("")
                                              .append("Min Flux"))
                                      .append($("")
                                              .append("Max Flux")));

            for (var i=0; i<media.media_compounds.length; i++) {
                var cpd = media.media_compounds[i];

                var $newRow = $("
")
                              .append($("")
                                      .append(cpd.name))
                              .append($("")
                                      .append(cpd.concentration))
                              .append($("")
                                      .append(cpd.min_flux))
                              .append($("")
                                      .append(cpd.max_flux));

                $mediaTable.append($newRow);
            }

            this.$elem.append($mediaTable);
            return this;
        },

        /**
         * Renders a simple error message.
         */
        renderError: function() {
            return this;
        },

        /**
         * Builds controls to fetch a media set and populate the current list.
         */
        buildFetchMediaDiv: function(mediaList) {
            var $fetchMediaDiv = $("
");
            $fetchMediaDiv.append("Select an existing media to modify (optional): ");

            var $mediaList = $("<select>");
            for (var i=0; i<mediaList.length; i++) {
                $mediaList.append($("<option>").append(mediaList[i][0]));
            }

            var $fetchButton = $("<button>")
                               .attr("type", "button")
                               .attr("value", "Fetch")
                               .addClass("btn btn-primary")
                               .append("Fetch")
                               .click($.proxy(
                                    function(event) {
                                        var mediaName = this.$elem.find("div > select").val();
                                        this.fbaClient.get_media_async({
                                                auth: this.authToken(),
                                                medias: [mediaName],
                                                workspaces: [this.wsId],
                                            },
                                            $.proxy(function(media) { this.updateMediaTable(media); }, this)
                                        );
                                    },
                                    this));

            $fetchMediaDiv.append($mediaList)
                          .append($fetchButton);

            return $fetchMediaDiv;
        },

        /**
         * name and pH
         */
        buildHeaderInputs: function() {
            var $headerInputDiv = $("
");
            $headerInputDiv.append($("")
                                   .append("Name (required): "))
                           .append($("<input>")
                                   .attr("type", "text")
                                   .attr("id", "media-name")
                                   .addClass("form-control"));
            $headerInputDiv.append($("")
                                   .append("pH (optional): "))
                           .append($("<input>")
                                   .attr("type", "text")
                                   .attr("id", "media-ph")
                                   .addClass("form-control"));

            return $headerInputDiv;
        },

        buildMediaTable: function() {
            var $mediaTable = $("
")
                              .addClass("table table-striped table-bordered")
                              .css({"margin-right": "auto", "margin-left": "auto"})
                              .append($("
")
                                      .append($("")
                                              .append("Compound"))
                                      .append($("")
                                              .append("Concentration"))
                                      .append($("")
                                              .append("Min Flux"))
                                      .append($("")
                                              .append("Max Flux"))
                                      .append($("")));

            return $mediaTable;
        },

        buildRowControlButton: function(trashOnly) {
            var self = this;

            var deleteRow = function(event) {
                $(event.currentTarget).closest("tr").remove();
            };

            var addRow = function(event) {
                self.addEmptyMediaRow();
                $(event.currentTarget).find("span")
                                      .addClass("glyphicon-trash")
                                      .removeClass("glyphicon-plus");

                $(event.currentTarget).off("click")
                                      .click(deleteRow);
            };

            var $button = $("<button>")
                          .addClass("form-control")
                          .append($("")
                                  .addClass("glyphicon")
                                  .addClass(trashOnly ? "glyphicon-trash" : "glyphicon-plus"));

            if (trashOnly)
                $button.click(deleteRow);
            else
                $button.click(addRow);

            return $button;
        },

        addEmptyMediaRow: function() {
            this.addMediaRow();
        },

        addMediaRow: function(cpd) {
            var self = this;
            var $newRowBtn = this.buildRowControlButton(cpd);

            // little hack to make the ternary operators below a little cleaner.
            if (!cpd) cpd = {};

            var $row = $("
")
                       .append($("")
                               .append($("<input>")
                                       .addClass("form-control")
                                       .attr("placeholder", "Add Compound")
                                       .attr("value", (cpd.name ? cpd.name : ""))))
                       .append($("")
                               .append($("<input>")
                                       .addClass("form-control")
                                       .attr("placeholder", "Add Concentration")
                                       .attr("value", (cpd.concentration ? cpd.concentration : ""))))
                       .append($("")
                               .append($("<input>")
                                       .addClass("form-control")
                                       .attr("placeholder", "Add Min Flux")
                                       .attr("value", (cpd.min_flux ? cpd.min_flux : ""))))
                       .append($("")
                               .append($("<input>")
                                       .addClass("form-control")
                                       .attr("placeholder", "Add Max Flux")
                                       .attr("value", (cpd.max_flux ? cpd.max_flux : ""))))
                       .append($("")
                               .append($newRowBtn));

            this.$mediaTable.append($row);
        },

        updateMediaTable: function(media) {
            media = media[0];
            this.$mediaTable.find("tr > td").closest("tr").remove();

            for (var i=0; i<media.media_compounds.length; i++) {
                this.addMediaRow(media.media_compounds[i]);
            }
            this.addEmptyMediaRow();

            this.$headerInputDiv.find("#media-name").val(media.name);
            this.$headerInputDiv.find("#media-ph").val(media.pH);
        },

        /**
         * This puts together and returns an array with a single JSON string
         * representing a new media set. This gets passed to the IPython back end
         * to be processed into a new medium.
         * @public
         * @return
         */
        getParameters: function() {
            var mediaName = this.$headerInputDiv.find("#media-name").val().trim().replace(/\s+/g, "_");
            if (!mediaName) {
                // stuuuuuuuuff...
            }

            var mediaParams = {
                // workspace and auth token will be handled by the IPython kernel.
                name : mediaName,
                media : mediaName,
                isDefined : 0,
                isMinimal : 0
            };

            var cpds = [],
                concs = [],
                minFluxes = [],
                maxFluxes = [];

            //$(cell.element).find("tr").each(function(a, b){ console.log($(b).find("td:nth-child(1) > input").val()); })

            // Find all
 in the table that has a  (e.g., NOT the header row - that has )
            // and iterate over those rows.
            this.$mediaTable.find("tr:has('td')").each(function(idx, row) {
                var cpd = $(row).find("td:nth-child(1) input").val().trim();
                var conc = $(row).find("td:nth-child(2) input").val().trim();
                var min = $(row).find("td:nth-child(3) input").val().trim();
                var max = $(row).find("td:nth-child(4) input").val().trim();

                if (cpd) {
                    cpds.push(cpd);
                    concs.push(conc);
                    minFluxes.push(min);
                    maxFluxes.push(max);
                }
            });

            mediaParams['compounds'] = cpds;
            mediaParams['concentrations'] = concs;
            mediaParams['maxflux'] = maxFluxes;
            mediaParams['minflux'] = minFluxes;

            return [ JSON.stringify(mediaParams) ];
        },

    });
})( jQuery );<|endoftext|>


<commit_before>class MCProcessesWorkflowOutlineComponentController {
    /*@ngInject*/
    constructor(processTree, datasetService, $stateParams, toast, experimentProcessesService, mcstate, mcbus) {
        this.processTree = processTree;
        this.datasetService = datasetService;
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.datasetId = $stateParams.dataset_id;
        this.toast = toast;
        this.myName = 'mcProcessesWorkflowOutline';
        this.experimentProcessesService = experimentProcessesService;
        this.mcstate = mcstate;
        this.mcbus = mcbus;
    }

    $onInit() {
        // To preserve this binding pass this.allProcessesGraph bound to an arrow function. Arrow
        // functions lexically scope, so this in the arrow function is the this for
        // MCProcessGraphOutlineComponentController
        let cb = (processes) => {
            this.processes = processes;
            this.buildOutline();
        };

        this.datasetProcesses = this.mcProcessesWorkflow.datasetProcesses;

        this.mcbus.subscribe('PROCESSES$CHANGE', this.myName, cb);
        this.buildOutline();
    }

    $onDestroy() {
        this.mcbus.leave('PROCESSES$CHANGE', this.myName);
    }

    // This method will be called implicitly when the component is loaded.
    $onChanges(changes) {
        if (changes.processes) {
            this.processes = changes.processes.currentValue;
        }
        if (changes.highlightProcesses) {
            this.highlightProcesses = changes.highlightProcesses.currentValue;
        }
        if (this.datasetProcesses) {
            this.buildOutline();
        }
    }

    buildOutline() {
        let t = this.processTree.build(this.processes, this.highlightProcesses);
        this.root = t.root;
        this.rootNode = t.rootNode;
        if (this.dataset) {
            this.root.walk((node) => {
                node.model.inds = (node.model.id in this.datasetProcesses);
            });
        }
    }

    showDetails(p) {
        this.processTree.clearSelected(this.root);
        p.selected = true;
        this.process = p;
        this.mcProcessesWorkflow.setSelectedProcess(p.id, p.children.length !== 0);
    }

    toggle(p) {
        if (p.inds) {
            this.experimentProcessesService.addProcessToDataset(p, this.dataset, this.datasetProcesses);
            this.datasetService.updateProcessesInDataset(this.projectId, this.experimentId, this.datasetId, [p.id], []).then(
                () => null,
                () => this.toast.error(`Unable to add process to dataset ${p.name}`)
            );
        } else {
            this.experimentProcessesService.removeProcessFromDataset(p, this.dataset, this.datasetProcesses);
            this.datasetService.updateProcessesInDataset(this.projectId, this.experimentId, this.datasetId, [], [p.id]).then(
                () => null,
                () => this.toast.error(`Unable to remove process from dataset ${p.name}`)
            );
        }
    }
}

class MCProcessesWorkflowOutlineDirDirectiveController {
    /*@ngInject*/
    constructor(datasetService, $stateParams, toast, experimentProcessesService) {
        this.datasetProcesses = this.mcProcessesWorkflowOutline.datasetProcesses;
        this.datasetService = datasetService;
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.datasetId = $stateParams.dataset_id;
        this.toast = toast;
        this.experimentProcessesService = experimentProcessesService;
    }

    showDetails(p) {
        this.mcProcessesWorkflowOutline.showDetails(p);
    }

    toggle(p) {
        if (p.inds) {
            this.experimentProcessesService.addProcessToDataset(p, this.dataset, this.datasetProcesses);
            this.datasetService.updateProcessesInDataset(this.projectId, this.experimentId, this.datasetId, [p.id], []).then(
                () => null,
                () => this.toast.error(`Unable to add process to dataset ${p.name}`)
            );
        } else {
            this.experimentProcessesService.removeProcessFromDataset(p, this.dataset, this.datasetProcesses);
            this.datasetService.updateProcessesInDataset(this.projectId, this.experimentId, this.datasetId, [], [p.id]).then(
                () => null,
                () => this.toast.error(`Unable to remove process from dataset ${p.name}`)
            );
        }
    }
}

/*@ngInject*/
function mcProcessesWorkflowOutlineDirDirective(RecursionHelper) {
    return {
        restrict: 'E',
        scope: {
            process: '=',
            highlightProcesses: '=',
            dataset: '=',

            // This needs to be passed in rather than required. It appears that RecursionHelper is
            // preventing the link function from being called, so we can't require mcProcessesWorkflow
            // and access it later.
            mcProcessesWorkflowOutline: '='
        },
        controller: MCProcessesWorkflowOutlineDirDirectiveController,
        controllerAs: '$ctrl',
        bindToController: true,
        templateUrl: 'app/project/experiments/experiment/components/workflow/mc-processes-workflow-outline-dir.html',
        compile: function(element) {
            return RecursionHelper.compile(element, function() {});
        }
    }
}

angular.module('materialscommons').directive('mcProcessesWorkflowOutlineDir', mcProcessesWorkflowOutlineDirDirective);

angular.module('materialscommons').component('mcProcessesWorkflowOutline', {
    templateUrl: 'app/project/experiments/experiment/components/workflow/mc-processes-workflow-outline.html',
    controller: MCProcessesWorkflowOutlineComponentController,
    bindings: {
        processes: '<',
        highlightProcesses: '<',
        dataset: '<'
    },
    require: {
        mcProcessesWorkflow: '^mcProcessesWorkflow'
    }
});
<commit_msg>In outline view the delete process button wasn't doing anything.<commit_after>class MCProcessesWorkflowOutlineComponentController {
    /*@ngInject*/
    constructor(processTree, datasetService, $stateParams, toast, workflowService,
                experimentProcessesService, mcbus) {
        this.processTree = processTree;
        this.datasetService = datasetService;
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.datasetId = $stateParams.dataset_id;
        this.toast = toast;
        this.myName = 'mcProcessesWorkflowOutline';
        this.workflowService = workflowService;
        this.experimentProcessesService = experimentProcessesService;
        this.mcbus = mcbus;
    }

    $onInit() {
        // To preserve this binding pass this.allProcessesGraph bound to an arrow function. Arrow
        // functions lexically scope, so this in the arrow function is the this for
        // MCProcessGraphOutlineComponentController
        let cb = (processes) => {
            this.processes = processes;
            this.buildOutline();
        };

        this.datasetProcesses = this.mcProcessesWorkflow.datasetProcesses;

        this.mcbus.subscribe('PROCESSES$CHANGE', this.myName, cb);
        this.buildOutline();
    }

    $onDestroy() {
        this.mcbus.leave('PROCESSES$CHANGE', this.myName);
    }

    // This method will be called implicitly when the component is loaded.
    $onChanges(changes) {
        if (changes.processes) {
            this.processes = changes.processes.currentValue;
        }
        if (changes.highlightProcesses) {
            this.highlightProcesses = changes.highlightProcesses.currentValue;
        }
        if (this.datasetProcesses) {
            this.buildOutline();
        }
    }

    buildOutline() {
        let t = this.processTree.build(this.processes, this.highlightProcesses);
        this.root = t.root;
        this.rootNode = t.rootNode;
        if (this.dataset) {
            this.root.walk((node) => {
                node.model.inds = (node.model.id in this.datasetProcesses);
            });
        }
    }

    showDetails(p) {
        this.processTree.clearSelected(this.root);
        p.selected = true;
        this.process = p;
        this.mcProcessesWorkflow.setSelectedProcess(p.id, p.children.length !== 0);
    }

    toggle(p) {
        if (p.inds) {
            this.experimentProcessesService.addProcessToDataset(p, this.dataset, this.datasetProcesses);
            this.datasetService.updateProcessesInDataset(this.projectId, this.experimentId, this.datasetId, [p.id], []).then(
                () => null,
                () => this.toast.error(`Unable to add process to dataset ${p.name}`)
            );
        } else {
            this.experimentProcessesService.removeProcessFromDataset(p, this.dataset, this.datasetProcesses);
            this.datasetService.updateProcessesInDataset(this.projectId, this.experimentId, this.datasetId, [], [p.id]).then(
                () => null,
                () => this.toast.error(`Unable to remove process from dataset ${p.name}`)
            );
        }
    }

    deleteProcess() {
        this.workflowService.deleteNodeAndProcess(this.projectId, this.experimentId, this.process.id)
    }
}

class MCProcessesWorkflowOutlineDirDirectiveController {
    /*@ngInject*/
    constructor(datasetService, $stateParams, toast, experimentProcessesService) {
        this.datasetProcesses = this.mcProcessesWorkflowOutline.datasetProcesses;
        this.datasetService = datasetService;
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.datasetId = $stateParams.dataset_id;
        this.toast = toast;
        this.experimentProcessesService = experimentProcessesService;
    }

    showDetails(p) {
        this.mcProcessesWorkflowOutline.showDetails(p);
    }

    toggle(p) {
        if (p.inds) {
            this.experimentProcessesService.addProcessToDataset(p, this.dataset, this.datasetProcesses);
            this.datasetService.updateProcessesInDataset(this.projectId, this.experimentId, this.datasetId, [p.id], []).then(
                () => null,
                () => this.toast.error(`Unable to add process to dataset ${p.name}`)
            );
        } else {
            this.experimentProcessesService.removeProcessFromDataset(p, this.dataset, this.datasetProcesses);
            this.datasetService.updateProcessesInDataset(this.projectId, this.experimentId, this.datasetId, [], [p.id]).then(
                () => null,
                () => this.toast.error(`Unable to remove process from dataset ${p.name}`)
            );
        }
    }
}

/*@ngInject*/
function mcProcessesWorkflowOutlineDirDirective(RecursionHelper) {
    return {
        restrict: 'E',
        scope: {
            process: '=',
            highlightProcesses: '=',
            dataset: '=',

            // This needs to be passed in rather than required. It appears that RecursionHelper is
            // preventing the link function from being called, so we can't require mcProcessesWorkflow
            // and access it later.
            mcProcessesWorkflowOutline: '='
        },
        controller: MCProcessesWorkflowOutlineDirDirectiveController,
        controllerAs: '$ctrl',
        bindToController: true,
        templateUrl: 'app/project/experiments/experiment/components/workflow/mc-processes-workflow-outline-dir.html',
        compile: function(element) {
            return RecursionHelper.compile(element, function() {});
        }
    }
}

angular.module('materialscommons').directive('mcProcessesWorkflowOutlineDir', mcProcessesWorkflowOutlineDirDirective);

angular.module('materialscommons').component('mcProcessesWorkflowOutline', {
    templateUrl: 'app/project/experiments/experiment/components/workflow/mc-processes-workflow-outline.html',
    controller: MCProcessesWorkflowOutlineComponentController,
    bindings: {
        processes: '<',
        highlightProcesses: '<',
        dataset: '<'
    },
    require: {
        mcProcessesWorkflow: '^mcProcessesWorkflow'
    }
});
<|endoftext|>


<commit_before>// 命令相关的配置
const os = require('os');
const nodeStorage = require('../core/nodeStorage');
const emptySymbol = Symbol();

let currentOs = os.type(),
    currentShellName = '',
    proxyConfig = {};

function processExec() {
    const processExec = require('../core/process.exec'); // 循环调用
    return new processExec('config.commnad');
}

let commandConfig = {
    windows:{
        execShellNameSync(command){ // 同步执行前缀
            if (this.shellName === 'powershell') {
                return `powershell -Command "${command.replace(/([^`])"/g, "$1\"\"\"\"")}"`;
            }

            return  command;
        },
        get execShellName (){ // child_process.exec
            if (this.shellName === 'powershell') { // 在powershell中不输出目录
                return 'powershell -NoLogo -Command -';
            }

            return  'cmd';
        },
        checkPowerShell(){
            return proxyConfig.whereCommand('powershell', 'powerShellBool', str => !!str, false, false) !== 'powershell';
        },
        get shellName(){ // 当前执行的命令的环境
            if (!currentShellName){

                currentShellName = this.checkPowerShell() ? 'powershell' : 'cmd';
            }

            return currentShellName;
        },
        get headArr(){
            let data = [];
            if (currentShellName === 'cmd') {
                data = [
                    '@echo off \n',
                ];
            }else if (currentShellName === 'powershell'){
                data = [

                ];
            }

            return data;
        },
        clearRow: '',  // 在cmd中用于清除当前行并输入后面内容，即命令行的进度条。未知正确字符串，目前只能以乱码形式显示在ide中
        endRow: '',

        // 命令是否执行结束处理
        /**
         * @return {string}
         */
        ShellEOFCommand(eof){
            return `echo "${eof}" \n`;
        },

        enter: [ // 留意替换顺序
            "\n", // todo 换行
            "\r\n", // todo \r\n\r\n命令结束
        ],
        /**
         * 不是回车符
         *
         * @param str
         * @returns {boolean}
         */
        ifNotEnter(str){
            return !(str.length === 2 && this.enter.includes(str))
        },

        /**
         * 换行切换数组
         *
         * @param str
         * @returns {Array}
         */
        lineArr(str){
            return str.replace("\r\n\r\n", "").split(/\r?\n/) || [];
        },

        getNotEnter(str){
            return str.trim()
        }
    },

    replaceEnter(str, replace, num = null){
        num = (num === null ? this.enter.length : num) - 1;

        return this.enter[num] ? this.replaceEnter(str.replace(this.enter[num], replace), replace, num) : str;
    },

    openShell(command){
        let environment = proxyConfig.shellName;

        if (environment === 'cmd'){
            return `start cmd /k ${command}`;
        }else if(environment === 'git-bash'){
            // todo 待改为git的bash，记得有部分功能有用
            return `start ${dockerConfig.bashCommandPath} -s -c '${command} ;read'`;
        }else if(environment === 'powershell'){
            command = `echo '${command.replace(/(^`)'/g, "$1''")}';${command}`; // 输出执行命令
            command = command.replace(/([^`])"/g, "$1\"\"\"\"\"\""); // 转双引号需要三对双引号
            // return `start powershell -NoExit ${docker_compose} logs ${logsArgs} ${getContainerName()}`; // cmd下打开powershell
            return `start powershell -ArgumentList "-NoExit ${command}"`; // powershell打开powershell
        }
    },
    linux:{
    },

    /**
     * 查看command命令所在路径
     *
     * @param {string} command 命令名
     * @param {string} cacheKey 缓存
     * @param {callback} cacheFunc 缓存结果特殊处理
     * @param {bool} async 是否异步保存缓存
     * @param {bool} checkPowerShell 是否特殊处理执行绝对路径命令
     * @returns {string}
     */
    whereCommand(command, cacheKey = '', cacheFunc = null, async = false, checkPowerShell = true){ // where npm
        let str, check;
        if (cacheKey && (str = nodeStorage.getItem(cacheKey, emptySymbol)) !== emptySymbol){
            return str;
        }
        function disFunc(str) { // 处理返回的str
            if (str){
                str = proxyConfig.lineArr(str).shift();
                if (checkPowerShell && (check = proxyConfig.checkPowerShell) && check()) { // 是powershell，需&"C:\Program Files\Docker\Docker\Resources\bin\docker-compose.exe"
                    str = '&"' + str + '"';
                }else{
                    str = '"' + str + '"';
                }
            }

            if (cacheKey){ // 不考虑str
                str = typeof cacheFunc === 'function' ? cacheFunc(str) : str;
                nodeStorage.setItem(cacheKey, str);
            }

            return str;
        }

        if (async) { // 异步
            setTimeout(function () {
                processExec().exec(`where ${command}`, {encoding : 'utf8'}, function (error, stdout, stderr) {
                    if (error){
                        console.error(`where ${command} error: ${stderr}`);
                        return;
                    }

                    return disFunc(stdout);
                });
            }, 0);

            return async === true ? command : async;
        }

        return disFunc(processExec().execSync(`where ${command}`, {encoding : 'utf8'}));
    },
};

proxyConfig = new Proxy(commandConfig, {
    get:(target, key) => {
        if (!Reflect.has(target, key)){ // 顶层不存在即读取系统配置层
            if (/windows/i.test(currentOs)) { // todo 可能要改为win
                target = target['windows'];
            }else if(/Linux/i.test(currentOs)){
                target = target['linux'];
            }
        }

        return Reflect.has(target, key) ? target[key] : false;
    }
});

module.exports = proxyConfig;
<commit_msg>兼容mac<commit_after>// 命令相关的配置
const os = require('os');
const nodeStorage = require('../core/nodeStorage');
const emptySymbol = Symbol();

let currentOs = os.type(),
    isWin = /windows/i.test(currentOs),
    isMac = /Darwin/i.test(currentOs),
    isLinux = /Linux/i.test(currentOs),
    currentShellName = '',
    proxyConfig = {};

function processExec() {
    const processExec = require('../core/process.exec'); // 循环调用
    return new processExec('config.commnad');
}

let commandConfig = {
    windows:{
        whereCmd(command){
            return `where ${command}`;
        },

        execShellNameSync(command){ // 同步执行前缀
            if (this.shellName === 'powershell') {
                return `powershell -Command "${command.replace(/([^`])"/g, "$1\"\"\"\"")}"`;
            }

            return  command;
        },
        get execShellName (){ // child_process.exec
            if (this.shellName === 'powershell') { // 在powershell中不输出目录
                return 'powershell -NoLogo -Command -';
            }

            return  'cmd';
        },
        checkPowerShell(){
            return proxyConfig.whereCommand('powershell', 'powerShellBool', str => !!str, false, false) !== 'powershell';
        },
        get shellName(){ // 当前执行的命令的环境
            if (!currentShellName){

                currentShellName = this.checkPowerShell() ? 'powershell' : 'cmd';
            }

            return currentShellName;
        },
        get headArr(){
            let data = [];
            if (currentShellName === 'cmd') {
                data = [
                    '@echo off \n',
                ];
            }else if (currentShellName === 'powershell'){
                data = [

                ];
            }

            return data;
        },
        clearRow: '',  // 在cmd中用于清除当前行并输入后面内容，即命令行的进度条。未知正确字符串，目前只能以乱码形式显示在ide中
        endRow: '',

        // 命令是否执行结束处理
        /**
         * @return {string}
         */
        ShellEOFCommand(eof){
            return `echo "${eof}" \n`;
        },

        enter: [ // 留意替换顺序
            "\n", // todo 换行
            "\r\n", // todo \r\n\r\n命令结束
        ],
        /**
         * 不是回车符
         *
         * @param str
         * @returns {boolean}
         */
        ifNotEnter(str){
            return !(str.length === 2 && this.enter.includes(str))
        },

        /**
         * 换行切换数组
         *
         * @param str
         * @returns {Array}
         */
        lineArr(str){
            return str.replace("\r\n\r\n", "").split(/\r?\n/) || [];
        },

        getNotEnter(str){
            return str.trim()
        }
    },

    replaceEnter(str, replace, num = null){
        num = (num === null ? this.enter.length : num) - 1;

        return this.enter[num] ? this.replaceEnter(str.replace(this.enter[num], replace), replace, num) : str;
    },

    openShell(command){
        let environment = proxyConfig.shellName;

        if (environment === 'cmd'){
            return `start cmd /k ${command}`;
        }else if(environment === 'git-bash'){
            // todo 待改为git的bash，记得有部分功能有用
            return `start ${dockerConfig.bashCommandPath} -s -c '${command} ;read'`;
        }else if(environment === 'powershell'){
            command = `echo '${command.replace(/(^`)'/g, "$1''")}';${command}`; // 输出执行命令
            command = command.replace(/([^`])"/g, "$1\"\"\"\"\"\""); // 转双引号需要三对双引号
            // return `start powershell -NoExit ${docker_compose} logs ${logsArgs} ${getContainerName()}`; // cmd下打开powershell
            return `start powershell -ArgumentList "-NoExit ${command}"`; // powershell打开powershell
        }
    },
    linux:{
        whereCmd(command){
            return `which ${command}`;
        },
    },
    mac:{
        whereCmd(command){
            return `which ${command}`;
        },
    },

    /**
     * 查看command命令所在路径
     *
     * @param {string} command 命令名
     * @param {string} cacheKey 缓存
     * @param {callback} cacheFunc 缓存结果特殊处理
     * @param {bool} async 是否异步保存缓存
     * @param {bool} checkPowerShell 是否特殊处理执行绝对路径命令
     * @returns {string}
     */
    whereCommand(command, cacheKey = '', cacheFunc = null, async = false, checkPowerShell = true){ // where npm
        let str, check;
        if (cacheKey && (str = nodeStorage.getItem(cacheKey, emptySymbol)) !== emptySymbol){
            return str;
        }
        function disFunc(str) { // 处理返回的str
            if (str){
                str = proxyConfig.lineArr(str).shift();
                if (checkPowerShell && (check = proxyConfig.checkPowerShell) && check()) { // 是powershell，需&"C:\Program Files\Docker\Docker\Resources\bin\docker-compose.exe"
                    str = '&"' + str + '"';
                }else{ // mac和linux也可以用引号包裹路径
                    str = '"' + str + '"';
                }
            }

            if (cacheKey){ // 不考虑str
                str = typeof cacheFunc === 'function' ? cacheFunc(str) : str;
                nodeStorage.setItem(cacheKey, str);
            }

            return str;
        }

        if (async) { // 异步
            setTimeout(function () {
                processExec().exec(proxyConfig.whereCmd(command), {encoding : 'utf8'}, function (error, stdout, stderr) {
                    if (error){
                        console.error(`whereCmd ${command} error: ${stderr}`);
                        return;
                    }

                    return disFunc(stdout);
                });
            }, 0);

            return async === true ? command : async;
        }

        return disFunc(processExec().execSync(proxyConfig.whereCmd(command), {encoding : 'utf8'}));
    },
};

proxyConfig = new Proxy(commandConfig, {
    get:(target, key) => {
        if (!Reflect.has(target, key)){ // 顶层不存在即读取系统配置层
            if (isWin) { // todo 可能要改为win
                target = target['windows'];
            } else if(isLinux){
                target = target['linux'];
            } else if (isMac){
                target = target['mac'];
            }
        }

        return Reflect.has(target, key) ? target[key] : false;
    }
});

module.exports = proxyConfig;
<|endoftext|>


<commit_before>var models = require("../../../models");
var _ = require('lodash');

var getCommonWhereDateOptions = function(options) {
  var where = {};
  var dateBefore, dateAfter;

  var dateAtBase = {};

  if (options.beforeFilter && options.afterFilter) {
    dateAtBase[options.dateColumn] = {
      $or: {
        $lt: options.beforeFilter,
        $gt: options.afterFilter
      }
    };
  } else if (options.beforeFilter) {
    dateAtBase[options.dateColumn] = {
      $lt: options.beforeFilter
    };
  } else if (options.afterFilter) {
    dateAtBase[options.dateColumn] = {
      $gt: options.afterFilter
    };
  } else if (options.beforeOrEqualFilter && options.afterOrEqualFilter) {
    dateAtBase[options.dateColumn] = {
      $and: {
        $lte: options.beforeOrEqualFilter,
        $gte: options.afterOrEqualFilter
      }
    };
  } else if (options.beforeOrEqualFilter) {
    dateAtBase[options.dateColumn] = {
      $lte: options.beforeOrEqualFilter
    };
  } else if (options.afterOrEqualFilter) {
    dateAtBase[options.dateColumn] = {
      $gte: options.afterOrEqualFilter
    };
  }

  if (!options.afterDate && !options.beforeDate) {
    _.merge(where, dateAtBase)
  } else if (JSON.stringify(dateAtBase) == JSON.stringify({})) {
    if (options.beforeDate) {
      beforeDate = {};

      beforeDate[options.dateColumn] = {
        $lt: options.beforeDate
      };

      _.merge(where, beforeDate);
    } else if (options.afterDate) {
      afterDate = {};

      afterDate[options.dateColumn] = {
        $gt: options.afterDate
      };

      _.merge(where, afterDate);
    }
  } else {
    if (options.beforeDate) {
      beforeDate = {};

      beforeDate[options.dateColumn] = {
        $lt: options.beforeDate
      };

      _.merge(where, {
        $and: [
          beforeDate,
          dateAtBase
        ]
      });
    } else if (options.afterDate) {
      afterDate = {};

      afterDate[options.dateColumn] = {
        $gt: options.afterDate
      };

      _.merge(where, {
        $and: [
          afterDate,
          dateAtBase
        ]
      });
    }
  }

  return where;
};

var getCommonWhereOptions = function(options) {
  var where = {
    status: 'active'
  };

  if (options.user_id) {
    where = _.merge(where, {
      user_id: options.user_id
    })
  }

  if (options.type) {
    where = _.merge(where, {
      type: options.type
    })
  }

  if (options.domain_id) {
    where = _.merge(where, {
      domain_id: options.domain_id
    })
  }

  if (options.community_id) {
    where = _.merge(where, {
      community_id: options.community_id
    })
  }

  if (options.group_id) {
    where = _.merge(where, {
      group_id: options.group_id
    })
  }

  if (options.post_id) {
    where = _.merge(where, {
      post_id: options.post_id
    })
  }

  return _.merge(where, getCommonWhereDateOptions(options));
};

var getModelDate = function(model, options, callback) {
  var where = getCommonWhereOptions(options);
  var dateColumn = options.dateColumn;
  if (model == models.AcActivity) {
    delete where.user_id;
    where = _.merge(where, {
      type: {
        $in: defaultKeyActivities
      }
    });
  }

  model.find({
    where: where,
    attributes: [dateColumn],
    order: [
      [ dateColumn, options.oldest ? 'asc' : 'desc' ]
    ]
  }).then(function (item) {
    if (item) {
      var date = item.getDataValue(dateColumn);
      callback(null, date);
    } else {
      callback();
    }
  }).catch(function (error) {
    callback(error);
  });
};

var getNewsFeedDate = function(options, type, callback) {
  getModelDate(models.AcNewsFeedItem, _.merge(options, {
    dateColumn: 'latest_activity_at',
    type: type
  }), callback)
};

var getActivityDate = function(options, callback) {
  getModelDate(models.AcActivity, _.merge(options, {
    dateColumn: 'created_at'
  }), callback)
};

var getProcessedRange = function(options, callback) {
  var where = getCommonWhereOptions(_.merge(options, {dateColumn: 'latest_activity_at'}));

  var order;
  if (options.oldest) {
    order = [['latest_activity_at', 'ASC']]
  } else {
    order = [['latest_activity_at', 'DESC']]
  }

  models.AcNewsFeedProcessedRange.find({
    where: where,
    attributes: ['latest_activity_at', 'oldest_activity_at'],
    order: [
      [ 'latest_activity_at', options.oldest ? 'asc' : 'desc' ]
    ]
  }).then(function (item) {
    if (item) {
      callback(null, item);
    } else {
      callback();
    }
  }).catch(function (error) {
    callback(error);
  });
};

var activitiesDefaultIncludes = function (options) {
  var community;
  var group;

  if (options.domain_id) {
    community = {
      model: models.Community,
      required: false,
      where: { access: models.Community.ACCESS_PUBLIC }
    };

    group = {
      model: models.Group,
      required: true,
      where: { access: models.Group.ACCESS_PUBLIC }
    }
  } else if (options.community_id) {
    community = {
      model: models.Community,
      required: false
    };

    group = {
      model: models.Group,
      required: true,
      where: { access: models.Group.ACCESS_PUBLIC }
    }
  } else {
    community = {
      model: models.Community,
      required: false
    };

    group = {
      model: models.Group,
      required: false
    }
  }

  return [
    {
      model: models.User,
      required: true
    },
    {
      model: models.Domain,
      required: true
    },
    community,
    group,
    {
      model: models.Post,
      required: false,
      include: [
        {
          // Category
          model: models.Category,
          required: false,
          include: [
            {
              model: models.Image,
              required: false,
              as: 'CategoryIconImages'
            }
          ]
        }
      ]
    },
    {
      model: models.Point,
      required: false,
      include: [
        {
          model: models.PointRevision,
          include: [
            {
              model: models.User,
              required: false
            }
          ],
          required: false
        }
      ]
    },
    {
      model: models.PostStatusChange,
      required: false
    }
  ]
};

  // Example query 1
  //  Get latest
    // If newer activities than latest_processed_range
      // Load latest notification news feed items with created_at $gt oldest_activity being processed
      // Generate items from activities newer than latest_processed_range_start or Max 30
      // Create processed_range

  // Get more
    // If activities older than last viewed and newer than last_processed_at (older than last viewed also)
      // Generate Items
      // Load latest notification news feed items with created_at $gt oldest_activity being processed
      // Create processed_range
    // Else load all items in the time range next processed range (older than last viewed)
  //  Get new updated
    // If newer activities than latest_processed_range and newer than last viewed
      // Generate items from activities newer than latest_processed_range_start and newer than the last viewed or Max 30
      // Load latest notification news feed items with created_at $gt oldest_activity being processed
      // Create processed_range
  // Else if processed_range newer than last viewed
    // load all items in the time range
  // Else if notification generated items newer than the last viewed
    // Deliver items

  // If I request older items by scrolling down

  //  AcNewsFeed Options
  //    Limit 30
  //  AcActivities
  //    modified_at $gt latest_dynamically_generated_processed_news_feed_ac_activity_modified_at
  //    modified_at $lt oldest_dynamically_generated_processed_news_feed_ac_activity_modified_at

  // Example query 2
  //  Get latest since last
  //  AcNewsFeed Options
  //    modified_at $gt latest_news_feed_item_at
  //  AcActivities
  //    $and
  //      A
  //        modified_at $gt latest_news_feed_item_at
  //      B
  //        modified_at $gt last_dynamically_generated_processed_news_feed_ac_activity_modified_at
  //        modified_at $lt first_dynamically_generated_processed_news_feed_ac_activity_modified_at

  // Example query 3
  // Get older since last shown item
  //  AcNewsFeed Options
  //    modified_at $lt last_shown_news_feed_item_at
  //  AcActivities
  //   $and
  //    A
  //      modified_at $lt last_shown_news_feed_item_at
  //    B
  //      modified_at $gt last_dynamically_generated_processed_news_feed_ac_activity_modified_at
  //      modified_at $lt first_dynamically_generated_processed_news_feed_ac_activity_modified_at

//defaultKeyActivities = ['activity.post.status.update','activity.post.officialStatus.successful',
//  'activity.point.new','activity.post.new','activity.post.officialStatus.failed',
//  'activity.post.officialStatus.inProgress'];

defaultKeyActivities = ['activity.post.status.update','activity.point.new','activity.post.new'];

module.exports = {
  activitiesDefaultIncludes: activitiesDefaultIncludes,
  getCommonWhereOptions: getCommonWhereOptions,
  defaultKeyActivities: defaultKeyActivities,
  getActivityDate: getActivityDate,
  getProcessedRange: getProcessedRange
};<commit_msg>Include images with post<commit_after>var models = require("../../../models");
var _ = require('lodash');

var getCommonWhereDateOptions = function(options) {
  var where = {};
  var dateBefore, dateAfter;

  var dateAtBase = {};

  if (options.beforeFilter && options.afterFilter) {
    dateAtBase[options.dateColumn] = {
      $or: {
        $lt: options.beforeFilter,
        $gt: options.afterFilter
      }
    };
  } else if (options.beforeFilter) {
    dateAtBase[options.dateColumn] = {
      $lt: options.beforeFilter
    };
  } else if (options.afterFilter) {
    dateAtBase[options.dateColumn] = {
      $gt: options.afterFilter
    };
  } else if (options.beforeOrEqualFilter && options.afterOrEqualFilter) {
    dateAtBase[options.dateColumn] = {
      $and: {
        $lte: options.beforeOrEqualFilter,
        $gte: options.afterOrEqualFilter
      }
    };
  } else if (options.beforeOrEqualFilter) {
    dateAtBase[options.dateColumn] = {
      $lte: options.beforeOrEqualFilter
    };
  } else if (options.afterOrEqualFilter) {
    dateAtBase[options.dateColumn] = {
      $gte: options.afterOrEqualFilter
    };
  }

  if (!options.afterDate && !options.beforeDate) {
    _.merge(where, dateAtBase)
  } else if (JSON.stringify(dateAtBase) == JSON.stringify({})) {
    if (options.beforeDate) {
      beforeDate = {};

      beforeDate[options.dateColumn] = {
        $lt: options.beforeDate
      };

      _.merge(where, beforeDate);
    } else if (options.afterDate) {
      afterDate = {};

      afterDate[options.dateColumn] = {
        $gt: options.afterDate
      };

      _.merge(where, afterDate);
    }
  } else {
    if (options.beforeDate) {
      beforeDate = {};

      beforeDate[options.dateColumn] = {
        $lt: options.beforeDate
      };

      _.merge(where, {
        $and: [
          beforeDate,
          dateAtBase
        ]
      });
    } else if (options.afterDate) {
      afterDate = {};

      afterDate[options.dateColumn] = {
        $gt: options.afterDate
      };

      _.merge(where, {
        $and: [
          afterDate,
          dateAtBase
        ]
      });
    }
  }

  return where;
};

var getCommonWhereOptions = function(options) {
  var where = {
    status: 'active'
  };

  if (options.user_id) {
    where = _.merge(where, {
      user_id: options.user_id
    })
  }

  if (options.type) {
    where = _.merge(where, {
      type: options.type
    })
  }

  if (options.domain_id) {
    where = _.merge(where, {
      domain_id: options.domain_id
    })
  }

  if (options.community_id) {
    where = _.merge(where, {
      community_id: options.community_id
    })
  }

  if (options.group_id) {
    where = _.merge(where, {
      group_id: options.group_id
    })
  }

  if (options.post_id) {
    where = _.merge(where, {
      post_id: options.post_id
    })
  }

  return _.merge(where, getCommonWhereDateOptions(options));
};

var getModelDate = function(model, options, callback) {
  var where = getCommonWhereOptions(options);
  var dateColumn = options.dateColumn;
  if (model == models.AcActivity) {
    delete where.user_id;
    where = _.merge(where, {
      type: {
        $in: defaultKeyActivities
      }
    });
  }

  model.find({
    where: where,
    attributes: [dateColumn],
    order: [
      [ dateColumn, options.oldest ? 'asc' : 'desc' ]
    ]
  }).then(function (item) {
    if (item) {
      var date = item.getDataValue(dateColumn);
      callback(null, date);
    } else {
      callback();
    }
  }).catch(function (error) {
    callback(error);
  });
};

var getNewsFeedDate = function(options, type, callback) {
  getModelDate(models.AcNewsFeedItem, _.merge(options, {
    dateColumn: 'latest_activity_at',
    type: type
  }), callback)
};

var getActivityDate = function(options, callback) {
  getModelDate(models.AcActivity, _.merge(options, {
    dateColumn: 'created_at'
  }), callback)
};

var getProcessedRange = function(options, callback) {
  var where = getCommonWhereOptions(_.merge(options, {dateColumn: 'latest_activity_at'}));

  var order;
  if (options.oldest) {
    order = [['latest_activity_at', 'ASC']]
  } else {
    order = [['latest_activity_at', 'DESC']]
  }

  models.AcNewsFeedProcessedRange.find({
    where: where,
    attributes: ['latest_activity_at', 'oldest_activity_at'],
    order: [
      [ 'latest_activity_at', options.oldest ? 'asc' : 'desc' ]
    ]
  }).then(function (item) {
    if (item) {
      callback(null, item);
    } else {
      callback();
    }
  }).catch(function (error) {
    callback(error);
  });
};

var activitiesDefaultIncludes = function (options) {
  var community;
  var group;

  if (options.domain_id) {
    community = {
      model: models.Community,
      required: false,
      where: { access: models.Community.ACCESS_PUBLIC }
    };

    group = {
      model: models.Group,
      required: true,
      where: { access: models.Group.ACCESS_PUBLIC }
    }
  } else if (options.community_id) {
    community = {
      model: models.Community,
      required: false
    };

    group = {
      model: models.Group,
      required: true,
      where: { access: models.Group.ACCESS_PUBLIC }
    }
  } else {
    community = {
      model: models.Community,
      required: false
    };

    group = {
      model: models.Group,
      required: false
    }
  }

  return [
    {
      model: models.User,
      required: true
    },
    {
      model: models.Domain,
      required: true
    },
    community,
    group,
    {
      model: models.Post,
      required: false,
      include: [
        { model: models.Image,
          as: 'PostHeaderImages',
          required: false
        },
        {
          // Category
          model: models.Category,
          required: false,
          include: [
            {
              model: models.Image,
              required: false,
              as: 'CategoryIconImages'
            }
          ]
        }
      ]
    },
    {
      model: models.Point,
      required: false,
      include: [
        {
          model: models.PointRevision,
          include: [
            {
              model: models.User,
              required: false
            }
          ],
          required: false
        }
      ]
    },
    {
      model: models.PostStatusChange,
      required: false
    }
  ]
};

  // Example query 1
  //  Get latest
    // If newer activities than latest_processed_range
      // Load latest notification news feed items with created_at $gt oldest_activity being processed
      // Generate items from activities newer than latest_processed_range_start or Max 30
      // Create processed_range

  // Get more
    // If activities older than last viewed and newer than last_processed_at (older than last viewed also)
      // Generate Items
      // Load latest notification news feed items with created_at $gt oldest_activity being processed
      // Create processed_range
    // Else load all items in the time range next processed range (older than last viewed)
  //  Get new updated
    // If newer activities than latest_processed_range and newer than last viewed
      // Generate items from activities newer than latest_processed_range_start and newer than the last viewed or Max 30
      // Load latest notification news feed items with created_at $gt oldest_activity being processed
      // Create processed_range
  // Else if processed_range newer than last viewed
    // load all items in the time range
  // Else if notification generated items newer than the last viewed
    // Deliver items

  // If I request older items by scrolling down

  //  AcNewsFeed Options
  //    Limit 30
  //  AcActivities
  //    modified_at $gt latest_dynamically_generated_processed_news_feed_ac_activity_modified_at
  //    modified_at $lt oldest_dynamically_generated_processed_news_feed_ac_activity_modified_at

  // Example query 2
  //  Get latest since last
  //  AcNewsFeed Options
  //    modified_at $gt latest_news_feed_item_at
  //  AcActivities
  //    $and
  //      A
  //        modified_at $gt latest_news_feed_item_at
  //      B
  //        modified_at $gt last_dynamically_generated_processed_news_feed_ac_activity_modified_at
  //        modified_at $lt first_dynamically_generated_processed_news_feed_ac_activity_modified_at

  // Example query 3
  // Get older since last shown item
  //  AcNewsFeed Options
  //    modified_at $lt last_shown_news_feed_item_at
  //  AcActivities
  //   $and
  //    A
  //      modified_at $lt last_shown_news_feed_item_at
  //    B
  //      modified_at $gt last_dynamically_generated_processed_news_feed_ac_activity_modified_at
  //      modified_at $lt first_dynamically_generated_processed_news_feed_ac_activity_modified_at

//defaultKeyActivities = ['activity.post.status.update','activity.post.officialStatus.successful',
//  'activity.point.new','activity.post.new','activity.post.officialStatus.failed',
//  'activity.post.officialStatus.inProgress'];

defaultKeyActivities = ['activity.post.status.update','activity.point.new','activity.post.new'];

module.exports = {
  activitiesDefaultIncludes: activitiesDefaultIncludes,
  getCommonWhereOptions: getCommonWhereOptions,
  defaultKeyActivities: defaultKeyActivities,
  getActivityDate: getActivityDate,
  getProcessedRange: getProcessedRange
};<|endoftext|>


<commit_before>import {
    ValidationRenderer,
    RenderInstruction,
    ValidateResult
} from 'aurelia-validation';

export class BootstrapFormRenderer {
    render(instruction: RenderInstruction) {
        this.update(instruction.unrender, this.remove);
        this.update(instruction.render, this.add);
    }

    update(instruction, action) {
        for (let { result, elements} of instruction) {
            elements.forEach(e => action(e, result));
        }
    }

    add(element: Element, result: ValidateResult) {
        if (result.valid) {
            return;
        }

        const formGroup = element.closest('.form-group');
        if (!formGroup) {
            return;
        }

        // add the has-error class to the enclosing form-group div
        formGroup.classList.add('has-error');

        // add help-block
        const message = document.createElement('span');
        message.className = 'help-block validation-message';
        message.textContent = result.message;
        message.id = `validation-message-${result.id}`;
        formGroup.appendChild(message);
    }

    remove(element: Element, result: ValidateResult) {
        if (result.valid) {
            return;
        }

        const formGroup = element.closest('.form-group');
        if (!formGroup) {
            return;
        }

        // remove help-block
        const message = formGroup.querySelector(`#validation-message-${result.id}`);
        if (message) {
            formGroup.removeChild(message);

            // remove the has-error class from the enclosing form-group div
            if (formGroup.querySelectorAll('.help-block.validation-message').length === 0) {
                formGroup.classList.remove('has-error');
            }
        }
    }
}<commit_msg>- fixed error message positioning<commit_after>import {
    ValidationRenderer,
    RenderInstruction,
    ValidateResult
} from 'aurelia-validation';

export class BootstrapFormRenderer {
    render(instruction: RenderInstruction) {
        this.update(instruction.unrender, this.remove);
        this.update(instruction.render, this.add);
    }

    update(instruction, action) {
        for (let { result, elements} of instruction) {
            elements.forEach(e => action(e, result));
        }
    }

    add(element: Element, result: ValidateResult) {
        if (result.valid) {
            return;
        }

        const formGroup = element.closest('.form-group');
        if (!formGroup) {
            return;
        }

        // add the has-error class to the enclosing form-group div
        formGroup.classList.add('has-error');

        // add help-block
        const message = document.createElement('span');
        message.className = 'help-block validation-message col-xs-9 col-xs-offset-3';
        message.textContent = result.message;
        message.id = `validation-message-${result.id}`;
        formGroup.appendChild(message);
    }

    remove(element: Element, result: ValidateResult) {
        if (result.valid) {
            return;
        }

        const formGroup = element.closest('.form-group');
        if (!formGroup) {
            return;
        }

        // remove help-block
        const message = formGroup.querySelector(`#validation-message-${result.id}`);
        if (message) {
            formGroup.removeChild(message);

            // remove the has-error class from the enclosing form-group div
            if (formGroup.querySelectorAll('.help-block.validation-message').length === 0) {
                formGroup.classList.remove('has-error');
            }
        }
    }
}<|endoftext|>


<commit_before>var QUIZ_FEEDBACK = {
    CORRECT: "☺",
    WRONG: "☹"
}

module.exports = {
    QUIZ_FEEDBACK: QUIZ_FEEDBACK
};<commit_msg>Quiz Feedback: Changed smileys.<commit_after>var QUIZ_FEEDBACK = {
    CORRECT: "😄",
    WRONG: "😑"
}

module.exports = {
    QUIZ_FEEDBACK: QUIZ_FEEDBACK
};<|endoftext|>