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

function getLastGlucose(data) {

    var now = data[0];
    var last = data[1];
    var avg;
    //TODO: calculate average using system_time instead of assuming 1 data point every 5m
    if (typeof data[3] !== 'undefined' && data[3].glucose > 30) {
        avg = ( now.glucose - data[3].glucose) / 3;
    } else if (typeof data[2] !== 'undefined' && data[2].glucose > 30) {
        avg = ( now.glucose - data[2].glucose) / 2;
    } else if (typeof data[1] !== 'undefined' && data[1].glucose > 30) {
        avg = now.glucose - data[1].glucose;
    } else { avg = 0; }
    var o = {
        delta: now.glucose - last.glucose
        , glucose: now.glucose
        , avgdelta: avg
    };

    return o;

}

function setTempBasal(rate, duration) {

    maxSafeBasal = Math.min(profile_data.max_basal, 3 * profile_data.max_daily_basal, 4 * profile_data.current_basal);

    if (rate < 0) { rate = 0; } // if >30m @ 0 required, zero temp will be extended to 30m instead
    else if (rate > maxSafeBasal) { rate = maxSafeBasal; }

    requestedTemp.duration = duration;
    requestedTemp.rate = Math.round( rate * 1000 ) / 1000;

};

if (!module.parent) {
    var iob_input = process.argv.slice(2, 3).pop()
    var temps_input = process.argv.slice(3, 4).pop()
    var glucose_input = process.argv.slice(4, 5).pop()
    var profile_input = process.argv.slice(5, 6).pop()

    if (!iob_input || !temps_input || !glucose_input || !profile_input) {
        console.error('usage: ', process.argv.slice(0, 2), '<iob.json> <current-temps.json> <glucose.json> <profile.json>');
        process.exit(1);
    }

    var cwd = process.cwd()
    var glucose_data = require(cwd + '/' + glucose_input);
    var temps_data = require(cwd + '/' + temps_input);
    var iob_data = require(cwd + '/' + iob_input);
    var profile_data = require(cwd + '/' + profile_input);

    if (typeof profile_data === 'undefined' || typeof profile_data.current_basal === 'undefined') {
        console.error('Error: could not get current basal rate');
        process.exit(1);
    }

    var max_iob = profile_data.max_iob; // maximum amount of non-bolus IOB OpenAPS will ever deliver

    // if target_bg is set, great. otherwise, if min and max are set, then set target to their average
    var target_bg;
    if (typeof profile_data.target_bg !== 'undefined') {
        target_bg = profile_data.target_bg;
    } else {
        if (typeof profile_data.max_bg !== 'undefined' && typeof profile_data.max_bg !== 'undefined') {
            target_bg = (profile_data.min_bg + profile_data.max_bg) / 2;
        } else {
            console.error('Error: could not determine target_bg');
            process.exit(1);
        }
    }

    var glucose_status = getLastGlucose(glucose_data);
    var bg = glucose_status.glucose;
    var tick;
    if (glucose_status.delta >= 0) { tick = "+" + glucose_status.delta; }
    else { tick = glucose_status.delta; }
    console.error("IOB: " + iob_data.iob.toFixed(2) + ", Bolus IOB: " + iob_data.bolusiob.toFixed(2));
    var bgi = -iob_data.activity * profile_data.sens * 5;
    console.error("Avg. Delta: " + glucose_status.avgdelta.toFixed(1) + ", BGI: " + bgi.toFixed(1));
    // project deviation over next 15 minutes
    var deviation = Math.round( 15 / 5 * ( glucose_status.avgdelta - bgi ) );
    console.error("15m deviation: " + deviation.toFixed(0));
    var bolusContrib = iob_data.bolusiob * profile_data.sens;
    var naive_eventualBG = Math.round( bg - (iob_data.iob * profile_data.sens) );
    var eventualBG = naive_eventualBG + deviation;
    var naive_snoozeBG = Math.round( naive_eventualBG + bolusContrib );
    var snoozeBG = naive_snoozeBG + deviation;
    console.error("BG: " + bg + tick + " -> " + eventualBG + "-" + snoozeBG + " (Unadjusted: " + naive_eventualBG + "-" + naive_snoozeBG + ")");
    if (typeof eventualBG === 'undefined') { console.error('Error: could not calculate eventualBG'); }
    var requestedTemp = {
        'temp': 'absolute'
        , 'bg': bg
        , 'tick': tick
        , 'eventualBG': eventualBG
        , 'snoozeBG': snoozeBG
    };

    //if old reading from Dexcom do nothing

    var systemTime = new Date();
    var bgTime;
    if (glucose_data[0].display_time) {
        bgTime = new Date(glucose_data[0].display_time.replace('T', ' '));
    } else if (glucose_data[0].dateString) {
        bgTime = new Date(glucose_data[0].dateString);
    } else { console.error("Could not determine last BG time"); }
    var minAgo = (systemTime - bgTime) / 60 / 1000
    var threshold = profile_data.min_bg - 30;
    var reason="";

    if (minAgo < 10 && minAgo > -5) { // Dexcom data is recent, but not far in the future

        if (bg > 10) {  //Dexcom is in ??? mode or calibrating, do nothing. Asked @benwest for raw data in iter_glucose

            if (bg < threshold) { // low glucose suspend mode: BG is < ~80
                reason = "BG " + bg + "<" + threshold;
                console.error(reason);
                if (glucose_status.delta > 0) { // if BG is rising
                    if (temps_data.rate > profile_data.current_basal) { // if a high-temp is running
                        setTempBasal(0, 0); // cancel high temp
                    } else if (temps_data.duration && eventualBG > profile_data.max_bg) { // if low-temped and predicted to go high from negative IOB
                        setTempBasal(0, 0); // cancel low temp
                    } else {
                        reason = bg + "<" + threshold + "; no high-temp to cancel";
                        console.error(reason);
                    }
                }
                else { // BG is not yet rising
                    setTempBasal(0, 30);
                }

            } else {

                // if BG is rising but eventual BG is below min, or BG is falling but eventual BG is above min
                if ((glucose_status.delta > 0 && eventualBG < profile_data.min_bg) || (glucose_status.delta < 0 && eventualBG >= profile_data.min_bg)) {
                    if (temps_data.duration > 0) { // if there is currently any temp basal running
                        // if it's a low-temp and eventualBG < profile_data.max_bg, let it run a bit longer
                        if (temps_data.rate <= profile_data.current_basal && eventualBG < profile_data.max_bg) {
                            reason = "BG" + tick + " but " + eventualBG + "<" + profile_data.max_bg;
                            console.error(reason);
                        } else {
                            reason = glucose_status.delta + " and " + eventualBG;
                            setTempBasal(0, 0); // cancel temp
                        }
                    } else {
                        reason = tick + "; no temp to cancel";
                        console.error(reason);
                    }

                } else if (eventualBG < profile_data.min_bg) { // if eventual BG is below target:
                    // if this is just due to boluses, we can snooze until the bolus IOB decays (at double speed)
                    if (snoozeBG > profile_data.min_bg) { // if adding back in the bolus contribution BG would be above min
                        // if BG is falling and high-temped, or rising and low-temped, cancel
                        if (glucose_status.delta < 0 && temps_data.rate > profile_data.current_basal) {
                            reason = tick + " and " + temps_data.rate + ">" + profile_data.current_basal;
                            setTempBasal(0, 0); // cancel temp
                        } else if (glucose_status.delta > 0 && temps_data.rate < profile_data.current_basal) {
                            reason = tick + " and " + temps_data.rate + "<" + profile_data.current_basal;
                            setTempBasal(0, 0); // cancel temp
                        } else {
                            reason = "bolus snooze: eventual BG range " + eventualBG + "-" + snoozeBG;
                            console.error(reason);
                        }
                    } else {
                        // calculate 30m low-temp required to get projected BG up to target
                        // negative insulin required to get up to min:
                        //var insulinReq = Math.max(0, (target_bg - eventualBG) / profile_data.sens);
                        // use snoozeBG instead of eventualBG to more gradually ramp in any counteraction of the user's boluses
                        var insulinReq = Math.min(0, (snoozeBG - target_bg) / profile_data.sens);
                        // rate required to deliver insulinReq less insulin over 30m:
                        var rate = profile_data.current_basal + (2 * insulinReq);
                        // if required temp < existing temp basal
                        if (typeof temps_data.rate !== 'undefined' && (temps_data.duration > 0 && rate > temps_data.rate - 0.1)) {
                            reason = temps_data.rate + "<~" + rate;
                            console.error(reason);
                        } else {
                            reason = "Eventual BG " + eventualBG + "<" + profile_data.min_bg;
                            console.error(reason);
                            setTempBasal(rate, 30);
                        }
                    }

                } else if (eventualBG > profile_data.max_bg) { // if eventual BG is above target:
                    // if iob is over max, just cancel any temps
                    var basal_iob = iob_data.iob - iob_data.bolusiob;
                    if (basal_iob > max_iob) {
                        reason = basal_iob + ">" + max_iob;
                        setTempBasal(0, 0);
                    }
                    // calculate 30m high-temp required to get projected BG down to target
                    // additional insulin required to get down to max bg:
                    var insulinReq = (eventualBG - target_bg) / profile_data.sens;
                    //TODO: verify this is working
                    // if that would put us over max_iob, then reduce accordingly
                    insulinReq = Math.min(insulinReq, max_iob-basal_iob);

                    // rate required to deliver insulinReq more insulin over 30m:
                    var rate = profile_data.current_basal + (2 * insulinReq);
                    maxSafeBasal = Math.min(profile_data.max_basal, 3 * profile_data.max_daily_basal, 4 * profile_data.current_basal);
                    if (rate > maxSafeBasal) { rate = maxSafeBasal; }
                    var insulinScheduled = temps_data.duration * temps_data.rate / 60;
                    if (insulinScheduled > insulinReq) { // if current temp would deliver more than the required insulin, lower the rate
                        reason = temps_data.duration + "@" + temps_data.rate + ">" + insulinReq + "U";
                        setTempBasal(rate, 30);
                    }
                    else if (typeof temps_data.rate !== 'undefined' && (temps_data.duration > 0 && rate < temps_data.rate + 0.1)) { // if required temp < existing temp basal
                        reason = temps_data.rate + ">~" + rate;
                        console.error(reason);
                    } else { // required temp > existing temp basal
                        setTempBasal(rate, 30);
                    }

                } else {
                    reason = eventualBG + " is in range. No action required.";
                    console.error(reason);
                }
            }
            // if no temp is running or required, set the current basal as a temp, so you can see on the pump that the loop is working
            if ((!temps_data.duration || (temps_data.rate == profile_data.current_basal)) && !requestedTemp.duration) {
                setTempBasal(profile_data.current_basal, 30);
                reason = reason + "; setting current basal of " + profile_data.current_basal + " as temp";
            }
        }  else {
            reason = "CGM is calibrating or in ??? state";
            console.error(reason);
        }
    } else {
        reason = "BG data is too old";
        console.error(reason);
    }

requestedTemp.reason = reason;
console.log(JSON.stringify(requestedTemp));
}

<commit_msg>insulinScheduled should be net basal, not gross<commit_after>#!/usr/bin/env node

function getLastGlucose(data) {

    var now = data[0];
    var last = data[1];
    var avg;
    //TODO: calculate average using system_time instead of assuming 1 data point every 5m
    if (typeof data[3] !== 'undefined' && data[3].glucose > 30) {
        avg = ( now.glucose - data[3].glucose) / 3;
    } else if (typeof data[2] !== 'undefined' && data[2].glucose > 30) {
        avg = ( now.glucose - data[2].glucose) / 2;
    } else if (typeof data[1] !== 'undefined' && data[1].glucose > 30) {
        avg = now.glucose - data[1].glucose;
    } else { avg = 0; }
    var o = {
        delta: now.glucose - last.glucose
        , glucose: now.glucose
        , avgdelta: avg
    };

    return o;

}

function setTempBasal(rate, duration) {

    maxSafeBasal = Math.min(profile_data.max_basal, 3 * profile_data.max_daily_basal, 4 * profile_data.current_basal);

    if (rate < 0) { rate = 0; } // if >30m @ 0 required, zero temp will be extended to 30m instead
    else if (rate > maxSafeBasal) { rate = maxSafeBasal; }

    requestedTemp.duration = duration;
    requestedTemp.rate = Math.round( rate * 1000 ) / 1000;

};

if (!module.parent) {
    var iob_input = process.argv.slice(2, 3).pop()
    var temps_input = process.argv.slice(3, 4).pop()
    var glucose_input = process.argv.slice(4, 5).pop()
    var profile_input = process.argv.slice(5, 6).pop()

    if (!iob_input || !temps_input || !glucose_input || !profile_input) {
        console.error('usage: ', process.argv.slice(0, 2), '<iob.json> <current-temps.json> <glucose.json> <profile.json>');
        process.exit(1);
    }

    var cwd = process.cwd()
    var glucose_data = require(cwd + '/' + glucose_input);
    var temps_data = require(cwd + '/' + temps_input);
    var iob_data = require(cwd + '/' + iob_input);
    var profile_data = require(cwd + '/' + profile_input);

    if (typeof profile_data === 'undefined' || typeof profile_data.current_basal === 'undefined') {
        console.error('Error: could not get current basal rate');
        process.exit(1);
    }

    var max_iob = profile_data.max_iob; // maximum amount of non-bolus IOB OpenAPS will ever deliver

    // if target_bg is set, great. otherwise, if min and max are set, then set target to their average
    var target_bg;
    if (typeof profile_data.target_bg !== 'undefined') {
        target_bg = profile_data.target_bg;
    } else {
        if (typeof profile_data.max_bg !== 'undefined' && typeof profile_data.max_bg !== 'undefined') {
            target_bg = (profile_data.min_bg + profile_data.max_bg) / 2;
        } else {
            console.error('Error: could not determine target_bg');
            process.exit(1);
        }
    }

    var glucose_status = getLastGlucose(glucose_data);
    var bg = glucose_status.glucose;
    var tick;
    if (glucose_status.delta >= 0) { tick = "+" + glucose_status.delta; }
    else { tick = glucose_status.delta; }
    console.error("IOB: " + iob_data.iob.toFixed(2) + ", Bolus IOB: " + iob_data.bolusiob.toFixed(2));
    var bgi = -iob_data.activity * profile_data.sens * 5;
    console.error("Avg. Delta: " + glucose_status.avgdelta.toFixed(1) + ", BGI: " + bgi.toFixed(1));
    // project deviation over next 15 minutes
    var deviation = Math.round( 15 / 5 * ( glucose_status.avgdelta - bgi ) );
    console.error("15m deviation: " + deviation.toFixed(0));
    var bolusContrib = iob_data.bolusiob * profile_data.sens;
    var naive_eventualBG = Math.round( bg - (iob_data.iob * profile_data.sens) );
    var eventualBG = naive_eventualBG + deviation;
    var naive_snoozeBG = Math.round( naive_eventualBG + bolusContrib );
    var snoozeBG = naive_snoozeBG + deviation;
    console.error("BG: " + bg + tick + " -> " + eventualBG + "-" + snoozeBG + " (Unadjusted: " + naive_eventualBG + "-" + naive_snoozeBG + ")");
    if (typeof eventualBG === 'undefined') { console.error('Error: could not calculate eventualBG'); }
    var requestedTemp = {
        'temp': 'absolute'
        , 'bg': bg
        , 'tick': tick
        , 'eventualBG': eventualBG
        , 'snoozeBG': snoozeBG
    };

    //if old reading from Dexcom do nothing

    var systemTime = new Date();
    var bgTime;
    if (glucose_data[0].display_time) {
        bgTime = new Date(glucose_data[0].display_time.replace('T', ' '));
    } else if (glucose_data[0].dateString) {
        bgTime = new Date(glucose_data[0].dateString);
    } else { console.error("Could not determine last BG time"); }
    var minAgo = (systemTime - bgTime) / 60 / 1000
    var threshold = profile_data.min_bg - 30;
    var reason="";

    if (minAgo < 10 && minAgo > -5) { // Dexcom data is recent, but not far in the future

        if (bg > 10) {  //Dexcom is in ??? mode or calibrating, do nothing. Asked @benwest for raw data in iter_glucose

            if (bg < threshold) { // low glucose suspend mode: BG is < ~80
                reason = "BG " + bg + "<" + threshold;
                console.error(reason);
                if (glucose_status.delta > 0) { // if BG is rising
                    if (temps_data.rate > profile_data.current_basal) { // if a high-temp is running
                        setTempBasal(0, 0); // cancel high temp
                    } else if (temps_data.duration && eventualBG > profile_data.max_bg) { // if low-temped and predicted to go high from negative IOB
                        setTempBasal(0, 0); // cancel low temp
                    } else {
                        reason = bg + "<" + threshold + "; no high-temp to cancel";
                        console.error(reason);
                    }
                }
                else { // BG is not yet rising
                    setTempBasal(0, 30);
                }

            } else {

                // if BG is rising but eventual BG is below min, or BG is falling but eventual BG is above min
                if ((glucose_status.delta > 0 && eventualBG < profile_data.min_bg) || (glucose_status.delta < 0 && eventualBG >= profile_data.min_bg)) {
                    if (temps_data.duration > 0) { // if there is currently any temp basal running
                        // if it's a low-temp and eventualBG < profile_data.max_bg, let it run a bit longer
                        if (temps_data.rate <= profile_data.current_basal && eventualBG < profile_data.max_bg) {
                            reason = "BG" + tick + " but " + eventualBG + "<" + profile_data.max_bg;
                            console.error(reason);
                        } else {
                            reason = glucose_status.delta + " and " + eventualBG;
                            setTempBasal(0, 0); // cancel temp
                        }
                    } else {
                        reason = tick + "; no temp to cancel";
                        console.error(reason);
                    }

                } else if (eventualBG < profile_data.min_bg) { // if eventual BG is below target:
                    // if this is just due to boluses, we can snooze until the bolus IOB decays (at double speed)
                    if (snoozeBG > profile_data.min_bg) { // if adding back in the bolus contribution BG would be above min
                        // if BG is falling and high-temped, or rising and low-temped, cancel
                        if (glucose_status.delta < 0 && temps_data.rate > profile_data.current_basal) {
                            reason = tick + " and " + temps_data.rate + ">" + profile_data.current_basal;
                            setTempBasal(0, 0); // cancel temp
                        } else if (glucose_status.delta > 0 && temps_data.rate < profile_data.current_basal) {
                            reason = tick + " and " + temps_data.rate + "<" + profile_data.current_basal;
                            setTempBasal(0, 0); // cancel temp
                        } else {
                            reason = "bolus snooze: eventual BG range " + eventualBG + "-" + snoozeBG;
                            console.error(reason);
                        }
                    } else {
                        // calculate 30m low-temp required to get projected BG up to target
                        // negative insulin required to get up to min:
                        //var insulinReq = Math.max(0, (target_bg - eventualBG) / profile_data.sens);
                        // use snoozeBG instead of eventualBG to more gradually ramp in any counteraction of the user's boluses
                        var insulinReq = Math.min(0, (snoozeBG - target_bg) / profile_data.sens);
                        // rate required to deliver insulinReq less insulin over 30m:
                        var rate = profile_data.current_basal + (2 * insulinReq);
                        // if required temp < existing temp basal
                        if (typeof temps_data.rate !== 'undefined' && (temps_data.duration > 0 && rate > temps_data.rate - 0.1)) {
                            reason = temps_data.rate + "<~" + rate;
                            console.error(reason);
                        } else {
                            reason = "Eventual BG " + eventualBG + "<" + profile_data.min_bg;
                            console.error(reason);
                            setTempBasal(rate, 30);
                        }
                    }

                } else if (eventualBG > profile_data.max_bg) { // if eventual BG is above target:
                    // if iob is over max, just cancel any temps
                    var basal_iob = iob_data.iob - iob_data.bolusiob;
                    if (basal_iob > max_iob) {
                        reason = basal_iob + ">" + max_iob;
                        setTempBasal(0, 0);
                    }
                    // calculate 30m high-temp required to get projected BG down to target
                    // additional insulin required to get down to max bg:
                    var insulinReq = (eventualBG - target_bg) / profile_data.sens;
                    //TODO: verify this is working
                    // if that would put us over max_iob, then reduce accordingly
                    insulinReq = Math.min(insulinReq, max_iob-basal_iob);

                    // rate required to deliver insulinReq more insulin over 30m:
                    var rate = profile_data.current_basal + (2 * insulinReq);
                    maxSafeBasal = Math.min(profile_data.max_basal, 3 * profile_data.max_daily_basal, 4 * profile_data.current_basal);
                    if (rate > maxSafeBasal) {
                        rate = maxSafeBasal;
                        //console.error(maxSafeBasal);
                    }
                    var insulinScheduled = temps_data.duration * (profile_data.current_basal-temps_data.rate) / 60;
                    if (insulinScheduled > insulinReq) { // if current temp would deliver more than the required insulin, lower the rate
                        reason = temps_data.duration + "@" + temps_data.rate + ">" + insulinReq + "U";
                        setTempBasal(rate, 30);
                    }
                    else if (typeof temps_data.rate !== 'undefined' && (temps_data.duration > 0 && rate < temps_data.rate + 0.1)) { // if required temp < existing temp basal
                        reason = temps_data.rate + ">~" + rate;
                        console.error(reason);
                    } else { // required temp > existing temp basal
                        reason = temps_data.rate + "<" + rate;
                        setTempBasal(rate, 30);
                    }

                } else {
                    reason = eventualBG + " is in range. No action required.";
                    console.error(reason);
                }
            }
            // if no temp is running or required, set the current basal as a temp, so you can see on the pump that the loop is working
            if ((!temps_data.duration || (temps_data.rate == profile_data.current_basal)) && !requestedTemp.duration) {
                setTempBasal(profile_data.current_basal, 30);
                reason = reason + "; setting current basal of " + profile_data.current_basal + " as temp";
            }
        }  else {
            reason = "CGM is calibrating or in ??? state";
            console.error(reason);
        }
    } else {
        reason = "BG data is too old";
        console.error(reason);
    }

requestedTemp.reason = reason;
console.log(JSON.stringify(requestedTemp));
}

<|endoftext|>


<commit_before>'use strict';

// requirements

var gulp = require('gulp'),
    browserify = require('gulp-browserify'),
    size = require('gulp-size'),
    rename = require('gulp-rename'),
    clean = require('gulp-clean');

// tasks

gulp.task('transform', function () {
  // add task
});

gulp.task('default', ['clean'], function () {
  console.log("Hello QBer!");
  gulp.start('transform');
  gulp.watch(['./app/static/js/qber.js','./app/static/js/**/*.js'], ['transform']);
});

gulp.task('transform', function () {
  return gulp.src('./app/static/js/qber.js')
    .pipe(browserify({transform: ['reactify']}))
    .pipe(rename('bundle.js'))
    .pipe(gulp.dest('./app/static/dist/js'))
    .pipe(size());
});

gulp.task('clean', function () {
  return gulp.src(['./app/static/dist/js'], {read: false})
    .pipe(clean());
});
<commit_msg>Fixed paths<commit_after>'use strict';

// requirements

var gulp = require('gulp'),
    browserify = require('gulp-browserify'),
    size = require('gulp-size'),
    rename = require('gulp-rename'),
    clean = require('gulp-clean');

// tasks

gulp.task('transform', function () {
  // add task
});

gulp.task('default', ['clean'], function () {
  console.log("Hello QBer!");
  gulp.start('transform');
  gulp.watch(['./js/qber.js','./js/**/*.js'], ['transform']);
});

gulp.task('transform', function () {
  return gulp.src('./js/qber.js')
    .pipe(browserify({transform: ['reactify']}))
    .pipe(rename('bundle.js'))
    .pipe(gulp.dest('./dist/js'))
    .pipe(size());
});

gulp.task('clean', function () {
  return gulp.src(['./dist/js'], {read: false})
    .pipe(clean());
});
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import color from 'color';
import _ from 'underscore';
import url from 'url';
import { svgIcon } from '../libs/svg-icons';
import * as globals from './globals';
import { BrowserSelector } from './objectutils';
import { BatchDownload, FacetList, TextFilter } from './search';

class GroupMoreButton extends React.Component {
    constructor() {
        super();

        // Bind this to non-React components.
        this.localHandleClick = this.localHandleClick.bind(this);
    }

    localHandleClick() {
        this.props.handleClick(this.props.id);
    }

    render() {
        return <button className="group-more-cell__button" onClick={this.localHandleClick}>{this.props.displayText}</button>;
    }
}

GroupMoreButton.propTypes = {
    id: PropTypes.string, // ID for the handleClick function to know which control was clicked
    handleClick: PropTypes.func.isRequired, // Call this parent function to handle the click in the button
    displayText: PropTypes.string, // Text to display in the button while closed
};

GroupMoreButton.defaultProps = {
    id: '',
    displayText: '',
};

class Matrix extends React.Component {
    static generateYGroupOpen(matrix) {
        // Make a state for each of the Y groups (each Y group currently shows a biosample type).
        // To do that, we have to get each of the bucket keys, which will be the keys into the
        // object that keeps track of whether the group shows all or not. If a group has fewer than
        // the maximum number of items to show a See More button, it doesn't get included in the
        // state.
        const primaryYGrouping = matrix.y.group_by[0];
        const secondaryYGrouping = matrix.y.group_by[1];
        const yLimit = matrix.y.limit;
        const yGroups = matrix.y[primaryYGrouping].buckets;
        const yGroupOpen = {};
        yGroups.forEach((group) => {
            if (group[secondaryYGrouping].buckets.length > yLimit) {
                yGroupOpen[group.key] = false;
            }
        });
        return yGroupOpen;
    }

    constructor(props) {
        super(props);

        // Set initial React state.
        const yGroupOpen = Matrix.generateYGroupOpen(this.props.context.matrix);
        this.state = {
            yGroupOpen,
            allYGroupsOpen: false,
        };

        // Bind this to non-React methods.
        this.onChange = this.onChange.bind(this);
        this.onFilter = this.onFilter.bind(this);
        this.updateElement = this.updateElement.bind(this);
        this.handleClick = this.handleClick.bind(this);
        this.handleSeeAllClick = this.handleSeeAllClick.bind(this);
    }

    componentWillReceiveProps(nextProps) {
        // This callback makes possible updating the See More buttons when the user clicks a facet,
        // which could cause these buttons to not be needed. This resets all the buttons to the See
        // More state.
        const yGroupOpen = Matrix.generateYGroupOpen(nextProps.context.matrix);
        this.setState({
            yGroupOpen,
            allYGroupsOpen: false,
        });
    }

    onChange(href) {
        this.context.navigate(href);
    }

    onFilter(e) {
        const search = e.currentTarget.getAttribute('href');
        this.context.navigate(search);
        e.stopPropagation();
        e.preventDefault();
    }

    // Called when the Visualize button dropdown menu gets opened or closed. `dropdownEl` is the
    // DOM node for the dropdown menu. This sets inline CSS to set the height of the wrapper

    // to make room for the dropdown.
    updateElement(dropdownEl) {
        const wrapperEl = this.hubscontrols;
        const dropdownHeight = dropdownEl.clientHeight;
        if (dropdownHeight === 0) {
            // The dropdown menu has closed
            wrapperEl.style.height = 'auto';
        } else {
            // The menu has dropped down
            wrapperEl.style.height = `${wrapperEl.clientHeight}${dropdownHeight}px`;
        }
    }

    // Handle a click in a See More link within the matrix (not for the facets)
    handleClick(groupKey) {
        const groupOpen = _.clone(this.state.yGroupOpen);
        groupOpen[groupKey] = !groupOpen[groupKey];
        this.setState({ yGroupOpen: groupOpen });
    }

    handleSeeAllClick() {
        this.setState((prevState) => {
            const newState = {};

            // If the See All button wasn't open (meaning this function was called because the user
            // wanted to see all), forget all the individual ones the user had opened so that
            // they're all closed when the user clicks See Fewer.
            if (!prevState.allYGroupsOpen) {
                const groupOpen = {};
                Object.keys(prevState.yGroupOpen).forEach((key) => {
                    groupOpen[key] = false;
                });
                newState.yGroupOpen = groupOpen;
            }

            // Toggle the state of allYGroupsOpen.
            newState.allYGroupsOpen = !prevState.allYGroupsOpen;
            return newState;
        });
    }

    render() {
        const context = this.props.context;
        const matrix = context.matrix;
        const parsedUrl = url.parse(this.context.location_href);
        const matrixBase = parsedUrl.search || '';
        const matrixSearch = matrixBase + (matrixBase ? '&' : '?');
        const notification = context.notification;
        const visualizeLimit = 500;
        if (notification === 'Success' || notification === 'No results found') {
            const xFacets = matrix.x.facets.map(f => _.findWhere(context.facets, { field: f })).filter(f => f);
            let yFacets = matrix.y.facets.map(f => _.findWhere(context.facets, { field: f })).filter(f => f);
            yFacets = yFacets.concat(_.reject(context.facets, f => _.contains(matrix.x.facets, f.field) || _.contains(matrix.y.facets, f.field)));
            const xGrouping = matrix.x.group_by;
            const primaryYGrouping = matrix.y.group_by[0];
            const secondaryYGrouping = matrix.y.group_by[1];
            const xBuckets = matrix.x.buckets;
            const xLimit = matrix.x.limit || xBuckets.length;
            const yGroups = matrix.y[primaryYGrouping].buckets;
            const yGroupFacet = _.findWhere(context.facets, { field: primaryYGrouping });
            const yGroupOptions = yGroupFacet ? yGroupFacet.terms.map(term => term.key) : [];
            yGroupOptions.sort();
            const searchBase = context.matrix.search_base;
            const visualizeDisabled = matrix.doc_count > visualizeLimit;
            const colCount = Math.min(xBuckets.length, xLimit + 1);

            // Get a sorted list of batch hubs keys with case-insensitive sort
            // NOTE: Tim thinks this is overkill as opposed to simple sort()
            let visualizeKeys = [];
            if (context.visualize_batch && Object.keys(context.visualize_batch).length) {
                visualizeKeys = Object.keys(context.visualize_batch).sort((a, b) => {
                    const aLower = a.toLowerCase();
                    const bLower = b.toLowerCase();
                    return (aLower > bLower) ? 1 : ((aLower < bLower) ? -1 : 0);
                });
            }

            // Map view icons to svg icons
            const view2svg = {
                'list-alt': 'search',
                table: 'table',
                summary: 'summary',
            };

            // Make an array of colors corresponding to the ordering of biosample_type
            const biosampleTypeColors = globals.biosampleTypeColors.colorList(yGroups.map(yGroup => yGroup.key));

            return (

{context.title}

Click or enter search terms to filter the experiments included in the matrix.

                                                <TextFilter filters={context.filters} searchBase={matrixSearch} onChange={this.onChange} />

                                <FacetList
                                    facets={xFacets}
                                    filters={context.filters}
                                    orientation="horizontal"
                                    searchBase={matrixSearch}
                                    onFilter={this.onFilter}
                                />

                                <FacetList facets={yFacets} filters={context.filters} searchBase={matrixSearch} onFilter={this.onFilter} />

                                            {matrix.y.label.toUpperCase()}

                                            {matrix.doc_count ?

                                                    {matrix.x.label.toUpperCase()}

                                            : null}

                                                        {matrix.doc_count} results

                                                        {matrix.doc_count && context.views ? context.views.map(view => {svgIcon(view2svg[view.icon])}) : ''}

                                                    {context.filters.length ?

                                                            Clear Filters

                                                    : null}

                                                {xBuckets.map((xb, i) => {
                                                    if (i < xLimit) {
                                                        const href = `${searchBase}&${xGrouping}=${globals.encodedURIComponent(xb.key)}`;
                                                        return
{xb.key}
	;
                                                    } else if (i === xLimit) {
                                                        const parsed = url.parse(matrixBase, true);
                                                        parsed.query['x.limit'] = null;
                                                        delete parsed.search; // this makes format compose the search string out of the query object
                                                        const unlimitedHref = url.format(parsed);
                                                        return
...and {xBuckets.length - xLimit} more
	;
                                                    }
                                                    return null;
                                                })}

                                            {yGroups.map((group, i) => {
                                                const groupColor = biosampleTypeColors[i];
                                                const seriesColor = color(groupColor);
                                                const parsed = url.parse(matrixBase, true);
                                                parsed.query[primaryYGrouping] = group.key;
                                                parsed.query['y.limit'] = null;
                                                delete parsed.search; // this makes format compose the search string out of the query object
                                                const groupHref = url.format(parsed);
                                                const rows = [
                                                    <tr key={`group-${group.key}`}>

                                                            {group.key}

,
                                                ];
                                                const groupBuckets = group[secondaryYGrouping].buckets;
                                                const yLimit = matrix.y.limit || groupBuckets.length;

                                                // If this group isn't open (noted by
                                                // this.state.yGroupOpen[key]), extract just the
                                                // group rows that are under the display limit.
                                                const groupRows = (this.state.yGroupOpen[group.key] || this.state.allYGroupsOpen) ? groupBuckets : groupBuckets.slice(0, yLimit);
                                                rows.push(...groupRows.map((yb) => {
                                                    const href = `${searchBase}&${secondaryYGrouping}=${globals.encodedURIComponent(yb.key)}`;
                                                    return (
                                                        <tr key={`yb-${yb.key}`}>
                                                            {yb.key}
                                                            {xBuckets.map((xb, k) => {
                                                                if (k < xLimit) {
                                                                    const value = yb[xGrouping][k];
                                                                    const cellColor = seriesColor.clone();
                                                                    // scale color between white and the series color
                                                                    cellColor.lightness(cellColor.lightness() + ((1 - (value / matrix.max_cell_doc_count)) * (100 - cellColor.lightness())));
                                                                    const textColor = cellColor.luminosity() > 0.5 ? '#000' : '#fff';
                                                                    const cellHref = `${searchBase}&${secondaryYGrouping}=${globals.encodedURIComponent(yb.key)}&${xGrouping}=${globals.encodedURIComponent(xb.key)}`;
                                                                    const title = `${yb.key} / ${xb.key}: ${value}`;
                                                                    return (

                                                                            {value ? {value} : null}

                                                                    );
                                                                }
                                                                return null;
                                                            })}
                                                            {xBuckets.length > xLimit && }

                                                    );
                                                }));
                                                if (groupBuckets.length > yLimit && !this.state.allYGroupsOpen) {
                                                    rows.push(

                                                                <GroupMoreButton
                                                                    id={group.key}
                                                                    handleClick={this.handleClick}
                                                                    displayText={this.state.yGroupOpen[group.key] ? '- See fewer' : `+ See ${groupBuckets.length - yLimit} more…`}
                                                                />

                                                            {_.range(colCount - 1).map(n => )}

                                                    );
                                                }
                                                return rows;
                                            })}

                                            {/* Display the See Fewer/See All button controlling
                                                the whole table if at least one biosample_type has
                                                more than the limit. We know this is the case if at
                                                least one yGroupOpen state member exists. */}
                                            {Object.keys(this.state.yGroupOpen).length ?

                                                        <button className="group-all-groups-cell__button" onClick={this.handleSeeAllClick}>
                                                            {this.state.allYGroupsOpen ? 'See fewer biosamples' : 'See all biosamples'}
                                                        </button>

                                            : null}

 { this.hubscontrols = div; }}>
                                    {context.batch_download ?
                                        <BatchDownload context={context} />
                                    : null}
                                    {' '}
                                    {visualizeKeys.length ?
                                        <BrowserSelector
                                            visualizeCfg={context.visualize_batch}
                                            disabled={visualizeDisabled}
                                            title={visualizeDisabled ? `Filter to ${visualizeLimit} to visualize` : 'Visualize'}
                                        />
                                    : null}

            );
        }
        return
{notification}
;
    }
}

Matrix.propTypes = {
    context: React.PropTypes.object.isRequired,
};

Matrix.contextTypes = {
    location_href: PropTypes.string,
    navigate: PropTypes.func,
    biosampleTypeColors: PropTypes.object, // DataColor instance for experiment project
};

globals.contentViews.register(Matrix, 'Matrix');
<commit_msg>ENCD-4058-expanding-matrix-creates-unaligned-header (#2280)<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import color from 'color';
import _ from 'underscore';
import url from 'url';
import { svgIcon } from '../libs/svg-icons';
import * as globals from './globals';
import { BrowserSelector } from './objectutils';
import { BatchDownload, FacetList, TextFilter } from './search';

class GroupMoreButton extends React.Component {
    constructor() {
        super();

        // Bind this to non-React components.
        this.localHandleClick = this.localHandleClick.bind(this);
    }

    localHandleClick() {
        this.props.handleClick(this.props.id);
    }

    render() {
        return <button className="group-more-cell__button" onClick={this.localHandleClick}>{this.props.displayText}</button>;
    }
}

GroupMoreButton.propTypes = {
    id: PropTypes.string, // ID for the handleClick function to know which control was clicked
    handleClick: PropTypes.func.isRequired, // Call this parent function to handle the click in the button
    displayText: PropTypes.string, // Text to display in the button while closed
};

GroupMoreButton.defaultProps = {
    id: '',
    displayText: '',
};

class Matrix extends React.Component {
    static generateYGroupOpen(matrix) {
        // Make a state for each of the Y groups (each Y group currently shows a biosample type).
        // To do that, we have to get each of the bucket keys, which will be the keys into the
        // object that keeps track of whether the group shows all or not. If a group has fewer than
        // the maximum number of items to show a See More button, it doesn't get included in the
        // state.
        const primaryYGrouping = matrix.y.group_by[0];
        const secondaryYGrouping = matrix.y.group_by[1];
        const yLimit = matrix.y.limit;
        const yGroups = matrix.y[primaryYGrouping].buckets;
        const yGroupOpen = {};
        yGroups.forEach((group) => {
            if (group[secondaryYGrouping].buckets.length > yLimit) {
                yGroupOpen[group.key] = false;
            }
        });
        return yGroupOpen;
    }

    constructor(props) {
        super(props);

        // Set initial React state.
        const yGroupOpen = Matrix.generateYGroupOpen(this.props.context.matrix);
        this.state = {
            yGroupOpen,
            allYGroupsOpen: false,
        };

        // Bind this to non-React methods.
        this.onChange = this.onChange.bind(this);
        this.onFilter = this.onFilter.bind(this);
        this.updateElement = this.updateElement.bind(this);
        this.handleClick = this.handleClick.bind(this);
        this.handleSeeAllClick = this.handleSeeAllClick.bind(this);
    }

    componentWillReceiveProps(nextProps) {
        // This callback makes possible updating the See More buttons when the user clicks a facet,
        // which could cause these buttons to not be needed. This resets all the buttons to the See
        // More state.
        const yGroupOpen = Matrix.generateYGroupOpen(nextProps.context.matrix);
        this.setState({
            yGroupOpen,
            allYGroupsOpen: false,
        });
    }

    onChange(href) {
        this.context.navigate(href);
    }

    onFilter(e) {
        const search = e.currentTarget.getAttribute('href');
        this.context.navigate(search);
        e.stopPropagation();
        e.preventDefault();
    }

    // Called when the Visualize button dropdown menu gets opened or closed. `dropdownEl` is the
    // DOM node for the dropdown menu. This sets inline CSS to set the height of the wrapper

    // to make room for the dropdown.
    updateElement(dropdownEl) {
        const wrapperEl = this.hubscontrols;
        const dropdownHeight = dropdownEl.clientHeight;
        if (dropdownHeight === 0) {
            // The dropdown menu has closed
            wrapperEl.style.height = 'auto';
        } else {
            // The menu has dropped down
            wrapperEl.style.height = `${wrapperEl.clientHeight}${dropdownHeight}px`;
        }
    }

    // Handle a click in a See More link within the matrix (not for the facets)
    handleClick(groupKey) {
        const groupOpen = _.clone(this.state.yGroupOpen);
        groupOpen[groupKey] = !groupOpen[groupKey];
        this.setState({ yGroupOpen: groupOpen });
    }

    handleSeeAllClick() {
        this.setState((prevState) => {
            const newState = {};

            // If the See All button wasn't open (meaning this function was called because the user
            // wanted to see all), forget all the individual ones the user had opened so that
            // they're all closed when the user clicks See Fewer.
            if (!prevState.allYGroupsOpen) {
                const groupOpen = {};
                Object.keys(prevState.yGroupOpen).forEach((key) => {
                    groupOpen[key] = false;
                });
                newState.yGroupOpen = groupOpen;
            }

            // Toggle the state of allYGroupsOpen.
            newState.allYGroupsOpen = !prevState.allYGroupsOpen;
            return newState;
        });
    }

    render() {
        const context = this.props.context;
        const matrix = context.matrix;
        const parsedUrl = url.parse(this.context.location_href);
        const matrixBase = parsedUrl.search || '';
        const matrixSearch = matrixBase + (matrixBase ? '&' : '?');
        const notification = context.notification;
        const visualizeLimit = 500;
        if (notification === 'Success' || notification === 'No results found') {
            const xFacets = matrix.x.facets.map(f => _.findWhere(context.facets, { field: f })).filter(f => f);
            let yFacets = matrix.y.facets.map(f => _.findWhere(context.facets, { field: f })).filter(f => f);
            yFacets = yFacets.concat(_.reject(context.facets, f => _.contains(matrix.x.facets, f.field) || _.contains(matrix.y.facets, f.field)));
            const xGrouping = matrix.x.group_by;
            const primaryYGrouping = matrix.y.group_by[0];
            const secondaryYGrouping = matrix.y.group_by[1];
            const xBuckets = matrix.x.buckets;
            const xLimit = matrix.x.limit || xBuckets.length;
            const yGroups = matrix.y[primaryYGrouping].buckets;
            const yGroupFacet = _.findWhere(context.facets, { field: primaryYGrouping });
            const yGroupOptions = yGroupFacet ? yGroupFacet.terms.map(term => term.key) : [];
            yGroupOptions.sort();
            const searchBase = context.matrix.search_base;
            const visualizeDisabled = matrix.doc_count > visualizeLimit;
            const colCount = Math.min(xBuckets.length, xLimit + 1);

            // Get a sorted list of batch hubs keys with case-insensitive sort
            // NOTE: Tim thinks this is overkill as opposed to simple sort()
            let visualizeKeys = [];
            if (context.visualize_batch && Object.keys(context.visualize_batch).length) {
                visualizeKeys = Object.keys(context.visualize_batch).sort((a, b) => {
                    const aLower = a.toLowerCase();
                    const bLower = b.toLowerCase();
                    return (aLower > bLower) ? 1 : ((aLower < bLower) ? -1 : 0);
                });
            }

            // Map view icons to svg icons
            const view2svg = {
                'list-alt': 'search',
                table: 'table',
                summary: 'summary',
            };

            // Make an array of colors corresponding to the ordering of biosample_type
            const biosampleTypeColors = globals.biosampleTypeColors.colorList(yGroups.map(yGroup => yGroup.key));

            return (

{context.title}

Click or enter search terms to filter the experiments included in the matrix.

                                                <TextFilter filters={context.filters} searchBase={matrixSearch} onChange={this.onChange} />

                                <FacetList
                                    facets={xFacets}
                                    filters={context.filters}
                                    orientation="horizontal"
                                    searchBase={matrixSearch}
                                    onFilter={this.onFilter}
                                />

                                <FacetList facets={yFacets} filters={context.filters} searchBase={matrixSearch} onFilter={this.onFilter} />

                                            {matrix.y.label.toUpperCase()}

                                            {matrix.doc_count ?

                                                    {matrix.x.label.toUpperCase()}

                                            : null}

                                                        {matrix.doc_count} results

                                                        {matrix.doc_count && context.views ? context.views.map(view => {svgIcon(view2svg[view.icon])}) : ''}

                                                    {context.filters.length ?

                                                            Clear Filters

                                                    : null}

                                                {xBuckets.map((xb, i) => {
                                                    if (i < xLimit) {
                                                        const href = `${searchBase}&${xGrouping}=${globals.encodedURIComponent(xb.key)}`;
                                                        return
{xb.key}
	;
                                                    } else if (i === xLimit) {
                                                        const parsed = url.parse(matrixBase, true);
                                                        parsed.query['x.limit'] = null;
                                                        delete parsed.search; // this makes format compose the search string out of the query object
                                                        const unlimitedHref = url.format(parsed);
                                                        return
...and {xBuckets.length - xLimit} more
	;
                                                    }
                                                    return null;
                                                })}

                                            {yGroups.map((group, i) => {
                                                const groupColor = biosampleTypeColors[i];
                                                const seriesColor = color(groupColor);
                                                const parsed = url.parse(matrixBase, true);
                                                parsed.query[primaryYGrouping] = group.key;
                                                parsed.query['y.limit'] = null;
                                                delete parsed.search; // this makes format compose the search string out of the query object
                                                const groupHref = url.format(parsed);
                                                const rows = [
                                                    <tr key={`group-${group.key}`}>

                                                            {group.key}

,
                                                ];
                                                const groupBuckets = group[secondaryYGrouping].buckets;
                                                const yLimit = matrix.y.limit || groupBuckets.length;

                                                // If this group isn't open (noted by
                                                // this.state.yGroupOpen[key]), extract just the
                                                // group rows that are under the display limit.
                                                const groupRows = (this.state.yGroupOpen[group.key] || this.state.allYGroupsOpen) ? groupBuckets : groupBuckets.slice(0, yLimit);
                                                rows.push(...groupRows.map((yb) => {
                                                    const href = `${searchBase}&${secondaryYGrouping}=${globals.encodedURIComponent(yb.key)}`;
                                                    return (
                                                        <tr key={`yb-${yb.key}`}>
                                                            {yb.key}
                                                            {xBuckets.map((xb, k) => {
                                                                if (k < xLimit) {
                                                                    const value = yb[xGrouping][k];
                                                                    const cellColor = seriesColor.clone();
                                                                    // scale color between white and the series color
                                                                    cellColor.lightness(cellColor.lightness() + ((1 - (value / matrix.max_cell_doc_count)) * (100 - cellColor.lightness())));
                                                                    const textColor = cellColor.luminosity() > 0.5 ? '#000' : '#fff';
                                                                    const cellHref = `${searchBase}&${secondaryYGrouping}=${globals.encodedURIComponent(yb.key)}&${xGrouping}=${globals.encodedURIComponent(xb.key)}`;
                                                                    const title = `${yb.key} / ${xb.key}: ${value}`;
                                                                    return (

                                                                            {value ? {value} : null}

                                                                    );
                                                                }
                                                                return null;
                                                            })}
                                                            {xBuckets.length > xLimit && }

                                                    );
                                                }));
                                                if (groupBuckets.length > yLimit && !this.state.allYGroupsOpen) {
                                                    rows.push(

                                                                <GroupMoreButton
                                                                    id={group.key}
                                                                    handleClick={this.handleClick}
                                                                    displayText={this.state.yGroupOpen[group.key] ? '- See fewer' : `+ See ${groupBuckets.length - yLimit} more…`}
                                                                />

                                                            {_.range(colCount - 1).map(n => )}

                                                    );
                                                }
                                                return rows;
                                            })}

                                            {/* Display the See Fewer/See All button controlling
                                                the whole table if at least one biosample_type has
                                                more than the limit. We know this is the case if at
                                                least one yGroupOpen state member exists. */}
                                            {Object.keys(this.state.yGroupOpen).length ?

                                                        <button className="group-all-groups-cell__button" onClick={this.handleSeeAllClick}>
                                                            {this.state.allYGroupsOpen ? 'See fewer biosamples' : 'See all biosamples'}
                                                        </button>

                                            : null}

 { this.hubscontrols = div; }}>
                                    {context.batch_download ?
                                        <BatchDownload context={context} />
                                    : null}
                                    {' '}
                                    {visualizeKeys.length ?
                                        <BrowserSelector
                                            visualizeCfg={context.visualize_batch}
                                            disabled={visualizeDisabled}
                                            title={visualizeDisabled ? `Filter to ${visualizeLimit} to visualize` : 'Visualize'}
                                        />
                                    : null}

            );
        }
        return
{notification}
;
    }
}

Matrix.propTypes = {
    context: React.PropTypes.object.isRequired,
};

Matrix.contextTypes = {
    location_href: PropTypes.string,
    navigate: PropTypes.func,
    biosampleTypeColors: PropTypes.object, // DataColor instance for experiment project
};

globals.contentViews.register(Matrix, 'Matrix');
<|endoftext|>


<commit_before>

class HotelDescriptionHandler{

	constructor(){

	}

	doFulfill(app,db){

		app.db().load("selectedHotel", (err, data) => {
			var descriptionArr = data.annotation.description;
			if(descriptionArr && descriptionArr.lengt>0){
				var descriptionText = "";
				descriptionArr.forEach((descEntry) => {
					descriptionText += descEntry+". ";
				})
	    	    app.ask("Alright here comes the description: "+descriptionText);
			}else{
				app.ask("I'm sorry, I couldn't find any description.");
			}
    	});

	}
}

module.exports = HotelDescriptionHandler;
<commit_msg>array/non-array destinction description<commit_after>

class HotelDescriptionHandler{

	constructor(){

	}

	doFulfill(app,db){

		app.db().load("selectedHotel", (err, data) => {
			var descriptionArr = data.annotation.description;
			if(descriptionArr){
				var descriptionText = "";
				if(Array.isArray(descriptionArr)){
					descriptionArr.forEach((descEntry) => {
						descriptionText += descEntry+". ";
					})
					if(descriptionText!==""){
						app.ask("Alright here comes the description: "+descriptionText);
					}else{
						app.ask("I'm sorry, but the description property is not available.")
					}
				}
				else{
					app.ask("Alright here comes the description: "+descriptionArr)
				}
			}else{
				app.ask("I'm sorry, I couldn't find any description.");
			}
    	});

	}
}

module.exports = HotelDescriptionHandler;
<|endoftext|>


<commit_before>const sanitize = require('sanitize-html');
const moment = require('moment');
const s = require('underscore.string');
const Boom = require('boom');
const _ = require('lodash');
const validator = require('validator');

const { Jobs, Inquiries } = require('../../models');
const config = require('../../../config');

module.exports = async function(ctx) {
  let { body } = ctx.request;

  body = _.pick(body, ['email', 'message']);

  if (!_.isString(body.email) || !validator.isEmail(body.email))
    return ctx.throw(Boom.badRequest(ctx.translate('INVALID_EMAIL')));

  if (!_.isUndefined(body.message) && !_.isString(body.message))
    delete body.message;

  if (body.message)
    body.message = sanitize(body.message, {
      allowedTags: [],
      allowedAttributes: []
    });

  if (body.message && s.isBlank(body.message))
    return ctx.throw(Boom.badRequest(ctx.translate('INVALID_MESSAGE')));

  if (!body.message) {
    body.message = ctx.translate('CONTACT_REQUEST_MESSAGE');
    body.is_email_only = true;
  } else if (body.message.length > config.contactRequestMaxLength) {
    return ctx.throw(Boom.badRequest(ctx.translate('INVALID_MESSAGE')));
  }

  // check if we already sent a contact request in the past day
  // with this given ip address or email, otherwise create and email
  const count = await Inquiries.count({
    $or: [
      {
        ip: ctx.req.ip
      },
      {
        email: body.email
      }
    ],
    created_at: {
      $gte: moment()
        .subtract(1, 'day')
        .toDate()
    }
  });

  if (count > 0 && config.env !== 'development')
    return ctx.throw(Boom.badRequest(ctx.translate('CONTACT_REQUEST_LIMIT')));

  try {
    const inquiry = await Inquiries.create({
      ...body,
      ip: ctx.req.ip
    });

    // TODO: ensure timestamp is shown on console and saved
    // we may want to use `console-stamp` package
    ctx.logger.info('created inquiry', inquiry);

    const job = await Jobs.create({
      name: 'email',
      data: {
        template: 'inquiry',
        to: body.email,
        cc: config.email.from,
        locals: {
          locale: ctx.req.locale,
          inquiry
        }
      }
    });

    ctx.logger.info('queued inquiry email', job);

    const message = ctx.translate('CONTACT_REQUEST_SENT');
    if (ctx.accepts('json')) {
      ctx.body = { message };
    } else {
      ctx.flash('success', message);
      ctx.redirect('back');
    }
  } catch (err) {
    // TODO: this should have a `user` object prop
    ctx.logger.error(err, {
      ip: ctx.req.ip,
      message: body.message,
      email: body.email
    });

    ctx.throw(ctx.translate('CONTACT_REQUEST_ERROR'));
  }
};
<commit_msg>patch contact page ctx.req.ip for tests<commit_after>const sanitize = require('sanitize-html');
const moment = require('moment');
const s = require('underscore.string');
const Boom = require('boom');
const _ = require('lodash');
const validator = require('validator');

const { Jobs, Inquiries } = require('../../models');
const config = require('../../../config');

module.exports = async function(ctx) {
  let { body } = ctx.request;

  // @TODO: Remove this once store ip is fixed
  if (config.env === 'test') ctx.req.ip = ctx.req.ip || '127.0.0.1';

  body = _.pick(body, ['email', 'message']);

  if (!_.isString(body.email) || !validator.isEmail(body.email))
    return ctx.throw(Boom.badRequest(ctx.translate('INVALID_EMAIL')));

  if (!_.isUndefined(body.message) && !_.isString(body.message))
    delete body.message;

  if (body.message)
    body.message = sanitize(body.message, {
      allowedTags: [],
      allowedAttributes: []
    });

  if (body.message && s.isBlank(body.message))
    return ctx.throw(Boom.badRequest(ctx.translate('INVALID_MESSAGE')));

  if (!body.message) {
    body.message = ctx.translate('CONTACT_REQUEST_MESSAGE');
    body.is_email_only = true;
  } else if (body.message.length > config.contactRequestMaxLength) {
    return ctx.throw(Boom.badRequest(ctx.translate('INVALID_MESSAGE')));
  }

  // check if we already sent a contact request in the past day
  // with this given ip address or email, otherwise create and email
  const count = await Inquiries.count({
    $or: [
      {
        ip: ctx.req.ip
      },
      {
        email: body.email
      }
    ],
    created_at: {
      $gte: moment()
        .subtract(1, 'day')
        .toDate()
    }
  });

  if (count > 0 && config.env !== 'development')
    return ctx.throw(Boom.badRequest(ctx.translate('CONTACT_REQUEST_LIMIT')));

  try {
    const inquiry = await Inquiries.create({
      ...body,
      ip: ctx.req.ip
    });

    // TODO: ensure timestamp is shown on console and saved
    // we may want to use `console-stamp` package
    ctx.logger.info('created inquiry', inquiry);

    const job = await Jobs.create({
      name: 'email',
      data: {
        template: 'inquiry',
        to: body.email,
        cc: config.email.from,
        locals: {
          locale: ctx.req.locale,
          inquiry
        }
      }
    });

    ctx.logger.info('queued inquiry email', job);

    const message = ctx.translate('CONTACT_REQUEST_SENT');
    if (ctx.accepts('json')) {
      ctx.body = { message };
    } else {
      ctx.flash('success', message);
      ctx.redirect('back');
    }
  } catch (err) {
    // TODO: this should have a `user` object prop
    ctx.logger.error(err, {
      ip: ctx.req.ip,
      message: body.message,
      email: body.email
    });

    ctx.throw(ctx.translate('CONTACT_REQUEST_ERROR'));
  }
};
<|endoftext|>


<commit_before>import koaRouter from 'koa-router';
import requireAll from 'require-all';

/**
 * Routing
 */
const routes = requireAll({
  dirname: __dirname + '/../services',
  filter: /(.+routes)\.js$/,
});

const apiRouter = koaRouter()
  .get('/', ctx => ctx.body = 'Welcome on Seekube API V2')
  .get('/healthz', ctx => ctx.status = 200);

Object.keys(routes).forEach(index => {
  apiRouter.use('', routes[index][`${index}.routes`].default.routes());
});

export default apiRouter;
<commit_msg>Update / endpoint<commit_after>import koaRouter from 'koa-router';
import requireAll from 'require-all';

/**
 * Routing
 */
const routes = requireAll({
  dirname: __dirname + '/../services',
  filter: /(.+routes)\.js$/,
});

const apiRouter = koaRouter()
  .get('/', ctx => ctx.body = 'Welcome on Koa2 Boilerplate')
  .get('/healthz', ctx => ctx.status = 200);

Object.keys(routes).forEach(index => {
  apiRouter.use('', routes[index][`${index}.routes`].default.routes());
});

export default apiRouter;
<|endoftext|>


<commit_before>angular.module('ngDrawer', [])

.provider('ngDrawer', function(){

	var ngDrawerConfig		=	{
									'snappingFunctions' : {}
								}

		this.config = function(custom_config){
			angular.extend(ngDrawerConfig, custom_config)
			return this
		}

		this.addSnappingFunction = function(name, fn){
			ngDrawerConfig.snappingFunctions[name] = fn
			return this
		}

		this.$get = function(){
			return ngDrawerConfig
		}

		this.addSnappingFunction('default',
			function (delta, distance, scrollWidth){
				var dir = 0

				if(Math.abs(delta)>=10){	//swipe
					dir = 	delta > 0
							?	 1
							:	-1

				}else{						//touchend
					dir	=	distance > 0.5
							?	 1
							:	-1

				}

				return	(Math.abs(delta)+4)*dir
			}
		)

})

.directive('ngDrawer',[

	'$document',
	'$interval',
	'ngDrawer',

	function($document, $interval, ngDrawer){
		return {

			transclude:		true,
			scope:			true,

			link: function(scope, element, attrs, ctrl, transclude){

				scope.drawn = false

				var container				= 	$document.find('body').eq(0),
					frame					=	element,
					shuttle					= 	angular.element('

'),
					last_scroll_pos 		= 	undefined,
					scroll_space			= 	undefined,
					available_space_left 	= 	0

				transclude(function(clone){
					shuttle.append(clone)
					element.append(shuttle)
				})

				frame.css({
					'overflow-x':			'scroll',
					'overflow-y':			'hidden',
				})

				shuttle.css({
					'position': 			'relative',
					'border-right-style':	'solid',
					'border-right-color':	'transparent',
					'width':				'0'
				})

				function frac2px(p){
					return available_space_left*p
				}

				function px2frac(p){
					return 	(p == 0 || available_space_left == 0)
							?	0
							:	p/available_space_left
				}

				function getMomentum(delta, distance, DOM){
					return ngDrawer.snappingFunctions['default'](delta, distance, DOM) //DOM -> this
				}

				scope.getDistance = function(){
					return px2frac(frame[0].scrollLeft)
				}

				function draw(){
					if(!scope.drawn) {

						scope.drawn = true

						function getOffset(from, to){
							var offset_parent = from[0].offsetParent
												?	angular.element(from[0].offsetParent)
												:	null

							console.log(from[0], from[0].offsetLeft)

							return 	offset_parent && (offset_parent != to[0])
									?	from[0].offsetLeft+getOffset(offset_parent, to)
									:	0
						}

						available_space_left 	=  	getOffset(frame, container)
						tucked_width			=	frame[0].offsetWidth

						console.log(available_space_left)

						frame.css({
							'padding-left':			available_space_left+'px',
							'width':				'auto'
						})

						shuttle.css({
							'border-right-width':	available_space_left+tucked_width+'px'//frame[0].clientWidth-element[0].clientWidth+'px'
						})

						element.addClass('drawn')
					}

					$document.find('body').eq(0).one('mouseup touchend', snap)

				}

				//todo alles auf parent element verschieben,
				// momentum funktion auf this

				function snap() {

					var last_scroll_pos 		= 	frame[0].scrollLeft,
						distance				=	undefined,
						check_scrolling 		= 	$interval(updateScrolling, 15, false)

					function updateScrolling(){
						var scroll_pos			= 	frame[0].scrollLeft,
							delta 				= 	(scroll_pos-last_scroll_pos),
							distance			=	distance == undefined ? scope.getDistance() : distance,
							momentum			=	getMomentum(delta, distance)

						last_scroll_pos = frame[0].scrollLeft

						frame[0].scrollLeft += momentum

						if([0,1].indexOf(distance) != -1 && delta == 0){
							$interval.cancel(check_scrolling)
						 	if(distance == 0) tuck()
						}
					}

				}

				function tuck(){
				 	scope.drawn = false
				 	element.removeClass('drawn')

				 	frame.css({
				 		'padding-left':		'',
				 		'width':			''
				 	})

				 	shuttle.css({
				 		'border-right-width':		'0'
				 	})

				}

				element.on('mousedown touchstart', draw)

				scope.$on('$destroy', function(){
					$document.find('body').eq(0).off('mouseup touchend', snap)
				})

			}

		}
	}
])

<commit_msg>first line of ngdoc comments<commit_after>angular.module('ngDrawer', [])

.provider('ngDrawer', function(){

	var ngDrawerConfig		=	{
									'snappingFunctions' : {}
								}

		this.config = function(custom_config){
			angular.extend(ngDrawerConfig, custom_config)
			return this
		}

		this.addSnappingFunction = function(name, fn){
			ngDrawerConfig.snappingFunctions[name] = fn
			return this
		}

		this.$get = function(){
			return ngDrawerConfig
		}

		this.addSnappingFunction('default',
			function (delta, distance, scrollWidth){
				var dir = 0

				if(Math.abs(delta)>=10){	//swipe
					dir = 	delta > 0
							?	 1
							:	-1

				}else{						//touchend
					dir	=	distance > 0.5
							?	 1
							:	-1

				}

				return	(Math.abs(delta)+4)*dir
			}
		)

})

/**
 * @ngdoc directive
 * @name ngDrawer.directive:ngDrawer
 * @scope
 * @restrict EA
 * @element
 * @function
 *
 * @description
 *
 *
 *
 * @example
   <example module="ngDrawer">
     <file name="index.html">

         	Pull me

     </file>
   </example>
 */

.directive('ngDrawer',[

	'$document',
	'$interval',
	'ngDrawer',

	function($document, $interval, ngDrawer){
		return {

			transclude:		true,
			scope:			true,
			restrict:		'EA',

			link: function(scope, element, attrs, ctrl, transclude){

				scope.drawn = false

				var container				= 	$document.find('body').eq(0),
					frame					=	element,
					shuttle					= 	angular.element('

'),
					last_scroll_pos 		= 	undefined,
					scroll_space			= 	undefined,
					available_space_left 	= 	0

				transclude(function(clone){
					shuttle.append(clone)
					element.append(shuttle)
				})

				frame.css({
					'overflow-x':			'scroll',
					'overflow-y':			'hidden',
				})

				shuttle.css({
					'position': 			'relative',
					'border-right-style':	'solid',
					'border-right-color':	'transparent',
					'width':				'0'
				})

				function frac2px(p){
					return available_space_left*p
				}

				function px2frac(p){
					return 	(p == 0 || available_space_left == 0)
							?	0
							:	p/available_space_left
				}

				function getMomentum(delta, distance, DOM){
					return ngDrawer.snappingFunctions['default'](delta, distance, DOM) //DOM -> this
				}

				scope.getDistance = function(){
					return px2frac(frame[0].scrollLeft)
				}

				function draw(){
					if(!scope.drawn) {

						scope.drawn = true

						function getOffset(from, to){
							var offset_parent = from[0].offsetParent
												?	angular.element(from[0].offsetParent)
												:	null

							console.log(from[0], from[0].offsetLeft)

							return 	offset_parent && (offset_parent != to[0])
									?	from[0].offsetLeft+getOffset(offset_parent, to)
									:	0
						}

						available_space_left 	=  	getOffset(frame, container)
						tucked_width			=	frame[0].offsetWidth

						console.log(available_space_left)

						frame.css({
							'padding-left':			available_space_left+'px',
							'width':				'auto'
						})

						shuttle.css({
							'border-right-width':	available_space_left+tucked_width+'px'//frame[0].clientWidth-element[0].clientWidth+'px'
						})

						element.addClass('drawn')
					}

					$document.find('body').eq(0).one('mouseup touchend', snap)

				}

				//todo alles auf parent element verschieben,
				// momentum funktion auf this

				function snap() {

					var last_scroll_pos 		= 	frame[0].scrollLeft,
						distance				=	undefined,
						check_scrolling 		= 	$interval(updateScrolling, 15, false)

					function updateScrolling(){
						var scroll_pos			= 	frame[0].scrollLeft,
							delta 				= 	(scroll_pos-last_scroll_pos),
							distance			=	distance == undefined ? scope.getDistance() : distance,
							momentum			=	getMomentum(delta, distance)

						last_scroll_pos = frame[0].scrollLeft

						frame[0].scrollLeft += momentum

						if([0,1].indexOf(distance) != -1 && delta == 0){
							$interval.cancel(check_scrolling)
						 	if(distance == 0) tuck()
						}
					}

				}

				function tuck(){
				 	scope.drawn = false
				 	element.removeClass('drawn')

				 	frame.css({
				 		'padding-left':		'',
				 		'width':			''
				 	})

				 	shuttle.css({
				 		'border-right-width':		'0'
				 	})

				}

				element.on('mousedown touchstart', draw)

				scope.$on('$destroy', function(){
					$document.find('body').eq(0).off('mouseup touchend', snap)
				})

			}

		}
	}
])

<|endoftext|>


<commit_before>var autoprefix = require("gulp-autoprefixer"),
    gulp = require('gulp-npm-run')(require('gulp'), {
      exclude: ['test'], // the test script is excluded
      include: [
        'build:release',
        'build:dev',
      ], // just a helpful description
      npmRun: true // rather than `npm run script` gulp runs the script's value / command(s)
    }),
    param = require('gulp-param')(gulp, process.argv),
    gutil = require('gulp-util'),
    rename = require('gulp-rename'),
    log = require('gulp-log'),
    filter = require('gulp-filter'),
    sicon = require('./scripts/icon'),
    buffer = require('gulp-buffer'),
    watch = require('gulp-watch'),
    path = require('path'),
    fs = require('fs'),
    sourcemaps = require('gulp-sourcemaps'),
    cleanCSS = require('gulp-clean-css'),
    sass = require("gulp-sass");

var paths = {
    scss: [
        "./scss/*.scss",
        "./scss/**/*.scss",
    ],
    components: [
        './components/*',
        './components/**/*',
        './webpack.config.js',
    ]
};

function handleSassError(err) {
    gutil.log(gutil.colors.red('sass error\r\n'), gutil.colors.yellow(err.message));
    this.emit('end');
}

gulp.task("sass", ['sass:dev'], function() {
    return gulp.src('../dist/css/veak.css')
        .pipe(rename(function (path) {
            path.basename = path.basename + '.min';
        }))
        .pipe(cleanCSS())
        .pipe(gulp.dest("../dist/css"))
        ;
});

gulp.task("sass:dev", function() {
    return gulp.src('./scss/veak.scss')
        .pipe(sourcemaps.init())
        .pipe(sass({ includePaths: [
            path.resolve(__dirname, "scss"),
        ]}))
        .on('error', handleSassError)
        .pipe(autoprefix("last 5 versions")) // for Safari flex
        .pipe(sourcemaps.write('./'))
        .pipe(gulp.dest("../dist/css"))
        ;
});

function makeIconSassTask (name) {
    return function () {
        var item = sicon.resolve(name);
        // var cssFilter = filter('*.css', { restore: true });
        // var fontFilter = filter(['.eot', '.svg', '.ttf', '.woff', '.woff2'], { restore: true });
        return gulp.src(item.cssFile)
                .pipe(rename(function (path) {
                    path.basename = '_icons-' + path.basename;
                    path.extname = ".scss"
                }))
                .pipe(buffer())
                .pipe(sicon.gulpContentFilter(function (file) {
                    file.contents = new Buffer(item.toSass(file.contents.toString('utf8')));
                }))
                .pipe(gulp.dest("../scss/components/icons/"))
            ;
    }
}

function makeIconFontTask (name) {
    return function () {
        var item = sicon.resolve(name);
        return gulp.src(item.fontsDir)
                .pipe(gulp.dest("./fonts/"))
            ;
    }
}

['photon', 'font-awesome'].forEach(function (it) {
    gulp.task("icon-" + it + '-sass', makeIconSassTask(it));
    gulp.task("icon-" + it + '-font', makeIconFontTask(it));
});

gulp.task("icon-photon", ["icon-photon-sass", "icon-photon-font"]);
gulp.task("icon-font-awesome", ["icon-font-awesome-sass", "icon-font-awesome-font"]);

// 更新图标库
gulp.task("icons", ["icon-font-awesome", "icon-photon"]);

gulp.task("fonts", function(){
    return gulp.src('./fonts/*').pipe(gulp.dest("../dist/fonts"));
});

gulp.task("dev", ["fonts"], function() {
    gulp.start("sass");
    watch(paths.scss, function(){
        gulp.start('sass');
    });

    gulp.start('build:dev');
    watch(paths.components, function () {
        gulp.start('build:dev');
    });
});

gulp.task("default", ['sass', 'fonts', 'build:release', 'build:dev']);
<commit_msg>10 version ?<commit_after>var autoprefix = require("gulp-autoprefixer"),
    gulp = require('gulp-npm-run')(require('gulp'), {
      exclude: ['test'], // the test script is excluded
      include: [
        'build:release',
        'build:dev',
      ], // just a helpful description
      npmRun: true // rather than `npm run script` gulp runs the script's value / command(s)
    }),
    param = require('gulp-param')(gulp, process.argv),
    gutil = require('gulp-util'),
    rename = require('gulp-rename'),
    log = require('gulp-log'),
    filter = require('gulp-filter'),
    sicon = require('./scripts/icon'),
    buffer = require('gulp-buffer'),
    watch = require('gulp-watch'),
    path = require('path'),
    fs = require('fs'),
    sourcemaps = require('gulp-sourcemaps'),
    cleanCSS = require('gulp-clean-css'),
    sass = require("gulp-sass");

var paths = {
    scss: [
        "./scss/*.scss",
        "./scss/**/*.scss",
    ],
    components: [
        './components/*',
        './components/**/*',
        './webpack.config.js',
    ]
};

function handleSassError(err) {
    gutil.log(gutil.colors.red('sass error\r\n'), gutil.colors.yellow(err.message));
    this.emit('end');
}

gulp.task("sass", ['sass:dev'], function() {
    return gulp.src('../dist/css/veak.css')
        .pipe(rename(function (path) {
            path.basename = path.basename + '.min';
        }))
        .pipe(cleanCSS())
        .pipe(gulp.dest("../dist/css"))
        ;
});

gulp.task("sass:dev", function() {
    return gulp.src('./scss/veak.scss')
        .pipe(sourcemaps.init())
        .pipe(sass({ includePaths: [
            path.resolve(__dirname, "scss"),
        ]}))
        .on('error', handleSassError)
        .pipe(autoprefix("last 10 versions")) // for Safari flex
        .pipe(sourcemaps.write('./'))
        .pipe(gulp.dest("../dist/css"))
        ;
});

function makeIconSassTask (name) {
    return function () {
        var item = sicon.resolve(name);
        // var cssFilter = filter('*.css', { restore: true });
        // var fontFilter = filter(['.eot', '.svg', '.ttf', '.woff', '.woff2'], { restore: true });
        return gulp.src(item.cssFile)
                .pipe(rename(function (path) {
                    path.basename = '_icons-' + path.basename;
                    path.extname = ".scss"
                }))
                .pipe(buffer())
                .pipe(sicon.gulpContentFilter(function (file) {
                    file.contents = new Buffer(item.toSass(file.contents.toString('utf8')));
                }))
                .pipe(gulp.dest("../scss/components/icons/"))
            ;
    }
}

function makeIconFontTask (name) {
    return function () {
        var item = sicon.resolve(name);
        return gulp.src(item.fontsDir)
                .pipe(gulp.dest("./fonts/"))
            ;
    }
}

['photon', 'font-awesome'].forEach(function (it) {
    gulp.task("icon-" + it + '-sass', makeIconSassTask(it));
    gulp.task("icon-" + it + '-font', makeIconFontTask(it));
});

gulp.task("icon-photon", ["icon-photon-sass", "icon-photon-font"]);
gulp.task("icon-font-awesome", ["icon-font-awesome-sass", "icon-font-awesome-font"]);

// 更新图标库
gulp.task("icons", ["icon-font-awesome", "icon-photon"]);

gulp.task("fonts", function(){
    return gulp.src('./fonts/*').pipe(gulp.dest("../dist/fonts"));
});

gulp.task("dev", ["fonts"], function() {
    gulp.start("sass");
    watch(paths.scss, function(){
        gulp.start('sass');
    });

    gulp.start('build:dev');
    watch(paths.components, function () {
        gulp.start('build:dev');
    });
});

gulp.task("default", ['sass', 'fonts', 'build:release', 'build:dev']);
<|endoftext|>