<commit_before>/*
 * ScrollSpy class definition
 */

import { isArray } from '../utils/array';
import { assign, keys } from '../utils/object';
import { observeDom, warn } from '../utils';
import {
    isElement,
    isVisible,
    closest,
    matches,
    getBCR,
    offset,
    position,
    selectAll,
    select,
    hasClass,
    addClass,
    removeClass,
    getAttr,
    eventOn,
    eventOff
} from '../utils/dom';

/*
 * Constants / Defaults
 */

const NAME = 'v-b-scrollspy';
const ACTIVATE_EVENT = 'bv::scrollspy::activate';

const Default = {
    element: 'body',
    offset: 10,
    method: 'auto',
    throttle: 75
};

const DefaultType = {
    element: '(string|element|component)',
    offset: 'number',
    method: 'string',
    throttle: 'number'
};

const ClassName = {
    DROPDOWN_ITEM: 'dropdown-item',
    ACTIVE: 'active'
};

const Selector = {
    ACTIVE: '.active',
    NAV_LIST_GROUP: '.nav, .list-group',
    NAV_LINKS: '.nav-link',
    NAV_ITEMS: '.nav-item',
    LIST_ITEMS: '.list-group-item',
    DROPDOWN: '.dropdown, dropup',
    DROPDOWN_ITEMS: '.dropdown-item',
    DROPDOWN_TOGGLE: '.dropdown-toggle'
};

const OffsetMethod = {
    OFFSET: 'offset',
    POSITION: 'position'
};

// HREFs must start with # but can be === '#', or start with '#/' or '#!' (which can be router links)
const HREF_REGEX = /^#[^/!]+/;

/*
 * Utility Methods
 */

// Better var type detection
function toType(obj) {
    return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
}

// Check config properties for expected types
function typeCheckConfig(componentName, config, configTypes) {
    for (const property in configTypes) {
        if (Object.prototype.hasOwnProperty.call(configTypes, property)) {
            const expectedTypes = configTypes[property];
            const value = config[property];
            let valueType = value && isElement(value) ? 'element' : toType(value);
            // handle Vue instances
            valueType = value && value._isVue ? 'component' : valueType;

            if (!new RegExp(expectedTypes).test(valueType)) {
                warn(
                    componentName + ': Option "' + property + '" provided type "' +
                    valueType + '", but expected type "' + expectedTypes + '"'
                );
            }
        }
    }
}

/*
 * ------------------------------------------------------------------------
 * Class Definition
 * ------------------------------------------------------------------------
 */

class ScrollSpy {

    constructor(element, config, $root) {
        // The element we activate links in
        this.$el = element;
        this.$scroller = null;
        this.$selector = [
            Selector.NAV_LINKS,
            Selector.LIST_ITEMS,
            Selector.DROPDOWN_ITEMS
        ].join(',');
        this.$offsets = [];
        this.$targets = [];
        this.$activeTarget = null;
        this.$scrollHeight = 0;
        this.$resizeTimeout = null;
        this.$obs_scroller = null;
        this.$obs_targets = null;
        this.$root = $root || null;
        this.$config = null;

        this.updateConfig(config);
    }

    static get Name() {
        return NAME;
    }

    static get Default() {
        return Default;
    }

    static get DefaultType() {
        return DefaultType;
    }

    updateConfig(config, $root) {
        if (this.$scroller) {
            // Just in case out scroll element has changed
            this.unlisten();
            this.$scroller = null;
        }
        const cfg = assign({}, this.constructor.Default, config);
        if ($root) {
            this.$root = $root;
        }
        typeCheckConfig(this.constructor.Name, cfg, this.constructor.DefaultType);
        this.$config = cfg;

        if (this.$root) {
            const self = this;
            this.$root.$nextTick(() => {
                self.listen();
            });
        } else {
            this.listen();
        }

    }

    dispose() {
        this.unlisten();
        clearTimeout(this.$resizeTimeout);
        this.$resizeTimeout = null;
        this.$el = null;
        this.$config = null;
        this.$scroller = null;
        this.$selector = null;
        this.$offsets = null;
        this.$targets = null;
        this.$activeTarget = null;
        this.$scrollHeight = null;
    }

    listen() {
        const scroller = this.getScroller();
        if (scroller && scroller.tagName !== 'BODY') {
            eventOn(scroller, 'scroll', this);
        }
        eventOn(window, 'scroll', this);
        eventOn(window, 'resize', this);
        eventOn(window, 'orientationchange', this);
        this.setObservers(true);
        // Scedule a refresh
        this.handleEvent('refresh');
    }

    unlisten() {
        const scroller = this.getScroller();
        this.setObservers(false);
        if (scroller && scroller.tagName !== 'BODY') {
            eventOff(scroller, 'scroll', this);
        }
        eventOff(window, 'scroll', this);
        eventOff(window, 'resize', this);
        eventOff(window, 'orientationchange', this);
    }

    setObservers(on) {
        // We observe both the scroller for content changes, and the target links
        if (this.$obs_scroller) {
            this.$obs_scroller.disconnect();
            this.$obs_scroller = null;
        }
        if (this.$obs_targets) {
            this.$obs_targets.disconnect();
            this.$obs_targets = null;
        }
        if (on) {
            this.$obs_targets = observeDom(this.$el, () => {this.handleEvent('mutation')}, {
                subtree: true,
                childList: true,
                attributes: true,
                attributeFilter: ['href']
            });
            this.$obs_scroller = observeDom(this.getScroller(), () => {this.handleEvent('mutation')}, {
                subtree: true,
                childList: true,
                characterData: true,
                attributes: true,
                attributeFilter: ['id', 'style', 'class']
            });
        }
    }

    // general event handler
    handleEvent(evt) {
        const type = typeof evt === 'string' ? evt : evt.type;

        const self = this;
        function resizeThrottle() {
            if(!self.$resizeTimeout) {
                self.$resizeTimeout = setTimeout(() => {
                    self.refresh();
                    self.process();
                    self.$resizeTimeout = null;
                }, self.$config.throttle);
            }
        }

        if (type === 'scroll') {
            if (!this.$obs_scroller) {
                // Just in case we are added to the DOM before the scroll target is
                // We re-instantiate our listeners, just in case
                this.listen();
            }
            this.process();
        } else if (/(resize|orientationchange|mutation|refresh)/.test(type)) {
            // Postpone these events by throttle time
            resizeThrottle();
        }
    }

    // Refresh the list of target links on the element we are applied to
    refresh() {
        const scroller = this.getScroller();
        if (!scroller) {
            return;
        }
        const autoMethod = scroller !== scroller.window ? OffsetMethod.POSITION : OffsetMethod.OFFSET;
        const method = this.$config.method === 'auto' ? autoMethod : this.$config.method;
        const methodFn = method === OffsetMethod.POSITION ? position : offset;
        const offsetBase = method === OffsetMethod.POSITION ? this.getScrollTop() : 0;

        this.$offsets = [];
        this.$targets = [];

        this.$scrollHeight = this.getScrollHeight();

        // Find all the unique link href's
        selectAll(this.$selector, this.$el)
        .map(link => getAttr(link, 'href'))
        .filter(href => HREF_REGEX.test(href|| ''))
        .map(href => {
            const el = select(href, scroller);
            if (isVisible(el)) {
                return {
                    offset: parseInt(methodFn(el).top, 10) + offsetBase,
                    target: href
                }
            }
            return null;
        })
        .filter(item => item)
        .sort((a, b) => a.offset - b.offset)
        .reduce((memo, item) => {
            // record only unique targets/offfsets
            if (!memo[item.target]) {
                this.$offsets.push(item.offset);
                this.$targets.push(item.target);
                memo[item.target] = true;
            }
            return memo;
        }, {});

        return this;
    }

    // Handle activating/clearing
    process() {
        const scrollTop = this.getScrollTop() + this.$config.offset;
        const scrollHeight = this.getScrollHeight();
        const maxScroll = this.$config.offset + scrollHeight - this.getOffsetHeight();

        if (this.$scrollHeight !== scrollHeight) {
            this.refresh();
        }

        if (scrollTop >= maxScroll) {
            const target = this.$targets[this.$targets.length - 1];
            if (this.$activeTarget !== target) {
                this.activate(target);
            }
            return;
        }

        if (this.$activeTarget && scrollTop < this.$offsets[0] && this.$offsets[0] > 0) {
            this.$activeTarget = null;
            this.clear();
            return;
        }

        for (let i = this.$offsets.length; i--;) {
            const isActiveTarget = this.$activeTarget !== this.$targets[i]
                && scrollTop >= this.$offsets[i]
                && (typeof this.$offsets[i + 1] === 'undefined' || scrollTop < this.$offsets[i + 1]);

            if (isActiveTarget) {
                this.activate(this.$targets[i]);
            }
        }
    }

    getScroller() {
        if (this.$scroller) {
            return this.$scroller;
        }
        let scroller = this.$config.element
        if (!scroller) {
            return null;
        } else if(isElement(scroller.$el)) {
            scroller = scroller.$el;
        } else if (typeof scroller === 'string') {
            scroller = select(scroller);
        }
        if (!scroller) {
            return null;
        }
        this.$scroller = scroller.tagName === 'BODY' ? window : scroller;
        return this.$scroller;
    }

    getScrollTop() {
        const scroller = this.getScroller();
        return scroller === window ? scroller.pageYOffset : scroller.scrollTop;
    }

    getScrollHeight() {
        return this.getScroller().scrollHeight || Math.max(
            document.body.scrollHeight,
            document.documentElement.scrollHeight
        );
    }

    getOffsetHeight() {
        const scroller = this.getScroller();
        return scroller === window ? window.innerHeight : getBCR(scroller).height;
    }

    activate(target) {
        this.$activeTarget = target;
        this.clear();

        // Grab the list of target links (<a href="{$target}">)
        const links = selectAll(
            this.$selector
            .split(',')
            .map(selector => `${selector}[href="${target}"]`)
            .join(',')
        , this.$el);

        links.forEach(link => {
            if (hasClass(link, ClassName.DROPDOWN_ITEM)) {
                // This is a dropdown item, so find the .dropdown-toggle and set it's state
                const dropdown = closest(Selector.DROPDOWN, link);
                if (dropdown) {
                    this.setActiveState(select(Selector.DROPDOWN_TOGGLE, dropdown), true);
                }
                // Also set this link's state
                this.setActiveState(link, true);
            } else {
                // Set triggered link as active
                this.setActiveState(link, true);
                if (matches(link.parentElement, Selector.NAV_ITEMS)) {
                    // Handle nav-link inside nav-item, and set nav-item active
                    this.setActiveState(link.parentElement, true);
                }
                // Set triggered links parents as active
                // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor
                let el = link;
                while(el) {
                    el = closest(Selector.NAV_LIST_GROUP, el);
                    const sibling = el ? el.previousElementSibling : null;
                    if (matches(sibling, `${Selector.NAV_LINKS}, ${Selector.LIST_ITEMS}`)) {
                        this.setActiveState(sibling, true);
                    }
                    // Handle special case where nav-link is inside a nav-item
                    if (matches(sibling, Selector.NAV_ITEMS)) {
                        this.setActiveState(select(Selector.NAV_LINKS, sibling), true);
                        // Add active state to nav-item as well
                        this.setActiveState(sibling, true);
                    }
                }
            }
        });

        // Signal event to via $root, passing ID of activaed target and reference to array of links
        if (links && links.length > 0 && this.$root) {
            this.$root.$emit(ACTIVATE_EVENT, target, links);
        }
    }

    clear() {
        selectAll(`${this.$selector}, ${Selector.NAV_ITEMS}`, this.$el)
        .filter(el => hasClass(el, ClassName.ACTIVE))
        .forEach(el => this.setActiveState(el, false));
    }

    setActiveState(el, active) {
        if (!el) {
            return;
        }
        if (active) {
            addClass(el, ClassName.ACTIVE);
        } else {
            removeClass(el, ClassName.ACTIVE);
        }
    }

}

export default ScrollSpy;
<commit_msg>fix(scrollspy): Trigger refresh on transitionend event<commit_after>/*
 * ScrollSpy class definition
 */

import { isArray } from '../utils/array';
import { assign, keys } from '../utils/object';
import { observeDom, warn } from '../utils';
import {
    isElement,
    isVisible,
    closest,
    matches,
    getBCR,
    offset,
    position,
    selectAll,
    select,
    hasClass,
    addClass,
    removeClass,
    getAttr,
    eventOn,
    eventOff
} from '../utils/dom';

/*
 * Constants / Defaults
 */

const NAME = 'v-b-scrollspy';
const ACTIVATE_EVENT = 'bv::scrollspy::activate';

const Default = {
    element: 'body',
    offset: 10,
    method: 'auto',
    throttle: 75
};

const DefaultType = {
    element: '(string|element|component)',
    offset: 'number',
    method: 'string',
    throttle: 'number'
};

const ClassName = {
    DROPDOWN_ITEM: 'dropdown-item',
    ACTIVE: 'active'
};

const Selector = {
    ACTIVE: '.active',
    NAV_LIST_GROUP: '.nav, .list-group',
    NAV_LINKS: '.nav-link',
    NAV_ITEMS: '.nav-item',
    LIST_ITEMS: '.list-group-item',
    DROPDOWN: '.dropdown, dropup',
    DROPDOWN_ITEMS: '.dropdown-item',
    DROPDOWN_TOGGLE: '.dropdown-toggle'
};

const OffsetMethod = {
    OFFSET: 'offset',
    POSITION: 'position'
};

// HREFs must start with # but can be === '#', or start with '#/' or '#!' (which can be router links)
const HREF_REGEX = /^#[^/!]+/;

// Transition Events
const TransitionEndEvents = [
    'webkitTransitionEnd',
    'transitionend',
    'otransitionend',
    'oTransitionEnd'
];

/*
 * Utility Methods
 */

// Better var type detection
function toType(obj) {
    return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase();
}

// Check config properties for expected types
function typeCheckConfig(componentName, config, configTypes) {
    for (const property in configTypes) {
        if (Object.prototype.hasOwnProperty.call(configTypes, property)) {
            const expectedTypes = configTypes[property];
            const value = config[property];
            let valueType = value && isElement(value) ? 'element' : toType(value);
            // handle Vue instances
            valueType = value && value._isVue ? 'component' : valueType;

            if (!new RegExp(expectedTypes).test(valueType)) {
                warn(
                    componentName + ': Option "' + property + '" provided type "' +
                    valueType + '", but expected type "' + expectedTypes + '"'
                );
            }
        }
    }
}

/*
 * ------------------------------------------------------------------------
 * Class Definition
 * ------------------------------------------------------------------------
 */

class ScrollSpy {

    constructor(element, config, $root) {
        // The element we activate links in
        this.$el = element;
        this.$scroller = null;
        this.$selector = [
            Selector.NAV_LINKS,
            Selector.LIST_ITEMS,
            Selector.DROPDOWN_ITEMS
        ].join(',');
        this.$offsets = [];
        this.$targets = [];
        this.$activeTarget = null;
        this.$scrollHeight = 0;
        this.$resizeTimeout = null;
        this.$obs_scroller = null;
        this.$obs_targets = null;
        this.$root = $root || null;
        this.$config = null;

        this.updateConfig(config);
    }

    static get Name() {
        return NAME;
    }

    static get Default() {
        return Default;
    }

    static get DefaultType() {
        return DefaultType;
    }

    updateConfig(config, $root) {
        if (this.$scroller) {
            // Just in case out scroll element has changed
            this.unlisten();
            this.$scroller = null;
        }
        const cfg = assign({}, this.constructor.Default, config);
        if ($root) {
            this.$root = $root;
        }
        typeCheckConfig(this.constructor.Name, cfg, this.constructor.DefaultType);
        this.$config = cfg;

        if (this.$root) {
            const self = this;
            this.$root.$nextTick(() => {
                self.listen();
            });
        } else {
            this.listen();
        }

    }

    dispose() {
        this.unlisten();
        clearTimeout(this.$resizeTimeout);
        this.$resizeTimeout = null;
        this.$el = null;
        this.$config = null;
        this.$scroller = null;
        this.$selector = null;
        this.$offsets = null;
        this.$targets = null;
        this.$activeTarget = null;
        this.$scrollHeight = null;
    }

    listen() {
        const scroller = this.getScroller();
        if (scroller && scroller.tagName !== 'BODY') {
            eventOn(scroller, 'scroll', this);
        }
        eventOn(window, 'scroll', this);
        eventOn(window, 'resize', this);
        eventOn(window, 'orientationchange', this);
        TransitionEndEvents.forEach(evtName => {
            eventOn(window, evtName, this);
        });
        this.setObservers(true);
        // Scedule a refresh
        this.handleEvent('refresh');
    }

    unlisten() {
        const scroller = this.getScroller();
        this.setObservers(false);
        if (scroller && scroller.tagName !== 'BODY') {
            eventOff(scroller, 'scroll', this);
        }
        eventOff(window, 'scroll', this);
        eventOff(window, 'resize', this);
        eventOff(window, 'orientationchange', this);
        TransitionEndEvents.forEach(evtName => {
            eventOffindow, evtName, this);
        });
    }

    setObservers(on) {
        // We observe both the scroller for content changes, and the target links
        if (this.$obs_scroller) {
            this.$obs_scroller.disconnect();
            this.$obs_scroller = null;
        }
        if (this.$obs_targets) {
            this.$obs_targets.disconnect();
            this.$obs_targets = null;
        }
        if (on) {
            this.$obs_targets = observeDom(this.$el, () => {this.handleEvent('mutation')}, {
                subtree: true,
                childList: true,
                attributes: true,
                attributeFilter: ['href']
            });
            this.$obs_scroller = observeDom(this.getScroller(), () => {this.handleEvent('mutation')}, {
                subtree: true,
                childList: true,
                characterData: true,
                attributes: true,
                attributeFilter: ['id', 'style', 'class']
            });
        }
    }

    // general event handler
    handleEvent(evt) {
        const type = typeof evt === 'string' ? evt : evt.type;

        const self = this;
        function resizeThrottle() {
            if(!self.$resizeTimeout) {
                self.$resizeTimeout = setTimeout(() => {
                    self.refresh();
                    self.process();
                    self.$resizeTimeout = null;
                }, self.$config.throttle);
            }
        }

        if (type === 'scroll') {
            if (!this.$obs_scroller) {
                // Just in case we are added to the DOM before the scroll target is
                // We re-instantiate our listeners, just in case
                this.listen();
            }
            this.process();
        } else if (/(resize|orientationchange|mutation|refresh)/.test(type)) {
            // Postpone these events by throttle time
            resizeThrottle();
        }
    }

    // Refresh the list of target links on the element we are applied to
    refresh() {
        const scroller = this.getScroller();
        if (!scroller) {
            return;
        }
        const autoMethod = scroller !== scroller.window ? OffsetMethod.POSITION : OffsetMethod.OFFSET;
        const method = this.$config.method === 'auto' ? autoMethod : this.$config.method;
        const methodFn = method === OffsetMethod.POSITION ? position : offset;
        const offsetBase = method === OffsetMethod.POSITION ? this.getScrollTop() : 0;

        this.$offsets = [];
        this.$targets = [];

        this.$scrollHeight = this.getScrollHeight();

        // Find all the unique link href's
        selectAll(this.$selector, this.$el)
        .map(link => getAttr(link, 'href'))
        .filter(href => HREF_REGEX.test(href|| ''))
        .map(href => {
            const el = select(href, scroller);
            if (isVisible(el)) {
                return {
                    offset: parseInt(methodFn(el).top, 10) + offsetBase,
                    target: href
                }
            }
            return null;
        })
        .filter(item => item)
        .sort((a, b) => a.offset - b.offset)
        .reduce((memo, item) => {
            // record only unique targets/offfsets
            if (!memo[item.target]) {
                this.$offsets.push(item.offset);
                this.$targets.push(item.target);
                memo[item.target] = true;
            }
            return memo;
        }, {});

        return this;
    }

    // Handle activating/clearing
    process() {
        const scrollTop = this.getScrollTop() + this.$config.offset;
        const scrollHeight = this.getScrollHeight();
        const maxScroll = this.$config.offset + scrollHeight - this.getOffsetHeight();

        if (this.$scrollHeight !== scrollHeight) {
            this.refresh();
        }

        if (scrollTop >= maxScroll) {
            const target = this.$targets[this.$targets.length - 1];
            if (this.$activeTarget !== target) {
                this.activate(target);
            }
            return;
        }

        if (this.$activeTarget && scrollTop < this.$offsets[0] && this.$offsets[0] > 0) {
            this.$activeTarget = null;
            this.clear();
            return;
        }

        for (let i = this.$offsets.length; i--;) {
            const isActiveTarget = this.$activeTarget !== this.$targets[i]
                && scrollTop >= this.$offsets[i]
                && (typeof this.$offsets[i + 1] === 'undefined' || scrollTop < this.$offsets[i + 1]);

            if (isActiveTarget) {
                this.activate(this.$targets[i]);
            }
        }
    }

    getScroller() {
        if (this.$scroller) {
            return this.$scroller;
        }
        let scroller = this.$config.element
        if (!scroller) {
            return null;
        } else if(isElement(scroller.$el)) {
            scroller = scroller.$el;
        } else if (typeof scroller === 'string') {
            scroller = select(scroller);
        }
        if (!scroller) {
            return null;
        }
        this.$scroller = scroller.tagName === 'BODY' ? window : scroller;
        return this.$scroller;
    }

    getScrollTop() {
        const scroller = this.getScroller();
        return scroller === window ? scroller.pageYOffset : scroller.scrollTop;
    }

    getScrollHeight() {
        return this.getScroller().scrollHeight || Math.max(
            document.body.scrollHeight,
            document.documentElement.scrollHeight
        );
    }

    getOffsetHeight() {
        const scroller = this.getScroller();
        return scroller === window ? window.innerHeight : getBCR(scroller).height;
    }

    activate(target) {
        this.$activeTarget = target;
        this.clear();

        // Grab the list of target links (<a href="{$target}">)
        const links = selectAll(
            this.$selector
            .split(',')
            .map(selector => `${selector}[href="${target}"]`)
            .join(',')
        , this.$el);

        links.forEach(link => {
            if (hasClass(link, ClassName.DROPDOWN_ITEM)) {
                // This is a dropdown item, so find the .dropdown-toggle and set it's state
                const dropdown = closest(Selector.DROPDOWN, link);
                if (dropdown) {
                    this.setActiveState(select(Selector.DROPDOWN_TOGGLE, dropdown), true);
                }
                // Also set this link's state
                this.setActiveState(link, true);
            } else {
                // Set triggered link as active
                this.setActiveState(link, true);
                if (matches(link.parentElement, Selector.NAV_ITEMS)) {
                    // Handle nav-link inside nav-item, and set nav-item active
                    this.setActiveState(link.parentElement, true);
                }
                // Set triggered links parents as active
                // With both <ul> and <nav> markup a parent is the previous sibling of any nav ancestor
                let el = link;
                while(el) {
                    el = closest(Selector.NAV_LIST_GROUP, el);
                    const sibling = el ? el.previousElementSibling : null;
                    if (matches(sibling, `${Selector.NAV_LINKS}, ${Selector.LIST_ITEMS}`)) {
                        this.setActiveState(sibling, true);
                    }
                    // Handle special case where nav-link is inside a nav-item
                    if (matches(sibling, Selector.NAV_ITEMS)) {
                        this.setActiveState(select(Selector.NAV_LINKS, sibling), true);
                        // Add active state to nav-item as well
                        this.setActiveState(sibling, true);
                    }
                }
            }
        });

        // Signal event to via $root, passing ID of activaed target and reference to array of links
        if (links && links.length > 0 && this.$root) {
            this.$root.$emit(ACTIVATE_EVENT, target, links);
        }
    }

    clear() {
        selectAll(`${this.$selector}, ${Selector.NAV_ITEMS}`, this.$el)
        .filter(el => hasClass(el, ClassName.ACTIVE))
        .forEach(el => this.setActiveState(el, false));
    }

    setActiveState(el, active) {
        if (!el) {
            return;
        }
        if (active) {
            addClass(el, ClassName.ACTIVE);
        } else {
            removeClass(el, ClassName.ACTIVE);
        }
    }

}

export default ScrollSpy;
<|endoftext|>


<commit_before>// > The deploy post route.
// > Removes previous deploy if it exists.
// > Accepts a tarball, unpacks it and puts it in the right place.
//
// > Copyright  ©  2013 Matt Styles
// > Licensed under the MIT license

'use strict';

// Includes
var icarus = require( '../../icarus' ),
    fs = require( 'fs' ),
    path = require( 'path' ),
    util = require( 'util' ),
    exec = require( 'child_process' ).exec,
    tar = require( 'tar' ),
    fstream = require( 'fstream' ),
    zlib = require( 'zlib' ),
    async = require( 'async' ),
    rimraf = require( 'rimraf' );

// Export the test route
module.exports = function( req, res ) {
    // Store the archive folder.

    // @todo should be in the config.
    var archiveFolder = '.icarus/archive/';

    // Get some info about the transfer
    var reqData = {
        start: new Date(),
        projectName: req.query.pkgName,
        tmpName: req.query.tmpName.match( /[^\/]+$/ )[0]
    };
    reqData.tmpPath = path.join( icarus.utils.getHome(), archiveFolder, reqData.tmpName );

    // Create the deploy path
    var deployPath = path.join( icarus.utils.getHome(), icarus.config.get( 'dir.main' ), icarus.config.get( 'dir.deploy' ), reqData.projectName );

    // Prep Step
    // ---

    // Set up the socket
    if ( icarus.socket ) {
        icarus.log.info( 'Connected via socket'.grey );
    }

    // Remove a pre-existing deploy of the project
    var removeProject = function( done ) {
        fs.exists( deployPath, function( exists ) {
            // If a previous deploy exists then remove it
            if ( exists ) {
                icarus.log.info( 'Removing previous deploy of '.grey + reqData.projectName.yellow.bold );

                // Remove the previous project directory
                rimraf( deployPath, function( err ) {
                    if ( err ) {
                        icarus.log.info( 'An error occurred removing a previous deploy : '.red + err );
                        return;
                    }

                    icarus.log.info( 'Previous deploy '.grey + 'removed'.yellow.bold );
                    done();
                });
            } else {
                // If no previous deploy then log it
                icarus.log.info( 'No previous deploy to remove'.grey );

                // Inform async we are finished
                done();
            }
        });
    };

    // Build Step
    // ---

    // Call exec to do the heavy lifting of an npm install
    var npmInstall = function( cb ) {
        var outputStr = "";

        // Inform the client the build has started
        icarus.socket.emit( 'build', { data: 'Starting npm install' } );

        // Create the child process
        var child = exec( 'npm install', { cwd: deployPath }, function( err, stdout, stderr ) {
            // Handle an error
            if ( err ) {
                icarus.log.error( 'Error occurred building project : ' + err );
            }

            icarus.log.info( 'Project Built'.grey );
            icarus.socket.emit( 'deployed', { data: 'passing data example' } );

            // Call the callback now that the child is finished
            cb();
        });

        // Handle npm output
        var handleOutput = function( chunk ) {
            // Print to stdout
            util.print( chunk );

            // If the chunk contains an endline character then add it to the output string and send to the socket
            if ( chunk.match( /\n$/ ) ) {
                outputStr = outputStr + chunk;
                icarus.socket.emit( 'buildstep', { data: outputStr } );
                outputStr = "";
            } else {
                // Otherwise just add the chunk to the output string
                outputStr = outputStr + chunk;
            }
        };

        // Handle output from the child to stdout
        child.stdout.on( 'data', handleOutput );

        // Npm uses stderr for the bulk of the output
        child.stderr.on( 'data', handleOutput );
    }

    // For now just manually trigger an npm install for when deploying from a built folder
    // (denoted by `/prod/` or `/dist/`)
    var buildProject = function( cb ) {
        // See if we need to build this deploy
        if ( req.query.build ) {
            icarus.log.info( 'Attempting to build the project'.grey );

            // Start a child process to handle the npm install
            npmInstall( cb );
        } else {
            // Otherwise just call the callback as we are done
            cb();
        }
    };

    // Deploy Step
    // ---
    icarus.log.info( 'Attempting to deploy '.grey + reqData.projectName.yellow.bold + ' tarball into '.grey + deployPath.yellow.bold );

    // Do some maintenance work before piping the request object into the deploy folder
    async.series([
        removeProject
    ], function( err, result ) {
        if ( err ) {
            icarus.log.error( '\nError deploying ' + reqData.projectName + ' : ' + err );
        }

        // Extract the request into the correct directory
        req
            .pipe( zlib.Unzip() )
            .pipe( tar.Extract( {
                path: deployPath,
                strip: true
            }))
                .on( 'error', function( err ) {
                    icarus.log.info( '\nDeployment'.grey + ' unsuccessful'.red.bold );
                    icarus.log.debug( 'The following error occurred while trying to deploy ' + reqData.projectName );
                    icarus.log.debug( err );
                    res.send( { "status": "something went horribly, horribly wrong" } );
                    return;
                })
                .on( 'end', function () {
                    // Do a basic speed check and record it
                    var endTime = new Date();
                    icarus.log.info( 'Time taken to deploy: '.grey + ( icarus.utils.timeFormat( endTime - reqData.start ) ).toString().yellow.bold );

                    // Perform the build step now the deployment is in the right place
                    buildProject( function() {
                        icarus.log.info( 'Deployment'.grey + (reqData.projectName ? ' of '.grey + reqData.projectName.yellow.bold  : '') + ' ...'.grey + 'successful'.green.bold  );
                        res.send( { "status": "deployed" } );
                    } );
                });

        // Write the tarball into an archive directory
        req
            .pipe( fstream.Writer( path.join( icarus.utils.getHome(), archiveFolder, reqData.tmpName ) ) )
            .on( 'ready', function() {
                icarus.log.info( 'Writing archive to '.grey + reqData.tmpPath.yellow.bold );
            })
            .on ('error', function( err ) {
                icarus.log.error( 'Error storing archive : ' + err );
            })
            .on( 'close', function() {
                icarus.log.info( 'finished writing to archive'.grey );
            });

    });

};<commit_msg>Build success or fail feedback<commit_after>// > The deploy post route.
// > Removes previous deploy if it exists.
// > Accepts a tarball, unpacks it and puts it in the right place.
//
// > Copyright  ©  2013 Matt Styles
// > Licensed under the MIT license

'use strict';

// Includes
var icarus = require( '../../icarus' ),
    fs = require( 'fs' ),
    path = require( 'path' ),
    util = require( 'util' ),
    exec = require( 'child_process' ).exec,
    tar = require( 'tar' ),
    fstream = require( 'fstream' ),
    zlib = require( 'zlib' ),
    async = require( 'async' ),
    rimraf = require( 'rimraf' );

// Export the test route
module.exports = function( req, res ) {
    // Store the archive folder.

    // @todo should be in the config.
    var archiveFolder = '.icarus/archive/';

    // Get some info about the transfer
    var reqData = {
        start: new Date(),
        projectName: req.query.pkgName,
        tmpName: req.query.tmpName.match( /[^\/]+$/ )[0]
    };
    reqData.tmpPath = path.join( icarus.utils.getHome(), archiveFolder, reqData.tmpName );

    // Create the deploy path
    var deployPath = path.join( icarus.utils.getHome(), icarus.config.get( 'dir.main' ), icarus.config.get( 'dir.deploy' ), reqData.projectName );

    // Prep Step
    // ---

    // Set up the socket
    if ( icarus.socket ) {
        icarus.log.info( 'Connected via socket'.grey );
    }

    // Remove a pre-existing deploy of the project
    var removeProject = function( done ) {
        fs.exists( deployPath, function( exists ) {
            // If a previous deploy exists then remove it
            if ( exists ) {
                icarus.log.info( 'Removing previous deploy of '.grey + reqData.projectName.yellow.bold );

                // Remove the previous project directory
                rimraf( deployPath, function( err ) {
                    if ( err ) {
                        icarus.log.info( 'An error occurred removing a previous deploy : '.red + err );
                        return;
                    }

                    icarus.log.info( 'Previous deploy '.grey + 'removed'.yellow.bold );
                    done();
                });
            } else {
                // If no previous deploy then log it
                icarus.log.info( 'No previous deploy to remove'.grey );

                // Inform async we are finished
                done();
            }
        });
    };

    // Build Step
    // ---

    // Call exec to do the heavy lifting of an npm install
    var npmInstall = function( cb ) {
        var outputStr = "";

        // Inform the client the build has started
        icarus.socket.emit( 'build', { data: 'Starting npm install' } );

        // Create the child process
        var child = exec( 'npm install', { cwd: deployPath }, function( err, stdout, stderr ) {
            // Handle an error
            if ( err ) {
                icarus.log.error( 'Error occurred building project : ' + err );
                icarus.socket.emit( 'build', { data: 'npm install ' + 'failure'.red } );
            }

            icarus.log.info( 'Project Built'.grey );
            icarus.socket.emit( 'build', { data: 'npm install ' + 'successful'.green } );

            // Call the callback now that the child is finished
            cb();
        });

        // Handle npm output
        var handleOutput = function( chunk ) {
            // Print to stdout
            util.print( chunk );

            // If the chunk contains an endline character then add it to the output string and send to the socket
            if ( chunk.match( /\n$/ ) ) {
                outputStr = outputStr + chunk;
                icarus.socket.emit( 'buildstep', { data: outputStr } );
                outputStr = "";
            } else {
                // Otherwise just add the chunk to the output string
                outputStr = outputStr + chunk;
            }
        };

        // Handle output from the child to stdout
        child.stdout.on( 'data', handleOutput );

        // Npm uses stderr for the bulk of the output
        child.stderr.on( 'data', handleOutput );
    }

    // For now just manually trigger an npm install for when deploying from a built folder
    // (denoted by `/prod/` or `/dist/`)
    var buildProject = function( cb ) {
        // See if we need to build this deploy
        if ( req.query.build ) {
            icarus.log.info( 'Attempting to build the project'.grey );

            // Start a child process to handle the npm install
            npmInstall( cb );
        } else {
            // Otherwise just call the callback as we are done
            cb();
        }
    };

    // Deploy Step
    // ---
    icarus.log.info( 'Attempting to deploy '.grey + reqData.projectName.yellow.bold + ' tarball into '.grey + deployPath.yellow.bold );

    // Do some maintenance work before piping the request object into the deploy folder
    async.series([
        removeProject
    ], function( err, result ) {
        if ( err ) {
            icarus.log.error( '\nError deploying ' + reqData.projectName + ' : ' + err );
        }

        // Extract the request into the correct directory
        req
            .pipe( zlib.Unzip() )
            .pipe( tar.Extract( {
                path: deployPath,
                strip: true
            }))
                .on( 'error', function( err ) {
                    icarus.log.info( '\nDeployment'.grey + ' unsuccessful'.red.bold );
                    icarus.log.debug( 'The following error occurred while trying to deploy ' + reqData.projectName );
                    icarus.log.debug( err );
                    res.send( { "status": "something went horribly, horribly wrong" } );
                    return;
                })
                .on( 'end', function () {
                    // Do a basic speed check and record it
                    var endTime = new Date();
                    icarus.log.info( 'Time taken to deploy: '.grey + ( icarus.utils.timeFormat( endTime - reqData.start ) ).toString().yellow.bold );

                    // Perform the build step now the deployment is in the right place
                    buildProject( function() {
                        icarus.log.info( 'Deployment'.grey + (reqData.projectName ? ' of '.grey + reqData.projectName.yellow.bold  : '') + ' ...'.grey + 'successful'.green.bold  );
                        res.send( { "status": "deployed" } );
                    } );
                });

        // Write the tarball into an archive directory
        req
            .pipe( fstream.Writer( path.join( icarus.utils.getHome(), archiveFolder, reqData.tmpName ) ) )
            .on( 'ready', function() {
                icarus.log.info( 'Writing archive to '.grey + reqData.tmpPath.yellow.bold );
            })
            .on ('error', function( err ) {
                icarus.log.error( 'Error storing archive : ' + err );
            })
            .on( 'close', function() {
                icarus.log.info( 'finished writing to archive'.grey );
            });

    });

};<|endoftext|>


<commit_before>/**
 * Это внутренний класс, который не должен использоваться приложением.
 * @classdesc Box - это тип View, который умеет выбирать какие View показывать.
 * @param {string} id
 * @param {object} params
 * @constructor
 * @private
 */
ns.Box = function(id, params) {
    this.id = id;
    this.params = params;

    this.views = {};

    // у бокса нет декларации и ключ строить не надо
    this.key = ns.Box.getKey(id);

    this.node = null;
    this.active = {};

    this._visible = false;
};

ns.Box.prototype._getCommonTree = ns.View.prototype._getCommonTree;

/**
 * Возвращает ключ вида
 * @param {string} id Название вида
 * @param {object} params Параметры для ключа
 * @param {ns.L.TYPE} type Тип
 * @returns {string}
 * @private
 */
ns.Box.prototype._getViewKey = function(id, params, type) {
    var key;
    if (type === ns.L.BOX) {
        key = ns.Box.getKey(id);
    } else {
        key = ns.View.getKey(id, params);
    }

    return key;
};

/**
 *
 * @param {string} id
 * @param {object} params
 * @param {string} type Тип вида.
 * @returns {ns.View}
 * @private
 */
ns.Box.prototype._getView = function(id, params, type) {
    var key = this._getViewKey(id, params, type);
    return this.views[key];
};

/**
 *
 * @param {string} id
 * @param {object} params
 * @param {ns.L} type
 * @returns {ns.View}
 * @private
 */
ns.Box.prototype._addView = function(id, params, type) {
    var view = this._getView(id, params, type);
    if (!view) {
        if (type === ns.L.BOX) {
            view = new ns.Box(id, params);
        } else {
            view = ns.View.create(id, params, type === ns.L.ASYNC);
        }
        this.views[view.key] = view;
    }
    return view;
};

/**
 *
 * @param {array} descs
 * @returns {array}
 * @private
 */
ns.Box.prototype._getDescendants = function(descs) {
    var views = this.views;
    var active = this.active;

    descs.push(this);

    for (var id in active) {
        var view = views[ active[id] ];
        descs.push(view);
        view._getDescendants(descs);
    }

    return descs;
};

/**
 * Ищем все новые блоки и блоки, требующие перерисовки.
 * @param {object} updated
 * @param {object} layout
 * @param {object} params
 * @private
 */
ns.Box.prototype._getRequestViews = function(updated, layout, params) {
    for (var id in layout) {
        //  Согласно новому layout'у здесь должен быть view с id/params.
        //  Создаем его (если он уже есть, он возьмется из this.views).
        var view = this._addView(id, params, layout[id].type);
        //  Идем вниз рекурсивно.
        view._getRequestViews(updated, layout[id].views, params);
    }
};

/**
 * Боксы всегда валидные, т.е. не toplevel, поэтому просто идем вниз по дереву.
 * @param {object} tree
 * @param {object} layout
 * @param {object} params
 * @private
 */
ns.Box.prototype._getUpdateTree = function(tree, layout, params) {
    if ( this.isNone() ) {
        tree.views[this.id] = this._getViewTree(layout, params);

    } else {
        var views = this.views;
        for (var id in layout) {
            var selfLayout = layout[id];
            var key = this._getViewKey(id, params, selfLayout.type);
            views[key]._getUpdateTree(tree, selfLayout.views, params);
        }
    }
};

/**
 * Строим дерево блоков.
 * @param {object} layout
 * @param {object} params
 * @returns {object}
 * @private
 */
ns.Box.prototype._getViewTree = function(layout, params) {
    var tree = this._getCommonTree();
    tree.box = true;

    var views = this.views;
    for (var id in layout) {
        var selfLayout = layout[id];
        var key = this._getViewKey(id, params, selfLayout.type);
        tree.views[id] = views[key]._getViewTree(selfLayout.views, params);
    }

    return tree;
};

/**
 * Обновляем бокс.
 * @param {HTMLElement} node
 * @param {object} layout
 * @param {object} params
 * @param {object} options
 * @param {object} events
 * @private
 */
ns.Box.prototype._updateHTML = function(node, layout, params, options, events) {
    var oldNode;
    // Если
    //  - старой ноды не было
    //  - или этот box не toplevel (т.е. родительский view обновил свою ноду)
    if (!this.node || !options.toplevel) {
        // Ищем новую ноду бокса.
        var newNode = ns.byClass('ns-view-' + this.id, node)[0];
        // И если есть
        if (newNode) {
            // Сохраним ссылку на старую ноду
            oldNode = this.node;
            // Обновим ноду бокса
            this.node = newNode;
        }
    }

    if (!this.node) {
        throw new Error("[ns.Box] Can't find node for '" + this.id + "'");
    }

    var views = this.views;

    //  this.active -- это объект (упорядоченный!), в котором лежат ключи
    //  активных (видимых) в данный момент блоков.
    var layoutActive = {};

    //  Строим новый active согласно layout'у.
    //  Т.е. это тот набор блоков, которые должны быть видимы в боксе после окончания всего апдейта
    //  (включая синхронную и все асинхронные подапдейты).
    for (var id in layout) {
        var selfLayout = layout[id];
        var key = this._getViewKey(id, params, selfLayout.type);
        layoutActive[id] = key;

        //  Достаем ранее созданный блок (в _getRequestViews).
        /** @type {ns.View} */
        var view = views[key];

        var viewWasNone = view.isNone();

        //  Обновляем его.
        view._updateHTML(node, selfLayout.views, params, options, events);

        // вставляем view в DOM, только если ноды раньше не было (view.status === NONE)
        // если нода была, то view._updateHTML() сделаем сам все свои обновления
        // NOTE: с такой логикой порядок view никак не гарантируется! Возможно это проблема...
        if (viewWasNone && ( view.isOk() || view.isLoading() ) ) {
            this.node.appendChild(view.node);
        }
    }

    //  Строим новый active, но уже не по layout'у, а по актуальным блокам.
    var newActive = {};
    for (var activeId in layoutActive) {
        var activeKey = layoutActive[activeId];
        newActive[activeId] = activeKey;
    }

    // Пройдёмся по всем вложенным видам, чтобы
    //  1. Спрятать виды, которые не попали в layout
    //  2. Перенести ноды вложенных видов в новую ноду бокса (если есть)
    for (var key in this.views) {
        var view = this.views[key];
        // Если вид не входит в новый active
        if (newActive[view.id] !== view.key) {
            // Скроем виды, не попавшие в layout
            var descs = view._getDescendants( [] );
            for (var i = 0, l = descs.length; i < l; i++) {
                // если view был скрыт
                if (descs[i]._hide()) {
                    events['ns-view-hide'].push(descs[i]);
                }
            }
            // Если нода вида лежит в старой ноде бокса
            if (oldNode && oldNode.contains(view.node)) {
                // Перенесём её в новую ноду бокса
                this.node.appendChild(view.node);
            }
        }
    }

    //  Запоминаем новый active.
    this.active = newActive;

    this._show();
};

/**
 *
 * @returns {boolean}
 * @private
 */
ns.Box.prototype._show = function() {
    if (this._visible === false) {
        this._showNode();
        this._visible = true;
        // always returns false to prevent events trigger
    }

    return false;
};

/**
 * Скрывает view
 * @returns {Boolean}
 * @protected
 */
ns.Box.prototype._hide = function() {
    if (this._visible === true) {
        this._hideNode();
        this._visible = false;
        // always returns false to prevent events trigger
    }

    return false;
};

/**
 * @private
 */
ns.Box.prototype._hideNode = function() {
    this.node.className = this.node.className.replace(' ns-view-visible', '') + ' ns-view-hidden';
};

/**
 *
 * @private
 */
ns.Box.prototype._showNode = function() {
    this.node.className = this.node.className.replace(' ns-view-hidden', '') + ' ns-view-visible';
};

ns.Box.prototype.isOk = no.true;
ns.Box.prototype.isLoading = no.false;

/**
 * Returns true if box has status NONE
 * @returns {boolean}
 */
ns.Box.prototype.isNone = function() {
    return !this.node;
};

/**
 * Создает ключ для ns.Box.
 * @param {string} id Название ns.Box.
 * @returns {string}
 * @static
 */
ns.Box.getKey = function(id) {
    return 'box=' + id;
};
<commit_msg>ns.Box дублирует виды в _getDescendants<commit_after>/**
 * Это внутренний класс, который не должен использоваться приложением.
 * @classdesc Box - это тип View, который умеет выбирать какие View показывать.
 * @param {string} id
 * @param {object} params
 * @constructor
 * @private
 */
ns.Box = function(id, params) {
    this.id = id;
    this.params = params;

    this.views = {};

    // у бокса нет декларации и ключ строить не надо
    this.key = ns.Box.getKey(id);

    this.node = null;
    this.active = {};

    this._visible = false;
};

ns.Box.prototype._getCommonTree = ns.View.prototype._getCommonTree;

/**
 * Возвращает ключ вида
 * @param {string} id Название вида
 * @param {object} params Параметры для ключа
 * @param {ns.L.TYPE} type Тип
 * @returns {string}
 * @private
 */
ns.Box.prototype._getViewKey = function(id, params, type) {
    var key;
    if (type === ns.L.BOX) {
        key = ns.Box.getKey(id);
    } else {
        key = ns.View.getKey(id, params);
    }

    return key;
};

/**
 *
 * @param {string} id
 * @param {object} params
 * @param {string} type Тип вида.
 * @returns {ns.View}
 * @private
 */
ns.Box.prototype._getView = function(id, params, type) {
    var key = this._getViewKey(id, params, type);
    return this.views[key];
};

/**
 *
 * @param {string} id
 * @param {object} params
 * @param {ns.L} type
 * @returns {ns.View}
 * @private
 */
ns.Box.prototype._addView = function(id, params, type) {
    var view = this._getView(id, params, type);
    if (!view) {
        if (type === ns.L.BOX) {
            view = new ns.Box(id, params);
        } else {
            view = ns.View.create(id, params, type === ns.L.ASYNC);
        }
        this.views[view.key] = view;
    }
    return view;
};

/**
 *
 * @param {array} descs
 * @returns {array}
 * @private
 */
ns.Box.prototype._getDescendants = function(descs) {
    var views = this.views;
    var active = this.active;

    descs.push(this);

    for (var id in active) {
        var view = views[ active[id] ];
        view._getDescendants(descs);
    }

    return descs;
};

/**
 * Ищем все новые блоки и блоки, требующие перерисовки.
 * @param {object} updated
 * @param {object} layout
 * @param {object} params
 * @private
 */
ns.Box.prototype._getRequestViews = function(updated, layout, params) {
    for (var id in layout) {
        //  Согласно новому layout'у здесь должен быть view с id/params.
        //  Создаем его (если он уже есть, он возьмется из this.views).
        var view = this._addView(id, params, layout[id].type);
        //  Идем вниз рекурсивно.
        view._getRequestViews(updated, layout[id].views, params);
    }
};

/**
 * Боксы всегда валидные, т.е. не toplevel, поэтому просто идем вниз по дереву.
 * @param {object} tree
 * @param {object} layout
 * @param {object} params
 * @private
 */
ns.Box.prototype._getUpdateTree = function(tree, layout, params) {
    if ( this.isNone() ) {
        tree.views[this.id] = this._getViewTree(layout, params);

    } else {
        var views = this.views;
        for (var id in layout) {
            var selfLayout = layout[id];
            var key = this._getViewKey(id, params, selfLayout.type);
            views[key]._getUpdateTree(tree, selfLayout.views, params);
        }
    }
};

/**
 * Строим дерево блоков.
 * @param {object} layout
 * @param {object} params
 * @returns {object}
 * @private
 */
ns.Box.prototype._getViewTree = function(layout, params) {
    var tree = this._getCommonTree();
    tree.box = true;

    var views = this.views;
    for (var id in layout) {
        var selfLayout = layout[id];
        var key = this._getViewKey(id, params, selfLayout.type);
        tree.views[id] = views[key]._getViewTree(selfLayout.views, params);
    }

    return tree;
};

/**
 * Обновляем бокс.
 * @param {HTMLElement} node
 * @param {object} layout
 * @param {object} params
 * @param {object} options
 * @param {object} events
 * @private
 */
ns.Box.prototype._updateHTML = function(node, layout, params, options, events) {
    var oldNode;
    // Если
    //  - старой ноды не было
    //  - или этот box не toplevel (т.е. родительский view обновил свою ноду)
    if (!this.node || !options.toplevel) {
        // Ищем новую ноду бокса.
        var newNode = ns.byClass('ns-view-' + this.id, node)[0];
        // И если есть
        if (newNode) {
            // Сохраним ссылку на старую ноду
            oldNode = this.node;
            // Обновим ноду бокса
            this.node = newNode;
        }
    }

    if (!this.node) {
        throw new Error("[ns.Box] Can't find node for '" + this.id + "'");
    }

    var views = this.views;

    //  this.active -- это объект (упорядоченный!), в котором лежат ключи
    //  активных (видимых) в данный момент блоков.
    var layoutActive = {};

    //  Строим новый active согласно layout'у.
    //  Т.е. это тот набор блоков, которые должны быть видимы в боксе после окончания всего апдейта
    //  (включая синхронную и все асинхронные подапдейты).
    for (var id in layout) {
        var selfLayout = layout[id];
        var key = this._getViewKey(id, params, selfLayout.type);
        layoutActive[id] = key;

        //  Достаем ранее созданный блок (в _getRequestViews).
        /** @type {ns.View} */
        var view = views[key];

        var viewWasNone = view.isNone();

        //  Обновляем его.
        view._updateHTML(node, selfLayout.views, params, options, events);

        // вставляем view в DOM, только если ноды раньше не было (view.status === NONE)
        // если нода была, то view._updateHTML() сделаем сам все свои обновления
        // NOTE: с такой логикой порядок view никак не гарантируется! Возможно это проблема...
        if (viewWasNone && ( view.isOk() || view.isLoading() ) ) {
            this.node.appendChild(view.node);
        }
    }

    //  Строим новый active, но уже не по layout'у, а по актуальным блокам.
    var newActive = {};
    for (var activeId in layoutActive) {
        var activeKey = layoutActive[activeId];
        newActive[activeId] = activeKey;
    }

    // Пройдёмся по всем вложенным видам, чтобы
    //  1. Спрятать виды, которые не попали в layout
    //  2. Перенести ноды вложенных видов в новую ноду бокса (если есть)
    for (var key in this.views) {
        var view = this.views[key];
        // Если вид не входит в новый active
        if (newActive[view.id] !== view.key) {
            // Скроем виды, не попавшие в layout
            var descs = view._getDescendants( [] );
            for (var i = 0, l = descs.length; i < l; i++) {
                // если view был скрыт
                if (descs[i]._hide()) {
                    events['ns-view-hide'].push(descs[i]);
                }
            }
            // Если нода вида лежит в старой ноде бокса
            if (oldNode && oldNode.contains(view.node)) {
                // Перенесём её в новую ноду бокса
                this.node.appendChild(view.node);
            }
        }
    }

    //  Запоминаем новый active.
    this.active = newActive;

    this._show();
};

/**
 *
 * @returns {boolean}
 * @private
 */
ns.Box.prototype._show = function() {
    if (this._visible === false) {
        this._showNode();
        this._visible = true;
        // always returns false to prevent events trigger
    }

    return false;
};

/**
 * Скрывает view
 * @returns {Boolean}
 * @protected
 */
ns.Box.prototype._hide = function() {
    if (this._visible === true) {
        this._hideNode();
        this._visible = false;
        // always returns false to prevent events trigger
    }

    return false;
};

/**
 * @private
 */
ns.Box.prototype._hideNode = function() {
    this.node.className = this.node.className.replace(' ns-view-visible', '') + ' ns-view-hidden';
};

/**
 *
 * @private
 */
ns.Box.prototype._showNode = function() {
    this.node.className = this.node.className.replace(' ns-view-hidden', '') + ' ns-view-visible';
};

ns.Box.prototype.isOk = no.true;
ns.Box.prototype.isLoading = no.false;

/**
 * Returns true if box has status NONE
 * @returns {boolean}
 */
ns.Box.prototype.isNone = function() {
    return !this.node;
};

/**
 * Создает ключ для ns.Box.
 * @param {string} id Название ns.Box.
 * @returns {string}
 * @static
 */
ns.Box.getKey = function(id) {
    return 'box=' + id;
};
<|endoftext|>


<commit_before>'use strict'

var Kitsu = require('../api')

async function getUserByUsername(username) {
  let response = await Kitsu.findAll('user', {
    filter: { name: username }
  })
  if (response.meta.count === 0) {
    throw new Error("No user exists");
  }
  let user = response[0]

  return user
}

async function getCurrentLibraryEntries(userId) {
  let response = await Kitsu.findAll('libraryEntry', {
    filter: { user_id: userId, status: 'current' },
    page: { limit: 9999 },
    include: 'anime'
  })

  if (response.meta.count === 0) {
    throw new Error("No library entries for user");
  }

  return response;
}

async function getCompletedLibraryEntries(userId) {
  let response = await Kitsu.findAll('libraryEntry', {
    filter: { user_id: userId, status: 'completed' },
    page: { limit: 9999 },
    include: 'anime'
  })

  if (response.meta.count === 0) {
    throw new Error("No library entries for user");
  }

  return response;
}

async function getAllLibraryEntries(userId) {
  let response = await Kitsu.findAll('libraryEntry', {
    filter: { user_id: userId },
    page: { limit: 9999 },
    include: 'anime'
  })

  if (response.meta.count === 0) {
    throw new Error("No library entries for user");
  }

  return response;
}

module.exports = {
  getUserByUsername,
  getCurrentLibraryEntries,
  getCompletedLibraryEntries,
  getAllLibraryEntries
}
<commit_msg>end-points for on-hold and dropped anime..<commit_after>'use strict'

var Kitsu = require('../api')

async function getUserByUsername(username) {
  let response = await Kitsu.findAll('user', {
    filter: { name: username }
  })
  if (response.meta.count === 0) {
    throw new Error("No user exists");
  }
  let user = response[0]

  return user
}

async function getCurrentLibraryEntries(userId) {
  let response = await Kitsu.findAll('libraryEntry', {
    filter: { user_id: userId, status: 'current' },
    page: { limit: 9999 },
    include: 'anime'
  })

  if (response.meta.count === 0) {
    throw new Error("No library entries for user");
  }

  return response;
}

async function getCompletedLibraryEntries(userId) {
  let response = await Kitsu.findAll('libraryEntry', {
    filter: { user_id: userId, status: 'completed' },
    page: { limit: 9999 },
    include: 'anime'
  })

  if (response.meta.count === 0) {
    throw new Error("No library entries for user");
  }

  return response;
}

async function getOnHoldLibraryEntries(userId) {
  let response = await Kitsu.findAll('libraryEntry', {
    filter: { user_id: userId, status: 'on_hold' },
    page: { limit: 9999 },
    include: 'anime'
  })

  if (response.meta.count === 0) {
    throw new Error("No library entries for user");
  }

  return response;
}

async function getDroppedLibraryEntries(userId) {
  let response = await Kitsu.findAll('libraryEntry', {
    filter: { user_id: userId, status: 'dropped' },
    page: { limit: 9999 },
    include: 'anime'
  })

  if (response.meta.count === 0) {
    throw new Error("No library entries for user");
  }

  return response;
}

async function getAllLibraryEntries(userId) {
  let response = await Kitsu.findAll('libraryEntry', {
    filter: { user_id: userId },
    page: { limit: 9999 },
    include: 'anime'
  })

  if (response.meta.count === 0) {
    throw new Error("No library entries for user");
  }

  return response;
}

module.exports = {
  getUserByUsername,
  getCurrentLibraryEntries,
  getCompletedLibraryEntries,
  getOnHoldLibraryEntries,
  getDroppedLibraryEntries,
  getAllLibraryEntries
}
<|endoftext|>


<commit_before>var nureechFactory = angular.module('nureechFactory', []);

nureechFactory.factory('auth', [
  '$http',
  '$window',
  function($http, $window) {
  var auth = {};
  var tokenId = 'nureech-token';

  auth.saveToken = function(token) {
    $window.localStorage[tokenId] = token;
  };

  auth.getToken = function() {
    return $window.localStorage[tokenId];
  };

  auth.isLoggedIn = function() {
    var token = auth.getToken();

    if (token) {
      var payload = JSON.parse($window.atob(token.split('.')[1]));

      return payload.exp > Date.now() / 1000;
    } else {
      return false;
    }
  };

  auth.currentUser = function() {
    if (auth.isLoggedIn()) {
      var token = auth.getToken();
      var payload = JSON.parse($window.atob(token.split('.')[1]));

      return payload.username;
    }
  };

  auth.register = function(user) {
    return $http.post('/register', user).success(function(data) {
      auth.saveToken(data.token);
    });
  };

  auth.logIn = function(user) {
    return $http.post('/login', user).success(function(data) {
      auth.saveToken(data.token);
    });
  };

  auth.logOut = function() {
    $window.localStorage.removeItem(tokenId);
  };

  return auth;
}]);

nureechFactory.factory('reeches', [
  '$http',
  'auth',
  function($http, auth) {
    var o = {
      reeches: []
    };
    o.getAll = function() {
      return $http.get('/reeches').success(function(data) {
        angular.copy(data, o.reeches);
      });
    };
    o.get = function(id) {
      return $http.get('/reeches' + id).then(function(res) {
        return res.data;
      });
    };
    o.create = function (reech) {
      return $http.post('/reeches', reech, {
        headers: {Authorization: 'Bearer ' + auth.getToken()}
      })
      .success(function(data) {
        o.reeches.push(data);
      });
    };

    return o;
}]);
<commit_msg>Built the addComment function<commit_after>var nureechFactory = angular.module('nureechFactory', []);

nureechFactory.factory('auth', [
  '$http',
  '$window',
  function($http, $window) {
  var auth = {};
  var tokenId = 'nureech-token';

  auth.saveToken = function(token) {
    $window.localStorage[tokenId] = token;
  };

  auth.getToken = function() {
    return $window.localStorage[tokenId];
  };

  auth.isLoggedIn = function() {
    var token = auth.getToken();

    if (token) {
      var payload = JSON.parse($window.atob(token.split('.')[1]));

      return payload.exp > Date.now() / 1000;
    } else {
      return false;
    }
  };

  auth.currentUser = function() {
    if (auth.isLoggedIn()) {
      var token = auth.getToken();
      var payload = JSON.parse($window.atob(token.split('.')[1]));

      return payload.username;
    }
  };

  auth.register = function(user) {
    return $http.post('/register', user).success(function(data) {
      auth.saveToken(data.token);
    });
  };

  auth.logIn = function(user) {
    return $http.post('/login', user).success(function(data) {
      auth.saveToken(data.token);
    });
  };

  auth.logOut = function() {
    $window.localStorage.removeItem(tokenId);
  };

  return auth;
}]);

nureechFactory.factory('reeches', [
  '$http',
  'auth',
  function($http, auth) {
    var o = {
      reeches: []
    };
    o.getAll = function() {
      return $http.get('/reeches').success(function(data) {
        angular.copy(data, o.reeches);
      });
    };
    o.get = function(id) {
      return $http.get('/reeches/' + id).then(function(res) {
        return res.data;
      });
    };
    o.create = function (reech) {
      return $http.post('/reeches', reech, {
        headers: {Authorization: 'Bearer ' + auth.getToken()}
      })
      .success(function(data) {
        o.reeches.push(data);
      });
    };
    o.addComment = function(id, comment) {
      return $http.post('/reeches/' + id + '/comments', comment, {
        headers: {Authorization: 'Bearer ' + auth.getToken()}
      });
    };

    return o;
}]);
<|endoftext|>


<commit_before>'use strict';

var moment = require('moment-timezone');
var _ = require('lodash');
var parse_duration = require('parse-duration'); // https://www.npmjs.com/package/parse-duration
var times = require('../times');

function init (client, $) {
  var careportal = { };

  var translate = client.translate;
  var storage = $.localStorage;

  careportal.events = [
      { val: '<none>', name: '<none>' }
    , { val: 'BG Check', name: 'BG Check' }
    , { val: 'Snack Bolus', name: 'Snack Bolus' }
    , { val: 'Meal Bolus', name: 'Meal Bolus' }
    , { val: 'Correction Bolus', name: 'Correction Bolus' }
    , { val: 'Carb Correction', name: 'Carb Correction' }
    , { val: 'Announcement', name: 'Announcement' }
    , { val: 'Note', name: 'Note' }
    , { val: 'Question', name: 'Question' }
    , { val: 'Exercise', name: 'Exercise' }
    , { val: 'Site Change', name: 'Pump Site Change' }
    , { val: 'Sensor Start', name: 'Dexcom Sensor Start' }
    , { val: 'Sensor Change', name: 'Dexcom Sensor Change' }
    , { val: 'Insulin Change', name: 'Insulin Cartridge Change' }
    , { val: 'Temp Basal Start', name: 'Temp Basal Start' }
    , { val: 'Temp Basal End', name: 'Temp Basal End' }
    , { val: 'D.A.D. Alert', name: 'D.A.D. Alert' }
  ];

  var eventTime = $('#eventTimeValue');
  var eventDate = $('#eventDateValue');

  function setDateAndTime (time) {
    time = time || moment();
    eventTime.val(time.format('HH:mm'));
    eventDate.val(time.format('YYYY-MM-DD'));
  }

  function mergeDateAndTime ( ) {
    return client.utils.mergeInputTime(eventTime.val(), eventDate.val());
  }

  function updateTime(ele, time) {
    ele.attr('oldminutes', time.minutes());
    ele.attr('oldhours', time.hours());
  }

  function maybePrevent (event) {
    if (event) {
      event.preventDefault();
    }
  }

  careportal.filterInputs = function filterInputs ( event ) {
    var inputMatrix = {
      '<none>':            { bg: true,  insulin: true,  carbs: true,  prebolus: true,  duration: false, percent: false, absolute: false }
    , 'BG Check':          { bg: true,  insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Snack Bolus':       { bg: true,  insulin: true,  carbs: true,  prebolus: true,  duration: false, percent: false, absolute: false  }
    , 'Meal Bolus':        { bg: true,  insulin: true,  carbs: true,  prebolus: true,  duration: false, percent: false, absolute: false  }
    , 'Correction Bolus':  { bg: true,  insulin: true,  carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Carb Correction':   { bg: true,  insulin: false, carbs: true,  prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Announcement':      { bg: true,  insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Note':              { bg: true,  insulin: false, carbs: false, prebolus: false, duration: true,  percent: false, absolute: false  }
    , 'Question':          { bg: true,  insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Exercise':          { bg: false, insulin: false, carbs: false, prebolus: false, duration: true,  percent: false, absolute: false  }
    , 'Site Change':       { bg: false, insulin: true,  carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Sensor Start':      { bg: false, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Sensor Change':     { bg: false, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Insulin Change':    { bg: false, insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Temp Basal Start':  { bg: true,  insulin: false, carbs: false, prebolus: false, duration: true,  percent: true,  absolute: true   }
    , 'Temp Basal End':    { bg: true,  insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'D.A.D. Alert':      { bg: true,  insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    };

    var eventType = $('#eventType').val();

    function displayType (enabled) {
      if (enabled) {
        return '';
      } else {
        return 'none';
      }
    }

    function resetIfHidden(visible, id) {
      if (!visible) {
        $(id).val('');
      }
    }

    $('#bg').css('display',displayType(inputMatrix[eventType]['bg']));
    $('#insulinGivenLabel').css('display',displayType(inputMatrix[eventType]['insulin']));
    $('#carbsGivenLabel').css('display',displayType(inputMatrix[eventType]['carbs']));
    $('#durationLabel').css('display',displayType(inputMatrix[eventType]['duration']));
    $('#percentLabel').css('display',displayType(inputMatrix[eventType]['percent'] && $('#absolute').val() === ''));
    $('#absoluteLabel').css('display',displayType(inputMatrix[eventType]['absolute'] && $('#percent').val() === ''));
    $('#preBolusLabel').css('display',displayType(inputMatrix[eventType]['prebolus']));

    resetIfHidden(inputMatrix[eventType]['insulin'], '#insulinGiven');
    resetIfHidden(inputMatrix[eventType]['carbs'], '#carbsGiven');
    resetIfHidden(inputMatrix[eventType]['duration'], '#duration');
    resetIfHidden(inputMatrix[eventType]['absolute'], '#absolute');
    resetIfHidden(inputMatrix[eventType]['percent'], '#percent');
    resetIfHidden(inputMatrix[eventType]['prebolus'], '#preBolus');

    maybePrevent(event);
  };

  careportal.prepareEvents = function prepareEvents ( ) {
    $('#eventType').empty();
    _.each(careportal.events, function eachEvent(event) {
      $('#eventType').append('<option value="' + event.val+ '">' + translate(event.name) + '</option>');
    });
    $('#eventType').change(careportal.filterInputs);
    $('#percentLabel').change(careportal.filterInputs);
    $('#absoluteLabel').change(careportal.filterInputs);
    careportal.filterInputs();
  };

  careportal.resolveEventName = function resolveEventName(value) {
    _.each(careportal.events, function eachEvent(e) {
      if (e.val === value) {
        value = e.name;
      }
    });
    return value;
  };

  careportal.prepare = function prepare ( ) {
    careportal.prepareEvents();
    $('#eventType').val('<none>');
    $('#glucoseValue').val('').attr('placeholder', translate('Value in') + ' ' + client.settings.units);
    $('#meter').prop('checked', true);
    $('#carbsGiven').val('');
    $('#insulinGiven').val('');
    $('#duration').val('');
    $('#percent').val('');
    $('#absolute').val('');
    $('#preBolus').val(0);
    $('#notes').val('');
    $('#enteredBy').val(storage.get('enteredBy') || '');
    $('#nowtime').prop('checked', true);
    setDateAndTime();
  };

  function gatherData ( ) {
    var data = {
      enteredBy: $('#enteredBy').val()
    , eventType: $('#eventType').val()
    , glucose: $('#glucoseValue').val()
    , glucoseType: $('#treatment-form').find('input[name=glucoseType]:checked').val()
    , carbs: $('#carbsGiven').val()
    , insulin: $('#insulinGiven').val()
    , duration: times.msecs(parse_duration($('#duration').val())).mins < 1 ? $('#duration').val() : times.msecs(parse_duration($('#duration').val())).mins
    , percent: $('#percent').val()
    , preBolus: parseInt($('#preBolus').val())
    , notes: $('#notes').val()
    , units: client.settings.units
    };

    //special handling for absolute to support temp to 0
    var absolute = $('#absolute').val();
    if ('' !== absolute && !isNaN(absolute)) {
      data.absolute = Number(absolute);
    }

    if ($('#othertime').is(':checked')) {
      data.eventTime = mergeDateAndTime().toDate();
    }

    if (data.eventType.indexOf('Temp Basal') > -1) {
      data.eventType = 'Temp Basal';
    }

    return data;
  }

  careportal.save = function save (event) {
    var data = gatherData();
    confirmPost(data);
    maybePrevent(event);
  };

  function buildConfirmText(data) {
    var text = [
      translate('Please verify that the data entered is correct') + ': '
      , translate('Event Type') + ': ' + translate(careportal.resolveEventName(data.eventType))
    ];

    function pushIf (check, valueText) {
      if (check) {
        text.push(valueText);
      }
    }

    pushIf(data.glucose, translate('Blood Glucose') + ': ' + data.glucose);
    pushIf(data.glucose, translate('Measurement Method') + ': ' + translate(data.glucoseType));

    pushIf(data.carbs, translate('Carbs Given') + ': ' + data.carbs);
    pushIf(data.insulin, translate('Insulin Given') + ': ' + data.insulin);
    pushIf(data.duration, translate('Duration') + ': ' + data.duration);
    pushIf(data.percent, translate('Percent') + ': ' + data.percent);
    pushIf(data.absolute, translate('Basal value') + ': ' + data.absolute);
    pushIf(data.preBolus, translate('Carb Time') + ': ' + data.preBolus + ' ' + translate('mins'));
    pushIf(data.notes, translate('Notes') + ': ' + data.notes);
    pushIf(data.enteredBy, translate('Entered By') + ': ' + data.enteredBy);

    text.push(translate('Event Time') + ': ' + (data.eventTime ? data.eventTime.toLocaleString() : new Date().toLocaleString()));
    return text.join('\n');
  }

  function confirmPost(data) {
    if (window.confirm(buildConfirmText(data))) {
      $.ajax({
        method: 'POST',
        url: '/api/v1/treatments/'
      , headers: {
        'api-secret': client.hashauth.hash()
      }
      , data: data
      }).done(function treatmentSaved (response) {
        console.info('treatment saved', response);
      }).fail(function treatmentSaveFail (response) {
        console.info('treatment saved', response);
        alert(translate('Entering record failed') + '. ' + translate('Status') + ': ' + response.status);
      });

      storage.set('enteredBy', data.enteredBy);

      client.browserUtils.closeDrawer('#treatmentDrawer');
    }
  }

  careportal.dateTimeFocus = function dateTimeFocus (event) {
    $('#othertime').prop('checked', true);
    updateTime($(this), mergeDateAndTime());
    maybePrevent(event);
  };

  careportal.dateTimeChange = function dateTimeChange (event) {
    $('#othertime').prop('checked', true);
    var ele = $(this);
    var merged = mergeDateAndTime();

    if (ele.attr('oldminutes') === '59' && merged.minutes() === 0) {
      merged.add(1, 'hours');
    }
    if (ele.attr('oldminutes') === '0' && merged.minutes() === 59) {
      merged.add(-1, 'hours');
    }

    setDateAndTime(merged);
    updateTime(ele, merged);
    maybePrevent(event);
  };

  careportal.eventTimeTypeChange = function eventTimeTypeChange (event) {
    if ($('#othertime').is(':checked')) {
      eventTime.focus();
    } else {
      setDateAndTime();
    }
    maybePrevent(event);
  };

  careportal.toggleDrawer = function toggleDrawer (event) {
    client.browserUtils.toggleDrawer('#treatmentDrawer', careportal.prepare);
    maybePrevent(event);
  };

  $('#treatmentDrawerToggle').click(careportal.toggleDrawer);
  $('#treatmentDrawer').find('button').click(careportal.save);
  $('#eventTime').find('input:radio').change(careportal.eventTimeTypeChange);

  $('.eventinput').focus(careportal.dateTimeFocus).change(careportal.dateTimeChange);

  return careportal;
}

module.exports = init;

<commit_msg>adjust inputMatrix<commit_after>'use strict';

var moment = require('moment-timezone');
var _ = require('lodash');
var parse_duration = require('parse-duration'); // https://www.npmjs.com/package/parse-duration
var times = require('../times');

function init (client, $) {
  var careportal = { };

  var translate = client.translate;
  var storage = $.localStorage;

  careportal.events = [
      { val: '<none>', name: '<none>' }
    , { val: 'BG Check', name: 'BG Check' }
    , { val: 'Snack Bolus', name: 'Snack Bolus' }
    , { val: 'Meal Bolus', name: 'Meal Bolus' }
    , { val: 'Correction Bolus', name: 'Correction Bolus' }
    , { val: 'Carb Correction', name: 'Carb Correction' }
    , { val: 'Announcement', name: 'Announcement' }
    , { val: 'Note', name: 'Note' }
    , { val: 'Question', name: 'Question' }
    , { val: 'Exercise', name: 'Exercise' }
    , { val: 'Site Change', name: 'Pump Site Change' }
    , { val: 'Sensor Start', name: 'Dexcom Sensor Start' }
    , { val: 'Sensor Change', name: 'Dexcom Sensor Change' }
    , { val: 'Insulin Change', name: 'Insulin Cartridge Change' }
    , { val: 'Temp Basal Start', name: 'Temp Basal Start' }
    , { val: 'Temp Basal End', name: 'Temp Basal End' }
    , { val: 'D.A.D. Alert', name: 'D.A.D. Alert' }
  ];

  var eventTime = $('#eventTimeValue');
  var eventDate = $('#eventDateValue');

  function setDateAndTime (time) {
    time = time || moment();
    eventTime.val(time.format('HH:mm'));
    eventDate.val(time.format('YYYY-MM-DD'));
  }

  function mergeDateAndTime ( ) {
    return client.utils.mergeInputTime(eventTime.val(), eventDate.val());
  }

  function updateTime(ele, time) {
    ele.attr('oldminutes', time.minutes());
    ele.attr('oldhours', time.hours());
  }

  function maybePrevent (event) {
    if (event) {
      event.preventDefault();
    }
  }

  careportal.filterInputs = function filterInputs ( event ) {
    var inputMatrix = {
      '<none>':            { bg: true,  insulin: true,  carbs: true,  prebolus: false, duration: false, percent: false, absolute: false  }
    , 'BG Check':          { bg: true,  insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Snack Bolus':       { bg: true,  insulin: true,  carbs: true,  prebolus: true,  duration: false, percent: false, absolute: false  }
    , 'Meal Bolus':        { bg: true,  insulin: true,  carbs: true,  prebolus: true,  duration: false, percent: false, absolute: false  }
    , 'Correction Bolus':  { bg: true,  insulin: true,  carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Carb Correction':   { bg: true,  insulin: false, carbs: true,  prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Announcement':      { bg: true,  insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Note':              { bg: true,  insulin: false, carbs: false, prebolus: false, duration: true,  percent: false, absolute: false  }
    , 'Question':          { bg: true,  insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Exercise':          { bg: false, insulin: false, carbs: false, prebolus: false, duration: true,  percent: false, absolute: false  }
    , 'Site Change':       { bg: true,  insulin: true,  carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Sensor Start':      { bg: true,  insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Sensor Change':     { bg: true,  insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Insulin Change':    { bg: true,  insulin: true,  carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'Temp Basal Start':  { bg: true,  insulin: false, carbs: false, prebolus: false, duration: true,  percent: true,  absolute: true   }
    , 'Temp Basal End':    { bg: true,  insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    , 'D.A.D. Alert':      { bg: true,  insulin: false, carbs: false, prebolus: false, duration: false, percent: false, absolute: false  }
    };

    var eventType = $('#eventType').val();

    function displayType (enabled) {
      if (enabled) {
        return '';
      } else {
        return 'none';
      }
    }

    function resetIfHidden(visible, id) {
      if (!visible) {
        $(id).val('');
      }
    }

    $('#bg').css('display',displayType(inputMatrix[eventType]['bg']));
    $('#insulinGivenLabel').css('display',displayType(inputMatrix[eventType]['insulin']));
    $('#carbsGivenLabel').css('display',displayType(inputMatrix[eventType]['carbs']));
    $('#durationLabel').css('display',displayType(inputMatrix[eventType]['duration']));
    $('#percentLabel').css('display',displayType(inputMatrix[eventType]['percent'] && $('#absolute').val() === ''));
    $('#absoluteLabel').css('display',displayType(inputMatrix[eventType]['absolute'] && $('#percent').val() === ''));
    $('#preBolusLabel').css('display',displayType(inputMatrix[eventType]['prebolus']));

    resetIfHidden(inputMatrix[eventType]['insulin'], '#insulinGiven');
    resetIfHidden(inputMatrix[eventType]['carbs'], '#carbsGiven');
    resetIfHidden(inputMatrix[eventType]['duration'], '#duration');
    resetIfHidden(inputMatrix[eventType]['absolute'], '#absolute');
    resetIfHidden(inputMatrix[eventType]['percent'], '#percent');
    resetIfHidden(inputMatrix[eventType]['prebolus'], '#preBolus');

    maybePrevent(event);
  };

  careportal.prepareEvents = function prepareEvents ( ) {
    $('#eventType').empty();
    _.each(careportal.events, function eachEvent(event) {
      $('#eventType').append('<option value="' + event.val+ '">' + translate(event.name) + '</option>');
    });
    $('#eventType').change(careportal.filterInputs);
    $('#percentLabel').change(careportal.filterInputs);
    $('#absoluteLabel').change(careportal.filterInputs);
    careportal.filterInputs();
  };

  careportal.resolveEventName = function resolveEventName(value) {
    _.each(careportal.events, function eachEvent(e) {
      if (e.val === value) {
        value = e.name;
      }
    });
    return value;
  };

  careportal.prepare = function prepare ( ) {
    careportal.prepareEvents();
    $('#eventType').val('<none>');
    $('#glucoseValue').val('').attr('placeholder', translate('Value in') + ' ' + client.settings.units);
    $('#meter').prop('checked', true);
    $('#carbsGiven').val('');
    $('#insulinGiven').val('');
    $('#duration').val('');
    $('#percent').val('');
    $('#absolute').val('');
    $('#preBolus').val(0);
    $('#notes').val('');
    $('#enteredBy').val(storage.get('enteredBy') || '');
    $('#nowtime').prop('checked', true);
    setDateAndTime();
  };

  function gatherData ( ) {
    var data = {
      enteredBy: $('#enteredBy').val()
    , eventType: $('#eventType').val()
    , glucose: $('#glucoseValue').val()
    , glucoseType: $('#treatment-form').find('input[name=glucoseType]:checked').val()
    , carbs: $('#carbsGiven').val()
    , insulin: $('#insulinGiven').val()
    , duration: times.msecs(parse_duration($('#duration').val())).mins < 1 ? $('#duration').val() : times.msecs(parse_duration($('#duration').val())).mins
    , percent: $('#percent').val()
    , preBolus: parseInt($('#preBolus').val())
    , notes: $('#notes').val()
    , units: client.settings.units
    };

    //special handling for absolute to support temp to 0
    var absolute = $('#absolute').val();
    if ('' !== absolute && !isNaN(absolute)) {
      data.absolute = Number(absolute);
    }

    if ($('#othertime').is(':checked')) {
      data.eventTime = mergeDateAndTime().toDate();
    }

    if (data.eventType.indexOf('Temp Basal') > -1) {
      data.eventType = 'Temp Basal';
    }

    return data;
  }

  careportal.save = function save (event) {
    var data = gatherData();
    confirmPost(data);
    maybePrevent(event);
  };

  function buildConfirmText(data) {
    var text = [
      translate('Please verify that the data entered is correct') + ': '
      , translate('Event Type') + ': ' + translate(careportal.resolveEventName(data.eventType))
    ];

    function pushIf (check, valueText) {
      if (check) {
        text.push(valueText);
      }
    }

    pushIf(data.glucose, translate('Blood Glucose') + ': ' + data.glucose);
    pushIf(data.glucose, translate('Measurement Method') + ': ' + translate(data.glucoseType));

    pushIf(data.carbs, translate('Carbs Given') + ': ' + data.carbs);
    pushIf(data.insulin, translate('Insulin Given') + ': ' + data.insulin);
    pushIf(data.duration, translate('Duration') + ': ' + data.duration);
    pushIf(data.percent, translate('Percent') + ': ' + data.percent);
    pushIf(data.absolute, translate('Basal value') + ': ' + data.absolute);
    pushIf(data.preBolus, translate('Carb Time') + ': ' + data.preBolus + ' ' + translate('mins'));
    pushIf(data.notes, translate('Notes') + ': ' + data.notes);
    pushIf(data.enteredBy, translate('Entered By') + ': ' + data.enteredBy);

    text.push(translate('Event Time') + ': ' + (data.eventTime ? data.eventTime.toLocaleString() : new Date().toLocaleString()));
    return text.join('\n');
  }

  function confirmPost(data) {
    if (window.confirm(buildConfirmText(data))) {
      $.ajax({
        method: 'POST',
        url: '/api/v1/treatments/'
      , headers: {
        'api-secret': client.hashauth.hash()
      }
      , data: data
      }).done(function treatmentSaved (response) {
        console.info('treatment saved', response);
      }).fail(function treatmentSaveFail (response) {
        console.info('treatment saved', response);
        alert(translate('Entering record failed') + '. ' + translate('Status') + ': ' + response.status);
      });

      storage.set('enteredBy', data.enteredBy);

      client.browserUtils.closeDrawer('#treatmentDrawer');
    }
  }

  careportal.dateTimeFocus = function dateTimeFocus (event) {
    $('#othertime').prop('checked', true);
    updateTime($(this), mergeDateAndTime());
    maybePrevent(event);
  };

  careportal.dateTimeChange = function dateTimeChange (event) {
    $('#othertime').prop('checked', true);
    var ele = $(this);
    var merged = mergeDateAndTime();

    if (ele.attr('oldminutes') === '59' && merged.minutes() === 0) {
      merged.add(1, 'hours');
    }
    if (ele.attr('oldminutes') === '0' && merged.minutes() === 59) {
      merged.add(-1, 'hours');
    }

    setDateAndTime(merged);
    updateTime(ele, merged);
    maybePrevent(event);
  };

  careportal.eventTimeTypeChange = function eventTimeTypeChange (event) {
    if ($('#othertime').is(':checked')) {
      eventTime.focus();
    } else {
      setDateAndTime();
    }
    maybePrevent(event);
  };

  careportal.toggleDrawer = function toggleDrawer (event) {
    client.browserUtils.toggleDrawer('#treatmentDrawer', careportal.prepare);
    maybePrevent(event);
  };

  $('#treatmentDrawerToggle').click(careportal.toggleDrawer);
  $('#treatmentDrawer').find('button').click(careportal.save);
  $('#eventTime').find('input:radio').change(careportal.eventTimeTypeChange);

  $('.eventinput').focus(careportal.dateTimeFocus).change(careportal.dateTimeChange);

  return careportal;
}

module.exports = init;

<|endoftext|>


<commit_before>var request = require('request');
var _ = require('lodash');
var geoip = require('geoip-lite');

/**
 * Converts numeric degrees to radians
 */
if (typeof (Number.prototype.toRad) === "undefined") {
  Number.prototype.toRad = function () {
    return this * Math.PI / 180;
  }
}

/**
 * Calculates the distance between two coordinates.
 * Uses the great circle calculation (haversine).
 * @param l1 origin coordinate in the form [lat, lon]
 * @param l2 destination coordinate in the form [lat, lon]
 * @return distance in kilometers
 */
function calcDistance (l1, l2) {
  // The earth's is a sphere, so use its radius (in IU units, like kilometers)
  var radius = 6371;
  // Calculate the raw distances between points
  // And convert to radians; no one uses degress in math
  var dist = [
    (l2[0] - l1[0]).toRad(),
    (l2[1] - l1[1]).toRad()
  ];
  // Use the haversine formula
  var a = Math.sin(dist[0]/2) * Math.sin(dist[0]/2) +
    Math.cos(l1[0].toRad()) * Math.cos(l2[0].toRad()) *
    Math.sin(dist[1]/2) * Math.sin(dist[1]/2);
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  var d = radius * c;
  // Return the distance in kilometers
  return d;
};

/**
 * Given an array of data, find data that matches
 * first_name and last_name
 * @param data array of input data
 * @param first_name first name string to match
 * @param last_name last name string to match
 * @return array of matching entries
 */
function findMatches (data, first_name, last_name) {
  // normalize first and last name
  var fn = first_name.toLowerCase().trim();
  var ln = last_name.toLowerCase().trim();
  var result = [];
  for (var i = 0; i < data.length; i++) {
    if ((data[i].first_name.toLowerCase().trim() == fn) &&
      (data[i].last_name.toLowerCase().trim() == ln)) {
      // match!
      result.push(data[i]);
    }
  }
  return result;
};

/**
 * Get the distance between someone with a
 * (name, IP) pair and a remote database.
 * param params the parameters array with IP and name
 * param cb callback function of the format (error_code, object)
 */
function getDistance (params, cb) {
  // Perform parameter validation
  if (_.isUndefined(params.ip)) {
    cb(422, { error: 'IP Address is required.'} );
    return;
  }
  if (_.isUndefined(params.first_name)) {
    cb(422, { error: 'First Name is required.'} );
    return;
  }
  if (_.isUndefined(params.last_name)) {
    cb(422, { error: 'Last Name is required.'} );
    return;
  }
  // Get the lat/long coordinates of the IP address
  var geo = geoip.lookup(params.ip);
  // Request the list of people from the remote server
  request({
    url: "http://private-5e0de-nodejsapitestprovider.apiary-mock.com/api/people",
    method: "GET",
    timeout: 20000
  }, function (error, response, body) {
    // success, parse results
    if (response.statusCode === 200) {
      // find responses that match the person specified
      var matches = findMatches(JSON.parse(body), params.first_name, params.last_name);
      // iterate through each of the entries and calculate the distance
      for (var i = 0; i < matches.length; i++) {
        matches[i].phone_distance_from_ip = calcDistance(
          [matches[i].phone_location.latitude, matches[i].phone_location.longitude],
          geo.ll
        );
        matches[i].stated_distance_from_ip = calcDistance(
          [matches[i].stated_location.latitude, matches[i].stated_location.longitude],
          geo.ll
        );
        matches[i].stated_distance_from_phone = calcDistance(
          [matches[i].stated_location.latitude, matches[i].stated_location.longitude],
          [matches[i].phone_location.latitude, matches[i].phone_location.longitude]
        );
      }
      // if only one match, just return object as per the spec
      // otherwise, return an array with all of the results
      if (matches.length === 1) {
        cb(null, matches[0]);
      } else {
        cb(null, matches);
      }
    }
    // error, respond to client with a corresponding error
    else {
      cb(500, { error: 'Remote server took too long to provide people.' });
    }
  });
};

module.exports = {
  calcDistance: calcDistance,
  findMatches: findMatches,
  getDistance: getDistance
};
<commit_msg>Rename ip > ip_address to match spec<commit_after>var request = require('request');
var _ = require('lodash');
var geoip = require('geoip-lite');

/**
 * Converts numeric degrees to radians
 */
if (typeof (Number.prototype.toRad) === "undefined") {
  Number.prototype.toRad = function () {
    return this * Math.PI / 180;
  }
}

/**
 * Calculates the distance between two coordinates.
 * Uses the great circle calculation (haversine).
 * @param l1 origin coordinate in the form [lat, lon]
 * @param l2 destination coordinate in the form [lat, lon]
 * @return distance in kilometers
 */
function calcDistance (l1, l2) {
  // The earth's is a sphere, so use its radius (in IU units, like kilometers)
  var radius = 6371;
  // Calculate the raw distances between points
  // And convert to radians; no one uses degress in math
  var dist = [
    (l2[0] - l1[0]).toRad(),
    (l2[1] - l1[1]).toRad()
  ];
  // Use the haversine formula
  var a = Math.sin(dist[0]/2) * Math.sin(dist[0]/2) +
    Math.cos(l1[0].toRad()) * Math.cos(l2[0].toRad()) *
    Math.sin(dist[1]/2) * Math.sin(dist[1]/2);
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
  var d = radius * c;
  // Return the distance in kilometers
  return d;
};

/**
 * Given an array of data, find data that matches
 * first_name and last_name
 * @param data array of input data
 * @param first_name first name string to match
 * @param last_name last name string to match
 * @return array of matching entries
 */
function findMatches (data, first_name, last_name) {
  // normalize first and last name
  var fn = first_name.toLowerCase().trim();
  var ln = last_name.toLowerCase().trim();
  var result = [];
  for (var i = 0; i < data.length; i++) {
    if ((data[i].first_name.toLowerCase().trim() == fn) &&
      (data[i].last_name.toLowerCase().trim() == ln)) {
      // match!
      result.push(data[i]);
    }
  }
  return result;
};

/**
 * Get the distance between someone with a
 * (name, IP) pair and a remote database.
 * param params the parameters array with IP and name
 * param cb callback function of the format (error_code, object)
 */
function getDistance (params, cb) {
  // Perform parameter validation
  if (_.isUndefined(params.ip_address)) {
    cb(422, { error: 'IP Address is required.'} );
    return;
  }
  if (_.isUndefined(params.first_name)) {
    cb(422, { error: 'First Name is required.'} );
    return;
  }
  if (_.isUndefined(params.last_name)) {
    cb(422, { error: 'Last Name is required.'} );
    return;
  }
  // Get the lat/long coordinates of the IP address
  var geo = geoip.lookup(params.ip_address);
  // Request the list of people from the remote server
  request({
    url: "http://private-5e0de-nodejsapitestprovider.apiary-mock.com/api/people",
    method: "GET",
    timeout: 20000
  }, function (error, response, body) {
    // success, parse results
    if (response.statusCode === 200) {
      // find responses that match the person specified
      var matches = findMatches(JSON.parse(body), params.first_name, params.last_name);
      // iterate through each of the entries and calculate the distance
      for (var i = 0; i < matches.length; i++) {
        matches[i].phone_distance_from_ip = calcDistance(
          [matches[i].phone_location.latitude, matches[i].phone_location.longitude],
          geo.ll
        );
        matches[i].stated_distance_from_ip = calcDistance(
          [matches[i].stated_location.latitude, matches[i].stated_location.longitude],
          geo.ll
        );
        matches[i].stated_distance_from_phone = calcDistance(
          [matches[i].stated_location.latitude, matches[i].stated_location.longitude],
          [matches[i].phone_location.latitude, matches[i].phone_location.longitude]
        );
      }
      // if only one match, just return object as per the spec
      // otherwise, return an array with all of the results
      if (matches.length === 1) {
        cb(null, matches[0]);
      } else {
        cb(null, matches);
      }
    }
    // error, respond to client with a corresponding error
    else {
      cb(500, { error: 'Remote server took too long to provide people.' });
    }
  });
};

module.exports = {
  calcDistance: calcDistance,
  findMatches: findMatches,
  getDistance: getDistance
};
<|endoftext|>