<commit_before>"use strict";
const fsp = require("fs-promise");

module.exports = {

	/**
	 * Read in the last `n` lines of a file
	 * @param  {string}  file (direct or relative path to file.)
	 * @param  {int}     maxLineCount max number of lines to read in.
	 * @return {promise} new Promise, resolved with lines or rejected with error.
	 */

	read: function(input_file_path, maxLineCount) {

		const NEW_LINE_CHARACTERS = ["\n", "\r"];

		const readPreviousChar = function( stat, file, currentCharacterCount) {
			return fsp.read(file, new Buffer(1), 0, 1, stat.size - 1 - currentCharacterCount)
				.then((bytesReadAndBuffer) => {
					return String.fromCharCode(bytesReadAndBuffer[1][0]);
				});
		};

		return new Promise((resolve, reject) => {
			let self = {
				stat: null,
				file: null,
			};

			fsp.exists(input_file_path)
			.then((exists) => {
				if (!exists) {
					throw new Error("file does not exist");
				}

			}).then(() => {
				let promises = [];

				// Load file Stats.
				promises.push(
					fsp.stat(input_file_path)
						.then(stat => self.stat = stat));

				// Open file for reading.
				promises.push(
					fsp.open(input_file_path, "r")
						.then(file => self.file = file));

				return promises;
			}).then((promises) => {
				return Promise.all(promises);
			}).then(() => {
				let chars = 0;
				let lineCount = 0;
				let lines = "";

				const do_while_loop = function() {
					if (lines.length > self.stat.size) {
						lines = lines.substring(lines.length - self.stat.size);
					}

					if (lines.length >= self.stat.size || lineCount >= maxLineCount) {
						if (NEW_LINE_CHARACTERS.includes(lines.substring(0, 1))) {
							lines = lines.substring(1);
						}
						fsp.close(self.file);
						return resolve(lines);
					}

					return readPreviousChar(self.stat, self.file, chars)
						.then((nextCharacter) => {
							lines = nextCharacter + lines;
							if (NEW_LINE_CHARACTERS.includes(nextCharacter) && lines.length > 1) {
								lineCount++;
							}
							chars++;
						})
						.then(do_while_loop);
				};
				return do_while_loop();

			}).catch((reason) => {
				if (self.file !== null) {
					fsp.close(self.file);
				}
				return reject(reason);
			});
		});
	},
};
<commit_msg>remove extra promise chain<commit_after>"use strict";
const fsp = require("fs-promise");

module.exports = {

	/**
	 * Read in the last `n` lines of a file
	 * @param  {string}  file (direct or relative path to file.)
	 * @param  {int}     maxLineCount max number of lines to read in.
	 * @return {promise} new Promise, resolved with lines or rejected with error.
	 */

	read: function(input_file_path, maxLineCount) {

		const NEW_LINE_CHARACTERS = ["\n", "\r"];

		const readPreviousChar = function( stat, file, currentCharacterCount) {
			return fsp.read(file, new Buffer(1), 0, 1, stat.size - 1 - currentCharacterCount)
				.then((bytesReadAndBuffer) => {
					return String.fromCharCode(bytesReadAndBuffer[1][0]);
				});
		};

		return new Promise((resolve, reject) => {
			let self = {
				stat: null,
				file: null,
			};

			fsp.exists(input_file_path)
			.then((exists) => {
				if (!exists) {
					throw new Error("file does not exist");
				}

			}).then(() => {
				let promises = [];

				// Load file Stats.
				promises.push(
					fsp.stat(input_file_path)
						.then(stat => self.stat = stat));

				// Open file for reading.
				promises.push(
					fsp.open(input_file_path, "r")
						.then(file => self.file = file));

				return Promise.all(promises);
			}).then(() => {
				let chars = 0;
				let lineCount = 0;
				let lines = "";

				const do_while_loop = function() {
					if (lines.length > self.stat.size) {
						lines = lines.substring(lines.length - self.stat.size);
					}

					if (lines.length >= self.stat.size || lineCount >= maxLineCount) {
						if (NEW_LINE_CHARACTERS.includes(lines.substring(0, 1))) {
							lines = lines.substring(1);
						}
						fsp.close(self.file);
						return resolve(lines);
					}

					return readPreviousChar(self.stat, self.file, chars)
						.then((nextCharacter) => {
							lines = nextCharacter + lines;
							if (NEW_LINE_CHARACTERS.includes(nextCharacter) && lines.length > 1) {
								lineCount++;
							}
							chars++;
						})
						.then(do_while_loop);
				};
				return do_while_loop();

			}).catch((reason) => {
				if (self.file !== null) {
					fsp.close(self.file);
				}
				return reject(reason);
			});
		});
	},
};
<|endoftext|>


<commit_before>#! /usr/bin/env node

var express = require('express'),
    os = require("os"),
    hostname = os.hostname(),
    app = express(),
    path = require('path'),
    bodyParser = require('body-parser'),
    logger = require("./lib/logger"),
    cjson = require('cjson'),
    events = require('./events'),
    sprinklers = require('./sprinklers');

const conf = cjson.load(path.join(__dirname, "../config/config.json"));

//settings
const SPRINKLER_BASE_URI = 'sprinklers';
const EVENTS_BASE_URI = 'events';

// parse application/json
app.use(bodyParser.json())

// enable CORS
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

// log queries
app.use((req, res, next) => {
  logger.info(req.method, req.originalUrl);
  next();
});

var routes = [];
// include the events route
app.use('/events', events.routes);
// include the sprinlers route
app.use('/sprinklers', sprinklers.routes);

// list all sprinklers on GET /
app.get('/', function(req, res) {
  var services = {
    [SPRINKLER_BASE_URI] : {
      uri: '/' + SPRINKLER_BASE_URI + '/'
    },
    [EVENTS_BASE_URI] : {
      uri: '/' + EVENTS_BASE_URI + '/'
    }
  }
  res.status(200);
  res.json(services);
});

var server = app.listen(process.env.PORT || conf.port || 3000, () => {
  logger.log("Server running on PORT", server.address().port);
});

// actual inits
sprinklers.init(); //@TODO: remove
<commit_msg>feat(TLS): add https functionality<commit_after>#! /usr/bin/env node

var express = require('express'),
    os = require("os"),
    hostname = os.hostname(),
    app = express(),
    path = require('path'),
    fs = require('fs'),
    http = require('http'),
    https = require('https'),
    bodyParser = require('body-parser'),
    logger = require("./lib/logger"),
    cjson = require('cjson'),
    events = require('./events'),
    sprinklers = require('./sprinklers');

const conf = cjson.load(path.join(__dirname, "../config/config.json"));

//settings
const SPRINKLER_BASE_URI = 'sprinklers';
const EVENTS_BASE_URI = 'events';

// parse application/json
app.use(bodyParser.json())

// enable CORS
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
  next();
});

// log queries
app.use((req, res, next) => {
  logger.info(req.method, req.originalUrl);
  next();
});

var routes = [];
// include the events route
app.use('/events', events.routes);
// include the sprinlers route
app.use('/sprinklers', sprinklers.routes);

// list all sprinklers on GET /
app.get('/', function(req, res) {
  var services = {
    [SPRINKLER_BASE_URI] : {
      uri: '/' + SPRINKLER_BASE_URI + '/'
    },
    [EVENTS_BASE_URI] : {
      uri: '/' + EVENTS_BASE_URI + '/'
    }
  }
  res.status(200);
  res.json(services);
});

//var server = app.listen(process.env.PORT || conf.port || 3000, () => {
//  logger.log("Server running on PORT", server.address().port);
//});

var privateKey  = fs.readFileSync("/etc/letsencrypt/live/nerderia.dedyn.io/privkey.pem");
var certificate = fs.readFileSync("/etc/letsencrypt/live/nerderia.dedyn.io/fullchain.pem");

var credentials = {key: privateKey, cert: certificate};

var httpServer = http.createServer(app);
var httpsServer = https.createServer(credentials, app);
httpsServer.listen(8443, () => {
  logger.log("HTTPS Server running on PORT", httpsServer.address().port);
})
httpServer.listen(process.env.PORT || conf.port || 3000, () => {
  logger.log("HTTP Server running on PORT", httpServer.address().port);
});

// actual inits
sprinklers.init(); //@TODO: remove
<|endoftext|>


<commit_before>'use strict'

/* @flow */

import {spawn} from 'child_process'
import getEnvironment from 'consistent-env'
import {assign, validate} from './helpers'
import type {Exec$Options, Exec$Result} from './types'

function exec(filePath: string, parameters: Array<string> = [], options: Exec$Options = {}): Promise<Exec$Result> {
  validate(filePath, parameters, options)

  return new Promise(function(resolve, reject) {
    const spawnOptions = assign({}, options, {
      env: assign(getEnvironment(), options.env)
    })
    // Note: We want to reject with a nice promise ourselves instead of silently killing as node does
    spawnOptions.timeout = null
    if (spawnOptions.env.OS) {
      spawnOptions.env.OS = undefined
    }

    const spawnedProcess = spawn(filePath, parameters, spawnOptions)
    const data = {stdout: [], stderr: []}
    let timeout

    spawnedProcess.stdout.on('data', function(chunk) {
      data.stdout.push(chunk)
    })
    spawnedProcess.stderr.on('data', function(chunk) {
      data.stderr.push(chunk)
    })
    spawnedProcess.on('error', function(error) {
      reject(error)
    })
    spawnedProcess.on('close', function() {
      clearTimeout(timeout)
      if (options.stream === 'stdout') {
        if (data.stderr.length && options.throwOnStdErr) {
          reject(new Error(data.stderr.join('').trim()))
        } else {
          resolve(data.stdout.join('').trim())
        }
      } else if (options.stream === 'stderr') {
        resolve(data.stderr.join('').trim())
      } else {
        resolve({stdout: data.stdout.join('').trim(), stderr: data.stderr.join('').trim()})
      }
    })

    if (options.stdin) {
      try {
        spawnedProcess.stdin.write(options.stdin)
      } catch (_) { /* No Op */ }
    }
    try {
      spawnedProcess.stdin.end()
    } catch (_) { /* No Op */ }

    if (options.timeout !== Infinity) {
      timeout = setTimeout(function () {
        try {
          spawnedProcess.kill()
        } catch (_) { /* No Op */ }
        reject(new Error('Process execution timed out'))
      }, options.timeout)
    }
  })
}

function execNode(filePath: string, parameters: Array<string> = [], options: Exec$Options = {}): Promise<Exec$Result> {
  validate(filePath, parameters, options)
  return exec(process.execPath, [filePath].concat(parameters), options)
}

module.exports = { exec, execNode }
<commit_msg>:new: Support electron runtime<commit_after>'use strict'

/* @flow */

import {spawn} from 'child_process'
import getEnvironment from 'consistent-env'
import {assign, validate} from './helpers'
import type {Exec$Options, Exec$Result} from './types'

function exec(filePath: string, parameters: Array<string> = [], options: Exec$Options = {}): Promise<Exec$Result> {
  validate(filePath, parameters, options)

  return new Promise(function(resolve, reject) {
    const spawnOptions = assign({}, options, {
      env: assign(getEnvironment(), options.env)
    })
    // Note: We want to reject with a nice promise ourselves instead of silently killing as node does
    spawnOptions.timeout = null
    if (spawnOptions.env.OS) {
      spawnOptions.env.OS = undefined
    }
    if (process.versions.electron) {
      spawnOptions.env.ELECTRON_INTERNAL_RUN_AS_NODE = '1'
      spawnOptions.env.ATOM_SHELL_INTERNAL_RUN_AS_NODE = '1'
    }

    const spawnedProcess = spawn(filePath, parameters, spawnOptions)
    const data = {stdout: [], stderr: []}
    let timeout

    spawnedProcess.stdout.on('data', function(chunk) {
      data.stdout.push(chunk)
    })
    spawnedProcess.stderr.on('data', function(chunk) {
      data.stderr.push(chunk)
    })
    spawnedProcess.on('error', function(error) {
      reject(error)
    })
    spawnedProcess.on('close', function() {
      clearTimeout(timeout)
      if (options.stream === 'stdout') {
        if (data.stderr.length && options.throwOnStdErr) {
          reject(new Error(data.stderr.join('').trim()))
        } else {
          resolve(data.stdout.join('').trim())
        }
      } else if (options.stream === 'stderr') {
        resolve(data.stderr.join('').trim())
      } else {
        resolve({stdout: data.stdout.join('').trim(), stderr: data.stderr.join('').trim()})
      }
    })

    if (options.stdin) {
      try {
        spawnedProcess.stdin.write(options.stdin)
      } catch (_) { /* No Op */ }
    }
    try {
      spawnedProcess.stdin.end()
    } catch (_) { /* No Op */ }

    if (options.timeout !== Infinity) {
      timeout = setTimeout(function () {
        try {
          spawnedProcess.kill()
        } catch (_) { /* No Op */ }
        reject(new Error('Process execution timed out'))
      }, options.timeout)
    }
  })
}

function execNode(filePath: string, parameters: Array<string> = [], options: Exec$Options = {}): Promise<Exec$Result> {
  validate(filePath, parameters, options)
  return exec(process.execPath, [filePath].concat(parameters), options)
}

module.exports = { exec, execNode }
<|endoftext|>


<commit_before>module.exports = function memoize( fn, delay ) {
  var wait = delay || 0;
  return function () {
    var args = Array.prototype.slice.call(arguments);
    var hash = "";
    var i = args.length;
    var currentArg = null;
    fn.memoize || (fn.memoize = {});
    while (i--) {
      currentArg = args[i];
      hash += (currentArg === Object(currentArg)) ?
      JSON.stringify(currentArg) : currentArg;
    }
    if (!(hash in fn.memoize)) {
      function clearMemo(hash) {
        return function() {
          setTimeout(function() {
            delete fn.memoize[hash];
          }, wait);
        }
      }
      fn.memoize[hash] = fn.apply(this, args);
      fn.memoize[hash].then(clearMemo(hash), clearMemo(hash));
    }
    return fn.memoize[hash];
  };
};
<commit_msg>Added an extra function for convenience for creating a memoization function with a default delay<commit_after>module.exports = function memoize( fn, delay ) {
  var wait = delay || 0;
  return function () {
    var args = Array.prototype.slice.call(arguments);
    var hash = "";
    var i = args.length;
    var currentArg = null;
    fn.memoize || (fn.memoize = {});
    while (i--) {
      currentArg = args[i];
      hash += (currentArg === Object(currentArg)) ?
      JSON.stringify(currentArg) : currentArg;
    }
    if (!(hash in fn.memoize)) {
      function clearMemo(hash) {
        return function() {
          setTimeout(function() {
            delete fn.memoize[hash];
          }, wait);
        }
      }
      fn.memoize[hash] = fn.apply(this, args);
      fn.memoize[hash].then(clearMemo(hash), clearMemo(hash));
    }
    return fn.memoize[hash];
  };
};

module.exports.withDelay = function spawn(delay) {
  return function(fn) {
    return memoize(fn, delay);
  }
}
<|endoftext|>


<commit_before>'use strict'

const mobileLocator = require('mobile-locator')
const meitrack = require('meitrack-parser')
const cellocator = require('cellocator-parser')
const queclink = require('queclink-parser')
const rg = require('simple-reverse-geocoder')
const tz = require('tz-parser')

/**
 * Set client Redis for cache results
 * @param  {String} uri Redis connection string. Ex redis://user:pass@host:port/db
 * @return {Void}
 */
const setCache = uri => {
  rg.setCache(uri)
}

/**
 * Get imei from raw data
 * @param  {Buffer} raw Raw data from tracking device
 * @return {String}     Imei
 */
const getImei = raw => {
  const fns = [
    tz.getImei,
    meitrack.getImei,
    cellocator.getImei,
    queclink.getImei
  ]
  const imei = fns.map(x => x(raw)).find(x => x !== null) || null
  return imei
}

/**
 * Set geolocation from cell tower information
 * @param  {Object} data           Parsed data
 * @param  {Number} options.mcc    Mobile country code
 * @param  {Number} options.mnc    Mobile network code
 * @param  {String} options.apiKey Google api key
 * @return {Object}                Parsed data with geolocation
 */
const setLoc = (data, { mcc = 730, mnc = 1, apiKey = null } = {}) => {
  const locate = mobileLocator('google', { apiKey })
  return locate({ mcc, mnc, lac: data.lac, cid: data.cid })
    .then(coords => {
      data.loc = {
        type: 'Point',
        coordinates: [coords.longitude, coords.latitude]
      }
      data.gps = 'triangulation'
      return data
    })
    .catch(() => data)
}

/**
 * Set address
 * @param  {Object} data   Parsed data
 * @param  {String} apiKey Google api key
 * @return {Object}        Parsed data with address
 */
const setAddress = (data, apiKey = null) => {
  if (!data.loc) return Promise.resolve(data)
  return rg
    .getAddress(data.loc, apiKey)
    .then(address => {
      data.address = address
      return data
    })
    .catch(() => {
      return data
    })
}

/**
 * Set gps (enable, triangulation, disable) and set address
 * @param  {Object} data           Parsed data
 * @param  {Number} options.mcc    Mobile country code
 * @param  {Number} options.mnc    Mobile network code
 * @param  {String} options.apiKey Google api key
 * @return {Object}                Parsed data with geolocation and address
 */
const setGps = (data, { mcc = 730, mnc = 1, apiKey = null } = {}) => {
  if (data.type !== 'data') return Promise.resolve(data)
  data.gps = data.loc ? 'enable' : 'disable'
  if (data.gps === 'enable') {
    return setAddress(data, apiKey)
  }
  return setLoc(data, { mcc, mnc, apiKey }).then(data => {
    return setAddress(data, apiKey)
  })
}

/**
 * Parse raw data, set, geolocation, gps (enable, triangulation, disable) and address
 * @param  {Buffer}               raw            Parsed data
 * @param  {Number}               options.mcc    Mobile country code
 * @param  {Number}               options.mnc    Mobile network code
 * @param  {String}               options.apiKey Google api key
 * @return {Object|Array<Object>}                Parsed data with geolocation, gps and address
 */
const parse = (raw, { mcc = 730, mnc = 1, apiKey = null } = {}) => {
  try {
    const options = { mcc, mnc, apiKey }
    const fns = [tz.parse, meitrack.parse, cellocator.parse, queclink.parse]
    const data = fns.map(x => x(raw)).find(x => x.type !== 'UNKNOWN') || {
      raw: raw.toString(),
      type: 'UNKNOWN'
    }
    if (Array.isArray(data)) {
      return Promise.all(data.map(x => setGps(x, options)))
    } else {
      return setGps(data, options)
    }
  } catch (err) {
    return Promise.reject(err)
  }
}

/**
 * Parse command to raw command
 * @param  {Object} data Command data
 * @return {String}      Raw command
 */
const parseCommand = data => {
  let command = null
  const fns = {
    tz: tz.parseCommand,
    meitrack: meitrack.parseCommand,
    cellocator: cellocator.parseCommand,
    queclink: queclink.parseCommand
  }
  if (fns.hasOwnProperty(data.device)) {
    command = fns[data.device](data)
  }
  return command
}

/**
 * Get raw roboot command
 * @param  {Object} data Command data
 * @return {String}      Raw command
 */
const getRebootCommand = data => {
  let command = null
  if (data.device === 'tz') {
    command = tz.getRebootCommand(data.password || '000000')
  } else if (data.device === 'meitrack') {
    command = meitrack.getRebootCommand(data.imei)
  } else if (data.device === 'queclink') {
    command = queclink.getRebootCommand(data.password, data.serial)
  }
  return command
}

module.exports = {
  getImei: getImei,
  setCache: setCache,
  parse: parse,
  parseCommand: parseCommand,
  getRebootCommand: getRebootCommand,
  getCellocatorAck: cellocator.ack
}
<commit_msg>fix(parser): fix set google api key<commit_after>'use strict'

const mobileLocator = require('mobile-locator')
const meitrack = require('meitrack-parser')
const cellocator = require('cellocator-parser')
const queclink = require('queclink-parser')
const rg = require('simple-reverse-geocoder')
const tz = require('tz-parser')

/**
 * Set client Redis for cache results
 * @param  {String} uri Redis connection string. Ex redis://user:pass@host:port/db
 * @return {Void}
 */
const setCache = uri => {
  rg.setCache(uri)
}

/**
 * Get imei from raw data
 * @param  {Buffer} raw Raw data from tracking device
 * @return {String}     Imei
 */
const getImei = raw => {
  const fns = [
    tz.getImei,
    meitrack.getImei,
    cellocator.getImei,
    queclink.getImei
  ]
  const imei = fns.map(x => x(raw)).find(x => x !== null) || null
  return imei
}

/**
 * Set geolocation from cell tower information
 * @param  {Object} data        Parsed data
 * @param  {Number} options.mcc Mobile country code
 * @param  {Number} options.mnc Mobile network code
 * @param  {String} options.key Google api key
 * @return {Object}             Parsed data with geolocation
 */
const setLoc = (data, { mcc = 730, mnc = 1, key = null } = {}) => {
  const locate = mobileLocator('google', { key })
  return locate({ mcc, mnc, lac: data.lac, cid: data.cid })
    .then(coords => {
      data.loc = {
        type: 'Point',
        coordinates: [coords.longitude, coords.latitude]
      }
      data.gps = 'triangulation'
      return data
    })
    .catch(() => data)
}

/**
 * Set address
 * @param  {Object} data   Parsed data
 * @param  {String} apiKey Google api key
 * @return {Object}        Parsed data with address
 */
const setAddress = (data, apiKey = null) => {
  if (!data.loc) return Promise.resolve(data)
  return rg
    .getAddress(data.loc, apiKey)
    .then(address => {
      data.address = address
      return data
    })
    .catch(() => {
      return data
    })
}

/**
 * Set gps (enable, triangulation, disable) and set address
 * @param  {Object} data           Parsed data
 * @param  {Number} options.mcc    Mobile country code
 * @param  {Number} options.mnc    Mobile network code
 * @param  {String} options.apiKey Google api key
 * @return {Object}                Parsed data with geolocation and address
 */
const setGps = (data, { mcc = 730, mnc = 1, apiKey = null } = {}) => {
  if (data.type !== 'data') return Promise.resolve(data)
  data.gps = data.loc ? 'enable' : 'disable'
  if (data.gps === 'enable') {
    return setAddress(data, apiKey)
  }
  return setLoc(data, {
    mcc: data.mcc || mcc,
    mnc: data.mnc || mnc,
    key: apiKey
  }).then(data => {
    return setAddress(data, apiKey)
  })
}

/**
 * Parse raw data, set, geolocation, gps (enable, triangulation, disable) and address
 * @param  {Buffer}               raw            Parsed data
 * @param  {Number}               options.mcc    Mobile country code
 * @param  {Number}               options.mnc    Mobile network code
 * @param  {String}               options.apiKey Google api key
 * @return {Object|Array<Object>}                Parsed data with geolocation, gps and address
 */
const parse = (raw, { mcc = 730, mnc = 1, apiKey = null } = {}) => {
  try {
    const options = { mcc, mnc, apiKey }
    const fns = [tz.parse, meitrack.parse, cellocator.parse, queclink.parse]
    const data = fns.map(x => x(raw)).find(x => x.type !== 'UNKNOWN') || {
      raw: raw.toString(),
      type: 'UNKNOWN'
    }
    if (Array.isArray(data)) {
      return Promise.all(data.map(x => setGps(x, options)))
    } else {
      return setGps(data, options)
    }
  } catch (err) {
    return Promise.reject(err)
  }
}

/**
 * Parse command to raw command
 * @param  {Object} data Command data
 * @return {String}      Raw command
 */
const parseCommand = data => {
  let command = null
  const fns = {
    tz: tz.parseCommand,
    meitrack: meitrack.parseCommand,
    cellocator: cellocator.parseCommand,
    queclink: queclink.parseCommand
  }
  if (fns.hasOwnProperty(data.device)) {
    command = fns[data.device](data)
  }
  return command
}

/**
 * Get raw roboot command
 * @param  {Object} data Command data
 * @return {String}      Raw command
 */
const getRebootCommand = data => {
  let command = null
  if (data.device === 'tz') {
    command = tz.getRebootCommand(data.password || '000000')
  } else if (data.device === 'meitrack') {
    command = meitrack.getRebootCommand(data.imei)
  } else if (data.device === 'queclink') {
    command = queclink.getRebootCommand(data.password, data.serial)
  }
  return command
}

module.exports = {
  getImei: getImei,
  setCache: setCache,
  parse: parse,
  parseCommand: parseCommand,
  getRebootCommand: getRebootCommand,
  getCellocatorAck: cellocator.ack
}
<|endoftext|>


<commit_before>/* globals window, location, document */

// analytics.js may not have loaded it's integrations by the time we start
// tracking events, page views and identifies. So we can use these *WhenReady()
// functions to defer the action until all the intgrations are ready.
// TODO: Consider whether to export something like this, maybe provide our own
//       API instead of just using analytics.js API.
const trackEventWhenReady = (...args) =>
  window.analytics.ready(() => window.analytics.track.apply(this, args));

const trackPageWhenReady = (...args) =>
  window.analytics.ready(() => window.analytics.page.apply(this, args));

const identifyWhenReady = (...args) =>
  window.analytics.ready(() => window.analytics.identify.apply(this, args));

// This is where analytics gets called...
const logPageLoad = (title, referrer) => {
  // Use setTimeout so it uses the location from after the route change
  setTimeout(() => {
    const page = {
      title,
      referrer,
      path: location.pathname,
      search: location.search,
      url: location.href,
    };

    // Track page on analytics
    trackPageWhenReady(page.title, page);
  }, 0);
};

// A simple wrapper to be explicit about doing the first page load...
const logFirstPageLoad = () => {
  logPageLoad(document.title, document.referrer);
};

// What we're doing here is Monkey Patching(tm) the window.history.pushState()
// function because, currently, the History API provides the 'popstate' event
// but this event only gets fired when history.back(), history.go() are called
// or the user uses the browser buttons, but NOT when history.pushState() is
// called.
const configurePageLoadTracking = () => {
  // Save reference to original pushState.
  const originalPushState = window.history.pushState;

  // Wrap original pushState to call new push state function
  // NOTE: this can't be an arrow function!
  window.history.pushState = function okgrowAnalyticsMonkeyPatchedPushState(...args) {
    // Make sure we catch any exception here so that we're sure to call the
    // originalPushState function (below)
    try {
      logPageLoad(document.title, location.href);
    } catch (e) {
      console.error(e); // eslint-disable-line no-console
    }

    // Call original pushState with incoming arguments
    return originalPushState.apply(window.history, args);
  };

  window.addEventListener('popstate', () => {
    logPageLoad(document.title, location.href);
  }, false);
};

const isEmptyObject = item =>
  typeof item === 'object' && !Object.keys(item).length;

const analyticsStartup = ({ integrations, options = {}, autorun = true }) => {
  if (!isEmptyObject(integrations)) {
    // You can review the function's signature & behaviour with the below link.
    // https://github.com/segmentio/analytics.js-core/blob/master/lib/analytics.js#L93-L105
    window.analytics.initialize(integrations, options);

    if (autorun !== false) {
      logFirstPageLoad();
      configurePageLoadTracking();
    }
  } else {
    console.error('analyticsStartup has failed! Your analytic integrations are missing!'); // eslint-disable-line no-console
  }
};

// What we're doing here is hooking into the window.onload event to:
//
// a) log the first page load, and
// b) setup logging for subsequent page/history changes
//
// NOTE: One concern here is the following scenario:
//
//       1. This code loads
//       2. Some other code loads and replaces window.onload kicking us out
//          BEFORE our function can execute.
//
// Possible solution is that we make analyticsStartup() (above) a public API
// a developer can call to manually set this all up.
const bootstrapAnalytics = (settings) => {
  const originalWindowOnLoad = window.onload;

  if (typeof originalWindowOnLoad === 'function') {
    window.onload = function okgrowAnalyticsMonkeyPatchedOnLoad(...args) {
      analyticsStartup(settings);
      originalWindowOnLoad.apply(this, args);
    };
  } else {
    window.onload = () => analyticsStartup(settings);
  }
};

// returns a string containing any missing functions.
const checkForMissingFunctions = (object = {}, functionsToCheck) => {
  const missingFunctions = functionsToCheck.reduce((accum, funcToCheck) => {
    if (typeof object[funcToCheck] !== 'function') {
      return `${accum}${funcToCheck} ,`;
    }
    return accum;
  }, '');
  return missingFunctions;
};

// Make our helpers available
export { trackEventWhenReady, trackPageWhenReady, identifyWhenReady };

export default function ({ analytics, integrations, options = {}, autorun = true } = {}) {
  // Ensure we have been supplied at least the analytics & segment integration objects.
  if (typeof analytics !== 'object' || typeof integrations !== 'object') {
    throw new Error('Analytics is not logging! You must initialize analytics with the correct params.');
  }

  // Ensure we are not missing the core functions we depend on.
  const expectedAnalyticsFuncs = ['ready', 'track', 'page', 'identify'];
  const missingAnalyticsFuncs = checkForMissingFunctions(analytics, expectedAnalyticsFuncs);
  if (missingAnalyticsFuncs.length) {
    throw new Error(`Analytics is not logging! Expected analytics to contain ${missingAnalyticsFuncs} function(s).`);
  }

  // Alert the user to any Integrations they have setup in their settings,
  // but are missing from the analytics.min.js bundle they have passed to us.
  const missingIntegrations = checkForMissingFunctions(analytics.Integrations, Object.keys(integrations));
  if (missingIntegrations.length) {
    console.warn(`Your analytics is missing the integrations you specified in your settings@ Expected analytics to contain ${missingIntegrations} integration(s).`);
    console.warn('The missing integration(s) will not work without being included in your analytics.js.');
  }

  // Make analytics available globally in the console
  window.analytics = analytics;

  // Set everything up...
  bootstrapAnalytics({ integrations, options, autorun });
}
<commit_msg>refactor: use window.location instead of location directly<commit_after>/* globals window, document */

// analytics.js may not have loaded it's integrations by the time we start
// tracking events, page views and identifies. So we can use these *WhenReady()
// functions to defer the action until all the intgrations are ready.
// TODO: Consider whether to export something like this, maybe provide our own
//       API instead of just using analytics.js API.
const trackEventWhenReady = (...args) =>
  window.analytics.ready(() => window.analytics.track.apply(this, args));

const trackPageWhenReady = (...args) =>
  window.analytics.ready(() => window.analytics.page.apply(this, args));

const identifyWhenReady = (...args) =>
  window.analytics.ready(() => window.analytics.identify.apply(this, args));

// This is where analytics gets called...
const logPageLoad = (title, referrer) => {
  // Use setTimeout so it uses the location from after the route change
  setTimeout(() => {
    const page = {
      title,
      referrer,
      path: window.location.pathname,
      search: window.location.search,
      url: window.location.href,
    };

    // Track page on analytics
    trackPageWhenReady(page.title, page);
  }, 0);
};

// A simple wrapper to be explicit about doing the first page load...
const logFirstPageLoad = () => {
  logPageLoad(document.title, document.referrer);
};

// What we're doing here is Monkey Patching(tm) the window.history.pushState()
// function because, currently, the History API provides the 'popstate' event
// but this event only gets fired when history.back(), history.go() are called
// or the user uses the browser buttons, but NOT when history.pushState() is
// called.
const configurePageLoadTracking = () => {
  // Save reference to original pushState.
  const originalPushState = window.history.pushState;

  // Wrap original pushState to call new push state function
  // NOTE: this can't be an arrow function!
  window.history.pushState = function okgrowAnalyticsMonkeyPatchedPushState(...args) {
    // Make sure we catch any exception here so that we're sure to call the
    // originalPushState function (below)
    try {
      logPageLoad(document.title, window.location.href);
    } catch (e) {
      console.error(e); // eslint-disable-line no-console
    }

    // Call original pushState with incoming arguments
    return originalPushState.apply(window.history, args);
  };

  window.addEventListener('popstate', () => {
    logPageLoad(document.title, window.location.href);
  }, false);
};

const isEmptyObject = item =>
  typeof item === 'object' && !Object.keys(item).length;

const analyticsStartup = ({ integrations, options = {}, autorun = true }) => {
  if (!isEmptyObject(integrations)) {
    // You can review the function's signature & behaviour with the below link.
    // https://github.com/segmentio/analytics.js-core/blob/master/lib/analytics.js#L93-L105
    window.analytics.initialize(integrations, options);

    if (autorun !== false) {
      logFirstPageLoad();
      configurePageLoadTracking();
    }
  } else {
    console.error('analyticsStartup has failed! Your analytic integrations are missing!'); // eslint-disable-line no-console
  }
};

// What we're doing here is hooking into the window.onload event to:
//
// a) log the first page load, and
// b) setup logging for subsequent page/history changes
//
// NOTE: One concern here is the following scenario:
//
//       1. This code loads
//       2. Some other code loads and replaces window.onload kicking us out
//          BEFORE our function can execute.
//
// Possible solution is that we make analyticsStartup() (above) a public API
// a developer can call to manually set this all up.
const bootstrapAnalytics = (settings) => {
  const originalWindowOnLoad = window.onload;

  if (typeof originalWindowOnLoad === 'function') {
    window.onload = function okgrowAnalyticsMonkeyPatchedOnLoad(...args) {
      analyticsStartup(settings);
      originalWindowOnLoad.apply(this, args);
    };
  } else {
    window.onload = () => analyticsStartup(settings);
  }
};

// returns a string containing any missing functions.
const checkForMissingFunctions = (object = {}, functionsToCheck) => {
  const missingFunctions = functionsToCheck.reduce((accum, funcToCheck) => {
    if (typeof object[funcToCheck] !== 'function') {
      return `${accum}${funcToCheck} ,`;
    }
    return accum;
  }, '');
  return missingFunctions;
};

// Make our helpers available
export { trackEventWhenReady, trackPageWhenReady, identifyWhenReady };

export default function ({
  analytics,
  integrations,
  options = {},
  autorun = true,
} = {}) {
  // Ensure we have been supplied at least the analytics & segment integration objects.
  if (typeof analytics !== 'object' || typeof integrations !== 'object') {
    throw new Error('Analytics is not logging! You must initialize analytics with the correct params.');
  }

  // Ensure we are not missing the core functions we depend on.
  const expectedAnalyticsFuncs = ['ready', 'track', 'page', 'identify'];
  const missingAnalyticsFuncs = checkForMissingFunctions(analytics, expectedAnalyticsFuncs);
  if (missingAnalyticsFuncs.length) {
    throw new Error(`Analytics is not logging! Expected analytics to contain ${missingAnalyticsFuncs} function(s).`);
  }

  // Alert the user to any Integrations they have setup in their settings,
  // but are missing from the analytics.min.js bundle they have passed to us.
  const missingIntegrations = checkForMissingFunctions(analytics.Integrations, Object.keys(integrations));
  if (missingIntegrations.length) {
    console.warn(`Your analytics is missing the integrations you specified in your settings@ Expected analytics to contain ${missingIntegrations} integration(s).`);
    console.warn('The missing integration(s) will not work without being included in your analytics.js.');
  }

  // Make analytics available globally in the console
  window.analytics = analytics;

  // Set everything up...
  bootstrapAnalytics({ integrations, options, autorun });
}
<|endoftext|>


<commit_before>
// -------------------------------------------------
// Binary search
function defaultComparator(candidate, target) {
  if (target < candidate) {
    return -1
  } else if (target > candidate) {
    return 1
  } else {
    return 0
  }
}

function binarySearch(sortedList, target, low, high, comparator) {
  if (high < low) {
    return null
  }

  const mid = Math.floor((low + high) / 2)
  const result = comparator(sortedList[mid], target)

  if (result === 0) {
    return sortedList[mid]
  } else if (result < 0) {
    return binarySearch(sortedList, target, low, mid-1, comparator)
  } else {
    return binarySearch(sortedList, target, mid+1, high, comparator)
  }
}
// -------------------------------------------------

// -------------------------------------------------
// Keyframe interpolation
function timingToInterpFn(timing, lib) {
  let map = {
    'linear': lib.linearInterpolate,
    'smooth': lib.smoothInterpolate
  }
  return map[timing] || map.linear
}

function keyframeInterpolate(keyframes, t, timing, lib) {
  const first = 0
  const last = keyframes.length - 1

  if (!Array.isArray(keyframes) || !keyframes.length) {
    return lib.zero()
  }

  if (keyframes.length === 1) {
    return keyframes[first].value
  }

  if (t < keyframes[first].stop) {
    return keyframes[first].value
  }

  if (t > keyframes[last].stop) {
    return keyframes[last].value
  }

  const pairs = []
  keyframes.reduce((a, b) => {
    pairs.push([a, b])
    return b
  })

  const pair = search.binary(pairs, t, pair => {
    const [a, b] = pair

    if (t < a.stop) { return -1 }
    else if (t > b.stop) { return 1 }
    return 0
  })

  if (pair != null) {
    const [a, b] = pair
    const t_segment = (t - a.stop) / (b.stop - a.stop)
    const interpolate = timingToInterpFn(timing, lib)
    return interpolate(a.value, b.value, t_segment)
  }

  return lib.zero()
}
// -------------------------------------------------

const util = {

  smooth(t) {
    return 3 * Math.pow(t, 2) - 2 * Math.pow(t, 3)
  },

  linearInterpolate(a, b, t) {
    return a + t*(b-a)
  },

  smoothInterpolate(a, b, t) {
    return a + util.smooth(t)*(b-a)
  },

  vectorToHex(vector) {
    const hexColor = '#' + vector.map((c) => {
      const byte = Math.round(c)
      const hex = byte.toString(16)
      return hex.length == 1 ? '0' + hex : hex
    }).join('')

    return hexColor
  },

  hexToVector(hexColor) {
    const hex = hexColor.slice(1)
    const r = parseInt(hex.slice(0, 2), 16)
    const g = parseInt(hex.slice(2, 4), 16)
    const b = parseInt(hex.slice(4, 6), 16)
    return [r, g, b]
  },

  vectorToRgb(vector) {
    vector = vector.map(function (byte) {
      return Math.round(byte)
    })
    return 'rgb(' + vector + ')'
  },

  rgbToVector(rgbColor) {
    var list = rgbColor.split(/[(), ]+/)
    return list.slice(1,4).map(function (s) {
      return +s
    })
  }
}

const search = {

  binary(sortedList, target, comparator=defaultComparator) {
    return binarySearch(sortedList, target, 0, sortedList.length-1, comparator)
  }
}

const scalar = {

  zero() {
    return 0
  },

  linearInterpolate(a, b, t) {
    return util.linearInterpolate(a, b, t)
  },

  smoothInterpolate(a, b, t) {
    return util.smoothInterpolate(a, b, t)
  },

  keyframeInterpolate(keyframes, t, timing='smooth') {
    return keyframeInterpolate(keyframes, t, timing, scalar)
  }
}

const vector2d = {

  zero() {
    return [0, 0]
  },

  linearInterpolate(a, b, t) {
    const lerp = util.linearInterpolate
    return [
      lerp(a[0], b[0], t),
      lerp(a[1], b[1], t)
    ]
  },

  smoothInterpolate(a, b, t) {
    const lerp = util.smoothInterpolate
    return [
      lerp(a[0], b[0], t),
      lerp(a[1], b[1], t)
    ]
  },

  keyframeInterpolate(keyframes, t, timing='smooth') {
    return keyframeInterpolate(keyframes, t, timing, vector2d)
  }
}

const vector3d = {

  zero() {
    return [0, 0, 0]
  },

  linearInterpolate(a, b, t) {
    const lerp = util.linearInterpolate
    return [
      lerp(a[0], b[0], t),
      lerp(a[1], b[1], t),
      lerp(a[2], b[2], t)
    ]
  },

  smoothInterpolate(a, b, t) {
    const lerp = util.smoothInterpolate
    return [
      lerp(a[0], b[0], t),
      lerp(a[1], b[1], t),
      lerp(a[2], b[2], t)
    ]
  },

  keyframeInterpolate(keyframes, t, timing='smooth') {
    return keyframeInterpolate(keyframes, t, timing, vector3d)
  }
}

exports.util = util
exports.search = search
exports.vector2d = vector2d
exports.vector3d = vector3d
<commit_msg>Default timing is 'linear'<commit_after>
// -------------------------------------------------
// Binary search
function defaultComparator(candidate, target) {
  if (target < candidate) {
    return -1
  } else if (target > candidate) {
    return 1
  } else {
    return 0
  }
}

function binarySearch(sortedList, target, low, high, comparator) {
  if (high < low) {
    return null
  }

  const mid = Math.floor((low + high) / 2)
  const result = comparator(sortedList[mid], target)

  if (result === 0) {
    return sortedList[mid]
  } else if (result < 0) {
    return binarySearch(sortedList, target, low, mid-1, comparator)
  } else {
    return binarySearch(sortedList, target, mid+1, high, comparator)
  }
}
// -------------------------------------------------

// -------------------------------------------------
// Keyframe interpolation
function timingToInterpFn(timing, lib) {
  let map = {
    'linear': lib.linearInterpolate,
    'smooth': lib.smoothInterpolate
  }
  return map[timing] || map.linear
}

function keyframeInterpolate(keyframes, t, timing, lib) {
  const first = 0
  const last = keyframes.length - 1

  if (!Array.isArray(keyframes) || !keyframes.length) {
    return lib.zero()
  }

  if (keyframes.length === 1) {
    return keyframes[first].value
  }

  if (t < keyframes[first].stop) {
    return keyframes[first].value
  }

  if (t > keyframes[last].stop) {
    return keyframes[last].value
  }

  const pairs = []
  keyframes.reduce((a, b) => {
    pairs.push([a, b])
    return b
  })

  const pair = search.binary(pairs, t, pair => {
    const [a, b] = pair

    if (t < a.stop) { return -1 }
    else if (t > b.stop) { return 1 }
    return 0
  })

  if (pair != null) {
    const [a, b] = pair
    const t_segment = (t - a.stop) / (b.stop - a.stop)
    const interpolate = timingToInterpFn(timing, lib)
    return interpolate(a.value, b.value, t_segment)
  }

  return lib.zero()
}
// -------------------------------------------------

const util = {

  smooth(t) {
    return 3 * Math.pow(t, 2) - 2 * Math.pow(t, 3)
  },

  linearInterpolate(a, b, t) {
    return a + t*(b-a)
  },

  smoothInterpolate(a, b, t) {
    return a + util.smooth(t)*(b-a)
  },

  vectorToHex(vector) {
    const hexColor = '#' + vector.map((c) => {
      const byte = Math.round(c)
      const hex = byte.toString(16)
      return hex.length == 1 ? '0' + hex : hex
    }).join('')

    return hexColor
  },

  hexToVector(hexColor) {
    const hex = hexColor.slice(1)
    const r = parseInt(hex.slice(0, 2), 16)
    const g = parseInt(hex.slice(2, 4), 16)
    const b = parseInt(hex.slice(4, 6), 16)
    return [r, g, b]
  },

  vectorToRgb(vector) {
    vector = vector.map(function (byte) {
      return Math.round(byte)
    })
    return 'rgb(' + vector + ')'
  },

  rgbToVector(rgbColor) {
    var list = rgbColor.split(/[(), ]+/)
    return list.slice(1,4).map(function (s) {
      return +s
    })
  }
}

const search = {

  binary(sortedList, target, comparator=defaultComparator) {
    return binarySearch(sortedList, target, 0, sortedList.length-1, comparator)
  }
}

const scalar = {

  zero() {
    return 0
  },

  linearInterpolate(a, b, t) {
    return util.linearInterpolate(a, b, t)
  },

  smoothInterpolate(a, b, t) {
    return util.smoothInterpolate(a, b, t)
  },

  keyframeInterpolate(keyframes, t, timing='linear') {
    return keyframeInterpolate(keyframes, t, timing, scalar)
  }
}

const vector2d = {

  zero() {
    return [0, 0]
  },

  linearInterpolate(a, b, t) {
    const lerp = util.linearInterpolate
    return [
      lerp(a[0], b[0], t),
      lerp(a[1], b[1], t)
    ]
  },

  smoothInterpolate(a, b, t) {
    const lerp = util.smoothInterpolate
    return [
      lerp(a[0], b[0], t),
      lerp(a[1], b[1], t)
    ]
  },

  keyframeInterpolate(keyframes, t, timing='linear') {
    return keyframeInterpolate(keyframes, t, timing, vector2d)
  }
}

const vector3d = {

  zero() {
    return [0, 0, 0]
  },

  linearInterpolate(a, b, t) {
    const lerp = util.linearInterpolate
    return [
      lerp(a[0], b[0], t),
      lerp(a[1], b[1], t),
      lerp(a[2], b[2], t)
    ]
  },

  smoothInterpolate(a, b, t) {
    const lerp = util.smoothInterpolate
    return [
      lerp(a[0], b[0], t),
      lerp(a[1], b[1], t),
      lerp(a[2], b[2], t)
    ]
  },

  keyframeInterpolate(keyframes, t, timing='linear') {
    return keyframeInterpolate(keyframes, t, timing, vector3d)
  }
}

exports.util = util
exports.search = search
exports.vector2d = vector2d
exports.vector3d = vector3d
<|endoftext|>


<commit_before>import memoize from 'memoize-id';
import path from 'path';
import React from 'react';
import ReactMarkdown from 'react-markdown';
import { Link } from 'react-router';
import {
  filter,
  reduce,
} from 'wu';
import Children from './components/Children';
import invertMap from './utils/invertMap';
import resolvePromiseMap from './utils/resolvePromiseMap';

export { Link };
export { default as assets } from './components/assets';
export { default as Children } from './components/Children';
export { default as query } from './components/query';
export { default as QueryContext } from './components/QueryContext';
export {
  queryRoute,
  queryChildRoutes,
  resolveQueries,
} from './query';

function createMarkdownComponent(Layout = Children, meta, markdown) {
  return function MarkdownPage() {
    return (
      <Layout {...meta}>
        <ReactMarkdown
          source={markdown}
          renderers={{
            Link: props => /^~/.test(props.href)
              ? <Link to={props.href.replace(/^~/, '')} {...props} />
              : <a {...props} />,
          }}
        />
      </Layout>
    );
  };
}

/**
 * Create a react-router PlainRoute from the given module and optional routePath.
 * Memoized to ensure at most one PlainRoute is created per module.

 * XXX: this function must be memoized, otherwise `getIndexRoute()` / `getChildRoutes()`
 * will return a *new* PlainRoute, which causes `isIndexRoute` to fail.
 *
 * @param  {Object} mod
 *   Module object
 * @param  {String} [routePath]
 *   Optional path which will be used as a fallback for `mod.path`
 * @param  {Array<PlainRoute>}
 *   Optional additional routes to be appended to the module's child routes
 * @return {PlainRoute}
 *   A react-router PlainRoute
 */
export const createRoute = memoize((mod, routePath, moreChildRoutes = []) => {
  // Memoize route getters with async: 'immediate' so the cached route is synchronously available
  // to react-router upon rendering
  const getIndexRoute = (
    mod.getIndexRoute && memoize(mod.getIndexRoute, { arity: 0, async: 'immediate' })
  );
  const getChildRoutes = (
    mod.getChildRoutes && memoize(
      (location, callback) => mod.getChildRoutes(location, (error, childRoutes) => {
        callback(error, [...childRoutes, ...moreChildRoutes]);
      }),
      { arity: 0, async: 'immediate' }
    )
  );

  return {
    ...mod,
    component: mod.component || (mod.markdown && createMarkdownComponent(mod.layout, mod.meta, mod.markdown)),
    getIndexRoute,
    getChildRoutes,
    path: mod.path || routePath,
  };
});

function plainBasename(moduleName) {
  return path.basename(moduleName, path.extname(moduleName));
}

function dedupeModuleMap(moduleMap) {
  const uniqModuleMap = reduce(
    (uniq, [moduleName, mod]) =>
      (!uniq.has(mod) || moduleName < uniq.get(mod).length)
        ? uniq.set(mod, moduleName)
        : uniq,
    new Map(),
    moduleMap
  );

  return invertMap(uniqModuleMap);
}

function createRoutesFromMap(moduleMap) {
  const nonIndexModuleMap = filter(
    ([moduleName]) => plainBasename(moduleName) !== 'index',
    moduleMap
  );
  const uniqModuleMap = dedupeModuleMap(nonIndexModuleMap);
  return [...uniqModuleMap].map(
    ([moduleName, mod]) => createRoute(mod, plainBasename(moduleName))
  );
}

export function includeRoute(loadModule) {
  return async (location, callback) => {
    callback(null, await loadModule());
  };
}

export function includeRoutes(context) {
  const loadModules = () => resolvePromiseMap(new Map(
    context.keys().map(moduleName => [moduleName, context(moduleName)()])
  ));

  return async (location, callback) => {
    const moduleMap = await loadModules();
    callback(null, createRoutesFromMap(moduleMap));
  };
}
<commit_msg>Use `createRoute` when creating index routes<commit_after>import memoize from 'memoize-id';
import path from 'path';
import React from 'react';
import ReactMarkdown from 'react-markdown';
import { Link } from 'react-router';
import {
  filter,
  reduce,
} from 'wu';
import Children from './components/Children';
import invertMap from './utils/invertMap';
import resolvePromiseMap from './utils/resolvePromiseMap';

export { Link };
export { default as assets } from './components/assets';
export { default as Children } from './components/Children';
export { default as query } from './components/query';
export { default as QueryContext } from './components/QueryContext';
export {
  queryRoute,
  queryChildRoutes,
  resolveQueries,
} from './query';

function createMarkdownComponent(Layout = Children, meta, markdown) {
  return function MarkdownPage() {
    return (
      <Layout {...meta}>
        <ReactMarkdown
          source={markdown}
          renderers={{
            Link: props => /^~/.test(props.href)
              ? <Link to={props.href.replace(/^~/, '')} {...props} />
              : <a {...props} />,
          }}
        />
      </Layout>
    );
  };
}

/**
 * Create a react-router PlainRoute from the given module and optional routePath.
 * Memoized to ensure at most one PlainRoute is created per module.

 * XXX: this function must be memoized, otherwise `getIndexRoute()` / `getChildRoutes()`
 * will return a *new* PlainRoute, which causes `isIndexRoute` to fail.
 *
 * @param  {Object} mod
 *   Module object
 * @param  {String} [routePath]
 *   Optional path which will be used as a fallback for `mod.path`
 * @param  {Array<PlainRoute>}
 *   Optional additional routes to be appended to the module's child routes
 * @return {PlainRoute}
 *   A react-router PlainRoute
 */
export const createRoute = memoize((mod, routePath, moreChildRoutes = []) => {
  // Memoize route getters with async: 'immediate' so the cached route is synchronously available
  // to react-router upon rendering
  const getIndexRoute = (
    mod.getIndexRoute && memoize(mod.getIndexRoute, { arity: 0, async: 'immediate' })
  );
  const getChildRoutes = (
    mod.getChildRoutes && memoize(
      (location, callback) => mod.getChildRoutes(location, (error, childRoutes) => {
        callback(error, [...childRoutes, ...moreChildRoutes]);
      }),
      { arity: 0, async: 'immediate' }
    )
  );

  return {
    ...mod,
    component: mod.component || (mod.markdown && createMarkdownComponent(mod.layout, mod.meta, mod.markdown)),
    getIndexRoute,
    getChildRoutes,
    path: mod.path || routePath,
  };
});

function plainBasename(moduleName) {
  return path.basename(moduleName, path.extname(moduleName));
}

function dedupeModuleMap(moduleMap) {
  const uniqModuleMap = reduce(
    (uniq, [moduleName, mod]) =>
      (!uniq.has(mod) || moduleName < uniq.get(mod).length)
        ? uniq.set(mod, moduleName)
        : uniq,
    new Map(),
    moduleMap
  );

  return invertMap(uniqModuleMap);
}

function createRoutesFromMap(moduleMap) {
  const nonIndexModuleMap = filter(
    ([moduleName]) => plainBasename(moduleName) !== 'index',
    moduleMap
  );
  const uniqModuleMap = dedupeModuleMap(nonIndexModuleMap);
  return [...uniqModuleMap].map(
    ([moduleName, mod]) => createRoute(mod, plainBasename(moduleName))
  );
}

export function includeRoute(loadModule) {
  return async (location, callback) => {
    callback(null, createRoute(await loadModule()));
  };
}

export function includeRoutes(context) {
  const loadModules = () => resolvePromiseMap(new Map(
    context.keys().map(moduleName => [moduleName, context(moduleName)()])
  ));

  return async (location, callback) => {
    const moduleMap = await loadModules();
    callback(null, createRoutesFromMap(moduleMap));
  };
}
<|endoftext|>


<commit_before>import detectIt from 'detect-it';
import addListener from 'the-listener';

function setupCurrentInput() {

}

(() => {
  const body = document.querySelector('body');
  if (body) setupCurrentInput();
  else document.addEventListener('DOMContentLoaded', setupCurrentInput);
})();
<commit_msg>Add setup current input function<commit_after>import detectIt from 'detect-it';
import addListener from 'the-listener';

function setupCurrentInput() {
  const body = document.querySelector('body');
  let currentInput = undefined;

  function updateCurrentInput(input) {
    if (input !== currentInput) {
      body.classList.remove(`current-input-${currentInput}`);
      body.classList.add(`current-input-${input}`);
      currentInput = input;
    }
  }

  if (detectIt.deviceType === 'mouseOnly') updateCurrentInput('mouse');
  else if (detectIt.deviceType === 'touchOnly') {
    updateCurrentInput('touch');
    // add blank touch listener to body to enable active state when touchin the screen
    addListener(body, { 'touchstart passive capture': () => {} });
  } else {
    // add listeners for hybrid device
    addListener(window,
      {
        'touchstart passive capture': () => { updateCurrentInput('touch'); },
        'mousedown mouseenter mousemove passive capture': () => { updateCurrentInput('mouse'); },
      },
      { pointermove: false }
    );
    /* eslint no-unused-expressions: ["error", { "allowTernary": true }] */
    detectIt.primaryHover === 'hover' ? updateCurrentInput('mouse') : updateCurrentInput('touch');
  }
}

(() => {
  const body = document.querySelector('body');
  if (body) setupCurrentInput();
  else document.addEventListener('DOMContentLoaded', setupCurrentInput);
})();
<|endoftext|>


<commit_before>'use strict';

var Font = require('./lib/font.js');

// 母体
function EasyFont() {
  this.$string = '';  // 初始值
  this.$option = {}; // 配置信息
  this.$result = {}; // 输出值
  this.$operationName = ''; // 操作
  this._settedOption = false;
}

/**
 * 入口函数
 * @param str 输入的字符串
 */
EasyFont.prototype.pipe = function(str) {
  this.$string = str;
  return this;
}

/**
 * 配置参数
 * @param option {object} 操作参数配置
 * measure: { fontSize, fontFamily, src, fontWeight }
 * truncate: { fontSize, fontFamily, src, fontWeight, width, row, lack, ellipsis }
 */
EasyFont.prototype.option = function (option) {
  this.$option = option;
  this._settedOption = true;
  verifyOption.bind(this)();
  return this;
}

/**
 * 设置操作
 * @param operation EasyFont.operationType
 */
EasyFont.prototype.operation = function (operation) {
  if (!verifyOperation.bind(this, operation)()) {
    throw 'operation should be one of ' + Object.keys(this.operationType).toString();
  }
  this.$operationName = operation;

  verifyOption.bind(this)();
  // 校验操作类型
  function verifyOperation (operation) {
    return Object.keys(this.operationType).some(function (ele) {
      return operation === ele;
    })
  }
  return this;
}

/**
 * 操作类型
 */
EasyFont.prototype.operationType = {
  measure: 'measure',
  truncate: 'truncate',
}

/**
 * 终点函数，获取结果值
 * @return Promise
 */
EasyFont.prototype.value = function () {

  /**
   * 测量字符串长度信息
   * @option { fontFamily, fontSize, fontWeight, src }
   */
  if (this.$operationName === this.operationType.measure) {
    return getMeasure.bind(this, this.$string, this.$option)();
  }

  // 字符串规则截断
  if (this.$operationName === this.operationType.truncate) {
    var originString = this.$string;
    var option = this.$option;
    return new Promise(function (resolve) {
      var _truncateOpt = Object.assign({}, {
        lack: 0,
        ellipsis: '...'
      }, option);

      var _resultString = originString + _truncateOpt.ellipsis;
      getMeasure.bind(this, _resultString, option)().then(function(result) {
        if (result) {
          var originRow = result.width / _truncateOpt.width;
          // 字符串短的情况
          if (originRow < _truncateOpt.row) {
            resolve({
              result: _resultString,
              origin: originString,
              row: _truncateOpt.row,
              boxWidth: _truncateOpt.width,
              lack: _truncateOpt.lack,
              ellipsis: _truncateOpt.ellipsis
            });
          } else {
            var lastIndex = Math.floor(_truncateOpt.width * _resultString.length / result.lenght) - 1;

            resolve({
              result: _resultString.slice(0, lastIndex),
              origin: originString,
              row: _truncateOpt.row,
              boxWidth: _truncateOpt.width,
              lack: _truncateOpt.lack,
              ellipsis: _truncateOpt.ellipsis
            });
          }
        }
      });
    });
  }
}

// 校验配置
function verifyOption () {
  if (this.$operationName && this.$operationName === this.operationType.truncate && this._settedOption && (!this.$option.width || !this.$option.row)) {
    throw new Error(this.$operationName + ' operation option mandotory: width, row');
  }
}

// 测量字符串
function getMeasure(str, option) {
  return new Promise(function (resolve) {
    // 判断依赖
    if (!Font) {
      console.error('EasyFont Error: easyfont should run into browser env.');
      resolve(null);
    }
    // 配置信息
    var _opt = Object.assign({
      fontFamily: "'PingFang SC', sans-serif",
      fontSize: 16,
      fontWeight: '',
      src: '', // use system font(s)
    }, option || {});

    // 『空』字符串的情况
    if (!str) {
      resolve({
        width: 0,
        fontSize: _opt.fontSize,
        fontFamily: _opt.fontFamily,
        src: _opt.src || 'system font'
      })
    }

    // 初始化
    var font = new Font();
    // 加载
    font.onload = function () {
      var measure = font.measureText(str, _opt.fontSize, _opt.fontWeight);
      resolve({
        width: measure.width,
        fontSize: measure.fontSize,
        fontFamily: measure.fontFamily,
        src: _opt.src || 'system font'
      });
    }

    // 加载失败
    font.onerror = function (err) {
      console.error('EasyFont Error: ' + err);
      resolve(null);
    }

    // 使用src标记加载事件完成
    font.fontFamily = _opt.fontFamily;
    font.src = _opt.src;
  });
}

module.exports = EasyFont;

/*
输入： string, width, row, lack, ellipsis

*/<commit_msg>update<commit_after>'use strict';

var Font = require('./lib/font.js');

// 母体
function EasyFont() {
  this.$string = '';  // 初始值
  this.$option = {}; // 配置信息
  this.$result = {}; // 输出值
  this.$operationName = ''; // 操作
  this._settedOption = false;
}

/**
 * 入口函数
 * @param str 输入的字符串
 */
EasyFont.prototype.pipe = function(str) {
  this.$string = str;
  return this;
}

/**
 * 配置参数
 * @param option {object} 操作参数配置
 * measure: { fontSize, fontFamily, src, fontWeight }
 * truncate: { fontSize, fontFamily, src, fontWeight, width, row, lack, ellipsis }
 */
EasyFont.prototype.option = function (option) {
  this.$option = option;
  this._settedOption = true;
  verifyOption.bind(this)();
  return this;
}

/**
 * 设置操作
 * @param operation EasyFont.operationType
 */
EasyFont.prototype.operation = function (operation) {
  if (!verifyOperation.bind(this, operation)()) {
    throw 'operation should be one of ' + Object.keys(this.operationType).toString();
  }
  this.$operationName = operation;

  verifyOption.bind(this)();
  // 校验操作类型
  function verifyOperation (operation) {
    return Object.keys(this.operationType).some(function (ele) {
      return operation === ele;
    })
  }
  return this;
}

/**
 * 操作类型
 */
EasyFont.prototype.operationType = {
  measure: 'measure',
  truncate: 'truncate',
}

/**
 * 终点函数，获取结果值
 * @return Promise
 */
EasyFont.prototype.value = function () {

  /**
   * 测量字符串长度信息
   * @option { fontFamily, fontSize, fontWeight, src }
   */
  if (this.$operationName === this.operationType.measure) {
    return getMeasure.bind(this, this.$string, this.$option)();
  }

  // 字符串规则截断
  if (this.$operationName === this.operationType.truncate) {
    var originString = this.$string;
    var option = this.$option;
    return new Promise(function (resolve) {
      var _truncateOpt = Object.assign({}, {
        lack: 0,
        ellipsis: '...'
      }, option);

      var _resultString = originString + _truncateOpt.ellipsis;
      getMeasure.bind(this, _resultString, option)().then(function(result) {
        if (result) {
          var originRow = result.width / _truncateOpt.width;
          // 字符串短的情况
          if (originRow < _truncateOpt.row) {
            resolve({
              result: _resultString,
              origin: originString,
              row: _truncateOpt.row,
              boxWidth: _truncateOpt.width,
              lack: _truncateOpt.lack,
              ellipsis: _truncateOpt.ellipsis
            });
          } else {
            var lastIndex = Math.floor(_truncateOpt.width * _truncateOpt.row * _resultString.length / result.lenght);

            resolve({
              result: _resultString.slice(0, lastIndex),
              origin: originString,
              row: _truncateOpt.row,
              boxWidth: _truncateOpt.width,
              lack: _truncateOpt.lack,
              ellipsis: _truncateOpt.ellipsis
            });
          }
        }
      });
    });
  }
}

// 校验配置
function verifyOption () {
  if (this.$operationName && this.$operationName === this.operationType.truncate && this._settedOption && (!this.$option.width || !this.$option.row)) {
    throw new Error(this.$operationName + ' operation option mandotory: width, row');
  }
}

// 测量字符串
function getMeasure(str, option) {
  return new Promise(function (resolve) {
    // 判断依赖
    if (!Font) {
      console.error('EasyFont Error: easyfont should run into browser env.');
      resolve(null);
    }
    // 配置信息
    var _opt = Object.assign({
      fontFamily: "'PingFang SC', sans-serif",
      fontSize: 16,
      fontWeight: '',
      src: '', // use system font(s)
    }, option || {});

    // 『空』字符串的情况
    if (!str) {
      resolve({
        width: 0,
        fontSize: _opt.fontSize,
        fontFamily: _opt.fontFamily,
        src: _opt.src || 'system font'
      })
    }

    // 初始化
    var font = new Font();
    // 加载
    font.onload = function () {
      var measure = font.measureText(str, _opt.fontSize, _opt.fontWeight);
      resolve({
        width: measure.width,
        fontSize: measure.fontSize,
        fontFamily: measure.fontFamily,
        src: _opt.src || 'system font'
      });
    }

    // 加载失败
    font.onerror = function (err) {
      console.error('EasyFont Error: ' + err);
      resolve(null);
    }

    // 使用src标记加载事件完成
    font.fontFamily = _opt.fontFamily;
    font.src = _opt.src;
  });
}

module.exports = EasyFont;

/*
输入： string, width, row, lack, ellipsis

*/<|endoftext|>


<commit_before>#!/usr/bin/env node

const fs = require('fs');
const path = require('path');
const os = require('os');
const GreenlockExpress = require('greenlock-express')
const DB = require('./services/database');
const { isAlreadyRunning, notifyRunning } = require('./services/is-already-running');
const bannerMessage = require('./services/cli-banner-message');
const httpsPrompts = require('./services/https-prompts');
const app = require('./app');

function showError(str) {
    console.error(str);

    if (process.pkg) {
        // In desktop app mode, keep the process alive
        console.error('(Press Ctrl+C to exit.)')
        setInterval(() => {}, 10000);
    } else {
        // In command line mode, exit with error code
        process.exit(1);
    }
}

(async function main() {
    // determine data directory
    const dataDir = path.join(process.env.APPDATA || os.homedir(), 'rpnow_data');

    // if not exists
    if (!fs.existsSync(dataDir)){
        // if interactive, prompt to make sure creating a directory is ok
        // TODO

        // create directory
        fs.mkdirSync(dataDir);
    }

    // initialize db
    await DB.open(dataDir);

    // check if the server is already running (or ports are used)
    if (await isAlreadyRunning(dataDir)) {
        return showError('ERROR: RPNow is already running');
    }

    // get remaining config (may trigger prompts if interactive)
    // const { https, letsencryptTos, letsencryptEmail, domain } = await httpsPrompts();
    const config = {
        httpPort: (+process.env.RPNOW_PORT) || 80,
        https: false,
        httpsPort: 443,
        domain: '',
        letsencryptAgree: false,
        letsencryptEmail: '',
        trustProxy: (process.env.RPNOW_TRUST_PROXY || '').toLowerCase() === 'true',
    };

    // write lastport.lock
    notifyRunning(dataDir, config.httpsPort || config.httpPort)

    // enable trustProxy?
    if (config.trustProxy) app.enable('trust proxy');

    // start server
    if (config.https) {
        if (!config.letsencryptAgree) return showError("ERROR: You must accept the Let's Encrypt TOS to use this service.");

        const httpsServer = GreenlockExpress.create({
            app,

            email: config.letsencryptEmail,
            agreeTos: config.letsencryptAgree,
            approvedDomains: [config.domain],
            configDir: dataDir,

            communityMember: false,
            securityUpdates: false,
            telemetry: true,
        });
        httpsServer.listen(config.httpPort, config.httpsPort, (err) => {
            if (err) return showError(`ERROR: RPNow failed to start: ${err}`);

            // delay logging this until messages generated by vue-pronto are done
            setTimeout(() => console.log(bannerMessage(config)), 2000);
        });
    } else {
        app.listen(config.httpPort, (err) => {
            if (err) return showError(`ERROR: RPNow failed to start: ${err}`);

            // delay logging this until messages generated by vue-pronto are done
            setTimeout(() => console.log(bannerMessage(config)), 2000);
        });
    }
}());
<commit_msg>contain letsencrypt data in one subfolder<commit_after>#!/usr/bin/env node

const fs = require('fs');
const path = require('path');
const os = require('os');
const GreenlockExpress = require('greenlock-express')
const DB = require('./services/database');
const { isAlreadyRunning, notifyRunning } = require('./services/is-already-running');
const bannerMessage = require('./services/cli-banner-message');
const httpsPrompts = require('./services/https-prompts');
const app = require('./app');

function showError(str) {
    console.error(str);

    if (process.pkg) {
        // In desktop app mode, keep the process alive
        console.error('(Press Ctrl+C to exit.)')
        setInterval(() => {}, 10000);
    } else {
        // In command line mode, exit with error code
        process.exit(1);
    }
}

(async function main() {
    // determine data directory
    const dataDir = path.join(process.env.APPDATA || os.homedir(), 'rpnow_data');
    const letsencryptDir = path.join(dataDir, 'letsencrypt');

    // if not exists
    if (!fs.existsSync(dataDir)){
        // if interactive, prompt to make sure creating a directory is ok
        // TODO

        // create directory
        fs.mkdirSync(dataDir);
        fs.mkdirSync(letsencryptDir);
    }

    // initialize db
    await DB.open(dataDir);

    // check if the server is already running (or ports are used)
    if (await isAlreadyRunning(dataDir)) {
        return showError('ERROR: RPNow is already running');
    }

    // get remaining config (may trigger prompts if interactive)
    // const { https, letsencryptTos, letsencryptEmail, domain } = await httpsPrompts();
    const config = {
        httpPort: (+process.env.RPNOW_PORT) || 80,
        https: false,
        httpsPort: 443,
        domain: '',
        letsencryptAgree: false,
        letsencryptEmail: '',
        trustProxy: (process.env.RPNOW_TRUST_PROXY || '').toLowerCase() === 'true',
    };

    // write lastport.lock
    notifyRunning(dataDir, config.httpsPort || config.httpPort)

    // enable trustProxy?
    if (config.trustProxy) app.enable('trust proxy');

    // start server
    if (config.https) {
        if (!config.letsencryptAgree) return showError("ERROR: You must accept the Let's Encrypt TOS to use this service.");

        const httpsServer = GreenlockExpress.create({
            app,

            email: config.letsencryptEmail,
            agreeTos: config.letsencryptAgree,
            approvedDomains: [config.domain],
            configDir: letsencryptDir,

            communityMember: false,
            securityUpdates: false,
            telemetry: true,
        });
        httpsServer.listen(config.httpPort, config.httpsPort, (err) => {
            if (err) return showError(`ERROR: RPNow failed to start: ${err}`);

            // delay logging this until messages generated by vue-pronto are done
            setTimeout(() => console.log(bannerMessage(config)), 2000);
        });
    } else {
        app.listen(config.httpPort, (err) => {
            if (err) return showError(`ERROR: RPNow failed to start: ${err}`);

            // delay logging this until messages generated by vue-pronto are done
            setTimeout(() => console.log(bannerMessage(config)), 2000);
        });
    }
}());
<|endoftext|>


<commit_before>var EventEmitter = require('events');
var util = require('util');
var Blocks = require('./engine/blocks');
var Sprite = require('./sprites/sprite');
var Runtime = require('./engine/runtime');
var sb2import = require('./import/sb2import');

/**
 * Handles connections between blocks, stage, and extensions.
 *
 * @author Andrew Sliwinski <ascii@media.mit.edu>
 */
var VirtualMachine = function () {
    var instance = this;
    // Bind event emitter and runtime to VM instance
    EventEmitter.call(instance);
    /**
     * VM runtime, to store blocks, I/O devices, sprites/targets, etc.
     * @type {!Runtime}
     */
    instance.runtime = new Runtime();
    /**
     * The "currently editing"/selected target ID for the VM.
     * Block events from any Blockly workspace are routed to this target.
     * @type {!string}
     */
    instance.editingTarget = null;
    // Runtime emits are passed along as VM emits.
    instance.runtime.on(Runtime.SCRIPT_GLOW_ON, function (glowData) {
        instance.emit(Runtime.SCRIPT_GLOW_ON, glowData);
    });
    instance.runtime.on(Runtime.SCRIPT_GLOW_OFF, function (glowData) {
        instance.emit(Runtime.SCRIPT_GLOW_OFF, glowData);
    });
    instance.runtime.on(Runtime.BLOCK_GLOW_ON, function (glowData) {
        instance.emit(Runtime.BLOCK_GLOW_ON, glowData);
    });
    instance.runtime.on(Runtime.BLOCK_GLOW_OFF, function (glowData) {
        instance.emit(Runtime.BLOCK_GLOW_OFF, glowData);
    });
    instance.runtime.on(Runtime.PROJECT_RUN_START, function () {
        instance.emit(Runtime.PROJECT_RUN_START);
    });
    instance.runtime.on(Runtime.PROJECT_RUN_STOP, function () {
        instance.emit(Runtime.PROJECT_RUN_STOP);
    });
    instance.runtime.on(Runtime.VISUAL_REPORT, function (visualReport) {
        instance.emit(Runtime.VISUAL_REPORT, visualReport);
    });
    instance.runtime.on(Runtime.SPRITE_INFO_REPORT, function (spriteInfo) {
        instance.emit(Runtime.SPRITE_INFO_REPORT, spriteInfo);
    });

    this.blockListener = this.blockListener.bind(this);
    this.flyoutBlockListener = this.flyoutBlockListener.bind(this);
};

/**
 * Inherit from EventEmitter
 */
util.inherits(VirtualMachine, EventEmitter);

/**
 * Start running the VM - do this before anything else.
 */
VirtualMachine.prototype.start = function () {
    this.runtime.start();
};

/**
 * "Green flag" handler - start all threads starting with a green flag.
 */
VirtualMachine.prototype.greenFlag = function () {
    this.runtime.greenFlag();
};

/**
 * Set whether the VM is in "turbo mode."
 * When true, loops don't yield to redraw.
 * @param {Boolean} turboModeOn Whether turbo mode should be set.
 */
VirtualMachine.prototype.setTurboMode = function (turboModeOn) {
    this.runtime.turboMode = !!turboModeOn;
};

/**
 * Set whether the VM is in 2.0 "compatibility mode."
 * When true, ticks go at 2.0 speed (30 TPS).
 * @param {Boolean} compatibilityModeOn Whether compatibility mode is set.
 */
VirtualMachine.prototype.setCompatibilityMode = function (compatibilityModeOn) {
    this.runtime.setCompatibilityMode(!!compatibilityModeOn);
};

/**
 * Stop all threads and running activities.
 */
VirtualMachine.prototype.stopAll = function () {
    this.runtime.stopAll();
};

/**
 * Clear out current running project data.
 */
VirtualMachine.prototype.clear = function () {
    this.runtime.dispose();
    this.editingTarget = null;
    this.emitTargetsUpdate();
};

/**
 * Get data for playground. Data comes back in an emitted event.
 */
VirtualMachine.prototype.getPlaygroundData = function () {
    var instance = this;
    // Only send back thread data for the current editingTarget.
    var threadData = this.runtime.threads.filter(function (thread) {
        return thread.target === instance.editingTarget;
    });
    // Remove the target key, since it's a circular reference.
    var filteredThreadData = JSON.stringify(threadData, function (key, value) {
        if (key === 'target') return;
        return value;
    }, 2);
    this.emit('playgroundData', {
        blocks: this.editingTarget.blocks,
        threads: filteredThreadData
    });
};

/**
 * Post I/O data to the virtual devices.
 * @param {?string} device Name of virtual I/O device.
 * @param {Object} data Any data object to post to the I/O device.
 */
VirtualMachine.prototype.postIOData = function (device, data) {
    if (this.runtime.ioDevices[device]) {
        this.runtime.ioDevices[device].postData(data);
    }
};

/**
 * Load a project from a Scratch 2.0 JSON representation.
 * @param {?string} json JSON string representing the project.
 */
VirtualMachine.prototype.loadProject = function (json) {
    this.clear();
    // @todo: Handle other formats, e.g., Scratch 1.4, Scratch 3.0.
    sb2import(json, this.runtime);
    // Select the first target for editing, e.g., the first sprite.
    this.editingTarget = this.runtime.targets[1];
    // Update the VM user's knowledge of targets and blocks on the workspace.
    this.emitTargetsUpdate();
    this.emitWorkspaceUpdate();
    this.runtime.setEditingTarget(this.editingTarget);
};

VirtualMachine.prototype.toJSON = function () {
    var obj = new Object();
    obj.sprites = this.runtime.targets;
    obj.meta = new Object();
    obj.meta.name = 'WIP';
    obj.meta.useragent = navigator.userAgent;
    return JSON.stringify(obj, null, 2);
};

VirtualMachine.prototype.fromJSON = function (json) {
    this.clear();
    var obj = JSON.parse(json);
    var i = 0;
    for (; i < obj.sprites.length; i++) {
        var blocks = new Blocks();
        var z = null;
        for (z in obj.sprites[i].sprite.blocks) {
            blocks[z] = obj.sprites[i].sprite.blocks[z];
        }
        var sprite = new Sprite(blocks, this.runtime);
        var y = null;
        for (y in obj.sprites[i].sprite) {
            if (y === 'blocks') {
                continue;
            }
            sprite[y] = obj.sprites[i].sprite[y];
        }
        var target = sprite.createClone();
        this.runtime.targets.push(target);
        var x = null;
        for (x in obj.sprites[i]) {
            if (x === 'sprite') {
                continue;
            }
            target[x] = obj.sprites[i][x];
        }
    }
    this.editingTarget = this.runtime.targets[1];
    // Update the VM user's knowledge of targets and blocks on the workspace.
    this.emitTargetsUpdate();
    this.emitWorkspaceUpdate();
    this.runtime.setEditingTarget(this.editingTarget);
};

/**
 * Add a single sprite from the "Sprite2" (i.e., SB2 sprite) format.
 * @param {?string} json JSON string representing the sprite.
 */
VirtualMachine.prototype.addSprite2 = function (json) {
    // Select new sprite.
    this.editingTarget = sb2import(json, this.runtime, true);
    // Update the VM user's knowledge of targets and blocks on the workspace.
    this.emitTargetsUpdate();
    this.emitWorkspaceUpdate();
    this.runtime.setEditingTarget(this.editingTarget);
};

/**
 * Add a costume to the current editing target.
 * @param {!Object} costumeObject Object representing the costume.
 */
VirtualMachine.prototype.addCostume = function (costumeObject) {
    this.editingTarget.sprite.costumes.push(costumeObject);
    // Switch to the costume.
    this.editingTarget.setCostume(
        this.editingTarget.sprite.costumes.length - 1
    );
};

/**
 * Add a backdrop to the stage.
 * @param {!Object} backdropObject Object representing the backdrop.
 */
VirtualMachine.prototype.addBackdrop = function (backdropObject) {
    var stage = this.runtime.getTargetForStage();
    stage.sprite.costumes.push(backdropObject);
    // Switch to the backdrop.
    stage.setCostume(stage.sprite.costumes.length - 1);
};

/**
 * Rename a sprite.
 * @param {string} targetId ID of a target whose sprite to rename.
 * @param {string} newName New name of the sprite.
 */
VirtualMachine.prototype.renameSprite = function (targetId, newName) {
    var target = this.runtime.getTargetById(targetId);
    if (target) {
        if (!target.isSprite()) {
            throw new Error('Cannot rename non-sprite targets.');
        }
        var sprite = target.sprite;
        if (!sprite) {
            throw new Error('No sprite associated with this target.');
        }
        sprite.name = newName;
        this.emitTargetsUpdate();
    } else {
        throw new Error('No target with the provided id.');
    }
};

/**
 * Delete a sprite and all its clones.
 * @param {string} targetId ID of a target whose sprite to delete.
 */
VirtualMachine.prototype.deleteSprite = function (targetId) {
    var target = this.runtime.getTargetById(targetId);
    if (target) {
        if (!target.isSprite()) {
            throw new Error('Cannot delete non-sprite targets.');
        }
        var sprite = target.sprite;
        if (!sprite) {
            throw new Error('No sprite associated with this target.');
        }
        var currentEditingTarget = this.editingTarget;
        for (var i = 0; i < sprite.clones.length; i++) {
            var clone = sprite.clones[i];
            this.runtime.stopForTarget(sprite.clones[i]);
            this.runtime.disposeTarget(sprite.clones[i]);
            // Ensure editing target is switched if we are deleting it.
            if (clone === currentEditingTarget) {
                this.setEditingTarget(this.runtime.targets[0].id);
            }
        }
        // Sprite object should be deleted by GC.
        this.emitTargetsUpdate();
    } else {
        throw new Error('No target with the provided id.');
    }
};

/**
 * Set the renderer for the VM/runtime
 * @param {!RenderWebGL} renderer The renderer to attach
 */
VirtualMachine.prototype.attachRenderer = function (renderer) {
    this.runtime.attachRenderer(renderer);
};

/**
 * Handle a Blockly event for the current editing target.
 * @param {!Blockly.Event} e Any Blockly event.
 */
VirtualMachine.prototype.blockListener = function (e) {
    if (this.editingTarget) {
        this.editingTarget.blocks.blocklyListen(e, this.runtime);
    }
};

/**
 * Handle a Blockly event for the flyout.
 * @param {!Blockly.Event} e Any Blockly event.
 */
VirtualMachine.prototype.flyoutBlockListener = function (e) {
    this.runtime.flyoutBlocks.blocklyListen(e, this.runtime);
};

/**
 * Set an editing target. An editor UI can use this function to switch
 * between editing different targets, sprites, etc.
 * After switching the editing target, the VM may emit updates
 * to the list of targets and any attached workspace blocks
 * (see `emitTargetsUpdate` and `emitWorkspaceUpdate`).
 * @param {string} targetId Id of target to set as editing.
 */
VirtualMachine.prototype.setEditingTarget = function (targetId) {
    // Has the target id changed? If not, exit.
    if (targetId === this.editingTarget.id) {
        return;
    }
    var target = this.runtime.getTargetById(targetId);
    if (target) {
        this.editingTarget = target;
        // Emit appropriate UI updates.
        this.emitTargetsUpdate();
        this.emitWorkspaceUpdate();
        this.runtime.setEditingTarget(target);
    }
};

/**
 * Emit metadata about available targets.
 * An editor UI could use this to display a list of targets and show
 * the currently editing one.
 */
VirtualMachine.prototype.emitTargetsUpdate = function () {
    this.emit('targetsUpdate', {
        // [[target id, human readable target name], ...].
        targetList: this.runtime.targets.filter(function (target) {
            // Don't report clones.
            return !target.hasOwnProperty('isOriginal') || target.isOriginal;
        }).map(function (target) {
            return [target.id, target.getName()];
        }),
        // Currently editing target id.
        editingTarget: this.editingTarget ? this.editingTarget.id : null
    });
};

/**
 * Emit an Blockly/scratch-blocks compatible XML representation
 * of the current editing target's blocks.
 */
VirtualMachine.prototype.emitWorkspaceUpdate = function () {
    this.emit('workspaceUpdate', {
        xml: this.editingTarget.blocks.toXML()
    });
};

/**
 * Post/edit sprite info for the current editing target.
 * @param {object} data An object with sprite info data to set.
 */
VirtualMachine.prototype.postSpriteInfo = function (data) {
    this.editingTarget.postSpriteInfo(data);
};

module.exports = VirtualMachine;
<commit_msg>Fix Drawable Properties Not Getting Updated<commit_after>var EventEmitter = require('events');
var util = require('util');
var Blocks = require('./engine/blocks');
var Sprite = require('./sprites/sprite');
var Runtime = require('./engine/runtime');
var sb2import = require('./import/sb2import');

/**
 * Handles connections between blocks, stage, and extensions.
 *
 * @author Andrew Sliwinski <ascii@media.mit.edu>
 */
var VirtualMachine = function () {
    var instance = this;
    // Bind event emitter and runtime to VM instance
    EventEmitter.call(instance);
    /**
     * VM runtime, to store blocks, I/O devices, sprites/targets, etc.
     * @type {!Runtime}
     */
    instance.runtime = new Runtime();
    /**
     * The "currently editing"/selected target ID for the VM.
     * Block events from any Blockly workspace are routed to this target.
     * @type {!string}
     */
    instance.editingTarget = null;
    // Runtime emits are passed along as VM emits.
    instance.runtime.on(Runtime.SCRIPT_GLOW_ON, function (glowData) {
        instance.emit(Runtime.SCRIPT_GLOW_ON, glowData);
    });
    instance.runtime.on(Runtime.SCRIPT_GLOW_OFF, function (glowData) {
        instance.emit(Runtime.SCRIPT_GLOW_OFF, glowData);
    });
    instance.runtime.on(Runtime.BLOCK_GLOW_ON, function (glowData) {
        instance.emit(Runtime.BLOCK_GLOW_ON, glowData);
    });
    instance.runtime.on(Runtime.BLOCK_GLOW_OFF, function (glowData) {
        instance.emit(Runtime.BLOCK_GLOW_OFF, glowData);
    });
    instance.runtime.on(Runtime.PROJECT_RUN_START, function () {
        instance.emit(Runtime.PROJECT_RUN_START);
    });
    instance.runtime.on(Runtime.PROJECT_RUN_STOP, function () {
        instance.emit(Runtime.PROJECT_RUN_STOP);
    });
    instance.runtime.on(Runtime.VISUAL_REPORT, function (visualReport) {
        instance.emit(Runtime.VISUAL_REPORT, visualReport);
    });
    instance.runtime.on(Runtime.SPRITE_INFO_REPORT, function (spriteInfo) {
        instance.emit(Runtime.SPRITE_INFO_REPORT, spriteInfo);
    });

    this.blockListener = this.blockListener.bind(this);
    this.flyoutBlockListener = this.flyoutBlockListener.bind(this);
};

/**
 * Inherit from EventEmitter
 */
util.inherits(VirtualMachine, EventEmitter);

/**
 * Start running the VM - do this before anything else.
 */
VirtualMachine.prototype.start = function () {
    this.runtime.start();
};

/**
 * "Green flag" handler - start all threads starting with a green flag.
 */
VirtualMachine.prototype.greenFlag = function () {
    this.runtime.greenFlag();
};

/**
 * Set whether the VM is in "turbo mode."
 * When true, loops don't yield to redraw.
 * @param {Boolean} turboModeOn Whether turbo mode should be set.
 */
VirtualMachine.prototype.setTurboMode = function (turboModeOn) {
    this.runtime.turboMode = !!turboModeOn;
};

/**
 * Set whether the VM is in 2.0 "compatibility mode."
 * When true, ticks go at 2.0 speed (30 TPS).
 * @param {Boolean} compatibilityModeOn Whether compatibility mode is set.
 */
VirtualMachine.prototype.setCompatibilityMode = function (compatibilityModeOn) {
    this.runtime.setCompatibilityMode(!!compatibilityModeOn);
};

/**
 * Stop all threads and running activities.
 */
VirtualMachine.prototype.stopAll = function () {
    this.runtime.stopAll();
};

/**
 * Clear out current running project data.
 */
VirtualMachine.prototype.clear = function () {
    this.runtime.dispose();
    this.editingTarget = null;
    this.emitTargetsUpdate();
};

/**
 * Get data for playground. Data comes back in an emitted event.
 */
VirtualMachine.prototype.getPlaygroundData = function () {
    var instance = this;
    // Only send back thread data for the current editingTarget.
    var threadData = this.runtime.threads.filter(function (thread) {
        return thread.target === instance.editingTarget;
    });
    // Remove the target key, since it's a circular reference.
    var filteredThreadData = JSON.stringify(threadData, function (key, value) {
        if (key === 'target') return;
        return value;
    }, 2);
    this.emit('playgroundData', {
        blocks: this.editingTarget.blocks,
        threads: filteredThreadData
    });
};

/**
 * Post I/O data to the virtual devices.
 * @param {?string} device Name of virtual I/O device.
 * @param {Object} data Any data object to post to the I/O device.
 */
VirtualMachine.prototype.postIOData = function (device, data) {
    if (this.runtime.ioDevices[device]) {
        this.runtime.ioDevices[device].postData(data);
    }
};

/**
 * Load a project from a Scratch 2.0 JSON representation.
 * @param {?string} json JSON string representing the project.
 */
VirtualMachine.prototype.loadProject = function (json) {
    this.clear();
    // @todo: Handle other formats, e.g., Scratch 1.4, Scratch 3.0.
    sb2import(json, this.runtime);
    // Select the first target for editing, e.g., the first sprite.
    this.editingTarget = this.runtime.targets[1];
    // Update the VM user's knowledge of targets and blocks on the workspace.
    this.emitTargetsUpdate();
    this.emitWorkspaceUpdate();
    this.runtime.setEditingTarget(this.editingTarget);
};

VirtualMachine.prototype.toJSON = function () {
    var obj = new Object();
    obj.sprites = this.runtime.targets;
    obj.meta = new Object();
    obj.meta.name = 'WIP';
    obj.meta.useragent = navigator.userAgent;
    return JSON.stringify(obj, null, 2);
};

VirtualMachine.prototype.fromJSON = function (json) {
    this.clear();
    var obj = JSON.parse(json);
    var i = 0;
    for (; i < obj.sprites.length; i++) {
        var blocks = new Blocks();
        var z = null;
        for (z in obj.sprites[i].sprite.blocks) {
            blocks[z] = obj.sprites[i].sprite.blocks[z];
        }
        var sprite = new Sprite(blocks, this.runtime);
        var y = null;
        for (y in obj.sprites[i].sprite) {
            if (y === 'blocks') {
                continue;
            }
            sprite[y] = obj.sprites[i].sprite[y];
        }
        var target = sprite.createClone();
        this.runtime.targets.push(target);
        var x = null;
        for (x in obj.sprites[i]) {
            if (x === 'sprite') {
                continue;
            }
            target[x] = obj.sprites[i][x];
        }
        target.updateAllDrawablePropertie();
    }
    this.editingTarget = this.runtime.targets[1];
    // Update the VM user's knowledge of targets and blocks on the workspace.
    this.emitTargetsUpdate();
    this.emitWorkspaceUpdate();
    this.runtime.setEditingTarget(this.editingTarget);
};

/**
 * Add a single sprite from the "Sprite2" (i.e., SB2 sprite) format.
 * @param {?string} json JSON string representing the sprite.
 */
VirtualMachine.prototype.addSprite2 = function (json) {
    // Select new sprite.
    this.editingTarget = sb2import(json, this.runtime, true);
    // Update the VM user's knowledge of targets and blocks on the workspace.
    this.emitTargetsUpdate();
    this.emitWorkspaceUpdate();
    this.runtime.setEditingTarget(this.editingTarget);
};

/**
 * Add a costume to the current editing target.
 * @param {!Object} costumeObject Object representing the costume.
 */
VirtualMachine.prototype.addCostume = function (costumeObject) {
    this.editingTarget.sprite.costumes.push(costumeObject);
    // Switch to the costume.
    this.editingTarget.setCostume(
        this.editingTarget.sprite.costumes.length - 1
    );
};

/**
 * Add a backdrop to the stage.
 * @param {!Object} backdropObject Object representing the backdrop.
 */
VirtualMachine.prototype.addBackdrop = function (backdropObject) {
    var stage = this.runtime.getTargetForStage();
    stage.sprite.costumes.push(backdropObject);
    // Switch to the backdrop.
    stage.setCostume(stage.sprite.costumes.length - 1);
};

/**
 * Rename a sprite.
 * @param {string} targetId ID of a target whose sprite to rename.
 * @param {string} newName New name of the sprite.
 */
VirtualMachine.prototype.renameSprite = function (targetId, newName) {
    var target = this.runtime.getTargetById(targetId);
    if (target) {
        if (!target.isSprite()) {
            throw new Error('Cannot rename non-sprite targets.');
        }
        var sprite = target.sprite;
        if (!sprite) {
            throw new Error('No sprite associated with this target.');
        }
        sprite.name = newName;
        this.emitTargetsUpdate();
    } else {
        throw new Error('No target with the provided id.');
    }
};

/**
 * Delete a sprite and all its clones.
 * @param {string} targetId ID of a target whose sprite to delete.
 */
VirtualMachine.prototype.deleteSprite = function (targetId) {
    var target = this.runtime.getTargetById(targetId);
    if (target) {
        if (!target.isSprite()) {
            throw new Error('Cannot delete non-sprite targets.');
        }
        var sprite = target.sprite;
        if (!sprite) {
            throw new Error('No sprite associated with this target.');
        }
        var currentEditingTarget = this.editingTarget;
        for (var i = 0; i < sprite.clones.length; i++) {
            var clone = sprite.clones[i];
            this.runtime.stopForTarget(sprite.clones[i]);
            this.runtime.disposeTarget(sprite.clones[i]);
            // Ensure editing target is switched if we are deleting it.
            if (clone === currentEditingTarget) {
                this.setEditingTarget(this.runtime.targets[0].id);
            }
        }
        // Sprite object should be deleted by GC.
        this.emitTargetsUpdate();
    } else {
        throw new Error('No target with the provided id.');
    }
};

/**
 * Set the renderer for the VM/runtime
 * @param {!RenderWebGL} renderer The renderer to attach
 */
VirtualMachine.prototype.attachRenderer = function (renderer) {
    this.runtime.attachRenderer(renderer);
};

/**
 * Handle a Blockly event for the current editing target.
 * @param {!Blockly.Event} e Any Blockly event.
 */
VirtualMachine.prototype.blockListener = function (e) {
    if (this.editingTarget) {
        this.editingTarget.blocks.blocklyListen(e, this.runtime);
    }
};

/**
 * Handle a Blockly event for the flyout.
 * @param {!Blockly.Event} e Any Blockly event.
 */
VirtualMachine.prototype.flyoutBlockListener = function (e) {
    this.runtime.flyoutBlocks.blocklyListen(e, this.runtime);
};

/**
 * Set an editing target. An editor UI can use this function to switch
 * between editing different targets, sprites, etc.
 * After switching the editing target, the VM may emit updates
 * to the list of targets and any attached workspace blocks
 * (see `emitTargetsUpdate` and `emitWorkspaceUpdate`).
 * @param {string} targetId Id of target to set as editing.
 */
VirtualMachine.prototype.setEditingTarget = function (targetId) {
    // Has the target id changed? If not, exit.
    if (targetId === this.editingTarget.id) {
        return;
    }
    var target = this.runtime.getTargetById(targetId);
    if (target) {
        this.editingTarget = target;
        // Emit appropriate UI updates.
        this.emitTargetsUpdate();
        this.emitWorkspaceUpdate();
        this.runtime.setEditingTarget(target);
    }
};

/**
 * Emit metadata about available targets.
 * An editor UI could use this to display a list of targets and show
 * the currently editing one.
 */
VirtualMachine.prototype.emitTargetsUpdate = function () {
    this.emit('targetsUpdate', {
        // [[target id, human readable target name], ...].
        targetList: this.runtime.targets.filter(function (target) {
            // Don't report clones.
            return !target.hasOwnProperty('isOriginal') || target.isOriginal;
        }).map(function (target) {
            return [target.id, target.getName()];
        }),
        // Currently editing target id.
        editingTarget: this.editingTarget ? this.editingTarget.id : null
    });
};

/**
 * Emit an Blockly/scratch-blocks compatible XML representation
 * of the current editing target's blocks.
 */
VirtualMachine.prototype.emitWorkspaceUpdate = function () {
    this.emit('workspaceUpdate', {
        xml: this.editingTarget.blocks.toXML()
    });
};

/**
 * Post/edit sprite info for the current editing target.
 * @param {object} data An object with sprite info data to set.
 */
VirtualMachine.prototype.postSpriteInfo = function (data) {
    this.editingTarget.postSpriteInfo(data);
};

module.exports = VirtualMachine;
<|endoftext|>