WOD 道具分组

Add item group feature

// ==UserScript==
// @name            WOD 道具分组
// @icon            http://info.world-of-dungeons.org/wod/css/WOD.gif
// @namespace       ttang.tw
// @grant           none
// @author          Taylor Tang
// @description     Add item group feature
// @include         *://*.world-of-dungeons.org/wod/spiel/hero/items.php*
// @modifier        Christophero
// @version         2022.08.21.2
// ==/UserScript==

(function(){
    const LANGUAGE = Object.freeze({
        COIN: '金币',
        ITEM: '物品',
        GROUP_ITEM: '团队物品',
        SELL: '出售',
        POSITION: '位置',
        WAREHOUSE: '仓库',
        GROUP_WAREHOUSE2: '团队仓库',
        GROUP_WAREHOUSE: '宝库',
        STORAGE_ROOM: '贮藏室',
        OWNER: '所有者',
        STORAGE_DATE: '入库时间',
        DROP_DATE: '掉落',
        MARKET: '市集',
        LOWEST_PRIZE: '最低价格',
        SELLER_COMMENT: '卖家注释',
        TRANSFER_TO: '移交给',
    });

    function insertCss() {
        var style = document.createElement('style');
        var row0 = document.querySelector('tr.row0');
        var row1 = document.querySelector('tr.row1');
        if(row0) {
            document.head.appendChild(style);
            var row0_color = getComputedStyle(row0).getPropertyValue('background-color');
            var row1_color = getComputedStyle(row1).getPropertyValue('background-color');
            style.sheet.insertRule('table.content_table .hidden_row { display: none; }', 0);
            style.sheet.insertRule('table.content_table .group_child > td:nth-child(1):before { content: "\u21D2"; }', 0);
            style.sheet.insertRule('table.content_table > tbody > :nth-child(2n) { background-color: ' + row1_color + '; }', 0);
            style.sheet.insertRule('table.content_table > tbody > :nth-child(2n+1) { background-color: ' + row0_color + '; }', 0);
        }
    }

    function setSelectValue(select, value) {
        if(select.querySelector('[value="' + value + '"]')) {
            select.value = value;
            return true;
        }
        return false;
    }

    function chomp(str) {
        var regex = new RegExp('[ ' + String.fromCharCode(8593) + String.fromCharCode(8595) + '\xA0\t\n\r]|&\w+;', 'g');
        return str.replace(regex, '');
    }

    function findTable() {
        function findSiblings(el) {
            if(!el) { return false; }
            var next = el.nextElementSibling;
            if(!next) { return null; }
            if(next.tagName.toLowerCase() === 'table' && next.classList.contains('content_table')) {
                return next;
            } else {
                return findSiblings(next);
            }
        }
        var search = document.querySelector('.search_container');
        return findSiblings(search);
    }

    function Item(row, table_map) {
        var head = row.parentNode.parentNode.tHead.querySelector('.header');
        var name = row.cells[1];
        var use = name.textContent.match(/\((\d+)\/\d+\)$/);

        this.el = row;
        for(var i = 0; i < table_map.length; i++) {
            if( table_map[i] in this.parser) {
                this.parser[table_map[i]].call(this, i);
            }
        }
    }

    Item.prototype.parser = Object.freeze({
        sell: function(idx) {
            var price = this.el.cells[idx];
            if(!price) { return false; }
            this.price = Number(price.textContent || 0);
            this.sell_checkbox = price.querySelector('input');
        },
        item: function(idx) {
            var item = this.el.cells[idx];
            var use = item.textContent.match(/\((\d+)\/\d+\)$/);
            item = item.querySelector('a');

            this.item = item.textContent.replace(/!$/, '');
            this.use = use ? Number(use[1]) : 1;
            this.item_useability = item.className;
            this.href = item.href;
        },
        owner: function(idx) {
            var owner = this.el.cells[idx];
            this.owner = owner.textContent;
        },
        group_item: function(idx) {
            var el = this.el.cells[idx];
            this.group_item_checkbox = el.querySelector('input[name^="SetGrpItem"]');
        },
        position: function(idx) {
            var el = this.el.cells[idx];
            this.item_position_select = el.querySelector('select');
        },
        storage_date: function(idx) {
            var storage_date = this.el.cells[idx];
            this.storage_date = storage_date.textContent;
        },
        drop_date: function(idx) {
            var drop_date = this.el.cells[idx];
            this.drop_date = drop_date.textContent;
        }
    });

    function ItemGroup(table, prime_key) {
        function useGetter() {
            var use = this.child.map((k) => (k.use));
            return use.reduce((sum, value) => (sum + value));
        }

        function priceGetter() {
            return this.child.map((k) => (k.price)).reduce((sum, value) => (sum + value));
        }

        function rowGetter() {
            if(!row) {
                row = this.createContainer();
            }
            return row;
        }

        var row;

        this.pk = prime_key;
        this.tbody = table.tBodies[0];
        this.thead = table.tHead.rows[1];
        this.child = [];

        Object.defineProperty(this, 'row', {
            get: rowGetter.bind(this),
            writeable: false
        });
        Object.defineProperty(this, 'use', {
            get: useGetter.bind(this),
            writeable: false
        });

        Object.defineProperty(this, 'price', {
            get: priceGetter.bind(this),
            writeable: false
        });
    }

    ItemGroup.prototype.add = function(item) {
        this.child.push(item);
        function setupSyncEvent(key) {
            if(!item[key]) { return false; }
            if(item[key].tagName.toLowerCase() === 'select') {
                item[key].addEventListener('change', (function(){
                    this.syncSelect(key);
                }).bind(this));
            } else {
                item[key].addEventListener('change', (function(){
                    this.syncCheckbox(key);
                }).bind(this));
            }
        }
        setupSyncEvent.call(this, 'sell_checkbox');
        setupSyncEvent.call(this, 'group_item_checkbox');
        setupSyncEvent.call(this, 'item_position_select');
    };

    ItemGroup.prototype.renderDefault = function(cell, idx, align) {
        var div = document.createElement('div');
        div.textContent = this.child[0].el.cells[idx].textContent;
        cell.style.textAlign = align || 'center';
        cell.appendChild(div);
    };

    ItemGroup.prototype.syncSelect = function(key) {
        var el = this[key];
        var prev, now, select;
        if(!el) { return false; }
        for(var i = 0; i < this.child.length; i++) {
            select = this.child[i][key];
            now = select.options[select.selectedIndex].value;
            if(typeof prev === 'undefined') {
                prev = now;
            } else if(prev != now){
                el.value = '0';
                return false;
            }
        }
        if(!setSelectValue(el, now.replace(/^-/, ''))) {
            el.value = '0';
        }
    };

    ItemGroup.prototype.syncCheckbox = function(key) {
        var el = this[key];
        var prev, now, check;
        if(!el) { return false; }
        for(var i = 0; i < this.child.length; i++) {
            check = this.child[i][key];
            if(!check) { return false; }
            now = check.checked;
            if(typeof prev === 'undefined') {
                prev = now;
            } else if(prev != now){
                return false;
            }
        }
        el.checked = now;
    };

    ItemGroup.prototype.renderDropDate = function(cell, idx) {
        this.renderDefault(cell, idx);
        if(this.pk != 'drop_date') {
            cell.children[0].style.visibility = 'hidden';
        }
    };

    ItemGroup.prototype.renderItemPosition = function(position, idx) {
        function newOps(txt, value) {
            var opt = document.createElement('option');
            opt.textContent = txt;
            opt.value = value;
            return opt;
        }
        var select = document.createElement('select');
        var head_cell = this.thead.cells[idx];
        var head_select = head_cell.querySelector('select');

        if(head_select) {
            position.style.textAlign = 'right';
            position.style.paddingRight = '23px';
            select.appendChild(newOps('-------', '0'));
            select.appendChild(newOps(LANGUAGE.WAREHOUSE, 'go_lager'));
            select.appendChild(newOps(LANGUAGE.GROUP_WAREHOUSE2, 'go_group_2'));
            select.appendChild(newOps(LANGUAGE.GROUP_WAREHOUSE, 'go_group'));
            select.appendChild(newOps(LANGUAGE.STORAGE_ROOM, 'go_keller'));
            select.addEventListener('change', (function(e){
                var value = e.target.options[e.target.selectedIndex].value;
                this.child.forEach(function(c){
                    if(c.item_position_select) {
                        setSelectValue(c.item_position_select, value);
                        setSelectValue(c.item_position_select, '-' + value);
                    }
                });
            }).bind(this));
            position.appendChild(select);
            this.item_position_select = select;
            head_select.addEventListener('change', (function() {
                this.syncSelect('item_position_select');
            }).bind(this));
        }
    };

    ItemGroup.prototype.renderItemPrice = function(price, idx) {
        var text = document.createElement('span');
        var checkbox = document.createElement('input');
        var head_cell = this.thead.cells[idx];
        price.style.textAlign = 'right';
        price.classList.add('small');
        text.textContent = this.price;
        checkbox.type = 'checkbox';
        checkbox.addEventListener('change', (function(e){
            var checked = e.target.checked;
            this.child.forEach(function(c) {
                if(c.sell_checkbox) {
                    c.sell_checkbox.checked = checked;
                }
            });
        }).bind(this));
        price.appendChild(text);
        price.appendChild(checkbox);
        this.sell_checkbox = checkbox;

        var head_checkbox = head_cell.querySelector('input[type="checkbox"]');
        if(head_checkbox){
            head_checkbox.addEventListener('change', (function() {
                this.syncCheckbox('sell_checkbox');
            }).bind(this));
        }
    };

    ItemGroup.prototype.renderGroupSetter = function(cell, idx) {
        var setter = document.createElement('input');
        var head_cell = this.thead.cells[idx];
        cell.style.textAlign = 'center';
        setter.type = 'checkbox';
        setter.addEventListener('change', (function(e){
            var checked = e.target.checked;
            this.child.forEach(function(c){
                if(c.group_item_checkbox) {
                    c.group_item_checkbox.checked = checked;
                }
            });
        }).bind(this));
        cell.appendChild(setter);
        this.group_item_checkbox = setter;
        head_cell.querySelector('input[type="checkbox"]').addEventListener('change', (function() {
            this.syncCheckbox('group_item_checkbox');
        }).bind(this));
    };

    ItemGroup.prototype.renderItemName = function(name) {
        if(this.pk === 'item') {
            var a = document.createElement('a');
            a.innerHTML = '&#128194 ';
            a.textContent += this.child[0].item + ' (' + this.use + ')';
            a.className = this.child[0].item_useability;
            a.addEventListener('click', (e) => {
                wo(this.child[0].href);
                e.stopPropagation();
            });
            name.appendChild(a);
        } else {
            name.innerHTML = '&#128194 ' + this.child.length + ' items';
        }
    };

    ItemGroup.prototype.toggleChild = function() {
        if(this.child.length <= 1) { return false; }
        if(this.child[0].el.classList.contains('hidden_row')) {
            this.expand();
        } else {
            this.shrink();
        }
    };

    ItemGroup.prototype.createContainer = function() {
        var row = this.tbody.insertRow();
        row.className = 'item_group';
        row.style.cursor = 'pointer';
        row.addEventListener('click', (function(e) {
            var tag = e.target.tagName.toLowerCase();
            if(tag != 'input' && tag != 'select' && tag != 'option') {
                this.toggleChild();
            }
        }).bind(this));
        return row;
    };

    ItemGroup.prototype.parseCell = function(type, idx) {
        var cell = this.row.insertCell();
        switch(type) {
            case 'index':
                this.renderDefault(cell, idx, 'right');
                break;
            case 'item':
                this.renderItemName(cell);
                break;
            case 'sell':
                this.renderItemPrice(cell, idx);
                break;
            case 'position':
                this.renderItemPosition(cell, idx);
                break;
            case 'group_item':
                this.renderGroupSetter(cell, idx);
                break;
            case 'drop_date':
                this.renderDropDate(cell, idx);
                break;
            case 'owner':
            case 'storage_date':
                if(this.pk === type) {
                    this.renderDefault(cell, idx);
                }
            break;
        }
    };

    ItemGroup.prototype.render = function(table_map) {
        if(this.child.length > 1) {
            this.renderGroup(table_map);
        } else {
            this.tbody.appendChild(this.child[0].el);
        }
    };
    ItemGroup.prototype.renderGroup = function(table_map) {
        for(var i = 0; i < table_map.length; i++) {
            this.parseCell(table_map[i], i);
        }
        this.syncSelect('item_position_select');
        this.syncCheckbox('group_item_checkbox');
        this.syncCheckbox('sell_checkbox');
        this.child.reverse();
    };

    ItemGroup.prototype.shrink = function() {
        var table_body = this.row.parentNode;
        if(this.child.length > 1) {
            this.child.forEach((c) => {
                c.el.remove();
                table_body.appendChild(c.el);
                c.el.classList.add('hidden_row');
            });
        }
    };

    ItemGroup.prototype.expand = function() {
        var table_body = this.row.parentNode;
        if(this.child.length > 1) {
            if(this.row) {
                this.child.forEach((c) => {
                    c.el.remove();
                    table_body.insertBefore(c.el, this.row.nextSibling);
                    c.el.classList.remove('hidden_row');
                    c.el.classList.add('group_child');
                });
            }
        }
    };
    function indexOf(obj, value) {
        var tmp = Object.entries(LANGUAGE);
        var idx = tmp.findIndex(function(pair){
            return pair[1] === value;
        });
        if(idx > 0) {
            return tmp[idx][0];
        } else {
            return '';
        }
    }

    function init() {
        var table = findTable();
        if(!(table && table.tHead && table.tBodies[0].rows.length > 1)) { return false; }

        function parseRow(item_db, table) {
            var row = table.tBodies[0].rows[0];
            var item = new Item(row, table_map);
            if(!item_db[item[group_by]]) {
                item_db[item[group_by]] = new ItemGroup(table, group_by);
            }
            item_db[item[group_by]].add(item);
        }

        function parseTableIndex() {
            var arr =  [];
            function parseCell(head_cell) {
                var btn, title = (btn = head_cell.querySelector('input[type="submit"]')) ? btn.value : head_cell.textContent;
                title = chomp(title);
                return indexOf(LANGUAGE, title).toLowerCase();
            }
            for(var i = 0; i < head.children.length; i++) {
                arr.push(parseCell(head.children[i]));
            }
            arr[0] = 'index';
            return arr;
        }

        function parseTable(table) {
            var item_db = {};
            var tbody = table.tBodies[0];
            while(tbody.rows[0]) {
                parseRow(item_db, table);
                tbody.rows[0].remove();
            }
            return item_db;
        }

        function initItemGroup(item_db) {
            var index = Number(item_db[Object.keys(item_db)[0]].child[0].el.cells[0].textContent);
            for(var k in item_db) {
                if(item_db.hasOwnProperty(k)){
                    item_db[k].child.forEach((c) => (c.el.cells[0].textContent = index++));
                    item_db[k].render(table_map);
                }
            }
            for(k in item_db) {
                if(item_db.hasOwnProperty(k)){
                    item_db[k].toggleChild();
                }
            }
        }

        function addToggleBtn(table, db) {
            var container =  document.createElement('span');
            var text_expand = document.createElement('a');
            var text_shrink = document.createElement('a');
            text_expand.textContent = 'Expand All';
            text_shrink.textContent = 'Shrink All';
            text_expand.style.display = 'inline-block';
            text_shrink.style.display = 'none';

            container.style.cursor = 'pointer';
            container.appendChild(text_expand);
            container.appendChild(text_shrink);
            table.tHead.rows[0].cells[0].appendChild(container);

            text_expand.addEventListener('click', function(e) {
                text_expand.style.display = 'none';
                text_shrink.style.display = 'inline-block';
                for(k in item_db) {
                    if(item_db.hasOwnProperty(k)){
                        item_db[k].expand();
                    }
                }
            });
            text_shrink.addEventListener('click', function(e) {
                text_expand.style.display = 'inline-block';
                text_shrink.style.display = 'none';
                for(k in item_db) {
                    if(item_db.hasOwnProperty(k)){
                        item_db[k].shrink();
                    }
                }
            });
        }


        var head = table.tHead.rows[1];
        var table_map = parseTableIndex(head);
        var group_by = indexOf(LANGUAGE, chomp(head.querySelector('.table_hl_sorted').value)).toLowerCase();

        if(/item|owner|storage_date|drop_date/.test(group_by)) {
            insertCss();
            var item_db = parseTable(table);
            initItemGroup(item_db);
            addToggleBtn(table, item_db);
        }
    }
    init();
})();