@jjlmoya/utils-hardware 1.18.0 → 1.20.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.
Files changed (62) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +3 -1
  3. package/src/entries.ts +7 -1
  4. package/src/index.ts +2 -0
  5. package/src/tests/locale_completeness.test.ts +2 -2
  6. package/src/tests/pagespeed_best_practices.test.ts +198 -0
  7. package/src/tests/tool_validation.test.ts +2 -2
  8. package/src/tool/batteryHealthEstimator/component.astro +3 -3
  9. package/src/tool/gamepadTest/component.astro +4 -3
  10. package/src/tool/gamepadVibrationTester/component.astro +3 -3
  11. package/src/tool/keyboardTest/component.astro +6 -1
  12. package/src/tool/monitorGhostingTest/bibliography.astro +14 -0
  13. package/src/tool/monitorGhostingTest/bibliography.ts +20 -0
  14. package/src/tool/monitorGhostingTest/component.astro +156 -0
  15. package/src/tool/monitorGhostingTest/entry.ts +29 -0
  16. package/src/tool/monitorGhostingTest/i18n/de.ts +293 -0
  17. package/src/tool/monitorGhostingTest/i18n/en.ts +293 -0
  18. package/src/tool/monitorGhostingTest/i18n/es.ts +293 -0
  19. package/src/tool/monitorGhostingTest/i18n/fr.ts +293 -0
  20. package/src/tool/monitorGhostingTest/i18n/id.ts +293 -0
  21. package/src/tool/monitorGhostingTest/i18n/it.ts +293 -0
  22. package/src/tool/monitorGhostingTest/i18n/ja.ts +293 -0
  23. package/src/tool/monitorGhostingTest/i18n/ko.ts +293 -0
  24. package/src/tool/monitorGhostingTest/i18n/nl.ts +293 -0
  25. package/src/tool/monitorGhostingTest/i18n/pl.ts +293 -0
  26. package/src/tool/monitorGhostingTest/i18n/pt.ts +293 -0
  27. package/src/tool/monitorGhostingTest/i18n/ru.ts +293 -0
  28. package/src/tool/monitorGhostingTest/i18n/sv.ts +293 -0
  29. package/src/tool/monitorGhostingTest/i18n/tr.ts +293 -0
  30. package/src/tool/monitorGhostingTest/i18n/zh.ts +293 -0
  31. package/src/tool/monitorGhostingTest/index.ts +9 -0
  32. package/src/tool/monitorGhostingTest/logic.ts +195 -0
  33. package/src/tool/monitorGhostingTest/monitor-ghosting-test.css +546 -0
  34. package/src/tool/monitorGhostingTest/seo.astro +15 -0
  35. package/src/tool/monitorGhostingTest/ui.ts +30 -0
  36. package/src/tool/mouseDoubleClickTest/bibliography.astro +14 -0
  37. package/src/tool/mouseDoubleClickTest/bibliography.ts +16 -0
  38. package/src/tool/mouseDoubleClickTest/component.astro +135 -0
  39. package/src/tool/mouseDoubleClickTest/entry.ts +29 -0
  40. package/src/tool/mouseDoubleClickTest/i18n/de.ts +274 -0
  41. package/src/tool/mouseDoubleClickTest/i18n/en.ts +274 -0
  42. package/src/tool/mouseDoubleClickTest/i18n/es.ts +274 -0
  43. package/src/tool/mouseDoubleClickTest/i18n/fr.ts +274 -0
  44. package/src/tool/mouseDoubleClickTest/i18n/id.ts +285 -0
  45. package/src/tool/mouseDoubleClickTest/i18n/it.ts +274 -0
  46. package/src/tool/mouseDoubleClickTest/i18n/ja.ts +274 -0
  47. package/src/tool/mouseDoubleClickTest/i18n/ko.ts +274 -0
  48. package/src/tool/mouseDoubleClickTest/i18n/nl.ts +274 -0
  49. package/src/tool/mouseDoubleClickTest/i18n/pl.ts +274 -0
  50. package/src/tool/mouseDoubleClickTest/i18n/pt.ts +274 -0
  51. package/src/tool/mouseDoubleClickTest/i18n/ru.ts +274 -0
  52. package/src/tool/mouseDoubleClickTest/i18n/sv.ts +274 -0
  53. package/src/tool/mouseDoubleClickTest/i18n/tr.ts +274 -0
  54. package/src/tool/mouseDoubleClickTest/i18n/zh.ts +274 -0
  55. package/src/tool/mouseDoubleClickTest/index.ts +9 -0
  56. package/src/tool/mouseDoubleClickTest/logic.ts +258 -0
  57. package/src/tool/mouseDoubleClickTest/mouse-double-click-test.css +488 -0
  58. package/src/tool/mouseDoubleClickTest/seo.astro +15 -0
  59. package/src/tool/mouseDoubleClickTest/ui.ts +26 -0
  60. package/src/tool/mousePollingTest/logic/RatonManager.ts +6 -6
  61. package/src/tool/toneGenerator/component.astro +7 -7
  62. package/src/tools.ts +3 -2
@@ -0,0 +1,258 @@
1
+ type MouseButtonId = 'left' | 'middle' | 'right' | 'back' | 'forward' | 'other';
2
+
3
+ interface ButtonState {
4
+ count: number;
5
+ lastTime: number;
6
+ suspicious: number;
7
+ fastestGap: number;
8
+ }
9
+
10
+ export interface ClickSample {
11
+ button: MouseButtonId;
12
+ label: string;
13
+ gap: number;
14
+ suspicious: boolean;
15
+ }
16
+
17
+ interface MouseDoubleClickElements {
18
+ target: HTMLElement | null;
19
+ total: HTMLElement | null;
20
+ suspicious: HTMLElement | null;
21
+ fastest: HTMLElement | null;
22
+ score: HTMLElement | null;
23
+ status: HTMLElement | null;
24
+ threshold: HTMLInputElement | null;
25
+ thresholdValue: HTMLElement | null;
26
+ lastGap: HTMLElement | null;
27
+ log: HTMLElement | null;
28
+ reset: HTMLElement | null;
29
+ buttons: NodeListOf<HTMLElement>;
30
+ }
31
+
32
+ interface MouseDoubleClickLabels {
33
+ statusIdle: string;
34
+ statusClean: string;
35
+ statusWarning: string;
36
+ emptyLog: string;
37
+ leftButton: string;
38
+ middleButton: string;
39
+ rightButton: string;
40
+ backButton: string;
41
+ forwardButton: string;
42
+ otherButton: string;
43
+ thresholdUnit: string;
44
+ cleanEvent: string;
45
+ suspiciousEvent: string;
46
+ }
47
+
48
+ const BUTTON_FROM_CODE: Record<number, MouseButtonId> = {
49
+ 0: 'left',
50
+ 1: 'middle',
51
+ 2: 'right',
52
+ 3: 'back',
53
+ 4: 'forward',
54
+ };
55
+
56
+ export class MouseDoubleClickTester {
57
+ private threshold = 80;
58
+ private samples: ClickSample[] = [];
59
+ private readonly states = new Map<MouseButtonId, ButtonState>();
60
+
61
+ constructor(
62
+ private readonly elements: MouseDoubleClickElements,
63
+ private readonly labels: MouseDoubleClickLabels,
64
+ ) {
65
+ this.threshold = Number(elements.threshold?.value ?? 80);
66
+ this.bindEvents();
67
+ this.render();
68
+ }
69
+
70
+ private bindEvents() {
71
+ this.elements.target?.addEventListener('pointerdown', (event) => this.recordPointer(event));
72
+ this.elements.target?.addEventListener('contextmenu', (event) => event.preventDefault());
73
+ this.elements.target?.addEventListener('auxclick', (event) => event.preventDefault());
74
+ this.elements.threshold?.addEventListener('input', () => {
75
+ this.threshold = Number(this.elements.threshold?.value ?? 80);
76
+ this.recalculateSuspicion();
77
+ this.render();
78
+ });
79
+ this.elements.reset?.addEventListener('click', () => this.reset());
80
+ }
81
+
82
+ private recordPointer(event: PointerEvent) {
83
+ event.preventDefault();
84
+
85
+ const button = BUTTON_FROM_CODE[event.button] ?? 'other';
86
+ const now = performance.now();
87
+ const state = this.getButtonState(button);
88
+ const gap = state.lastTime > 0 ? now - state.lastTime : 0;
89
+ const suspicious = gap > 0 && gap <= this.threshold;
90
+
91
+ state.count++;
92
+ state.lastTime = now;
93
+
94
+ if (gap > 0) {
95
+ state.fastestGap = Math.min(state.fastestGap, gap);
96
+ if (suspicious) state.suspicious++;
97
+ this.samples.unshift({
98
+ button,
99
+ label: this.getButtonLabel(button),
100
+ gap,
101
+ suspicious,
102
+ });
103
+ }
104
+
105
+ this.flashButton(button, suspicious);
106
+ this.render();
107
+ }
108
+
109
+ private getButtonState(button: MouseButtonId) {
110
+ const existing = this.states.get(button);
111
+ if (existing) return existing;
112
+
113
+ const state: ButtonState = {
114
+ count: 0,
115
+ lastTime: 0,
116
+ suspicious: 0,
117
+ fastestGap: Infinity,
118
+ };
119
+ this.states.set(button, state);
120
+ return state;
121
+ }
122
+
123
+ private recalculateSuspicion() {
124
+ for (const state of this.states.values()) {
125
+ state.suspicious = 0;
126
+ }
127
+
128
+ this.samples = this.samples.map((sample) => {
129
+ const suspicious = sample.gap <= this.threshold;
130
+ if (suspicious) {
131
+ this.getButtonState(sample.button).suspicious++;
132
+ }
133
+ return { ...sample, suspicious };
134
+ });
135
+ }
136
+
137
+ private reset() {
138
+ this.states.clear();
139
+ this.samples = [];
140
+ this.elements.buttons.forEach((button) => {
141
+ button.dataset.count = '0';
142
+ button.dataset.state = '';
143
+ });
144
+ this.render();
145
+ }
146
+
147
+ private getTotals() {
148
+ let total = 0;
149
+ let suspicious = 0;
150
+ let fastestGap = Infinity;
151
+
152
+ for (const state of this.states.values()) {
153
+ total += state.count;
154
+ suspicious += state.suspicious;
155
+ fastestGap = Math.min(fastestGap, state.fastestGap);
156
+ }
157
+
158
+ return { total, suspicious, fastestGap };
159
+ }
160
+
161
+ private getScore() {
162
+ const { total, suspicious } = this.getTotals();
163
+ if (total < 2) return 100;
164
+ const cleanRatio = 1 - suspicious / Math.max(total - 1, 1);
165
+ return Math.max(0, Math.round(cleanRatio * 100));
166
+ }
167
+
168
+ private render() {
169
+ const score = this.getScore();
170
+ const totals = this.getTotals();
171
+ const lastSample = this.samples[0];
172
+
173
+ this.renderMetrics(score, totals, lastSample);
174
+ this.renderButtonCounts();
175
+ this.renderStatus(totals.total, totals.suspicious);
176
+ this.renderLog();
177
+ }
178
+
179
+ private renderMetrics(score: number, totals: ReturnType<MouseDoubleClickTester['getTotals']>, lastSample: ClickSample | undefined) {
180
+ this.setText(this.elements.total, String(totals.total));
181
+ this.setText(this.elements.suspicious, String(totals.suspicious));
182
+ this.setText(this.elements.fastest, this.formatGap(totals.fastestGap));
183
+ this.setText(this.elements.score, `${score}%`);
184
+ this.setText(this.elements.thresholdValue, String(this.threshold));
185
+ this.setText(this.elements.lastGap, lastSample ? `${lastSample.label} ${this.formatGap(lastSample.gap)}` : '--');
186
+ }
187
+
188
+ private setText(element: HTMLElement | null, value: string) {
189
+ if (element) element.textContent = value;
190
+ }
191
+
192
+ private formatGap(gap: number) {
193
+ return Number.isFinite(gap) ? `${Math.round(gap)} ${this.labels.thresholdUnit}` : '--';
194
+ }
195
+
196
+ private renderButtonCounts() {
197
+ this.elements.buttons.forEach((element) => {
198
+ const button = (element.dataset.button ?? 'other') as MouseButtonId;
199
+ const state = this.states.get(button);
200
+ element.dataset.count = String(state?.count ?? 0);
201
+ element.dataset.state = state && state.suspicious > 0 ? 'warning' : '';
202
+ });
203
+ }
204
+
205
+ private renderStatus(total: number, suspicious: number) {
206
+ if (!this.elements.status) return;
207
+
208
+ if (total < 2) {
209
+ this.elements.status.textContent = this.labels.statusIdle;
210
+ this.elements.status.dataset.state = 'clean';
211
+ return;
212
+ }
213
+
214
+ this.elements.status.textContent = suspicious > 0 ? this.labels.statusWarning : this.labels.statusClean;
215
+ this.elements.status.dataset.state = suspicious > 0 ? 'warning' : 'clean';
216
+ }
217
+
218
+ private renderLog() {
219
+ if (!this.elements.log) return;
220
+
221
+ if (this.samples.length === 0) {
222
+ this.elements.log.innerHTML = `<li class="mdct-empty">${this.labels.emptyLog}</li>`;
223
+ return;
224
+ }
225
+
226
+ this.elements.log.innerHTML = this.samples
227
+ .slice(0, 14)
228
+ .map((sample) => {
229
+ const state = sample.suspicious ? 'suspect' : 'clean';
230
+ const label = sample.suspicious ? this.labels.suspiciousEvent : this.labels.cleanEvent;
231
+ return `<li class="mdct-log-item ${state}"><b>${sample.label}</b><span>${Math.round(sample.gap)} ${this.labels.thresholdUnit}</span><em>${label}</em></li>`;
232
+ })
233
+ .join('');
234
+ }
235
+
236
+ private flashButton(button: MouseButtonId, suspicious: boolean) {
237
+ this.elements.target?.setAttribute('data-flash', suspicious ? 'warning' : 'clean');
238
+ const visualButton = Array.from(this.elements.buttons).find((element) => element.dataset.button === button);
239
+ if (visualButton) visualButton.dataset.flash = suspicious ? 'warning' : 'clean';
240
+
241
+ window.setTimeout(() => {
242
+ this.elements.target?.setAttribute('data-flash', '');
243
+ if (visualButton) visualButton.dataset.flash = '';
244
+ }, 180);
245
+ }
246
+
247
+ private getButtonLabel(button: MouseButtonId) {
248
+ const labels: Record<MouseButtonId, string> = {
249
+ left: this.labels.leftButton,
250
+ middle: this.labels.middleButton,
251
+ right: this.labels.rightButton,
252
+ back: this.labels.backButton,
253
+ forward: this.labels.forwardButton,
254
+ other: this.labels.otherButton,
255
+ };
256
+ return labels[button];
257
+ }
258
+ }