@jjlmoya/utils-tabletop 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/entries.ts +11 -1
- package/src/index.ts +1 -0
- package/src/tests/locale_completeness.test.ts +1 -1
- package/src/tests/tool_validation.test.ts +1 -1
- package/src/tool/decision-wheel/audio.ts +78 -0
- package/src/tool/decision-wheel/bibliography.astro +16 -0
- package/src/tool/decision-wheel/bibliography.ts +8 -0
- package/src/tool/decision-wheel/client.ts +226 -0
- package/src/tool/decision-wheel/component.astro +48 -0
- package/src/tool/decision-wheel/components/SegmentEditor.astro +22 -0
- package/src/tool/decision-wheel/components/SpinHistory.astro +17 -0
- package/src/tool/decision-wheel/components/SpinResult.astro +13 -0
- package/src/tool/decision-wheel/components/WheelCanvas.astro +23 -0
- package/src/tool/decision-wheel/decision-wheel.css +632 -0
- package/src/tool/decision-wheel/drawing.ts +144 -0
- package/src/tool/decision-wheel/entry.ts +48 -0
- package/src/tool/decision-wheel/i18n/de.ts +97 -0
- package/src/tool/decision-wheel/i18n/en.ts +251 -0
- package/src/tool/decision-wheel/i18n/es.ts +93 -0
- package/src/tool/decision-wheel/i18n/fr.ts +93 -0
- package/src/tool/decision-wheel/i18n/id.ts +93 -0
- package/src/tool/decision-wheel/i18n/it.ts +93 -0
- package/src/tool/decision-wheel/i18n/ja.ts +93 -0
- package/src/tool/decision-wheel/i18n/ko.ts +93 -0
- package/src/tool/decision-wheel/i18n/nl.ts +93 -0
- package/src/tool/decision-wheel/i18n/pl.ts +93 -0
- package/src/tool/decision-wheel/i18n/pt.ts +93 -0
- package/src/tool/decision-wheel/i18n/ru.ts +75 -0
- package/src/tool/decision-wheel/i18n/sv.ts +93 -0
- package/src/tool/decision-wheel/i18n/tr.ts +93 -0
- package/src/tool/decision-wheel/i18n/zh.ts +93 -0
- package/src/tool/decision-wheel/index.ts +9 -0
- package/src/tool/decision-wheel/logic.test.ts +163 -0
- package/src/tool/decision-wheel/logic.ts +145 -0
- package/src/tool/decision-wheel/seo.astro +16 -0
- package/src/tool/decision-wheel/types.ts +14 -0
- package/src/tool/decision-wheel/ui.ts +121 -0
- package/src/tool/fantasy-runes-translator/bibliography.astro +16 -0
- package/src/tool/fantasy-runes-translator/bibliography.ts +16 -0
- package/src/tool/fantasy-runes-translator/client.ts +205 -0
- package/src/tool/fantasy-runes-translator/component.astro +95 -0
- package/src/tool/fantasy-runes-translator/entry.ts +56 -0
- package/src/tool/fantasy-runes-translator/fantasy-runes-translator.css +712 -0
- package/src/tool/fantasy-runes-translator/i18n/de.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/en.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/es.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/fr.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/id.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/it.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/ja.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/ko.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/nl.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/pl.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/pt.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/ru.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/sv.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/tr.ts +139 -0
- package/src/tool/fantasy-runes-translator/i18n/zh.ts +139 -0
- package/src/tool/fantasy-runes-translator/index.ts +9 -0
- package/src/tool/fantasy-runes-translator/logic.ts +174 -0
- package/src/tool/fantasy-runes-translator/seo.astro +16 -0
- package/src/tool/fantasy-runes-translator/types.ts +26 -0
- package/src/tool/rpg-initiative-tracker/bibliography.astro +16 -0
- package/src/tool/rpg-initiative-tracker/bibliography.ts +12 -0
- package/src/tool/rpg-initiative-tracker/client.ts +201 -0
- package/src/tool/rpg-initiative-tracker/component.astro +23 -0
- package/src/tool/rpg-initiative-tracker/components/AddForm.astro +49 -0
- package/src/tool/rpg-initiative-tracker/components/CombatantList.astro +16 -0
- package/src/tool/rpg-initiative-tracker/components/TurnControls.astro +28 -0
- package/src/tool/rpg-initiative-tracker/entry.ts +50 -0
- package/src/tool/rpg-initiative-tracker/i18n/de.ts +266 -0
- package/src/tool/rpg-initiative-tracker/i18n/en.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/es.ts +264 -0
- package/src/tool/rpg-initiative-tracker/i18n/fr.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/id.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/it.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/ja.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/ko.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/nl.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/pl.ts +264 -0
- package/src/tool/rpg-initiative-tracker/i18n/pt.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/ru.ts +264 -0
- package/src/tool/rpg-initiative-tracker/i18n/sv.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/tr.ts +263 -0
- package/src/tool/rpg-initiative-tracker/i18n/zh.ts +263 -0
- package/src/tool/rpg-initiative-tracker/index.ts +9 -0
- package/src/tool/rpg-initiative-tracker/logic.test.ts +121 -0
- package/src/tool/rpg-initiative-tracker/logic.ts +122 -0
- package/src/tool/rpg-initiative-tracker/rpg-initiative-tracker.css +617 -0
- package/src/tool/rpg-initiative-tracker/seo.astro +16 -0
- package/src/tool/rpg-initiative-tracker/types.ts +15 -0
- package/src/tool/score-tracker/bibliography.astro +16 -0
- package/src/tool/score-tracker/bibliography.ts +12 -0
- package/src/tool/score-tracker/client.ts +248 -0
- package/src/tool/score-tracker/component.astro +34 -0
- package/src/tool/score-tracker/components/Leaderboard.astro +19 -0
- package/src/tool/score-tracker/components/PlayerSetup.astro +32 -0
- package/src/tool/score-tracker/components/RoundScoring.astro +22 -0
- package/src/tool/score-tracker/components/ScoreHistory.astro +25 -0
- package/src/tool/score-tracker/entry.ts +57 -0
- package/src/tool/score-tracker/i18n/de.ts +278 -0
- package/src/tool/score-tracker/i18n/en.ts +278 -0
- package/src/tool/score-tracker/i18n/es.ts +251 -0
- package/src/tool/score-tracker/i18n/fr.ts +278 -0
- package/src/tool/score-tracker/i18n/id.ts +278 -0
- package/src/tool/score-tracker/i18n/it.ts +278 -0
- package/src/tool/score-tracker/i18n/ja.ts +278 -0
- package/src/tool/score-tracker/i18n/ko.ts +278 -0
- package/src/tool/score-tracker/i18n/nl.ts +278 -0
- package/src/tool/score-tracker/i18n/pl.ts +278 -0
- package/src/tool/score-tracker/i18n/pt.ts +278 -0
- package/src/tool/score-tracker/i18n/ru.ts +278 -0
- package/src/tool/score-tracker/i18n/sv.ts +278 -0
- package/src/tool/score-tracker/i18n/tr.ts +278 -0
- package/src/tool/score-tracker/i18n/zh.ts +278 -0
- package/src/tool/score-tracker/index.ts +9 -0
- package/src/tool/score-tracker/logic.test.ts +83 -0
- package/src/tool/score-tracker/logic.ts +122 -0
- package/src/tool/score-tracker/score-tracker.css +1029 -0
- package/src/tool/score-tracker/seo.astro +16 -0
- package/src/tool/score-tracker/types.ts +17 -0
- package/src/tools.ts +8 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface RuneCharacter {
|
|
2
|
+
symbol: string;
|
|
3
|
+
name: string;
|
|
4
|
+
meaning: string;
|
|
5
|
+
phonetic: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type RuneAlphabetKey = 'elder-futhark' | 'anglo-saxon' | 'fantasy' | 'theban' | 'enochian' | 'daemonic' | 'ogham';
|
|
9
|
+
|
|
10
|
+
export interface RuneAlphabet {
|
|
11
|
+
key: RuneAlphabetKey;
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
mapping: Record<string, RuneCharacter>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RuneCharacterResult {
|
|
18
|
+
input: string;
|
|
19
|
+
char: RuneCharacter | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RuneTranslationResult {
|
|
23
|
+
alphabetKey: RuneAlphabetKey;
|
|
24
|
+
characters: RuneCharacterResult[];
|
|
25
|
+
raw: string;
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { initiativeTracker } from './index';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'en' } = Astro.props as Props;
|
|
11
|
+
const loader = initiativeTracker.i18n[locale] || initiativeTracker.i18n.en;
|
|
12
|
+
const content = await loader?.();
|
|
13
|
+
if (!content) return null;
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
{content && <SharedBibliography links={content.bibliography} />}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BibliographyEntry } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const bibliography: BibliographyEntry[] = [
|
|
4
|
+
{
|
|
5
|
+
name: 'Dungeons & Dragons Basic Rules',
|
|
6
|
+
url: 'https://media.wizards.com/2018/dnd/downloads/DnD_BasicRules_2018.pdf',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: 'Pathfinder Initiative Rules',
|
|
10
|
+
url: 'https://2e.aonprd.com/Rules.aspx?ID=2539',
|
|
11
|
+
},
|
|
12
|
+
];
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { addCombatant, removeCombatant, toggleStatus, startCombat, nextTurn, prevTurn, endCombat, createBlankState } from './logic';
|
|
2
|
+
import type { InitiativeState } from './types';
|
|
3
|
+
|
|
4
|
+
const STORAGE_KEY = 'initiative-tracker-state';
|
|
5
|
+
|
|
6
|
+
const wrapper = document.querySelector('.init-main-wrapper') as HTMLElement;
|
|
7
|
+
if (!wrapper) throw new Error('Initiative tracker wrapper not found');
|
|
8
|
+
|
|
9
|
+
const ui: Record<string, string> = JSON.parse(wrapper.dataset.ui || '{}');
|
|
10
|
+
|
|
11
|
+
function saveState(): void {
|
|
12
|
+
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch { }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function loadState(): InitiativeState {
|
|
16
|
+
try {
|
|
17
|
+
const s = localStorage.getItem(STORAGE_KEY);
|
|
18
|
+
if (s) return JSON.parse(s) as InitiativeState;
|
|
19
|
+
} catch { }
|
|
20
|
+
return createBlankState();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let state: InitiativeState = loadState();
|
|
24
|
+
|
|
25
|
+
const $ = (id: string) => document.getElementById(id) as HTMLElement;
|
|
26
|
+
const combatantList = $('init-combatant-list');
|
|
27
|
+
const emptyState = $('init-empty-state');
|
|
28
|
+
const nameInput = $('init-name-input') as HTMLInputElement;
|
|
29
|
+
const initInput = $('init-value-input') as HTMLInputElement;
|
|
30
|
+
const modInput = $('init-mod-input') as HTMLInputElement;
|
|
31
|
+
const addBtn = $('init-add-btn');
|
|
32
|
+
const startBtn = $('init-start-btn');
|
|
33
|
+
const endBtn = $('init-end-btn');
|
|
34
|
+
const nextBtn = $('init-next-btn');
|
|
35
|
+
const prevBtn = $('init-prev-btn');
|
|
36
|
+
const roundLabel = $('init-round-label');
|
|
37
|
+
|
|
38
|
+
let currentType: 'player' | 'npc' = 'player';
|
|
39
|
+
|
|
40
|
+
function renderCombatantList(): void {
|
|
41
|
+
if (state.combatants.length === 0) {
|
|
42
|
+
combatantList.innerHTML = '';
|
|
43
|
+
combatantList.appendChild(emptyState);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const items = state.combatants.map((c, i) => {
|
|
47
|
+
const isActive = state.isCombatActive && i === state.currentIndex;
|
|
48
|
+
const typeClass = c.isPlayer ? 'player-order' : 'npc-order';
|
|
49
|
+
const activeClass = isActive ? ' active' : '';
|
|
50
|
+
const npcClass = c.isPlayer ? '' : ' npc';
|
|
51
|
+
const orderClass = isActive ? ' active-order' : ' ' + typeClass;
|
|
52
|
+
const statusHtml = c.statuses.map(s => {
|
|
53
|
+
return `<span class="init-status-tag active" data-status="${s}">${s}</span>`;
|
|
54
|
+
}).join('');
|
|
55
|
+
return `<div class="init-combatant-item${activeClass}${npcClass}" data-cbt-id="${c.id}"><span class="init-cbt-order${orderClass}">${i + 1}</span><span class="init-cbt-name">${c.name}</span><span class="init-cbt-init">${c.initiative}</span><div class="init-cbt-statuses">${statusHtml}</div><div class="init-cbt-actions"><button type="button" class="init-btn-icon remove" data-action="remove" title="${ui.removeCombatant}"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button></div></div>`;
|
|
56
|
+
}).join('');
|
|
57
|
+
combatantList.innerHTML = items;
|
|
58
|
+
combatantList.querySelectorAll('[data-action="remove"]').forEach(b => b.addEventListener('click', onRemove));
|
|
59
|
+
combatantList.querySelectorAll('.init-status-tag').forEach(t => {
|
|
60
|
+
t.addEventListener('click', onToggleStatus);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function onRemove(e: Event): void {
|
|
65
|
+
const item = (e.currentTarget as HTMLElement).closest('.init-combatant-item') as HTMLElement;
|
|
66
|
+
const id = item?.dataset.cbtId;
|
|
67
|
+
if (id) {
|
|
68
|
+
state = removeCombatant(state, id);
|
|
69
|
+
renderAll();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function onToggleStatus(e: Event): void {
|
|
74
|
+
const tag = e.currentTarget as HTMLElement;
|
|
75
|
+
const item = tag.closest('.init-combatant-item') as HTMLElement;
|
|
76
|
+
const id = item?.dataset.cbtId;
|
|
77
|
+
const status = tag.dataset.status;
|
|
78
|
+
if (id && status) {
|
|
79
|
+
state = toggleStatus(state, id, status);
|
|
80
|
+
renderAll();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function spawnTurnParticle(text: string): void {
|
|
85
|
+
const el = document.createElement('div');
|
|
86
|
+
el.className = 'init-turn-particle';
|
|
87
|
+
el.textContent = text;
|
|
88
|
+
el.style.cssText = 'left:50%;top:50%;position:fixed';
|
|
89
|
+
document.body.appendChild(el);
|
|
90
|
+
setTimeout(() => el.remove(), 1000);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function longPress(el: HTMLElement, duration: number, cb: () => void): void {
|
|
94
|
+
let timer: number | null = null;
|
|
95
|
+
const start = (): void => {
|
|
96
|
+
el.style.setProperty('--lp-dur', duration + 'ms');
|
|
97
|
+
el.classList.add('lp-active');
|
|
98
|
+
timer = window.setTimeout(() => {
|
|
99
|
+
el.classList.remove('lp-active');
|
|
100
|
+
cb();
|
|
101
|
+
}, duration);
|
|
102
|
+
};
|
|
103
|
+
const cancel = (): void => {
|
|
104
|
+
if (timer !== null) {
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
timer = null;
|
|
107
|
+
}
|
|
108
|
+
el.classList.remove('lp-active');
|
|
109
|
+
};
|
|
110
|
+
el.addEventListener('mousedown', start);
|
|
111
|
+
el.addEventListener('mouseup', cancel);
|
|
112
|
+
el.addEventListener('mouseleave', cancel);
|
|
113
|
+
el.addEventListener('touchstart', start);
|
|
114
|
+
el.addEventListener('touchend', cancel);
|
|
115
|
+
el.addEventListener('touchcancel', cancel);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function getActiveCombatantName(): string {
|
|
119
|
+
const c = state.combatants[state.currentIndex];
|
|
120
|
+
return c ? c.name : '';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function renderAll(): void {
|
|
124
|
+
renderCombatantList();
|
|
125
|
+
const hasCombatants = state.combatants.length > 0;
|
|
126
|
+
const inCombat = state.isCombatActive;
|
|
127
|
+
|
|
128
|
+
if (inCombat && hasCombatants) {
|
|
129
|
+
roundLabel.textContent = ui.roundLabel + ' ' + state.round + ' - ' + ui.turnLabel + ' ' + (state.currentIndex + 1);
|
|
130
|
+
} else {
|
|
131
|
+
roundLabel.textContent = ui.roundLabel + ': --';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
startBtn.style.display = inCombat ? 'none' : 'inline-block';
|
|
135
|
+
startBtn.disabled = !hasCombatants;
|
|
136
|
+
endBtn.style.display = inCombat ? 'inline-block' : 'none';
|
|
137
|
+
endBtn.disabled = !inCombat;
|
|
138
|
+
nextBtn.disabled = !inCombat;
|
|
139
|
+
prevBtn.disabled = !inCombat;
|
|
140
|
+
|
|
141
|
+
saveState();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
addBtn.addEventListener('click', () => {
|
|
145
|
+
const name = nameInput.value.trim();
|
|
146
|
+
const initiative = parseInt(initInput.value, 10);
|
|
147
|
+
const modifier = parseInt(modInput.value, 10) || 0;
|
|
148
|
+
if (!name || isNaN(initiative)) return;
|
|
149
|
+
state = addCombatant(state, name, initiative, { modifier, isPlayer: currentType === 'player' });
|
|
150
|
+
nameInput.value = '';
|
|
151
|
+
initInput.value = '';
|
|
152
|
+
modInput.value = '';
|
|
153
|
+
nameInput.focus();
|
|
154
|
+
renderAll();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
initInput.addEventListener('keydown', (e) => {
|
|
158
|
+
if (e.key === 'Enter') addBtn.click();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
nameInput.addEventListener('keydown', (e) => {
|
|
162
|
+
if (e.key === 'Enter') initInput.focus();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const toggleBtns = document.querySelectorAll('.init-toggle-btn');
|
|
166
|
+
toggleBtns.forEach(btn => {
|
|
167
|
+
btn.addEventListener('click', () => {
|
|
168
|
+
toggleBtns.forEach(b => b.classList.remove('active'));
|
|
169
|
+
btn.classList.add('active');
|
|
170
|
+
currentType = btn.dataset.type as 'player' | 'npc';
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
startBtn.addEventListener('click', () => {
|
|
175
|
+
state = startCombat(state);
|
|
176
|
+
const name = getActiveCombatantName();
|
|
177
|
+
if (name) spawnTurnParticle(name + ' ' + ui.yourTurn);
|
|
178
|
+
renderAll();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
nextBtn.addEventListener('click', () => {
|
|
182
|
+
state = nextTurn(state);
|
|
183
|
+
const name = getActiveCombatantName();
|
|
184
|
+
if (name) spawnTurnParticle(name + ' ' + ui.yourTurn);
|
|
185
|
+
renderAll();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
prevBtn.addEventListener('click', () => {
|
|
189
|
+
state = prevTurn(state);
|
|
190
|
+
const name = getActiveCombatantName();
|
|
191
|
+
if (name) spawnTurnParticle(name + ' ' + ui.yourTurn);
|
|
192
|
+
renderAll();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
longPress(endBtn, 1000, () => {
|
|
196
|
+
state = endCombat(state);
|
|
197
|
+
spawnTurnParticle(ui.combatEnded);
|
|
198
|
+
renderAll();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
renderAll();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
import './rpg-initiative-tracker.css';
|
|
3
|
+
import AddForm from './components/AddForm.astro';
|
|
4
|
+
import CombatantList from './components/CombatantList.astro';
|
|
5
|
+
import TurnControls from './components/TurnControls.astro';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
ui: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { ui } = Astro.props;
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
<div class="init-main-wrapper" data-ui={JSON.stringify(ui)}>
|
|
15
|
+
<div class="init-header">
|
|
16
|
+
<h2>{ui.title}</h2>
|
|
17
|
+
</div>
|
|
18
|
+
<AddForm labels={ui} />
|
|
19
|
+
<CombatantList labels={ui} />
|
|
20
|
+
<TurnControls labels={ui} />
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<script src="./client.ts"></script>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
labels: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const { labels } = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div class="init-add-form" id="init-add-form">
|
|
10
|
+
<div class="init-form-group">
|
|
11
|
+
<label for="init-name-input">{labels.namePlaceholder}</label>
|
|
12
|
+
<input
|
|
13
|
+
type="text"
|
|
14
|
+
class="init-input"
|
|
15
|
+
id="init-name-input"
|
|
16
|
+
placeholder={labels.namePlaceholder}
|
|
17
|
+
autocomplete="off"
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="init-form-group">
|
|
21
|
+
<label for="init-value-input">{labels.initiativeLabel}</label>
|
|
22
|
+
<input
|
|
23
|
+
type="number"
|
|
24
|
+
class="init-input"
|
|
25
|
+
id="init-value-input"
|
|
26
|
+
placeholder={labels.initiativePlaceholder}
|
|
27
|
+
/>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="init-form-group">
|
|
30
|
+
<label for="init-mod-input">{labels.modifierLabel}</label>
|
|
31
|
+
<input
|
|
32
|
+
type="number"
|
|
33
|
+
class="init-input"
|
|
34
|
+
id="init-mod-input"
|
|
35
|
+
placeholder={labels.modifierPlaceholder}
|
|
36
|
+
/>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="init-toggle-group" id="init-type-toggle">
|
|
39
|
+
<button type="button" class="init-toggle-btn player active" data-type="player">
|
|
40
|
+
{labels.playerLabel}
|
|
41
|
+
</button>
|
|
42
|
+
<button type="button" class="init-toggle-btn npc" data-type="npc">
|
|
43
|
+
{labels.npcLabel}
|
|
44
|
+
</button>
|
|
45
|
+
</div>
|
|
46
|
+
<button type="button" class="init-btn-add" id="init-add-btn">
|
|
47
|
+
{labels.addButton}
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
labels: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const { labels } = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div id="init-combatant-list" class="init-combatant-list">
|
|
10
|
+
<div id="init-empty-state" class="init-empty-state">
|
|
11
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
12
|
+
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
|
|
13
|
+
</svg>
|
|
14
|
+
<span>{labels.noCombatants}</span>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
labels: Record<string, string>;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const { labels } = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<div class="init-round-display" id="init-round-display">
|
|
10
|
+
<span class="init-round-number" id="init-round-label">
|
|
11
|
+
{labels.roundLabel}: --
|
|
12
|
+
</span>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<div class="init-turn-controls" id="init-turn-controls">
|
|
16
|
+
<button type="button" class="init-turn-btn primary" id="init-start-btn">
|
|
17
|
+
{labels.startCombat}
|
|
18
|
+
</button>
|
|
19
|
+
<button type="button" class="init-turn-btn" id="init-prev-btn" disabled>
|
|
20
|
+
{labels.prevTurn}
|
|
21
|
+
</button>
|
|
22
|
+
<button type="button" class="init-turn-btn" id="init-next-btn" disabled>
|
|
23
|
+
{labels.nextTurn}
|
|
24
|
+
</button>
|
|
25
|
+
<button type="button" class="init-turn-btn danger" id="init-end-btn" disabled>
|
|
26
|
+
{labels.endCombat}
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { TabletopToolEntry, ToolLocaleContent } from '../../types';
|
|
2
|
+
|
|
3
|
+
export type InitiativeTrackerUI = {
|
|
4
|
+
title: string;
|
|
5
|
+
addCombatant: string;
|
|
6
|
+
removeCombatant: string;
|
|
7
|
+
playerLabel: string;
|
|
8
|
+
npcLabel: string;
|
|
9
|
+
initiativeLabel: string;
|
|
10
|
+
modifierLabel: string;
|
|
11
|
+
namePlaceholder: string;
|
|
12
|
+
initiativePlaceholder: string;
|
|
13
|
+
modifierPlaceholder: string;
|
|
14
|
+
addButton: string;
|
|
15
|
+
startCombat: string;
|
|
16
|
+
endCombat: string;
|
|
17
|
+
nextTurn: string;
|
|
18
|
+
prevTurn: string;
|
|
19
|
+
roundLabel: string;
|
|
20
|
+
turnLabel: string;
|
|
21
|
+
noCombatants: string;
|
|
22
|
+
statusInput: string;
|
|
23
|
+
combatStarted: string;
|
|
24
|
+
combatEnded: string;
|
|
25
|
+
yourTurn: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type InitiativeTrackerLocaleContent = ToolLocaleContent<InitiativeTrackerUI>;
|
|
29
|
+
|
|
30
|
+
export const initiativeTracker: TabletopToolEntry<InitiativeTrackerUI> = {
|
|
31
|
+
id: 'rpg-initiative-tracker',
|
|
32
|
+
icons: { bg: 'mdi:sword-cross', fg: 'mdi:sword-cross' },
|
|
33
|
+
i18n: {
|
|
34
|
+
de: () => import('./i18n/de').then((m) => m.content),
|
|
35
|
+
en: () => import('./i18n/en').then((m) => m.content),
|
|
36
|
+
es: () => import('./i18n/es').then((m) => m.content),
|
|
37
|
+
fr: () => import('./i18n/fr').then((m) => m.content),
|
|
38
|
+
id: () => import('./i18n/id').then((m) => m.content),
|
|
39
|
+
it: () => import('./i18n/it').then((m) => m.content),
|
|
40
|
+
ja: () => import('./i18n/ja').then((m) => m.content),
|
|
41
|
+
ko: () => import('./i18n/ko').then((m) => m.content),
|
|
42
|
+
nl: () => import('./i18n/nl').then((m) => m.content),
|
|
43
|
+
pl: () => import('./i18n/pl').then((m) => m.content),
|
|
44
|
+
pt: () => import('./i18n/pt').then((m) => m.content),
|
|
45
|
+
ru: () => import('./i18n/ru').then((m) => m.content),
|
|
46
|
+
sv: () => import('./i18n/sv').then((m) => m.content),
|
|
47
|
+
tr: () => import('./i18n/tr').then((m) => m.content),
|
|
48
|
+
zh: () => import('./i18n/zh').then((m) => m.content),
|
|
49
|
+
},
|
|
50
|
+
};
|