<commit_before>// Post requests

var json_parser = function () {
	this.parsed = [];
	this.time_stamp = {
		start_time: Date.now(),
		end_time: 0
	}
	var frame_init = 0;
	var index = 0;
	this.post_partial = async function (exp_id, game, data) {
		var frame_last = game.time;
		var partial = JSON.stringify(game.gameStates.slice(frame_init, frame_last));
		frame_init = frame_last;

		var steps = game.steps
		var last_state = game.gameStates.length;
		var win = game.gameStates[last_state-1].win;
		var score = game.gameStates[last_state-1].score;
		$.ajax({
			type: 'PUT',
			url: "/experiment/"+exp_id,
			data: {timeStamp: JSON.stringify(this.time_stamp),
			 	   gameStates: partial,
			 	   // score: score,
			 	   // win: win,
			 	   // steps: steps,
			 	   data: data},
			success: function (status) {
				if (!status.success) {
					console.log('could not put experiment');
				}
			},
		})
	}
}

var next_experiment = function (exp_id, game, parser, data, callback) {
	$('body').addClass('loading')
	if (exp_id == '0') {
		callback();
		return;
	}
	$.ajax({
		type: 'POST',
		url: '/experiment/'+exp_id+'/next',
		data: data,
		success: function (status) {
			if (status.success) {
				callback();
			}
		}
	})
	// post_experiment(exp_id, game, parser, data, 'next', callback);
}

var retry_experiment = function (exp_id, game, parser, data, callback) {
	$('body').addClass('loading')
	if (exp_id == '0') {
		callback();
		return;
	}
	$.ajax({
		type: 'POST',
		url: '/experiment/'+exp_id+'/retry',
		success: function (status) {
			if (status.success) {
				callback();
			}
		}
	})
	// post_experiment(exp_id, game, parser, data, 'retry', callback);
}

/**
 * @fileoverview
 * Draw lines, polygons, circles, etc on the screen.
 * Render text in a certain font to the screen.
 */

var gamejs = require('gamejs');;
var vgdl_parser = VGDLParser(gamejs);
var game = vgdl_parser.playGame(vgdl_game.game, vgdl_game.level, color_scheme);
// var on_game_end = function () {
// }
// game.paused = false;
// gamejs.ready(game.run(on_game_end));
// console.log('game started');

var interval;
var parser;

var retry_game = function () {
	window.location.reload();
}

var continue_game = function () {
	next_experiment(exp_id, game, parser, data, function () {
		window.location.href = `/experiment/${exp_id}`
	});
}

var page_refresh = function () {
	game.paused = true;
	if (game.win === null) {
		retry_experiment(exp_id, game, parser, data, function () {
			console.log('game refreshed');
		})
	}
}

$(document).on('click', '#forfeit', function () {
	next_experiment(exp_id, game, parser, data, function () {
		location.reload();
	})
})

$(document).on('click', '#continue', continue_game);

$(document).on('click', '#return', function () {
	window.location.href = '/admin';
})
$(document).on('click', '#retry', retry_game);

$(document).on('click', '#pause', function () {
	game.paused = !game.paused;
})
$(window).bind('beforeunload', page_refresh);

$(document).ready(function () {

	var cont_button = $('<button id="continue">Continue</button>');
	var return_button = $('<button id="return">Return</button>');

	var forfeit_div = $('

')
	var forfeit_text = $('
If you want to forfeit the entire level and try a new one, press "Forfeit".
');
	var forfeit_button = $('<button id="forfeit">Forfeit</button>');

	var retry_container = $('

');
	var retry_text = $('
If you get stuck, you can press "Retry" to reset this level.
')
	var retry_button = $('<button id="retry">Retry</button>')
	retry_container.append(retry_text)
	retry_container.append(retry_button)

	forfeit_div.append(forfeit_text);
	forfeit_div.append(forfeit_button);

	var end_game_delay = 1000;
	var retry_delay = 30000;
	var forfeit_delay = 60000*2;
	var ended = false;

	var on_game_end = function () {
		parser.post_partial(exp_id, game, data);
		clearInterval(interval);
		game.paused = true;
		ended = true
		$('#retry-div').remove()
		var show_status = function () {
			var status_text = '';
			if (game.win) {
				var container = $('

');
				if (exp_id != '0') {
					var button = cont_button;
				} else {
					var button = return_button;
				}
			}
			else {
				var container = retry_container;
				var button = retry_button;
			}

			container.append(button);
			$('body').append(container)
			$('#forfeit-div').remove();
			$('#retry-text').remove();
		}

		if (game.win) {
			$('#title').text('Game Won!')
			show_status()

		} else {
			$('#title').text('Game Lost!')
			window.setTimeout(show_status, end_game_delay);
		}
	}

	var begin_game = function () {
		parser = new json_parser();
		interval = window.setInterval(function(){
		  parser.post_partial(exp_id, game, data)
		}, 1000);

		$('#start-div').remove();
		game.paused = false;
		window.setTimeout(function () {
			if (!ended)
				$('body').append(retry_container)
		}, retry_delay);
		window.setTimeout(function () {
			if (!ended)
				$('#retry-div').append(forfeit_div);
		}, forfeit_delay);
	}

	$('#gjs-canvas').focus();
	$('#start').click(begin_game)

	// start_modal.style.display = 'none';
	// game.paused = true;
	gamejs.ready(game.run(on_game_end));

	begin_game();

});

// // gamejs.ready will call your main function
// // once all components and resources are ready.
// gamejs.ready(main);
<commit_msg>everything's working now lol<commit_after>// Post requests

var json_parser = function () {
	this.parsed = [];
	this.time_stamp = {
		start_time: Date.now(),
		end_time: 0
	}
	var frame_init = 0;
	var index = 0;
	this.post_partial = async function (exp_id, game, data) {
		var frame_last = game.time;
		var partial = JSON.stringify(game.gameStates.slice(frame_init, frame_last));
		frame_init = frame_last;

		var steps = game.steps
		var last_state = game.gameStates.length;
		var win = game.gameStates[last_state-1].win;
		var score = game.gameStates[last_state-1].score;
		$.ajax({
			type: 'PUT',
			url: "/experiment/"+exp_id,
			data: {timeStamp: JSON.stringify(this.time_stamp),
			 	   gameStates: partial,
			 	   // score: score,
			 	   // win: win,
			 	   // steps: steps,
			 	   data: data},
			success: function (status) {
				if (!status.success) {
					console.log('could not put experiment');
				}
			},
		})
	}
}

var next_experiment = function (exp_id, game, parser, data, callback) {
	$('body').addClass('loading')
	if (exp_id == '0') {
		callback();
		return;
	}
	$.ajax({
		type: 'POST',
		url: '/experiment/'+exp_id+'/next',
		data: data,
		success: function (status) {
			if (status.success) {
				callback();
			}
		}
	})
	// post_experiment(exp_id, game, parser, data, 'next', callback);
}

var retry_experiment = function (exp_id, game, parser, data, callback) {
	$('body').addClass('loading')
	if (exp_id == '0') {
		callback();
		return;
	}
	$.ajax({
		type: 'POST',
		url: '/experiment/'+exp_id+'/retry',
		success: function (status) {
			if (status.success) {
				callback();
			}
		}
	})
	// post_experiment(exp_id, game, parser, data, 'retry', callback);
}

/**
 * @fileoverview
 * Draw lines, polygons, circles, etc on the screen.
 * Render text in a certain font to the screen.
 */

var gamejs = require('gamejs');;
var vgdl_parser = VGDLParser(gamejs);
var game = vgdl_parser.playGame(vgdl_game.game, vgdl_game.level, color_scheme);
// var on_game_end = function () {
// }
// game.paused = false;
// gamejs.ready(game.run(on_game_end));
// console.log('game started');

var interval;
var parser;

var retry_game = function () {
	window.location.reload();
}

var continue_game = function () {
	next_experiment(exp_id, game, parser, data, function () {
		window.location.href = `/experiment/${exp_id}`
	});
}

var page_refresh = function () {
	game.paused = true;
	if (game.win === null) {
		retry_experiment(exp_id, game, parser, data, function () {
			console.log('game refreshed');
		})
	}
}

$(document).on('click', '#forfeit', function () {
	next_experiment(exp_id, game, parser, data, function () {
		location.reload();
	})
})

$(document).on('click', '#continue', continue_game);

$(document).on('click', '#return', function () {
	window.location.href = '/admin';
})
$(document).on('click', '#retry', retry_game);

$(document).on('click', '#pause', function () {
	game.paused = !game.paused;
})
$(window).bind('beforeunload', page_refresh);

$(document).ready(function () {

	var cont_button = $('<button id="continue">Continue</button>');
	var return_button = $('<button id="return">Return</button>');

	var forfeit_div = $('

')
	var forfeit_text = $('
If you want to forfeit the entire level and try a new one, press "Forfeit".
');
	var forfeit_button = $('<button id="forfeit">Forfeit</button>');

	var retry_container = $('

');
	var retry_text = $('
If you get stuck, you can press "Retry" to reset this level.
')
	var retry_button = $('<button id="retry">Retry</button>')
	retry_container.append(retry_text)
	retry_container.append(retry_button)

	forfeit_div.append(forfeit_text);
	forfeit_div.append(forfeit_button);

	var end_game_delay = 1000;
	var retry_delay = 40000;
	// var forfeit_delay = 60000*2;
	var ended = false;

	var on_game_end = function () {
		parser.post_partial(exp_id, game, data);
		clearInterval(interval);
		game.paused = true;
		ended = true
		$('#retry-div').remove()
		var show_status = function () {
			var status_text = '';
			if (game.win) {
				var container = $('

');
				if (exp_id != '0') {
					var button = cont_button;
				} else {
					var button = return_button;
				}
			}
			else {
				var container = retry_container;
				var button = retry_button;
			}

			container.append(button);
			$('body').append(container)
			$('#forfeit-div').remove();
			$('#retry-text').remove();
		}

		if (game.win) {
			$('#title').text('Game Won!')
			show_status()

		} else {
			$('#title').text('Game Lost!')
			window.setTimeout(show_status, end_game_delay);
		}
	}

	var begin_game = function () {
		parser = new json_parser();
		interval = window.setInterval(function(){
		  parser.post_partial(exp_id, game, data)
		}, 1000);

		$('#start-div').remove();
		game.paused = false;
		window.setTimeout(function () {
			if (!ended)
				$('body').append(retry_container)
		}, retry_delay);
		console.log(data.round);
		if (data.round >= 3) {
			$('body').append(forfeit_div);
		}
		// window.setTimeout(function () {
		// 	if (!ended)
		// 		$('#retry-div').append(forfeit_div);
		// }, forfeit_delay);
	}

	$('#gjs-canvas').focus();
	$('#start').click(begin_game)

	// start_modal.style.display = 'none';
	// game.paused = true;
	gamejs.ready(game.run(on_game_end));

	begin_game();

});

// // gamejs.ready will call your main function
// // once all components and resources are ready.
// gamejs.ready(main);
<|endoftext|>


<commit_before>var _ = require('lodash');
var parallelStream = require('pelias-parallel-stream');
var peliasConfig = require( 'pelias-config' ).generate();
var countries = require('../data/countries');
var regions = require('../data/regions');
var peliasLogger = require( 'pelias-logger' );
var getAdminLayers = require( './getAdminLayers' );

var logger = peliasLogger.get( 'wof-admin-lookup', {
  transports: [
    new peliasLogger.winston.transports.File( {
      filename: 'suspect_wof_records.log',
      timestamp: false
    })
  ]
});

countries.isSupported = function(country) {
  return this.hasOwnProperty(country);
};

countries.getCode = function(countries) {
  if (_.isEmpty(countries)) {
    return undefined;
  }

  if (this.isSupported(countries[0].name)) {
    return this[countries[0].name];
  }

  return undefined;

};

regions.isSupported = function(country, name) {
  return this.hasOwnProperty(country) && this[country].hasOwnProperty(name);
};

regions.getCode = function(countries, regions) {
  if (_.isEmpty(countries) || _.isEmpty(regions)) {
    return undefined;
  }

  var country = countries[0].name;
  var region = regions[0].name;

  if (this.isSupported(country, region)) {
    return this[country][region];
  }

  return undefined;

};

function setFields(values, doc, wofFieldName, abbreviation) {
  try {
    if (!_.isEmpty(values)) {
      doc.addParent(wofFieldName, values[0].name, values[0].id.toString(), abbreviation);
    }
  }
  catch (err) {
    logger.info('invalid value', {
      centroid: doc.getCentroid(),
      result: {
        type: wofFieldName,
        values: values,
        abbreviation: abbreviation
      }
    });
  }
}

function hasCountry(result) {
  return _.isEmpty(result.country);
}

function hasAnyMultiples(result) {
  return Object.keys(result).some(function(element) {
    return result[element].length > 1;
  });
}

function createLookupStream(resolver, config) {

  if (!resolver) {
    throw new Error('createLookupStream requires a valid resolver to be passed in as the first parameter');
  }

  config = config || peliasConfig;

  var maxConcurrentReqs = 1;
  if (config.imports.adminLookup && config.imports.adminLookup.maxConcurrentReqs) {
    maxConcurrentReqs = config.imports.adminLookup.maxConcurrentReqs;
  }

  var stream = parallelStream(maxConcurrentReqs, function (doc, enc, callback) {

    // don't do anything if there's no centroid
    if (_.isEmpty(doc.getCentroid())) {
      return callback(null, doc);
    }

    resolver.lookup(doc.getCentroid(), function (err, result) {

      // assume errors at this point are fatal, so pass them upstream to kill stream
      if (err) {
        logger.error(err);
        return callback(new Error('PIP server failed: ' + (err.message || JSON.stringify(err))));
      }

      // log results w/o country OR any multiples
      if (hasCountry(result)) {
        logger.info('no country', {
          centroid: doc.getCentroid(),
          result: result
        });
      }
      if (hasAnyMultiples(result)) {
        logger.info('multiple values', {
          centroid: doc.getCentroid(),
          result: result
        });
      }

      var regionCode = regions.getCode(result.country, result.region);
      var countryCode = countries.getCode(result.country);

      // set code if available
      if (!_.isUndefined(countryCode)) {
        doc.setAlpha3(countryCode);
      }

      setFields(result.country, doc, 'country', countryCode);
      setFields(result.macroregion, doc, 'macroregion');
      if (!_.isEmpty(result.region)) { // if there are regions, use them
        setFields(result.region, doc, 'region', regionCode);
      } else { // go with dependency for region (eg - Puerto Rico is a dependency)
        setFields(result.dependency, doc, 'region');
      }
      setFields(result.macrocounty, doc, 'macrocounty');
      setFields(result.county, doc, 'county');
      setFields(result.locality, doc, 'locality');
      setFields(result.localadmin, doc, 'localadmin');
      setFields(result.borough, doc, 'borough');
      setFields(result.neighbourhood, doc, 'neighbourhood');

      callback(null, doc);
    }, getAdminLayers(doc.getLayer()));
  },
  function end() {
    if (typeof resolver.end === 'function') {
      resolver.end();
    }
  });

  return stream;
}

module.exports = {
  createLookupStream: createLookupStream
};
<commit_msg>feat: Merge pull request #63 from pelias/suspect-records-printing<commit_after>var _ = require('lodash');
var parallelStream = require('pelias-parallel-stream');
var peliasConfig = require( 'pelias-config' ).generate();
var countries = require('../data/countries');
var regions = require('../data/regions');
var peliasLogger = require( 'pelias-logger' );
var getAdminLayers = require( './getAdminLayers' );

//defaults to nowhere
var optsArg = {
  transports: []
};
//only prints to suspect records log if flag is set
if (peliasConfig.logger.suspectFile === true){
  optsArg.transports.push(new peliasLogger.winston.transports.File( {
    filename: 'suspect_wof_records.log',
    timestamp: false
  }));
}

var logger = peliasLogger.get( 'wof-admin-lookup', optsArg );

countries.isSupported = function(country) {
  return this.hasOwnProperty(country);
};

countries.getCode = function(countries) {
  if (_.isEmpty(countries)) {
    return undefined;
  }

  if (this.isSupported(countries[0].name)) {
    return this[countries[0].name];
  }

  return undefined;

};

regions.isSupported = function(country, name) {
  return this.hasOwnProperty(country) && this[country].hasOwnProperty(name);
};

regions.getCode = function(countries, regions) {
  if (_.isEmpty(countries) || _.isEmpty(regions)) {
    return undefined;
  }

  var country = countries[0].name;
  var region = regions[0].name;

  if (this.isSupported(country, region)) {
    return this[country][region];
  }

  return undefined;

};

function setFields(values, doc, wofFieldName, abbreviation) {
  try {
    if (!_.isEmpty(values)) {
      doc.addParent(wofFieldName, values[0].name, values[0].id.toString(), abbreviation);
    }
  }
  catch (err) {
    logger.info('invalid value', {
      centroid: doc.getCentroid(),
      result: {
        type: wofFieldName,
        values: values,
        abbreviation: abbreviation
      }
    });
  }
}

function hasCountry(result) {
  return _.isEmpty(result.country);
}

function hasAnyMultiples(result) {
  return Object.keys(result).some(function(element) {
    return result[element].length > 1;
  });
}

function createLookupStream(resolver, config) {

  if (!resolver) {
    throw new Error('createLookupStream requires a valid resolver to be passed in as the first parameter');
  }

  config = config || peliasConfig;

  var maxConcurrentReqs = 1;
  if (config.imports.adminLookup && config.imports.adminLookup.maxConcurrentReqs) {
    maxConcurrentReqs = config.imports.adminLookup.maxConcurrentReqs;
  }

  var stream = parallelStream(maxConcurrentReqs, function (doc, enc, callback) {

    // don't do anything if there's no centroid
    if (_.isEmpty(doc.getCentroid())) {
      return callback(null, doc);
    }

    resolver.lookup(doc.getCentroid(), function (err, result) {

      // assume errors at this point are fatal, so pass them upstream to kill stream
      if (err) {
        logger.error(err);
        return callback(new Error('PIP server failed: ' + (err.message || JSON.stringify(err))));
      }

      // log results w/o country OR any multiples
      if (hasCountry(result)) {
        logger.info('no country', {
          centroid: doc.getCentroid(),
          result: result
        });
      }
      if (hasAnyMultiples(result)) {
        logger.info('multiple values', {
          centroid: doc.getCentroid(),
          result: result
        });
      }

      var regionCode = regions.getCode(result.country, result.region);
      var countryCode = countries.getCode(result.country);

      // set code if available
      if (!_.isUndefined(countryCode)) {
        doc.setAlpha3(countryCode);
      }

      setFields(result.country, doc, 'country', countryCode);
      setFields(result.macroregion, doc, 'macroregion');
      if (!_.isEmpty(result.region)) { // if there are regions, use them
        setFields(result.region, doc, 'region', regionCode);
      } else { // go with dependency for region (eg - Puerto Rico is a dependency)
        setFields(result.dependency, doc, 'region');
      }
      setFields(result.macrocounty, doc, 'macrocounty');
      setFields(result.county, doc, 'county');
      setFields(result.locality, doc, 'locality');
      setFields(result.localadmin, doc, 'localadmin');
      setFields(result.borough, doc, 'borough');
      setFields(result.neighbourhood, doc, 'neighbourhood');

      callback(null, doc);
    }, getAdminLayers(doc.getLayer()));
  },
  function end() {
    if (typeof resolver.end === 'function') {
      resolver.end();
    }
  });

  return stream;
}

module.exports = {
  createLookupStream: createLookupStream
};
<|endoftext|>


<commit_before>// codplayer web control widget server
//
// Copyright 2013 Peter Liljenberg <peter.liljenberg@gmail.com>
//
// Distributed under an MIT license, please see LICENSE in the top dir.

var fs = require('fs');
var jf = require('jsonfile');
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io').listen(server, { 'log level': 1 });

//
// Command line parsing
//

var configPath;
var config;

if (process.argv.length != 3) {
    console.error('Usage: ' + process.argv[0] + ' ' + process.argv[1]
		  + ' config.json');
    process.exit(1);
}

configPath = process.argv[2];
try {
    config = jf.readFileSync(configPath);
}
catch (err) {
    console.error('error reading ' + configPath + ': ' + err);
    process.exit(1);
}

var checkConfigParam = function(param, type) {
    if (!config[param]) {
	console.error('missing config parameter: ' + param);
	process.exit(1);
    }

    if (typeof config[param] !== type) {
	console.error('expected ' + type + ' config parameter: ' + param);
	process.exit(1);
    }
};

checkConfigParam('serverPort', 'number');
checkConfigParam('stateFile', 'string');
checkConfigParam('pidFile', 'string');
checkConfigParam('controlFifo', 'string');

//
// State updating
//

var numClients = 0;

var sendStateOnce = function() {
    jf.readFile(config.stateFile, function(err, state) {
	if (state) {
	    io.sockets.emit('cod-state', state);
	}
    });
};

var sendState = function() {
    if (numClients < 1) {
	console.log('stopping sending state updates');
	return;
    };

    jf.readFile(config.stateFile, function(err, state) {
	var timeout = state.state === 'NO_DISC' ? 5000 : 1000;

	if (state) {
	    io.sockets.emit('cod-state', state);
	    setTimeout(sendState, timeout);
	} else {
	    // we might be reading while the state file is being
	    // swapped. In that case, sleep a tad longer than a second
	    // to try to avoid running synchronised with codplayerd
	    console.log('error reading state file: ' + err);
	    setTimeout(sendState, 1100);
	}
    });

};

//
// Web server setup
//

app.use(express.static(__dirname + '/static'));

app.get('/', function(req, res) {
    res.sendfile(__dirname + '/static/codctl.html');
});

server.listen(config.serverPort, function() {
  console.log('Server listening on port ' + config.serverPort);
});

//
// Socket callbacks
//

io.sockets.on('connection', function (socket) {
    numClients++;
    console.log('connection, now ' + numClients + ' clients');
    if (numClients == 1) {
	// Kick off sending updates
	sendState();
    }

    socket.on('disconnect', function () {
	numClients--;
	console.log('disconnect, now ' + numClients + ' clients');
    });

    socket.on('cod-command', function (data) {
	var command = data.command;
	var buffer;

	console.log('got command: ' + command);

	buffer = new Buffer(command + '\n');

	// Open r+ to fail if there is no FIFO
	fs.open(config.controlFifo, 'r+', function(err, fd) {
	    if (err) {
		console.error('error opening ' + config.controlFifo + ': ' + err);
	    }
	    else {
		fs.write(fd, buffer, 0, buffer.length, null, function(err, written, buffer) {
		    if (err) {
			console.error('error writing to ' + config.controlFifo + ': ' + err);
		    }
		    else {
			// Get an early state update to the clients
			setTimeout(sendStateOnce(), 100);
		    }

		    fs.close(fd, function(err) { });
		});
	    }
	});
    });
});
<commit_msg>Fixes #1: sync updates to state file changes<commit_after>// codplayer web control widget server
//
// Copyright 2013 Peter Liljenberg <peter.liljenberg@gmail.com>
//
// Distributed under an MIT license, please see LICENSE in the top dir.

var fs = require('fs');
var jf = require('jsonfile');
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io').listen(server, { 'log level': 1 });

//
// Command line parsing
//

var configPath;
var config;

if (process.argv.length != 3) {
    console.error('Usage: ' + process.argv[0] + ' ' + process.argv[1]
		  + ' config.json');
    process.exit(1);
}

configPath = process.argv[2];
try {
    config = jf.readFileSync(configPath);
}
catch (err) {
    console.error('error reading ' + configPath + ': ' + err);
    process.exit(1);
}

var checkConfigParam = function(param, type) {
    if (!config[param]) {
	console.error('missing config parameter: ' + param);
	process.exit(1);
    }

    if (typeof config[param] !== type) {
	console.error('expected ' + type + ' config parameter: ' + param);
	process.exit(1);
    }
};

checkConfigParam('serverPort', 'number');
checkConfigParam('stateFile', 'string');
checkConfigParam('pidFile', 'string');
checkConfigParam('controlFifo', 'string');

//
// State updating
//

var numClients = 0;
var currentState;
var lastStateTime = 0;
var stateDelayed = false;

var delayTimeout = function(short, long) {
    var timeout = stateDelayed ? long : short;
    stateDelayed = true;

    return timeout;
};

var sendStateOnce = function() {
    jf.readFile(config.stateFile, function(err, state) {
	if (state) {
	    io.sockets.emit('cod-state', state);
	}
    });
};

var sendState = function() {
    if (numClients < 1) {
	console.log('stopping sending state updates');
	return;
    };

    fs.stat(config.stateFile, function(err, stats) {
	var timeout = 1007;

	if (err) {
	    timeout = delayTimeout(97, 5007);
	}
	else {
	    if (lastStateTime != stats.mtime.getTime()) {
		// Read updated file
		jf.readFile(config.stateFile, function(err, state) {
		    if (state) {
			io.sockets.emit('cod-state', state);

			lastStateTime = stats.mtime.getTime();
			currentState = state;
			stateDelayed = false;

			// Sleep until a bit after the expected next update of the file
			timeout = lastStateTime + 1097 - Date.now();
			if (timeout < 10 || timeout > 1000) {
			    // In case clocks are out of sync...
			    timeout = 1007;
			}
		    } else {
			timeout = delayTimeout(97, 5007);
		    }
		});
	    }
	    else {
		// File hasn't changed
		timeout = delayTimeout(97, 1007);
	    }
	}

	setTimeout(sendState, timeout);
    });
};

//
// Web server setup
//

app.use(express.static(__dirname + '/static'));

app.get('/', function(req, res) {
    res.sendfile(__dirname + '/static/codctl.html');
});

server.listen(config.serverPort, function() {
  console.log('Server listening on port ' + config.serverPort);
});

//
// Socket callbacks
//

io.sockets.on('connection', function (socket) {
    // Tell new client immediately about current state, if any
    if (currentState) {
	socket.emit('cod-state', currentState);
    }

    numClients++;
    console.log('connection, now ' + numClients + ' clients');
    if (numClients == 1) {
	// Kick off sending updates
	sendState();
    }

    socket.on('disconnect', function () {
	numClients--;
	console.log('disconnect, now ' + numClients + ' clients');
    });

    socket.on('cod-command', function (data) {
	var command = data.command;
	var buffer;

	console.log('got command: ' + command);

	buffer = new Buffer(command + '\n');

	// Open r+ to fail if there is no FIFO
	fs.open(config.controlFifo, 'r+', function(err, fd) {
	    if (err) {
		console.error('error opening ' + config.controlFifo + ': ' + err);
	    }
	    else {
		fs.write(fd, buffer, 0, buffer.length, null, function(err, written, buffer) {
		    if (err) {
			console.error('error writing to ' + config.controlFifo + ': ' + err);
		    }
		    else {
			// Get an early state update to the clients
			setTimeout(sendStateOnce(), 100);
		    }

		    fs.close(fd, function(err) { });
		});
	    }
	});
    });
});
<|endoftext|>


<commit_before>/* @flow */
import template from './template';
import type { FlagsState, EventReaction, RealmEmojiState } from '../../types';
import { shortTime } from '../../utils/date';
import messageTagsAsHtml from './messageTagsAsHtml';
import messageReactionListAsHtml from './messageReactionListAsHtml';

const messageDiv = (id: number, msgClass: string, flags: Object): string =>
  template`
 template`data-${flag}="true" `).join('')}
    >`;

const messageSubheader = ({
  fromName,
  timestamp,
  twentyFourHourTime,
}: {
  fromName: string,
  timestamp: number,
  twentyFourHourTime: boolean,
}) => template`

    ${fromName}

    ${shortTime(new Date(timestamp * 1000), twentyFourHourTime)}

`;

type BriefMessageProps = {
  content: string,
  flags: FlagsState,
  id: number,
  isOutbox: boolean,
  ownEmail: string,
  reactions: EventReaction[],
  realmEmoji: RealmEmojiState,
  timeEdited: Date,
};

type FullMessageProps = BriefMessageProps & {
  fromName: string,
  fromEmail: string,
  timestamp: number,
  avatarUrl: string,
  twentyFourHourTime: boolean,
};

type Props = FullMessageProps & {
  isBrief: boolean,
};

const messageBody = ({
  content,
  flags,
  id,
  isOutbox,
  ownEmail,
  reactions,
  realmEmoji,
  timeEdited,
}: {
  content: string,
  flags: Object,
  id: number,
  isOutbox: boolean,
  ownEmail: string,
  reactions: EventReaction[],
  realmEmoji: RealmEmojiState,
  timeEdited: Date,
}) => template`
$!${content}
$!${isOutbox ? '

' : ''}
$!${messageTagsAsHtml(flags, timeEdited)}
$!${messageReactionListAsHtml(reactions, id, ownEmail, realmEmoji)}
`;

const briefMessageAsHtml = ({
  content,
  flags,
  id,
  isOutbox,
  ownEmail,
  reactions,
  realmEmoji,
  timeEdited,
}: BriefMessageProps) => template`
$!${messageDiv(id, 'message-brief', flags)}

    $!${messageBody({ content, flags, id, isOutbox, ownEmail, reactions, realmEmoji, timeEdited })}

`;

const fullMessageAsHtml = ({
  id,
  content,
  flags,
  fromName,
  fromEmail,
  timestamp,
  avatarUrl,
  twentyFourHourTime,
  timeEdited,
  isOutbox,
  reactions,
  ownEmail,
  realmEmoji,
}: FullMessageProps) => template`
$!${messageDiv(id, 'message-full', flags)}

    $!${messageSubheader({ fromName, timestamp, twentyFourHourTime })}
    $!${messageBody({ content, flags, id, isOutbox, ownEmail, reactions, realmEmoji, timeEdited })}

`;

export default (props: Props) =>
  props.isBrief ? briefMessageAsHtml(props) : fullMessageAsHtml(props);
<commit_msg>a11y: Make avatar in webview voice-readable<commit_after>/* @flow */
import template from './template';
import type { FlagsState, EventReaction, RealmEmojiState } from '../../types';
import { shortTime } from '../../utils/date';
import messageTagsAsHtml from './messageTagsAsHtml';
import messageReactionListAsHtml from './messageReactionListAsHtml';

const messageDiv = (id: number, msgClass: string, flags: Object): string =>
  template`
 template`data-${flag}="true" `).join('')}
    >`;

const messageSubheader = ({
  fromName,
  timestamp,
  twentyFourHourTime,
}: {
  fromName: string,
  timestamp: number,
  twentyFourHourTime: boolean,
}) => template`

    ${fromName}

    ${shortTime(new Date(timestamp * 1000), twentyFourHourTime)}

`;

type BriefMessageProps = {
  content: string,
  flags: FlagsState,
  id: number,
  isOutbox: boolean,
  ownEmail: string,
  reactions: EventReaction[],
  realmEmoji: RealmEmojiState,
  timeEdited: Date,
};

type FullMessageProps = BriefMessageProps & {
  fromName: string,
  fromEmail: string,
  timestamp: number,
  avatarUrl: string,
  twentyFourHourTime: boolean,
};

type Props = FullMessageProps & {
  isBrief: boolean,
};

const messageBody = ({
  content,
  flags,
  id,
  isOutbox,
  ownEmail,
  reactions,
  realmEmoji,
  timeEdited,
}: {
  content: string,
  flags: Object,
  id: number,
  isOutbox: boolean,
  ownEmail: string,
  reactions: EventReaction[],
  realmEmoji: RealmEmojiState,
  timeEdited: Date,
}) => template`
$!${content}
$!${isOutbox ? '

' : ''}
$!${messageTagsAsHtml(flags, timeEdited)}
$!${messageReactionListAsHtml(reactions, id, ownEmail, realmEmoji)}
`;

const briefMessageAsHtml = ({
  content,
  flags,
  id,
  isOutbox,
  ownEmail,
  reactions,
  realmEmoji,
  timeEdited,
}: BriefMessageProps) => template`
$!${messageDiv(id, 'message-brief', flags)}

    $!${messageBody({ content, flags, id, isOutbox, ownEmail, reactions, realmEmoji, timeEdited })}

`;

const fullMessageAsHtml = ({
  id,
  content,
  flags,
  fromName,
  fromEmail,
  timestamp,
  avatarUrl,
  twentyFourHourTime,
  timeEdited,
  isOutbox,
  reactions,
  ownEmail,
  realmEmoji,
}: FullMessageProps) => template`
$!${messageDiv(id, 'message-full', flags)}

    $!${messageSubheader({ fromName, timestamp, twentyFourHourTime })}
    $!${messageBody({ content, flags, id, isOutbox, ownEmail, reactions, realmEmoji, timeEdited })}

`;

export default (props: Props) =>
  props.isBrief ? briefMessageAsHtml(props) : fullMessageAsHtml(props);
<|endoftext|>


<commit_before>/** HTTP-based NTP implementation for Node.js and AJAX clients */
"use strict";

// Exports
var util = require('util');

/** Build event handler for debuging */
function debug_event_handler(name) {
	return function() {
		var args = Array.prototype.slice.call(arguments);
		var now = (new Date()).getTime();
		util.debug( '[' + now + '] ' + name + ' (' + args.map(function(a) { return util.inspect(a); }).join(', ') + ')' );
	};
}

var _id = 0;
var _cache = {};

/** */
function _cache_cleanup(id) {
	if(_cache[id] !== undefined) {
		delete _cache[id];
	}
}

/* */
function get_time() {
	return (new Date()).getTime();
}

/** HTTP request runner */
function http_runner(req, res) {
	var server_start_time = get_time();
	var cache, params, id;

	params = require('url').parse(req.url, true).query;

	if(req.url === '/time') {
		_id = Math.floor(Math.random()*100000000000000000);
		cache = {'id':_id, 'start': server_start_time, 'end':undefined};
		_cache[_id] = cache;

		req.on('close', _cache_cleanup.bind(undefined, _id) );

		req.on('end', function() {
			cache.status = true;
			cache.end = get_time();
		});

		setTimeout(_cache_cleanup.bind(undefined, _id), 1000*30);

		res.writeHead(200, {'Content-Type': 'application/json'});
		res.end(JSON.stringify(_id));
	} else if(req.url.substr(0, 6) === '/fetch') {
		id = parseInt(params.id, 10);
		if(Object.prototype.hasOwnProperty.call(_cache, id)) {
			res.writeHead(200, {'Content-Type': 'application/json'});

			res.end(JSON.stringify(_cache[id]));
			_cache_cleanup(id);
		} else {
			res.writeHead(404, {'Content-Type': 'application/json'});
			res.end(JSON.stringify("404 - Not Found"));
		}
	} else {
		res.writeHead(404, {'Content-Type': 'application/json'});
		res.end(JSON.stringify("404 - Not Found"));
	}
}

/** Create instance of NTP HTTP Server */
function Server(opts) {
	opts = opts || {};
	this._http = opts.http || require('http');
	this._server = this._http.createServer(http_runner);
	var port = opts.listen || opts.port;
	if(port) {
		this._server.listen(port);
	}
}

/** Start listening */
Server.prototype.listen = function(host, port) {
	if(port === undefined) {
		port = host;
		host = undefined;
	}

	if(host === undefined) {
		this._server.listen(port);
	} else {
		this._server.listen(host, port);
	}
	return this;
};

/** Creates HTTP-based NTP server */
Server.create = function(opts) {
	return new Server(opts);
};

// Exports
module.exports = Server;

/* EOF */
<commit_msg>Updated<commit_after>/** HTTP-based NTP implementation for Node.js and AJAX clients */
"use strict";

var util = require('util');

/** Build event handler for debuging */
function debug_event_handler(name) {
	return function() {
		var args = Array.prototype.slice.call(arguments);
		var now = (new Date()).getTime();
		util.debug( '[' + now + '] ' + name + ' (' + args.map(function(a) { return util.inspect(a); }).join(', ') + ')' );
	};
}

/* Get time in milliseconds */
function get_time() {
	return (new Date()).getTime();
}

/* Module builder */
function _module_creator(mod_opts) {
	mod_opts = mod_opts || {};
	var _http = mod_opts.http || require('http');

	var _id = 0;
	var _cache = {};

	/** */
	function _cache_cleanup(id) {
		if(_cache[id] !== undefined) {
			delete _cache[id];
		}
	}

	/** HTTP request runner */
	function http_runner(req, res) {
		var server_start_time = get_time();
		var cache, params, id;

		params = require('url').parse(req.url, true).query;

		if(req.url === '/time') {
			_id = Math.floor(Math.random()*100000000000000000);
			cache = {'id':_id, 'start': server_start_time, 'end':undefined};
			_cache[_id] = cache;

			req.on('close', _cache_cleanup.bind(undefined, _id) );

			req.on('end', function() {
				cache.status = true;
				cache.end = get_time();
			});

			setTimeout(_cache_cleanup.bind(undefined, _id), 1000*30);

			res.writeHead(200, {'Content-Type': 'application/json'});
			res.end(JSON.stringify(_id));
		} else if(req.url.substr(0, 6) === '/fetch') {
			id = parseInt(params.id, 10);
			if(Object.prototype.hasOwnProperty.call(_cache, id)) {
				res.writeHead(200, {'Content-Type': 'application/json'});

				res.end(JSON.stringify(_cache[id]));
				_cache_cleanup(id);
			} else {
				res.writeHead(404, {'Content-Type': 'application/json'});
				res.end(JSON.stringify("404 - Not Found"));
			}
		} else {
			res.writeHead(404, {'Content-Type': 'application/json'});
			res.end(JSON.stringify("404 - Not Found"));
		}
	}

	/** Create instance of NTP HTTP Server */
	function Server(opts) {
		opts = opts || {};
		this._server = http.createServer(http_runner);
		var port = opts.listen || opts.port;
		if(port) {
			this._server.listen(port);
		}
	}

	/** Returns the internal value of HTTP server */
	Server.prototype.server = function() {
		return this._server;
	};

	/** Start listening */
	Server.prototype.listen = function(host, port) {
		if(port === undefined) {
			port = host;
			host = undefined;
		}
		if(host === undefined) {
			this._server.listen(port);
		} else {
			this._server.listen(host, port);
		}
		return this;
	};

	/** Creates HTTP-based NTP server */
	Server.create = function(opts) {
		return new Server(opts);
	};

	/* Alias for `Server.create` */
	Server.createServer = Server.create;

	return Server;
}

// Exports
module.exports = _module_creator;

/* EOF */
<|endoftext|>


<commit_before>import mongoose from 'mongoose';
import timestamps from 'mongoose-timestamp';
import moment from 'moment';
import js2xmlparser from 'js2xmlparser';
import shortid from 'shortid';

import SoundCloud from './SoundCloud.js';
import CONSTANTS from '../lib/constants.js';

var schema = new mongoose.Schema({
  _id: {
    type: String,
    unique: true,
    'default': shortid.generate
  },

  // supplied by user
  title: String,
  userId: Number,
  regexString: String,

  // retrieved from soundcloud api
  author: String,
  link: String,
  description: String,
  image: String,

  tracks: [{
    _id: { type: Number, unique: true },
    title: String,
    description: String,
    duration: String,
    url: String,
    size: Number,
    fileFormat: String,
    httpFormat: String,
    published: String
  }]
});
schema.plugin(timestamps);

schema.methods.regex = function() {
  return new RegExp(this.regexString, 'i');
};

schema.methods.needsUpdate = function() {
  if (this.tracks.length === 0) return true;
  var now = Date.now();
  var lastUpdated = new Date(this.updatedAt);
  return now - lastUpdated > CONSTANTS.CACHE_EXPIRATION;
};

schema.methods.update = function() {
  var sc = new SoundCloud(CONSTANTS.CLIENT_ID);
  return sc.getUser(this.userId)
    .then((user) => {
      this.author = user.username;
      this.link = user.permalink_url;
      this.description = user.description;
      this.image = user.avatar_url;
    })
    .then(() => sc.getTracks(this.userId))
    .then((tracks) => {
      this.tracks = tracks
        .filter(track => {
          return !!track.title.match(this.regex())
            && track.downloadable
            && track.state === 'finished';
        })
        .map((trackData) => {
          var track = {};
          if (this.tracks.id(track.id)) track = this.tracks.id(track.id);
          return {
            track: track,
            trackData: trackData
          };
        })
        .map(({track, trackData}) => {
          track._id = trackData.id;
          track.title = trackData.title;
          track.description = trackData.description;
          track.duration = formatDuration(trackData.duration);
          track.url = `${trackData.download_url}?client_id=${CONSTANTS.CLIENT_ID}`;
          track.size = trackData.original_content_size;
          track.fileFormat = trackData.original_format;
          track.httpFormat = httpFormat(track.fileFormat);
          track.published = moment(trackData.created_at, 'YYYY/MM/DD HH:mm:ss ZZ').utc().format('ddd, DD MMM YYYY HH:mm:ss ZZ');
          return track;
        });
      this.updatedAt = new Date;
      return this.save();
    });

  function formatDuration(duration) {
    var m = moment.duration(duration);
    return ['hours', 'minutes', 'seconds']
      .map(t => String(m[t]()))
      .map(t => (t.length === 1) ? '0'+t : t)
      .join(':');
  }

  function httpFormat(format) {
    var formats = {
      mp3: 'audio/mpeg'
    };
    if (!formats[format]) console.log('WARNING: no format found for', format);
    return formats[format];
  }
};

schema.methods.toXml = function() {
  var data = {
    '@': {
      'xmlns:itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd',
      version: '2.0'
    },
    channel: {
      title: this.title,
      'itunes:author': this.author,
      link: this.link,
      description: `This is a custom podcast featuring the music of ${this.author} created using Soundcasts: http://soundcasts.net.\nShort url: http://soundcasts.net/${this.id}`,
      'itunes:image': {
        '@': {
          href: this.image.replace(/^https/, 'http')
        }
      },
      item: this.tracks.map((track) => {
        return {
          title: track.title,
          'itunes:duration': track.duration,
          pubDate: track.published,
          'itunes:summary': track.description,
          description: track.description,
          guid: track.id,
          enclosure: {
            '@': {
              url: `http://soundcasts.net/${this.id}/tracks/${track.id}.${track.fileFormat}`,
              length: track.size,
              type: track.httpFormat
            }
          }
        };
      })
    }
  };
  return js2xmlparser('rss', data);
};

export default mongoose.model('Soundcast', schema);
<commit_msg>get larger artwork<commit_after>import mongoose from 'mongoose';
import timestamps from 'mongoose-timestamp';
import moment from 'moment';
import js2xmlparser from 'js2xmlparser';
import shortid from 'shortid';

import SoundCloud from './SoundCloud.js';
import CONSTANTS from '../lib/constants.js';

var schema = new mongoose.Schema({
  _id: {
    type: String,
    unique: true,
    'default': shortid.generate
  },

  // supplied by user
  title: String,
  userId: Number,
  regexString: String,

  // retrieved from soundcloud api
  author: String,
  link: String,
  description: String,
  image: String,

  tracks: [{
    _id: { type: Number, unique: true },
    title: String,
    description: String,
    duration: String,
    url: String,
    size: Number,
    fileFormat: String,
    httpFormat: String,
    published: String
  }]
});
schema.plugin(timestamps);

schema.methods.regex = function() {
  return new RegExp(this.regexString, 'i');
};

schema.methods.needsUpdate = function() {
  if (this.tracks.length === 0) return true;
  var now = Date.now();
  var lastUpdated = new Date(this.updatedAt);
  return now - lastUpdated > CONSTANTS.CACHE_EXPIRATION;
};

schema.methods.update = function() {
  var sc = new SoundCloud(CONSTANTS.CLIENT_ID);
  return sc.getUser(this.userId)
    .then((user) => {
      this.author = user.username;
      this.link = user.permalink_url;
      this.description = user.description;
      this.image = user.avatar_url.replace(/\-large/, 't200x200');
    })
    .then(() => sc.getTracks(this.userId))
    .then((tracks) => {
      this.tracks = tracks
        .filter(track => {
          return !!track.title.match(this.regex())
            && track.downloadable
            && track.state === 'finished';
        })
        .map((trackData) => {
          var track = {};
          if (this.tracks.id(track.id)) track = this.tracks.id(track.id);
          return {
            track: track,
            trackData: trackData
          };
        })
        .map(({track, trackData}) => {
          track._id = trackData.id;
          track.title = trackData.title;
          track.description = trackData.description;
          track.duration = formatDuration(trackData.duration);
          track.url = `${trackData.download_url}?client_id=${CONSTANTS.CLIENT_ID}`;
          track.size = trackData.original_content_size;
          track.fileFormat = trackData.original_format;
          track.httpFormat = httpFormat(track.fileFormat);
          track.published = moment(trackData.created_at, 'YYYY/MM/DD HH:mm:ss ZZ').utc().format('ddd, DD MMM YYYY HH:mm:ss ZZ');
          return track;
        });
      this.updatedAt = new Date;
      return this.save();
    });

  function formatDuration(duration) {
    var m = moment.duration(duration);
    return ['hours', 'minutes', 'seconds']
      .map(t => String(m[t]()))
      .map(t => (t.length === 1) ? '0'+t : t)
      .join(':');
  }

  function httpFormat(format) {
    var formats = {
      mp3: 'audio/mpeg'
    };
    if (!formats[format]) console.log('WARNING: no format found for', format);
    return formats[format];
  }
};

schema.methods.toXml = function() {
  var data = {
    '@': {
      'xmlns:itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd',
      version: '2.0'
    },
    channel: {
      title: this.title,
      'itunes:author': this.author,
      link: this.link,
      description: `This is a custom podcast featuring the music of ${this.author} created using Soundcasts: http://soundcasts.net.\nShort url: http://soundcasts.net/${this.id}`,
      'itunes:image': {
        '@': {
          href: this.image.replace(/^https/, 'http')
        }
      },
      item: this.tracks.map((track) => {
        return {
          title: track.title,
          'itunes:duration': track.duration,
          pubDate: track.published,
          'itunes:summary': track.description,
          description: track.description,
          guid: track.id,
          enclosure: {
            '@': {
              url: `http://soundcasts.net/${this.id}/tracks/${track.id}.${track.fileFormat}`,
              length: track.size,
              type: track.httpFormat
            }
          }
        };
      })
    }
  };
  return js2xmlparser('rss', data);
};

export default mongoose.model('Soundcast', schema);
<|endoftext|>


<commit_before>(function ($) {

  var LAYOUT = {
    list: 0,
    table: 1
  };

  $.ui.richAutocomplete = {};

  $.extend($.ui.richAutocomplete, {
    objectCache: function () {
      var cache = {};
      return {
        get: function (term) {
          return cache[term];
        },
        put: function (term, value) {
          cache[term] = value;
        }
      }
    }
  });

  $.widget('rf.richAutocomplete', {

    LAYOUT: LAYOUT,

    options: {
      token: "",
      showButton: false,
      autoFocus: false,
      autoFill: false,
      source: [],
      layout: LAYOUT.list,
      minLength: 0,

      /**
       * cached turns caching on
       */
      cached: false,
      /**
       * defines what prefix of searchTerm will be used to query cache
       */
      extractCacheSearchPrefix: defaultExtractCacheSearchPrefix,
      /**
       * pluggable cache implementation
       */
      cacheImplemenation: $.ui.richAutocomplete.objectCache,

      /**
       * Provide function which will be used to filter array of suggestions by given searchTerm
       * function filter(array, searchTerm)
       */
      filter: $.ui.autocomplete.filter,

      /**
       * Function called when search triggered but before suggestions are composed.
       *
       * It gives chance to update source of suggestions as reflection to current search term.
       *
       * function({ term: currentSearchTerm }, doneCallback)
       *
       * when doneCallback is specified, autocomplete will wait with update of suggestions before doneCallback
       * is called. Usually it is called on the end of AJAX data update.
       */
      update: null,

      // TODO should not be option, but private member
      suggestions: []
    },

    _create: function () {
      var widget = this;
      this.input = this.element;

      // initialize DOM structure
      this.root = this._initDom();

      if (this.options.cached) {
        this.cache = new this.options.cacheImplemenation();
      }

      if (!this.options.layout) {
        this._setOption('layout', this.LAYOUT.list);
      }

      var autocompleteOptions = this._getAutocompleteOptions();
      this.input.autocomplete(autocompleteOptions);

      this.input.keydown(function(event) {
        widget.lastKeyupEvent = event;
      });

      this._registerListeners();

      if (this.options.source) {
        this._setOption('source', this.options.source);
      }

      if (this.input.disabled) {
        this._setOption('disabled', true);
      }
    },

    _destroy: function () {
      this.input.autocomplete('destroy');
      this._destroyDom();
    },

    _enable: function () {
      this.input.autocomplete('enable');
      this.button.removeAttr('disabled');
    },

    _disable: function () {
      this.input.autocomplete('disable');
      this.button.attr('disabled', 'disabled');
    },

    _initDom: function () {
      this.root = $($('

').insertBefore(this.input)[0]);
      this.root.append(this.input.detach());

      if (this.options.showButton) {
        var widget = this;

        this.root.addClass("input-group");
        this.button = $('<button class="btn btn-light" type="button"></button>').appendTo(this.root).find("button");

        this.buttonClickHandler = function () {
          widget.input.autocomplete("search");
          widget.input.focus();
        };

        this.button.on('click', this.buttonClickHandler);
      }

      return this.root;
    },

    _destroyDom: function () {
      this.input.detach().insertAfter(this.root);
      this.root.remove();
    },

    _getAutocompleteOptions: function () {
      var widget = this;

      return {
        delay: 0,
        minLength: this.options.minLength,
        autoFocus: this.options.autoFocus,
        source: function (request, response) {
          widget._getSuggestions(request, response);
        },
        search: function () {
          this.opened = true;
          if (widget.options.autoFill) {
            if (widget.entered === widget.input.val()) {
              return false;
            }
          }
          widget.entered = widget.input.val();

          if (widget.button) {
            widget.button.off('click', widget.buttonClickHandler);
            widget.button.find('i').removeClass('icon-chevron-down');
            widget.button.find('i').addClass('icon-chevron-up');
          }
        },
        focus: function (event, ui) {
          if (!widget.options.autoFill) {
            return false;
          }
          if (widget.lastKeyupEvent.keyCode == 8) {
            // refuse to auto-fill on on backspace
            return false;
          }
          var input = widget.input,
            original = widget.entered;
            label = ui.item.label;

          original = original.substring(0, input[0].selectionStart);

          if (original.length > 0 && label.toLowerCase().indexOf(original.toLowerCase()) === 0) {
            input.val(original + label.substring(original.length));

            input[0].selectionStart = original.length;
            input[0].selectionEnd = label.length;
            return false;
          } else {
            return true;
          }
        },
        close: function (event) {
          if (widget.button) {
            widget.button.find('i').removeClass('icon-chevron-up');
            widget.button.find('i').addClass('icon-chevron-down');

            // we must delay attaching of event handler because otherwise the click will happen
            // and the handler will be triggered right after closing the menu
            setTimeout(function() {
              widget.button.on('click', widget.buttonClickHandler);
            }, 150);
          }
        },
        select: function (event, ui) {
          this.value = widget._selectValue(event, ui, this.value);
          return false;
        }
      };
    },

    _splitTokens: function (val) {
      var regexp = new RegExp("\\s*" + this.options.token + "\\s*");
      return val.split(regexp);
    },

    _extractLastToken: function (term) {
      return this._splitTokens(term).pop();
    },

    _extractSearchTerm: function (request) {
      if (this.options.token) {
        return this._extractLastToken(request.term);
      } else {
        return request.term;
      }
    },

    _selectValue: function (event, ui, value) {
      if (this.options.token) {
        var terms = this._splitTokens(value);
        // remove the current input
        terms.pop();
        // add the selected item
        terms.push(ui.item.value);
        return terms.join(this.options.token + " ");
      } else {
        return ui.item.value;
      }
    },

    _getSuggestions: function (request, response) {
      var searchTerm = this._extractSearchTerm(request);

      var req = $.extend({}, request, {
        term: searchTerm
      });

      if (this.cache) {
        req.term = this.options.extractCacheSearchPrefix.call(this, searchTerm);
      }

      var resp = $.proxy(function () {
        if (this.cache) {
          this.cache.put(searchTerm, arguments);
        }
        return response.apply(window, arguments);
      }, this);

      if (this.cache) {
        var result = this.cache.get(searchTerm);
        if (result) {
          response.apply(window, result);
          return;
        }
      }

      this._retrieveSuggestions(req, resp);
    },

    _retrieveSuggestions: function (request, response) {
      var source = this.options.source;

      if (isDomBasedSource(source)) {
        // DOM-based
        this._suggestFromDom(request, response);
      } else if ($.isFunction(source)) {
        // function-based
        source(request, response);
      } else {
        // array-based
        response(this.options.filter(source, request.term));
      }
    },

    _suggestFromDom: function(request, response) {
      var updateFn = this.options.update;

      var updateSuggestionsAndRespond = $.proxy(function () {
        this._updateDomSuggestions();
        response(this.options.filter(this.options.suggestions, request.term));
      }, this);

      if ($.isFunction(updateFn)) {
        // has the function second parameter? (which is 'done' callback)
        if (updateFn.length >= 2) {
          updateFn.call(window, request, updateSuggestionsAndRespond);
        } else {
          updateFn.call(window, request);
          updateSuggestionsAndRespond();
        }
      } else {
        response(this.options.filter(this.options.suggestions, request.term));
      }
    },

    _updateDomSuggestions: function () {
      var suggestions = [];
      var domSource = $(this.options.source);
      var layout = LAYOUT.list;

      if (domSource.is('table')) {
        layout = LAYOUT.table;
        domSource = domSource.children('tbody');
      }
      $(domSource).children('tr, li').each(function () {
        suggestions.push({
          value: $(this).data("label") || $(this).text(),
          dom: $(this).clone()
        })
      });

      if (this.option('layout') !== layout) {
        this._setOption('layout', layout);
      }

      this._setOption('suggestions', suggestions);
    },

    _preventTabbing: function () {
      this.element.bind("keydown", function (event) {
        if (event.keyCode === $.ui.keyCode.TAB &&
          $(this).data("autocomplete").menu.active) {
          event.preventDefault();
        }
      });
    },

    _registerListeners: function () {
      this.input.bind("autocompletesearch", this.options.onsearch);
      this.input.bind("autocompleteopen", this.options.onopen);
      this.input.bind("autocompletefocus", this.options.onfocus);
      this.input.bind("autocompleteselect", this.options.onselect);
      this.input.bind("autocompleteclose", this.options.onclose);
      this.input.bind("autocompletechange", this.options.onchange);
    },

    _setLayout: function (layout) {
      var data = this.input.autocomplete().data('ui-autocomplete');
      switch (layout) {
        case this.LAYOUT.list :
          data._renderMenu = $.ui.autocomplete.prototype._renderMenu;
          data._renderItem = function (ul, item) {
            var content = item.dom ? $("").html(item.dom.children()) : $("").text(item.label);
            return $("
").append(content).appendTo(ul);
          };
          break;
        case this.LAYOUT.table :
          this._setOption("appendTo", $("
").appendTo($("body")));
          data._renderMenu = function (ul, items) {
            ul.addClass('ui-autocomplete-layout-table');
            return $.ui.autocomplete.prototype._renderMenu.call(this, ul, items);
          };
          data._renderItem = function (ul, item) {
            var link = $("");
            item.dom.find("td").each(function () {
              $('').html($(this).html()).appendTo(link)
            })
            return $("

")
              .data("item.autocomplete", item)
              .append(link)
              .appendTo(ul);
          };
          break;
      }
    },

    _setOption: function (key, value) {
      if (key === 'layout') {
        this._setLayout(value);
      }
      if (key === 'disabled') {
        if (value) {
          this._disable();
        } else {
          this._enable();
        }
      }

      this._super(key, value);

      if (key === 'source') {
        if (isDomBasedSource(value)) {
          this._updateDomSuggestions();
        }
      }
    }
  });

  /**
   * Default implementation of extracting searchTerm prefix for checking cache
   */
  function defaultExtractCacheSearchPrefix(searchTerm) {
    if (searchTerm && this.options.minLength > 0 && this.options.minLength < searchTerm.length) {
      return searchTerm.substring(0, this.options.minLength);
    } else {
      return searchTerm;
    }
  }

  function isDomBasedSource(source) {
    return source instanceof HTMLElement || typeof source == 'string';
  }

}(jQuery));<commit_msg>widget(autocomplete): take minLength into consideration when using token #43 + fixing list-based suggesting<commit_after>(function ($) {

  var LAYOUT = {
    list: 0,
    table: 1
  };

  $.ui.richAutocomplete = {};

  $.extend($.ui.richAutocomplete, {
    objectCache: function () {
      var cache = {};
      return {
        get: function (term) {
          return cache[term];
        },
        put: function (term, value) {
          cache[term] = value;
        }
      }
    }
  });

  $.widget('rf.richAutocomplete', {

    LAYOUT: LAYOUT,

    options: {
      token: "",
      showButton: false,
      autoFocus: false,
      autoFill: false,
      source: [],
      layout: LAYOUT.list,
      minLength: 0,

      /**
       * cached turns caching on
       */
      cached: false,
      /**
       * defines what prefix of searchTerm will be used to query cache
       */
      extractCacheSearchPrefix: defaultExtractCacheSearchPrefix,
      /**
       * pluggable cache implementation
       */
      cacheImplemenation: $.ui.richAutocomplete.objectCache,

      /**
       * Provide function which will be used to filter array of suggestions by given searchTerm
       * function filter(array, searchTerm)
       */
      filter: $.ui.autocomplete.filter,

      /**
       * Function called when search triggered but before suggestions are composed.
       *
       * It gives chance to update source of suggestions as reflection to current search term.
       *
       * function({ term: currentSearchTerm }, doneCallback)
       *
       * when doneCallback is specified, autocomplete will wait with update of suggestions before doneCallback
       * is called. Usually it is called on the end of AJAX data update.
       */
      update: null,

      // TODO should not be option, but private member
      suggestions: []
    },

    _create: function () {
      var widget = this;
      this.input = this.element;

      // initialize DOM structure
      this.root = this._initDom();

      if (this.options.cached) {
        this.cache = new this.options.cacheImplemenation();
      }

      if (!this.options.layout) {
        this._setOption('layout', this.LAYOUT.list);
      }

      var autocompleteOptions = this._getAutocompleteOptions();
      this.input.autocomplete(autocompleteOptions);

      this.input.keydown(function(event) {
        widget.lastKeyupEvent = event;
      });

      this._registerListeners();

      if (this.options.source) {
        this._setOption('source', this.options.source);
      }

      if (this.input.disabled) {
        this._setOption('disabled', true);
      }
    },

    _destroy: function () {
      this.input.autocomplete('destroy');
      this._destroyDom();
    },

    _enable: function () {
      this.input.autocomplete('enable');
      this.button.removeAttr('disabled');
    },

    _disable: function () {
      this.input.autocomplete('disable');
      this.button.attr('disabled', 'disabled');
    },

    _initDom: function () {
      this.root = $($('

').insertBefore(this.input)[0]);
      this.root.append(this.input.detach());

      if (this.options.showButton) {
        var widget = this;

        this.root.addClass("input-group");
        this.button = $('<button class="btn btn-light" type="button"></button>').appendTo(this.root).find("button");

        this.buttonClickHandler = function () {
          widget.input.autocomplete("search");
          widget.input.focus();
        };

        this.button.on('click', this.buttonClickHandler);
      }

      return this.root;
    },

    _destroyDom: function () {
      this.input.detach().insertAfter(this.root);
      this.root.remove();
    },

    _getAutocompleteOptions: function () {
      var widget = this;

      return {
        delay: 0,
        minLength: this.options.minLength,
        autoFocus: this.options.autoFocus,
        source: function (request, response) {
          widget._getSuggestions(request, response);
        },
        search: function () {
          this.opened = true;
          if (widget.options.autoFill) {
            if (widget.entered === widget.input.val()) {
              return false;
            }
          }
          widget.entered = widget.input.val();

          if (widget.button) {
            widget.button.off('click', widget.buttonClickHandler);
            widget.button.find('i').removeClass('icon-chevron-down');
            widget.button.find('i').addClass('icon-chevron-up');
          }
        },
        focus: function (event, ui) {
          if (!widget.options.autoFill) {
            return false;
          }
          if (widget.lastKeyupEvent.keyCode == 8) {
            // refuse to auto-fill on on backspace
            return false;
          }
          var input = widget.input,
            original = widget.entered;
            label = ui.item.label;

          original = original.substring(0, input[0].selectionStart);

          if (original.length > 0 && label.toLowerCase().indexOf(original.toLowerCase()) === 0) {
            input.val(original + label.substring(original.length));

            input[0].selectionStart = original.length;
            input[0].selectionEnd = label.length;
            return false;
          } else {
            return true;
          }
        },
        close: function (event) {
          if (widget.button) {
            widget.button.find('i').removeClass('icon-chevron-up');
            widget.button.find('i').addClass('icon-chevron-down');

            // we must delay attaching of event handler because otherwise the click will happen
            // and the handler will be triggered right after closing the menu
            setTimeout(function() {
              widget.button.on('click', widget.buttonClickHandler);
            }, 150);
          }
        },
        select: function (event, ui) {
          this.value = widget._selectValue(event, ui, this.value);
          return false;
        }
      };
    },

    _splitTokens: function (val) {
      var regexp = new RegExp("\\s*" + this.options.token + "\\s*");
      return val.split(regexp);
    },

    _extractLastToken: function (term) {
      return this._splitTokens(term).pop();
    },

    _extractSearchTerm: function (request) {
      if (this.options.token) {
        return this._extractLastToken(request.term);
      } else {
        return request.term;
      }
    },

    _selectValue: function (event, ui, value) {
      if (this.options.token) {
        var terms = this._splitTokens(value);
        // remove the current input
        terms.pop();
        // add the selected item
        terms.push(ui.item.value);
        return terms.join(this.options.token + " ");
      } else {
        return ui.item.value;
      }
    },

    _getSuggestions: function (request, response) {
      var searchTerm = this._extractSearchTerm(request);

      if ($.type(searchTerm) === 'string' && searchTerm.length < this.options.minLength) {
        return false;
      }

      var req = $.extend({}, request, {
        term: searchTerm
      });

      if (this.cache) {
        req.term = this.options.extractCacheSearchPrefix.call(this, searchTerm);
      }

      var resp = $.proxy(function () {
        if (this.cache) {
          this.cache.put(searchTerm, arguments);
        }
        return response.apply(window, arguments);
      }, this);

      if (this.cache) {
        var result = this.cache.get(searchTerm);
        if (result) {
          response.apply(window, result);
          return;
        }
      }

      this._retrieveSuggestions(req, resp);
    },

    _retrieveSuggestions: function (request, response) {
      var source = this.options.source;

      if (isDomBasedSource(source)) {
        // DOM-based
        this._suggestFromDom(request, response);
      } else if ($.isFunction(source)) {
        // function-based
        source(request, response);
      } else {
        // array-based
        response(this.options.filter(source, request.term));
      }
    },

    _suggestFromDom: function(request, response) {
      var updateFn = this.options.update;

      var updateSuggestionsAndRespond = $.proxy(function () {
        this._updateDomSuggestions();
        response(this.options.filter(this.options.suggestions, request.term));
      }, this);

      if ($.isFunction(updateFn)) {
        // has the function second parameter? (which is 'done' callback)
        if (updateFn.length >= 2) {
          updateFn.call(window, request, updateSuggestionsAndRespond);
        } else {
          updateFn.call(window, request);
          updateSuggestionsAndRespond();
        }
      } else {
        response(this.options.filter(this.options.suggestions, request.term));
      }
    },

    _updateDomSuggestions: function () {
      var suggestions = [];
      var domSource = $(this.options.source);
      var layout = LAYOUT.list;

      if (domSource.is('table')) {
        layout = LAYOUT.table;
        domSource = domSource.children('tbody');
      }
      $(domSource).children('tr, li').each(function () {
        suggestions.push({
          value: $(this).data("label") || $(this).text().trim(),
          dom: $(this).clone()
        })
      });

      if (this.option('layout') !== layout) {
        this._setOption('layout', layout);
      }

      this._setOption('suggestions', suggestions);
    },

    _preventTabbing: function () {
      this.element.bind("keydown", function (event) {
        if (event.keyCode === $.ui.keyCode.TAB &&
          $(this).data("autocomplete").menu.active) {
          event.preventDefault();
        }
      });
    },

    _registerListeners: function () {
      this.input.bind("autocompletesearch", this.options.onsearch);
      this.input.bind("autocompleteopen", this.options.onopen);
      this.input.bind("autocompletefocus", this.options.onfocus);
      this.input.bind("autocompleteselect", this.options.onselect);
      this.input.bind("autocompleteclose", this.options.onclose);
      this.input.bind("autocompletechange", this.options.onchange);
    },

    _setLayout: function (layout) {
      var data = this.input.autocomplete().data('ui-autocomplete');
      switch (layout) {
        case this.LAYOUT.list :
          data._renderMenu = $.ui.autocomplete.prototype._renderMenu;
          data._renderItem = function (ul, item) {
            var content = item.dom ? $("").html(item.dom.html()) : $("").text(item.label);
            return $("
").append(content).appendTo(ul);
          };
          break;
        case this.LAYOUT.table :
          this._setOption("appendTo", $("
").appendTo($("body")));
          data._renderMenu = function (ul, items) {
            ul.addClass('ui-autocomplete-layout-table');
            return $.ui.autocomplete.prototype._renderMenu.call(this, ul, items);
          };
          data._renderItem = function (ul, item) {
            var link = $("");
            item.dom.find("td").each(function () {
              $('').html($(this).html()).appendTo(link)
            })
            return $("

")
              .data("item.autocomplete", item)
              .append(link)
              .appendTo(ul);
          };
          break;
      }
    },

    _setOption: function (key, value) {
      if (key === 'layout') {
        this._setLayout(value);
      }
      if (key === 'disabled') {
        if (value) {
          this._disable();
        } else {
          this._enable();
        }
      }

      this._super(key, value);

      if (key === 'source') {
        if (isDomBasedSource(value)) {
          this._updateDomSuggestions();
        }
      }
    }
  });

  /**
   * Default implementation of extracting searchTerm prefix for checking cache
   */
  function defaultExtractCacheSearchPrefix(searchTerm) {
    if (searchTerm && this.options.minLength > 0 && this.options.minLength < searchTerm.length) {
      return searchTerm.substring(0, this.options.minLength);
    } else {
      return searchTerm;
    }
  }

  function isDomBasedSource(source) {
    return source instanceof HTMLElement || typeof source == 'string';
  }

}(jQuery));<|endoftext|>


<commit_before>
// @require core/widget/widget.js

(function ( $, _, Svelto, Widgets, Factory ) {

  /* CONFIG */

  let config = {
    name: 'tableHelper',
    plugin: true,
    selector: 'table.table',
    templates: {
      row: _.template ( `
        <tr <%= o.id ? 'class=' + o.id : '' %> >
          <% for ( var i = 0, l = o.datas.length; i < l; i++ ) { %>
            <%= o.datas[i] %>
          <% } %>
          <% for ( var i = 0, l = o.missing; i < l; i++ ) { %>

          <% } %>

      ` )
    },
    options: {
      rowIdPrefix: 'srid',
      selectors: {
        header: 'thead',
        body: 'tbody',
        headerCell: 'th',
        rowCell: 'td',
        emptyRow: 'tr.table-row-empty',
        notEmptyRow: 'tr:not(.table-row-empty)'
      },
      callbacks: {
        change: _.noop,
        add: _.noop,
        update: _.noop,
        remove: _.noop,
        clear: _.noop
      }
    },
  };

  /* TABLE HELPER */

  class TableHelper extends Widgets.Widget {

    /* SPECIAL */

    _variables () {

      this.$table = this.$element;
      this.$header = this.$table.find ( this.options.selectors.header );
      this.$body = this.$table.find ( this.options.selectors.body );
      this.$headerCells = this.$header.find ( this.options.selectors.headerCell );
      this.$emptyRow = this.$body.find ( this.options.selectors.emptyRow );

      this.columnsNr = this.$headerCells.length;

    }

    /* PRIVATE */

    _getRowId ( id ) {

      return this.options.rowIdPrefix + '-' + this.guid + '-' + id;

    }

    /* API */

    add ( id, ...datas ) {

      let rowId = id ? this._getRowId ( id ) : false;

      if ( datas.length ) {

        if ( rowId && $( '.' + rowId ).length === 1 ) return;

        let chunks = _.chunk ( datas, this.columnsNr ),
            $rows = $();

        chunks.forEach ( chunk => {

          let rowHtml = this._template ( 'row', { id: rowId, datas: chunk, missing: this.columnsNr - chunk.length } );

          $rows = $rows.add ( rowHtml );

        });

        this.$body.append ( $rows );

        this._trigger ( 'change' );

        this._trigger ( 'add', {
          $rows: $rows
        });

      }

    }

    update ( id, ...datas ) {

      let $row = $( '.' + this._getRowId ( id ) );

      if ( datas.length && $row.length === 1 ) {

        let $rowCells = $row.find ( this.options.selectors.rowCell );

        for ( let i = 0, l = datas.length; i < l; i++ ) {

          if ( _.isString ( datas[i] ) ) {

            $rowCells.eq ( i ).html ( datas[i] );

          }

        }

        this._trigger ( 'change' );

        this._trigger ( 'update', {
          $row: $row
        });

      }

    }

    remove ( id ) {

      let $row = $( '.' + this._getRowId ( id ) );

      if ( $row.length === 1 ) {

        $row.remove ();

        this._trigger ( 'change' );

        this._trigger ( 'remove', {
          $row: $row
        });

      }

    }

    clear () {

      let $rows = this.$body.find ( this.options.selectors.notEmptyRow );

      if ( $rows.length ) {

        $rows.remove ();

        this._trigger ( 'change' );

        this._trigger ( 'clear', {
          $rows: $rows
        });

      }

    }

  }

  /* FACTORY */

  Factory.make ( TableHelper, config );

}( Svelto.$, Svelto._, Svelto, Svelto.Widgets, Svelto.Factory ));
<commit_msg>TableHelper: more robust parsing of the `row` template<commit_after>
// @require core/widget/widget.js

(function ( $, _, Svelto, Widgets, Factory ) {

  /* CONFIG */

  let config = {
    name: 'tableHelper',
    plugin: true,
    selector: 'table.table',
    templates: {
      row: _.template ( `
        <tr <%= o.id ? 'class=' + o.id : '' %> >
          <% for ( var i = 0, l = o.datas.length; i < l; i++ ) { %>
            <%= o.datas[i] %>
          <% } %>
          <% for ( var i = 0, l = o.missing; i < l; i++ ) { %>

          <% } %>

      ` )
    },
    options: {
      rowIdPrefix: 'srid',
      selectors: {
        header: 'thead',
        body: 'tbody',
        headerCell: 'th',
        rowCell: 'td',
        emptyRow: 'tr.table-row-empty',
        notEmptyRow: 'tr:not(.table-row-empty)'
      },
      callbacks: {
        change: _.noop,
        add: _.noop,
        update: _.noop,
        remove: _.noop,
        clear: _.noop
      }
    },
  };

  /* TABLE HELPER */

  class TableHelper extends Widgets.Widget {

    /* SPECIAL */

    _variables () {

      this.$table = this.$element;
      this.$header = this.$table.find ( this.options.selectors.header );
      this.$body = this.$table.find ( this.options.selectors.body );
      this.$headerCells = this.$header.find ( this.options.selectors.headerCell );
      this.$emptyRow = this.$body.find ( this.options.selectors.emptyRow );

      this.columnsNr = this.$headerCells.length;

    }

    /* PRIVATE */

    _getRowId ( id ) {

      return this.options.rowIdPrefix + '-' + this.guid + '-' + id;

    }

    /* API */

    add ( id, ...datas ) {

      let rowId = id ? this._getRowId ( id ) : false;

      if ( datas.length ) {

        if ( rowId && $( '.' + rowId ).length === 1 ) return;

        let chunks = _.chunk ( datas, this.columnsNr ),
            $rows = $();

        chunks.forEach ( chunk => {

          let rowHtml = this._template ( 'row', { id: rowId, datas: chunk, missing: this.columnsNr - chunk.length } ),
              $row = $(`<table>${rowHtml}</table>`).find ( 'tr' ); // Parsing a `tr` tag without a table might not be supported by the DOM library

          $rows = $rows.add ( $row );

        });

        this.$body.append ( $rows );

        this._trigger ( 'change' );

        this._trigger ( 'add', {
          $rows: $rows
        });

      }

    }

    update ( id, ...datas ) {

      let $row = $( '.' + this._getRowId ( id ) );

      if ( datas.length && $row.length === 1 ) {

        let $rowCells = $row.find ( this.options.selectors.rowCell );

        for ( let i = 0, l = datas.length; i < l; i++ ) {

          if ( _.isString ( datas[i] ) ) {

            $rowCells.eq ( i ).html ( datas[i] );

          }

        }

        this._trigger ( 'change' );

        this._trigger ( 'update', {
          $row: $row
        });

      }

    }

    remove ( id ) {

      let $row = $( '.' + this._getRowId ( id ) );

      if ( $row.length === 1 ) {

        $row.remove ();

        this._trigger ( 'change' );

        this._trigger ( 'remove', {
          $row: $row
        });

      }

    }

    clear () {

      let $rows = this.$body.find ( this.options.selectors.notEmptyRow );

      if ( $rows.length ) {

        $rows.remove ();

        this._trigger ( 'change' );

        this._trigger ( 'clear', {
          $rows: $rows
        });

      }

    }

  }

  /* FACTORY */

  Factory.make ( TableHelper, config );

}( Svelto.$, Svelto._, Svelto, Svelto.Widgets, Svelto.Factory ));
<|endoftext|>


<commit_before>
const Direct = Symbol('MagicMapper.Direct');

function mapWithSchema(propertyName, schema, sourceValue){
	const schemaValue = schema[propertyName];
	if(schemaValue === Direct){
		return sourceValue;
	}
	else if (typeof schemaValue === 'function'){
		return schemaValue.call(null, sourceValue);
	}
	else{
		return schemaValue;
	}
}

/**
 * This JSON Mapper helps you to map JSON objects to other ones.
 */
class MagicMapper{

	/**
	 * Symbol to declare an object to be mapped as it is.
	 * @returns {Symbol}
	 * @constructor
	 */
	static get Direct() { return Direct; }

	/**
	 * @param {object} options Options that the Mapper uses on _all_ mappings. Available options are:
	 * - propertyTransform: transform function _fn(propertyName)_ for property
	 * - valueTransform: transform function _fn(value)_ for value
	 * - exclusive: flag to indicate whether _all_ properties shall be mapped (false), or only properties defined in passed schema (true)
	 */
	constructor(options = {}){

		this.options = Object.assign({
			propertyTransform: null,
			valueTransform: null,
			exclusive : false
		}, options);

		if(this.options.propertyTransform && (typeof this.options.propertyTransform !== 'function'))
			throw "'propertyTransform' must be a function";

		if(this.options.valueTransform && (typeof this.options.valueTransform !== 'function'))
			throw "'valueTransform ' must be a function";
	}

	/**
	 * Maps properties of src to a new object
	 * @param {object} from The source object
	 * @param {object} schema Optional Schema, that will be applied selectively. When using _options.exclusive=true_ schema is mandatory.
	 * @returns {object} A new object with all properties and values from source object
	 */
	map(from, schema = null){
		let mapped = {};

		if(this.options.exclusive && !schema) throw "Exclusive option requires a schema!";

		Object.keys(from).forEach( p => {
			const tp = this.options.propertyTransform ? this.options.propertyTransform(p) : p;
			const fromValue = this.options.valueTransform ? this.options.valueTransform(from[p]) : from[p];
			const isArray = fromValue && Array.isArray(fromValue);
			const isObject = fromValue && (typeof fromValue === 'object') && !isArray;

			if(this.options.exclusive && !schema[tp]) return;

			if(!isObject && schema && schema.hasOwnProperty(tp)){
				mapped[tp] = mapWithSchema(tp, schema, fromValue);
			}
			else if(isArray){
				mapped[tp] = fromValue.map( v => typeof v === 'object' ? this.map(v,schema) : v );
			}
			else if (isObject){
				const s = schema && schema[tp];
				if(typeof s === 'function'){
					mapped[tp] = schema[tp].call(null, fromValue);
				}
				else{
					mapped[tp] = this.map(fromValue, s );
				}
			}
			else if(!this.options.exclusive){
				mapped[tp] = fromValue;
			}
		});
		return mapped;
	}
}

export default MagicMapper;<commit_msg>fixed #2<commit_after>
const Direct = '@@MagicMapper.Direct'; // I prefer Symbol, but doesn't work in IE

function mapWithSchema(propertyName, schema, sourceValue){
	const schemaValue = schema[propertyName];
	if(schemaValue === Direct){
		return sourceValue;
	}
	else if (typeof schemaValue === 'function'){
		return schemaValue.call(null, sourceValue);
	}
	else{
		return schemaValue;
	}
}

/**
 * This JSON Mapper helps you to map JSON objects to other ones.
 */
class MagicMapper{

	/**
	 * Symbol to declare an object to be mapped as it is.
	 * @returns {Symbol}
	 * @constructor
	 */
	static get Direct() { return Direct; }

	/**
	 * @param {object} options Options that the Mapper uses on _all_ mappings. Available options are:
	 * - propertyTransform: transform function _fn(propertyName)_ for property
	 * - valueTransform: transform function _fn(value)_ for value
	 * - exclusive: flag to indicate whether _all_ properties shall be mapped (false), or only properties defined in passed schema (true)
	 */
	constructor(options = {}){

		this.options = Object.assign({
			propertyTransform: null,
			valueTransform: null,
			exclusive : false
		}, options);

		if(this.options.propertyTransform && (typeof this.options.propertyTransform !== 'function'))
			throw "'propertyTransform' must be a function";

		if(this.options.valueTransform && (typeof this.options.valueTransform !== 'function'))
			throw "'valueTransform ' must be a function";
	}

	/**
	 * Maps properties of src to a new object
	 * @param {object} from The source object
	 * @param {object} schema Optional Schema, that will be applied selectively. When using _options.exclusive=true_ schema is mandatory.
	 * @returns {object} A new object with all properties and values from source object
	 */
	map(from, schema = null){
		let mapped = {};

		if(this.options.exclusive && !schema) throw "Exclusive option requires a schema!";

		Object.keys(from).forEach( p => {
			const tp = this.options.propertyTransform ? this.options.propertyTransform(p) : p;
			const fromValue = this.options.valueTransform ? this.options.valueTransform(from[p]) : from[p];
			const isArray = fromValue && Array.isArray(fromValue);
			const isObject = fromValue && (typeof fromValue === 'object') && !isArray;

			if(this.options.exclusive && !schema[tp]) return;

			if(!isObject && schema && schema.hasOwnProperty(tp)){
				mapped[tp] = mapWithSchema(tp, schema, fromValue);
			}
			else if(isArray){
				mapped[tp] = fromValue.map( v => typeof v === 'object' ? this.map(v,schema) : v );
			}
			else if (isObject){
				const s = schema && schema[tp];
				if(typeof s === 'function'){
					mapped[tp] = schema[tp].call(null, fromValue);
				}
				else{
					mapped[tp] = this.map(fromValue, s );
				}
			}
			else if(!this.options.exclusive){
				mapped[tp] = fromValue;
			}
		});
		return mapped;
	}
}

export default MagicMapper;<|endoftext|>


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

/*!
 * FlexCss.Tooltip
 * Licensed under the MIT License (MIT)
 * Copyright (c) 2015 David Heidrich, BowlingX <me@bowlingx.com>
 */

'use strict';

import Util from 'util/Util';
/**
 * @type {string}
 */
const CLASS_NAMES_TOOLTIP = 'tooltip-container';
/**
 * @type {string}
 */
const CLASS_NAMES_OPEN = 'open';

/**
 * @type {string}
 */
const ATTR_DATA_CLASSNAME = 'data-class';

/**
 * @type {HTMLDocument}
 */
const doc = global.document;

/**
 * Simple Tooltip
 */
export default
class Tooltip {

    /**
     * Creates a Tooltip
     * @param {HTMLElement|String} DelegateContainer
     * @param {Object} options
     */
    constructor(DelegateContainer, options) {

        /**
         * The Container where possible events are captured
         */
        this.container = DelegateContainer instanceof HTMLElement ?
            DelegateContainer : doc.getElementById(DelegateContainer);

        if (!this.container) {
            throw 'Could not create Tooltip, DelegateContainer not found';
        }

        /**
         * The Container where tooltips are stored for this instance
         * @type {HTMLElement}
         */
        this.tooltipContainer = null;

        /**
         * Default Options
         */
        this.options = {
            containerClass: '',
            selectorAttribute: 'data-tooltip'
        };

        Object.assign(this.options, options);
    }

    /**
     * Creates and shows a tooltip
     * @param {HTMLElement} target where is this tooltip positioned
     * @param {String} text text content in tooltip, will be NOT escaped
     * @param {Boolean} removeTitle removes title element if given
     */
    createTooltip(target, text, removeTitle) {
        // abort if text is empty
        if (!text || text && text.trim() === '') {
            return;
        }
        let tooltipContainer = this.tooltipContainer;

        if (!tooltipContainer) {
            tooltipContainer = doc.createElement('div');
            this._restoreClassNames(tooltipContainer, target);
            this.container.appendChild(tooltipContainer);
            this.tooltipContainer = tooltipContainer;
        }
        tooltipContainer.style.left = 'auto';
        tooltipContainer.style.top = 'auto';
        tooltipContainer.innerHTML = text;
        tooltipContainer.flexTooltipCurrentTarget = target;
        if (removeTitle) {
            target.oldTitle = text;
            target.removeAttribute('title');
        }

        Util.setupPositionNearby(target, tooltipContainer, this.container, true, true);

        tooltipContainer.classList.add(CLASS_NAMES_OPEN);

    }

    _restoreClassNames(container, target) {
        // allow additional classname per tooltip on target element
        const classNames = [CLASS_NAMES_TOOLTIP, this.options.containerClass],
            maybeTargetClass = target.getAttribute(ATTR_DATA_CLASSNAME);
        if (maybeTargetClass) {
            classNames.push(maybeTargetClass);
        }
        container.className = classNames.join(" ");
        return this;
    }

    /**
     * @returns {HTMLElement|null}
     */
    getCurrentTarget() {
        return this.tooltipContainer ? this.tooltipContainer.flexTooltipCurrentTarget : null;
    }

    /**
     * Removes a Tooltip on given target
     * @param {HTMLElement} [target], if not given will remove current open tooltip on this instance
     */
    removeTooltip(target) {
        if (!target && this.tooltipContainer) {
            target = this.tooltipContainer.flexTooltipCurrentTarget;
        }
        if (this.tooltipContainer) {
            if (this.tooltipContainer.flexTooltipCurrentTarget !== target) {
                return;
            }
            this.tooltipContainer.classList.remove(CLASS_NAMES_OPEN);
            delete this.tooltipContainer.flexTooltipCurrentTarget;
        }
        if (target && target.oldTitle) {
            target.setAttribute('title', target.oldTitle);
        }
    }

    /**
     * Initilizes mouse events on container element
     */
    registerEvents() {
        const self = this;
        this.container.addEventListener('mouseover', function (e) {
            if (e.target.hasAttribute(self.options.selectorAttribute)) {
                self.createTooltip(e.target, e.target.getAttribute('title'), true);
            }
        });

        this.container.addEventListener('mouseout', function (e) {
            if (e.target.hasAttribute(self.options.selectorAttribute)) {
                self.removeTooltip(e.target);
            }
        });
        return this;
    }
}
<commit_msg>make attribute optional<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 David Heidrich, BowlingX <me@bowlingx.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/*!
 * FlexCss.Tooltip
 * Licensed under the MIT License (MIT)
 * Copyright (c) 2015 David Heidrich, BowlingX <me@bowlingx.com>
 */

'use strict';

import Util from 'util/Util';
/**
 * @type {string}
 */
const CLASS_NAMES_TOOLTIP = 'tooltip-container';
/**
 * @type {string}
 */
const CLASS_NAMES_OPEN = 'open';

/**
 * @type {string}
 */
const ATTR_DATA_CLASSNAME = 'data-class';

/**
 * @type {HTMLDocument}
 */
const doc = global.document;

/**
 * Simple Tooltip
 */
export default
class Tooltip {

    /**
     * Creates a Tooltip
     * @param {HTMLElement|String} DelegateContainer
     * @param {Object} options
     */
    constructor(DelegateContainer, options) {

        /**
         * The Container where possible events are captured
         */
        this.container = DelegateContainer instanceof HTMLElement ?
            DelegateContainer : doc.getElementById(DelegateContainer);

        if (!this.container) {
            throw 'Could not create Tooltip, DelegateContainer not found';
        }

        /**
         * The Container where tooltips are stored for this instance
         * @type {HTMLElement}
         */
        this.tooltipContainer = null;

        /**
         * Default Options
         */
        this.options = {
            containerClass: '',
            selectorAttribute: 'data-tooltip'
        };

        Object.assign(this.options, options);
    }

    /**
     * Creates and shows a tooltip
     * @param {HTMLElement} target where is this tooltip positioned
     * @param {String} text text content in tooltip, will be NOT escaped
     * @param {Boolean} [removeTitle] removes title element if given
     */
    createTooltip(target, text, removeTitle) {
        // abort if text is empty
        if (!text || text && text.trim() === '') {
            return;
        }
        let tooltipContainer = this.tooltipContainer;

        if (!tooltipContainer) {
            tooltipContainer = doc.createElement('div');
            this._restoreClassNames(tooltipContainer, target);
            this.container.appendChild(tooltipContainer);
            this.tooltipContainer = tooltipContainer;
        }
        tooltipContainer.style.left = 'auto';
        tooltipContainer.style.top = 'auto';
        tooltipContainer.innerHTML = text;
        tooltipContainer.flexTooltipCurrentTarget = target;
        if (removeTitle) {
            target.oldTitle = text;
            target.removeAttribute('title');
        }

        Util.setupPositionNearby(target, tooltipContainer, this.container, true, true);

        tooltipContainer.classList.add(CLASS_NAMES_OPEN);

    }

    _restoreClassNames(container, target) {
        // allow additional classname per tooltip on target element
        const classNames = [CLASS_NAMES_TOOLTIP, this.options.containerClass],
            maybeTargetClass = target.getAttribute(ATTR_DATA_CLASSNAME);
        if (maybeTargetClass) {
            classNames.push(maybeTargetClass);
        }
        container.className = classNames.join(" ");
        return this;
    }

    /**
     * @returns {HTMLElement|null}
     */
    getCurrentTarget() {
        return this.tooltipContainer ? this.tooltipContainer.flexTooltipCurrentTarget : null;
    }

    /**
     * Removes a Tooltip on given target
     * @param {HTMLElement} [target], if not given will remove current open tooltip on this instance
     */
    removeTooltip(target) {
        if (!target && this.tooltipContainer) {
            target = this.tooltipContainer.flexTooltipCurrentTarget;
        }
        if (this.tooltipContainer) {
            if (this.tooltipContainer.flexTooltipCurrentTarget !== target) {
                return;
            }
            this.tooltipContainer.classList.remove(CLASS_NAMES_OPEN);
            delete this.tooltipContainer.flexTooltipCurrentTarget;
        }
        if (target && target.oldTitle) {
            target.setAttribute('title', target.oldTitle);
        }
    }

    /**
     * Initilizes mouse events on container element
     */
    registerEvents() {
        const self = this;
        this.container.addEventListener('mouseover', function (e) {
            if (e.target.hasAttribute(self.options.selectorAttribute)) {
                self.createTooltip(e.target, e.target.getAttribute('title'), true);
            }
        });

        this.container.addEventListener('mouseout', function (e) {
            if (e.target.hasAttribute(self.options.selectorAttribute)) {
                self.removeTooltip(e.target);
            }
        });
        return this;
    }
}
<|endoftext|>