<commit_before>'use strict';

/*
 * Copyright 2014 Next Century Corporation
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

angular.module('neonDemo.directives')
.directive('countBy', ['ConnectionService', function(connectionService) {
	return {
		templateUrl: 'partials/directives/countby.html',
		restrict: 'EA',
		scope: {
		},
		link: function($scope, el) {
			el.addClass('countByDirective');

			$scope.countField = "";
			$scope.fields = [];
			$scope.tableId = 'query-results-' + uuid();
			$scope.filterKey = "countby-" + uuid();
			$scope.filterSet = undefined;

			var $tableDiv = $(el).find('.count-by-grid');
			$tableDiv.attr("id", $scope.tableId);

			/**
			 * Updates the size of the table to fill the available space in the directive's area.
			 * @method updateSize
			 * @private
			 */
			var updateSize = function() {
				$('#' + $scope.tableId).height(el.height() - $(el).find('.count-by-header').outerHeight(true));
				if($scope.table) {
					$scope.table.refreshLayout();
				}
			};

			/**
			 * Initializes the name of the directive's scope variables
			 * and the Neon Messenger used to monitor data change events.
			 * @method initialize
			 */
			$scope.initialize = function() {
				// Setup our messenger.
				$scope.messenger = new neon.eventing.Messenger();

				$scope.messenger.events({
					activeDatasetChanged: onDatasetChanged,
					filtersChanged: onFiltersChanged
				});

				$scope.$watch('countField', function() {
					$scope.queryForData();
				});

                $scope.$on('$destroy', function() {
                    $scope.messenger.removeFilter($scope.filterKey);
                });

				el.resize(function() {
					updateSize();
				});

                // The header is resized whenever filtering is set or cleared.
                el.find('.count-by-header').resize(function() {
                    updateSize();
                });
			};

			function createOptions(data) {
				var options = {
					data: data.data,
					gridOptions: {
						forceFitColumns: true,
						enableColumnReorder: true,
						forceSyncScrolling: true
					}
				};

				return options;
			}

            /**
             * Saves the given field and value as the current filter.
             * @param {String} The filter field
             * @param {String} The filter value
             */
            var handleSetFilter = function(field, value) {
                $scope.filterSet = {
                    key: field,
                    value: value
                };
            };

            /**
             * Clears the current filter.
             */
            var clearFilter = function() {
                $scope.filterSet = undefined;
            };

			/**
			 * Event handler for filter changed events issued over Neon's messaging channels.
			 * @method onFiltersChanged
			 * @private
			 */
			var onFiltersChanged = function() {
				$scope.queryForData();
			};

			/**
			 * Event handler for dataset changed events issued over Neon's messaging channels.
			 * @param {Object} message A Neon dataset changed message.
			 * @param {String} message.database The database that was selected.
			 * @param {String} message.table The table within the database that was selected.
			 * @method onDatasetChanged
			 * @private
			 */
			var onDatasetChanged = function(message) {
				XDATA.activityLogger.logSystemActivity('CountBy- received neon dataset changed event');
				$scope.databaseName = message.database;
				$scope.tableName = message.table;

				// if there is no active connection, try to make one.
				connectionService.connectToDataset(message.datastore, message.hostname, message.database, message.table);
				$scope.displayActiveDataset();
			};

			/**
			 * Displays data for any currently active datasets.
			 * @method displayActiveDataset
			 */
			$scope.displayActiveDataset = function() {
				var connection = connectionService.getActiveConnection();
				if(connection) {
					connectionService.loadMetadata(function() {
						var info = connectionService.getActiveDataset();
						$scope.databaseName = info.database;
						$scope.tableName = info.table;
						connection.getFieldNames($scope.tableName, function(results) {
							$scope.$apply(function() {
								$scope.fields = results;
								$scope.queryForData();
							});
						});
						$scope.queryForData();
					});
				}
			};

			/**
			 * Triggers a Neon query that pull the a number of records that match the current Neon connection
			 * and filter set.  The query will be limited by the record number and sorted by the field
			 * selected in this directive's form.  This directive includes support for a show-data directive attribute
			 * that binds to a scope variable and controls table display.  If the bound variable evaulates to false,
			 * no data table is generated.  queryForData will not issue a query until the directive thinks it needs to
			 * poll for data and should show data.
			 * Resets internal "need to query" state to false.
			 * @method queryForData
			 */
			$scope.queryForData = function() {
                if(!$scope.countField) {
                    return;
                }

				var connection = connectionService.getActiveConnection();
				if(connection) {
					var query = $scope.buildQuery();

					XDATA.activityLogger.logSystemActivity('CountBy - query for data');
					connection.executeQuery(query, function(queryResults) {
						XDATA.activityLogger.logSystemActivity('CountBy - received data');
						$scope.$apply(function() {
							$scope.updateData(queryResults);
							XDATA.activityLogger.logSystemActivity('CountBy - rendered data');
						});
					});
				}
			};

			$scope.stripIdField = function(dataObject) {
				var data = dataObject.data;

				var cleanData = [];
				for(var i = 0; i < data.length; i++) {
					var row = {};
					row[$scope.countField] = data[i][$scope.countField];
					row.count = data[i].count;
					cleanData.push(row);
				}
				dataObject.data = cleanData;
				return dataObject;
			};

            /**
             * Saves the given field and value as the current filter for the
             * dashboard and this widget.
             * @param {String} The filter field
             * @param {String} The filter value
             */
            $scope.setFilter = function(field, value) {
				var connection = connectionService.getActiveConnection();
                if($scope.messenger && connection) {
                    var filterClause = neon.query.where(field, '=', value);
                    var filter = new neon.query.Filter().selectFrom($scope.databaseName, $scope.tableName).where(filterClause);
                    if(!$scope.filterSet) {
                        $scope.messenger.addFilter($scope.filterKey, filter, function() {
                            handleSetFilter(field, value);
                        });
                    }
                    else {
                        $scope.messenger.replaceFilter($scope.filterKey, filter, function() {
                            handleSetFilter(field, value);
                        });
                    }
                }
            };

            /**
             * Adds an onClick listener for selecting the rows in the table that
             * sets a filter on the data in the selected row.
             */
            $scope.addOnClickListener = function() {
                $scope.table.addOnClickListener(function(columns, row) {
                    $(".count-by-grid").addClass("filtered");
                    var field = columns[0].field;
                    $scope.setFilter(field, row[field]);
                });
            };

			/**
			 * Updates the data bound to the table managed by this directive.  This will trigger a change in
			 * the chart's visualization.
			 * @param {Object} queryResults Results returned from a Neon query.
			 * @param {Array} queryResults.data The aggregate numbers for the heat chart cells.
			 * @method updateData
			 */
			$scope.updateData = function(queryResults) {
				var cleanData = $scope.stripIdField(queryResults);
                var sortInfo = $scope.table ? $scope.table.sortInfo_ : {};

				$scope.tableOptions = createOptions(cleanData);
				$scope.table = new tables.Table("#" + $scope.tableId, $scope.tableOptions).draw();
                $scope.addOnClickListener();
				updateSize();

                if(sortInfo.hasOwnProperty("field") && sortInfo.hasOwnProperty("sortAsc")) {
                    $scope.table.sortColumnAndChangeGlyph(sortInfo);
                }

                // If the table is recreated while a filter is set, we must re-select the filtered row/cells.
                if($scope.filterSet !== undefined) {
                    $scope.table.setActiveCellIfMatchExists($scope.filterSet.key, $scope.filterSet.value);
                }
			};

			/**
			 * Builds a query to pull a limited set of records that match any existing filter sets.
			 * @return neon.query.Query
			 * @method buildQuery
			 */
			$scope.buildQuery = function() {
				var query = new neon.query.Query().selectFrom($scope.databaseName, $scope.tableName)
				.groupBy($scope.countField);

                // The widget displays its own ignored rows with 0.5 opacity.
                query.ignoreFilters([$scope.filterKey]);
				query.aggregate(neon.query.COUNT, '*', 'count');

				return query;
			};

            /**
             * Removes the current filter from the dashboard and this widget.
             */
            $scope.clearFilter = function() {
                if($scope.messenger) {
                    $scope.messenger.removeFilter($scope.filterKey, function() {
                        $(".count-by-grid").removeClass("filtered");
                        $scope.table.deselect();
                        clearFilter();
                    });
                }
            };

			neon.ready(function() {
				$scope.initialize();
				$scope.displayActiveDataset();
			});
		}
	};
}]);

<commit_msg>NEON-1314 Now clicking on the selected (filtered) row/cell in the CountBy widget will deselect the row and remove the filter.<commit_after>'use strict';

/*
 * Copyright 2014 Next Century Corporation
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

angular.module('neonDemo.directives')
.directive('countBy', ['ConnectionService', function(connectionService) {
	return {
		templateUrl: 'partials/directives/countby.html',
		restrict: 'EA',
		scope: {
		},
		link: function($scope, el) {
			el.addClass('countByDirective');

			$scope.countField = "";
			$scope.fields = [];
			$scope.tableId = 'query-results-' + uuid();
			$scope.filterKey = "countby-" + uuid();
			$scope.filterSet = undefined;

			var $tableDiv = $(el).find('.count-by-grid');
			$tableDiv.attr("id", $scope.tableId);

			/**
			 * Updates the size of the table to fill the available space in the directive's area.
			 * @method updateSize
			 * @private
			 */
			var updateSize = function() {
				$('#' + $scope.tableId).height(el.height() - $(el).find('.count-by-header').outerHeight(true));
				if($scope.table) {
					$scope.table.refreshLayout();
				}
			};

			/**
			 * Initializes the name of the directive's scope variables
			 * and the Neon Messenger used to monitor data change events.
			 * @method initialize
			 */
			$scope.initialize = function() {
				// Setup our messenger.
				$scope.messenger = new neon.eventing.Messenger();

				$scope.messenger.events({
					activeDatasetChanged: onDatasetChanged,
					filtersChanged: onFiltersChanged
				});

				$scope.$watch('countField', function() {
					$scope.queryForData();
				});

                $scope.$on('$destroy', function() {
                    $scope.messenger.removeFilter($scope.filterKey);
                });

				el.resize(function() {
					updateSize();
				});

                // The header is resized whenever filtering is set or cleared.
                el.find('.count-by-header').resize(function() {
                    updateSize();
                });
			};

			function createOptions(data) {
				var options = {
					data: data.data,
					gridOptions: {
						forceFitColumns: true,
						enableColumnReorder: true,
						forceSyncScrolling: true
					}
				};

				return options;
			}

            /**
             * Saves the given field and value as the current filter.
             * @param {String} The filter field
             * @param {String} The filter value
             */
            var handleSetFilter = function(field, value) {
                $scope.filterSet = {
                    key: field,
                    value: value
                };
            };

            /**
             * Clears the current filter.
             */
            var clearFilter = function() {
                $scope.filterSet = undefined;
            };

			/**
			 * Event handler for filter changed events issued over Neon's messaging channels.
			 * @method onFiltersChanged
			 * @private
			 */
			var onFiltersChanged = function() {
				$scope.queryForData();
			};

			/**
			 * Event handler for dataset changed events issued over Neon's messaging channels.
			 * @param {Object} message A Neon dataset changed message.
			 * @param {String} message.database The database that was selected.
			 * @param {String} message.table The table within the database that was selected.
			 * @method onDatasetChanged
			 * @private
			 */
			var onDatasetChanged = function(message) {
				XDATA.activityLogger.logSystemActivity('CountBy- received neon dataset changed event');
				$scope.databaseName = message.database;
				$scope.tableName = message.table;

				// if there is no active connection, try to make one.
				connectionService.connectToDataset(message.datastore, message.hostname, message.database, message.table);
				$scope.displayActiveDataset();
			};

			/**
			 * Displays data for any currently active datasets.
			 * @method displayActiveDataset
			 */
			$scope.displayActiveDataset = function() {
				var connection = connectionService.getActiveConnection();
				if(connection) {
					connectionService.loadMetadata(function() {
						var info = connectionService.getActiveDataset();
						$scope.databaseName = info.database;
						$scope.tableName = info.table;
						connection.getFieldNames($scope.tableName, function(results) {
							$scope.$apply(function() {
								$scope.fields = results;
								$scope.queryForData();
							});
						});
						$scope.queryForData();
					});
				}
			};

			/**
			 * Triggers a Neon query that pull the a number of records that match the current Neon connection
			 * and filter set.  The query will be limited by the record number and sorted by the field
			 * selected in this directive's form.  This directive includes support for a show-data directive attribute
			 * that binds to a scope variable and controls table display.  If the bound variable evaulates to false,
			 * no data table is generated.  queryForData will not issue a query until the directive thinks it needs to
			 * poll for data and should show data.
			 * Resets internal "need to query" state to false.
			 * @method queryForData
			 */
			$scope.queryForData = function() {
                if(!$scope.countField) {
                    return;
                }

				var connection = connectionService.getActiveConnection();
				if(connection) {
					var query = $scope.buildQuery();

					XDATA.activityLogger.logSystemActivity('CountBy - query for data');
					connection.executeQuery(query, function(queryResults) {
						XDATA.activityLogger.logSystemActivity('CountBy - received data');
						$scope.$apply(function() {
							$scope.updateData(queryResults);
							XDATA.activityLogger.logSystemActivity('CountBy - rendered data');
						});
					});
				}
			};

			$scope.stripIdField = function(dataObject) {
				var data = dataObject.data;

				var cleanData = [];
				for(var i = 0; i < data.length; i++) {
					var row = {};
					row[$scope.countField] = data[i][$scope.countField];
					row.count = data[i].count;
					cleanData.push(row);
				}
				dataObject.data = cleanData;
				return dataObject;
			};

            /**
             * Saves the given field and value as the current filter for the
             * dashboard and this widget.
             * @param {String} The filter field
             * @param {String} The filter value
             */
            $scope.setFilter = function(field, value) {
				var connection = connectionService.getActiveConnection();
                if($scope.messenger && connection) {
                    var filterClause = neon.query.where(field, '=', value);
                    var filter = new neon.query.Filter().selectFrom($scope.databaseName, $scope.tableName).where(filterClause);
                    if(!$scope.filterSet) {
                        $scope.messenger.addFilter($scope.filterKey, filter, function() {
                            handleSetFilter(field, value);
                        });
                    }
                    else {
                        $scope.messenger.replaceFilter($scope.filterKey, filter, function() {
                            handleSetFilter(field, value);
                        });
                    }
                }
            };

            /**
             * Adds an onClick listener for selecting the rows in the table that
             * sets a filter on the data in the selected row.
             */
            $scope.addOnClickListener = function() {
                $scope.table.addOnClickListener(function(columns, row) {
                    var field = columns[0].field;

                    // If the user clicks on the filtered row/cell, clear the filter.
                    if($scope.filterSet !== undefined) {
                        if($scope.filterSet.key === field && $scope.filterSet.value === row[field]) {
                            $scope.clearFilter();
                            return;
                        }
                    }

                    $(".count-by-grid").addClass("filtered");
                    $scope.setFilter(field, row[field]);
                });
            };

			/**
			 * Updates the data bound to the table managed by this directive.  This will trigger a change in
			 * the chart's visualization.
			 * @param {Object} queryResults Results returned from a Neon query.
			 * @param {Array} queryResults.data The aggregate numbers for the heat chart cells.
			 * @method updateData
			 */
			$scope.updateData = function(queryResults) {
				var cleanData = $scope.stripIdField(queryResults);
                var sortInfo = $scope.table ? $scope.table.sortInfo_ : {};

				$scope.tableOptions = createOptions(cleanData);
				$scope.table = new tables.Table("#" + $scope.tableId, $scope.tableOptions).draw();
                $scope.addOnClickListener();
				updateSize();

                if(sortInfo.hasOwnProperty("field") && sortInfo.hasOwnProperty("sortAsc")) {
                    $scope.table.sortColumnAndChangeGlyph(sortInfo);
                }

                // If the table is recreated while a filter is set, we must re-select the filtered row/cells.
                if($scope.filterSet !== undefined) {
                    $scope.table.setActiveCellIfMatchExists($scope.filterSet.key, $scope.filterSet.value);
                }
			};

			/**
			 * Builds a query to pull a limited set of records that match any existing filter sets.
			 * @return neon.query.Query
			 * @method buildQuery
			 */
			$scope.buildQuery = function() {
				var query = new neon.query.Query().selectFrom($scope.databaseName, $scope.tableName)
				.groupBy($scope.countField);

                // The widget displays its own ignored rows with 0.5 opacity.
                query.ignoreFilters([$scope.filterKey]);
				query.aggregate(neon.query.COUNT, '*', 'count');

				return query;
			};

            /**
             * Removes the current filter from the dashboard and this widget.
             */
            $scope.clearFilter = function() {
                if($scope.messenger) {
                    $scope.messenger.removeFilter($scope.filterKey, function() {
                        $(".count-by-grid").removeClass("filtered");
                        $scope.table.deselect();
                        clearFilter();
                    });
                }
            };

			neon.ready(function() {
				$scope.initialize();
				$scope.displayActiveDataset();
			});
		}
	};
}]);

<|endoftext|>


<commit_before>import {ElementResizeDetector} from 'widget/elementResizeDetector'
import {ViewportResizeDetector} from 'widget/viewportResizeDetector'
import {Widget} from 'widget/widget'
import {animationFrameScheduler, combineLatest, fromEvent, interval, merge, of} from 'rxjs'
import {compose} from 'compose'
import {distinctUntilChanged, filter, map, mapTo, scan, switchMap, withLatestFrom} from 'rxjs/operators'
import Hammer from 'hammerjs'
import Portal from 'widget/portal'
import PropTypes from 'prop-types'
import React from 'react'
import _ from 'lodash'
import styles from './slider.module.css'
import withSubscriptions from 'subscription'

const clamp = (value, {min, max}) => Math.max(min, Math.min(max, value))

const lerp = (rate, speed = 1) => (value, target) => value + (target - value) * (rate * speed)

const normalizeLinear = (value, min, max) => {
    const offset = min
    return (value - offset) / (max - offset)
}

const denormalizeLinear = (value, min, max) => {
    const offset = min
    return value * (max - offset) + offset
}

const normalizeLog = (value, min, max) => {
    const offset = min - 1
    return Math.log(value - offset) / Math.log(max - offset)
}

const denormalizeLog = (value, min, max) => {
    const offset = min - 1
    return Math.exp(value * Math.log(max - offset)) + offset
}

const invertNormalized = (value, invert) =>
    invert ? 1 - value : value

const normalize = (value, min, max, scale, invert) =>
    invertNormalized(scale === 'log' ? normalizeLog(value, min, max) : normalizeLinear(value, min, max), invert)

const denormalize = (value, min, max, scale, invert) =>
    scale === 'log'
        ? denormalizeLog(invertNormalized(value, invert), min, max)
        : denormalizeLinear(invertNormalized(value, invert), min, max)

class SliderContainer extends React.Component {
    clickTarget = React.createRef()

    renderTick({position, value, label}) {
        const left = `${Math.trunc(position)}px`
        return (
            <React.Fragment key={value}>

{label}

            </React.Fragment>
        )
    }

    renderAxis() {
        const {ticks} = this.props
        return (

                {ticks.map(tick => this.renderTick(tick))}

        )
    }

    renderClickTarget() {
        return (

        )
    }

    renderDynamics() {
        const {input, width, range, decimals, ticks, snap, minValue, maxValue, invert, onChange} = this.props
        return (
            <SliderDynamics
                input={input}
                minValue={minValue}
                maxValue={maxValue}
                width={width}
                decimals={decimals}
                ticks={ticks}
                snap={snap}
                range={range}
                invert={invert}
                clickTarget={this.clickTarget}
                normalize={value => {
                    const {scale, minValue, maxValue} = this.props
                    return normalize(value, minValue, maxValue, scale, invert)
                }}
                denormalize={value => {
                    const {scale, minValue, maxValue} = this.props
                    return denormalize(value, minValue, maxValue, scale, invert)
                }}
                onChange={onChange}
            />
        )
    }

    render() {
        return (

                {this.renderAxis()}
                {this.renderClickTarget()}
                {this.renderDynamics()}

        )
    }
}

SliderContainer.propTypes = {
    input: PropTypes.object.isRequired,
    decimals: PropTypes.number,
    denormalize: PropTypes.func,
    info: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.func
    ]),
    maxValue: PropTypes.number,
    minValue: PropTypes.number,
    normalize: PropTypes.func,
    snap: PropTypes.bool,
    ticks: PropTypes.oneOfType([
        PropTypes.number,
        PropTypes.array
    ]),
    width: PropTypes.number,
    onChange: PropTypes.func
}

class _SliderDynamics extends React.Component {
    state = {
        position: null,
        previewPosition: null,
        clickTargetOffset: null,
        dragging: null,
        inhibitInput: null
    }
    handle = React.createRef()

    renderLeftRange() {
        const {position} = this.state
        const {width} = this.props
        return <div className={styles.range} style={{right: `${width - position}px`}}/>
    }

    renderRightRange() {
        const {position} = this.state
        return <div className={styles.range} style={{left: `${position}px`}}/>
    }

    renderRanges() {
        const {range, invert} = this.props
        return (
            <React.Fragment>
                {((range === 'low' && !invert) || (range === 'high' && invert)) ? this.renderLeftRange() : null}
                {((range === 'high' && !invert) || (range === 'low' && invert)) ? this.renderRightRange() : null}
            </React.Fragment>
        )
    }

    renderPreview() {
        const {previewPosition} = this.state
        return previewPosition !== null ? (
            <div className={[styles.cursor, styles.preview].join(' ')} ref={this.preview}
                style={{left: `${previewPosition}px`}}/>
        ) : null
    }

    renderHandle() {
        const {position, dragging} = this.state
        return (
            <div className={[styles.cursor, styles.handle, dragging ? styles.dragging : null].join(' ')}
                ref={this.handle}
                style={{left: `${position}px`}}/>
        )
    }

    renderOverlay() {
        const {dragging} = this.state
        return dragging
            ? (
                <Portal type='global'>

                </Portal>
            ) : null
    }

    renderViewportResizer() {
        return (
            <ViewportResizeDetector onChange={() => this.setClickTargetOffset()}/>
        )
    }

    render() {
        return (
            <React.Fragment>
                {this.renderRanges()}
                {this.renderPreview()}
                {this.renderHandle()}
                {this.renderOverlay()}
                {this.renderViewportResizer()}
            </React.Fragment>
        )
    }

    componentDidMount() {
        this.initialize({
            handleRef: this.handle.current,
            clickableAreaRef: this.props.clickTarget.current
        })
    }

    initialize({handleRef, clickableAreaRef}) {
        const {addSubscription} = this.props

        this.setHandlePositionByValue()

        const handle = new Hammer(handleRef)
        // limit handle movement to horizontal axis
        handle.get('pan').set({
            direction: Hammer.DIRECTION_HORIZONTAL,
            threshold: 1
        })

        const clickableArea = new Hammer(clickableAreaRef, {threshold: 1})

        const mouseMove$ = fromEvent(clickableAreaRef, 'mousemove')
        const mouseLeave$ = fromEvent(clickableAreaRef, 'mouseleave')
        const pan$ = fromEvent(handle, 'panstart panmove panend')
        const panStart$ = pan$.pipe(filter(e => e.type === 'panstart'))
        const panMove$ = pan$.pipe(filter(e => e.type === 'panmove'))
        const panEnd$ = pan$.pipe(filter(e => e.type === 'panend'))
        const animationFrame$ = interval(0, animationFrameScheduler)

        const hoverPosition$ = merge(
            mouseMove$.pipe(
                map(e => e.clientX - this.state.clickTargetOffset)
            ),
            mouseLeave$.pipe(
                mapTo(null)
            )
        )

        // target position by clicking
        const clickPosition$ = fromEvent(clickableArea, 'tap').pipe(
            map(tap => this.snapPosition(tap.center.x - this.state.clickTargetOffset))
        )

        // target position by dragging
        const dragPosition$ = panStart$.pipe(
            switchMap(() => {
                const {position} = this.state
                return merge(
                    panMove$.pipe(
                        map(event => this.clampPosition(position + event.deltaX)),
                        distinctUntilChanged()
                    ),
                    panEnd$.pipe(
                        map(() => this.snapPosition(this.state.previewPosition))
                    )
                )
            })
        )

        const handleDragging$ = merge(
            of(false),
            panStart$.pipe(mapTo(true)),
            panEnd$.pipe(mapTo(false)),
        )

        const targetPosition$ = merge(clickPosition$, dragPosition$).pipe(
            map(position => Math.round(position))
        )

        const handlePosition$ = targetPosition$.pipe(
            withLatestFrom(handleDragging$),
            switchMap(([targetPosition, dragging]) => {
                const {position} = this.state
                return animationFrame$.pipe(
                    map(() => targetPosition),
                    scan(lerp(dragging ? .25 : .1), position),
                    map(position => Math.round(position)),
                    distinctUntilChanged()
                )
            })
        )

        const handleMoving$ = combineLatest(handlePosition$, targetPosition$).pipe(
            map(([currentPosition, targetPosition]) => currentPosition !== targetPosition),
            distinctUntilChanged()
        )

        const previewPosition$ = merge(targetPosition$, hoverPosition$).pipe(
            map(position => position === null ? null : this.snapPosition(position)),
            distinctUntilChanged()
        )

        const inhibitInput$ = handleMoving$.pipe(
            withLatestFrom(handleDragging$),
            map(([dragging, moving]) => dragging || moving)
        )

        const updateInput$ = merge(clickPosition$, panEnd$)

        addSubscription(
            handlePosition$.subscribe(position =>
                this.setHandlePosition(position)
            ),
            previewPosition$.subscribe(previewPosition =>
                this.setPreviewPosition(previewPosition)
            ),
            handleDragging$.subscribe(dragging =>
                this.setDragging(dragging)
            ),
            inhibitInput$.subscribe(inhibit =>
                this.setInhibitInput(inhibit)
            ),
            updateInput$.subscribe(() =>
                this.updateInputValue()
            )
        )
    }

    componentDidUpdate(prevProps) {
        const {inhibitInput} = this.state
        if (!inhibitInput && !_.isEqual(prevProps, this.props)) {
            this.setHandlePositionByValue()
        }
    }

    toPosition(value) {
        const {normalize, width} = this.props
        return normalize(value) * width
    }

    toValue(position) {
        const {denormalize, width} = this.props
        return denormalize(position / width)
    }

    clampPosition(position) {
        const {width} = this.props
        return Math.round(
            clamp(position, {
                min: 0,
                max: width
            })
        )
    }

    snapPosition(value) {
        const {ticks, snap} = this.props
        if (snap) {
            const closest = _(ticks)
                .map(({position}) => ({position, distance: Math.abs(position - value)}))
                .sortBy('distance')
                .head()
            return closest.position
        } else {
            return value
        }
    }

    setHandlePositionByValue() {
        const {input} = this.props
        this.setHandlePosition(this.toPosition(input.value))
    }

    setHandlePosition(position) {
        if (position >= 0) {
            position = Math.round(position)
            if (position !== this.state.position) {
                this.setState({position})
            }
        }
    }

    updateInputValue() {
        const {input, decimals, onChange} = this.props
        const {previewPosition} = this.state
        const value = this.toValue(this.snapPosition(previewPosition))
        const factor = Math.pow(10, decimals)
        const roundedValue = Math.round(value * factor) / factor
        input.set(roundedValue)
        onChange && onChange(roundedValue)
    }

    setPreviewPosition(previewPosition) {
        this.setState({previewPosition})
    }

    setInhibitInput(inhibitInput) {
        this.setState({inhibitInput})
    }

    setDragging(dragging) {
        this.setState({dragging})
    }

    setClickTargetOffset() {
        const {clickTarget} = this.props
        const clickTargetOffset = Math.trunc(clickTarget.current.getBoundingClientRect().left)
        if (this.state.clickTargetOffset !== clickTargetOffset) {
            this.setState({clickTargetOffset})
        }
    }
}

const SliderDynamics = compose(
    _SliderDynamics,
    withSubscriptions()
)

SliderDynamics.propTypes = {
    denormalize: PropTypes.func.isRequired,
    input: PropTypes.object.isRequired,
    maxValue: PropTypes.number.isRequired,
    minValue: PropTypes.number.isRequired,
    normalize: PropTypes.func.isRequired,
    decimals: PropTypes.number,
    invert: PropTypes.bool,
    snap: PropTypes.bool,
    ticks: PropTypes.array,
    width: PropTypes.number,
    onChange: PropTypes.func
}

export class FormSlider extends React.Component {
    state = {
        width: null,
        ticks: [],
        minValue: null,
        maxValue: null
    }

    static getDerivedStateFromProps(props, state) {
        const mapTicks = ticks =>
            ticks.map((tick, index) => {
                if (_.isObject(tick)) {
                    const value = tick.value || index
                    const label = tick.label || value
                    return {value, label}
                }
                if (_.isString(tick)) {
                    return {value: index, externalValue: tick, label: tick}
                }
                return {value: tick, externalValue: tick, label: tick}
            })

        const ticks = mapTicks(props.ticks ? props.ticks : [props.minValue, props.maxValue])
        const minValue = props.minValue || ticks.reduce((min, {value}) => Math.min(min, value), null)
        const maxValue = props.maxValue || ticks.reduce((max, {value}) => Math.max(max, value), null)
        return {
            ticks: ticks
                .filter(({value}) => value >= minValue && value <= maxValue)
                .map(tick => ({
                    ...tick,
                    position: state.width * normalize(tick.value, minValue, maxValue, props.scale, props.invert)
                })),
            minValue,
            maxValue
        }
    }

    renderInfo() {
        const {input, info, alignment} = this.props
        const {ticks} = this.state
        const tick = ticks && ticks.find(tick => tick.value === input.value)
        const label = (tick && tick.label) || input.value
        return info ? (

                {_.isFunction(info) ? info(label, input.value) : info}

        ) : null
    }

    renderSlider() {
        const {width} = this.state
        return (

                    <ElementResizeDetector onResize={({width}) => this.setState({width})}/>
                    {width ? this.renderContainer() : null}

        )
    }

    renderContainer() {
        const {input, scale = 'linear', decimals = 0, snap, range = 'left', invert = false, info, onChange} = this.props
        const {ticks, minValue, maxValue, width} = this.state
        return (
            <SliderContainer
                input={input}
                minValue={minValue}
                maxValue={maxValue}
                decimals={decimals}
                ticks={ticks}
                snap={snap}
                range={range}
                scale={scale}
                invert={invert}
                info={info}
                width={width}
                onChange={onChange}
            />
        )

    }

    render() {
        const {label, tooltip, tooltipPlacement, alignment, disabled} = this.props
        return (
            <Widget
                className={styles.wrapper}
                label={label}
                alignment={alignment}
                disabled={disabled}
                tooltip={tooltip}
                tooltipPlacement={tooltipPlacement}>
                {this.renderInfo()}
                {this.renderSlider()}
            </Widget>
        )
    }
}

FormSlider.propTypes = {
    input: PropTypes.object.isRequired,
    alignment: PropTypes.oneOf(['left', 'center', 'right']),
    decimals: PropTypes.number,
    disabled: PropTypes.any,
    info: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.func
    ]),
    invert: PropTypes.any,
    label: PropTypes.string,
    maxValue: PropTypes.number,
    minValue: PropTypes.number,
    range: PropTypes.oneOf(['none', 'low', 'high']),
    scale: PropTypes.oneOf(['linear', 'log']),
    snap: PropTypes.any,
    ticks: PropTypes.oneOfType([
        // PropTypes.number,
        PropTypes.array
    ]),
    tooltip: PropTypes.string,
    tooltipPlacement: PropTypes.string,
    onChange: PropTypes.func
}
<commit_msg>Slightly increased FormSlider responsiveness.<commit_after>import {ElementResizeDetector} from 'widget/elementResizeDetector'
import {ViewportResizeDetector} from 'widget/viewportResizeDetector'
import {Widget} from 'widget/widget'
import {animationFrameScheduler, combineLatest, fromEvent, interval, merge, of} from 'rxjs'
import {compose} from 'compose'
import {distinctUntilChanged, filter, map, mapTo, scan, switchMap, withLatestFrom} from 'rxjs/operators'
import Hammer from 'hammerjs'
import Portal from 'widget/portal'
import PropTypes from 'prop-types'
import React from 'react'
import _ from 'lodash'
import styles from './slider.module.css'
import withSubscriptions from 'subscription'

const clamp = (value, {min, max}) => Math.max(min, Math.min(max, value))

const lerp = (rate, speed = 1) => (value, target) => value + (target - value) * (rate * speed)

const normalizeLinear = (value, min, max) => {
    const offset = min
    return (value - offset) / (max - offset)
}

const denormalizeLinear = (value, min, max) => {
    const offset = min
    return value * (max - offset) + offset
}

const normalizeLog = (value, min, max) => {
    const offset = min - 1
    return Math.log(value - offset) / Math.log(max - offset)
}

const denormalizeLog = (value, min, max) => {
    const offset = min - 1
    return Math.exp(value * Math.log(max - offset)) + offset
}

const invertNormalized = (value, invert) =>
    invert ? 1 - value : value

const normalize = (value, min, max, scale, invert) =>
    invertNormalized(scale === 'log' ? normalizeLog(value, min, max) : normalizeLinear(value, min, max), invert)

const denormalize = (value, min, max, scale, invert) =>
    scale === 'log'
        ? denormalizeLog(invertNormalized(value, invert), min, max)
        : denormalizeLinear(invertNormalized(value, invert), min, max)

class SliderContainer extends React.Component {
    clickTarget = React.createRef()

    renderTick({position, value, label}) {
        const left = `${Math.trunc(position)}px`
        return (
            <React.Fragment key={value}>

{label}

            </React.Fragment>
        )
    }

    renderAxis() {
        const {ticks} = this.props
        return (

                {ticks.map(tick => this.renderTick(tick))}

        )
    }

    renderClickTarget() {
        return (

        )
    }

    renderDynamics() {
        const {input, width, range, decimals, ticks, snap, minValue, maxValue, invert, onChange} = this.props
        return (
            <SliderDynamics
                input={input}
                minValue={minValue}
                maxValue={maxValue}
                width={width}
                decimals={decimals}
                ticks={ticks}
                snap={snap}
                range={range}
                invert={invert}
                clickTarget={this.clickTarget}
                normalize={value => {
                    const {scale, minValue, maxValue} = this.props
                    return normalize(value, minValue, maxValue, scale, invert)
                }}
                denormalize={value => {
                    const {scale, minValue, maxValue} = this.props
                    return denormalize(value, minValue, maxValue, scale, invert)
                }}
                onChange={onChange}
            />
        )
    }

    render() {
        return (

                {this.renderAxis()}
                {this.renderClickTarget()}
                {this.renderDynamics()}

        )
    }
}

SliderContainer.propTypes = {
    input: PropTypes.object.isRequired,
    decimals: PropTypes.number,
    denormalize: PropTypes.func,
    info: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.func
    ]),
    maxValue: PropTypes.number,
    minValue: PropTypes.number,
    normalize: PropTypes.func,
    snap: PropTypes.bool,
    ticks: PropTypes.oneOfType([
        PropTypes.number,
        PropTypes.array
    ]),
    width: PropTypes.number,
    onChange: PropTypes.func
}

class _SliderDynamics extends React.Component {
    state = {
        position: null,
        previewPosition: null,
        clickTargetOffset: null,
        dragging: null,
        inhibitInput: null
    }
    handle = React.createRef()

    renderLeftRange() {
        const {position} = this.state
        const {width} = this.props
        return <div className={styles.range} style={{right: `${width - position}px`}}/>
    }

    renderRightRange() {
        const {position} = this.state
        return <div className={styles.range} style={{left: `${position}px`}}/>
    }

    renderRanges() {
        const {range, invert} = this.props
        return (
            <React.Fragment>
                {((range === 'low' && !invert) || (range === 'high' && invert)) ? this.renderLeftRange() : null}
                {((range === 'high' && !invert) || (range === 'low' && invert)) ? this.renderRightRange() : null}
            </React.Fragment>
        )
    }

    renderPreview() {
        const {previewPosition} = this.state
        return previewPosition !== null ? (
            <div className={[styles.cursor, styles.preview].join(' ')} ref={this.preview}
                style={{left: `${previewPosition}px`}}/>
        ) : null
    }

    renderHandle() {
        const {position, dragging} = this.state
        return (
            <div className={[styles.cursor, styles.handle, dragging ? styles.dragging : null].join(' ')}
                ref={this.handle}
                style={{left: `${position}px`}}/>
        )
    }

    renderOverlay() {
        const {dragging} = this.state
        return dragging
            ? (
                <Portal type='global'>

                </Portal>
            ) : null
    }

    renderViewportResizer() {
        return (
            <ViewportResizeDetector onChange={() => this.setClickTargetOffset()}/>
        )
    }

    render() {
        return (
            <React.Fragment>
                {this.renderRanges()}
                {this.renderPreview()}
                {this.renderHandle()}
                {this.renderOverlay()}
                {this.renderViewportResizer()}
            </React.Fragment>
        )
    }

    componentDidMount() {
        this.initialize({
            handleRef: this.handle.current,
            clickableAreaRef: this.props.clickTarget.current
        })
    }

    initialize({handleRef, clickableAreaRef}) {
        const {addSubscription} = this.props

        this.setHandlePositionByValue()

        const handle = new Hammer(handleRef)
        // limit handle movement to horizontal axis
        handle.get('pan').set({
            direction: Hammer.DIRECTION_HORIZONTAL,
            threshold: 1
        })

        const clickableArea = new Hammer(clickableAreaRef, {threshold: 1})

        const mouseMove$ = fromEvent(clickableAreaRef, 'mousemove')
        const mouseLeave$ = fromEvent(clickableAreaRef, 'mouseleave')
        const pan$ = fromEvent(handle, 'panstart panmove panend')
        const panStart$ = pan$.pipe(filter(e => e.type === 'panstart'))
        const panMove$ = pan$.pipe(filter(e => e.type === 'panmove'))
        const panEnd$ = pan$.pipe(filter(e => e.type === 'panend'))
        const animationFrame$ = interval(0, animationFrameScheduler)

        const hoverPosition$ = merge(
            mouseMove$.pipe(
                map(e => e.clientX - this.state.clickTargetOffset)
            ),
            mouseLeave$.pipe(
                mapTo(null)
            )
        )

        // target position by clicking
        const clickPosition$ = fromEvent(clickableArea, 'tap').pipe(
            map(tap => this.snapPosition(tap.center.x - this.state.clickTargetOffset))
        )

        // target position by dragging
        const dragPosition$ = panStart$.pipe(
            switchMap(() => {
                const {position} = this.state
                return merge(
                    panMove$.pipe(
                        map(event => this.clampPosition(position + event.deltaX)),
                        distinctUntilChanged()
                    ),
                    panEnd$.pipe(
                        map(() => this.snapPosition(this.state.previewPosition))
                    )
                )
            })
        )

        const handleDragging$ = merge(
            of(false),
            panStart$.pipe(mapTo(true)),
            panEnd$.pipe(mapTo(false)),
        )

        const targetPosition$ = merge(clickPosition$, dragPosition$).pipe(
            map(position => Math.round(position))
        )

        const handlePosition$ = targetPosition$.pipe(
            withLatestFrom(handleDragging$),
            switchMap(([targetPosition, dragging]) => {
                const {position} = this.state
                return animationFrame$.pipe(
                    map(() => targetPosition),
                    scan(lerp(dragging ? .30 : .15), position),
                    map(position => Math.round(position)),
                    distinctUntilChanged()
                )
            })
        )

        const handleMoving$ = combineLatest(handlePosition$, targetPosition$).pipe(
            map(([currentPosition, targetPosition]) => currentPosition !== targetPosition),
            distinctUntilChanged()
        )

        const previewPosition$ = merge(targetPosition$, hoverPosition$).pipe(
            map(position => position === null ? null : this.snapPosition(position)),
            distinctUntilChanged()
        )

        const inhibitInput$ = handleMoving$.pipe(
            withLatestFrom(handleDragging$),
            map(([dragging, moving]) => dragging || moving)
        )

        const updateInput$ = merge(clickPosition$, panEnd$)

        addSubscription(
            handlePosition$.subscribe(position =>
                this.setHandlePosition(position)
            ),
            previewPosition$.subscribe(previewPosition =>
                this.setPreviewPosition(previewPosition)
            ),
            handleDragging$.subscribe(dragging =>
                this.setDragging(dragging)
            ),
            inhibitInput$.subscribe(inhibit =>
                this.setInhibitInput(inhibit)
            ),
            updateInput$.subscribe(() =>
                this.updateInputValue()
            )
        )
    }

    componentDidUpdate(prevProps) {
        const {inhibitInput} = this.state
        if (!inhibitInput && !_.isEqual(prevProps, this.props)) {
            this.setHandlePositionByValue()
        }
    }

    toPosition(value) {
        const {normalize, width} = this.props
        return normalize(value) * width
    }

    toValue(position) {
        const {denormalize, width} = this.props
        return denormalize(position / width)
    }

    clampPosition(position) {
        const {width} = this.props
        return Math.round(
            clamp(position, {
                min: 0,
                max: width
            })
        )
    }

    snapPosition(value) {
        const {ticks, snap} = this.props
        if (snap) {
            const closest = _(ticks)
                .map(({position}) => ({position, distance: Math.abs(position - value)}))
                .sortBy('distance')
                .head()
            return closest.position
        } else {
            return value
        }
    }

    setHandlePositionByValue() {
        const {input} = this.props
        this.setHandlePosition(this.toPosition(input.value))
    }

    setHandlePosition(position) {
        if (position >= 0) {
            position = Math.round(position)
            if (position !== this.state.position) {
                this.setState({position})
            }
        }
    }

    updateInputValue() {
        const {input, decimals, onChange} = this.props
        const {previewPosition} = this.state
        const value = this.toValue(this.snapPosition(previewPosition))
        const factor = Math.pow(10, decimals)
        const roundedValue = Math.round(value * factor) / factor
        input.set(roundedValue)
        onChange && onChange(roundedValue)
    }

    setPreviewPosition(previewPosition) {
        this.setState({previewPosition})
    }

    setInhibitInput(inhibitInput) {
        this.setState({inhibitInput})
    }

    setDragging(dragging) {
        this.setState({dragging})
    }

    setClickTargetOffset() {
        const {clickTarget} = this.props
        const clickTargetOffset = Math.trunc(clickTarget.current.getBoundingClientRect().left)
        if (this.state.clickTargetOffset !== clickTargetOffset) {
            this.setState({clickTargetOffset})
        }
    }
}

const SliderDynamics = compose(
    _SliderDynamics,
    withSubscriptions()
)

SliderDynamics.propTypes = {
    denormalize: PropTypes.func.isRequired,
    input: PropTypes.object.isRequired,
    maxValue: PropTypes.number.isRequired,
    minValue: PropTypes.number.isRequired,
    normalize: PropTypes.func.isRequired,
    decimals: PropTypes.number,
    invert: PropTypes.bool,
    snap: PropTypes.bool,
    ticks: PropTypes.array,
    width: PropTypes.number,
    onChange: PropTypes.func
}

export class FormSlider extends React.Component {
    state = {
        width: null,
        ticks: [],
        minValue: null,
        maxValue: null
    }

    static getDerivedStateFromProps(props, state) {
        const mapTicks = ticks =>
            ticks.map((tick, index) => {
                if (_.isObject(tick)) {
                    const value = tick.value || index
                    const label = tick.label || value
                    return {value, label}
                }
                if (_.isString(tick)) {
                    return {value: index, externalValue: tick, label: tick}
                }
                return {value: tick, externalValue: tick, label: tick}
            })

        const ticks = mapTicks(props.ticks ? props.ticks : [props.minValue, props.maxValue])
        const minValue = props.minValue || ticks.reduce((min, {value}) => Math.min(min, value), null)
        const maxValue = props.maxValue || ticks.reduce((max, {value}) => Math.max(max, value), null)
        return {
            ticks: ticks
                .filter(({value}) => value >= minValue && value <= maxValue)
                .map(tick => ({
                    ...tick,
                    position: state.width * normalize(tick.value, minValue, maxValue, props.scale, props.invert)
                })),
            minValue,
            maxValue
        }
    }

    renderInfo() {
        const {input, info, alignment} = this.props
        const {ticks} = this.state
        const tick = ticks && ticks.find(tick => tick.value === input.value)
        const label = (tick && tick.label) || input.value
        return info ? (

                {_.isFunction(info) ? info(label, input.value) : info}

        ) : null
    }

    renderSlider() {
        const {width} = this.state
        return (

                    <ElementResizeDetector onResize={({width}) => this.setState({width})}/>
                    {width ? this.renderContainer() : null}

        )
    }

    renderContainer() {
        const {input, scale = 'linear', decimals = 0, snap, range = 'left', invert = false, info, onChange} = this.props
        const {ticks, minValue, maxValue, width} = this.state
        return (
            <SliderContainer
                input={input}
                minValue={minValue}
                maxValue={maxValue}
                decimals={decimals}
                ticks={ticks}
                snap={snap}
                range={range}
                scale={scale}
                invert={invert}
                info={info}
                width={width}
                onChange={onChange}
            />
        )

    }

    render() {
        const {label, tooltip, tooltipPlacement, alignment, disabled} = this.props
        return (
            <Widget
                className={styles.wrapper}
                label={label}
                alignment={alignment}
                disabled={disabled}
                tooltip={tooltip}
                tooltipPlacement={tooltipPlacement}>
                {this.renderInfo()}
                {this.renderSlider()}
            </Widget>
        )
    }
}

FormSlider.propTypes = {
    input: PropTypes.object.isRequired,
    alignment: PropTypes.oneOf(['left', 'center', 'right']),
    decimals: PropTypes.number,
    disabled: PropTypes.any,
    info: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.func
    ]),
    invert: PropTypes.any,
    label: PropTypes.string,
    maxValue: PropTypes.number,
    minValue: PropTypes.number,
    range: PropTypes.oneOf(['none', 'low', 'high']),
    scale: PropTypes.oneOf(['linear', 'log']),
    snap: PropTypes.any,
    ticks: PropTypes.oneOfType([
        // PropTypes.number,
        PropTypes.array
    ]),
    tooltip: PropTypes.string,
    tooltipPlacement: PropTypes.string,
    onChange: PropTypes.func
}
<|endoftext|>


<commit_before>/*
 * grunt-custom
 * https://github.com/felixlaumon/grunt-custom
 *
 * Copyright (c) 2012 Felix Lau
 * Licensed under the MIT license.
 */

module.exports = function(grunt) {

  // Please see the grunt documentation for more information regarding task and
  // helper creation: https://github.com/cowboy/grunt/blob/master/docs/toc.md

  // ==========================================================================
  // TASKS
  // ==========================================================================

  /*
    Usage: grunt custom:+optionA,-optionB,+optionC
  */
  grunt.registerTask('custom', 'Customize files to be concatenated', function(input) {
    grunt.helper('custom', input);
  });

  grunt.registerHelper('custom', function(input, opts) {
    var _ = grunt.utils._;
    opts = opts || grunt.config('build');

    // Transform string input +optionA,-optionB,+optionC into an object
    // { ..., optionA: { op: '+', file: [...] } ,...}
    var modulesName = (input && input.length) ? input.split(',') : [];
    var buildOpts = {};
    var shouldFail;
    modulesName.forEach(function(moduleName){
      // Check if the first letter is "+" or "-". Otherwise fail.
      if (moduleName[0] !== '+' && moduleName[0] !== '-') {
        grunt.log.writeln(moduleName + ' lacks an operator ("+" or "-")');
        shouldFail = true;
      }
      var name = moduleName.replace(/[\+]*[\-]*/g, '');
      // Check if the name has been inputted because "exclusivity beats inclusivity"
      // If yes, check if already excluded
      // If already excluded, ignore the inclusivity
      if (buildOpts[name]) {
        if (buildOpts[name].op === '-') {
          return;
        }
      }
      buildOpts[name] = {
        op: moduleName[0],
        file: opts.options[name]
      };
    });
    if (shouldFail) {
      return false;
    }

    var essential = opts.essential;
    var dest = opts.dest ? opts.dest : grunt.config('concat.dest');
    // FIXME: why grunt.template.process doesn't works?
    dest = _.template(dest, grunt.config());
    var options = opts.options;

    // Get a list of file names that should be included
    var filesToBeConcat = [];
    Object.keys(options).forEach(function(key){
      var file = options[key];
      file = file.map(function(f){
        // FIXME: why grunt.template.process or grunt.file.expandFiles doesn't works?
        return _.template(f, grunt.config());
      });
      // If key is included in the essential list, concat
      if (essential.indexOf(key) !== -1) {
        // Only only results in unique files
        filesToBeConcat = _.union(filesToBeConcat, file);
      // Else if key is included in buildOpts and should be included
      } else if (buildOpts[key] && buildOpts[key].op === '+') {
        // Only only results in unique files
        filesToBeConcat = _.union(filesToBeConcat, file);
      }

      // Take out any files that is explicitly excluded
      if (buildOpts[key] && buildOpts[key].op === '-') {
        filesToBeConcat = _.difference(filesToBeConcat, file);
      }
    });

    // Insert intro, if provided
    if (opts.intro && opts.intro.length) {
      filesToBeConcat = _.union(opts.intro, filesToBeConcat);
    }

    // Insert outro, if provided
    if (opts.outro && opts.outro.length) {
      filesToBeConcat = _.union(filesToBeConcat, opts.outro);
    }

    // Prefix file path if necessary
    if (opts.prefix) {
      filesToBeConcat = filesToBeConcat.map(function(fileName) {
        return opts.prefix + fileName;
      });
    }

    // Only concatenate files if there is something we should concatenate
    if (filesToBeConcat.length) {
      grunt.log.writeln('Concatenating ' + filesToBeConcat.toString().replace(/,/g, ', '));
    } else {
      grunt.log.writeln('No files to be concatenated');
      return false;
    }

    // TODO: defaults separator to grunt's one
    var separator = opts.separator ? opts.separator : '\n';
    var src = grunt.helper('concat', filesToBeConcat, {separator: separator});
    grunt.file.write(dest, src);

    // Fail task if errors were logged.
    if (this.errorCount) { return false; }

    // Otherwise, print a success message.
    grunt.log.writeln('File "' + dest + '" created.');
  });

};
<commit_msg>Update dest defaults<commit_after>/*
 * grunt-custom
 * https://github.com/felixlaumon/grunt-custom
 *
 * Copyright (c) 2012 Felix Lau
 * Licensed under the MIT license.
 */

module.exports = function(grunt) {

  // Please see the grunt documentation for more information regarding task and
  // helper creation: https://github.com/cowboy/grunt/blob/master/docs/toc.md

  // ==========================================================================
  // TASKS
  // ==========================================================================

  /*
    Usage: grunt custom:+optionA,-optionB,+optionC
  */
  grunt.registerTask('custom', 'Customize files to be concatenated', function(input) {
    grunt.helper('custom', input);
  });

  grunt.registerHelper('custom', function(input, opts) {
    var _ = grunt.utils._;
    opts = opts || grunt.config('build');

    // Transform string input +optionA,-optionB,+optionC into an object
    // { ..., optionA: { op: '+', file: [...] } ,...}
    var modulesName = (input && input.length) ? input.split(',') : [];
    var buildOpts = {};
    var shouldFail;
    modulesName.forEach(function(moduleName){
      // Check if the first letter is "+" or "-". Otherwise fail.
      if (moduleName[0] !== '+' && moduleName[0] !== '-') {
        grunt.log.writeln(moduleName + ' lacks an operator ("+" or "-")');
        shouldFail = true;
      }
      var name = moduleName.replace(/[\+]*[\-]*/g, '');
      // Check if the name has been inputted because "exclusivity beats inclusivity"
      // If yes, check if already excluded
      // If already excluded, ignore the inclusivity
      if (buildOpts[name]) {
        if (buildOpts[name].op === '-') {
          return;
        }
      }
      buildOpts[name] = {
        op: moduleName[0],
        file: opts.options[name]
      };
    });
    if (shouldFail) {
      return false;
    }

    var essential = opts.essential;
    var dest = opts.dest ? opts.dest : grunt.config('concat.dist.dest');
    // FIXME: why grunt.template.process doesn't works?
    dest = _.template(dest, grunt.config());
    var options = opts.options;

    // Get a list of file names that should be included
    var filesToBeConcat = [];
    Object.keys(options).forEach(function(key){
      var file = options[key];
      file = file.map(function(f){
        // FIXME: why grunt.template.process or grunt.file.expandFiles doesn't works?
        return _.template(f, grunt.config());
      });
      // If key is included in the essential list, concat
      if (essential.indexOf(key) !== -1) {
        // Only only results in unique files
        filesToBeConcat = _.union(filesToBeConcat, file);
      // Else if key is included in buildOpts and should be included
      } else if (buildOpts[key] && buildOpts[key].op === '+') {
        // Only only results in unique files
        filesToBeConcat = _.union(filesToBeConcat, file);
      }

      // Take out any files that is explicitly excluded
      if (buildOpts[key] && buildOpts[key].op === '-') {
        filesToBeConcat = _.difference(filesToBeConcat, file);
      }
    });

    // Insert intro, if provided
    if (opts.intro && opts.intro.length) {
      filesToBeConcat = _.union(opts.intro, filesToBeConcat);
    }

    // Insert outro, if provided
    if (opts.outro && opts.outro.length) {
      filesToBeConcat = _.union(filesToBeConcat, opts.outro);
    }

    // Prefix file path if necessary
    if (opts.prefix) {
      filesToBeConcat = filesToBeConcat.map(function(fileName) {
        return opts.prefix + fileName;
      });
    }

    // Only concatenate files if there is something we should concatenate
    if (filesToBeConcat.length) {
      grunt.log.writeln('Concatenating ' + filesToBeConcat.toString().replace(/,/g, ', '));
    } else {
      grunt.log.writeln('No files to be concatenated');
      return false;
    }

    // TODO: defaults separator to grunt's one
    var separator = opts.separator ? opts.separator : '\n';
    var src = grunt.helper('concat', filesToBeConcat, {separator: separator});
    grunt.file.write(dest, src);

    // Fail task if errors were logged.
    if (this.errorCount) { return false; }

    // Otherwise, print a success message.
    grunt.log.writeln('File "' + dest + '" created.');
  });

};
<|endoftext|>


<commit_before>var entries = ['formula', 'formula_name_structure_1','reference_1', 'hf298_1', 'calc_quality', 'max_lst_sq_error_1', 'source', 'date', 'additional_information_1'];

function createTable(dataset){

	var table = d3.select("#table").append("table");
    var thead = table.append("thead");
    var tbody = table.append("tbody");
	//add header row:
	var header_entries = ["CAS"].concat(entries);
	thead.selectAll("th").data(header_entries).enter().append('th').text(function(d) {
		return d;
	});

	//append rows for each entry of 'specie':
	var species = dataset;
	var tr = d3.select("tbody").selectAll("tr").data(species).enter().append("tr");

	// CAS
	tr.selectAll("tr").data(function(d) {
		return d3.select(d)[0];
	}).enter().append("td").text(function(d) {
		var cas = d.getAttribute("CAS");
		return cas;
	}).attr('id', 'CAS');

	//enter data for the child nodes:
	for (var i = 0; i < entries.length; i++) {
		var entry = entries[i];
		tr.selectAll("tr").data(function(d) {
			return d3.select(d).select(entry)[0];
		}).enter().append("td").text(function(d) {
			return (d == null) ? "" : d.textContent;
		}).attr('id', entry);
	}

	 //remove CAS number from td:
	 d3.selectAll('#CAS').text('');
	 //append an img element and set it to the image generated by the CAS number:
	 d3.selectAll('#CAS').append("img")
	 //.attr("src", function(d){return create_image_url(d.getAttribute("CAS"));});
	 .attr("alt", function(d){return d.getAttribute("CAS");});

	/*
	 * https://github.com/jmosbech/StickyTableHeaders
	 * bower install StickyTableHeaders
	 */
	$('table').stickyTableHeaders();

	//select all table rows and print values in kJ by default:
	var hf_arr = d3.selectAll("#hf298_1")[0],
	    hf_data = [];

	for(var i = 0; i < hf_arr.length; i++){
		var unit_kcal = false,//reset
		    hf_val = hf_arr[i].textContent;
		unit_kcal = hf_val.toUpperCase().indexOf('CAL') > -1 ? true: false;

		hf_val = hf_val.split('KJ')[0].split('KCAL')[0];
		arr = hf_val.split('+/-');
		hf_val = parseFloat(arr[0].trim());
		hf_data.push((unit_kcal? hf_val*4.186 : hf_val).toFixed(2));
	}

	//update existing nodes with parsed values in kJ:
	var hf_nodes = d3.selectAll("#hf298_1");
	hf_nodes.data(hf_data).text(function (d){return d;});

	 //we push a new header entry 'inchi', and enter the new data array in the header rows:
  	header_entries.push('uncertainty');
	thead.selectAll("th").data(header_entries).enter().append('th').text(function(d) {
		return d;
	});

	//we select all table rows and add an entry for the 'uncertainty' table cell:
	tr.selectAll("tr").data(function(d) {
		return d3.select(d)[0];
	}).enter().append("td").text(function(d) {
		var uncertainty = get_uncertainty(d);
		return uncertainty;
	}).attr('id', 'uncertainty');

  //we push a new header entry 'inchi', and enter the new data array in the header rows:
  	header_entries.push('inchi');
	thead.selectAll("th").data(header_entries).enter().append('th').text(function(d) {
		return d;
	});

	//we select all table rows and add an entry for the inchi table cell:
	tr.selectAll("tr").data(function(d) {
		return d3.select(d)[0];
	}).enter().append("td").text(function(d) {
		var cas = d.getAttribute("CAS");
		var inchi = get_inchi(cas);
		return inchi;
	}).attr('id', 'inchi');

	sortColumns(header_entries);
}

function httpGet(theUrl)
{
    var xmlHttp = null;

    xmlHttp = new XMLHttpRequest();
    //xmlHttp.timeout = 1000;//only possible for asynchronous requests
    xmlHttp.open( "GET", theUrl, false );//asynchronous = true/false
    xmlHttp.send( null );
    return xmlHttp.responseText;
}
function get_inchi(cas_string){
	if(cas_string.indexOf('N/A')>-1) return '';
	cas_arr = cas_string.split(' ');
	var cas = cas_arr[0];
	var base_url = 'http://cactus.nci.nih.gov/chemical/structure/';
	//var inchi = httpGet(base_url + cas + "/stdinchi");
	var inchi = base_url + cas + "/stdinchi";
	return inchi.indexOf("404") > -1 ? '': inchi;
}
function create_image_url(cas) {
	var base_url = "http://cactus.nci.nih.gov/chemical/structure/";
	var background_color = "e5f5f9";//hex
	return base_url + cas + "/image?format=png&bgcolor=%23"+background_color+"&header=\"" + cas +"\"";
}

function get_uncertainty(species) {

	var unit_kcal = false, //reset
	hf = species.getElementsByTagName('hf298_1');

	if (hf.length == 0){//check if specie node has 'hf298_1' child:
		return NaN;
	}
	hf_val = hf[0].textContent;
	unit_kcal = hf_val.toUpperCase().indexOf('CAL') > -1 ? true: false;

	hf_val = hf_val.split('KJ')[0].split('KCAL')[0];
	hf_val = hf_val.split('+/-');

	if (hf_val.length > 1) {
		uncertainty = parseFloat(hf_val[1].trim());
		uncertainty = ( unit_kcal ? uncertainty * 4.186 : uncertainty).toFixed(2);
	} else {
		uncertainty = NaN;
	}

	return uncertainty;
}<commit_msg>chain some parsing functions in get_uncertainty(species)<commit_after>var entries = ['formula', 'formula_name_structure_1','reference_1', 'hf298_1', 'calc_quality', 'max_lst_sq_error_1', 'source', 'date', 'additional_information_1'];

function createTable(dataset){

	var table = d3.select("#table").append("table");
    var thead = table.append("thead");
    var tbody = table.append("tbody");
	//add header row:
	var header_entries = ["CAS"].concat(entries);
	thead.selectAll("th").data(header_entries).enter().append('th').text(function(d) {
		return d;
	});

	//append rows for each entry of 'specie':
	var species = dataset;
	var tr = d3.select("tbody").selectAll("tr").data(species).enter().append("tr");

	// CAS
	tr.selectAll("tr").data(function(d) {
		return d3.select(d)[0];
	}).enter().append("td").text(function(d) {
		var cas = d.getAttribute("CAS");
		return cas;
	}).attr('id', 'CAS');

	//enter data for the child nodes:
	for (var i = 0; i < entries.length; i++) {
		var entry = entries[i];
		tr.selectAll("tr").data(function(d) {
			return d3.select(d).select(entry)[0];
		}).enter().append("td").text(function(d) {
			return (d == null) ? "" : d.textContent;
		}).attr('id', entry);
	}

	 //remove CAS number from td:
	 d3.selectAll('#CAS').text('');
	 //append an img element and set it to the image generated by the CAS number:
	 d3.selectAll('#CAS').append("img")
	 //.attr("src", function(d){return create_image_url(d.getAttribute("CAS"));});
	 .attr("alt", function(d){return d.getAttribute("CAS");});

	/*
	 * https://github.com/jmosbech/StickyTableHeaders
	 * bower install StickyTableHeaders
	 */
	$('table').stickyTableHeaders();

	//select all table rows and print values in kJ by default:
	var hf_arr = d3.selectAll("#hf298_1")[0],
	    hf_data = [];

	for(var i = 0; i < hf_arr.length; i++){
		var unit_kcal = false,//reset
		    hf_val = hf_arr[i].textContent;
		unit_kcal = hf_val.toUpperCase().indexOf('CAL') > -1 ? true: false;

		hf_val = hf_val.split('KJ')[0].split('KCAL')[0];
		arr = hf_val.split('+/-');
		hf_val = parseFloat(arr[0].trim());
		hf_data.push((unit_kcal? hf_val*4.186 : hf_val).toFixed(2));
	}

	//update existing nodes with parsed values in kJ:
	var hf_nodes = d3.selectAll("#hf298_1");
	hf_nodes.data(hf_data).text(function (d){return d;});

	 //we push a new header entry 'inchi', and enter the new data array in the header rows:
  	header_entries.push('uncertainty');
	thead.selectAll("th").data(header_entries).enter().append('th').text(function(d) {
		return d;
	});

	//we select all table rows and add an entry for the 'uncertainty' table cell:
	tr.selectAll("tr").data(function(d) {
		return d3.select(d)[0];
	}).enter().append("td").text(function(d) {
		var uncertainty = get_uncertainty(d);
		return uncertainty;
	}).attr('id', 'uncertainty');

  //we push a new header entry 'inchi', and enter the new data array in the header rows:
  	header_entries.push('inchi');
	thead.selectAll("th").data(header_entries).enter().append('th').text(function(d) {
		return d;
	});

	//we select all table rows and add an entry for the inchi table cell:
	tr.selectAll("tr").data(function(d) {
		return d3.select(d)[0];
	}).enter().append("td").text(function(d) {
		var cas = d.getAttribute("CAS");
		var inchi = get_inchi(cas);
		return inchi;
	}).attr('id', 'inchi');

	sortColumns(header_entries);
}

function httpGet(theUrl)
{
    var xmlHttp = null;

    xmlHttp = new XMLHttpRequest();
    //xmlHttp.timeout = 1000;//only possible for asynchronous requests
    xmlHttp.open( "GET", theUrl, false );//asynchronous = true/false
    xmlHttp.send( null );
    return xmlHttp.responseText;
}
function get_inchi(cas_string){
	if(cas_string.indexOf('N/A')>-1) return '';
	cas_arr = cas_string.split(' ');
	var cas = cas_arr[0];
	var base_url = 'http://cactus.nci.nih.gov/chemical/structure/';
	//var inchi = httpGet(base_url + cas + "/stdinchi");
	var inchi = base_url + cas + "/stdinchi";
	return inchi.indexOf("404") > -1 ? '': inchi;
}
function create_image_url(cas) {
	var base_url = "http://cactus.nci.nih.gov/chemical/structure/";
	var background_color = "e5f5f9";//hex
	return base_url + cas + "/image?format=png&bgcolor=%23"+background_color+"&header=\"" + cas +"\"";
}

function get_uncertainty(species) {
	var hf = species.getElementsByTagName('hf298_1');

	if (hf.length == 0){//check if specie node has 'hf298_1' child:
		return NaN;
	}
	var unit_kcal = false, //reset
	hf_val = hf[0].textContent;
	unit_kcal = hf_val.toUpperCase().indexOf('CAL') > -1 ? true : false;

	hf_val = hf_val.split('KJ')[0].split('KCAL')[0]
	.split('+/-')
	.map(function(d) {
		return parseFloat(d.trim());
	})
	.map(function(d) {
		return unit_kcal ? d * 4.186 : d;
	});
	hf_val.length == 1 ? hf_val.push(NaN) : hf_val;
	return hf_val[1];

}<|endoftext|>


<commit_before>/* eslint-env mocha, node */
import {expect} from 'chai';

import CubicEasing from '../../src/easings/Cubic.es6';

describe('Animation', () => {
  it('should interpolate cubical', () => {
    const easing = new CubicEasing('in');
    for (let i = 0; i < 1; i += 0.1) {
      expect(easing.getValueForProgress(i)).to.equal(Math.pow(i, 3));
    }
  });
});
<commit_msg>temp remove animation test<commit_after><|endoftext|>


<commit_before>'use strict';

module.exports = function(gulp) {
  var spawn = require('child_process').spawn,
      pkg  = require('../package.json');

  if (!pkg.hasOwnProperty('blenderCmd')) {
    return;
  }
  var cmd = pkg.blenderCmd;

  gulp.task('deploy', function() {
    if (cmd.hasOwnProperty('deploy') && cmd.deploy !== '') {
      var deploy = spawn(cmd.deploy);
      deploy.stdout.on('data', function(data) {
        process.stdout.write(data);
      });
    } else {
      console.log('Deployment command is missing! Add blenderCmd.deploy to package.json to use this task!');
    }
  });

};
<commit_msg>Using promises for the deploy task. Fixes #25.<commit_after>'use strict';

module.exports = function(gulp) {
  var spawn = require('child_process').spawn,
      pkg  = require('../package.json'),
      Q = require('q');

  if (!pkg.hasOwnProperty('blenderCmd')) {
    console.error('blenderCmd object is missing from configuration.');
    return;
  }
  var cmd = pkg.blenderCmd;

  gulp.task('deploy', function() {
    var deferred = Q.defer();

    if (cmd.hasOwnProperty('deploy') && cmd.deploy !== '') {
      var deploy = spawn(cmd.deploy);
      deploy.stdout.on('data', function(data) {
        process.stdout.write(data);
      });

      deploy.stdout.on('close', function(){
        return deferred.resolve();
      });
    } else {
      console.log('Deployment command is missing! Add blenderCmd.deploy to package.json to use this task!');
      return deferred.resolve();
    }

    return deferred.promise;
  });
};
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');

const request = require('request');

const providers = require('./providers');

module.exports = function(config) {
  let output = {
    options: config,
    results: []
  };

  let remainingIterations = config.test.iterations;
  let instanceIds = {};

  providers.prepareConcurrency(config, () => {
    let executeRound = (currentRound) => {
      providers.deploy(config, (uris) => {
        if (uris.length > remainingIterations) {
          uris = uris.slice(0, remainingIterations);
        }

        let remainingInRound = uris.length;

        for (let i = 0; i < uris.length; i++) {
          output.results[i] = [];
          let remainingInIteration = config.test.timings.length;

          let delay = 3 * config.test.timings.length * config.test.concurrency;
          let start = Date.now() + delay;

          for (let n = 0; n < config.test.timings.length; n++) {
            setTimeout(
              (currentIteration) => request.post({
                url: uris[i],
                body: JSON.stringify({ duration: config.function.duration }),
                time: true
              }, (err, res, body) => {
                if (err) throw err;
                if (res.statusCode != 200) {
                  throw new Error('Unexpected response. Status code: ' + res.statusCode + '. Body: ' + body);
                }

                let parsedBody = JSON.parse(body);
                if (parsedBody.id in instanceIds && instanceIds[parsedBody.id] !== currentIteration) {
                  throw new Error('Function id mismatch in ' + currentIteration + ': ' + parsedBody.id);
                }
                instanceIds[parsedBody.id] = currentIteration;

                let overhead = res.elapsedTime - parsedBody.duration;
                console.log(overhead + 'ms');

                output.results[currentIteration][n] = {
                  executionOverhead: overhead,
                  intendedRequestStart: config.test.timings[n],
                  requestStart: res.request.startTime - start,
                  requestDuration: res.elapsedTime,
                  functionDuration: parsedBody.duration,
                  instanceId: parsedBody.id
                };

                if (--remainingInIteration == 0) {
                  console.log('Finished iteration ' + currentIteration);

                  if (--remainingIterations == 0) {
                    fs.writeFile(config.resultsFile, JSON.stringify(output, null, 4));
                    providers.cleanupDeployment(config);
                  } else if (--remainingInRound == 0) {
                    executeRound(currentRound + 1);
                  }
                }
              }),
              start + config.test.timings[n] - Date.now(),
              currentRound * config.test.concurrency + i
            );
          }
        }
      });
    };
    executeRound(0);
  });
}
<commit_msg>Add Content-Type header for Google request body interpretation<commit_after>'use strict';

const fs = require('fs');

const request = require('request');

const providers = require('./providers');

module.exports = function(config) {
  let output = {
    options: config,
    results: []
  };

  let remainingIterations = config.test.iterations;
  let instanceIds = {};

  providers.prepareConcurrency(config, () => {
    let executeRound = (currentRound) => {
      providers.deploy(config, (uris) => {
        if (uris.length > remainingIterations) {
          uris = uris.slice(0, remainingIterations);
        }

        let remainingInRound = uris.length;

        for (let i = 0; i < uris.length; i++) {
          output.results[i] = [];
          let remainingInIteration = config.test.timings.length;

          let delay = 3 * config.test.timings.length * config.test.concurrency;
          let start = Date.now() + delay;

          for (let n = 0; n < config.test.timings.length; n++) {
            setTimeout(
              (currentIteration) => request.post({
                url: uris[i],
                body: JSON.stringify({ duration: config.function.duration }),
                headers: { 'Content-Type': 'application/json' },
                time: true
              }, (err, res, body) => {
                if (err) throw err;
                if (res.statusCode != 200) {
                  throw new Error('Unexpected response. Status code: ' + res.statusCode + '. Body: ' + body);
                }

                let parsedBody = JSON.parse(body);
                if (parsedBody.id in instanceIds && instanceIds[parsedBody.id] !== currentIteration) {
                  throw new Error('Function id mismatch in ' + currentIteration + ': ' + parsedBody.id);
                }
                instanceIds[parsedBody.id] = currentIteration;

                let overhead = res.elapsedTime - parsedBody.duration;
                console.log(overhead + 'ms');

                output.results[currentIteration][n] = {
                  executionOverhead: overhead,
                  intendedRequestStart: config.test.timings[n],
                  requestStart: res.request.startTime - start,
                  requestDuration: res.elapsedTime,
                  functionDuration: parsedBody.duration,
                  instanceId: parsedBody.id
                };

                if (--remainingInIteration == 0) {
                  console.log('Finished iteration ' + currentIteration);

                  if (--remainingIterations == 0) {
                    fs.writeFile(config.resultsFile, JSON.stringify(output, null, 4));
                    providers.cleanupDeployment(config);
                  } else if (--remainingInRound == 0) {
                    executeRound(currentRound + 1);
                  }
                }
              }),
              start + config.test.timings[n] - Date.now(),
              currentRound * config.test.concurrency + i
            );
          }
        }
      });
    };
    executeRound(0);
  });
}
<|endoftext|>


<commit_before>/**
 * @file listSelfAssignableRoles command
 * @author Sankarsan Kampa (a.k.a k3rn31p4nic)
 * @license MIT
 */

exports.exec = async (Bastion, message, args) => {
  try {
    let guildSettings = await Bastion.db.get(`SELECT selfAssignableRoles FROM guildSettings WHERE guildID=${message.guild.id}`);

    if (!guildSettings || !guildSettings.selfAssignableRoles) {
      /**
      * Error condition is encountered.
      * @fires error
      */
      return Bastion.emit('error', Bastion.strings.error(message.guild.language, 'notFound'), Bastion.strings.error(message.guild.language, 'notSet', true, 'self-assignable roles'), message.channel);
    }

    let roles = guildSettings.selfAssignableRoles.split(' ');
    roles = roles.filter(r => message.guild.roles.get(r));
    roles = [ ...new Set(roles) ];

    let roleNames = [];
    for (let i = 0; i < roles.length; i++) {
      roleNames.push(message.guild.roles.get(roles[i]).name);
    }
    roleNames = roleNames.map((r, i) => `${i + 1}. ${r}`);

    let noOfPages = roleNames.length / 10;
    let i = (args.page > 0 && args.page < noOfPages + 1) ? args.page : 1;
    i = i - 1;

    message.channel.send({
      embed: {
        color: Bastion.colors.BLUE,
        title: 'Self assignable roles:',
        description: roleNames.slice(i * 10, (i * 10) + 10).join('\n'),
        footer: {
          text: `Page: ${i + 1} of ${noOfPages > parseInt(noOfPages) ? parseInt(noOfPages) + 1 : parseInt(noOfPages)}`
        }
      }
    }).catch(e => {
      Bastion.log.error(e);
    });
  }
  catch (e) {
    Bastion.log.error(e);
  }
};

exports.config = {
  aliases: [ 'lsar' ],
  enabled: true,
  argsDefinitions: [
    { name: 'page', type: Number, alias: 'p', defaultOption: true, defaultValue: 1 }
  ]
};

exports.help = {
  name: 'listSelfAssignableRoles',
  botPermission: '',
  userTextPermission: '',
  userVoicePermission: '',
  usage: 'listSelfAssignableRoles [page_no]',
  example: [ 'listSelfAssignableRoles', 'listSelfAssignableRoles 2' ]
};
<commit_msg>Implemented new DB - lsar command.<commit_after>/**
 * @file listSelfAssignableRoles command
 * @author Sankarsan Kampa (a.k.a k3rn31p4nic)
 * @license MIT
 */

exports.exec = async (Bastion, message, args) => {
  try {
    let guildModel = await Bastion.database.models.guild.findOne({
      attributes: [ 'selfAssignableRoles' ],
      where: {
        guildID: message.guild.id
      }
    });

    if (!guildModel || !guildModel.dataValues.selfAssignableRoles) {
      /**
      * Error condition is encountered.
      * @fires error
      */
      return Bastion.emit('error', Bastion.strings.error(message.guild.language, 'notFound'), Bastion.strings.error(message.guild.language, 'notSet', true, 'self-assignable roles'), message.channel);
    }

    let roles = guildModel.dataValues.selfAssignableRoles.split(' ');
    roles = roles.filter(r => message.guild.roles.get(r));
    roles = [ ...new Set(roles) ];

    let roleNames = [];
    for (let i = 0; i < roles.length; i++) {
      roleNames.push(message.guild.roles.get(roles[i]).name);
    }
    roleNames = roleNames.map((r, i) => `${i + 1}. ${r}`);

    let noOfPages = roleNames.length / 10;
    let i = (args.page > 0 && args.page < noOfPages + 1) ? args.page : 1;
    i = i - 1;

    message.channel.send({
      embed: {
        color: Bastion.colors.BLUE,
        title: 'Self assignable roles:',
        description: roleNames.slice(i * 10, (i * 10) + 10).join('\n'),
        footer: {
          text: `Page: ${i + 1} of ${noOfPages > parseInt(noOfPages) ? parseInt(noOfPages) + 1 : parseInt(noOfPages)}`
        }
      }
    }).catch(e => {
      Bastion.log.error(e);
    });
  }
  catch (e) {
    Bastion.log.error(e);
  }
};

exports.config = {
  aliases: [ 'lsar' ],
  enabled: true,
  argsDefinitions: [
    { name: 'page', type: Number, alias: 'p', defaultOption: true, defaultValue: 1 }
  ]
};

exports.help = {
  name: 'listSelfAssignableRoles',
  botPermission: '',
  userTextPermission: '',
  userVoicePermission: '',
  usage: 'listSelfAssignableRoles [page_no]',
  example: [ 'listSelfAssignableRoles', 'listSelfAssignableRoles 2' ]
};
<|endoftext|>


<commit_before>/* Test the Ephemeral socket
 */

var StatsDClient = require('../lib/statsd-client'),
    FakeServer = require('./FakeServer'),
    assert = require('chai').assert;

/*global describe before it*/

describe('EphemeralSocket', function () {
    var s, c;

    before(function (done) {
        s = new FakeServer();
        c = new StatsDClient({
            //debug: true
        });

        s.start(done);
    });

    it("Send 50 messages", function (done) {
        for (var i = 0; i < 50; i += 1) {
            c.gauge('foobar', i);
        }
        setTimeout(function () {
            // Received some packets
            assert.closeTo(
                s._packetsReceived.length,
                50, // Should get 100
                5 // ±10
            );
            s._packetsReceived = [];
            return done();
        }, 25);
    });

    it("Closes _socket when 'error' is emitted", function (done) {
        var es = c._ephemeralSocket;
        es._createSocket(function () {
            // Emit error, wait some and check.
            es._socket.emit('error');
            setTimeout(function () {
                assert(!es._socket, "Socket isn't closed.");
                done();
            }, 10);
        });
    });
});
<commit_msg>Test on 'raw' EphemeralSocket in ditto tests.<commit_after>/* Test the Ephemeral socket
 */

var EphemeralSocket = require('../lib/EphemeralSocket'),
    FakeServer = require('./FakeServer'),
    assert = require('chai').assert;

/*global describe before it*/

describe('EphemeralSocket', function () {
    var s, e;

    before(function (done) {
        s = new FakeServer();
        e = new EphemeralSocket();

        s.start(done);
    });

    it("Send 50 messages", function (done) {
        for (var i = 0; i < 50; i += 1) {
            e.send('foobar');
        }
        setTimeout(function () {
            // Received some packets
            assert.closeTo(
                s._packetsReceived.length,
                50, // Should get 100
                5 // ±10
            );
            s._packetsReceived = [];
            return done();
        }, 25);
    });

    it("Closes _socket when 'error' is emitted", function (done) {
        e._createSocket(function () {
            // Emit error, wait some and check.
            e._socket.emit('error');
            setTimeout(function () {
                assert(!e._socket, "Socket isn't closed.");
                done();
            }, 10);
        });
    });
});
<|endoftext|>


<commit_before>/*
 * grunt-gluejs
 * https://github.com/svizzari/grunt-gluejs
 *
 * Copyright (c) 2013 Stefan Vizzari
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {

  var Glue = require('gluejs'),
      fs   = require('fs'),
      path = require('path');

  // Please see the Grunt documentation for more information regarding task
  // creation: http://gruntjs.com/creating-tasks

  grunt.registerMultiTask('gluejs', 'Grunt plugin for GlueJS (~2.0)', function() {
    // Merge task-specific and/or target-specific options with these defaults.
    var options = this.options(),
        done    = this.async(),
        base    = process.cwd();

    // Iterate over all specified file groups.
    this.files.forEach(function(f) {
      var glue = new Glue();

      // Handle options
      if (options.export) { glue.export(options.export); }
      if (options.basepath) {
        glue.basepath(options.basepath);
        // We need to tell GlueJS to work from the basepath dir., not from Gruntfile dir.
        grunt.file.setBase(path.resolve(options.basepath));
      }
      // if (options.include) { glue.include(options.include); }
      if (options.exclude) { glue.exclude(options.exclude); }
      if (options.replace) { glue.replace(options.replace); }
      if (options.main) { glue.main(options.main); }
      if (options.cache) { glue.set('cache', options.cache); }
      if (!!options.globalRequire) { glue.set('global-require', true); }
      // TODO: Add support for remap

      f.src.forEach(function(src) {
        if (src !== f.dest) {
          glue.include(src);
        }
      });

      // Reinstate the original working directory, if it was changed for basepath
      if (options.basepath) { grunt.file.setBase(base); }

      grunt.log.write('Glueing ' + f.dest + '...');

      glue.render(function(err, output) {
        if (err) {
          grunt.log.error();
          done(false);
        } else {
          grunt.file.write(f.dest, output);
          grunt.log.ok();
          done();
        }
      });

    });
  });

};
<commit_msg>Emits events after render<commit_after>/*
 * grunt-gluejs
 * https://github.com/svizzari/grunt-gluejs
 *
 * Copyright (c) 2013 Stefan Vizzari
 * Licensed under the MIT license.
 */

'use strict';

module.exports = function(grunt) {

  var Glue = require('gluejs'),
      fs   = require('fs'),
      path = require('path');

  // Please see the Grunt documentation for more information regarding task
  // creation: http://gruntjs.com/creating-tasks

  grunt.registerMultiTask('gluejs', 'Grunt plugin for GlueJS (~2.0)', function() {
    // Merge task-specific and/or target-specific options with these defaults.
    var options = this.options(),
        done    = this.async(),
        base    = process.cwd();

    // Iterate over all specified file groups.
    this.files.forEach(function(f) {
      var glue = new Glue();

      // Handle options
      if (options.export) { glue.export(options.export); }
      if (options.basepath) {
        glue.basepath(options.basepath);
        // We need to tell GlueJS to work from the basepath dir., not from Gruntfile dir.
        grunt.file.setBase(path.resolve(options.basepath));
      }
      // if (options.include) { glue.include(options.include); }
      if (options.exclude) { glue.exclude(options.exclude); }
      if (options.replace) { glue.replace(options.replace); }
      if (options.main) { glue.main(options.main); }
      if (options.cache) { glue.set('cache', options.cache); }
      if (!!options.globalRequire) { glue.set('global-require', true); }
      // TODO: Add support for remap

      f.src.forEach(function(src) {
        if (src !== f.dest) {
          glue.include(src);
        }
      });

      // Reinstate the original working directory, if it was changed for basepath
      if (options.basepath) { grunt.file.setBase(base); }

      grunt.log.write('Glueing ' + f.dest + '...');

      glue.render(function(err, output) {
        if (err) {
          grunt.log.error();
          grunt.event.emit('gluejs.fail');
          done(false);
        } else {
          grunt.file.write(f.dest, output);
          grunt.event.emit('gluejs.done');
          grunt.log.ok();
          done();
        }
      });

    });
  });

};
<|endoftext|>