@jjlmoya/utils-games-development 1.60.0 → 1.62.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 +2 -1
- package/src/entries.ts +2 -1
- package/src/index.ts +1 -0
- package/src/tests/locale_completeness.test.ts +2 -2
- package/src/tests/spanish_leakage.test.ts +25 -3
- package/src/tests/tool_validation.test.ts +2 -2
- package/src/tool/gameUIAccessibilityTester/bibliography.astro +6 -0
- package/src/tool/gameUIAccessibilityTester/bibliography.ts +10 -0
- package/src/tool/gameUIAccessibilityTester/component.astro +210 -0
- package/src/tool/gameUIAccessibilityTester/controller-actions.ts +92 -0
- package/src/tool/gameUIAccessibilityTester/controller.ts +237 -0
- package/src/tool/gameUIAccessibilityTester/dom-views.ts +193 -0
- package/src/tool/gameUIAccessibilityTester/entry.ts +28 -0
- package/src/tool/gameUIAccessibilityTester/evaluator.ts +43 -0
- package/src/tool/gameUIAccessibilityTester/game-ui-accessibility-stress-tester.css +982 -0
- package/src/tool/gameUIAccessibilityTester/i18n/de.ts +203 -0
- package/src/tool/gameUIAccessibilityTester/i18n/en.ts +199 -0
- package/src/tool/gameUIAccessibilityTester/i18n/es.ts +203 -0
- package/src/tool/gameUIAccessibilityTester/i18n/fr.ts +203 -0
- package/src/tool/gameUIAccessibilityTester/i18n/id.ts +203 -0
- package/src/tool/gameUIAccessibilityTester/i18n/it.ts +203 -0
- package/src/tool/gameUIAccessibilityTester/i18n/ja.ts +203 -0
- package/src/tool/gameUIAccessibilityTester/i18n/ko.ts +203 -0
- package/src/tool/gameUIAccessibilityTester/i18n/nl.ts +203 -0
- package/src/tool/gameUIAccessibilityTester/i18n/pl.ts +203 -0
- package/src/tool/gameUIAccessibilityTester/i18n/pt.ts +203 -0
- package/src/tool/gameUIAccessibilityTester/i18n/ru.ts +203 -0
- package/src/tool/gameUIAccessibilityTester/i18n/sv.ts +203 -0
- package/src/tool/gameUIAccessibilityTester/i18n/tr.ts +203 -0
- package/src/tool/gameUIAccessibilityTester/i18n/zh.ts +203 -0
- package/src/tool/gameUIAccessibilityTester/index.ts +11 -0
- package/src/tool/gameUIAccessibilityTester/logic.test.ts +57 -0
- package/src/tool/gameUIAccessibilityTester/logic.ts +157 -0
- package/src/tool/gameUIAccessibilityTester/report.ts +59 -0
- package/src/tool/gameUIAccessibilityTester/seo.astro +15 -0
- package/src/tool/gameUIAccessibilityTester/storage.ts +34 -0
- package/src/tool/gameUIAccessibilityTester/ui.ts +80 -0
- package/src/tools.ts +2 -0
package/package.json
CHANGED
package/src/category/index.ts
CHANGED
|
@@ -4,10 +4,11 @@ import { spriteSheetPacker } from '../tool/spriteSheetPacker/entry';
|
|
|
4
4
|
import { audioLoopPointFinder } from '../tool/audioLoopPointFinder/entry';
|
|
5
5
|
import { saveFileEditor } from '../tool/saveFileEditor/entry';
|
|
6
6
|
import { steamBbcodeTranslator } from '../tool/steamBbcodeTranslator/entry';
|
|
7
|
+
import { gameUiAccessibilityTester } from '../tool/gameUIAccessibilityTester/entry';
|
|
7
8
|
|
|
8
9
|
export const gamesCategory: GamesCategoryEntry = {
|
|
9
10
|
icon: 'mdi:gamepad-variant',
|
|
10
|
-
tools: [steamCapsuleGenerator, spriteSheetPacker, audioLoopPointFinder, saveFileEditor, steamBbcodeTranslator],
|
|
11
|
+
tools: [steamCapsuleGenerator, spriteSheetPacker, audioLoopPointFinder, saveFileEditor, steamBbcodeTranslator, gameUiAccessibilityTester],
|
|
11
12
|
i18n: {
|
|
12
13
|
de: () => import('./i18n/de').then((m) => m.content),
|
|
13
14
|
en: () => import('./i18n/en').then((m) => m.content),
|
package/src/entries.ts
CHANGED
|
@@ -26,5 +26,6 @@ import { localizationSanitizer } from './tool/localizationSanitizer/entry';
|
|
|
26
26
|
import { steamBbcodeTranslator } from './tool/steamBbcodeTranslator/entry';
|
|
27
27
|
import { retroSfxGenerator } from './tool/retroSfxGenerator/entry';
|
|
28
28
|
import { pixelArtPaletteSwapper } from './tool/pixelArtPaletteSwapper/entry';
|
|
29
|
+
import { gameUiAccessibilityTester } from './tool/gameUIAccessibilityTester/entry';
|
|
29
30
|
|
|
30
|
-
export const ALL_ENTRIES = [steamCapsuleGenerator, itchioGameTester, spriteSheetPacker, audioLoopPointFinder, saveFileEditor, localizationSanitizer, steamBbcodeTranslator, retroSfxGenerator, pixelArtPaletteSwapper];
|
|
31
|
+
export const ALL_ENTRIES = [steamCapsuleGenerator, itchioGameTester, spriteSheetPacker, audioLoopPointFinder, saveFileEditor, localizationSanitizer, steamBbcodeTranslator, retroSfxGenerator, pixelArtPaletteSwapper, gameUiAccessibilityTester];
|
package/src/index.ts
CHANGED
|
@@ -20,5 +20,6 @@ export { STEAM_CAPSULE_GENERATOR_TOOL, steamCapsuleGenerator } from './tool/stea
|
|
|
20
20
|
export { STEAM_BBCODE_TRANSLATOR_TOOL, steamBbcodeTranslator } from './tool/steamBbcodeTranslator';
|
|
21
21
|
export { RETRO_SFX_GENERATOR_TOOL, retroSfxGenerator } from './tool/retroSfxGenerator';
|
|
22
22
|
export { PIXEL_ART_PALETTE_SWAPPER_TOOL, pixelArtPaletteSwapper } from './tool/pixelArtPaletteSwapper';
|
|
23
|
+
export { GAME_UI_ACCESSIBILITY_TESTER_TOOL, gameUiAccessibilityTester } from './tool/gameUIAccessibilityTester';
|
|
23
24
|
|
|
24
25
|
export type { ToolLocaleContent as GamesToolLocaleContent } from './types';
|
|
@@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
|
|
|
2
2
|
import { ALL_TOOLS } from '../tools';
|
|
3
3
|
|
|
4
4
|
describe('Locale Completeness Validation', () => {
|
|
5
|
-
it('
|
|
6
|
-
expect(ALL_TOOLS.length).toBe(
|
|
5
|
+
it('ten tools are registered', () => {
|
|
6
|
+
expect(ALL_TOOLS.length).toBe(10);
|
|
7
7
|
});
|
|
8
8
|
});
|
|
@@ -28,6 +28,8 @@ const TRANSLATABLE_KEYS = [
|
|
|
28
28
|
'schemas',
|
|
29
29
|
] as const;
|
|
30
30
|
|
|
31
|
+
const COPY_THRESHOLD = 0.9;
|
|
32
|
+
|
|
31
33
|
const SPANISH_MARKERS = [
|
|
32
34
|
['sangre', /\bsangre\b/gi],
|
|
33
35
|
['molino', /\bmolino\b/gi],
|
|
@@ -65,12 +67,16 @@ function normalize(value: string): string {
|
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
function isTechnicalInvariant(text: string): boolean {
|
|
70
|
+
const ledUnitMatches = text.match(/\b\d+(?:-\d+)?\s*(?:w|lm)\b/g) ?? [];
|
|
68
71
|
return [
|
|
69
72
|
'data:image/svg+xml;base64',
|
|
70
73
|
'background-image: url',
|
|
71
74
|
'.layout-playground {',
|
|
72
75
|
'const samplerate =',
|
|
73
|
-
].some((pattern) => text.includes(pattern)) ||
|
|
76
|
+
].some((pattern) => text.includes(pattern)) ||
|
|
77
|
+
(text.includes('presets') && text.includes('hz')) ||
|
|
78
|
+
(text.includes('t_rectal') && text.includes('exp(-k * t)')) ||
|
|
79
|
+
(text.includes('led') && ledUnitMatches.length >= 3);
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
function collectString(value: string, output: string[]): void {
|
|
@@ -111,10 +117,26 @@ function findSpanishMarkers(text: string[]): string[] {
|
|
|
111
117
|
);
|
|
112
118
|
}
|
|
113
119
|
|
|
120
|
+
function similarity(left: string, right: string): number {
|
|
121
|
+
const leftTokens = left.split(/\s+/);
|
|
122
|
+
const rightCounts = new Map<string, number>();
|
|
123
|
+
right.split(/\s+/).forEach((token) => rightCounts.set(token, (rightCounts.get(token) ?? 0) + 1));
|
|
124
|
+
const matches = leftTokens.reduce((total, token) => {
|
|
125
|
+
const count = rightCounts.get(token) ?? 0;
|
|
126
|
+
if (count > 0) rightCounts.set(token, count - 1);
|
|
127
|
+
return total + (count > 0 ? 1 : 0);
|
|
128
|
+
}, 0);
|
|
129
|
+
return (2 * matches) / (leftTokens.length + right.split(/\s+/).length);
|
|
130
|
+
}
|
|
131
|
+
|
|
114
132
|
function findCopiedFragments(spanish: string[], translated: string[]): string[] {
|
|
115
|
-
const corpus = translated.join(' ');
|
|
116
133
|
return spanish
|
|
117
|
-
.filter((fragment) => fragment.length >= 80
|
|
134
|
+
.filter((fragment) => fragment.length >= 80)
|
|
135
|
+
.filter((fragment) => translated.some((candidate) =>
|
|
136
|
+
candidate.length >= 80 &&
|
|
137
|
+
Math.min(fragment.length, candidate.length) / Math.max(fragment.length, candidate.length) >= COPY_THRESHOLD &&
|
|
138
|
+
similarity(fragment, candidate) >= COPY_THRESHOLD,
|
|
139
|
+
))
|
|
118
140
|
.sort((a, b) => b.length - a.length)
|
|
119
141
|
.slice(0, 3);
|
|
120
142
|
}
|
|
@@ -4,8 +4,8 @@ import { gamesCategory } from '../data';
|
|
|
4
4
|
|
|
5
5
|
describe('Tool Validation Suite', () => {
|
|
6
6
|
describe('Library Registration', () => {
|
|
7
|
-
it('should have
|
|
8
|
-
expect(ALL_TOOLS.length).toBe(
|
|
7
|
+
it('should have 10 tools in ALL_TOOLS', () => {
|
|
8
|
+
expect(ALL_TOOLS.length).toBe(10);
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
it('gamesCategory should be defined', () => {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const bibliographyEntries = [
|
|
2
|
+
{
|
|
3
|
+
name: 'Machado, Oliveira and Fernandes: A Physiologically Based Model for Simulation of Color Vision Deficiency',
|
|
4
|
+
url: 'https://profs.ic.uff.br/~laffernandes/content/publications/journal/2009_tvcg_15%286%29/machado_oliveira_fernandes-tvcg-15%286%29-2009-corrected.pdf',
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
name: 'W3C Web Accessibility Initiative: Understanding Non Text Contrast',
|
|
8
|
+
url: 'https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast',
|
|
9
|
+
},
|
|
10
|
+
];
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { GameUiAccessibilityTesterUI } from './ui';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
ui: GameUiAccessibilityTesterUI;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const { ui } = Astro.props;
|
|
9
|
+
const serializedUi = JSON.stringify(ui).replace(/</g, '\\u003c');
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<article class="gax" data-game-ui-accessibility-tester data-ready="false" data-compare="side" data-zoom="1">
|
|
13
|
+
<script is:inline type="application/json" data-ui set:html={serializedUi}></script>
|
|
14
|
+
|
|
15
|
+
<div class="gax-intro">
|
|
16
|
+
<div class="gax-signal" aria-hidden="true"><span></span><span></span><span></span></div>
|
|
17
|
+
<p>{ui.onboarding}</p>
|
|
18
|
+
<p class="gax-privacy">{ui.privacyNote}</p>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<section class="gax-upload" data-dropzone aria-labelledby="game-ui-drop-title">
|
|
22
|
+
<svg viewBox="0 0 64 64" aria-hidden="true">
|
|
23
|
+
<path d="M12 17h40v30H12z M20 37l8-9 7 7 5-5 8 9 M32 10v13 M26 16l6 7 6-7" />
|
|
24
|
+
</svg>
|
|
25
|
+
<div>
|
|
26
|
+
<h2 id="game-ui-drop-title">{ui.dropTitle}</h2>
|
|
27
|
+
<p>{ui.dropHint}</p>
|
|
28
|
+
<small>{ui.supportedFiles}</small>
|
|
29
|
+
</div>
|
|
30
|
+
<input
|
|
31
|
+
class="gax-file-input"
|
|
32
|
+
id="game-ui-image-input"
|
|
33
|
+
data-file-input
|
|
34
|
+
type="file"
|
|
35
|
+
accept="image/png,image/jpeg,image/webp"
|
|
36
|
+
/>
|
|
37
|
+
<label class="gax-upload-action" for="game-ui-image-input">
|
|
38
|
+
<span class="gax-upload-new">{ui.chooseImage}</span>
|
|
39
|
+
<span class="gax-upload-replace">{ui.replaceImage}</span>
|
|
40
|
+
</label>
|
|
41
|
+
</section>
|
|
42
|
+
|
|
43
|
+
<section class="gax-optics" aria-label={ui.lensLabel}>
|
|
44
|
+
<div class="gax-control-head">
|
|
45
|
+
<span>{ui.lensLabel}</span>
|
|
46
|
+
<span class="gax-control-rule" aria-hidden="true"></span>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="gax-lens-rack">
|
|
49
|
+
<button type="button" data-mode value="original" aria-pressed="false">{ui.lensOriginal}</button>
|
|
50
|
+
<button type="button" data-mode value="protanopia" aria-pressed="false">{ui.lensProtanopia}</button>
|
|
51
|
+
<button type="button" data-mode value="deuteranopia" aria-pressed="true">{ui.lensDeuteranopia}</button>
|
|
52
|
+
<button type="button" data-mode value="tritanopia" aria-pressed="false">{ui.lensTritanopia}</button>
|
|
53
|
+
<button type="button" data-mode value="achromatopsia" aria-pressed="false">{ui.lensAchromatopsia}</button>
|
|
54
|
+
<button type="button" data-mode value="reduced-contrast" aria-pressed="false">{ui.lensReducedContrast}</button>
|
|
55
|
+
<button type="button" data-mode value="desaturation" aria-pressed="false">{ui.lensDesaturation}</button>
|
|
56
|
+
</div>
|
|
57
|
+
</section>
|
|
58
|
+
|
|
59
|
+
<section class="gax-stage" aria-label={ui.simulatedView}>
|
|
60
|
+
<div class="gax-stage-toolbar">
|
|
61
|
+
<div class="gax-segment" role="group" aria-label={ui.compareLabel}>
|
|
62
|
+
<button type="button" data-compare value="side" class="is-active" aria-pressed="true">{ui.compareSideBySide}</button>
|
|
63
|
+
<button type="button" data-compare value="split" aria-pressed="false">{ui.compareSplit}</button>
|
|
64
|
+
<button type="button" data-compare value="press" aria-pressed="false">{ui.comparePress}</button>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="gax-segment" role="group" aria-label={ui.zoomLabel}>
|
|
67
|
+
<button type="button" data-zoom value="1" class="is-active" aria-pressed="true">1x</button>
|
|
68
|
+
<button type="button" data-zoom value="2" aria-pressed="false">2x</button>
|
|
69
|
+
<button type="button" data-zoom value="4" aria-pressed="false">4x</button>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div class="gax-viewports">
|
|
74
|
+
<figure class="gax-viewport gax-original">
|
|
75
|
+
<figcaption><span></span>{ui.originalView}</figcaption>
|
|
76
|
+
<div class="gax-canvas-wrap">
|
|
77
|
+
<canvas data-original-canvas tabindex="0" aria-label={ui.originalView}></canvas>
|
|
78
|
+
<span class="gax-marker gax-marker-a" data-marker="0" hidden>A</span>
|
|
79
|
+
<span class="gax-marker gax-marker-b" data-marker="1" hidden>B</span>
|
|
80
|
+
</div>
|
|
81
|
+
</figure>
|
|
82
|
+
<figure class="gax-viewport gax-simulated">
|
|
83
|
+
<figcaption><span></span>{ui.simulatedView}</figcaption>
|
|
84
|
+
<div class="gax-canvas-wrap">
|
|
85
|
+
<canvas data-simulated-canvas aria-label={ui.simulatedView}></canvas>
|
|
86
|
+
</div>
|
|
87
|
+
</figure>
|
|
88
|
+
<p class="gax-empty">{ui.emptyCanvas}</p>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div class="gax-compare-actions">
|
|
92
|
+
<label class="gax-split-control" for="game-ui-split">
|
|
93
|
+
{ui.splitPosition}
|
|
94
|
+
<input id="game-ui-split" data-split type="range" min="10" max="90" value="50" />
|
|
95
|
+
</label>
|
|
96
|
+
<button type="button" class="gax-hold" data-hold-original>{ui.holdOriginal}</button>
|
|
97
|
+
</div>
|
|
98
|
+
</section>
|
|
99
|
+
|
|
100
|
+
<section class="gax-stress" aria-labelledby="game-ui-stress-title">
|
|
101
|
+
<div class="gax-control-head">
|
|
102
|
+
<h2 id="game-ui-stress-title">{ui.stressLabel}</h2>
|
|
103
|
+
<span class="gax-control-rule" aria-hidden="true"></span>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="gax-stress-line">
|
|
106
|
+
<label for="game-ui-blur">{ui.blurLabel}</label>
|
|
107
|
+
<input id="game-ui-blur" data-blur type="range" min="0" max="8" step="0.5" value="0" />
|
|
108
|
+
<div class="gax-segment" role="group" aria-label={ui.downscaleLabel}>
|
|
109
|
+
<button type="button" data-downscale value="1" class="is-active" aria-pressed="true">{ui.downscaleFull}</button>
|
|
110
|
+
<button type="button" data-downscale value="0.5" aria-pressed="false">{ui.downscaleHalf}</button>
|
|
111
|
+
<button type="button" data-downscale value="0.25" aria-pressed="false">{ui.downscaleQuarter}</button>
|
|
112
|
+
<button type="button" data-downscale value="0.125" aria-pressed="false">{ui.downscaleEighth}</button>
|
|
113
|
+
</div>
|
|
114
|
+
<label class="gax-toggle">
|
|
115
|
+
<input data-heatmap type="checkbox" />
|
|
116
|
+
<span aria-hidden="true"></span>
|
|
117
|
+
<span><strong>{ui.heatmapLabel}</strong><small>{ui.heatmapHint}</small></span>
|
|
118
|
+
</label>
|
|
119
|
+
</div>
|
|
120
|
+
</section>
|
|
121
|
+
|
|
122
|
+
<section class="gax-sampler" aria-labelledby="game-ui-sample-title">
|
|
123
|
+
<header>
|
|
124
|
+
<div>
|
|
125
|
+
<h2 id="game-ui-sample-title">{ui.sampleTitle}</h2>
|
|
126
|
+
<p>{ui.sampleInstructions}</p>
|
|
127
|
+
</div>
|
|
128
|
+
<span class="gax-classification">{ui.heuristicLabel}</span>
|
|
129
|
+
</header>
|
|
130
|
+
<div class="gax-sample-strip">
|
|
131
|
+
<div class="gax-sample-unit">
|
|
132
|
+
<button type="button" data-sample-button value="0" class="is-active" aria-pressed="true">
|
|
133
|
+
<span data-swatch="0" class="gax-swatch"></span>{ui.sampleA}
|
|
134
|
+
</button>
|
|
135
|
+
<div class="gax-sample-copy">
|
|
136
|
+
<label for="game-ui-sample-a">{ui.sampleAName}</label>
|
|
137
|
+
<input id="game-ui-sample-a" data-name="0" type="text" value={ui.sampleAInitial} maxlength="40" />
|
|
138
|
+
<code data-hex="0">{ui.noSample}</code>
|
|
139
|
+
</div>
|
|
140
|
+
<label class="gax-color-input" for="game-ui-color-a">{ui.manualColor}</label>
|
|
141
|
+
<input id="game-ui-color-a" data-color-input="0" type="color" value="#000000" />
|
|
142
|
+
</div>
|
|
143
|
+
<div class="gax-link-beam" aria-hidden="true"><span></span></div>
|
|
144
|
+
<div class="gax-sample-unit">
|
|
145
|
+
<button type="button" data-sample-button value="1" aria-pressed="false">
|
|
146
|
+
<span data-swatch="1" class="gax-swatch"></span>{ui.sampleB}
|
|
147
|
+
</button>
|
|
148
|
+
<div class="gax-sample-copy">
|
|
149
|
+
<label for="game-ui-sample-b">{ui.sampleBName}</label>
|
|
150
|
+
<input id="game-ui-sample-b" data-name="1" type="text" value={ui.sampleBInitial} maxlength="40" />
|
|
151
|
+
<code data-hex="1">{ui.noSample}</code>
|
|
152
|
+
</div>
|
|
153
|
+
<label class="gax-color-input" for="game-ui-color-b">{ui.manualColor}</label>
|
|
154
|
+
<input id="game-ui-color-b" data-color-input="1" type="color" value="#000000" />
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
<div class="gax-instruments" aria-live="polite">
|
|
158
|
+
<div><span>{ui.originalContrast}</span><strong data-original-contrast>--</strong><small>{ui.measurementLabel}</small></div>
|
|
159
|
+
<div><span>{ui.simulatedContrast}</span><strong data-simulated-contrast>--</strong><small>{ui.measurementLabel}</small></div>
|
|
160
|
+
<div><span>{ui.separationRetained}</span><strong data-retained>--</strong><small>{ui.heuristicLabel}</small></div>
|
|
161
|
+
<div class="gax-diagnostic"><span>{ui.manualReviewLabel}</span><strong data-diagnostic data-level="pending">{ui.statusPending}</strong></div>
|
|
162
|
+
</div>
|
|
163
|
+
<div class="gax-method-notes">
|
|
164
|
+
<p><strong>{ui.measurementLabel}</strong>{ui.measurementHint}</p>
|
|
165
|
+
<p><strong>{ui.heuristicLabel}</strong>{ui.heuristicHint}</p>
|
|
166
|
+
</div>
|
|
167
|
+
</section>
|
|
168
|
+
|
|
169
|
+
<section class="gax-review" aria-labelledby="game-ui-review-title">
|
|
170
|
+
<div class="gax-review-rail">
|
|
171
|
+
<h2 id="game-ui-review-title">{ui.promptTitle}</h2>
|
|
172
|
+
<ol>
|
|
173
|
+
<li>{ui.promptColorOnly}</li>
|
|
174
|
+
<li>{ui.promptChangingBackground}</li>
|
|
175
|
+
<li>{ui.promptMinimap}</li>
|
|
176
|
+
<li>{ui.promptStates}</li>
|
|
177
|
+
<li>{ui.promptShape}</li>
|
|
178
|
+
</ol>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="gax-findings-panel">
|
|
181
|
+
<label for="game-ui-finding">{ui.findingLabel}</label>
|
|
182
|
+
<div class="gax-finding-entry">
|
|
183
|
+
<input id="game-ui-finding" data-finding-input type="text" placeholder={ui.findingPlaceholder} maxlength="180" />
|
|
184
|
+
<button type="button" data-add-finding>{ui.addFinding}</button>
|
|
185
|
+
</div>
|
|
186
|
+
<p data-findings-empty>{ui.findingsEmpty}</p>
|
|
187
|
+
<ul data-findings></ul>
|
|
188
|
+
</div>
|
|
189
|
+
</section>
|
|
190
|
+
|
|
191
|
+
<footer class="gax-actions">
|
|
192
|
+
<div>
|
|
193
|
+
<p>{ui.localOnlyDisclosure}</p>
|
|
194
|
+
<p>{ui.limitationDisclosure}</p>
|
|
195
|
+
<p data-status role="status">{ui.noSample}</p>
|
|
196
|
+
</div>
|
|
197
|
+
<div class="gax-action-buttons">
|
|
198
|
+
<button type="button" data-export-sheet disabled>{ui.exportSheet}</button>
|
|
199
|
+
<button type="button" data-export-report disabled>{ui.exportReport}</button>
|
|
200
|
+
<button type="button" data-reset>{ui.resetTool}</button>
|
|
201
|
+
</div>
|
|
202
|
+
</footer>
|
|
203
|
+
</article>
|
|
204
|
+
|
|
205
|
+
<script>
|
|
206
|
+
import { mountGameUiAccessibilityTester } from './controller';
|
|
207
|
+
|
|
208
|
+
document.querySelectorAll<HTMLElement>('[data-game-ui-accessibility-tester]')
|
|
209
|
+
.forEach((root) => mountGameUiAccessibilityTester(root));
|
|
210
|
+
</script>
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { evaluatePair } from './evaluator';
|
|
2
|
+
import { createComparisonSheet, downloadCanvas } from './dom-views';
|
|
3
|
+
import { downloadStructuredReport } from './report';
|
|
4
|
+
import { clearSettings } from './storage';
|
|
5
|
+
import type { TesterState } from './controller';
|
|
6
|
+
|
|
7
|
+
function required<T extends Element>(root: ParentNode, selector: string): T {
|
|
8
|
+
const element = root.querySelector<T>(selector);
|
|
9
|
+
if (!element) throw new Error(`Missing tester element ${selector}`);
|
|
10
|
+
return element;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function setStatus(state: TesterState, message: string): void {
|
|
14
|
+
required<HTMLElement>(state.root, '[data-status]').textContent = message;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function addFinding(state: TesterState): void {
|
|
18
|
+
const input = required<HTMLInputElement>(state.root, '[data-finding-input]');
|
|
19
|
+
const finding = input.value.trim();
|
|
20
|
+
if (!finding) return;
|
|
21
|
+
state.findings.push(finding);
|
|
22
|
+
const item = document.createElement('li');
|
|
23
|
+
item.textContent = finding;
|
|
24
|
+
required(state.root, '[data-findings]').append(item);
|
|
25
|
+
required<HTMLElement>(state.root, '[data-findings-empty]').hidden = true;
|
|
26
|
+
input.value = '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function exportReport(state: TesterState): void {
|
|
30
|
+
const [first, second] = state.samples;
|
|
31
|
+
if (!first || !second) return;
|
|
32
|
+
downloadStructuredReport({
|
|
33
|
+
mode: state.settings.mode,
|
|
34
|
+
blur: state.settings.blur,
|
|
35
|
+
downscale: state.settings.downscale,
|
|
36
|
+
firstName: required<HTMLInputElement>(state.root, '[data-name="0"]').value,
|
|
37
|
+
secondName: required<HTMLInputElement>(state.root, '[data-name="1"]').value,
|
|
38
|
+
first,
|
|
39
|
+
second,
|
|
40
|
+
diagnostic: evaluatePair(first, second, state.settings.mode),
|
|
41
|
+
findings: state.findings,
|
|
42
|
+
}, state.ui);
|
|
43
|
+
setStatus(state, state.ui.reportDownloaded);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function exportSheet(state: TesterState): void {
|
|
47
|
+
if (!state.source) return;
|
|
48
|
+
const simulated = required<HTMLCanvasElement>(state.root, '[data-simulated-canvas]');
|
|
49
|
+
const settings = `${state.ui.lensLabel}: ${state.settings.mode}, ${state.ui.blurLabel}: ${state.settings.blur}px`;
|
|
50
|
+
const sheet = createComparisonSheet(state.source, simulated, {
|
|
51
|
+
title: state.ui.reportTitle,
|
|
52
|
+
original: state.ui.originalView,
|
|
53
|
+
simulated: state.ui.simulatedView,
|
|
54
|
+
settings,
|
|
55
|
+
localOnly: state.ui.localOnlyDisclosure,
|
|
56
|
+
});
|
|
57
|
+
downloadCanvas(sheet, 'game-ui-accessibility-comparison.png');
|
|
58
|
+
setStatus(state, state.ui.sheetDownloaded);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function reset(state: TesterState): void {
|
|
62
|
+
clearSettings();
|
|
63
|
+
state.source = null;
|
|
64
|
+
state.samples = [null, null];
|
|
65
|
+
state.findings = [];
|
|
66
|
+
state.root.dataset.ready = 'false';
|
|
67
|
+
state.root.querySelectorAll<HTMLButtonElement>('[data-export-sheet], [data-export-report]')
|
|
68
|
+
.forEach((button) => { button.disabled = true; });
|
|
69
|
+
required(state.root, '[data-findings]').replaceChildren();
|
|
70
|
+
required<HTMLElement>(state.root, '[data-findings-empty]').hidden = false;
|
|
71
|
+
required<HTMLInputElement>(state.root, '[data-file-input]').value = '';
|
|
72
|
+
setStatus(state, state.ui.noSample);
|
|
73
|
+
const diagnostic = required<HTMLElement>(state.root, '[data-diagnostic]');
|
|
74
|
+
diagnostic.textContent = state.ui.statusPending;
|
|
75
|
+
diagnostic.dataset.level = 'pending';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function bindHold(state: TesterState): void {
|
|
79
|
+
const hold = required(state.root, '[data-hold-original]');
|
|
80
|
+
hold.addEventListener('pointerdown', () => { state.root.dataset.hold = 'true'; });
|
|
81
|
+
['pointerup', 'pointerleave', 'blur'].forEach((event) => {
|
|
82
|
+
hold.addEventListener(event, () => { delete state.root.dataset.hold; });
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function bindActions(state: TesterState): void {
|
|
87
|
+
required(state.root, '[data-add-finding]').addEventListener('click', () => addFinding(state));
|
|
88
|
+
required(state.root, '[data-export-report]').addEventListener('click', () => exportReport(state));
|
|
89
|
+
required(state.root, '[data-export-sheet]').addEventListener('click', () => exportSheet(state));
|
|
90
|
+
required(state.root, '[data-reset]').addEventListener('click', () => reset(state));
|
|
91
|
+
bindHold(state);
|
|
92
|
+
}
|