From ff7f24f02e8ae7c8e9d655f78f02269e65fa968a Mon Sep 17 00:00:00 2001 From: Lurkars Date: Fri, 3 May 2024 19:45:07 +0200 Subject: [PATCH] initialize --- assets/add.svg | 1 + assets/close.svg | 1 + assets/dark.svg | 1 + assets/dices.svg | 1 + assets/dices/10.svg | 15 ++ assets/dices/100.svg | 15 ++ assets/dices/12.svg | 15 ++ assets/dices/20.svg | 15 ++ assets/dices/4.svg | 15 ++ assets/dices/6.svg | 15 ++ assets/dices/8.svg | 15 ++ assets/dices/custom.svg | 15 ++ assets/export.svg | 1 + assets/history.svg | 1 + assets/import.svg | 1 + assets/light.svg | 1 + assets/menu.svg | 1 + assets/roll.svg | 1 + index.html | 72 ++++++++ js/libs/sidebar/sidebar.css | 41 +++++ js/libs/sidebar/sidebar.js | 17 ++ script.js | 318 ++++++++++++++++++++++++++++++++++++ style.css | 290 ++++++++++++++++++++++++++++++++ 23 files changed, 868 insertions(+) create mode 100644 assets/add.svg create mode 100644 assets/close.svg create mode 100644 assets/dark.svg create mode 100644 assets/dices.svg create mode 100755 assets/dices/10.svg create mode 100755 assets/dices/100.svg create mode 100755 assets/dices/12.svg create mode 100755 assets/dices/20.svg create mode 100755 assets/dices/4.svg create mode 100755 assets/dices/6.svg create mode 100755 assets/dices/8.svg create mode 100644 assets/dices/custom.svg create mode 100644 assets/export.svg create mode 100644 assets/history.svg create mode 100644 assets/import.svg create mode 100644 assets/light.svg create mode 100644 assets/menu.svg create mode 100644 assets/roll.svg create mode 100755 index.html create mode 100644 js/libs/sidebar/sidebar.css create mode 100644 js/libs/sidebar/sidebar.js create mode 100755 script.js create mode 100755 style.css diff --git a/assets/add.svg b/assets/add.svg new file mode 100644 index 0000000..b7b9ead --- /dev/null +++ b/assets/add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/close.svg b/assets/close.svg new file mode 100644 index 0000000..989837c --- /dev/null +++ b/assets/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/dark.svg b/assets/dark.svg new file mode 100644 index 0000000..49e0f57 --- /dev/null +++ b/assets/dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/dices.svg b/assets/dices.svg new file mode 100644 index 0000000..efd2e89 --- /dev/null +++ b/assets/dices.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/dices/10.svg b/assets/dices/10.svg new file mode 100755 index 0000000..8a1a234 --- /dev/null +++ b/assets/dices/10.svg @@ -0,0 +1,15 @@ + + + + diff --git a/assets/dices/100.svg b/assets/dices/100.svg new file mode 100755 index 0000000..898ef0e --- /dev/null +++ b/assets/dices/100.svg @@ -0,0 +1,15 @@ + + + + diff --git a/assets/dices/12.svg b/assets/dices/12.svg new file mode 100755 index 0000000..974d316 --- /dev/null +++ b/assets/dices/12.svg @@ -0,0 +1,15 @@ + + + + diff --git a/assets/dices/20.svg b/assets/dices/20.svg new file mode 100755 index 0000000..c1e60b2 --- /dev/null +++ b/assets/dices/20.svg @@ -0,0 +1,15 @@ + + + + diff --git a/assets/dices/4.svg b/assets/dices/4.svg new file mode 100755 index 0000000..9f58e51 --- /dev/null +++ b/assets/dices/4.svg @@ -0,0 +1,15 @@ + + + + diff --git a/assets/dices/6.svg b/assets/dices/6.svg new file mode 100755 index 0000000..f2495cc --- /dev/null +++ b/assets/dices/6.svg @@ -0,0 +1,15 @@ + + + + diff --git a/assets/dices/8.svg b/assets/dices/8.svg new file mode 100755 index 0000000..b55084c --- /dev/null +++ b/assets/dices/8.svg @@ -0,0 +1,15 @@ + + + + diff --git a/assets/dices/custom.svg b/assets/dices/custom.svg new file mode 100644 index 0000000..e60fd55 --- /dev/null +++ b/assets/dices/custom.svg @@ -0,0 +1,15 @@ + + + + diff --git a/assets/export.svg b/assets/export.svg new file mode 100644 index 0000000..e40eaee --- /dev/null +++ b/assets/export.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/history.svg b/assets/history.svg new file mode 100644 index 0000000..3f7c900 --- /dev/null +++ b/assets/history.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/import.svg b/assets/import.svg new file mode 100644 index 0000000..ee50f7e --- /dev/null +++ b/assets/import.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/light.svg b/assets/light.svg new file mode 100644 index 0000000..6eda6ed --- /dev/null +++ b/assets/light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/menu.svg b/assets/menu.svg new file mode 100644 index 0000000..16fbd60 --- /dev/null +++ b/assets/menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/roll.svg b/assets/roll.svg new file mode 100644 index 0000000..f338e42 --- /dev/null +++ b/assets/roll.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100755 index 0000000..f61f04f --- /dev/null +++ b/index.html @@ -0,0 +1,72 @@ + + + +RPG Dices + + + + + + + + + + +
+
+
+ +
+ + D + + + + + + + +
+
+ +
+ + +
+
+
+ + + + + \ No newline at end of file diff --git a/js/libs/sidebar/sidebar.css b/js/libs/sidebar/sidebar.css new file mode 100644 index 0000000..2f80227 --- /dev/null +++ b/js/libs/sidebar/sidebar.css @@ -0,0 +1,41 @@ +/* sidebar */ +#sidebar { + position: fixed; + top: 0; + left: 0; + height: 100%; + transition: 0.4s; + overflow: hidden; + z-index: 10; + } + + .sidebar-toggle { + cursor: pointer; + font-size: 2em; + padding: 5px; + } + + #sidebar-toggle { + position: fixed; + top: 0; + z-index: 1; + } + + #sidebar-toggle-input { + display: none; + } + + #sidebar-toggle-input:not(:checked) ~ #sidebar { + width: 0px; + } + + #sidebar-toggle-input:checked ~ #sidebar { + width: 250px; + } + + @media screen and (max-width: 767px) { + #sidebar-toggle-input:checked ~ #sidebar { + width: 100%; + } + } + \ No newline at end of file diff --git a/js/libs/sidebar/sidebar.js b/js/libs/sidebar/sidebar.js new file mode 100644 index 0000000..6f06d37 --- /dev/null +++ b/js/libs/sidebar/sidebar.js @@ -0,0 +1,17 @@ +window.Sidebar = function () { + let sidebar = document.querySelector("#sidebar"); + let close = document.querySelector("#sidebar-toggle-close"); + let toggle = document.querySelector("#sidebar-toggle-input"); + + sidebar.addEventListener("click", function (event) { + if (document.body.clientWidth < 1280) { + toggle.checked = false; + } + }) + + close.addEventListener("click", function (event) { + if (document.body.clientWidth < 1280) { + event.preventDefault(); + } + }) + } \ No newline at end of file diff --git a/script.js b/script.js new file mode 100755 index 0000000..892dfb1 --- /dev/null +++ b/script.js @@ -0,0 +1,318 @@ +class Dice { + constructor(sides, addition = 0, count = 1, color = "#000") { + this.sides = sides; + this.addition = addition; + this.count = count; + this.color = color; + this.selected = false; + } +} + +class DiceHistoryEntry { + constructor(dice = undefined, formula = undefined, result = undefined) { + this.dice = dice; + this.formula = formula; + this.result = result; + } +} + +const default_sides = [4, 6, 8, 10, 12, 20, 100]; +const default_colors = ["#de324c", "#f4895f", "#f8e16f", "#95cf92", "#369acc", "#9656a2", "#000000"]; + +let darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + +let dices = []; +let history = []; + +let dicesContainer = document.getElementById("dices"); +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) { + 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"); + } else { + document.getElementById('roll-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 = getDiceLabel(dice); + + 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"); + } else { + document.getElementById('roll-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"); + } else { + document.getElementById('history-button').classList.add("disabled"); + } + history.forEach((entry) => { + if (entry.dice && entry.result) { + const entryElement = document.createElement("div"); + entryElement.classList.add("entry"); + const diceLabel = document.createElement("label"); + diceLabel.innerHTML = getDiceLabel(entry.dice) + ": "; + diceLabel.style.color = entry.dice.color; + // revert b/w on dark-mode + if (darkMode) { + if (["#000", "#000000", "black", "rgb(0,0,0)", "rgb(0, 0, 0)"].indexOf(entry.dice.color) != -1) { + diceLabel.style.color = "#fff"; + } else if (["#fff", "#ffffff", "white", "rgb(255,255,255)", "rgb(255, 255, 255)"].indexOf(entry.dice.color) != -1) { + diceLabel.style.color = "#000"; + } + } + entryElement.appendChild(diceLabel); + + const diceResult = document.createElement("span"); + diceResult.innerText = entry.result; + entryElement.appendChild(diceResult); + + const diceFormula = document.createElement("span"); + const diceEqual = document.createElement("span"); + entryElement.appendChild(diceEqual); + entryElement.appendChild(diceFormula); + + if (entry.formula) { + diceFormula.innerText = entry.formula; + diceEqual.innerText = " = " + } + + historyContainer.appendChild(entryElement); + } else { + historyContainer.appendChild(document.createElement("hr")); + } + }) +} + +function roll(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; + } + const result = eval(formula); + if (formula.indexOf("+") == -1) { + formula = ""; + } + history.unshift(new DiceHistoryEntry(dice, formula, result)); + localStorage.setItem('history', JSON.stringify(history)); + renderHistory(); +} + +function rollSelected() { + if (dices.filter((dice) => dice.selected).length) { + if (history.length) { + history.unshift(new DiceHistoryEntry()); + } + dices.forEach((dice) => { + if (dice.selected) { + roll(dice); + } + }) + } +} + +function clearHistory() { + history = []; + localStorage.removeItem('history'); + renderHistory(); +} + +function exportData() { + const downloadButton = document.createElement('a'); + downloadButton.setAttribute('href', 'data:application/json;charset=utf-8,' + encodeURIComponent(JSON.stringify({ dices: dices, history: history }))); + 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; + renderDices(); + } + if (data.history) { + history = data.history; + renderHistory(); + } + }); + + 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(); +} + +if (localStorage.getItem('dices')) { + dices = JSON.parse(localStorage.getItem('dices')); +} 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')); +} + +document.getElementById("importFile").addEventListener("change", importData); + +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"; +} \ No newline at end of file diff --git a/style.css b/style.css new file mode 100755 index 0000000..f477378 --- /dev/null +++ b/style.css @@ -0,0 +1,290 @@ +* { + box-sizing: border-box; +} + + +body { + background-color: #fff; + color: #000; +} + +.hidden { + display: none; +} + +#sidebar { + white-space: nowrap; + background-color: #fff; +} + +.menu { + margin: 0; + padding: 0; + margin-top: 1.5em; + margin-left: 0.5em; + list-style: none; +} + +.menu input[type="file"] { + width: 0; + height: 0; + margin: 0; + padding: 0; + visibility: hidden; +} + +.menu li { + cursor: pointer; + display: flex; + justify-content: start; + align-items: center; + margin: 0.4em 0; +} + +.menu li:hover { + opacity: 0.7; +} + +.menu li label { + cursor: pointer; + display: flex; + justify-content: start; + align-items: center; +} + +.menu li img { + height: 1.2em; + width: auto; + margin-right: 0.2em; +} + +.container, +.dices-container, +.history-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + max-width: 100%; +} + +.dices { + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + min-height: 12em; +} + +.dice { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 10em; + height: 12em; + margin: 0 0.2em; + border: 0.3em solid transparent; +} + +.dice .label-container { + position: relative; + display: flex; + justify-content: center; + align-items: center; + margin-top: 0.2em; + z-index: 2; + height: 2em; +} + +.dice label { + font-size: 1em; + opacity: 0.7; +} + +.dice.selected label { + font-size: 1.2em; + font-weight: bold; + opacity: 1; +} + +.dice .dice-image-container { + position: relative; + width: 8em; + height: 8em; + z-index: 1; + filter: drop-shadow(0.1em 0.1em 0.1em transparent) drop-shadow(-0.1em -0.1em 0.1em transparent); +} + +.dice .dice-image { + position: relative; + width: 100%; + height: 100%; + mask-size: contain; + mask-repeat: no-repeat; + mask-position: center; +} + +.dice.selected .dice-image-container { + filter: drop-shadow(0.1em 0.1em 0.1em #333) drop-shadow(-0.1em -0.1em 0.1em #333); +} + +.dice .remove { + cursor: pointer; + position: absolute; + top: 0.2em; + right: 0.2em; + height: 1.2em; + width: 1.2em; + z-index: 5; +} + +.dice .remove::before { + content: ' '; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #DD0000; + mask-size: contain; + mask-repeat: no-repeat; + mask-position: center; + mask-image: url(./assets/close.svg); +} + + +.dice .remove:hover { + filter: brightness(0.5); +} + +.dice .dice-image.dice-image-4 { + mask-image: url(./assets/dices/4.svg); +} + +.dice .dice-image.dice-image-6 { + mask-image: url(./assets/dices/6.svg); +} + +.dice .dice-image.dice-image-8 { + mask-image: url(./assets/dices/8.svg); +} + +.dice .dice-image.dice-image-10 { + mask-image: url(./assets/dices/10.svg); +} + +.dice .dice-image.dice-image-12 { + mask-image: url(./assets/dices/12.svg); +} + +.dice .dice-image.dice-image-20 { + mask-image: url(./assets/dices/20.svg); +} + +.dice .dice-image.dice-image-100 { + mask-image: url(./assets/dices/100.svg); +} + +.dice .dice-image.dice-image-custom { + mask-image: url(./assets/dices/custom.svg); +} + +.form { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + margin-top: 1em; +} + +.form input, +.form select { + width: 5em; +} + +.history-container { + margin-top: 2em; + width: 400px; +} + +.history-container .menu { + display: flex; + justify-content: space-between; + width: 100%; +} + +.history { + display: flex; + flex-direction: column; + width: 100%; + margin-top: 2em; +} + +.history .entry { + margin: 0.3em 0; + display: grid; + grid-template-columns: 1fr 1fr 0.5fr 5fr; +} + +.history hr { + width: 100%; +} + +button { + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + background-color: transparent; + color: #000; + border: none; + outline: none; + font-size: 1.3em; +} + +button:hover { + opacity: 0.7; +} + +button img { + height: 1.2em; + width: auto; + margin-right: 0.2em; +} + +button.disabled { + pointer-events: none; + opacity: 0.5; +} + +body.dark { + background-color: #000; + color: #efefef; +} + +body.dark #sidebar { + background-color: #000; +} + +body.dark #sidebar-toggle, +body.dark #sidebar-toggle-close, +body.dark .menu li img, +body.dark button img { + filter: invert(); +} + +body.dark button { + color: #fff; +} + +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); +} + +@media screen and (max-width: 720px) { + .dices { + font-size: 0.65em; + } +} \ No newline at end of file