<commit_before>// MIT License
//
// Copyright (c) 2017 Christopher Chudzicki
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

var container = $(".container")
container.attr("ng-app", 'math3dApp')

// app = angular.module('math3dApp', ['ui.sortable']);
app = angular.module('math3dApp', ['ui.tree', 'ngAnimate', 'ngCookies', 'ui.bootstrap', 'ui.toggle']);

// Change default tags to '[[' and ']]' to prevent conflict with Flask
app.config(function($interpolateProvider, $httpProvider) {
    $interpolateProvider.startSymbol('[[');
    $interpolateProvider.endSymbol(']]');

    // Include csrf_token in every request
    $httpProvider.defaults.xsrfCookieName = "csrf_token";
    $httpProvider.defaults.xsrfHeaderName = "X-CSRFToken";
});

app.directive('compileTemplate', ["$compile", "$parse", function($compile, $parse) {
    // http://stackoverflow.com/a/25407201/2747370
    return {
        restrict: 'A',
        link: function($scope, element, attr) {
            var parse = $parse(attr.ngBindHtml);
            function value() { return (parse($scope) || '').toString(); }

            $scope.$watch(value, function() {
                $compile(element, null, -9999)($scope);
            });
        }
    }
}]);

// http://stackoverflow.com/a/29571230/2747370
app.directive( 'elemReady', function( $parse ) {
   return {
       restrict: 'A',
       link: function( $scope, elem, attrs ) {
          elem.ready(function(){
            $scope.$apply(function(){
                var func = $parse(attrs.elemReady);
                func($scope);
            })
          })
       }
    }
})

// https://gist.github.com/BobNisco/9885852 (modifications in comments)
// Add this directive where you keep your directives
app.directive('onLongPress', function($timeout) {
    return {
		restrict: 'A',
        link: function(scope, elem, attrs) {
            var timeoutHandler;

            elem.bind('mousedown touchstart', function() {
                timeoutHandler = $timeout(function() {
                    scope.$eval(attrs.onLongPress);
                }, 600);
            });

            elem.bind('mouseup touchend', function() {
                $timeout.cancel(timeoutHandler);
            });
		}
	};
})
app.directive('onShortPress', function($timeout) {
	return {
		restrict: 'A',
		link: function($scope, $elm, $attrs) {
			$elm.bind('mousedown touchstart', function(evt) {
                // Locally scoped variable that will keep track of the short press
				$scope.shortPress = true;

				// After a timeout of 600 ms, shortPress is false;
				$timeout(function() {
					$scope.shortPress=false;
				}, 600);
			});

			$elm.bind('mouseup touchend', function(evt) {
                // Some touchscreens seem to trigger both events, so let's prevent onShortPress action from occuring too often (100ms).
                if ($scope.prevent){
                    return;
                } else {
                    $scope.prevent = true;
    				$timeout(function() {
    					$scope.prevent=false;
    				}, 500);
                }
                // Prevent the onShortPress event from firing
                if ($scope.shortPress){
					$scope.$apply(function() {
						$scope.$eval($attrs.onShortPress)
					});
                }
				// If there is an on-touch-end function attached to this element, apply it
				if ($attrs.onTouchEnd) {
					$scope.$apply(function() {
						$scope.$eval($attrs.onTouchEnd)
					});
				}
			});
		}
	};
})

app.controller('treeCtrl', function($scope)  {
    $scope.treeOptions = {
        accept: function(sourceNodeScope, destNodesScope, destIndex) {
            return sourceNodeScope.depth() - 1 === destNodesScope.depth()
        },
        toggle: function(collapsed, sourceNodeScope){
            sourceNodeScope.branch.collapsed = collapsed;
            var reflowInterval = setInterval(function(){
                var failures = reflowBranch(sourceNodeScope['branch']);
                if (failures===0){
                    clearInterval(reflowInterval);
                }
            }, 100);
        }
    };

    function reflowBranch(branch){
        // If folders are collapsed on page load, mathquill as are initialized in zero-height spans. We need to reflow each of them when the folder is expanded.
        // Reflow only really needs to be done once, has the mq-reflow tracking class.
        var failures = 0;
        _.forEach(branch.objects, function(obj){
            _.forEach(obj.wrappedMathFields, function(wmf){

                var mf = wmf.mathfield;
                var el = $(mf.el());
                if (el.css('height')==='0px'){
                    failures += 1;
                }
                else if ( !el.hasClass('mq-reflowed') ){
                    mf.reflow();
                    el.addClass('mq-reflowed');
                }

            })
        })

        return failures;
    }
});

app.controller('checkLoginCtrl', ['$scope', '$cookies', function($scope, $cookies) {
    var username = $cookies.get("username");
    if (username) {
        $scope.username = username;
        $scope.logged_in = true;
    }

    $scope.logout = function() {
        $cookies.remove("username");
        $scope.logged_in = false;
    }
}]);

app.controller('saveCtrl', ['$scope', function($scope){
    $scope.saveURL = function(){
        var url = math3d.saveSettingsAsURL();
        $("#save-modal textarea").val(url);
        $("#url-length").html(url.length);
    };
}])

app.controller('saveToDBCtrl', ['$scope', '$http', function($scope, $http) {
    $scope.saveToDB = function(){
        var title = $("#save-modal input").val();
        var serialized_setting = math3d.saveSettingsAsString();

        $http.post("/api/graph/save", {
            title: title,
            serialized_graph: serialized_setting,
        }).then(function(response) {
            if (response.data == "Success!") {
                $("#save-modal").modal('hide');
            }
        });
    }
}]);

app.controller('graphListCtrl', ['$rootScope', '$scope', '$http', function($rootScope, $scope, $http) {
    // Using rootScope so that when clicking the Graphs tab this can be called.
    // The graphs tab button is out of the scope of graphListCtrl.
    $rootScope.updateGraphs = function() {
        if ($scope.logged_in) {
            $http.get("/api/graph/get").then(function(response){
                $rootScope.graphs = response.data;
            });
        }
    }

    $scope.loadGraph = function(serialized_string) {
        math3d.clear()
        math3d.load(Math3D.decodeSettingsAsURL64(serialized_string));
    }

    $rootScope.updateGraphs();

}]);

app.controller('axesSettingsCtrl', ['$scope', function($scope){
    $scope.settings = math3d.settings;
}])

app.controller('addObjectCtrl',['$scope', '$sce', function($scope, $sce) {
    $scope.debug = arg => console.log(arg);

    $scope.math3d = math3d;

    $scope.createNewObject = function(type){
        var metaMathObj = {type:type, settings:{}};
        var mathObj = MathObject.renderNewObject(math3d, metaMathObj);
    }

    $scope.createNewFolder = function(){
        $scope.math3d.mathTree.push({name:'Untitled', objects:[], collapsed:false});
    }

    $scope.addOjbectToUi = function(obj){
        var content = `
            <div id="object-${obj.id}" ng-include="'/static/resources/templates/${obj.type.toLowerCase()}.html'">
            </div>`;

        //Re-initialize jscolor palletes. This seems hacky.
        setTimeout(function(){ jscolor.installByClassName("jscolor"); }, 0);
        //Re-initialize textarea autosizing
        autosize($("textarea.object-description"))

        return $sce.trustAsHtml(content)
    };

    $scope.removeFolder = function(branch){
        // Should only be used if branch is empty
        if (branch.objects.length===0){
            _.remove(math3d.mathTree, function(arg){return arg===branch;});
        }
    }

    $scope.setupMathGraphicMF = function(obj){
        var el = $(`#object-${obj.id} span.mathquill-large`)
        if (!el.hasClass('has-mq')){
            var keys = {
                Vector: 'components',
                ExplicitSurface: 'rawExpressionZ',
                ExplicitSurfacePolar:'rawExpressionZ',
            }
            var key = keys[obj.type];
            if (key === undefined){ key = 'rawExpression' };
            var mf = new WrappedMathFieldMain(el[0],obj, key);
        }

    }

    $scope.setupVariableMF = function(obj){
        var elName = $(`#object-${obj.id} span.mathquill-large .variable-rawName`);
        var elEqual = $(`#object-${obj.id} span.mathquill-large .variable-equal`);
        var elExpression = $(`#object-${obj.id} span.mathquill-large .variable-rawExpression`);

        if (!elName.hasClass('has-mq')){
            new WrappedMathFieldMain(elName[0], obj, 'rawName', $scope);
            new MathQuill.getInterface(2).StaticMath(elEqual[0]);
        }
        if (!elExpression.hasClass('has-mq')){
            new WrappedMathFieldMain(elExpression[0], obj, 'rawExpression', $scope);
        }
    }

}]);

app.controller('mathObjectCtrl',['$scope','$timeout', function($scope, $timeout){
    $scope.debug = arg => console.log(arg)

    initialize($scope.obj);

    $scope.setColor = function(obj){
        document.getElementById(`jscolor-${obj.id}`).jscolor.show()
    }
    $scope.getStyle = function(obj){
        if (obj.settings.visible){
            var backgroundColor = obj.settings.color;
            var borderColor = Utility.lightenColor(obj.settings.color,-0.5);
        } else {
            var backgroundColor = 'lightgray';
            var borderColor = 'darkgray';
        }
        return `background-color:${backgroundColor}; border-color:${borderColor}`
    }

    function initialize(obj){
        if (obj instanceof MathGraphic){
            Utility.extendSetter(obj.settings, 'visible', function(){
                // Call $apply inside timeout; see https://docs.angularjs.org/error/$rootScope/inprog?p0=$apply "Trigger Events Programatically"
                $timeout(function(){
                    $scope.$apply();
                }, 0, false);
            });
        }
    }
}])

app.controller('sliderCtrl', ['$scope', function($scope){
    $scope.intervalID = null;
    $scope.toggleAnimate = function(obj){
        if (!obj.settings.animationRunning){
            $scope.startAnimation(obj)
        }
        else {
            $scope.stopAnimation(obj)
        }
        return
    }
    $scope.startAnimation = function(obj){
        obj.settings.animationRunning = true;
        var ele = document.getElementById("slider-"+obj.id);
        ele.step = ((ele.max-ele.min)/200) * obj.speeds[obj.settings.speedIdx].value;
        $scope.intervalID = setInterval(function(){
           ele.stepUp();
           if (ele.value > ele.max - ele.step){
               ele.value = ele.min
           }
           angular.element(ele).trigger('input')
        },20)
    }
    $scope.stopAnimation = function(obj){
        obj.settings.animationRunning = false;
        clearInterval($scope.intervalID);
    }

    $scope.incrementSpeed = function(obj, incr){
        obj.settings.speedIdx += incr;
        obj.settings.speedIdx = MathUtility.clamp(0, obj.settings.speedIdx, obj.speeds.length - 1);
        $scope.toggleAnimate(obj);
        $scope.toggleAnimate(obj);
    }

    $scope.initSlider = function(obj){
        var ele = document.getElementById("slider-"+obj.id);
        ele.step = ((ele.max-ele.min)/200) * obj.speeds[obj.settings.speedIdx].value;
        if (obj.animationStarted === undefined && obj.settings.animationRunning){
            $scope.startAnimation(obj);
            obj.animationStarted = true;
        }
    }

}])

//http://stackoverflow.com/a/32366115/2747370
//http://codepen.io/dmvianna/pen/OyNNJx
app.controller('popoverCtrl', ['$scope', function($scope) {
    // query popover
    $scope.myPopover = {
        isOpen: false,
        templateUrl: '/static/resources/templates/settings_popover.html',
        open: function open() {
            $scope.myPopover.isOpen = true;
            $scope.myPopover.data = 'Hello!';
        },
        close: function close() {
            $scope.myPopover.isOpen = false;
        }
    };
}]);

// Prevent key events from bubbling up to the 3d scene
container.on("keypress keydown keyup", function(e) {
    e.stopPropagation();
});<commit_msg>Reset graphsList when logging out<commit_after>// MIT License
//
// Copyright (c) 2017 Christopher Chudzicki
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

var container = $(".container")
container.attr("ng-app", 'math3dApp')

// app = angular.module('math3dApp', ['ui.sortable']);
app = angular.module('math3dApp', ['ui.tree', 'ngAnimate', 'ngCookies', 'ui.bootstrap', 'ui.toggle']);

// Change default tags to '[[' and ']]' to prevent conflict with Flask
app.config(function($interpolateProvider, $httpProvider) {
    $interpolateProvider.startSymbol('[[');
    $interpolateProvider.endSymbol(']]');

    // Include csrf_token in every request
    $httpProvider.defaults.xsrfCookieName = "csrf_token";
    $httpProvider.defaults.xsrfHeaderName = "X-CSRFToken";
});

app.directive('compileTemplate', ["$compile", "$parse", function($compile, $parse) {
    // http://stackoverflow.com/a/25407201/2747370
    return {
        restrict: 'A',
        link: function($scope, element, attr) {
            var parse = $parse(attr.ngBindHtml);
            function value() { return (parse($scope) || '').toString(); }

            $scope.$watch(value, function() {
                $compile(element, null, -9999)($scope);
            });
        }
    }
}]);

// http://stackoverflow.com/a/29571230/2747370
app.directive( 'elemReady', function( $parse ) {
   return {
       restrict: 'A',
       link: function( $scope, elem, attrs ) {
          elem.ready(function(){
            $scope.$apply(function(){
                var func = $parse(attrs.elemReady);
                func($scope);
            })
          })
       }
    }
})

// https://gist.github.com/BobNisco/9885852 (modifications in comments)
// Add this directive where you keep your directives
app.directive('onLongPress', function($timeout) {
    return {
		restrict: 'A',
        link: function(scope, elem, attrs) {
            var timeoutHandler;

            elem.bind('mousedown touchstart', function() {
                timeoutHandler = $timeout(function() {
                    scope.$eval(attrs.onLongPress);
                }, 600);
            });

            elem.bind('mouseup touchend', function() {
                $timeout.cancel(timeoutHandler);
            });
		}
	};
})
app.directive('onShortPress', function($timeout) {
	return {
		restrict: 'A',
		link: function($scope, $elm, $attrs) {
			$elm.bind('mousedown touchstart', function(evt) {
                // Locally scoped variable that will keep track of the short press
				$scope.shortPress = true;

				// After a timeout of 600 ms, shortPress is false;
				$timeout(function() {
					$scope.shortPress=false;
				}, 600);
			});

			$elm.bind('mouseup touchend', function(evt) {
                // Some touchscreens seem to trigger both events, so let's prevent onShortPress action from occuring too often (100ms).
                if ($scope.prevent){
                    return;
                } else {
                    $scope.prevent = true;
    				$timeout(function() {
    					$scope.prevent=false;
    				}, 500);
                }
                // Prevent the onShortPress event from firing
                if ($scope.shortPress){
					$scope.$apply(function() {
						$scope.$eval($attrs.onShortPress)
					});
                }
				// If there is an on-touch-end function attached to this element, apply it
				if ($attrs.onTouchEnd) {
					$scope.$apply(function() {
						$scope.$eval($attrs.onTouchEnd)
					});
				}
			});
		}
	};
})

app.controller('treeCtrl', function($scope)  {
    $scope.treeOptions = {
        accept: function(sourceNodeScope, destNodesScope, destIndex) {
            return sourceNodeScope.depth() - 1 === destNodesScope.depth()
        },
        toggle: function(collapsed, sourceNodeScope){
            sourceNodeScope.branch.collapsed = collapsed;
            var reflowInterval = setInterval(function(){
                var failures = reflowBranch(sourceNodeScope['branch']);
                if (failures===0){
                    clearInterval(reflowInterval);
                }
            }, 100);
        }
    };

    function reflowBranch(branch){
        // If folders are collapsed on page load, mathquill as are initialized in zero-height spans. We need to reflow each of them when the folder is expanded.
        // Reflow only really needs to be done once, has the mq-reflow tracking class.
        var failures = 0;
        _.forEach(branch.objects, function(obj){
            _.forEach(obj.wrappedMathFields, function(wmf){

                var mf = wmf.mathfield;
                var el = $(mf.el());
                if (el.css('height')==='0px'){
                    failures += 1;
                }
                else if ( !el.hasClass('mq-reflowed') ){
                    mf.reflow();
                    el.addClass('mq-reflowed');
                }

            })
        })

        return failures;
    }
});

app.controller('checkLoginCtrl', ['$scope', '$cookies', '$rootScope', function($scope, $cookies, $rootScope) {
    var username = $cookies.get("username");
    if (username) {
        $scope.username = username;
        $scope.logged_in = true;
    }

    $scope.logout = function() {
        $cookies.remove("username");
        $scope.logged_in = false;
        $rootScope.updateGraphs();
    }
}]);

app.controller('saveCtrl', ['$scope', function($scope){
    $scope.saveURL = function(){
        var url = math3d.saveSettingsAsURL();
        $("#save-modal textarea").val(url);
        $("#url-length").html(url.length);
    };
}])

app.controller('saveToDBCtrl', ['$scope', '$http', function($scope, $http) {
    $scope.saveToDB = function(){
        var title = $("#save-modal input").val();
        var serialized_setting = math3d.saveSettingsAsString();

        $http.post("/api/graph/save", {
            title: title,
            serialized_graph: serialized_setting,
        }).then(function(response) {
            if (response.data == "Success!") {
                $("#save-modal").modal('hide');
            }
        });
    }
}]);

app.controller('graphListCtrl', ['$rootScope', '$scope', '$http', function($rootScope, $scope, $http) {
    // Using rootScope so that when clicking the Graphs tab this can be called.
    // The graphs tab button is out of the scope of graphListCtrl.
    $rootScope.updateGraphs = function() {
        if ($scope.logged_in) {
            $http.get("/api/graph/get").then(function(response){
                $rootScope.graphs = response.data;
            });
        } else {
            $rootScope.graphs = [];
        }
    }

    $scope.loadGraph = function(serialized_string) {
        math3d.clear()
        math3d.load(Math3D.decodeSettingsAsURL64(serialized_string));
    }

    $rootScope.updateGraphs();

}]);

app.controller('axesSettingsCtrl', ['$scope', function($scope){
    $scope.settings = math3d.settings;
}])

app.controller('addObjectCtrl',['$scope', '$sce', function($scope, $sce) {
    $scope.debug = arg => console.log(arg);

    $scope.math3d = math3d;

    $scope.createNewObject = function(type){
        var metaMathObj = {type:type, settings:{}};
        var mathObj = MathObject.renderNewObject(math3d, metaMathObj);
    }

    $scope.createNewFolder = function(){
        $scope.math3d.mathTree.push({name:'Untitled', objects:[], collapsed:false});
    }

    $scope.addOjbectToUi = function(obj){
        var content = `
            <div id="object-${obj.id}" ng-include="'/static/resources/templates/${obj.type.toLowerCase()}.html'">
            </div>`;

        //Re-initialize jscolor palletes. This seems hacky.
        setTimeout(function(){ jscolor.installByClassName("jscolor"); }, 0);
        //Re-initialize textarea autosizing
        autosize($("textarea.object-description"))

        return $sce.trustAsHtml(content)
    };

    $scope.removeFolder = function(branch){
        // Should only be used if branch is empty
        if (branch.objects.length===0){
            _.remove(math3d.mathTree, function(arg){return arg===branch;});
        }
    }

    $scope.setupMathGraphicMF = function(obj){
        var el = $(`#object-${obj.id} span.mathquill-large`)
        if (!el.hasClass('has-mq')){
            var keys = {
                Vector: 'components',
                ExplicitSurface: 'rawExpressionZ',
                ExplicitSurfacePolar:'rawExpressionZ',
            }
            var key = keys[obj.type];
            if (key === undefined){ key = 'rawExpression' };
            var mf = new WrappedMathFieldMain(el[0],obj, key);
        }

    }

    $scope.setupVariableMF = function(obj){
        var elName = $(`#object-${obj.id} span.mathquill-large .variable-rawName`);
        var elEqual = $(`#object-${obj.id} span.mathquill-large .variable-equal`);
        var elExpression = $(`#object-${obj.id} span.mathquill-large .variable-rawExpression`);

        if (!elName.hasClass('has-mq')){
            new WrappedMathFieldMain(elName[0], obj, 'rawName', $scope);
            new MathQuill.getInterface(2).StaticMath(elEqual[0]);
        }
        if (!elExpression.hasClass('has-mq')){
            new WrappedMathFieldMain(elExpression[0], obj, 'rawExpression', $scope);
        }
    }

}]);

app.controller('mathObjectCtrl',['$scope','$timeout', function($scope, $timeout){
    $scope.debug = arg => console.log(arg)

    initialize($scope.obj);

    $scope.setColor = function(obj){
        document.getElementById(`jscolor-${obj.id}`).jscolor.show()
    }
    $scope.getStyle = function(obj){
        if (obj.settings.visible){
            var backgroundColor = obj.settings.color;
            var borderColor = Utility.lightenColor(obj.settings.color,-0.5);
        } else {
            var backgroundColor = 'lightgray';
            var borderColor = 'darkgray';
        }
        return `background-color:${backgroundColor}; border-color:${borderColor}`
    }

    function initialize(obj){
        if (obj instanceof MathGraphic){
            Utility.extendSetter(obj.settings, 'visible', function(){
                // Call $apply inside timeout; see https://docs.angularjs.org/error/$rootScope/inprog?p0=$apply "Trigger Events Programatically"
                $timeout(function(){
                    $scope.$apply();
                }, 0, false);
            });
        }
    }
}])

app.controller('sliderCtrl', ['$scope', function($scope){
    $scope.intervalID = null;
    $scope.toggleAnimate = function(obj){
        if (!obj.settings.animationRunning){
            $scope.startAnimation(obj)
        }
        else {
            $scope.stopAnimation(obj)
        }
        return
    }
    $scope.startAnimation = function(obj){
        obj.settings.animationRunning = true;
        var ele = document.getElementById("slider-"+obj.id);
        ele.step = ((ele.max-ele.min)/200) * obj.speeds[obj.settings.speedIdx].value;
        $scope.intervalID = setInterval(function(){
           ele.stepUp();
           if (ele.value > ele.max - ele.step){
               ele.value = ele.min
           }
           angular.element(ele).trigger('input')
        },20)
    }
    $scope.stopAnimation = function(obj){
        obj.settings.animationRunning = false;
        clearInterval($scope.intervalID);
    }

    $scope.incrementSpeed = function(obj, incr){
        obj.settings.speedIdx += incr;
        obj.settings.speedIdx = MathUtility.clamp(0, obj.settings.speedIdx, obj.speeds.length - 1);
        $scope.toggleAnimate(obj);
        $scope.toggleAnimate(obj);
    }

    $scope.initSlider = function(obj){
        var ele = document.getElementById("slider-"+obj.id);
        ele.step = ((ele.max-ele.min)/200) * obj.speeds[obj.settings.speedIdx].value;
        if (obj.animationStarted === undefined && obj.settings.animationRunning){
            $scope.startAnimation(obj);
            obj.animationStarted = true;
        }
    }

}])

//http://stackoverflow.com/a/32366115/2747370
//http://codepen.io/dmvianna/pen/OyNNJx
app.controller('popoverCtrl', ['$scope', function($scope) {
    // query popover
    $scope.myPopover = {
        isOpen: false,
        templateUrl: '/static/resources/templates/settings_popover.html',
        open: function open() {
            $scope.myPopover.isOpen = true;
            $scope.myPopover.data = 'Hello!';
        },
        close: function close() {
            $scope.myPopover.isOpen = false;
        }
    };
}]);

// Prevent key events from bubbling up to the 3d scene
container.on("keypress keydown keyup", function(e) {
    e.stopPropagation();
});<|endoftext|>


<commit_before>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* 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.
*/

'use strict';

// MODULES //

var logger = require( 'debug' );
var browserify = require( 'browserify' );
var copy = require( '@stdlib/utils/copy' );
var isStringArray = require( '@stdlib/assert/is-string-array' ).primitives;
var isFunction = require( '@stdlib/assert/is-function' );
var isArray = require( '@stdlib/assert/is-array' );
var browserifyString = require( '@stdlib/_tools/browserify/string' );
var validate = require( './validate.js' );
var defaults = require( './defaults.json' );
var unique = require( './unique.js' );
var setdiff = require( './setdiff.js' );
var treeFilter = require( './tree_filter.js' );
var namespaces = require( './namespaces.js' );
var toFlatNamespace = require( './to_flat.js' );
var toTreeNamespace = require( './to_tree.js' );
var fileStringRequire = require( './file_string_require.js' );

// VARIABLES //

var debug = logger( 'bundle-pkg-list:main' );

// MAIN //

/**
* Returns a custom bundle.
*
* @param {StringArray} pkgs - list of packages to bundle
* @param {Options} [options] - function options
* @param {string} [options.namespace="tree"] - specifies whether the namespace should be a "tree", "flat", or "none"
* @param {string} [options.bundler="browserify"] - bundler
* @param {string} [options.standalone] - standalone browserify bundle export name
* @param {string} [options.requireName="require"] - name of function exposed to supporting external loading of modules from a browserify bundle
* @param {string} [options.exportName="@stdlib/stdlib"] - bundle target name which is used to load a bundle from the external environment
* @param {Array} [options.transforms] - transforms to apply when bundling
* @param {Array} [options.plugins] - plugins to apply when bundling
* @param {StringArray} [options.external] - module(s) to omit from a generated bundle which can be assumed to be present in a separate external bundle
* @param {Callback} clbk - callback to invoke upon completion
* @throws {TypeError} first argument must be an array of strings
* @throws {TypeError} options argument must be an object
* @throws {TypeError} must provide valid options
* @throws {RangeError} must provide a recognized `namespace` option
* @throws {RangeError} must provide a recognized `bundler` option
* @throws {TypeError} last argument must be a function
* @returns {void}
*
* @example
* var pkgs = [
*     '@stdlib/math/base/special/erf',
*     '@stdlib/math/base/special/gamma'
* ];
*
* bundle( pkgs, onBundle );
*
* function onBundle( error, bundle ) {
*     if ( error ) {
*         throw error;
*     }
*     console.log( bundle.toString() );
* }
*/
function bundle( pkgs, options, clbk ) {
	var bopts;
	var opts;
	var err;
	var out;
	var cb;
	var b;
	var v;
	var i;

	if ( !isStringArray( pkgs ) ) {
		throw new TypeError( 'invalid argument. First argument must be an array of strings. Value: `'+pkgs+'`.' );
	}
	opts = copy( defaults );
	if ( arguments.length > 2 ) {
		cb = clbk;
		err = validate( opts, options );
		if ( err ) {
			throw err;
		}
	} else {
		cb = options;
	}
	if ( !isFunction( cb ) ) {
		throw new TypeError( 'invalid argument. Last argument must be a function. Value: `'+cb+'`.' );
	}
	debug( 'Options: %s', JSON.stringify( opts ) );

	// Sort the packages in lexicographic order (e.g., [ '@stdlib/math/base', '@stdlib/math' ] => [ '@stdlib/math', '@stdlib/math/base' ] ):
	pkgs.sort();

	// Remove duplicates:
	pkgs = unique( pkgs );

	// If we do not need to generate a namespace for a browserify bundle, we will generate a bundle which allows a bundle consumer to `require` each package individually...
	if ( opts.namespace === 'none' && opts.bundler === 'browserify' ) {
		bopts = {
			'externalRequireName': opts.requireName,
			'require': pkgs
		};
		debug( 'Browserify options: %s', JSON.stringify( bopts ) );
		b = browserify( bopts );
		if ( opts.transforms ) {
			for ( i = 0; i < opts.transforms.length; i++ ) {
				v = opts.transforms[ i ];
				if ( isArray( v ) ) {
					b.transform( v[ 0 ], v[ 1 ] );
				} else {
					b.transform( v );
				}
			}
		}
		if ( opts.plugins ) {
			for ( i = 0; i < opts.plugins.length; i++ ) {
				v = opts.plugins[ i ];
				if ( isArray( v ) ) {
					b.plugin( v[ 0 ], v[ 1 ] );
				} else {
					b.plugin( v );
				}
			}
		}
		if ( opts.external ) {
			b.external( opts.external );
		}
		return b.bundle( onBundle );
	}

	// Generate a namespace...
	if ( opts.namespace === 'flat' ) {
		debug( 'Generating a flat namespace...' );

		debug( 'Excluding namespace packages...' );
		pkgs = setdiff( namespaces, pkgs );

		out = toFlatNamespace( pkgs );
	} else {
		debug( 'Generating a tree namespace...' );

		debug( 'Filtering namespace packages...' );
		pkgs = treeFilter( pkgs );

		out = toTreeNamespace( pkgs );
	}
	debug( 'Finished generating a namespace.' );

	debug( 'Generating an entry file...' );
	out = fileStringRequire( out );
	debug( 'Finished generating an entry file.' );

	debug( 'Generating bundle...' );
	if ( opts.bundler === 'browserify' ) {
		bopts = {
			'requireName': opts.requireName,
			'transforms': opts.transforms,
			'plugins': opts.plugins
		};
		if ( opts.standalone ) {
			bopts.standalone = opts.standalone;
		} else if ( opts.exportName ) {
			bopts.exportName = opts.exportName;
		}
		if ( opts.external ) {
			bopts.external = opts.external;
		}
		debug( 'Browserify options: %s', JSON.stringify( bopts ) );
		return browserifyString( out, bopts, onBundle );
	}
	// TODO: support other bundlers

	/**
	* Callback invoked upon generating a browserify bundle.
	*
	* @private
	* @param {(Error|null)} error - error object
	* @param {(string|Buffer)} bundle - bundle
	* @returns {void}
	*/
	function onBundle( error, bundle ) {
		if ( error ) {
			debug( 'Encountered an error when generating a browserify bundle: %s', error.message );
			return done( error );
		}
		debug( 'Successfully generated a browserify bundle.' );
		done( null, bundle );
	}

	/**
	* Callback invoked upon completion.
	*
	* @private
	* @param {(Error|null)} error - error object
	* @param {(string|Buffer)} bundle - bundle
	* @returns {void}
	*/
	function done( error, bundle ) {
		if ( error ) {
			return cb( error );
		}
		cb( null, bundle );
	}
}

// EXPORTS //

module.exports = bundle;
<commit_msg>Set the `basedir` for resolving relative paths<commit_after>/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* 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.
*/

'use strict';

// MODULES //

var logger = require( 'debug' );
var browserify = require( 'browserify' );
var copy = require( '@stdlib/utils/copy' );
var isStringArray = require( '@stdlib/assert/is-string-array' ).primitives;
var isFunction = require( '@stdlib/assert/is-function' );
var isArray = require( '@stdlib/assert/is-array' );
var browserifyString = require( '@stdlib/_tools/browserify/string' );
var cwd = require( '@stdlib/process/cwd' );
var validate = require( './validate.js' );
var defaults = require( './defaults.json' );
var unique = require( './unique.js' );
var setdiff = require( './setdiff.js' );
var treeFilter = require( './tree_filter.js' );
var namespaces = require( './namespaces.js' );
var toFlatNamespace = require( './to_flat.js' );
var toTreeNamespace = require( './to_tree.js' );
var fileStringRequire = require( './file_string_require.js' );

// VARIABLES //

var debug = logger( 'bundle-pkg-list:main' );

// MAIN //

/**
* Returns a custom bundle.
*
* @param {StringArray} pkgs - list of packages to bundle
* @param {Options} [options] - function options
* @param {string} [options.namespace="tree"] - specifies whether the namespace should be a "tree", "flat", or "none"
* @param {string} [options.bundler="browserify"] - bundler
* @param {string} [options.standalone] - standalone browserify bundle export name
* @param {string} [options.requireName="require"] - name of function exposed to supporting external loading of modules from a browserify bundle
* @param {string} [options.exportName="@stdlib/stdlib"] - bundle target name which is used to load a bundle from the external environment
* @param {Array} [options.transforms] - transforms to apply when bundling
* @param {Array} [options.plugins] - plugins to apply when bundling
* @param {StringArray} [options.external] - module(s) to omit from a generated bundle which can be assumed to be present in a separate external bundle
* @param {Callback} clbk - callback to invoke upon completion
* @throws {TypeError} first argument must be an array of strings
* @throws {TypeError} options argument must be an object
* @throws {TypeError} must provide valid options
* @throws {RangeError} must provide a recognized `namespace` option
* @throws {RangeError} must provide a recognized `bundler` option
* @throws {TypeError} last argument must be a function
* @returns {void}
*
* @example
* var pkgs = [
*     '@stdlib/math/base/special/erf',
*     '@stdlib/math/base/special/gamma'
* ];
*
* bundle( pkgs, onBundle );
*
* function onBundle( error, bundle ) {
*     if ( error ) {
*         throw error;
*     }
*     console.log( bundle.toString() );
* }
*/
function bundle( pkgs, options, clbk ) {
	var bopts;
	var opts;
	var err;
	var out;
	var cb;
	var b;
	var v;
	var i;

	if ( !isStringArray( pkgs ) ) {
		throw new TypeError( 'invalid argument. First argument must be an array of strings. Value: `'+pkgs+'`.' );
	}
	opts = copy( defaults );
	if ( arguments.length > 2 ) {
		cb = clbk;
		err = validate( opts, options );
		if ( err ) {
			throw err;
		}
	} else {
		cb = options;
	}
	if ( !isFunction( cb ) ) {
		throw new TypeError( 'invalid argument. Last argument must be a function. Value: `'+cb+'`.' );
	}
	debug( 'Options: %s', JSON.stringify( opts ) );

	// Sort the packages in lexicographic order (e.g., [ '@stdlib/math/base', '@stdlib/math' ] => [ '@stdlib/math', '@stdlib/math/base' ] ):
	pkgs.sort();

	// Remove duplicates:
	pkgs = unique( pkgs );

	// If we do not need to generate a namespace for a browserify bundle, we will generate a bundle which allows a bundle consumer to `require` each package individually...
	if ( opts.namespace === 'none' && opts.bundler === 'browserify' ) {
		bopts = {
			'externalRequireName': opts.requireName,
			'require': pkgs
		};
		debug( 'Browserify options: %s', JSON.stringify( bopts ) );
		b = browserify( bopts );
		if ( opts.transforms ) {
			for ( i = 0; i < opts.transforms.length; i++ ) {
				v = opts.transforms[ i ];
				if ( isArray( v ) ) {
					b.transform( v[ 0 ], v[ 1 ] );
				} else {
					b.transform( v );
				}
			}
		}
		if ( opts.plugins ) {
			for ( i = 0; i < opts.plugins.length; i++ ) {
				v = opts.plugins[ i ];
				if ( isArray( v ) ) {
					b.plugin( v[ 0 ], v[ 1 ] );
				} else {
					b.plugin( v );
				}
			}
		}
		if ( opts.external ) {
			b.external( opts.external );
		}
		return b.bundle( onBundle );
	}

	// Generate a namespace...
	if ( opts.namespace === 'flat' ) {
		debug( 'Generating a flat namespace...' );

		debug( 'Excluding namespace packages...' );
		pkgs = setdiff( namespaces, pkgs );

		out = toFlatNamespace( pkgs );
	} else {
		debug( 'Generating a tree namespace...' );

		debug( 'Filtering namespace packages...' );
		pkgs = treeFilter( pkgs );

		out = toTreeNamespace( pkgs );
	}
	debug( 'Finished generating a namespace.' );

	debug( 'Generating an entry file...' );
	out = fileStringRequire( out );
	debug( 'Finished generating an entry file.' );

	debug( 'Generating bundle...' );
	if ( opts.bundler === 'browserify' ) {
		bopts = {
			'requireName': opts.requireName,
			'transforms': opts.transforms,
			'plugins': opts.plugins
		};
		if ( opts.standalone ) {
			bopts.standalone = opts.standalone;
		} else if ( opts.exportName ) {
			bopts.exportName = opts.exportName;
		}
		if ( opts.external ) {
			bopts.external = opts.external;
		}
		bopts.basedir = cwd();

		debug( 'Browserify options: %s', JSON.stringify( bopts ) );
		return browserifyString( out, bopts, onBundle );
	}
	// TODO: support other bundlers

	/**
	* Callback invoked upon generating a browserify bundle.
	*
	* @private
	* @param {(Error|null)} error - error object
	* @param {(string|Buffer)} bundle - bundle
	* @returns {void}
	*/
	function onBundle( error, bundle ) {
		if ( error ) {
			debug( 'Encountered an error when generating a browserify bundle: %s', error.message );
			return done( error );
		}
		debug( 'Successfully generated a browserify bundle.' );
		done( null, bundle );
	}

	/**
	* Callback invoked upon completion.
	*
	* @private
	* @param {(Error|null)} error - error object
	* @param {(string|Buffer)} bundle - bundle
	* @returns {void}
	*/
	function done( error, bundle ) {
		if ( error ) {
			return cb( error );
		}
		cb( null, bundle );
	}
}

// EXPORTS //

module.exports = bundle;
<|endoftext|>


<commit_before>/** Class representing a node in the tree */
class Node {
  /**
   * Allows us to quickly create nodes for our binary search tree
   *
   * @constructor
   * @param {Number|String} value - value held by node
   */
  constructor(value) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}

/**
 * @description: Helper for deleteNode function. Finds minimum of tree and
 * deletes it.
 *
 * Strategy: Move parent and child pointers left until child is min. Upon match,
 * update parent's left pointer to min's right subtree.
 *
 * Edge case(s): empty tree, incorrect input type. If root is min, then we
 * cannot remove it from the tree, but we can still return the node.
 *
 * Time complexity: O(log N)
 * Space complexity: O(1)
 *
 * @param {Object} root - tree whose minimum will be removed
 * @return {Object} - node with minimum value in tree
 */
function deleteMin(root) {
  let parent = root;
  if (parent === null || typeof parent !== 'object') {
    throw new Error('Please insert valid tree');
  }

  let child = root.left;
  if (child === null) {
    // no children means parent is min
    return parent.right === null ? parent : parent.right;
  }

  while (child !== null && child.left !== null) {
    parent = parent.left;
    child = child.left;
  }

  // min may have right subtree, so point parent to that subtree (may also be null)
  parent.left = child.right;
  return child;
}

/**
 * @description: Helper for delete method. Decides strategy based on number
 * of children.
 *
 * Strategy: If no children, return null. If one child, return it. If two
 * children, we have some work to do... delete and store minimum in right
 * subtree, assign minimum's left and right pointers to those of the node to
 * be deleted, then return min node.
 *
 * Edge case(s): right subtree contains only one node
 *
 * Time complexity: O(log N)
 * Space complexity: O(1)
 *
 * @param {Object} node - node to be removed
 * @return {Object} - null, node's only child, or right subtree minimum
 */
function deleteNode(node) {
  let numberOfChildren = 0;
  if (node.left !== null) { numberOfChildren++; }
  if (node.right !== null) { numberOfChildren++; }

  switch (numberOfChildren) {
    case 0:
      return null;
    case 1:
      return node.left === null ? node.right : node.left;
    case 2:
      const rightMin = deleteMin(node.right);
      rightMin.left = node.left;

      // if right subtree min is node's right child, then replace with null
      // to avoid accidental circular reference
      rightMin.right = node.right === rightMin ? null : node.right;
      return rightMin;
  }
}

/** Class representing our tree */
class BinarySearchTree {
  /**
   * Track root of tree.
   *
   * @constructor
   */
  constructor() {
    this.root = null;
  }

  /**
   * @description: Adds a node with the given value to the tree.
   *
   * Strategy: Start at root node, then check if input value is smaller or greater
   * than root value. Each direction will check to see if a node is already present
   * in that direction. If exists, set current node to that node to traverse.
   * If not, insert new node with input value.
   *
   * Edge case(s): inappropriate inputs, duplicates
   *
   * Time complexity: O(log N)
   * Space complexity: O(1)
   *
   * @param {Number|String} value - value to be inserted into new node
   * @return {Boolean} - true means add was successful
   */
  add(value) {
    // Type checking
    if (value === undefined || typeof value === 'object' || isNaN(value)) {
      throw new Error('Please avoid adding silly values like undefined, null, objects or NaN');
    }

    let newNode = new Node(value);

    // If tree is empty...
    if (this.root === null) {
      this.root = newNode;
      return true;
    }

    let node = this.root;

    // Traverse tree
    while (node !== null) {
      if (value === node.value) { throw new Error('Cannot add duplicate values'); }

      // Lesser values move us left or create new left tree
      if (value < node.value) {
        if (node.left === null) {
          node.left = newNode;
          return true;
        }

        node = node.left;
      }

      // Greater values move us right or create new right tree
      if (value > node.value) {
        if (node.right === null) {
          node.right = newNode;
          return true;
        }

        node = node.right;
      }
    }
  }

  /**
   * @description Check if node with given value exists in tree.
   *
   * Strategy: Start at root node. Loop to traverse tree until null pointer.
   * Check if input value ever equals a node's value.
   *
   * Time complexity: O(log N)
   * Space complexity: O(1)
   *
   * @param {*} value - searching for this value in the tree
   * @return {Boolean|String} - whether or not value exists in tree
   */
  contains(value) {
    let node = this.root;

    while (node !== null) {
      if (value === node.value) { return true; }
      if (value < node.value) { node = node.left; }
      if (value > node.value) { node = node.right; }
    }

    return false;
  }

  /**
   * @description: Hibbard deletion. Removes a node with the given value from
   * the tree. If used extensively, slants tree and makes tree height sqrt(N),
   * which slows typically logarithmic operations.
   *
   * Strategy: Traverse with parent pointer until find child node containing
   * the given value. Call deleteNode on matched child and update parent pointer
   * in the direction previously traversed.
   *
   * Edge case(s): delete root, input does not exist in tree, empty tree
   *
   * Time complexity: O(log N)
   * Space complexity: O(1)
   *
   * @param {Number|String} value - value of node to be removed
   * @return {Object} - node removed
   */
  delete(value) {
    if (this.root === null) { throw new Error('Tree is empty, my dear friend!'); }
    if (value === undefined || typeof value === 'object' || isNaN(value)) {
      throw new Error('Values like undefined, null, objects and NaN do not exist for deletion');
    }

    let parent = this.root;
    let child;

    // direction remembers whether child is right or left subtree of parent
    let direction;

    // check if root is node to be deleted
    if (value === parent.value) {
      this.root = deleteNode(parent);
      return parent;
    }

    // set initial child to check value against
    if (value < parent.value) {
      child = parent.left;
      direction = 'left';
    } else if (value > parent.value) {
      child = parent.right;
      direction = 'right';
    }

    // traverse tree
    while (child !== null) {
      if (value === child.value) {
        // parent overwrites pointer to matched child
        parent[direction] = deleteNode(child);
        return child;
      } else if (value < child.value) {
        child = child.left;
        parent = parent[direction];
        direction = 'left';
      } else if (value > child.value) {
        child = child.right;
        parent = parent[direction];
        direction = 'right';
      }
    }

    throw new Error('No match found for deletion.');
  }

  /**
   * @description Find maximum value in tree.
   *
   * Strategy: Maximum is right-most node. Traverse tree by starting at root node
   * and reassigning node to that node's right. When right pointer is null, current
   * node's value will be the maximum value in the tree.
   *
   * Edge case(s): Empty tree
   *
   * Time complexity: O(log N)
   * Space complexity: O(1)
   *
   * @return {Number|String} - maximum value in tree
   */
  max() {
    let node = this.root;
    if (node === null) { throw new Error('Tree is empty, my dear friend!'); }
    while (node.right !== null) { node = node.right; }
    return node.value;
  }

  /**
   * @description Find minimum value in tree.
   *
   * Strategy: Minimum is left-most node. Traverse tree by starting at root node
   * and reassigning node to that node's left. When left pointer is null, current
   * node's value will be the minimum value in the tree.
   *
   * Edge case(s): Empty tree
   *
   * Time complexity: O(log N)
   * Space complexity: O(1)
   *
   * @return {Number|String} - minimum value in tree
   */
  min() {
    let node = this.root;
    if (node === null) { throw new Error('Tree is empty, my dear friend!'); }
    while (node.left !== null) { node = node.left; }
    return node.value;
  }

  /**
   * @description Depth-first in-order traversal that applies callback to
   * all node values in this order: left leaf, root, right leaf.
   *
   * Strategy: Callback is applied between recursive calls to left and right
   * subtrees. End traversal in either direction when pointer in that direction
   * is null.
   *
   * Edge case(s): Empty tree
   *
   * Time complexity: O(N)
   * Space complexity: O(1)
   *
   * @param {Function} callback - invoked on every node's value in-order
   * @param {Object=} node - defaults to root and used to traverse recursively
   */
  traverseInOrder(callback, node = this.root) {
    if (node === null) { throw new Error('Tree is empty, my dear friend!'); }

    if (node.left !== null) { this.traverseInOrder(callback, node.left); }
    callback(node.value);
    if (node.right !== null) { this.traverseInOrder(callback, node.right); }
  }

  /**
   * @description Depth-first post-order traversal that applies callback to
   * all node values in this order: left leaf, right leaf, root
   *
   * Strategy: Callback is applied after recursive calls to left and right
   * subtrees. End traversal in either direction when pointer in that direction
   * is null.
   *
   * Edge case(s): Empty tree
   *
   * Time complexity: O(N)
   * Space complexity: O(1)
   *
   * @param {Function} callback - invoked on every node's value post-order
   * @param {Object=} node - defaults to root and used to traverse recursively
   */
  traversePostOrder(callback, node = this.root) {
    if (node === null) { throw new Error('Tree is empty, my dear friend!'); }

    if (node.left !== null) { this.traversePostOrder(callback, node.left); }
    if (node.right !== null) { this.traversePostOrder(callback, node.right); }
    callback(node.value);
  }

  /**
   * @description Depth-first pre-order traversal that applies callback to
   * all node values in this order: root, left leaf, right leaf
   *
   * Strategy: Callback is applied before recursive calls to left and right
   * subtrees. End traversal in either direction when pointer in that direction
   * is null.
   *
   * Edge case(s): Empty tree
   *
   * Time complexity: O(N)
   * Space complexity: O(1)
   *
   * @param {Function} callback - invoked on every node's value pre-order
   * @param {Object=} node - defaults to root and used to traverse recursively
   */
  traversePreOrder(callback, node = this.root) {
    if (node === null) { throw new Error('Tree is empty, my dear friend!'); }

    callback(node.value);
    if (node.left !== null) { this.traversePreOrder(callback, node.left); }
    if (node.right !== null) { this.traversePreOrder(callback, node.right); }
  }
}

module.exports = BinarySearchTree;
<commit_msg>Add breadthFirstSearch<commit_after>/** Class representing a node in the tree */
class Node {
  /**
   * Allows us to quickly create nodes for our binary search tree
   *
   * @constructor
   * @param {Number|String} value - value held by node
   */
  constructor(value) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}

/**
 * @description: Helper for deleteNode function. Finds minimum of tree and
 * deletes it.
 *
 * Strategy: Move parent and child pointers left until child is min. Upon match,
 * update parent's left pointer to min's right subtree.
 *
 * Edge case(s): empty tree, incorrect input type. If root is min, then we
 * cannot remove it from the tree, but we can still return the node.
 *
 * Time complexity: O(log N)
 * Space complexity: O(1)
 *
 * @param {Object} root - tree whose minimum will be removed
 * @return {Object} - node with minimum value in tree
 */
function deleteMin(root) {
  let parent = root;
  if (parent === null || typeof parent !== 'object') {
    throw new Error('Please insert valid tree');
  }

  let child = root.left;
  if (child === null) {
    // no children means parent is min
    return parent.right === null ? parent : parent.right;
  }

  while (child !== null && child.left !== null) {
    parent = parent.left;
    child = child.left;
  }

  // min may have right subtree, so point parent to that subtree (may also be null)
  parent.left = child.right;
  return child;
}

/**
 * @description: Helper for delete method. Decides strategy based on number
 * of children.
 *
 * Strategy: If no children, return null. If one child, return it. If two
 * children, we have some work to do... delete and store minimum in right
 * subtree, assign minimum's left and right pointers to those of the node to
 * be deleted, then return min node.
 *
 * Edge case(s): right subtree contains only one node
 *
 * Time complexity: O(log N)
 * Space complexity: O(1)
 *
 * @param {Object} node - node to be removed
 * @return {Object} - null, node's only child, or right subtree minimum
 */
function deleteNode(node) {
  let numberOfChildren = 0;
  if (node.left !== null) { numberOfChildren++; }
  if (node.right !== null) { numberOfChildren++; }

  switch (numberOfChildren) {
    case 0:
      return null;
    case 1:
      return node.left === null ? node.right : node.left;
    case 2:
      const rightMin = deleteMin(node.right);
      rightMin.left = node.left;

      // if right subtree min is node's right child, then replace with null
      // to avoid accidental circular reference
      rightMin.right = node.right === rightMin ? null : node.right;
      return rightMin;
  }
}

/** Class representing our tree */
class BinarySearchTree {
  /**
   * Track root of tree.
   *
   * @constructor
   */
  constructor() {
    this.root = null;
  }

  /**
   * @description: Adds a node with the given value to the tree.
   *
   * Strategy: Start at root node, then check if input value is smaller or greater
   * than root value. Each direction will check to see if a node is already present
   * in that direction. If exists, set current node to that node to traverse.
   * If not, insert new node with input value.
   *
   * Edge case(s): inappropriate inputs, duplicates
   *
   * Time complexity: O(log N)
   * Space complexity: O(1)
   *
   * @param {Number|String} value - value to be inserted into new node
   * @return {Boolean} - true means add was successful
   */
  add(value) {
    // Type checking
    if (value === undefined || typeof value === 'object' || isNaN(value)) {
      throw new Error('Please avoid adding silly values like undefined, null, objects or NaN');
    }

    let newNode = new Node(value);

    // If tree is empty...
    if (this.root === null) {
      this.root = newNode;
      return true;
    }

    let node = this.root;

    // Traverse tree
    while (node !== null) {
      if (value === node.value) { throw new Error('Cannot add duplicate values'); }

      // Lesser values move us left or create new left tree
      if (value < node.value) {
        if (node.left === null) {
          node.left = newNode;
          return true;
        }

        node = node.left;
      }

      // Greater values move us right or create new right tree
      if (value > node.value) {
        if (node.right === null) {
          node.right = newNode;
          return true;
        }

        node = node.right;
      }
    }
  }

  /**
   * @description Apply a callback to the entire tree in level order.
   *
   * Strategy: Initialize queue with root. Repeatedly use queue to access
   * children and store in new queue. End loop when no children are added.
   *
   * Time complexity: O(N)
   * Space complexity: O(N)
   *
   * @param {Function} callback - calls all values in breadth-first order
   */
  breadthFirstSearch(callback) {
    if (this.root === null) { throw new Error('Empty tree, my friend!'); }
    let queue = [this.root];
    while (true) {
      // use another queue to avoid expensive shifting costs
      let newQueue = [];

      // key idea is to use nodes in queue to set up next queue
      queue.forEach(node => {
        callback(node.value);
        if (node.left !== null) { newQueue.push(node.left); }
        if (node.right !== null) { newQueue.push(node.right); }
      });

      // nothing added to new queue means everything has been searched
      if (newQueue.length === 0) { return; }

      // reassign queue to list of nodes in next level of tree
      queue = newQueue;
    }
  }

  /**
   * @description Check if node with given value exists in tree.
   *
   * Strategy: Start at root node. Loop to traverse tree until null pointer.
   * Check if input value ever equals a node's value.
   *
   * Time complexity: O(log N)
   * Space complexity: O(1)
   *
   * @param {*} value - searching for this value in the tree
   * @return {Boolean} - whether or not value exists in tree
   */
  contains(value) {
    let node = this.root;

    while (node !== null) {
      if (value === node.value) { return true; }
      if (value < node.value) { node = node.left; }
      if (value > node.value) { node = node.right; }
    }

    return false;
  }

  /**
   * @description: Hibbard deletion. Removes a node with the given value from
   * the tree. If used extensively, slants tree and makes tree height sqrt(N),
   * which slows typically logarithmic operations.
   *
   * Strategy: Traverse with parent pointer until find child node containing
   * the given value. Call deleteNode on matched child and update parent pointer
   * in the direction previously traversed.
   *
   * Edge case(s): delete root, input does not exist in tree, empty tree
   *
   * Time complexity: O(log N)
   * Space complexity: O(1)
   *
   * @param {Number|String} value - value of node to be removed
   * @return {Object} - node removed
   */
  delete(value) {
    if (this.root === null) { throw new Error('Tree is empty, my dear friend!'); }
    if (value === undefined || typeof value === 'object' || isNaN(value)) {
      throw new Error('Values like undefined, null, objects and NaN do not exist for deletion');
    }

    let parent = this.root;
    let child;

    // direction remembers whether child is right or left subtree of parent
    let direction;

    // check if root is node to be deleted
    if (value === parent.value) {
      this.root = deleteNode(parent);
      return parent;
    }

    // set initial child to check value against
    if (value < parent.value) {
      child = parent.left;
      direction = 'left';
    } else if (value > parent.value) {
      child = parent.right;
      direction = 'right';
    }

    // traverse tree
    while (child !== null) {
      if (value === child.value) {
        // parent overwrites pointer to matched child
        parent[direction] = deleteNode(child);
        return child;
      } else if (value < child.value) {
        child = child.left;
        parent = parent[direction];
        direction = 'left';
      } else if (value > child.value) {
        child = child.right;
        parent = parent[direction];
        direction = 'right';
      }
    }

    throw new Error('No match found for deletion.');
  }

  /**
   * @description Depth-first in-order traversal that applies callback to
   * all node values in ascending order.
   *
   * Strategy: Callback is applied between recursive calls to left and right
   * subtrees. End traversal in either direction when pointer in that direction
   * is null.
   *
   * Edge case(s): Empty tree
   *
   * Time complexity: O(N)
   * Space complexity: O(1)
   *
   * @param {Function} callback - invoked on every node's value in-order
   * @param {Object=} node - current node being traversed and called
   */
  depthInOrder(callback, node = this.root) {
    if (node === null) { throw new Error('Tree is empty, my dear friend!'); }

    if (node.left !== null) { this.depthInOrder(callback, node.left); }
    callback(node.value);
    if (node.right !== null) { this.depthInOrder(callback, node.right); }
  }

  /**
   * @description Depth-first post-order traversal that applies callback to
   * all node values in this order: left leaf, right leaf, root
   *
   * Strategy: Callback is applied after recursive calls to left and right
   * subtrees. End traversal in either direction when pointer in that direction
   * is null.
   *
   * Edge case(s): Empty tree
   *
   * Time complexity: O(N)
   * Space complexity: O(1)
   *
   * @param {Function} callback - invoked on every node's value post-order
   * @param {Object=} node - current node being traversed and called
   */
  depthPostOrder(callback, node = this.root) {
    if (node === null) { throw new Error('Tree is empty, my dear friend!'); }

    if (node.left !== null) { this.depthPostOrder(callback, node.left); }
    if (node.right !== null) { this.depthPostOrder(callback, node.right); }
    callback(node.value);
  }

  /**
   * @description Depth-first pre-order traversal that applies callback to
   * all node values in this order: root, left leaf, right leaf
   *
   * Strategy: Callback is applied before recursive calls to left and right
   * subtrees. End traversal in either direction when pointer in that direction
   * is null.
   *
   * Edge case(s): Empty tree
   *
   * Time complexity: O(N)
   * Space complexity: O(1)
   *
   * @param {Function} callback - invoked on every node's value pre-order
   * @param {Object=} node - current node being traversed and called
   */
  depthPreOrder(callback, node = this.root) {
    if (node === null) { throw new Error('Tree is empty, my dear friend!'); }

    callback(node.value);
    if (node.left !== null) { this.depthPreOrder(callback, node.left); }
    if (node.right !== null) { this.depthPreOrder(callback, node.right); }
  }

  /**
   * @description Find maximum value in tree.
   *
   * Strategy: Maximum is right-most node. Traverse tree by starting at root node
   * and reassigning node to that node's right. When right pointer is null, current
   * node's value will be the maximum value in the tree.
   *
   * Edge case(s): Empty tree
   *
   * Time complexity: O(log N)
   * Space complexity: O(1)
   *
   * @return {Number|String} - maximum value in tree
   */
  max() {
    let node = this.root;
    if (node === null) { throw new Error('Tree is empty, my dear friend!'); }
    while (node.right !== null) { node = node.right; }
    return node.value;
  }

  /**
   * @description Find minimum value in tree.
   *
   * Strategy: Minimum is left-most node. Traverse tree by starting at root node
   * and reassigning node to that node's left. When left pointer is null, current
   * node's value will be the minimum value in the tree.
   *
   * Edge case(s): Empty tree
   *
   * Time complexity: O(log N)
   * Space complexity: O(1)
   *
   * @return {Number|String} - minimum value in tree
   */
  min() {
    let node = this.root;
    if (node === null) { throw new Error('Tree is empty, my dear friend!'); }
    while (node.left !== null) { node = node.left; }
    return node.value;
  }
}

module.exports = BinarySearchTree;
<|endoftext|>


<commit_before>/**
 * Policy Mappings
 * (sails.config.policies)
 *
 * Policies are simple functions which run **before** your controllers.
 * You can apply one or more policies to a given controller, or protect
 * its actions individually.
 *
 * Any policy file (e.g. `api/policies/authenticated.js`) can be accessed
 * below by its filename, minus the extension, (e.g. "authenticated")
 *
 * For more information on how policies work, see:
 * http://sailsjs.org/#/documentation/concepts/Policies
 *
 * For more information on configuring policies, check out:
 * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.policies.html
 */

module.exports.policies = {

  /***************************************************************************
  *                                                                          *
  * Default policy for all controllers and actions (`true` allows public     *
  * access)                                                                  *
  *                                                                          *
  ***************************************************************************/

  '*': true,

  /***************************************************************************
  *                                                                          *
  * Here's an example of mapping some policies to run before a controller    *
  * and its actions                                                          *
  *                                                                          *
  ***************************************************************************/
	// RabbitController: {

		// Apply the `false` policy as the default for all of RabbitController's actions
		// (`false` prevents all access, which ensures that nothing bad happens to our rabbits)
		// '*': false,

		// For the action `nurture`, apply the 'isRabbitMother' policy
		// (this overrides `false` above)
		// nurture	: 'isRabbitMother',

		// Apply the `isNiceToAnimals` AND `hasRabbitFood` policies
		// before letting any users feed our rabbits
		// feed : ['isNiceToAnimals', 'hasRabbitFood']
	// }
  PostController: {
    edit: 'sessionAuth',
    create: 'sessionAuth',
    delete: 'sessionAuth'
  },

  UserController: {
    edit: 'sessionAuth',
    create: 'sessionAuth',
    delete: 'sessionAuth'
  }
};
<commit_msg>Настройка политик доступа<commit_after>/**
 * Policy Mappings
 * (sails.config.policies)
 *
 * Policies are simple functions which run **before** your controllers.
 * You can apply one or more policies to a given controller, or protect
 * its actions individually.
 *
 * Any policy file (e.g. `api/policies/authenticated.js`) can be accessed
 * below by its filename, minus the extension, (e.g. "authenticated")
 *
 * For more information on how policies work, see:
 * http://sailsjs.org/#/documentation/concepts/Policies
 *
 * For more information on configuring policies, check out:
 * http://sailsjs.org/#/documentation/reference/sails.config/sails.config.policies.html
 */

module.exports.policies = {

  /***************************************************************************
  *                                                                          *
  * Default policy for all controllers and actions (`true` allows public     *
  * access)                                                                  *
  *                                                                          *
  ***************************************************************************/

  '*': true,

  /***************************************************************************
  *                                                                          *
  * Here's an example of mapping some policies to run before a controller    *
  * and its actions                                                          *
  *                                                                          *
  ***************************************************************************/
	// RabbitController: {

		// Apply the `false` policy as the default for all of RabbitController's actions
		// (`false` prevents all access, which ensures that nothing bad happens to our rabbits)
		// '*': false,

		// For the action `nurture`, apply the 'isRabbitMother' policy
		// (this overrides `false` above)
		// nurture	: 'isRabbitMother',

		// Apply the `isNiceToAnimals` AND `hasRabbitFood` policies
		// before letting any users feed our rabbits
		// feed : ['isNiceToAnimals', 'hasRabbitFood']
	// }
  PostController: {
    '*': false,
    'find': true,
    'findOne': true
  },

  UserController: {
    '*': 'sessionAuth'
  }
};
<|endoftext|>


<commit_before>require.config({
  paths: {
    jquery: 'lib/jquery/jquery',
    text: 'lib/requirejs-text/text',
    base64: 'lib/base64/base64',
    hogan: 'lib/hogan/web/builds/2.0.0/hogan-2.0.0.amd',
    hgn: 'lib/requirejs-hogan-plugin/hgn',
    jasmine: 'lib/jasmine/lib/jasmine-core/jasmine',
    'jasmine-html': 'lib/jasmine/lib/jasmine-core/jasmine-html',
    'jasmine-jquery': 'lib/jasmine-jquery/lib/jasmine-jquery',
    'event-emitter': 'lib/event-emitter/src/event-emitter',
    'livefyre-package-attribute': 'lib/livefyre-package-attribute/src/main',
    inherits: 'lib/inherits/inherits',
    json: 'lib/requirejs-plugins/src/json'
  },
  packages: [{
    name: "streamhub-hot-collections",
    location: "src/"
  },{
    name: "streamhub-hot-collections-tests",
    location: "tests/"
  },{
    name: "streamhub-metrics",
    location: "lib/streamhub-metrics/src"
  },{
    name: "streamhub-sdk",
    location: "lib/streamhub-sdk/src/"
  },{
    name: 'streamhub-sdk/modal',
    location: "lib/streamhub-sdk/src/modal"
  },{
    name: 'streamhub-sdk/auth',
    location: 'lib/streamhub-sdk/src/auth'
  },{
    name: 'streamhub-sdk/collection',
    location: "lib/streamhub-sdk/src/collection"
  },{
    name: 'streamhub-sdk/content',
    location: "lib/streamhub-sdk/src/content"
  },{
    name: "stream",
    location: "lib/stream/src"
  },{
    name: "view",
    location: "lib/view/src",
    main: "view"
  },{
    name: "css",
    location: "lib/require-css",
    main: "css"
  },{
    name: "less",
    location: "lib/require-less",
    main: "less"
  }],
  css: {
    clearFileEachBuild: 'dist/streamhub-hot-collections.min.css',
    transformEach: {
      requirejs: 'lib/livefyre-package-attribute/tools/prefix-css-requirejs',
      node: 'lib/livefyre-package-attribute/tools/prefix-css-node'
    }
  },
  shim: {
    jquery: {
        exports: '$'
    },
    jasmine: {
        exports: 'jasmine'
    },
    'jasmine-html': {
        deps: ['jasmine'],
        exports: 'jasmine'
    },
    'jasmine-jquery': {
        deps: ['jquery', 'jasmine']
    },
    rework: {
      exports: 'rework'
    }
  }
});
<commit_msg>Removed extra slash and added rework to require conf<commit_after>require.config({
  paths: {
    jquery: 'lib/jquery/jquery',
    text: 'lib/requirejs-text/text',
    base64: 'lib/base64/base64',
    hogan: 'lib/hogan/web/builds/2.0.0/hogan-2.0.0.amd',
    hgn: 'lib/requirejs-hogan-plugin/hgn',
    jasmine: 'lib/jasmine/lib/jasmine-core/jasmine',
    'jasmine-html': 'lib/jasmine/lib/jasmine-core/jasmine-html',
    'jasmine-jquery': 'lib/jasmine-jquery/lib/jasmine-jquery',
    'event-emitter': 'lib/event-emitter/src/event-emitter',
    'livefyre-package-attribute': 'lib/livefyre-package-attribute/src/main',
    rework: 'lib/rework/rework',
    inherits: 'lib/inherits/inherits',
    json: 'lib/requirejs-plugins/src/json'
  },
  packages: [{
    name: "streamhub-hot-collections",
    location: "src"
  },{
    name: "streamhub-hot-collections-tests",
    location: "tests/"
  },{
    name: "streamhub-metrics",
    location: "lib/streamhub-metrics/src"
  },{
    name: "streamhub-sdk",
    location: "lib/streamhub-sdk/src/"
  },{
    name: 'streamhub-sdk/modal',
    location: "lib/streamhub-sdk/src/modal"
  },{
    name: 'streamhub-sdk/auth',
    location: 'lib/streamhub-sdk/src/auth'
  },{
    name: 'streamhub-sdk/collection',
    location: "lib/streamhub-sdk/src/collection"
  },{
    name: 'streamhub-sdk/content',
    location: "lib/streamhub-sdk/src/content"
  },{
    name: "stream",
    location: "lib/stream/src"
  },{
    name: "view",
    location: "lib/view/src",
    main: "view"
  },{
    name: "css",
    location: "lib/require-css",
    main: "css"
  },{
    name: "less",
    location: "lib/require-less",
    main: "less"
  }],
  css: {
    clearFileEachBuild: 'dist/streamhub-hot-collections.min.css',
    transformEach: {
      requirejs: 'lib/livefyre-package-attribute/tools/prefix-css-requirejs',
      node: 'lib/livefyre-package-attribute/tools/prefix-css-node'
    }
  },
  shim: {
    jquery: {
        exports: '$'
    },
    jasmine: {
        exports: 'jasmine'
    },
    'jasmine-html': {
        deps: ['jasmine'],
        exports: 'jasmine'
    },
    'jasmine-jquery': {
        deps: ['jquery', 'jasmine']
    },
    rework: {
      exports: 'rework'
    }
  }
});
<|endoftext|>


<commit_before>/*
    Just Journal JavaScript Library
    author: Lucas Holt
    date created: June 10, 2007
*/

function addFavorite(entryId) {
    'use strict';

    var request = jQuery.ajax({
        url: "/api/favorite/" + entryId,
        type: "POST",
        data: {}
    });

    request.done(function () {
        window.alert('Favorite saved.');
    });

    request.fail(function (jqXHR, textStatus) {
        window.alert("Favorite not saved. Request failed: " + textStatus);
    });
}

function deleteFavorite(entryId) {
    'use strict';

    var request = jQuery.ajax({
        url: "/api/favorite/" + entryId,
        type: "DELETE",
        data: {}
    });

    request.done(function () {
        window.alert('Favorite removed.');
    });

    request.fail(function (jqXHR, textStatus) {
        window.alert("Favorite not removed. Request failed: " + textStatus);
    });
}

function deleteEntry(entryId) {
    'use strict';
    if (confirmDelete()) {
        var request = jQuery.ajax({
            url: "/api/entry/" + entryId,
            type: "DELETE",
            data: {}
        });

        request.done(function() {
          window.alert('Removed Entry');
        });

        request.fail(function(jqXHR, textStatus) {
          window.alert("Request failed: " + textStatus);
        });
    }
}

function confirmDelete() {
    'use strict';
    return window.confirm("Are you sure you want to delete this?");
}

function showbox(boxId) {
    'use strict';
    var box = document.getElementById(boxId);
    var parentBox = document.getElementById(boxId + "parent");
    box.style.top = getAbsY(parentBox) + "px";
    box.style.left = getAbsX(parentBox) + "px";
    box.style.visibility='visible';
}

function hidebox(boxId) {
    'use strict';
    var box = document.getElementById(boxId);
    box.style.visibility='hidden';
}

// get the true X offset of anything on NS4, IE4/5 &
// NS6, even if it's in a table!
function getAbsX(elt) {
    'use strict';
    return (elt.x) ? elt.x : getAbsPos(elt,"Left");
}

// get the true Y offset of anything on NS4, IE4/5 &
// NS6, even if it's in a table!
function getAbsY(elt) {
    'use strict';

    return (elt.y) ? elt.y : getAbsPos(elt,"Top");
}

function getAbsPos(elt,which) {
    'use strict';
    var iPos = 0;

    while (elt !== null) {
        iPos += elt["offset" + which];
        elt = elt.offsetParent;
    }

    return iPos;
}
<commit_msg>add a delete comment function.<commit_after>/*
    Just Journal JavaScript Library
    author: Lucas Holt
    date created: June 10, 2007
*/

function addFavorite(entryId) {
    'use strict';

    var request = jQuery.ajax({
        url: "/api/favorite/" + entryId,
        type: "POST",
        data: {}
    });

    request.done(function () {
        window.alert('Favorite saved.');
    });

    request.fail(function (jqXHR, textStatus) {
        window.alert("Favorite not saved. Request failed: " + textStatus);
    });
}

function deleteFavorite(entryId) {
    'use strict';

    var request = jQuery.ajax({
        url: "/api/favorite/" + entryId,
        type: "DELETE",
        data: {}
    });

    request.done(function () {
        window.alert('Favorite removed.');
    });

    request.fail(function (jqXHR, textStatus) {
        window.alert("Favorite not removed. Request failed: " + textStatus);
    });
}

function deleteEntry(entryId) {
    'use strict';
    if (confirmDelete()) {
        var request = jQuery.ajax({
            url: "/api/entry/" + entryId,
            type: "DELETE",
            data: {}
        });

        request.done(function() {
          window.alert('Removed Entry');
        });

        request.fail(function(jqXHR, textStatus) {
          window.alert("Request failed: " + textStatus);
        });
    }
}

function deleteComment(commentId) {
    'use strict';
    if (confirmDelete()) {
        var request = jQuery.ajax({
            url: "/api/comment/" + commentId,
            type: "DELETE",
            data: {}
        });

        request.done(function() {
          window.alert('Removed Comment');
        });

        request.fail(function(jqXHR, textStatus) {
          window.alert("Request failed: " + textStatus);
        });
    }
}

function confirmDelete() {
    'use strict';
    return window.confirm("Are you sure you want to delete this?");
}

function showbox(boxId) {
    'use strict';
    var box = document.getElementById(boxId);
    var parentBox = document.getElementById(boxId + "parent");
    box.style.top = getAbsY(parentBox) + "px";
    box.style.left = getAbsX(parentBox) + "px";
    box.style.visibility='visible';
}

function hidebox(boxId) {
    'use strict';
    var box = document.getElementById(boxId);
    box.style.visibility='hidden';
}

// get the true X offset of anything on NS4, IE4/5 &
// NS6, even if it's in a table!
function getAbsX(elt) {
    'use strict';
    return (elt.x) ? elt.x : getAbsPos(elt,"Left");
}

// get the true Y offset of anything on NS4, IE4/5 &
// NS6, even if it's in a table!
function getAbsY(elt) {
    'use strict';

    return (elt.y) ? elt.y : getAbsPos(elt,"Top");
}

function getAbsPos(elt,which) {
    'use strict';
    var iPos = 0;

    while (elt !== null) {
        iPos += elt["offset" + which];
        elt = elt.offsetParent;
    }

    return iPos;
}
<|endoftext|>