/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 10);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {
module.exports = jQuery;
/***/ }),
/* 1 */
/***/ (function(module, exports) {
module.exports = moment;
/***/ }),
/* 2 */
/***/ (function(module, exports) {
module.exports = "<div id=\"menu_wrap\">\r\n <div id=\"sidemenu\" class=\"container\"></div>\r\n <div id=\"sidemenu-key\" class=\"glyphicon glyphicon-menu-left\"></div>\r\n</div>";
/***/ }),
/* 3 */
/***/ (function(module, exports) {
module.exports = "<div id=\"predictor-alert\" class=\"row\"><h5 class='sidemenu-txt'>読み込み中…</h5></div>\r\n<div id=\"predictor-data\" class=\"row\">\r\n <div class=\"input-group col-xs-12\">\r\n <span class=\"input-group-addon\">順位<span class=\"glyphicon glyphicon-question-sign\" aria-hidden=\"true\" data-html=\"true\" data-toggle=\"tooltip\" data-placement=\"right\" title=\"\" data-original-title=\"Rated内の順位です。複数人同順位の際は人数を加味します(5位が4人居たら6.5位として計算)\"></span></span>\r\n <input class=\"form-control\" id=\"predictor-input-rank\">\r\n <span class=\"input-group-addon\">位</span>\r\n </div>\r\n \r\n <div class=\"input-group col-xs-12\">\r\n <span class=\"input-group-addon\">パフォーマンス</span>\r\n <input class=\"form-control\" id=\"predictor-input-perf\">\r\n </div>\r\n\r\n <div class=\"input-group col-xs-12\">\r\n <span class=\"input-group-addon\">レーティング</span>\r\n <input class=\"form-control\" id=\"predictor-input-rate\">\r\n </div>\r\n</div>\r\n<div class=\"row\">\r\n <div class=\"btn-group\">\r\n <button class=\"btn btn-default\" id=\"predictor-current\">現在の順位</button>\r\n <button type=\"button\" class=\"btn btn-primary\" id=\"predictor-reload\" data-loading-text=\"更新中…\">更新</button>\r\n <!--<button class=\"btn btn-default\" id=\"predictor-solved\" disabled>現問題AC後</button>-->\r\n </div>\r\n</div>";
/***/ }),
/* 4 */
/***/ (function(module, exports) {
module.exports = "<div id=\"estimator-alert\"></div>\r\n<div class=\"row\">\r\n\t<div class=\"input-group\">\r\n\t\t<span class=\"input-group-addon\" id=\"estimator-input-desc\"></span>\r\n\t\t<input type=\"number\" class=\"form-control\" id=\"estimator-input\">\r\n\t</div>\r\n</div>\r\n<div class=\"row\">\r\n\t<div class=\"input-group\">\r\n\t\t<span class=\"input-group-addon\" id=\"estimator-res-desc\"></span>\r\n\t\t<input class=\"form-control\" id=\"estimator-res\" disabled=\"disabled\">\r\n\t\t<span class=\"input-group-btn\">\r\n\t\t\t<button class=\"btn btn-default\" id=\"estimator-toggle\">入替</button>\r\n\t\t</span>\r\n\t</div>\r\n</div>\r\n<div class=\"row\" style=\"margin: 10px 0px;\">\r\n\t<a class=\"btn btn-default col-xs-offset-8 col-xs-4\" rel=\"nofollow\" onClick=\"window.open(encodeURI(decodeURI(this.href)),'twwindow','width=550, height=450, personalbar=0, toolbar=0, scrollbars=1'); return false;\" id='estimator-tweet'>ツイート</a>\r\n</div>";
/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {
var content = __webpack_require__(6);
if(typeof content === 'string') content = [[module.i, content, '']];
var transform;
var insertInto;
var options = {"hmr":true}
options.transform = transform
options.insertInto = undefined;
var update = __webpack_require__(8)(content, options);
if(content.locals) module.exports = content.locals;
if(false) {}
/***/ }),
/* 6 */
/***/ (function(module, exports, __webpack_require__) {
exports = module.exports = __webpack_require__(7)(false);
// Module
exports.push([module.i, "@charset \"UTF-8\";\n#menu_wrap {\n display: block;\n position: fixed;\n top: 0;\n z-index: 20;\n width: 400px;\n right: -350px;\n transition: all 150ms 0ms ease;\n margin-top: 50px; }\n\n#sidemenu {\n background: #000;\n opacity: 0.85; }\n\n#sidemenu-key {\n border-radius: 5px 0px 0px 5px;\n background: #000;\n opacity: 0.85;\n color: #FFF;\n padding: 30px 0;\n cursor: pointer;\n margin-top: 100px;\n text-align: center; }\n\n#sidemenu {\n display: inline-block;\n width: 350px;\n float: right; }\n\n#sidemenu-key {\n display: inline-block;\n width: 50px;\n float: right; }\n\n.sidemenu-active {\n transform: translateX(-350px); }\n\n.sidemenu-txt {\n color: #DDD; }\n\n/*アコーディオンメニュー*/\n.menu-wrapper {\n border-bottom: 1px solid #FFF; }\n\n.menu-header {\n margin: 10px 20px 10px 20px;\n user-select: none; }\n\n.menu-box {\n overflow: hidden;\n transition: all 300ms 0s ease; }\n\n.menu-box-collapse {\n height: 0px !important; }\n .menu-box-collapse .menu-content {\n transform: translateY(-100%); }\n\n.menu-content {\n padding: 10px 20px 10px 20px;\n transition: all 300ms 0s ease; }\n", ""]);
/***/ }),
/* 7 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
// css base code, injected by the css-loader
module.exports = function (useSourceMap) {
var list = []; // return the list of modules as css string
list.toString = function toString() {
return this.map(function (item) {
var content = cssWithMappingToString(item, useSourceMap);
if (item[2]) {
return '@media ' + item[2] + '{' + content + '}';
} else {
return content;
}
}).join('');
}; // import a list of modules into the list
list.i = function (modules, mediaQuery) {
if (typeof modules === 'string') {
modules = [[null, modules, '']];
}
var alreadyImportedModules = {};
for (var i = 0; i < this.length; i++) {
var id = this[i][0];
if (id != null) {
alreadyImportedModules[id] = true;
}
}
for (i = 0; i < modules.length; i++) {
var item = modules[i]; // skip already imported module
// this implementation is not 100% perfect for weird media query combinations
// when a module is imported multiple times with different media queries.
// I hope this will never occur (Hey this way we have smaller bundles)
if (item[0] == null || !alreadyImportedModules[item[0]]) {
if (mediaQuery && !item[2]) {
item[2] = mediaQuery;
} else if (mediaQuery) {
item[2] = '(' + item[2] + ') and (' + mediaQuery + ')';
}
list.push(item);
}
}
};
return list;
};
function cssWithMappingToString(item, useSourceMap) {
var content = item[1] || '';
var cssMapping = item[3];
if (!cssMapping) {
return content;
}
if (useSourceMap && typeof btoa === 'function') {
var sourceMapping = toComment(cssMapping);
var sourceURLs = cssMapping.sources.map(function (source) {
return '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */';
});
return [content].concat(sourceURLs).concat([sourceMapping]).join('\n');
}
return [content].join('\n');
} // Adapted from convert-source-map (MIT)
function toComment(sourceMap) {
// eslint-disable-next-line no-undef
var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))));
var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64;
return '/*# ' + data + ' */';
}
/***/ }),
/* 8 */
/***/ (function(module, exports, __webpack_require__) {
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Tobias Koppers @sokra
*/
var stylesInDom = {};
var memoize = function (fn) {
var memo;
return function () {
if (typeof memo === "undefined") memo = fn.apply(this, arguments);
return memo;
};
};
var isOldIE = memoize(function () {
// Test for IE <= 9 as proposed by Browserhacks
// @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805
// Tests for existence of standard globals is to allow style-loader
// to operate correctly into non-standard environments
// @see https://github.com/webpack-contrib/style-loader/issues/177
return window && document && document.all && !window.atob;
});
var getTarget = function (target, parent) {
if (parent){
return parent.querySelector(target);
}
return document.querySelector(target);
};
var getElement = (function (fn) {
var memo = {};
return function(target, parent) {
// If passing function in options, then use it for resolve "head" element.
// Useful for Shadow Root style i.e
// {
// insertInto: function () { return document.querySelector("#foo").shadowRoot }
// }
if (typeof target === 'function') {
return target();
}
if (typeof memo[target] === "undefined") {
var styleTarget = getTarget.call(this, target, parent);
// Special case to return head of iframe instead of iframe itself
if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) {
try {
// This will throw an exception if access to iframe is blocked
// due to cross-origin restrictions
styleTarget = styleTarget.contentDocument.head;
} catch(e) {
styleTarget = null;
}
}
memo[target] = styleTarget;
}
return memo[target]
};
})();
var singleton = null;
var singletonCounter = 0;
var stylesInsertedAtTop = [];
var fixUrls = __webpack_require__(9);
module.exports = function(list, options) {
if (typeof DEBUG !== "undefined" && DEBUG) {
if (typeof document !== "object") throw new Error("The style-loader cannot be used in a non-browser environment");
}
options = options || {};
options.attrs = typeof options.attrs === "object" ? options.attrs : {};
// Force single-tag solution on IE6-9, which has a hard limit on the # of <style>
// tags it will allow on a page
if (!options.singleton && typeof options.singleton !== "boolean") options.singleton = isOldIE();
// By default, add <style> tags to the <head> element
if (!options.insertInto) options.insertInto = "head";
// By default, add <style> tags to the bottom of the target
if (!options.insertAt) options.insertAt = "bottom";
var styles = listToStyles(list, options);
addStylesToDom(styles, options);
return function update (newList) {
var mayRemove = [];
for (var i = 0; i < styles.length; i++) {
var item = styles[i];
var domStyle = stylesInDom[item.id];
domStyle.refs--;
mayRemove.push(domStyle);
}
if(newList) {
var newStyles = listToStyles(newList, options);
addStylesToDom(newStyles, options);
}
for (var i = 0; i < mayRemove.length; i++) {
var domStyle = mayRemove[i];
if(domStyle.refs === 0) {
for (var j = 0; j < domStyle.parts.length; j++) domStyle.parts[j]();
delete stylesInDom[domStyle.id];
}
}
};
};
function addStylesToDom (styles, options) {
for (var i = 0; i < styles.length; i++) {
var item = styles[i];
var domStyle = stylesInDom[item.id];
if(domStyle) {
domStyle.refs++;
for(var j = 0; j < domStyle.parts.length; j++) {
domStyle.parts[j](item.parts[j]);
}
for(; j < item.parts.length; j++) {
domStyle.parts.push(addStyle(item.parts[j], options));
}
} else {
var parts = [];
for(var j = 0; j < item.parts.length; j++) {
parts.push(addStyle(item.parts[j], options));
}
stylesInDom[item.id] = {id: item.id, refs: 1, parts: parts};
}
}
}
function listToStyles (list, options) {
var styles = [];
var newStyles = {};
for (var i = 0; i < list.length; i++) {
var item = list[i];
var id = options.base ? item[0] + options.base : item[0];
var css = item[1];
var media = item[2];
var sourceMap = item[3];
var part = {css: css, media: media, sourceMap: sourceMap};
if(!newStyles[id]) styles.push(newStyles[id] = {id: id, parts: [part]});
else newStyles[id].parts.push(part);
}
return styles;
}
function insertStyleElement (options, style) {
var target = getElement(options.insertInto)
if (!target) {
throw new Error("Couldn't find a style target. This probably means that the value for the 'insertInto' parameter is invalid.");
}
var lastStyleElementInsertedAtTop = stylesInsertedAtTop[stylesInsertedAtTop.length - 1];
if (options.insertAt === "top") {
if (!lastStyleElementInsertedAtTop) {
target.insertBefore(style, target.firstChild);
} else if (lastStyleElementInsertedAtTop.nextSibling) {
target.insertBefore(style, lastStyleElementInsertedAtTop.nextSibling);
} else {
target.appendChild(style);
}
stylesInsertedAtTop.push(style);
} else if (options.insertAt === "bottom") {
target.appendChild(style);
} else if (typeof options.insertAt === "object" && options.insertAt.before) {
var nextSibling = getElement(options.insertAt.before, target);
target.insertBefore(style, nextSibling);
} else {
throw new Error("[Style Loader]\n\n Invalid value for parameter 'insertAt' ('options.insertAt') found.\n Must be 'top', 'bottom', or Object.\n (https://github.com/webpack-contrib/style-loader#insertat)\n");
}
}
function removeStyleElement (style) {
if (style.parentNode === null) return false;
style.parentNode.removeChild(style);
var idx = stylesInsertedAtTop.indexOf(style);
if(idx >= 0) {
stylesInsertedAtTop.splice(idx, 1);
}
}
function createStyleElement (options) {
var style = document.createElement("style");
if(options.attrs.type === undefined) {
options.attrs.type = "text/css";
}
if(options.attrs.nonce === undefined) {
var nonce = getNonce();
if (nonce) {
options.attrs.nonce = nonce;
}
}
addAttrs(style, options.attrs);
insertStyleElement(options, style);
return style;
}
function createLinkElement (options) {
var link = document.createElement("link");
if(options.attrs.type === undefined) {
options.attrs.type = "text/css";
}
options.attrs.rel = "stylesheet";
addAttrs(link, options.attrs);
insertStyleElement(options, link);
return link;
}
function addAttrs (el, attrs) {
Object.keys(attrs).forEach(function (key) {
el.setAttribute(key, attrs[key]);
});
}
function getNonce() {
if (false) {}
return __webpack_require__.nc;
}
function addStyle (obj, options) {
var style, update, remove, result;
// If a transform function was defined, run it on the css
if (options.transform && obj.css) {
result = typeof options.transform === 'function'
? options.transform(obj.css)
: options.transform.default(obj.css);
if (result) {
// If transform returns a value, use that instead of the original css.
// This allows running runtime transformations on the css.
obj.css = result;
} else {
// If the transform function returns a falsy value, don't add this css.
// This allows conditional loading of css
return function() {
// noop
};
}
}
if (options.singleton) {
var styleIndex = singletonCounter++;
style = singleton || (singleton = createStyleElement(options));
update = applyToSingletonTag.bind(null, style, styleIndex, false);
remove = applyToSingletonTag.bind(null, style, styleIndex, true);
} else if (
obj.sourceMap &&
typeof URL === "function" &&
typeof URL.createObjectURL === "function" &&
typeof URL.revokeObjectURL === "function" &&
typeof Blob === "function" &&
typeof btoa === "function"
) {
style = createLinkElement(options);
update = updateLink.bind(null, style, options);
remove = function () {
removeStyleElement(style);
if(style.href) URL.revokeObjectURL(style.href);
};
} else {
style = createStyleElement(options);
update = applyToTag.bind(null, style);
remove = function () {
removeStyleElement(style);
};
}
update(obj);
return function updateStyle (newObj) {
if (newObj) {
if (
newObj.css === obj.css &&
newObj.media === obj.media &&
newObj.sourceMap === obj.sourceMap
) {
return;
}
update(obj = newObj);
} else {
remove();
}
};
}
var replaceText = (function () {
var textStore = [];
return function (index, replacement) {
textStore[index] = replacement;
return textStore.filter(Boolean).join('\n');
};
})();
function applyToSingletonTag (style, index, remove, obj) {
var css = remove ? "" : obj.css;
if (style.styleSheet) {
style.styleSheet.cssText = replaceText(index, css);
} else {
var cssNode = document.createTextNode(css);
var childNodes = style.childNodes;
if (childNodes[index]) style.removeChild(childNodes[index]);
if (childNodes.length) {
style.insertBefore(cssNode, childNodes[index]);
} else {
style.appendChild(cssNode);
}
}
}
function applyToTag (style, obj) {
var css = obj.css;
var media = obj.media;
if(media) {
style.setAttribute("media", media)
}
if(style.styleSheet) {
style.styleSheet.cssText = css;
} else {
while(style.firstChild) {
style.removeChild(style.firstChild);
}
style.appendChild(document.createTextNode(css));
}
}
function updateLink (link, options, obj) {
var css = obj.css;
var sourceMap = obj.sourceMap;
/*
If convertToAbsoluteUrls isn't defined, but sourcemaps are enabled
and there is no publicPath defined then lets turn convertToAbsoluteUrls
on by default. Otherwise default to the convertToAbsoluteUrls option
directly
*/
var autoFixUrls = options.convertToAbsoluteUrls === undefined && sourceMap;
if (options.convertToAbsoluteUrls || autoFixUrls) {
css = fixUrls(css);
}
if (sourceMap) {
// http://stackoverflow.com/a/26603875
css += "\n/*# sourceMappingURL=data:application/json;base64," + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + " */";
}
var blob = new Blob([css], { type: "text/css" });
var oldSrc = link.href;
link.href = URL.createObjectURL(blob);
if(oldSrc) URL.revokeObjectURL(oldSrc);
}
/***/ }),
/* 9 */
/***/ (function(module, exports) {
/**
* When source maps are enabled, `style-loader` uses a link element with a data-uri to
* embed the css on the page. This breaks all relative urls because now they are relative to a
* bundle instead of the current page.
*
* One solution is to only use full urls, but that may be impossible.
*
* Instead, this function "fixes" the relative urls to be absolute according to the current page location.
*
* A rudimentary test suite is located at `test/fixUrls.js` and can be run via the `npm test` command.
*
*/
module.exports = function (css) {
// get current location
var location = typeof window !== "undefined" && window.location;
if (!location) {
throw new Error("fixUrls requires window.location");
}
// blank or null?
if (!css || typeof css !== "string") {
return css;
}
var baseUrl = location.protocol + "//" + location.host;
var currentDir = baseUrl + location.pathname.replace(/\/[^\/]*$/, "/");
// convert each url(...)
/*
This regular expression is just a way to recursively match brackets within
a string.
/url\s*\( = Match on the word "url" with any whitespace after it and then a parens
( = Start a capturing group
(?: = Start a non-capturing group
[^)(] = Match anything that isn't a parentheses
| = OR
\( = Match a start parentheses
(?: = Start another non-capturing groups
[^)(]+ = Match anything that isn't a parentheses
| = OR
\( = Match a start parentheses
[^)(]* = Match anything that isn't a parentheses
\) = Match a end parentheses
) = End Group
*\) = Match anything and then a close parens
) = Close non-capturing group
* = Match anything
) = Close capturing group
\) = Match a close parens
/gi = Get all matches, not the first. Be case insensitive.
*/
var fixedCss = css.replace(/url\s*\(((?:[^)(]|\((?:[^)(]+|\([^)(]*\))*\))*)\)/gi, function(fullMatch, origUrl) {
// strip quotes (if they exist)
var unquotedOrigUrl = origUrl
.trim()
.replace(/^"(.*)"$/, function(o, $1){ return $1; })
.replace(/^'(.*)'$/, function(o, $1){ return $1; });
// already a full url? no change
if (/^(#|data:|http:\/\/|https:\/\/|file:\/\/\/|\s*$)/i.test(unquotedOrigUrl)) {
return fullMatch;
}
// convert the url to a full url
var newUrl;
if (unquotedOrigUrl.indexOf("//") === 0) {
//TODO: should we add protocol?
newUrl = unquotedOrigUrl;
} else if (unquotedOrigUrl.indexOf("/") === 0) {
// path should be relative to the base url
newUrl = baseUrl + unquotedOrigUrl; // already starts with '/'
} else {
// path should be relative to current directory
newUrl = currentDir + unquotedOrigUrl.replace(/^\.\//, ""); // Strip leading './'
}
// send back the fixed url(...)
return "url(" + JSON.stringify(newUrl) + ")";
});
// send back the fixed css
return fixedCss;
};
/***/ }),
/* 10 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
// CONCATENATED MODULE: ./src/libs/tabID.js
let created;
let id;
function initializeID() {
created = Date.now();
id = Date.now() * 1024 + Math.floor(Math.random() * 1024);
}
// EXTERNAL MODULE: ./src/libs/sidemenu/sidemenu.scss
var sidemenu = __webpack_require__(5);
// EXTERNAL MODULE: external "jQuery"
var external_jQuery_ = __webpack_require__(0);
// EXTERNAL MODULE: ./src/libs/sidemenu/sidemenu.html
var sidemenu_sidemenu = __webpack_require__(2);
var sidemenu_sidemenu_default = /*#__PURE__*/__webpack_require__.n(sidemenu_sidemenu);
// CONCATENATED MODULE: ./src/libs/sidemenu/element.js
/**
* サイドメニューに追加される要素のクラスです。
* @property {RegExp} [match]
*/
class SideMenuElement {
/**
* オブジェクト生成用のコンストラクタです
* @param {string} [id] 要素のコンテナに付加されるIDです。
* @param {string} [title] 要素に表示されるタイトルです。
* @param {RegExp} [match] 表示されるページを指定するための正規表現です。
* @param {string} [document] 要素のHTMLです。
* @param {Function} [afterAppend] 要素が追加された後に実行される処理用の関数です。
*/
constructor(id, title, match, document, afterAppend) {
this.id = id;
this.title = title;
this.match = match;
this.document = document;
this.afterAppend = afterAppend;
}
shouldDisplayed(url) {
return this.match.test(url);
}
GetHTML() {
return `<div class="menu-wrapper">
<div class="menu-header">
<h4 class="sidemenu-txt">${this.title}<span class="glyphicon glyphicon-menu-up" style="float: right"></span></h4>
</div>
<div class="menu-box"><div class="menu-content" id="${this.id}">${this.document}</div></div>
</div>`;
}
}
// CONCATENATED MODULE: ./src/libs/sidemenu/sidemenu.js
class sidemenu_SideMenu {
constructor() {
this.Generate();
}
Generate() {
external_jQuery_('#main-div').append(sidemenu_sidemenu_default.a);
resizeSidemenuHeight();
external_jQuery_('#sidemenu-key').click(function () {
external_jQuery_('#sidemenu-key').toggleClass('glyphicon-menu-left glyphicon-menu-right');
external_jQuery_('#menu_wrap').toggleClass('sidemenu-active');
});
external_jQuery_(window).resize(resizeSidemenuHeight);
external_jQuery_('#sidemenu').on('click', '.menu-header', (event) => {
external_jQuery_(event.target).parents('.menu-wrapper').find('.menu-box').toggleClass('menu-box-collapse');
external_jQuery_(event.target).find('.glyphicon').toggleClass('glyphicon-menu-down glyphicon-menu-up');
});
function resizeSidemenuHeight() {
external_jQuery_('#sidemenu').height(external_jQuery_(window).height());
}
}
/**
* サイドメニューに要素を追加します
* @param {SideMenuElement} [element] 追加する要素
*/
addElement(element) {
if (!element.shouldDisplayed(document.location.href)) return;
const elementHtml = external_jQuery_(element.GetHTML());
external_jQuery_('#sidemenu').append(elementHtml);
elementHtml.ready(() => {
const content = external_jQuery_('.menu-content', elementHtml);
content.parents('.menu-box').css('height', content.outerHeight(true));
element.afterAppend();
});
}
}
// EXTERNAL MODULE: ./src/elements/predictor/dom.html
var dom = __webpack_require__(3);
var dom_default = /*#__PURE__*/__webpack_require__.n(dom);
// EXTERNAL MODULE: external "moment"
var external_moment_ = __webpack_require__(1);
var external_moment_default = /*#__PURE__*/__webpack_require__.n(external_moment_);
// CONCATENATED MODULE: ./src/libs/datas/data.js
/**
* データを保存/更新するためのクラスです。
*/
class Data {
/**
* オブジェクト生成用のコンストラクタです
* @param {Function} [getNewData] 更新の際に新たなデータオブジェクトを返す関数です。
*/
constructor(getNewData) {
this.getNewData = getNewData;
}
/**
* データのアップデートをする関数です。
*/
async update() {
this.data = await this.getNewData();
return this.data;
}
}
/**
* GETでデータを取得します。
*/
class WebData extends Data {
/**
* オブジェクト生成用のコンストラクタです
* @param {string} [dataURL] データ取得先のURLです。
*/
constructor(dataURL) {
super(async () => {
return await $.ajax(dataURL);
});
}
}
// CONCATENATED MODULE: ./src/libs/datas/history.js
class history_HistoryData extends WebData {
constructor(userScreenName) {
super(
`https://atcoder.jp/users/${userScreenName}/history/json`
);
}
}
let history_historyData = null;
/**
* パフォーマンス履歴を取得します。
* @return {Promise<{}[]>} パフォーマンス履歴を返すpromise
*/
async function getHistory(){
return new Promise((resolve) => {
if (history_historyData) resolve(history_historyData);
new history_HistoryData(userScreenName).update().then((data) => {
resolve(history_historyData = data);
})
});
}
/**
* レーテっと
* @return {Promise<number[]>}
*/
async function getRatedContestPerformanceHistory() {
return getHistory().then((data) => data.filter(x => x.IsRated)
.sort((a, b) => external_moment_default()(b.EndTime) - external_moment_default()(a.EndTime))
.map(x => x.Performance));
}
// CONCATENATED MODULE: ./src/libs/datas/standings.js
/**
* コンテストの順位表データを取得し、他のタブと同期的にデータを扱います。
*/
class standings_StandingsData extends WebData {
constructor(contestScreenName) {
super(
`https://atcoder.jp/contests/${contestScreenName}/standings/json`
);
}
}
// CONCATENATED MODULE: ./src/libs/datas/aperfs.js
class aperfs_APerfsData extends WebData {
constructor(contestScreenName) {
super(
`https://ac-predictor.azurewebsites.net/api/aperfs/${contestScreenName}`
);
}
}
// CONCATENATED MODULE: ./src/libs/utils/ratingColor.js
const colorBounds = {
"gray": 0,
"brown": 400,
"green": 800,
"cyan": 1200,
"blue": 1600,
"yellow": 2000,
"orange": 2400,
"red": 2800
};
const colorNames = ["unrated", "gray", "brown", "green", "cyan", "blue", "yellow", "orange", "red"];
function getColor(rating) {
let colorIndex = rating > 0 ? Math.min(Math.floor(rating / 400) + 1, 8) : 0;
return colorNames[colorIndex];
}
// CONCATENATED MODULE: ./src/libs/contest/fetchContestInformation.js
class ContestInformation{
/**
* @param {number[]} [participatableRange]
* @param {number[]} [ratedRange]
* @param {number} [penalty]
*/
constructor(participatableRange, ratedRange, penalty){
this.ParticipatableRange = participatableRange;
this.RatedRange = ratedRange;
this.Penalty = penalty;
}
/**
* @param {object} object
* @return {ContestInformation}
*/
static GenerateFromObject(object){
return new ContestInformation(object.ParticipatableRange, object.RatedRange, object.Penalty);
}
}
/**
* @param contestScreenName
* @return {Promise<ContestInformation>}
*/
async function fetchContestInformation(contestScreenName) {
return new Promise(async (resolve) => {
const topPageDom = await $.ajax(`https://atcoder.jp/contests/${contestScreenName}`).then(x => new DOMParser().parseFromString(x, "text/html"));
const dataParagraph = topPageDom.getElementsByClassName("small")[0];
const data = Array.from(dataParagraph.children).map(x => x.innerText.split(':')[1].trim());
resolve(new ContestInformation(parseRangeString(data[0]), parseRangeString(data[1]), parseDurationString(data[2])));
});
/**
* @param {string} [s]
* @return {number[]}
*/
function parseRangeString(s){
if (s === 'All') return [0, Infinity];
if (s.indexOf('~') === -1) return [0, -1];
let res = s.split('~').map(x => parseInt(x.trim()));
if (isNaN(res[0])) res[0] = 0;
if (isNaN(res[1])) res[1] = Infinity;
return res;
}
/**
* parse duration string and return a millisecond
* @param {string} [s]
* @return {number}
*/
function parseDurationString(s) {
const dic = {ヶ月: "month", 日: "day", 時間: "hour", 分: "minute", 秒: "second"};
let res = {};
s.match(/(\d+[^\d]+)/g).forEach(x => {
const trimmed = x.trim(' ','s');
const num = trimmed.match(/\d+/)[0];
const unit = trimmed.match(/[^\d]+/)[0];
const convertedUnit = dic[unit]||unit;
res[convertedUnit] = num;
});
return external_moment_["duration"](res).asMilliseconds();
}
}
// CONCATENATED MODULE: ./src/libs/contest/results/results.js
class Results{
constructor(){}
/**
* @param {string} userScreenName
* @return {Result}
*/
getUserResult(userScreenName){}
}
// CONCATENATED MODULE: ./src/libs/database/database.js
/**
* オブジェクト生成用のコンストラクタです
* @param {Function} [getNewData] 更新の際に新たなデータオブジェクトを返す関数です。
* @param {string} [lsKey] 保存に用いるローカルストレージのkeyです。
* @param {Function} [onUpdate] 更新の際に呼ばれる関数です。
*/
class DataBase {
/**
* オブジェクト生成用のコンストラクタです
* @param {string} [name] indexedDBにアクセスする際に用いる名前です。
* @param {Number} [version] indexedDBにアクセスする際に用いるバージョンです。
*/
constructor(name, version, update) {
this.name = name;
this.version = version;
indexedDB.open(name, version).onupgradeneeded = update;
}
/**
* データをデータベースに追加/更新します。
* @param {string} [storeName] indexedDBからストアを取得する際の名前です。
* @param {string} [key] ストアにセットする際に用いるkeyです。
* @param {Object} [value] ストアにセットする値です。
* @returns {Promise} 非同期のpromiseです。
*/
async setData(storeName, key, value) {
return new Promise((resolve, reject) => {
try {
indexedDB.open(this.name).onsuccess = (e) => {
const db = e.target.result;
const trans = db.transaction(storeName, 'readwrite');
const objStore = trans.objectStore(storeName);
const data = {id: key, data: value};
const putReq = objStore.put(data);
putReq.onsuccess = () => {
db.close();
resolve();
};
};
} catch (e) {
reject(e);
}
});
}
/**
* データをデータベースから取得します。存在しなかった場合はrejectされます。
* @param {string} [storeName] indexedDBからストアを取得する際の名前です。
* @param {string} [key] ストアにセットする際に用いるkeyです。
* @returns {Promise} 非同期のpromiseです。
*/
async getData(storeName, key) {
return new Promise((resolve, reject) => {
try {
indexedDB.open(this.name).onsuccess = (openEvent) => {
const db = openEvent.target.result;
const trans = db.transaction(storeName, 'readwrite');
const objStore = trans.objectStore(storeName);
objStore.get(key).onsuccess = (getEvent) => {
const result = getEvent.target.result;
db.close();
if (!result) reject(`key '${key}' not found in store '${storeName}'`);
else resolve(result.data);
};
};
} catch (e) {
reject(e);
}
});
}
}
// CONCATENATED MODULE: ./src/libs/database/predictorDB.js
const StoreKeys = { aperfs: "APerfs", standings: "Standings" };
class predictorDB_PredictorDB extends DataBase {
constructor() {
super("PredictorDB", 1, (event) => {
const db = event.target.result;
const storeNames = ["APerfs", "Standings"];
storeNames.forEach(store => {
db.createObjectStore(store, { keyPath: "id" });
});
});
}
}
// CONCATENATED MODULE: ./src/libs/contest/results/result.js
class Result{
/***
* @param {boolean} isRated
* @param {boolean} isSubmitted
* @param {string} userScreenName
* @param {number} performance
* @param {number} place
* @param {number} ratedRank
* @param {number} competitions
* @param {number} innerPerformance
* @param {number} oldRating
* @param {number} newRating
*/
constructor(isRated, isSubmitted, userScreenName, place, ratedRank, oldRating, newRating, competitions, performance, innerPerformance){
this.IsRated = isRated;
this.IsSubmitted = isSubmitted;
this.UserScreenName = userScreenName;
this.Place = place;
this.RatedRank = ratedRank;
this.OldRating = oldRating;
this.NewRating = newRating;
this.Competitions = competitions;
this.Performance = performance;
this.InnerPerformance = innerPerformance;
}
}
// CONCATENATED MODULE: ./src/libs/contest/contest.js
class contest_Contest{
constructor(contestScreenName, contestInformation, standings, aPerfs){
this.ratedLimit = contestInformation.RatedRange[1] + 1;
this.perfLimit = this.ratedLimit + 400;
this.standings = standings;
this.aPerfs = aPerfs;
this.rankMemo = {};
const analyzedData = analyzeStandingsData(standings.Fixed, standings.StandingsData, aPerfs, {2000: 800, 2800: 1000, Infinity: 1200}[this.ratedLimit] || 1200, this.ratedLimit);
this.contestantAPerf = analyzedData.contestantAPerf;
this.templateResults = analyzedData.templateResults;
this.IsRated = analyzedData.isRated;
/** @return {{contestantAPerf: number[], templateResults: Object<string, Result>, isRated: boolean}} */
function analyzeStandingsData(fixed, standingsData, aPerfs, defaultAPerf, ratedLimit) {
let analyzedData = analyze((data) => data.IsRated && data.TotalResult.Count !== 0);
analyzedData.isRated = true;
if (analyzedData.contestantAPerf.length === 0) {
analyzedData = analyze((data) => data.OldRating < ratedLimit && data.TotalResult.Count !== 0);
analyzedData.isRated = false;
}
return analyzedData;
/** @return {{contestantAPerf: number[], templateResults: Object.<string, Result>}}*/
function analyze(isUserRated) {
let contestantAPerf = [];
let templateResults = {};
let currentRatedRank = 1;
let lastRank = 0;
let tiedUsers = [];
let ratedInTiedUsers = 0;
function applyTiedUsers(){
tiedUsers.forEach((data) => {
if (isUserRated(data)){
contestantAPerf.push(aPerfs[data.UserScreenName] || defaultAPerf);
ratedInTiedUsers++;
}
});
let ratedRank = currentRatedRank + Math.max(0, ratedInTiedUsers - 1) / 2;
tiedUsers.forEach((data) => {
templateResults[data.UserScreenName] =
new Result(
isUserRated(data),
data.TotalResult.Count !== 0,
data.UserScreenName,
data.Rank,
ratedRank,
fixed ? data.OldRating : data.Rating,
null,
data.Competitions,
null,
null
);
});
currentRatedRank += ratedInTiedUsers;
tiedUsers.length = 0;
ratedInTiedUsers = 0;
}
standingsData.forEach((data) => {
if (lastRank !== data.Rank) applyTiedUsers();
lastRank = data.Rank;
tiedUsers.push(data);
});
applyTiedUsers();
return {contestantAPerf: contestantAPerf, templateResults: templateResults};
}
}
}
getRatedRank(X) {
if (this.rankMemo[X]) return this.rankMemo[X];
return this.rankMemo[X] = this.contestantAPerf.reduce((val, APerf) => val + (1.0 / (1.0 + Math.pow(6.0, ((X - APerf) / 400.0)))), 0);
}
getPerf(ratedRank){
return Math.min(this.getInnerPerf(ratedRank), this.perfLimit);
}
getInnerPerf(ratedRank){
let upper = 6144;
let lower = -2048;
while (upper - lower > 0.5) {
const mid = (upper + lower) / 2;
if (ratedRank - 0.5 > this.getRatedRank(mid)) upper = mid;
else lower = mid;
}
return Math.round((upper + lower) / 2);
}
}
// CONCATENATED MODULE: ./src/libs/utils/atcoderRating.js
//Copyright © 2017 koba-e964.
//from : https://github.com/koba-e964/atcoder-rating-estimator
const finf = bigf(400);
function bigf(n) {
let numerator = 1.0;
let denominator = 1.0;
for (let i = 0; i < n; ++i) {
numerator *= 0.81;
denominator *= 0.9;
}
numerator = (1 - numerator) * 0.81 / 0.19;
denominator = (1 - denominator) * 0.9 / 0.1;
return Math.sqrt(numerator) / denominator;
}
function f(n) {
return (bigf(n) - finf) / (bigf(1) - finf) * 1200.0;
}
/**
* calculate unpositivized rating from performance history
* @param {Number[]} [history] performance history
* @returns {Number} unpositivized rating
*/
function calcRatingFromHistory(history) {
let n = history.length;
let numerator = 0.0;
let denominator = 0.0;
for (let i = n - 1; i >= 0; --i) {
numerator *= 0.9;
numerator += 0.9 * Math.pow(2, history[i] / 800.0);
denominator *= 0.9;
denominator += 0.9;
}
return Math.log2(numerator / denominator) * 800.0 - f(n);
}
/**
* calculate unpositivized rating from last state
* @param {Number} [last] last unpositivized rating
* @param {Number} [perf] performance
* @param {Number} [ratedMatches] count of participated rated contest
* @returns {number} estimated unpositivized rating
*/
function calcRatingFromLast(last, perf, ratedMatches) {
if (ratedMatches === 0) return perf - 1200;
last += f(ratedMatches);
const weight = 9 - 9 * 0.9 ** ratedMatches;
const numerator = weight * (2 ** (last / 800.0)) + 2 ** (perf / 800.0);
const denominator = 1 + weight;
return Math.log2(numerator / denominator) * 800.0 - f(ratedMatches + 1);
}
/**
* (-inf, inf) -> (0, inf)
* @param {Number} [rating] unpositivized rating
* @returns {number} positivized rating
*/
function positivizeRating(rating) {
if (rating >= 400.0) {
return rating;
}
return 400.0 * Math.exp((rating - 400.0) / 400.0);
}
/**
* (0, inf) -> (-inf, inf)
* @param {Number} [rating] positivized rating
* @returns {number} unpositivized rating
*/
function unpositivizeRating(rating) {
if (rating >= 400.0) {
return rating;
}
return 400.0 + 400.0 * Math.log(rating / 400.0);
}
/**
* calculate the performance required to reach a target rate
* @param {Number} [targetRating] targeted unpositivized rating
* @param {Number[]} [history] performance history
* @returns {number} performance
*/
function calcRequiredPerformance(targetRating, history) {
let upper = 10000.0;
let lower = -10000.0;
for (let i = 0; i < 100; ++i) {
const mid = (lower + upper) / 2;
const rating = calcRatingFromHistory([mid].concat(history));
if (targetRating <= rating) upper = mid;
else lower = mid;
}
return lower;
}
// CONCATENATED MODULE: ./src/libs/contest/results/standingsResults.js
class standingsResults_OnDemandResults extends Results{
/**
* @param {Contest} contest
* @param {Results[]} templateResults
*/
constructor(contest, templateResults){
super();
this.Contest = contest;
this.TemplateResults = templateResults;
}
/**
* @param {string} userScreenName
* @return {Result}
*/
getUserResult(userScreenName){
const baseResults = this.TemplateResults[userScreenName];
if (!baseResults) return null;
if (!baseResults.Performance) {
baseResults.InnerPerformance = this.Contest.getInnerPerf(baseResults.RatedRank);
baseResults.Performance = Math.min(baseResults.InnerPerformance, this.Contest.perfLimit);
baseResults.NewRating = Math.round(positivizeRating(calcRatingFromLast(unpositivizeRating(baseResults.OldRating), baseResults.Performance, baseResults.Competitions)));
}
return baseResults;
}
}
// CONCATENATED MODULE: ./src/libs/contest/results/fIxedResults.js
const defaultResult = new Result(false, false, "", 0 , 0 , 0 ,0 ,0,0,0);
class fIxedResults_FixedResults extends Results{
/**
* @param {Result[]} results
*/
constructor(results){
super();
this.resultsDic = {};
results.forEach((result) => {
this.resultsDic[result.UserScreenName] = result;
});
}
/**
* @param {string} userScreenName
* @return {Result}
*/
getUserResult(userScreenName){
return this.resultsDic[userScreenName] || defaultResult;
}
}
// CONCATENATED MODULE: ./src/libs/datas/results.js
/**
* コンテストの結果データを取得します。
*/
class results_ResultsData extends WebData {
constructor(contestScreenName) {
super(
`https://atcoder.jp/contests/${contestScreenName}/results/json`
);
}
}
// CONCATENATED MODULE: ./src/elements/predictor/model/PredictorModel.js
class PredictorModel{
/**
* @param {PredictorModel} [model]
*/
constructor(model){
this.enabled = model.enabled;
this.contest = model.contest;
this.history = model.history;
this.updateInformation(model.information);
this.updateData(model.rankValue, model.perfValue, model.rateValue);
}
/**
* @param {boolean} state
*/
setEnable(state){
this.enabled = state;
}
/**
* @param {string} information
*/
updateInformation(information){
this.information = information;
}
/**
* @param {number} rankValue
* @param {number} perfValue
* @param {number} rateValue
*/
updateData(rankValue, perfValue, rateValue){
this.rankValue = rankValue;
this.perfValue = perfValue;
this.rateValue = rateValue;
}
}
// CONCATENATED MODULE: ./src/elements/predictor/model/calcFromRankModel.js
class calcFromRankModel_CalcFromRankModel extends PredictorModel{
updateData(rankValue, perfValue, rateValue) {
perfValue = this.contest.getPerf(rankValue);
rateValue = positivizeRating(calcRatingFromHistory([perfValue].concat(this.history)));
super.updateData(rankValue, perfValue, rateValue);
}
}
// CONCATENATED MODULE: ./src/elements/predictor/model/calcFromPerfModel.js
class calcFromPerfModel_CalcFromPerfModel extends PredictorModel{
updateData(rankValue, perfValue, rateValue) {
rankValue = this.contest.getRatedRank(perfValue);
rateValue = positivizeRating(calcRatingFromHistory([perfValue].concat(this.history)));
super.updateData(rankValue, perfValue, rateValue);
}
}
// CONCATENATED MODULE: ./src/elements/predictor/model/calcFromRateModel.js
class calcFromRateModel_CalcFromRateModel extends PredictorModel{
updateData(rankValue, perfValue, rateValue) {
perfValue = calcRequiredPerformance(unpositivizeRating(rateValue), this.history);
rankValue = this.contest.getRatedRank(perfValue);
super.updateData(rankValue, perfValue, rateValue);
}
}
// CONCATENATED MODULE: ./src/libs/utils/roundValue.js
function roundValue(value, digit) {
return Math.round(value * (10 ** digit)) / (10 ** digit);
}
// CONCATENATED MODULE: ./src/elements/predictor/script.js
let predictor = new SideMenuElement('predictor','Predictor',/atcoder.jp\/contests\/.+/, dom_default.a, afterAppend);
const firstContestDate = external_moment_default()("2016-07-16 21:00");
const predictorElements = ['predictor-input-rank', 'predictor-input-perf', 'predictor-input-rate', 'predictor-current', 'predictor-reload', 'predictor-tweet'];
async function afterAppend() {
const isStandingsPage = /standings([^\/]*)?$/.test(document.location.href);
const predictorDB = new predictorDB_PredictorDB();
const standingsData = new standings_StandingsData(contestScreenName);
const aperfsData = new aperfs_APerfsData(contestScreenName);
const historyData = await getRatedContestPerformanceHistory();
const contestInformation = await fetchContestInformation(contestScreenName);
/** @type Results */
let results;
/** @type Contest */
let contest;
/** @type PredictorModel */
let model = new PredictorModel({rankValue:0, perfValue:0, rateValue:0, enabled:false, history: historyData});
$('[data-toggle="tooltip"]').tooltip();
if (!shouldEnabledPredictor().verdict) {
model.updateInformation(shouldEnabledPredictor().message);
updateView();
return;
}
try{
await initPredictor();
}
catch (e){
model.updateInformation(e.message);
model.setEnable(false);
updateView();
}
subscribeEvents();
function subscribeEvents() {
$('#predictor-reload').click(async () => {
model.updateInformation('読み込み中…');
$('#predictor-reload').button('loading');
updateView();
await updateStandingsFromAPI();
$('#predictor-reload').button('reset');
updateView();
});
$('#predictor-current').click(function () {
const myResult = contest.templateResults[userScreenName];
if (!myResult) return;
model = new calcFromRankModel_CalcFromRankModel(model);
model.updateData(myResult.RatedRank, model.perfValue, model.rateValue);
updateView();
});
$('#predictor-input-rank').keyup(function (event) {
const inputString = $('#predictor-input-rank').val();
if (!isFinite(inputString)) return;
const inputNumber = parseInt(inputString);
model = new calcFromRankModel_CalcFromRankModel(model);
model.updateData(inputNumber, 0, 0);
updateView();
});
$('#predictor-input-perf').keyup(function (event) {
const inputString = $('#predictor-input-perf').val();
if (!isFinite(inputString)) return;
const inputNumber = parseInt(inputString);
model = new calcFromPerfModel_CalcFromPerfModel(model);
model.updateData(0, inputNumber, 0);
updateView();
});
$('#predictor-input-rate').keyup(function (event) {
const inputString = $('#predictor-input-rate').val();
if (!isFinite(inputString)) return;
const inputNumber = parseInt(inputString);
model = new calcFromRateModel_CalcFromRateModel(model);
model.updateData(0, 0, inputNumber);
updateView();
});
}
async function initPredictor(){
let aPerfs;
let standings;
try{
standings = await standingsData.update();
}
catch (e){
throw new Error('順位表の取得に失敗しました。');
}
try {
let result =
await (standings.Fixed ?
getAPerfsFromLocalData().catch(() => getAPerfsFromAPI()) :
getAPerfsFromAPI().catch(() => getAPerfsFromLocalData()));
aPerfs = result.data;
model.updateInformation(result.message);
}
catch (e) {
throw new Error('APerfの取得に失敗しました。');
}
async function getAPerfsFromAPI(){
let data = await aperfsData.update();
return {data : data, message : `最終更新 : ${external_moment_default()().format('HH:mm:ss')}`};
}
async function getAPerfsFromLocalData(){
let data = await predictorDB.getData("APerfs", contestScreenName);
return {data : data, message : '保存されたAPerfから計算しています。'};
}
await updateData(aPerfs, standings);
model.setEnable(true);
if(isStandingsPage) {
$('thead > tr').append('<th class="standings-result-th" style="width:84px;min-width:84px;">perf</th><th class="standings-result-th" style="width:168px;min-width:168px;">レート変化</th>');
new MutationObserver(addPerfToStandings).observe(document.getElementById('standings-tbody'), { childList: true });
}
updateView();
}
async function updateStandingsFromAPI(){
try{
const shouldEnabled = shouldEnabledPredictor();
if (!shouldEnabled.verdict) throw new Error(shouldEnabled.message);
const standings = await standingsData.update();
await updateData(contest.aPerfs, standings);
model.updateInformation(`最終更新 : ${external_moment_default()().format('HH:mm:ss')}`);
model.setEnable(true);
}
catch(e){
model.updateInformation(e.message);
model.setEnable(false);
}
}
async function updateData(aperfs, standings) {
if (Object.keys(aperfs).length === 0) {
throw new Error('APerfのデータが提供されていません');
}
predictorDB.setData('APerfs', contestScreenName, aperfs);
contest = new contest_Contest(contestScreenName, contestInformation, standings, aperfs);
model.contest = contest;
await updateResultsData();
}
function updateView() {
const roundedRankValue = isFinite(model.rankValue) ? roundValue(model.rankValue, 2) : '';
const roundedPerfValue = isFinite(model.perfValue) ? roundValue(model.perfValue, 2) : '';
const roundedRateValue = isFinite(model.rateValue) ? roundValue(model.rateValue, 2) : '';
$("#predictor-input-rank").val(roundedRankValue);
$("#predictor-input-perf").val(roundedPerfValue);
$("#predictor-input-rate").val(roundedRateValue);
$("#predictor-alert").html(`<h5 class='sidemenu-txt'>${model.information}</h5>`);
if (model.enabled) enabled();
else disabled();
if (isStandingsPage) {
addPerfToStandings();
}
function enabled() {
$('#predictor-reload').button('reset');
predictorElements.forEach(element => {
$(`#${element}`).removeAttr("disabled");
});
}
function disabled() {
$('#predictor-reload').button('reset');
predictorElements.forEach(element => {
$(`#${element}`).attr("disabled", true);
});
}
}
function shouldEnabledPredictor(){
if (!startTime.isBefore())
return {'verdict':false ,'message':'コンテストは始まっていません'};
if (external_moment_default()(startTime) < firstContestDate)
return {'verdict':false ,'message':'現行レートシステム以前のコンテストです'};
if (contestInformation.RatedRange[0] > contestInformation.RatedRange[1])
return {'verdict':false ,'message':'ratedなコンテストではありません'};
return {'verdict':true ,'message':''};
}
//全員の結果データを更新する
async function updateResultsData() {
if (contest.standings.Fixed && contest.IsRated){
let rawResult = await new results_ResultsData(contestScreenName).update();
rawResult.sort((a, b) => a.Place !== b.Place ? a.Place - b.Place : b.OldRating - a.OldRating);
let sortedStandingsData = Array.from(contest.standings.StandingsData).filter(x => x.TotalResult.Count !== 0);
sortedStandingsData.sort(
(a, b) =>
a.TotalResult.Count === 0 && b.TotalResult.Count === 0 ? 0 :
a.TotalResult.Count === 0 ? 1 :
b.TotalResult.Count === 0 ? -1 :
a.Rank !== b.Rank ? a.Rank - b.Rank :
b.OldRating !== a.OldRating ? b.OldRating - a.OldRating :
a.UserIsDeleted ? -1 :
b.UserIsDeleted ? 1 : 0
);
let lastPerformance = contest.perfLimit;
let deletedCount = 0;
results = new fIxedResults_FixedResults(
sortedStandingsData.map((data, index) => {
let result = rawResult[index - deletedCount];
if (!result || data.OldRating !== result.OldRating) {
deletedCount++;
result = null;
}
return new Result(
result ? result.IsRated : false,
data.TotalResult.Count !== 0,
data.UserScreenName,
data.Rank,
-1,
data.OldRating,
result ? result.NewRating : 0,
0,
result && result.IsRated ? lastPerformance = result.Performance : lastPerformance,
result ? result.InnerPerformance : 0
);
})
);
}
else{
results = new standingsResults_OnDemandResults(contest, contest.templateResults);
}
}
//結果データを順位表に追加する
function addPerfToStandings() {
$('.standings-perf , .standings-rate').remove();
$('#standings-tbody > tr').each((index, elem) => {
if (elem.childNodes.length <= 3) {
let unparticipatedResultCell = elem.getElementsByClassName("standings-result")[0];
unparticipatedResultCell.setAttribute("colspan", parseInt(unparticipatedResultCell.getAttribute("colspan")) + 2);
return;
}
const userName = $('.standings-username .username', elem).text();
const result = results.getUserResult(userName);
const perfElem = !result || !result.IsSubmitted ? '-' : getRatingSpan(result.Performance);
const rateElem = !result ? '-' : result.IsRated && contest.IsRated ? getRatingChangeElem(result.OldRating, result.NewRating) : getUnratedElem(result.OldRating);
$(elem).append(`<td class="standings-result standings-perf">${perfElem}</td>`);
$(elem).append(`<td class="standings-result standings-rate">${rateElem}</td>`);
function getRatingChangeElem(oldRate, newRate) {
return `<span class="bold">${getRatingSpan(oldRate)}</span> → <span class="bold">${getRatingSpan(newRate)}</span> <span class="grey">(${(newRate >= oldRate ? '+' : '')}${newRate - oldRate})</span>`;
}
function getUnratedElem(rate) {
return `<span class="bold">${getRatingSpan(rate)}</span> <span class="grey">(unrated)</span>`;
}
function getRatingSpan(rate) {
return `<span class="user-${getColor(rate)}">${rate}</span>`;
}
});
}
}
// EXTERNAL MODULE: ./src/elements/estimator/dom.html
var estimator_dom = __webpack_require__(4);
var estimator_dom_default = /*#__PURE__*/__webpack_require__.n(estimator_dom);
// CONCATENATED MODULE: ./src/elements/estimator/state/EstimatorModel.js
class EstimatorModel{
constructor(inputValue, perfHistory){
this.inputDesc = "";
this.resultDesc = "";
this.perfHistory = perfHistory;
this.updateInput(inputValue);
}
updateInput(value){
this.inputValue = value;
this.resultValue = this.calcResult(value);
}
toggle(){}
/**
* @param {Number} [input]
* @return {Number}
*/
calcResult(input){ return input }
}
// CONCATENATED MODULE: ./src/elements/estimator/state/CalcRatingModel.js
class CalcRatingModel_CalcRatingModel extends EstimatorModel{
constructor(inputValue, perfHistory){
super(inputValue, perfHistory);
this.inputDesc = "パフォーマンス";
this.resultDesc = "到達レーティング";
}
toggle(){
return new CalcPerfModel_CalcPerfModel(this.resultValue, this.perfHistory);
}
calcResult(input){
return positivizeRating(calcRatingFromHistory([input].concat(this.perfHistory)));
}
}
// CONCATENATED MODULE: ./src/elements/estimator/state/CalcPerfModel.js
class CalcPerfModel_CalcPerfModel extends EstimatorModel{
constructor(inputValue, perfHistory){
super(inputValue, perfHistory);
this.inputDesc = "目標レーティング";
this.resultDesc = "必要パフォーマンス";
}
toggle(){
return new CalcRatingModel_CalcRatingModel(this.resultValue, this.perfHistory);
}
calcResult(input){
return calcRequiredPerformance(unpositivizeRating(input), this.perfHistory);
}
}
// CONCATENATED MODULE: ./src/atcoder-lib/utils.js
// format
if (typeof String.prototype.format === 'undefined') {
String.prototype.format = function(arg) {
var rep_fn = undefined;
if (typeof arg == "object") {
rep_fn = function(m, k) { return arg[k];};
} else {
var args = arguments;
rep_fn = function(m, k) { return args[parseInt(k)];};
}
return this.replace(/\{(\w+)\}/g, rep_fn);
}
}
// array search
function has(a, val) {
return a.indexOf(val) != -1;
}
// other util
function arrayToSet(a) {
var s = new Set();
for (var i in a) s.add(a[i]);
return s;
}
function setToArray(s) {
var a = [];
s.forEach(function(val) { a.push(val);});
return a;
}
// cookie
var COOKIE_EXPIRES = 10000;
function setCookie(key, val, expires) {
Cookies.set(key, val, { expires: expires || COOKIE_EXPIRES});
}
function getCookie(key) {
return Cookies.getJSON(key);
}
function getCookieBool(key) {
return (Cookies.get(key) === 'true');
}
function delCookie(key) {
Cookies.remove(key);
}
// local storage
function setLS(key, val) {
try {
localStorage.setItem(key, JSON.stringify(val));
} catch(error) {
console.log(error);
}
}
function getLS(key) {
var val = localStorage.getItem(key);
return val?JSON.parse(val):val;
}
function delLS(key) {
localStorage.removeItem(key);
}
// migrate Cookie to LocalStorage
{
var val;
if (val = getCookie('fav')) {
for (var i in val) favSet.add(val[i]);
storeFavs();
delCookie('fav');
}
var keys = ['plain_editor','auto_height','defaultLang',
'show_affiliation','show_fav_btn','show_fav_only','show_rated_only'];
for (var i = 0; i < keys.length; i++) {
if (val = Cookies.get(keys[i])) {
setLS(keys[i], val);
delCookie(keys[i]);
}
}
}
// server time
var timeDelta = getCookie('timeDelta');
if (typeof timeDelta === 'undefined') {
timeDelta = 0;
setCookie('timeDelta', 0, 1/24.0);
$.ajax('/servertime?ts={}'.format(moment().unix())).done(function(serverTime) {
serverTime = moment(serverTime);
if (!serverTime.isValid()) return;
timeDelta = serverTime.diff(moment());
setCookie('timeDelta', timeDelta, 1/24.0);
});
}
function getServerTime() { return moment().add(timeDelta);}
// escape
function E(str) {
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
// toRegExp
function toRegExp(pattern) {
pattern = pattern
.replace(/[-\/\\^+.()|[\]{}]/g, '\\$&')
.replace(/\?/g, '.')
.replace(/\*/g, '.*');
return new RegExp('^'+pattern);
}
// randint
function rand(range) {
return Math.floor(Math.random()*(range[1]-range[0]))+range[0];
}
// clipboard
function copy(textVal){
var copyFrom = document.createElement("textarea");
copyFrom.textContent = textVal;
var bodyElm = document.getElementsByTagName("body")[0];
bodyElm.appendChild(copyFrom);
copyFrom.select();
var retVal = document.execCommand('copy');
bodyElm.removeChild(copyFrom);
return retVal;
}
// fit font size
(function($) {
$.fn.fitFontSize = function(width, len, max) {
$(this).css('font-size', Math.min(width/len, max)+'px');
};
})(jQuery);
// user favs
var favSet;
function storeFavs() {
setLS('fav', setToArray(favSet));
}
function reloadFavs() {
favSet = arrayToSet(getLS('fav') || []);
}
function toggleFav(val) {
reloadFavs();
var res;
if (favSet.has(val)) {
favSet.delete(val);
res = false;
} else {
favSet.add(val);
res = true;
}
storeFavs();
return res; // has val now
}
// CONCATENATED MODULE: ./src/libs/utils/twitter.js
/**
*
* @param {string} [content]
* @param {string} [url]
* @return {string}
*/
function GetEmbedTweetLink(content, url){
return `https://twitter.com/share?text=${encodeURI(content)}&url=${encodeURI(url)}`
}
// CONCATENATED MODULE: ./src/elements/estimator/script.js
let estimator = new SideMenuElement('estimator','Estimator',/atcoder.jp/, estimator_dom_default.a, script_afterAppend);
async function script_afterAppend() {
const estimatorInputSelector = $("#estimator-input");
const estimatorResultSelector = $("#estimator-res");
let model = GetModelFromStateCode(getLS("sidemenu_estimator_state"), getLS("sidemenu_estimator_value"), await getRatedContestPerformanceHistory());
updateView();
$("#estimator-toggle").click(function () {
model = model.toggle();
updateLocalStorage();
updateView();
});
estimatorInputSelector.keyup(() => {
updateModel();
updateLocalStorage();
updateView();
});
/** modelをinputの値に応じて更新 */
function updateModel() {
const inputString = estimatorInputSelector.val();
if (!isFinite(inputString)) return;
const inputNumber = parseInt(inputString);
model.updateInput(inputNumber);
}
/** modelの状態をLSに保存 */
function updateLocalStorage() {
setLS("sidemenu_estimator_value", model.inputValue);
setLS("sidemenu_estimator_state", model.constructor.name);
}
/** modelを元にviewを更新 */
function updateView() {
const roundedInput = roundValue(model.inputValue, 2);
const roundedResult = roundValue(model.resultValue, 2);
$("#estimator-input-desc").text(model.inputDesc);
$("#estimator-res-desc").text(model.resultDesc);
estimatorInputSelector.val(roundedInput);
estimatorResultSelector.val(roundedResult);
const tweetStr = `AtCoderのハンドルネーム: ${userScreenName}\n${model.inputDesc}: ${roundedInput}\n${model.resultDesc}: ${roundedResult}\n`;
$('#estimator-tweet').attr("href", GetEmbedTweetLink(tweetStr, "https://gf.qytechs.cn/ja/scripts/369954-ac-predictor"));
}
}
const models = [CalcPerfModel_CalcPerfModel, CalcRatingModel_CalcRatingModel];
/**
* LocalStorageに保存されたステートコードから状態を復元します
* @param {string} [state] ステートを示す文字列(型名)
* @param {number} [value] 最初に入る値
* @param {number[]} [history] パフォーマンス履歴(時間降順)
* @return {EstimatorModel} 構築されたモデル
*/
function GetModelFromStateCode(state, value, history) {
let model = models.find(model => model.name === state);
if (!model) model = CalcPerfModel_CalcPerfModel;
return new model(value, history);
}
// CONCATENATED MODULE: ./src/main.js
// ==UserScript==
// @name ac-predictor
// @namespace http://ac-predictor.azurewebsites.net/
// @version 1.2.1
// @description コンテスト中にAtCoderのパフォーマンスを予測します
// @author keymoon
// @license MIT
// @supportURL https://github.com/key-moon/ac-predictor.user.js/issues
// @match https://atcoder.jp/*
// @exclude https://atcoder.jp/*/json
// ==/UserScript==
initializeID();
let main_sidemenu = new sidemenu_SideMenu();
main_sidemenu.addElement(predictor);
main_sidemenu.addElement(estimator);
/***/ })
/******/ ]);