|
|
|
@ -1,7 +1,7 @@
|
|
|
|
|
// ==UserScript==
|
|
|
|
|
// @name Blaseball Userscript Options
|
|
|
|
|
// @namespace https://glaceon.social/@monorail
|
|
|
|
|
// @version 0.4
|
|
|
|
|
// @version 0.5
|
|
|
|
|
// @description Add a userscript options menu to the account settings page on the blaseball website.
|
|
|
|
|
// @author monorail
|
|
|
|
|
// @match https://blaseball.com/*
|
|
|
|
@ -11,7 +11,7 @@
|
|
|
|
|
// @updateURL https://git.hollymcfarland.com/monorail/blaseball-userscript-options/raw/branch/main/blaseball-userscript-options.user.js
|
|
|
|
|
// ==/UserScript==
|
|
|
|
|
|
|
|
|
|
document._BLASEBALL_USERSCRIPT_OPTIONS_VERSION = "0.4";
|
|
|
|
|
document._BLASEBALL_USERSCRIPT_OPTIONS_VERSION = "0.5";
|
|
|
|
|
(function() {
|
|
|
|
|
"use strict";
|
|
|
|
|
|
|
|
|
@ -25,6 +25,12 @@ document._BLASEBALL_USERSCRIPT_OPTIONS_VERSION = "0.4";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const namespaceObj = JSON.parse((storage.getItem(namespace)) ?? "{}");
|
|
|
|
|
|
|
|
|
|
if (!("_hidden" in namespaceObj)) {
|
|
|
|
|
// Include an internal option called "_hidden" for every script that determines if the options
|
|
|
|
|
// should be shown or not
|
|
|
|
|
namespaceObj["_hidden"] = {value: true, type: "checkbox"};
|
|
|
|
|
}
|
|
|
|
|
if (!(option in namespaceObj)) {
|
|
|
|
|
namespaceObj[option] = {value: defaultValue, type: type};
|
|
|
|
|
}
|
|
|
|
@ -52,22 +58,95 @@ document._BLASEBALL_USERSCRIPT_OPTIONS_VERSION = "0.4";
|
|
|
|
|
optionsHeader.innerText = "Userscript Options";
|
|
|
|
|
|
|
|
|
|
for (const namespace of Object.keys(namespaces)) {
|
|
|
|
|
const namespaceObj = JSON.parse(storage.getItem(namespace) ?? "{}");
|
|
|
|
|
|
|
|
|
|
const hidden = namespaceObj["_hidden"].value;
|
|
|
|
|
|
|
|
|
|
const title = document.createElement("h3");
|
|
|
|
|
title.innerText = namespace;
|
|
|
|
|
settingsNode.appendChild(title);
|
|
|
|
|
title.innerText = `${namespace} ${hidden ? "▸" : "▾"}`;
|
|
|
|
|
const optionsDiv = document.createElement("div");
|
|
|
|
|
optionsDiv.classList.add("_script_options");
|
|
|
|
|
if (hidden) {
|
|
|
|
|
optionsDiv.classList.add("_hidden");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const namespaceObj = JSON.parse(storage.getItem(namespace) ?? "{}");
|
|
|
|
|
// This inner div is used so that we know how tall the outer div
|
|
|
|
|
// should be when expanding, it has no other purpose
|
|
|
|
|
const staticDiv = document.createElement("div");
|
|
|
|
|
|
|
|
|
|
title.addEventListener('click', function() {
|
|
|
|
|
const hidden = !document._BLASEBALL_USERSCRIPT_OPTIONS_GET(namespace, "_hidden");
|
|
|
|
|
document._BLASEBALL_USERSCRIPT_OPTIONS_SET(namespace, "_hidden", hidden);
|
|
|
|
|
this.innerText = `${namespace} ${hidden ? "▸" : "▾"}`;
|
|
|
|
|
|
|
|
|
|
// This next part is a bit cursed, and it took me a long time to figure out.
|
|
|
|
|
// Each script keeps its options inside of two divs, like this:
|
|
|
|
|
|
|
|
|
|
// <h3>Script Name</h3>
|
|
|
|
|
// <div> <!-- "outer" -->
|
|
|
|
|
// <div> <!-- "inner" -->
|
|
|
|
|
// <!-- Option 1 -->
|
|
|
|
|
// <!-- Option 2 -->
|
|
|
|
|
// </div>
|
|
|
|
|
// </div>
|
|
|
|
|
|
|
|
|
|
// The reason for this is that I want to animate "outer" smoothly between a
|
|
|
|
|
// height of 0 and `auto`. But you can't transition to or from `auto`, only
|
|
|
|
|
// numeric values. "inner" exists only to be the same size as "outer" would be
|
|
|
|
|
// if its height was set to `auto`. Using Javascript, "outer" is given an
|
|
|
|
|
// inline height of `${inner.clientHeight}px`. The "_hidden" class sets height
|
|
|
|
|
// to 0, and overrides the inline style with `!important`. So by adding and
|
|
|
|
|
// removing that class, the animation moves between a kind of "fake auto" an 0.
|
|
|
|
|
|
|
|
|
|
// In principle, this works, but there's one problem: At the time "outer" and
|
|
|
|
|
// "inner" are created, "inner" doesn't yet have any height because it hasn't
|
|
|
|
|
// been rendered. Instead, we have to set the inline style on "outer" at some
|
|
|
|
|
// other time: Right now, inside the click handler. This *almost* works.
|
|
|
|
|
|
|
|
|
|
// Because the inline style is set and the class is added in such a short time,
|
|
|
|
|
// the browser doesn't actually handle the CSS in between those events. It
|
|
|
|
|
// tries to optimize by only waking up the CSS engine after both things are
|
|
|
|
|
// done. But that's a problem, because the CSS engine doesn't see "this
|
|
|
|
|
// element has an inline height of [whatever] that's being overwritten by a
|
|
|
|
|
// class with 0 height". It only sees that it currently has a height of 0, and
|
|
|
|
|
// last time it looked, it had a height of `auto`. It can't animate between
|
|
|
|
|
// those, so in the case of the first click being to close the div, the
|
|
|
|
|
// animation doesn't play.
|
|
|
|
|
|
|
|
|
|
// That's why the following code has `setTimeout(() => {...}, 0)`. Naively,
|
|
|
|
|
// this seems unecessary. But by telling the browser to run that function "in
|
|
|
|
|
// the future" (even though it will actually be right away), it doesn't try to
|
|
|
|
|
// wait for a more optimal time to process CSS. It immediately handles the
|
|
|
|
|
// inline style, which has no visible effect, but it now knows that element
|
|
|
|
|
// has a numeric height and can be animated when it later processes the added
|
|
|
|
|
// class.
|
|
|
|
|
|
|
|
|
|
const optionsDiv = this.nextSibling;
|
|
|
|
|
optionsDiv.style.height = `${optionsDiv.children[0].clientHeight}px`;
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
if (hidden) {
|
|
|
|
|
optionsDiv.classList.add("_hidden");
|
|
|
|
|
} else {
|
|
|
|
|
optionsDiv.classList.remove("_hidden");
|
|
|
|
|
}
|
|
|
|
|
}, 0);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
settingsNode.appendChild(title);
|
|
|
|
|
settingsNode.appendChild(optionsDiv);
|
|
|
|
|
optionsDiv.appendChild(staticDiv);
|
|
|
|
|
|
|
|
|
|
for (const option of namespaces[namespace]) {
|
|
|
|
|
const isCheckbox = namespaceObj[option].type === "checkbox";
|
|
|
|
|
|
|
|
|
|
const newDiv = document.createElement("div");
|
|
|
|
|
settingsNode.appendChild(newDiv);
|
|
|
|
|
newDiv.classList.add("_script_option");
|
|
|
|
|
staticDiv.appendChild(newDiv);
|
|
|
|
|
|
|
|
|
|
const label = document.createElement("label");
|
|
|
|
|
label.for = `${namespace} - ${option}`;
|
|
|
|
|
label.innerText = option;
|
|
|
|
|
label.style.float = "left";
|
|
|
|
|
newDiv.appendChild(label);
|
|
|
|
|
|
|
|
|
|
const input = document.createElement("input");
|
|
|
|
@ -78,8 +157,6 @@ document._BLASEBALL_USERSCRIPT_OPTIONS_VERSION = "0.4";
|
|
|
|
|
input.value = namespaceObj[option].value;
|
|
|
|
|
}
|
|
|
|
|
input.name = `${namespace} - ${option}`;
|
|
|
|
|
input.style.float = "right";
|
|
|
|
|
input.style.backgroundColor = "white";
|
|
|
|
|
newDiv.appendChild(input);
|
|
|
|
|
|
|
|
|
|
const valueMethods = {
|
|
|
|
@ -107,6 +184,45 @@ document._BLASEBALL_USERSCRIPT_OPTIONS_VERSION = "0.4";
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function applyCSS() {
|
|
|
|
|
const head = document.head || document.getElementsByTagName("head")[0];
|
|
|
|
|
const css = `
|
|
|
|
|
._script_option {
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* all script options except first in each block */
|
|
|
|
|
._script_option + ._script_option {
|
|
|
|
|
margin-top: 1rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
._script_option > label {
|
|
|
|
|
float: left;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
._script_option > input {
|
|
|
|
|
float: right;
|
|
|
|
|
background-color: white;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
._script_options {
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
transition: height 0.2s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
._hidden {
|
|
|
|
|
height: 0 !important;
|
|
|
|
|
}
|
|
|
|
|
`;
|
|
|
|
|
const style = document.createElement("style");
|
|
|
|
|
style.id = "_blaseball-userscript-options_style";
|
|
|
|
|
|
|
|
|
|
head.appendChild(style);
|
|
|
|
|
style.appendChild(document.createTextNode(css));
|
|
|
|
|
}
|
|
|
|
|
applyCSS();
|
|
|
|
|
|
|
|
|
|
if (document.hasOwnProperty("_BLASEBALL_USERSCRIPT_REGISTER")) {
|
|
|
|
|
document._BLASEBALL_USERSCRIPT_REGISTER("Userscript Options", callback, (mutations) => (document.querySelector(".account__content")));
|
|
|
|
|
} else {
|
|
|
|
|