1196 lines
55 KiB
JavaScript
1196 lines
55 KiB
JavaScript
if (enabledMods.includes("mods/betterMenuScreens.js")) {
|
|
const properties = {
|
|
meta: [
|
|
{name: "name", type: "string", viewOnly: true, required: true},
|
|
{name: "category", type: "string", required: true},
|
|
{name: "desc", type: "string"},
|
|
{name: "behavior", type: "string"},
|
|
{name: "alias", type: "string"},
|
|
{name: "seed", type: "string"},
|
|
{name: "color", type: ["color", "array"], viewOnly: true},
|
|
{name: "breakInto", type: ["string", "array"], viewOnly: true},
|
|
{name: "darkText", type: "boolean"},
|
|
{name: "baby", type: "string"},
|
|
{name: "id", type: "number", viewOnly: true, creatorIgnore: true}
|
|
],
|
|
default_properties: [
|
|
{name: "viscosity", type: "number"},
|
|
{name: "density", type: "number"},
|
|
{name: "hardness", type: "number"},
|
|
{name: "maxSize", type: "number"},
|
|
{name: "conduct", type: "number"},
|
|
{name: "foodNeed", type: "number"},
|
|
{name: "stain", type: "number"},
|
|
],
|
|
data: [
|
|
{name: "hidden", type: "boolean"},
|
|
{name: "insulate", type: "boolean"},
|
|
{name: "noMix", type: "boolean"},
|
|
{name: "isFood", type: "boolean"},
|
|
{name: "forceAutoGen", type: "boolean"},
|
|
{name: "customColor", type: "boolean"},
|
|
{name: "ignoreAir", type: "boolean"},
|
|
{name: "excludeRandom", type: "boolean"},
|
|
],
|
|
burn: [
|
|
{name: "burn", type: "number"},
|
|
{name: "burnTime", type: "number"},
|
|
{name: "burnInto", type: ["string", "array"], viewOnly: true},
|
|
{name: "burning", type: "boolean"},
|
|
{name: "fireElement", type: ["string", "array"], viewOnly: true},
|
|
{name: "fireColor", type: ["color", "array"], viewOnly: true},
|
|
],
|
|
flip: [
|
|
{name: "flipX", type: "boolean"},
|
|
{name: "flipY", type: "boolean"},
|
|
{name: "flippableX", type: "boolean"},
|
|
{name: "flippableY", type: "boolean"},
|
|
],
|
|
states: [
|
|
{name: "state", type: "string"},
|
|
{name: "stateHigh", type: "string"},
|
|
{name: "stateHighName", type: "string"},
|
|
{name: "stateHighColor", type: "color"},
|
|
{name: "stateHighColorMultiplier", type: "number"},
|
|
{name: "stateLow", type: "string"},
|
|
{name: "stateLowName", type: "string"},
|
|
{name: "stateLowColor", type: "color"},
|
|
{name: "stateLowColorMultiplier", type: "number"},
|
|
],
|
|
temperature: [
|
|
{name: "temp", type: "number"},
|
|
{name: "tempHigh", type: "number"},
|
|
{name: "extraTempHigh", type: "number"},
|
|
{name: "tempLow", type: "number"},
|
|
{name: "extraTempLow", type: "number"},
|
|
]
|
|
}
|
|
|
|
const br = () => document.createElement("br");
|
|
const createDiv = () => document.createElement("div");
|
|
const span = (innerText) => {
|
|
const element = document.createElement("span");
|
|
element.innerText = innerText;
|
|
return element;
|
|
}
|
|
const createInput = (type, disabled, id, className="") => {
|
|
const element = document.createElement("input");
|
|
element.id = id;
|
|
element.className = className;
|
|
element.type = type;
|
|
element.disabled = disabled;
|
|
return element;
|
|
}
|
|
|
|
const defaultSettings = {clearElements: false, allowFreeRemoval: false};
|
|
|
|
const toggleSetting = (setting, target) => {
|
|
target.value = target.value == "ON" ? "OFF" : "ON";
|
|
target.setAttribute("state", target.getAttribute("state") == "1" ? "0" : "1");
|
|
const settings_ = Storage.get("settings", defaultSettings);
|
|
settings_[setting] = !settings_[setting];
|
|
Storage.set("settings", settings_);
|
|
}
|
|
|
|
class Storage {
|
|
static get(id, fallback = null, setOnNull = false) {
|
|
const res = JSON.parse(localStorage.getItem(`elementsManager/${id}`));
|
|
if (!res && fallback && setOnNull) {
|
|
Storage.set(id, fallback);
|
|
}
|
|
return res ?? fallback;
|
|
}
|
|
static set(id, value) {
|
|
localStorage.setItem(`elementsManager/${id}`, JSON.stringify(value));
|
|
}
|
|
static remove(id) {
|
|
localStorage.removeItem(`elementsManager/${id}`);
|
|
}
|
|
static append(id, value) {
|
|
const current = Storage.get(id, []);
|
|
current.push(value);
|
|
Storage.set(id, current);
|
|
}
|
|
static filter(id, condition) {
|
|
Storage.set(id, Storage.get(id, []).filter(condition));
|
|
}
|
|
}
|
|
|
|
const importElements = (elements_) => {
|
|
const settings = Storage.get("settings", {clearElements: false, allowFreeRemoval: false}, true);
|
|
if (settings.clearElements) {
|
|
Storage.set("elements", elements_);
|
|
} else {
|
|
const beforeElements = Storage.get("elements", []);
|
|
Storage.set("elements", beforeElements.concat(elements_));
|
|
}
|
|
alert(`Successfully imported ${elements_.length} element${elements_.length != 1 ? "s" : ""}`)
|
|
}
|
|
|
|
const exportElements = () => {
|
|
let element = document.createElement('a');
|
|
const blob = new Blob([JSON.stringify(Storage.get("elements", []))], {type: "application/json"});
|
|
const url = URL.createObjectURL(blob);
|
|
element.setAttribute('href', url);
|
|
element.setAttribute('download', "elements.json");
|
|
|
|
document.body.appendChild(element);
|
|
|
|
element.click();
|
|
|
|
document.body.removeChild(element);
|
|
}
|
|
|
|
const cssInject = () => {
|
|
const style = document.createElement("style");
|
|
style.innerHTML = `.categoryTitle {
|
|
font-size: 1.25em;
|
|
}
|
|
#elementManagerParent input[type="number"], #elementManagerParent input[type="text"], #elementCreatorParent input[type="number"], #elementCreatorParent input[type="text"] {
|
|
background-color: black;
|
|
vertical-align: middle;
|
|
margin-left: 5px;
|
|
margin-right: 5px;
|
|
border: rgb(150, 150, 150) 1px solid;
|
|
border-radius: 20px;
|
|
padding: 0.5em;
|
|
color: white;
|
|
font-size: 1em;
|
|
font-family: Arial, Helvetica, sans-serif;
|
|
}
|
|
#elementsManagerFilter {
|
|
background-color: rgb(66, 66, 66);
|
|
vertical-align: middle;
|
|
margin-left: 5px;
|
|
margin-right: 5px;
|
|
width: 75%;
|
|
color: white;
|
|
font-size: 1em;
|
|
font-family: 'Press Start 2P', Arial;
|
|
padding: 0.5em;
|
|
border: none;
|
|
display: inline-block;
|
|
}
|
|
#elementsManagerFilter:focus {
|
|
outline: none;
|
|
background-color: rgb(60, 60, 60);
|
|
}
|
|
#elementsList li span:hover, #customElementsList li span:hover, #deletedElementsList li span:hover, .hoverableText:hover {
|
|
cursor: pointer;
|
|
color: rgb(200, 200, 200);
|
|
}
|
|
#elementAdd {
|
|
background-color: rgb(66, 66, 66);
|
|
color: white;
|
|
font-size: 1em;
|
|
font-family: 'Press Start 2P', Arial;
|
|
width: auto;
|
|
padding: 0.5em;
|
|
border: none;
|
|
outline: none;
|
|
display: inline-block;
|
|
vertical-align: middle;
|
|
margin-left: 5px;
|
|
margin-right: 5px;
|
|
}
|
|
#elementAdd:hover {
|
|
cursor: pointer;
|
|
background-color: rgb(60, 60, 60);
|
|
}
|
|
#elementsManagerSettingsButton {
|
|
background-color: rgb(66, 66, 66);
|
|
color: white;
|
|
font-size: 1em;
|
|
font-family: 'Press Start 2P', Arial;
|
|
width: auto;
|
|
padding: 0.5em;
|
|
border: none;
|
|
outline: none;
|
|
display: inline-block;
|
|
vertical-align: middle;
|
|
margin-left: 5px;
|
|
margin-right: 5px;
|
|
}
|
|
#elementsManagerSettingsButton:hover {
|
|
cursor: pointer;
|
|
background-color: rgb(60, 60, 60);
|
|
}
|
|
.plusButton {
|
|
position: absolute;
|
|
right: 0px;
|
|
top: 0px;
|
|
font-size: 2em;
|
|
padding: 5px;
|
|
text-align: center;
|
|
border: 1px solid #fff;
|
|
background-color: #1da100;
|
|
z-index: 12;
|
|
}
|
|
.plusButton:hover {
|
|
background-color: #29d902;
|
|
}
|
|
.elementRemoveButton {
|
|
color: red
|
|
}
|
|
#elementsList li .elementRemoveButton:hover, #customElementsList li .elementRemoveButton:hover {
|
|
color: darkred;
|
|
cursor: pointer;
|
|
}
|
|
.createButton {
|
|
width: auto;
|
|
padding: 5px;
|
|
vertical-align: middle;
|
|
font-size: 1.25em;
|
|
display: block;
|
|
margin-left: 5px;
|
|
margin-right: 5px;
|
|
padding: 5px;
|
|
text-align: center;
|
|
border: 1px solid #1da100;
|
|
border-radius: 10px;
|
|
}
|
|
.createButton:hover {
|
|
background-color: #1da100;
|
|
}`
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
// ugly way of doing it but probably works
|
|
const checkType = (key, value) => {
|
|
if (key == "behavior" && (typeof value == "function" || (value instanceof Array && value.filter(e => e instanceof Array && e.filter(s => typeof s == "string").length == e.length).length == value.length))) return true;
|
|
else if (key == "behavior") return false;
|
|
if (["darkText", "hidden", "insulate", "noMix", "isFood", "forceAutoGen", "customColor", "ignoreAir", "excludeRandom", "burning", "flipX", "flipY", "flippableX", "flippableY"].includes(key) && typeof value != "boolean") return false;
|
|
if (["name", "category", "desc", "alias", "seed", "baby", "state", "stateHigh", "stateHighName", "stateHighColor", "stateLow", "stateLowNmae", "stateLowColor"].includes(key) && typeof value != "string") return false;
|
|
if (["id", "burn", "burnTime", "stateHighColorMultiplier", "stateLowColorMutliplier", "temp", "tempHigh", "extraTempHigh", "tempLow", "extraTempLow"].includes(key) && typeof value != "number") return false;
|
|
if (["color", "breakInto", "burnInto", "fireElement", "fireColor"].includes(key)) {
|
|
if (value instanceof Array) return value.filter(l => typeof l == "string").length == value.length;
|
|
if (typeof value != "string") return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const loadChanges = () => {
|
|
const newElements = Storage.get("elements", []);
|
|
for (const element of newElements) {
|
|
const element_ = element;
|
|
if (Object.keys(behaviors).includes(element_["behavior"])) element_["behavior"] = behaviors[element_["behavior"]];
|
|
elements[element.name] = {};
|
|
// elements[element.name] = element_;
|
|
for (const key of Object.keys(element_)) {
|
|
const val = element_[key];
|
|
if (checkType(key, val)) elements[element.name][key] = val;
|
|
else if (["name", "category"].includes(key)) elements[element.name][key] = key == "name" ? "NewElement" : "other";
|
|
}
|
|
}
|
|
const changes = Storage.get("changes", []);
|
|
for (const change of changes) {
|
|
for (const key of Object.keys(change.changes)) {
|
|
const c = change.changes[key];
|
|
if (checkType(key, c)) elements[change.element][key] = c;
|
|
}
|
|
}
|
|
const deleted = Storage.get("deletedElements", []);
|
|
for (const element of deleted) {
|
|
delete elements[element];
|
|
}
|
|
}
|
|
|
|
const saveChanges = () => {
|
|
const element = Storage.get("currentElement");
|
|
const changes = Storage.get("tempChanges", []);
|
|
if (Storage.get("elements", []).find(a => a.name == element)) {
|
|
const elements_ = Storage.get("elements", []);
|
|
for (const change of changes) {
|
|
elements_.find(a => a.name == element)[change.property] = change.value;
|
|
}
|
|
Storage.set("elements", elements_);
|
|
} else {
|
|
const permChanges = Storage.get("changes", []);
|
|
for (const change of changes) {
|
|
let a;
|
|
if (a = permChanges.find(c => c.element == element)) {
|
|
a.changes[change.property] = change.value;
|
|
} else {
|
|
let c = {};
|
|
c[change.property] = change.value;
|
|
permChanges.push({
|
|
element,
|
|
changes: c
|
|
})
|
|
}
|
|
}
|
|
Storage.set("changes", permChanges);
|
|
}
|
|
}
|
|
|
|
const applyChange = (property, value) => {
|
|
// if (element && elements[element])
|
|
// elements[element][property] = value;
|
|
const element = Storage.get("currentElement");
|
|
const changes = Storage.get("tempChanges", []);
|
|
changes.push({property, value});
|
|
Storage.set("tempChanges", changes);
|
|
const nullish = {
|
|
string: "",
|
|
boolean: false,
|
|
number: 0,
|
|
array: [],
|
|
}
|
|
if (elements[element][property] == value || value == nullish[value instanceof Array ? "array" : typeof value]) {
|
|
Storage.filter("tempChanges", e => e.property != property);
|
|
}
|
|
}
|
|
|
|
const elementsManagerLoader = () => {
|
|
const listDiv = createDiv();
|
|
const list = document.createElement("ul");
|
|
list.id = "elementsList";
|
|
let customElements = Storage.get("elements", []);
|
|
let deletedElements = Storage.get("deletedElements", []);
|
|
let lastFreeRemoval = Storage.get("settings", {allowFreeRemoval: false, clearElements: false}, true).allowFreeRemoval;
|
|
Storage.set("lastFreeRemoval", lastFreeRemoval);
|
|
for (const key of Object.keys(elements).concat(customElements.map(e => e.name)).sort((a, b) => a.localeCompare(b, undefined, {caseFirst: "false"}))) {
|
|
const element = document.createElement("li");
|
|
const text = span(key); // only the text should be clickable
|
|
text.onclick = () => {
|
|
Storage.set("currentElement", key);
|
|
openMenu("elementManager", true);
|
|
}
|
|
element.appendChild(text);
|
|
if (customElements.find(e => e.name == key) || lastFreeRemoval) {
|
|
const removeButton = span("X");
|
|
removeButton.className = "elementRemoveButton";
|
|
removeButton.onclick = () => {
|
|
if (confirm("Are you sure you want to delete that element?")) {
|
|
if (customElements.find(e => e.name == key)) {
|
|
Storage.filter("elements", e => e.name != key);
|
|
customElements = customElements.filter(e => e.name != key);
|
|
delete elements[key];
|
|
element.style.display = "none";
|
|
document.getElementById("customElementsList/" + key).remove();
|
|
if (document.getElementById("customElementsList").children.length == 0) {
|
|
document.getElementById("noCustomElementsMessage").style.display = "";
|
|
}
|
|
} else {
|
|
Storage.append("deletedElements", key);
|
|
delete elements[key];
|
|
element.style.display = "none";
|
|
document.getElementById("noDeletedElementsMessage").style.display = "none";
|
|
const li = document.createElement("li");
|
|
li.innerText = key;
|
|
li.onclick = () => {
|
|
if (confirm("Are you sure you want to re-add '" + key + "'?")) {
|
|
Storage.filter("deletedElements", a => a != key);
|
|
li.style.display = "none";
|
|
}
|
|
}
|
|
document.getElementById("deletedElementsList").appendChild(li);
|
|
}
|
|
}
|
|
}
|
|
const emptySpan = span(" ");
|
|
emptySpan.className = "elementRemoveButton";
|
|
element.appendChild(emptySpan);
|
|
element.appendChild(removeButton)
|
|
}
|
|
if (deletedElements.includes(key)) {
|
|
element.style.display = "none";
|
|
}
|
|
list.appendChild(element);
|
|
}
|
|
|
|
const filterInput = document.createElement("input");
|
|
filterInput.id = "elementsManagerFilter";
|
|
filterInput.type = "text";
|
|
filterInput.placeholder = "Search elements...";
|
|
filterInput.onkeyup = (ev) => {
|
|
const val = ev.target.value;
|
|
const deleted = Storage.get("deletedElements", []);
|
|
for (const c of document.getElementById("elementsList").children) {
|
|
const span_ = c.querySelector("span");
|
|
if (!span_.innerText.toLowerCase().includes(val.toLowerCase()) || deleted.includes(span_.innerText)) {
|
|
c.style.display = "none";
|
|
} else {
|
|
c.style.display = "";
|
|
}
|
|
}
|
|
}
|
|
const addElement = document.createElement("input");
|
|
addElement.id = "elementAdd";
|
|
addElement.value = "+";
|
|
addElement.type = "button";
|
|
addElement.onclick = (_) => {
|
|
openMenu("elementCreator", true);
|
|
}
|
|
const settingsButton = document.createElement("input");
|
|
settingsButton.id = "elementsManagerSettingsButton";
|
|
settingsButton.type = "button";
|
|
settingsButton.value = "*";
|
|
settingsButton.onclick = (_) => {
|
|
openMenu("elementsSettings", true);
|
|
}
|
|
listDiv.appendChild(filterInput);
|
|
listDiv.appendChild(addElement);
|
|
listDiv.appendChild(settingsButton);
|
|
listDiv.appendChild(list);
|
|
listDiv.style.overflowY = "scroll";
|
|
new MenuScreen()
|
|
.setTitle("Elements Manager")
|
|
.setCloseButtonText("-")
|
|
.setParentDivId("elementsManagerParent")
|
|
.setInnerDivId("elementsManager")
|
|
.addNode(listDiv)
|
|
.build();
|
|
}
|
|
|
|
const parseColor = (colorString) => {
|
|
// technically color arrays are handled differently, but ill add it just in case
|
|
if (colorString instanceof Array) return parseColor(colorString[0]);
|
|
if (typeof colorString != "string") return "#ffffff";
|
|
if (colorString.startsWith("rgb(")) {
|
|
const color = colorString.replace("rgb(", "").replace(")", "");
|
|
return `#${color.split(",").map(a => parseInt(a).toString(16)).join("")}`;
|
|
} else {
|
|
if (colorString.startsWith("#")) {
|
|
const color = colorString.slice(1);
|
|
if (color.length == 3) return `#${color.repeat(2)}`;
|
|
else if (color.length == 2) return `#${color.repeat(3)}`;
|
|
else if (color.length >= 6) return `#${color.slice(0, 6)}`;
|
|
else return `#${color}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
const elementManagerLoader = () => {
|
|
const nodes = [];
|
|
for (const key of Object.keys(properties)) {
|
|
const category = createDiv();
|
|
category.innerHTML += `<span class="categoryTitle">${key.split("_").map(k => k[0].toUpperCase() + k.slice(1)).join(" ")}</span><br>`
|
|
for (const prop of properties[key]) {
|
|
const id = "elementsManager/" + key + "/" + prop.name;
|
|
const div = createDiv();
|
|
div.className = "elementsManager/propertyEntry";
|
|
div.appendChild(span(prop.name));
|
|
if (prop.viewOnly) {
|
|
if (prop.type == "boolean") {
|
|
const el = createInput("button", true, id, "toggleInput");
|
|
div.appendChild(el);
|
|
} else if (prop.type == "longString") {
|
|
const el = document.createElement("textarea");
|
|
el.id = id;
|
|
el.className = "elementsCode";
|
|
el.disabled = true;
|
|
div.appendChild(el);
|
|
} else if (prop.type instanceof Array) {
|
|
const el = createInput("text", true, id);
|
|
div.appendChild(el);
|
|
} else {
|
|
const el = createInput(prop.type == "string" ? "text" : prop.type, true, id);
|
|
div.appendChild(el);
|
|
}
|
|
} else {
|
|
if (prop.type == "boolean") {
|
|
const el = createInput("button", false, id, "toggleInput");
|
|
el.onclick = (ev) => {
|
|
ev.target.value = ev.target.value == "ON" ? "OFF" : "ON";
|
|
ev.target.setAttribute("state", ev.target.getAttribute("state") == "1" ? "0" : "1");
|
|
applyChange(prop.name, ev.target.value == "ON");
|
|
}
|
|
div.appendChild(el);
|
|
} else if (prop.type == "longString") {
|
|
const el = document.createElement("textarea");
|
|
el.id = id;
|
|
el.className = "elementsCode";
|
|
el.onchange = (ev) => {
|
|
applyChange(prop.name, ev.target.value);
|
|
}
|
|
div.appendChild(el);
|
|
} else if (prop.type instanceof Array) {
|
|
const el = createInput("text", false, id);
|
|
el.onchange = (ev) => {
|
|
applyChange(prop.name, ev.target.value.split(";"));
|
|
}
|
|
div.appendChild(el);
|
|
} else if (prop.name == "behavior") {
|
|
const dropdown = document.createElement("select");
|
|
dropdown.id = id;
|
|
let i = 0;
|
|
for (const bKey of Object.keys(behaviors)) {
|
|
const option = document.createElement("option");
|
|
option.value = bKey;
|
|
option.id = id + "/option/" + i;
|
|
option.innerText = bKey;
|
|
dropdown.appendChild(option);
|
|
i++;
|
|
}
|
|
const customOption = document.createElement("option");
|
|
customOption.value = "CUSTOM";
|
|
customOption.id = id + "/option/custom"
|
|
customOption.innerText = "Custom Behavior";
|
|
dropdown.appendChild(customOption);
|
|
dropdown.onchange = (ev) => {
|
|
if (ev.target.value == "CUSTOM") {
|
|
document.getElementById(id + "/textInput").style.display = "";
|
|
} else if (ev.target.value == "NONE") {
|
|
applyChange(prop.name, null);
|
|
} else {
|
|
document.getElementById(id + "/textInput").style.display = "none";
|
|
applyChange(prop.name, ev.target.value);
|
|
}
|
|
}
|
|
const noneOption = document.createElement("option");
|
|
noneOption.value = "NONE";
|
|
noneOption.id = id + "/option/none"
|
|
noneOption.innerText = "None";
|
|
dropdown.appendChild(noneOption);
|
|
const el = createInput("text", false, id + "/textInput");
|
|
el.style.display = "none";
|
|
el.onchange = (ev) => {
|
|
if (document.getElementById(id).value == "CUSTOM") {
|
|
applyChange(prop.name, ev.target.value.split(";").map(e => e.split(",")));
|
|
} else {
|
|
ev.target.style.display = "none";
|
|
}
|
|
}
|
|
div.appendChild(dropdown);
|
|
div.appendChild(el);
|
|
} else {
|
|
const el = createInput(prop.type == "string" ? "text" : prop.type, false, id);
|
|
el.onchange = (ev) => {
|
|
applyChange(prop.name, prop.type == "number" ? parseFloat(ev.target.value) : ev.target.value);
|
|
}
|
|
div.appendChild(el);
|
|
}
|
|
}
|
|
category.appendChild(div);
|
|
}
|
|
category.appendChild(br());
|
|
nodes.push(category);
|
|
}
|
|
|
|
const saveButton = span("Save Changes");
|
|
saveButton.className = "createButton";
|
|
saveButton.onclick = () => {
|
|
saveChanges();
|
|
Storage.remove("tempChanges");
|
|
closeMenu();
|
|
alert("Changes successfully applied");
|
|
}
|
|
|
|
nodes.push(br(), saveButton)
|
|
|
|
new MenuScreen()
|
|
.setTitle("Element Manager")
|
|
.setCloseButtonText("<")
|
|
.setParentDivId("elementManagerParent")
|
|
.setInnerDivId("elementManager")
|
|
.addNode(nodes)
|
|
.build();
|
|
}
|
|
|
|
const elementCreatorLoader = () => {
|
|
const nodes = [];
|
|
for (const key of Object.keys(properties)) {
|
|
const category = createDiv();
|
|
category.innerHTML += `<span class="categoryTitle">${key.split("_").map(k => k[0].toUpperCase() + k.slice(1)).join(" ")}</span><br>`
|
|
for (const prop of properties[key]) {
|
|
if (prop.creatorIgnore) continue;
|
|
const div = createDiv();
|
|
div.className = "elementsManager/propertyEntry";
|
|
div.innerHTML += `<span>${prop.name}</span>`
|
|
const id = "elementsManager/creator/" + key + "/" + prop.name;
|
|
if (prop.type == "boolean") {
|
|
const el = createInput("button", false, id, "toggleInput");
|
|
el.onclick = (ev) => {
|
|
const elementData = Storage.get("newElement", {});
|
|
ev.target.value = ev.target.value == "ON" ? "OFF" : "ON";
|
|
elementData[prop.name] = ev.target.value == "ON";
|
|
ev.target.setAttribute("state", ev.target.getAttribute("state") == "1" ? "0" : "1");
|
|
Storage.set("newElement", elementData);
|
|
}
|
|
div.appendChild(el);
|
|
} else if (prop.type == "longString") {
|
|
const el = document.createElement("textarea");
|
|
el.id = id;
|
|
el.className = "elementsCode";
|
|
el.onchange = (ev) => {
|
|
const elementData = Storage.get("newElement", {});
|
|
elementData[prop.name] = ev.target.value;
|
|
Storage.set("newElement", elementData);
|
|
}
|
|
div.appendChild(el);
|
|
} else if (prop.type instanceof Array && prop.type[0] != "color") {
|
|
const el = createInput("text", false, id);
|
|
el.onchange = (ev) => {
|
|
const elementData = Storage.get("newElement", {});
|
|
elementData[prop.name] = ev.target.value.split(";");
|
|
Storage.set("newElement", elementData);
|
|
}
|
|
div.appendChild(el);
|
|
} else if (prop.name == "behavior") {
|
|
const dropdown = document.createElement("select");
|
|
dropdown.id = id;
|
|
let i = 0;
|
|
for (const bKey of Object.keys(behaviors)) {
|
|
const option = document.createElement("option");
|
|
option.value = bKey;
|
|
option.selected = i == 0;
|
|
option.innerText = bKey;
|
|
dropdown.appendChild(option);
|
|
i++;
|
|
}
|
|
const customOption = document.createElement("option");
|
|
customOption.value = "CUSTOM";
|
|
customOption.innerText = "Custom Behavior";
|
|
dropdown.appendChild(customOption);
|
|
dropdown.onchange = (ev) => {
|
|
if (ev.target.value == "CUSTOM") {
|
|
document.getElementById(id + "/textInput").style.display = "";
|
|
} else {
|
|
document.getElementById(id + "/textInput").style.display = "none";
|
|
const elementData = Storage.get("newElement", {});
|
|
elementData[prop.name] = ev.target.value;
|
|
Storage.set("newElement", elementData);
|
|
}
|
|
}
|
|
const el = createInput("text", false, id + "/textInput");
|
|
el.style.display = "none";
|
|
el.onchange = (ev) => {
|
|
if (document.getElementById(id).value == "CUSTOM") {
|
|
const elementData = Storage.get("newElement", {});
|
|
elementData[prop.name] = ev.target.value.split(";").map(e => e.split(","));
|
|
Storage.set("newElement", elementData);
|
|
} else {
|
|
ev.target.style.display = "none";
|
|
}
|
|
}
|
|
div.appendChild(dropdown);
|
|
div.appendChild(el);
|
|
} else {
|
|
const el = createInput(prop.type == "string" ? "text" : prop.type instanceof Array ? "color" : prop.type, false, id);
|
|
el.onchange = (ev) => {
|
|
const elementData = Storage.get("newElement", {});
|
|
if (prop.type == "number") elementData[prop.name] = parseFloat(ev.target.value);
|
|
else if (prop.type == "color" || prop.type[0] == "color") elementData[prop.name] = parseColor(ev.target.value);
|
|
else if (prop.type == "string") elementData[prop.name] = ev.target.value;
|
|
Storage.set("newElement", elementData);
|
|
}
|
|
div.appendChild(el);
|
|
}
|
|
if (prop.required) {
|
|
const requiredMessage = span(" This field is required");
|
|
requiredMessage.id = id + "/required";
|
|
requiredMessage.style.display = "none";
|
|
requiredMessage.style.color = "red";
|
|
div.appendChild(requiredMessage);
|
|
}
|
|
category.appendChild(div);
|
|
}
|
|
category.appendChild(br());
|
|
nodes.push(category);
|
|
}
|
|
|
|
const createButton = span("Create Element");
|
|
createButton.className = "createButton";
|
|
createButton.onclick = () => {
|
|
const elementData = Storage.get("newElement", {});
|
|
if (!elementData["name"]) {
|
|
document.getElementById("elementsManager/creator/meta/name/required").style.display = "";
|
|
}
|
|
if (!elementData["category"]) {
|
|
document.getElementById("elementsManager/creator/meta/category/required").style.display = "";
|
|
}
|
|
|
|
if (!elementData || !elementData["name"] || !elementData["category"]) return;
|
|
Storage.append("elements", elementData);
|
|
Storage.remove("newElement");
|
|
closeMenu();
|
|
alert("Element successfully created");
|
|
}
|
|
|
|
nodes.push(br(), createButton);
|
|
|
|
new MenuScreen()
|
|
.setTitle("Element creator")
|
|
.setParentDivId("elementCreatorParent")
|
|
.setInnerDivId("elementCreator")
|
|
.appendInnerHtml("<span>Takes effect after reload</span>")
|
|
.addNode(nodes)
|
|
.build();
|
|
}
|
|
|
|
const readFileAsync = (file) => {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onload = (event) => {
|
|
resolve(JSON.parse(event.target.result));
|
|
};
|
|
reader.onerror = (event) => {
|
|
reject(event.target.error);
|
|
};
|
|
reader.readAsText(file);
|
|
});
|
|
}
|
|
|
|
const settingsLoader = () => {
|
|
const nodes = [];
|
|
let settings = Storage.get("settings", {
|
|
clearElements: false,
|
|
allowFreeRemoval: false
|
|
}, true);
|
|
if (!settings) {
|
|
settings = {
|
|
clearElementsButton: false,
|
|
allowFreeRemoval: false
|
|
};
|
|
Storage.set("settings", settings);
|
|
}
|
|
const elements = createDiv();
|
|
const elementsTitle = span("Elements");
|
|
elementsTitle.className = "categoryTitle";
|
|
elements.appendChild(elementsTitle);
|
|
const importDiv = createDiv();
|
|
document.addEventListener("change", async (ev) => {
|
|
if (ev.target && ev.target.id == "importElements") {
|
|
const promises = [];
|
|
for (const file of ev.target.files) {
|
|
promises.push(readFileAsync(file));
|
|
}
|
|
const res = await Promise.all(promises);
|
|
const result = [];
|
|
for (const array of res) {
|
|
for (const element of array) {
|
|
if (result.find(r => r.name == element.name)) continue;
|
|
result.push(element);
|
|
}
|
|
}
|
|
importElements(result);
|
|
}
|
|
})
|
|
const importInput = document.createElement("input");
|
|
importInput.type = "file";
|
|
importInput.accept = "application/JSON, application/json";
|
|
importInput.id = "importElements";
|
|
importInput.style.display = "none";
|
|
const importLabel = document.createElement("label");
|
|
importLabel.setAttribute("for", "importElements");
|
|
importLabel.innerText = "Import Elements"
|
|
importLabel.className = "hoverableText";
|
|
importDiv.appendChild(importInput);
|
|
importDiv.appendChild(importLabel);
|
|
elements.appendChild(importDiv);
|
|
const exportButton = span("Export Elements");
|
|
exportButton.className = "hoverableText";
|
|
exportButton.onclick = () => {exportElements()};
|
|
elements.appendChild(exportButton);
|
|
const clearButton = span("Clear custom elements");
|
|
clearButton.className = "hoverableText";
|
|
clearButton.onclick = () => {
|
|
if (confirm("Are you sure you want to remove all the custom elements?")) {
|
|
Storage.remove("elements");
|
|
const customElementsList = document.getElementById("customElementsList");
|
|
customElementsList.replaceChildren();
|
|
const message = document.getElementById("noCustomElementsMessage");
|
|
message.style.display = "";
|
|
}
|
|
}
|
|
elements.appendChild(br());
|
|
elements.appendChild(clearButton);
|
|
elements.appendChild(br());
|
|
const general = createDiv();
|
|
general.innerHTML += `<span class="categoryTitle">General</span><br>`;
|
|
const clearElementsDiv = createDiv();
|
|
clearElementsDiv.innerHTML += "<span>Clear custom elements on import</span>";
|
|
const clearElementsButton = document.createElement("input");
|
|
clearElementsButton.className = "toggleInput";
|
|
clearElementsButton.type = "button";
|
|
if (settings && settings.clearElements) {
|
|
clearElementsButton.value = "ON";
|
|
clearElementsButton.setAttribute("state", "1");
|
|
} else {
|
|
clearElementsButton.value = "OFF";
|
|
clearElementsButton.setAttribute("state", "0");
|
|
}
|
|
clearElementsButton.onclick = (ev) => {
|
|
toggleSetting("clearElements", ev.target)
|
|
}
|
|
clearElementsDiv.appendChild(clearElementsButton);
|
|
general.appendChild(clearElementsDiv)
|
|
const allowRemovalDiv = createDiv();
|
|
allowRemovalDiv.innerHTML += "<span>Allow free element removal</span>";
|
|
const allowRemovalButton = document.createElement("input");
|
|
allowRemovalButton.className = "toggleInput";
|
|
allowRemovalButton.type = "button";
|
|
if (settings && settings.allowFreeRemoval) {
|
|
allowRemovalButton.value = "ON";
|
|
allowRemovalButton.setAttribute("state", "1");
|
|
} else {
|
|
allowRemovalButton.value = "OFF";
|
|
allowRemovalButton.setAttribute("state", "0");
|
|
}
|
|
allowRemovalButton.onclick = (ev) => {
|
|
toggleSetting("allowFreeRemoval", ev.target);
|
|
}
|
|
allowRemovalDiv.appendChild(allowRemovalButton);
|
|
general.appendChild(allowRemovalDiv);
|
|
const customElements = createDiv();
|
|
const customElementsTitle = span("Custom Elements");
|
|
customElementsTitle.className = "categoryTitle";
|
|
customElements.appendChild(customElementsTitle);
|
|
const list = document.createElement("ul");
|
|
list.id = "customElementsList";
|
|
const elements_ = Storage.get("elements", []);
|
|
const deleted = Storage.get("deletedElements", []);
|
|
for (const element of elements_) {
|
|
const li = document.createElement("li");
|
|
li.id = "customElementsList/" + element;
|
|
const text = span(element.name);
|
|
text.onclick = () => {
|
|
Storage.set("currentElement", element.name);
|
|
openMenu("elementManager", true);
|
|
}
|
|
li.appendChild(text);
|
|
const removeButton = span("X");
|
|
removeButton.className = "elementRemoveButton";
|
|
removeButton.onclick = () => {
|
|
if (confirm("Are you sure you want to delete that element?")) {
|
|
delete elements[element.name];
|
|
Storage.filter("elements", a => a.name != element.name);
|
|
li.style.display = "none";
|
|
document.getElementById("customElementsList/" + key).remove();
|
|
if (document.getElementById("customElementsList").children.length == 0) {
|
|
document.getElementById("noCustomElementsMessage").style.display = "";
|
|
}
|
|
}
|
|
}
|
|
const emptySpan = span(" ")
|
|
emptySpan.className = "elementRemoveButton";
|
|
li.appendChild(emptySpan);
|
|
li.appendChild(removeButton)
|
|
list.appendChild(li);
|
|
}
|
|
const message = span("There are no custom elements");
|
|
message.id = "noCustomElementsMessage";
|
|
if (elements_.length > 0) message.style.display = "none";
|
|
customElements.appendChild(br());
|
|
customElements.appendChild(message);
|
|
customElements.appendChild(list);
|
|
const deletedElements = createDiv();
|
|
const deletedElementsTitle = span("Deleted Elements");
|
|
deletedElementsTitle.className = "categoryTitle";
|
|
deletedElements.appendChild(deletedElementsTitle);
|
|
deletedElements.appendChild(br());
|
|
const deletedElementsInfo = span("Elements will reappear after reload.\nCustom elements get deleted permanently.");
|
|
deletedElements.appendChild(br());
|
|
deletedElements.appendChild(deletedElementsInfo);
|
|
const deletedElementsMessage = span("There are no deleted elements");
|
|
deletedElementsMessage.style.display = deleted.length > 0 ? "none" : "";
|
|
deletedElementsMessage.id = "noDeletedElementsMessage";
|
|
const deletedElementsList = document.createElement("ul");
|
|
deletedElementsList.id = "deletedElementsList";
|
|
for (const element of deleted) {
|
|
const li = document.createElement("li");
|
|
li.innerText = element;
|
|
li.className = "hoverableText"
|
|
li.onclick = () => {
|
|
if (confirm("Are you sure you want to re-add '" + element + "'?")) {
|
|
Storage.filter("deletedElements", a => a != element);
|
|
li.style.display = "none";
|
|
if (Storage.get("deletedElements", []).length == 0) {
|
|
document.getElementById("noDeletedElementsMessage").style.display = "";
|
|
}
|
|
}
|
|
}
|
|
deletedElementsList.appendChild(li);
|
|
}
|
|
deletedElements.appendChild(br());
|
|
deletedElements.appendChild(deletedElementsMessage);
|
|
deletedElements.appendChild(deletedElementsList);
|
|
nodes.push(elements, br(), general, br(), customElements, br(), deletedElements);
|
|
|
|
new MenuScreen()
|
|
.setTitle("Elements Manager Settings")
|
|
.setParentDivId("elementsManagerSettingsParent")
|
|
.setInnerDivId("elementsManagerSettings")
|
|
.setCloseButtonText("<")
|
|
.addNode(nodes)
|
|
.build();
|
|
}
|
|
|
|
menuScreens.elementsManager = {
|
|
name: "Elements Manager",
|
|
parentDiv: "elementsManagerParent",
|
|
buttonDescription: "Elements manager",
|
|
show: true,
|
|
loader: elementsManagerLoader
|
|
}
|
|
|
|
menuScreens.elementManager = {
|
|
name: "Element Manager",
|
|
parentDiv: "elementManagerParent",
|
|
show: false,
|
|
loader: elementManagerLoader,
|
|
preOpen: () => {
|
|
Storage.remove("tempChanges");
|
|
const currentElement = Storage.get("currentElement");
|
|
if (!currentElement) return closeMenu();
|
|
const element = elements[currentElement];
|
|
if (!element) return closeMenu();
|
|
for (const key of Object.keys(properties)) {
|
|
for (const prop of properties[key]) {
|
|
const id = "elementsManager/" + key + "/" + prop.name;
|
|
const el = document.getElementById(id);
|
|
if (prop.type instanceof Array) {
|
|
if (element[prop.name]) {
|
|
if (element[prop.name] instanceof Array) {
|
|
el.setAttribute("value", element[prop.name].join(";"));
|
|
} else {
|
|
const type = {
|
|
string: "text",
|
|
number: "number",
|
|
color: "color"
|
|
}
|
|
el.setAttribute("type", type[prop.type[0]]);
|
|
el.setAttribute("value", parseColor(element[prop.name]));
|
|
}
|
|
} else {
|
|
if (prop.type[0] == "color") {
|
|
el.setAttribute("type", "color");
|
|
el.setAttribute("value", "#ff0000");
|
|
}
|
|
else el.setAttribute("value", "none");
|
|
}
|
|
} else {
|
|
if (element[prop.name]) {
|
|
if (prop.type == "boolean") {
|
|
el.setAttribute("value", element[prop.name] ? "ON" : "OFF");
|
|
el.setAttribute("state", element[prop.name] ? "1" : "0");
|
|
} else if (prop.type == "color") {
|
|
el.setAttribute("value", parseColor(element[prop.name]));
|
|
} else if (prop.name == "behavior") {
|
|
const behavior = element[prop.name];
|
|
const index = Object.keys(behaviors).map(b => `${behaviors[b] instanceof Array ? behaviors[b].join(";") : behaviors[b]}`).indexOf(behavior instanceof Array ? behavior.join(";") : behavior);
|
|
if (index == -1) {
|
|
document.getElementById(id + "/option/custom").selected = true;
|
|
document.getElementById(id + "/textInput").style.display = "";
|
|
document.getElementById(id + "/textInput").setAttribute("value", behavior.join(";"))
|
|
} else {
|
|
document.getElementById(id + "/textInput").style.display = "none";
|
|
document.getElementById(id + "/option/" + index).selected = true;
|
|
}
|
|
} else {
|
|
el.setAttribute("value", element[prop.name] instanceof Function ? element[prop.name].toString() : element[prop.name]);
|
|
}
|
|
} else if (prop.name == "name") {
|
|
el.setAttribute("value", currentElement);
|
|
} else if (prop.name == "behavior") {
|
|
console.log(element[prop.name], element);
|
|
document.getElementById(id + "/option/none").selected = true;
|
|
document.getElementById(id + "/textInput").style.display = "none";
|
|
} else {
|
|
const default_ = {
|
|
string: "none",
|
|
number: 0,
|
|
color: "#ff0000"
|
|
}
|
|
if (prop.type == "boolean") {
|
|
el.setAttribute("value", "OFF");
|
|
el.setAttribute("state", "0");
|
|
} else {
|
|
el.setAttribute("value", default_[prop.type])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
close: () => {
|
|
if (!Storage.get("tempChanges") || !Storage.get("tempChanges", []).length || confirm("Are you sure you want to close the menu without saving the changes?")) {
|
|
const menuParent = document.getElementById("elementManagerParent");
|
|
menuParent.style.display = "none";
|
|
Storage.remove("tempChanges");
|
|
Storage.remove("noClose");
|
|
} else {
|
|
Storage.set("noClose", true);
|
|
}
|
|
},
|
|
onClose: () => {
|
|
if (!Storage.get("noClose")) {
|
|
showingMenu = false;
|
|
openMenu("elementsManager", true);
|
|
}
|
|
}
|
|
}
|
|
|
|
menuScreens.elementCreator = {
|
|
name: "Element Creator",
|
|
parentDiv: "elementCreatorParent",
|
|
show: false,
|
|
loader: elementCreatorLoader,
|
|
preOpen: () => {
|
|
const elementData = {};
|
|
for (const key of Object.keys(properties)) {
|
|
for (const prop of properties[key]) {
|
|
if (prop.creatorIgnore) continue;
|
|
const el = document.getElementById("elementsManager/creator/" + key + "/" + prop.name)
|
|
if (prop.type instanceof Array) {
|
|
el.setAttribute("value", "none");
|
|
} else {
|
|
const default_ = {
|
|
string: "none",
|
|
number: 0,
|
|
color: "#ff0000",
|
|
boolean: "OFF"
|
|
}
|
|
if (prop.name == "name") {
|
|
let name = `NewElement${Math.floor(Math.random() * 10000) + 1}`;
|
|
if (elements[name]) {
|
|
let i = 0;
|
|
let j = 1;
|
|
// try 100 times, then increase the pool
|
|
while (elements[name]) {
|
|
name = `NewElement${Math.floor(Math.random() * (10000 * i)) + 1}`;
|
|
i++;
|
|
if (i <= 100) {
|
|
j++;
|
|
i = 0;
|
|
}
|
|
// if already incresed the pool 5 times and there is still nothing, just break and use static name
|
|
if (j <= 5) {
|
|
name = "NewElement";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
el.setAttribute("value", name)
|
|
elementData[prop.name] = name;
|
|
} else if (prop.name == "category") {
|
|
el.setAttribute("value", "other")
|
|
elementData[prop.name] = "other";
|
|
} else if (prop.name == "behavior") {
|
|
elementData[prop.name] = "POWDER_OLD";
|
|
} else {
|
|
el.setAttribute("value", default_[prop.type]);
|
|
}
|
|
if (prop.type == "boolean") {
|
|
el.setAttribute("state", "0")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Storage.set("newElement", elementData);
|
|
},
|
|
close: () => {
|
|
if (!Storage.get("newElement") || confirm("Are you sure you want to close the menu without creating the element?")) {
|
|
const menuParent = document.getElementById("elementCreatorParent");
|
|
menuParent.style.display = "none";
|
|
Storage.remove("newElement");
|
|
Storage.remove("noClose");
|
|
} else {
|
|
Storage.set("noClose", true);
|
|
}
|
|
},
|
|
onClose: () => {
|
|
if (!Storage.get("noClose")) {
|
|
showingMenu = false;
|
|
openMenu("elementsManager", true);
|
|
}
|
|
}
|
|
}
|
|
|
|
menuScreens.elementsSettings = {
|
|
name: "Elements Settings",
|
|
show: false,
|
|
parentDiv: "elementsManagerSettingsParent",
|
|
loader: settingsLoader,
|
|
preOpen: () => {
|
|
const customElements = Storage.get("elements", []);
|
|
const message = document.getElementById("noCustomElementsMessage");
|
|
const list = document.getElementById("customElementsList");
|
|
if (customElements.length > 0) {
|
|
message.style.display = "none";
|
|
list.style.display = "";
|
|
list.replaceChildren();
|
|
for (const element of customElements) {
|
|
const li = document.createElement("li");
|
|
const text = span(element.name);
|
|
text.onclick = () => {
|
|
Storage.set("currentElement", element.name);
|
|
openMenu("elementManager", true);
|
|
}
|
|
li.appendChild(text);
|
|
const removeButton = span("X");
|
|
removeButton.className = "elementRemoveButton";
|
|
removeButton.onclick = () => {
|
|
if (confirm("Are you sure you want to delete that element?")) {
|
|
delete elements[element.name];
|
|
Storage.filter("elements", a => a.name != element.name);
|
|
li.style.display = "none";
|
|
document.getElementById("customElementsList/" + key).remove();
|
|
if (document.getElementById("customElementsList").children.length == 0) {
|
|
document.getElementById("noCustomElementsMessage").style.display = "";
|
|
}
|
|
}
|
|
}
|
|
const emptySpan = span(" ");
|
|
emptySpan.className = "elementRemoveButton";
|
|
li.appendChild(emptySpan);
|
|
li.appendChild(removeButton);
|
|
list.appendChild(li);
|
|
}
|
|
} else {
|
|
list.style.display = "none";
|
|
message.style.display = "";
|
|
}
|
|
},
|
|
onClose: () => {
|
|
const settings = Storage.get("settings", {allowFreeRemoval: false, clearElements: false}, true);
|
|
const elements_ = Storage.get("elements", []).map(e => e.name);
|
|
const lastFreeRemoval = Storage.get("lastFreeRemoval", false);
|
|
if (settings.allowFreeRemoval && !lastFreeRemoval) {
|
|
for (const li of document.getElementById("elementsList").children) {
|
|
const name = li.querySelector("span").innerText
|
|
if (!elements_.includes(name) && li.getElementsByClassName("elementRemoveButton").length == 0) {
|
|
const removeButton = span("X");
|
|
removeButton.className = "elementRemoveButton";
|
|
removeButton.onclick = () => {
|
|
if (confirm("Are you sure you want to delete that element?")) {
|
|
delete elements[name];
|
|
li.style.display = "none";
|
|
Storage.append("deletedElements", name);
|
|
document.getElementById("noDeletedElementsMessage").style.display = "none";
|
|
const li2 = document.createElement("li");
|
|
li2.innerText = name;
|
|
li2.onclick = () => {
|
|
if (confirm("Are you sure you want to re-add '" + name + "'?")) {
|
|
Storage.filter("deletedElements", a => a != name);
|
|
li2.style.display = "none";
|
|
}
|
|
}
|
|
document.getElementById("deletedElementsList").appendChild(li2);
|
|
}
|
|
}
|
|
const emptySpan = span(" ")
|
|
emptySpan.className = "elementRemoveButton";
|
|
li.appendChild(emptySpan);
|
|
li.appendChild(removeButton)
|
|
}
|
|
}
|
|
} else if (!settings.allowFreeRemoval && lastFreeRemoval) {
|
|
for (const li of document.getElementById("elementsList").children) {
|
|
if (!elements_.includes(li.querySelector("span").innerText))
|
|
li.querySelectorAll(".elementRemoveButton").forEach(e => e.remove());
|
|
}
|
|
}
|
|
Storage.set("lastFreeRemoval", settings.allowFreeRemoval);
|
|
openMenu("elementsManager", true);
|
|
}
|
|
}
|
|
|
|
runAfterLoadList.push(cssInject, loadChanges);
|
|
} else {
|
|
enabledMods.unshift("mods/betterMenuScreens.js");
|
|
localStorage.setItem("enabledMods", JSON.stringify(enabledMods));
|
|
window.location.reload();
|
|
}
|