<commit_before>"use strict";
const Socket = require("./common/floop");
const _ = require("lodash");
// const editorAction = require("./common/editor_action");
const term2_package = atom.packages.loadPackage('term3');
// https://discuss.atom.io/t/cant-activate-package-in-specs/13672/9
term2_package.activateNow();
const term2 = term2_package.mainModule;
const CompositeDisposable = require('event-kit').CompositeDisposable;

function TerminalManager () {
  // floobits term id to Term2 TermialView
  this.terms = {};
}

TerminalManager.prototype.nameForTerm = function (term) {
  return `atom-${this.username}-${term.id}`;
};

TerminalManager.prototype.start = function (username, floobits_terms) {
  this.username = username;
  const that = this;

  _.each(floobits_terms, function (t, termID) {
    const term = term2.newTerm(false, t.size[1], t.size[0], t.owner);
    that.terms[termID] = term;

    const subs = new CompositeDisposable();

    subs.add(term.onSTDIN(function (d) {
      Socket.send_term_stdin({'data': new Buffer(d).toString("base64"), id: termID});
    }));

    subs.add(term.onExit(function () {
      subs.dispose();
      delete that.terms[termID];
    }));
  });

  term2.getTerminals().forEach(function (t) {
    const d = t.getDimensions();
    Socket.send_create_term({term_name: that.nameForTerm(t), size: [d.rows, d.cols]});
  });

  Socket.onCREATE_TERM(function (req) {
    const name = req.term_name;
    const termID = req.id;

    const term = _.find(term2.getTerminals(), function (t) {
      return name === that.nameForTerm(t);
    });

    if (!term) {
      that.terms[termID] = term2.newTerm(false);
      return;
    }
    const subs = new CompositeDisposable();
    subs.add(term.onSTDOUT(function (d) {
      console.log('pty', d);
      Socket.send_term_stdout({'data': new Buffer(d).toString("base64"), id: termID});
    }));

    subs.add(term.onResize(function (rows, cols) {
      Socket.send_update_term({id: termID, size: [rows, cols]});
    }));

    subs.add(term.onExit( function () {
      Socket.send_delete_term({id: termID});
      subs.dispose();
      delete that.terms[termID];
    }));
  });

  Socket.onTERM_STDIN(function (data) {
    const term = that.terms[data.id];

    if (!term) {
      return;
    }

    try {
      term.input(new Buffer(data.data, "base64").toString("utf-8"));
    } catch (e) {
      console.warn(e);
    }
  });

  Socket.onDELETE_TERM(function (data) {
    const term = that.terms[data.id];

    if (!term) {
      return;
    }

    term.input("\r\n   *** TERMINAL CLOSED ***   \r\n");
  });

  Socket.onTERM_STDOUT(function (data) {
    const term = that.terms[data.id];

    if (!term) {
      return;
    }

    try {
      term.input(new Buffer(data.data, "base64").toString("utf-8"));
    } catch (e) {
      console.warn(e);
    }
  });

  Socket.onUPDATE_TERM(function (data) {
    const term = that.terms[data.id];

    if (!term) {
      return;
    }

    console.log("update term", data);
    if (data.size) {
      term.resize(data.size[0], data.size[1]);
    }
  });

  Socket.onSYNC(function (terms) {
    console.log("Attempting to sync...");
    _.each(terms, function (data, id) {
      const term = that.terms[id];

      if (!term) {
        return;
      }

      term.resize(data.cols, data.rows);
    });
  });
};

module.exports = new TerminalManager();
<commit_msg>remove debug statement<commit_after>"use strict";
const Socket = require("./common/floop");
const _ = require("lodash");
// const editorAction = require("./common/editor_action");
const term2_package = atom.packages.loadPackage('term3');
// https://discuss.atom.io/t/cant-activate-package-in-specs/13672/9
term2_package.activateNow();
const term2 = term2_package.mainModule;
const CompositeDisposable = require('event-kit').CompositeDisposable;

function TerminalManager () {
  // floobits term id to Term2 TermialView
  this.terms = {};
}

TerminalManager.prototype.nameForTerm = function (term) {
  return `atom-${this.username}-${term.id}`;
};

TerminalManager.prototype.start = function (username, floobits_terms) {
  this.username = username;
  const that = this;

  _.each(floobits_terms, function (t, termID) {
    const term = term2.newTerm(false, t.size[1], t.size[0], t.owner);
    that.terms[termID] = term;

    const subs = new CompositeDisposable();

    subs.add(term.onSTDIN(function (d) {
      Socket.send_term_stdin({'data': new Buffer(d).toString("base64"), id: termID});
    }));

    subs.add(term.onExit(function () {
      subs.dispose();
      delete that.terms[termID];
    }));
  });

  term2.getTerminals().forEach(function (t) {
    const d = t.getDimensions();
    Socket.send_create_term({term_name: that.nameForTerm(t), size: [d.rows, d.cols]});
  });

  Socket.onCREATE_TERM(function (req) {
    const name = req.term_name;
    const termID = req.id;

    const term = _.find(term2.getTerminals(), function (t) {
      return name === that.nameForTerm(t);
    });

    if (!term) {
      that.terms[termID] = term2.newTerm(false);
      return;
    }
    const subs = new CompositeDisposable();
    subs.add(term.onSTDOUT(function (d) {
      Socket.send_term_stdout({'data': new Buffer(d).toString("base64"), id: termID});
    }));

    subs.add(term.onResize(function (rows, cols) {
      Socket.send_update_term({id: termID, size: [rows, cols]});
    }));

    subs.add(term.onExit( function () {
      Socket.send_delete_term({id: termID});
      subs.dispose();
      delete that.terms[termID];
    }));
  });

  Socket.onTERM_STDIN(function (data) {
    const term = that.terms[data.id];

    if (!term) {
      return;
    }

    try {
      term.input(new Buffer(data.data, "base64").toString("utf-8"));
    } catch (e) {
      console.warn(e);
    }
  });

  Socket.onDELETE_TERM(function (data) {
    const term = that.terms[data.id];

    if (!term) {
      return;
    }

    term.input("\r\n   *** TERMINAL CLOSED ***   \r\n");
  });

  Socket.onTERM_STDOUT(function (data) {
    const term = that.terms[data.id];

    if (!term) {
      return;
    }

    try {
      term.input(new Buffer(data.data, "base64").toString("utf-8"));
    } catch (e) {
      console.warn(e);
    }
  });

  Socket.onUPDATE_TERM(function (data) {
    const term = that.terms[data.id];

    if (!term) {
      return;
    }

    console.log("update term", data);
    if (data.size) {
      term.resize(data.size[0], data.size[1]);
    }
  });

  Socket.onSYNC(function (terms) {
    console.log("Attempting to sync...");
    _.each(terms, function (data, id) {
      const term = that.terms[id];

      if (!term) {
        return;
      }

      term.resize(data.cols, data.rows);
    });
  });
};

module.exports = new TerminalManager();
<|endoftext|>


<commit_before>horizon.modals.success = function (data, textStatus, jqXHR) {
  $('body').append(data);
  $('.modal span.help-block').hide();
  $('.modal:last').modal();

  horizon.datatables.validate_button();

  // TODO(tres): Find some better way to deal with grouped form fields.
  var volumeField = $("#id_volume");
  if(volumeField) {
    var volumeContainer = volumeField.parent().parent();
    var deviceContainer = $("#id_device_name").parent().parent();
    var deleteOnTermContainer = $("#id_delete_on_terminate").parent().parent();

    function toggle_fields(show) {
      if(show) {
        volumeContainer.removeClass("hide");
        deviceContainer.removeClass("hide");
        deleteOnTermContainer.removeClass("hide");
      } else {
        volumeContainer.addClass("hide");
        deviceContainer.addClass("hide");
        deleteOnTermContainer.addClass("hide");
      }
    }

    if(volumeField.find("option").length == 1) {
      toggle_fields(false);
    } else {
      var disclosureElement = $("<div />").addClass("volume_boot_disclosure").text("Boot From Volume");

      volumeContainer.before(disclosureElement);

      disclosureElement.click(function() {
        if(volumeContainer.hasClass("hide")) {
          disclosureElement.addClass("on");
          toggle_fields(true);
        } else {
          disclosureElement.removeClass("on");
          toggle_fields(false);
        }
      });

      toggle_fields(false);
    }
  }
};

horizon.addInitFunction(function() {
  $(document).on('click', '.modal:not(.static_page) .cancel', function (evt) {
    $(this).closest('.modal').modal('hide');
    return false;
  });

  $(document).on('submit', '.modal:not(.static_page) form', function (evt) {
    var $form = $(this);
    if ($form.attr("enctype") === "multipart/form-data") {
      // AJAX-upload for files is not currently supported.
      return;
    }
    evt.preventDefault();
    $.ajax({
      type: "POST",
      url: $form.attr('action'),
      data: $form.serialize(),
      success: function (data, textStatus, jqXHR) {
        // TODO(gabriel): This isn't a long-term solution for AJAX redirects.
        // https://blueprints.launchpad.net/horizon/+spec/global-ajax-communication
        var header = jqXHR.getResponseHeader("X-Horizon-Location");
        if (header) {
          location.href = header;
        }
        $form.closest(".modal").modal("hide");
        horizon.modals.success(data, textStatus, jqXHR);
      },
      error: function(jqXHR, status, errorThrown) {
        $form.closest(".modal").modal("hide");
        horizon.alert("error", "There was an error submitting the form. Please try again.");
      }
    });
  });

  // Handle all modal hidden event to remove them as default
  $(document).on('hidden', '.modal', function () {
    $(this).remove();
  });

  $(document).on('show', '.modal', function(evt) {
    var scrollShift = $('body').scrollTop();
    var topVal = $(this).css('top');
    $(this).css('top', scrollShift + parseInt(topVal, 10));
  });

  $('.ajax-modal').live('click', function (evt) {
    var $this = $(this);
    $.ajax($this.attr('href'), {
      error: function(jqXHR, status, errorThrown) {
        if (jqXHR.status === 401){
          var redir_url = jqXHR.getResponseHeader("X-Horizon-Location");
          if (redir_url){
            location.href = redir_url;
          } else {
            location.reload(true);
          }
        }
      },
      success: horizon.modals.success
    });
    evt.preventDefault();
  });
});
<commit_msg>Prevent multiple modals/multiple form submissions via ajax.<commit_after>// Storage for our current jqXHR object.
horizon.modals._request = null;

horizon.modals.success = function (data, textStatus, jqXHR) {
  $('body').append(data);
  $('.modal span.help-block').hide();
  $('.modal:last').modal();

  horizon.datatables.validate_button();

  // TODO(tres): Find some better way to deal with grouped form fields.
  var volumeField = $("#id_volume");
  if(volumeField) {
    var volumeContainer = volumeField.parent().parent();
    var deviceContainer = $("#id_device_name").parent().parent();
    var deleteOnTermContainer = $("#id_delete_on_terminate").parent().parent();

    function toggle_fields(show) {
      if(show) {
        volumeContainer.removeClass("hide");
        deviceContainer.removeClass("hide");
        deleteOnTermContainer.removeClass("hide");
      } else {
        volumeContainer.addClass("hide");
        deviceContainer.addClass("hide");
        deleteOnTermContainer.addClass("hide");
      }
    }

    if(volumeField.find("option").length == 1) {
      toggle_fields(false);
    } else {
      var disclosureElement = $("<div />").addClass("volume_boot_disclosure").text("Boot From Volume");

      volumeContainer.before(disclosureElement);

      disclosureElement.click(function() {
        if(volumeContainer.hasClass("hide")) {
          disclosureElement.addClass("on");
          toggle_fields(true);
        } else {
          disclosureElement.removeClass("on");
          toggle_fields(false);
        }
      });

      toggle_fields(false);
    }
  }
};

horizon.addInitFunction(function() {
  $(document).on('click', '.modal .cancel', function (evt) {
    $(this).closest('.modal').modal('hide');
    evt.preventDefault();
  });

  $(document).on('submit', '.modal form', function (evt) {
    var $form = $(this),
        $button = $form.find(".modal-footer .btn-primary");
    if ($form.attr("enctype") === "multipart/form-data") {
      // AJAX-upload for files is not currently supported.
      return;
    }
    evt.preventDefault();

    // Prevent duplicate form POSTs
    $button.prop("disabled", true);

    $.ajax({
      type: "POST",
      url: $form.attr('action'),
      data: $form.serialize(),
      complete: function () {
        $button.prop("disabled", false);
      },
      success: function (data, textStatus, jqXHR) {
        // TODO(gabriel): This isn't a long-term solution for AJAX redirects.
        // https://blueprints.launchpad.net/horizon/+spec/global-ajax-communication
        var header = jqXHR.getResponseHeader("X-Horizon-Location");
        if (header) {
          location.href = header;
        }
        $form.closest(".modal").modal("hide");
        horizon.modals.success(data, textStatus, jqXHR);
      },
      error: function(jqXHR, status, errorThrown) {
        $form.closest(".modal").modal("hide");
        horizon.alert("error", "There was an error submitting the form. Please try again.");
      }
    });
  });

  // Handle all modal hidden event to remove them as default
  $(document).on('hidden', '.modal', function () {
    $(this).remove();
  });

  $(document).on('show', '.modal', function(evt) {
    var scrollShift = $('body').scrollTop();
    var topVal = $(this).css('top');
    $(this).css('top', scrollShift + parseInt(topVal, 10));
  });

  $('.ajax-modal').live('click', function (evt) {
    var $this = $(this);

    // If there's an existing modal request open, cancel it out.
    if (horizon.modals._request && typeof(horizon.modals._request.abort) !== undefined) {
      horizon.modals._request.abort();
    }

    horizon.modals._request = $.ajax($this.attr('href'), {
      complete: function () {
        // Clear the global storage;
        horizon.modals._request = null;
      },
      error: function(jqXHR, status, errorThrown) {
        if (jqXHR.status === 401){
          var redir_url = jqXHR.getResponseHeader("X-Horizon-Location");
          if (redir_url){
            location.href = redir_url;
          } else {
            location.reload(true);
          }
        }
      },
      success: horizon.modals.success
    });
    evt.preventDefault();
  });
});
<|endoftext|>


<commit_before>var documentView = Backbone.View.extend({
  el: '#content',
  table: '#documentTableID',
  colid: 0,
  docid: 0,
  currentKey: 0,

  init: function () {
    this.initTable();
  },

  events: {
    "click #saveDocument"       : "saveDocument",
    //"click #addDocumentLine"    : "addLine",
    "click #deleteRow"          : "deleteLine",
    "click #sourceView"         : "sourceView",
    "click #editFirstRow"       : "editFirst",
    "click #documentTableID tr" : "clicked",
    "click #editSecondRow"      : "editSecond",
    "keydown .sorting_1"        : "listenKey",
    "keydown"                   : "listenGlobalKey"
    //"focusout textarea"         : "checkFocus"
  },

  checkFocus: function(e) {
    //check if new row has to be saved
    var self = this;
    var data = $(this.table).dataTable().fnGetData();
    $.each(data, function(key, val) {
      if (val[0] === self.currentKey) {
        $(self.table).dataTable().fnDeleteRow( key );
      }
    });

  },

  listenGlobalKey: function(e) {
    if (e.keyCode === 27) {
      this.checkFocus();
    }
  },

  listenKey: function(e) {
    if (e.keyCode === 13) {
      $('.btn-success').click();
    }
  },

  template: new EJS({url: '/_admin/html/js/templates/documentView.ejs'}),

  typeCheck: function (type) {
    if (type === 'edge') {
      var result = window.arangoDocumentStore.getEdge(this.colid, this.docid);
      if (result === true) {
        this.initTable();
        this.drawTable();
      }
      else if (result === false) {
      }
    }
    else if (type === 'document') {
      var result = window.arangoDocumentStore.getDocument(this.colid, this.docid);
      if (result === true) {
        this.initTable();
        this.drawTable();
      }
      else if (result === false) {
      }
    }
  },
  clicked: function (a) {
    self = a.currentTarget;
    var checkData = $(this.table).dataTable().fnGetData(self);
    try {
      if (checkData[0] === '

') {
        this.addLine();
        return;
      }
    }
    catch (e) {
      return;
    }
  },
  render: function() {
    $(this.el).html(this.template.text);
    this.breadcrumb();
    return this;
  },
  editFirst: function (e) {
    var element = e.currentTarget;
    var prevElement = $(element).parent().prev();
    $(prevElement).click();
  },
  editSecond: function (e) {
    var element = e.currentTarget;
    var prevElement = $(element).parent().prev();
    $(prevElement).click();
  },
  sourceView: function () {
    window.location.hash = window.location.hash + "/source";
  },
  saveDocument: function () {
    if (this.type === 'document') {
      var model = window.arangoDocumentStore.models[0].attributes;
      model = JSON.stringify(model);
      var result = window.arangoDocumentStore.saveDocument(this.colid, this.docid, model);
      if (result === true) {
        arangoHelper.arangoNotification('Document saved');
      }
      else if (result === false) {
        arangoHelper.arangoAlert('Document error');
      }
    }
    else if (this.type === 'edge') {
      var model = window.arangoDocumentStore.models[0].attributes;
      model = JSON.stringify(model);
      var result = window.arangoDocumentStore.saveEdge(this.colid, this.docid, model);
      if (result === true) {
        arangoHelper.arangoNotification('Edge saved');
      }
      else if (result === false) {
        arangoHelper.arangoError('Edge error');
      }
    }
  },
  breadcrumb: function () {
    var name = window.location.hash.split("/");
    $('#transparentHeader').append(
      '
'+
      'Collections'+
      '  >  '+
      ''+name[1]+''+
      '  >  '+
      ''+name[2]+''+
      '
'
    );
  },
  drawTable: function () {
    var self = this;
    $(self.table).dataTable().fnAddData([
      '

',
      '

',
      ' ',
      '

',
      '

',
      '<button class="enabled" id="addRow"></button>'
    ]);
    $.each(window.arangoDocumentStore.models[0].attributes, function(key, value) {
      if (arangoHelper.isSystemAttribute(key)) {
        $(self.table).dataTable().fnAddData([
          key,
          '',
          self.value2html(value, true),
          JSON.stringify(value),
          "",
          ""
        ]);
      }
      else {
        $(self.table).dataTable().fnAddData(
          [
            key,
            '',
            self.value2html(value),
            JSON.stringify(value),
            '',
            '<button class="enabled" id="deleteRow"></button>'
        ]);
      }
    });
    this.makeEditable();
    $(this.table).dataTable().fnSort([ [0,'asc'] ]);

  },

  addLine: function () {
    var randomKey = arangoHelper.getRandomToken();
    var self = this;
    self.currentKey = "zkey" + randomKey;
    $(this.table).dataTable().fnAddData(
      [
        self.currentKey,
        '',
        this.value2html("editme"),
        JSON.stringify("editme"),
        '',
        '<button class="enabled" id="deleteRow"></button>'
      ]
    );
    this.makeEditable();
    var tableContent = $('table').find('td');
    $.each(tableContent, function(key, val) {
      if ($(val).text() === self.currentKey) {
        $(val).click();
        $('.jediTextarea textarea').val("");
        return;
      }
    });
  },

  deleteLine: function (a) {
    var row = $(a.currentTarget).closest("tr").get(0);
    $(this.table).dataTable().fnDeleteRow($(this.table).dataTable().fnGetPosition(row));
    this.updateLocalDocumentStorage();
  },

  initTable: function () {
    var documentTable = $(this.table).dataTable({
      "aaSorting": [[ 1, "asc" ]],
      "bAutoWidth": false,
      "bFilter": false,
      "bPaginate":false,
      "bSortable": false,
      "bLengthChange": false,
      "bDeferRender": true,
      "iDisplayLength": -1,
      "aoColumns": [
        {"sClass":"writeable", "bSortable": false, "sWidth":"200px" },
        {"sClass":"read_only leftCell", "bSortable": false, "sWidth": "20px"},
        {"sClass":"writeable rightCell", "bSortable": false},
        {"bVisible": false },
        {"sClass":"read_only leftCell", "bSortable": false, "sWidth": "20px"},
        {"sClass":"read_only leftCell", "bSortable": false, "sWidth": "30px"}
      ],
      "oLanguage": {"sEmptyTable": "No documents"}
    });
  },

  value2html: function (value, isReadOnly) {
    var self = this;
    var typify = function (value) {
      var checked = typeof(value);
      switch(checked) {
        case 'number':
          return ("" + value + "");
        case 'string':
          return ("" + self.escaped(value) + "");
        case 'boolean':
          return ("" + value + "");
        case 'object':
          if (value instanceof Array) {
          return ("" + self.escaped(JSON.stringify(value)) + "");
        }
        else {
          return (""+ self.escaped(JSON.stringify(value)) + "");
        }
      }
    };
    return (isReadOnly ? "(read-only) " : "") + typify(value);
  },

  escaped: function (value) {
    return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "&");
  },

  updateLocalDocumentStorage: function () {
    var data = $(this.table).dataTable().fnGetData();
    var result = {};

    for (row in data) {
      //Exclude "add-collection" row
      if (row !== '0') {
        var row_data = data[row];
        result[row_data[0]] = JSON.parse(row_data[3]);
      }
    }
    window.arangoDocumentStore.updateLocalDocument(result);
    //then sent to server
    this.saveDocument();
  },
  makeEditable: function () {
    var documentEditTable = $(this.table).dataTable();
    var self=this;
    var i = 0;
    $('.writeable', documentEditTable.fnGetNodes() ).each(function () {
      var aPos = documentEditTable.fnGetPosition(this);
      if (aPos[0] === 0) {
        $(this).removeClass('writeable');
      }
      if ( i == 1) {
        $(this).removeClass('writeable');
        i = 0;
      }
      if (arangoHelper.isSystemAttribute(this.innerHTML)) {
        $(this).removeClass('writeable');
        i = 1;
      }
    });
    $('.writeable', documentEditTable.fnGetNodes()).editable(function(value, settings) {
      var aPos = documentEditTable.fnGetPosition(this);
      if (aPos[1] == 0) {
          documentEditTable.fnUpdate(self.escaped(value), aPos[0], aPos[1]);
          self.updateLocalDocumentStorage();
          return value;
      }
      if (aPos[1] == 2) {
        var oldContent = JSON.parse(documentEditTable.fnGetData(aPos[0], aPos[1] + 1));
        var test = self.getTypedValue(value);
        if (String(value) == String(oldContent)) {
          // no change
          return self.value2html(oldContent);
        }
        else {
          // change update hidden row
          documentEditTable.fnUpdate(JSON.stringify(test), aPos[0], aPos[1] + 1);
          self.updateLocalDocumentStorage();
          // return visible row
          return self.value2html(test);
        }
      }
    },{
      data: function() {
        var aPos = documentEditTable.fnGetPosition(this);
        var value = documentEditTable.fnGetData(aPos[0], aPos[1]);
        if (aPos[1] == 0) {
          //check if this row was newly created
          if (value === self.currentKey) {
            return value;
          }
          return value;
        }
        if (aPos[1] == 2) {
          var oldContent = documentEditTable.fnGetData(aPos[0], aPos[1] + 1);
          if (typeof(oldContent) == 'object') {
            //grep hidden row and paste in visible row
            return value2html(oldContent);
          }
          else {
            return oldContent;
          }
        }
      },
      width: "none",
      type: "autogrow",
      tooltip: 'click to edit',
      cssclass : 'jediTextarea',
      submitcssclass: 'btn btn-success pull-right',
      cancelcssclass: 'btn btn-danger pull-right',
      cancel: '<button class="cancelButton btn btn-danger pull-right">Cancel</button">',
      submit: 'Save',
      onblur: 'ignore',
      //onblur: 'cancel',
      autogrow: {lineHeight: 16, minHeight: 30}
      //style: 'display: inline',
    });
  },
  getTypedValue: function (value) {
    value = value.replace(/(^\s+|\s+$)/g, '');
    if (value == 'true') {
      return true;
    }
    if (value == 'false') {
      return false;
    }
    if (value == 'null') {
      return null;
    }
    if (value.match(/^-?((\d+)?\.)?\d+$/)) {
      // TODO: support exp notation
      return parseFloat(value);
    }

    try {
      // assume list or object
      var test = JSON.parse(value);
      if (test instanceof Array) {
        // value is an array
        return test;
      }
      if (typeof(test) == 'object') {
        // value is an object
        return test;
      }
    }
    catch (err) {
    }

    // fallback: value is a string
    value = value + '';

    if (value !== '' && (value.substr(0, 1) != '"' || value.substr(-1) != '"')) {
      arangoHelper.arangoNotification('You have entered an invalid string value. Please review and adjust it.');
      throw "error";
    }

    try {
      value = JSON.parse(value);
    }
    catch (e) {
      arangoHelper.arangoNotification('You have entered an invalid string value. Please review and adjust it.');
      throw e;
    }
    return value;
  }

});
<commit_msg>do not make input rows bigger each row<commit_after>var documentView = Backbone.View.extend({
  el: '#content',
  table: '#documentTableID',
  colid: 0,
  docid: 0,
  currentKey: 0,

  init: function () {
    this.initTable();
  },

  events: {
    "click #saveDocument"       : "saveDocument",
    //"click #addDocumentLine"    : "addLine",
    "click #deleteRow"          : "deleteLine",
    "click #sourceView"         : "sourceView",
    "click #editFirstRow"       : "editFirst",
    "click #documentTableID tr" : "clicked",
    "click #editSecondRow"      : "editSecond",
    "keydown .sorting_1"        : "listenKey",
    "keydown"                   : "listenGlobalKey"
  },

  checkFocus: function(e) {
    //check if new row has to be saved
    var self = this;
    var data = $(this.table).dataTable().fnGetData();
    $.each(data, function(key, val) {
      if (val[0] === self.currentKey) {
        $(self.table).dataTable().fnDeleteRow( key );
      }
    });

  },

  listenGlobalKey: function(e) {
    if (e.keyCode === 27) {
      this.checkFocus();
    }
  },

  listenKey: function(e) {
    if (e.keyCode === 13) {
      $('.btn-success').click();
    }
  },

  template: new EJS({url: '/_admin/html/js/templates/documentView.ejs'}),

  typeCheck: function (type) {
    if (type === 'edge') {
      var result = window.arangoDocumentStore.getEdge(this.colid, this.docid);
      if (result === true) {
        this.initTable();
        this.drawTable();
      }
      else if (result === false) {
      }
    }
    else if (type === 'document') {
      var result = window.arangoDocumentStore.getDocument(this.colid, this.docid);
      if (result === true) {
        this.initTable();
        this.drawTable();
      }
      else if (result === false) {
      }
    }
  },
  clicked: function (a) {
    self = a.currentTarget;
    var checkData = $(this.table).dataTable().fnGetData(self);
    try {
      if (checkData[0] === '

') {
        this.addLine();
        return;
      }
    }
    catch (e) {
      return;
    }
  },
  render: function() {
    $(this.el).html(this.template.text);
    this.breadcrumb();
    return this;
  },
  editFirst: function (e) {
    var element = e.currentTarget;
    var prevElement = $(element).parent().prev();
    $(prevElement).click();
  },
  editSecond: function (e) {
    var element = e.currentTarget;
    var prevElement = $(element).parent().prev();
    $(prevElement).click();
  },
  sourceView: function () {
    window.location.hash = window.location.hash + "/source";
  },
  saveDocument: function () {
    if (this.type === 'document') {
      var model = window.arangoDocumentStore.models[0].attributes;
      model = JSON.stringify(model);
      var result = window.arangoDocumentStore.saveDocument(this.colid, this.docid, model);
      if (result === true) {
        arangoHelper.arangoNotification('Document saved');
      }
      else if (result === false) {
        arangoHelper.arangoAlert('Document error');
      }
    }
    else if (this.type === 'edge') {
      var model = window.arangoDocumentStore.models[0].attributes;
      model = JSON.stringify(model);
      var result = window.arangoDocumentStore.saveEdge(this.colid, this.docid, model);
      if (result === true) {
        arangoHelper.arangoNotification('Edge saved');
      }
      else if (result === false) {
        arangoHelper.arangoError('Edge error');
      }
    }
  },
  breadcrumb: function () {
    var name = window.location.hash.split("/");
    $('#transparentHeader').append(
      '
'+
      'Collections'+
      '  >  '+
      ''+name[1]+''+
      '  >  '+
      ''+name[2]+''+
      '
'
    );
  },
  drawTable: function () {
    var self = this;
    $(self.table).dataTable().fnAddData([
      '

',
      '

',
      ' ',
      '

',
      '

',
      '<button class="enabled" id="addRow"></button>'
    ]);
    $.each(window.arangoDocumentStore.models[0].attributes, function(key, value) {
      if (arangoHelper.isSystemAttribute(key)) {
        $(self.table).dataTable().fnAddData([
          key,
          '',
          self.value2html(value, true),
          JSON.stringify(value),
          "",
          ""
        ]);
      }
      else {
        $(self.table).dataTable().fnAddData(
          [
            key,
            '',
            self.value2html(value),
            JSON.stringify(value),
            '',
            '<button class="enabled" id="deleteRow"></button>'
        ]);
      }
    });
    this.makeEditable();
    $(this.table).dataTable().fnSort([ [0,'asc'] ]);

  },

  addLine: function () {
    var randomKey = arangoHelper.getRandomToken();
    var self = this;
    self.currentKey = "zkey" + randomKey;
    $(this.table).dataTable().fnAddData(
      [
        self.currentKey,
        '',
        this.value2html("editme"),
        JSON.stringify("editme"),
        '',
        '<button class="enabled" id="deleteRow"></button>'
      ]
    );
    this.makeEditable();
    var tableContent = $('table').find('td');
    $.each(tableContent, function(key, val) {
      if ($(val).text() === self.currentKey) {
        $(val).click();
        $('.jediTextarea textarea').val("");
        return;
      }
    });
  },

  deleteLine: function (a) {
    var row = $(a.currentTarget).closest("tr").get(0);
    $(this.table).dataTable().fnDeleteRow($(this.table).dataTable().fnGetPosition(row));
    this.updateLocalDocumentStorage();
  },

  initTable: function () {
    var documentTable = $(this.table).dataTable({
      "aaSorting": [[ 1, "asc" ]],
      "bAutoWidth": false,
      "bFilter": false,
      "bPaginate":false,
      "bSortable": false,
      "bLengthChange": false,
      "bDeferRender": true,
      "iDisplayLength": -1,
      "aoColumns": [
        {"sClass":"writeable", "bSortable": false, "sWidth":"200px" },
        {"sClass":"read_only leftCell", "bSortable": false, "sWidth": "20px"},
        {"sClass":"writeable rightCell", "bSortable": false},
        {"bVisible": false },
        {"sClass":"read_only leftCell", "bSortable": false, "sWidth": "20px"},
        {"sClass":"read_only leftCell", "bSortable": false, "sWidth": "30px"}
      ],
      "oLanguage": {"sEmptyTable": "No documents"}
    });
  },

  value2html: function (value, isReadOnly) {
    var self = this;
    var typify = function (value) {
      var checked = typeof(value);
      switch(checked) {
        case 'number':
          return ("" + value + "");
        case 'string':
          return ("" + self.escaped(value) + "");
        case 'boolean':
          return ("" + value + "");
        case 'object':
          if (value instanceof Array) {
          return ("" + self.escaped(JSON.stringify(value)) + "");
        }
        else {
          return (""+ self.escaped(JSON.stringify(value)) + "");
        }
      }
    };
    return (isReadOnly ? "(read-only) " : "") + typify(value);
  },

  escaped: function (value) {
    return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "&");
  },

  updateLocalDocumentStorage: function () {
    var data = $(this.table).dataTable().fnGetData();
    var result = {};

    for (row in data) {
      //Exclude "add-collection" row
      if (row !== '0') {
        var row_data = data[row];
        var key = row_data[0];
        var value = row_data[3];

        result[key] = JSON.parse(value);
      }
    }
    window.arangoDocumentStore.updateLocalDocument(result);
    //then sent to server
    this.saveDocument();
  },
  makeEditable: function () {
    var documentEditTable = $(this.table).dataTable();
    var self=this;
    var i = 0;
    $('.writeable', documentEditTable.fnGetNodes() ).each(function () {
      var aPos = documentEditTable.fnGetPosition(this);
      if (aPos[0] === 0) {
        $(this).removeClass('writeable');
      }
      if ( i == 1) {
        $(this).removeClass('writeable');
        i = 0;
      }
      if (arangoHelper.isSystemAttribute(this.innerHTML)) {
        $(this).removeClass('writeable');
        i = 1;
      }
    });
    $('.writeable', documentEditTable.fnGetNodes()).editable(function(value, settings) {
      var aPos = documentEditTable.fnGetPosition(this);
      if (aPos[1] == 0) {
          documentEditTable.fnUpdate(self.escaped(value), aPos[0], aPos[1]);
          self.updateLocalDocumentStorage();
          return value;
      }
      if (aPos[1] == 2) {
        var oldContent = JSON.parse(documentEditTable.fnGetData(aPos[0], aPos[1] + 1));
        var test = self.getTypedValue(value);
        if (String(value) == String(oldContent)) {
          // no change
          return self.value2html(oldContent);
        }
        else {
          // change update hidden row
          documentEditTable.fnUpdate(JSON.stringify(test), aPos[0], aPos[1] + 1);
          self.updateLocalDocumentStorage();
          // return visible row
          return self.value2html(test);
        }
      }
    },{
      data: function() {
        var aPos = documentEditTable.fnGetPosition(this);
        var value = documentEditTable.fnGetData(aPos[0], aPos[1]);
        if (aPos[1] == 0) {
          //check if this row was newly created
          if (value === self.currentKey) {
            return value;
          }
          return value;
        }
        if (aPos[1] == 2) {
          var oldContent = documentEditTable.fnGetData(aPos[0], aPos[1] + 1);
          if (typeof(oldContent) == 'object') {
            //grep hidden row and paste in visible row
            return value2html(oldContent);
          }
          else {
            return oldContent;
          }
        }
      },
      width: "none", // if not set, each row will get bigger & bigger (Safari & Firefox)
      type: "autogrow",
      tooltip: 'click to edit',
      cssclass : 'jediTextarea',
      submitcssclass: 'btn btn-success pull-right',
      cancelcssclass: 'btn btn-danger pull-right',
      cancel: '<button class="cancelButton btn btn-danger pull-right">Cancel</button">',
      submit: 'Save',
      onblur: 'ignore'
    });
  },
  getTypedValue: function (value) {
    value = value.replace(/(^\s+|\s+$)/g, '');
    if (value == 'true') {
      return true;
    }
    if (value == 'false') {
      return false;
    }
    if (value == 'null') {
      return null;
    }
    if (value.match(/^-?((\d+)?\.)?\d+$/)) {
      // TODO: support exp notation
      return parseFloat(value);
    }

    try {
      // assume list or object
      var test = JSON.parse(value);
      if (test instanceof Array) {
        // value is an array
        return test;
      }
      if (typeof(test) == 'object') {
        // value is an object
        return test;
      }
    }
    catch (err) {
    }

    // fallback: value is a string
    value = value + '';

    if (value !== '' && (value.substr(0, 1) != '"' || value.substr(-1) != '"')) {
      arangoHelper.arangoNotification('You have entered an invalid string value. Please review and adjust it.');
      throw "error";
    }

    try {
      value = JSON.parse(value);
    }
    catch (e) {
      arangoHelper.arangoNotification('You have entered an invalid string value. Please review and adjust it.');
      throw e;
    }
    return value;
  }

});
<|endoftext|>


<commit_before>import { throttle } from './func'
import { createEvent } from '../utils'

export function getParentScroller (vnode) {
  if (!vnode) return null
  if (vnode.weexType === 'scroller' || vnode.weexType === 'list') {
    return vnode
  }
  return getParentScroller(vnode.$parent)
}

export function hasIntersection (rect, ctRect) {
  return (rect.left < ctRect.right && rect.right > ctRect.left)
    && (rect.top < ctRect.bottom && rect.bottom > ctRect.top)
}

/**
 * [isElementVisible description]
 * @param  {HTMLElement}  el    a dom element.
 * @param  {HTMLElement}  container  optional, the container of this el.
 */
export function isElementVisible (el, container) {
  const bodyRect = {
    top: 0,
    left: 0,
    bottom: window.innerHeight,
    right: window.innerWidth
  }
  const ctRect = (container === document.body)
    ? bodyRect : container
    ? container.getBoundingClientRect() : bodyRect
  return hasIntersection(
    el.getBoundingClientRect(),
    ctRect)
}

export function isComponentVisible (component) {
  if (component.$el) {
    const scroller = getParentScroller(component)
    if (scroller && scroller.$el) {
      return hasIntersection(
        component.$el.getBoundingClientRect(),
        scroller.$el.getBoundingClientRect()
      )
    }
    else {
      return isElementVisible(component.$el)
    }
  }
  return false
}

// TODO: improve the efficiency
export function watchAppear (context) {
  if (!context) return null

  context.$nextTick(() => {
    if (context.$options && context.$options._parentListeners) {
      const on = context.$options._parentListeners
      if (on.appear || on.disappear) {
        const scroller = getParentScroller(context)
        let isWindow = false
        let container = window
        if (scroller && scroller.$el) {
          container = scroller.$el
        }
        else {
          isWindow = true
        }
        let lastScrollTop = container.scrollTop || window.pageYOffset

        context._visible = isElementVisible(context.$el, isWindow ? document.body : container)
        if (context._visible && on.appear) {
          if (on.appear.fn) {
            on.appear = on.appear.fn
          }
          on.appear(createEvent(context.$el, 'appear', { direction: null }))
        }
        const handler = throttle(event => {
          const visible = isElementVisible(context.$el, isWindow ? document.body : container)
          let listener = null
          let type = null
          if (visible !== context._visible) {
            context._visible = visible
            if (visible) {
              listener = on.appear
              type = 'appear'
            }
            else {
              listener = on.disappear
              type = 'disappear'
            }
            if (listener && listener.fn) {
              listener = listener.fn
            }
            const scrollTop = container.scrollTop || window.pageYOffset
            listener && listener(createEvent(context.$el, type, {
              direction: scrollTop < lastScrollTop ? 'down'
                : scrollTop > lastScrollTop ? 'up' : null
            }))
            lastScrollTop = scrollTop
          }
        }, 25, true)

        container.addEventListener('scroll', handler, false)
      }
    }
  })
}
<commit_msg>* [html5] support @appear.native for compatiblitiy with weex native.<commit_after>import { throttle, extend } from './func'
import { createEvent } from './event'

export function getParentScroller (vnode) {
  if (!vnode) return null
  if (vnode.weexType === 'scroller' || vnode.weexType === 'list') {
    return vnode
  }
  return getParentScroller(vnode.$parent)
}

export function hasIntersection (rect, ctRect) {
  return (rect.left < ctRect.right && rect.right > ctRect.left)
    && (rect.top < ctRect.bottom && rect.bottom > ctRect.top)
}

/**
 * [isElementVisible description]
 * @param  {HTMLElement}  el    a dom element.
 * @param  {HTMLElement}  container  optional, the container of this el.
 */
export function isElementVisible (el, container) {
  const bodyRect = {
    top: 0,
    left: 0,
    bottom: window.innerHeight,
    right: window.innerWidth
  }
  const ctRect = (container === document.body)
    ? bodyRect : container
    ? container.getBoundingClientRect() : bodyRect
  return hasIntersection(
    el.getBoundingClientRect(),
    ctRect)
}

export function isComponentVisible (component) {
  if (component.$el) {
    const scroller = getParentScroller(component)
    if (scroller && scroller.$el) {
      return hasIntersection(
        component.$el.getBoundingClientRect(),
        scroller.$el.getBoundingClientRect()
      )
    }
    else {
      return isElementVisible(component.$el)
    }
  }
  return false
}

// TODO: improve the efficiency
export function watchAppear (context) {
  if (!context) return null

  context.$nextTick(() => {
    if ((context.$options && context.$options._parentListeners)
      || (context.$vnode && context.$vnode.data && context.$vnode.data.on)) {
      const on = extend({}, context.$options._parentListeners, context.$vnode.data.on)
      if (on.appear || on.disappear) {
        const scroller = getParentScroller(context)
        let isWindow = false
        let container = window
        if (scroller && scroller.$el) {
          container = scroller.$el
        }
        else {
          isWindow = true
        }
        let lastScrollTop = container.scrollTop || window.pageYOffset

        context._visible = isElementVisible(context.$el, isWindow ? document.body : container)
        if (context._visible && on.appear) {
          if (on.appear.fn) {
            on.appear = on.appear.fn
          }
          on.appear(createEvent(context.$el, 'appear', { direction: null }))
        }
        const handler = throttle(event => {
          const visible = isElementVisible(context.$el, isWindow ? document.body : container)
          let listener = null
          let type = null
          if (visible !== context._visible) {
            context._visible = visible
            if (visible) {
              listener = on.appear
              type = 'appear'
            }
            else {
              listener = on.disappear
              type = 'disappear'
            }
            if (listener && listener.fn) {
              listener = listener.fn
            }
            const scrollTop = container.scrollTop || window.pageYOffset
            listener && listener(createEvent(context.$el, type, {
              direction: scrollTop < lastScrollTop ? 'down'
                : scrollTop > lastScrollTop ? 'up' : null
            }))
            lastScrollTop = scrollTop
          }
        }, 25, true)

        container.addEventListener('scroll', handler, false)
      }
    }
  })
}
<|endoftext|>


<commit_before>/**
 * A component that can load and render a model
 *
 * @class
 * @extends F.Component
 */
F.ModelComponent = new Class({
	toString: 'ModelComponent',
	extend: F.Component,

	/** @constructor */
	construct: function(config) {
		this.Model = this.Model || config.Model;
	},

	/** @lends F.ModelComponent# */

	/***
	 * Refresh the model
	 *
	 * @param {Function} Callback to call after successful refresh
	 *
	 * @returns {F.ModelComponent}	this, chainable
	 */
	refresh: function(callback) {
		this.model.fetch({
			success: function() {
				this.trigger('modelLoaded');

				if (typeof callback === 'function')
					callback.call(this, this.model);
			}.bind(this)
		});

		return this;
	},

	/**
	 * Use a different item model
	 *
	 * @param {Backbone.Model} model
	 *
	 * @returns {F.ModelComponent}	this, chainable
	 */
	setModel: function(model) {
		this.model = model;

		if (this.view) {
			// Tell the view to re-render the next time it loads
			this.view.rendered = null;
		}

		return this;
	},

	/**
	 * Load an item's model by ID
	 *
	 * @param {Function} itemId	ID of the item to fetch
	 * @param {Function} callback	Callback to execute on successful fetch
	 *
	 * @returns {F.ModelComponent}	this, chainable
	 */
	loadModel: function(itemId, callback) {
		// Create a model
		var model = new this.Model({
			id: itemId
		});

		// Assign the model to the view
		this.setModel(model);

		// Fetch model contents
		model.fetch({
			// TBD: add fetch options
			success: function() {
				this.trigger('modelLoaded');
				if (typeof callback === 'function')
					callback.call(this, model);
			}.bind(this)
		});

		return this;
	},

	/**
	 * Show this component, optionally fetching an item by ID or assiging a new model before render
	 *
	 * @param {Object} options
	 *
	 * @returns {F.ModelComponent}	this, chainable
	 */
	show: function(options) {
		options = options || {};
		if (options.id) {
			if (F.config.debug) {
				console.log('ModelComponent %s: fetching item with ID %s', this.toString(), options.id);
			}

			// Load the model by itemId, then show
			this.loadModel(options.id, function(model) {
				if (F.config.debug) {
					console.log('ModelComponent %s: fetch complete!', this.toString());
				}
				this.show(); // pass nothing to show and the view will re-render
			});
		}
		else if (options.model) {
			console.log('ModelComponent %s: showing with new model', this.toString(), options.model);
			this.setModel(options.model);
			this.show();
		}
		else
			this.inherited(arguments);

		return this;
	}
});
<commit_msg>Standardized loadModel to load, takes item ID or model. Switched 'config' to 'options', use Underscore extend instead of jQuery<commit_after>/**
 * A component that can load and render a model
 *
 * @class
 * @extends F.Component
 */
F.ModelComponent = new Class({
	toString: 'ModelComponent',
	extend: F.Component,

	/** @constructor */
	construct: function(options) {
		this.Model = this.Model || options.Model;
	},

	/** @lends F.ModelComponent# */

	/***
	 * Refresh the model
	 *
	 * @param {Function} Callback to call after successful refresh
	 *
	 * @returns {F.ModelComponent}	this, chainable
	 */
	refresh: function(callback) {
		this.model.fetch({
			success: function() {
				this.trigger('modelLoaded');

				if (typeof callback === 'function')
					callback.call(this, this.model);
			}.bind(this)
		});

		return this;
	},

	/**
	 * Use a different item model
	 *
	 * @param {Backbone.Model} model
	 *
	 * @returns {F.ModelComponent}	this, chainable
	 */
	_setModel: function(model) {
		this.model = model;

		if (this.view) {
			// Tell the view to re-render the next time it loads
			this.view.rendered = null;
		}

		return this;
	},

	/**
	 * Save a model to the server
	 *
	 * @param {Function} callback	Callback to execute on successful fetch
	 *
	 * @returns {F.ModelComponent}	this, chainable
	 */
	save: function(callback) {
		if (this.model) {
			this.model.save({
				success: function() {
					if (typeof callback === 'function')
						callback.call(this, this.model);

					this.trigger('saved');
				}.bind(this)
			});
		}
		else {
			console.warn('%s: Cannot save, model is not truthy', this.toString());
		}
		return this;
	},

	/**
	 * Load an item's model by ID
	 *
	 * @param {Function} itemId	ID of the item to fetch
	 * @param {Function} callback	Callback to execute on successful fetch
	 *
	 * @returns {F.ModelComponent}	this, chainable
	 */
	load: function(itemIdOrModel, callback) {
		// Load models
		if (typeof itemIdOrModel === 'string' || typeof itemIdOrModel === 'number') {
			// Create a blank model
			var model = new this.Model({
				id: itemIdOrModel
			});

			// Fetch model contents
			model.fetch({
				// TBD: add fetch options
				success: function() {
					// Assign the model to the view
					this._setModel(model);

					// Nofity
					this.trigger('modelLoaded');

					// Call callback
					if (typeof callback === 'function')
						callback.call(this, model);
				}.bind(this)
			});
		}
		else {
			// It must be an object
			this._setModel(model);
		}
		return this;

	},

	/**
	 * Show this component, optionally fetching an item by ID or assiging a new model before render
	 *
	 * @param {Object} options
	 *
	 * @returns {F.ModelComponent}	this, chainable
	 */
	show: function(options) {
		options = options || {};
		if (options.id) {
			if (F.options.debug) {
				console.log('ModelComponent %s: fetching item with ID %s', this.toString(), options.id);
			}

			// Load the model by itemId, then show
			this.load(options.id, function(model) {
				if (F.options.debug) {
					console.log('ModelComponent %s: fetch complete!', this.toString());
				}
				this.show(); // pass nothing to show and the view will re-render
			});
		}
		else if (options.model) {
			console.log('ModelComponent %s: showing with new model', this.toString(), options.model);
			this.setModel(options.model);
			this.show();
		}
		else
			this.inherited(arguments);

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


<commit_before>// Copyright 2016-2020, University of Colorado Boulder

/**
 * The button that pops up the Keyboard Help Dialog, which appears in the right side of the navbar and
 * to the left of the PhetButton.
 *
 * @author Jesse Greenberg
 */

import merge from '../../phet-core/js/merge.js';
import Image from '../../scenery/js/nodes/Image.js';
import Node from '../../scenery/js/nodes/Node.js';
import Dialog from '../../sun/js/Dialog.js';
import PhetioCapsule from '../../tandem/js/PhetioCapsule.js';
import PhetioObject from '../../tandem/js/PhetioObject.js';
import darkIconImage from '../images/keyboard-icon-on-white_png.js'; // on a white navbar
import brightIconImage from '../images/keyboard-icon_png.js'; // on a black navbar
import joist from './joist.js';
import JoistButton from './JoistButton.js';
import joistStrings from './joistStrings.js';
import KeyboardHelpDialog from './KeyboardHelpDialog.js';

// constants
const HELP_BUTTON_HEIGHT = 67;
const HELP_BUTTON_SCALE = 0.30;  // scale applied to the icon

class KeyboardHelpButton extends JoistButton {

  /**
   * @param {Property.<{keyboardHelpNode}>} screenProperty - Property that holds an object that stores keyboardHelpNode on it
   * @param {Property.<Color|string>} backgroundColorProperty
   * @param {Tandem} tandem
   * @param {Object} [options]
   */
  constructor( screenProperty, backgroundColorProperty, tandem, options ) {

    options = merge( {
      highlightExtensionWidth: 5,
      highlightExtensionHeight: 10,

      // The keyboard button is not vertically symmetric, due to the cable on the top.
      // This offset adjusts the body of the keyboard to be in the center, so it
      // will align with the speaker button and the PhET logo.
      highlightCenterOffsetY: 2,

      // pdom
      tagName: 'button',
      innerContent: joistStrings.a11y.keyboardHelp.hotKeysAndHelp
    }, options );

    PhetioObject.mergePhetioComponentOptions( { visibleProperty: { phetioFeatured: true } }, options );

    const content = new Node();

    // When the screen changes, swap out keyboard help content to the selected screen's content.
    screenProperty.link( screen => {
      assert && assert( screen.keyboardHelpNode, 'screen should have keyboardHelpNode' );
      content.children = [ screen.keyboardHelpNode ];
    } );

    const keyboardHelpDialogCapsule = new PhetioCapsule( tandem => {
      return new KeyboardHelpDialog( content, {
        focusOnCloseNode: this,
        tandem: tandem
      } );
    }, [], {
      tandem: tandem.createTandem( 'keyboardHelpDialogCapsule' ),
      phetioType: PhetioCapsule.PhetioCapsuleIO( Dialog.DialogIO )
    } );

    assert && assert( !options.listener, 'KeyboardHelpButton sets listener' );
    options.listener = () => {

      // Open the dialog.
      const keyboardHelpDialog = keyboardHelpDialogCapsule.getElement();
      keyboardHelpDialog.show();

      // If the button fired because of accessibility, focus the dialog's close button.
      if ( this.buttonModel.isA11yClicking() ) {
        keyboardHelpDialog.focusCloseButton();
      }
    };

    const icon = new Image( brightIconImage, {
      scale: HELP_BUTTON_SCALE / brightIconImage.height * HELP_BUTTON_HEIGHT,
      pickable: false
    } );

    super( icon, backgroundColorProperty, tandem, options );

    // Change the icon so that it is visible when the background changes from dark to light.
    backgroundColorProperty.link( backgroundColor => {
      icon.image = ( backgroundColor === 'black' ) ? brightIconImage : darkIconImage;
    } );
  }
}

joist.register( 'KeyboardHelpButton', KeyboardHelpButton );
export default KeyboardHelpButton;<commit_msg>revert conversion of KeyboardHelpButton to ES6 class (again!), https://github.com/phetsims/tasks/issues/1044<commit_after>// Copyright 2016-2020, University of Colorado Boulder

/**
 * The button that pops up the Keyboard Help Dialog, which appears in the right side of the navbar and
 * to the left of the PhetButton.
 *
 * @author Jesse Greenberg
 */

import inherit from '../../phet-core/js/inherit.js';
import merge from '../../phet-core/js/merge.js';
import Image from '../../scenery/js/nodes/Image.js';
import Node from '../../scenery/js/nodes/Node.js';
import Dialog from '../../sun/js/Dialog.js';
import PhetioCapsule from '../../tandem/js/PhetioCapsule.js';
import PhetioObject from '../../tandem/js/PhetioObject.js';
import darkIconImage from '../images/keyboard-icon-on-white_png.js'; // on a white navbar
import brightIconImage from '../images/keyboard-icon_png.js'; // on a black navbar
import joist from './joist.js';
import JoistButton from './JoistButton.js';
import joistStrings from './joistStrings.js';
import KeyboardHelpDialog from './KeyboardHelpDialog.js';

// constants
const hotKeysAndHelpString = joistStrings.a11y.keyboardHelp.hotKeysAndHelp;
const HELP_BUTTON_HEIGHT = 67;
const HELP_BUTTON_SCALE = 0.30;  // scale applied to the icon

/**
 * @param {Property.<{keyboardHelpNode}>} screenProperty - Property that holds an object that stores keyboardHelpNode on it
 * @param {Property.<Color|string>} backgroundColorProperty
 * @param {Tandem} tandem
 * @param {Object} [options]
 * @constructor
 */
function KeyboardHelpButton( screenProperty, backgroundColorProperty, tandem, options ) {

  options = merge( {
    highlightExtensionWidth: 5,
    highlightExtensionHeight: 10,

    // The keyboard button is not vertically symmetric, due to the cable on the top.
    // This offset adjusts the body of the keyboard to be in the center, so it
    // will align with the speaker button and the PhET logo
    highlightCenterOffsetY: 2,

    // pdom
    tagName: 'button',
    innerContent: hotKeysAndHelpString
  }, options );

  assert && assert( !options.listener, 'KeyboardHelpButton set\'s its own listener' );

  PhetioObject.mergePhetioComponentOptions( { visibleProperty: { phetioFeatured: true } }, options );

  const content = new Node();

  // When the screen changes, swap out keyboard help content to the selected screen's content
  screenProperty.link( screen => {
    assert && assert( screen.keyboardHelpNode, 'screen should have keyboardHelpNode' );
    content.children = [ screen.keyboardHelpNode ];
  } );

  const keyboardHelpDialogCapsule = new PhetioCapsule( tandem => {
    return new KeyboardHelpDialog( content, {
      focusOnCloseNode: this,
      tandem: tandem
    } );
  }, [], {
    tandem: tandem.createTandem( 'keyboardHelpDialogCapsule' ),
    phetioType: PhetioCapsule.PhetioCapsuleIO( Dialog.DialogIO )
  } );

  options.listener = () => {
    const keyboardHelpDialog = keyboardHelpDialogCapsule.getElement();
    keyboardHelpDialog.show();

    // if listener was fired because of accessibility
    if ( this.buttonModel.isA11yClicking() ) {

      // focus the close button if the dialog is open with a keyboard
      keyboardHelpDialog.focusCloseButton();
    }
  };

  const icon = new Image( brightIconImage, {
    scale: HELP_BUTTON_SCALE / brightIconImage.height * HELP_BUTTON_HEIGHT,
    pickable: false
  } );

  JoistButton.call( this, icon, backgroundColorProperty, tandem, options );

  // change the icon so that it is visible when the background changes from dark to light
  backgroundColorProperty.link( function( backgroundColor ) {
    icon.image = backgroundColor === 'black' ? brightIconImage : darkIconImage;
  } );
}

joist.register( 'KeyboardHelpButton', KeyboardHelpButton );

inherit( JoistButton, KeyboardHelpButton );
export default KeyboardHelpButton;<|endoftext|>


<commit_before>function pageViewModel(gvm) {
    // Page specific i18n bindings
    gvm.title = ko.computed(function(){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("AdminPage");}, gvm);
    gvm.pageHeaderEditUser = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserEditTitle");}, gvm);

    gvm.edituserid = $("#usereditHeader").data('value');

    gvm.userName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserName");}, gvm);
    gvm.firstName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("Firstname");}, gvm);
    gvm.lastName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("Lastname");}, gvm);
    gvm.userStatus = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserStatus");}, gvm);

    gvm.permissionRole = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("PermissionRole");}, gvm);
    gvm.permissionDescription = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("PermissionDescription");}, gvm);

    gvm.rights = ko.observableArray([]);
    gvm.allRights = ko.observableArray([]);
    gvm.user = ko.observableArray([]);

    gvm.updateUser = function(user)
    {
        gvm.user.push(user);
    },

    gvm.updatePermissions = function(permission)
    {
        gvm.rights.push(permission);
    },

    gvm.updateAllPermissions = function(permission)
    {
        gvm.allRights.push(permission);
    },

    gvm.removeUser = function(user) {
        gvm.user.remove(user);
        removeUser(user);
    },

    gvm.clearStructure = function() {
        gvm.rights.destroyAll();
        gvm.user.destroyAll();
    }
}

function initPage() {
    getAllUserDataById(viewModel.edituserid);
    setRights();
    checkPermissions();
}

function setRights(){
    viewModel.updateAllPermissions("GUEST");
    viewModel.updateAllPermissions("STUDENT");
    viewModel.updateAllPermissions("USER");
    viewModel.updateAllPermissions("SUPERUSER");
}

function getAllUserDataById(edituserid){
    console.log("Get data from user with id " + edituserid);
    $.getJSON("/api/edituser/" + edituserid, function(data)
    {
        var addedUsername = "";
        $.each(data, function(i, item){
            var current = item.username;

            $.each(data, function(i, item)
            {
                if(item.username == current && addedUsername != current){
                    console.log(current + " " + item.roleid + " " + item.role );
                    viewModel.updatePermissions(new Permission(item.roleid, item.role))
                }
            });

            if (addedUsername != current){
                addedUsername = current;
                console.log("Added: " + addedUsername);
                viewModel.updateUser(new User(item.userid, item.username, item.firstname, item.lastname, item.status, viewModel.rights()));
            }
        });
    });
}

function checkPermissions(){

    $.each(viewModel.allRights(), function(i, item){
        $.each(viewModel.rights, function(i, item){
            console.log(i + " " + item.permission);
            console.log("rights");

        });
    });
}

function Permission(id, permissions) {
    return {
        id: ko.observable(id),
        permission: ko.observable(permissions)
    };
}

function User(id, username, firstname, lastname, status, permissions) {
    return {
        id: ko.observable(id),
        username: ko.observable(username),
        firstname: ko.observable(firstname),
        lastname: ko.observable(lastname),
        status: ko.observable(status),
        permissions: ko.observableArray(permissions),

        removeThisUser: function() {
            if(confirm('Are you sure you want to remove this user?'))
            {
                viewModel.removeUser(this);
            }
        },

        changeStatus: function() {
            if (this.status() == "ACTIVE"){
                this.status("DISABLED");
            } else if (this.status() == "DISABLED") {
                this.status("ACTIVE");
            } else {
                this.status("WAIT_ACTIVATION");
            }
            updateUserStatus(this);
        }
    };
}<commit_msg>Admin edit user<commit_after>function pageViewModel(gvm) {
    // Page specific i18n bindings
    gvm.title = ko.computed(function(){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("AdminPage");}, gvm);
    gvm.pageHeaderEditUser = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserEditTitle");}, gvm);

    gvm.edituserid = $("#usereditHeader").data('value');

    gvm.userName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserName");}, gvm);
    gvm.firstName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("Firstname");}, gvm);
    gvm.lastName = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("Lastname");}, gvm);
    gvm.userStatus = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("UserStatus");}, gvm);

    gvm.permissionRole = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("PermissionRole");}, gvm);
    gvm.permissionDescription = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("PermissionDescription");}, gvm);

    gvm.rights = ko.observableArray([]);
    gvm.allRights = ko.observableArray([]);
    gvm.user = ko.observableArray([]);

    gvm.updateUser = function(user)
    {
        gvm.user.push(user);
    },

    gvm.updatePermissions = function(permission)
    {
        gvm.rights.push(permission);
    },

    gvm.updateAllPermissions = function(permission)
    {
        gvm.allRights.push(permission);
    },

    gvm.removeUser = function(user) {
        gvm.user.remove(user);
        removeUser(user);
    },

    gvm.clearStructure = function() {
        gvm.rights.destroyAll();
        gvm.user.destroyAll();
    }
}

function initPage() {
    getAllUserDataById(viewModel.edituserid);
    setRights();
    checkPermissions();
}

function setRights(){
    viewModel.updateAllPermissions("GUEST");
    viewModel.updateAllPermissions("STUDENT");
    viewModel.updateAllPermissions("USER");
    viewModel.updateAllPermissions("SUPERUSER");
}

function getAllUserDataById(edituserid){
    console.log("Get data from user with id " + edituserid);
    $.getJSON("/api/edituser/" + edituserid, function(data)
    {
        var addedUsername = "";
        $.each(data, function(i, item){
            var current = item.username;

            $.each(data, function(i, item)
            {
                if(item.username == current && addedUsername != current){
                    console.log(current + " " + item.roleid + " " + item.role );
                    viewModel.updatePermissions(new Permission(item.roleid, item.role))
                }
            });

            if (addedUsername != current){
                addedUsername = current;
                console.log("Added: " + addedUsername);
                viewModel.updateUser(new User(item.userid, item.username, item.firstname, item.lastname, item.status, viewModel.rights()));
            }
        });
    });
}

function checkPermissions(){

    $.each(viewModel.allRights(), function(i, item){
        $.each(viewModel.rights, function(i, item){
            console.log(viewModel.rights);
            console.log(i.permission + " " + item.permission);
            console.log("rights");

        });
    });
}

function Permission(id, permissions) {
    return {
        id: ko.observable(id),
        permission: ko.observable(permissions)
    };
}

function User(id, username, firstname, lastname, status, permissions) {
    return {
        id: ko.observable(id),
        username: ko.observable(username),
        firstname: ko.observable(firstname),
        lastname: ko.observable(lastname),
        status: ko.observable(status),
        permissions: ko.observableArray(permissions),

        removeThisUser: function() {
            if(confirm('Are you sure you want to remove this user?'))
            {
                viewModel.removeUser(this);
            }
        },

        changeStatus: function() {
            if (this.status() == "ACTIVE"){
                this.status("DISABLED");
            } else if (this.status() == "DISABLED") {
                this.status("ACTIVE");
            } else {
                this.status("WAIT_ACTIVATION");
            }
            updateUserStatus(this);
        }
    };
}<|endoftext|>


<commit_before>/*globals window*/
var $                     = require('jquery'),
    _                     = require('underscore'),
    Backbone              = require('backbone'),
    BaseController        = require('lib/base_controller'),
    AdminListView         = require('views/admin_list_view'),
    DetailView            = require('views/detail_view'),
    EditView              = require('views/edit_view'),
    FilterView            = require('views/filter_view'),
    IndexView             = require('views/index_view'),
    ListView              = require('views/list_view'),
    AboutView             = require('views/about_view'),
    Query                 = require('lib/query'),
    applicationController = new BaseController({ el: '#linksf' }),
    facilities            = require('collections/facilities').instance;

var Router = Backbone.Router.extend({
  routes: {
    '':                   'index',
    'list':               'index',
    'query?:queryString': 'query',
    'query':              'query',
    'detail/:id':         'detail',
    'edit/:id':           'edit',
    'about' :             'about',
    'filter':             'filter'
  },

  listView: null,

  listViewClass: ListView,
  initialize: function() {
    this.routesHit = 0;
    //keep count of number of routes handled by your application
    Backbone.history.on('route', function() { this.routesHit++; }, this);
  },
  back: function() {
    if(this.routesHit > 1) {
      //more than one route hit -> user did not land to current page directly
      window.history.back();
    } else {
      //otherwise go to the home page. Use replaceState if available so
      //the navigation doesn't create an extra history entry
      this.navigate('', {trigger:true, replace:true});
    }
  },
  index: function() {
    var indexView = new IndexView();
    return applicationController.render(indexView);
  },

  query: function(queryString) {
    var listViewClass = this.listViewClass,
        self          = this,
        queryParams;

    this.listView = self.listView || new listViewClass({ collection: facilities, isSingleton: true });

    queryParams = this.listView.generateQueryParams(queryString);
    queryParams.limit = 20;

    this.listView.performQuery(queryParams).done(function(results) {
      self.listView.reset();
      self.listView.options.categories = queryParams.filter.categories || [];
      self.listView.offset = results.offset;
      applicationController.render(self.listView);
      window.scrollTo(0, 0); // Scroll to top
    });
  },

  list: function() {
    var listView = this.listView || new this.listViewClass({collection: facilities});
    listView.collection = facilities;
    listView.searchParams = $('.query form').serializeObject();

    applicationController.render(listView);

    // run a default query
    if ( facilities.length === 0 ) {
      listView.submitQuery();
    }
  },

  filter: function() {
    this.filterView = this.filterView || new FilterView({isSingleton: true });
    applicationController.render(this.filterView);
  },

  renderFacility: function(facility) {
    var detailView = new DetailView({ model: facility.presentJSON() });
    window.scrollTo(0, 0); // Scroll to top
    return applicationController.render(detailView);
  },

  renderEdit: function(facility) {
    var editView = new EditView({ model: facility });
    return applicationController.render(editView);
  },

  detail: function(id) {
    this._getFacility(id, function(facility) {
      this.renderFacility(facility);
    }.bind(this));
  },

  edit: function(id) {
    this._getFacility(id, function(fac) {
      this.renderEdit(fac);
    }.bind(this));
  },
  about: function() {
    this.aboutView = this.aboutView || new AboutView();
    applicationController.render(this.aboutView);
  },

  _getFacility: function(id, done) {
    var facility = facilities.get(id);

    if ( !facility ) {
      //Fetch Facility from backend if not in collection
      Query.getByID(id).then(function(facility) {
        done(facility);
      });
    } else {
      done(facility);
    }
  }
});

var instance = new Router();
module.exports = { instance: instance };
<commit_msg>remove defunct list route<commit_after>/*globals window*/
var $                     = require('jquery'),
    _                     = require('underscore'),
    Backbone              = require('backbone'),
    BaseController        = require('lib/base_controller'),
    AdminListView         = require('views/admin_list_view'),
    DetailView            = require('views/detail_view'),
    EditView              = require('views/edit_view'),
    FilterView            = require('views/filter_view'),
    IndexView             = require('views/index_view'),
    ListView              = require('views/list_view'),
    AboutView             = require('views/about_view'),
    Query                 = require('lib/query'),
    applicationController = new BaseController({ el: '#linksf' }),
    facilities            = require('collections/facilities').instance;

var Router = Backbone.Router.extend({
  routes: {
    '':                   'index',
    'query?:queryString': 'query',
    'query':              'query',
    'detail/:id':         'detail',
    'edit/:id':           'edit',
    'about' :             'about',
    'filter':             'filter'
  },

  listView: null,

  listViewClass: ListView,
  initialize: function() {
    this.routesHit = 0;
    //keep count of number of routes handled by your application
    Backbone.history.on('route', function() { this.routesHit++; }, this);
  },
  back: function() {
    if(this.routesHit > 1) {
      //more than one route hit -> user did not land to current page directly
      window.history.back();
    } else {
      //otherwise go to the home page. Use replaceState if available so
      //the navigation doesn't create an extra history entry
      this.navigate('', {trigger:true, replace:true});
    }
  },
  index: function() {
    var indexView = new IndexView();
    return applicationController.render(indexView);
  },

  query: function(queryString) {
    var listViewClass = this.listViewClass,
        self          = this,
        queryParams;

    this.listView = self.listView || new listViewClass({ collection: facilities, isSingleton: true });

    queryParams = this.listView.generateQueryParams(queryString);
    queryParams.limit = 20;

    this.listView.performQuery(queryParams).done(function(results) {
      self.listView.reset();
      self.listView.options.categories = queryParams.filter.categories || [];
      self.listView.offset = results.offset;
      applicationController.render(self.listView);
      window.scrollTo(0, 0); // Scroll to top
    });
  },

  filter: function() {
    this.filterView = this.filterView || new FilterView({isSingleton: true });
    applicationController.render(this.filterView);
  },

  renderFacility: function(facility) {
    var detailView = new DetailView({ model: facility.presentJSON() });
    window.scrollTo(0, 0); // Scroll to top
    return applicationController.render(detailView);
  },

  renderEdit: function(facility) {
    var editView = new EditView({ model: facility });
    return applicationController.render(editView);
  },

  detail: function(id) {
    this._getFacility(id, function(facility) {
      this.renderFacility(facility);
    }.bind(this));
  },

  edit: function(id) {
    this._getFacility(id, function(fac) {
      this.renderEdit(fac);
    }.bind(this));
  },
  about: function() {
    this.aboutView = this.aboutView || new AboutView();
    applicationController.render(this.aboutView);
  },

  _getFacility: function(id, done) {
    var facility = facilities.get(id);

    if ( !facility ) {
      //Fetch Facility from backend if not in collection
      Query.getByID(id).then(function(facility) {
        done(facility);
      });
    } else {
      done(facility);
    }
  }
});

var instance = new Router();
module.exports = { instance: instance };
<|endoftext|>


<commit_before>function greedyJumbotron(jumbotron) {
    var HEIGHT_CHANGE_TOLERANCE = 100; // Approximately URL bar height in Chrome on tablet

    var viewportHeight = screen.height;

    window.onresize = function () {
        if (Math.abs(viewportHeight - screen.height) > HEIGHT_CHANGE_TOLERANCE) {
            viewportHeight = screen.height;
            update();
        }
    }

    function update() {
        jumbotron.style.height = viewportHeight + 'px';
    }

    update();
}<commit_msg>Fix to avoid reset on browsers - hack<commit_after>function greedyJumbotron(jumbotron) {
    // HAAXX
    if (screen.width >= 768) {
        jumbotron.height = 500;
        return;
    }
    var HEIGHT_CHANGE_TOLERANCE = 100; // Approximately URL bar height in Chrome on tablet

    var viewportHeight = screen.height;

    window.onresize = function () {
        if (Math.abs(viewportHeight - screen.height) > HEIGHT_CHANGE_TOLERANCE) {
            viewportHeight = screen.height;
            update();
        }
    }

    function update() {
        jumbotron.style.height = viewportHeight + 'px';
    }

    update();
}<|endoftext|>


<commit_before>(function ($)
  {
    $(document).ready(function() {
      // Access to clipboard functionality
      var clipboard = $('#clipboard-menu').data('clipboard');
      if (typeof clipboard === 'undefined')
      {
        return;
      }

      var $area = $('#clipboardButtonNode');
      var $buttons = $area.find('button');

      $area.find('.all, .none').on('click', function (event) {
        event.preventDefault();

        // Generator of toggle function returning deferred objects so we can
        // put them in a queue and execute sequentially.
        function getToggleFn(button) {
          return function() {
            return clipboard.toggle(true, { 'target': button })
          }
        }

        // Are we selecting or unselecting all?
        var select = $(this).hasClass('all');

        // Our queue of toggle functions
        var queue = [];
        $buttons.each(function(index) {
          var $button = $buttons.eq(index);

          if (select === $button.hasClass('added'))
          {
            return;
          }

          queue.push(getToggleFn($button.get()))
        });

        // Execute functions sequentially
        var d = $.Deferred().resolve();
        while (queue.length > 0) {
          d = d.then(queue.shift());
        }
      });
    });
  }
)(jQuery);
<commit_msg>Description update clipboard tooltip, Refs #10007<commit_after>(function ($)
  {
    $(document).ready(function() {
      // Access to clipboard functionality
      var clipboard = $('#clipboard-menu').data('clipboard');
      if (typeof clipboard === 'undefined')
      {
        return;
      }

      var $area = $('#clipboardButtonNode');
      var $buttons = $area.find('button');

      $area.find('.all, .none').on('click', function (event) {
        event.preventDefault();

        // Generator of toggle function returning deferred objects so we can
        // put them in a queue and execute sequentially.
        function getToggleFn(button) {
          return function() {
            return clipboard.toggle(false, { 'target': button })
          }
        }

        // Are we selecting or unselecting all?
        var select = $(this).hasClass('all');

        // Our queue of toggle functions
        var queue = [];
        $buttons.each(function(index) {
          var $button = $buttons.eq(index);

          if (select === $button.hasClass('added'))
          {
            return;
          }

          queue.push(getToggleFn($button.get()))
        });

        // Execute functions sequentially
        var d = $.Deferred().resolve();
        while (queue.length > 0) {
          d = d.then(queue.shift());
        }
      });
    });
  }
)(jQuery);
<|endoftext|>


<commit_before>/**
 *
 * @license
 * Copyright (C) 2016 Joseph Roque
 *
 * 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.
 *
 * @author Joseph Roque
 * @file Tooltip.js
 * @providesModule Tooltip
 * @description Displays a text overlay on the screen at a specified position, to be dismissed by the user
 *
 * @flow
 */
'use strict';

// React imports
import React from 'react';
import {
  AsyncStorage,
  Dimensions,
  LayoutAnimation,
  Platform,
  StyleSheet,
  Text,
  TouchableOpacity,
  View,
} from 'react-native';

// Type definition for component state.
type State = {
  active: boolean,
  backgroundColor: string,
  callback: ?() => any,
  hAlign: 'left' | 'right',
  text: ?string,
  textColor: string,
  vAlign: 'top' | 'bottom',
  x: number,
  y: number,
};

// Parameters that creating a tooltip accepts
type TooltipOptions = {
  backgroundColor?: string,
  callback?: () => any,
  hAlign: 'left' | 'right' | 'center',
  text: string,
  textColor?: string,
  vAlign: 'top' | 'bottom' | 'center',
  x?: number,
  y?: number,
};

// Imports
const Constants = require('Constants');
const Ionicon = require('react-native-vector-icons/Ionicons');

// For defining width, position of the tooltip
const {width, height} = Dimensions.get('window');
const TOOLTIP_WIDTH_PCT: number = 0.75;

// Size of the dismiss icon
const DISMISS_ICON_SIZE: number = 24;

// Name of the close icon to display
let dismissIcon: string;
if (Platform.OS === 'ios') {
  dismissIcon = 'ios-close';
} else {
  dismissIcon = 'md-close';
}

/** Instance of the tooltip. */
let globalTooltip: any;

class Tooltip extends React.Component {

  /** Represents if the user has seen the tooltip indicating how to search all. */
  static HOW_TO_SEARCH_ALL: string = 'tooltip_search_all';

  /** Represents if the user has seen the tooltip to show building image. */
  static SHOW_BUILDING_IMAGE: string = 'tooltip_show_building_image';

  /**
   * Checks if a certain tooltip has already been acknowledged by a user.
   *
   * @param {string} id     tooltip id to check
   * @param {function} done callback to return result
   */
  static hasSeenTooltip(id: string, done: () => any): void {
    if (id == null || id.length === 0) {
      // If id is invalid, return false
      done(false);
      return;
    }

    AsyncStorage.getItem(id, (err, result) => {
      if (err) {
        // Output any errors, return false
        console.error('Error retrieving tooltip info: ' + id + '. ' + err);
        done(false);
        return;
      }

      // Only return true if the value is exactly equal to true
      done(result === 'true');
    });
  }

  /**
   * Tooltip has been acknowledged by a user.
   *
   * @param {string} id tooltip to acknowledge
   */
  static setHasSeenTooltip(id: string) {
    AsyncStorage.setItem(id, 'true');
  }

  /**
   * Returns true if and only if a tooltip is currently shown.
   *
   * @returns {boolean} false if the tooltip is null or not active, true otherwise.
   */
  static isTooltipActive(): boolean {
    return globalTooltip != null && globalTooltip.state.active;
  }

  /**
   * Shows the tooltip overlaying a certain position, with the text provided.
   *
   * @param {TooltipOptions} options configuration of tooltip
   */
  static showTooltip(options: TooltipOptions) {
    if (globalTooltip != null) {
      // Either center horizontally, or set to provided x value
      let x = 0;
      if (options.hAlign === 'center') {
        x = width / 2 - (width * TOOLTIP_WIDTH_PCT) / 2;
      } else {
        x = options.x ? options.x : 0;
      }

      // Either center vertically, or set to provided y value
      let y = 0;
      if (options.vAlign === 'center') {
        y = height / 2;
      } else {
        y = options.y ? options.y : 0;
      }

      LayoutAnimation.easeInEaseOut();
      globalTooltip.setState({
        active: true,
        backgroundColor: options.backgroundColor ? options.backgroundColor : Constants.Colors.charcoalGrey,
        callback: options.callback,
        hAlign: options.hAlign,
        text: options.text,
        textColor: options.textColor ? options.textColor : Constants.Colors.primaryWhiteText,
        vAlign: options.vAlign,
        x: x,
        y: y,
      });
    }
  }

  /** State of the component. */
  state: State;

  /**
   * Declares initial state.
   *
   * @param {{}} props props of the component
   */
  constructor(props: {}) {
    super(props);

    this.state = {
      active: false,
      backgroundColor: Constants.Colors.charcoalGrey,
      callback: null,
      hAlign: 'left',
      text: null,
      textColor: Constants.Colors.primaryWhiteText,
      vAlign: 'top',
      x: 0,
      y: 0,
    };
  }

  /**
   * Stores a reference to this Tooltip instance in a variable.
   */
  componentDidMount(): void {

    /* eslint-disable consistent-this */

    globalTooltip = this;

    /* eslint-disable consistent-this */

  }

  /**
   * Clears the reference to this Tooltip.
   */
  componentWillUnmount(): void {
    globalTooltip = null;
  }

  /**
   * Hides the tooltip when clicked.
   */
  _dismiss(): void {
    if (this.state.callback) {
      this.state.callback();
    }

    LayoutAnimation.easeInEaseOut();
    this.setState({
      active: false,
    });
  }

  /**
   * Displays the Tooltip if it is active.
   *
   * @returns {?ReactElement<any>} the tooltip, if this.state.active is true, null otherwise.
   */
  render(): ?ReactElement< any > {
    if (!this.state.active) {
      return null;
    }

    let hAlign: Object = {};
    if (this.state.hAlign === 'right') {
      hAlign = {
        right: this.state.x,
      };
    } else {
      hAlign = {
        left: this.state.x,
      };
    }

    let vAlign: Object = {};
    if (this.state.vAlign === 'bottom') {
      vAlign = {
        bottom: this.state.y,
      };
    } else {
      vAlign = {
        top: this.state.y,
      };
    }

    return (
      <TouchableOpacity
          style={[{position: 'absolute'}, hAlign, vAlign]}
          onPress={this._dismiss.bind(this)}>
        <View style={[_styles.tooltip, {backgroundColor: this.state.backgroundColor}]}>
          <Text style={[_styles.tooltipText, {color: this.state.textColor}]}>{this.state.text}</Text>
          <Ionicon
              color={Constants.Colors.secondaryWhiteText}
              name={dismissIcon}
              size={DISMISS_ICON_SIZE}
              style={_styles.tooltipIcon} />
        </View>
      </TouchableOpacity>
    );
  }
}

// Private styles for the component
const _styles = StyleSheet.create({
  tooltip: {
    width: width * TOOLTIP_WIDTH_PCT,
    borderRadius: 5,
    flexDirection: 'row',
    alignItems: 'center',
  },
  tooltipText: {
    flex: 1,
    margin: 10,
    fontSize: Constants.Text.Medium,
  },
  tooltipIcon: {
    marginRight: 10,
  },
});

module.exports = Tooltip;
<commit_msg>Dismiss tooltips with static method<commit_after>/**
 *
 * @license
 * Copyright (C) 2016 Joseph Roque
 *
 * 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.
 *
 * @author Joseph Roque
 * @file Tooltip.js
 * @providesModule Tooltip
 * @description Displays a text overlay on the screen at a specified position, to be dismissed by the user
 *
 * @flow
 */
'use strict';

// React imports
import React from 'react';
import {
  AsyncStorage,
  Dimensions,
  LayoutAnimation,
  Platform,
  StyleSheet,
  Text,
  TouchableOpacity,
  View,
} from 'react-native';

// Type definition for component state.
type State = {
  active: boolean,
  backgroundColor: string,
  callback: ?() => any,
  hAlign: 'left' | 'right',
  id: ?string,
  text: ?string,
  textColor: string,
  vAlign: 'top' | 'bottom',
  x: number,
  y: number,
};

// Parameters that creating a tooltip accepts
type TooltipOptions = {
  backgroundColor?: string,
  callback?: () => any,
  hAlign: 'left' | 'right' | 'center',
  id?: string,
  text: string,
  textColor?: string,
  vAlign: 'top' | 'bottom' | 'center',
  x?: number,
  y?: number,
};

// Imports
const Constants = require('Constants');
const Ionicon = require('react-native-vector-icons/Ionicons');

// For defining width, position of the tooltip
const {width, height} = Dimensions.get('window');
const TOOLTIP_WIDTH_PCT: number = 0.75;

// Size of the dismiss icon
const DISMISS_ICON_SIZE: number = 24;

// Name of the close icon to display
let dismissIcon: string;
if (Platform.OS === 'ios') {
  dismissIcon = 'ios-close';
} else {
  dismissIcon = 'md-close';
}

/** Instance of the tooltip. */
let globalTooltip: any;

class Tooltip extends React.Component {

  /** Represents if the user has seen the tooltip indicating how to search all. */
  static HOW_TO_SEARCH_ALL: string = 'tooltip_search_all';

  /** Represents if the user has seen the tooltip to show building image. */
  static SHOW_BUILDING_IMAGE: string = 'tooltip_show_building_image';

  /**
   * Hides the current Tooltip if its id matches the one provided.
   *
   * @param {string} id id of the Tooltip to hide
   */
  static dismiss(id?: string) {
    if (globalTooltip != null) {
      if (id == null || globalTooltip.state.id === id) {
        globalTooltip._dismiss();
      }
    }
  }

  /**
   * Checks if a certain tooltip has already been acknowledged by a user.
   *
   * @param {string} id     tooltip id to check
   * @param {function} done callback to return result
   */
  static hasSeenTooltip(id: string, done: () => any): void {
    if (id == null || id.length === 0) {
      // If id is invalid, return false
      done(false);
      return;
    }

    AsyncStorage.getItem(id, (err, result) => {
      if (err) {
        // Output any errors, return false
        console.error('Error retrieving tooltip info: ' + id + '. ' + err);
        done(false);
        return;
      }

      // Only return true if the value is exactly equal to true
      done(result === 'true');
    });
  }

  /**
   * Tooltip has been acknowledged by a user.
   *
   * @param {string} id tooltip to acknowledge
   */
  static setHasSeenTooltip(id: string) {
    AsyncStorage.setItem(id, 'true');
  }

  /**
   * Returns true if and only if a tooltip is currently shown.
   *
   * @returns {boolean} false if the tooltip is null or not active, true otherwise.
   */
  static isTooltipActive(): boolean {
    return globalTooltip != null && globalTooltip.state.active;
  }

  /**
   * Shows the tooltip overlaying a certain position, with the text provided.
   *
   * @param {TooltipOptions} options configuration of tooltip
   */
  static showTooltip(options: TooltipOptions) {
    if (globalTooltip != null) {
      // Either center horizontally, or set to provided x value
      let x = 0;
      if (options.hAlign === 'center') {
        x = width / 2 - (width * TOOLTIP_WIDTH_PCT) / 2;
      } else {
        x = options.x ? options.x : 0;
      }

      // Either center vertically, or set to provided y value
      let y = 0;
      if (options.vAlign === 'center') {
        y = height / 2;
      } else {
        y = options.y ? options.y : 0;
      }

      LayoutAnimation.easeInEaseOut();
      globalTooltip.setState({
        active: true,
        backgroundColor: options.backgroundColor ? options.backgroundColor : Constants.Colors.charcoalGrey,
        callback: options.callback,
        hAlign: options.hAlign,
        id: options.id,
        text: options.text,
        textColor: options.textColor ? options.textColor : Constants.Colors.primaryWhiteText,
        vAlign: options.vAlign,
        x: x,
        y: y,
      });
    }
  }

  /** State of the component. */
  state: State;

  /**
   * Declares initial state.
   *
   * @param {{}} props props of the component
   */
  constructor(props: {}) {
    super(props);

    this.state = {
      active: false,
      backgroundColor: Constants.Colors.charcoalGrey,
      callback: null,
      hAlign: 'left',
      text: null,
      textColor: Constants.Colors.primaryWhiteText,
      vAlign: 'top',
      x: 0,
      y: 0,
    };
  }

  /**
   * Stores a reference to this Tooltip instance in a variable.
   */
  componentDidMount(): void {

    /* eslint-disable consistent-this */

    globalTooltip = this;

    /* eslint-disable consistent-this */

  }

  /**
   * Clears the reference to this Tooltip.
   */
  componentWillUnmount(): void {
    globalTooltip = null;
  }

  /**
   * Hides the tooltip when clicked.
   */
  _dismiss(): void {
    if (!this.state.active) {
      return;
    }

    if (this.state.callback) {
      this.state.callback();
    }

    LayoutAnimation.easeInEaseOut();
    this.setState({
      active: false,
    });
  }

  /**
   * Displays the Tooltip if it is active.
   *
   * @returns {?ReactElement<any>} the tooltip, if this.state.active is true, null otherwise.
   */
  render(): ?ReactElement< any > {
    if (!this.state.active) {
      return null;
    }

    let hAlign: Object = {};
    if (this.state.hAlign === 'right') {
      hAlign = {
        right: this.state.x,
      };
    } else {
      hAlign = {
        left: this.state.x,
      };
    }

    let vAlign: Object = {};
    if (this.state.vAlign === 'bottom') {
      vAlign = {
        bottom: this.state.y,
      };
    } else {
      vAlign = {
        top: this.state.y,
      };
    }

    return (
      <TouchableOpacity
          style={[{position: 'absolute'}, hAlign, vAlign]}
          onPress={this._dismiss.bind(this)}>
        <View style={[_styles.tooltip, {backgroundColor: this.state.backgroundColor}]}>
          <Text style={[_styles.tooltipText, {color: this.state.textColor}]}>{this.state.text}</Text>
          <Ionicon
              color={Constants.Colors.secondaryWhiteText}
              name={dismissIcon}
              size={DISMISS_ICON_SIZE}
              style={_styles.tooltipIcon} />
        </View>
      </TouchableOpacity>
    );
  }
}

// Private styles for the component
const _styles = StyleSheet.create({
  tooltip: {
    width: width * TOOLTIP_WIDTH_PCT,
    borderRadius: 5,
    flexDirection: 'row',
    alignItems: 'center',
  },
  tooltipText: {
    flex: 1,
    margin: 10,
    fontSize: Constants.Text.Medium,
  },
  tooltipIcon: {
    marginRight: 10,
  },
});

module.exports = Tooltip;
<|endoftext|>