@jjlmoya/utils-drones 1.30.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.
- package/package.json +1 -1
- package/src/category/i18n/en.ts +1 -1
- package/src/category/i18n/fr.ts +1 -1
- package/src/category/index.ts +2 -0
- package/src/components/PreviewNavSidebar.astro +116 -116
- package/src/components/PreviewToolbar.astro +143 -143
- package/src/data.ts +1 -1
- package/src/entries.ts +4 -0
- package/src/env.d.ts +1 -1
- package/src/index.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/faq_count.test.ts +1 -1
- package/src/tests/locale_completeness.test.ts +1 -2
- package/src/tests/mocks/astro_mock.js +1 -1
- package/src/tests/no_h1_in_components.test.ts +1 -1
- package/src/tests/tool_validation.test.ts +17 -17
- 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 +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/sv.ts +192 -192
- package/src/tool/drone-battery-c-rating-calculator/i18n/tr.ts +192 -192
- 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/bibliography.astro +6 -0
- package/src/tool/drone-motor-propeller-calculator/bibliography.ts +12 -0
- package/src/tool/drone-motor-propeller-calculator/component.astro +109 -0
- package/src/tool/drone-motor-propeller-calculator/controller.ts +174 -0
- package/src/tool/drone-motor-propeller-calculator/dom-views.ts +78 -0
- package/src/tool/drone-motor-propeller-calculator/drone-motor-propeller-calculator.css +562 -0
- package/src/tool/drone-motor-propeller-calculator/entry.ts +27 -0
- package/src/tool/drone-motor-propeller-calculator/evaluator.ts +14 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/de.ts +202 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/en.ts +202 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/es.ts +202 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/fr.ts +202 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/id.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/it.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/ja.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/ko.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/nl.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/pl.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/pt.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/ru.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/sv.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/tr.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/i18n/zh.ts +192 -0
- package/src/tool/drone-motor-propeller-calculator/index.ts +11 -0
- package/src/tool/drone-motor-propeller-calculator/logic.test.ts +56 -0
- package/src/tool/drone-motor-propeller-calculator/logic.ts +108 -0
- package/src/tool/drone-motor-propeller-calculator/seo.astro +15 -0
- package/src/tool/drone-motor-propeller-calculator/storage.ts +26 -0
- package/src/tool/drone-motor-propeller-calculator/ui.ts +55 -0
- package/src/tool/gps-coordinates-converter/component.astro +275 -275
- package/src/tool/gps-coordinates-converter/seo.astro +15 -15
- package/src/tools.ts +3 -0
|
@@ -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 }} />}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BibliographyEntry } from '../../types';
|
|
2
|
+
|
|
3
|
+
export const BIBLIOGRAPHY_ITEMS: BibliographyEntry[] = [
|
|
4
|
+
{
|
|
5
|
+
name: 'NASA Thrust Coefficient Definition for Propellers',
|
|
6
|
+
url: 'https://ntrs.nasa.gov/api/citations/19900020078/downloads/19900020078.pdf',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
name: 'University of Illinois UIUC Propeller Data Site',
|
|
10
|
+
url: 'https://m-selig.ae.illinois.edu/props/propDB.html',
|
|
11
|
+
},
|
|
12
|
+
];
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { DroneMotorPropellerUI } from './ui';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
ui: DroneMotorPropellerUI;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const { ui } = Astro.props;
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<div class="drone-motor-tool" data-drone-motor-calculator data-unit="metric">
|
|
12
|
+
<script is:inline type="application/json" data-drone-motor-ui set:html={JSON.stringify(ui)}></script>
|
|
13
|
+
<div class="tool-controls">
|
|
14
|
+
<section class="setup-card presets-card" aria-labelledby="presets-title">
|
|
15
|
+
<div class="eyebrow">{ui.presetsHeader}</div>
|
|
16
|
+
<div class="preset-row" id="presets-title">
|
|
17
|
+
<button type="button" class="preset-button" data-preset="tiny-cruiser">{ui.presetTinyCruiser}</button>
|
|
18
|
+
<button type="button" class="preset-button is-selected" data-preset="freestyle">{ui.presetFreestyle}</button>
|
|
19
|
+
<button type="button" class="preset-button" data-preset="long-range">{ui.presetLongRange}</button>
|
|
20
|
+
<button type="button" class="preset-button" data-preset="cinelifter">{ui.presetCinelifter}</button>
|
|
21
|
+
</div>
|
|
22
|
+
</section>
|
|
23
|
+
|
|
24
|
+
<section class="setup-card unit-card" aria-labelledby="unit-title">
|
|
25
|
+
<div class="eyebrow" id="unit-title">{ui.unitHeader}</div>
|
|
26
|
+
<div class="unit-toggle" role="group" aria-label={ui.unitHeader}>
|
|
27
|
+
<button type="button" class="unit-button is-selected" data-unit="metric" aria-pressed="true">{ui.metricUnit}</button>
|
|
28
|
+
<button type="button" class="unit-button" data-unit="imperial" aria-pressed="false">{ui.imperialUnit}</button>
|
|
29
|
+
</div>
|
|
30
|
+
</section>
|
|
31
|
+
|
|
32
|
+
<section class="setup-card specs-card" aria-labelledby="setup-title">
|
|
33
|
+
<div class="section-heading">
|
|
34
|
+
<div class="eyebrow">01</div>
|
|
35
|
+
<h2 id="setup-title">{ui.setupHeader}</h2>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="input-grid">
|
|
38
|
+
<label class="field"><span>{ui.motorKvLabel}</span><input data-field="motorKv" type="number" value="1950" min="100" max="10000" step="50" inputmode="decimal" /><small>KV</small></label>
|
|
39
|
+
<label class="field"><span>{ui.batteryVoltageLabel}</span><input data-field="batteryVoltage" type="number" value="22.2" min="1" max="60" step="0.1" inputmode="decimal" /><small>V</small></label>
|
|
40
|
+
<label class="field"><span>{ui.propDiameterLabel}</span><input data-field="propDiameterInches" type="number" value="5.1" min="1" max="30" step="0.1" inputmode="decimal" /><small data-unit-label="length">in</small></label>
|
|
41
|
+
<label class="field"><span>{ui.propPitchLabel}</span><input data-field="propPitchInches" type="number" value="4.3" min="0.5" max="25" step="0.1" inputmode="decimal" /><small data-unit-label="length">in</small></label>
|
|
42
|
+
<div class="choice-field"><span>{ui.bladeCountLabel}</span><div class="choice-row" role="group" aria-label={ui.bladeCountLabel}><button type="button" data-choice="bladeCount" data-value="2" aria-pressed="false">{ui.twoBlades}</button><button type="button" class="is-selected" data-choice="bladeCount" data-value="3" aria-pressed="true">{ui.threeBlades}</button><button type="button" data-choice="bladeCount" data-value="4" aria-pressed="false">{ui.fourBlades}</button></div></div>
|
|
43
|
+
<div class="choice-field"><span>{ui.motorCountLabel}</span><div class="choice-row" role="group" aria-label={ui.motorCountLabel}><button type="button" data-choice="motorCount" data-value="2" aria-pressed="false">{ui.twoMotors}</button><button type="button" class="is-selected" data-choice="motorCount" data-value="4" aria-pressed="true">{ui.fourMotors}</button><button type="button" data-choice="motorCount" data-value="6" aria-pressed="false">{ui.sixMotors}</button><button type="button" data-choice="motorCount" data-value="8" aria-pressed="false">{ui.eightMotors}</button></div></div>
|
|
44
|
+
<label class="field field-wide"><span>{ui.droneWeightLabel}</span><input data-field="droneWeightGrams" type="number" value="700" min="1" max="50000" step="10" inputmode="decimal" /><small data-unit-label="mass">g</small></label>
|
|
45
|
+
</div>
|
|
46
|
+
</section>
|
|
47
|
+
|
|
48
|
+
<section class="setup-card bench-card" aria-labelledby="bench-title">
|
|
49
|
+
<div class="section-heading">
|
|
50
|
+
<div class="eyebrow">02</div>
|
|
51
|
+
<h2 id="bench-title">{ui.benchDataHeader} <span>{ui.optionalLabel}</span></h2>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="input-grid bench-grid">
|
|
54
|
+
<label class="field"><span>{ui.benchThrustLabel}</span><input data-field="benchThrustGrams" type="number" value="1900" min="1" max="30000" step="10" inputmode="decimal" /><small data-unit-label="mass">g</small></label>
|
|
55
|
+
<label class="field"><span>{ui.benchVoltageLabel}</span><input data-field="benchVoltage" type="number" value="22.2" min="1" max="60" step="0.1" inputmode="decimal" /><small>V</small></label>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="bench-foot">
|
|
58
|
+
<p class="fine-print"><span data-output="source-note">{ui.benchBasedLabel}</span></p>
|
|
59
|
+
<button type="button" class="clear-button" data-action="clear-bench">{ui.clearBenchData}</button>
|
|
60
|
+
</div>
|
|
61
|
+
</section>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<section class="results-card" aria-labelledby="results-title">
|
|
65
|
+
<div class="results-topline">
|
|
66
|
+
<div>
|
|
67
|
+
<div class="eyebrow">03</div>
|
|
68
|
+
<h2 id="results-title">{ui.resultsHeader}</h2>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="status-pill" data-output="status" aria-live="polite">{ui.headroomStatus}</div>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="wind-tunnel">
|
|
73
|
+
<div class="tunnel-label tunnel-label-top">{ui.thrustAxisLabel}</div>
|
|
74
|
+
<div class="thrust-stream stream-left"></div>
|
|
75
|
+
<div class="thrust-stream stream-right"></div>
|
|
76
|
+
<div class="rotor-stage" data-role="rotor-stage">
|
|
77
|
+
<div class="rotor-shadow"></div>
|
|
78
|
+
<div class="rotor-disc">
|
|
79
|
+
<span class="rotor-blade blade-one"></span>
|
|
80
|
+
<span class="rotor-blade blade-two"></span>
|
|
81
|
+
<span class="rotor-blade blade-three"></span>
|
|
82
|
+
<span class="rotor-blade blade-four"></span>
|
|
83
|
+
<span class="rotor-hub"></span>
|
|
84
|
+
</div>
|
|
85
|
+
<div class="rotor-readout"><strong data-output="loaded-rpm">15,000 rpm</strong><span>{ui.sceneCaption}</span></div>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="weight-marker"><span class="marker-line"></span><span>{ui.weightAxisLabel}</span></div>
|
|
88
|
+
<div class="lift-beam"><span></span></div>
|
|
89
|
+
</div>
|
|
90
|
+
<p class="status-advice" data-output="status-advice" aria-live="polite">{ui.headroomAdvice}</p>
|
|
91
|
+
<div class="result-grid">
|
|
92
|
+
<div class="result-cell accent"><span>{ui.thrustPerMotorLabel}</span><strong data-output="thrust-per-motor">1,900 g</strong></div>
|
|
93
|
+
<div class="result-cell accent"><span>{ui.totalThrustLabel}</span><strong data-output="total-thrust">7,600 g</strong></div>
|
|
94
|
+
<div class="result-cell"><span>{ui.pitchSpeedLabel}</span><strong data-output="pitch-speed">1,250 km/h</strong></div>
|
|
95
|
+
<div class="result-cell"><span>{ui.totalPowerLabel}</span><strong data-output="total-power">1,200 W</strong></div>
|
|
96
|
+
<div class="result-cell"><span>{ui.totalCurrentLabel}</span><strong data-output="total-current">54 A</strong></div>
|
|
97
|
+
<div class="result-cell"><span>{ui.thrustMarginLabel}</span><strong data-output="thrust-margin">986%</strong></div>
|
|
98
|
+
<div class="result-cell"><span>{ui.hoverThrottleLabel}</span><strong data-output="hover-throttle">30%</strong></div>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="notes-row"><p>{ui.safetyNote}</p><p>{ui.modelNote}</p></div>
|
|
101
|
+
</section>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<script>
|
|
105
|
+
import { initDroneMotorPropellerCalculator } from './controller';
|
|
106
|
+
|
|
107
|
+
const root = document.querySelector<HTMLElement>('[data-drone-motor-calculator]');
|
|
108
|
+
if (root) initDroneMotorPropellerCalculator(root);
|
|
109
|
+
</script>
|