@jjlmoya/utils-tabletop 1.34.0 → 1.36.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/category/index.ts +3 -1
- package/src/entries.ts +2 -0
- package/src/index.ts +1 -0
- package/src/tests/locale_completeness.test.ts +1 -1
- package/src/tests/qa-test-helpers.ts +32 -0
- package/src/tests/qa_bibliography_links.test.ts +46 -0
- package/src/tests/qa_claim_evidence.test.ts +69 -0
- package/src/tests/qa_logic_reference_coverage.test.ts +46 -0
- package/src/tests/qa_runtime_i18n.test.ts +100 -0
- package/src/tests/registry_contract.test.ts +67 -0
- package/src/tests/tool_validation.test.ts +1 -1
- package/src/tests/translation_copy.test.ts +0 -1
- package/src/tool/board-game-timer/i18n/en.ts +1 -2
- package/src/tool/board-game-timer/i18n/es.ts +2 -2
- package/src/tool/card-draw-odds-calculator/bibliography.astro +16 -0
- package/src/tool/card-draw-odds-calculator/bibliography.ts +7 -0
- package/src/tool/card-draw-odds-calculator/card-draw-odds-calculator.css +382 -0
- package/src/tool/card-draw-odds-calculator/component.astro +89 -0
- package/src/tool/card-draw-odds-calculator/controller.ts +118 -0
- package/src/tool/card-draw-odds-calculator/dom-views.ts +90 -0
- package/src/tool/card-draw-odds-calculator/entry.ts +26 -0
- package/src/tool/card-draw-odds-calculator/evaluator.ts +30 -0
- package/src/tool/card-draw-odds-calculator/i18n/de.ts +48 -0
- package/src/tool/card-draw-odds-calculator/i18n/en.ts +76 -0
- package/src/tool/card-draw-odds-calculator/i18n/es.ts +48 -0
- package/src/tool/card-draw-odds-calculator/i18n/fr.ts +48 -0
- package/src/tool/card-draw-odds-calculator/i18n/id.ts +27 -0
- package/src/tool/card-draw-odds-calculator/i18n/it.ts +15 -0
- package/src/tool/card-draw-odds-calculator/i18n/ja.ts +15 -0
- package/src/tool/card-draw-odds-calculator/i18n/ko.ts +15 -0
- package/src/tool/card-draw-odds-calculator/i18n/nl.ts +15 -0
- package/src/tool/card-draw-odds-calculator/i18n/pl.ts +15 -0
- package/src/tool/card-draw-odds-calculator/i18n/pt.ts +15 -0
- package/src/tool/card-draw-odds-calculator/i18n/ru.ts +15 -0
- package/src/tool/card-draw-odds-calculator/i18n/sv.ts +15 -0
- package/src/tool/card-draw-odds-calculator/i18n/tr.ts +15 -0
- package/src/tool/card-draw-odds-calculator/i18n/zh.ts +15 -0
- package/src/tool/card-draw-odds-calculator/index.ts +11 -0
- package/src/tool/card-draw-odds-calculator/logic.test.ts +50 -0
- package/src/tool/card-draw-odds-calculator/logic.ts +100 -0
- package/src/tool/card-draw-odds-calculator/seo.astro +16 -0
- package/src/tool/card-draw-odds-calculator/storage.ts +22 -0
- package/src/tool/card-draw-odds-calculator/ui.ts +37 -0
- package/src/tool/dice-roller-simulator/i18n/es.ts +3 -3
- package/src/tool/dungeon-map-generator/component.astro +14 -10
- package/src/tool/dungeon-map-generator/dom-views.ts +5 -4
- package/src/tool/dungeon-map-generator/i18n/es.ts +2 -2
- package/src/tool/encounter-difficulty-calculator/dom-views.ts +8 -1
- package/src/tool/fantasy-runes-translator/logic.test.ts +32 -0
- package/src/tool/first-player-selector/i18n/en.ts +1 -1
- package/src/tool/first-player-selector/i18n/es.ts +3 -3
- package/src/tool/mus-scoreboard/component.astro +3 -3
- package/src/tool/mus-scoreboard/controller.ts +2 -1
- package/src/tool/mus-scoreboard/dom-views.ts +6 -1
- package/src/tool/rpg-settlement-exploration-map-generator/component.astro +14 -3
- package/src/tool/rpg-settlement-exploration-map-generator/editor.ts +38 -6
- package/src/tools.ts +2 -0
|
@@ -9,6 +9,15 @@ const { ui } = Astro.props;
|
|
|
9
9
|
const initialMap = generateSettlement(DEFAULT_CONFIG);
|
|
10
10
|
const initialEvaluation = evaluateSettlement(initialMap, ui);
|
|
11
11
|
const builtInServices = ['tavern', 'smithy', 'temple', 'market', 'stable', 'hall'] as const;
|
|
12
|
+
const styleOptions = [
|
|
13
|
+
{ value: 'medieval', label: ui.styleMedieval },
|
|
14
|
+
{ value: 'edo', label: ui.styleEdo },
|
|
15
|
+
{ value: 'sahelian', label: ui.styleSahelian },
|
|
16
|
+
{ value: 'timber', label: ui.styleTimber },
|
|
17
|
+
{ value: 'stone', label: ui.styleStone },
|
|
18
|
+
{ value: 'coastal', label: ui.styleCoastal },
|
|
19
|
+
{ value: 'highland', label: ui.styleHighland },
|
|
20
|
+
] as const;
|
|
12
21
|
---
|
|
13
22
|
<div class="rsm" data-settlement-tool>
|
|
14
23
|
<script is:inline type="application/json" data-ui set:html={JSON.stringify(ui)} />
|
|
@@ -28,7 +37,7 @@ const builtInServices = ['tavern', 'smithy', 'temple', 'market', 'stable', 'hall
|
|
|
28
37
|
<div class="rsm-field">
|
|
29
38
|
<span class="rsm-label">{ui.styleLabel}</span>
|
|
30
39
|
<small>{ui.styleHint}</small>
|
|
31
|
-
<div class="rsm-choice-row" data-styles>{
|
|
40
|
+
<div class="rsm-choice-row" data-styles>{styleOptions.map((option) => <button type="button" class:list={{ 'rsm-choice': true, 'is-active': option.value === DEFAULT_CONFIG.style }} data-style={option.value} aria-pressed={option.value === DEFAULT_CONFIG.style}>{option.label}</button>)}</div>
|
|
32
41
|
</div>
|
|
33
42
|
<div class="rsm-field">
|
|
34
43
|
<span class="rsm-label">{ui.sizeLabel}</span>
|
|
@@ -46,15 +55,17 @@ const builtInServices = ['tavern', 'smithy', 'temple', 'market', 'stable', 'hall
|
|
|
46
55
|
<fieldset class="rsm-field rsm-services">
|
|
47
56
|
<legend>{ui.servicesLabel}</legend>
|
|
48
57
|
<small>{ui.serviceListHint}</small>
|
|
49
|
-
<div class="rsm-service-row" data-service-list>{builtInServices.map((service) => <div class="rsm-service-entry" data-service-entry={service}><label class="rsm-service-chip"><input type="checkbox" value={service} data-service-option checked={DEFAULT_CONFIG.services.includes(service)} /><span>{ui[`service${service.charAt(0).toUpperCase()}${service.slice(1)}`]}</span></label></div>)}</div>
|
|
58
|
+
<div class="rsm-service-row" data-service-list aria-label={ui.serviceSelectLabel}>{builtInServices.map((service) => <div class="rsm-service-entry" data-service-entry={service}><label class="rsm-service-chip"><input type="checkbox" value={service} data-service-option checked={DEFAULT_CONFIG.services.includes(service)} /><span>{ui[`service${service.charAt(0).toUpperCase()}${service.slice(1)}`]}</span></label></div>)}</div>
|
|
50
59
|
<div class="rsm-add-service"><input id="rsm-new-service" type="text" data-new-service aria-label={ui.newServicePlaceholder} placeholder={ui.newServicePlaceholder} maxlength="28" /><button type="button" data-add-service>{ui.addService}</button></div>
|
|
51
60
|
</fieldset>
|
|
52
61
|
<button type="button" class="rsm-generate" data-generate>{ui.generate}</button>
|
|
53
62
|
</div>
|
|
54
63
|
<div class="rsm-map-column">
|
|
55
64
|
<div class="rsm-map-heading"><div><span class="rsm-eyebrow">{ui.mapSummary}</span><strong data-terrain>{initialEvaluation.terrainLabel}</strong></div><span class="rsm-badge" data-badge>{initialEvaluation.badge}</span></div>
|
|
56
|
-
<div class="rsm-map-
|
|
65
|
+
<div class="rsm-map-tools" aria-label={ui.editLabel}><span class="rsm-label">{ui.editLabel}</span><div class="rsm-tool-group"><button type="button" class="rsm-tool is-active" data-tool="select" aria-pressed="true">{ui.toolSelect}</button><button type="button" class="rsm-tool" data-tool="building" aria-pressed="false">{ui.toolBuilding}</button><button type="button" class="rsm-tool" data-tool="path" aria-pressed="false">{ui.toolPath}</button><button type="button" class="rsm-tool" data-tool="water" aria-pressed="false">{ui.toolWater}</button><button type="button" class="rsm-tool" data-tool="tree" aria-pressed="false">{ui.toolTree}</button><button type="button" class="rsm-tool" data-tool="erase" aria-pressed="false">{ui.toolErase}</button></div></div>
|
|
66
|
+
<div class="rsm-map-frame"><div class="rsm-map-host" data-map-host set:html={buildMapSvg(initialMap, ui)}></div><div class="rsm-map-help">{ui.clickHint} {ui.selectedHint}: {ui.serviceNone}</div><div class="rsm-context-menu" data-context-menu hidden role="menu" aria-label={ui.contextMenuLabel}><button type="button" data-context-tool="select" role="menuitem">{ui.contextInspect}</button><button type="button" data-context-tool="building" role="menuitem">{ui.contextBuilding}</button><button type="button" data-context-tool="path" role="menuitem">{ui.contextPath}</button><button type="button" data-context-tool="water" role="menuitem">{ui.contextWater}</button><button type="button" data-context-tool="tree" role="menuitem">{ui.contextTree}</button><button type="button" data-context-tool="erase" role="menuitem">{ui.contextErase}</button></div></div>
|
|
57
67
|
<div class="rsm-stats" aria-live="polite"><div><span>{ui.buildings}</span><strong data-building-count>{initialEvaluation.buildingCount}</strong></div><div><span>{ui.paths}</span><strong data-path-count>{initialEvaluation.pathCount}</strong></div><div><span>{ui.services}</span><strong data-service-count>{initialEvaluation.serviceCount}</strong></div><div><span>{ui.terrain}</span><strong data-water-count>{initialEvaluation.waterCount}</strong></div></div>
|
|
68
|
+
<div class="rsm-legend" aria-label={ui.legendLabel}><span><i class="rsm-legend-home" aria-hidden="true"></i>{ui.legendHome}</span><span><i class="rsm-legend-service" aria-hidden="true"></i>{ui.legendService}</span><span><i class="rsm-legend-path" aria-hidden="true"></i>{ui.legendPath}</span><span><i class="rsm-legend-water" aria-hidden="true"></i>{ui.legendWater}</span><span><i class="rsm-legend-wild" aria-hidden="true"></i>{ui.legendWild}</span></div>
|
|
58
69
|
<div class="rsm-actions"><button type="button" class="rsm-action-primary" data-share>{ui.shareLink}</button><button type="button" data-export-png>{ui.exportPng}</button><button type="button" data-export-svg>{ui.exportSvg}</button><details class="rsm-data-menu"><summary>{ui.mapData}</summary><div><button type="button" data-export-json>{ui.exportJson}</button><label><span>{ui.importJson}</span><input type="file" accept="application/json,.json" data-import-json /></label></div></details></div>
|
|
59
70
|
<p class="rsm-status" data-status aria-live="polite"></p>
|
|
60
71
|
</div>
|
|
@@ -5,6 +5,12 @@ interface CellPoint {
|
|
|
5
5
|
y: number;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
function bindToolButtons(root: HTMLElement, setActiveTool: (tool: EditTool) => void): void {
|
|
9
|
+
root.querySelectorAll<HTMLButtonElement>('[data-tool]').forEach((button) => button.addEventListener('click', () => {
|
|
10
|
+
setActiveTool(button.dataset.tool as EditTool);
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
|
|
8
14
|
type EditHandler = (point: CellPoint, tool: EditTool) => void;
|
|
9
15
|
|
|
10
16
|
function svgCell(event: MouseEvent, svg: SVGSVGElement): CellPoint | null {
|
|
@@ -60,10 +66,17 @@ function bindPointerEvents(opts: PointerOptions): void {
|
|
|
60
66
|
opts.host.addEventListener('pointerleave', cancel);
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
69
|
+
function bindMapSelection(host: HTMLElement, getActiveTool: () => EditTool, onEdit: EditHandler): void {
|
|
70
|
+
host.addEventListener('click', (event) => {
|
|
71
|
+
const tool = getActiveTool();
|
|
72
|
+
if (tool === 'select') return;
|
|
73
|
+
const svg = (event.target as Element).closest<SVGSVGElement>('[data-settlement-map]');
|
|
74
|
+
const cell = svg ? svgCell(event, svg) : null;
|
|
75
|
+
if (cell) onEdit(cell, tool);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function bindContextMenu(root: HTMLElement, host: HTMLElement, menu: HTMLElement, onEdit: EditHandler): void {
|
|
67
80
|
let selected: CellPoint | null = null;
|
|
68
81
|
const flags: PointerFlags = { ignoreNextContext: false, ignoreNextClick: false };
|
|
69
82
|
host.addEventListener('contextmenu', (event) => {
|
|
@@ -72,8 +85,8 @@ export function bindContextEditor(root: HTMLElement, onEdit: EditHandler): void
|
|
|
72
85
|
selected = openContextMenu(root, menu, event);
|
|
73
86
|
});
|
|
74
87
|
bindPointerEvents({ host, root, menu, flags, onOpen: (cell) => { selected = cell; } });
|
|
75
|
-
menu.querySelectorAll<HTMLButtonElement>('[data-context-tool]').forEach((
|
|
76
|
-
if (selected) onEdit(selected,
|
|
88
|
+
menu.querySelectorAll<HTMLButtonElement>('[data-context-tool]').forEach((button) => button.addEventListener('click', () => {
|
|
89
|
+
if (selected) onEdit(selected, button.dataset.contextTool as EditTool);
|
|
77
90
|
menu.hidden = true;
|
|
78
91
|
}));
|
|
79
92
|
document.addEventListener('click', (event) => {
|
|
@@ -83,3 +96,22 @@ export function bindContextEditor(root: HTMLElement, onEdit: EditHandler): void
|
|
|
83
96
|
window.addEventListener('resize', () => { menu.hidden = true; });
|
|
84
97
|
window.addEventListener('scroll', () => { menu.hidden = true; }, true);
|
|
85
98
|
}
|
|
99
|
+
|
|
100
|
+
export function bindContextEditor(root: HTMLElement, onEdit: EditHandler): void {
|
|
101
|
+
const host = root.querySelector<HTMLElement>('[data-map-host]');
|
|
102
|
+
const menu = root.querySelector<HTMLElement>('[data-context-menu]');
|
|
103
|
+
if (!host || !menu) return;
|
|
104
|
+
let activeTool: EditTool = 'select';
|
|
105
|
+
const toolButtons = root.querySelectorAll<HTMLButtonElement>('[data-tool]');
|
|
106
|
+
const setActiveTool = (tool: EditTool): void => {
|
|
107
|
+
activeTool = tool;
|
|
108
|
+
toolButtons.forEach((button) => {
|
|
109
|
+
const active = button.dataset.tool === tool;
|
|
110
|
+
button.classList.toggle('is-active', active);
|
|
111
|
+
button.setAttribute('aria-pressed', String(active));
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
bindToolButtons(root, setActiveTool);
|
|
115
|
+
bindMapSelection(host, () => activeTool, onEdit);
|
|
116
|
+
bindContextMenu(root, host, menu, onEdit);
|
|
117
|
+
}
|
package/src/tools.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { ENCOUNTER_DIFFICULTY_CALCULATOR_TOOL } from './tool/encounter-difficult
|
|
|
16
16
|
import { TOKEN_STAMP_STUDIO_TOOL } from './tool/token-stamp-studio';
|
|
17
17
|
import { MUS_SCOREBOARD_TOOL } from './tool/mus-scoreboard';
|
|
18
18
|
import { RPG_SETTLEMENT_EXPLORATION_MAP_GENERATOR_TOOL } from './tool/rpg-settlement-exploration-map-generator';
|
|
19
|
+
import { CARD_DRAW_ODDS_CALCULATOR_TOOL } from './tool/card-draw-odds-calculator';
|
|
19
20
|
|
|
20
21
|
export const ALL_TOOLS: ToolDefinition[] = [
|
|
21
22
|
DICE_ROLLER_SIMULATOR_TOOL,
|
|
@@ -34,5 +35,6 @@ export const ALL_TOOLS: ToolDefinition[] = [
|
|
|
34
35
|
TOKEN_STAMP_STUDIO_TOOL,
|
|
35
36
|
MUS_SCOREBOARD_TOOL,
|
|
36
37
|
RPG_SETTLEMENT_EXPLORATION_MAP_GENERATOR_TOOL,
|
|
38
|
+
CARD_DRAW_ODDS_CALCULATOR_TOOL,
|
|
37
39
|
];
|
|
38
40
|
|