@jjlmoya/utils-drones 1.31.0 → 1.33.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/i18n/de.ts +1 -1
- package/src/category/i18n/en.ts +2 -2
- package/src/category/i18n/es.ts +1 -1
- package/src/category/i18n/fr.ts +2 -2
- package/src/category/i18n/id.ts +1 -1
- package/src/category/i18n/it.ts +1 -1
- package/src/category/i18n/nl.ts +1 -1
- package/src/category/i18n/pl.ts +1 -1
- package/src/category/i18n/pt.ts +1 -1
- package/src/category/i18n/sv.ts +1 -1
- package/src/category/i18n/tr.ts +1 -1
- package/src/components/PreviewNavSidebar.astro +116 -116
- package/src/components/PreviewToolbar.astro +143 -143
- package/src/data.ts +1 -1
- package/src/env.d.ts +1 -1
- package/src/layouts/PreviewLayout.astro +118 -118
- package/src/pages/[locale].astro +251 -251
- package/src/pages/index.astro +4 -4
- package/src/tests/bibliography_wellformed_export.test.ts +46 -0
- package/src/tests/category_seo_quality.test.ts +76 -0
- package/src/tests/faq_count.test.ts +1 -1
- package/src/tests/mocks/astro_mock.js +1 -1
- package/src/tests/no_h1_in_components.test.ts +1 -1
- package/src/tests/seo_translation_completeness.test.ts +69 -0
- package/src/tests/seo_wellformed_export.test.ts +65 -0
- package/src/tests/tool_validation.test.ts +15 -15
- package/src/tool/antenna-length-calculator/bibliography.astro +6 -6
- package/src/tool/antenna-length-calculator/component.astro +329 -329
- package/src/tool/antenna-length-calculator/seo.astro +15 -15
- package/src/tool/drone-battery-c-rating-calculator/i18n/de.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/en.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/es.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/id.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/ja.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/ko.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/nl.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/pl.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/ru.ts +184 -184
- package/src/tool/drone-battery-c-rating-calculator/i18n/sv.ts +189 -189
- package/src/tool/drone-battery-c-rating-calculator/i18n/tr.ts +187 -187
- package/src/tool/drone-battery-c-rating-calculator/i18n/zh.ts +192 -192
- package/src/tool/drone-flight-time/component.astro +187 -187
- package/src/tool/drone-flight-time/seo.astro +15 -15
- package/src/tool/drone-motor-propeller-calculator/i18n/de.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/fr.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/id.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/it.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/ja.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/ko.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/nl.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/pl.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/pt.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/ru.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/sv.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/tr.ts +1 -1
- package/src/tool/drone-motor-propeller-calculator/i18n/zh.ts +2 -2
- package/src/tool/drone-motor-propeller-calculator/seo.astro +7 -4
- package/src/tool/fpv-drone-thrust-to-weight-ratio/i18n/id.ts +1 -1
- package/src/tool/fpv-drone-thrust-to-weight-ratio/i18n/it.ts +1 -1
- package/src/tool/fpv-drone-thrust-to-weight-ratio/i18n/nl.ts +1 -1
- package/src/tool/fpv-drone-thrust-to-weight-ratio/i18n/pt.ts +1 -1
- package/src/tool/fpv-drone-thrust-to-weight-ratio/i18n/ru.ts +1 -1
- package/src/tool/fpv-drone-thrust-to-weight-ratio/i18n/sv.ts +2 -2
- package/src/tool/fpv-drone-thrust-to-weight-ratio/i18n/tr.ts +1 -1
- package/src/tool/gps-coordinates-converter/component.astro +275 -275
- 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 }} />}
|
|
@@ -147,7 +147,7 @@ const seo: SEOSection[] = [
|
|
|
147
147
|
},
|
|
148
148
|
{
|
|
149
149
|
type: 'paragraph',
|
|
150
|
-
html: 'Propellerschub hängt von der Luftdichte und Geometrie ab. Reale Prüfstandsdaten des Herstellers ermöglichen die genaueste Kalibrierung des Rechners.',
|
|
150
|
+
html: 'Propellerschub hängt von der Luftdichte und Geometrie ab. Reale Prüfstandsdaten des Herstellers ermöglichen die genaueste Kalibrierung des Rechners. Vergleichen Sie mehrere Motor-Propeller-Kombinationen unter denselben Bedingungen. Gesamtgewicht, Spannung unter Last und tatsächlicher Wirkungsgrad verändern Schub und Stromaufnahme. Nutzen Sie die Berechnung nicht ungeprüft für ein anderes Modell: Messen Sie Strom und Temperatur am Prüfstand und lassen Sie vor dem Erstflug ausreichend Reserve.',
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
153
|
type: 'tip',
|
|
@@ -147,7 +147,7 @@ const seo: SEOSection[] = [
|
|
|
147
147
|
},
|
|
148
148
|
{
|
|
149
149
|
type: 'paragraph',
|
|
150
|
-
html: 'La poussée dépend de la géométrie de hélice et de la densité de l air. Un point de mesure réel apporte la meilleure calibration.',
|
|
150
|
+
html: 'La poussée dépend de la géométrie de l hélice et de la densité de l air. Un point de mesure réel apporte la meilleure calibration. Utilisez les résultats pour comparer plusieurs couples moteur-hélice dans les mêmes conditions. Le poids total, la tension sous charge et le rendement réel peuvent modifier la poussée et le courant. Faites un essai sur banc, surveillez la température et gardez une marge avant le premier vol.',
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
153
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'Daya dorong baling baling bergantung pada kerapatan udara dan bentuk bilah. Data uji nyata memberikan kalibrasi paling akurat.',
|
|
140
|
+
html: 'Daya dorong baling baling bergantung pada kerapatan udara dan bentuk bilah. Data uji nyata memberikan kalibrasi paling akurat. Gunakan hasil ini untuk membandingkan motor dan baling-baling dalam kondisi yang sama. Berat total, tegangan saat dibebani, pengendali, dan pemasangan dapat mengubah daya dorong serta arus. Ukur suhu dan konsumsi di bangku uji, sisakan cadangan sebelum penerbangan pertama, dan verifikasi konfigurasi Anda sendiri.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'La spinta dell elica dipende dalla densità dell aria e dalla geometria. Un dato di banco reale offre la calibrazione più accurata.',
|
|
140
|
+
html: 'La spinta dell elica dipende dalla densità dell aria e dalla geometria. Un dato di banco reale offre la calibrazione più accurata. Usa il risultato per confrontare più combinazioni nelle stesse condizioni, senza trasferire automaticamente un dato da un modello all\'altro. Peso, tensione sotto carico, regolatore e installazione cambiano spinta e corrente. Misura temperatura e assorbimento al banco, poi modifica la configurazione per gradi.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: '
|
|
140
|
+
html: 'プロペラの推力は空気密度やブレード形状に依存します。実測データを使用することで高精度な補正が可能です。計算結果は同じ条件でモーターとプロペラを比較するために使い、機体重量、負荷時の電圧、ESC、取り付け状態も確認してください。初飛行前に電流と温度を測定し、余裕を残して設定を調整します。',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: '프로펠러 추력은 공기 밀도와 형상에 따라 달라집니다. 실측 데이터를 사용하면 가장 정확한 교정이 가능합니다.',
|
|
140
|
+
html: '프로펠러 추력은 공기 밀도와 형상에 따라 달라집니다. 실측 데이터를 사용하면 가장 정확한 교정이 가능합니다. 같은 조건에서 모터와 프로펠러를 비교하고 기체 무게, 부하 전압, ESC, 장착 상태도 확인하세요. 첫 비행 전 전류와 온도를 측정하고 충분한 여유를 두고 설정을 조정합니다.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'Propellerstuwkracht hangt af van de luchtdichtheid en vorm. Een echt testpunt biedt de nauwkeurigste kalibratie.',
|
|
140
|
+
html: 'Propellerstuwkracht hangt af van de luchtdichtheid en vorm. Een echt testpunt biedt de nauwkeurigste kalibratie. Gebruik de uitkomst om motoren en propellers onder dezelfde omstandigheden te vergelijken. Gewicht, spanning onder belasting, regelaar en installatie veranderen de stuwkracht en stroom. Meet temperatuur en verbruik op de testbank en houd reserve voor de eerste vlucht. Gebruik waarden van een ander model daarom alleen als vergelijking.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'Ciąg śmigła zależy od gęstości powietrza i kształtu łopat. Rzeczywisty pomiar z hamowni zapewnia najwyższą dokładność.',
|
|
140
|
+
html: 'Ciąg śmigła zależy od gęstości powietrza i kształtu łopat. Rzeczywisty pomiar z hamowni zapewnia najwyższą dokładność. Użyj wyniku do porównania silników i śmigieł w tych samych warunkach. Masa, napięcie pod obciążeniem, regulator i montaż zmieniają ciąg oraz prąd. Zmierz temperaturę i pobór na stanowisku, a przed pierwszym lotem zostaw bezpieczny zapas. Traktuj dane z innego modelu wyłącznie jako punkt odniesienia i sprawdź całą konfigurację przed użyciem.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'O empuxo da hélice depende da geometria e densidade do ar. Dados de teste medidos oferecem a melhor calibração.',
|
|
140
|
+
html: 'O empuxo da hélice depende da geometria e densidade do ar. Dados de teste medidos oferecem a melhor calibração. Use o resultado para comparar combinações nas mesmas condições, considerando peso, tensão sob carga, controlador e instalação. Meça corrente e temperatura em bancada e ajuste a configuração gradualmente antes do primeiro voo. Não transfira automaticamente um valor de outro modelo: bateria, montagem e eficiência real também alteram o resultado.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'Тяга винта зависит от профиля лопастей и плотности воздуха. Реальные стендовые данные обеспечивают точную калибровку.',
|
|
140
|
+
html: 'Тяга винта зависит от профиля лопастей и плотности воздуха. Реальные стендовые данные обеспечивают точную калибровку. Используйте расчет для сравнения моторов и винтов в одинаковых условиях. Масса, напряжение под нагрузкой, регулятор и установка меняют тягу и ток. Измеряйте температуру и потребление на стенде, а перед первым полетом оставляйте запас. Данные другой модели подходят только как ориентир и требуют проверки на вашей сборке.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'Propellerdragkraft beror på luftdensitet och form. Uppmätta testbänksdata ger den mest exakta kalibreringen.',
|
|
140
|
+
html: 'Propellerdragkraft beror på luftdensitet och form. Uppmätta testbänksdata ger den mest exakta kalibreringen. Använd resultatet för att jämföra motorer och propellrar under samma förhållanden. Total vikt, spänning under belastning, reglage och montering påverkar dragkraft och ström. Mät temperatur och förbrukning på testbänken, lämna marginal före första flygningen och verifiera alltid den egna modellen. Dokumentera mätningarna så att du kan följa ändringar i konfigurationen.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -137,7 +137,7 @@ const seo: SEOSection[] = [
|
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: 'Pervane itişi hava yoğunluğuna ve geometriye bağlıdır. Ölçülen test verileri en doğru kalibrasyonu sağlar.',
|
|
140
|
+
html: 'Pervane itişi hava yoğunluğuna ve geometriye bağlıdır. Ölçülen test verileri en doğru kalibrasyonu sağlar. Sonucu aynı koşullarda motor ve pervane seçeneklerini karşılaştırmak için kullanın. Toplam ağırlık, yük altındaki voltaj, hız kontrol cihazı ve montaj şekli itişi ve akımı değiştirir. Test standında sıcaklık ile tüketimi ölçün ve ilk uçuştan önce güvenli bir pay bırakın. Başka bir modelin verilerini yalnızca referans kabul ederek kendi kurulumunuzu ayrıca doğrulayın.',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -132,12 +132,12 @@ const seo: SEOSection[] = [
|
|
|
132
132
|
},
|
|
133
133
|
{
|
|
134
134
|
type: 'title',
|
|
135
|
-
text: '
|
|
135
|
+
text: '测试数据的重要性',
|
|
136
136
|
level: 2,
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
type: 'paragraph',
|
|
140
|
-
html: '
|
|
140
|
+
html: '螺旋桨推力取决于空气密度和几何形状。使用厂家台测数据能提供最准确的校准。请在相同条件下比较不同的电机和螺旋桨,并同时检查起飞重量、负载电压、电调和安装状态。首次飞行前测量电流与温度,保留足够余量,再逐步调整配置。其他机型的数据只能作为参考,必须结合自己的电池、机架和实际测试确认。记录每次测试结果,便于复核和改进。不要忽略电池电量变化和环境温度,飞行前再次检查连接,确认设备状态正常。',
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
type: 'tip',
|
|
@@ -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
|
-
|
|
7
|
+
locale?: KnownLocale;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
const {
|
|
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
|
|
15
|
+
{content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -149,7 +149,7 @@ const seoSections: SEOSection[] = [
|
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
type: 'paragraph',
|
|
152
|
-
html: 'Untuk drone dengan TWR di atas 10 banding 1, disarankan mengaktifkan Throttle PID Attenuation (TPA) pada Betaflight guna mencegah osilasi motor pada kecepatan tinggi.',
|
|
152
|
+
html: 'Untuk drone dengan TWR di atas 10 banding 1, disarankan mengaktifkan Throttle PID Attenuation (TPA) pada Betaflight guna mencegah osilasi motor pada kecepatan tinggi. Gunakan rasio dorong terhadap berat untuk membandingkan konfigurasi dalam kondisi yang sama. Perhitungkan berat saat lepas landas, tegangan ketika dibebani, konsumsi nyata, dan cadangan yang dibutuhkan untuk gaya terbang Anda. Uji secara terkendali sebelum terbang dalam kondisi berat.',
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
type: 'tip',
|
|
@@ -149,7 +149,7 @@ const seoSections: SEOSection[] = [
|
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
type: 'paragraph',
|
|
152
|
-
html: 'Per droni con TWR superiore a 10 a 1 è opportuno abilitare la funzione Throttle PID Attenuation (TPA) in Betaflight per prevenire oscillazioni a pieno gas e preservare la stabilità.',
|
|
152
|
+
html: 'Per droni con TWR superiore a 10 a 1 è opportuno abilitare la funzione Throttle PID Attenuation (TPA) in Betaflight per prevenire oscillazioni a pieno gas e preservare la stabilità. Il rapporto spinta peso è un riferimento per confrontare configurazioni, non una garanzia di comportamento in volo. Considera il peso pronto al decollo, il consumo reale, la temperatura delle celle e il margine necessario per il tuo stile di pilotaggio. Una prova controllata permette di verificare la risposta prima di usare il drone in condizioni impegnative.',
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
type: 'tip',
|
|
@@ -149,7 +149,7 @@ const seoSections: SEOSection[] = [
|
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
type: 'paragraph',
|
|
152
|
-
html: 'Voor drones met een TWR hoger dan 10 op 1 is het verstandig om Throttle PID Attenuation (TPA) in te stellen in Betaflight om trillingen bij volgas te voorkomen.',
|
|
152
|
+
html: 'Voor drones met een TWR hoger dan 10 op 1 is het verstandig om Throttle PID Attenuation (TPA) in te stellen in Betaflight om trillingen bij volgas te voorkomen. Gebruik de stuwkracht-gewichtsverhouding om configuraties onder dezelfde omstandigheden te vergelijken. Houd rekening met het startgewicht, de spanning onder belasting, het werkelijke verbruik en de gewenste reserve. Een gecontroleerde test laat zien hoe het model reageert voordat je in veeleisende omstandigheden vliegt.',
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
type: 'tip',
|
|
@@ -149,7 +149,7 @@ const seoSections: SEOSection[] = [
|
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
type: 'paragraph',
|
|
152
|
-
html: 'Para drones com TWR superior a 10 para 1, recomenda-se configurar a atenuação Throttle PID Attenuation (TPA) no Betaflight para evitar vibrações indesejadas em linha reta.',
|
|
152
|
+
html: 'Para drones com TWR superior a 10 para 1, recomenda-se configurar a atenuação Throttle PID Attenuation (TPA) no Betaflight para evitar vibrações indesejadas em linha reta. A relação empuxo-peso serve para comparar configurações, mas não prevê sozinha o comportamento em voo. Considere o peso pronto para decolar, o consumo real, a temperatura das células e a margem necessária para seu estilo de pilotagem. Compare o empuxo em diferentes níveis de aceleração, registre as medições e faça ajustes graduais antes de voar em condições exigentes.',
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
type: 'tip',
|
|
@@ -149,7 +149,7 @@ const seoSections: SEOSection[] = [
|
|
|
149
149
|
},
|
|
150
150
|
{
|
|
151
151
|
type: 'paragraph',
|
|
152
|
-
html: 'Для сборок с TWR выше 10 к 1 рекомендуется включить Throttle PID Attenuation (TPA) в Betaflight, чтобы предотвратить высокочастотные колебания на полном газе.',
|
|
152
|
+
html: 'Для сборок с TWR выше 10 к 1 рекомендуется включить Throttle PID Attenuation (TPA) в Betaflight, чтобы предотвратить высокочастотные колебания на полном газе. Используйте тяговооруженность для сравнения конфигураций, но учитывайте полетный вес, просадку напряжения, реальный ток и запас для своего стиля пилотирования. Контролируемый тест помогает проверить реакцию модели перед полетом в сложных условиях.',
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
type: 'tip',
|