diff --git a/README.md b/README.md index da655ce..d4a9dac 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,70 @@ # Betaflight - Configurator for Android -[![GPL-3.0 license](https://img.shields.io/badge/license-GPL--3-blue.svg)](https://github.com/cmengler/betaflight-configurator-android/blob/master/LICENSE) ![Built with Cordova](https://img.shields.io/badge/built_with-Cordova-4CC2E4.svg) ![Betaflight Configurator Version](https://img.shields.io/badge/configurator-10.3.0-yellow.svg) ![Status](https://img.shields.io/badge/status-development-red.svg) +[![GPL-3.0 license](https://img.shields.io/badge/license-GPL--3-blue.svg)](https://github.com/cmengler/betaflight-configurator-android/blob/master/LICENSE) ![Built with Cordova](https://img.shields.io/badge/built_with-Cordova-4CC2E4.svg) ![Betaflight Configurator Version](https://img.shields.io/badge/configurator-10.6.0-yellow.svg) ![Status](https://img.shields.io/badge/status-development-red.svg) -### This project uses Apache Cordova to build a version of the Betaflight Configurator to run on Android devices with USB On-The-Go (OTG) capabilities. +**This project uses Apache Cordova to build a version of the Betaflight Configurator to run on Android devices with USB On-The-Go (OTG) capabilities.** This Android version is only suitable for large device screens such as tablets in landscape mode. This is a work-in-progress -- please be aware that functionality of the desktop Betaflight Configurator may not work as intended in this version. ## Prerequisites + * [Android SDK](https://developer.android.com/) -* [Node.js](https://nodejs.org/) +* [Node.js](https://nodejs.org/) v10.x +* JDK v1.8.0 +* Gradle v3.5.1 ## Build + +Refer to [build tips](README_build_tips.md) for additional help with the build process. + +### 1. Prepare Betaflight Configurator + ```bash git submodule update --init +cd betaflight-configurator +yarn install +yarn gulp dist +``` + +### 2. Build Android APK + +```bash npm install ./node_modules/gulp/bin/gulp.js www -cordova prepare -cordova build android --debug +./node_modules/cordova/bin/cordova platform add android +./node_modules/cordova/bin/cordova run ``` -This will generate the following Android APK file platforms/android/build/outputs/apk/armv7/debug/android-armv7-debug.apk + +This will generate a number of APK files in the directory `platforms/android/app/build/outputs/apk/` ## Debugging + 1. Connect the device via USB to your development machine. 2. Open Chrome -> Developer Tools (F12) -> More tools -> Remove Devices. 3. Under the development device, click the Inspect button on the WebView. Console logs will be visible in this session. ### ADB + Android Debug Bridge (adb) is a command-line tool that lets you communicate with an Android device. It's particularly useful for quickly uploading and installing APK's to the device. `adb` is included in the Android SDK [platform-tools](https://developer.android.com/studio/releases/platform-tools) package. ```bash -adb install -r platforms/android/build/outputs/apk/armv7/debug/android-armv7-debug.apk +adb install -r platforms/android/app/build/outputs/apk/armv7/debug/app-armv7-debug.apk ``` ## Notes + * `_locales` directory is renamed to `i18n` due to an Android [issue](https://issues.apache.org/jira/browse/CB-8245) with directories starting with underscores * TCP support `tcp://[IP]:[PORT]` has not been **tested** ## Known issues + * CLI output doesn't show all data when "dump" is requested * Firmware tab intentionally hidden ## Roadmap + * Propose changes to [Betaflight Configurator](https://github.com/betaflight/betaflight-configurator) HTML/CSS to be responsive for smaller screens * Revise serial support (e.g. create chrome.serial plugin for Cordova) to remove necessity to override [Betaflight Configurator](https://github.com/betaflight/betaflight-configurator) core JavaScript files in `merges` -* iOS support using WiFi \ No newline at end of file +* iOS support using WiFi diff --git a/README_build_tips.md b/README_build_tips.md new file mode 100644 index 0000000..d9a5072 --- /dev/null +++ b/README_build_tips.md @@ -0,0 +1,17 @@ +# Build Tips + +## OpenJDK + +```bash +sudo apt install openjdk-8-jdk +export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 +``` + +## Gradle + +```bash +wget https://downloads.gradle-dn.com/distributions/gradle-3.5.1-bin.zip +unzip gradle-3.5.1-bin.zip +export GRADLE_HOME=/path/to/gradle-3.5.1/bin +export PATH=$PATH:$GRADLE_HOME +``` diff --git a/betaflight-configurator b/betaflight-configurator index 40ed78a..dc484a2 160000 --- a/betaflight-configurator +++ b/betaflight-configurator @@ -1 +1 @@ -Subproject commit 40ed78ab353f0f7c3534450ad3c0611986bc4549 +Subproject commit dc484a272a37d2896a206fc19cbbd77d169530d4 diff --git a/config.xml b/config.xml index a3fe1c5..030c17b 100644 --- a/config.xml +++ b/config.xml @@ -1,5 +1,5 @@ - + Betaflight - Configurator Crossplatform configuration tool for Betaflight flight control system Betaflight Squad @@ -37,6 +37,7 @@ + @@ -59,5 +60,5 @@ - + diff --git a/gulpfile.js b/gulpfile.js index 7b47a9f..ba93533 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -2,48 +2,32 @@ const gulp = require('gulp'); const del = require('del'); -const install = require('gulp-install'); const replace = require('gulp-replace'); +const fs = require('fs'); const WWW_DIR = './www/'; gulp.task('clean-www', clean_www); -var wwwBuild = gulp.series(clean_www, www_src, www_locale, www_libraries, www_resources, www_replace); +var wwwBuild = gulp.series(clean_www, www_dist, www_locale, www_replace); gulp.task('www', wwwBuild); -function www_src() { +function www_dist() { var wwwSources = [ - './betaflight-configurator/src/**/*', - '!./betaflight-configurator/src/css/dropdown-lists/LICENSE', - '!./betaflight-configurator/src/css/font-awesome/css/font-awesome.css', - '!./betaflight-configurator/src/css/opensans_webfontkit/*.{txt,html}', - '!./betaflight-configurator/src/support/**' + './betaflight-configurator/dist/**/*', + '!./betaflight-configurator/dist/css/dropdown-lists/LICENSE', + '!./betaflight-configurator/dist/css/font-awesome/css/font-awesome.css', + '!./betaflight-configurator/dist/css/opensans_webfontkit/*.{txt,html}', ]; - return gulp.src(wwwSources, { base: 'betaflight-configurator/src' }) - .pipe(gulp.src('betaflight-configurator/manifest.json', { passthrougth: true })) - .pipe(gulp.src('betaflight-configurator/package.json', { passthrougth: true })) + return gulp.src(wwwSources, { base: 'betaflight-configurator/dist' }) .pipe(gulp.src('betaflight-configurator/changelog.html', { passthrougth: true })) - .pipe(gulp.dest(WWW_DIR)) - .pipe(install({ - npm: '--production --ignore-scripts' - })); + .pipe(gulp.dest(WWW_DIR)); } -function www_locale() { - return gulp.src('betaflight-configurator/locales/**/*', { base: 'betaflight-configurator/locales'}) - .pipe(gulp.dest(WWW_DIR + 'i18n')); -} - -function www_libraries() { - return gulp.src('betaflight-configurator/libraries/**/*', { base: 'betaflight-configurator/libraries'}) - .pipe(gulp.dest(WWW_DIR + 'js/libraries')); -} - -function www_resources() { - return gulp.src('betaflight-configurator/resources/**/*', { base: 'betaflight-configurator/resources'}) - .pipe(gulp.dest(WWW_DIR + 'resources')); +function www_locale(cb) { + fs.renameSync(WWW_DIR + '_locales', WWW_DIR + 'i18n'); + cb(); } function www_replace() { diff --git a/merges/android/js/Beepers.js b/merges/android/js/Beepers.js index 6bfbacd..2f2b535 100644 --- a/merges/android/js/Beepers.js +++ b/merges/android/js/Beepers.js @@ -33,6 +33,12 @@ var Beepers = function (config, supportedConditions) { ); } + if (semver.gte(config.apiVersion, "1.39.0")) { + beepers.push( + {bit: 22, name: 'RC_SMOOTHING_INIT_FAIL', visible: true} + ); + } + if (supportedConditions) { self._beepers = []; beepers.forEach(function (beeper) { @@ -81,7 +87,7 @@ Beepers.prototype.generateElements = function (template, destination) { destination.append(element); var input_e = $(element).find('input'); - var label_e = $(element).find('label'); + var label_e = $(element).find('div'); var span_e = $(element).find('span'); input_e.attr('id', 'beeper-' + i); diff --git a/merges/android/js/ConfigStorage.js b/merges/android/js/ConfigStorage.js new file mode 100644 index 0000000..f0ced70 --- /dev/null +++ b/merges/android/js/ConfigStorage.js @@ -0,0 +1,51 @@ +'use strict'; + +// idea here is to abstract around the use of chrome.storage.local as it functions differently from "localStorage" and IndexedDB +// localStorage deals with strings, not objects, so the objects have been serialized. +var ConfigStorage = { + // key can be one string, or array of strings + get: function(key, callback) { + if (GUI.isChromeApp()) { + chrome.storage.local.get(key,callback); + } else { + //console.log('Abstraction.get',key); + if (Array.isArray(key)) { + var obj = {}; + key.forEach(function (element) { + try { + obj = $.extend(obj, JSON.parse(window.localStorage.getItem(element))); + } catch (e) { + // is okay + } + }); + callback(obj); + } else { + var keyValue = window.localStorage.getItem(key); + if (keyValue) { + var obj = {}; + try { + obj = JSON.parse(keyValue); + } catch (e) { + // It's fine if we fail that parse + } + callback(obj); + } else { + callback({}); + } + } + } + }, + // set takes an object like {'userLanguageSelect':'DEFAULT'} + set: function(input) { + if (GUI.isChromeApp()) { + chrome.storage.local.set(input); + } else { + //console.log('Abstraction.set',input); + Object.keys(input).forEach(function (element) { + var tmpObj = {}; + tmpObj[element] = input[element]; + window.localStorage.setItem(element, JSON.stringify(tmpObj)); + }); + } + } +} diff --git a/merges/android/js/localization.js b/merges/android/js/localization.js index 59f3cce..069d214 100644 --- a/merges/android/js/localization.js +++ b/merges/android/js/localization.js @@ -6,14 +6,13 @@ var i18n = {} -const languagesAvailables = ['ca', 'de', 'en', 'es', 'fr', 'it', 'ja', 'ko', 'lv', 'pt', 'zh_CN']; +const languagesAvailables = ['ca', 'de', 'en', 'es', 'fr', 'gl', 'hr', 'id', 'it', 'ja', 'ko', 'lv', 'pt', 'ru', 'sv', 'zh_CN']; /** * Functions that depend on the i18n framework */ i18n.init = function(cb) { - - getStoredUserLocale(function(userLanguage){ + getStoredUserLocale(function(userLanguage) { i18next .use(i18nextXHRBackend) @@ -30,14 +29,30 @@ i18n.init = function(cb) { console.error('Error loading i18n ' + err); } else { console.log('i18n system loaded'); + var detectedLanguage = i18n.getMessage('language_' + getValidLocale("DEFAULT")); + i18n.addResources({"detectedLanguage": detectedLanguage }); } if (cb !== undefined) { cb(); } }); }); + // This function should do the same things that the i18n.localizePage function below does. + i18next.on('languageChanged', function (newLang) { + var translate = function(messageID) { + return i18n.getMessage(messageID); + }; + i18n.localizePage(true); + updateStatusBarVersion(); + }); } +i18n.changeLanguage = function(languageSelected) { + ConfigStorage.set({'userLanguageSelect': languageSelected}); + i18next.changeLanguage(getValidLocale(languageSelected)); + i18n.selectedLanguage = languageSelected; + GUI.log(i18n.getMessage('language_changed')); +} i18n.getMessage = function(messageID, parameters) { var translatedString; @@ -67,48 +82,70 @@ i18n.getLanguagesAvailables = function() { return languagesAvailables; } +i18n.getCurrentLocale = function() { + return i18next.language; +} + +i18n.existsMessage = function(key) { + return i18next.exists(key); +} + /** * Helper functions, don't depend of the i18n framework */ -i18n.localizePage = function() { +i18n.localizePage = function(forceReTranslate) { var localized = 0; var translate = function(messageID) { localized++; - return i18n.getMessage(messageID); }; - $('[i18n]:not(.i18n-replaced)').each(function() { - var element = $(this); - - element.html(translate(element.attr('i18n'))); - element.addClass('i18n-replaced'); - }); - - $('[i18n_title]:not(.i18n_title-replaced)').each(function() { - var element = $(this); - - element.attr('title', translate(element.attr('i18n_title'))); - element.addClass('i18n_title-replaced'); - }); - - $('[i18n_value]:not(.i18n_value-replaced)').each(function() { - var element = $(this); + if (forceReTranslate) { + $('[i18n]').each(function() { + var element = $(this); + element.html(translate(element.attr('i18n'))); + }); + $('[i18n_title]').each(function() { + var element = $(this); + element.attr('title', translate(element.attr('i18n_title'))); + }); + $('[i18n_value]').each(function() { + var element = $(this); + element.val(translate(element.attr('i18n_value'))); + }); + $('[i18n_placeholder]').each(function() { + var element = $(this); + element.attr('placeholder', translate(element.attr('i18n_placeholder'))); + }); + } else { - element.val(translate(element.attr('i18n_value'))); - element.addClass('i18n_value-replaced'); - }); + $('[i18n]:not(.i18n-replaced)').each(function() { + var element = $(this); + element.html(translate(element.attr('i18n'))); + element.addClass('i18n-replaced'); + }); - $('[i18n_placeholder]:not(.i18n_placeholder-replaced)').each(function() { - var element = $(this); + $('[i18n_title]:not(.i18n_title-replaced)').each(function() { + var element = $(this); + element.attr('title', translate(element.attr('i18n_title'))); + element.addClass('i18n_title-replaced'); + }); - element.attr('placeholder', translate(element.attr('i18n_placeholder'))); - element.addClass('i18n_placeholder-replaced'); - }); + $('[i18n_value]:not(.i18n_value-replaced)').each(function() { + var element = $(this); + element.val(translate(element.attr('i18n_value'))); + element.addClass('i18n_value-replaced'); + }); + $('[i18n_placeholder]:not(.i18n_placeholder-replaced)').each(function() { + var element = $(this); + element.attr('placeholder', translate(element.attr('i18n_placeholder'))); + element.addClass('i18n_placeholder-replaced'); + }); + } return localized; } @@ -117,11 +154,12 @@ i18n.localizePage = function() { * returns the current locale to the callback */ function getStoredUserLocale(cb) { - chrome.storage.local.get('userLanguageSelect', function (result) { + ConfigStorage.get('userLanguageSelect', function (result) { var userLanguage = 'DEFAULT'; if (result.userLanguageSelect) { userLanguage = result.userLanguageSelect } + i18n.selectedLanguage = userLanguage; userLanguage = getValidLocale(userLanguage); @@ -163,4 +201,4 @@ i18n.addResources = function(bundle) { var lang = takeFirst(i18next.options.fallbackLng), ns = takeFirst(i18next.options.defaultNS); i18next.addResourceBundle(lang, ns, bundle, true, true); -}; \ No newline at end of file +}; diff --git a/merges/android/js/main.js b/merges/android/js/main.js index 1305926..ce1883b 100644 --- a/merges/android/js/main.js +++ b/merges/android/js/main.js @@ -1,19 +1,93 @@ 'use strict'; -openNewWindowsInExternalBrowser(); +var googleAnalytics = analytics; +var analytics = undefined; + +function checkSetupAnalytics(callback) { + if (!analytics) { + setTimeout(function () { + ConfigStorage.get(['userId', 'analyticsOptOut', 'checkForConfiguratorUnstableVersions', ], function (result) { + if (!analytics) { + setupAnalytics(result); + } + + callback(analytics); + }); + }); + } else if (callback) { + callback(analytics); + } +}; + +function getBuildType() { + return GUI.Mode; +} + +function setupAnalytics(result) { + var userId; + if (result.userId) { + userId = result.userId; + } else { + var uid = new ShortUniqueId(); + userId = uid.randomUUID(13); + + ConfigStorage.set({ 'userId': userId }); + } + + var optOut = !!result.analyticsOptOut; + var checkForDebugVersions = !!result.checkForConfiguratorUnstableVersions; + + var debugMode = typeof process === "object" && process.versions['nw-flavor'] === 'sdk'; + + analytics = new Analytics('UA-123002063-1', userId, 'Betaflight Configurator', CONFIGURATOR.version, CONFIGURATOR.gitChangesetId, GUI.operating_system, checkForDebugVersions, optOut, debugMode, getBuildType()); + + function logException(exception) { + analytics.sendException(exception.stack); + } + + if (typeof process === "object") { + process.on('uncaughtException', logException); + } + + analytics.sendEvent(analytics.EVENT_CATEGORIES.APPLICATION, 'AppStart', { sessionControl: 'start' }); + + function sendCloseEvent() { + analytics.sendEvent(analytics.EVENT_CATEGORIES.APPLICATION, 'AppClose', { sessionControl: 'end' }) + } + + if (GUI.isNWJS()) { + var win = GUI.nwGui.Window.get(); + win.on('close', function () { + sendCloseEvent(); + + this.close(true); + }); + win.on('new-win-policy', function(frame, url, policy) { + // do not open the window + policy.ignore(); + // and open it in external browser + GUI.nwGui.Shell.openExternal(url); + }); + } else if (!GUI.isOther()) { + // Looks like we're in Chrome - but the event does not actually get fired + chrome.runtime.onSuspend.addListener(sendCloseEvent); + } + + $('.connect_b a.connect').removeClass('disabled'); + $('.firmware_b a.flash').removeClass('disabled'); +} //Process to execute to real start the app function startProcess() { - // translate to user-selected language i18n.localizePage(); // alternative - window.navigator.appVersion.match(/Chrome\/([0-9.]*)/)[1]; - GUI.log(i18n.getMessage('infoVersions',{operatingSystem: GUI.operating_system, - chromeVersion: window.navigator.appVersion.replace(/.*Chrome\/([0-9.]*).*/, "$1"), - configuratorVersion: getManifestVersion()})); + GUI.log(i18n.getMessage('infoVersions', { operatingSystem: GUI.operating_system, + chromeVersion: window.navigator.appVersion.replace(/.*Chrome\/([0-9.]*).*/, "$1"), + configuratorVersion: CONFIGURATOR.version })); - $('#logo .version').text(getManifestVersion()); + $('#logo .version').text(CONFIGURATOR.version); updateStatusBarVersion(); updateTopBarVersion(); @@ -32,16 +106,10 @@ function startProcess() { break; } - if (GUI.operating_system !== 'ChromeOS') { + if (!GUI.isOther() && GUI.operating_system !== 'ChromeOS') { checkForConfiguratorUpdates(); } - chrome.storage.local.get('logopen', function (result) { - if (result.logopen) { - $("#showlog").trigger('click'); - } - }); - // log webgl capability // it would seem the webgl "enabling" through advanced settings will be ignored in the future // and webgl will be supported if gpu supports it by default (canary 40.0.2175.0), keep an eye on this one @@ -51,6 +119,13 @@ function startProcess() { console.log('Libraries: jQuery - ' + $.fn.jquery + ', d3 - ' + d3.version + ', three.js - ' + THREE.REVISION); // Tabs + $("#tabs ul.mode-connected li").click(function() { + // store the first class of the current tab (omit things like ".active") + ConfigStorage.set({ + lastTab: $(this).attr("class").split(' ')[0] + }); + }); + var ui_tabs = $('#tabs > ul'); $('a', ui_tabs).click(function () { if ($(this).parent().hasClass('active') == false && !GUI.tab_switch_in_progress) { // only initialize when the tab isn't already active @@ -72,7 +147,14 @@ function startProcess() { return; } - if (GUI.allowedTabs.indexOf(tab) < 0) { + if (GUI.allowedTabs.indexOf(tab) < 0 && tabName == "Firmware Flasher") { + if (GUI.connected_to || GUI.connecting_to) { + $('a.connect').click(); + } else { + self.disconnect(); + } + $('div.open_firmware_flasher a.flash').click(); + } else if (GUI.allowedTabs.indexOf(tab) < 0) { GUI.log(i18n.getMessage('tabSwitchUpgradeRequired', [tabName])); return; } @@ -80,6 +162,11 @@ function startProcess() { GUI.tab_switch_in_progress = true; GUI.tab_switch_cleanup(function () { + // disable active firmware flasher if it was active + if ($('div#flashbutton a.flash_state').hasClass('active') && $('div#flashbutton a.flash').hasClass('active')) { + $('div#flashbutton a.flash_state').removeClass('active'); + $('div#flashbutton a.flash').removeClass('active'); + } // disable previously active tab highlight $('li', ui_tabs).removeClass('active'); @@ -97,10 +184,20 @@ function startProcess() { GUI.tab_switch_in_progress = false; } + checkSetupAnalytics(function (analytics) { + analytics.sendAppView(tab); + }); + switch (tab) { case 'landing': TABS.landing.initialize(content_ready); break; + case 'changelog': + TABS.staticTab.initialize('changelog', content_ready); + break; + case 'privacy_policy': + TABS.staticTab.initialize('privacy_policy', content_ready); + break; case 'firmware_flasher': TABS.firmware_flasher.initialize(content_ready); break; @@ -128,6 +225,9 @@ function startProcess() { case 'osd': TABS.osd.initialize(content_ready); break; + case 'vtx': + TABS.vtx.initialize(content_ready); + break; case 'power': TABS.power.initialize(content_ready); break; @@ -166,7 +266,7 @@ function startProcess() { TABS.onboard_logging.initialize(content_ready); break; case 'cli': - TABS.cli.initialize(content_ready); + TABS.cli.initialize(content_ready, GUI.nwGui); break; default: @@ -190,7 +290,7 @@ function startProcess() { // translate to user-selected language i18n.localizePage(); - chrome.storage.local.get('permanentExpertMode', function (result) { + ConfigStorage.get('permanentExpertMode', function (result) { if (result.permanentExpertMode) { $('div.permanentExpertMode input').prop('checked', true); } @@ -198,18 +298,21 @@ function startProcess() { $('div.permanentExpertMode input').change(function () { var checked = $(this).is(':checked'); - chrome.storage.local.set({'permanentExpertMode': checked}); + ConfigStorage.set({'permanentExpertMode': checked}); $('input[name="expertModeCheckbox"]').prop('checked', checked).change(); - if (FEATURE_CONFIG) { - updateTabList(FEATURE_CONFIG.features); - } - }).change(); }); + ConfigStorage.get('rememberLastTab', function (result) { + $('div.rememberLastTab input') + .prop('checked', !!result.rememberLastTab) + .change(function() { ConfigStorage.set({rememberLastTab: $(this).is(':checked')}) }) + .change(); + }); + if (GUI.operating_system !== 'ChromeOS') { - chrome.storage.local.get('checkForConfiguratorUnstableVersions', function (result) { + ConfigStorage.get('checkForConfiguratorUnstableVersions', function (result) { if (result.checkForConfiguratorUnstableVersions) { $('div.checkForConfiguratorUnstableVersions input').prop('checked', true); } @@ -217,7 +320,7 @@ function startProcess() { $('div.checkForConfiguratorUnstableVersions input').change(function () { var checked = $(this).is(':checked'); - chrome.storage.local.set({'checkForConfiguratorUnstableVersions': checked}); + ConfigStorage.set({'checkForConfiguratorUnstableVersions': checked}); checkForConfiguratorUpdates(); }); @@ -226,29 +329,48 @@ function startProcess() { $('div.checkForConfiguratorUnstableVersions').hide(); } - chrome.storage.local.get('userLanguageSelect', function (result) { - - var userLanguage_e = $('div.userLanguage select'); - var languagesAvailables = i18n.getLanguagesAvailables(); - userLanguage_e.append(''); - userLanguage_e.append(''); - languagesAvailables.forEach(function(element) { - var languageName = i18n.getMessage('language_' + element); - userLanguage_e.append(''); - }); - - if (result.userLanguageSelect) { - userLanguage_e.val(result.userLanguageSelect); + ConfigStorage.get('analyticsOptOut', function (result) { + if (result.analyticsOptOut) { + $('div.analyticsOptOut input').prop('checked', true); } - - userLanguage_e.change(function () { - var languageSelected = $(this).val(); - // Select the new language, a restart is required - chrome.storage.local.set({'userLanguageSelect': languageSelected}); - }); + $('div.analyticsOptOut input').change(function () { + var checked = $(this).is(':checked'); + + ConfigStorage.set({'analyticsOptOut': checked}); + + checkSetupAnalytics(function (analytics) { + if (checked) { + analytics.sendEvent(analytics.EVENT_CATEGORIES.APPLICATION, 'OptOut'); + } + + analytics.setOptOut(checked); + + if (!checked) { + analytics.sendEvent(analytics.EVENT_CATEGORIES.APPLICATION, 'OptIn'); + } + }); + }).change(); }); + $('div.cliAutoComplete input') + .prop('checked', CliAutoComplete.configEnabled) + .change(function () { + var checked = $(this).is(':checked'); + + ConfigStorage.set({'cliAutoComplete': checked}); + CliAutoComplete.setEnabled(checked); + }).change(); + + $('#darkThemeSelect') + .val(DarkTheme.configEnabled) + .change(function () { + var value = parseInt($(this).val()); + + ConfigStorage.set({'darkTheme': value}); + setDarkTheme(value); + }).change(); + function close_and_cleanup(e) { if (e.type == 'click' && !$.contains($('div#options-window')[0], e.target) || e.type == 'keyup' && e.keyCode == 27) { $(document).unbind('click keyup', close_and_cleanup); @@ -353,7 +475,7 @@ function startProcess() { $("#content").removeClass('logopen'); $(".tab_container").removeClass('logopen'); $("#scrollicon").removeClass('active'); - chrome.storage.local.set({'logopen': false}); + ConfigStorage.set({'logopen': false}); state = false; } else { @@ -362,7 +484,7 @@ function startProcess() { $("#content").addClass('logopen'); $(".tab_container").addClass('logopen'); $("#scrollicon").addClass('active'); - chrome.storage.local.set({'logopen': true}); + ConfigStorage.set({'logopen': true}); state = true; } @@ -370,19 +492,51 @@ function startProcess() { $(this).data('state', state); }); - chrome.storage.local.get('permanentExpertMode', function (result) { + ConfigStorage.get('logopen', function (result) { + if (result.logopen) { + $("#showlog").trigger('click'); + } + }); + + ConfigStorage.get('permanentExpertMode', function (result) { if (result.permanentExpertMode) { $('input[name="expertModeCheckbox"]').prop('checked', true); } $('input[name="expertModeCheckbox"]').change(function () { - if (FEATURE_CONFIG) { + var checked = $(this).is(':checked'); + checkSetupAnalytics(function (analytics) { + analytics.setDimension(analytics.DIMENSIONS.CONFIGURATOR_EXPERT_MODE, checked ? 'On' : 'Off'); + }); + + if (FEATURE_CONFIG && FEATURE_CONFIG.features !== 0) { updateTabList(FEATURE_CONFIG.features); } }).change(); }); + + ConfigStorage.get('cliAutoComplete', function (result) { + CliAutoComplete.setEnabled(typeof result.cliAutoComplete == 'undefined' || result.cliAutoComplete); // On by default + }); + + ConfigStorage.get('darkTheme', function (result) { + if (result.darkTheme === undefined || typeof result.darkTheme !== "number") { + // sets dark theme to auto if not manually changed + setDarkTheme(2); + } else { + setDarkTheme(result.darkTheme); + } + }); }; +function setDarkTheme(enabled) { + DarkTheme.setConfig(enabled); + + checkSetupAnalytics(function (analytics) { + analytics.sendEvent(analytics.EVENT_CATEGORIES.APPLICATION, 'DarkTheme', enabled); + }); +} + function checkForConfiguratorUpdates() { var releaseChecker = new ReleaseChecker('configurator', 'https://api.github.com/repos/betaflight/betaflight-configurator/releases'); @@ -390,7 +544,7 @@ function checkForConfiguratorUpdates() { } function notifyOutdatedVersion(releaseData) { - chrome.storage.local.get('checkForConfiguratorUnstableVersions', function (result) { + ConfigStorage.get('checkForConfiguratorUnstableVersions', function (result) { var showUnstableReleases = false; if (result.checkForConfiguratorUnstableVersions) { showUnstableReleases = true; @@ -408,7 +562,7 @@ function notifyOutdatedVersion(releaseData) { } }); - if (versions.length > 0 && semver.lt(getManifestVersion(), versions[0].tag_name)) { + if (versions.length > 0 && semver.lt(CONFIGURATOR.version, versions[0].tag_name)) { GUI.log(i18n.getMessage('configuratorUpdateNotice', [versions[0].tag_name, versions[0].html_url])); var dialog = $('.dialogConfiguratorUpdate')[0]; @@ -471,28 +625,25 @@ function isExpertModeEnabled() { } function updateTabList(features) { - if (features.isEnabled('GPS') && isExpertModeEnabled()) { - $('#tabs ul.mode-connected li.tab_gps').show(); - } else { - $('#tabs ul.mode-connected li.tab_gps').hide(); - } if (isExpertModeEnabled()) { $('#tabs ul.mode-connected li.tab_failsafe').show(); - } else { - $('#tabs ul.mode-connected li.tab_failsafe').hide(); - } - - if (isExpertModeEnabled()) { $('#tabs ul.mode-connected li.tab_adjustments').show(); + $('#tabs ul.mode-connected li.tab_servos').show(); + $('#tabs ul.mode-connected li.tab_sensors').show(); + $('#tabs ul.mode-connected li.tab_logging').show(); } else { + $('#tabs ul.mode-connected li.tab_failsafe').hide(); $('#tabs ul.mode-connected li.tab_adjustments').hide(); + $('#tabs ul.mode-connected li.tab_servos').hide(); + $('#tabs ul.mode-connected li.tab_sensors').hide(); + $('#tabs ul.mode-connected li.tab_logging').hide(); } - if (isExpertModeEnabled()) { - $('#tabs ul.mode-connected li.tab_servos').show(); + if (features.isEnabled('GPS') && isExpertModeEnabled()) { + $('#tabs ul.mode-connected li.tab_gps').show(); } else { - $('#tabs ul.mode-connected li.tab_servos').hide(); + $('#tabs ul.mode-connected li.tab_gps').hide(); } if (features.isEnabled('LED_STRIP')) { @@ -501,18 +652,6 @@ function updateTabList(features) { $('#tabs ul.mode-connected li.tab_led_strip').hide(); } - if (isExpertModeEnabled()) { - $('#tabs ul.mode-connected li.tab_sensors').show(); - } else { - $('#tabs ul.mode-connected li.tab_sensors').hide(); - } - - if (isExpertModeEnabled()) { - $('#tabs ul.mode-connected li.tab_logging').show(); - } else { - $('#tabs ul.mode-connected li.tab_logging').hide(); - } - if (features.isEnabled('TRANSPONDER')) { $('#tabs ul.mode-connected li.tab_transponder').show(); } else { @@ -530,6 +669,13 @@ function updateTabList(features) { } else { $('#tabs ul.mode-connected li.tab_power').hide(); } + + if (semver.gte(CONFIG.apiVersion, "1.42.0")) { + $('#tabs ul.mode-connected li.tab_vtx').show(); + } else { + $('#tabs ul.mode-connected li.tab_vtx').hide(); + } + } function zeroPad(value, width) { @@ -565,28 +711,36 @@ function generateFilename(prefix, suffix) { return filename + '.' + suffix; } -function getFirmwareVersion(firmwareVersion, firmwareId, hardwareId) { +function getTargetVersion(hardwareId) { + var versionText = ''; + + if (hardwareId) { + versionText += i18n.getMessage('versionLabelTarget') + ': ' + hardwareId; + } + + return versionText; +} + +function getFirmwareVersion(firmwareVersion, firmwareId) { var versionText = ''; if (firmwareVersion) { versionText += i18n.getMessage('versionLabelFirmware') + ': ' + firmwareId + ' ' + firmwareVersion; - - if (hardwareId) { - versionText += ' (' + i18n.getMessage('versionLabelTarget') + ': ' + hardwareId + ')'; - } } return versionText; } function getConfiguratorVersion() { - return i18n.getMessage('versionLabelConfigurator') + ': ' + getManifestVersion(); + return i18n.getMessage('versionLabelConfigurator') + ': ' + CONFIGURATOR.version; } function updateTopBarVersion(firmwareVersion, firmwareId, hardwareId) { var versionText = getConfiguratorVersion() + '
'; - versionText = versionText + getFirmwareVersion(firmwareVersion, firmwareId, hardwareId); + versionText = versionText + getFirmwareVersion(firmwareVersion, firmwareId) + '
'; + + versionText = versionText + getTargetVersion(hardwareId); $('#logo .logo_text').html(versionText); } @@ -594,43 +748,32 @@ function updateTopBarVersion(firmwareVersion, firmwareId, hardwareId) { function updateStatusBarVersion(firmwareVersion, firmwareId, hardwareId) { var versionText = ''; - versionText = versionText + getFirmwareVersion(firmwareVersion, firmwareId, hardwareId); + versionText = versionText + getFirmwareVersion(firmwareVersion, firmwareId); if (versionText !== '') { versionText = versionText + ', '; } - versionText = versionText + getConfiguratorVersion(); - - $('#status-bar .version').text(versionText); -} + let targetVersion = getTargetVersion(hardwareId); + versionText = versionText + targetVersion; -function getManifestVersion(manifest) { - if (!manifest) { - manifest = chrome.runtime.getManifest(); + if (targetVersion !== '') { + versionText = versionText + ', '; } - var version = manifest.version_name; - if (!version) { - version = manifest.version; - } + versionText = versionText + getConfiguratorVersion() + ' (' + CONFIGURATOR.gitChangesetId + ')'; - return version; + $('#status-bar .version').text(versionText); } -function openNewWindowsInExternalBrowser() { - try { - var gui = require('nw.gui'); +function showErrorDialog(message) { + var dialog = $('.dialogError')[0]; - //Get the current window - var win = gui.Window.get(); + $('.dialogError-content').html(message); - //Listen to the new window event - win.on('new-win-policy', function (frame, url, policy) { - gui.Shell.openExternal(url); - policy.ignore(); - }); - } catch (ex) { - console.log("require does not exist, maybe inside chrome"); - } -} \ No newline at end of file + $('.dialogError-closebtn').click(function() { + dialog.close(); + }); + + dialog.showModal(); +} diff --git a/merges/android/js/port_handler.js b/merges/android/js/port_handler.js index c776a3e..0341b3d 100755 --- a/merges/android/js/port_handler.js +++ b/merges/android/js/port_handler.js @@ -1,8 +1,11 @@ 'use strict'; -var usbDevices = { - STM32DFU: {'vendorId': 1155, 'productId': 57105} -}; +const TIMEOUT_CHECK = 500; // With 250 it seems that it produces a memory leak and slowdown in some versions, reason unknown + +var usbDevices = { filters: [ + {'vendorId': 1155, 'productId': 57105}, + {'vendorId': 10473, 'productId': 393} +] }; var PortHandler = new function () { this.initial_ports = false; @@ -12,7 +15,7 @@ var PortHandler = new function () { }; PortHandler.initialize = function () { - // start listening, check after 250ms + // start listening, check after TIMEOUT_CHECK ms this.check(); }; @@ -63,7 +66,7 @@ PortHandler.check = function () { // auto-select last used port (only during initialization) if (!self.initial_ports) { - chrome.storage.local.get('last_used_port', function (result) { + ConfigStorage.get('last_used_port', function (result) { // if last_used_port was set, we try to select it if (result.last_used_port) { current_ports.forEach(function(port) { @@ -141,7 +144,7 @@ PortHandler.check = function () { GUI.updateManualPortVisibility(); setTimeout(function () { self.check(); - }, 250); + }, TIMEOUT_CHECK); }); }; @@ -156,7 +159,9 @@ PortHandler.update_port_select = function (ports) { $('div#port-picker #port').append($("