diff --git a/locales/en/messages.json b/locales/en/messages.json index baa81d6e86..7380bead4b 100755 --- a/locales/en/messages.json +++ b/locales/en/messages.json @@ -6291,7 +6291,7 @@ "message": "RSSI dBm", "description": "Text of the RSSI dBm alarm" }, - + "osdWarningTextArmingDisabled": { "message": "Arming disabled", "description": "One of the warnings that can be selected to be shown in the OSD" @@ -7389,5 +7389,24 @@ "betaflightSupportButton": { "message": "Wiki", "description": "Text for the button to open the support URL" + }, + "showNotifications": { + "message": "Show notifications for long operations" + }, + "flashEraseDoneNotification": { + "message": "Dataflash erase has been completed", + "description": "Notification message when flash erase is done" + }, + "flashDownloadDoneNotification": { + "message": "Dataflash logs have been downloaded", + "description": "Notification message when flash logs are done downloading" + }, + "programmingSuccessfulNotification": { + "message": "FC has been programmed successfully", + "description": "Notification message when programming is successful" + }, + "programmingFailedNotification": { + "message": "FC programming failed", + "description": "Notification message when programming is unsuccessful" } } diff --git a/src/js/main.js b/src/js/main.js index 6b810f3440..132e951118 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -18,6 +18,7 @@ import { isExpertModeEnabled } from './utils/isExportModeEnabled.js'; import { updateTabList } from './utils/updateTabList.js'; import { checkForConfiguratorUpdates } from './utils/checkForConfiguratorUpdates.js'; import * as THREE from 'three'; +import NotificationManager from './utils/notifications.js'; if (typeof String.prototype.replaceAll === "undefined") { String.prototype.replaceAll = function(match, replace) { @@ -85,6 +86,9 @@ function appReady() { initializeSerialBackend(); }); + if (getConfig('showNotifications') && NotificationManager.checkPermission() === 'default') { + NotificationManager.requestPermission(); + } } //Process to execute to real start the app diff --git a/src/js/protocols/webstm32.js b/src/js/protocols/webstm32.js index 9cfb243b4b..ece1b124a0 100644 --- a/src/js/protocols/webstm32.js +++ b/src/js/protocols/webstm32.js @@ -18,6 +18,8 @@ import $ from 'jquery'; import serial from "../webSerial"; import DFU from "../protocols/webusbdfu"; import { read_serial } from "../serial_backend"; +import NotificationManager from "../utils/notifications"; +import { get as getConfig } from '../ConfigStorage'; function readSerialAdapter(event) { read_serial(event.detail.buffer); @@ -802,6 +804,11 @@ class STM32Protocol { // update progress bar TABS.firmware_flasher.flashingMessage(i18n.getMessage('stm32ProgrammingSuccessful'), TABS.firmware_flasher.FLASH_MESSAGE_TYPES.VALID); + // Show notification + if (getConfig('showNotifications')) { + NotificationManager.showNotification("Betaflight Configurator", {body: i18n.getMessage('programmingSuccessfulNotification'), icon: "/images/pwa/favicon.ico"}); + } + // proceed to next step this.upload_procedure(7); } else { @@ -809,6 +816,11 @@ class STM32Protocol { // update progress bar TABS.firmware_flasher.flashingMessage(i18n.getMessage('stm32ProgrammingFailed'), TABS.firmware_flasher.FLASH_MESSAGE_TYPES.INVALID); + // Show notification + if (getConfig('showNotifications')) { + NotificationManager.showNotification("Betaflight Configurator", {body: i18n.getMessage('programmingFailedNotification'), icon: "/images/pwa/favicon.ico"}); + } + // disconnect this.upload_procedure(99); } diff --git a/src/js/protocols/webusbdfu.js b/src/js/protocols/webusbdfu.js index 24e069d44d..7db8bcdc37 100644 --- a/src/js/protocols/webusbdfu.js +++ b/src/js/protocols/webusbdfu.js @@ -17,6 +17,8 @@ import GUI, { TABS } from "../gui"; import { i18n } from "../localization"; import { gui_log } from "../gui_log"; import { usbDevices } from "../usb_devices"; +import NotificationManager from "../utils/notifications"; +import { get as getConfig } from '../ConfigStorage'; class WEBUSBDFU_protocol extends EventTarget { constructor() { @@ -1064,6 +1066,11 @@ class WEBUSBDFU_protocol extends EventTarget { // update progress bar TABS.firmware_flasher.flashingMessage(i18n.getMessage('stm32ProgrammingSuccessful'), TABS.firmware_flasher.FLASH_MESSAGE_TYPES.VALID); + // Show notification + if (getConfig('showNotifications')) { + NotificationManager.showNotification("Betaflight Configurator", {body: i18n.getMessage('programmingSuccessfulNotification'), icon: "/images/pwa/favicon.ico"}); + } + // proceed to next step this.leave(); } else { @@ -1071,6 +1078,11 @@ class WEBUSBDFU_protocol extends EventTarget { // update progress bar TABS.firmware_flasher.flashingMessage(i18n.getMessage('stm32ProgrammingFailed'), TABS.firmware_flasher.FLASH_MESSAGE_TYPES.INVALID); + // Show notification + if (getConfig('showNotifications')) { + NotificationManager.showNotification("Betaflight Configurator", {body: i18n.getMessage('programmingFailedNotification'), icon: "/images/pwa/favicon.ico"}); + } + // disconnect this.cleanup(); } diff --git a/src/js/tabs/onboard_logging.js b/src/js/tabs/onboard_logging.js index 010e87cbc1..f668c5a402 100644 --- a/src/js/tabs/onboard_logging.js +++ b/src/js/tabs/onboard_logging.js @@ -14,6 +14,8 @@ import $ from 'jquery'; import DEBUG from "../debug"; import FileSystem from "../FileSystem"; import { isExpertModeEnabled } from "../utils/isExportModeEnabled"; +import NotificationManager from "../../js/utils/notifications"; +import { get as getConfig } from '../ConfigStorage'; let sdcardTimer; @@ -380,6 +382,8 @@ onboard_logging.initialize = function (callback) { } $(".dataflash-saving").addClass("done"); + + NotificationManager.showNotification("Betaflight Configurator", {body: i18n.getMessage('flashDownloadDoneNotification'), icon: "/images/pwa/favicon.ico"}); } function flash_update_summary(onDone) { @@ -498,6 +502,9 @@ onboard_logging.initialize = function (callback) { if (CONFIGURATOR.connectionValid && !eraseCancelled) { if (FC.DATAFLASH.ready) { $(".dataflash-confirm-erase")[0].close(); + if (getConfig('showNotifications')) { + NotificationManager.showNotification("Betaflight Configurator", {body: i18n.getMessage('flashEraseDoneNotification'), icon: "/images/pwa/favicon.ico"}); + } } else { setTimeout(poll_for_erase_completion, 500); } diff --git a/src/js/tabs/options.js b/src/js/tabs/options.js index 382e45d8e2..67f1800020 100644 --- a/src/js/tabs/options.js +++ b/src/js/tabs/options.js @@ -28,7 +28,7 @@ options.initialize = function (callback) { TABS.options.initCordovaForceComputerUI(); TABS.options.initDarkTheme(); TABS.options.initShowDevToolsOnStartup(); - + TABS.options.initShowNotifications(); TABS.options.initShowWarnings(); GUI.content_ready(callback); @@ -182,6 +182,16 @@ options.initShowDevToolsOnStartup = function () { .change(); }; +options.initShowNotifications = function () { + const result = getConfig("showNotifications"); + $("div.showNotifications input") + .prop("checked", !!result.showNotifications) + .change(function () { + setConfig({ showNotifications: $(this).is(":checked") }); + }) + .change(); +}; + // TODO: remove when modules are in place TABS.options = options; export { options }; diff --git a/src/js/utils/notifications.js b/src/js/utils/notifications.js new file mode 100644 index 0000000000..1b98194a4f --- /dev/null +++ b/src/js/utils/notifications.js @@ -0,0 +1,21 @@ +export default class NotificationManager { + static async requestPermission() { + if (Notification.permission === "default") { + const permission = await Notification.requestPermission(); + return permission; + } + return Notification.permission; + } + + static showNotification(title, options = {}) { + if (Notification.permission !== "granted") { + throw new Error("Notification permission not granted."); + } + + return new Notification(title, options); + } + + static checkPermission() { + return Notification.permission; + } +} diff --git a/src/tabs/options.html b/src/tabs/options.html index 86178167e5..fc967ef8fa 100644 --- a/src/tabs/options.html +++ b/src/tabs/options.html @@ -67,6 +67,12 @@ +
+
+ +
+ +