Infinite Worlds tweaks

Keeps the story illustrations onscreen, story keyboard navigation (1-5 for prompts, "a" and "s" to swap images, "e" to edit images), "ESC" can be used to close singe-button dialogs, adds arrow key navigation to community worlds, widens "Illustration details" window, and closes Discord popup. Keyboard navigation only works when the focus is not on an "input" element (use "ESC" to leave input focus).

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Infinite Worlds tweaks
// @namespace    http://tampermonkey.net/
// @version      1.8
// @description  Keeps the story illustrations onscreen, story keyboard navigation (1-5 for prompts, "a" and "s" to swap images, "e" to edit images), "ESC" can be used to close singe-button dialogs, adds arrow key navigation to community worlds, widens "Illustration details" window, and closes Discord popup.  Keyboard navigation only works when the focus is not on an "input" element (use "ESC" to leave input focus).
// @author       HiEv
// @license      MIT
// @match        https://infiniteworlds.app/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=infiniteworlds.app
// @grant        none
// @require      http://code.jquery.com/jquery-3.7.1.min.js
// ==/UserScript==

/*
	global $
*/

(function() {
	'use strict';

    // Styling tweaks
    $('html > head').append($(
        '<style>\n' + // Illustration image tweaks.
        '.anvil-panel-section .has-components .anvil-spacing-above-small[style="min-height: 20px; text-align: center; overflow: clip;"] > img {\n' +
        '    transition: margin-top 0.5s ease-out;\n' +
        '    width: auto !important;\n' +
        '    max-height: calc(100vh - 47px) !important;\n' +
        '}\n' +
        '@media (min-width: 768px) {\n' + // Widen the "Illustration details" window.
        '    .modal-sm {\n' +
        '        width: 800px;\n' +
        '        max-width: 90vw;\n' +
        '    }\n' +
        '}</style>'));

    // Keyboard navigation tweaks
    setTimeout(() => {
        $(document).on("keyup", function (event) { // Keyboard handler
            if (($("input:focus").length === 0) && ($("textarea:focus").length === 0) && ($("div[contenteditable='true']:focus").length == 0)) {
                if (event.key === "ArrowLeft") { // Community Worlds navigate previous
                    $('[data-page="prev"]').last().trigger("click");
                }
                if (event.key === "ArrowRight") { // Community Worlds navigate next
                    $('[data-page="next"]').last().trigger("click");
                }
                if (event.key === "e") { // Open the edit dialog
                    if ($("div.modal.alert-modal").length == 0) {
                        let links = $('[title="View/modify image instructions"] button');
                        if (links.length >= 1) {
                            links[0].click();
                        }
                    } else { // Close the edit dialog
                        $("button.close").click();
                    }
                }
                if (event.key === "a") { // Swap to previous image
                    let links = $('div[title="Previous image"] button');
                    if (links.length >= 1) {
                        links[0].click();
                    }
                }
                if (event.key === "s") { // Swap to next image
                    // let links = $('button').children("i.fa-rotate");  // old version
                    let links = $('div[title="Next image"] button');
                    if (links.length >= 1) {
                        // $(links[0]).parent().click();  // old version
                        links[0].click();
                    }
                }
                if (event.key === "1") { // Option #1
                    let links = $('[anvil-role="option-button"] button');
                    if (links.length >= 1) {
                        links[0].click();
                    }
                }
                if (event.key === "2") { // Option #2
                    let links = $('[anvil-role="option-button"] button');
                    if (links.length >= 2) {
                        links[1].click();
                    }
                }
                if (event.key === "3") { // Option #3
                    let links = $('[anvil-role="option-button"] button');
                    if (links.length >= 3) {
                        links[2].click();
                    }
                }
                if (event.key === "4") { // Action input
                    let links = $('textarea');
                    if (links.length >= 1) {
                        links[0].focus();
                    }
                }
                if (event.key === "5") { // AI input
                    let links = $('textarea');
                    if (links.length >= 2) {
                        links[1].focus();
                    }
                }
                if (event.key === "Escape") { // Close dialog with one option
                    if ($('[class="modal-content"] button:visible').length === 1) {
                        $('[class="modal-content"] button:visible').click();
                    }
                }
            } else { // Focus is on an input.
                if (event.key === "Escape") {
                    document.activeElement.blur(); // Remove input focus.
                }
            }
        });
    }, 500);

    // Illustration positioning tweaks
    let check = 0;
    let skip = 0;
    setInterval(() => { // Periodically check for updates.
        let imageContainers = $("div.column-panel.col-padding-huge").filter(function () { return $(this).find("img").length && !$(this).hasClass("anvil-role-full-height-form"); });
        if (imageContainers.length > 0) { // If we're in a story with images...
            ++check;
            let img = $(imageContainers[0]).find("img"); // This should be the illustration image.
            let textTop = Math.ceil($(".anvil-role-full-height-form > div").filter(function () { return $(this).find(img).length; }).position().top);
            let pos = Math.max(0, $("html").scrollTop() - textTop);
            let mtop = parseFloat(img.css("margin-top"));
            let diff = pos - mtop;
            let limit = Infinity;
            let turnOld = img.data("turn");
            let turnCur = $(".anvil-role-secondary-text .label-text").filter(function () { return $(this).text().includes("Turn number"); }).text(); // Get turn text.
            if (turnOld != turnCur) { // Different turn, so set the turn and clear the limit.
                img.data("turn", turnCur);
                img.removeData("limit");
                skip = 3; // Skip the next three limit update checks.
            } else {
                if (diff < -80) { // Scrolled up, so clear the limit.
                    img.removeData("limit");
                } else {
                    limit = img.data("limit") ?? Infinity;
                }
            }
            let loaded = $("i.left.fa-redo:visible").length > 0 || $("i.left.fa-floppy-o:visible").length > 0;
            if (!loaded) {
                limit = Infinity;
            }
            if ((pos <= limit || mtop < limit) && Math.abs(diff) >= 4) {
                // If (the target position isn't greater than the limit or the current top is less than the limit) and the target position is more than 4 pixels different from the current position, then update the position.
                let footer = $('[anvil-role="footer-panel"]').offset();
                img.css({
                    marginTop: Math.min(pos, limit)
                });
                if (loaded && skip-- <= 0 && diff > 0) { // If not a recently new turn and scrolled down...
                    setTimeout(() => { // ...wait to see if the footer is moved by repositioning the image.
                        if ($('[anvil-role="footer-panel"]').offset()?.top > (footer?.top ?? Infinity) + 4) { // If the footer moved down by more than 4 pixels, then we moved the image down too much.
                            let newLimit = Math.round(parseFloat(img.css("margin-top")) + footer.top - $('[anvil-role="footer-panel"]').offset().top);
                            img.data("limit", newLimit).css("margin-top", newLimit); // Set the limit and fix the position.
                        }
                    }, 100);
                }
            }
        }

        // Popup tweaks
        if ($('.modal-content a[href="https://discord.gg/eJKbKwdArY"]').length > 0) { // Close Discord popup.
            $(".modal-content .btn-success").click();
        }
    }, 300);
})();