1
0
Fork 0
mirror of https://github.com/betaflight/betaflight-configurator.git synced 2025-07-20 06:45:12 +03:00
Setup vue with gulp and initial components:

* Status Bar
* Logo
* Battery Legend
This commit is contained in:
Tomas Chmelevskij 2020-10-10 13:31:07 +02:00
parent 14d98c4dea
commit d6cee386e8
27 changed files with 1511 additions and 283 deletions

View file

@ -0,0 +1,30 @@
<div class="motorOutputReorderComponent">
<h3 i18n="motorsRemapDialogTitle" class="motorOutputReorderComponentHeader"></h3>
<div class="componentContent" id="dialogMotorOutputReorderMainContent">
<canvas id="motorOutputReorderCanvas"></canvas>
<div id="motorOutputReorderActionPanel">
<h4 id="motorOutputReorderActionHint"></h4>
</div>
<div id="motorOutputReorderSaveStartOverButtonsPanel">
<a href="#" id="motorsRemapDialogSave" class="regular-button left" i18n="motorsRemapDialogSave"></a>
<a href="#" id="motorsRemapDialogStartOver" class="regular-button left" i18n="motorsRemapDialogStartOver"></a>
</div>
</div>
<div class="componentContent" id="dialogMotorOutputReorderWarning">
<div class="notice">
<p class="motorsRemapDialogRiskNoticeText" i18n="motorsRemapDialogRiskNotice"></p>
<div class="motorsRemapToggleParentContainer">
<div class="motorsRemapToggleNarrow">
<input id="motorsEnableTestMode-dialogMotorOutputReorder" type="checkbox" class="toggle"/>
</div>
<div class="motorsRemapToggleWide">
<span class="motorsEnableTestMode motorsRemapDialogRiskNoticeText" i18n="motorsRemapDialogUnderstandRisks"></span>
</div>
</div>
<div class="motorsRemapDialogRExplanationText" i18n="motorsRemapDialogExplanations"></div>
</div>
<div class="buttons">
<a href="#" id="dialogMotorOutputReorderAgreeButton" class="regular-button" i18n="motorOutputReorderDialogAgree"></a>
</div>
</div>
</div>

View file

@ -0,0 +1,269 @@
'use strict';
class MotorOutputReorderCanvas
{
constructor(canvas, droneConfiguration, motorClickCallback, spinMotorCallback)
{
this._spinMotorCallback = spinMotorCallback;
this._canvas = canvas;
this._motorClickCallback = motorClickCallback;
this._width = this._canvas.width();
this._height = this._canvas.height();
this._screenSize = Math.min(this._width, this._height);
this._config = new MotorOutputReorderConfig(this._screenSize);
// no component resize allowing yet
this._canvas.prop({
width: this._width,
height: this._height,
});
this._droneConfiguration = droneConfiguration;
this._ctx = this._canvas[0].getContext("2d");
this._ctx.translate(this._width / 2, this._height / 2);
this._canvas.mousemove((event) =>
{
this._onMouseMove(event);
});
this._canvas.mouseleave(() =>
{
this._onMouseLeave();
});
this._canvas.mousedown(() =>
{
this._onMouseDown();
});
this._canvas.mouseup(() =>
{
this._onMouseUp(event);
});
this._canvas.click(() =>
{
this._onMouseClick();
});
this.startOver();
}
pause()
{
this._keepDrawing = false;
}
startOver()
{
this.readyMotors = []; //motors that already being selected for remapping by user
this.remappingReady = false;
this._motorIndexToSpinOnMouseDown = -1;
this._keepDrawing = true;
this._mouse = {x : 0, y: 0};
window.requestAnimationFrame(() =>
{
this._drawOnce();
});
}
_drawOnce()
{
this._ctx.clearRect(-this._width / 2, -this._height / 2, this._width, this._height);
this._drawFrame();
this._drawDirectionArrow();
this._markMotors();
this._drawMotors();
if (this._keepDrawing) {
window.requestAnimationFrame(() =>
{
this._drawOnce();
});
}
}
_onMouseDown()
{
if (this.remappingReady) {
this._motorIndexToSpinOnMouseDown = this._getMouseHoverMotorIndex();
if (this._spinMotorCallback) {
this._spinMotorCallback(this._motorIndexToSpinOnMouseDown);
}
}
}
_onMouseUp()
{
if (-1 !== this._motorIndexToSpinOnMouseDown) {
this._motorIndexToSpinOnMouseDown = -1;
if (this._spinMotorCallback) {
this._spinMotorCallback(this._motorIndexToSpinOnMouseDown);
}
}
}
_onMouseClick()
{
const motorIndex = this._getMouseHoverMotorIndex();
if (this._motorClickCallback && -1 !== motorIndex && !this.readyMotors.includes(motorIndex)) {
this._motorClickCallback(motorIndex);
}
}
_onMouseMove(event)
{
const boundingRect = this._canvas[0].getBoundingClientRect();
this._mouse.x = event.clientX - boundingRect.left - this._width / 2;
this._mouse.y = event.clientY - boundingRect.top - this._height / 2;
}
_onMouseLeave()
{
this._mouse.x = Number.MIN_SAFE_INTEGER;
this._mouse.y = Number.MIN_SAFE_INTEGER;
if (-1 !== this._motorIndexToSpinOnMouseDown) {
this._motorIndexToSpinOnMouseDown = -1;
if (this._spinMotorCallback) {
this._spinMotorCallback(this._motorIndexToSpinOnMouseDown);
}
}
}
_markMotors()
{
const motors = this._config[this._droneConfiguration].Motors;
const mouseHoverMotorIndex = this._getMouseHoverMotorIndex();
if (-1 === this._motorIndexToSpinOnMouseDown) {
for (let i = 0; i < this.readyMotors.length; i++) {
const motorIndex = this.readyMotors[i];
this._ctx.beginPath();
this._ctx.arc(motors[motorIndex].x, motors[motorIndex].y, this._config[this._droneConfiguration].PropRadius, 0, 2 * Math.PI);
this._ctx.closePath();
this._ctx.fillStyle = this._config.MotorReadyColor;
this._ctx.fill();
}
if (-1 !== mouseHoverMotorIndex && !this.readyMotors.includes(mouseHoverMotorIndex)) {
this._ctx.beginPath();
this._ctx.arc(motors[mouseHoverMotorIndex].x, motors[mouseHoverMotorIndex].y, this._config[this._droneConfiguration].PropRadius, 0, 2 * Math.PI);
this._ctx.closePath();
this._ctx.fillStyle = this._config.MotorMouseHoverColor;
this._ctx.fill();
}
} else {
const spinningMotor = this._motorIndexToSpinOnMouseDown;
for (let i = 0; i < motors.length; i++) {
this._ctx.fillStyle = this._config.MotorReadyColor;
if (spinningMotor === i) {
this._ctx.fillStyle = this._config.MotorSpinningColor;
} else if (mouseHoverMotorIndex === i) {
this._ctx.fillStyle = this._config.MotorMouseHoverColor;
}
this._ctx.beginPath();
this._ctx.arc(motors[i].x, motors[i].y, this._config[this._droneConfiguration].PropRadius, 0, 2 * Math.PI);
this._ctx.closePath();
this._ctx.fill();
}
}
}
_getMouseHoverMotorIndex()
{
const x = this._mouse.x;
const y = this._mouse.y;
let result = -1;
let currentDist = Number.MAX_SAFE_INTEGER;
const motors = this._config[this._droneConfiguration].Motors;
for (let i = 0; i < motors.length; i++) {
const dist = Math.sqrt((x - motors[i].x) * (x - motors[i].x) + (y - motors[i].y) * (y - motors[i].y));
if (dist < this._config[this._droneConfiguration].PropRadius && dist < currentDist) {
currentDist = dist;
result = i;
}
}
return result;
}
_drawMotors()
{
this._ctx.lineWidth = this._config.PropEdgeLineWidth;
this._ctx.strokeStyle = this._config.PropEdgeColor;
const motors = this._config[this._droneConfiguration].Motors;
for (let i = 0; i < motors.length; i++) {
this._ctx.beginPath();
this._ctx.arc(motors[i].x, motors[i].y, this._config[this._droneConfiguration].PropRadius, 0, 2 * Math.PI);
this._ctx.stroke();
}
}
_drawDirectionArrow()
{
this._ctx.beginPath();
this._ctx.moveTo(this._config.DirectionArrowPoints[0].x, this._config.DirectionArrowPoints[0].y);
for (let i = 1; i < this._config.DirectionArrowPoints.length; i++) {
this._ctx.lineTo(this._config.DirectionArrowPoints[i].x, this._config.DirectionArrowPoints[i].y);
}
this._ctx.closePath();
this._ctx.fillStyle = this._config.ArrowColor;
this._ctx.fill();
}
_drawFrame()
{
this._ctx.beginPath();
this._ctx.lineWidth = this._config[this._droneConfiguration].ArmWidth;
this._ctx.lineCap = "round";
this._ctx.strokeStyle = this._config.FrameColor;
const motors = this._config[this._droneConfiguration].Motors;
switch(this._droneConfiguration) {
case "Quad X":
case "Quad +":
this._ctx.moveTo(motors[0].x, motors[0].y);
this._ctx.lineTo(motors[3].x, motors[3].y);
this._ctx.moveTo(motors[1].x, motors[1].y);
this._ctx.lineTo(motors[2].x, motors[2].y);
break;
case "Quad X 1234":
this._ctx.moveTo(motors[0].x, motors[0].y);
this._ctx.lineTo(motors[2].x, motors[2].y);
this._ctx.moveTo(motors[3].x, motors[3].y);
this._ctx.lineTo(motors[1].x, motors[1].y);
break;
case "Tricopter":
this._ctx.moveTo(motors[1].x, motors[1].y);
this._ctx.lineTo(motors[2].x, motors[2].y);
this._ctx.moveTo(motors[0].x, motors[0].y);
this._ctx.lineTo(motors[0].x, motors[2].y);
break;
case "Hex +":
case "Hex X":
this._ctx.moveTo(motors[0].x, motors[0].y);
this._ctx.lineTo(motors[3].x, motors[3].y);
this._ctx.moveTo(motors[1].x, motors[1].y);
this._ctx.lineTo(motors[2].x, motors[2].y);
this._ctx.moveTo(motors[4].x, motors[4].y);
this._ctx.lineTo(motors[5].x, motors[5].y);
break;
}
this._ctx.stroke();
}
}

View file

@ -0,0 +1,258 @@
'use strict';
class MotorOutputReorderComponent
{
constructor(contentDiv, onLoadedCallback, droneConfiguration, motorStopValue, motorSpinValue)
{
this._contentDiv = contentDiv;
this._onLoadedCallback = onLoadedCallback;
this._droneConfiguration = droneConfiguration;
this._motorStopValue = motorStopValue;
this._motorSpinValue = motorSpinValue;
this._config = new MotorOutputReorderConfig(100);
this._currentJerkingTimeout = -1;
this._currentJerkingMotor = -1;
this._currentSpinningMotor = -1;
this._contentDiv.load("./components/MotorOutputReordering/Body.html", () =>
{
this._setupdialog();
});
}
_readDom()
{
this._domAgreeSafetyCheckBox = $('#motorsEnableTestMode-dialogMotorOutputReorder');
this._domAgreeButton = $('#dialogMotorOutputReorderAgreeButton');
this._domStartOverButton = $('#motorsRemapDialogStartOver');
this._domSaveButton = $('#motorsRemapDialogSave');
this._domMainContentBlock = $('#dialogMotorOutputReorderMainContent');
this._domWarningContentBlock = $('#dialogMotorOutputReorderWarning');
this._domActionHintBlock = $('#motorOutputReorderActionHint');
this._domCanvas = $('#motorOutputReorderCanvas');
}
_setupdialog()
{
i18n.localizePage();
this._readDom();
this._resetGui();
this._domAgreeSafetyCheckBox.change(() =>
{
const enabled = this._domAgreeSafetyCheckBox.is(':checked');
this._domAgreeButton.toggle(enabled);
});
this._domAgreeButton.click(() =>
{
this._onAgreeButtonClicked();
});
this._domStartOverButton.click(() =>
{
this._startOver();
});
this._domSaveButton.click(() =>
{
this._save();
});
this._onLoadedCallback();
}
close()
{
this._stopAnyMotorJerking();
this._stopMotor();
this._stopUserInteraction();
this._resetGui();
}
_resetGui()
{
this._domMainContentBlock.hide();
this._domWarningContentBlock.show();
this._domAgreeButton.hide();
this._domAgreeSafetyCheckBox.prop('checked', false);
this._domAgreeSafetyCheckBox.change();
this._showSaveStartOverButtons(false);
}
_save()
{
function save_to_eeprom()
{
MSP.send_message(MSPCodes.MSP_EEPROM_WRITE, false, false, reboot);
}
function reboot()
{
GUI.log(i18n.getMessage('configurationEepromSaved'));
GUI.tab_switch_cleanup(function()
{
MSP.send_message(MSPCodes.MSP_SET_REBOOT, false, false);
reinitialiseConnection(self);
});
}
FC.MOTOR_OUTPUT_ORDER = Array.from(this._newMotorOutputReorder);
MSP.send_message(MSPCodes.MSP2_SET_MOTOR_OUTPUT_REORDERING, mspHelper.crunch(MSPCodes.MSP2_SET_MOTOR_OUTPUT_REORDERING));
save_to_eeprom();
}
_calculateNewMotorOutputReorder()
{
this._newMotorOutputReorder = [];
for (let i = 0; i < this.motorOutputReorderCanvas.readyMotors.length; i++) {
this._newMotorOutputReorder.push(this._remapMotorIndex(i));
}
}
_remapMotorIndex(motorIndex)
{
return FC.MOTOR_OUTPUT_ORDER[this.motorOutputReorderCanvas.readyMotors.indexOf(motorIndex)];
}
_startOver()
{
this._showSaveStartOverButtons(false);
this._startUserInteraction();
}
_showSaveStartOverButtons(show)
{
if (show) {
this._domStartOverButton.show();
this._domSaveButton.show();
} else {
this._domStartOverButton.hide();
this._domSaveButton.hide();
}
}
_onAgreeButtonClicked()
{
this._domActionHintBlock.text(i18n.getMessage("motorOutputReorderDialogSelectSpinningMotor"));
this._domWarningContentBlock.hide();
this._domMainContentBlock.show();
this._startUserInteraction();
}
_stopUserInteraction()
{
if (this.motorOutputReorderCanvas) {
this.motorOutputReorderCanvas.pause();
}
}
_startUserInteraction()
{
if (this.motorOutputReorderCanvas) {
this.motorOutputReorderCanvas.startOver();
} else {
this.motorOutputReorderCanvas = new MotorOutputReorderCanvas(this._domCanvas,
this._droneConfiguration,
(motorIndex) =>
{ // motor click callback
this._onMotorClick(motorIndex);
},
(motorIndex) =>
{ // motor spin callback
let indexToSpin = -1;
if (-1 !== motorIndex) {
indexToSpin = this.motorOutputReorderCanvas.readyMotors.indexOf(motorIndex);
}
this._spinMotor(indexToSpin);
},
);
}
this._startMotorJerking(0);
}
_stopAnyMotorJerking()
{
if (-1 !== this._currentJerkingTimeout) {
clearTimeout(this._currentJerkingTimeout);
this._currentJerkingTimeout = -1;
this._spinMotor(-1);
}
this._currentJerkingMotor = -1;
}
_startMotorJerking(motorIndex)
{
this._stopAnyMotorJerking();
this._currentJerkingMotor = motorIndex;
this._motorStartTimeout(motorIndex);
}
_motorStartTimeout(motorIndex)
{
this._spinMotor(motorIndex);
this._currentJerkingTimeout = setTimeout(() =>
{
this._motorStopTimeout(motorIndex);
}, 250);
}
_motorStopTimeout(motorIndex)
{
this._spinMotor(-1);
this._currentJerkingTimeout = setTimeout(() =>
{
this._motorStartTimeout(motorIndex);
}, 500);
}
_spinMotor(motorIndex)
{
this._currentSpinningMotor = motorIndex;
const buffer = [];
for (let i = 0; i < this._config[this._droneConfiguration].Motors.length; i++) {
if (i === motorIndex) {
buffer.push16(this._motorSpinValue);
} else {
buffer.push16(this._motorStopValue);
}
}
MSP.send_message(MSPCodes.MSP_SET_MOTOR, buffer);
}
_stopMotor()
{
if (-1 !== this._currentSpinningMotor) {
this._spinMotor(-1);
}
}
_onMotorClick(motorIndex)
{
this.motorOutputReorderCanvas.readyMotors.push(motorIndex);
this._currentJerkingMotor ++;
if (this._currentJerkingMotor < this._config[this._droneConfiguration].Motors.length) {
this._startMotorJerking(this._currentJerkingMotor);
} else {
this._stopAnyMotorJerking();
this._domActionHintBlock.text(i18n.getMessage("motorOutputReorderDialogRemapIsDone"));
this._calculateNewMotorOutputReorder();
this.motorOutputReorderCanvas.remappingReady = true;
this._showSaveStartOverButtons(true);
}
}
}

View file

@ -0,0 +1,140 @@
'use strict';
function MotorOutputReorderConfig(screenSize)
{
this.FrameColor = 'rgb(186, 186, 186)';
this.PropEdgeColor = 'rgb(255, 187, 0)';
this.PropEdgeLineWidth = 3;
this.MotorNumberTextFont = `${screenSize * 0.1}px 'Open Sans', 'Segoe UI', Tahoma, sans-serif`;
this.MotorNumberTextColor = 'rgb(0, 0, 0)';
this.MotorMouseHoverColor = 'rgba(255, 187, 0, 0.4)';
this.MotorSpinningColor = 'rgba(255, 0, 0, 0.4)';
this.MotorReadyColor = 'rgba(0,128,0,0.4)';
this.ArrowColor = 'rgb(182,67,67)';
this.DirectionArrowPoints = [
{x: -0.03 * screenSize, y: 0.11 * screenSize},
{x: -0.03 * screenSize, y: -0.01 * screenSize},
{x: -0.07 * screenSize, y: -0.01 * screenSize},
{x: 0.0 * screenSize, y: -0.13 * screenSize},
{x: 0.07 * screenSize, y: -0.01 * screenSize},
{x: 0.03 * screenSize, y: -0.01 * screenSize},
{x: 0.03 * screenSize, y: 0.11 * screenSize},
];
//===========================================
let frameRaduis = 0.28 * screenSize;
this["Quad X"] =
{
PropRadius: 0.2 * screenSize,
ArmWidth: 0.1 * screenSize,
Motors:
[
{x: frameRaduis, y: frameRaduis},
{x: frameRaduis, y: -frameRaduis},
{x: -frameRaduis, y: frameRaduis},
{x: -frameRaduis, y: -frameRaduis},
],
};
//===========================================
frameRaduis = 0.28 * screenSize;
this["Quad X 1234"] =
{
PropRadius: 0.2 * screenSize,
ArmWidth: 0.1 * screenSize,
Motors:
[
{x: -frameRaduis, y: -frameRaduis},
{x: frameRaduis, y: -frameRaduis},
{x: frameRaduis, y: frameRaduis},
{x: -frameRaduis, y: frameRaduis},
],
};
//===========================================
frameRaduis = 0.32 * screenSize;
this["Quad +"] =
{
PropRadius: 0.15 * screenSize,
ArmWidth: 0.1 * screenSize,
Motors:
[
{x: 0, y: frameRaduis},
{x: frameRaduis, y: 0 },
{x: -frameRaduis, y: 0 },
{x: 0, y: -frameRaduis},
],
};
//===========================================
frameRaduis = 0.30 * screenSize;
this["Tricopter"] =
{
PropRadius: 0.15 * screenSize,
ArmWidth: 0.1 * screenSize,
Motors:
[
{x: 0, y: frameRaduis},
{x: frameRaduis, y: -frameRaduis},
{x: -frameRaduis, y: -frameRaduis},
],
};
//===========================================
frameRaduis = 0.35 * screenSize;
this["Hex +"] =
{
PropRadius: 0.14 * screenSize,
ArmWidth: 0.1 * screenSize,
Motors: [],
};
let dAngle = Math.PI / 3;
let angle = 0;
angle = dAngle * 1;
this["Hex +"].Motors.push({x: Math.sin(angle) * frameRaduis, y: Math.cos(angle) * frameRaduis});
angle = dAngle * 2;
this["Hex +"].Motors.push({x: Math.sin(angle) * frameRaduis, y: Math.cos(angle) * frameRaduis});
angle = -dAngle * 1;
this["Hex +"].Motors.push({x: Math.sin(angle) * frameRaduis, y: Math.cos(angle) * frameRaduis});
angle = -dAngle * 2;
this["Hex +"].Motors.push({x: Math.sin(angle) * frameRaduis, y: Math.cos(angle) * frameRaduis});
angle = dAngle * 3;
this["Hex +"].Motors.push({x: Math.sin(angle) * frameRaduis, y: Math.cos(angle) * frameRaduis});
angle = dAngle * 0;
this["Hex +"].Motors.push({x: Math.sin(angle) * frameRaduis, y: Math.cos(angle) * frameRaduis});
//===========================================
frameRaduis = 0.35 * screenSize;
this["Hex X"] =
{
PropRadius: 0.14 * screenSize,
ArmWidth: 0.1 * screenSize,
Motors: [],
};
dAngle = Math.PI / 3;
angle = dAngle * 1;
this["Hex X"].Motors.push({x: Math.cos(angle) * frameRaduis, y: Math.sin(angle) * frameRaduis});
angle = -dAngle * 1;
this["Hex X"].Motors.push({x: Math.cos(angle) * frameRaduis, y: Math.sin(angle) * frameRaduis});
angle = dAngle * 2;
this["Hex X"].Motors.push({x: Math.cos(angle) * frameRaduis, y: Math.sin(angle) * frameRaduis});
angle = -dAngle * 2;
this["Hex X"].Motors.push({x: Math.cos(angle) * frameRaduis, y: Math.sin(angle) * frameRaduis});
angle = dAngle * 0;
this["Hex X"].Motors.push({x: Math.cos(angle) * frameRaduis, y: Math.sin(angle) * frameRaduis});
angle = dAngle * 3;
this["Hex X"].Motors.push({x: Math.cos(angle) * frameRaduis, y: Math.sin(angle) * frameRaduis});
}

View file

@ -0,0 +1,72 @@
.motorOutputReorderComponent {
display:flex;
height:100%;
flex-flow:column;
}
.motorOutputReorderComponentHeader {
border-bottom: 1px solid var(--accent);
margin-bottom: 10px;
padding-bottom: 12px;
}
#dialogMotorOutputReorderMainContent {
display:flex;
height:100%;
flex-flow:column;
}
#dialogMotorOutputReorderWarning {
display:flex;
height:100%;
flex-flow:column;
}
#motorOutputReorderCanvas {
width:100%;
flex-grow: 1;
}
#motorOutputReorderActionPanel {
height: 46px;
}
#dialogMotorOutputReorderSave {
margin-right: 0px;
}
.motorsRemapToggleParentContainer {
display: flex;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
#motorOutputReorderActionHint {
margin-top: 1.0em;
display: inline-block;
}
.motorsRemapToggleNarrow {
margin-right: 12px;
display: flex;
align-items: center;
}
.motorsRemapToggleWide {
flex: 1;
}
.motorsRemapDialogRiskNoticeText {
font-size: 1.2em;
}
.motorsRemapDialogRExplanationText {
font-size: 1.0em;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
#motorOutputReorderSaveStartOverButtonsPanel {
position: absolute;
bottom: 4px;
}

View file

@ -0,0 +1,108 @@
<style>
.logo {
height: 70px;
width: 240px;
background-image: url(./images/light-wide-2.svg);
background-repeat: no-repeat;
background-position: left center;
background-size: contain;
position: relative;
margin-top: -25px;
}
.logo_text {
position: absolute;
left: 80px;
top: 49px;
color: #949494;
opacity: 0.5;
font-size: 10px;
min-width: 210px;
}
.tab_container .logo {
display: none;
}
@media all and (max-width: 575px) {
.logo {
height: 24px;
width: 150px;
background-image: url(./images/light-wide-2-compact.svg);
background-position: left center;
order: 2;
margin-top: 0;
}
.logo_text {
display: none !important;
}
.tab_container .logo {
display: block;
background-image: url(./images/light-wide-2.svg);
background-repeat: no-repeat;
background-position: center 20px;
background-position-x: 12px;
background-size: 80%;
height: 120px;
width: auto;
margin-top: unset;
position: relative;
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
}
.tab_container .logo .logo_text {
display: block !important;
left: 82px;
top: 62px;
}
}
@media all and (min-width: 1125px) {
.logo {
width: 340px;
}
.logo_text {
font-size: inherit;
position: relative;
}
}
</style>
<template>
<div class="logo">
<div class="logo_text">
<span>
{{ $t("versionLabelConfigurator.message") }}: {{ configuratorVersion }}
<br />
<span v-if="firmwareVersion && firmwareId">
{{ $t("versionLabelFirmware.message") }}: {{ firmwareVersion }}
{{ firmwareId }}
</span>
<br />
<span v-if="hardwareId">
{{ $t("versionLabelTarget.message") }}: {{ hardwareId }}
</span>
</span>
</div>
</div>
</template>
<script>
export default {
props: {
configuratorVersion: {
type: String,
required: true,
},
firmwareVersion: {
type: String,
},
firmwareId: {
type: String,
},
hardwareId: {
type: String,
},
},
};
</script>

40
src/components/init.js Normal file
View file

@ -0,0 +1,40 @@
import Vue from "vue";
import vueI18n from "./vueI18n.js";
import BatteryLegend from "./quad-status/BatteryLegend.vue";
import BetaflightLogo from "./betaflight-logo/BetaflightLogo.vue";
import StatusBar from "./status-bar/StatusBar.vue";
// a bit of a hack here to get around the current translations.
// vue i18n provides slightly different api for this. But
// it's also possible to provide custom formatter
Vue.filter(
"stripEnd",
(value) => value.replace(/\$1%/, "")
);
// Most of the global objects can go here at first.
// It's a bit of overkill for simple components,
// but these instance would eventually have more children
// which would find the use for those extra properties.
const betaflightModel = {
CONFIGURATOR,
FC,
MSP,
PortUsage,
};
const app = new Vue({
i18n: vueI18n,
data: betaflightModel,
components: {
BatteryLegend,
BetaflightLogo,
StatusBar,
},
el: '#main-wrapper',
});
// Not strictly necessary here, but if needed
// it's always possible to modify this model in
// jquery land to trigger updates in vue
window.vm = betaflightModel;

View file

@ -0,0 +1,32 @@
<template>
<div class="battery-legend">{{ reading }}</div>
</template>
<script>
const NO_BATTERY_VOLTAGE_MAXIMUM = 1.8; // Maybe is better to add a call to MSP_BATTERY_STATE but is not available for all versions
export default {
props: {
voltage: {
type: Number,
default: 0,
},
vbatmaxcellvoltage: {
type: Number,
default: 1,
},
},
computed: {
reading() {
let nbCells = Math.floor(this.voltage / this.vbatmaxcellvoltage) + 1;
if (this.voltage === 0) {
nbCells = 1;
}
const cellsText =
this.voltage > NO_BATTERY_VOLTAGE_MAXIMUM ? `${nbCells}S` : "USB";
return `${this.voltage.toFixed(2)}V (${cellsText})`;
},
},
};
</script>

View file

@ -0,0 +1,29 @@
<template>
<div>
<span>{{ $t("statusbar_port_utilization.message") }}</span>
<ReadingStat
message="statusbar_usage_download"
:value="usageDown"
unit="%"
/>
<ReadingStat message="statusbar_usage_upload" :value="usageUp" unit="%" />
</div>
</template>
<script>
import ReadingStat from "./ReadingStat.vue";
export default {
props: {
usageDown: {
type: Number,
},
usageUp: {
type: Number,
},
},
components: {
ReadingStat,
},
};
</script>

View file

@ -0,0 +1,22 @@
<template>
<span>
<span>{{ $t(message + ".message") | stripEnd }}</span>
<span>{{ value }}</span>
<span v-if="unit">{{ unit }}</span>
</span>
</template>
<script>
export default {
props: {
message: {
type: String,
},
value: {
type: Number,
},
unit: {
type: String,
},
},
};
</script>

View file

@ -0,0 +1,65 @@
<template>
<div id="status-bar">
<PortUtilization :usage-down="portUsageDown" :usage-up="portUsageUp" />
<ReadingStat message="statusbar_packet_error" :value="packetError" />
<ReadingStat message="statusbar_i2c_error" :value="i2cError" />
<ReadingStat message="statusbar_cycle_time" :value="cycleTime" />
<ReadingStat message="statusbar_cpu_load" :value="cpuLoad" unit="%" />
<StatusBarVersion
:configurator-version="configuratorVersion"
:firmware-version="firmwareVersion"
:firmware-id="firmwareId"
:hardware-id="hardwareId"
:git-changeset-id="gitChangesetId"
/>
</div>
</template>
<script>
import StatusBarVersion from "./StatusBarVersion.vue";
import ReadingStat from "./ReadingStat.vue";
import PortUtilization from "./PortUtilization.vue";
export default {
props: {
portUsageDown: {
type: Number,
},
portUsageUp: {
type: Number,
},
packetError: {
type: Number,
},
i2cError: {
type: Number,
},
cycleTime: {
type: Number,
},
cpuLoad: {
type: Number,
},
configuratorVersion: {
type: String,
},
firmwareVersion: {
type: String,
},
firmwareId: {
type: String,
},
hardwareId: {
type: String,
},
gitChangesetId: {
type: String,
},
},
components: {
PortUtilization,
ReadingStat,
StatusBarVersion,
},
};
</script>

View file

@ -0,0 +1,35 @@
<template>
<div class="version">
{{ $t("versionLabelConfigurator.message") }}: {{ configuratorVersion }}
<span v-if="firmwareVersion && firmwareId">
, {{ $t("versionLabelFirmware.message") }}: {{ firmwareVersion }}
{{ firmwareId }}
</span>
<span v-if="hardwareId">
, {{ $t("versionLabelTarget.message") }}: {{ hardwareId }}
</span>
({{ gitChangesetId }})
</div>
</template>
<script>
export default {
props: {
configuratorVersion: {
type: String,
},
firmwareVersion: {
type: String,
},
firmwareId: {
type: String,
},
hardwareId: {
type: String,
},
gitChangesetId: {
type: String,
},
},
};
</script>

18
src/components/vueI18n.js Normal file
View file

@ -0,0 +1,18 @@
import Vue from "vue";
import VueI18n from "vue-i18n";
Vue.use(VueI18n);
const vueI18n = new VueI18n(i18next);
i18next.on("initialized", () => {
vueI18n.setLocaleMessage("en", i18next.getDataByLanguage("en").messages);
});
i18next.on("languageChanged", (lang) => {
vueI18n.setLocaleMessage(lang, i18next.getDataByLanguage(lang).messages);
vueI18n.locale = lang;
document.querySelector("html").setAttribute("lang", lang);
});
export default vueI18n;