24 Commits
1.0.4 ... 1.1.2

Author SHA1 Message Date
gitea
cb1d34fd09 [no ci] release 1.1.2 2025-02-16 12:38:55 +00:00
8a212a5044 Merge pull request 'adds html rich preview' (#31) from rich-preview into main
All checks were successful
CI / deploy (push) Successful in 10m46s
Reviewed-on: #31
2025-02-16 13:28:16 +01:00
09473f627b adds html rich preview
All checks were successful
CI / deploy (push) Successful in 5m15s
CI / deploy (pull_request) Successful in 10m10s
2025-02-16 12:56:33 +01:00
gitea
fbfd23d335 [no ci] prepare new Version 2025-02-12 17:07:20 +00:00
gitea
0ccbd9dd0b [no ci] release 1.1.1 2025-02-12 17:07:14 +00:00
b79c2b1e42 adds user to chatoverlay
All checks were successful
CI / deploy (push) Successful in 4m50s
2025-02-12 18:02:24 +01:00
gitea
f3f788ea51 [no ci] prepare new Version 2025-02-12 15:20:36 +00:00
gitea
cd5417d1d1 [no ci] release 1.1.0 2025-02-12 15:20:29 +00:00
f3e8432452 Merge pull request 'creates chatoverlay' (#30) from chatoverlay into main
All checks were successful
CI / deploy (push) Successful in 4m48s
Reviewed-on: #30
2025-02-12 16:15:48 +01:00
1eae3a8bbf creates chatoverlay
All checks were successful
CI / deploy (push) Successful in 4m25s
CI / deploy (pull_request) Successful in 4m28s
2025-02-12 16:06:22 +01:00
gitea
dd1497c432 [no ci] prepare new Version 2025-02-12 12:28:34 +00:00
gitea
867883b1e7 [no ci] release 1.0.7 2025-02-12 12:28:28 +00:00
af04783685 Merge pull request 'cleans up code' (#29) from clean-up into main
All checks were successful
CI / deploy (push) Successful in 4m47s
Reviewed-on: #29
2025-02-12 13:23:47 +01:00
9a9aa95cd1 cleans up code
All checks were successful
CI / deploy (push) Successful in 4m27s
CI / deploy (pull_request) Successful in 4m26s
2025-02-12 13:13:11 +01:00
9e32ed95d1 [no ci] updates preview 2025-02-12 06:17:07 +01:00
gitea
8089bbeebd [no ci] prepare new Version 2025-02-12 04:55:38 +00:00
gitea
52d16cfee5 [no ci] release 1.0.6 2025-02-12 04:55:31 +00:00
e23592b6a4 moves git tag before snapshot
All checks were successful
CI / deploy (push) Successful in 8m36s
2025-02-12 05:46:56 +01:00
a166eecd60 Merge pull request 'removes redundant rejoin button' (#28) from redesign into main
Some checks failed
CI / deploy (push) Has been cancelled
Reviewed-on: #28
2025-02-12 05:44:40 +01:00
4b8b2ce5f3 removes redundant rejoin button
Some checks failed
CI / deploy (push) Has been cancelled
CI / deploy (pull_request) Has been cancelled
2025-02-12 05:43:19 +01:00
gitea
022186964d [no ci] prepare new Version 2025-02-12 04:26:25 +00:00
gitea
4604be151b [no ci] release 1.0.5 2025-02-12 04:26:19 +00:00
e906c38630 Merge pull request 'redesigns page to fit inside 800x600' (#27) from redesign into main
All checks were successful
CI / deploy (push) Successful in 4m53s
Reviewed-on: #27
2025-02-12 05:21:31 +01:00
035445ac3f redesigns page to fit inside 800x600
All checks were successful
CI / deploy (push) Successful in 4m25s
CI / deploy (pull_request) Successful in 4m32s
2025-02-12 05:12:16 +01:00
17 changed files with 894 additions and 631 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@@ -62,9 +62,9 @@ jobs:
git config user.name "gitea" git config user.name "gitea"
git add ./pom.xml git add ./pom.xml
git commit -m "[no ci] release ${{ steps.version.outputs.VERSION }}" git commit -m "[no ci] release ${{ steps.version.outputs.VERSION }}"
git tag ${{ steps.version.outputs.VERSION }} -m "release ${{ steps.version.outputs.VERSION }}"
./mvnw -B --no-transfer-progress clean validate -Pnew-snapshot ./mvnw -B --no-transfer-progress clean validate -Pnew-snapshot
git add ./pom.xml git add ./pom.xml
git commit -m "[no ci] prepare new Version" git commit -m "[no ci] prepare new Version"
git tag ${{ steps.version.outputs.VERSION }} -m "release ${{ steps.version.outputs.VERSION }}"
git push origin main git push origin main
git push origin ${{ steps.version.outputs.VERSION }} git push origin ${{ steps.version.outputs.VERSION }}

View File

@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>de.arindy</groupId> <groupId>de.arindy</groupId>
<artifactId>dice-tower</artifactId> <artifactId>dice-tower</artifactId>
<version>1.0.5-SNAPSHOT</version> <version>1.1.2</version>
<properties> <properties>
<compiler-plugin.version>3.13.0</compiler-plugin.version> <compiler-plugin.version>3.13.0</compiler-plugin.version>

View File

@@ -0,0 +1,40 @@
package de.arindy.dicetower
import io.quarkus.qute.TemplateInstance
import jakarta.ws.rs.GET
import jakarta.ws.rs.Path
import jakarta.ws.rs.PathParam
import jakarta.ws.rs.Produces
import jakarta.ws.rs.QueryParam
import jakarta.ws.rs.core.MediaType
@Path("chatoverlay")
class ChatOverlayResource {
@GET
@Path("/{channel}")
@Produces(MediaType.TEXT_HTML)
fun get(
@PathParam("channel") channel: String,
@QueryParam("scale") scale: Int? = 7,
@QueryParam("maxDice") maxDice: Int? = 20,
@QueryParam("modsAllowed") modsAllowed: Boolean = false,
@QueryParam("vipAllowed") vipAllowed: Boolean = false,
@QueryParam("subsAllowed") subsAllowed: Boolean = false,
@QueryParam("cmd") cmd: String? = "roll",
@QueryParam("theme") theme: String? = "default",
@QueryParam("themeColor") themeColor: String? = "default",
@QueryParam("clearAfter") clearAfter: Long? = -1,
@QueryParam("timeout") timeout: Long? = -1
): TemplateInstance {
return Templates.chatoverlay(channel, scale ?: 7, maxDice ?: 20, modsAllowed, vipAllowed, subsAllowed, cmd ?: "roll", theme ?: "default", themeColor ?: "ff0202", clearAfter ?: 10, timeout ?: 60)
}
@GET
@Produces(MediaType.TEXT_HTML)
fun config(
): TemplateInstance {
return Templates.chatoverlayconfig()
}
}

View File

@@ -1,23 +1,12 @@
package de.arindy.dicetower package de.arindy.dicetower
import io.quarkus.qute.TemplateInstance import io.quarkus.qute.TemplateInstance
import io.quarkus.runtime.annotations.RegisterForReflection
import jakarta.enterprise.context.ApplicationScoped import jakarta.enterprise.context.ApplicationScoped
import jakarta.ws.rs.Consumes
import jakarta.ws.rs.GET import jakarta.ws.rs.GET
import jakarta.ws.rs.POST
import jakarta.ws.rs.Path import jakarta.ws.rs.Path
import jakarta.ws.rs.PathParam
import jakarta.ws.rs.Produces import jakarta.ws.rs.Produces
import jakarta.ws.rs.QueryParam
import jakarta.ws.rs.core.Context
import jakarta.ws.rs.core.MediaType import jakarta.ws.rs.core.MediaType
import jakarta.ws.rs.sse.OutboundSseEvent
import jakarta.ws.rs.sse.Sse
import jakarta.ws.rs.sse.SseBroadcaster
import jakarta.ws.rs.sse.SseEventSink
import org.eclipse.microprofile.config.inject.ConfigProperty import org.eclipse.microprofile.config.inject.ConfigProperty
import java.util.UUID
@Path("/") @Path("/")
@ApplicationScoped @ApplicationScoped

View File

@@ -7,8 +7,28 @@ import io.quarkus.qute.TemplateInstance
object Templates { object Templates {
@JvmStatic @JvmStatic
external fun overlay(diceid: String, scale: Int?, clearAfter: Long?): TemplateInstance external fun overlay(diceid: String, scale: Int?, clearAfter: Long?): TemplateInstance
@JvmStatic @JvmStatic
external fun results(room: String, name: String?, user: String?): TemplateInstance external fun results(room: String, name: String?, user: String?): TemplateInstance
@JvmStatic @JvmStatic
external fun index(version: String): TemplateInstance external fun index(version: String): TemplateInstance
@JvmStatic
external fun chatoverlayconfig(): TemplateInstance
@JvmStatic
external fun chatoverlay(
channel: String,
scale: Int?,
maxDice: Int?,
modsAllowed: Boolean,
vipAllowed: Boolean,
subsAllowed: Boolean,
cmd: String?,
theme: String?,
themeColor: String?,
clearAfter: Long?,
timeout: Long?
): TemplateInstance
} }

View File

@@ -0,0 +1,199 @@
function addDice() {
let amount = +document.getElementById('dice-amount').innerText
document.getElementById('dice-amount').innerText = `${amount + 1}`
}
function removeDice() {
let amount = +document.getElementById('dice-amount').innerText
if (amount > 1) {
document.getElementById('dice-amount').innerText = `${amount - 1}`
}
}
function url() {
return window.location.protocol + '//' + window.location.hostname + (window.location.port?.length > 0 ? ':' + window.location.port : '');
}
function start(event = undefined) {
if ((!event || event.keyCode === 13) && document.getElementById('name').value.length > 0 && document.getElementById('room').value.length > 0) {
document.getElementById('overlayId').value = url() + '/overlay/' + document.getElementById('room').value + ':' + localStorage.getItem('userId') + '?scale=7&clearAfter=30';
document.getElementById('resultsId').value = url() + '/overlay/' + document.getElementById('room').value + '/results';
document.getElementById('myResultsId').value = document.getElementById('resultsId').value + '?name=' + encodeURIComponent(document.getElementById('name').value) + '&user=' + localStorage.getItem('userId');
document.getElementById('resultFrame').src = document.getElementById('myResultsId').value;
document.getElementById('roll').hidden = false;
document.getElementById('start-container').hidden = true;
document.getElementById('options-container').hidden = false;
document.getElementById('dice-tower').hidden = false;
document.getElementById('name').hidden = true;
document.getElementById('room').hidden = true;
document.getElementById('how-to').hidden = true;
document.getElementById('chatOverlay').hidden = true;
document.getElementById('results').hidden = false;
document.getElementById('all-results').hidden = !document.getElementById('gm').checked;
document.getElementById('all-results-urls').style.display = document.getElementById('gm').checked ? 'fles' : 'none';
document.getElementById('nameH').innerHTML = '<strong style="font-size:x-large;">' + document.getElementById('name').value + '</strong>';
document.getElementById('save-dice-hint-name').innerHTML = '<strong>' + document.getElementById('name').value + '</strong>';
document.getElementById('roomLabel').hidden = true;
document.getElementById('nameLabel').hidden = true;
document.getElementById('nameH').hidden = false;
document.getElementById('room-hint').innerHTML = '<p>Room: <strong style="font-size:medium;">' + document.getElementById('room').value + '</strong></p>';
document.getElementById('overlayLabel').innerHTML = 'Dice-Overlay for <strong>' + document.getElementById('name').value + '</strong>';
document.title = document.getElementById('name').value + ' - Dice-Tower';
localStorage.setItem('last-name', document.getElementById('name').value)
localStorage.setItem('last-room', document.getElementById('room').value)
localStorage.setItem('last-gm', document.getElementById('gm').checked)
if (localStorage.getItem(document.getElementById('name').value + "-theme")) {
document.getElementById('theme').value = localStorage.getItem(document.getElementById('name').value + "-theme")
}
if (localStorage.getItem(document.getElementById('name').value + "-themeColor")) {
document.getElementById('themeColor').setColor(localStorage.getItem(document.getElementById('name').value + "-themeColor"));
}
if (!localStorage.getItem(document.getElementById('name').value + '-started')) {
document.getElementById('urls-overlay').showPopover();
}
localStorage.setItem(document.getElementById('name').value + '-started', "true")
let httpRequest = new XMLHttpRequest();
httpRequest.open('POST', url() + '/dice/' + document.getElementById('room').value + '/register')
httpRequest.setRequestHeader('Content-Type', 'application/json')
httpRequest.send(JSON.stringify({
name: document.getElementById('name').value,
overlay: document.getElementById('overlayId').value,
id: document.getElementById('room').value + ':' + localStorage.getItem('userId')
}))
if (document.getElementById('gm').checked) {
document.getElementById('resultSwitch').checked = true;
document.getElementById('resultFrame').src = document.getElementById('resultsId').value;
const evtSource = new EventSource(url() + '/dice/' + document.getElementById('room').value + '/users');
evtSource.addEventListener('message', function (event) {
let data = JSON.parse(event.data);
if (data.id !== document.getElementById('room').value + ':' + localStorage.getItem('userId')) {
let overlays = document.getElementById('overlay-urls');
let newOverlay = document.getElementById(data.id) ?? document.createElement('div');
newOverlay.replaceChildren(...[]);
newOverlay.id = data.id;
newOverlay.style.display = "flex";
newOverlay.style.flexDirection = "row";
newOverlay.style.justifyContent = "space-between";
newOverlay.style.alignItems = "baseline";
let newLabel = document.createElement('label');
newLabel.for = data.id + 'url';
newLabel.innerHTML = "Dice-Overlay for <strong>" + data.name + "</strong>";
let newInput = document.getElementById('overlayId').cloneNode();
newInput.type = "text";
newInput.readOnly = true;
newInput.id = data.id + 'url';
newInput.style.flexGrow = '1';
newInput.value = data.overlay;
newInput.onclick = () => copyToClipboard(newInput.id)
newOverlay.appendChild(newLabel);
newOverlay.appendChild(newInput);
overlays.appendChild(newOverlay);
}
configurePopover();
});
}
}
}
function rollEasy(dice) {
document.getElementById('command').value = document.getElementById('dice-amount').innerText + dice;
roll();
}
function roll(event) {
if ((!event || event.keyCode === 13) && document.getElementById('command').value?.length > 0) {
let httpRequest = new XMLHttpRequest();
httpRequest.open('POST', url() + '/dice/' + document.getElementById('room').value + ':' + localStorage.getItem(`userId`))
httpRequest.setRequestHeader('Content-Type', 'application/json')
httpRequest.send(JSON.stringify({
name: document.getElementById('name').value,
command: document.getElementById('command').value,
themeColor: document.getElementById('themeColor').value,
theme: document.getElementById('theme').value
}))
}
}
function saveDice() {
localStorage.setItem(document.getElementById('name').value + "-theme", document.getElementById('theme').value)
localStorage.setItem(document.getElementById('name').value + "-themeColor", document.getElementById('themeColor').value)
}
function configurePopover() {
const popover = document.querySelectorAll("[popovertarget][data-trigger='hover']");
popover.forEach((e) => {
const target = document.querySelector("#" + e.getAttribute("popovertarget"));
e.addEventListener("mouseover", () => {
showSnackbar(target.innerHTML);
});
e.addEventListener("mouseout", () => {
hideSnackbar();
});
});
}
function copyToClipboard(id) {
let copyText = document.getElementById(id);
copyText.select();
copyText.setSelectionRange(0, 99999);
navigator.clipboard.writeText(copyText.value).then(() => {
showSnackbar("<i class='fa-regular fa-copy'></i> Link copied to clipboard: <br/>" + copyText.value);
})
}
function showSnackbar(message) {
let snackbar = document.getElementById("snackbar");
let snackbarContainer = document.getElementById("snackbar-container");
snackbar.innerHTML = message;
snackbar.className = "show";
snackbarContainer.className = "show";
}
function hideSnackbar() {
let snackbar = document.getElementById("snackbar");
let snackbarContainer = document.getElementById("snackbar-container");
snackbar.className = snackbar.className.replace("show", "");
snackbarContainer.className = snackbarContainer.className.replace("show", "");
}
document.addEventListener("DOMContentLoaded", async () => {
document.querySelector('meta[property="og:url"]').setAttribute("content", url());
document.querySelector('meta[property="twitter:url"]').setAttribute("content", url());
document.querySelector('meta[property="og:image"]').setAttribute("content", url() + '/rich.png');
document.querySelector('meta[name="twitter:image"]').setAttribute("content", url() + '/rich.png');
document.querySelector('meta[property="twitter:domain"]').setAttribute("content", window.location.hostname);
if (localStorage.getItem('last-name') && localStorage.getItem('last-room')) {
document.getElementById('name').value = localStorage.getItem('last-name');
document.getElementById('room').value = localStorage.getItem('last-room');
document.getElementById('gm').checked = localStorage.getItem('last-gm') === 'true';
}
document.getElementById('resultSwitch').addEventListener('change', function () {
if (!this.checked) {
document.getElementById('resultFrame').src = document.getElementById('myResultsId').value;
} else {
document.getElementById('resultFrame').src = document.getElementById('resultsId').value;
}
})
document.getElementById('chatOverlayLink').href = url() + '/chatoverlay'
document.addEventListener("DOMContentLoaded", async () => {
if (!localStorage.getItem("userId")) {
localStorage.setItem("userId", self.crypto.randomUUID());
}
})
configurePopover();
})

View File

@@ -0,0 +1,15 @@
import DiceBox from "/vendor/dice-box/dice-box.es.js";
document.addEventListener("DOMContentLoaded", async () => {
document.getElementById('preview').onclick = async () => {
document.getElementById('dice-box').replaceChildren(...[])
const diceBox = new DiceBox("#dice-box", {
assetPath: "/vendor/assets/",
theme: document.getElementById('theme').value,
themeColor: document.getElementById('themeColor').value,
scale: 14
});
await diceBox.init()
diceBox.roll(['1d2', '1d4', '1d6', '1d8', '1d10', '1d12', '1d20', '1d100']);
}
})

View File

@@ -0,0 +1,36 @@
html,
body {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow: hidden;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
perspective: 1000px;
}
#dice-box {
position: relative;
box-sizing: border-box;
width: 100%;
height: 100%;
background: transparent;
background-size: cover;
}
#dice-box canvas {
width: 100%;
height: 100%;
}
.tooltip {
position: fixed;
border-radius: 0.5rem;
padding: 15px;
max-width: 80%;
font-size: 400%;
color: #fff; !important;
background-color: #333333dd; !important
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

View File

@@ -0,0 +1,209 @@
.w3-theme-l6 {
color: #000 !important;
background-color: #999999 !important;
border-radius: 10px;
}
.w3-theme-l4 {
color: #fff !important;
background-color: #666666 !important;
border-radius: 10px;
}
.w3-theme-l1 {
color: #fff !important;
background-color: #333333 !important;
border-radius: 10px;
}
button {
padding: 10px;
border: #333333 3px solid;
border-radius: 10px;
background: #333333;
color: #fff
}
button:hover {
background: #444444;
}
button:active {
background: #222222;
}
input {
margin: 10px;
}
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 25px;
}
/* Hide default HTML checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: #333333;
}
input:focus + .slider {
box-shadow: 0 0 1px #333333;
}
input:checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
.tooltip {
position: fixed;
border-radius: 0.5rem;
padding: 15px;
color: #fff !important;
background-color: #333333dd !important
}
#dice-box {
position: relative;
justify-self: center;
box-sizing: border-box;
width: 100%;
height: 100%;
background: transparent;
background-size: cover;
}
#dice-box canvas {
width: 100%;
height: 100%;
}
.checkbox {
position: relative;
padding-left: 50px;
margin-bottom: 12px;
margin-left: 10px;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.checkbox input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkmark {
margin-left: 25px;
position: absolute;
top: 0;
left: 0;
height: 15px;
width: 15px;
background-color: #eee;
}
.checkbox:hover input ~ .checkmark {
background-color: #ccc;
}
/* When the checkbox is checked, add a blue background */
.checkbox input:checked ~ .checkmark {
background-color: #333333;
}
/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
content: "";
position: absolute;
display: none;
}
/* Show the checkmark when checked */
.checkbox input:checked ~ .checkmark:after {
display: block;
}
/* Style the checkmark/indicator */
.checkbox .checkmark:after {
left: 5px;
top: 2px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 3px 3px 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
#snackbar-container {
visibility: hidden;
position: fixed;
width: 100%;
z-index: 1;
bottom: 10%;
display: flex;
justify-content: center;
align-items: center;
}
#snackbar {
visibility: hidden;
text-align: center;
border-radius: 0.5rem;
padding: 15px;
color: #fff !important;
border: #fff 5px solid;
background-color: #333333dd !important
}
#snackbar-container.show {
visibility: visible;
}
#snackbar.show {
visibility: visible;
-webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;
animation: fadein 0.5s, fadeout 0.5s 2.5s;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dice-Tower - Overlay</title>
<link rel="icon" type="image/png" href="/favicon.png">
<link rel="stylesheet" href="/overlay/style.css">
<script src="/vendor/comfy.js/comfy.min.js"></script>
</head>
<body>
<div id="dice-box"></div>
<div popover id="results" class="tooltip">
</div>
<script type="module">
import DiceBox from "/vendor/dice-box/dice-box.es.js";
const diceBox = new DiceBox("#dice-box", {
assetPath: "/vendor/assets/",
theme: '{theme}',
themeColor: '{themeColor}',
scale: {scale}
});
diceBox.init()
ComfyJS.Init('{channel}');
//maxDice
ComfyJS.onCommand = async (user, command, message, flags) => {
if ((
flags.broadcaster || ({modsAllowed} && flags.mod) || ({vipAllowed} && flags.vip) || ({subsAllowed} && flags.subscriber
)) && '{cmd}' === command && !shouldWait() && message.match(/^\d+d(4|6|8|10|12|20|100)$/) && (+message.split('d')[0] <= {maxDice})
) {
toggleWait(true);
diceBox.onRollComplete = (rollResult) => {
rollResult.forEach(result => {
let values = []
result.rolls.forEach(roll => {
values.push(roll.value);
})
document.getElementById('results').innerHTML = '<strong>' + user + '</strong> rolls <strong>' + message + '</strong>:<br/> [' + values.map(value => value === 1 ? '<strong style="text-shadow: 2px 2px 10px red">' + value + '</strong>' : value === result.sides ? '<strong style="text-shadow: 2px 2px 10px green">' + value + '</strong>' : value).join(' + ') + (result.modifier > 0 ? ' <a style="text-decoration: underline">+' + result.modifier + '</a>' : result.modifier < 0 ? ' <a style="text-decoration: underline">' + result.modifier + '</a>' : '') + '] = <strong>' + result.value + '</strong> '
})
document.getElementById('results').showPopover()
setTimeout(() => {
diceBox.clear();
document.getElementById('results').hidePopover()
}, {clearAfter} * 1000)
}
diceBox.roll(message);
setTimeout(() => {
toggleWait(false);
}, {clearAfter} * 1000 + {timeout} * 1000)
} else {
console.log('Not a valid command or not ready yet');
}
};
let wait;
function shouldWait() {
return wait;
}
function toggleWait(value) {
wait = value;
}
</script>
</body>
</html>

View File

@@ -0,0 +1,142 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Dice-Tower - Chatoverlay</title>
<link rel="stylesheet" href="/vendor/w3css/4/w3.css">
<link rel="stylesheet" href="/vendor/font-awesome/css/fontawesome.css">
<link rel="stylesheet" href="/vendor/font-awesome/css/all.css">
<link rel="stylesheet" href="/style.css">
<link rel="icon" type="image/png" href="/favicon.png">
<script src="/vendor/color-picker.js"></script>
</head>
<body class="w3-theme-l1">
<div class="w3-container w3-content"
style="height: 95vh; display: flex; flex-direction: column; justify-content: space-between; padding: 25px">
<h1 style="text-align: center"><i class="fa-solid fa-dice-d20"></i> Dice-Tower - Chatoverlay <i
class="fa-solid fa-dice-d20"></i></h1>
<div class="w3-panel w3-theme-l4 w3-card w3-display-container" style="padding: 25px; text-align: center;">
<p>Allows Chat to roll a given amount of one dice</p>
<p>Example: "!roll 5d20"</p>
</div>
<div class="w3-panel w3-theme-l4 w3-card w3-display-container"
style="padding: 25px; text-align: center; margin-bottom: auto;">
<label for="theme">Theme </label>
<select name="theme" id="theme" style="margin: 25px">
<option value="default">Default</option>
<option value="blueGreenMetal">Blue-Green Metal</option>
<option value="diceOfRolling">Dice of Rolling</option>
<option value="gemstone">Gemstone</option>
<option value="gemstoneMarble">Marble Gemstone</option>
<option value="rock">Rock</option>
<option value="rust">Rust</option>
<option value="smooth">Smooth</option>
<option value="wooden">Wooden</option>
</select>
<color-picker id="themeColor"></color-picker>
<div>
<label for="channel">Channel </label>
<input type="text" id="channel" style="width: 400px; margin-top: 20px" value="arindy"/>
<label for="cmd">Command !</label>
<input type="text" id="cmd" style="width: 100px; margin-top: 20px; margin-left: 0" value="roll"/>
</div>
<div>
<label class="checkbox" id="modsAllowed-container">Allow mods to roll
<input type="checkbox" id="modsAllowed">
<span class="checkmark"></span>
</label>
<label class="checkbox" id="vipAllowed-container">Allow VIPs to roll
<input type="checkbox" id="vipAllowed">
<span class="checkmark"></span>
</label>
<label class="checkbox" id="subsAllowed-container">Allow subs to roll
<input type="checkbox" id="subsAllowed">
<span class="checkmark"></span>
</label>
</div>
<div>
<label for="scale">Dice-Scale </label>
<input type="number" id="scale" style="width: 50px; margin-top: 20px" value="9"/>
<label for="maxDice">Max number of dice </label>
<input type="number" id="maxDice" style="width: 50px; margin-top: 20px" value="20"/>
</div>
<div>
<label for="clearAfter">Clear dice after (in seconds)</label>
<input type="number" id="clearAfter" style="width: 50px; margin-top: 20px" value="10"/>
<label for="timeout">Command-timeout (in seconds)</label>
<input type="number" id="timeout" style="width: 50px; margin-top: 20px" value="60"/>
</div>
<div id="dice-box" style="height: 400px"></div>
<button style="margin: 10px" id="preview">Preview <i class="fa-solid fa-magnifying-glass"></i></button>
<button style="margin: 10px" id="generate">Generate overlay-link <i class="fa-solid fa-link"></i></button>
<div>
<input type="text" readonly id="link" style="width: 80%; margin-top: 20px"
onclick="copyToClipboard(this.id)"/>
</div>
</div>
</div>
<div id="snackbar-container">
<div id="snackbar">.. they see them rolling</div>
</div>
<script>
function copyToClipboard(id) {
let copyText = document.getElementById(id)
if (copyText.value.length > 0) {
copyText.select();
copyText.setSelectionRange(0, 99999);
navigator.clipboard.writeText(copyText.value).then(() => {
showSnackbar("<i class='fa-regular fa-copy'></i> Link copied to clipboard: <br/>" + copyText.value);
})
}
}
function showSnackbar(message) {
let snackbar = document.getElementById("snackbar");
let snackbarContainer = document.getElementById("snackbar-container");
snackbar.innerHTML = message;
snackbar.className = "show";
snackbarContainer.className = "show";
setTimeout(() => {
snackbar.className = "";
snackbarContainer.className = "";
}, 3000)
}
</script>
<script type="module">
import DiceBox from "/vendor/dice-box/dice-box.es.js";
function url() {
return window.location.protocol + '//' + window.location.hostname + (window.location.port?.length > 0 ? ':' + window.location.port : '');
}
document.addEventListener("DOMContentLoaded", async () => {
document.getElementById('preview').onclick = async () => {
document.getElementById('dice-box').replaceChildren(...[])
const diceBox = new DiceBox("#dice-box", {
assetPath: "/vendor/assets/",
theme: document.getElementById('theme').value,
themeColor: document.getElementById('themeColor').value,
scale: +document.getElementById('scale').value
});
await diceBox.init()
diceBox.roll(['1d2', '1d4', '1d6', '1d8', '1d10', '1d12', '1d20', '1d100']);
}
document.getElementById('generate').onclick = async () => {
document.getElementById('link').value = url() +
"/chatoverlay/" + document.getElementById('channel').value +
"?cmd=" + document.getElementById('cmd').value +
"&theme=" + document.getElementById('theme').value +
"&themeColor=" + encodeURIComponent(document.getElementById('themeColor').value) +
"&scale=" + document.getElementById('scale').value +
"&maxDice=" + document.getElementById('maxDice').value +
"&clearAfter=" + document.getElementById('clearAfter').value +
"&timeout=" + document.getElementById('timeout').value +
"&modsAllowed=" + document.getElementById('modsAllowed').checked +
"&vipAllowed=" + document.getElementById('vipAllowed').checked +
"&subsAllowed=" + document.getElementById('subsAllowed').checked
}
})
</script>
</body>
</html>

View File

@@ -3,396 +3,174 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Dice-Tower</title> <title>Dice-Tower</title>
<meta property="og:title" content="Dice-Tower">
<meta name="twitter:title" content="Dice-Tower">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">
<meta property="og:url" content="https://dice-tower.com">
<meta property="twitter:url" content="https://dice-tower.com">
<meta property="twitter:domain" content="dice-tower.com">
<meta property="og:image" content="https://dice-tower.com/rich.png">
<meta name="twitter:image" content="https://dice-tower.com/rich.png">
<meta name="description" content="
Easy to use online dice rolling with customizable overlays.
">
<link rel="stylesheet" href="/vendor/w3css/4/w3.css"> <link rel="stylesheet" href="/vendor/w3css/4/w3.css">
<link rel="stylesheet" href="/vendor/font-awesome/css/fontawesome.css"> <link rel="stylesheet" href="/vendor/font-awesome/css/fontawesome.css">
<link rel="stylesheet" href="/vendor/font-awesome/css/all.css"> <link rel="stylesheet" href="/vendor/font-awesome/css/all.css">
<link rel="stylesheet" href="/style.css">
<link rel="icon" type="image/png" href="/favicon.png"> <link rel="icon" type="image/png" href="/favicon.png">
<script src="/vendor/color-picker.js"></script> <script src="/vendor/color-picker.js"></script>
<style> <script type="module" src="/dice-preview.js"></script>
.w3-theme-l6 { <script type="text/javascript" src="/app.js"></script>
color: #000 !important;
background-color: #999999 !important;
border-radius: 10px;
}
.w3-theme-l4 {
color: #fff !important;
background-color: #666666 !important;
border-radius: 10px;
}
.w3-theme-l1 {
color: #fff !important;
background-color: #333333 !important;
border-radius: 10px;
}
.collapsible {
background-color: #333333;
color: black;
margin-top: 10px;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
}
.content {
padding: 0 18px;
display: none;
overflow: hidden;
background-color: #999999;
color: #000;
border-radius: 10px;
}
button {
padding: 10px;
border: #333333 3px solid;
border-radius: 10px;
background: #333333;
color: #fff
}
button:hover {
background: #444444;
}
button:active {
background: #222222;
}
input {
margin: 10px;
}
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 25px;
}
/* Hide default HTML checkbox */
.switch input {
opacity: 0;
width: 0;
height: 0;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}
input:checked + .slider {
background-color: #333333;
}
input:focus + .slider {
box-shadow: 0 0 1px #333333;
}
input:checked + .slider:before {
-webkit-transform: translateX(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
.overlayButton {
background: transparent;
border: none;
width: 5px;
height: 5px;
font-size: large;
color: #000
}
.overlayButton:hover {
background: transparent;
}
.tooltip {
position: fixed;
border-radius: 0.5rem;
padding: 15px;
color: #fff !important;
background-color: #333333dd !important
}
#dice-box {
position: relative;
box-sizing: border-box;
width: 100%;
height: 100%;
background: transparent;
background-size: cover;
}
#dice-box canvas {
width: 100%;
height: 100%;
margin: 20px
}
.checkbox {
position: relative;
padding-left: 50px;
margin-bottom: 12px;
margin-left: 10px;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.checkbox input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkmark {
margin-left: 25px;
position: absolute;
top: 0;
left: 0;
height: 15px;
width: 15px;
background-color: #eee;
}
.checkbox:hover input ~ .checkmark {
background-color: #ccc;
}
/* When the checkbox is checked, add a blue background */
.checkbox input:checked ~ .checkmark {
background-color: #333333;
}
/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
content: "";
position: absolute;
display: none;
}
/* Show the checkmark when checked */
.checkbox input:checked ~ .checkmark:after {
display: block;
}
/* Style the checkmark/indicator */
.checkbox .checkmark:after {
left: 5px;
top: 2px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 3px 3px 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
#snackbar-container {
visibility: hidden;
position: fixed;
width: 100%;
z-index: 1;
bottom: 200px;
display: flex;
justify-content: center;
align-items: center;
}
#snackbar {
visibility: hidden;
text-align: center;
border-radius: 0.5rem;
padding: 15px;
color: #fff !important;
border: #fff 5px solid;
background-color: #333333dd !important
}
#snackbar-container.show {
visibility: visible;
}
#snackbar.show {
visibility: visible;
-webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;
animation: fadein 0.5s, fadeout 0.5s 2.5s;
}
</style>
</head> </head>
<body class="w3-theme-l1"> <body class="w3-theme-l1">
<div class="w3-container w3-content" <div class="w3-container w3-content"
style="height: 95vh; display: flex; flex-direction: column; justify-content: space-between; padding: 25px"> style="height: 95vh; display: flex; flex-direction: column; justify-content: space-between; padding: 25px">
<h1 style="text-align: center"><i class="fa-solid fa-dice-d20"></i> Dice-Tower <i class="fa-solid fa-dice-d20"></i></h1> <h1 style="text-align: center"><i class="fa-solid fa-dice-d20"></i> Dice-Tower <i class="fa-solid fa-dice-d20"></i>
</h1>
<div class="w3-panel w3-theme-l4 w3-card w3-display-container" <div class="w3-panel w3-theme-l4 w3-card w3-display-container"
style="padding: 25px; text-align: center; margin-bottom: auto;"> style="padding: 25px; text-align: center; margin-bottom: auto;">
<label for="name" id="nameLabel">Name </label><input type="text" id="name" style="width: 50%; margin-top: 20px" <h2 id="nameH" popovertarget="room-hint" data-trigger="hover" style="margin: 0" hidden>Name</h2>
required onkeyup="start(event)"/><br/> <div>
<label for="room" id="roomLabel">Room </label><input type="text" id="room" style="width: 50%" required <label for="name" id="nameLabel">Name </label>
onkeyup="start(event)"/><br/> <input type="text" id="name" style="width: 50%; margin-top: 20px" required onkeyup="start(event)"/>
<div id="start-container" > </div>
<button id="start" onclick="start()" style="align-self: center; margin-top: 20px">Join <i <div>
class="fa-solid fa-right-to-bracket"></i></button> <label for="room" id="roomLabel">Room </label>
<label class="checkbox" id="gm-container">Join as GM <input type="text" id="room" style="width: 50%" required onkeyup="start(event)"/>
<input type="checkbox" id="gm" > </div>
<span class="checkmark"></span> <div id="start-container">
</label> <div>
<br/> <button id="start" onclick="start()" style="align-self: center; margin-top: 20px">Join <i
class="fa-solid fa-right-to-bracket"></i></button>
<label class="checkbox" id="gm-container">Join as GM
<input type="checkbox" id="gm">
<span class="checkmark"></span>
</label>
</div>
</div>
<div id="options-container" hidden style="position: absolute; right: 25px; top: 5px">
<button id="customize" popovertarget="customize-overlay" style="align-self: center; margin-top: 20px"><i class="fa-solid fa-palette"></i>
</button>
<button id="urls" popovertarget="urls-overlay" style="align-self: center; margin-top: 20px"><i class="fa-solid fa-link"></i></button>
</div> </div>
</div> </div>
<div id="dice-tower" hidden class="w3-panel w3-theme-l4 w3-card w3-display-container" <div id="dice-tower" hidden class="w3-panel w3-theme-l4 w3-card w3-display-container"
style="padding: 25px; margin-bottom: auto"> style="padding: 25px; margin-bottom: auto">
<button type="button" class="collapsible" style="color: white; font-weight: bold">Overlay URLs <a><i
class="fa-solid fa-chevron-down"></i></a></button>
<div class="content"> <div style="display: flex; flex-direction: row; justify-content: space-between; align-items: center; margin: 10px; padding-right: 25px; padding-left: 25px">
<div id="overlay-urls"> <label style="font-size: large; font-weight: bold; margin: 10px">Roll </label>
<div style="display: flex; flex-direction: row; justify-content: space-between; align-items: baseline;"> <button style="border: transparent; border-radius: 100%; font-size: large; font-weight: bold; height: 50px; width: 50px"
<label for="overlayId" id="overlayLabel">Dice-Overlay </label>
<input type="text" readonly id="overlayId" style="flex-grow: 1" onclick="copyToClipboard(this.id)"/>
<button id="overlay-hint-button" popovertarget="overlay-hint" data-trigger="hover"
class="overlayButton"><i class="fa-solid fa-circle-info"></i>
</button>
</div>
</div>
<div style="display: flex; flex-direction: row; justify-content: space-between; align-items: baseline;"
hidden id="all-results-urls">
<label for="resultsId">All-Results-Overlay </label>
<input type="text" readonly id="resultsId" style="flex-grow: 1" onclick="copyToClipboard(this.id)"/>
<button popovertarget="all-results-hint" data-trigger="hover" class="overlayButton"><i
class="fa-solid fa-circle-info"></i></button>
</div>
<div style="display: flex; flex-direction: row; justify-content: space-between; align-items: baseline;">
<label for="myResultsId">My-Results-Overlay </label>
<input type="text" readonly id="myResultsId" style="flex-grow: 1" onclick="copyToClipboard(this.id)"/>
<button popovertarget="my-results-hint" data-trigger="hover" class="overlayButton"><i
class="fa-solid fa-circle-info"></i></button>
</div>
</div>
<button type="button" class="collapsible" style="color: white; font-weight: bold">Customize Dice <a><i
class="fa-solid fa-chevron-down"></i></a>
</button>
<div class="content">
<label for="theme">Theme </label>
<select name="theme" id="theme" style="margin: 25px">
<option value="default">Default</option>
<option value="blueGreenMetal">Blue-Green Metal</option>
<option value="diceOfRolling">Dice of Rolling</option>
<option value="gemstone">Gemstone</option>
<option value="gemstoneMarble">Marble Gemstone</option>
<option value="rock">Rock</option>
<option value="rust">Rust</option>
<option value="smooth">Smooth</option>
<option value="wooden">Wooden</option>
</select>
<color-picker id="themeColor"></color-picker>
<div id="dice-box"></div>
<button style="margin: 10px" id="preview">Preview <i class="fa-solid fa-magnifying-glass"></i></button>
<button style="margin: 10px" onclick="saveDice()">Save <i class="fa-solid fa-floppy-disk"></i></button>
<button popovertarget="save-dice-hint" data-trigger="hover" class="overlayButton"><i
class="fa-solid fa-circle-info"></i></button>
</div>
<div style="display: flex; flex-direction: row; justify-content: space-between; align-items: center; margin: 20px 25px;">
<label style="font-size: x-large; font-weight: bold;">Roll </label>
<button style="border: transparent; border-radius: 100%; font-size: x-large; font-weight: bold; height: 50px; width: 50px"
onclick="removeDice()">- onclick="removeDice()">-
</button> </button>
<label style="font-size: x-large; font-weight: bold;" id="dice-amount">1</label> <label style="font-size: large; font-weight: bold" id="dice-amount">1</label>
<button style="border: transparent; border-radius: 100%; font-size: x-large; font-weight: bold; height: 50px; width: 50px" <button style="border: transparent; border-radius: 100%; font-size: large; font-weight: bold; height: 50px; width: 50px"
onclick="addDice()">+ onclick="addDice()">+
</button> </button>
<button style="font-size: x-large; font-weight: bold;" onclick="rollEasy('d4')">D4</button> <button style="font-size: large; font-weight: bold;" onclick="rollEasy('d4')">D4</button>
<button style="font-size: x-large; font-weight: bold;" onclick="rollEasy('d6')">D6</button> <button style="font-size: large; font-weight: bold;" onclick="rollEasy('d6')">D6</button>
<button style="font-size: x-large; font-weight: bold;" onclick="rollEasy('d8')">D8</button> <button style="font-size: large; font-weight: bold;" onclick="rollEasy('d8')">D8</button>
<button style="font-size: x-large; font-weight: bold;" onclick="rollEasy('d10')">D10</button> <button style="font-size: large; font-weight: bold;" onclick="rollEasy('d10')">D10</button>
<button style="font-size: x-large; font-weight: bold;" onclick="rollEasy('d12')">D12</button> <button style="font-size: large; font-weight: bold;" onclick="rollEasy('d12')">D12</button>
<button style="font-size: x-large; font-weight: bold;" onclick="rollEasy('d20')">D20</button> <button style="font-size: large; font-weight: bold;" onclick="rollEasy('d20')">D20</button>
<button style="font-size: x-large; font-weight: bold;" onclick="rollEasy('d100')">D100</button> <button style="font-size: large; font-weight: bold;" onclick="rollEasy('d100')">D100</button>
</div> </div>
<div style="display: flex; flex-direction: row; justify-content: space-between; align-items: baseline; margin: 20px 25px;"> <div style="display: flex; flex-direction: row; justify-content: space-between; align-items: baseline; margin: 20px 25px 0;">
<label for="command">Command </label> <label for="command">Command </label>
<input type="text" id="command" style="flex-grow: 1" onkeyup="roll(event)"/> <input popovertarget="command-hint" data-trigger="hover" type="text" id="command" style="flex-grow: 1" onkeyup="roll(event)"/>
<button hidden id="roll" onclick="roll()">Roll <i class="fa-solid fa-dice"></i></button> <button hidden id="roll" onclick="roll()">Roll <i class="fa-solid fa-dice"></i></button>
<button popovertarget="command-hint" data-trigger="hover" class="overlayButton"><i <div style="margin: 20px 25px" id="all-results">
class="fa-solid fa-circle-info"></i></button> <label for="resultSwitch">Show all results </label>
<label class="switch">
<input type="checkbox" id="resultSwitch">
<span class="slider"></span>
</label>
</div>
</div> </div>
<div style="margin: 20px 25px" id="all-results">
<label for="resultSwitch">Show all results </label>
<label class="switch">
<input type="checkbox" id="resultSwitch">
<span class="slider"></span>
</label>
</div>
</div> </div>
<div id="results" hidden class="w3-panel w3-theme-l6 w3-card w3-display-container" <div id="results" hidden class="w3-panel w3-theme-l6 w3-card w3-display-container"
style="padding: 25px; flex-grow: 1; margin-bottom: auto"> style="padding: 25px; flex-grow: 1; margin-bottom: auto">
<iframe id="resultFrame" title="results" style="width: 100%; height: 85%; overflow: hidden; border: 0" <iframe id="resultFrame" title="results" style="width: 100%; height: 100%; overflow: hidden; border: 0"
onload="this.height=this.contentWindow.document.body.scrollHeight;"></iframe> onload="this.height=this.contentWindow.document.body.scrollHeight;"></iframe>
</div> </div>
<div popover id="overlay-hint" class="tooltip"> <div popover id="urls-overlay" class="tooltip" style="width: 600px">
<p style="color: red; font-weight: bold">Only the last loaded instance of that overlay rolls the dice!</p> <div id="overlay-urls">
<p>Query Params you can Change:</p> <div style="display: flex; flex-direction: row; justify-content: space-between; align-items: baseline;">
<ul> <label for="overlayId" id="overlayLabel">Dice-Overlay </label>
<li><strong>scale</strong> changes the size of the dice (any value over 1)</li> <input popovertarget="overlay-hint" data-trigger="hover" type="text" readonly id="overlayId" style="flex-grow: 1" onclick="copyToClipboard(this.id)"/>
<li><strong>clearAfter</strong> time until dice are cleared (in seconds; remove param or set -1 to keep the </div>
dice) </div>
</li> <div style="display: flex; flex-direction: row; justify-content: space-between; align-items: baseline;"
</ul> hidden id="all-results-urls">
<label for="resultsId">All-Results-Overlay </label>
<input popovertarget="all-results-hint" data-trigger="hover" type="text" readonly id="resultsId" style="flex-grow: 1" onclick="copyToClipboard(this.id)"/>
</div>
<div style="display: flex; flex-direction: row; justify-content: space-between; align-items: baseline;">
<label for="myResultsId">My-Results-Overlay </label>
<input popovertarget="my-results-hint" data-trigger="hover" type="text" readonly id="myResultsId" style="flex-grow: 1" onclick="copyToClipboard(this.id)"/>
</div>
</div> </div>
<div popover id="all-results-hint" class="tooltip"> <div popover id="customize-overlay" class="tooltip" style="width: 680px;">
<p>Shows all Results in this room</p> <label for="theme">Theme </label>
<select name="theme" id="theme" style="margin: 25px">
<option value="default">Default</option>
<option value="blueGreenMetal">Blue-Green Metal</option>
<option value="diceOfRolling">Dice of Rolling</option>
<option value="gemstone">Gemstone</option>
<option value="gemstoneMarble">Marble Gemstone</option>
<option value="rock">Rock</option>
<option value="rust">Rust</option>
<option value="smooth">Smooth</option>
<option value="wooden">Wooden</option>
</select>
<color-picker id="themeColor"></color-picker>
<div id="dice-box"></div>
<button style="margin: 10px" id="preview">Preview <i class="fa-solid fa-magnifying-glass"></i></button>
<button popovertarget="save-dice-hint" data-trigger="hover" style="margin: 10px" onclick="saveDice()">Save <i class="fa-solid fa-floppy-disk"></i></button>
</div> </div>
<div popover id="my-results-hint" class="tooltip"> <div hidden id="overlay-hint">
<p>Shows only my Results in this room</p> <div style="text-align: left">
</div> <p style="color: red; font-weight: bold">Only the last loaded instance of that overlay rolls the dice!</p>
<div popover id="save-dice-hint" class="tooltip"> <p>Query Params you can Change:</p>
This saves your current theme and theme color for current Name <ul>
</div> <li><strong>scale</strong> changes the size of the dice (any value over 1)</li>
<div popover id="command-hint" class="tooltip"> <li><strong>clearAfter</strong> time until dice are cleared (in seconds; remove param or set -1 to keep the
Example Commands: "1d6", "2d8 1d100", "1d4 and 1d6", "2d20 & 1d2, "5d6+10" dice)
</li>
</ul>
</div>
</div> </div>
<div style="margin-top: 20px; flex-grow: 1" id="how-to"> <div hidden id="all-results-hint">
<div class="w3-panel w3-theme-l4 w3-card w3-display-container" style="padding: 25px; margin-bottom: auto"> <p>Shows all Results in this room</p>
</div>
<div hidden id="my-results-hint">
<p>Shows only my Results in this room</p>
</div>
<div hidden id="save-dice-hint">
<p>This saves your current theme and theme color for <a id="save-dice-hint-name"></a></p>
</div>
<div hidden id="command-hint">
<p>Example Commands: "1d6", "2d8 1d100", "1d4 and 1d6", "2d20 & 1d2, "5d6+10"</p>
</div>
<div hidden id="room-hint">
<p>How is your character called?</p>
</div>
<div style="margin-top: 20px" id="how-to">
<div class="w3-panel w3-theme-l4 w3-card w3-display-container" style="padding: 25px;">
<h2 style="text-align: center">How-To</h2> <h2 style="text-align: center">How-To</h2>
<ul> <ul>
<li> <li>
@@ -406,8 +184,7 @@
<li style="color: red; font-weight: bold">Only the last loaded instance of that overlay rolls the <li style="color: red; font-weight: bold">Only the last loaded instance of that overlay rolls the
dice! dice!
</li> </li>
<li>You can configure your Overlay with query parameters (more information at the info element next <li>You can configure your Overlay with query parameters (for more information hover over the link)
to the link)
</li> </li>
</ul> </ul>
<li>Configure your dice</li> <li>Configure your dice</li>
@@ -416,234 +193,18 @@
</ul> </ul>
</div> </div>
</div> </div>
<div style="margin-top: 20px; flex-grow: 1" id="chatOverlay">
<div class="w3-panel w3-theme-l4 w3-card w3-display-container" style="padding: 25px; margin-bottom: auto">
If you are looking for a way to let your twitch chat roll click <a href target="_blank" id="chatOverlayLink">here</a>
</div>
</div>
</div> </div>
<div id="snackbar-container"> <div id="snackbar-container">
<div id="snackbar">.. they see them rolling</div> <div id="snackbar">.. they see them rolling</div>
</div> </div>
<script>
function addDice() {
let amount = +document.getElementById('dice-amount').innerText
document.getElementById('dice-amount').innerText = amount + 1
}
function removeDice() {
let amount = +document.getElementById('dice-amount').innerText
if (amount > 1) {
document.getElementById('dice-amount').innerText = amount - 1
}
}
function url() {
return window.location.protocol + '//' + window.location.hostname + (window.location.port?.length > 0 ? ':' + window.location.port : '');
}
function start(event) {
if ((!event || event.keyCode === 13) && document.getElementById('name').value.length > 0 && document.getElementById('room').value.length > 0) {
document.getElementById('overlayId').value = url() + '/overlay/' + document.getElementById('room').value + ':' + localStorage.getItem('userId') + '?scale=7&clearAfter=30';
document.getElementById('resultsId').value = url() + '/overlay/' + document.getElementById('room').value + '/results';
document.getElementById('myResultsId').value = document.getElementById('resultsId').value + '?name=' + encodeURIComponent(document.getElementById('name').value) + '&user=' + localStorage.getItem('userId');
document.getElementById('resultFrame').src = document.getElementById('myResultsId').value;
document.getElementById('roll').hidden = false;
if (document.getElementById('rejoin')) {
document.getElementById('rejoin').hidden = true;
}
document.getElementById('start').hidden = true;
document.getElementById('dice-tower').hidden = false;
document.getElementById('name').hidden = true;
document.getElementById('room').hidden = true;
document.getElementById('gm-container').hidden = true;
document.getElementById('how-to').hidden = true;
document.getElementById('results').hidden = false;
document.getElementById('all-results').hidden = !document.getElementById('gm').checked;
document.getElementById('all-results-urls').style.display = document.getElementById('gm').checked ? 'fles' : 'none';
document.getElementById('nameLabel').innerHTML = '<strong style="font-size:x-large;">' + document.getElementById('name').value + '</strong>';
document.getElementById('roomLabel').innerHTML = '<strong style="font-size:medium;">' + document.getElementById('room').value + '</strong>';
document.getElementById('overlayLabel').innerHTML = 'Dice-Overlay for <strong>' + document.getElementById('name').value + '</strong>';
document.title = document.getElementById('name').value + ' - Dice-Tower';
localStorage.setItem('last-name', document.getElementById('name').value)
localStorage.setItem('last-room', document.getElementById('room').value)
localStorage.setItem('last-gm', document.getElementById('gm').checked)
if (localStorage.getItem(document.getElementById('name').value + "-theme")) {
document.getElementById('theme').value = localStorage.getItem(document.getElementById('name').value + "-theme")
}
if (localStorage.getItem(document.getElementById('name').value + "-themeColor")) {
document.getElementById('themeColor').setColor(localStorage.getItem(document.getElementById('name').value + "-themeColor"));
}
let httpRequest = new XMLHttpRequest();
httpRequest.open('POST', url() + '/dice/' + document.getElementById('room').value + '/register')
httpRequest.setRequestHeader('Content-Type', 'application/json')
httpRequest.send(JSON.stringify({
name: document.getElementById('name').value,
overlay: document.getElementById('overlayId').value,
id: document.getElementById('room').value + ':' + localStorage.getItem('userId')
}))
if (document.getElementById('gm').checked) {
document.getElementById('resultSwitch').checked = true;
document.getElementById('resultFrame').src = document.getElementById('resultsId').value;
const evtSource = new EventSource(url() + '/dice/' + document.getElementById('room').value + '/users');
evtSource.addEventListener('message', function (event) {
let data = JSON.parse(event.data);
if (data.id !== document.getElementById('room').value + ':' + localStorage.getItem('userId')) {
let overlays = document.getElementById('overlay-urls');
let newOverlay = document.getElementById(data.id) ?? document.createElement('div');
newOverlay.replaceChildren(...[]);
newOverlay.id = data.id;
newOverlay.style.display = "flex";
newOverlay.style.flexDirection = "row";
newOverlay.style.justifyContent = "space-between";
newOverlay.style.alignItems = "baseline";
let newLabel = document.createElement('label');
newLabel.for = data.id + 'url';
newLabel.innerHTML = "Dice-Overlay for <strong>" + data.name + "</strong>";
let newInput = document.createElement('input');
newInput.type = "text";
newInput.readOnly = true;
newInput.id = data.id + 'url';
newInput.style.flexGrow = '1';
newInput.value = data.overlay;
newInput.onclick = () => copyToClipboard(newInput.id)
let hint = document.getElementById('overlay-hint-button').cloneNode(true);
newOverlay.appendChild(newLabel);
newOverlay.appendChild(newInput);
newOverlay.appendChild(hint);
overlays.appendChild(newOverlay);
}
showPopover();
});
}
}
}
function rollEasy(dice) {
document.getElementById('command').value = document.getElementById('dice-amount').innerText + dice;
roll();
}
function roll(event) {
if ((!event || event.keyCode === 13) && document.getElementById('command').value?.length > 0) {
let httpRequest = new XMLHttpRequest();
httpRequest.open('POST', url() + '/dice/' + document.getElementById('room').value + ':' + localStorage.getItem(`userId`))
httpRequest.setRequestHeader('Content-Type', 'application/json')
httpRequest.send(JSON.stringify({
name: document.getElementById('name').value,
command: document.getElementById('command').value,
themeColor: document.getElementById('themeColor').value,
theme: document.getElementById('theme').value
}))
}
}
function saveDice() {
localStorage.setItem(document.getElementById('name').value + "-theme", document.getElementById('theme').value)
localStorage.setItem(document.getElementById('name').value + "-themeColor", document.getElementById('themeColor').value)
}
let coll = document.getElementsByClassName("collapsible");
for (let i = 0; i < coll.length; i++) {
coll[i].addEventListener("click", function () {
const content = this.nextElementSibling;
if (content.style.display === "block") {
content.style.display = "none";
this.children[0].innerHTML = "<i class='fa-solid fa-chevron-down'></i>"
} else {
content.style.display = "block";
this.children[0].innerHTML = "<i class='fa-solid fa-chevron-up'></i>"
}
});
}
if (localStorage.getItem('last-name') && localStorage.getItem('last-room')) {
let rejoin = document.createElement('button');
rejoin.id = 'rejoin';
rejoin.style.alignSelf = 'center';
rejoin.style.marginTop = '20px';
rejoin.style.marginRight = '20px';
rejoin.innerHTML = 'Rejoin as <strong>' + (localStorage.getItem('last-gm') === 'true' ? 'GM ' : '') + localStorage.getItem('last-name') + '</strong> in <strong>' + localStorage.getItem('last-room') + '</strong> <i class="fa-solid fa-right-to-bracket"></i>'
rejoin.onclick = () => {
document.getElementById('name').value = localStorage.getItem('last-name');
document.getElementById('room').value = localStorage.getItem('last-room');
document.getElementById('gm').checked = localStorage.getItem('last-gm') === 'true';
start();
}
document.getElementById('start-container').appendChild(rejoin)
}
function showPopover() {
const popover = document.querySelectorAll("[popovertarget][data-trigger='hover']");
popover.forEach((e) => {
const target = document.querySelector("#" + e.getAttribute("popovertarget"));
e.addEventListener("mouseover", () => {
target.showPopover();
});
e.addEventListener("mouseout", () => {
target.hidePopover();
});
});
}
showPopover();
document.getElementById('resultSwitch').addEventListener('change', function () {
if (!this.checked) {
document.getElementById('resultFrame').src = document.getElementById('myResultsId').value;
} else {
document.getElementById('resultFrame').src = document.getElementById('resultsId').value;
}
})
document.addEventListener("DOMContentLoaded", async () => {
if (!localStorage.getItem("userId")) {
localStorage.setItem("userId", self.crypto.randomUUID());
}
})
function copyToClipboard(id) {
let copyText = document.getElementById(id);
copyText.select();
copyText.setSelectionRange(0, 99999);
navigator.clipboard.writeText(copyText.value);
showSnackbar("<i class='fa-regular fa-copy'></i> Link copied to clipboard: <br/>" + copyText.value);
}
function showSnackbar(message) {
let snackbar = document.getElementById("snackbar");
let snackbarContainer = document.getElementById("snackbar-container");
snackbar.innerHTML = message;
snackbar.className = "show";
snackbarContainer.className = "show";
setTimeout(function () {
snackbar.className = snackbar.className.replace("show", "");
snackbarContainer.className = snackbarContainer.className.replace("show", "");
}, 5000);
}
</script>
<script type="module">
import DiceBox from "/vendor/dice-box/dice-box.es.js";
document.addEventListener("DOMContentLoaded", async () => {
document.getElementById('preview').onclick = async () => {
document.getElementById('dice-box').replaceChildren(...[])
const diceBox = new DiceBox("#dice-box", {
assetPath: "/vendor/assets/",
theme: document.getElementById('theme').value,
themeColor: document.getElementById('themeColor').value,
scale: 14
});
await diceBox.init()
diceBox.roll(['1d2', '1d4', '1d6', '1d8', '1d10', '1d12', '1d20', '1d100']);
}
})
</script>
</body> </body>
<footer class="w3-theme-l1 w3-center w3-padding-16"> <footer class="w3-theme-l1 w3-center">
Version {version} :: Version {version}
<a href="https://git.arindy.de/arindy/dice-tower" target="_blank" class="w3-hover-text-black">Dice-Tower on my <a href="https://git.arindy.de/arindy/dice-tower" target="_blank" class="w3-hover-text-black"><i class="fa-solid fa-code"></i> Source on GitTea</a>
GitTea</a>
</footer> </footer>
</html> </html>

View File

@@ -4,35 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Dice-Tower - Overlay</title> <title>Dice-Tower - Overlay</title>
<link rel="icon" type="image/png" href="/favicon.png"> <link rel="icon" type="image/png" href="/favicon.png">
<style> <link rel="stylesheet" href="/overlay/style.css">
html,
body {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow: hidden;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
perspective: 1000px;
}
#dice-box {
position: relative;
box-sizing: border-box;
width: 100%;
height: 100%;
background: transparent;
background-size: cover;
}
#dice-box canvas {
width: 100%;
height: 100%;
}
</style>
</head> </head>
<body> <body>
<div id="dice-box"></div> <div id="dice-box"></div>
@@ -40,7 +12,9 @@
function url() { function url() {
return window.location.protocol + '//' + window.location.hostname + (window.location.port?.length > 0 ? ':' + window.location.port : ''); return window.location.protocol + '//' + window.location.hostname + (window.location.port?.length > 0 ? ':' + window.location.port : '');
} }
import DiceBox from "/vendor/dice-box/dice-box.es.js"; import DiceBox from "/vendor/dice-box/dice-box.es.js";
const evtSource = new EventSource(url() + "/dice/{diceid??}/stream"); const evtSource = new EventSource(url() + "/dice/{diceid??}/stream");
const diceBox = new DiceBox("#dice-box", { const diceBox = new DiceBox("#dice-box", {
assetPath: "/vendor/assets/", assetPath: "/vendor/assets/",
@@ -59,7 +33,7 @@
scale: {scale} scale: {scale}
}); });
document.addEventListener("DOMContentLoaded", async() => { document.addEventListener("DOMContentLoaded", async () => {
await diceBox.init() await diceBox.init()
let timeout = 0; let timeout = 0;
@@ -68,20 +42,27 @@
let data = JSON.parse(event.data); let data = JSON.parse(event.data);
diceBox.onRollComplete = (rollResult) => { diceBox.onRollComplete = (rollResult) => {
let httpRequest = new XMLHttpRequest(); let httpRequest = new XMLHttpRequest();
httpRequest.open('POST',url() + '/dice/' + data.room + '/results') httpRequest.open('POST', url() + '/dice/' + data.room + '/results')
httpRequest.setRequestHeader('Content-Type', 'application/json') httpRequest.setRequestHeader('Content-Type', 'application/json')
httpRequest.send(JSON.stringify({ httpRequest.send(JSON.stringify({
name: data.name, name: data.name,
user: data.user, user: data.user,
themeColor: data.themeColor, themeColor: data.themeColor,
results: rollResult, results: rollResult,
} )) }))
if ({clearAfter} > 0) { if ({clearAfter} >
timeout = setTimeout(() => diceBox.clear(), {clearAfter} * 1000) 0
)
{
timeout = setTimeout(() => diceBox.clear(), {clearAfter} * 1000
)
} }
} }
diceBox.roll(data.roll, { theme: data.theme?.length > 0 ? data.theme : 'default', themeColor: data.themeColor.length > 0 ? data.themeColor : '#4545FF' }); diceBox.roll(data.roll, {
theme: data.theme?.length > 0 ? data.theme : 'default',
themeColor: data.themeColor.length > 0 ? data.themeColor : '#4545FF'
});
}) })
}) })
</script> </script>

View File

@@ -8,12 +8,12 @@
<link rel="icon" type="image/png" href="/favicon.png"> <link rel="icon" type="image/png" href="/favicon.png">
</head> </head>
<body> <body>
<div id="results" style="font-size: x-large"> <div id="results" style="font-size: x-large"></div>
</div>
<script type="module"> <script type="module">
function url() { function url() {
return window.location.protocol + '//' + window.location.hostname + (window.location.port?.length > 0 ? ':' + window.location.port : ''); return window.location.protocol + '//' + window.location.hostname + (window.location.port?.length > 0 ? ':' + window.location.port : '');
} }
const evtSource = new EventSource(url() + '/dice/{room}/results'); const evtSource = new EventSource(url() + '/dice/{room}/results');
evtSource.addEventListener('message', function (event) { evtSource.addEventListener('message', function (event) {
let data = JSON.parse(event.data); let data = JSON.parse(event.data);