<commit_before>
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is guacamole-common-js.
 *
 * The Initial Developer of the Original Code is
 * Michael Jumper.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// Guacamole namespace
var Guacamole = Guacamole || {};

/**
 * Provides cross-browser and cross-keyboard keyboard for a specific element.
 * Browser and keyboard layout variation is abstracted away, providing events
 * which represent keys as their corresponding X11 keysym.
 *
 * @constructor
 * @param {Element} element The Element to use to provide keyboard events.
 */
Guacamole.Keyboard = function(element) {

    /**
     * Reference to this Guacamole.Keyboard.
     * @private
     */
    var guac_keyboard = this;

    /**
     * Fired whenever the user presses a key with the element associated
     * with this Guacamole.Keyboard in focus.
     *
     * @event
     * @param {Number} keysym The keysym of the key being pressed.
     * @returns {Boolean} true if the originating event of this keypress should
     *                    be allowed through to the browser, false or undefined
     *                    otherwise.
     */
    this.onkeydown = null;

    /**
     * Fired whenever the user releases a key with the element associated
     * with this Guacamole.Keyboard in focus.
     *
     * @event
     * @param {Number} keysym The keysym of the key being released.
     * @returns {Boolean} true if the originating event of this key release
     *                    should be allowed through to the browser, false or
     *                    undefined otherwise.
     */
    this.onkeyup = null;

    /**
     * Map of known JavaScript keycodes which do not map to typable characters
     * to their unshifted X11 keysym equivalents.
     * @private
     */
    var unshiftedKeySym = {
        8:   0xFF08, // backspace
        9:   0xFF09, // tab
        13:  0xFF0D, // enter
        16:  0xFFE1, // shift
        17:  0xFFE3, // ctrl
        18:  0xFFE9, // alt
        19:  0xFF13, // pause/break
        20:  0xFFE5, // caps lock
        27:  0xFF1B, // escape
        33:  0xFF55, // page up
        34:  0xFF56, // page down
        35:  0xFF57, // end
        36:  0xFF50, // home
        37:  0xFF51, // left arrow
        38:  0xFF52, // up arrow
        39:  0xFF53, // right arrow
        40:  0xFF54, // down arrow
        45:  0xFF63, // insert
        46:  0xFFFF, // delete
        91:  0xFFEB, // left window key (super_l)
        92:  0xFF67, // right window key (menu key?)
        93:  null,   // select key
        112: 0xFFBE, // f1
        113: 0xFFBF, // f2
        114: 0xFFC0, // f3
        115: 0xFFC1, // f4
        116: 0xFFC2, // f5
        117: 0xFFC3, // f6
        118: 0xFFC4, // f7
        119: 0xFFC5, // f8
        120: 0xFFC6, // f9
        121: 0xFFC7, // f10
        122: 0xFFC8, // f11
        123: 0xFFC9, // f12
        144: 0xFF7F, // num lock
        145: 0xFF14  // scroll lock
    };

    /**
     * Map of known JavaScript keycodes which do not map to typable characters
     * to their shifted X11 keysym equivalents. Keycodes must only be listed
     * here if their shifted X11 keysym equivalents differ from their unshifted
     * equivalents.
     * @private
     */
    var shiftedKeySym = {
        18:  0xFFE7  // alt
    };

    /**
     * All modifiers and their states.
     */
    this.modifiers = {

        /**
         * Whether shift is currently pressed.
         */
        "shift": false,

        /**
         * Whether ctrl is currently pressed.
         */
        "ctrl" : false,

        /**
         * Whether alt is currently pressed.
         */
        "alt"  : false

    };

    /**
     * The state of every key, indexed by keysym. If a particular key is
     * pressed, the value of pressed for that keysym will be true. If a key
     * is not currently pressed, the value for that keysym may be false or
     * undefined.
     */
    this.pressed = [];

    var keydownChar = new Array();

    // ID of routine repeating keystrokes. -1 = not repeating.
    var repeatKeyTimeoutId = -1;
    var repeatKeyIntervalId = -1;

    // Starts repeating keystrokes
    function startRepeat(keySym) {
        repeatKeyIntervalId = setInterval(function() {
            sendKeyReleased(keySym);
            sendKeyPressed(keySym);
        }, 50);
    }

    // Stops repeating keystrokes
    function stopRepeat() {
        if (repeatKeyTimeoutId != -1) clearInterval(repeatKeyTimeoutId);
        if (repeatKeyIntervalId != -1) clearInterval(repeatKeyIntervalId);
    }

    function getKeySymFromKeyIdentifier(shifted, keyIdentifier) {

        var unicodePrefixLocation = keyIdentifier.indexOf("U+");
        if (unicodePrefixLocation >= 0) {

            var hex = keyIdentifier.substring(unicodePrefixLocation+2);
            var codepoint = parseInt(hex, 16);
            var typedCharacter;

            // Convert case if shifted
            if (shifted == 0)
                typedCharacter = String.fromCharCode(codepoint).toLowerCase();
            else
                typedCharacter = String.fromCharCode(codepoint).toUpperCase();

            // Get codepoint
            codepoint = typedCharacter.charCodeAt(0);

            return getKeySymFromCharCode(codepoint);

        }

        return null;

    }

    function getKeySymFromCharCode(keyCode) {

        if (keyCode >= 0x0000 && keyCode <= 0x00FF)
            return keyCode;

        if (keyCode >= 0x0100 && keyCode <= 0x10FFFF)
            return 0x01000000 | keyCode;

        return null;

    }

    function getKeySymFromKeyCode(keyCode) {

        var keysym = null;
        if (!guac_keyboard.modifiers.shift) keysym = unshiftedKeySym[keyCode];
        else {
            keysym = shiftedKeySym[keyCode];
            if (keysym == null) keysym = unshiftedKeySym[keyCode];
        }

        return keysym;

    }

    // Sends a single keystroke over the network
    function sendKeyPressed(keysym) {

        // Mark key as pressed
        guac_keyboard.pressed[keysym] = true;

        // Send key event
        if (keysym != null && guac_keyboard.onkeydown)
            return guac_keyboard.onkeydown(keysym) != false;

        return true;

    }

    // Sends a single keystroke over the network
    function sendKeyReleased(keysym) {

        // Mark key as released
        guac_keyboard.pressed[keysym] = false;

        // Send key event
        if (keysym != null && guac_keyboard.onkeyup)
            return guac_keyboard.onkeyup(keysym) != false;

        return true;

    }

    var keydown_code = null;

    var deferred_keypress = null;
    var keydown_keysym = null;
    var keypress_keysym = null;

    function fireKeyPress() {

        // Prefer keysym from keypress
        var keysym = keypress_keysym || keydown_keysym;
        var keynum = keydown_code;

        if (keydownChar[keynum] != keysym) {

            // If this button is already pressed, release first
            var lastKeyDownChar = keydownChar[keydown_code];
            if (lastKeyDownChar)
                sendKeyReleased(lastKeyDownChar);

            // Send event
            keydownChar[keynum] = keysym;
            sendKeyPressed(keysym);

            // Clear old key repeat, if any.
            stopRepeat();

            // Start repeating (if not a modifier key) after a short delay
            if (keynum != 16 && keynum != 17 && keynum != 18)
                repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);

        }

        // Done with deferred key event
        deferred_keypress = null;

    }

    // When key pressed
    element.onkeydown = function(e) {

        // Only intercept if handler set
        if (!guac_keyboard.onkeydown) return true;

        var keynum;
        if (window.event) keynum = window.event.keyCode;
        else if (e.which) keynum = e.which;

        // Ctrl/Alt/Shift
        if (keynum == 16)      guac_keyboard.modifiers.shift = true;
        else if (keynum == 17) guac_keyboard.modifiers.ctrl  = true;
        else if (keynum == 18) guac_keyboard.modifiers.alt   = true;

        // Try to get keysym from keycode
        keydown_keysym = getKeySymFromKeyCode(keynum);

        // Also try to get get keysym from keyIdentifier
        if (e.keyIdentifier)
            keydown_keysym = getKeySymFromKeyIdentifier(guac_keyboard.modifiers.shift, e.keyIdentifier);

        // Set keycode which will be associated with any future keypress
        keydown_code = keynum;

        // Defer handling of event until after any other pending
        // key events.
        if (!deferred_keypress)
            deferred_keypress = window.setTimeout(fireKeyPress, 0);

        return false;

    };

    // When key pressed
    element.onkeypress = function(e) {

        // Only intercept if handler set
        if (!guac_keyboard.onkeydown) return true;

        if (keySymSource != KEYPRESS) return false;

        var keynum;
        if (window.event) keynum = window.event.keyCode;
        else if (e.which) keynum = e.which;

        keypress_keysym = getKeySymFromCharCode(keynum);

        // Defer handling of event until after any other pending
        // key events.
        if (!deferred_keypress)
            deferred_keypress = window.setTimeout(fireKeyPress, 0);

        return false;

    };

    // When key released
    element.onkeyup = function(e) {

        // Only intercept if handler set
        if (!guac_keyboard.onkeyup) return true;

        var keynum;
        if (window.event) keynum = window.event.keyCode;
        else if (e.which) keynum = e.which;

        // Ctrl/Alt/Shift
        if (keynum == 16)      guac_keyboard.modifiers.shift = false;
        else if (keynum == 17) guac_keyboard.modifiers.ctrl  = false;
        else if (keynum == 18) guac_keyboard.modifiers.alt   = false;
        else
            stopRepeat();

        // Get corresponding character
        var lastKeyDownChar = keydownChar[keynum];

        // Clear character record
        keydownChar[keynum] = null;

        // Send release event
        return sendKeyReleased(lastKeyDownChar);

    };

    // When focus is lost, clear modifiers.
    element.onblur = function() {
        guac_keyboard.modifiers.alt = false;
        guac_keyboard.modifiers.ctrl = false;
        guac_keyboard.modifiers.shift = false;
    };

};
<commit_msg>Add keyidentifier map.<commit_after>
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is guacamole-common-js.
 *
 * The Initial Developer of the Original Code is
 * Michael Jumper.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// Guacamole namespace
var Guacamole = Guacamole || {};

/**
 * Provides cross-browser and cross-keyboard keyboard for a specific element.
 * Browser and keyboard layout variation is abstracted away, providing events
 * which represent keys as their corresponding X11 keysym.
 *
 * @constructor
 * @param {Element} element The Element to use to provide keyboard events.
 */
Guacamole.Keyboard = function(element) {

    /**
     * Reference to this Guacamole.Keyboard.
     * @private
     */
    var guac_keyboard = this;

    /**
     * Fired whenever the user presses a key with the element associated
     * with this Guacamole.Keyboard in focus.
     *
     * @event
     * @param {Number} keysym The keysym of the key being pressed.
     * @returns {Boolean} true if the originating event of this keypress should
     *                    be allowed through to the browser, false or undefined
     *                    otherwise.
     */
    this.onkeydown = null;

    /**
     * Fired whenever the user releases a key with the element associated
     * with this Guacamole.Keyboard in focus.
     *
     * @event
     * @param {Number} keysym The keysym of the key being released.
     * @returns {Boolean} true if the originating event of this key release
     *                    should be allowed through to the browser, false or
     *                    undefined otherwise.
     */
    this.onkeyup = null;

    /**
     * Map of known JavaScript keycodes which do not map to typable characters
     * to their unshifted X11 keysym equivalents.
     * @private
     */
    var unshiftedKeySym = {
        8:   0xFF08, // backspace
        9:   0xFF09, // tab
        13:  0xFF0D, // enter
        16:  0xFFE1, // shift
        17:  0xFFE3, // ctrl
        18:  0xFFE9, // alt
        19:  0xFF13, // pause/break
        20:  0xFFE5, // caps lock
        27:  0xFF1B, // escape
        33:  0xFF55, // page up
        34:  0xFF56, // page down
        35:  0xFF57, // end
        36:  0xFF50, // home
        37:  0xFF51, // left arrow
        38:  0xFF52, // up arrow
        39:  0xFF53, // right arrow
        40:  0xFF54, // down arrow
        45:  0xFF63, // insert
        46:  0xFFFF, // delete
        91:  0xFFEB, // left window key (super_l)
        92:  0xFF67, // right window key (menu key?)
        93:  null,   // select key
        112: 0xFFBE, // f1
        113: 0xFFBF, // f2
        114: 0xFFC0, // f3
        115: 0xFFC1, // f4
        116: 0xFFC2, // f5
        117: 0xFFC3, // f6
        118: 0xFFC4, // f7
        119: 0xFFC5, // f8
        120: 0xFFC6, // f9
        121: 0xFFC7, // f10
        122: 0xFFC8, // f11
        123: 0xFFC9, // f12
        144: 0xFF7F, // num lock
        145: 0xFF14  // scroll lock
    };

    /**
     * Map of known JavaScript keyidentifiers which do not map to typable
     * characters to their unshifted X11 keysym equivalents.
     * @private
     */
    var keyidentifier_keysym = {
        "AllCandidates": 0xFF3D,
        "Alphanumeric": 0xFF30,
        "Alt": 0xFFE9,
        "Attn": 0xFD0E,
        "AltGraph": 0xFFEA,
        "CapsLock": 0xFFE5,
        "Clear": 0xFF0B,
        "Convert": 0xFF21,
        "Copy": 0xFD15,
        "Crsel": 0xFD1C,
        "CodeInput": 0xFF37,
        "Control": 0xFFE3,
        "Down": 0xFF54,
        "End": 0xFF57,
        "Enter": 0xFF0D,
        "EraseEof": 0xFD06,
        "Execute": 0xFF62,
        "Exsel": 0xFD1D,
        "F1": 0xFFBE,
        "F2": 0xFFBF,
        "F3": 0xFFC0,
        "F4": 0xFFC1,
        "F5": 0xFFC2,
        "F6": 0xFFC3,
        "F7": 0xFFC4,
        "F8": 0xFFC5,
        "F9": 0xFFC6,
        "F10": 0xFFC7,
        "F11": 0xFFC8,
        "F12": 0xFFC9,
        "F13": 0xFFCA,
        "F14": 0xFFCB,
        "F15": 0xFFCC,
        "F16": 0xFFCD,
        "F17": 0xFFCE,
        "F18": 0xFFCF,
        "F19": 0xFFD0,
        "F20": 0xFFD1,
        "F21": 0xFFD2,
        "F22": 0xFFD3,
        "F23": 0xFFD4,
        "F24": 0xFFD5,
        "Find": 0xFF68,
        "FullWidth": null,
        "HalfWidth": null,
        "HangulMode": 0xFF31,
        "HanjaMode": 0xFF34,
        "Help": 0xFF6A,
        "Hiragana": 0xFF25,
        "Home": 0xFF50,
        "Insert": 0xFF63,
        "JapaneseHiragana": 0xFF25,
        "JapaneseKatakana": 0xFF26,
        "JapaneseRomaji": 0xFF24,
        "JunjaMode": 0xFF38,
        "KanaMode": 0xFF2D,
        "KanjiMode": 0xFF21,
        "Katakana": 0xFF26,
        "Left": 0xFF51,
        "Meta": 0xFFE7,
        "NumLock": 0xFF7F,
        "PageDown": 0xFF55,
        "PageUp": 0xFF56,
        "Pause": 0xFF13,
        "PreviousCandidate": 0xFF3E,
        "PrintScreen": 0xFD1D,
        "Right": 0xFF53,
        "RomanCharacters": null,
        "Scroll": 0xFF14,
        "Select": 0xFF60,
        "Shift": 0xFFE1,
        "Up": 0xFF52,
        "Undo": 0xFF65,
        "Win": 0xFFEB
    };

    /**
     * Map of known JavaScript keycodes which do not map to typable characters
     * to their shifted X11 keysym equivalents. Keycodes must only be listed
     * here if their shifted X11 keysym equivalents differ from their unshifted
     * equivalents.
     * @private
     */
    var shiftedKeySym = {
        18:  0xFFE7  // alt
    };

    /**
     * All modifiers and their states.
     */
    this.modifiers = {

        /**
         * Whether shift is currently pressed.
         */
        "shift": false,

        /**
         * Whether ctrl is currently pressed.
         */
        "ctrl" : false,

        /**
         * Whether alt is currently pressed.
         */
        "alt"  : false

    };

    /**
     * The state of every key, indexed by keysym. If a particular key is
     * pressed, the value of pressed for that keysym will be true. If a key
     * is not currently pressed, the value for that keysym may be false or
     * undefined.
     */
    this.pressed = [];

    var keydownChar = new Array();

    // ID of routine repeating keystrokes. -1 = not repeating.
    var repeatKeyTimeoutId = -1;
    var repeatKeyIntervalId = -1;

    // Starts repeating keystrokes
    function startRepeat(keySym) {
        repeatKeyIntervalId = setInterval(function() {
            sendKeyReleased(keySym);
            sendKeyPressed(keySym);
        }, 50);
    }

    // Stops repeating keystrokes
    function stopRepeat() {
        if (repeatKeyTimeoutId != -1) clearInterval(repeatKeyTimeoutId);
        if (repeatKeyIntervalId != -1) clearInterval(repeatKeyIntervalId);
    }

    function getKeySymFromKeyIdentifier(shifted, keyIdentifier) {

        var unicodePrefixLocation = keyIdentifier.indexOf("U+");
        if (unicodePrefixLocation >= 0) {

            var hex = keyIdentifier.substring(unicodePrefixLocation+2);
            var codepoint = parseInt(hex, 16);
            var typedCharacter;

            // Convert case if shifted
            if (shifted == 0)
                typedCharacter = String.fromCharCode(codepoint).toLowerCase();
            else
                typedCharacter = String.fromCharCode(codepoint).toUpperCase();

            // Get codepoint
            codepoint = typedCharacter.charCodeAt(0);

            return getKeySymFromCharCode(codepoint);

        }

        return keyidentifier_keysym[keyIdentifier];

    }

    function getKeySymFromCharCode(keyCode) {

        if (keyCode >= 0x0000 && keyCode <= 0x00FF)
            return keyCode;

        if (keyCode >= 0x0100 && keyCode <= 0x10FFFF)
            return 0x01000000 | keyCode;

        return null;

    }

    function getKeySymFromKeyCode(keyCode) {

        var keysym = null;
        if (!guac_keyboard.modifiers.shift) keysym = unshiftedKeySym[keyCode];
        else {
            keysym = shiftedKeySym[keyCode];
            if (keysym == null) keysym = unshiftedKeySym[keyCode];
        }

        return keysym;

    }

    // Sends a single keystroke over the network
    function sendKeyPressed(keysym) {

        // Mark key as pressed
        guac_keyboard.pressed[keysym] = true;

        // Send key event
        if (keysym != null && guac_keyboard.onkeydown)
            return guac_keyboard.onkeydown(keysym) != false;

        return true;

    }

    // Sends a single keystroke over the network
    function sendKeyReleased(keysym) {

        // Mark key as released
        guac_keyboard.pressed[keysym] = false;

        // Send key event
        if (keysym != null && guac_keyboard.onkeyup)
            return guac_keyboard.onkeyup(keysym) != false;

        return true;

    }

    var keydown_code = null;

    var deferred_keypress = null;
    var keydown_keysym = null;
    var keypress_keysym = null;

    function fireKeyPress() {

        // Prefer keysym from keypress
        var keysym = keypress_keysym || keydown_keysym;
        var keynum = keydown_code;

        if (keydownChar[keynum] != keysym) {

            // If this button is already pressed, release first
            var lastKeyDownChar = keydownChar[keydown_code];
            if (lastKeyDownChar)
                sendKeyReleased(lastKeyDownChar);

            // Send event
            keydownChar[keynum] = keysym;
            sendKeyPressed(keysym);

            // Clear old key repeat, if any.
            stopRepeat();

            // Start repeating (if not a modifier key) after a short delay
            if (keynum != 16 && keynum != 17 && keynum != 18)
                repeatKeyTimeoutId = setTimeout(function() { startRepeat(keysym); }, 500);

        }

        // Done with deferred key event
        deferred_keypress = null;

    }

    // When key pressed
    element.onkeydown = function(e) {

        // Only intercept if handler set
        if (!guac_keyboard.onkeydown) return true;

        var keynum;
        if (window.event) keynum = window.event.keyCode;
        else if (e.which) keynum = e.which;

        // Ctrl/Alt/Shift
        if (keynum == 16)      guac_keyboard.modifiers.shift = true;
        else if (keynum == 17) guac_keyboard.modifiers.ctrl  = true;
        else if (keynum == 18) guac_keyboard.modifiers.alt   = true;

        // Try to get keysym from keycode
        keydown_keysym = getKeySymFromKeyCode(keynum);

        // Also try to get get keysym from keyIdentifier
        if (e.keyIdentifier)
            keydown_keysym = getKeySymFromKeyIdentifier(guac_keyboard.modifiers.shift, e.keyIdentifier);

        // Set keycode which will be associated with any future keypress
        keydown_code = keynum;

        // Defer handling of event until after any other pending
        // key events.
        if (!deferred_keypress)
            deferred_keypress = window.setTimeout(fireKeyPress, 0);

        return false;

    };

    // When key pressed
    element.onkeypress = function(e) {

        // Only intercept if handler set
        if (!guac_keyboard.onkeydown) return true;

        if (keySymSource != KEYPRESS) return false;

        var keynum;
        if (window.event) keynum = window.event.keyCode;
        else if (e.which) keynum = e.which;

        keypress_keysym = getKeySymFromCharCode(keynum);

        // Defer handling of event until after any other pending
        // key events.
        if (!deferred_keypress)
            deferred_keypress = window.setTimeout(fireKeyPress, 0);

        return false;

    };

    // When key released
    element.onkeyup = function(e) {

        // Only intercept if handler set
        if (!guac_keyboard.onkeyup) return true;

        var keynum;
        if (window.event) keynum = window.event.keyCode;
        else if (e.which) keynum = e.which;

        // Ctrl/Alt/Shift
        if (keynum == 16)      guac_keyboard.modifiers.shift = false;
        else if (keynum == 17) guac_keyboard.modifiers.ctrl  = false;
        else if (keynum == 18) guac_keyboard.modifiers.alt   = false;
        else
            stopRepeat();

        // Get corresponding character
        var lastKeyDownChar = keydownChar[keynum];

        // Clear character record
        keydownChar[keynum] = null;

        // Send release event
        return sendKeyReleased(lastKeyDownChar);

    };

    // When focus is lost, clear modifiers.
    element.onblur = function() {
        guac_keyboard.modifiers.alt = false;
        guac_keyboard.modifiers.ctrl = false;
        guac_keyboard.modifiers.shift = false;
    };

};
<|endoftext|>


<commit_before>/* This file is used for processing all forms except registration in the system */
// TODO: event submission is currently handled in the events middleware, we might want to migrate that here
const keystone	= require('keystone');

exports.submitCarDonation = function submitCarDonation( req, res, next ) {
	// TODO: fill in the email submissions for car donation when handling the email system tasks
	req.flash( 'info', { title: 'Emails will be sent once that portion of the system is built out' } );
		// reload the form to display the flash message
		res.redirect( '/forms/car-donation-form' );
}<commit_msg>#82 | add form middleware with placeholder flash messaging for have a question form<commit_after>/* This file is used for processing all forms except registration in the system */
// TODO: event submission is currently handled in the events middleware, we might want to migrate that here
const keystone	= require('keystone');

exports.submitCarDonation = function submitCarDonation( req, res, next ) {
	// TODO: fill in the email submissions for car donation when handling the email system tasks
	req.flash( 'info', { title: 'Emails will be sent once that portion of the system is built out' } );
		// reload the form to display the flash message
		res.redirect( '/forms/car-donation-form' );
}

exports.submitQuestion = function submitQuestion( req, res, next ) {
	// TODO: fill in the email submissions for car donation when handling the email system tasks
	req.flash( 'info', { title: 'Emails will be sent once that portion of the system is built out' } );
		// reload the form to display the flash message
		res.redirect( '/forms/have-a-question-form' );
}<|endoftext|>


<commit_before>/**
 * This test is made to ensure that all known elasticsearch routes that should fire a notification are plugged
 * to the notifier core component.
 *
 * This is to ensure that new routes are firing notifications if they are meant to.
 */
var
  should = require('should'),
  q = require('q'),
  _ = require('lodash'),
  rewire = require('rewire'),
  RequestObject = require.main.require('lib/api/core/models/requestObject'),
  ResponseObject = require.main.require('lib/api/core/models/responseObject'),
  Notifier = rewire('../../../../lib/api/core/notifier'),
  params = require('rc')('kuzzle'),
  Kuzzle = require.main.require('lib/api/Kuzzle');

describe('Test: notifier.checkNewRoutes', function () {
  var
    kuzzle,
    blacklist = [
      'init',
      'search',
      'get',
      'mget',
      'count',
      'deleteCollection',
      'import',
      'createOrReplaceMapping',
      'getMapping',
      'listCollections',
      'deleteIndexes',
      'createCollection',
      'truncateCollection',
      'createIndex',
      'deleteIndex',
      'listIndexes',
      'getInfos'
    ],
    requestObject = new RequestObject({
      controller: 'write',
      action: '',
      requestId: 'foo',
      collection: 'bar',
      index: '%test',
      body: { foo: 'bar' }
    }),
    responseObject = new ResponseObject(requestObject, {});

  before(function () {
    kuzzle = new Kuzzle();
    return kuzzle.start(params, {dummy: true});
  });

  it('should map every notifiable action to a notification handler', function (done) {
    var
      knownActions = [],
      performedActions = [],
      mockupAction = function (responseObject) {
        performedActions.push(responseObject.action);
        return q({});
      };

    this.timeout(100);

    Notifier.__with__({
      notifyDocumentCreate: mockupAction,
      notifyDocumentUpdate: mockupAction,
      notifyDocumentDelete: mockupAction
    })(function () {
      for (action in kuzzle.services.list.readEngine) {
        if (kuzzle.services.list.readEngine.hasOwnProperty(action) && typeof kuzzle.services.list.readEngine[action] === 'function') {
          knownActions.push(action);
          responseObject.action = action;
          (Notifier.__get__('workerNotification')).call(kuzzle, responseObject);
        }
      }

      setTimeout(() => {
        var diff = _.difference(knownActions, performedActions);
        try {
          should(diff.sort()).be.an.Array().and.match(blacklist.sort());
          done();
        }
        catch (e) {
          done(e);
        }
      }, 20);
    });
  });
});
<commit_msg>#KUZ-369 : Bad conflict resolution<commit_after>/**
 * This test is made to ensure that all known elasticsearch routes that should fire a notification are plugged
 * to the notifier core component.
 *
 * This is to ensure that new routes are firing notifications if they are meant to.
 */
var
  should = require('should'),
  q = require('q'),
  _ = require('lodash'),
  rewire = require('rewire'),
  RequestObject = require.main.require('lib/api/core/models/requestObject'),
  ResponseObject = require.main.require('lib/api/core/models/responseObject'),
  Notifier = rewire('../../../../lib/api/core/notifier'),
  params = require('rc')('kuzzle'),
  Kuzzle = require.main.require('lib/api/Kuzzle');

describe('Test: notifier.checkNewRoutes', function () {
  var
    kuzzle,
    blacklist = [
      'init',
      'search',
      'get',
      'mget',
      'count',
      'deleteCollection',
      'import',
      'createOrReplaceMapping',
      'getMapping',
      'listCollections',
      'deleteIndexes',
      'createCollection',
      'truncateCollection',
      'createIndex',
      'deleteIndex',
      'listIndexes',
      'getInfos'
    ],
    requestObject = new RequestObject({
      controller: 'write',
      action: '',
      requestId: 'foo',
      collection: 'bar',
      index: '%test',
      body: { foo: 'bar' }
    }),
    responseObject = new ResponseObject(requestObject, {});

  before(function () {
    kuzzle = new Kuzzle();
    return kuzzle.start(params, {dummy: true});
  });

  it('should map every notifiable action to a notification handler', function (done) {
    var
      knownActions = [],
      performedActions = [],
      mockupAction = function (responseObject) {
        performedActions.push(responseObject.action);
        return q({});
      },
      action;

    this.timeout(100);

    Notifier.__with__({
      notifyDocumentCreate: mockupAction,
      notifyDocumentUpdate: mockupAction,
      notifyDocumentDelete: mockupAction
    })(function () {
      for (action in kuzzle.services.list.readEngine) {
        if (kuzzle.services.list.readEngine.hasOwnProperty(action) && typeof kuzzle.services.list.readEngine[action] === 'function') {
          knownActions.push(action);
          responseObject.action = action;
          (Notifier.__get__('workerNotification')).call(kuzzle, responseObject);
        }
      }

      setTimeout(() => {
        var diff = _.difference(knownActions, performedActions);
        try {
          should(diff.sort()).be.an.Array().and.match(blacklist.sort());
          done();
        }
        catch (e) {
          done(e);
        }
      }, 20);
    });
  });
});
<|endoftext|>


<commit_before>var encryption = require("./Encryption.js");
var hkdf = require("node-hkdf");
var net = require('net');
var server_fac = require("./Server.js");

HAPSession.prototype = {
	updateEncryptTrafficStatus: function updateEncryptTrafficStatus() {
		if (this.shouldEncrypt) {
			this.isEncryptInUse = true;
		}
	},
	sendHAPEvent: function sendHAPEvent(data) {
		if (this.isEncryptInUse && (this.socket !== undefined)) {
			var resultMsg = encryption.layer_encrypt(data,this.accessoryToControllerCount,this.accessoryToControllerKey);
	    	this.socket.write(resultMsg);
		}
	},
	setupSession: function setupSession(socket, hapPort) {
		console.log("New Session", socket.remotePort);

		//From HAP Server
		var serviceSocket = new net.Socket();
		serviceSocket.connect(parseInt(hapPort), "127.0.0.1", function () {
	        console.log("Server Connection Established", serviceSocket.localPort);
	        this.localPort = serviceSocket.localPort;
	        this.registerCallback(this.socket.remotePort, serviceSocket.localPort);
	    }.bind(this));
	    serviceSocket.on("data", function (data) {
	    	if (this.isEncryptInUse) {
				var resultMsg = encryption.layer_encrypt(data,this.accessoryToControllerCount,this.accessoryToControllerKey);
	    		socket.write(resultMsg);
	    	} else {
	    		socket.write(data);
	    		this.updateEncryptTrafficStatus();
	    	}
		}.bind(this));
		serviceSocket.on("close", function() {
		  console.log('Server Disconnected');
		  this.tcpServer.removeSession(this.localPort);
		  socket.end();
		  this.socket = undefined;
		}.bind(this));

		//From iOS
	    socket.on('data', function (msg) {
	    	if (this.isEncryptInUse) {
	    		var resultMsg = encryption.layer_decrypt(msg,this.controllerToAccessoryCount,this.controllerToAccessoryKey);
	    		serviceSocket.write(resultMsg);
	    	} else {
	    		serviceSocket.write(msg);
	    	}
	    }.bind(this));
	    socket.on("close", function() {
		  console.log('Client Disconnected');
		  this.tcpServer.removeSession(this.localPort);
		  this.socket = undefined;
		  serviceSocket.end();
		}.bind(this));
	},
	enableEncryptionForSession: function enableEncryptionForSession(sharedSec) {
		this.shouldEncrypt = true;
		this.sharedSec = sharedSec;
		this.accessoryToControllerCount = {Value: 0};
		this.controllerToAccessoryCount = {Value: 0};

		var enc_salt = new Buffer("Control-Salt");
		var info_read = new Buffer("Control-Read-Encryption-Key");
		var info_write = new Buffer("Control-Write-Encryption-Key");

		var output_key_read = hkdf.HKDF("sha512",enc_salt,sharedSec,info_read,32);
		this.accessoryToControllerKey = output_key_read;

		var output_key_write = hkdf.HKDF("sha512",enc_salt,sharedSec,info_write,32);
		this.controllerToAccessoryKey = output_key_write;
	}
}

function HAPSession(tcpServer, socket, hapPort, persistStore, accessoryInfo, callback) {
	if (!(this instanceof HAPSession))  {
		return new HAPSession(tcpServer, socket, persistStore, accessoryInfo);
	}
	this.tcpServer = tcpServer;
	this.socket = socket;
	this.persistStore = persistStore;
	this.shouldEncrypt = false;
	this.isEncryptInUse = false;
	this.registerCallback = callback;
	this.setupSession(socket, hapPort);
}

function randomInt (low, high) {
    return Math.floor(Math.random() * (high - low) + low);
}

module.exports = {
	HAPSession: HAPSession
}<commit_msg>Add Error Handler for Sockets<commit_after>var encryption = require("./Encryption.js");
var hkdf = require("node-hkdf");
var net = require('net');
var server_fac = require("./Server.js");

HAPSession.prototype = {
	updateEncryptTrafficStatus: function updateEncryptTrafficStatus() {
		if (this.shouldEncrypt) {
			this.isEncryptInUse = true;
		}
	},
	sendHAPEvent: function sendHAPEvent(data) {
		if (this.isEncryptInUse && (this.socket !== undefined)) {
			var resultMsg = encryption.layer_encrypt(data,this.accessoryToControllerCount,this.accessoryToControllerKey);
			try {
				this.socket.write(resultMsg);
			} catch (ex) {
				console.log("An Error Occured when sending events,",ex);
			}
		}
	},
	setupSession: function setupSession(socket, hapPort) {
		console.log("New Session", socket.remotePort);

		//From HAP Server
		var serviceSocket = new net.Socket();
		serviceSocket.connect(parseInt(hapPort), "127.0.0.1", function () {
	        console.log("Server Connection Established", serviceSocket.localPort);
	        this.localPort = serviceSocket.localPort;
	        this.registerCallback(this.socket.remotePort, serviceSocket.localPort);
	    }.bind(this));
	    serviceSocket.on("data", function (data) {
	    	if (this.isEncryptInUse) {
				var resultMsg = encryption.layer_encrypt(data,this.accessoryToControllerCount,this.accessoryToControllerKey);
	    		try {
					socket.write(resultMsg);
				} catch (ex) {
					console.log("An Error Occured when sending data from server,",ex);
				}
	    	} else {
	    		try {
					socket.write(data);
				} catch (ex) {
					console.log("An Error Occured when sending data from server,",ex);
				}
	    		this.updateEncryptTrafficStatus();
	    	}
		}.bind(this));
		serviceSocket.on("close", function() {
		  console.log('Server Disconnected');
		  this.tcpServer.removeSession(this.localPort);
		  try {
		  	socket.end();
		  } catch (ex) {
		  	console.log("An Error Occured when closing the socket",ex);
		  }
		  this.socket = undefined;
		}.bind(this));
		serviceSocket.on("error", function(err){
        	console.log("An Error Occured on HAP side connection,",err);
        	socket.end();
    	});

		//From iOS
	    socket.on('data', function (msg) {
	    	if (this.isEncryptInUse) {
	    		var resultMsg = encryption.layer_decrypt(msg,this.controllerToAccessoryCount,this.controllerToAccessoryKey);
	    		try {
					serviceSocket.write(resultMsg);
				} catch (ex) {
					console.log("An Error Occured when sending data from server,",ex);
				}
	    	} else {
	    		try {
					serviceSocket.write(msg);
				} catch (ex) {
					console.log("An Error Occured when sending data from server,",ex);
				}
	    	}
	    }.bind(this));
	    socket.on("close", function() {
		  console.log('Client Disconnected');
		  this.tcpServer.removeSession(this.localPort);
		  try {
		  	serviceSocket.end();
		  } catch (ex) {
		  	console.log("An Error Occured when closing socket to HAP Server,",ex);
		  }
		  this.socket = undefined;
		}.bind(this));
		socket.on("error", function(err){
        	console.log("An Error Occured on client side connection,",err);
        	serviceSocket.end();
    	});
	},
	enableEncryptionForSession: function enableEncryptionForSession(sharedSec) {
		this.shouldEncrypt = true;
		this.sharedSec = sharedSec;
		this.accessoryToControllerCount = {Value: 0};
		this.controllerToAccessoryCount = {Value: 0};

		var enc_salt = new Buffer("Control-Salt");
		var info_read = new Buffer("Control-Read-Encryption-Key");
		var info_write = new Buffer("Control-Write-Encryption-Key");

		var output_key_read = hkdf.HKDF("sha512",enc_salt,sharedSec,info_read,32);
		this.accessoryToControllerKey = output_key_read;

		var output_key_write = hkdf.HKDF("sha512",enc_salt,sharedSec,info_write,32);
		this.controllerToAccessoryKey = output_key_write;
	}
}

function HAPSession(tcpServer, socket, hapPort, persistStore, accessoryInfo, callback) {
	if (!(this instanceof HAPSession))  {
		return new HAPSession(tcpServer, socket, persistStore, accessoryInfo);
	}
	this.tcpServer = tcpServer;
	this.socket = socket;
	this.persistStore = persistStore;
	this.shouldEncrypt = false;
	this.isEncryptInUse = false;
	this.registerCallback = callback;
	this.setupSession(socket, hapPort);
}

function randomInt (low, high) {
    return Math.floor(Math.random() * (high - low) + low);
}

module.exports = {
	HAPSession: HAPSession
}<|endoftext|>


<commit_before>const isStatefulComponent = component =>
  typeof component.type !== 'string' && component.type.prototype && component.type.prototype.render;

const isStatefulComponentType = componentType =>
  typeof componentType !== 'string' && componentType.prototype && componentType.prototype.render;

export {
  isStatefulComponent,
  isStatefulComponentType,
};
<commit_msg>Trying again. Seems to be trapped between linting rules.<commit_after>const isStatefulComponent = component => typeof component.type !== 'string' && component.type.prototype && component.type.prototype.render;

const isStatefulComponentType = componentType => typeof componentType !== 'string' && componentType.prototype && componentType.prototype.render;

export {
  isStatefulComponent,
  isStatefulComponentType,
};
<|endoftext|>


<commit_before>module.exports = {};
const api = require('./api');
const emailValidator = require('email-validator');

module.exports.validateUsernameLocally = username => {
    if (!username || username === '') {
        return {valid: false, errMsgId: 'general.required'};
    } else if (username.length < 3) {
        return {valid: false, errMsgId: 'registration.validationUsernameMinLength'};
    } else if (username.length > 20) {
        return {valid: false, errMsgId: 'registration.validationUsernameMaxLength'};
    } else if (!/^[\w-]+$/i.test(username)) {
        return {valid: false, errMsgId: 'registration.validationUsernameRegexp'};
    }
    return {valid: true};
};

module.exports.validateUsernameRemotely = username => (
    new Promise(resolve => {
        api({
            uri: `/accounts/checkusername/${username}/`
        }, (err, body, res) => {
            if (err || res.statusCode !== 200) {
                resolve({requestSucceeded: false, valid: false, errMsgId: 'general.error'});
            }
            switch (body.msg) {
            case 'valid username':
                resolve({requestSucceeded: true, valid: true});
                break;
            case 'username exists':
                resolve({requestSucceeded: true, valid: false, errMsgId: 'registration.validationUsernameExists'});
                break;
            case 'bad username': // i.e., vulgar
                resolve({requestSucceeded: true, valid: false, errMsgId: 'registration.validationUsernameNotAllowed'});
                break;
            case 'invalid username':
            default:
                resolve({requestSucceeded: true, valid: false, errMsgId: 'registration.validationUsernameNotAllowed'});
            }
        });
    })
);

/**
 * Validate password value, optionally also considering username value
 * @param  {string} password     password value to validate
 * @param  {string} username     username value to compare
 * @return {object}              {valid: boolean, errMsgId: string}
 */
module.exports.validatePassword = (password, username) => {
    if (!password) {
        return {valid: false, errMsgId: 'general.required'};
    } else if (password.length < 6) {
        return {valid: false, errMsgId: 'registration.validationPasswordLength'};
    } else if (password === 'password') {
        return {valid: false, errMsgId: 'registration.validationPasswordNotEquals'};
    } else if (username && password === username) {
        return {valid: false, errMsgId: 'registration.validationPasswordNotUsername'};
    }
    return {valid: true};
};

module.exports.validatePasswordConfirm = (password, passwordConfirm) => {
    if (!passwordConfirm) {
        return {valid: false, errMsgId: 'general.required'};
    } else if (password !== passwordConfirm) {
        return {valid: false, errMsgId: 'registration.validationPasswordConfirmNotEquals'};
    }
    return {valid: true};
};

module.exports.validateEmailLocally = email => {
    if (!email || email === '') {
        return {valid: false, errMsgId: 'general.required'};
    } else if (emailValidator.validate(email)) {
        return {valid: true};
    }
    return ({valid: false, errMsgId: 'registration.validationEmailInvalid'});
};

module.exports.validateEmailRemotely = email => (
    new Promise(resolve => {
        api({
            host: '', // not handled by API; use existing infrastructure
            params: {email: email},
            uri: '/accounts/check_email/'
        }, (err, body, res) => {
            if (err || res.statusCode !== 200 || !body || body.length < 1 || !body[0].msg) {
                resolve({requestSucceeded: false, valid: false, errMsgId: 'general.apiError'});
            }
            switch (body[0].msg) {
            case 'valid email':
                resolve({requestSucceeded: true, valid: true});
                break;
            case 'Scratch is not allowed to send email to this address.': // e.g., bad TLD or block-listed
            case 'Enter a valid email address.':
            default:
                resolve({requestSucceeded: true, valid: false, errMsgId: 'registration.validationEmailInvalid'});
                break;
            }
        });
    })
);

const responseErrorMsgs = module.exports.responseErrorMsgs = {
    username: {
        'username exists': {errMsgId: 'registration.errorUsernameExists'},
        'bad username': {errMsgId: 'registration.errorBadUsername'}
    },
    password: {
        'Ensure this value has at least 6 characters \\(it has \\d\\).': {
            errMsgId: 'registration.errorPasswordTooShort'
        }
    },
    recaptcha: {
        'Incorrect, please try again.': {errMsgId: 'registration.errorCaptcha'}
    }
};

module.exports.responseErrorMsg = (fieldName, serverRawErr) => {
    if (fieldName && responseErrorMsgs[fieldName]) {
        const serverErrPatterns = responseErrorMsgs[fieldName];
        // use regex compare to find matching error string in responseErrorMsgs
        const matchingKey = Object.keys(serverErrPatterns).find(errPattern => (
            RegExp(errPattern).test(serverRawErr)
        ));
        if (matchingKey) return responseErrorMsgs[fieldName][matchingKey].errMsgId;
    }
    return null;
};
<commit_msg>Switch to using scratchr2 check_username endpoing instead of api. There are some cleanspeak differences with the api one that need to be resolved.<commit_after>module.exports = {};
const api = require('./api');
const emailValidator = require('email-validator');

module.exports.validateUsernameLocally = username => {
    if (!username || username === '') {
        return {valid: false, errMsgId: 'general.required'};
    } else if (username.length < 3) {
        return {valid: false, errMsgId: 'registration.validationUsernameMinLength'};
    } else if (username.length > 20) {
        return {valid: false, errMsgId: 'registration.validationUsernameMaxLength'};
    } else if (!/^[\w-]+$/i.test(username)) {
        return {valid: false, errMsgId: 'registration.validationUsernameRegexp'};
    }
    return {valid: true};
};

module.exports.validateUsernameRemotely = username => (
    new Promise(resolve => {
        api({
            host: '', // not handled by API; use existing infrastructure
            uri: `/accounts/check_username/${username}/`
        }, (err, body, res) => {
            if (err || res.statusCode !== 200) {
                resolve({requestSucceeded: false, valid: false, errMsgId: 'general.error'});
            }
            let msg = '';
            if (body && body[0]) {
                msg = body[0].msg;
            }
            switch (msg) {
            case 'valid username':
                resolve({requestSucceeded: true, valid: true});
                break;
            case 'username exists':
                resolve({requestSucceeded: true, valid: false, errMsgId: 'registration.validationUsernameExists'});
                break;
            case 'bad username': // i.e., vulgar
                resolve({requestSucceeded: true, valid: false, errMsgId: 'registration.validationUsernameNotAllowed'});
                break;
            case 'invalid username':
            default:
                resolve({requestSucceeded: true, valid: false, errMsgId: 'registration.validationUsernameNotAllowed'});
            }
        });
    })
);

/**
 * Validate password value, optionally also considering username value
 * @param  {string} password     password value to validate
 * @param  {string} username     username value to compare
 * @return {object}              {valid: boolean, errMsgId: string}
 */
module.exports.validatePassword = (password, username) => {
    if (!password) {
        return {valid: false, errMsgId: 'general.required'};
    } else if (password.length < 6) {
        return {valid: false, errMsgId: 'registration.validationPasswordLength'};
    } else if (password === 'password') {
        return {valid: false, errMsgId: 'registration.validationPasswordNotEquals'};
    } else if (username && password === username) {
        return {valid: false, errMsgId: 'registration.validationPasswordNotUsername'};
    }
    return {valid: true};
};

module.exports.validatePasswordConfirm = (password, passwordConfirm) => {
    if (!passwordConfirm) {
        return {valid: false, errMsgId: 'general.required'};
    } else if (password !== passwordConfirm) {
        return {valid: false, errMsgId: 'registration.validationPasswordConfirmNotEquals'};
    }
    return {valid: true};
};

module.exports.validateEmailLocally = email => {
    if (!email || email === '') {
        return {valid: false, errMsgId: 'general.required'};
    } else if (emailValidator.validate(email)) {
        return {valid: true};
    }
    return ({valid: false, errMsgId: 'registration.validationEmailInvalid'});
};

module.exports.validateEmailRemotely = email => (
    new Promise(resolve => {
        api({
            host: '', // not handled by API; use existing infrastructure
            params: {email: email},
            uri: '/accounts/check_email/'
        }, (err, body, res) => {
            if (err || res.statusCode !== 200 || !body || body.length < 1 || !body[0].msg) {
                resolve({requestSucceeded: false, valid: false, errMsgId: 'general.apiError'});
            }
            switch (body[0].msg) {
            case 'valid email':
                resolve({requestSucceeded: true, valid: true});
                break;
            case 'Scratch is not allowed to send email to this address.': // e.g., bad TLD or block-listed
            case 'Enter a valid email address.':
            default:
                resolve({requestSucceeded: true, valid: false, errMsgId: 'registration.validationEmailInvalid'});
                break;
            }
        });
    })
);

const responseErrorMsgs = module.exports.responseErrorMsgs = {
    username: {
        'username exists': {errMsgId: 'registration.errorUsernameExists'},
        'bad username': {errMsgId: 'registration.errorBadUsername'}
    },
    password: {
        'Ensure this value has at least 6 characters \\(it has \\d\\).': {
            errMsgId: 'registration.errorPasswordTooShort'
        }
    },
    recaptcha: {
        'Incorrect, please try again.': {errMsgId: 'registration.errorCaptcha'}
    }
};

module.exports.responseErrorMsg = (fieldName, serverRawErr) => {
    if (fieldName && responseErrorMsgs[fieldName]) {
        const serverErrPatterns = responseErrorMsgs[fieldName];
        // use regex compare to find matching error string in responseErrorMsgs
        const matchingKey = Object.keys(serverErrPatterns).find(errPattern => (
            RegExp(errPattern).test(serverRawErr)
        ));
        if (matchingKey) return responseErrorMsgs[fieldName][matchingKey].errMsgId;
    }
    return null;
};
<|endoftext|>


<commit_before>var BoardClass = function(){
	this.piecePosition = {col: 0, row: 0};

	this.boardState = [
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' ']
	];

	this.cols = this.boardState[0].length;
	this.rows = this.boardState.length;
	this.piece = [];
};

BoardClass.prototype = function(){

	var pieces = [
		[
			['#',' '],
			['#',' '],
			['#','#']
		],
		[
			[' ','#'],
			[' ','#'],
			['#','#']
		],
		[
			['#','#',' '],
			[' ','#','#']
		],
		[
			[' ','#','#'],
			['#','#',' ']
		],
		[
			['#','#','#'],
			[' ','#',' ']
		],
		[
			['#','#'],
			['#','#']
		],
		[
			['#'],
			['#'],
			['#'],
			['#']
		]
	];

	var start = function(){
		this.piece = getRandomPiece();
	}

	function getRandomArbitrary(min, max) {
	  return Math.floor(Math.random() * (max - min)) + min;
	}

	function getRandomPiece(){
		return pieces[ getRandomArbitrary(0, pieces.length)];
	}

	var fixPieceOnBoard = function(){
		var piecePosition = this.piecePosition;
		for(var pieceRow = 0; pieceRow < this.piece.length; pieceRow++){
			for(var pieceCol = 0; pieceCol < this.piece[pieceRow].length; pieceCol++){
				if(this.piece[pieceRow][pieceCol] == '#')
					this.boardState[piecePosition.row+pieceRow][piecePosition.col+pieceCol] = '#';
			}
		}
	}

	var rotateThisPiece = function(pieceToRotate){
		var newPiece = new Array(pieceToRotate[0].length);
		for(var npieceRow = 0; npieceRow < newPiece.length; npieceRow++){
			newPiece[npieceRow] = new Array(pieceToRotate.length);
		}

		for(var pieceRow = 0; pieceRow < pieceToRotate.length; pieceRow++){
			for(var pieceCol = pieceToRotate[pieceRow].length -1; pieceCol >= 0; pieceCol--){
				var pos = pieceToRotate[pieceRow].length -1 - pieceCol;
				newPiece[pos][pieceRow] = pieceToRotate[pieceRow][pieceCol];
			}
		}
		return newPiece;
	}

	var rotatePiece = function(){
		var piecePosition = this.piecePosition;
		this.piece = rotateThisPiece.call(this,this.piece);
		while(piecePosition.col+this.piece[0].length > this.cols){
			piecePosition.col--;
		}
	}

	var throwANewPiece = function(){
		this.piece = pieces[ getRandomArbitrary(0, pieces.length)];
		this.piecePosition.row = 0;
		this.piecePosition.col = getRandomArbitrary(0,this.cols-this.piece[0].length +1);
	}

	var thereIsSomethingBelowThePiece = function(){
		var piecePosition = this.piecePosition;
		for(var pieceRow = 0; pieceRow < this.piece.length; pieceRow++){
			for(var pieceCol = 0; pieceCol < this.piece[pieceRow].length; pieceCol++){
				if(this.piece[pieceRow][pieceCol] == '#' && this.boardState[piecePosition.row+pieceRow+1][piecePosition.col+pieceCol] == '#'){
					return true;
				}
			}
		}
		return false;
	}

	function checkIfLineIsFull(line){
		for(var col = 0; col < this.cols; col++){
			if(this.boardState[line][col] == ' ')
				return false;
		}
		return true;
	}

	var dropLinesAbove = function(line){
		for(var row = line; row > 0; row--){
			for(var col = 0; col < this.cols; col++){
				this.boardState[row][col] = this.boardState[row-1][col];
			}
		}
	}

	var removeLines = function(){
		var linesToRemove = [];
		for(var row = this.rows -1 ; row >= 0; row--){
			if(checkIfLineIsFull.call(this,row)){
				linesToRemove.push(row);
			}
		}

		var removedCount = 0;
		for(var line = 0; line < linesToRemove.length; line++){
			dropLinesAbove.call(this,linesToRemove[line]+removedCount);
			removedCount++;
		}
	}

	var step = function(){
		if(this.piecePosition.row+this.piece.length == this.rows || thereIsSomethingBelowThePiece.call(this)){
			fixPieceOnBoard.call(this);
			throwANewPiece.call(this);
			removeLines.call(this);
		}else{
			this.piecePosition.row+=1;
		}
	}

	var getPieceWithPosition = function(){
		return {
			piece : this.piece,
			col : this.piecePosition.col,
			row : this.piecePosition.row
		};
	}

	function moveRight(){
		if(this.piecePosition.col + this.piece[0].length < this.cols)
			this.piecePosition.col++;
	}

	function moveLeft(){
		if(this.piecePosition.col > 0)
			this.piecePosition.col--;
	}

	return {
		start : start,
		step : step,
		getPieceWithPosition : getPieceWithPosition,
		moveRight : moveRight,
		moveLeft : moveLeft,
		rotatePiece : rotatePiece
	};
}();

var Board = new BoardClass();
var Board2 = new BoardClass();<commit_msg>Minor refactoring<commit_after>var BoardClass = function(){
	this.piecePosition = {col: 0, row: 0};

	this.boardState = [
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' '],
		[' ',' ',' ',' ',' ',' ',' ',' ',' ',' ']
	];

	this.cols = this.boardState[0].length;
	this.rows = this.boardState.length;
	this.piece = [];
};

BoardClass.prototype = function(){

	var pieces = [
		[
			['#',' '],
			['#',' '],
			['#','#']
		],
		[
			[' ','#'],
			[' ','#'],
			['#','#']
		],
		[
			['#','#',' '],
			[' ','#','#']
		],
		[
			[' ','#','#'],
			['#','#',' ']
		],
		[
			['#','#','#'],
			[' ','#',' ']
		],
		[
			['#','#'],
			['#','#']
		],
		[
			['#'],
			['#'],
			['#'],
			['#']
		]
	];

	function start(){
		this.piece = getRandomPiece();
	}

	function getRandomArbitrary(min, max) {
	  return Math.floor(Math.random() * (max - min)) + min;
	}

	function getRandomPiece(){
		return pieces[ getRandomArbitrary(0, pieces.length)];
	}

	function fixPieceOnBoard(){
		var piecePosition = this.piecePosition;
		for(var pieceRow = 0; pieceRow < this.piece.length; pieceRow++){
			for(var pieceCol = 0; pieceCol < this.piece[pieceRow].length; pieceCol++){
				if(this.piece[pieceRow][pieceCol] == '#')
					this.boardState[piecePosition.row+pieceRow][piecePosition.col+pieceCol] = '#';
			}
		}
	}

	function rotateThisPiece(pieceToRotate){
		var newPiece = new Array(pieceToRotate[0].length);
		for(var npieceRow = 0; npieceRow < newPiece.length; npieceRow++){
			newPiece[npieceRow] = new Array(pieceToRotate.length);
		}

		for(var pieceRow = 0; pieceRow < pieceToRotate.length; pieceRow++){
			for(var pieceCol = pieceToRotate[pieceRow].length -1; pieceCol >= 0; pieceCol--){
				var pos = pieceToRotate[pieceRow].length -1 - pieceCol;
				newPiece[pos][pieceRow] = pieceToRotate[pieceRow][pieceCol];
			}
		}
		return newPiece;
	}

	function rotatePiece(){
		var piecePosition = this.piecePosition;
		this.piece = rotateThisPiece.call(this,this.piece);
		while(piecePosition.col+this.piece[0].length > this.cols){
			piecePosition.col--;
		}
	}

	function throwANewPiece(){
		this.piece = pieces[ getRandomArbitrary(0, pieces.length)];
		this.piecePosition.row = 0;
		this.piecePosition.col = getRandomArbitrary(0,this.cols-this.piece[0].length +1);
	}

	function thereIsSomethingBelowThePiece(){
		var piecePosition = this.piecePosition;
		for(var pieceRow = 0; pieceRow < this.piece.length; pieceRow++){
			for(var pieceCol = 0; pieceCol < this.piece[pieceRow].length; pieceCol++){
				if(this.piece[pieceRow][pieceCol] == '#' && this.boardState[piecePosition.row+pieceRow+1][piecePosition.col+pieceCol] == '#'){
					return true;
				}
			}
		}
		return false;
	}

	function checkIfLineIsFull(line){
		for(var col = 0; col < this.cols; col++){
			if(this.boardState[line][col] == ' ')
				return false;
		}
		return true;
	}

	function dropLinesAbove(line){
		for(var row = line; row > 0; row--){
			for(var col = 0; col < this.cols; col++){
				this.boardState[row][col] = this.boardState[row-1][col];
			}
		}
	}

	function removeLines(){
		var linesToRemove = [];
		for(var row = this.rows -1 ; row >= 0; row--){
			if(checkIfLineIsFull.call(this,row)){
				linesToRemove.push(row);
			}
		}

		var removedCount = 0;
		for(var line = 0; line < linesToRemove.length; line++){
			dropLinesAbove.call(this,linesToRemove[line]+removedCount);
			removedCount++;
		}
	}

	function step(){
		if(this.piecePosition.row+this.piece.length == this.rows || thereIsSomethingBelowThePiece.call(this)){
			fixPieceOnBoard.call(this);
			throwANewPiece.call(this);
			removeLines.call(this);
		}else{
			this.piecePosition.row+=1;
		}
	}

	function getPieceWithPosition(){
		return {
			piece : this.piece,
			col : this.piecePosition.col,
			row : this.piecePosition.row
		};
	}

	function moveRight(){
		if(this.piecePosition.col + this.piece[0].length < this.cols)
			this.piecePosition.col++;
	}

	function moveLeft(){
		if(this.piecePosition.col > 0)
			this.piecePosition.col--;
	}

	return {
		start : start,
		step : step,
		getPieceWithPosition : getPieceWithPosition,
		moveRight : moveRight,
		moveLeft : moveLeft,
		rotatePiece : rotatePiece
	};
}();

var Board = new BoardClass();
var Board2 = new BoardClass();<|endoftext|>


<commit_before>function generateBarChart(id,data){

    var margin = {top: 0, right: 80, bottom: 30, left: 30},
        width = $(id).width() - margin.left - margin.right,
        height =  $(id).height() - margin.top - margin.bottom;

    var chart = d3.select(".chart")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var x = d3.scale.ordinal()
        .rangeRoundBands([0, width], .1);

    var y = d3.scale.linear()
        .range([height, 0]);

    var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom");

    var yAxis = d3.svg.axis()
        .scale(y)
        .orient("left")
        .ticks(5);

    var yGrid = d3.svg.axis()
        .scale(y)
        .orient("left")
        .tickSize(-width, 0, 0)
        .tickFormat("")
        .ticks(5);

    var svg = d3.select(id).append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var max = d3.max(data, function(d) { return d.newCases; });

    x.domain(data.map(function(d) { return formatDate(d.Date); }));
    y.domain([0, max]);

    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

    svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);

    svg.append("g")
        .attr("class", "grid")
        .call(yGrid);

    svg.selectAll(".totalbar")
        .data(data)
    .enter().append("rect")
        .attr("class", "totalbar")
        .attr("x", function(d) { return x(formatDate(d.Date)); })
        .attr("width", x.rangeBand()/2)
        .attr("y", function(d) { return y(d.newCases); })
        .attr("height", function(d) {
           return height - y(d.newCases);});

    svg.selectAll(".deathbar")
        .data(data)
    .enter().append("rect")
        .attr("class", "deathbar")
        .attr("x", function(d) { return x(formatDate(d.Date))+x.rangeBand()/2; })
        .attr("width", x.rangeBand()/2)
        .attr("y", function(d) { return y(d.newDeaths); })
        .attr("height", function(d) {
           return height - y(d.newDeaths);});

    svg.selectAll(".current")
        .data(data)
    .enter().append("rect")
        .attr("class", "current")
        .attr("x", function(d) { return x(formatDate(d.Date)); })
        .attr("width", x.rangeBand())
        .attr("y", function(d) { return y(max); })
        .attr("height", function(d) {
           return height - y(max);})
        .attr("opacity",0)
        .attr("id",function(d,i){return "barSelect"+i;})
        .on("click",function(d,i){
            d3.select("#barSelect"+currentWeek).attr("opacity",0);
            currentWeek=i;
            d3.select("#barSelect"+currentWeek).attr("opacity",0.15);
            transitionMap();
        });

    var g = svg.append("g");

    g.append("rect")
        .attr("x", width+10)
        .attr("y", 10)
        .attr("width", 10)
        .attr("height", 10)
        .attr("fill","steelblue");

    g.append("rect")
        .attr("x", width+10)
        .attr("y", 30)
        .attr("width", 10)
        .attr("height", 10)
        .attr("fill","red");

    g.append("text")
        .attr("x",width+25)
        .attr("y",18)
        .text("New Cases")
        .attr("font-size","10px");

    g.append("text")
        .attr("x",width+25)
        .attr("y",38)
        .text("Deaths")
        .attr("font-size","10px");

}

function generateMap(){
    var margin = {top: 10, right: 10, bottom: 10, left: 10},
    width = $('#map').width() - margin.left - margin.right,
    height = 425;

    var projection = d3.geo.mercator()
        .center([-5,7.7])
        .scale(1500);

    var svg = d3.select('#map').append("svg")
        .attr("width", width)
        .attr("height", height);

    var path = d3.geo.path()
        .projection(projection);

    var g = svg.append("g");

    g.selectAll("path")
        .data(westafrica.features)
        .enter()
        .append("path")
        .attr("d", path)
        .attr("stroke",'#aaaaaa')
        .attr("fill",'#ffffff')
        .attr("class","country");

    var g = svg.append("g");

    g.selectAll("path")
        .data(regions.features)
        .enter()
        .append("path")
        .attr("d", path)
        .attr("stroke",'#aaaaaa')
        .attr("fill",'#ff0000')
        .attr("opacity",0)
        .attr("id",function(d){return d.properties.NAME_REF.replace(/\s/g, '');})
        .attr("class","region");

    var g = svg.append("g");

    g.selectAll("path")
        .data(blockades_geo.features)
        .enter()
        .append("path")
        .attr("d", path)
        .attr("stroke",'brown')
        .attr("fill","none")
        .attr("stroke-width",2)
        .attr("opacity",1)
        .attr("class","blockade")
        .attr("id",function(d){
                    return d.properties.Border_ID;});

    var g = svg.append("g");

    g.selectAll("circles")
        .data(medical_centres_geo.features)
        .enter()
        .append("circle")
        .attr('cx',function(d){
                    var point = projection([ d.geometry.coordinates[0], d.geometry.coordinates[1] ]);
                    return point[0];
                })
        .attr('cy',function(d){
                    var point = projection([ d.geometry.coordinates[0], d.geometry.coordinates[1] ]);
                    return point[1];
                })
        .attr("r", 5)
        .attr("id",function(d){
                    return d.properties.ID;
        })
        .attr("class","medical_centres")
        .attr("fill",function(d){
            if(d.properties.Type=="Isolation"){
                return "green";
            }else{
                return "blue";
            }
                })
        .attr("opacity",0.7);

    var g = svg.append("g");

    g.append("rect")
        .attr("x", 0)
        .attr("y", 350)
        .attr("width", 10)
        .attr("height", 10)
        .attr("fill","#ff9999");

    g.append("text")
        .attr("x",15)
        .attr("y",358)
        .text("Confirmed Cases")
        .attr("font-size","10px");

    g.append("rect")
        .attr("x", 0)
        .attr("y", 374)
        .attr("width", 10)
        .attr("height", 2)
        .attr("fill","brown");

    g.append("text")
        .attr("x",15)
        .attr("y",378)
        .text("Blockade")
        .attr("font-size","10px");

    g.append("circle")
        .attr("cx",5)
        .attr("cy",335)
        .attr("r",5)
        .attr("fill","green");

    g.append("text")
        .attr("x",15)
        .attr("y",338)
        .text("Referral Centre")
        .attr("font-size","10px");

    g.append("circle")
        .attr("cx",5)
        .attr("cy",315)
        .attr("r",5)
        .attr("fill","blue");

    g.append("text")
        .attr("x",15)
        .attr("y",318)
        .text("Treatment")
        .attr("font-size","10px");

    var mapLabels = svg.append("g");

    mapLabels.selectAll('text')
      .data(westafrica.features)
         .enter()
         .append("text")
         .attr("x", function(d,i){
                     return path.centroid(d)[0]-20;})
         .attr("y", function(d,i){
                     return path.centroid(d)[1];})
         .attr("dy", ".55em")
         .attr("class","maplabel")
         .style("font-size","20px")
         .attr("opacity",0.4)
         .text(function(d,i){
                      return d.properties.NAME_REF;
                  });

}

function transitionMap(){

    $('#week').html("
Map for week ending " + mapSettings[currentWeek].Date + "
");

    var projection = d3.geo.mercator()
        .center([mapSettings[currentWeek].lng,mapSettings[currentWeek].lat])
        .scale(mapSettings[currentWeek].scale);

    var path = d3.geo.path()
        .projection(projection);

    d3.selectAll('.country').transition()
            .attr('d', path);

    d3.selectAll('.blockade').transition()
            .attr('d', path);

    d3.selectAll('.maplabel').transition()
        .attr("x", function(d,i){
                     return path.centroid(d)[0]-20;})
        .attr("y", function(d,i){
                     return path.centroid(d)[1];});

    //d3.selectAll(".medical_centres").transition();

    var data = regionDeaths[currentWeek].Deaths;
    data.forEach(function(element){
               d3.select("#"+element.Region.replace(/\s/g, ''))
                        .transition()
                        .attr("opacity",convertDeathsToOpacity(element.Deaths))
                        .attr('d', path);
            });

    var data = blockades[currentWeek].blockades;
    data.forEach(function(element){
               d3.select("#"+element.blockade_id.split(' ').join('_'))
                        .transition()
                        .attr("opacity",convertBlockadesToOpacity(element.enforced))
                        .attr('d', path);
            });

    var data = medical_centres[currentWeek].medical_centres;
    data.forEach(function(element){
               d3.select("#"+element.id.split(' ').join('_'))
                        .transition()
                        .attr("opacity",convertMedicalCentresToOpacity(element.open))
                        .attr('cx',function(d){
                                    var point = projection([ d.geometry.coordinates[0], d.geometry.coordinates[1] ]);
                                    return point[0];
                                })
                        .attr('cy',function(d){
                                    var point = projection([ d.geometry.coordinates[0], d.geometry.coordinates[1] ]);
                                    return point[1];
                                });
            });

}

function convertDeathsToOpacity(deaths){
    var opacity = deaths/200*0.7;
    if(opacity>0){opacity=opacity+0.2;}
    return opacity;
}

function convertBlockadesToOpacity(enforce){
    if(enforce=="F"){
        return 0;
    } else {
        return 0.75;
    }
}

function convertMedicalCentresToOpacity(open){
    if(open==1){
        return 0.75;
    } else {
        return 0;
    }
}

function formatDate(date){
    var month=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
    return date.substring(0,2) + " " + month[parseInt((date.substring(4,5))-1)];
}

var currentWeek=0;
generateBarChart('#bar_chart',totalCasesAndDeaths);
d3.select("#barSelect"+currentWeek).attr("opacity",0.15);
generateMap();
transitionMap();

$(document).keydown(function(e) {
    switch(e.which) {
        case 37:
            d3.select("#barSelect"+currentWeek).attr("opacity",0);
            currentWeek=currentWeek-1;
            if(currentWeek<0){currentWeek=0;}
            d3.select("#barSelect"+currentWeek).attr("opacity",0.15);
            transitionMap();
            break;

        case 39:
            d3.select("#barSelect"+currentWeek).attr("opacity",0);
            currentWeek=currentWeek+1;
            if(currentWeek>totalCasesAndDeaths.length-1){
                currentWeek=totalCasesAndDeaths.length-1;
            }
            d3.select("#barSelect"+currentWeek).attr("opacity",0.15);
            transitionMap();
            break;

        default: return; // exit this handler for other keys
    }
    e.preventDefault(); // prevent the default action (scroll / move caret)
});
<commit_msg>Update country names<commit_after>function generateBarChart(id,data){

    var margin = {top: 0, right: 80, bottom: 30, left: 30},
        width = $(id).width() - margin.left - margin.right,
        height =  $(id).height() - margin.top - margin.bottom;

    var chart = d3.select(".chart")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var x = d3.scale.ordinal()
        .rangeRoundBands([0, width], .1);

    var y = d3.scale.linear()
        .range([height, 0]);

    var xAxis = d3.svg.axis()
        .scale(x)
        .orient("bottom");

    var yAxis = d3.svg.axis()
        .scale(y)
        .orient("left")
        .ticks(5);

    var yGrid = d3.svg.axis()
        .scale(y)
        .orient("left")
        .tickSize(-width, 0, 0)
        .tickFormat("")
        .ticks(5);

    var svg = d3.select(id).append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var max = d3.max(data, function(d) { return d.newCases; });

    x.domain(data.map(function(d) { return formatDate(d.Date); }));
    y.domain([0, max]);

    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

    svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);

    svg.append("g")
        .attr("class", "grid")
        .call(yGrid);

    svg.selectAll(".totalbar")
        .data(data)
    .enter().append("rect")
        .attr("class", "totalbar")
        .attr("x", function(d) { return x(formatDate(d.Date)); })
        .attr("width", x.rangeBand()/2)
        .attr("y", function(d) { return y(d.newCases); })
        .attr("height", function(d) {
           return height - y(d.newCases);});

    svg.selectAll(".deathbar")
        .data(data)
    .enter().append("rect")
        .attr("class", "deathbar")
        .attr("x", function(d) { return x(formatDate(d.Date))+x.rangeBand()/2; })
        .attr("width", x.rangeBand()/2)
        .attr("y", function(d) { return y(d.newDeaths); })
        .attr("height", function(d) {
           return height - y(d.newDeaths);});

    svg.selectAll(".current")
        .data(data)
    .enter().append("rect")
        .attr("class", "current")
        .attr("x", function(d) { return x(formatDate(d.Date)); })
        .attr("width", x.rangeBand())
        .attr("y", function(d) { return y(max); })
        .attr("height", function(d) {
           return height - y(max);})
        .attr("opacity",0)
        .attr("id",function(d,i){return "barSelect"+i;})
        .on("click",function(d,i){
            d3.select("#barSelect"+currentWeek).attr("opacity",0);
            currentWeek=i;
            d3.select("#barSelect"+currentWeek).attr("opacity",0.15);
            transitionMap();
        });

    var g = svg.append("g");

    g.append("rect")
        .attr("x", width+10)
        .attr("y", 10)
        .attr("width", 10)
        .attr("height", 10)
        .attr("fill","steelblue");

    g.append("rect")
        .attr("x", width+10)
        .attr("y", 30)
        .attr("width", 10)
        .attr("height", 10)
        .attr("fill","red");

    g.append("text")
        .attr("x",width+25)
        .attr("y",18)
        .text("New Cases")
        .attr("font-size","10px");

    g.append("text")
        .attr("x",width+25)
        .attr("y",38)
        .text("Deaths")
        .attr("font-size","10px");

}

function generateMap(){
    var margin = {top: 10, right: 10, bottom: 10, left: 10},
    width = $('#map').width() - margin.left - margin.right,
    height = 425;

    var projection = d3.geo.mercator()
        .center([-5,7.7])
        .scale(1500);

    var svg = d3.select('#map').append("svg")
        .attr("width", width)
        .attr("height", height);

    var path = d3.geo.path()
        .projection(projection);

    var g = svg.append("g");

    g.selectAll("path")
        .data(westafrica.features)
        .enter()
        .append("path")
        .attr("d", path)
        .attr("stroke",'#aaaaaa')
        .attr("fill",'#ffffff')
        .attr("class","country");

    var g = svg.append("g");

    g.selectAll("path")
        .data(regions.features)
        .enter()
        .append("path")
        .attr("d", path)
        .attr("stroke",'#aaaaaa')
        .attr("fill",'#ff0000')
        .attr("opacity",0)
        .attr("id",function(d){return d.properties.NAME_REF.replace(/\s/g, '');})
        .attr("class","region");

    var g = svg.append("g");

    g.selectAll("path")
        .data(blockades_geo.features)
        .enter()
        .append("path")
        .attr("d", path)
        .attr("stroke",'brown')
        .attr("fill","none")
        .attr("stroke-width",2)
        .attr("opacity",1)
        .attr("class","blockade")
        .attr("id",function(d){
                    return d.properties.Border_ID;});

    var g = svg.append("g");

    g.selectAll("circles")
        .data(medical_centres_geo.features)
        .enter()
        .append("circle")
        .attr('cx',function(d){
                    var point = projection([ d.geometry.coordinates[0], d.geometry.coordinates[1] ]);
                    return point[0];
                })
        .attr('cy',function(d){
                    var point = projection([ d.geometry.coordinates[0], d.geometry.coordinates[1] ]);
                    return point[1];
                })
        .attr("r", 5)
        .attr("id",function(d){
                    return d.properties.ID;
        })
        .attr("class","medical_centres")
        .attr("fill",function(d){
            if(d.properties.Type=="Isolation"){
                return "green";
            }else{
                return "blue";
            }
                })
        .attr("opacity",0.7);

    var g = svg.append("g");

    g.append("rect")
        .attr("x", 0)
        .attr("y", 350)
        .attr("width", 10)
        .attr("height", 10)
        .attr("fill","#ff9999");

    g.append("text")
        .attr("x",15)
        .attr("y",358)
        .text("Confirmed Cases")
        .attr("font-size","10px");

    g.append("rect")
        .attr("x", 0)
        .attr("y", 374)
        .attr("width", 10)
        .attr("height", 2)
        .attr("fill","brown");

    g.append("text")
        .attr("x",15)
        .attr("y",378)
        .text("Blockade")
        .attr("font-size","10px");

    g.append("circle")
        .attr("cx",5)
        .attr("cy",335)
        .attr("r",5)
        .attr("fill","green");

    g.append("text")
        .attr("x",15)
        .attr("y",338)
        .text("Referral Centre")
        .attr("font-size","10px");

    g.append("circle")
        .attr("cx",5)
        .attr("cy",315)
        .attr("r",5)
        .attr("fill","blue");

    g.append("text")
        .attr("x",15)
        .attr("y",318)
        .text("Treatment")
        .attr("font-size","10px");

    var mapLabels = svg.append("g");

    mapLabels.selectAll('text')
      .data(westafrica.features)
         .enter()
         .append("text")
         .attr("x", function(d,i){
                     return path.centroid(d)[0]-20;})
         .attr("y", function(d,i){
                     return path.centroid(d)[1];})
         .attr("dy", ".55em")
         .attr("class","maplabel")
         .style("font-size","20px")
         .attr("opacity",0.4)
         .text(function(d,i){
                      return d.properties.NAME;
                  });

}

function transitionMap(){

    $('#week').html("
Map for week ending " + mapSettings[currentWeek].Date + "
");

    var projection = d3.geo.mercator()
        .center([mapSettings[currentWeek].lng,mapSettings[currentWeek].lat])
        .scale(mapSettings[currentWeek].scale);

    var path = d3.geo.path()
        .projection(projection);

    d3.selectAll('.country').transition()
            .attr('d', path);

    d3.selectAll('.blockade').transition()
            .attr('d', path);

    d3.selectAll('.maplabel').transition()
        .attr("x", function(d,i){
                     return path.centroid(d)[0]-20;})
        .attr("y", function(d,i){
                     return path.centroid(d)[1];});

    var data = regionDeaths[currentWeek].Deaths;
    data.forEach(function(element){
               d3.select("#"+element.Region.replace(/\s/g, ''))
                        .transition()
                        .attr("opacity",convertDeathsToOpacity(element.Deaths))
                        .attr('d', path);
            });

    var data = blockades[currentWeek].blockades;
    data.forEach(function(element){
               d3.select("#"+element.blockade_id.split(' ').join('_'))
                        .transition()
                        .attr("opacity",convertBlockadesToOpacity(element.enforced))
                        .attr('d', path);
            });

    var data = medical_centres[currentWeek].medical_centres;
    data.forEach(function(element){
               d3.select("#"+element.id.split(' ').join('_'))
                        .transition()
                        .attr("opacity",convertMedicalCentresToOpacity(element.open))
                        .attr('cx',function(d){
                                    var point = projection([ d.geometry.coordinates[0], d.geometry.coordinates[1] ]);
                                    return point[0];
                                })
                        .attr('cy',function(d){
                                    var point = projection([ d.geometry.coordinates[0], d.geometry.coordinates[1] ]);
                                    return point[1];
                                });
            });

}

function convertDeathsToOpacity(deaths){
    var opacity = deaths/200*0.7;
    if(opacity>0){opacity=opacity+0.2;}
    return opacity;
}

function convertBlockadesToOpacity(enforce){
    if(enforce=="F"){
        return 0;
    } else {
        return 0.75;
    }
}

function convertMedicalCentresToOpacity(open){
    if(open==1){
        return 0.75;
    } else {
        return 0;
    }
}

function formatDate(date){
    var month=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
    return date.substring(0,2) + " " + month[parseInt((date.substring(4,5))-1)];
}

var currentWeek=0;
generateBarChart('#bar_chart',totalCasesAndDeaths);
d3.select("#barSelect"+currentWeek).attr("opacity",0.15);
generateMap();
transitionMap();

$(document).keydown(function(e) {
    switch(e.which) {
        case 37:
            d3.select("#barSelect"+currentWeek).attr("opacity",0);
            currentWeek=currentWeek-1;
            if(currentWeek<0){currentWeek=0;}
            d3.select("#barSelect"+currentWeek).attr("opacity",0.15);
            transitionMap();
            break;

        case 39:
            d3.select("#barSelect"+currentWeek).attr("opacity",0);
            currentWeek=currentWeek+1;
            if(currentWeek>totalCasesAndDeaths.length-1){
                currentWeek=totalCasesAndDeaths.length-1;
            }
            d3.select("#barSelect"+currentWeek).attr("opacity",0.15);
            transitionMap();
            break;

        default: return; // exit this handler for other keys
    }
    e.preventDefault(); // prevent the default action (scroll / move caret)
});
<|endoftext|>