<commit_before>const Command = require("../base/slashCommand");
require("moment-duration-format");

class MyMods extends Command {
    constructor(Bot) {
        super(Bot, {
            name: "mymods",
            category: "SWGoH",
            guildOnly: false,
            aliases: ["charactermods", "charmods", "cmods", "cm", "mm"],
            permissions: ["EMBED_LINKS"],
            options: [
                {
                    name: "character",
                    description: "Show the mod stat for the specified character",
                    type: "SUB_COMMAND",
                    options: [
                        {
                            name: "character",
                            required: true,
                            type: "STRING",
                            description: "The character you want to check"
                        },
                        {
                            name: "allycode",
                            description: "The ally code for whoever you're wanting to look up",
                            type: "STRING"
                        },
                    ]
                },
                {
                    name: "best",
                    type: "SUB_COMMAND",
                    description: "Show the characters that have the best of a stat",
                    options: [
                        {
                            name: "stat",
                            required: true,
                            description: "Which stat you want it to show",
                            type: "STRING",
                            choices: [
                                { name: "Health", value: "Health" },
                                { name: "Protection", value: "Protection" },
                                { name: "Speed", value: "Speed" },
                                { name: "Potency", value: "Potency" },
                                { name: "Physical Critical Chance", value: "Physical Critical Chance" },
                                { name: "Physical Damage", value: "Physical Damage" },
                                { name: "Special Critical Chance", value: "Special Critical Chance" },
                                { name: "Special Damage", value: "Special Damage" },
                                { name: "Critical Damage", value: "Critical Damage" },
                                { name: "Tenacity", value: "Tenacity" },
                                { name: "Accuracy", value: "Accuracy" },
                                { name: "Armor", value: "Armor" },
                                { name: "Resistance", value: "Resistance" }
                            ]
                        },
                        {
                            name: "allycode",
                            description: "The ally code for whoever you're wanting to look up",
                            type: "STRING"
                        },
                        {
                            name: "total",
                            type: "BOOLEAN",
                            description: "Show the total stat, instead of just what the mods add on"
                        }
                    ]
                }
            ]
        });
    }

    async run(Bot, interaction) {
        const cooldown = await Bot.getPlayerCooldown(interaction.user.id);

        const subCommand = interaction.options.getSubcommand();
        let allycode = interaction.options.getString("allycode");
        allycode = await Bot.getAllyCode(interaction, allycode);

        if (!allycode) {
            return super.error(interaction, "I could not find a match for the provided ally code.");
        }
        await interaction.reply({content: interaction.language.get("COMMAND_MYMODS_WAIT")});

        if (subCommand === "character") {
            const searchChar = interaction.options.getString("character");

            let character;
            if (!searchChar) {
                return interaction.editReply({content: interaction.language.get("BASE_SWGOH_MISSING_CHAR")});
            }

            const chars = Bot.findChar(searchChar, Bot.characters);
            if (chars.length === 0) {
                return interaction.editReply({content: interaction.language.get("BASE_SWGOH_NO_CHAR_FOUND", searchChar)});
            } else if (chars.length > 1) {
                const charL = [];
                const charS = chars.sort((p, c) => p.name > c.name ? 1 : -1);
                charS.forEach(c => {
                    charL.push(c.name);
                });
                return super.error(interaction, (interaction.language.get("BASE_SWGOH_CHAR_LIST", charL.join("\n"))), {edit: true});
            } else {
                character = chars[0];
            }

            let player;
            try {
                player = await Bot.swgohAPI.unitStats(allycode, cooldown);
                if (Array.isArray(player)) player = player[0];
            } catch (e) {
                Bot.logger.error(e);
            }

            if (!player) {
                // TODO Lang this
                return super.error(interaction, ("Sorry, but I could not load your profile at this time."), {edit: true});
            }

            const footer = Bot.updatedFooter(player.updated, interaction, "player", cooldown);

            let charMods = player.roster.find(c => c.defId === character.uniqueName);

            if (!charMods) {
                return super.error(interaction, "Looks like you don't have that character activated yet.");
            }

            charMods = await Bot.swgohAPI.langChar(charMods, interaction.guildSettings.swgohLanguage);

            if (charMods) {
                charMods = charMods.mods;
                const slots = {};

                const sets = interaction.language.get("BASE_MODSETS_FROM_GAME");
                const stats = interaction.language.get("BASE_MODS_FROM_GAME");

                charMods.forEach(mod => {
                    slots[mod.slot] = {
                        stats: [],
                        type: sets[mod.set],
                        lvl: mod.level,
                        pip: mod.pips
                    };

                    // Add the primary in
                    slots[mod.slot].stats.push(`${mod.primaryStat.value} ${stats[mod.primaryStat.unitStat].replace("+", "").replace("%", "")}`);

                    // Then all the secondaries
                    mod.secondaryStat.forEach(s => {
                        let t = stats[s.unitStat];
                        if (t.indexOf("%") > -1) {
                            t = t.replace("%", "").trim();
                            s.value = s.value.toFixed(2) + "%";
                        }

                        let statStr = s.value;
                        if (s.roll > 0) statStr = `(${s.roll}) ${statStr}`;
                        statStr +=  " " + t;
                        slots[mod.slot].stats.push(statStr);
                    });
                });

                const fields = [];
                const modSlots = ["square", "arrow", "diamond", "triangle", "circle", "cross"];
                Object.keys(slots).forEach(mod => {
                    let typeIcon  = slots[mod].type;
                    let shapeIcon = Bot.toProperCase(modSlots[mod-1]);
                    const stats = slots[mod].stats;
                    // If the bot has the right perms to use external emotes, go for it
                    if (!interaction.guild || interaction.channel.permissionsFor(interaction.guild.me).has("USE_EXTERNAL_EMOJIS")) {
                        const shapeIconString = `${modSlots[mod-1]}Mod${slots[mod].pip === 6 ? "Gold" : ""}`;
                        shapeIcon = Bot.emotes[shapeIconString] || shapeIcon;

                        const typeIconString = `modset${slots[mod].type.replace(/\s*/g, "")}`;
                        typeIcon = Bot.emotes[typeIconString] || typeIcon;
                    }
                    fields.push({
                        name: `${shapeIcon} ${typeIcon} (${slots[mod].pip}* Lvl: ${slots[mod].lvl})`,
                        value: `**${stats.shift()}**\n${stats.join("\n")}\n\`${"-".repeat(23)}\``,
                        inline: true
                    });
                });

                return interaction.editReply({content: null, embeds: [{
                    author: {
                        name: `${player.name}'s ${character.name}`,
                        icon_url: character.avatarURL
                    },
                    fields: fields,
                    footer: footer
                }]});
            } else {
                // They don't have the character
                return interaction.editReply({content: null, embeds: [{
                    author: {
                        name: player.name + "'s " + character.name
                    },
                    description: interaction.language.get("BASE_SWGOH_LOCKED_CHAR"),
                    footer: footer
                }]});
            }
        } else if (subCommand === "best") {
            const statToCheck = interaction.options.getString("stat");
            const showTotal = interaction.options.getBoolean("total");

            let player;
            try {
                player = await Bot.swgohAPI.unitStats(allycode, cooldown);
                if (Array.isArray(player)) player = player[0];
            } catch (e) {
                return super.error(interaction, Bot.codeBlock(e.message), {
                    title: interaction.language.get("BASE_SOMETHING_BROKE"),
                    footer: "Please try again in a bit."
                });
            }

            let updated, stats;
            if (player && player.roster) {
                updated = player.updated;
                stats = player.roster;
            }

            // Get rid of the ship listings
            const delArr = [];
            for (const c in stats) {
                if (Bot.ships.find(s => s.uniqueName === stats[c].defId)) {
                    delArr.push(c);
                }
            }
            for (const ix of delArr.reverse()) {
                stats.splice(ix, 1);
            }

            stats.forEach(c => {
                if (c.stats && c.stats.final && !c.stats.final[statToCheck]) {
                    c.stats.final[statToCheck] = 0;
                }
                if (c.stats && c.stats.mods && !c.stats.mods[statToCheck]) {
                    c.stats.mods[statToCheck] = 0;
                }
            });

            let sorted;
            if (showTotal) {  // If looking for the total stats
                sorted = stats.sort((p, c) => {
                    if (p.stats && c.stats && p.stats.final[statToCheck] && c.stats.final[statToCheck]) {
                        return c.stats.final[statToCheck] - p.stats.final[statToCheck];
                    } else if (!c.stats || !c.stats.final[statToCheck]) {
                        return -1;
                    } else {
                        return 1;
                    }
                });
            } else {  // Or if looking for just the amount added by mods
                sorted = stats.sort((p, c) => {
                    if (p.stats && c.stats && p.stats.mods  && c.stats.mods && p.stats.mods[statToCheck] && c.stats.mods[statToCheck]) {
                        return c.stats.mods[statToCheck] - p.stats.mods[statToCheck];
                    } else if (!c.stats || !c.stats.mods[statToCheck]) {
                        return -1;
                    } else {
                        return 1;
                    }
                });
            }

            for (const c in sorted) {
                sorted[c] = await Bot.swgohAPI.langChar(sorted[c], interaction.guildSettings.swgohLanguage);
            }

            const out = sorted.map(c => {
                const finalStat = c.stats && c.stats.final[statToCheck] ? (!c.stats.final[statToCheck] % 1 === 0 ? c.stats.final[statToCheck] : (c.stats.final[statToCheck] * 100).toFixed(2)+"%") : 0;
                const modStat = c.stats && c.stats.final[statToCheck] ? (!c.stats.final[statToCheck] % 1 === 0 ? `(${parseInt(c.stats.mods[statToCheck], 10)})` : `(${(c.stats.mods[statToCheck] * 100).toFixed(2)}%)`) : "";
                return {
                    stat: `${finalStat}${modStat.length ? " " + modStat : ""}`,
                    name: `: ${c.nameKey}`
                };
            });
            const longest = out.reduce((max, s) => Math.max(max, s.stat.length), 0);
            let outStr = "";
            for (let ix = 0; ix < 20; ix++) {
                outStr += "`" + out[ix].stat + ` ${Bot.constants.zws}`.repeat(longest-out[ix].stat.length) + "`**" + out[ix].name + "**\n";
            }
            const author = {};
            if (showTotal) {
                // ${playerName}'s Highest ${stat} Characters
                author.name = interaction.language.get("COMMAND_MYMODS_HEADER_TOTAL", player.name, statToCheck);
            } else {
                // ${playerName}'s Best ${stat} From Mods
                author.name = interaction.language.get("COMMAND_MYMODS_HEADER_MODS", player.name, statToCheck);
            }

            const fields = [];
            if (stats.warnings) {
                fields.push({
                    name: "Warnings",
                    value: stats.warnings.join("\n")
                });
            }

            return interaction.editReply({content: null, embeds: [{
                author: author,
                description: "==============================\n" + outStr + "==============================",
                fields: fields,
                footer: {
                    text: updated ? interaction.language.get("BASE_SWGOH_LAST_UPDATED", Bot.duration(updated, interaction)) : ""
                }
            }]});
        }
    }
}

module.exports = MyMods;
<commit_msg>Add `/mymods bestmods` to compare individual mods<commit_after>const Command = require("../base/slashCommand");
const statEnums = require("../data/statEnum.js");

const modSlots = ["square", "arrow", "diamond", "triangle", "circle", "cross"];

class MyMods extends Command {
    constructor(Bot) {
        super(Bot, {
            name: "mymods",
            category: "SWGoH",
            guildOnly: false,
            aliases: ["charactermods", "charmods", "cmods", "cm", "mm"],
            permissions: ["EMBED_LINKS"],
            options: [
                {
                    name: "character",
                    description: "Show the mod stat for the specified character",
                    type: "SUB_COMMAND",
                    options: [
                        {
                            name: "character",
                            required: true,
                            type: "STRING",
                            description: "The character you want to check"
                        },
                        {
                            name: "allycode",
                            description: "The ally code for whoever you're wanting to look up",
                            type: "STRING"
                        },
                    ]
                },
                {
                    name: "best",
                    type: "SUB_COMMAND",
                    description: "Show the characters that have the best of a stat",
                    options: [
                        {
                            name: "stat",
                            required: true,
                            description: "Which stat you want it to show",
                            type: "STRING",
                            choices: [
                                { name: "Accuracy",                 value: "Accuracy" },
                                { name: "Armor",                    value: "Armor" },
                                { name: "Critical Damage",          value: "Critical Damage" },
                                { name: "Health",                   value: "Health" },
                                { name: "Physical Critical Chance", value: "Physical Critical Chance" },
                                { name: "Physical Damage",          value: "Physical Damage" },
                                { name: "Potency",                  value: "Potency" },
                                { name: "Protection",               value: "Protection" },
                                { name: "Resistance",               value: "Resistance" },
                                { name: "Special Critical Chance",  value: "Special Critical Chance" },
                                { name: "Special Damage",           value: "Special Damage" },
                                { name: "Speed",                    value: "Speed" },
                                { name: "Tenacity",                 value: "Tenacity" },
                            ]
                        },
                        {
                            name: "allycode",
                            description: "The ally code for whoever you're wanting to look up",
                            type: "STRING"
                        },
                        {
                            name: "total",
                            type: "BOOLEAN",
                            description: "Show the total stat, instead of just what the mods add on"
                        }
                    ]
                },
                {
                    name: "bestmods",
                    type: "SUB_COMMAND",
                    description: "Show the characters that have the best of a stat",
                    options: [
                        {
                            name: "stat",
                            required: true,
                            description: "Which stat you want it to show",
                            type: "STRING",
                            choices: [
                                { name: "Crit Chance %", value: "Critical Chance" }, // Crit Chance, 53
                                { name: "Defense",       value: "Defense" },         // Defense, 42
                                { name: "Health %",      value: "Health %" },        // Health %, 55
                                { name: "Health",        value: "Health" },          // Health flat value, 1
                                { name: "Offense %",     value: "Offense %" },       // Offense %, 48
                                { name: "Offense",       value: "Offense" },         // Offense flat value, 41
                                { name: "Potency %",     value: "Potency" },         // Potency %, 17
                                { name: "Protection %",  value: "Protection %" },    // Protection %, 56
                                { name: "Protection",    value: "Protection" },      // Protection flat value, 28
                                { name: "Speed",         value: "Speed" },           // Speed, 5
                                { name: "Tenacity %",    value: "Tenacity" },        // Tenacity %, 18
                            ]
                        },
                        {
                            name: "allycode",
                            description: "The ally code for whoever you're wanting to look up",
                            type: "STRING"
                        }
                    ]
                }
            ]
        });
    }

    async run(Bot, interaction) {
        const cooldown = await Bot.getPlayerCooldown(interaction.user.id);

        const subCommand = interaction.options.getSubcommand();
        let allycode = interaction.options.getString("allycode");
        allycode = await Bot.getAllyCode(interaction, allycode);

        if (!allycode) {
            return super.error(interaction, "I could not find a match for the provided ally code.");
        }
        await interaction.reply({content: interaction.language.get("COMMAND_MYMODS_WAIT")});

        if (subCommand === "character") {
            const searchChar = interaction.options.getString("character");

            let character;
            if (!searchChar) {
                return interaction.editReply({content: interaction.language.get("BASE_SWGOH_MISSING_CHAR")});
            }

            const chars = Bot.findChar(searchChar, Bot.characters);
            if (chars.length === 0) {
                return interaction.editReply({content: interaction.language.get("BASE_SWGOH_NO_CHAR_FOUND", searchChar)});
            } else if (chars.length > 1) {
                const charL = [];
                const charS = chars.sort((p, c) => p.name > c.name ? 1 : -1);
                charS.forEach(c => {
                    charL.push(c.name);
                });
                return super.error(interaction, (interaction.language.get("BASE_SWGOH_CHAR_LIST", charL.join("\n"))), {edit: true});
            } else {
                character = chars[0];
            }

            let player;
            try {
                player = await Bot.swgohAPI.unitStats(allycode, cooldown);
                if (Array.isArray(player)) player = player[0];
            } catch (e) {
                Bot.logger.error(e);
            }

            if (!player) {
                // TODO Lang this
                return super.error(interaction, ("Sorry, but I could not load your profile at this time."), {edit: true});
            }

            const footer = Bot.updatedFooter(player.updated, interaction, "player", cooldown);

            let charMods = player.roster.find(c => c.defId === character.uniqueName);

            if (!charMods) {
                return super.error(interaction, "Looks like you don't have that character activated yet.");
            }

            charMods = await Bot.swgohAPI.langChar(charMods, interaction.guildSettings.swgohLanguage);

            if (charMods) {
                charMods = charMods.mods;
                const slots = {};

                const sets = interaction.language.get("BASE_MODSETS_FROM_GAME");
                const stats = interaction.language.get("BASE_MODS_FROM_GAME");

                charMods.forEach(mod => {
                    slots[mod.slot] = {
                        stats: [],
                        type: sets[mod.set],
                        lvl: mod.level,
                        pip: mod.pips
                    };

                    // Add the primary in
                    slots[mod.slot].stats.push(`${mod.primaryStat.value} ${stats[mod.primaryStat.unitStat].replace("+", "").replace("%", "")}`);

                    // Then all the secondaries
                    mod.secondaryStat.forEach(s => {
                        let t = stats[s.unitStat];
                        if (t.indexOf("%") > -1) {
                            t = t.replace("%", "").trim();
                            s.value = s.value.toFixed(2) + "%";
                        }

                        let statStr = s.value;
                        if (s.roll > 0) statStr = `(${s.roll}) ${statStr}`;
                        statStr +=  " " + t;
                        slots[mod.slot].stats.push(statStr);
                    });
                });

                const fields = [];
                Object.keys(slots).forEach(mod => {
                    let typeIcon  = slots[mod].type;
                    let shapeIcon = Bot.toProperCase(modSlots[mod-1]);
                    const stats = slots[mod].stats;
                    // If the bot has the right perms to use external emotes, go for it
                    if (!interaction.guild || interaction.channel.permissionsFor(interaction.guild.me).has("USE_EXTERNAL_EMOJIS")) {
                        const shapeIconString = `${modSlots[mod-1]}Mod${slots[mod].pip === 6 ? "Gold" : ""}`;
                        shapeIcon = Bot.emotes[shapeIconString] || shapeIcon;

                        const typeIconString = `modset${slots[mod].type.replace(/\s*/g, "")}`;
                        typeIcon = Bot.emotes[typeIconString] || typeIcon;
                    }
                    fields.push({
                        name: `${shapeIcon} ${typeIcon} (${slots[mod].pip}* Lvl: ${slots[mod].lvl})`,
                        value: `**${stats.shift()}**\n${stats.join("\n")}\n\`${"-".repeat(23)}\``,
                        inline: true
                    });
                });

                return interaction.editReply({content: null, embeds: [{
                    author: {
                        name: `${player.name}'s ${character.name}`,
                        icon_url: character.avatarURL
                    },
                    fields: fields,
                    footer: footer
                }]});
            } else {
                // They don't have the character
                return interaction.editReply({content: null, embeds: [{
                    author: {
                        name: player.name + "'s " + character.name
                    },
                    description: interaction.language.get("BASE_SWGOH_LOCKED_CHAR"),
                    footer: footer
                }]});
            }
        } else if (subCommand === "best") {
            const statToCheck = interaction.options.getString("stat");
            const showTotal = interaction.options.getBoolean("total");

            let player;
            try {
                player = await Bot.swgohAPI.unitStats(allycode, cooldown);
                if (Array.isArray(player)) player = player[0];
            } catch (e) {
                return super.error(interaction, Bot.codeBlock(e.message), {
                    title: interaction.language.get("BASE_SOMETHING_BROKE"),
                    footer: "Please try again in a bit."
                });
            }

            let updated, stats;
            if (player && player.roster) {
                updated = player.updated;
                stats = player.roster;
            }

            // Get rid of the ship listings
            const delArr = [];
            for (const c in stats) {
                if (Bot.ships.find(s => s.uniqueName === stats[c].defId)) {
                    delArr.push(c);
                }
            }
            for (const ix of delArr.reverse()) {
                stats.splice(ix, 1);
            }

            stats.forEach(c => {
                if (c.stats && c.stats.final && !c.stats.final[statToCheck]) {
                    c.stats.final[statToCheck] = 0;
                }
                if (c.stats && c.stats.mods && !c.stats.mods[statToCheck]) {
                    c.stats.mods[statToCheck] = 0;
                }
            });

            let sorted;
            if (showTotal) {  // If looking for the total stats
                sorted = stats.sort((p, c) => {
                    if (p.stats && c.stats && p.stats.final[statToCheck] && c.stats.final[statToCheck]) {
                        return c.stats.final[statToCheck] - p.stats.final[statToCheck];
                    } else if (!c.stats || !c.stats.final[statToCheck]) {
                        return -1;
                    } else {
                        return 1;
                    }
                });
            } else {  // Or if looking for just the amount added by mods
                sorted = stats.sort((p, c) => {
                    if (p.stats && c.stats && p.stats.mods  && c.stats.mods && p.stats.mods[statToCheck] && c.stats.mods[statToCheck]) {
                        return c.stats.mods[statToCheck] - p.stats.mods[statToCheck];
                    } else if (!c.stats || !c.stats.mods[statToCheck]) {
                        return -1;
                    } else {
                        return 1;
                    }
                });
            }

            for (const c in sorted) {
                sorted[c] = await Bot.swgohAPI.langChar(sorted[c], interaction.guildSettings.swgohLanguage);
            }

            const out = sorted.map(c => {
                const finalStat = c.stats && c.stats.final[statToCheck] ? (!c.stats.final[statToCheck] % 1 === 0 ? c.stats.final[statToCheck] : (c.stats.final[statToCheck] * 100).toFixed(2)+"%") : 0;
                const modStat = c.stats && c.stats.final[statToCheck] ? (!c.stats.final[statToCheck] % 1 === 0 ? `(${parseInt(c.stats.mods[statToCheck], 10)})` : `(${(c.stats.mods[statToCheck] * 100).toFixed(2)}%)`) : "";
                return {
                    stat: `${finalStat}${modStat.length ? " " + modStat : ""}`,
                    name: `: ${c.nameKey}`
                };
            });
            const longest = out.reduce((max, s) => Math.max(max, s.stat.length), 0);
            let outStr = "";
            for (let ix = 0; ix < 20; ix++) {
                outStr += "`" + out[ix].stat + ` ${Bot.constants.zws}`.repeat(longest-out[ix].stat.length) + "`**" + out[ix].name + "**\n";
            }
            const author = {};
            if (showTotal) {
                // ${playerName}'s Highest ${stat} Characters
                author.name = interaction.language.get("COMMAND_MYMODS_HEADER_TOTAL", player.name, statToCheck);
            } else {
                // ${playerName}'s Best ${stat} From Mods
                author.name = interaction.language.get("COMMAND_MYMODS_HEADER_MODS", player.name, statToCheck);
            }

            const fields = [];
            if (stats.warnings) {
                fields.push({
                    name: "Warnings",
                    value: stats.warnings.join("\n")
                });
            }

            return interaction.editReply({content: null, embeds: [{
                author: author,
                description: "==============================\n" + outStr + "==============================",
                fields: fields,
                footer: {
                    text: updated ? interaction.language.get("BASE_SWGOH_LAST_UPDATED", Bot.duration(updated, interaction)) : ""
                }
            }]});
        } else if (subCommand === "bestmods") {
            // Check for best individual mods of a stat
            const statToCheck = interaction.options.getString("stat");
            const statIndex = statEnums.stats.indexOf(statToCheck);

            let player;
            try {
                player = await Bot.swgohAPI.unitStats(allycode, cooldown);
                if (Array.isArray(player)) player = player[0];
            } catch (e) {
                return super.error(interaction, Bot.codeBlock(e.message), {
                    title: interaction.language.get("BASE_SOMETHING_BROKE"),
                    footer: "Please try again in a bit."
                });
            }
            if (!player?.roster) {
                // If there's no characters, then there's nothing to show...
                return super.error(interaction, "Unable to retrieve roster.", {
                    title: interaction.language.get("BASE_SOMETHING_BROKE"),
                    footer: "Please try again in a bit."
                });
            }

            // Go through the player's roster and get a list of each stat per mod
            const statMap = [];
            for (const character of player.roster) {
                // For each character
                for (const mod of character.mods) {
                    // For each mod on the character
                    for (const stat of mod.secondaryStat) {
                        // For each stat on the mod
                        if (stat.unitStat === statIndex) {
                            // If it's the stat the user wants, go ahead and grab it with the characters defId
                            statMap.push({
                                slot:  mod.slot,
                                value: stat.value,
                                defId: character.defId
                            });
                        }
                    }
                }
            }

            // Sort mods, chop off the top x, then grab matching names for the character they're on
            const sortedMods = statMap.sort((a, b) => b.value - a.value);
            const topSorted = sortedMods.slice(0, 20);
            const namedSorted = topSorted.map(mod => {
                const charName = Bot.characters.find(char => char.uniqueName === mod.defId);
                mod.name = charName?.name;
                return mod;
            });

            // Grab the longest any mod value will be
            const maxLen = Math.max(...statMap.map(stat => stat.value.toLocaleString().length));

            // Format everything into the needed string
            let outStr = "";
            for (const mod of namedSorted) {
                const shapeIconString = `${modSlots[mod.slot-1]}Mod`;
                const value = mod.value % 1 === 0 ? mod.value : mod.value.toFixed(2) + "%";
                outStr += `**\`${value.toLocaleString().padEnd(maxLen)}\`** ${Bot.emotes[shapeIconString]} | ${mod.name}\n`;
            }

            const author = {
                name: `${player.name}'s top ${statToCheck} values`
            };

            // Send it on back to the user
            return interaction.editReply({content: null, embeds: [{
                author: author,
                description: "==============================\n" + outStr + "==============================",
                footer: {
                    text: player.updated ? interaction.language.get("BASE_SWGOH_LAST_UPDATED", Bot.duration(player.updated, interaction)) : ""
                }
            }]});

        }
    }
}

module.exports = MyMods;
<|endoftext|>


<commit_before>goog.require('goog.array');
goog.require('goog.testing.jsunit');
goog.require('ol.Array');
goog.require('ol.ArrayEventType');

function testEmpty() {
  var a = new ol.Array();
  assertEquals(0, a.getLength());
  assertTrue(goog.array.equals(a.getArray(), []));
  assertUndefined(a.getAt(0));
}

function testConstruct() {
  var array = [0, 1, 2];
  var a = new ol.Array(array);
  assertEquals(0, a.getAt(0));
  assertEquals(1, a.getAt(1));
  assertEquals(2, a.getAt(2));
}

function testPush() {
  var a = new ol.Array();
  a.push(1);
  assertEquals(1, a.getLength());
  assertTrue(goog.array.equals(a.getArray(), [1]));
  assertEquals(1, a.getAt(0));
}

function testPushPop() {
  var a = new ol.Array();
  a.push(1);
  a.pop();
  assertEquals(0, a.getLength());
  assertTrue(goog.array.equals(a.getArray(), []));
  assertUndefined(a.getAt(0));
}

function testInsertAt() {
  var a = new ol.Array([0, 2]);
  a.insertAt(1, 1);
  assertEquals(0, a.getAt(0));
  assertEquals(1, a.getAt(1));
  assertEquals(2, a.getAt(2));
}

function testSetAt() {
  var a = new ol.Array();
  a.setAt(1, 1);
  assertEquals(2, a.getLength());
  assertUndefined(a.getAt(0));
  assertEquals(1, a.getAt(1));
}

function testRemoveAt() {
  var a = new ol.Array([0, 1, 2]);
  a.removeAt(1);
  assertEquals(0, a.getAt(0));
  assertEquals(2, a.getAt(1));
}

function testForEachEmpty() {
  var a = new ol.Array();
  var forEachCalled = false;
  a.forEach(function() {
    forEachCalled = true;
  });
  assertFalse(forEachCalled);
}

function testForEachPopulated() {
  var a = new ol.Array();
  a.push(1);
  a.push(2);
  var forEachCount = 0;
  a.forEach(function() {
    ++forEachCount;
  });
  assertEquals(2, forEachCount);
}

function testSetAtEvent() {
  var a = new ol.Array(['a', 'b']);
  var index, prev;
  goog.events.listen(a, ol.ArrayEventType.SET_AT, function(e) {
    index = e.index;
    prev = e.prev;
  });
  a.setAt(1, 1);
  assertEquals(1, index);
  assertEquals('b', prev);
}

function testRemoveAtEvent() {
  var a = new ol.Array(['a']);
  var index, prev;
  goog.events.listen(a, ol.ArrayEventType.REMOVE_AT, function(e) {
    index = e.index;
    prev = e.prev;
  });
  a.pop();
  assertEquals(0, index);
  assertEquals('a', prev);
}

function testInsertAtEvent() {
  var a = new ol.Array([0, 2]);
  var index;
  goog.events.listen(a, ol.ArrayEventType.INSERT_AT, function(e) {
    index = e.index;
  });
  a.insertAt(1, 1);
  assertEquals(1, index);
}

function testSetAtBeyondEnd() {
  var a = new ol.Array();
  var inserts = [];
  a.insert_at = function(index) {
    inserts.push(index);
  };
  a.setAt(2, 0);
  assertEquals(3, a.getLength());
  assertUndefined(a.getAt(0));
  assertUndefined(a.getAt(1));
  assertEquals(0, a.getAt(2));
  assertEquals(3, inserts.length);
  assertEquals(0, inserts[0]);
  assertEquals(1, inserts[1]);
  assertEquals(2, inserts[2]);
}

function testCreateFromArray() {
  var a = [0, 1, 2];
  var array = ol.Array.create(a);
  assertTrue(array instanceof ol.Array);
  assertEquals(3, array.getLength());
  assertEquals(0, array.getAt(0));
  assertEquals(1, array.getAt(1));
  assertEquals(2, array.getAt(2));
}

function testCreateFromArray() {
  var array1 = new ol.Array();
  var array2 = ol.Array.create(array1);
  assertTrue(array1 === array2);

}
<commit_msg>Add more ol.Array tests<commit_after>goog.require('goog.array');
goog.require('goog.testing.jsunit');
goog.require('ol.Array');
goog.require('ol.ArrayEventType');

function testEmpty() {
  var a = new ol.Array();
  assertEquals(0, a.getLength());
  assertTrue(goog.array.equals(a.getArray(), []));
  assertUndefined(a.getAt(0));
}

function testConstruct() {
  var array = [0, 1, 2];
  var a = new ol.Array(array);
  assertEquals(0, a.getAt(0));
  assertEquals(1, a.getAt(1));
  assertEquals(2, a.getAt(2));
}

function testPush() {
  var a = new ol.Array();
  a.push(1);
  assertEquals(1, a.getLength());
  assertTrue(goog.array.equals(a.getArray(), [1]));
  assertEquals(1, a.getAt(0));
}

function testPushPop() {
  var a = new ol.Array();
  a.push(1);
  a.pop();
  assertEquals(0, a.getLength());
  assertTrue(goog.array.equals(a.getArray(), []));
  assertUndefined(a.getAt(0));
}

function testInsertAt() {
  var a = new ol.Array([0, 2]);
  a.insertAt(1, 1);
  assertEquals(0, a.getAt(0));
  assertEquals(1, a.getAt(1));
  assertEquals(2, a.getAt(2));
}

function testSetAt() {
  var a = new ol.Array();
  a.setAt(1, 1);
  assertEquals(2, a.getLength());
  assertUndefined(a.getAt(0));
  assertEquals(1, a.getAt(1));
}

function testRemoveAt() {
  var a = new ol.Array([0, 1, 2]);
  a.removeAt(1);
  assertEquals(0, a.getAt(0));
  assertEquals(2, a.getAt(1));
}

function testForEachEmpty() {
  var a = new ol.Array();
  var forEachCalled = false;
  a.forEach(function() {
    forEachCalled = true;
  });
  assertFalse(forEachCalled);
}

function testForEachPopulated() {
  var a = new ol.Array();
  a.push(1);
  a.push(2);
  var forEachCount = 0;
  a.forEach(function() {
    ++forEachCount;
  });
  assertEquals(2, forEachCount);
}

function testSetAtEvent() {
  var a = new ol.Array(['a', 'b']);
  var index, prev;
  goog.events.listen(a, ol.ArrayEventType.SET_AT, function(e) {
    index = e.index;
    prev = e.prev;
  });
  a.setAt(1, 1);
  assertEquals(1, index);
  assertEquals('b', prev);
}

function testRemoveAtEvent() {
  var a = new ol.Array(['a']);
  var index, prev;
  goog.events.listen(a, ol.ArrayEventType.REMOVE_AT, function(e) {
    index = e.index;
    prev = e.prev;
  });
  a.pop();
  assertEquals(0, index);
  assertEquals('a', prev);
}

function testInsertAtEvent() {
  var a = new ol.Array([0, 2]);
  var index;
  goog.events.listen(a, ol.ArrayEventType.INSERT_AT, function(e) {
    index = e.index;
  });
  a.insertAt(1, 1);
  assertEquals(1, index);
}

function testSetAtBeyondEnd() {
  var a = new ol.Array();
  var inserts = [];
  a.insert_at = function(index) {
    inserts.push(index);
  };
  a.setAt(2, 0);
  assertEquals(3, a.getLength());
  assertUndefined(a.getAt(0));
  assertUndefined(a.getAt(1));
  assertEquals(0, a.getAt(2));
  assertEquals(3, inserts.length);
  assertEquals(0, inserts[0]);
  assertEquals(1, inserts[1]);
  assertEquals(2, inserts[2]);
}

function testCreateFromArray() {
  var a = [0, 1, 2];
  var array = ol.Array.create(a);
  assertTrue(array instanceof ol.Array);
  assertEquals(3, array.getLength());
  assertEquals(0, array.getAt(0));
  assertEquals(1, array.getAt(1));
  assertEquals(2, array.getAt(2));
}

function testCreateFromArray() {
  var array1 = new ol.Array();
  var array2 = ol.Array.create(array1);
  assertTrue(array1 === array2);
}

function testLengthChangeInsertAt() {
  var array = ol.Array.create([0, 1, 2]);
  var lengthChangedCalled;
  array.length_changed = function() {
    lengthChangedCalled = true;
  };
  array.insertAt(2, 3);
  assertTrue(lengthChangedCalled);
}

function testLengthChangeRemoveAt() {
  var array = ol.Array.create([0, 1, 2]);
  var lengthChangedCalled;
  array.length_changed = function() {
    lengthChangedCalled = true;
  };
  array.removeAt(0);
  assertTrue(lengthChangedCalled);
}

function testLengthChangeSetAt() {
  var array = ol.Array.create([0, 1, 2]);
  var lengthChangedCalled;
  array.length_changed = function() {
    lengthChangedCalled = true;
  };
  array.setAt(1, 1);
  assertUndefined(lengthChangedCalled);
}
<|endoftext|>


<commit_before>var debug = require('debug')('orchestra:api:universe')
	, Discover = require('harmonyhubjs-discover')
	, Client = require('harmonyhubjs-client')
	, EventEmitter = require('events').EventEmitter
	, q = require('q')
	, util = require('util');

var Universe = function() {
	var self = this;

	self._discover = new Discover(61991);
	self._discoveredHubs = [];
	self._clients = {};

	self._discover.on('update', function(hubs) {
		debug('received update event from harmonyhubjs-discover. there are ' + hubs.length + ' hubs');
		self._discoveredHubs = hubs;
		self.emit('discoveredHubs', hubs);
	});
	self._discover.start();

	EventEmitter.call(self);
};
util.inherits(Universe, EventEmitter);

function createClientForHub(hub) {
	var self = this;

	return Client(hub.ip)
		.then(function(client) {
			debug('created new client for hub with uuid ' + hub.uuid);

			client._xmppClient.on('offline', function() {
				debug('client for hub ' + hub.uuid + ' went offline. re-establish.');
				return createClientForHub.call(self, hub);
			});

			client.on('stateDigest', function(stateDigest) {
				debug('got state digest. reemit it');
				self.emit('stateDigest', {
					hub: hub
					, stateDigest: stateDigest
				});
			});

			self._clients[hub.uuid] = client;
			return client;
		});
}

Universe.prototype.getDiscoveredHubs = function getDiscoveredHubs() {
	debug('return list of ' + this._discoveredHubs.length + ' discovered hubs');
	return this._discoveredHubs;
};

Universe.prototype.getClientForHubWithUuid = function getClientForHubWithUuid(uuid) {
	debug('getClientForHubWithUuid(' + uuid + ')');

	var hub = this.getDiscoveredHubs()
		.filter(function(hub) { return (hub.uuid === uuid); });

	if(hub.length > 0) {
		return this.getClientForHub(hub[0]);
	} else {
		return q.reject(new Error('no hub with uuid ' + uuid + ' discovered yet'));
	}
};

Universe.prototype.getClientForHub = function getClientForHub(hub) {
	debug('getClientForHub(' + hub.uuid + ')');

	if(!this._clients[hub.uuid]) {
		debug('request new client for hub with uuid ' + hub.uuid);
		return createClientForHub.call(this, hub);
	} else {
		debug('return existing client for hub with uuid ' + hub.uuid);
		return q.when(this._clients[hub.uuid]);
	}
};

module.exports = Universe;
<commit_msg>proper cleanup of offline harmonyhubjs-client<commit_after>var debug = require('debug')('orchestra:api:universe')
	, Discover = require('harmonyhubjs-discover')
	, Client = require('harmonyhubjs-client')
	, EventEmitter = require('events').EventEmitter
	, q = require('q')
	, util = require('util');

var Universe = function() {
	var self = this;

	self._discover = new Discover(61991);
	self._discoveredHubs = [];
	self._clients = {};

	self._discover.on('update', function(hubs) {
		debug('received update event from harmonyhubjs-discover. there are ' + hubs.length + ' hubs');
		self._discoveredHubs = hubs;
		self.emit('discoveredHubs', hubs);
	});
	self._discover.start();

	EventEmitter.call(self);
};
util.inherits(Universe, EventEmitter);

function createClientForHub(hub) {
	var self = this;

	return Client(hub.ip)
		.then(function(client) {
			debug('created new client for hub with uuid ' + hub.uuid);

			client._xmppClient.on('offline', function() {
				debug('client for hub ' + hub.uuid + ' went offline. re-establish.');
				self._clients[hub.uuid] = undefined;
				return createClientForHub.call(self, hub);
			});

			client.on('stateDigest', function(stateDigest) {
				debug('got state digest. reemit it');
				self.emit('stateDigest', {
					hub: hub
					, stateDigest: stateDigest
				});
			});

			self._clients[hub.uuid] = client;
			return client;
		});
}

Universe.prototype.getDiscoveredHubs = function getDiscoveredHubs() {
	debug('return list of ' + this._discoveredHubs.length + ' discovered hubs');
	return this._discoveredHubs;
};

Universe.prototype.getClientForHubWithUuid = function getClientForHubWithUuid(uuid) {
	debug('getClientForHubWithUuid(' + uuid + ')');

	var hub = this.getDiscoveredHubs()
		.filter(function(hub) { return (hub.uuid === uuid); });

	if(hub.length > 0) {
		return this.getClientForHub(hub[0]);
	} else {
		return q.reject(new Error('no hub with uuid ' + uuid + ' discovered yet'));
	}
};

Universe.prototype.getClientForHub = function getClientForHub(hub) {
	debug('getClientForHub(' + hub.uuid + ')');

	if(!this._clients[hub.uuid]) {
		debug('request new client for hub with uuid ' + hub.uuid);
		return createClientForHub.call(this, hub);
	} else {
		debug('return existing client for hub with uuid ' + hub.uuid);
		return q.when(this._clients[hub.uuid]);
	}
};

module.exports = Universe;
<|endoftext|>


<commit_before>(function() {

  var arcToSegmentsCache = { },
      segmentToBezierCache = { },
      _join = Array.prototype.join;

  /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp
   * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here
   * http://mozilla.org/MPL/2.0/
   */
  function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) {
    var argsString = _join.call(arguments);
    if (arcToSegmentsCache[argsString]) {
      return arcToSegmentsCache[argsString];
    }

    var PI = Math.PI, th = rotateX * PI / 180,
        sinTh = Math.sin(th),
        cosTh = Math.cos(th),
        fromX = 0, fromY = 0;

    rx = Math.abs(rx);
    ry = Math.abs(ry);

    var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5,
        py = -cosTh * toY * 0.5 + sinTh * toX * 0.5,
        rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px,
        pl = rx2 * ry2 - rx2 * py2 - ry2 * px2,
        root = 0;

    if (pl < 0) {
      var s = Math.sqrt(1 - pl/(rx2 * ry2));
      rx *= s;
      ry *= s;
    }
    else {
      root = (large === sweep ? -1.0 : 1.0) *
              Math.sqrt( pl /(rx2 * py2 + ry2 * px2));
    }

    var cx = root * rx * py / ry,
        cy = -root * ry * px / rx,
        cx1 = cosTh * cx - sinTh * cy + toX * 0.5,
        cy1 = sinTh * cx + cosTh * cy + toY * 0.5,
        mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry),
        dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry);

    if (sweep === 0 && dtheta > 0) {
      dtheta -= 2 * PI;
    }
    else if (sweep === 1 && dtheta < 0) {
      dtheta += 2 * PI;
    }

    // Convert into cubic bezier segments <= 90deg
    var segments = Math.ceil(Math.abs(dtheta / PI * 2)),
        result = [], mDelta = dtheta / segments,
        mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2),
        th3 = mTheta + mDelta;

    for (var i = 0; i < segments; i++) {
      result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY);
      fromX = result[i][4];
      fromY = result[i][5];
      mTheta = th3;
      th3 += mDelta;
    }
    arcToSegmentsCache[argsString] = result;
    return result;
  }

  function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) {
    var argsString2 = _join.call(arguments);
    if (segmentToBezierCache[argsString2]) {
      return segmentToBezierCache[argsString2];
    }

    var costh2 = Math.cos(th2),
        sinth2 = Math.sin(th2),
        costh3 = Math.cos(th3),
        sinth3 = Math.sin(th3),
        toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1,
        toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1,
        cp1X = fromX + mT * ( - cosTh * rx * sinth2 - sinTh * ry * costh2),
        cp1Y = fromY + mT * ( - sinTh * rx * sinth2 + cosTh * ry * costh2),
        cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3),
        cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3);

    segmentToBezierCache[argsString2] = [
      cp1X, cp1Y,
      cp2X, cp2Y,
      toX, toY
    ];
    return segmentToBezierCache[argsString2];
  }

  /*
  * Private
  */
  function calcVectorAngle(ux, uy, vx, vy) {
    var ta = Math.atan2(uy, ux),
        tb = Math.atan2(vy, vx);
    if (tb >= ta) {
      return tb - ta;
    }
    else {
      return 2 * Math.PI - (ta - tb);
    }
  }

  /**
   * Draws arc
   * @param {CanvasRenderingContext2D} ctx
   * @param {Number} fx
   * @param {Number} fy
   * @param {Array} coords
   */
  fabric.util.drawArc = function(ctx, fx, fy, coords) {
    var rx = coords[0],
        ry = coords[1],
        rot = coords[2],
        large = coords[3],
        sweep = coords[4],
        tx = coords[5],
        ty = coords[6],
        segs = [[ ], [ ], [ ], [ ]],
        segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);

    for (var i = 0, len = segsNorm.length; i < len; i++) {
      segs[i][0] = segsNorm[i][0] + fx;
      segs[i][1] = segsNorm[i][1] + fy;
      segs[i][2] = segsNorm[i][2] + fx;
      segs[i][3] = segsNorm[i][3] + fy;
      segs[i][4] = segsNorm[i][4] + fx;
      segs[i][5] = segsNorm[i][5] + fy;
      ctx.bezierCurveTo.apply(ctx, segs[i]);
    }
  };

  /**
   * Calculate bounding box of a elliptic-arc
   * arc is represented by f starting point,
   * rx, ry rays of ellipses, rot rotation of axe,
   * large and sweep flags and t end point
   * @param {Number} fx
   * @param {Number} fy
   * @param {Number} rx
   * @param {Number} ry
   * @param {Number} rot
   * @param {Number} large
   * @param {Number} sweep
   * @param {Number} tx
   * @param {Number} ty
   */
  fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) {

    var fromX = 0, fromY = 0, bound = [ ], bounds = [ ],
    segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);

    for (var i = 0, len = segs.length; i < len; i++) {
      bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]);
      bound[0].x += fx;
      bound[0].y += fy;
      bound[1].x += fx;
      bound[1].y += fy;
      bounds.push(bound[0]);
      bounds.push(bound[1]);
      fromX = segs[i][4];
      fromY = segs[i][5];
    }
    return bounds;
  };

  /**
   * Calculate bounding box of a beziercurve
   * curve is represented by p0 starting point,
   * p1,p2 control points, p3 end point
   * @param {Number} x0
   * @param {Number} y0
   * @param {Number} x1
   * @param {Number} y1
   * @param {Number} x2
   * @param {Number} y2
   * @param {Number} x3
   * @param {Number} y3
   */
  // taken from http://jsbin.com/ivomiq/56/edit  no credits available for that.
  function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) {

    var bounds = getInnerPointsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3),
        j = bounds[0].length, min = Math.min, max = Math.max;

    bounds[0][j] = x0;
    bounds[1][j] = y0;
    bounds[0][j + 1] = x3;
    bounds[1][j + 1] = y3;
    /*while (j--) {
      bounds[0][j] += x0;
      bounds[1][j] += y0;
    }*/

    var result = [
      {
        x: min.apply(null, bounds[0]),
        y: min.apply(null, bounds[1])
      },
      {
        x: max.apply(null, bounds[0]),
        y: max.apply(null, bounds[1])
      }
    ];
    return result;
  }

  /*
   * Private
   */
  function getInnerPointsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) {

    var sqrt = Math.sqrt,
        abs = Math.abs, tvalues = [ ],
        bounds = [[ ], [ ]],
        a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3,
        b = 6 * x0 - 12 * x1 + 6 * x2,
        c = 3 * x1 - 3 * x0, t, t1, t2, b2ac, sqrtb2ac;

    for (var i = 0; i < 2; ++i) {
      if (i > 0) {
        b = 6 * y0 - 12 * y1 + 6 * y2;
        a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
        c = 3 * y1 - 3 * y0;
      }

      if (abs(a) < 1e-12) {
        if (abs(b) < 1e-12) {
          continue;
        }
        t = -c / b;
        if (0 < t && t < 1) {
          tvalues.push(t);
        }
        continue;
      }
      b2ac = b * b - 4 * c * a;
      if (b2ac < 0) {
        continue;
      }
      sqrtb2ac = sqrt(b2ac);
      t1 = (-b + sqrtb2ac) / (2 * a);
      if (0 < t1 && t1 < 1) {
        tvalues.push(t1);
      }
      t2 = (-b - sqrtb2ac) / (2 * a);
      if (0 < t2 && t2 < 1) {
        tvalues.push(t2);
      }
    }
    var j = tvalues.length, mt;
    while (j--) {
      t = tvalues[j];
      mt = 1 - t;
      bounds[0][j] = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
      bounds[1][j] = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
    }
    return bounds;
  }

  fabric.util.getBoundsOfCurve = getBoundsOfCurve;

})();
<commit_msg>Update arc.js<commit_after>(function() {

  var arcToSegmentsCache = { },
      segmentToBezierCache = { },
      boundsOfCurveCache = { }
      _join = Array.prototype.join;

  /* Adapted from http://dxr.mozilla.org/mozilla-central/source/content/svg/content/src/nsSVGPathDataParser.cpp
   * by Andrea Bogazzi code is under MPL. if you don't have a copy of the license you can take it here
   * http://mozilla.org/MPL/2.0/
   */
  function arcToSegments(toX, toY, rx, ry, large, sweep, rotateX) {
    var argsString = _join.call(arguments);
    if (arcToSegmentsCache[argsString]) {
      return arcToSegmentsCache[argsString];
    }

    var PI = Math.PI, th = rotateX * PI / 180,
        sinTh = Math.sin(th),
        cosTh = Math.cos(th),
        fromX = 0, fromY = 0;

    rx = Math.abs(rx);
    ry = Math.abs(ry);

    var px = -cosTh * toX * 0.5 - sinTh * toY * 0.5,
        py = -cosTh * toY * 0.5 + sinTh * toX * 0.5,
        rx2 = rx * rx, ry2 = ry * ry, py2 = py * py, px2 = px * px,
        pl = rx2 * ry2 - rx2 * py2 - ry2 * px2,
        root = 0;

    if (pl < 0) {
      var s = Math.sqrt(1 - pl/(rx2 * ry2));
      rx *= s;
      ry *= s;
    }
    else {
      root = (large === sweep ? -1.0 : 1.0) *
              Math.sqrt( pl /(rx2 * py2 + ry2 * px2));
    }

    var cx = root * rx * py / ry,
        cy = -root * ry * px / rx,
        cx1 = cosTh * cx - sinTh * cy + toX * 0.5,
        cy1 = sinTh * cx + cosTh * cy + toY * 0.5,
        mTheta = calcVectorAngle(1, 0, (px - cx) / rx, (py - cy) / ry),
        dtheta = calcVectorAngle((px - cx) / rx, (py - cy) / ry, (-px - cx) / rx, (-py - cy) / ry);

    if (sweep === 0 && dtheta > 0) {
      dtheta -= 2 * PI;
    }
    else if (sweep === 1 && dtheta < 0) {
      dtheta += 2 * PI;
    }

    // Convert into cubic bezier segments <= 90deg
    var segments = Math.ceil(Math.abs(dtheta / PI * 2)),
        result = [], mDelta = dtheta / segments,
        mT = 8 / 3 * Math.sin(mDelta / 4) * Math.sin(mDelta / 4) / Math.sin(mDelta / 2),
        th3 = mTheta + mDelta;

    for (var i = 0; i < segments; i++) {
      result[i] = segmentToBezier(mTheta, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY);
      fromX = result[i][4];
      fromY = result[i][5];
      mTheta = th3;
      th3 += mDelta;
    }
    arcToSegmentsCache[argsString] = result;
    return result;
  }

  function segmentToBezier(th2, th3, cosTh, sinTh, rx, ry, cx1, cy1, mT, fromX, fromY) {
    var argsString2 = _join.call(arguments);
    if (segmentToBezierCache[argsString2]) {
      return segmentToBezierCache[argsString2];
    }

    var costh2 = Math.cos(th2),
        sinth2 = Math.sin(th2),
        costh3 = Math.cos(th3),
        sinth3 = Math.sin(th3),
        toX = cosTh * rx * costh3 - sinTh * ry * sinth3 + cx1,
        toY = sinTh * rx * costh3 + cosTh * ry * sinth3 + cy1,
        cp1X = fromX + mT * ( - cosTh * rx * sinth2 - sinTh * ry * costh2),
        cp1Y = fromY + mT * ( - sinTh * rx * sinth2 + cosTh * ry * costh2),
        cp2X = toX + mT * ( cosTh * rx * sinth3 + sinTh * ry * costh3),
        cp2Y = toY + mT * ( sinTh * rx * sinth3 - cosTh * ry * costh3);

    segmentToBezierCache[argsString2] = [
      cp1X, cp1Y,
      cp2X, cp2Y,
      toX, toY
    ];
    return segmentToBezierCache[argsString2];
  }

  /*
  * Private
  */
  function calcVectorAngle(ux, uy, vx, vy) {
    var ta = Math.atan2(uy, ux),
        tb = Math.atan2(vy, vx);
    if (tb >= ta) {
      return tb - ta;
    }
    else {
      return 2 * Math.PI - (ta - tb);
    }
  }

  /**
   * Draws arc
   * @param {CanvasRenderingContext2D} ctx
   * @param {Number} fx
   * @param {Number} fy
   * @param {Array} coords
   */
  fabric.util.drawArc = function(ctx, fx, fy, coords) {
    var rx = coords[0],
        ry = coords[1],
        rot = coords[2],
        large = coords[3],
        sweep = coords[4],
        tx = coords[5],
        ty = coords[6],
        segs = [[ ], [ ], [ ], [ ]],
        segsNorm = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);

    for (var i = 0, len = segsNorm.length; i < len; i++) {
      segs[i][0] = segsNorm[i][0] + fx;
      segs[i][1] = segsNorm[i][1] + fy;
      segs[i][2] = segsNorm[i][2] + fx;
      segs[i][3] = segsNorm[i][3] + fy;
      segs[i][4] = segsNorm[i][4] + fx;
      segs[i][5] = segsNorm[i][5] + fy;
      ctx.bezierCurveTo.apply(ctx, segs[i]);
    }
  };

  /**
   * Calculate bounding box of a elliptic-arc
   * @param {Number} fx start point of arc
   * @param {Number} fy
   * @param {Number} rx horizontal radius
   * @param {Number} ry vertical radius
   * @param {Number} rot angle of horizontal axe
   * @param {Number} large 1 or 0, whatever the arc is the big or the small on the 2 points
   * @param {Number} sweep 1 or 0, 1 clockwise or counterclockwise direction
   * @param {Number} tx end point of arc
   * @param {Number} ty
   */
  fabric.util.getBoundsOfArc = function(fx, fy, rx, ry, rot, large, sweep, tx, ty) {

    var fromX = 0, fromY = 0, bound = [ ], bounds = [ ],
    segs = arcToSegments(tx - fx, ty - fy, rx, ry, large, sweep, rot);

    for (var i = 0, len = segs.length; i < len; i++) {
      bound = getBoundsOfCurve(fromX, fromY, segs[i][0], segs[i][1], segs[i][2], segs[i][3], segs[i][4], segs[i][5]);
      bound[0].x += fx;
      bound[0].y += fy;
      bound[1].x += fx;
      bound[1].y += fy;
      bounds.push(bound[0]);
      bounds.push(bound[1]);
      fromX = segs[i][4];
      fromY = segs[i][5];
    }
    return bounds;
  };

  /**
   * Calculate bounding box of a beziercurve
   * @param {Number} x0 starting point
   * @param {Number} y0
   * @param {Number} x1 first control point
   * @param {Number} y1
   * @param {Number} x2 secondo control point
   * @param {Number} y2
   * @param {Number} x3 end of beizer
   * @param {Number} y3
   */
  // taken from http://jsbin.com/ivomiq/56/edit  no credits available for that.
  function getBoundsOfCurve(x0, y0, x1, y1, x2, y2, x3, y3) {
    var argsString = _join.call(arguments);
    if (boundsOfCurveCache[argsString]) {
      return boundsOfCurveCache[argsString];
    }

    var pow = Math.pow, sqrt = Math.sqrt,
        min = Math.min, max = Math.max,
        abs = Math.abs, tvalues = [ ],
        bounds = [[ ], [ ]], points = [ ],
        a, b, c, t, t1, t2, b2ac, sqrtb2ac;

    b = 6 * x0 - 12 * x1 + 6 * x2;
    a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
    c = 3 * x1 - 3 * x0;

    for (var i = 0; i < 2; ++i) {
      if (i > 0) {
        b = 6 * y0 - 12 * y1 + 6 * y2;
        a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
        c = 3 * y1 - 3 * y0;
      }

      if (abs(a) < 1e-12) {
        if (abs(b) < 1e-12) {
          continue;
        }
        t = -c / b;
        if (0 < t && t < 1) {
          tvalues.push(t);
        }
        continue;
      }
      b2ac = b * b - 4 * c * a;
      if (b2ac < 0) {
        continue;
      }
      sqrtb2ac = sqrt(b2ac);
      t1 = (-b + sqrtb2ac) / (2 * a);
      if (0 < t1 && t1 < 1) {
        tvalues.push(t1);
      }
      t2 = (-b - sqrtb2ac) / (2 * a);
      if (0 < t2 && t2 < 1) {
        tvalues.push(t2);
      }
    }

    var x, y, j = tvalues.length, jlen = j, mt;
    while(j--) {
      t = tvalues[j];
      mt = 1 - t;
      x = (mt * mt * mt * x0) + (3 * mt * mt * t * x1) + (3 * mt * t * t * x2) + (t * t * t * x3);
      bounds[0][j] = x;

      y = (mt * mt * mt * y0) + (3 * mt * mt * t * y1) + (3 * mt * t * t * y2) + (t * t * t * y3);
      bounds[1][j] = y;
    }

    bounds[0][jlen] = x0;
    bounds[1][jlen] = y0;
    bounds[0][jlen + 1] = x3;
    bounds[1][jlen + 1] = y3;
    var result = [
      {
        x: min.apply(null, bounds[0]),
        y: min.apply(null, bounds[1])
      },
      {
        x: max.apply(null, bounds[0]),
        y: max.apply(null, bounds[1])
      }
    ];
    boundsOfCurveCache[argsString] = result;
    return result;
  };

  fabric.util.getBoundsOfCurve = getBoundsOfCurve;

})();
<|endoftext|>


<commit_before>var config = require('../config')

/**
 * Check if a node is in the document.
 * Note: document.documentElement.contains should work here
 * but always returns false for comment nodes in phantomjs,
 * making unit tests difficult. This is fixed byy doing the
 * contains() check on the node's parentNode instead of
 * the node itself.
 *
 * @param {Node} node
 * @return {Boolean}
 */

exports.inDoc = function (node) {
  var doc = document.documentElement
  var parent = node && node.parentNode
  return doc === node ||
    doc === parent ||
    !!(parent && parent.nodeType === 1 && (doc.contains(parent)))
}

/**
 * Extract an attribute from a node.
 *
 * @param {Node} node
 * @param {String} attr
 */

exports.attr = function (node, attr) {
  attr = config.prefix + attr
  var val = node.getAttribute(attr)
  if (val !== null) {
    node.removeAttribute(attr)
  }
  return val
}

/**
 * Insert el before target
 *
 * @param {Element} el
 * @param {Element} target
 */

exports.before = function (el, target) {
  target.parentNode.insertBefore(el, target)
}

/**
 * Insert el after target
 *
 * @param {Element} el
 * @param {Element} target
 */

exports.after = function (el, target) {
  if (target.nextSibling) {
    exports.before(el, target.nextSibling)
  } else {
    target.parentNode.appendChild(el)
  }
}

/**
 * Remove el from DOM
 *
 * @param {Element} el
 */

exports.remove = function (el) {
  el.parentNode.removeChild(el)
}

/**
 * Prepend el to target
 *
 * @param {Element} el
 * @param {Element} target
 */

exports.prepend = function (el, target) {
  if (target.firstChild) {
    exports.before(el, target.firstChild)
  } else {
    target.appendChild(el)
  }
}

/**
 * Replace target with el
 *
 * @param {Element} target
 * @param {Element} el
 */

exports.replace = function (target, el) {
  var parent = target.parentNode
  if (parent) {
    parent.replaceChild(el, target)
  }
}

/**
 * Add event listener shorthand.
 *
 * @param {Element} el
 * @param {String} event
 * @param {Function} cb
 */

exports.on = function (el, event, cb) {
  el.addEventListener(event, cb)
}

/**
 * Remove event listener shorthand.
 *
 * @param {Element} el
 * @param {String} event
 * @param {Function} cb
 */

exports.off = function (el, event, cb) {
  el.removeEventListener(event, cb)
}

/**
 * Add class with compatibility for IE & SVG
 *
 * @param {Element} el
 * @param {Strong} cls
 */

exports.addClass = function (el, cls) {
  if (el.classList) {
    el.classList.add(cls)
  } else {
    var cur = ' ' + (el.getAttribute('class') || '') + ' '
    if (cur.indexOf(' ' + cls + ' ') < 0) {
      el.setAttribute('class', (cur + cls).trim())
    }
  }
}

/**
 * Remove class with compatibility for IE & SVG
 *
 * @param {Element} el
 * @param {Strong} cls
 */

exports.removeClass = function (el, cls) {
  if (el.classList) {
    el.classList.remove(cls)
  } else {
    var cur = ' ' + (el.getAttribute('class') || '') + ' '
    var tar = ' ' + cls + ' '
    while (cur.indexOf(tar) >= 0) {
      cur = cur.replace(tar, ' ')
    }
    el.setAttribute('class', cur.trim())
  }
}

/**
 * Extract raw content inside an element into a temporary
 * container div
 *
 * @param {Element} el
 * @param {Boolean} asFragment
 * @return {Element}
 */

exports.extractContent = function (el, asFragment) {
  var child
  var rawContent
  /* istanbul ignore if */
  if (
    exports.isTemplate(el) &&
    el.content instanceof DocumentFragment
  ) {
    el = el.content
  }
  if (el.hasChildNodes()) {
    rawContent = asFragment
      ? document.createDocumentFragment()
      : document.createElement('div')
    /* jshint boss:true */
    while (child = el.firstChild) {
      rawContent.appendChild(child)
    }
  }
  return rawContent
}

/**
 * Check if an element is a template tag.
 * Note if the template appears inside an SVG its tagName
 * will be in lowercase.
 *
 * @param {Element} el
 */

exports.isTemplate = function (el) {
  return el.tagName &&
    el.tagName.toLowerCase() === 'template'
}<commit_msg>trim empty textNodes when extracting inline template (fix #949)<commit_after>var config = require('../config')

/**
 * Check if a node is in the document.
 * Note: document.documentElement.contains should work here
 * but always returns false for comment nodes in phantomjs,
 * making unit tests difficult. This is fixed byy doing the
 * contains() check on the node's parentNode instead of
 * the node itself.
 *
 * @param {Node} node
 * @return {Boolean}
 */

exports.inDoc = function (node) {
  var doc = document.documentElement
  var parent = node && node.parentNode
  return doc === node ||
    doc === parent ||
    !!(parent && parent.nodeType === 1 && (doc.contains(parent)))
}

/**
 * Extract an attribute from a node.
 *
 * @param {Node} node
 * @param {String} attr
 */

exports.attr = function (node, attr) {
  attr = config.prefix + attr
  var val = node.getAttribute(attr)
  if (val !== null) {
    node.removeAttribute(attr)
  }
  return val
}

/**
 * Insert el before target
 *
 * @param {Element} el
 * @param {Element} target
 */

exports.before = function (el, target) {
  target.parentNode.insertBefore(el, target)
}

/**
 * Insert el after target
 *
 * @param {Element} el
 * @param {Element} target
 */

exports.after = function (el, target) {
  if (target.nextSibling) {
    exports.before(el, target.nextSibling)
  } else {
    target.parentNode.appendChild(el)
  }
}

/**
 * Remove el from DOM
 *
 * @param {Element} el
 */

exports.remove = function (el) {
  el.parentNode.removeChild(el)
}

/**
 * Prepend el to target
 *
 * @param {Element} el
 * @param {Element} target
 */

exports.prepend = function (el, target) {
  if (target.firstChild) {
    exports.before(el, target.firstChild)
  } else {
    target.appendChild(el)
  }
}

/**
 * Replace target with el
 *
 * @param {Element} target
 * @param {Element} el
 */

exports.replace = function (target, el) {
  var parent = target.parentNode
  if (parent) {
    parent.replaceChild(el, target)
  }
}

/**
 * Add event listener shorthand.
 *
 * @param {Element} el
 * @param {String} event
 * @param {Function} cb
 */

exports.on = function (el, event, cb) {
  el.addEventListener(event, cb)
}

/**
 * Remove event listener shorthand.
 *
 * @param {Element} el
 * @param {String} event
 * @param {Function} cb
 */

exports.off = function (el, event, cb) {
  el.removeEventListener(event, cb)
}

/**
 * Add class with compatibility for IE & SVG
 *
 * @param {Element} el
 * @param {Strong} cls
 */

exports.addClass = function (el, cls) {
  if (el.classList) {
    el.classList.add(cls)
  } else {
    var cur = ' ' + (el.getAttribute('class') || '') + ' '
    if (cur.indexOf(' ' + cls + ' ') < 0) {
      el.setAttribute('class', (cur + cls).trim())
    }
  }
}

/**
 * Remove class with compatibility for IE & SVG
 *
 * @param {Element} el
 * @param {Strong} cls
 */

exports.removeClass = function (el, cls) {
  if (el.classList) {
    el.classList.remove(cls)
  } else {
    var cur = ' ' + (el.getAttribute('class') || '') + ' '
    var tar = ' ' + cls + ' '
    while (cur.indexOf(tar) >= 0) {
      cur = cur.replace(tar, ' ')
    }
    el.setAttribute('class', cur.trim())
  }
}

/**
 * Extract raw content inside an element into a temporary
 * container div
 *
 * @param {Element} el
 * @param {Boolean} asFragment
 * @return {Element}
 */

exports.extractContent = function (el, asFragment) {
  var child
  var rawContent
  /* istanbul ignore if */
  if (
    exports.isTemplate(el) &&
    el.content instanceof DocumentFragment
  ) {
    el = el.content
  }
  if (el.hasChildNodes()) {
    trim(el, el.firstChild)
    trim(el, el.lastChild)
    rawContent = asFragment
      ? document.createDocumentFragment()
      : document.createElement('div')
    /* jshint boss:true */
    while (child = el.firstChild) {
      rawContent.appendChild(child)
    }
  }
  return rawContent
}

function trim (content, node) {
  if (node && node.nodeType === 3 && !node.data.trim()) {
    content.removeChild(node)
  }
}

/**
 * Check if an element is a template tag.
 * Note if the template appears inside an SVG its tagName
 * will be in lowercase.
 *
 * @param {Element} el
 */

exports.isTemplate = function (el) {
  return el.tagName &&
    el.tagName.toLowerCase() === 'template'
}
<|endoftext|>


<commit_before>// Class for creating & handling the dat.gui controls
// as well as creating a common interface for all modes to use

var GUI = (function (scope) {
	//Constructor
	function GUI(){

	}

	GUI.prototype.init = function(mode,callbacks,mode_obj){
		// Colors to use throughout
		this.colors = {
			'projections':'#e59a1c', // For the line/place of intersection
			'slices':'#3090FF' // For the actual shape/data
		}

		// These are all the possible parameters to keep track of
		this.params = {
			// Shape Properties
			'source':'cartesian',
			'equation':'',
			'points':'', // For convex hull
			'param_a':'', // These are the ranges the parameters take
			'param_b':'',
			'param_c':'',
			'param_d':'',
			'param_eq_x':'', // This is the parametric equation
			'param_eq_y':'',
			'param_eq_z':'',
			'param_eq_w':'',
			'render_shape':false,
			'render_slices':false,
			'resolution':60, // For the marching squares/cubes

			// Viewing Controls
			'axis_value':0.1,
			'axis':'Y',
			'samples':200,
			'thickness':'medium'
		};
		this.gui = null;
		this.mode = "";
		this.last_source = this.params.source;

		// 2D defaults
		this.defaults = {}
		// Alternative parametric: (a: [0,2pi],b:[0.5,1])
		//b * (1-cos(a))*sin(a) * 5
		//b * (1-cos(a))*cos(a) * 5
		// Alternative square:
		// (0,5),(5,0),(0,-5),(-5,0)
		this.defaults['2D'] = {
			'equation':'x^2+y^2 = 10', // Circle
			'points':'(5,5),(5,-5),(-5,-5),(-5,5)', // Square
			'param_eq_x':'sin(a) * b * 5', // butterfly wings
			'param_eq_y':'sin(a * 2) * b * 5',
			'param_a':'0 < a < 2 * PI',
			'param_b':'0 < b < 1',
			'axis':'Y'
		}
		// 3D defaults
		this.defaults['3D'] = {
			'equation':'x^2+y^2+z^2 = 10', // Sphere
			'resolution':20,
			'points':'(5,0,5),(5,0,-5),(-5,0,-5),(-5,0,5),(5,5,5),(5,5,-5),(-5,5,-5),(-5,5,5)', // Cube
			'param_eq_x':'(4 + 0.5 * (3 + 5 * cos(b)) ) * sin(a)',
			'param_eq_y':'(4 + 0.5 * (3 + 5 * cos(b)) ) * cos(a)',
			'param_eq_z':'4 + c * sin(b)',
			'param_a':'0 < a < 2 * PI',
			'param_b':'0 < b < PI',
			'param_c':'0 < c < 5'
			//'param_eq_x':'b * cos(a) - c * sin(a)', // Spiral tube
			//'param_eq_y':'b * sin(a) + c * cos(a)',
			//'param_eq_z':'a/3',
			//'param_a':'-4 * PI < a < 4 * PI',
			//'param_b':'0 < b < 1',
			//'param_c':'0 < c < 1'
		}

		// 3D defaults
		this.defaults['4D'] = {
			'equation':'x^2+y^2+z^2+w^2 = 10',
			'points':'',
			'resolution':20,
			'param_eq_x':'',
			'param_eq_y':'',
			'param_eq_z':'',
			'param_eq_w':'',
			'param_a':'0 < a < 1',
			'param_b':'0 < b < 1',
			'param_c':'0 < c < 1',
			'param_d':'0 < d < 1',
			'axis':'W'
		}

		this.mode_obj = null;
		this.callbacks = null;

		// Creates the scene and everything
		this.gui = new dat.GUI();
		this.mode = mode;
		this.mode_obj = mode_obj;
		this.callbacks = callbacks;
		// Create the two folders
		var shapeProperties = this.gui.addFolder('Shape Properties');
	    var viewingControls = this.gui.addFolder('Viewing Controls');
	    this.shapeProperties = shapeProperties; this.viewingControls = viewingControls;
	    var current_scope = this;
	    var params = this.params;

	    // Load defaults
	    for(var key in params){
	    	if(this.defaults[mode][key]){
	    		params[key] = this.defaults[mode][key]
	    	}
	    }

	    shapeProperties.add(params, 'source',['cartesian','parametric','convex-hull']).onChange(function(val){
	    	if(val == 'cartesian' && current_scope.last_source != 'cartesian'){
	    		current_scope.destroyConvexSource();
	    		current_scope.destroyParamSource();
	    		current_scope.initCartesianSource();
	    	}
	    	if(val == 'parametric' && current_scope.last_source != 'parametric'){
	    		current_scope.destroyConvexSource();
	    		current_scope.destroyCartesianSource();
	    		current_scope.initParamSource();
	    	}
	    	if(val == 'convex-hull' && current_scope.last_source != 'convex-hull'){
	    		current_scope.destroyCartesianSource();
	    		current_scope.destroyParamSource();
	    		current_scope.initConvexSource();
	    	}
	    	current_scope.last_source = val

	    	if(callbacks['source']) callbacks['source'](mode_obj,val);
	    });

	    shapeProperties.add(params, 'render_shape').name("Render Shape").listen().onChange(function(val){
	    	if(callbacks['render_shape']) callbacks['render_shape'](mode_obj,val);
	    });

			shapeProperties.add(params, 'render_slices').name("Render Slices").listen().onChange(function(val){
	    	if(callbacks['render_slices']) callbacks['render_slices'](mode_obj,val);
	    });

	    // Init cartesian by default
	    this.initCartesianSource();

	    // Now the viewing controls
	    var axis_value_control = this.viewingControls.add(params, 'axis_value').min(-10).max(10).step(0.01).listen();
	    axis_value_control.onChange(function(val){
	    	if(callbacks['axis_value']) callbacks['axis_value'](mode_obj,val);
	    })
	    var axes_list = ['X','Y'];
	    if(mode == "3D" || mode == "4D") axes_list.push("Z");
	    if(mode == "4D") axes_list.push("W");
	    this.viewingControls.add(params, 'axis', axes_list ).onChange(function(val){
	    	axis_value_control.name(val + " = ")
	    	params.axis_value = 0;
	    	if(callbacks['axis']) callbacks['axis'](mode_obj,val);
	    });
	    // Set axis name
	    axis_value_control.name(params.axis + " = ")

	    /*
	    this.viewingControls.add(params, 'samples', 200, 10000).name('Samples');
	    this.viewingControls.add(params, 'thickness', ['thin','medium','thick']).name('Thickness').onChange(function(val){
	    	if(callbacks['thickness']) callbacks['thickness'](mode_obj,val);
	    });
	    */

	    // Turn all sliders orange after the first one (the first one is the resolution one)
	    var sliders = document.querySelectorAll(".slider-fg")
	    for(var i=1;i<sliders.length;i++){ //notice i=1
	    	var slider = sliders[i]
	    	slider.style.background = this.colors.projections
		    slider.parentNode.parentNode.querySelector(".dg .cr.number input[type='text']").style.color = this.colors.projections
	    	slider.parentNode.parentNode.parentNode.parentNode.style['border-left'] = "3px solid " + this.colors.projections
	    }
	}

	// Functions for creating the controls for the 3 different inputs (cartesian, parametric and convex hull)
	GUI.prototype.initCartesianSource = function(){
		var arr = [];
		var callbacks = this.callbacks;
		var mode_obj = this.mode_obj;

		var eq = this.shapeProperties.add(this.params, 'equation').name('Equation').onChange(function(val){
			if(callbacks['equation']) callbacks['equation'](mode_obj,val);
		});
		arr.push(eq)
		var res = this.shapeProperties.add(this.params, 'resolution', 20, 200).name('Resolution').step(1).onChange(function(val){
			if(callbacks['resolution']) callbacks['resolution'](mode_obj,val);
		});
		arr.push(res)

		this.cartesianSourceItems = arr;
	}
	GUI.prototype.destroyCartesianSource = function(){
		if(!this.cartesianSourceItems) return;
		for(var i=0;i<this.cartesianSourceItems.length;i++){
			this.shapeProperties.remove(this.cartesianSourceItems[i])
		}
		this.cartesianSourceItems = [];
	}
	GUI.prototype.initParamSource = function(){
		var arr = [];
		var names = ["param_eq_x","param_eq_y","param_a","param_b","param_eq_z","param_c","param_eq_w","param_d"]
		var callbacks = this.callbacks;
		var mode_obj = this.mode_obj;

		arr.push(this.shapeProperties.add(this.params, 'param_eq_x').name('x = ').onChange(function(val){
			if(callbacks['param_eq_x']) callbacks['param_eq_x'](mode_obj,val);
		}));
		arr.push(this.shapeProperties.add(this.params, 'param_eq_y').name('y = ').onChange(function(val){
			if(callbacks['param_eq_y']) callbacks['param_eq_y'](mode_obj,val);
		}));
		if(this.mode == "3D" || this.mode  == "4D"){
			arr.push(this.shapeProperties.add(this.params, 'param_eq_z').name('z = ').onChange(function(val){
				if(callbacks['param_eq_z']) callbacks['param_eq_z'](mode_obj,val);
			}));
		}
		if(this.mode == "4D"){
			arr.push(this.shapeProperties.add(this.params, 'param_eq_w').name('w = ').onChange(function(val){
				if(callbacks['param_eq_w']) callbacks['param_eq_w'](mode_obj,val);
			}));
		}
		arr.push(this.shapeProperties.add(this.params, 'param_a').name('a = ').onChange(function(val){
			if(callbacks['param_a']) callbacks['param_a'](mode_obj,val);
		}));
		arr.push(this.shapeProperties.add(this.params, 'param_b').name('b = ').onChange(function(val){
			if(callbacks['param_b']) callbacks['param_b'](mode_obj,val);
		}));
		if(this.mode == "3D" || this.mode  == "4D"){
			arr.push(this.shapeProperties.add(this.params, 'param_c').name('c = ').onChange(function(val){
				if(callbacks['param_c']) callbacks['param_c'](mode_obj,val);
			}));
		}
		if(this.mode == "4D"){
			arr.push(this.shapeProperties.add(this.params, 'param_d').name('d = ').onChange(function(val){
				if(callbacks['param_d']) callbacks['param_d'](mode_obj,val);
			}));
		}

		// for(var i=0;i<arr.length;i++){
		// 	// Use a closure to capture the value of the current names[i]
		// 	var changeFunc = (function(name){ return function(val){ if(callbacks[name]) callbacks[name](mode_obj,val); } })(names[i]);
		// 	arr[i].onChange(function(val){
		// 		changeFunc(val)
		// 	})
		// }

		this.paramSourceItems = arr;
	}
	GUI.prototype.destroyParamSource = function(){
		if(!this.paramSourceItems) return;
		for(var i=0;i<this.paramSourceItems.length;i++){
			this.shapeProperties.remove(this.paramSourceItems[i])
		}
		this.paramSourceItems = [];
	}
	GUI.prototype.initConvexSource = function(){
		var arr = [];
		var callbacks = this.callbacks;
		var mode_obj = this.mode_obj;

		var points = this.shapeProperties.add(this.params, 'points').onChange(function(val){
			if(callbacks['points']) callbacks['points'](mode_obj,val);
		});

		arr.push(points)
		this.convexSourceItems = arr;
	}
	GUI.prototype.destroyConvexSource = function(){
		if(!this.convexSourceItems) return;
		for(var i=0;i<this.convexSourceItems.length;i++){
			this.shapeProperties.remove(this.convexSourceItems[i])
		}
		this.convexSourceItems = [];
	}

	GUI.prototype.cleanup = function(){
		//Destroys everything created
		this.gui.destroy();
		// Reset all stuff
		this.convexSourceItems = null;
		this.paramSourceItems = null;
		this.cartesianSourceItems = null;
	}

	scope.GUI = GUI;
	return GUI;
})(typeof exports === 'undefined' ? {} : exports);
<commit_msg>Swapped colors of projections/slicing<commit_after>// Class for creating & handling the dat.gui controls
// as well as creating a common interface for all modes to use

var GUI = (function (scope) {
	//Constructor
	function GUI(){

	}

	GUI.prototype.init = function(mode,callbacks,mode_obj){
		// Colors to use throughout
		this.colors = {
			'slices':'#e59a1c', // For the line/place of intersection
			'projections':'#3090FF' // For the actual shape/data
		}

		// These are all the possible parameters to keep track of
		this.params = {
			// Shape Properties
			'source':'cartesian',
			'equation':'',
			'points':'', // For convex hull
			'param_a':'', // These are the ranges the parameters take
			'param_b':'',
			'param_c':'',
			'param_d':'',
			'param_eq_x':'', // This is the parametric equation
			'param_eq_y':'',
			'param_eq_z':'',
			'param_eq_w':'',
			'render_shape':false,
			'render_slices':false,
			'resolution':60, // For the marching squares/cubes

			// Viewing Controls
			'axis_value':0.1,
			'axis':'Y',
			'samples':200,
			'thickness':'medium'
		};
		this.gui = null;
		this.mode = "";
		this.last_source = this.params.source;

		// 2D defaults
		this.defaults = {}
		// Alternative parametric: (a: [0,2pi],b:[0.5,1])
		//b * (1-cos(a))*sin(a) * 5
		//b * (1-cos(a))*cos(a) * 5
		// Alternative square:
		// (0,5),(5,0),(0,-5),(-5,0)
		this.defaults['2D'] = {
			'equation':'x^2+y^2 = 10', // Circle
			'points':'(5,5),(5,-5),(-5,-5),(-5,5)', // Square
			'param_eq_x':'sin(a) * b * 5', // butterfly wings
			'param_eq_y':'sin(a * 2) * b * 5',
			'param_a':'0 < a < 2 * PI',
			'param_b':'0 < b < 1',
			'axis':'Y'
		}
		// 3D defaults
		this.defaults['3D'] = {
			'equation':'x^2+y^2+z^2 = 10', // Sphere
			'resolution':20,
			'points':'(5,0,5),(5,0,-5),(-5,0,-5),(-5,0,5),(5,5,5),(5,5,-5),(-5,5,-5),(-5,5,5)', // Cube
			'param_eq_x':'(4 + 0.5 * (3 + 5 * cos(b)) ) * sin(a)',
			'param_eq_y':'(4 + 0.5 * (3 + 5 * cos(b)) ) * cos(a)',
			'param_eq_z':'4 + c * sin(b)',
			'param_a':'0 < a < 2 * PI',
			'param_b':'0 < b < PI',
			'param_c':'0 < c < 5'
			//'param_eq_x':'b * cos(a) - c * sin(a)', // Spiral tube
			//'param_eq_y':'b * sin(a) + c * cos(a)',
			//'param_eq_z':'a/3',
			//'param_a':'-4 * PI < a < 4 * PI',
			//'param_b':'0 < b < 1',
			//'param_c':'0 < c < 1'
		}

		// 3D defaults
		this.defaults['4D'] = {
			'equation':'x^2+y^2+z^2+w^2 = 10',
			'points':'',
			'resolution':20,
			'param_eq_x':'',
			'param_eq_y':'',
			'param_eq_z':'',
			'param_eq_w':'',
			'param_a':'0 < a < 1',
			'param_b':'0 < b < 1',
			'param_c':'0 < c < 1',
			'param_d':'0 < d < 1',
			'axis':'W'
		}

		this.mode_obj = null;
		this.callbacks = null;

		// Creates the scene and everything
		this.gui = new dat.GUI();
		this.mode = mode;
		this.mode_obj = mode_obj;
		this.callbacks = callbacks;
		// Create the two folders
		var shapeProperties = this.gui.addFolder('Shape Properties');
	    var viewingControls = this.gui.addFolder('Viewing Controls');
	    this.shapeProperties = shapeProperties; this.viewingControls = viewingControls;
	    var current_scope = this;
	    var params = this.params;

	    // Load defaults
	    for(var key in params){
	    	if(this.defaults[mode][key]){
	    		params[key] = this.defaults[mode][key]
	    	}
	    }

	    shapeProperties.add(params, 'source',['cartesian','parametric','convex-hull']).onChange(function(val){
	    	if(val == 'cartesian' && current_scope.last_source != 'cartesian'){
	    		current_scope.destroyConvexSource();
	    		current_scope.destroyParamSource();
	    		current_scope.initCartesianSource();
	    	}
	    	if(val == 'parametric' && current_scope.last_source != 'parametric'){
	    		current_scope.destroyConvexSource();
	    		current_scope.destroyCartesianSource();
	    		current_scope.initParamSource();
	    	}
	    	if(val == 'convex-hull' && current_scope.last_source != 'convex-hull'){
	    		current_scope.destroyCartesianSource();
	    		current_scope.destroyParamSource();
	    		current_scope.initConvexSource();
	    	}
	    	current_scope.last_source = val

	    	if(callbacks['source']) callbacks['source'](mode_obj,val);
	    });

	    shapeProperties.add(params, 'render_shape').name("Render Shape").listen().onChange(function(val){
	    	if(callbacks['render_shape']) callbacks['render_shape'](mode_obj,val);
	    });

			shapeProperties.add(params, 'render_slices').name("Render Slices").listen().onChange(function(val){
	    	if(callbacks['render_slices']) callbacks['render_slices'](mode_obj,val);
	    });

	    // Init cartesian by default
	    this.initCartesianSource();

	    // Now the viewing controls
	    var axis_value_control = this.viewingControls.add(params, 'axis_value').min(-10).max(10).step(0.01).listen();
	    axis_value_control.onChange(function(val){
	    	if(callbacks['axis_value']) callbacks['axis_value'](mode_obj,val);
	    })
	    var axes_list = ['X','Y'];
	    if(mode == "3D" || mode == "4D") axes_list.push("Z");
	    if(mode == "4D") axes_list.push("W");
	    this.viewingControls.add(params, 'axis', axes_list ).onChange(function(val){
	    	axis_value_control.name(val + " = ")
	    	params.axis_value = 0;
	    	if(callbacks['axis']) callbacks['axis'](mode_obj,val);
	    });
	    // Set axis name
	    axis_value_control.name(params.axis + " = ")

	    /*
	    this.viewingControls.add(params, 'samples', 200, 10000).name('Samples');
	    this.viewingControls.add(params, 'thickness', ['thin','medium','thick']).name('Thickness').onChange(function(val){
	    	if(callbacks['thickness']) callbacks['thickness'](mode_obj,val);
	    });
	    */

	    // Turn all sliders orange after the first one (the first one is the resolution one)
	    var sliders = document.querySelectorAll(".slider-fg")
	    for(var i=1;i<sliders.length;i++){ //notice i=1
	    	var slider = sliders[i]
	    	slider.style.background = this.colors.projections
		    slider.parentNode.parentNode.querySelector(".dg .cr.number input[type='text']").style.color = this.colors.projections
	    	slider.parentNode.parentNode.parentNode.parentNode.style['border-left'] = "3px solid " + this.colors.projections
	    }
	}

	// Functions for creating the controls for the 3 different inputs (cartesian, parametric and convex hull)
	GUI.prototype.initCartesianSource = function(){
		var arr = [];
		var callbacks = this.callbacks;
		var mode_obj = this.mode_obj;

		var eq = this.shapeProperties.add(this.params, 'equation').name('Equation').onChange(function(val){
			if(callbacks['equation']) callbacks['equation'](mode_obj,val);
		});
		arr.push(eq)
		var res = this.shapeProperties.add(this.params, 'resolution', 20, 200).name('Resolution').step(1).onChange(function(val){
			if(callbacks['resolution']) callbacks['resolution'](mode_obj,val);
		});
		arr.push(res)

		this.cartesianSourceItems = arr;
	}
	GUI.prototype.destroyCartesianSource = function(){
		if(!this.cartesianSourceItems) return;
		for(var i=0;i<this.cartesianSourceItems.length;i++){
			this.shapeProperties.remove(this.cartesianSourceItems[i])
		}
		this.cartesianSourceItems = [];
	}
	GUI.prototype.initParamSource = function(){
		var arr = [];
		var names = ["param_eq_x","param_eq_y","param_a","param_b","param_eq_z","param_c","param_eq_w","param_d"]
		var callbacks = this.callbacks;
		var mode_obj = this.mode_obj;

		arr.push(this.shapeProperties.add(this.params, 'param_eq_x').name('x = ').onChange(function(val){
			if(callbacks['param_eq_x']) callbacks['param_eq_x'](mode_obj,val);
		}));
		arr.push(this.shapeProperties.add(this.params, 'param_eq_y').name('y = ').onChange(function(val){
			if(callbacks['param_eq_y']) callbacks['param_eq_y'](mode_obj,val);
		}));
		if(this.mode == "3D" || this.mode  == "4D"){
			arr.push(this.shapeProperties.add(this.params, 'param_eq_z').name('z = ').onChange(function(val){
				if(callbacks['param_eq_z']) callbacks['param_eq_z'](mode_obj,val);
			}));
		}
		if(this.mode == "4D"){
			arr.push(this.shapeProperties.add(this.params, 'param_eq_w').name('w = ').onChange(function(val){
				if(callbacks['param_eq_w']) callbacks['param_eq_w'](mode_obj,val);
			}));
		}
		arr.push(this.shapeProperties.add(this.params, 'param_a').name('a = ').onChange(function(val){
			if(callbacks['param_a']) callbacks['param_a'](mode_obj,val);
		}));
		arr.push(this.shapeProperties.add(this.params, 'param_b').name('b = ').onChange(function(val){
			if(callbacks['param_b']) callbacks['param_b'](mode_obj,val);
		}));
		if(this.mode == "3D" || this.mode  == "4D"){
			arr.push(this.shapeProperties.add(this.params, 'param_c').name('c = ').onChange(function(val){
				if(callbacks['param_c']) callbacks['param_c'](mode_obj,val);
			}));
		}
		if(this.mode == "4D"){
			arr.push(this.shapeProperties.add(this.params, 'param_d').name('d = ').onChange(function(val){
				if(callbacks['param_d']) callbacks['param_d'](mode_obj,val);
			}));
		}

		// for(var i=0;i<arr.length;i++){
		// 	// Use a closure to capture the value of the current names[i]
		// 	var changeFunc = (function(name){ return function(val){ if(callbacks[name]) callbacks[name](mode_obj,val); } })(names[i]);
		// 	arr[i].onChange(function(val){
		// 		changeFunc(val)
		// 	})
		// }

		this.paramSourceItems = arr;
	}
	GUI.prototype.destroyParamSource = function(){
		if(!this.paramSourceItems) return;
		for(var i=0;i<this.paramSourceItems.length;i++){
			this.shapeProperties.remove(this.paramSourceItems[i])
		}
		this.paramSourceItems = [];
	}
	GUI.prototype.initConvexSource = function(){
		var arr = [];
		var callbacks = this.callbacks;
		var mode_obj = this.mode_obj;

		var points = this.shapeProperties.add(this.params, 'points').onChange(function(val){
			if(callbacks['points']) callbacks['points'](mode_obj,val);
		});

		arr.push(points)
		this.convexSourceItems = arr;
	}
	GUI.prototype.destroyConvexSource = function(){
		if(!this.convexSourceItems) return;
		for(var i=0;i<this.convexSourceItems.length;i++){
			this.shapeProperties.remove(this.convexSourceItems[i])
		}
		this.convexSourceItems = [];
	}

	GUI.prototype.cleanup = function(){
		//Destroys everything created
		this.gui.destroy();
		// Reset all stuff
		this.convexSourceItems = null;
		this.paramSourceItems = null;
		this.cartesianSourceItems = null;
	}

	scope.GUI = GUI;
	return GUI;
})(typeof exports === 'undefined' ? {} : exports);
<|endoftext|>


<commit_before>import chalk from 'chalk'

const errorStyle = chalk.bold.red
const warningStyle = chalk.yellow

export function logError (entry) {
  console.log(sagui(), errorStyle(entry))
}

export function logWarning (entry) {
  console.log(sagui(), warningStyle(entry))
}

export function log (entry) {
  console.log(sagui(), entry)
}

function sagui () {
  return chalk.black.bgCyan('[sagui]')
}
<commit_msg>Prettier logging<commit_after>import chalk from 'chalk'

const errorStyle = chalk.bold.red
const warningStyle = chalk.yellow

export function logError (entry) {
  console.log(sagui(), errorStyle(entry))
}

export function logWarning (entry) {
  console.log(sagui(), warningStyle(entry))
}

export function log (entry) {
  console.log(sagui(), entry)
}

function sagui () {
  return chalk.dim('🐵  Sagui')
}
<|endoftext|>


<commit_before>'use strict'

var $ = window.jQuery

/**
 * User interface module
 * @module
 * @name ui
 * @description A module to provide configured ui elements to the plugin
 */

module.exports = {
  lockNode: lockNode,
  unlockNode: unlockNode,
  addUnselectableCSSClass: addUnselectableCSSClass,
  resetMenuItemState: resetMenuItemState,
  autoAddMenuItems: autoAddMenuItems,
  mapMceLayoutElements: mapMceLayoutElements,
  getElementHeight: getElementHeight
}

/**
 * Lock a node
 * @method
 * @mixin
 * @returns {undefined}
 */
function lockNode () {
  var $this = $(this)
  $this.attr('contenteditable', false)
  $this.addClass('unselectable')
}

/**
 * Unlock a node
 * @method
 * @mixin
 * @returns {undefined}
 */
function unlockNode () {
  var $this = $(this)
  $this.attr('contenteditable', true)
  $this.removeClass('unselectable')
  $this.focus()
}

/**
 * Create and apply the unselectable CSS class to the active document
 * @method
 * @static
 * @param {Editor} editor The tinymce active editor
 * @returns {undefined}
 */
function addUnselectableCSSClass (editor) {
  var head = $('head', editor.getDoc())
  var unselectableCSSRules = '.unselectable { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; }'
  var style = $('<style>').attr('type', 'text/css').html(unselectableCSSRules)
  style.appendTo(head)
}

/**
 * Reset a menu item state
 * @method
 * @static
 * @param {Editor} editor The tinymce active editor
 * @param {String} selector The selector to use to DO WHAT ?
 * @TODO finish to describe the selector parameter
 */
function resetMenuItemState (editor, selector) {
  var selectedElement = editor.selection.getStart()
  var $sel = $(selectedElement)
  var parents = $sel.parents(selector)
  this.disabled(!parents.length)
}

/**
 * Add the plugin's menu items
 */
function autoAddMenuItems () {
  for (var itemName in this.menuItemsList) {
    this.editor.addMenuItem(itemName, this.menuItemsList[itemName])
  }
}

function mapMceLayoutElements (bodyClass, stackedLayout) {
  stackedLayout.root = $('.' + bodyClass)
  stackedLayout.wrapper = stackedLayout.root.children('.mce-tinymce')
  stackedLayout.layout = stackedLayout.wrapper.children('.mce-stack-layout')
  stackedLayout.menubar = stackedLayout.layout.children('.mce-stack-layout-item.mce-menubar.mce-toolbar')
  stackedLayout.toolbar = stackedLayout.layout.children('.mce-stack-layout-item.mce-toolbar-grp')
  stackedLayout.editarea = stackedLayout.layout.children('.mce-stack-layout-item.mce-edit-area')
  stackedLayout.statusbar = {}
  stackedLayout.statusbar.wrapper = stackedLayout.layout.children('.mce-stack-layout-item.mce-statusbar')
  stackedLayout.statusbar.flowLayout = stackedLayout.statusbar.wrapper.children('.mce-flow-layout')
  stackedLayout.statusbar.path = stackedLayout.statusbar.wrapper.children('.mce-path')
  stackedLayout.statusbar.wordcount = stackedLayout.layout.children('.mce-wordcount')
  stackedLayout.statusbar.resizehandle = stackedLayout.layout.children('.mce-resizehandle')
}

function getElementHeight (element, win) {
  win = win || window
  return win.getComputedStyle(element).height
}
<commit_msg>fix uiUtils.getElementHeight() adding paddings and margins values<commit_after>'use strict'

var units = require('./units')
var $ = window.jQuery

/**
 * User interface module
 * @module
 * @name ui
 * @description A module to provide configured ui elements to the plugin
 */

module.exports = {
  jQuery: $,
  lockNode: lockNode,
  unlockNode: unlockNode,
  addUnselectableCSSClass: addUnselectableCSSClass,
  resetMenuItemState: resetMenuItemState,
  autoAddMenuItems: autoAddMenuItems,
  mapMceLayoutElements: mapMceLayoutElements,
  getElementHeight: getElementHeight
}

/**
 * Lock a node
 * @method
 * @mixin
 * @returns {undefined}
 */
function lockNode () {
  var $this = $(this)
  $this.attr('contenteditable', false)
  $this.addClass('unselectable')
}

/**
 * Unlock a node
 * @method
 * @mixin
 * @returns {undefined}
 */
function unlockNode () {
  var $this = $(this)
  $this.attr('contenteditable', true)
  $this.removeClass('unselectable')
  $this.focus()
}

/**
 * Create and apply the unselectable CSS class to the active document
 * @method
 * @static
 * @param {Editor} editor The tinymce active editor
 * @returns {undefined}
 */
function addUnselectableCSSClass (editor) {
  var head = $('head', editor.getDoc())
  var unselectableCSSRules = '.unselectable { -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; }'
  var style = $('<style>').attr('type', 'text/css').html(unselectableCSSRules)
  style.appendTo(head)
}

/**
 * Reset a menu item state
 * @method
 * @static
 * @param {Editor} editor The tinymce active editor
 * @param {String} selector The selector to use to DO WHAT ?
 * @TODO finish to describe the selector parameter
 */
function resetMenuItemState (editor, selector) {
  var selectedElement = editor.selection.getStart()
  var $sel = $(selectedElement)
  var parents = $sel.parents(selector)
  this.disabled(!parents.length)
}

/**
 * Add the plugin's menu items
 */
function autoAddMenuItems () {
  for (var itemName in this.menuItemsList) {
    this.editor.addMenuItem(itemName, this.menuItemsList[itemName])
  }
}

function mapMceLayoutElements (bodyClass, stackedLayout) {
  stackedLayout.root = $('.' + bodyClass)
  stackedLayout.wrapper = stackedLayout.root.children('.mce-tinymce')
  stackedLayout.layout = stackedLayout.wrapper.children('.mce-stack-layout')
  stackedLayout.menubar = stackedLayout.layout.children('.mce-stack-layout-item.mce-menubar.mce-toolbar')
  stackedLayout.toolbar = stackedLayout.layout.children('.mce-stack-layout-item.mce-toolbar-grp')
  stackedLayout.editarea = stackedLayout.layout.children('.mce-stack-layout-item.mce-edit-area')
  stackedLayout.statusbar = {}
  stackedLayout.statusbar.wrapper = stackedLayout.layout.children('.mce-stack-layout-item.mce-statusbar')
  stackedLayout.statusbar.flowLayout = stackedLayout.statusbar.wrapper.children('.mce-flow-layout')
  stackedLayout.statusbar.path = stackedLayout.statusbar.wrapper.children('.mce-path')
  stackedLayout.statusbar.wordcount = stackedLayout.layout.children('.mce-wordcount')
  stackedLayout.statusbar.resizehandle = stackedLayout.layout.children('.mce-resizehandle')
}

function getElementHeight (element, win) {
  win = win || window
  var style = win.getComputedStyle(element)
  var height = px(style.height) +
    px(style.paddingTop) + px(style.paddingBottom) +
    px(style.marginTop) + px(style.marginBottom)
  return height

  function px (style) {
    return Number(units.getValueFromStyle(style))
  }
}
<|endoftext|>