582 lines
20 KiB
JavaScript
Executable File
582 lines
20 KiB
JavaScript
Executable File
class Dice {
|
|
constructor(sides, addition = 0, count = 1, color = undefined) {
|
|
this.sides = sides;
|
|
this.addition = addition;
|
|
this.count = count;
|
|
this.color = color;
|
|
this.selected = false;
|
|
}
|
|
|
|
toText(color = false) {
|
|
let result = "";
|
|
if (this.count > 1) {
|
|
result += this.count;
|
|
}
|
|
result += "D" + this.sides;
|
|
if (this.addition) {
|
|
result += (this.addition < 0 ? "-" : "+") + Math.abs(this.addition);
|
|
}
|
|
if (color && this.color) {
|
|
result += "[" + this.color + "]";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fromText(value) {
|
|
const result = value.match(dice_regex);
|
|
if (result) {
|
|
if (result[1]) {
|
|
this.count = +result[1];
|
|
}
|
|
this.sides = +result[2];
|
|
if (result[3]) {
|
|
this.addition = +result[3];
|
|
}
|
|
if (result[5]) {
|
|
this.color = result[5];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class DiceHistoryEntry {
|
|
constructor(dices = undefined, formula = undefined, result = undefined, time = undefined) {
|
|
this.dices = dices;
|
|
this.formula = formula;
|
|
this.result = result;
|
|
this.time = time;
|
|
}
|
|
}
|
|
|
|
const default_sides = [4, 6, 8, 10, 12, 20, 100];
|
|
const default_colors = ["#de324c", "#f4895f", "#f8e16f", "#95cf92", "#369acc", "#9656a2", "#6c584c"];
|
|
|
|
const dice_regex = /(\d+)?[D|d](\d+)([\+|\-]\d+)?(\[(.+)\])?/;
|
|
|
|
let darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
|
|
let dices = [];
|
|
let history = [];
|
|
let redoHistory = [];
|
|
|
|
let dicesContainer = document.getElementById("dices");
|
|
let historyContainer = document.getElementById("history");
|
|
|
|
function renderDice(dice, index) {
|
|
const diceElement = document.createElement("div");
|
|
diceElement.classList.add("dice");
|
|
|
|
const diceImage = document.createElement("div");
|
|
diceImage.classList.add("dice-image");
|
|
if (default_sides.indexOf(dice.sides) != -1) {
|
|
diceImage.classList.add("dice-image-" + dice.sides);
|
|
} else {
|
|
diceImage.classList.add("dice-image-custom");
|
|
}
|
|
if (dice.color) {
|
|
diceImage.style.backgroundColor = dice.color;
|
|
// revert b/w on dark-mode
|
|
if (darkMode) {
|
|
if (["#000", "#000000", "black", "rgb(0,0,0)", "rgb(0, 0, 0)"].indexOf(dice.color) != -1) {
|
|
diceImage.style.backgroundColor = "#fff";
|
|
} else if (["#fff", "#ffffff", "white", "rgb(255,255,255)", "rgb(255, 255, 255)"].indexOf(dice.color) != -1) {
|
|
diceImage.style.backgroundColor = "#000";
|
|
}
|
|
}
|
|
}
|
|
|
|
const diceImageContainer = document.createElement("div");
|
|
diceImageContainer.classList.add("dice-image-container");
|
|
diceImageContainer.onclick = function () {
|
|
if (dices[index].selected) {
|
|
dices[index].selected = false;
|
|
diceElement.classList.remove("selected");
|
|
} else {
|
|
dices[index].selected = true;
|
|
diceElement.classList.add("selected");
|
|
}
|
|
|
|
if (dices.filter((dice) => dice.selected).length) {
|
|
document.getElementById('roll-button').classList.remove("disabled");
|
|
document.getElementById('roll-add-button').classList.add("disabled");
|
|
if (dices.filter((dice) => dice.selected).length > 1) {
|
|
document.getElementById('roll-add-button').classList.remove("disabled");
|
|
}
|
|
} else {
|
|
document.getElementById('roll-button').classList.add("disabled");
|
|
document.getElementById('roll-add-button').classList.add("disabled");
|
|
}
|
|
|
|
localStorage.setItem('dices', JSON.stringify(dices));
|
|
};
|
|
diceImageContainer.appendChild(diceImage);
|
|
|
|
diceElement.appendChild(diceImageContainer);
|
|
|
|
const diceLabelContainer = document.createElement("div");
|
|
diceLabelContainer.classList.add("label-container");
|
|
|
|
const diceLabel = document.createElement("label");
|
|
diceLabel.innerText = dice.toText();
|
|
|
|
diceLabelContainer.appendChild(diceLabel);
|
|
diceElement.appendChild(diceLabelContainer);
|
|
|
|
const diceRemove = document.createElement("div");
|
|
diceRemove.classList.add("remove");
|
|
diceRemove.onclick = function () {
|
|
removeDice(index);
|
|
};
|
|
diceElement.appendChild(diceRemove);
|
|
|
|
if (dice.selected) {
|
|
diceElement.classList.add("selected");
|
|
} else {
|
|
diceElement.classList.remove("selected");
|
|
}
|
|
|
|
|
|
dicesContainer.appendChild(diceElement);
|
|
}
|
|
|
|
function renderDices() {
|
|
dicesContainer.innerHTML = "";
|
|
dices.forEach((dice, index) => {
|
|
renderDice(dice, index);
|
|
})
|
|
|
|
if (dices.filter((dice) => dice.selected).length) {
|
|
document.getElementById('roll-button').classList.remove("disabled");
|
|
document.getElementById('roll-add-button').classList.add("disabled");
|
|
if (dices.filter((dice) => dice.selected).length > 1) {
|
|
document.getElementById('roll-add-button').classList.remove("disabled");
|
|
}
|
|
} else {
|
|
document.getElementById('roll-button').classList.add("disabled");
|
|
document.getElementById('roll-add-button').classList.add("disabled");
|
|
}
|
|
}
|
|
|
|
function addDice(sides, addition = 0, count = 1, color = "#000") {
|
|
dices.push(new Dice(sides, addition, count, color));
|
|
localStorage.setItem('dices', JSON.stringify(dices));
|
|
renderDices();
|
|
}
|
|
|
|
function removeDice(index) {
|
|
dices.splice(index, 1);
|
|
localStorage.setItem('dices', JSON.stringify(dices));
|
|
renderDices();
|
|
}
|
|
|
|
function resetDices() {
|
|
dices = [];
|
|
default_sides.forEach((side, i) => {
|
|
dices.push(new Dice(side, 0, 1, default_colors[i]));
|
|
});
|
|
localStorage.setItem('dices', JSON.stringify(dices));
|
|
renderDices();
|
|
}
|
|
|
|
|
|
function addDiceForm() {
|
|
let inputSides = document.getElementById("inputSides");
|
|
if (!inputSides.value) {
|
|
inputSides = document.getElementById("inputCustom")
|
|
}
|
|
const inputAddition = document.getElementById("inputAddition");
|
|
const inputCount = document.getElementById("inputCount");
|
|
const inputColor = document.getElementById("inputColor");
|
|
addDice(+inputSides.value, +inputAddition.value, +inputCount.value, inputColor.value);
|
|
}
|
|
|
|
function setDicesContainer(container) {
|
|
dicesContainer = container;
|
|
}
|
|
|
|
function renderHistory() {
|
|
historyContainer.innerHTML = "";
|
|
if (history.length) {
|
|
document.getElementById('history-button').classList.remove("disabled");
|
|
document.getElementById('history-undo-button').classList.remove("disabled");
|
|
} else {
|
|
document.getElementById('history-button').classList.add("disabled");
|
|
document.getElementById('history-undo-button').classList.add("disabled");
|
|
}
|
|
|
|
if (redoHistory.length) {
|
|
console.log(redoHistory);
|
|
document.getElementById('history-redo-button').classList.remove("disabled");
|
|
} else {
|
|
document.getElementById('history-redo-button').classList.add("disabled");
|
|
}
|
|
|
|
history.forEach((entry) => {
|
|
if (entry.result) {
|
|
const diceLabelContainer = document.createElement("div");
|
|
diceLabelContainer.classList.add("label");
|
|
if (entry.dices) {
|
|
entry.dices.forEach((diceData, i) => {
|
|
const dice = new Dice(diceData.sides, diceData.addition, diceData.count, diceData.color);
|
|
const diceLabel = document.createElement("label");
|
|
diceLabel.innerHTML = dice.toText() + (i == entry.dices.length - 1 ? ": " : "");
|
|
diceLabel.style.color = dice.color;
|
|
// revert b/w on dark-mode
|
|
if (darkMode) {
|
|
if (["#000", "#000000", "black", "rgb(0,0,0)", "rgb(0, 0, 0)"].indexOf(dice.color) != -1) {
|
|
diceLabel.style.color = "#fff";
|
|
} else if (["#fff", "#ffffff", "white", "rgb(255,255,255)", "rgb(255, 255, 255)"].indexOf(dice.color) != -1) {
|
|
diceLabel.style.color = "#000";
|
|
}
|
|
}
|
|
diceLabelContainer.appendChild(diceLabel);
|
|
if (i < entry.dices.length - 1) {
|
|
const diceLabelAdd = document.createElement("span");
|
|
diceLabelAdd.innerHTML = " + ";
|
|
diceLabelContainer.appendChild(diceLabelAdd);
|
|
}
|
|
})
|
|
}
|
|
historyContainer.appendChild(diceLabelContainer);
|
|
|
|
|
|
const diceResult = document.createElement("span");
|
|
diceResult.classList.add("result");
|
|
diceResult.innerText = entry.result;
|
|
historyContainer.appendChild(diceResult);
|
|
|
|
const diceFormulaContainer = document.createElement("span");
|
|
diceFormulaContainer.classList.add("formula-container");
|
|
historyContainer.appendChild(diceFormulaContainer);
|
|
|
|
if (entry.formula) {
|
|
const diceFormulaEqual = document.createElement("span");
|
|
diceFormulaEqual.innerText = " = ";
|
|
diceFormulaContainer.appendChild(diceFormulaEqual);
|
|
const diceFormula = document.createElement("span");
|
|
diceFormula.classList.add("formula");
|
|
diceFormula.innerText = entry.formula;
|
|
diceFormulaContainer.appendChild(diceFormula);
|
|
}
|
|
|
|
const diceTime = document.createElement("span");
|
|
diceTime.classList.add("time");
|
|
historyContainer.appendChild(diceTime);
|
|
|
|
if (entry.time) {
|
|
diceTime.innerText = entry.time.fromNow();
|
|
diceTime.title = entry.time.format("LLLL");
|
|
}
|
|
|
|
} else {
|
|
historyContainer.appendChild(document.createElement("hr"));
|
|
}
|
|
})
|
|
}
|
|
|
|
function formula(dice) {
|
|
let formula = "";
|
|
for (let index = 0; index < dice.count; index++) {
|
|
formula += Math.floor(Math.random() * dice.sides + 1);
|
|
if (index < dice.count - 1) {
|
|
formula += " + ";
|
|
}
|
|
}
|
|
if (dice.addition) {
|
|
formula += " + " + dice.addition;
|
|
}
|
|
return formula;
|
|
}
|
|
|
|
function roll(dice, time = true) {
|
|
let f = formula(dice);
|
|
const result = eval(f);
|
|
if (f.indexOf("+") == -1) {
|
|
f = "";
|
|
}
|
|
history.unshift(new DiceHistoryEntry([dice], f, result, time ? moment() : undefined));
|
|
localStorage.setItem('history', JSON.stringify(history));
|
|
redoHistory = [];
|
|
localStorage.removeItem('redoHistory');
|
|
renderHistory();
|
|
}
|
|
|
|
function rollSelected() {
|
|
const selected = dices.filter((dice) => dice.selected);
|
|
if (selected.length) {
|
|
if (history.length) {
|
|
history.unshift(new DiceHistoryEntry());
|
|
}
|
|
selected.forEach((dice, i) => {
|
|
roll(dice, i == selected.length - 1);
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
function rollForm() {
|
|
let inputSides = document.getElementById("inputSides");
|
|
if (!inputSides.value) {
|
|
inputSides = document.getElementById("inputCustom")
|
|
}
|
|
const inputAddition = document.getElementById("inputAddition");
|
|
const inputCount = document.getElementById("inputCount");
|
|
const inputColor = document.getElementById("inputColor");
|
|
if (history.length) {
|
|
history.unshift(new DiceHistoryEntry());
|
|
}
|
|
roll(new Dice(+inputSides.value, +inputAddition.value, +inputCount.value, inputColor.value));
|
|
}
|
|
|
|
function rollText() {
|
|
let inputText = document.getElementById("inputText");
|
|
const dicesTexts = inputText.value.split(" + ");
|
|
let textDices = [];
|
|
dicesTexts.forEach((diceText, i) => {
|
|
let dice = new Dice(0);
|
|
dice.fromText(diceText);
|
|
if (!dice.color) {
|
|
dice.color = default_colors[(i + dices.length) % 7];
|
|
}
|
|
if (dice.sides > 1) {
|
|
textDices.push(dice);
|
|
}
|
|
})
|
|
if (textDices.length) {
|
|
if (history.length) {
|
|
history.unshift(new DiceHistoryEntry());
|
|
}
|
|
let f = "";
|
|
textDices.forEach((dice, i) => {
|
|
f += formula(dice);
|
|
if (i < textDices.length - 1) {
|
|
f += " + ";
|
|
}
|
|
})
|
|
const result = eval(f);
|
|
if (f.indexOf("+") == -1) {
|
|
f = "";
|
|
}
|
|
history.unshift(new DiceHistoryEntry(textDices, f, result, moment()));
|
|
localStorage.setItem('history', JSON.stringify(history));
|
|
renderHistory();
|
|
}
|
|
}
|
|
|
|
function addSelected() {
|
|
const selected = dices.filter((dice) => dice.selected);
|
|
if (selected.length) {
|
|
if (history.length) {
|
|
history.unshift(new DiceHistoryEntry());
|
|
}
|
|
let f = "";
|
|
selected.forEach((dice, i) => {
|
|
f += formula(dice);
|
|
if (i < selected.length - 1) {
|
|
f += " + ";
|
|
}
|
|
})
|
|
const result = eval(f);
|
|
if (f.indexOf("+") == -1) {
|
|
f = "";
|
|
}
|
|
history.unshift(new DiceHistoryEntry(selected, f, result, moment()));
|
|
localStorage.setItem('history', JSON.stringify(history));
|
|
renderHistory();
|
|
}
|
|
}
|
|
|
|
function addDicesText() {
|
|
let inputText = document.getElementById("inputText");
|
|
const dicesTexts = inputText.value.split(" + ");
|
|
let textDices = [];
|
|
dicesTexts.forEach((diceText, i) => {
|
|
let dice = new Dice(0);
|
|
dice.fromText(diceText);
|
|
if (!dice.color) {
|
|
dice.color = default_colors[(i + dices.length) % 7];
|
|
}
|
|
if (dice.sides > 1) {
|
|
textDices.push(dice);
|
|
}
|
|
})
|
|
if (textDices.length) {
|
|
textDices.forEach((dice) => {
|
|
dices.push(dice);
|
|
})
|
|
localStorage.setItem('dices', JSON.stringify(dices));
|
|
renderDices();
|
|
}
|
|
}
|
|
|
|
function clearHistory() {
|
|
history = [];
|
|
localStorage.removeItem('history');
|
|
localStorage.removeItem('redoHistory');
|
|
renderHistory();
|
|
}
|
|
|
|
function exportData() {
|
|
const downloadButton = document.createElement('a');
|
|
downloadButton.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(JSON.stringify({ dices: dices, history: history, redoHistory: redoHistory })));
|
|
downloadButton.setAttribute('download', 'rgp-dices-' + new Date().toISOString() + '.json');
|
|
document.body.appendChild(downloadButton);
|
|
downloadButton.click();
|
|
document.body.removeChild(downloadButton);
|
|
}
|
|
|
|
function importData(event) {
|
|
event.target.parentElement.classList.remove("error");
|
|
try {
|
|
const reader = new FileReader();
|
|
reader.addEventListener('load', async (event) => {
|
|
const data = JSON.parse(event.target.result);
|
|
if (data.dices) {
|
|
dices = data.dices.map((dice) => new Dice(dice.sides, dice.addition, dice.count, dice.color));
|
|
|
|
localStorage.setItem('dices', JSON.stringify(dices));
|
|
renderDices();
|
|
}
|
|
if (data.history) {
|
|
history = data.history.map((entry) => new DiceHistoryEntry(entry.dices && entry.dices.map((dice) => new Dice(dice.sides, dice.addition, dice.count, dice.color)) || undefined, entry.formula, entry.result, entry.time && moment(entry.time) || undefined));
|
|
localStorage.setItem('history', JSON.stringify(history));
|
|
renderHistory();
|
|
}
|
|
if (data.redoHistory) {
|
|
redoHistory = data.redoHistory.map((entry) => new DiceHistoryEntry(entry.dices && entry.dices.map((dice) => new Dice(dice.sides, dice.addition, dice.count, dice.color)) || undefined, entry.formula, entry.result, entry.time && moment(entry.time) || undefined));
|
|
localStorage.setItem('redoHistory', JSON.stringify(redoHistory));
|
|
}
|
|
});
|
|
|
|
reader.readAsText(event.target.files[0]);
|
|
} catch (e) {
|
|
console.warn(e);
|
|
event.target.parentElement.classList.add("error");
|
|
}
|
|
}
|
|
|
|
function updateCustom() {
|
|
if (!document.getElementById("inputSides").value) {
|
|
document.getElementById("inputCustom").classList.remove("hidden");
|
|
} else {
|
|
document.getElementById("inputCustom").classList.add("hidden");
|
|
}
|
|
}
|
|
|
|
function toggleDarkMode() {
|
|
if (darkMode) {
|
|
darkMode = false;
|
|
document.body.classList.remove("dark");
|
|
document.getElementById("dark-mode-icon").src = "./assets/dark.svg";
|
|
document.getElementById("dark-mode-text").innerText = "Dark Mode";
|
|
} else {
|
|
darkMode = true;
|
|
document.body.classList.add("dark");
|
|
document.getElementById("dark-mode-icon").src = "./assets/light.svg";
|
|
document.getElementById("dark-mode-text").innerText = "Light Mode";
|
|
}
|
|
renderDices();
|
|
renderHistory();
|
|
}
|
|
|
|
function undo() {
|
|
while (history.length && history[0].result) {
|
|
redoHistory.push(history.shift());
|
|
}
|
|
if (history.length && !history[0].result) {
|
|
redoHistory.push(history.shift());
|
|
}
|
|
|
|
localStorage.setItem('history', JSON.stringify(history));
|
|
localStorage.setItem('redoHistory', JSON.stringify(redoHistory));
|
|
renderHistory();
|
|
}
|
|
|
|
function redo() {
|
|
if (redoHistory.length && !redoHistory[redoHistory.length - 1].result) {
|
|
history.unshift(redoHistory.pop());
|
|
} else if (redoHistory.length) {
|
|
history.unshift(new DiceHistoryEntry());
|
|
}
|
|
|
|
while (redoHistory.length && redoHistory[redoHistory.length - 1].result) {
|
|
history.unshift(redoHistory.pop());
|
|
}
|
|
|
|
localStorage.setItem('history', JSON.stringify(history));
|
|
localStorage.setItem('redoHistory', JSON.stringify(redoHistory));
|
|
renderHistory();
|
|
}
|
|
|
|
async function clearAndRefresh() {
|
|
if ('caches' in window) {
|
|
const keyList = await caches.keys();
|
|
await Promise.all(keyList.map(async (key) => await caches.delete(key)));
|
|
}
|
|
window.location.reload()
|
|
}
|
|
|
|
if (localStorage.getItem('dices')) {
|
|
dices = JSON.parse(localStorage.getItem('dices')).map((dice) => new Dice(dice.sides, dice.addition, dice.count, dice.color));
|
|
} else {
|
|
default_sides.forEach((side, i) => {
|
|
dices.push(new Dice(side, 0, 1, default_colors[i]));
|
|
})
|
|
}
|
|
|
|
if (localStorage.getItem('history')) {
|
|
history = JSON.parse(localStorage.getItem('history')).map((entry) => new DiceHistoryEntry(entry.dices && entry.dices.map((dice) => new Dice(dice.sides, dice.addition, dice.count, dice.color)) || undefined, entry.formula, entry.result, entry.time && moment(entry.time) || undefined));
|
|
}
|
|
|
|
if (localStorage.getItem('redoHistory')) {
|
|
redoHistory = JSON.parse(localStorage.getItem('history')).map((entry) => new DiceHistoryEntry(entry.dices && entry.dices.map((dice) => new Dice(dice.sides, dice.addition, dice.count, dice.color)) || undefined, entry.formula, entry.result, entry.time && moment(entry.time) || undefined));
|
|
}
|
|
|
|
document.getElementById("importFile").addEventListener("change", importData);
|
|
|
|
document.getElementById("inputText").addEventListener("keyup", (event) => {
|
|
if (event.key.toUpperCase() === 'ENTER') {
|
|
this.rollText();
|
|
}
|
|
});
|
|
|
|
|
|
document.addEventListener("keyup", (event) => {
|
|
if (!isNaN(+event.key)) {
|
|
let number = +event.key;
|
|
if (number == 0) {
|
|
number = 10;
|
|
}
|
|
number--;
|
|
if (number < dices.length) {
|
|
if (history.length) {
|
|
history.unshift(new DiceHistoryEntry());
|
|
}
|
|
roll(dices[number]);
|
|
}
|
|
} else if (event.ctrlKey && event.key.toUpperCase() === 'Z') {
|
|
undo();
|
|
} else if (event.ctrlKey && (event.shiftKey && event.key.toUpperCase() === 'Z' || event.key.toUpperCase() === 'Y')) {
|
|
redo();
|
|
}
|
|
});
|
|
|
|
renderDices();
|
|
renderHistory();
|
|
updateCustom();
|
|
|
|
if (darkMode) {
|
|
document.body.classList.add("dark");
|
|
document.getElementById("dark-mode-icon").src = "./assets/light.svg";
|
|
document.getElementById("dark-mode-text").innerText = "Light Mode";
|
|
}
|
|
|
|
if ("serviceWorker" in navigator) {
|
|
window.addEventListener("load", function () {
|
|
navigator.serviceWorker
|
|
.register("/sw.js")
|
|
.then(res => console.trace("service worker registered"))
|
|
.catch(err => console.error("service worker not registered", err))
|
|
})
|
|
}
|