<commit_before>import Morph from 'src/components/widgets/lively-morph.js';
import { uuid, without, getTempKeyFor, getObjectFor, flatMap } from 'utils';
import VivideObject from 'src/client/vivide/vivideobject.js';
import Script from 'src/client/vivide/vividescript.js';

/**
 * Smart widget choosing
 */
class WidgetChooser {
  static getPreferredWidgetType(forest, viewConfig) {
    if (viewConfig.has('widget')) {
      return viewConfig.get('widget');
    }

    // #TODO: this is too dependent on internal structure of the model/VivideObject
    // PROPOSAL: Models should not know about views, therefore they cannot return
    //   a suggested view, but they could return a data type suggestion like:
    //     model.dataType == "data-points" || "list" || "text"
    //   Additionally, this data type could be set manually or via an "intelligent"
    //   algorithm.
    if (forest && forest.length > 0) {
      // #Question: this model has an objects array, what is the data structure of this model?
      const model = forest[0];

      if(model.properties.has('dataPoints') && typeof model.properties.get('dataPoints')[0] === 'number') {
        return 'boxplot';
      } else if (model.properties.has('text')) {
        return 'text';
      }
    }
    return 'tree';
  }
  static findAppropriateWidget(forest, viewConfig) {
    const type = this.getPreferredWidgetType(forest, viewConfig);

    // full type specified
    if(type.includes('-')) {
      return type;
    }

    // shorthand notation used
    return `vivide-${type}-widget`;
  }
}

export default class VivideView extends Morph {
  static findViewWithId(id, context=document.body) {
    return lively.query(context, `vivide-view[vivide-view-id=${id}]`);
  }

  static getIdForView(view) {
    return view.id;
  }

  static get idAttribute() { return 'vivide-view-id'; }

  static get outportAttribute() { return 'outport-target'; }

  static get scriptAttribute() { return 'vivide-script'; }

  static get widgetId() { return 'widget'; }

  static get widgetSelector() { return '#' + this.widgetId; }

  static forestToData(model) {
    return model.map(m => m.object);
  }

  // unused?
  static dataToForest(data) {
    return data.map(d => new VivideObject(d));
  }

  get widget() { return this.get(VivideView.widgetSelector); }

  get input() { return this._input || (this._input = []); }
  set input(val) { return this._input = val; }

  get id() {
    let id = this.getAttribute(VivideView.idAttribute);
    if(id) { return id; }

    // ensure uuid begins with a letter to match the requirements for a css selector
    let newId = 'vivide-view-' + uuid();
    this.setAttribute(VivideView.idAttribute, newId);
    return newId;
  }

  get outportTargets() {
    let ids = this.getJSONAttribute(VivideView.outportAttribute);
    if(ids) {
      return flatMap.call(ids, id => {
        let view = VivideView.findViewWithId(id, lively.findWorldContext(this)) ;
        if(view === null) {
          lively.error('could not find view: ' + id);
          return [];
        }
        return [view];
      });
    }

    return this.outportTargets = [];
  }

  set outportTargets(targets) {
    return this.setJSONAttribute(
      VivideView.outportAttribute,
      targets.map(VivideView.getIdForView)
    );
  }

  addOutportTarget(target) {
    return this.outportTargets = this.outportTargets.concat(target);
  }

  removeOutportTarget(target) {
    return this.outportTargets = without.call(this.outportTargets, target);
  }

  get inportSources() {
    return Array.from(document.body.querySelectorAll(`vivide-view[${VivideView.outportAttribute}*=${this.id}]`));
  }

  get targetHull() {
    let hull = new Set();

    function addToHull(view) {
      if(view && !hull.has(view)) {
        hull.add(view);
        view.outportTargets.forEach(addToHull);
      }
    }
    addToHull(this);

    return Array.from(hull);
  }

  connectTo(target) {
    // #TODO: cycle detection, here?
    this.addOutportTarget(target);
  }
  removeConnectionTo(target) {
    this.removeOutportTarget(target);
  }

  transmitDataToOutportTargets(dataToTransmit) {
    this.outportTargets.forEach(target => target.newDataFromUpstream(dataToTransmit));
  }
  reallyNotifyOutportTargets(stuffToTransmit) {
    this.transmitDataToOutportTargets(VivideView.forestToData(stuffToTransmit));
  }
  notifyOutportTargets() {
    lively.warn('VIEW::NOTIFY2', this.forestToDisplay[0])
    this.reallyNotifyOutportTargets(this.forestToDisplay);
  }

  updateOutportTargets() {
    const dataToTransmit = this.getDataToTransmit();
    if(dataToTransmit) {
      lively.warn('VIEW::UPDATE', dataToTransmit[0])
      this.transmitDataToOutportTargets(dataToTransmit);
    }
  }

  getDataToTransmit() {
      lively.warn('display all data')
    if(this.widget && this.widget.multiSelectionEnabled) {
      return this.getSelectedData();
    } else if (this.widget && this.widget.localName === 'vivide-text-widget') {
      return this.getSelectedData();
    } else {
      // use all data
      return VivideObject.forestToData(this.forestToDisplay);
    }
  }

  getSelectedData() {
    const widget = this.widget;
    if(widget) {
      return widget.getSelectedData();
    }
    return undefined;
  }

  selectionChanged() {
    this.updateOutportTargets();
  }

  addDragInfoTo(evt) {
    const dt = evt.dataTransfer;
    // #TODO: An improved fix would be to change what is returned by the widget selection
    let selection = this.getDataToTransmit();
    if(selection) {
      lively.warn('VivideView::addDragInfoTo', selection[0])
      dt.setData("javascript/object", getTempKeyFor(selection));
    } else {
      lively.error('could not add drag data');
    }

    dt.setData("vivide", "");
    dt.setData("vivide/source-view", getTempKeyFor(this));
  }

  async initialize() {
    this.windowTitle = "VivideView";
    this.addEventListener('extent-changed', evt => this.onExtentChanged(evt));

    this.addEventListener('dragenter', evt => this.dragenter(evt), false);
    this.addEventListener('dragover', evt => this.dragover(evt), false);
    this.addEventListener('dragleave', evt => this.dragleave(evt), false);
    this.addEventListener('drop', evt => this.drop(evt), false);

    // restore data...

    // not migrating, but initializing from source
    if (!this.myCurrentScript) {
      var scriptJSON = this.getAttribute("vivide-script")
      if (scriptJSON) {
        this.myCurrentScript = await Script.fromJSON(scriptJSON, this)
      } else {
        //This got commented because it is not reliable. It will often overwrite or take longer to setup and overwrite later on
        // await this.initDefaultScript();
      }

      var dataJSON = this.getAttribute("vivide-data")
      if (dataJSON) {
        // await this.createScriptEditor();
        var data = JSON.parse(dataJSON)
        this.newDataFromUpstream(data)
      }
    }
  }

  onExtentChanged() {
    this.childNodes.forEach(childNode => {
      if(childNode.dispatchEvent) {
        childNode.dispatchEvent(new CustomEvent("extent-changed"));
      }
    })
  }

  dragenter(evt) {}
  dragover(evt) {
    evt.preventDefault();

    this._resetDropOverEffects();
    this.classList.add('over');

    const dt = evt.dataTransfer;

    let hasSourceView = dt.types.includes("vivide") && dt.types.includes("vivide/source-view");
    if(hasSourceView) {
      // unfortunately, we cannot check for a circular dependency here,
      // because we cannot get data from the dataTransfer outside dragStart and drop
      // see: https://stackoverflow.com/a/31922258/1152174
      this.classList.add('accept-drop');
      dt.dropEffect = "link";

      return;
    }

    let hasData = dt.types.includes("javascript/object");
    if(hasData) {
      this.classList.add('accept-drop');
      dt.dropEffect = "copy";

      return;
    }

    this.classList.add('reject-drop');
  }
  dragleave(evt) {
    this._resetDropOverEffects();
  }
  _resetDropOverEffects() {
    this.classList.remove('over');
    this.classList.remove('reject-drop');
    this.classList.remove('accept-drop');
  }
  drop(evt) {
    this._resetDropOverEffects();

    let shouldPreventPropagation = false;

    const dt = evt.dataTransfer;
    if (dt.types.includes("javascript/object")) {
      lively.success('drop data');

      const data = getObjectFor(dt.getData("javascript/object"));
      this.newDataFromUpstream(data);

      shouldPreventPropagation = true;
    }

    if (dt.types.includes("vivide") && dt.types.includes("vivide/source-view")) {
      lively.success('drop vivide');

      const sourceView = getObjectFor(dt.getData("vivide/source-view"));

      if(this.targetHull.includes(sourceView)) {
        lively.warn('cannot connect views', 'preventing cyclic dependencies')
      } else {
        sourceView.connectTo(this);
      }

      shouldPreventPropagation = true;
    }

    if(shouldPreventPropagation) {
      evt.stopPropagation();
    }
  }

  get myCurrentScript() { return this._myCurrentScript; }
  set myCurrentScript(script) {
    this._myCurrentScript = script;
    this.setAttribute(VivideView.scriptAttribute, script.toJSON());
  }

  async initDefaultScript() {
    const script = await Script.createDefaultScript(this);
    this.myCurrentScript = script;
    return script;
    // this.setJSONAttribute(VivideView.scriptAttribute, this.myCurrentScript.toJSON());
  }

  async newDataFromUpstream(data) {
    this.input = data;

    await this.calculateOutputModel();
    await this.updateWidget();
    this.updateOutportTargets();
  }

  getInputData() {
    return this.input;
  }

  async calculateOutputModel() {
    const firstStep = this.myCurrentScript.getInitialStep();
    const data = this.input.slice(0);

    this.forestToDisplay = await firstStep.processData(data);
  }

  // #TODO: nearly a duplicate with newDataFromUpstream; remove duplication
  async scriptGotUpdated() {
    // #TODO: save script to web-component
    // #TODO: later support multiple profiles
    lively.notify('VivideView::scriptGotUpdated');
    await this.calculateOutputModel();
    await this.updateWidget();
    // Update outport views
    // #TODO: nearly a duplicate with notifyOutportTargets, remove duplication
    this.updateOutportTargets();
  }

  async updateWidget() {
    this.innerHTML = '';

    const viewConfig = await this.myCurrentScript.getViewConfig();
    const chosenWidgetType = WidgetChooser.findAppropriateWidget(this.forestToDisplay, viewConfig);

    const widget = await lively.create(chosenWidgetType);
    widget.setView(this);
    widget.setAttribute('id', VivideView.widgetId);
    this.appendChild(widget);
    await widget.display(this.forestToDisplay, viewConfig);
  }

  async createScriptEditor() {
    const viewWindow = lively.findWindow(this);
    const reference = viewWindow && viewWindow.tagName === "LIVELY-WINDOW" ?
        viewWindow : this;
    const pos = lively.getGlobalBounds(reference).topRight();

    const scriptEditor = await lively.openComponentInWindow('vivide-script-editor', pos);
    scriptEditor.setView(this);
    // #TODO: only do setView with this as argument, the following line should not be required
    await scriptEditor.setScript(this.myCurrentScript);
    return scriptEditor;
  }

  livelyPrepareSave() {
    this.setAttribute("vivide-script", this.myCurrentScript.toJSON());
    try {
      var json = JSON.stringify(this.getInputData())
      this.setAttribute("vivide-data", json)
    } catch(e) {
      console.warn("could not persist vivide data", this.getInputData() )
    }
  }

  async livelyExample() {
    const exampleData = [
      {
        name: "object",
        subclasses:[
          {name: "morph"}
        ]},
      {
        name: "list",
        subclasses:[
          {
            name: "linkedlist",
            subclasses:[
              {name: "stack"}
            ]},
          {
            name: "arraylist"
          }]
      },
      {
        name: "usercontrol",
        subclasses:[
          {name: "textbox"},
          {name: "button"},
          {name: "label"}
        ]},
    ];

    await this.initDefaultScript();
    await this.createScriptEditor();
    await this.newDataFromUpstream(exampleData);
  }

  livelyMigrate(other) {
    lively.notify('VivideView::migrate')
    this.myCurrentScript = other.myCurrentScript;
    this.myCurrentScript.setView(this);

    this.newDataFromUpstream(other.input);
  }

  livelyInspect(contentNode, inspector) {
    if (this.myCurrentScript) {
      contentNode.appendChild(inspector.display(this.myCurrentScript, false, "#script", this));
    }
    if (this.input) {
      contentNode.appendChild(inspector.display(this.input, false, "#input", this));
    }

    var outportTargets = this.outportTargets
    if (outportTargets) {
      contentNode.appendChild(inspector.display(outportTargets, false, "#outportTargets", this));
    }

  }

  livelyHalo() {
    return {
      configureHalo(halo) {
        halo.get('#default-items').style.display = 'none';
        halo.get('#vivide-items').style.display = 'flex';

        // dynamically create outport connection visualizations
        const outportContainer = halo.get('#vivide-outport-connection-items');
        this.outportTargets.forEach(target => {
          const item = document.createElement('lively-halo-vivide-outport-connection-item')
          item.classList.add('halo');
          item.setTarget(target);
          outportContainer.appendChild(item);
        });

        const inportContainer = halo.get('#vivide-inport-connection-items');
        this.inportSources.forEach(source => {
          const item = document.createElement('lively-halo-vivide-inport-connection-item')
          item.classList.add('halo');
          item.setSource(source);
          inportContainer.appendChild(item);
        });
      }
    };
  }
}
<commit_msg>AUTO-COMMIT src/client/vivide/components/vivide-view.js<commit_after>import Morph from 'src/components/widgets/lively-morph.js';
import { uuid, without, getTempKeyFor, getObjectFor, flatMap } from 'utils';
import VivideObject from 'src/client/vivide/vivideobject.js';
import Script from 'src/client/vivide/vividescript.js';

/**
 * Smart widget choosing
 */
class WidgetChooser {
  static getPreferredWidgetType(forest, viewConfig) {
    if (viewConfig.has('widget')) {
      return viewConfig.get('widget');
    }

    // #TODO: this is too dependent on internal structure of the model/VivideObject
    // PROPOSAL: Models should not know about views, therefore they cannot return
    //   a suggested view, but they could return a data type suggestion like:
    //     model.dataType == "data-points" || "list" || "text"
    //   Additionally, this data type could be set manually or via an "intelligent"
    //   algorithm.
    if (forest && forest.length > 0) {
      // #Question: this model has an objects array, what is the data structure of this model?
      const model = forest[0];

      if(model.properties.has('dataPoints') && typeof model.properties.get('dataPoints')[0] === 'number') {
        return 'boxplot';
      } else if (model.properties.has('text')) {
        return 'text';
      }
    }
    return 'tree';
  }
  static findAppropriateWidget(forest, viewConfig) {
    const type = this.getPreferredWidgetType(forest, viewConfig);

    // full type specified
    if(type.includes('-')) {
      return type;
    }

    // shorthand notation used
    return `vivide-${type}-widget`;
  }
}

export default class VivideView extends Morph {
  static findViewWithId(id, context=document.body) {
    return lively.query(context, `vivide-view[vivide-view-id=${id}]`);
  }

  static getIdForView(view) {
    return view.id;
  }

  static get idAttribute() { return 'vivide-view-id'; }

  static get outportAttribute() { return 'outport-target'; }

  static get scriptAttribute() { return 'vivide-script'; }

  static get widgetId() { return 'widget'; }

  static get widgetSelector() { return '#' + this.widgetId; }

  static forestToData(model) {
    return model.map(m => m.object);
  }

  // unused?
  static dataToForest(data) {
    return data.map(d => new VivideObject(d));
  }

  get widget() { return this.get(VivideView.widgetSelector); }

  get input() { return this._input || (this._input = []); }
  set input(val) { return this._input = val; }

  get id() {
    let id = this.getAttribute(VivideView.idAttribute);
    if(id) { return id; }

    // ensure uuid begins with a letter to match the requirements for a css selector
    let newId = 'vivide-view-' + uuid();
    this.setAttribute(VivideView.idAttribute, newId);
    return newId;
  }

  get outportTargets() {
    let ids = this.getJSONAttribute(VivideView.outportAttribute);
    if(ids) {
      return flatMap.call(ids, id => {
        let view = VivideView.findViewWithId(id, lively.findWorldContext(this)) ;
        if(view === null) {
          lively.error('could not find view: ' + id);
          return [];
        }
        return [view];
      });
    }

    return this.outportTargets = [];
  }

  set outportTargets(targets) {
    return this.setJSONAttribute(
      VivideView.outportAttribute,
      targets.map(VivideView.getIdForView)
    );
  }

  addOutportTarget(target) {
    return this.outportTargets = this.outportTargets.concat(target);
  }

  removeOutportTarget(target) {
    return this.outportTargets = without.call(this.outportTargets, target);
  }

  get inportSources() {
    return Array.from(document.body.querySelectorAll(`vivide-view[${VivideView.outportAttribute}*=${this.id}]`));
  }

  get targetHull() {
    let hull = new Set();

    function addToHull(view) {
      if(view && !hull.has(view)) {
        hull.add(view);
        view.outportTargets.forEach(addToHull);
      }
    }
    addToHull(this);

    return Array.from(hull);
  }

  connectTo(target) {
    // #TODO: cycle detection, here?
    this.addOutportTarget(target);
  }
  removeConnectionTo(target) {
    this.removeOutportTarget(target);
  }

  transmitDataToOutportTargets(dataToTransmit) {
    this.outportTargets.forEach(target => target.newDataFromUpstream(dataToTransmit));
  }
  reallyNotifyOutportTargets(stuffToTransmit) {
    this.transmitDataToOutportTargets(VivideView.forestToData(stuffToTransmit));
  }
  notifyOutportTargets() {
    lively.warn('VIEW::NOTIFY2', this.forestToDisplay[0])
    this.reallyNotifyOutportTargets(this.forestToDisplay);
  }

  updateOutportTargets() {
    const dataToTransmit = this.getDataToTransmit();
    if(dataToTransmit) {
      lively.warn('VIEW::UPDATE', dataToTransmit[0])
      this.transmitDataToOutportTargets(dataToTransmit);
    }
  }

  getDataToTransmit() {
      lively.warn('display all data')
    if(this.widget && this.widget.multiSelectionEnabled) {
      return this.getSelectedData();
    } else if (this.widget && this.widget.localName === 'vivide-text-widget') {
      return this.getSelectedData();
    } else {
      // use all data
      return VivideObject.forestToData(this.forestToDisplay);
    }
  }

  getSelectedData() {
    const widget = this.widget;
    if(widget) {
      return widget.getSelectedData();
    }
    return undefined;
  }

  selectionChanged() {
    this.updateOutportTargets();
  }

  addDragInfoTo(evt) {
    const dt = evt.dataTransfer;
    // #TODO: An improved fix would be to change what is returned by the widget selection
    let selection = this.getDataToTransmit();
    if(selection) {
      lively.warn('VivideView::addDragInfoTo', selection[0])
      dt.setData("javascript/object", getTempKeyFor(selection));
    } else {
      lively.error('could not add drag data');
    }

    dt.setData("vivide", "");
    dt.setData("vivide/source-view", getTempKeyFor(this));
  }

  async initialize() {
    this.windowTitle = "VivideView";
    this.addEventListener('extent-changed', evt => this.onExtentChanged(evt));

    this.addEventListener('dragenter', evt => this.dragenter(evt), false);
    this.addEventListener('dragover', evt => this.dragover(evt), false);
    this.addEventListener('dragleave', evt => this.dragleave(evt), false);
    this.addEventListener('drop', evt => this.drop(evt), false);

    // restore data...

    // not migrating, but initializing from source
    if (!this.myCurrentScript) {
      debugger
      var scriptJSON = this.getAttribute("vivide-script")
      if (scriptJSON) {
        this.myCurrentScript = await Script.fromJSON(scriptJSON, this)
      } else {
        //This got commented because it is not reliable. It will often overwrite or take longer to setup and overwrite later on
        // await this.initDefaultScript();
      }

      var dataJSON = this.getAttribute("vivide-data")
      if (dataJSON) {
        // await this.createScriptEditor();
        var data = JSON.parse(dataJSON)
        this.newDataFromUpstream(data)
      }
    }
  }

  onExtentChanged() {
    this.childNodes.forEach(childNode => {
      if(childNode.dispatchEvent) {
        childNode.dispatchEvent(new CustomEvent("extent-changed"));
      }
    })
  }

  dragenter(evt) {}
  dragover(evt) {
    evt.preventDefault();

    this._resetDropOverEffects();
    this.classList.add('over');

    const dt = evt.dataTransfer;

    let hasSourceView = dt.types.includes("vivide") && dt.types.includes("vivide/source-view");
    if(hasSourceView) {
      // unfortunately, we cannot check for a circular dependency here,
      // because we cannot get data from the dataTransfer outside dragStart and drop
      // see: https://stackoverflow.com/a/31922258/1152174
      this.classList.add('accept-drop');
      dt.dropEffect = "link";

      return;
    }

    let hasData = dt.types.includes("javascript/object");
    if(hasData) {
      this.classList.add('accept-drop');
      dt.dropEffect = "copy";

      return;
    }

    this.classList.add('reject-drop');
  }
  dragleave(evt) {
    this._resetDropOverEffects();
  }
  _resetDropOverEffects() {
    this.classList.remove('over');
    this.classList.remove('reject-drop');
    this.classList.remove('accept-drop');
  }
  drop(evt) {
    this._resetDropOverEffects();

    let shouldPreventPropagation = false;

    const dt = evt.dataTransfer;
    if (dt.types.includes("javascript/object")) {
      lively.success('drop data');

      const data = getObjectFor(dt.getData("javascript/object"));
      this.newDataFromUpstream(data);

      shouldPreventPropagation = true;
    }

    if (dt.types.includes("vivide") && dt.types.includes("vivide/source-view")) {
      lively.success('drop vivide');

      const sourceView = getObjectFor(dt.getData("vivide/source-view"));

      if(this.targetHull.includes(sourceView)) {
        lively.warn('cannot connect views', 'preventing cyclic dependencies')
      } else {
        sourceView.connectTo(this);
      }

      shouldPreventPropagation = true;
    }

    if(shouldPreventPropagation) {
      evt.stopPropagation();
    }
  }

  get myCurrentScript() { return this._myCurrentScript; }
  set myCurrentScript(script) {
    this._myCurrentScript = script;
    this.setAttribute(VivideView.scriptAttribute, script.toJSON());
  }

  async initDefaultScript() {
    const script = await Script.createDefaultScript(this);
    this.myCurrentScript = script;
    return script;
    // this.setJSONAttribute(VivideView.scriptAttribute, this.myCurrentScript.toJSON());
  }

  async newDataFromUpstream(data) {
    this.input = data;

    await this.calculateOutputModel();
    await this.updateWidget();
    this.updateOutportTargets();
  }

  getInputData() {
    return this.input;
  }

  async calculateOutputModel() {
    const firstStep = this.myCurrentScript.getInitialStep();
    const data = this.input.slice(0);

    this.forestToDisplay = await firstStep.processData(data);
  }

  // #TODO: nearly a duplicate with newDataFromUpstream; remove duplication
  async scriptGotUpdated() {
    // #TODO: save script to web-component
    // #TODO: later support multiple profiles
    lively.notify('VivideView::scriptGotUpdated');
    await this.calculateOutputModel();
    await this.updateWidget();
    // Update outport views
    // #TODO: nearly a duplicate with notifyOutportTargets, remove duplication
    this.updateOutportTargets();
  }

  async updateWidget() {
    this.innerHTML = '';

    const viewConfig = await this.myCurrentScript.getViewConfig();
    const chosenWidgetType = WidgetChooser.findAppropriateWidget(this.forestToDisplay, viewConfig);

    const widget = await lively.create(chosenWidgetType);
    widget.setView(this);
    widget.setAttribute('id', VivideView.widgetId);
    this.appendChild(widget);
    await widget.display(this.forestToDisplay, viewConfig);
  }

  async createScriptEditor() {
    const viewWindow = lively.findWindow(this);
    const reference = viewWindow && viewWindow.tagName === "LIVELY-WINDOW" ?
        viewWindow : this;
    const pos = lively.getGlobalBounds(reference).topRight();

    const scriptEditor = await lively.openComponentInWindow('vivide-script-editor', pos);
    scriptEditor.setView(this);
    // #TODO: only do setView with this as argument, the following line should not be required
    await scriptEditor.setScript(this.myCurrentScript);
    return scriptEditor;
  }

  livelyPrepareSave() {
    this.setAttribute("vivide-script", this.myCurrentScript.toJSON());
    try {
      var json = JSON.stringify(this.getInputData())
      this.setAttribute("vivide-data", json)
    } catch(e) {
      console.warn("could not persist vivide data", this.getInputData() )
    }
  }

  async livelyExample() {
    const exampleData = [
      {
        name: "object",
        subclasses:[
          {name: "morph"}
        ]},
      {
        name: "list",
        subclasses:[
          {
            name: "linkedlist",
            subclasses:[
              {name: "stack"}
            ]},
          {
            name: "arraylist"
          }]
      },
      {
        name: "usercontrol",
        subclasses:[
          {name: "textbox"},
          {name: "button"},
          {name: "label"}
        ]},
    ];

    await this.initDefaultScript();
    await this.createScriptEditor();
    await this.newDataFromUpstream(exampleData);
  }

  livelyMigrate(other) {
    lively.notify('VivideView::migrate')
    this.myCurrentScript = other.myCurrentScript;
    this.myCurrentScript.setView(this);

    this.newDataFromUpstream(other.input);
  }

  livelyInspect(contentNode, inspector) {
    if (this.myCurrentScript) {
      contentNode.appendChild(inspector.display(this.myCurrentScript, false, "#script", this));
    }
    if (this.input) {
      contentNode.appendChild(inspector.display(this.input, false, "#input", this));
    }

    var outportTargets = this.outportTargets
    if (outportTargets) {
      contentNode.appendChild(inspector.display(outportTargets, false, "#outportTargets", this));
    }

  }

  livelyHalo() {
    return {
      configureHalo(halo) {
        halo.get('#default-items').style.display = 'none';
        halo.get('#vivide-items').style.display = 'flex';

        // dynamically create outport connection visualizations
        const outportContainer = halo.get('#vivide-outport-connection-items');
        this.outportTargets.forEach(target => {
          const item = document.createElement('lively-halo-vivide-outport-connection-item')
          item.classList.add('halo');
          item.setTarget(target);
          outportContainer.appendChild(item);
        });

        const inportContainer = halo.get('#vivide-inport-connection-items');
        this.inportSources.forEach(source => {
          const item = document.createElement('lively-halo-vivide-inport-connection-item')
          item.classList.add('halo');
          item.setSource(source);
          inportContainer.appendChild(item);
        });
      }
    };
  }
}
<|endoftext|>


<commit_before>//
//   Copyright 2009-2014 Ilkka Oksanen <iao@iki.fi>
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing,
//   software distributed under the License is distributed on an "AS
//   IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
//   express or implied.  See the License for the specific language
//   governing permissions and limitations under the License.
//

'use strict';

/* globals $, FileAPI, emojify, titlenotifier */

import Ember from 'ember';
import { play } from '../../../helpers/sound';
import UploadMixin from '../../../mixins/upload';

export default Ember.Component.extend(UploadMixin, {
    classNames: [ 'window', 'flex-grow-column' ],
    attributeBindings: [ 'row:data-row', 'column:data-column', 'desktop:data-desktop' ],

    classNameBindings: [
        'animating:velocity-animating:',
        'expanded:expanded:',
        'visible:visible:hidden',
        'ircServerWindow:irc-server-window:'
    ],

    expanded: false,
    animating: false,
    scrolling: false,
    scrollLock: false,

    linesAmount: null,
    deletedLine: false,

    $messagePanel: null,
    $images: null,
    logModeEnabled: false,

    row: Ember.computed.alias('content.row'),
    column: Ember.computed.alias('content.column'),
    desktop: Ember.computed.alias('content.desktop'),

    selectedDesktop: 0,

    visible: function() {
        return this.get('selectedDesktop') === this.get('content.desktop');
    }.property('selectedDesktop', 'content.desktop'),

    windowChanged: function() {
        this.sendAction('relayout', { animate: true });
    }.observes('row', 'column', 'desktop'),

    visibilityChanged: function() {
        if (this.get('visible')) {
            this.set('content.newMessagesCount', 0);

            // Hidden div can't be scrolled so the scrolling in the linedAdded() observer
            // hasn't worked if new messages arrived to this window while it was hidden.
            Ember.run.scheduleOnce('afterRender', this, function() {
                this._goToBottom(false);
            });
        }

        this.sendAction('relayout', { animate: false });
    }.observes('visible'),

    lineAdded: function() {
        let messages = this.get('content.messages');
        let previousLines = this.get('linesAmount');
        this.set('linesAmount', messages.length);

        if (previousLines && previousLines >= messages.length) {
            // Line was removed.
            this.set('deletedLine', true);
            return;
        }

        if (!this.get('scrollLock')) {
            // Prevents _addScrollHandler to make faulty conclusion.
            // We need to scroll and we we will after debounce kicks in.
            this.set('scrolling', true);
        }

        Ember.run.debounce(this, function() {
            // Update images array
            this.$images = this.$('img[data-src]');

            Ember.run.scheduleOnce('afterRender', this, function() {
                this._goToBottom(true);
            });
        }, 300); // This should be more than duration of goToBottom() scrolling animation

        let cat = messages[messages.length - 1].cat; // Message that was just added.
        let importantMessage = cat === 'msg' || cat === 'error' || cat === 'action';

        if ((!this.get('visible') || this.get('scrollLock')) && importantMessage) {
            this.incrementProperty('content.newMessagesCount');
        }

        if (document.hidden && importantMessage) {
            // Browser title notification
            if (this.get('content.titleAlert')) {
                titlenotifier.add();
            }

            // Sound notification
            if (this.get('content.sounds')) {
                play();
            }
        }
    }.observes('content.messages.@each'),

    ircServerWindow: function() {
        return this.get('content.userId') === 'iSERVER' ? 'irc-server-window' : '';
    }.property('content.userId'),

    isGroup: function() {
        return this.get('content.type') === 'group';
    }.property('content.type'),

    cssType: function() {
        if (this.get('content.type') === 'group') {
            return 'group';
        } else if (this.get('content.userId') === 'iSERVER') {
            return 'server-1on1';
        } else {
            return 'private-1on1';
        }
    }.property('content.type'),

    actions: {
        expand() {
            this.set('expanded', true);
            this.sendAction('relayout', { animate: true });
        },

        compress() {
            this.set('expanded', false);
            this.sendAction('relayout', { animate: true });
        },

        browse() {
            this.set('logModeEnabled', true);
            this.set('expanded', true);
            this.sendAction('relayout', { animate: true });
        },

        toggleMemberListWidth() {
            this.toggleProperty('content.minimizedNamesList');
        },

        sendMessage() {
            let message = this.get('newMessage');

            if (message) {
                this.sendAction('action', 'sendMessage', this.content, message);
                this.set('newMessage', '');
            }
        },

        close() {
            this.sendAction('action', 'close', this.content);
        },

        menu(operation) {
            this.sendAction('menuAction', operation, this.content);
        },

        jumpToBottom() {
            this.set('scrollLock', false);
            this._goToBottom(true);
        }
    },

    mouseDown(event) {
        if (!$(event.target).hasClass('fa-arrows')) {
            return; // Not moving the window
        }

        event.preventDefault();
        this.sendAction('dragWindowStart', this, event);
    },

    layoutDone() {
        Ember.run.scheduleOnce('afterRender', this, function() {
            this._goToBottom(false);
        });
    },

    didInsertElement() {
        let that = this;

        this.$images = this.$('img[data-src]');
        this.$messagePanel = this.$('.window-messages');
        this._addScrollHandler();

        this.$('.window-caption').tooltip();
        this.$messagePanel.tooltip({
            selector: '.timestamp',
            placement: 'right'
        });

        let selectedUserId;

        this.$('.window-members').contextmenu({
            target: '#window-contextMenu',
            before(e) {
                let $target = $(e.target);

                if ($target.hasClass('window-members')) {
                    return false;
                }

                e.preventDefault();
                let $row = $target.closest('.member-row');

                let selectedNick = $row.data('nick');
                let avatar = $row.find('.gravatar').attr('src');
                selectedUserId = $row.data('userid');

                this.getMenu().find('li').eq(0).html(
                    '' +  selectedNick);

                // Only MAS users can be added to a contacts list.
                $('.window-contexMenu-request-friend').toggle(selectedUserId.charAt(0) === 'm');

                return true;
            },
            onItem(context, e) {
                let action = $(e.target).data('action');
                that.sendAction('action', action, that.content, selectedUserId);
            }
        });

        this.$('.window-members').click(function(e) {
            $(this).contextmenu('show', e);
            e.preventDefault();
            return false;
        });

        let emojisList = $.map(emojify.emojiNames, function(value, i) {
            return { id: i, name: value };
        });

        let emojiListTemplate = '
 ${name}
';

        this.$('.form-control').atwho({
            at: ':',
            displayTpl: emojiListTemplate,
            insertTpl: ':${name}:',
            data: emojisList,
            highlightFirst: false,
            limit: 20
        });

        function getNick(item) {
            return item.nick;
        }

        let nickList = this.get('content.operatorNames').map(getNick)
            .concat(this.get('content.voiceNames').map(getNick))
            .concat(this.get('content.userNames').map(getNick));

        this.$('.form-control').atwho({
            at: '@',
            data: nickList,
            limit: 10
        });

        this.$messagePanel.magnificPopup({
            type: 'image',
            delegate: '.user-img',
            closeOnContentClick: true,
            image: {
                verticalFit: false,
                titleSrc(item) {
                    let href = item.el.attr('href');

                    return 'Link to the original image:' + href + '';
                }
            }
        });

        let fileInput = this.$('.btn-file input')[0];

        FileAPI.event.on(fileInput, 'change', function(evt) {
            let files = FileAPI.getFiles(evt); // Retrieve file list
            this.send('upload', files, 'jpeg');
        }.bind(this));

        this.sendAction('relayout', { animate: false });
    },

    willDestroyElement() {
        Ember.run.scheduleOnce('afterRender', this, function() {
            this.sendAction('relayout', { animate: true });
        });
    },

    _goToBottom(animate) {
        if (this.get('scrollLock')) {
            return;
        }

        // TBD: Animation running and Ember updating {{each}} doesn't always seem to mix well
        // Maybe glimmer fixes that.
        let duration = animate ? 200 : 0;

        this.$('.window-messages-end').velocity('stop').velocity('scroll', {
            container: this.$messagePanel,
            duration: duration,
            easing: 'spring',
            offset: 100, // Shouldn't be needed
            begin: function() {
                this.set('scrolling', true);
            }.bind(this),
            complete: function() {
                this.set('scrolling', false);
                this._showImages();
            }.bind(this)
        });
    },

    _addScrollHandler() {
        this.$messagePanel.on('scroll', function() {
            Ember.run.debounce(this, function() {
                if (this.get('animating') || this.get('scrolling')) {
                    return;
                }

                let $panel = this.$messagePanel;
                let scrollPos = $panel.scrollTop();

                // User doesn't need to scroll exactly to the end.
                let bottomTreshhold = $panel.prop('scrollHeight') - 5;

                if (scrollPos + $panel.innerHeight() >= bottomTreshhold) {
                    this.set('scrollLock', false);
                    Ember.Logger.info('scrollock off');
                } else if (!this.get('deletedLine')) {
                    this.set('scrollLock', true);
                    Ember.Logger.info('scrollock on');
                }

                this.set('deletedLine', false); // Hack
                this._showImages();
            }, 150);
        }.bind(this));
    },

    _showImages() {
        if (!this.$images) {
            return;
        }

        let placeHolderHeight = 31;
        let panelHeight = this.$messagePanel.height();
        let that = this;

        this.$images = this.$images.filter(function() {
            let $img = $(this);

            // We want to know image's position in .window-messages container div. For position()
            // to work correctly, .window-messages has to have position set to 'relative'. See
            // jQuery offsetParent() documentation for details.
            let pos = $img.position().top;

            if (pos + placeHolderHeight >= 0 && pos <= panelHeight) {
                $img.attr('src', $img.data('src'));

                $img.one('load', function() {
                    $img.removeClass('loader loader-small-dark');
                    $img.removeAttr('data-src');
                    that._goToBottom(true);
                });

                $img.one('error', function() {
                    $img.hide();
                    that._goToBottom(true);
                });

                return false;
            }

            return true;
        });
    }
});
<commit_msg>Fix use of Ember.run.debounce() and throttle()<commit_after>//
//   Copyright 2009-2014 Ilkka Oksanen <iao@iki.fi>
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing,
//   software distributed under the License is distributed on an "AS
//   IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
//   express or implied.  See the License for the specific language
//   governing permissions and limitations under the License.
//

'use strict';

/* globals $, FileAPI, emojify, titlenotifier */

import Ember from 'ember';
import { play } from '../../../helpers/sound';
import UploadMixin from '../../../mixins/upload';

export default Ember.Component.extend(UploadMixin, {
    classNames: [ 'window', 'flex-grow-column' ],
    attributeBindings: [ 'row:data-row', 'column:data-column', 'desktop:data-desktop' ],

    classNameBindings: [
        'animating:velocity-animating:',
        'expanded:expanded:',
        'visible:visible:hidden',
        'ircServerWindow:irc-server-window:'
    ],

    expanded: false,
    animating: false,
    scrolling: false,
    scrollLock: false,

    linesAmount: null,
    deletedLine: false,

    $messagePanel: null,
    $images: null,
    logModeEnabled: false,

    row: Ember.computed.alias('content.row'),
    column: Ember.computed.alias('content.column'),
    desktop: Ember.computed.alias('content.desktop'),

    selectedDesktop: 0,

    visible: function() {
        return this.get('selectedDesktop') === this.get('content.desktop');
    }.property('selectedDesktop', 'content.desktop'),

    windowChanged: function() {
        this.sendAction('relayout', { animate: true });
    }.observes('row', 'column', 'desktop'),

    visibilityChanged: function() {
        if (this.get('visible')) {
            this.set('content.newMessagesCount', 0);

            // Hidden div can't be scrolled so the scrolling in the linedAdded() observer
            // hasn't worked if new messages arrived to this window while it was hidden.
            Ember.run.scheduleOnce('afterRender', this, function() {
                this._goToBottom(false);
            });
        }

        this.sendAction('relayout', { animate: false });
    }.observes('visible'),

    lineAdded: function() {
        let messages = this.get('content.messages');
        let previousLines = this.get('linesAmount');
        this.set('linesAmount', messages.length);

        if (previousLines && previousLines >= messages.length) {
            // Line was removed.
            this.set('deletedLine', true);
            return;
        }

        if (!this.get('scrollLock')) {
            // Prevents _addScrollHandler to make faulty conclusion.
            // We need to scroll and we we will after debounce kicks in.
            this.set('scrolling', true);
        }

        // Threshold should be more than duration of goToBottom() scrolling animation
        Ember.run.debounce(this, this._checkNewImages, 300);

        let cat = messages[messages.length - 1].cat; // Message that was just added.
        let importantMessage = cat === 'msg' || cat === 'error' || cat === 'action';

        if ((!this.get('visible') || this.get('scrollLock')) && importantMessage) {
            this.incrementProperty('content.newMessagesCount');
        }

        if (document.hidden && importantMessage) {
            // Browser title notification
            if (this.get('content.titleAlert')) {
                titlenotifier.add();
            }

            // Sound notification
            if (this.get('content.sounds')) {
                play();
            }
        }
    }.observes('content.messages.@each'),

    _checkNewImages() {
        // Update images array
        this.$images = this.$('img[data-src]');

        Ember.run.scheduleOnce('afterRender', this, function() {
            this._goToBottom(true);
        });
    },

    ircServerWindow: function() {
        return this.get('content.userId') === 'iSERVER' ? 'irc-server-window' : '';
    }.property('content.userId'),

    isGroup: function() {
        return this.get('content.type') === 'group';
    }.property('content.type'),

    cssType: function() {
        if (this.get('content.type') === 'group') {
            return 'group';
        } else if (this.get('content.userId') === 'iSERVER') {
            return 'server-1on1';
        } else {
            return 'private-1on1';
        }
    }.property('content.type'),

    actions: {
        expand() {
            this.set('expanded', true);
            this.sendAction('relayout', { animate: true });
        },

        compress() {
            this.set('expanded', false);
            this.sendAction('relayout', { animate: true });
        },

        browse() {
            this.set('logModeEnabled', true);
            this.set('expanded', true);
            this.sendAction('relayout', { animate: true });
        },

        toggleMemberListWidth() {
            this.toggleProperty('content.minimizedNamesList');
        },

        sendMessage() {
            let message = this.get('newMessage');

            if (message) {
                this.sendAction('action', 'sendMessage', this.content, message);
                this.set('newMessage', '');
            }
        },

        close() {
            this.sendAction('action', 'close', this.content);
        },

        menu(operation) {
            this.sendAction('menuAction', operation, this.content);
        },

        jumpToBottom() {
            this.set('scrollLock', false);
            this._goToBottom(true);
        }
    },

    mouseDown(event) {
        if (!$(event.target).hasClass('fa-arrows')) {
            return; // Not moving the window
        }

        event.preventDefault();
        this.sendAction('dragWindowStart', this, event);
    },

    layoutDone() {
        Ember.run.scheduleOnce('afterRender', this, function() {
            this._goToBottom(false);
        });
    },

    didInsertElement() {
        let that = this;

        this.$images = this.$('img[data-src]');
        this.$messagePanel = this.$('.window-messages');
        this._addScrollHandler();

        this.$('.window-caption').tooltip();
        this.$messagePanel.tooltip({
            selector: '.timestamp',
            placement: 'right'
        });

        let selectedUserId;

        this.$('.window-members').contextmenu({
            target: '#window-contextMenu',
            before(e) {
                let $target = $(e.target);

                if ($target.hasClass('window-members')) {
                    return false;
                }

                e.preventDefault();
                let $row = $target.closest('.member-row');

                let selectedNick = $row.data('nick');
                let avatar = $row.find('.gravatar').attr('src');
                selectedUserId = $row.data('userid');

                this.getMenu().find('li').eq(0).html(
                    '' +  selectedNick);

                // Only MAS users can be added to a contacts list.
                $('.window-contexMenu-request-friend').toggle(selectedUserId.charAt(0) === 'm');

                return true;
            },
            onItem(context, e) {
                let action = $(e.target).data('action');
                that.sendAction('action', action, that.content, selectedUserId);
            }
        });

        this.$('.window-members').click(function(e) {
            $(this).contextmenu('show', e);
            e.preventDefault();
            return false;
        });

        let emojisList = $.map(emojify.emojiNames, function(value, i) {
            return { id: i, name: value };
        });

        let emojiListTemplate = '
 ${name}
';

        this.$('.form-control').atwho({
            at: ':',
            displayTpl: emojiListTemplate,
            insertTpl: ':${name}:',
            data: emojisList,
            highlightFirst: false,
            limit: 20
        });

        function getNick(item) {
            return item.nick;
        }

        let nickList = this.get('content.operatorNames').map(getNick)
            .concat(this.get('content.voiceNames').map(getNick))
            .concat(this.get('content.userNames').map(getNick));

        this.$('.form-control').atwho({
            at: '@',
            data: nickList,
            limit: 10
        });

        this.$messagePanel.magnificPopup({
            type: 'image',
            delegate: '.user-img',
            closeOnContentClick: true,
            image: {
                verticalFit: false,
                titleSrc(item) {
                    let href = item.el.attr('href');

                    return 'Link to the original image:' + href + '';
                }
            }
        });

        let fileInput = this.$('.btn-file input')[0];

        FileAPI.event.on(fileInput, 'change', function(evt) {
            let files = FileAPI.getFiles(evt); // Retrieve file list
            this.send('upload', files, 'jpeg');
        }.bind(this));

        this.sendAction('relayout', { animate: false });
    },

    willDestroyElement() {
        Ember.run.scheduleOnce('afterRender', this, function() {
            this.sendAction('relayout', { animate: true });
        });
    },

    _goToBottom(animate) {
        if (this.get('scrollLock')) {
            return;
        }

        let duration = animate ? 200 : 0;

        this.$('.window-messages-end').velocity('stop').velocity('scroll', {
            container: this.$messagePanel,
            duration: duration,
            easing: 'spring',
            offset: -1 * this.$messagePanel.innerHeight(),
            begin: function() {
                this.set('scrolling', true);
            }.bind(this),
            complete: function() {
                this.set('scrolling', false);
                this._showImages();
            }.bind(this)
        });
    },

    _addScrollHandler() {
        let handler = function() {
            if (this.get('animating') || this.get('scrolling')) {
                return;
            }

            let $panel = this.$messagePanel;
            let scrollPos = $panel.scrollTop();

            // User doesn't need to scroll exactly to the end.
            let bottomTreshhold = $panel.prop('scrollHeight') - 5;

            if (scrollPos + $panel.innerHeight() >= bottomTreshhold) {
                this.set('scrollLock', false);
                Ember.Logger.info('scrollock off');
            } else if (!this.get('deletedLine')) {
                this.set('scrollLock', true);
                Ember.Logger.info('scrollock on');
            }

            this.set('deletedLine', false); // Hack
            this._showImages();
        };

        this.$messagePanel.on('scroll', () => {
            Ember.run.throttle(this, handler, 150);
        });
    },

    _showImages() {
        if (!this.$images) {
            return;
        }

        let placeHolderHeight = 31;
        let panelHeight = this.$messagePanel.height();
        let that = this;

        this.$images = this.$images.filter(function() {
            let $img = $(this);

            // We want to know image's position in .window-messages container div. For position()
            // to work correctly, .window-messages has to have position set to 'relative'. See
            // jQuery offsetParent() documentation for details.
            let pos = $img.position().top;

            if (pos + placeHolderHeight >= 0 && pos <= panelHeight) {
                $img.attr('src', $img.data('src'));

                $img.one('load', function() {
                    $img.removeClass('loader loader-small-dark');
                    $img.removeAttr('data-src');
                    that._goToBottom(true);
                });

                $img.one('error', function() {
                    $img.hide();
                    that._goToBottom(true);
                });

                return false;
            }

            return true;
        });
    }
});
<|endoftext|>


<commit_before>(function() {

'use strict';

function AuthoringWidgetsProvider() {

    var widgets = [];

    this.widget = function(id, config) {
        widgets.push(angular.extend({ // make a new instance for every widget
        }, config, {_id: id}));
    };

    this.$get = function() {
        return widgets;
    };
}

WidgetsManagerCtrl.$inject = ['$scope', '$routeParams', 'authoringWidgets', 'archiveService'];
function WidgetsManagerCtrl($scope, $routeParams, authoringWidgets, archiveService) {
    $scope.active = null;

    $scope.$watch('item', function(item) {
        if (!item) {
            $scope.widgets = null;
            return;
        }

        var display;

        if (archiveService.isLegal(item)) {
            display = 'legalArchive';
        } else {
            display = item.type === 'composite' ? 'packages' : 'authoring';
        }

        $scope.widgets = authoringWidgets.filter(function(widget) {
            return !!widget.display[display];
        });
    });

    $scope.isLocked = function(widget) {
        return (widget.needUnlock && $scope.item._locked) ||
        (widget.needEditable && !$scope.item._editable);
    };

    $scope.activate = function(widget) {
        if (!$scope.isLocked(widget)) {
            $scope.active = $scope.active === widget ? null : widget;
        }
    };

    this.activate = function(widget) {
        $scope.activate(widget);
    };

    $scope.closeWidget = function(widget) {
        $scope.active = null;
    };

    // activate widget based on query string
    angular.forEach($scope.widgets, function(widget) {
        if ($routeParams[widget._id]) {
            $scope.activate(widget);
        }
    });

    $scope.$watch('item._locked', function() {
        if ($scope.active) {
            var widget = $scope.active;
            $scope.closeWidget(widget);
            $scope.activate(widget);
        }
    });
}

function AuthoringWidgetsDir() {
    return {
        controller: WidgetsManagerCtrl,
        templateUrl: 'scripts/superdesk-authoring/widgets/views/authoring-widgets.html',
        transclude: true
    };
}

angular.module('superdesk.authoring.widgets', [])
    .provider('authoringWidgets', AuthoringWidgetsProvider)
    .directive('sdAuthoringWidgets', AuthoringWidgetsDir);

})();
<commit_msg>feat(authoring): SD-3414 Navigate throw right side panes<commit_after>(function() {

'use strict';

function AuthoringWidgetsProvider() {

    var widgets = [];

    this.widget = function(id, config) {
        widgets.push(angular.extend({ // make a new instance for every widget
        }, config, {_id: id}));
    };

    this.$get = function() {
        return widgets;
    };
}

WidgetsManagerCtrl.$inject = ['$scope', '$routeParams', 'authoringWidgets', 'archiveService', 'keyboardManager'];
function WidgetsManagerCtrl($scope, $routeParams, authoringWidgets, archiveService, keyboardManager) {
    $scope.active = null;

    $scope.$watch('item', function(item) {
        if (!item) {
            $scope.widgets = null;
            return;
        }

        var display;

        if (archiveService.isLegal(item)) {
            display = 'legalArchive';
        } else {
            display = item.type === 'composite' ? 'packages' : 'authoring';
        }

        $scope.widgets = authoringWidgets.filter(function(widget) {
            return !!widget.display[display];
        });

        /*
         * Navigate throw right tab widgets with keyboard combination
         * Combination: Ctrl + {{widget number}}
         */
        angular.forEach($scope.widgets, function (widget) {
            keyboardManager.bind('ctrl+' + widget.order, function () {
                $scope.activate(widget);
            }, {global: true});
        });
    });

    $scope.isLocked = function(widget) {
        return (widget.needUnlock && $scope.item._locked) ||
        (widget.needEditable && !$scope.item._editable);
    };

    $scope.activate = function(widget) {
        if (!$scope.isLocked(widget)) {
            $scope.active = $scope.active === widget ? null : widget;
        }
    };

    this.activate = function(widget) {
        $scope.activate(widget);
    };

    $scope.closeWidget = function(widget) {
        $scope.active = null;
    };

    // activate widget based on query string
    angular.forEach($scope.widgets, function(widget) {
        if ($routeParams[widget._id]) {
            $scope.activate(widget);
        }
    });

    $scope.$watch('item._locked', function() {
        if ($scope.active) {
            var widget = $scope.active;
            $scope.closeWidget(widget);
            $scope.activate(widget);
        }
    });
}

function AuthoringWidgetsDir() {
    return {
        controller: WidgetsManagerCtrl,
        templateUrl: 'scripts/superdesk-authoring/widgets/views/authoring-widgets.html',
        transclude: true
    };
}

angular.module('superdesk.authoring.widgets', [])
    .provider('authoringWidgets', AuthoringWidgetsProvider)
    .directive('sdAuthoringWidgets', AuthoringWidgetsDir);

})();
<|endoftext|>


<commit_before>var placeholder = document.createElement("div");
placeholder.className = "placeholder";

var List = React.createClass({
  getInitialState: function () {
    return { data: this.props.data };
  },
  onClick: function (e) {
    console.log(e.currentTarget.lastChild);
  },
  dragStart: function (e) {
    this.dragged = e.currentTarget;
    e.dataTransfer.effectAllowed = 'move';

    // Firefox requires dataTransfer data to be set
    e.dataTransfer.setData("text/html", e.currentTarget);
  },
  dragEnd: function () {
    this.dragged.style.display = "block";
    this.dragged.parentNode.removeChild(placeholder);

    // Update data
    var data = this.state.data;
    var from = Number(this.dragged.dataset.id);
    var to = Number(this.over.dataset.id);
    if (from < to) to--;
    if (this.nodePlacement === "after") to++;
    data.splice(to, 0, data.splice(from, 1)[0]);
    this.setState({data: data});
  },

  dragOver: function (e) {
    e.preventDefault();
    this.dragged.style.display = "none";
    if (e.target.className === "placeholder") return;
    this.over = e.target;
    // Inside the dragOver method
    var relY = e.clientY - this.over.offsetTop;
    var height = this.over.offsetHeight / 2;
    var parent = e.target.parentNode;

    if (relY > height) {
      this.nodePlacement = "after";
      parent.insertBefore(placeholder, e.target.nextElementSibling);
    } else if (relY < height) {
      this.nodePlacement = "before";
      parent.insertBefore(placeholder, e.target);
    }
  },
  render: function () {
    var style = {
      cursor: 'pointer',
      padding: '0',
      margin: '0 0 2px 10px',
      color: '#FFFFFF',
      listStyleType: 'none'
    };
    var listItems = this.state.data.map((function (item, i) {
      return (

          {item}

      );
    }).bind(this));

    return (

{listItems}

    );
  }
});

//list of dummy user data
var currentUser = {
  currentPlaylist: {
    title: 'Test Playlist',
    songs: [
      {
        title: 'Darude - Sandstorm',
        id: '2HQaBWziYvY',
        duration: 224,
        durationDisplay: '03:44'
      },
      {
        title: 'The Growlers - One Million Lovers',
        id: '59CZt1xsh5s',
        duration: 278,
        durationDisplay: '04:38'
      },
      {
        title: 'FIDLAR - No Waves (Music Video)',
        id: 'BYbJmQj5VkE',
        duration: 190,
        durationDisplay: '03:10'
      },
      {
        title: 'Darude - Sandstorm',
        id: '2HQaBWziYvY',
        duration: 224,
        durationDisplay: '03:44'
      },
      {
        title: 'The Growlers - One Million Lovers',
        id: '59CZt1xsh5s',
        duration: 278,
        durationDisplay: '04:38'
      },
      {
        title: 'FIDLAR - No Waves (Music Video)',
        id: 'BYbJmQj5VkE',
        duration: 190,
        durationDisplay: '03:10'
      }
    ]
  }
};

//take the songs from the user data
var arrSongs = [];
var populatePlaylist = function() {
  for (var song in currentUser.currentPlaylist.songs) {
    arrSongs.push([currentUser.currentPlaylist.songs[song].title + ' | ' + currentUser.currentPlaylist.songs[song].durationDisplay]);
  }
};

populatePlaylist();

var addSongToPlaylist = function (songNode) {
  currentUser.currentPlaylist.songs.push(songNode);
  console.log("Added ", songNode, " to playlist");
  populatePlaylist();
  // React.addons.update(arrSongs, {$push: [songNode.title + ' | ' + songNode.durationDisplay]})
}

var Songs = React.createClass({
  render: function() {
    return (
      <List data={arrSongs} />
    );
  }
});

var PlaylistTitle = React.createClass({
  render: function(){
    var style = {
      textAlign: 'center',
      color: 'white',
      marginTop: '10px',
      paddingBottom: '10px',
      borderBottom: '2px solid #444444'
    };
    return (

Current Playlist

    );
  }
});

var PlaylistSaved = React.createClass({
  render: function() {
    var style = {
      background: '#222222',
      border: '2px solid #444444',
      position: 'absolute',
      width: '100%',
      height: '50%',
      overflow: 'auto',
      textAlign: 'left',
      bottom: '0'
    };
    return (

        <PlaylistTitle />
        <Songs />

    );
  }
});<commit_msg>(client) Update playlistSaved logic to dynamically assign title<commit_after>var placeholder = document.createElement("div");
placeholder.className = "placeholder";

var List = React.createClass({
  getInitialState: function () {
    return { data: this.props.data };
  },
  onClick: function (e) {
    console.log(e.currentTarget.lastChild);
  },
  dragStart: function (e) {
    this.dragged = e.currentTarget;
    e.dataTransfer.effectAllowed = 'move';

    // Firefox requires dataTransfer data to be set
    e.dataTransfer.setData("text/html", e.currentTarget);
  },
  dragEnd: function () {
    this.dragged.style.display = "block";
    this.dragged.parentNode.removeChild(placeholder);

    // Update data
    var data = this.state.data;
    var from = Number(this.dragged.dataset.id);
    var to = Number(this.over.dataset.id);
    if (from < to) to--;
    if (this.nodePlacement === "after") to++;
    data.splice(to, 0, data.splice(from, 1)[0]);
    this.setState({data: data});
  },

  dragOver: function (e) {
    e.preventDefault();
    this.dragged.style.display = "none";
    if (e.target.className === "placeholder") return;
    this.over = e.target;
    // Inside the dragOver method
    var relY = e.clientY - this.over.offsetTop;
    var height = this.over.offsetHeight / 2;
    var parent = e.target.parentNode;

    if (relY > height) {
      this.nodePlacement = "after";
      parent.insertBefore(placeholder, e.target.nextElementSibling);
    } else if (relY < height) {
      this.nodePlacement = "before";
      parent.insertBefore(placeholder, e.target);
    }
  },
  render: function () {
    var style = {
      cursor: 'pointer',
      padding: '0',
      margin: '0 0 2px 10px',
      color: '#FFFFFF',
      listStyleType: 'none'
    };
    var listItems = this.state.data.map((function (item, i) {
      return (

          {item}

      );
    }).bind(this));

    return (

{listItems}

    );
  }
});

//list of dummy user data
var currentUser = {
  currentPlaylist: {
    title: 'Test Playlist',
    songs: [
      {
        title: 'Darude - Sandstorm',
        id: '2HQaBWziYvY',
        duration: 224,
        durationDisplay: '03:44'
      },
      {
        title: 'The Growlers - One Million Lovers',
        id: '59CZt1xsh5s',
        duration: 278,
        durationDisplay: '04:38'
      },
      {
        title: 'FIDLAR - No Waves (Music Video)',
        id: 'BYbJmQj5VkE',
        duration: 190,
        durationDisplay: '03:10'
      }
    ]
  }
};

//take the songs from the user data
var arrSongs = [];
var populatePlaylist = function() {
  arrSongs = [];
  for (var song in currentUser.currentPlaylist.songs) {
    arrSongs.push([currentUser.currentPlaylist.songs[song].title + ' | ' + currentUser.currentPlaylist.songs[song].durationDisplay]);
  }
  console.log(arrSongs)
};

populatePlaylist();

var addSongToPlaylist = function (songNode) {
  currentUser.currentPlaylist.songs.push(songNode);
  console.log("Added ", songNode, " to playlist");
  populatePlaylist();
}

var Songs = React.createClass({
  render: function() {
    return (
      <List data={arrSongs} />
    );
  }
});

var PlaylistTitle = React.createClass({
  render: function(){
    var style = {
      textAlign: 'center',
      color: 'white',
      marginTop: '10px',
      paddingBottom: '10px',
      borderBottom: '2px solid #444444'
    };
    return (

{currentUser.currentPlaylist.title}

    );
  }
});

var PlaylistSaved = React.createClass({
  render: function() {
    var style = {
      background: '#222222',
      border: '2px solid #444444',
      position: 'absolute',
      width: '100%',
      height: '50%',
      overflow: 'auto',
      textAlign: 'left',
      bottom: '0'
    };
    return (

        <PlaylistTitle />
        <Songs />

    );
  }
});<|endoftext|>


<commit_before>import './View.scss'
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { Table, Icon, Row, Col } from 'antd'
import { Link } from 'react-router-dom'
const mockEditor = require('./mockEditor.js')
import { formatTime } from '../../../../common.js';
import ErrMsg from '../../../../components/ErrMsg/ErrMsg.js';
import variable from '../../../../constants/variable';
// import { Card } from 'antd'
// import { getMockUrl } from '../../reducer/modules/news.js'

@connect(state => {
  return {
    curData: state.inter.curdata,
    currProject: state.project.currProject
  }
})

class View extends Component {
  constructor(props) {
    super(props);
    this.state = {
      init: true
    }
  }
  static propTypes = {
    curData: PropTypes.object,
    currProject: PropTypes.object
  }

  req_body_form(req_body_type, req_body_form) {

    if (req_body_type === 'form') {

      const columns = [{
        title: '参数名称',
        dataIndex: 'name',
        key: 'name',
        width: 140
      }, {
        title: '参数类型',
        dataIndex: 'type',
        key: 'type',
        width: 100,
        render: (text) => {
          text = text || "";
          return text.toLowerCase() === "text" ? T文本 : <Icon type="file" className="query-icon" />文件
        }
      }, {
        title: '是否必须',
        dataIndex: 'required',
        key: 'required',
        width: 100
      }, {
        title: '示例',
        dataIndex: 'example',
        key: 'example',
        render(_, item) {
          return
{item.example}
;
        }
      }, {
        title: '备注',
        dataIndex: 'value',
        key: 'value',
        render(_, item) {
          return
{item.value}
;
        }
      }];

      const dataSource = [];
      if (req_body_form && req_body_form.length) {
        req_body_form.map((item, i) => {
          dataSource.push({
            key: i,
            name: item.name,
            value: item.desc,
            example: item.example,
            required: item.required == 0 ? "否" : "是",
            type: item.type
          })
        })
      }

      return

Body：

    } else if (req_body_type === 'file') {

      return

Body：

{this.props.curData.req_body_other}

    } else if (req_body_type === 'raw') {

      return

Body：

{this.props.curData.req_body_other}

    }
  }
  res_body(res_body_type, res_body) {
    if (res_body_type === 'json') {
      let h = this.countEnter(this.props.curData.res_body);
      return

    } else if (res_body_type === 'raw') {
      return

{res_body}

    }
  }

  req_query(query) {
    const columns = [{
      title: '参数名称',
      dataIndex: 'name',
      width: 140,
      key: 'name'
    }, {
      title: '是否必须',
      width: 100,
      dataIndex: 'required',
      key: 'required'
    }, {
      title: '示例',
      dataIndex: 'example',
      key: 'example',
      render(_, item) {
        return
{item.example}
;
      }
    }, {
      title: '备注',
      dataIndex: 'value',
      key: 'value',
      render(_, item) {
        return
{item.value}
;
      }
    }];

    const dataSource = [];
    if (query && query.length) {
      query.map((item, i) => {
        dataSource.push({
          key: i,
          name: item.name,
          value: item.desc,
          example: item.example,
          required: item.required == 0 ? "否" : "是"
        })
      })
    }

    return
;
  }

  countEnter(str) {
    let i = 0;
    let c = 0;
    if (!str || !str.indexOf) return 0;
    while (str.indexOf('\n', i) > -1) {
      i = str.indexOf('\n', i) + 2;
      c++;
    }
    return c;
  }
  bindAceEditor() {
    if (this.props.curData.req_body_type === "json" && this.props.curData.title) {
      mockEditor({
        container: 'vreq_body_json',
        data: this.props.curData.req_body_other,
        readOnly: true,
        onChange: function () { }
      })
    }
    if (this.props.curData.title && this.props.curData.res_body_type === "json") {
      let content = this.props.curData.res_body ? this.props.curData.res_body: '没有定义';
      mockEditor({
        container: 'vres_body_json',
        data: content,
        readOnly: true,
        onChange: function () { }
      })
    }
  }
  componentDidMount() {

    if (this.props.curData.title) {
      this.bindAceEditor.bind(this)();
    }
    if (!this.props.curData.title && this.state.init) {
      this.setState({ init: false });
    }
  }
  componentDidUpdate() {
    this.bindAceEditor.bind(this)();
  }
  componentWillUpdate() {
    if (!this.props.curData.title && this.state.init) {
      this.setState({ init: false });
    }
  }
  render() {
    const dataSource = [];
    if (this.props.curData.req_headers && this.props.curData.req_headers.length) {
      this.props.curData.req_headers.map((item, i) => {
        dataSource.push({
          key: i,
          name: item.name,
          required: item.required == 0 ? "否" : "是",
          value: item.value,
          example: item.example,
          desc: item.desc
        })
      })
    }

    const req_dataSource = [];
    if (this.props.curData.req_params && this.props.curData.req_params.length) {
      this.props.curData.req_params.map((item, i) => {
        req_dataSource.push({
          key: i,
          name: item.name,
          desc: item.desc,
          example: item.example
        })
      })
    }
    const req_params_columns = [{
      title: '参数名称',
      dataIndex: 'name',
      key: 'name',
      width: 140
    }, {
      title: '示例',
      dataIndex: 'example',
      key: 'example',
      render(_, item) {
        return
{item.example}
;
      }
    }, {
      title: '备注',
      dataIndex: 'desc',
      key: 'desc',
      render(_, item) {
        return
{item.desc}
;
      }
    }];

    const columns = [{
      title: '参数名称',
      dataIndex: 'name',
      key: 'name',
      width: '200px'
    }, {
      title: '参数值',
      dataIndex: 'value',
      key: 'value',
      width: '300px'
    }, {
      title: '是否必须',
      dataIndex: 'required',
      key: 'required',
      width: '100px'
    }, {
      title: '示例',
      dataIndex: 'example',
      key: 'example',
      render(_, item) {
        return
{item.example}
;
      }
    }, {
      title: '备注',
      dataIndex: 'desc',
      key: 'desc',
      render(_, item) {
        return
{item.desc}
;
      }
    }];
    let status = {
      undone: "未完成",
      done: "已完成"
    }

    let requestShow = (dataSource&& dataSource.length) || (req_dataSource && req_dataSource.length) || (this.props.curData.req_query && this.props.curData.req_query.length) || (this.props.curData.req_body_other) || (this.props.curData.req_body_form && this.props.curData.req_body_form.length);
    let methodColor = variable.METHOD_COLOR[this.props.curData.method ? this.props.curData.method.toLowerCase() : "get"];
    // statusColor = statusColor[this.props.curData.status?this.props.curData.status.toLowerCase():"undone"];
    let h = this.countEnter(this.props.curData.req_body_other);
    const aceEditor =

      请求Body：

    if (!methodColor) methodColor = "get";
    let res =

基本信息

        <Row className="row">
          <Col span={4} className="colKey">接口名称：</Col>
          <Col span={8}>{this.props.curData.title}</Col>
          <Col span={4} className="colKey">创 建 人：</Col>
          <Col span={8} className="colValue"><Link className="user-name" to={"/user/profile/" + this.props.curData.uid} >{this.props.curData.username}</Link></Col>
        </Row>
        <Row className="row">
          <Col span={4} className="colKey">状  态：</Col>
          <Col span={8} className={'tag-status ' + this.props.curData.status}>{status[this.props.curData.status]}</Col>
          <Col span={4} className="colKey">更新时间：</Col>
          <Col span={8}>{formatTime(this.props.curData.up_time)}</Col>
        </Row>
        <Row className="row">
          <Col span={4} className="colKey">接口路径：</Col>
          <Col span={18} className="colValue">
            {this.props.curData.method}
            {this.props.currProject.basepath}{this.props.curData.path}
          </Col>
        </Row>
        <Row className="row">
          <Col span={4} className="colKey">Mock地址：</Col>
          <Col span={18} className="colValue href">
             window.open(location.protocol + '//' + location.hostname + (location.port !== "" ? ":" + location.port : "") + `/mock/${this.props.currProject._id}${this.props.currProject.basepath}${this.props.curData.path}`, '_blank')}>{location.protocol + '//' + location.hostname + (location.port !== "" ? ":" + location.port : "") + `/mock/${this.props.currProject._id}${this.props.currProject.basepath}${this.props.curData.path}`}</Col>
        </Row>
        {this.props.curData.desc ?
          <Row className="row remark">
            <Col span={4} className="colKey">接口备注：</Col>
            <Col span={18} className="colValue" dangerouslySetInnerHTML={{ __html: this.props.curData.desc }}></Col>
          </Row> : ""}

        Request

      {req_dataSource.length ?

路径参数：

 : ""}
      {dataSource.length ?

Headers：

 : ""}
      {this.props.curData.req_query && this.props.curData.req_query.length ?

Query：

        {this.req_query(this.props.curData.req_query)}

 : ""}
      {/*

        请求Body类型：
        {this.props.curData.req_body_type}

*/}
      {aceEditor}
      {this.req_body_form(this.props.curData.req_body_type, this.props.curData.req_body_form)}
      {/*

        返回Body类型：
        {this.props.curData.res_body_type}

*/}

Response

      {this.res_body(this.props.curData.res_body_type, this.props.curData.res_body)}

;
    if (!this.props.curData.title) {
      if (this.state.init) {
        res =

;
      } else {
        res = <ErrMsg type="noData" />;
      }
    }
    return res;
  }
}

export default View
<commit_msg>fix: post 请求改成 get，body 参数没删掉<commit_after>import './View.scss'
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { Table, Icon, Row, Col } from 'antd'
import { Link } from 'react-router-dom'
const mockEditor = require('./mockEditor.js')
import { formatTime } from '../../../../common.js';
import ErrMsg from '../../../../components/ErrMsg/ErrMsg.js';
import variable from '../../../../constants/variable';
import constants from '../../../../constants/variable.js'

const HTTP_METHOD = constants.HTTP_METHOD;
// import { Card } from 'antd'
// import { getMockUrl } from '../../reducer/modules/news.js'

@connect(state => {
  return {
    curData: state.inter.curdata,
    currProject: state.project.currProject
  }
})

class View extends Component {
  constructor(props) {
    super(props);
    this.state = {
      init: true
    }
  }
  static propTypes = {
    curData: PropTypes.object,
    currProject: PropTypes.object
  }

  req_body_form(req_body_type, req_body_form) {

    if (req_body_type === 'form') {

      const columns = [{
        title: '参数名称',
        dataIndex: 'name',
        key: 'name',
        width: 140
      }, {
        title: '参数类型',
        dataIndex: 'type',
        key: 'type',
        width: 100,
        render: (text) => {
          text = text || "";
          return text.toLowerCase() === "text" ? T文本 : <Icon type="file" className="query-icon" />文件
        }
      }, {
        title: '是否必须',
        dataIndex: 'required',
        key: 'required',
        width: 100
      }, {
        title: '示例',
        dataIndex: 'example',
        key: 'example',
        render(_, item) {
          return
{item.example}
;
        }
      }, {
        title: '备注',
        dataIndex: 'value',
        key: 'value',
        render(_, item) {
          return
{item.value}
;
        }
      }];

      const dataSource = [];
      if (req_body_form && req_body_form.length) {
        req_body_form.map((item, i) => {
          dataSource.push({
            key: i,
            name: item.name,
            value: item.desc,
            example: item.example,
            required: item.required == 0 ? "否" : "是",
            type: item.type
          })
        })
      }

      return

Body：

    } else if (req_body_type === 'file') {

      return

Body：

{this.props.curData.req_body_other}

    } else if (req_body_type === 'raw') {

      return

Body：

{this.props.curData.req_body_other}

    }
  }
  res_body(res_body_type, res_body) {
    if (res_body_type === 'json') {
      let h = this.countEnter(this.props.curData.res_body);
      return

    } else if (res_body_type === 'raw') {
      return

{res_body}

    }
  }

  req_query(query) {
    const columns = [{
      title: '参数名称',
      dataIndex: 'name',
      width: 140,
      key: 'name'
    }, {
      title: '是否必须',
      width: 100,
      dataIndex: 'required',
      key: 'required'
    }, {
      title: '示例',
      dataIndex: 'example',
      key: 'example',
      render(_, item) {
        return
{item.example}
;
      }
    }, {
      title: '备注',
      dataIndex: 'value',
      key: 'value',
      render(_, item) {
        return
{item.value}
;
      }
    }];

    const dataSource = [];
    if (query && query.length) {
      query.map((item, i) => {
        dataSource.push({
          key: i,
          name: item.name,
          value: item.desc,
          example: item.example,
          required: item.required == 0 ? "否" : "是"
        })
      })
    }

    return
;
  }

  countEnter(str) {
    let i = 0;
    let c = 0;
    if (!str || !str.indexOf) return 0;
    while (str.indexOf('\n', i) > -1) {
      i = str.indexOf('\n', i) + 2;
      c++;
    }
    return c;
  }
  bindAceEditor() {
    if (this.props.curData.req_body_type === "json" && this.props.curData.title) {
      mockEditor({
        container: 'vreq_body_json',
        data: this.props.curData.req_body_other,
        readOnly: true,
        onChange: function () { }
      })
    }
    if (this.props.curData.title && this.props.curData.res_body_type === "json") {
      let content = this.props.curData.res_body ? this.props.curData.res_body: '没有定义';
      mockEditor({
        container: 'vres_body_json',
        data: content,
        readOnly: true,
        onChange: function () { }
      })
    }
  }
  componentDidMount() {

    if (this.props.curData.title) {
      this.bindAceEditor.bind(this)();
    }
    if (!this.props.curData.title && this.state.init) {
      this.setState({ init: false });
    }
  }
  componentDidUpdate() {
    this.bindAceEditor.bind(this)();
  }
  componentWillUpdate() {
    if (!this.props.curData.title && this.state.init) {
      this.setState({ init: false });
    }
  }
  render() {
    const dataSource = [];
    if (this.props.curData.req_headers && this.props.curData.req_headers.length) {
      this.props.curData.req_headers.map((item, i) => {
        dataSource.push({
          key: i,
          name: item.name,
          required: item.required == 0 ? "否" : "是",
          value: item.value,
          example: item.example,
          desc: item.desc
        })
      })
    }

    const req_dataSource = [];
    if (this.props.curData.req_params && this.props.curData.req_params.length) {
      this.props.curData.req_params.map((item, i) => {
        req_dataSource.push({
          key: i,
          name: item.name,
          desc: item.desc,
          example: item.example
        })
      })
    }
    const req_params_columns = [{
      title: '参数名称',
      dataIndex: 'name',
      key: 'name',
      width: 140
    }, {
      title: '示例',
      dataIndex: 'example',
      key: 'example',
      render(_, item) {
        return
{item.example}
;
      }
    }, {
      title: '备注',
      dataIndex: 'desc',
      key: 'desc',
      render(_, item) {
        return
{item.desc}
;
      }
    }];

    const columns = [{
      title: '参数名称',
      dataIndex: 'name',
      key: 'name',
      width: '200px'
    }, {
      title: '参数值',
      dataIndex: 'value',
      key: 'value',
      width: '300px'
    }, {
      title: '是否必须',
      dataIndex: 'required',
      key: 'required',
      width: '100px'
    }, {
      title: '示例',
      dataIndex: 'example',
      key: 'example',
      render(_, item) {
        return
{item.example}
;
      }
    }, {
      title: '备注',
      dataIndex: 'desc',
      key: 'desc',
      render(_, item) {
        return
{item.desc}
;
      }
    }];
    let status = {
      undone: "未完成",
      done: "已完成"
    }

    let requestShow = (dataSource&& dataSource.length) || (req_dataSource && req_dataSource.length) || (this.props.curData.req_query && this.props.curData.req_query.length) || (this.props.curData.req_body_other) || (this.props.curData.req_body_form && this.props.curData.req_body_form.length);
    let methodColor = variable.METHOD_COLOR[this.props.curData.method ? this.props.curData.method.toLowerCase() : "get"];
    // statusColor = statusColor[this.props.curData.status?this.props.curData.status.toLowerCase():"undone"];
    let h = this.countEnter(this.props.curData.req_body_other);
    const aceEditor =

      请求Body：

    if (!methodColor) methodColor = "get";
    let res =

基本信息

        <Row className="row">
          <Col span={4} className="colKey">接口名称：</Col>
          <Col span={8}>{this.props.curData.title}</Col>
          <Col span={4} className="colKey">创 建 人：</Col>
          <Col span={8} className="colValue"><Link className="user-name" to={"/user/profile/" + this.props.curData.uid} >{this.props.curData.username}</Link></Col>
        </Row>
        <Row className="row">
          <Col span={4} className="colKey">状  态：</Col>
          <Col span={8} className={'tag-status ' + this.props.curData.status}>{status[this.props.curData.status]}</Col>
          <Col span={4} className="colKey">更新时间：</Col>
          <Col span={8}>{formatTime(this.props.curData.up_time)}</Col>
        </Row>
        <Row className="row">
          <Col span={4} className="colKey">接口路径：</Col>
          <Col span={18} className="colValue">
            {this.props.curData.method}
            {this.props.currProject.basepath}{this.props.curData.path}
          </Col>
        </Row>
        <Row className="row">
          <Col span={4} className="colKey">Mock地址：</Col>
          <Col span={18} className="colValue href">
             window.open(location.protocol + '//' + location.hostname + (location.port !== "" ? ":" + location.port : "") + `/mock/${this.props.currProject._id}${this.props.currProject.basepath}${this.props.curData.path}`, '_blank')}>{location.protocol + '//' + location.hostname + (location.port !== "" ? ":" + location.port : "") + `/mock/${this.props.currProject._id}${this.props.currProject.basepath}${this.props.curData.path}`}</Col>
        </Row>
        {this.props.curData.desc ?
          <Row className="row remark">
            <Col span={4} className="colKey">接口备注：</Col>
            <Col span={18} className="colValue" dangerouslySetInnerHTML={{ __html: this.props.curData.desc }}></Col>
          </Row> : ""}

        Request

      {req_dataSource.length ?

路径参数：

 : ""}
      {dataSource.length ?

Headers：

 : ""}
      {this.props.curData.req_query && this.props.curData.req_query.length ?

Query：

        {this.req_query(this.props.curData.req_query)}

 : ""}
      {/*

        请求Body类型：
        {this.props.curData.req_body_type}

*/}

        { aceEditor }
        {
          this.req_body_form(this.props.curData.req_body_type, this.props.curData.req_body_form)
        }

      {/*

        返回Body类型：
        {this.props.curData.res_body_type}

*/}

Response

      {this.res_body(this.props.curData.res_body_type, this.props.curData.res_body)}

;
    if (!this.props.curData.title) {
      if (this.state.init) {
        res =

;
      } else {
        res = <ErrMsg type="noData" />;
      }
    }
    return res;
  }
}

export default View
<|endoftext|>


<commit_before>var React = require('react-native');
var NavBar = require('./../../shared/navbar');
var Button = require('./../../shared/button');

var {
  View,
  Text,
} = React;

// class ToggleButton extends React.Component {
//   constructor(props)
//   submitAnswer(answer) {
//     // send socket with answer
//     this.socket.emit('studentConnect', {
//       userId: this.state.userId,

//     })
//       this.props.navigator.pop();
//     // go to previous page
//   }
//   render() {
//     return (
//       <TouchableHighlight underlayColor='#FF0000' style={styles.button} onPress={this.submitAnswer.bind(this, this.props.label)}>
//         <Text>{this.props.label}</Text>
//       </TouchableHighlight>
//     )
//   }
// }

class MultiChoice extends React.Component {
  constructor (props){
    super(props);
    this.state = {
      pollInfo: this.props.route.pollInfo,
      userId: this.props.route.userId,
      socket: this.props.route.socket
    };
  }

  submitAnswer(answer) {
    console.log('Student',this.state.userId,'answered',answer,'to poll',this.state.pollInfo.pollId);
    // send socket with answer
    this.state.socket.emit('studentResponse', {
      userId: this.state.userId,
      answer: answer,
      pollId: this.state.pollInfo.pollId
    })
    this.props.navigator.pop();
    // go to previous page
  }

  renderButton(value) {
    return (
      <Button onPress={this.submitAnswer.bind(this, value)} text={value} />
    )
  }

  render() {
    return (
      <View style={{flex:1}}>
        <NavBar navi={this.props.navigator}>MultiChoice</NavBar>
        <View style={{flex: 1, justifyContent: 'center', alignItems: 'center' }}>
          {this.renderButton("A")}
          {this.renderButton("B")}
          {this.renderButton("C")}
          {this.renderButton("D")}
        </View>
      </View>
    )
  }
}

module.exports = MultiChoice;
<commit_msg>Add socket listener for poll close<commit_after>var React = require('react-native');
var NavBar = require('./../../shared/navbar');
var Button = require('./../../shared/button');

var {
  View,
  Text,
} = React;

class MultiChoice extends React.Component {
  constructor (props){
    super(props);
    this.socket = this.props.route.socket;
    this.userId = this.props.route.userId;
    this.pollInfo = this.props.route.pollInfo;

    this.socket.on('closePoll', function(data) {
      this.props.navigator.pop();
    });
  }

  submitAnswer(answer) {
    console.log('Student',this.userId,'answered',answer,'to poll',this.pollInfo.pollId);
    // send socket with answer
    this.socket.emit('studentResponse', {
      userId: this.userId,
      answer: answer,
      pollId: this.pollInfo.pollId
    })
    this.props.navigator.pop();
    // go to previous page
  }

  renderButton(value) {
    return (
      <Button onPress={this.submitAnswer.bind(this, value)} text={value} />
    )
  }

  render() {
    return (
      <View style={{flex:1}}>
        <NavBar navi={this.props.navigator}>MultiChoice</NavBar>
        <View style={{flex: 1, justifyContent: 'center', alignItems: 'center' }}>
          {this.renderButton("A")}
          {this.renderButton("B")}
          {this.renderButton("C")}
          {this.renderButton("D")}
        </View>
      </View>
    )
  }
}

module.exports = MultiChoice;
<|endoftext|>