hx0 / Timus Charts

'use strict';

// ==UserScript==
// @name         Timus Charts
// @namespace    timus_charts
// @description  Adds charts to Timus Online Judge profiles
// @copyright    Alexander Borzunov, 2012-2013, 2015-2016
// @version      1.6
// @license      MIT
// @icon         http://acm.timus.ru/favicon.ico
// @downloadURL  https://openuserjs.org/install/hx0/Timus_Charts.user.js
// @updateURL    https://openuserjs.org/install/hx0/Timus_Charts.user.js
// @match        http://acm.timus.ru/author.aspx*
// @match        http://acm-judge.urfu.ru/author.aspx*
// @match        http://timus.online/author.aspx*
// @match        https://acm.timus.ru/author.aspx*
// @match        https://acm-judge.urfu.ru/author.aspx*
// @match        https://timus.online/author.aspx*
// @grant        GM_getValue
// @grant        GM_setValue
// @require      http://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.2/jquery.min.js
// @require      http://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.8/jquery.jqplot.min.js
// @require      http://cdnjs.cloudflare.com/ajax/libs/jqPlot/1.0.8/plugins/jqplot.dateAxisRenderer.min.js
// ==/UserScript==

var SCRIPT_VERSION = '1.6';
var CACHE_VERSION = 2;
"use strict";

var isGreasemonkey = typeof GM_getValue !== "undefined" && typeof GM_setValue !== "undefined";
var isChrome = typeof chrome !== "undefined";

function getValue(key) {
    var value;
    if (isGreasemonkey) value = GM_getValue(key);else value = localStorage[key];

    if (value === undefined) throw new Error("Storage doesn't contain this key");
    return value;
}

function setValue(key, value) {
    try {
        if (isGreasemonkey) GM_setValue(key, value);else localStorage[key] = value;
    } catch (err) {}
}
'use strict';

// jquery.jqplot.min.css
/* jqPlot 1.0.8r1250 | (c) 2009-2013 Chris Leonello | jplot.com
   jsDate | (c) 2010-2013 Chris Leonello */
var JQPLOT_STYLE = '.jqplot-target{position:relative;color:#666;font-family:"Trebuchet MS",Arial,Helvetica,sans-serif;font-size:1em;}.jqplot-axis{font-size:.75em;}.jqplot-xaxis{margin-top:10px;}.jqplot-x2axis{margin-bottom:10px;}.jqplot-yaxis{margin-right:10px;}.jqplot-y2axis,.jqplot-y3axis,.jqplot-y4axis,.jqplot-y5axis,.jqplot-y6axis,.jqplot-y7axis,.jqplot-y8axis,.jqplot-y9axis,.jqplot-yMidAxis{margin-left:10px;margin-right:10px;}.jqplot-axis-tick,.jqplot-xaxis-tick,.jqplot-yaxis-tick,.jqplot-x2axis-tick,.jqplot-y2axis-tick,.jqplot-y3axis-tick,.jqplot-y4axis-tick,.jqplot-y5axis-tick,.jqplot-y6axis-tick,.jqplot-y7axis-tick,.jqplot-y8axis-tick,.jqplot-y9axis-tick,.jqplot-yMidAxis-tick{position:absolute;white-space:pre;}.jqplot-xaxis-tick{top:0;left:15px;vertical-align:top;}.jqplot-x2axis-tick{bottom:0;left:15px;vertical-align:bottom;}.jqplot-yaxis-tick{right:0;top:15px;text-align:right;}.jqplot-yaxis-tick.jqplot-breakTick{right:-20px;margin-right:0;padding:1px 5px 1px 5px;z-index:2;font-size:1.5em;}.jqplot-y2axis-tick,.jqplot-y3axis-tick,.jqplot-y4axis-tick,.jqplot-y5axis-tick,.jqplot-y6axis-tick,.jqplot-y7axis-tick,.jqplot-y8axis-tick,.jqplot-y9axis-tick{left:0;top:15px;text-align:left;}.jqplot-yMidAxis-tick{text-align:center;white-space:nowrap;}.jqplot-xaxis-label{margin-top:10px;font-size:11pt;position:absolute;}.jqplot-x2axis-label{margin-bottom:10px;font-size:11pt;position:absolute;}.jqplot-yaxis-label{margin-right:10px;font-size:11pt;position:absolute;}.jqplot-yMidAxis-label{font-size:11pt;position:absolute;}.jqplot-y2axis-label,.jqplot-y3axis-label,.jqplot-y4axis-label,.jqplot-y5axis-label,.jqplot-y6axis-label,.jqplot-y7axis-label,.jqplot-y8axis-label,.jqplot-y9axis-label{font-size:11pt;margin-left:10px;position:absolute;}.jqplot-meterGauge-tick{font-size:.75em;color:#999;}.jqplot-meterGauge-label{font-size:1em;color:#999;}table.jqplot-table-legend{margin-top:12px;margin-bottom:12px;margin-left:12px;margin-right:12px;}table.jqplot-table-legend,table.jqplot-cursor-legend{background-color:rgba(255,255,255,0.6);border:1px solid #ccc;position:absolute;font-size:.75em;}td.jqplot-table-legend{vertical-align:middle;}td.jqplot-seriesToggle:hover,td.jqplot-seriesToggle:active{cursor:pointer;}.jqplot-table-legend .jqplot-series-hidden{text-decoration:line-through;}div.jqplot-table-legend-swatch-outline{border:1px solid #ccc;padding:1px;}div.jqplot-table-legend-swatch{width:0;height:0;border-top-width:5px;border-bottom-width:5px;border-left-width:6px;border-right-width:6px;border-top-style:solid;border-bottom-style:solid;border-left-style:solid;border-right-style:solid;}.jqplot-title{top:0;left:0;padding-bottom:.5em;font-size:1.2em;}table.jqplot-cursor-tooltip{border:1px solid #ccc;font-size:.75em;}.jqplot-cursor-tooltip{border:1px solid #ccc;font-size:.75em;white-space:nowrap;background:rgba(208,208,208,0.5);padding:1px;}.jqplot-highlighter-tooltip,.jqplot-canvasOverlay-tooltip{border:1px solid #ccc;font-size:.75em;white-space:nowrap;background:rgba(208,208,208,0.5);padding:1px;}.jqplot-point-label{font-size:.75em;z-index:2;}td.jqplot-cursor-legend-swatch{vertical-align:middle;text-align:center;}div.jqplot-cursor-legend-swatch{width:1.2em;height:.7em;}.jqplot-error{text-align:center;}.jqplot-error-message{position:relative;top:46%;display:inline-block;}div.jqplot-bubble-label{font-size:.8em;padding-left:2px;padding-right:2px;color:rgb(20%,20%,20%);}div.jqplot-bubble-label.jqplot-bubble-label-highlight{background:rgba(90%,90%,90%,0.7);}div.jqplot-noData-container{text-align:center;background-color:rgba(96%,96%,96%,0.3);}';

var EXTENSION_STYLE = '#chart {\n    width: 100%;\n}\n\n.chart_box {\n    height: 255px;\n    position: relative;\n}\n\n.chart_comment {\n    color: #555;\n    font-size: 15;\n}\n\n#chart_loading_error_judge_id {\n    margin-top: 5px;\n}\n\n.chart_judge_id_input {\n    height: 20px;\n    width: 80px;\n}\n\n.chart_legend_box {\n    font-size: 15;\n    margin: -5px 7px 10px 25px;\n    min-height: 20px;\n    overflow: auto;\n}\n\n.chart_users_table {\n    border-spacing: 0;\n    font-size: 15;\n}\n\n.chart_users_table td {\n    padding: 0 3px;\n}\n\n.chart_users_table td:first-child {\n    padding-left: 2px;\n}\n\n.chart_users_table td:last-child {\n    padding-right: 2px;\n}\n\n.chart_legend_open {\n    float: right;\n}\n\n.chart_spin {\n    position: relative;\n    top: 130px;\n}\n\n#chart_error {\n    clear: right;\n    position: relative;\n    top: 40%;\n}\n\n.chart_new_user {\n    margin-top: 5px;\n}\n\n.chart_user_add {\n    margin-left: 8px;\n}\n\n.chart_user_color {\n    border: 1px solid black;\n    float: left;\n    height: 11px;\n    width: 11px;\n}\n\n#chart_new_user_color {\n    border-style: dashed;\n    cursor: pointer;\n    margin: 2px;\n    margin-right: 7px;\n}\n\n.chart_user_judge_id {\n    color: #707070;\n}\n\n.chart_user_problems_count {\n    color: #707070;\n    text-align: right;\n}\n\n.chart_legend {\n    border: 1px solid #1a5cc8;\n    float: right;\n    margin-bottom: 10px;\n    padding: 5px;\n    text-align: left;\n}\n\n.chart_toggle {\n    display: inline-block;\n    margin-top: 15px;\n}\n\n.chart_user_remove {\n    float: right;\n}\n\n.chart_version {\n    float: right;\n}\n\n.chart_copyright {\n    position: absolute;\n    bottom: 10px;\n    right: 0;\n}';

function addStyles(observer) {
    observer.forEach('head', function () {
        var _arr = [JQPLOT_STYLE, EXTENSION_STYLE];

        for (var _i = 0; _i < _arr.length; _i++) {
            var style = _arr[_i];
            var elem = document.createElement('style');
            elem.textContent = style;
            document.head.appendChild(elem);
        }
    });
}
"use strict";

var LOCALES = {
    "en": {
        add: "Add",
        addUsers: "Add users",
        author: "Alexander Borzunov",
        del: "Delete",
        hideChart: "Hide chart",
        judgeIDDoesntExist: "This user doesn't exist!",
        judgeIDNotEnoughOfAccepted: "The user must have at least two solved problems!",
        judgeIDIncorrectFormat: "Incorrect Judge ID format (there's no digits)!",
        judgeIDIsAlreadyAdded: "This Judge ID has already been added!",
        judgeIDLabel: "Judge ID or link:",
        queryFailed: "An error occured on the request to the server.",
        refreshPage: "Try to refresh the page.",
        showChart: "Show chart",
        version: "version",
        wrongJudgeID: "There's no submits on this Judge ID",
        highlightLastSolvedProblems: "Mark recent ACs",
        notEnoughData: "Too little data for the chart"
    },
    "ru": {
        add: "Добавить",
        addUsers: "Добавить пользователей",
        author: "Александр Борзунов",
        del: "Удалить",
        hideChart: "Скрыть график",
        judgeIDDoesntExist: "Такого пользователя не существует!",
        judgeIDNotEnoughOfAccepted: "Пользователь должен иметь не менее двух решённых задач!",
        judgeIDIncorrectFormat: "Некорректный формат Judge ID (нет цифр)!",
        judgeIDIsAlreadyAdded: "Этот Judge ID уже присутствует на графике!",
        judgeIDLabel: "Judge ID или ссылка:",
        queryFailed: "Произошла ошибка при запросе к серверу. ",
        refreshPage: "Попробуйте обновить страницу.",
        showChart: "Показать график",
        version: "версия",
        wrongJudgeID: "Не найдено посылок по этому Judge ID",
        highlightLastSolvedProblems: "Выделять недавние AC",
        notEnoughData: "Слишком мало данных для графика"
    }
};

var locale = LOCALES.en;

function updateLocale(observer, callback) {
    observer.forEachTextIn('.panel a[href="/news.aspx"]', function (newsLabel) {
        locale = LOCALES[newsLabel.textContent === 'Site news' ? 'en' : 'ru'];
        callback();
    });
}
'use strict';

function substTemplateVariables(template, variables) {
    for (var name in locale) {
        template = template.replace(new RegExp('\{% locale.' + name + ' %\}', 'g'), locale[name]);
    }for (var _name in variables) {
        template = template.replace(new RegExp('\{% ' + _name + ' %\}', 'g'), variables[_name]);
    }return template;
}

var COLOR_GREEN = '#4f4';
var COLOR_RED = '#f99';
var COLOR_BLUE = '#88f';

var TEMPLATE_TOGGLE_LINK = '<br /><a href="#" class="chart_toggle">{% label %}</a>';

var TEMPLATE_USER_BEGIN = '<tr id="{% row_id %}">\n<td><div class="chart_user_color" style="background: {% color %};"></div></td>\n<td class="chart_user_judge_id">{% judge_id %}</td>\n<td>{% name %}</td>';
var TEMPLATE_USER_SEVERAL_LINES = '<td class="chart_user_problems_count">{% problems_count %}</td>\n<td><a href="#" class="chart_user_remove">{% locale.del %}</a></td>';
var TEMPLATE_USER_END = '</tr>';

var TEMPLATE_CHART = '<div id="chart_place">\n<div id="chart_loading" class="chart_box">\n    <div class="chart_comment chart_version">\n        Timus Charts, {% locale.version %} ' + SCRIPT_VERSION + '\n    </div>\n    <div class="chart_comment chart_copyright">&copy; {% locale.author %}</div>\n\n    <div class="chart_spin"></div>\n    <div id="chart_error" class="chart_comment" style="display: none;"></div>\n</div>\n<div id="chart" class="chart_box" style="display: none;"></div>\n<div class="chart_legend_box">\n    <a href="#" class="chart_legend_open" style="display: none;">\n        {% locale.addUsers %}\n    </a>\n    <div class="chart_legend" style="display: none;">\n        <table class="chart_users_table"></table>\n        <div class="chart_new_user">\n            <div id="chart_new_user_color" class="chart_user_color" style="background: ' + COLOR_BLUE + ';"></div>\n            {% locale.judgeIDLabel %} <input type="text" class="chart_judge_id_input" />\n            <a href="#" class="chart_user_add">{% locale.add %}</a>\n            <div id="chart_loading_error_judge_id" class="chart_comment" style="display: none;"></div>\n        </div>\n    </div>\n</div>\n</div>';
'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var ELEMENT_HANDLER_CLASS_PREFIX = 'tc__observer-element-';
var TEXT_HANDLER_CLASS_PREFIX = 'tc__observer-text-';

var ElementObserver = function () {
    _createClass(ElementObserver, null, [{
        key: '_invokeHandlers',
        value: function _invokeHandlers(handlers, classPrefix, matchedElement, passedNode) {
            handlers.forEach(function (_ref, i) {
                var selector = _ref.selector,
                    handler = _ref.handler;

                if (matchedElement.matches(selector) && !matchedElement.classList.contains(classPrefix + i)) handler(passedNode);
            });
        }
    }]);

    function ElementObserver() {
        var _this = this;

        _classCallCheck(this, ElementObserver);

        this._elementHandlers = [];
        this._textHandlers = [];
        new MutationObserver(function (mutations) {
            mutations.forEach(function (mutation) {
                Array.from(mutation.addedNodes).forEach(function (node) {
                    if (node instanceof Element) ElementObserver._invokeHandlers(_this._elementHandlers, ELEMENT_HANDLER_CLASS_PREFIX, node, node);else if (node instanceof Text && node.parentElement !== null) ElementObserver._invokeHandlers(_this._textHandlers, TEXT_HANDLER_CLASS_PREFIX, node.parentElement, node);
                });
            });
        }).observe(document.documentElement, {
            childList: true,
            subtree: true
        });
    }

    _createClass(ElementObserver, [{
        key: 'forEach',
        value: function forEach(selector, handler) {
            var handlerId = this._elementHandlers.length;
            var _iteratorNormalCompletion = true;
            var _didIteratorError = false;
            var _iteratorError = undefined;

            try {
                for (var _iterator = Array.from(document.querySelectorAll(selector))[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
                    var el = _step.value;

                    handler(el);
                    el.classList.add(ELEMENT_HANDLER_CLASS_PREFIX + handlerId);
                }
            } catch (err) {
                _didIteratorError = true;
                _iteratorError = err;
            } finally {
                try {
                    if (!_iteratorNormalCompletion && _iterator.return) {
                        _iterator.return();
                    }
                } finally {
                    if (_didIteratorError) {
                        throw _iteratorError;
                    }
                }
            }

            this._elementHandlers.push({ selector: selector, handler: handler });
        }
    }, {
        key: 'forEachTextIn',
        value: function forEachTextIn(selector, handler) {
            var handlerId = this._textHandlers.length;
            var _iteratorNormalCompletion2 = true;
            var _didIteratorError2 = false;
            var _iteratorError2 = undefined;

            try {
                for (var _iterator2 = Array.from(document.querySelectorAll(selector))[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
                    var el = _step2.value;
                    var _iteratorNormalCompletion3 = true;
                    var _didIteratorError3 = false;
                    var _iteratorError3 = undefined;

                    try {
                        for (var _iterator3 = el.childNodes[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
                            var node = _step3.value;

                            if (node instanceof Text) handler(node);
                        }
                    } catch (err) {
                        _didIteratorError3 = true;
                        _iteratorError3 = err;
                    } finally {
                        try {
                            if (!_iteratorNormalCompletion3 && _iterator3.return) {
                                _iterator3.return();
                            }
                        } finally {
                            if (_didIteratorError3) {
                                throw _iteratorError3;
                            }
                        }
                    }

                    el.classList.add(TEXT_HANDLER_CLASS_PREFIX + handlerId);
                }
            } catch (err) {
                _didIteratorError2 = true;
                _iteratorError2 = err;
            } finally {
                try {
                    if (!_iteratorNormalCompletion2 && _iterator2.return) {
                        _iterator2.return();
                    }
                } finally {
                    if (_didIteratorError2) {
                        throw _iteratorError2;
                    }
                }
            }

            this._textHandlers.push({ selector: selector, handler: handler });
        }
    }]);

    return ElementObserver;
}();
'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Submit = function () {
    function Submit() {
        _classCallCheck(this, Submit);
    }

    _createClass(Submit, [{
        key: 'getProblemID',
        value: function getProblemID() {
            return this.space + ',' + this.problemNo;
        }
    }, {
        key: 'getCacheKey',
        value: function getCacheKey() {
            return 'problem' + this.space + '_' + this.problemNo;
        }
    }, {
        key: 'isConsidered',
        value: function isConsidered() {
            if (this.verdict !== 'Accepted') return false;
            if (this.space == 1) return true;

            // Check whether the online-contest problem is
            // copied to the main archive
            var problemID = this.getProblemID();
            var archiveNo;
            if (problemID in Submit.problemsFromContests) {
                archiveNo = Submit.problemsFromContests[problemID];
            } else {
                try {
                    archiveNo = getValue(this.getCacheKey());
                    Submit.problemsFromContests[problemID] = archiveNo;
                } catch (err) {}
            }
            if (archiveNo !== undefined) {
                if (archiveNo == "null") return false;
                this.space = 1;
                this.problemNo = archiveNo;
                return true;
            }
            return null;
        }
    }, {
        key: 'queryWhetherConsidered',
        value: function queryWhetherConsidered(resultCallback, failCallback) {
            var _this = this;

            var address = document.location.origin + '/problem.aspx?space=' + this.space + '&num=' + this.problemNo;
            $.get(address, function (data) {
                var problemID = _this.getProblemID();
                var cacheKey = _this.getCacheKey();
                var match = /<A HREF="problem\.aspx\?space=1(&amp;|&)num=(\d{4})"><nobr>\d{4}. .*?<\/nobr><\/A>/i.exec(data);
                if (match !== null) {
                    var archiveNo = match[2];
                    Submit.problemsFromContests[problemID] = archiveNo;
                    setValue(cacheKey, archiveNo);

                    _this.space = 1;
                    _this.problemNo = archiveNo;
                    resultCallback(true);
                } else {
                    Submit.problemsFromContests[problemID] = "null";
                    setValue(cacheKey, "null");

                    resultCallback(false);
                }
            }).fail(failCallback);
        }
    }]);

    return Submit;
}();

// This dictionary duplicates some "problem*_*" records of local storage
// because it can be full or inaccessible


Submit.problemsFromContests = {};
'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var MSEC_PER_SEC = 1e3;

var Author = function () {
    function Author(judgeID) {
        _classCallCheck(this, Author);

        this.judgeID = judgeID;
        this.acceptedProblems = {};
        this.acceptedProblemsCount = 0;
        this.submitsQuery = '?author=' + judgeID + '&status=accepted';
        this.lastSubmitID = null;
        this.noMorePages = false;
        this.hasDeletedProblems = false;
    }

    _createClass(Author, [{
        key: 'getCacheKeyPrefix',
        value: function getCacheKeyPrefix() {
            return 'author' + this.judgeID;
        }
    }, {
        key: 'saveToCache',
        value: function saveToCache() {
            var keyPrefix = this.getCacheKeyPrefix();
            setValue(keyPrefix + '_cacheVer', CACHE_VERSION);

            setValue(keyPrefix + '_acceptedProblems', JSON.stringify(this.acceptedProblems));
            setValue(keyPrefix + '_acceptedProblemsCount', this.acceptedProblemsCount);
            setValue(keyPrefix + '_lastKnownSubmitID', this.lastSubmitID);
        }
    }, {
        key: 'loadFromCache',
        value: function loadFromCache() {
            var keyPrefix = this.getCacheKeyPrefix();
            try {
                var cacheVersion = parseInt(getValue(keyPrefix + '_cacheVer'));
                if (cacheVersion !== CACHE_VERSION) throw new Error('Incompatible cache version');

                this.acceptedProblems = JSON.parse(getValue(keyPrefix + '_acceptedProblems'));
                this.cachedAcceptedProblemsCount = parseInt(getValue(keyPrefix + '_acceptedProblemsCount'));
                this.cachedLastSubmitID = parseInt(getValue(keyPrefix + '_lastKnownSubmitID'));
                this.cacheAvailable = true;
            } catch (err) {
                this.cacheAvailable = false;
            }
        }
    }, {
        key: 'retrieve',
        value: function retrieve(expectedAcProblems, resultCallback, failCallback) {
            this.loadFromCache();
            if (this.cacheAvailable && this.cachedAcceptedProblemsCount == expectedAcProblems) {
                this.acceptedProblemsCount = this.cachedAcceptedProblemsCount;
                this.lastSubmitID = this.cachedLastSubmitID;

                if (resultCallback !== undefined) resultCallback();
                return;
            }

            this.retrieveCallback = resultCallback;
            this.failCallback = failCallback;
            this.parseSubmitsPage(null);
        }
    }, {
        key: 'getSubmitsPage',
        value: function getSubmitsPage(query, resultCallback, failCallback) {
            var _this = this;

            var url = document.location.origin + '/textstatus.aspx' + query;
            if (!this.hasDeletedProblems) {
                $.get(url, function (data) {
                    var expr = /<HTML>/i;
                    if (expr.test(data)) {
                        _this.hasDeletedProblems = true;
                        _this.getSubmitsPage(query, resultCallback, failCallback);
                        return;
                    }
                    resultCallback(data);
                }).fail(failCallback);
                return;
            }

            // Timus API used to throw an exception if the author have submits on
            // deleted problems (e.g. in private contests). If we got an exception,
            // just skip additional problem spaces.
            $.get(url + '&space=1').then(resultCallback, failCallback);
        }
    }, {
        key: 'parseSubmitsPage',
        value: function parseSubmitsPage(fromSubmitID) {
            var _this2 = this;

            var submitsQueried = this.cacheAvailable ? 200 : 1000;
            var query = this.submitsQuery + '&count=' + submitsQueried;
            if (fromSubmitID !== null) query += '&from=' + fromSubmitID;
            var author = this;
            this.getSubmitsPage(query, function (data) {
                var lines = data.split('\n').filter(function (line) {
                    return line !== '';
                });
                if (!lines.length || !lines[0].startsWith('submit')) {
                    _this2.failCallback();
                    return;
                }

                lines = lines.slice(1);
                if (lines.length < submitsQueried) _this2.noMorePages = true;
                try {
                    _this2.submits = lines.map(function (line) {
                        var fields = line.split('\t');
                        var submit = new Submit();

                        submit.id = parseInt(fields[0]);
                        submit.space = parseInt(fields[3]);
                        submit.problemNo = parseInt(fields[4]);
                        var elems = fields[1].replace(/-/g, ' ').replace(/:/g, ' ').split(' ');
                        submit.time = new Date(elems[0], elems[1] - 1, elems[2], elems[3], elems[4], elems[5]).getTime();
                        submit.verdict = fields[6];
                        if (isNaN(submit.id) || isNaN(submit.space) || isNaN(submit.problemNo) || isNaN(submit.time)) throw new Error("Failed to parse information about submit");
                        return submit;
                    });
                } catch (err) {
                    _this2.failCallback();
                    return;
                }
                _this2.processSubmitsFrom(0);
            }, this.failCallback);
        }
    }, {
        key: 'considerSubmit',
        value: function considerSubmit(submit) {
            var seconds = Math.floor(submit.time / MSEC_PER_SEC);
            var alreadyAccepted = submit.problemNo in this.acceptedProblems;
            if (!alreadyAccepted) this.acceptedProblemsCount++;
            if (!alreadyAccepted || seconds < this.acceptedProblems[submit.problemNo]) this.acceptedProblems[submit.problemNo] = seconds;
        }
    }, {
        key: 'processSubmitsFrom',
        value: function processSubmitsFrom(index) {
            while (index < this.submits.length) {
                var submit = this.submits[index];
                if (this.lastSubmitID === null) this.lastSubmitID = submit.id;
                if (this.cacheAvailable && submit.id <= this.cachedLastSubmitID) {
                    this.acceptedProblemsCount += this.cachedAcceptedProblemsCount;
                    this.noMorePages = true;
                    break;
                }

                var isConsidered = submit.isConsidered();
                if (isConsidered === null) {
                    this.queryAndProcessSubmitsFrom(index);
                    return;
                }
                if (isConsidered === true) this.considerSubmit(submit);
                index++;
            }

            if (this.noMorePages) {
                this.saveToCache();

                if (this.retrieveCallback !== undefined) this.retrieveCallback();
            } else this.parseSubmitsPage(this.submits[this.submits.length - 1].id - 1);
        }
    }, {
        key: 'queryAndProcessSubmitsFrom',
        value: function queryAndProcessSubmitsFrom(index) {
            var _this3 = this;

            var submit = this.submits[index];
            submit.queryWhetherConsidered(function (result) {
                if (result) _this3.considerSubmit(submit);
                _this3.processSubmitsFrom(index + 1);
            }, this.failCallback);
        }
    }]);

    return Author;
}();
'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var PageParser = function () {
    function PageParser() {
        _classCallCheck(this, PageParser);

        this.parsed = false;

        var match = /id=(\d+)/i.exec(location.href);
        var id = match !== null ? match[1] : null;
        match = /compareto=(\d+)/i.exec(location.href);
        var compareto = match !== null ? match[1] : null;
        if (compareto === null) {
            this.ourId = id;
            this.rivalId = null;
        } else {
            this.ourId = compareto;
            this.rivalId = id;
        }
    }

    _createClass(PageParser, [{
        key: 'parse',
        value: function parse() {
            if (this.parsed) return;
            this.parsed = true;

            var link = $('h2.author_name a');
            var profileName = link.length ? link.html() : $('h2.author_name').contents().get(0).nodeValue;
            if (this.rivalId !== null) {
                var bothCount = $('td.both').length - 1;
                this.ourCount = $('td.accepted').length + bothCount - 1;
                this.rivalCount = $('td.cmpac').length + bothCount - 1;

                this.ourName = $('.author_comparison_legend .padright:first').html();
                this.rivalName = profileName;
            } else {
                this.ourCount = $('td.accepted').length;

                this.ourName = profileName;
            }
        }
    }]);

    return PageParser;
}();
'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var DataRetriever = function () {
    function DataRetriever(pageParser) {
        _classCallCheck(this, DataRetriever);

        this._pageParser = pageParser;

        this._authorStates = {};
    }

    _createClass(DataRetriever, [{
        key: '_getExpectedAcProblems',
        value: function _getExpectedAcProblems(judgeID) {
            switch (judgeID) {
                case this._pageParser.ourId:
                    return this._pageParser.ourCount;
                case this._pageParser.rivalId:
                    return this._pageParser.rivalCount;
                default:
                    return null;
            }
        }
    }, {
        key: '_startRetrieval',
        value: function _startRetrieval(judgeID, resultCb, failCb) {
            var _this = this;

            var resultCallbacks = [];
            var failCallbacks = [];
            var author = new Author(judgeID);
            var state = {
                status: 'process',
                resultCallbacks: resultCallbacks,
                failCallbacks: failCallbacks,
                author: author
            };
            DataRetriever._pushCallbacks(state, resultCb, failCb);
            this._authorStates[judgeID] = state;

            author.retrieve(this._getExpectedAcProblems(judgeID), function () {
                _this._authorStates[judgeID] = {
                    status: 'success',
                    author: author
                };
                resultCallbacks.forEach(function (cb) {
                    return cb(author);
                });
            }, function () {
                _this._authorStates[judgeID] = {
                    status: 'fail',
                    author: author
                };
                failCallbacks.forEach(function (cb) {
                    return cb(author);
                });
            });
        }
    }, {
        key: 'retrieve',
        value: function retrieve(judgeID, resultCb, failCb) {
            var state = this._authorStates[judgeID];
            if (state === undefined) {
                this._startRetrieval(judgeID, resultCb, failCb);
                return;
            }

            switch (state.status) {
                case 'process':
                    DataRetriever._pushCallbacks(state, resultCb, failCb);
                    break;
                case 'success':
                    resultCb(state.author);
                    break;
                case 'fail':
                    failCb(state.author);
                    break;
                default:
                    throw new Error("Unknown retrieval state " + state.status);
            }
        }
    }], [{
        key: '_pushCallbacks',
        value: function _pushCallbacks(state, resultCb, failCb) {
            if (resultCb !== undefined) state.resultCallbacks.push(resultCb);
            if (failCb !== undefined) state.failCallbacks.push(failCb);
        }
    }]);

    return DataRetriever;
}();
"use strict";

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Line = function () {
    function Line(author, name, color) {
        _classCallCheck(this, Line);

        this.author = author;
        this.name = name;
        this.color = color;
    }

    _createClass(Line, [{
        key: "make",
        value: function make() {
            var _this = this;

            this.points = Object.keys(this.author.acceptedProblems).map(function (key) {
                return _this.author.acceptedProblems[key] * MSEC_PER_SEC;
            }).sort(function (a, b) {
                return a - b;
            }).map(function (date, i) {
                return [new Date(date), i + 1];
            });
        }
    }]);

    return Line;
}();
'use strict';

/**
 * Copyright (c) 2011-2014 Felix Gnass
 * Licensed under the MIT license
 * http://spin.js.org/
 *
 * Modified 2016 Alexander Borzunov - Make a public factory function
 *
 * Example:
    var opts = {
      lines: 12             // The number of lines to draw
    , length: 7             // The length of each line
    , width: 5              // The line thickness
    , radius: 10            // The radius of the inner circle
    , scale: 1.0            // Scales overall size of the spinner
    , corners: 1            // Roundness (0..1)
    , color: '#000'         // #rgb or #rrggbb
    , opacity: 1/4          // Opacity of the lines
    , rotate: 0             // Rotation offset
    , direction: 1          // 1: clockwise, -1: counterclockwise
    , speed: 1              // Rounds per second
    , trail: 100            // Afterglow percentage
    , fps: 20               // Frames per second when using setTimeout()
    , zIndex: 2e9           // Use a high z-index by default
    , className: 'spinner'  // CSS class to assign to the element
    , top: '50%'            // center vertically
    , left: '50%'           // center horizontally
    , shadow: false         // Whether to render a shadow
    , hwaccel: false        // Whether to use hardware acceleration (might be buggy)
    , position: 'absolute'  // Element positioning
    }
    var target = document.getElementById('foo')
    var spinner = new Spinner(opts).spin(target)
 */

function makeSpinner() {
  "use strict";

  var prefixes = ['webkit', 'Moz', 'ms', 'O'] /* Vendor prefixes */
  ,
      animations = {} /* Animation rules keyed by their name */
  ,
      useCssAnimations /* Whether to use CSS animations or setTimeout */
  ,
      sheet; /* A stylesheet to hold the @keyframe or VML rules. */

  /**
   * Utility function to create elements. If no tag name is given,
   * a DIV is created. Optionally properties can be passed.
   */
  function createEl(tag, prop) {
    var el = document.createElement(tag || 'div'),
        n;

    for (n in prop) {
      el[n] = prop[n];
    }return el;
  }

  /**
   * Appends children and returns the parent.
   */
  function ins(parent /* child1, child2, ...*/) {
    for (var i = 1, n = arguments.length; i < n; i++) {
      parent.appendChild(arguments[i]);
    }

    return parent;
  }

  /**
   * Creates an opacity keyframe animation rule and returns its name.
   * Since most mobile Webkits have timing issues with animation-delay,
   * we create separate rules for each line/segment.
   */
  function addAnimation(alpha, trail, i, lines) {
    var name = ['opacity', trail, ~~(alpha * 100), i, lines].join('-'),
        start = 0.01 + i / lines * 100,
        z = Math.max(1 - (1 - alpha) / trail * (100 - start), alpha),
        prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase(),
        pre = prefix && '-' + prefix + '-' || '';

    if (!animations[name]) {
      sheet.insertRule('@' + pre + 'keyframes ' + name + '{' + '0%{opacity:' + z + '}' + start + '%{opacity:' + alpha + '}' + (start + 0.01) + '%{opacity:1}' + (start + trail) % 100 + '%{opacity:' + alpha + '}' + '100%{opacity:' + z + '}' + '}', sheet.cssRules.length);

      animations[name] = 1;
    }

    return name;
  }

  /**
   * Tries various vendor prefixes and returns the first supported property.
   */
  function vendor(el, prop) {
    var s = el.style,
        pp,
        i;

    prop = prop.charAt(0).toUpperCase() + prop.slice(1);
    if (s[prop] !== undefined) return prop;
    for (i = 0; i < prefixes.length; i++) {
      pp = prefixes[i] + prop;
      if (s[pp] !== undefined) return pp;
    }
  }

  /**
   * Sets multiple style properties at once.
   */
  function css(el, prop) {
    for (var n in prop) {
      el.style[vendor(el, n) || n] = prop[n];
    }

    return el;
  }

  /**
   * Fills in default values.
   */
  function merge(obj) {
    for (var i = 1; i < arguments.length; i++) {
      var def = arguments[i];
      for (var n in def) {
        if (obj[n] === undefined) obj[n] = def[n];
      }
    }
    return obj;
  }

  /**
   * Returns the line color from the given string or array.
   */
  function getColor(color, idx) {
    return typeof color == 'string' ? color : color[idx % color.length];
  }

  // Built-in defaults

  var defaults = {
    lines: 12 // The number of lines to draw
    , length: 7 // The length of each line
    , width: 5 // The line thickness
    , radius: 10 // The radius of the inner circle
    , scale: 1.0 // Scales overall size of the spinner
    , corners: 1 // Roundness (0..1)
    , color: '#000' // #rgb or #rrggbb
    , opacity: 1 / 4 // Opacity of the lines
    , rotate: 0 // Rotation offset
    , direction: 1 // 1: clockwise, -1: counterclockwise
    , speed: 1 // Rounds per second
    , trail: 100 // Afterglow percentage
    , fps: 20 // Frames per second when using setTimeout()
    , zIndex: 2e9 // Use a high z-index by default
    , className: 'spinner' // CSS class to assign to the element
    , top: '50%' // center vertically
    , left: '50%' // center horizontally
    , shadow: false // Whether to render a shadow
    , hwaccel: false // Whether to use hardware acceleration (might be buggy)
    , position: 'absolute' // Element positioning


    /** The constructor */
  };function Spinner(o) {
    this.opts = merge(o || {}, Spinner.defaults, defaults);
  }

  // Global defaults that override the built-ins:
  Spinner.defaults = {};

  merge(Spinner.prototype, {
    /**
     * Adds the spinner to the given target element. If this instance is already
     * spinning, it is automatically removed from its previous target b calling
     * stop() internally.
     */
    spin: function spin(target) {
      this.stop();

      var self = this,
          o = self.opts,
          el = self.el = createEl(null, { className: o.className });

      css(el, {
        position: o.position,
        width: 0,
        zIndex: o.zIndex,
        left: o.left,
        top: o.top
      });

      if (target) {
        target.insertBefore(el, target.firstChild || null);
      }

      el.setAttribute('role', 'progressbar');
      self.lines(el, self.opts);

      if (!useCssAnimations) {
        // No CSS animation support, use setTimeout() instead
        var i = 0,
            start = (o.lines - 1) * (1 - o.direction) / 2,
            alpha,
            fps = o.fps,
            f = fps / o.speed,
            ostep = (1 - o.opacity) / (f * o.trail / 100),
            astep = f / o.lines;(function anim() {
          i++;
          for (var j = 0; j < o.lines; j++) {
            alpha = Math.max(1 - (i + (o.lines - j) * astep) % f * ostep, o.opacity);

            self.opacity(el, j * o.direction + start, alpha, o);
          }
          self.timeout = self.el && setTimeout(anim, ~~(1000 / fps));
        })();
      }
      return self;
    }

    /**
     * Stops and removes the Spinner.
     */
    , stop: function stop() {
      var el = this.el;
      if (el) {
        clearTimeout(this.timeout);
        if (el.parentNode) el.parentNode.removeChild(el);
        this.el = undefined;
      }
      return this;
    }

    /**
     * Internal method that draws the individual lines. Will be overwritten
     * in VML fallback mode below.
     */
    , lines: function lines(el, o) {
      var i = 0,
          start = (o.lines - 1) * (1 - o.direction) / 2,
          seg;

      function fill(color, shadow) {
        return css(createEl(), {
          position: 'absolute',
          width: o.scale * (o.length + o.width) + 'px',
          height: o.scale * o.width + 'px',
          background: color,
          boxShadow: shadow,
          transformOrigin: 'left',
          transform: 'rotate(' + ~~(360 / o.lines * i + o.rotate) + 'deg) translate(' + o.scale * o.radius + 'px' + ',0)',
          borderRadius: (o.corners * o.scale * o.width >> 1) + 'px'
        });
      }

      for (; i < o.lines; i++) {
        seg = css(createEl(), {
          position: 'absolute',
          top: 1 + ~(o.scale * o.width / 2) + 'px',
          transform: o.hwaccel ? 'translate3d(0,0,0)' : '',
          opacity: o.opacity,
          animation: useCssAnimations && addAnimation(o.opacity, o.trail, start + i * o.direction, o.lines) + ' ' + 1 / o.speed + 's linear infinite'
        });

        if (o.shadow) ins(seg, css(fill('#000', '0 0 4px #000'), { top: '2px' }));
        ins(el, ins(seg, fill(getColor(o.color, i), '0 0 1px rgba(0,0,0,.1)')));
      }
      return el;
    }

    /**
     * Internal method that adjusts the opacity of a single line.
     * Will be overwritten in VML fallback mode below.
     */
    , opacity: function opacity(el, i, val) {
      if (i < el.childNodes.length) el.childNodes[i].style.opacity = val;
    }

  });

  function initVML() {

    /* Utility function to create a VML tag */
    function vml(tag, attr) {
      return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr);
    }

    // No CSS transforms but VML support, add a CSS rule for VML elements:
    sheet.addRule('.spin-vml', 'behavior:url(#default#VML)');

    Spinner.prototype.lines = function (el, o) {
      var r = o.scale * (o.length + o.width),
          s = o.scale * 2 * r;

      function grp() {
        return css(vml('group', {
          coordsize: s + ' ' + s,
          coordorigin: -r + ' ' + -r
        }), { width: s, height: s });
      }

      var margin = -(o.width + o.length) * o.scale * 2 + 'px',
          g = css(grp(), { position: 'absolute', top: margin, left: margin }),
          i;

      function seg(i, dx, filter) {
        ins(g, ins(css(grp(), { rotation: 360 / o.lines * i + 'deg', left: ~~dx }), ins(css(vml('roundrect', { arcsize: o.corners }), { width: r,
          height: o.scale * o.width,
          left: o.scale * o.radius,
          top: -o.scale * o.width >> 1,
          filter: filter
        }), vml('fill', { color: getColor(o.color, i), opacity: o.opacity }), vml('stroke', { opacity: 0 }) // transparent stroke to fix color bleeding upon opacity change
        )));
      }

      if (o.shadow) for (i = 1; i <= o.lines; i++) {
        seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)');
      }

      for (i = 1; i <= o.lines; i++) {
        seg(i);
      }return ins(el, g);
    };

    Spinner.prototype.opacity = function (el, i, val, o) {
      var c = el.firstChild;
      o = o.shadow && o.lines || 0;
      if (c && i + o < c.childNodes.length) {
        c = c.childNodes[i + o];c = c && c.firstChild;c = c && c.firstChild;
        if (c) c.opacity = val;
      }
    };
  }

  if (typeof document !== 'undefined') {
    sheet = function () {
      var el = createEl('style', { type: 'text/css' });
      ins(document.getElementsByTagName('head')[0], el);
      return el.sheet || el.styleSheet;
    }();

    var probe = css(createEl('group'), { behavior: 'url(#default#VML)' });

    if (!vendor(probe, 'transform') && probe.adj) initVML();else useCssAnimations = vendor(probe, 'animation');
  }

  return Spinner;
}
'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function zfill(str, width) {
    while (str.length < width) {
        str = '0' + str;
    }return str;
}

function randomColor() {
    return '#' + zfill(Math.floor(Math.random() * 0x1000).toString(16), 3);
}

var YAXIS_SMALLEST_MAX = 10;
var XAXIS_CRITICAL_DIFF = 1000 * 60 * 60 * 24 * 3;

var MIN_PROBLEMS_TO_SHOW_USER = 2;

var Chart = function () {
    function Chart(observer, pageParser, dataRetriever) {
        _classCallCheck(this, Chart);

        this.observer = observer;
        this.pageParser = pageParser;
        this.dataRetriever = dataRetriever;

        this.ready = false;
        this.visible = false;

        this.lines = [];
        this.linesExpected = 0;

        this.loadingState = false;
        this.errorShown = false;
    }

    _createClass(Chart, [{
        key: 'loading',
        value: function loading(state) {
            if (state === false) {
                this.spinner.stop();
                $('#chart_loading').hide();
                $('#chart').show();
                this.loadingState = false;
                return;
            }
            if (state === true) {
                this.loadingState = true;
                $('#chart').hide();
                $('#chart_loading').show();
                this.spinner.spin($('.chart_spin')[0]);
                return;
            }
            return this.loadingState;
        }
    }, {
        key: 'showError',
        value: function showError(html) {
            this.errorShown = true;
            this.loading(false);
            $('.chart_legend_open').hide();
            $('.chart_legend').hide();
            $('#chart').hide();
            $('#chart_loading').show();
            $('#chart_error').html(html);
            $('#chart_error').show();
        }
    }, {
        key: 'showQueryError',
        value: function showQueryError() {
            this.showError(locale.queryFailed + '<br />' + locale.refreshPage);
        }
    }, {
        key: 'redrawLegend',
        value: function redrawLegend() {
            var _this = this;

            var severalLines = this.lines.length > 1;
            var code = '';
            this.lines.forEach(function (line) {
                code += substTemplateVariables(TEMPLATE_USER_BEGIN, {
                    row_id: Chart.getLegendRowID(line),
                    color: line.color,
                    judge_id: line.author.judgeID,
                    name: line.name
                });
                if (severalLines) code += substTemplateVariables(TEMPLATE_USER_SEVERAL_LINES, {
                    problems_count: line.author.acceptedProblemsCount
                });
                code += TEMPLATE_USER_END;
            });
            $('.chart_users_table').html(code);

            if (!severalLines) return;
            this.lines.forEach(function (line) {
                $('#' + Chart.getLegendRowID(line) + ' .chart_user_remove').click(function (event) {
                    _this.removeUser(line.author.judgeID);
                    event.preventDefault();
                });
            });
        }
    }, {
        key: 'fixPlot',
        value: function fixPlot(plot) {
            var needReplot = false;
            // Fix non-integer ticks on X axis when maximal value is small
            if (plot.axes.yaxis.max < YAXIS_SMALLEST_MAX) {
                plot.axes.yaxis.reset();
                plot.axes.yaxis.max = YAXIS_SMALLEST_MAX;
                needReplot = true;
            }
            // Fix inadequate behavior of ticks when Y axis dates range is small
            if (plot.axes.xaxis.max - plot.axes.xaxis.min <= XAXIS_CRITICAL_DIFF) {
                plot.axes.xaxis.numberTicks = 5;
                plot.axes.xaxis.tickOptions.formatString += ', %H:%M';
                needReplot = true;
            }
            if (needReplot) plot.replot();
        }
    }, {
        key: 'redraw',
        value: function redraw() {
            this.lines.sort(function (a, b) {
                return b.author.acceptedProblemsCount - a.author.acceptedProblemsCount;
            });
            this.redrawLegend();

            var points = this.lines.map(function (line) {
                return line.points;
            }).reverse();
            var colors = this.lines.map(function (line) {
                return line.color;
            }).reverse();

            if (!$('.chart_legend').is(':visible')) $('.chart_legend_open').show();
            $('#chart').html('');
            this.loading(false);
            var plot = $.jqplot('chart', points, {
                gridPadding: {
                    bottom: 50
                },
                grid: {
                    shadow: false
                },
                axes: {
                    xaxis: {
                        renderer: $.jqplot.DateAxisRenderer,
                        tickOptions: { formatString: '%#d %b %Y' }
                    },
                    yaxis: {
                        min: 0,
                        tickOptions: { formatString: '%d' }
                    }
                },
                seriesDefaults: {
                    shadow: false,
                    showMarker: false,
                    lineWidth: 2
                },
                seriesColors: colors
            });
            this.fixPlot(plot);
        }
    }, {
        key: 'showJudgeIDError',
        value: function showJudgeIDError(message) {
            this.loading(false);
            $('#chart_loading_error_judge_id').html(message).show();
        }
    }, {
        key: 'expectUsers',
        value: function expectUsers(count) {
            this.linesExpected += count;
        }
    }, {
        key: 'addUser',
        value: function addUser(judgeID, name, color, isCritical, callback) {
            var _this2 = this;

            this.dataRetriever.retrieve(judgeID, function (author) {
                var line = new Line(author, name, color);
                line.make();
                if (line.points.length < MIN_PROBLEMS_TO_SHOW_USER) {
                    if (isCritical) _this2.showQueryError();else _this2.showJudgeIDError(locale.judgeIDNotEnoughOfAccepted);
                    _this2.linesExpected--;
                    return;
                }

                _this2.lines.push(line);
                if (_this2.lines.length === _this2.linesExpected) {
                    _this2.redraw();
                    if (callback !== undefined) callback();
                }
            }, function () {
                if (isCritical) _this2.showQueryError();else _this2.showJudgeIDError(locale.queryFailed);
                _this2.linesExpected--;
            });
        }
    }, {
        key: 'removeUser',
        value: function removeUser(judgeID) {
            var index = this.lines.findIndex(function (line) {
                return line.author.judgeID == judgeID;
            });
            if (index == -1) return;

            this.lines.splice(index, 1);
            this.linesExpected--;
            this.redraw();
        }
    }, {
        key: 'loadJudgeID',
        value: function loadJudgeID() {
            var _this3 = this;

            if (this.loading()) return;
            this.loading(true);
            $('#chart_loading_error_judge_id').hide();

            var judgeID = $('.chart_judge_id_input').val();
            var match = /(\d+)[^\d]*$/.exec(judgeID);
            if (match === null) {
                this.showJudgeIDError(locale.judgeIDIncorrectFormat);
                return;
            }
            judgeID = match[1];

            if (this.lines.some(function (line) {
                return line.author.judgeID == judgeID;
            })) {
                this.showJudgeIDError(locale.judgeIDIsAlreadyAdded);
                return;
            }

            var address = document.location.origin + '/author.aspx?id=' + judgeID;
            $.get(address, function (data) {
                var match = /<H2 CLASS="author_name">(<A HREF=".*?" TARGET="_blank">)?(.+?)(<\/A>)?<\/H2>/i.exec(data);
                if (match === null) {
                    _this3.showJudgeIDError(locale.judgeIDDoesntExist);
                    return;
                }
                var name = match[2];

                var color = $('#chart_new_user_color').css('background-color');
                _this3.expectUsers(1);
                _this3.addUser(judgeID, name, color, false, function () {
                    $('.chart_judge_id_input').val('');
                    $('#chart_new_user_color').css('background-color', randomColor());
                });
            }).fail(function () {
                return _this3.showJudgeIDError(locale.queryFailed);
            });
        }
    }, {
        key: 'showLegend',
        value: function showLegend() {
            $('.chart_legend_open').hide();
            $('.chart_legend').show();
            $('.chart_judge_id_input').focus();
        }
    }, {
        key: 'areEnoughDataPresent',
        value: function areEnoughDataPresent() {
            return this.pageParser.ourCount >= MIN_PROBLEMS_TO_SHOW_USER || this.pageParser.rivalId !== null && this.pageParser.rivalCount >= MIN_PROBLEMS_TO_SHOW_USER;
        }
    }, {
        key: 'createToggleLink',
        value: function createToggleLink(authorLinksElem, expectedVisibility) {
            var _this4 = this;

            var label = expectedVisibility ? locale.hideChart : locale.showChart;
            $(authorLinksElem).append(substTemplateVariables(TEMPLATE_TOGGLE_LINK, {
                label: label
            }));
            $('.chart_toggle').click(function (event) {
                if (_this4.visible) _this4.hide();else _this4.show();
                event.preventDefault();
            });
        }
    }, {
        key: 'createChartPlace',
        value: function createChartPlace() {
            var _this5 = this;

            $('.author_links').after(substTemplateVariables(TEMPLATE_CHART, {}));
            $('#chart_new_user_color').click(function () {
                $(this).css('background-color', randomColor());
            });
            $('.chart_legend_open').click(function (event) {
                _this5.showLegend();
                event.preventDefault();
            });
            $('.chart_judge_id_input').keypress(function (event) {
                if (event.which === 13) {
                    _this5.loadJudgeID();
                    event.preventDefault();
                }
            });
            $('.chart_user_add').click(function (event) {
                _this5.loadJudgeID();
                event.preventDefault();
            });

            var Spinner = makeSpinner();
            this.spinner = new Spinner();
        }
    }, {
        key: 'loadInitialData',
        value: function loadInitialData() {
            var showUs = this.pageParser.ourCount >= MIN_PROBLEMS_TO_SHOW_USER;
            var showRival = this.pageParser.rivalId !== null && this.pageParser.rivalCount >= MIN_PROBLEMS_TO_SHOW_USER;
            this.expectUsers(showUs + showRival);

            if (showUs) this.addUser(this.pageParser.ourId, this.pageParser.ourName, COLOR_GREEN, true);
            if (showRival) this.addUser(this.pageParser.rivalId, this.pageParser.rivalName, COLOR_RED, true);
        }
    }, {
        key: 'hide',
        value: function hide() {
            if (!this.visible) return;
            this.visible = false;
            setValue('chart_visible', '0');

            $('.chart_toggle').html(locale.showChart);
            $('#chart_place').hide();
        }
    }, {
        key: 'show',
        value: function show() {
            var _this6 = this;

            if (this.visible) return;
            this.visible = true;
            setValue('chart_visible', '1');

            $('.chart_toggle').html(locale.hideChart);
            if (!this.ready) {
                this.createChartPlace();
                this.loading(true);
                $(function () {
                    _this6.pageParser.parse();
                    if (_this6.areEnoughDataPresent()) _this6.loadInitialData();else _this6.showError(locale.notEnoughData);
                });

                this.ready = true;
            } else {
                $('#chart_place').show();
                if (!this.loadingState) {
                    // If the last redrawing happened when the chart was hidden,
                    // jqPlot wouldn't accomplish it correctly,
                    // so we need run to redraw the chart again
                    this.redraw();
                }
            }
        }
    }, {
        key: 'getDefaultVisibility',
        value: function getDefaultVisibility() {
            try {
                if (getValue('chart_visible') === '0') return false;
            } catch (err) {}
            return true;
        }
    }, {
        key: 'arrange',
        value: function arrange() {
            var _this7 = this;

            this.observer.forEach('.author_links', function (elem) {
                var visible = _this7.getDefaultVisibility();
                _this7.createToggleLink(elem, visible);
                if (visible) _this7.show();
            });
        }
    }], [{
        key: 'getLegendRowID',
        value: function getLegendRowID(line) {
            return 'chart_user_' + line.author.judgeID;
        }
    }]);

    return Chart;
}();
'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var SECS_PER_DAY = 60 * 60 * 24;
var SECS_TO_HIGHLIGHT = 60 * SECS_PER_DAY;

var LastACHighlighter = function () {
    function LastACHighlighter(observer, pageParser, dataRetriever) {
        _classCallCheck(this, LastACHighlighter);

        this.observer = observer;
        this.pageParser = pageParser;
        this.dataRetriever = dataRetriever;
    }

    _createClass(LastACHighlighter, [{
        key: 'show',
        value: function show() {
            this.pageParser.parse();
            var curTime = Date.now() / MSEC_PER_SEC;
            this.dataRetriever.retrieve(this.pageParser.ourId, function (author) {
                var acTimes = author.acceptedProblems;
                var _iteratorNormalCompletion = true;
                var _didIteratorError = false;
                var _iteratorError = undefined;

                try {
                    for (var _iterator = Object.keys(acTimes).sort(function (a, b) {
                        return acTimes[b] - acTimes[a];
                    })[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
                        var problem = _step.value;

                        var acTime = acTimes[problem];
                        if (curTime - acTime > SECS_TO_HIGHLIGHT) break;
                        var ratio = (curTime - acTime) / SECS_TO_HIGHLIGHT;

                        var td = $('.attempt_list td.accepted:contains("' + problem + '")');
                        var hue = 120 + (1 - ratio) * 60;
                        td.css('background-color', 'hsl(' + hue + ', 100%, 78%)');
                    }
                } catch (err) {
                    _didIteratorError = true;
                    _iteratorError = err;
                } finally {
                    try {
                        if (!_iteratorNormalCompletion && _iterator.return) {
                            _iterator.return();
                        }
                    } finally {
                        if (_didIteratorError) {
                            throw _iteratorError;
                        }
                    }
                }
            });
        }
    }, {
        key: 'hide',
        value: function hide() {
            $('.attempt_list td.accepted').css('background-color', 'hsl(120, 100%, 78%)');
        }
    }, {
        key: 'createToggler',
        value: function createToggler(prevCell) {
            var _this = this;

            var checkbox = $('<input type="checkbox">');
            var label = $('<label>').append(checkbox).append(document.createTextNode(locale.highlightLastSolvedProblems));
            var td = $('<td align="right">').append(label);
            $(prevCell).after(td);

            checkbox.prop('checked', this.visible);
            checkbox.change(function () {
                return _this.setVisibility(checkbox.is(':checked'));
            });
        }
    }, {
        key: 'getDefaultVisibility',
        value: function getDefaultVisibility() {
            try {
                if (getValue('highlight_last_solved_problems') === '0') return false;
            } catch (err) {}
            return true;
        }
    }, {
        key: 'setVisibility',
        value: function setVisibility(visibility) {
            setValue('highlight_last_solved_problems', visibility ? '1' : '0');
            this.visible = visibility;
            if (visibility) this.show();else this.hide();
        }
    }, {
        key: 'arrange',
        value: function arrange() {
            var _this2 = this;

            if (this.pageParser.rivalId !== null) return;
            this.observer.forEach('.solved_map_links td:first-child', function (elem) {
                _this2.visible = _this2.getDefaultVisibility();
                _this2.createToggler(elem);
            });
            $(function () {
                if (_this2.visible) _this2.show();
            });
        }
    }]);

    return LastACHighlighter;
}();
"use strict";

var observer = new ElementObserver();
var pageParser = new PageParser();
var dataRetriever = new DataRetriever(pageParser);

addStyles(observer);

updateLocale(observer, function () {
    new Chart(observer, pageParser, dataRetriever).arrange();
    new LastACHighlighter(observer, pageParser, dataRetriever).arrange();
});