<commit_before>import {FlowRouter} from 'meteor/kadira:flow-router';
import {Ladders} from '/lib/collections.js';
import {Matches} from '/lib/collections.js';
import {Pairings} from '/lib/collections.js';
import {Players} from '/lib/collections.js';
import {Queue} from '/lib/queue.js';
import {ReactiveDict} from 'meteor/reactive-dict';
import {Result} from '/lib/result.js';
import {Setups} from '/lib/collections.js';
import {SeedsOptions} from '/lib/seeds.js';
import {StandingsSelector} from '/lib/standings.js';
import {TimeSync} from 'meteor/mizzao:timesync';
import {Template} from 'meteor/templating';

import format from 'date-fns/format';
import {standingsSortFn} from '/lib/standings.js';

import './ladderPage.html';

Template.ladderPage.onCreated(function() {
  this.dict = new ReactiveDict();
  this.dict.set('poolIndex', 0);
  const handle = this.subscribe('ladders');
  this.slug = () => FlowRouter.getParam('slug');

  this.autorun(() => {
    if (handle.ready()) {
      const ladder = Ladders.findOne({slug: this.slug()});
      if (ladder) {
        this.dict.set('name', ladder.name);
        this.dict.set('id', ladder._id);

        this.subscribe('players', ladder._id);
        this.subscribe('pairings', ladder._id);
        this.subscribe('setups', ladder._id);
        this.subscribe('matches', ladder._id);
      }
    }
  });
});

Template.ladderPage.helpers({
  'ladderName': function() {
    return Template.instance().dict.get('name');
  },

  'started': function() {
    const ladder = Ladders.findOne(Template.instance().dict.get('id'));
    return ladder ? ladder.started : false;
  },

  'smashggImport': function() {
    const smashggImport = Template.instance().dict.get('smashggImport');
    return smashggImport ? {
      name: smashggImport.name,
      pool: smashggImport.pools[Template.instance().dict.get('poolIndex')],
    } : undefined;
  },

  'players': function() {
    return Players.find({}, SeedsOptions);
  },

  'getSeed': function(seed) {
    return seed < Number.MAX_SAFE_INTEGER ? seed + 1 : undefined;
  },

  'selected': function(thisTier, selectedTier) {
    if (thisTier === selectedTier) {
      return 'selected';
    }
  },

  'unqueued': function() {
    return Players.find({queue: Queue.NONE}).fetch().sort((a, b) => {
      if ((a.games > 0 && b.games > 0) ||
          (a.games === 0 && b.games === 0)) {
        return a.queueTime - b.queueTime;
      }
      if (a.games === 0) {
        return 1;
      }
      if (b.games === 0) {
        return -1;
      }
    });
  },

  'matchmaking': function() {
    return Players.find(
        {queue: Queue.MATCHMAKING}, {sort: [['queueTime', 'asc']]});
  },

  'waiting': function() {
    return Pairings.find(
        {queue: Queue.WAITING}, {sort: [['queueTime', 'asc']]});
  },

  'playing': function() {
    return Pairings.find(
        {queue: Queue.PLAYING}, {sort: [['queueTime', 'asc']]});
  },

  'standings': function() {
    return Players.find({queue: Queue.FROZEN}, {sort: [['queueTime', 'asc']]})
        .fetch()
        .concat(Players.find(StandingsSelector).fetch().sort(standingsSortFn));
  },

  'setups': function() {
    return Setups.find({}, {sort: [['number', 'asc']]});
  },

  'matches': function() {
    return Matches.find({}, {sort: [['time', 'desc']]});
  },

  'hasBonuses': function(bonuses, games) {
    return bonuses > games;
  },

  'isFrozen': function(queue) {
    return queue === Queue.FROZEN;
  },

  'shouldHighlight': function(queueTime) {
    return TimeSync.serverTime(undefined, 10000) - queueTime < 120000;
  },

  'elapsedTimeString': function(queueTime) {
    return format(TimeSync.serverTime(undefined, 1000) - queueTime, 'm:ss');
  },

  'durationString': function(durationMs) {
    return format(durationMs, 'm:ss');
  },

  'percentiles': function() {
    const PERCENTILES = [10, 50, 90];
    const durations = Matches.find({}, {sort: [['duration', 'asc']]})
        .map((match) => {
          return match.duration;
        });
    const length = durations.length;

    const percentileFn = function(p) {
      if (length === 0) return 0;
      if (p <= 0) return durations[0];
      if (p >= 100) return durations[length - 1];

      const index = (length - 1) * p / 100;
      const lower = Math.floor(index);
      const upper = lower + 1;
      const weight = index % 1;
      if (upper >= length) {
        return format(duration[lower], 'm:ss');
      }
      const ms = durations[lower] * (1 - weight) + durations[upper] * weight;
      return format(ms, 'm:ss');
    };

    return PERCENTILES.map((p) => {
      const kString = 'p' + p;
      return {k: kString, v: percentileFn(p)};
    });
  },

  'prettifyResults': function(results) {
    return results.reduce((resultsStr, result) => {
      if (result === Result.WIN) {
        return resultsStr + 'W';
      } else {
        return resultsStr + 'L';
      }
    }, "");
  },

  'open': function() {
    const ladder = Ladders.findOne(Template.instance().dict.get('id'));
    return ladder ? !ladder.closed : false;
  }
});

Template.ladderPage.events({
  'submit .addPlayer': function(event, templateInstance) {
    const playerName = event.target.playerName;
    Meteor.call('addPlayer', templateInstance.dict.get('id'), playerName.value);
    playerName.value = '';
    return false;
  },

  'click .removePlayer': function(event, templateInstance) {
    Meteor.call(
        'removePlayer', templateInstance.dict.get('id'), event.target.value);
  },

  'click .addSetup': function(event, templateInstance) {
    Meteor.call('addSetup', templateInstance.dict.get('id'));
  },

  'click .removeSetup': function(event, templateInstance) {
    Meteor.call(
        'removeSetup', templateInstance.dict.get('id'), event.target.value);
  },

  'submit .importFromSmashgg': function(event, templateInstance) {
    Meteor.call('importFromSmashgg', event.target.slug.value, (err, res) => {
      if (err || !res) {
        if (err.error === 'NOT_FOUND') {
          res = {
            name: 'Slug: "' + event.target.slug.value + '" not found.',
            pools: [],
          };
        } else {
          res = {
            name: err.reason + ': ' + err.details,
            pools: [],
          };
        }
      }
      templateInstance.dict.set('smashggImport', res);
    });
    return false;
  },

  'click .addAll': function(event, templateInstance) {
    const smashggImport = templateInstance.dict.get('smashggImport');
    if (!smashggImport) {
      return;
    }
    const entrants =
        smashggImport.pools[templateInstance.dict.get('poolIndex')].entrants;
    if (entrants.length === 0) {
      return;
    }

    const ladderId = templateInstance.dict.get('id');
    Meteor.call('addAllPlayers', ladderId, entrants, (err) => {
      if (!err) {
        templateInstance.dict.set('smashggImport', undefined);
        templateInstance.dict.set('poolIndex', 0);
      }
    });
  },

  'click .incPoolIndex': function(event, templateInstance) {
    const smashggImport = templateInstance.dict.get('smashggImport');
    if (!smashggImport) {
      return;
    }
    let nextPoolIndex = templateInstance.dict.get('poolIndex') + 1;
    if (nextPoolIndex >= smashggImport.pools.length) {
      nextPoolIndex = 0;
    }
    templateInstance.dict.set('poolIndex', nextPoolIndex);
  },

  'click .raiseSeed': function(event, templateInstance) {
    Meteor.call(
        'raiseSeed', templateInstance.dict.get('id'), event.target.value);
  },

  'click .lowerSeed': function(event, templateInstance) {
    Meteor.call(
        'lowerSeed', templateInstance.dict.get('id'), event.target.value);
  },

  'change .tier': function(event, templateInstance) {
    const value = event.target.value;
    const tier = value && parseInt(value) ? parseInt(value): 0;
    Meteor.call(
        'setTier', templateInstance.dict.get('id'), event.target.name, tier);
  },

  'click .startLadder': function(event, templateInstance) {
    Meteor.call('startLadder', templateInstance.dict.get('id'));
  },

  'click .closeLadder': function(event, templateInstance) {
    Meteor.call('setLadderClosed', templateInstance.dict.get('id'), true);
  },

  'click .openLadder': function(event, templateInstance) {
    Meteor.call('setLadderClosed', templateInstance.dict.get('id'), false);
  },

  'click .queuePlayer': function(event, templateInstance) {
    Meteor.call(
        'queuePlayer', templateInstance.dict.get('id'), event.target.value);
  },

  'click .unqueueMatchmaking': function(event, templateInstance) {
    Meteor.call(
        'unqueueFromMatchmaking',
        templateInstance.dict.get('id'),
        event.target.value);
  },

  'submit .unqueueWaiting': function(event, templateInstance) {
    if (event.target.quitter.value === 'player1') {
      Meteor.call(
          'unqueueFromWaiting',
          templateInstance.dict.get('id'),
          event.target.pairingId.value, 1);
    } else if (event.target.quitter.value === 'player2') {
      Meteor.call(
          'unqueueFromWaiting',
          templateInstance.dict.get('id'),
          event.target.pairingId.value, 2);
    }
    return false;
  },

  'submit .winPlayer': function(event, templateInstance) {
    if (event.target.winner.value === 'player1') {
      Meteor.call(
          'submitWinner',
          templateInstance.dict.get('id'),
          event.target.pairingId.value,
          1);
    } else if (event.target.winner.value === 'player2') {
      Meteor.call(
          'submitWinner',
          templateInstance.dict.get('id'),
          event.target.pairingId.value,
          2);
    }
    return false;
  },

  'click .fixMatch': function(event, templateInstance) {
    Meteor.call(
        'fixMatch', templateInstance.dict.get('id'), event.target.value);
  },

  'click .freezePlayer': function(event, templateInstance) {
    Meteor.call(
        'cullPlayer', templateInstance.dict.get('id'), event.target.value);
  },

  'click .unfreezePlayer': function(event, templateInstance) {
    Meteor.call(
        'reinstatePlayer', templateInstance.dict.get('id'), event.target.value);
  },
});
<commit_msg>Add a simple JSON match export<commit_after>import {FlowRouter} from 'meteor/kadira:flow-router';
import {Ladders} from '/lib/collections.js';
import {Matches} from '/lib/collections.js';
import {Pairings} from '/lib/collections.js';
import {Players} from '/lib/collections.js';
import {Queue} from '/lib/queue.js';
import {ReactiveDict} from 'meteor/reactive-dict';
import {Result} from '/lib/result.js';
import {Setups} from '/lib/collections.js';
import {SeedsOptions} from '/lib/seeds.js';
import {StandingsSelector} from '/lib/standings.js';
import {TimeSync} from 'meteor/mizzao:timesync';
import {Template} from 'meteor/templating';

import format from 'date-fns/format';
import {standingsSortFn} from '/lib/standings.js';

import './ladderPage.html';

Template.ladderPage.onCreated(function() {
  this.dict = new ReactiveDict();
  this.dict.set('poolIndex', 0);
  const handle = this.subscribe('ladders');
  this.slug = () => FlowRouter.getParam('slug');

  this.autorun(() => {
    if (handle.ready()) {
      const ladder = Ladders.findOne({slug: this.slug()});
      if (ladder) {
        this.dict.set('name', ladder.name);
        this.dict.set('id', ladder._id);

        this.subscribe('players', ladder._id);
        this.subscribe('pairings', ladder._id);
        this.subscribe('setups', ladder._id);
        this.subscribe('matches', ladder._id);
      }
    }
  });
});

Template.ladderPage.helpers({
  'ladderName': function() {
    return Template.instance().dict.get('name');
  },

  'started': function() {
    const ladder = Ladders.findOne(Template.instance().dict.get('id'));
    return ladder ? ladder.started : false;
  },

  'smashggImport': function() {
    const smashggImport = Template.instance().dict.get('smashggImport');
    return smashggImport ? {
      name: smashggImport.name,
      pool: smashggImport.pools[Template.instance().dict.get('poolIndex')],
    } : undefined;
  },

  'players': function() {
    return Players.find({}, SeedsOptions);
  },

  'getSeed': function(seed) {
    return seed < Number.MAX_SAFE_INTEGER ? seed + 1 : undefined;
  },

  'selected': function(thisTier, selectedTier) {
    if (thisTier === selectedTier) {
      return 'selected';
    }
  },

  'unqueued': function() {
    return Players.find({queue: Queue.NONE}).fetch().sort((a, b) => {
      if ((a.games > 0 && b.games > 0) ||
          (a.games === 0 && b.games === 0)) {
        return a.queueTime - b.queueTime;
      }
      if (a.games === 0) {
        return 1;
      }
      if (b.games === 0) {
        return -1;
      }
    });
  },

  'matchmaking': function() {
    return Players.find(
        {queue: Queue.MATCHMAKING}, {sort: [['queueTime', 'asc']]});
  },

  'waiting': function() {
    return Pairings.find(
        {queue: Queue.WAITING}, {sort: [['queueTime', 'asc']]});
  },

  'playing': function() {
    return Pairings.find(
        {queue: Queue.PLAYING}, {sort: [['queueTime', 'asc']]});
  },

  'standings': function() {
    return Players.find({queue: Queue.FROZEN}, {sort: [['queueTime', 'asc']]})
        .fetch()
        .concat(Players.find(StandingsSelector).fetch().sort(standingsSortFn));
  },

  'setups': function() {
    return Setups.find({}, {sort: [['number', 'asc']]});
  },

  'matches': function() {
    return Matches.find({}, {sort: [['time', 'desc']]});
  },

  'matchesObj': function() {
    return JSON.stringify(Matches.find({}, {sort: [['time', 'asc']]}).fetch().map((match) => {
      return {
        winner: match.winnerName,
        loser: match.loserName,
        time: match.time,
      };
    }));
  },

  'hasBonuses': function(bonuses, games) {
    return bonuses > games;
  },

  'isFrozen': function(queue) {
    return queue === Queue.FROZEN;
  },

  'shouldHighlight': function(queueTime) {
    return TimeSync.serverTime(undefined, 10000) - queueTime < 120000;
  },

  'elapsedTimeString': function(queueTime) {
    return format(TimeSync.serverTime(undefined, 1000) - queueTime, 'm:ss');
  },

  'durationString': function(durationMs) {
    return format(durationMs, 'm:ss');
  },

  'percentiles': function() {
    const PERCENTILES = [10, 50, 90];
    const durations = Matches.find({}, {sort: [['duration', 'asc']]})
        .map((match) => {
          return match.duration;
        });
    const length = durations.length;

    const percentileFn = function(p) {
      if (length === 0) return 0;
      if (p <= 0) return durations[0];
      if (p >= 100) return durations[length - 1];

      const index = (length - 1) * p / 100;
      const lower = Math.floor(index);
      const upper = lower + 1;
      const weight = index % 1;
      if (upper >= length) {
        return format(duration[lower], 'm:ss');
      }
      const ms = durations[lower] * (1 - weight) + durations[upper] * weight;
      return format(ms, 'm:ss');
    };

    return PERCENTILES.map((p) => {
      const kString = 'p' + p;
      return {k: kString, v: percentileFn(p)};
    });
  },

  'prettifyResults': function(results) {
    return results.reduce((resultsStr, result) => {
      if (result === Result.WIN) {
        return resultsStr + 'W';
      } else {
        return resultsStr + 'L';
      }
    }, "");
  },

  'open': function() {
    const ladder = Ladders.findOne(Template.instance().dict.get('id'));
    return ladder ? !ladder.closed : false;
  }
});

Template.ladderPage.events({
  'submit .addPlayer': function(event, templateInstance) {
    const playerName = event.target.playerName;
    Meteor.call('addPlayer', templateInstance.dict.get('id'), playerName.value);
    playerName.value = '';
    return false;
  },

  'click .removePlayer': function(event, templateInstance) {
    Meteor.call(
        'removePlayer', templateInstance.dict.get('id'), event.target.value);
  },

  'click .addSetup': function(event, templateInstance) {
    Meteor.call('addSetup', templateInstance.dict.get('id'));
  },

  'click .removeSetup': function(event, templateInstance) {
    Meteor.call(
        'removeSetup', templateInstance.dict.get('id'), event.target.value);
  },

  'submit .importFromSmashgg': function(event, templateInstance) {
    Meteor.call('importFromSmashgg', event.target.slug.value, (err, res) => {
      if (err || !res) {
        if (err.error === 'NOT_FOUND') {
          res = {
            name: 'Slug: "' + event.target.slug.value + '" not found.',
            pools: [],
          };
        } else {
          res = {
            name: err.reason + ': ' + err.details,
            pools: [],
          };
        }
      }
      templateInstance.dict.set('smashggImport', res);
    });
    return false;
  },

  'click .addAll': function(event, templateInstance) {
    const smashggImport = templateInstance.dict.get('smashggImport');
    if (!smashggImport) {
      return;
    }
    const entrants =
        smashggImport.pools[templateInstance.dict.get('poolIndex')].entrants;
    if (entrants.length === 0) {
      return;
    }

    const ladderId = templateInstance.dict.get('id');
    Meteor.call('addAllPlayers', ladderId, entrants, (err) => {
      if (!err) {
        templateInstance.dict.set('smashggImport', undefined);
        templateInstance.dict.set('poolIndex', 0);
      }
    });
  },

  'click .incPoolIndex': function(event, templateInstance) {
    const smashggImport = templateInstance.dict.get('smashggImport');
    if (!smashggImport) {
      return;
    }
    let nextPoolIndex = templateInstance.dict.get('poolIndex') + 1;
    if (nextPoolIndex >= smashggImport.pools.length) {
      nextPoolIndex = 0;
    }
    templateInstance.dict.set('poolIndex', nextPoolIndex);
  },

  'click .raiseSeed': function(event, templateInstance) {
    Meteor.call(
        'raiseSeed', templateInstance.dict.get('id'), event.target.value);
  },

  'click .lowerSeed': function(event, templateInstance) {
    Meteor.call(
        'lowerSeed', templateInstance.dict.get('id'), event.target.value);
  },

  'change .tier': function(event, templateInstance) {
    const value = event.target.value;
    const tier = value && parseInt(value) ? parseInt(value): 0;
    Meteor.call(
        'setTier', templateInstance.dict.get('id'), event.target.name, tier);
  },

  'click .startLadder': function(event, templateInstance) {
    Meteor.call('startLadder', templateInstance.dict.get('id'));
  },

  'click .closeLadder': function(event, templateInstance) {
    Meteor.call('setLadderClosed', templateInstance.dict.get('id'), true);
  },

  'click .openLadder': function(event, templateInstance) {
    Meteor.call('setLadderClosed', templateInstance.dict.get('id'), false);
  },

  'click .queuePlayer': function(event, templateInstance) {
    Meteor.call(
        'queuePlayer', templateInstance.dict.get('id'), event.target.value);
  },

  'click .unqueueMatchmaking': function(event, templateInstance) {
    Meteor.call(
        'unqueueFromMatchmaking',
        templateInstance.dict.get('id'),
        event.target.value);
  },

  'submit .unqueueWaiting': function(event, templateInstance) {
    if (event.target.quitter.value === 'player1') {
      Meteor.call(
          'unqueueFromWaiting',
          templateInstance.dict.get('id'),
          event.target.pairingId.value, 1);
    } else if (event.target.quitter.value === 'player2') {
      Meteor.call(
          'unqueueFromWaiting',
          templateInstance.dict.get('id'),
          event.target.pairingId.value, 2);
    }
    return false;
  },

  'submit .winPlayer': function(event, templateInstance) {
    if (event.target.winner.value === 'player1') {
      Meteor.call(
          'submitWinner',
          templateInstance.dict.get('id'),
          event.target.pairingId.value,
          1);
    } else if (event.target.winner.value === 'player2') {
      Meteor.call(
          'submitWinner',
          templateInstance.dict.get('id'),
          event.target.pairingId.value,
          2);
    }
    return false;
  },

  'click .fixMatch': function(event, templateInstance) {
    Meteor.call(
        'fixMatch', templateInstance.dict.get('id'), event.target.value);
  },

  'click .freezePlayer': function(event, templateInstance) {
    Meteor.call(
        'cullPlayer', templateInstance.dict.get('id'), event.target.value);
  },

  'click .unfreezePlayer': function(event, templateInstance) {
    Meteor.call(
        'reinstatePlayer', templateInstance.dict.get('id'), event.target.value);
  },
});
<|endoftext|>


<commit_before>Template.form_visits.helpers ({

    patients: function() {
        return patients.find().fetch();
    },

    patientLookup: function(patientId) {
        if ( (patientId == null) && globalPatientIdForNewVisit ) {
          $('#input_patientId option:selected').val(globalPatientIdForNewVisit);
          patientId = globalPatientIdForNewVisit;
          globalPatientIdForNewVisit = '';
        }
        if (patientId) {
            return patients.findOne(patientId).name + ' ' + patients.findOne(patientId).surname;
        } else {
            return "";
        }
    },

    patientIdFixer: function(patientId) {
        if ( (patientId == null) && globalPatientIdForNewVisit ) {
            return globalPatientIdForNewVisit
        } else {
            return patientId
        }
    }

});

Template.form_visits.onRendered(function () {

    $('.datepicker').pickadate({
      format: 'yyyy-mm-dd',
      selectMonths: true, // Creates a dropdown to control month
      selectYears: 50 // Creates a dropdown of 15 years to control year
    });

    $('.timepicker').pickatime({
      autoclose: false,
      twelvehour: false
    });

    if ($('.multiple-select option:selected').val() == "") {
        $('.multiple-select option:selected').prop('disabled', true);
    }

    $(document).ready(function() {
      $('select').material_select();
    });

    if (Router.current().params._id) {
        $('.datepicker').val(visits.findOne(Router.current().params._id).visitDate);
    }

    $('img.anatomic-map').maphilight();

});

Template.form_visits.events ({

    'click area#head': function () {
        $('div.anatomic-map').remove();
        $( '' ).insertAfter( "input#input_anatomicalLocation" );
        $('img.anatomic-map').maphilight();
    }

});

Template.form_patients.onRendered(function () {

    $('.datepicker').pickadate({
      format: 'yyyy-mm-dd',
      selectMonths: true, // Creates a dropdown to control month
      selectYears: 250 // Creates a dropdown of 15 years to control year
    });

    $(document).ready(function() {
      $('select').material_select();
    });

    $('.datepicker').val(patients.findOne(Router.current().params._id).birthDate);

});

Template.index.onRendered(function () {

    scheduler.config.multi_day = true;
	var pizza_size = [
		{ key: 1, label: 'Small' },
		{ key: 2, label: 'Medium' },
		{ key: 3, label: 'Large' }
	];

	scheduler.locale.labels.section_text = 'Text';
	scheduler.locale.labels.section_checkbox = 'Checkbox';
	scheduler.locale.labels.section_radiobutton = 'Radiobutton';
	scheduler.locale.labels.section_select = 'Select';
	scheduler.locale.labels.section_template = 'Template';

	scheduler.config.lightbox.sections = [
		{ name: "text", height: 50, map_to: "text", type: "textarea", focus: true },
		{ name: "checkbox", map_to: "single_checkbox", type: "checkbox", checked_value: "registrable", unchecked_value: "unchecked" },
		{ name: "radiobutton", height: 58, options: pizza_size, map_to: "radiobutton_option", type: "radio", vertical: true },
		{ name: "select", height: 21, map_to: "type", type: "select", options: pizza_size },
		{ name: "template", height: 21, map_to: "text", type: "template" },
		{ name: "time", height: 72, type: "calendar_time", map_to: "auto" },
		{ name: "time", height: 72, type: "time", map_to: "auto"}
	];

	scheduler.config.full_day = true;
    //scheduler.config.first_hour = 7;
    //scheduler.config.last_hour = 19;

    scheduler.xy.scale_height=30;

    scheduler.config.dblclick_create = false

    scheduler.config.xml_date="%Y-%m-%d %H:%i";
    scheduler.init("scheduler_here", new Date());
    scheduler.load("./visits.xml");

});
<commit_msg>Image preloading added.<commit_after>Template.form_visits.helpers ({

    patients: function() {
        return patients.find().fetch();
    },

    patientLookup: function(patientId) {
        if ( (patientId == null) && globalPatientIdForNewVisit ) {
          $('#input_patientId option:selected').val(globalPatientIdForNewVisit);
          patientId = globalPatientIdForNewVisit;
          globalPatientIdForNewVisit = '';
        }
        if (patientId) {
            return patients.findOne(patientId).name + ' ' + patients.findOne(patientId).surname;
        } else {
            return "";
        }
    },

    patientIdFixer: function(patientId) {
        if ( (patientId == null) && globalPatientIdForNewVisit ) {
            return globalPatientIdForNewVisit
        } else {
            return patientId
        }
    }

});

Template.form_visits.onRendered(function () {

    $('.datepicker').pickadate({
      format: 'yyyy-mm-dd',
      selectMonths: true, // Creates a dropdown to control month
      selectYears: 50 // Creates a dropdown of 15 years to control year
    });

    $('.timepicker').pickatime({
      autoclose: false,
      twelvehour: false
    });

    if ($('.multiple-select option:selected').val() == "") {
        $('.multiple-select option:selected').prop('disabled', true);
    }

    $(document).ready(function() {
      $('select').material_select();
    });

    if (Router.current().params._id) {
        $('.datepicker').val(visits.findOne(Router.current().params._id).visitDate);
    }

    $('img.anatomic-map').maphilight();

    var images = new Array()
	function preload() {
		for (i = 0; i < preload.arguments.length; i++) {
			images[i] = new Image()
			images[i].src = preload.arguments[i]
		}
	}
    preload('/image-map/male-mesh/head.png');

});

Template.form_visits.events ({

    'click area#head': function () {
        $('div.anatomic-map').remove();
        $( '' ).insertAfter( "input#input_anatomicalLocation" );
        $('img.anatomic-map').maphilight();
    }

});

Template.form_patients.onRendered(function () {

    $('.datepicker').pickadate({
      format: 'yyyy-mm-dd',
      selectMonths: true, // Creates a dropdown to control month
      selectYears: 250 // Creates a dropdown of 15 years to control year
    });

    $(document).ready(function() {
      $('select').material_select();
    });

    $('.datepicker').val(patients.findOne(Router.current().params._id).birthDate);

});

Template.index.onRendered(function () {

    scheduler.config.multi_day = true;
	var pizza_size = [
		{ key: 1, label: 'Small' },
		{ key: 2, label: 'Medium' },
		{ key: 3, label: 'Large' }
	];

	scheduler.locale.labels.section_text = 'Text';
	scheduler.locale.labels.section_checkbox = 'Checkbox';
	scheduler.locale.labels.section_radiobutton = 'Radiobutton';
	scheduler.locale.labels.section_select = 'Select';
	scheduler.locale.labels.section_template = 'Template';

	scheduler.config.lightbox.sections = [
		{ name: "text", height: 50, map_to: "text", type: "textarea", focus: true },
		{ name: "checkbox", map_to: "single_checkbox", type: "checkbox", checked_value: "registrable", unchecked_value: "unchecked" },
		{ name: "radiobutton", height: 58, options: pizza_size, map_to: "radiobutton_option", type: "radio", vertical: true },
		{ name: "select", height: 21, map_to: "type", type: "select", options: pizza_size },
		{ name: "template", height: 21, map_to: "text", type: "template" },
		{ name: "time", height: 72, type: "calendar_time", map_to: "auto" },
		{ name: "time", height: 72, type: "time", map_to: "auto"}
	];

	scheduler.config.full_day = true;
    //scheduler.config.first_hour = 7;
    //scheduler.config.last_hour = 19;

    scheduler.xy.scale_height=30;

    scheduler.config.dblclick_create = false

    scheduler.config.xml_date="%Y-%m-%d %H:%i";
    scheduler.init("scheduler_here", new Date());
    scheduler.load("./visits.xml");

});
<|endoftext|>


<commit_before>import React from 'react';

export default class Home extends React.Component
{
    render() {
        return (
Home
) }
}
<commit_msg>show welcome message on home<commit_after>import React from 'react';

export default class Home extends React.Component
{
    render() {
        var username = window.APP_DATA.authed ? window.APP_DATA.username : 'guest';
        return (
Welcome, {username}
) }
}
<|endoftext|>


<commit_before>var app = require('ampersand-app');
var View = require('ampersand-view');
var PageView = require('./base');
var templates = require('../templates');
var Widget = require('../views/widget');

var widgetSelectorItemView = View.extend({
    template: '<button type="button" class="btn btn-default" data-hook="item"></button>',
    bindings: {
        'model.type': {
            type: 'text',
            hook: 'item'
        },
    },
    events: {
        'click [data-hook~=item]':    'handleClick',
    },
    handleClick:  function () {
        var w = new Widget({model: this.model});
        this.parent.renderSubview(w, this.parent.queryByHook('widgets') );
    },
});

module.exports = PageView.extend({
    pageTitle: 'more info',
    template: templates.pages.info,

    initialize: function() {
        this.collection = app.widgets;
    },

    subviews: {},

    render: function() {
        this.renderWithTemplate(this);
        this.renderCollection(app.widgets, widgetSelectorItemView, this.queryByHook('widget-selector'));
        return this;
    }
});
<commit_msg>Apply naming convention<commit_after>var app = require('ampersand-app');
var View = require('ampersand-view');
var PageView = require('./base');
var templates = require('../templates');
var widgetView = require('../views/widget');

var widgetSelectorItemView = View.extend({
    template: '<button type="button" class="btn btn-default" data-hook="item"></button>',
    bindings: {
        'model.type': {
            type: 'text',
            hook: 'item'
        },
    },
    events: {
        'click [data-hook~=item]':    'handleClick',
    },
    handleClick:  function () {
        var w = new widgetView({model: this.model});
        this.parent.renderSubview(w, this.parent.queryByHook('widgets') );
    },
});

module.exports = PageView.extend({
    pageTitle: 'more info',
    template: templates.pages.info,

    initialize: function() {
        this.collection = app.widgets;
    },

    subviews: {},

    render: function() {
        this.renderWithTemplate(this);
        this.renderCollection(app.widgets, widgetSelectorItemView, this.queryByHook('widget-selector'));
        return this;
    }
});
<|endoftext|>


<commit_before>var Batch = require('batch');
var debounce = require('debounce');
var geocode = require('geocode');
var Journey = require('journey');
var Location = require('location');
var log = require('./client/log')('plan');
var defaults = require('model-defaults');
var model = require('model');
var qs = require('querystring');

var loadPlan = require('./load');
var store = require('./store');
var updateRoutes = require('./update-routes');

/**
 * Debounce updates to once every 50ms
 */

var DEBOUNCE_UPDATES = 25;

/**
 * Expose `Plan`
 */

var Plan = module.exports = model('Plan')
  .use(defaults({
    bike: true,
    bus: true,
    car: true,
    days: 'M—F',
    end_time: 9,
    from: '',
    from_valid: false,
    loading: true,
    options: [],
    start_time: 7,
    to: '',
    to_valid: false,
    train: true,
    tripsPerYear: 235,
    walk: true
  }))
  .attr('bike')
  .attr('bus')
  .attr('car')
  .attr('days')
  .attr('end_time')
  .attr('from')
  .attr('from_id')
  .attr('from_ll')
  .attr('from_valid')
  .attr('loading')
  .attr('journey')
  .attr('options')
  .attr('scorer')
  .attr('start_time')
  .attr('to')
  .attr('to_id')
  .attr('to_ll')
  .attr('to_valid')
  .attr('train')
  .attr('tripsPerYear')
  .attr('walk');

/**
 * Expose `load`
 */

module.exports.load = function(ctx, next) {
  loadPlan(Plan, ctx, next);
};

/**
 * Sync plans with localStorage
 */

Plan.on('change', function(plan, name, val) {
  log('plan.%s changed to %s', name, val);

  // Store in localStorage & track the change
  if (name !== 'options' && name !== 'journey' && name !== 'loading') plan.store();
});

/**
 * Keep start/end times in sync
 */

Plan.on('change start_time', function(plan, val, prev) {
  if (val >= plan.end_time()) plan.end_time(val + 1);
});

Plan.on('change end_time', function(plan, val, prev) {
  if (val <= plan.start_time()) plan.start_time(val - 1);
});

/**
 * Update routes. Restrict to once every 25ms.
 */

Plan.prototype.updateRoutes = debounce(function(opts, callback) {
  updateRoutes(this, opts, callback);
}, DEBOUNCE_UPDATES);

/**
 * Geocode
 */

Plan.prototype.geocode = function(dest, callback) {
  if (!callback) callback = function() {};

  var plan = this;
  var address = plan[dest]();
  var ll = plan[dest + '_ll']();
  if (address && address.length > 0) {
    geocode(address, function(err, ll) {
      if (err) {
        callback(err);
      } else {
        plan[dest + '_ll'](ll);
        callback(null, ll);
      }
    });
  } else {
    callback(null, ll);
  }
};

/**
 * Save Journey
 */

Plan.prototype.saveJourney = function(callback) {
  var opts = {};
  var skipKeys = ['options', 'journey', 'scorer'];
  for (var key in this.attrs) {
    if (skipKeys.indexOf(key) !== -1 || key.indexOf('to') === 0 || key.indexOf('from') === 0) {
      continue;
    }
    opts[key] = this.attrs[key];
  }

  // Create new journey
  var journey = new Journey({
    locations: [{
      _id: this.from_id()
    }, {
      _id: this.to_id()
    }],
    opts: opts
  });

  // Save
  journey.save(callback);
};

/**
 * Valid coordinates
 */

Plan.prototype.validCoordinates = function() {
  return this.coordinateIsValid(this.from_ll()) && this.coordinateIsValid(this.to_ll());
};

/**
 * Set Address
 */

Plan.prototype.setAddress = function(name, address, callback) {
  callback = callback || function() {}; // noop callback
  var location = new Location();
  var plan = this;
  var c = address.split(',');
  var isCoordinate = c.length === 2 && !isNaN(parseFloat(c[0])) && !isNaN(parseFloat(c[1]));

  if (!address || address.length < 1) return callback();

  if (isCoordinate) {
    location.coordinate({
      lat: parseFloat(c[1]),
      lng: parseFloat(c[0])
    });
  } else {
    location.address(address);
  }

  location.save(function(err, res) {
    if (err) {
      callback(err);
    } else {
      var changes = {};
      if (isCoordinate)
        changes[name] = res.body.address + ', ' + res.body.city + ', ' + res.body.state;
      else
        changes[name] = address;

      changes[name + '_ll'] = res.body.coordinate;
      changes[name + '_id'] = res.body._id;
      changes[name + '_valid'] = true;

      plan.set(changes);
      callback(null, res.body);
    }
  });
};

/**
 * Set both addresses
 */

Plan.prototype.setAddresses = function(from, to, callback) {
  // Initialize the default locations
  var plan = this;

  Batch()
    .push(function(done) {
      plan.setAddress('from', from, done);
    })
    .push(function(done) {
      plan.setAddress('to', to, done);
    }).end(callback);
};

/**
 * Rescore Options
 */

Plan.prototype.rescoreOptions = function() {
  var scorer = this.scorer();
  var options = this.options();

  options.forEach(function(o) {
    o.rescore(scorer);
  });

  this.store();
};

/**
 * To Lower Case
 */

function toLowerCase(s) {
  return s ? s.toLowerCase() : '';
}

Plan.prototype.coordinateIsValid = function(c) {
  return !!c && !!parseFloat(c.lat) && !!parseFloat(c.lng);
};

/**
 * Modes as a CSV
 */

Plan.prototype.modesCSV = function() {
  var modes = [];
  if (this.bike()) modes.push('BICYCLE');
  if (this.bus()) modes.push('BUS');
  if (this.train()) modes.push('TRAINISH');
  if (this.walk()) modes.push('WALK');
  if (this.car()) modes.push('CAR');

  return modes.join(',');
};

/**
 * Set modes from string
 */

Plan.prototype.setModes = function(csv) {
  if (!csv || csv.length < 1) return;

  this.bike(csv.indexOf('BICYCLE') !== -1);
  this.bus(csv.indexOf('BUS') !== -1);
  this.train(csv.indexOf('TRAINISH') !== -1);
  this.car(csv.indexOf('CAR') !== -1);
};

/**
 * Generate Query Parameters for this plan
 */

Plan.prototype.generateQuery = function() {
  var from = this.from_ll() || {};
  var to = this.to_ll() || {};

  // Transit modes
  var accessModes = ['WALK'];
  var directModes = ['CAR', 'WALK'];
  var egressModes = ['WALK'];
  var transitModes = [];

  if (this.bike()) {
    accessModes.push('BICYCLE');
    directModes.push('BICYCLE');
  }
  if (this.bus()) transitModes.push('BUS');
  if (this.car()) accessModes.push('CAR');
  if (this.train()) transitModes.push('TRAINISH');

  var startTime = this.start_time();
  var endTime = this.end_time();
  var scorer = this.scorer();

  // Convert the hours into strings
  startTime += ':00';
  endTime = endTime === 24 ? '23:59' : endTime + ':00';

  return {
    accessModes: accessModes.join(','),
    bikeSpeed: scorer.rates.bikeSpeed,
    date: this.nextDate(),
    directModes: directModes.join(','),
    egressModes: egressModes.join(','),
    endTime: endTime,
    from: {
      lat: from.lat,
      lon: from.lng,
      name: 'From'
    },
    startTime: startTime,
    to: {
      lat: to.lat,
      lon: to.lng,
      name: 'To'
    },
    limit: 3,
    transitModes: transitModes.join(','),
    walkSpeed: scorer.rates.walkSpeed
  };
};

/**
 * Store in localStorage. Restrict this I/O to once every 25ms.
 */

Plan.prototype.store = debounce(function() {
  store(this);
}, DEBOUNCE_UPDATES);

/**
 * Clear localStorage
 */

Plan.prototype.clearStore = store.clear;

/**
 * Save URL
 */

Plan.prototype.saveURL = function() {
  window.history.replaceState(null, '', '/planner?' + this.generateQueryString());
};

/**
 * Get next date for day of the week
 */

Plan.prototype.nextDate = function() {
  var now = new Date();
  var date = now.getDate();
  var dayOfTheWeek = now.getDay();
  switch (this.days()) {
    case 'M—F':
      if (dayOfTheWeek === 0) now.setDate(date + 1);
      if (dayOfTheWeek === 6) now.setDate(date + 2);
      break;
    case 'Sat':
      now.setDate(date + (6 - dayOfTheWeek));
      break;
    case 'Sun':
      now.setDate(date + (7 - dayOfTheWeek));
      break;
  }
  return now.toISOString().split('T')[0];
};

/**
 * Generate `places` for transitive
 */

Plan.prototype.generatePlaces = function() {
  var fll = this.from_ll();
  var tll = this.to_ll();
  if (!fll || !tll) return [];

  return [{
    place_id: 'from',
    place_lat: fll.lat,
    place_lon: fll.lng,
    place_name: 'From'
  }, {
    place_id: 'to',
    place_lat: tll.lat,
    place_lon: tll.lng,
    place_name: 'To'
  }];
};

/**
 * Generate QueryString
 */

Plan.prototype.generateQueryString = function() {
  return qs.stringify({
    from: this.from(),
    to: this.to(),
    modes: this.modesCSV(),
    start_time: this.start_time(),
    end_time: this.end_time(),
    days: this.days()
  });
};
<commit_msg>Set the default limit to 2<commit_after>var Batch = require('batch');
var debounce = require('debounce');
var geocode = require('geocode');
var Journey = require('journey');
var Location = require('location');
var log = require('./client/log')('plan');
var defaults = require('model-defaults');
var model = require('model');
var qs = require('querystring');

var loadPlan = require('./load');
var store = require('./store');
var updateRoutes = require('./update-routes');

/**
 * Debounce updates to once every 50ms
 */

var DEBOUNCE_UPDATES = 25;
var LIMIT = 2;

/**
 * Expose `Plan`
 */

var Plan = module.exports = model('Plan')
  .use(defaults({
    bike: true,
    bus: true,
    car: true,
    days: 'M—F',
    end_time: 9,
    from: '',
    from_valid: false,
    loading: true,
    options: [],
    start_time: 7,
    to: '',
    to_valid: false,
    train: true,
    tripsPerYear: 235,
    walk: true
  }))
  .attr('bike')
  .attr('bus')
  .attr('car')
  .attr('days')
  .attr('end_time')
  .attr('from')
  .attr('from_id')
  .attr('from_ll')
  .attr('from_valid')
  .attr('loading')
  .attr('journey')
  .attr('options')
  .attr('scorer')
  .attr('start_time')
  .attr('to')
  .attr('to_id')
  .attr('to_ll')
  .attr('to_valid')
  .attr('train')
  .attr('tripsPerYear')
  .attr('walk');

/**
 * Expose `load`
 */

module.exports.load = function(ctx, next) {
  loadPlan(Plan, ctx, next);
};

/**
 * Sync plans with localStorage
 */

Plan.on('change', function(plan, name, val) {
  log('plan.%s changed to %s', name, val);

  // Store in localStorage & track the change
  if (name !== 'options' && name !== 'journey' && name !== 'loading') plan.store();
});

/**
 * Keep start/end times in sync
 */

Plan.on('change start_time', function(plan, val, prev) {
  if (val >= plan.end_time()) plan.end_time(val + 1);
});

Plan.on('change end_time', function(plan, val, prev) {
  if (val <= plan.start_time()) plan.start_time(val - 1);
});

/**
 * Update routes. Restrict to once every 25ms.
 */

Plan.prototype.updateRoutes = debounce(function(opts, callback) {
  updateRoutes(this, opts, callback);
}, DEBOUNCE_UPDATES);

/**
 * Geocode
 */

Plan.prototype.geocode = function(dest, callback) {
  if (!callback) callback = function() {};

  var plan = this;
  var address = plan[dest]();
  var ll = plan[dest + '_ll']();
  if (address && address.length > 0) {
    geocode(address, function(err, ll) {
      if (err) {
        callback(err);
      } else {
        plan[dest + '_ll'](ll);
        callback(null, ll);
      }
    });
  } else {
    callback(null, ll);
  }
};

/**
 * Save Journey
 */

Plan.prototype.saveJourney = function(callback) {
  var opts = {};
  var skipKeys = ['options', 'journey', 'scorer'];
  for (var key in this.attrs) {
    if (skipKeys.indexOf(key) !== -1 || key.indexOf('to') === 0 || key.indexOf('from') === 0) {
      continue;
    }
    opts[key] = this.attrs[key];
  }

  // Create new journey
  var journey = new Journey({
    locations: [{
      _id: this.from_id()
    }, {
      _id: this.to_id()
    }],
    opts: opts
  });

  // Save
  journey.save(callback);
};

/**
 * Valid coordinates
 */

Plan.prototype.validCoordinates = function() {
  return this.coordinateIsValid(this.from_ll()) && this.coordinateIsValid(this.to_ll());
};

/**
 * Set Address
 */

Plan.prototype.setAddress = function(name, address, callback) {
  callback = callback || function() {}; // noop callback
  var location = new Location();
  var plan = this;
  var c = address.split(',');
  var isCoordinate = c.length === 2 && !isNaN(parseFloat(c[0])) && !isNaN(parseFloat(c[1]));

  if (!address || address.length < 1) return callback();

  if (isCoordinate) {
    location.coordinate({
      lat: parseFloat(c[1]),
      lng: parseFloat(c[0])
    });
  } else {
    location.address(address);
  }

  location.save(function(err, res) {
    if (err) {
      callback(err);
    } else {
      var changes = {};
      if (isCoordinate)
        changes[name] = res.body.address + ', ' + res.body.city + ', ' + res.body.state;
      else
        changes[name] = address;

      changes[name + '_ll'] = res.body.coordinate;
      changes[name + '_id'] = res.body._id;
      changes[name + '_valid'] = true;

      plan.set(changes);
      callback(null, res.body);
    }
  });
};

/**
 * Set both addresses
 */

Plan.prototype.setAddresses = function(from, to, callback) {
  // Initialize the default locations
  var plan = this;

  Batch()
    .push(function(done) {
      plan.setAddress('from', from, done);
    })
    .push(function(done) {
      plan.setAddress('to', to, done);
    }).end(callback);
};

/**
 * Rescore Options
 */

Plan.prototype.rescoreOptions = function() {
  var scorer = this.scorer();
  var options = this.options();

  options.forEach(function(o) {
    o.rescore(scorer);
  });

  this.store();
};

/**
 * To Lower Case
 */

function toLowerCase(s) {
  return s ? s.toLowerCase() : '';
}

Plan.prototype.coordinateIsValid = function(c) {
  return !!c && !!parseFloat(c.lat) && !!parseFloat(c.lng);
};

/**
 * Modes as a CSV
 */

Plan.prototype.modesCSV = function() {
  var modes = [];
  if (this.bike()) modes.push('BICYCLE');
  if (this.bus()) modes.push('BUS');
  if (this.train()) modes.push('TRAINISH');
  if (this.walk()) modes.push('WALK');
  if (this.car()) modes.push('CAR');

  return modes.join(',');
};

/**
 * Set modes from string
 */

Plan.prototype.setModes = function(csv) {
  if (!csv || csv.length < 1) return;

  this.bike(csv.indexOf('BICYCLE') !== -1);
  this.bus(csv.indexOf('BUS') !== -1);
  this.train(csv.indexOf('TRAINISH') !== -1);
  this.car(csv.indexOf('CAR') !== -1);
};

/**
 * Generate Query Parameters for this plan
 */

Plan.prototype.generateQuery = function() {
  var from = this.from_ll() || {};
  var to = this.to_ll() || {};

  // Transit modes
  var accessModes = ['WALK'];
  var directModes = ['CAR', 'WALK'];
  var egressModes = ['WALK'];
  var transitModes = [];

  if (this.bike()) {
    accessModes.push('BICYCLE');
    directModes.push('BICYCLE');
  }
  if (this.bus()) transitModes.push('BUS');
  if (this.car()) accessModes.push('CAR');
  if (this.train()) transitModes.push('TRAINISH');

  var startTime = this.start_time();
  var endTime = this.end_time();
  var scorer = this.scorer();

  // Convert the hours into strings
  startTime += ':00';
  endTime = endTime === 24 ? '23:59' : endTime + ':00';

  return {
    accessModes: accessModes.join(','),
    bikeSpeed: scorer.rates.bikeSpeed,
    date: this.nextDate(),
    directModes: directModes.join(','),
    egressModes: egressModes.join(','),
    endTime: endTime,
    from: {
      lat: from.lat,
      lon: from.lng,
      name: 'From'
    },
    startTime: startTime,
    to: {
      lat: to.lat,
      lon: to.lng,
      name: 'To'
    },
    limit: LIMIT,
    transitModes: transitModes.join(','),
    walkSpeed: scorer.rates.walkSpeed
  };
};

/**
 * Store in localStorage. Restrict this I/O to once every 25ms.
 */

Plan.prototype.store = debounce(function() {
  store(this);
}, DEBOUNCE_UPDATES);

/**
 * Clear localStorage
 */

Plan.prototype.clearStore = store.clear;

/**
 * Save URL
 */

Plan.prototype.saveURL = function() {
  window.history.replaceState(null, '', '/planner?' + this.generateQueryString());
};

/**
 * Get next date for day of the week
 */

Plan.prototype.nextDate = function() {
  var now = new Date();
  var date = now.getDate();
  var dayOfTheWeek = now.getDay();
  switch (this.days()) {
    case 'M—F':
      if (dayOfTheWeek === 0) now.setDate(date + 1);
      if (dayOfTheWeek === 6) now.setDate(date + 2);
      break;
    case 'Sat':
      now.setDate(date + (6 - dayOfTheWeek));
      break;
    case 'Sun':
      now.setDate(date + (7 - dayOfTheWeek));
      break;
  }
  return now.toISOString().split('T')[0];
};

/**
 * Generate `places` for transitive
 */

Plan.prototype.generatePlaces = function() {
  var fll = this.from_ll();
  var tll = this.to_ll();
  if (!fll || !tll) return [];

  return [{
    place_id: 'from',
    place_lat: fll.lat,
    place_lon: fll.lng,
    place_name: 'From'
  }, {
    place_id: 'to',
    place_lat: tll.lat,
    place_lon: tll.lng,
    place_name: 'To'
  }];
};

/**
 * Generate QueryString
 */

Plan.prototype.generateQueryString = function() {
  return qs.stringify({
    from: this.from(),
    to: this.to(),
    modes: this.modesCSV(),
    start_time: this.start_time(),
    end_time: this.end_time(),
    days: this.days()
  });
};
<|endoftext|>


<commit_before>const $ = query => document.getElementById(query);
const $$ = query => document.body.querySelector(query);
const isURL = text => /^((https?:\/\/|www)[^\s]+)/g.test(text.toLowerCase());
window.isDownloadSupported = (typeof document.createElement('a').download !== 'undefined');
window.isProductionEnvironment = !window.location.host.startsWith('localhost');

class PeersUI {

    constructor() {
        Events.on('peer-joined', e => this._onPeerJoined(e.detail));
        Events.on('peer-left', e => this._onPeerLeft(e.detail));
        Events.on('peers', e => this._onPeers(e.detail));
        Events.on('file-progress', e => this._onFileProgress(e.detail));
    }

    _onPeerJoined(peer) {
        if (document.getElementById(peer.id)) return;
        const peerUI = new PeerUI(peer);
        $$('x-peers').appendChild(peerUI.$el);
    }

    _onPeers(peers) {
        this._clearPeers();
        peers.forEach(peer => this._onPeerJoined(peer));
    }

    _onPeerLeft(peerId) {
        const $peer = $(peerId);
        if (!$peer) return;
        $peer.remove();
    }

    _onFileProgress(progress) {
        const peerId = progress.sender || progress.recipient;
        const $peer = $(peerId);
        if (!$peer) return;
        $peer.ui.setProgress(progress.progress);
    }

    _clearPeers() {
        const $peers = $$('x-peers').innerHTML = '';
    }
}

class PeerUI {

    html() {
        return `

                <input type="file" multiple>
                <x-icon shadow="1">
                    <svg class="icon"><use xlink:href="#"/></svg>
                </x-icon>

            `
    }

    constructor(peer) {
        this._peer = peer;
        this._initDom();
        this._bindListeners(this.$el);
    }

    _initDom() {
        const el = document.createElement('x-peer');
        el.id = this._peer.id;
        el.innerHTML = this.html();
        el.ui = this;
        el.querySelector('svg use').setAttribute('xlink:href', this._icon());
        el.querySelector('.name').textContent = this._name();
        this.$el = el;
        this.$progress = el.querySelector('.progress');
    }

    _bindListeners(el) {
        el.querySelector('input').addEventListener('change', e => this._onFilesSelected(e));
        el.addEventListener('drop', e => this._onDrop(e));
        el.addEventListener('dragend', e => this._onDragEnd(e));
        el.addEventListener('dragleave', e => this._onDragEnd(e));
        el.addEventListener('dragover', e => this._onDragOver(e));
        el.addEventListener('contextmenu', e => this._onRightClick(e));
        el.addEventListener('touchstart', e => this._onTouchStart(e));
        el.addEventListener('touchend', e => this._onTouchEnd(e));
        // prevent browser's default file drop behavior
        Events.on('dragover', e => e.preventDefault());
        Events.on('drop', e => e.preventDefault());
    }

    _name() {
        if (this._peer.name.model) {
            return this._peer.name.os + ' ' + this._peer.name.model;
        }
        this._peer.name.os = this._peer.name.os.replace('Mac OS', 'Mac');
        return this._peer.name.os + ' ' + this._peer.name.browser;
    }

    _icon() {
        const device = this._peer.name.device || this._peer.name;
        if (device.type === 'mobile') {
            return '#phone-iphone';
        }
        if (device.type === 'tablet') {
            return '#tablet-mac';
        }
        return '#desktop-mac';
    }

    _onFilesSelected(e) {
        const $input = e.target;
        const files = $input.files;
        Events.fire('files-selected', {
            files: files,
            to: this._peer.id
        });
        $input.value = null; // reset input
        this.setProgress(0.01);
    }

    setProgress(progress) {
        if (progress > 0) {
            this.$el.setAttribute('transfer', '1');
        }
        if (progress > 0.5) {
            this.$progress.classList.add('over50');
        } else {
            this.$progress.classList.remove('over50');
        }
        const degrees = `rotate(${360 * progress}deg)`;
        this.$progress.style.setProperty('--progress', degrees);
        if (progress >= 1) {
            this.setProgress(0);
            this.$el.removeAttribute('transfer');
        }
    }

    _onDrop(e) {
        e.preventDefault();
        const files = e.dataTransfer.files;
        Events.fire('files-selected', {
            files: files,
            to: this._peer.id
        });
        this._onDragEnd();
    }

    _onDragOver() {
        this.$el.setAttribute('drop', 1);
    }

    _onDragEnd() {
        this.$el.removeAttribute('drop');
    }

    _onRightClick(e) {
        e.preventDefault();
        Events.fire('text-recipient', this._peer.id);
    }

    _onTouchStart(e) {
        this._touchStart = Date.now();
        this._touchTimer = setTimeout(_ => this._onTouchEnd(), 610);
    }

    _onTouchEnd(e) {
        if (Date.now() - this._touchStart < 500) {
            clearTimeout(this._touchTimer);
        } else { // this was a long tap
            if (e) e.preventDefault();
            Events.fire('text-recipient', this._peer.id);
        }
    }
}

class Dialog {
    constructor(id) {
        this.$el = $(id);
        this.$el.querySelectorAll('[close]').forEach(el => el.addEventListener('click', e => this.hide()))
        this.$autoFocus = this.$el.querySelector('[autofocus]');
    }

    show() {
        this.$el.setAttribute('show', 1);
        if (this.$autoFocus) this.$autoFocus.focus();
    }

    hide() {
        this.$el.removeAttribute('show');
        document.activeElement.blur();
        window.blur();
    }
}

class ReceiveDialog extends Dialog {

    constructor() {
        super('receiveDialog');
        Events.on('file-received', e => {
            this._nextFile(e.detail);
            window.blop.play();
        });
        this._filesQueue = [];
    }

    _nextFile(nextFile) {
        if (nextFile) this._filesQueue.push(nextFile);
        if (this._busy) return;
        this._busy = true;
        const file = this._filesQueue.shift();
        this._displayFile(file);
    }

    _dequeueFile() {
        if (!this._filesQueue.length) { // nothing to do
            this._busy = false;
            return;
        }
        // dequeue next file
        setTimeout(_ => {
            this._busy = false;
            this._nextFile();
        }, 300);
    }

    _displayFile(file) {
        const $a = this.$el.querySelector('#download');
        $a.href = file.url;
        $a.download = file.name;

        this.$el.querySelector('#fileName').textContent = file.name;
        this.$el.querySelector('#fileSize').textContent = this._formatFileSize(file.size);
        this.show();

        if (window.isDownloadSupported) return;
        $a.target = "_blank"; // fallback
    }

    _formatFileSize(bytes) {
        if (bytes >= 1e9) {
            return (Math.round(bytes / 1e8) / 10) + ' GB';
        } else if (bytes >= 1e6) {
            return (Math.round(bytes / 1e5) / 10) + ' MB';
        } else if (bytes > 1000) {
            return Math.round(bytes / 1000) + ' KB';
        } else {
            return bytes + ' Bytes';
        }
    }

    hide() {
        super.hide();
        this._dequeueFile();
    }
}

class SendTextDialog extends Dialog {
    constructor() {
        super('sendTextDialog');
        Events.on('text-recipient', e => this._onRecipient(e.detail))
        this.$text = this.$el.querySelector('#textInput');
        const button = this.$el.querySelector('form');
        button.addEventListener('submit', e => this._send(e));
    }

    _onRecipient(recipient) {
        this._recipient = recipient;
        this.show();
        this.$text.setSelectionRange(0, this.$text.value.length)
    }

    _send(e) {
        e.preventDefault();
        Events.fire('send-text', {
            to: this._recipient,
            text: this.$text.value
        });
    }
}

class ReceiveTextDialog extends Dialog {
    constructor() {
        super('receiveTextDialog');
        Events.on('text-received', e => this._onText(e.detail))
        this.$text = this.$el.querySelector('#text');
        const $copy = this.$el.querySelector('#copy');
        copy.addEventListener('click', _ => this._onCopy());
    }

    _onText(e) {
        this.$text.innerHTML = '';
        const text = e.text;
        if (isURL(text)) {
            const $a = document.createElement('a');
            $a.href = text;
            $a.target = '_blank';
            $a.textContent = text;
            this.$text.appendChild($a);
        } else {
            this.$text.textContent = text;
        }
        this.show();
        window.blop.play();
    }

    _onCopy() {
        if (!document.copy(this.$text.textContent)) return;
        Events.fire('notify-user', 'Copied to clipboard');
    }
}

class Toast extends Dialog {
    constructor() {
        super('toast');
        Events.on('notify-user', e => this._onNotfiy(e.detail));
    }

    _onNotfiy(message) {
        this.$el.textContent = message;
        this.show();
        setTimeout(_ => this.hide(), 3000);
    }
}

class Notifications {

    constructor() {
        // Check if the browser supports notifications
        if (!('Notification' in window)) return;

        // Check whether notification permissions have already been granted
        if (Notification.permission !== 'granted') {
            this.$button = $('notification');
            this.$button.removeAttribute('hidden');
            this.$button.addEventListener('click', e => this._requestPermission());
        }
        Events.on('text-received', e => this._messageNotification(e.detail.text));
        Events.on('file-received', e => this._downloadNotification(e.detail.name));
    }

    _requestPermission() {
        Notification.requestPermission(permission => {
            if (permission !== 'granted') {
                Events.fire('notify-user', Notifications.PERMISSION_ERROR || 'Error');
                return;
            }
            this._notify('Even more snappy sharing!');
            this.$button.setAttribute('hidden', 1);
        });
    }

    _notify(message, body) {
        const config = {
            body: body,
            icon: '/images/logo_transparent_128x128.png',
        }
        try {
            return new Notification(message, config);
        } catch (e) {
            // android doesn't support "new Notification" if service worker is installed
            if (!serviceWorker || !serviceWorker.showNotification) return;
            return serviceWorker.showNotification(message, config);
        }

    }

    _messageNotification(message) {
        if (isURL(message)) {
            const notification = this._notify(message, 'Click to open link');
            this._bind(notification, e => window.open(message, '_blank', null, true));
        } else {
            const notification = this._notify(message, 'Click to copy text');
            this._bind(notification, e => this._copyText(message, notification));
        }
    }

    _downloadNotification(message) {
        const notification = this._notify(message, 'Click to download');
        if (!window.isDownloadSupported) return;
        this._bind(notification, e => this._download(notification));
    }

    _download(notification) {
        document.querySelector('x-dialog [download]').click();
        notification.close();
    }

    _copyText(message, notification) {
        notification.close();
        if(!document.copy(message)) return;
        this._notify('Copied text to clipboard');
    }

    _bind(notification, handler) {
        if (notification.then) {
            notification.then(e => serviceWorker.getNotifications().then(notifications => {
                serviceWorker.addEventListener('notificationclick', handler);
            }));
        } else {
            notification.onclick = handler;
        }
    }
}

class Snapdrop {
    constructor() {
        const server = new ServerConnection();
        const peers = new PeersManager(server);
        const peersUI = new PeersUI();
        Events.on('load', e => {
            const receiveDialog = new ReceiveDialog();
            const sendTextDialog = new SendTextDialog();
            const receiveTextDialog = new ReceiveTextDialog();
            const toast = new Toast();
            const notifications = new Notifications();
        })
    }
}

const snapdrop = new Snapdrop();

document.copy = text => {
    // A  contains the text to copy
    const span = document.createElement('span');
    span.textContent = text;
    span.style.whiteSpace = 'pre'; // Preserve consecutive spaces and newlines

    // Paint the span outside the viewport
    span.style.position = 'absolute';
    span.style.left = '-9999px';
    span.style.top = '-9999px';

    const win = window;
    const selection = win.getSelection();
    win.document.body.appendChild(span);

    const range = win.document.createRange();
    selection.removeAllRanges();
    range.selectNode(span);
    selection.addRange(range);

    let success = false;
    try {
        success = win.document.execCommand('copy');
    } catch (err) {}

    selection.removeAllRanges();
    span.remove();

    return success;
}

if ('serviceWorker' in navigator) {
    navigator.serviceWorker
        .register('/service-worker.js')
        .then(serviceWorker => {
            console.log('Service Worker registered');
            window.serviceWorker = serviceWorker
        });
}

// Background Animation
Events.on('load', () => {
    var requestAnimFrame = (function() {
        return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
            function(callback) {
                window.setTimeout(callback, 1000 / 60);
            };
    })();
    var c = document.createElement('canvas');
    document.body.appendChild(c);
    var style = c.style;
    style.width = '100%';
    style.position = 'absolute';
    style.zIndex = -1;
    var ctx = c.getContext('2d');
    var x0, y0, w, h, dw;

    function init() {
        w = window.innerWidth;
        h = window.innerHeight;
        c.width = w;
        c.height = h;
        var offset = h > 380 ? 100 : 65;
        x0 = w / 2;
        y0 = h - offset;
        dw = Math.max(w, h, 1000) / 13;
        drawCircles();
    }
    window.onresize = init;

    function drawCicrle(radius) {
        ctx.beginPath();
        var color = Math.round(255 * (1 - radius / Math.max(w, h)));
        ctx.strokeStyle = 'rgba(' + color + ',' + color + ',' + color + ',0.1)';
        ctx.arc(x0, y0, radius, 0, 2 * Math.PI);
        ctx.stroke();
        ctx.lineWidth = 2;
    }

    var step = 0;

    function drawCircles() {
        ctx.clearRect(0, 0, w, h);
        for (var i = 0; i < 8; i++) {
            drawCicrle(dw * i + step % dw);
        }
        step += 1;
    }

    var loading = true;

    function animate() {
        if (loading || step % dw < dw - 5) {
            requestAnimFrame(function() {
                drawCircles();
                animate();
            });
        }
    }
    window.animateBackground = function(l) {
        loading = l;
        animate();
    };
    init();
    animate();
    setTimeout(e => window.animateBackground(false), 3000);
});

Notifications.PERMISSION_ERROR = `
Notifications permission has been blocked
as the user has dismissed the permission prompt several times.
This can be reset in Page Info
which can be accessed by clicking the lock icon next to the URL.`;

document.body.onclick = e => { // safari hack to fix audio
    document.body.onclick = null;
    if (!(/.*Version.*Safari.*/.test(navigator.userAgent))) return;
    blop.play();
}<commit_msg>Fix iOS 12 download bug<commit_after>const $ = query => document.getElementById(query);
const $$ = query => document.body.querySelector(query);
const isURL = text => /^((https?:\/\/|www)[^\s]+)/g.test(text.toLowerCase());
window.isDownloadSupported = (typeof document.createElement('a').download !== 'undefined');
window.isProductionEnvironment = !window.location.host.startsWith('localhost');

class PeersUI {

    constructor() {
        Events.on('peer-joined', e => this._onPeerJoined(e.detail));
        Events.on('peer-left', e => this._onPeerLeft(e.detail));
        Events.on('peers', e => this._onPeers(e.detail));
        Events.on('file-progress', e => this._onFileProgress(e.detail));
    }

    _onPeerJoined(peer) {
        if (document.getElementById(peer.id)) return;
        const peerUI = new PeerUI(peer);
        $$('x-peers').appendChild(peerUI.$el);
    }

    _onPeers(peers) {
        this._clearPeers();
        peers.forEach(peer => this._onPeerJoined(peer));
    }

    _onPeerLeft(peerId) {
        const $peer = $(peerId);
        if (!$peer) return;
        $peer.remove();
    }

    _onFileProgress(progress) {
        const peerId = progress.sender || progress.recipient;
        const $peer = $(peerId);
        if (!$peer) return;
        $peer.ui.setProgress(progress.progress);
    }

    _clearPeers() {
        const $peers = $$('x-peers').innerHTML = '';
    }
}

class PeerUI {

    html() {
        return `

                <input type="file" multiple>
                <x-icon shadow="1">
                    <svg class="icon"><use xlink:href="#"/></svg>
                </x-icon>

            `
    }

    constructor(peer) {
        this._peer = peer;
        this._initDom();
        this._bindListeners(this.$el);
    }

    _initDom() {
        const el = document.createElement('x-peer');
        el.id = this._peer.id;
        el.innerHTML = this.html();
        el.ui = this;
        el.querySelector('svg use').setAttribute('xlink:href', this._icon());
        el.querySelector('.name').textContent = this._name();
        this.$el = el;
        this.$progress = el.querySelector('.progress');
    }

    _bindListeners(el) {
        el.querySelector('input').addEventListener('change', e => this._onFilesSelected(e));
        el.addEventListener('drop', e => this._onDrop(e));
        el.addEventListener('dragend', e => this._onDragEnd(e));
        el.addEventListener('dragleave', e => this._onDragEnd(e));
        el.addEventListener('dragover', e => this._onDragOver(e));
        el.addEventListener('contextmenu', e => this._onRightClick(e));
        el.addEventListener('touchstart', e => this._onTouchStart(e));
        el.addEventListener('touchend', e => this._onTouchEnd(e));
        // prevent browser's default file drop behavior
        Events.on('dragover', e => e.preventDefault());
        Events.on('drop', e => e.preventDefault());
    }

    _name() {
        if (this._peer.name.model) {
            return this._peer.name.os + ' ' + this._peer.name.model;
        }
        this._peer.name.os = this._peer.name.os.replace('Mac OS', 'Mac');
        return this._peer.name.os + ' ' + this._peer.name.browser;
    }

    _icon() {
        const device = this._peer.name.device || this._peer.name;
        if (device.type === 'mobile') {
            return '#phone-iphone';
        }
        if (device.type === 'tablet') {
            return '#tablet-mac';
        }
        return '#desktop-mac';
    }

    _onFilesSelected(e) {
        const $input = e.target;
        const files = $input.files;
        Events.fire('files-selected', {
            files: files,
            to: this._peer.id
        });
        $input.value = null; // reset input
        this.setProgress(0.01);
    }

    setProgress(progress) {
        if (progress > 0) {
            this.$el.setAttribute('transfer', '1');
        }
        if (progress > 0.5) {
            this.$progress.classList.add('over50');
        } else {
            this.$progress.classList.remove('over50');
        }
        const degrees = `rotate(${360 * progress}deg)`;
        this.$progress.style.setProperty('--progress', degrees);
        if (progress >= 1) {
            this.setProgress(0);
            this.$el.removeAttribute('transfer');
        }
    }

    _onDrop(e) {
        e.preventDefault();
        const files = e.dataTransfer.files;
        Events.fire('files-selected', {
            files: files,
            to: this._peer.id
        });
        this._onDragEnd();
    }

    _onDragOver() {
        this.$el.setAttribute('drop', 1);
    }

    _onDragEnd() {
        this.$el.removeAttribute('drop');
    }

    _onRightClick(e) {
        e.preventDefault();
        Events.fire('text-recipient', this._peer.id);
    }

    _onTouchStart(e) {
        this._touchStart = Date.now();
        this._touchTimer = setTimeout(_ => this._onTouchEnd(), 610);
    }

    _onTouchEnd(e) {
        if (Date.now() - this._touchStart < 500) {
            clearTimeout(this._touchTimer);
        } else { // this was a long tap
            if (e) e.preventDefault();
            Events.fire('text-recipient', this._peer.id);
        }
    }
}

class Dialog {
    constructor(id) {
        this.$el = $(id);
        this.$el.querySelectorAll('[close]').forEach(el => el.addEventListener('click', e => this.hide()))
        this.$autoFocus = this.$el.querySelector('[autofocus]');
    }

    show() {
        this.$el.setAttribute('show', 1);
        if (this.$autoFocus) this.$autoFocus.focus();
    }

    hide() {
        this.$el.removeAttribute('show');
        document.activeElement.blur();
        window.blur();
    }
}

class ReceiveDialog extends Dialog {

    constructor() {
        super('receiveDialog');
        Events.on('file-received', e => {
            this._nextFile(e.detail);
            window.blop.play();
        });
        this._filesQueue = [];
    }

    _nextFile(nextFile) {
        if (nextFile) this._filesQueue.push(nextFile);
        if (this._busy) return;
        this._busy = true;
        const file = this._filesQueue.shift();
        this._displayFile(file);
    }

    _dequeueFile() {
        if (!this._filesQueue.length) { // nothing to do
            this._busy = false;
            return;
        }
        // dequeue next file
        setTimeout(_ => {
            this._busy = false;
            this._nextFile();
        }, 300);
    }

    _displayFile(file) {
        const $a = this.$el.querySelector('#download');
        $a.href = file.url;
        $a.download = file.name;

        this.$el.querySelector('#fileName').textContent = file.name;
        this.$el.querySelector('#fileSize').textContent = this._formatFileSize(file.size);
        this.show();

        if (window.isDownloadSupported) return;
        // $a.target = "_blank"; // fallback
    }

    _formatFileSize(bytes) {
        if (bytes >= 1e9) {
            return (Math.round(bytes / 1e8) / 10) + ' GB';
        } else if (bytes >= 1e6) {
            return (Math.round(bytes / 1e5) / 10) + ' MB';
        } else if (bytes > 1000) {
            return Math.round(bytes / 1000) + ' KB';
        } else {
            return bytes + ' Bytes';
        }
    }

    hide() {
        super.hide();
        this._dequeueFile();
    }
}

class SendTextDialog extends Dialog {
    constructor() {
        super('sendTextDialog');
        Events.on('text-recipient', e => this._onRecipient(e.detail))
        this.$text = this.$el.querySelector('#textInput');
        const button = this.$el.querySelector('form');
        button.addEventListener('submit', e => this._send(e));
    }

    _onRecipient(recipient) {
        this._recipient = recipient;
        this.show();
        this.$text.setSelectionRange(0, this.$text.value.length)
    }

    _send(e) {
        e.preventDefault();
        Events.fire('send-text', {
            to: this._recipient,
            text: this.$text.value
        });
    }
}

class ReceiveTextDialog extends Dialog {
    constructor() {
        super('receiveTextDialog');
        Events.on('text-received', e => this._onText(e.detail))
        this.$text = this.$el.querySelector('#text');
        const $copy = this.$el.querySelector('#copy');
        copy.addEventListener('click', _ => this._onCopy());
    }

    _onText(e) {
        this.$text.innerHTML = '';
        const text = e.text;
        if (isURL(text)) {
            const $a = document.createElement('a');
            $a.href = text;
            $a.target = '_blank';
            $a.textContent = text;
            this.$text.appendChild($a);
        } else {
            this.$text.textContent = text;
        }
        this.show();
        window.blop.play();
    }

    _onCopy() {
        if (!document.copy(this.$text.textContent)) return;
        Events.fire('notify-user', 'Copied to clipboard');
    }
}

class Toast extends Dialog {
    constructor() {
        super('toast');
        Events.on('notify-user', e => this._onNotfiy(e.detail));
    }

    _onNotfiy(message) {
        this.$el.textContent = message;
        this.show();
        setTimeout(_ => this.hide(), 3000);
    }
}

class Notifications {

    constructor() {
        // Check if the browser supports notifications
        if (!('Notification' in window)) return;

        // Check whether notification permissions have already been granted
        if (Notification.permission !== 'granted') {
            this.$button = $('notification');
            this.$button.removeAttribute('hidden');
            this.$button.addEventListener('click', e => this._requestPermission());
        }
        Events.on('text-received', e => this._messageNotification(e.detail.text));
        Events.on('file-received', e => this._downloadNotification(e.detail.name));
    }

    _requestPermission() {
        Notification.requestPermission(permission => {
            if (permission !== 'granted') {
                Events.fire('notify-user', Notifications.PERMISSION_ERROR || 'Error');
                return;
            }
            this._notify('Even more snappy sharing!');
            this.$button.setAttribute('hidden', 1);
        });
    }

    _notify(message, body) {
        const config = {
            body: body,
            icon: '/images/logo_transparent_128x128.png',
        }
        try {
            return new Notification(message, config);
        } catch (e) {
            // android doesn't support "new Notification" if service worker is installed
            if (!serviceWorker || !serviceWorker.showNotification) return;
            return serviceWorker.showNotification(message, config);
        }

    }

    _messageNotification(message) {
        if (isURL(message)) {
            const notification = this._notify(message, 'Click to open link');
            this._bind(notification, e => window.open(message, '_blank', null, true));
        } else {
            const notification = this._notify(message, 'Click to copy text');
            this._bind(notification, e => this._copyText(message, notification));
        }
    }

    _downloadNotification(message) {
        const notification = this._notify(message, 'Click to download');
        if (!window.isDownloadSupported) return;
        this._bind(notification, e => this._download(notification));
    }

    _download(notification) {
        document.querySelector('x-dialog [download]').click();
        notification.close();
    }

    _copyText(message, notification) {
        notification.close();
        if(!document.copy(message)) return;
        this._notify('Copied text to clipboard');
    }

    _bind(notification, handler) {
        if (notification.then) {
            notification.then(e => serviceWorker.getNotifications().then(notifications => {
                serviceWorker.addEventListener('notificationclick', handler);
            }));
        } else {
            notification.onclick = handler;
        }
    }
}

class Snapdrop {
    constructor() {
        const server = new ServerConnection();
        const peers = new PeersManager(server);
        const peersUI = new PeersUI();
        Events.on('load', e => {
            const receiveDialog = new ReceiveDialog();
            const sendTextDialog = new SendTextDialog();
            const receiveTextDialog = new ReceiveTextDialog();
            const toast = new Toast();
            const notifications = new Notifications();
        })
    }
}

const snapdrop = new Snapdrop();

document.copy = text => {
    // A  contains the text to copy
    const span = document.createElement('span');
    span.textContent = text;
    span.style.whiteSpace = 'pre'; // Preserve consecutive spaces and newlines

    // Paint the span outside the viewport
    span.style.position = 'absolute';
    span.style.left = '-9999px';
    span.style.top = '-9999px';

    const win = window;
    const selection = win.getSelection();
    win.document.body.appendChild(span);

    const range = win.document.createRange();
    selection.removeAllRanges();
    range.selectNode(span);
    selection.addRange(range);

    let success = false;
    try {
        success = win.document.execCommand('copy');
    } catch (err) {}

    selection.removeAllRanges();
    span.remove();

    return success;
}

if ('serviceWorker' in navigator) {
    navigator.serviceWorker
        .register('/service-worker.js')
        .then(serviceWorker => {
            console.log('Service Worker registered');
            window.serviceWorker = serviceWorker
        });
}

// Background Animation
Events.on('load', () => {
    var requestAnimFrame = (function() {
        return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
            function(callback) {
                window.setTimeout(callback, 1000 / 60);
            };
    })();
    var c = document.createElement('canvas');
    document.body.appendChild(c);
    var style = c.style;
    style.width = '100%';
    style.position = 'absolute';
    style.zIndex = -1;
    var ctx = c.getContext('2d');
    var x0, y0, w, h, dw;

    function init() {
        w = window.innerWidth;
        h = window.innerHeight;
        c.width = w;
        c.height = h;
        var offset = h > 380 ? 100 : 65;
        x0 = w / 2;
        y0 = h - offset;
        dw = Math.max(w, h, 1000) / 13;
        drawCircles();
    }
    window.onresize = init;

    function drawCicrle(radius) {
        ctx.beginPath();
        var color = Math.round(255 * (1 - radius / Math.max(w, h)));
        ctx.strokeStyle = 'rgba(' + color + ',' + color + ',' + color + ',0.1)';
        ctx.arc(x0, y0, radius, 0, 2 * Math.PI);
        ctx.stroke();
        ctx.lineWidth = 2;
    }

    var step = 0;

    function drawCircles() {
        ctx.clearRect(0, 0, w, h);
        for (var i = 0; i < 8; i++) {
            drawCicrle(dw * i + step % dw);
        }
        step += 1;
    }

    var loading = true;

    function animate() {
        if (loading || step % dw < dw - 5) {
            requestAnimFrame(function() {
                drawCircles();
                animate();
            });
        }
    }
    window.animateBackground = function(l) {
        loading = l;
        animate();
    };
    init();
    animate();
    setTimeout(e => window.animateBackground(false), 3000);
});

Notifications.PERMISSION_ERROR = `
Notifications permission has been blocked
as the user has dismissed the permission prompt several times.
This can be reset in Page Info
which can be accessed by clicking the lock icon next to the URL.`;

document.body.onclick = e => { // safari hack to fix audio
    document.body.onclick = null;
    if (!(/.*Version.*Safari.*/.test(navigator.userAgent))) return;
    blop.play();
}<|endoftext|>


<commit_before>const path = require('path');
const fs = require('fs-extra');
const prettier = require('prettier');
const prettierConfig = { semi: false, parser: 'css' };

const buildDir = require('./.boltrc.multi-lang.js').buildDir;

describe('Tests to confirm that the latest upcoming release of the @bolt/build-tools is compiling as expected', () => {
  test('Check that Bolt Build Tools is generating JSON data from Sass files', async () => {
    const dataFileExists = async function() {
      try {
        await fs.promises.access(
          path.resolve(
            process.cwd(),
            `${buildDir}/data/colors/brand.bolt.json`,
          ),
        );
        return true;
      } catch (error) {
        return false;
      }
    };

    expect(await dataFileExists()).toBe(true);
  });

  test('Check JSON data for colors was generated and outputted by the @bolt/build-tools', async () => {
    const getColorData = async function() {
      try {
        await fs.promises.access(
          path.resolve(
            process.cwd(),
            `${buildDir}/data/colors/brand.bolt.json`,
          ),
        );
        const colorData = JSON.parse(
          fs.readFileSync(
            path.resolve(
              process.cwd(),
              `${buildDir}/data/colors/brand.bolt.json`,
            ),
          ),
        );
        return colorData;
      } catch (error) {
        return false;
      }
    };

    const colorData = await getColorData();

    expect(colorData).toMatchSnapshot();

    expect(colorData.indigo.xdark).toBe('rgb(6, 9, 35)');
    expect(colorData.yellow.base).toBe('rgb(255, 204, 77)');
  });

  test('Check that the Japanese-specific CSS bundle was generated and contains the correct font', async () => {
    const getCSSData = async function() {
      try {
        const cssData = fs
          .readFileSync(
            path.resolve(process.cwd(), `${buildDir}/bolt-global-ja.css`),
          )
          .toString();
        return cssData;
      } catch (error) {
        return false;
      }
    };

    const cssFile = await getCSSData();

    const japaneseFontRule =
      'font-family:-apple-system,BlinkMacSystemFont,\"ヒラギノ角ゴ ProN\",\"Hiragino Kaku Gothic ProN\",\"游ゴシック\",\"游ゴシック体\",YuGothic,\"Yu Gothic\",\"メイリオ\",Meiryo,\"ＭＳ ゴシック\",\"MS Gothic\",HiraKakuProN-W3,\"TakaoExゴシック\",TakaoExGothic,MotoyaLCedar,\"Droid Sans Japanese\",sans-serif';

    const englishFontFamilyRule =
      'font-family:"Open Sans","Helvetica Neue",sans-serif;font-family:var(--bolt-font-family-heading)';

    expect(cssFile).toEqual(expect.stringContaining(japaneseFontRule));
    expect(cssFile).toEqual(expect.not.stringContaining(englishFontFamilyRule));

    const prettyCss = await prettier.format(cssFile, prettierConfig);
    expect(prettyCss).toMatchSnapshot();
  });

  test('Check that the Japanese-specific bolt-global-ja.css file built contains the correct utility class styles for the older u-bolt-margin-bottom syntax.', async () => {
    const getCSSData = async function() {
      try {
        const cssData = fs
          .readFileSync(
            path.resolve(process.cwd(), `${buildDir}/bolt-global-ja.css`),
          )
          .toString();
        return cssData;
      } catch (error) {
        return false;
      }
    };

    const cssFile = await getCSSData();
    const prettyCss = await prettier.format(cssFile, prettierConfig);

    expect(prettyCss).toEqual(
      expect.stringContaining(
        await prettier.format(
          '.u-bolt-margin-bottom{margin-bottom:1.155rem!important}',
          prettierConfig,
        ),
      ),
    );
  });

  test('Check that the default English-specific bolt-global.css file built contains the correct utility class styles for the older u-bolt-margin-bottom syntax.', async () => {
    const getCSSData = async function() {
      try {
        const cssData = fs
          .readFileSync(
            path.resolve(process.cwd(), `${buildDir}/bolt-global-en.css`),
          )
          .toString();
        return cssData;
      } catch (error) {
        return false;
      }
    };

    const cssFile = await getCSSData();
    const prettyCss = await prettier.format(cssFile, prettierConfig);

    expect(prettyCss).toEqual(
      expect.stringContaining(
        await prettier.format(
          `.u-bolt-margin-bottom {margin-bottom: 1.65rem !important;}`,
          prettierConfig,
        ),
      ),
    );

    expect(prettyCss).toMatchSnapshot();
  });
});
<commit_msg>fix: update test file to address prettier issue<commit_after>const path = require('path');
const fs = require('fs-extra');
const prettier = require('prettier');
const prettierConfig = { semi: false, parser: 'css' };

const buildDir = require('./.boltrc.multi-lang.js').buildDir;

describe('Tests to confirm that the latest upcoming release of the @bolt/build-tools is compiling as expected', () => {
  test('Check that Bolt Build Tools is generating JSON data from Sass files', async () => {
    const dataFileExists = async function() {
      try {
        await fs.promises.access(
          path.resolve(
            process.cwd(),
            `${buildDir}/data/colors/brand.bolt.json`,
          ),
        );
        return true;
      } catch (error) {
        return false;
      }
    };

    expect(await dataFileExists()).toBe(true);
  });

  test('Check JSON data for colors was generated and outputted by the @bolt/build-tools', async () => {
    const getColorData = async function() {
      try {
        await fs.promises.access(
          path.resolve(
            process.cwd(),
            `${buildDir}/data/colors/brand.bolt.json`,
          ),
        );
        const colorData = JSON.parse(
          fs.readFileSync(
            path.resolve(
              process.cwd(),
              `${buildDir}/data/colors/brand.bolt.json`,
            ),
          ),
        );
        return colorData;
      } catch (error) {
        return false;
      }
    };

    const colorData = await getColorData();

    expect(colorData).toMatchSnapshot();

    expect(colorData.indigo.xdark).toBe('rgb(6, 9, 35)');
    expect(colorData.yellow.base).toBe('rgb(255, 204, 77)');
  });

  test('Check that the Japanese-specific CSS bundle was generated and contains the correct font', async () => {
    const getCSSData = async function() {
      try {
        const cssData = fs
          .readFileSync(
            path.resolve(process.cwd(), `${buildDir}/bolt-global-ja.css`),
          )
          .toString();
        return cssData;
      } catch (error) {
        return false;
      }
    };

    const cssFile = await getCSSData();

    const japaneseFontRule =
      'font-family:-apple-system,BlinkMacSystemFont,"ヒラギノ角ゴ ProN","Hiragino Kaku Gothic ProN","游ゴシック","游ゴシック体",YuGothic,"Yu Gothic","メイリオ",Meiryo,"ＭＳ ゴシック","MS Gothic",HiraKakuProN-W3,"TakaoExゴシック",TakaoExGothic,MotoyaLCedar,"Droid Sans Japanese",sans-serif';

    const englishFontFamilyRule =
      'font-family:"Open Sans","Helvetica Neue",sans-serif;font-family:var(--bolt-font-family-heading)';

    expect(cssFile).toEqual(expect.stringContaining(japaneseFontRule));
    expect(cssFile).toEqual(expect.not.stringContaining(englishFontFamilyRule));

    const prettyCss = await prettier.format(cssFile, prettierConfig);
    expect(prettyCss).toMatchSnapshot();
  });

  test('Check that the Japanese-specific bolt-global-ja.css file built contains the correct utility class styles for the older u-bolt-margin-bottom syntax.', async () => {
    const getCSSData = async function() {
      try {
        const cssData = fs
          .readFileSync(
            path.resolve(process.cwd(), `${buildDir}/bolt-global-ja.css`),
          )
          .toString();
        return cssData;
      } catch (error) {
        return false;
      }
    };

    const cssFile = await getCSSData();
    const prettyCss = await prettier.format(cssFile, prettierConfig);

    expect(prettyCss).toEqual(
      expect.stringContaining(
        await prettier.format(
          '.u-bolt-margin-bottom{margin-bottom:1.155rem!important}',
          prettierConfig,
        ),
      ),
    );
  });

  test('Check that the default English-specific bolt-global.css file built contains the correct utility class styles for the older u-bolt-margin-bottom syntax.', async () => {
    const getCSSData = async function() {
      try {
        const cssData = fs
          .readFileSync(
            path.resolve(process.cwd(), `${buildDir}/bolt-global-en.css`),
          )
          .toString();
        return cssData;
      } catch (error) {
        return false;
      }
    };

    const cssFile = await getCSSData();
    const prettyCss = await prettier.format(cssFile, prettierConfig);

    expect(prettyCss).toEqual(
      expect.stringContaining(
        await prettier.format(
          `.u-bolt-margin-bottom {margin-bottom: 1.65rem !important;}`,
          prettierConfig,
        ),
      ),
    );

    expect(prettyCss).toMatchSnapshot();
  });
});
<|endoftext|>