1
0
Fork 0
mirror of https://github.com/betaflight/betaflight-configurator.git synced 2025-07-16 04:45:20 +03:00
betaflight-configurator/src/js/gui.js
WalcoFPV 4f93e54ae6 Cordova framework integration, Android support, mobile UI & options tab
Cordova integration and android platform :
- Added cordova directory with required config
- Added cordova applications generation in gulpfile
- Added cordova development instructions
- Used cordova plugins to simulate missing chrome api  plugins (chrome.serial and chrome.fileSystem)
- Added cordova clipboard support
- Added android operating system and Cordova gui mode
- Fixed some css and js files to make them working on Android as well as on computers
- Added --skipdep argument to accelerate cordova build (gulp task)
- Added a webview helper to help people to update the webview app of their device

New options tab :
- Added options tab replacing the options dropdown
- Added option to switch between phones UI and computers UI

Mobile interface and global interface improvements :
- Simplified the structure of the header with flex css
- Made headerbar and tab container responsive (compact headerbar and side menu)
- All tabs are adapted to mobile interface (except firmware flasher)
- The servos and adjustments tabs are not fully adapted but are "usable"
- Improved header bar animation
- Improved log expandation animation
- Added swipe gesture to toggle side menu

Fixes during the development :
- Logo position
- Dark mode
- Auto connection
- Error messages (cordova_chromeapi.js)
- Responsive grid
- Testing
- Disconnection
- Width of boxes inside the OSD tab
- Fixed cli tab
- OSD tab
- Motor stop switch
- White spaces in boxes
- Dialogs size
- Connect button state
- Prevent tablet with a height larger than 575px to switch to computers ui
- Fixed logging tab
- Fixed code smell
- Fixed yarn cordova plugin install issue
- Fixed content_wrapper
- Fixed vibrations when scrolling
- Fixed scrolling bar alignment
- Fixed dialogReportProblem height
- Fixed rates logo
- Fixed auto connection default value (true)
- Fixed D to D max
- Fixed dialogs

Added required messages in locales/en/messages.json file

Requested changes
2020-07-03 16:19:33 +02:00

399 lines
11 KiB
JavaScript

'use strict';
var TABS = {}; // filled by individual tab js file
var GUI_control = function () {
this.auto_connect = false;
this.connecting_to = false;
this.connected_to = false;
this.connect_lock = false;
this.active_tab = null;
this.tab_switch_in_progress = false;
this.operating_system = null;
this.interval_array = [];
this.timeout_array = [];
this.defaultAllowedTabsWhenDisconnected = [
'landing',
'changelog',
'firmware_flasher',
'privacy_policy',
'options',
'help'
];
this.defaultAllowedFCTabsWhenConnected = [
'setup',
'failsafe',
'transponder',
'osd',
'power',
'adjustments',
'auxiliary',
'cli',
'configuration',
'gps',
'led_strip',
'logging',
'onboard_logging',
'modes',
'motors',
'pid_tuning',
'ports',
'receiver',
'sensors',
'servos',
'vtx',
];
this.allowedTabs = this.defaultAllowedTabsWhenDisconnected;
// check which operating system is user running
this.operating_system = GUI_checkOperatingSystem();
// Check the method of execution
this.nwGui = null;
try {
this.nwGui = require('nw.gui');
this.Mode = GUI_Modes.NWJS;
} catch (ex) {
if (typeof cordovaApp !== 'undefined') {
this.Mode = GUI_Modes.Cordova;
} else {
if (window.chrome && chrome.storage && chrome.storage.local) {
this.Mode = GUI_Modes.ChromeApp;
} else {
this.Mode = GUI_Modes.Other;
}
}
}
};
const GUI_Modes = {
NWJS: "NW.js",
ChromeApp: "Chrome",
Cordova: "Cordova",
Other: "Other"
};
function GUI_checkOperatingSystem() {
if (navigator.appVersion.indexOf("Win") !== -1) {
return "Windows";
} else if (navigator.appVersion.indexOf("Mac") !== -1) {
return "MacOS";
} else if (navigator.appVersion.indexOf("CrOS") !== -1) {
return "ChromeOS";
} else if (navigator.appVersion.indexOf("Android") !== -1) {
return "Android";
} else if (navigator.appVersion.indexOf("Linux") !== -1) {
return "Linux";
} else if (navigator.appVersion.indexOf("X11") !== -1) {
return "UNIX";
} else {
return "Unknown";
}
}
// Timer managing methods
// name = string
// code = function reference (code to be executed)
// interval = time interval in miliseconds
// first = true/false if code should be ran initially before next timer interval hits
GUI_control.prototype.interval_add = function (name, code, interval, first) {
var data = {'name': name, 'timer': null, 'code': code, 'interval': interval, 'fired': 0, 'paused': false};
if (first == true) {
code(); // execute code
data.fired++; // increment counter
}
data.timer = setInterval(function() {
code(); // execute code
data.fired++; // increment counter
}, interval);
this.interval_array.push(data); // push to primary interval array
return data;
};
// name = string
GUI_control.prototype.interval_remove = function (name) {
for (var i = 0; i < this.interval_array.length; i++) {
if (this.interval_array[i].name == name) {
clearInterval(this.interval_array[i].timer); // stop timer
this.interval_array.splice(i, 1); // remove element/object from array
return true;
}
}
return false;
};
// name = string
GUI_control.prototype.interval_pause = function (name) {
for (var i = 0; i < this.interval_array.length; i++) {
if (this.interval_array[i].name == name) {
clearInterval(this.interval_array[i].timer);
this.interval_array[i].paused = true;
return true;
}
}
return false;
};
// name = string
GUI_control.prototype.interval_resume = function (name) {
for (var i = 0; i < this.interval_array.length; i++) {
if (this.interval_array[i].name == name && this.interval_array[i].paused) {
var obj = this.interval_array[i];
obj.timer = setInterval(function() {
obj.code(); // execute code
obj.fired++; // increment counter
}, obj.interval);
obj.paused = false;
return true;
}
}
return false;
};
// input = array of timers thats meant to be kept, or nothing
// return = returns timers killed in last call
GUI_control.prototype.interval_kill_all = function (keep_array) {
var self = this;
var timers_killed = 0;
for (var i = (this.interval_array.length - 1); i >= 0; i--) { // reverse iteration
var keep = false;
if (keep_array) { // only run through the array if it exists
keep_array.forEach(function (name) {
if (self.interval_array[i].name == name) {
keep = true;
}
});
}
if (!keep) {
clearInterval(this.interval_array[i].timer); // stop timer
this.interval_array.splice(i, 1); // remove element/object from array
timers_killed++;
}
}
return timers_killed;
};
// name = string
// code = function reference (code to be executed)
// timeout = timeout in miliseconds
GUI_control.prototype.timeout_add = function (name, code, timeout) {
var self = this;
var data = {'name': name, 'timer': null, 'timeout': timeout};
// start timer with "cleaning" callback
data.timer = setTimeout(function() {
code(); // execute code
// remove object from array
var index = self.timeout_array.indexOf(data);
if (index > -1) self.timeout_array.splice(index, 1);
}, timeout);
this.timeout_array.push(data); // push to primary timeout array
return data;
};
// name = string
GUI_control.prototype.timeout_remove = function (name) {
for (var i = 0; i < this.timeout_array.length; i++) {
if (this.timeout_array[i].name == name) {
clearTimeout(this.timeout_array[i].timer); // stop timer
this.timeout_array.splice(i, 1); // remove element/object from array
return true;
}
}
return false;
};
// no input parameters
// return = returns timers killed in last call
GUI_control.prototype.timeout_kill_all = function () {
var timers_killed = 0;
for (var i = 0; i < this.timeout_array.length; i++) {
clearTimeout(this.timeout_array[i].timer); // stop timer
timers_killed++;
}
this.timeout_array = []; // drop objects
return timers_killed;
};
// message = string
GUI_control.prototype.log = function (message) {
var command_log = $('div#log');
var d = new Date();
var year = d.getFullYear();
var month = ((d.getMonth() < 9) ? '0' + (d.getMonth() + 1) : (d.getMonth() + 1));
var date = ((d.getDate() < 10) ? '0' + d.getDate() : d.getDate());
var time = ((d.getHours() < 10) ? '0' + d.getHours(): d.getHours())
+ ':' + ((d.getMinutes() < 10) ? '0' + d.getMinutes(): d.getMinutes())
+ ':' + ((d.getSeconds() < 10) ? '0' + d.getSeconds(): d.getSeconds());
var formattedDate = "{0}-{1}-{2} {3}".format(
year,
month,
date,
' @ ' + time
);
$('div.wrapper', command_log).append('<p>' + formattedDate + ' -- ' + message + '</p>');
command_log.scrollTop($('div.wrapper', command_log).height());
};
// Method is called every time a valid tab change event is received
// callback = code to run when cleanup is finished
// default switch doesn't require callback to be set
GUI_control.prototype.tab_switch_cleanup = function (callback) {
MSP.callbacks_cleanup(); // we don't care about any old data that might or might not arrive
GUI.interval_kill_all(); // all intervals (mostly data pulling) needs to be removed on tab switch
if (this.active_tab && TABS[this.active_tab]) {
TABS[this.active_tab].cleanup(callback);
} else {
callback();
}
};
GUI_control.prototype.switchery = function() {
$('.togglesmall').each(function(index, elem) {
var switchery = new Switchery(elem, {
size: 'small',
color: 'var(--accent)',
secondaryColor: 'var(--switcherysecond)'
});
$(elem).on("change", function () {
switchery.setPosition();
});
$(elem).removeClass('togglesmall');
});
$('.toggle').each(function(index, elem) {
var switchery = new Switchery(elem, {
color: 'var(--accent)',
secondaryColor: 'var(--switcherysecond)'
});
$(elem).on("change", function () {
switchery.setPosition();
});
$(elem).removeClass('toggle');
});
$('.togglemedium').each(function(index, elem) {
var switchery = new Switchery(elem, {
className: 'switcherymid',
color: 'var(--accent)',
secondaryColor: 'var(--switcherysecond)'
});
$(elem).on("change", function () {
switchery.setPosition();
});
$(elem).removeClass('togglemedium');
});
};
GUI_control.prototype.content_ready = function (callback) {
this.switchery();
if (CONFIGURATOR.connectionValid) {
// Build link to in-use CF version documentation
var documentationButton = $('div#content #button-documentation');
documentationButton.html("Wiki");
documentationButton.attr("href","https://github.com/betaflight/betaflight/wiki");
}
// loading tooltip
jQuery(document).ready(function() {
new jBox('Tooltip', {
attach: '.cf_tip',
trigger: 'mouseenter',
closeOnMouseleave: true,
closeOnClick: 'body',
delayOpen: 100,
delayClose: 100,
position: {
x: 'right',
y: 'center'
},
outside: 'x'
});
new jBox('Tooltip', {
theme: 'Widetip',
attach: '.cf_tip_wide',
trigger: 'mouseenter',
closeOnMouseleave: true,
closeOnClick: 'body',
delayOpen: 100,
delayClose: 100,
position: {
x: 'right',
y: 'center'
},
outside: 'x'
});
});
if (callback) callback();
};
GUI_control.prototype.selectDefaultTabWhenConnected = function() {
ConfigStorage.get(['rememberLastTab', 'lastTab'], function (result) {
if (!(result.rememberLastTab
&& !!result.lastTab
&& result.lastTab.substring(4) != "cli")) {
$('#tabs ul.mode-connected .tab_setup a').click();
return;
}
$("#tabs ul.mode-connected ." + result.lastTab + " a").click();
});
};
GUI_control.prototype.isChromeApp = function () {
return this.Mode === GUI_Modes.ChromeApp;
};
GUI_control.prototype.isNWJS = function () {
return this.Mode === GUI_Modes.NWJS;
};
GUI_control.prototype.isCordova = function () {
return this.Mode === GUI_Modes.Cordova;
};
GUI_control.prototype.isOther = function () {
return this.Mode === GUI_Modes.Other;
};
// initialize object into GUI variable
var GUI = new GUI_control();