<commit_before>//some of the functions are adjusted from Three.js(http://threejs.org)

'use strict';

var p5 = require('../core/core');
/**
 * p5 Geometry class
 * @class p5.Geometry
 * @constructor
 * @param  {function | Object} vertData callback function or Object
 *                     containing routine(s) for vertex data generation
 * @param  {Integer} [detailX] number of vertices on horizontal surface
 * @param  {Integer} [detailY] number of vertices on horizontal surface
 * @param {function} [callback] function to call upon object instantiation.
 *
 */
p5.Geometry = function(detailX, detailY, callback) {
  //an array containing every vertex
  //@type [p5.Vector]
  this.vertices = [];

  //an array containing every vertex for stroke drawing
  this.lineVertices = [];

  //an array 1 normal per lineVertex with
  //final position representing which direction to
  //displace for strokeWeight
  //[[0,0,-1,1], [0,1,0,-1] ...];
  this.lineNormals = [];

  //an array containing 1 normal per vertex
  //@type [p5.Vector]
  //[p5.Vector, p5.Vector, p5.Vector,p5.Vector, p5.Vector, p5.Vector,...]
  this.vertexNormals = [];
  //an array containing each three vertex indices that form a face
  //[[0, 1, 2], [2, 1, 3], ...]
  this.faces = [];
  //a 2D array containing uvs for every vertex
  //[[0.0,0.0],[1.0,0.0], ...]
  this.uvs = [];
  // a 2D array containing edge connectivity pattern for create line vertices
  //based on faces for most objects;
  this.edges = [];
  this.detailX = detailX !== undefined ? detailX : 1;
  this.detailY = detailY !== undefined ? detailY : 1;
  if (callback instanceof Function) {
    callback.call(this);
  }
  this.name = 'p5.Geometry'; // for friendly debugger system

  return this; // TODO: is this a constructor?
};

/**
 * @method computeFaces
 * @chainable
 */
p5.Geometry.prototype.computeFaces = function() {
  var sliceCount = this.detailX + 1;
  var a, b, c, d;
  for (var i = 0; i < this.detailY; i++) {
    for (var j = 0; j < this.detailX; j++) {
      a = i * sliceCount + j; // + offset;
      b = i * sliceCount + j + 1; // + offset;
      c = (i + 1) * sliceCount + j + 1; // + offset;
      d = (i + 1) * sliceCount + j; // + offset;
      this.faces.push([a, b, d]);
      this.faces.push([d, b, c]);
    }
  }
  return this;
};

p5.Geometry.prototype._getFaceNormal = function(faceId) {
  //This assumes that vA->vB->vC is a counter-clockwise ordering
  var face = this.faces[faceId];
  var vA = this.vertices[face[0]];
  var vB = this.vertices[face[1]];
  var vC = this.vertices[face[2]];
  var ab = p5.Vector.sub(vB, vA);
  var ac = p5.Vector.sub(vC, vA);
  var n = p5.Vector.cross(ab, ac);
  var ln = p5.Vector.mag(n);
  var sinAlpha = ln / (p5.Vector.mag(ab) * p5.Vector.mag(ac));
  if (sinAlpha === 0 || isNaN(sinAlpha)) {
    throw new Error(
      'Degenerate geometry: This face either has colinear sides or a repeated vertex.'
    );
  }
  return n.mult(Math.asin(sinAlpha) / ln);
};
/**
 * computes smooth normals per vertex as an average of each
 * face.
 * @method computeNormals
 * @chainable
 */
p5.Geometry.prototype.computeNormals = function() {
  var normals = [];
  for (var v = 0; v < this.vertices.length; v++) {
    var normal = new p5.Vector();
    for (var i = 0; i < this.faces.length; i++) {
      //if our face contains a given vertex
      //calculate an average of the normals
      //of the triangles adjacent to that vertex
      if (
        this.faces[i][0] === v ||
        this.faces[i][1] === v ||
        this.faces[i][2] === v
      ) {
        var faceNormal = normals[i] || (normals[i] = this._getFaceNormal(i));
        normal = normal.add(faceNormal);
      }
    }
    normal = normal.normalize();
    this.vertexNormals.push(normal);
  }
  return this;
};

/**
 * Averages the vertex normals. Used in curved
 * surfaces
 * @method averageNormals
 * @chainable
 */
p5.Geometry.prototype.averageNormals = function() {
  for (var i = 0; i <= this.detailY; i++) {
    var offset = this.detailX + 1;
    var temp = p5.Vector.add(
      this.vertexNormals[i * offset],
      this.vertexNormals[i * offset + this.detailX]
    );

    temp = p5.Vector.div(temp, 2);
    this.vertexNormals[i * offset] = temp;
    this.vertexNormals[i * offset + this.detailX] = temp;
  }
  return this;
};

/**
 * Averages pole normals.  Used in spherical primitives
 * @method averagePoleNormals
 * @chainable
 */
p5.Geometry.prototype.averagePoleNormals = function() {
  //average the north pole
  var sum = new p5.Vector(0, 0, 0);
  for (var i = 0; i < this.detailX; i++) {
    sum.add(this.vertexNormals[i]);
  }
  sum = p5.Vector.div(sum, this.detailX);

  for (i = 0; i < this.detailX; i++) {
    this.vertexNormals[i] = sum;
  }

  //average the south pole
  sum = new p5.Vector(0, 0, 0);
  for (
    i = this.vertices.length - 1;
    i > this.vertices.length - 1 - this.detailX;
    i--
  ) {
    sum.add(this.vertexNormals[i]);
  }
  sum = p5.Vector.div(sum, this.detailX);

  for (
    i = this.vertices.length - 1;
    i > this.vertices.length - 1 - this.detailX;
    i--
  ) {
    this.vertexNormals[i] = sum;
  }
  return this;
};

/**
 * Create a 2D array for establishing stroke connections
 * @private
 * @return {p5.Geometry}
 */
p5.Geometry.prototype._makeTriangleEdges = function() {
  if (Array.isArray(this.strokeIndices)) {
    for (var i = 0, max = this.strokeIndices.length; i < max; i++) {
      this.edges.push(this.strokeIndices[i]);
    }
  } else {
    for (var j = 0; j < this.faces.length; j++) {
      this.edges.push([this.faces[j][0], this.faces[j][1]]);
      this.edges.push([this.faces[j][1], this.faces[j][2]]);
      this.edges.push([this.faces[j][2], this.faces[j][0]]);
    }
  }
  return this;
};

/**
 * Create 4 vertices for each stroke line, two at the beginning position
 * and two at the end position. These vertices are displaced relative to
 * that line's normal on the GPU
 * @private
 * @return {p5.Geometry}
 */
p5.RendererGL.prototype._edgesToVertices = function(geom) {
  geom.lineVertices = [];
  for (var i = 0; i < geom.edges.length; i++) {
    var begin = geom.vertices[geom.edges[i][0]];
    var end = geom.vertices[geom.edges[i][1]];
    var dir = end
      .copy()
      .sub(begin)
      .normalize();
    var a = begin.array();
    var b = begin.array();
    var c = end.array();
    var d = end.array();
    var dirAdd = dir.array();
    var dirSub = dir.array();
    // below is used to displace the pair of vertices at beginning and end
    // in opposite directions
    dirAdd.push(1);
    dirSub.push(-1);
    geom.lineNormals.push(dirAdd, dirSub, dirAdd, dirAdd, dirSub, dirSub);
    geom.lineVertices.push(a, b, c, c, b, d);
  }
};

/**
 * Modifies all vertices to be centered within the range -100 to 100.
 * @method normalize
 * @chainable
 */
p5.Geometry.prototype.normalize = function() {
  if (this.vertices.length > 0) {
    // Find the corners of our bounding box
    var maxPosition = this.vertices[0].copy();
    var minPosition = this.vertices[0].copy();

    for (var i = 0; i < this.vertices.length; i++) {
      maxPosition.x = Math.max(maxPosition.x, this.vertices[i].x);
      minPosition.x = Math.min(minPosition.x, this.vertices[i].x);
      maxPosition.y = Math.max(maxPosition.y, this.vertices[i].y);
      minPosition.y = Math.min(minPosition.y, this.vertices[i].y);
      maxPosition.z = Math.max(maxPosition.z, this.vertices[i].z);
      minPosition.z = Math.min(minPosition.z, this.vertices[i].z);
    }

    var center = p5.Vector.lerp(maxPosition, minPosition, 0.5);
    var dist = p5.Vector.sub(maxPosition, minPosition);
    var longestDist = Math.max(Math.max(dist.x, dist.y), dist.z);
    var scale = 200 / longestDist;

    for (i = 0; i < this.vertices.length; i++) {
      this.vertices[i].sub(center);
      this.vertices[i].mult(scale);
    }
  }
  return this;
};

module.exports = p5.Geometry;
<commit_msg>Move nonfatal condition from throw Error to warn<commit_after>//some of the functions are adjusted from Three.js(http://threejs.org)

'use strict';

var p5 = require('../core/core');
/**
 * p5 Geometry class
 * @class p5.Geometry
 * @constructor
 * @param  {function | Object} vertData callback function or Object
 *                     containing routine(s) for vertex data generation
 * @param  {Integer} [detailX] number of vertices on horizontal surface
 * @param  {Integer} [detailY] number of vertices on horizontal surface
 * @param {function} [callback] function to call upon object instantiation.
 *
 */
p5.Geometry = function(detailX, detailY, callback) {
  //an array containing every vertex
  //@type [p5.Vector]
  this.vertices = [];

  //an array containing every vertex for stroke drawing
  this.lineVertices = [];

  //an array 1 normal per lineVertex with
  //final position representing which direction to
  //displace for strokeWeight
  //[[0,0,-1,1], [0,1,0,-1] ...];
  this.lineNormals = [];

  //an array containing 1 normal per vertex
  //@type [p5.Vector]
  //[p5.Vector, p5.Vector, p5.Vector,p5.Vector, p5.Vector, p5.Vector,...]
  this.vertexNormals = [];
  //an array containing each three vertex indices that form a face
  //[[0, 1, 2], [2, 1, 3], ...]
  this.faces = [];
  //a 2D array containing uvs for every vertex
  //[[0.0,0.0],[1.0,0.0], ...]
  this.uvs = [];
  // a 2D array containing edge connectivity pattern for create line vertices
  //based on faces for most objects;
  this.edges = [];
  this.detailX = detailX !== undefined ? detailX : 1;
  this.detailY = detailY !== undefined ? detailY : 1;
  if (callback instanceof Function) {
    callback.call(this);
  }
  this.name = 'p5.Geometry'; // for friendly debugger system

  return this; // TODO: is this a constructor?
};

/**
 * @method computeFaces
 * @chainable
 */
p5.Geometry.prototype.computeFaces = function() {
  var sliceCount = this.detailX + 1;
  var a, b, c, d;
  for (var i = 0; i < this.detailY; i++) {
    for (var j = 0; j < this.detailX; j++) {
      a = i * sliceCount + j; // + offset;
      b = i * sliceCount + j + 1; // + offset;
      c = (i + 1) * sliceCount + j + 1; // + offset;
      d = (i + 1) * sliceCount + j; // + offset;
      this.faces.push([a, b, d]);
      this.faces.push([d, b, c]);
    }
  }
  return this;
};

p5.Geometry.prototype._getFaceNormal = function(faceId) {
  //This assumes that vA->vB->vC is a counter-clockwise ordering
  var face = this.faces[faceId];
  var vA = this.vertices[face[0]];
  var vB = this.vertices[face[1]];
  var vC = this.vertices[face[2]];
  var ab = p5.Vector.sub(vB, vA);
  var ac = p5.Vector.sub(vC, vA);
  var n = p5.Vector.cross(ab, ac);
  var ln = p5.Vector.mag(n);
  var sinAlpha = ln / (p5.Vector.mag(ab) * p5.Vector.mag(ac));
  if (sinAlpha === 0 || isNaN(sinAlpha)) {
    console.warn(
      'p5.Geometry.prototype._getFaceNormal:',
      'face has colinear sides or a repeated vertex'
    );
    return n;
  }
  return n.mult(Math.asin(sinAlpha) / ln);
};
/**
 * computes smooth normals per vertex as an average of each
 * face.
 * @method computeNormals
 * @chainable
 */
p5.Geometry.prototype.computeNormals = function() {
  var normals = [];
  for (var v = 0; v < this.vertices.length; v++) {
    var normal = new p5.Vector();
    for (var i = 0; i < this.faces.length; i++) {
      //if our face contains a given vertex
      //calculate an average of the normals
      //of the triangles adjacent to that vertex
      if (
        this.faces[i][0] === v ||
        this.faces[i][1] === v ||
        this.faces[i][2] === v
      ) {
        var faceNormal = normals[i] || (normals[i] = this._getFaceNormal(i));
        normal = normal.add(faceNormal);
      }
    }
    normal = normal.normalize();
    this.vertexNormals.push(normal);
  }
  return this;
};

/**
 * Averages the vertex normals. Used in curved
 * surfaces
 * @method averageNormals
 * @chainable
 */
p5.Geometry.prototype.averageNormals = function() {
  for (var i = 0; i <= this.detailY; i++) {
    var offset = this.detailX + 1;
    var temp = p5.Vector.add(
      this.vertexNormals[i * offset],
      this.vertexNormals[i * offset + this.detailX]
    );

    temp = p5.Vector.div(temp, 2);
    this.vertexNormals[i * offset] = temp;
    this.vertexNormals[i * offset + this.detailX] = temp;
  }
  return this;
};

/**
 * Averages pole normals.  Used in spherical primitives
 * @method averagePoleNormals
 * @chainable
 */
p5.Geometry.prototype.averagePoleNormals = function() {
  //average the north pole
  var sum = new p5.Vector(0, 0, 0);
  for (var i = 0; i < this.detailX; i++) {
    sum.add(this.vertexNormals[i]);
  }
  sum = p5.Vector.div(sum, this.detailX);

  for (i = 0; i < this.detailX; i++) {
    this.vertexNormals[i] = sum;
  }

  //average the south pole
  sum = new p5.Vector(0, 0, 0);
  for (
    i = this.vertices.length - 1;
    i > this.vertices.length - 1 - this.detailX;
    i--
  ) {
    sum.add(this.vertexNormals[i]);
  }
  sum = p5.Vector.div(sum, this.detailX);

  for (
    i = this.vertices.length - 1;
    i > this.vertices.length - 1 - this.detailX;
    i--
  ) {
    this.vertexNormals[i] = sum;
  }
  return this;
};

/**
 * Create a 2D array for establishing stroke connections
 * @private
 * @return {p5.Geometry}
 */
p5.Geometry.prototype._makeTriangleEdges = function() {
  if (Array.isArray(this.strokeIndices)) {
    for (var i = 0, max = this.strokeIndices.length; i < max; i++) {
      this.edges.push(this.strokeIndices[i]);
    }
  } else {
    for (var j = 0; j < this.faces.length; j++) {
      this.edges.push([this.faces[j][0], this.faces[j][1]]);
      this.edges.push([this.faces[j][1], this.faces[j][2]]);
      this.edges.push([this.faces[j][2], this.faces[j][0]]);
    }
  }
  return this;
};

/**
 * Create 4 vertices for each stroke line, two at the beginning position
 * and two at the end position. These vertices are displaced relative to
 * that line's normal on the GPU
 * @private
 * @return {p5.Geometry}
 */
p5.RendererGL.prototype._edgesToVertices = function(geom) {
  geom.lineVertices = [];
  for (var i = 0; i < geom.edges.length; i++) {
    var begin = geom.vertices[geom.edges[i][0]];
    var end = geom.vertices[geom.edges[i][1]];
    var dir = end
      .copy()
      .sub(begin)
      .normalize();
    var a = begin.array();
    var b = begin.array();
    var c = end.array();
    var d = end.array();
    var dirAdd = dir.array();
    var dirSub = dir.array();
    // below is used to displace the pair of vertices at beginning and end
    // in opposite directions
    dirAdd.push(1);
    dirSub.push(-1);
    geom.lineNormals.push(dirAdd, dirSub, dirAdd, dirAdd, dirSub, dirSub);
    geom.lineVertices.push(a, b, c, c, b, d);
  }
};

/**
 * Modifies all vertices to be centered within the range -100 to 100.
 * @method normalize
 * @chainable
 */
p5.Geometry.prototype.normalize = function() {
  if (this.vertices.length > 0) {
    // Find the corners of our bounding box
    var maxPosition = this.vertices[0].copy();
    var minPosition = this.vertices[0].copy();

    for (var i = 0; i < this.vertices.length; i++) {
      maxPosition.x = Math.max(maxPosition.x, this.vertices[i].x);
      minPosition.x = Math.min(minPosition.x, this.vertices[i].x);
      maxPosition.y = Math.max(maxPosition.y, this.vertices[i].y);
      minPosition.y = Math.min(minPosition.y, this.vertices[i].y);
      maxPosition.z = Math.max(maxPosition.z, this.vertices[i].z);
      minPosition.z = Math.min(minPosition.z, this.vertices[i].z);
    }

    var center = p5.Vector.lerp(maxPosition, minPosition, 0.5);
    var dist = p5.Vector.sub(maxPosition, minPosition);
    var longestDist = Math.max(Math.max(dist.x, dist.y), dist.z);
    var scale = 200 / longestDist;

    for (i = 0; i < this.vertices.length; i++) {
      this.vertices[i].sub(center);
      this.vertices[i].mult(scale);
    }
  }
  return this;
};

module.exports = p5.Geometry;
<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

export const MODALS = {
  PATIENT_HISTORY: 'patientHistory',
  REGIMEN_DATA: 'regimenDataModal',
  STOCKTAKE_BATCH_EDIT: 'stocktakeBatchEditModal',
  STOCKTAKE_BATCH_EDIT_WITH_REASONS: 'stocktakeBatchEditModalWithReasons',
  STOCKTAKE_BATCH_EDIT_WITH_PRICES: 'stocktakeBatchEditModalWithPrices',
  STOCKTAKE_BATCH_EDIT_WITH_REASONS_AND_PRICES: 'stocktakeBatchEditModalWithReasonsAndPrices',
  SUPPLIER_CREDIT_FROM_INVOICE: 'supplierCreditFromInvoice',
  SUPPLIER_CREDIT_FROM_ITEM: 'supplierCreditFromItem',
};

export const FORMS = {
  PATIENT: 'patient',
  PRESCRIBER: 'prescriber',
};

export const TABS = {
  ITEM: 'itemSelect',
  PRESCRIBER: 'prescriberSelect',
};
<commit_msg>Add stocktakeBatch constant<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2020
 */

export const MODALS = {
  PATIENT_HISTORY: 'patientHistory',
  REGIMEN_DATA: 'regimenDataModal',
  STOCKTAKE_BATCH_EDIT: 'stocktakeBatchEditModal',
  STOCKTAKE_BATCH_EDIT_WITH_REASONS: 'stocktakeBatchEditModalWithReasons',
  STOCKTAKE_BATCH_EDIT_WITH_PRICES: 'stocktakeBatchEditModalWithPrices',
  STOCKTAKE_BATCH_EDIT_WITH_REASONS_AND_PRICES: 'stocktakeBatchEditModalWithReasonsAndPrices',
  STOCKTAKE_BATCH_EDIT_WITH_VACCINES: 'stocktakeBatchEditWithVaccines',
  SUPPLIER_CREDIT_FROM_INVOICE: 'supplierCreditFromInvoice',
  SUPPLIER_CREDIT_FROM_ITEM: 'supplierCreditFromItem',
};

export const FORMS = {
  PATIENT: 'patient',
  PRESCRIBER: 'prescriber',
};

export const TABS = {
  ITEM: 'itemSelect',
  PRESCRIBER: 'prescriberSelect',
};
<|endoftext|>


<commit_before>// 0:stopped, 1:downloading,2:extracting,3:complete

// error-state
// 1:invalid url
// 2:connection err
// 3:unzip err

var appData = Windows.Storage.ApplicationData.current;
var FileOpts = Windows.Storage.CreationCollisionOption;
var getFolderFromPathAsync = Windows.Storage.StorageFolder.getFolderFromPathAsync;
var getFileFromPathAsync = Windows.Storage.StorageFile.getFileFromPathAsync;
var replaceExisting = Windows.Storage.NameCollisionOption.replaceExisting;
var AppPath = Windows.ApplicationModel.Package.current.installedLocation.path;

function cleanPath(pathStr) {
    return pathStr.replace(/\//g, "\\");
}

function copyRootApp(destPath) {

}

function copyCordovaAssets(destPath) {

}

// this can throw exceptions, callers responsibility
function startDownload(src, storageFile) {
    var uri = Windows.Foundation.Uri(src);
    var downloader = new Windows.Networking.BackgroundTransfer.BackgroundDownloader();
    var download = downloader.createDownload(uri, storageFile);
    return download.startAsync();
}

function recursiveCopyFolderAsync(src, dst, name) {
    //console.log("recursiveCopyFolderAsync :: " + src.name + " => " + dst.name);
    name = name ? name : src.name;
    return new WinJS.Promise(function (complete, failed) {
        WinJS.Promise.join({
            destFolder: dst.createFolderAsync(name, FileOpts.openIfExists),
            files: src.getFilesAsync(),
            folders: src.getFoldersAsync()
        })
        .done(function (resultObj) {
            if (!(resultObj.files.length || resultObj.folders.length)) {
                // nothing to copy
                complete();
                return 1;
            }
            var fileCount = resultObj.files.length;
            var copyfolders = function () {
                if (!fileCount--) {
                    complete();
                    return 2;
                }
                recursiveCopyFolderAsync(resultObj.folders[fileCount], dst)
                .done(function () {
                    copyfolders();
                }, failed);
            };
            var copyfiles = function () {
                if (!fileCount--) {
                    // done with files, move on to folders
                    fileCount = resultObj.folders.length;
                    copyfolders();
                    return 3;
                }
                var file = resultObj.files[fileCount];
                //console.log("copying " + file.name + " => " + resultObj.destFolder.name);
                file.copyAsync(resultObj.destFolder, file.name, replaceExisting)
                .done(function () {
                    copyfiles();
                }, failed);
            };

            copyfiles();
        },
        failed);
    });
}

var Sync = {
    sync: function (cbSuccess, cbFail, options) {

        // Note, all defaults are set in base file www/index.js
        // so we can proceed knowing these are all defined.
        // options = [ src,id,type,headers,bCopyCordovaAssets,bCopyRootApp,timeout] ;

        var src = options[0];
        var id = cleanPath(options[1]);
        var type = options[2];
        var headers = options[3];
        var bCopyCordovaAssets = options[4];
        var bCopyRootApp = options[5];
        var timeout = options[6];
        var trustHost = options[7];
        var manifest = options[8];

        var destFolderPath = cleanPath(id);
        var rootTempFolder = null;

        var destFolder = null;
        var destZipFile = null;
        var fileName = id;

        if (id.indexOf("\\") > -1) {
            var pathParts = id.split("\\");
            fileName = pathParts.pop();
        }

        if (fileName.indexOf(".zip") < 0) { // todo, could be some.zip/file ...
            fileName += ".zip";
        }

        var folderExisted = false;
        function getOrCreateLocalFolder(folderPath) {
            console.log("folderPath = " + folderPath);
            return appData.localFolder.getFolderAsync(folderPath)
            .then(function (folder) {
                folderExisted = true;
                return folder;
            },
            function (err) {
                // folder does not exist, let's create it
                console.log("error: " + err.description);
                return appData.localFolder.createFolderAsync(folderPath)
                .then(function (folder) {
                    return folder;
                },
                function (err) {

                });
            });
        }

        WinJS.Promise.join({
            wwwFolder: getFolderFromPathAsync(AppPath + "\\www"),
            destFolder: getOrCreateLocalFolder(destFolderPath)
        }).done(function (res) {
            if (folderExisted && type == 'local') {
                // get out of the promise chain
                cbSuccess({ 'localPath': destFolderPath, 'status': 3 }, { keepCallback: false });
            }
            else {
                destFolder = res.destFolder;
                wwwFolder = res.wwwFolder;
                var job = WinJS.Promise.wrap(null);
                if (bCopyRootApp) {
                    job = recursiveCopyFolderAsync(wwwFolder, destFolder);
                }
                else {

                }
                //var dlJob = new WinJS.Promise
                job = job.then(function () {
                    return destFolder.createFileAsync(fileName, FileOpts.replaceExisting).then(function (storageFile) {
                        destZipFile = storageFile;
                    },
                    function (err) {
                        console.log(err);
                    });
                });
                job = job.then(function (res) {
                    try {
                        if (src) {
                            return startDownload(src, destZipFile);
                        }
                        else {
                            return false;
                        }
                    } catch (e) {
                        console.log(e.message);
                        cbFail(1); // INVALID_URL_ERR
                    }
                }).then(function downloadComplete(dlResult) { // download is done
                    if (dlResult) {
                        console.log("download is complete " + dlResult);
                        cbSuccess({ 'progress': 50, 'status': 2 }, { keepCallback: true }); // EXTRACTING

                        return ZipWinProj.PGZipInflate.inflateAsync(dlResult.resultFile, destFolder)
                        .then(function (obj) {
                            console.log("got a result from inflateAsync :: " + obj);
                            return true;
                        },
                        function (e) {
                            console.log("got err from inflateAsync :: " + e);
                            cbFail(3); // UNZIP_ERR
                            return false;
                        });
                    }
                    else {
                        return false;
                    }

                },
                function (err) {   // download error
                    console.log(err);
                    cbFail(1); // INVALID_URL_ERR
                    return false;
                },
                function (progressEvent) {
                    var total = progressEvent.progress.totalBytesToReceive;
                    var bytes = progressEvent.progress.bytesReceived;
                    var progPercent = total ? Math.round(bytes / total * 50) : 0;
                    console.log("progPercent =  " + progPercent);
                    cbSuccess({ 'progress': progPercent, 'status': 1 }, { keepCallback: true });    // 0:stopped, 1:downloading, 2:extracting,  3:complete
                })
                .then(function (boom) {
                    cbSuccess({ 'localPath': destFolder, 'status': 3 }, { keepCallback: false });
                })

            }
        },
        function (err) {
            cbFail(2);
        });

        return;

        //destFolder.createFileAsync(fileName, FileOpts.replaceExisting).then(function (storageFile) {
        // destZipFile = storageFile;
        //});

        // download
        //a.then(function (boo) {
        //    console.log("boo = " + boo);
        //    try {
        //        if (src) {
        //            return startDownload(src, destZipFile);
        //        }
        //        else {
        //            return false;
        //        }
        //    } catch (e) {
        //        // so we handle this and call errorCallback
        //        //errorCallback(new FTErr(FTErr.INVALID_URL_ERR));
        //        console.log(e.message);
        //        cbFail(1); // INVALID_URL_ERR
        //    }
        //},
        //function (err) {
        //    //console.log(err);
        //    cbFail(1); // INVALID_URL_ERR
        //})
        b.then(function downloadComplete(dlResult) { // download is done
            if (dlResult) {
                console.log("download is complete " + dlResult);
                cbSuccess({ 'progress': 50, 'status': 2 }, { keepCallback: true }); // EXTRACTING

                return ZipWinProj.PGZipInflate.inflateAsync(dlResult.resultFile, destFolder)
                .then(function (obj) {
                    console.log("got a result from inflateAsync :: " + obj);

                    return true;
                },
                function (e) {
                    console.log("got err from inflateAsync :: " + e);
                    cbFail(3); // UNZIP_ERR
                    return false;
                });
            }
            else {
                return false;
            }

        },
        function (err) {   // download error
            //console.log(err);
            cbFail(1); // INVALID_URL_ERR
            return false;
        },
        function (progressEvent) {
            var total = progressEvent.progress.totalBytesToReceive;
            var bytes = progressEvent.progress.bytesReceived;
            var progPercent = Math.round(bytes / total * 50);
            console.log("progPercent =  " + progPercent);
            cbSuccess({ 'progress': progPercent, 'status': 1 }, { keepCallback: true });    // 0:stopped, 1:downloading, 2:extracting,  3:complete
        })
        .then(function (boom) {
            cbSuccess({ 'localPath': destFolder, 'status': 3 }, { keepCallback: false });
        })
        //.then(function doCopyCordovaAssets(res) {
        //    //.then(function (wwwFolder) {
        //    //    console.log('wwwFolder = ' + wwwFolder);
        //    //    Windows.Storage.StorageFile.getFileFromPathAsync(path + "\\index.html")
        //    //    .then(function (file) {
        //    //        return file.copyAsync(wwwFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting);

        //    //    });
        //    //});

        //    //cbSuccess({ 'localPath': destFolder, 'status': 3 }, { keepCallback: false });
        //},
        //function (err) {
        //    console.log("got err  : " + err);
        //});

        // to pass progress events, call onSuccess with {progress:0-100,status:state}
        // to complete, call onSuccess with {localPath:"...",cached:boolean}
        // on error, call error callback with an integer:ERROR_STATE
    },
    cancel: function (cbSuccess, cbFail, options) {
        var id = options.id;
        if (DownloadQueue[id]) {

            var downloadJob = DownloadQueue[id];
            if (!downloadJob.isCancelled) { // prevent multiple callbacks for the same cancel
                downloadJob.isCancelled = true;
                if (!downloadJob.request) {
                    // todo: abort it
                }
                DownloadQueue[id] = null;
            }
            cbSuccess();
        }
        else {
            // TODO: error, id not found
            cbFail();
        }
    },
    download: function (cbSuccess, cbFail, options) {
        var url = options[0];
        var unknown = options[1];
        var headers = options[2];
    },
    unzip: function (cbSuccess, cbFail, options) {
        var srcUrl = options[0];
        var destUrl = options[1];
    }
};

require("cordova/exec/proxy").add("Sync", Sync);
require("cordova/exec/proxy").add("Zip", Sync);

<commit_msg>updated promise structure<commit_after>// progress-state
// 0:stopped, 1:downloading,2:extracting,3:complete

// error-state
// 1:invalid url
// 2:connection err
// 3:unzip err

var appData = Windows.Storage.ApplicationData.current;
var FileOpts = Windows.Storage.CreationCollisionOption;
var getFolderFromPathAsync = Windows.Storage.StorageFolder.getFolderFromPathAsync;
var getFileFromPathAsync = Windows.Storage.StorageFile.getFileFromPathAsync;
var replaceExisting = Windows.Storage.NameCollisionOption.replaceExisting;
var AppPath = Windows.ApplicationModel.Package.current.installedLocation.path;

function cleanPath(pathStr) {
    return pathStr.replace(/\//g, "\\");
}

function copyRootApp(destPath) {

}

function copyCordovaAssets(destPath) {

}

// this can throw exceptions, callers responsibility
function startDownload(src, storageFile) {
    var uri = Windows.Foundation.Uri(src);
    var downloader = new Windows.Networking.BackgroundTransfer.BackgroundDownloader();
    var download = downloader.createDownload(uri, storageFile);
    return download.startAsync();
}

function recursiveCopyFolderAsync(src, dst, name, skipRoot) {
    name = name ? name : src.name;

    var getDestFolder = function () { return WinJS.Promise.wrap(dst); };
    if (!skipRoot) {
        getDestFolder = function () {
            return dst.createFolderAsync(name, FileOpts.openIfExists)
        }
    }

    return new WinJS.Promise(function (complete, failed) {
        WinJS.Promise.join({
            destFolder: getDestFolder(),
            files: src.getFilesAsync(),
            folders: src.getFoldersAsync()
        })
        .done(function (resultObj) {
            //console.log("destFolder = " + resultObj.destFolder.path);
            if (!(resultObj.files.length || resultObj.folders.length)) {
                // nothing to copy
                complete();
                return 1;
            }
            var fileCount = resultObj.files.length;
            var copyfolders = function () {
                if (!fileCount--) {
                    complete();
                    return 2;
                }
                recursiveCopyFolderAsync(resultObj.folders[fileCount], resultObj.destFolder)
                .done(function () {
                    copyfolders();
                }, failed);
            };
            var copyfiles = function () {
                if (!fileCount--) {
                    // done with files, move on to folders
                    fileCount = resultObj.folders.length;
                    copyfolders();
                    return 3;
                }
                var file = resultObj.files[fileCount];
                //console.log("copying " + file.name + " => " + resultObj.destFolder.name);
                file.copyAsync(resultObj.destFolder || dst, file.name, replaceExisting)
                .done(function () {
                    copyfiles();
                }, failed);
            };
            copyfiles();
        },
        failed);
    });
}

var Sync = {
    sync: function (cbSuccess, cbFail, options) {

        // Note, all defaults are set in base file www/index.js
        // so we can proceed knowing these are all defined.
        // options = [ src,id,type,headers,bCopyCordovaAssets,bCopyRootApp,timeout] ;

        var src = options[0];
        var id = cleanPath(options[1]);
        var type = options[2];
        var headers = options[3];
        var bCopyCordovaAssets = options[4];
        var bCopyRootApp = options[5];
        var timeout = options[6];
        var trustHost = options[7];
        var manifest = options[8];

        var destFolderPath = cleanPath(id);
        var rootTempFolder = null;

        var destFolder = null;
        var destZipFile = null;
        var fileName = id;

        if (id.indexOf("\\") > -1) {
            var pathParts = id.split("\\");
            fileName = pathParts.pop();
        }

        if (fileName.indexOf(".zip") < 0) { // todo, could be some.zip/file ...
            fileName += ".zip";
        }

        var folderExisted = false;
        function getOrCreateLocalFolder(folderPath) {
            console.log("folderPath = " + folderPath);
            return appData.localFolder.getFolderAsync(folderPath)
            .then(function (folder) {
                folderExisted = true;
                return folder;
            },
            function (err) {
                // folder does not exist, let's create it
                console.log("error: " + err.description);
                return appData.localFolder.createFolderAsync(folderPath)
                .then(function (folder) {
                    return folder;
                },
                function (err) {

                });
            });
        }

        WinJS.Promise.join({
            wwwFolder: getFolderFromPathAsync(AppPath + "\\www"),
            destFolder: getOrCreateLocalFolder(destFolderPath)
        }).done(function (res) {
            if (folderExisted && type == 'local') {
                // get out of the promise chain
                cbSuccess({ 'localPath': destFolderPath, 'status': 3 }, { keepCallback: false });
            }
            else {
                destFolder = res.destFolder;
                wwwFolder = res.wwwFolder;
                var job = WinJS.Promise.wrap(null);
                if (bCopyRootApp) {
                    job = recursiveCopyFolderAsync(wwwFolder, destFolder, "www", true);
                }
                else {

                }

                job = job.then(function () {
                    return destFolder.createFileAsync(fileName, FileOpts.replaceExisting).then(function (storageFile) {
                        destZipFile = storageFile;
                    },
                    function (err) {
                        console.log(err);
                    });
                });
                job = job.then(function (res) {
                    try {
                        if (src) {
                            return startDownload(src, destZipFile);
                        }
                        else {
                            return false;
                        }
                    } catch (e) {
                        console.log(e.message);
                        cbFail(1); // INVALID_URL_ERR
                    }
                }).then(function downloadComplete(dlResult) { // download is done
                    if (dlResult) {
                        //console.log("download is complete " + dlResult);
                        cbSuccess({ 'progress': 50, 'status': 2 }, { keepCallback: true }); // EXTRACTING

                        return ZipWinProj.PGZipInflate.inflateAsync(dlResult.resultFile, destFolder)
                        .then(function (obj) {
                            //console.log("got a result from inflateAsync :: " + obj);
                            return true;
                        },
                        function (e) {
                            //console.log("got err from inflateAsync :: " + e);
                            cbFail(3); // UNZIP_ERR
                            return false;
                        });
                    }
                    else {
                        return false;
                    }

                },
                function (err) {   // download error
                    console.log(err);
                    cbFail(1); // INVALID_URL_ERR
                    return false;
                },
                function (progressEvent) {
                    var total = progressEvent.progress.totalBytesToReceive;
                    var bytes = progressEvent.progress.bytesReceived;
                    var progPercent = total ? Math.round(bytes / total * 50) : 0;
                    cbSuccess({ 'progress': progPercent, 'status': 1 }, { keepCallback: true });    // 0:stopped, 1:downloading, 2:extracting,  3:complete
                })
                .then(function doCopyCordovaAssets(res) {
                    return true;
                    //.then(function (wwwFolder) {
                    //    console.log('wwwFolder = ' + wwwFolder);
                    //    Windows.Storage.StorageFile.getFileFromPathAsync(path + "\\index.html")
                    //    .then(function (file) {
                    //        return file.copyAsync(wwwFolder, file.name, Windows.Storage.NameCollisionOption.replaceExisting);

                    //    });
                    //});
                },
                function (err) {
                    console.log("got err  : " + err);
                })
                .then(function (boom) {
                    cbSuccess({ 'localPath': destFolder, 'status': 3 }, { keepCallback: false });
                })

            }
        },
        function (err) {
            console.log("Error: " + err.description);
            cbFail(2);
        });
    },
    cancel: function (cbSuccess, cbFail, options) {
        var id = options.id;
        if (DownloadQueue[id]) {

            var downloadJob = DownloadQueue[id];
            if (!downloadJob.isCancelled) { // prevent multiple callbacks for the same cancel
                downloadJob.isCancelled = true;
                if (!downloadJob.request) {
                    // todo: abort it
                }
                DownloadQueue[id] = null;
            }
            cbSuccess();
        }
        else {
            // TODO: error, id not found
            cbFail();
        }
    },
    download: function (cbSuccess, cbFail, options) {
        var url = options[0];
        var unknown = options[1];
        var headers = options[2];
    },
    unzip: function (cbSuccess, cbFail, options) {
        var srcUrl = options[0];
        var destUrl = options[1];
    }
};

require("cordova/exec/proxy").add("Sync", Sync);
require("cordova/exec/proxy").add("Zip", Sync);
<|endoftext|>


<commit_before>import _ from 'intl'
import classNames from 'classnames'
import Component from 'base-component'
import Icon from 'icon'
import isEmpty from 'lodash/isEmpty'
import Link from 'link'
import map from 'lodash/map'
import React from 'react'
import Tooltip from 'tooltip'
import { UpdateTag } from '../xoa-updates'
import {
  addSubscriptions,
  connectStore,
  getXoaPlan,
  noop
} from 'utils'
import {
  connect,
  signOut,
  subscribePermissions,
  subscribeResourceSets
} from 'xo'
import {
  createFilter,
  createGetObjectsOfType,
  createSelector,
  getStatus,
  getUser,
  isAdmin
} from 'selectors'

import styles from './index.css'

const returnTrue = () => true

@connectStore(() => ({
  isAdmin,
  nTasks: createGetObjectsOfType('task').count(
    [ task => task.status === 'pending' ]
  ),
  pools: createGetObjectsOfType('pool'),
  nHosts: createGetObjectsOfType('host').count(),
  srs: createGetObjectsOfType('SR'),
  status: getStatus,
  user: getUser
}), {
  withRef: true
})
@addSubscriptions({
  permissions: subscribePermissions,
  resourceSets: subscribeResourceSets
})
export default class Menu extends Component {
  componentWillMount () {
    const updateCollapsed = () => {
      this.setState({ collapsed: window.innerWidth < 1200 })
    }
    updateCollapsed()

    window.addEventListener('resize', updateCollapsed)
    this._removeListener = () => {
      window.removeEventListener('resize', updateCollapsed)
      this._removeListener = noop
    }
  }

  componentWillUnmount () {
    this._removeListener()
  }

  _checkPermissions = createSelector(
    () => this.props.isAdmin,
    () => this.props.permissions,
    (isAdmin, permissions) => isAdmin
      ? returnTrue
      : ({ id }) => permissions && permissions[id] && permissions[id].operate
  )

  _getNoOperatablePools = createSelector(
    createFilter(
      () => this.props.pools,
      this._checkPermissions
    ),
    isEmpty
  )

  _getNoOperatableSrs = createSelector(
    createFilter(
      () => this.props.srs,
      this._checkPermissions
    ),
    isEmpty
  )

  _getNoResourceSets = createSelector(
    () => this.props.resourceSets,
    isEmpty
  )

  get height () {
    return this.refs.content.offsetHeight
  }

  _toggleCollapsed = event => {
    event.preventDefault()
    this._removeListener()
    this.setState({ collapsed: !this.state.collapsed })
  }

  _connect = event => {
    event.preventDefault()
    return connect()
  }

  _signOut = event => {
    event.preventDefault()
    return signOut()
  }

  render () {
    const { isAdmin, nTasks, status, user, pools, nHosts } = this.props
    const noOperatablePools = this._getNoOperatablePools()
    const noOperatableSrs = this._getNoOperatableSrs()
    const noResourceSets = this._getNoResourceSets()

    /* eslint-disable object-property-newline */
    const items = [
      { to: '/home', icon: 'menu-home', label: 'homePage', subMenu: [
        { to: '/home?t=VM', icon: 'vm', label: 'homeVmPage' },
        nHosts !== 0 && { to: '/home?t=host', icon: 'host', label: 'homeHostPage' },
        !isEmpty(pools) && { to: '/home?t=pool', icon: 'pool', label: 'homePoolPage' },
        isAdmin && { to: '/home?t=VM-template', icon: 'template', label: 'homeTemplatePage' },
        !noOperatableSrs && { to: '/home?t=SR', icon: 'sr', label: 'homeSrPage' }
      ]},
      { to: '/dashboard/overview', icon: 'menu-dashboard', label: 'dashboardPage', subMenu: [
        { to: '/dashboard/overview', icon: 'menu-dashboard-overview', label: 'overviewDashboardPage' },
        { to: '/dashboard/visualizations', icon: 'menu-dashboard-visualization', label: 'overviewVisualizationDashboardPage' },
        { to: '/dashboard/stats', icon: 'menu-dashboard-stats', label: 'overviewStatsDashboardPage' },
        { to: '/dashboard/health', icon: 'menu-dashboard-health', label: 'overviewHealthDashboardPage' }
      ]},
      isAdmin && { to: '/self', icon: 'menu-self-service', label: 'selfServicePage' },
      isAdmin && { to: '/backup/overview', icon: 'menu-backup', label: 'backupPage', subMenu: [
        { to: '/backup/overview', icon: 'menu-backup-overview', label: 'backupOverviewPage' },
        { to: '/backup/new', icon: 'menu-backup-new', label: 'backupNewPage' },
        { to: '/backup/restore', icon: 'menu-backup-restore', label: 'backupRestorePage' },
        { to: '/backup/file-restore', icon: 'menu-backup-file-restore', label: 'backupFileRestorePage' }
      ]},
      isAdmin && { to: '/xoa-update', icon: 'menu-update', label: 'updatePage', extra: <UpdateTag /> },
      isAdmin && { to: '/settings/servers', icon: 'menu-settings', label: 'settingsPage', subMenu: [
        { to: '/settings/servers', icon: 'menu-settings-servers', label: 'settingsServersPage' },
        { to: '/settings/users', icon: 'menu-settings-users', label: 'settingsUsersPage' },
        { to: '/settings/groups', icon: 'menu-settings-groups', label: 'settingsGroupsPage' },
        { to: '/settings/acls', icon: 'menu-settings-acls', label: 'settingsAclsPage' },
        { to: '/settings/remotes', icon: 'menu-backup-remotes', label: 'backupRemotesPage' },
        { to: '/settings/plugins', icon: 'menu-settings-plugins', label: 'settingsPluginsPage' },
        { to: '/settings/logs', icon: 'menu-settings-logs', label: 'settingsLogsPage' },
        { to: '/settings/ips', icon: 'ip', label: 'settingsIpsPage' },
        { to: '/settings/config', icon: 'menu-settings-config', label: 'settingsConfigPage' }
      ]},
      isAdmin && { to: '/jobs/overview', icon: 'menu-jobs', label: 'jobsPage', subMenu: [
        { to: '/jobs/overview', icon: 'menu-jobs-overview', label: 'jobsOverviewPage' },
        { to: '/jobs/new', icon: 'menu-jobs-new', label: 'jobsNewPage' },
        { to: '/jobs/schedules', icon: 'menu-jobs-schedule', label: 'jobsSchedulingPage' }
      ]},
      { to: '/about', icon: 'menu-about', label: 'aboutPage' },
      { to: '/tasks', icon: 'task', label: 'taskMenu', pill: nTasks },
      isAdmin && { to: '/xosan', icon: 'menu-xosan', label: 'xosan' },
      !(noOperatablePools && noResourceSets) && { to: '/vms/new', icon: 'menu-new', label: 'newMenu', subMenu: [
        { to: '/vms/new', icon: 'menu-new-vm', label: 'newVmPage' },
        isAdmin && { to: '/new/sr', icon: 'menu-new-sr', label: 'newSrPage' },
        isAdmin && { to: '/settings/servers', icon: 'menu-settings-servers', label: 'newServerPage' },
        !noOperatablePools && { to: '/vms/import', icon: 'menu-new-import', label: 'newImport' }
      ]}
    ]
    /* eslint-enable object-property-newline */

    return

              XO
              Xen Orchestra

            <Icon icon='menu-collapse' size='lg' fixedWidth />

        {map(items, (item, index) =>
          item && <MenuLinkItem key={index} item={item} />
        )}

          <Link className='nav-link' style={{display: 'flex'}} to={'/about'}>
            {+process.env.XOA_PLAN === 5
              ?

                  <Icon icon='alarm' size='lg' fixedWidth /> {_('noSupport')}

                  <Icon icon='alarm' size='lg' fixedWidth />

              : +process.env.XOA_PLAN === 1
                ?

                    <Icon icon='info' size='lg' fixedWidth /> {_('freeUpgrade')}

                    <Icon icon='info' size='lg' fixedWidth />

                :

                    <Icon icon='info' size='lg' fixedWidth /> {getXoaPlan()}

                    <Icon icon='info' size='lg' fixedWidth />

            }
          </Link>

            <Icon icon='sign-out' size='lg' fixedWidth />
            {' '}{_('signOut')}

          <Link className='nav-link text-xs-center' to={'/user'}>
            <Tooltip content={_('editUserProfile', { username: user ? user.email : '' })}>
              <Icon icon='user' size='lg' />
            </Tooltip>
          </Link>

        {status === 'connecting'
          ?
{_('statusConnecting')}

          : status === 'disconnected' &&

                <Icon icon='alarm' size='lg' fixedWidth /> {_('statusDisconnected')}

        }

  }
}

const MenuLinkItem = props => {
  const { item } = props
  const { to, icon, label, subMenu, pill, extra } = item

  return

    <Link activeClassName='active' className={classNames('nav-link', styles.centerCollapsed)} to={to}>
      <Icon className={classNames((pill || extra) && styles.hiddenCollapsed)} icon={`${icon}`} size='lg' fixedWidth />
      {' '}{_(label)}
      {pill > 0 && {pill}}
      {extra}
    </Link>
    {subMenu && <SubMenu items={subMenu} />}

}

const SubMenu = props => {
  return

    {map(props.items, (item, index) => (
      item &&

        <Link activeClassName='active' className='nav-link' to={item.to}>
          <Icon icon={`${item.icon}`} size='lg' fixedWidth />
          {' '}
          {_(item.label)}
        </Link>

    ))}

}
<commit_msg>feat(Menu): remove About if non admins (#2110)<commit_after>import _ from 'intl'
import classNames from 'classnames'
import Component from 'base-component'
import Icon from 'icon'
import isEmpty from 'lodash/isEmpty'
import Link from 'link'
import map from 'lodash/map'
import React from 'react'
import Tooltip from 'tooltip'
import { UpdateTag } from '../xoa-updates'
import {
  addSubscriptions,
  connectStore,
  getXoaPlan,
  noop
} from 'utils'
import {
  connect,
  signOut,
  subscribePermissions,
  subscribeResourceSets
} from 'xo'
import {
  createFilter,
  createGetObjectsOfType,
  createSelector,
  getStatus,
  getUser,
  isAdmin
} from 'selectors'

import styles from './index.css'

const returnTrue = () => true

@connectStore(() => ({
  isAdmin,
  nTasks: createGetObjectsOfType('task').count(
    [ task => task.status === 'pending' ]
  ),
  pools: createGetObjectsOfType('pool'),
  nHosts: createGetObjectsOfType('host').count(),
  srs: createGetObjectsOfType('SR'),
  status: getStatus,
  user: getUser
}), {
  withRef: true
})
@addSubscriptions({
  permissions: subscribePermissions,
  resourceSets: subscribeResourceSets
})
export default class Menu extends Component {
  componentWillMount () {
    const updateCollapsed = () => {
      this.setState({ collapsed: window.innerWidth < 1200 })
    }
    updateCollapsed()

    window.addEventListener('resize', updateCollapsed)
    this._removeListener = () => {
      window.removeEventListener('resize', updateCollapsed)
      this._removeListener = noop
    }
  }

  componentWillUnmount () {
    this._removeListener()
  }

  _checkPermissions = createSelector(
    () => this.props.isAdmin,
    () => this.props.permissions,
    (isAdmin, permissions) => isAdmin
      ? returnTrue
      : ({ id }) => permissions && permissions[id] && permissions[id].operate
  )

  _getNoOperatablePools = createSelector(
    createFilter(
      () => this.props.pools,
      this._checkPermissions
    ),
    isEmpty
  )

  _getNoOperatableSrs = createSelector(
    createFilter(
      () => this.props.srs,
      this._checkPermissions
    ),
    isEmpty
  )

  _getNoResourceSets = createSelector(
    () => this.props.resourceSets,
    isEmpty
  )

  get height () {
    return this.refs.content.offsetHeight
  }

  _toggleCollapsed = event => {
    event.preventDefault()
    this._removeListener()
    this.setState({ collapsed: !this.state.collapsed })
  }

  _connect = event => {
    event.preventDefault()
    return connect()
  }

  _signOut = event => {
    event.preventDefault()
    return signOut()
  }

  render () {
    const { isAdmin, nTasks, status, user, pools, nHosts } = this.props
    const noOperatablePools = this._getNoOperatablePools()
    const noOperatableSrs = this._getNoOperatableSrs()
    const noResourceSets = this._getNoResourceSets()

    /* eslint-disable object-property-newline */
    const items = [
      { to: '/home', icon: 'menu-home', label: 'homePage', subMenu: [
        { to: '/home?t=VM', icon: 'vm', label: 'homeVmPage' },
        nHosts !== 0 && { to: '/home?t=host', icon: 'host', label: 'homeHostPage' },
        !isEmpty(pools) && { to: '/home?t=pool', icon: 'pool', label: 'homePoolPage' },
        isAdmin && { to: '/home?t=VM-template', icon: 'template', label: 'homeTemplatePage' },
        !noOperatableSrs && { to: '/home?t=SR', icon: 'sr', label: 'homeSrPage' }
      ]},
      { to: '/dashboard/overview', icon: 'menu-dashboard', label: 'dashboardPage', subMenu: [
        { to: '/dashboard/overview', icon: 'menu-dashboard-overview', label: 'overviewDashboardPage' },
        { to: '/dashboard/visualizations', icon: 'menu-dashboard-visualization', label: 'overviewVisualizationDashboardPage' },
        { to: '/dashboard/stats', icon: 'menu-dashboard-stats', label: 'overviewStatsDashboardPage' },
        { to: '/dashboard/health', icon: 'menu-dashboard-health', label: 'overviewHealthDashboardPage' }
      ]},
      isAdmin && { to: '/self', icon: 'menu-self-service', label: 'selfServicePage' },
      isAdmin && { to: '/backup/overview', icon: 'menu-backup', label: 'backupPage', subMenu: [
        { to: '/backup/overview', icon: 'menu-backup-overview', label: 'backupOverviewPage' },
        { to: '/backup/new', icon: 'menu-backup-new', label: 'backupNewPage' },
        { to: '/backup/restore', icon: 'menu-backup-restore', label: 'backupRestorePage' },
        { to: '/backup/file-restore', icon: 'menu-backup-file-restore', label: 'backupFileRestorePage' }
      ]},
      isAdmin && { to: '/xoa-update', icon: 'menu-update', label: 'updatePage', extra: <UpdateTag /> },
      isAdmin && { to: '/settings/servers', icon: 'menu-settings', label: 'settingsPage', subMenu: [
        { to: '/settings/servers', icon: 'menu-settings-servers', label: 'settingsServersPage' },
        { to: '/settings/users', icon: 'menu-settings-users', label: 'settingsUsersPage' },
        { to: '/settings/groups', icon: 'menu-settings-groups', label: 'settingsGroupsPage' },
        { to: '/settings/acls', icon: 'menu-settings-acls', label: 'settingsAclsPage' },
        { to: '/settings/remotes', icon: 'menu-backup-remotes', label: 'backupRemotesPage' },
        { to: '/settings/plugins', icon: 'menu-settings-plugins', label: 'settingsPluginsPage' },
        { to: '/settings/logs', icon: 'menu-settings-logs', label: 'settingsLogsPage' },
        { to: '/settings/ips', icon: 'ip', label: 'settingsIpsPage' },
        { to: '/settings/config', icon: 'menu-settings-config', label: 'settingsConfigPage' }
      ]},
      isAdmin && { to: '/jobs/overview', icon: 'menu-jobs', label: 'jobsPage', subMenu: [
        { to: '/jobs/overview', icon: 'menu-jobs-overview', label: 'jobsOverviewPage' },
        { to: '/jobs/new', icon: 'menu-jobs-new', label: 'jobsNewPage' },
        { to: '/jobs/schedules', icon: 'menu-jobs-schedule', label: 'jobsSchedulingPage' }
      ]},
      isAdmin && { to: '/about', icon: 'menu-about', label: 'aboutPage' },
      { to: '/tasks', icon: 'task', label: 'taskMenu', pill: nTasks },
      isAdmin && { to: '/xosan', icon: 'menu-xosan', label: 'xosan' },
      !(noOperatablePools && noResourceSets) && { to: '/vms/new', icon: 'menu-new', label: 'newMenu', subMenu: [
        { to: '/vms/new', icon: 'menu-new-vm', label: 'newVmPage' },
        isAdmin && { to: '/new/sr', icon: 'menu-new-sr', label: 'newSrPage' },
        isAdmin && { to: '/settings/servers', icon: 'menu-settings-servers', label: 'newServerPage' },
        !noOperatablePools && { to: '/vms/import', icon: 'menu-new-import', label: 'newImport' }
      ]}
    ]
    /* eslint-enable object-property-newline */

    return

              XO
              Xen Orchestra

            <Icon icon='menu-collapse' size='lg' fixedWidth />

        {map(items, (item, index) =>
          item && <MenuLinkItem key={index} item={item} />
        )}

          <Link className='nav-link' style={{display: 'flex'}} to={'/about'}>
            {+process.env.XOA_PLAN === 5
              ?

                  <Icon icon='alarm' size='lg' fixedWidth /> {_('noSupport')}

                  <Icon icon='alarm' size='lg' fixedWidth />

              : +process.env.XOA_PLAN === 1
                ?

                    <Icon icon='info' size='lg' fixedWidth /> {_('freeUpgrade')}

                    <Icon icon='info' size='lg' fixedWidth />

                :

                    <Icon icon='info' size='lg' fixedWidth /> {getXoaPlan()}

                    <Icon icon='info' size='lg' fixedWidth />

            }
          </Link>

            <Icon icon='sign-out' size='lg' fixedWidth />
            {' '}{_('signOut')}

          <Link className='nav-link text-xs-center' to={'/user'}>
            <Tooltip content={_('editUserProfile', { username: user ? user.email : '' })}>
              <Icon icon='user' size='lg' />
            </Tooltip>
          </Link>

        {status === 'connecting'
          ?
{_('statusConnecting')}

          : status === 'disconnected' &&

                <Icon icon='alarm' size='lg' fixedWidth /> {_('statusDisconnected')}

        }

  }
}

const MenuLinkItem = props => {
  const { item } = props
  const { to, icon, label, subMenu, pill, extra } = item

  return

    <Link activeClassName='active' className={classNames('nav-link', styles.centerCollapsed)} to={to}>
      <Icon className={classNames((pill || extra) && styles.hiddenCollapsed)} icon={`${icon}`} size='lg' fixedWidth />
      {' '}{_(label)}
      {pill > 0 && {pill}}
      {extra}
    </Link>
    {subMenu && <SubMenu items={subMenu} />}

}

const SubMenu = props => {
  return

    {map(props.items, (item, index) => (
      item &&

        <Link activeClassName='active' className='nav-link' to={item.to}>
          <Icon icon={`${item.icon}`} size='lg' fixedWidth />
          {' '}
          {_(item.label)}
        </Link>

    ))}

}
<|endoftext|>


<commit_before>var vow = require('vow'),
    webpack = require('webpack');

modules.exports = require('enb/lib/build-flow').create()
    .name('bem-babel')
    .target('target', '?.browser.js')
    .useFileList(['vanilla.js', 'js', 'browser.js'])
    .builder(function (files) {
        var res = vow.defer(),
            node = this.node,
            target = this._target,
            targetDir = node.getDir(),
            preWebpackPath = node.resolvePath('.pre-webpack.' + target),
            resultPath = node.resolvePath(target);

        fs.writeFile(
            preWebpackPath,
            files.map(function(file) {
                return 'import \'bem-source:' + file.fullname + '\';';
            }).join('\n'),
            function(err) {
                webpack(
                    {
                        entry : preWebpackPath,
                        output : {
                            path : targetDir,
                            filename : target
                        },
                        module : {
                            loaders : [
                                {
                                    test : /\.js$/,
                                    loader : 'babel-loader',
                                    query : {
                                        optional : ['runtime'],
                                        plugins : 'bem',
                                        loose : ['es6.modules']
                                    }
                                }
                            ]
                        }
                    },
                    function(err, stats) {
                        // TODO: remove preWebpackPath
                        if(err) {
                            res.reject('Fatal error in webpack');
                        } else {
                            stats = stats.toJson();
                            if(stats.errors.length) {
                                return res.reject(stats.errors.join('\n'));
                            }

                            if(stats.warnings.length) {
                                node.getLogger().logWarningAction(
                                    'bem-babel',
                                    path.join(node.getPath(), target),
                                    stats.warnings);
                            }
                            res.resolve(resultPath);
                        }
                    });
            });
        return res.promise();
    })
    .saver(function() {})
    .createTech();
<commit_msg>fix a typo<commit_after>var vow = require('vow'),
    webpack = require('webpack');

module.exports = require('enb/lib/build-flow').create()
    .name('bem-babel')
    .target('target', '?.browser.js')
    .useFileList(['vanilla.js', 'js', 'browser.js'])
    .builder(function (files) {
        var res = vow.defer(),
            node = this.node,
            target = this._target,
            targetDir = node.getDir(),
            preWebpackPath = node.resolvePath('.pre-webpack.' + target),
            resultPath = node.resolvePath(target);

        fs.writeFile(
            preWebpackPath,
            files.map(function(file) {
                return 'import \'bem-source:' + file.fullname + '\';';
            }).join('\n'),
            function(err) {
                webpack(
                    {
                        entry : preWebpackPath,
                        output : {
                            path : targetDir,
                            filename : target
                        },
                        module : {
                            loaders : [
                                {
                                    test : /\.js$/,
                                    loader : 'babel-loader',
                                    query : {
                                        optional : ['runtime'],
                                        plugins : 'bem',
                                        loose : ['es6.modules']
                                    }
                                }
                            ]
                        }
                    },
                    function(err, stats) {
                        // TODO: remove preWebpackPath
                        if(err) {
                            res.reject('Fatal error in webpack');
                        } else {
                            stats = stats.toJson();
                            if(stats.errors.length) {
                                return res.reject(stats.errors.join('\n'));
                            }

                            if(stats.warnings.length) {
                                node.getLogger().logWarningAction(
                                    'bem-babel',
                                    path.join(node.getPath(), target),
                                    stats.warnings);
                            }
                            res.resolve(resultPath);
                        }
                    });
            });
        return res.promise();
    })
    .saver(function() {})
    .createTech();
<|endoftext|>


<commit_before>// @flow

import * as React from 'react'
import {NoticeView} from '../components/notice'
import {BuildingHoursList} from './list'
import {type ReduxState} from '../../flux'
import {connect} from 'react-redux'
import moment from 'moment-timezone'
import type {TopLevelViewPropsType} from '../types'
import type {BuildingType} from './types'
import * as defaultData from '../../../docs/building-hours.json'
import {reportNetworkProblem} from '../../lib/report-network-problem'
import toPairs from 'lodash/toPairs'
import groupBy from 'lodash/groupBy'
import delay from 'delay'

import {CENTRAL_TZ} from './lib'
const githubBaseUrl =
  'https://stodevx.github.io/AAO-React-Native/building-hours.json'

const groupBuildings = (buildings: BuildingType[], favorites: string[]) => {
  const favoritesGroup = {
    title: 'Favorites',
    data: buildings.filter(b => favorites.includes(b.name)),
  }

  const grouped = groupBy(buildings, b => b.category || 'Other')
  let groupedBuildings = toPairs(grouped).map(([key, value]) => ({
    title: key,
    data: value,
  }))

  if (favoritesGroup.data.length > 0) {
    groupedBuildings = [favoritesGroup, ...groupedBuildings]
  }

  return groupedBuildings
}

type ReduxStateProps = {
  favoriteBuildings: Array<string>,
}

type Props = TopLevelViewPropsType & ReduxStateProps

type State = {
  error: ?Error,
  loading: boolean,
  now: moment,
  buildings: Array<{title: string, data: Array<BuildingType>}>,
  allBuildings: Array<BuildingType>,
  intervalId: number,
}

export class BuildingHoursView extends React.PureComponent<Props, State> {
  static navigationOptions = {
    title: 'Building Hours',
    headerBackTitle: 'Hours',
  }

  state = {
    error: null,
    loading: false,
    // now: moment.tz('Wed 7:25pm', 'ddd h:mma', null, CENTRAL_TZ),
    now: moment.tz(CENTRAL_TZ),
    buildings: groupBuildings(defaultData.data, this.props.favoriteBuildings),
    allBuildings: defaultData.data,
    intervalId: 0,
  }

  componentWillMount() {
    this.fetchData()

    // This updates the screen every second, so that the building
    // info statuses are updated without needing to leave and come back.
    this.setState({intervalId: setInterval(this.updateTime, 1000)})
  }

  componentWillUnmount() {
    clearTimeout(this.state.intervalId)
  }

  componentWillReceiveProps(nextProps) {
    this.setState(state => ({
      buildings: groupBuildings(state.allBuildings, nextProps.favoriteBuildings),
    }))
  }

  updateTime = () => {
    this.setState(() => ({now: moment.tz(CENTRAL_TZ)}))
  }

  refresh = async (): any => {
    let start = Date.now()
    this.setState(() => ({loading: true}))

    await this.fetchData()

    // wait 0.5 seconds – if we let it go at normal speed, it feels broken.
    let elapsed = Date.now() - start
    if (elapsed < 500) {
      await delay(500 - elapsed)
    }

    this.setState(() => ({loading: false}))
  }

  fetchData = async () => {
    let {data: buildings} = await fetchJson(githubBaseUrl).catch(err => {
      reportNetworkProblem(err)
      return defaultData
    })
    if (process.env.NODE_ENV === 'development') {
      buildings = defaultData.data
    }
    this.setState(() => ({
      buildings: groupBuildings(buildings, this.props.favoriteBuildings),
      allBuildings: buildings,
      now: moment.tz(CENTRAL_TZ),
    }))
  }

  render() {
    if (this.state.error) {
      return <NoticeView text={`Error: ${this.state.error.message}`} />
    }

    return (
      <BuildingHoursList
        buildings={this.state.buildings}
        loading={this.state.loading}
        navigation={this.props.navigation}
        now={this.state.now}
        onRefresh={this.refresh}
      />
    )
  }
}

function mapStateToProps(state: ReduxState): ReduxStateProps {
  return {
    favoriteBuildings: state.buildings ? state.buildings.favorites : [],
  }
}

export const ConnectedBuildingHoursView = connect(mapStateToProps)(
  BuildingHoursView,
)
<commit_msg>run prettier<commit_after>// @flow

import * as React from 'react'
import {NoticeView} from '../components/notice'
import {BuildingHoursList} from './list'
import {type ReduxState} from '../../flux'
import {connect} from 'react-redux'
import moment from 'moment-timezone'
import type {TopLevelViewPropsType} from '../types'
import type {BuildingType} from './types'
import * as defaultData from '../../../docs/building-hours.json'
import {reportNetworkProblem} from '../../lib/report-network-problem'
import toPairs from 'lodash/toPairs'
import groupBy from 'lodash/groupBy'
import delay from 'delay'

import {CENTRAL_TZ} from './lib'
const githubBaseUrl =
  'https://stodevx.github.io/AAO-React-Native/building-hours.json'

const groupBuildings = (buildings: BuildingType[], favorites: string[]) => {
  const favoritesGroup = {
    title: 'Favorites',
    data: buildings.filter(b => favorites.includes(b.name)),
  }

  const grouped = groupBy(buildings, b => b.category || 'Other')
  let groupedBuildings = toPairs(grouped).map(([key, value]) => ({
    title: key,
    data: value,
  }))

  if (favoritesGroup.data.length > 0) {
    groupedBuildings = [favoritesGroup, ...groupedBuildings]
  }

  return groupedBuildings
}

type ReduxStateProps = {
  favoriteBuildings: Array<string>,
}

type Props = TopLevelViewPropsType & ReduxStateProps

type State = {
  error: ?Error,
  loading: boolean,
  now: moment,
  buildings: Array<{title: string, data: Array<BuildingType>}>,
  allBuildings: Array<BuildingType>,
  intervalId: number,
}

export class BuildingHoursView extends React.PureComponent<Props, State> {
  static navigationOptions = {
    title: 'Building Hours',
    headerBackTitle: 'Hours',
  }

  state = {
    error: null,
    loading: false,
    // now: moment.tz('Wed 7:25pm', 'ddd h:mma', null, CENTRAL_TZ),
    now: moment.tz(CENTRAL_TZ),
    buildings: groupBuildings(defaultData.data, this.props.favoriteBuildings),
    allBuildings: defaultData.data,
    intervalId: 0,
  }

  componentWillMount() {
    this.fetchData()

    // This updates the screen every second, so that the building
    // info statuses are updated without needing to leave and come back.
    this.setState({intervalId: setInterval(this.updateTime, 1000)})
  }

  componentWillUnmount() {
    clearTimeout(this.state.intervalId)
  }

  componentWillReceiveProps(nextProps) {
    this.setState(state => ({
      buildings: groupBuildings(
        state.allBuildings,
        nextProps.favoriteBuildings,
      ),
    }))
  }

  updateTime = () => {
    this.setState(() => ({now: moment.tz(CENTRAL_TZ)}))
  }

  refresh = async (): any => {
    let start = Date.now()
    this.setState(() => ({loading: true}))

    await this.fetchData()

    // wait 0.5 seconds – if we let it go at normal speed, it feels broken.
    let elapsed = Date.now() - start
    if (elapsed < 500) {
      await delay(500 - elapsed)
    }

    this.setState(() => ({loading: false}))
  }

  fetchData = async () => {
    let {data: buildings} = await fetchJson(githubBaseUrl).catch(err => {
      reportNetworkProblem(err)
      return defaultData
    })
    if (process.env.NODE_ENV === 'development') {
      buildings = defaultData.data
    }
    this.setState(() => ({
      buildings: groupBuildings(buildings, this.props.favoriteBuildings),
      allBuildings: buildings,
      now: moment.tz(CENTRAL_TZ),
    }))
  }

  render() {
    if (this.state.error) {
      return <NoticeView text={`Error: ${this.state.error.message}`} />
    }

    return (
      <BuildingHoursList
        buildings={this.state.buildings}
        loading={this.state.loading}
        navigation={this.props.navigation}
        now={this.state.now}
        onRefresh={this.refresh}
      />
    )
  }
}

function mapStateToProps(state: ReduxState): ReduxStateProps {
  return {
    favoriteBuildings: state.buildings ? state.buildings.favorites : [],
  }
}

export const ConnectedBuildingHoursView = connect(mapStateToProps)(
  BuildingHoursView,
)
<|endoftext|>


<commit_before>// @flow

import * as React from 'react'
import {NoticeView} from '@frogpond/notice'
import {BuildingHoursList} from './list'
import {type ReduxState} from '../../redux'
import {connect} from 'react-redux'
import type {TopLevelViewPropsType} from '../types'
import type {BuildingType} from './types'
import * as defaultData from '../../../docs/building-hours.json'
import {reportNetworkProblem} from '@frogpond/analytics'
import toPairs from 'lodash/toPairs'
import groupBy from 'lodash/groupBy'
import delay from 'delay'
import {CENTRAL_TZ} from './lib'
import {API} from '@frogpond/api'
import {Timer} from '@frogpond/timer'

const buildingHoursUrl = API('/spaces/hours')

const groupBuildings = (buildings: BuildingType[], favorites: string[]) => {
	const favoritesGroup = {
		title: 'Favorites',
		data: buildings.filter(b => favorites.includes(b.name)),
	}

	const grouped = groupBy(buildings, b => b.category || 'Other')
	let groupedBuildings = toPairs(grouped).map(([key, value]) => ({
		title: key,
		data: value,
	}))

	if (favoritesGroup.data.length > 0) {
		groupedBuildings = [favoritesGroup, ...groupedBuildings]
	}

	return groupedBuildings
}

type ReduxStateProps = {
	favoriteBuildings: Array<string>,
}

type Props = TopLevelViewPropsType & ReduxStateProps

type State = {
	error: ?Error,
	loading: boolean,
	buildings: Array<{title: string, data: Array<BuildingType>}>,
	allBuildings: Array<BuildingType>,
}

export class BuildingHoursView extends React.PureComponent<Props, State> {
	static navigationOptions = {
		title: 'Building Hours',
		headerBackTitle: 'Hours',
	}

	state = {
		error: null,
		loading: false,
		buildings: groupBuildings(defaultData.data, this.props.favoriteBuildings),
		allBuildings: defaultData.data,
		intervalId: null,
	}

	static getDerivedStateFromProps(nextProps: Props, prevState: State) {
		return {
			buildings: groupBuildings(
				prevState.allBuildings,
				nextProps.favoriteBuildings,
			),
		}
	}

	componentDidMount() {
		this.fetchData()
	}

	refresh = async (): any => {
		let start = Date.now()
		this.setState(() => ({loading: true}))

		await this.fetchData()

		// wait 0.5 seconds – if we let it go at normal speed, it feels broken.
		let elapsed = Date.now() - start
		if (elapsed < 500) {
			await delay(500 - elapsed)
		}

		this.setState(() => ({loading: false}))
	}

	fetchData = async () => {
		let {data: buildings} = await fetchJson(buildingHoursUrl).catch(err => {
			reportNetworkProblem(err)
			return defaultData
		})
		if (process.env.NODE_ENV === 'development') {
			buildings = defaultData.data
		}
		this.setState(() => ({
			buildings: groupBuildings(buildings, this.props.favoriteBuildings),
			allBuildings: buildings,
		}))
	}

	render() {
		if (this.state.error) {
			return <NoticeView text={`Error: ${this.state.error.message}`} />
		}

		return (
			<Timer
				interval={60000}
				moment={true}
				render={({now}) => (
					<BuildingHoursList
						buildings={this.state.buildings}
						loading={this.state.loading}
						navigation={this.props.navigation}
						now={now}
						onRefresh={this.refresh}
					/>
				)}
				timezone={CENTRAL_TZ}
			/>
		)
	}
}

function mapStateToProps(state: ReduxState): ReduxStateProps {
	return {
		favoriteBuildings: state.buildings ? state.buildings.favorites : [],
	}
}

export const ConnectedBuildingHoursView = connect(mapStateToProps)(
	BuildingHoursView,
)
<commit_msg>clean up BuildingHours/List a bit more<commit_after>// @flow

import * as React from 'react'
import {NoticeView} from '@frogpond/notice'
import {BuildingHoursList} from './list'
import {type ReduxState} from '../../redux'
import {connect} from 'react-redux'
import type {TopLevelViewPropsType} from '../types'
import type {BuildingType} from './types'
import * as defaultData from '../../../docs/building-hours.json'
import {reportNetworkProblem} from '@frogpond/analytics'
import toPairs from 'lodash/toPairs'
import groupBy from 'lodash/groupBy'
import delay from 'delay'
import {CENTRAL_TZ} from './lib'
import {API} from '@frogpond/api'
import {Timer} from '@frogpond/timer'

const buildingHoursUrl = API('/spaces/hours')

const groupBuildings = (buildings: BuildingType[], favorites: string[]) => {
	const favoritesGroup = {
		title: 'Favorites',
		data: buildings.filter(b => favorites.includes(b.name)),
	}

	const grouped = groupBy(buildings, b => b.category || 'Other')
	let groupedBuildings = toPairs(grouped).map(([key, value]) => ({
		title: key,
		data: value,
	}))

	if (favoritesGroup.data.length > 0) {
		groupedBuildings = [favoritesGroup, ...groupedBuildings]
	}

	return groupedBuildings
}

type ReduxStateProps = {
	favoriteBuildings: Array<string>,
}

type Props = TopLevelViewPropsType & ReduxStateProps

type State = {|
	error: ?Error,
	loading: boolean,
	buildings: Array<{title: string, data: Array<BuildingType>}>,
	allBuildings: Array<BuildingType>,
|}

export class BuildingHoursView extends React.PureComponent<Props, State> {
	static navigationOptions = {
		title: 'Building Hours',
		headerBackTitle: 'Hours',
	}

	state = {
		error: null,
		loading: false,
		buildings: groupBuildings(defaultData.data, this.props.favoriteBuildings),
		allBuildings: defaultData.data,
	}

	static getDerivedStateFromProps(nextProps: Props, prevState: State) {
		return {
			buildings: groupBuildings(
				prevState.allBuildings,
				nextProps.favoriteBuildings,
			),
		}
	}

	componentDidMount() {
		this.fetchData()
	}

	refresh = async (): any => {
		let start = Date.now()
		this.setState(() => ({loading: true}))

		await this.fetchData()

		// wait 0.5 seconds – if we let it go at normal speed, it feels broken.
		let elapsed = Date.now() - start
		if (elapsed < 500) {
			await delay(500 - elapsed)
		}

		this.setState(() => ({loading: false}))
	}

	fetchData = async () => {
		let {data: buildings} = await fetchJson(buildingHoursUrl).catch(err => {
			reportNetworkProblem(err)
			return defaultData
		})
		if (process.env.NODE_ENV === 'development') {
			buildings = defaultData.data
		}
		this.setState(() => ({
			buildings: groupBuildings(buildings, this.props.favoriteBuildings),
			allBuildings: buildings,
		}))
	}

	render() {
		if (this.state.error) {
			return <NoticeView text={`Error: ${this.state.error.message}`} />
		}

		return (
			<Timer
				interval={60000}
				moment={true}
				render={({now}) => (
					<BuildingHoursList
						buildings={this.state.buildings}
						loading={this.state.loading}
						navigation={this.props.navigation}
						now={now}
						onRefresh={this.refresh}
					/>
				)}
				timezone={CENTRAL_TZ}
			/>
		)
	}
}

function mapStateToProps(state: ReduxState): ReduxStateProps {
	return {
		favoriteBuildings: state.buildings ? state.buildings.favorites : [],
	}
}

export const ConnectedBuildingHoursView = connect(mapStateToProps)(
	BuildingHoursView,
)
<|endoftext|>


<commit_before>import { _ }         from 'meteor/underscore';
import { Meteor }    from 'meteor/meteor';
import { Template }  from 'meteor/templating';
import { Spacebars } from 'meteor/spacebars';
// import { Session }   from 'meteor/session';

let Session = false;
try {
  Session = require('meteor/session').Session;
  // import { Session } from 'meteor/session';
} catch (e) {
  // session package is not installed
}

class TemplateHelpers {
  constructor() {}
  session(key, adds) {
    let set;
    let action;
    if (!Session) {
      throw new Meteor.Error(404, '"session" package is missing, install it first: "meteor add session"');
    }

    if(_.isUndefined(adds)){
      action = 'get';
    }

    if(_.isString(adds)){
      action = 'get';
    }

    if(_.isObject(adds)){
      action = 'get';
      if(adds.hash && _.has(adds.hash, 'set')){
        action = (_.has(adds.hash, 'action')) ? adds.hash.action : 'set';
        set = adds.hash.set || undefined;
      }
    }

    switch(action){
    case 'setDefault':
      Session.setDefault(key, set);
      return void 0;

    case 'set':
      Session.set(key, set);
      return void 0;

    default:
      return Session.get(key);
    }
  }

  log(...args) {
    console.log('arguments: ', args, 'this: ', this);
    const key = args.shift();
    try {
      return `${JSON.stringify(key, null, 2)} | ${JSON.stringify(args, null, 2)}`;
    } catch (e) {
      return `${key} | ${args}`;
    }
  }

  compare(...args) {
    if (args[args.length - 1] instanceof Spacebars.kw) {
      args.pop();
    }

    const res = [];
    if(args.length > 3){
      res.push(args[0]);
      for (let i = 0; i < args.length - 1; ) {
        res.push(templatehelpers.compare(res[res.length - 1], args[++i], args[++i]));
      }
      return res[res.length - 1];
    }

    let first = args[0];
    let second = args[2];
    const operator = args[1];
    if(_.isObject(first) && _.isObject(second)){
      first = JSON.stringify(first);
      second = JSON.stringify(second);
    }

    if(_.isString(second) && !!~second.indexOf('|')){
      const inclusive = second.split('|');
      for (let j = 0; j < inclusive.length; j++) {
        res.push(templatehelpers.compare(first, operator, inclusive[j]));
        if (res[res.length - 1] === true) {
          return true;
        }
      }

      return res[res.length - 1];
    }

    switch (operator){
    case '>':
    case 'gt':
    case 'greaterThan':
      return (first > second);

    case '>=':
    case 'gte':
    case 'greaterThanEqual':
      return (first >= second);

    case '<':
    case 'lt':
    case 'lessThan':
      return (first < second);

    case '<=':
    case 'lte':
    case 'lessThanEqual':
      return (first <= second);

    case '===':
    case 'is':
      return (first === second);

    case '!==':
    case 'isnt':
      return (first !== second);

    case 'isEqual':
    case '==':
      return (first == second);

    case 'isNotEqual':
    case '!=':
      return (first != second);

    case '&&':
    case 'and':
      return (first && second);

    case '&!':
      return (first && !second);

    case '!&':
      return (!first && second);

    case '!&!':
      return (!first && !second);

    case '||':
    case 'or':
      return (first || second);

    case '!|':
      return (!first || second);

    case '|!':
      return (first || !second);

    case '!|!':
      return (!first || !second);

    case '!||':
    case 'nor':
      return !(first || second);

    case '!&&':
    case 'nand':
      return !(first && second);

    case 'xor':
      return ((first && !second) || (!first && second));

    case 'nxor':
      return !((first && !second) || (!first && second));

    default:
      console.error(`[ostrio:templatehelpers] [comparison operator: "${operator}" is not supported!]`);
      return false;
    }
  }

  underscore(...args) {
    if (!_) {
      throw new Meteor.Error(404, '"underscore" package is missing, install it first: "meteor add underscore"');
    }

    if(args.length){
      if (args[args.length - 1] instanceof Spacebars.kw) {
        args.pop();
      }
      const fn = args[0];
      args.shift();
      return _[fn].apply(_, args);
    }
    return void 0;
  }
}

const templatehelpers = new TemplateHelpers();

/*
 * @description Get or set session value from views via Session helper
 * @example
 * GET: {{Session 'key'}}
 * SET: {{Session 'key' set="new value"}}
 * SET Default: {{Session 'key' set="new value" action="setDefault"}}
 */
Template.registerHelper('Session', templatehelpers.session);

/*
 * @description Debug helper console log
 * and return passed objects as a string
 */
Template.registerHelper('log', templatehelpers.log);

/*
 * @description Compare two or more arguments in template
 */
Template.registerHelper('compare', templatehelpers.compare);

/*
 * @description Use underscore as a helper
 */
Template.registerHelper('_', templatehelpers.underscore);

export { templatehelpers };
<commit_msg>Fix #4<commit_after>import { _ }         from 'meteor/underscore';
import { Meteor }    from 'meteor/meteor';
import { Template }  from 'meteor/templating';
import { Spacebars } from 'meteor/spacebars';

let Session = false;
try {
  Session = require('meteor/session').Session;
} catch (e) {
  // session package is not installed
}

class TemplateHelpers {
  constructor() {}
  session(key, adds) {
    let set;
    let action;
    if (!Session) {
      throw new Meteor.Error(404, '"session" package is missing, install it first: "meteor add session"');
    }

    if(_.isUndefined(adds)){
      action = 'get';
    }

    if(_.isString(adds)){
      action = 'get';
    }

    if(_.isObject(adds)){
      action = 'get';
      if(adds.hash && _.has(adds.hash, 'set')){
        action = (_.has(adds.hash, 'action')) ? adds.hash.action : 'set';
        set = adds.hash.set || undefined;
      }
    }

    switch(action){
    case 'setDefault':
      Session.setDefault(key, set);
      return void 0;

    case 'set':
      Session.set(key, set);
      return void 0;

    default:
      return Session.get(key);
    }
  }

  log(...args) {
    console.log('arguments: ', args, 'this: ', this);
    const key = args.shift();
    try {
      return `${JSON.stringify(key, null, 2)} | ${JSON.stringify(args, null, 2)}`;
    } catch (e) {
      return `${key} | ${args}`;
    }
  }

  compare(...args) {
    if (args[args.length - 1] instanceof Spacebars.kw) {
      args.pop();
    }

    const res = [];
    if(args.length > 3){
      res.push(args[0]);
      for (let i = 0; i < args.length - 1; ) {
        res.push(templatehelpers.compare(res[res.length - 1], args[++i], args[++i]));
      }
      return res[res.length - 1];
    }

    let first = args[0];
    let second = args[2];
    const operator = args[1];
    if(_.isObject(first) && _.isObject(second)){
      first = JSON.stringify(first);
      second = JSON.stringify(second);
    }

    if(_.isString(second) && !!~second.indexOf('|')){
      const inclusive = second.split('|');
      for (let j = 0; j < inclusive.length; j++) {
        res.push(templatehelpers.compare(first, operator, inclusive[j]));
        if (res[res.length - 1] === true) {
          return true;
        }
      }

      return res[res.length - 1];
    }

    switch (operator){
    case '>':
    case 'gt':
    case 'greaterThan':
      return (first > second);

    case '>=':
    case 'gte':
    case 'greaterThanEqual':
      return (first >= second);

    case '<':
    case 'lt':
    case 'lessThan':
      return (first < second);

    case '<=':
    case 'lte':
    case 'lessThanEqual':
      return (first <= second);

    case '===':
    case 'is':
      return (first === second);

    case '!==':
    case 'isnt':
      return (first !== second);

    case 'isEqual':
    case '==':
      return (first == second);

    case 'isNotEqual':
    case '!=':
      return (first != second);

    case '&&':
    case 'and':
      return (first && second);

    case '&!':
      return (first && !second);

    case '!&':
      return (!first && second);

    case '!&!':
      return (!first && !second);

    case '||':
    case 'or':
      return (first || second);

    case '!|':
      return (!first || second);

    case '|!':
      return (first || !second);

    case '!|!':
      return (!first || !second);

    case '!||':
    case 'nor':
      return !(first || second);

    case '!&&':
    case 'nand':
      return !(first && second);

    case 'xor':
      return ((first && !second) || (!first && second));

    case 'nxor':
      return !((first && !second) || (!first && second));

    default:
      console.error(`[ostrio:templatehelpers] [comparison operator: "${operator}" is not supported!]`);
      return false;
    }
  }

  underscore(...args) {
    if (!_) {
      throw new Meteor.Error(404, '"underscore" package is missing, install it first: "meteor add underscore"');
    }

    if(args.length){
      if (args[args.length - 1] instanceof Spacebars.kw) {
        args.pop();
      }
      const fn = args[0];
      args.shift();
      return _[fn].apply(_, args);
    }
    return void 0;
  }
}

const templatehelpers = new TemplateHelpers();

/*
 * @description Get or set session value from views via Session helper
 * @example
 * GET: {{Session 'key'}}
 * SET: {{Session 'key' set="new value"}}
 * SET Default: {{Session 'key' set="new value" action="setDefault"}}
 */
Template.registerHelper('Session', templatehelpers.session);

/*
 * @description Debug helper console log
 * and return passed objects as a string
 */
Template.registerHelper('log', templatehelpers.log);

/*
 * @description Compare two or more arguments in template
 */
Template.registerHelper('compare', templatehelpers.compare);

/*
 * @description Use underscore as a helper
 */
Template.registerHelper('_', templatehelpers.underscore);

export { templatehelpers };
<|endoftext|>


<commit_before>import tape from 'tape';
import { using_default_seed, using_custom_seed, custom_seed } from './data';
import * as a from '../lib/arrispwgen';

// DRY function for testing single password
function test_single_passwords(assert, data, seed) {
    for (let p in data) {
        let date = (new Date(parseInt(p)));
        let potd = a.generate(date, seed);
        assert.equal(potd, data[p], 'Generated password should be correct.');
    }

    assert.end();
}

tape('Should generate the correct password for the given days with the default seed', function(assert) {
    test_single_passwords(assert, using_default_seed);
});

tape('Should generate the correct password for the given days with a custom seed', function (assert) {
    test_single_passwords(assert, using_custom_seed, custom_seed);
});

tape('Should throw an exception if start date is after end date', function (assert) {
    assert.plan(1);
    let fn = function () {
        let d1 = new Date(2016, 1, 10);
        let d2 = new Date(2016, 1, 5);
        a.generate_multi(d1, d2);
    };

    assert.throws(fn);
});

tape('Should generate a single password if the date interval is just one day', function (assert) {
    assert.plan(1);
    let d1 = new Date(2016, 1, 5);
    let d2 = new Date(2016, 1, 5);
    let potd = a.generate_multi(d1, d2);

    assert.equal(Object.keys(potd).length, 1, 'Should generate 1 password');
});

function test_multiple_passwords(assert, data, start_index, end_index, seed) {
    let keys = Object.keys(data);

    // First set of passwords
    let start = new Date(parseInt(keys[start_index]));
    let end = new Date(parseInt(keys[end_index]));
    let potd = a.generate_multi(start, end, seed);
    let count = end_index - start_index + 1;
    assert.equal(Object.keys(potd).length, count, 'Should generate ' + count + ' passwords');
    for (let p in potd) {
        assert.equal(potd[p], data[p], 'Generated password should be correct.');
    }
}

tape('Should generate the correct passwords for the given date interval, with the default seed', function (assert) {
    test_multiple_passwords(assert, using_default_seed, 0, 3);
    test_multiple_passwords(assert, using_default_seed, 4, 6);
    assert.end();
});

tape('Should generate the correct passwords for the given date interval, with a custom seed', function (assert) {
    test_multiple_passwords(assert, using_custom_seed, 0, 3, custom_seed);
    test_multiple_passwords(assert, using_custom_seed, 4, 6, custom_seed);
    assert.end();
});
<commit_msg>Use sub-tests in iterations<commit_after>import tape from 'tape';
import { using_default_seed, using_custom_seed, custom_seed } from './data';
import * as a from '../lib/arrispwgen';

function simple_date(date) {
    return date.toLocaleDateString('en-GB', {day: '2-digit', month: '2-digit', year: 'numeric'});
}

// DRY function for testing single password
function test_single_passwords(assert, data, seed) {
    for (let p in data) {
        let date = (new Date(parseInt(p)));
        let potd = a.generate(date, seed);
        assert.test('Password for: ' + simple_date(date) + ' (timestamp: ' + p + ')', function (test) {
            test.equal(potd, data[p], 'Generated password should be correct.');
            test.end();
        });
    }

    assert.end();
}

tape('Should generate the correct password for the given days with the default seed', function(assert) {
    test_single_passwords(assert, using_default_seed);
});

tape('Should generate the correct password for the given days with a custom seed', function (assert) {
    test_single_passwords(assert, using_custom_seed, custom_seed);
});

tape('Should throw an exception if start date is after end date', function (assert) {
    assert.plan(1);
    let fn = function () {
        let d1 = new Date(2016, 1, 10);
        let d2 = new Date(2016, 1, 5);
        a.generate_multi(d1, d2);
    };

    assert.throws(fn);
});

tape('Should generate a single password if the date interval is just one day', function (assert) {
    assert.plan(1);
    let d1 = new Date(2016, 1, 5);
    let d2 = new Date(2016, 1, 5);
    let potd = a.generate_multi(d1, d2);

    assert.equal(Object.keys(potd).length, 1, 'Should generate 1 password');
});

function test_multiple_passwords(assert, data, start_index, end_index, seed) {
    let keys = Object.keys(data);

    let start = new Date(parseInt(keys[start_index]));
    let end = new Date(parseInt(keys[end_index]));
    let potd = a.generate_multi(start, end, seed);
    let count = end_index - start_index + 1;
    assert.equal(Object.keys(potd).length, count, 'Should generate ' + count + ' passwords');
    for (let p in potd) {
        let date = simple_date((new Date(parseInt(p))));
        assert.test('Password for: ' + date + ' (timestamp: ' + p + ')', function (test) {
            test.equal(potd[p], data[p], 'Generated password should be correct.');
            test.end()
        });
    }
}

tape('Should generate the correct passwords for the given date interval, with the default seed', function (assert) {
    test_multiple_passwords(assert, using_default_seed, 0, 3);
    test_multiple_passwords(assert, using_default_seed, 4, 6);
    assert.end();
});

tape('Should generate the correct passwords for the given date interval, with a custom seed', function (assert) {
    test_multiple_passwords(assert, using_custom_seed, 0, 3, custom_seed);
    test_multiple_passwords(assert, using_custom_seed, 4, 6, custom_seed);
    assert.end();
});
<|endoftext|>