WK Extra study mover

Moves the WaniKani extra study box below the level progress

// ==UserScript==
// @name         WK Extra study mover
// @namespace    http://tampermonkey.net/
// @version      0.8.7
// @description  Moves the WaniKani extra study box below the level progress
// @author       Gorbit99
// @include      /^https?:\/\/(www\.)?(preview\.)?wanikani\.com\/?(dashboard)?$/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=wanikani.com
// @grant        none
// @license      MIT
// @require      https://greasyfork.org/scripts/441792-cidwwa/code/CIDWWA.js?version=1270274
// @require      https://greasyfork.org/scripts/489759-wk-custom-icons/code/CustomIcons.js?version=1344498
// @run-at       document-end
// ==/UserScript==

(() => {
    "use strict";

    const headerObserverConfig = {
        childList: true,
    };

    const headerObserver = new MutationObserver((mutationList) => {
        for (const mutation of mutationList) {
            for (const added of mutation.addedNodes) {
                if (added.id === "LnRHeader") {
                    const progress = document.querySelector(".wk-panel--level-progress");
                    const recentMistakes = document.querySelector(".wk-panel--recent-mistakes");
                    const extraStudy = document.querySelector(".wk-panel--extra-study");
                    const dashboard = document.querySelector(".progress-and-forecast");
                    dashboard.style.gridTemplateRows = "auto auto auto auto";
                    switch (settings.position) {
                        case "none":
                            progress.style.gridRow = settings.goodMojo == "yes" ? "1/5" : "2/5";
                            recentMistakes.style.gridRow = "1";
                            break;
                        case "top":
                            recentMistakes.style.gridRow = "3";
                            extraStudy.style.setProperty("grid-row", settings.goodMojo == "yes" ? "1/4" : "1/3", "important");
                            break;
                        case "above-rm":
                            recentMistakes.style.gridRow = "3";
                            extraStudy.style.setProperty("grid-row", settings.goodMojo == "yes" ? "1/4" : "1/3", "important");
                            break;
                        case "above-lp":
                            recentMistakes.style.gridRow = "1";
                            extraStudy.style.setProperty("grid-row", settings.goodMojo == "yes" ? "1/4" : "2/4", "important");
                            break;
                        case "below-lp":
                            progress.style.gridRow = settings.goodMojo == "yes" ? "1/4" : "2 / 4";
                            recentMistakes.style.gridRow = "1";
                            break;
                        case "above-ib":
                            progress.style.gridRow = settings.goodMojo == "yes" ? "1/4" : "2 / 4";
                            recentMistakes.style.gridRow = "1";
                            break;
                        case "below-ib":
                            progress.style.gridRow = settings.goodMojo == "yes" ? "1/4" : "2 / 4";
                            recentMistakes.style.gridRow = "1";
                            break;
                        case "above-rf":
                            progress.style.gridRow = settings.goodMojo == "yes" ? "1/4" : "3/4";
                            recentMistakes.style.gridRow = "1/3";
                            break;
                        case "below-rf":
                            progress.style.gridRow = settings.goodMojo == "yes" ? "1/5" : "2/5";
                            recentMistakes.style.gridRow = "1";
                            break;
                        case "header":
                            progress.style.gridRow = settings.goodMojo == "yes" ? "1/4" : "2 / 4";
                            recentMistakes.style.gridRow = "1";
                            break;
                    }
                    headerObserver.disconnect();
                }
            }
        }
    });

    const gearIcon = `
    <svg id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M424.5,216.5h-15.2c-12.4,0-22.8-10.7-22.8-23.4c0-6.4,2.7-12.2,7.5-16.5l9.8-9.6c9.7-9.6,9.7-25.3,0-34.9l-22.3-22.1  c-4.4-4.4-10.9-7-17.5-7c-6.6,0-13,2.6-17.5,7l-9.4,9.4c-4.5,5-10.5,7.7-17,7.7c-12.8,0-23.5-10.4-23.5-22.7V89.1  c0-13.5-10.9-25.1-24.5-25.1h-30.4c-13.6,0-24.4,11.5-24.4,25.1v15.2c0,12.3-10.7,22.7-23.5,22.7c-6.4,0-12.3-2.7-16.6-7.4l-9.7-9.6  c-4.4-4.5-10.9-7-17.5-7s-13,2.6-17.5,7L110,132c-9.6,9.6-9.6,25.3,0,34.8l9.4,9.4c5,4.5,7.8,10.5,7.8,16.9  c0,12.8-10.4,23.4-22.8,23.4H89.2c-13.7,0-25.2,10.7-25.2,24.3V256v15.2c0,13.5,11.5,24.3,25.2,24.3h15.2  c12.4,0,22.8,10.7,22.8,23.4c0,6.4-2.8,12.4-7.8,16.9l-9.4,9.3c-9.6,9.6-9.6,25.3,0,34.8l22.3,22.2c4.4,4.5,10.9,7,17.5,7  c6.6,0,13-2.6,17.5-7l9.7-9.6c4.2-4.7,10.2-7.4,16.6-7.4c12.8,0,23.5,10.4,23.5,22.7v15.2c0,13.5,10.8,25.1,24.5,25.1h30.4  c13.6,0,24.4-11.5,24.4-25.1v-15.2c0-12.3,10.7-22.7,23.5-22.7c6.4,0,12.4,2.8,17,7.7l9.4,9.4c4.5,4.4,10.9,7,17.5,7  c6.6,0,13-2.6,17.5-7l22.3-22.2c9.6-9.6,9.6-25.3,0-34.9l-9.8-9.6c-4.8-4.3-7.5-10.2-7.5-16.5c0-12.8,10.4-23.4,22.8-23.4h15.2  c13.6,0,23.3-10.7,23.3-24.3V256v-15.2C447.8,227.2,438.1,216.5,424.5,216.5z M336.8,256L336.8,256c0,44.1-35.7,80-80,80  c-44.3,0-80-35.9-80-80l0,0l0,0c0-44.1,35.7-80,80-80C301.1,176,336.8,211.9,336.8,256L336.8,256z"/></svg>
    `;

    headerObserver.observe(document.head, headerObserverConfig);

    let settings = {
        position: "below-lp",
        style: "normal",
        questionmarks: "yes",
        orientation: "horizontal",
        goodMojo: "no",
    };

    const modal = window.createModal({
        title: "Extra Study Mover Settings",
    });

    const iconStyle = document.createElement("style");
    document.head.append(iconStyle);
    iconStyle.innerHTML = `
      .es-mover-settings__gear svg {
        width: 1.5em;
      }
    `;

    function loadSettings() {
        const storedSettings = window.localStorage.getItem("esMover.settings");
        if (storedSettings) {
            settings = JSON.parse(storedSettings);
            settings.questionmarks ??= "yes";
            settings.orientation ??= "horizontal";
            settings.goodMojo ??= "no";
        }
    }

    function saveSettings() {
        window.localStorage.setItem("esMover.settings", JSON.stringify(settings));
    }

    function injectChanges() {
        if (document.querySelector(".wk-panel--extra-study").childElementCount === 0) {
            requestAnimationFrame(injectChanges);
            return;
        }
        changeOrientation();
        changePosition();
        createSettingsButton();
        populateSettingsModal();
        changeStyling();

        if (settings.questionmarks === "no") {
            removeQuestionmarks();
        }
    }

    function changeStyling() {
        const extraStudy = document.querySelector(".wk-panel--extra-study");
        if (settings.position !== "header") {
            const title = extraStudy.querySelector("h2");
            switch (settings.style) {
                case "normal":
                    break;
                case "only-text":
                    extraStudy.querySelector("img").parentNode.remove();
                    extraStudy.querySelector(".extra-study__info").style.flexBasis = "100%";
                    break;
                case "only-image":
                    extraStudy.querySelector(".extra-study__info p").remove();
                    break;
                case "reduced":
                    extraStudy.querySelector(".extra-study__info p").remove();
                    extraStudy.querySelector("img").parentNode.remove();
                    extraStudy.querySelector(".extra-study__info").style.flexBasis = "100%";
                    break;
                case "minimal": {
                    const items = extraStudy.querySelector(".extra-study__buttons");
                    title.after(items);
                    items.classList.remove("w-full");
                    extraStudy.querySelector(".extra-study").remove();
                    title.parentNode.style.display = "flex";
                    title.parentNode.style.flexDirection = "row";
                    title.parentNode.style.alignItems = "center";
                    title.style.marginBottom = "0";
                    break;
                }
            }
        }
        document.querySelector(".wk-panel--extra-study")
            .classList.add(`es-mover-${settings.style}`);
    }

    function changeOrientation() {
        if (settings.orientation === "horizontal") {
            const extraStudyList = document.querySelector(".extra-study__buttons");
            extraStudyList.style.flexDirection = "row";
            extraStudyList.style.display = "flex";

            const orientationStyleElement = document.createElement("style");
            document.head.append(orientationStyleElement);
            orientationStyleElement.innerHTML = `
         .extra-study__buttons > div{
          margin-bottom: 0 !important;
          margin-top: 0 !important;
          min-width: 0;
          flex: 1 1 0;
          position: relative;
        }

        .extra-study-button__link {
          width: 100%;
        }

        .extra-study__info {
          width: 100%;
        }

        .extra-study-button__tooltip-button {
          position: absolute;
          right: -0.95em;
          bottom: 0.05em;
        }
        `;
            }
        }

    function changePosition() {
        const container = document.querySelector(".progress-and-forecast");
        const extraStudy = document.querySelector(".wk-panel--extra-study");
        const dashboardProgress = document.querySelector(".wk-panel--level-progress");
        const lAndR = document.querySelector(".lessons-and-reviews");
        const srsProgress = document.querySelector(".srs-progress");
        const reviewForecast = document.querySelector(".wk-panel--review-forecast");
        const recentMistakes = document.querySelector(".wk-panel--recent-mistakes");
        extraStudy.classList.add("es-mover");

        if (settings.goodMojo == "yes") {
            recentMistakes.style.display = "none";
        }

        switch (settings.position) {
            case "top":
                container.style.gridTemplateRows = "auto auto auto";
                extraStudy.style.setProperty("grid-row", "1", "important");
                lAndR.style.gridRow = settings.goodMojo == "yes" ? "2/4" : "2/3";
                lAndR.style.marginBottom = "0";
                recentMistakes.style.gridRow = 3;
                break;
            case "above-rm":
                extraStudy.style.setProperty("grid-row", settings.goodMojo == "yes" ? "2/4" : "2", "important");
                recentMistakes.style.gridRow = 3;
                break;
            case "above-lp":
                extraStudy.style.setProperty("grid-row", settings.goodMojo == "yes" ? "2/4" : "3", "important");
                break;
            case "below-lp":
                container.style.marginBottom = "30px";
                dashboardProgress.style.gridRow = settings.goodMojo == "yes" ? "2/4" : "3";
                dashboardProgress.style.marginBottom = "0";
                extraStudy.style.setProperty("grid-row", "4", "important");
                extraStudy.style.marginBottom = "0";
                reviewForecast.style.marginBottom = "0";
                break;
            case "above-ib":
                container.style.gridTemplateRows = "auto auto";
                dashboardProgress.style.gridRow = settings.goodMojo == "yes" ? "2/4" : "3";
                dashboardProgress.style.marginBottom = "0";
                extraStudy.style.marginBottom = "30px";
                lAndR.style.gridRow = "1";
                lAndR.style.marginBottom = "0";
                srsProgress.before(extraStudy);
                break;
            case "below-ib":
                container.style.gridTemplateRows = "auto auto";
                dashboardProgress.style.gridRow = settings.goodMojo == "yes" ? "2/4" : "3";
                dashboardProgress.style.marginBottom = "0";
                extraStudy.style.marginBottom = "30px";
                lAndR.style.gridRow = "1";
                lAndR.style.marginBottom = "0";
                srsProgress.after(extraStudy);
                break;
            case "above-rf":
                container.style.gridTemplateRows = "auto auto auto";
                dashboardProgress.style.gridRow = settings.goodMojo == "yes" ? "2/4" : "3";
                dashboardProgress.style.marginBottom = "0";
                extraStudy.style.gridColumn = "5/span 2";
                extraStudy.style.setProperty("grid-row", "1", "important");
                extraStudy.style.marginBottom = "0";
                lAndR.style.gridRow = "1";
                lAndR.style.marginBottom = "0";
                reviewForecast.style.gridRow = "2/5";
                break;
            case "below-rf":
                container.style.gridTemplateRows = "auto auto";
                dashboardProgress.style.gridRow = settings.goodMojo == "yes" ? "2/5" : "3/5";
                extraStudy.style.gridColumn = "5/span 2";
                extraStudy.style.setProperty("grid-row", "4", "important");
                extraStudy.style.marginBottom = "30px";
                lAndR.style.gridRow = "1";
                lAndR.style.marginBottom = "0";
                reviewForecast.style.gridRow = "1/4";
                reviewForecast.style.marginBottom = "0";
                break;
            case "none": {
                container.style.gridTemplateRows = "auto auto";
                dashboardProgress.style.gridRow = settings.goodMojo == "yes" ? "2/5" : "3/5";
                extraStudy.style.display = "none";
                lAndR.style.gridRow = "1";
                lAndR.style.marginBottom = "0";
                const menus = document.querySelectorAll(".sitemap__section--subsection");
                const settingsMenu = menus[menus.length - 1];
                settingsMenu.querySelector(".sitemap__pages").innerHTML += `
          <li class="sitemap__page" style="cursor: pointer">
            <a class="es-mover-settings">Extra Study Position</a>
          </li>
        `;
                break;
            }
            case "header": {
                const button = window.createButton({
                    englishText: "Extra Study",
                    japaneseText: "エクストラ",
                    color: "#ffb13d",
                    hoverColor: "#ffbf5e",
                    withDropdown: {
                        bgColor: "#f4f4f4",
                    },
                });

                const buttonDropdownStyle = `
          position: relative;
          color: black;
          overflow: visible;
        `;

                const settingsGearStyle = `
          position: absolute;
          top: 2px;
          right: 10px;
          color: var(--color-text);
        `;

                const buttonsContainerStyle = `
          display: flex;
          flex-direction: column;
          padding: 26px 12px 12px;
          gap: 12px;
          margin: 0;
          color: white;
          list-style-type: none;
        `;

                button.setDropdownContent(`
            <div
              class="wk-panel--extra-study es-mover es-mover-header"
              style="${buttonDropdownStyle}"
            >
              <span
                class="es-mover-settings cursor-pointer"
                style="${settingsGearStyle}"
              >
                <span class="es-mover-settings__gear">
                  ${gearIcon}
                </span>
              </span>
              <div style="${buttonsContainerStyle}" class="extra-study-buttons">
              </div>
            </div>
          `);
                const buttonContainer = document.querySelector(".extra-study-buttons");
                [...document.querySelectorAll(".extra-study__buttons > div")]
                    .forEach((button) => {
                    buttonContainer.append(button);
                });

                buttonContainer.classList.add("extra-study__buttons");

                const audioQuizSwitch = document.querySelector("#audio-quiz-switch");
                if (audioQuizSwitch) {
                    buttonContainer.after(
                        document.querySelector("[for=audio-quiz-switch]")
                    );
                    buttonContainer.after(audioQuizSwitch);
                }

                const style = document.createElement("style");
                style.innerHTML = ".wk-panel--extra-study ul > div {margin: 0;}";
                document.head.append(style);

                extraStudy.remove();
                dashboardProgress.style.gridRow = settings.goodMojo == "yes" ? "2/4" : "3";
                dashboardProgress.style.marginBottom = "0";

                break;
            }
        }

        document.querySelector(".wk-panel--extra-study")
            .classList.add(`es-mover-${settings.position}`);
    }

    function createSettingsButton() {
        if (settings.position !== "header" && settings.position !== "none") {
            const title = document.querySelector(".wk-panel--extra-study h2");
            title.style.marginBottom = "0";

            title.outerHTML = `
      <div style="display: flex; justify-content: space-between; align-items: center; width: 100%; margin-bottom: var(--spacing-tight)">
        ${title.outerHTML}
        <div class="es-mover-settings" style="cursor: pointer">
          <span class="es-mover-settings__gear">${gearIcon}</span>
        </div>
      </div>
    `;
        }

        const settingsButton = document.querySelector(".es-mover-settings");
        settingsButton.addEventListener("click", () => modal.toggle());
    }

    function populateSettingsModal() {
        const positionOptions = [
            { value: "top", label: "Top" },
            { value: "above-rm", label: "Above Recent Mistakes" },
            { value: "above-lp", label: "Above Level Progress" },
            { value: "below-lp", label: "Below Level Progress" },
            { value: "above-ib", label: "Above Item Breakdown" },
            { value: "below-ib", label: "Below Item Breakdown" },
            { value: "above-rf", label: "Above Review Forecast" },
            { value: "below-rf", label: "Below Review Forecast" },
            { value: "header", label: "In the header" },
            { value: "none", label: "None" },
        ];

        const positionOptionsHTML = positionOptions.map(option =>
                                                        `<option
      value="${option.value}" 
      ${option.value == settings.position ? "selected" : ""}
    >
      ${option.label}
    </option>`)
        .join("");

        const styleOptions = [
            { value: "normal", label: "Normal" },
            { value: "only-text", label: "Only Text" },
            { value: "only-image", label: "Only Image" },
            { value: "reduced", label: "Reduced" },
            { value: "minimal", label: "Minimal" },
        ];

        const styleOptionsHTML = styleOptions.map(option =>
                                                  `<option
      value="${option.value}" 
      ${option.value == settings.style ? "selected" : ""}
    >
      ${option.label}
    </option>`)
      .join("");

        const questionmarkOptions = [
            { value: "yes", label: "Yes" },
            { value: "no", label: "No" },
        ];

        const questionmarkOptionsHTML = questionmarkOptions.map(option =>
                                                                `<option
      value="${option.value}" 
      ${option.value == settings.questionmarks ? "selected" : ""}
    >
      ${option.label}
    </option>`)
      .join("");

        const orientationOptions = [
            { value: "horizontal", label: "Horizontal" },
            { value: "vertical", label: "Vertical" },
        ];

        const orientationOptionsHTML = orientationOptions.map(option =>
                                                              `<option
      value="${option.value}" 
      ${option.value == settings.orientation ? "selected" : ""}
    >
      ${option.label}
    </option>`)
      .join("");

        const goodMojoOptions = [
            { value: "no", label: "No" },
            { value: "yes", label: "Yes" },
        ];

        const goodMojoOptionsHTML = goodMojoOptions.map(option =>
                                                              `<option
      value="${option.value}"
      ${option.value == settings.goodMojo ? "selected" : ""}
    >
      ${option.label}
    </option>`)
      .join("");

        const buttonsStyle = `
    margin-top: 20px;
    display: flex;
    justify-content: center;
    gap: 3em;
  `;

        modal.setContent(`
    <div class="es-mover-settings-items">
      <div class="control-group flag">
        <label class="flag__item">Extra study panel position</label>
        <select 
          class="flag__asset position-select" 
          value="${settings.position}"
        >
          ${positionOptionsHTML}
        </select>
      </div>
      <div class="control-group flag">
        <label class="flag__item">Extra study style</label>
        <select class="flag__asset style-select" value="${settings.style}">
          ${styleOptionsHTML}
        </select>
      </div>
      <div class="control-group flag">
        <label class="flag__item">Include questionmarks</label>
        <select 
          class="flag__asset questionmark-select" 
          value="${settings.questionmarks}"
        >
          ${questionmarkOptionsHTML}
        </select>
      </div>
      <div class="control-group flag">
        <label class="flag__item">Button orientation</label>
        <select 
          class="flag__asset orientation-select" 
          value="${settings.orientation}"
        >
          ${orientationOptionsHTML}
        </select>
      </div>
      <div class="control-group flag">
        <label class="flag__item">Good Mojo</label>
        <select
          class="flag__asset good-mojo-select"
          value="${settings.goodMojo}"
        >
          ${goodMojoOptionsHTML}
        </select>
      </div>
      <div class="buttons" style="${buttonsStyle}">
        <button class="btn btn-cancel">Cancel</button>
        <button class="btn btn-ok">Ok</button>
      </div>
    </div>
  `);
        const settingsItems = document.querySelector(".es-mover-settings-items");
        const buttons = settingsItems.querySelector(".buttons");

        const positionSelect = settingsItems.querySelector(".position-select");
        const styleSelect = settingsItems.querySelector(".style-select");
        const questionmarkSelect =
              settingsItems.querySelector(".questionmark-select");
        const orientationSelect =
              settingsItems.querySelector(".orientation-select");
        const goodMojoSelect =
              settingsItems.querySelector(".good-mojo-select");

        const okButton = buttons.querySelector(".btn-ok");
        const cancelButton = buttons.querySelector(".btn-cancel");

        okButton.addEventListener("click", () => {
            settings.position = positionSelect.value;
            settings.style = styleSelect.value;
            settings.questionmarks = questionmarkSelect.value;
            settings.orientation = orientationSelect.value;
            settings.goodMojo = goodMojoSelect.value;
            saveSettings();
            location.reload();
        });

        cancelButton.addEventListener("click", () => {
            positionSelect.value = settings.position;
            styleSelect.value = settings.style;
            questionmarkSelect.value = settings.questionmarks;
            orientationSelect.value = settings.orientation;
            goodMojoSelect.value = settings.goodMojo;
            modal.close();
        });
    }

    function removeQuestionmarks() {
        [...document.querySelectorAll(".extra-study-button__tooltip-button")]
            .forEach((button) => {
            button.style.display = "none";
            //button.querySelector(":scope > div").remove();
            //button.querySelector("a, button").style.paddingRight = "12px";
        });
    }

    loadSettings();
    injectChanges();
})();