DanielCausebrook / Habitica Decluttered Stable

// ==UserScript==
// @name        Habitica Decluttered Stable
// @description Changes Habitica's stable layout to be cleaner, and hides missing creatures by default.
// @author      Daniel Causebrook
// @copyright   2020, Daniel Causebrook
// @license     MIT
// @version     1.2
// @include     https://habitica.com/*
// ==/UserScript==

// Configure this script's behaviour here.
const USER_CONFIG = {

    STABLE: {

        /**
         * Enabling this sets the stable to hide missing creatures by default!
         */
        HIDE_MISSING: { ENABLED: true },

        /**
         * Enabling this tries to put creature types on one row if you have few
         *     enough of each.
         */
        COLLAPSE_ROWS: { ENABLED: true },

        /**
         * Enabling this will load all creatures automatically.
         *
         * It can also remove the "Show More" / "Show Less" buttons.
         *
         * Warning: this may cause performance issues (especially in Firefox). Test
         *     it out by navigating to and from the stable tab a few times.
         */
        EXPAND_ALL: {
            ENABLED: false,
            HIDE_BUTTONS: true,
        },

    },
};

const CREATURE_ROW_SELECTOR = "div.stable > div.standard-page > div:not(:first-child):not(.drawer-container)";

function addStyle(css) {
    var headElems = document.getElementsByTagName('head');
    if(headElems.length === 0) return;
    var style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = css;
    headElems[0].appendChild(style);
}

// Add flexbox CSS styles to page.
if(USER_CONFIG.STABLE.COLLAPSE_ROWS.ENABLED) {
    addStyle(`
    ` + CREATURE_ROW_SELECTOR + ` {
        display: flex;
        flex-flow: row wrap;
    }
    ` + CREATURE_ROW_SELECTOR + ` > h4 {
        width: 100%;
    }
    ` + CREATURE_ROW_SELECTOR + ` > div.btn-show-more {
        align-self: center;
        margin: 0 1.71em 12px 0;
        max-width: 100px;
    }
    `);
}

var clickedButtons = new Set();
var showMoreLabel = null;

/**
 * Clicks all "Show More" buttons, and hides all buttons.
 *
 * If a button label is not recognised, no action is taken. Tracks processed
 * nodes in `clickedButtons`, and will skip any that have already been checked.
 */
function clickButtons(buttons) {
    for(const btn of buttons) {
        if(!clickedButtons.has(btn)) {
            var btnText = btn.innerHTML.trim().toLowerCase();
            if(showMoreLabel === null) showMoreLabel = btnText;
            if(btnText === showMoreLabel) {
                btn.click();
                if(USER_CONFIG.STABLE.EXPAND_ALL.HIDE_BUTTONS) {
                    btn.style.display = "none";
                }
            } else {
                if(USER_CONFIG.STABLE.EXPAND_ALL.HIDE_BUTTONS) {
                    btn.style.display = "none";
                }
            }
            clickedButtons.add(btn);
        }
    }
}

var processing = false;
var hideMissingToggled = false;
var inStable = false;

/**
 * Checks the document for certain nodes in the stable and modifies them
 * if found.
 *
 * Will not run if called inside itself to avoid infinite recursion caused by
 *   being called inside itself.
 */
var callback = function (nutationList, observer) {
    if(!processing) {
        processing = true;

        if(window.location.pathname.match(/^\/inventory\/stable/) !== null) {
            // We are on the stable page.
            inStable = true;

            // Switches the "Hide Missing" toggle to on.
            if(USER_CONFIG.STABLE.HIDE_MISSING.ENABLED) {
                var stableHideMissing = document.querySelector("div.stable > div.standard-sidebar > div.form > div.form-group:last-child input");
                if(stableHideMissing !== null && !hideMissingToggled) {
                    stableHideMissing.checked = true;
                    stableHideMissing.dispatchEvent(new Event('change', {'bubbles': true}));
                    hideMissingToggled = true;
                }
            }

            // Expands all creature sections.
            if(USER_CONFIG.STABLE.EXPAND_ALL.ENABLED) {
                var stableShowMoreButtons = document.querySelectorAll(CREATURE_ROW_SELECTOR + " > div.btn-show-more");
                clickButtons(stableShowMoreButtons);
            }

        } else {
            if(inStable) {
                // We've just switched away from the stable page, reset state.
                hideMissingToggled = false;
                clickedButtons.clear();
                inStable = false;
            }
        }
        processing = false;
    }
}

// Setup a MutationObserver to watch the document for the nodes we need.
if(USER_CONFIG.STABLE.HIDE_MISSING.ENABLED ||
  USER_CONFIG.STABLE.EXPAND_ALL.ENABLED) {
    var targetNode = document.querySelector('body');
    var observerConfig = {
        childList: true,
        subtree: true,
    };
    var observer = new MutationObserver(callback);
    observer.observe(targetNode, observerConfig);
}