@jjlmoya/utils-drones 1.31.0 → 1.32.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 (34) hide show
  1. package/package.json +1 -1
  2. package/src/category/i18n/en.ts +1 -1
  3. package/src/category/i18n/fr.ts +1 -1
  4. package/src/components/PreviewNavSidebar.astro +116 -116
  5. package/src/components/PreviewToolbar.astro +143 -143
  6. package/src/data.ts +1 -1
  7. package/src/env.d.ts +1 -1
  8. package/src/layouts/PreviewLayout.astro +118 -118
  9. package/src/pages/[locale].astro +251 -251
  10. package/src/pages/index.astro +4 -4
  11. package/src/tests/faq_count.test.ts +1 -1
  12. package/src/tests/mocks/astro_mock.js +1 -1
  13. package/src/tests/no_h1_in_components.test.ts +1 -1
  14. package/src/tests/tool_validation.test.ts +15 -15
  15. package/src/tool/antenna-length-calculator/bibliography.astro +6 -6
  16. package/src/tool/antenna-length-calculator/component.astro +329 -329
  17. package/src/tool/antenna-length-calculator/seo.astro +15 -15
  18. package/src/tool/drone-battery-c-rating-calculator/i18n/de.ts +192 -192
  19. package/src/tool/drone-battery-c-rating-calculator/i18n/en.ts +192 -192
  20. package/src/tool/drone-battery-c-rating-calculator/i18n/es.ts +192 -192
  21. package/src/tool/drone-battery-c-rating-calculator/i18n/id.ts +192 -192
  22. package/src/tool/drone-battery-c-rating-calculator/i18n/ja.ts +192 -192
  23. package/src/tool/drone-battery-c-rating-calculator/i18n/ko.ts +192 -192
  24. package/src/tool/drone-battery-c-rating-calculator/i18n/nl.ts +192 -192
  25. package/src/tool/drone-battery-c-rating-calculator/i18n/pl.ts +192 -192
  26. package/src/tool/drone-battery-c-rating-calculator/i18n/ru.ts +192 -192
  27. package/src/tool/drone-battery-c-rating-calculator/i18n/sv.ts +192 -192
  28. package/src/tool/drone-battery-c-rating-calculator/i18n/tr.ts +192 -192
  29. package/src/tool/drone-battery-c-rating-calculator/i18n/zh.ts +192 -192
  30. package/src/tool/drone-flight-time/component.astro +187 -187
  31. package/src/tool/drone-flight-time/seo.astro +15 -15
  32. package/src/tool/drone-motor-propeller-calculator/seo.astro +7 -4
  33. package/src/tool/gps-coordinates-converter/component.astro +275 -275
  34. package/src/tool/gps-coordinates-converter/seo.astro +15 -15
@@ -1,187 +1,187 @@
1
- ---
2
- const { ui } = Astro.props;
3
- import BatterySpecs from "./components/BatterySpecs.astro";
4
- import ConsumptionStats from "./components/ConsumptionStats.astro";
5
- import FlightDashboard from "./components/FlightDashboard.astro";
6
- import AutonomyChart from "./components/AutonomyChart.astro";
7
- ---
8
-
9
- <div class="flight-calculator-ui">
10
- <div class="tech-mega-card animate-in fade-in zoom-in duration-700">
11
- <div class="card-grid">
12
- <div class="config-sidebar">
13
- <BatterySpecs ui={ui} />
14
- <div class="divider"></div>
15
- <ConsumptionStats ui={ui} />
16
- </div>
17
-
18
- <div class="main-display">
19
- <FlightDashboard ui={ui} />
20
- <div class="divider"></div>
21
- <AutonomyChart ui={ui} />
22
- </div>
23
- </div>
24
- </div>
25
- </div>
26
-
27
- <script>
28
- import { Chart, registerables } from "chart.js";
29
- Chart.register(...registerables);
30
-
31
- const capInput = document.getElementById("capacityInput") as HTMLInputElement;
32
- const cellsInput = document.getElementById("cellsInput") as HTMLSelectElement;
33
- const safetyInput = document.getElementById("safetyInput") as HTMLInputElement;
34
- const drawInput = document.getElementById("drawInput") as HTMLInputElement;
35
- const wattsInput = document.getElementById("wattsInput") as HTMLInputElement;
36
- const effInput = document.getElementById("efficiencyInput") as HTMLInputElement;
37
-
38
- const displays = {
39
- cap: document.getElementById("capDisplay"),
40
- cells: document.getElementById("cellsDisplay"),
41
- volts: document.getElementById("voltsDisplay"),
42
- safety: document.getElementById("safetyDisplay"),
43
- draw: document.getElementById("drawDisplay"),
44
- watts: document.getElementById("wattsDisplay"),
45
- eff: document.getElementById("effDisplay"),
46
- timeMain: document.getElementById("timeMainDisplay"),
47
- timeTotal: document.getElementById("timeTotalDisplay"),
48
- wh: document.getElementById("whDisplay"),
49
- co2: document.getElementById("co2Display"),
50
- circle: document.getElementById("timeCircle")
51
- };
52
-
53
- let chart: Chart | null = null;
54
-
55
- function getChartConfig(label: string) {
56
- return {
57
- type: "line",
58
- data: {
59
- labels: [],
60
- datasets: [{
61
- label,
62
- data: [],
63
- borderColor: "#f59e0b",
64
- backgroundColor: "rgba(245, 158, 11, 0.1)",
65
- borderWidth: 3,
66
- fill: true,
67
- tension: 0.4,
68
- pointRadius: 0,
69
- pointHitRadius: 10
70
- }]
71
- },
72
- options: {
73
- responsive: true,
74
- maintainAspectRatio: false,
75
- plugins: { legend: { display: false } },
76
- scales: {
77
- y: { beginAtZero: true, grid: { color: "rgba(0,0,0,0.05)" } },
78
- x: { grid: { display: false } }
79
- }
80
- }
81
- };
82
- }
83
-
84
- function initChart() {
85
- const canvas = document.getElementById("autonomyChart") as HTMLCanvasElement;
86
- const ctx = canvas?.getContext("2d");
87
- if (!ctx) return;
88
- const chartLabel = canvas.dataset.label || "Minutos";
89
- chart = new Chart(ctx, getChartConfig(chartLabel));
90
- }
91
-
92
- function calculate(source: string) {
93
- const cap = parseFloat(capInput.value);
94
- const cells = parseInt(cellsInput.value);
95
- const safety = parseFloat(safetyInput.value) / 100;
96
- let draw = parseFloat(drawInput.value);
97
- const voltage = cells * 3.7;
98
-
99
- if (source === 'watts') {
100
- draw = parseFloat(wattsInput.value) / voltage;
101
- drawInput.value = draw.toFixed(1);
102
- } else {
103
- wattsInput.value = (draw * voltage).toFixed(1);
104
- }
105
-
106
- const state = { cap, cells, safety, draw, voltage };
107
- updateDisplays(state);
108
-
109
- const usableAh = (cap / 1000) * safety;
110
- const totalAh = cap / 1000;
111
-
112
- const safeTimeMin = (usableAh / draw) * 60;
113
- const totalTimeMin = (totalAh / draw) * 60;
114
-
115
- const energyWh = (cap / 1000) * voltage;
116
-
117
- renderResults(safeTimeMin, totalTimeMin, energyWh, cap);
118
- updateChart(usableAh);
119
- }
120
-
121
- function updateDisplays(state: { cap: number; cells: number; safety: number; draw: number; voltage: number }) {
122
- if (displays.cap) displays.cap.textContent = state.cap.toString();
123
- if (displays.cells) displays.cells.textContent = state.cells.toString();
124
- if (displays.volts) displays.volts.textContent = state.voltage.toFixed(1);
125
- if (displays.safety) displays.safety.textContent = Math.round(state.safety * 100).toString();
126
- if (displays.draw) displays.draw.textContent = state.draw.toFixed(1);
127
- if (displays.watts) displays.watts.textContent = (state.draw * state.voltage).toFixed(1);
128
- if (displays.eff) displays.eff.textContent = effInput.value;
129
- }
130
-
131
- function renderResults(safeMin: number, totalMin: number, wh: number, cap: number) {
132
- if (displays.timeMain) displays.timeMain.textContent = formatTime(safeMin);
133
- if (displays.timeTotal) displays.timeTotal.textContent = formatTime(totalMin);
134
- if (displays.wh) displays.wh.textContent = `${wh.toFixed(1)} Wh`;
135
- if (displays.co2) displays.co2.textContent = `${Math.round(cap * 0.05)} g`;
136
-
137
- if (displays.circle) {
138
- const limit = 20;
139
- const progress = Math.min(safeMin / limit, 1);
140
- const offset = 283 - (progress * 283);
141
- (displays.circle as HTMLElement).style.strokeDashoffset = offset.toString();
142
- }
143
- }
144
-
145
- function formatTime(decimalMin: number) {
146
- if (isNaN(decimalMin) || decimalMin === Infinity) return "0:00";
147
- const mins = Math.floor(decimalMin);
148
- const secs = Math.floor((decimalMin - mins) * 60);
149
- return `${mins}:${secs.toString().padStart(2, '0')}`;
150
- }
151
-
152
- function updateChart(usableAh: number) {
153
- if (!chart) return;
154
- const data: number[] = [];
155
- const labels: string[] = [];
156
- const currentDraw = parseFloat(drawInput.value);
157
-
158
- for (let a = Math.max(1, currentDraw - 20); a <= currentDraw + 20; a += 2) {
159
- labels.push(`${a}A`);
160
- const val = ((usableAh / a) * 60);
161
- data.push(Number(val.toFixed(1)));
162
- }
163
-
164
- chart.data.labels = labels;
165
- if (chart.data.datasets[0]) chart.data.datasets[0].data = data;
166
- chart.update('none');
167
- }
168
-
169
- [capInput, cellsInput, safetyInput, drawInput, effInput].forEach(el => {
170
- el?.addEventListener("input", () => calculate('basic'));
171
- });
172
-
173
- wattsInput?.addEventListener("input", () => calculate('watts'));
174
-
175
- document.querySelectorAll('.preset-btn').forEach(btn => {
176
- btn.addEventListener('click', () => {
177
- capInput.value = btn.getAttribute('data-val') || "1500";
178
- calculate('basic');
179
- });
180
- });
181
-
182
- initChart();
183
- calculate('basic');
184
- </script>
185
-
186
-
187
-
1
+ ---
2
+ const { ui } = Astro.props;
3
+ import BatterySpecs from "./components/BatterySpecs.astro";
4
+ import ConsumptionStats from "./components/ConsumptionStats.astro";
5
+ import FlightDashboard from "./components/FlightDashboard.astro";
6
+ import AutonomyChart from "./components/AutonomyChart.astro";
7
+ ---
8
+
9
+ <div class="flight-calculator-ui">
10
+ <div class="tech-mega-card animate-in fade-in zoom-in duration-700">
11
+ <div class="card-grid">
12
+ <div class="config-sidebar">
13
+ <BatterySpecs ui={ui} />
14
+ <div class="divider"></div>
15
+ <ConsumptionStats ui={ui} />
16
+ </div>
17
+
18
+ <div class="main-display">
19
+ <FlightDashboard ui={ui} />
20
+ <div class="divider"></div>
21
+ <AutonomyChart ui={ui} />
22
+ </div>
23
+ </div>
24
+ </div>
25
+ </div>
26
+
27
+ <script>
28
+ import { Chart, registerables } from "chart.js";
29
+ Chart.register(...registerables);
30
+
31
+ const capInput = document.getElementById("capacityInput") as HTMLInputElement;
32
+ const cellsInput = document.getElementById("cellsInput") as HTMLSelectElement;
33
+ const safetyInput = document.getElementById("safetyInput") as HTMLInputElement;
34
+ const drawInput = document.getElementById("drawInput") as HTMLInputElement;
35
+ const wattsInput = document.getElementById("wattsInput") as HTMLInputElement;
36
+ const effInput = document.getElementById("efficiencyInput") as HTMLInputElement;
37
+
38
+ const displays = {
39
+ cap: document.getElementById("capDisplay"),
40
+ cells: document.getElementById("cellsDisplay"),
41
+ volts: document.getElementById("voltsDisplay"),
42
+ safety: document.getElementById("safetyDisplay"),
43
+ draw: document.getElementById("drawDisplay"),
44
+ watts: document.getElementById("wattsDisplay"),
45
+ eff: document.getElementById("effDisplay"),
46
+ timeMain: document.getElementById("timeMainDisplay"),
47
+ timeTotal: document.getElementById("timeTotalDisplay"),
48
+ wh: document.getElementById("whDisplay"),
49
+ co2: document.getElementById("co2Display"),
50
+ circle: document.getElementById("timeCircle")
51
+ };
52
+
53
+ let chart: Chart | null = null;
54
+
55
+ function getChartConfig(label: string) {
56
+ return {
57
+ type: "line",
58
+ data: {
59
+ labels: [],
60
+ datasets: [{
61
+ label,
62
+ data: [],
63
+ borderColor: "#f59e0b",
64
+ backgroundColor: "rgba(245, 158, 11, 0.1)",
65
+ borderWidth: 3,
66
+ fill: true,
67
+ tension: 0.4,
68
+ pointRadius: 0,
69
+ pointHitRadius: 10
70
+ }]
71
+ },
72
+ options: {
73
+ responsive: true,
74
+ maintainAspectRatio: false,
75
+ plugins: { legend: { display: false } },
76
+ scales: {
77
+ y: { beginAtZero: true, grid: { color: "rgba(0,0,0,0.05)" } },
78
+ x: { grid: { display: false } }
79
+ }
80
+ }
81
+ };
82
+ }
83
+
84
+ function initChart() {
85
+ const canvas = document.getElementById("autonomyChart") as HTMLCanvasElement;
86
+ const ctx = canvas?.getContext("2d");
87
+ if (!ctx) return;
88
+ const chartLabel = canvas.dataset.label || "Minutos";
89
+ chart = new Chart(ctx, getChartConfig(chartLabel));
90
+ }
91
+
92
+ function calculate(source: string) {
93
+ const cap = parseFloat(capInput.value);
94
+ const cells = parseInt(cellsInput.value);
95
+ const safety = parseFloat(safetyInput.value) / 100;
96
+ let draw = parseFloat(drawInput.value);
97
+ const voltage = cells * 3.7;
98
+
99
+ if (source === 'watts') {
100
+ draw = parseFloat(wattsInput.value) / voltage;
101
+ drawInput.value = draw.toFixed(1);
102
+ } else {
103
+ wattsInput.value = (draw * voltage).toFixed(1);
104
+ }
105
+
106
+ const state = { cap, cells, safety, draw, voltage };
107
+ updateDisplays(state);
108
+
109
+ const usableAh = (cap / 1000) * safety;
110
+ const totalAh = cap / 1000;
111
+
112
+ const safeTimeMin = (usableAh / draw) * 60;
113
+ const totalTimeMin = (totalAh / draw) * 60;
114
+
115
+ const energyWh = (cap / 1000) * voltage;
116
+
117
+ renderResults(safeTimeMin, totalTimeMin, energyWh, cap);
118
+ updateChart(usableAh);
119
+ }
120
+
121
+ function updateDisplays(state: { cap: number; cells: number; safety: number; draw: number; voltage: number }) {
122
+ if (displays.cap) displays.cap.textContent = state.cap.toString();
123
+ if (displays.cells) displays.cells.textContent = state.cells.toString();
124
+ if (displays.volts) displays.volts.textContent = state.voltage.toFixed(1);
125
+ if (displays.safety) displays.safety.textContent = Math.round(state.safety * 100).toString();
126
+ if (displays.draw) displays.draw.textContent = state.draw.toFixed(1);
127
+ if (displays.watts) displays.watts.textContent = (state.draw * state.voltage).toFixed(1);
128
+ if (displays.eff) displays.eff.textContent = effInput.value;
129
+ }
130
+
131
+ function renderResults(safeMin: number, totalMin: number, wh: number, cap: number) {
132
+ if (displays.timeMain) displays.timeMain.textContent = formatTime(safeMin);
133
+ if (displays.timeTotal) displays.timeTotal.textContent = formatTime(totalMin);
134
+ if (displays.wh) displays.wh.textContent = `${wh.toFixed(1)} Wh`;
135
+ if (displays.co2) displays.co2.textContent = `${Math.round(cap * 0.05)} g`;
136
+
137
+ if (displays.circle) {
138
+ const limit = 20;
139
+ const progress = Math.min(safeMin / limit, 1);
140
+ const offset = 283 - (progress * 283);
141
+ (displays.circle as HTMLElement).style.strokeDashoffset = offset.toString();
142
+ }
143
+ }
144
+
145
+ function formatTime(decimalMin: number) {
146
+ if (isNaN(decimalMin) || decimalMin === Infinity) return "0:00";
147
+ const mins = Math.floor(decimalMin);
148
+ const secs = Math.floor((decimalMin - mins) * 60);
149
+ return `${mins}:${secs.toString().padStart(2, '0')}`;
150
+ }
151
+
152
+ function updateChart(usableAh: number) {
153
+ if (!chart) return;
154
+ const data: number[] = [];
155
+ const labels: string[] = [];
156
+ const currentDraw = parseFloat(drawInput.value);
157
+
158
+ for (let a = Math.max(1, currentDraw - 20); a <= currentDraw + 20; a += 2) {
159
+ labels.push(`${a}A`);
160
+ const val = ((usableAh / a) * 60);
161
+ data.push(Number(val.toFixed(1)));
162
+ }
163
+
164
+ chart.data.labels = labels;
165
+ if (chart.data.datasets[0]) chart.data.datasets[0].data = data;
166
+ chart.update('none');
167
+ }
168
+
169
+ [capInput, cellsInput, safetyInput, drawInput, effInput].forEach(el => {
170
+ el?.addEventListener("input", () => calculate('basic'));
171
+ });
172
+
173
+ wattsInput?.addEventListener("input", () => calculate('watts'));
174
+
175
+ document.querySelectorAll('.preset-btn').forEach(btn => {
176
+ btn.addEventListener('click', () => {
177
+ capInput.value = btn.getAttribute('data-val') || "1500";
178
+ calculate('basic');
179
+ });
180
+ });
181
+
182
+ initChart();
183
+ calculate('basic');
184
+ </script>
185
+
186
+
187
+
@@ -1,15 +1,15 @@
1
- ---
2
- import { SEORenderer } from '@jjlmoya/utils-shared';
3
- import { droneFlightTime } from './index';
4
- import type { KnownLocale } from '../../types';
5
-
6
- interface Props {
7
- locale?: KnownLocale;
8
- }
9
-
10
- const { locale = 'es' } = Astro.props;
11
- const content = await droneFlightTime.i18n[locale]?.();
12
- if (!content) return null;
13
- ---
14
-
15
- {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { droneFlightTime } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'es' } = Astro.props;
11
+ const content = await droneFlightTime.i18n[locale]?.();
12
+ if (!content) return null;
13
+ ---
14
+
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -1,12 +1,15 @@
1
1
  ---
2
- import type { SEOSection } from '../../types';
3
2
  import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import type { KnownLocale } from '../../types';
4
+ import { droneMotorPropellerCalculator } from './entry';
4
5
 
5
6
  interface Props {
6
- content: SEOSection[];
7
+ locale?: KnownLocale;
7
8
  }
8
9
 
9
- const { content } = Astro.props;
10
+ const { locale = 'es' } = Astro.props;
11
+ const content = await droneMotorPropellerCalculator.i18n[locale]?.();
12
+ if (!content) return null;
10
13
  ---
11
14
 
12
- <SEORenderer content={{ locale: 'en', sections: content }} />
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}