<commit_before>define(['../durandal/system', '../durandal/app', '../durandal/viewModel', './history'],
function (system, app, viewModel, history) {

    var optionalParam = /\((.*?)\)/g;
    var namedParam = /(\(\?)?:\w+/g;
    var splatParam = /\*\w+/g;
    var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;

    var queue = [],
        isNavigating = ko.observable(false),
        currentActivation,
        currentInstruction,
        activeItem = viewModel.activator(),
        isConfigured = false;

    var router = {
        routes:[],
        activeItem: activeItem,
        isNavigating: isNavigating,
        events: app
    };

    function routeStringToRegExp(routeString) {
        routeString = routeString.replace(escapeRegExp, '\\$&')
            .replace(optionalParam, '(?:$1)?')
            .replace(namedParam, function(match, optional) {
                return optional ? match : '([^\/]+)';
            })
            .replace(splatParam, '(.*?)');

        return new RegExp('^' + routeString + '$');
    }

    // Given a route, and a URL fragment that it matches, return the array of
    // extracted decoded parameters. Empty or unmatched parameters will be
    // treated as `null` to normalize cross-browser behavior.
    function extractParameters(route, fragment) {
        var params = route.exec(fragment).slice(1);

        for (var i = 0; i < params.length; i++) {
            var current = params[i];
            params[i] = current ? decodeURIComponent(current) : null;
        }

        return params;
    }

    function completeNavigation(instance, instruction) {
        system.log('Navigation Complete', instance, instruction);

        currentActivation = instance;
        currentInstruction = instruction;

        router.updateDocumentTitle(instance, instruction);

        router.events.trigger('router:navigation-complete', instance, instruction);
    }

    function cancelNavigation(instance, instruction) {
        system.log('Navigation Cancelled');

        if (currentInstruction) {
            router.navigate(currentInstruction.fragment, { replace: true });
        }

        isNavigating(false);

        router.events.trigger('router:navigation-cancelled', instance, instruction);
    }

    function redirect(url) {
        system.log('Navigation Redirecting');

        isNavigating(false);
        router.navigate(url, { trigger: true, replace: true });
    }

    function activateRoute(instance, instruction) {
        activeItem.activateItem(instance, instruction.params).then(function (succeeded) {
            if (succeeded) {
                completeNavigation(instance, instruction);
            } else {
                cancelNavigation(instance, instruction);
            }
        });
    }

    function handleGuardedRoute(instance, instruction) {
        var resultOrPromise = router.guardRoute(instance, instruction);
        if (resultOrPromise) {
            if (resultOrPromise.then) {
                resultOrPromise.then(function (result) {
                    if (result) {
                        if (system.isString(result)) {
                            redirect(result);
                        } else {
                            activateRoute(instance, instruction);
                        }
                    } else {
                        cancelNavigation(instance, instruction);
                    }
                });
            } else {
                if (system.isString(resultOrPromise)) {
                    redirect(resultOrPromise);
                } else {
                    activateRoute(instance, instruction);
                }
            }
        } else {
            cancelNavigation(instance, instruction);
        }
    }

    function dequeueRoute() {
        if (isNavigating()) {
            return;
        }

        var instruction = queue.shift();
        queue = [];

        if (!instruction) {
            return;
        }

        isNavigating(true);

        system.acquire(instruction.config.moduleId).then(function (module) {
            var instance = router.getActivatableInstance(module, instruction);

            if (router.guardRoute) {
                handleGuardedRoute(instance, instruction);
            } else {
                activateRoute(instance, instruction);
            }
        });
    }

    function queueRoute(instruction) {
        queue.unshift(instruction);
        dequeueRoute();
    }

    function mapRoute(config) {
        if (!system.isRegExp(config.route)) {
            config.title = info.title || router.convertRouteToTitle(config.route);
            config.moduleId = config.moduleId || router.convertRouteToModuleId(config.url);
            config.hash = config.hash || '#/' + config.route;
            config.route = routeStringToRegExp(config.route);
        }

        config.caption = config.caption || config.title;
        config.settings = config.settings || {};

        router.routes.push(config);

        history.route(config.route, function (fragment) {
            queueRoute({
                fragment: fragment,
                config: config,
                params: extractParameters(config.route, fragment)
            });
        });

        return router;
    }

    router.updateDocumentTitle = function (instance, instruction) {
        if (instruction.config.title) {
            if (app.title) {
                document.title = instruction.config.title + " | " + app.title;
            } else {
                document.title = instruction.config.title;
            }
        } else if (app.title) {
            document.title = app.title;
        }
    };

    router.getActivatableInstance = function (module, instruction) {
        if (system.isFunction(module)) {
            return new module();
        } else {
            return module;
        }
    };

    router.stripParametersFromRoute = function(route) {
        var colonIndex = route.indexOf(':');
        var length = colonIndex > 0 ? colonIndex - 1 : route.length;
        return route.substring(0, length);
    };

    router.convertRouteToModuleId = function (route) {
        return router.stripParametersFromRoute(route);
    };

    router.convertRouteToTitle = function (route) {
        var value = router.stripParametersFromRoute(route);
        return value.substring(0, 1).toUpperCase() + value.substring(1);
    };

    // Manually bind a single named route to a module. For example:
    //
    //     router.map('search/:query/p:num', 'viewmodels/search');
    //
    router.map = function (route, config) {
        if (system.isArray(route)) {
            // We have to reverse the
            // order of the routes here to support behavior where the most general
            // routes can be defined at the bottom of the route array.
            var current;

            while ((current = route.pop()) != null) {
                router.map(current);
            }

            return router;
        }

        if (system.isString(route) || system.isRegExp(route)) {
            if (!config) {
                config = {};
            } else if (system.isString(config)) {
                config = {
                    moduleId: config
                };
            }

            config.route = route;
        } else {
            config = route;
        }

        return mapRoute(config);
    };

    // Simple proxy to `history` to save a fragment into the history.
    router.navigate = function (fragment, options) {
        history.navigate(fragment, options);
        return router;
    };

    router.afterCompose = function() {
        setTimeout(function() {
            isNavigating(false);
            router.events.trigger('router:navigation-composed', currentActivation, currentInstruction);
            dequeueRoute();
        }, 10);
    };

    router.buildNavigation = function () {
        //if (config.nav) {
        //    config.isActive = ko.computed(function () {
        //        return activeItem() && activeItem().__moduleId__ == config.moduleId;
        //    });
        //}
    };

    router.configure = function(options) {
        if (isConfigured) {
            return router;
        }

        router.options = options || {};
        router.buildNavigation();
        isConfigured = true;

        return router;
    };

    router.start = function () {
        if (!isConfigured) {
            throw new Error('You must call "configure" before you can start the router.');
        }

        history.start(router.options);

        return router;
    };

    router.stop = function () {
        history.stop();
        return router;
    };

    router.reset = function () {
        isConfigured = false;

        history.handlers = [];
        router.routes = [];

        return router;
    };

    return router;
});<commit_msg>Adding  nav model capability and cleaning up the API of the router.<commit_after>define(['../durandal/system', '../durandal/app', '../durandal/viewModel', './history'],
function (system, app, viewModel, history) {

    var optionalParam = /\((.*?)\)/g;
    var namedParam = /(\(\?)?:\w+/g;
    var splatParam = /\*\w+/g;
    var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;

    var queue = [],
        isNavigating = ko.observable(false),
        currentActivation,
        currentInstruction,
        activeItem = viewModel.activator();

    var router = {
        routes: [],
        navigationModel: ko.observableArray([]),
        activeItem: activeItem,
        isNavigating: isNavigating,
        events: app
    };

    function routeStringToRegExp(routeString) {
        routeString = routeString.replace(escapeRegExp, '\\$&')
            .replace(optionalParam, '(?:$1)?')
            .replace(namedParam, function(match, optional) {
                return optional ? match : '([^\/]+)';
            })
            .replace(splatParam, '(.*?)');

        return new RegExp('^' + routeString + '$');
    }

    // Given a route, and a URL fragment that it matches, return the array of
    // extracted decoded parameters. Empty or unmatched parameters will be
    // treated as `null` to normalize cross-browser behavior.
    function extractParameters(route, fragment) {
        var params = route.exec(fragment).slice(1);

        for (var i = 0; i < params.length; i++) {
            var current = params[i];
            params[i] = current ? decodeURIComponent(current) : null;
        }

        return params;
    }

    function completeNavigation(instance, instruction) {
        system.log('Navigation Complete', instance, instruction);

        currentActivation = instance;
        currentInstruction = instruction;

        router.updateDocumentTitle(instance, instruction);

        router.events.trigger('router:navigation-complete', instance, instruction);
    }

    function cancelNavigation(instance, instruction) {
        system.log('Navigation Cancelled');

        if (currentInstruction) {
            router.navigate(currentInstruction.fragment, { replace: true });
        }

        isNavigating(false);

        router.events.trigger('router:navigation-cancelled', instance, instruction);
    }

    function redirect(url) {
        system.log('Navigation Redirecting');

        isNavigating(false);
        router.navigate(url, { trigger: true, replace: true });
    }

    function activateRoute(instance, instruction) {
        activeItem.activateItem(instance, instruction.params).then(function (succeeded) {
            if (succeeded) {
                completeNavigation(instance, instruction);
            } else {
                cancelNavigation(instance, instruction);
            }
        });
    }

    function handleGuardedRoute(instance, instruction) {
        var resultOrPromise = router.guardRoute(instance, instruction);
        if (resultOrPromise) {
            if (resultOrPromise.then) {
                resultOrPromise.then(function (result) {
                    if (result) {
                        if (system.isString(result)) {
                            redirect(result);
                        } else {
                            activateRoute(instance, instruction);
                        }
                    } else {
                        cancelNavigation(instance, instruction);
                    }
                });
            } else {
                if (system.isString(resultOrPromise)) {
                    redirect(resultOrPromise);
                } else {
                    activateRoute(instance, instruction);
                }
            }
        } else {
            cancelNavigation(instance, instruction);
        }
    }

    function dequeueRoute() {
        if (isNavigating()) {
            return;
        }

        var instruction = queue.shift();
        queue = [];

        if (!instruction) {
            return;
        }

        isNavigating(true);

        system.acquire(instruction.config.moduleId).then(function (module) {
            var instance = router.getActivatableInstance(module, instruction);

            if (router.guardRoute) {
                handleGuardedRoute(instance, instruction);
            } else {
                activateRoute(instance, instruction);
            }
        });
    }

    function queueRoute(instruction) {
        queue.unshift(instruction);
        dequeueRoute();
    }

    function mapRoute(config) {
        if (!system.isRegExp(config.route)) {
            config.title = info.title || router.convertRouteToTitle(config.route);
            config.moduleId = config.moduleId || router.convertRouteToModuleId(config.url);
            config.hash = config.hash || '#/' + config.route;
            config.route = routeStringToRegExp(config.route);
        }

        config.caption = config.caption || config.title;
        config.settings = config.settings || {};

        router.routes.push(config);

        history.route(config.route, function (fragment) {
            queueRoute({
                fragment: fragment,
                config: config,
                params: extractParameters(config.route, fragment)
            });
        });

        return router;
    }

    function addActiveFlag(config) {
        config.isActive = ko.computed(function () {
            return activeItem() && activeItem().__moduleId__ == config.moduleId;
        });
    }

    router.updateDocumentTitle = function (instance, instruction) {
        if (instruction.config.title) {
            if (app.title) {
                document.title = instruction.config.title + " | " + app.title;
            } else {
                document.title = instruction.config.title;
            }
        } else if (app.title) {
            document.title = app.title;
        }
    };

    router.getActivatableInstance = function (module, instruction) {
        if (system.isFunction(module)) {
            return new module();
        } else {
            return module;
        }
    };

    router.stripParametersFromRoute = function(route) {
        var colonIndex = route.indexOf(':');
        var length = colonIndex > 0 ? colonIndex - 1 : route.length;
        return route.substring(0, length);
    };

    router.convertRouteToModuleId = function (route) {
        return router.stripParametersFromRoute(route);
    };

    router.convertRouteToTitle = function (route) {
        var value = router.stripParametersFromRoute(route);
        return value.substring(0, 1).toUpperCase() + value.substring(1);
    };

    // Manually bind a single named route to a module. For example:
    //
    //     router.map('search/:query/p:num', 'viewmodels/search');
    //
    router.map = function (route, config) {
        if (system.isArray(route)) {
            // We have to reverse the
            // order of the routes here to support behavior where the most general
            // routes can be defined at the bottom of the route array.
            var current;

            while ((current = route.pop()) != null) {
                router.map(current);
            }

            return router;
        }

        if (system.isString(route) || system.isRegExp(route)) {
            if (!config) {
                config = {};
            } else if (system.isString(config)) {
                config = {
                    moduleId: config
                };
            }

            config.route = route;
        } else {
            config = route;
        }

        return mapRoute(config);
    };

    router.navigate = function (fragment, options) {
        history.navigate(fragment, options);
        return router;
    };

    router.navigateBack = function() {
        history.history.back();
    };

    router.afterCompose = function() {
        setTimeout(function() {
            isNavigating(false);
            router.events.trigger('router:navigation-composed', currentActivation, currentInstruction);
            dequeueRoute();
        }, 10);
    };

    router.buildNavigationModel = function() {
        var nav = [], routes = router.routes;

        for (var i = 0; i < routes.length; i++) {
            var current = routes[i];

            if (current.nav != undefined) {
                addActiveFlag(current);
                nav.push(current);
            }
        }

        nav.sort(function(a, b) { return a - b; });
        router.navigationModel(nav);

        return router;
    };

    router.start = function (options) {
        router.options = options || router.options || {};
        history.start(router.options);
        return router;
    };

    router.stop = function () {
        history.stop();
        return router;
    };

    router.reset = function () {
        history.handlers = [];
        router.routes = [];
        return router;
    };

    return router;
});<|endoftext|>


<commit_before><commit_msg>Config<commit_after>//var vTranslations = Aspectize.CreateView('Translations', aas.Controls.TranslationManager.TranslationManager, aas.Zones.Home.ZoneInfo);

//vTranslations.OnLoad.BindCommand(aas.Services.Server.MyTranslationManagerService.LoadTranslations(), aas.Data.TranslationData, true, true);
//vTranslations.BtnExtractTranslation.click.BindCommand('Server/MyTranslationManagerService.ExtractLiterals');
//vTranslations.BtnExportToExcel.href.BindData('MyTranslationManagerService.ExportTranslation.bin.cmd.ashx');
//vTranslations.UploadExcelFile.OnFileSelected.BindCommand(aas.Services.Server.MyTranslationManagerService.ImportTraductionFromExcel(vTranslations.UploadExcelFile.SelectedFile));
//vTranslations.BtnSave.click.BindCommand(aas.Services.Server.MyTranslationManagerService.SaveTranslations(aas.Data.TranslationData), '', false, true);
//vTranslations.TxtSearch.keyup.BindCommand(aas.Services.Browser.UIService.SetCustomFilter, { controlName: aas.ViewName.Translations.GridTranslation, customFilter: aas.Expression('(Key).toLowerCase().indexOf("' + vTranslations.TxtSearch.value + '".toLowerCase()) !== -1') });

//vTranslations.GridTranslation.BindGrid(aas.Data.TranslationData.Translation, 'Key ASC');
//vTranslations.GridTranslation.PageSize.BindData(20);
//var cName = vTranslations.GridTranslation.AddGridColumn('Translation', 'Span');
//cName.Text.BindData(vTranslations.GridTranslation.DataSource.Key);

//var vTranslationItemValues = Aspectize.CreateRepeatedView('TranslationItemValues', aas.Controls.TranslationManager.TranslationItemValues, aas.Zones.Translations.RepeaterPanelTranslationItemValues, aas.Data.TranslationData.Translation.Values);
//vTranslationItemValues.Language.BindData(vTranslationItemValues.ParentData.Language);
//vTranslationItemValues.TxtTranslation.value.BindData(vTranslationItemValues.ParentData.Value);<|endoftext|>


<commit_before>function Scoreboard(options) {
  if (!options) options= {};

  this._message = options.message || '';
  this._score = options.score || 0;

  if (options.onTimeExpired) {
    this._countdown = new __Countdown({
      duration: options.countDownFrom
    });
    this.onTimeExpired(
      options.onTimeExpired ? options.onTimeExpired : function(){}
    );
  }
}

/****
 * Message
 */
Scoreboard.prototype.showMessage = function() {
  this.ensureDom();
  this.message_el.style.display = 'block';
  return this;
};

Scoreboard.prototype.hideMessage = function() {
  this.message_el.style.display = 'none';
  return this;
};

Scoreboard.prototype.clearMessage = function() {
  this.setMessage();
  return this;
};

Scoreboard.prototype.setMessage = function(message) {
  if (!message) message = '';

  this.showMessage();
  this._message = message;
  this.message_el.innerHTML = this._message;

  this.message_el.scrollTop = this.message_el.scrollHeight;

  return this;
};

Scoreboard.prototype.appendMessage = function(message) {
  function div(s) { return '
' + s + '
'; }
  this.setMessage((this._message || '') + div(message));
  return this;
};

Scoreboard.prototype.addMessage = Scoreboard.prototype.appendMessage;
Scoreboard.prototype.message = Scoreboard.prototype.appendMessage;

// Message

/*****
 * Help
 */
Scoreboard.prototype.showHelp = function() {
  this.ensureDom();
  this.help_el.style.display = 'block';
  return this;
};

Scoreboard.prototype.hideHelp = function() {
  this.help_el.style.display = 'none';
  return this;
};

Scoreboard.prototype.toggleHelp = function() {
  this.help_el.style.display = this.help_el.style.display == 'none' ?
    'block' : 'none';
  return this;
};

Scoreboard.prototype.showHelpFooter = function() {
  this.ensureDom();
  this.help_footer.style.display = 'block';
  return this;
};

Scoreboard.prototype.setHelp = function(help) {
  this.showHelpFooter();
  this._help = help;
  this.help_el.innerHTML = this._help;

  this.help_el.scrollTop = this.help_el.scrollHeight;

  return this;
};

Scoreboard.prototype.addHelp = Scoreboard.prototype.setHelp;
Scoreboard.prototype.help = Scoreboard.prototype.setHelp;

// Help

/****
 * Score
 */

Scoreboard.prototype.showScore = function() {
  this.ensureDom();
  this.score_el.style.display = 'block';
  return this;
};

Scoreboard.prototype.setScore = function(score) {
  this.showScore();
  this._score = score;

  this.score_el.innerHTML = "Score: " + this._score;

  return this;
};

Scoreboard.prototype.getScore = function() {
  return this._score;
};

Scoreboard.prototype.score = Scoreboard.prototype.setScore;

Scoreboard.prototype.addPoints = function(points) {
  this.setScore(this._score + points);
};

Scoreboard.prototype.subtractPoints = function(points) {
  this.setScore(this._score - points);
};

// Timer

/*****
 * Timer
 */

Scoreboard.prototype.showTimer = function() {
  this.ensureDom();
  this._showTimer = true;
  this.timer_el.style.display = 'block';
  this.setTimer();
  return this;
};
Scoreboard.prototype.timer = Scoreboard.prototype.showTimer;

Scoreboard.prototype.hideTimer = function() {
  this.ensureDom();
  this._showTimer = false;
  this.timer_el.style.display = 'none';
  return this;
};

Scoreboard.prototype.setTimer = function() {
  var that = this;
  if (!this._timer) this._timer = new __Timer();
  this._timer.onUpdate(function(time) {
    that.timer_el.innerHTML = 'Time: ' + time;
  });
  this.timer_el.style.display = this._showTimer ? 'block' : 'none';
};

Scoreboard.prototype.stopTimer = function() {
  this._timer.stop();
};

Scoreboard.prototype.startTimer = function() {
  this._timer.start();
};

Scoreboard.prototype.resetTimer = function() {
  this._timer.reset();
};

Scoreboard.prototype.getTime = function() {
  return this._timer.diff() / 1000;
};

// Timer

/*****
 * Countdown
 */

Scoreboard.prototype.showCountdown = function(duration) {
  this.ensureDom();
  this._showCountdown = true;
  this.countdown_el.style.display = 'block';
  this.setCountdown(duration);
  return this;
};

Scoreboard.prototype.countdown = Scoreboard.prototype.showCountdown;

Scoreboard.prototype.hideCountdown = function() {
  this.ensureDom();
  this._showCountdown = false;
  this.countdown_el.style.display = 'none';
  return this;
};

Scoreboard.prototype.setCountdown = function(duration) {
  var that = this;

  if (!this._countdown) this._countdown = new __Countdown({
    duration: duration
  });

  if (duration) this._countdown.reset(duration);
  this._countdown.onUpdate(function(time) {
    that.countdown_el.innerHTML = 'Remaining: ' + time;
  });
  this.countdown_el.style.display = this._showCountdown ? 'block' : 'none';
};

Scoreboard.prototype.stopCountdown = function() {
  this._countdown.stop();
};

Scoreboard.prototype.startCountdown = function() {
  this._countdown.start();
};

Scoreboard.prototype.resetCountdown = function(time) {
  this._countdown.reset(time);
};

Scoreboard.prototype.getTimeRemaining = function() {
  return this._countdown.diff() / 1000;
};

Scoreboard.prototype.onTimeExpired = function(cb) {
  if (!cb) return;

  var that = this;
  this._on_time_expired = (typeof(cb) == 'string') ?
    function() { that.setMessage(cb); } : cb;

  this._countdown.onTimeExpired(function() {
    that.stopTimer();
    that._on_time_expired();
  });
};

// Countdown

Scoreboard.prototype.ensureDom = function() {
  if (this.el) return;

  var el = this.el = document.createElement('div');
  el.id = 'scoreboard';
  el.style.position = 'absolute';
  el.style.backgroundColor = 'black';
  el.style.opacity = 0.7;
  el.style.borderRadius = "5px";
  el.style.padding = "5px 20px";
  el.style.right = "50px";
  el.style.top = "75px";
  el.style.width = (window.innerWidth * 0.2) + "px";
  el.style.minWidth = '200px';

  el.style.color = 'yellow';
  el.style.fontFamily = 'Arial, San Serif';
  el.style.fontWeight = 'bold';
  el.style.fontSize = (window.innerHeight * 0.033) + "px";

  var countdown_el = this.countdown_el = document.createElement('div');
  el.appendChild(countdown_el);

  var timer_el = this.timer_el = document.createElement('div');
  el.appendChild(timer_el);

  var score_el = this.score_el = document.createElement('div');
  el.appendChild(score_el);

  var message_el = this.message_el = document.createElement('div');
  message_el.style.fontWeight = 'normal';
  message_el.style.color = 'white';
  message_el.style.maxHeight = (window.innerHeight * 0.2) + "px";
  message_el.style.overflowY = 'auto';
  el.appendChild(message_el);

  var help_el = this.help_el = document.createElement('div');
  help_el.style.display = 'none';
  help_el.style.fontWeight = 'normal';
  help_el.style.color = 'white';
  help_el.style.fontSize = (window.innerHeight * 0.028) + "px";
  help_el.style.borderTop = "1px #676767 solid";
  help_el.style.marginTop = '5px';
  help_el.style.paddingTop = '3px';
  help_el.style.maxHeight = (window.innerHeight * 0.2) + "px";
  help_el.style.overflowY = 'auto';
  el.appendChild(help_el);

  var help_footer = this.help_footer = document.createElement('div');
  help_footer.style.display = 'none';
  help_footer.style.fontWeight = 'normal';
  help_footer.style.color = 'white';
  help_footer.style.maxHeight = (window.innerHeight * 0.2) + "px";
  help_footer.style.fontSize = (window.innerHeight * 0.02) + "px";
  help_footer.style.borderTop = "1px #676767 solid";
  help_footer.style.marginTop = '5px';
  help_footer.innerHTML = 'Press ? for help';
  el.appendChild(help_footer);

  var that = this;
  document.addEventListener('keypress', function(event) {
    if (event.keyCode == 63) that.toggleHelp();
  });

  document.body.appendChild(el);
};

/****
 * Timer Class
 */
function __Timer() {
  this._start = (new Date).getTime();
}

__Timer.prototype.toString = function() {
  var minutes = "" + this._minutes(),
      seconds = "" + this._seconds();

  if (minutes.length == 1) minutes = "0" + minutes;
  if (seconds.length == 1) seconds = "0" + seconds;

  return minutes + ":" + seconds;
};

__Timer.prototype.diff = function() {
  var now = this.last();
  return now - this._start;
};

__Timer.prototype.last = function() {
  if (this._last) return this._last;
  return (new Date).getTime();
};

__Timer.prototype.stop = function() {
  if (this._last) return;
  this._last = (new Date).getTime();
};

__Timer.prototype.start = function() {
  this._last = undefined;
};

__Timer.prototype.reset = function() {
  this._start = (new Date).getTime();
  this.start();
};

__Timer.prototype._minutes = function() {
  return Math.floor(this.diff()/(60*1000));
};

__Timer.prototype._seconds = function() {
  var ms = this.diff() % (60*1000);
  return Math.floor(ms / 1000);
};

__Timer.prototype.seconds = function() {
  return this.diff() / (60*1000);
};

__Timer.prototype.onUpdate = function(cb) {
  var that = this;
  cb(that.toString());
  setInterval(function() {cb(that.toString());}, 500);
};

// Timer

/*****
 * Countdown
 */

function __Countdown(options) {
  this._start = (new Date).getTime();
  this.duration = options.duration || 60;
  this._onTimeExpired = function(){};
  this.waitForTimeExpired();
}

__Countdown.prototype = new __Timer();

__Countdown.prototype.diff = function() {
  var now = this.last(),
      diff = now - this._start;

  if (diff > this.duration * 1000) return 0;
  return this.duration * 1000 - diff;
};

__Countdown.prototype.reset = function(duration) {
  if (duration) this.duration = duration;
  this._start = (new Date).getTime();
  this.waitForTimeExpired();
  this.start();
};

__Countdown.prototype.waitForTimeExpired = function() {
  var that = this;

  function _wait() {
    if (that.diff() > 0) return setTimeout(_wait, 500);
    that.stop();
    return that._onTimeExpired(that.toString());
  }
  _wait();
};

__Countdown.prototype.onTimeExpired = function(cb) {
  this._onTimeExpired = cb;
};

// Countdown
<commit_msg>Better default behavior<commit_after>function Scoreboard(options) {
  if (!options) options= {};

  this._message = options.message || '';
  this._score = options.score || 0;

  if (options.onTimeExpired) {
    this._countdown = new __Countdown({
      duration: options.countDownFrom
    });
    this.onTimeExpired(
      options.onTimeExpired ? options.onTimeExpired : function(){}
    );
  }
}

/****
 * Message
 */
Scoreboard.prototype.showMessage = function() {
  this.ensureDom();
  this.message_el.style.display = 'block';
  return this;
};

Scoreboard.prototype.hideMessage = function() {
  this.message_el.style.display = 'none';
  return this;
};

Scoreboard.prototype.clearMessage = function() {
  this.setMessage();
  return this;
};

Scoreboard.prototype.setMessage = function(message) {
  if (!message) message = '';

  this.showMessage();
  this._message = message;
  this.message_el.innerHTML = this._message;

  this.message_el.scrollTop = this.message_el.scrollHeight;

  return this;
};

Scoreboard.prototype.appendMessage = function(message) {
  function div(s) { return '
' + s + '
'; }
  this.setMessage((this._message || '') + div(message));
  return this;
};

Scoreboard.prototype.addMessage = Scoreboard.prototype.appendMessage;
Scoreboard.prototype.message = Scoreboard.prototype.setMessage;

// Message

/*****
 * Help
 */
Scoreboard.prototype.showHelp = function() {
  this.ensureDom();
  this.help_el.style.display = 'block';
  return this;
};

Scoreboard.prototype.hideHelp = function() {
  this.help_el.style.display = 'none';
  return this;
};

Scoreboard.prototype.toggleHelp = function() {
  this.help_el.style.display = this.help_el.style.display == 'none' ?
    'block' : 'none';
  return this;
};

Scoreboard.prototype.showHelpFooter = function() {
  this.ensureDom();
  this.help_footer.style.display = 'block';
  return this;
};

Scoreboard.prototype.setHelp = function(help) {
  this.showHelpFooter();
  this._help = help;
  this.help_el.innerHTML = this._help;

  this.help_el.scrollTop = this.help_el.scrollHeight;

  return this;
};

Scoreboard.prototype.addHelp = Scoreboard.prototype.setHelp;
Scoreboard.prototype.help = Scoreboard.prototype.setHelp;

// Help

/****
 * Score
 */

Scoreboard.prototype.showScore = function() {
  this.ensureDom();
  this.score_el.style.display = 'block';
  return this;
};

Scoreboard.prototype.setScore = function(score) {
  this.showScore();
  this._score = score || 0;

  this.score_el.innerHTML = "Score: " + this._score;

  return this;
};

Scoreboard.prototype.getScore = function() {
  return this._score;
};

Scoreboard.prototype.score = Scoreboard.prototype.setScore;

Scoreboard.prototype.addPoints = function(points) {
  this.setScore(this._score + points);
};

Scoreboard.prototype.subtractPoints = function(points) {
  this.setScore(this._score - points);
};

// Timer

/*****
 * Timer
 */

Scoreboard.prototype.showTimer = function() {
  this.ensureDom();
  this._showTimer = true;
  this.timer_el.style.display = 'block';
  this.setTimer();
  return this;
};
Scoreboard.prototype.timer = Scoreboard.prototype.showTimer;

Scoreboard.prototype.hideTimer = function() {
  this.ensureDom();
  this._showTimer = false;
  this.timer_el.style.display = 'none';
  return this;
};

Scoreboard.prototype.setTimer = function() {
  var that = this;
  if (!this._timer) this._timer = new __Timer();
  this._timer.onUpdate(function(time) {
    that.timer_el.innerHTML = 'Time: ' + time;
  });
  this.timer_el.style.display = this._showTimer ? 'block' : 'none';
};

Scoreboard.prototype.stopTimer = function() {
  this._timer.stop();
};

Scoreboard.prototype.startTimer = function() {
  this._timer.start();
};

Scoreboard.prototype.resetTimer = function() {
  this._timer.reset();
};

Scoreboard.prototype.getTime = function() {
  return this._timer.diff() / 1000;
};

// Timer

/*****
 * Countdown
 */

Scoreboard.prototype.showCountdown = function(duration) {
  this.ensureDom();
  this._showCountdown = true;
  this.countdown_el.style.display = 'block';
  this.setCountdown(duration);
  return this;
};

Scoreboard.prototype.countdown = Scoreboard.prototype.showCountdown;

Scoreboard.prototype.hideCountdown = function() {
  this.ensureDom();
  this._showCountdown = false;
  this.countdown_el.style.display = 'none';
  return this;
};

Scoreboard.prototype.setCountdown = function(duration) {
  var that = this;

  if (!this._countdown) this._countdown = new __Countdown({
    duration: duration
  });

  if (duration) this._countdown.reset(duration);
  this._countdown.onUpdate(function(time) {
    that.countdown_el.innerHTML = 'Remaining: ' + time;
  });
  this.countdown_el.style.display = this._showCountdown ? 'block' : 'none';
};

Scoreboard.prototype.stopCountdown = function() {
  this._countdown.stop();
};

Scoreboard.prototype.startCountdown = function() {
  this._countdown.start();
};

Scoreboard.prototype.resetCountdown = function(time) {
  this._countdown.reset(time);
};

Scoreboard.prototype.getTimeRemaining = function() {
  return this._countdown.diff() / 1000;
};

Scoreboard.prototype.onTimeExpired = function(cb) {
  if (!cb) return;

  var that = this;
  this._on_time_expired = (typeof(cb) == 'string') ?
    function() { that.setMessage(cb); } : cb;

  this._countdown.onTimeExpired(function() {
    that.stopTimer();
    that._on_time_expired();
  });
};

// Countdown

Scoreboard.prototype.ensureDom = function() {
  if (this.el) return;

  var el = this.el = document.createElement('div');
  el.id = 'scoreboard';
  el.style.position = 'absolute';
  el.style.backgroundColor = 'black';
  el.style.opacity = 0.7;
  el.style.borderRadius = "5px";
  el.style.padding = "5px 20px";
  el.style.right = "50px";
  el.style.top = "75px";
  el.style.width = (window.innerWidth * 0.2) + "px";
  el.style.minWidth = '200px';

  el.style.color = 'yellow';
  el.style.fontFamily = 'Arial, San Serif';
  el.style.fontWeight = 'bold';
  el.style.fontSize = (window.innerHeight * 0.033) + "px";

  var countdown_el = this.countdown_el = document.createElement('div');
  el.appendChild(countdown_el);

  var timer_el = this.timer_el = document.createElement('div');
  el.appendChild(timer_el);

  var score_el = this.score_el = document.createElement('div');
  el.appendChild(score_el);

  var message_el = this.message_el = document.createElement('div');
  message_el.style.fontWeight = 'normal';
  message_el.style.color = 'white';
  message_el.style.maxHeight = (window.innerHeight * 0.2) + "px";
  message_el.style.overflowY = 'auto';
  el.appendChild(message_el);

  var help_el = this.help_el = document.createElement('div');
  help_el.style.display = 'none';
  help_el.style.fontWeight = 'normal';
  help_el.style.color = 'white';
  help_el.style.fontSize = (window.innerHeight * 0.028) + "px";
  help_el.style.borderTop = "1px #676767 solid";
  help_el.style.marginTop = '5px';
  help_el.style.paddingTop = '3px';
  help_el.style.maxHeight = (window.innerHeight * 0.2) + "px";
  help_el.style.overflowY = 'auto';
  el.appendChild(help_el);

  var help_footer = this.help_footer = document.createElement('div');
  help_footer.style.display = 'none';
  help_footer.style.fontWeight = 'normal';
  help_footer.style.color = 'white';
  help_footer.style.maxHeight = (window.innerHeight * 0.2) + "px";
  help_footer.style.fontSize = (window.innerHeight * 0.02) + "px";
  help_footer.style.borderTop = "1px #676767 solid";
  help_footer.style.marginTop = '5px';
  help_footer.innerHTML = 'Press ? for help';
  el.appendChild(help_footer);

  var that = this;
  document.addEventListener('keypress', function(event) {
    if (event.keyCode == 63) that.toggleHelp();
  });

  document.body.appendChild(el);
};

/****
 * Timer Class
 */
function __Timer() {
  this._start = (new Date).getTime();
}

__Timer.prototype.toString = function() {
  var minutes = "" + this._minutes(),
      seconds = "" + this._seconds();

  if (minutes.length == 1) minutes = "0" + minutes;
  if (seconds.length == 1) seconds = "0" + seconds;

  return minutes + ":" + seconds;
};

__Timer.prototype.diff = function() {
  var now = this.last();
  return now - this._start;
};

__Timer.prototype.last = function() {
  if (this._last) return this._last;
  return (new Date).getTime();
};

__Timer.prototype.stop = function() {
  if (this._last) return;
  this._last = (new Date).getTime();
};

__Timer.prototype.start = function() {
  this._last = undefined;
};

__Timer.prototype.reset = function() {
  this._start = (new Date).getTime();
  this.start();
};

__Timer.prototype._minutes = function() {
  return Math.floor(this.diff()/(60*1000));
};

__Timer.prototype._seconds = function() {
  var ms = this.diff() % (60*1000);
  return Math.floor(ms / 1000);
};

__Timer.prototype.seconds = function() {
  return this.diff() / (60*1000);
};

__Timer.prototype.onUpdate = function(cb) {
  var that = this;
  cb(that.toString());
  setInterval(function() {cb(that.toString());}, 500);
};

// Timer

/*****
 * Countdown
 */

function __Countdown(options) {
  this._start = (new Date).getTime();
  this.duration = options.duration || 60;
  this._onTimeExpired = function(){};
  this.waitForTimeExpired();
}

__Countdown.prototype = new __Timer();

__Countdown.prototype.diff = function() {
  var now = this.last(),
      diff = now - this._start;

  if (diff > this.duration * 1000) return 0;
  return this.duration * 1000 - diff;
};

__Countdown.prototype.reset = function(duration) {
  if (duration) this.duration = duration;
  this._start = (new Date).getTime();
  this.waitForTimeExpired();
  this.start();
};

__Countdown.prototype.waitForTimeExpired = function() {
  var that = this;

  function _wait() {
    if (that.diff() > 0) return setTimeout(_wait, 500);
    that.stop();
    return that._onTimeExpired(that.toString());
  }
  _wait();
};

__Countdown.prototype.onTimeExpired = function(cb) {
  this._onTimeExpired = cb;
};

// Countdown
<|endoftext|>


<commit_before>import {
  omit,
  decodeBase64Url
} from './utils'

const reservedUserProperties = [
  '_id',
  '_rev',
  'name',
  'password',
  'roles',
  'type',
  'salt',
  'derived_key',
  'password_scheme',
  'iterations'
]

class LoginService {
  constructor (
    $http,
    $window,
    $rootRouter,
    config,
    sessionService,
    toastService,
    mainService
  ) {
    this.$http = $http
    this.$window = $window
    this.$rootRouter = $rootRouter
    this.config = config
    this.sessionService = sessionService
    this.toastService = toastService
    this.mainService = mainService
  }

  createUser (username, token) {
    const data = {
      username,
      token
    }
    const opts = {
      headers: {
        'x-api-key': this.config.middleware.key
      }
    }
    return this.$http.post(this.config.middleware.url + '/users', data, opts)
  }

  handleLoginError (error) {
    let message = 'Could not log you in'
    if (error && error.data && error.data.message) {
      message += `. The response was "${error.data.message}".`
    }
    return this.toastService.error('login-failed', message)
  }

  setSession (session) {
    const doc = Object.assign({}, session, {
      _id: '_local/session'
    })
    const opts = {
      forceUpdate: true
    }
    return this.sessionService.save(doc, opts)
  }

  init (session) {
    this.mainService.init(session)
    this.$rootRouter.navigate(['/Nav'])
    return session
  }

  navLogin () {
    this.$window.location.href = this.config.nav + '/SignIn.aspx'
  }

  navLogout () {
    this.$window.location.href = this.config.nav + '/SignOut.aspx'
  }

  loginOrCreateUser (username, token) {
    const retry = () => {
      return this.createUser(username, token)
        .then(() => this.sessionService.login(username, token))
    }
    return this.sessionService.login(username, token)
      .catch(retry)
  }

  getSession () {
    return this.sessionService.get('_local/session')
  }

  canActivate () {
    const isValidSession = (local, remote) => {
      if (!remote.ok) {
        return Promise.reject('session invalid')
      }
      if (!remote.userCtx) {
        return Promise.reject('missing user context in session')
      }
      if (remote.userCtx.name !== local.userName) {
        return Promise.reject(`session mismatch for user ${local.userName}`)
      }
    }

    const checkSession = localSession => {
      return this.sessionService.getSession()
        .then(remoteSession => isValidSession(localSession, remoteSession))
        .then(() => this.init(localSession))
        .catch(err => {
          if ('status' in err && err.status === 0) {
            // User looks to be offline, grant login
            return this.init(localSession)
          }
          throw err
        })
    }

    return this.getSession()
      .then(localSession => checkSession(localSession))
      .catch(err => this.navLogin(err))
  }

  updateSession (username, session) {
    const omitted = omit(session, reservedUserProperties)
    const metadata = Object.assign(omitted, {
      lastLogin: new Date().toISOString()
    })

    const updateIdRev = res => {
      if (!res.ok) {
        return session
      }
      return Object.assign(metadata, omit(res, ['ok']))
    }

    const opts = {
      metadata
    }
    return this.sessionService.putUser(username, opts)
      .then(updateIdRev)
  }

  login (instruction) {
    if (!(instruction.params.username && instruction.params.token)) {
      return this.canActivate()
    }

    const username = this.$window.decodeURI(instruction.params.username)
      .toLowerCase()
    const token = decodeBase64Url(instruction.params.token)

    return this.loginOrCreateUser(username, token)
      .then(() => this.sessionService.getUser(username))
      .then(session => this.updateSession(username, session))
      .then(session => this.setSession(session))
      .then(session => this.init(session))
      .catch(err => this.handleLoginError(err))
  }

  logout () {
    return this.getSession()
      .then(session => this.sessionService.remove(session))
  }
}

LoginService.$inject = [
  '$http',
  '$window',
  '$rootRouter',
  'config',
  'sessionService',
  'toastService',
  'mainService'
]

export default LoginService
<commit_msg>fix: type check error before condition<commit_after>import {
  omit,
  decodeBase64Url
} from './utils'

const reservedUserProperties = [
  '_id',
  '_rev',
  'name',
  'password',
  'roles',
  'type',
  'salt',
  'derived_key',
  'password_scheme',
  'iterations'
]

class LoginService {
  constructor (
    $http,
    $window,
    $rootRouter,
    config,
    sessionService,
    toastService,
    mainService
  ) {
    this.$http = $http
    this.$window = $window
    this.$rootRouter = $rootRouter
    this.config = config
    this.sessionService = sessionService
    this.toastService = toastService
    this.mainService = mainService
  }

  createUser (username, token) {
    const data = {
      username,
      token
    }
    const opts = {
      headers: {
        'x-api-key': this.config.middleware.key
      }
    }
    return this.$http.post(this.config.middleware.url + '/users', data, opts)
  }

  handleLoginError (error) {
    let message = 'Could not log you in'
    if (error && error.data && error.data.message) {
      message += `. The response was "${error.data.message}".`
    }
    return this.toastService.error('login-failed', message)
  }

  setSession (session) {
    const doc = Object.assign({}, session, {
      _id: '_local/session'
    })
    const opts = {
      forceUpdate: true
    }
    return this.sessionService.save(doc, opts)
  }

  init (session) {
    this.mainService.init(session)
    this.$rootRouter.navigate(['/Nav'])
    return session
  }

  navLogin () {
    this.$window.location.href = this.config.nav + '/SignIn.aspx'
  }

  navLogout () {
    this.$window.location.href = this.config.nav + '/SignOut.aspx'
  }

  loginOrCreateUser (username, token) {
    const retry = () => {
      return this.createUser(username, token)
        .then(() => this.sessionService.login(username, token))
    }
    return this.sessionService.login(username, token)
      .catch(retry)
  }

  getSession () {
    return this.sessionService.get('_local/session')
  }

  canActivate () {
    const isValidSession = (local, remote) => {
      if (!remote.ok) {
        return Promise.reject('session invalid')
      }
      if (!remote.userCtx) {
        return Promise.reject('missing user context in session')
      }
      if (remote.userCtx.name !== local.userName) {
        return Promise.reject(`session mismatch for user ${local.userName}`)
      }
    }

    const checkSession = localSession => {
      return this.sessionService.getSession()
        .then(remoteSession => isValidSession(localSession, remoteSession))
        .then(() => this.init(localSession))
        .catch(err => {
          if (angular.isObject(err) && err.status === 0) {
            // User looks to be offline, grant login
            return this.init(localSession)
          }
          throw err
        })
    }

    return this.getSession()
      .then(localSession => checkSession(localSession))
      .catch(err => this.navLogin(err))
  }

  updateSession (username, session) {
    const omitted = omit(session, reservedUserProperties)
    const metadata = Object.assign(omitted, {
      lastLogin: new Date().toISOString()
    })

    const updateIdRev = res => {
      if (!res.ok) {
        return session
      }
      return Object.assign(metadata, omit(res, ['ok']))
    }

    const opts = {
      metadata
    }
    return this.sessionService.putUser(username, opts)
      .then(updateIdRev)
  }

  login (instruction) {
    if (!(instruction.params.username && instruction.params.token)) {
      return this.canActivate()
    }

    const username = this.$window.decodeURI(instruction.params.username)
      .toLowerCase()
    const token = decodeBase64Url(instruction.params.token)

    return this.loginOrCreateUser(username, token)
      .then(() => this.sessionService.getUser(username))
      .then(session => this.updateSession(username, session))
      .then(session => this.setSession(session))
      .then(session => this.init(session))
      .catch(err => this.handleLoginError(err))
  }

  logout () {
    return this.getSession()
      .then(session => this.sessionService.remove(session))
  }
}

LoginService.$inject = [
  '$http',
  '$window',
  '$rootRouter',
  'config',
  'sessionService',
  'toastService',
  'mainService'
]

export default LoginService
<|endoftext|>


<commit_before>/* ==============================================================================
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
;(function(root, factory) {
  if (typeof define === "function" && define.amd) {
    define(["jquery"], factory)
  } else {
    root.DateTime = factory(root.jQuery)
  }
})(this, function($) {
  var DateTime = function(date) {
    if(typeof date == 'string') this.date = new Date(date)
    else this.date = date || new Date()
  }

  $.each([
    'getTime',
    'getFullYear',
    'getMonth',
    'getDate',
    'getDay',
    'getHours',
    'getMinutes',
    'getSeconds',
    'getMilliseconds'
  ], function(_index, func) {
    DateTime.prototype[func] = function() { return this.date[func]() }
  })

  DateTime.prototype.withTime = function(h, m) {
    var dateWithTime = this.clone()
    dateWithTime.date.setHours(h)
    dateWithTime.date.setMinutes(m)
    dateWithTime.date.setMilliseconds(0)
    return dateWithTime
  }

  DateTime.DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
  DateTime.SECOND = 1000
  DateTime.MINUTE = 60 * DateTime.SECOND
  DateTime.HOUR = 60 * DateTime.MINUTE
  DateTime.DAY = 24 * DateTime.HOUR
  DateTime.WEEK = 7 * DateTime.DAY

  DateTime.now = function() {
    if(typeof DateTime._now == 'undefined') {
      DateTime._now = new DateTime()
    }
    return DateTime._now
  }

  DateTime.getDaysInMonth = function(year, month) {
    if(((0 == (year % 4)) && ( (0 != (year % 100)) || (0 == (year % 400)))) && month == 1) {
      return 29
    } else {
      return DateTime.DAYS_IN_MONTH[month]
    }
  }

  DateTime.getDayInYear = function(year, month, day) {
    var days = 0
    for(var i = 0; i < month; i++) {
      days += DateTime.getDaysInMonth(year, i)
    }
    days += day
    return days
  }

  DateTime.prototype.getDaysInMonth = function() { return DateTime.getDaysInMonth(this.getFullYear(), this.getMonth()) }

  DateTime.prototype.getDayInYear = function() { return DateTime.getDayInYear(this.getFullYear(), this.getMonth(), this.getDate()) }

  DateTime.prototype.plusDays = function(days) {
    var newDateTime = this.clone()
    var hours = this.getHours()
    newDateTime.date.setTime(this.getTime() + days * DateTime.DAY)

    // Fix the DateTime offset caused by daylight saving time
    var delta = hours - newDateTime.getHours()
    if(delta != 0) {
      // Correct the delta to be between [-12, 12]
      if(delta > 12) {
        delta -= 24
      }
      if(delta < -12) {
        delta += 24
      }
      newDateTime.date.setTime(newDateTime.getTime() + (delta * DateTime.HOUR))
    }
    return newDateTime
  }

  DateTime.prototype.stripped = function() { return new Date(parseInt(this.getTime() / DateTime.DAY, 10)) }

  DateTime.prototype.compareTo = function(date) {
    if(!date) {
      return 1
    }
    var lhs = this.getTime()
    var rhs = date.getTime()
    if(lhs < rhs) {
      return -1
    } else {
      if(lhs > rhs) {
        return 1
      } else {
        return 0
      }
    }
  }

  DateTime.prototype.isToday = function() { return this.equalsOnlyDate(DateTime.now()) }

  DateTime.prototype.getWeekInYear = function(weekNumberingSystem) {
    if(weekNumberingSystem != "US" && weekNumberingSystem != "ISO") {
      throw("Week numbering system must be either US or ISO, was " + weekNumberingSystem)
    }

    var firstDay = new Date(this.getFullYear(), 0, 1).getDay()
    if(weekNumberingSystem == "US") {
      return Math.ceil((this.getDayInYear() + firstDay) / 7)
    }

    var THU = 4
    var weekday = this.getDay()
    if(weekday == 0) {
      weekday = 7
    }
    if(firstDay == 0) {
      firstDay = 7
    }

    // If Dec 29 falls on Mon, Dec 30 on Mon or Tue, Dec 31 on Mon - Wed, it's on the first week of next year
    if(this.getMonth() == 11 && this.getDate() >= 29 && (this.getDate() - weekday) > 27) {
      return 1
    }
    // If Jan 1-3 falls on Fri, Sat or Sun, it's on the last week of the previous year
    if(this.getMonth() == 0 && this.getDate() < 4 && weekday > THU) {
      return new DateTime(new Date(this.getFullYear() - 1, 11, 31)).getWeekInYear('ISO')
    }

    var week = Math.ceil((this.getDayInYear() + firstDay - 1) / 7)

    // If first days of this year are on last year's last week, the above gives one week too much
    if(firstDay > THU) {
      week--
    }
    return week
  }

  //TODO refactor
  DateTime.prototype.clone = function() { return new DateTime(new Date(this.getTime())) }

  DateTime.prototype.isOddMonth = function() { return this.getMonth() % 2 != 0 }

  DateTime.prototype.equalsOnlyDate = function(date) {
    if(!date) {
      return false
    }
    return this.getMonth() == date.getMonth() && this.getDate() == date.getDate() && this.getFullYear() == date.getFullYear()
  }

  DateTime.prototype.isBetweenDates = function(start, end) { return this.compareTo(start) >= 0 && this.compareTo(end) <= 0 }

  DateTime.prototype.firstDateOfMonth = function() { return new DateTime((this.getMonth() + 1) + "/1/" + this.getFullYear()) }

  DateTime.prototype.lastDateOfMonth = function() { return new DateTime((this.getMonth() + 1) + "/" + this.getDaysInMonth() + "/" + this.getFullYear()) }

  DateTime.prototype.distanceInDays = function(date) {
    var first = parseInt(this.getTime() / DateTime.DAY, 10)
    var last = parseInt(date.getTime() / DateTime.DAY, 10)
    return (last - first)
  }

  DateTime.prototype.withWeekday = function(weekday) { return this.plusDays(weekday - this.getDay()) }

  DateTime.prototype.getOnlyDate = function() { return new DateTime(new Date(this.getFullYear(), this.getMonth(), this.getDate())) }

  DateTime.prototype.getTimezone = function() {
    return this.date.toString().replace(/^.*? ([A-Z]{3}) [0-9]{4}.*$/, "$1").replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, "$1$2$3")
  }

  DateTime.prototype.getDayOfYear = function() {
    var num = 0
    DateTime.daysInMonth[1] = this.isLeapYear() ? 29 : 28
    for(var i = 0; i < this.getMonth(); ++i) {
      num += DateTime.daysInMonth[i]
    }
    return num + this.getDate() - 1
  }

  DateTime.prototype.isLeapYear = function() {
    var year = this.getFullYear()
    return ((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)))
  }

  DateTime.prototype.getDaysInMonth = function() {
    DateTime.daysInMonth[1] = this.isLeapYear() ? 29 : 28
    return DateTime.daysInMonth[this.getMonth()]
  }

  DateTime.prototype.getSuffix = function() {
    switch(this.getDate()) {
      case 1:
      case 21:
      case 31:
        return "st"
      case 2:
      case 22:
        return "nd"
      case 3:
      case 23:
        return "rd"
      default:
        return "th"
    }
  }

  DateTime.prototype.isWeekend = function() { return this.getDay() == 6 || this.getDay() == 0 }

  DateTime.prototype.toString = function() { return this.date.toISOString() }

  DateTime.daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
  DateTime.y2kYear = 50
  DateTime.monthNumbers = { Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11 }

  return DateTime
})
<commit_msg>Improve withTime function by resetting seconds and allowing input in 'hh:mm' format<commit_after>/* ==============================================================================
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
;(function(root, factory) {
  if (typeof define === "function" && define.amd) {
    define(["jquery"], factory)
  } else {
    root.DateTime = factory(root.jQuery)
  }
})(this, function($) {
  var DateTime = function(date) {
    if(typeof date == 'string') this.date = new Date(date)
    else this.date = date || new Date()
  }

  $.each([
    'getTime',
    'getFullYear',
    'getMonth',
    'getDate',
    'getDay',
    'getHours',
    'getMinutes',
    'getSeconds',
    'getMilliseconds'
  ], function(_index, func) {
    DateTime.prototype[func] = function() { return this.date[func]() }
  })

  DateTime.prototype.withTime = function(h, m) {
    if(typeof h == 'string') {
      var hoursAndMinutes = h.split(':')
      h = hoursAndMinutes[0]
      m = hoursAndMinutes[1]
    }
    var dateWithTime = this.clone()
    dateWithTime.date.setHours(h)
    dateWithTime.date.setMinutes(m)
    dateWithTime.date.setSeconds(0)
    dateWithTime.date.setMilliseconds(0)
    return dateWithTime
  }

  DateTime.DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
  DateTime.SECOND = 1000
  DateTime.MINUTE = 60 * DateTime.SECOND
  DateTime.HOUR = 60 * DateTime.MINUTE
  DateTime.DAY = 24 * DateTime.HOUR
  DateTime.WEEK = 7 * DateTime.DAY

  DateTime.now = function() {
    if(typeof DateTime._now == 'undefined') {
      DateTime._now = new DateTime()
    }
    return DateTime._now
  }

  DateTime.getDaysInMonth = function(year, month) {
    if(((0 == (year % 4)) && ( (0 != (year % 100)) || (0 == (year % 400)))) && month == 1) {
      return 29
    } else {
      return DateTime.DAYS_IN_MONTH[month]
    }
  }

  DateTime.getDayInYear = function(year, month, day) {
    var days = 0
    for(var i = 0; i < month; i++) {
      days += DateTime.getDaysInMonth(year, i)
    }
    days += day
    return days
  }

  DateTime.prototype.getDaysInMonth = function() { return DateTime.getDaysInMonth(this.getFullYear(), this.getMonth()) }

  DateTime.prototype.getDayInYear = function() { return DateTime.getDayInYear(this.getFullYear(), this.getMonth(), this.getDate()) }

  DateTime.prototype.plusDays = function(days) {
    var newDateTime = this.clone()
    var hours = this.getHours()
    newDateTime.date.setTime(this.getTime() + days * DateTime.DAY)

    // Fix the DateTime offset caused by daylight saving time
    var delta = hours - newDateTime.getHours()
    if(delta != 0) {
      // Correct the delta to be between [-12, 12]
      if(delta > 12) {
        delta -= 24
      }
      if(delta < -12) {
        delta += 24
      }
      newDateTime.date.setTime(newDateTime.getTime() + (delta * DateTime.HOUR))
    }
    return newDateTime
  }

  DateTime.prototype.stripped = function() { return new Date(parseInt(this.getTime() / DateTime.DAY, 10)) }

  DateTime.prototype.compareTo = function(date) {
    if(!date) {
      return 1
    }
    var lhs = this.getTime()
    var rhs = date.getTime()
    if(lhs < rhs) {
      return -1
    } else {
      if(lhs > rhs) {
        return 1
      } else {
        return 0
      }
    }
  }

  DateTime.prototype.isToday = function() { return this.equalsOnlyDate(DateTime.now()) }

  DateTime.prototype.getWeekInYear = function(weekNumberingSystem) {
    if(weekNumberingSystem != "US" && weekNumberingSystem != "ISO") {
      throw("Week numbering system must be either US or ISO, was " + weekNumberingSystem)
    }

    var firstDay = new Date(this.getFullYear(), 0, 1).getDay()
    if(weekNumberingSystem == "US") {
      return Math.ceil((this.getDayInYear() + firstDay) / 7)
    }

    var THU = 4
    var weekday = this.getDay()
    if(weekday == 0) {
      weekday = 7
    }
    if(firstDay == 0) {
      firstDay = 7
    }

    // If Dec 29 falls on Mon, Dec 30 on Mon or Tue, Dec 31 on Mon - Wed, it's on the first week of next year
    if(this.getMonth() == 11 && this.getDate() >= 29 && (this.getDate() - weekday) > 27) {
      return 1
    }
    // If Jan 1-3 falls on Fri, Sat or Sun, it's on the last week of the previous year
    if(this.getMonth() == 0 && this.getDate() < 4 && weekday > THU) {
      return new DateTime(new Date(this.getFullYear() - 1, 11, 31)).getWeekInYear('ISO')
    }

    var week = Math.ceil((this.getDayInYear() + firstDay - 1) / 7)

    // If first days of this year are on last year's last week, the above gives one week too much
    if(firstDay > THU) {
      week--
    }
    return week
  }

  //TODO refactor
  DateTime.prototype.clone = function() { return new DateTime(new Date(this.getTime())) }

  DateTime.prototype.isOddMonth = function() { return this.getMonth() % 2 != 0 }

  DateTime.prototype.equalsOnlyDate = function(date) {
    if(!date) {
      return false
    }
    return this.getMonth() == date.getMonth() && this.getDate() == date.getDate() && this.getFullYear() == date.getFullYear()
  }

  DateTime.prototype.isBetweenDates = function(start, end) { return this.compareTo(start) >= 0 && this.compareTo(end) <= 0 }

  DateTime.prototype.firstDateOfMonth = function() { return new DateTime((this.getMonth() + 1) + "/1/" + this.getFullYear()) }

  DateTime.prototype.lastDateOfMonth = function() { return new DateTime((this.getMonth() + 1) + "/" + this.getDaysInMonth() + "/" + this.getFullYear()) }

  DateTime.prototype.distanceInDays = function(date) {
    var first = parseInt(this.getTime() / DateTime.DAY, 10)
    var last = parseInt(date.getTime() / DateTime.DAY, 10)
    return (last - first)
  }

  DateTime.prototype.withWeekday = function(weekday) { return this.plusDays(weekday - this.getDay()) }

  DateTime.prototype.getOnlyDate = function() { return new DateTime(new Date(this.getFullYear(), this.getMonth(), this.getDate())) }

  DateTime.prototype.getTimezone = function() {
    return this.date.toString().replace(/^.*? ([A-Z]{3}) [0-9]{4}.*$/, "$1").replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, "$1$2$3")
  }

  DateTime.prototype.getDayOfYear = function() {
    var num = 0
    DateTime.daysInMonth[1] = this.isLeapYear() ? 29 : 28
    for(var i = 0; i < this.getMonth(); ++i) {
      num += DateTime.daysInMonth[i]
    }
    return num + this.getDate() - 1
  }

  DateTime.prototype.isLeapYear = function() {
    var year = this.getFullYear()
    return ((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)))
  }

  DateTime.prototype.getDaysInMonth = function() {
    DateTime.daysInMonth[1] = this.isLeapYear() ? 29 : 28
    return DateTime.daysInMonth[this.getMonth()]
  }

  DateTime.prototype.getSuffix = function() {
    switch(this.getDate()) {
      case 1:
      case 21:
      case 31:
        return "st"
      case 2:
      case 22:
        return "nd"
      case 3:
      case 23:
        return "rd"
      default:
        return "th"
    }
  }

  DateTime.prototype.isWeekend = function() { return this.getDay() == 6 || this.getDay() == 0 }

  DateTime.prototype.toString = function() { return this.date.toISOString() }

  DateTime.daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
  DateTime.y2kYear = 50
  DateTime.monthNumbers = { Jan: 0, Feb: 1, Mar: 2, Apr: 3, May: 4, Jun: 5, Jul: 6, Aug: 7, Sep: 8, Oct: 9, Nov: 10, Dec: 11 }

  return DateTime
})
<|endoftext|>


<commit_before>import React from 'react';
import { render } from 'react-dom';

import Root from 'containers/Root';
import { createHistory } from 'history';
import { syncHistoryWithStore } from 'react-router-redux';
import { useRouterHistory } from 'react-router';
import configureStore from 'store/configureStore';

require('./theme/style.css');

const store = configureStore();

const browserHistory = useRouterHistory(createHistory)({
   basename: ROUTE_BASE
});

const history = syncHistoryWithStore(browserHistory, store);

const rootElement = document.getElementById('root');

render(<Root store={store} history={history} />, rootElement);
<commit_msg>Changed stylesheet import<commit_after>import React from 'react';
import { render } from 'react-dom';

import Root from 'containers/Root';
import { createHistory } from 'history';
import { syncHistoryWithStore } from 'react-router-redux';
import { useRouterHistory } from 'react-router';
import configureStore from 'store/configureStore';

import 'theme/style.css';

const store = configureStore();

const browserHistory = useRouterHistory(createHistory)({
   basename: ROUTE_BASE
});

const history = syncHistoryWithStore(browserHistory, store);

const rootElement = document.getElementById('root');

render(<Root store={store} history={history} />, rootElement);
<|endoftext|>


<commit_before>//TODO: Split into multiple files
var AuthController = ['$rootScope', 'AuthService', 'AUTH_EVENTS',
    function ($rootScope, AuthService, AUTH_EVENTS) {
        var auth = this;
        this.credentials = {};
        this.loginError = null;
        this.registerError = null;

        this.login = function () {
            AuthService.login(auth.credentials);
        };

        this.logout = function () {
            AuthService.logout();
        };

        this.register = function () {
            AuthService.register(auth.credentials);
        };

        this.getUserName = function () {
            return AuthService.getUserName();
        };

        $rootScope.$on(AUTH_EVENTS.login_failure, function () {
            auth.loginError = 'Falscher Benutzername oder falsches Passwort!';
        });

        $rootScope.$on(AUTH_EVENTS.register_failure, function (evt, data) {
            auth.registerError = !!data ? data.errorMessage : 'Fehler beim Registrieren!';
        });

        $rootScope.$on(AUTH_EVENTS.login_success, function () {
            auth.registerError = null;
            auth.loginError = null;
        });
    }];

var HomeController = ['$http', '$rootScope', 'AuthService',
    function ($http, $rootScope, AuthService) {
        var ctrl = this;
        this.teachers = {};

        AuthService.runWhenAuthenticated(function () {
            $http.get('/api/teacher/list')
                .success(function (data) {
                    for (var i = 0; i < data.length; i++) {
                        var split = data[i].name.split(' ', 2);
                        data[i].firstName = split[0];
                        data[i].lastName = split[1];
                    }

                    ctrl.teachers = data;
                });
        });
    }];

var RegisterController = function () {

};

var LoginController = ['$stateParams', '$rootScope', '$location',
    function ($stateParams, $rootScope, $location) {
        this.back = function () {
            if ($rootScope.returnto !== null) {
                $location.path($rootScope.returnto);
                $rootScope.returnto = null;
            } else {
                $location.path('/');
            }
        };
    }];

var TeacherDetailController = ['TeacherDetailService', '$stateParams',
    function (TeacherDetailService, $stateParams) {
        this.getFields = TeacherDetailService.getFields;
        this.getTeacher = TeacherDetailService.getTeacher;

        this.saveField = TeacherDetailService.saveField;
        this.uneditField = TeacherDetailService.uneditField;
        this.editField = TeacherDetailService.editField;
        this.addField = TeacherDetailService.addField;
        this.deleteField = TeacherDetailService.deleteField;
        this.isFetched = TeacherDetailService.isFetched;

        TeacherDetailService.fetchTeacher($stateParams.id);
    }];

var NavDataController = ['$http', 'AuthService',
    function ($http, AuthService) {
        var ctrl = this;
        this.teachers = {};
        this.teachersLoaded = false;

        AuthService.onAuthChange(function () {
            $http.get('/api/teacher/list')
                .success(function (data) {
                    ctrl.teachers = data;
                    ctrl.teachersLoaded = true;
                });
        }, function () {
            ctrl.teachersLoaded = false;
            ctrl.teachers = {};
        });
    }];

var tingoApp = angular.module('tingo', ['ui.router', 'ui.bootstrap', 'ngCookies']);

tingoApp.factory('AuthInterceptor', ['$q', 'TokenService', '$location', '$rootScope',
    function ($q, TokenService, $location, $rootScope) {
        var authInterceptor = {};

        authInterceptor.request = function (config) {
            if (TokenService.hasToken()) {
                config.headers.Authorization = 'Bearer ' + TokenService.getToken();
            }
            return config;
        };

        authInterceptor.responseError = function (response) {
            if (response.status === 401) {
                console.info("401 encountered");
                TokenService.setToken(null);
                $rootScope.returnto = $location.path(); //save current path for login back
                $location.path('/login');
            }
            return $q.reject(response);
        };

        return authInterceptor;
    }]);

tingoApp.config(['$stateProvider', '$urlRouterProvider', '$urlMatcherFactoryProvider', '$httpProvider',
    function ($stateProvider, $urlRouterProvider, $urlMatcherFactoryProvider, $httpProvider) {
        $urlRouterProvider.otherwise('/');
        $urlRouterProvider.when('/teachers', '/teachers/list');

        $stateProvider
            .state('home', {
                url: '/',
                templateUrl: 'partials/home.html',
                controller: 'HomeController',
                controllerAs: 'homeCtrl',
                data: {
                    no_auth: true
                }
            })
            .state('login', {
                url: '/login?errRedirect',
                templateUrl: 'partials/login.html',
                controller: 'LoginController',
                controllerAs: 'loginCtrl',
                data: {
                    no_auth: true
                }
            })
            .state('register', {
                url: '/register',
                templateUrl: 'partials/register.html',
                controller: 'RegisterController',
                controllerAs: 'regCtrl',
                data: {
                    no_auth: true
                }
            })
            .state('teachers', {
                url: '/teachers',
                abstract: true,
                template: '<ui-view/>'
            })
            .state('teachers.detail', {
                url: '/detail/:id',
                templateUrl: 'partials/teacher-detail.html',
                controller: 'TeacherDetailController',
                controllerAs: 'detailCtrl'
            });

        //Prevent Spring Security from displaying auth dialog, we control authentication ourselves!
        $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
        $httpProvider.interceptors.push('AuthInterceptor');
    }]);

tingoApp.factory('TeacherDetailService', ['$http',
    function ($http) {
        var detailService = {};
        detailService.teacher = {name: 'Loading..', abbreviation: '....'};
        detailService.fields = [];
        var fetched = false;
        var backupField = {};

        detailService.saveField = function (field) {
            var myIndex = _.indexOf(detailService.fields, field);
            if (field.text.length === 0 || !field.text.trim()) {
                detailService.fields.splice(myIndex);
                return;
            }

            $http.post('/api/field/save', field)
                .then(function (response) {
                    detailService.fields[myIndex] = response.data;
                }, function (response) {
                    console.warn('Couldn\'t save fields: ');
                    console.warn(response);
                    alert('Fehler beim Speichern: ' + response.data.errorMessage);
                });
        };

        detailService.uneditField = function (field) {
            var myIndex = _.indexOf(detailService.fields, field);
            backupField.editing = false;
            detailService.fields[myIndex] = backupField;
        };

        detailService.editField = function (field) {
            backupField = _.clone(field);
            field.editing = true;
        };

        detailService.addField = function () {
            detailService.fields.push({text: '', teacherId: detailService.teacher.id, editing: true});
        };

        detailService.deleteField = function (field) {
            $http.post('/api/field/delete', field)
                .then(function (response) {
                    detailService.fields = _.without(detailService.fields, field);
                }, function (response) {
                    console.warn('Couldn\'t delete field: ');
                    console.warn(response);
                    alert('Fehler beim Löschen: ' + response.data.errorMessage);
                });
        };

        detailService.fetchTeacher = function (id) {
            fetched = false;
            $http.get('/api/field/by/teacher/' + id)
                .success(function (data) {
                    detailService.teacher = data.teacher;
                    detailService.fields = data.fields;
                    fetched = true;
                });
        };

        // Getters and Setters
        detailService.isFetched = function () {
            return fetched;
        };

        detailService.getFields = function () {
            return detailService.fields;
        };

        detailService.getTeacher = function () {
            return detailService.teacher;
        };

        return detailService;
    }]);

tingoApp.factory('TokenService', ['$window', '$cookies',
    function ($window, $cookies) {
        var tokenService = {};
        var token = null;
        var cookieOpts = {
            path: '/',
            //secure: true, //doesn't work with local HTTP-only debug; TODO
            expires: new Date(new Date().getTime() + (1000 * 60 * 60 * 24 * 15)) /* 15 days */
        };

        tokenService.setToken = function (newToken) {
            token = newToken;

            if (!!token) {
                $window.localStorage.setItem('tingo-token', token);
                $cookies.put('tingo-token-cookie', token, cookieOpts);

            } else {
                $window.localStorage.removeItem('tingo-token');
                $cookies.remove('tingo-token-cookie', cookieOpts);
            }
        };

        tokenService.getToken = function () {
            return token;
        };

        tokenService.hasToken = function () {
            return !!token;
        };

        var storedToken = $window.localStorage.getItem('tingo-token');
        if (!!storedToken) {
            token = storedToken;
        }

        return tokenService;
    }]);

tingoApp.factory('AuthService', ['$http', '$rootScope', '$location', 'AUTH_EVENTS', '$window', 'TokenService',
    function ($http, $rootScope, $location, AUTH_EVENTS, $window, TokenService) {
        var authService = {};

        authService.credentials = {};
        authService.username = null;
        //authService.authenticated = false;
        authService.error = false;
        authService.errorMessage = "Falscher Benutzername oder falsches Passwort!";
        authService.authChecked = false;
        authService.authRequested = false;

        var setAuth = function (token) {
            authService.setToken(token);
            $rootScope.$broadcast(AUTH_EVENTS.auth_status, authService.authenticated);
        };

        var resetAuth = function () {
            setAuth(null);
        };

        var authenticate = function (credentials, callback) {
            $http.post('auth/login', credentials).then(function (response) {
                authService.authChecked = true;
                if (response.data.token) {
                    setAuth(response.data.token);
                } else {
                    resetAuth();
                }

                if (callback) {
                    callback(authService.authenticated, response.data);
                }
            }, function () {
                resetAuth();
                if (callback) {
                    callback(false, {});
                }
            });
        };

        authService.login = function (credentials) {
            authenticate(credentials,
                function (authSuccess, data) {
                    authService.error = !authSuccess;
                    if (authSuccess) {
                        $rootScope.$broadcast(AUTH_EVENTS.login_success, data);
                        if ($rootScope.returnto !== null) {
                            $location.path($rootScope.returnto);
                            $rootScope.returnto = null;
                        } else {
                            $location.path('/');
                        }
                    } else {
                        $rootScope.$broadcast(AUTH_EVENTS.login_failure, data);
                        $location.path('/login');
                    }
                }
            );
        };

        authService.logout = function () {
            resetAuth();
            $location.path('/');
            $rootScope.$broadcast(AUTH_EVENTS.logout);
        };

        authService.register = function (credentials) {
            resetAuth();
            $http.post('auth/register', credentials).then(function () {
                authService.login(credentials);
            }, function (response) {
                authService.error = true;
                authService.errorMessage = response.data.errorMessage;
                $rootScope.$broadcast(AUTH_EVENTS.register_failure, response.data);
            });
        };

        authService.isAuthenticated = function () {
            return TokenService.hasToken() && authService.authenticated;
        };

        authService.runWhenAuthenticated = function (callback) { //this is kind of deprecated, use #onAuthChange(...)
            onAuthChange(callback, null);
        };

        authService.onAuthChange = function (onLogin, onLogout) {
            if (onLogin) {
                if (authService.isAuthenticated()) {
                    onLogin();
                }
                //Call again on re-authentication
                $rootScope.$on(AUTH_EVENTS.login_success, onLogin);
            }

            if (onLogout) {
                if(!authService.isAuthenticated()) {
                    onLogout();
                }
                $rootScope.$on(AUTH_EVENTS.logout, onLogout);
            }
        };

        authService.isAuthenticatedSafe = function (callback) {
            callback(authService.isAuthenticated());
        };

        authService.getUserName = function () {
            return !!authService.username ? authService.username : 'Anon';
        };

        authService.setToken = function (token) {
            authService.token = token;
            if (token === null) {
                authService.username = null;
                TokenService.setToken(null);
                $rootScope.authenticated = authService.authenticated = false;
            } else {
                var base64 = token.split('.')[1].replace('-', '+').replace('_', '/'); //2nd string is payload, undo URL encoding
                var claimsObj = JSON.parse($window.atob(base64)); //convert base64 to text and parse as JSON
                TokenService.setToken(token);
                authService.username = claimsObj.sub; //subject is the username
                $rootScope.authenticated = authService.authenticated = true;
            }
        };

        $rootScope.isAuthenticated = authService.isAuthenticated;

        setAuth(TokenService.getToken());

        return authService;
    }]);

tingoApp.run(['$rootScope', '$state', '$location', 'AuthService',
    function ($rootScope, $state, $location, AuthService) { //just inject so it gets initialised
        $rootScope.authenticated = false;
        $rootScope.returnto = null;
        $rootScope.$on('$stateChangeStart', function (evt, to) {
            if (!to.data || !to.data.no_auth) {
                $rootScope.returnto = $location.path();
                //There must be a better solution to this
                //This event is called also when the first state is loaded, so
                //we might not know whether we're authenticated yet. If we don't, we can't really
                //do anything except wait for the auth reply - this happens primarily when a user
                //reloads an authed page or enters the URL directly
                //
                //This might flicker the new state for a fraction of a second, but they can't access
                //sensitive data anyways since the server checks auth itself
                //
                //The proper way to do this would be to somehow get Angular to wait for the login state
                //before loading any page, or use an intent, which Angular's event system doesn't support.
                AuthService.isAuthenticatedSafe(function (authenticated) {
                    console.log('auth callback: ' + authenticated);
                    if (!authenticated) {
                        $state.go('login');
                    }
                });
            }
        });
    }]);

tingoApp.controller('HomeController', HomeController);
tingoApp.controller('AuthController', AuthController);
tingoApp.controller('TeacherDetailController', TeacherDetailController);
tingoApp.controller('LoginController', LoginController);
tingoApp.controller('RegisterController', RegisterController);
tingoApp.controller('NavDataController', NavDataController);
tingoApp.constant('AUTH_EVENTS', {
    login_success: 'auth-login-success',
    login_failure: 'auth-login-failure',
    register_failure: 'auth-register-failure',
    auth_status: 'auth-status-change',
    logout: 'auth-logout'
});
<commit_msg>Fix JS ReferenceError on home page<commit_after>//TODO: Split into multiple files
var AuthController = ['$rootScope', 'AuthService', 'AUTH_EVENTS',
    function ($rootScope, AuthService, AUTH_EVENTS) {
        var auth = this;
        this.credentials = {};
        this.loginError = null;
        this.registerError = null;

        this.login = function () {
            AuthService.login(auth.credentials);
        };

        this.logout = function () {
            AuthService.logout();
        };

        this.register = function () {
            AuthService.register(auth.credentials);
        };

        this.getUserName = function () {
            return AuthService.getUserName();
        };

        $rootScope.$on(AUTH_EVENTS.login_failure, function () {
            auth.loginError = 'Falscher Benutzername oder falsches Passwort!';
        });

        $rootScope.$on(AUTH_EVENTS.register_failure, function (evt, data) {
            auth.registerError = !!data ? data.errorMessage : 'Fehler beim Registrieren!';
        });

        $rootScope.$on(AUTH_EVENTS.login_success, function () {
            auth.registerError = null;
            auth.loginError = null;
        });
    }];

var HomeController = ['$http', '$rootScope', 'AuthService',
    function ($http, $rootScope, AuthService) {
        var ctrl = this;
        this.teachers = {};

        AuthService.runWhenAuthenticated(function () {
            $http.get('/api/teacher/list')
                .success(function (data) {
                    for (var i = 0; i < data.length; i++) {
                        var split = data[i].name.split(' ', 2);
                        data[i].firstName = split[0];
                        data[i].lastName = split[1];
                    }

                    ctrl.teachers = data;
                });
        });
    }];

var RegisterController = function () {

};

var LoginController = ['$stateParams', '$rootScope', '$location',
    function ($stateParams, $rootScope, $location) {
        this.back = function () {
            if ($rootScope.returnto !== null) {
                $location.path($rootScope.returnto);
                $rootScope.returnto = null;
            } else {
                $location.path('/');
            }
        };
    }];

var TeacherDetailController = ['TeacherDetailService', '$stateParams',
    function (TeacherDetailService, $stateParams) {
        this.getFields = TeacherDetailService.getFields;
        this.getTeacher = TeacherDetailService.getTeacher;

        this.saveField = TeacherDetailService.saveField;
        this.uneditField = TeacherDetailService.uneditField;
        this.editField = TeacherDetailService.editField;
        this.addField = TeacherDetailService.addField;
        this.deleteField = TeacherDetailService.deleteField;
        this.isFetched = TeacherDetailService.isFetched;

        TeacherDetailService.fetchTeacher($stateParams.id);
    }];

var NavDataController = ['$http', 'AuthService',
    function ($http, AuthService) {
        var ctrl = this;
        this.teachers = {};
        this.teachersLoaded = false;

        AuthService.onAuthChange(function () {
            $http.get('/api/teacher/list')
                .success(function (data) {
                    ctrl.teachers = data;
                    ctrl.teachersLoaded = true;
                });
        }, function () {
            ctrl.teachersLoaded = false;
            ctrl.teachers = {};
        });
    }];

var tingoApp = angular.module('tingo', ['ui.router', 'ui.bootstrap', 'ngCookies']);

tingoApp.factory('AuthInterceptor', ['$q', 'TokenService', '$location', '$rootScope',
    function ($q, TokenService, $location, $rootScope) {
        var authInterceptor = {};

        authInterceptor.request = function (config) {
            if (TokenService.hasToken()) {
                config.headers.Authorization = 'Bearer ' + TokenService.getToken();
            }
            return config;
        };

        authInterceptor.responseError = function (response) {
            if (response.status === 401) {
                console.info("401 encountered");
                TokenService.setToken(null);
                $rootScope.returnto = $location.path(); //save current path for login back
                $location.path('/login');
            }
            return $q.reject(response);
        };

        return authInterceptor;
    }]);

tingoApp.config(['$stateProvider', '$urlRouterProvider', '$urlMatcherFactoryProvider', '$httpProvider',
    function ($stateProvider, $urlRouterProvider, $urlMatcherFactoryProvider, $httpProvider) {
        $urlRouterProvider.otherwise('/');
        $urlRouterProvider.when('/teachers', '/teachers/list');

        $stateProvider
            .state('home', {
                url: '/',
                templateUrl: 'partials/home.html',
                controller: 'HomeController',
                controllerAs: 'homeCtrl',
                data: {
                    no_auth: true
                }
            })
            .state('login', {
                url: '/login?errRedirect',
                templateUrl: 'partials/login.html',
                controller: 'LoginController',
                controllerAs: 'loginCtrl',
                data: {
                    no_auth: true
                }
            })
            .state('register', {
                url: '/register',
                templateUrl: 'partials/register.html',
                controller: 'RegisterController',
                controllerAs: 'regCtrl',
                data: {
                    no_auth: true
                }
            })
            .state('teachers', {
                url: '/teachers',
                abstract: true,
                template: '<ui-view/>'
            })
            .state('teachers.detail', {
                url: '/detail/:id',
                templateUrl: 'partials/teacher-detail.html',
                controller: 'TeacherDetailController',
                controllerAs: 'detailCtrl'
            });

        //Prevent Spring Security from displaying auth dialog, we control authentication ourselves!
        $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
        $httpProvider.interceptors.push('AuthInterceptor');
    }]);

tingoApp.factory('TeacherDetailService', ['$http',
    function ($http) {
        var detailService = {};
        detailService.teacher = {name: 'Loading..', abbreviation: '....'};
        detailService.fields = [];
        var fetched = false;
        var backupField = {};

        detailService.saveField = function (field) {
            var myIndex = _.indexOf(detailService.fields, field);
            if (field.text.length === 0 || !field.text.trim()) {
                detailService.fields.splice(myIndex);
                return;
            }

            $http.post('/api/field/save', field)
                .then(function (response) {
                    detailService.fields[myIndex] = response.data;
                }, function (response) {
                    console.warn('Couldn\'t save fields: ');
                    console.warn(response);
                    alert('Fehler beim Speichern: ' + response.data.errorMessage);
                });
        };

        detailService.uneditField = function (field) {
            var myIndex = _.indexOf(detailService.fields, field);
            backupField.editing = false;
            detailService.fields[myIndex] = backupField;
        };

        detailService.editField = function (field) {
            backupField = _.clone(field);
            field.editing = true;
        };

        detailService.addField = function () {
            detailService.fields.push({text: '', teacherId: detailService.teacher.id, editing: true});
        };

        detailService.deleteField = function (field) {
            $http.post('/api/field/delete', field)
                .then(function (response) {
                    detailService.fields = _.without(detailService.fields, field);
                }, function (response) {
                    console.warn('Couldn\'t delete field: ');
                    console.warn(response);
                    alert('Fehler beim Löschen: ' + response.data.errorMessage);
                });
        };

        detailService.fetchTeacher = function (id) {
            fetched = false;
            $http.get('/api/field/by/teacher/' + id)
                .success(function (data) {
                    detailService.teacher = data.teacher;
                    detailService.fields = data.fields;
                    fetched = true;
                });
        };

        // Getters and Setters
        detailService.isFetched = function () {
            return fetched;
        };

        detailService.getFields = function () {
            return detailService.fields;
        };

        detailService.getTeacher = function () {
            return detailService.teacher;
        };

        return detailService;
    }]);

tingoApp.factory('TokenService', ['$window', '$cookies',
    function ($window, $cookies) {
        var tokenService = {};
        var token = null;
        var cookieOpts = {
            path: '/',
            //secure: true, //doesn't work with local HTTP-only debug; TODO
            expires: new Date(new Date().getTime() + (1000 * 60 * 60 * 24 * 15)) /* 15 days */
        };

        tokenService.setToken = function (newToken) {
            token = newToken;

            if (!!token) {
                $window.localStorage.setItem('tingo-token', token);
                $cookies.put('tingo-token-cookie', token, cookieOpts);

            } else {
                $window.localStorage.removeItem('tingo-token');
                $cookies.remove('tingo-token-cookie', cookieOpts);
            }
        };

        tokenService.getToken = function () {
            return token;
        };

        tokenService.hasToken = function () {
            return !!token;
        };

        var storedToken = $window.localStorage.getItem('tingo-token');
        if (!!storedToken) {
            token = storedToken;
        }

        return tokenService;
    }]);

tingoApp.factory('AuthService', ['$http', '$rootScope', '$location', 'AUTH_EVENTS', '$window', 'TokenService',
    function ($http, $rootScope, $location, AUTH_EVENTS, $window, TokenService) {
        var authService = {};

        authService.credentials = {};
        authService.username = null;
        //authService.authenticated = false;
        authService.error = false;
        authService.errorMessage = "Falscher Benutzername oder falsches Passwort!";
        authService.authChecked = false;
        authService.authRequested = false;

        var setAuth = function (token) {
            authService.setToken(token);
            $rootScope.$broadcast(AUTH_EVENTS.auth_status, authService.authenticated);
        };

        var resetAuth = function () {
            setAuth(null);
        };

        var authenticate = function (credentials, callback) {
            $http.post('auth/login', credentials).then(function (response) {
                authService.authChecked = true;
                if (response.data.token) {
                    setAuth(response.data.token);
                } else {
                    resetAuth();
                }

                if (callback) {
                    callback(authService.authenticated, response.data);
                }
            }, function () {
                resetAuth();
                if (callback) {
                    callback(false, {});
                }
            });
        };

        authService.login = function (credentials) {
            authenticate(credentials,
                function (authSuccess, data) {
                    authService.error = !authSuccess;
                    if (authSuccess) {
                        $rootScope.$broadcast(AUTH_EVENTS.login_success, data);
                        if ($rootScope.returnto !== null) {
                            $location.path($rootScope.returnto);
                            $rootScope.returnto = null;
                        } else {
                            $location.path('/');
                        }
                    } else {
                        $rootScope.$broadcast(AUTH_EVENTS.login_failure, data);
                        $location.path('/login');
                    }
                }
            );
        };

        authService.logout = function () {
            resetAuth();
            $location.path('/');
            $rootScope.$broadcast(AUTH_EVENTS.logout);
        };

        authService.register = function (credentials) {
            resetAuth();
            $http.post('auth/register', credentials).then(function () {
                authService.login(credentials);
            }, function (response) {
                authService.error = true;
                authService.errorMessage = response.data.errorMessage;
                $rootScope.$broadcast(AUTH_EVENTS.register_failure, response.data);
            });
        };

        authService.isAuthenticated = function () {
            return TokenService.hasToken() && authService.authenticated;
        };

        authService.runWhenAuthenticated = function (callback) { //this is kind of deprecated, use #onAuthChange(...)
            authService.onAuthChange(callback, null);
        };

        authService.onAuthChange = function (onLogin, onLogout) {
            if (onLogin) {
                if (authService.isAuthenticated()) {
                    onLogin();
                }
                //Call again on re-authentication
                $rootScope.$on(AUTH_EVENTS.login_success, onLogin);
            }

            if (onLogout) {
                if(!authService.isAuthenticated()) {
                    onLogout();
                }
                $rootScope.$on(AUTH_EVENTS.logout, onLogout);
            }
        };

        authService.isAuthenticatedSafe = function (callback) {
            callback(authService.isAuthenticated());
        };

        authService.getUserName = function () {
            return !!authService.username ? authService.username : 'Anon';
        };

        authService.setToken = function (token) {
            authService.token = token;
            if (token === null) {
                authService.username = null;
                TokenService.setToken(null);
                $rootScope.authenticated = authService.authenticated = false;
            } else {
                var base64 = token.split('.')[1].replace('-', '+').replace('_', '/'); //2nd string is payload, undo URL encoding
                var claimsObj = JSON.parse($window.atob(base64)); //convert base64 to text and parse as JSON
                TokenService.setToken(token);
                authService.username = claimsObj.sub; //subject is the username
                $rootScope.authenticated = authService.authenticated = true;
            }
        };

        $rootScope.isAuthenticated = authService.isAuthenticated;

        setAuth(TokenService.getToken());

        return authService;
    }]);

tingoApp.run(['$rootScope', '$state', '$location', 'AuthService',
    function ($rootScope, $state, $location, AuthService) { //just inject so it gets initialised
        $rootScope.authenticated = false;
        $rootScope.returnto = null;
        $rootScope.$on('$stateChangeStart', function (evt, to) {
            if (!to.data || !to.data.no_auth) {
                $rootScope.returnto = $location.path();
                //There must be a better solution to this
                //This event is called also when the first state is loaded, so
                //we might not know whether we're authenticated yet. If we don't, we can't really
                //do anything except wait for the auth reply - this happens primarily when a user
                //reloads an authed page or enters the URL directly
                //
                //This might flicker the new state for a fraction of a second, but they can't access
                //sensitive data anyways since the server checks auth itself
                //
                //The proper way to do this would be to somehow get Angular to wait for the login state
                //before loading any page, or use an intent, which Angular's event system doesn't support.
                AuthService.isAuthenticatedSafe(function (authenticated) {
                    console.log('auth callback: ' + authenticated);
                    if (!authenticated) {
                        $state.go('login');
                    }
                });
            }
        });
    }]);

tingoApp.controller('HomeController', HomeController);
tingoApp.controller('AuthController', AuthController);
tingoApp.controller('TeacherDetailController', TeacherDetailController);
tingoApp.controller('LoginController', LoginController);
tingoApp.controller('RegisterController', RegisterController);
tingoApp.controller('NavDataController', NavDataController);
tingoApp.constant('AUTH_EVENTS', {
    login_success: 'auth-login-success',
    login_failure: 'auth-login-failure',
    register_failure: 'auth-register-failure',
    auth_status: 'auth-status-change',
    logout: 'auth-logout'
});
<|endoftext|>