@jjlmoya/utils-hardware 1.1.0 → 1.3.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-hardware",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
2
3
  import type { KnownLocale } from '../../types';
3
4
  import { pixelesPantalla } from './index';
4
5
 
@@ -10,22 +11,4 @@ const { locale = 'es' } = Astro.props;
10
11
  const content = await pixelesPantalla.i18n[locale]?.();
11
12
  ---
12
13
 
13
- {
14
- content && content.bibliography.length > 0 && (
15
- <article class="prose prose-lg dark:prose-invert max-w-4xl mx-auto mt-20 px-6 font-sans">
16
- <h2 class="text-3xl font-black dark:text-blue-100 mb-8 tracking-tight">
17
- {content.bibliographyTitle}
18
- </h2>
19
- <ul>
20
- {content.bibliography.map((item) => (
21
- <li>
22
- <a href={item.url} target="_blank" rel="noopener noreferrer">
23
- {item.name}
24
- </a>
25
- </li>
26
- ))}
27
- </ul>
28
- </article>
29
- )
30
- }
31
-
14
+ {content && content.bibliography.length > 0 && <SharedBibliography links={content.bibliography} />}
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
2
3
  import type { KnownLocale } from '../../types';
3
4
  import { testMando } from './index';
4
5
 
@@ -10,21 +11,4 @@ const { locale = 'es' } = Astro.props;
10
11
  const content = await testMando.i18n[locale]?.();
11
12
  ---
12
13
 
13
- {
14
- content && content.bibliography.length > 0 && (
15
- <article class="prose prose-lg dark:prose-invert max-w-4xl mx-auto mt-20 px-6 font-sans">
16
- <h2 class="text-3xl font-black dark:text-blue-100 mb-8 tracking-tight">
17
- {content.bibliographyTitle}
18
- </h2>
19
- <ul>
20
- {content.bibliography.map((item) => (
21
- <li>
22
- <a href={item.url} target="_blank" rel="noopener noreferrer">
23
- {item.name}
24
- </a>
25
- </li>
26
- ))}
27
- </ul>
28
- </article>
29
- )
30
- }
14
+ {content && content.bibliography.length > 0 && <SharedBibliography links={content.bibliography} />}
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
2
3
  import type { KnownLocale } from '../../types';
3
4
  import { testTeclado } from './index';
4
5
 
@@ -10,21 +11,4 @@ const { locale = 'es' } = Astro.props;
10
11
  const content = await testTeclado.i18n[locale]?.();
11
12
  ---
12
13
 
13
- {
14
- content && content.bibliography.length > 0 && (
15
- <article class="prose prose-lg dark:prose-invert max-w-4xl mx-auto mt-20 px-6 font-sans">
16
- <h2 class="text-3xl font-black dark:text-blue-100 mb-8 tracking-tight">
17
- {content.bibliographyTitle}
18
- </h2>
19
- <ul>
20
- {content.bibliography.map((item) => (
21
- <li>
22
- <a href={item.url} target="_blank" rel="noopener noreferrer">
23
- {item.name}
24
- </a>
25
- </li>
26
- ))}
27
- </ul>
28
- </article>
29
- )
30
- }
14
+ {content && content.bibliography.length > 0 && <SharedBibliography links={content.bibliography} />}
@@ -232,6 +232,6 @@ const t = (ui ?? {}) as TestRatonUI;
232
232
  placeholder: document.getElementById('tr-placeholder'),
233
233
  canvas: document.getElementById('tr-canvas') as HTMLCanvasElement | null,
234
234
  area: document.getElementById('tr-area')
235
- }, new URL('./worker/mouseWorker.ts', import.meta.url));
235
+ });
236
236
  </script>
237
237
  </div>
@@ -1,3 +1,6 @@
1
+ import { MovingAverage } from './MovingAverage';
2
+ import { StatisticalFilter } from './StatisticalFilter';
3
+
1
4
  export interface UIToolElements {
2
5
  avg: HTMLElement | null;
3
6
  max: HTMLElement | null;
@@ -10,88 +13,124 @@ export interface UIToolElements {
10
13
  export class RatonManager {
11
14
  private el: UIToolElements;
12
15
  private ctx: CanvasRenderingContext2D | null;
13
- private worker: Worker;
14
16
  private history: number[] = [];
15
- private readonly MAX_HISTORY = 100;
17
+ private readonly MAX_HISTORY: number = 100;
16
18
  private idleTimer: ReturnType<typeof setTimeout> | null = null;
17
19
  private lastMouseEvent: MouseEvent | null = null;
20
+ private lastTime: number = 0;
21
+ private maxHz: number = 0;
22
+ private totalHistory: number[] = [];
23
+ private filter: StatisticalFilter = new StatisticalFilter();
24
+ private smoother: MovingAverage = new MovingAverage(20);
18
25
 
19
- constructor(el: UIToolElements, workerUrl: URL) {
26
+ constructor(el: UIToolElements) {
20
27
  this.el = el;
21
28
  this.ctx = el.canvas?.getContext('2d') ?? null;
22
- this.worker = new Worker(workerUrl, { type: 'module' });
23
29
  this.init();
24
30
  }
25
31
 
26
32
  private init(): void {
27
33
  window.addEventListener('resize', () => this.resize());
28
34
  this.resize();
29
-
30
- this.worker.onmessage = (event) => this.handleWorkerMessage(event.data);
31
-
32
- this.el.area?.addEventListener('mousemove', (e) => this.processMove(e));
35
+ this.el.area?.addEventListener('mousemove', (e: MouseEvent) => this.processMove(e));
33
36
  this.el.area?.addEventListener('mouseleave', () => this.handleMouseLeave());
34
37
  }
35
38
 
36
39
  private resize(): void {
37
- if (!this.el.area || !this.el.canvas) return;
38
- const rect = this.el.area.getBoundingClientRect();
39
- this.el.canvas.width = rect.width;
40
- this.el.canvas.height = rect.height;
40
+ const { area, canvas } = this.el;
41
+ if (!area || !canvas) return;
42
+ const rect = area.getBoundingClientRect();
43
+ canvas.width = rect.width;
44
+ canvas.height = rect.height;
41
45
  }
42
46
 
43
- private handleWorkerMessage(data: { hz: number; smoothedHz: number; avgHz: number; maxHz: number }): void {
44
- const { hz, smoothedHz, avgHz, maxHz } = data;
45
-
46
- this.history.push(smoothedHz);
47
+ private processMove(e: MouseEvent): void {
48
+ this.lastMouseEvent = e;
49
+ const now: number = performance.now();
50
+ this.calculateHz(now);
51
+ this.lastTime = now;
52
+ this.refreshIdleTimer();
53
+ }
54
+
55
+ private calculateHz(now: number): void {
56
+ if (this.lastTime <= 0) return;
57
+ const delta: number = now - this.lastTime;
58
+ if (delta <= 0.5 || delta >= 200) return;
59
+
60
+ const hz: number = 1000 / delta;
61
+ if (this.filter.isOutlier(hz)) return;
62
+
63
+ this.processValidMove(hz);
64
+ }
65
+
66
+ private processValidMove(hz: number): void {
67
+ const smoothed: number = this.smoother.next(hz);
68
+ this.history.push(smoothed);
47
69
  if (this.history.length > this.MAX_HISTORY) this.history.shift();
48
70
 
49
- if (this.el.max) this.el.max.textContent = Math.round(maxHz).toString();
50
- if (this.el.avg) this.el.avg.textContent = Math.round(avgHz).toString();
71
+ this.totalHistory.push(hz);
72
+ if (this.totalHistory.length > 200) this.totalHistory.shift();
73
+
74
+ if (hz > this.maxHz) this.maxHz = hz;
75
+ const avg: number = this.totalHistory.reduce((acc: number, val: number) => acc + val, 0) / this.totalHistory.length;
51
76
 
77
+ this.updateUI(hz, avg);
78
+ }
79
+
80
+ private refreshIdleTimer(): void {
81
+ if (this.idleTimer) clearTimeout(this.idleTimer);
82
+ this.idleTimer = setTimeout(() => this.reset(), 100);
83
+ }
84
+
85
+ private updateUI(hz: number, avg: number): void {
86
+ const { max, avg: avgEl, placeholder } = this.el;
87
+ if (max) max.textContent = Math.round(this.maxHz).toString();
88
+ if (avgEl) avgEl.textContent = Math.round(avg).toString();
89
+ if (placeholder) placeholder.style.opacity = '0';
90
+ this.updateFollower(hz);
52
91
  this.updateGraph();
92
+ }
53
93
 
54
- if (this.el.follower && this.lastMouseEvent) {
55
- this.el.follower.textContent = `${Math.round(hz)} Hz`;
56
- const rect = this.el.area!.getBoundingClientRect();
57
- this.el.follower.style.transform = `translate(${this.lastMouseEvent.clientX - rect.left + 15}px, ${this.lastMouseEvent.clientY - rect.top + 15}px)`;
58
- this.el.follower.style.opacity = '1';
59
- }
60
-
61
- if (this.el.placeholder) this.el.placeholder.style.opacity = '0';
94
+ private updateFollower(hz: number): void {
95
+ const { follower, area } = this.el;
96
+ if (!follower || !this.lastMouseEvent || !area) return;
97
+ follower.textContent = `${Math.round(hz)} Hz`;
98
+ const rect = area.getBoundingClientRect();
99
+ const x: number = this.lastMouseEvent.clientX - rect.left + 15;
100
+ const y: number = this.lastMouseEvent.clientY - rect.top + 15;
101
+ follower.style.transform = `translate(${x}px, ${y}px)`;
102
+ follower.style.opacity = '1';
62
103
  }
63
104
 
64
105
  private updateGraph(): void {
65
- if (!this.ctx || !this.el.canvas) return;
66
- const { width: w, height: h } = this.el.canvas;
67
- this.ctx.clearRect(0, 0, w, h);
68
- this.ctx.beginPath();
69
- this.ctx.strokeStyle = '#10b981';
70
- this.ctx.lineWidth = 2;
71
- const currentMax = Math.max(1000, ...this.history);
72
- const stepX = w / this.MAX_HISTORY;
73
- this.history.forEach((hz, i) => {
74
- const x = i * stepX;
75
- const y = h - (hz / currentMax) * (h * 0.8);
76
- if (i === 0) this.ctx!.moveTo(x, y);
77
- else this.ctx!.lineTo(x, y);
106
+ const ctx = this.ctx;
107
+ const canvas = this.el.canvas;
108
+ if (!ctx || !canvas) return;
109
+ const { width: w, height: h } = canvas;
110
+ ctx.clearRect(0, 0, w, h);
111
+ ctx.beginPath();
112
+ ctx.strokeStyle = '#10b981';
113
+ ctx.lineWidth = 2;
114
+ const currentMax: number = Math.max(1000, ...this.history);
115
+ const stepX: number = w / this.MAX_HISTORY;
116
+ this.history.forEach((hz: number, i: number) => {
117
+ const x: number = i * stepX;
118
+ const y: number = h - (hz / currentMax) * (h * 0.8);
119
+ if (i === 0) ctx.moveTo(x, y);
120
+ else ctx.lineTo(x, y);
78
121
  });
79
- this.ctx.stroke();
122
+ ctx.stroke();
80
123
  }
81
124
 
82
- private processMove(e: MouseEvent): void {
83
- this.lastMouseEvent = e;
84
- this.worker.postMessage({ time: performance.now() });
85
-
86
- if (this.idleTimer) clearTimeout(this.idleTimer);
87
- this.idleTimer = setTimeout(() => {
88
- if (this.el.follower) this.el.follower.style.opacity = '0';
89
- this.worker.postMessage('reset');
90
- }, 100);
125
+ private reset(): void {
126
+ this.lastTime = 0;
127
+ this.filter.reset();
128
+ this.smoother.reset();
129
+ this.totalHistory = [];
130
+ if (this.el.follower) this.el.follower.style.opacity = '0';
91
131
  }
92
132
 
93
133
  private handleMouseLeave(): void {
94
- if (this.el.follower) this.el.follower.style.opacity = '0';
95
- this.worker.postMessage('reset');
134
+ this.reset();
96
135
  }
97
136
  }
@@ -1,38 +1,30 @@
1
- export interface FilterConfig {
2
- maxThreshold?: number;
3
- stdDevMultiplier?: number;
4
- minSamples?: number;
5
- }
6
-
7
1
  export class StatisticalFilter {
8
2
  private samples: number[] = [];
9
- private readonly config: FilterConfig;
10
-
11
- constructor(config: FilterConfig = {}) {
12
- this.config = {
13
- maxThreshold: 1100,
14
- stdDevMultiplier: 2,
15
- minSamples: 20,
16
- ...config,
17
- };
18
- }
3
+ private readonly MAX_THRESHOLD: number = 10000;
4
+ private readonly STD_DEV_MULTIPLIER: number = 4;
5
+ private readonly MIN_SAMPLES: number = 20;
19
6
 
20
7
  public isOutlier(value: number): boolean {
21
- if (this.config.maxThreshold && value > this.config.maxThreshold) {
22
- return true;
23
- }
8
+ if (value > this.MAX_THRESHOLD) return true;
24
9
 
25
- if (this.samples.length < (this.config.minSamples || 20)) {
10
+ if (this.samples.length < this.MIN_SAMPLES) {
26
11
  this.samples.push(value);
27
12
  return false;
28
13
  }
29
14
 
30
- const avg = this.samples.reduce((a, b) => a + b, 0) / this.samples.length;
31
- const stdDev = Math.sqrt(
32
- this.samples.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / this.samples.length
33
- );
15
+ const total: number = this.samples.reduce((acc: number, val: number) => acc + val, 0);
16
+ const avg: number = total / this.samples.length;
17
+
18
+ const squareDiffs: number = this.samples.reduce((acc: number, val: number) => {
19
+ const diff: number = val - avg;
20
+ return acc + (diff * diff);
21
+ }, 0);
22
+
23
+ const stdDev: number = Math.sqrt(squareDiffs / this.samples.length);
24
+
25
+ if (stdDev === 0) return false;
34
26
 
35
- const isFiltered = Math.abs(value - avg) > stdDev * (this.config.stdDevMultiplier || 2.5);
27
+ const isFiltered: boolean = Math.abs(value - avg) > stdDev * this.STD_DEV_MULTIPLIER;
36
28
 
37
29
  if (!isFiltered) {
38
30
  this.samples.push(value);
@@ -1,50 +0,0 @@
1
- import { StatisticalFilter } from '../logic/StatisticalFilter';
2
- import { MovingAverage } from '../logic/MovingAverage';
3
-
4
- const statisticalFilter = new StatisticalFilter();
5
- const smoothingFilter = new MovingAverage(20);
6
-
7
- let lastTime = 0;
8
- let maxHz = 0;
9
- const history: number[] = [];
10
-
11
- self.onmessage = (event: MessageEvent<{ time: number } | 'reset'>) => {
12
- if (event.data === 'reset') {
13
- lastTime = 0;
14
- return;
15
- }
16
-
17
- const { time } = event.data;
18
-
19
- if (lastTime === 0) {
20
- lastTime = time;
21
- return;
22
- }
23
-
24
- const delta = time - lastTime;
25
- lastTime = time;
26
-
27
- if (delta <= 0 || delta > 100) return;
28
-
29
- const hz = 1000 / delta;
30
-
31
- if (statisticalFilter.isOutlier(hz)) {
32
- return;
33
- }
34
-
35
- const smoothedHz = smoothingFilter.next(hz);
36
-
37
- history.push(hz);
38
- if (history.length > 200) history.shift();
39
-
40
- if (hz > maxHz) maxHz = hz;
41
-
42
- const avgHz = history.reduce((a, b) => a + b, 0) / history.length;
43
-
44
- self.postMessage({
45
- hz,
46
- smoothedHz,
47
- avgHz,
48
- maxHz
49
- });
50
- };