Melvor DungeonTimer

Displays different statistics relating to dungeon completion (Completion count since the script has been installed,previous time, best time, average time)

// ==UserScript==
// @name		Melvor DungeonTimer
// @namespace	http://tampermonkey.net/
// @version		1.0.0.1
// @description	Displays different statistics relating to dungeon completion (Completion count since the script has been installed,previous time, best time, average time)
// @description	Please report issues via message to Chrono#1840 on Discord
// @author		Chrono
// @match		https://*.melvoridle.com/*
// @exclude		https://wiki.melvoridle.com*
// @noframes
// ==/UserScript==

var DISPLAY_CLEAR_BUTTONS = false;

function script() {
    if (window.DungeonTimer !== undefined) {
        console.error('DungeonTimer is already loaded!');
    } else {
        createDungeonTimerData();
        createDungeonTimer();
        loadDungeonTimer();
    }

    function createDungeonTimerData() {

        //get the player key (allow multi account) and define local storage key name
        var playerStorageKey = getKeysForCharacter(currentCharacter) +'DungeonTimerDataV2' ;

        //Check if there is already a key matching this script in local storage
        if (localStorage.getItem(playerStorageKey)!== null)
        {
            window.DungeonTimerData = JSON.parse(localStorage.getItem(playerStorageKey))
            console.log("DungeonTimerData Key found - Data retrieved");
        }
        else
        {
            //check if previous version of the local storage exists
            if (localStorage.getItem('DungeonTimerDataV2')!== null)
            {
                window.DungeonTimerData = JSON.parse(localStorage.getItem('DungeonTimerDataV2'))
                console.log("DungeonTimerData Key found - Data retrieved");
                localStorage.removeItem('DungeonTimerDataV2');
            }
            else
            {
                window.DungeonTimerData = []

                if (localStorage.getItem('DungeonTimerData')!== null)
                {
                    window.DungeonTimerData = JSON.parse(localStorage.getItem('DungeonTimerData'))

                    for (i=0; i<window.DungeonTimerData.length; i++)
                    {
                        window.DungeonTimerData[i][3] = [window.DungeonTimerData[i][3]];
                    }

                    localStorage.removeItem('DungeonTimerData');
                }
            }
        }

        //If the key is missing dungeon entries, add them to the end of the list
        var i
        for (i=window.DungeonTimerData.length;i<DUNGEONS.length;i++)
        {
            //comp count, last time, best time, average times (last 20)
            window.DungeonTimerData.push([0,0,0,[]])
        }

        // save settings to local storage
        window.DungeonTimerSettings = {
            save: () => {
                window.localStorage[playerStorageKey] = window.JSON.stringify(window.DungeonTimerData);
            }
        };

        //Check for the dungeon completion key

        window.DungeonCompCount = []

        if (dungeonCompleteCount!== null)
        {
            window.DungeonCompCount = dungeonCompleteCount;
        }
        else
        {
            for (i=0;i<DUNGEONS.length;i++)
            {
                window.DungeonCompCount.push(window.DungeonTimerData[i][0])
            }
        }
    }

    function createDungeonTimer() {
        // global object
        window.DungeonTimer = {};

        DungeonTimer.log = function (...args) {
            console.log("Melvor DungeonTimer:", ...args)
        }

        DungeonTimer.createSettingsMenu = () => {
            // set names
            DungeonTimer.menuItemID = 'DungeonTimerButton';
            DungeonTimer.modalID = 'DungeonTimerModal';

            // clean up in case elements already exist
            destroyMenu(DungeonTimer.menuItemID, DungeonTimer.modalID);

            // create wrappers and access point
            DungeonTimer.radio = document.createElement('div');
            DungeonTimer.content = document.createElement('div');
            DungeonTimer.content.className = 'mdtGridContainer';

            addMenuItem('Dungeon Timers', 'https://cdn.melvor.net/core/v018/assets/media/skills/combat/dungeon.svg', DungeonTimer.menuItemID, 'DungeonTimerModal')
            addModal('Dungeon Timers', DungeonTimer.modalID, [DungeonTimer.radio,DungeonTimer.content])

            // create the dungeon cards
            DungeonTimer.addDungeonData();

            // log
            DungeonTimer.log('added Timer menu!')
        }

        //the dungeonID order does not match the order shown in the dungeon combat menu, so artificially change dungeon order
        //if a new dungeon is added, revert order until manually fixed
        var i
        var dungeonOrder = [0,1,6,7,2,13,4,3,12,5,14,8,9,10,11,15]
        if (dungeonOrder.length!=DUNGEONS.length)
        {
            dungeonOrder = []
            for (i=0;i<DUNGEONS.length;i++)
            {
                dungeonOrder.push(i)
            }
        }

        //for each dungeon, add a card to the modal menu that includes the dungeon image, its name and the data from local storage
        DungeonTimer.addDungeonData = () => {
            DungeonTimer.toggleCard = new Card(DungeonTimer.radio, '', '150px', true);
            DungeonTimer.toggleCard.addRadio('Display Timer Clear Buttons   ', 25, 'ClearButtonToggle', ['Show','Hide'], [ShowMDTButtons,HideMDTButtons], 1)

            for (i=0;i<DUNGEONS.length;i++)
            {
                DungeonTimer.DungeonCard = new Card(DungeonTimer.content, '166px', '105px', true);

                DungeonTimer.DungeonCard.addImage(DUNGEONS[dungeonOrder[i]].media, 30,'');
                DungeonTimer.DungeonCard.addSectionTitle(DUNGEONS[dungeonOrder[i]].name, '')
                DungeonTimer.DungeonCard.addNumberOutput('Completion Count ', dungeonCompleteCount[dungeonOrder[i]], 20,'', 'Completion'+dungeonOrder[i])
                DungeonTimer.DungeonCard.addNumberOutput('Previous Time ', formatTime(window.DungeonTimerData[dungeonOrder[i]][1]), 20,'', 'PreviousTime'+dungeonOrder[i])
                DungeonTimer.DungeonCard.addNumberOutput('Best Time ', formatTime(window.DungeonTimerData[dungeonOrder[i]][2]), 20,'', 'BestTime'+dungeonOrder[i])
                DungeonTimer.DungeonCard.addNumberOutput('Average Time ', formatTime(AverageTimeCalc(window.DungeonTimerData[dungeonOrder[i]][3])), 20,'', 'AverageTime'+dungeonOrder[i])
                DungeonTimer.DungeonCard.addClearButtons('Clear Best', 'Clear Avg', deleteBestTime, deleteAverageTime,i)

            };
        }

        ////////////////////
        //internal methods//
        ////////////////////

        function HideMDTButtons() {
            var clearButtons = document.getElementsByClassName("mdtbutton");
            var grid = document.getElementsByClassName('mdtGridContainer')[0];
            var cards = grid.getElementsByClassName('mdtCardContainer');

            clearButtons.forEach(button => button.style.display = "none");
            grid.style.gridTemplateRows = "167px 167px 167px 167px 167px";
            cards.forEach(card => card.style.height = '166px');
        }

        function ShowMDTButtons() {
            var clearButtons = document.getElementsByClassName("mdtbutton");
            var grid = document.getElementsByClassName('mdtGridContainer')[0];
            var cards = grid.getElementsByClassName('mdtCardContainer');

            clearButtons.forEach(button => button.style.display = "block");
            grid.style.gridTemplateRows = "207px 207px 207px 207px 207px";
            cards.forEach(card => card.style.height = '206px');
        }

        function deleteBestTime(index) {
            console.log('Best Time Cleared.');
            window.DungeonTimerData[dungeonOrder[index]][2] = 0
            UpdateMenu(index)
        }

        function deleteAverageTime(index) {
            console.log('Average Time Cleared.');
            window.DungeonTimerData[dungeonOrder[index]][3] = []
            UpdateMenu(index)
        }

        function AverageTimeCalc(list) {
            if (list.length >0)
            {
                return list.reduce((a, b) => a + b, 0)/list.length
            }
            else
            {
                return 0
            }
        }

        // Convert milliseconds to minutes/seconds and format them
        function formatTime(ms) {
            let seconds = Math.round(ms / 100)/10;
            // split minutes and seconds
            let m = Math.floor(seconds / 60);
            let s = ('0000'+Number(seconds- m*60).toFixed(1)).slice(-4) ;

            var formattedTime = m + "min " + s + "s";

            // append m and s etc then concat and return
            if (ms == 0)
            {
                return "-";
            }
            else
            {
                return formattedTime;
            }
        }

        //declare global variables that will be used to measure time
        var dungeonTickStopwatch = [];
        var dungeonStartTick;
        var dungeonIconElement = document.getElementById("combat-dungeon-img");

        // Store a reference to the game's selectDungeon function
        combatManager._selectDungeon = combatManager.selectDungeon;
        // Overwrite the game's selectDungeon function to inject timer function and tooltip generation function
        combatManager.selectDungeon = (dungeonID) => {
            combatManager._selectDungeon(dungeonID);
            try {
                //start the timer
                dungeonStartTick = combatManager.tickCount;
                generateTooltip(dungeonID);
            } catch (e) {
                console.error(e);
            }
        };

        // Store a reference to the game's pauseDungeon function
        combatManager._pauseDungeon = combatManager.pauseDungeon;
        // Overwrite the game's pauseDungeon function to stop the stopwatch when the dungeon is paused
        combatManager.pauseDungeon = () => {
            try {
                //stop the watch and add the time to the array
                dungeonTickStopwatch.push(combatManager.tickCount - dungeonStartTick);
            } catch (e) {
                console.error(e);
            } finally {
                combatManager._pauseDungeon();
            }
        };

        // Store a reference to the game's resumeDungeon function
        combatManager._resumeDungeon = combatManager.resumeDungeon;
        // Overwrite the game's resumeDungeon function to restart the stopwatch when the dungeon is resumed
        combatManager.resumeDungeon = () => {
            try {
                //end the timer and process the output
                dungeonStartTick = combatManager.tickCount;
            } catch (e) {
                console.error(e);
            } finally {
                combatManager._resumeDungeon();
            }
        };

        combatManager._stopCombat = combatManager.stopCombat;
        // Overwrite the game's stopCombat function to empty the stopwatch array in case of death or if running away, and destroy the tooltip
        combatManager.stopCombat = () => {
            try {
                if (dungeonIconElement._tippy)
                    dungeonIconElement._tippy.destroy();
                dungeonTickStopwatch = []
            } catch (e) {
                console.error(e);
            } finally {
                combatManager._stopCombat();
            }
        };

        const _rollForPetDungeonPet = rollForPetDungeonPet;
        // Overwrite the game's rollForPetDungeonPet function to tally timers, update dungeon data and destroy tooltip when the dungeon is completed
        rollForPetDungeonPet = (petID, dungeonID) => {
            try {
                //end the timer and process the output
                var dungeonTimeTick = combatManager.tickCount - dungeonStartTick;
                if (!isNaN(dungeonTimeTick))
                {
                    dungeonTickStopwatch.forEach(tickCount => dungeonTimeTick += tickCount);
                    dungeonTickStopwatch = []
                    FillLocalStorageValues(dungeonID,dungeonTimeTick*50);

                    if (loadingOfflineProgress == false)
                    {
                        UpdateMenu(dungeonID);
                    }
                    //console.log(dungeonTimeTick*50);
                }

                if (dungeonIconElement._tippy)
                {
                    dungeonIconElement._tippy.destroy();
                }
                generateTooltip(dungeonID);

                dungeonStartTime = Date.now();
                dungeonStartTick = combatManager.tickCount;


            } catch (e) {
                console.error(e);
            } finally {
                _rollForPetDungeonPet(petID, dungeonID);
            }
        };

        //update the data of the selected dungeon in local storage
        function FillLocalStorageValues(dungeonID,dungeonTime) {
            //Dungeon Completion count
            window.DungeonTimerData[dungeonID][0]++; //used for average time
            //window.DungeonCompCount[dungeonID]++; //used for display
            //Latest time
            window.DungeonTimerData[dungeonID][1] = dungeonTime;
            //Best time
            if (window.DungeonTimerData[dungeonID][2] == 0 || window.DungeonTimerData[dungeonID][2] > window.DungeonTimerData[dungeonID][1])
            {
                window.DungeonTimerData[dungeonID][2] = window.DungeonTimerData[dungeonID][1]
            }
            //Average time
            //store 20 times and average
            if (window.DungeonTimerData[dungeonID][3].length === 20)
            {
                window.DungeonTimerData[dungeonID][3].splice(0, 1);
            }

            window.DungeonTimerData[dungeonID][3].push(dungeonTime);

        }

        //Update the values for the completed dungeon in the script menu
        function UpdateMenu(dungeonID){
            document.getElementById('Completion'+dungeonID).innerText = window.DungeonCompCount[dungeonID]
            document.getElementById('PreviousTime'+dungeonID).innerText = formatTime(window.DungeonTimerData[dungeonID][1])
            document.getElementById('BestTime'+dungeonID).innerText = formatTime(window.DungeonTimerData[dungeonID][2])
            document.getElementById('AverageTime'+dungeonID).innerText = formatTime(AverageTimeCalc(window.DungeonTimerData[dungeonID][3]))
        }

        //create tooltip and populate its content
        function generateTooltip(dungeonID) {
            // Generate progression Tooltips
            if (!dungeonIconElement._tippy) {
                tippy(dungeonIconElement, {
                    allowHTML: true,
                    interactive: false,
                    animation: false,
                });
            }
            var tooltip = '' ;
            if (window.DungeonTimerData[dungeonID][0] == 0)
            {
                tooltip = "No time on record";
            }
            else
            {
                tooltip += 'Previous Time : ' + formatTime(window.DungeonTimerData[dungeonID][1]) + '<br>';
                tooltip += 'Best Time : ' + formatTime(window.DungeonTimerData[dungeonID][2]) + '<br>';
                tooltip += 'Average Time : ' + formatTime(AverageTimeCalc(window.DungeonTimerData[dungeonID][3]));
            }
            // wrap and return
            dungeonIconElement._tippy.setContent(`<div>${tooltip}</div>`);
        }
    }

    function loadDungeonTimer() {
        // Loading script
        DungeonTimer.log('loading...');

        DungeonTimer.log('loaded!');
        setTimeout(DungeonTimer.createSettingsMenu, 50);

        // regularly save settings to local storage
        setInterval(window.DungeonTimerSettings.save, 1000)
    }
}

// inject the script
(function () {
    function injectScript(main) {
        const scriptElement = document.createElement('script');
        scriptElement.textContent = `try {(${main})();} catch (e) {console.log(e);}`;
        document.body.appendChild(scriptElement).parentNode.removeChild(scriptElement);
    }

    function loadScript() {
        if ((window.isLoaded && !window.currentlyCatchingUp)
            || (typeof unsafeWindow !== 'undefined' && unsafeWindow.isLoaded && !unsafeWindow.currentlyCatchingUp)) {
            // Only load script after game has opened
            clearInterval(scriptLoader);
            injectScript(script);
        }
    }
    const scriptLoader = setInterval(loadScript, 200);
})();


//all the next functions are taken from Melvor Idle Combat Simulator (https://github.com/visua0/Melvor-Idle-Combat-Simulator-Reloaded) and are used to create the sidebar menu

destroyMenu = (menuItemId, modalID, menuID = 'mdtToolsMenu') => {
    // remove the MICSR tab access point
    const tab = document.getElementById(menuItemId);
    if (tab !== null) {
        window.menuTabs = window.menuTabs.filter(x => x !== tab);
        tab.remove();
    }
    // remove the tools menu if it is empty
    const menu = document.getElementById(menuID);
    if (menu !== null && menu.length === 0) {
        menu.remove();
    }
    // hide and remove the modal
    const modal = document.getElementById(modalID);
    if (modal !== null) {
        $(modal).modal('hide');
        $(modal).modal('dispose');
        modal.remove();
    }
}

addMenuItem = (itemTitle, iconSrc, accessID, modalID, menuTitle = 'Tools', menuID = 'mcsToolsMenu', eyeID = 'mdtHeadingEye') => {
    createMenu(menuTitle, menuID, eyeID);
    if (window.menuTabs === undefined) {
        window.menuTabs = [];
    }

    const tabDiv = document.createElement('li');
    window.menuTabs.push(tabDiv);
    tabDiv.id = accessID;
    tabDiv.style.cursor = 'pointer';
    tabDiv.className = 'nav-main-item mdtNoSelect';

    const menuButton = document.createElement('div');
    menuButton.className = 'nav-main-link nav-compact';
    menuButton.dataset.toggle = 'modal';
    menuButton.dataset.target = '#' + modalID;
    tabDiv.appendChild(menuButton);

    const icon = document.createElement('img');
    icon.className = 'nav-img';
    icon.src = iconSrc;
    menuButton.appendChild(icon);

    const menuText = document.createElement('span');
    menuText.className = 'nav-main-link-name';
    menuText.textContent = itemTitle;
    menuButton.appendChild(menuText);

    document.getElementById(menuID).after(tabDiv);

    // return access point
    return tabDiv;
}

addModal = (title, id, content) => {
    // create modal
    const modal = document.createElement('div');
    modal.id = id;
    modal.className = 'modal';

    // create dialog
    const modalDialog = document.createElement('div');
    modalDialog.className = 'modal-dialog';
    modal.appendChild(modalDialog);

    // create content wrapper
    const modalContent = document.createElement('div');
    modalContent.className = 'modal-content mdtmodal-content';
    modalDialog.appendChild(modalContent);

    // create header
    const modalHeader = $(`<div class="block block-themed block-transparent mb-0"><div class="block-header bg-primary-dark">
        <h3 class="block-title">${title}</h3>
        <div class="block-options"><button type="button" class="btn-block-option" data-dismiss="modal" aria-label="Close">
        <i class="fa fa-fw fa-times"></i></button></div></div></div>`);
    $(modalContent).append(modalHeader);

    // add content
    content.forEach(x => modalContent.appendChild(x));

    // insert modal
    document.getElementById('page-container').appendChild(modal);

    // return modal
    return modal;
}

createMenu = (title, menuID, eyeID) => {
    // check if tools menu already exists
    let menu = document.getElementById(menuID);
    if (menu !== null) {
        return menu;
    }

    // Create new tools menu
    menu = document.createElement('li');
    menu.id = menuID;
    menu.className = 'nav-main-heading mdtNoSelect';
    menu.textContent = title;

    // Create heading eye
    const headingEye = document.createElement('i');
    headingEye.className = 'far fa-eye text-muted ml-1';
    headingEye.id = eyeID;
    headingEye.onclick = () => headingEyeOnClick(eyeID);
    headingEye.style.cursor = 'pointer';
    menu.appendChild(headingEye);
    window.eyeHidden = false;

    // insert menu before Minigames
    document.getElementsByClassName('nav-main-heading').forEach(heading => {
        if (heading.textContent === 'Minigame') {
            heading.parentElement.insertBefore(menu, heading);
        }
    });
}

headingEyeOnClick = (eyeID) => {
    const headingEye = document.getElementById(eyeID);
    if (window.eyeHidden) {
        headingEye.className = 'far fa-eye text-muted ml-1';
        window.menuTabs.forEach(tab => tab.style.display = '');
        window.eyeHidden = false;
    } else {
        headingEye.className = 'far fa-eye-slash text-muted ml-1';
        window.menuTabs.forEach(tab => tab.style.display = 'none');
        window.eyeHidden = true;
    }
}


Card = class {
    constructor(parentElement, height, inputWidth, outer = false) {
        this.outerContainer = document.createElement('div');
        this.outerContainer.className = `mdtCardContainer${outer ? ' mdtOuter block block-rounded border-top border-combat border-4x bg-combat-inner-dark' : ''}`;
        if (height !== '') {
            this.outerContainer.style.height = height;
        }
        this.container = document.createElement('div');
        this.container.className = 'mdtCardContentContainer';
        this.outerContainer.appendChild(this.container);
        parentElement.appendChild(this.outerContainer);
        this.inputWidth = inputWidth;
        this.dropDowns = [];
        this.numOutputs = [];
    }

    addImage(imageSource, imageSize, imageID = '') {
        const newImage = document.createElement('img');
        newImage.style.width = `${imageSize}px`;
        newImage.style.height = `${imageSize}px`;
        newImage.id = imageID;
        newImage.src = imageSource;
        const div = document.createElement('div');
        div.className = 'mb-1';
        div.style.textAlign = 'center';
        div.appendChild(newImage);
        this.container.appendChild(div);
        return newImage;
    }

    addNumberOutput(labelText, initialValue, height, imageSrc, outputID, setLabelID = false) {
        if (!outputID) {
            outputID = `mdt ${labelText} Output`;
        }
        const newCCContainer = this.createCCContainer();
        if (imageSrc && imageSrc !== '') {
            newCCContainer.appendChild(this.createImage(imageSrc, height));
        }
        const newLabel = this.createLabel(labelText, outputID, setLabelID);
        if (setLabelID) {
            newLabel.id = `mdt ${labelText} Label`;
        }
        newCCContainer.appendChild(newLabel);
        const newOutput = document.createElement('span');
        newOutput.className = 'mdtNumberOutput';
        newOutput.style.width = this.inputWidth;
        newOutput.textContent = initialValue;
        newOutput.id = outputID;
        newCCContainer.appendChild(newOutput);


        this.container.appendChild(newCCContainer);
        this.numOutputs.push(newOutput);
    }

    addSectionTitle(titleText, titleID) {
        const newSectionTitle = document.createElement('div');
        if (titleID) {
            newSectionTitle.id = titleID;
        }
        newSectionTitle.textContent = titleText;
        newSectionTitle.className = 'mdtSectionTitle';
        const titleContainer = document.createElement('div');
        titleContainer.className = 'd-flex justify-content-center';
        titleContainer.appendChild(newSectionTitle);
        this.container.appendChild(titleContainer);
    }

    addClearButtons(buttonText1,buttonText2, onclickCallback1, onclickCallback2, index) {
        const newButton1 = document.createElement('button');
        newButton1.type = 'button';
        newButton1.id = `MCS ${buttonText1} Button`;
        newButton1.className = 'btn btn-danger m-1 mdtbutton';
        newButton1.style.width = `50%`;
        newButton1.style.color = `#fff`;
        newButton1.textContent = buttonText1;
        newButton1.onclick = () => onclickCallback1(index);

        const newButton2 = document.createElement('button');
        newButton2.type = 'button';
        newButton2.id = `MCS ${buttonText2} Button`;
        newButton2.className = 'btn btn-danger m-1 mdtbutton';
        newButton2.style.width = `50%`;
        newButton2.style.color = `#fff`;
        newButton2.textContent = buttonText2;
        newButton2.onclick = () => onclickCallback2(index);

        const buttonContainer = document.createElement('div');
        buttonContainer.className = 'd-flex';
        buttonContainer.appendChild(newButton1);
        buttonContainer.appendChild(newButton2);
        this.container.appendChild(buttonContainer);
    }

    createCCContainer() {
        const newCCContainer = document.createElement('div');
        newCCContainer.className = 'mdtCCContainer';
        return newCCContainer;
    }

    createLabel(labelText, referenceID) {
        const newLabel = document.createElement('label');
        newLabel.className = 'mdtLabel';
        newLabel.textContent = labelText;
        newLabel.for = referenceID;
        return newLabel;
    }

    createImage(imageSrc, height) {
        const newImage = document.createElement('img');
        newImage.style.height = `${height}px`;
        newImage.src = imageSrc;
        return newImage;
    }

    addRadio(labelText, height, radioName, radioLabels, radioCallbacks, initialRadio, imageSrc = '') {
        const newCCContainer = this.createCCContainer();
        if (imageSrc && imageSrc !== '') {
            newCCContainer.appendChild(this.createImage(imageSrc, height));
        }
        newCCContainer.appendChild(this.createLabel(labelText, ''));
        newCCContainer.id = `MCS ${labelText} Radio Container`;
        const radioContainer = document.createElement('div');
        radioContainer.className = 'mcsRadioContainer';
        newCCContainer.appendChild(radioContainer);
        // Create Radio elements with labels
        for (let i = 0; i < radioLabels.length; i++) {
            radioContainer.appendChild(this.createRadio(radioName, radioLabels[i], `MCS ${labelText} Radio ${radioLabels[i]}`, initialRadio === i, radioCallbacks[i]));
        }
        this.container.appendChild(newCCContainer);
    }

    createRadio(radioName, radioLabel, radioID, checked, radioCallback) {
        const newDiv = document.createElement('div');
        newDiv.className = 'custom-control custom-radio custom-control-inline';
        const newRadio = document.createElement('input');
        newRadio.type = 'radio';
        newRadio.id = radioID;
        newRadio.name = radioName;
        newRadio.className = 'custom-control-input';
        if (checked) {
            newRadio.checked = true;
        }
        newRadio.addEventListener('change', radioCallback);
        newDiv.appendChild(newRadio);
        const label = this.createLabel(radioLabel, radioID);
        label.className = 'custom-control-label';
        label.setAttribute('for', radioID);
        newDiv.appendChild(label);
        return newDiv;
    }
}



//define GM_addStyle
if (typeof GM_addStyle == 'undefined') {
    this.GM_addStyle = (aCss) => {
        'use strict';
        let head = document.getElementsByTagName('head')[0];
        if (head) {
            let style = document.createElement('style');
            style.setAttribute('type', 'text/css');
            style.textContent = aCss;
            head.appendChild(style);
            return style;
        }
        return null;
    };
}

//CSS Style for the grid container
GM_addStyle ( "                                                   \
.mdtGridContainer {                                               \
display:                   grid;                                  \
grid-template-columns:     265px 265px 265px;                     \
grid-template-rows:        167px 167px 167px 167px 167px 167px;   \
column-gap:                5px;                                   \
row-gap:                   5px;                                   \
justify-content:           center;                                \
align-items:               center;                                \
}                                                                 \
.mdtmodal-content {                                               \
width:                     fit-content;                           \
padding:                   0px 10px;                              \
}                                                                 \
#mdtModal.show {                                                  \
display:                   flex !important;                       \
}                                                                 \
#mdtModal .modal-dialog {                                         \
max-width:                 95%;                                   \
display:                   inline-block;                          \
}                                                                 \
.mdtSectionTitle {                                                \
text-align:                center;                                \
font-size:                 18px;                                  \
max-width:                 240px;                                 \
margin-top:                0.25rem;                               \
margin-bottom:             0.25rem;                               \
}                                                                 \
.mdtNoSelect {                                                    \
user-select:               none;                                  \
}                                                                 \
.mdtOuter {                                                       \
padding:                   0.25rem;                               \
margin:                    0.25rem !important;                    \
min-width:                 250px;                                 \
}                                                                 \
.mdtNumberOutput {                                                \
text-align:                right;                                 \
border-bottom:             1px solid;                             \
line-height:               1.15;                                  \
padding:                   0 2px;                                 \
}                                                                 \
.mdtSectionTitle {                                                \
text-align:                center;                                \
font-size:                 18px;                                  \
max-width:                 240px;                                 \
margin-top:                0.25rem;                               \
margin-bottom:             0.25rem;                               \
}                                                                 \
.mdtLabel {                                                       \
text-align:                right;                                 \
margin-right:              4px;                                   \
margin-bottom:             0px;                                   \
white-space:               nowrap;                                \
font-weight:               unset;                                 \
}                                                                 \
.mdtCCContainer {                                                 \
display:                   flex;                                  \
align-items:               center;                                \
justify-content:           flex-end;                              \
line-height:               20px;                                  \
padding:                   0 0.25rem;                             \
}                                                                 \
.mdtbutton {                                                      \
display:                   none;                                  \
}                                                                 \
" );