<commit_before>/**
 This Source Code is licensed under the MIT license. If a copy of the
 MIT-license was not distributed with this file, You can obtain one at:
 http://opensource.org/licenses/mit-license.html.

 @author: Hans van den Akker (mysim1)
 @license MIT
 @copyright Bizboard, 2015

 */

import _                from 'lodash';
import FlexScrollView   from 'famous-flex/src/FlexScrollView.js';
import {Throttler}      from 'arva-utils/Throttler.js';

export class DataBoundScrollView extends FlexScrollView {

    constructor(OPTIONS = {}) {
        super(_.extend({
            autoPipeEvents: true,
            throttleDelay: 0, /* If set to 0, no delay is added in between adding items to the DataBoundScrollView. */
            dataSource: [],
            sortingDirection: 'ascending',
            flow: true,
            flowOptions: {
                spring: {               // spring-options used when transitioning between states
                    dampingRatio: 0.8,  // spring damping ratio
                    period: 1000        // duration of the animation
                },
                insertSpec: {           // render-spec used when inserting renderables
                    opacity: 0          // start opacity is 0, causing a fade-in effect,
                }
            }
        }, OPTIONS));

        this.isGrouped = this.options.groupBy != null;
        this.isDescending = this.options.sortingDirection === 'descending';
        this.throttler = new Throttler(this.options.throttleDelay, true, this);

        /* If no orderBy method is set, or it is a string field name, we set our own ordering method. */
        if (this.options.orderBy && typeof this.options.orderBy === 'string') {
            let fieldName = this.options.orderBy || 'id';
            this.options.orderBy = function (currentChild, compareChild) {
                if (this.isDescending) {
                    return currentChild[fieldName] < compareChild.data[fieldName];
                } else {
                    return currentChild[fieldName] > compareChild.data[fieldName];
                }
            }
        }

        /* If present in options.headerTemplate or options.placeholderTemplate, we build the header and placeholder elements. */
        this._addHeader();
        this._addPlaceholder();

        if (this.options.dataStore) {
            this._bindDataSource(this.options.dataStore);
        } else {
            console.log('No DataSource was set.');
        }
    }

    /**
     * Reloads the dataFilter option of the DataBoundScrollView, and verifies whether the items in the dataStore are allowed by the new filter.
     * It removes any currently visible items that aren't allowed anymore, and adds any non-visible ones that are allowed now.
     * @param {Function} newFilter New filter function to verify item visibility with.
     * @returns {void}
     */
    reloadFilter(newFilter) {
        this.options.dataFilter = newFilter;

        for (let entry of this.options.dataStore) {
            let surface = _.find(this._dataSource, (surface) => surface.dataId === entry.id);
            let alreadyExists = surface !== undefined;
            let result = newFilter(entry);

            if (result instanceof Promise) {
                result.then(function (shouldShow) { this._handleNewFilterResult(shouldShow, alreadyExists, entry); }.bind(this))
            } else {
                this._handleNewFilterResult(result, alreadyExists, entry);
            }
        }
    }

    _handleNewFilterResult(shouldShow, alreadyExists, entry) {
        if (shouldShow) {
            /* This entry should be in the view, add it if it doesn't exist yet. */
            if (!alreadyExists) {
                this._addItem(entry);
            }
        } else {
            /* This entry should not be in the view, remove if present. */
            if (alreadyExists) {
                this._removeItem(entry);
            }
        }
    }

    _findGroup(groupId) {
        return _.findIndex(this._dataSource, function (surface) {
            return surface.groupId === groupId;
        });
    }

    _findNextGroup(fromIndex) {
        let dslength = this._dataSource.length;
        for (let pos = fromIndex; pos < dslength; pos++) {
            if (this._dataSource[pos].groupId) {
                return pos;
            }
        }

        return this._dataSource.length;
    }

    _getGroupByValue(child) {
        let groupByValue = '';
        if (typeof this.options.groupBy === 'function') {
            groupByValue = this.options.groupBy(child);
        } else if (typeof this.options.groupBy === 'string') {
            groupByValue = this.options.groupBy;
        }
        return groupByValue;
    }

    _addGroupItem(groupByValue, insertIndex) {
        let newSurface = this.options.groupTemplate(groupByValue);
        newSurface.groupId = groupByValue;

        this.insert(insertIndex, newSurface);
    }

    _getInsertIndex(child, previousSiblingID = null) {
        /* By default, add item to the beginning if descending order, or at the end otherwise. */
        let firstIndex = this.header ? 1 : 0;
        let insertIndex = this.isDescending ? firstIndex : this._dataSource.length;

        /* If we have an orderBy function, find the index we should be inserting at. */
        if (this.options.orderBy && typeof this.options.orderBy === 'function') {
            let foundOrderedIndex = _.findIndex(this._dataSource, function (compareChild) {

                /* Ignore the header, placeholder, and group items. */
                if (!compareChild.isHeader && !compareChild.isPlaceholder && !compareChild.groupId) {
                    return this.options.orderBy(child, compareChild);
                }
                return false;

            }.bind(this));

            if (foundOrderedIndex !== -1) {
                insertIndex = foundOrderedIndex;
            }
        } else if (previousSiblingID) {
            /* We don't have an orderBy method, but do have a previousSiblingID we can use to find the correct insertion index. */
            let siblingIndex = _.findIndex(this._dataSource, (sibling) => sibling.dataId === previousSiblingID);
            if (siblingIndex !== -1) {
                insertIndex = siblingIndex + 1;
            }
        }

        if(this.isGrouped && insertIndex === firstIndex && this._dataSource[insertIndex] && this._dataSource[insertIndex].groupId
            && this._dataSource[insertIndex].groupId === this._getGroupByValue(child)) {
            /* We didn't get an insert index from the ordering method or a previous sibling ID, and our insert index is already occupied by
             * a group item that we need to insert after, so we'll move the index to after the group item. */
            insertIndex++;
        } else if (this.isGrouped && insertIndex > 0 && this._dataSource[insertIndex-1] && this._dataSource[insertIndex-1].groupId
            && this._dataSource[insertIndex-1].groupId !== this._getGroupByValue(child)) {
            /* The insert index is after a group item that this child does not belong to, so we'll have to decrement the index by one
             * to avoid the child being added under this group. */
            insertIndex--;
        }

        return insertIndex;
    }

    _addItem(child, previousSiblingID) {
        this._removePlaceholder();

        let insertIndex = this._getInsertIndex(child, previousSiblingID);

        /* If we're using groups, check if we need to insert a group item before this child. */
        if (this.isGrouped) {
            let groupByValue = this._getGroupByValue(child);
            let groupIndex = this._findGroup(groupByValue);
            if (groupIndex === -1) {
                /* No group of this value exists yet, so we'll need to create one. */
                this._addGroupItem(groupByValue, insertIndex);
                insertIndex++;
            }
        }

        let newSurface = this.options.itemTemplate(child);
        newSurface.dataId = child.id;
        newSurface.data = child;
        newSurface.on('click', function () {
            this._eventOutput.emit('child_click', {renderNode: newSurface, dataObject: child});
        }.bind(this));

        this.insert(insertIndex, newSurface);
    }

    _replaceItem(child) {
        let index = this._getDataSourceIndex(child.id);

        let newSurface = this.options.itemTemplate(child);
        newSurface.dataId = child.id;
        this.replace(index, newSurface);
    }

    _removeItem(child) {
        let index = _.findIndex(this._dataSource, function (surface) {
            return surface.dataId === child.id;
        });

        if (index > -1) {
            this.remove(index);
        }

        /* If we're using groups, check if we need to remove the group that this child belonged to. */
        if (this.isGrouped) {
            let groupByValue = this._getGroupByValue(child);
            let otherGroupChilds = _.findIndex(this._dataSource, function (otherChild) {
                return this._getGroupByValue(otherChild) === groupByValue;
            }.bind(this));

            if (otherGroupChilds === -1) {
                /* No more childs in this group, so let's remove the group. */
                let groupIndex = this._findGroup(groupByValue);
                if (groupIndex !== -1) {
                    this.remove(groupIndex);
                }
            }
        }

        /* The amount of items in the dataSource is subtracted with a header if present, to get the total amount of actual items in the scrollView. */
        let itemCount = this._dataSource.length - (this.header ? 1 : 0);
        if (itemCount === 0) {
            this._addPlaceholder();
        }
    }

    _moveItem(oldId, prevChildId = null) {

        let oldIndex = this._getDataSourceIndex(oldId);
        let previousSiblingIndex = this._getNextVisibleIndex(prevChildId);

        if (oldIndex !== previousSiblingIndex) {
            this.move(oldIndex, previousSiblingIndex);
        }
    }

    _addHeader() {
        if (this.options.headerTemplate && !this.header) {
            this.header = this.options.headerTemplate();
            this.header.isHeader = true;
            this.insert(0, this.header);
        }
    }

    _addPlaceholder() {
        if (this.options.placeholderTemplate && !this.placeholder) {
            let insertIndex = this.header ? 1 : 0;
            this.placeholder = this.options.placeholderTemplate();
            this.placeholder.dataId = this.placeholder.id = '_placeholder';
            this.placeholder.isPlaceholder = true;
            this.insert(insertIndex, this.placeholder);
        }
    }

    _removePlaceholder() {
        if (this.placeholder) {
            this._removeItem(this.placeholder);
            this.placeholder = null;
        }
    }

    _bindDataSource() {

        if (!this.options.dataStore || !this.options.itemTemplate) {
            console.log('Datasource and template should both be set.');
            return;
        }

        if (!this.options.template instanceof Function) {
            console.log('Template needs to be a function.');
            return;
        }

        this.options.dataStore.on('child_added', function (child, previousSiblingID) {

            if (this.options.dataFilter &&
                (typeof this.options.dataFilter === 'function')) {

                let result = this.options.dataFilter(child);

                if (result instanceof Promise) {
                    /* If the result is a Promise, show the item when that promise resolves. */
                    result.then((show) => { if (show) { this.throttler.add(() => { return this._addItem(child), previousSiblingID; }); } });
                } else if (result) {
                    /* The result is an item, so we can add it directly. */
                    this.throttler.add(() => { return this._addItem(child, previousSiblingID); });
                }
            } else {
                /* There is no dataFilter method, so we can add this child. */
                this.throttler.add(() => { return this._addItem(child, previousSiblingID); });
            }
        }.bind(this));

        this.options.dataStore.on('child_changed', function (child, previousSiblingID) {

            let changedItem = this._getDataSourceIndex(child.id);

            if (this._dataSource && changedItem < this._dataSource.length) {

                if (this.options.dataFilter &&
                    typeof this.options.dataFilter === 'function' && !this.options.dataFilter(child)) {
                    this._removeItem(child);
                } else {
                    if (changedItem === -1) {
                        this.throttler.add(() => { return this._addItem(child, previousSiblingID); });
                    } else {
                        this.throttler.add(() => { return this._replaceItem(child); });
                        this.throttler.add(() => { return this._moveItem(child.id, previousSiblingID); });
                    }
                }
            }
        }.bind(this));

        this.options.dataStore.on('child_moved', function (child, previousSibling) {
            let current = this._getDataSourceIndex(child.id);
            let previous = this._getDataSourceIndex(previousSibling);
            this.throttler.add(() => { return this._moveItem(current, previous); });
        }.bind(this));

        this.options.dataStore.on('child_removed', function (child) {
            this.throttler.add(() => { return this._removeItem(child); });
        }.bind(this));
    }

    _getDataSourceIndex(id) {
        return _.findIndex(this._dataSource, function (surface) {
            return surface.dataId === id;
        });
    }

    _getNextVisibleIndex(id) {

        let viewIndex = this._getDataSourceIndex(id);
        if (viewIndex === -1) {
            let modelIndex = _.findIndex(this.options.dataStore, function (model) {
                return model.id === id;
            });

            if (modelIndex === 0 || modelIndex === -1) {
                return this.isDescending ? this._dataSource ? this._dataSource.length - 1 : 0 : 0;
            } else {
                let nextModel = this.options.dataStore[this.isDescending ? modelIndex + 1 : modelIndex - 1];
                let nextIndex = this._getDataSourceIndex(nextModel.id);
                if (nextIndex > -1) {

                    return this.isDescending ? nextIndex === 0 ? 0 : nextIndex - 1 :
                           this._dataSource.length === nextIndex + 1 ? nextIndex : nextIndex + 1;
                } else {
                    return this._getNextVisibleIndex(nextModel.id);
                }
            }
        } else {
            return this.isDescending ? viewIndex === 0 ? 0 : viewIndex - 1 :
                   this._dataSource.length === viewIndex + 1 ? viewIndex : viewIndex + 1;
        }
    }
}<commit_msg>Added promises support for the child_changed events<commit_after>/**
 This Source Code is licensed under the MIT license. If a copy of the
 MIT-license was not distributed with this file, You can obtain one at:
 http://opensource.org/licenses/mit-license.html.

 @author: Hans van den Akker (mysim1)
 @license MIT
 @copyright Bizboard, 2015

 */

import _                from 'lodash';
import FlexScrollView   from 'famous-flex/src/FlexScrollView.js';
import {Throttler}      from 'arva-utils/Throttler.js';

export class DataBoundScrollView extends FlexScrollView {

    constructor(OPTIONS = {}) {
        super(_.extend({
            autoPipeEvents: true,
            throttleDelay: 0, /* If set to 0, no delay is added in between adding items to the DataBoundScrollView. */
            dataSource: [],
            sortingDirection: 'ascending',
            flow: true,
            flowOptions: {
                spring: {               // spring-options used when transitioning between states
                    dampingRatio: 0.8,  // spring damping ratio
                    period: 1000        // duration of the animation
                },
                insertSpec: {           // render-spec used when inserting renderables
                    opacity: 0          // start opacity is 0, causing a fade-in effect,
                }
            }
        }, OPTIONS));

        this.isGrouped = this.options.groupBy != null;
        this.isDescending = this.options.sortingDirection === 'descending';
        this.throttler = new Throttler(this.options.throttleDelay, true, this);

        /* If no orderBy method is set, or it is a string field name, we set our own ordering method. */
        if (this.options.orderBy && typeof this.options.orderBy === 'string') {
            let fieldName = this.options.orderBy || 'id';
            this.options.orderBy = function (currentChild, compareChild) {
                if (this.isDescending) {
                    return currentChild[fieldName] < compareChild.data[fieldName];
                } else {
                    return currentChild[fieldName] > compareChild.data[fieldName];
                }
            }
        }

        /* If present in options.headerTemplate or options.placeholderTemplate, we build the header and placeholder elements. */
        this._addHeader();
        this._addPlaceholder();

        if (this.options.dataStore) {
            this._bindDataSource(this.options.dataStore);
        } else {
            console.log('No DataSource was set.');
        }
    }

    /**
     * Reloads the dataFilter option of the DataBoundScrollView, and verifies whether the items in the dataStore are allowed by the new filter.
     * It removes any currently visible items that aren't allowed anymore, and adds any non-visible ones that are allowed now.
     * @param {Function} newFilter New filter function to verify item visibility with.
     * @returns {void}
     */
    reloadFilter(newFilter) {
        this.options.dataFilter = newFilter;

        for (let entry of this.options.dataStore) {
            let surface = _.find(this._dataSource, (surface) => surface.dataId === entry.id);
            let alreadyExists = surface !== undefined;
            let result = newFilter(entry);

            if (result instanceof Promise) {
                result.then(function (shouldShow) { this._handleNewFilterResult(shouldShow, alreadyExists, entry); }.bind(this))
            } else {
                this._handleNewFilterResult(result, alreadyExists, entry);
            }
        }
    }

    _handleNewFilterResult(shouldShow, alreadyExists, entry) {
        if (shouldShow) {
            /* This entry should be in the view, add it if it doesn't exist yet. */
            if (!alreadyExists) {
                this._addItem(entry);
            }
        } else {
            /* This entry should not be in the view, remove if present. */
            if (alreadyExists) {
                this._removeItem(entry);
            }
        }
    }

    _findGroup(groupId) {
        return _.findIndex(this._dataSource, function (surface) {
            return surface.groupId === groupId;
        });
    }

    _findNextGroup(fromIndex) {
        let dslength = this._dataSource.length;
        for (let pos = fromIndex; pos < dslength; pos++) {
            if (this._dataSource[pos].groupId) {
                return pos;
            }
        }

        return this._dataSource.length;
    }

    _getGroupByValue(child) {
        let groupByValue = '';
        if (typeof this.options.groupBy === 'function') {
            groupByValue = this.options.groupBy(child);
        } else if (typeof this.options.groupBy === 'string') {
            groupByValue = this.options.groupBy;
        }
        return groupByValue;
    }

    _addGroupItem(groupByValue, insertIndex) {
        let newSurface = this.options.groupTemplate(groupByValue);
        newSurface.groupId = groupByValue;

        this.insert(insertIndex, newSurface);
    }

    _getInsertIndex(child, previousSiblingID = null) {
        /* By default, add item to the beginning if descending order, or at the end otherwise. */
        let firstIndex = this.header ? 1 : 0;
        let insertIndex = this.isDescending ? firstIndex : this._dataSource.length;

        /* If we have an orderBy function, find the index we should be inserting at. */
        if (this.options.orderBy && typeof this.options.orderBy === 'function') {
            let foundOrderedIndex = _.findIndex(this._dataSource, function (compareChild) {

                /* Ignore the header, placeholder, and group items. */
                if (!compareChild.isHeader && !compareChild.isPlaceholder && !compareChild.groupId) {
                    return this.options.orderBy(child, compareChild);
                }
                return false;

            }.bind(this));

            if (foundOrderedIndex !== -1) {
                insertIndex = foundOrderedIndex;
            }
        } else if (previousSiblingID) {
            /* We don't have an orderBy method, but do have a previousSiblingID we can use to find the correct insertion index. */
            let siblingIndex = _.findIndex(this._dataSource, (sibling) => sibling.dataId === previousSiblingID);
            if (siblingIndex !== -1) {
                insertIndex = siblingIndex + 1;
            }
        }

        if(this.isGrouped && insertIndex === firstIndex && this._dataSource[insertIndex] && this._dataSource[insertIndex].groupId
            && this._dataSource[insertIndex].groupId === this._getGroupByValue(child)) {
            /* We didn't get an insert index from the ordering method or a previous sibling ID, and our insert index is already occupied by
             * a group item that we need to insert after, so we'll move the index to after the group item. */
            insertIndex++;
        } else if (this.isGrouped && insertIndex > 0 && this._dataSource[insertIndex-1] && this._dataSource[insertIndex-1].groupId
            && this._dataSource[insertIndex-1].groupId !== this._getGroupByValue(child)) {
            /* The insert index is after a group item that this child does not belong to, so we'll have to decrement the index by one
             * to avoid the child being added under this group. */
            insertIndex--;
        }

        return insertIndex;
    }

    _addItem(child, previousSiblingID) {
        this._removePlaceholder();

        let insertIndex = this._getInsertIndex(child, previousSiblingID);

        /* If we're using groups, check if we need to insert a group item before this child. */
        if (this.isGrouped) {
            let groupByValue = this._getGroupByValue(child);
            let groupIndex = this._findGroup(groupByValue);
            if (groupIndex === -1) {
                /* No group of this value exists yet, so we'll need to create one. */
                this._addGroupItem(groupByValue, insertIndex);
                insertIndex++;
            }
        }

        let newSurface = this.options.itemTemplate(child);
        newSurface.dataId = child.id;
        newSurface.data = child;
        newSurface.on('click', function () {
            this._eventOutput.emit('child_click', {renderNode: newSurface, dataObject: child});
        }.bind(this));

        this.insert(insertIndex, newSurface);
    }

    _replaceItem(child) {
        let index = this._getDataSourceIndex(child.id);

        let newSurface = this.options.itemTemplate(child);
        newSurface.dataId = child.id;
        this.replace(index, newSurface);
    }

    _removeItem(child) {
        let index = _.findIndex(this._dataSource, function (surface) {
            return surface.dataId === child.id;
        });

        if (index > -1) {
            this.remove(index);
        }

        /* If we're using groups, check if we need to remove the group that this child belonged to. */
        if (this.isGrouped) {
            let groupByValue = this._getGroupByValue(child);
            let otherGroupChilds = _.findIndex(this._dataSource, function (otherChild) {
                return this._getGroupByValue(otherChild) === groupByValue;
            }.bind(this));

            if (otherGroupChilds === -1) {
                /* No more childs in this group, so let's remove the group. */
                let groupIndex = this._findGroup(groupByValue);
                if (groupIndex !== -1) {
                    this.remove(groupIndex);
                }
            }
        }

        /* The amount of items in the dataSource is subtracted with a header if present, to get the total amount of actual items in the scrollView. */
        let itemCount = this._dataSource.length - (this.header ? 1 : 0);
        if (itemCount === 0) {
            this._addPlaceholder();
        }
    }

    _moveItem(oldId, prevChildId = null) {

        let oldIndex = this._getDataSourceIndex(oldId);
        let previousSiblingIndex = this._getNextVisibleIndex(prevChildId);

        if (oldIndex !== previousSiblingIndex) {
            this.move(oldIndex, previousSiblingIndex);
        }
    }

    _addHeader() {
        if (this.options.headerTemplate && !this.header) {
            this.header = this.options.headerTemplate();
            this.header.isHeader = true;
            this.insert(0, this.header);
        }
    }

    _addPlaceholder() {
        if (this.options.placeholderTemplate && !this.placeholder) {
            let insertIndex = this.header ? 1 : 0;
            this.placeholder = this.options.placeholderTemplate();
            this.placeholder.dataId = this.placeholder.id = '_placeholder';
            this.placeholder.isPlaceholder = true;
            this.insert(insertIndex, this.placeholder);
        }
    }

    _removePlaceholder() {
        if (this.placeholder) {
            this._removeItem(this.placeholder);
            this.placeholder = null;
        }
    }

    _bindDataSource() {

        if (!this.options.dataStore || !this.options.itemTemplate) {
            console.log('Datasource and template should both be set.');
            return;
        }

        if (!this.options.template instanceof Function) {
            console.log('Template needs to be a function.');
            return;
        }

        this.options.dataStore.on('child_added', function (child, previousSiblingID) {

            if (this.options.dataFilter &&
                (typeof this.options.dataFilter === 'function')) {

                let result = this.options.dataFilter(child);

                if (result instanceof Promise) {
                    /* If the result is a Promise, show the item when that promise resolves. */
                    result.then((show) => { if (show) { this.throttler.add(() => { return this._addItem(child), previousSiblingID; }); } });
                } else if (result) {
                    /* The result is an item, so we can add it directly. */
                    this.throttler.add(() => { return this._addItem(child, previousSiblingID); });
                }
            } else {
                /* There is no dataFilter method, so we can add this child. */
                this.throttler.add(() => { return this._addItem(child, previousSiblingID); });
            }
        }.bind(this));

        this.options.dataStore.on('child_changed', function (child, previousSiblingID) {
            let changedItem = this._getDataSourceIndex(child.id);

            if (this._dataSource && changedItem < this._dataSource.length) {

                let result = this.options.dataFilter(child);

                if(result instanceof Promise){
                    result.then(function(show) {
                        if(show) {
                            this.throttler.add(() => { return this._replaceItem(child); });
                            this.throttler.add(() => { return this._moveItem(child.id, previousSiblingID); })
                        } else {
                            this._removeItem(child);
                        }
                    }.bind(this));
                }
                else if (this.options.dataFilter &&
                    typeof this.options.dataFilter === 'function' && !result) {
                    this._removeItem(child);
                } else {
                    if (changedItem === -1) {
                        this.throttler.add(() => { return this._addItem(child, previousSiblingID); });
                    } else {
                        this.throttler.add(() => { return this._replaceItem(child); });
                        this.throttler.add(() => { return this._moveItem(child.id, previousSiblingID); });
                    }
                }
            }
        }.bind(this));

        this.options.dataStore.on('child_moved', function (child, previousSibling) {
            let current = this._getDataSourceIndex(child.id);
            let previous = this._getDataSourceIndex(previousSibling);
            this.throttler.add(() => { return this._moveItem(current, previous); });
        }.bind(this));

        this.options.dataStore.on('child_removed', function (child) {
            this.throttler.add(() => { return this._removeItem(child); });
        }.bind(this));
    }

    _getDataSourceIndex(id) {
        return _.findIndex(this._dataSource, function (surface) {
            return surface.dataId === id;
        });
    }

    _getNextVisibleIndex(id) {

        let viewIndex = this._getDataSourceIndex(id);
        if (viewIndex === -1) {
            let modelIndex = _.findIndex(this.options.dataStore, function (model) {
                return model.id === id;
            });

            if (modelIndex === 0 || modelIndex === -1) {
                return this.isDescending ? this._dataSource ? this._dataSource.length - 1 : 0 : 0;
            } else {
                let nextModel = this.options.dataStore[this.isDescending ? modelIndex + 1 : modelIndex - 1];
                let nextIndex = this._getDataSourceIndex(nextModel.id);
                if (nextIndex > -1) {

                    return this.isDescending ? nextIndex === 0 ? 0 : nextIndex - 1 :
                           this._dataSource.length === nextIndex + 1 ? nextIndex : nextIndex + 1;
                } else {
                    return this._getNextVisibleIndex(nextModel.id);
                }
            }
        } else {
            return this.isDescending ? viewIndex === 0 ? 0 : viewIndex - 1 :
                   this._dataSource.length === viewIndex + 1 ? viewIndex : viewIndex + 1;
        }
    }
}<|endoftext|>


<commit_before>/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2014 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function(){

var ui = angular.module('axelor.ui');
var popoverElem = null;
var popoverTimer = null;

function canDisplayPopover(scope, details) {
	var mode = __appSettings['application.mode'];
	var tech = __appSettings['user.technical'];

	if(mode == 'prod' && !tech) {
		return details ? false : scope.field && scope.field.help;
	}

	return true;
}

function makePopover(scope, element, callback, placement) {

	var mode = __appSettings['application.mode'];
	var tech = __appSettings['user.technical'];
	var doc = $(document);

	var table = null;

	function addRow(label, text, klass) {
		if (table === null) {
			table = $('

');
		}

		var tr = $('

').appendTo(table);
		if (label) {
			$('	').text(label).appendTo(tr);
		}
		if (klass == null) {
			text = '' + text + '';
		}
		var td = $('	').html(text).addClass(klass).appendTo(tr);
		if (!label) {
			td.attr('colspan', 2);
		}
		return table;
	}

	element.popover({
		html: true,
		delay: { show: 1000, hide: 100 },
		animate: true,
		placement: function() {
			if (placement) return placement;
			var coord = $(element.get(0)).offset(),
				viewport = {height: innerHeight, width: window.innerWidth};
			if(viewport.height < (coord.top + 100))
				return 'top';
			if(coord.left > (viewport.width / 2))
				return 'left';
			return 'right';
		},
		trigger: 'manual',
		container: 'body',
		title: function() {
			return element.text();
		},
		content: function() {
			if (table) {
				table.remove();
				table = null;
			}
			callback(scope, addRow);
			if (table) return table;
			return "";
		}
	});

	element.on('mouseenter.popover', enter);
	element.on('mouseleave.popover', leave);

	function enter(e) {
		if (popoverTimer) {
			clearTimeout(popoverTimer);
		}
		popoverTimer = setTimeout(function () {
			if (popoverElem === null) {
				popoverElem = element;
				popoverElem.popover('show');
			}
			var tip = element.data('popover').$tip;
			if (tip) {
				tip.attr('tabIndex', 0);
				tip.css('outline', 'none');
			}
		}, 1000);
	}

	function leave(e) {

		if (e.ctrlKey) {
			doc.off('mousemove.popover');
			doc.on('mousemove.popover', leave);
			return;
		}

		if (popoverTimer) {
			clearTimeout(popoverTimer);
			popoverTimer = null;
		}
		if (popoverElem) {
			popoverElem.popover('hide');
			popoverElem = null;
			doc.off('mousemove.popover');
		}
	}

	function destroy() {
		if (element) {
			element.off('mouseenter.popover');
			element.off('mouseleave.popover');
			element.popover('destroy');
			element = null;
		}
		if (table) {
			table.remove();
			table = null;
		}
		doc.off('mousemove.popover');
	}

	scope.$on('$destroy', destroy);
}

ui.directive('uiTabPopover', function() {

	function getHelp(scope, addRow) {
		var tab = scope.tab || {};
		var type = tab.viewType;
		var view = _.findWhere(tab.views, {type: type});

		var viewScope = tab.$viewScope;
		if (viewScope && viewScope.schema) {
			view = viewScope.schema;
		}

		if (tab.action) {
			addRow(_t('Action'), tab.action);
		}
		if (tab.model) {
			addRow(_t('Object'), tab.model);
		}
		if (tab.domain) {
			addRow(_t('Domain'), tab.domain);
		}
		if (view && view.name) {
			addRow(_t('View'), view.name);
		}
	}

	return function (scope, element, attrs) {
		if(canDisplayPopover(scope, true)) {
			return makePopover(scope, element, getHelp, 'bottom');
		}
	};
});

ui.directive('uiHelpPopover', function() {

	function getHelp(scope, addRow) {

		var field = scope.field;
		var text = field.help;
		if (text) {
			text = text.replace(/\\n/g, '
');
			addRow(null, text, 'help-text');
		}

		if (text) {
			addRow(null, '
', 'help-text');
		}

		if(!canDisplayPopover(scope, true)) {
			return;
		}

		var model = scope._model;
		if (model === field.target) {
			model = scope._parentModel || scope.$parent._model;
		}

		addRow(_t('Object'), model);
		addRow(_t('Field Name'), field.name);
		addRow(_t('Field Type'), field.serverType);

		if (field.type === 'text') {
			return;
		}

		if (field.domain) {
			addRow(_t('Filter'), field.domain);
		}

		if (field.target) {
			addRow(_t('Reference'), field.target);
		}

		var value = scope.$eval('$$original.' + field.name);
		if (value && /-one$/.test(field.serverType)) {
			value = _.compact([value.id, value[field.targetName]]).join(',');
			value = '(' + value + ')';
		}
		if (value && field.type === "password") {
			value = _.str.repeat('*', value.length);
		}
		if (value && /^(string|image|binary)$/.test(field.type)) {
			var length = value.length;
			value = _.first(value, 50);
			if (length > 50) {
				value.push('...');
			}
			value = value.join('');
		}
		if (value && /(panel-related|one-to-many|many-to-many)/.test(field.serverType)) {
			var length = value.length;
			value = _.first(value, 5);
			value = _.map(value, function(v){
				return v.id;
			});
			if (length > 5) {
				value.push('...');
			}
			value = value.join(', ');
		}

		addRow(_t('Orig. Value'), value);
	}

	function doLink(scope, element, attrs) {
		var field = scope.field;
		if (field == null) {
			return;
		}
		if(canDisplayPopover(scope, false)) {
			makePopover(scope, element, getHelp);
		}
	};

	return function(scope, element, attrs) {
		var field = scope.field;
		if (!_.isEmpty(field)) {
			return doLink(scope, element, attrs);
		}
		var unwatch = scope.$watch('field', function(field, old) {
			if (!field) {
				return;
			}
			unwatch();
			doLink(scope, element, attrs);
		}, true);
	};
});

/**
 * The Label widget.
 *
 */
ui.formItem('Label', {

	css: 'label-item',
	cellCss: 'form-label',

	transclude: true,

	link: function(scope, element, attrs) {
		var field = scope.field;
		if (field && field.required) {
			element.addClass('required');
		}
	},

	template: '?'
});

/**
 * The Spacer widget.
 *
 */
ui.formItem('Spacer', {
	css: 'spacer-item',
	template: '

'
});

/**
 * The Separator widget.
 *
 */
ui.formItem('Separator', {
	css: 'separator-item',
	showTitle: false,
	template: '
{{field.title}}

'
});

/**
 * The Static Text widget.
 *
 */
ui.formItem('Static', {
	css: 'static-item',
	transclude: true,
	template: ''
});

/**
 * The button widget.
 */
ui.formItem('Button', {
	css: 'button-item',
	transclude: true,
	link: function(scope, element, attrs, model) {
		var field = scope.field || {};

		var icon = field.icon || "";
		var iconHover = field.iconHover || "";

		var isIcon = icon.indexOf('fa-') === 0;

		if (isIcon || icon) {
			element.prepend(' ');
		}

		if (isIcon) {
			var e = $('').addClass('fa').addClass(icon).prependTo(element);
			if (iconHover) {
				e.hover(function() {
					$(this).removeClass(icon).addClass(iconHover);
				}, function() {
					$(this).removeClass(iconHover).addClass(icon);
				});
			}
		} else if (icon) {
			$('').attr('src', icon).prependTo(element);
		}

		if (!field.title) {
			element.addClass("button-icon");
		}

		if (_.isString(field.link)) {
			element.removeClass('btn');
			element.attr("href", field.link);
		}

		element.tooltip({
			html: true,
			title: function() {
				if (field.help) {
					return field.help;
				}
				if (element.innerWidth() < element[0].scrollWidth) {
					return field.title;
				}
			},
			delay: { show: 1000, hide: 100 },
			container: 'body'
		});

		element.on("click", function(e) {

			if (scope.isReadonlyExclusive() || element.hasClass('disabled')) {
				return;
			}

			function enable() {
				scope.ajaxStop(function () {
					setDisabled(false);
				}, 100);
			}

			function setEnable(p) {
				if (p && p.then) {
					p.then(enable, enable);
				} else {
					scope.ajaxStop(enable, 500);
				}
			}

			function doClick() {
				setEnable(scope.fireAction("onClick"));
			}

			setDisabled(true);

			if (scope.waitForActions) {
				return scope.waitForActions(doClick);
			}
			return doClick();
		});

		function setDisabled(disabled) {
			if (disabled || disabled === undefined) {
				return element.addClass("disabled").attr('tabindex', -1);
			}
			return element.removeClass("disabled").removeAttr('tabindex');
		}

		var readonlySet = false;
		scope.$watch('isReadonlyExclusive()', function(readonly, old) {
			if (readonly === old && readonlySet) return;
			readonlySet = true;
			return setDisabled(readonly);
		});

		scope.$watch('attr("title")', function(title, old) {
			if (!title || title === old) return;
			if (element.is('button')) {
				return element.html(title);
			}
			element.children('.btn-text').html(title);
		});
	},
	template: ''+
		''+
	''
});

ui.formItem('ToolButton', 'Button', {

	getViewDef: function(element) {
		return this.btn;
	},

	link: function(scope, element, attrs) {
		this._super.apply(this, arguments);
		var field = scope.field;
		if (field == null) {
			return;
		}

		scope.title = field.title;

		scope.btn.isHidden = function() {
			return scope.isHidden();
		};
	},

	template: '<button class="btn" ui-show="!isHidden()" name="{{btn.name}}" ui-actions ui-widget-states>{{title}}</button>'
});

})(this);
<commit_msg>Fix button widget readonly issue<commit_after>/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2014 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function(){

var ui = angular.module('axelor.ui');
var popoverElem = null;
var popoverTimer = null;

function canDisplayPopover(scope, details) {
	var mode = __appSettings['application.mode'];
	var tech = __appSettings['user.technical'];

	if(mode == 'prod' && !tech) {
		return details ? false : scope.field && scope.field.help;
	}

	return true;
}

function makePopover(scope, element, callback, placement) {

	var mode = __appSettings['application.mode'];
	var tech = __appSettings['user.technical'];
	var doc = $(document);

	var table = null;

	function addRow(label, text, klass) {
		if (table === null) {
			table = $('

');
		}

		var tr = $('

').appendTo(table);
		if (label) {
			$('	').text(label).appendTo(tr);
		}
		if (klass == null) {
			text = '' + text + '';
		}
		var td = $('	').html(text).addClass(klass).appendTo(tr);
		if (!label) {
			td.attr('colspan', 2);
		}
		return table;
	}

	element.popover({
		html: true,
		delay: { show: 1000, hide: 100 },
		animate: true,
		placement: function() {
			if (placement) return placement;
			var coord = $(element.get(0)).offset(),
				viewport = {height: innerHeight, width: window.innerWidth};
			if(viewport.height < (coord.top + 100))
				return 'top';
			if(coord.left > (viewport.width / 2))
				return 'left';
			return 'right';
		},
		trigger: 'manual',
		container: 'body',
		title: function() {
			return element.text();
		},
		content: function() {
			if (table) {
				table.remove();
				table = null;
			}
			callback(scope, addRow);
			if (table) return table;
			return "";
		}
	});

	element.on('mouseenter.popover', enter);
	element.on('mouseleave.popover', leave);

	function enter(e) {
		if (popoverTimer) {
			clearTimeout(popoverTimer);
		}
		popoverTimer = setTimeout(function () {
			if (popoverElem === null) {
				popoverElem = element;
				popoverElem.popover('show');
			}
			var tip = element.data('popover').$tip;
			if (tip) {
				tip.attr('tabIndex', 0);
				tip.css('outline', 'none');
			}
		}, 1000);
	}

	function leave(e) {

		if (e.ctrlKey) {
			doc.off('mousemove.popover');
			doc.on('mousemove.popover', leave);
			return;
		}

		if (popoverTimer) {
			clearTimeout(popoverTimer);
			popoverTimer = null;
		}
		if (popoverElem) {
			popoverElem.popover('hide');
			popoverElem = null;
			doc.off('mousemove.popover');
		}
	}

	function destroy() {
		if (element) {
			element.off('mouseenter.popover');
			element.off('mouseleave.popover');
			element.popover('destroy');
			element = null;
		}
		if (table) {
			table.remove();
			table = null;
		}
		doc.off('mousemove.popover');
	}

	scope.$on('$destroy', destroy);
}

ui.directive('uiTabPopover', function() {

	function getHelp(scope, addRow) {
		var tab = scope.tab || {};
		var type = tab.viewType;
		var view = _.findWhere(tab.views, {type: type});

		var viewScope = tab.$viewScope;
		if (viewScope && viewScope.schema) {
			view = viewScope.schema;
		}

		if (tab.action) {
			addRow(_t('Action'), tab.action);
		}
		if (tab.model) {
			addRow(_t('Object'), tab.model);
		}
		if (tab.domain) {
			addRow(_t('Domain'), tab.domain);
		}
		if (view && view.name) {
			addRow(_t('View'), view.name);
		}
	}

	return function (scope, element, attrs) {
		if(canDisplayPopover(scope, true)) {
			return makePopover(scope, element, getHelp, 'bottom');
		}
	};
});

ui.directive('uiHelpPopover', function() {

	function getHelp(scope, addRow) {

		var field = scope.field;
		var text = field.help;
		if (text) {
			text = text.replace(/\\n/g, '
');
			addRow(null, text, 'help-text');
		}

		if (text) {
			addRow(null, '
', 'help-text');
		}

		if(!canDisplayPopover(scope, true)) {
			return;
		}

		var model = scope._model;
		if (model === field.target) {
			model = scope._parentModel || scope.$parent._model;
		}

		addRow(_t('Object'), model);
		addRow(_t('Field Name'), field.name);
		addRow(_t('Field Type'), field.serverType);

		if (field.type === 'text') {
			return;
		}

		if (field.domain) {
			addRow(_t('Filter'), field.domain);
		}

		if (field.target) {
			addRow(_t('Reference'), field.target);
		}

		var value = scope.$eval('$$original.' + field.name);
		if (value && /-one$/.test(field.serverType)) {
			value = _.compact([value.id, value[field.targetName]]).join(',');
			value = '(' + value + ')';
		}
		if (value && field.type === "password") {
			value = _.str.repeat('*', value.length);
		}
		if (value && /^(string|image|binary)$/.test(field.type)) {
			var length = value.length;
			value = _.first(value, 50);
			if (length > 50) {
				value.push('...');
			}
			value = value.join('');
		}
		if (value && /(panel-related|one-to-many|many-to-many)/.test(field.serverType)) {
			var length = value.length;
			value = _.first(value, 5);
			value = _.map(value, function(v){
				return v.id;
			});
			if (length > 5) {
				value.push('...');
			}
			value = value.join(', ');
		}

		addRow(_t('Orig. Value'), value);
	}

	function doLink(scope, element, attrs) {
		var field = scope.field;
		if (field == null) {
			return;
		}
		if(canDisplayPopover(scope, false)) {
			makePopover(scope, element, getHelp);
		}
	};

	return function(scope, element, attrs) {
		var field = scope.field;
		if (!_.isEmpty(field)) {
			return doLink(scope, element, attrs);
		}
		var unwatch = scope.$watch('field', function(field, old) {
			if (!field) {
				return;
			}
			unwatch();
			doLink(scope, element, attrs);
		}, true);
	};
});

/**
 * The Label widget.
 *
 */
ui.formItem('Label', {

	css: 'label-item',
	cellCss: 'form-label',

	transclude: true,

	link: function(scope, element, attrs) {
		var field = scope.field;
		if (field && field.required) {
			element.addClass('required');
		}
	},

	template: '?'
});

/**
 * The Spacer widget.
 *
 */
ui.formItem('Spacer', {
	css: 'spacer-item',
	template: '

'
});

/**
 * The Separator widget.
 *
 */
ui.formItem('Separator', {
	css: 'separator-item',
	showTitle: false,
	template: '
{{field.title}}

'
});

/**
 * The Static Text widget.
 *
 */
ui.formItem('Static', {
	css: 'static-item',
	transclude: true,
	template: ''
});

/**
 * The button widget.
 */
ui.formItem('Button', {
	css: 'button-item',
	transclude: true,
	link: function(scope, element, attrs, model) {
		var field = scope.field || {};

		var icon = field.icon || "";
		var iconHover = field.iconHover || "";

		var isIcon = icon.indexOf('fa-') === 0;

		if (isIcon || icon) {
			element.prepend(' ');
		}

		if (isIcon) {
			var e = $('').addClass('fa').addClass(icon).prependTo(element);
			if (iconHover) {
				e.hover(function() {
					$(this).removeClass(icon).addClass(iconHover);
				}, function() {
					$(this).removeClass(iconHover).addClass(icon);
				});
			}
		} else if (icon) {
			$('').attr('src', icon).prependTo(element);
		}

		if (!field.title) {
			element.addClass("button-icon");
		}

		if (_.isString(field.link)) {
			element.removeClass('btn');
			element.attr("href", field.link);
		}

		element.tooltip({
			html: true,
			title: function() {
				if (field.help) {
					return field.help;
				}
				if (element.innerWidth() < element[0].scrollWidth) {
					return field.title;
				}
			},
			delay: { show: 1000, hide: 100 },
			container: 'body'
		});

		element.on("click", function(e) {

			if (scope.isReadonlyExclusive() || element.hasClass('disabled')) {
				return;
			}

			function enable() {
				scope.ajaxStop(function () {
					setDisabled(scope.isReadonly());
				}, 100);
			}

			function setEnable(p) {
				if (p && p.then) {
					p.then(enable, enable);
				} else {
					scope.ajaxStop(enable, 500);
				}
			}

			function doClick() {
				setEnable(scope.fireAction("onClick"));
			}

			setDisabled(true);

			if (scope.waitForActions) {
				return scope.waitForActions(doClick);
			}
			return doClick();
		});

		function setDisabled(disabled) {
			if (disabled || disabled === undefined) {
				return element.addClass("disabled").attr('tabindex', -1);
			}
			return element.removeClass("disabled").removeAttr('tabindex');
		}

		var readonlySet = false;
		scope.$watch('isReadonlyExclusive()', function(readonly, old) {
			if (readonly === old && readonlySet) return;
			readonlySet = true;
			return setDisabled(readonly);
		});

		scope.$watch('attr("title")', function(title, old) {
			if (!title || title === old) return;
			if (element.is('button')) {
				return element.html(title);
			}
			element.children('.btn-text').html(title);
		});
	},
	template: ''+
		''+
	''
});

ui.formItem('ToolButton', 'Button', {

	getViewDef: function(element) {
		return this.btn;
	},

	link: function(scope, element, attrs) {
		this._super.apply(this, arguments);
		var field = scope.field;
		if (field == null) {
			return;
		}

		scope.title = field.title;

		scope.btn.isHidden = function() {
			return scope.isHidden();
		};
	},

	template: '<button class="btn" ui-show="!isHidden()" name="{{btn.name}}" ui-actions ui-widget-states>{{title}}</button>'
});

})(this);
<|endoftext|>


<commit_before>'use strict';

if (!process.env.SSH_PRIVATE_KEY) {
  throw new Error('Error: Specify SSH_PRIVATE_KEY in environment');
}
const SSH_PRIVATE_KEY = process.env.SSH_PRIVATE_KEY;

if (!process.env.SSH_PUBLIC_KEY) {
  throw new Error('Error: Specify SSH_PUBLIC_KEY in environment');
}
const SSH_PUBLIC_KEY = process.env.SSH_PUBLIC_KEY;

if (!process.env.SSH_KEY_PASSPHRASE) {
  throw new Error('Error: Specify SSH_KEY_PASSPHRASE in environment');
}

const SSH_KEY_PASSPHRASE = process.env.SSH_KEY_PASSPHRASE;

if (!process.env.VALID_RELEASE_USERS) {
  throw new Error('Error: Specify comma separated list of VALID_RELEASE_USERS in environment');
}

const VALID_RELEASE_USERS = process.env.VALID_RELEASE_USERS;
const releaseProject = 'release-project';
const releaseType = 'release-type';

const uniqueid = require('uniqueid');
const getUniqueId = uniqueid(process.pid);
const fs = require('mz/fs');
const Git = require('nodegit');
const Github = require('../lib/github');
const cheeseName = require('cheese-name');
const rmdir = require('rimraf');
const mkdirp = require('mkdirp');
const moment = require('moment');
const uniqueReleaseName = require('../lib/uniqueReleaseName');
const { generateReleaseNotes } = require('generate-github-release-notes');
const incrementPackageVersion = require('../lib/incrementPackageVersion');
const { releaseList } = require('../lib/releaseList');

const cloneRepository = async (owner, repo, target) => {
  const url = `git@github.com:${owner}/${repo}`;
  console.log(`Cloning ${url} into ${target}`);
  const opts = {
    fetchOpts: {
      callbacks: {
        credentials: async (url, userName) => {
          return await Git.Cred.sshKeyMemoryNew(
            userName,
            SSH_PUBLIC_KEY,
            SSH_PRIVATE_KEY,
            SSH_KEY_PASSPHRASE
          );
        }
      }
    }
  };

  return await Git.Clone(url, target, opts);
};

const commitAndTag = async (dir, name, releaseName) => {
  const message = `${name} ${releaseName}`;
  console.log(`Commit and tag ${message} in ${dir}`);

  // From the examples at https://github.com/nodegit/nodegit/blob/master/examples/
  const repository = await Git.Repository.open(dir);
  const index = await repository.refreshIndex();
  await index.addAll();
  await index.write();
  const oid = await index.writeTree();
  const HEAD = await Git.Reference.nameToId(repository, 'HEAD');
  const parent = await repository.getCommit(HEAD);

  const now = moment().utc().unix();
  const author = Git.Signature.create('Zorgbort', 'info@iliosproject.org', now, 0);
  const commit = await repository.createCommit('HEAD', author, author, message, oid, [parent]);

  console.log(`Create tag ${name} "${message}"`);
  await repository.createTag(commit, name, message);
  const opts = {
    callbacks: {
      credentials: async (url, userName) => {
        return await Git.Cred.sshKeyMemoryNew(
          userName,
          SSH_PUBLIC_KEY,
          SSH_PRIVATE_KEY,
          SSH_KEY_PASSPHRASE
        );
      }
    }
  };
  const remote = await Git.Remote.lookup(repository, 'origin');
  console.log('Pushing new tag to origin');
  return await remote.push([
    'refs/heads/master:refs/heads/master',
    `refs/tags/${name}:refs/tags/${name}`,
  ], opts);
};

const createTempDirectory = async (name) => {
  console.log(`Creating temporary directory for ${name}`);
  const appRoot = require('app-root-path');
  const dir = `${appRoot}/tmp/${name}/` + getUniqueId();
  const exists = await fs.exists(dir);
  if (exists) {
    throw new Error(`Tried to create directory, but it already exists: ${dir}`);
  }
  mkdirp(dir);
  console.log(`${dir} created`);

  return dir;
};

const removeTempDirectory = async (name) => {
  const dir = `/tmp/${name}/` + getUniqueId();
  console.log(`Removing temporary directory ${dir}`);

  return await rmdir(dir, err => {
    console.error(err);
  });
};

const releaseAndTag = async (owner, repo, releaseType, namer) => {
  console.log(`Release and tag ${owner}/${repo} as ${releaseType}`);
  const dir = await createTempDirectory(repo);
  await cloneRepository(owner, repo, dir);

  const { nextVersion, currentVersion } = await incrementPackageVersion(dir, releaseType);
  const version = `v${nextVersion}`;
  console.log(`${nextVersion} will be called ${version}`);
  const releaseName = await uniqueReleaseName(nextVersion, releaseList, namer, owner, repo);
  const releaseNotes = await generateReleaseNotes(Github, owner, repo, `v${currentVersion}`, version);
  await commitAndTag(dir, version, releaseName);
  await removeTempDirectory(repo);
  console.log(`Creating release for ${owner}/${repo} at ${version} as ${releaseName}`);

  const release = await Github.repos.createRelease({
    owner,
    repo,
    tag_name: version,
    name: releaseName,
    body: releaseNotes,
    draft: false,
    prerelease: false,
  });

  return {
    version,
    releaseName,
    releaseUrl: release.data.html_url
  };
};

const validateRequestAndRelease = async (bot, message, owner, repo, releaseType, namer) => {
  const user = message.user;

  const validUsers = VALID_RELEASE_USERS.split(',');
  if (validUsers.includes(user)) {
    try {
      return await releaseAndTag(owner, repo, releaseType, namer);
    } catch (e) {
      bot.reply(message, `Error: ${e.message} (stack trace in logs)`);
      console.error(e);
    }
  } else {
    bot.api.users.info({user}, (err, resp) => {
      if (err) {
        bot.reply(message, `Error: ${err.message} (stack trace in logs)`);
        console.error(err);
      }
      let bestName = 'Dave';
      if (resp.ok) {
        if (resp.user.profile.first_name) {
          bestName = resp.user.profile.first_name;
        } else {
          bestName = resp.user.name;
        }
      }
      bot.reply(message, `:no_entry: I'm Sorry, ${bestName}, I'm afraid Zorgbort can't do that.`);
    });

  }
};

const createActionReply = (text, callback_id, actions) => {
  return {
    text: "Let's release some code!",
    attachments: [
      {
        text,
        callback_id,
        attachment_type: 'default',
        color: '#84c444',
        actions
      }
    ]
  };
};

const getPersonFromMessage = (message) => {
  let person = '<@' + message.user + '>';
  if (message.channel[0] == 'D') {
    person = 'You';
  }

  return person;
};

const startRelease = async (bot, message) => {
  bot.reply(message, createActionReply('Which Project?', releaseProject, [
    {
      'name': 'frontend',
      'text': 'Ilios Frontend',
      'value': 'frontend',
      'type': 'button',
    },
    {
      'name': 'common',
      'text': 'Ilios Common Addon',
      'value': 'common',
      'type': 'button',
    },
    {
      'name': 'lti-server',
      'text': 'Ilios LTI Server',
      'value': 'lti-server',
      'type': 'button',
    },
    {
      'name': 'lti-dashboard',
      'text': 'Ilios LTI Dashboard',
      'value': 'lti-dashboard',
      'type': 'button',
    },
  ]));
};

const chooseReleaseType = async (bot, message) => {
  if (message.callback_id === releaseProject) {
    const selection = message.actions[0].value;
    const person = getPersonFromMessage(message);
    const reply = createActionReply('What kind of change is this?', releaseType, [
      {
        name: 'major',
        text: 'Breaking / API Bump',
        value: selection + '$$major',
        type: 'button',
        style: 'danger',
      },
      {
        name: 'minor',
        text: 'Feature',
        value: selection + '$$minor',
        type: 'button',
      },
      {
        name: 'patch',
        text: 'Bugfix',
        value: selection + '$$patch',
        type: 'button',
        style: 'primary',
      },
    ]);
    const text = person + ' chose to release ' + selection;
    reply.attachments.unshift({ text, color: '#84c444' });
    bot.replyInteractive(message, reply);
  }
};

const doRelease = async (bot, message) => {
  if (message.callback_id === releaseType) {
    const [selection, npmType] = message.actions[0].value.split('$$');
    const person = getPersonFromMessage(message);
    const text = `${person} chose to release a ${npmType} version of ${selection}`;
    const reply = createActionReply(text, false, []);
    reply.attachments.push({
      text: ":robot_face: Ok, I'm buildng your release now...",
      color: '#ffc339',
    });
    bot.replyInteractive(message, reply);
    const owner = 'ilios';
    let namer = version => `${version}`;
    switch (selection) {
    case 'frontend':
      namer = cheeseName;
      break;
    case 'common':
      namer = version => `Ilios Common ${version}`;
      break;
    case 'lti-server':
      namer = version => `LTI Server ${version}`;
      break;
    case 'lti-dashboard':
      namer = version => `LTI Dashboard ${version}`;
      break;
    }
    const result = await validateRequestAndRelease(bot, message, owner, selection, npmType, namer);

    const finishedReply = createActionReply(text, false, []);
    finishedReply.attachments.push({
      text: `:rocket: ${owner}:${selection} ${result.releaseName} has been released. :tada:`,
      color: '#84c444',
    });
    finishedReply.attachments.push({
      text: `Release notes at ${result.releaseUrl}`,
      color: '#84c444',
    });
    bot.replyInteractive(message, finishedReply);
  }
};

module.exports = bot => {
  bot.hears(['start release', 'release'], ['direct_message', 'direct_mention', 'mention'], startRelease);
  bot.on('interactive_message_callback', chooseReleaseType);
  bot.on('interactive_message_callback', doRelease);
};
<commit_msg>Release simple charts<commit_after>'use strict';

if (!process.env.SSH_PRIVATE_KEY) {
  throw new Error('Error: Specify SSH_PRIVATE_KEY in environment');
}
const SSH_PRIVATE_KEY = process.env.SSH_PRIVATE_KEY;

if (!process.env.SSH_PUBLIC_KEY) {
  throw new Error('Error: Specify SSH_PUBLIC_KEY in environment');
}
const SSH_PUBLIC_KEY = process.env.SSH_PUBLIC_KEY;

if (!process.env.SSH_KEY_PASSPHRASE) {
  throw new Error('Error: Specify SSH_KEY_PASSPHRASE in environment');
}

const SSH_KEY_PASSPHRASE = process.env.SSH_KEY_PASSPHRASE;

if (!process.env.VALID_RELEASE_USERS) {
  throw new Error('Error: Specify comma separated list of VALID_RELEASE_USERS in environment');
}

const VALID_RELEASE_USERS = process.env.VALID_RELEASE_USERS;
const releaseProject = 'release-project';
const releaseType = 'release-type';

const uniqueid = require('uniqueid');
const getUniqueId = uniqueid(process.pid);
const fs = require('mz/fs');
const Git = require('nodegit');
const Github = require('../lib/github');
const cheeseName = require('cheese-name');
const rmdir = require('rimraf');
const mkdirp = require('mkdirp');
const moment = require('moment');
const uniqueReleaseName = require('../lib/uniqueReleaseName');
const { generateReleaseNotes } = require('generate-github-release-notes');
const incrementPackageVersion = require('../lib/incrementPackageVersion');
const { releaseList } = require('../lib/releaseList');

const cloneRepository = async (owner, repo, target) => {
  const url = `git@github.com:${owner}/${repo}`;
  console.log(`Cloning ${url} into ${target}`);
  const opts = {
    fetchOpts: {
      callbacks: {
        credentials: async (url, userName) => {
          return await Git.Cred.sshKeyMemoryNew(
            userName,
            SSH_PUBLIC_KEY,
            SSH_PRIVATE_KEY,
            SSH_KEY_PASSPHRASE
          );
        }
      }
    }
  };

  return await Git.Clone(url, target, opts);
};

const commitAndTag = async (dir, name, releaseName) => {
  const message = `${name} ${releaseName}`;
  console.log(`Commit and tag ${message} in ${dir}`);

  // From the examples at https://github.com/nodegit/nodegit/blob/master/examples/
  const repository = await Git.Repository.open(dir);
  const index = await repository.refreshIndex();
  await index.addAll();
  await index.write();
  const oid = await index.writeTree();
  const HEAD = await Git.Reference.nameToId(repository, 'HEAD');
  const parent = await repository.getCommit(HEAD);

  const now = moment().utc().unix();
  const author = Git.Signature.create('Zorgbort', 'info@iliosproject.org', now, 0);
  const commit = await repository.createCommit('HEAD', author, author, message, oid, [parent]);

  console.log(`Create tag ${name} "${message}"`);
  await repository.createTag(commit, name, message);
  const opts = {
    callbacks: {
      credentials: async (url, userName) => {
        return await Git.Cred.sshKeyMemoryNew(
          userName,
          SSH_PUBLIC_KEY,
          SSH_PRIVATE_KEY,
          SSH_KEY_PASSPHRASE
        );
      }
    }
  };
  const remote = await Git.Remote.lookup(repository, 'origin');
  console.log('Pushing new tag to origin');
  return await remote.push([
    'refs/heads/master:refs/heads/master',
    `refs/tags/${name}:refs/tags/${name}`,
  ], opts);
};

const createTempDirectory = async (name) => {
  console.log(`Creating temporary directory for ${name}`);
  const appRoot = require('app-root-path');
  const dir = `${appRoot}/tmp/${name}/` + getUniqueId();
  const exists = await fs.exists(dir);
  if (exists) {
    throw new Error(`Tried to create directory, but it already exists: ${dir}`);
  }
  mkdirp(dir);
  console.log(`${dir} created`);

  return dir;
};

const removeTempDirectory = async (name) => {
  const dir = `/tmp/${name}/` + getUniqueId();
  console.log(`Removing temporary directory ${dir}`);

  return await rmdir(dir, err => {
    console.error(err);
  });
};

const releaseAndTag = async (owner, repo, releaseType, namer) => {
  console.log(`Release and tag ${owner}/${repo} as ${releaseType}`);
  const dir = await createTempDirectory(repo);
  await cloneRepository(owner, repo, dir);

  const { nextVersion, currentVersion } = await incrementPackageVersion(dir, releaseType);
  const version = `v${nextVersion}`;
  console.log(`${nextVersion} will be called ${version}`);
  const releaseName = await uniqueReleaseName(nextVersion, releaseList, namer, owner, repo);
  const releaseNotes = await generateReleaseNotes(Github, owner, repo, `v${currentVersion}`, version);
  await commitAndTag(dir, version, releaseName);
  await removeTempDirectory(repo);
  console.log(`Creating release for ${owner}/${repo} at ${version} as ${releaseName}`);

  const release = await Github.repos.createRelease({
    owner,
    repo,
    tag_name: version,
    name: releaseName,
    body: releaseNotes,
    draft: false,
    prerelease: false,
  });

  return {
    version,
    releaseName,
    releaseUrl: release.data.html_url
  };
};

const validateRequestAndRelease = async (bot, message, owner, repo, releaseType, namer) => {
  const user = message.user;

  const validUsers = VALID_RELEASE_USERS.split(',');
  if (validUsers.includes(user)) {
    try {
      return await releaseAndTag(owner, repo, releaseType, namer);
    } catch (e) {
      bot.reply(message, `Error: ${e.message} (stack trace in logs)`);
      console.error(e);
    }
  } else {
    bot.api.users.info({user}, (err, resp) => {
      if (err) {
        bot.reply(message, `Error: ${err.message} (stack trace in logs)`);
        console.error(err);
      }
      let bestName = 'Dave';
      if (resp.ok) {
        if (resp.user.profile.first_name) {
          bestName = resp.user.profile.first_name;
        } else {
          bestName = resp.user.name;
        }
      }
      bot.reply(message, `:no_entry: I'm Sorry, ${bestName}, I'm afraid Zorgbort can't do that.`);
    });

  }
};

const createActionReply = (text, callback_id, actions) => {
  return {
    text: "Let's release some code!",
    attachments: [
      {
        text,
        callback_id,
        attachment_type: 'default',
        color: '#84c444',
        actions
      }
    ]
  };
};

const getPersonFromMessage = (message) => {
  let person = '<@' + message.user + '>';
  if (message.channel[0] == 'D') {
    person = 'You';
  }

  return person;
};

const startRelease = async (bot, message) => {
  bot.reply(message, createActionReply('Which Project?', releaseProject, [
    {
      'name': 'frontend',
      'text': 'Ilios Frontend',
      'value': 'frontend',
      'type': 'button',
    },
    {
      'name': 'common',
      'text': 'Ilios Common Addon',
      'value': 'common',
      'type': 'button',
    },
    {
      'name': 'lti-server',
      'text': 'Ilios LTI Server',
      'value': 'lti-server',
      'type': 'button',
    },
    {
      'name': 'lti-dashboard',
      'text': 'Ilios LTI Dashboard',
      'value': 'lti-dashboard',
      'type': 'button',
    },
    {
      'name': 'simple-charts',
      'text': 'Ember Simple Charts',
      'value': 'simple-charts',
      'type': 'button',
    },
  ]));
};

const chooseReleaseType = async (bot, message) => {
  if (message.callback_id === releaseProject) {
    const selection = message.actions[0].value;
    const person = getPersonFromMessage(message);
    const reply = createActionReply('What kind of change is this?', releaseType, [
      {
        name: 'major',
        text: 'Breaking / API Bump',
        value: selection + '$$major',
        type: 'button',
        style: 'danger',
      },
      {
        name: 'minor',
        text: 'Feature',
        value: selection + '$$minor',
        type: 'button',
      },
      {
        name: 'patch',
        text: 'Bugfix',
        value: selection + '$$patch',
        type: 'button',
        style: 'primary',
      },
    ]);
    const text = person + ' chose to release ' + selection;
    reply.attachments.unshift({ text, color: '#84c444' });
    bot.replyInteractive(message, reply);
  }
};

const doRelease = async (bot, message) => {
  if (message.callback_id === releaseType) {
    const [selection, npmType] = message.actions[0].value.split('$$');
    const person = getPersonFromMessage(message);
    const text = `${person} chose to release a ${npmType} version of ${selection}`;
    const reply = createActionReply(text, false, []);
    reply.attachments.push({
      text: ":robot_face: Ok, I'm buildng your release now...",
      color: '#ffc339',
    });
    bot.replyInteractive(message, reply);
    const owner = 'ilios';
    let namer = version => `${version}`;
    switch (selection) {
    case 'frontend':
      namer = cheeseName;
      break;
    case 'common':
      namer = version => `Ilios Common ${version}`;
      break;
    case 'lti-server':
      namer = version => `LTI Server ${version}`;
      break;
    case 'lti-dashboard':
      namer = version => `LTI Dashboard ${version}`;
      break;
    }
    const result = await validateRequestAndRelease(bot, message, owner, selection, npmType, namer);

    const finishedReply = createActionReply(text, false, []);
    finishedReply.attachments.push({
      text: `:rocket: ${owner}:${selection} ${result.releaseName} has been released. :tada:`,
      color: '#84c444',
    });
    finishedReply.attachments.push({
      text: `Release notes at ${result.releaseUrl}`,
      color: '#84c444',
    });
    bot.replyInteractive(message, finishedReply);
  }
};

module.exports = bot => {
  bot.hears(['start release', 'release'], ['direct_message', 'direct_mention', 'mention'], startRelease);
  bot.on('interactive_message_callback', chooseReleaseType);
  bot.on('interactive_message_callback', doRelease);
};
<|endoftext|>


<commit_before>import React, { Component } from 'react';

/* A DiffLine contains metadata about a line in a DiffBlock */
export function DiffLine(props) {
  // Information about the line itself
  var c = props.change
  var cov = props.code_cov_info
  // Added, deleted or unchanged line
  var change_type = props.change.type
  // CSS tr and td classes
  var [row_class, cov_status_class] = ['nolinechange', 'nocovchange']
  var row_id = props.id
  // Cell contents
  var cov_status = ' ' // We need blank string to respect width value of cell
  var [old_line_number, new_line_number] = ['', '']

  if (change_type === 'add') {
    // Added line - <cov_status> | <blank> | <new line number>
    row_class = change_type
    cov_status_class = 'miss' // Let's start assuming a miss
    if (cov) {
      let state = cov.changes.find(line_cov_info =>
        (line_cov_info.new_line === c.ln) && (line_cov_info.coverage === 'Y')
      )
      cov_status_class = state ? 'hit' : 'miss'
    }
    new_line_number = c.ln
  } else if (change_type === 'del') {
    // Removed line - <blank> | <old line number> | <blank>
    row_class = change_type
    old_line_number = c.ln
  } else {
    // Unchanged line - <blank> | <old line number> | <blank>
    row_class = change_type
    old_line_number = c.ln1
    if (old_line_number !== c.ln2) {
      new_line_number = c.ln2
    }
  }
  return (

      {cov_status}
      {old_line_number}
      {new_line_number}

{c.content}

  );
}
<commit_msg>feat: line coverage with ? should not be marked as miss<commit_after>import React, { Component } from 'react';

/* A DiffLine contains metadata about a line in a DiffBlock */
export function DiffLine(props) {
  // Information about the line itself
  var c = props.change
  var cov = props.code_cov_info
  // Added, deleted or unchanged line
  var change_type = props.change.type
  // CSS tr and td classes
  var [row_class, cov_status_class] = ['nolinechange', 'nocovchange']
  var row_id = props.id
  // Cell contents
  var cov_status = ' ' // We need blank string to respect width value of cell
  var [old_line_number, new_line_number] = ['', '']

  if (change_type === 'add') {
    // Added line - <cov_status> | <blank> | <new line number>
    row_class = change_type
    cov_status_class = 'miss' // Let's start assuming a miss
    if (cov) {
      let { coverage } = cov.changes.find(line_cov_info =>
        (line_cov_info.new_line === c.ln))
      cov_status_class = (coverage === 'Y') ? 'hit' :
                         (coverage === 'N') ? 'miss': 'undefined'
    }
    new_line_number = c.ln
  } else if (change_type === 'del') {
    // Removed line - <blank> | <old line number> | <blank>
    row_class = change_type
    old_line_number = c.ln
  } else {
    // Unchanged line - <blank> | <old line number> | <blank>
    row_class = change_type
    old_line_number = c.ln1
    if (old_line_number !== c.ln2) {
      new_line_number = c.ln2
    }
  }
  return (

      {cov_status}
      {old_line_number}
      {new_line_number}

{c.content}

  );
}
<|endoftext|>


<commit_before>Template.historicalcard.events({
  "click .settings-icon": function (event,template) {
    if(!template.$(".card-settings-icon").hasClass("opened")) {
      template.$(".card-settings-icon").addClass("opened");
      template.$(".card-content").animate({"min-width":"+=350px"},"slow", function(){
        template.$(".card-settings-icon > iron-icon").fadeOut(300, function(){
          template.$(".card-settings-icon > iron-icon").attr("icon","icons:close").fadeIn(300);
        });
      });
    } else {
      template.$(".card-settings-icon").removeClass("opened");
      template.$(".card-content").animate({"min-width":"-=350px"},"slow ", function(){
        template.$(".card-settings-icon > iron-icon").fadeOut(300, function(){
          template.$(".card-settings-icon > iron-icon").attr("icon","icons:settings").fadeIn(300);
        });
      });
    }
  },
  "click .hc-toggle": function(event,template) {
    if(template.$(".hc-toggle").attr("checked")){
      Session.set("hc-toggle",true);
      Session.set("data-from","1999");
      Session.set("data-to","2012");
      setTimeout(function() {
        $("#selector").ionRangeSlider({
          type: 'double ',
          min: 1999,
          max: 2012,
          step: 1,
          grid: true,
          grid_snap: true,
          onChange: function(data) {
            Session.set("data-from",data.from);
            Session.set("data-to",data.to);
            // Websocket.send('{"reuqestId": "5645f7f7ef0bde57344c84de"}');
          }
        });

        var data = [
          { x: 0,  y: 3535},
          { x: 1,  y: 3863},
          { x: 2,  y: 4921},
          { x: 3,  y: 5847},
          { x: 4,  y: 6232},
          { x: 5,  y: 6770},
          { x: 6,  y: 7420},
          { x: 7,  y: 7469},
          { x: 8,  y: 7403},
          { x: 9,  y: 6014},
          { x: 10, y: 5434},
          { x: 11, y: 5285},
          { x: 12, y: 4841}
        ];

        var margin = {top: 20, right: 20, bottom: 30, left: 50},
        width  = 335,
        height = 170;

        var x = d3.scale.linear()
        .domain([0, d3.max(data, function(d) { return d.x; })])
        .range([0, width]);

        var y = d3.scale.linear()
        .domain([0, d3.max(data, function(d) { return d.y; })])
        .range([height, 0]);

        var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom");

        var yAxis = d3.svg.axis()
        .scale(y)
        .orient("left");

        var area = d3.svg.area()
        .x(function(d) { return x(d.x); })
        .y0(height)
        .y1(function(d) { return y(d.y); });

        var svg = d3.select("svg#area")
        .attr("width",width)
        .attr("height",height)
        .insert("g")
        .attr("transform", "translate(0,0)")
        .attr("class", "animated flipInX");

        svg.append("path")
        .datum(data)
        .attr("class", "area")
        .attr("d", area);

      },250);
    } else {
      Session.set("hc-toggle",false);
      template.$(".historicalcard-paper").css("opacity","1");
      Session.set("data-from","1999");
      Session.set("data-to","2012");
    }
  },
  "change .hc-paper-slider": function(event,template) {
    var n = template.$(".hc-paper-slider").attr("value");
    Session.set("hc-compliance", n);
    // Websocket.send('{"reuqestId": "5645f7f7ef0bde57344c84de"}');
    if(n==5) template.$(".historicalcard-paper").css("opacity","1");
    if(n==4) template.$(".historicalcard-paper").css("opacity","0.85");
    if(n==3) template.$(".historicalcard-paper").css("opacity","0.75");
    if(n==2) template.$(".historicalcard-paper").css("opacity","0.65");
    if(n==1) template.$(".historicalcard-paper").css("opacity","0.55");
    if(n==0) template.$(".historicalcard-paper").css("opacity","0.45");
  },
  "click .card-info": function (event,template) {
    template.$(".help-info").css("display","flex");
  },
  "click .close-info": function (event,template) {
    template.$(".help-info").fadeOut("fast");
  },
  "click .help-info": function (event,template) {
    template.$(".help-info").fadeOut("fast");
  },
  "click": function(event,template){
    /*** Interaction Recorder ***/
    var self = this;
    var myEvent = event;
    Recorder.insert({
      "user": Meteor.connection._lastSessionId,
      "template": template.view.name,
      "target": $(event.target).first().attr('class'),
      "screenX": event.screenX,
      "screenY": event.screenY,
      "offsetX": event.offsetX,
      "offsetY": event.offsetY,
      "timestamp": new Date()
    });
  }

});

Template.historicalcard.helpers({
  isOn: function() {
    return Session.get("hc-toggle");
  },
  compliance: function() {
    return Session.get("hc-compliance");
  },
  students: function () {
    var result = 0;
    var x = Historial.find({}).fetch();
    for (i=0; i < x.length; i++) {
      if ((Session.get("data-from") <= parseInt(x[i].year)) && (Session.get("data-to") >= parseInt(x[i].year)))
      result = parseInt(x[i].students) + result;
    }
    return result.toLocaleString();
  },
  courses: function () {
    var result = 0;
    var x = Historial.find({}).fetch();
    for (i=0; i < x.length; i++) {
      if ((Session.get("data-from") <= parseInt(x[i].year)) && (Session.get("data-to") >= parseInt(x[i].year)))
      result = parseInt(x[i].courses) + result;
    }
    return result.toLocaleString();
  },
  from: function () {
    return Session.get("data-from");
  },
  to: function () {
    return Session.get("data-to");
  }
});

Template.historicalcard.rendered = function () {
  Session.set("hc-toggle", true);
  setTimeout(function() {
    $("#selector").ionRangeSlider({
      type: 'double ',
      min: 1999,
      max: 2012,
      step: 1,
      grid: true,
      grid_snap: true,
      onChange: function(data) {
        Session.set("data-from",data.from);
        Session.set("data-to",data.to);
        var courses = Session.get('courses');
        var student = Session.get('student');
        if(Websocket.readyState == 1) {
          var str = "";
          if(courses) {
            for (var i=0; i<courses.length-1; i++){ str += '{"id": "'+courses[i]+'", "compliance": 5},'; }
            str+= '{"id": "'+courses[courses.length-1]+'", "compliance": 5}';
            Websocket.send('{"requestId": "'+Meteor.connection._lastSessionId+'",'+
            '"student": [{"id": '+student+',"gpa": 7.0793,'+
            '"performance": 0.6,"compliance": 3}],'+
            '"courses": ['+ str + '],'+
            '"data": [{"from": '+data.from+',"to": '+data.to+','+
            '"program": true,'+
            '"sylabus": true,'+
            '"evaluation": false,'+
            '"instructors": true,'+
            '"compliance": 2}]}');
          }
        } else if (Websocket.readyState == 3) {
          // $("#paperToast").attr("text","Lost connection...");
          // document.querySelector('#paperToast').show();
        }
      }
    });

    var data = [
      { x: 0,  y: 3535},
      { x: 1,  y: 3863},
      { x: 2,  y: 4921},
      { x: 3,  y: 5847},
      { x: 4,  y: 6232},
      { x: 5,  y: 6770},
      { x: 6,  y: 7420},
      { x: 7,  y: 7469},
      { x: 8,  y: 7403},
      { x: 9,  y: 6014},
      { x: 10, y: 5434},
      { x: 11, y: 5285},
      { x: 12, y: 4841}
    ];

    var margin = {top: 20, right: 20, bottom: 30, left: 50},
    width  = 335,
    height = 170;

    var x = d3.scale.linear()
    .domain([0, d3.max(data, function(d) { return d.x; })])
    .range([0, width]);

    var y = d3.scale.linear()
    .domain([0, d3.max(data, function(d) { return d.y; })])
    .range([height, 0]);

    var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

    var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

    var area = d3.svg.area()
    .x(function(d) { return x(d.x); })
    .y0(height)
    .y1(function(d) { return y(d.y); });

    var svg = d3.select("svg#area")
    .attr("width",width)
    .attr("height",height)
    .insert("g")
    .attr("transform", "translate(0,0)")
    .attr("class", "animated flipInX");

    svg.append("path")
    .datum(data)
    .attr("class", "area")
    .attr("d", area);

  },250);
};
<commit_msg>Added help-info to all cards<commit_after>Template.historicalcard.events({
  "click .settings-icon": function (event,template) {
    if(!template.$(".card-settings-icon").hasClass("opened")) {
      template.$(".card-settings-icon").addClass("opened");
      template.$(".card-content").animate({"min-width":"+=350px"},"slow", function(){
        template.$(".card-settings-icon > iron-icon").fadeOut(300, function(){
          template.$(".card-settings-icon > iron-icon").attr("icon","icons:close").fadeIn(300);
        });
      });
    } else {
      template.$(".card-settings-icon").removeClass("opened");
      template.$(".card-content").animate({"min-width":"-=350px"},"slow ", function(){
        template.$(".card-settings-icon > iron-icon").fadeOut(300, function(){
          template.$(".card-settings-icon > iron-icon").attr("icon","icons:settings").fadeIn(300);
        });
      });
    }
  },
  "click .hc-toggle": function(event,template) {
    if(template.$(".hc-toggle").attr("checked")){
      Session.set("hc-toggle",true);
      Session.set("data-from","1999");
      Session.set("data-to","2012");
      setTimeout(function() {
        $("#selector").ionRangeSlider({
          type: 'double ',
          min: 1999,
          max: 2012,
          step: 1,
          grid: true,
          grid_snap: true,
          onChange: function(data) {
            Session.set("data-from",data.from);
            Session.set("data-to",data.to);
            // Websocket.send('{"reuqestId": "5645f7f7ef0bde57344c84de"}');
          }
        });

        var data = [
          { x: 0,  y: 3535},
          { x: 1,  y: 3863},
          { x: 2,  y: 4921},
          { x: 3,  y: 5847},
          { x: 4,  y: 6232},
          { x: 5,  y: 6770},
          { x: 6,  y: 7420},
          { x: 7,  y: 7469},
          { x: 8,  y: 7403},
          { x: 9,  y: 6014},
          { x: 10, y: 5434},
          { x: 11, y: 5285},
          { x: 12, y: 4841}
        ];

        var margin = {top: 20, right: 20, bottom: 30, left: 50},
        width  = 335,
        height = 170;

        var x = d3.scale.linear()
        .domain([0, d3.max(data, function(d) { return d.x; })])
        .range([0, width]);

        var y = d3.scale.linear()
        .domain([0, d3.max(data, function(d) { return d.y; })])
        .range([height, 0]);

        var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom");

        var yAxis = d3.svg.axis()
        .scale(y)
        .orient("left");

        var area = d3.svg.area()
        .x(function(d) { return x(d.x); })
        .y0(height)
        .y1(function(d) { return y(d.y); });

        var svg = d3.select("svg#area")
        .attr("width",width)
        .attr("height",height)
        .insert("g")
        .attr("transform", "translate(0,0)")
        .attr("class", "animated flipInX");

        svg.append("path")
        .datum(data)
        .attr("class", "area")
        .attr("d", area);

      },250);
    } else {
      Session.set("hc-toggle",false);
      template.$(".historicalcard-paper").css("opacity","1");
      Session.set("data-from","1999");
      Session.set("data-to","2012");
    }
  },
  "change .hc-paper-slider": function(event,template) {
    var n = template.$(".hc-paper-slider").attr("value");
    Session.set("hc-compliance", n);
    // Websocket.send('{"reuqestId": "5645f7f7ef0bde57344c84de"}');
    if(n==5) template.$(".historicalcard-paper").css("opacity","1");
    if(n==4) template.$(".historicalcard-paper").css("opacity","0.85");
    if(n==3) template.$(".historicalcard-paper").css("opacity","0.75");
    if(n==2) template.$(".historicalcard-paper").css("opacity","0.65");
    if(n==1) template.$(".historicalcard-paper").css("opacity","0.55");
    if(n==0) template.$(".historicalcard-paper").css("opacity","0.45");
  },
  "click .card-info": function (event,template) {
    template.$(".help-info").css("display","flex");
  },
  "click .close-info": function (event,template) {
    template.$(".help-info").fadeOut("fast");
  },
  "click .help-info": function (event,template) {
    template.$(".help-info").fadeOut("fast");
  },
  "click": function(event,template){
    /*** Interaction Recorder ***/
    var self = this;
    var myEvent = event;
    Recorder.insert({
      "user": Meteor.connection._lastSessionId,
      "template": template.view.name,
      "target": $(event.target).first().attr('class'),
      "screenX": event.screenX,
      "screenY": event.screenY,
      "offsetX": event.offsetX,
      "offsetY": event.offsetY,
      "timestamp": new Date()
    });
  }
});

Template.historicalcard.helpers({
  isOn: function() {
    return Session.get("hc-toggle");
  },
  compliance: function() {
    return Session.get("hc-compliance");
  },
  students: function () {
    var result = 0;
    var x = Historial.find({}).fetch();
    for (i=0; i < x.length; i++) {
      if ((Session.get("data-from") <= parseInt(x[i].year)) && (Session.get("data-to") >= parseInt(x[i].year)))
      result = parseInt(x[i].students) + result;
    }
    return result.toLocaleString();
  },
  courses: function () {
    var result = 0;
    var x = Historial.find({}).fetch();
    for (i=0; i < x.length; i++) {
      if ((Session.get("data-from") <= parseInt(x[i].year)) && (Session.get("data-to") >= parseInt(x[i].year)))
      result = parseInt(x[i].courses) + result;
    }
    return result.toLocaleString();
  },
  from: function () {
    return Session.get("data-from");
  },
  to: function () {
    return Session.get("data-to");
  }
});

Template.historicalcard.rendered = function () {
  Session.set("hc-toggle", true);
  setTimeout(function() {
    $("#selector").ionRangeSlider({
      type: 'double ',
      min: 1999,
      max: 2012,
      step: 1,
      grid: true,
      grid_snap: true,
      onChange: function(data) {
        Session.set("data-from",data.from);
        Session.set("data-to",data.to);
        var courses = Session.get('courses');
        var student = Session.get('student');
        if(Websocket.readyState == 1) {
          var str = "";
          if(courses) {
            for (var i=0; i<courses.length-1; i++){ str += '{"id": "'+courses[i]+'", "compliance": 5},'; }
            str+= '{"id": "'+courses[courses.length-1]+'", "compliance": 5}';
            Websocket.send('{"requestId": "'+Meteor.connection._lastSessionId+'",'+
            '"student": [{"id": '+student+',"gpa": 7.0793,'+
            '"performance": 0.6,"compliance": 3}],'+
            '"courses": ['+ str + '],'+
            '"data": [{"from": '+data.from+',"to": '+data.to+','+
            '"program": true,'+
            '"sylabus": true,'+
            '"evaluation": false,'+
            '"instructors": true,'+
            '"compliance": 2}]}');
          }
        } else if (Websocket.readyState == 3) {
          // $("#paperToast").attr("text","Lost connection...");
          // document.querySelector('#paperToast').show();
        }
      }
    });

    var data = [
      { x: 0,  y: 3535},
      { x: 1,  y: 3863},
      { x: 2,  y: 4921},
      { x: 3,  y: 5847},
      { x: 4,  y: 6232},
      { x: 5,  y: 6770},
      { x: 6,  y: 7420},
      { x: 7,  y: 7469},
      { x: 8,  y: 7403},
      { x: 9,  y: 6014},
      { x: 10, y: 5434},
      { x: 11, y: 5285},
      { x: 12, y: 4841}
    ];

    var margin = {top: 20, right: 20, bottom: 30, left: 50},
    width  = 335,
    height = 170;

    var x = d3.scale.linear()
    .domain([0, d3.max(data, function(d) { return d.x; })])
    .range([0, width]);

    var y = d3.scale.linear()
    .domain([0, d3.max(data, function(d) { return d.y; })])
    .range([height, 0]);

    var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

    var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

    var area = d3.svg.area()
    .x(function(d) { return x(d.x); })
    .y0(height)
    .y1(function(d) { return y(d.y); });

    var svg = d3.select("svg#area")
    .attr("width",width)
    .attr("height",height)
    .insert("g")
    .attr("transform", "translate(0,0)")
    .attr("class", "animated flipInX");

    svg.append("path")
    .datum(data)
    .attr("class", "area")
    .attr("d", area);

  },250);
};
<|endoftext|>