This commit is contained in:
Lurkars 2024-05-04 22:06:19 +02:00
parent ff7f24f02e
commit 027e223e7e
5 changed files with 336 additions and 80 deletions

1
assets/plus.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>

After

Width:  |  Height:  |  Size: 192 B

View File

@ -4,6 +4,7 @@
<title>RPG Dices</title> <title>RPG Dices</title>
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<script src="js/libs/moment.js"></script>
<script src="js/libs/sidebar/sidebar.js"></script> <script src="js/libs/sidebar/sidebar.js"></script>
<link href="js/libs/sidebar/sidebar.css" type="text/css" rel="stylesheet" /> <link href="js/libs/sidebar/sidebar.css" type="text/css" rel="stylesheet" />
<style> <style>
@ -25,7 +26,8 @@
</li> </li>
</ul> </ul>
<ul class="menu"> <ul class="menu">
<li onclick="toggleDarkMode()"><img id="dark-mode-icon" src="./assets/dark.svg"><span id="dark-mode-text">Dark Mode</span></li> <li onclick="toggleDarkMode()"><img id="dark-mode-icon" src="./assets/dark.svg"><span
id="dark-mode-text">Dark Mode</span></li>
</ul> </ul>
<script> <script>
Sidebar(); Sidebar();
@ -35,7 +37,8 @@
<div class="dices-container"> <div class="dices-container">
<div class="dices" id="dices"></div> <div class="dices" id="dices"></div>
<div class="form" id="form"> <div class="form-container">
<div class="form">
<input min="1" value="1" type="number" id="inputCount"> <input min="1" value="1" type="number" id="inputCount">
D D
<select value="6" id="inputSides" onchange="updateCustom()"> <select value="6" id="inputSides" onchange="updateCustom()">
@ -51,15 +54,30 @@
<input class="hidden" min="2" value="2" type="number" id="inputCustom"> <input class="hidden" min="2" value="2" type="number" id="inputCustom">
+ +
<input value="0" type="number" id="inputAddition"> <input value="0" type="number" id="inputAddition">
<input value="" type="color" id="inputColor"> &nbsp;
<button onclick="addDiceForm()"><img src="./assets/roll.svg"> Add Dice</button> <input type="color" id="inputColor">
</div>
<div class="actions">
<button onclick="addDiceForm()"><img src="./assets/plus.svg"> Add Dice</button>
<button onclick="rollForm()"><img src="./assets/roll.svg"> Roll</button>
</div>
<div class="form">
<input type="text" id="inputText">
</div>
<div class="actions">
<button onclick="addDicesText()"><img src="./assets/plus.svg"> Add Dices</button>
<button onclick="rollText()"><img src="./assets/roll.svg"> Roll</button>
</div>
</div> </div>
</div> </div>
<div class="history-container"> <div class="history-container">
<div class="menu"> <div class="actions">
<button id="roll-button" onclick="rollSelected()"><img src="./assets/roll.svg"> Roll</button> <button id="history-button" onclick="clearHistory()"><img src="./assets/history.svg"> Clear
<button id="history-button" onclick="clearHistory()"><img src="./assets/history.svg"> Clear History</button> History</button>
<button id="roll-add-button" onclick="addSelected()"><img src="./assets/add.svg"> Add Selected</button>
<button id="roll-button" onclick="rollSelected()"><img src="./assets/roll.svg"> Roll Selected</button>
</div> </div>
<div class="history" id="history"></div> <div class="history" id="history"></div>

2
js/libs/moment.js Normal file

File diff suppressed because one or more lines are too long

228
script.js
View File

@ -1,24 +1,58 @@
class Dice { class Dice {
constructor(sides, addition = 0, count = 1, color = "#000") { constructor(sides, addition = 0, count = 1, color = undefined) {
this.sides = sides; this.sides = sides;
this.addition = addition; this.addition = addition;
this.count = count; this.count = count;
this.color = color; this.color = color;
this.selected = false; 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 { class DiceHistoryEntry {
constructor(dice = undefined, formula = undefined, result = undefined) { constructor(dices = undefined, formula = undefined, result = undefined, time = undefined) {
this.dice = dice; this.dices = dices;
this.formula = formula; this.formula = formula;
this.result = result; this.result = result;
this.time = time;
} }
} }
const default_sides = [4, 6, 8, 10, 12, 20, 100]; const default_sides = [4, 6, 8, 10, 12, 20, 100];
const default_colors = ["#de324c", "#f4895f", "#f8e16f", "#95cf92", "#369acc", "#9656a2", "#000000"]; const default_colors = ["#de324c", "#f4895f", "#f8e16f", "#95cf92", "#369acc", "#9656a2", "#000000"];
const dice_regex = /(\d+)?[D|d](\d+)([\+|\-]\d+)?(\[(.+)\])?/;
let darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; let darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
let dices = []; let dices = [];
@ -27,10 +61,6 @@ let history = [];
let dicesContainer = document.getElementById("dices"); let dicesContainer = document.getElementById("dices");
let historyContainer = document.getElementById("history"); let historyContainer = document.getElementById("history");
function getDiceLabel(dice) {
return (dice.count > 1 ? dice.count : "") + "D" + dice.sides + (dice.addition ? (dice.addition < 0 ? "-" : "+") + Math.abs(dice.addition) : "");
}
function renderDice(dice, index) { function renderDice(dice, index) {
const diceElement = document.createElement("div"); const diceElement = document.createElement("div");
diceElement.classList.add("dice"); diceElement.classList.add("dice");
@ -67,8 +97,10 @@ function renderDice(dice, index) {
if (dices.filter((dice) => dice.selected).length) { if (dices.filter((dice) => dice.selected).length) {
document.getElementById('roll-button').classList.remove("disabled"); document.getElementById('roll-button').classList.remove("disabled");
document.getElementById('roll-add-button').classList.remove("disabled");
} else { } else {
document.getElementById('roll-button').classList.add("disabled"); document.getElementById('roll-button').classList.add("disabled");
document.getElementById('roll-add-button').classList.add("disabled");
} }
localStorage.setItem('dices', JSON.stringify(dices)); localStorage.setItem('dices', JSON.stringify(dices));
@ -81,7 +113,7 @@ function renderDice(dice, index) {
diceLabelContainer.classList.add("label-container"); diceLabelContainer.classList.add("label-container");
const diceLabel = document.createElement("label"); const diceLabel = document.createElement("label");
diceLabel.innerText = getDiceLabel(dice); diceLabel.innerText = dice.toText();
diceLabelContainer.appendChild(diceLabel); diceLabelContainer.appendChild(diceLabel);
diceElement.appendChild(diceLabelContainer); diceElement.appendChild(diceLabelContainer);
@ -111,8 +143,10 @@ function renderDices() {
if (dices.filter((dice) => dice.selected).length) { if (dices.filter((dice) => dice.selected).length) {
document.getElementById('roll-button').classList.remove("disabled"); document.getElementById('roll-button').classList.remove("disabled");
document.getElementById('roll-add-button').classList.remove("disabled");
} else { } else {
document.getElementById('roll-button').classList.add("disabled"); document.getElementById('roll-button').classList.add("disabled");
document.getElementById('roll-add-button').classList.add("disabled");
} }
} }
@ -161,44 +195,63 @@ function renderHistory() {
document.getElementById('history-button').classList.add("disabled"); document.getElementById('history-button').classList.add("disabled");
} }
history.forEach((entry) => { history.forEach((entry) => {
if (entry.dice && entry.result) { if (entry.result) {
const entryElement = document.createElement("div"); const diceLabelContainer = document.createElement("div");
entryElement.classList.add("entry"); 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"); const diceLabel = document.createElement("label");
diceLabel.innerHTML = getDiceLabel(entry.dice) + ":&nbsp;"; diceLabel.innerHTML = dice.toText() + (i == entry.dices.length - 1 ? ":&nbsp;" : "");
diceLabel.style.color = entry.dice.color; diceLabel.style.color = dice.color;
// revert b/w on dark-mode // revert b/w on dark-mode
if (darkMode) { if (darkMode) {
if (["#000", "#000000", "black", "rgb(0,0,0)", "rgb(0, 0, 0)"].indexOf(entry.dice.color) != -1) { if (["#000", "#000000", "black", "rgb(0,0,0)", "rgb(0, 0, 0)"].indexOf(dice.color) != -1) {
diceLabel.style.color = "#fff"; diceLabel.style.color = "#fff";
} else if (["#fff", "#ffffff", "white", "rgb(255,255,255)", "rgb(255, 255, 255)"].indexOf(entry.dice.color) != -1) { } else if (["#fff", "#ffffff", "white", "rgb(255,255,255)", "rgb(255, 255, 255)"].indexOf(dice.color) != -1) {
diceLabel.style.color = "#000"; diceLabel.style.color = "#000";
} }
} }
entryElement.appendChild(diceLabel); diceLabelContainer.appendChild(diceLabel);
if (i < entry.dices.length - 1) {
const diceLabelAdd = document.createElement("span");
diceLabelAdd.innerHTML = "&nbsp;+&nbsp;";
diceLabelContainer.appendChild(diceLabelAdd);
}
})
}
historyContainer.appendChild(diceLabelContainer);
const diceResult = document.createElement("span"); const diceResult = document.createElement("span");
diceResult.classList.add("result");
diceResult.innerText = entry.result; diceResult.innerText = entry.result;
entryElement.appendChild(diceResult); historyContainer.appendChild(diceResult);
const diceFormula = document.createElement("span"); const diceFormula = document.createElement("span");
const diceEqual = document.createElement("span"); diceFormula.classList.add("formula");
entryElement.appendChild(diceEqual); historyContainer.appendChild(diceFormula);
entryElement.appendChild(diceFormula);
if (entry.formula) { if (entry.formula) {
diceFormula.innerText = entry.formula; diceFormula.innerText = " = " + entry.formula;
diceEqual.innerText = " = " }
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");
} }
historyContainer.appendChild(entryElement);
} else { } else {
historyContainer.appendChild(document.createElement("hr")); historyContainer.appendChild(document.createElement("hr"));
} }
}) })
} }
function roll(dice) { function formula(dice) {
let formula = ""; let formula = "";
for (let index = 0; index < dice.count; index++) { for (let index = 0; index < dice.count; index++) {
formula += Math.floor(Math.random() * dice.sides + 1); formula += Math.floor(Math.random() * dice.sides + 1);
@ -209,25 +262,125 @@ function roll(dice) {
if (dice.addition) { if (dice.addition) {
formula += " + " + dice.addition; formula += " + " + dice.addition;
} }
const result = eval(formula); return formula;
if (formula.indexOf("+") == -1) { }
formula = "";
function roll(dice, time = true) {
let f = formula(dice);
const result = eval(f);
if (f.indexOf("+") == -1) {
f = "";
} }
history.unshift(new DiceHistoryEntry(dice, formula, result)); history.unshift(new DiceHistoryEntry([dice], f, result, time ? moment() : undefined));
localStorage.setItem('history', JSON.stringify(history)); localStorage.setItem('history', JSON.stringify(history));
renderHistory(); renderHistory();
} }
function rollSelected() { function rollSelected() {
if (dices.filter((dice) => dice.selected).length) { const selected = dices.filter((dice) => dice.selected);
if (selected.length) {
if (history.length) { if (history.length) {
history.unshift(new DiceHistoryEntry()); history.unshift(new DiceHistoryEntry());
} }
dices.forEach((dice) => { selected.forEach((dice, i) => {
if (dice.selected) { roll(dice, i == selected.length - 1);
roll(dice); })
}
}
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();
} }
} }
@ -253,11 +406,14 @@ function importData(event) {
reader.addEventListener('load', async (event) => { reader.addEventListener('load', async (event) => {
const data = JSON.parse(event.target.result); const data = JSON.parse(event.target.result);
if (data.dices) { if (data.dices) {
dices = data.dices; dices = data.dices.map((dice) => new Dice(dice.sides, dice.addition, dice.count, dice.color));
localStorage.setItem('dices', JSON.stringify(dices));
renderDices(); renderDices();
} }
if (data.history) { if (data.history) {
history = 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(); renderHistory();
} }
}); });
@ -294,7 +450,7 @@ function toggleDarkMode() {
} }
if (localStorage.getItem('dices')) { if (localStorage.getItem('dices')) {
dices = JSON.parse(localStorage.getItem('dices')); dices = JSON.parse(localStorage.getItem('dices')).map((dice) => new Dice(dice.sides, dice.addition, dice.count, dice.color));
} else { } else {
default_sides.forEach((side, i) => { default_sides.forEach((side, i) => {
dices.push(new Dice(side, 0, 1, default_colors[i])); dices.push(new Dice(side, 0, 1, default_colors[i]));
@ -302,7 +458,7 @@ if (localStorage.getItem('dices')) {
} }
if (localStorage.getItem('history')) { if (localStorage.getItem('history')) {
history = JSON.parse(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));
} }
document.getElementById("importFile").addEventListener("change", importData); document.getElementById("importFile").addEventListener("change", importData);

111
style.css
View File

@ -6,6 +6,7 @@
body { body {
background-color: #fff; background-color: #fff;
color: #000; color: #000;
font-family: 'Courier New', Courier, monospace;
} }
.hidden { .hidden {
@ -68,6 +69,11 @@ body {
max-width: 100%; max-width: 100%;
} }
.dices-container {
margin-top: 1em;
font-size: 0.8em;
}
.dices { .dices {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -95,6 +101,7 @@ body {
align-items: center; align-items: center;
margin-top: 0.2em; margin-top: 0.2em;
z-index: 2; z-index: 2;
font-size: 1.3em;
height: 2em; height: 2em;
} }
@ -121,6 +128,7 @@ body {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: #000;
mask-size: contain; mask-size: contain;
mask-repeat: no-repeat; mask-repeat: no-repeat;
mask-position: center; mask-position: center;
@ -191,48 +199,98 @@ body {
mask-image: url(./assets/dices/custom.svg); mask-image: url(./assets/dices/custom.svg);
} }
.form { .form-container {
margin-top: 1em;
display: grid;
align-items: center;
row-gap: 1em;
grid-template-columns: auto auto;
}
.form-container .actions {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: space-between;
align-items: center;
}
.form {
font-size: 1.5em;
display: flex;
flex-wrap: wrap;
justify-content: start;
align-items: center; align-items: center;
margin-top: 1em;
} }
.form input, .form input,
.form select { .form select {
font-size: 0.9em;
width: 5em; width: 5em;
height: 2.2em;
background-color: #fff;
font-family: 'Courier New', Courier, monospace;
border: 0.15em solid #000;
outline: none;
padding: 0.4em;
box-sizing: border-box;
}
.form #inputText {
width: 22em;
max-width: 99vw;
} }
.history-container { .history-container {
margin-top: 2em; margin: 2em 0;
width: 400px; max-width: 99vw;
} }
.history-container .menu { .history-container .actions {
display: flex; display: flex;
justify-content: space-between; flex-wrap: wrap;
justify-content: space-around;
width: 100%; width: 100%;
} }
.history-container .actions button {
font-size: 1em;
margin: 0 0.5em;
}
.history { .history {
display: flex; display: grid;
flex-direction: column; grid-template-columns: repeat(4, auto);
width: 100%; width: 100%;
margin-top: 2em; margin-top: 2em;
} row-gap: 0.3em;
column-gap: 0.3em;
.history .entry {
margin: 0.3em 0;
display: grid;
grid-template-columns: 1fr 1fr 0.5fr 5fr;
} }
.history hr { .history hr {
width: 100%; width: 100%;
grid-column: span 4;
}
.history .label {
font-size: 1.2em;
}
.history .result {
text-align: center;
font-size: 1.2em;
font-weight: bold;
}
.history .formula {
margin: 0 0.5em;
}
.history .time {
text-align: right;
} }
button { button {
font-family: 'Courier New', Courier, monospace;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -279,12 +337,33 @@ body.dark button {
color: #fff; color: #fff;
} }
body.dark .dice .dice-image {
background-color: #fff;
}
body.dark .dice.selected .dice-image-container { body.dark .dice.selected .dice-image-container {
filter: drop-shadow(0.1em 0.1em 0.1em #efefef) drop-shadow(-0.1em -0.1em 0.1em #efefef); filter: drop-shadow(0.1em 0.1em 0.1em #efefef) drop-shadow(-0.1em -0.1em 0.1em #efefef);
} }
@media screen and (max-width: 720px) { body.dark .form input,
body.dark .form select {
color: #fff;
background-color: #000;
border-color: #fff;
}
@media screen and (min-width: 768px) {
.container {
padding: 0 250px;
}
}
@media screen and (max-width: 767px) {
.dices { .dices {
font-size: 0.65em; font-size: 0.65em;
} }
.form-container {
grid-template-columns: auto;
}
} }