Compare commits

...

5 Commits
0.9.4 ... main

9 changed files with 1275 additions and 1249 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ hint-report/
.csslintrc .csslintrc
coverage/ coverage/
tmp/ tmp/
*.geany

View File

@ -1,3 +1,3 @@
# sr2ini # sr2ini a simple initiative tracker for Shadowrun 2e
Simple Initiative tracker for Shadowrun 2e. sr2ini is a lightweight, single-page initiative tracker for the TTRPG Shadowrun in its 2nd edition. The app helps DMs and players to manage fights by tracking each combatant's initiative, order of action, damage (stun and physical), and wound modifiers. It was written specifically for mobile use and can be installed as a web app.

69
TODO.md
View File

@ -118,22 +118,16 @@
- x color scheme beim Favicon anpassen - x color scheme beim Favicon anpassen
- https://realfavicongenerator.net/ - https://realfavicongenerator.net/
- see also here: https://github.com/audreyfeldroy/favicon-cheat-sheet - see also here: https://github.com/audreyfeldroy/favicon-cheat-sheet
- x Mauszeiger soll Finger werden, wenn er über combatant-name/ini/dice-and-rea hovert - x Mauszeiger soll Finger werden, wenn er über combatant-name/ini/dice-and-rea hovert
- x actions-menu erscheint nicht direkt unter dem Button - x actions-menu erscheint nicht direkt unter dem Button
- beide dropdowns sind gerade garbled - beide dropdowns sind gerade garbled
- x im FP3T Tor Browser kann ich rauszoomen, bis ich die ganzen damage monitors und action menus sehe -> verhindern!
- action-menu und damage-monitor sliden jetzt nicht mehr rein, sondern bleiben an Ort und Stelle
### open - das sollte die Sache verhindern
- x warum fkt. das Ganze nicht als Webapp?
- unter die GPL stellen - Firefox (android) sieht die Seite nicht als installable an
- https://www.gnu.org/licenses/gpl-howto.html - Webmanifest ist aber da und scheint auch in Ordnung zu sein (sagt Firefox on Linux)
- x favicon checken: https://realfavicongenerator.net/favicon_checker
- sr2ini_ynh auf neue Version bringen
- manifest: Version, tarball URL und sha256 ersetzen
- README.md: Version ersetzen
- ggf. doc/screenshots/sr2ini-screenshot.jpg ersetzen (960x640)
- favicon checken: https://realfavicongenerator.net/favicon_checker
- jetzt fkt. immer noch nicht die URLs im Icon manifest - jetzt fkt. immer noch nicht die URLs im Icon manifest
- x Links zu den versch. favicons fkt. nicht: sie lauten /… statt /sr2ini/… - x Links zu den versch. favicons fkt. nicht: sie lauten /… statt /sr2ini/…
- x Lösung: parcel build braucht als --public-url "./" (statt "/"), dann werden die Links korrekt erzeugt - x Lösung: parcel build braucht als --public-url "./" (statt "/"), dann werden die Links korrekt erzeugt
@ -146,25 +140,42 @@
- parcel build - parcel build
- im Dist-Verz.: python3 -m http.server - im Dist-Verz.: python3 -m http.server
- CORS-Fehler im Firefox vermeiden: about:config -> content.cors.disable = true - CORS-Fehler im Firefox vermeiden: about:config -> content.cors.disable = true
- letztes Problem (hoffentlich): die Links zu den android-chrome-XYZxXYZ Icons im site.webmanifest stimmen nicht
- hab site.manifest nach src/ verschoben und die Links angepasst -> jetzt scheint's zu gehen
- x focus-related stuff
- x enter key doesn't work right away after clicking add button
- x wenn ein damage monitor offen ist und ich auf add combatant clicke, springt der Fokus nicht zuverlässig ins erste input feld
- x after pressing damage level button, focus moves to first table row act button
- it's probably b/c I resort the table
- x Geheimfunktion, um Eclipse, Solitaire, Pi und Q zu adden: hold navbar title
- x refactor (in neuem Branch):
- jede combatant tablerow kriegt eine unique #id
- Funktionen holen sich ihre Infos Infos nicht mehr aus dem DOM, sondern kriegen sie als Parameter übergeben
- tablerow id
- table
- modal
- …
### open
- focus trapping im modal fkt. nur rückwärts (shift-tab), aber nicht vorwärts
- wenn ich in einem modal mit Tab durchgehe und zu den Buttons ganz unten komme, bewegt sich der untere Rand des Modals ab und auf
- comments with general info in source files?
- unter die GPL stellen
- https://www.gnu.org/licenses/gpl-howto.html
- sr2ini_ynh auf neue Version bringen
- manifest: Version, tarball URL und sha256 ersetzen
- README.md: Version ersetzen
- ggf. doc/screenshots/sr2ini-screenshot.jpg ersetzen (960x640)
- Deployment: - Deployment:
- CI/CD: es gibt Jenkins für Yunohost - CI/CD: es gibt Jenkins für Yunohost
- hier ist ein Tutorial, um Jenkins und Gitea miteinander bekannt zu machen: https://mike42.me/blog/2019-05-how-to-integrate-gitea-and-jenkins - hier ist ein Tutorial, um Jenkins und Gitea miteinander bekannt zu machen: https://mike42.me/blog/2019-05-how-to-integrate-gitea-and-jenkins
- Jenkins Doc: https://www.jenkins.io/doc/book/using/best-practices/ - Jenkins Doc: https://www.jenkins.io/doc/book/using/best-practices/
- ist aber wohl mit Kanonen auf Spatzen
- use minified libraries (aug-ui, bs, jq) - use minified libraries (aug-ui, bs, jq)
- focus-related stuff
- enter key doesn't work right away after clicking add button
- after pressing damage button, focus moves to first table row act button
- it's probably b/c I resort the table
- wenn ein damage monitor offen ist und ich auf add combatant clicke, springt der Fokus nicht zuverlässig ins erste input feld
- focus trapping im modal fkt. nur rückwärts (shift-tab), aber nicht vorwärts
- zumindest im Chrome; FF ungetestet
- im FP3T Tor Browser kann ich rauszoomen, bis ich die ganzen damage monitors und action menus sehe -> verhindern!
- action-menu und damage-monitor sliden jetzt nicht mehr rein, sondern bleiben an Ort und Stelle
- das sollte die Sache verhindern
- warum fkt. das Ganze nicht als Webapp?
- Firefox (android) sieht die Seite nicht als installable an
- Webmanifest ist aber da und scheint auch in Ordnung zu sein (sagt Firefox on Linux)
- comments with general info in source files?
## Feature Requests ## Feature Requests
@ -175,8 +186,6 @@
- sr2ini.js: test combatant - sr2ini.js: test combatant
- git: user.name, user.email - git: user.name, user.email
- git commits, ggf. tags - git commits, ggf. tags
- Seite als Web App auf FF4And installable machen
- mal sehen …
- Animationen? Transitions? - Animationen? Transitions?
- deployment: dist/* soll direkt auf hermes hochgeladen werden - deployment: dist/* soll direkt auf hermes hochgeladen werden
- HTML soll nicht in eine Zeile umgedingst werden, das sieht doch nicht aus - HTML soll nicht in eine Zeile umgedingst werden, das sieht doch nicht aus
@ -187,7 +196,6 @@
- falls ja: .htmlnanorc anlegen, s. https://parceljs.org/languages/html/#minification und https://htmlnano.netlify.app/modules#collapsewhitespace - 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: 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 - nicetohave: Anzeige, wieviele Aktionen einer hat u.d wieviele davon schon verbraucht sind
- Sache mit dem ServiceWorker mal richtig angehen
- x progressive web app - x progressive web app
- x Service Worker einrichten, um die Dateien lokal zu cachen - x Service Worker einrichten, um die Dateien lokal zu cachen
@ -228,4 +236,3 @@
- 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 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 - x CSS aufräumen
- Variablen für Farben, Filter etc. - Variablen für Farben, Filter etc.

View File

@ -1,19 +0,0 @@
{
"name": "A simple Initiative tracker for Shadowrun 2e",
"short_name": "sr2ini",
"icons": [
{
"src": "android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "gold",
"background_color": "#004aa5",
"display": "standalone"
}

View File

@ -1,12 +1,12 @@
{ {
"name": "sr2ini", "name": "sr2ini",
"version": "0.9.3", "version": "0.9.5",
"description": "Simple Initiative tracker for Shadowrun 2e", "description": "Simple Initiative tracker for Shadowrun 2e",
"private": true, "private": true,
"author": { "author": {
"name": "Eclipse729", "name": "Eclipse",
"email": "eclipse@unterdemradar.de", "email": "eclipse@unterdemradar.de",
"url": "https://git.unterdemradar.de" "url": "https://git.unterdemradar.de/eclipse"
}, },
"homepage": "https://unterdemradar.de/sr2ini/", "homepage": "https://unterdemradar.de/sr2ini/",
"license": "ISC", "license": "ISC",
@ -32,7 +32,7 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git@git.unterdemradar.de:tobias/sr2ini.git" "url": "git@git.unterdemradar.de:eclipse/sr2ini.git"
}, },
"keywords": [ "keywords": [
"Shadowrun", "Shadowrun",
@ -52,10 +52,5 @@
"augmented-ui": "^2.0.0", "augmented-ui": "^2.0.0",
"bootstrap": "^5.2.3", "bootstrap": "^5.2.3",
"jquery": "^3.6.3" "jquery": "^3.6.3"
},
"comments": {
"dependencies": {
"@parcel/service-worker": "^2.8.3"
}
} }
} }

View File

@ -260,6 +260,8 @@ header.navbar {
--aug-border-bottom: 0px; --aug-border-bottom: 0px;
--aug-border-right: 0px; --aug-border-right: 0px;
position: relative;
} }
tr:last-of-type td, tr:last-of-type td,

View File

@ -17,7 +17,7 @@
<link rel="apple-touch-icon" sizes="180x180" href="../icons/apple-touch-icon.png"> <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="32x32" href="../icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="../icons/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="../icons/favicon-16x16.png">
<link rel="manifest" href="../icons/site.webmanifest"> <link rel="manifest" href="site.webmanifest">
<link rel="mask-icon" href="../icons/safari-pinned-tab.svg" color="#004aa5"> <link rel="mask-icon" href="../icons/safari-pinned-tab.svg" color="#004aa5">
<link rel="shortcut icon" href="../icons/favicon.ico"> <link rel="shortcut icon" href="../icons/favicon.ico">
<meta name="msapplication-TileColor" content="#004aa5"> <meta name="msapplication-TileColor" content="#004aa5">
@ -29,7 +29,7 @@
<!-- navbar --> <!-- navbar -->
<div class="container"> <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"> <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> <span id="navbar-title" class="navbar-brand ps-4">SR2 Initiative Tracker</span>
<nav class="container-fluid justify-content-end" aria-label="Main navigation"> <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"><svg viewbox="0 0 512 512"><use href="#add" /></svg> <button type="submit" class="sr2-button" id="add-combatant-button" title="Add combatant" data-bs-toggle="modal" data-bs-target="#combatant-modal"><svg viewbox="0 0 512 512"><use href="#add" /></svg>
</button> </button>
@ -70,8 +70,8 @@
</div> </div>
<div class="modal-footer" data-augmented-ui="inlay"> <div class="modal-footer" data-augmented-ui="inlay">
<button type="button" class="sr2-button" data-bs-dismiss="modal">Cancel</button> <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="button" class="sr2-button" id="confirm-modal-new-round-ok-button" data-bs-dismiss="modal">OK</button>
<button type="submit" class="sr2-button display-none" id="confirm-modal-remove-combatant-ok-button" data-bs-dismiss="modal">OK</button> <button type="button" class="sr2-button display-none" id="confirm-modal-remove-combatant-ok-button" data-bs-dismiss="modal">OK</button>
</div> </div>
</div> </div>
</div> </div>
@ -85,8 +85,8 @@
<h2 class="modal-title">Add New Combatant</h2> <h2 class="modal-title">Add New Combatant</h2>
<button type="button" class="sr2-button" data-bs-dismiss="modal" aria-label="Close">&#10006;</button> <button type="button" class="sr2-button" data-bs-dismiss="modal" aria-label="Close">&#10006;</button>
</div> </div>
<div class="modal-body" data-augmented-ui="inlay">
<form id="combatant-form" name="combatant-modal-form" class="was-validated" onsubmit="return false;"> <form id="combatant-form" name="combatant-modal-form" class="was-validated" onsubmit="return false;">
<div class="modal-body" data-augmented-ui="inlay">
<div> <div>
<input type="text" maxlength="40" class="form-control form-control-sm" id="combatant-modal-name" form="combatant-form" placeholder="Name" required> <input type="text" maxlength="40" class="form-control form-control-sm" id="combatant-modal-name" form="combatant-form" placeholder="Name" required>
</div> </div>
@ -108,14 +108,14 @@
<input type="range" class="form-range" min="0" max="10" value="0" id="combatant-modal-physical" list="damage-level"> <input type="range" class="form-range" min="0" max="10" value="0" id="combatant-modal-physical" list="damage-level">
</div> </div>
</div> </div>
</form>
</div> </div>
<div class="modal-footer" data-augmented-ui="inlay"> <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="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="button" class="sr2-button" id="combatant-modal-add-apply-button">Apply</button>
<button type="submit" class="sr2-button" id="combatant-modal-add-ok-button" >OK</button> <button type="button" class="sr2-button" id="combatant-modal-add-ok-button" data-bs-dismiss="modal">OK</button>
<button type="submit" class="sr2-button display-none" id="combatant-modal-edit-ok-button" >OK</button> <button type="button" class="sr2-button display-none" id="combatant-modal-edit-ok-button" data-bs-dismiss="modal">OK</button>
</div> </div>
</form>
</div> </div>
</div> </div>
</div> </div>
@ -190,7 +190,7 @@
<!-- footer --> <!-- footer -->
<div class="footer-container container"> <div class="footer-container container">
<footer data-augmented-ui="tl-clip br-clip both"> <footer data-augmented-ui="tl-clip br-clip both">
<p><a href="https://git.unterdemradar.de/tobias/sr2ini" tabindex="-1" title="sr2ini">sr2ini</a> | Copyright (C) 2023 by Eclipse729 | background by <a href="https://www.deviantart.com/xxaries1970xx" tabindex="-1" title="xxAries1970xx on DeviantArt">xxAries1970xx</a></p> <p><a href="https://git.unterdemradar.de/eclipse/sr2ini" tabindex="-1" title="sr2ini">sr2ini</a> | Copyright (C) 2022-23 by <a href="https://git.unterdemradar.de/eclipse">Eclipse</a> | background by <a href="https://www.deviantart.com/xxaries1970xx" tabindex="-1" title="xxAries1970xx on DeviantArt">xxAries1970xx</a></p>
</footer> </footer>
</div> </div>
</body> </body>

View File

@ -1,26 +1,9 @@
/* *****************
// Register Service Worker
if (navigator && navigator.serviceWorker) {
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 * import libraries
*/ */
const bs = require("../../node_modules/bootstrap/js/dist/modal.js");
const $ = require("../../node_modules/jquery/dist/jquery.js"); const $ = require("../../node_modules/jquery/dist/jquery.js");
const bs = require("../../node_modules/bootstrap/js/dist/modal.js");
/* /*
@ -30,7 +13,7 @@ const $ = require("../../node_modules/jquery/dist/jquery.js");
const DAMAGE_PENALTY = [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4]; const DAMAGE_PENALTY = [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4];
const DAMAGE_NIVEAU = ["", "L", "M", "S", "D"]; const DAMAGE_NIVEAU = ["", "L", "M", "S", "D"];
const COMBATANT_TABLE_ROW = [ const $COMBATANT_TABLE_ROW = $($.parseHTML([
'<tr class="combatant-row" data-true-ini="">\n', '<tr class="combatant-row" data-true-ini="">\n',
'<td class="combatant-name" title="Combatant\'s name" data-bs-toggle="modal" data-bs-target="#combatant-modal" data-augmented-ui="both"></td>\n', '<td class="combatant-name" title="Combatant\'s name" data-bs-toggle="modal" data-bs-target="#combatant-modal" data-augmented-ui="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-ini" title="Effective initiative (w/ wound penalties)" data-bs-toggle="modal" data-bs-target="#combatant-modal" data-augmented-ui="both"></td>\n',
@ -41,19 +24,6 @@ const COMBATANT_TABLE_ROW = [
'</div>\n', '</div>\n',
'<div class="damage-dropdown">\n', '<div class="damage-dropdown">\n',
'<button type="button" class="sr2-button damage-button" title="Take damage"><svg viewbox="0 0 512 512"><use href="#take-damage" /></svg></button>\n', '<button type="button" class="sr2-button damage-button" title="Take damage"><svg viewbox="0 0 512 512"><use href="#take-damage" /></svg></button>\n',
'</div>\n',
'<div class="actions-dropdown">\n',
'<button type="button" class="sr2-button actions-button" title="More actions"><svg viewbox="0 0 512 512"><use href="#more-actions" /</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 viewbox="0 0 512 512"><use href="#edit" /></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 viewbox="0 0 512 512"><use href="#clone" /></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 viewbox="0 0 512 512"><use href="#delete" /></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', '<div class="damage-monitor" data-augmented-ui="tl-scoop bl-clip-y tr-clip-y br-scoop both">\n',
'<table>\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" 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',
@ -67,16 +37,39 @@ const DAMAGE_MONITOR_HTML = [
'<tr><td><button type="button" class="damage-stun active" tabindex="-1">&nbsp;</button></td><td><button type="button" class="damage-physical active" tabindex="-1">&nbsp;</button></td></tr>\n', '<tr><td><button type="button" class="damage-stun active" tabindex="-1">&nbsp;</button></td><td><button type="button" class="damage-physical active" tabindex="-1">&nbsp;</button></td></tr>\n',
'<tr><td><button type="button" class="damage-stun active" title="K.O." tabindex="-1"><svg viewbox="0 0 512 512"><use href="#ko" /></svg></button></td><td><button type="button" class="damage-physical active" title="Dead" tabindex="-1"><svg viewbox="0 0 512 512"><use href="#dead" /></svg></button></td></tr>\n', '<tr><td><button type="button" class="damage-stun active" title="K.O." tabindex="-1"><svg viewbox="0 0 512 512"><use href="#ko" /></svg></button></td><td><button type="button" class="damage-physical active" title="Dead" tabindex="-1"><svg viewbox="0 0 512 512"><use href="#dead" /></svg></button></td></tr>\n',
'</table>\n', '</table>\n',
'</div>'].join(""); '</div>\n',
'</div>\n',
'<div class="actions-dropdown">\n',
'<button type="button" class="sr2-button more-actions-button" title="More actions"><svg viewbox="0 0 512 512"><use href="#more-actions" /</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 viewbox="0 0 512 512"><use href="#edit" /></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 viewbox="0 0 512 512"><use href="#clone" /></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 viewbox="0 0 512 512"><use href="#delete" /></svg></button>\n',
'</div>\n',
'</div>\n',
'</td>\n',
'</tr>'].join("")));
const STUN_BADGE_HTML = '<sup><span class="badge bg-warning translate-middle stun-badge" title="Stun damage niveau"></span></sup>'; const $STUN_BADGE_HTML = $($.parseHTML('<sup><span class="badge bg-warning translate-middle stun-badge" title="Stun damage niveau"></span></sup>'));
const PHYSICAL_BADGE_HTML = '<sub><span class="badge bg-danger translate-middle physical-badge" title="Physical damage niveau"></span></sub>'; const $PHYSICAL_BADGE_HTML = $($.parseHTML('<sub><span class="badge bg-danger translate-middle physical-badge" title="Physical damage niveau"></span></sub>'));
/* /*
* helper functions * helper class and functions
*/ */
// this class generates unique IDs
// thx to stackoverflow user user1005939 (https://stackoverflow.com/questions/26203453/jquery-generate-unique-ids#comment128736848_33226136)
class IDGenerator {
#id = 0;
get next() { return this.#id++; }
}
// IDGenerator instance that will be used to generate combatant IDs
var idGen = new IDGenerator();
// roll for initiative with the given reaction and number of ini dice // roll for initiative with the given reaction and number of ini dice
function rollForInitiative(dice, rea) { function rollForInitiative(dice, rea) {
if (isNaN(parseInt(dice)) || isNaN(parseInt(rea))) { if (isNaN(parseInt(dice)) || isNaN(parseInt(rea))) {
@ -110,26 +103,59 @@ function whoGoesFirst(a, b) {
// compute a combatant's effective ini value (modified by wound penalties) // compute a combatant's effective ini value (modified by wound penalties)
function getEffectiveIni(tr) { function getEffectiveIni(tr) {
let $tr = $(tr);
// return 0 if combatant is K.O. or dead // return 0 if combatant is K.O. or dead
if ($(tr).hasClass("ko-or-dead")) { if ($tr.hasClass("ko-or-dead")) {
return 0; return -1;
} }
// otherwise compute effective ini (true ini minus wound penalties) // 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]; 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); return Math.max(effectiveIni, 0);
} }
// add test combatant for testing purposes (duh) function getStatsFromCombatantModal() {
function addTestCombatant() { return {
// Eclipse name: $("#combatant-modal-name").val().trim(),
// $("#add-combatant-button").click(); ini: $("#combatant-modal-ini").val().trim(),
$("#combatant-modal-name").val("Eclipse"); dice: $("#combatant-modal-dice").val().trim(),
$("#combatant-modal-dice").val(3); rea: $("#combatant-modal-rea").val().trim(),
$("#combatant-modal-rea").val(6); stun: $("#combatant-modal-stun").val() || "0",
// $("#combatant-modal-ini").val(12); physical: $("#combatant-modal-physical").val() || "0"
addCombatant();
// setTimeout( () => $("#combatant-modal-add-ok-button").click(), 500);
} }
};
function addTestCombatants() {
// Eclipse
$("#combatant-modal-name").val("Eclipse");
$("#combatant-modal-dice").val("3");
$("#combatant-modal-rea").val("6");
$("#combatant-modal-stun").val("0");
$("#combatant-modal-physical").val("0");
handleCombatantModalAddApplyButtonClick();
// Solitaire
$("#combatant-modal-name").val("Solitaire");
$("#combatant-modal-dice").val("3");
$("#combatant-modal-rea").val("17");
$("#combatant-modal-stun").val("0");
$("#combatant-modal-physical").val("0");
handleCombatantModalAddApplyButtonClick();
// Q
$("#combatant-modal-name").val("Q");
$("#combatant-modal-dice").val("2");
$("#combatant-modal-rea").val("9");
$("#combatant-modal-stun").val("0");
$("#combatant-modal-physical").val("0");
handleCombatantModalAddApplyButtonClick();
// Pie
$("#combatant-modal-name").val("Pie");
$("#combatant-modal-dice").val("3");
$("#combatant-modal-rea").val("19");
$("#combatant-modal-stun").val("0");
$("#combatant-modal-physical").val("0");
handleCombatantModalAddApplyButtonClick();
}
/* /*
@ -140,10 +166,9 @@ function addTestCombatant() {
function handleActButtonClick(event) { function handleActButtonClick(event) {
// reduce ini by 10 but not lower than 0 // reduce ini by 10 but not lower than 0
let ini = Math.max(parseInt($(event.target).parents(".combatant-row").attr("data-true-ini")) - 10, 0); let ini = Math.max(parseInt($(event.target).parents(".combatant-row").attr("data-true-ini")) - 10, 0);
// set new ini value
$(event.target).parents(".combatant-row").attr("data-true-ini", ini); $(event.target).parents(".combatant-row").attr("data-true-ini", ini);
// resort table // resort table
sortTable(); updateTable($("#combatants-table"));
} }
// click handler for add button // click handler for add button
@ -152,15 +177,16 @@ function handleAddButtonClick(event) {
$("#combatant-modal .modal-title").text("Add Combatant"); $("#combatant-modal .modal-title").text("Add Combatant");
$("#combatant-modal-add-ok-button, #combatant-modal-add-apply-button").removeClass("display-none"); $("#combatant-modal-add-ok-button, #combatant-modal-add-apply-button").removeClass("display-none");
$("#combatant-modal-edit-ok-button").addClass("display-none"); $("#combatant-modal-edit-ok-button").addClass("display-none");
// set default values // clear values
$("#combatant-modal-stun, #combatant-modal-physical").val("0");
// $("#combatant-modal-name #combatant-modal-ini #combatant-modal-dice #combatant-modal-rea").val("");
// preset values
$("#combatant-modal-name").val("Goon 1"); $("#combatant-modal-name").val("Goon 1");
$("#combatant-modal-dice").val("2"); $("#combatant-modal-dice").val("2");
$("#combatant-modal-rea").val("7"); $("#combatant-modal-rea").val("7");
// set damage sliders to zero
$("#combatant-modal-stun, #combatant-modal-physical").val("0");
// add handler for enter key // add handler for enter key
$("#combatant-modal input[id*='combatant-modal']").off("keydown"); $("#combatant-modal input[id*='combatant-modal']").off("keydown");
$("#combatant-modal input[id*='combatant-modal']").on("keydown", (e) => { $("#combatant-modal input[id*='combatant-modal']").on("keydown", e => {
if (e.which == 13 || e.which == 10) { if (e.which == 13 || e.which == 10) {
handleCombatantModalAddOkButtonClick(e); handleCombatantModalAddOkButtonClick(e);
} }
@ -195,25 +221,55 @@ function handleCloneButtonClick(event) {
// click handler for combatant modal OK button (add mode) // click handler for combatant modal OK button (add mode)
function handleCombatantModalAddOkButtonClick(event) { function handleCombatantModalAddOkButtonClick(event) {
if (validateCombatant()) { // validate
bs.getInstance($("#combatant-modal")).hide(); if ( ! validateCombatant($("#combatant-form").get(0))) {
addCombatant(); return false;
} }
// hide modal
bs.getInstance($("#combatant-modal")).hide();
// everything else can be handled by the apply button handler
handleCombatantModalAddApplyButtonClick(event);
} }
// click handler for combatant modal Apply button (add mode) // click handler for combatant modal Apply button (add mode)
function handleCombatantModalAddApplyButtonClick(event) { function handleCombatantModalAddApplyButtonClick(event) {
if (validateCombatant()) { // validate
addCombatant(); if ( ! validateCombatant($("#combatant-form").get(0))) {
return false;
} }
// prepare new table row
let $tr = $COMBATANT_TABLE_ROW.clone();
$tr.attr("id", idGen.next);
$tr.find(".act-button").on("click", handleActButtonClick);
$tr.find(".damage-button").on("click", handleDamageButtonClick);
$tr.find(".damage-stun, .damage-physical").on("click", handleDamageLevelClick);
$tr.find(".more-actions-button").on("click", handleMoreActionsButtonClick);
$tr.find(".edit-button, .combatant-name, .combatant-ini, .combatant-dice-and-rea").on("click", handleEditButtonClick);
$tr.find(".clone-button").on("click", handleCloneButtonClick);
$tr.find(".remove-button").on("click", handleRemoveButtonClick);
// update table row with stats from modal
$tr = updateCombatantTablerow(getStatsFromCombatantModal(), $tr);
// append row to table
$("#combatants-table").append($tr);
// update (and sort) table
updateTable($("#combatants-table"));
} }
// click handler for combatant modal OK button (edit mode) // click handler for combatant modal OK button (edit mode)
function handleCombatantModalEditOkButtonClick(event) { function handleCombatantModalEditOkButtonClick(event) {
if (validateCombatant()) { // validate
bs.getInstance($("#combatant-modal")).hide(); if ( ! validateCombatant($("#combatant-form").get(0)) ) {
editCombatant(); return false;
} }
// hide modal
bs.getInstance($("#combatant-modal")).hide();
// update table row
let id = $("#combatant-modal").data("row-id"),
$tr = updateCombatantTablerow(getStatsFromCombatantModal(), $(".combatant-row#" + id));
// update table
updateTable($("#combatants-table"));
// clean up
$("#combatant-modal").data("row-id", "");
} }
// click handler for damage buttons; basically toggles visibility of .damage-monitor // click handler for damage buttons; basically toggles visibility of .damage-monitor
@ -222,7 +278,7 @@ function handleDamageButtonClick(event) {
let seenAtClick = $(event.target).parents(".damage-dropdown").find(".damage-monitor").hasClass("seen"); let seenAtClick = $(event.target).parents(".damage-dropdown").find(".damage-monitor").hasClass("seen");
// hide all damage monitors and actions menus // hide all damage monitors and actions menus
$(".damage-monitor.seen, .actions-menu.seen").removeClass("seen").find("button").attr("tabindex", "-1"); $(".damage-monitor.seen, .actions-menu.seen").removeClass("seen").find("button").attr("tabindex", "-1");
// if targeted dm was hidden before, show it now // if targeted damage monitor was hidden before, show it now
if (! seenAtClick) { if (! seenAtClick) {
$(event.target).parents(".damage-dropdown").find(".damage-monitor").addClass("seen").find("button").attr("tabindex", "0"); $(event.target).parents(".damage-dropdown").find(".damage-monitor").addClass("seen").find("button").attr("tabindex", "0");
} }
@ -231,6 +287,7 @@ function handleDamageButtonClick(event) {
// handle click on damage level button in damage monitor and apply damage to combatant // handle click on damage level button in damage monitor and apply damage to combatant
function handleDamageLevelClick(event) { function handleDamageLevelClick(event) {
// find button
let $btn = $(event.target).is("button") ? $(event.target) : $(event.target).closest("button"); let $btn = $(event.target).is("button") ? $(event.target) : $(event.target).closest("button");
// retrieve new damage level and type from button position and "damage-[type]" class // retrieve new damage level and type from button position and "damage-[type]" class
let damageLevel = $btn.parent().parent().index(); let damageLevel = $btn.parent().parent().index();
@ -239,18 +296,21 @@ function handleDamageLevelClick(event) {
} }
let damageType = $btn.attr("class").split(" ").filter(cls => cls.substr(0, 7) == "damage-" ? cls : false).toString().substr(7); let damageType = $btn.attr("class").split(" ").filter(cls => cls.substr(0, 7) == "damage-" ? cls : false).toString().substr(7);
// add damage level to table row as as data attribute // add damage level to table row as as data attribute
$btn.parents("tr.combatant-row").attr("data-damage-" + damageType, damageLevel); $btn.parents(".combatant-row").attr("data-damage-" + damageType, damageLevel);
// select/unselect damage buttons above/below // select/unselect damage buttons above/below
$btn.toggleClass("active"); $btn.toggleClass("active");
$btn.parent().parent().prevAll().find("button.damage-" + damageType).removeClass("active"); $btn.parent().parent().prevAll().find(".damage-" + damageType).removeClass("active");
$btn.parent().parent().nextAll().find("button.damage-" + damageType + ":not(.active)").addClass("active"); $btn.parent().parent().nextAll().find(".damage-" + damageType).addClass("active");
sortTable(); updateTable($("#combatants-table"));
$btn.focus();
} }
// click handler for edit buttons // click handler for edit buttons
function handleEditButtonClick(event) { function handleEditButtonClick(event) {
// find current table row // save ID of row being edited
// 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 only used with JS, not with HTML or CSS
let $tr = $(event.target).parents(".combatant-row"); let $tr = $(event.target).parents(".combatant-row");
$("#combatant-modal").data("row-id", $tr.attr("id"));
// restyle modal // restyle modal
$("#combatant-modal .modal-title").text("Edit Combatant"); $("#combatant-modal .modal-title").text("Edit Combatant");
$("#combatant-modal-edit-ok-button").removeClass("display-none"); $("#combatant-modal-edit-ok-button").removeClass("display-none");
@ -262,8 +322,6 @@ function handleEditButtonClick(event) {
$("#combatant-modal-ini").val($tr.attr("data-true-ini")); $("#combatant-modal-ini").val($tr.attr("data-true-ini"));
$("#combatant-modal-stun").val($tr.attr("data-damage-stun") || "0"); $("#combatant-modal-stun").val($tr.attr("data-damage-stun") || "0");
$("#combatant-modal-physical").val($tr.attr("data-damage-physical") || "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 // add handler for enter key
$("#combatant-modal input[id*='combatant-modal']").off("keydown"); $("#combatant-modal input[id*='combatant-modal']").off("keydown");
$("#combatant-modal input[id*='combatant-modal']").on("keydown", (e) => { $("#combatant-modal input[id*='combatant-modal']").on("keydown", (e) => {
@ -277,14 +335,12 @@ function handleEditButtonClick(event) {
function handleMoreActionsButtonClick(event) { function handleMoreActionsButtonClick(event) {
// get visibility status at click time // get visibility status at click time
let seenAtClick = $(event.target).parents(".actions-dropdown").find(".actions-menu").hasClass("seen"); let seenAtClick = $(event.target).parents(".actions-dropdown").find(".actions-menu").hasClass("seen");
// hide all damage monitors // hide all damage monitors
$(".actions-menu.seen, .damage-monitor.seen").removeClass("seen").find("button").attr("tabindex", "-1"); $(".actions-menu.seen, .damage-monitor.seen").removeClass("seen").find("button").attr("tabindex", "-1");
// if targeted dm was seen before, show it now // if targeted actions menu was seen before, show it now
if (! seenAtClick) { if (! seenAtClick) {
$(event.target).parents(".actions-dropdown").find(".actions-menu").addClass("seen").find("button").attr("tabindex", "0"); $(event.target).parents(".actions-dropdown").find(".actions-menu").addClass("seen").find("button").attr("tabindex", "0");
} }
return false; return false;
} }
@ -306,12 +362,13 @@ function handleNewRoundButtonClick(event) {
// click handler for remove buttons // click handler for remove buttons
function handleRemoveButtonClick(event) { function handleRemoveButtonClick(event) {
// mark which row is being removed
let id = $(event.target).parents(".combatant-row").attr("id");
$("#confirm-modal").data("row-id", id); // again, here it's okay to use jQuery .data() method (see handleEditButtonClick())
// restyle modal // restyle modal
$("#confirm-modal .modal-title").text("Remove Combatant"); $("#confirm-modal .modal-title").text("Remove Combatant");
$("#confirm-modal-remove-combatant-ok-button").removeClass("display-none"); $("#confirm-modal-remove-combatant-ok-button").removeClass("display-none");
$("#confirm-modal-new-round-ok-button").addClass("display-none"); $("#confirm-modal-new-round-ok-button").addClass("display-none");
// mark which row is being removed
$("#confirm-modal").data("row", $(".combatant-row").index($(event.target).parents(".combatant-row"))); // here it's okay to use .data() b/c HTML/CSS does not care about this value
// add handler for enter key // add handler for enter key
$("#confirm-modal").off("keydown"); $("#confirm-modal").off("keydown");
$("#confirm-modal").on("keydown", (e) => { $("#confirm-modal").on("keydown", (e) => {
@ -327,70 +384,31 @@ function handleRemoveButtonClick(event) {
* Main functions * Main functions
*/ */
// add new combatant
function addCombatant() {
// 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);
$tr.find(".damage-stun, .damage-physical").on("click", handleDamageLevelClick);
// append row to table and sort
$("#combatants-table").append($tr);
sortTable();
}
// edit combatant function updateCombatantTablerow(stats, $tr) {
function editCombatant() { // roll for initiative if necessary
// get values let ini = (stats["ini"] == "") ? rollForInitiative(stats["dice"], stats["rea"]) : stats["ini"];
let name = $("#combatant-modal-name").val().trim(); // populate table row with combatant stats
let ini = $("#combatant-modal-ini").val().trim(); $tr.find(".combatant-name").text(stats["name"]);
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"));
let $tr = $("tr.combatant-row").eq(index);
// set new values
$tr.attr("data-true-ini", ini); $tr.attr("data-true-ini", ini);
$tr.find(".combatant-name").text(name); $tr.find(".combatant-dice").attr("data-combatant-dice", stats["dice"]);
$tr.find(".combatant-dice").attr("data-combatant-dice", dice); $tr.find(".combatant-rea").attr("data-combatant-rea", stats["rea"]);
$tr.find(".combatant-rea").attr("data-combatant-rea", rea); // set initial damage levels
$tr.attr("data-damage-stun", $("#combatant-modal-stun").val() || "0"); $tr.attr("data-damage-stun", stats["stun"]);
$tr.find(".damage-stun").addClass("active").slice(0, parseInt($tr.attr("data-damage-stun")) || 0).removeClass("active"); $tr.find(".damage-stun").addClass("active").slice(0, parseInt(stats["stun"])).removeClass("active");
$tr.attr("data-damage-physical", $("#combatant-modal-physical").val() || "0"); $tr.attr("data-damage-physical", stats["physical"]);
$tr.find(".damage-physical").addClass("active").slice(0, parseInt($tr.attr("data-damage-physical")) || 0).removeClass("active"); $tr.find(".damage-physical").addClass("active").slice(0, parseInt(stats["physical"])).removeClass("active");
// sort table // done
sortTable(); return $tr;
// clean up
$("#combatant-modal").data("row", "");
} }
// remove combatant // remove combatant
function removeCombatant() { function removeCombatant() {
// remove correct row // remove correct row
let index = parseInt($("#confirm-modal").data("row")); let id = $("#confirm-modal").data("row-id");
$(".combatant-row").eq(index).remove(); $(".combatant-row#" + id).remove();
sortTable(); // update table
updateTable($("#combatants-table"));
// clean up // clean up
$("#confirm-modal").data("row", ""); $("#confirm-modal").data("row", "");
} }
@ -403,98 +421,94 @@ function startNewRound() {
} }
// reset ini values // reset ini values
$(".combatant-row").each(function () { $(".combatant-row").each(function () {
if ($(this).find(".combatant-dice").attr("data-combatant-dice") == "") { let $this = $(this);
$(this).attr("data-true-ini", 1); if ($this.find(".combatant-dice").attr("data-combatant-dice") == "") {
$this.attr("data-true-ini", 1);
} else { } 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")))); $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 // resort table
sortTable(); updateTable($("#combatants-table"));
} }
// sort combatants by ini value and add contextual classes // update combatants' table's effective inis, contextual classes, and order
function sortTable() { function updateTable(table) {
// do some clean up: remove previous classes from rows, remove effective ini and damage badges let $table = $(table),
$(".combatant-row").removeClass("ko-or-dead max-ini zero-ini"); //REGULAR_INI $rows = $table.find(".combatant-row");
$(".combatant-ini").empty(); // do some clean up: remove contextual classes, remove effective ini and damage badges
$rows.removeClass("ko-or-dead max-ini zero-ini");
$table.find(".combatant-ini").empty();
$table.find(".stun-badge .physical-badge").remove();
// disable all act buttons // disable all act buttons
$(".combatant-row").find(".act-button").prop("disabled", true).attr("aria-disabled", "true"); $table.find(".act-button").prop("disabled", true).attr("aria-disabled", "true");
// mark KO or death with class // mark KO/death with class
$(".combatant-row").each(function() { $rows.each(function(i) {
if (parseInt($(this).attr("data-damage-stun")) == 10 || parseInt($(this).attr("data-damage-physical")) == 10) { let $this = $(this);
$(this).addClass("ko-or-dead"); if (parseInt($this.attr("data-damage-stun")) == 10 || parseInt($this.attr("data-damage-physical")) == 10) {
$this.addClass("ko-or-dead");
} }
}); });
// compute highest effective ini // compute highest effective ini while writing effective ini to each row
let iniMax = Math.max.apply(null, $.map($(".combatant-row"), function (tr, i) { let iniMax = Math.max.apply(null, $.map($rows, (tr, i) => {
// write current effective ini to table row let $tr = $(tr);
$(tr).find(".combatant-ini").text($(tr).hasClass("ko-or-dead") ? 0 : getEffectiveIni($(tr))); $tr.find(".combatant-ini").text($tr.hasClass("ko-or-dead") ? 0 : getEffectiveIni($tr));
return $(tr).find(".combatant-ini").text(); return parseInt($tr.find(".combatant-ini").text());
})); }));
// add damage badges and contextual classes // iterate over rows to add damage badges and contextual classes
$(".combatant-row").each(function () { $rows.each(function(i) {
let $this = $(this);
// damage badges // damage badges
if ($(this).attr("data-damage-stun") && $(this).attr("data-damage-stun") != "0") { if ($this.attr("data-damage-stun") && $this.attr("data-damage-stun") != "0") {
$(this).find(".combatant-ini").append($.parseHTML(STUN_BADGE_HTML)); $this.find(".combatant-ini").append($STUN_BADGE_HTML.clone());
$(this).find(".stun-badge").append(DAMAGE_NIVEAU[DAMAGE_PENALTY[$(this).attr("data-damage-stun")]]); $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") { if ($this.attr("data-damage-physical") && $this.attr("data-damage-physical") != "0") {
$(this).find(".combatant-ini").append($.parseHTML(PHYSICAL_BADGE_HTML)); $this.find(".combatant-ini").append($PHYSICAL_BADGE_HTML.clone());
$(this).find(".physical-badge").append(DAMAGE_NIVEAU[DAMAGE_PENALTY[$(this).attr("data-damage-physical")]]); $this.find(".physical-badge").append(DAMAGE_NIVEAU[DAMAGE_PENALTY[$this.attr("data-damage-physical")]]);
} }
// K.O./dead -> do nothing // K.O./dead -> done
if ($(this).hasClass("ko-or-dead")) { if ($this.hasClass("ko-or-dead")) {
return true; return true;
} }
// ini = zero -> set contextual class // ini = zero -> set class and done
if (parseInt($(this).find(".combatant-ini").text()) == 0) { if (parseInt($this.find(".combatant-ini").text()) == 0) {
$(this).addClass("zero-ini"); $this.addClass("zero-ini");
return true; return true;
} }
// ini = max and non-zero -> enable act-button // ini = max and non-zero -> set class, enable act-button
if (parseInt($(this).find(".combatant-ini").text()) == iniMax && iniMax > 0) { if (parseInt($this.find(".combatant-ini").text()) == iniMax && iniMax > 0) {
$(this).addClass("max-ini").find(".act-button").prop("disabled", false).removeAttr("aria-disabled"); $this.addClass("max-ini").find(".act-button").prop("disabled", false).removeAttr("aria-disabled");
return true; return true;
} }
}) })
// sort rows and append them in new order // sort rows and append them in new order
let $rows = $(".combatant-row").toArray().sort(whoGoesFirst); rows = $rows.toArray().sort(whoGoesFirst);
for (let i = 0; i < $rows.length; i++) { for (let i = 0; i < rows.length; i++) {
$("#combatants-table").append($rows[i]); $table.append($(rows[i]).css("z-index", 50-i));
$($rows[i]).css("z-index", 50-i).css("position", "relative");
} }
return; return $table;
} }
// validate a combatant row form by checking for all conditions, including regular HTML5 validation // validate a combatant row form by checking for all conditions, including regular HTML5 validation
function validateCombatant() { function validateCombatant(form) {
// do standard HTML5 form validation first // do standard HTML5 form validation first
// (makes sure that name is not empty and that all other values are numbers within their individual ranges) // (makes sure that name is not empty and that all other values are numbers within their individual ranges)
if ( ! $("#combatant-form").get(0).reportValidity() ) { if ( ! form.reportValidity() ) {
return false; return false;
} }
// get input elements // get input values
let inputElements = { let ini = $(form).find("#combatant-modal-ini").get(0).value.trim(),
name: $("#combatant-modal-name").get(0), dice = $(form).find("#combatant-modal-dice").get(0).value.trim(),
ini: $("#combatant-modal-ini").get(0), rea = $(form).find("#combatant-modal-rea").get(0).value.trim();
dice: $("#combatant-modal-dice").get(0),
rea: $("#combatant-modal-rea").get(0)
};
// 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 // invalidate if ini, dice and rea are all empty
if (ini == "" && (dice == "" || rea == "")) { if (ini == "" && (dice == "" || rea == "")) {
inputElements["ini"].setCustomValidity("Values required for either initiative, or dice and reaction, or all three"); $(form).find("#combatant-modal-ini").get(0).setCustomValidity("Values required for either initiative, or dice and reaction, or all three");
inputElements["ini"].reportValidity();
return false; return false;
} }
// invalidate if dice or rea is empty but not both // invalidate if dice or rea is empty but not both
if ((dice == "") != (rea == "")) { if ((dice == "") != (rea == "")) {
inputElements["dice"].setCustomValidity("Values required for both dice and reaction, or none (in which case ini is required)"); $(form).find("#combatant-modal-ini").get(0).setCustomValidity("Values required for both dice and reaction, or none (in which case ini is required)");
inputElements["dice"].reportValidity();
return false; return false;
} }
// ok then // ok then
@ -514,16 +528,10 @@ $(document).ready(function () {
$("#combatant-modal-add-apply-button").on("click", handleCombatantModalAddApplyButtonClick); $("#combatant-modal-add-apply-button").on("click", handleCombatantModalAddApplyButtonClick);
$("#combatant-modal-add-ok-button").on("click", handleCombatantModalAddOkButtonClick); $("#combatant-modal-add-ok-button").on("click", handleCombatantModalAddOkButtonClick);
$("#combatant-modal-edit-ok-button").on("click", handleCombatantModalEditOkButtonClick); $("#combatant-modal-edit-ok-button").on("click", handleCombatantModalEditOkButtonClick);
$("#confirm-modal-new-round-ok-button").on("click", () => { $("#confirm-modal-new-round-ok-button").on("click", startNewRound);
startNewRound(); $("#confirm-modal-remove-combatant-ok-button").on("click", removeCombatant);
}); // add event handler removing custom validity messages in combatant modal
$("#confirm-modal-remove-combatant-ok-button").on("click", () => { $("#combatant-modal-dice #combatant-modal-rea #combatant-modal-ini").on("input", (event) => event.target.setCustomValidity(""));
removeCombatant();
});
// add event handler to certain input elements that removes any custom validity message
$("#combatant-modal-dice #combatant-modal-rea #combatant-modal-ini").on("input", (event) => {
event.target.setCustomValidity("");
});
// add event listeners to damage sliders in combatant modal // add event listeners to damage sliders in combatant modal
$("#combatant-modal-stun").on("input change", () => { $("#combatant-modal-stun").on("input change", () => {
if ($("#combatant-modal-stun").val() == "10") { if ($("#combatant-modal-stun").val() == "10") {
@ -544,9 +552,10 @@ $(document).ready(function () {
} }
}); });
// always focus name input field when combatant modal appears // always focus name input field when combatant modal appears
$('#combatant-modal').on('shown.bs.modal', () => $('#combatant-modal-name').focus()); // (need to use vanilla JS b/c jQuery can't seem to attach event handler correctly)
document.getElementById('combatant-modal').addEventListener('shown.bs.modal', () => $('#combatant-modal-name').focus());
// always empty input fields when combatant modal disappears // always empty input fields when combatant modal disappears
$("#combatant-modal").on('hidden.bs.modal', () => $("input[id*='combatant-modal']").val("")); document.getElementById('combatant-modal').addEventListener('hidden.bs.modal', () => $("input[id*='combatant-modal']").val(""));
// Hide damage monitors and actions menus after click somewhere else // Hide damage monitors and actions menus after click somewhere else
$("html").on("click", (e) => { $("html").on("click", (e) => {
if ($(e.target).parents(".damage-monitor").length == 0) { if ($(e.target).parents(".damage-monitor").length == 0) {
@ -556,7 +565,16 @@ $(document).ready(function () {
$(".actions-menu.seen").removeClass("seen"); $(".actions-menu.seen").removeClass("seen");
} }
}); });
addTestCombatant(); // add test combatants when title is held for one second
// thx to stackoverflow user Šime Vidas (https://stackoverflow.com/a/6091129)
$("#navbar-title").mousedown(function(e) {
clearTimeout(this.downTimer);
this.downTimer = setTimeout(function() {
addTestCombatants();
}, 1000);
}).mouseup(function(e) {
clearTimeout(this.downTimer);
});
}); });
module.exports = { rollForInitiative, validateCombatant, whoGoesFirst, getEffectiveIni }; module.exports = { rollForInitiative, validateCombatant, whoGoesFirst, getEffectiveIni };

22
src/site.webmanifest Normal file
View File

@ -0,0 +1,22 @@
{
"name": "sr2ini",
"short_name": "sr2ini",
"description": "A simple Initiative tracker for Shadowrun 2e",
"start_url": ".",
"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": "gold",
"background_color": "#004aa5",
"display": "standalone",
"orientstion": "portrait"
}