24 Commits

Author SHA1 Message Date
gitea
1dc4940d8c [no ci] prepare new Version 2025-02-11 23:06:46 +00:00
gitea
1cefb117fe [no ci] release 1.0.2 2025-02-11 23:06:40 +00:00
ba48da80ed Merge pull request 'upload-to-packages' (#24) from upload-to-packages into main
All checks were successful
CI / deploy (push) Successful in 4m55s
Reviewed-on: #24
2025-02-12 00:01:51 +01:00
4fd83f48cd adds Version to index
All checks were successful
CI / deploy (push) Successful in 4m22s
CI / deploy (pull_request) Successful in 4m27s
2025-02-11 23:52:08 +01:00
24acc0b942 push to packages
All checks were successful
CI / deploy (push) Successful in 4m25s
CI / deploy (pull_request) Successful in 4m26s
2025-02-11 23:34:46 +01:00
cab7ec34d2 push to packages
Some checks failed
CI / deploy (push) Failing after 6s
2025-02-11 23:01:24 +01:00
gitea
c8cd05f035 [no ci] prepare new Version 2025-02-11 21:56:44 +00:00
gitea
3e35dfb9ba [no ci] release 1.0.1 2025-02-11 21:56:38 +00:00
f465e1c101 fix SNAPSHOTS
All checks were successful
CI / deploy (push) Successful in 5m5s
2025-02-11 22:51:33 +01:00
gitea
2f4cc2de8b [no ci] release 1.0.1 2025-02-11 21:40:20 +00:00
b977996689 uses mvn:versions instead of jgitver
All checks were successful
CI / deploy (push) Successful in 4m46s
2025-02-11 22:35:30 +01:00
1544776ecc fix tag
Some checks failed
CI / deploy (push) Failing after 4m59s
2025-02-11 22:00:44 +01:00
468d9660c4 Merge pull request 'jgitver' (#23) from jgitver into main
Some checks failed
CI / deploy (push) Failing after 4m48s
Reviewed-on: #23
2025-02-11 21:42:15 +01:00
a43f5b30f7 remove SNAPSHOT
All checks were successful
CI / deploy (push) Successful in 4m53s
CI / deploy (pull_request) Successful in 4m50s
2025-02-11 21:31:28 +01:00
4ebd9a7262 fetch tags
All checks were successful
CI / deploy (push) Successful in 6m58s
2025-02-11 20:42:22 +01:00
ad4179a517 Merge pull request 'adds jgitver' (#22) from jgitver into main
All checks were successful
CI / deploy (push) Successful in 4m35s
Reviewed-on: #22
2025-02-11 20:36:32 +01:00
Arindy
f7705e15e5 adds jgitver
All checks were successful
CI / deploy (push) Successful in 4m30s
CI / deploy (pull_request) Successful in 4m27s
2025-02-11 20:24:09 +01:00
0299f89901 Merge pull request 'copies links to clipboard' (#20) from copy-to-clipboard into main
All checks were successful
CI / deploy (push) Successful in 4m28s
Reviewed-on: #20
2025-02-11 11:24:44 +01:00
Arindy
7f7a27a178 copies links to clipboard
All checks were successful
CI / deploy (push) Successful in 4m22s
CI / deploy (pull_request) Successful in 4m18s
2025-02-11 11:15:37 +01:00
d9caee420a Merge pull request 'overwrites dice-overlay sse, so that only the last instance rolls the dice' (#19) from remove-sse-on-close into main
All checks were successful
CI / deploy (push) Successful in 4m21s
Reviewed-on: #19
2025-02-10 21:39:03 +01:00
Arindy
f9893e2a17 overwrites dice-overlay sse, so that only the last instance rolls the dice
All checks were successful
CI / deploy (push) Successful in 4m18s
CI / deploy (pull_request) Successful in 4m20s
2025-02-10 21:30:11 +01:00
fdd06978d8 Merge pull request 'show-all-overlays' (#18) from show-all-overlays into main
All checks were successful
CI / deploy (push) Successful in 4m24s
Reviewed-on: #18
2025-02-10 20:21:20 +01:00
Arindy
13f206ae97 splits gm-view from normal-view
All checks were successful
CI / deploy (push) Successful in 4m16s
CI / deploy (pull_request) Successful in 4m20s
2025-02-10 20:08:29 +01:00
Arindy
827b74ebf1 moves overlay notice 2025-02-10 18:41:13 +01:00
8 changed files with 440 additions and 62 deletions

View File

@@ -11,6 +11,8 @@ jobs:
steps:
- name: Check out repository code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup JDK
uses: actions/setup-java@v4
@@ -18,8 +20,14 @@ jobs:
distribution: 'zulu'
java-version: '21'
- name: new Version
if: github.ref_name == 'main'
run: |
./mvnw -B --no-transfer-progress versions:set -DremoveSnapshot
- name: Build Runner
run: ./mvnw --no-transfer-progress clean verify -Pnative -Dquarkus.native.remote-container-build=true
run: |
./mvnw -B --no-transfer-progress clean verify -Pnative -Dquarkus.native.remote-container-build=true -Djgitver.skip=true
- name: Add coverage to PR
id: jacoco
@@ -31,10 +39,32 @@ jobs:
min-coverage-changed-files: 60
title: ${{ env.REPO }} Coverage
- id: version
name: Version
run: echo "VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout)" >> ${GITHUB_OUTPUT}
- name: Build Container
if: github.ref_name == 'main'
run: docker build -f src/main/docker/Dockerfile.native-micro -t ${{ env.REPO }} .
run: |
echo ${{secrets.PACKAGES_TOKEN}} | docker login --username ${{ secrets.PACKAGES_USER }} --password-stdin git.arindy.de
docker build -f src/main/docker/Dockerfile.native-micro -t git.arindy.de/arindy/dice-tower:latest -t git.arindy.de/arindy/dice-tower:${{ steps.version.outputs.VERSION }} .
docker push git.arindy.de/arindy/dice-tower:${{ steps.version.outputs.VERSION }}
docker push git.arindy.de/arindy/dice-tower:latest
- name: Deploy
if: github.ref_name == 'main'
run: "docker compose up -d"
- name: create tag
if: github.ref_name == 'main'
run: |
git config user.email "ci@git.arindy.de"
git config user.name "gitea"
git add ./pom.xml
git commit -m "[no ci] release ${{ steps.version.outputs.VERSION }}"
./mvnw -B --no-transfer-progress clean validate -Pnew-snapshot
git add ./pom.xml
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 ${{ steps.version.outputs.VERSION }}

View File

@@ -6,7 +6,8 @@
<p align="center">
<a href="#key-features">Key Features</a> •
<a href="#how-to-build">How To Build</a> •
<a href="#start-container">Start Container</a> •
<a href="#how-to-build-from-scratch">How To Build from scratch</a> •
<a href="#credits">Credits</a> •
<a href="#license">License</a>
</p>
@@ -24,8 +25,28 @@
* Watch roll results (also available as Browser Source in OBS)
---
## Start Container
## How To Build
You can start dice-tower with docker compose
Create a `compose.yml`-File with following content:
```yaml
services:
api:
container_name: dice-tower
image: git.arindy.de/arindy/dice-tower:latest
restart: always
ports:
- "8080:8080"
```
Run the container with:
```bash
docker compose up -d
```
---
## How To Build from scratch
To clone and run this application, you'll need `git`, `java21` and `docker`.

View File

@@ -1,7 +1,7 @@
services:
api:
container_name: dice-tower
image: dice-tower
image: git.arindy.de/arindy/dice-tower:latest
restart: always
networks:
- reverse_proxy

46
pom.xml
View File

@@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>de.arindy</groupId>
<artifactId>dice-tower</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>1.0.3-SNAPSHOT</version>
<properties>
<compiler-plugin.version>3.13.0</compiler-plugin.version>
@@ -21,6 +21,13 @@
<rxjava-version>2.2.8</rxjava-version>
</properties>
<scm>
<connection>scm:git:https://git.arindy.de/arindy/dice-tower.git</connection>
<developerConnection>scm:git:https://git.arindy.de/arindy/dice-tower.git</developerConnection>
<url>https://git.arindy.de/arindy/dice-tower</url>
<tag>HEAD</tag>
</scm>
<dependencyManagement>
<dependencies>
<dependency>
@@ -267,5 +274,42 @@
<quarkus.native.enabled>true</quarkus.native.enabled>
</properties>
</profile>
<profile>
<id>new-snapshot</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>next-version</id>
<goals>
<goal>parse-version</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.18.0</version>
<executions>
<execution>
<id>release-version</id>
<phase>validate</phase>
<goals>
<goal>set</goal>
</goals>
<configuration>
<newVersion>${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.nextIncrementalVersion}-SNAPSHOT</newVersion>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@@ -26,9 +26,6 @@ final class DiceResource(@Context val sse: Sse) {
@POST
@Consumes(MediaType.APPLICATION_JSON)
fun parseCommand(@PathParam("id") id: String, data: RollPayload) {
if (!sseBroadcasters.containsKey(id)) {
sseBroadcasters[id] = sse.newBroadcaster()
}
data.roll = data.command.split(" ", "&", "and").filter { it.isNotEmpty() }.map { it.trim() }.toTypedArray<String>()
data.room = id.split(":")[0]
data.user = id.split(":")[1]
@@ -41,6 +38,17 @@ final class DiceResource(@Context val sse: Sse) {
}
}
@POST
@Path("/register")
@Consumes(MediaType.APPLICATION_JSON)
fun register(@PathParam("id") id: String, data: Any) {
println("id = [${id}], data = [${data}]")
sseBroadcasters["register:$id"]?.broadcast(
eventBuilder.id((UUID.randomUUID()).toString())
.mediaType(MediaType.APPLICATION_JSON_TYPE).data(data).build())
}
@POST
@Path("/results")
@Consumes(MediaType.APPLICATION_JSON)
@@ -55,9 +63,7 @@ final class DiceResource(@Context val sse: Sse) {
@Path("/stream")
@Produces(MediaType.SERVER_SENT_EVENTS)
fun stream(@PathParam("id") id: String, @Context sseEventSink: SseEventSink) {
if (!sseBroadcasters.containsKey(id)) {
sseBroadcasters[id] = sse.newBroadcaster()
}
sseBroadcasters[id] = sse.newBroadcaster()
sseBroadcasters[id]?.register(sseEventSink)
}
@@ -71,7 +77,17 @@ final class DiceResource(@Context val sse: Sse) {
sseBroadcasters[id]?.register(sseEventSink)
}
@GET
@Path("/users")
@Produces(MediaType.SERVER_SENT_EVENTS)
fun users(@PathParam("id") id: String, @Context sseEventSink: SseEventSink) {
if (!sseBroadcasters.containsKey("register:$id")) {
sseBroadcasters["register:$id"] = sse.newBroadcaster()
}
sseBroadcasters["register:$id"]?.register(sseEventSink)
}
@RegisterForReflection
data class Result(val name: String, val user: String, val themeColor: String) {}
data class Result(val name: String, val user: String, val themeColor: String)
}

View File

@@ -0,0 +1,35 @@
package de.arindy.dicetower
import io.quarkus.qute.TemplateInstance
import io.quarkus.runtime.annotations.RegisterForReflection
import jakarta.enterprise.context.ApplicationScoped
import jakarta.ws.rs.Consumes
import jakarta.ws.rs.GET
import jakarta.ws.rs.POST
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.Context
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 java.util.UUID
@Path("/")
@ApplicationScoped
final class IndexResource() {
@ConfigProperty(name = "quarkus.application.version")
private lateinit var version: String
@GET
@Produces(MediaType.TEXT_HTML)
fun get(): TemplateInstance {
return Templates.index(version)
}
}

View File

@@ -9,4 +9,6 @@ object Templates {
external fun overlay(diceid: String, scale: Int?, clearAfter: Long?): TemplateInstance
@JvmStatic
external fun results(room: String, name: String?, user: String?): TemplateInstance
@JvmStatic
external fun index(version: String): TemplateInstance
}

View File

@@ -5,11 +5,26 @@
<title>Dice-Tower</title>
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="/vendor/color-picker.js"></script>
<script src="/META-INF/resources/vendor/color-picker.js"></script>
<style>
.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;}
.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;
}
.collapsible {
background-color: #333333;
color: black;
@@ -42,6 +57,7 @@
button:hover {
background: #444444;
}
button:active {
background: #222222;
}
@@ -122,8 +138,8 @@
position: fixed;
border-radius: 0.5rem;
padding: 15px;
color:#fff !important;
background-color:#333333dd !important
color: #fff !important;
background-color: #333333dd !important
}
#dice-box {
@@ -140,33 +156,150 @@
height: 100%;
margin: 20px
}
.checkbox {
position: relative;
padding-left: 100px;
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: 75px;
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>
<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">
<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">Dice-Tower</h1>
<div class="w3-panel w3-theme-l4 w3-card w3-display-container" 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" required onkeyup="start(event)"/><br/>
<label for="room" id="roomLabel">Room </label><input type="text" id="room" style="width: 50%" required onkeyup="start(event)"/><br/>
<button id="start" onclick="start()" style="align-self: center; margin-top: 20px">Start 🞂</button>
<div class="w3-panel w3-theme-l4 w3-card w3-display-container"
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"
required onkeyup="start(event)"/><br/>
<label for="room" id="roomLabel">Room </label><input type="text" id="room" style="width: 50%" required
onkeyup="start(event)"/><br/>
<div>
<button id="start" onclick="start()" style="align-self: center; margin-top: 20px">Start 🞂</button>
<label class="checkbox" id="gm-container">Join as GM
<input type="checkbox" id="gm" style="margin-left: 50px">
<span class="checkmark"></span>
</label>
</div>
</div>
<div id="dice-tower" hidden class="w3-panel w3-theme-l4 w3-card w3-display-container" style="padding: 25px; margin-bottom: auto">
<button type="button" class="collapsible" style="color: white; font-weight: bold">Overlay URLs <a>🞃</a></button>
<div id="dice-tower" hidden class="w3-panel w3-theme-l4 w3-card w3-display-container"
style="padding: 25px; margin-bottom: auto">
<button type="button" class="collapsible" style="color: white; font-weight: bold">Overlay URLs <a>🞃</a></button>
<div class="content">
<div style="display: flex; flex-direction: row; justify-content: space-between; align-items: baseline;">
<label for="overlayId">Dice-Overlay <a style="color: red"> (Only open once) </a> </label>
<input type="text" readonly id="overlayId" style="flex-grow: 1"/>
<button popovertarget="overlay-hint" data-trigger="hover" class="overlayButton">🛈</button>
<div id="overlay-urls">
<div style="display: flex; flex-direction: row; justify-content: space-between; align-items: baseline;">
<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">🛈
</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">🛈</button>
</div>
<div style="display: flex; flex-direction: row; justify-content: space-between; align-items: baseline;">
<label for="resultsId">All-Results-Overlay </label><input type="text" readonly id="resultsId" style="flex-grow: 1"/><button popovertarget="all-results-hint" data-trigger="hover" class="overlayButton">🛈</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"/><button popovertarget="my-results-hint" data-trigger="hover" class="overlayButton">🛈</button>
<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">🛈</button>
</div>
</div>
<button type="button" class="collapsible" style="color: white; font-weight: bold">Customize Dice <a>🞃</a></button>
<button type="button" class="collapsible" style="color: white; font-weight: bold">Customize Dice <a>🞃</a>
</button>
<div class="content">
<label for="theme">Theme </label>
<select name="theme" id="theme" style="margin: 25px">
@@ -189,9 +322,13 @@
</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()">-</button>
<button style="border: transparent; border-radius: 100%; font-size: x-large; font-weight: bold; height: 50px; width: 50px"
onclick="removeDice()">-
</button>
<label style="font-size: x-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" onclick="addDice()">+</button>
<button style="border: transparent; border-radius: 100%; font-size: x-large; font-weight: bold; height: 50px; width: 50px"
onclick="addDice()">+
</button>
<button style="font-size: x-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: x-large; font-weight: bold;" onclick="rollEasy('d8')">D8</button>
@@ -207,7 +344,7 @@
<button popovertarget="command-hint" data-trigger="hover" class="overlayButton">🛈</button>
</div>
<div style="margin: 20px 25px">
<div style="margin: 20px 25px" id="all-results">
<label for="resultSwitch">Show all results </label>
<label class="switch">
<input type="checkbox" id="resultSwitch">
@@ -215,16 +352,20 @@
</label>
</div>
</div>
<div id="results" hidden class="w3-panel w3-theme-l6 w3-card w3-display-container" style="padding: 25px; flex-grow: 1; margin-bottom: auto">
<iframe id="resultFrame" title="results" style="width: 100%; height: 85%; overflow: hidden; border: 0" onload="this.height=this.contentWindow.document.body.scrollHeight;" ></iframe>
<div id="results" hidden class="w3-panel w3-theme-l6 w3-card w3-display-container"
style="padding: 25px; flex-grow: 1; margin-bottom: auto">
<iframe id="resultFrame" title="results" style="width: 100%; height: 85%; overflow: hidden; border: 0"
onload="this.height=this.contentWindow.document.body.scrollHeight;"></iframe>
</div>
<div popover id="overlay-hint" class="tooltip">
<p style="color: red; font-weight: bold">Only open Overlay once, unless you want multiple results</p>
<p style="color: red; font-weight: bold">Only the last loaded instance of that overlay rolls the dice!</p>
<p>Query Params you can Change:</p>
<ul>
<li><strong>scale</strong> changes the size of the dice (any value over 1)</li>
<li><strong>clearAfter</strong> time until dice are cleared (in seconds; remove param or set -1 to keep the dice)</li>
<li><strong>clearAfter</strong> time until dice are cleared (in seconds; remove param or set -1 to keep the
dice)
</li>
</ul>
</div>
<div popover id="all-results-hint" class="tooltip">
@@ -244,11 +385,20 @@
<div class="w3-panel w3-theme-l4 w3-card w3-display-container" style="padding: 25px; margin-bottom: auto">
<h2 style="text-align: center">How-To</h2>
<ul>
<li>Join a room by entering your character name and the name of the room</li>
<li>
Join a room by entering your character name and the name of the room.<br/>
<strong>If you are a GM, make sure to join the room first or let all other players rejoin to get all
Overlay-URLs.</strong>
</li>
<li>Open your Dice-Overlay either in a new Tab or as a browser source in OBS</li>
<ul>
<li>Only open your Dice-Overlay once!</li>
<li>You can configure your Overlay with query parameters (more information at the info element next to the link)</li>
<li style="color: red; font-weight: bold">Only the last loaded instance of that overlay rolls the
dice!
</li>
<li>You can configure your Overlay with query parameters (more information at the info element next
to the link)
</li>
</ul>
<li>Configure your dice</li>
<li>Save your dice configuration</li>
@@ -257,6 +407,9 @@
</div>
</div>
</div>
<div id="snackbar-container">
<div id="snackbar">.. they see them rolling</div>
</div>
<script>
function addDice() {
let amount = +document.getElementById('dice-amount').innerText
@@ -269,11 +422,15 @@
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) {
console.log(document.getElementById('gm').checked)
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');
@@ -283,10 +440,14 @@
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';
if (localStorage.getItem(document.getElementById('name').value + "-theme")) {
@@ -295,6 +456,48 @@
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();
});
}
}
}
@@ -304,7 +507,7 @@
}
function roll(event) {
if((!event || event.keyCode === 13) && document.getElementById('command').value?.length > 0) {
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')
@@ -313,7 +516,7 @@
command: document.getElementById('command').value,
themeColor: document.getElementById('themeColor').value,
theme: document.getElementById('theme').value
} ))
}))
}
}
@@ -337,20 +540,24 @@
});
}
const popover = document.querySelectorAll("[popovertarget][data-trigger='hover']");
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();
popover.forEach((e) => {
const target = document.querySelector("#" + e.getAttribute("popovertarget"));
e.addEventListener("mouseover", () => {
target.showPopover();
});
e.addEventListener("mouseout", () => {
target.hidePopover();
});
});
}
e.addEventListener("mouseout",()=>{
target.hidePopover();
});
});
showPopover();
document.getElementById('resultSwitch').addEventListener('change', function() {
document.getElementById('resultSwitch').addEventListener('change', function () {
if (!this.checked) {
document.getElementById('resultFrame').src = document.getElementById('myResultsId').value;
} else {
@@ -358,15 +565,36 @@
}
})
document.addEventListener("DOMContentLoaded", async() => {
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("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.addEventListener("DOMContentLoaded", async () => {
document.getElementById('preview').onclick = async () => {
document.getElementById('dice-box').replaceChildren(...[])
const diceBox = new DiceBox("#dice-box", {
@@ -376,13 +604,15 @@
scale: 14
});
await diceBox.init()
diceBox.roll(['1d2','1d4','1d6','1d8','1d10','1d12','1d20','1d100']);
diceBox.roll(['1d2', '1d4', '1d6', '1d8', '1d10', '1d12', '1d20', '1d100']);
}
})
</script>
</body>
<footer class="w3-theme-l1 w3-center w3-padding-16">
<a href="https://git.arindy.de/arindy/dice-tower" target="_blank" class="w3-hover-text-black">Dice-Tower on my GitTea</a>
Version {version} ::
<a href="https://git.arindy.de/arindy/dice-tower" target="_blank" class="w3-hover-text-black">Dice-Tower on my
GitTea</a>
</footer>
</html>