<commit_before>var geohash = require('ngeohash');
var geolib = require('geolib');

var helper = require('./util/helper');

var query = require('./query');
var range = require('./range');

var convertUnitsFromMeters = helper.convertUnitsFromMeters;
var convertUnitsToMeters = helper.convertUnitsToMeters;
var buildObjectFromKeyedArray = helper.buildObjectFromKeyedArray;
var buildObjectFromStringArray = helper.buildObjectFromStringArray;

// emulated interface constructor

function EmulatedInterface(client) {
    this.client = client;
    console.log('GeoRedis using EMULATED interface...');
}

// Native Geo Commands

EmulatedInterface.prototype.geoadd = function(locationName, point, zSetName, callBack) {
    this.client.zadd(zSetName, geohash.encode_int(point.latitude, point.longitude, 52), locationName, callBack);
};

EmulatedInterface.prototype.geoadd_multi = function(locationSet, zSetName, callBack) {
    var argArray = [zSetName];
    var locationName;
    var location;

    for (locationName in locationSet) {
        location = locationSet[locationName];
        argArray.push(geohash.encode_int(location.latitude, location.longitude, 52), locationName);
    }

    this.client.zadd(argArray, callBack);
};

EmulatedInterface.prototype.geodist = function(locationA, locationB, units, zSetName, callBack) {

    var multi = this.client.multi();
    var pointA;
    var pointB;
    var distance;

    multi.zscore(zSetName, locationA);
    multi.zscore(zSetName, locationB);

    multi.exec(function(err, replies) {

        if (err) {
            callBack(err, null);
        } else {

            pointA = geohashDecode(replies[0]);
            pointB = geohashDecode(replies[1]);

            distance = geolib.getDistance(pointA, pointB, 1);
            distance = convertUnitsFromMeters(units, distance);

            callBack(null, distance);
        }
    });
};

EmulatedInterface.prototype.geohash = function(member, zSetName, callBack) {

    var point;
    var hash;

    this.client.zscore(zSetName, member, function(err, result) {
        if (err) {
            callBack(err, null);
        } else {
            if (result !== null) {
                point = geohashDecode(result);
                hash = geohash.encode(point.latitude, point.longitude);
            } else {
                hash = null;
            }
            callBack(null, hash);
        }
    });
};

EmulatedInterface.prototype.geohashes = function(members, zSetName, callBack) {

    var locationSet = {};
    var point;
    var i;
    var multi = this.client.multi();

    for (i = 0; i < members.length; i++) {
        multi.zscore(zSetName, members[i]);
    }

    multi.exec(function(err, replies) {

        if (err) {
            callBack(err, null);
        } else {

            for (i = 0; i < replies.length; i++) {
                if (replies[i] !== null) {
                    point = geohashDecode(replies[i]);
                    locationSet[members[i]] = geohash.encode(point.latitude, point.longitude);
                } else {
                    locationSet[members[i]] = null;
                }
            }

            callBack(null, locationSet);
        }
    });
};

EmulatedInterface.prototype.geopos = function(members, zSetName, callBack) {

    var argArray = [zSetName].concat(members);
    var locationSet = {};
    var i;

    this.client.send_command('geopos', argArray, function(err, results) {
        if (err) {
            callBack(err, null);
        } else {
            for (i = 0; i < results.length; i++) {
                if (results[i] !== null) {
                    locationSet[members[i]] = {
                        latitude: results[i][1],
                        longitude: results[i][0]
                    };
                } else {
                    locationSet[members[i]] = null;
                }
            }
            callBack(null, locationSet);
        }
    });
};

EmulatedInterface.prototype.geopos_multi = function(members, zSetName, callBack) {

    var argArray = [zSetName].concat(members);
    var locationSet = {};
    var i;

    this.client.send_command('geopos', argArray, function(err, results) {
        if (err) {
            callBack(err, null);
        } else {
            for (i = 0; i < results.length; i++) {
                if (results[i] !== null) {
                    locationSet[members[i]] = {
                        latitude: results[i][1],
                        longitude: results[i][0]
                    };
                } else {
                    locationSet[members[i]] = null;
                }
            }
            callBack(null, locationSet);
        }
    });
};

EmulatedInterface.prototype.georadius = function(point, radius, options, zSetName, callBack) {
    options.precise = true;
    this.nearby(point, radius, options, zSetName, callBack);
};

EmulatedInterface.prototype.georadiusbymember = function(member, radius, options, zSetName, callBack) {
    options.precise = true;
    this.nearbymember(member, radius, options, zSetName, callBack);
};

EmulatedInterface.prototype.nearby = function(point, distance, options, zSetName, callBack) {

    var ranges;
    var withValues = false;
    var units = options.units || 'm';
    var optionsLength = Object.keys(options).length;

    distance = convertUnitsToMeters(units, distance);

    ranges = range(point.latitude, point.longitude, distance);

    if ((options.units && optionsLength > 1) || optionsLength > 0) {
        withValues = true;
    }

    query(this.client, zSetName, ranges, withValues, function(err, locations) {

        if (!err) {
            if (withValues === true) {
                locations = processLocations(locations, point, distance, options, callBack);
            } else {
                Object.defineProperty(locations, 'locationSet', {
                    get: function() {
                        buildObjectFromStringArray(this);
                    }
                });
            }

            callBack(null, locations);
        } else {
            callBack(err, null);
        }
    });
};

EmulatedInterface.prototype.nearbymember = function(locationName, distance, options, zSetName, callBack) {

    var self = this;

    this.geopos([locationName], zSetName, function(err, location) {
        if (!err) {
            self.nearby(location, distance, options, zSetName, callBack);
        } else {
            callBack(err, null);
        }
    });
};

// Native Ordered Set Commands

EmulatedInterface.prototype.del = function(zSetName, callBack) {
    this.client.del(zSetName, callBack);
};

EmulatedInterface.prototype.zrem = function(locations, zSetName, callBack) {
    this.client.zrem(zSetName, locations, callBack);
};

function processLocations(locationSetOriginal, point, queryDistance, options, callBack) {

    var locations = [];

    var units = options.units || 'm';
    var withDistances = options.withDistances;
    var withCoordinates = options.withCoordinates;
    var withHashes = options.withHashes;
    var order = options.order;
    var precise = options.precise;

    var locationName;
    var location;
    var distance;

    if (order) {

        locations = orderResults(locationSetOriginal, point, queryDistance, withDistances, withCoordinates, withHashes, order, precise, units);

    } else {

        for (locationName in locationSetOriginal) {

            location = locationSetOriginal[locationName];
            distance = geolib.getDistance(point, location, 1);

            if (precise !== true || distance <= queryDistance) {

                location.key = locationName;
                location.distance = convertUnitsFromMeters(units, distance);

                if (withHashes === true) {
                    location.hash = geohash.encode(location.latitude, location.longitude);
                }

                locations.push(location);
            }
        }
    }

    Object.defineProperty(locations, 'locationSet', {
        get: function() {
            buildObjectFromKeyedArray(this);
        }
    });

    return locations;
}

function orderResults(locationSetOriginal, point, queryDistance, withDistances, withCoordinates, withHashes, order, precise, units) {

    var orderedLocations = geolib.orderByDistance(point, locationSetOriginal);
    var location;
    var i;

    if (precise) {
        for (i = orderedLocations.length - 1; i > -1; i--) {
            location = orderedLocations[i];
            if (location.distance > queryDistance) {
                orderedLocations = orderedLocations.slice(i);
                break;
            }
        }
    }

    if (withHashes) {
        for (i = 0; i < orderedLocations.length; i++) {
            location = orderedLocations[i];
            location.hash = geohash.encode(location.latitude, location.longitude);
        }
    }

    if (order === 'DESC') {
        orderedLocations = orderedLocations.reverse();
    }

    return orderedLocations;
}

function geohashDecode(hash) {

    var decoded;
    var latlon;

    if (hash !== null) {

        latlon = geohash.decode_int(hash, 52);

        decoded = {
            latitude: latlon.latitude,
            longitude: latlon.longitude
        };

    } else {

        decoded = {
            latitude: null,
            longitude: null
        };
    }

    return decoded;
}

module.exports = EmulatedInterface;
<commit_msg>Handling count option.<commit_after>var geohash = require('ngeohash');
var geolib = require('geolib');

var helper = require('./util/helper');

var query = require('./query');
var range = require('./range');

var convertUnitsFromMeters = helper.convertUnitsFromMeters;
var convertUnitsToMeters = helper.convertUnitsToMeters;
var buildObjectFromKeyedArray = helper.buildObjectFromKeyedArray;
var buildObjectFromStringArray = helper.buildObjectFromStringArray;

// emulated interface constructor

function EmulatedInterface(client) {
    this.client = client;
    console.log('GeoRedis using EMULATED interface...');
}

// Native Geo Commands

EmulatedInterface.prototype.geoadd = function(locationName, point, zSetName, callBack) {
    this.client.zadd(zSetName, geohash.encode_int(point.latitude, point.longitude, 52), locationName, callBack);
};

EmulatedInterface.prototype.geoadd_multi = function(locationSet, zSetName, callBack) {
    var argArray = [zSetName];
    var locationName;
    var location;

    for (locationName in locationSet) {
        location = locationSet[locationName];
        argArray.push(geohash.encode_int(location.latitude, location.longitude, 52), locationName);
    }

    this.client.zadd(argArray, callBack);
};

EmulatedInterface.prototype.geodist = function(locationA, locationB, units, zSetName, callBack) {

    var multi = this.client.multi();
    var pointA;
    var pointB;
    var distance;

    multi.zscore(zSetName, locationA);
    multi.zscore(zSetName, locationB);

    multi.exec(function(err, replies) {

        if (err) {
            callBack(err, null);
        } else {

            pointA = geohashDecode(replies[0]);
            pointB = geohashDecode(replies[1]);

            distance = geolib.getDistance(pointA, pointB, 1);
            distance = convertUnitsFromMeters(units, distance);

            callBack(null, distance);
        }
    });
};

EmulatedInterface.prototype.geohash = function(member, zSetName, callBack) {

    var point;
    var hash;

    this.client.zscore(zSetName, member, function(err, result) {
        if (err) {
            callBack(err, null);
        } else {
            if (result !== null) {
                point = geohashDecode(result);
                hash = geohash.encode(point.latitude, point.longitude);
            } else {
                hash = null;
            }
            callBack(null, hash);
        }
    });
};

EmulatedInterface.prototype.geohashes = function(members, zSetName, callBack) {

    var locationSet = {};
    var point;
    var i;
    var multi = this.client.multi();

    for (i = 0; i < members.length; i++) {
        multi.zscore(zSetName, members[i]);
    }

    multi.exec(function(err, replies) {

        if (err) {
            callBack(err, null);
        } else {

            for (i = 0; i < replies.length; i++) {
                if (replies[i] !== null) {
                    point = geohashDecode(replies[i]);
                    locationSet[members[i]] = geohash.encode(point.latitude, point.longitude);
                } else {
                    locationSet[members[i]] = null;
                }
            }

            callBack(null, locationSet);
        }
    });
};

EmulatedInterface.prototype.geopos = function(members, zSetName, callBack) {

    var argArray = [zSetName].concat(members);
    var locationSet = {};
    var i;

    this.client.send_command('geopos', argArray, function(err, results) {
        if (err) {
            callBack(err, null);
        } else {
            for (i = 0; i < results.length; i++) {
                if (results[i] !== null) {
                    locationSet[members[i]] = {
                        latitude: results[i][1],
                        longitude: results[i][0]
                    };
                } else {
                    locationSet[members[i]] = null;
                }
            }
            callBack(null, locationSet);
        }
    });
};

EmulatedInterface.prototype.geopos_multi = function(members, zSetName, callBack) {

    var argArray = [zSetName].concat(members);
    var locationSet = {};
    var i;

    this.client.send_command('geopos', argArray, function(err, results) {
        if (err) {
            callBack(err, null);
        } else {
            for (i = 0; i < results.length; i++) {
                if (results[i] !== null) {
                    locationSet[members[i]] = {
                        latitude: results[i][1],
                        longitude: results[i][0]
                    };
                } else {
                    locationSet[members[i]] = null;
                }
            }
            callBack(null, locationSet);
        }
    });
};

EmulatedInterface.prototype.georadius = function(point, radius, options, zSetName, callBack) {
    options.precise = true;
    this.nearby(point, radius, options, zSetName, callBack);
};

EmulatedInterface.prototype.georadiusbymember = function(member, radius, options, zSetName, callBack) {
    options.precise = true;
    this.nearbymember(member, radius, options, zSetName, callBack);
};

EmulatedInterface.prototype.nearby = function(point, distance, options, zSetName, callBack) {

    var ranges;
    var withValues = (options.precise || options.withDistances || options.withCoordinates || options.withHashes) ? true : false;
    var count = options.count;
    var units = options.units || 'm';

    distance = convertUnitsToMeters(units, distance);

    ranges = range(point.latitude, point.longitude, distance);

    query(this.client, zSetName, ranges, withValues, function(err, locations) {

        if (!err) {
            if (withValues === true) {
                locations = processLocations(locations, point, distance, options, callBack);
            } else {

                if (typeof count === 'number') {
                    locations = locations.slice(count);
                }

                Object.defineProperty(locations, 'locationSet', {
                    get: function() {
                        buildObjectFromStringArray(this);
                    }
                });
            }

            callBack(null, locations);
        } else {
            callBack(err, null);
        }
    });
};

EmulatedInterface.prototype.nearbymember = function(locationName, distance, options, zSetName, callBack) {

    var self = this;

    this.geopos([locationName], zSetName, function(err, location) {
        if (!err) {
            self.nearby(location, distance, options, zSetName, callBack);
        } else {
            callBack(err, null);
        }
    });
};

// Native Ordered Set Commands

EmulatedInterface.prototype.del = function(zSetName, callBack) {
    this.client.del(zSetName, callBack);
};

EmulatedInterface.prototype.zrem = function(locations, zSetName, callBack) {
    this.client.zrem(zSetName, locations, callBack);
};

function processLocations(locationSetOriginal, point, queryDistance, options, callBack) {

    var locations = [];

    var units = options.units || 'm';
    var withDistances = options.withDistances;
    var withCoordinates = options.withCoordinates;
    var withHashes = options.withHashes;
    var order = options.order;
    var count = options.count;
    var precise = options.precise;

    var locationName;
    var location;
    var distance;

    if (order) {

        locations = orderResults(locationSetOriginal, point, queryDistance, withDistances, withCoordinates, withHashes, order, precise, units);

    } else {

        for (locationName in locationSetOriginal) {

            location = locationSetOriginal[locationName];
            distance = geolib.getDistance(point, location, 1);

            if (precise !== true || distance <= queryDistance) {

                location.key = locationName;
                location.distance = convertUnitsFromMeters(units, distance);

                if (withHashes === true) {
                    location.hash = geohash.encode(location.latitude, location.longitude);
                }

                locations.push(location);
            }
        }
    }

    if (typeof count === 'number') {
        locations = locations.slice(count);
    }

    Object.defineProperty(locations, 'locationSet', {
        get: function() {
            buildObjectFromKeyedArray(this);
        }
    });

    return locations;
}

function orderResults(locationSetOriginal, point, queryDistance, withDistances, withCoordinates, withHashes, order, precise, units) {

    var orderedLocations = geolib.orderByDistance(point, locationSetOriginal);
    var location;
    var i;

    if (precise) {
        for (i = orderedLocations.length - 1; i > -1; i--) {
            location = orderedLocations[i];
            if (location.distance > queryDistance) {
                orderedLocations = orderedLocations.slice(i);
                break;
            }
        }
    }

    if (withHashes) {
        for (i = 0; i < orderedLocations.length; i++) {
            location = orderedLocations[i];
            location.hash = geohash.encode(location.latitude, location.longitude);
        }
    }

    if (order === 'DESC') {
        orderedLocations = orderedLocations.reverse();
    }

    return orderedLocations;
}

function geohashDecode(hash) {

    var decoded;
    var latlon;

    if (hash !== null) {

        latlon = geohash.decode_int(hash, 52);

        decoded = {
            latitude: latlon.latitude,
            longitude: latlon.longitude
        };

    } else {

        decoded = {
            latitude: null,
            longitude: null
        };
    }

    return decoded;
}

module.exports = EmulatedInterface;
<|endoftext|>


<commit_before>function TeamStatus()
{
	//hold the tab object
	this.tab = null;

	//hold signals for the page
	this._signals = new Array();

	//hold status message for the page
	this._prompt = null;

	//keep track of whether object is initialised
	this._inited = false;

	// connect up the submit event for the form
	this._signals.push(connect( 'team-status-save', 'onclick', bind(this.saveStatus, this)));

	// list of text fields
	this._fields = ['name', 'description', 'feed', 'url'];
}

/* *****	Initialization code	***** */
TeamStatus.prototype.init = function()
{
	if(this._inited == false)
	{
		logDebug("TeamStatus: Initializing");

		/* Initialize a new tab for switchboard - Do this only once */
		this.tab = new Tab( "Team Status" );
		this._signals.push(connect( this.tab, "onfocus", bind( this._onfocus, this ) ));
		this._signals.push(connect( this.tab, "onblur", bind( this._onblur, this ) ));
		this._signals.push(connect( this.tab, "onclickclose", bind( this._close, this ) ));
		this._signals.push(connect( 'upload-helper', "onload", bind( this._uploadHelperLoad, this ) ));
		tabbar.add_tab( this.tab );

		/* Initialise indiviual page elements */
		this._clearFields();
		this.GetStatus();

		/* remember that we are initialised */
		this._inited = true;
	}

	/* now switch to it */
	tabbar.switch_to(this.tab);
}
/* *****	End Initialization Code 	***** */

/* ***** Tab events: onfocus, onblur and close		***** */
TeamStatus.prototype._onfocus = function()
{
	setStyle($("team-status-page"), {'display':'block'});
}

TeamStatus.prototype._onblur = function()
{
	/* Clear any prompts */
	if( this._prompt != null ) {
		this._prompt.close();
		this._prompt = null;
	}
	setStyle($("team-status-page"), {'display':'none'});
}

TeamStatus.prototype._close = function()
{
	/* Clear any prompts */
	if( this._prompt != null ) {
		this._prompt.close();
		this._prompt = null;
	}
	/* Clear class variables */
	this.milestone = null;
	this.events = null;

	/* Disconnect all signals */
	for(var i = 0; i < this._signals; i++) {
		disconnect(this._signals[i]);
	}
	this._signals = new Array();

	/* Close tab */
	this.tab.close();
	this._inited = false;

	/* hide switchboard page */
	setStyle($("team-status-page"), {'display':'none'});
}
/* *****	End Tab events		***** */

/* *****	Field Handling		***** */
TeamStatus.prototype._getField = function(name)
{
	return $('team-status-'+name+'-input');
}
TeamStatus.prototype._setReviewState = function(reviewed)
{
	for (var i=0; i < this._fields.length; i++) {
		var field = this._fields[i];
		if ( field in reviewed ) {
			addElementClass(this._getField(field), reviewed[field] ? 'valid' : 'rejected');
		} else {
			removeElementClass(this._getField(field), 'valid');
			removeElementClass(this._getField(field), 'rejected');
		}
	}
}
TeamStatus.prototype._setFields = function(data)
{
	for (var i=0; i < this._fields.length; i++)
	{
		var field = this._fields[i];
		this._getField(field).value = data[field] || '';
	}
}
TeamStatus.prototype._clearFields = function()
{
	this._setFields({});
	this._setReviewState({});
}
TeamStatus.prototype._getFields = function()
{
	var data = {};
	for (var i=0; i < this._fields.length; i++)
	{
		var field = this._fields[i];
		var value = this._getField(field).value;
		if (!IDE_string_empty(value))	// not null or whitespace
		{
			data[field] = value;
		}
	}
	return data;
}
/* *****	End Field Handling		***** */

/* *****	Team status fetch code	***** */
TeamStatus.prototype._receiveGetStatus = function(nodes)
{
	if (nodes.error)
	{
		this._errorGetStatus();
		return;
	}
	this._setFields(nodes.items);
	this._setReviewState(nodes.reviewed);
}
TeamStatus.prototype._errorGetStatus = function()
{
	this._clearFields();
	this._prompt = status_msg("Unable to get team status", LEVEL_ERROR);
	logDebug("TeamStatus: Failed to retrieve info");
	return;
}
TeamStatus.prototype.GetStatus = function()
{
	logDebug("TeamStatus: Retrieving team status");
	IDE_backend_request("team/status-get", { team: team },
	                    bind(this._receiveGetStatus, this),
	                    bind(this._errorGetStatus, this));
}
/* *****    End Team Status fetch code	***** */

/* *****    Hidden iframe Wrapping	***** */
TeamStatus.prototype._uploadHelperLoad = function(ev)
{
	var frame = ev.src();
	// If you serve json to Firefox then it shows you a download box, so we need to serve it as text.
	// This means that it then wraps it in <pre>, which we need to get the contents of.
	var content = frame.contentWindow.document.body.textContent;
	logDebug('iframe content: '+content);
	IDE_handle_backend_response(content, {},
	                            bind(this._receivePutStatusImage, this),
	                            bind(this._errorPutStatusImage, this));
}
TeamStatus.prototype._receivePutStatusImage = function(nodes)
{
	if (nodes.error)
	{
		this._errorPutStatus();
		return;
	}
	this._prompt = status_msg("Saved robot image successfully", LEVEL_OK);
}
TeamStatus.prototype._errorPutStatusImage = function()
{
	this._prompt = status_msg("Unable to save robot image", LEVEL_ERROR);
	logDebug("TeamStatus: Failed to upload robot image");
	return;
}
/* *****    End Hidden iframe Wrapping	***** */

/* *****    Team Status save code	***** */

TeamStatus.prototype._receivePutStatus = function(nodes)
{
	if (nodes.error)
	{
		this._errorPutStatus();
		return;
	}
	this._prompt = status_msg("Saved team status successfully", LEVEL_OK);
	this.GetStatus();
}
TeamStatus.prototype._errorPutStatus = function()
{
	this._prompt = status_msg("Unable to save team status", LEVEL_ERROR);
	logDebug("TeamStatus: Failed to put info");
	return;
}
TeamStatus.prototype._putStatus = function()
{
	var data = this._getFields();
	data.team = team;
	logDebug("TeamStatus: saving team status");
	IDE_backend_request("team/status-put", data,
	                    bind(this._receivePutStatus, this),
	                    bind(this._errorPutStatus, this));
	this._setReviewState({});
}
TeamStatus.prototype.saveStatus = function()
{
	// TODO: up-to-date checking?
	this._putStatus();

	var imageInput = this._getField('image');
	if (!IDE_string_empty(imageInput.value))	// not null or whitespace
	{
		// TODO: verify that it looks like an image
		$('team-status-image-command').value = 'team/status-put-image';
		$('team-status-image-team').value = team;
		$('team-status-image-upload-form').submit();
	}
}
/* *****    End Team Status save code	***** */
<commit_msg>Clear the file field after a successful upload.<commit_after>function TeamStatus()
{
	//hold the tab object
	this.tab = null;

	//hold signals for the page
	this._signals = new Array();

	//hold status message for the page
	this._prompt = null;

	//keep track of whether object is initialised
	this._inited = false;

	// connect up the submit event for the form
	this._signals.push(connect( 'team-status-save', 'onclick', bind(this.saveStatus, this)));

	// list of text fields
	this._fields = ['name', 'description', 'feed', 'url'];
}

/* *****	Initialization code	***** */
TeamStatus.prototype.init = function()
{
	if(this._inited == false)
	{
		logDebug("TeamStatus: Initializing");

		/* Initialize a new tab for switchboard - Do this only once */
		this.tab = new Tab( "Team Status" );
		this._signals.push(connect( this.tab, "onfocus", bind( this._onfocus, this ) ));
		this._signals.push(connect( this.tab, "onblur", bind( this._onblur, this ) ));
		this._signals.push(connect( this.tab, "onclickclose", bind( this._close, this ) ));
		this._signals.push(connect( 'upload-helper', "onload", bind( this._uploadHelperLoad, this ) ));
		tabbar.add_tab( this.tab );

		/* Initialise indiviual page elements */
		this._clearFields();
		this.GetStatus();

		/* remember that we are initialised */
		this._inited = true;
	}

	/* now switch to it */
	tabbar.switch_to(this.tab);
}
/* *****	End Initialization Code 	***** */

/* ***** Tab events: onfocus, onblur and close		***** */
TeamStatus.prototype._onfocus = function()
{
	setStyle($("team-status-page"), {'display':'block'});
}

TeamStatus.prototype._onblur = function()
{
	/* Clear any prompts */
	if( this._prompt != null ) {
		this._prompt.close();
		this._prompt = null;
	}
	setStyle($("team-status-page"), {'display':'none'});
}

TeamStatus.prototype._close = function()
{
	/* Clear any prompts */
	if( this._prompt != null ) {
		this._prompt.close();
		this._prompt = null;
	}
	/* Clear class variables */
	this.milestone = null;
	this.events = null;

	/* Disconnect all signals */
	for(var i = 0; i < this._signals; i++) {
		disconnect(this._signals[i]);
	}
	this._signals = new Array();

	/* Close tab */
	this.tab.close();
	this._inited = false;

	/* hide switchboard page */
	setStyle($("team-status-page"), {'display':'none'});
}
/* *****	End Tab events		***** */

/* *****	Field Handling		***** */
TeamStatus.prototype._getField = function(name)
{
	return $('team-status-'+name+'-input');
}
TeamStatus.prototype._setReviewState = function(reviewed)
{
	for (var i=0; i < this._fields.length; i++) {
		var field = this._fields[i];
		if ( field in reviewed ) {
			addElementClass(this._getField(field), reviewed[field] ? 'valid' : 'rejected');
		} else {
			removeElementClass(this._getField(field), 'valid');
			removeElementClass(this._getField(field), 'rejected');
		}
	}
}
TeamStatus.prototype._setFields = function(data)
{
	for (var i=0; i < this._fields.length; i++)
	{
		var field = this._fields[i];
		this._getField(field).value = data[field] || '';
	}
}
TeamStatus.prototype._clearFields = function()
{
	this._setFields({});
	this._setReviewState({});
}
TeamStatus.prototype._getFields = function()
{
	var data = {};
	for (var i=0; i < this._fields.length; i++)
	{
		var field = this._fields[i];
		var value = this._getField(field).value;
		if (!IDE_string_empty(value))	// not null or whitespace
		{
			data[field] = value;
		}
	}
	return data;
}
/* *****	End Field Handling		***** */

/* *****	Team status fetch code	***** */
TeamStatus.prototype._receiveGetStatus = function(nodes)
{
	if (nodes.error)
	{
		this._errorGetStatus();
		return;
	}
	this._setFields(nodes.items);
	this._setReviewState(nodes.reviewed);
}
TeamStatus.prototype._errorGetStatus = function()
{
	this._clearFields();
	this._prompt = status_msg("Unable to get team status", LEVEL_ERROR);
	logDebug("TeamStatus: Failed to retrieve info");
	return;
}
TeamStatus.prototype.GetStatus = function()
{
	logDebug("TeamStatus: Retrieving team status");
	IDE_backend_request("team/status-get", { team: team },
	                    bind(this._receiveGetStatus, this),
	                    bind(this._errorGetStatus, this));
}
/* *****    End Team Status fetch code	***** */

/* *****    Hidden iframe Wrapping	***** */
TeamStatus.prototype._uploadHelperLoad = function(ev)
{
	var frame = ev.src();
	// If you serve json to Firefox then it shows you a download box, so we need to serve it as text.
	// This means that it then wraps it in <pre>, which we need to get the contents of.
	var content = frame.contentWindow.document.body.textContent;
	logDebug('iframe content: '+content);
	IDE_handle_backend_response(content, {},
	                            bind(this._receivePutStatusImage, this),
	                            bind(this._errorPutStatusImage, this));
}
TeamStatus.prototype._receivePutStatusImage = function(nodes)
{
	if (nodes.error)
	{
		this._errorPutStatus();
		return;
	}
	this._prompt = status_msg("Saved robot image successfully", LEVEL_OK);
	this._getField('image').value = '';
}
TeamStatus.prototype._errorPutStatusImage = function()
{
	this._prompt = status_msg("Unable to save robot image", LEVEL_ERROR);
	logDebug("TeamStatus: Failed to upload robot image");
	return;
}
/* *****    End Hidden iframe Wrapping	***** */

/* *****    Team Status save code	***** */

TeamStatus.prototype._receivePutStatus = function(nodes)
{
	if (nodes.error)
	{
		this._errorPutStatus();
		return;
	}
	this._prompt = status_msg("Saved team status successfully", LEVEL_OK);
	this.GetStatus();
}
TeamStatus.prototype._errorPutStatus = function()
{
	this._prompt = status_msg("Unable to save team status", LEVEL_ERROR);
	logDebug("TeamStatus: Failed to put info");
	return;
}
TeamStatus.prototype._putStatus = function()
{
	var data = this._getFields();
	data.team = team;
	logDebug("TeamStatus: saving team status");
	IDE_backend_request("team/status-put", data,
	                    bind(this._receivePutStatus, this),
	                    bind(this._errorPutStatus, this));
	this._setReviewState({});
}
TeamStatus.prototype.saveStatus = function()
{
	// TODO: up-to-date checking?
	this._putStatus();

	var imageInput = this._getField('image');
	if (!IDE_string_empty(imageInput.value))	// not null or whitespace
	{
		// TODO: verify that it looks like an image
		$('team-status-image-command').value = 'team/status-put-image';
		$('team-status-image-team').value = team;
		$('team-status-image-upload-form').submit();
	}
}
/* *****    End Team Status save code	***** */
<|endoftext|>


<commit_before>'use strict';

/**
 * The post processing step that steps through all currently indexed assets
 * and deletes every asset that was not indexed during this run of the update to
 * the index.
 */

const Q = require('q');
const es = require('../../collections-online/lib/services/elasticsearch');
const _ = require('lodash');

module.exports = function(state) {
  var activity = 'Post-processing to delete removed assets';

  console.log('\n=== ' + activity + ' ===');

  if (!['all', 'catalog', 'single'].includes(state.mode)) {
    console.log('Removed assets gets deleted only in "all", "catalog" or ' +
                '"single" mode.');
    return state;
  }

  let deletedAssetIdsPromise;

  if (state.mode === 'all') {
    // In 'all' mode we just check the full list of items we indexed,
    // and remove all the assets and series in elasticsearch that are
    // not in our list -- we did a full re-index, so all of these can
    // be removed.
    const deletedAssetIds = [];

    const indexedIds = state.queries
      .filter((query) => query.offset === 0)
      .map((query) => query.indexedIds)
      .reduce((a, b) => a.concat(b), []);

    deletedAssetIdsPromise = es.scrollSearch({
      'query': {
        'bool': {
          'must_not': {
            'ids': {
              'values': indexedIds
            }
          }
        }
      }
    }, function(deletedEntry) {
      deletedAssetIds.push(deletedEntry._id);
    }).then(function() {
      return deletedAssetIds;
    });
  }
  else if (state.mode === 'catalog') {
    deletedAssetIdsPromise = state.queries.reduce((deletedAssetIdsPromise, query) => {
      console.log('Indexed', query.indexedIds.length, 'assets and series from', query.catalogAlias);
      return deletedAssetIdsPromise.then(deletedAssetIds => {
        if (query.offset > 0) {
          console.log('Skipping a query that had a non-zero offset.');
          return deletedAssetIds;
        }
        // Scroll search for all assets in the catalog that was not indexed.
        return es.scrollSearch({
          'query': {
            'bool': {
              'must': {
                'match': {
                  'catalog.raw': query.catalogAlias
                }
              },
              'must_not': {
                'ids': {
                  'values': query.indexedIds.filter((id) => !id.startsWith('series/')),
                }
              }
            }
          }
        }, function(deletedAsset) {
          deletedAssetIds.push(deletedAsset._id);
        }).then(function() {
          return deletedAssetIds;
        });
      });
    }, new Q([]));
  }
  else {
    const deletedAssetIds = state.queries.reduce((deletedAssetIds, query) => {
      const assetIds = query.assetIds
        .filter((assetId) => !assetId.startsWith('series/'))
        .map((assetId) => query.catalogAlias + '-' + assetId);

      const indexedAssetIds = query.indexedIds
        .filter((id) => !id.startsWith('series/'));

      const moreDeletedAssetIds = _.difference(assetIds, indexedAssetIds);
      return _.union(deletedAssetIds, moreDeletedAssetIds);
    }, []);

    deletedAssetIdsPromise = new Q(deletedAssetIds);
  }

  return Q.when(deletedAssetIdsPromise).then(deletedAssetIds => {
    console.log('Deleting', deletedAssetIds.length, 'asset(s)');
    var actions = deletedAssetIds.map(deletedAssetId => {
      return {delete: {_id: deletedAssetId}};
    });
    if (actions.length > 0) {
      return es.bulk({
        index: state.context.index,
        body: actions
      });
    }
  }).then(function() {
    return state;
  });
};
<commit_msg>Edge cases in cleanup after all indexing<commit_after>'use strict';

/**
 * The post processing step that steps through all currently indexed assets
 * and deletes every asset that was not indexed during this run of the update to
 * the index.
 */

const Q = require('q');
const es = require('../../collections-online/lib/services/elasticsearch');
const _ = require('lodash');

module.exports = function(state) {
  var activity = 'Post-processing to delete removed assets';

  console.log('\n=== ' + activity + ' ===');

  if (!['all', 'catalog', 'single'].includes(state.mode)) {
    console.log('Removed assets gets deleted only in "all", "catalog" or ' +
                '"single" mode.');
    return state;
  }

  let deletedAssetIdsPromise;

  if (state.mode === 'all') {
    // In 'all' mode we just check the full list of items we indexed,
    // and remove all the assets and series in elasticsearch that are
    // not in our list -- we did a full re-index, so all of these can
    // be removed.
    const deletedAssetIds = [];

    const indexedIds = state.queries
      // If query is falsy (undefined or 0) there is no offset
      // and we want to grab it and get its indexed IDs
      .filter((query) => !query.offset)
      .map((query) => query.indexedIds)
      .reduce((a, b) => a.concat(b), []);

    deletedAssetIdsPromise = es.scrollSearch({
      'query': {
        'bool': {
          'must_not': {
            'ids': {
              'values': indexedIds
            }
          }
        }
      }
    }, function(deletedEntry) {
      deletedAssetIds.push(deletedEntry._id);
    }).then(function() {
      return deletedAssetIds;
    });
  }
  else if (state.mode === 'catalog') {
    deletedAssetIdsPromise = state.queries.reduce((deletedAssetIdsPromise, query) => {
      console.log('Indexed', query.indexedIds.length, 'assets and series from', query.catalogAlias);
      return deletedAssetIdsPromise.then(deletedAssetIds => {
        if (query.offset > 0) {
          console.log('Skipping a query that had a non-zero offset.');
          return deletedAssetIds;
        }
        // Scroll search for all assets in the catalog that was not indexed.
        return es.scrollSearch({
          'query': {
            'bool': {
              'must': {
                'match': {
                  'catalog.raw': query.catalogAlias
                }
              },
              'must_not': {
                'ids': {
                  'values': query.indexedIds.filter((id) => !id.startsWith('series/')),
                }
              }
            }
          }
        }, function(deletedAsset) {
          deletedAssetIds.push(deletedAsset._id);
        }).then(function() {
          return deletedAssetIds;
        });
      });
    }, new Q([]));
  }
  else {
    const deletedAssetIds = state.queries.reduce((deletedAssetIds, query) => {
      const assetIds = query.assetIds
        .filter((assetId) => !assetId.startsWith('series/'))
        .map((assetId) => query.catalogAlias + '-' + assetId);

      const indexedAssetIds = query.indexedIds
        .filter((id) => !id.startsWith('series/'));

      const moreDeletedAssetIds = _.difference(assetIds, indexedAssetIds);
      return _.union(deletedAssetIds, moreDeletedAssetIds);
    }, []);

    deletedAssetIdsPromise = new Q(deletedAssetIds);
  }

  return Q.when(deletedAssetIdsPromise).then(deletedAssetIds => {
    console.log('Deleting', deletedAssetIds.length, 'asset(s)');
    var actions = deletedAssetIds.map(deletedAssetId => {
      if(deletedAssetId.startsWith('series/')) {
        return {delete: {_type: 'series', _id: deletedAssetId}};
      }
      return {delete: {_type: 'asset', _id: deletedAssetId}};
    });
    if (actions.length > 0) {
      return es.bulk({
        index: state.context.index,
        body: actions
      });
    }
  }).then(function() {
    return state;
  });
};
<|endoftext|>


<commit_before>import utils from "./utils";
import Renderer from "./renderer";

const SVGRenderer = function(boardElement, { hooks, options }) {
  Renderer.call(this, boardElement, { hooks: hooks, options: options });
  utils.addClass(boardElement, "tenuki-svg-renderer");
};

SVGRenderer.prototype = Object.create(Renderer.prototype);
SVGRenderer.prototype.constructor = SVGRenderer;

const constructSVG = function(renderer, boardState, { hasCoordinates, smallerStones, texturedStones }) {
  const svg = utils.createSVGElement("svg");
  const defs = utils.createSVGElement("defs");
  utils.appendElement(svg, defs);

  const blackGradient = utils.createSVGElement("radialGradient", {
    attributes: {
      id: renderer.blackGradientID,
      cy: "0",
      r: "55%"
    }
  });
  utils.appendElement(blackGradient, utils.createSVGElement("stop", {
    attributes: {
      offset: "0%",
      "stop-color": "#848484"
    }
  }));
  utils.appendElement(blackGradient, utils.createSVGElement("stop", {
    attributes: {
      offset: "100%",
      "stop-color": "hsl(0, 0%, 20%)"
    }
  }));
  utils.appendElement(defs, blackGradient);

  const whiteGradient = utils.createSVGElement("radialGradient", {
    attributes: {
      id: renderer.whiteGradientID,
      cy: "0",
      r: "70%"
    }
  });
  utils.appendElement(whiteGradient, utils.createSVGElement("stop", {
    attributes: {
      offset: "0%",
      "stop-color": "white"
    }
  }));
  utils.appendElement(whiteGradient, utils.createSVGElement("stop", {
    attributes: {
      offset: "100%",
      "stop-color": "#DDDDDD"
    }
  }));
  utils.appendElement(defs, whiteGradient);

  const contentsContainer = utils.createSVGElement("g", {
    attributes: {
      class: "contents",
      transform: `translate(${renderer.MARGIN}, ${renderer.MARGIN})`
    }
  });
  utils.appendElement(svg, contentsContainer);

  const lines = utils.createSVGElement("g", {
    attributes: {
      class: "lines"
    }
  });
  utils.appendElement(contentsContainer, lines);

  for (let y = 0; y < boardState.boardSize - 1; y++) {
    for (let x = 0; x < boardState.boardSize - 1; x++) {
      const lineBox = utils.createSVGElement("rect", {
        attributes: {
          y: y * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
          x: x * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
          width: renderer.INTERSECTION_GAP_SIZE + 1,
          height: renderer.INTERSECTION_GAP_SIZE + 1,
          class: "line-box"
        }
      });

      utils.appendElement(lines, lineBox);
    }
  }

  const hoshiPoints = utils.createSVGElement("g", { attributes: { class: "hoshi" }});
  utils.appendElement(contentsContainer, hoshiPoints);

  Renderer.hoshiPositionsFor(boardState.boardSize).forEach(h => {
    const hoshi = utils.createSVGElement("circle", {
      attributes: {
        class: "hoshi",
        cy: h.top * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
        cx: h.left * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
        r: 2
      }
    });

    utils.appendElement(hoshiPoints, hoshi);
  });

  const intersections = utils.createSVGElement("g", { attributes: { class: "intersections" }});
  utils.appendElement(contentsContainer, intersections);

  if (hasCoordinates) {
    const coordinateContainer = utils.createSVGElement("g", {
      attributes: {
        class: "coordinates",
        transform: `translate(${renderer.MARGIN}, ${renderer.MARGIN})`
      }
    });

    for (let y = 0; y < boardState.boardSize; y++) {
      // TODO: 16 is for the rendered height _on my browser_. not reliable...

      [16/2 + 1 - (16 + 16/2 + 16/(2*2) + 16/(2*2*2)), 16/2 + 1 + (16 + 16/2) + (boardState.boardSize - 1)*(renderer.INTERSECTION_GAP_SIZE + 1)].forEach(verticalOffset => {
        utils.appendElement(coordinateContainer, utils.createSVGElement("text", {
          text: boardState.xCoordinateFor(y),
          attributes: {
            "text-anchor": "middle",
            y: verticalOffset - 0.5,
            x: y * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5
          }
        }));
      });

      [-1*(16 + 16/2 + 16/(2*2)), (16 + 16/2 + 16/(2*2)) + (boardState.boardSize - 1)*(renderer.INTERSECTION_GAP_SIZE + 1)].forEach(horizontalOffset => {
        utils.appendElement(coordinateContainer, utils.createSVGElement("text", {
          text: boardState.yCoordinateFor(y),
          attributes: {
            "text-anchor": "middle",
            y: y * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5 + 16/(2*2),
            x: horizontalOffset - 0.5
          }
        }));
      });

      utils.appendElement(svg, coordinateContainer);
    }
  }

  for (let y = 0; y < boardState.boardSize; y++) {
    for (let x = 0; x < boardState.boardSize; x++) {
      const intersectionGroup = utils.createSVGElement("g", {
        attributes: {
          class: "intersection"
        }
      });
      intersectionGroup.setAttribute("data-intersection-y", y);
      intersectionGroup.setAttribute("data-intersection-x", x);
      utils.appendElement(intersections, intersectionGroup);

      const intersectionInnerContainer = utils.createSVGElement("g", {
        attributes: {
          class: "intersection-inner-container"
        }
      });
      utils.appendElement(intersectionGroup, intersectionInnerContainer);

      const intersectionBox = utils.createSVGElement("rect", {
        attributes: {
          y: y * (renderer.INTERSECTION_GAP_SIZE + 1) - renderer.INTERSECTION_GAP_SIZE/2 - 0.5,
          x: x * (renderer.INTERSECTION_GAP_SIZE + 1) - renderer.INTERSECTION_GAP_SIZE/2 - 0.5,
          width: renderer.INTERSECTION_GAP_SIZE,
          height: renderer.INTERSECTION_GAP_SIZE
        }
      });
      utils.appendElement(intersectionInnerContainer, intersectionBox);

      let stoneRadius = renderer.INTERSECTION_GAP_SIZE / 2;

      if (smallerStones) {
        stoneRadius -= 1;
      }

      const stoneAttributes = {
        class: "stone",
        cy: y * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
        cx: x * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
        r: stoneRadius
      };

      if (texturedStones) {
        utils.appendElement(intersectionInnerContainer, utils.createSVGElement("circle", {
          attributes: {
            class: "stone-shadow",
            cy: stoneAttributes["cy"] + 2,
            cx: stoneAttributes["cx"],
            r: stoneRadius
          }
        }));
      }

      const intersection = utils.createSVGElement("circle", {
        attributes: stoneAttributes
      });
      utils.appendElement(intersectionInnerContainer, intersection);

      utils.appendElement(intersectionInnerContainer, utils.createSVGElement("circle", {
        attributes: {
          class: "marker",
          cy: y * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
          cx: x * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
          r: 4.5
        }
      }));

      utils.appendElement(intersectionInnerContainer, utils.createSVGElement("rect", {
        attributes: {
          class: "ko-marker",
          y: y * (renderer.INTERSECTION_GAP_SIZE + 1) - 6 - 0.5,
          x: x * (renderer.INTERSECTION_GAP_SIZE + 1) - 6 - 0.5,
          width: 12,
          height: 12
        }
      }));

      utils.appendElement(intersectionInnerContainer, utils.createSVGElement("rect", {
        attributes: {
          class: "territory-marker",
          y: y * (renderer.INTERSECTION_GAP_SIZE + 1) - 6,
          x: x * (renderer.INTERSECTION_GAP_SIZE + 1) - 6,
          width: 11,
          height: 11
        }
      }));
    }
  }

  return svg;
};

SVGRenderer.prototype.generateBoard = function(boardState, { hasCoordinates, smallerStones, texturedStones }) {
  this.blackGradientID = utils.randomID("black-gradient");
  this.whiteGradientID = utils.randomID("white-gradient");

  const svg = constructSVG(this, boardState, { hasCoordinates, smallerStones, texturedStones });

  this.svgElement = svg;
  this.svgElement.setAttribute("height", this.BOARD_LENGTH);
  this.svgElement.setAttribute("width", this.BOARD_LENGTH);

  const intersectionGroups = svg.querySelectorAll(".intersection");
  intersectionGroups.forEach((intersectionGroup) => {
    const y = Number(intersectionGroup.getAttribute("data-intersection-y"));
    const x = Number(intersectionGroup.getAttribute("data-intersection-x"));

    this.grid[y] = this.grid[y] || [];
    this.grid[y][x] = intersectionGroup;

    this.addIntersectionEventListeners(intersectionGroup, y, x);
  });

  return svg;
};

SVGRenderer.prototype.computeSizing = function() {
  Renderer.prototype.computeSizing.call(this);

  // In addition to the will-change re-raster in Renderer,
  // the SVG element appears to sometimes need this to
  // prevent blurriness on resize.
  this.svgElement.style.transform = "none";

  window.requestAnimationFrame(() => {
    this.svgElement.style.transform = "";
  });
};

SVGRenderer.prototype.setIntersectionClasses = function(intersectionEl, intersection, classes) {
  if (intersectionEl.getAttribute("class") !== classes.join(" ")) {
    intersectionEl.setAttribute("class", classes.join(" "));
  }

  if (this.texturedStones) {
    if (intersection.isEmpty()) {
      intersectionEl.querySelector(".stone").setAttribute("style", "");
    } else {
      intersectionEl.querySelector(".stone").setAttribute("style", "fill: url(" + window.location + "#" + this[intersection.value + "GradientID"] + ")");
    }
  }
};

export default SVGRenderer;
<commit_msg>Bring grid setup back into the main intersection loop<commit_after>import utils from "./utils";
import Renderer from "./renderer";

const SVGRenderer = function(boardElement, { hooks, options }) {
  Renderer.call(this, boardElement, { hooks: hooks, options: options });
  utils.addClass(boardElement, "tenuki-svg-renderer");
};

SVGRenderer.prototype = Object.create(Renderer.prototype);
SVGRenderer.prototype.constructor = SVGRenderer;

const constructSVG = function(renderer, boardState, { hasCoordinates, smallerStones, texturedStones }) {
  const svg = utils.createSVGElement("svg");
  const defs = utils.createSVGElement("defs");
  utils.appendElement(svg, defs);

  const blackGradient = utils.createSVGElement("radialGradient", {
    attributes: {
      id: renderer.blackGradientID,
      cy: "0",
      r: "55%"
    }
  });
  utils.appendElement(blackGradient, utils.createSVGElement("stop", {
    attributes: {
      offset: "0%",
      "stop-color": "#848484"
    }
  }));
  utils.appendElement(blackGradient, utils.createSVGElement("stop", {
    attributes: {
      offset: "100%",
      "stop-color": "hsl(0, 0%, 20%)"
    }
  }));
  utils.appendElement(defs, blackGradient);

  const whiteGradient = utils.createSVGElement("radialGradient", {
    attributes: {
      id: renderer.whiteGradientID,
      cy: "0",
      r: "70%"
    }
  });
  utils.appendElement(whiteGradient, utils.createSVGElement("stop", {
    attributes: {
      offset: "0%",
      "stop-color": "white"
    }
  }));
  utils.appendElement(whiteGradient, utils.createSVGElement("stop", {
    attributes: {
      offset: "100%",
      "stop-color": "#DDDDDD"
    }
  }));
  utils.appendElement(defs, whiteGradient);

  const contentsContainer = utils.createSVGElement("g", {
    attributes: {
      class: "contents",
      transform: `translate(${renderer.MARGIN}, ${renderer.MARGIN})`
    }
  });
  utils.appendElement(svg, contentsContainer);

  const lines = utils.createSVGElement("g", {
    attributes: {
      class: "lines"
    }
  });
  utils.appendElement(contentsContainer, lines);

  for (let y = 0; y < boardState.boardSize - 1; y++) {
    for (let x = 0; x < boardState.boardSize - 1; x++) {
      const lineBox = utils.createSVGElement("rect", {
        attributes: {
          y: y * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
          x: x * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
          width: renderer.INTERSECTION_GAP_SIZE + 1,
          height: renderer.INTERSECTION_GAP_SIZE + 1,
          class: "line-box"
        }
      });

      utils.appendElement(lines, lineBox);
    }
  }

  const hoshiPoints = utils.createSVGElement("g", { attributes: { class: "hoshi" }});
  utils.appendElement(contentsContainer, hoshiPoints);

  Renderer.hoshiPositionsFor(boardState.boardSize).forEach(h => {
    const hoshi = utils.createSVGElement("circle", {
      attributes: {
        class: "hoshi",
        cy: h.top * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
        cx: h.left * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
        r: 2
      }
    });

    utils.appendElement(hoshiPoints, hoshi);
  });

  const intersections = utils.createSVGElement("g", { attributes: { class: "intersections" }});
  utils.appendElement(contentsContainer, intersections);

  if (hasCoordinates) {
    const coordinateContainer = utils.createSVGElement("g", {
      attributes: {
        class: "coordinates",
        transform: `translate(${renderer.MARGIN}, ${renderer.MARGIN})`
      }
    });

    for (let y = 0; y < boardState.boardSize; y++) {
      // TODO: 16 is for the rendered height _on my browser_. not reliable...

      [16/2 + 1 - (16 + 16/2 + 16/(2*2) + 16/(2*2*2)), 16/2 + 1 + (16 + 16/2) + (boardState.boardSize - 1)*(renderer.INTERSECTION_GAP_SIZE + 1)].forEach(verticalOffset => {
        utils.appendElement(coordinateContainer, utils.createSVGElement("text", {
          text: boardState.xCoordinateFor(y),
          attributes: {
            "text-anchor": "middle",
            y: verticalOffset - 0.5,
            x: y * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5
          }
        }));
      });

      [-1*(16 + 16/2 + 16/(2*2)), (16 + 16/2 + 16/(2*2)) + (boardState.boardSize - 1)*(renderer.INTERSECTION_GAP_SIZE + 1)].forEach(horizontalOffset => {
        utils.appendElement(coordinateContainer, utils.createSVGElement("text", {
          text: boardState.yCoordinateFor(y),
          attributes: {
            "text-anchor": "middle",
            y: y * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5 + 16/(2*2),
            x: horizontalOffset - 0.5
          }
        }));
      });

      utils.appendElement(svg, coordinateContainer);
    }
  }

  for (let y = 0; y < boardState.boardSize; y++) {
    for (let x = 0; x < boardState.boardSize; x++) {
      const intersectionGroup = utils.createSVGElement("g", {
        attributes: {
          class: "intersection"
        }
      });
      intersectionGroup.setAttribute("data-intersection-y", y);
      intersectionGroup.setAttribute("data-intersection-x", x);
      utils.appendElement(intersections, intersectionGroup);

      const intersectionInnerContainer = utils.createSVGElement("g", {
        attributes: {
          class: "intersection-inner-container"
        }
      });
      utils.appendElement(intersectionGroup, intersectionInnerContainer);

      const intersectionBox = utils.createSVGElement("rect", {
        attributes: {
          y: y * (renderer.INTERSECTION_GAP_SIZE + 1) - renderer.INTERSECTION_GAP_SIZE/2 - 0.5,
          x: x * (renderer.INTERSECTION_GAP_SIZE + 1) - renderer.INTERSECTION_GAP_SIZE/2 - 0.5,
          width: renderer.INTERSECTION_GAP_SIZE,
          height: renderer.INTERSECTION_GAP_SIZE
        }
      });
      utils.appendElement(intersectionInnerContainer, intersectionBox);

      let stoneRadius = renderer.INTERSECTION_GAP_SIZE / 2;

      if (smallerStones) {
        stoneRadius -= 1;
      }

      const stoneAttributes = {
        class: "stone",
        cy: y * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
        cx: x * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
        r: stoneRadius
      };

      if (texturedStones) {
        utils.appendElement(intersectionInnerContainer, utils.createSVGElement("circle", {
          attributes: {
            class: "stone-shadow",
            cy: stoneAttributes["cy"] + 2,
            cx: stoneAttributes["cx"],
            r: stoneRadius
          }
        }));
      }

      const intersection = utils.createSVGElement("circle", {
        attributes: stoneAttributes
      });
      utils.appendElement(intersectionInnerContainer, intersection);

      utils.appendElement(intersectionInnerContainer, utils.createSVGElement("circle", {
        attributes: {
          class: "marker",
          cy: y * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
          cx: x * (renderer.INTERSECTION_GAP_SIZE + 1) - 0.5,
          r: 4.5
        }
      }));

      utils.appendElement(intersectionInnerContainer, utils.createSVGElement("rect", {
        attributes: {
          class: "ko-marker",
          y: y * (renderer.INTERSECTION_GAP_SIZE + 1) - 6 - 0.5,
          x: x * (renderer.INTERSECTION_GAP_SIZE + 1) - 6 - 0.5,
          width: 12,
          height: 12
        }
      }));

      utils.appendElement(intersectionInnerContainer, utils.createSVGElement("rect", {
        attributes: {
          class: "territory-marker",
          y: y * (renderer.INTERSECTION_GAP_SIZE + 1) - 6,
          x: x * (renderer.INTERSECTION_GAP_SIZE + 1) - 6,
          width: 11,
          height: 11
        }
      }));

      renderer.grid[y] = renderer.grid[y] || [];
      renderer.grid[y][x] = intersectionGroup;

      renderer.addIntersectionEventListeners(intersectionGroup, y, x);
    }
  }

  return svg;
};

SVGRenderer.prototype.generateBoard = function(boardState, { hasCoordinates, smallerStones, texturedStones }) {
  this.blackGradientID = utils.randomID("black-gradient");
  this.whiteGradientID = utils.randomID("white-gradient");

  const svg = constructSVG(this, boardState, { hasCoordinates, smallerStones, texturedStones });

  this.svgElement = svg;
  this.svgElement.setAttribute("height", this.BOARD_LENGTH);
  this.svgElement.setAttribute("width", this.BOARD_LENGTH);

  return svg;
};

SVGRenderer.prototype.computeSizing = function() {
  Renderer.prototype.computeSizing.call(this);

  // In addition to the will-change re-raster in Renderer,
  // the SVG element appears to sometimes need this to
  // prevent blurriness on resize.
  this.svgElement.style.transform = "none";

  window.requestAnimationFrame(() => {
    this.svgElement.style.transform = "";
  });
};

SVGRenderer.prototype.setIntersectionClasses = function(intersectionEl, intersection, classes) {
  if (intersectionEl.getAttribute("class") !== classes.join(" ")) {
    intersectionEl.setAttribute("class", classes.join(" "));
  }

  if (this.texturedStones) {
    if (intersection.isEmpty()) {
      intersectionEl.querySelector(".stone").setAttribute("style", "");
    } else {
      intersectionEl.querySelector(".stone").setAttribute("style", "fill: url(" + window.location + "#" + this[intersection.value + "GradientID"] + ")");
    }
  }
};

export default SVGRenderer;
<|endoftext|>


<commit_before>/*global require,window,getPortal*/
require.config({
  paths: {
    fetch: 'https://cdnjs.cloudflare.com/ajax/libs/fetch/0.9.0/fetch'
  }
});

if (!window.fetch) {
  var fetch = require('fetch');
}

export default class Client {
  constructor(clientData) {
    this.coreTable = clientData.coreTable;
    this.extGroup = clientData.extGroup;
    this.extTable = clientData.extTable;
    this.coreTableNumber = clientData.coreTableNumber;
    this.foreignKey = (clientData.foreignKey !== undefined ? clientData.foreignKey : undefined);
  }

  /**
   * Returns an object that contains the parameters for the tlist_child_auth.html page
   * @param  {object} record contains column name to column value mapping
   * @return {object}        object that will be used to populate the tlist_child parameters
   */
  _getAuthMetadata(record) {
    if (!record) {
      throw TypeError('required parameter record is undefined');
    } else if (!Object.keys(record).length) {
      throw TypeError('parameter record is an empty object, must contain at least one key-value pair');
    }
    return {
      extGroup: this.extGroup,
      extTable: this.extTable,
      displayCols: Object.keys(record).filter((elem) => {
        return elem !== 'id';
      }).join(','),
      fieldNames: Object.keys(record).filter((elem) => {
        return elem !== 'id';
      }).join(',')
    };
  }

  _getLocation() {
    return window.location.pathname;
  }

  /**
   * Returns the name of the portal the user is currently on
   * by parsing the what's between the first two slashes in the URL
   * @return {string} portal can be: 'admin', 'teachers', 'guardian'
   */
  _getPortal() {
    var location = this._getLocation();
    var portal;
    var pos = location.indexOf('/', 1);
    if (pos > 0) {
      return location.substring(1, pos);
    }
  }

  /**
   * Encodes an object into a URI parameters string
   * @param  {object} obj object to be encoded
   * @return {string}     uri encoded string serialization of obj
   */
  _encodeUri(obj) {
    return Object.keys(obj).map(key => {
      return encodeURIComponent(key) + '=' + encodeURIComponent(obj[key])
    }).join('&');
  }

  /**
   * Convert key name for field to tlist format
   * @param  {string} key        column name
   * @param  {number} [recordId] id of record to updated
   * @return {string}            column name converted to tlist format
   */
  _keyToTlist(key, recordId) {
    if (!key) {
      throw TypeError('required parameter key is undefined');
    }
    return 'CF-[' +
      this.coreTable +
      ':' +
      (this.foreignKey !== undefined ? this.foreignKey : 0) +
      '.' +
      this.extGroup +
      '.' +
      this.extTable +
      ':' +
      (recordId !== undefined ? recordId : '-1') +
      ']' +
      key;
  }

  /**
   * Convert a "plain" object of data, where the column name maps to value,
   * to a new object that maps the form field name as
   * it would be rendered by a tlist_child tag to its value
   * @param  {object} obj object that maps column name to column value
   * @return {object}     object that maps a form field name as it would be rendered by a tlist_child tag to its value
   */
  _objToTlist(obj) {
    let newObj = {};
    let recordId = (obj.id !== undefined ? obj.id : undefined);
    for (let key of Object.keys(obj)) {
      if (key !== 'id') {
        newObj[this._keyToTlist(key, recordId)] = obj[key];
      }
    }
    return newObj;
  }

  /**
   * Converts a simple object of column name to column value mapping
   * to a string that will be sent as a POST request payload
   * @param  {object} record simple object of column name to column value mappings
   * @return {string}        string that will be sent as a POST request payload
   */
  _objToPostStr(record) {
    var acString;
    switch (this._getPortal(this._getLocation())) {
      case 'guardian':
        acString = '&ac=autosendupdate';
        break;
      case 'admin':
        acString = '&ac=prim';
        break;
    }

    return this._encodeUri(this._objToTlist(record)) + acString;
  }

  /**
   * Use the user's current portal to determine the corresponding form action URL
   * @return {string} form action URL
   */
  _getPostUrl() {
    var postUrl;
    switch (this._getPortal(window.location.pathname)) {
      case 'guardian':
        postUrl = '/guardian/changesrecorded.html';
        break;
      case 'admin':
        postUrl = '/admin/changesrecorded.white.html';
        break;
    }
    return postUrl;
  }

  /**
   * Executes a GET request to a page that contains all of the fields that we will be saving,
   * thus authenticating the POST request in save()
   * @param  {object} record simple object of column name to column value mappings
   * @return {Promise}       Resolves when the auth request's full response has arrived
   */
  _auth(record) {
    var authMetadata = this._getAuthMetadata(record);
    var encodedAuthMetadata = this._encodeUri(authMetadata);
    var portal = this._getPortal(window.location.pathname);
    var authUrl = `/${portal}/tlist_child_auth.html?${encodedAuthMetadata}`;

    if (this._getPortal(window.location.pathname) !== 'guardian' && this.coreTableNumber && this.foreignKey) {
      authUrl += `&frn=${this.coreTableNumber}${this.foreignKey}`;
    }
    return window.fetch(authUrl, {
      credentials: 'include'
    }).then(function(rawData) {
      return rawData.text();
    });
  }

  /**
   * Sends a POST request to save (either insert or update) the record
   * @param  {object} record simple object of column name to column value mappings
   * @return {Promise}       Resolves when the POST request full response has arrived.
   * Rejects if the POST request's response contains an Authorization error.
   */
  save(record) {
    var _this = this;
    return this._auth(record)
      .then(function() {
        return window.fetch(_this._getPostUrl(), {
          method: 'post',
          headers: {
            'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
          },
          body: _this._objToPostStr(record),
          credentials: 'include'
        });
      })
      .then(function(saveResp) {
        return saveResp.text();
      })
      .then(function(saveResp) {
        return new Promise(function(resolve, reject) {
          if (saveResp.indexOf('Authorization') !== -1) {
            reject('Request failed');
          } else {
            resolve(saveResp);
          }
        });
      });
  }
}
<commit_msg>Removed require call in src. Let consumers handle that<commit_after>/*global require,window,getPortal*/

export default class Client {
  constructor(clientData) {
    this.coreTable = clientData.coreTable;
    this.extGroup = clientData.extGroup;
    this.extTable = clientData.extTable;
    this.coreTableNumber = clientData.coreTableNumber;
    this.foreignKey = (clientData.foreignKey !== undefined ? clientData.foreignKey : undefined);
  }

  /**
   * Returns an object that contains the parameters for the tlist_child_auth.html page
   * @param  {object} record contains column name to column value mapping
   * @return {object}        object that will be used to populate the tlist_child parameters
   */
  _getAuthMetadata(record) {
    if (!record) {
      throw TypeError('required parameter record is undefined');
    } else if (!Object.keys(record).length) {
      throw TypeError('parameter record is an empty object, must contain at least one key-value pair');
    }
    return {
      extGroup: this.extGroup,
      extTable: this.extTable,
      displayCols: Object.keys(record).filter((elem) => {
        return elem !== 'id';
      }).join(','),
      fieldNames: Object.keys(record).filter((elem) => {
        return elem !== 'id';
      }).join(',')
    };
  }

  _getLocation() {
    return window.location.pathname;
  }

  /**
   * Returns the name of the portal the user is currently on
   * by parsing the what's between the first two slashes in the URL
   * @return {string} portal can be: 'admin', 'teachers', 'guardian'
   */
  _getPortal() {
    var location = this._getLocation();
    var portal;
    var pos = location.indexOf('/', 1);
    if (pos > 0) {
      return location.substring(1, pos);
    }
  }

  /**
   * Encodes an object into a URI parameters string
   * @param  {object} obj object to be encoded
   * @return {string}     uri encoded string serialization of obj
   */
  _encodeUri(obj) {
    return Object.keys(obj).map(key => {
      return encodeURIComponent(key) + '=' + encodeURIComponent(obj[key])
    }).join('&');
  }

  /**
   * Convert key name for field to tlist format
   * @param  {string} key        column name
   * @param  {number} [recordId] id of record to updated
   * @return {string}            column name converted to tlist format
   */
  _keyToTlist(key, recordId) {
    if (!key) {
      throw TypeError('required parameter key is undefined');
    }
    return 'CF-[' +
      this.coreTable +
      ':' +
      (this.foreignKey !== undefined ? this.foreignKey : 0) +
      '.' +
      this.extGroup +
      '.' +
      this.extTable +
      ':' +
      (recordId !== undefined ? recordId : '-1') +
      ']' +
      key;
  }

  /**
   * Convert a "plain" object of data, where the column name maps to value,
   * to a new object that maps the form field name as
   * it would be rendered by a tlist_child tag to its value
   * @param  {object} obj object that maps column name to column value
   * @return {object}     object that maps a form field name as it would be rendered by a tlist_child tag to its value
   */
  _objToTlist(obj) {
    let newObj = {};
    let recordId = (obj.id !== undefined ? obj.id : undefined);
    for (let key of Object.keys(obj)) {
      if (key !== 'id') {
        newObj[this._keyToTlist(key, recordId)] = obj[key];
      }
    }
    return newObj;
  }

  /**
   * Converts a simple object of column name to column value mapping
   * to a string that will be sent as a POST request payload
   * @param  {object} record simple object of column name to column value mappings
   * @return {string}        string that will be sent as a POST request payload
   */
  _objToPostStr(record) {
    var acString;
    switch (this._getPortal(this._getLocation())) {
      case 'guardian':
        acString = '&ac=autosendupdate';
        break;
      case 'admin':
        acString = '&ac=prim';
        break;
    }

    return this._encodeUri(this._objToTlist(record)) + acString;
  }

  /**
   * Use the user's current portal to determine the corresponding form action URL
   * @return {string} form action URL
   */
  _getPostUrl() {
    var postUrl;
    switch (this._getPortal(window.location.pathname)) {
      case 'guardian':
        postUrl = '/guardian/changesrecorded.html';
        break;
      case 'admin':
        postUrl = '/admin/changesrecorded.white.html';
        break;
    }
    return postUrl;
  }

  /**
   * Executes a GET request to a page that contains all of the fields that we will be saving,
   * thus authenticating the POST request in save()
   * @param  {object} record simple object of column name to column value mappings
   * @return {Promise}       Resolves when the auth request's full response has arrived
   */
  _auth(record) {
    var authMetadata = this._getAuthMetadata(record);
    var encodedAuthMetadata = this._encodeUri(authMetadata);
    var portal = this._getPortal(window.location.pathname);
    var authUrl = `/${portal}/tlist_child_auth.html?${encodedAuthMetadata}`;

    if (this._getPortal(window.location.pathname) !== 'guardian' && this.coreTableNumber && this.foreignKey) {
      authUrl += `&frn=${this.coreTableNumber}${this.foreignKey}`;
    }
    return window.fetch(authUrl, {
      credentials: 'include'
    }).then(function(rawData) {
      return rawData.text();
    });
  }

  /**
   * Sends a POST request to save (either insert or update) the record
   * @param  {object} record simple object of column name to column value mappings
   * @return {Promise}       Resolves when the POST request full response has arrived.
   * Rejects if the POST request's response contains an Authorization error.
   */
  save(record) {
    var _this = this;
    return this._auth(record)
      .then(function() {
        return window.fetch(_this._getPostUrl(), {
          method: 'post',
          headers: {
            'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
          },
          body: _this._objToPostStr(record),
          credentials: 'include'
        });
      })
      .then(function(saveResp) {
        return saveResp.text();
      })
      .then(function(saveResp) {
        return new Promise(function(resolve, reject) {
          if (saveResp.indexOf('Authorization') !== -1) {
            reject('Request failed');
          } else {
            resolve(saveResp);
          }
        });
      });
  }
}
<|endoftext|>


<commit_before>var juice = require('juice'),
    Template = require('Template').Template,
    Validation = require('juice/validation').Validation;

var Application = function Application() {
  this.actions = {};
  this.controllers = {};
  this.helpers = {
    source : function( x ) { return x.toSource(); }
  };

  Object.defineProperty(this, 'setupRun', { writable: true, value: false });
}

exports.Application = Application;

Application.prototype.setup = function setup(docRoot) {
  var self = this;
  var closure = function Juice_App_jsgi(env) { return self.run(env) };
  if (this.setupRun)
    return closure;

  for (k in this.actions) {
    this.actions[ k ] = this.buildAction( k, this.actions[ k ] );
  }

  this.docRoot = docRoot;
  this.setupRun = true;

  return closure;
}

Application.prototype.run = function run(env) {
  var app = this;
  try {
    var ctx = new juice.Context(env, app);
    env.juice.docRoot = this.docRoot;

    // Dispatch the url
    var url = env.pathInfo;
    ctx.format = env.headers.accept &&
                 env.headers.accept.match( 'application/json' )
               ? 'json'
               : 'html';

    for (k in ctx.actions) {
      let info = ctx.actions[ k ];

      let re = info.__matcher;

      var res = re(url);

      if (!res)
        continue;

      // We matched! dispatch to the action

      // We assume that re is infact a regexp, so res is an array of the form:
      //
      //  [ 'whole matched text', 'capture1', ... 'captureN' ]
      //
      //  We dont want the whole matched text.
      res.shift();

      return ctx.runAction(res, info);
    }

    // 404!
    return app.notFound(env);
  }
  catch (e) {
    return app.handleError(env, e);
  }
}

/**
 * CSS for built in 404 and 500 error pages.
 */
Application.prototype.errorCss = [
'    * { margin: 0; padding: 0; }',
'    body { background: #eee; color: #000; font: 200 87.5%/1.5 "Lucida Grande", Calibri, sans-serif; }',
'    #overview { background: #79d108; border-bottom: 1.5em solid #90e71d; padding: 1.5em; }',
'    #details { padding: 1.5em; }',
'    h1 { font-size: 1.5em; font-weight: 200; line-height: 1; }',
'    dl { font-family: Consolas, monospace; margin-top: 1.5em; overflow: auto; }',
'    dt { clear: both; float: left; margin-right: 1em; text-align: right; width: 9em; }',
'    dd { float: left; }',
'    ol { font-family: Consolas, monospace; margin: 1.5em; }',
'    code { background: #a6f540; color: #000; font-family: Consolas, monospace; font-size: 1em; padding: 0.125em; }',
'    ol.stacktrace {display: table }',
'    ol.stacktrace li { display: table-row; }',
'    ol.stacktrace span { display: table-cell; }',
'    ol.stacktrace span:first-child { padding-right: 1em; text-align: right; }',
'    ol.stacktrace span:last-child { padding-left: 1em; }'
].join('\n');

/**
 * Template to render when no actions match
 */
Application.prototype.notFoundTemplate = <><![CDATA[<!DOCTYPE html>

  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
  <title>Page not found</title>

Page not found — 404

URL:

[% url %]

Method:

[%env.requestMethod%]

Juice tried to match the URL [% env.pathInfo %] against the
    following patterns, but failed to find a match.

[% FOREACH k IN actions %]

[% k.key %]

[% END -%]

]]></> + "";

Application.prototype.notFound = function(env) {

  var url = env.scheme
          + "://"
          + env.serverName
          + ":"
          + env.serverPort
          + env.scriptName
          + env.pathInfo;

  var tt = new Template();
  return {
    status: 404,
    headers: { contentType : 'text/html' },
    body: [ tt.process(
      this.notFoundTemplate,
      { url: url,
        actions: this.actions,
        env: env,
        css: this.errorCss
      }
    ) ]
  };
}

/**
 * Template to render when handling an action throws an error
 */
Application.prototype.errorTemplate = <><![CDATA[<!DOCTYPE html>

  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
  <title>Server error</title>

[% errorType %] — 500

Error message:

[% e.message || e %]

URL:

[% url %]

Method:

[% env.requestMethod %]

[% IF e.fileName %]

File:

[%e.fileName%]

[% END;
   IF e.lineNumber %]

Line number:

[%e.lineNumber%]

[% END %]

[% IF !frames.length %]

Sorry, no stack trace is avilable

[% ELSE %]

[% FOREACH f IN frames; %]

[%
              IF f.function;
                "$f.function";
              ELSE;
                "Unknown";
              END -%]
          @

            [%-
              IF f.fileName;
                "$f.fileName";
              ELSE;
                "Unknown";
              END;
              IF f.lineNumber;
                ", $f.lineNumber";
              END;
          -%]

[% END %]

[% END %]

]]></> + "";

Application.prototype.handleError = function(env, e) {

  var url = env.scheme
          + "://"
          + env.serverName
          + ":"
          + env.serverPort
          + env.scriptName
          + env.pathInfo;

  var errorType = (e.constructor ? e.constructor.name : false) || "Unknown Error";
  var msg = e.message || e;

  var tt = new Template();
  var data = {
    url: url,
    e: e,
    errorType: errorType,
    env: env,
    css: this.errorCss
  };

  if (e.stack)
    data.frames = juice.Utils.splitErrorStack(e.stack);

  return {
    status: 500,
    headers: { contentType : 'text/html' },
    body: [ tt.process( this.errorTemplate, data ) ]
  };
}

Application.prototype.getAction = function( context, name ) {
  for ( var [k,v] in Iterator( name.split( '.' ) ) ) {
    if ( v in context == false )
      throw new TypeError( name + " cannot be found in the controller" );
    context = context[ v ];
  }
  return context;
}

Application.prototype.buildAction = function( url, action ) {
  if ( typeof action == "function" || typeof action == "string" )
    action = { action : action };

  if ( typeof action.action == "string" ) {
    if ( ! action.render && ! action.redirect )
      action.render = action.action.replace( '.', '/', 'g' ) + '.tt';
    action.action = this.getAction( this.controllers, action.action );
  }

  if ( "__matcher" in action && typeof action.__matcher != "function" )
    throw new TypeError( "action.__matcher is not a function" );

  if ( ! "__matcher" in action == false )
    action.__matcher = new RegExp( "^" + url + "$" );

  return action;
}

Application.prototype.validate = function( form, params ) {
  var data = {};
  // loop through |params|, stick values into |field| key of the spec.
  for (let [field, spec] in Iterator(form)) {
    data[field] = juice.Utils.clone(spec, true);
    // use input: key if it exists, else take key in fields: struct
    if ("input" in spec) data[field].value = params[spec.input];
    else if (field in params) data[field].value = params[field];
  }

  return (new Validation(data)).validate();
}

<commit_msg>Create an empty models dict on the application<commit_after>var juice = require('juice'),
    Template = require('Template').Template,
    Validation = require('juice/validation').Validation;

var Application = function Application() {
  this.actions = {};
  this.controllers = {};
  this.models = {};
  this.helpers = {
    source : function( x ) { return x.toSource(); }
  };

  Object.defineProperty(this, 'setupRun', { writable: true, value: false });
}

exports.Application = Application;

Application.prototype.setup = function setup(docRoot) {
  var self = this;
  var closure = function Juice_App_jsgi(env) { return self.run(env) };
  if (this.setupRun)
    return closure;

  for (k in this.actions) {
    this.actions[ k ] = this.buildAction( k, this.actions[ k ] );
  }

  this.docRoot = docRoot;
  this.setupRun = true;

  return closure;
}

Application.prototype.run = function run(env) {
  var app = this;
  try {
    var ctx = new juice.Context(env, app);
    env.juice.docRoot = this.docRoot;

    // Dispatch the url
    var url = env.pathInfo;
    ctx.format = env.headers.accept &&
                 env.headers.accept.match( 'application/json' )
               ? 'json'
               : 'html';

    for (k in ctx.actions) {
      let info = ctx.actions[ k ];

      let re = info.__matcher;

      var res = re(url);

      if (!res)
        continue;

      // We matched! dispatch to the action

      // We assume that re is infact a regexp, so res is an array of the form:
      //
      //  [ 'whole matched text', 'capture1', ... 'captureN' ]
      //
      //  We dont want the whole matched text.
      res.shift();

      return ctx.runAction(res, info);
    }

    // 404!
    return app.notFound(env);
  }
  catch (e) {
    return app.handleError(env, e);
  }
}

/**
 * CSS for built in 404 and 500 error pages.
 */
Application.prototype.errorCss = [
'    * { margin: 0; padding: 0; }',
'    body { background: #eee; color: #000; font: 200 87.5%/1.5 "Lucida Grande", Calibri, sans-serif; }',
'    #overview { background: #79d108; border-bottom: 1.5em solid #90e71d; padding: 1.5em; }',
'    #details { padding: 1.5em; }',
'    h1 { font-size: 1.5em; font-weight: 200; line-height: 1; }',
'    dl { font-family: Consolas, monospace; margin-top: 1.5em; overflow: auto; }',
'    dt { clear: both; float: left; margin-right: 1em; text-align: right; width: 9em; }',
'    dd { float: left; }',
'    ol { font-family: Consolas, monospace; margin: 1.5em; }',
'    code { background: #a6f540; color: #000; font-family: Consolas, monospace; font-size: 1em; padding: 0.125em; }',
'    ol.stacktrace {display: table }',
'    ol.stacktrace li { display: table-row; }',
'    ol.stacktrace span { display: table-cell; }',
'    ol.stacktrace span:first-child { padding-right: 1em; text-align: right; }',
'    ol.stacktrace span:last-child { padding-left: 1em; }'
].join('\n');

/**
 * Template to render when no actions match
 */
Application.prototype.notFoundTemplate = <><![CDATA[<!DOCTYPE html>

  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
  <title>Page not found</title>

Page not found — 404

URL:

[% url %]

Method:

[%env.requestMethod%]

Juice tried to match the URL [% env.pathInfo %] against the
    following patterns, but failed to find a match.

[% FOREACH k IN actions %]

[% k.key %]

[% END -%]

]]></> + "";

Application.prototype.notFound = function(env) {

  var url = env.scheme
          + "://"
          + env.serverName
          + ":"
          + env.serverPort
          + env.scriptName
          + env.pathInfo;

  var tt = new Template();
  return {
    status: 404,
    headers: { contentType : 'text/html' },
    body: [ tt.process(
      this.notFoundTemplate,
      { url: url,
        actions: this.actions,
        env: env,
        css: this.errorCss
      }
    ) ]
  };
}

/**
 * Template to render when handling an action throws an error
 */
Application.prototype.errorTemplate = <><![CDATA[<!DOCTYPE html>

  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
  <title>Server error</title>

[% errorType %] — 500

Error message:

[% e.message || e %]

URL:

[% url %]

Method:

[% env.requestMethod %]

[% IF e.fileName %]

File:

[%e.fileName%]

[% END;
   IF e.lineNumber %]

Line number:

[%e.lineNumber%]

[% END %]

[% IF !frames.length %]

Sorry, no stack trace is avilable

[% ELSE %]

[% FOREACH f IN frames; %]

[%
              IF f.function;
                "$f.function";
              ELSE;
                "Unknown";
              END -%]
          @

            [%-
              IF f.fileName;
                "$f.fileName";
              ELSE;
                "Unknown";
              END;
              IF f.lineNumber;
                ", $f.lineNumber";
              END;
          -%]

[% END %]

[% END %]

]]></> + "";

Application.prototype.handleError = function(env, e) {

  var url = env.scheme
          + "://"
          + env.serverName
          + ":"
          + env.serverPort
          + env.scriptName
          + env.pathInfo;

  var errorType = (e.constructor ? e.constructor.name : false) || "Unknown Error";
  var msg = e.message || e;

  var tt = new Template();
  var data = {
    url: url,
    e: e,
    errorType: errorType,
    env: env,
    css: this.errorCss
  };

  if (e.stack)
    data.frames = juice.Utils.splitErrorStack(e.stack);

  return {
    status: 500,
    headers: { contentType : 'text/html' },
    body: [ tt.process( this.errorTemplate, data ) ]
  };
}

Application.prototype.getAction = function( context, name ) {
  for ( var [k,v] in Iterator( name.split( '.' ) ) ) {
    if ( v in context == false )
      throw new TypeError( name + " cannot be found in the controller" );
    context = context[ v ];
  }
  return context;
}

Application.prototype.buildAction = function( url, action ) {
  if ( typeof action == "function" || typeof action == "string" )
    action = { action : action };

  if ( typeof action.action == "string" ) {
    if ( ! action.render && ! action.redirect )
      action.render = action.action.replace( '.', '/', 'g' ) + '.tt';
    action.action = this.getAction( this.controllers, action.action );
  }

  if ( "__matcher" in action && typeof action.__matcher != "function" )
    throw new TypeError( "action.__matcher is not a function" );

  if ( ! "__matcher" in action == false )
    action.__matcher = new RegExp( "^" + url + "$" );

  return action;
}

Application.prototype.validate = function( form, params ) {
  var data = {};
  // loop through |params|, stick values into |field| key of the spec.
  for (let [field, spec] in Iterator(form)) {
    data[field] = juice.Utils.clone(spec, true);
    // use input: key if it exists, else take key in fields: struct
    if ("input" in spec) data[field].value = params[spec.input];
    else if (field in params) data[field].value = params[field];
  }

  return (new Validation(data)).validate();
}

<|endoftext|>


<commit_before>const labels = {
  success: 'Success',
  error: 'Error',
  ready: 'Ready',
  processing: 'Processing'
};

const icons = {
  success: <Icon icon='check2' />,
  error: <Icon icon='x' />,
  processing: <LoadingSpinner size='1em' overlayColor='transparent' />
}

class Example extends React.Component {

  state = { state: 'ready' }

  onClick = () => {
    if (this.timeout) {
      return;
    }
    this.setState({ state: 'processing'});
    this.timeout1 = setTimeout(() => {
      this.setState({ state: Math.random() > .5 ? 'success' : 'error'});
      this.timeout2 = setTimeout(() => {
        this.setState({ state: 'ready' });
        clearTimeout(this.timeout1);
        clearTimeout(this.timeout2);
      }, 1987);
    }, 1987);
  }

  render = () => (
    <FlexView>

      <FlexView marginRight={30}>
        <button onClick={this.onClick}>click!</button>
      </FlexView>

      <FlexView>
        <AsyncStatusIndicator
          state={this.state.state}
          icons={icons}
          labels={labels}
        />
      </FlexView>

    </FlexView>
  );

}
<commit_msg>one timeout is ok<commit_after>const labels = {
  success: 'Success',
  error: 'Error',
  ready: 'Ready',
  processing: 'Processing'
};

const icons = {
  success: <Icon icon='check2' />,
  error: <Icon icon='x' />,
  processing: <LoadingSpinner size='1em' overlayColor='transparent' />
}

class Example extends React.Component {

  state = { state: 'ready' }

  onClick = () => {
    if (this.timeout) {
      return;
    }
    this.setState({ state: 'processing'});
    this.timeout = setTimeout(() => {
      this.setState({ state: Math.random() > .5 ? 'success' : 'error'});
      this.timeout = setTimeout(() => {
        this.setState({ state: 'ready' });
        clearTimeout(this.timeout);
      }, 1987);
    }, 1987);
  }

  render = () => (
    <FlexView>

      <FlexView marginRight={30}>
        <button onClick={this.onClick}>click!</button>
      </FlexView>

      <FlexView>
        <AsyncStatusIndicator
          state={this.state.state}
          icons={icons}
          labels={labels}
        />
      </FlexView>

    </FlexView>
  );

}
<|endoftext|>


<commit_before>//@ts-check
"use strict";

const {
  $,
  $$,
  chalk,
  debug,
  header,
  setenv,
  detectOS,
  setVerbose,
} = require("@itchio/bob");

const { resolve } = require("path");
const { mkdirSync } = require("fs");

const DEFAULT_ARCH = "x86_64";

/**
 * @typedef OsInfo
 * @type {{
 *   architectures: {
 *     [key: string]: {
 *       prependPath?: string,
 *     }
 *   }
 * }}
 */

/**
 * @type {{[name: string]: OsInfo}}
 */
const OS_INFOS = {
  windows: {
    architectures: {
      i686: {
        prependPath: "/mingw32/bin",
      },
      x86_64: {
        prependPath: "/mingw64/bin",
      },
    },
  },
  linux: {
    architectures: {
      x86_64: {},
    },
  },
  darwin: {
    architectures: {
      x86_64: {},
    },
  },
};

/**
 * @param {string[]} args
 */
async function main(args) {
  /**
   * @type {{
   *   os: "windows" | "linux" | "darwin",
   *   arch: "i686" | "x86_64",
   *   userSpecifiedOS?: boolean,
   *   userSpecifiedArch?: boolean,
   * }}
   */
  let opts = {
    os: detectOS(),
    arch: DEFAULT_ARCH,
  };

  for (let i = 0; i < args.length; i++) {
    let arg = args[i];

    let matches = /^--(.*)$/.exec(arg);
    if (matches) {
      let k = matches[1];
      if (k == "verbose") {
        setVerbose(true);
        continue;
      }

      if (k === "os" || k === "arch") {
        i++;
        let v = args[i];

        if (k === "os") {
          if (v === "linux" || v === "windows" || v === "darwin") {
            opts.os = v;
            opts.userSpecifiedOS = true;
          } else {
            throw new Error(`Unsupported os ${chalk.yellow(v)}`);
          }
        } else if (k === "arch") {
          if (v === "i686" || v === "x86_64") {
            opts.arch = v;
            opts.userSpecifiedArch = true;
          } else {
            throw new Error(`Unsupported arch ${chalk.yellow(v)}`);
          }
        }
      } else {
        throw new Error(`Unsupported long option ${chalk.yellow(arg)}`);
      }
    }
  }

  if (opts.userSpecifiedOS) {
    console.log(`Using user-specified OS ${chalk.yellow(opts.os)}`);
  } else {
    console.log(
      `Using detected OS ${chalk.yellow(opts.os)} (use --os to override)`,
    );
  }

  if (opts.userSpecifiedArch) {
    console.log(`Using user-specified arch ${chalk.yellow(opts.arch)}`);
  } else {
    console.log(
      `Using detected arch ${chalk.yellow(opts.arch)} (use --arch to override)`,
    );
  }

  let osInfo = OS_INFOS[opts.os];
  debug({ osInfo });
  if (!osInfo) {
    throw new Error(`Unsupported OS ${chalk.yellow(opts.os)}`);
  }

  let archInfo = osInfo.architectures[opts.arch];
  debug({ archInfo });
  if (!archInfo) {
    throw new Error(`Unsupported arch '${opts.arch}' for os '${opts.os}'`);
  }

  if (archInfo.prependPath) {
    if (opts.os === "windows") {
      let prependPath = $$(`cygpath -w ${archInfo.prependPath}`).trim();
      console.log(
        `Prepending ${chalk.yellow(archInfo.prependPath)} (aka ${chalk.yellow(
          prependPath,
        )}) to $PATH`,
      );
      process.env.PATH = `${prependPath};${process.env.PATH}`;
    } else {
      console.log(`Prepending ${chalk.yellow(archInfo.prependPath)} to $PATH`);
      process.env.PATH = `${archInfo.prependPath}:${process.env.PATH}`;
    }
  }

  header("Showing tool versions");
  $(`node --version`);
  $(`go version`);

  let version = "head";
  let builtAt = $$(`date +%s`);
  if (process.env.CI_BUILD_TAG) {
    version = process.env.CI_BUILD_TAG;
  } else if (
    process.env.CI_BUILD_REF_NAME &&
    process.env.CI_BUILD_REF_NAME !== "master"
  ) {
    version = process.env.CI_BUILD_REF_NAME;
  }

  let bi = `github.com/itchio/butler/buildinfo`;

  let ldflags = [
    `-X ${bi}.Version=${version}`,
    `-X ${bi}.BuiltAt=${builtAt}`,
    `-X ${bi}.Commit=${process.env.CI_BUILD_REF || ""}`,
    "-w",
    "-s",
  ].join(" ");

  if (opts.os === "darwin") {
    setenv(`CGO_CFLAGS`, `-mmacosx-version-min=10.10`);
    setenv(`CGO_LDFLAGS`, `-mmacosx-version-min=10.10`);
  }

  let target = `butler`;
  if (opts.os === "windows") {
    target = `${target}.exe`;
  }

  if (opts.os === "windows") {
    console.log(`Compiling Windows manifest`);
    $(`windres -o butler.syso butler.rc`);
  }

  console.log(`Compiling binary`);
  let goArch = archToGoArch(opts.arch);
  setenv(`GOOS`, opts.os);
  setenv(`GOARCH`, goArch);
  setenv(`CGO_ENABLED`, `1`);
  $(`go build -ldflags "${ldflags}"`);

  if (opts.os === "linux") {
    console.log(`Checking minimum glibc version`);
    try {
      $(`./butler diag --no-net --glibc`);
    } catch (e) {
      if (process.env.CI) {
        throw e;
      } else {
        console.log(`Ignoring butler diag failure becaus we're not on CI`);
      }
    }
  }

  if (opts.os === "windows") {
    let signArgs = [
      "sign", // verb
      "//v", // verbose
      "//s MY", // store
      `//n "itch corp"`, // name
      `//fd sha256`, // file digest algo (default is SHA-1)
      `//tr http://timestamp.comodoca.com/?td=sha256`, // URL of RFC 3161 timestamp server
      `//td sha256`, // timestamp digest algo
      target,
    ];

    $(`tools/signtool.exe ${signArgs.join(" ")}`);
  }

  if (opts.os === "darwin") {
    let signKey = "Developer ID Application: Amos Wenger (B2N6FSRTPV)";
    $(`codesign --deep --force --verbose --sign "${signKey}" "${target}"`);
    $(`codesign --verify -vvvv "${target}"`);
    // We don't use spctl -a on purpose, see
    // https://stackoverflow.com/questions/39811791/mac-os-gatekeeper-blocking-signed-command-line-tool
  }

  header("Packaging...");
  let artifactDir = `./artifacts/${opts.os}-${goArch}`;
  mkdirSync(artifactDir, { recursive: true });

  let fullTarget = `${artifactDir}/${target}`;
  $(`mv ${target} ${fullTarget}`);
  $(`file ${fullTarget}`);
  $(`${fullTarget} -V`);
  $(`${fullTarget} fetch-7z-libs`);

  let fullButlerPath = resolve(process.cwd(), fullTarget);
  $(`go test -v ./butlerd/integrate --butlerPath='${fullButlerPath}'`);
}

/**
 * @param {"i686" | "x86_64"} arch
 * @returns {"386" | "amd64"}
 */
function archToGoArch(arch) {
  switch (arch) {
    case "i686":
      return "386";
    case "x86_64":
      return "amd64";
    default:
      throw new Error(`unsupported arch: ${chalk.yellow(arch)}`);
  }
}

main(process.argv.slice(2));
<commit_msg>update certs used<commit_after>//@ts-check
"use strict";

const {
  $,
  $$,
  chalk,
  debug,
  header,
  setenv,
  detectOS,
  setVerbose,
} = require("@itchio/bob");

const { resolve } = require("path");
const { mkdirSync } = require("fs");

const DEFAULT_ARCH = "x86_64";

/**
 * @typedef OsInfo
 * @type {{
 *   architectures: {
 *     [key: string]: {
 *       prependPath?: string,
 *     }
 *   }
 * }}
 */

/**
 * @type {{[name: string]: OsInfo}}
 */
const OS_INFOS = {
  windows: {
    architectures: {
      i686: {
        prependPath: "/mingw32/bin",
      },
      x86_64: {
        prependPath: "/mingw64/bin",
      },
    },
  },
  linux: {
    architectures: {
      x86_64: {},
    },
  },
  darwin: {
    architectures: {
      x86_64: {},
    },
  },
};

/**
 * @param {string[]} args
 */
async function main(args) {
  /**
   * @type {{
   *   os: "windows" | "linux" | "darwin",
   *   arch: "i686" | "x86_64",
   *   userSpecifiedOS?: boolean,
   *   userSpecifiedArch?: boolean,
   * }}
   */
  let opts = {
    os: detectOS(),
    arch: DEFAULT_ARCH,
  };

  for (let i = 0; i < args.length; i++) {
    let arg = args[i];

    let matches = /^--(.*)$/.exec(arg);
    if (matches) {
      let k = matches[1];
      if (k == "verbose") {
        setVerbose(true);
        continue;
      }

      if (k === "os" || k === "arch") {
        i++;
        let v = args[i];

        if (k === "os") {
          if (v === "linux" || v === "windows" || v === "darwin") {
            opts.os = v;
            opts.userSpecifiedOS = true;
          } else {
            throw new Error(`Unsupported os ${chalk.yellow(v)}`);
          }
        } else if (k === "arch") {
          if (v === "i686" || v === "x86_64") {
            opts.arch = v;
            opts.userSpecifiedArch = true;
          } else {
            throw new Error(`Unsupported arch ${chalk.yellow(v)}`);
          }
        }
      } else {
        throw new Error(`Unsupported long option ${chalk.yellow(arg)}`);
      }
    }
  }

  if (opts.userSpecifiedOS) {
    console.log(`Using user-specified OS ${chalk.yellow(opts.os)}`);
  } else {
    console.log(
      `Using detected OS ${chalk.yellow(opts.os)} (use --os to override)`,
    );
  }

  if (opts.userSpecifiedArch) {
    console.log(`Using user-specified arch ${chalk.yellow(opts.arch)}`);
  } else {
    console.log(
      `Using detected arch ${chalk.yellow(opts.arch)} (use --arch to override)`,
    );
  }

  let osInfo = OS_INFOS[opts.os];
  debug({ osInfo });
  if (!osInfo) {
    throw new Error(`Unsupported OS ${chalk.yellow(opts.os)}`);
  }

  let archInfo = osInfo.architectures[opts.arch];
  debug({ archInfo });
  if (!archInfo) {
    throw new Error(`Unsupported arch '${opts.arch}' for os '${opts.os}'`);
  }

  if (archInfo.prependPath) {
    if (opts.os === "windows") {
      let prependPath = $$(`cygpath -w ${archInfo.prependPath}`).trim();
      console.log(
        `Prepending ${chalk.yellow(archInfo.prependPath)} (aka ${chalk.yellow(
          prependPath,
        )}) to $PATH`,
      );
      process.env.PATH = `${prependPath};${process.env.PATH}`;
    } else {
      console.log(`Prepending ${chalk.yellow(archInfo.prependPath)} to $PATH`);
      process.env.PATH = `${archInfo.prependPath}:${process.env.PATH}`;
    }
  }

  header("Showing tool versions");
  $(`node --version`);
  $(`go version`);

  let version = "head";
  let builtAt = $$(`date +%s`);
  if (process.env.CI_BUILD_TAG) {
    version = process.env.CI_BUILD_TAG;
  } else if (
    process.env.CI_BUILD_REF_NAME &&
    process.env.CI_BUILD_REF_NAME !== "master"
  ) {
    version = process.env.CI_BUILD_REF_NAME;
  }

  let bi = `github.com/itchio/butler/buildinfo`;

  let ldflags = [
    `-X ${bi}.Version=${version}`,
    `-X ${bi}.BuiltAt=${builtAt}`,
    `-X ${bi}.Commit=${process.env.CI_BUILD_REF || ""}`,
    "-w",
    "-s",
  ].join(" ");

  if (opts.os === "darwin") {
    setenv(`CGO_CFLAGS`, `-mmacosx-version-min=10.10`);
    setenv(`CGO_LDFLAGS`, `-mmacosx-version-min=10.10`);
  }

  let target = `butler`;
  if (opts.os === "windows") {
    target = `${target}.exe`;
  }

  if (opts.os === "windows") {
    console.log(`Compiling Windows manifest`);
    $(`windres -o butler.syso butler.rc`);
  }

  console.log(`Compiling binary`);
  let goArch = archToGoArch(opts.arch);
  setenv(`GOOS`, opts.os);
  setenv(`GOARCH`, goArch);
  setenv(`CGO_ENABLED`, `1`);
  $(`go build -ldflags "${ldflags}"`);

  if (opts.os === "linux") {
    console.log(`Checking minimum glibc version`);
    try {
      $(`./butler diag --no-net --glibc`);
    } catch (e) {
      if (process.env.CI) {
        throw e;
      } else {
        console.log(`Ignoring butler diag failure becaus we're not on CI`);
      }
    }
  }

  if (opts.os === "windows") {
    let signArgs = [
      "sign", // verb
      "//v", // verbose
      "//s MY", // store
      `//n "itch corp"`, // name
      `//fd sha256`, // file digest algo (default is SHA-1)
      `//tr http://timestamp.comodoca.com/?td=sha256`, // URL of RFC 3161 timestamp server
      `//td sha256`, // timestamp digest algo
      `//a`, // choose best cert
      target,
    ];

    $(`tools/signtool.exe ${signArgs.join(" ")}`);
  }

  if (opts.os === "darwin") {
    let signKey = "Developer ID Application: itch corp. (AK2D34UDP2)";
    $(`codesign --deep --force --verbose --sign "${signKey}" "${target}"`);
    $(`codesign --verify -vvvv "${target}"`);
    // We don't use spctl -a on purpose, see
    // https://stackoverflow.com/questions/39811791/mac-os-gatekeeper-blocking-signed-command-line-tool
  }

  header("Packaging...");
  let artifactDir = `./artifacts/${opts.os}-${goArch}`;
  mkdirSync(artifactDir, { recursive: true });

  let fullTarget = `${artifactDir}/${target}`;
  $(`mv ${target} ${fullTarget}`);
  $(`file ${fullTarget}`);
  $(`${fullTarget} -V`);
  $(`${fullTarget} fetch-7z-libs`);

  let fullButlerPath = resolve(process.cwd(), fullTarget);
  $(`go test -v ./butlerd/integrate --butlerPath='${fullButlerPath}'`);
}

/**
 * @param {"i686" | "x86_64"} arch
 * @returns {"386" | "amd64"}
 */
function archToGoArch(arch) {
  switch (arch) {
    case "i686":
      return "386";
    case "x86_64":
      return "amd64";
    default:
      throw new Error(`unsupported arch: ${chalk.yellow(arch)}`);
  }
}

main(process.argv.slice(2));
<|endoftext|>