cleaned up repository
118
TODO.md
@ -1,118 +0,0 @@
|
|||||||
# sr2ini: Bugs and Features Tracker
|
|
||||||
|
|
||||||
## Bugs
|
|
||||||
|
|
||||||
- x Bug: KO/dead buttons fkt. nicht
|
|
||||||
- applyDamage, ersetze erste Zeile: let $btn = $(e.target).is("button") ? $(e.target) : $(e.target).parents("button")[0];
|
|
||||||
- x Bug: Kann nicht weit genug runterscrollen für remove bzw die unteren schadenskastchen
|
|
||||||
- Footer hatte viel zu großen z-index
|
|
||||||
- x Bug: resort after remove -> einfach eingefügt
|
|
||||||
- x background-image wiederholt sich -> hat sich nach Redesign erledigt
|
|
||||||
- x Habe alle $("…").modal("hide|show") eliminiert; die Sichtbarkeit der Modals wird jetzt ausschließlich durch data-bs-Attribute gesteuert
|
|
||||||
- x add-Button leert die inputs vom Modal nicht mehr
|
|
||||||
- event hidden.bs.modal feuert nicht mehr
|
|
||||||
- zuerst dachte ich, es liegt daran, dass ich das bootstrap js zu Klasse Modal nicht laden konnte. Mittlerweile habe ich es hingekriegr, doch das event feuert trotzdem nicht
|
|
||||||
- hab's hingekriegt, indem ich jquery und bootstrap nun doch wieder in der index.html lade und nicht mehr als import.
|
|
||||||
- x in großer Darstellung bleiben u.U. links und rechts Ränder frei, wo das Hintergrundbild nicht skaliert wird -> background-size: cover
|
|
||||||
- x bei Benutzung von augmented-ui: der Text von .combatant-name ist nach oben verschoben und links+rechts abgeschnitten
|
|
||||||
- grundlegendes Problem: augmented-ui nutzt ::before und ::after, um die Styles zu erzeugen; mein eigenes ::before kollidiert damit
|
|
||||||
- füge den .combatant-name jetzt wieder direkt ein, nicht mehr per Datenattribut
|
|
||||||
- x damit nirgendwo ein Stück border fehlt, müssen die Ecken von zwei benachbarten combatant tablerows nicht gegeneinander verschoben sein
|
|
||||||
- d.h. die beiden linken und die beiden rechten Ecken einer Tablerow müssen jeweils die gleiche X-Position haben
|
|
||||||
- x alles, was vom damage monitor über den unteren Tabellenrand rüberragt, wird abgeschnitten
|
|
||||||
- musste den clip-path von tr und td auf none setzen
|
|
||||||
- x jetzt kann ich den damage-monitor zwar aufklappen, aber die Buttons der weiter unten liegenden combatant-rows überdecken ihn jedesmal.
|
|
||||||
- x wenn ich einen Damage-Button anklicke, beginnt die Transition des damage-monitor von Neuem
|
|
||||||
- x wenn die Navbar borders hat, sollte sie nicht direkt am oberen Bildrand anfangen, sondern ein paar Pixel weiter unten
|
|
||||||
- x Unterschied zwischen enabled und disabled button nicht gut erkennbar
|
|
||||||
- x Output vom HTML Validator:
|
|
||||||
- img elements müssen alt attributes haben
|
|
||||||
- The form attribute must refer to a form element. (bei den Input Elementen im combatant-modal) -> muss nachsehen, was ich dann genau da eintragen muss
|
|
||||||
- x minusten.svg benutzt die falsche Schriftart
|
|
||||||
- x manchmal spinnt das actions-menu, dann taucht es einfach nicht mehr auf
|
|
||||||
- Bsp: more -> clone -> cancel
|
|
||||||
- x derzeit werden die icons im actions-menu nicht dunkler gemacht (.zero-ini o.Ä.), weil ich die img-Rules aus dem CSS rausgenommen habe
|
|
||||||
- ich könnte die Rules wieder reinnehmen
|
|
||||||
- x oder die Icons mit SVG nachbauen (edit und trash)
|
|
||||||
- x damage-monitor und actions-menu überlappen sich; die oberen drei phys-dmg-buttons lösen edit/clone/remove aus
|
|
||||||
- x bei tot/KO wird die Klasse ko-or-dead nicht gesetzt
|
|
||||||
- es werden gar keine contextual classes gesetzt
|
|
||||||
- musste die entspr. arrow function in sortTable() wieder zu einer regular function machen
|
|
||||||
- x combatant modal hat auf einmal zwei OK buttons
|
|
||||||
- Fehler beim sichtbar/unsichtbar-Setzen der modal buttons in handleEditButtonClick()
|
|
||||||
|
|
||||||
- x Änderungen im edit modal werden nicht mehr übernommen
|
|
||||||
- war ein fehlendes let in editCombatant() -> wo das nur wieder herkam …
|
|
||||||
- im FP3T Tor Browser kann ich rauszoomen, bis ich die ganzen damage monitors und action menus sehe -> verhindern!
|
|
||||||
- bug in validateCombatant: OK schließt das modal, auch wenn die Eingaben invalid sind
|
|
||||||
- wenn ich bei add combatant mit der Maus auf OK klicke, macht er das Modal zu, auch wenn die Eingaben invalid sind
|
|
||||||
- nach dem Laden passiert es manchmal, dass nach dem Einfügen von testCombatant das add Modal gleich wieder aufgeht
|
|
||||||
- schien die gleiche Sache zu sein wie mit dem hidden.bs.modal event
|
|
||||||
- jetzt kommt es aber trotzdem manchmal wieder
|
|
||||||
- vllt. ein timeout-Problem?
|
|
||||||
- add modal geht bei enter key nicht zu -> liegt daran, dass ich nicht mehr .modal("hide") verwende
|
|
||||||
- ich könnt's umstellen, aber will ich das?
|
|
||||||
- wenn ein damage monitor offen ist und ich auf add combatant clicke, springt der Fokus nicht zuverlässig ins erste input feld
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Feature Requests
|
|
||||||
|
|
||||||
- x nochmal wg. Daten wie name, dice, rea, true-ini und damage-x:
|
|
||||||
- Verwalte sie jetzt komplett mit der data-* API; verwende dafür ausschließlich .attr() als Getter/Setter
|
|
||||||
- Füge die Werte aus dem Attribut per CSS direkt ins Element ein (::after und content).
|
|
||||||
- x prettify code: alle HTML class names von camelCale zu dash-case komvertieren
|
|
||||||
- x Design cyberpunkig machen
|
|
||||||
- x im modal soll man die damage levels einstellen/verändern können
|
|
||||||
- x Im modal, wenn ich die wound penalties anzeige, die Fälle KO und Tod gesondert behandeln
|
|
||||||
- x clone button
|
|
||||||
- x und im combatant-modal ein weiterer OK-Button, der das Modal offenlässt
|
|
||||||
- x Design: favicon
|
|
||||||
- x imput[type=range] schicker machen
|
|
||||||
- x input elements styling anpassen für :focus. :focus-visible, :valid, :invalid
|
|
||||||
- x contextual classes hübsch machen
|
|
||||||
- x neue Icons
|
|
||||||
- x dmg-mon/actions-menu: tabindex auf -1 setzen, wenn nicht sichtbar
|
|
||||||
- x dmg-mon/actions-menu direkt unter dem jeweiligen Button ausrichten (X-Achse)
|
|
||||||
- ich richte die Buttons jetzt rechtsbündig aus, dann weiß ich immer die (ungefähre) Position des Buttons
|
|
||||||
- x SVG Icons alle in eine externe Datei als <symbol>s mit ID packen. Diese kann ich dann in anderen Files referenzieren: <svg><use href="icons.svg#icon1" /></svg>
|
|
||||||
- https://stackoverflow.com/questions/34225008/how-to-reuse-an-embedded-svg-element-in-the-same-page
|
|
||||||
- leider geht das nicht: addCombatant() fügt HTML dynamisch ins Dokument ein, und wenn in diesem HTML SVG enthalten ist, werden die <use>-Elemente darin nicht ausgewertet (also duch das referenzierte <symbol> ersetzt). Das SVG wird daher nicht angezeigt.
|
|
||||||
- um das zu beheben. müsste ich die jQuery-Methode parseHTML() modifizieren und alle Aufrufe von createElement() durch createElementNS() ersetzen
|
|
||||||
- aber am Ende bringt das gar nichts, weil der Browser bei jedem <use> den Inhalt des <symbol>s aufs Neue ins DOM kopiert. Die HTML-Platzersparnis wäre also gleich Null.
|
|
||||||
- x kann die classes text-center, text-end etc aus den Tabellenzellen entfernen
|
|
||||||
- x CSS: maroon durch b3005f ersetzen
|
|
||||||
- x anonymous functions wo möglich durch arrow functions ersetzen
|
|
||||||
- x auf bootstrap.js verzichten?
|
|
||||||
- zumindest modal.js muss ich einzeln laden
|
|
||||||
- aber sonst brauch ich es glaube ich nicht
|
|
||||||
- x dependencies lokal einbinden
|
|
||||||
- x progressive web app
|
|
||||||
- x Service Worker einrichten, um die Dateien lokal zu cachen
|
|
||||||
- x lief nicht mit Parcel pur, brauchte Paket "serve" -> npx serve dist/
|
|
||||||
-> mittlerweile geht's doch
|
|
||||||
- x parcel bindet sw.js nicht automatisch mit ein, ich muss es nach dist/ hardverlinken
|
|
||||||
- und die File-list in sw.js darf nur Files enthalten, die auch geladen werden können; sobald einer 404 ergibt, schlägt der gesamte Cache-Vorgang fehl
|
|
||||||
- geht
|
|
||||||
- x dafür sorgen, dass die Seite erst dann aufgebaut wird, wenn die CSS-Files geladen sind, damit man nicht den ungestylten Krams sieht -> passt schon
|
|
||||||
- x CSS aufräumen
|
|
||||||
- Variablen für Farben, Filter etc.
|
|
||||||
|
|
||||||
- installability (PWA) auf android noch ungetestet
|
|
||||||
- Seite auch mal im Chrome checken
|
|
||||||
- -moz-… mit -webkit-… ergänzen
|
|
||||||
- noch mehr Design
|
|
||||||
- Seite für größere Screens anpassen
|
|
||||||
- Schrift, Buttons, Icons skalieren
|
|
||||||
- em und % statt px
|
|
||||||
- Tabellenbreite begrenzen
|
|
||||||
- Animationen? Transitions?
|
|
||||||
- deployment: dist/* soll direkt auf hermes hochgeladen werden
|
|
||||||
- warum sind im dist/-Folder immer zwei Versionen der gleichen Datei?
|
|
||||||
- HTML soll nicht in eine Zeile umgedingst werden, das sieht doch nicht aus
|
|
||||||
- bootstrap, jquery, font auch lokal vorhalten
|
|
||||||
- docstrings
|
|
||||||
- parcel soll aus dem HTML code nicht die Newlines rausnehmen -> macht er das überhaupt noch?
|
|
||||||
- falls ja: .htmlnanorc anlegen, s. https://parceljs.org/languages/html/#minification und https://htmlnano.netlify.app/modules#collapsewhitespace
|
|
||||||
- nicetohave: Wenn ich rea editiere, könnte sich die ini automatisch anpassen -> da müsste ich aber die Würfelergebnisse für speichern
|
|
||||||
- nicetohave: Anzeige, wieviele Aktionen einer hat u.d wieviele davon schon verbraucht sind
|
|
||||||
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 13 KiB |
@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<browserconfig>
|
|
||||||
<msapplication>
|
|
||||||
<tile>
|
|
||||||
<square150x150logo src="/icons/mstile-150x150.png"/>
|
|
||||||
<TileColor>#004aa5</TileColor>
|
|
||||||
</tile>
|
|
||||||
</msapplication>
|
|
||||||
</browserconfig>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 11 KiB |
@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Shadowrun 2e Ini Tracker",
|
|
||||||
"short_name": "sr2ini",
|
|
||||||
"start_url": "https://unterdemradar.de/sr2ini",
|
|
||||||
"description": "A simple Initiative tracker for Shadowrun 2e",
|
|
||||||
"orientation": "portrait",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "/icons/android-chrome-192x192.png",
|
|
||||||
"sizes": "192x192",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/icons/android-chrome-512x512.png",
|
|
||||||
"sizes": "512x512",
|
|
||||||
"type": "image/png"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"theme_color": "deeppink",
|
|
||||||
"background_color": "#004aa5",
|
|
||||||
"display": "standalone"
|
|
||||||
}
|
|
||||||
@ -38,7 +38,7 @@ ram.runtime = "1M"
|
|||||||
|
|
||||||
[resources]
|
[resources]
|
||||||
[resources.sources.main]
|
[resources.sources.main]
|
||||||
url = "https://git.unterdemradar.de/tobias/sr2ini_ynh/raw/branch/main/tools/sr2ini_ynh-0.1~ynh1.tar.gz"
|
url = "https://git.unterdemradar.de/tobias/sr2ini_ynh/raw/branch/main/dist/sr2ini_ynh-0.1~ynh1.tar.gz"
|
||||||
sha256 = "abfb79af75dbf3ff5dc9373f112df1767056f4bdb0b06ccdd885823a13e70df9"
|
sha256 = "abfb79af75dbf3ff5dc9373f112df1767056f4bdb0b06ccdd885823a13e70df9"
|
||||||
format = "tar.gz"
|
format = "tar.gz"
|
||||||
in_subdir = "false"
|
in_subdir = "false"
|
||||||
|
|||||||
58
package.json
@ -1,58 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "sr2ini_ynh",
|
|
||||||
"version": "0.1~ynh1",
|
|
||||||
"description": "sr2ini as a Yunohost package.",
|
|
||||||
"private": true,
|
|
||||||
"author": {
|
|
||||||
"name": "Eclipse729",
|
|
||||||
"email": "eclipse@unterdemradar.de",
|
|
||||||
"url": "https://unterdemradar.de"
|
|
||||||
},
|
|
||||||
"homepage": "",
|
|
||||||
"license": "ISC",
|
|
||||||
"source": "src/index.html",
|
|
||||||
"devDependencies": {
|
|
||||||
"@parcel/packager-raw-url": "^2.8.3",
|
|
||||||
"@parcel/packager-xml": "^2.8.3",
|
|
||||||
"@parcel/transformer-sass": "^2.8.3",
|
|
||||||
"@parcel/transformer-webmanifest": "^2.8.3",
|
|
||||||
"@parcel/transformer-xml": "^2.8.3",
|
|
||||||
"hint": "^7.1.3",
|
|
||||||
"jest": "^29.4.3",
|
|
||||||
"jest-environment-jsdom": "^29.4.3",
|
|
||||||
"jsdom": "^21.1.0",
|
|
||||||
"parcel": "^2.8.3",
|
|
||||||
"parcel-reporter-static-files-copy": "^1.5.0"
|
|
||||||
},
|
|
||||||
"staticFiles": {
|
|
||||||
"staticPath": "src/img"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"start": "npx parcel serve src/index.html --public-url / --dist-dir dist",
|
|
||||||
"prebuild": "rm -rf dist/",
|
|
||||||
"build": "npx parcel build --no-optimize --public-url ./",
|
|
||||||
"test": "jest --coverage --env=jsdom",
|
|
||||||
"webhint": "hint http://localhost:1234"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git@git.unterdemradar.de:tobias/sr2ini.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"Shadowrun",
|
|
||||||
"Initiative tracker",
|
|
||||||
"sr2e"
|
|
||||||
],
|
|
||||||
"jest": {
|
|
||||||
"setupFiles": [
|
|
||||||
"./tools/setup-jest.js"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"type": "module",
|
|
||||||
"dependencies": {
|
|
||||||
"@parcel/service-worker": "^2.8.3",
|
|
||||||
"augmented-ui": "^2.0.0",
|
|
||||||
"bootstrap": "^5.2.3",
|
|
||||||
"jquery": "^3.6.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,506 +0,0 @@
|
|||||||
$fg: deeppink;
|
|
||||||
$fg-bright: lightpink;
|
|
||||||
$fg-dark: #b3005f;
|
|
||||||
|
|
||||||
$bg: cyan;
|
|
||||||
$bg-bright: lightcyan;
|
|
||||||
$bg-dark: darkcyan;
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Electrolize";
|
|
||||||
src: local("Electrolize"), url("../img/Electrolize-Regular.ttf") format("truetype"), url("https://fonts.googleapis.com/css2?family=Electrolize&display=swap");
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin aug() {
|
|
||||||
--aug-b: 5px;
|
|
||||||
--aug-bl: 5px;
|
|
||||||
--aug-br: 5px;
|
|
||||||
--aug-l: 5px;
|
|
||||||
--aug-r: 5px;
|
|
||||||
--aug-tl: 5px;
|
|
||||||
--aug-tr: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin border() {
|
|
||||||
--aug-border-all: 2px;
|
|
||||||
--aug-border-bg: cyan; // variables don't work in this specific instance
|
|
||||||
--aug-border-opacity: .5;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin inlay() {
|
|
||||||
--aug-inlay-bg: rgba(0, 0, 0, .5);
|
|
||||||
--aug-inlay-y: 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin button() {
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid $bg;
|
|
||||||
border-radius: 1px;
|
|
||||||
box-shadow: 0 0 2px $bg-bright, 0 0 4px $bg, 0 0 8px $bg-dark;
|
|
||||||
color: $fg;
|
|
||||||
padding-inline: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-family: 'Electrolize', sans-serif;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
overflow-x: hidden;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: darkslategray;
|
|
||||||
background-image: url("../img/bg.jpg");
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon { fill: $fg; }
|
|
||||||
|
|
||||||
.sr2-button:focus-visible {
|
|
||||||
border: 1px solid $bg !important;
|
|
||||||
box-shadow: 0 0 4px $bg-bright, 0 0 8px $bg, 0 0 16px $bg-dark !important;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
padding: 4px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
header.navbar {
|
|
||||||
@include border;
|
|
||||||
@include inlay;
|
|
||||||
|
|
||||||
--aug-b: 7px;
|
|
||||||
--aug-bl: 7px;
|
|
||||||
--aug-br: 7px;
|
|
||||||
--aug-inlay-bg: rgba(0, 0, 0, .75);
|
|
||||||
--aug-l: 7px;
|
|
||||||
--aug-r: 7px;
|
|
||||||
--aug-tl: 7px;
|
|
||||||
--aug-tr: 7px;
|
|
||||||
padding-right: .6rem;;
|
|
||||||
|
|
||||||
.navbar-brand {
|
|
||||||
color: $bg;
|
|
||||||
text-shadow: 0 0 3px, 0 0 6px, 0 0 12px, 0 0 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
@include button;
|
|
||||||
|
|
||||||
height: 38px;
|
|
||||||
margin-left: 3px;
|
|
||||||
margin-right: 3px;
|
|
||||||
width: 38px;
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
filter: invert(34%) sepia(78%) saturate(7014%) hue-rotate(316deg) brightness(95%) contrast(105%);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-responsive { margin-bottom: 1px; }
|
|
||||||
|
|
||||||
.combatants-table {
|
|
||||||
margin-top: .5rem;
|
|
||||||
|
|
||||||
tr {
|
|
||||||
@include aug;
|
|
||||||
@include border;
|
|
||||||
@include inlay;
|
|
||||||
|
|
||||||
--aug-border-bottom: 0px;
|
|
||||||
--aug-border-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.combatant-row {
|
|
||||||
clip-path: none;
|
|
||||||
vertical-align: middle !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
th:not(:first-of-type) { text-align: center; }
|
|
||||||
|
|
||||||
.th-ini { min-width: 3rem; }
|
|
||||||
|
|
||||||
.th-dice-and-rea { min-width: 3.75rem; }
|
|
||||||
|
|
||||||
.th-actions { min-width: 6.5rem; }
|
|
||||||
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
@include aug;
|
|
||||||
@include border;
|
|
||||||
@include inlay;
|
|
||||||
|
|
||||||
--aug-border-bottom: 0px;
|
|
||||||
--aug-border-right: 0px;
|
|
||||||
background: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
color: $bg;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
clip-path: none;
|
|
||||||
color: $fg;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
th:first-of-type,
|
|
||||||
td:first-of-type {
|
|
||||||
padding-left: 1rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
th:last-of-type,
|
|
||||||
td:last-of-type {
|
|
||||||
--aug-border-right: 2px;
|
|
||||||
padding-right: .75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:last-of-type td,
|
|
||||||
tr:last-of-type th {
|
|
||||||
--aug-border-bottom: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.combatant-actions {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sr2-button {
|
|
||||||
@include button;
|
|
||||||
|
|
||||||
height: 24px;
|
|
||||||
margin-left: 3px;
|
|
||||||
margin-right: 3px;
|
|
||||||
width: 24px;
|
|
||||||
|
|
||||||
img,
|
|
||||||
.icon {
|
|
||||||
bottom: 3px;
|
|
||||||
height: 16px;
|
|
||||||
position: relative;
|
|
||||||
width: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
box-shadow: none;
|
|
||||||
border-color: $bg-dark;
|
|
||||||
|
|
||||||
.icon { fill: $fg-dark; }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.combatant-ini {
|
|
||||||
padding-right: 1rem !important;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.combatant-dice-and-rea { text-align: center; }
|
|
||||||
|
|
||||||
.combatant-dice::before { content: attr(data-combatant-dice); }
|
|
||||||
|
|
||||||
.combatant-rea::before { content: attr(data-combatant-rea); }
|
|
||||||
|
|
||||||
.actions-menu {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
padding: 6px;
|
|
||||||
|
|
||||||
button {
|
|
||||||
@include button;
|
|
||||||
|
|
||||||
height: 24px;
|
|
||||||
margin: 4px;
|
|
||||||
width: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.max-ini td {
|
|
||||||
text-shadow: 0 0 .15em $fg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.zero-ini td {
|
|
||||||
color: $fg-dark !important;
|
|
||||||
|
|
||||||
.icon { fill: $fg-dark !important; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.ko-or-dead td {
|
|
||||||
background-color: rgba(0, 0, 0, .5);
|
|
||||||
color: $fg-dark !important;
|
|
||||||
text-decoration: line-through .1em $fg;
|
|
||||||
|
|
||||||
.icon { fill: $fg-dark !important; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge.bg-warning {
|
|
||||||
background: radial-gradient(circle at center, $bg, $bg-dark);
|
|
||||||
bottom: -4px;
|
|
||||||
left: 12px;
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge.bg-danger {
|
|
||||||
background: radial-gradient(circle at center, $fg, $fg-dark);
|
|
||||||
left: 12px;
|
|
||||||
top: 12px;
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.damage-dropdown,
|
|
||||||
.actions-dropdown { display: inline-block; }
|
|
||||||
|
|
||||||
.damage-monitor,
|
|
||||||
.actions-menu {
|
|
||||||
@include aug;
|
|
||||||
@include border;
|
|
||||||
|
|
||||||
--aug-inlay-bg: rgba(0, 0, 0, .5);
|
|
||||||
--aug-border-opacity: .75;
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
position: absolute;
|
|
||||||
top: calc(100% - 2px);
|
|
||||||
transition: transform .25s ease-in-out;
|
|
||||||
z-index: 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
.damage-monitor { left: calc(100vw - 1em); }
|
|
||||||
|
|
||||||
.actions-menu { left: calc(100vw + 3.5em); }
|
|
||||||
|
|
||||||
.seen { transform: translateX(-100vw); }
|
|
||||||
|
|
||||||
.damage-monitor {
|
|
||||||
button {
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
font-size: smaller;
|
|
||||||
height: 24px;
|
|
||||||
margin: 0px 2px;
|
|
||||||
width: 24px;
|
|
||||||
|
|
||||||
&:focus-visible {
|
|
||||||
filter: brightness(150%) !important;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.damage-stun {
|
|
||||||
background: radial-gradient(circle at center, $bg, $bg-dark);
|
|
||||||
box-shadow: none;
|
|
||||||
transition: background .5s, box-shadow .5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.damage-stun.active {
|
|
||||||
background: radial-gradient(circle at center, $bg-bright, $bg);
|
|
||||||
box-shadow: 0 0 3px $bg-bright, 0 0 6px $bg, 0 0 12px $bg-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
.damage-physical {
|
|
||||||
background: radial-gradient(circle at center, $fg, $fg-dark);
|
|
||||||
border-radius: 50%;;
|
|
||||||
box-shadow: none;
|
|
||||||
transition: background .5s, box-shadow .5s;
|
|
||||||
|
|
||||||
&:focus-visible { outline: $fg !important; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.damage-physical.active {
|
|
||||||
background: radial-gradient(circle at center, $fg-bright, $fg);
|
|
||||||
box-shadow: 0 0 3px $fg-bright, 0 0 6px $fg, 0 0 12px $fg-dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.footer-container {
|
|
||||||
bottom: 0px;
|
|
||||||
left: 0px;
|
|
||||||
position: fixed;
|
|
||||||
right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
|
||||||
@include aug;
|
|
||||||
@include inlay;
|
|
||||||
|
|
||||||
--aug-border-all: 2px !important;
|
|
||||||
--aug-border-bg: cyan !important; // vars don't work here
|
|
||||||
--aug-border-opacity: .5 !important;
|
|
||||||
--aug-inlay-bg: rgba(0, 0, 0, .75) !important;
|
|
||||||
--aug-tl: 10px;
|
|
||||||
--aug-tr: 10px;
|
|
||||||
height: 2.5em;
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: $bg;
|
|
||||||
font-size: xx-small;
|
|
||||||
margin: .25rem;
|
|
||||||
padding-top: .65em;
|
|
||||||
text-align: center;
|
|
||||||
user-select: auto;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: hotpink;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.sr2-modal {
|
|
||||||
@include border;
|
|
||||||
@include aug;
|
|
||||||
|
|
||||||
color: $bg;
|
|
||||||
pointer-events: auto;
|
|
||||||
|
|
||||||
button { @include button; }
|
|
||||||
|
|
||||||
.modal-header {
|
|
||||||
@include inlay;
|
|
||||||
@include border;
|
|
||||||
|
|
||||||
--aug-inlay-bottom: 0;
|
|
||||||
border-bottom: none;
|
|
||||||
text-transform: uppercase;
|
|
||||||
|
|
||||||
button { width: 30px; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-body {
|
|
||||||
@include inlay;
|
|
||||||
@include border;
|
|
||||||
|
|
||||||
--aug-inlay-y: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-footer {
|
|
||||||
@include inlay;
|
|
||||||
@include border;
|
|
||||||
|
|
||||||
--aug-inlay-top: 0;
|
|
||||||
border-top: none;
|
|
||||||
|
|
||||||
button { width: 4rem; }
|
|
||||||
}
|
|
||||||
|
|
||||||
label { margin: 0; }
|
|
||||||
|
|
||||||
.label-swap {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
|
|
||||||
label { order: 2; }
|
|
||||||
|
|
||||||
input { order: 1; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
background-color: transparent;
|
|
||||||
color: $fg;
|
|
||||||
user-select: text;
|
|
||||||
|
|
||||||
&::selection {
|
|
||||||
background-color: $fg;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
background-color: transparent;
|
|
||||||
color: $fg;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus-visible {
|
|
||||||
background-color: transparent;
|
|
||||||
color: $fg;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not([type=range]):valid {
|
|
||||||
background-image: none !important;
|
|
||||||
border: 1px solid $bg !important ;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:invalid {
|
|
||||||
background-image: none !important;
|
|
||||||
border: 1px solid $fg;
|
|
||||||
box-shadow: 0 0 3px $fg-bright, 0 0 6px $fg, 0 0 12px $fg-dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[type=number]::-webkit-inner-spin-button,
|
|
||||||
&[type=number]::-webkit-outer-spin-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[type=number] {
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
appearance: textfield;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[type=range] {
|
|
||||||
// -webkit-appearance: none;
|
|
||||||
margin-left: calc(4.5% - 2px);
|
|
||||||
width: 91%;
|
|
||||||
|
|
||||||
+ datalist {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
option {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
width: 9%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-range-track {
|
|
||||||
border: 1px;
|
|
||||||
border-radius: 1px;
|
|
||||||
height: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-range-thumb {
|
|
||||||
height: 20px;
|
|
||||||
width: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group-text {
|
|
||||||
background-color: transparent;
|
|
||||||
border: none;
|
|
||||||
color: $fg;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
border-top: 2px solid $bg;
|
|
||||||
opacity: .5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#combatant-modal-stun {
|
|
||||||
&::-moz-range-track { background-color: $bg-dark; }
|
|
||||||
&::-moz-range-thumb { background-color: $bg; }
|
|
||||||
}
|
|
||||||
|
|
||||||
#combatant-modal-physical {
|
|
||||||
&::-moz-range-track { background-color: $fg-dark; }
|
|
||||||
&::-moz-range-thumb { background-color: $fg; }
|
|
||||||
}
|
|
||||||
BIN
src/img/add.png
|
Before Width: | Height: | Size: 7.7 KiB |
BIN
src/img/bg.jpg
|
Before Width: | Height: | Size: 620 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 9.2 KiB |
BIN
src/img/zzz.png
|
Before Width: | Height: | Size: 11 KiB |
130
src/index.html
@ -1,130 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
|
|
||||||
<title>Shadowrun 2e Ini Tracker</title>
|
|
||||||
<meta name="description" content="A simple Initiative tracker for Shadowrun 2e" />
|
|
||||||
<meta name="author" content="Eclipse729" />
|
|
||||||
<meta name="theme-color" content="" />
|
|
||||||
|
|
||||||
<link rel="manifest" href="/icons/sr2ini.webmanifest">
|
|
||||||
|
|
||||||
<link type="text/css" rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.css" >
|
|
||||||
<link type="text/css" rel="stylesheet" href="../node_modules/augmented-ui/augmented-ui.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="css/sr2ini.scss">
|
|
||||||
|
|
||||||
<script type="module" src="js/sr2ini.js" defer></script>
|
|
||||||
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/icons/apple-touch-icon.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32x32.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/icons/favicon-16x16.png">
|
|
||||||
<link rel="manifest" href="/icons/sr2ini.webmanifest">
|
|
||||||
<link rel="shortcut icon" href="/icons/favicon.ico">
|
|
||||||
<meta name="msapplication-TileColor" content="#004aa5">
|
|
||||||
<meta name="msapplication-config" content="/icons/browserconfig.xml">
|
|
||||||
<meta name="theme-color" content="#004aa5">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<header class="navbar navbar-expand" data-augmented-ui="tl-2-clip-x tr-clip-y bl-clip-y br-2-clip-x b-scoop-x both">
|
|
||||||
<span class="navbar-brand ps-4">SR2 Initiative Tracker</span>
|
|
||||||
<nav class="container-fluid justify-content-end" aria-label="Main navigation">
|
|
||||||
<button type="submit" class="sr2-button" id="add-combatant-button" title="Add combatant" data-bs-toggle="modal" data-bs-target="#combatant-modal"><img src="img/add.png" alt="user outline with a plus sign"></button>
|
|
||||||
<button type="submit" class="sr2-button" id="new-round-button" title="Start new round" data-bs-toggle="modal" data-bs-target="#confirm-modal"><img src="img/newround.png" alt="a die being rolled"></button>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<main class="table-responsive overflow-visible">
|
|
||||||
<table class="table table-sm table-borderless combatants-table">
|
|
||||||
<thead>
|
|
||||||
<tr data-augmented-ui="tl-2-clip-y r-clip-y">
|
|
||||||
<th class="col th-name" data-augmented-ui="tl-2-clip-y both" title="Name">Name</th>
|
|
||||||
<th class="col-2 th-ini" data-augmented-ui="both" title="Initiative">Ini</th>
|
|
||||||
<th class="col-2 th-dice-and-rea" data-augmented-ui="both" title="Initiative Dice and Reaction">D+R</th>
|
|
||||||
<th class="col-3 th-actions" data-augmented-ui="r-clip-y both" title="Actions">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer-container">
|
|
||||||
<footer data-augmented-ui="tl-2-clip-x tr-2-clip-x both">
|
|
||||||
<p>code & design by <a href="https://tobias-radloff.de/" tabindex="-1" title="Eclipse">Eclipse</a> | Shadowrun trademarked by <a href="https://www.topps.com/" tabindex="-1" title="Topps">Topps</a><br>
|
|
||||||
icons by <a href="https://www.freepik.com" tabindex="-1" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" tabindex="-1" title="Flaticon">www.flaticon.com</a> | background by <a href="https://www.deviantart.com/xxaries1970xx" tabindex="-1" title="xxAries1970xx on DeviantArt">xxAries1970xx</a></p>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal fade" id="confirm-modal" tabindex="-2" role="dialog">
|
|
||||||
<div class="modal-dialog modal-sm" role="document">
|
|
||||||
<div class="sr2-modal" data-augmented-ui="tl-2-clip-x tr-clip-y bl-clip-y br-2-clip-x b-scoop-x border">
|
|
||||||
<div class="modal-header" data-augmented-ui="inlay">
|
|
||||||
<h5 class="modal-title">Start New Round</h5>
|
|
||||||
<button type="button" class="sr2-button" data-bs-dismiss="modal" aria-label="Close">✖</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" data-augmented-ui="inlay">
|
|
||||||
<p>Are you sure?</p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer" data-augmented-ui="inlay">
|
|
||||||
<button type="button" class="sr2-button" data-bs-dismiss="modal">Cancel</button>
|
|
||||||
<button type="submit" class="sr2-button" id="confirm-modal-new-round-ok-button" data-bs-dismiss="modal">OK</button>
|
|
||||||
<button type="submit" class="sr2-button d-none" id="confirm-modal-remove-combatant-ok-button" data-bs-dismiss="modal">OK</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="modal fade" id="combatant-modal" tabindex="-1" role="dialog">
|
|
||||||
<div class="modal-dialog modal-sm" role="document">
|
|
||||||
<div class="sr2-modal" data-augmented-ui="tl-2-clip-x tr-clip-y bl-clip-y br-2-clip-x b-scoop-x border">
|
|
||||||
<div class="modal-header" data-augmented-ui="inlay">
|
|
||||||
<h5 class="modal-title">Add New Combatant</h5>
|
|
||||||
<button type="button" class="sr2-button" data-bs-dismiss="modal" aria-label="Close">✖</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" data-augmented-ui="inlay">
|
|
||||||
<form id="combatant-form" name="combatant-modal-form" class="was-validated" onsubmit="return false;">
|
|
||||||
<div class="my-2">
|
|
||||||
<input type="text" maxlength="40" class="form-control form-control-sm" id="combatant-modal-name" form="combatant-form" placeholder="Name" required>
|
|
||||||
</div>
|
|
||||||
<div class="input-group input-group-sm my-2">
|
|
||||||
<input type="number" min="1" max="5" class="form-control form-control-sm" id="combatant-modal-dice" form="combatant-form" placeholder="Dice">
|
|
||||||
<span class="input-group-text">D+</span>
|
|
||||||
<input type="number" min="1" max="25" class="form-control form-control-sm" id="combatant-modal-rea" form="combatant-form" placeholder="REA">
|
|
||||||
<span class="input-group-text"> </span>
|
|
||||||
<input type="number" min="0" max="55" class="form-control form-control-sm" id="combatant-modal-ini" form="combatant-form" placeholder="Ini">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="my-2">
|
|
||||||
<label for="combatant-modal-stun" class="form-label">Stun Damage <span id="combatant-modal-penalty-stun"></span></label>
|
|
||||||
<input type="range" class="form-range" min="0" max="10" value="0" id="combatant-modal-stun" list="damage">
|
|
||||||
<datalist id="damage">
|
|
||||||
<option>-</option><option>L</option><option>.</option><option>M</option><option>.</option><option>.</option><option>S</option><option>.</option><option>.</option><option>.</option><option>D</option>
|
|
||||||
</datalist>
|
|
||||||
<div class="label-swap">
|
|
||||||
<label for="combatant-modal-physical" class="form-label">Physical Damage <span id="combatant-modal-penalty-physical"></span></label>
|
|
||||||
<input type="range" class="form-range" min="0" max="10" value="0" id="combatant-modal-physical" list="damage">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer" data-augmented-ui="inlay">
|
|
||||||
<button type="button" class="sr2-button" id="combatant-modal-cancel-button" data-bs-dismiss="modal">Cancel</button>
|
|
||||||
<button type="submit" class="sr2-button" id="combatant-modal-add-apply-button">Apply</button>
|
|
||||||
<button type="submit" class="sr2-button" id="combatant-modal-add-ok-button" data-bs-dismiss="modal">OK</button>
|
|
||||||
<button type="submit" class="sr2-button d-none" id="combatant-modal-edit-ok-button" data-bs-dismiss="modal">OK</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
import {manifest, version} from '@parcel/service-worker';
|
|
||||||
|
|
||||||
console.log("[Service Worker] Version:", version);
|
|
||||||
console.log("[Service Worker] Manifest:", manifest);
|
|
||||||
|
|
||||||
// install Service Worker
|
|
||||||
async function install() {
|
|
||||||
const cache = await caches.open(version); //cacheNAme
|
|
||||||
console.log("[Service Worker] Caching all: app shell and content");
|
|
||||||
await cache.addAll(manifest); // fileList
|
|
||||||
}
|
|
||||||
console.log("[Service Worker] Install");
|
|
||||||
self.addEventListener("install", e => e.waitUntil(install()));
|
|
||||||
|
|
||||||
// fetch content
|
|
||||||
/*self.addEventListener('fetch', (e) => {
|
|
||||||
e.respondWith((async () => {
|
|
||||||
const r = await caches.match(e.request);
|
|
||||||
console.log(`[Service Worker] Fetching resource: ${e.request.url}`);
|
|
||||||
if (r) {
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
const response = await fetch(e.request);
|
|
||||||
const cache = await caches.open(cacheName);
|
|
||||||
console.log(`[Service Worker] Caching new resource: ${e.request.url}`);
|
|
||||||
cache.put(e.request, response.clone());
|
|
||||||
return response;
|
|
||||||
})());
|
|
||||||
});*/
|
|
||||||
|
|
||||||
|
|
||||||
// activate: clear outdated files from cache
|
|
||||||
async function activate() {
|
|
||||||
const keys = await caches.keys();
|
|
||||||
await Promise.all(keys.map(key => key !== version && caches.delete(key)));
|
|
||||||
};
|
|
||||||
console.log("[Service Worker] Activate");
|
|
||||||
self.addEventListener("activate", e => e.waitUntil(activate()));
|
|
||||||
506
src/js/sr2ini.js
@ -1,506 +0,0 @@
|
|||||||
// Register Service Worker
|
|
||||||
if ("serviceWorker" in navigator) {
|
|
||||||
navigator.serviceWorker.register(
|
|
||||||
new URL("service-worker.js", import.meta.url), { type: "module" }
|
|
||||||
).then( (registration) => {
|
|
||||||
console.log('ServiceWorker registration successful with scope: ', registration.scope);
|
|
||||||
}, (err) => {
|
|
||||||
console.log('ServiceWorker registration failed: ', err);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error("Service workers are not supported.");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* import libraries
|
|
||||||
*/
|
|
||||||
|
|
||||||
const bs = require("../../node_modules/bootstrap/js/dist/modal.js");
|
|
||||||
const $ = require("../../node_modules/jquery/dist/jquery.js");
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* constants definitions
|
|
||||||
*/
|
|
||||||
|
|
||||||
const DAMAGE_PENALTY = [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4];
|
|
||||||
const DAMAGE_NIVEAU = ["", "L", "M", "S", "D"];
|
|
||||||
|
|
||||||
const COMBATANT_TABLE_ROW = [
|
|
||||||
'<tr class="combatant-row" data-true-ini="" data-augmented-ui="tl-scoop bl-clip-y tr-clip-y br-scoop">\n', //TODO: add data-damage-* attributes with initial damage levels
|
|
||||||
'<td class="combatant-name" title="Combatant\'s name" data-bs-toggle="modal" data-bs-target="#combatant-modal" data-augmented-ui="tl-scoop bl-clip-y both"></td>\n',
|
|
||||||
'<td class="combatant-ini" title="Effective initiative (w/ wound penalties)" data-bs-toggle="modal" data-bs-target="#combatant-modal" data-augmented-ui="both"></td>\n',
|
|
||||||
'<td class="combatant-dice-and-rea" title="Iniative dice and reaction" data-bs-toggle="modal" data-bs-target="#combatant-modal" data-augmented-ui="both"><span class="combatant-dice"></span>D+<span class="combatant-rea"></span></td>\n',
|
|
||||||
'<td class="combatant-actions" data-augmented-ui="tr-clip-y br-scoop both">\n',
|
|
||||||
'<button type="button" class="sr2-button act-button" title="Act and reduce ini by 10"><svg class="icon" viewBox="0 0 512 512"><path d="M 0 272 h 96 v 64 h -96 Z" /><path d="M 160 64 h 64 v 384 h -64 v -296 l -64 64 l -40 -40 Z" /><path d="M 352 64 h 96 l 64 64 v 256 l -64 64 h -96 l -64 -64 v -256 l 64 -64 l 32 64 h 32 l 32 32 v 192 l -32 32 h -32 l -32 -32 v -192 l 32 -32" fill-rule="evenodd" /></svg></button>\n',
|
|
||||||
'<div class="damage-dropdown">\n',
|
|
||||||
'<button type="button" class="sr2-button damage-button" title="Take damage"><svg class="icon" viewBox="0 0 512 512"><path d="M 0 288 L 144 224 L 64 32 L 224 128 L 272 0 L 336 144 L 480 96 L 400 224 L 512 304 L 384 352 L 432 512 L 272 416 L 128 512 L 160 352 L 0 288 L 166 267 L 222 290 L 211 346 L 262 312 L 318 346 L 301 290 L 346 273 L 306 245 L 334 200 L 284 217 L 262 166 L 245 211 L 189 178 L 217 245 L 166 267" fill-rule="evenodd" /></svg></button>\n',
|
|
||||||
'</div>\n',
|
|
||||||
'<div class="actions-dropdown">\n',
|
|
||||||
'<button type="button" class="sr2-button actions-button" title="More actions"><svg class="icon" viewBox="0 0 512 512"><polygon points="32 32 480 32 256 480" /></svg></button>\n',
|
|
||||||
'<div class="actions-menu" data-augmented-ui="tl-scoop bl-clip-y tr-clip-y br-scoop both">\n',
|
|
||||||
'<button type="button" class="sr2-button edit-button" title="Edit combatant" data-bs-toggle="modal" data-bs-target="#combatant-modal" tabindex="-1"><svg class="icon" viewBox="0 0 512 512"><polygon points="0 512 0 352 224 128 384 288 160 512" /><polygon points="352 0 512 160 416 256 256 96" /></svg></button>\n',
|
|
||||||
'<button type="button" class="sr2-button clone-button" title="Clone combatant" data-bs-toggle="modal" data-bs-target="#combatant-modal" tabindex="-1"><svg class="icon" viewBox="0 0 512 512"><rect x="0" y="0" width="512" height="128" /><rect x="0" y="384" width="512" height="128" /><polygon points="128 192 384 192 256 320" />/svg></button>\n',
|
|
||||||
'<button type="button" class="sr2-button remove-button" title="Remove combatant" data-bs-toggle="modal" data-bs-target="#confirm-modal" tabindex="-1"><svg class="icon" viewBox="0 0 512 512"><polygon points="96 0 416 0 416 64 480 64 480 128 32 128 32 64 96 64" /><path d="M 64 512 H 448 V 160 H 64 V 512 L 128 480 V 192 H 192 V 480 H 224 V 192 H 288 V 480 H 320 V 192 H 384 V 480 H 128" fill-rule="evenodd" /></svg></button>\n',
|
|
||||||
'</div>\n',
|
|
||||||
'</div>\n',
|
|
||||||
'</td>\n',
|
|
||||||
'</tr>'].join("");
|
|
||||||
|
|
||||||
const DAMAGE_MONITOR_HTML = [
|
|
||||||
'<div class="damage-monitor" data-augmented-ui="tl-scoop bl-clip-y tr-clip-y br-scoop both">\n',
|
|
||||||
'<table>\n',
|
|
||||||
'<tr><td><button type="button" class="damage-stun active" title="Light stun damage" tabindex="-1">L</button></td><td><button type="button" class="damage-physical active" title="Light physical damage" tabindex="-1">L</button></td></tr>\n',
|
|
||||||
'<tr><td><button type="button" class="damage-stun active" tabindex="-1"></button></td><td><button type="button" class="damage-physical active" tabindex="-1"></button></td></tr>\n',
|
|
||||||
'<tr><td><button type="button" class="damage-stun active" title="Medium stun damage" tabindex="-1">M</button></td><td><button type="button" class="damage-physical active" title="Medium physical damage" tabindex="-1">M</button></td></tr>\n',
|
|
||||||
'<tr><td><button type="button" class="damage-stun active" tabindex="-1"></button></td><td><button type="button" class="damage-physical active" tabindex="-1"></button></td></tr>\n',
|
|
||||||
'<tr><td><button type="button" class="damage-stun active" tabindex="-1"></button></td><td><button type="button" class="damage-physical active" tabindex="-1"></button></td></tr>\n',
|
|
||||||
'<tr><td><button type="button" class="damage-stun active" title="Severe stun damage" tabindex="-1">S</button></td><td><button type="button" class="damage-physical active" title="Severe physical damage" tabindex="-1">S</button></td></tr>\n',
|
|
||||||
'<tr><td><button type="button" class="damage-stun active" tabindex="-1"></button></td><td><button type="button" class="damage-physical active" tabindex="-1"></button></td></tr>\n',
|
|
||||||
'<tr><td><button type="button" class="damage-stun active" tabindex="-1"></button></td><td><button type="button" class="damage-physical active" tabindex="-1"></button></td></tr>\n',
|
|
||||||
'<tr><td><button type="button" class="damage-stun active" tabindex="-1"></button></td><td><button type="button" class="damage-physical active" tabindex="-1"></button></td></tr>\n',
|
|
||||||
'<tr><td><button type="button" class="damage-stun active" title="K.O." tabindex="-1"><img src="zzz.png" height="16" /></button></td><td><button type="button" class="damage-physical active" title="dead" tabindex="-1" ><img src="cross.png" height="16"/></button></td></tr>\n',
|
|
||||||
'</table>\n',
|
|
||||||
'</div>'].join("");
|
|
||||||
|
|
||||||
const STUN_BADGE_HTML = '<sup><span class="badge bg-warning position-absolute translate-middle stun-badge" title="Stun damage niveau"></span></sup>';
|
|
||||||
const PHYSICAL_BADGE_HTML = '<sub><span class="badge bg-danger position-absolute translate-middle physical-badge" title="Physical damage niveau"></span></sub>';
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* helper functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
// roll for initiative with the given reaction and number of ini dice
|
|
||||||
function rollForInitiative(dice, rea) {
|
|
||||||
let diceRolls = Array.from({ length: parseInt(dice) }, () => Math.ceil(Math.random() * 6));
|
|
||||||
return diceRolls.reduce((a, b) => a + b, 0) + parseInt(rea);
|
|
||||||
}
|
|
||||||
|
|
||||||
// figure out whose action comes first out of two combatants a and b
|
|
||||||
function whoGoesFirst(a, b) {
|
|
||||||
// check for K.O./death
|
|
||||||
let tmpA = $(a).hasClass("ko-or-dead") ? -1 : parseInt($(a).find(".combatant-ini").text()) || 0;
|
|
||||||
let tmpB = $(b).hasClass("ko-or-dead") ? -1 : parseInt($(b).find(".combatant-ini").text()) || 0;
|
|
||||||
// compare ini
|
|
||||||
let compIni = tmpB - tmpA;
|
|
||||||
if (compIni != 0) {
|
|
||||||
return compIni;
|
|
||||||
}
|
|
||||||
// tie; compare reaction
|
|
||||||
else {
|
|
||||||
if ($(a).find(".combatant-rea").text() == "" || $(b).find(".combatant-rea").text() == "") {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return parseInt($(b).find(".combatant-rea").text()) - parseInt($(a).find(".combatant-rea").text());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// compute a combatant's effective ini value (modified by wound penalties)
|
|
||||||
function getEffectiveIni(tr) {
|
|
||||||
// return -1 if combatant is K.O. or dead
|
|
||||||
if ($(tr).hasClass("ko-or-dead")) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
// otherwise compute effective ini (true ini minus wound penalties)
|
|
||||||
let effectiveIni = parseInt($(tr).attr("data-true-ini")) - DAMAGE_PENALTY[parseInt($(tr).attr("data-damage-stun")) || 0] - DAMAGE_PENALTY[parseInt($(tr).attr("data-damage-physical")) || 0];
|
|
||||||
return Math.max(effectiveIni, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add test combatant for testing purposes (duh)
|
|
||||||
function addTestCombatant() {
|
|
||||||
// Eclipse
|
|
||||||
$("#add-combatant-button").click();
|
|
||||||
$("#combatant-modal-name").val("Eclipse");
|
|
||||||
$("#combatant-modal-dice").val(3);
|
|
||||||
$("#combatant-modal-rea").val(6);
|
|
||||||
// $("#combatant-modal-ini").val(12);
|
|
||||||
addCombatant();
|
|
||||||
// setTimeout( () => $("#combatant-modal-add-ok-button").click(), 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Event handler functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
// click handler for act buttons; reduces ini by 10
|
|
||||||
function handleActButtonClick(e) {
|
|
||||||
// reduce ini by 10 but not lower than 0
|
|
||||||
let ini = Math.max(parseInt($(e.target).parents(".combatant-row").attr("data-true-ini")) - 10, 0);
|
|
||||||
// set new ini value
|
|
||||||
$(e.target).parents(".combatant-row").attr("data-true-ini", ini);
|
|
||||||
// resort table
|
|
||||||
sortTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// click handler for add buttons
|
|
||||||
function handleAddButtonClick(e) {
|
|
||||||
// restyle modal
|
|
||||||
$("#combatant-modal .modal-title").text("Add Combatant");
|
|
||||||
$("#combatant-modal-add-ok-button, #combatant-modal-add-apply-button").removeClass("d-none");
|
|
||||||
$("#combatant-modal-edit-ok-button").addClass("d-none");
|
|
||||||
// set default values
|
|
||||||
$("#combatant-modal-name").val("Goon 1");
|
|
||||||
$("#combatant-modal-dice").val("2");
|
|
||||||
$("#combatant-modal-rea").val("7");
|
|
||||||
// set damage sliders to zero
|
|
||||||
$("#combatant-modal-stun, #combatant-modal-physical").val("0");
|
|
||||||
// add handler for enter key
|
|
||||||
$("#combatant-modal input[id*='combatant-modal']").off("keydown");
|
|
||||||
$("#combatant-modal input[id*='combatant-modal']").on("keydown", (e) => { if (e.which == 13 || e.which == 10) { addCombatant(e); } });
|
|
||||||
}
|
|
||||||
|
|
||||||
// click handler for clone buttons -> like handleAddButtonClick but with a pre-filled modal
|
|
||||||
function handleCloneButtonClick(e) {
|
|
||||||
// find current table row
|
|
||||||
let $tr = $(e.target).parents(".combatant-row");
|
|
||||||
// hide actions menu
|
|
||||||
$tr.find(".actions-menu").removeClass("seen");
|
|
||||||
// restyle modal
|
|
||||||
$("#combatant-modal .modal-title").text("Clone Combatant");
|
|
||||||
$("#combatant-modal-add-ok-button, #combatant-modal-add-apply-button").removeClass("d-none");
|
|
||||||
$("#combatant-modal-edit-ok-button").addClass("d-none");
|
|
||||||
// populate modal with values from row
|
|
||||||
$("#combatant-modal-name").val($tr.find(".combatant-name").text());
|
|
||||||
$("#combatant-modal-dice").val($tr.find(".combatant-dice").attr("data-combatant-dice"));
|
|
||||||
$("#combatant-modal-rea").val($tr.find(".combatant-rea").attr("data-combatant-rea"));
|
|
||||||
$("#combatant-modal-ini").val($tr.attr("data-true-ini"));
|
|
||||||
$("#combatant-modal-stun").val($tr.attr("data-damage-stun") || "0");
|
|
||||||
$("#combatant-modal-physical").val($tr.attr("data-damage-physical") || "0");
|
|
||||||
// add handler for enter key
|
|
||||||
$("#combatant-modal input[id*='combatant-modal']").off("keydown");
|
|
||||||
$("#combatant-modal input[id*='combatant-modal']").on("keydown", (e) => { if (e.which == 13 || e.which == 10) { addCombatant(e); } });
|
|
||||||
}
|
|
||||||
|
|
||||||
// click handler for damage buttons; basically toggles visibility of table.damage-monitor
|
|
||||||
function handleDamageButtonClick(e) {
|
|
||||||
// get visibility status at click time
|
|
||||||
let seenAtClick = $(e.target).parents(".damage-dropdown").find(".damage-monitor").hasClass("seen");
|
|
||||||
// hide all damage monitors and actions menus
|
|
||||||
$(".damage-monitor.seen, .actions-menu.seen").removeClass("seen").find("button").attr("tabindex", "-1");
|
|
||||||
// if targeted dm was hidden before, show it now
|
|
||||||
if (! seenAtClick) {
|
|
||||||
$(e.target).parents(".damage-dropdown").find(".damage-monitor").addClass("seen").find("button").attr("tabindex", "0");
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// click handler for edit buttons
|
|
||||||
function handleEditButtonClick(e) {
|
|
||||||
// find current table row
|
|
||||||
let $tr = $(e.target).parents(".combatant-row");
|
|
||||||
// restyle modal
|
|
||||||
$("#combatant-modal .modal-title").text("Edit Combatant");
|
|
||||||
$("#combatant-modal-edit-ok-button").removeClass("d-none");
|
|
||||||
$("#combatant-modal-add-ok-button, #combatant-modal-add-apply-button").addClass("d-none");
|
|
||||||
// populate modal with values from row
|
|
||||||
$("#combatant-modal-name").val($tr.find(".combatant-name").text());
|
|
||||||
$("#combatant-modal-dice").val($tr.find(".combatant-dice").attr("data-combatant-dice"));
|
|
||||||
$("#combatant-modal-rea").val($tr.find(".combatant-rea").attr("data-combatant-rea"));
|
|
||||||
$("#combatant-modal-ini").val($tr.attr("data-true-ini"));
|
|
||||||
$("#combatant-modal-stun").val($tr.attr("data-damage-stun") || "0");
|
|
||||||
$("#combatant-modal-physical").val($tr.attr("data-damage-physical") || "0");
|
|
||||||
// mark which row is being edited
|
|
||||||
$("#combatant-modal").data("row", $(".combatant-row").index($tr)); // here it's okay to use the jQuery data() function (which is not the same as using a data attribute) b/c this value is used only in this script and not via HTML or CSS
|
|
||||||
// add handler for enter key
|
|
||||||
$("#combatant-modal input[id*='combatant-modal']").off("keydown");
|
|
||||||
$("#combatant-modal input[id*='combatant-modal']").on("keydown", (e) => { if (e.which == 13 || e.which == 10) { editCombatant(e); } });
|
|
||||||
}
|
|
||||||
|
|
||||||
// click handler for the more-actions menus
|
|
||||||
function handleMoreActionsButtonClick(e) {
|
|
||||||
// get visibility status at click time
|
|
||||||
let seenAtClick = $(e.target).parents(".actions-dropdown").find(".actions-menu").hasClass("seen");
|
|
||||||
|
|
||||||
// hide all damage monitors
|
|
||||||
$(".actions-menu.seen, .damage-monitor.seen").removeClass("seen").find("button").attr("tabindex", "-1");
|
|
||||||
// if targeted dm was seen before, show it now
|
|
||||||
if (! seenAtClick) {
|
|
||||||
$(e.target).parents(".actions-dropdown").find(".actions-menu").addClass("seen").find("button").attr("tabindex", "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleNewRoundButton(e) {
|
|
||||||
// restyle modal
|
|
||||||
$("#confirm-modal .modal-title").text("Start new Round");
|
|
||||||
$("#confirm-modal-new-round-ok-button").removeClass("d-none");
|
|
||||||
$("#confirm-modal-remove-combatant-ok-button").addClass("d-none");
|
|
||||||
}
|
|
||||||
|
|
||||||
// click handler for remove buttons
|
|
||||||
function handleRemoveButtonClick(e) {
|
|
||||||
// restyle modal
|
|
||||||
$("#confirm-modal .modal-title").text("Remove Combatant");
|
|
||||||
$("#confirm-modal-remove-combatant-ok-button").removeClass("d-none");
|
|
||||||
$("#confirm-modal-new-round-ok-button").addClass("d-none");
|
|
||||||
// mark which row is being removed
|
|
||||||
$("#confirm-modal").data("row", $(".combatant-row").index($(e.target).parents(".combatant-row"))); // here it's okay to use .data() b/c HTML/CSS does not care about this value
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Main functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
// add new combatant
|
|
||||||
function addCombatant(e) {
|
|
||||||
// e.preventDefault();
|
|
||||||
// validate form
|
|
||||||
if (!validateCombatant()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// roll for initiative if necessary
|
|
||||||
let ini = $("#combatant-modal-ini").val().trim();
|
|
||||||
ini = (ini != "") ? ini : rollForInitiative($("#combatant-modal-dice").val(), $("#combatant-modal-rea").val());
|
|
||||||
// construct jQuery object for table row
|
|
||||||
let $tr = $($.parseHTML(COMBATANT_TABLE_ROW));
|
|
||||||
$tr.find(".damage-dropdown").append($.parseHTML(DAMAGE_MONITOR_HTML));
|
|
||||||
// populate table row with values from modal
|
|
||||||
$tr.attr("data-true-ini", ini);
|
|
||||||
$tr.find(".combatant-name").text($("#combatant-modal-name").val().trim());
|
|
||||||
$tr.find(".combatant-dice").attr("data-combatant-dice", $("#combatant-modal-dice").val().trim());
|
|
||||||
$tr.find(".combatant-rea").attr("data-combatant-rea", $("#combatant-modal-rea").val().trim());
|
|
||||||
// retrieve initial damage levels
|
|
||||||
$tr.attr("data-damage-stun", $("#combatant-modal-stun").val() || "0");
|
|
||||||
$tr.find(".damage-stun").addClass("active").slice(0, parseInt($tr.attr("data-damage-stun")) || 0).removeClass("active");
|
|
||||||
$tr.attr("data-damage-physical", $("#combatant-modal-physical").val() || "0");
|
|
||||||
$tr.find(".damage-physical").addClass("active").slice(0, parseInt($tr.attr("data-damage-physical")) || 0).removeClass("active");
|
|
||||||
// add event handlers
|
|
||||||
$tr.find("button.act-button").on("click", handleActButtonClick);
|
|
||||||
$tr.find("button.damage-button").on("click", handleDamageButtonClick);
|
|
||||||
$tr.find("button.actions-button").on("click", handleMoreActionsButtonClick);
|
|
||||||
$tr.find("button.edit-button, .combatant-name, .combatant-ini, .combatant-dice-and-rea").on("click", handleEditButtonClick);
|
|
||||||
$tr.find("button.clone-button").on("click", handleCloneButtonClick);
|
|
||||||
$tr.find("button.remove-button").on("click", handleRemoveButtonClick);
|
|
||||||
// add event handler to damage monitor
|
|
||||||
$tr.find(".damage-stun, .damage-physical").on("click", applyDamage);
|
|
||||||
// append row to table and sort
|
|
||||||
$(".combatants-table").append($tr);
|
|
||||||
sortTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply damage to combatant
|
|
||||||
function applyDamage(e) {
|
|
||||||
let $btn = $(e.target).is("button") ? $(e.target) : $(e.target).parent();
|
|
||||||
// retrieve new damage level and type from button position and "damage-[type]" class
|
|
||||||
let damageLevel = $btn.parent().parent().index();
|
|
||||||
if ( $btn.hasClass("active") ) {
|
|
||||||
damageLevel += 1;
|
|
||||||
}
|
|
||||||
let damageType = $btn.attr("class").split(" ").filter(cls => cls.substr(0, 7) == "damage-" ? cls : false).toString().substr(7);
|
|
||||||
console.log("damageType is", damageType);
|
|
||||||
// add damage level to table row as as data attribute
|
|
||||||
$btn.parents("tr.combatant-row").attr("data-damage-" + damageType, damageLevel);
|
|
||||||
// select/unselect damage buttons above/below
|
|
||||||
$btn.toggleClass("active");
|
|
||||||
$btn.parent().parent().prevAll().find("button.damage-" + damageType).removeClass("active");
|
|
||||||
$btn.parent().parent().nextAll().find("button.damage-" + damageType + ":not(.active)").addClass("active");
|
|
||||||
sortTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// edit combatant
|
|
||||||
function editCombatant(e) {
|
|
||||||
console.log("editing combatant …")
|
|
||||||
// e.preventDefault();
|
|
||||||
// validate form
|
|
||||||
if (!validateCombatant()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// get values
|
|
||||||
let name = $("#combatant-modal-name").val().trim();
|
|
||||||
let ini = $("#combatant-modal-ini").val().trim();
|
|
||||||
let dice = $("#combatant-modal-dice").val().trim();
|
|
||||||
let rea = $("#combatant-modal-rea").val().trim();
|
|
||||||
// roll for initiative if ini is empty
|
|
||||||
ini = (ini != "") ? ini : rollForInitiative(dice, rea);
|
|
||||||
// get correct row
|
|
||||||
let index = parseInt($("#combatant-modal").data("row"));
|
|
||||||
console.log("row index is", index);
|
|
||||||
let $tr = $("tr.combatant-row").eq(index);
|
|
||||||
console.log("row is", $tr);
|
|
||||||
// set new values
|
|
||||||
$tr.attr("data-true-ini", ini);
|
|
||||||
$tr.find(".combatant-name").text(name);
|
|
||||||
$tr.find(".combatant-dice").attr("data-combatant-dice", dice);
|
|
||||||
$tr.find(".combatant-rea").attr("data-combatant-rea", rea);
|
|
||||||
$tr.attr("data-damage-stun", $("#combatant-modal-stun").val() || "0");
|
|
||||||
$tr.find(".damage-stun").addClass("active").slice(0, parseInt($tr.attr("data-damage-stun")) || 0).removeClass("active");
|
|
||||||
$tr.attr("data-damage-physical", $("#combatant-modal-physical").val() || "0");
|
|
||||||
$tr.find(".damage-physical").addClass("active").slice(0, parseInt($tr.attr("data-damage-physical")) || 0).removeClass("active");
|
|
||||||
// sort table
|
|
||||||
sortTable();
|
|
||||||
// clean up
|
|
||||||
$("#combatant-modal").data("row", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove combatant
|
|
||||||
function removeCombatant(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
// remove correct row
|
|
||||||
let index = parseInt($("#confirm-modal").data("row"));
|
|
||||||
$(".combatant-row").eq(index).remove();
|
|
||||||
sortTable();
|
|
||||||
// clean up
|
|
||||||
$("#confirm-modal").data("row", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
// start a new combat round
|
|
||||||
function startNewRound(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
// are there rows at all?
|
|
||||||
if ($(".combatant-row").length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// reset ini values
|
|
||||||
$(".combatant-row").each(function () {
|
|
||||||
if ($(this).find(".combatant-dice").attr("data-combatant-dice") == "") {
|
|
||||||
$(this).attr("data-true-ini", 1);
|
|
||||||
} else {
|
|
||||||
$(this).attr("data-true-ini", rollForInitiative(parseInt($(this).find(".combatant-dice").attr("data-combatant-dice")), parseInt($(this).find(".combatant-rea").attr("data-combatant-rea"))));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// resort table
|
|
||||||
sortTable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort combatants by ini value and add contextual classes
|
|
||||||
function sortTable() {
|
|
||||||
// do some clean up: remove previous classes from rows, disable act buttons, remove effective ini and damage badges
|
|
||||||
$(".combatant-row").removeClass("ko-or-dead max-ini zero-ini"); //REGULAR_INI
|
|
||||||
$(".combatant-row").find(".act-button").prop("disabled", true).attr("aria-disabled", "true");
|
|
||||||
$(".combatant-ini").empty();
|
|
||||||
// mark KO or death with class
|
|
||||||
$(".combatant-row").each(function() {
|
|
||||||
if (parseInt($(this).attr("data-damage-stun")) == 10 || parseInt($(this).attr("data-damage-physical")) == 10) {
|
|
||||||
$(this).addClass("ko-or-dead");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// compute highest effective ini
|
|
||||||
let iniMax = Math.max.apply(null, $.map($(".combatant-row"), function (tr, i) {
|
|
||||||
// write current effective ini to table row
|
|
||||||
$(tr).find(".combatant-ini").text($(tr).hasClass("ko-or-dead") ? 0 : getEffectiveIni($(tr)));
|
|
||||||
return $(tr).find(".combatant-ini").text();
|
|
||||||
}));
|
|
||||||
// add damage badges and contextual classes
|
|
||||||
$(".combatant-row").each(function () {
|
|
||||||
// damage badges
|
|
||||||
if ($(this).attr("data-damage-stun") && $(this).attr("data-damage-stun") != "0") {
|
|
||||||
$(this).find(".combatant-ini").append($.parseHTML(STUN_BADGE_HTML));
|
|
||||||
$(this).find(".stun-badge").append(DAMAGE_NIVEAU[DAMAGE_PENALTY[$(this).attr("data-damage-stun")]]);
|
|
||||||
}
|
|
||||||
if ($(this).attr("data-damage-physical") && $(this).attr("data-damage-physical") != "0") {
|
|
||||||
$(this).find(".combatant-ini").append($.parseHTML(PHYSICAL_BADGE_HTML));
|
|
||||||
$(this).find(".physical-badge").append(DAMAGE_NIVEAU[DAMAGE_PENALTY[$(this).attr("data-damage-physical")]]);
|
|
||||||
}
|
|
||||||
// K.O./dead -> don't add anything
|
|
||||||
if ($(this).hasClass("ko-or-dead")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// ini = zero
|
|
||||||
if (parseInt($(this).find(".combatant-ini").text()) == 0) {
|
|
||||||
$(this).addClass("zero-ini");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// ini = max and non-zero
|
|
||||||
if (parseInt($(this).find(".combatant-ini").text()) == iniMax && iniMax > 0) {
|
|
||||||
$(this).addClass("max-ini").find(".act-button").prop("disabled", false).removeAttr("aria-disabled");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// sort rows and append them in new order
|
|
||||||
let $rows = $(".combatant-row").toArray().sort(whoGoesFirst);
|
|
||||||
for (let i = 0; i < $rows.length; i++) {
|
|
||||||
$(".combatants-table").append($rows[i]);
|
|
||||||
$($rows[i]).css("z-index", 50-i).css("position", "relative");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate a combatant row form by checking for all conditions, including regular HTML5 validation
|
|
||||||
function validateCombatant() {
|
|
||||||
// get input elements
|
|
||||||
let inputElements = {
|
|
||||||
name: $("#combatant-modal-name").get(0),
|
|
||||||
ini: $("#combatant-modal-ini").get(0),
|
|
||||||
dice: $("#combatant-modal-dice").get(0),
|
|
||||||
rea: $("#combatant-modal-rea").get(0)
|
|
||||||
};
|
|
||||||
// do standard HTML5 form validation first
|
|
||||||
// (makes sure that name is not empty and that all other values are numbers within their individual ranges)
|
|
||||||
let valid = true;
|
|
||||||
Object.values(inputElements).forEach( input => {
|
|
||||||
if (!input.reportValidity()) { valid = false; }
|
|
||||||
})
|
|
||||||
if (!valid) { return false; }
|
|
||||||
// now for some custom validation; first we need to get the input values
|
|
||||||
let ini = inputElements["ini"].value.trim();
|
|
||||||
let dice = inputElements["dice"].value.trim();
|
|
||||||
let rea = inputElements["rea"].value.trim();
|
|
||||||
// invalidate if ini, dice and rea are all empty
|
|
||||||
if (ini == "" && (dice == "" || rea == "")) {
|
|
||||||
inputElements["ini"].setCustomValidity("Requiring values for ini dice and reaction, or initiative, or all three");
|
|
||||||
inputElements["ini"].reportValidity();
|
|
||||||
inputElements["ini"].setCustomValidity("");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// invalidate if dice or rea is empty but not both
|
|
||||||
if ((dice == "") != (rea == "")) {
|
|
||||||
inputElements["dice"].setCustomValidity("Values required for both dice and reaction, or none (in which case ini is required)");
|
|
||||||
inputElements["dice"].reportValidity();
|
|
||||||
inputElements["dice"].setCustomValidity("");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// ok then
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize document
|
|
||||||
*/
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
// add event handlers to navbar buttons
|
|
||||||
$("#add-combatant-button").on("click", handleAddButtonClick);
|
|
||||||
$("#new-round-button").on("click", handleNewRoundButton);
|
|
||||||
// add event handlers to modal buttons
|
|
||||||
$("#combatant-modal-add-ok-button, #combatant-modal-add-apply-button").on("click", addCombatant);
|
|
||||||
$("#combatant-modal-edit-ok-button").on("click", editCombatant);
|
|
||||||
$("#confirm-modal-new-round-ok-button").on("click", startNewRound);
|
|
||||||
$("#confirm-modal-remove-combatant-ok-button").on("click", removeCombatant);
|
|
||||||
|
|
||||||
// add event listeners to damage sliders in combatant modal
|
|
||||||
$("#combatant-modal-stun").on("change", () => {
|
|
||||||
if ($("#combatant-modal-stun").val() == "10") {
|
|
||||||
$("#combatant-modal-penalty-stun").text("(K.O.)");
|
|
||||||
} else {
|
|
||||||
$("#combatant-modal-penalty-stun").text("(wound penalty -" + DAMAGE_PENALTY[$("#combatant-modal-stun").val()] + ")");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$("#combatant-modal-physical").on("change", () => {
|
|
||||||
if ($("#combatant-modal-physical").val() == "10") {
|
|
||||||
$("#combatant-modal-penalty-physical").text("(dead)");
|
|
||||||
} else {
|
|
||||||
$("#combatant-modal-penalty-physical").text("(wound penalty -" + DAMAGE_PENALTY[$("#combatant-modal-physical").val()] + ")");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// always focus name input field when combatant modal appears
|
|
||||||
$('#combatant-modal').on('shown.bs.modal', () => $('#combatant-modal-name').focus());
|
|
||||||
// always empty input fields when combatant modal disappears
|
|
||||||
$("#combatant-modal").on('hidden.bs.modal', () => $("input[id*='combatant-modal']").val(""));
|
|
||||||
// Hide damage monitors after click somewhere else
|
|
||||||
$("html").on("click", (e) => {
|
|
||||||
if ($(e.target).parents(".damage-monitor").length == 0) {
|
|
||||||
$(".damage-monitor.seen").removeClass("seen");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
addTestCombatant();
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = { rollForInitiative, validateCombatant };
|
|
||||||
|
Before Width: | Height: | Size: 16 KiB |
@ -1,31 +0,0 @@
|
|||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #004aa5;
|
|
||||||
}
|
|
||||||
|
|
||||||
div {
|
|
||||||
width: 60%;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
position: relative;
|
|
||||||
top: 100px;
|
|
||||||
|
|
||||||
|
|
||||||
border: 8px solid cyan;
|
|
||||||
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
background-color: rgba(0, 0, 0, .75);
|
|
||||||
box-shadow: 0 0 16px lightcyan, 0 0 32px lightcyan, 0 0 32px cyan, 0 0 64px cyan, 0 0 64px darkcyan, 0 0 128px darkcyan;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
width: 384px;
|
|
||||||
height: 384px;
|
|
||||||
filter: invert(34%) sepia(78%) saturate(7014%) hue-rotate(316deg) brightness(125%) contrast(105%);
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head><link type="text/css" rel="stylesheet" href="tmp.css"></head>
|
|
||||||
<body><div><button><img src="dice512.png"></button></div></body></html>
|
|
||||||
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.3 MiB |
@ -1 +0,0 @@
|
|||||||
{"result":{"status":"success"},"favicon":{"package_url":"https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/favicon_package_v0.16.zip","files_urls":["https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/android-chrome-192x192.png","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/android-chrome-512x512.png","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/apple-touch-icon.png","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/browserconfig.xml","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/favicon-16x16.png","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/favicon-32x32.png","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/favicon.ico","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/mstile-150x150.png","https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/package_files/site.webmanifest"],"html_code":"<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/icons/apple-touch-icon.png\">\n<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/icons/favicon-32x32.png\">\n<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/icons/favicon-16x16.png\">\n<link rel=\"manifest\" href=\"/icons/site.webmanifest\">\n<link rel=\"shortcut icon\" href=\"/icons/favicon.ico\">\n<meta name=\"msapplication-TileColor\" content=\"#004aa5\">\n<meta name=\"msapplication-config\" content=\"/icons/browserconfig.xml\">\n<meta name=\"theme-color\" content=\"#004aa5\">","compression":"false","overlapping_markups":["link[rel=\"apple-touch-icon\"]","link[rel=\"shortcut\"]","link[rel=\"shortcut icon\"]","link[rel=\"icon\",sizes=\"16x16\"]","link[rel=\"icon\",sizes=\"32x32\"]","meta[name=\"msapplication-TileColor\"]","meta[name=\"msapplication-config\"]","link[rel=\"manifest\"]","meta[name=\"theme-color\"]"]},"files_location":{"type":"path","path":"/icons"},"preview_picture_url":"https://realfavicongenerator.net/files/d925f2495c2cc5046691e320cba3357e995bcbb8/favicon_preview.png","version":"0.16"}
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
"masterPicture": "tools/favicon-master-image.png",
|
|
||||||
"iconsPath": "/icons",
|
|
||||||
"design": {
|
|
||||||
"ios": {
|
|
||||||
"pictureAspect": "backgroundAndMargin",
|
|
||||||
"backgroundColor": "#004aa5",
|
|
||||||
"margin": "11%",
|
|
||||||
"assets": {
|
|
||||||
"ios6AndPriorIcons": false,
|
|
||||||
"ios7AndLaterIcons": false,
|
|
||||||
"precomposedIcons": false,
|
|
||||||
"declareOnlyDefaultIcon": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"desktopBrowser": {
|
|
||||||
"design": "raw"
|
|
||||||
},
|
|
||||||
"windows": {
|
|
||||||
"pictureAspect": "noChange",
|
|
||||||
"backgroundColor": "#004aa5",
|
|
||||||
"onConflict": "override",
|
|
||||||
"assets": {
|
|
||||||
"windows80Ie10Tile": false,
|
|
||||||
"windows10Ie11EdgeTiles": {
|
|
||||||
"small": false,
|
|
||||||
"medium": true,
|
|
||||||
"big": false,
|
|
||||||
"rectangle": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"androidChrome": {
|
|
||||||
"pictureAspect": "backgroundAndMargin",
|
|
||||||
"margin": "13%",
|
|
||||||
"backgroundColor": "#004aa5",
|
|
||||||
"themeColor": "#004aa5",
|
|
||||||
"manifest": {
|
|
||||||
"display": "standalone",
|
|
||||||
"orientation": "notSet",
|
|
||||||
"onConflict": "override",
|
|
||||||
"declared": true
|
|
||||||
},
|
|
||||||
"assets": {
|
|
||||||
"legacyIcon": false,
|
|
||||||
"lowResolutionIcons": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"scalingAlgorithm": "Mitchell",
|
|
||||||
"errorOnImageTooSmall": false,
|
|
||||||
"readmeFile": false,
|
|
||||||
"htmlCodeFile": false,
|
|
||||||
"usePathAsIs": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
global.jQuery = global.$ = require('./jquery-latest.min.js');
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip-headtop">
|
|
||||||
<path d="M 0 144 v -144 h 216 v 112 h 80 v -112 h 216 v 144 Z" />
|
|
||||||
</clipPath>
|
|
||||||
<clipPath id="clip-coattop">
|
|
||||||
<path d="M 100 264 a 144 132 0 0 0 312 0 h 100 v 256 h -512 v -256 Z" />
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
<rect x="0" y="0" width="512" height="512" fill="green" stroke="none"/>
|
|
||||||
<path d="M 112 164 h 288 v 72 l -24 24 h -88 q -32 -128 -64 0 h -88 l -24 -24 v -72 Z" fill-rule="evenodd" /> <!-- glasses -->
|
|
||||||
<path d="M 136 280 a 132 164 0 0 0 240 0 Z" /> <!-- bottom of head -->
|
|
||||||
<path d="M 128 144 a 144 128 0 0 1 256 0 Z" clip-path="url(#clip-headtop)"/> <!-- top of head -->
|
|
||||||
<path d="M 232 0 h 48 v 96 h -48 Z" /> <!-- mohawk -->
|
|
||||||
<path d="M 64 304 h 384 v 48 l -64 176 h -256 l -64 -192 Z" clip-path="url(#clip-coattop)" /> <!-- coat -->
|
|
||||||
|
|
||||||
<path d="M 128 512 h -128 v -112 l 48 -32 l 48 144 Z" />
|
|
||||||
<path d="M 384 512 h 128 v -112 l -48 -32 l -48 144 Z" />
|
|
||||||
|
|
||||||
<!-- <path d="M 32 96 h 448 v 96 l -32 32 h -144 q -56 -160 -96 0 h -144 l -32 -32 v -96 Z" fill-rule="evenodd" /> glasses in big -->
|
|
||||||
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB |