Camamba Chat Tweaks

tweaks layout of the chat

目前為 2020-12-23 提交的版本,檢視 最新版本

// ==UserScript==
// @name            Camamba Chat Tweaks
// @namespace       dannysaurus.camamba
// @version         0.1
// @description     tweaks layout of the chat
// @license         MIT License
// @include         https://www.camamba.com/chat/
// @include         https://www.de.camamba.com/chat/
// @grant           GM.getValue
// @grant           GM.setValue
// ==/UserScript==

/* jslint esnext: true */

(function() {
    'use strict';

    // --- initial sizes ---
    const SIZES = {
        FONT_EM: {
            userList: 1.2,
            chatBox: 1.8,
        },
        WIDTH_EM: {
            sidebarLeft: 10,
            sidebarRight: 14,
        },
    };


    // --- HTML Selector Helpers ---
    const containers = (() => {
        const SELECTORS = {
            ID: {
                userList: 'userList',
                chatBox: 'chatBox',
                chatWindow: 'chatWindow',
                mediaContainer1 : 'mediaContainer1',
                mediaContainer2 : 'mediaContainer2',
                mediaContainer3 : 'mediaContainer3',
                mediaContainer4 : 'mediaContainer4',
                mediaContainer5 : 'mediaContainer5',
                mediaContainer6 : 'mediaContainer6',
                mediaContainer7 : 'mediaContainer7',
                mediaContainer8 : 'mediaContainer8',
            },
            CLASS: {
                noTextSelect: 'noTextSelect',
                borderBox: 'borderBox',
                camBox: 'camBox'
            }
        };
        
        let userList, chatBox, sidebars, camslots;

        return {
            get userList() {
                if (typeof userList === "undefined") {
                    userList = document.getElementById(SELECTORS.ID.userList);
                }
                return userList;
            },
            get chatBox() {
                if (typeof chatBox === "undefined") {
                    chatBox = document.getElementById(SELECTORS.ID.chatBox);
                }
                return chatBox;
            },
            get sidebars() {
                if (typeof sidebars === "undefined") {
                    sidebars = document.getElementById(SELECTORS.ID.chatWindow).querySelectorAll(`.${SELECTORS.CLASS.noTextSelect}`);
                }
                return sidebars;
            },
            get sidebarLeft() {
                return this.sidebars[0];
            },
            get sidebarTop() {
                return this.sidebars[1];
            },
            get sidebarRight() {
                return this.sidebars[2];
            },
            get camslots() {
                 if (typeof camslots === "undefined") {
                   const parentContainers = [
                       SELECTORS.ID.mediaContainer1,
                       SELECTORS.ID.mediaContainer2,
                       SELECTORS.ID.mediaContainer3,
                       SELECTORS.ID.mediaContainer4,
                       SELECTORS.ID.mediaContainer5,
                       SELECTORS.ID.mediaContainer6,
                       SELECTORS.ID.mediaContainer7,
                       SELECTORS.ID.mediaContainer8,
                    ]
                        .map(id => document.getElementById(id))
                        .filter(el => el !== null)
                        .map(el => el.parentNode);

                     camslots = [ ...new Set(parentContainers)];
                }
                return camslots;
            }
        };
    })();
    
    // --- HTML Create Element Helpers ---
    const addNewInput = ({ parentElement, type, id, initialValue, labelText }) => {
        const div = parentElement.appendChild(document.createElement('div'));

        const input = document.createElement('input');
        input.type = type;
        input.id = id;
        input.style.backgroundColor = 'rgba(39,62,77,1)';

        const label = document.createElement('label');
        label.htmlFor = id;
        label.appendChild(document.createTextNode(labelText));

        div.appendChild(input);
        div.appendChild(label);
        return input;
    };

    const addNewInputPersistent = ({ parentElement, type, id, initialValue, labelText }) => {
        const input = addNewInput({ parentElement, type, id, initialValue, labelText });

        input.updateValue = () => GM.getValue(input.id, initialValue).then(value => {
            input.value = value;
        });

        input.addEventListener('input', () => {
            GM.setValue(input.id, input.value);
        });

        return input;
    };

    const addNewCheckbox = ({ parentElement, id, initialChecked, labelText }) => {
        const checkbox = addNewInput({ parentElement, type:'checkbox', id, initialValue: '', labelText });

        checkbox.updateValue = () => GM.getValue(checkbox.id, !!initialChecked).then(isChecked => {
            checkbox.checked = !!isChecked;
        });

        checkbox.addEventListener('click', () => {
            GM.setValue(checkbox.id, checkbox.checked);
        });

        return checkbox;
    };

    const addNewSpinner = ({ parentElement, id, initialValue, min, max, step, labelText }) => {
        const spinner = addNewInputPersistent({ parentElement, type:'number', id, initialValue, labelText });
        spinner.min = min;
        spinner.max = max;
        spinner.step = step;
        return spinner;
    };

    // --- Main ---
    const layoutPatcher = new class {
        constructor() {
            this.historyCamslotsRemoved = [];
        }

        initControls() {
            const sidebarLeftCenter = containers.sidebarLeft.children[1];
            sidebarLeftCenter.innerHTML = "";
            const container = sidebarLeftCenter.appendChild(document.createElement('div'));

            // checkbox camslots on/off
            const cbCamslots = addNewCheckbox({
                parentElement: container,
                id: 'cb-camslots',
                initialChecked: true,
                labelText: 'camslots'
            });

            const updateCbCamslots = () => {
                if (cbCamslots.checked) {
                    this.showCamslots();
                } else {
                    this.hideCamslots();
                }
            };
            cbCamslots.addEventListener('click', () => updateCbCamslots());
            cbCamslots.updateValue().then(() => updateCbCamslots());

            // spinner userlist font
            const spinnerUserlistFont = addNewSpinner({
                parentElement: container,
                id: 'spinner-userlist-font',
                initialValue: SIZES.FONT_EM.userList,
                min: 1.0,
                max: 2.2,
                step: 0.1,
                labelText: 'users'
            });

            const updateSpinnerUserlistFont = () => {
                const fontSize = `${spinnerUserlistFont.value}em`;
                this.setFontSizeOfUserList(fontSize);
            };
            spinnerUserlistFont.addEventListener('input', () => updateSpinnerUserlistFont());
            spinnerUserlistFont.updateValue().then(() => updateSpinnerUserlistFont());

            // spinner chat font
            const spinnerChatFont = addNewSpinner({
                parentElement: container,
                id: 'spinner-chat-font',
                initialValue: SIZES.FONT_EM.chatBox,
                min: 1.0,
                max: 2.2,
                step: 0.1,
                labelText: 'chat'
            });

            const updateSpinnerChatFont = () => {
                const fontSize = `${spinnerChatFont.value}em`;
                this.setFontSizeOfChat(fontSize);
            };
            spinnerChatFont.addEventListener('input', () => updateSpinnerChatFont());
            spinnerChatFont.updateValue().then(() => updateSpinnerChatFont());
       }

        patchSizes() {
            // this.setWidthOfSidebarLeft(`${SIZES.WIDTH_EM.sidebarLeft}em`);
            this.setWidthOfSidebarRight(`${SIZES.WIDTH_EM.sidebarRight}em`);
            return this;
        }

        setFontSizeOfUserList(fontSize) {
            containers.userList.style.fontSize = fontSize;
            return this;
        }

        setFontSizeOfChat(fontSize) {
            containers.chatBox.style.fontSize = fontSize;
            return this;
        }

        setWidthOfSidebarLeft(width) {
            containers.sidebarLeft.style.width = width;
            return this;
        }

        setWidthOfSidebarRight(width) {
            containers.sidebarLeft.style.width = width;
            return this;
        }

        showCamslots() {
            for (let i = 0; i < this.historyCamslotsRemoved.length; i++) {
                const { parent, index, element } = this.historyCamslotsRemoved.pop();
                parent.insertBefore(element, parent.children[index]);
            }
            return this;
        }

        hideCamslots() {
            for (let element of containers.camslots) {
                const parent = element.parentNode;
                if (parent) {
                    let index = Array.from(parent.children).indexOf(element);
                    parent.removeChild(element);

                    this.historyCamslotsRemoved.push({ parent, index, element });
                }
            }
            return this;
        }
    }();

    const hookIntoInitSettings = (onInitSettings, timeOutRetryMillis) => {
        if (typeof initSettings === "undefined") {
            setTimeout(() => {
                hookIntoInitSettings();
            }, timeOutRetryMillis);
        }

        /* eslint-disable no-undef */
        const originalInitSettings = initSettings;
        initSettings = () => {
            /* eslint-enable no-undef */
            originalInitSettings();
            onInitSettings();
        };
    };

    hookIntoInitSettings(() => {
        layoutPatcher.patchSizes();
    }, 350);

    layoutPatcher.patchSizes();
    layoutPatcher.initControls();
})();