mirror of
https://github.com/betaflight/betaflight-configurator.git
synced 2025-07-20 06:45:12 +03:00
Add Vue
Setup vue with gulp and initial components: * Status Bar * Logo * Battery Legend
This commit is contained in:
parent
14d98c4dea
commit
d6cee386e8
27 changed files with 1511 additions and 283 deletions
30
src/components/MotorOutputReordering/Body.html
Normal file
30
src/components/MotorOutputReordering/Body.html
Normal 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>
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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});
|
||||
}
|
72
src/components/MotorOutputReordering/Styles.css
Normal file
72
src/components/MotorOutputReordering/Styles.css
Normal 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;
|
||||
}
|
108
src/components/betaflight-logo/BetaflightLogo.vue
Normal file
108
src/components/betaflight-logo/BetaflightLogo.vue
Normal 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
40
src/components/init.js
Normal 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;
|
32
src/components/quad-status/BatteryLegend.vue
Normal file
32
src/components/quad-status/BatteryLegend.vue
Normal 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>
|
29
src/components/status-bar/PortUtilization.vue
Normal file
29
src/components/status-bar/PortUtilization.vue
Normal 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>
|
22
src/components/status-bar/ReadingStat.vue
Normal file
22
src/components/status-bar/ReadingStat.vue
Normal 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>
|
65
src/components/status-bar/StatusBar.vue
Normal file
65
src/components/status-bar/StatusBar.vue
Normal 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>
|
35
src/components/status-bar/StatusBarVersion.vue
Normal file
35
src/components/status-bar/StatusBarVersion.vue
Normal 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
18
src/components/vueI18n.js
Normal 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;
|
Loading…
Add table
Add a link
Reference in a new issue