1
0
Fork 0
mirror of https://github.com/betaflight/betaflight-configurator.git synced 2025-07-18 22:05:13 +03:00

ISSUE-1609: Create communication mechanism between windows, implement dark theme for sticks window using it. Small fix for Tip popup window.

This commit is contained in:
Anisotropic 2019-09-17 23:21:43 +03:00
parent c6fd43fdec
commit 262bc05ab5
11 changed files with 190 additions and 72 deletions

View file

@ -11,6 +11,8 @@
--paper: url(../../images/paper-dark.jpg); --paper: url(../../images/paper-dark.jpg);
--ledAccent: #6e6e6e; --ledAccent: #6e6e6e;
--ledBackground: #424242; --ledBackground: #424242;
--gimbalBackground: var(--subtleAccent);
--gimbalCrosshair: var(--mutedText);
--switcherysecond: #858585; --switcherysecond: #858585;
} }
@ -149,7 +151,7 @@ button {
} }
.noUi-pips { .noUi-pips {
color: #dbdbdb; color: var(--mutedText);
} }
.jBox-container { .jBox-container {
@ -164,3 +166,7 @@ button {
text-shadow: 0 1px 1px #ffffff; text-shadow: 0 1px 1px #ffffff;
color: white; color: white;
} }
.jBox-pointer:after {
background: #393b3a;
}

View file

@ -1,6 +1,6 @@
:root { :root {
--accent: #ffbb00; --accent: #ffbb00;
--error: red; --error: red;
--subtleAccent: silver; --subtleAccent: silver;
--quietText: #ffffff; --quietText: #ffffff;
--quietHeader: #828885; --quietHeader: #828885;
@ -13,6 +13,8 @@
--paper: url(../../images/paper.jpg); --paper: url(../../images/paper.jpg);
--ledAccent: #adadad; --ledAccent: #adadad;
--ledBackground: #e9e9e9; --ledBackground: #e9e9e9;
--gimbalBackground: #eee;
--gimbalCrosshair: var(--subtleAccent);
--switcherysecond: #c4c4c4; --switcherysecond: #c4c4c4;
} }
@ -1155,7 +1157,7 @@ dialog {
margin-left: auto; margin-left: auto;
} }
/** Hack to change the "display: none" by a "visibility:hidden", to apply /** Hack to change the "display: none" by a "visibility:hidden", to apply
the margin-left: auto needed by the first element to align to the right the buttons */ the margin-left: auto needed by the first element to align to the right the buttons */
.toolbar_fixed_bottom .content_toolbar div[style='display: none;']:first-child { .toolbar_fixed_bottom .content_toolbar div[style='display: none;']:first-child {
visibility: hidden; visibility: hidden;
@ -2141,3 +2143,33 @@ input {
margin-top: 10px; margin-top: 10px;
margin-bottom: 10px; margin-bottom: 10px;
} }
/*
noUi slider stylings
*/
.noUi-target {
background: var(--alternativeBackground);
border-color: var(--subtleAccent);
box-shadow: none;
}
.noUi-handle {
background: var(--sideBackground);
border-color: var(--subtleAccent);
box-shadow: none;
}
.noUi-handle:before,
.noUi-handle:after {
background-color: var(--subtleAccent);
}
.noUi-background {
background: var(--alternativeBackground);
box-shadow: none;
}
.noUi-connect {
box-shadow: none;
}

View file

@ -15,7 +15,3 @@
background-color: #3a3a3a; background-color: #3a3a3a;
color: white; color: white;
} }
.tab-adjustments .noUi-background {
background-color: #858585;
}

View file

@ -4,18 +4,10 @@
.tab-adjustments .range .marker, .tab-adjustments .channel-slider .noUi-connect { .tab-adjustments .range .marker, .tab-adjustments .channel-slider .noUi-connect {
background: var(--accent); background: var(--accent);
box-shadow: inset 0 0px 2px rgba(0, 0, 0, 0.4), 0px 1px 0px rgba(255, 255, 255, 0.6);
} }
.noUi-target { .noUi-target {
border-radius: 4px; border-radius: 4px;
border: 1px solid #D3D3D3;
box-shadow: inset 0 0px 2px rgba(0, 0, 0, 0.4), 0px 1px 0px rgba(255, 255, 255, 0.6);
}
.noUi-background {
box-shadow: inset 0 0px 2px rgba(0, 0, 0, 0.4), 0px 1px 0px rgba(255, 255, 255, 0.6);
background: #D2D2D2;
} }
.tab-adjustments .adjustments { .tab-adjustments .adjustments {
@ -60,7 +52,7 @@
.tab-adjustments .adjustment select { .tab-adjustments .adjustment select {
/* outline: 1px solid silver; */ /* outline: 1px solid silver; */
border-radius: 3px; border-radius: 3px;
border: 1px solid var(--subtleAccent); border: 1px solid var(--subtleAccent);
} }
.tab-adjustments .adjustment td { .tab-adjustments .adjustment td {

View file

@ -8,7 +8,7 @@ body {
} }
.control-gimbals { .control-gimbals {
/* A generous padding around the window edges ensures that we continue to receive mousemove events (since /* A generous padding around the window edges ensures that we continue to receive mousemove events (since
* cursor stays in the window for longer) * cursor stays in the window for longer)
*/ */
padding: 25px; padding: 25px;
@ -21,7 +21,7 @@ body {
position: relative; position: relative;
width: 120px; width: 120px;
height: 120px; height: 120px;
background-color: #eee; background-color: var(--gimbalBackground);
margin-left: 1em; margin-left: 1em;
margin-right: 1em; margin-right: 1em;
margin-bottom: 2em; margin-bottom: 2em;
@ -33,7 +33,7 @@ body {
.crosshair { .crosshair {
display: block; display: block;
position: absolute; position: absolute;
background-color: var(--subtleAccent); background-color: var(--gimbalCrosshair);
} }
.crosshair-vert { .crosshair-vert {

View file

@ -29,27 +29,28 @@ var css_dark = [
var DarkTheme = { var DarkTheme = {
configEnabled: undefined, configEnabled: undefined,
}; };
DarkTheme.isDarkThemeEnabled = function (val) {
DarkTheme.setConfig = function(result) { return val === 0 || val === 2 && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}
DarkTheme.setConfig = function (result) {
if (this.configEnabled != result) { if (this.configEnabled != result) {
this.configEnabled = result; this.configEnabled = result;
if (this.configEnabled === 0 || this.configEnabled === 2 && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { if (this.isDarkThemeEnabled(this.configEnabled)) {
this.applyDark(); this.applyDark();
} else { } else {
this.applyNormal(); this.applyNormal();
} }
windowWatcherUtil.passValue(chrome.app.window.get("receiver_msp"), 'darkTheme', this.isDarkThemeEnabled(this.configEnabled));
} }
}; };
DarkTheme.applyDark = function() { DarkTheme.applyDark = function () {
for (var i = 0; i < css_dark.length; i++) { css_dark.forEach((el) => $('link[href="' + el + '"]').prop('disabled', false));
$('link[href="' + css_dark[i] + '"]').prop('disabled', false);
}
}; };
DarkTheme.applyNormal = function() { DarkTheme.applyNormal = function () {
for (var i = 0; i < css_dark.length; i++) { css_dark.forEach((el) => $('link[href="' + el + '"]').prop('disabled', true));
$('link[href="' + css_dark[i] + '"]').prop('disabled', true);
}
}; };

View file

@ -76,7 +76,7 @@ TABS.receiver.initialize = function (callback) {
tab.yawDeadband = parseInt($(this).val()); tab.yawDeadband = parseInt($(this).val());
}).change(); }).change();
} }
if (semver.lt(CONFIG.apiVersion, "1.15.0")) { if (semver.lt(CONFIG.apiVersion, "1.15.0")) {
$('.sticks').hide(); $('.sticks').hide();
} else { } else {
@ -319,6 +319,9 @@ TABS.receiver.initialize = function (callback) {
return false; return false;
} }
} }
windowWatcherUtil.passValue(createdWindow, 'darkTheme', DarkTheme.isDarkThemeEnabled(DarkTheme.configEnabled));
}); });
}); });
@ -350,7 +353,7 @@ TABS.receiver.initialize = function (callback) {
if ($(this).val() == 1) { if ($(this).val() == 1) {
rcSmoothingnNumberElement.val(RX_CONFIG.rcSmoothingInputCutoff); rcSmoothingnNumberElement.val(RX_CONFIG.rcSmoothingInputCutoff);
$('.tab-receiver .rcSmoothing-input-cutoff').show(); $('.tab-receiver .rcSmoothing-input-cutoff').show();
} }
}); });
$('.tab-receiver .rcSmoothing-derivative-cutoff').show(); $('.tab-receiver .rcSmoothing-derivative-cutoff').show();
@ -578,7 +581,7 @@ TABS.receiver.initModelPreview = function () {
if (CONFIG.flightControllerIdentifier == 'BTFL' && semver.lt(CONFIG.flightControllerVersion, '2.8.0')) { if (CONFIG.flightControllerIdentifier == 'BTFL' && semver.lt(CONFIG.flightControllerVersion, '2.8.0')) {
useOldRateCurve = true; useOldRateCurve = true;
} }
this.rateCurve = new RateCurve(useOldRateCurve); this.rateCurve = new RateCurve(useOldRateCurve);
$(window).on('resize', $.proxy(this.model.resize, this.model)); $(window).on('resize', $.proxy(this.model.resize, this.model));
@ -665,4 +668,4 @@ function updateInterpolationView() {
if (RX_CONFIG.rcSmoothingInputCutoff == 0) { if (RX_CONFIG.rcSmoothingInputCutoff == 0) {
$('.tab-receiver .rcSmoothing-input-cutoff').hide(); $('.tab-receiver .rcSmoothing-input-cutoff').hide();
} }
} }

View file

@ -1,10 +1,15 @@
"use strict"; "use strict";
let css_dark = [
'/css/main-dark.css'
];
var var
CHANNEL_MIN_VALUE = 1000, CHANNEL_MIN_VALUE = 1000,
CHANNEL_MID_VALUE = 1500, CHANNEL_MID_VALUE = 1500,
CHANNEL_MAX_VALUE = 2000, CHANNEL_MAX_VALUE = 2000,
// What's the index of each channel in the MSP channel list? // What's the index of each channel in the MSP channel list?
channelMSPIndexes = { channelMSPIndexes = {
Roll: 0, Roll: 0,
@ -16,7 +21,7 @@ var
Aux3: 6, Aux3: 6,
Aux4: 7, Aux4: 7,
}, },
// Set reasonable initial stick positions (Mode 2) // Set reasonable initial stick positions (Mode 2)
stickValues = { stickValues = {
Throttle: CHANNEL_MIN_VALUE, Throttle: CHANNEL_MIN_VALUE,
@ -28,21 +33,31 @@ var
Aux3: CHANNEL_MIN_VALUE, Aux3: CHANNEL_MIN_VALUE,
Aux4: CHANNEL_MIN_VALUE Aux4: CHANNEL_MIN_VALUE
}, },
// First the vertical axis, then the horizontal: // First the vertical axis, then the horizontal:
gimbals = [ gimbals = [
["Throttle", "Yaw"], ["Throttle", "Yaw"],
["Pitch", "Roll"], ["Pitch", "Roll"],
], ],
gimbalElems, gimbalElems,
sliderElems, sliderElems,
enableTX = false; enableTX = false;
// This is a hack to get the i18n var of the parent, but the localizePage not works // This is a hack to get the i18n var of the parent, but the localizePage not works
const i18n = opener.i18n; const i18n = opener.i18n;
let watchers = {
darkTheme: (val) => {
if (val) {
applyDarkTheme();
} else {
applyNormalTheme();
}
}
};
$(document).ready(function () { $(document).ready(function () {
$('[i18n]:not(.i18n-replaced)').each(function() { $('[i18n]:not(.i18n-replaced)').each(function() {
var element = $(this); var element = $(this);
@ -50,20 +65,22 @@ $(document).ready(function () {
element.html(i18n.getMessage(element.attr('i18n'))); element.html(i18n.getMessage(element.attr('i18n')));
element.addClass('i18n-replaced'); element.addClass('i18n-replaced');
}); });
})
windowWatcherUtil.bindWatchers(window, watchers);
});
function transmitChannels() { function transmitChannels() {
var var
channelValues = [0, 0, 0, 0, 0, 0, 0, 0]; channelValues = [0, 0, 0, 0, 0, 0, 0, 0];
if (!enableTX) { if (!enableTX) {
return; return;
} }
for (var stickName in stickValues) { for (var stickName in stickValues) {
channelValues[channelMSPIndexes[stickName]] = stickValues[stickName]; channelValues[channelMSPIndexes[stickName]] = stickValues[stickName];
} }
// Callback given to us by the window creator so we can have it send data over MSP for us: // Callback given to us by the window creator so we can have it send data over MSP for us:
if (!window.setRawRx(channelValues)) { if (!window.setRawRx(channelValues)) {
// MSP connection has gone away // MSP connection has gone away
@ -73,7 +90,7 @@ function transmitChannels() {
function stickPortionToChannelValue(portion) { function stickPortionToChannelValue(portion) {
portion = Math.min(Math.max(portion, 0.0), 1.0); portion = Math.min(Math.max(portion, 0.0), 1.0);
return Math.round(portion * (CHANNEL_MAX_VALUE - CHANNEL_MIN_VALUE) + CHANNEL_MIN_VALUE); return Math.round(portion * (CHANNEL_MAX_VALUE - CHANNEL_MIN_VALUE) + CHANNEL_MIN_VALUE);
} }
@ -85,15 +102,15 @@ function updateControlPositions() {
for (var stickName in stickValues) { for (var stickName in stickValues) {
var var
stickValue = stickValues[stickName]; stickValue = stickValues[stickName];
// Look for the gimbal which corresponds to this stick name // Look for the gimbal which corresponds to this stick name
for (var gimbalIndex in gimbals) { for (var gimbalIndex in gimbals) {
var var
gimbal = gimbals[gimbalIndex], gimbal = gimbals[gimbalIndex],
gimbalElem = gimbalElems.get(gimbalIndex), gimbalElem = gimbalElems.get(gimbalIndex),
gimbalSize = $(gimbalElem).width(), gimbalSize = $(gimbalElem).width(),
stickElem = $(".control-stick", gimbalElem); stickElem = $(".control-stick", gimbalElem);
if (gimbal[0] == stickName) { if (gimbal[0] == stickName) {
stickElem.css('top', (1.0 - channelValueToStickPortion(stickValue)) * gimbalSize + "px"); stickElem.css('top', (1.0 - channelValueToStickPortion(stickValue)) * gimbalSize + "px");
break; break;
@ -106,62 +123,70 @@ function updateControlPositions() {
} }
function handleGimbalMouseDrag(e) { function handleGimbalMouseDrag(e) {
var var
gimbal = $(gimbalElems.get(e.data.gimbalIndex)), gimbal = $(gimbalElems.get(e.data.gimbalIndex)),
gimbalOffset = gimbal.offset(), gimbalOffset = gimbal.offset(),
gimbalSize = gimbal.width(); gimbalSize = gimbal.width();
stickValues[gimbals[e.data.gimbalIndex][0]] = stickPortionToChannelValue(1.0 - (e.pageY - gimbalOffset.top) / gimbalSize); stickValues[gimbals[e.data.gimbalIndex][0]] = stickPortionToChannelValue(1.0 - (e.pageY - gimbalOffset.top) / gimbalSize);
stickValues[gimbals[e.data.gimbalIndex][1]] = stickPortionToChannelValue((e.pageX - gimbalOffset.left) / gimbalSize); stickValues[gimbals[e.data.gimbalIndex][1]] = stickPortionToChannelValue((e.pageX - gimbalOffset.left) / gimbalSize);
updateControlPositions(); updateControlPositions();
} }
function localizeAxisNames() { function localizeAxisNames() {
for (var gimbalIndex in gimbals) { for (var gimbalIndex in gimbals) {
var var
gimbal = gimbalElems.get(gimbalIndex); gimbal = gimbalElems.get(gimbalIndex);
$(".gimbal-label-vert", gimbal).text(i18n.getMessage("controlAxis" + gimbals[gimbalIndex][0])); $(".gimbal-label-vert", gimbal).text(i18n.getMessage("controlAxis" + gimbals[gimbalIndex][0]));
$(".gimbal-label-horz", gimbal).text(i18n.getMessage("controlAxis" + gimbals[gimbalIndex][1])); $(".gimbal-label-horz", gimbal).text(i18n.getMessage("controlAxis" + gimbals[gimbalIndex][1]));
} }
for (var sliderIndex = 0; sliderIndex < 4; sliderIndex++) { for (var sliderIndex = 0; sliderIndex < 4; sliderIndex++) {
$(".slider-label", sliderElems.get(sliderIndex)).text(i18n.getMessage("controlAxisAux" + (sliderIndex + 1))); $(".slider-label", sliderElems.get(sliderIndex)).text(i18n.getMessage("controlAxisAux" + (sliderIndex + 1)));
} }
} }
function applyDarkTheme() {
css_dark.forEach((el) => $('link[href="' + el + '"]').prop('disabled', false));
};
function applyNormalTheme() {
css_dark.forEach((el) => $('link[href="' + el + '"]').prop('disabled', true));
};
$(document).ready(function() { $(document).ready(function() {
$(".button-enable .btn").click(function() { $(".button-enable .btn").click(function() {
var var
shrinkHeight = $(".warning").height(); shrinkHeight = $(".warning").height();
$(".warning").slideUp("short", function() { $(".warning").slideUp("short", function() {
chrome.app.window.current().innerBounds.minHeight -= shrinkHeight; chrome.app.window.current().innerBounds.minHeight -= shrinkHeight;
chrome.app.window.current().innerBounds.height -= shrinkHeight; chrome.app.window.current().innerBounds.height -= shrinkHeight;
chrome.app.window.current().innerBounds.maxHeight -= shrinkHeight; chrome.app.window.current().innerBounds.maxHeight -= shrinkHeight;
}); });
enableTX = true; enableTX = true;
}); });
gimbalElems = $(".control-gimbal"); gimbalElems = $(".control-gimbal");
sliderElems = $(".control-slider"); sliderElems = $(".control-slider");
gimbalElems.each(function(gimbalIndex) { gimbalElems.each(function(gimbalIndex) {
$(this).on('mousedown', {gimbalIndex: gimbalIndex}, function(e) { $(this).on('mousedown', {gimbalIndex: gimbalIndex}, function(e) {
if (e.which == 1) { // Only move sticks on left mouse button if (e.which == 1) { // Only move sticks on left mouse button
handleGimbalMouseDrag(e); handleGimbalMouseDrag(e);
$(window).on('mousemove', {gimbalIndex: gimbalIndex}, handleGimbalMouseDrag); $(window).on('mousemove', {gimbalIndex: gimbalIndex}, handleGimbalMouseDrag);
} }
}); });
}); });
$(".slider", sliderElems).each(function(sliderIndex) { $(".slider", sliderElems).each(function(sliderIndex) {
var var
initialValue = stickValues["Aux" + (sliderIndex + 1)]; initialValue = stickValues["Aux" + (sliderIndex + 1)];
$(this) $(this)
.noUiSlider({ .noUiSlider({
start: initialValue, start: initialValue,
@ -171,27 +196,27 @@ $(document).ready(function() {
} }
}).on('slide change set', function(e, value) { }).on('slide change set', function(e, value) {
value = Math.round(parseFloat(value)); value = Math.round(parseFloat(value));
stickValues["Aux" + (sliderIndex + 1)] = value; stickValues["Aux" + (sliderIndex + 1)] = value;
$(".tooltip", this).text(value); $(".tooltip", this).text(value);
}); });
$(this).append('<div class="tooltip"></div>'); $(this).append('<div class="tooltip"></div>');
$(".tooltip", this).text(initialValue); $(".tooltip", this).text(initialValue);
}); });
/* /*
* Mouseup handler needs to be bound to the window in order to receive mouseup if mouse leaves window. * Mouseup handler needs to be bound to the window in order to receive mouseup if mouse leaves window.
*/ */
$(window).mouseup(function(e) { $(window).mouseup(function(e) {
$(this).off('mousemove', handleGimbalMouseDrag); $(this).off('mousemove', handleGimbalMouseDrag);
}); });
localizeAxisNames(); localizeAxisNames();
updateControlPositions(); updateControlPositions();
setInterval(transmitChannels, 50); setInterval(transmitChannels, 50);
}); });

View file

@ -0,0 +1,59 @@
'use strict';
/*
This utility is intended to communicate between chrome windows.
One window could watch passed values from another window and react to them.
*/
var windowWatcherUtil = {};
windowWatcherUtil.invokeWatcher = function(bindingKey, bindingVal, watchersObject) {
if (watchersObject[bindingKey]) {
watchersObject[bindingKey](bindingVal);
}
}
windowWatcherUtil.iterateOverBindings = function(bindings, watchersObject) {
let entries = Object.entries(bindings);
for (const [key, val] of entries) {
this.invokeWatcher(key, val, watchersObject)
}
}
windowWatcherUtil.bindWatchers = function(windowObject, watchersObject) {
if (!windowObject.bindings) {
windowObject.bindings = {};
} else {
this.iterateOverBindings(windowObject.bindings, watchersObject);
}
windowObject.bindings = new Proxy(windowObject.bindings, {
set(target, prop, val, receiver) {
windowWatcherUtil.invokeWatcher(prop, val, watchersObject);
return Reflect.set(target, prop, val, receiver);
}
});
}
// 'Windows' here could be array or single window reference
windowWatcherUtil.passValue = function(windows, key, val) {
let applyBinding = function(win, key, val) {
if (!win) {
return;
}
if (win.contentWindow.bindings) {
win.contentWindow.bindings[key] = val;
} else {
win.contentWindow.bindings = {
[key]: val
};
}
}
if (Array.isArray(windows)) {
windows.forEach((el) => applyBinding(el, key, val));
} else {
applyBinding(windows, key, val)
}
}

View file

@ -85,6 +85,7 @@
<script type="text/javascript" src="./js/libraries/jquery.ba-throttle-debounce.min.js"></script> <script type="text/javascript" src="./js/libraries/jquery.ba-throttle-debounce.min.js"></script>
<script type="text/javascript" src="./node_modules/inflection/inflection.min.js"></script> <script type="text/javascript" src="./node_modules/inflection/inflection.min.js"></script>
<script type="text/javascript" src="./js/libraries/analytics.js"></script> <script type="text/javascript" src="./js/libraries/analytics.js"></script>
<script type="text/javascript" src="./js/utils/window_watchers.js"></script>
<script type="text/javascript" src="./js/injected_methods.js"></script> <script type="text/javascript" src="./js/injected_methods.js"></script>
<script type="text/javascript" src="./js/ConfigStorage.js"></script> <script type="text/javascript" src="./js/ConfigStorage.js"></script>
<script type="text/javascript" src="./js/data_storage.js"></script> <script type="text/javascript" src="./js/data_storage.js"></script>
@ -281,7 +282,7 @@
<ul class="mode-connected"> <ul class="mode-connected">
<li class="tab_setup"><a href="#" i18n="tabSetup" class="tabicon ic_setup" i18n_title="tabSetup"></a></li> <li class="tab_setup"><a href="#" i18n="tabSetup" class="tabicon ic_setup" i18n_title="tabSetup"></a></li>
<li class="tab_setup_osd"><a href="#" i18n="tabSetupOSD" class="tabicon ic_setup" i18n_title="tabSetupOSD"></a></li> <li class="tab_setup_osd"><a href="#" i18n="tabSetupOSD" class="tabicon ic_setup" i18n_title="tabSetupOSD"></a></li>
<li class="tab_ports"><a href="#" i18n="tabPorts" class="tabicon ic_ports" i18n_title="tabPorts"></a></li> <li class="tab_ports"><a href="#" i18n="tabPorts" class="tabicon ic_ports" i18n_title="tabPorts"></a></li>
<li class="tab_configuration"><a href="#" i18n="tabConfiguration" class="tabicon ic_config" <li class="tab_configuration"><a href="#" i18n="tabConfiguration" class="tabicon ic_config"
i18n_title="tabConfiguration"></a></li> i18n_title="tabConfiguration"></a></li>

View file

@ -5,6 +5,7 @@
<script type="text/javascript" src="/node_modules/jquery-ui-npm/jquery-ui.min.js"></script> <script type="text/javascript" src="/node_modules/jquery-ui-npm/jquery-ui.min.js"></script>
<script type="text/javascript" src="/js/libraries/jquery.nouislider.all.min.js"></script> <script type="text/javascript" src="/js/libraries/jquery.nouislider.all.min.js"></script>
<script type="text/javascript" src="/js/utils/window_watchers.js"></script>
<script type="text/javascript" src="/js/tabs/receiver_msp.js"></script> <script type="text/javascript" src="/js/tabs/receiver_msp.js"></script>
<link type="text/css" rel="stylesheet" href="/js/libraries/jquery.nouislider.min.css"> <link type="text/css" rel="stylesheet" href="/js/libraries/jquery.nouislider.min.css">
@ -12,6 +13,8 @@
<link type="text/css" rel="stylesheet" href="/css/main.css" media="all" /> <link type="text/css" rel="stylesheet" href="/css/main.css" media="all" />
<link type="text/css" rel="stylesheet" href="/css/tabs/receiver_msp.css" media="all" /> <link type="text/css" rel="stylesheet" href="/css/tabs/receiver_msp.css" media="all" />
<link type="text/css" rel="stylesheet" href="/css/main-dark.css" media="all" disabled/>
</head> </head>
<body> <body>
<div class="control-gimbals"> <div class="control-gimbals">