<commit_before>

var restify = require('restify');
var url = require('url');
var _ = require('underscore');
var async = require('async');
const qs = require('querystring');
const got = require('got');

function configureServer (opts, ctx) {
  var server = restify.createServer(opts);
  var consul = ctx.consul;

  function get_candidates (service, next) {
    return consul.catalog.service.nodes({service: service}, next);
  }

  function apply_policies (services) {
    function byTotalActive (x) { return x.HealthStatus.total.active; }
    function byCapacityQuotient (x) { return x.HealthStatus.total.expected / x.HealthStatus.total.max; }
    function hasHealth (x) { return x && x.HealthStatus; }
    return _.sortBy(services.filter(hasHealth), byCapacityQuotient);
  }

  function require_health (services, next) {
    async.each(services, get_health, function finish (err) {
      next(err, services);
    });
  }

  function fetch_services (service, tenants, next) {
    if (tenants.call) {
      next = tenants;
      tenants = [ ];
    }
    return get_candidates(service, function (err, clusters) {
      function filter_runners (tenant) {
        var runners = _.where(clusters, { ServiceAddress : tenant.ServiceAddress });
        return runners;
      }
      var candidates = [ ]
      var orig_len = clusters.length;
      if (1 == tenants.length) {
        candidates = filter_runners(tenants[0]);
        console.log('tenants', tenants.length, 'orig', orig_len, 'filtered', candidates.length);
        if (candidates.length == 1) {
          return next(err, candidates);
        }
      } else if (tenants.length > 1) {
        clusters = _.flatten(_.map(tenants, filter_runners));
        console.log('tenants', tenants.length, 'orig', orig_len, 'filtered', clusters.length);
        return next(err, clusters);
      }
      return require_health(clusters, next);
    });
  }

  function fetch_elected_services (service, tenants, next) {
    if (tenants.call) {
      next = tenants;
      tenants = [ ];
    }
    return fetch_services(service, tenants, function (err, services) {
      if (err) throw err;
      if (tenants.length > 0) {
        return next(services);
      }
      return next(apply_policies(services));
    });
  }

  function get_best_service (service, next) {
    return fetch_elected_services(service, function (services) {
      // if (err) throw err;
      return next(elect(services));
    });
  }

  function elect (services) {
    return services[0];
  }

  function suggest (elected) {
    var candidate = { };
    _.extend(candidate, elected);
    return candidate;
  }

  function ratify (elected, next) {
  }

  function get_health (runner, done) {
    var uri = url.format({ hostname: runner.ServiceAddress, port: runner.ServicePort, pathname: '/stats/active', protocol: 'http' });
    console.log("fetching", uri);
    got(uri).json( ).then(function (body) {
      runner.HealthStatus = body;
      done( );
    }).catch(function (err) {
      console.log("ERROR skipping", arguments);
      done( );
    });
  }

  server.use(function (req, res, next) {
    res.locals = { };
    next( );
  });

  function format_locals (req, res, next) {
    res.send(res.locals);
    next( );
  }

  server.get('/suggested/consul/:service', function (req, res, next) {
  });
  server.get('/available/consul/:service', function (req, res, next) {
    fetch_services(req.params.service, function (err, services) {
      res.locals.services = services;
      next( );
    });
  }, format_locals);

  server.get('/elected/consul/:service', function (req, res, next) {
    get_best_service(req.params.service, function (clusters) {
      res.locals.service = clusters;
      console.log('BEST SERVICE', clusters);
      next( );
    })
  },
  format_locals
  );

  server.get('/scheduled/consul/:service/:tenant/:suffix',
  function (req, res, next) {
    var suffix = req.params.suffix || 'backends';
    var domain = [ req.params.tenant, suffix ].join('.');
    var search  = {service: suffix, tag: req.params.tenant };
    console.log('SEARCH TENANT', search);
    consul.catalog.service.nodes(search, function (err, tenants) {
      if (err) {
        console.log(err);
        return next(err);
      }

      var orig_num_tenants = tenants ? tenants.length : 0;
      tenants = _.uniq(tenants, _.iteratee('ServiceAddress'));
      console.log("SEARCH TENANT RESULTS", err, orig_num_tenants, tenants ? tenants.length : err);

      res.locals.tenants = tenants;
      next( );

    });
  },
  function (req, res, next) {
    var search = {service: req.params.service };
    if (res.locals.tenants.length >= 1) {
      // res.locals.services = res.locals.tenants;
      // res.locals.preferred = res.locals.tenants[0];
      // console.log("skipping CLUSTER search", res.locals.tenants.length);
      // next( );
      // return;
    }
    fetch_elected_services(req.params.service, res.locals.tenants, function (services) {
      console.log("SEARCH CLUSTER RESULTS", services.length);
      res.locals.services = services;
      res.locals.preferred = elect(services);
      next( );
    });
  },
  function (req, res, next) {
    var tenants = res.locals.tenants;
    function hostsTenant  ( ) { }
    function electRunners (tenant) {
      var runners = _.where(res.locals.services, { ServiceAddress : tenant.ServiceAddress });
      return runners;
    }
    var candidates = _.flatten(_.map(tenants, electRunners));
    console.log("candidates", candidates.length);
    res.locals.candidates = candidates;
    next( );
  },
  function (req, res, next) {
    if (_.isEmpty(res.locals.candidates)) {
      // this is a new/non-existing tenant
      res.locals.elected = res.locals.preferred;
    } else {
      //pre-existing, use a candidate
      res.locals.elected = res.locals.candidates[0];
    }
    if (!res.locals.elected) {
      var fail = { tenant: res.locals.tenants[0], candidates: res.locals.candidates };
      console.log("FAILED TENANT", fail.tenant);
      console.log("FAIL NO SUITABLE CANDIDATE", fail);
      res.send(404, fail);
      return next(fail);
    }
    var urlish = { hostname: res.locals.elected.ServiceAddress
    , port: res.locals.elected.ServicePort
    , pathname: '/environs/' + req.params.tenant
    , protocol: 'http'
    };
    var uri = url.format(urlish);
    console.log('elected', uri);
    res.locals.endpoint = uri;
    res.header('X-ELECTED-RUNNER', uri);
    next( );
  },
  format_locals
  );

  server.get('/consul/:service', function (req, res, next) {
    consul.catalog.service.nodes({service: req.params.service}, function (err, clusters) {
      res.locals.services = clusters;
      next( );
    });
  },
  function (req, res, next) {
    async.each(res.locals.services, get_health, function finish ( ) {
      res.locals.services = _.sortBy(res.locals.services.filter(function (x) { return x && x.HealthStatus; }), function (x) { return x.HealthStatus.total.active; });
      next( );
    });
  },
  function (req, res, next) {
    res.send(res.locals);
    next( );
  });
  return server;
}

function consul_url_to_opts (input) {
  var u = url.parse(input);
  var q = qs.parse(u.query);
  var protos = { consul: 'http', consuls: 'https' };
  var scheme = protos[u.protocol] || u.protocol;
  var o = {
    host: u.hostname,
    port: u.port,
    url: url.format({protocol: scheme, host: u.host})
  };
  if (u.search && 'https' == scheme) {
    if (q.promisify) {
      o.promisify = true;
    }
    // TODO: security
  }
  return o;
}

function configureWatcher (opts) {
  var config = {

    consul: consul_url_to_opts(options.url)
  };
  console.log('connecting to consul', opts);
  var client = require('consul')(opts);
  return client;
  client.agent.self( function (err, data, res) {
    // client.agent.self(console.log.bind(console, "DEBUG CONSUL"));
    // console.log("CONNECTED TO CONSUL", client, arguments);
    if (err) throw err;
    fn(err, client, data, res);
  });
  // client.agent.self(console.log.bind(console, "DEBUG CONSUL"));
  return client;
}

if (!module.parent) {
  var port = parseInt(process.env.PORT || '2829')
  var boot = require('bootevent')( );
  var env = require('./lib/env');
  boot.acquire(function consul_env (ctx, next) {
    var opts = consul_url_to_opts(env.consul.url);
    ctx.consul = require('consul')(opts);
    ctx.consul.agent.self( function (err, data, res) {
      if (err) throw err;
      console.log('connected to consul');
      next( );
    });
  }).acquire(function server_env (ctx, next) {
    ctx.server = configureServer({ }, ctx);
    next( );
  }).boot(function (ctx) {
    ctx.server.listen(port, function ( ) {
      console.log('listening on', this.address( ));
    });
  });
}

<commit_msg>stricter tenant and candidate matching<commit_after>

var restify = require('restify');
var url = require('url');
var _ = require('underscore');
var async = require('async');
const qs = require('querystring');
const got = require('got');

function configureServer (opts, ctx) {
  var server = restify.createServer(opts);
  var consul = ctx.consul;

  function get_candidates (service, next) {
    return consul.catalog.service.nodes({service: service}, next);
  }

  function apply_policies (services) {
    function byTotalActive (x) { return x.HealthStatus.total.active; }
    function byCapacityQuotient (x) { return x.HealthStatus.total.expected / x.HealthStatus.total.max; }
    function hasHealth (x) { return x && x.HealthStatus; }
    return _.sortBy(services.filter(hasHealth), byCapacityQuotient);
  }

  function require_health (services, next) {
    async.each(services, get_health, function finish (err) {
      next(err, services);
    });
  }

  function fetch_services (service, tenants, next) {
    if (tenants.call) {
      next = tenants;
      tenants = [ ];
    }
    return get_candidates(service, function (err, clusters) {
      function filter_runners (tenant) {
        var runners = _.where(clusters, { ServiceAddress : tenant.ServiceAddress });
        return runners;
      }
      var candidates = [ ];
      var orig_len = clusters.length;
      if (1 == tenants.length) {
        candidates = filter_runners(tenants[0]);
        console.log('tenants', tenants.length, 'orig', orig_len, 'filtered', candidates.length);
        if (candidates.length == 1) {
          return next(err, candidates);
        }
      } else if (tenants.length > 1) {
        candidates = _.flatten(_.map(tenants, filter_runners));
        console.log('tenants', tenants.length, 'orig', orig_len, 'filtered', candidates.length);
        if (candidates.length > 0) {
          return next(err, candidates);
        }
      }
      // return require_health(clusters, next);
      return require_health(clusters, function (err, services) {
        if (err) { throw err; }
        var ordered = apply_policies(services);
        console.log('ordered policies, no matching candidates for tenant');
        return next(err, ordered);
      });
    });
  }

  function fetch_elected_services (service, tenants, next) {
    if (tenants.call) {
      next = tenants;
      tenants = [ ];
    }
    return fetch_services(service, tenants, function (err, services) {
      if (err) throw err;
      if (tenants.length > 0) {
        return next(services);
      }
      return next(apply_policies(services));
    });
  }

  function get_best_service (service, next) {
    return fetch_elected_services(service, function (services) {
      // if (err) throw err;
      return next(elect(services));
    });
  }

  function elect (services) {
    return services[0];
  }

  function suggest (elected) {
    var candidate = { };
    _.extend(candidate, elected);
    return candidate;
  }

  function ratify (elected, next) {
  }

  function get_health (runner, done) {
    var uri = url.format({ hostname: runner.ServiceAddress, port: runner.ServicePort, pathname: '/stats/active', protocol: 'http' });
    console.log("fetching", uri);
    got(uri).json( ).then(function (body) {
      runner.HealthStatus = body;
      done( );
    }).catch(function (err) {
      console.log("ERROR skipping", arguments);
      done( );
    });
  }

  server.use(function (req, res, next) {
    res.locals = { };
    next( );
  });

  function format_locals (req, res, next) {
    res.send(res.locals);
    next( );
  }

  server.get('/suggested/consul/:service', function (req, res, next) {
  });
  server.get('/available/consul/:service', function (req, res, next) {
    fetch_services(req.params.service, function (err, services) {
      res.locals.services = services;
      next( );
    });
  }, format_locals);

  server.get('/elected/consul/:service', function (req, res, next) {
    get_best_service(req.params.service, function (clusters) {
      res.locals.service = clusters;
      console.log('BEST SERVICE', clusters);
      next( );
    })
  },
  format_locals
  );

  server.get('/scheduled/consul/:service/:tenant/:suffix',
  function (req, res, next) {
    var suffix = req.params.suffix || 'backends';
    var domain = [ req.params.tenant, suffix ].join('.');
    var search  = {service: suffix, tag: req.params.tenant };
    console.log('SEARCH TENANT', search);
    consul.catalog.service.nodes(search, function (err, tenants) {
      if (err) {
        console.log(err);
        return next(err);
      }

      var orig_num_tenants = tenants ? tenants.length : 0;
      tenants = _.uniq(tenants, _.iteratee('ServiceAddress'));
      console.log("SEARCH TENANT RESULTS", err, orig_num_tenants, tenants ? tenants.length : err);

      res.locals.tenants = tenants;
      next( );

    });
  },
  function (req, res, next) {
    var search = {service: req.params.service };
    if (res.locals.tenants.length >= 1) {
      // res.locals.services = res.locals.tenants;
      // res.locals.preferred = res.locals.tenants[0];
      // console.log("skipping CLUSTER search", res.locals.tenants.length);
      // next( );
      // return;
    }
    fetch_elected_services(req.params.service, res.locals.tenants, function (services) {
      console.log("SEARCH CLUSTER RESULTS", services.length);
      res.locals.services = services;
      res.locals.preferred = elect(services);
      next( );
    });
  },
  function (req, res, next) {
    var tenants = res.locals.tenants;
    function hostsTenant  ( ) { }
    function electRunners (tenant) {
      var runners = _.where(res.locals.services, { ServiceAddress : tenant.ServiceAddress });
      return runners;
    }
    var candidates = _.flatten(_.map(tenants, electRunners));
    console.log("candidates", candidates.length);
    res.locals.candidates = candidates;
    next( );
  },
  function (req, res, next) {
    if (_.isEmpty(res.locals.candidates)) {
      // this is a new/non-existing tenant
      res.locals.elected = res.locals.preferred;
    } else {
      //pre-existing, use a candidate
      res.locals.elected = res.locals.candidates[0];
    }
    if (!res.locals.elected) {
      var fail = { tenant: res.locals.tenants[0], candidates: res.locals.candidates };
      console.log("FAILED TENANT", fail.tenant);
      console.log("FAIL NO SUITABLE CANDIDATE", fail);
      res.send(404, fail);
      return next(fail);
    }
    var urlish = { hostname: res.locals.elected.ServiceAddress
    , port: res.locals.elected.ServicePort
    , pathname: '/environs/' + req.params.tenant
    , protocol: 'http'
    };
    var uri = url.format(urlish);
    console.log('elected', uri);
    res.locals.endpoint = uri;
    res.header('X-ELECTED-RUNNER', uri);
    next( );
  },
  format_locals
  );

  server.get('/consul/:service', function (req, res, next) {
    consul.catalog.service.nodes({service: req.params.service}, function (err, clusters) {
      res.locals.services = clusters;
      next( );
    });
  },
  function (req, res, next) {
    async.each(res.locals.services, get_health, function finish ( ) {
      res.locals.services = _.sortBy(res.locals.services.filter(function (x) { return x && x.HealthStatus; }), function (x) { return x.HealthStatus.total.active; });
      next( );
    });
  },
  function (req, res, next) {
    res.send(res.locals);
    next( );
  });
  return server;
}

function consul_url_to_opts (input) {
  var u = url.parse(input);
  var q = qs.parse(u.query);
  var protos = { consul: 'http', consuls: 'https' };
  var scheme = protos[u.protocol] || u.protocol;
  var o = {
    host: u.hostname,
    port: u.port,
    url: url.format({protocol: scheme, host: u.host})
  };
  if (u.search && 'https' == scheme) {
    if (q.promisify) {
      o.promisify = true;
    }
    // TODO: security
  }
  return o;
}

function configureWatcher (opts) {
  var config = {

    consul: consul_url_to_opts(options.url)
  };
  console.log('connecting to consul', opts);
  var client = require('consul')(opts);
  return client;
  client.agent.self( function (err, data, res) {
    // client.agent.self(console.log.bind(console, "DEBUG CONSUL"));
    // console.log("CONNECTED TO CONSUL", client, arguments);
    if (err) throw err;
    fn(err, client, data, res);
  });
  // client.agent.self(console.log.bind(console, "DEBUG CONSUL"));
  return client;
}

if (!module.parent) {
  var port = parseInt(process.env.PORT || '2829')
  var boot = require('bootevent')( );
  var env = require('./lib/env');
  boot.acquire(function consul_env (ctx, next) {
    var opts = consul_url_to_opts(env.consul.url);
    ctx.consul = require('consul')(opts);
    ctx.consul.agent.self( function (err, data, res) {
      if (err) throw err;
      console.log('connected to consul');
      next( );
    });
  }).acquire(function server_env (ctx, next) {
    ctx.server = configureServer({ }, ctx);
    next( );
  }).boot(function (ctx) {
    ctx.server.listen(port, function ( ) {
      console.log('listening on', this.address( ));
    });
  });
}

<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react'
import classNames from 'classnames'
import shallowCompare from 'react-addons-shallow-compare'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as playbackStates from '../constants/playbackStates'
import * as UIActions from '../actions/ui'
import Slider from '../components/Slider'
import Levels from '../components/Levels'
import clamp from '../lib/clamp'

const songStyleRequire = require.context('../../songs', true, /.less$/)
const avatarRequire = require.context('../../meta/avatar', false, /.png$/)

function msToTimeString(ms) {
  const seconds = Math.floor(ms / 1000)
  const minutes = Math.floor(seconds / 60)
  const remSeconds = seconds - 60 * minutes
  return `${minutes}:${remSeconds < 10 ? '0' : ''}${remSeconds}`
}

const repoURL = 'https://github.com/hacktunes/hacktunes'
const aboutURL = repoURL + '#about'
const chatURL = 'https://euphoria.io/room/hacktunes'

class App extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState)
  }

  render() {
    const { songKey, song, playerState, levels, songTime, songDuration, grabbing, seekSliderTime, songStyle, actions } = this.props

    const songPercent = 100 * (songTime / songDuration)

    let playButton
    if (playerState === playbackStates.PLAYING) {
      playButton = <button className="play-toggle pause" onClick={actions.pausePlayback} />
    } else {
      // TODO: loading state
      playButton = <button className="play-toggle play" onClick={actions.fetchAndStartPlayback} />
    }

    return (

hacktun.es

              what's this?
              fork on GitHub

                {playButton}

{song.projectName}

{song.title}

              <div className="filled" style={{width: songDuration && `${songPercent}%`}} />

{msToTimeString(songDuration)}

              <Slider
                className="slider"
                value={songDuration && seekSliderTime / songDuration}
                onGrab={actions.grabSlider}
                onMove={frac => actions.moveSeekSlider(frac * songDuration)}
                onRelease={frac => actions.releaseSeekSlider(frac * songDuration)}
              >{msToTimeString(seekSliderTime)}</Slider>

{song.description}

          {song.tracks.map((track, trackKey) => {
            const trackLevels = levels.get(trackKey)
            let avatarScale = 1
            if (trackLevels) {
              const maxLevel = clamp(0, (trackLevels.leftMax + trackLevels.rightMax) / 2, 1)
              avatarScale = 1 + .35 * maxLevel
            }
            const codeURL = `${repoURL}/blob/master/songs/${songKey}/${trackKey}/`
            return (

                  <div className="avatar" style={{transform: `scale(${avatarScale})`}}>

                      {track.name}
                       by
                      {track.author.name}

{track.description}

                    <Levels levels={trackLevels} />

            )
          }).valueSeq()}

          {song.credits.map((credit, category) =>

              {category}
              {credit.name}

          ).valueSeq()}

              Add your own track to "{song.title}"
              Submissions end {song.projectEnd}.

            about
            source code
            community chat


<commit_before>let moment = require('moment-timezone');
require('moment/locale/en-gb');
moment.locale("en-gb");

describe('Viewing the terminal dashboard page', function () {

  beforeEach(function () {
    cy.deleteData();
  });

  it("should display a box for every queue in the terminal", () => {

    const schDateString = moment().hours(14).minutes(10).seconds(0).toISOString();
    const viewingDateString = moment().hours(14).minutes(15).seconds(0).toISOString();
    cy
      .addFlightWithFlightCode("TS0100", schDateString)
      .asABorderForceOfficerWithRoles(["terminal-dashboard"])
      .navigateHome()
      .visit("/#terminal/T1/dashboard/summary/?start=" + viewingDateString)
      .get(".pax-bar")
      .contains("51 passengers")
      .get(".time-label")
      .contains("14:15 - 14:30")
      .get(".eeaDesk")
      .contains("38 pax joining")
      .get(".eeaDesk")
      .contains("6 min wait")
      .get(".eeaDesk > :nth-child(4) > .fa")
      .should('have.class', 'fa-arrow-up')
      .get(".next-bar > a")
      .click()
      .get(".time-label")
      .contains("14:30 - 14:45")
      .get(".eeaDesk")
      .contains("10 min wait")
      .get(".eeaDesk > :nth-child(4) > .fa")
      .should('have.class', 'fa-arrow-up')
      .get(".next-bar > a")
      .get(".prev-bar > a")
      .click()
      .get(".prev-bar > a")
      .click()
      .get(".time-label")
      .contains("14:00 - 14:15")
      .get(".eeaDesk")
      .contains("0 min wait")
      .get(".eeaDesk > :nth-child(4) > .fa")
      .should('have.class', 'fa-arrow-right')
  })

});
<commit_msg>DRT-5777 update E2E tests to use new markup<commit_after>let moment = require('moment-timezone');
require('moment/locale/en-gb');
moment.locale("en-gb");

describe('Viewing the terminal dashboard page', function () {

  beforeEach(function () {
    cy.deleteData();
  });

  it("should display a box for every queue in the terminal", () => {

    const schDateString = moment().hours(14).minutes(10).seconds(0).toISOString();
    const viewingDateString = moment().hours(14).minutes(15).seconds(0).toISOString();
    cy
      .addFlightWithFlightCode("TS0100", schDateString)
      .asABorderForceOfficerWithRoles(["terminal-dashboard"])
      .navigateHome()
      .visit("/#terminal/T1/dashboard/summary/?start=" + viewingDateString)
      .get(".pax-bar")
      .contains("51 passengers")
      .get(".time-label")
      .contains("14:15 - 14:30")
      .get(".eeaDesk")
      .contains("38 pax joining")
      .get(".eeaDesk")
      .contains("6 min wait")
      .get(".eeaDesk > :nth-child(4) > .fa")
      .should('have.class', 'fa-arrow-up')
      .get(".next-bar")
      .click()
      .get(".time-label")
      .contains("14:30 - 14:45")
      .get(".eeaDesk")
      .contains("10 min wait")
      .get(".eeaDesk > :nth-child(4) > .fa")
      .should('have.class', 'fa-arrow-up')
      .get(".prev-bar")
      .click()
      .get(".prev-bar")
      .click()
      .get(".time-label")
      .contains("14:00 - 14:15")
      .get(".eeaDesk")
      .contains("0 min wait")
      .get(".eeaDesk > :nth-child(4) > .fa")
      .should('have.class', 'fa-arrow-right')
  })

});
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2008-2009
 * Sven Wagner-Boysen
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

if(!ORYX.Plugins)
	ORYX.Plugins = new Object();

ORYX.Plugins.Workflownets = {

	/**
	 *	Constructor
	 *	@param {Object} Facade: The Facade of the Editor
	 */
	construct: function(facade){
		this.facade = facade;

		this.facade.registerOnEvent("Workflownets.Activity.serialize", this.handleSerialize.bind(this));
	},

	handleSerialize: function(event) {
		var shape = event.shape;
		var data = event.data;
		console.log("serialize");
		var numOfOutgoings = shape.getOutgoingShapes().length;
		data.push({
				  name:"numOfOutgoings",
				  prefix:"oryx",
				  value:numOfOutgoings,
				  type:"literal"
				  });

		event.result = data;
	},
};

ORYX.Plugins.Workflownets = Clazz.extend(ORYX.Plugins.Workflownets);
<commit_msg>removed console.log<commit_after>/**
 * Copyright (c) 2008-2009
 * Sven Wagner-Boysen
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 **/

if(!ORYX.Plugins)
	ORYX.Plugins = new Object();

ORYX.Plugins.Workflownets = {

	/**
	 *	Constructor
	 *	@param {Object} Facade: The Facade of the Editor
	 */
	construct: function(facade){
		this.facade = facade;

		this.facade.registerOnEvent("Workflownets.Activity.serialize", this.handleSerialize.bind(this));
	},

	handleSerialize: function(event) {
		var shape = event.shape;
		var data = event.data;
		var numOfOutgoings = shape.getOutgoingShapes().length;
		data.push({
				  name:"numOfOutgoings",
				  prefix:"oryx",
				  value:numOfOutgoings,
				  type:"literal"
				  });

		event.result = data;
	},
};

ORYX.Plugins.Workflownets = Clazz.extend(ORYX.Plugins.Workflownets);
<|endoftext|>


<commit_before>angular.module('editor.views.recipes.recipe', ['ui.router',
  'editor.data.ingredients', 'editor.data.recipes', 'editor.data.styles',
  'editor.data.equipments', 'editor.data.settings', 'editor.services.calculator',
  'editor.views.recipes', 'editor.directives.range', 'editor.filters.recipe'])
  .config(['$stateProvider', function($stateProvider) {
    $stateProvider.state('recipes.recipe', {
      url: '/:id',
      views: {
        '': {
          templateUrl: 'editor/views/recipes.recipe/layout.html',
          controller: 'RecipesRecipeController'
        },
        'content@recipes.recipe': {
          templateUrl: 'editor/views/recipes.recipe/recipe.html'
        },
        'header@recipes.recipe': {
          templateUrl: 'editor/views/recipes.recipe/header.html'
        },
        'info@recipes.recipe': {
          templateUrl: 'editor/views/recipes.recipe/profile.html'
        }
      }
    });
  }])
  .controller('RecipesRecipeController', function($scope, $state, $stateParams,
    $filter, styles, equipments, recipes, ingredients, calculator, settings) {

    $scope.styles = styles;
    $scope.equipments = equipments;
    $scope.settings = settings;
    $scope.calc = calculator;

    var recipeId = $stateParams.id;
    $scope.recipeId = $stateParams.id;

    recipes.get(recipeId).then(function(recipe) {
      $scope.recipe = recipe;
      $scope.headerEdit = !recipe.name;

      // Go to last recipe if current recipe disappear
      $scope.recipes = recipes;
      $scope.$watch('(recipes.items | filter:{id:recipe.id}).length',
        function(len) {
          if( len > 0 ) { return; }
          $state.go('recipes.last');
        }
      );

    });

    // Update equipment
    $scope.$watch('recipe.equipment', function(id) {
      if( !id ) { return settings.defaults.equipment; }
      var found = equipments.items.filter(function(item) {
        return item.id == id;
      });
      $scope.equipment = found ? found[0] : settings.defaults.equipment;
    });

    // Watch for volume scaling
    $scope.$watch('recipe.finalVolume', function(newVolume, oldVolume) {
      if( !newVolume || !oldVolume ) { return; }
      if( settings.global.recipeAutoscale ) {
        calculator.recipeScaleVolume($scope.recipe, $scope.equipment, oldVolume, newVolume);
      }
    });

    // Watch for equipment scaling
    $scope.$watch('equipment', function(newEquipment, oldEquipment) {
      if( !newEquipment || !oldEquipment ) { return; }
      if( settings.global.recipeAutoscale ) {
        calculator.recipeScaleEquipment($scope.recipe, oldEquipment, newEquipment);
      }
    });

    $scope.copy = angular.copy;
    $scope.remove = function(array, item) {
      var index = array.indexOf(item);
      array.splice(index, 1);
    };

    // Ingredients
    $scope.ingredients = ingredients;

    $scope.numberOfIngredients = function(recipe) {
      if( !recipe ) { return 0; }
      return recipe.fermentables.length + recipe.hops.length
             + recipe.yeast.length + recipe.others.length;
    };

    $scope.addFermentable = function(recipe, fermentable) {
      var item = angular.copy(fermentable);
      item.weight = 0;
      recipe.fermentables.push(item);
    };

    $scope.addHop = function(recipe, hop) {
      var item = angular.copy(hop);
      item.weight = 0;
      item.time = 0;
      item.format = 'pellet';
      item.moment = 'boil';
      recipe.hops.push(item);
    };

    $scope.addOther = function(recipe, ingredient) {
      var item = angular.copy(ingredient);
      item.weight = 0;
      item.time = 0;
      item.moment = 'boil';
      recipe.others.push(item);
    };

  });

<commit_msg>views.recipes.recipe: Fix default equipment<commit_after>angular.module('editor.views.recipes.recipe', ['ui.router',
  'editor.data.ingredients', 'editor.data.recipes', 'editor.data.styles',
  'editor.data.equipments', 'editor.data.settings', 'editor.services.calculator',
  'editor.views.recipes', 'editor.directives.range', 'editor.filters.recipe'])
  .config(['$stateProvider', function($stateProvider) {
    $stateProvider.state('recipes.recipe', {
      url: '/:id',
      views: {
        '': {
          templateUrl: 'editor/views/recipes.recipe/layout.html',
          controller: 'RecipesRecipeController'
        },
        'content@recipes.recipe': {
          templateUrl: 'editor/views/recipes.recipe/recipe.html'
        },
        'header@recipes.recipe': {
          templateUrl: 'editor/views/recipes.recipe/header.html'
        },
        'info@recipes.recipe': {
          templateUrl: 'editor/views/recipes.recipe/profile.html'
        }
      }
    });
  }])
  .controller('RecipesRecipeController', function($scope, $state, $stateParams,
    $filter, styles, equipments, recipes, ingredients, calculator, settings) {

    $scope.styles = styles;
    $scope.equipments = equipments;
    $scope.settings = settings;
    $scope.calc = calculator;

    var recipeId = $stateParams.id;
    $scope.recipeId = $stateParams.id;

    recipes.get(recipeId).then(function(recipe) {
      $scope.recipe = recipe;
      $scope.headerEdit = !recipe.name;

      // Go to last recipe if current recipe disappear
      $scope.recipes = recipes;
      $scope.$watch('(recipes.items | filter:{id:recipe.id}).length',
        function(len) {
          if( len > 0 ) { return; }
          $state.go('recipes.last');
        }
      );

    });

    // Update equipment
    $scope.$watch('recipe.equipment', function(id) {
      var found = equipments.items.find(function(item) {
        return item.id == id;
      });
      $scope.equipment = found || settings.defaults.equipment;
    });

    // Watch for volume scaling
    $scope.$watch('recipe.finalVolume', function(newVolume, oldVolume) {
      if( !newVolume || !oldVolume ) { return; }
      if( settings.global.recipeAutoscale ) {
        calculator.recipeScaleVolume($scope.recipe, $scope.equipment, oldVolume, newVolume);
      }
    });

    // Watch for equipment scaling
    $scope.$watch('equipment', function(newEquipment, oldEquipment) {
      if( !newEquipment || !oldEquipment ) { return; }
      if( settings.global.recipeAutoscale ) {
        calculator.recipeScaleEquipment($scope.recipe, oldEquipment, newEquipment);
      }
    });

    $scope.copy = angular.copy;
    $scope.remove = function(array, item) {
      var index = array.indexOf(item);
      array.splice(index, 1);
    };

    // Ingredients
    $scope.ingredients = ingredients;

    $scope.numberOfIngredients = function(recipe) {
      if( !recipe ) { return 0; }
      return recipe.fermentables.length + recipe.hops.length
             + recipe.yeast.length + recipe.others.length;
    };

    $scope.addFermentable = function(recipe, fermentable) {
      var item = angular.copy(fermentable);
      item.weight = 0;
      recipe.fermentables.push(item);
    };

    $scope.addHop = function(recipe, hop) {
      var item = angular.copy(hop);
      item.weight = 0;
      item.time = 0;
      item.format = 'pellet';
      item.moment = 'boil';
      recipe.hops.push(item);
    };

    $scope.addOther = function(recipe, ingredient) {
      var item = angular.copy(ingredient);
      item.weight = 0;
      item.time = 0;
      item.moment = 'boil';
      recipe.others.push(item);
    };

  });

<|endoftext|>


<commit_before>dojo.provide("dijit.layout._LayoutWidget");

dojo.require("dijit._Widget");
dojo.require("dijit._Container");

dojo.declare("dijit.layout._LayoutWidget",
	[dijit._Widget, dijit._Container, dijit._Contained],
	{
		// summary
		//		Mixin for widgets that contain a list of children like SplitContainer.
		//		Widgets which mixin this code must define layout() to lay out the children

		isLayoutContainer: true,

		postCreate: function(){
			dojo.addClass(this.domNode, "dijitContainer");
		},

		startup: function(){
			// summary:
			//		Called after all the widgets have been instantiated and their
			//		dom nodes have been inserted somewhere under document.body.
			//
			//		Widgets should override this method to do any initialization
			//		dependent on other widgets existing, and then call
			//		this superclass method to finish things off.
			//
			//		startup() in subclasses shouldn't do anything
			//		size related because the size of the widget hasn't been set yet.

			if(this._started){
				return;
			}
			this._started=true;

			if(this.getChildren){
				dojo.forEach(this.getChildren(), function(child){ child.startup(); });
			}

			// If I am a top level widget
			if(!this.getParent || !this.getParent()){
				// Do recursive sizing and layout of all my descendants
				// (passing in no argument to resize means that it has to glean the size itself)
				this.resize();

				// since my parent isn't a layout container, and my style is width=height=100% (or something similar),
				// then I need to watch when the window resizes, and size myself accordingly
				// (passing in no argument to resize means that it has to glean the size itself)
				this.connect(window, 'onresize', function(){this.resize();});
			}
		},

		resize: function(args){
			// summary:
			//		Explicitly set this widget's size (in pixels),
			//		and then call layout() to resize contents (and maybe adjust child widgets)
			//
			// args: Object?
			//		{w: int, h: int, l: int, t: int}

			var node = this.domNode;

			// set margin box size, unless it wasn't specified, in which case use current size
			if(args){
				dojo.marginBox(node, args);

				// set offset of the node
				if(args.t){ node.style.top = args.t + "px"; }
				if(args.l){ node.style.left = args.l + "px"; }
			}
			// If either height or width wasn't specified by the user, then query node for it.
			// But note that setting the margin box and then immediately querying dimensions may return
			// inaccurate results, so try not to depend on it.
			var mb = dojo.mixin(dojo.marginBox(node), args||{});

			// Save the size of my content box.
			this._contentBox = dijit.layout.marginBox2contentBox(node, mb);

			// Callback for widget to adjust size of it's children
			this.layout();
		},

		layout: function(){
			//	summary
			//		Widgets override this method to size & position their contents/children.
			//		When this is called this._contentBox is guaranteed to be set (see resize()).
			//
			//		This is called after startup(), and also when the widget's size has been
			//		changed.
		}
	}
);

dijit.layout.marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){
	// summary:
	//		Given the margin-box size of a node, return it's content box size.
	//		Functions like dojo.contentBox() but is more reliable since it doesn't have
	//		to wait for the browser to compute sizes.
	var cs = dojo.getComputedStyle(node);
	var me=dojo._getMarginExtents(node, cs);
	var pb=dojo._getPadBorderExtents(node, cs);
	return {
		l: dojo._toPixelValue(node, cs.paddingLeft),
		t: dojo._toPixelValue(node, cs.paddingTop),
		w: mb.w - (me.w + pb.w),
		h: mb.h - (me.h + pb.h)
	};
};

dijit.layout.layoutChildren = function(/*DomNode*/ container, /*Object*/ dim, /*Object[]*/ children){
	/**
	 * summary
	 *		Layout a bunch of child dom nodes within a parent dom node
	 * container:
	 *		parent node
	 * dim:
	 *		{l, t, w, h} object specifying dimensions of container into which to place children
	 * children:
	 *		an array like [ {domNode: foo, layoutAlign: "bottom" }, {domNode: bar, layoutAlign: "client"} ]
	 */

	// copy dim because we are going to modify it
	dim = dojo.mixin({}, dim);

	dojo.addClass(container, "dijitLayoutContainer");

	// set positions/sizes
	var ret=true;
	dojo.forEach(children, function(child){
		var elm=child.domNode,
			pos=child.layoutAlign;

		// set elem to upper left corner of unused space; may move it later
		var elmStyle = elm.style;
		elmStyle.left = dim.l+"px";
		elmStyle.top = dim.t+"px";
		elmStyle.bottom = elmStyle.right = "auto";

		var capitalize = function(word){
			return word.substring(0,1).toUpperCase() + word.substring(1);
		};
		var size = function(widget, dim){
			// size the child
			widget.resize ? widget.resize(dim) : dojo.marginBox(widget.domNode, dim);

			// record child's size, but favor our own numbers when we have them.
			// the browser lies sometimes
			dojo.mixin(widget, dojo.marginBox(widget.domNode));
			dojo.mixin(widget, dim);
		};

		dojo.addClass(elm, "dijitAlign" + capitalize(pos));

		// set size && adjust record of remaining space.
		// note that setting the width of a <div> may affect it's height.
		if (pos=="top" || pos=="bottom"){
			size(child, { w: dim.w });
			dim.h -= child.h;
			if(pos=="top"){
				dim.t += child.h;
			}else{
				elmStyle.top = dim.t + dim.h + "px";
			}
		}else if(pos=="left" || pos=="right"){
			size(child, { h: dim.h });
			dim.w -= child.w;
			if(pos=="left"){
				dim.l += child.w;
			}else{
				elmStyle.left = dim.l + dim.w + "px";
			}
		}else if(pos=="flood" || pos=="client"){
			size(child, dim);
		}
	});
	return ret;
};
<commit_msg>make layoutChildren() friendly for lists of DOM nodes (not just widgets). Also, make sure that capitalize() and size() aren't always re-defined should the interpreter be written poorly. Fixes #4121<commit_after>dojo.provide("dijit.layout._LayoutWidget");

dojo.require("dijit._Widget");
dojo.require("dijit._Container");

dojo.declare("dijit.layout._LayoutWidget",
	[dijit._Widget, dijit._Container, dijit._Contained],
	{
		// summary
		//		Mixin for widgets that contain a list of children like SplitContainer.
		//		Widgets which mixin this code must define layout() to lay out the children

		isLayoutContainer: true,

		postCreate: function(){
			dojo.addClass(this.domNode, "dijitContainer");
		},

		startup: function(){
			// summary:
			//		Called after all the widgets have been instantiated and their
			//		dom nodes have been inserted somewhere under document.body.
			//
			//		Widgets should override this method to do any initialization
			//		dependent on other widgets existing, and then call
			//		this superclass method to finish things off.
			//
			//		startup() in subclasses shouldn't do anything
			//		size related because the size of the widget hasn't been set yet.

			if(this._started){
				return;
			}
			this._started=true;

			if(this.getChildren){
				dojo.forEach(this.getChildren(), function(child){ child.startup(); });
			}

			// If I am a top level widget
			if(!this.getParent || !this.getParent()){
				// Do recursive sizing and layout of all my descendants
				// (passing in no argument to resize means that it has to glean the size itself)
				this.resize();

				// since my parent isn't a layout container, and my style is width=height=100% (or something similar),
				// then I need to watch when the window resizes, and size myself accordingly
				// (passing in no argument to resize means that it has to glean the size itself)
				this.connect(window, 'onresize', function(){this.resize();});
			}
		},

		resize: function(args){
			// summary:
			//		Explicitly set this widget's size (in pixels),
			//		and then call layout() to resize contents (and maybe adjust child widgets)
			//
			// args: Object?
			//		{w: int, h: int, l: int, t: int}

			var node = this.domNode;

			// set margin box size, unless it wasn't specified, in which case use current size
			if(args){
				dojo.marginBox(node, args);

				// set offset of the node
				if(args.t){ node.style.top = args.t + "px"; }
				if(args.l){ node.style.left = args.l + "px"; }
			}
			// If either height or width wasn't specified by the user, then query node for it.
			// But note that setting the margin box and then immediately querying dimensions may return
			// inaccurate results, so try not to depend on it.
			var mb = dojo.mixin(dojo.marginBox(node), args||{});

			// Save the size of my content box.
			this._contentBox = dijit.layout.marginBox2contentBox(node, mb);

			// Callback for widget to adjust size of it's children
			this.layout();
		},

		layout: function(){
			//	summary
			//		Widgets override this method to size & position their contents/children.
			//		When this is called this._contentBox is guaranteed to be set (see resize()).
			//
			//		This is called after startup(), and also when the widget's size has been
			//		changed.
		}
	}
);

dijit.layout.marginBox2contentBox = function(/*DomNode*/ node, /*Object*/ mb){
	// summary:
	//		Given the margin-box size of a node, return it's content box size.
	//		Functions like dojo.contentBox() but is more reliable since it doesn't have
	//		to wait for the browser to compute sizes.
	var cs = dojo.getComputedStyle(node);
	var me=dojo._getMarginExtents(node, cs);
	var pb=dojo._getPadBorderExtents(node, cs);
	return {
		l: dojo._toPixelValue(node, cs.paddingLeft),
		t: dojo._toPixelValue(node, cs.paddingTop),
		w: mb.w - (me.w + pb.w),
		h: mb.h - (me.h + pb.h)
	};
};

(function(){
	var capitalize = function(word){
		return word.substring(0,1).toUpperCase() + word.substring(1);
	};

	var size = function(widget, dim){
		// size the child
		widget.resize ? widget.resize(dim) : dojo.marginBox(widget.domNode, dim);

		// record child's size, but favor our own numbers when we have them.
		// the browser lies sometimes
		dojo.mixin(widget, dojo.marginBox(widget.domNode));
		dojo.mixin(widget, dim);
	};

	dijit.layout.layoutChildren = function(/*DomNode*/ container, /*Object*/ dim, /*Object[]*/ children){
		/**
		 * summary
		 *		Layout a bunch of child dom nodes within a parent dom node
		 * container:
		 *		parent node
		 * dim:
		 *		{l, t, w, h} object specifying dimensions of container into which to place children
		 * children:
		 *		an array like [ {domNode: foo, layoutAlign: "bottom" }, {domNode: bar, layoutAlign: "client"} ]
		 */

		// copy dim because we are going to modify it
		dim = dojo.mixin({}, dim);

		dojo.addClass(container, "dijitLayoutContainer");

		// set positions/sizes
		var ret=true;
		dojo.forEach(children, function(child){
			var elm = (child["domNode"]||child),
				pos = (child["layoutAlign"]||child.getAttribute("layoutAlign"));

			// set elem to upper left corner of unused space; may move it later
			var elmStyle = elm.style;
			elmStyle.left = dim.l+"px";
			elmStyle.top = dim.t+"px";
			elmStyle.bottom = elmStyle.right = "auto";

			dojo.addClass(elm, "dijitAlign" + capitalize(pos));

			// set size && adjust record of remaining space.
			// note that setting the width of a <div> may affect it's height.
			if(pos=="top" || pos=="bottom"){
				size(child, { w: dim.w });
				dim.h -= child.h;
				if(pos=="top"){
					dim.t += child.h;
				}else{
					elmStyle.top = dim.t + dim.h + "px";
				}
			}else if(pos=="left" || pos=="right"){
				size(child, { h: dim.h });
				dim.w -= child.w;
				if(pos=="left"){
					dim.l += child.w;
				}else{
					elmStyle.left = dim.l + dim.w + "px";
				}
			}else if(pos=="flood" || pos=="client"){
				size(child, dim);
			}
		});
		return ret;
	};

})();
<|endoftext|>


<commit_before>/*
 * This file implements several helpers for fixing up full-page web apps on touchscreen
 * browsers:
 *
 * AndroidDetectWindowSize
 * EmulateDoubleClick
 * ResponsiveSingleClick
 * PreventDragScrolling
 *
 * Most of these are annoying hacks to work around the fact that WebKit on browsers was
 * designed with displaying scrolling webpages in mind, apparently without consideration
 * for full-screen applications: pages that should fill the screen at all times.  Most
 * of the browser mobile hacks no longer make sense: separate display viewports, touch
 * dragging, double-click zooming and their associated side-effects.
 */

/*
 * AndroidDetectWindowSize
 *
 * Implementing a full-page web app for Android is hard, because if you set the page to
 * "width: 100%; height: 100%;" it'll eat a big chunk of the screen with the address bar
 * which can't be scrolled off in that configuration.  We have to play games to figure out
 * the real size of the window, and set the body size to it explicitly.  This handler does
 * the following:
 *
 * - capture resize events
 * - cancel the resize event; we'll fire it again when we're done
 * - enable a large padding div, to ensure that we can scroll the window downward
 * - switch the body to overflow: auto
 * - window.scrollTo(0, 1) to scroll the address bar off screen, which increases the window
 *   size to the maximum
 * - wait a little while; unbelievably, window.scrollTo on Android animates rather than
 *   snapping to the position as all sane browsers do
 * - set the body to the size of the window
 * - hide the padding div and set the body back to overflow: hidden
 * - synthesize a new resize event to continue other event handlers that we originally cancelled
 *
 * resize will always be fired at least once as a result of constructing this class.
 *
 * This is only used on Android.
 */

function AndroidDetectWindowSize()
{
  /* This is shown to make sure we can scroll the address bar off. */
  this.padding = document.createElement("DIV");
  this.padding.setStyle({width: "5000px", height: "5000px"});
  this.padding.hide();
  document.documentElement.appendChild(this.padding);

  this.window_size = [0, 0];
  this.finish = this.finish.bind(this);
  this.event_onresize = this.event_onresize.bindAsEventListener(this);

  this.finish_timer = null;
  this.active = false;

  /* This will run a detection cycle, which will fire resize and then leave us capturing resize. */
  this.begin();
}

/* Return true if Android resize handling is needed. */
AndroidDetectWindowSize.required = function()
{
  // XXX: be more specific
  return navigator.userAgent.indexOf("Android") != -1;
}

/* Dispatch a resize event that we won't intercept, and which will continue on to regular
 * listeners.  This is used after we've completed processing. */
AndroidDetectWindowSize.prototype.dispatch_resize_event = function()
{
  debug.log("dispatch");
  var e = document.createEvent("Event");
  e.from_window_size_detection = true;
  e.initEvent("resize", true, true);
  document.documentElement.dispatchEvent(e);
}

AndroidDetectWindowSize.prototype.begin = function()
{
  if(this.active)
    return;

  var initial_window_size = this.current_window_size();
  if(this.window_size && initial_window_size[0] == this.window_size[0] && initial_window_size[1] == this.window_size[1])
  {
    debug.log("skipped");
    return;
  }

  debug.log("begin");
  document.body.setStyle({overflow: "auto"});
  this.window_size = initial_window_size;
  this.padding.show();
  this.active = true;
  window.removeEventListener("resize", this.event_onresize, true);

  if(this.finish_timer != null)
    this.finish_timer = window.clearTimeout(this.finish_timer);
  this.finish_timer = window.setTimeout(this.finish, 250);

  window.scrollTo(0, 1);
}

AndroidDetectWindowSize.prototype.end = function()
{
  if(!this.active)
    return;
  this.active = false;

  this.padding.hide();
  document.body.setStyle({overflow: "hidden"});
  window.addEventListener("resize", this.event_onresize, true);
}

AndroidDetectWindowSize.prototype.current_window_size = function()
{
  return [window.innerWidth, window.innerHeight];
}

AndroidDetectWindowSize.prototype.finish = function()
{
  if(!this.active)
    return;
  debug.log("finish()");
  this.end();

  this.window_size[0] = Math.max(this.window_size[0], window.innerWidth);
  this.window_size[1] = Math.max(this.window_size[1], window.innerHeight);

  /* If the orientation's been changed, start over. */
  var was_landscape = this.window_size[0] > this.window_size[1];
  var is_landscape = window.innerWidth > window.innerHeight;
  if(was_landscape != is_landscape)
  {
    debug.log("restart: orientation changed");
    this.end();
    this.begin();
    return;
  }

  // We need to fudge the height up a pixel, or in many cases we'll end up with a white line
  // at the bottom of the screen.  This seems to be sub-pixel rounding error.
  debug.log("new window size: " + this.window_size[0] + "x" + this.window_size[1]);
  document.body.setStyle({width: this.window_size[0] + "px", height: (this.window_size[1]+1) + "px"});

  this.dispatch_resize_event();
}

AndroidDetectWindowSize.prototype.event_onresize = function(e)
{
  /* Ignore events that we generated ourselves after completion. */
  if(e.from_window_size_detection)
  {
    debug.log("ignored");
    return;
  }

  debug.log("stopping resize event");
  e.stopPropagation();

  /* A resize event starts a new detection cycle, if we're not already in one. */
  if(!this.active)
  {
    debug.log("resize");
    this.begin();
  }
}

/*
 * Work around a bug on many touchscreen browsers: even when the page isn't
 * zoomable, dblclick is never fired.  We have to emulate it.
 *
 * This isn't an exact emulation of the event behavior:
 *
 * - It triggers from touchstart rather than mousedown.  The second mousedown
 *   of a double click isn't being fired reliably in Android's WebKit.
 *
 * - preventDefault on the triggering event should prevent a dblclick, but
 *   we can't find out if it's been called; there's nothing like Firefox's
 *   getPreventDefault.  We could mostly emulate this by overriding
 *   Event.preventDefault to set a flag that we can read.
 *
 * - The conditions for a double click won't match the ones of the platform.
 *
 * This is needed on Android and iPhone's WebKit.
 */

function EmulateDoubleClick()
{
  this.touchstart_event = this.touchstart_event.bindAsEventListener(this);
  this.last_click_event = null;

  window.addEventListener("touchstart", this.touchstart_event, false);
}

EmulateDoubleClick.prototype.touchstart_event = function(event)
{
  var this_click = event;
  var last_click  = this.last_click_event;

  this.last_click_event = this_click;
  if(last_click == null)
      return;

  /* Check that not too much time has passed. */
  var time_since_previous = this_click.timeStamp - last_click.timeStamp;
  if(time_since_previous > 500)
    return;

  /* Check that the clicks aren't too far apart. */
  var distance = Math.pow(this_click.screenX - last_click.screenX, 2) + Math.pow(this_click.screenY - last_click.screenY, 2);
  if(distance > 8)
    return;

  /* Make sure these attributes match in both clicks: */
  var properties_to_match = ["ctrlKey", "altKey", "shiftKey", "metaKey", "target", "button"];
  for(var i = 0; i < properties_to_match.length; ++i)
  {
    var name = properties_to_match[i]
    if(this_click[name] != last_click[name])
      return;
  }

  /* Synthesize a dblclick event. */
  var e = document.createEvent("MouseEvent");
  e.initMouseEvent("dblclick", true, true, window,
                     2,
                     this_click.screenX, this_click.screenY,
                     this_click.clientX, this_click.clientY,
                     this_click.ctrlKey, this_click.altKey,
                     this_click.shiftKey, this_click.metaKey,
                     this_click.button, null);

  this.last_click_event = null;
  this_click.target.dispatchEvent(e);
}

/*
 * Mobile WebKit has serious problems with the click event: it delays them for the
 * entire double-click timeout, and if a double-click happens it doesn't deliver the
 * click at all.  This makes clicks unresponsive, and it has this behavior even
 * when the page can't be zoomed, which means nothing happens at all.
 *
 * Generate click events from touchend events to bypass this mess.
 *
 * XXX: This needs to understand multitouch.
 */
ResponsiveSingleClick = function()
{
  this.click_event = this.click_event.bindAsEventListener(this);
  this.touchstart_event = this.touchstart_event.bindAsEventListener(this);
  this.touchend_event = this.touchend_event.bindAsEventListener(this);

  this.last_touch = null;

  window.addEventListener("touchstart", this.touchstart_event, false);
  window.addEventListener("touchend", this.touchend_event, false);

  /* This is a capturing listener, so we can intercept clicks before they're
   * delivered to anyone. */
  window.addEventListener("click", this.click_event, true);
}

ResponsiveSingleClick.prototype.touchstart_event = function(event)
{
  /* Watch out: in Android 2.1's browser, the event.touches array and the items inside
   * it are actually modified in-place when the user drags.  That means that we can't just
   * save the entire array for comparing in touchend. */
  var touch = event.touches.item(0);
  this.last_touch = [touch.screenX, touch.screenY];
}

ResponsiveSingleClick.prototype.touchend_event = function(event)
{
  var touch = event.changedTouches.item(0);
  var this_touch = [touch.screenX, touch.screenY];
  var last_touch = this.last_touch;

  /* Don't trigger a click if the point has moved too far. */
  var distance = distance_squared(this_touch[0], this_touch[1], last_touch[0], last_touch[1]);
  if(distance > 50)
    return;

  var e = document.createEvent("MouseEvent");
  e.initMouseEvent("click", true, true, window,
                     1,
                     event.screenX, event.screenY,
                     event.clientX, event.clientY,
                     event.ctrlKey, event.altKey,
                     event.shiftKey, event.metaKey,
                     0, /* touch clicks are always button 0 - maybe not for multitouch */
                     null);
  e.synthesized_click = true;

  /* If we dispatch the click immediately, EmulateDoubleClick won't receive a
   * touchstart for the next click.  Defer dispatching it until we return. */
  (function() { event.target.dispatchEvent(e); }).defer();
}

/* Capture and cancel all clicks except the ones we generate. */
ResponsiveSingleClick.prototype.click_event = function(event)
{
  if(!event.synthesized_click)
    event.stop();
}

/* Stop all touchmove events on the document, to prevent dragging the window around. */
PreventDragScrolling = function()
{
  Element.observe(document, "touchmove", function(event) {
    event.preventDefault();
  });
}

var InitializeFullScreenBrowserHandlers = function()
{
  /* These handlers deal with heavily browser-specific issues.  Only install them
   * on browsers that have been tested to need them. */
  if(navigator.userAgent.indexOf("Android") != -1 && navigator.userAgent.indexOf("WebKit") != -1)
  {
    new ResponsiveSingleClick();
    new EmulateDoubleClick();
    PreventDragScrolling();
  }
  else if((navigator.userAgent.indexOf("iPhone") != -1 || navigator.userAgent.indexOf("iPad") != -1)
      && navigator.userAgent.indexOf("WebKit") != -1)
  {
    new ResponsiveSingleClick();
    new EmulateDoubleClick();
  }
}

<commit_msg>handlers needed on iPhone too<commit_after>/*
 * This file implements several helpers for fixing up full-page web apps on touchscreen
 * browsers:
 *
 * AndroidDetectWindowSize
 * EmulateDoubleClick
 * ResponsiveSingleClick
 * PreventDragScrolling
 *
 * Most of these are annoying hacks to work around the fact that WebKit on browsers was
 * designed with displaying scrolling webpages in mind, apparently without consideration
 * for full-screen applications: pages that should fill the screen at all times.  Most
 * of the browser mobile hacks no longer make sense: separate display viewports, touch
 * dragging, double-click zooming and their associated side-effects.
 */

/*
 * AndroidDetectWindowSize
 *
 * Implementing a full-page web app for Android is hard, because if you set the page to
 * "width: 100%; height: 100%;" it'll eat a big chunk of the screen with the address bar
 * which can't be scrolled off in that configuration.  We have to play games to figure out
 * the real size of the window, and set the body size to it explicitly.  This handler does
 * the following:
 *
 * - capture resize events
 * - cancel the resize event; we'll fire it again when we're done
 * - enable a large padding div, to ensure that we can scroll the window downward
 * - switch the body to overflow: auto
 * - window.scrollTo(0, 1) to scroll the address bar off screen, which increases the window
 *   size to the maximum
 * - wait a little while; unbelievably, window.scrollTo on Android animates rather than
 *   snapping to the position as all sane browsers do
 * - set the body to the size of the window
 * - hide the padding div and set the body back to overflow: hidden
 * - synthesize a new resize event to continue other event handlers that we originally cancelled
 *
 * resize will always be fired at least once as a result of constructing this class.
 *
 * This is only used on Android.
 */

function AndroidDetectWindowSize()
{
  /* This is shown to make sure we can scroll the address bar off. */
  this.padding = document.createElement("DIV");
  this.padding.setStyle({width: "5000px", height: "5000px"});
  this.padding.hide();
  document.documentElement.appendChild(this.padding);

  this.window_size = [0, 0];
  this.finish = this.finish.bind(this);
  this.event_onresize = this.event_onresize.bindAsEventListener(this);

  this.finish_timer = null;
  this.active = false;

  /* This will run a detection cycle, which will fire resize and then leave us capturing resize. */
  this.begin();
}

/* Return true if Android resize handling is needed. */
AndroidDetectWindowSize.required = function()
{
  // XXX: be more specific
  return navigator.userAgent.indexOf("Android") != -1;
}

/* Dispatch a resize event that we won't intercept, and which will continue on to regular
 * listeners.  This is used after we've completed processing. */
AndroidDetectWindowSize.prototype.dispatch_resize_event = function()
{
  debug.log("dispatch");
  var e = document.createEvent("Event");
  e.from_window_size_detection = true;
  e.initEvent("resize", true, true);
  document.documentElement.dispatchEvent(e);
}

AndroidDetectWindowSize.prototype.begin = function()
{
  if(this.active)
    return;

  var initial_window_size = this.current_window_size();
  if(this.window_size && initial_window_size[0] == this.window_size[0] && initial_window_size[1] == this.window_size[1])
  {
    debug.log("skipped");
    return;
  }

  debug.log("begin");
  document.body.setStyle({overflow: "auto"});
  this.window_size = initial_window_size;
  this.padding.show();
  this.active = true;
  window.removeEventListener("resize", this.event_onresize, true);

  if(this.finish_timer != null)
    this.finish_timer = window.clearTimeout(this.finish_timer);
  this.finish_timer = window.setTimeout(this.finish, 250);

  window.scrollTo(0, 1);
}

AndroidDetectWindowSize.prototype.end = function()
{
  if(!this.active)
    return;
  this.active = false;

  this.padding.hide();
  document.body.setStyle({overflow: "hidden"});
  window.addEventListener("resize", this.event_onresize, true);
}

AndroidDetectWindowSize.prototype.current_window_size = function()
{
  return [window.innerWidth, window.innerHeight];
}

AndroidDetectWindowSize.prototype.finish = function()
{
  if(!this.active)
    return;
  debug.log("finish()");
  this.end();

  this.window_size[0] = Math.max(this.window_size[0], window.innerWidth);
  this.window_size[1] = Math.max(this.window_size[1], window.innerHeight);

  /* If the orientation's been changed, start over. */
  var was_landscape = this.window_size[0] > this.window_size[1];
  var is_landscape = window.innerWidth > window.innerHeight;
  if(was_landscape != is_landscape)
  {
    debug.log("restart: orientation changed");
    this.end();
    this.begin();
    return;
  }

  // We need to fudge the height up a pixel, or in many cases we'll end up with a white line
  // at the bottom of the screen.  This seems to be sub-pixel rounding error.
  debug.log("new window size: " + this.window_size[0] + "x" + this.window_size[1]);
  document.body.setStyle({width: this.window_size[0] + "px", height: (this.window_size[1]+1) + "px"});

  this.dispatch_resize_event();
}

AndroidDetectWindowSize.prototype.event_onresize = function(e)
{
  /* Ignore events that we generated ourselves after completion. */
  if(e.from_window_size_detection)
  {
    debug.log("ignored");
    return;
  }

  debug.log("stopping resize event");
  e.stopPropagation();

  /* A resize event starts a new detection cycle, if we're not already in one. */
  if(!this.active)
  {
    debug.log("resize");
    this.begin();
  }
}

/*
 * Work around a bug on many touchscreen browsers: even when the page isn't
 * zoomable, dblclick is never fired.  We have to emulate it.
 *
 * This isn't an exact emulation of the event behavior:
 *
 * - It triggers from touchstart rather than mousedown.  The second mousedown
 *   of a double click isn't being fired reliably in Android's WebKit.
 *
 * - preventDefault on the triggering event should prevent a dblclick, but
 *   we can't find out if it's been called; there's nothing like Firefox's
 *   getPreventDefault.  We could mostly emulate this by overriding
 *   Event.preventDefault to set a flag that we can read.
 *
 * - The conditions for a double click won't match the ones of the platform.
 *
 * This is needed on Android and iPhone's WebKit.
 */

function EmulateDoubleClick()
{
  this.touchstart_event = this.touchstart_event.bindAsEventListener(this);
  this.last_click_event = null;

  window.addEventListener("touchstart", this.touchstart_event, false);
}

EmulateDoubleClick.prototype.touchstart_event = function(event)
{
  var this_click = event;
  var last_click  = this.last_click_event;

  this.last_click_event = this_click;
  if(last_click == null)
      return;

  /* Check that not too much time has passed. */
  var time_since_previous = this_click.timeStamp - last_click.timeStamp;
  if(time_since_previous > 500)
    return;

  /* Check that the clicks aren't too far apart. */
  var distance = Math.pow(this_click.screenX - last_click.screenX, 2) + Math.pow(this_click.screenY - last_click.screenY, 2);
  if(distance > 8)
    return;

  /* Make sure these attributes match in both clicks: */
  var properties_to_match = ["ctrlKey", "altKey", "shiftKey", "metaKey", "target", "button"];
  for(var i = 0; i < properties_to_match.length; ++i)
  {
    var name = properties_to_match[i]
    if(this_click[name] != last_click[name])
      return;
  }

  /* Synthesize a dblclick event. */
  var e = document.createEvent("MouseEvent");
  e.initMouseEvent("dblclick", true, true, window,
                     2,
                     this_click.screenX, this_click.screenY,
                     this_click.clientX, this_click.clientY,
                     this_click.ctrlKey, this_click.altKey,
                     this_click.shiftKey, this_click.metaKey,
                     this_click.button, null);

  this.last_click_event = null;
  this_click.target.dispatchEvent(e);
}

/*
 * Mobile WebKit has serious problems with the click event: it delays them for the
 * entire double-click timeout, and if a double-click happens it doesn't deliver the
 * click at all.  This makes clicks unresponsive, and it has this behavior even
 * when the page can't be zoomed, which means nothing happens at all.
 *
 * Generate click events from touchend events to bypass this mess.
 *
 * XXX: This needs to understand multitouch.
 */
ResponsiveSingleClick = function()
{
  this.click_event = this.click_event.bindAsEventListener(this);
  this.touchstart_event = this.touchstart_event.bindAsEventListener(this);
  this.touchend_event = this.touchend_event.bindAsEventListener(this);

  this.last_touch = null;

  window.addEventListener("touchstart", this.touchstart_event, false);
  window.addEventListener("touchend", this.touchend_event, false);

  /* This is a capturing listener, so we can intercept clicks before they're
   * delivered to anyone. */
  window.addEventListener("click", this.click_event, true);
}

ResponsiveSingleClick.prototype.touchstart_event = function(event)
{
  /* Watch out: in Android 2.1's browser, the event.touches array and the items inside
   * it are actually modified in-place when the user drags.  That means that we can't just
   * save the entire array for comparing in touchend. */
  var touch = event.touches.item(0);
  this.last_touch = [touch.screenX, touch.screenY];
}

ResponsiveSingleClick.prototype.touchend_event = function(event)
{
  var touch = event.changedTouches.item(0);
  var this_touch = [touch.screenX, touch.screenY];
  var last_touch = this.last_touch;

  /* Don't trigger a click if the point has moved too far. */
  var distance = distance_squared(this_touch[0], this_touch[1], last_touch[0], last_touch[1]);
  if(distance > 50)
    return;

  var e = document.createEvent("MouseEvent");
  e.initMouseEvent("click", true, true, window,
                     1,
                     event.screenX, event.screenY,
                     event.clientX, event.clientY,
                     event.ctrlKey, event.altKey,
                     event.shiftKey, event.metaKey,
                     0, /* touch clicks are always button 0 - maybe not for multitouch */
                     null);
  e.synthesized_click = true;

  /* If we dispatch the click immediately, EmulateDoubleClick won't receive a
   * touchstart for the next click.  Defer dispatching it until we return. */
  (function() { event.target.dispatchEvent(e); }).defer();
}

/* Capture and cancel all clicks except the ones we generate. */
ResponsiveSingleClick.prototype.click_event = function(event)
{
  if(!event.synthesized_click)
    event.stop();
}

/* Stop all touchmove events on the document, to prevent dragging the window around. */
PreventDragScrolling = function()
{
  Element.observe(document, "touchmove", function(event) {
    event.preventDefault();
  });
}

var InitializeFullScreenBrowserHandlers = function()
{
  /* These handlers deal with heavily browser-specific issues.  Only install them
   * on browsers that have been tested to need them. */
  if(navigator.userAgent.indexOf("Android") != -1 && navigator.userAgent.indexOf("WebKit") != -1)
  {
    new ResponsiveSingleClick();
    new EmulateDoubleClick();
  }
  else if((navigator.userAgent.indexOf("iPhone") != -1 || navigator.userAgent.indexOf("iPad") != -1)
      && navigator.userAgent.indexOf("WebKit") != -1)
  {
    new ResponsiveSingleClick();
    new EmulateDoubleClick();
  }

  PreventDragScrolling();
}

<|endoftext|>


<commit_before>'use strict';

const fs = require('fs-extra');
const temp = require('temp');
const util = require('util');

const mkdirTemp = util.promisify(temp.mkdir);

function mkTmpDirIn(dir) {
  return fs.ensureDir(dir).then(() => mkdirTemp({ dir }));
}

module.exports = mkTmpDirIn;
<commit_msg>Convert mkTmpDirIn to async/await syntax<commit_after>'use strict';

const fs = require('fs-extra');
const temp = require('temp');
const util = require('util');

const mkdirTemp = util.promisify(temp.mkdir);

async function mkTmpDirIn(dir) {
  await fs.ensureDir(dir);
  return mkdirTemp({ dir });
}

module.exports = mkTmpDirIn;
<|endoftext|>


<commit_before>// Simple audio event module and wire-up for a futuristic compturized GUI.
// Should be called at the end of the page, when document is nearly ready.
// Authored by justin.warwick@gmail.com
// Released under MIT license; see LICENSE file.
//
// Assumes you have this at the bottom of your document:
//   <audio id="audUmmy"/><!--Just leave this here, just needed to enable automatic query canplay in the initialization code to load compatible media types -->

var lcarsAudio = {
	audioExtensions: {
		ogg: "ogg",
		webm:"webm",
		mp4: "aac"
	},
	audioPath: "./audio/",
	audioType: "",
	//Inidividual Sound Element holders
	audTactInputAcknowledge:    null,
	audTactInputAltAcknowledge: null,
	audTactInputNegAcknowledge: null,
	audAlert:                   null,
	audRedAlert:                null,
	audReady:                   null,
	initialize: function(auDummy) {
		//Determine browser capability and willingness
		for (var key in this.audioExtensions) {
			if (auDummy.canPlayType("audio/"+this.audioExtensions[key]) != '') {
				auDummy.src = this.audioPath + "ready." + key;
				//TODO: test it further??
				console.log(auDummy.src + "   " +auDummy.src.lastIndexOf("."));
				this.audioType =  auDummy.src.slice(auDummy.src.lastIndexOf(".") + 1);
				console.log("lcarsAudio: We have a winner: " + this.audioType);
				break;
			}
		}
		this.audTactInputAcknowledge = new Audio("audio/tactinput_acknowledge." + this.audioType);
		this.audTactInputAltAcknowledge = new Audio("audio/tactinput_alt_acknowledge." + this.audioType);
		this.audTactInputNegAcknowledge = new Audio("audio/tactinput_neg_acknowledge." + this.audioType);
		this.audAlert = new Audio("audio/alert." + this.audioType);
		this.audRedAlert = new Audio("audio/red_alert." + this.audioType);
		this.audReady = new Audio("audio/ready." + this.audioType);

		//Actual Standard Wire-up steps
		var button_list = document.querySelectorAll("#container div.lcars-element.button, div.lcars-app-container div.lcars-element.button");
		for (var button of button_list) {
			console.log("Audio-enabling container-contained button " + button.id + " " + button.innerText);
			button.addEventListener("click", function(){ lcarsAudio.TactileInputAcknowledge(); });
		}

		button_list = document.querySelectorAll("path.button");
		for (var button of button_list) {
			console.log("Audio-enabling SVG button " + button.id );
			button.addEventListener("click", function(){ lcarsAudio.TactileInputAcknowledge(); });
		}

		button_list = document.querySelectorAll(".lcars-tactinput-acknowledge-audio");
		for (var button of button_list) {
			console.log("Audio-enabling button " + button.id + " " + button.innerText);
			button.addEventListener("click", function(){ lcarsAudio.TactileInputAcknowledge(); });
		}
		button_list = document.querySelectorAll(".lcars-tactinput-alt-acknowledge-audio");
		for (var button of button_list) {
			console.log("Audio-enabling button " + button.id + " " + button.innerText);
			button.addEventListener("click", function(){ lcarsAudio.TactileInputAlternateAcknowledge(); });
		}
		button_list = document.querySelectorAll(".lcars-tactinput-neg-acknowledge-audio");
		for (var button of button_list) {
			console.log("Audio-enabling button " + button.id + " " + button.innerText);
			button.addEventListener("click", function(){ lcarsAudio.TactileInputNegativeAcknowledge(); });
		}
		button_list = document.querySelectorAll(".lcars-alert-audio");
		for (var button of button_list) {
			console.log("Audio-enabling button " + button.id + " " + button.innerText);
			button.addEventListener("click", function(){ lcarsAudio.Alert(); });
		}
		button_list = document.querySelectorAll(".lcars-red-alert-audio");
		for (var button of button_list) {
			console.log("Audio-enabling button " + button.id + " " + button.innerText);
			button.addEventListener("click", function(){ lcarsAudio.RedAlert(); });
		}
	},
	TactileInputAcknowledge: function() {
		this.audTactInputAcknowledge.play();
	},
	TactileInputAlternateAcknowledge: function () {
		this.audTactInputAltAcknowledge.play();
	},
	TactileInputNegativeAcknowledge: function () {
		this.audTactInputNegAcknowledge.play();
	},
	Alert: function () {
		window.console && console.log(this.audAlert.readyState)
		//audAlert.load();
		this.audAlert.play();
		//TODO:bonus: have a fallback for no supported file types (or MUTE active):
		//floating div pops up and flashes, then disappears.
	},
	RedAlert: function () {
		window.console && console.log(this.audAlert.readyState)
		this.audRedAlert.play();
		//TODO:bonus: have a fallback for no supported file types (or MUTE active):
		//floating div pops up and flashes, then disappears.
	},
	Ready: function () {
		var promise = this.audReady.play();
		if (promise !== undefined) {
			promise.then(_ => {
				console.log("Sound probably just actually played, so user interaction with document status must be 'true'.");
			}).catch(error => {
				console.warn("Autoplay was prevented. User interaction required before playback will be allowed by browser.");
			});
		} else {
		       console.warn("Didn't even get a promise back. Possibly some error or device limitation.");
		}
	}
}

function enableAudio(el) {
	if (el.classList.contains("disabled")) {
		el.addEventListener("click", function(){ audioNegativeAcknowledge(); });
	} else {
		el.addEventListener("click", function(){ audioAcknowledge(); });
	}
}

//=\\ Standard Wire-up
var audDummy = document.getElementById("audDummy").appendChild(document.createElement("audio"));
lcarsAudio.initialize(audDummy);

/*
 * To have an All "booted" up notification, place this little snippet at the bottom of the page in a script block:
document.addEventListener("DOMContentLoaded", function(event) {
	//Note though: Many browsers won't play this next sound at first
	//because the user hasn't "blessed" the action with a UI click yet.
	lcarsAudio.Ready();
});

*/
<commit_msg>Minor cleanup in audio module.<commit_after>// Simple audio event module and wire-up for a futuristic compturized GUI.
// Should be called at the end of the page, when document is nearly ready.
// Authored by justin.warwick@gmail.com
// Released under MIT license; see LICENSE file.
//
// Assumes you have this at the bottom of your document:
//   <audio id="audUmmy"/><!--Just leave this here, just needed to enable automatic query canplay in the initialization code to load compatible media types -->

var lcarsAudio = {
	audioExtensions: {
		ogg: "ogg",
		webm:"webm",
		mp4: "aac"
	},
	audioPath: "./audio/",
	audioType: "",
	//Inidividual Sound Element holders
	audTactInputAcknowledge:    null,
	audTactInputAltAcknowledge: null,
	audTactInputNegAcknowledge: null,
	audAlert:                   null,
	audRedAlert:                null,
	audReady:                   null,
	initialize: function(auDummy) {
		//Determine browser capability and willingness
		for (var key in this.audioExtensions) {
			if (auDummy.canPlayType("audio/"+this.audioExtensions[key]) != '') {
				auDummy.src = this.audioPath + "ready." + key;
				//TODO: test it further??
				console.log(auDummy.src + "   " +auDummy.src.lastIndexOf("."));
				this.audioType =  auDummy.src.slice(auDummy.src.lastIndexOf(".") + 1);
				console.log("lcarsAudio: We have a winner: " + this.audioType);
				break;
			}
		}
		this.audTactInputAcknowledge = new Audio("audio/tactinput_acknowledge." + this.audioType);
		this.audTactInputAltAcknowledge = new Audio("audio/tactinput_alt_acknowledge." + this.audioType);
		this.audTactInputNegAcknowledge = new Audio("audio/tactinput_neg_acknowledge." + this.audioType);
		this.audAlert = new Audio("audio/alert." + this.audioType);
		this.audRedAlert = new Audio("audio/red_alert." + this.audioType);
		this.audReady = new Audio("audio/ready." + this.audioType);

		//Actual Standard Wire-up steps
		var button_list = document.querySelectorAll("#container div.lcars-element.button, div.lcars-app-container div.lcars-element.button");
		for (var button of button_list) {
			console.log("Audio-enabling container-contained button " + button.id + " " + button.innerText);
			button.addEventListener("click", function(){ lcarsAudio.TactileInputAcknowledge(); });
		}

		button_list = document.querySelectorAll("path.button");
		for (var button of button_list) {
			console.log("Audio-enabling SVG button " + button.id );
			button.addEventListener("click", function(){ lcarsAudio.TactileInputAcknowledge(); });
		}

		button_list = document.querySelectorAll(".lcars-tactinput-acknowledge-audio");
		for (var button of button_list) {
			console.log("Audio-enabling button " + button.id + " " + button.innerText);
			button.addEventListener("click", function(){ lcarsAudio.TactileInputAcknowledge(); });
		}
		button_list = document.querySelectorAll(".lcars-tactinput-alt-acknowledge-audio");
		for (var button of button_list) {
			console.log("Audio-enabling button " + button.id + " " + button.innerText);
			button.addEventListener("click", function(){ lcarsAudio.TactileInputAlternateAcknowledge(); });
		}
		button_list = document.querySelectorAll(".lcars-tactinput-neg-acknowledge-audio");
		for (var button of button_list) {
			console.log("Audio-enabling button " + button.id + " " + button.innerText);
			button.addEventListener("click", function(){ lcarsAudio.TactileInputNegativeAcknowledge(); });
		}
		button_list = document.querySelectorAll(".lcars-alert-audio");
		for (var button of button_list) {
			console.log("Audio-enabling button " + button.id + " " + button.innerText);
			button.addEventListener("click", function(){ lcarsAudio.Alert(); });
		}
		button_list = document.querySelectorAll(".lcars-red-alert-audio");
		for (var button of button_list) {
			console.log("Audio-enabling button " + button.id + " " + button.innerText);
			button.addEventListener("click", function(){ lcarsAudio.RedAlert(); });
		}
	},
	TactileInputAcknowledge: function() {
		this.audTactInputAcknowledge.play();
	},
	TactileInputAlternateAcknowledge: function () {
		this.audTactInputAltAcknowledge.play();
	},
	TactileInputNegativeAcknowledge: function () {
		this.audTactInputNegAcknowledge.play();
	},
	Alert: function () {
		//DEBUG//window.console && console.log(this.audAlert.readyState)
		this.audAlert.play();
		//TODO:bonus: have a fallback for no supported file types (or MUTE active):
		//floating div pops up and flashes, then disappears.
	},
	RedAlert: function () {
		//DEBUG//window.console && console.log(this.audAlert.readyState)
		this.audRedAlert.play();
		//TODO:bonus: have a fallback for no supported file types (or MUTE active):
		//floating div pops up and flashes, then disappears.
	},
	Ready: function () {
		//TODO: consider putting this in all the other play functions, too. Or maybe moving it out to some kind of in-audible primer sound?
		var promise = this.audReady.play();
		if (promise !== undefined) {
			promise.then(_ => {
				console.log("Sound probably just actually played, so user interaction with document status must be 'true'.");
			}).catch(error => {
				console.warn("Autoplay was prevented. User interaction required before playback will be allowed by browser.");
			});
		} else {
		       console.warn("Didn't even get a promise back. Possibly some error or device limitation.");
		}
	}
}

function enableAudio(el) {
	if (el.classList.contains("disabled")) {
		el.addEventListener("click", function(){ audioNegativeAcknowledge(); });
	} else {
		el.addEventListener("click", function(){ audioAcknowledge(); });
	}
}

//=\\ Standard Wire-up
var audDummy = document.getElementById("audDummy").appendChild(document.createElement("audio"));
lcarsAudio.initialize(audDummy);

/*
 * To have an All "booted" up notification, place this little snippet at the bottom of the page in a script block:
document.addEventListener("DOMContentLoaded", function(event) {
	//Note though: Many browsers won't play this next sound at first
	//because the user hasn't "blessed" the action with a UI click yet.
	lcarsAudio.Ready();
});

*/
<|endoftext|>


<commit_before>// ==UserScript==
// @name         GMail Oldest First
// @namespace    https://blog.jverkamp.com/
// @version      0.2
// @description  Display emails in Gmail oldest first (per page)
// @author       JP Verkamp
// @match        https://mail.google.com/*
// @grant        none
// ==/UserScript==

/* jshint -W097 */
'use strict';

(function() {
    let attemptReverse = () => {
        let reversed = false;
        document.querySelectorAll('tbody').forEach((el) => {
            if (el.childElementCount < 10 || el.getAttribute('style')) {
                return;
            }

            el.setAttribute('style', 'display: flex; flex-direction: column-reverse;');
            reversed = true;
            console.log('GMail Oldest First: Reverse applied');
        });

        if (reversed) {
            setTimeout(attemptReverse, 10000);
        } else {
            setTimeout(attemptReverse, 100);
        }
    };
    setTimeout(attemptReverse, 100);
})();
<commit_msg>Another version, using directly styles but finding proper class names<commit_after>// ==UserScript==
// @name         GMail Oldest First
// @namespace    https://blog.jverkamp.com/
// @version      0.2
// @description  Display emails in Gmail oldest first (per page)
// @author       JP Verkamp
// @match        https://mail.google.com/*
// @grant        none
// ==/UserScript==

// Warning, this will break if (when) GMail changes their class names

/* jshint -W097 */
'use strict';

(function() {
    let head = document.getElementsByTagName('head')[0];
    let style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = `
table.F.cf.zt tbody {
    display: flex;
    flex-direction: column-reverse;
}
    `;
    head.appendChild(style);
    console.log('GMail Oldest First applied style');
})();<|endoftext|>


<commit_before>/** SearchWidget used in every page in the entire administrator interface.
 *
 * Enables users to search for everything (like the dashboard) or just within
 * the current item.
 * */
Ext.define('devilry.administrator.AdministratorSearchWidget', {
    extend: 'devilry.extjshelpers.searchwidget.DashboardSearchWidget',

    nodeRowTpl: [
        '
',
        '
{long_name:ellipsis(40)}
',
        '
'
    ],

    subjectRowTpl: [
        '
',
        '
{long_name:ellipsis(40)}
',
        '
'
    ],

    periodRowTpl: [
        '
',
        '
{parentnode__short_name}
',
        '
{long_name:ellipsis(40)}
',
        '
'
    ],

    /**
    * @cfg
    * Url prefix. Should be the absolute URL path to /administrator/.
    */
    urlPrefix: '',

    initComponent: function() {
        Ext.apply(this, {
            searchResultItems: [{
                xtype: 'searchresults',
                title: 'Nodes',
                store: this._createStore('devilry.apps.administrator.simplified.SimplifiedNode'),
                filterconfig: {
                    type: 'node'
                },
                resultitemConfig: {
                    tpl: this.nodeRowTpl,
                    defaultbutton: {
                        text: 'View',
                        clickLinkTpl: this.urlPrefix + 'node/{id}'
                    }
                }
            }, {
                xtype: 'searchresults',
                title: 'Subjects',
                store: this._createStore('devilry.apps.administrator.simplified.SimplifiedSubject'),
                filterconfig: {
                    type: 'subject'
                },
                resultitemConfig: {
                    tpl: this.subjectRowTpl,
                    defaultbutton: {
                        text: 'View',
                        clickLinkTpl: this.urlPrefix + 'subject/{id}'
                    }
                }
            }, {
                xtype: 'searchresults',
                title: 'Periods',
                store: this._createStore('devilry.apps.administrator.simplified.SimplifiedPeriod'),
                filterconfig: {
                    type: 'period'
                },
                resultitemConfig: {
                    tpl: this.periodRowTpl,
                    defaultbutton: {
                        text: 'View',
                        clickLinkTpl: this.urlPrefix + 'period/{id}'
                    }
                }
            }, {
                xtype: 'searchresults',
                title: 'Assignments',
                store: this._createStore('devilry.apps.administrator.simplified.SimplifiedAssignment'),
                filterconfig: devilry.extjshelpers.searchwidget.FilterConfigDefaults.assignment,
                resultitemConfig: {
                    tpl: this.assignmentRowTpl,
                    defaultbutton: {
                        text: 'View',
                        clickLinkTpl: this.urlPrefix + 'assignment/{id}'
                    },
                    menuitems: [{
                        text: 'Show deliveries',
                        clickFilter: 'type:delivery assignment:{id}'
                    }]
                }
            }, {
                xtype: 'searchresults',
                title: 'Assignment groups',
                store: this._createStore('devilry.apps.administrator.simplified.SimplifiedAssignmentGroup'),
                filterconfig: devilry.extjshelpers.searchwidget.FilterConfigDefaults.assignmentgroup,
                resultitemConfig: {
                    tpl: this.assignmentgroupRowTpl,
                    defaultbutton: {
                        text: 'View',
                        clickLinkTpl: this.urlPrefix + 'assignmentgroup/{id}'
                    }
                }
            }, {
                xtype: 'searchresults',
                title: 'Deliveries',
                store: this._createStore('devilry.apps.administrator.simplified.SimplifiedDelivery'),
                filterconfig: devilry.extjshelpers.searchwidget.FilterConfigDefaults.delivery,
                resultitemConfig: {
                    tpl: this.deliveryRowTpl,
                    defaultbutton: {
                        text: 'View',
                        clickLinkTpl: this.urlPrefix + 'assignmentgroup/{deadline__assignment_group}?deliveryid={id}'
                    }
                }
            }]
        });
        this.callParent(arguments);
    }
});
<commit_msg>apps.administrator...AdministratorSearchWidget: Translations.<commit_after>/** SearchWidget used in every page in the entire administrator interface.
 *
 * Enables users to search for everything (like the dashboard) or just within
 * the current item.
 * */
Ext.define('devilry.administrator.AdministratorSearchWidget', {
    extend: 'devilry.extjshelpers.searchwidget.DashboardSearchWidget',

    nodeRowTpl: [
        '
',
        '
{long_name:ellipsis(40)}
',
        '
'
    ],

    subjectRowTpl: [
        '
',
        '
{long_name:ellipsis(40)}
',
        '
'
    ],

    periodRowTpl: [
        '
',
        '
{parentnode__short_name}
',
        '
{long_name:ellipsis(40)}
',
        '
'
    ],

    /**
    * @cfg
    * Url prefix. Should be the absolute URL path to /administrator/.
    */
    urlPrefix: '',

    initComponent: function() {
        Ext.apply(this, {
            searchResultItems: [{
                xtype: 'searchresults',
                title: gettext('Nodes'),
                store: this._createStore('devilry.apps.administrator.simplified.SimplifiedNode'),
                filterconfig: {
                    type: 'node'
                },
                resultitemConfig: {
                    tpl: this.nodeRowTpl,
                    defaultbutton: {
                        text: gettext('View'),
                        clickLinkTpl: this.urlPrefix + 'node/{id}'
                    }
                }
            }, {
                xtype: 'searchresults',
                title: gettext('Subjects'),
                store: this._createStore('devilry.apps.administrator.simplified.SimplifiedSubject'),
                filterconfig: {
                    type: 'subject'
                },
                resultitemConfig: {
                    tpl: this.subjectRowTpl,
                    defaultbutton: {
                        text: gettext('View'),
                        clickLinkTpl: this.urlPrefix + 'subject/{id}'
                    }
                }
            }, {
                xtype: 'searchresults',
                title: gettext('Periods'),
                store: this._createStore('devilry.apps.administrator.simplified.SimplifiedPeriod'),
                filterconfig: {
                    type: 'period'
                },
                resultitemConfig: {
                    tpl: this.periodRowTpl,
                    defaultbutton: {
                        text: gettext('View'),
                        clickLinkTpl: this.urlPrefix + 'period/{id}'
                    }
                }
            }, {
                xtype: 'searchresults',
                title: gettext('Assignments'),
                store: this._createStore('devilry.apps.administrator.simplified.SimplifiedAssignment'),
                filterconfig: devilry.extjshelpers.searchwidget.FilterConfigDefaults.assignment,
                resultitemConfig: {
                    tpl: this.assignmentRowTpl,
                    defaultbutton: {
                        text: gettext('View'),
                        clickLinkTpl: this.urlPrefix + 'assignment/{id}'
                    },
                    menuitems: [{
                        text: 'Show deliveries',
                        clickFilter: 'type:delivery assignment:{id}'
                    }]
                }
            }, {
                xtype: 'searchresults',
                title: gettext('Groups'),
                store: this._createStore('devilry.apps.administrator.simplified.SimplifiedAssignmentGroup'),
                filterconfig: devilry.extjshelpers.searchwidget.FilterConfigDefaults.assignmentgroup,
                resultitemConfig: {
                    tpl: this.assignmentgroupRowTpl,
                    defaultbutton: {
                        text: gettext('View'),
                        clickLinkTpl: this.urlPrefix + 'assignmentgroup/{id}'
                    }
                }
            }, {
                xtype: 'searchresults',
                title: gettext('Deliveries'),
                store: this._createStore('devilry.apps.administrator.simplified.SimplifiedDelivery'),
                filterconfig: devilry.extjshelpers.searchwidget.FilterConfigDefaults.delivery,
                resultitemConfig: {
                    tpl: this.deliveryRowTpl,
                    defaultbutton: {
                        text: gettext('View'),
                        clickLinkTpl: this.urlPrefix + 'assignmentgroup/{deadline__assignment_group}?deliveryid={id}'
                    }
                }
            }]
        });
        this.callParent(arguments);
    }
});
<|endoftext|>