NovelAI Toggle Quick Start Gallery

Script to toggle the display of the Quick Start Gallery in NovelAI

// ==UserScript==
// @name         NovelAI Toggle Quick Start Gallery
// @name:ja      NovelAI Toggle Quick Start Gallery
// @namespace    https://github.com/NeviumX/NovelAI-Toggle-Quick-Start-Gallery
// @version      1.0.2
// @description  Script to toggle the display of the Quick Start Gallery in NovelAI
// @description:ja NovelAIのクイックスタートギャラリーの表示/非表示を切り替えるスクリプト
// @author       Nevium7
// @copyright    Nevium7
// @license      MIT
// @match        https://novelai.net/*
// @icon         https://novelai.net/icons/novelai-round.png
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// ==/UserScript==

GM_addStyle(`
    #nai-quickstart-gallery-switcher {
        display: flex;
        align-items: center;
        justify-content: flex-start;
        padding: 5px 10px;
        width: 100%;
        box-sizing: border-box;
        border-bottom: 1px solid #22253f;
    }
    #nai-quickstart-gallery-switcher span.qsg-label {
        margin-right: 8px;
        font-size: 0.875rem;
        font-weight: 500;
        color: #C5C5C5;
        user-select: none;
    }
    .qsg-switch {
        position: relative;
        display: inline-block;
        width: 47px;
        height: 20px;
        flex-shrink: 0;
    }
    .qsg-switch input {
        opacity: 0;
        width: 0;
        height: 0;
    }
    .qsg-slider {
        position: absolute;
        cursor: pointer;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: #0e0f21;
        transition: .4s;
        border-radius: 3px;
    }
    .qsg-slider:before {
        position: absolute;
        content: '×';
        height: 16px;
        width: 26px;
        left: 2px;
        bottom: 2px;
        background-color: #22253f;
        transition: .4s;
        border-radius: 3px;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 12px;
        font-weight: bold;
        color: #91929f;
    }
    input:checked + .qsg-slider {
        background-color: #f5f3c2;
    }
    input:checked + .qsg-slider:before {
        background-color: #0e0f21;
        transform: translateX(17px);
        content: '✓';
        color: #f5f3c2;
        font-size: 10px;
    }
`);

const QUICK_START_GALLERY_VISIBILITY_TRG = 'naiQuickStartGalleryVisibility';

class UIManager {
    constructor(root) {
        this.switch = this.injectUI(root);
        this.isHidden = GM_getValue(QUICK_START_GALLERY_VISIBILITY_TRG, false);
        if (this.switch) {
            this.switch.querySelector('input[type="checkbox"]').checked = this.isHidden;
        }
        this.toggleVisibility(this.isHidden);
    }
    injectUI(root) {
        const switcherID = 'nai-quickstart-gallery-switcher';
        if (root.querySelector(`#${switcherID}`)) return;
        if (root) {
            const switcher = this.createSwitcher();
            switcher.id = switcherID;
            //root.insertAdjacentElement('afterend', switcher);
            root.prepend(switcher);
            return switcher;
        }
    }
    destroy() {
        if (!this.switch) return;
        this.toggleVisibility(false);
        if (this.switch.isConnected) this.switch.remove();
        this.switch = null;
    }
    createSwitcher() {
        const container = document.createElement('div');

        const labelSpan = document.createElement('span');
        labelSpan.textContent = 'Hide Quick Start Gallery';
        labelSpan.className = 'qsg-label';
        container.appendChild(labelSpan);

        const switchLabel = document.createElement('label');
        switchLabel.className = 'qsg-switch';

        const input = document.createElement('input');
        input.type = 'checkbox';
        
        const slider = document.createElement('span');
        slider.className = 'qsg-slider';

        input.addEventListener('change', (e) => {
            this.isHidden = e.target.checked;
            this.toggleVisibility(this.isHidden);
            GM_setValue(QUICK_START_GALLERY_VISIBILITY_TRG, this.isHidden);
        });

        switchLabel.appendChild(input);
        switchLabel.appendChild(slider);
        container.appendChild(switchLabel);

        return container;
    }
    toggleVisibility(isHidden) {
        if (!this.switch || !this.switch.parentElement) {
            return;
        }
        const rootElement = this.switch.parentElement;
        const displayStyle = isHidden ? 'none' : '';
        rootElement.querySelectorAll(':scope > div').forEach(childDiv => {
            if (childDiv.id !== 'nai-quickstart-gallery-switcher') {
                childDiv.style.display = displayStyle;
                //console.log(`Quick Start Gallery visibility set to: ${isHidden ? 'hidden' : 'visible'}`);
            }
        });
    }
}

class QuickStartGalleryObserver {
    constructor() {
        this.map = new Map();
        this.mo = new MutationObserver((muts) => this.handle(muts));
    }
    start() {
        this.mo.observe(document.documentElement,{childList:true,subtree:true});
    }
    attach(root) {
        const prev = this.map.get(root);
        if (prev && !prev.switch?.isConnected) {
            prev.destroy();
            this.map.delete(root);
        }
        if (!this.map.has(root)) {
            const ui = new UIManager(root);
            this.map.set(root, ui);
        }
        //console.log('Attached Quick Start Gallery switcher.');
    }
    detach(root) {
        const switcher = this.map.get(root);
        if (!switcher) return;
        switcher.destroy();
        this.map.delete(root);
        //console.log('Detached Quick Start Gallery switcher.');
    }
    handle(muts) {
        muts.forEach(m=> {
            m.addedNodes.forEach(n=> {
                if (n.nodeType !== 1) return;
                n.querySelectorAll?.('.quickstart-gallery').forEach(el=> this.attach(el));
                if(n.classList?.contains('quickstart-gallery')) this.attach(n);
            });
            m.removedNodes.forEach(n=> {
                if (n.nodeType !== 1) return;
                n.querySelectorAll?.('.quickstart-gallery').forEach(el=> this.detach(el));
                if(n.classList?.contains('quickstart-gallery')) this.detach(n);
            });
        });
    }
}

(function() {
    'use strict';
    const observer = new QuickStartGalleryObserver();
    observer.start();
})();