<commit_before>'use strict';
var OpenT2TError = require('opent2t').OpenT2TError;
var OpenT2TConstants = require('opent2t').OpenT2TConstants;

// This code uses ES2015 syntax that requires at least Node.js v4.
// For Node.js ES2015 support details, reference http://node.green/

function validateArgumentType(arg, argName, expectedType) {
    if (typeof arg === 'undefined') {
        throw new OpenT2TError(400, 'Missing argument: ' + argName + '. ' +
            'Expected type: ' + expectedType + '.');
    } else if (typeof arg !== expectedType) {
        throw new OpenT2TError(400, 'Invalid argument: ' + argName + '. ' +
            'Expected type: ' + expectedType + ', got: ' + (typeof arg));
    }
}

/**
 * Finds a resource for an entity in a schema
 */
function findResource(schema, di, resourceId) {
    // Find the entity by the unique di
    var entity = schema.entities.find((d) => {
        return d.di === di;
    });

    if (!entity) throw new Error('NotFound');

    var resource = entity.resources.find((r) => {
        return r.id === resourceId;
    });

    if (!resource) throw new Error('NotFound');
    return resource;
}

// Wink does not always populate every desired_state property, but last_reading doesn't necessarily
// update as soon as we send our PUT request. Instead of relying just on one state or the other,
// we use this StateReader class to read from desired_state if it is there, and fall back to last_reading
// if it is not.
class StateReader {
    constructor(desired_state, last_reading) {
        this.desired_state = desired_state;
        this.last_reading = last_reading;
    }

    get(state) {
        if (this.desired_state[state] !== undefined) {
            return this.desired_state[state];
        }
        else {
            return this.last_reading[state];
        }
    }
}

var deviceHvacModeToTranslatorHvacModeMap = {
    'cool_only': 'coolOnly',
    'heat_only': 'heatOnly',
    'auto': 'auto'
}

var translatorHvacModeToDeviceHvacModeMap = {
    'coolOnly': 'cool_only',
    'heatOnly': 'heat_only',
    'auto': 'auto'
}

function deviceHvacModeToTranslatorHvacMode(mode) {
    return deviceHvacModeToTranslatorHvacModeMap[mode];
}

function translatorHvacModeToDeviceHvacMode(mode) {
    return translatorHvacModeToDeviceHvacModeMap[mode];
}

function deviceSupportedModesToTranslatorSupportedModes(deviceSupportedModes) {
    var supportedModes = deviceSupportedModes.map(deviceHvacModeToTranslatorHvacMode);
    return supportedModes.filter((m) => !!m);
}

function readHvacMode(stateReader) {
    var supportedHvacModes = deviceSupportedModesToTranslatorSupportedModes(stateReader.get('modes_allowed'))
    var hvacMode = deviceHvacModeToTranslatorHvacMode(stateReader.get('mode'));
    var powered = stateReader.get('powered');

    supportedHvacModes.push('off');
    if (!powered) {
        hvacMode = 'off';
    }

    return {
        supportedModes: supportedHvacModes,
        modes: [hvacMode]
    };
}

function createResource(resourceType, accessLevel, id, expand, state) {
    var resource = {
        href: '/' + id,
        rt: [resourceType],
        if: [accessLevel, 'oic.if.baseline']
    }

    if (expand) {
        resource.id = id;
        Object.assign(resource, state);
    }

    return resource;
}

// Helper method to convert the provider schema to the platform schema.
function providerSchemaToPlatformSchema(providerSchema, expand) {
    var stateReader = new StateReader(providerSchema.desired_state, providerSchema.last_reading);

    var max = stateReader.get('max_set_point');
    var min = stateReader.get('min_set_point');
    var temperatureUnits = stateReader.get('units');

    var ambientTemperature = createResource('oic.r.temperature', 'oic.if.s', 'ambientTemperature', expand, {
        temperature: stateReader.get('temperature'),
        units: temperatureUnits
    });

    var targetTemperature = createResource('oic.r.temperature', 'oic.if.a', 'targetTemperature', expand, {
        temperature: (max + min) / 2,
        units: temperatureUnits
    });

    var targetTemperatureHigh = createResource('oic.r.temperature', 'oic.if.a', 'targetTemperatureHigh', expand, {
        temperature: max,
        units: temperatureUnits
    });

    var targetTemperatureLow = createResource('oic.r.temperature', 'oic.if.a', 'targetTemperatureLow', expand, {
        temperature: min,
        units: temperatureUnits
    });

    var awayMode = createResource('oic.r.mode', 'oic.if.a', 'awayMode', expand, {
        modes: [stateReader.get('users_away') ? 'away' : 'home'],
        supportedModes: ['home', 'away']
    });

    var ecoMode = createResource('oic.r.sensor', 'oic.if.s', 'ecoMode', expand, {
        value: stateReader.get('eco_target')
    });

    var hvacMode = createResource('oic.r.mode', 'oic.if.a', 'hvacMode', expand, readHvacMode(stateReader));

    var hasFan = createResource('oic.r.sensor', 'oic.if.s', 'hasFan', expand, {
        value: stateReader.get('has_fan')
    });

    var fanActive = createResource('oic.r.sensor', 'oic.if.s', 'fanActive', expand, {
        value: stateReader.get('fan_timer_active')
    });

    return {
        opent2t: {
            schema: 'org.opent2t.sample.thermostat.superpopular',
            translator: 'opent2t-translator-com-wink-thermostat',
            controlId: providerSchema.thermostat_id
        },
        pi: providerSchema['uuid'],
        mnmn: providerSchema['device_manufacturer'],
        mnmo: providerSchema['manufacturer_device_model'],
        n: providerSchema['name'],
        rt: ['org.opent2t.sample.thermostat.superpopular'],
        entities: [
            {
                rt: ['opent2t.d.thermostat'],
                di: deviceIds['opent2t.d.thermostat'],
                resources: [
                    ambientTemperature,
                    targetTemperature,
                    targetTemperatureHigh,
                    targetTemperatureLow,
                    awayMode,
                    ecoMode,
                    hvacMode,
                    hasFan,
                    fanActive
                ]
            }
        ]
    };
}

// Helper method to convert the translator schema to the device schema.
function resourceSchemaToProviderSchema(resourceId, resourceSchema) {

    // build the object with desired state
    var result = { 'desired_state': {} };
    var desired_state = result.desired_state;

    // Quirks:
    // Wink does not have a target temperature field, so ignoring that resource.
    // See: http://docs.winkapiv2.apiary.io/#reference/device/thermostats
    // Instead, we infer it from the max and min setpoint
    // Wink has a separate 'powered' property rather than 'off' being part of the 'mode' property

    switch (resourceId) {
        case 'targetTemperatureHigh':
            desired_state['max_set_point'] = resourceSchema.temperature;
            break;
        case 'targetTemperatureLow':
            desired_state['min_set_point'] = resourceSchema.temperature;
            break;
        case 'awayMode':
            desired_state['users_away'] = resourceSchema.modes[0] === 'away';
            break;
        case 'hvacMode': {
                var mode = resourceSchema.modes[0];

                if (mode === 'off') {
                    desired_state['powered'] = false;
                }
                else {
                    desired_state['powered'] = true;
                    desired_state['mode'] = translatorHvacModeToDeviceHvacMode(mode);
                }
                break;
            }
        case 'targetTemperature':
        case 'awayTemperatureHigh':
        case 'awayTemperatureLow':
        case 'fanTimerTimeout':
        case 'fanMode':
            throw new OpenT2TError(501, OpenT2TConstants.NotImplemented);
        default:
            throw new OpenT2TError(404, OpenT2TConstants.ResourceNotFound);
    }

    return result;
}

function validateResourceGet(resourceId) {
    switch (resourceId) {
        case 'humidity':
        case 'awayTemperatureHigh':
        case 'awayTemperatureLow':
        case 'heatingFuelSource':
        case 'fanTimerActive':
        case 'fanTimerTimeout':
        case 'fanMode':
            throw new OpenT2TError(501, 'NotImplemented');
    }
}

function findResource(schema, di, resourceId) {
    var entity = schema.entities.find((d) => {
        return d.di === di;
    });

    if (!entity) {
        throw new OpenT2TError(404, 'NotFound');
    }

    var resource = entity.resources.find((r) => {
        return r.id === resourceId;
    });

    if (!resource) {
        throw new OpenT2TError(404, 'NotFound');
    }

    return resource;
}

function getDeviceResource(translator, di, resourceId) {
    validateResourceGet(resourceId);

    return translator.get(true)
        .then(response => {
            return findResource(response, di, resourceId);
        });
}

function postDeviceResource(di, resourceId, payload) {
    if (di === deviceIds['opent2t.d.thermostat']) {
        var putPayload = resourceSchemaToProviderSchema(resourceId, payload);

        return winkHub.putDeviceDetailsAsync(deviceType, controlId, putPayload)
            .then((response) => {
                var schema = providerSchemaToPlatformSchema(response.data, true);

                return findResource(schema, di, resourceId);
            });
    } else {
        throw new OpenT2TError(404, 'NotFound');
    }
}

var controlId;
var deviceType = 'thermostats';
var winkHub;

var deviceIds = {
    'oic.d.thermostat': 'B610F482-19A4-4EC4-ADB3-3517C7969183',
    'opent2t.d.thermostat': 'D5D37EB6-F428-41FA-AC5D-918F084A4C93'
}

// This translator class implements the 'org.opent2t.sample.thermostat.superpopular' schema.
class Translator {

    constructor(deviceInfo) {
        console.log('Initializing device.');

        validateArgumentType(deviceInfo, "deviceInfo", "object");

        this.controlId = deviceInfo.deviceInfo.opent2t.controlId;
        this.winkHub = deviceInfo.hub;
        this.deviceType = 'thermostats';

        console.log('Wink Thermostat Translator initialized.');
    }

    /**
     * Queries the entire state of the thermostat
     * and returns an object that maps to the json schema org.opent2t.sample.thermostat.superpopular
     */
    get(expand, payload) {
        if (payload) {
            return providerSchemaToPlatformSchema(payload, expand);
        } else {
            return this.winkHub.getDeviceDetailsAsync(this.deviceType, this.controlId)
                .then((response) => {
                    return providerSchemaToPlatformSchema(response.data, expand);
                });
        }
    }

    /**
     * Finds a resource on a platform by the id
     */
    getDeviceResource(di, resourceId) {
        validateResourceGet(resourceId);

        return this.get(true)
            .then(response => {
                return findResource(response, di, resourceId);
            });
    }

    /**
     * Updates the specified resource with the provided payload.
     */
    postDeviceResource(di, resourceId, payload) {
        if (di === deviceIds['opent2t.d.thermostat']) {
            var putPayload = resourceSchemaToProviderSchema(resourceId, payload);

            return this.winkHub.putDeviceDetailsAsync(this.deviceType, this.controlId, putPayload)
                .then((response) => {
                    var schema = providerSchemaToPlatformSchema(response.data, true);

                    return findResource(schema, di, resourceId);
                });
        } else {
            throw new Error('NotFound');
        }
    }

    getDevicesAmbientTemperature(di) {
        return this.getDeviceResource(di, 'ambientTemperature');
    }

    getDevicesTargetTemperature(di) {
        return this.getDeviceResource(di, 'targetTemperature');
    }

    postDevicesTargetTemperature(di, payload) {
        return this.postDeviceResource(di, 'targetTemperature', payload);
    }

    getDevicesHumidity(di) {
        return this.getDeviceResource(di, 'humidity');
    }

    getDevicesTargetTemperatureHigh(di) {
        return this.getDeviceResource(di, 'targetTemperatureHigh');
    }

    postDevicesTargetTemperatureHigh(di, payload) {
        return this.postDeviceResource(di, 'targetTemperatureHigh', payload);
    }

    getDevicesTargetTemperatureLow(di) {
        return this.getDeviceResource(di, 'targetTemperatureLow');
    }

    postDevicesTargetTemperatureLow(di, payload) {
        return this.postDeviceResource(di, 'targetTemperatureLow', payload);
    }

    getDevicesAwayMode(di) {
        return this.getDeviceResource(di, 'awayMode');
    }

    postDevicesAwayMode(di, payload) {
        return this.postDeviceResource(di, 'awayMode', payload);
    }

    getDevicesAwayTemperatureHigh(di) {
        return this.getDeviceResource(di, 'awayTemperatureHigh');
    }

    postDevicesAwayTemperatureHigh(di, payload) {
        return this.postDeviceResource(di, 'awayTemperatureHigh', payload);
    }

    getDevicesAwayTemperatureLow(di) {
        return this.getDeviceResource(di, 'awayTemperatureLow');
    }

    postDevicesAwayTemperatureLow(di, payload) {
        return this.postDeviceResource(di, 'awayTemperatureLow', payload);
    }

    getDevicesEcoMode(di) {
        return this.getDeviceResource(di, 'ecoMode');
    }

    getDevicesHvacMode(di) {
        return this.getDeviceResource(di, 'hvacMode');
    }

    postDevicesHvacMode(di, payload) {
        return this.postDeviceResource(di, 'hvacMode', payload);
    }

    getDevicesHeatingFuelSource(di) {
        return this.getDeviceResource(di, 'heatingFuelSource');
    }

    getDevicesHasFan(di) {
        return this.getDeviceResource(di, 'hasFan');
    }

    getDevicesFanActive(di) {
        return this.getDeviceResource(di, 'fanActive');
    }

    getDevicesFanTimerActive(di) {
        return this.getDeviceResource(di, 'fanTimerActive');
    }

    getDevicesFanTimerTimeout(di) {
        return this.getDeviceResource(di, 'fanTimerTimeout');
    }

    postDevicesFanTimerTimeout(di, payload) {
        return this.postDeviceResource(di, 'fanTimerTimeout', payload);
    }

    getDevicesFanMode(di) {
        return this.getDeviceResource(di, 'fanMode');
    }

    postDevicesFanMode(di, payload) {
        return this.postDeviceResource(di, 'fanMode', payload);
    }

    postSubscribe(subscriptionInfo) {
        subscriptionInfo.deviceId = this.controlId;
        subscriptionInfo.deviceType = this.deviceType;
        return this.winkHub._subscribe(subscriptionInfo);
    }

    deleteSubscribe(subscriptionInfo) {
        subscriptionInfo.deviceId = this.controlId;
        subscriptionInfo.deviceType = this.deviceType;
        return this.winkHub._unsubscribe(subscriptionInfo);
    }
}

// Export the translator from the module.
module.exports = Translator;<commit_msg>Fixing bad merge<commit_after>'use strict';
var OpenT2TError = require('opent2t').OpenT2TError;
var OpenT2TConstants = require('opent2t').OpenT2TConstants;

// This code uses ES2015 syntax that requires at least Node.js v4.
// For Node.js ES2015 support details, reference http://node.green/

function validateArgumentType(arg, argName, expectedType) {
    if (typeof arg === 'undefined') {
        throw new OpenT2TError(400, 'Missing argument: ' + argName + '. ' +
            'Expected type: ' + expectedType + '.');
    } else if (typeof arg !== expectedType) {
        throw new OpenT2TError(400, 'Invalid argument: ' + argName + '. ' +
            'Expected type: ' + expectedType + ', got: ' + (typeof arg));
    }
}

/**
 * Finds a resource for an entity in a schema
 */
function findResource(schema, di, resourceId) {
    // Find the entity by the unique di
    var entity = schema.entities.find((d) => {
        return d.di === di;
    });

    if (!entity) {
        throw new OpenT2TError(404, OpenT2TConstants.DeviceNotFound);
    }

    var resource = entity.resources.find((r) => {
        return r.id === resourceId;
    });

    if (!resource) {
        throw new OpenT2TError(404, OpenT2TConstants.ResourceNotFound);
    }

    return resource;
}

// Wink does not always populate every desired_state property, but last_reading doesn't necessarily
// update as soon as we send our PUT request. Instead of relying just on one state or the other,
// we use this StateReader class to read from desired_state if it is there, and fall back to last_reading
// if it is not.
class StateReader {
    constructor(desired_state, last_reading) {
        this.desired_state = desired_state;
        this.last_reading = last_reading;
    }

    get(state) {
        if (this.desired_state[state] !== undefined) {
            return this.desired_state[state];
        }
        else {
            return this.last_reading[state];
        }
    }
}

var deviceHvacModeToTranslatorHvacModeMap = {
    'cool_only': 'coolOnly',
    'heat_only': 'heatOnly',
    'auto': 'auto'
}

var translatorHvacModeToDeviceHvacModeMap = {
    'coolOnly': 'cool_only',
    'heatOnly': 'heat_only',
    'auto': 'auto'
}

function deviceHvacModeToTranslatorHvacMode(mode) {
    return deviceHvacModeToTranslatorHvacModeMap[mode];
}

function translatorHvacModeToDeviceHvacMode(mode) {
    return translatorHvacModeToDeviceHvacModeMap[mode];
}

function deviceSupportedModesToTranslatorSupportedModes(deviceSupportedModes) {
    var supportedModes = deviceSupportedModes.map(deviceHvacModeToTranslatorHvacMode);
    return supportedModes.filter((m) => !!m);
}

function readHvacMode(stateReader) {
    var supportedHvacModes = deviceSupportedModesToTranslatorSupportedModes(stateReader.get('modes_allowed'))
    var hvacMode = deviceHvacModeToTranslatorHvacMode(stateReader.get('mode'));
    var powered = stateReader.get('powered');

    supportedHvacModes.push('off');
    if (!powered) {
        hvacMode = 'off';
    }

    return {
        supportedModes: supportedHvacModes,
        modes: [hvacMode]
    };
}

function createResource(resourceType, accessLevel, id, expand, state) {
    var resource = {
        href: '/' + id,
        rt: [resourceType],
        if: [accessLevel, 'oic.if.baseline']
    }

    if (expand) {
        resource.id = id;
        Object.assign(resource, state);
    }

    return resource;
}

// Helper method to convert the provider schema to the platform schema.
function providerSchemaToPlatformSchema(providerSchema, expand) {
    var stateReader = new StateReader(providerSchema.desired_state, providerSchema.last_reading);

    var max = stateReader.get('max_set_point');
    var min = stateReader.get('min_set_point');
    var temperatureUnits = stateReader.get('units');

    var ambientTemperature = createResource('oic.r.temperature', 'oic.if.s', 'ambientTemperature', expand, {
        temperature: stateReader.get('temperature'),
        units: temperatureUnits
    });

    var targetTemperature = createResource('oic.r.temperature', 'oic.if.a', 'targetTemperature', expand, {
        temperature: (max + min) / 2,
        units: temperatureUnits
    });

    var targetTemperatureHigh = createResource('oic.r.temperature', 'oic.if.a', 'targetTemperatureHigh', expand, {
        temperature: max,
        units: temperatureUnits
    });

    var targetTemperatureLow = createResource('oic.r.temperature', 'oic.if.a', 'targetTemperatureLow', expand, {
        temperature: min,
        units: temperatureUnits
    });

    var awayMode = createResource('oic.r.mode', 'oic.if.a', 'awayMode', expand, {
        modes: [stateReader.get('users_away') ? 'away' : 'home'],
        supportedModes: ['home', 'away']
    });

    var ecoMode = createResource('oic.r.sensor', 'oic.if.s', 'ecoMode', expand, {
        value: stateReader.get('eco_target')
    });

    var hvacMode = createResource('oic.r.mode', 'oic.if.a', 'hvacMode', expand, readHvacMode(stateReader));

    var hasFan = createResource('oic.r.sensor', 'oic.if.s', 'hasFan', expand, {
        value: stateReader.get('has_fan')
    });

    var fanActive = createResource('oic.r.sensor', 'oic.if.s', 'fanActive', expand, {
        value: stateReader.get('fan_timer_active')
    });

    return {
        opent2t: {
            schema: 'org.opent2t.sample.thermostat.superpopular',
            translator: 'opent2t-translator-com-wink-thermostat',
            controlId: providerSchema.thermostat_id
        },
        pi: providerSchema['uuid'],
        mnmn: providerSchema['device_manufacturer'],
        mnmo: providerSchema['manufacturer_device_model'],
        n: providerSchema['name'],
        rt: ['org.opent2t.sample.thermostat.superpopular'],
        entities: [
            {
                rt: ['opent2t.d.thermostat'],
                di: deviceIds['opent2t.d.thermostat'],
                resources: [
                    ambientTemperature,
                    targetTemperature,
                    targetTemperatureHigh,
                    targetTemperatureLow,
                    awayMode,
                    ecoMode,
                    hvacMode,
                    hasFan,
                    fanActive
                ]
            }
        ]
    };
}

// Helper method to convert the translator schema to the device schema.
function resourceSchemaToProviderSchema(resourceId, resourceSchema) {

    // build the object with desired state
    var result = { 'desired_state': {} };
    var desired_state = result.desired_state;

    // Quirks:
    // Wink does not have a target temperature field, so ignoring that resource.
    // See: http://docs.winkapiv2.apiary.io/#reference/device/thermostats
    // Instead, we infer it from the max and min setpoint
    // Wink has a separate 'powered' property rather than 'off' being part of the 'mode' property

    switch (resourceId) {
        case 'targetTemperatureHigh':
            desired_state['max_set_point'] = resourceSchema.temperature;
            break;
        case 'targetTemperatureLow':
            desired_state['min_set_point'] = resourceSchema.temperature;
            break;
        case 'awayMode':
            desired_state['users_away'] = resourceSchema.modes[0] === 'away';
            break;
        case 'hvacMode': {
                var mode = resourceSchema.modes[0];

                if (mode === 'off') {
                    desired_state['powered'] = false;
                }
                else {
                    desired_state['powered'] = true;
                    desired_state['mode'] = translatorHvacModeToDeviceHvacMode(mode);
                }
                break;
            }
        case 'targetTemperature':
        case 'awayTemperatureHigh':
        case 'awayTemperatureLow':
        case 'fanTimerTimeout':
        case 'fanMode':
            throw new OpenT2TError(501, OpenT2TConstants.NotImplemented);
        default:
            throw new OpenT2TError(404, OpenT2TConstants.ResourceNotFound);
    }

    return result;
}

function validateResourceGet(resourceId) {
    switch (resourceId) {
        case 'humidity':
        case 'awayTemperatureHigh':
        case 'awayTemperatureLow':
        case 'heatingFuelSource':
        case 'fanTimerActive':
        case 'fanTimerTimeout':
        case 'fanMode':
            throw new OpenT2TError(501, 'NotImplemented');
    }
}

var deviceIds = {
    'oic.d.thermostat': 'B610F482-19A4-4EC4-ADB3-3517C7969183',
    'opent2t.d.thermostat': 'D5D37EB6-F428-41FA-AC5D-918F084A4C93'
}

// This translator class implements the 'org.opent2t.sample.thermostat.superpopular' schema.
class Translator {

    constructor(deviceInfo) {
        console.log('Initializing device.');

        validateArgumentType(deviceInfo, "deviceInfo", "object");

        this.controlId = deviceInfo.deviceInfo.opent2t.controlId;
        this.winkHub = deviceInfo.hub;
        this.deviceType = 'thermostats';

        console.log('Wink Thermostat Translator initialized.');
    }

    /**
     * Queries the entire state of the thermostat
     * and returns an object that maps to the json schema org.opent2t.sample.thermostat.superpopular
     */
    get(expand, payload) {
        if (payload) {
            return providerSchemaToPlatformSchema(payload, expand);
        } else {
            return this.winkHub.getDeviceDetailsAsync(this.deviceType, this.controlId)
                .then((response) => {
                    return providerSchemaToPlatformSchema(response.data, expand);
                });
        }
    }

    /**
     * Finds a resource on a platform by the id
     */
    getDeviceResource(di, resourceId) {
        validateResourceGet(resourceId);

        return this.get(true)
            .then(response => {
                return findResource(response, di, resourceId);
            });
    }

    /**
     * Updates the specified resource with the provided payload.
     */
    postDeviceResource(di, resourceId, payload) {
        if (di === deviceIds['opent2t.d.thermostat']) {
            var putPayload = resourceSchemaToProviderSchema(resourceId, payload);

            return this.winkHub.putDeviceDetailsAsync(this.deviceType, this.controlId, putPayload)
                .then((response) => {
                    var schema = providerSchemaToPlatformSchema(response.data, true);

                    return findResource(schema, di, resourceId);
                });
        } else {
            throw new Error('NotFound');
        }
    }

    getDevicesAmbientTemperature(di) {
        return this.getDeviceResource(di, 'ambientTemperature');
    }

    getDevicesTargetTemperature(di) {
        return this.getDeviceResource(di, 'targetTemperature');
    }

    postDevicesTargetTemperature(di, payload) {
        return this.postDeviceResource(di, 'targetTemperature', payload);
    }

    getDevicesHumidity(di) {
        return this.getDeviceResource(di, 'humidity');
    }

    getDevicesTargetTemperatureHigh(di) {
        return this.getDeviceResource(di, 'targetTemperatureHigh');
    }

    postDevicesTargetTemperatureHigh(di, payload) {
        return this.postDeviceResource(di, 'targetTemperatureHigh', payload);
    }

    getDevicesTargetTemperatureLow(di) {
        return this.getDeviceResource(di, 'targetTemperatureLow');
    }

    postDevicesTargetTemperatureLow(di, payload) {
        return this.postDeviceResource(di, 'targetTemperatureLow', payload);
    }

    getDevicesAwayMode(di) {
        return this.getDeviceResource(di, 'awayMode');
    }

    postDevicesAwayMode(di, payload) {
        return this.postDeviceResource(di, 'awayMode', payload);
    }

    getDevicesAwayTemperatureHigh(di) {
        return this.getDeviceResource(di, 'awayTemperatureHigh');
    }

    postDevicesAwayTemperatureHigh(di, payload) {
        return this.postDeviceResource(di, 'awayTemperatureHigh', payload);
    }

    getDevicesAwayTemperatureLow(di) {
        return this.getDeviceResource(di, 'awayTemperatureLow');
    }

    postDevicesAwayTemperatureLow(di, payload) {
        return this.postDeviceResource(di, 'awayTemperatureLow', payload);
    }

    getDevicesEcoMode(di) {
        return this.getDeviceResource(di, 'ecoMode');
    }

    getDevicesHvacMode(di) {
        return this.getDeviceResource(di, 'hvacMode');
    }

    postDevicesHvacMode(di, payload) {
        return this.postDeviceResource(di, 'hvacMode', payload);
    }

    getDevicesHeatingFuelSource(di) {
        return this.getDeviceResource(di, 'heatingFuelSource');
    }

    getDevicesHasFan(di) {
        return this.getDeviceResource(di, 'hasFan');
    }

    getDevicesFanActive(di) {
        return this.getDeviceResource(di, 'fanActive');
    }

    getDevicesFanTimerActive(di) {
        return this.getDeviceResource(di, 'fanTimerActive');
    }

    getDevicesFanTimerTimeout(di) {
        return this.getDeviceResource(di, 'fanTimerTimeout');
    }

    postDevicesFanTimerTimeout(di, payload) {
        return this.postDeviceResource(di, 'fanTimerTimeout', payload);
    }

    getDevicesFanMode(di) {
        return this.getDeviceResource(di, 'fanMode');
    }

    postDevicesFanMode(di, payload) {
        return this.postDeviceResource(di, 'fanMode', payload);
    }

    postSubscribe(subscriptionInfo) {
        subscriptionInfo.deviceId = this.controlId;
        subscriptionInfo.deviceType = this.deviceType;
        return this.winkHub._subscribe(subscriptionInfo);
    }

    deleteSubscribe(subscriptionInfo) {
        subscriptionInfo.deviceId = this.controlId;
        subscriptionInfo.deviceType = this.deviceType;
        return this.winkHub._unsubscribe(subscriptionInfo);
    }
}

// Export the translator from the module.
module.exports = Translator;<|endoftext|>


<commit_before>'use strict';

require('../css/main.css');
require('../css/nojs.css');
require('../css/columns/name-size-date.css');
require('../css/columns/name-size.css');

const wraptile = require('wraptile');
const load = require('load.js');

const {
    registerSW,
    listenSW,
} = require('./sw/register');

const isDev = process.env.NODE_ENV === 'development';

// prevent additional loading of emitify
window.Emitify = require('emitify');

module.exports = window.CloudCmd = async (config) => {
    window.Util = require('../common/util');
    window.CloudFunc = require('../common/cloudfunc');

    window.DOM = require('./dom');
    window.CloudCmd = require('./client');

    await register(config);

    require('./listeners');
    require('./key');
    require('./sort');

    const prefix = getPrefix(config.prefix);

    window.CloudCmd.init(prefix, config);
};

function getPrefix(prefix) {
    if (!prefix)
        return '';

    if (!prefix.indexOf('/'))
        return prefix;

    return `/${prefix}`;
}

const onUpdateFound = wraptile(async (config) => {
    if (isDev)
        return;

    const {DOM} = window;
    const prefix = getPrefix(config.prefix);

    await load.js(`${prefix}/dist/cloudcmd.common.js`);
    await load.js(`${prefix}/dist/cloudcmd.js`);

    console.log('cloudcmd: sw: updated');

    DOM.Events.removeAll();
    window.CloudCmd(config);
});

async function register(config) {
    const {prefix} = config;
    const sw = await registerSW(prefix);

    listenSW(sw, 'updatefound', onUpdateFound(config));
}

<commit_msg>fix(client) cloudcmd: rm window.Emitify<commit_after>'use strict';

require('../css/main.css');
require('../css/nojs.css');
require('../css/columns/name-size-date.css');
require('../css/columns/name-size.css');

const wraptile = require('wraptile');
const load = require('load.js');

const {
    registerSW,
    listenSW,
} = require('./sw/register');

const isDev = process.env.NODE_ENV === 'development';

module.exports = window.CloudCmd = async (config) => {
    window.Util = require('../common/util');
    window.CloudFunc = require('../common/cloudfunc');

    window.DOM = require('./dom');
    window.CloudCmd = require('./client');

    await register(config);

    require('./listeners');
    require('./key');
    require('./sort');

    const prefix = getPrefix(config.prefix);

    window.CloudCmd.init(prefix, config);
};

function getPrefix(prefix) {
    if (!prefix)
        return '';

    if (!prefix.indexOf('/'))
        return prefix;

    return `/${prefix}`;
}

const onUpdateFound = wraptile(async (config) => {
    if (isDev)
        return;

    const {DOM} = window;
    const prefix = getPrefix(config.prefix);

    await load.js(`${prefix}/dist/cloudcmd.common.js`);
    await load.js(`${prefix}/dist/cloudcmd.js`);

    console.log('cloudcmd: sw: updated');

    DOM.Events.removeAll();
    window.CloudCmd(config);
});

async function register(config) {
    const {prefix} = config;
    const sw = await registerSW(prefix);

    listenSW(sw, 'updatefound', onUpdateFound(config));
}

<|endoftext|>


<commit_before>$(function() {
	var socket = io();
	var commands = [
		"/close",
		"/connect",
		"/deop",
		"/devoice",
		"/disconnect",
		"/invite",
		"/join",
		"/kick",
		"/leave",
		"/mode",
		"/msg",
		"/nick",
		"/notice",
		"/op",
		"/part",
		"/query",
		"/quit",
		"/raw",
		"/say",
		"/send",
		"/server",
		"/slap",
		"/topic",
		"/voice",
		"/whois"
	];

	var sidebar = $("#sidebar");
	var chat = $("#chat");

	try {
		var pop = new Audio();
		pop.src = "/audio/pop.ogg";
	} catch(e) {
		var pop = {
			play: $.noop
		};
	}

	// Request notification permissions if we don't already have it
	if (Notification.permission !== 'granted') {
		Notification.requestPermission();
	}

	$("#play").on("click", function() { pop.play(); });
	$("#footer .icon").tooltip();

	var favico = new Favico({
		animation: "none"
	});

	function render(name, data) {
		return Handlebars.templates[name](data);
	}

	Handlebars.registerHelper(
		"partial", function(id) {
			return new Handlebars.SafeString(render(id, this));
		}
	);

	socket.on("error", function(e) {
		console.log(e);
	});

	socket.on("connect_error", function(e) {
		location.reload();
	});

	socket.on("auth", function(data) {
		var body = $("body");
		var login = $("#sign-in");
		if (!login.length) {
			location.reload();
			return;
		}
		if (body.hasClass("signed-out")) {
			var error = login.find(".error");
			error.show().closest("form").one("submit", function() {
				error.hide();
			});
		}
		body.addClass("signed-out");
		var input = login.find("input[name='user']");
		if (input.val() === "") {
			input.val($.cookie("user") || "");
		}
		sidebar.find(".sign-in")
			.click()
			.end()
			.find(".networks")
			.html("")
			.next()
			.show();
	});

	socket.on("init", function(data) {
		if (data.networks.length === 0) {
			$("#footer").find(".connect").trigger("click");
			return;
		}

		sidebar.find(".networks").html(
			render("network", {
				networks: data.networks
			})
		);

		var channels = $.map(data.networks, function(n) {
			return n.channels;
		});
		chat.html(
			render("chat", {
				channels: channels
			})
		);

		sidebar.find(".empty").hide();
		$("body").removeClass("signed-out");
		$("#sign-in").detach();

		var id = $.cookie("target");
		var target = sidebar.find("[data-target='" + id + "']").trigger("click");
		if (target.length === 0) {
			var first = sidebar.find(".chan")
				.eq(0)
				.trigger("click");
			if (first.length === 0) {
				$("#footer").find(".connect").trigger("click");
			}
		}
	});

	socket.on("join", function(data) {
		var id = data.network;
		var network = sidebar.find("#network-" + id);
		network.append(
			render("chan", {
				channels: [data.chan]
			})
		);
		chat.append(
			render("chat", {
				channels: [data.chan]
			})
		);
		sidebar.find(".chan")
			.sort(function(a, b) { return $(a).data("id") - $(b).data("id"); })
			.last()
			.trigger("click");
	});

	socket.on("msg", function(data) {
		var target = data.chan;
		if (data.msg.type == "error") {
			target = chat.find(".active").data("id");
		}
		target = "#chan-" + target;
		chat.find(target)
			.find(".messages")
			.append(render("msg", {messages: [data.msg]}))
			.trigger("msg", [
				target,
				data.msg
			]);
	});

	socket.on("more", function(data) {
		var target = data.chan;
		var chan = chat
			.find("#chan-" + target)
			.find(".messages")
			.prepend(render("msg", {messages: data.messages}))
			.end();
		if (data.messages.length != 100) {
			var more = chan
				.find(".show-more")
				.remove();
		}
	});

	socket.on("network", function(data) {
		sidebar.find(".empty").hide();
		sidebar.find(".networks").append(
			render("network", {
				networks: [data.network]
			})
		);
		chat.append(
			render("chat", {
				channels: data.network.channels
			})
		);
		sidebar.find(".chan")
			.last()
			.trigger("click");
		$("#connect")
			.find(".btn")
			.prop("disabled", false)
			.end()
			.find("input")
			.each(function() {
				var self = $(this);
				self.val(self.data("default"));
			});
	});

	socket.on("nick", function(data) {
		console.log(data);
	});

	socket.on("part", function(data) {
		var id = data.chan;
		sidebar.find("[data-target=#chan-" + id + "]")
			.remove()
			.end()
			.find(".chan")
			.eq(0)
			.trigger("click");
	});

	socket.on("quit", function(data) {
		var id = data.network;
		sidebar.find("#network-" + id)
			.remove()
			.end();
		var chan = sidebar.find(".chan")
			.eq(0)
			.trigger("click");
		if (chan.length === 0) {
			sidebar.find(".empty").show();
		}
	});

	socket.on("users", function(data) {
		chat.find("#chan-" + data.chan)
			.find(".users")
			.html(render("user", data));
	});

	$("#connect")
		.find("input")
		.each(function() {
			var self = $(this);
			self.data("default", self.val());
		});

	$.cookie.json = true;

	var settings = $("#settings");
	var options = $.extend({
		join: true,
		mode: true,
		motd: false,
		nick: true,
		notification: true,
		part: true,
		quit: true,
	}, $.cookie("settings"));

	for (var i in options) {
		if (options[i]) {
			settings.find("input[name=" + i + "]").prop("checked", true);
		}
	}

	settings.on("change", "input", function() {
		var self = $(this);
		var name = self.attr("name");
		options[name] = self.prop("checked");
		$.cookie("settings", options);
		if ([
			"join",
			"mode",
			"motd",
			"nick",
			"part",
			"quit",
		].indexOf(name) !== -1) {
			chat.toggleClass("hide-" + name, !self.prop("checked"));
		}
	}).find("input")
		.trigger("change");

	var viewport = $("#viewport");

	viewport.on("click", ".lt, .rt", function(e) {
		var self = $(this);
		viewport.toggleClass(self.attr("class"));
		if (viewport.is(".lt, .rt")) {
			e.stopPropagation();
			chat.find(".chat").one("click", function() {
				viewport.removeClass("lt rt");
			});
		}
	});

	var input = $("#input")
		.history()
		.tab(complete, {hint: false});

	var form = $("#form").on("submit", function(e) {
		e.preventDefault();
		var text = input.val();
		input.val("");
		socket.emit("input", {
			target: chat.data("id"),
			text: text
		});
	});

	var top = 1;
	sidebar.on("click", "button", function() {
		var self = $(this);
		var target = self.data("target");
		if (!target) {
			return;
		}

		if (self.hasClass("chan")) {
			$.cookie("target", target);
		}
		chat.data(
			"id",
			self.data("id")
		);

		sidebar.find(".active").removeClass("active");
		self.addClass("active")
			.find(".badge")
			.removeClass("highlight")
			.data("count", "")
			.empty();

		if (sidebar.find(".highlight").length === 0) {
			favico.badge("");
		}

		viewport.removeClass();
		$("#windows .active").removeClass("active");

		var chan = $(target)
			.addClass("active")
			.trigger("show")
			.css("z-index", top++)
			.find(".chat")
			.sticky()
			.end();

		if (screen.width > 768 && chan.hasClass("chan")) {
			input.focus();
		}
	});

	sidebar.on("click", "#sign-out", function() {
		location.reload();
	});

	sidebar.on("click", ".close", function() {
		var cmd = "/close";
		var chan = $(this).closest(".chan");
		if (chan.hasClass("lobby")) {
			cmd = "/quit";
			var server = chan
				.clone()
				.remove("span")
				.text()
				.trim();
			if (!confirm("Disconnect from " + server + "?")) {
				return false;
			}
		}
		socket.emit("input", {
			target: chan.data("id"),
			text: cmd
		});
		chan.css({
			transition: "none",
			opacity: 0.4
		});
		return false;
	});

	chat.on("input", ".search", function() {
		var value = $(this).val().toLowerCase();
		var names = $(this).closest(".users").find(".names");
		names.find("button").each(function() {
			var btn = $(this);
			var name = btn.text().toLowerCase().replace(/[+%@~]/, "");
			if (name.indexOf(value) === 0) {
				btn.show();
			} else {
				btn.hide();
			}
		});
	});

	chat.on("click", ".user", function() {
		var user = $(this).html().trim().replace(/[+%@~]/, "");
		if (user.indexOf("#") !== -1) {
			return;
		}
		var text = "/whois " + user;
		socket.emit("input", {
			target: chat.data("id"),
			text: text
		});
	});

	chat.on("msg", ".messages", function(e, target, msg) {
		var btn = sidebar.find(".chan[data-target=" + target + "]:not(.active)");
		var query = btn.hasClass("query");
		var type = msg.type;
		var highlight = type.contains("highlight");
		if (highlight || query) {
			pop.play();
			if (!document.hasFocus() || !$(target).hasClass("active")) {
				favico.badge("!");
				if (Notification.permission === 'granted') {
					var n = new Notification( msg.from + ' - ' + btn.data('title'), {
						body: msg.text
					} );
					n.onclick = function() {
						window.focus();
						btn.click();
						this.close();
					};
					// Close notification after 2s
					window.setTimeout(function() {
						n.close();
					}, 2 * 1000);
				}
			}
		}

		if (btn.length === 0) {
			return;
		}

		var ignore = [
			"join",
			"part",
			"quit",
			"nick"
		];
		if ($.inArray(type, ignore) !== -1){
			return;
		}

		var badge = btn.find(".badge");
		if (badge.length !== 0) {
			var i = (badge.data("count") || 0) + 1;
			badge.data("count", i);
			badge.html(i > 999 ? (i / 1000).toFixed(1) + "k" : i);
			if (highlight || query) {
				badge.addClass("highlight");
			}
		}
	});

	chat.on("click", ".show-more", function() {
		var self = $(this);
		var id = self.data("id");
		var count = self.next(".messages").children().length;
		socket.emit("more", {
			target: id,
			count: count
		});
	});

	var windows = $("#windows");
	var forms = $("#sign-in, #connect");

	windows.on("show", "#sign-in", function() {
		var self = $(this);
		var inputs = self.find("input");
		inputs.each(function() {
			var self = $(this);
			if (self.val() === "") {
				self.focus();
				return false;
			}
		});
	});

	windows.on("click", ".input", function() {
		$(this).select();
	});

	forms.on("submit", "form", function(e) {
		e.preventDefault();
		var event = "auth";
		var form = $(this);
		if (form.closest(".window").attr("id") == "connect") {
			event = "conn";
			form.find(".btn")
				.attr("disabled", true)
				.end();
		}
		var values = {};
		$.each(form.serializeArray(), function(i, obj) {
			if (obj.value !== "") {
				values[obj.name] = obj.value;
			}
		});
		if (values.user) {
			$.cookie("user", values.user);
		}
		socket.emit(
			event, values
		);
	});

	Mousetrap.bind([
		"command+up",
		"command+down",
		"ctrl+up",
		"ctrl+down"
	], function(e, keys) {
		var channels = sidebar.find(".chan");
		var index = channels.index(channels.filter(".active"));

		var direction = keys.split("+").pop();
		switch (direction) {
		case "up":
			// Wrap around!
			var upTarget = (channels.length + (index - 1 + channels.length)) % channels.length;
			channels.eq(upTarget).click();
			break;

		case "down":
			// Wrap aroud!
			var downTarget = (channels.length + (index + 1 + channels.length)) % channels.length;
			channels.eq(downTarget).click();
			break;
		}
	});

	function complete(word) {
		var words = commands.slice();
		var users = chat.find(".active")
			.find(".names")
			.children()
			.each(function() {
				words.push($(this).text().replace(/[+%@~]/, ""));
			});
		var channels = sidebar.find(".channel")
			.each(function() {
				var chan = $(this).clone().remove("span").text().trim();
				words.push(chan);
			});
		return $.grep(
			words,
			function(w) {
				return !w.toLowerCase().indexOf(word.toLowerCase());
			}
		);
	}

	document.addEventListener(
		"visibilitychange",
		function() {
			if (sidebar.find(".highlight").length === 0) {
				favico.badge("");
			}
		}
	);
});
<commit_msg>Increase notification appearance timeout to 5s from 2s<commit_after>$(function() {
	var socket = io();
	var commands = [
		"/close",
		"/connect",
		"/deop",
		"/devoice",
		"/disconnect",
		"/invite",
		"/join",
		"/kick",
		"/leave",
		"/mode",
		"/msg",
		"/nick",
		"/notice",
		"/op",
		"/part",
		"/query",
		"/quit",
		"/raw",
		"/say",
		"/send",
		"/server",
		"/slap",
		"/topic",
		"/voice",
		"/whois"
	];

	var sidebar = $("#sidebar");
	var chat = $("#chat");

	try {
		var pop = new Audio();
		pop.src = "/audio/pop.ogg";
	} catch(e) {
		var pop = {
			play: $.noop
		};
	}

	// Request notification permissions if we don't already have it
	if (Notification.permission !== 'granted') {
		Notification.requestPermission();
	}

	$("#play").on("click", function() { pop.play(); });
	$("#footer .icon").tooltip();

	var favico = new Favico({
		animation: "none"
	});

	function render(name, data) {
		return Handlebars.templates[name](data);
	}

	Handlebars.registerHelper(
		"partial", function(id) {
			return new Handlebars.SafeString(render(id, this));
		}
	);

	socket.on("error", function(e) {
		console.log(e);
	});

	socket.on("connect_error", function(e) {
		location.reload();
	});

	socket.on("auth", function(data) {
		var body = $("body");
		var login = $("#sign-in");
		if (!login.length) {
			location.reload();
			return;
		}
		if (body.hasClass("signed-out")) {
			var error = login.find(".error");
			error.show().closest("form").one("submit", function() {
				error.hide();
			});
		}
		body.addClass("signed-out");
		var input = login.find("input[name='user']");
		if (input.val() === "") {
			input.val($.cookie("user") || "");
		}
		sidebar.find(".sign-in")
			.click()
			.end()
			.find(".networks")
			.html("")
			.next()
			.show();
	});

	socket.on("init", function(data) {
		if (data.networks.length === 0) {
			$("#footer").find(".connect").trigger("click");
			return;
		}

		sidebar.find(".networks").html(
			render("network", {
				networks: data.networks
			})
		);

		var channels = $.map(data.networks, function(n) {
			return n.channels;
		});
		chat.html(
			render("chat", {
				channels: channels
			})
		);

		sidebar.find(".empty").hide();
		$("body").removeClass("signed-out");
		$("#sign-in").detach();

		var id = $.cookie("target");
		var target = sidebar.find("[data-target='" + id + "']").trigger("click");
		if (target.length === 0) {
			var first = sidebar.find(".chan")
				.eq(0)
				.trigger("click");
			if (first.length === 0) {
				$("#footer").find(".connect").trigger("click");
			}
		}
	});

	socket.on("join", function(data) {
		var id = data.network;
		var network = sidebar.find("#network-" + id);
		network.append(
			render("chan", {
				channels: [data.chan]
			})
		);
		chat.append(
			render("chat", {
				channels: [data.chan]
			})
		);
		sidebar.find(".chan")
			.sort(function(a, b) { return $(a).data("id") - $(b).data("id"); })
			.last()
			.trigger("click");
	});

	socket.on("msg", function(data) {
		var target = data.chan;
		if (data.msg.type == "error") {
			target = chat.find(".active").data("id");
		}
		target = "#chan-" + target;
		chat.find(target)
			.find(".messages")
			.append(render("msg", {messages: [data.msg]}))
			.trigger("msg", [
				target,
				data.msg
			]);
	});

	socket.on("more", function(data) {
		var target = data.chan;
		var chan = chat
			.find("#chan-" + target)
			.find(".messages")
			.prepend(render("msg", {messages: data.messages}))
			.end();
		if (data.messages.length != 100) {
			var more = chan
				.find(".show-more")
				.remove();
		}
	});

	socket.on("network", function(data) {
		sidebar.find(".empty").hide();
		sidebar.find(".networks").append(
			render("network", {
				networks: [data.network]
			})
		);
		chat.append(
			render("chat", {
				channels: data.network.channels
			})
		);
		sidebar.find(".chan")
			.last()
			.trigger("click");
		$("#connect")
			.find(".btn")
			.prop("disabled", false)
			.end()
			.find("input")
			.each(function() {
				var self = $(this);
				self.val(self.data("default"));
			});
	});

	socket.on("nick", function(data) {
		console.log(data);
	});

	socket.on("part", function(data) {
		var id = data.chan;
		sidebar.find("[data-target=#chan-" + id + "]")
			.remove()
			.end()
			.find(".chan")
			.eq(0)
			.trigger("click");
	});

	socket.on("quit", function(data) {
		var id = data.network;
		sidebar.find("#network-" + id)
			.remove()
			.end();
		var chan = sidebar.find(".chan")
			.eq(0)
			.trigger("click");
		if (chan.length === 0) {
			sidebar.find(".empty").show();
		}
	});

	socket.on("users", function(data) {
		chat.find("#chan-" + data.chan)
			.find(".users")
			.html(render("user", data));
	});

	$("#connect")
		.find("input")
		.each(function() {
			var self = $(this);
			self.data("default", self.val());
		});

	$.cookie.json = true;

	var settings = $("#settings");
	var options = $.extend({
		join: true,
		mode: true,
		motd: false,
		nick: true,
		notification: true,
		part: true,
		quit: true,
	}, $.cookie("settings"));

	for (var i in options) {
		if (options[i]) {
			settings.find("input[name=" + i + "]").prop("checked", true);
		}
	}

	settings.on("change", "input", function() {
		var self = $(this);
		var name = self.attr("name");
		options[name] = self.prop("checked");
		$.cookie("settings", options);
		if ([
			"join",
			"mode",
			"motd",
			"nick",
			"part",
			"quit",
		].indexOf(name) !== -1) {
			chat.toggleClass("hide-" + name, !self.prop("checked"));
		}
	}).find("input")
		.trigger("change");

	var viewport = $("#viewport");

	viewport.on("click", ".lt, .rt", function(e) {
		var self = $(this);
		viewport.toggleClass(self.attr("class"));
		if (viewport.is(".lt, .rt")) {
			e.stopPropagation();
			chat.find(".chat").one("click", function() {
				viewport.removeClass("lt rt");
			});
		}
	});

	var input = $("#input")
		.history()
		.tab(complete, {hint: false});

	var form = $("#form").on("submit", function(e) {
		e.preventDefault();
		var text = input.val();
		input.val("");
		socket.emit("input", {
			target: chat.data("id"),
			text: text
		});
	});

	var top = 1;
	sidebar.on("click", "button", function() {
		var self = $(this);
		var target = self.data("target");
		if (!target) {
			return;
		}

		if (self.hasClass("chan")) {
			$.cookie("target", target);
		}
		chat.data(
			"id",
			self.data("id")
		);

		sidebar.find(".active").removeClass("active");
		self.addClass("active")
			.find(".badge")
			.removeClass("highlight")
			.data("count", "")
			.empty();

		if (sidebar.find(".highlight").length === 0) {
			favico.badge("");
		}

		viewport.removeClass();
		$("#windows .active").removeClass("active");

		var chan = $(target)
			.addClass("active")
			.trigger("show")
			.css("z-index", top++)
			.find(".chat")
			.sticky()
			.end();

		if (screen.width > 768 && chan.hasClass("chan")) {
			input.focus();
		}
	});

	sidebar.on("click", "#sign-out", function() {
		location.reload();
	});

	sidebar.on("click", ".close", function() {
		var cmd = "/close";
		var chan = $(this).closest(".chan");
		if (chan.hasClass("lobby")) {
			cmd = "/quit";
			var server = chan
				.clone()
				.remove("span")
				.text()
				.trim();
			if (!confirm("Disconnect from " + server + "?")) {
				return false;
			}
		}
		socket.emit("input", {
			target: chan.data("id"),
			text: cmd
		});
		chan.css({
			transition: "none",
			opacity: 0.4
		});
		return false;
	});

	chat.on("input", ".search", function() {
		var value = $(this).val().toLowerCase();
		var names = $(this).closest(".users").find(".names");
		names.find("button").each(function() {
			var btn = $(this);
			var name = btn.text().toLowerCase().replace(/[+%@~]/, "");
			if (name.indexOf(value) === 0) {
				btn.show();
			} else {
				btn.hide();
			}
		});
	});

	chat.on("click", ".user", function() {
		var user = $(this).html().trim().replace(/[+%@~]/, "");
		if (user.indexOf("#") !== -1) {
			return;
		}
		var text = "/whois " + user;
		socket.emit("input", {
			target: chat.data("id"),
			text: text
		});
	});

	chat.on("msg", ".messages", function(e, target, msg) {
		var btn = sidebar.find(".chan[data-target=" + target + "]:not(.active)");
		var query = btn.hasClass("query");
		var type = msg.type;
		var highlight = type.contains("highlight");
		if (highlight || query) {
			pop.play();
			if (!document.hasFocus() || !$(target).hasClass("active")) {
				favico.badge("!");
				if (Notification.permission === 'granted') {
					var n = new Notification( msg.from + ' - ' + btn.data('title'), {
						body: msg.text
					} );
					n.onclick = function() {
						window.focus();
						btn.click();
						this.close();
					};
					// Close notification after 2s
					window.setTimeout(function() {
						n.close();
					}, 5 * 1000);
				}
			}
		}

		if (btn.length === 0) {
			return;
		}

		var ignore = [
			"join",
			"part",
			"quit",
			"nick"
		];
		if ($.inArray(type, ignore) !== -1){
			return;
		}

		var badge = btn.find(".badge");
		if (badge.length !== 0) {
			var i = (badge.data("count") || 0) + 1;
			badge.data("count", i);
			badge.html(i > 999 ? (i / 1000).toFixed(1) + "k" : i);
			if (highlight || query) {
				badge.addClass("highlight");
			}
		}
	});

	chat.on("click", ".show-more", function() {
		var self = $(this);
		var id = self.data("id");
		var count = self.next(".messages").children().length;
		socket.emit("more", {
			target: id,
			count: count
		});
	});

	var windows = $("#windows");
	var forms = $("#sign-in, #connect");

	windows.on("show", "#sign-in", function() {
		var self = $(this);
		var inputs = self.find("input");
		inputs.each(function() {
			var self = $(this);
			if (self.val() === "") {
				self.focus();
				return false;
			}
		});
	});

	windows.on("click", ".input", function() {
		$(this).select();
	});

	forms.on("submit", "form", function(e) {
		e.preventDefault();
		var event = "auth";
		var form = $(this);
		if (form.closest(".window").attr("id") == "connect") {
			event = "conn";
			form.find(".btn")
				.attr("disabled", true)
				.end();
		}
		var values = {};
		$.each(form.serializeArray(), function(i, obj) {
			if (obj.value !== "") {
				values[obj.name] = obj.value;
			}
		});
		if (values.user) {
			$.cookie("user", values.user);
		}
		socket.emit(
			event, values
		);
	});

	Mousetrap.bind([
		"command+up",
		"command+down",
		"ctrl+up",
		"ctrl+down"
	], function(e, keys) {
		var channels = sidebar.find(".chan");
		var index = channels.index(channels.filter(".active"));

		var direction = keys.split("+").pop();
		switch (direction) {
		case "up":
			// Wrap around!
			var upTarget = (channels.length + (index - 1 + channels.length)) % channels.length;
			channels.eq(upTarget).click();
			break;

		case "down":
			// Wrap aroud!
			var downTarget = (channels.length + (index + 1 + channels.length)) % channels.length;
			channels.eq(downTarget).click();
			break;
		}
	});

	function complete(word) {
		var words = commands.slice();
		var users = chat.find(".active")
			.find(".names")
			.children()
			.each(function() {
				words.push($(this).text().replace(/[+%@~]/, ""));
			});
		var channels = sidebar.find(".channel")
			.each(function() {
				var chan = $(this).clone().remove("span").text().trim();
				words.push(chan);
			});
		return $.grep(
			words,
			function(w) {
				return !w.toLowerCase().indexOf(word.toLowerCase());
			}
		);
	}

	document.addEventListener(
		"visibilitychange",
		function() {
			if (sidebar.find(".highlight").length === 0) {
				favico.badge("");
			}
		}
	);
});
<|endoftext|>


<commit_before>// Copyright © 2013-2014 Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0

var Q = require('q');
var db = require('./db');

var analytics = {};

analytics._map = function () {

    function convertVariablesToLists(entry) {
        var result = {};
        for (var property in entry) {
            if (entry.hasOwnProperty(property) && property !== 'timepoint') {
                result[property] = [ entry[property] ];
            }
        }
        return result;
    }

    function roundByMultiple(n, multiple) {
        return Math.round(n / multiple) * multiple
    }

    var playerTimeData = this.playerTimeData;
    for (var playerId in  playerTimeData) {
        if (playerTimeData.hasOwnProperty(playerId)) {
            var entries = playerTimeData[playerId];
            var startTime = null;
            entries.forEach(function (entry) {
                startTime = startTime || entry.timepoint;
                var relativeTime = entry.timepoint - startTime;

                var result = convertVariablesToLists(entry);
                result.timepoint = roundByMultiple(relativeTime, 5000);
                emit(result.timepoint, result);
            })
        }
    }
};

analytics._reduce = function (id, entries) {

    function compareNumbers(a, b) {
        return a - b;
    }

    function copyOrAppendProperties(dest, src) {
        for (var property in src) {
            if (src.hasOwnProperty(property)) {
                if (src[property].length) {
                    var values = dest[property] = (dest[property] || []);
                    src[property].forEach(function (value) {
                        values.push(value);
                    });
                } else {
                    dest[property] = src[property];
                }
            }
        }
    }

    function sortListProperties(obj) {
        for (var property in obj) {
            if (obj.hasOwnProperty(property) && obj[property].length) {
                obj[property].sort(compareNumbers);
            }
        }
    }

    var reduced = {};
    for (var i = 0; i < entries.length; i++) {
        copyOrAppendProperties(reduced, entries[i]);
    }
    sortListProperties(reduced);
    return reduced;
};

analytics._finalize = function (id, merged) {

    function deduplicate(values, percentiles) {
        for (var i = 0; i < values.length - 1; i++) {
            if (values[i] === values[i + 1] || percentiles[i] === percentiles[i + 1]) {
                values.splice(i, 1);
                percentiles.splice(i, 1);
                i--;
            }
        }
    }

    function calculatePercentiles(values) {
        var percentiles = [];
        var count = values.length;
        for (var i = 0; i < count; i++) {
            percentiles.push(Math.round(100 * (i + 1) / count)); // "a*c/b" instead of "a/(b/c)" produces more accurate fractions
        }
        deduplicate(values, percentiles);
        return { values: values, percentiles: percentiles };
    }

    for (var property in merged) {
        if (merged.hasOwnProperty(property) && merged[property].length) {
            merged[property] = calculatePercentiles(merged[property]);
        }
    }
    return merged;
};

analytics.refresh = function () {
    return db.games.mapReduce(
        analytics._map,
        analytics._reduce,
        {
            out: db.percentiles.name(),
            finalize: analytics._finalize
        });
};

analytics.at = function (timepoint) {
    timepoint = Math.max(0, timepoint);
    return db.percentiles
        .find({ 'value.timepoint': { $lte: timepoint }})
        .sort({ 'value.timepoint': -1 })
        .limit(1)
        .next()
        .then(function (doc) {
            if (!doc) {
                throw new Error("No data found at timepoint " + timepoint);
            }
            return doc.value;
        });
};

analytics.refreshAndGet = function (timepoint) {
    return analytics.refresh().then(function () {
        return analytics.at(timepoint);
    });
};

module.exports = analytics;
<commit_msg>Debug logging<commit_after>// Copyright © 2013-2014 Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0

var Q = require('q');
var db = require('./db');

var analytics = {};

analytics._map = function () {
    var game = this;

    function convertVariablesToLists(entry) {
        var result = {};
        for (var property in entry) {
            if (entry.hasOwnProperty(property) && property !== 'timepoint') {
                result[property] = [ entry[property] ];
            }
        }
        return result;
    }

    function roundByMultiple(n, multiple) {
        return Math.round(n / multiple) * multiple
    }

    var playerTimeData = game.playerTimeData;
    for (var playerId in  playerTimeData) {
        if (playerTimeData.hasOwnProperty(playerId)) {
            var entries = playerTimeData[playerId];
            var startTime = null;
            entries.forEach(function (entry) {
                startTime = startTime || entry.timepoint;
                var relativeTime = entry.timepoint - startTime;

                var result = convertVariablesToLists(entry);
                result.timepoint = roundByMultiple(relativeTime, 5000);
                emit(result.timepoint, result);

                // TODO: remove this debug logging after locating the funny games
                if (result.timepoint === 0 && entry.armyCount > 100) {
                    print("Game", game.gameId, "had armyCount", entry.armyCount, "at timepoint", entry.timepoint, "/ relativeTime", relativeTime);
                }
            })
        }
    }
};

analytics._reduce = function (id, entries) {

    function compareNumbers(a, b) {
        return a - b;
    }

    function copyOrAppendProperties(dest, src) {
        for (var property in src) {
            if (src.hasOwnProperty(property)) {
                if (src[property].length) {
                    var values = dest[property] = (dest[property] || []);
                    src[property].forEach(function (value) {
                        values.push(value);
                    });
                } else {
                    dest[property] = src[property];
                }
            }
        }
    }

    function sortListProperties(obj) {
        for (var property in obj) {
            if (obj.hasOwnProperty(property) && obj[property].length) {
                obj[property].sort(compareNumbers);
            }
        }
    }

    var reduced = {};
    for (var i = 0; i < entries.length; i++) {
        copyOrAppendProperties(reduced, entries[i]);
    }
    sortListProperties(reduced);
    return reduced;
};

analytics._finalize = function (id, merged) {

    function deduplicate(values, percentiles) {
        for (var i = 0; i < values.length - 1; i++) {
            if (values[i] === values[i + 1] || percentiles[i] === percentiles[i + 1]) {
                values.splice(i, 1);
                percentiles.splice(i, 1);
                i--;
            }
        }
    }

    function calculatePercentiles(values) {
        var percentiles = [];
        var count = values.length;
        for (var i = 0; i < count; i++) {
            percentiles.push(Math.round(100 * (i + 1) / count)); // "a*c/b" instead of "a/(b/c)" produces more accurate fractions
        }
        deduplicate(values, percentiles);
        return { values: values, percentiles: percentiles };
    }

    for (var property in merged) {
        if (merged.hasOwnProperty(property) && merged[property].length) {
            merged[property] = calculatePercentiles(merged[property]);
        }
    }
    return merged;
};

analytics.refresh = function () {
    return db.games.mapReduce(
        analytics._map,
        analytics._reduce,
        {
            out: db.percentiles.name(),
            finalize: analytics._finalize
        });
};

analytics.at = function (timepoint) {
    timepoint = Math.max(0, timepoint);
    return db.percentiles
        .find({ 'value.timepoint': { $lte: timepoint }})
        .sort({ 'value.timepoint': -1 })
        .limit(1)
        .next()
        .then(function (doc) {
            if (!doc) {
                throw new Error("No data found at timepoint " + timepoint);
            }
            return doc.value;
        });
};

analytics.refreshAndGet = function (timepoint) {
    return analytics.refresh().then(function () {
        return analytics.at(timepoint);
    });
};

module.exports = analytics;
<|endoftext|>


<commit_before>// Turn on full stack traces in errors to help debugging
Error.stackTraceLimit = Infinity;

jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;

// // Cancel Karma's synchronous start,
// // we will call `__karma__.start()` later, once all the specs are loaded.
__karma__.loaded = function() {};

System.config({baseURL: '/base'});

System.config({
  paths: {
    // paths serve as alias
    'npm:': 'node_modules/'
  },
  // map tells the System loader where to look for things
  map: {
    // our temp folder
    temp: 'temp',

    // angular bundles
    '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
    '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
    '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
    '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
    '@angular/platform-browser-dynamic':
        'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
    '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
    '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
    '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',

    // angular testing umd bundles
    '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
    '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
    '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
    '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
    '@angular/platform-browser-dynamic/testing':
        'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
    '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
    '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
    '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js',

    // other libraries
    'rxjs': 'npm:rxjs'
  },
  packages: {temp: {main: 'core.js', defaultExtension: 'js'}, rxjs: {defaultExtension: 'js'}}
});

System.import('@angular/core/testing')
    .then(function(coreTesting) {
      return System.import('@angular/platform-browser-dynamic/testing').then(function(browserTesting) {
        coreTesting.getTestBed().initTestEnvironment(
            browserTesting.BrowserDynamicTestingModule, browserTesting.platformBrowserDynamicTesting());
      });
    })
    .then(function() { return Promise.all(customMatchers()); })
    .then(function() { return Promise.all(resolveTestFiles()); })
    .then(function() { __karma__.start(); }, function(error) { __karma__.error(error.stack || error); });

function createPathRecords(pathsMapping, appPath) {
  // creates local module name mapping to global path with karma's fingerprint in path, e.g.:
  // './accordion/accordion':
  // '/base/temp/accordion/accordion.js?f4523daf879cfb7310ef6242682ccf10b2041b3e'
  var pathParts = appPath.split('/');
  var moduleName = './' + pathParts.slice(Math.max(pathParts.length - 2, 1)).join('/');
  moduleName = moduleName.replace(/\.js$/, '');
  pathsMapping[moduleName] = appPath + '?' + window.__karma__.files[appPath];
  return pathsMapping;
}

function customMatchers() {
  return System.import('/base/temp/test/matchers.js');
}

function onlyBuiltFiles(filePath) {
  return /^\/base\/temp\//.test(filePath);
}

function onlySpecFiles(path) {
  return /\.spec\.js$/.test(path);
}

function resolveTestFiles() {
  return Object
      .keys(window.__karma__.files)  // All files served by Karma.
      .filter(onlyBuiltFiles)
      .filter(onlySpecFiles)
      .map(function(moduleName) {
        // loads all spec files via their global module names (e.g.
        // 'base/temp/accordion/accordion.test')
        return System.import(moduleName);
      });
}
<commit_msg>fix: add ie polyfills<commit_after>// Turn on full stack traces in errors to help debugging
Error.stackTraceLimit = Infinity;

jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;

// // Cancel Karma's synchronous start,
// // we will call `__karma__.start()` later, once all the specs are loaded.
__karma__.loaded = function() {};

System.config({baseURL: '/base'});

System.config({
  paths: {
    // paths serve as alias
    'npm:': 'node_modules/'
  },
  // map tells the System loader where to look for things
  map: {
    // our temp folder
    temp: 'temp',
    // polyfills
    'core-js/es6/reflect': 'npm:core-js/es6/reflect.js',
    'core-js/es7/reflect': 'npm:core-js/es7/reflect.js',
    'zone.js/dist/zone': 'npm:zone.js/dist/zone.js',
    'zone.js/dist/long-stack-trace-zone': 'npm:zone.js/dist/long-stack-trace-zone.js',
    'zone.js/dist/proxy': 'npm:zone.js/dist/proxy.js',
    'zone.js/dist/sync-test': 'npm:zone.js/dist/sync-test.js',
    'zone.js/dist/jasmine-patch': 'npm:zone.js/dist/jasmine-patch.js',
    'zone.js/dist/async-test': 'npm:zone.js/dist/async-test.js',
    'zone.js/dist/fake-async-test': 'npm:zone.js/dist/fake-async-test.js',

    // angular bundles
    '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
    '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
    '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
    '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
    '@angular/platform-browser-dynamic':
        'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
    '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
    '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
    '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',

    // angular testing umd bundles
    '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js',
    '@angular/common/testing': 'npm:@angular/common/bundles/common-testing.umd.js',
    '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js',
    '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js',
    '@angular/platform-browser-dynamic/testing':
        'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
    '@angular/http/testing': 'npm:@angular/http/bundles/http-testing.umd.js',
    '@angular/router/testing': 'npm:@angular/router/bundles/router-testing.umd.js',
    '@angular/forms/testing': 'npm:@angular/forms/bundles/forms-testing.umd.js',

    // other libraries
    'rxjs': 'npm:rxjs'
  },
  packages: {temp: {main: 'core.js', defaultExtension: 'js'}, rxjs: {defaultExtension: 'js'}}
});

System.import('@angular/core/testing')
    .then(function(coreTesting) {
      return System.import('@angular/platform-browser-dynamic/testing').then(function(browserTesting) {
        coreTesting.getTestBed().initTestEnvironment(
            browserTesting.BrowserDynamicTestingModule, browserTesting.platformBrowserDynamicTesting());
      });
    })
    .then(function() { return Promise.all(customMatchers()); })
    .then(function() { return Promise.all(resolveTestFiles()); })
    .then(function() { __karma__.start(); }, function(error) { __karma__.error(error.stack || error); });

function createPathRecords(pathsMapping, appPath) {
  // creates local module name mapping to global path with karma's fingerprint in path, e.g.:
  // './accordion/accordion':
  // '/base/temp/accordion/accordion.js?f4523daf879cfb7310ef6242682ccf10b2041b3e'
  var pathParts = appPath.split('/');
  var moduleName = './' + pathParts.slice(Math.max(pathParts.length - 2, 1)).join('/');
  moduleName = moduleName.replace(/\.js$/, '');
  pathsMapping[moduleName] = appPath + '?' + window.__karma__.files[appPath];
  return pathsMapping;
}

function customMatchers() {
  return System.import('/base/temp/test/matchers.js');
}

function onlyBuiltFiles(filePath) {
  return /^\/base\/temp\//.test(filePath);
}

function onlySpecFiles(path) {
  return /\.spec\.js$/.test(path);
}

function resolveTestFiles() {
  return Object
      .keys(window.__karma__.files)  // All files served by Karma.
      .filter(onlyBuiltFiles)
      .filter(onlySpecFiles)
      .map(function(moduleName) {
        // loads all spec files via their global module names (e.g.
        // 'base/temp/accordion/accordion.test')
        return System.import(moduleName);
      });
}
<|endoftext|>


<commit_before>const getSchedule = () => {
  return `
  2018 Schedule
    9/10: Rams
    9/16: @ Broncos
    9/23: @ Dolphins
    9/30: Browns
    10/7: @ Chargers
    10/14: @ London
    10/21: BYE
    `;
};

module.exports = getSchedule;
<commit_msg>full schedule<commit_after>const getSchedule = () => {
  return `
  2018 Schedule
    9/10: Rams (MNF)
    9/16: @ Broncos
    9/23: @ Dolphins
    9/30: Browns
    10/7: @ Chargers
    10/14: Seahawks (London)
    10/21: BYE
    10/28: Colts
    11/1: @ SF (Thurs)
    11/11: Chargers
    11/18: @ Cardinals
    11/25: @ Baltimore
    12/2: Chiefs
    12/9: Steelers (SNF)
    12/16: @ Bengals
    12/24: Broncos (MNF)
    12/30: @ Chiefs
    `;
};

module.exports = getSchedule;
<|endoftext|>


<commit_before>function depthFirstSearch(node) {
	if (typeof node.children != "undefined") {
		for (var i=0, c=node.children.length; i<c; i++) {
			depthFirstSearch(node.children[i]);
		}
	}
}

function gatherTips(node, tips) {
	if (typeof node.children != "undefined") {
		for (var i=0, c=node.children.length; i<c; i++) {
			gatherTips(node.children[i], tips);
		}
	}
	else {
		tips.push(node);
	}
	return tips;
}

function gatherInternals(node, internals) {
	if (typeof node.children != "undefined") {
		internals.push(node);
		for (var i=0, c=node.children.length; i<c; i++) {
			gatherInternals(node.children[i], internals);
		}
	}
	return internals;
}

function setFrequencies(node) {
	if (typeof node.frequency == "undefined") {
		node.frequency = 0.01;
	}
	if (typeof node.children != "undefined") {
		for (var i=0, c=node.children.length; i<c; i++) {
			setFrequencies(node.children[i]);
		}
	}
}

/*
function setFrequencies(node, date) {
	if (typeof node.frequencies != "undefined") {
		var sdate = ymd_format(date);
		var dates = [];
		for (var i=0, c=node.frequencies.length; i<c; i++) {
			dates.push(node.frequencies[i].date);
		}
		var index = d3.bisect(dates, sdate) - 1;
		node.frequency = node.frequencies[index].frequency;
	}
	if (typeof node.children != "undefined") {
		for (var i=0, c=node.children.length; i<c; i++) {
			setFrequencies(node.children[i], date);
		}
	}
}
*/

function minimumAttribute(node, attr, min) {
	if (typeof node.children != "undefined") {
		for (var i=0, c=node.children.length; i<c; i++) {
			min = minimumAttribute(node.children[i], attr, min);
		}
	}
	else {
		if (node[attr] < min) {
			min = node[attr];
		}
	}
	return min;
}

function maximumAttribute(node, attr, max) {
	if (typeof node.children != "undefined") {
		for (var i=0, c=node.children.length; i<c; i++) {
			max = maximumAttribute(node.children[i], attr, max);
		}
	}
	else {
		if (node[attr] > max) {
			max = node[attr];
		}
	}
	return max;
}

var width = 800,
	height = 600;

var globalDate = new Date();
var ymd_format = d3.time.format("%Y-%m-%d");

var tree = d3.layout.tree()
	.size([height, width]);

var treeplot = d3.select("#treeplot")
	.attr("width", width)
	.attr("height", height);

var tooltip = d3.tip()
	.direction('e')
	.attr('class', 'd3-tip')
	.offset([0, 10])
	.html(function(d) {
		string = ""
		if (typeof d.frequency != "undefined") {
			if (d.frequency > 0.01) {
				string = d.frequency;
			}
		}
		if (typeof d.target != "undefined") {
			if (typeof d.target.frequency != "undefined") {
				if (d.target.frequency > 0.01) {
					string = d.target.frequency;
				}
			}
		}
		if (typeof d.strain != "undefined") {
			string = d.strain;
		}
		return string;
	});

treeplot.call(tooltip);

function rescale(dMin, dMax, lMin, lMax, xScale, yScale, nodes, links, tips, internals) {

	var speed = 1500;
	xScale.domain([dMin,dMax]);
	yScale.domain([lMin,lMax]);

	nodes.forEach(function (d) {
		d.x = xScale(d.xvalue);
		d.y = yScale(d.yvalue);
	});

	treeplot.selectAll(".tip").data(tips)
    	.transition().duration(speed)
    	.attr("cx", function(d) { return d.x; })
    	.attr("cy", function(d) { return d.y; });

	treeplot.selectAll(".internal").data(internals)
    	.transition().duration(speed)
		.attr("x", function(d) {
			if (typeof d.frequency != "undefined") {
				return d.x - 5*Math.sqrt(d.frequency) - 0.5;
			}
			else {
				return d.x - 1;
			}
		})
		.attr("y", function(d) {
			if (typeof d.frequency != "undefined") {
				return d.y - 5*Math.sqrt(d.frequency) - 0.5;
			}
			else {
				return d.y - 1;
			}
		});

	treeplot.selectAll(".link").data(links)
    	.transition().duration(speed)
		.attr("points", function(d) {
			var mod = 5*Math.sqrt(d.target.frequency)+0.5;
			return (d.source.x-mod).toString() + "," + d.source.y.toString() + " "
			+ (d.source.x-mod).toString() + "," + d.target.y.toString() + " "
			+ (d.target.x).toString() + "," + d.target.y.toString()
		});

}

d3.json("https://s3.amazonaws.com/augur-data/data/auspice.json", function(error, root) {
//d3.json("auspice.json", function(error, root) {
	var nodes = tree.nodes(root),
		links = tree.links(nodes);

	var rootNode = nodes[0];
	var tips = gatherTips(rootNode, []);
	var internals = gatherInternals(rootNode, []);
	setFrequencies(rootNode);

	var	xValues = nodes.map(function(d) {
  		return +d.xvalue;
  	});

	var yValues = nodes.map(function(d) {
  		return +d.yvalue;
  	});

  	var dateValues = nodes.filter(function(d) {
		return typeof d.date === 'string';
  		}).map(function(d) {
  		return new Date(d.date);
  	});

  	var yearValues = nodes.filter(function(d) {
		return typeof d.date === 'string';
  		}).map(function(d) {
  		return (new Date(d.date)).getFullYear();
  	});

	var xScale = d3.scale.linear()
		.domain([d3.min(xValues), d3.max(xValues)])
		.range([10, width-10]);

	var yScale = d3.scale.linear()
		.domain([d3.min(yValues), d3.max(yValues)])
		.range([10, height-10]);

	var dateScale = d3.time.scale()
		.domain([new Date(d3.min(dateValues) + 30), new Date()])
		.range([-100, 100])
		.clamp([true]);

	var yearScale = d3.scale.ordinal()
		.domain([2014, "undefined", 2011, 2012, 2013])
		.range(["#ff7f0e", "#1f77b4", "#7f7f7f", "#7f7f7f", "#7f7f7f"]);

	var recencyScale = d3.scale.threshold()
		.domain([0.00, 0.33, 0.66, 1.0])
		.range(["#aaa", "#E04328", "#E78C36", "#CFB642", "#799CB3"]);	// red, orange, yellow, blue

	var freqScale = d3.scale.sqrt()
		.domain([0, 1])
		.range([1, 10]);

	nodes.forEach(function (d) {
		d.x = xScale(d.xvalue);
		d.y = yScale(d.yvalue);
	});

	// straight links
/*	var link = treeplot.selectAll(".link")
		.data(links)
		.enter().append("line")
		.attr("class", "link")
		.attr("x1", function(d) { return d.source.x; })
	    .attr("y1", function(d) { return d.source.y; })
	    .attr("x2", function(d) { return d.target.x; })
	    .attr("y2", function(d) { return d.target.y; });
  */
	var link = treeplot.selectAll(".link")
		.data(links)
		.enter().append("polyline")
		.attr("class", "link")
		.attr("points", function(d) {
			var mod = 5*Math.sqrt(d.target.frequency)+0.5;
			return (d.source.x-mod).toString() + "," + d.source.y.toString() + " "
			+ (d.source.x-mod).toString() + "," + d.target.y.toString() + " "
			+ (d.target.x).toString() + "," + d.target.y.toString()
		})
	    .style("stroke-width", function(d) {
			return freqScale(d.target.frequency);
	    })
		.on('mouseover', tooltip.show)
      	.on('mouseout', tooltip.hide)
     	.on('click', function(d) {
      		var dMin = minimumAttribute(d.target, "xvalue", d.target.xvalue),
      			dMax = maximumAttribute(d.target, "xvalue", d.target.xvalue),
      			lMin = minimumAttribute(d.target, "yvalue", d.target.yvalue),
      			lMax = maximumAttribute(d.target, "yvalue", d.target.yvalue);
      		rescale(dMin, dMax, lMin, lMax, xScale, yScale, nodes, links, tips, internals)
      	});

	var tipCircles = treeplot.selectAll(".tip")
		.data(tips)
		.enter()
		.append("circle")
		.attr("class", "tip")
		.attr("cx", function(d) {return d.x})
		.attr("cy", function(d) {return d.y})
		.attr("r", 2)
		.style("fill", function(d) {
			var date = new Date(d.date);
			var oneYear = 365.25*24*60*60*1000; // days*hours*minutes*seconds*milliseconds
			var diffDays = (globalDate.getTime() - date.getTime()) / oneYear;
			var col = recencyScale(diffDays);
			return d3.rgb(col).brighter([0.7]).toString();
		})
		.style("stroke", function(d) {
			var date = new Date(d.date);
			var oneYear = 365.25*24*60*60*1000; // days*hours*minutes*seconds*milliseconds
			var diffDays = (globalDate.getTime() - date.getTime()) / oneYear;
			var col = recencyScale(diffDays);
			return d3.rgb(col).toString();
		})
		.on('mouseover', tooltip.show)
      	.on('mouseout', tooltip.hide);

	var drag = d3.behavior.drag()
		.origin(function(d) { return d; })
		.on("drag", dragged);

	function dragged(d) {

		d.date = dateScale.invert(d3.event.x);
		d.x = dateScale(d.date);
		d3.selectAll(".counter-text")
			.text(function(d){
    			return format(d.date)
    		});
		globalDate = d.date;
		d3.selectAll(".tip")
			.style("fill", function(d) {
				var date = new Date(d.date);
				var oneYear = 365.25*24*60*60*1000; // days*hours*minutes*seconds*milliseconds
				var diffDays = (globalDate.getTime() - date.getTime()) / oneYear;
				var col = recencyScale(diffDays);
				return d3.rgb(col).brighter([0.7]).toString();
			})
			.style("stroke", function(d) {
				var date = new Date(d.date);
				var oneYear = 365.25*24*60*60*1000; // days*hours*minutes*seconds*milliseconds
				var diffDays = (globalDate.getTime() - date.getTime()) / oneYear;
				var col = recencyScale(diffDays);
				return d3.rgb(col).toString();
			});

	}

	var counterData = {}
	counterData['date'] = globalDate
	counterData['x'] = dateScale(globalDate)

	var format = d3.time.format("%Y %b %-d");
	var counterText = treeplot.selectAll(".counter-text")
		.data([counterData])
		.enter()
		.append("text")
		.attr("class", "counter-text")
    	.attr("transform", "translate(100,40)")
    	.style("text-anchor", "middle")
    	.style("alignment-baseline", "middle")
    	.text(function(d){
    		return format(d.date)
    	})
    	.call(drag);

	d3.select("#reset")
        .on("click", function(d) {
			var dMin = d3.min(xValues),
      			dMax = d3.max(xValues),
      			lMin = d3.min(yValues),
      			lMax = d3.max(yValues);
            rescale(dMin, dMax, lMin, lMax, xScale, yScale, nodes, links, tips, internals);
		})

});
<commit_msg>Improve sizing.<commit_after>function depthFirstSearch(node) {
	if (typeof node.children != "undefined") {
		for (var i=0, c=node.children.length; i<c; i++) {
			depthFirstSearch(node.children[i]);
		}
	}
}

function gatherTips(node, tips) {
	if (typeof node.children != "undefined") {
		for (var i=0, c=node.children.length; i<c; i++) {
			gatherTips(node.children[i], tips);
		}
	}
	else {
		tips.push(node);
	}
	return tips;
}

function gatherInternals(node, internals) {
	if (typeof node.children != "undefined") {
		internals.push(node);
		for (var i=0, c=node.children.length; i<c; i++) {
			gatherInternals(node.children[i], internals);
		}
	}
	return internals;
}

function setFrequencies(node) {
	if (typeof node.frequency == "undefined") {
		node.frequency = 0.01;
	}
	if (typeof node.children != "undefined") {
		for (var i=0, c=node.children.length; i<c; i++) {
			setFrequencies(node.children[i]);
		}
	}
}

/*
function setFrequencies(node, date) {
	if (typeof node.frequencies != "undefined") {
		var sdate = ymd_format(date);
		var dates = [];
		for (var i=0, c=node.frequencies.length; i<c; i++) {
			dates.push(node.frequencies[i].date);
		}
		var index = d3.bisect(dates, sdate) - 1;
		node.frequency = node.frequencies[index].frequency;
	}
	if (typeof node.children != "undefined") {
		for (var i=0, c=node.children.length; i<c; i++) {
			setFrequencies(node.children[i], date);
		}
	}
}
*/

function minimumAttribute(node, attr, min) {
	if (typeof node.children != "undefined") {
		for (var i=0, c=node.children.length; i<c; i++) {
			min = minimumAttribute(node.children[i], attr, min);
		}
	}
	else {
		if (node[attr] < min) {
			min = node[attr];
		}
	}
	return min;
}

function maximumAttribute(node, attr, max) {
	if (typeof node.children != "undefined") {
		for (var i=0, c=node.children.length; i<c; i++) {
			max = maximumAttribute(node.children[i], attr, max);
		}
	}
	else {
		if (node[attr] > max) {
			max = node[attr];
		}
	}
	return max;
}

var width = 800,
	height = 600;

var globalDate = new Date();
var ymd_format = d3.time.format("%Y-%m-%d");

var tree = d3.layout.tree()
	.size([height, width]);

var treeplot = d3.select("#treeplot")
	.attr("width", width)
	.attr("height", height);

var tooltip = d3.tip()
	.direction('e')
	.attr('class', 'd3-tip')
	.offset([0, 10])
	.html(function(d) {
		string = ""
		if (typeof d.frequency != "undefined") {
			if (d.frequency > 0.01) {
				string = d.frequency;
			}
		}
		if (typeof d.target != "undefined") {
			if (typeof d.target.frequency != "undefined") {
				if (d.target.frequency > 0.01) {
					string = d.target.frequency;
				}
			}
		}
		if (typeof d.strain != "undefined") {
			string = d.strain;
		}
		return string;
	});

treeplot.call(tooltip);

function rescale(dMin, dMax, lMin, lMax, xScale, yScale, nodes, links, tips, internals) {

	var speed = 1500;
	xScale.domain([dMin,dMax]);
	yScale.domain([lMin,lMax]);

	nodes.forEach(function (d) {
		d.x = xScale(d.xvalue);
		d.y = yScale(d.yvalue);
	});

	treeplot.selectAll(".tip").data(tips)
    	.transition().duration(speed)
    	.attr("cx", function(d) { return d.x; })
    	.attr("cy", function(d) { return d.y; });

	treeplot.selectAll(".internal").data(internals)
    	.transition().duration(speed)
		.attr("x", function(d) {
			if (typeof d.frequency != "undefined") {
				return d.x - 5*Math.sqrt(d.frequency) - 0.5;
			}
			else {
				return d.x - 1;
			}
		})
		.attr("y", function(d) {
			if (typeof d.frequency != "undefined") {
				return d.y - 5*Math.sqrt(d.frequency) - 0.5;
			}
			else {
				return d.y - 1;
			}
		});

	treeplot.selectAll(".link").data(links)
    	.transition().duration(speed)
		.attr("points", function(d) {
			var mod = 5*Math.sqrt(d.target.frequency)+0.5;
			return (d.source.x-mod).toString() + "," + d.source.y.toString() + " "
			+ (d.source.x-mod).toString() + "," + d.target.y.toString() + " "
			+ (d.target.x).toString() + "," + d.target.y.toString()
		});

}

d3.json("https://s3.amazonaws.com/augur-data/data/auspice.json", function(error, root) {
//d3.json("auspice.json", function(error, root) {
	var nodes = tree.nodes(root),
		links = tree.links(nodes);

	var rootNode = nodes[0];
	var tips = gatherTips(rootNode, []);
	var internals = gatherInternals(rootNode, []);
	setFrequencies(rootNode);

	var	xValues = nodes.map(function(d) {
  		return +d.xvalue;
  	});

	var yValues = nodes.map(function(d) {
  		return +d.yvalue;
  	});

  	var dateValues = nodes.filter(function(d) {
		return typeof d.date === 'string';
  		}).map(function(d) {
  		return new Date(d.date);
  	});

  	var yearValues = nodes.filter(function(d) {
		return typeof d.date === 'string';
  		}).map(function(d) {
  		return (new Date(d.date)).getFullYear();
  	});

	var xScale = d3.scale.linear()
		.domain([d3.min(xValues), d3.max(xValues)])
		.range([10, width-10]);

	var yScale = d3.scale.linear()
		.domain([d3.min(yValues), d3.max(yValues)])
		.range([10, height-10]);

	var dateScale = d3.time.scale()
		.domain([new Date(d3.min(dateValues) + 30), new Date()])
		.range([-100, 100])
		.clamp([true]);

	var yearScale = d3.scale.ordinal()
		.domain([2014, "undefined", 2011, 2012, 2013])
		.range(["#ff7f0e", "#1f77b4", "#7f7f7f", "#7f7f7f", "#7f7f7f"]);

	var recencyColorScale = d3.scale.threshold()
		.domain([0.00, 0.33, 0.66, 1.0])
		.range(["#aaa", "#E04328", "#E78C36", "#CFB642", "#799CB3"]);	// red, orange, yellow, blue

	var recencySizeScale = d3.scale.threshold()
		.domain([0.00, 0.33, 0.66, 1.0])
		.range([0, 3.25, 2.5, 1.75, 1]);

	var freqScale = d3.scale.sqrt()
		.domain([0, 1])
		.range([1, 10]);

	nodes.forEach(function (d) {
		d.x = xScale(d.xvalue);
		d.y = yScale(d.yvalue);
	});

	// straight links
/*	var link = treeplot.selectAll(".link")
		.data(links)
		.enter().append("line")
		.attr("class", "link")
		.attr("x1", function(d) { return d.source.x; })
	    .attr("y1", function(d) { return d.source.y; })
	    .attr("x2", function(d) { return d.target.x; })
	    .attr("y2", function(d) { return d.target.y; });
  */
	var link = treeplot.selectAll(".link")
		.data(links)
		.enter().append("polyline")
		.attr("class", "link")
		.attr("points", function(d) {
			var mod = 5*Math.sqrt(d.target.frequency)+0.5;
			return (d.source.x-mod).toString() + "," + d.source.y.toString() + " "
			+ (d.source.x-mod).toString() + "," + d.target.y.toString() + " "
			+ (d.target.x).toString() + "," + d.target.y.toString()
		})
	    .style("stroke-width", function(d) {
			return freqScale(d.target.frequency);
	    })
		.on('mouseover', tooltip.show)
      	.on('mouseout', tooltip.hide)
     	.on('click', function(d) {
      		var dMin = minimumAttribute(d.target, "xvalue", d.target.xvalue),
      			dMax = maximumAttribute(d.target, "xvalue", d.target.xvalue),
      			lMin = minimumAttribute(d.target, "yvalue", d.target.yvalue),
      			lMax = maximumAttribute(d.target, "yvalue", d.target.yvalue);
      		rescale(dMin, dMax, lMin, lMax, xScale, yScale, nodes, links, tips, internals)
      	});

	var tipCircles = treeplot.selectAll(".tip")
		.data(tips)
		.enter()
		.append("circle")
		.attr("class", "tip")
		.attr("cx", function(d) {return d.x})
		.attr("cy", function(d) {return d.y})
		.attr("r", function(d) {
			var date = new Date(d.date);
			var oneYear = 365.25*24*60*60*1000; // days*hours*minutes*seconds*milliseconds
			var diffYears = (globalDate.getTime() - date.getTime()) / oneYear;
			return recencySizeScale(diffYears);
		})
		.style("fill", function(d) {
			var date = new Date(d.date);
			var oneYear = 365.25*24*60*60*1000; // days*hours*minutes*seconds*milliseconds
			var diffYears = (globalDate.getTime() - date.getTime()) / oneYear;
			var col = recencyColorScale(diffYears);
			return d3.rgb(col).brighter([0.7]).toString();
		})
		.style("stroke", function(d) {
			var date = new Date(d.date);
			var oneYear = 365.25*24*60*60*1000; // days*hours*minutes*seconds*milliseconds
			var diffYears = (globalDate.getTime() - date.getTime()) / oneYear;
			var col = recencyColorScale(diffYears);
			return d3.rgb(col).toString();
		})
		.on('mouseover', tooltip.show)
      	.on('mouseout', tooltip.hide);

	var drag = d3.behavior.drag()
		.origin(function(d) { return d; })
		.on("drag", dragged);

	function dragged(d) {

		d.date = dateScale.invert(d3.event.x);
		d.x = dateScale(d.date);
		d3.selectAll(".counter-text")
			.text(function(d){
    			return format(d.date)
    		});
		globalDate = d.date;
		d3.selectAll(".tip")
			.attr("r", function(d) {
				var date = new Date(d.date);
				var oneYear = 365.25*24*60*60*1000; // days*hours*minutes*seconds*milliseconds
				var diffYears = (globalDate.getTime() - date.getTime()) / oneYear;
				return recencySizeScale(diffYears);
			})
			.style("fill", function(d) {
				var date = new Date(d.date);
				var oneYear = 365.25*24*60*60*1000; // days*hours*minutes*seconds*milliseconds
				var diffYears = (globalDate.getTime() - date.getTime()) / oneYear;
				var col = recencyColorScale(diffYears);
				return d3.rgb(col).brighter([0.7]).toString();
			})
			.style("stroke", function(d) {
				var date = new Date(d.date);
				var oneYear = 365.25*24*60*60*1000; // days*hours*minutes*seconds*milliseconds
				var diffYears = (globalDate.getTime() - date.getTime()) / oneYear;
				var col = recencyColorScale(diffYears);
				return d3.rgb(col).toString();
			});

	}

	var counterData = {}
	counterData['date'] = globalDate
	counterData['x'] = dateScale(globalDate)

	var format = d3.time.format("%Y %b %-d");
	var counterText = treeplot.selectAll(".counter-text")
		.data([counterData])
		.enter()
		.append("text")
		.attr("class", "counter-text")
    	.attr("transform", "translate(100,40)")
    	.style("text-anchor", "middle")
    	.style("alignment-baseline", "middle")
    	.text(function(d){
    		return format(d.date)
    	})
    	.call(drag);

	d3.select("#reset")
        .on("click", function(d) {
			var dMin = d3.min(xValues),
      			dMax = d3.max(xValues),
      			lMin = d3.min(yValues),
      			lMax = d3.max(yValues);
            rescale(dMin, dMax, lMin, lMax, xScale, yScale, nodes, links, tips, internals);
		})

});
<|endoftext|>


<commit_before>const express = require('express');
const _ = require('lodash');
const tj = require('@mapbox/togeojson');
const fs = require('fs');
const path = require('path');
const models = require('../models');
const router = express.Router();
const request = require('request');
const checkJwt = require('./../checkJwt');
const kmlMapName = require('../../config').map.filename;
const DOMParser = require('xmldom').DOMParser;
const { inSubarea } = require('../../helpers/geometry');
const config = require('./../../config');
const geocoder = require('google-geocoder')({ key: config.google.googleServerAuthToken });
// const kml = require(`../../maps/${kmlMapName}`);

router.get('/', (req, res) => {
  models.Group.findAll({
    include: [models.Subarea],
  }).then((groups) => {
    const sortedGroups =  _.orderBy(groups, ['town'], ['asc']);
    res.send(sortedGroups);
  });
});

/**
 * Increment visits
 */
router.post('/visits', (req, res) => {
  models.Group.increment('visits', {
    by: req.body.count,
    where: {
      name: req.body.name
    }
  }).then(() => {
    res.send({});
  });
});

/**
 * Returns a distance matrix between different groups.
 * DOESN'T WORK FOR > 10 groups per subarea.
 */
router.get('/matrix', (req, res) => {
  models.Group.findAll({
    include: [models.Subarea],
  }).then((groups) => {
    // Group groups per subarea
    const groupedGroups = _.groupBy(groups, 'Subarea.name');

    const distanceMatrix = _.mapValues(groupedGroups, function(groups) {
      const groupLocations = _.map(groups, (group) => {
        return {lat: group.latitude, lng: group.longitude};
      });
      console.log(groupLocations);
      googleMapsClient.distanceMatrix({
        origins: groupLocations,
        destinations: groupLocations
      }, (err, response) => {
        return response;
      });
    });

    res.send(distanceMatrix);
  });
});

function fillDatabaseWithGroups(res) {
  // If database is empty, then continue rest.
  request('https://jotihunt.net/api/1.0/deelnemers', (error, response, body) => {
    if (error) {
      res.status(500).send(error);
    } else {
      const groups = JSON.parse(body);
      groups.data.map((group) => {
        models.Group.create({
          name: group.teamnaam,
          town: group.plaats,
          location: `${group.straat} ${group.huisnummer} <br /> ${group.postcode} ${group.plaats}`,
          latitude: group.lat,
          longitude: group.long,
          visits: 0,
          SubareaId: null,
        });
      }).then((repsonse) => {
        res.send("done");
      });
    }
  });
}

router.post('/increment', (req, res) => {
  models.Group.increment({
    visits: req.body.value,
  }, {
    where: {
      id: req.body.groupId,
    }
  }).then(() => {
    models.Group.findAll({
      include: [models.Subarea],
    }).then((groups) => {
      const sortedGroups =  _.orderBy(groups, ['town'], ['asc']);
      res.send(sortedGroups);
    });
  });
});

router.get('/fill', (req, res) => {
  // Check if database is empty.
  models.Group.findAll({
    include: [models.Subarea],
  }).then((groups) => {
    if (groups && groups.length > 0) {
      // Database is filled, send a message the database has to be filled first.
      res.send("Database moet eerst leeg zijn om gevuld te kunnen worden.");
    } else {
      // Database is empty, fill with data.
      fillDatabaseWithGroups(res);
    }
  });

});

module.exports = router;
<commit_msg>Changed importing groups statement<commit_after>const express = require('express');
const _ = require('lodash');
const tj = require('@mapbox/togeojson');
const fs = require('fs');
const path = require('path');
const models = require('../models');
const router = express.Router();
const request = require('request');
const checkJwt = require('./../checkJwt');
const kmlMapName = require('../../config').map.filename;
const DOMParser = require('xmldom').DOMParser;
const { inSubarea } = require('../../helpers/geometry');
const config = require('./../../config');
const geocoder = require('google-geocoder')({ key: config.google.googleServerAuthToken });
// const kml = require(`../../maps/${kmlMapName}`);

router.get('/', (req, res) => {
  models.Group.findAll({
    include: [models.Subarea],
  }).then((groups) => {
    const sortedGroups =  _.orderBy(groups, ['town'], ['asc']);
    res.send(sortedGroups);
  });
});

/**
 * Increment visits
 */
router.post('/visits', (req, res) => {
  models.Group.increment('visits', {
    by: req.body.count,
    where: {
      name: req.body.name
    }
  }).then(() => {
    res.send({});
  });
});

/**
 * Returns a distance matrix between different groups.
 * DOESN'T WORK FOR > 10 groups per subarea.
 */
router.get('/matrix', (req, res) => {
  models.Group.findAll({
    include: [models.Subarea],
  }).then((groups) => {
    // Group groups per subarea
    const groupedGroups = _.groupBy(groups, 'Subarea.name');

    const distanceMatrix = _.mapValues(groupedGroups, function(groups) {
      const groupLocations = _.map(groups, (group) => {
        return {lat: group.latitude, lng: group.longitude};
      });
      console.log(groupLocations);
      googleMapsClient.distanceMatrix({
        origins: groupLocations,
        destinations: groupLocations
      }, (err, response) => {
        return response;
      });
    });

    res.send(distanceMatrix);
  });
});

function fillDatabaseWithGroups(res) {
  // If database is empty, then continue rest.
  request('https://jotihunt.net/api/1.0/deelnemers', (error, response, body) => {
    if (error) {
      res.status(500).send(error);
    } else {
      const groups = JSON.parse(body);
      groups.data.map((group) => {
        models.Group.create({
          name: group.teamnaam,
          town: group.plaats,
          location: `${group.straat} ${group.huisnummer} <br /> ${group.postcode} ${group.plaats}`,
          latitude: group.lat,
          longitude: group.long,
          visits: 0,
          SubareaId: null,
        });
      });
      res.send("Done importing groups.");
    }
  });
}

router.post('/increment', (req, res) => {
  models.Group.increment({
    visits: req.body.value,
  }, {
    where: {
      id: req.body.groupId,
    }
  }).then(() => {
    models.Group.findAll({
      include: [models.Subarea],
    }).then((groups) => {
      const sortedGroups =  _.orderBy(groups, ['town'], ['asc']);
      res.send(sortedGroups);
    });
  });
});

router.get('/fill', (req, res) => {
  // Check if database is empty.
  models.Group.findAll({
    include: [models.Subarea],
  }).then((groups) => {
    if (groups && groups.length > 0) {
      // Database is filled, send a message the database has to be filled first.
      res.send("Database moet eerst leeg zijn om gevuld te kunnen worden.");
    } else {
      // Database is empty, fill with data.
      fillDatabaseWithGroups(res);
    }
  });

});

module.exports = router;
<|endoftext|>