ac-rating-icon

Add icons to the AtCoder standings table according to ratings.

2022-04-28 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

// ==UserScript==
// @name        ac-rating-icon
// @namespace   https://su8ru.dev/
// @version     1.0.0
// @description Add icons to the AtCoder standings table according to ratings.
// @author      subaru <contact@su8ru.dev>
// @supportURL  https://github.com/su8ru/ac-rating-icon/issues
// @license     MIT
// @match       https://atcoder.jp/*
// @exclude     https://atcoder.jp/*/json
// ==/UserScript==

// ================================================
//   View source code before bundling on GitHub:
//   https://github.com/su8ru/ac-rating-icon
// ================================================
const isElementWithVue = (element) => Object.prototype.hasOwnProperty.call(element, "__vue__");

const isString = (value) => typeof value === "string";
const isNumber = (value) => typeof value === "number";
const isBoolean = (value) => typeof value === "boolean";
const isObject = (value) => typeof value === "object" && value !== null;

const isVueWithUserInfo = (vue) => isObject(vue.u) &&
    isNumber(vue.u.Rating) &&
    isBoolean(vue.u.IsTeam) &&
    isString(vue.u.UserScreenName);

const icons = [
    '<svg width="16" height="16" viewBox="0 0 8.47 8.47" xmlns="http://www.w3.org/2000/svg"><circle style="fill:currentColor;stroke:none;fill-opacity:1" cx="4.23" cy="4.23" r="2.5"/></svg>',
    '<svg width="16" height="16" viewBox="0 0 8.47 8.47" xmlns="http://www.w3.org/2000/svg"><g style="fill:currentColor;fill-opacity:1;stroke:none"><circle cx="2.35" cy="2.32" r="1.5"/><circle cx="6.12" cy="6.15" r="1.5"/><path d="m2.7 1.97-.7.7L5.75 6.5l.71-.7Z"/></g></svg>',
    '<svg width="16" height="16" viewBox="0 0 8.47 8.47" xmlns="http://www.w3.org/2000/svg"><g style="fill:currentColor;fill-opacity:1;stroke:none"><circle cx="2.29" cy="2.51" r="1.5"/><circle cx="6.18" cy="2.53" r="1.5"/><circle cx="4.32" cy="5.96" r="1.5"/><path d="m1.4 2 .46.76 2.48 4.21 2.67-4.93Zm1.77 1.01 2.17.02L4.3 4.94Z"/></g></svg>',
    '<svg width="16" height="16" viewBox="0 0 8.47 8.47" xmlns="http://www.w3.org/2000/svg"><g style="fill:currentColor;fill-opacity:1;stroke:none"><circle cx="2.32" cy="2.27" r="1.5"/><circle cx="6.15" cy="2.27" r="1.5"/><circle cx="2.32" cy="6.19" r="1.5"/><circle cx="6.15" cy="6.19" r="1.5"/><path d="M1.82 1.77V6.7h4.83V1.77Zm1 1h2.83V5.7H2.82Z"/></g></svg>',
];

const createIconElement = (iconSvg) => {
    const template = document.createElement("template");
    template.innerHTML = iconSvg;
    return template.content.firstChild;
};
const updateStandings = () => {
    Array.from(document.querySelectorAll(".standings-username > a.username")).map((userElement) => {
        var _a;
        if (isElementWithVue(userElement) &&
            isVueWithUserInfo(userElement.__vue__) &&
            !userElement.__vue__.u.IsTeam &&
            !userElement.querySelector("img") &&
            !userElement.querySelector("svg")) {
            const rating = userElement.__vue__.u.Rating;
            const rank = rating > 2800 ? 0 : ((rating % 400) / 100) | 0;
            const iconElement = createIconElement(icons[rank]);
            // set style
            const colorClassName = (_a = userElement.querySelector("span")) === null || _a === void 0 ? void 0 : _a.className;
            iconElement.setAttribute("class", colorClassName !== null && colorClassName !== void 0 ? colorClassName : "");
            iconElement.style.verticalAlign = "text-bottom";
            iconElement.style.marginRight = "2px";
            // insert
            userElement.insertBefore(iconElement, userElement.querySelector("span"));
        }
    });
};

var _a;
const observeTable = () => {
    var _a;
    updateStandings();
    const tableElement = (_a = document.getElementById("standings-tbody")) === null || _a === void 0 ? void 0 : _a.parentElement;
    if (tableElement)
        new MutationObserver(updateStandings).observe(tableElement.tBodies[0], {
            childList: true,
        });
};
if (/standings(\/virtual)?\/?/.test(document.location.href)) {
    const loaded = () => !!document.getElementById("standings-tbody");
    const loadingElement = (_a = document
        .getElementById("vue-standings")) === null || _a === void 0 ? void 0 : _a.getElementsByClassName("loading-show")[0];
    if (loadingElement)
        new MutationObserver(() => {
            if (loaded())
                observeTable();
        }).observe(loadingElement, { attributes: true });
}