<commit_before>window.onload = function() {

    var socket = io();
    var init = false;
    var spinnerEl = document.querySelector('.spinner__container');
    var loginWrapperEl = document.querySelector('.login-wrapper');
    var loginFormEl = document.querySelector('.login');
    var loginInputEl = document.querySelector('.login-input');
    var usernameInvalidEl = document.querySelector('.username-invalid');
    var usernameTakenEl = document.querySelector('.username-taken');
    var usersOnlineEl = document.querySelector('.users-online');
    var userNumEl = document.querySelectorAll('.user-no');
    var chatBoxEl = document.querySelector('.chatbox');
    var messageFormEl = document.querySelector('.send-msg form');
    var messageInputEl = document.querySelector('.msg');
    var openSideBarEl = document.querySelector('.pull');
    var closeSideBarEl = document.querySelector('.close');
    var username;
    var users;
    var isTyping = false;
    var lastTimeout;

    loginFormEl.addEventListener('submit', onLogin);
    messageFormEl.addEventListener('submit', onMessage);
    openSideBarEl.addEventListener('click', toggleSideBar);
    closeSideBarEl.addEventListener('click', toggleSideBar);
    messageInputEl.onkeydown = onTyping;

    socket.on('init', onInit);
    socket.on('joined', postNotice);
    socket.on('disconnect', postNotice);
    socket.on('chat message', postMessage);
    socket.on('started typing', onStartedTyping);
    socket.on('ended typing', onEndedTyping);

    function onLogin(evt) {
        evt.preventDefault();
        if (!init) return;
        username = loginInputEl.value.trim();
        if (!validateUsername())
            return;

        users.push(username);
        socket.emit('login', username);

        updateUserNum(users.length);
        var meEl = document.querySelector('.me');
        meEl.appendChild(document.createTextNode(username));
        meEl.style.display = 'block';

        loginWrapperEl.addEventListener('transitionend', function(evt) {
            loginWrapperEl.parentNode.removeChild(loginWrapperEl);
            loginWrapperEl.removeEventListener('transitionend', this);
        });
        loginWrapperEl.classList.add('login-wrapper--disabled');

        loginFormEl.removeEventListener('submit', onLogin);
    }

    function validateUsername() {
        if (!/^[a-z0-9_-]{3,15}$/i.test(username)) {
            if (usernameTakenEl.style.display === 'block')
                usernameTakenEl.style.display = 'none';
            if (usernameInvalidEl.style.display === 'none' || !usernameInvalidEl.style.display)
                usernameInvalidEl.style.display = 'block';
            return false;
        }
        if (usernameInvalidEl.style.display === 'block')
                usernameInvalidEl.style.display = 'none';
        var usernameTaken = false;
        for (var i = 0; i < users.length; i++) {
            if (users[i] === username) {
                usernameTaken = true;
                break;
            }
        }
        if (usernameTaken) {
            if (usernameTakenEl.style.display === 'none' || !usernameTakenEl.style.display)
                usernameTakenEl.style.display = 'block';
            return false;
        }
        if (usernameTakenEl.style.display === 'block')
            usernameTakenEl.style.display = 'none';
        return true;
    }

    function onInit(existingUsers) {
        init = true;
        users = existingUsers;
        updateUserNum(users.length);
        users.forEach(function(user) {
            var newUserListEl = document.createElement('div');
            newUserListEl.setAttribute('data-user', user);
            var newUsername = document.createTextNode(user);
            newUserListEl.appendChild(newUsername);
            usersOnlineEl.appendChild(newUserListEl);
        });
        spinnerEl.addEventListener('transitionend', function(evt) {
            spinnerEl.parentNode.removeChild(spinnerEl);
            spinnerEl.removeEventListener('transitionend', this);
        });
        spinnerEl.classList.add('spinner__container--disabled');
    }

    function onTyping(evt) {
        if (!isTyping) {
            isTyping = true;
            socket.emit('started typing', username);
        }
        if (lastTimeout)
            clearTimeout(lastTimeout);
        lastTimeout = setTimeout(function() {
            isTyping = false;
            socket.emit('ended typing', username);
        }, 2000);
    }

    function onStartedTyping(user) {
        var newBubbleEl = document.createElement('div');
        newBubbleEl.classList.add('bubble');
        newBubbleEl.classList.add('typing-indicator');
        newBubbleEl.innerHTML = '';
        var lastMessageEl = chatBoxEl.querySelectorAll('li');
        lastMessageEl = lastMessageEl[lastMessageEl.length - 1];
        if (lastMessageEl && lastMessageEl.getAttribute('data-user') === user) {
            lastMessageEl.appendChild(newBubbleEl);
            return;
        }
        var newMessageEl = document.createElement('li');
        newMessageEl.setAttribute('data-user', user);
        newMessageEl.setAttribute('data-typing', 'true');
        var newUserEl = document.createElement('span');
        newUserEl.classList.add('name');
        newUserEl.appendChild(document.createTextNode(user));
        newMessageEl.appendChild(newUserEl);
        newMessageEl.appendChild(newBubbleEl);
        chatBoxEl.appendChild(newMessageEl);
    }

    function onEndedTyping(user) {
        var messageEl = document.querySelectorAll('li[data-user="' + user + '"]');
        messageEl = messageEl[messageEl.length - 1];
        if (messageEl.hasAttribute('data-typing')) {
            chatBoxEl.removeChild(messageEl);
            return;
        }
        var typingEl = messageEl.lastChild;
        messageEl.removeChild(typingEl);
    }

    function onMessage(evt) {
        evt.preventDefault();
        var msg = messageInputEl.value.trim();
        if (!msg)
            return;
        if (isTyping) {
            clearTimeout(lastTimeout);
            isTyping = false;
            socket.emit('ended typing', username);
        }
        postMessage(msg, username, new Date());
        socket.emit('chat message', msg, username);
        messageInputEl.value = '';
    }

    function postNotice(user) {
        var index = users.indexOf(user);
        var hasLeft = index > -1;
        var newNoticeEl = document.createElement('li');
        newNoticeEl.classList.add('notice');
        var newUserEl = document.createElement('span');
        newUserEl.classList.add('user');
        newUserEl.appendChild(document.createTextNode(user));
        newNoticeEl.appendChild(newUserEl);
        newNoticeEl.appendChild(document.createTextNode(' ' + (hasLeft ? 'disconnected' : 'connected')));
        chatBoxEl.appendChild(newNoticeEl);
        scrollDown();
        if (hasLeft) {
            usersOnlineEl.removeChild(usersOnlineEl.querySelector('div[data-user="' + user + '"]'));
            users.splice(index, 1);
        } else {
            var newUserListEl = document.createElement('div');
            newUserListEl.setAttribute('data-user', user);
            newUserListEl.appendChild(document.createTextNode(user));
            usersOnlineEl.appendChild(newUserListEl);
            users.push(user);
        }
        updateUserNum(users.length);
    }

    function postMessage(msg, user) {
        var time = getTime();
        var newBubbleEl = document.createElement('div');
        newBubbleEl.classList.add('bubble');
        newBubbleEl.setAttribute('data-time', time);
        newBubbleEl.appendChild(document.createTextNode(msg));
        var newTimeEl;
        var lastMessageEl = chatBoxEl.querySelectorAll('li');
        lastMessageEl = lastMessageEl[lastMessageEl.length - 1];
        if (lastMessageEl && lastMessageEl.getAttribute('data-user') === user) {
            var lastBubbleEl = lastMessageEl.querySelectorAll('.bubble');
            lastBubbleEl = lastBubbleEl[lastBubbleEl.length - 1];
            if (lastBubbleEl.getAttribute('data-time') === time) {
                var brEl = document.createElement('br');
                lastMessageEl.insertBefore(brEl, lastBubbleEl.nextSibling);
                lastMessageEl.insertBefore(newBubbleEl, brEl.nextSibling);
            } else {
                newTimeEl = document.createElement('span');
                newTimeEl.classList.add('time');
                newTimeEl.appendChild(document.createTextNode(time));
                lastMessageEl.appendChild(newBubbleEl);
                lastMessageEl.appendChild(newTimeEl);
            }
        } else {
            var newMessageEl = document.createElement('li');
            newMessageEl.setAttribute('data-user', user);
            if (user === username)
                newMessageEl.classList.add('mine');
            else {
                var newUserEl = document.createElement('span');
                newUserEl.classList.add('name');
                newUserEl.appendChild(document.createTextNode(user));
                newMessageEl.appendChild(newUserEl);
            }
            newTimeEl = document.createElement('span');
            newTimeEl.classList.add('time');
            newTimeEl.appendChild(document.createTextNode(time));
            newMessageEl.appendChild(newBubbleEl);
            newMessageEl.appendChild(newTimeEl);
            chatBoxEl.appendChild(newMessageEl);
        }
        scrollDown();
    }

    function updateUserNum(newUserNum) {
        if (newUserNum === 0) {
            userNumEl[0].innerHTML = 'No Users Online';
            userNumEl[1].innerHTML = 'No Users Online';
            return;
        }
        userNumEl[0].innerHTML = 'View ' + newUserNum + ' User' + (users.length > 1 ? 's' : '');
        userNumEl[1].innerHTML = newUserNum + ' User' + (users.length > 1 ? 's' : '') + ' Online';
    }

    function toggleSideBar() {
        usersOnlineEl.classList.toggle('users-online--visible');
    }

};

function getTime() {
    var time = new Date();
    var hours = time.getHours();
    var minutes = time.getMinutes();
    if (hours < 10) hours = '0' + hours;
    if (minutes < 10) minutes = '0' + minutes;
    return hours + ':' + minutes;
}

function scrollDown() {
    window.scrollTo(0, document.body.scrollHeight);
}
<commit_msg>Typing notification bug fix<commit_after>window.onload = function() {

    var socket = io();
    var init = false;
    var spinnerEl = document.querySelector('.spinner__container');
    var loginWrapperEl = document.querySelector('.login-wrapper');
    var loginFormEl = document.querySelector('.login');
    var loginInputEl = document.querySelector('.login-input');
    var usernameInvalidEl = document.querySelector('.username-invalid');
    var usernameTakenEl = document.querySelector('.username-taken');
    var usersOnlineEl = document.querySelector('.users-online');
    var userNumEl = document.querySelectorAll('.user-no');
    var chatBoxEl = document.querySelector('.chatbox');
    var messageFormEl = document.querySelector('.send-msg form');
    var messageInputEl = document.querySelector('.msg');
    var openSideBarEl = document.querySelector('.pull');
    var closeSideBarEl = document.querySelector('.close');
    var username;
    var users;
    var isTyping = false;
    var lastTimeout;

    loginFormEl.addEventListener('submit', onLogin);
    messageFormEl.addEventListener('submit', onMessage);
    openSideBarEl.addEventListener('click', toggleSideBar);
    closeSideBarEl.addEventListener('click', toggleSideBar);
    messageInputEl.onkeydown = onTyping;

    socket.on('init', onInit);
    socket.on('joined', postNotice);
    socket.on('disconnect', postNotice);
    socket.on('chat message', postMessage);
    socket.on('started typing', onStartedTyping);
    socket.on('ended typing', onEndedTyping);

    function onLogin(evt) {
        evt.preventDefault();
        if (!init) return;
        username = loginInputEl.value.trim();
        if (!validateUsername())
            return;

        users.push(username);
        socket.emit('login', username);

        updateUserNum(users.length);
        var meEl = document.querySelector('.me');
        meEl.appendChild(document.createTextNode(username));
        meEl.style.display = 'block';

        loginWrapperEl.addEventListener('transitionend', function(evt) {
            loginWrapperEl.parentNode.removeChild(loginWrapperEl);
            loginWrapperEl.removeEventListener('transitionend', this);
        });
        loginWrapperEl.classList.add('login-wrapper--disabled');

        loginFormEl.removeEventListener('submit', onLogin);
    }

    function validateUsername() {
        if (!/^[a-z0-9_-]{3,15}$/i.test(username)) {
            if (usernameTakenEl.style.display === 'block')
                usernameTakenEl.style.display = 'none';
            if (usernameInvalidEl.style.display === 'none' || !usernameInvalidEl.style.display)
                usernameInvalidEl.style.display = 'block';
            return false;
        }
        if (usernameInvalidEl.style.display === 'block')
                usernameInvalidEl.style.display = 'none';
        var usernameTaken = false;
        for (var i = 0; i < users.length; i++) {
            if (users[i] === username) {
                usernameTaken = true;
                break;
            }
        }
        if (usernameTaken) {
            if (usernameTakenEl.style.display === 'none' || !usernameTakenEl.style.display)
                usernameTakenEl.style.display = 'block';
            return false;
        }
        if (usernameTakenEl.style.display === 'block')
            usernameTakenEl.style.display = 'none';
        return true;
    }

    function onInit(existingUsers) {
        init = true;
        users = existingUsers;
        updateUserNum(users.length);
        users.forEach(function(user) {
            var newUserListEl = document.createElement('div');
            newUserListEl.setAttribute('data-user', user);
            var newUsername = document.createTextNode(user);
            newUserListEl.appendChild(newUsername);
            usersOnlineEl.appendChild(newUserListEl);
        });
        spinnerEl.addEventListener('transitionend', function(evt) {
            spinnerEl.parentNode.removeChild(spinnerEl);
            spinnerEl.removeEventListener('transitionend', this);
        });
        spinnerEl.classList.add('spinner__container--disabled');
    }

    function onTyping(evt) {
        if (!messageInputEl.value) return;
        if (!isTyping) {
            isTyping = true;
            socket.emit('started typing', username);
        }
        if (lastTimeout)
            clearTimeout(lastTimeout);
        lastTimeout = setTimeout(function() {
            isTyping = false;
            socket.emit('ended typing', username);
        }, 2000);
    }

    function onStartedTyping(user) {
        var newBubbleEl = document.createElement('div');
        newBubbleEl.classList.add('bubble');
        newBubbleEl.classList.add('typing-indicator');
        newBubbleEl.innerHTML = '';
        var lastMessageEl = chatBoxEl.querySelectorAll('li');
        lastMessageEl = lastMessageEl[lastMessageEl.length - 1];
        if (lastMessageEl && lastMessageEl.getAttribute('data-user') === user) {
            lastMessageEl.appendChild(newBubbleEl);
            return;
        }
        var newMessageEl = document.createElement('li');
        newMessageEl.setAttribute('data-user', user);
        newMessageEl.setAttribute('data-typing', 'true');
        var newUserEl = document.createElement('span');
        newUserEl.classList.add('name');
        newUserEl.appendChild(document.createTextNode(user));
        newMessageEl.appendChild(newUserEl);
        newMessageEl.appendChild(newBubbleEl);
        chatBoxEl.appendChild(newMessageEl);
    }

    function onEndedTyping(user) {
        var messageEl = document.querySelectorAll('li[data-user="' + user + '"]');
        messageEl = messageEl[messageEl.length - 1];
        if (messageEl.hasAttribute('data-typing')) {
            chatBoxEl.removeChild(messageEl);
            return;
        }
        var typingEl = messageEl.lastChild;
        messageEl.removeChild(typingEl);
    }

    function onMessage(evt) {
        evt.preventDefault();
        var msg = messageInputEl.value.trim();
        if (!msg)
            return;
        if (isTyping) {
            clearTimeout(lastTimeout);
            isTyping = false;
            socket.emit('ended typing', username);
        }
        postMessage(msg, username, new Date());
        socket.emit('chat message', msg, username);
        messageInputEl.value = '';
    }

    function postNotice(user) {
        var index = users.indexOf(user);
        var hasLeft = index > -1;
        var newNoticeEl = document.createElement('li');
        newNoticeEl.classList.add('notice');
        var newUserEl = document.createElement('span');
        newUserEl.classList.add('user');
        newUserEl.appendChild(document.createTextNode(user));
        newNoticeEl.appendChild(newUserEl);
        newNoticeEl.appendChild(document.createTextNode(' ' + (hasLeft ? 'disconnected' : 'connected')));
        chatBoxEl.appendChild(newNoticeEl);
        scrollDown();
        if (hasLeft) {
            usersOnlineEl.removeChild(usersOnlineEl.querySelector('div[data-user="' + user + '"]'));
            users.splice(index, 1);
        } else {
            var newUserListEl = document.createElement('div');
            newUserListEl.setAttribute('data-user', user);
            newUserListEl.appendChild(document.createTextNode(user));
            usersOnlineEl.appendChild(newUserListEl);
            users.push(user);
        }
        updateUserNum(users.length);
    }

    function postMessage(msg, user) {
        var time = getTime();
        var newBubbleEl = document.createElement('div');
        newBubbleEl.classList.add('bubble');
        newBubbleEl.setAttribute('data-time', time);
        newBubbleEl.appendChild(document.createTextNode(msg));
        var newTimeEl;
        var lastMessageEl = chatBoxEl.querySelectorAll('li');
        lastMessageEl = lastMessageEl[lastMessageEl.length - 1];
        if (lastMessageEl && lastMessageEl.getAttribute('data-user') === user) {
            var lastBubbleEl = lastMessageEl.querySelectorAll('.bubble');
            lastBubbleEl = lastBubbleEl[lastBubbleEl.length - 1];
            if (lastBubbleEl.getAttribute('data-time') === time) {
                var brEl = document.createElement('br');
                lastMessageEl.insertBefore(brEl, lastBubbleEl.nextSibling);
                lastMessageEl.insertBefore(newBubbleEl, brEl.nextSibling);
            } else {
                newTimeEl = document.createElement('span');
                newTimeEl.classList.add('time');
                newTimeEl.appendChild(document.createTextNode(time));
                lastMessageEl.appendChild(newBubbleEl);
                lastMessageEl.appendChild(newTimeEl);
            }
        } else {
            var newMessageEl = document.createElement('li');
            newMessageEl.setAttribute('data-user', user);
            if (user === username)
                newMessageEl.classList.add('mine');
            else {
                var newUserEl = document.createElement('span');
                newUserEl.classList.add('name');
                newUserEl.appendChild(document.createTextNode(user));
                newMessageEl.appendChild(newUserEl);
            }
            newTimeEl = document.createElement('span');
            newTimeEl.classList.add('time');
            newTimeEl.appendChild(document.createTextNode(time));
            newMessageEl.appendChild(newBubbleEl);
            newMessageEl.appendChild(newTimeEl);
            chatBoxEl.appendChild(newMessageEl);
        }
        scrollDown();
    }

    function updateUserNum(newUserNum) {
        if (newUserNum === 0) {
            userNumEl[0].innerHTML = 'No Users Online';
            userNumEl[1].innerHTML = 'No Users Online';
            return;
        }
        userNumEl[0].innerHTML = 'View ' + newUserNum + ' User' + (users.length > 1 ? 's' : '');
        userNumEl[1].innerHTML = newUserNum + ' User' + (users.length > 1 ? 's' : '') + ' Online';
    }

    function toggleSideBar() {
        usersOnlineEl.classList.toggle('users-online--visible');
    }

};

function getTime() {
    var time = new Date();
    var hours = time.getHours();
    var minutes = time.getMinutes();
    if (hours < 10) hours = '0' + hours;
    if (minutes < 10) minutes = '0' + minutes;
    return hours + ':' + minutes;
}

function scrollDown() {
    window.scrollTo(0, document.body.scrollHeight);
}
<|endoftext|>


<commit_before>(function() {
    var env = angular.module("env", []);

    env.constant("version",'1.0.1-RC1');
    env.constant("env",'stage');
    env.constant("color",'success');

})();
<commit_msg>change env<commit_after>(function() {
    var env = angular.module("env", []);

    env.constant("version",'1.0.1-RC1');
    env.constant("env",'1.0.1-RC');
    env.constant("color",'danger');

})();
<|endoftext|>


<commit_before>/**
    Activity base class
**/
'use strict';

var ko = require('knockout');
var NavAction = require('../viewmodels/NavAction');
var NavBar = require('../viewmodels/NavBar');

require('../utils/Function.prototype._inherits');
var showConfirm = require('../modals/confirm').show;
var insertCss = require('insert-css');

/**
    Activity class definition
**/
function Activity($activity, app) {

    this.$activity = $activity;
    this.app = app;

    // Default access level: anyone
    // Activities can use the enumeration: this.app.UserType
    this.accessLevel = null;

    // By default, reset scroll to top on activity.show
    this.resetScroll = true;

    // Object to hold the options passed on 'show' as a result
    // of a request from another activity
    this.requestData = null;

    // Default navBar object.
    this.navBar = new NavBar({
        title: null, // null for logo
        leftAction: null,
        rightAction: null
    });

    /**
     * When the activity is being shown (just at ending of method 'show'),
     * false for hidden (restored at ending of method 'hide')
     * @member {KnockoutObservable<boolean>}
     */
    this.isShown = ko.observable(false);

    /**
     * Observable property to allow each activity to set-up a title.
     * This first value is a placeholder, can be replaced by a constant string
     * or a computed at the activity constructor (before first 'show').
     * @member {KnockoutObservable<string>|string}
     */
    this.title = ko.observable('');

    // Knockout binding of viewModel delayed to first show
    // to avoid problems with subclasses replacing the viewModel property
}

module.exports = Activity;

/**
 * The text to attach as suffix to the instance title as part of the
 * window/tab title.
 * @member {string}
 * @readonly
 * @static
 */
Activity.TRAILING_WINDOW_TITLE = ' - Loconomics';

/**
 * Applies a new title to the window.
 * This utility is fine to be used as a callback.
 * @param {string} title New title to set in the window (the common trailing
 * text will be appended)
 * @static
 */
Activity.applyTitle = function (title) {
    // A specific title per activity is being MANDATORY, so we throw
    // a console error to make devs aware of that.
    if (!title) {
        console.error('A title for the activity is mandatory, empty was given.');
    }
    document.title = (title || '') + Activity.TRAILING_WINDOW_TITLE;
};

/**
    Set-up visualization of the view with the given options/state,
    with a reset of current state.
    Must be executed every time the activity is put in the current view.
**/
Activity.prototype.show = function show(options) {
    /* eslint complexity:"off" */
    //console.log('Activity show', this.constructor.name);
    if (!this.__bindingDone) {
        // Default viewModel: the Activity instance (for more simple scenarios)
        this.viewModel = this.viewModel || this;
        // Share title field with viewModel
        if (!this.viewModel.title) {
            this.viewModel.title = this.title;
        }
        if (!this.viewModel.isShown) {
            this.viewModel.isShown = this.isShown;
        }
        // Set-up dynamic update of the title on observable value change
        if (ko.isObservable(this.title)) {
            this.registerHandler({
                target: this.title,
                handler: Activity.applyTitle
            });
        }
        // A view model and bindings being applied is ever required
        // even on Activities without need for a view model, since
        // the use of components and templates, or any other data-bind
        // syntax, requires to be in a context with binding enabled:
        ko.applyBindings(this.viewModel, this.$activity.get(0));

        this.__bindingDone = true;

        // Additionally to binding, inject styles is needed too only first time
        /**
         * Enable the own activity style CSS, if there is someone.
         * @private
         */
        this.__styleElement = this.constructor.style && insertCss(this.constructor.style, {
            container: this.$activity.get(0)
        });
    }

    options = options || {};
    this.requestData = options;

    // Apply current title
    Activity.applyTitle(ko.unwrap(this.title));

    // Enable registered handlers
    // Validation of each settings object is performed
    // on registered, avoided here.
    if (this._handlers &&
        this._handlersAreConnected !== true) {
        this._handlers.forEach(function(settings) {
            // Check if is an observable subscription
            if (!settings.event && settings.target.subscribe) {
                var subscription = settings.target.subscribe(settings.handler);
                // Observables has not a 'unsubscribe' function,
                // they return an object that must be 'disposed'.
                // Saving that with settings to allow 'unsubscribe' later.
                settings._subscription = subscription;

                // Inmediate execution: if current observable value is different
                // than previous one, execute the handler:
                // (this avoid that a changed state get omitted because happened
                // when subscription was off; it means a first time execution too).
                // NOTE: 'undefined' value on observable may cause this to fall
                if (settings._latestSubscribedValue !== settings.target()) {
                    settings.handler.call(settings.target, settings.target());
                }
            }
            else if (settings.selector) {
                settings.target.on(settings.event, settings.selector, settings.handler);
            }
            else if (settings.target.on) {
                settings.target.on(settings.event, settings.handler);
            }
            else {
                console.error('Activity.show: Bad registered handler', settings);
            }
        });
        // To avoid double connections:
        // NOTE: may happen that 'show' gets called several times without a 'hide'
        // in between, because 'show' acts as a refresher right now even from segment
        // changes from the same activity.
        this._handlersAreConnected = true;
    }

    // Scroll to top immediately, if wanted by the activity (defaults to true):
    if (this.resetScroll)
        this.$activity.scrollTop(0);

    this.isShown(true);
};

/**
    Perform tasks to stop anything running or stop handlers from listening.
    Must be executed every time the activity is hidden/removed
    from the current view.
**/
Activity.prototype.hide = function hide() {
    // Disable registered handlers
    if (this._handlers) {
        this._handlers.forEach(function(settings) {
            // Check if is an observable subscription
            if (settings._subscription) {
                settings._subscription.dispose();
                // Save latest observable value to make a comparision
                // next time is enabled to ensure is executed if there was
                // a change while disabled:
                settings._latestSubscribedValue = settings.target();
            }
            else if (settings.target.off) {
                if (settings.selector)
                    settings.target.off(settings.event, settings.selector, settings.handler);
                else
                    settings.target.off(settings.event, settings.handler);
            }
            else if (settings.target.removeListener) {
                settings.target.removeListener(settings.event, settings.handler);
            }
            else {
                console.error('Activity.hide: Bad registered handler', settings);
            }
        });

        this._handlersAreConnected = false;
    }

    this.isShown(false);
};

/**
 * Dispose any ressources that cannot be done automatically.
 *
 * IMPORTANT: Currently, this is only executed on activities that are removed
 * from DOM and 'singleton' instance; that's applied only to new folder-based
 * bundle activities, aplying the new activity lifecycle #457.
 */
Activity.prototype.dispose = function() {
    if (this.__styleElement) {
        this.__styleElement.parentNode.removeChild(this.__styleElement);
    }
};

/**
    Register a handler that acts on an event or subscription notification,
    that will be enabled on Activity.show and disabled on Activity.hide.

    @param settings:object {
        target: jQuery, EventEmitter, Knockout.observable. Required
        event: string. Event name (can have namespaces, several events allowed). Its required except when the target is an observable, there must
            be omitted.
        handler: Function. Required,
        selector: string. Optional. For jQuery events only, passed as the
            selector for delegated handlers.
    }
**/
Activity.prototype.registerHandler = function registerHandler(settings) {

    if (!settings)
        throw new Error('Register require a settings object');

    if (!settings.target || (!settings.target.on && !settings.target.subscribe))
        throw new Error('Target is null or not a jQuery, EventEmmiter or Observable object');

    if (typeof(settings.handler) !== 'function') {
        throw new Error('Handler must be a function.');
    }

    if (!settings.event && !settings.target.subscribe) {
        throw new Error('Event is null; it\'s required for non observable objects');
    }

    this._handlers = this._handlers || [];

    this._handlers.push(settings);
};

/**
    Static utilities
**/
// For commodity, common classes are exposed as static properties
Activity.NavBar = NavBar;
Activity.NavAction = NavAction;

// Quick creation of common types of NavBar
Activity.createSectionNavBar = function createSectionNavBar(title) {
    return new NavBar({
        title: title,
        leftAction: NavAction.menuIn
        // NOTE: Removed as of #726 until a new menu for it is implemented as of #191 child issues.
        //rightAction: NavAction.menuNewItem
    });
};

Activity.createSubsectionNavBar = function createSubsectionNavBar(title, options) {

    options = options || {};

    var goBackOptions = {
        text: title,
        isTitle: true
    };

    if (options.backLink) {
        goBackOptions.link = options.backLink;
        goBackOptions.isShell = false;
    }

    var helpLink = options.helpLink;
    if (helpLink) {
        var del = helpLink.indexOf('?') > -1 ? '&' : '?';
        helpLink += del + 'mustReturn=true&returnText=' + encodeURIComponent(title);
    }

    var rightOptions = helpLink ?
        NavAction.goHelpIndex.model.clone({
            link: helpLink,
            text: "Help"
        }) :
        NavAction.goHelpIndex;

    return new NavBar({
        title: '', // No title
        leftAction: NavAction.goBack.model.clone(goBackOptions),
        rightAction: rightOptions
    });
};

Activity.prototype.createCancelAction = function createCancelAction(cancelLink, state) {

    var app = this.app;

    var action = new NavAction({
        link: cancelLink,
        text: 'Cancel',
        handler: function(event) {
            var link = this.link();
            var eoptions = event && event.options || {};

            var goLink = function() {
                if (link)
                    app.shell.go(link, state);
                else
                    app.shell.goBack(state);
            };

            // A silentMode passed to the event requires
            // avoid the modal (used when executing a saving task for example)
            if (eoptions.silentMode) {
                goLink();
            }
            else {
                // TODO L18N
                showConfirm({
                    title: 'Cancel',
                    message: 'Are you sure?',
                    yes: 'Yes',
                    no: 'No'
                })
                .then(function() {
                    // Confirmed cancellation:
                    goLink();
                });
            }
        }
    });

    return action;
};

Activity.prototype.convertToCancelAction = function convertToCancelAction(actionModel, cancelLink) {
    var cancel = this.createCancelAction(cancelLink);
    actionModel.model.updateWith(cancel);
    // DUDE: handler is cpied by updateWith?
    actionModel.handler(cancel.handler());
};

/**
    Singleton helper.
    With the name parameter, a named instance can be created allowing
    several instances per class not being purely 'singleton', more like
    a factory where singletons per name are created.
**/
var singletonInstances = {};
var createSingleton = function createSingleton(ActivityClass, $activity, app, name) {

    var key = ActivityClass.name + '::' + (name || '');

    if (singletonInstances[key] instanceof ActivityClass) {
        return singletonInstances[key];
    }
    else {
        var s = new ActivityClass($activity, app);
        singletonInstances[key] = s;
        return s;
    }
};
// Example of use
//exports.init = createSingleton.bind(null, ActivityClass);

// #457 workaround:
// IMPORTANT: Workaround used in conjuntion with shell/loader
// regarding a new activity lifecycle (#457) that disposes the instance
// and html-DOM when the activity stop being used.
// This new lifecycle applies only to folder-based activities, bundle individually
// with it's copy of html and css. Most activities still are *legacy*, and
// this is not used on that case.
// The new lifecycle allows to make safer assumptions on how the code runs
// (specially working with components) and result is a more clear code and less
// memory wasted.
//
// This method just removes the activity 'singleton' instance created, so is
// recreated next time, while DOM removal is at shell/loader.
Activity.deleteSingletonInstance = function(ActivityClass, name) {
    var key = ActivityClass.name + '::' + (name || '');
    if (singletonInstances[key] instanceof ActivityClass) {
        singletonInstances[key].dispose();
        delete singletonInstances[key];
    }
};

/**
    Static method extends to help inheritance.
    Additionally, it adds a static init method ready for the new class
    that generates/retrieves the singleton.
    NOTE: 'extends' cannot be used, reserved keyword, breaks some browsers and some future may broke too
    (so just removed the 's').
**/
Activity.extend = function extendsActivity(ClassFn) {

    ClassFn._inherits(Activity);

    ClassFn.init = function($activity, app, name) {
        return createSingleton(ClassFn, $activity, app, name);
    };

    return ClassFn;
};

Activity.init = function($activity, app, name) {
    return createSingleton(this, $activity, app, name);
};

/**
 * Must be implemented by derived classes
 * @static @member {string}
 */
Object.defineProperty(Activity, 'template', {
    get: function() { throw new Error('No activity template defined'); },
    enumerable: true,
    configurable: true
});

/**
 * CSS text defining the style created for the activity.
 * @static @member {string}
 */
Object.defineProperty(Activity, 'style', {
    get: function() { return undefined; },
    enumerable: true,
    configurable: true
});
<commit_msg>Extended Activity class with diposables logic for on-hide, and the observeChanges and subscribeTo methods as seen in komponents (but related to an Activity cycle, that happens show-hide rather than constructor-dispose because of need to support legacy lifecycle)<commit_after>/**
    Activity base class
**/
'use strict';

var ko = require('knockout');
var NavAction = require('../viewmodels/NavAction');
var NavBar = require('../viewmodels/NavBar');

require('../utils/Function.prototype._inherits');
var showConfirm = require('../modals/confirm').show;
var insertCss = require('insert-css');

/**
    Activity class definition
**/
function Activity($activity, app) {

    this.$activity = $activity;
    this.app = app;

    // Default access level: anyone
    // Activities can use the enumeration: this.app.UserType
    this.accessLevel = null;

    // By default, reset scroll to top on activity.show
    this.resetScroll = true;

    // Object to hold the options passed on 'show' as a result
    // of a request from another activity
    this.requestData = null;

    // Default navBar object.
    this.navBar = new NavBar({
        title: null, // null for logo
        leftAction: null,
        rightAction: null
    });

    /**
     * When the activity is being shown (just at ending of method 'show'),
     * false for hidden (restored at ending of method 'hide')
     * @member {KnockoutObservable<boolean>}
     */
    this.isShown = ko.observable(false);

    /**
     * Observable property to allow each activity to set-up a title.
     * This first value is a placeholder, can be replaced by a constant string
     * or a computed at the activity constructor (before first 'show').
     * @member {KnockoutObservable<string>|string}
     */
    this.title = ko.observable('');

    /**
     * Holds a list of objects with a 'dispose' method or functions that
     * need to be called when disposing the activity on hidding, freeing up ressources
     * that don't do it automatically (see `dispose` method).
     * @member {Array<(Function,IDisposable)>}
     * @private
     */
    this._onHideDisposables = [];

    // Knockout binding of viewModel delayed to first show
    // to avoid problems with subclasses replacing the viewModel property
}

module.exports = Activity;

/**
 * The text to attach as suffix to the instance title as part of the
 * window/tab title.
 * @member {string}
 * @readonly
 * @static
 */
Activity.TRAILING_WINDOW_TITLE = ' - Loconomics';

/**
 * Applies a new title to the window.
 * This utility is fine to be used as a callback.
 * @param {string} title New title to set in the window (the common trailing
 * text will be appended)
 * @static
 */
Activity.applyTitle = function (title) {
    // A specific title per activity is being MANDATORY, so we throw
    // a console error to make devs aware of that.
    if (!title) {
        console.error('A title for the activity is mandatory, empty was given.');
    }
    document.title = (title || '') + Activity.TRAILING_WINDOW_TITLE;
};

/**
    Set-up visualization of the view with the given options/state,
    with a reset of current state.
    Must be executed every time the activity is put in the current view.
**/
Activity.prototype.show = function show(options) {
    /* eslint complexity:"off" */
    //console.log('Activity show', this.constructor.name);
    if (!this.__bindingDone) {
        // Default viewModel: the Activity instance (for more simple scenarios)
        this.viewModel = this.viewModel || this;
        // Share title field with viewModel
        if (!this.viewModel.title) {
            this.viewModel.title = this.title;
        }
        if (!this.viewModel.isShown) {
            this.viewModel.isShown = this.isShown;
        }
        // Set-up dynamic update of the title on observable value change
        if (ko.isObservable(this.title)) {
            this.registerHandler({
                target: this.title,
                handler: Activity.applyTitle
            });
        }
        // A view model and bindings being applied is ever required
        // even on Activities without need for a view model, since
        // the use of components and templates, or any other data-bind
        // syntax, requires to be in a context with binding enabled:
        ko.applyBindings(this.viewModel, this.$activity.get(0));

        this.__bindingDone = true;

        // Additionally to binding, inject styles is needed too only first time
        /**
         * Enable the own activity style CSS, if there is someone.
         * @private
         */
        this.__styleElement = this.constructor.style && insertCss(this.constructor.style, {
            container: this.$activity.get(0)
        });
    }

    options = options || {};
    this.requestData = options;

    // Apply current title
    Activity.applyTitle(ko.unwrap(this.title));

    // Enable registered handlers
    // Validation of each settings object is performed
    // on registered, avoided here.
    if (this._handlers &&
        this._handlersAreConnected !== true) {
        this._handlers.forEach(function(settings) {
            // Check if is an observable subscription
            if (!settings.event && settings.target.subscribe) {
                var subscription = settings.target.subscribe(settings.handler);
                // Observables has not a 'unsubscribe' function,
                // they return an object that must be 'disposed'.
                // Saving that with settings to allow 'unsubscribe' later.
                settings._subscription = subscription;

                // Inmediate execution: if current observable value is different
                // than previous one, execute the handler:
                // (this avoid that a changed state get omitted because happened
                // when subscription was off; it means a first time execution too).
                // NOTE: 'undefined' value on observable may cause this to fall
                if (settings._latestSubscribedValue !== settings.target()) {
                    settings.handler.call(settings.target, settings.target());
                }
            }
            else if (settings.selector) {
                settings.target.on(settings.event, settings.selector, settings.handler);
            }
            else if (settings.target.on) {
                settings.target.on(settings.event, settings.handler);
            }
            else {
                console.error('Activity.show: Bad registered handler', settings);
            }
        });
        // To avoid double connections:
        // NOTE: may happen that 'show' gets called several times without a 'hide'
        // in between, because 'show' acts as a refresher right now even from segment
        // changes from the same activity.
        this._handlersAreConnected = true;
    }

    // Scroll to top immediately, if wanted by the activity (defaults to true):
    if (this.resetScroll)
        this.$activity.scrollTop(0);

    this.isShown(true);
};

/**
    Perform tasks to stop anything running or stop handlers from listening.
    Must be executed every time the activity is hidden/removed
    from the current view.
**/
Activity.prototype.hide = function hide() {
    // Disable registered handlers
    if (this._handlers) {
        this._handlers.forEach(function(settings) {
            // Check if is an observable subscription
            if (settings._subscription) {
                settings._subscription.dispose();
                // Save latest observable value to make a comparision
                // next time is enabled to ensure is executed if there was
                // a change while disabled:
                settings._latestSubscribedValue = settings.target();
            }
            else if (settings.target.off) {
                if (settings.selector)
                    settings.target.off(settings.event, settings.selector, settings.handler);
                else
                    settings.target.off(settings.event, settings.handler);
            }
            else if (settings.target.removeListener) {
                settings.target.removeListener(settings.event, settings.handler);
            }
            else {
                console.error('Activity.hide: Bad registered handler', settings);
            }
        });

        this._handlersAreConnected = false;
    }

    this.isShown(false);
};

/**
 * Dispose any ressources that cannot be done automatically.
 *
 * IMPORTANT: Currently, this is only executed on activities that are removed
 * from DOM and 'singleton' instance; that's applied only to new folder-based
 * bundle activities, aplying the new activity lifecycle #457.
 */
Activity.prototype.dispose = function() {
    if (this.__styleElement) {
        this.__styleElement.parentNode.removeChild(this.__styleElement);
    }
};

/**
    Register a handler that acts on an event or subscription notification,
    that will be enabled on Activity.show and disabled on Activity.hide.

    @param settings:object {
        target: jQuery, EventEmitter, Knockout.observable. Required
        event: string. Event name (can have namespaces, several events allowed). Its required except when the target is an observable, there must
            be omitted.
        handler: Function. Required,
        selector: string. Optional. For jQuery events only, passed as the
            selector for delegated handlers.
    }
**/
Activity.prototype.registerHandler = function registerHandler(settings) {

    if (!settings)
        throw new Error('Register require a settings object');

    if (!settings.target || (!settings.target.on && !settings.target.subscribe))
        throw new Error('Target is null or not a jQuery, EventEmmiter or Observable object');

    if (typeof(settings.handler) !== 'function') {
        throw new Error('Handler must be a function.');
    }

    if (!settings.event && !settings.target.subscribe) {
        throw new Error('Event is null; it\'s required for non observable objects');
    }

    this._handlers = this._handlers || [];

    this._handlers.push(settings);
};

/**
 * It creates a Knockout Computed with the given function that will be
 * automatically disposed at the end of a cycle of the activity (on hide).
 * The context of the function will be the activity instance.
 * @param {function} task Function that will read some observables and perform
 * a task with their values, repeating at every data change
 * @returns {KnockoutComputed} The computed generated, that can be extended
 * as usual.
 */
Activity.prototype.observeChanges = function(task) {
    var computed = ko.computed(task, this);
    this._onHideDisposables.push(computed);
    return computed;
};

/**
 * It subscribes to the given object and automatically disposes the
 * subscription at the end of a cycle of the activity (on hide).
 * @param {ISubscribable} subscribable An object implementing the `subscribe`
 * method, that lets to subscribe to notifications received in the callback
 * and lets `dispose` that subscription to prevent memory leaks.
 * Common used objects here are SingleEvents and Knockout Observables (as alternative to
 * observeChanges when just one observable is watched and don't want a first
 * time execution of the callback when connecting).
 * @param {function} callback Function executed every time the subscribable
 * notifies including data in the parameters.
 * @returns {IDisposable} Returns back the subscription that allows disposal.
 * Remember that manual disposal is not needed, except disposal before
 * the end of cycle of the activity is wanted.
 */
Activity.prototype.subscribeTo = function(subscribable, callback) {
    if (!subscribable || typeof(subscribable.subscribe) !== 'function') {
        throw new Error('Given object is not subscribable', subscribable);
    }
    var disposable = subscribable.subscribe(callback);
    if (!disposable || typeof(disposable.dispose) !== 'function') {
        throw new Error('Given subscribable does not allows disposal', subscribable, disposable);
    }
    this._onHideDisposables.push(disposable);
    return disposable;
};

/**
    Static utilities
**/
// For commodity, common classes are exposed as static properties
Activity.NavBar = NavBar;
Activity.NavAction = NavAction;

// Quick creation of common types of NavBar
Activity.createSectionNavBar = function createSectionNavBar(title) {
    return new NavBar({
        title: title,
        leftAction: NavAction.menuIn
        // NOTE: Removed as of #726 until a new menu for it is implemented as of #191 child issues.
        //rightAction: NavAction.menuNewItem
    });
};

Activity.createSubsectionNavBar = function createSubsectionNavBar(title, options) {

    options = options || {};

    var goBackOptions = {
        text: title,
        isTitle: true
    };

    if (options.backLink) {
        goBackOptions.link = options.backLink;
        goBackOptions.isShell = false;
    }

    var helpLink = options.helpLink;
    if (helpLink) {
        var del = helpLink.indexOf('?') > -1 ? '&' : '?';
        helpLink += del + 'mustReturn=true&returnText=' + encodeURIComponent(title);
    }

    var rightOptions = helpLink ?
        NavAction.goHelpIndex.model.clone({
            link: helpLink,
            text: "Help"
        }) :
        NavAction.goHelpIndex;

    return new NavBar({
        title: '', // No title
        leftAction: NavAction.goBack.model.clone(goBackOptions),
        rightAction: rightOptions
    });
};

Activity.prototype.createCancelAction = function createCancelAction(cancelLink, state) {

    var app = this.app;

    var action = new NavAction({
        link: cancelLink,
        text: 'Cancel',
        handler: function(event) {
            var link = this.link();
            var eoptions = event && event.options || {};

            var goLink = function() {
                if (link)
                    app.shell.go(link, state);
                else
                    app.shell.goBack(state);
            };

            // A silentMode passed to the event requires
            // avoid the modal (used when executing a saving task for example)
            if (eoptions.silentMode) {
                goLink();
            }
            else {
                // TODO L18N
                showConfirm({
                    title: 'Cancel',
                    message: 'Are you sure?',
                    yes: 'Yes',
                    no: 'No'
                })
                .then(function() {
                    // Confirmed cancellation:
                    goLink();
                });
            }
        }
    });

    return action;
};

Activity.prototype.convertToCancelAction = function convertToCancelAction(actionModel, cancelLink) {
    var cancel = this.createCancelAction(cancelLink);
    actionModel.model.updateWith(cancel);
    // DUDE: handler is cpied by updateWith?
    actionModel.handler(cancel.handler());
};

/**
    Singleton helper.
    With the name parameter, a named instance can be created allowing
    several instances per class not being purely 'singleton', more like
    a factory where singletons per name are created.
**/
var singletonInstances = {};
var createSingleton = function createSingleton(ActivityClass, $activity, app, name) {

    var key = ActivityClass.name + '::' + (name || '');

    if (singletonInstances[key] instanceof ActivityClass) {
        return singletonInstances[key];
    }
    else {
        var s = new ActivityClass($activity, app);
        singletonInstances[key] = s;
        return s;
    }
};
// Example of use
//exports.init = createSingleton.bind(null, ActivityClass);

// #457 workaround:
// IMPORTANT: Workaround used in conjuntion with shell/loader
// regarding a new activity lifecycle (#457) that disposes the instance
// and html-DOM when the activity stop being used.
// This new lifecycle applies only to folder-based activities, bundle individually
// with it's copy of html and css. Most activities still are *legacy*, and
// this is not used on that case.
// The new lifecycle allows to make safer assumptions on how the code runs
// (specially working with components) and result is a more clear code and less
// memory wasted.
//
// This method just removes the activity 'singleton' instance created, so is
// recreated next time, while DOM removal is at shell/loader.
Activity.deleteSingletonInstance = function(ActivityClass, name) {
    var key = ActivityClass.name + '::' + (name || '');
    if (singletonInstances[key] instanceof ActivityClass) {
        singletonInstances[key].dispose();
        delete singletonInstances[key];
    }
};

/**
    Static method extends to help inheritance.
    Additionally, it adds a static init method ready for the new class
    that generates/retrieves the singleton.
    NOTE: 'extends' cannot be used, reserved keyword, breaks some browsers and some future may broke too
    (so just removed the 's').
**/
Activity.extend = function extendsActivity(ClassFn) {

    ClassFn._inherits(Activity);

    ClassFn.init = function($activity, app, name) {
        return createSingleton(ClassFn, $activity, app, name);
    };

    return ClassFn;
};

Activity.init = function($activity, app, name) {
    return createSingleton(this, $activity, app, name);
};

/**
 * Must be implemented by derived classes
 * @static @member {string}
 */
Object.defineProperty(Activity, 'template', {
    get: function() { throw new Error('No activity template defined'); },
    enumerable: true,
    configurable: true
});

/**
 * CSS text defining the style created for the activity.
 * @static @member {string}
 */
Object.defineProperty(Activity, 'style', {
    get: function() { return undefined; },
    enumerable: true,
    configurable: true
});
<|endoftext|>


<commit_before>var aliasRgxp = /__(.*?)__/;

/**
 * Добавить новую директиву в пространство имён Snakeskin
 *
 * @param {string} name - название добавляемой директивы
 * @param {Object} params - дополнительные параметры
 *
 * @param {?boolean=} [params.alias=false] - если true, то директива считается псевдонимом
 *     (только для приватных директив)
 *
 * @param {?boolean=} [params.text=false] - если true, то декларируется,
 *     что директива выводится как текст
 *
 * @param {?string=} [params.placement] - если параметр задан, то делается проверка
 *     где именно размещена директива ('global', 'template', ...)
 *
 * @param {?boolean=} [params.notEmpty=false] - если true, то директива не может быть "пустой"
 * @param {(Array|string)=} [params.chain] - название главной директивы (цепи), к которой принадлежит директива,
 *     или массив названий
 *
 * @param {(Array|string)=} [params.group] - название группы, к которой принадлежит директива,
 *     или массив названий
 *
 * @param {?boolean=} [params.sys=false] - если true, то директива считается системной
 *     (например, block или proto, т.е. которые участвуют только на этапе трансляции)
 *
 * @param {?boolean=} [params.block=false] - если true, то директива считается блочной
 *     (т.е. требует закрывающей директивы)
 *
 * @param {?boolean=} [params.selfInclude=true] - если false, то директива не может быть вложена
 *     в директиву схожего типа
 *
 * @param {Object=} [params.replacers] - таблица коротких сокращений директивы
 *     replacers: {
 *         // В ключе должно быть не более 2-х символов
 *         '?': (cmd) => cmd.replace(/^\?/, 'void ')
 *     }
 *
 * @param {Object=} [params.inside] - таблица директив, которые могут быть вложены в исходную
 *     inside: {
 *         'case': true,
 *         'default': true
 *     }
 *
 * @param {Object=} [params.after] - таблица директив, которые могут идти после исходной
 *     after: {
 *         'catch': true,
 *         'finally': true
 *     }
 *
 * @param {function(this:DirObj, string, number, string, (boolean|number))} constr - конструктор директивы
 * @param {?function(this:DirObj, string, number, string, (boolean|number))=} opt_destr - деструктор директивы
 */
Snakeskin.addDirective = function (name, params, constr, opt_destr) {
	params = params || {};

	if (params.replacers) {
		let repls = params.replacers;

		for (let key in repls) {
			/* istanbul ignore if */
			if (!repls.hasOwnProperty(key)) {
				continue;
			}

			replacers[key] = repls[key];

			if (key.charAt(0) !== '/') {
				shortMap[key] = true;
			}
		}
	}

	after[name] = params.after;
	inside[name] = params.inside;

	sys[name] = Boolean(params.sys);
	block[name] = Boolean(params.block);
	text[name] = Boolean(params.text);

	if (params.alias) {
		aliases[name] = name.replace(aliasRgxp, '$1');
	}

	if (params.group) {
		let group = Array.isArray(params.group) ?
			params.group : [params.group];

		for (let i = -1; ++i < group.length;) {
			if (!groups[group[i]]) {
				groups[group[i]] = {};
				groupsList[group[i]] = [];
			}

			groups[group[i]][name] = true;
			groupsList[group[i]].push(`"${name}"`);
		}
	}

	if (params.chain) {
		let chain = Array.isArray(params.chain) ?
			params.chain : [params.chain];

		for (let i = -1; ++i < chain.length;) {
			if (!chains[chain[i]]) {
				chains[chain[i]] = {};
			}

			chains[chain[i]][name] = true;
		}
	}

	params.selfInclude = params.selfInclude !== false;

	if (!params.selfInclude) {
		params.block = true;
	}

	Snakeskin.Directions[name] = function (command, commandLength, type, jsDoc) {
		var dir = this;
		var sourceName = getName(name),
			dirName = getName(name);

		if (dir.ctx) {
			dirName = dir.name || dirName;
			dir = dir.ctx;
		}

		var ignore = groups['ignore'][dirName],
			struct = dir.structure;

		dir.name = dirName;
		switch (params.placement) {
			case 'template':
				if (!struct.parent) {
					dir.error(`directive "${dirName}" can be used only within a ${groupsList['template'].join(', ')}`);
				}

				break;

			case 'global':
				if (struct.parent) {
					dir.error(`directive "${dirName}" can be used only within the global space`);
				}

				break;

			default:
				if (params.placement && dir.hasParent(params.placement)) {
					dir.error(`directive "${dirName}" can be used only within a "${params.placement}"`);
				}
		}

		if (params.notEmpty && !command) {
			return dir.error(`directive "${dirName}" should have a body`);
		}

		if (struct.strong) {
			if (inside[struct.name][dirName]) {
				dir.chainSpace = false;

			} else if (!ignore && sourceName === dirName && dirName !== 'end') {
				return dir.error(`directive "${dirName}" can't be used within the "${struct.name}"`);
			}
		}

		if (!params.selfInclude && dir.has(dirName)) {
			return dir.error(`directive "${dirName}" can't be used within the "${dirName}"`);
		}

		if (params.text) {
			dir.text = true;
		}

		var from = dir.res.length;

		constr.call(dir, command, commandLength, type, jsDoc);

		if (dir.structure.params._from === void 0) {
			dir.structure.params._from = from;
		}

		var newStruct = dir.structure;

		if (inside[dirName]) {
			newStruct.strong = true;
			dir.chainSpace = true;
		}

		if (dirName === sourceName) {
			if (struct === newStruct) {
				if (!ignore && after[struct.name] && !after[struct.name][dirName]) {
					return dir.error(`directive "${dirName}" can't be used after the "${struct.name}"`);
				}

			} else {
				let siblings = sourceName === 'end' ?
					newStruct.children : newStruct.parent && newStruct.parent.children;

				if (siblings) {
					let j = 1,
						prev;

					while ((prev = siblings[siblings.length - j]) && (prev.name === 'text' || prev === newStruct)) {
						j++;
					}

					if (!ignore && prev && after[prev.name] && !after[prev.name][dirName]) {
						return dir.error(`directive "${dirName}" can't be used after the "${prev.name}"`);
					}
				}
			}
		}

		dir.applyQueue();

		if (dir.inline === true) {
			baseEnd.call(dir);

			if (opt_destr) {
				opt_destr.call(dir, command, commandLength, type, jsDoc);
			}

			dir.inline = null;
			dir.structure = dir.structure.parent;

			if (dir.blockStructure && dir.blockStructure.name === 'const') {
				dir.blockStructure = dir.blockStructure.parent;
			}
		}
	};

	Snakeskin.Directions[`${name}End`] = opt_destr;
	var baseEnd = Snakeskin.Directions[`${name}BaseEnd`] = function () {
		var struct = this.structure,
			params = struct.params;

		if (params._scope) {
			this.scope.pop();
		}

		if (params._consts) {
			let consts = Object(params._consts);

			for (let key in consts) {
				/* istanbul ignore if */
				if (!consts.hasOwnProperty(key)) {
					continue;
				}

				constCache[this.tplName][key] = consts[key];
			}
		}

		var res = params._res ?
			params._res : this.res;

		var from = params._from,
			to = res.length;

		if (from == null) {
			return;
		}

		var parent = struct.parent;

		if ((!parent || parent.name === 'root') && !this.getGroup('define')[name] && from !== to) {
			try {
				this.evalStr(res.substring(from, to));

			} catch (err) {
				return this.error(err.message);
			}

			if (fsStack.length) {
				this.source = this.source.substring(0, this.i + 1) +
					fsStack.join('') +
					this.source.substring(this.i + 1);

				fsStack.splice(0, fsStack.length);
			}
		}
	};
};

//#include ./directions/*.js
<commit_msg>Добавлено исключение<commit_after>var aliasRgxp = /__(.*?)__/;

/**
 * Добавить новую директиву в пространство имён Snakeskin
 *
 * @param {string} name - название добавляемой директивы
 * @param {Object} params - дополнительные параметры
 *
 * @param {?boolean=} [params.alias=false] - если true, то директива считается псевдонимом
 *     (только для приватных директив)
 *
 * @param {?boolean=} [params.text=false] - если true, то декларируется,
 *     что директива выводится как текст
 *
 * @param {?string=} [params.placement] - если параметр задан, то делается проверка
 *     где именно размещена директива ('global', 'template', ...)
 *
 * @param {?boolean=} [params.notEmpty=false] - если true, то директива не может быть "пустой"
 * @param {(Array|string)=} [params.chain] - название главной директивы (цепи), к которой принадлежит директива,
 *     или массив названий
 *
 * @param {(Array|string)=} [params.group] - название группы, к которой принадлежит директива,
 *     или массив названий
 *
 * @param {?boolean=} [params.sys=false] - если true, то директива считается системной
 *     (например, block или proto, т.е. которые участвуют только на этапе трансляции)
 *
 * @param {?boolean=} [params.block=false] - если true, то директива считается блочной
 *     (т.е. требует закрывающей директивы)
 *
 * @param {?boolean=} [params.selfInclude=true] - если false, то директива не может быть вложена
 *     в директиву схожего типа
 *
 * @param {Object=} [params.replacers] - таблица коротких сокращений директивы
 *     replacers: {
 *         // В ключе должно быть не более 2-х символов
 *         '?': (cmd) => cmd.replace(/^\?/, 'void ')
 *     }
 *
 * @param {Object=} [params.inside] - таблица директив, которые могут быть вложены в исходную
 *     inside: {
 *         'case': true,
 *         'default': true
 *     }
 *
 * @param {Object=} [params.after] - таблица директив, которые могут идти после исходной
 *     after: {
 *         'catch': true,
 *         'finally': true
 *     }
 *
 * @param {function(this:DirObj, string, number, string, (boolean|number))} constr - конструктор директивы
 * @param {?function(this:DirObj, string, number, string, (boolean|number))=} opt_destr - деструктор директивы
 */
Snakeskin.addDirective = function (name, params, constr, opt_destr) {
	params = params || {};

	if (params.replacers) {
		let repls = params.replacers;

		for (let key in repls) {
			/* istanbul ignore if */
			if (!repls.hasOwnProperty(key)) {
				continue;
			}

			if (key.length > 2) {
				throw new Error(`Invalid shorthand key "${key}" (key.length > 2)`);
			}

			replacers[key] = repls[key];

			if (key.charAt(0) !== '/') {
				shortMap[key] = true;
			}
		}
	}

	after[name] = params.after;
	inside[name] = params.inside;
	sys[name] = Boolean(params.sys);
	block[name] = Boolean(params.block);
	text[name] = Boolean(params.text);

	if (params.alias) {
		aliases[name] = name.replace(aliasRgxp, '$1');
	}

	if (params.group) {
		let group = Array.isArray(params.group) ?
			params.group : [params.group];

		for (let i = -1; ++i < group.length;) {
			if (!groups[group[i]]) {
				groups[group[i]] = {};
				groupsList[group[i]] = [];
			}

			groups[group[i]][name] = true;
			groupsList[group[i]].push(`"${name}"`);
		}
	}

	if (params.chain) {
		let chain = Array.isArray(params.chain) ?
			params.chain : [params.chain];

		for (let i = -1; ++i < chain.length;) {
			if (!chains[chain[i]]) {
				chains[chain[i]] = {};
			}

			chains[chain[i]][name] = true;
		}
	}

	params.selfInclude = params.selfInclude !== false;

	if (!params.selfInclude) {
		params.block = true;
	}

	Snakeskin.Directions[name] = function (command, commandLength, type, jsDoc) {
		var dir = this;
		var sourceName = getName(name),
			dirName = getName(name);

		if (dir.ctx) {
			dirName = dir.name || dirName;
			dir = dir.ctx;
		}

		var ignore = groups['ignore'][dirName],
			struct = dir.structure;

		dir.name = dirName;
		switch (params.placement) {
			case 'template':
				if (!struct.parent) {
					dir.error(`directive "${dirName}" can be used only within a ${groupsList['template'].join(', ')}`);
				}

				break;

			case 'global':
				if (struct.parent) {
					dir.error(`directive "${dirName}" can be used only within the global space`);
				}

				break;

			default:
				if (params.placement && dir.hasParent(params.placement)) {
					dir.error(`directive "${dirName}" can be used only within a "${params.placement}"`);
				}
		}

		if (params.notEmpty && !command) {
			return dir.error(`directive "${dirName}" should have a body`);
		}

		if (struct.strong) {
			if (inside[struct.name][dirName]) {
				dir.chainSpace = false;

			} else if (!ignore && sourceName === dirName && dirName !== 'end') {
				return dir.error(`directive "${dirName}" can't be used within the "${struct.name}"`);
			}
		}

		if (!params.selfInclude && dir.has(dirName)) {
			return dir.error(`directive "${dirName}" can't be used within the "${dirName}"`);
		}

		if (params.text) {
			dir.text = true;
		}

		var from = dir.res.length;

		constr.call(dir, command, commandLength, type, jsDoc);

		if (dir.structure.params._from === void 0) {
			dir.structure.params._from = from;
		}

		var newStruct = dir.structure;

		if (inside[dirName]) {
			newStruct.strong = true;
			dir.chainSpace = true;
		}

		if (dirName === sourceName) {
			if (struct === newStruct) {
				if (!ignore && after[struct.name] && !after[struct.name][dirName]) {
					return dir.error(`directive "${dirName}" can't be used after the "${struct.name}"`);
				}

			} else {
				let siblings = sourceName === 'end' ?
					newStruct.children : newStruct.parent && newStruct.parent.children;

				if (siblings) {
					let j = 1,
						prev;

					while ((prev = siblings[siblings.length - j]) && (prev.name === 'text' || prev === newStruct)) {
						j++;
					}

					if (!ignore && prev && after[prev.name] && !after[prev.name][dirName]) {
						return dir.error(`directive "${dirName}" can't be used after the "${prev.name}"`);
					}
				}
			}
		}

		dir.applyQueue();

		if (dir.inline === true) {
			baseEnd.call(dir);

			if (opt_destr) {
				opt_destr.call(dir, command, commandLength, type, jsDoc);
			}

			dir.inline = null;
			dir.structure = dir.structure.parent;

			if (dir.blockStructure && dir.blockStructure.name === 'const') {
				dir.blockStructure = dir.blockStructure.parent;
			}
		}
	};

	Snakeskin.Directions[`${name}End`] = opt_destr;
	var baseEnd = Snakeskin.Directions[`${name}BaseEnd`] = function () {
		var struct = this.structure,
			params = struct.params;

		if (params._scope) {
			this.scope.pop();
		}

		if (params._consts) {
			let consts = Object(params._consts);

			for (let key in consts) {
				/* istanbul ignore if */
				if (!consts.hasOwnProperty(key)) {
					continue;
				}

				constCache[this.tplName][key] = consts[key];
			}
		}

		var res = params._res ?
			params._res : this.res;

		var from = params._from,
			to = res.length;

		if (from == null) {
			return;
		}

		var parent = struct.parent;

		if ((!parent || parent.name === 'root') && !this.getGroup('define')[name] && from !== to) {
			try {
				this.evalStr(res.substring(from, to));

			} catch (err) {
				return this.error(err.message);
			}

			if (fsStack.length) {
				this.source = this.source.substring(0, this.i + 1) +
					fsStack.join('') +
					this.source.substring(this.i + 1);

				fsStack.splice(0, fsStack.length);
			}
		}
	};
};

//#include ./directions/*.js
<|endoftext|>


<commit_before>/* public/script.js */

window.onload = function() {
    var converter = new showdown.Converter();
    var pad = document.getElementById('pad');
    var markdownArea = document.getElementById('markdown');

    pad.addEventListener('keydown',function(e) {
        if(e.keyCode === 9) { // tab was pressed
            // get caret position/selection
            var start = this.selectionStart;
            var end = this.selectionEnd;

            var target = e.target;
            var value = target.value;

            // set textarea value to: text before caret + tab + text after caret
            target.value = value.substring(0, start)
                            + "\t"
                            + value.substring(end);

            // put caret at right position again (add one for the tab)
            this.selectionStart = this.selectionEnd = start + 1;

            // prevent the focus lose
            e.preventDefault();
        }
    });

    var previousMarkdownValue;

    var convertTextAreaToMarkdown = function(){
        var markdownText = pad.value;
        previousMarkdownValue = markdownText;
        html = converter.makeHtml(markdownText);
        markdownArea.innerHTML = html;
    };

    var didChangeOccur = function(){
        if(previousMarkdownValue != pad.value){
            return true;
        }
        return false;
    };

    setInterval(function(){
        if(didChangeOccur()){
            convertTextAreaToMarkdown();
        }
    }, 1000);

    pad.addEventListener('input', convertTextAreaToMarkdown);

    if(document.location.pathname.length < 2){
        sharejs.open(document.location.pathname, 'text', function(error, doc) {
            doc.attach_textarea(pad);
            convertTextAreaToMarkdown();
        });
    }

    convertTextAreaToMarkdown();

};<commit_msg>add comment to frontend js<commit_after>/* public/script.js */

window.onload = function() {
    var converter = new showdown.Converter();
    var pad = document.getElementById('pad');
    var markdownArea = document.getElementById('markdown');

    // make the tab act like a tab
    pad.addEventListener('keydown',function(e) {
        if(e.keyCode === 9) { // tab was pressed
            // get caret position/selection
            var start = this.selectionStart;
            var end = this.selectionEnd;

            var target = e.target;
            var value = target.value;

            // set textarea value to: text before caret + tab + text after caret
            target.value = value.substring(0, start)
                            + "\t"
                            + value.substring(end);

            // put caret at right position again (add one for the tab)
            this.selectionStart = this.selectionEnd = start + 1;

            // prevent the focus lose
            e.preventDefault();
        }
    });

    var previousMarkdownValue;

    // convert text area to markdown html
    var convertTextAreaToMarkdown = function(){
        var markdownText = pad.value;
        previousMarkdownValue = markdownText;
        html = converter.makeHtml(markdownText);
        markdownArea.innerHTML = html;
    };

    var didChangeOccur = function(){
        if(previousMarkdownValue != pad.value){
            return true;
        }
        return false;
    };

    // check every second if the text area has changed
    setInterval(function(){
        if(didChangeOccur()){
            convertTextAreaToMarkdown();
        }
    }, 1000);

    // convert textarea on input change
    pad.addEventListener('input', convertTextAreaToMarkdown);

    // ignore if on home page
    if(document.location.pathname.length < 2){
        // implement share js
        sharejs.open(document.location.pathname, 'text', function(error, doc) {
            doc.attach_textarea(pad);
            convertTextAreaToMarkdown();
        });
    }

    // convert on page load
    convertTextAreaToMarkdown();

};<|endoftext|>


<commit_before>"use strict"

var named = require('node-named')
  , EventEmitter = require('events').EventEmitter
  , Logger = require('./logger')
  , fs = require('fs-extra')
  , path = require('path')
  , util = require('util')

var DNSServer = function (app, domain) {

  EventEmitter.call(this)

  this.domain = domain
  this.app = app
  this.entries = {}
  this.server = named.createServer({name: "Muguet DNS Server"})

  this.server.on('query', function (query) {

    var requestedDomain = query.name(),
        type = query.type()

    switch (type) {
      case 'A':

        if (this.entries[requestedDomain]) {
          var record = new named.ARecord(this.entries[requestedDomain])
          query.addAnswer(requestedDomain, record, 10)
        } else {
          Logger.error("DNS query (A) for domain %s does not match any container", requestedDomain)
        }

        this.server.send(query)
        break

      default:
        this.server.send(query)
    }

  }.bind(this))
}

util.inherits(DNSServer, EventEmitter)

/**
 * Setup the /etc/resolver/{domain} file
 * @private
 */
DNSServer.prototype._setupResolver = function (port, ip) {
  var file = path.join('/etc/resolver', this.domain)

  var contents = [
    "nameserver " + ip,
    "port " + port
  ].join("\n")

  fs.outputFile(file, contents, function () {
    Logger.debug('Resolver file %s updated', file)
  })

  return this
}

DNSServer.prototype.setEntries = function (entries) {
  this.entries = {}
  var self = this
  entries.forEach(function (entry) {
    entry.names.forEach(function (name) {
      self.entries[name] = entry.ip
    })
  })
}

DNSServer.prototype.getEntries = function () {
  return this.entries
}

DNSServer.prototype.listen = function (port, ip) {
  port = port || 9999
  this._setupResolver(port, ip)

  this.server.listen(port, '::', function () {
    Logger.info('Muguet DNS Server listening on port %d', port)
  })

  return this
}

exports.DNSServer = DNSServer
<commit_msg>add muguet.docker default domain<commit_after>"use strict"

var named = require('node-named')
  , EventEmitter = require('events').EventEmitter
  , Logger = require('./logger')
  , fs = require('fs-extra')
  , path = require('path')
  , util = require('util')

var DNSServer = function (app, domain) {

  EventEmitter.call(this)

  this.domain = domain
  this.app = app
  this.entries = {}
  this.server = named.createServer({name: "Muguet DNS Server"})

  this.server.on('query', function (query) {

    var requestedDomain = query.name(),
        type = query.type()

    switch (type) {
      case 'A':

        if (this.entries[requestedDomain]) {
          var record = new named.ARecord(this.entries[requestedDomain])
          query.addAnswer(requestedDomain, record, 10)
        } else {
          Logger.error("DNS query (A) for domain %s does not match any container", requestedDomain)
        }

        this.server.send(query)
        break

      default:
        this.server.send(query)
    }

  }.bind(this))
}

util.inherits(DNSServer, EventEmitter)

/**
 * Setup the /etc/resolver/{domain} file
 * @private
 */
DNSServer.prototype._setupResolver = function (port, ip) {
  var file = path.join('/etc/resolver', this.domain)

  var contents = [
    "nameserver " + ip,
    "port " + port
  ].join("\n")

  fs.outputFile(file, contents, function () {
    Logger.debug('Resolver file %s updated', file)
  })

  return this
}

DNSServer.prototype.setEntries = function (entries) {

  this.entries = {}
  this.entries['muguet.' + this.domain] = this.app.getProxyIp()

  var self = this

  entries.forEach(function (entry) {
    entry.names.forEach(function (name) {
      self.entries[name] = entry.ip
    })
  })
}

DNSServer.prototype.getEntries = function () {
  return this.entries
}

DNSServer.prototype.listen = function (port, ip) {
  port = port || 9999
  this._setupResolver(port, ip)

  this.server.listen(port, '::', function () {
    Logger.info('Muguet DNS Server listening on port %d', port)
  })

  return this
}

exports.DNSServer = DNSServer
<|endoftext|>


<commit_before>var serverUrl = "/";
var localStream, room, recording;

function getParameterByName(name) {
  name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
  var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
      results = regex.exec(location.search);
  return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

function startRecording (){
  if (room!=undefined){
    if (!recording){
      room.startRecording(localStream);
      recording = true;
    }else{
      room.stopRecording(localStream);
      recording = false;
    }
  }
}

window.onload = function () {
  recording = false;
  var screen = getParameterByName("screen");

  localStream = Erizo.Stream({audio: true, video: true, data: true, screen: screen, videoSize: [640, 480, 640, 480]});
  var createToken = function(userName, role, callback) {

    var req = new XMLHttpRequest();
    var url = serverUrl + 'createToken/';
    var body = {username: userName, role: role};

    req.onreadystatechange = function () {
      if (req.readyState === 4) {
        callback(req.responseText);
      }
    };

    req.open('POST', url, true);
    req.setRequestHeader('Content-Type', 'application/json');
    req.send(JSON.stringify(body));
  };

  createToken("user", "presenter", function (response) {
    var token = response;
    console.log(token);
    room = Erizo.Room({token: token});

    localStream.addEventListener("access-accepted", function () {
      var subscribeToStreams = function (streams) {
        for (var index in streams) {
          var stream = streams[index];
          if (localStream.getID() !== stream.getID()) {
            room.subscribe(stream);
          }
        }
      };

      room.addEventListener("room-connected", function (roomEvent) {

        room.publish(localStream, {maxVideoBW: 300});
        subscribeToStreams(roomEvent.streams);
      });

      room.addEventListener("stream-subscribed", function(streamEvent) {
        var stream = streamEvent.stream;
        /*var div = document.createElement('div');
        div.setAttribute("style", "width: 320px; height: 240px;");
        div.setAttribute("id", "test" + stream.getID());

        document.body.appendChild(div);*/
		app_js2java.create_view(320, 240, "test" + stream.getID());
        stream.show("test" + stream.getID());

      });

      room.addEventListener("stream-added", function (streamEvent) {
        var streams = [];
        streams.push(streamEvent.stream);
        subscribeToStreams(streams);
      });

      room.addEventListener("stream-removed", function (streamEvent) {
        // Remove stream from DOM
        var stream = streamEvent.stream;
        if (stream.elementID !== undefined) {
          //var element = document.getElementById(stream.elementID);
          //document.body.removeChild(element);
			app_js2java.remove_view("test" + stream.getID());
        }
      });

      room.connect();

      localStream.show("myVideo");

    });
    localStream.init();
  });
};
<commit_msg>update app<commit_after>var serverUrl = "/";
var localStream, room, recording;

function getParameterByName(name) {
  name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
  var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
      results = regex.exec(location.search);
  return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}

function startRecording (){
  if (room!=undefined){
    if (!recording){
      room.startRecording(localStream);
      recording = true;
    }else{
      room.stopRecording(localStream);
      recording = false;
    }
  }
}

window.onload = function () {
  recording = false;
  var screen = getParameterByName("screen");

  localStream = Erizo.Stream({audio: true, video: true, data: true, screen: screen, videoSize: [640, 480, 640, 480]});
  var createToken = function(userName, role, callback) {

    var req = new XMLHttpRequest();
    var url = serverUrl + 'createToken/';
    var body = {username: userName, role: role};

    req.onreadystatechange = function () {
      if (req.readyState === 4) {
        callback(req.responseText);
      }
    };

    req.open('POST', url, true);
    req.setRequestHeader('Content-Type', 'application/json');
    req.send(JSON.stringify(body));
  };

  createToken("user", "presenter", function (response) {
    var token = response;
    console.log(token);
    room = Erizo.Room({token: token});

    localStream.addEventListener("access-accepted", function () {
      var subscribeToStreams = function (streams) {
        for (var index in streams) {
          var stream = streams[index];
          if (localStream.getID() !== stream.getID()) {
            room.subscribe(stream);
          }
        }
      };

      room.addEventListener("room-connected", function (roomEvent) {

        room.publish(localStream, {maxVideoBW: 300});
        subscribeToStreams(roomEvent.streams);
      });

      room.addEventListener("stream-subscribed", function(streamEvent) {
        var stream = streamEvent.stream;
        /*var div = document.createElement('div');
        div.setAttribute("style", "width: 320px; height: 240px;");
        div.setAttribute("id", "test" + stream.getID());

        document.body.appendChild(div);*/
        viewManager.create_view(320, 240, "test" + stream.getID());
        stream.show("test" + stream.getID());

      });

      room.addEventListener("stream-added", function (streamEvent) {
        var streams = [];
        streams.push(streamEvent.stream);
        subscribeToStreams(streams);
      });

      room.addEventListener("stream-removed", function (streamEvent) {
        // Remove stream from DOM
        var stream = streamEvent.stream;
        if (stream.elementID !== undefined) {
          //var element = document.getElementById(stream.elementID);
          //document.body.removeChild(element);
        	viewManager.remove_view("test" + stream.getID());
        }
      });

      room.connect();

      localStream.show("myVideo");

    });
    localStream.init();
  });
};
<|endoftext|>


<commit_before>var state
var size
var columns
var step
var sprite
var dir

function setup() {
  //frameRate(5);
  state = 'menu'
  size = 150
  maze = new Maze(size)
  maze.setup()
  easyButton = new Button({x: 10, y: 550}, {width: 150, height: 40}, 'draw-maze', "Easy", ['menu'])
  mediumButton = new Button({x: 225, y: 550}, {width: 150, height: 40}, 'draw-maze', "Medium", ['menu'])
  hardButton = new Button({x: 440, y: 550}, {width: 150, height: 40}, 'draw-maze', "Hard", ['menu'])
  columns = floor(width/size)
  step = true
  dir = true
  sprite = loadImage('/assets/R1.png')
}

function draw() {
  background(51)
  easyButton.update(state)
  mediumButton.update(state)
  hardButton.update(state)

  easyButton.render()
  mediumButton.render()
  hardButton.render()

  if(checkCurrent() && state != 'draw-maze') {
    size = findNextDivisible(width, size)
    maze.current = undefined
    maze = new Maze(size)
    maze.setup()
    state = 'draw-maze'
  }

  if(state === 'menu') {
  } else if (state === 'draw-maze') {
    maze.draw();
    if(maze.current.i === 0 && maze.current.j === 0) {
      state = 'game'
    }
  } else if (state === 'game') {
    maze.draw()
    maze.current.highlight(sprite)
  }
}

function mouseClicked() {
  if(easyButton.isClicked(mouseX, mouseY)) state = easyButton.state
  if(mediumButton.isClicked(mouseX, mouseY)) state = mediumButton.state
  if(hardButton.isClicked(mouseX, mouseY)) state = hardButton.state
}

function keyTyped() {
  if(state === 'game') {
    if(key === 'w' && !maze.current.walls[0]) {
      maze.current = maze.grid[maze.current.i + columns*(maze.current.j-1)]
      if(dir) {
        if(step) sprite = loadImage('/assets/R1.png')
        else sprite = loadImage('assets/R2.png')
      } else {
        if(step) sprite = loadImage('/assets/L1.png')
        else sprite = loadImage('assets/L2.png')
      }
    } else if(key === 'd' && !maze.current.walls[1]) {
      maze.current = maze.grid[maze.current.i + columns*(maze.current.j) + 1]
      if(step) sprite = loadImage('/assets/R1.png')
      else sprite = loadImage('assets/R2.png')
      dir = true
      maze.current.highlight(sprite)
    } else if(key === 's' && !maze.current.walls[2]) {
      maze.current = maze.grid[maze.current.i + columns*(maze.current.j+1)]
      if(dir) {
        if(step) sprite = loadImage('/assets/R1.png')
        else sprite = loadImage('assets/R2.png')
      } else {
        if(step) sprite = loadImage('/assets/L1.png')
        else sprite = loadImage('assets/L2.png')
      }
    } else if(key === 'a' && !maze.current.walls[3]) {
      maze.current = maze.grid[maze.current.i + columns*(maze.current.j) - 1]
      if(step) sprite = loadImage('/assets/L1.png')
      else sprite = loadImage('assets/L2.png')
      dir = false
      maze.current.highlight(sprite)
    }
    step = !step
  }
}

function checkCurrent() {
  return maze.current === maze.grid[maze.grid.length - 1]
}

function findNextDivisible(divisor, current) {
  while(divisor % --current != 0) {
    //do nothing
  }
  return current
}
<commit_msg>Minor change to divisor function<commit_after>var state
var size
var columns
var step
var sprite
var dir

function setup() {
  //frameRate(5);
  state = 'menu'
  size = 150
  maze = new Maze(size)
  maze.setup()
  easyButton = new Button({x: 10, y: 550}, {width: 150, height: 40}, 'draw-maze', "Easy", ['menu'])
  mediumButton = new Button({x: 225, y: 550}, {width: 150, height: 40}, 'draw-maze', "Medium", ['menu'])
  hardButton = new Button({x: 440, y: 550}, {width: 150, height: 40}, 'draw-maze', "Hard", ['menu'])
  columns = floor(width/size)
  step = true
  dir = true
  sprite = loadImage('/assets/R1.png')
}

function draw() {
  background(51)
  easyButton.update(state)
  mediumButton.update(state)
  hardButton.update(state)

  easyButton.render()
  mediumButton.render()
  hardButton.render()

  if(checkCurrent() && state != 'draw-maze') {
    size = findNextDivisible(width, size)
    maze.current = undefined
    maze = new Maze(size)
    maze.setup()
    columns = floor(width/size)
    state = 'draw-maze'
  }

  if(state === 'menu') {
  } else if (state === 'draw-maze') {
    maze.draw();
    if(maze.current.i === 0 && maze.current.j === 0) {
      state = 'game'
    }
  } else if (state === 'game') {
    maze.draw()
    maze.current.highlight(sprite)
  }
}

function mouseClicked() {
  if(easyButton.isClicked(mouseX, mouseY)) state = easyButton.state
  if(mediumButton.isClicked(mouseX, mouseY)) state = mediumButton.state
  if(hardButton.isClicked(mouseX, mouseY)) state = hardButton.state
}

function keyTyped() {
  if(state === 'game') {
    if(key === 'w' && !maze.current.walls[0]) {
      maze.current = maze.grid[maze.current.i + columns*(maze.current.j-1)]
      if(dir) {
        if(step) sprite = loadImage('/assets/R1.png')
        else sprite = loadImage('assets/R2.png')
      } else {
        if(step) sprite = loadImage('/assets/L1.png')
        else sprite = loadImage('assets/L2.png')
      }
    } else if(key === 'd' && !maze.current.walls[1]) {
      maze.current = maze.grid[maze.current.i + columns*(maze.current.j) + 1]
      if(step) sprite = loadImage('/assets/R1.png')
      else sprite = loadImage('assets/R2.png')
      dir = true
      maze.current.highlight(sprite)
    } else if(key === 's' && !maze.current.walls[2]) {
      maze.current = maze.grid[maze.current.i + columns*(maze.current.j+1)]
      if(dir) {
        if(step) sprite = loadImage('/assets/R1.png')
        else sprite = loadImage('assets/R2.png')
      } else {
        if(step) sprite = loadImage('/assets/L1.png')
        else sprite = loadImage('assets/L2.png')
      }
    } else if(key === 'a' && !maze.current.walls[3]) {
      maze.current = maze.grid[maze.current.i + columns*(maze.current.j) - 1]
      if(step) sprite = loadImage('/assets/L1.png')
      else sprite = loadImage('assets/L2.png')
      dir = false
      maze.current.highlight(sprite)
    }
    step = !step
  }
}

function checkCurrent() {
  return maze.current === maze.grid[maze.grid.length - 1]
}

function findNextDivisible(divisor, current) {
  while(current > 0 && divisor % --current != 0) {
    //do nothing
  }
  return current
}
<|endoftext|>