<commit_before>/* This file contains keyboard definitions and constants, plus some conversion
 * functions between dir-vectors and direction keys. */

const ROT = require('../../lib/rot.js');
const RG = require('./rg');

const Keys = {};

/* eslint-disable */
Keys.VK_a = ROT.VK_A + 32;
Keys.VK_b = ROT.VK_B + 32;
Keys.VK_c = ROT.VK_C + 32;
Keys.VK_d = ROT.VK_D + 32;
Keys.VK_e = ROT.VK_E + 32;
Keys.VK_f = ROT.VK_F + 32;
Keys.VK_g = ROT.VK_G + 32;
Keys.VK_h = ROT.VK_H + 32;
Keys.VK_i = ROT.VK_I + 32;
Keys.VK_j = ROT.VK_J + 32;
Keys.VK_k = ROT.VK_K + 32;
Keys.VK_l = ROT.VK_L + 32;
Keys.VK_m = ROT.VK_M + 32;
Keys.VK_n = ROT.VK_N + 32;
Keys.VK_o = ROT.VK_O + 32;
Keys.VK_p = ROT.VK_P + 32;
Keys.VK_q = ROT.VK_Q + 32;
Keys.VK_r = ROT.VK_R + 32;
Keys.VK_s = ROT.VK_S + 32;
Keys.VK_t = ROT.VK_T + 32;
Keys.VK_u = ROT.VK_U + 32;
Keys.VK_v = ROT.VK_V + 32;
Keys.VK_w = ROT.VK_W + 32;
Keys.VK_x = ROT.VK_X + 32;
Keys.VK_y = ROT.VK_Y + 32;
Keys.VK_z = ROT.VK_Z + 32;
/* eslint-enable */

Keys.VK_COMMA = 44;
Keys.VK_PERIOD = 46;
Keys.VK_LT = 60;
Keys.VK_GT = 62;

/* Lookup table object for movement and actions keys.*/
Keys.KeyMap = {

    moveKeyMap: { },

    // Start from W, go clock wise on keyboard
    initMap: function() {
        this.moveKeyMap[Keys.KEY.MOVE_N] = 0;
        this.moveKeyMap[Keys.KEY.MOVE_NE] = 1;
        this.moveKeyMap[Keys.KEY.MOVE_E] = 2;
        this.moveKeyMap[Keys.KEY.MOVE_SE] = 3;
        this.moveKeyMap[Keys.KEY.MOVE_S] = 4;
        this.moveKeyMap[Keys.KEY.MOVE_SW] = 5;
        this.moveKeyMap[Keys.KEY.MOVE_W] = 6;
        this.moveKeyMap[Keys.KEY.MOVE_NW] = 7;

        this.moveKeyMap[ROT.VK_8] = 0;
        this.moveKeyMap[ROT.VK_9] = 1;
        this.moveKeyMap[ROT.VK_6] = 2;
        this.moveKeyMap[ROT.VK_3] = 3;
        this.moveKeyMap[ROT.VK_2] = 4;
        this.moveKeyMap[ROT.VK_1] = 5;
        this.moveKeyMap[ROT.VK_4] = 6;
        this.moveKeyMap[ROT.VK_7] = 7;
    },

    inMoveCodeMap: function(code) {
        return this.moveKeyMap.hasOwnProperty(code);
    },

    isRest: function(code) {
        return code === Keys.VK_s || code === Keys.VK_PERIOD;
    },
    isPickup: function(code) {return code === Keys.KEY.PICKUP;},
    isUseStairs: function(code) {
        return code === Keys.KEY.USE_STAIRS_DOWN ||
            code === Keys.KEY.USE_STAIRS_UP;
    },
    isChat: function(code) {return code === Keys.KEY.CHAT;},
    isConfirmYes: function(code) {return code === Keys.KEY.YES;},
    isFightMode: function(code) {return code === Keys.KEY.FIGHT;},
    isGoto: function(code) {return code === Keys.KEY.GOTO;},
    isIssueOrder: function(code) {return code === Keys.KEY.ORDER;},
    isLook: function(code) {return code === Keys.KEY.LOOK;},
    isMark: function(code) {return code === Keys.KEY.MARK;},
    isNextItem: function(code) {return code === Keys.KEY.NEXT_ITEM;},
    isNextTarget: function(code) {return code === Keys.KEY.NEXT;},
    isPrevTarget: function(code) {return code === Keys.KEY.PREV;},
    isRunMode: function(code) {return code === Keys.KEY.RUN;},
    isSelect: function(code) {return code === Keys.KEY.SELECT;},
    isSelectAll: function(code) {return code === Keys.KEY.SELECT_ALL;},
    isTargetMode: function(code) {return code === Keys.KEY.TARGET;},
    isToggleDoor: function(code) {return code === Keys.KEY.DOOR;},
    isUsePower: function(code) {return code === Keys.KEY.POWER;},

    /* Based on keycode, computes and returns a new x,y pair. If code is
     * invalid, returns null. */
    getDiff: function(code, x, y) {
        if (this.moveKeyMap.hasOwnProperty(code)) {
            const diff = ROT.DIRS[8][this.moveKeyMap[code]];
            const newX = x + diff[0];
            const newY = y + diff[1];
            return [newX, newY];
        }
        else if (code === Keys.VK_s) {
            return [x, y];
        }
        else {
            return null;
        }
    },

    /* Returns a direction vector for given keycode. */
    getDir: function(code) {
        if (this.moveKeyMap.hasOwnProperty(code)) {
            return ROT.DIRS[8][this.moveKeyMap[code]];
        }
        else if (this.isRest(code)) {
            return [0, 0];
        }
        return null;
    },

    /* Converts a direction vector to keycode. */
    dirToKeyCode: function(dXArg, dYArg) {
        // Normalize first to unit vector (-1,0 or 1)
        let dX = dXArg;
        let dY = dYArg;
        if (dX !== 0) {dX = dX / Math.abs(dX);}
        if (dY !== 0) {dY = dY / Math.abs(dY);}
        switch (dX) {
            case -1:
                switch (dY) {
                    case -1: return Keys.KEY.MOVE_NW;
                    case 0: return Keys.KEY.MOVE_W;
                    case 1: return Keys.KEY.MOVE_SW;
                    default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            case 0:
                switch (dY) {
                    case -1: return Keys.KEY.MOVE_N;
                    case 0: return Keys.KEY.REST;
                    case 1: return Keys.KEY.MOVE_S;
                    default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            case 1:
                switch (dY) {
                    case -1: return Keys.KEY.MOVE_NE;
                    case 0: return Keys.KEY.MOVE_E;
                    case 1: return Keys.KEY.MOVE_SE;
                    default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                `Dir ${dX},${dY} not supported`);
        }
        return null;
    },

    keyCodeToCardinalDir: function(code) {
        switch (code) {
            case Keys.KEY.MOVE_NW: return 'NW';
            case Keys.KEY.MOVE_W: return 'W';
            case Keys.KEY.MOVE_SW: return 'SW';
            case Keys.KEY.MOVE_N: return 'N';
            case Keys.KEY.REST: return 'REST';
            case Keys.KEY.MOVE_S: return 'S';
            case Keys.KEY.MOVE_NE: return 'NE';
            case Keys.KEY.MOVE_E: return 'E';
            case Keys.KEY.MOVE_SE: return 'SE';
            default: return '';
        }
    }

};

Keys.menuIndices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f',
    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
    'u', 'v', 'w', 'x', 'y', 'z'
];

/* Given key code, returns the corresponding character in menu indices. */
Keys.codeToMenuChar = code => {
    const index = Keys.codeToIndex(code);
    return Keys.menuIndices[index];
};

/* Convert a selection index into a keycode. For example, if user presses 'a',
 * this function should return keycode for a, ie Keys.VK_a. */
Keys.selectIndexToCode = indexChar => {
    const arrayIndex = Keys.menuIndices.findIndex(val => val === indexChar);
    if (arrayIndex >= 0) {
        if (arrayIndex >= 0 && arrayIndex <= 9) {
            return ROT.VK_0 + arrayIndex;
        }
        else {
            const addToCode = arrayIndex - 10; // Offset in menuIndices
            return Keys.VK_a + addToCode;
        }
    }
    RG.err('RG', 'selectIndexToCode',
        `Inv. select index |${indexChar}|`);
    return -1;

};

/* Converts the keycode into a selection index starting from 0. */
Keys.codeToIndex = code => {
    if (code >= ROT.VK_0 && code <= ROT.VK_9) {
        return code - ROT.VK_0;
    }
    else if (code >= Keys.VK_a && code <= Keys.VK_z) {
        return code - Keys.VK_a + 10;
    }
    return -1;
};

/* Returns true if keyCode corresponds to a numeric key. */
Keys.isNumeric = keyCode => {
    return keyCode >= ROT.VK_0 && keyCode <= ROT.VK_9;
};

Keys.KEY = {};

// Assign ROT keys to meaningful constants
Keys.KEY.MOVE_N = ROT.VK_W + 32;
Keys.KEY.MOVE_NE = ROT.VK_E + 32;
Keys.KEY.MOVE_E = ROT.VK_D + 32;
Keys.KEY.MOVE_SE = ROT.VK_C + 32;
Keys.KEY.MOVE_S = ROT.VK_X + 32;
Keys.KEY.MOVE_SW = ROT.VK_Z + 32;
Keys.KEY.MOVE_W = ROT.VK_A + 32;
Keys.KEY.MOVE_NW = ROT.VK_Q + 32;

Keys.KEY.CHAT = ROT.VK_C;
Keys.KEY.DOOR = ROT.VK_O + 32;
Keys.KEY.FIGHT = ROT.VK_F + 32;
Keys.KEY.GOTO = Keys.VK_g;
Keys.KEY.LOOK = ROT.VK_L + 32;
Keys.KEY.MARK = Keys.VK_b;
Keys.KEY.NEXT = Keys.VK_n;
Keys.KEY.NEXT_ITEM = ROT.VK_H + 32;
Keys.KEY.ORDER = ROT.VK_O;
Keys.KEY.PICKUP = Keys.VK_COMMA;
Keys.KEY.POWER = ROT.VK_P + 32;
Keys.KEY.PREV = ROT.VK_P + 32;
Keys.KEY.QUIT_MENU = Keys.VK_q;
Keys.KEY.REST = ROT.VK_S + 32;
Keys.KEY.RUN = ROT.VK_R + 32;
Keys.KEY.SELECT = Keys.VK_s;
Keys.KEY.SELECT_ALL = ROT.VK_A;
Keys.KEY.TARGET = Keys.VK_t;
Keys.KEY.USE_STAIRS_DOWN = Keys.VK_GT;
Keys.KEY.USE_STAIRS_UP = Keys.VK_LT;
Keys.KEY.YES = ROT.VK_Y + 32;
Keys.KeyMap.initMap();

Keys.KEY.NO_ACTION = ROT.VK_CAPS_LOCK;

// Used in the GUI only
Keys.GUI = {};
Keys.GUI.CharInfo = ROT.VK_I;
Keys.GUI.Goto = Keys.KEY.GOTO;
Keys.GUI.Help = ROT.VK_H;
Keys.GUI.Inv = Keys.VK_i;
Keys.GUI.Look = Keys.VK_l;
Keys.GUI.Map = Keys.VK_m;
Keys.GUI.OwMap = ROT.VK_M;
Keys.GUI.Use = Keys.VK_u;

Keys.isValidKey = keyCode => {
    let found = false;
    Object.keys(Keys.KEY).forEach(key => {
        found = found || Keys.KEY[key] === keyCode;
    });
    found = found || Keys.KeyMap.inMoveCodeMap(keyCode);
    return found;
};

/* Given keycode, returns the valid char for that key. */
Keys.getChar = keyCode => {
    return '`' + String.fromCharCode(keyCode) + '`';
};

module.exports = Keys;
<commit_msg>Added KEY.DELETE.<commit_after>/* This file contains keyboard definitions and constants, plus some conversion
 * functions between dir-vectors and direction keys. */

const ROT = require('../../lib/rot.js');
const RG = require('./rg');

const Keys = {};

/* eslint-disable */
Keys.VK_a = ROT.VK_A + 32;
Keys.VK_b = ROT.VK_B + 32;
Keys.VK_c = ROT.VK_C + 32;
Keys.VK_d = ROT.VK_D + 32;
Keys.VK_e = ROT.VK_E + 32;
Keys.VK_f = ROT.VK_F + 32;
Keys.VK_g = ROT.VK_G + 32;
Keys.VK_h = ROT.VK_H + 32;
Keys.VK_i = ROT.VK_I + 32;
Keys.VK_j = ROT.VK_J + 32;
Keys.VK_k = ROT.VK_K + 32;
Keys.VK_l = ROT.VK_L + 32;
Keys.VK_m = ROT.VK_M + 32;
Keys.VK_n = ROT.VK_N + 32;
Keys.VK_o = ROT.VK_O + 32;
Keys.VK_p = ROT.VK_P + 32;
Keys.VK_q = ROT.VK_Q + 32;
Keys.VK_r = ROT.VK_R + 32;
Keys.VK_s = ROT.VK_S + 32;
Keys.VK_t = ROT.VK_T + 32;
Keys.VK_u = ROT.VK_U + 32;
Keys.VK_v = ROT.VK_V + 32;
Keys.VK_w = ROT.VK_W + 32;
Keys.VK_x = ROT.VK_X + 32;
Keys.VK_y = ROT.VK_Y + 32;
Keys.VK_z = ROT.VK_Z + 32;
/* eslint-enable */

Keys.VK_COMMA = 44;
Keys.VK_PERIOD = 46;
Keys.VK_LT = 60;
Keys.VK_GT = 62;

/* Lookup table object for movement and actions keys.*/
Keys.KeyMap = {

    moveKeyMap: { },

    // Start from W, go clock wise on keyboard
    initMap: function() {
        this.moveKeyMap[Keys.KEY.MOVE_N] = 0;
        this.moveKeyMap[Keys.KEY.MOVE_NE] = 1;
        this.moveKeyMap[Keys.KEY.MOVE_E] = 2;
        this.moveKeyMap[Keys.KEY.MOVE_SE] = 3;
        this.moveKeyMap[Keys.KEY.MOVE_S] = 4;
        this.moveKeyMap[Keys.KEY.MOVE_SW] = 5;
        this.moveKeyMap[Keys.KEY.MOVE_W] = 6;
        this.moveKeyMap[Keys.KEY.MOVE_NW] = 7;

        this.moveKeyMap[ROT.VK_8] = 0;
        this.moveKeyMap[ROT.VK_9] = 1;
        this.moveKeyMap[ROT.VK_6] = 2;
        this.moveKeyMap[ROT.VK_3] = 3;
        this.moveKeyMap[ROT.VK_2] = 4;
        this.moveKeyMap[ROT.VK_1] = 5;
        this.moveKeyMap[ROT.VK_4] = 6;
        this.moveKeyMap[ROT.VK_7] = 7;
    },

    inMoveCodeMap: function(code) {
        return this.moveKeyMap.hasOwnProperty(code);
    },

    isRest: function(code) {
        return code === Keys.VK_s || code === Keys.VK_PERIOD;
    },
    isPickup: function(code) {return code === Keys.KEY.PICKUP;},
    isUseStairs: function(code) {
        return code === Keys.KEY.USE_STAIRS_DOWN ||
            code === Keys.KEY.USE_STAIRS_UP;
    },
    isChat: function(code) {return code === Keys.KEY.CHAT;},
    isConfirmYes: function(code) {return code === Keys.KEY.YES;},
    isFightMode: function(code) {return code === Keys.KEY.FIGHT;},
    isGoto: function(code) {return code === Keys.KEY.GOTO;},
    isIssueOrder: function(code) {return code === Keys.KEY.ORDER;},
    isLook: function(code) {return code === Keys.KEY.LOOK;},
    isMark: function(code) {return code === Keys.KEY.MARK;},
    isNextItem: function(code) {return code === Keys.KEY.NEXT_ITEM;},
    isNextTarget: function(code) {return code === Keys.KEY.NEXT;},
    isPrevTarget: function(code) {return code === Keys.KEY.PREV;},
    isRunMode: function(code) {return code === Keys.KEY.RUN;},
    isSelect: function(code) {return code === Keys.KEY.SELECT;},
    isSelectAll: function(code) {return code === Keys.KEY.SELECT_ALL;},
    isTargetMode: function(code) {return code === Keys.KEY.TARGET;},
    isToggleDoor: function(code) {return code === Keys.KEY.DOOR;},
    isUsePower: function(code) {return code === Keys.KEY.POWER;},

    /* Based on keycode, computes and returns a new x,y pair. If code is
     * invalid, returns null. */
    getDiff: function(code, x, y) {
        if (this.moveKeyMap.hasOwnProperty(code)) {
            const diff = ROT.DIRS[8][this.moveKeyMap[code]];
            const newX = x + diff[0];
            const newY = y + diff[1];
            return [newX, newY];
        }
        else if (code === Keys.VK_s) {
            return [x, y];
        }
        else {
            return null;
        }
    },

    /* Returns a direction vector for given keycode. */
    getDir: function(code) {
        if (this.moveKeyMap.hasOwnProperty(code)) {
            return ROT.DIRS[8][this.moveKeyMap[code]];
        }
        else if (this.isRest(code)) {
            return [0, 0];
        }
        return null;
    },

    /* Converts a direction vector to keycode. */
    dirToKeyCode: function(dXArg, dYArg) {
        // Normalize first to unit vector (-1,0 or 1)
        let dX = dXArg;
        let dY = dYArg;
        if (dX !== 0) {dX = dX / Math.abs(dX);}
        if (dY !== 0) {dY = dY / Math.abs(dY);}
        switch (dX) {
            case -1:
                switch (dY) {
                    case -1: return Keys.KEY.MOVE_NW;
                    case 0: return Keys.KEY.MOVE_W;
                    case 1: return Keys.KEY.MOVE_SW;
                    default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            case 0:
                switch (dY) {
                    case -1: return Keys.KEY.MOVE_N;
                    case 0: return Keys.KEY.REST;
                    case 1: return Keys.KEY.MOVE_S;
                    default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            case 1:
                switch (dY) {
                    case -1: return Keys.KEY.MOVE_NE;
                    case 0: return Keys.KEY.MOVE_E;
                    case 1: return Keys.KEY.MOVE_SE;
                    default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                `Dir ${dX},${dY} not supported`);
        }
        return null;
    },

    keyCodeToCardinalDir: function(code) {
        switch (code) {
            case Keys.KEY.MOVE_NW: return 'NW';
            case Keys.KEY.MOVE_W: return 'W';
            case Keys.KEY.MOVE_SW: return 'SW';
            case Keys.KEY.MOVE_N: return 'N';
            case Keys.KEY.REST: return 'REST';
            case Keys.KEY.MOVE_S: return 'S';
            case Keys.KEY.MOVE_NE: return 'NE';
            case Keys.KEY.MOVE_E: return 'E';
            case Keys.KEY.MOVE_SE: return 'SE';
            default: return '';
        }
    }

};

Keys.menuIndices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f',
    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
    'u', 'v', 'w', 'x', 'y', 'z'
];

/* Given key code, returns the corresponding character in menu indices. */
Keys.codeToMenuChar = code => {
    const index = Keys.codeToIndex(code);
    return Keys.menuIndices[index];
};

/* Convert a selection index into a keycode. For example, if user presses 'a',
 * this function should return keycode for a, ie Keys.VK_a. */
Keys.selectIndexToCode = indexChar => {
    const arrayIndex = Keys.menuIndices.findIndex(val => val === indexChar);
    if (arrayIndex >= 0) {
        if (arrayIndex >= 0 && arrayIndex <= 9) {
            return ROT.VK_0 + arrayIndex;
        }
        else {
            const addToCode = arrayIndex - 10; // Offset in menuIndices
            return Keys.VK_a + addToCode;
        }
    }
    RG.err('RG', 'selectIndexToCode',
        `Inv. select index |${indexChar}|`);
    return -1;

};

/* Converts the keycode into a selection index starting from 0. */
Keys.codeToIndex = code => {
    if (code >= ROT.VK_0 && code <= ROT.VK_9) {
        return code - ROT.VK_0;
    }
    else if (code >= Keys.VK_a && code <= Keys.VK_z) {
        return code - Keys.VK_a + 10;
    }
    return -1;
};

/* Returns true if keyCode corresponds to a numeric key. */
Keys.isNumeric = keyCode => {
    return keyCode >= ROT.VK_0 && keyCode <= ROT.VK_9;
};

Keys.KEY = {};

// Assign ROT keys to meaningful constants
Keys.KEY.MOVE_N = ROT.VK_W + 32;
Keys.KEY.MOVE_NE = ROT.VK_E + 32;
Keys.KEY.MOVE_E = ROT.VK_D + 32;
Keys.KEY.MOVE_SE = ROT.VK_C + 32;
Keys.KEY.MOVE_S = ROT.VK_X + 32;
Keys.KEY.MOVE_SW = ROT.VK_Z + 32;
Keys.KEY.MOVE_W = ROT.VK_A + 32;
Keys.KEY.MOVE_NW = ROT.VK_Q + 32;

Keys.KEY.CHAT = ROT.VK_C;
Keys.KEY.DELETE = ROT.VK_D;
Keys.KEY.DOOR = ROT.VK_O + 32;
Keys.KEY.FIGHT = ROT.VK_F + 32;
Keys.KEY.GOTO = Keys.VK_g;
Keys.KEY.LOOK = ROT.VK_L + 32;
Keys.KEY.MARK = Keys.VK_b;
Keys.KEY.NEXT = Keys.VK_n;
Keys.KEY.NEXT_ITEM = ROT.VK_H + 32;
Keys.KEY.ORDER = ROT.VK_O;
Keys.KEY.PICKUP = Keys.VK_COMMA;
Keys.KEY.POWER = ROT.VK_P + 32;
Keys.KEY.PREV = ROT.VK_P + 32;
Keys.KEY.QUIT_MENU = Keys.VK_q;
Keys.KEY.REST = ROT.VK_S + 32;
Keys.KEY.RUN = ROT.VK_R + 32;
Keys.KEY.SELECT = Keys.VK_s;
Keys.KEY.SELECT_ALL = ROT.VK_A;
Keys.KEY.TARGET = Keys.VK_t;
Keys.KEY.USE_STAIRS_DOWN = Keys.VK_GT;
Keys.KEY.USE_STAIRS_UP = Keys.VK_LT;
Keys.KEY.YES = ROT.VK_Y + 32;
Keys.KeyMap.initMap();

Keys.KEY.NO_ACTION = ROT.VK_CAPS_LOCK;

// Used in the GUI only
Keys.GUI = {};
Keys.GUI.CharInfo = ROT.VK_I;
Keys.GUI.Goto = Keys.KEY.GOTO;
Keys.GUI.Help = ROT.VK_H;
Keys.GUI.Inv = Keys.VK_i;
Keys.GUI.Look = Keys.VK_l;
Keys.GUI.Map = Keys.VK_m;
Keys.GUI.OwMap = ROT.VK_M;
Keys.GUI.Use = Keys.VK_u;

Keys.isValidKey = keyCode => {
    let found = false;
    Object.keys(Keys.KEY).forEach(key => {
        found = found || Keys.KEY[key] === keyCode;
    });
    found = found || Keys.KeyMap.inMoveCodeMap(keyCode);
    return found;
};

/* Given keycode, returns the valid char for that key. */
Keys.getChar = keyCode => {
    return '`' + String.fromCharCode(keyCode) + '`';
};

module.exports = Keys;
<|endoftext|>


<commit_before>/* This file contains keyboard definitions and constants, plus some conversion
 * functions between dir-vectors and direction keys. */

const ROT = require('../../lib/rot.js');
const RG = require('./rg');

const Keys = {};

/* eslint-disable */
Keys.VK_a = ROT.VK_A + 32;
Keys.VK_b = ROT.VK_B + 32;
Keys.VK_c = ROT.VK_C + 32;
Keys.VK_d = ROT.VK_D + 32;
Keys.VK_e = ROT.VK_E + 32;
Keys.VK_f = ROT.VK_F + 32;
Keys.VK_g = ROT.VK_G + 32;
Keys.VK_h = ROT.VK_H + 32;
Keys.VK_i = ROT.VK_I + 32;
Keys.VK_j = ROT.VK_J + 32;
Keys.VK_k = ROT.VK_K + 32;
Keys.VK_l = ROT.VK_L + 32;
Keys.VK_m = ROT.VK_M + 32;
Keys.VK_n = ROT.VK_N + 32;
Keys.VK_o = ROT.VK_O + 32;
Keys.VK_p = ROT.VK_P + 32;
Keys.VK_q = ROT.VK_Q + 32;
Keys.VK_r = ROT.VK_R + 32;
Keys.VK_s = ROT.VK_S + 32;
Keys.VK_t = ROT.VK_T + 32;
Keys.VK_u = ROT.VK_U + 32;
Keys.VK_v = ROT.VK_V + 32;
Keys.VK_w = ROT.VK_W + 32;
Keys.VK_x = ROT.VK_X + 32;
Keys.VK_y = ROT.VK_Y + 32;
Keys.VK_z = ROT.VK_Z + 32;
/* eslint-enable */

Keys.VK_COMMA = 44;
Keys.VK_PERIOD = 46;
Keys.VK_LT = 60;
Keys.VK_GT = 62;

/* Lookup table object for movement and actions keys.*/
Keys.KeyMap = {

    moveKeyMap: { },

    // Start from W, go clock wise on keyboard
    initMap: function() {
        this.moveKeyMap[Keys.KEY.MOVE_N] = 0;
        this.moveKeyMap[Keys.KEY.MOVE_NE] = 1;
        this.moveKeyMap[Keys.KEY.MOVE_E] = 2;
        this.moveKeyMap[Keys.KEY.MOVE_SE] = 3;
        this.moveKeyMap[Keys.KEY.MOVE_S] = 4;
        this.moveKeyMap[Keys.KEY.MOVE_SW] = 5;
        this.moveKeyMap[Keys.KEY.MOVE_W] = 6;
        this.moveKeyMap[Keys.KEY.MOVE_NW] = 7;

        this.moveKeyMap[ROT.VK_8] = 0;
        this.moveKeyMap[ROT.VK_9] = 1;
        this.moveKeyMap[ROT.VK_6] = 2;
        this.moveKeyMap[ROT.VK_3] = 3;
        this.moveKeyMap[ROT.VK_2] = 4;
        this.moveKeyMap[ROT.VK_1] = 5;
        this.moveKeyMap[ROT.VK_4] = 6;
        this.moveKeyMap[ROT.VK_7] = 7;
    },

    inMoveCodeMap: function(code) {
        return this.moveKeyMap.hasOwnProperty(code);
    },

    isRest: function(code) {
        return code === Keys.VK_s || code === Keys.VK_PERIOD;
    },
    isPickup: function(code) {return code === Keys.KEY.PICKUP;},
    isUseStairs: function(code) {
        return code === Keys.KEY.USE_STAIRS_DOWN ||
            code === Keys.KEY.USE_STAIRS_UP;
    },
    isChat: function(code) {return code === Keys.KEY.CHAT;},
    isConfirmYes: function(code) {return code === Keys.KEY.YES;},
    isFightMode: function(code) {return code === Keys.KEY.FIGHT;},
    isGoto: function(code) {return code === Keys.KEY.GOTO;},
    isJump: function(code) {return code === Keys.KEY.JUMP;},
    isIssueOrder: function(code) {return code === Keys.KEY.ORDER;},
    isLook: function(code) {return code === Keys.KEY.LOOK;},
    isMark: function(code) {return code === Keys.KEY.MARK;},
    isNextItem: function(code) {return code === Keys.KEY.NEXT_ITEM;},
    isNextTarget: function(code) {return code === Keys.KEY.NEXT;},
    isPrevTarget: function(code) {return code === Keys.KEY.PREV;},
    isRunMode: function(code) {return code === Keys.KEY.RUN;},
    isSelect: function(code) {return code === Keys.KEY.SELECT;},
    isSelectAll: function(code) {return code === Keys.KEY.SELECT_ALL;},
    isTargetMode: function(code) {return code === Keys.KEY.TARGET;},
    isToggleDoor: function(code) {return code === Keys.KEY.DOOR;},
    isUsePower: function(code) {return code === Keys.KEY.POWER;},

    /* Based on keycode, computes and returns a new x,y pair. If code is
     * invalid, returns null. */
    getDiff: function(code, x, y) {
        if (this.moveKeyMap.hasOwnProperty(code)) {
            const diff = ROT.DIRS[8][this.moveKeyMap[code]];
            const newX = x + diff[0];
            const newY = y + diff[1];
            return [newX, newY];
        }
        else if (code === Keys.VK_s) {
            return [x, y];
        }
        else {
            return null;
        }
    },

    /* Returns a direction vector for given keycode. */
    getDir: function(code) {
        if (this.moveKeyMap.hasOwnProperty(code)) {
            return ROT.DIRS[8][this.moveKeyMap[code]];
        }
        else if (this.isRest(code)) {
            return [0, 0];
        }
        return null;
    },

    /* Converts a direction vector to keycode. */
    dirToKeyCode: function(dXArg, dYArg) {
        // Normalize first to unit vector (-1,0 or 1)
        let dX = dXArg;
        let dY = dYArg;
        if (dX !== 0) {dX = dX / Math.abs(dX);}
        if (dY !== 0) {dY = dY / Math.abs(dY);}
        switch (dX) {
            case -1:
                switch (dY) {
                    case -1: return Keys.KEY.MOVE_NW;
                    case 0: return Keys.KEY.MOVE_W;
                    case 1: return Keys.KEY.MOVE_SW;
                    default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            case 0:
                switch (dY) {
                    case -1: return Keys.KEY.MOVE_N;
                    case 0: return Keys.KEY.REST;
                    case 1: return Keys.KEY.MOVE_S;
                    default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            case 1:
                switch (dY) {
                    case -1: return Keys.KEY.MOVE_NE;
                    case 0: return Keys.KEY.MOVE_E;
                    case 1: return Keys.KEY.MOVE_SE;
                    default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                `Dir ${dX},${dY} not supported`);
        }
        return null;
    },

    keyCodeToCardinalDir: function(code) {
        switch (code) {
            case Keys.KEY.MOVE_NW: return 'NW';
            case Keys.KEY.MOVE_W: return 'W';
            case Keys.KEY.MOVE_SW: return 'SW';
            case Keys.KEY.MOVE_N: return 'N';
            case Keys.KEY.REST: return 'REST';
            case Keys.KEY.MOVE_S: return 'S';
            case Keys.KEY.MOVE_NE: return 'NE';
            case Keys.KEY.MOVE_E: return 'E';
            case Keys.KEY.MOVE_SE: return 'SE';
            default: return '';
        }
    }

};

Keys.menuIndices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f',
    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
    'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
    'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
    'Y', 'Z'
];

/* Given key code, returns the corresponding character in menu indices. */
Keys.codeToMenuChar = code => {
    const index = Keys.codeToIndex(code);
    return Keys.menuIndices[index];
};

const reCharLC = /[a-z]/;
const reCharUC = /[A-Z]/;

/* Convert a selection index into a keycode. For example, if user presses 'a',
 * this function should return keycode for a, ie Keys.VK_a. */
Keys.selectIndexToCode = indexChar => {
    const arrayIndex = Keys.menuIndices.findIndex(val => val === indexChar);
    if (arrayIndex >= 0) {
        if (arrayIndex >= 0 && arrayIndex <= 9) {
            return ROT.VK_0 + arrayIndex;
        }
        else if (reCharLC.test(indexChar)) {
            const addToCode = arrayIndex - Keys.menuIndices.indexOf('a');
            return Keys.VK_a + addToCode;
        }
        else if (reCharUC.test(indexChar)) {
            const addToCode = arrayIndex - Keys.menuIndices.indexOf('A');
            return ROT.VK_A + addToCode;
        }
    }
    RG.err('RG', 'selectIndexToCode',
        `Inv. select index |${indexChar}|`);
    return -1;

};

/* Converts the keycode into a selection index starting from 0. */
Keys.codeToIndex = code => {
    if (code >= ROT.VK_0 && code <= ROT.VK_9) {
        return code - ROT.VK_0;
    }
    else if (code >= Keys.VK_a && code <= Keys.VK_z) {
        return code - Keys.VK_a + Keys.menuIndices.indexOf('a');
    }
    else if (code >= ROT.VK_A && code <= ROT.VK_Z) {
        return code - ROT.VK_A + Keys.menuIndices.indexOf('A');
    }
    return -1;
};

/* Returns true if keyCode corresponds to a numeric key. */
Keys.isNumeric = keyCode => {
    return keyCode >= ROT.VK_0 && keyCode <= ROT.VK_9;
};

Keys.KEY = {};

// Assign ROT keys to meaningful constants
Keys.KEY.MOVE_N = ROT.VK_W + 32;
Keys.KEY.MOVE_NE = ROT.VK_E + 32;
Keys.KEY.MOVE_E = ROT.VK_D + 32;
Keys.KEY.MOVE_SE = ROT.VK_C + 32;
Keys.KEY.MOVE_S = ROT.VK_X + 32;
Keys.KEY.MOVE_SW = ROT.VK_Z + 32;
Keys.KEY.MOVE_W = ROT.VK_A + 32;
Keys.KEY.MOVE_NW = ROT.VK_Q + 32;

Keys.KEY.CHAT = ROT.VK_C;
Keys.KEY.DELETE = ROT.VK_D;
Keys.KEY.DOOR = ROT.VK_O + 32;
Keys.KEY.FIGHT = ROT.VK_F + 32;
Keys.KEY.GOTO = Keys.VK_g;
Keys.KEY.JUMP = Keys.VK_j;
Keys.KEY.LOOK = ROT.VK_L + 32;
Keys.KEY.MARK = Keys.VK_b;
Keys.KEY.NEXT = Keys.VK_n;
Keys.KEY.NEXT_ITEM = ROT.VK_H + 32;
Keys.KEY.ORDER = ROT.VK_O;
Keys.KEY.PICKUP = Keys.VK_COMMA;
Keys.KEY.POWER = ROT.VK_P + 32;
Keys.KEY.PREV = ROT.VK_P + 32;
Keys.KEY.QUIT_MENU = Keys.VK_q;
Keys.KEY.REST = ROT.VK_S + 32;
Keys.KEY.RUN = ROT.VK_R + 32;
Keys.KEY.SELECT = Keys.VK_s;
Keys.KEY.SELECT_ALL = ROT.VK_A;
Keys.KEY.TARGET = Keys.VK_t;
Keys.KEY.USE_STAIRS_DOWN = Keys.VK_GT;
Keys.KEY.USE_STAIRS_UP = Keys.VK_LT;
Keys.KEY.YES = ROT.VK_Y + 32;
Keys.KeyMap.initMap();

Keys.KEY.NO_ACTION = ROT.VK_CAPS_LOCK;

// Used in the GUI only
Keys.GUI = {};
Keys.GUI.CharInfo = ROT.VK_I;
Keys.GUI.Goto = Keys.KEY.GOTO;
Keys.GUI.Help = ROT.VK_H;
Keys.GUI.Inv = Keys.VK_i;
Keys.GUI.Look = Keys.VK_l;
Keys.GUI.Map = Keys.VK_m;
Keys.GUI.OwMap = ROT.VK_M;
Keys.GUI.Use = Keys.VK_u;

Keys.isValidKey = keyCode => {
    let found = false;
    Object.keys(Keys.KEY).forEach(key => {
        found = found || Keys.KEY[key] === keyCode;
    });
    found = found || Keys.KeyMap.inMoveCodeMap(keyCode);
    return found;
};

/* Given keycode, returns the valid char for that key. */
Keys.getChar = keyCode => {
    return '`' + String.fromCharCode(keyCode) + '`';
};

module.exports = Keys;
<commit_msg>Added key for using ability.<commit_after>/* This file contains keyboard definitions and constants, plus some conversion
 * functions between dir-vectors and direction keys. */

const ROT = require('../../lib/rot.js');
const RG = require('./rg');

const Keys = {};

/* eslint-disable */
Keys.VK_a = ROT.VK_A + 32;
Keys.VK_b = ROT.VK_B + 32;
Keys.VK_c = ROT.VK_C + 32;
Keys.VK_d = ROT.VK_D + 32;
Keys.VK_e = ROT.VK_E + 32;
Keys.VK_f = ROT.VK_F + 32;
Keys.VK_g = ROT.VK_G + 32;
Keys.VK_h = ROT.VK_H + 32;
Keys.VK_i = ROT.VK_I + 32;
Keys.VK_j = ROT.VK_J + 32;
Keys.VK_k = ROT.VK_K + 32;
Keys.VK_l = ROT.VK_L + 32;
Keys.VK_m = ROT.VK_M + 32;
Keys.VK_n = ROT.VK_N + 32;
Keys.VK_o = ROT.VK_O + 32;
Keys.VK_p = ROT.VK_P + 32;
Keys.VK_q = ROT.VK_Q + 32;
Keys.VK_r = ROT.VK_R + 32;
Keys.VK_s = ROT.VK_S + 32;
Keys.VK_t = ROT.VK_T + 32;
Keys.VK_u = ROT.VK_U + 32;
Keys.VK_v = ROT.VK_V + 32;
Keys.VK_w = ROT.VK_W + 32;
Keys.VK_x = ROT.VK_X + 32;
Keys.VK_y = ROT.VK_Y + 32;
Keys.VK_z = ROT.VK_Z + 32;
/* eslint-enable */

Keys.VK_COMMA = 44;
Keys.VK_PERIOD = 46;
Keys.VK_LT = 60;
Keys.VK_GT = 62;

/* Lookup table object for movement and actions keys.*/
Keys.KeyMap = {

    moveKeyMap: { },

    // Start from W, go clock wise on keyboard
    initMap: function() {
        this.moveKeyMap[Keys.KEY.MOVE_N] = 0;
        this.moveKeyMap[Keys.KEY.MOVE_NE] = 1;
        this.moveKeyMap[Keys.KEY.MOVE_E] = 2;
        this.moveKeyMap[Keys.KEY.MOVE_SE] = 3;
        this.moveKeyMap[Keys.KEY.MOVE_S] = 4;
        this.moveKeyMap[Keys.KEY.MOVE_SW] = 5;
        this.moveKeyMap[Keys.KEY.MOVE_W] = 6;
        this.moveKeyMap[Keys.KEY.MOVE_NW] = 7;

        this.moveKeyMap[ROT.VK_8] = 0;
        this.moveKeyMap[ROT.VK_9] = 1;
        this.moveKeyMap[ROT.VK_6] = 2;
        this.moveKeyMap[ROT.VK_3] = 3;
        this.moveKeyMap[ROT.VK_2] = 4;
        this.moveKeyMap[ROT.VK_1] = 5;
        this.moveKeyMap[ROT.VK_4] = 6;
        this.moveKeyMap[ROT.VK_7] = 7;
    },

    inMoveCodeMap: function(code) {
        return this.moveKeyMap.hasOwnProperty(code);
    },

    isRest: function(code) {
        return code === Keys.VK_s || code === Keys.VK_PERIOD;
    },
    isPickup: function(code) {return code === Keys.KEY.PICKUP;},
    isUseStairs: function(code) {
        return code === Keys.KEY.USE_STAIRS_DOWN ||
            code === Keys.KEY.USE_STAIRS_UP;
    },
    isChat: function(code) {return code === Keys.KEY.CHAT;},
    isConfirmYes: function(code) {return code === Keys.KEY.YES;},
    isFightMode: function(code) {return code === Keys.KEY.FIGHT;},
    isGoto: function(code) {return code === Keys.KEY.GOTO;},
    isJump: function(code) {return code === Keys.KEY.JUMP;},
    isIssueOrder: function(code) {return code === Keys.KEY.ORDER;},
    isLook: function(code) {return code === Keys.KEY.LOOK;},
    isMark: function(code) {return code === Keys.KEY.MARK;},
    isNextItem: function(code) {return code === Keys.KEY.NEXT_ITEM;},
    isNextTarget: function(code) {return code === Keys.KEY.NEXT;},
    isPrevTarget: function(code) {return code === Keys.KEY.PREV;},
    isRunMode: function(code) {return code === Keys.KEY.RUN;},
    isSelect: function(code) {return code === Keys.KEY.SELECT;},
    isSelectAll: function(code) {return code === Keys.KEY.SELECT_ALL;},
    isTargetMode: function(code) {return code === Keys.KEY.TARGET;},
    isToggleDoor: function(code) {return code === Keys.KEY.DOOR;},
    isUsePower: function(code) {return code === Keys.KEY.POWER;},
    isUseAbility: function(code) {return code === Keys.KEY.ABILITY;},

    /* Based on keycode, computes and returns a new x,y pair. If code is
     * invalid, returns null. */
    getDiff: function(code, x, y) {
        if (this.moveKeyMap.hasOwnProperty(code)) {
            const diff = ROT.DIRS[8][this.moveKeyMap[code]];
            const newX = x + diff[0];
            const newY = y + diff[1];
            return [newX, newY];
        }
        else if (code === Keys.VK_s) {
            return [x, y];
        }
        else {
            return null;
        }
    },

    /* Returns a direction vector for given keycode. */
    getDir: function(code) {
        if (this.moveKeyMap.hasOwnProperty(code)) {
            return ROT.DIRS[8][this.moveKeyMap[code]];
        }
        else if (this.isRest(code)) {
            return [0, 0];
        }
        return null;
    },

    /* Converts a direction vector to keycode. */
    dirToKeyCode: function(dXArg, dYArg) {
        // Normalize first to unit vector (-1,0 or 1)
        let dX = dXArg;
        let dY = dYArg;
        if (dX !== 0) {dX = dX / Math.abs(dX);}
        if (dY !== 0) {dY = dY / Math.abs(dY);}
        switch (dX) {
            case -1:
                switch (dY) {
                    case -1: return Keys.KEY.MOVE_NW;
                    case 0: return Keys.KEY.MOVE_W;
                    case 1: return Keys.KEY.MOVE_SW;
                    default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            case 0:
                switch (dY) {
                    case -1: return Keys.KEY.MOVE_N;
                    case 0: return Keys.KEY.REST;
                    case 1: return Keys.KEY.MOVE_S;
                    default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            case 1:
                switch (dY) {
                    case -1: return Keys.KEY.MOVE_NE;
                    case 0: return Keys.KEY.MOVE_E;
                    case 1: return Keys.KEY.MOVE_SE;
                    default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                        `Dir ${dX},${dY} not supported`);
                }
                break;
            default: RG.err('Keys.KeyMap', 'dirToKeyCode',
                `Dir ${dX},${dY} not supported`);
        }
        return null;
    },

    keyCodeToCardinalDir: function(code) {
        switch (code) {
            case Keys.KEY.MOVE_NW: return 'NW';
            case Keys.KEY.MOVE_W: return 'W';
            case Keys.KEY.MOVE_SW: return 'SW';
            case Keys.KEY.MOVE_N: return 'N';
            case Keys.KEY.REST: return 'REST';
            case Keys.KEY.MOVE_S: return 'S';
            case Keys.KEY.MOVE_NE: return 'NE';
            case Keys.KEY.MOVE_E: return 'E';
            case Keys.KEY.MOVE_SE: return 'SE';
            default: return '';
        }
    }

};

Keys.menuIndices = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f',
    'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
    'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
    'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
    'Y', 'Z'
];

/* Given key code, returns the corresponding character in menu indices. */
Keys.codeToMenuChar = code => {
    const index = Keys.codeToIndex(code);
    return Keys.menuIndices[index];
};

const reCharLC = /[a-z]/;
const reCharUC = /[A-Z]/;

/* Convert a selection index into a keycode. For example, if user presses 'a',
 * this function should return keycode for a, ie Keys.VK_a. */
Keys.selectIndexToCode = indexChar => {
    const arrayIndex = Keys.menuIndices.findIndex(val => val === indexChar);
    if (arrayIndex >= 0) {
        if (arrayIndex >= 0 && arrayIndex <= 9) {
            return ROT.VK_0 + arrayIndex;
        }
        else if (reCharLC.test(indexChar)) {
            const addToCode = arrayIndex - Keys.menuIndices.indexOf('a');
            return Keys.VK_a + addToCode;
        }
        else if (reCharUC.test(indexChar)) {
            const addToCode = arrayIndex - Keys.menuIndices.indexOf('A');
            return ROT.VK_A + addToCode;
        }
    }
    RG.err('RG', 'selectIndexToCode',
        `Inv. select index |${indexChar}|`);
    return -1;

};

/* Converts the keycode into a selection index starting from 0. */
Keys.codeToIndex = code => {
    if (code >= ROT.VK_0 && code <= ROT.VK_9) {
        return code - ROT.VK_0;
    }
    else if (code >= Keys.VK_a && code <= Keys.VK_z) {
        return code - Keys.VK_a + Keys.menuIndices.indexOf('a');
    }
    else if (code >= ROT.VK_A && code <= ROT.VK_Z) {
        return code - ROT.VK_A + Keys.menuIndices.indexOf('A');
    }
    return -1;
};

/* Returns true if keyCode corresponds to a numeric key. */
Keys.isNumeric = keyCode => {
    return keyCode >= ROT.VK_0 && keyCode <= ROT.VK_9;
};

Keys.KEY = {};

// Assign ROT keys to meaningful constants
Keys.KEY.MOVE_N = ROT.VK_W + 32;
Keys.KEY.MOVE_NE = ROT.VK_E + 32;
Keys.KEY.MOVE_E = ROT.VK_D + 32;
Keys.KEY.MOVE_SE = ROT.VK_C + 32;
Keys.KEY.MOVE_S = ROT.VK_X + 32;
Keys.KEY.MOVE_SW = ROT.VK_Z + 32;
Keys.KEY.MOVE_W = ROT.VK_A + 32;
Keys.KEY.MOVE_NW = ROT.VK_Q + 32;

Keys.KEY.ABILITY = Keys.VK_k;
Keys.KEY.CHAT = ROT.VK_C;
Keys.KEY.DELETE = ROT.VK_D;
Keys.KEY.DOOR = ROT.VK_O + 32;
Keys.KEY.FIGHT = ROT.VK_F + 32;
Keys.KEY.GOTO = Keys.VK_g;
Keys.KEY.JUMP = Keys.VK_j;
Keys.KEY.LOOK = ROT.VK_L + 32;
Keys.KEY.MARK = Keys.VK_b;
Keys.KEY.NEXT = Keys.VK_n;
Keys.KEY.NEXT_ITEM = ROT.VK_H + 32;
Keys.KEY.ORDER = ROT.VK_O;
Keys.KEY.PICKUP = Keys.VK_COMMA;
Keys.KEY.POWER = ROT.VK_P + 32;
Keys.KEY.PREV = ROT.VK_P + 32;
Keys.KEY.QUIT_MENU = Keys.VK_q;
Keys.KEY.REST = ROT.VK_S + 32;
Keys.KEY.RUN = ROT.VK_R + 32;
Keys.KEY.SELECT = Keys.VK_s;
Keys.KEY.SELECT_ALL = ROT.VK_A;
Keys.KEY.TARGET = Keys.VK_t;
Keys.KEY.USE_ABILITY = Keys.VK_k;
Keys.KEY.USE_STAIRS_DOWN = Keys.VK_GT;
Keys.KEY.USE_STAIRS_UP = Keys.VK_LT;
Keys.KEY.YES = ROT.VK_Y + 32;
Keys.KeyMap.initMap();

Keys.KEY.NO_ACTION = ROT.VK_CAPS_LOCK;

// Used in the GUI only
Keys.GUI = {};
Keys.GUI.CharInfo = ROT.VK_I;
Keys.GUI.Goto = Keys.KEY.GOTO;
Keys.GUI.Help = ROT.VK_H;
Keys.GUI.Inv = Keys.VK_i;
Keys.GUI.Look = Keys.VK_l;
Keys.GUI.Map = Keys.VK_m;
Keys.GUI.OwMap = ROT.VK_M;
Keys.GUI.Use = Keys.VK_u;

Keys.isValidKey = keyCode => {
    let found = false;
    Object.keys(Keys.KEY).forEach(key => {
        found = found || Keys.KEY[key] === keyCode;
    });
    found = found || Keys.KeyMap.inMoveCodeMap(keyCode);
    return found;
};

/* Given keycode, returns the valid char for that key. */
Keys.getChar = keyCode => {
    return '`' + String.fromCharCode(keyCode) + '`';
};

module.exports = Keys;
<|endoftext|>


<commit_before>'use strict';

const cp = require('child_process');
const electron = require('electron');
const promisify = require('bluebird').promisify;
const os = require('os');

module.exports = function buildNative() {
  const exec = promisify(cp.exec);

  return Promise.all([
    exec(`${electron} --version`, { encoding: 'utf8' }),
    exec(`${electron} --abi`, { encoding: 'utf8' }),
  ])
    .then(([version, abi]) => {
      const electronVersion = version.match(/v(\d+\.\d+\.\d+)/)[1];
      const electronABI = abi.match(/(\d+)/)[1];

      return new Promise((resolve, reject) => {
        let cmd = 'npm';
        if (os.platform() === 'win32') {
          cmd = 'npm.cmd';
        }

        const build = cp.spawn(cmd, [
          'rebuild',
          '--runtime=electron',
          `--target=${electronVersion}`,
          '--disturl=https://atom.io/download/atom-shell',
          `--abi=${electronABI}`,
        ]);

        build.stdout.pipe(process.stdout);
        build.stderr.pipe(process.stderr);

        build.on('close', code => {
          if (code) {
            reject(new Error(`Rebuild exited with code ${code}`));
          } else {
            resolve();
          }
        });
      });
    })
    .then(() => {
      console.log('Done building native modules'); // eslint-disable-line no-console
    })
    .catch(error => {
      console.log('There was a problem building', error); // eslint-disable-line no-console
    });
};
<commit_msg>Refactor UI build script.<commit_after>'use strict';

const cp = require('child_process');
const electron = require('electron');
const promisify = require('bluebird').promisify;
const os = require('os');

module.exports = function buildNative() {
  const exec = promisify(cp.exec);

  return Promise.all([
    exec(`${electron} --version`, { encoding: 'utf8' }),
    exec(`${electron} --abi`, { encoding: 'utf8' }),
  ])
    .then(([version, abi]) => {
      const electronVersion = version.match(/v(\d+\.\d+\.\d+)/)[1];
      const electronABI = abi.match(/(\d+)/)[1];

      return new Promise((resolve, reject) => {
        let cmd = 'npm';
        if (os.platform() === 'win32') {
          cmd = 'npm.cmd';
        }

        const build = cp.spawn(cmd, [
          'rebuild',
          'leveldown',
          '--depth=0',
          '--runtime=electron',
          `--target=${electronVersion}`,
          '--disturl=https://atom.io/download/atom-shell',
          `--abi=${electronABI}`,
        ]);

        build.stdout.pipe(process.stdout);
        build.stderr.pipe(process.stderr);

        build.on('close', code => {
          if (code) {
            reject(new Error(`Rebuild exited with code ${code}`));
          } else {
            resolve();
          }
        });
      });
    })
    .then(() => {
      console.log('Done building native modules'); // eslint-disable-line no-console
    })
    .catch(error => {
      console.log('There was a problem building', error); // eslint-disable-line no-console
    });
};
<|endoftext|>


<commit_before>#!/usr/bin/env node
// polyfills
import 'babel-polyfill';

// npm modules
import chalk from 'chalk';
import meow from 'meow';
import pick from 'lodash.pick';
import stdin from 'get-stdin';

// local modules
import help from './help';
import lint from './';
import {
	format,
	getConfiguration,
	getPreset,
	getMessages
} from './';

import pkg from '../package';

/**
 * Behavioural rules
 */
const rules = {
	fromStdin: (input, flags) => input.length === 0 &&
		flags.from === null &&
		flags.to === null &&
		!flags.edit
};

const configuration = {
	// flags of string type
	string: ['from', 'to', 'preset', 'extends'],
	// flags of array type
	// flags of bool type
	boolean: ['edit', 'help', 'version', 'quiet', 'color'],
	// flag aliases
	alias: {
		c: 'color',
		e: 'edit',
		f: 'from',
		p: 'preset',
		t: 'to',
		q: 'quiet',
		h: 'help',
		v: 'version',
		x: 'extends'
	},
	description: {
		color: 'toggle formatted output',
		edit: 'read last commit message found in ./git/COMMIT_EDITMSG',
		'extends': 'array of shareable configurations to extend',
		from: 'lower end of the commit range to lint; applies if edit=false',
		preset: 'conventional-changelog-preset to use for commit message parsing',
		to: 'upper end of the commit range to lint; applies if edit=false',
		quiet: 'toggle console output'
	},
	// flag defaults
	default: {
		color: true,
		edit: false,
		from: null,
		preset: 'angular',
		to: null,
		quiet: false
	},
	// fail on unknown
	unknown(arg) {
		throw new Error(`unknown flags: ${arg}`);
	}
};

// Init meow 😸cli
const cli = meow({
	help: `[input] reads from stdin if --edit, --from, --to are omitted\n${help(configuration)}`,
	description: `${pkg.name}@${pkg.version} - ${pkg.description}`
}, configuration);

// Assemble the engine
async function main(options) {
	const {input: raw, flags} = options;
	const fromStdin = rules.fromStdin(raw, flags);

	const input = fromStdin ?
		[await stdin()] :
		await getMessages(
			pick(flags, 'edit', 'from', 'to')
		);

	return Promise.all(input
		.map(async commit => {
			const fmt = new chalk.constructor({enabled: flags.color});

			const seed = {};
			if (flags.extends) {
				seed.extends = flags.extends.split(',');
			}

			const report = await lint(commit, {
				preset: await getPreset(flags.preset),
				configuration: await getConfiguration(
					'conventional-changelog-lint',
					{
						prefix: 'conventional-changelog-lint-config'
					},
					seed
				)
			});

			const formatted = format(report, {
				color: flags.color,
				signs: [' ', '⚠', '✖'],
				colors: ['white', 'yellow', 'red']
			});

			if (!flags.quiet) {
				console.log(`${fmt.grey('⧗')}   input: ${fmt.bold(commit.split('\n')[0])}`);
				console.log(formatted.join('\n'));
			}

			if (report.errors.length > 0) {
				const error = new Error(formatted[formatted.length - 1]);
				error.type = pkg.name;
				throw error;
			}

			console.log('');
		}));
}

// Start the engine
main(cli)
	.catch(error =>
		setTimeout(() => {
			if (error.type === pkg.name) {
				process.exit(1);
			}
			throw error;
		})
	)
<commit_msg>fix: catch unhandled rejections<commit_after>#!/usr/bin/env node
// polyfills
import 'babel-polyfill';

// npm modules
import chalk from 'chalk';
import meow from 'meow';
import pick from 'lodash.pick';
import stdin from 'get-stdin';

// local modules
import help from './help';
import lint from './';
import {
	format,
	getConfiguration,
	getPreset,
	getMessages
} from './';

import pkg from '../package';

/**
 * Behavioural rules
 */
const rules = {
	fromStdin: (input, flags) => input.length === 0 &&
		flags.from === null &&
		flags.to === null &&
		!flags.edit
};

const configuration = {
	// flags of string type
	string: ['from', 'to', 'preset', 'extends'],
	// flags of array type
	// flags of bool type
	boolean: ['edit', 'help', 'version', 'quiet', 'color'],
	// flag aliases
	alias: {
		c: 'color',
		e: 'edit',
		f: 'from',
		p: 'preset',
		t: 'to',
		q: 'quiet',
		h: 'help',
		v: 'version',
		x: 'extends'
	},
	description: {
		color: 'toggle formatted output',
		edit: 'read last commit message found in ./git/COMMIT_EDITMSG',
		extends: 'array of shareable configurations to extend',
		from: 'lower end of the commit range to lint; applies if edit=false',
		preset: 'conventional-changelog-preset to use for commit message parsing',
		to: 'upper end of the commit range to lint; applies if edit=false',
		quiet: 'toggle console output'
	},
	// flag defaults
	default: {
		color: true,
		edit: false,
		from: null,
		preset: 'angular',
		to: null,
		quiet: false
	},
	// fail on unknown
	unknown(arg) {
		throw new Error(`unknown flags: ${arg}`);
	}
};

// Init meow 😸cli
const cli = meow({
	help: `[input] reads from stdin if --edit, --from, --to are omitted\n${help(configuration)}`,
	description: `${pkg.name}@${pkg.version} - ${pkg.description}`
}, configuration);

// Assemble the engine
async function main(options) {
	const {input: raw, flags} = options;
	const fromStdin = rules.fromStdin(raw, flags);

	const input = fromStdin ?
		[await stdin()] :
		await getMessages(
			pick(flags, 'edit', 'from', 'to')
		);

	return Promise.all(input
		.map(async commit => {
			const fmt = new chalk.constructor({enabled: flags.color});

			const seed = {};
			if (flags.extends) {
				seed.extends = flags.extends.split(',');
			}

			const report = await lint(commit, {
				preset: await getPreset(flags.preset),
				configuration: await getConfiguration(
					'conventional-changelog-lint',
					{
						prefix: 'conventional-changelog-lint-config'
					},
					seed
				)
			});

			const formatted = format(report, {
				color: flags.color,
				signs: [' ', '⚠', '✖'],
				colors: ['white', 'yellow', 'red']
			});

			if (!flags.quiet) {
				console.log(`${fmt.grey('⧗')}   input: ${fmt.bold(commit.split('\n')[0])}`);
				console.log(formatted.join('\n'));
			}

			if (report.errors.length > 0) {
				const error = new Error(formatted[formatted.length - 1]);
				error.type = pkg.name;
				throw error;
			}

			return console.log('');
		}));
}

// Start the engine
main(cli)
	.catch(error =>
		setTimeout(() => {
			if (error.type === pkg.name) {
				process.exit(1);
			}
			throw error;
		})
	);

// Catch unhandled rejections globally
process.on('unhandledRejection', (reason, promise) => {
	console.log('Unhandled Rejection at: Promise ', promise, ' reason: ', reason);
	throw reason;
});
<|endoftext|>


<commit_before>var checkCacheAndReport = require("./checkCacheAndReport");
var MaxRetryExceededError = require("./../../errors/MaxRetryExceededError");
var fastCat = require("./../../get/util/support").fastCat;
var collectLru = require("./../../lru/collect");
var getSize = require("./../../support/getSize");
var AssignableDisposable = require("./../AssignableDisposable");
var InvalidSourceError = require("../../errors/InvalidSourceError");

/**
 * The get request cycle for checking the cache and reporting
 * values.  If there are missing paths then the async request cycle to
 * the data source is performed until all paths are resolved or max
 * requests are made.
 * @param {GetResponse} getResponse -
 * @param {Model} model - The model that the request was made with.
 * @param {Object} results -
 * @param {Function} onNext -
 * @param {Function} onError -
 * @param {Function} onCompleted -
 * @private
 */
module.exports = function getRequestCycle(getResponse, model, results, observer,
                                          errors, count) {
    // we have exceeded the maximum retry limit.
    if (count === model._maxRetries) {
        observer.onError(new MaxRetryExceededError(results.optimizedMissingPaths));
        return {
            dispose: function() {}
        };
    }

    var requestQueue = model._request;
    var requestedMissingPaths = results.requestedMissingPaths;
    var optimizedMissingPaths = results.optimizedMissingPaths;
    var depthDifferences = results.depthDifferences;
    var disposable = new AssignableDisposable();

    // We need to prepend the bound path to all requested missing paths and
    // pass those into the requestQueue.
    var boundRequestedMissingPaths = [];
    var boundPath = model._path;
    var boundPathLength = boundPath.length;
    if (boundPath.length) {
        for (var i = 0, len = requestedMissingPaths.length; i < len; ++i) {
            boundRequestedMissingPaths[i] =
                fastCat(boundPath, requestedMissingPaths[i]);
            depthDifferences[i] += boundPathLength;
        }
    }

    // No bound path, no array copy and concat.
    else {
        boundRequestedMissingPaths = requestedMissingPaths;
    }

    var currentRequestDisposable = requestQueue.
        get(boundRequestedMissingPaths, optimizedMissingPaths, depthDifferences, function(err, data, hasInvalidatedResult) {
            if (model._treatDataSourceErrorsAsJSONGraphErrors ? err instanceof InvalidSourceError : !!err) {
                if (results.hasValues) {
                    observer.onNext(results.values && results.values[0]);
                }
                observer.onError(err);
                return;
            }

            var nextRequestedMissingPaths;
            var nextSeed;

            // If merging over an existing branch structure with refs has invalidated our intermediate json,
            // we want to start over and re-get all requested paths with a fresh seed
            if (hasInvalidatedResult) {
                nextRequestedMissingPaths = getResponse.currentRemainingPaths;
                nextSeed = [{}];
            } else {
                nextRequestedMissingPaths = requestedMissingPaths;
                nextSeed = results.values;
            }

             // Once the request queue finishes, check the cache and bail if
             // we can.
            var nextResults = checkCacheAndReport(model, nextRequestedMissingPaths,
                                                  observer,
                                                  getResponse.isProgressive,
                                                  getResponse.isJSONGraph,
                                                  nextSeed, errors);

            // If there are missing paths coming back form checkCacheAndReport
            // the its reported from the core cache check method.
            if (nextResults) {

                // update the which disposable to use.
                disposable.currentDisposable =
                    getRequestCycle(getResponse, model, nextResults, observer,
                                    errors, count + 1);
            }

            // We have finished.  Since we went to the dataSource, we must
            // collect on the cache.
            else {

                var modelRoot = model._root;
                var modelCache = modelRoot.cache;
                var currentVersion = modelCache.$_version;

                collectLru(modelRoot, modelRoot.expired, getSize(modelCache),
                        model._maxSize, model._collectRatio, currentVersion);
            }

        });
    disposable.currentDisposable = currentRequestDisposable;
    return disposable;
};
<commit_msg>fix retry count logic - currently retries 1 less time than intended<commit_after>var checkCacheAndReport = require("./checkCacheAndReport");
var MaxRetryExceededError = require("./../../errors/MaxRetryExceededError");
var fastCat = require("./../../get/util/support").fastCat;
var collectLru = require("./../../lru/collect");
var getSize = require("./../../support/getSize");
var AssignableDisposable = require("./../AssignableDisposable");
var InvalidSourceError = require("../../errors/InvalidSourceError");

/**
 * The get request cycle for checking the cache and reporting
 * values.  If there are missing paths then the async request cycle to
 * the data source is performed until all paths are resolved or max
 * requests are made.
 * @param {GetResponse} getResponse -
 * @param {Model} model - The model that the request was made with.
 * @param {Object} results -
 * @param {Function} onNext -
 * @param {Function} onError -
 * @param {Function} onCompleted -
 * @private
 */
module.exports = function getRequestCycle(getResponse, model, results, observer,
                                          errors, count) {
    // we have exceeded the maximum retry limit.
    if (count > model._maxRetries) {
        observer.onError(new MaxRetryExceededError(results.optimizedMissingPaths));
        return {
            dispose: function() {}
        };
    }

    var requestQueue = model._request;
    var requestedMissingPaths = results.requestedMissingPaths;
    var optimizedMissingPaths = results.optimizedMissingPaths;
    var depthDifferences = results.depthDifferences;
    var disposable = new AssignableDisposable();

    // We need to prepend the bound path to all requested missing paths and
    // pass those into the requestQueue.
    var boundRequestedMissingPaths = [];
    var boundPath = model._path;
    var boundPathLength = boundPath.length;
    if (boundPath.length) {
        for (var i = 0, len = requestedMissingPaths.length; i < len; ++i) {
            boundRequestedMissingPaths[i] =
                fastCat(boundPath, requestedMissingPaths[i]);
            depthDifferences[i] += boundPathLength;
        }
    }

    // No bound path, no array copy and concat.
    else {
        boundRequestedMissingPaths = requestedMissingPaths;
    }

    var currentRequestDisposable = requestQueue.
        get(boundRequestedMissingPaths, optimizedMissingPaths, depthDifferences, function(err, data, hasInvalidatedResult) {
            if (model._treatDataSourceErrorsAsJSONGraphErrors ? err instanceof InvalidSourceError : !!err) {
                if (results.hasValues) {
                    observer.onNext(results.values && results.values[0]);
                }
                observer.onError(err);
                return;
            }

            var nextRequestedMissingPaths;
            var nextSeed;

            // If merging over an existing branch structure with refs has invalidated our intermediate json,
            // we want to start over and re-get all requested paths with a fresh seed
            if (hasInvalidatedResult) {
                nextRequestedMissingPaths = getResponse.currentRemainingPaths;
                nextSeed = [{}];
            } else {
                nextRequestedMissingPaths = requestedMissingPaths;
                nextSeed = results.values;
            }

             // Once the request queue finishes, check the cache and bail if
             // we can.
            var nextResults = checkCacheAndReport(model, nextRequestedMissingPaths,
                                                  observer,
                                                  getResponse.isProgressive,
                                                  getResponse.isJSONGraph,
                                                  nextSeed, errors);

            // If there are missing paths coming back form checkCacheAndReport
            // the its reported from the core cache check method.
            if (nextResults) {

                // update the which disposable to use.
                disposable.currentDisposable =
                    getRequestCycle(getResponse, model, nextResults, observer,
                                    errors, count + 1);
            }

            // We have finished.  Since we went to the dataSource, we must
            // collect on the cache.
            else {

                var modelRoot = model._root;
                var modelCache = modelRoot.cache;
                var currentVersion = modelCache.$_version;

                collectLru(modelRoot, modelRoot.expired, getSize(modelCache),
                        model._maxSize, model._collectRatio, currentVersion);
            }

        });
    disposable.currentDisposable = currentRequestDisposable;
    return disposable;
};
<|endoftext|>


<commit_before>var checkCacheAndReport = require("./checkCacheAndReport");
var MaxRetryExceededError = require("./../../errors/MaxRetryExceededError");
var fastCat = require("./../../get/util/support").fastCat;
var collectLru = require("./../../lru/collect");
var getSize = require("./../../support/getSize");
var AssignableDisposable = require("./../AssignableDisposable");
var InvalidSourceError = require("../../errors/InvalidSourceError");

/**
 * The get request cycle for checking the cache and reporting
 * values.  If there are missing paths then the async request cycle to
 * the data source is performed until all paths are resolved or max
 * requests are made.
 * @param {GetResponse} getResponse -
 * @param {Model} model - The model that the request was made with.
 * @param {Object} results -
 * @param {Function} onNext -
 * @param {Function} onError -
 * @param {Function} onCompleted -
 * @private
 */
module.exports = function getRequestCycle(getResponse, model, results, observer,
                                          errors, count) {
    // we have exceeded the maximum retry limit.
    if (count === model._maxRetries) {
        observer.onError(new MaxRetryExceededError());
        return {
            dispose: function() {}
        };
    }

    var requestQueue = model._request;
    var requestedMissingPaths = results.requestedMissingPaths;
    var optimizedMissingPaths = results.optimizedMissingPaths;
    var disposable = new AssignableDisposable();

    // We need to prepend the bound path to all requested missing paths and
    // pass those into the requestQueue.
    var boundRequestedMissingPaths = [];
    var boundPath = model._path;
    if (boundPath.length) {
        for (var i = 0, len = requestedMissingPaths.length; i < len; ++i) {
            boundRequestedMissingPaths[i] =
                fastCat(boundPath, requestedMissingPaths[i]);
        }
    }

    // No bound path, no array copy and concat.
    else {
        boundRequestedMissingPaths = requestedMissingPaths;
    }

    var currentRequestDisposable = requestQueue.
        get(boundRequestedMissingPaths, optimizedMissingPaths, function(err) {

            if (model._treatDataSourceErrorsAsJSONGraphErrors ? err instanceof InvalidSourceError : !!err) {
                observer.onError(err);
                return;
            }

            // Once the request queue finishes, check the cache and bail if
            // we can.
            var nextResults = checkCacheAndReport(model, requestedMissingPaths,
                                                  observer,
                                                  getResponse.isProgressive,
                                                  getResponse.isJSONGraph,
                                                  results, errors);

            // If there are missing paths coming back form checkCacheAndReport
            // the its reported from the core cache check method.
            if (nextResults) {

                // update the which disposable to use.
                disposable.currentDisposable =
                    getRequestCycle(getResponse, model, nextResults, observer,
                                    errors, count + 1);
            }

            // We have finished.  Since we went to the dataSource, we must
            // collect on the cache.
            else {

                var modelRoot = model._root;
                var modelCache = modelRoot.cache;
                var currentVersion = modelCache.$_version;

                collectLru(modelRoot, modelRoot.expired, getSize(modelCache),
                        model._maxSize, model._collectRatio, currentVersion);
            }

        });
    disposable.currentDisposable = currentRequestDisposable;
    return disposable;
};
<commit_msg>Nexts if hasValues<commit_after>var checkCacheAndReport = require("./checkCacheAndReport");
var MaxRetryExceededError = require("./../../errors/MaxRetryExceededError");
var fastCat = require("./../../get/util/support").fastCat;
var collectLru = require("./../../lru/collect");
var getSize = require("./../../support/getSize");
var AssignableDisposable = require("./../AssignableDisposable");
var InvalidSourceError = require("../../errors/InvalidSourceError");

/**
 * The get request cycle for checking the cache and reporting
 * values.  If there are missing paths then the async request cycle to
 * the data source is performed until all paths are resolved or max
 * requests are made.
 * @param {GetResponse} getResponse -
 * @param {Model} model - The model that the request was made with.
 * @param {Object} results -
 * @param {Function} onNext -
 * @param {Function} onError -
 * @param {Function} onCompleted -
 * @private
 */
module.exports = function getRequestCycle(getResponse, model, results, observer,
                                          errors, count) {
    // we have exceeded the maximum retry limit.
    if (count === model._maxRetries) {
        observer.onError(new MaxRetryExceededError());
        return {
            dispose: function() {}
        };
    }

    var requestQueue = model._request;
    var requestedMissingPaths = results.requestedMissingPaths;
    var optimizedMissingPaths = results.optimizedMissingPaths;
    var disposable = new AssignableDisposable();

    // We need to prepend the bound path to all requested missing paths and
    // pass those into the requestQueue.
    var boundRequestedMissingPaths = [];
    var boundPath = model._path;
    if (boundPath.length) {
        for (var i = 0, len = requestedMissingPaths.length; i < len; ++i) {
            boundRequestedMissingPaths[i] =
                fastCat(boundPath, requestedMissingPaths[i]);
        }
    }

    // No bound path, no array copy and concat.
    else {
        boundRequestedMissingPaths = requestedMissingPaths;
    }

    var currentRequestDisposable = requestQueue.
        get(boundRequestedMissingPaths, optimizedMissingPaths, function(err) {

            if (model._treatDataSourceErrorsAsJSONGraphErrors ? err instanceof InvalidSourceError : !!err) {
                if (results.hasValues) {
                    observer.onNext(results.values && results.values[0]);
                }
                observer.onError(err);
                return;
            }

            // Once the request queue finishes, check the cache and bail if
            // we can.
            var nextResults = checkCacheAndReport(model, requestedMissingPaths,
                                                  observer,
                                                  getResponse.isProgressive,
                                                  getResponse.isJSONGraph,
                                                  results, errors);

            // If there are missing paths coming back form checkCacheAndReport
            // the its reported from the core cache check method.
            if (nextResults) {

                // update the which disposable to use.
                disposable.currentDisposable =
                    getRequestCycle(getResponse, model, nextResults, observer,
                                    errors, count + 1);
            }

            // We have finished.  Since we went to the dataSource, we must
            // collect on the cache.
            else {

                var modelRoot = model._root;
                var modelCache = modelRoot.cache;
                var currentVersion = modelCache.$_version;

                collectLru(modelRoot, modelRoot.expired, getSize(modelCache),
                        model._maxSize, model._collectRatio, currentVersion);
            }

        });
    disposable.currentDisposable = currentRequestDisposable;
    return disposable;
};
<|endoftext|>


<commit_before>/**
 * This requires the entire Exhibit infrastructure to be oriented around
 * generating registered state changes.
 */

/**
 * @fileOverview Local interface to a history manager.
 * @author Ryan Lee
 */

/**
 * @namespace For history management related methods.
 */
Exhibit.History = {
    /**
     *
     */
    enabled: false,

    /**
     * @private
     */
    _registeredComponents: [],

    /**
     * @private
     */
    _state: 0,

    /**
     * @private
     */
    _originalTitle: ''
};

/**
 *
 *
 * @depends History.js
 */
Exhibit.History.init = function() {
    if (typeof History !== "undefined" && History.enabled) {
        Exhibit.History.enabled = true;
        Exhibit.History._originalTitle = document.title;

        Exhibit.History.stateListener();
        $(window).bind('statechange', Exhibit.History.stateListener);
        // initial state without bookmarking lacks a title, replaceState
        // should run if no bookmark was executed first to set the title
        // appropriately
        if (!Exhibit.Bookmark.run) {
            Exhibit.History.pushState(null, Exhibit.History._originalTitle, null);
        }
    }
};

/**
 *
 */
Exhibit.History.stateListener = function() {
};

/**
 * Register any thing on the page that maintains state in response to user
 * interaction.  Registered components are polled after each interaction
 * to gather history tracking information.  Each component must implement
 * the method getState that returns identifying information and the current
 * state, enough to replicate the current state to a different user.
 *
 */
Exhibit.History.register = function() {
};

/**
 * Passes through to History.js History.getState function.
 *
 * @static
 * @returns {Object}
 */
Exhibit.History.getState = function() {
    if (Exhibit.History.enabled) {
        return History.getState();
    } else {
        return null;
    }
};

/**
 * Passes through to History.js History.pushState function.
 *
 * @static
 * @param data {Object}
 * @param title {String}
 * @param url {String}
 */
Exhibit.History.pushState = function(data, title, url) {
    if (Exhibit.History.enabled) {
        History.pushState(data, title, url);
    }
};

/**
 * Passes through to History.js History.replaceState function.
 *
 * @static
 * @param data {Object}
 * @param title {String}
 * @param url {String}
 */
Exhibit.History.replaceState = function(data, title, url) {
    if (Exhibit.History.enabled) {
        History.replaceState(data, title, url);
    }
};
<commit_msg>running bookmark from within, fleshing out the role of various methods and making the behavior more solid and deterministic<commit_after>/**
 * This requires the entire Exhibit infrastructure to be oriented around
 * generating registered state changes.
 */

/**
 * @fileOverview Local interface to a history manager.
 * @author Ryan Lee
 */

/**
 * @namespace For history management related methods.
 */
Exhibit.History = {
    /**
     * Whether the history module is available.
     */
    enabled: false,

    /**
     * @private
     */
    _state: 0,

    /**
     * @private
     */
    _originalTitle: "",

    /**
     * @private
     */
    _originalLocation: ""
};

/**
 * @depends History.js
 */
Exhibit.History.init = function() {
    var state, types, i, j, keys, component;

    if (typeof History !== "undefined" && History.enabled) {
        Exhibit.History.enabled = true;
        Exhibit.History._originalTitle = document.title;
        Exhibit.History._originalLocation = Exhibit.Persistence.getURLWithoutQueryAndHash();

        $(window).bind("statechange", Exhibit.History.stateListener);
        if (Exhibit.Bookmark.runBookmark()) {
            Exhibit.Bookmark.implementBookmark(Exhibit.Bookmark.state);
        } else {
            state = Exhibit.History.getState();
            if (typeof state.data.components === "undefined") {
                state.data.components = {};
                state.data.state = Exhibit.History._state;
                types = [ "facet", "view", "viewPanel" ];
                for (i = 0; i < types.length; i++) {
                    keys = Exhibit.Registry.getKeys(types[i]);
                    for (j = 0; j < keys.length; j++) {
                        component = Exhibit.Registry.get(types[i], keys[j]);
                        if (typeof component.exportState === "function") {
                            state.data.components[keys[j]] = {};
                            state.data.components[keys[j]].type = types[i];
                            state.data.components[keys[j]].state = component.exportState();
                        }
                    }
                }
                Exhibit.History.replaceState(state.data);
            }
            Exhibit.History.stateListener();
        }
    }
};

/**
 * @param {jQuery.Event} evt
 */
Exhibit.History.stateListener = function(evt) {
    var fullState, components, key, id, componentState, component;

    fullState = Exhibit.History.getState();

    if (fullState.data.lengthy) {
        $(document).trigger("busyDialogStart.exhibit");
    }

    components = fullState.data.components;
    for (key in components) {
        console.log(key);
        if (components.hasOwnProperty(key)) {
            componentState = components[key].state;
            component = Exhibit.Registry.get(components[key].type, key);
            if (component !== null &&
                typeof component.importState === "function") {
                console.log(componentState);
                component.importState(componentState);
            }
        }
    }
    Exhibit.History._state = fullState.data.state || 0;

    if (fullState.data.lengthy) {
        $(document).trigger("busyDialogEnd.exhibit");
    }
};

/**
 * Passes through to History.js History.getState function.
 *
 * @static
 * @returns {Object}
 */
Exhibit.History.getState = function() {
    if (Exhibit.History.enabled) {
        return History.getState();
    } else {
        return null;
    }
};

Exhibit.History.pushComponentState = function(component, registry, data, subtitle, lengthy) {
    var state = Exhibit.History.getState();

    if (typeof state === "undefined" || state === null) {
        state = { "data": { "components": {} } };
    }

    if (!state.hasOwnProperty("data")) {
        state.data = { "components": {} };
    }
    if (!state.data.hasOwnProperty("components")) {
        state.data.components = {};
    }

    state.data.lengthy = lengthy;
    state.data.components[component.getID()] = {
        "type": registry,
        "state": data
    }

    Exhibit.History.pushState(state.data, subtitle);
};

/**
 * Passes through to History.js History.pushState function.
 *
 * @static
 * @param {Object} data
 * @param {String} subtitle
 */
Exhibit.History.pushState = function(data, subtitle) {
    var title, url;

    if (Exhibit.History.enabled) {
        Exhibit.History._state++;
        data.state = Exhibit.History._state;

        title = Exhibit.History._originalTitle;

        if (typeof subtitle !== "undefined" &&
            subtitle !== null &&
            subtitle !== "") {
            title += " {" + subtitle + "}";
        }

        url = Exhibit.History._originalLocation;

        History.pushState(data, title, url);
    }
};

/**
 * Passes through to History.js History.replaceState function.
 *
 * @static
 * @param {Object} data
 * @param {String} subtitle
 */
Exhibit.History.replaceState = function(data, subtitle, url) {
    var title;

    if (Exhibit.History.enabled) {
        title = Exhibit.History._originalTitle;

        if (typeof subtitle !== "undefined" &&
            subtitle !== null &&
            subtitle !== "") {
            title += " {" + subtitle + "}";
        }

        if (typeof url === "undefined" || url === null) {
            url = Exhibit.History._originalLocation;
        }

        History.replaceState(data, title, url);
    }
};
<|endoftext|>


<commit_before>/*
 * KOTournament Test
 */
define([ '../kotournament', '../game', '../tournament', '../../lib/implements' ], function (KOTournament, Game, Tournament, Interface) {
  QUnit.test("KOTournament", function () {
    var i, names, kot, opts;

    QUnit.equal(Interface(Tournament), '', 'Tournament interface validation');
    QUnit.equal(Interface(Tournament, KOTournament, 'rfm'), '', 'KOTournament interface match');

    names = [ 'Antje', 'Basta', 'Christian', 'David', 'Erik', 'Fabe', 'Gert',
        'Hartmut', 'Inka', 'Jennifer' ];

    kot = new KOTournament();
    for (i in names) {
      kot.addPlayer(Number(i));
    }

    opts = kot.getOptions();
    opts.matchingMethod = KOTournament.OPTIONS.matchingMethod.set;
    opts.loserMatchMinRound = 1;
    kot.setOptions(opts);
    kot.start();

    while (kot.getState() === Tournament.STATE.RUNNING) {
      kot.finishGame(kot.getGames()[0], [ 13, 0 ]);
      // FIXME visual investigation shows too few 'third place' matches
    }

    return;
  });
});
<commit_msg>further KOTournament unit test debugging<commit_after>/*
 * KOTournament Test
 */
define([ '../kotournament', '../game', '../tournament', '../../lib/implements' ], function (KOTournament, Game, Tournament, Interface) {
  QUnit.test("KOTournament", function () {
    var i, names, kot, opts;

    QUnit.equal(Interface(Tournament), '', 'Tournament interface validation');
    QUnit.equal(Interface(Tournament, KOTournament, 'rfm'), '', 'KOTournament interface match');

    names = [ 'Antje', 'Basta', 'Christian', 'David', 'Erik', 'Fabe', 'Gert',
        'Hartmut', 'Inka', 'Jennifer' ];

    kot = new KOTournament();
    for (i in names) {
      kot.addPlayer(Number(i));
    }

    opts = kot.getOptions();
    opts.matchingMethod = KOTournament.OPTIONS.firstround.set;
    opts.loserMatchMinRound = 1;
    kot.setOptions(opts);
    kot.start();

    while (kot.getState() === Tournament.STATE.RUNNING) {
      kot.finishGame(kot.getGames()[0], [ 13, 0 ]);
      // FIXME visual investigation shows too few 'third place' matches
    }

    return;
  });
});
<|endoftext|>


<commit_before>/**
 * Mainly for tweaking UI
 * Since there are still no decent mock suite for Webextension,
 * I faked a subset of common apis by hand to mimic the behaviours.
 */
import _ from 'lodash'

const locales = require('@/_locales/messages.json')

const platform =
  navigator.userAgent.indexOf('Chrome') !== -1 ? 'chrome' : 'firefox'

// Tabs and runtime share the same message tunnel since we are developing only one page.
// Messages won't be sent back to its own script so we use two tunnels here.
window.msgPageListeners = window.msgPageListeners || []
window.msgBackgroundListeners = window.msgBackgroundListeners || []

window.browser = {
  browserAction: {
    onClicked: {
      addListener () {}
    },
    setTitle () {},
    setBadgeText () {},
    getBadgeText (x, cb) {
      cb(Date.now().toString())
    },
    setBadgeBackgroundColor () {}
  },
  commands: {
    onCommand: {
      addListener () {},
      hasListener () {},
      removeListener () {}
    }
  },
  contextMenus: {
    onClicked: {
      addListener () {},
      hasListener () {},
      removeListener () {}
    },
    removeAll () {
      return Promise.resolve()
    },
    create () {
      return Promise.resolve()
    }
  },
  extension: {
    inIncognitoContext: false
  },
  i18n: {
    getMessage (k) {
      return locales[k] ? locales[k].zh_CN : k
    },
    getUILanguage () {
      return 'en'
    }
  },
  notifications: {
    create: _.partial(console.log, 'create notifications:'),
    onClicked: {
      addListener () {}
    }
  },
  storage: genStorageApis(),
  tabs: {
    create ({ url }) {
      window.open(url || '')
      return Promise.resolve({
        active: true,
        url: url,
        id: String(Date.now())
        // ... add other info accordingly
      })
    },
    query () {
      return Promise.resolve([])
    },
    highlight () {
      return Promise.resolve()
    },
    // No other tab to receive anyway
    sendMessage () {
      return Promise.resolve()
    }
  },
  webRequest: {
    onBeforeRequest: {
      addListener () {},
      hasListener () {},
      removeListener () {}
    },
    onHeadersReceived: {
      addListener () {},
      hasListener () {},
      removeListener () {}
    }
  },
  runtime: {
    id: 'mdidnbkkjainbfbcenphabdajogedcnx',
    getURL (name) {
      return name
    },
    getPlatformInfo () {
      return Promise.resolve({ os: 'win' })
    },
    getManifest () {
      return _.assign(
        {},
        require(`../../src/manifest/common.manifest.json`),
        require('../../src/manifest/' + platform + '.manifest.json')
      )
    },
    reload () {
      window.location.reload(true)
    },
    onStartup: {
      addListener (listener) {
        if (!_.isFunction(listener)) {
          throw new TypeError('Wrong argument type')
        }
        // delay startup calls
        setTimeout(listener, 0)
      }
    },
    onInstalled: {
      addListener (listener) {
        if (!_.isFunction(listener)) {
          throw new TypeError('Wrong argument type')
        }
        listener({ reason: 'install' })
      }
    },
    sendMessage,
    onMessage: {
      addListener (listener) {
        if (!_.isFunction(listener)) {
          throw new TypeError('Wrong argument type')
        }
        if (!_.some(window.msgPageListeners, x => x === listener)) {
          window.msgPageListeners.push(listener)
        }
      },
      removeListener (listener) {
        if (!_.isFunction(listener)) {
          throw new TypeError('Wrong argument type')
        }
        window.msgPageListeners = _.filter(
          window.msgPageListeners,
          x => x !== listener
        )
      },
      hasListener (listener) {
        if (!_.isFunction(listener)) {
          throw new TypeError('Wrong argument type')
        }
        return _.some(window.msgPageListeners, x => x === listener)
      }
    }
  }
}

function sendMessage (extensionId, message) {
  return new Promise((resolve, reject) => {
    if (!_.isString(extensionId)) {
      message = extensionId
    }
    try {
      message = JSON.parse(JSON.stringify(message))
    } catch (err) {
      return reject(new TypeError('Wrong argument type'))
    }

    let isClosed = false
    let isAsync = false
    function sendResponse (response) {
      if (isClosed) {
        return reject(new Error('Attempt to response a closed channel'))
      }
      try {
        // deep clone & check data
        response = response && JSON.parse(JSON.stringify(response))
      } catch (err) {
        return reject(new TypeError('Response data not serializable'))
      }
      resolve(response)
    }

    _.each(window.msgBackgroundListeners, listener => {
      const hint = listener(message, {}, sendResponse)
      // return true or Promise to send a response asynchronously
      if (hint === true) {
        isAsync = true
      } else if (hint && _.isFunction(hint.then)) {
        isAsync = true
        hint.then(sendResponse)
      }
    })

    // close synchronous response
    setTimeout(() => {
      if (!isAsync) {
        isClosed = true
      }
    }, 0)
  })
}

/**
 * For both sync and local
 */
function genStorageApis () {
  const accounts = JSON.parse(process.env.VUE_APP_ACCOUNTS)
  Object.keys(accounts).forEach(key => {
    accounts[key] = btoa(encodeURIComponent(accounts[key]))
  })
  /* global storageData */
  window['storageData'] = {
    local: accounts,
    sync: {},
    listeners: []
  }

  return {
    sync: genStorageAreaApis('sync'),
    local: genStorageAreaApis('local'),
    managed: {
      // if you need to use managed area you should feed your own data
      get: () => ({})
    },
    onChanged: {
      addListener (listener) {
        if (!_.isFunction(listener)) {
          return Promise.reject(new TypeError('Wrong argument type'))
        }
        if (!_.some(storageData.listeners, x => x === listener)) {
          storageData.listeners.push(listener)
        }
      },
      removeListener (listener) {
        if (!_.isFunction(listener)) {
          return Promise.reject(new TypeError('Wrong argument type'))
        }
        storageData.listeners = _.filter(
          storageData.listeners,
          x => x !== listener
        )
      },
      hasListener (listener) {
        if (!_.isFunction(listener)) {
          return Promise.reject(new TypeError('Wrong argument type'))
        }
        return _.some(storageData.listeners, x => x === listener)
      }
    }
  }

  function genStorageAreaApis (area) {
    return {
      get (keys) {
        if (keys == null) {
          return Promise.resolve(_.cloneDeep(storageData[area]))
        }
        if (_.isString(keys)) {
          if (!keys) {
            return Promise.resolve({})
          }
          keys = [keys]
        } else if (_.isArray(keys)) {
          if (keys.length <= 0) {
            return Promise.resolve({})
          }
        } else if (_.isObject(keys)) {
          keys = Object.keys(keys)
          if (keys.length <= 0) {
            return Promise.resolve({})
          }
        } else {
          return Promise.reject(new TypeError('Wrong argument type'))
        }
        return Promise.resolve(_.pick(_.cloneDeep(storageData[area]), keys))
      },
      set (keys) {
        if (!_.isObject(keys)) {
          return Promise.reject(new TypeError('Argument 1 should be an object'))
        }
        try {
          // deep clone & check data
          keys = JSON.parse(JSON.stringify(keys))
        } catch (err) {
          return Promise.reject(new TypeError('Data not serializable'))
        }
        const newData = _.assign({}, storageData[area], keys)
        const changes = Object.keys(keys)
          .filter(k => !_.isEqual(newData[k], storageData[area][k]))
          .reduce((x, k) => {
            x[k] = {
              newValue: _.cloneDeep(newData[k]),
              oldValue: _.cloneDeep(storageData[area][k])
            }
            return x
          }, {})
        if (Object.keys(changes).length > 0) {
          setTimeout(() => alertListeners(changes, area), 0)
        }
        storageData[area] = newData
        return Promise.resolve()
      },
      remove (keys) {
        if (_.isString(keys)) {
          keys = keys ? [keys] : []
        } else if (!_.isArray(keys)) {
          return Promise.reject(new TypeError('Wrong argument type'))
        }
        const newData = _.omit(storageData[area], keys)
        const changes = keys
          .filter(k => !_.isUndefined(storageData[area][k]))
          .reduce((x, k) => {
            x[k] = {
              newValue: undefined,
              oldValue: _.cloneDeep(storageData[area][k])
            }
            return x
          }, {})
        if (changes.length > 0) {
          setTimeout(() => alertListeners(changes, area), 0)
        }
        storageData[area] = newData
        return Promise.resolve()
      },
      clear () {
        const changes = Object.keys(storageData[area])
          .filter(k => !_.isUndefined(storageData[area][k]))
          .reduce((x, k) => {
            x[k] = {
              newValue: undefined,
              oldValue: _.cloneDeep(storageData[area][k])
            }
            return x
          }, {})
        if (changes.length > 0) {
          setTimeout(() => alertListeners(changes, area), 0)
        }
        storageData[area] = {}
        return Promise.resolve()
      },
      getBytesInUse (keys) {
        if (_.isNull(keys)) {
          return Promise.resolve(
            new Blob([JSON.stringify(storageData[area])]).size
          )
        }
        if (_.isString(keys)) {
          keys = keys ? [keys] : []
        } else if (!_.isArray(keys)) {
          return Promise.reject(new TypeError('Wrong argument type'))
        }
        if (keys.length <= 0) {
          return Promise.resolve(0)
        }
        return Promise.resolve(
          new Blob([JSON.stringify(_.pick(storageData[area], keys))]).size
        )
      }
    }
  }

  function alertListeners (changes, area) {
    storageData.listeners.forEach(listener =>
      listener(_.cloneDeep(changes), area)
    )
  }
}
<commit_msg>fix accounts data<commit_after>/**
 * Mainly for tweaking UI
 * Since there are still no decent mock suite for Webextension,
 * I faked a subset of common apis by hand to mimic the behaviours.
 */
import _ from 'lodash'

const locales = require('@/_locales/messages.json')

const platform =
  navigator.userAgent.indexOf('Chrome') !== -1 ? 'chrome' : 'firefox'

// Tabs and runtime share the same message tunnel since we are developing only one page.
// Messages won't be sent back to its own script so we use two tunnels here.
window.msgPageListeners = window.msgPageListeners || []
window.msgBackgroundListeners = window.msgBackgroundListeners || []

window.browser = {
  browserAction: {
    onClicked: {
      addListener () {}
    },
    setTitle () {},
    setBadgeText () {},
    getBadgeText (x, cb) {
      cb(Date.now().toString())
    },
    setBadgeBackgroundColor () {}
  },
  commands: {
    onCommand: {
      addListener () {},
      hasListener () {},
      removeListener () {}
    }
  },
  contextMenus: {
    onClicked: {
      addListener () {},
      hasListener () {},
      removeListener () {}
    },
    removeAll () {
      return Promise.resolve()
    },
    create () {
      return Promise.resolve()
    }
  },
  extension: {
    inIncognitoContext: false
  },
  i18n: {
    getMessage (k) {
      return locales[k] ? locales[k].zh_CN : k
    },
    getUILanguage () {
      return 'en'
    }
  },
  notifications: {
    create: _.partial(console.log, 'create notifications:'),
    onClicked: {
      addListener () {}
    }
  },
  storage: genStorageApis(),
  tabs: {
    create ({ url }) {
      window.open(url || '')
      return Promise.resolve({
        active: true,
        url: url,
        id: String(Date.now())
        // ... add other info accordingly
      })
    },
    query () {
      return Promise.resolve([])
    },
    highlight () {
      return Promise.resolve()
    },
    // No other tab to receive anyway
    sendMessage () {
      return Promise.resolve()
    }
  },
  webRequest: {
    onBeforeRequest: {
      addListener () {},
      hasListener () {},
      removeListener () {}
    },
    onHeadersReceived: {
      addListener () {},
      hasListener () {},
      removeListener () {}
    }
  },
  runtime: {
    id: 'mdidnbkkjainbfbcenphabdajogedcnx',
    getURL (name) {
      return name
    },
    getPlatformInfo () {
      return Promise.resolve({ os: 'win' })
    },
    getManifest () {
      return _.assign(
        {},
        require(`../../src/manifest/common.manifest.json`),
        require('../../src/manifest/' + platform + '.manifest.json')
      )
    },
    reload () {
      window.location.reload(true)
    },
    onStartup: {
      addListener (listener) {
        if (!_.isFunction(listener)) {
          throw new TypeError('Wrong argument type')
        }
        // delay startup calls
        setTimeout(listener, 0)
      }
    },
    onInstalled: {
      addListener (listener) {
        if (!_.isFunction(listener)) {
          throw new TypeError('Wrong argument type')
        }
        listener({ reason: 'install' })
      }
    },
    sendMessage,
    onMessage: {
      addListener (listener) {
        if (!_.isFunction(listener)) {
          throw new TypeError('Wrong argument type')
        }
        if (!_.some(window.msgPageListeners, x => x === listener)) {
          window.msgPageListeners.push(listener)
        }
      },
      removeListener (listener) {
        if (!_.isFunction(listener)) {
          throw new TypeError('Wrong argument type')
        }
        window.msgPageListeners = _.filter(
          window.msgPageListeners,
          x => x !== listener
        )
      },
      hasListener (listener) {
        if (!_.isFunction(listener)) {
          throw new TypeError('Wrong argument type')
        }
        return _.some(window.msgPageListeners, x => x === listener)
      }
    }
  }
}

function sendMessage (extensionId, message) {
  return new Promise((resolve, reject) => {
    if (!_.isString(extensionId)) {
      message = extensionId
    }
    try {
      message = JSON.parse(JSON.stringify(message))
    } catch (err) {
      return reject(new TypeError('Wrong argument type'))
    }

    let isClosed = false
    let isAsync = false
    function sendResponse (response) {
      if (isClosed) {
        return reject(new Error('Attempt to response a closed channel'))
      }
      try {
        // deep clone & check data
        response = response && JSON.parse(JSON.stringify(response))
      } catch (err) {
        return reject(new TypeError('Response data not serializable'))
      }
      resolve(response)
    }

    _.each(window.msgBackgroundListeners, listener => {
      const hint = listener(message, {}, sendResponse)
      // return true or Promise to send a response asynchronously
      if (hint === true) {
        isAsync = true
      } else if (hint && _.isFunction(hint.then)) {
        isAsync = true
        hint.then(sendResponse)
      }
    })

    // close synchronous response
    setTimeout(() => {
      if (!isAsync) {
        isClosed = true
      }
    }, 0)
  })
}

/**
 * For both sync and local
 */
function genStorageApis () {
  const accounts = JSON.parse(process.env.VUE_APP_ACCOUNTS)
  Object.keys(accounts).forEach(key => {
    accounts[key].enable = true
    accounts[key] = btoa(encodeURIComponent(JSON.stringify(accounts[key])))
  })
  /* global storageData */
  window['storageData'] = {
    local: accounts,
    sync: {},
    listeners: []
  }

  return {
    sync: genStorageAreaApis('sync'),
    local: genStorageAreaApis('local'),
    managed: {
      // if you need to use managed area you should feed your own data
      get: () => ({})
    },
    onChanged: {
      addListener (listener) {
        if (!_.isFunction(listener)) {
          return Promise.reject(new TypeError('Wrong argument type'))
        }
        if (!_.some(storageData.listeners, x => x === listener)) {
          storageData.listeners.push(listener)
        }
      },
      removeListener (listener) {
        if (!_.isFunction(listener)) {
          return Promise.reject(new TypeError('Wrong argument type'))
        }
        storageData.listeners = _.filter(
          storageData.listeners,
          x => x !== listener
        )
      },
      hasListener (listener) {
        if (!_.isFunction(listener)) {
          return Promise.reject(new TypeError('Wrong argument type'))
        }
        return _.some(storageData.listeners, x => x === listener)
      }
    }
  }

  function genStorageAreaApis (area) {
    return {
      get (keys) {
        if (keys == null) {
          return Promise.resolve(_.cloneDeep(storageData[area]))
        }
        if (_.isString(keys)) {
          if (!keys) {
            return Promise.resolve({})
          }
          keys = [keys]
        } else if (_.isArray(keys)) {
          if (keys.length <= 0) {
            return Promise.resolve({})
          }
        } else if (_.isObject(keys)) {
          keys = Object.keys(keys)
          if (keys.length <= 0) {
            return Promise.resolve({})
          }
        } else {
          return Promise.reject(new TypeError('Wrong argument type'))
        }
        return Promise.resolve(_.pick(_.cloneDeep(storageData[area]), keys))
      },
      set (keys) {
        if (!_.isObject(keys)) {
          return Promise.reject(new TypeError('Argument 1 should be an object'))
        }
        try {
          // deep clone & check data
          keys = JSON.parse(JSON.stringify(keys))
        } catch (err) {
          return Promise.reject(new TypeError('Data not serializable'))
        }
        const newData = _.assign({}, storageData[area], keys)
        const changes = Object.keys(keys)
          .filter(k => !_.isEqual(newData[k], storageData[area][k]))
          .reduce((x, k) => {
            x[k] = {
              newValue: _.cloneDeep(newData[k]),
              oldValue: _.cloneDeep(storageData[area][k])
            }
            return x
          }, {})
        if (Object.keys(changes).length > 0) {
          setTimeout(() => alertListeners(changes, area), 0)
        }
        storageData[area] = newData
        return Promise.resolve()
      },
      remove (keys) {
        if (_.isString(keys)) {
          keys = keys ? [keys] : []
        } else if (!_.isArray(keys)) {
          return Promise.reject(new TypeError('Wrong argument type'))
        }
        const newData = _.omit(storageData[area], keys)
        const changes = keys
          .filter(k => !_.isUndefined(storageData[area][k]))
          .reduce((x, k) => {
            x[k] = {
              newValue: undefined,
              oldValue: _.cloneDeep(storageData[area][k])
            }
            return x
          }, {})
        if (changes.length > 0) {
          setTimeout(() => alertListeners(changes, area), 0)
        }
        storageData[area] = newData
        return Promise.resolve()
      },
      clear () {
        const changes = Object.keys(storageData[area])
          .filter(k => !_.isUndefined(storageData[area][k]))
          .reduce((x, k) => {
            x[k] = {
              newValue: undefined,
              oldValue: _.cloneDeep(storageData[area][k])
            }
            return x
          }, {})
        if (changes.length > 0) {
          setTimeout(() => alertListeners(changes, area), 0)
        }
        storageData[area] = {}
        return Promise.resolve()
      },
      getBytesInUse (keys) {
        if (_.isNull(keys)) {
          return Promise.resolve(
            new Blob([JSON.stringify(storageData[area])]).size
          )
        }
        if (_.isString(keys)) {
          keys = keys ? [keys] : []
        } else if (!_.isArray(keys)) {
          return Promise.reject(new TypeError('Wrong argument type'))
        }
        if (keys.length <= 0) {
          return Promise.resolve(0)
        }
        return Promise.resolve(
          new Blob([JSON.stringify(_.pick(storageData[area], keys))]).size
        )
      }
    }
  }

  function alertListeners (changes, area) {
    storageData.listeners.forEach(listener =>
      listener(_.cloneDeep(changes), area)
    )
  }
}
<|endoftext|>