<commit_before>import angular from 'angular';

import App from 'src/app';
import 'jquery';
import AppController from 'src/AppController';
window.jsonpCallback  = function() {
}
/**
 * Manually bootstrap the application when AngularJS and
 * the application classes have been loaded.
 */

var UQLibrary = angular.module("UQLibrary", [App.name]);
var initInjector = angular.injector(["ng"]);
var $http = initInjector.get("$http");

function fetchData() {
//    return $http.jsonp('//app.library.uq.edu.au/api/v2/library_hours?callback=JSON_CALLBACK')
//        .success(function(data) {
//            sessionStorage.setItem('libraryData', JSON.stringify(data.locations));
//        }).error(function(data) {
//            alert(data);
//            console.log('Error: ' + data);
//        });
    return $.ajax({
        url: "HTTPS://app.library.uq.edu.au/api/v2/library_hours",
        dataType: "jsonp",
        sonpCallback: "jsonpCallback"
    }).done((result) => {
        sessionStorage.setItem('libraryData', JSON.stringify(data.locations));
    });
}

function fetchAvailability() {
    return $.ajax({
          url: "HTTPS://app.library.uq.edu.au/api/computer_availability",
          dataType: "jsonp",
          jsonpCallback: "jsonpCallback"
    }).done((result) => {
            sessionStorage.setItem('availability', JSON.stringify(result));
    });
}

function bootstrapApplication() {
    angular.element(document).ready(function() {
        UQLibrary.controller('AppController', AppController);
        angular.bootstrap(document, ["UQLibrary"]);
    });
}

fetchData().then(fetchAvailability().then(bootstrapApplication));<commit_msg>Fix again<commit_after>import angular from 'angular';

import App from 'src/app';
import 'jquery';
import AppController from 'src/AppController';
window.jsonpCallback  = function() {
}
/**
 * Manually bootstrap the application when AngularJS and
 * the application classes have been loaded.
 */

var UQLibrary = angular.module("UQLibrary", [App.name]);
var initInjector = angular.injector(["ng"]);
var $http = initInjector.get("$http");

function fetchData() {
//    return $http.jsonp('//app.library.uq.edu.au/api/v2/library_hours?callback=JSON_CALLBACK')
//        .success(function(data) {
//            sessionStorage.setItem('libraryData', JSON.stringify(data.locations));
//        }).error(function(data) {
//            alert(data);
//            console.log('Error: ' + data);
//        });
    return $.ajax({
        url: "https://app.library.uq.edu.au/api/v2/library_hours",
        dataType: "jsonp",
        sonpCallback: "jsonpCallback"
    }).done((result) => {
        sessionStorage.setItem('libraryData', JSON.stringify(data.locations));
    });
}

function fetchAvailability() {
    return $.ajax({
          url: "https://app.library.uq.edu.au/api/computer_availability",
          dataType: "jsonp",
          jsonpCallback: "jsonpCallback"
    }).done((result) => {
            sessionStorage.setItem('availability', JSON.stringify(result));
    });
}

function bootstrapApplication() {
    angular.element(document).ready(function() {
        UQLibrary.controller('AppController', AppController);
        angular.bootstrap(document, ["UQLibrary"]);
    });
}

fetchData().then(fetchAvailability().then(bootstrapApplication));<|endoftext|>


<commit_before>// Load mist textures

let numTexturesLoaded = 0;
let texturesLoaded = false;

// Paths to all the mist particle textures
const texturePaths = [
  require('./textures/mist-particle-1.png'),
  require('./textures/mist-particle-2.png'),
  require('./textures/mist-particle-3.png'),
  require('./textures/mist-particle-4.png'),
  require('./textures/mist-particle-5.png'),
];

// Array of Image objects, one for each mist particle texture
const textures = texturePaths.map(() => new Image());

// Called when a mist particle texture finishes loading to be able to tell when all mist particles
// have loaded.
function textureLoadCallback() {
  numTexturesLoaded += 1;
  if (numTexturesLoaded === texturePaths.length) {
    texturesLoaded = true;
  }
}

// Assign a mist particle texture to each Image object
for (let i = 0; i < textures.length; i += 1) {
  textures[i].addEventListener('load', textureLoadCallback);
  textures[i].src = texturePaths[i];
}

// Helper functions

// My [].fill polyfill
if (!Array.prototype.fill) {
  /* eslint-disable no-extend-native */
  Array.prototype.fill = (value) => {
    for (let i = 0; i < this.length; i += 1) {
      this[i] = value;
    }
    return this;
  };
  /* eslint-enable no-extend-native */
}

/** Return a random number between `low` and `high` */
function random(low, high) {
  return (Math.random() * (high - low)) + low;
}

/** Return a random integer between `low` and `high-1` */
function randint(low, high) {
  return Math.floor(random(low, high));
}

/** Return a random item from an array */
function randchoice(array) {
  return array[randint(0, array.length)];
}

/** Implements basic particle physics (moving and bouncing) */
class Particle {
  /**
   * Create a new particle in a simulation.
   * Particles have positions and velocities, and remember which renderer they're bound to.
   * @param {ParticleRenderer} renderer - the ParticleRender that the particle belongs to.
   * @param {number} x - the initial X position of the particle
   * @param {number} y - the initial Y position of the particle
   * @param {number} vx - the initial X velocity of the particle
   * @param {number} vy - the initial Y velocity of the particle
   * @param {number} vrot - the initial rotational velocity of the particle (degrees)
   */
  constructor(renderer, x, y, vx, vy, vrot, image) {
    this.renderer = renderer;
    // Image object for mist particle texture. Not used by Particle, used by ParticleRenderer
    this.image = image;

    this.rot = 0;      // Rotation (in degrees)
    this.x = x;        // X position on canvas
    this.y = y;        // Y position on canvas
    this.vx = vx;      // X velocity
    this.vy = vy;      // Y velocity
    this.vrot = vrot;  // Rotational velocity
  }

  /** Advance the simulation by one step */
  step() {
    // Bounce particles when they hit the edges of the canvas
    const marginX = this.image.width / 2;
    const marginY = this.image.height / 2;
    if (this.x < -marginX || this.x >= this.renderer.width() + marginX) {
      this.bounceX();
    }
    if (this.y < -1 * marginY || this.y >= this.renderer.height() + marginX) {
      this.bounceY();
    }

    this.x += this.vx;
    this.y += this.vy;

    this.rot += this.vrot;
    this.rot %= 360;
  }

  /** Cause the current particle to bounce in the X direction */
  bounceX() {
    this.vx = -1 * this.vx;
  }

  /** Cause the current particle to bounce in the Y direction */
  bounceY() {
    this.vy = -1 * this.vy;
  }
}

/** Orchestrates and renders particles */
class ParticleRenderer {
  /**
   * Create a new particle renderer.
   * @param {CanvasRenderingContext2D} ctx - the rendering context of the canvas to draw on
   * @param {number} particleCount - the number of particles to maintain in the simulation
   * @param {number} fps - the number of frames per second at which the simulation should run
   */
  constructor(ctx, particleCount, fps) {
    this.ctx = ctx;
    this.particleCount = particleCount;
    this.fps = fps;

    this.particles = new Array(this.particleCount).fill(0).map(() => new Particle(
      this,
      randint(0, this.width()),   // X position
      randint(0, this.height()),  // Y position
      randint(-4, 4),             // X velocity
      randint(-4, 4),             // Y velocity
      random(-0.5, 0.5),          // Rotational velocity
      randchoice(textures),       // Random choice of the mist particle textures
    ));

    // Make sure no particles have 0 velocity in either direction
    this.particles = this.particles.map((p) => {
      const particle = p;
      while (particle.vx === 0) particle.vx = randint(-4, 4);
      while (particle.vy === 0) particle.vy = randint(-4, 4);
      return particle;
    });
  }

  /**
   * Draw the particles on the canvas
   */
  draw() {
    this.ctx.globalCompositeOperation = 'source-over';

    if (texturesLoaded) {
      this.ctx.clearRect(0, 0, this.width(), this.height());
      this.particles.forEach((p) => {
        const width = window.innerWidth / 3;
        const height = width;
        const rotation = p.rot * (Math.PI / 180);

        this.ctx.translate(p.x, p.y);
        this.ctx.rotate(rotation);

        this.ctx.drawImage(
          p.image,
          -(width / 2), -(height / 2),
          width, height,
        );

        this.ctx.rotate(-rotation);
        this.ctx.translate(-p.x, -p.y);
      });
    }

    // Create fade by using blend modes to create gradient opacity masks

    this.ctx.globalCompositeOperation = 'destination-out';
    // Short linear fade at the top
    const gradient1 = this.ctx.createLinearGradient(0, 0, 0, this.height() / 2);
    gradient1.addColorStop(0, 'rgba(255, 255, 255, 1)');
    gradient1.addColorStop(1, 'rgba(255, 255, 255, 0)');
    this.ctx.fillStyle = gradient1;
    this.ctx.fillRect(0, 0, this.width(), this.height() / 2);

    // Large radial gradient to cut out the top center in an arc
    const gradient2 = this.ctx.createRadialGradient(
      (this.width() / 2), 0, 0,
      (this.width() / 2), 0, (this.height() * 2),
    );
    gradient2.addColorStop(0, 'rgba(255, 255, 255, 1)');
    gradient2.addColorStop(1, 'rgba(255, 255, 255, 0)');
    this.ctx.fillStyle = gradient2;
    this.ctx.fillRect(0, 0, this.width(), this.height() * 2);
  }

  /**
   * Advance the simulation by one step
   */
  step() {
    this.particles.forEach(particle => particle.step());
  }

  /**
   * Begin a render loop
   */
  start() {
    // TODO
    this.interval = setInterval(() => {
      this.step();
      requestAnimationFrame(() => { this.draw(); });
    }, 1000 / this.fps);
  }

  /**
   * Get the width of the canvas coordinate system.
   * @return {number} the width of the canvas
   */
  width() {
    return this.ctx.canvas.width;
  }

  /**
   * Get the height of the canvas coordinate system
   * @return {number} the height of the canvas
   */
  height() {
    return this.ctx.canvas.height;
  }
}

window.addEventListener('resize', () => {
  const canvas = document.getElementById('mist');

  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight * 0.8;
});

window.addEventListener('load', () => window.dispatchEvent(new Event('resize')));

window.addEventListener('load', () => {
  const canvas = document.getElementById('mist');
  const ctx = canvas.getContext('2d');
  window.mistSim = new ParticleRenderer(ctx, 85, 25);
  window.mistSim.start();
});
<commit_msg>Allow finer values for particle position and velocity<commit_after>// Load mist textures

let numTexturesLoaded = 0;
let texturesLoaded = false;

// Paths to all the mist particle textures
const texturePaths = [
  require('./textures/mist-particle-1.png'),
  require('./textures/mist-particle-2.png'),
  require('./textures/mist-particle-3.png'),
  require('./textures/mist-particle-4.png'),
  require('./textures/mist-particle-5.png'),
];

// Array of Image objects, one for each mist particle texture
const textures = texturePaths.map(() => new Image());

// Called when a mist particle texture finishes loading to be able to tell when all mist particles
// have loaded.
function textureLoadCallback() {
  numTexturesLoaded += 1;
  if (numTexturesLoaded === texturePaths.length) {
    texturesLoaded = true;
  }
}

// Assign a mist particle texture to each Image object
for (let i = 0; i < textures.length; i += 1) {
  textures[i].addEventListener('load', textureLoadCallback);
  textures[i].src = texturePaths[i];
}

// Helper functions

// My [].fill polyfill
if (!Array.prototype.fill) {
  /* eslint-disable no-extend-native */
  Array.prototype.fill = (value) => {
    for (let i = 0; i < this.length; i += 1) {
      this[i] = value;
    }
    return this;
  };
  /* eslint-enable no-extend-native */
}

/** Return a random number between `low` and `high` */
function random(low, high) {
  return (Math.random() * (high - low)) + low;
}

/** Return a random integer between `low` and `high-1` */
function randint(low, high) {
  return Math.floor(random(low, high));
}

/** Return a random item from an array */
function randchoice(array) {
  return array[randint(0, array.length)];
}

/** Implements basic particle physics (moving and bouncing) */
class Particle {
  /**
   * Create a new particle in a simulation.
   * Particles have positions and velocities, and remember which renderer they're bound to.
   * @param {ParticleRenderer} renderer - the ParticleRender that the particle belongs to.
   * @param {number} x - the initial X position of the particle
   * @param {number} y - the initial Y position of the particle
   * @param {number} vx - the initial X velocity of the particle
   * @param {number} vy - the initial Y velocity of the particle
   * @param {number} vrot - the initial rotational velocity of the particle (degrees)
   */
  constructor(renderer, x, y, vx, vy, vrot, image) {
    this.renderer = renderer;
    // Image object for mist particle texture. Not used by Particle, used by ParticleRenderer
    this.image = image;

    this.rot = 0;      // Rotation (in degrees)
    this.x = x;        // X position on canvas
    this.y = y;        // Y position on canvas
    this.vx = vx;      // X velocity
    this.vy = vy;      // Y velocity
    this.vrot = vrot;  // Rotational velocity
  }

  /** Advance the simulation by one step */
  step() {
    // Bounce particles when they hit the edges of the canvas
    const marginX = this.image.width / 2;
    const marginY = this.image.height / 2;
    if (this.x < -marginX || this.x >= this.renderer.width() + marginX) {
      this.bounceX();
    }
    if (this.y < -1 * marginY || this.y >= this.renderer.height() + marginX) {
      this.bounceY();
    }

    this.x += this.vx;
    this.y += this.vy;

    this.rot += this.vrot;
    this.rot %= 360;
  }

  /** Cause the current particle to bounce in the X direction */
  bounceX() {
    this.vx = -1 * this.vx;
  }

  /** Cause the current particle to bounce in the Y direction */
  bounceY() {
    this.vy = -1 * this.vy;
  }
}

/** Orchestrates and renders particles */
class ParticleRenderer {
  /**
   * Create a new particle renderer.
   * @param {CanvasRenderingContext2D} ctx - the rendering context of the canvas to draw on
   * @param {number} particleCount - the number of particles to maintain in the simulation
   * @param {number} fps - the number of frames per second at which the simulation should run
   */
  constructor(ctx, particleCount, fps) {
    this.ctx = ctx;
    this.particleCount = particleCount;
    this.fps = fps;

    this.particles = new Array(this.particleCount).fill(0).map(() => new Particle(
      this,
      random(0, this.width()),   // X position
      random(0, this.height()),  // Y position
      random(-4, 4),             // X velocity
      random(-4, 4),             // Y velocity
      random(-0.5, 0.5),         // Rotational velocity
      randchoice(textures),      // Random choice of the mist particle textures
    ));

    // Make sure no particles have 0 velocity in either direction
    this.particles = this.particles.map((p) => {
      const particle = p;
      while (particle.vx < 0.5) particle.vx = random(-4, 4);
      while (particle.vy < 0.5) particle.vy = random(-4, 4);
      return particle;
    });
  }

  /**
   * Draw the particles on the canvas
   */
  draw() {
    this.ctx.globalCompositeOperation = 'source-over';

    if (texturesLoaded) {
      this.ctx.clearRect(0, 0, this.width(), this.height());
      this.particles.forEach((p) => {
        const width = window.innerWidth / 3;
        const height = width;
        const rotation = p.rot * (Math.PI / 180);

        this.ctx.translate(p.x, p.y);
        this.ctx.rotate(rotation);

        this.ctx.drawImage(
          p.image,
          -(width / 2), -(height / 2),
          width, height,
        );

        this.ctx.rotate(-rotation);
        this.ctx.translate(-p.x, -p.y);
      });
    }

    // Create fade by using blend modes to create gradient opacity masks

    this.ctx.globalCompositeOperation = 'destination-out';
    // Short linear fade at the top
    const gradient1 = this.ctx.createLinearGradient(0, 0, 0, this.height() / 2);
    gradient1.addColorStop(0, 'rgba(255, 255, 255, 1)');
    gradient1.addColorStop(1, 'rgba(255, 255, 255, 0)');
    this.ctx.fillStyle = gradient1;
    this.ctx.fillRect(0, 0, this.width(), this.height() / 2);

    // Large radial gradient to cut out the top center in an arc
    const gradient2 = this.ctx.createRadialGradient(
      (this.width() / 2), 0, 0,
      (this.width() / 2), 0, (this.height() * 2),
    );
    gradient2.addColorStop(0, 'rgba(255, 255, 255, 1)');
    gradient2.addColorStop(1, 'rgba(255, 255, 255, 0)');
    this.ctx.fillStyle = gradient2;
    this.ctx.fillRect(0, 0, this.width(), this.height() * 2);
  }

  /**
   * Advance the simulation by one step
   */
  step() {
    this.particles.forEach(particle => particle.step());
  }

  /**
   * Begin a render loop
   */
  start() {
    // TODO
    this.interval = setInterval(() => {
      this.step();
      requestAnimationFrame(() => { this.draw(); });
    }, 1000 / this.fps);
  }

  /**
   * Get the width of the canvas coordinate system.
   * @return {number} the width of the canvas
   */
  width() {
    return this.ctx.canvas.width;
  }

  /**
   * Get the height of the canvas coordinate system
   * @return {number} the height of the canvas
   */
  height() {
    return this.ctx.canvas.height;
  }
}

window.addEventListener('resize', () => {
  const canvas = document.getElementById('mist');

  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight * 0.8;
});

window.addEventListener('load', () => window.dispatchEvent(new Event('resize')));

window.addEventListener('load', () => {
  const canvas = document.getElementById('mist');
  const ctx = canvas.getContext('2d');
  window.mistSim = new ParticleRenderer(ctx, 85, 25);
  window.mistSim.start();
});
<|endoftext|>


<commit_before>/**
    SchedulingPreferences activity
**/
'use strict';

var Activity = require('../components/Activity');
var ko = require('knockout');
var moment = require('moment');

var A = Activity.extend(function SchedulingPreferencesActivity() {

    Activity.apply(this, arguments);

    this.viewModel = new ViewModel(this.app);
    this.accessLevel = this.app.UserType.serviceProfessional;

    this.navBar = Activity.createSubsectionNavBar('Scheduler', {
        backLink: '/scheduling',
        helpLink: '/help/relatedArticles/201961423-setting-your-scheduling-preferences'
        // '/help/relatedArticles/201964173-setting-your-weekly-schedule'
    });

    this.defaultNavBar = this.navBar.model.toPlainObject(true);

    this.registerHandler({
        target: this.app.model.weeklySchedule,
        event: 'error',
        handler: function(err) {
            var msg = err.task === 'save' ? 'Error saving your weekly schedule.' : 'Error loading your weekly schedule.';
            this.app.modals.showError({
                title: msg,
                error: err && err.task && err.error || err
            });
        }.bind(this)
    });

    this.registerHandler({
        target: this.app.model.schedulingPreferences,
        event: 'error',
        handler: function(err) {
            var msg = err.task === 'save' ? 'Error saving scheduling preferences.' : 'Error loading scheduling preferences.';
            this.app.modals.showError({
                title: msg,
                error: err && err.task && err.error || err
            });
        }.bind(this)
    });
});

exports.init = A.init;

A.prototype.updateNavBarState = function updateNavBarState() {

    if (!this.app.model.onboarding.updateNavBar(this.navBar)) {
        // Reset
        this.navBar.model.updateWith(this.defaultNavBar, true);
    }
};

A.prototype.show = function show(state) {
    Activity.prototype.show.call(this, state);

    this.updateNavBarState();

    // Keep data updated:
    this.app.model.schedulingPreferences.sync();
    this.app.model.weeklySchedule.sync();
    // Discard any previous unsaved edit
    this.viewModel.discard();
};

/// View Models

function ViewModel(app) {

    this.isInOnboarding = app.model.onboarding.inProgress;

    this.schedulingPreferences = new SchedulingPreferencesVM(app);
    this.weeklySchedule = new WeeklyScheduleVM(app);

    this.save = function save() {
        return Promise.all([
            this.schedulingPreferences.save(),
            this.weeklySchedule.save()
        ])
        .then(function() {
            // A weekly schedule change may change the status of userJobTitles and bookMeButtonReady, so
            // force a refresh of that data
            app.model.userJobProfile.clearCache();
            app.model.userJobProfile.syncList();
            // Move forward:
            if (app.model.onboarding.inProgress()) {
                app.model.onboarding.goNext();
            } else {
                app.successSave();
            }
        })
        .catch(function() {
            // catch error, managed on event
        });
    }.bind(this);

    this.discard = function discard() {
        this.schedulingPreferences.discard();
        this.weeklySchedule.discard();
    }.bind(this);

    this.isLoading = ko.pureComputed(function() {
        return this.schedulingPreferences.isLoading() || this.weeklySchedule.isLoading();
    }, this);
    this.isSaving = ko.pureComputed(function() {
        return this.schedulingPreferences.isSaving() || this.weeklySchedule.isSaving();
    }, this);
    this.isLocked = ko.pureComputed(function() {
        return this.schedulingPreferences.isLocked() || this.weeklySchedule.isLocked();
    }, this);

    this.submitText = ko.pureComputed(function() {
        return (
            app.model.onboarding.inProgress() ?
                'Save and continue' :
                this.isLoading() ?
                    'loading...' :
                    this.isSaving() ?
                        'saving...' :
                        'Save'
        );
    }, this);
    this.submitText = ko.pureComputed(function() {
        return (
            this.isLoading() ?
                'loading...' :
                this.isSaving() ?
                    'saving...' :
                    'Save'
        );
    }, this);
}

function SchedulingPreferencesVM(app) {

    var schedulingPreferences = app.model.schedulingPreferences;

    var prefsVersion = schedulingPreferences.newVersion();
    prefsVersion.isObsolete.subscribe(function(itIs) {
        if (itIs) {
            // new version from server while editing
            // FUTURE: warn about a new remote version asking
            // confirmation to load them or discard and overwrite them;
            // the same is need on save(), and on server response
            // with a 509:Conflict status (its body must contain the
            // server version).
            // Right now, just overwrite current changes with
            // remote ones:
            prefsVersion.pull({ evenIfNewer: true });
        }
    });

    // Actual data for the form:
    this.prefs = prefsVersion.version;

    this.isLoading = schedulingPreferences.isLoading;
    this.isSaving = schedulingPreferences.isSaving;
    this.isLocked = schedulingPreferences.isLocked;

    this.discard = function discard() {
        prefsVersion.pull({ evenIfNewer: true });
    }.bind(this);

    this.save = function save() {
        return prefsVersion.pushSave();
    }.bind(this);

    this.incrementsExample = ko.pureComputed(function() {

        var str = 'e.g. ',
            incSize = this.incrementsSizeInMinutes(),
            m = moment({ hour: 10, minute: 0 }),
            hours = [m.format('HH:mm')];

        for (var i = 1; i < 4; i++) {
            hours.push(
                m.add(incSize, 'minutes')
                .format('HH:mm')
            );
        }
        str += hours.join(', ');

        return str;

    }, this.prefs);
}

function WeeklyScheduleVM(app) {

    var weeklySchedule = app.model.weeklySchedule;

    var scheduleVersion = weeklySchedule.newVersion();
    scheduleVersion.isObsolete.subscribe(function(itIs) {
        if (itIs) {
            // new version from server while editing
            // FUTURE: warn about a new remote version asking
            // confirmation to load them or discard and overwrite them;
            // the same is need on save(), and on server response
            // with a 509:Conflict status (its body must contain the
            // server version).
            // Right now, just overwrite current changes with
            // remote ones:
            scheduleVersion.pull({ evenIfNewer: true });
        }
    });

    // Actual data for the form:
    this.schedule = scheduleVersion.version;

    this.isLoading = weeklySchedule.isLoading;
    this.isSaving = weeklySchedule.isSaving;
    this.isLocked = weeklySchedule.isLocked;

    this.discard = function discard() {
        scheduleVersion.pull({ evenIfNewer: true });
    };

    this.save = function save() {
        return scheduleVersion.pushSave();
    };
}

<commit_msg>Removed duplicate submittext code in..<commit_after>/**
    SchedulingPreferences activity
**/
'use strict';

var Activity = require('../components/Activity');
var ko = require('knockout');
var moment = require('moment');

var A = Activity.extend(function SchedulingPreferencesActivity() {

    Activity.apply(this, arguments);

    this.viewModel = new ViewModel(this.app);
    this.accessLevel = this.app.UserType.serviceProfessional;

    this.navBar = Activity.createSubsectionNavBar('Scheduler', {
        backLink: '/scheduling',
        helpLink: '/help/relatedArticles/201961423-setting-your-scheduling-preferences'
        // '/help/relatedArticles/201964173-setting-your-weekly-schedule'
    });

    this.defaultNavBar = this.navBar.model.toPlainObject(true);

    this.registerHandler({
        target: this.app.model.weeklySchedule,
        event: 'error',
        handler: function(err) {
            var msg = err.task === 'save' ? 'Error saving your weekly schedule.' : 'Error loading your weekly schedule.';
            this.app.modals.showError({
                title: msg,
                error: err && err.task && err.error || err
            });
        }.bind(this)
    });

    this.registerHandler({
        target: this.app.model.schedulingPreferences,
        event: 'error',
        handler: function(err) {
            var msg = err.task === 'save' ? 'Error saving scheduling preferences.' : 'Error loading scheduling preferences.';
            this.app.modals.showError({
                title: msg,
                error: err && err.task && err.error || err
            });
        }.bind(this)
    });
});

exports.init = A.init;

A.prototype.updateNavBarState = function updateNavBarState() {

    if (!this.app.model.onboarding.updateNavBar(this.navBar)) {
        // Reset
        this.navBar.model.updateWith(this.defaultNavBar, true);
    }
};

A.prototype.show = function show(state) {
    Activity.prototype.show.call(this, state);

    this.updateNavBarState();

    // Keep data updated:
    this.app.model.schedulingPreferences.sync();
    this.app.model.weeklySchedule.sync();
    // Discard any previous unsaved edit
    this.viewModel.discard();
};

/// View Models

function ViewModel(app) {

    this.isInOnboarding = app.model.onboarding.inProgress;

    this.schedulingPreferences = new SchedulingPreferencesVM(app);
    this.weeklySchedule = new WeeklyScheduleVM(app);

    this.save = function save() {
        return Promise.all([
            this.schedulingPreferences.save(),
            this.weeklySchedule.save()
        ])
        .then(function() {
            // A weekly schedule change may change the status of userJobTitles and bookMeButtonReady, so
            // force a refresh of that data
            app.model.userJobProfile.clearCache();
            app.model.userJobProfile.syncList();
            // Move forward:
            if (app.model.onboarding.inProgress()) {
                app.model.onboarding.goNext();
            } else {
                app.successSave();
            }
        })
        .catch(function() {
            // catch error, managed on event
        });
    }.bind(this);

    this.discard = function discard() {
        this.schedulingPreferences.discard();
        this.weeklySchedule.discard();
    }.bind(this);

    this.isLoading = ko.pureComputed(function() {
        return this.schedulingPreferences.isLoading() || this.weeklySchedule.isLoading();
    }, this);
    this.isSaving = ko.pureComputed(function() {
        return this.schedulingPreferences.isSaving() || this.weeklySchedule.isSaving();
    }, this);
    this.isLocked = ko.pureComputed(function() {
        return this.schedulingPreferences.isLocked() || this.weeklySchedule.isLocked();
    }, this);

    this.submitText = ko.pureComputed(function() {
        return (
            app.model.onboarding.inProgress() ?
                'Save and continue' :
                this.isLoading() ?
                    'loading...' :
                    this.isSaving() ?
                        'Saving...' :
                        'Save'
        );
    }, this);
}

function SchedulingPreferencesVM(app) {

    var schedulingPreferences = app.model.schedulingPreferences;

    var prefsVersion = schedulingPreferences.newVersion();
    prefsVersion.isObsolete.subscribe(function(itIs) {
        if (itIs) {
            // new version from server while editing
            // FUTURE: warn about a new remote version asking
            // confirmation to load them or discard and overwrite them;
            // the same is need on save(), and on server response
            // with a 509:Conflict status (its body must contain the
            // server version).
            // Right now, just overwrite current changes with
            // remote ones:
            prefsVersion.pull({ evenIfNewer: true });
        }
    });

    // Actual data for the form:
    this.prefs = prefsVersion.version;

    this.isLoading = schedulingPreferences.isLoading;
    this.isSaving = schedulingPreferences.isSaving;
    this.isLocked = schedulingPreferences.isLocked;

    this.discard = function discard() {
        prefsVersion.pull({ evenIfNewer: true });
    }.bind(this);

    this.save = function save() {
        return prefsVersion.pushSave();
    }.bind(this);

    this.incrementsExample = ko.pureComputed(function() {

        var str = 'e.g. ',
            incSize = this.incrementsSizeInMinutes(),
            m = moment({ hour: 10, minute: 0 }),
            hours = [m.format('HH:mm')];

        for (var i = 1; i < 4; i++) {
            hours.push(
                m.add(incSize, 'minutes')
                .format('HH:mm')
            );
        }
        str += hours.join(', ');

        return str;

    }, this.prefs);
}

function WeeklyScheduleVM(app) {

    var weeklySchedule = app.model.weeklySchedule;

    var scheduleVersion = weeklySchedule.newVersion();
    scheduleVersion.isObsolete.subscribe(function(itIs) {
        if (itIs) {
            // new version from server while editing
            // FUTURE: warn about a new remote version asking
            // confirmation to load them or discard and overwrite them;
            // the same is need on save(), and on server response
            // with a 509:Conflict status (its body must contain the
            // server version).
            // Right now, just overwrite current changes with
            // remote ones:
            scheduleVersion.pull({ evenIfNewer: true });
        }
    });

    // Actual data for the form:
    this.schedule = scheduleVersion.version;

    this.isLoading = weeklySchedule.isLoading;
    this.isSaving = weeklySchedule.isSaving;
    this.isLocked = weeklySchedule.isLocked;

    this.discard = function discard() {
        scheduleVersion.pull({ evenIfNewer: true });
    };

    this.save = function save() {
        return scheduleVersion.pushSave();
    };
}

<|endoftext|>


<commit_before>/**
 * Node app that calls phantomJs to render the page and then outputs the page content.
 * App takes 3 arguments in the following order:
 *  1. which url to render
 *  2. path to phantomjs executable
 *  3. snapshot interval
 *  4. (optional) debug mode - to enable pass "debug" as 4th parameter
 *
 *  Note: this js file needs to be inside the PHP folder, otherwise webpack will strip it out when deploying the app to production.
 */
const phantom = require("phantom");

const args = process.argv.slice(2);
const url = args[0];
const snapshotInterval = args[2] || false;
const debug = args[3] || false;
const maxExecutionTime = 15000; // 15s
let status = 0;

phantom.create([
    '--ignore-ssl-errors=yes',
    '--load-images=false',
    '--local-storage-path=/tmp',
    '--local-storage-quota=5000',
    '--offline-storage-path=/tmp',
    '--offline-storage-quota=5000'], {
    phantomPath: args[1],
    logLevel: (debug) ? 'info' : 'error'
}).then((ph) => {
    ph.createPage().then((page) => {

        if (debug) {
            page.on("onConsoleMessage", function (msg, lineNum, sourceId) {
                console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');
            });

            page.on("onResourceRequested", (requestData) => {
                console.info('Requesting', requestData.url);
            });

            page.on("onResourceError", function (resourceError) {
                console.log('Unable to load resource (#' + resourceError.id + 'URL:' + resourceError.url + ')');
                console.log('Error code: ' + resourceError.errorCode + '. Description: ' + resourceError.errorString);
            });
        }

        page.property('viewportSize', {width: 1440, height: 768});
        page.property('customHeaders', {"XWebinyStaticRender": "true"});

        page.on('onResourceReceived', function (resource) {
            //statusCode = resource.status;
            if (resource.url == url) {
                if (status == 0) {
                    status = resource.status;
                    console.log('status code:' + resource.status);
                }
                if (resource.status != 200) {
                    process.exit();
                }
            }
        });

        page.open(url).then((status) => {

            let runs = maxExecutionTime / snapshotInterval;
            let webinyStartedInterval = setInterval(() => {

                page.evaluate(function () {
                    if (typeof window.webinyFirstRenderDone === 'function') {
                        if (window.webinyFirstRenderDone()) {
                            // we need to remove all the script elements from the static page (can cause problems with googlebot)
                            return document.documentElement.innerHTML.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
                        }
                    }
                    return false;
                }).then(content => {
                    if (content) {
                        clearInterval(webinyStartedInterval);

                        console.log(content);
                        process.exit();

                        return content;
                    }
                }).catch(error => {
                    if (debug) {
                        console.log(error);
                    }
                    process.exit();
                });

                runs--;
                if (runs < 0) {
                    process.exit();
                }

            }, snapshotInterval);

        }).catch(error => {
            if (debug) {
                console.log(error);
            }
            process.exit();
        });
    }).catch(error => {
        if (debug) {
            console.log(error);
        }
        process.exit();
    });
});<commit_msg>Added a call to kill phantomjs process after execution.<commit_after>/**
 * Node app that calls phantomJs to render the page and then outputs the page content.
 * App takes 3 arguments in the following order:
 *  1. which url to render
 *  2. path to phantomjs executable
 *  3. snapshot interval
 *  4. (optional) debug mode - to enable pass "debug" as 4th parameter
 *
 *  Note: this js file needs to be inside the PHP folder, otherwise webpack will strip it out when deploying the app to production.
 */
const phantom = require("phantom");

const args = process.argv.slice(2);
const url = args[0];
const snapshotInterval = args[2] || false;
const debug = args[3] || false;
const maxExecutionTime = 15000; // 15s
let status = 0;

phantom.create([
    '--ignore-ssl-errors=yes',
    '--load-images=false',
    '--local-storage-path=/tmp',
    '--local-storage-quota=5000',
    '--offline-storage-path=/tmp',
    '--offline-storage-quota=5000'], {
    phantomPath: args[1],
    logLevel: (debug) ? 'info' : 'error'
}).then((ph) => {
    ph.createPage().then((page) => {

        if (debug) {
            page.on("onConsoleMessage", function (msg, lineNum, sourceId) {
                console.log('CONSOLE: ' + msg + ' (from line #' + lineNum + ' in "' + sourceId + '")');
            });

            page.on("onResourceRequested", (requestData) => {
                console.info('Requesting', requestData.url);
            });

            page.on("onResourceError", function (resourceError) {
                console.log('Unable to load resource (#' + resourceError.id + 'URL:' + resourceError.url + ')');
                console.log('Error code: ' + resourceError.errorCode + '. Description: ' + resourceError.errorString);
            });
        }

        page.property('viewportSize', {width: 1440, height: 768});
        page.property('customHeaders', {"XWebinyStaticRender": "true"});

        page.on('onResourceReceived', function (resource) {
            //statusCode = resource.status;
            if (resource.url == url) {
                if (status == 0) {
                    status = resource.status;
                    console.log('status code:' + resource.status);
                }
                if (resource.status != 200) {
                    process.exit();
                }
            }
        });

        page.open(url).then((status) => {

            let runs = maxExecutionTime / snapshotInterval;
            let webinyStartedInterval = setInterval(() => {

                page.evaluate(function () {
                    if (typeof window.webinyFirstRenderDone === 'function') {
                        if (window.webinyFirstRenderDone()) {
                            // we need to remove all the script elements from the static page (can cause problems with googlebot)
                            return document.documentElement.innerHTML.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '');
                        }
                    }
                    return false;
                }).then(content => {
                    if (content) {
                        clearInterval(webinyStartedInterval);

                        console.log(content);
                        ph.exit();
                        process.exit();

                        return content;
                    }
                }).catch(error => {
                    if (debug) {
                        console.log(error);
                    }
                    ph.exit();
                    process.exit();
                });

                runs--;
                if (runs < 0) {
                    ph.exit();
                    process.exit();
                }

            }, snapshotInterval);

        }).catch(error => {
            if (debug) {
                console.log(error);
            }
            ph.exit();
            process.exit();
        });
    }).catch(error => {
        if (debug) {
            console.log(error);
        }
        ph.exit();
        process.exit();
    });
});<|endoftext|>


<commit_before>class Menu {

    constructor(label, route = null, icon = '', key = '') {
        Object.assign(this, {label, route, icon});
        this.key = key ? key : label;
        this.action = null;
        this.overrideExisting = false;
    }

    setAction(label, route, icon = '') {
        this.action = {label, route, icon};
        return this;
    }

    setOverrideExisting() {
        this.overrideExisting = true;
        return this;
    }
}

export default Menu;<commit_msg>Added "setRole" method to Menu class.<commit_after>class Menu {

    constructor(label, route = null, icon = '', key = '') {
        Object.assign(this, {label, route, icon});
        this.key = key ? key : label;
        this.action = null;
        this.role = null;
        this.overrideExisting = false;
    }

    setAction(label, route, icon = '') {
        this.action = {label, route, icon};
        return this;
    }

    setOverrideExisting() {
        this.overrideExisting = true;
        return this;
    }

    setRole(role) {
        this.role = role;
        return this;
    }
}

export default Menu;<|endoftext|>


<commit_before>import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  ListView,
  Navigator,
  Image,
  TouchableHighlight
} from 'react-native';

class DogProfile extends Components {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.text}>
          This is Dog Profile
        </Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 14,
  }
});

module.exports = DogProfile;
<commit_msg>Add UserEdit.js<commit_after>import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  TextInput,
  View,
  ListView,
  Navigator,
  Image,
  TouchableHighlight
} from 'react-native';

class UserEdit extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.text}>
          Edit User
        </Text>
        <TextInput
          placeholder="Username"
          style={styles.input}
        />
        <TextInput
          placeholder="Name"
          style={styles.input}
        />
        <TextInput
          placeholder="Email"
          style={styles.input}
        />
        <TextInput
          placeholder="Password"
          style={styles.input}
        />
        <TouchableHighlight
          style={styles.button}>
          <Text style={styles.buttonText}>
            Edit
          </Text>
        </TouchableHighlight>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 40,
  },
  button: {
    height: 36,
    backgroundColor: "#48bbec",
    borderWidth: 1,
    borderRadius: 8,
    marginBottom: 10,
    alignSelf: "stretch",
  },
  buttonText: {
    fontSize: 18,
    color: "white",
    alignSelf: "center",
  },
  input: {
    height: 40,
  }
});

module.exports = UserEdit;
<|endoftext|>


<commit_before>"use strict";

var path = require('path');
var fs = require('graceful-fs');

var program = require('commander');
var unzip = require('unzip');
var tmp = require('tmp');
var Q = require('q');
var Map = require('es6-map');

var containingCube = require('../src/containingCube.js');
var computeMeshVolume = require('../src/computeMeshVolume.js');
var _3dsFormatToAntsBinaryBuffer = require('../src/3dsFormatToAntsBinaryBuffer.js');
var parse3ds = require('../src/parse3ds.js');

var getTileAltitudes = require('./getTileAltitudes.js');
var tileAltitudesP = getTileAltitudes(path.resolve(process.cwd(), './data/DALLAGE_3D.csv'));

function tmpdir(){
    var def = Q.defer();

    tmp.dir(function(err, dir){
        if(err)
            def.reject(err);
        else
            def.resolve(dir);
    });

    return def.promise;
}
var readdir = Q.nfbind(fs.readdir);
var readFile = Q.nfbind(fs.readFile);
var writeFile = Q.nfbind(fs.writeFile);
var lstat = Q.nfbind(fs.lstat);

/*
    Takes an Array<T> and a function(x: T) => Promise<U>
    Processes each array element in sequence.
*/
function allInSequence(arr, f){
    if(arr.length === 0){
        var def = Q.defer();
        def.resolve([]);
        return def.promise;
    }

    if(arr.length === 1){
        return f(arr[0]).then(function(res){
            return [res];
        })
    }
    else{
        var first = arr[0];
        var tail = arr.slice(1);

        return f(first).then(function(res){
            return allInSequence(tail, f).then(function(tailRes){
                tailRes.push(res);
                return tailRes;
            });
        });
    }
}

program
    .version('0.0.1')
    .option('--zip [path]', 'path to zip file')
    .option('--out [path]', 'output directory')
    .parse(process.argv);

if(!program.zip || !program.out)
    throw new Error('--zip and --out parameters are compulsory');

var zipAbsolutePath = path.resolve(process.cwd(), program.zip);
var outAbsolutePath = path.resolve(process.cwd(), program.out);

console.log(zipAbsolutePath, outAbsolutePath);

// name => next nb to use to append to the name to make it unique
var ids = new Map();

// in : one archive
// out : a bunch of binaries + metadata

function extractBuildings(_3dsPath, x, y){
    var def = Q.defer();
    parse3ds(_3dsPath, function(err, data){
        if(err){
            def.reject(err);
            return;
        }

        var objects = data.getObjects();
        var meshes = objects.map(function(o){
            // Generate unique ids since the data producers may fail to do so
            var id;
            if(!ids.has(o.name)){
                id = o.name;
                ids.set(o.name, 1);
            }
            else{
                var nb = ids.get(o.name);
                id = o.name + '-' + nb;
                while(ids.has(id)){
                    nb++;
                    id = o.name + '-' + nb;
                }
                ids.set(o.name, nb+1);
            }

            return {
                id: id,
                vertices: o.meshes.vertices,
                faces: o.meshes.faces
            };

        });

        /*
            Working around https://github.com/anthill/bordeaux3d/issues/11
        */
        // looking for xXXXyYYY object. There is only one per 3ds file
        var xyObject = meshes.filter(function(m){ return !!m.id.match(/x(\d{1,4})y(\d{1,4})/) })[0];
        var xyContainingCube = containingCube(xyObject);

        // Ideally, xyContainingCube.xmax and xyContainingCube.ymax should be +100. Finding the translation.
        var deltaX = 100 - xyContainingCube.maxX;
        var deltaY = 100 - xyContainingCube.maxY;

        if(deltaX !== 0 || deltaY !== 0){
            // apply translation to all tile objects
            meshes.forEach(function(m){
                m.vertices.forEach(function(v){
                    v.x += deltaX;
                    v.y += deltaY;
                });
            });
        }

        tileAltitudesP.then(function(tilesAltitudes){
            var key = _3dsPath.match(/x\d{1,4}y\d{1,4}/)[0];

            var deltaZ = tilesAltitudes[key];

            meshes.forEach(function(m){
                m.vertices.forEach(function(v){
                    v.z += deltaZ;
                });
            });

            // Find tile bounding box
            var containingCubes = meshes.map(containingCube);
            // create a fake mesh based on the cubes descriptions
            var fakeCombiningMesh = {
                vertices: containingCubes.reduce(function(acc, cubeDesc){
                    acc.push({
                        x: cubeDesc.minX,
                        y: cubeDesc.minY,
                        z: cubeDesc.minZ,
                    });
                    acc.push({
                        x: cubeDesc.maxX,
                        y: cubeDesc.maxY,
                        z: cubeDesc.maxZ,
                    });

                    return acc;
                }, [])
            };
            var tileContainingCube = containingCube(fakeCombiningMesh);

            var minZ = Math.floor(tileContainingCube.minZ);
            var maxZ = Math.ceil(tileContainingCube.maxZ);
            if(minZ === maxZ){ // happens for flat floor objects
                maxZ = minZ + 1;
            }

            // integer approximation
            var tileMetadata = {
                X: x,
                Y: y,
                minX: Math.floor(tileContainingCube.minX),
                maxX: Math.ceil(tileContainingCube.maxX),
                minY: Math.floor(tileContainingCube.minY),
                maxY: Math.ceil(tileContainingCube.maxY),
                minZ: minZ,
                maxZ: maxZ,
                objects: Object.create(null)
            };

            var buildingBuffers = Object.create(null);
            meshes.forEach(function(m, i){
                try{
                buildingBuffers[m.id] = _3dsFormatToAntsBinaryBuffer(m, tileMetadata);
                }
                catch(e){
                    console.error('compacting error', x, y, e)
                }
            });

            meshes.forEach(function(m){

                // we get the type (either building or terrain)
                var objectType;
                if (m.id[0] == "x"){
                    objectType = "terrain";
                } else {
                    objectType = "building"
                }
                // for recentering
                var objectCube = containingCube(m);

                tileMetadata.objects[m.id] = {
                    x: Math.round( (objectCube.minX + objectCube.maxX)/2 ),
                    y: Math.round( (objectCube.minY + objectCube.maxY)/2 ),
                    type: objectType
                };
            });

            def.resolve({
                buildingBuffers: buildingBuffers,
                tileMetadata : tileMetadata
            });

        });

    });

    return def.promise;
}

function unzipInTmpDir(pathToZip){
    var readStream = fs.createReadStream(pathToZip);

    return tmpdir().then(function(tmpDir){
        var def = Q.defer();

        var extractWriteStream = unzip.Extract({ path: tmpDir })
        readStream.pipe(extractWriteStream);

        extractWriteStream.on('close', function(e){ // 'close' event, unlike 'finish' guarantees all writes to disk are finished
            //console.log('close');
            def.resolve(tmpDir);
        });

        extractWriteStream.on('error', function(err){
            def.reject(err);
        });

        extractWriteStream.on('finish', function(e){ // 'close' event, unlike 'finish' guarantees all writes to disk are finished
            console.log('finish', pathToZip, tmpDir);
            extractWriteStream.end();
        });

        return def.promise;
    });
}

/*
    Process the directory of a single selection
*/
function processSelectionDirectory(selectionZipDirPath){
    //console.log('processSelectionDirectory', selectionZipDirPath);

    var selectionName = path.basename(selectionZipDirPath, '.zip'); // assumed 2 letters like "RL"

    return unzipInTmpDir(selectionZipDirPath)
        .then(function(selectionDir){
            //console.log('unzipped', selectionName, selectionDir);

            var selectionPath = path.join(selectionDir, selectionName);
            return readdir(selectionPath)
                .then(function(selectionFiles){
                    //selectionFiles = selectionFiles.slice(0, 4);

                    //console.log("selectionFiles", selectionFiles);

                    var tile3dsPathsAndxy = selectionFiles
                        .filter(function(f){
                            return f.indexOf('.') === -1; // weak test
                        })
                        .map(function(f){
                            var matches = f.match(/x(\d{1,4})y(\d{1,4})/);

                            return {
                                x: parseInt(matches[1]),
                                y: parseInt(matches[2]),
                                path: path.join(selectionPath, f, f +'.3ds')
                            };
                        });
                    //console.log(name, tile3dsPaths);

                    return Q.all(tile3dsPathsAndxy.map(function(tpxy){
                        var tile3dsPath = tpxy.path;
                        var x = tpxy.x;
                        var y = tpxy.y;

                        return extractBuildings(tile3dsPath, x, y).then(function(res){
                            var buildingBuffers = res.buildingBuffers;
                            var tileMetadata = res.tileMetadata;

                            return Q.all(Object.keys(buildingBuffers).map(function(id){
                                var buildingOutPath = path.join(outAbsolutePath, id);

                                // despite graceful-fs, we see some EMFILE errors in writes
                                function tryWriteFile(){
                                    return writeFile(buildingOutPath, buildingBuffers[id]).fail(function(err){
                                        if(String(err).indexOf('EMFILE') !== -1){
                                            console.error('tryWriteFile EMFILE', selectionName, x, y, tile3dsPath, err)
                                            var def = Q.defer();

                                            // retry later
                                            setTimeout(function(){
                                                def.resolve(tryWriteFile());
                                            }, 100);

                                            return def.promise;
                                        }
                                        else{// forward error
                                            throw err;
                                        }
                                    })
                                }

                                return tryWriteFile();
                            })).then(function(){
                                // metadata is returned when all building binary files have been written
                                return tileMetadata;
                            });

                        }).fail(function(err){
                            console.error('extractBuildings error', selectionName, x, y, tile3dsPath, err);
                        });
                    }));
                });
        });

}

console.time('all');
console.time('extract');

unzipInTmpDir(zipAbsolutePath)
    .then(function(tmpDir){
        console.timeEnd('extract');

        return readdir(tmpDir).then(function(selectionZips){
            //console.log("selectionZips", selectionZips);

            //selectionZips = selectionZips.slice(0, 3);

            var absoluteZipPaths = selectionZips.map(function(zipPath){
                return path.join(tmpDir, zipPath);
            });

            //console.log(absoluteZipPaths);

            //return Q.all(absoluteZipPaths.map(processSelectionDirectory));

            return allInSequence(absoluteZipPaths, processSelectionDirectory);
        });
    })
    .then(function(dallesMetadata){
        //console.log('final result', dallesMetadata);

        var tilesMetadata = dallesMetadata.reduce(function(acc, tm){
            return acc.concat(tm)
        }, [])

        var nbObjects = tilesMetadata.reduce(function(acc, tm){
            return acc + Object.keys(tm.objects).length;
        }, 0)

        console.log('nb of objects', nbObjects);

        return writeFile(path.join(outAbsolutePath, 'metadata.json'), JSON.stringify(tilesMetadata));

    })
    .then(function(){

        console.timeEnd('all');

    })
    .fail(function(err){ console.error(err) });

<commit_msg>some corrections<commit_after>"use strict";

var path = require('path');
var fs = require('graceful-fs');

var program = require('commander');
var unzip = require('unzip');
var tmp = require('tmp');
var Q = require('q');
var Map = require('es6-map');

var containingCube = require('../src/containingCube.js');
var computeMeshVolume = require('../src/computeMeshVolume.js');
var _3dsFormatToAntsBinaryBuffer = require('../src/3dsFormatToAntsBinaryBuffer.js');
var parse3ds = require('../src/parse3ds.js');

var getTileAltitudes = require('./getTileAltitudes.js');
var tileAltitudesP = getTileAltitudes(path.resolve(process.cwd(), './data/DALLAGE_3D.csv'));

function tmpdir(){
    var def = Q.defer();

    tmp.dir(function(err, dir){
        if(err)
            def.reject(err);
        else
            def.resolve(dir);
    });

    return def.promise;
}
var readdir = Q.nfbind(fs.readdir);
var readFile = Q.nfbind(fs.readFile);
var writeFile = Q.nfbind(fs.writeFile);
var lstat = Q.nfbind(fs.lstat);

/*
    Takes an Array<T> and a function(x: T) => Promise<U>
    Processes each array element in sequence.
*/
function allInSequence(arr, f){
    if(arr.length === 0){
        var def = Q.defer();
        def.resolve([]);
        return def.promise;
    }

    if(arr.length === 1){
        return f(arr[0]).then(function(res){
            return [res];
        })
    }
    else{
        var first = arr[0];
        var tail = arr.slice(1);

        return f(first).then(function(res){
            return allInSequence(tail, f).then(function(tailRes){
                tailRes.push(res);
                return tailRes;
            });
        });
    }
}

program
    .version('0.0.1')
    .option('--zip [path]', 'path to zip file')
    .option('--out [path]', 'output directory')
    .parse(process.argv);

if(!program.zip || !program.out)
    throw new Error('--zip and --out parameters are compulsory');

var zipAbsolutePath = path.resolve(process.cwd(), program.zip);
var outAbsolutePath = path.resolve(process.cwd(), program.out);

console.log(zipAbsolutePath, outAbsolutePath);

// name => next nb to use to append to the name to make it unique
var ids = new Map();

// in : one archive
// out : a bunch of binaries + metadata

function extractBuildings(_3dsPath, x, y){
    var def = Q.defer();
    parse3ds(_3dsPath, function(err, data){
        if(err){
            def.reject(err);
            return;
        }

        var objects = data.getObjects();
        var meshes = objects.map(function(o){
            // Generate unique ids since the data producers may fail to do so
            var id;
            if(!ids.has(o.name)){
                id = o.name;
                ids.set(o.name, 1);
            }
            else{
                var nb = ids.get(o.name);
                id = o.name + '-' + nb;
                while(ids.has(id)){
                    nb++;
                    id = o.name + '-' + nb;
                }
                ids.set(o.name, nb+1);
            }

            return {
                id: id,
                vertices: o.meshes.vertices,
                faces: o.meshes.faces
            };

        });

        /*
            Working around https://github.com/anthill/bordeaux3d/issues/11
        */
        // looking for xXXXyYYY object. There is only one per 3ds file
        var xyObject = meshes.filter(function(m){ return !!m.id.match(/x(\d{1,4})y(\d{1,4})/) })[0];
        var xyContainingCube = containingCube(xyObject);

        // Ideally, xyContainingCube.xmax and xyContainingCube.ymax should be +100. Finding the translation.
        var deltaX = 100 - xyContainingCube.maxX;
        var deltaY = 100 - xyContainingCube.maxY;

        if(deltaX !== 0 || deltaY !== 0){
            // apply translation to all tile objects
            meshes.forEach(function(m){
                m.vertices.forEach(function(v){
                    v.x += deltaX;
                    v.y += deltaY;
                });
            });
        }

        tileAltitudesP.then(function(tilesAltitudes){
            var key = _3dsPath.match(/x\d{1,4}y\d{1,4}/)[0];

            var deltaZ = tilesAltitudes[key];

            meshes.forEach(function(m){
                m.vertices.forEach(function(v){
                    v.z += deltaZ;
                });
            });

            // Find tile bounding box
            var containingCubes = meshes.map(containingCube);
            // create a fake mesh based on the cubes descriptions
            var fakeCombiningMesh = {
                vertices: containingCubes.reduce(function(acc, cubeDesc){
                    acc.push({
                        x: cubeDesc.minX,
                        y: cubeDesc.minY,
                        z: cubeDesc.minZ,
                    });
                    acc.push({
                        x: cubeDesc.maxX,
                        y: cubeDesc.maxY,
                        z: cubeDesc.maxZ,
                    });

                    return acc;
                }, [])
            };
            var tileContainingCube = containingCube(fakeCombiningMesh);

            var minZ = Math.floor(tileContainingCube.minZ);
            var maxZ = Math.ceil(tileContainingCube.maxZ);
            if(minZ === maxZ){ // happens for flat floor objects
                maxZ = minZ + 1;
            }

            // integer approximation
            var tileMetadata = {
                X: x,
                Y: y,
                minX: Math.floor(tileContainingCube.minX),
                maxX: Math.ceil(tileContainingCube.maxX),
                minY: Math.floor(tileContainingCube.minY),
                maxY: Math.ceil(tileContainingCube.maxY),
                minZ: minZ,
                maxZ: maxZ,
                objects: Object.create(null)
            };

            var buildingBuffers = Object.create(null);
            meshes.forEach(function(m, i){
                try{
                buildingBuffers[m.id] = _3dsFormatToAntsBinaryBuffer(m, tileMetadata);
                }
                catch(e){
                    console.error('compacting error', x, y, e)
                }
            });

            meshes.forEach(function(m){

                // we get the type (either building or terrain)
                var objectType;
                // TODO with regexp
                if (m.id[0] === "x"){
                    objectType = "terrain";
                } else {
                    objectType = "building";
                }
                // for recentering
                var objectCube = containingCube(m);

                tileMetadata.objects[m.id] = {
                    x: Math.round( (objectCube.minX + objectCube.maxX)/2 ),
                    y: Math.round( (objectCube.minY + objectCube.maxY)/2 ),
                    type: objectType
                };
            });

            def.resolve({
                buildingBuffers: buildingBuffers,
                tileMetadata : tileMetadata
            });

        });

    });

    return def.promise;
}

function unzipInTmpDir(pathToZip){
    var readStream = fs.createReadStream(pathToZip);

    return tmpdir().then(function(tmpDir){
        var def = Q.defer();

        var extractWriteStream = unzip.Extract({ path: tmpDir })
        readStream.pipe(extractWriteStream);

        extractWriteStream.on('close', function(e){ // 'close' event, unlike 'finish' guarantees all writes to disk are finished
            //console.log('close');
            def.resolve(tmpDir);
        });

        extractWriteStream.on('error', function(err){
            def.reject(err);
        });

        extractWriteStream.on('finish', function(e){ // 'close' event, unlike 'finish' guarantees all writes to disk are finished
            console.log('finish', pathToZip, tmpDir);
            extractWriteStream.end();
        });

        return def.promise;
    });
}

/*
    Process the directory of a single selection
*/
function processSelectionDirectory(selectionZipDirPath){
    //console.log('processSelectionDirectory', selectionZipDirPath);

    var selectionName = path.basename(selectionZipDirPath, '.zip'); // assumed 2 letters like "RL"

    return unzipInTmpDir(selectionZipDirPath)
        .then(function(selectionDir){
            //console.log('unzipped', selectionName, selectionDir);

            var selectionPath = path.join(selectionDir, selectionName);
            return readdir(selectionPath)
                .then(function(selectionFiles){
                    //selectionFiles = selectionFiles.slice(0, 4);

                    //console.log("selectionFiles", selectionFiles);

                    var tile3dsPathsAndxy = selectionFiles
                        .filter(function(f){
                            return f.indexOf('.') === -1; // weak test
                        })
                        .map(function(f){
                            var matches = f.match(/x(\d{1,4})y(\d{1,4})/);

                            return {
                                x: parseInt(matches[1]),
                                y: parseInt(matches[2]),
                                path: path.join(selectionPath, f, f +'.3ds')
                            };
                        });
                    //console.log(name, tile3dsPaths);

                    return Q.all(tile3dsPathsAndxy.map(function(tpxy){
                        var tile3dsPath = tpxy.path;
                        var x = tpxy.x;
                        var y = tpxy.y;

                        return extractBuildings(tile3dsPath, x, y).then(function(res){
                            var buildingBuffers = res.buildingBuffers;
                            var tileMetadata = res.tileMetadata;

                            return Q.all(Object.keys(buildingBuffers).map(function(id){
                                var buildingOutPath = path.join(outAbsolutePath, id);

                                // despite graceful-fs, we see some EMFILE errors in writes
                                function tryWriteFile(){
                                    return writeFile(buildingOutPath, buildingBuffers[id]).fail(function(err){
                                        if(String(err).indexOf('EMFILE') !== -1){
                                            console.error('tryWriteFile EMFILE', selectionName, x, y, tile3dsPath, err)
                                            var def = Q.defer();

                                            // retry later
                                            setTimeout(function(){
                                                def.resolve(tryWriteFile());
                                            }, 100);

                                            return def.promise;
                                        }
                                        else{// forward error
                                            throw err;
                                        }
                                    })
                                }

                                return tryWriteFile();
                            })).then(function(){
                                // metadata is returned when all building binary files have been written
                                return tileMetadata;
                            });

                        }).fail(function(err){
                            console.error('extractBuildings error', selectionName, x, y, tile3dsPath, err);
                        });
                    }));
                });
        });

}

console.time('all');
console.time('extract');

unzipInTmpDir(zipAbsolutePath)
    .then(function(tmpDir){
        console.timeEnd('extract');

        return readdir(tmpDir).then(function(selectionZips){
            //console.log("selectionZips", selectionZips);

            //selectionZips = selectionZips.slice(0, 3);

            var absoluteZipPaths = selectionZips.map(function(zipPath){
                return path.join(tmpDir, zipPath);
            });

            //console.log(absoluteZipPaths);

            //return Q.all(absoluteZipPaths.map(processSelectionDirectory));

            return allInSequence(absoluteZipPaths, processSelectionDirectory);
        });
    })
    .then(function(dallesMetadata){
        //console.log('final result', dallesMetadata);

        var tilesMetadata = dallesMetadata.reduce(function(acc, tm){
            return acc.concat(tm)
        }, [])

        var nbObjects = tilesMetadata.reduce(function(acc, tm){
            return acc + Object.keys(tm.objects).length;
        }, 0)

        console.log('nb of objects', nbObjects);

        return writeFile(path.join(outAbsolutePath, 'metadata.json'), JSON.stringify(tilesMetadata));

    })
    .then(function(){

        console.timeEnd('all');

    })
    .fail(function(err){ console.error(err) });

<|endoftext|>


<commit_before>(function($, $TD, TD){
  //
  // Variable: Current highlighted column jQuery object.
  //
  var highlightedColumnEle;

  //
  // Variable: Currently highlighted tweet jQuery object.
  //
  var highlightedTweetEle;

  //
  // Function: Initializes TweetD*ck events. Called after the website app is loaded.
  //
  var initializeTweetDck = function(){
    // Settings button hook
    $("[data-action='settings-menu']").click(function(){
      setTimeout(function(){
        var menu = $(".js-dropdown-content").children("ul").first();
        if (menu.length === 0)return;

        menu.children(".drp-h-divider").last().after([
          '
'+$TD.brandName+' settings
',
          '
'+$TD.brandName+' plugins
',
          '

'
        ].join(""));

        var buttons = menu.children("[data-std]");

        buttons.on("click", "a", function(){
          var action = $(this).attr("data-action");

          if (action === "td-settings"){
            $TD.openSettingsMenu();
          }
          else if (action === "td-plugins"){
            $TD.openPluginsMenu();
          }
        });

        buttons.hover(function(){
          $(this).addClass("is-selected");
        }, function(){
          $(this).removeClass("is-selected");
        });
      }, 0);
    });

    // Notification handling
    $.subscribe("/notifications/new", function(obj){
      for(let index = obj.items.length-1; index >= 0; index--){
        onNewTweet(obj.column, obj.items[index]);
      }
    });

    // Setup video element replacement and fix missing target in user links
    new MutationObserver(function(){
      $("video").each(function(){
        $(this).parent().replaceWith("► Open video in browser");
      });

      $("a[rel='user']").attr("target", "_blank");
    }).observe($(".js-app-columns")[0], {
      childList: true,
      subtree: true
    });

    // Finish init and load plugins
    $TD.loadFontSizeClass(TD.settings.getFontSize());
    $TD.loadNotificationHeadContents(getNotificationHeadContents());

    window.TD_APP_READY = true;

    if (window.TD_PLUGINS){
      window.TD_PLUGINS.onReady();
    }
  };

  //
  // Function: Prepends code at the beginning of a function. If the prepended function returns true, execution of the original function is cancelled.
  //
  var prependToFunction = function(func, extension){
    return function(){
      return extension.apply(this, arguments) === true ? undefined : func.apply(this, arguments);
    };
  };

  //
  // Function: Appends code at the end of a function.
  //
  var appendToFunction = function(func, extension){
    return function(){
      var res = func.apply(this, arguments);
      extension.apply(this, arguments);
      return res;
    };
  };

  //
  // Function: Event callback for a new tweet.
  //
  var onNewTweet = function(column, tweet){
    if (column.model.getHasNotification()){
      var html = $(tweet.render({
        withFooter: false,
        withTweetActions: false,
        withMediaPreview: true,
        isMediaPreviewOff: true,
        isMediaPreviewSmall: false,
        isMediaPreviewLarge: false
      }));

      html.css("border", "0");
      html.find(".tweet-body").first().children("footer").remove();

      var url = html.find("time").first().children("a").first().attr("href") || "";

      $TD.onTweetPopup(html.html(), url, tweet.text.length); // TODO column
    }
    else if (column.model.getHasSound()){
      $TD.onTweetSound(); // TODO disable original
    }
  };

  //
  // Function: Retrieves the tags to be put into  for notification HTML code.
  //
  var getNotificationHeadContents = function(){
    var tags = [];

    $(document.head).children("link[rel='stylesheet'],meta[charset],meta[http-equiv]").each(function(){
      tags.push($(this)[0].outerHTML);
    });

    return tags.join("");
  };

  //
  // Block: Observe the app
 element and initialize TweetD*ck whenever possible.
  //
  var app = $("body").children(".js-app");

  new MutationObserver(function(){
    if (window.TD_APP_READY && app.hasClass("is-hidden")){
      window.TD_APP_READY = false;
    }
    else if (!window.TD_APP_READY && !app.hasClass("is-hidden")){
      initializeTweetDck();
    }
  }).observe(app[0], {
    attributes: true,
    attributeFilter: [ "class" ]
  });

  //
  // Block: Hook into settings object to detect when the settings change.
  //
  TD.settings.setFontSize = appendToFunction(TD.settings.setFontSize, function(name){
    $TD.loadFontSizeClass(name);
  });

  TD.settings.setTheme = appendToFunction(TD.settings.setTheme, function(){
    setTimeout(function(){
      $TD.loadNotificationHeadContents(getNotificationHeadContents());
    }, 0);
  });

  //
  // Block: Force popup notification settings.
  //
  TD.controller.notifications.hasNotifications = function(){
    return true;
  };

  TD.controller.notifications.isPermissionGranted = function(){
    return true;
  };

  //
  // Block: Expand shortened links on hover or display tooltip.
  //
  (function(){
    var cutStart = function(str, search){
      return str.startsWith(search) ? str.substr(search.length) : str;
    };

    var prevMouseX = -1, prevMouseY = -1;
    var tooltipTimer, tooltipDisplayed;

    $(document.body).delegate("a[data-full-url]", "mouseenter mouseleave mousemove", function(e){
      var me = $(this);

      if (e.type === "mouseenter"){
        var text = me.text();

        if (text.charCodeAt(text.length-1) !== 8230){ // horizontal ellipsis
          return;
        }

        if ($TD.expandLinksOnHover){
          tooltipTimer = window.setTimeout(function(){
            var expanded = me.attr("data-full-url");
            expanded = cutStart(expanded, "https://");
            expanded = cutStart(expanded, "http://");
            expanded = cutStart(expanded, "www.");

            me.attr("td-prev-text", text);
            me.text(expanded);
          }, 200);
        }
        else{
          tooltipTimer = window.setTimeout(function(){
            $TD.displayTooltip(me.attr("data-full-url"), false);
            tooltipDisplayed = true;
          }, 400);
        }
      }
      else if (e.type === "mouseleave"){
        if ($TD.expandLinksOnHover){
          var prevText = me.attr("td-prev-text");

          if (prevText){
            me.text(prevText);
          }
        }

        window.clearTimeout(tooltipTimer);

        if (tooltipDisplayed){
          tooltipDisplayed = false;
          $TD.displayTooltip(null, false);
        }
      }
      else if (e.type === "mousemove"){
        if (tooltipDisplayed && (prevMouseX !== e.clientX || prevMouseY !== e.clientY)){
          $TD.displayTooltip(me.attr("data-full-url"), false);
          prevMouseX = e.clientX;
          prevMouseY = e.clientY;
        }
      }
    });
  })();

  //
  // Block: Allow bypassing of t.co in context menus.
  //
  $(document.body).delegate("a", "contextmenu", function(){
    $TD.setLastRightClickedLink($(this).attr("data-full-url") || "");
  });

  //
  // Block: Hook into the notification sound effect.
  //
  (function(){
    var soundEle = document.getElementById("update-sound");

    soundEle.play = prependToFunction(soundEle.play, function(){
      return $TD.muteNotifications;
    });
  })();

  /* TODO document.getElementById("update-sound").play = function(){
    $TD.onTweetSound();
  };*/

  //
  // Block: Update highlighted column.
  //
  app.delegate("section", "mouseenter mouseleave", function(e){
    if (e.type === "mouseenter"){
      highlightedColumnEle = $(this);
    }
    else if (e.type === "mouseleave"){
      highlightedColumnEle = null;
    }
  });

  //
  // Block: Copy tweet address and update highlighted tweet.
  //
  (function(){
    var lastTweet = "";

    var updateHighlightedTweet = function(link, embeddedLink){
      if (lastTweet !== link){
        $TD.setLastHighlightedTweet(link, embeddedLink);
        lastTweet = link;
      }
    };

    app.delegate("article.js-stream-item", "mouseenter mouseleave", function(e){
      if (e.type === "mouseenter"){
        highlightedTweetEle = $(this);

        var link = $(this).find("time").first().children("a").first();
        var embedded = $(this).find(".quoted-tweet[data-tweet-id]").first();

        updateHighlightedTweet(link.length > 0 ? link.attr("href") : "", embedded.length > 0 ? embedded.find(".account-link").first().attr("href")+"/status/"+embedded.attr("data-tweet-id") : "");
      }
      else if (e.type === "mouseleave"){
        highlightedTweetEle = null;
        updateHighlightedTweet("", "");
      }
    });
  })();

  //
  // Block: Paste images when tweeting.
  //
  (function(){
    var lastPasteElement;
    var prevScrollTop;

    var getScroller = function(){
      return $(".js-drawer").find(".js-compose-scroller").first().children().first();
    };

    var clickUpload = function(){
      var button = $(".js-add-image-button").first();

      var scroller = getScroller();
      prevScrollTop = scroller.scrollTop();

      scroller.scrollTop(0);
      scroller.scrollTop(button.offset().top); // scrolls the button into view

      var buttonPos = button.children().first().offset(); // finds the camera icon offset
      $TD.clickUploadImage(Math.floor(buttonPos.left), Math.floor(buttonPos.top));
    };

    $(".js-app").delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(){
      lastPasteElement = $(this);
      $TD.tryPasteImage();
    });

    window.TDGF_tryPasteImage = function(){
      if (lastPasteElement){
        var parent = lastPasteElement.parent();

        if (parent.siblings(".js-add-image-button").length === 0){
          var pop = parent.closest(".js-inline-reply,.rpl").find(".js-inline-compose-pop,.js-reply-popout");

          if (pop.length === 0){
            lastPasteElement = null;
            return;
          }

          pop.click();

          var drawer = $(".js-drawer");
          var counter = 0;

          var interval = setInterval(function(){
            if (drawer.offset().left >= 195){
              clickUpload();
              clearInterval(interval);
            }
            else if (++counter >= 10){
              clearInterval(interval);
            }
          }, 51);
        }
        else{
          clickUpload();
        }

        lastPasteElement = null;
      }
    };

    window.TDGF_tryPasteImageFinish = function(){
      setTimeout(function(){
        getScroller().scrollTop(prevScrollTop);
        $(".js-drawer").find(".js-compose-text").first()[0].focus();
      }, 10);
    };
  })();

  //
  // Block: Support for extra mouse buttons.
  //
  window.TDGF_onMouseClickExtra = function(button){
    if (button === 1){ // back button
      var inlineComposer, drawerComposer, modal;

      if ((modal = $("#open-modal")).is(":visible")){
        modal.find("a[rel=dismiss]").click();
      }
      else if ((inlineComposer = $(".js-inline-compose-close")).length === 1){
        inlineComposer.click();
      }
      else if (highlightedColumnEle && highlightedColumnEle.closest(".js-column").is(".is-shifted-1")){
        highlightedColumnEle.find(".js-column-back").first().click();
      }
      else if ((drawerComposer = $(".js-app-content.is-open .js-drawer-close:visible")).length === 1){
        drawerComposer.click();
      }
      else{
        $(".js-column-back").click();
      }
    }
    else if (button === 2){ // forward button
      if (highlightedTweetEle){
        highlightedTweetEle.children().first().click();
      }
    }
  };

  //
  // Block: Fix scheduled tweets not showing up sometimes.
  //
  $(document).on("dataTweetSent", function(e, data){
    if (data.response.state && data.response.state === "scheduled"){
      var column = Object.values(TD.controller.columnManager.getAll()).find(column => column.model.state.type === "scheduled");

      if (column){
        setTimeout(function(){
          column.reloadTweets();
        }, 1000);
      }
    }
  });

  //
  // Block: Inject custom CSS and layout into the page.
  //
  (function(){
    var styleOfficial = document.createElement("style");
    document.head.appendChild(styleOfficial);

    styleOfficial.sheet.insertRule("a[data-full-url] { word-break: break-all; }", 0); // break long urls
    styleOfficial.sheet.insertRule(".column-nav-link .attribution { position: absolute; }", 0); // fix cut off account names
    styleOfficial.sheet.insertRule(".txt-base-smallest .badge-verified:before { height: 13px !important; }", 0); // fix cut off badge icon
    styleOfficial.sheet.insertRule(".keyboard-shortcut-list { vertical-align: top; }", 0); // fix keyboard navigation alignment

    if ($TD.hasCustomBrowserCSS){
      var styleCustom = document.createElement("style");
      styleCustom.innerHTML = $TD.customBrowserCSS;
      document.head.appendChild(styleCustom);
    }
  })();
})($, $TD, TD);
<commit_msg>Remove unnecessary code from notification html builder<commit_after>(function($, $TD, TD){
  //
  // Variable: Current highlighted column jQuery object.
  //
  var highlightedColumnEle;

  //
  // Variable: Currently highlighted tweet jQuery object.
  //
  var highlightedTweetEle;

  //
  // Function: Initializes TweetD*ck events. Called after the website app is loaded.
  //
  var initializeTweetDck = function(){
    // Settings button hook
    $("[data-action='settings-menu']").click(function(){
      setTimeout(function(){
        var menu = $(".js-dropdown-content").children("ul").first();
        if (menu.length === 0)return;

        menu.children(".drp-h-divider").last().after([
          '
'+$TD.brandName+' settings
',
          '
'+$TD.brandName+' plugins
',
          '

'
        ].join(""));

        var buttons = menu.children("[data-std]");

        buttons.on("click", "a", function(){
          var action = $(this).attr("data-action");

          if (action === "td-settings"){
            $TD.openSettingsMenu();
          }
          else if (action === "td-plugins"){
            $TD.openPluginsMenu();
          }
        });

        buttons.hover(function(){
          $(this).addClass("is-selected");
        }, function(){
          $(this).removeClass("is-selected");
        });
      }, 0);
    });

    // Notification handling
    $.subscribe("/notifications/new", function(obj){
      for(let index = obj.items.length-1; index >= 0; index--){
        onNewTweet(obj.column, obj.items[index]);
      }
    });

    // Setup video element replacement and fix missing target in user links
    new MutationObserver(function(){
      $("video").each(function(){
        $(this).parent().replaceWith("► Open video in browser");
      });

      $("a[rel='user']").attr("target", "_blank");
    }).observe($(".js-app-columns")[0], {
      childList: true,
      subtree: true
    });

    // Finish init and load plugins
    $TD.loadFontSizeClass(TD.settings.getFontSize());
    $TD.loadNotificationHeadContents(getNotificationHeadContents());

    window.TD_APP_READY = true;

    if (window.TD_PLUGINS){
      window.TD_PLUGINS.onReady();
    }
  };

  //
  // Function: Prepends code at the beginning of a function. If the prepended function returns true, execution of the original function is cancelled.
  //
  var prependToFunction = function(func, extension){
    return function(){
      return extension.apply(this, arguments) === true ? undefined : func.apply(this, arguments);
    };
  };

  //
  // Function: Appends code at the end of a function.
  //
  var appendToFunction = function(func, extension){
    return function(){
      var res = func.apply(this, arguments);
      extension.apply(this, arguments);
      return res;
    };
  };

  //
  // Function: Event callback for a new tweet.
  //
  var onNewTweet = function(column, tweet){
    if (column.model.getHasNotification()){
      var html = $(tweet.render({
        withFooter: false,
        withTweetActions: false,
        withMediaPreview: true,
        isMediaPreviewOff: true,
        isMediaPreviewSmall: false,
        isMediaPreviewLarge: false
      }));

      html.css("border", "0");

      var url = html.find("time").first().children("a").first().attr("href") || "";

      $TD.onTweetPopup(html.html(), url, tweet.text.length); // TODO column
    }
    else if (column.model.getHasSound()){
      $TD.onTweetSound(); // TODO disable original
    }
  };

  //
  // Function: Retrieves the tags to be put into  for notification HTML code.
  //
  var getNotificationHeadContents = function(){
    var tags = [];

    $(document.head).children("link[rel='stylesheet'],meta[charset],meta[http-equiv]").each(function(){
      tags.push($(this)[0].outerHTML);
    });

    return tags.join("");
  };

  //
  // Block: Observe the app
 element and initialize TweetD*ck whenever possible.
  //
  var app = $("body").children(".js-app");

  new MutationObserver(function(){
    if (window.TD_APP_READY && app.hasClass("is-hidden")){
      window.TD_APP_READY = false;
    }
    else if (!window.TD_APP_READY && !app.hasClass("is-hidden")){
      initializeTweetDck();
    }
  }).observe(app[0], {
    attributes: true,
    attributeFilter: [ "class" ]
  });

  //
  // Block: Hook into settings object to detect when the settings change.
  //
  TD.settings.setFontSize = appendToFunction(TD.settings.setFontSize, function(name){
    $TD.loadFontSizeClass(name);
  });

  TD.settings.setTheme = appendToFunction(TD.settings.setTheme, function(){
    setTimeout(function(){
      $TD.loadNotificationHeadContents(getNotificationHeadContents());
    }, 0);
  });

  //
  // Block: Force popup notification settings.
  //
  TD.controller.notifications.hasNotifications = function(){
    return true;
  };

  TD.controller.notifications.isPermissionGranted = function(){
    return true;
  };

  //
  // Block: Expand shortened links on hover or display tooltip.
  //
  (function(){
    var cutStart = function(str, search){
      return str.startsWith(search) ? str.substr(search.length) : str;
    };

    var prevMouseX = -1, prevMouseY = -1;
    var tooltipTimer, tooltipDisplayed;

    $(document.body).delegate("a[data-full-url]", "mouseenter mouseleave mousemove", function(e){
      var me = $(this);

      if (e.type === "mouseenter"){
        var text = me.text();

        if (text.charCodeAt(text.length-1) !== 8230){ // horizontal ellipsis
          return;
        }

        if ($TD.expandLinksOnHover){
          tooltipTimer = window.setTimeout(function(){
            var expanded = me.attr("data-full-url");
            expanded = cutStart(expanded, "https://");
            expanded = cutStart(expanded, "http://");
            expanded = cutStart(expanded, "www.");

            me.attr("td-prev-text", text);
            me.text(expanded);
          }, 200);
        }
        else{
          tooltipTimer = window.setTimeout(function(){
            $TD.displayTooltip(me.attr("data-full-url"), false);
            tooltipDisplayed = true;
          }, 400);
        }
      }
      else if (e.type === "mouseleave"){
        if ($TD.expandLinksOnHover){
          var prevText = me.attr("td-prev-text");

          if (prevText){
            me.text(prevText);
          }
        }

        window.clearTimeout(tooltipTimer);

        if (tooltipDisplayed){
          tooltipDisplayed = false;
          $TD.displayTooltip(null, false);
        }
      }
      else if (e.type === "mousemove"){
        if (tooltipDisplayed && (prevMouseX !== e.clientX || prevMouseY !== e.clientY)){
          $TD.displayTooltip(me.attr("data-full-url"), false);
          prevMouseX = e.clientX;
          prevMouseY = e.clientY;
        }
      }
    });
  })();

  //
  // Block: Allow bypassing of t.co in context menus.
  //
  $(document.body).delegate("a", "contextmenu", function(){
    $TD.setLastRightClickedLink($(this).attr("data-full-url") || "");
  });

  //
  // Block: Hook into the notification sound effect.
  //
  (function(){
    var soundEle = document.getElementById("update-sound");

    soundEle.play = prependToFunction(soundEle.play, function(){
      return $TD.muteNotifications;
    });
  })();

  /* TODO document.getElementById("update-sound").play = function(){
    $TD.onTweetSound();
  };*/

  //
  // Block: Update highlighted column.
  //
  app.delegate("section", "mouseenter mouseleave", function(e){
    if (e.type === "mouseenter"){
      highlightedColumnEle = $(this);
    }
    else if (e.type === "mouseleave"){
      highlightedColumnEle = null;
    }
  });

  //
  // Block: Copy tweet address and update highlighted tweet.
  //
  (function(){
    var lastTweet = "";

    var updateHighlightedTweet = function(link, embeddedLink){
      if (lastTweet !== link){
        $TD.setLastHighlightedTweet(link, embeddedLink);
        lastTweet = link;
      }
    };

    app.delegate("article.js-stream-item", "mouseenter mouseleave", function(e){
      if (e.type === "mouseenter"){
        highlightedTweetEle = $(this);

        var link = $(this).find("time").first().children("a").first();
        var embedded = $(this).find(".quoted-tweet[data-tweet-id]").first();

        updateHighlightedTweet(link.length > 0 ? link.attr("href") : "", embedded.length > 0 ? embedded.find(".account-link").first().attr("href")+"/status/"+embedded.attr("data-tweet-id") : "");
      }
      else if (e.type === "mouseleave"){
        highlightedTweetEle = null;
        updateHighlightedTweet("", "");
      }
    });
  })();

  //
  // Block: Paste images when tweeting.
  //
  (function(){
    var lastPasteElement;
    var prevScrollTop;

    var getScroller = function(){
      return $(".js-drawer").find(".js-compose-scroller").first().children().first();
    };

    var clickUpload = function(){
      var button = $(".js-add-image-button").first();

      var scroller = getScroller();
      prevScrollTop = scroller.scrollTop();

      scroller.scrollTop(0);
      scroller.scrollTop(button.offset().top); // scrolls the button into view

      var buttonPos = button.children().first().offset(); // finds the camera icon offset
      $TD.clickUploadImage(Math.floor(buttonPos.left), Math.floor(buttonPos.top));
    };

    $(".js-app").delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(){
      lastPasteElement = $(this);
      $TD.tryPasteImage();
    });

    window.TDGF_tryPasteImage = function(){
      if (lastPasteElement){
        var parent = lastPasteElement.parent();

        if (parent.siblings(".js-add-image-button").length === 0){
          var pop = parent.closest(".js-inline-reply,.rpl").find(".js-inline-compose-pop,.js-reply-popout");

          if (pop.length === 0){
            lastPasteElement = null;
            return;
          }

          pop.click();

          var drawer = $(".js-drawer");
          var counter = 0;

          var interval = setInterval(function(){
            if (drawer.offset().left >= 195){
              clickUpload();
              clearInterval(interval);
            }
            else if (++counter >= 10){
              clearInterval(interval);
            }
          }, 51);
        }
        else{
          clickUpload();
        }

        lastPasteElement = null;
      }
    };

    window.TDGF_tryPasteImageFinish = function(){
      setTimeout(function(){
        getScroller().scrollTop(prevScrollTop);
        $(".js-drawer").find(".js-compose-text").first()[0].focus();
      }, 10);
    };
  })();

  //
  // Block: Support for extra mouse buttons.
  //
  window.TDGF_onMouseClickExtra = function(button){
    if (button === 1){ // back button
      var inlineComposer, drawerComposer, modal;

      if ((modal = $("#open-modal")).is(":visible")){
        modal.find("a[rel=dismiss]").click();
      }
      else if ((inlineComposer = $(".js-inline-compose-close")).length === 1){
        inlineComposer.click();
      }
      else if (highlightedColumnEle && highlightedColumnEle.closest(".js-column").is(".is-shifted-1")){
        highlightedColumnEle.find(".js-column-back").first().click();
      }
      else if ((drawerComposer = $(".js-app-content.is-open .js-drawer-close:visible")).length === 1){
        drawerComposer.click();
      }
      else{
        $(".js-column-back").click();
      }
    }
    else if (button === 2){ // forward button
      if (highlightedTweetEle){
        highlightedTweetEle.children().first().click();
      }
    }
  };

  //
  // Block: Fix scheduled tweets not showing up sometimes.
  //
  $(document).on("dataTweetSent", function(e, data){
    if (data.response.state && data.response.state === "scheduled"){
      var column = Object.values(TD.controller.columnManager.getAll()).find(column => column.model.state.type === "scheduled");

      if (column){
        setTimeout(function(){
          column.reloadTweets();
        }, 1000);
      }
    }
  });

  //
  // Block: Inject custom CSS and layout into the page.
  //
  (function(){
    var styleOfficial = document.createElement("style");
    document.head.appendChild(styleOfficial);

    styleOfficial.sheet.insertRule("a[data-full-url] { word-break: break-all; }", 0); // break long urls
    styleOfficial.sheet.insertRule(".column-nav-link .attribution { position: absolute; }", 0); // fix cut off account names
    styleOfficial.sheet.insertRule(".txt-base-smallest .badge-verified:before { height: 13px !important; }", 0); // fix cut off badge icon
    styleOfficial.sheet.insertRule(".keyboard-shortcut-list { vertical-align: top; }", 0); // fix keyboard navigation alignment

    if ($TD.hasCustomBrowserCSS){
      var styleCustom = document.createElement("style");
      styleCustom.innerHTML = $TD.customBrowserCSS;
      document.head.appendChild(styleCustom);
    }
  })();
})($, $TD, TD);
<|endoftext|>