32 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
80d331cdd3 Merge pull request 'adds buttons for simple commands' (#16) from adds-easy-way-to-roll into main
All checks were successful
CI / deploy (push) Successful in 4m18s
Reviewed-on: #16
2025-02-10 18:39:00 +01:00
Arindy
0e3d88cd4a fixes overlay tooltip
All checks were successful
CI / deploy (push) Successful in 4m14s
CI / deploy (pull_request) Successful in 4m17s
2025-02-10 18:26:44 +01:00
Arindy
cb077b50de adds how-to
All checks were successful
CI / deploy (push) Successful in 4m21s
CI / deploy (pull_request) Successful in 4m19s
2025-02-10 18:19:54 +01:00
Arindy
9def3240b2 styles buttons
All checks were successful
CI / deploy (push) Successful in 4m29s
CI / deploy (pull_request) Successful in 4m22s
2025-02-10 18:01:57 +01:00
Arindy
13eacc886d adds buttons for simple commands
All checks were successful
CI / deploy (push) Successful in 4m18s
CI / deploy (pull_request) Successful in 4m17s
2025-02-10 17:46:10 +01:00
af41ff5d78 Merge pull request 'adds a dice preview' (#15) from fix-color-picker into main
All checks were successful
CI / deploy (push) Successful in 4m24s
Reviewed-on: #15
2025-02-10 17:20:26 +01:00
Arindy
153b25e53f rounds elements
All checks were successful
CI / deploy (push) Successful in 4m23s
CI / deploy (pull_request) Successful in 4m13s
2025-02-10 17:04:57 +01:00
7ee16d5f3e Merge pull request 'enter in color picker now sets the color' (#14) from fix-color-picker into main
All checks were successful
CI / deploy (push) Successful in 4m22s
Reviewed-on: #14
2025-02-10 16:05:24 +01:00
11 changed files with 783 additions and 335 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

@@ -0,0 +1,618 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<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="/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;
}
.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: 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">
<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/>
<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 class="content">
<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="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>
<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 🔎</button>
<button style="margin: 10px" onclick="saveDice()">Save 💾</button>
<button popovertarget="save-dice-hint" data-trigger="hover" class="overlayButton">🛈</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()">-
</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="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>
<button style="font-size: x-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: x-large; font-weight: bold;" onclick="rollEasy('d20')">D20</button>
<button style="font-size: x-large; font-weight: bold;" onclick="rollEasy('d100')">D100</button>
</div>
<div style="display: flex; flex-direction: row; justify-content: space-between; align-items: baseline; margin: 20px 25px;">
<label for="command">Command </label>
<input type="text" id="command" style="flex-grow: 1" onkeyup="roll(event)"/>
<button hidden id="roll" onclick="roll()">Roll 🎲</button>
<button popovertarget="command-hint" data-trigger="hover" class="overlayButton">🛈</button>
</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 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 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>
</ul>
</div>
<div popover id="all-results-hint" class="tooltip">
<p>Shows all Results in this room</p>
</div>
<div popover id="my-results-hint" class="tooltip">
<p>Shows only my Results in this room</p>
</div>
<div popover id="save-dice-hint" class="tooltip">
This saves your current theme and theme color for current Name
</div>
<div popover id="command-hint" class="tooltip">
Example Commands: "1d6", "2d8 1d100", "1d4 and 1d6", "2d20 & 1d2, "5d6+10"
</div>
<div style="margin-top: 20px; flex-grow: 1" id="how-to">
<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.<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 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>
<li>Start rolling</li>
</ul>
</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
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) {
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');
document.getElementById('resultFrame').src = document.getElementById('myResultsId').value;
document.getElementById('roll').hidden = false;
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';
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 = "🞃"
} else {
content.style.display = "block";
this.children[0].innerHTML = "🞁"
}
});
}
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("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>
<footer class="w3-theme-l1 w3-center w3-padding-16">
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>

View File

@@ -61,7 +61,9 @@
document.addEventListener("DOMContentLoaded", async() => {
await diceBox.init()
let timeout = 0;
evtSource.addEventListener("message", function (event) {
clearInterval(timeout);
let data = JSON.parse(event.data);
diceBox.onRollComplete = (rollResult) => {
let httpRequest = new XMLHttpRequest();
@@ -74,7 +76,7 @@
results: rollResult,
} ))
if ({clearAfter} > 0) {
setTimeout(() => diceBox.clear(), {clearAfter} * 1000)
timeout = setTimeout(() => diceBox.clear(), {clearAfter} * 1000)
}
}

View File

@@ -1,320 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<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>
<style>
.w3-theme-l6 { color:#000 !important; background-color:#999999 !important}
.w3-theme-l4 { color:#fff !important; background-color:#666666 !important}
.w3-theme-l1 { color:#fff !important; background-color:#333333 !important}
.collapsible {
background-color: #333333;
color: black;
cursor: pointer;
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
}
button {
padding: 10px;
border: #333333 3px solid;
border-radius: 5px;
background: #333333;
color: #fff
}
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
}
.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
}
</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">
<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: 75%" required onkeyup="start(event)"/><br/>
<label for="room" id="roomLabel">Room </label><input type="text" id="room" style="width: 75%" required onkeyup="start(event)"/><br/>
<button id="start" onclick="start()" style="align-self: center">Start 🞂</button>
</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 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>
<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>
</div>
</div>
<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">
<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>
<br/>
<color-picker id="themeColor"></color-picker><br/>
<div id="dice-box"></div>
<button style="margin-bottom: 20px" id="preview">Preview 🔎</button>
<button style="margin-bottom: 20px" onclick="saveDice()">Save 💾</button>
<button popovertarget="save-dice-hint" data-trigger="hover" class="overlayButton">🛈</button>
</div><br/>
<p>Example Commands: "1d6", "2d8 1d100", "1d4 and 1d6", "2d20 & 1d2, "5d6+10"</p>
<label for="command">Command </label><input type="text" id="command" onkeyup="roll(event)"/>
<button hidden id="roll" onclick="roll()">Roll 🞂</button><br/><br/>
<label for="resultSwitch">Show all results </label>
<label class="switch">
<input type="checkbox" id="resultSwitch">
<span class="slider"></span>
</label>
</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>
<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>Query Params you can Change:</p>
<ul>
<li><strong>scale</strong> changes the size of the dice (any value over 1; remove param or set -1 to keep the dice)</li>
<li><strong>clearAfter</strong> time until dice are cleared (in seconds)</li>
</ul>
</div>
<div popover id="all-results-hint" class="tooltip">
<p>Shows all Results in this room</p>
</div>
<div popover id="my-results-hint" class="tooltip">
<p>Shows only my Results in this room</p>
</div>
<div popover id="save-dice-hint" class="tooltip">
This saves your current theme and theme color for current Name
</div>
</div>
<script>
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;
document.getElementById('start').hidden = true;
document.getElementById('dice-tower').hidden = false;
document.getElementById('name').hidden = true;
document.getElementById('room').hidden = true;
document.getElementById('results').hidden = false;
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.title = document.getElementById('name').value + ' - Dice-Tower';
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"));
}
}
}
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 = "🞃"
} else {
content.style.display = "block";
this.children[0].innerHTML = "🞁"
}
});
}
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();
});
});
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());
}
})
</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>
<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>
</footer>
</html>

View File

@@ -31,7 +31,7 @@
resultText += '<br/>&ensp; D' + result.sides + ': [' + values.map(value => value === 1 ? '<strong style="color: red">' + value + '</strong>' : value === result.sides ? '<strong style="color: 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 style="font-size: x-large">' + result.value + '</strong> '
})
}
node.innerHTML = '<strong style="text-shadow: 2px 2px 10px ' + data.themeColor + ';">' + data.name + ':</strong> &#127922; ' + resultText
node.innerHTML = '<strong style="text-shadow: 2px 2px 10px ' + data.themeColor + ';">' + data.name + ':</strong> 🎲 ' + resultText
name.appendChild(node)
document.getElementById('results').insertBefore(name, document.getElementById('results').firstChild);
}