<commit_before>/* globals controlplane: true */

/* AppsController
 * Displays top level apps
 */
(function() {
    'use strict';

    controlplane.controller("AppsController", [
        "$rootScope", "$scope", "$routeParams", "$location",
        "$notification", "resourcesFactory", "authService",
        "$modalService", "$translate", "$timeout",
        "$cookies", "miscUtils",
        "ngTableParams", "$filter",
        "Service","InternalService", "$q",
    function($rootScope, $scope, $routeParams, $location,
    $notification, resourcesFactory, authService,
    $modalService, $translate, $timeout,
    $cookies, utils,
    NgTableParams, $filter,
    Service, InternalService, $q){

        // Ensure logged in
        authService.checkLogin($scope);

        // alias hostname instead of using localhost or IP
        $scope.defaultHostAlias = $location.host();

        // redirect to specific service details
        $scope.routeToService = function(service) {
            if (service.isIsvc()) {
                resourcesFactory.routeToInternalServices();
            } else {
                resourcesFactory.routeToService(service.id);
            }
        };

        // redirect to specific pool
        $scope.routeToPool = function(id) {
            resourcesFactory.routeToPool(id);
        };

        $scope.modal_deployWizard = function() {

            // the modal occasionally won't show on page load, so we use a timeout to get around that.
            $timeout(function(){
                $('#addApp').modal('show');

                // don't auto-show this wizard again
                // NOTE: $cookies can only deal with string values
                $cookies.put("autoRunWizardHasRun","true");
            });
        };

        $scope.modal_addTemplate = function() {
            $modalService.create({
                templateUrl: "add-template.html",
                model: $scope,
                title: "template_add",
                actions: [
                    {
                        role: "cancel",
                        action: function(){
                            $scope.newHost = {};
                            this.close();
                        }
                    },{
                        role: "ok",
                        label: "template_add",
                        action: function(){
                            if(this.validate()){
                                var data = new FormData();

                                $.each($("#new_template_filename")[0].files, function(key, value){
                                    data.append("tpl", value);
                                });

                                // disable ok button, and store the re-enable function
                                var enableSubmit = this.disableSubmitButton();

                                resourcesFactory.addAppTemplate(data)
                                    .success(function(data){
                                        $notification.create("Added template", data.Detail).success();
                                        refreshTemplates();
                                        this.close();
                                    }.bind(this))
                                    .error(function(data){
                                        this.createNotification("Adding template failed", data.Detail).error();
                                        enableSubmit();
                                    }.bind(this));
                            }
                        }
                    }
                ]
            });
        };

        $scope.modal_removeService = function(service) {
            $modalService.create({
                template: $translate.instant("warning_remove_service"),
                model: $scope,
                title: "remove_service",
                actions: [
                    {
                        role: "cancel"
                    },{
                        role: "ok",
                        label: "remove_service",
                        classes: "btn-danger submit",
                        action: function(){
                            if(this.validate()){
                                this.disableSubmitButton();

                                removeService(service)
                                    .success(() => {
                                        refreshApps().then(() => {
                                            $notification.create("Removed App", service.name).success();
                                        });
                                        this.close();
                                    })
                                    .error((data, status) => {
                                        $notification.create("Remove App failed", data.Detail).error();
                                        this.close();
                                    });
                            }
                        }
                    }
                ]
            });
        };

        $scope.startService = function(service){
            $scope.modal_startStopService(service, "start");
        };
        $scope.stopService = function(service){
            $scope.modal_startStopService(service, "stop");
        };
        $scope.modal_startStopService = function(service, status) {
            var displayStatus = utils.capitalizeFirst(status);

            $modalService.create({
                template: $translate.instant("confirm_"+ status +"_app"),
                model: $scope,
                title: displayStatus +" Services",
                actions: [
                    {
                        role: "cancel"
                    },{
                        role: "ok",
                        label: displayStatus +" Services",
                        action: function(){
                            service[status]();
                            this.close();
                        }
                    }
                ]
            });
        };

        // sets a service to start, stop or restart state
        $scope.setServiceState = function(service, state, skipChildren){
            service[state](skipChildren).error(function(data, status){
                $notification.create("Unable to " + state + " service", data.Detail).error();
            });
        };

        // clicks to a service's start, stop, or restart
        // button should first determine if the service has
        // children and ask the user to choose to start all
        // children or only the top service
        $scope.clickRunning = function(service, state){
            let onStartService = function(modal){
                // the arg here explicitly prevents child services
                // from being started
                $scope.setServiceState(service, state, true);
                modal.close();
            };
            let onStartServiceAndChildren = function(modal){
                $scope.setServiceState(service, state);
                modal.close();
            };

            Service.countAffectedDescendants(service, state)
                .then(count => {
                    $modalService.modals.confirmServiceStateChange(service, state,
                        count, onStartService,
                        onStartServiceAndChildren);
                })
                .catch(err => {
                    console.warn("couldnt get descendant count", err);
                    $modalService.modals.confirmServiceStateChange(service, state,
                        0, onStartService,
                        onStartServiceAndChildren);
                });

            // let the user know they gonna have to hold onto
            // their horses for just one moment.
            $modalService.modals.oneMoment();
        };

        // verifies if use wants to start parent service, or parent
        // and all children
        $scope.modal_confirmSetServiceState = function(service, state, childCount){
            $modalService.create({
                template: ["
"+ $translate.instant("choose_services_"+ state) +"

",
                    "
"+ $translate.instant(state +"_service_name", {name: ""+service.name+""}) +"
",
                    "
"+ $translate.instant(state +"_service_name_and_children", {name: ""+service.name+"", count: ""+childCount+""}) +"

"
                ].join(""),
                model: $scope,
                title: $translate.instant(state +"_service"),
                actions: [
                    {
                        role: "cancel"
                    },{
                        role: "ok",
                        classes: " ",
                        label: $translate.instant(state +"_service"),
                        action: function(){
                            // the arg here explicitly prevents child services
                            // from being started
                            $scope.setServiceState(service, state, true);
                            this.close();
                        }
                    },{
                        role: "ok",
                        label: $translate.instant(state +"_service_and_children", {count: childCount}),
                        action: function(){
                            $scope.setServiceState(service, state);
                            this.close();
                        }
                    }
                ]
            });
        };

        $scope.modal_deleteTemplate = function(templateID){
            $modalService.create({
                template: $translate.instant("template_remove_confirm") + ""+ templateID +"",
                model: $scope,
                title: "template_remove",
                actions: [
                    {
                        role: "cancel"
                    },{
                        role: "ok",
                        label: "template_remove",
                        classes: "btn-danger",
                        action: function(){
                            deleteTemplate(templateID)
                                .success(() => {
                                    $notification.create("Removed Template", templateID).success();
                                    this.close();
                                })
                                .error((data, status) => {
                                    $notification.create("Remove Template failed", data.Detail).error();
                                });
                        }
                    }
                ]
            });
        };

        /*
         * PRIVATE FUNCTIONS
         */
        function refreshTemplates(){
            resourcesFactory.getAppTemplates().success(function(templates) {
                $scope.templates.data = utils.mapToArr(templates);
            });
        }

        function refreshApps() {
            return fetchApps()
                .then(fillOutApps)
                .then(fetchServicesHealth)
                .then(fetchInternalServicesHealth)
                .then(() => touch())
                .catch((error) => console.warn(error));
        }

        function fetchApps() {
            return $q.all([fetchInternalService(), fetchServices()])
                .then(results => {
                    $scope.apps = [];

                    var isvc = results[0];
                    $scope.apps.push(isvc);

                    var services = results[1];
                    services.forEach(s => {
                        $scope.apps.push(s);
                    });
                });
        }

        function fetchChildEndpoints(app) {
            return resourcesFactory.v2.getServiceChildPublicEndpoints(app.id).then(data => {
                app.publicEndpoints = data;
            });
        }

        function fillOutApps() {
            let promises = [];
            $scope.apps.forEach(a => {
                if (a.isIsvc()) {
                    promises.push(() => a.fetchInstances());
                } else {
                    promises.push(fetchChildEndpoints(a));
                }
            });

            return $q.all(promises);
        }

        function fetchServices() {
            let deferred = $q.defer();
            resourcesFactory.v2.getTenants().then( data => {
                    var services = data.map(s => new Service(s));
                    deferred.resolve(services);
                },
                error => {
                    console.warn(error);
                    deferred.reject();
                });
            return deferred.promise;
        }

        function fetchServicesHealth() {
            if (!$scope.apps) { return; }

            $scope.apps.forEach(app => {
                if (!app.isIsvc()) {
                    app.fetchAllStates();
                }
            });
        }

        function fetchInternalService() {
            let deferred = $q.defer();
            resourcesFactory.v2.getInternalServices()
                .then(data => {
                    let parent = data.find(i => !i.Parent);
                    if (parent) {
                        var internalServices = new InternalService(parent);
                        deferred.resolve(internalServices);
                    } else {
                        deferred.reject("Parent not found");
                    }
                },
                error => {
                    console.warn(error);
                    deferred.reject();
                });
            return deferred.promise;
        }

        function fetchInternalServicesHealth() {
            $scope.apps.forEach(app => {
                if (app.isIsvc()) {
                    app.fetchInstances().then(() => {
                        resourcesFactory.v2.getInternalServiceStatuses([app.id]).then(data => {
                            let statusMap = data.reduce((map, s) => {
                                map[s.ServiceID] = s;
                                return map;
                            }, {});

                            app.updateStatus(statusMap[app.id]);
                        });
                    });
                }
            });
        }

        function fetchGraphConfigs() {
          resourcesFactory.getStorage().then(data => {
            for (let [n, s] of data.entries()) {
              s.MonitoringProfile.GraphConfigs.forEach(g => {
                if (g.id === "storage.pool.data.usage") {
                  // in the case where we have more than one entry in storage
                  // we need our graph ids to be unique, so concat their index
                  g.id += "." + String(n)
                  $scope.graphConfigs.push(g)
                }
              });
            }
          });
        }

        // poll for apps that are being deployed
        $scope.deployingServices = [];
        var lastPollResults = 0;
        function getDeploying(){
            resourcesFactory.getDeployingTemplates().success(function(data) {
                if(data){
                    $scope.deployingServices = data;
                }

                //if we have fewer results than last poll, we need to refresh our table
                //TODO - better checking for deploying apps
                if(lastPollResults > $scope.deployingServices.length){
                    refreshApps();
                }
                lastPollResults = $scope.deployingServices.length;
            });
        }

        function removeService(service) {
            return resourcesFactory.removeService(service.id)
                .success(function(){
                    // NOTE: this is here because services are
                    // incrementally updated, which makes it impossible
                    // to determine if a service has been removed
                    // TODO - once the backend notifies on deleted
                    // services, this should be removed
                    // FIXME - should not modify servicesFactory's
                    // objects!
                    for(var i = 0; i < $scope.apps.length; i++){
                        // find the removed service and remove it
                        if($scope.apps[i].id === service.id){
                            $scope.apps.splice(i, 1);
                            touch();
                            return;
                        }
                    }
                });
        }

        function deleteTemplate(templateID){
            return resourcesFactory.removeAppTemplate(templateID)
                .success(refreshTemplates);
        }

        var lastUpdate;
        function touch() {
            lastUpdate = new Date().getTime();
        }

        // init stuff for this controller
        function init(){
            touch();

            if(utils.needsHostAlias($location.host())){
                resourcesFactory.getHostAlias().success(function(data) {
                    $scope.defaultHostAlias = data.hostalias;
                });
            }

            // configure tables
            // TODO - move table config to view/directive
            $scope.breadcrumbs = [
                { label: 'breadcrumb_deployed', itemClass: 'active' }
            ];

            $scope.servicesTable = {
                sorting: {
                    name: "asc"
                },
                searchColumns: ['name','model.Description', 'model.DeploymentID', 'model.PoolID'],
                getData: function(data, params) {
                    // use built-in angular filter
                    var orderedData = params.sorting() ?
                        $filter('orderBy')(data, params.orderBy()) :
                        data;

                    if(!orderedData){
                        return;
                    }

                    // mark any deploying services so they can be treated differently
                    orderedData.forEach(function(service){
                        service.deploying = false;
                        $scope.deployingServices.forEach(function(deploying){
                            if(service.model.DeploymentID === deploying.DeploymentID){
                                service.deploying = true;
                            }
                        });
                    });

                    return orderedData;
                },
                watchExpression: function(){
                    // TODO - check $scope.deployingServices as well
                    return lastUpdate;
                }
            };

            $scope.templates = { data: [] };
            // table config
            $scope.templatesTable = {
                sorting: {
                    Name: "asc"
                },
                searchColumns: ['Name','ID', 'Description']
            };

            // get configurations for graphs
            $scope.graphConfigs = [];
            fetchGraphConfigs();

            // Get a list of templates
            refreshTemplates();

            // check for deploying apps
            getDeploying();

            refreshApps().then(() => {
                $scope.$emit("ready");

                // if only isvcs are deployed, and this is the first time
                // running deploy wizard, show the deploy apps modal
                if(!$cookies.get("autoRunWizardHasRun") && $scope.apps.length === 1){
                    $scope.modal_deployWizard();
                }
            });

            //register polls
            resourcesFactory.registerPoll("deployingApps", getDeploying, 3000);
            resourcesFactory.registerPoll("templates", refreshTemplates, 5000);
            resourcesFactory.registerPoll("internalServiceHealth", fetchInternalServicesHealth, 3000);
            resourcesFactory.registerPoll("appHealth", fetchServicesHealth, 3000);

            //unregister polls on destroy
            $scope.$on("$destroy", function(){
                resourcesFactory.unregisterAllPolls();
            });

            // be sure to update apps table after deploywiz
            // finishes its dark magicks
            $rootScope.$on("wizard.deployed", () => {
                refreshApps();
            });
        }

        // kick this controller off
        init();

        $scope.$on("$destroy", function(){
            resourcesFactory.unregisterAllPolls();
        });
    }]);
})();
<commit_msg>semicolons...<commit_after>/* globals controlplane: true */

/* AppsController
 * Displays top level apps
 */
(function() {
    'use strict';

    controlplane.controller("AppsController", [
        "$rootScope", "$scope", "$routeParams", "$location",
        "$notification", "resourcesFactory", "authService",
        "$modalService", "$translate", "$timeout",
        "$cookies", "miscUtils",
        "ngTableParams", "$filter",
        "Service","InternalService", "$q",
    function($rootScope, $scope, $routeParams, $location,
    $notification, resourcesFactory, authService,
    $modalService, $translate, $timeout,
    $cookies, utils,
    NgTableParams, $filter,
    Service, InternalService, $q){

        // Ensure logged in
        authService.checkLogin($scope);

        // alias hostname instead of using localhost or IP
        $scope.defaultHostAlias = $location.host();

        // redirect to specific service details
        $scope.routeToService = function(service) {
            if (service.isIsvc()) {
                resourcesFactory.routeToInternalServices();
            } else {
                resourcesFactory.routeToService(service.id);
            }
        };

        // redirect to specific pool
        $scope.routeToPool = function(id) {
            resourcesFactory.routeToPool(id);
        };

        $scope.modal_deployWizard = function() {

            // the modal occasionally won't show on page load, so we use a timeout to get around that.
            $timeout(function(){
                $('#addApp').modal('show');

                // don't auto-show this wizard again
                // NOTE: $cookies can only deal with string values
                $cookies.put("autoRunWizardHasRun","true");
            });
        };

        $scope.modal_addTemplate = function() {
            $modalService.create({
                templateUrl: "add-template.html",
                model: $scope,
                title: "template_add",
                actions: [
                    {
                        role: "cancel",
                        action: function(){
                            $scope.newHost = {};
                            this.close();
                        }
                    },{
                        role: "ok",
                        label: "template_add",
                        action: function(){
                            if(this.validate()){
                                var data = new FormData();

                                $.each($("#new_template_filename")[0].files, function(key, value){
                                    data.append("tpl", value);
                                });

                                // disable ok button, and store the re-enable function
                                var enableSubmit = this.disableSubmitButton();

                                resourcesFactory.addAppTemplate(data)
                                    .success(function(data){
                                        $notification.create("Added template", data.Detail).success();
                                        refreshTemplates();
                                        this.close();
                                    }.bind(this))
                                    .error(function(data){
                                        this.createNotification("Adding template failed", data.Detail).error();
                                        enableSubmit();
                                    }.bind(this));
                            }
                        }
                    }
                ]
            });
        };

        $scope.modal_removeService = function(service) {
            $modalService.create({
                template: $translate.instant("warning_remove_service"),
                model: $scope,
                title: "remove_service",
                actions: [
                    {
                        role: "cancel"
                    },{
                        role: "ok",
                        label: "remove_service",
                        classes: "btn-danger submit",
                        action: function(){
                            if(this.validate()){
                                this.disableSubmitButton();

                                removeService(service)
                                    .success(() => {
                                        refreshApps().then(() => {
                                            $notification.create("Removed App", service.name).success();
                                        });
                                        this.close();
                                    })
                                    .error((data, status) => {
                                        $notification.create("Remove App failed", data.Detail).error();
                                        this.close();
                                    });
                            }
                        }
                    }
                ]
            });
        };

        $scope.startService = function(service){
            $scope.modal_startStopService(service, "start");
        };
        $scope.stopService = function(service){
            $scope.modal_startStopService(service, "stop");
        };
        $scope.modal_startStopService = function(service, status) {
            var displayStatus = utils.capitalizeFirst(status);

            $modalService.create({
                template: $translate.instant("confirm_"+ status +"_app"),
                model: $scope,
                title: displayStatus +" Services",
                actions: [
                    {
                        role: "cancel"
                    },{
                        role: "ok",
                        label: displayStatus +" Services",
                        action: function(){
                            service[status]();
                            this.close();
                        }
                    }
                ]
            });
        };

        // sets a service to start, stop or restart state
        $scope.setServiceState = function(service, state, skipChildren){
            service[state](skipChildren).error(function(data, status){
                $notification.create("Unable to " + state + " service", data.Detail).error();
            });
        };

        // clicks to a service's start, stop, or restart
        // button should first determine if the service has
        // children and ask the user to choose to start all
        // children or only the top service
        $scope.clickRunning = function(service, state){
            let onStartService = function(modal){
                // the arg here explicitly prevents child services
                // from being started
                $scope.setServiceState(service, state, true);
                modal.close();
            };
            let onStartServiceAndChildren = function(modal){
                $scope.setServiceState(service, state);
                modal.close();
            };

            Service.countAffectedDescendants(service, state)
                .then(count => {
                    $modalService.modals.confirmServiceStateChange(service, state,
                        count, onStartService,
                        onStartServiceAndChildren);
                })
                .catch(err => {
                    console.warn("couldnt get descendant count", err);
                    $modalService.modals.confirmServiceStateChange(service, state,
                        0, onStartService,
                        onStartServiceAndChildren);
                });

            // let the user know they gonna have to hold onto
            // their horses for just one moment.
            $modalService.modals.oneMoment();
        };

        // verifies if use wants to start parent service, or parent
        // and all children
        $scope.modal_confirmSetServiceState = function(service, state, childCount){
            $modalService.create({
                template: ["
"+ $translate.instant("choose_services_"+ state) +"

",
                    "
"+ $translate.instant(state +"_service_name", {name: ""+service.name+""}) +"
",
                    "
"+ $translate.instant(state +"_service_name_and_children", {name: ""+service.name+"", count: ""+childCount+""}) +"

"
                ].join(""),
                model: $scope,
                title: $translate.instant(state +"_service"),
                actions: [
                    {
                        role: "cancel"
                    },{
                        role: "ok",
                        classes: " ",
                        label: $translate.instant(state +"_service"),
                        action: function(){
                            // the arg here explicitly prevents child services
                            // from being started
                            $scope.setServiceState(service, state, true);
                            this.close();
                        }
                    },{
                        role: "ok",
                        label: $translate.instant(state +"_service_and_children", {count: childCount}),
                        action: function(){
                            $scope.setServiceState(service, state);
                            this.close();
                        }
                    }
                ]
            });
        };

        $scope.modal_deleteTemplate = function(templateID){
            $modalService.create({
                template: $translate.instant("template_remove_confirm") + ""+ templateID +"",
                model: $scope,
                title: "template_remove",
                actions: [
                    {
                        role: "cancel"
                    },{
                        role: "ok",
                        label: "template_remove",
                        classes: "btn-danger",
                        action: function(){
                            deleteTemplate(templateID)
                                .success(() => {
                                    $notification.create("Removed Template", templateID).success();
                                    this.close();
                                })
                                .error((data, status) => {
                                    $notification.create("Remove Template failed", data.Detail).error();
                                });
                        }
                    }
                ]
            });
        };

        /*
         * PRIVATE FUNCTIONS
         */
        function refreshTemplates(){
            resourcesFactory.getAppTemplates().success(function(templates) {
                $scope.templates.data = utils.mapToArr(templates);
            });
        }

        function refreshApps() {
            return fetchApps()
                .then(fillOutApps)
                .then(fetchServicesHealth)
                .then(fetchInternalServicesHealth)
                .then(() => touch())
                .catch((error) => console.warn(error));
        }

        function fetchApps() {
            return $q.all([fetchInternalService(), fetchServices()])
                .then(results => {
                    $scope.apps = [];

                    var isvc = results[0];
                    $scope.apps.push(isvc);

                    var services = results[1];
                    services.forEach(s => {
                        $scope.apps.push(s);
                    });
                });
        }

        function fetchChildEndpoints(app) {
            return resourcesFactory.v2.getServiceChildPublicEndpoints(app.id).then(data => {
                app.publicEndpoints = data;
            });
        }

        function fillOutApps() {
            let promises = [];
            $scope.apps.forEach(a => {
                if (a.isIsvc()) {
                    promises.push(() => a.fetchInstances());
                } else {
                    promises.push(fetchChildEndpoints(a));
                }
            });

            return $q.all(promises);
        }

        function fetchServices() {
            let deferred = $q.defer();
            resourcesFactory.v2.getTenants().then( data => {
                    var services = data.map(s => new Service(s));
                    deferred.resolve(services);
                },
                error => {
                    console.warn(error);
                    deferred.reject();
                });
            return deferred.promise;
        }

        function fetchServicesHealth() {
            if (!$scope.apps) { return; }

            $scope.apps.forEach(app => {
                if (!app.isIsvc()) {
                    app.fetchAllStates();
                }
            });
        }

        function fetchInternalService() {
            let deferred = $q.defer();
            resourcesFactory.v2.getInternalServices()
                .then(data => {
                    let parent = data.find(i => !i.Parent);
                    if (parent) {
                        var internalServices = new InternalService(parent);
                        deferred.resolve(internalServices);
                    } else {
                        deferred.reject("Parent not found");
                    }
                },
                error => {
                    console.warn(error);
                    deferred.reject();
                });
            return deferred.promise;
        }

        function fetchInternalServicesHealth() {
            $scope.apps.forEach(app => {
                if (app.isIsvc()) {
                    app.fetchInstances().then(() => {
                        resourcesFactory.v2.getInternalServiceStatuses([app.id]).then(data => {
                            let statusMap = data.reduce((map, s) => {
                                map[s.ServiceID] = s;
                                return map;
                            }, {});

                            app.updateStatus(statusMap[app.id]);
                        });
                    });
                }
            });
        }

        function fetchGraphConfigs() {
          resourcesFactory.getStorage().then(data => {
            for (let [n, s] of data.entries()) {
              s.MonitoringProfile.GraphConfigs.forEach(g => {
                if (g.id === "storage.pool.data.usage") {
                  // in the case where we have more than one entry in storage
                  // we need our graph ids to be unique, so concat their index
                  g.id += "." + String(n);
                  $scope.graphConfigs.push(g);
                }
              });
            }
          });
        }

        // poll for apps that are being deployed
        $scope.deployingServices = [];
        var lastPollResults = 0;
        function getDeploying(){
            resourcesFactory.getDeployingTemplates().success(function(data) {
                if(data){
                    $scope.deployingServices = data;
                }

                //if we have fewer results than last poll, we need to refresh our table
                //TODO - better checking for deploying apps
                if(lastPollResults > $scope.deployingServices.length){
                    refreshApps();
                }
                lastPollResults = $scope.deployingServices.length;
            });
        }

        function removeService(service) {
            return resourcesFactory.removeService(service.id)
                .success(function(){
                    // NOTE: this is here because services are
                    // incrementally updated, which makes it impossible
                    // to determine if a service has been removed
                    // TODO - once the backend notifies on deleted
                    // services, this should be removed
                    // FIXME - should not modify servicesFactory's
                    // objects!
                    for(var i = 0; i < $scope.apps.length; i++){
                        // find the removed service and remove it
                        if($scope.apps[i].id === service.id){
                            $scope.apps.splice(i, 1);
                            touch();
                            return;
                        }
                    }
                });
        }

        function deleteTemplate(templateID){
            return resourcesFactory.removeAppTemplate(templateID)
                .success(refreshTemplates);
        }

        var lastUpdate;
        function touch() {
            lastUpdate = new Date().getTime();
        }

        // init stuff for this controller
        function init(){
            touch();

            if(utils.needsHostAlias($location.host())){
                resourcesFactory.getHostAlias().success(function(data) {
                    $scope.defaultHostAlias = data.hostalias;
                });
            }

            // configure tables
            // TODO - move table config to view/directive
            $scope.breadcrumbs = [
                { label: 'breadcrumb_deployed', itemClass: 'active' }
            ];

            $scope.servicesTable = {
                sorting: {
                    name: "asc"
                },
                searchColumns: ['name','model.Description', 'model.DeploymentID', 'model.PoolID'],
                getData: function(data, params) {
                    // use built-in angular filter
                    var orderedData = params.sorting() ?
                        $filter('orderBy')(data, params.orderBy()) :
                        data;

                    if(!orderedData){
                        return;
                    }

                    // mark any deploying services so they can be treated differently
                    orderedData.forEach(function(service){
                        service.deploying = false;
                        $scope.deployingServices.forEach(function(deploying){
                            if(service.model.DeploymentID === deploying.DeploymentID){
                                service.deploying = true;
                            }
                        });
                    });

                    return orderedData;
                },
                watchExpression: function(){
                    // TODO - check $scope.deployingServices as well
                    return lastUpdate;
                }
            };

            $scope.templates = { data: [] };
            // table config
            $scope.templatesTable = {
                sorting: {
                    Name: "asc"
                },
                searchColumns: ['Name','ID', 'Description']
            };

            // get configurations for graphs
            $scope.graphConfigs = [];
            fetchGraphConfigs();

            // Get a list of templates
            refreshTemplates();

            // check for deploying apps
            getDeploying();

            refreshApps().then(() => {
                $scope.$emit("ready");

                // if only isvcs are deployed, and this is the first time
                // running deploy wizard, show the deploy apps modal
                if(!$cookies.get("autoRunWizardHasRun") && $scope.apps.length === 1){
                    $scope.modal_deployWizard();
                }
            });

            //register polls
            resourcesFactory.registerPoll("deployingApps", getDeploying, 3000);
            resourcesFactory.registerPoll("templates", refreshTemplates, 5000);
            resourcesFactory.registerPoll("internalServiceHealth", fetchInternalServicesHealth, 3000);
            resourcesFactory.registerPoll("appHealth", fetchServicesHealth, 3000);

            //unregister polls on destroy
            $scope.$on("$destroy", function(){
                resourcesFactory.unregisterAllPolls();
            });

            // be sure to update apps table after deploywiz
            // finishes its dark magicks
            $rootScope.$on("wizard.deployed", () => {
                refreshApps();
            });
        }

        // kick this controller off
        init();

        $scope.$on("$destroy", function(){
            resourcesFactory.unregisterAllPolls();
        });
    }]);
})();
<|endoftext|>


<commit_before>/**
 * main.js  - 17.02.2016
 *
 * @author Johannes Haeussler - Johannes.3.Haeussler(at)uni-konstanz.de
 * @version 1.0
 */
$( function () {
    $.widget( 'jh.timeIntervalPicker', {
        options : {
            width : 500,
            height : 500,
            radius : 200,
            strokecolor : 'black',
            strokewidth : 5,
            multiselection : true
        },

        _create : function () {
            var lastY = 0;
            var lastX = 0;
            var _this = this;

            var startTimes = [];
            var endTimes = [];
            var actualStartTime = null;
            var actualEndTime = null;

            var mousedown = false;
            var appendMode = false;

            var arcStartPoints = [];
            var actualArcStartPoint = {};
            var arcPath = null;
            var interactionGroup = null;

            var dist = _this.options.radius + _this.options.strokewidth;
            var interactionGroupOptions = {
                transform: 'translate('+ dist + ',' + dist + ')',
                stroke: 'blue',
                strokeOpacity: 0.4,
                fill: 'blue',
                fillOpacity:"0.4"
            };

            this.svg = null;
            this.element.css( 'width', this.options.width );
            this.element.css( 'height', this.options.height );

            /**
             * Public Functions
             */
            this.getRelativePosition = function ( x, y ) {
                // Get absolute position of svgContainer
                var offset = _this.element.svgContainer.offset();
                return {
                    x : x - offset.left,
                    y : y - offset.top
                };
            }

            this.getAngleFromRelativePosition = function ( x2, y2 ) {
                // Position to angle
                var x1y1 =  _this.svgWidthHeight / 2;
                return Math.atan2( x1y1 - y2, x1y1 - x2 ) * 180 / Math.PI;
            }

            /**
             * Init elements
             */
            this._createElements();

            /**
             * Mouse Events
             */
            this.element.svgContainer.mousedown( function ( event ) {
                // Draw a path
                console.log( "Mousedown" );

                // Right Click to reset
                if(event.which == 3) {
                    clearSelection();
                    return;
                }

                // Check ctrl hold for append mode
                appendMode = event.ctrlKey && _this.options.multiselection;
                if(!appendMode) {
                    clearSelection();
                }

                // Start an arc path and draw a line
                actualArcStartPoint = getArcPointFromAbsolutePosition(event.pageX, event.pageY);
                arcStartPoints.push(actualArcStartPoint);
                drawSvgArc(null);

                // Set start time
                actualStartTime = getTimeFromAbsolutePosition(event.pageX, event.pageY);
                console.log( "Setting StartTime: " + actualStartTime );

                mousedown = true;
            } );

            this.element.svgContainer.mouseup( function ( event ) {
                // End arc
                console.log( "Mouseup" );

                if(event.which == 3) {
                    return;
                }

                actualEndTime = getTimeFromAbsolutePosition(event.pageX, event.pageY);
                console.log( "Setting EndTime: " + actualEndTime );
                endTimes.push(actualEndTime);

                //actualArcStartPoint = {};
                mousedown = false;
            } );

            this.element.svgContainer.mouseleave( function ( ) {
                _this.element.tooltipContainer.hide();
            } );

            this.element.svgContainer.mouseenter( function ( ) {
                _this.element.tooltipContainer.show();
            } );

            this.element.svgContainer.mousemove( function ( event ) {
                if ( event.pageX == lastX && event.pageY == lastY ) {
                    return;
                }
                lastX = event.pageX;
                lastY = event.pageY;

                var pRel = _this.getRelativePosition( lastX, lastY );
                var angle = _this.getAngleFromRelativePosition(pRel.x, pRel.y );
                var time = getTimeFromAngle( angle );

                _this.element.tooltipContainer.css( 'left', lastX + 15 ).css( 'top', lastY).text(time);

                if(mousedown) {
                    // Continue arc
                    var radius = _this.options.radius;

                    // In order to have 0 - 360 degree
                    var angleOffset = angle + 180;
                    var arcEndPoint = polarToCartesian(0, 0, radius, angleOffset);

                    //console.log('---------------------------');
                    //console.log('Start - X: ' + actualArcStartPoint.x + ' Y: ' +  + actualArcStartPoint.y);
                    //console.log('End   - X: ' + arcEndPoint.x + ' Y: ' +  + arcEndPoint.y);
                    //console.log('AngleOffset: ' + angleOffset);

                    // Draw the Arc
                    drawSvgArc(arcEndPoint, angleOffset);

                    if (angle < 0) {
                        //_this.svg.path(_this.svgClockFaceGroup, arcPath.move(0, 0).line([[0, 0], [radius, 0]])
                        //   .arc(radius, radius, 1, 0, 1, arcEndPoint.x, arcEndPoint.y)
                        //   , {stroke: 'blue', fill: 'blue'});
                    } else {

                    }
                }
            } );

            function clearSelection() {
                startTimes = [];
                endTimes = [];
                actualStartTime = null;
                arcStartPoints = [];
                actualArcStartPoint = {};

                if(interactionGroup != null) {
                    _this.svg.remove(interactionGroup);
                }
                arcPath = null;
                interactionGroup = null;
            }

            function getArcPointFromAbsolutePosition(x, y) {
                var pRel = _this.getRelativePosition( x, y );
                var angle = _this.getAngleFromRelativePosition(pRel.x, pRel.y );
                var radianAngle = -1* (angle+180) * (Math.PI/180);

                // See http://math.stackexchange.com/questions/700237/coordinates-of-sector-of-circle
                return {
                    x: _this.options.radius * Math.cos(radianAngle),
                    y: _this.options.radius * Math.sin(radianAngle) * -1,
                    angle : angle
                };
            }

            function drawSvgArc(arcEndPoint, angleoffset) {
                // See http://stackoverflow.com/questions/21205652/how-to-draw-a-circle-sector-in-css/21206274#21206274
                // See http://svg.tutorial.aptico.de/start3.php?knr=10&kname=Pfade&uknr=10.8&ukname=A%20und%20a%20-%20Bogenkurven
                if(interactionGroup != null) {
                    _this.svg.remove(interactionGroup);
                }
                interactionGroup = _this.svg.group(interactionGroupOptions);

                arcPath = _this.svg.createPath();
                if (arcEndPoint == null) {
                    // Just draw a line
                    _this.svg.path(interactionGroup, arcPath.move(0, 0).line([[0, 0], [actualArcStartPoint.x, actualArcStartPoint.y]]).close());
                } else {
                    // Draw additional arc
                    var clockwise;
                    var anglediff = actualArcStartPoint.angle  + 180 - angleoffset;

                    if(actualArcStartPoint.angle >= 90 && actualArcStartPoint.angle <= 180) {
                        // Between 12 and 3
                        if(anglediff < 0 && anglediff > -90
                            || anglediff <= 360 && anglediff > 180) {
                            clockwise = 0;
                        } else{
                            clockwise = 1;
                        }
                    } else if(actualArcStartPoint.angle <= -90 && actualArcStartPoint.angle >= -180
                        || actualArcStartPoint.angle >= -90 && actualArcStartPoint.angle <= 0) {
                        // Between 3 and 9
                        if(anglediff > -180 && anglediff < 0) {
                            clockwise = 0;
                        } else{
                            clockwise = 1;
                        }
                    } else if(actualArcStartPoint.angle >= 0 && actualArcStartPoint.angle <= 90) {
                        // Between 9 and 12
                        if(anglediff < 0 && anglediff > -180
                        || anglediff > 180) {
                            clockwise = 0;
                        } else{
                            clockwise = 1;
                        }
                    }

                    _this.svg.path(interactionGroup, arcPath.move(0, 0).line([[0, 0], [actualArcStartPoint.x, actualArcStartPoint.y]]).arc(_this.options.radius, _this.options.radius, 1, clockwise, 1, arcEndPoint.x, arcEndPoint.y).line([[arcEndPoint.x,arcEndPoint.y], [0, 0]]).close());
                }
            }

            function getTimeFromAbsolutePosition(x, y) {
                var pRel = _this.getRelativePosition( x, y );
                var angle = _this.getAngleFromRelativePosition(pRel.x, pRel.y );
                return getTimeFromAngle( angle );
            }

            /**
             *
             * @param centerX
             * @param centerY
             * @param radius
             * @param angleInDegrees
             * @returns {{x: *, y: *}}
             */
            function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
                var angleInRadians = angleInDegrees * Math.PI / 180.0;
                var x = centerX + radius * Math.cos(angleInRadians);
                var y = centerY + radius * Math.sin(angleInRadians);
                return {x:x,y:y};
            }

            /**
             *
             * @param angleDeg
             * @returns {string}
             */
            function getTimeFromAngle ( angleDeg ) {
                // Angle to time
                var angle = angleDeg + 180;
                var time = "";
                var decimalValue = 3.0 - ( 1.0 / 30.0 ) * ( angle % 360 ) * -1;
                if ( decimalValue < 0 )
                    decimalValue += 12.0;
                if ( decimalValue > 12 )
                    decimalValue -= 12.0;
                var hours = parseInt( decimalValue );
                if ( hours == 0 )
                    hours = 12;
                time += ( hours < 10 ? "0" + hours : hours ) + ":";
                var minutes = parseInt( decimalValue * 60 ) % 60;
                time += minutes < 10 ? "0" + minutes : minutes;
                return time;
            }
        },

        _createElements : function () {
            var _this = this;

            // Time tooltip
            this.element.tooltipContainer = $( '

' ).attr( 'id', 'jh-text-container' ).css( 'position', 'absolute').css('background-color', 'white').hide().appendTo( this.element );

            // AM/PM Buttons
            var buttonContainer = $( '

' ).attr( 'id', 'jh-button-container').css('text-align', 'center').appendTo( this.element );

            $('<input type="checkbox" id="jh-AM-button">AM').button().appendTo(buttonContainer);
            $('<input type="checkbox" id="jh-PM-button">PM').button().appendTo(buttonContainer);

            this.svgWidthHeight = 2 * this.options.radius + 2 * this.options.strokewidth
            this.element.svgContainer = $( '

' ).attr( 'id', 'jh-svg-container' ).width( this.svgWidthHeight ).height( this.svgWidthHeight).attr('oncontextmenu',"return false;").css('margin','auto').appendTo( this.element );
            this.element.svgContainer.svg( {
                onLoad : drawClock
            } );

            function drawClock ( svg ) {
                _this.svg = svg;
                var radius = _this.options.radius;
                var centerX = _this.options.radius + _this.options.strokewidth;
                var centerY = _this.options.radius + _this.options.strokewidth;

                svg.circle(centerX, centerY, radius, {
                    fill : '#f1f1f1',
                    stroke : _this.options.strokecolor,
                    strokeWidth : _this.options.strokewidth
                } );

                // Draw the face
                // 12 to 12
                var dist = _this.options.radius + _this.options.strokewidth;
                var svgClockFaceGroup = svg.group({stroke: 'black', transform: 'translate('+ dist + ',' + dist + ')'});

                // 12-3-6-9
                var lengthShort = _this.options.radius - 5;
                for (var i = 0; i < 360; i+=6) {
                    svg.line(svgClockFaceGroup, 0, _this.options.radius , 0 , lengthShort, {strokeWidth: 2, transform : 'rotate('+ i +')'});
                }

                // 12-3-6-9
                var lengthLong = _this.options.radius - 10;
                for (var i = 0; i < 360; i+=30) {
                    svg.line(svgClockFaceGroup, 0, _this.options.radius , 0 , lengthLong, {strokeWidth: 5, transform : 'rotate('+ i +')'});
                }

                // Later for ticks
                //svg.path(g, path.move(0,0).line([[0,0], [0,radius]]), {transform:'rotate(30)',stroke:'blue'});
            }
        },

        _destroy : function () {
        },

        _setOption : function ( key, value ) {
        }
    } );

    $( document ).ready( function () {
        $( "#time-picker" ).timeIntervalPicker();
    } );

} );
<commit_msg>modified<commit_after>/**
 * main.js  - 17.02.2016
 *
 * @author Johannes Haeussler - Johannes.3.Haeussler(at)uni-konstanz.de
 * @version 1.0
 */
$(function () {
    $.widget('jh.clockTimeIntervalPicker', {
        options: {
            width: 500,
            height: 500,
            radius: 200,
            strokeColor: 'black',
            strokeWidth: 5,
            multiSelection: true,
            showFaceCircle : true
        },

        _create: function () {
            var lastY = 0;
            var lastX = 0;
            var _this = this;

            var actualStartTime = null;
            var actualEndTime = null;
            var selectedIntervals = [];
            var intervalCount = 0;

            var mousedown = false;
            var appendMode = false;

            var actualArcStartPoint = {};
            var arcPath = null;
            var interactionGroups = [];

            var dist = _this.options.radius + _this.options.strokeWidth;
            var interactionGroupOptions = {
                transform: 'translate(' + dist + ',' + dist + ')',
                stroke: 'blue',
                strokeOpacity: 0.4,
                fill: 'blue',
                fillOpacity: "0.4"
            };

            this.svg = null;
            this.element.css('width', this.options.width);
            this.element.css('height', this.options.height);

            /**
             * Public Functions
             */
            this.getRelativePosition = function (x, y) {
                // Get absolute position of svgContainer
                var offset = _this.element.svgContainer.offset();
                return {
                    x: x - offset.left,
                    y: y - offset.top
                };
            }

            this.getAngleFromRelativePosition = function (x2, y2) {
                // Position to angle
                var x1y1 = _this.svgWidthHeight / 2;
                return Math.atan2(x1y1 - y2, x1y1 - x2) * 180 / Math.PI;
            }

            /**
             * Init elements
             */
            this._createElements();

            /**
             * Mouse Events
             */
            this.element.svgContainer.mousedown(function (event) {
                // Right Click to reset
                if (event.which == 3) {
                    clearSelection();
                    return;
                }

                _this.element.css('cursor', 'pointer');

                // Check ctrl hold for append mode
                appendMode = event.ctrlKey && _this.options.multiSelection;
                if (!appendMode) {
                    clearSelection();
                }

                // Start an arc path and draw a line
                actualArcStartPoint = getArcPointFromAbsolutePosition(event.pageX, event.pageY);
                drawSvgArc(null);

                // Set start time
                actualStartTime = getTimeFromAbsolutePosition(event.pageX, event.pageY);

                mousedown = true;

                _this.element.trigger("selectionStarted", {startTime : actualStartTime});
            });

            this.element.svgContainer.mouseup(function (event) {
                if (event.which == 3) {
                    return;
                }

                _this.element.css('cursor', 'default');

                // End arc
                actualEndTime = getTimeFromAbsolutePosition(event.pageX, event.pageY);
                intervalCount++;
                mousedown = false;

                var intervalObject = {startTime: actualStartTime, endTime: actualEndTime};
                selectedIntervals.push(intervalObject);
                _this.element.trigger("selectionEnded", intervalObject);
                _this.element.trigger("selectionChanged", {intervals : selectedIntervals});
            });

            this.element.svgContainer.mouseleave(function () {
                _this.element.tooltipContainer.hide();
            });

            this.element.svgContainer.mouseenter(function () {
                _this.element.tooltipContainer.show();
            });

            this.element.svgContainer.mousemove(function (event) {
                if (event.pageX == lastX && event.pageY == lastY) {
                    return;
                }
                lastX = event.pageX;
                lastY = event.pageY;

                var pRel = _this.getRelativePosition(lastX, lastY);
                var angle = _this.getAngleFromRelativePosition(pRel.x, pRel.y);
                var time = getTimeFromAngle(angle);

                _this.element.tooltipContainer.css('left', lastX + 15).css('top', lastY).text(time);

                if (mousedown) {
                    // Continue arc
                    var radius = _this.options.radius;

                    // In order to have 0 - 360 degree
                    var angleOffset = angle + 180;
                    var arcEndPoint = polarToCartesian(0, 0, radius, angleOffset);

                    // Draw the Arc
                    drawSvgArc(arcEndPoint, angleOffset);

                    // Clear Text Selection since sometimes it selects labels (e.g. the timelabel)
                    clearTextSelection();
                }
            });

            function clearSelection() {
                actualStartTime = null;
                actualArcStartPoint = {};
                intervalCount = 0;
                selectedIntervals = [];

                clearArcGroups();
                arcPath = null;

            }

            function clearArcGroups() {
                if (interactionGroups.length > 0) {
                    for (var i = 0; i < interactionGroups.length; i++) {
                        _this.svg.remove(interactionGroups[i]);
                    }
                }
                interactionGroups = [];
            }

            function getArcPointFromAbsolutePosition(x, y) {
                var pRel = _this.getRelativePosition(x, y);
                var angle = _this.getAngleFromRelativePosition(pRel.x, pRel.y);
                var radianAngle = -1 * (angle + 180) * (Math.PI / 180);

                // See http://math.stackexchange.com/questions/700237/coordinates-of-sector-of-circle
                return {
                    x: _this.options.radius * Math.cos(radianAngle),
                    y: _this.options.radius * Math.sin(radianAngle) * -1,
                    angle: angle
                };
            }

            function drawSvgArc(arcEndPoint, angleoffset) {
                // See http://stackoverflow.com/questions/21205652/how-to-draw-a-circle-sector-in-css/21206274#21206274
                // See http://svg.tutorial.aptico.de/start3.php?knr=10&kname=Pfade&uknr=10.8&ukname=A%20und%20a%20-%20Bogenkurven
                if (!appendMode) {
                    // Clear all arcs
                    clearArcGroups();
                } else {
                    // Clear only last arc if there is one
                    if (interactionGroups.length >= intervalCount) {
                        for (var i = intervalCount; i < interactionGroups.length; i++) {
                            _this.svg.remove(interactionGroups[i]);
                            interactionGroups.pop();
                        }
                    }
                }
                if (interactionGroups.length != intervalCount) {
                    console.error("length: " + interactionGroups.length);
                    console.error("Count:  " + intervalCount);
                }

                interactionGroups.push(_this.svg.group(interactionGroupOptions));

                arcPath = _this.svg.createPath();
                if (arcEndPoint == null) {
                    // Just draw a line
                    arcPath.move(0, 0).line([[0, 0], [actualArcStartPoint.x, actualArcStartPoint.y]]).close();
                } else {
                    // Draw additional arc
                    var clockwise;
                    var angleDiff = actualArcStartPoint.angle + 180 - angleoffset;

                    if (actualArcStartPoint.angle >= 90 && actualArcStartPoint.angle <= 180) {
                        // Between 12 and 3
                        if (angleDiff < 0 && angleDiff > -90
                            || angleDiff <= 360 && angleDiff > 180) {
                            clockwise = 0;
                        } else {
                            clockwise = 1;
                        }
                    } else if (actualArcStartPoint.angle <= -90 && actualArcStartPoint.angle >= -180
                        || actualArcStartPoint.angle >= -90 && actualArcStartPoint.angle <= 0) {
                        // Between 3 and 9
                        if (angleDiff > -180 && angleDiff < 0) {
                            clockwise = 0;
                        } else {
                            clockwise = 1;
                        }
                    } else if (actualArcStartPoint.angle >= 0 && actualArcStartPoint.angle <= 90) {
                        // Between 9 and 12
                        if (angleDiff < 0 && angleDiff > -180
                            || angleDiff > 180) {
                            clockwise = 0;
                        } else {
                            clockwise = 1;
                        }
                    }

                    arcPath.move(0, 0)
                        .line([[0, 0], [actualArcStartPoint.x, actualArcStartPoint.y]])
                        .arc(_this.options.radius, _this.options.radius, 1, clockwise, 1, arcEndPoint.x, arcEndPoint.y)
                        .line([[arcEndPoint.x, arcEndPoint.y], [0, 0]])
                        .close();
                }
                _this.svg.path(interactionGroups[intervalCount], arcPath);
            }

            function getTimeFromAbsolutePosition(x, y) {
                var pRel = _this.getRelativePosition(x, y);
                var angle = _this.getAngleFromRelativePosition(pRel.x, pRel.y);
                return getTimeFromAngle(angle);
            }

            /**
             *
             * @param centerX
             * @param centerY
             * @param radius
             * @param angleInDegrees
             * @returns {{x: *, y: *}}
             */
            function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
                var angleInRadians = angleInDegrees * Math.PI / 180.0;
                var x = centerX + radius * Math.cos(angleInRadians);
                var y = centerY + radius * Math.sin(angleInRadians);
                return {x: x, y: y};
            }

            /**
             *
             * @param angleDeg
             * @returns {string}
             */
            function getTimeFromAngle(angleDeg) {
                // Angle to time
                var angle = angleDeg + 180;
                var time = "";
                var decimalValue = 3.0 - ( 1.0 / 30.0 ) * ( angle % 360 ) * -1;
                if (decimalValue < 0)
                    decimalValue += 12.0;
                if (decimalValue > 12)
                    decimalValue -= 12.0;
                var hours = parseInt(decimalValue);
                if (hours == 0)
                    hours = 12;
                time += ( hours < 10 ? "0" + hours : hours ) + ":";
                var minutes = parseInt(decimalValue * 60) % 60;
                time += minutes < 10 ? "0" + minutes : minutes;
                return time;
            }

            function clearTextSelection() {
                if (window.getSelection) {
                    if (window.getSelection().empty) {  // Chrome
                        window.getSelection().empty();
                    } else if (window.getSelection().removeAllRanges) {  // Firefox
                        window.getSelection().removeAllRanges();
                    }
                } else if (document.selection) {  // IE?
                    document.selection.empty();
                }
            }
        },

        _createElements: function () {
            var _this = this;

            // Time tooltip
            this.element.tooltipContainer = $('

').attr('id', 'jh-text-container').css('position', 'absolute').css('background-color', 'white').hide().appendTo(this.element);

            // AM/PM Buttons
            var buttonContainer = $('

').attr('id', 'jh-button-container').css('text-align', 'center').css('margin', 5).appendTo(this.element);

            $(' <input type="checkbox" id="jh-am-button">AM').appendTo(buttonContainer);
            $(' <input type="checkbox" id="jh-pm-button">PM').appendTo(buttonContainer);
            buttonContainer.buttonset();

            this.svgWidthHeight = 2 * this.options.radius + 2 * this.options.strokeWidth;
            this.element.svgContainer = $('

').attr('id', 'jh-svg-container').width(this.svgWidthHeight).height(this.svgWidthHeight).attr('oncontextmenu', "return false;").css('margin', 'auto').appendTo(this.element);
            this.element.svgContainer.svg({
                onLoad: drawClock
            });

            function drawClock(svg) {
                _this.svg = svg;
                var radius = _this.options.radius;
                var centerX = _this.options.radius + _this.options.strokeWidth;
                var centerY = _this.options.radius + _this.options.strokeWidth;

                if(_this.options.showFaceCircle) {
                    svg.circle(centerX, centerY, radius, {
                        fill: '#f1f1f1',
                        fillOpacity : '1.0',
                        stroke: _this.options.strokeColor,
                        strokeWidth: _this.options.strokeWidth
                    });
                }

                // Draw the face
                // 12 to 12
                var dist = _this.options.radius + _this.options.strokeWidth;
                var svgClockFaceGroup = svg.group({stroke: 'black', transform: 'translate(' + dist + ',' + dist + ')'});

                // 12-3-6-9
                var lengthShort = _this.options.radius - 5;
                for (var i = 0; i < 360; i += 6) {
                    svg.line(svgClockFaceGroup, 0, _this.options.radius, 0, lengthShort, {
                        strokeWidth: 2,
                        transform: 'rotate(' + i + ')'
                    });
                }

                // 12-3-6-9
                var lengthLong = _this.options.radius - 10;
                for (var i = 0; i < 360; i += 30) {
                    svg.line(svgClockFaceGroup, 0, _this.options.radius, 0, lengthLong, {
                        strokeWidth: 5,
                        transform: 'rotate(' + i + ')'
                    });
                }

                // Later for ticks
                //svg.path(g, path.move(0,0).line([[0,0], [0,radius]]), {transform:'rotate(30)',stroke:'blue'});
            }
        },

        _destroy: function () {
        },

        _setOption: function (key, value) {
        }
    });
});
<|endoftext|>


<commit_before>"use strict";

const gulp = require("gulp");
const config = require("../config").config;
const browserSync = require("browser-sync").create();

// グローバルにインスタンスを保存
gulp.browserSync = browserSync;

// ファイル変更を監視します。
gulp.task("watch", ["server"], (done) => {
	// Assemble
	gulp.watch(`${config.handlebarsDir}*.hbs`, ["assemble"]);
	gulp.watch(`${config.handlebarsPartialsDir}*.hbs`, ["assemble"]);
	gulp.watch(`${config.distDir}*.html`).on("change", browserSync.reload);

	// Sass
	gulp.watch(`${config.sassDir}**`, ["sassLint", "sass"]);

	// JavaScript
	// by webpack
	gulp.watch(`${config.jsSourceDir}**`, ["webpack"]);
	// by Browserify
	// gulp.watch(`${config.jsSourceDir}**`, ["watchify"]);
	gulp.watch(`${config.distDir}js/*.js`).on("change", browserSync.reload);

	done();
});

// ローカルサーバーを起動します。
gulp.task("server", ["default"], (done) => {
	browserSync.init({
		server: {
			baseDir: config.distDir
		},
		// 自動的に開く: true / local / external / ui / ui-external / tunnel / false
		open: false,
		// 通知ポップアップ: true / false (default: false)
		notify: false,
		rewriteRules: [
			/*
			SSI対応
			{
				match: /<!--#include virtual="(.+?)" -->/g,
				fn(req, res, match, filename) {
					const includeFilePath = path.join(`${__dirname}/../../dist/`, filename);
					if (fs.existsSync(includeFilePath)) {
						return fs.readFileSync(includeFilePath);
					} else {
						return `<span style="color: red">\`${includeFilePath}\` could not be found</span>`;
					}
				}
			}
			*/
		]
	});
	done();
});
<commit_msg>Change open url<commit_after>"use strict";

const gulp = require("gulp");
const config = require("../config").config;
const browserSync = require("browser-sync").create();

// グローバルにインスタンスを保存
gulp.browserSync = browserSync;

// ファイル変更を監視します。
gulp.task("watch", ["server"], done => {
	// Assemble
	gulp.watch(`${config.handlebarsDir}*.hbs`, ["assemble"]);
	gulp.watch(`${config.handlebarsPartialsDir}*.hbs`, ["assemble"]);
	gulp.watch(`${config.distDir}*.html`).on("change", browserSync.reload);

	// Sass
	gulp.watch(`${config.sassDir}**`, ["sassLint", "sass"]);

	// JavaScript
	// by webpack
	gulp.watch(`${config.jsSourceDir}**`, ["webpack"]);
	// by Browserify
	// gulp.watch(`${config.jsSourceDir}**`, ["watchify"]);
	gulp.watch(`${config.distDir}js/*.js`).on("change", browserSync.reload);

	done();
});

// ローカルサーバーを起動します。
gulp.task("server", ["default"], done => {
	browserSync.init({
		server: {
			baseDir: config.distDir
		},
		// 自動的に開く: true / local / external / ui / ui-external / tunnel / false
		open: "external",
		// 通知ポップアップ: true / false (default: false)
		notify: false,
		rewriteRules: [
			/*
			SSI対応
			{
				match: /<!--#include virtual="(.+?)" -->/g,
				fn(req, res, match, filename) {
					const includeFilePath = path.join(`${__dirname}/../../dist/`, filename);
					if (fs.existsSync(includeFilePath)) {
						return fs.readFileSync(includeFilePath);
					} else {
						return `<span style="color: red">\`${includeFilePath}\` could not be found</span>`;
					}
				}
			}
			*/
		]
	});
	done();
});
<|endoftext|>


<commit_before>function pad(width, n) {
    n = n + '';
    return n.length >= width ? n : new Array(width - n.length + 1).join("0") + n;
}

function format_time(milliseconds) {
    total_seconds = Math.round(milliseconds/1000);
    var hours = Math.floor(total_seconds/3600);
    var minutes = Math.floor((total_seconds%3600)/60);
    var seconds = total_seconds%60;
    if (hours) {
        return hours + ":" + pad(2, minutes) + ":" + pad(2, seconds);
    } else {
        return minutes + ":" + pad(2, seconds);
    }
}

function search_term_for_track(track) {
    var artist_term = track.a.n.split(" ").filter(function(s) {
        return (s !== "&");
    }).join(" ");
    var track_term = "";
    var tokens = track.t.split(" ");
    for (i=0; i < tokens.length; i++) {
        var token = tokens[i];
        if (token.startsWith("-")) { break; }
        if (token.startsWith("(") && i > 0) { break; }
        track_term += " " + token;
    }
    return artist_term + track_term;
}

var TrackTable = function(div) {
    this.table_type = "TrackTable";
    this.div = div;
    this.tracks = [];
    this.track_data = {};
    this.insert_html();
    this.attach_header_listeners();
};

TrackTable.prototype = {

    "row_template": _.template(`
        <tr class="trackrow" uid="<%= uid %>" style="display:<%= visibility %>;">
          <%= track.u %>
          <%= track.t %>
          <%= track.a.n %>
          <%= track.c.t %>
          <%= duration %>
          <%= track.c.r %>

            <button class="remove">remove</button>
            <button class="search">search</button>

    `),

    "insert_html": function() {
        this.div.html(`

                  uid
                  <button class="sort_button">title</button>
                  <button class="sort_button">artist</button>
                  <button class="sort_button">album</button>
                  <button class="sort_button">duration</button>

                    <button class="sort_button">release date</button>

                    Max:<input type="text" id="max_date"></input>

                    Min:<input type="text" id="min_date"></input>

                  action

        `);
    },

    "attach_header_listeners": function() {
        var table = this;
        table.make_sortable(".column_track", function(t) { return t.t; });
        table.make_sortable(".column_artist", function(t) { return t.a.n; });
        table.make_sortable(".column_album", function(t) { return t.c.t; });
        table.make_sortable(".column_duration", function(t) { return t.d; });
        table.make_sortable(".column_release", function(t) { return t.c.r; });
        table.div.find("th input").keypress(function(e) {
            if (e.which == 13) {
                table.filter_display();
            }
        });
    },

    "filter_display": function() {
        var table = this;
        var max_date = table.div.find("#max_date").val();
        var min_date = table.div.find("#min_date").val();
        _(table.tracks).each(function(uid) {
            var track = table.track_data[uid];
            var row = table.div.find("tr[uid='" + uid + "']");
            if (max_date && track.c.r > max_date) {
                track.v = false;
                row.hide();
            } else if (min_date && track.c.r < min_date) {
                track.v = false;
                row.hide();
            } else {
                track.v = true;
                row.show();
            }
        });
    },

    "make_sortable": function(column, sort_fn) {
        var table = this;
        table.div.find(".header " + column + " .sort_button").click(function() {
            var sorting = $(this).attr("sorting");
            if (!sorting) {
                $(this).attr("sorting", "asc");
                table.tracks = _.sortBy(table.tracks, function(uri) {
                    return sort_fn(table.track_data[uri]); });
            } else if (sorting === "asc") {
                $(this).attr("sorting", "desc");
                table.tracks = _.sortBy(table.tracks, function(uri) {
                    return sort_fn(table.track_data[uri]); });
                table.tracks.reverse();
            } else {
                $(this).removeAttr("sorting");
                table.tracks = _.sortBy(table.tracks, function(uri) {
                    return table.track_data[uri].idx; });
            }
            table.reset_display();
            table.reset_row_listeners();
        });
    },

    "load_tracks_from_url": function(url) {
        var table = this;
        $.get(url, function(data) {
            var idx = table.next_idx();
            _(data.tracks).each(function(t) {
                t.idx = idx++;
                t.v = true;
                t.d = parseInt(t.d);
                var uid = t.u.split(":").pop();
                table.tracks.push(uid);
                table.track_data[uid] = t;
            });
            table.reset_display();
            table.reset_row_listeners();
        });
    },

    "reset_display": function() {
        var table = this;
        track_html = "";
        _(table.tracks).each(function(uid) {
            var track = table.track_data[uid];
            track_html += table.row_template({
                "track": track,
                "uid": uid,
                "visibility": track.v ? "table-row" : "none",
                "duration": format_time(track.d)
            });
        });
        table.div.find(".data").html(track_html);
    },

    "reset_row_listeners": function() {
        var table = this;
        table.div.find("button.remove").unbind("click");
        table.div.find("button.search").unbind("click");
        table.div.find("button.remove").click(function() {
            table.remove_track($(this).parent().parent().attr("uid"));
        });
        table.div.find("button.search").click(function() {
            console.log("search");
        });
    },

    "add_track": function(track) {
        var idx = this.next_idx();
        track.idx = idx;
        track.v = true;
        var uid = track.u.split(":").pop();
        this.tracks.push(uid);
        this.track_data[uid] = track;
        this.reset_display();
        this.reset_row_listeners();
        this.filter_display();
    },

    "remove_track": function(uid) {
        delete(this.track_data[uid]);
        var idx = this.tracks.indexOf(uid);
        if (idx > -1) {
            this.tracks.splice(idx, 1);
        }
        this.div.find("tr[uid='" + uid + "']").remove();
    },

    "get_tracks": function(visible) {
        var returned_tracks = [];
        for (i=0; i<this.tracks.length; i++) {
           var t = this.track_data[this.tracks[i]]
           if (!visible || t.v) { returned_tracks.push(t); }
        }
        return returned_tracks;
    },

    "next_idx": function() {
        var table = this;
        var idx = 0;
        _(this.tracks).each(function(uri) {
            if (table.track_data[uri].idx > idx) {
                idx = table.track_data[uri].idx;
            }
        });
        return idx + 1;
    },

    "log_me": function() {
        console.log(this.table_type + " " + this.div.selector);
    }

};

var SearchTable = function(div, manager) {
    this.table_type = "SearchTable";
    this.div = div;
    this.manager = manager;
    this.manager.tables.push(this);
    this.tracks = [];
    this.track_data = {};
    this.insert_html();
    //this.attach_header_listeners();
};

SearchTable.prototype = {

    "row_template": _.template(`
        <tr class="searchrow" uid="<%= uid %>">
          <%= track.u %>
          <%= track.t %>
          <%= track.a.n %>
          <%= track.c.t %>
          <%= duration %>
          <%= track.c.r %>
          <button class="<%= action %>"><%= action %></button>

    `),

    "insert_html": function() {
        this.div.html(`

                  uid
                  <button class="sort_button">title</button>
                  <button class="sort_button">artist</button>
                  <button class="sort_button">album</button>
                  <button class="sort_button">duration</button>
                  <button class="sort_button">release date</button>

                  action

        `);
    },

    "reset_display": function() {
        var table = this;
        track_html = "";
        _(table.tracks).each(function(uid) {
            var track = table.track_data[uid];
            track_html += table.row_template({
                "track": track,
                "uid": uid,
                "duration": format_time(track.d)
            });
        });
        table.div.find(".data").html(track_html);
    },

    "inject_search_results": function(tracks) {
        var table = this;
        var results_html = `<button class="hide_results" style="float: right;">hide</button>

`
        _(tracks).each(function(t) {
            var uid = t.u.split(":").pop();
            table.tracks.push(uid);
            table.track_data[uid] = t;
            results_html += table.row_template({
                "track": t,
                "action": table.manager.target_table.track_data[uid] ? "remove" : "add",
                "duration": format_time(t.d),
                "uid": uid
            });
        });
        results_html += "

";
        table.div.html(results_html);
        table.div.find(".add").click(function() {
            table.add_track($(this).parent().parent().attr("uid"));
        });
        table.div.find(".remove").click(function() {
            table.remove_track($(this).parent().parent().attr("uid"));
        });
        table.div.find(".hide_results").click(function() {
            table.div.html("");
        });
    },

    "add_track": function(uid) {
        this.manager.target_table.add_track(this.track_data[uid]);
    },

    "remove_track": function(uid) {
        this.manager.target_table.remove_track(uid);
    },

    "search_from_url": function(url, form) {
        var table = this;
        $.get(url, form, function(data) {
            table.inject_search_results(data.tracks);
        });
    },

    "log_me": TrackTable.prototype.log_me

};

var SearchTableManager = function(target_table) {
    this.tables = [];
    this.target_table = target_table;
};
<commit_msg>move the track filter into its own function<commit_after>function pad(width, n) {
    n = n + '';
    return n.length >= width ? n : new Array(width - n.length + 1).join("0") + n;
}

function format_time(milliseconds) {
    total_seconds = Math.round(milliseconds/1000);
    var hours = Math.floor(total_seconds/3600);
    var minutes = Math.floor((total_seconds%3600)/60);
    var seconds = total_seconds%60;
    if (hours) {
        return hours + ":" + pad(2, minutes) + ":" + pad(2, seconds);
    } else {
        return minutes + ":" + pad(2, seconds);
    }
}

function search_term_for_track(track) {
    var artist_term = track.a.n.split(" ").filter(function(s) {
        return (s !== "&");
    }).join(" ");
    var track_term = "";
    var tokens = track.t.split(" ");
    for (i=0; i < tokens.length; i++) {
        var token = tokens[i];
        if (token.startsWith("-")) { break; }
        if (token.startsWith("(") && i > 0) { break; }
        track_term += " " + token;
    }
    return artist_term + track_term;
}

var TrackTable = function(div) {
    this.table_type = "TrackTable";
    this.div = div;
    this.tracks = [];
    this.track_data = {};
    this.insert_html();
    this.attach_header_listeners();
};

TrackTable.prototype = {

    "row_template": _.template(`
        <tr class="trackrow" uid="<%= uid %>" style="display:<%= visibility %>;">
          <%= track.u %>
          <%= track.t %>
          <%= track.a.n %>
          <%= track.c.t %>
          <%= duration %>
          <%= track.c.r %>

            <button class="remove">remove</button>
            <button class="search">search</button>

    `),

    "insert_html": function() {
        this.div.html(`

                  uid
                  <button class="sort_button">title</button>
                  <button class="sort_button">artist</button>
                  <button class="sort_button">album</button>
                  <button class="sort_button">duration</button>

                    <button class="sort_button">release date</button>

                    Max:<input type="text" id="max_date"></input>

                    Min:<input type="text" id="min_date"></input>

                  action

        `);
    },

    "attach_header_listeners": function() {
        var table = this;
        table.make_sortable(".column_track", function(t) { return t.t; });
        table.make_sortable(".column_artist", function(t) { return t.a.n; });
        table.make_sortable(".column_album", function(t) { return t.c.t; });
        table.make_sortable(".column_duration", function(t) { return t.d; });
        table.make_sortable(".column_release", function(t) { return t.c.r; });
        table.div.find("th input").keypress(function(e) {
            if (e.which == 13) {
                table.filter_display();
            }
        });
    },

    "compile_filter": function() {
        return {
            "max_date": this.div.find("#max_date").val(),
            "min_date": this.div.find("#min_date").val()
        }
    },

    "show_track": function(uid, params) {
        var track = this.track_data[uid];
        if (params.min_date && track.c.r < params.min_date) {
            return false;
        }
        if (params.max_date && track.c.r > params.max_date) {
            return false;
        }
        return true;
    },

    "filter_display": function() {
        var table = this;
        var params = table.compile_filter();
        _(table.tracks).each(function(uid) {
            var track = table.track_data[uid];
            var row = table.div.find("tr[uid='" + uid + "']");
            track.v = table.show_track(uid, params);
            if (track.v) {
                row.show();
            } else {
                row.hide();
            }
        });
    },

    "make_sortable": function(column, sort_fn) {
        var table = this;
        table.div.find(".header " + column + " .sort_button").click(function() {
            var sorting = $(this).attr("sorting");
            if (!sorting) {
                $(this).attr("sorting", "asc");
                table.tracks = _.sortBy(table.tracks, function(uri) {
                    return sort_fn(table.track_data[uri]); });
            } else if (sorting === "asc") {
                $(this).attr("sorting", "desc");
                table.tracks = _.sortBy(table.tracks, function(uri) {
                    return sort_fn(table.track_data[uri]); });
                table.tracks.reverse();
            } else {
                $(this).removeAttr("sorting");
                table.tracks = _.sortBy(table.tracks, function(uri) {
                    return table.track_data[uri].idx; });
            }
            table.reset_display();
            table.reset_row_listeners();
        });
    },

    "load_tracks_from_url": function(url) {
        var table = this;
        $.get(url, function(data) {
            var idx = table.next_idx();
            _(data.tracks).each(function(t) {
                t.idx = idx++;
                t.v = true;
                t.d = parseInt(t.d);
                var uid = t.u.split(":").pop();
                table.tracks.push(uid);
                table.track_data[uid] = t;
            });
            table.reset_display();
            table.reset_row_listeners();
        });
    },

    "reset_display": function() {
        var table = this;
        track_html = "";
        _(table.tracks).each(function(uid) {
            var track = table.track_data[uid];
            track_html += table.row_template({
                "track": track,
                "uid": uid,
                "visibility": track.v ? "table-row" : "none",
                "duration": format_time(track.d)
            });
        });
        table.div.find(".data").html(track_html);
    },

    "reset_row_listeners": function() {
        var table = this;
        table.div.find("button.remove").unbind("click");
        table.div.find("button.search").unbind("click");
        table.div.find("button.remove").click(function() {
            table.remove_track($(this).parent().parent().attr("uid"));
        });
        table.div.find("button.search").click(function() {
            console.log("search");
        });
    },

    "add_track": function(track) {
        var idx = this.next_idx();
        track.idx = idx;
        track.v = true;
        var uid = track.u.split(":").pop();
        this.tracks.push(uid);
        this.track_data[uid] = track;
        this.reset_display();
        this.reset_row_listeners();
        this.filter_display();
    },

    "remove_track": function(uid) {
        delete(this.track_data[uid]);
        var idx = this.tracks.indexOf(uid);
        if (idx > -1) {
            this.tracks.splice(idx, 1);
        }
        this.div.find("tr[uid='" + uid + "']").remove();
    },

    "get_tracks": function(visible) {
        var returned_tracks = [];
        for (i=0; i<this.tracks.length; i++) {
           var t = this.track_data[this.tracks[i]]
           if (!visible || t.v) { returned_tracks.push(t); }
        }
        return returned_tracks;
    },

    "next_idx": function() {
        var table = this;
        var idx = 0;
        _(this.tracks).each(function(uri) {
            if (table.track_data[uri].idx > idx) {
                idx = table.track_data[uri].idx;
            }
        });
        return idx + 1;
    },

    "log_me": function() {
        console.log(this.table_type + " " + this.div.selector);
    }

};

var SearchTable = function(div, manager) {
    this.table_type = "SearchTable";
    this.div = div;
    this.manager = manager;
    this.manager.tables.push(this);
    this.tracks = [];
    this.track_data = {};
    this.insert_html();
    //this.attach_header_listeners();
};

SearchTable.prototype = {

    "row_template": _.template(`
        <tr class="searchrow" uid="<%= uid %>">
          <%= track.u %>
          <%= track.t %>
          <%= track.a.n %>
          <%= track.c.t %>
          <%= duration %>
          <%= track.c.r %>
          <button class="<%= action %>"><%= action %></button>

    `),

    "insert_html": function() {
        this.div.html(`

                  uid
                  <button class="sort_button">title</button>
                  <button class="sort_button">artist</button>
                  <button class="sort_button">album</button>
                  <button class="sort_button">duration</button>
                  <button class="sort_button">release date</button>

                  action

        `);
    },

    "reset_display": function() {
        var table = this;
        track_html = "";
        _(table.tracks).each(function(uid) {
            var track = table.track_data[uid];
            track_html += table.row_template({
                "track": track,
                "uid": uid,
                "duration": format_time(track.d)
            });
        });
        table.div.find(".data").html(track_html);
    },

    "inject_search_results": function(tracks) {
        var table = this;
        var results_html = `<button class="hide_results" style="float: right;">hide</button>

`
        _(tracks).each(function(t) {
            var uid = t.u.split(":").pop();
            table.tracks.push(uid);
            table.track_data[uid] = t;
            results_html += table.row_template({
                "track": t,
                "action": table.manager.target_table.track_data[uid] ? "remove" : "add",
                "duration": format_time(t.d),
                "uid": uid
            });
        });
        results_html += "

";
        table.div.html(results_html);
        table.div.find(".add").click(function() {
            table.add_track($(this).parent().parent().attr("uid"));
        });
        table.div.find(".remove").click(function() {
            table.remove_track($(this).parent().parent().attr("uid"));
        });
        table.div.find(".hide_results").click(function() {
            table.div.html("");
        });
    },

    "add_track": function(uid) {
        this.manager.target_table.add_track(this.track_data[uid]);
    },

    "remove_track": function(uid) {
        this.manager.target_table.remove_track(uid);
    },

    "search_from_url": function(url, form) {
        var table = this;
        $.get(url, form, function(data) {
            table.inject_search_results(data.tracks);
        });
    },

    "log_me": TrackTable.prototype.log_me

};

var SearchTableManager = function(target_table) {
    this.tables = [];
    this.target_table = target_table;
};
<|endoftext|>


<commit_before>/*global m */

// Select ======================================================================
var mc = mc || {};

mc.Tabs = {
  controller: function (tabs, viewsCtrl, activeTab) {
    this.tabs = tabs || [];
    this.viewsCtrl = viewsCtrl || {};
    this.activeTab = activeTab || '';
  },

  view: function (ctrl, opts) {
    opts = opts || {};
    var tabs = normalizeTabs(opts.tabs || ctrl.tabs),
    // default to tab param on URL, then to first tab
      activeTab = opts.activeTab || ctrl.activeTab || m.route.param('tab') || Object.keys(tabs)[0];

    return [
      m('.tabs', [
        m('ul', Object.keys(tabs).map(tab))
      ]),
      tabs[activeTab].view(tabs[activeTab].ctrl)
    ];

    function tab (name) {
      return m('li', [
        m('a',
          { class: activeTab === name ? 'selected' : '', href: '/' + name, config: m.route },
          tabs[name].label
        )
      ]);
    }

    function normalizeTabs (tabs) {
      var norm = {},
        lastCtrl = {};

      Object.keys(tabs).forEach(function (key) { // depends on .keys() returning keys in stored order
        var tab = tabs[key];
        if (typeof tab === 'function') {
          norm[key] = { view: tab, ctrl: lastCtrl, label: key };
        } else {
          if (tab.ctrl) { lastCtrl = tab.ctrl; }
          norm[key] = { view: tab.view, ctrl: lastCtrl, label: tab.label || key};
        }
      });

      return norm;
    }
  }
};<commit_msg>Fixing bug in tabs.js<commit_after>/*global m */

// Select ======================================================================
var mc = mc || {};

mc.Tabs = {
  controller: function (tabs, activeTab) {
    this.tabs = tabs || [];
    this.activeTab = activeTab || '';
  },

  view: function (ctrl, opts) {
    opts = opts || {};
    var tabs = normalizeTabs(opts.tabs || ctrl.tabs),
    // default to tab param on URL, then to first tab
      activeTab = opts.activeTab || ctrl.activeTab || m.route.param('tab') || Object.keys(tabs)[0];

    return [
      m('.tabs', m('ul', Object.keys(tabs).map(tab))),
      tabs[activeTab].view(tabs[activeTab].ctrl)
    ];

    function tab (name) {
      return m('li', [
        m('a',
          { class: activeTab === name ? 'selected' : '', href: '/' + name, config: m.route },
          tabs[name].label
        )
      ]);
    }

    function normalizeTabs (tabs) {
      var norm = {},
        lastCtrl = {};

      Object.keys(tabs).forEach(function (key) { // depends on .keys() returning keys in stored order
        var tab = tabs[key];
        if (typeof tab === 'function') {
          norm[key] = { view: tab, ctrl: lastCtrl, label: key };
        } else {
          if (tab.ctrl) { lastCtrl = tab.ctrl; }
          norm[key] = { view: tab.view, ctrl: lastCtrl, label: tab.label || key};
        }
      });

      return norm;
    }
  }
};<|endoftext|>


<commit_before>var gulp = require('gulp');
var istanbul = require('gulp-istanbul');
var mocha = require('gulp-mocha');

gulp.task("test", function (done) {
  return gulp.src([ './src.js' ])
  .pipe(istanbul())
  .pipe(istanbul.hookRequire())
  .on('finish', function () {
    gulp.src([ './test.js' ], { read: false })
    .pipe(mocha({
      reporter: 'spec',
      timeout: 10000
    }))
    .pipe(istanbul.writeReports())
    .pipe(istanbul.enforceThresholds({ thresholds: { global: 90 } }))
    .on('end', done);
  });
});

<commit_msg>[JavaScript] Fix the bug on gulpfile.<commit_after>var gulp = require('gulp');
var istanbul = require('gulp-istanbul');
var mocha = require('gulp-mocha');

gulp.task("test", function (done) {
  gulp.src([ './src.js' ])
  .pipe(istanbul())
  .pipe(istanbul.hookRequire())
  .on('finish', function () {
    gulp.src([ './test.js' ], { read: false })
    .pipe(mocha({
      reporter: 'spec',
      timeout: 10000
    }))
    .pipe(istanbul.writeReports())
    .pipe(istanbul.enforceThresholds({ thresholds: { global: 90 } }))
    .on('end', done);
  });
});

<|endoftext|>