diff --git a/locales/en/messages.json b/locales/en/messages.json
index a7d1e0a8de..0e176895f1 100755
--- a/locales/en/messages.json
+++ b/locales/en/messages.json
@@ -368,7 +368,60 @@
"tabOptions": {
"message": "Options"
},
-
+ "tabNornConfig": {
+ "message": "NORN Config"
+ },
+ "nornManticore": {
+ "message": "Manticore"
+ },
+ "nornVtx": {
+ "message": "VTX"
+ },
+ "nornNone": {
+ "message": "None"
+ },
+ "nornGenerate": {
+ "message": "Generate"
+ },
+ "nornOutput": {
+ "message": "NORN Configuration"
+ },
+ "nornDrone": {
+ "message": "Drone"
+ },
+ "nornController": {
+ "message": "Controller"
+ },
+ "nornRadioController": {
+ "message": "Radio Controller"
+ },
+ "nornSize": {
+ "message": "Size"
+ },
+ "nornCraftName": {
+ "message": "Craft Name"
+ },
+ "nornMBId": {
+ "message": "MB ID"
+ },
+ "nornFailSafe": {
+ "message": "FailSafe"
+ },
+ "nornVideoFormat": {
+ "message": "Video Format"
+ },
+ "nornCamera": {
+ "message": "Camera"
+ },
+ "nornGPS": {
+ "message": "GPS"
+ },
+ "nornCopy": {
+ "message": "Copy to clipboard"
+ },
+ "nornSave": {
+ "message": "Save"
+ },
"tabSetup": {
"message": "Setup"
},
@@ -1937,6 +1990,12 @@
"portsFunction_VTX_MSP": {
"message": "VTX (MSP + Displayport)"
},
+ "portsFunction_MANTICORE_INITIATOR_V1": {
+ "message": "Manticore V1"
+ },
+ "portsFunction_MANTICORE_INITIATOR_V2": {
+ "message": "Manticore V2"
+ },
"pidTuningProfileOption": {
"message": "Profile $1"
},
@@ -7745,6 +7804,15 @@
"message": "Sonar",
"description": "Displays distance from sonar sensor in cm"
},
+ "osdTextElementManticoreV1": {
+ "message": "Manticore V1"
+ },
+ "osdTextElementManticoreV2": {
+ "message": "Manticore V2"
+ },
+ "osdTextElementManticoreV2Logger": {
+ "message": "Manticore V2 Logger"
+ },
"osdDescElementLidar": {
"message": "$t(osdTextElementLidar.message)",
"description": "Don't translate!!!"
diff --git a/locales/uk/messages.json b/locales/uk/messages.json
index 5af82e33fe..471c5459aa 100644
--- a/locales/uk/messages.json
+++ b/locales/uk/messages.json
@@ -7510,5 +7510,44 @@
"osdDescElementLidar": {
"message": "$t(osdTextElementLidar.message)",
"description": "Don't translate!!!"
+ },
+ "nornManticore": {
+ "message": "Manticore"
+ },
+ "nornVtx": {
+ "message": "VTX"
+ },
+ "nornNone": {
+ "message": "Немає"
+ },
+ "nornGenerate": {
+ "message": "Згенерувати"
+ },
+ "nornOutput": {
+ "message": "Конфігурація"
+ },
+ "nornDrone": {
+ "message": "Дрон"
+ },
+ "nornController": {
+ "message": "Контролер"
+ },
+ "nornSize": {
+ "message": "Розмір"
+ },
+ "nornCraftName": {
+ "message": "Назва дрона"
+ },
+ "nornGPS": {
+ "message": "GPS"
+ },
+ "nornCopy": {
+ "message": "Копіювати в буффер"
+ },
+ "nornSave": {
+ "message": "Зберегти"
+ },
+ "tabNornConfig": {
+ "message": "NORN Налаштування"
}
}
diff --git a/package.json b/package.json
index 995de18faa..5bee84d710 100644
--- a/package.json
+++ b/package.json
@@ -41,6 +41,8 @@
"node": "20.x"
},
"dependencies": {
+ "ejs": "^3.1.9",
+ "handlebars": "^4.7.8",
"@capacitor/android": "^7.0.1",
"@capacitor/core": "^7.0.1",
"@fortawesome/fontawesome-free": "^6.5.2",
diff --git a/src/index.html b/src/index.html
index f68454799c..3acd8137ee 100644
--- a/src/index.html
+++ b/src/index.html
@@ -130,12 +130,14 @@
+
+
diff --git a/src/js/gui.js b/src/js/gui.js
index e2aa085294..c86efe5bc9 100644
--- a/src/js/gui.js
+++ b/src/js/gui.js
@@ -19,7 +19,14 @@ class GuiControl {
this.timeout_array = [];
this.buttonDisabledClass = "disabled";
- this.defaultAllowedTabsWhenDisconnected = ["landing", "firmware_flasher", "privacy_policy", "options", "help"];
+ this.defaultAllowedTabsWhenDisconnected = [
+ "landing",
+ "firmware_flasher",
+ "privacy_policy",
+ "options",
+ "help",
+ "norn_config",
+ ];
this.defaultAllowedTabs = [
"setup",
@@ -30,6 +37,7 @@ class GuiControl {
"presets",
"cli",
"configuration",
+ "norn_config",
"logging",
"onboard_logging",
"modes",
diff --git a/src/js/main.js b/src/js/main.js
index b19fbf185c..bd426350af 100644
--- a/src/js/main.js
+++ b/src/js/main.js
@@ -279,6 +279,9 @@ function startProcess() {
configuration.initialize(content_ready),
);
break;
+ case "norn_config":
+ import("./tabs/norn_config").then(({ norn_config }) => norn_config.initialize(content_ready));
+ break;
case "pid_tuning":
import("./tabs/pid_tuning").then(({ pid_tuning }) => pid_tuning.initialize(content_ready));
break;
diff --git a/src/js/msp/MSPHelper.js b/src/js/msp/MSPHelper.js
index 7112b32b0f..627066a729 100644
--- a/src/js/msp/MSPHelper.js
+++ b/src/js/msp/MSPHelper.js
@@ -67,6 +67,11 @@ function MspHelper() {
LIDAR_TF: 15,
FRSKY_OSD: 16,
VTX_MSP: 17,
+ GIMBAL: 18,
+ KOLIBRI_INITIATOR: 19,
+ DETONATOR: 20,
+ MANTICORE_INITIATOR_V1: 21,
+ MANTICORE_INITIATOR_V2: 22,
};
self.REBOOT_TYPES = {
diff --git a/src/js/serial_backend.js b/src/js/serial_backend.js
index 88a034e477..31ab4facce 100644
--- a/src/js/serial_backend.js
+++ b/src/js/serial_backend.js
@@ -508,6 +508,7 @@ function checkReportProblems() {
}
}
+ needsProblemReportingDialog = false;
if (needsProblemReportingDialog) {
problems.forEach((problem) => {
problemItemTemplate.clone().html(problem.description).appendTo(problemDialogList);
diff --git a/src/js/tabs/firmware_flasher.js b/src/js/tabs/firmware_flasher.js
index a42f857ea7..8b98960a70 100644
--- a/src/js/tabs/firmware_flasher.js
+++ b/src/js/tabs/firmware_flasher.js
@@ -740,10 +740,60 @@ firmware_flasher.initialize = async function (callback) {
STM32.rebootMode = 0;
GUI.connect_lock = false;
startFlashing();
+ } else {
+ // Auto-detect board when firmware flasher tab is active and no flash-on-connect
+ console.log(`${self.logHead} Auto-detecting board for connected USB device`);
+ AutoDetect.verifyBoard();
+ }
+ }
+
+ function detectedSerialDevice(device) {
+ const isFlashOnConnect = $("input.flash_on_connect").is(":checked");
+
+ console.log(`${self.logHead} Detected serial device:`, device);
+ console.log(`${self.logHead} Reboot mode: %s, flash on connect`, STM32.rebootMode, isFlashOnConnect);
+
+ if (STM32.rebootMode || isFlashOnConnect) {
+ STM32.rebootMode = 0;
+ GUI.connect_lock = false;
+ startFlashing();
+ } else {
+ // Auto-detect board when firmware flasher tab is active and no flash-on-connect
+ console.log(`${self.logHead} Auto-detecting board for connected serial device`);
+ AutoDetect.verifyBoard();
}
}
EventBus.$on("port-handler:auto-select-usb-device", detectedUsbDevice);
+ EventBus.$on("port-handler:auto-select-serial-device", detectedSerialDevice);
+
+ // Also listen for port changes to catch reconnections
+ function onPortChange(port) {
+ console.log(`${self.logHead} Port changed to:`, port);
+ // Auto-detect board when port changes and we're on firmware flasher tab
+ if (port && port !== "0" && !$("input.flash_on_connect").is(":checked") && !STM32.rebootMode) {
+ console.log(`${self.logHead} Auto-detecting board for port change`);
+ setTimeout(() => {
+ AutoDetect.verifyBoard();
+ }, 500); // Small delay to ensure port is ready
+ } else if (!port || port === "0") {
+ // Clear board selection when no port is selected
+ console.log(`${self.logHead} Clearing board selection - no port selected`);
+ $('select[name="board"]').val("0").trigger("change");
+ }
+ }
+
+ // Listen for device removal to clear board selection
+ function onDeviceRemoved(devicePath) {
+ console.log(`${self.logHead} Device removed:`, devicePath);
+ // Clear board selection when device is removed
+ $('select[name="board"]').val("0").trigger("change");
+ // Also clear any loaded firmware
+ clearBufferedFirmware();
+ }
+
+ EventBus.$on("ports-input:change", onPortChange);
+ EventBus.$on("port-handler:device-removed", onDeviceRemoved);
async function saveFirmware() {
const fileType = self.firmware_type;
@@ -1458,6 +1508,17 @@ firmware_flasher.initialize = async function (callback) {
$("a.exit_dfu").removeClass("disabled");
}
+ // Auto-detect board if a drone is already connected when tab becomes active
+ if (
+ (PortHandler.portAvailable && !$('select[name="board"]').val()) ||
+ $('select[name="board"]').val() === "0"
+ ) {
+ console.log(`${self.logHead} Auto-detecting board for already connected device`);
+ setTimeout(() => {
+ AutoDetect.verifyBoard();
+ }, 1000); // Small delay to ensure tab is fully loaded
+ }
+
GUI.content_ready(callback);
}
@@ -1476,6 +1537,12 @@ firmware_flasher.cleanup = function (callback) {
$(document).unbind("keypress");
$(document).off("click", "span.progressLabel a");
+ // Clean up EventBus listeners
+ EventBus.$off("port-handler:auto-select-usb-device");
+ EventBus.$off("port-handler:auto-select-serial-device");
+ EventBus.$off("ports-input:change");
+ EventBus.$off("port-handler:device-removed");
+
if (callback) callback();
};
diff --git a/src/js/tabs/norn_config.js b/src/js/tabs/norn_config.js
new file mode 100644
index 0000000000..90a070b4b5
--- /dev/null
+++ b/src/js/tabs/norn_config.js
@@ -0,0 +1,387 @@
+import Handlebars from "handlebars";
+import { i18n } from "../localization";
+import GUI, { TABS } from "../gui";
+import { mspHelper } from "../msp/MSPHelper";
+import MSP from "../msp";
+import MSPCodes from "../msp/MSPCodes";
+import $ from "jquery";
+
+// Register Handlebars helpers
+Handlebars.registerHelper("eq", function (a, b) {
+ return a === b;
+});
+
+Handlebars.registerHelper("ne", function (a, b) {
+ return a !== b;
+});
+
+Handlebars.registerHelper("and", function (a, b) {
+ return a && b;
+});
+
+Handlebars.registerHelper("or", function (a, b) {
+ return a || b;
+});
+
+Handlebars.registerHelper("not", function (a) {
+ return !a;
+});
+
+Handlebars.registerHelper("elseif", function (condition, options) {
+ if (condition) {
+ return options.fn(this);
+ }
+ return options.inverse(this);
+});
+
+// Discover config sources (text-based)
+const templateFiles = import.meta.glob("../../norn-configs/template.hbs", { eager: true, as: "raw" });
+
+const norn_config = {
+ analyticsChanges: {},
+};
+
+norn_config.initialize = function (callback) {
+ const self = this;
+
+ GUI.active_tab = "norn_config";
+
+ function load_configuration_from_fc() {
+ // Load any prerequisite data from FC, then load the HTML
+ mspHelper.readFullConfiguration?.(() => {
+ $("#content").load("./tabs/norn_config.html", on_tab_loaded_handler);
+ }) || $("#content").load("./tabs/norn_config.html", on_tab_loaded_handler);
+ }
+
+ function update_ui() {
+ i18n.localizePage();
+
+ // Populate Flight Controller list (explicit options)
+ const fcSelect = $("select[name='norn_fc']");
+ if (fcSelect.length) {
+ fcSelect.empty();
+ fcSelect.append(``);
+ fcSelect.append(``);
+ fcSelect.append(``);
+ fcSelect.append(``);
+ fcSelect.append(``);
+ fcSelect.on("change", function () {
+ self.analyticsChanges["NornFC"] = $(this).val() || null;
+ clearMandatoryFieldValidation();
+ });
+ }
+
+ // Populate Drone Size list (explicit options)
+ const droneSizeSelect = $("select[name='norn_drone_size']");
+ if (droneSizeSelect.length) {
+ droneSizeSelect.empty();
+ droneSizeSelect.append(``);
+ droneSizeSelect.append(``);
+ droneSizeSelect.append(``);
+ droneSizeSelect.append(``);
+ droneSizeSelect.append(``);
+ droneSizeSelect.append(``);
+ droneSizeSelect.on("change", function () {
+ self.analyticsChanges["NornDroneSize"] = $(this).val() || null;
+ clearMandatoryFieldValidation();
+ });
+ }
+
+ // Populate Manticore models (allow None) with explicit options
+ const manticoreSelect = $("select[name='norn_manticore']");
+ if (manticoreSelect.length) {
+ manticoreSelect.empty();
+ manticoreSelect.append(``);
+ manticoreSelect.append(``);
+ manticoreSelect.append(``);
+ manticoreSelect.on("change", function () {
+ self.analyticsChanges["NornManticore"] = $(this).val() || null;
+ clearMandatoryFieldValidation();
+ });
+ }
+
+ // Populate VTX profiles (allow None) with explicit options
+ const vtxSelect = $("select[name='norn_vtx']");
+ if (vtxSelect.length) {
+ vtxSelect.empty();
+ vtxSelect.append(``);
+ vtxSelect.append(``);
+ vtxSelect.append(``);
+ vtxSelect.append(``);
+ vtxSelect.on("change", function () {
+ self.analyticsChanges["NornVtx"] = $(this).val() || null;
+ clearMandatoryFieldValidation();
+ });
+ }
+
+ // Example dropdown wiring
+ // none for now
+
+ // Button wiring
+ $("a.generate").on("click", on_generate_handler);
+ $("a.copy").on("click", on_copy_handler);
+ $("a.save").on("click", on_save_handler);
+
+ // GPS toggle wiring
+ const gpsToggle = $("#norn_gps");
+ gpsToggle.on("change", function () {
+ self.analyticsChanges["NornGPS"] = $(this).is(":checked");
+ });
+
+ // Craft name input wiring
+ const craftNameInput = $("#norn_craft_name");
+ craftNameInput.on("input", function () {
+ self.analyticsChanges["NornCraftName"] = $(this).val() || null;
+ });
+
+ // MB ID input wiring
+ const mbIdInput = $("#norn_mb_id");
+ mbIdInput.on("input", function () {
+ self.analyticsChanges["NornMBId"] = $(this).val() || null;
+ });
+
+ // Populate Controller list (explicit options)
+ const controllerSelect = $("select[name='norn_controller']");
+ if (controllerSelect.length) {
+ controllerSelect.empty();
+ controllerSelect.append(``);
+ controllerSelect.append(``);
+ controllerSelect.append(``);
+ controllerSelect.on("change", function () {
+ self.analyticsChanges["NornController"] = $(this).val() || null;
+ clearMandatoryFieldValidation();
+ });
+ }
+
+ // Populate FailSafe list (explicit options)
+ const failSafeSelect = $("select[name='norn_failsafe']");
+ if (failSafeSelect.length) {
+ failSafeSelect.empty();
+ failSafeSelect.append(``);
+ failSafeSelect.append(``);
+ failSafeSelect.on("change", function () {
+ self.analyticsChanges["NornFailSafe"] = $(this).val() || null;
+ });
+ }
+
+ // Populate Video Format list (explicit options)
+ const videoFormatSelect = $("select[name='norn_video_format']");
+ if (videoFormatSelect.length) {
+ videoFormatSelect.empty();
+ videoFormatSelect.append(``);
+ videoFormatSelect.append(``);
+ videoFormatSelect.append(``);
+ videoFormatSelect.on("change", function () {
+ self.analyticsChanges["NornVideoFormat"] = $(this).val() || null;
+ clearMandatoryFieldValidation();
+ });
+ }
+ }
+
+ function on_tab_loaded_handler() {
+ update_ui();
+
+ GUI.interval_add(
+ "status_pull",
+ function status_pull() {
+ MSP.send_message(MSPCodes.MSP_STATUS);
+ },
+ 500,
+ true,
+ );
+
+ GUI.content_ready(callback);
+ }
+
+ // No save/copy handlers for now (kept minimal per request)
+
+ function readFileRaw(pathMap, path) {
+ if (!path) return "";
+ try {
+ return pathMap[path] || "";
+ } catch (e) {
+ console.error("Cannot read", path, e);
+ return "";
+ }
+ }
+
+ function getSelectedKeys() {
+ const fcKey = $("select[name='norn_fc']").val() || "";
+ const droneSize = $("select[name='norn_drone_size']").val() || "";
+ const manticoreKey = $("select[name='norn_manticore']").val() || "";
+ const vtxKey = $("select[name='norn_vtx']").val() || "";
+ const gpsEnabled = $("#norn_gps").is(":checked");
+ const craftName = $("#norn_craft_name").val() || "";
+ const mbId = $("#norn_mb_id").val() || "";
+ const controller = $("select[name='norn_controller']").val() || "";
+ const failSafe = $("select[name='norn_failsafe']").val() || "";
+ const videoFormat = $("select[name='norn_video_format']").val() || "";
+ return {
+ fcKey,
+ droneSize,
+ manticoreKey,
+ vtxKey,
+ gpsEnabled,
+ craftName,
+ mbId,
+ controller,
+ failSafe,
+ videoFormat,
+ };
+ }
+
+ function updateMandatoryFieldValidation() {
+ // Clear previous validation
+ clearMandatoryFieldValidation();
+
+ // Check mandatory fields and highlight missing ones
+ const mandatoryFields = [
+ { selector: "select[name='norn_fc']", label: "Flight Controller" },
+ { selector: "select[name='norn_drone_size']", label: "Drone Size" },
+ { selector: "select[name='norn_manticore']", label: "Manticore" },
+ { selector: "select[name='norn_vtx']", label: "VTX" },
+ { selector: "select[name='norn_video_format']", label: "Video Format" },
+ { selector: "select[name='norn_controller']", label: "Radio Controller" },
+ ];
+
+ mandatoryFields.forEach((field) => {
+ const element = $(field.selector);
+ if (!element.val()) {
+ // For Camera section dropdowns, highlight the entire Camera section
+ if (
+ field.selector === "select[name='norn_vtx']" ||
+ field.selector === "select[name='norn_video_format']"
+ ) {
+ element.closest(".gui_box").addClass("mandatory-missing");
+ } else {
+ element.closest(".gui_box").addClass("mandatory-missing");
+ }
+ element.addClass("mandatory-missing");
+ }
+ });
+ }
+
+ function clearMandatoryFieldValidation() {
+ // Remove validation highlighting
+ $(".gui_box").removeClass("mandatory-missing");
+ $(".select").removeClass("mandatory-missing");
+ $("select, input").removeClass("mandatory-missing");
+ }
+
+ function on_generate_handler(e) {
+ e?.preventDefault?.();
+
+ // Validate mandatory fields
+ const fcKey = $("select[name='norn_fc']").val();
+ const droneSize = $("select[name='norn_drone_size']").val();
+ const manticoreKey = $("select[name='norn_manticore']").val();
+ const vtxKey = $("select[name='norn_vtx']").val();
+ const videoFormat = $("select[name='norn_video_format']").val();
+ const controller = $("select[name='norn_controller']").val();
+
+ const missingFields = [];
+ if (!fcKey) missingFields.push("Flight Controller");
+ if (!droneSize) missingFields.push("Drone Size");
+ if (!manticoreKey) missingFields.push("Manticore");
+ if (!vtxKey) missingFields.push("VTX");
+ if (!videoFormat) missingFields.push("Video Format");
+ if (!controller) missingFields.push("Radio Controller");
+
+ if (missingFields.length > 0) {
+ // Highlight missing mandatory fields
+ updateMandatoryFieldValidation();
+ // alert(`Please select the following mandatory fields:\n${missingFields.join(", ")}`);
+ return;
+ }
+
+ // Clear any validation highlighting
+ clearMandatoryFieldValidation();
+
+ const templatePath = Object.keys(templateFiles)[0];
+ let result;
+ if (templatePath) {
+ const tpl = readFileRaw(templateFiles, templatePath);
+ const template = Handlebars.compile(tpl);
+ result = template(getSelectedKeys());
+ } else {
+ // Fallback to old behavior if template missing
+ const parts = [];
+ const genericPath = Object.keys(genericFiles)[0];
+ parts.push(readFileRaw(genericFiles, genericPath));
+ const manticorePath = $("select[name='norn_manticore']").val();
+ if (manticorePath) parts.push(readFileRaw(manticoreFiles, manticorePath));
+ const vtxPath = $("select[name='norn_vtx']").val();
+ if (vtxPath) parts.push(readFileRaw(vtxFiles, vtxPath));
+ result = parts.filter(Boolean).join("\n\n").trim();
+ }
+
+ $("#norn_config_output").val(result);
+ }
+
+ function on_copy_handler(e) {
+ e?.preventDefault?.();
+ const text = $("#norn_config_output").val();
+ if (text) {
+ navigator.clipboard
+ ?.writeText(text)
+ .then(() => {
+ console.log("Config copied to clipboard");
+ })
+ .catch((err) => {
+ console.error("Failed to copy to clipboard:", err);
+ });
+ }
+ }
+
+ function on_save_handler(e) {
+ e?.preventDefault?.();
+ const text = $("#norn_config_output").val();
+ if (!text) return;
+
+ // Generate filename based on selected options
+ const parts = [];
+ const fcKey = $("select[name='norn_fc']").val();
+ const droneSize = $("select[name='norn_drone_size']").val();
+ const manticoreKey = $("select[name='norn_manticore']").val();
+ const vtxKey = $("select[name='norn_vtx']").val();
+ const gpsEnabled = $("#norn_gps").is(":checked");
+ const craftName = $("#norn_craft_name").val();
+ const mbId = $("#norn_mb_id").val();
+ const controller = $("select[name='norn_controller']").val();
+ const failSafe = $("select[name='norn_failsafe']").val();
+ const videoFormat = $("select[name='norn_video_format']").val();
+
+ if (fcKey) parts.push(fcKey);
+ if (droneSize) parts.push(`${droneSize}inch`);
+ if (manticoreKey) parts.push(manticoreKey);
+ if (vtxKey) parts.push(vtxKey);
+ if (gpsEnabled) parts.push("GPS");
+ if (craftName) parts.push(craftName);
+ if (mbId) parts.push(`MB${mbId}`);
+ if (controller) parts.push(controller);
+ if (failSafe) parts.push(failSafe);
+ if (videoFormat) parts.push(videoFormat);
+
+ const filename = parts.length > 0 ? `norn_config_${parts.join("_")}.txt` : "norn_config.txt";
+
+ // Create and trigger download
+ const blob = new Blob([text], { type: "text/plain" });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement("a");
+ a.href = url;
+ a.download = filename;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ }
+
+ load_configuration_from_fc();
+};
+
+norn_config.cleanup = function (callback) {
+ if (callback) callback();
+};
+
+TABS.norn_config = norn_config;
+export { norn_config };
diff --git a/src/js/tabs/osd.js b/src/js/tabs/osd.js
index c9d49cfdaa..c012400e6f 100644
--- a/src/js/tabs/osd.js
+++ b/src/js/tabs/osd.js
@@ -1773,6 +1773,78 @@ OSD.loadDisplayFields = function () {
positionable: true,
preview: "RF:---",
},
+ MB_OSD_STATS: {
+ name: "MB_OSD_STATS",
+ text: "MB_OSD_STATS",
+ desc: "MB_OSD_STATS",
+ defaultPosition: -1,
+ draw_order: 610,
+ positionable: true,
+ preview: "MB_OSD_STATS",
+ },
+ MB_VRX_RSSI: {
+ name: "MB_VRX_RSSI",
+ text: "MB_VRX_RSSI",
+ desc: "MB_VRX_RSSI",
+ defaultPosition: -1,
+ draw_order: 610,
+ positionable: true,
+ preview: "MB_VRX_RSSI",
+ },
+ MB_INIK_INITIATOR: {
+ name: "MB_INIK_INITIATOR",
+ text: "MB_INIK_INITIATOR",
+ desc: "MB_INIK_INITIATOR",
+ defaultPosition: -1,
+ draw_order: 610,
+ positionable: true,
+ preview: "MB_INIK_INITIATOR",
+ },
+ MB_USE_KOLIBRI_INITIATOR_OSD: {
+ name: "MB_USE_KOLIBRI_INITIATOR_OSD",
+ text: "MB_USE_KOLIBRI_INITIATOR_OSD",
+ desc: "MB_USE_KOLIBRI_INITIATOR_OSD",
+ defaultPosition: -1,
+ draw_order: 610,
+ positionable: true,
+ preview: "MB_USE_KOLIBRI_INITIATOR_OSD",
+ },
+ MB_LASERDETON_INITIATOR_OSD: {
+ name: "MB_LASERDETON_INITIATOR_OSD",
+ text: "MB_LASERDETON_INITIATOR_OSD",
+ desc: "MB_LASERDETON_INITIATOR_OSD",
+ defaultPosition: -1,
+ draw_order: 610,
+ positionable: true,
+ preview: "MB_LASERDETON_INITIATOR_OSD",
+ },
+ MANTICORE_INITIATOR_V1: {
+ name: "MANTICORE_INITIATOR_V1",
+ text: "osdTextElementManticoreV1",
+ desc: "osdTextElementManticoreV1",
+ defaultPosition: -1,
+ draw_order: 1075,
+ positionable: true,
+ preview: "MANTICORE V1",
+ },
+ MANTICORE_INITIATOR_V2: {
+ name: "MANTICORE_INITIATOR_V2",
+ text: "osdTextElementManticoreV2",
+ desc: "osdTextElementManticoreV2",
+ defaultPosition: -1,
+ draw_order: 1075,
+ positionable: true,
+ preview: "MANTICORE V2",
+ },
+ MANTICORE_INITIATOR_LOGGER_V2: {
+ name: "MANTICORE_INITIATOR_LOGGER_V2",
+ text: "osdTextElementManticoreV2Logger",
+ desc: "osdTextElementManticoreV2Logger",
+ defaultPosition: -1,
+ draw_order: 1076,
+ positionable: true,
+ preview: "MANTICORE V2 LOGGER",
+ },
};
if (semver.gte(FC.CONFIG.apiVersion, API_VERSION_1_47)) {
@@ -2220,6 +2292,14 @@ OSD.chooseFields = function () {
F.CUSTOM_MSG2,
F.CUSTOM_MSG3,
F.OSD_LIDAR_DIST,
+ F.MB_OSD_STATS,
+ F.MB_VRX_RSSI,
+ F.MB_INIK_INITIATOR,
+ F.MB_USE_KOLIBRI_INITIATOR_OSD,
+ F.MB_LASERDETON_INITIATOR_OSD,
+ F.MANTICORE_INITIATOR_V1,
+ F.MANTICORE_INITIATOR_V2,
+ F.MANTICORE_INITIATOR_LOGGER_V2,
]);
}
// Choose statistic fields
@@ -3544,7 +3624,11 @@ osd.initialize = function (callback) {
OSD.msp.decode(info);
}
- if (OSD.data.state.haveMax7456FontDeviceConfigured && !OSD.data.state.isMax7456FontDeviceDetected && !OSD.data.state.haveAirbotTheiaOsdDevice) {
+ if (
+ OSD.data.state.haveMax7456FontDeviceConfigured &&
+ !OSD.data.state.isMax7456FontDeviceDetected &&
+ !OSD.data.state.haveAirbotTheiaOsdDevice
+ ) {
$(".noOsdChipDetect").show();
}
@@ -3820,7 +3904,10 @@ osd.initialize = function (callback) {
$(".requires-max7456").hide();
}
- if (!OSD.data.state.isMax7456FontDeviceDetected || (!OSD.data.state.haveMax7456FontDeviceConfigured && !OSD.data.state.haveAirbotTheiaOsdDevice)) {
+ if (
+ !OSD.data.state.isMax7456FontDeviceDetected ||
+ (!OSD.data.state.haveMax7456FontDeviceConfigured && !OSD.data.state.haveAirbotTheiaOsdDevice)
+ ) {
$(".requires-max7456-font-device-detected").addClass("disabled");
}
diff --git a/src/js/tabs/ports.js b/src/js/tabs/ports.js
index d50c623637..5fa27a0b6d 100644
--- a/src/js/tabs/ports.js
+++ b/src/js/tabs/ports.js
@@ -73,6 +73,12 @@ ports.initialize = function (callback) {
functionRules.push({ name: "VTX_MSP", groups: ["peripherals"], sharableWith: ["msp"], maxPorts: 1 });
}
+ functionRules.push({ name: "GIMBAL", groups: ["peripherals"], maxPorts: 1 });
+ functionRules.push({ name: "KOLIBRI_INITIATOR", groups: ["peripherals"], maxPorts: 1 });
+ functionRules.push({ name: "DETONATOR", groups: ["peripherals"], maxPorts: 1 });
+ functionRules.push({ name: "MANTICORE_INITIATOR_V1", groups: ["peripherals"], sharableWith: ["msp"], maxPorts: 1 });
+ functionRules.push({ name: "MANTICORE_INITIATOR_V2", groups: ["peripherals"], sharableWith: ["msp"], maxPorts: 1 });
+
for (const rule of functionRules) {
rule.displayName = i18n.getMessage(`portsFunction_${rule.name}`);
}
diff --git a/src/norn-configs/README.txt b/src/norn-configs/README.txt
new file mode 100644
index 0000000000..180addd128
--- /dev/null
+++ b/src/norn-configs/README.txt
@@ -0,0 +1,78 @@
+# NORN Configuration Template Documentation
+# ======================================
+#
+# This template generates Betaflight CLI configuration based on selected options.
+# Available template variables (from getSelectedKeys() in norn_config.js):
+#
+# fcKey: Flight Controller type (from select[name='norn_fc'])
+# - '': Empty/None selected
+# - 'FLASHHOBBYF405': FLASHHOBBYF405 (F4 chip)
+# - 'SPEEDYBEEF405V3': SPEEDYBEEF405V3 (F7 chip)
+# - 'TAKERF722SE': TAKERF722SE (H7 chip)
+# - 'GEPRCF722': GEPRCF722 (H7 chip)
+#
+# droneSize: Drone frame size in inches (from select[name='norn_drone_size'])
+# - '': Empty/None selected
+# - '7': 7-inch frame
+# - '8': 8-inch frame
+# - '9': 9-inch frame
+# - '10': 10-inch frame
+# - '13': 13-inch frame
+# - '15': 15-inch frame
+#
+# manticoreKey: Manticore system configuration (from select[name='norn_manticore'])
+# - '': Empty/None selected
+# - 'UART': UART-based Manticore setup
+# - 'GPIO': GPIO-based Manticore setup
+#
+# vtxKey: Video Transmitter configuration (from select[name='norn_vtx'])
+# - '': Empty/None selected
+# - '3.3VTX': 3.3GHz VTX setup
+# - '5.8VTX': 5.8GHz VTX setup
+# - 'OPTICA': Optica VTX setup
+#
+# gpsEnabled: Boolean flag for GPS functionality (from #norn_gps checkbox)
+# - true: Enable GPS features
+# - false: No GPS configuration
+#
+# craftName: Custom craft name (from #norn_craft_name input)
+# - '': Empty string if no name entered
+# - Any string value for craft identification
+#
+# mbId: MB ID identifier (from #norn_mb_id input)
+# - '': Empty string if no MB ID entered
+# - Any string value for MB identification
+#
+# controller: Controller type (from select[name='norn_controller'])
+# - '': Empty/None selected
+# - 'BOXER': BOXER controller
+# - 'TX12': TX12 controller
+#
+# failSafe: FailSafe configuration (from select[name='norn_failsafe'])
+# - '': Empty/None selected
+# - 'Default': Default failSafe configuration
+#
+# videoFormat: Video format (from select[name='norn_video_format'])
+# - '': Empty/None selected
+# - 'NTSC': NTSC video format
+# - 'PAL': PAL video format
+#
+# Template Syntax (Handlebars):
+# - {{#if condition}} ... {{/if}} : Conditional blocks
+# - {{variable}} : Output variable value
+# - {{!-- comment --}} : Template comments (not included in output)
+# - {{#if (eq variable 'value')}} : Equality comparison with helper
+#
+# Example usage in conditional blocks:
+# {{#if (eq fcKey 'FLASHHOBBYF405')}}
+# # This section applies only to FLASHHOBBYF405 flight controllers
+# {{/if}}
+#
+# {{#if droneSize}}
+# # This section applies only when drone size is selected
+# {{/if}}
+#
+# Note: All select elements have an empty option for "None" selection
+# Checkbox elements return boolean values (true/false)
+# Input elements return string values (may be empty string)
+# The 'eq' helper is registered for equality comparisons in Handlebars
\ No newline at end of file
diff --git a/src/norn-configs/template.hbs b/src/norn-configs/template.hbs
new file mode 100644
index 0000000000..6cdab0ed9e
--- /dev/null
+++ b/src/norn-configs/template.hbs
@@ -0,0 +1,315 @@
+# FC: {{fcKey}}
+# Drone Size: {{droneSize}}
+# Manticore: {{manticoreKey}}
+# VTX: {{vtxKey}}
+# Video Format: {{videoFormat}}
+# GPS: {{gpsEnabled}}
+# Craft Name: {{craftName}}
+# MB ID: {{mbId}}
+# Controller: {{controller}}
+# FailSafe: {{failSafe}}
+
+defaults nosave
+batch start
+defaults nosave
+
+{{!-- GPIO Configuration --}}
+{{#if (eq manticoreKey 'GPIO')}}
+# RESOURCES CONFIGURATION
+ {{!-- FLASHHOBBYF405 specific GPIO resources --}}
+ {{#if (eq fcKey 'FLASHHOBBYF405')}}
+resource PINIO 1 A01
+resource PINIO 2 A00
+ {{/if}}
+ {{!-- TAKERF722SE specific GPIO resources --}}
+ {{#if (eq fcKey 'TAKERF722SE')}}
+resource PINIO 1 A03
+resource PINIO 3 A02
+ {{/if}}
+ {{!-- Common GPIO configuration for ALL GPIO setups --}}
+set pinio_config = 1,1,1,1
+set pinio_box = 58,255,59,255
+{{/if}}
+
+# FEATURES CONFIGURATION
+{{!-- Common features for all configurations --}}
+feature TELEMETRY
+feature OSD
+{{!-- GPS feature --}}
+{{#if gpsEnabled}}
+feature GPS
+{{/if}}
+
+# PORTS CONFIGURATION
+{{#if (ne vtxKey 'OPTICA')}}
+serial UART1 8192 115200 57600 0 115200
+{{/if}}
+{{!-- Manticore UART Configuration --}}
+{{#if (eq manticoreKey 'UART')}}
+ {{#if (eq fcKey 'FLASHHOBBYF405')}}
+serial UART4 4194304 115200 57600 0 115200
+ {{else}}
+serial UART2 4194304 115200 57600 0 115200
+ {{/if}}
+{{/if}}
+{{!-- Manticore GPIO Configuration --}}
+{{#if (eq manticoreKey 'GPIO')}}
+ {{#if (eq fcKey 'FLASHHOBBYF405')}}
+serial UART4 2097152 115200 57600 0 115200
+ {{else}}
+serial UART2 2097152 115200 57600 0 115200
+ {{/if}}
+{{/if}}
+serial UART5 64 115200 57600 0 115200
+{{!-- GPS Configuration --}}
+{{#if gpsEnabled}}
+serial UART6 2 115200 57600 0 115200
+{{/if}}
+
+# AUX CONFIGURATION
+{{!-- TX12 Controller AUX Configuration --}}
+{{#if (eq controller 'TX12')}}
+aux 0 0 0 1800 2100 0 0
+aux 1 1 1 1400 2100 0 0
+aux 2 58 2 1400 2100 0 0
+aux 3 59 2 1800 2100 0 0
+{{/if}}
+{{!-- Default AUX Configuration (for BOXER or no controller selected) --}}
+{{#if (ne controller 'TX12')}}
+aux 0 0 0 1800 2100 0 0
+aux 1 1 1 1700 2100 0 0
+aux 2 2 1 1300 1700 0 0
+aux 4 58 4 1800 2100 0 0
+aux 5 59 5 1800 2100 0 0
+{{/if}}
+
+# CAMERA/VTX CONFIGURATION
+{{!-- Camera Configuration for all VTX types and video formats --}}
+{{#if vtxKey}}
+ {{!-- 5.8GHz VTX Configuration --}}
+ {{#if (eq vtxKey '5.8VTX')}}
+vtxtable bands 8
+vtxtable channels 8
+vtxtable band 1 BAND_A A CUSTOM 5865 5845 5825 5805 5785 5765 5745 5725
+vtxtable band 2 BAND_B B CUSTOM 5733 5752 5771 5790 5809 5828 5847 5866
+vtxtable band 3 BAND_E E CUSTOM 5705 5685 5665 5645 5885 5905 5925 5945
+vtxtable band 4 BAND_F F CUSTOM 5740 5760 5780 5800 5820 5840 5860 5880
+vtxtable band 5 BAND_R R CUSTOM 5658 5695 5732 5769 5806 5843 5880 5917
+vtxtable band 6 BAND_L L CUSTOM 5917 5800 5645 5493 5333 5200 5050 4990
+vtxtable band 7 BAND_X X CUSTOM 4990 5020 5050 5080 5110 5140 5170 5200
+vtxtable band 8 BAND_C C CUSTOM 4990 5020 5050 5080 5110 5140 5170 5200
+vtxtable powerlevels 5
+vtxtable powervalues 25 100 200 400 600
+vtxtable powerlabels 25 200 500 1.5 2.5
+set vtx_channel = 4
+set vtx_power = 4
+set vtx_low_power_disarm = ON
+set vtx_freq = 5800
+ {{/if}}
+ {{!-- 3.3GHz VTX Configuration --}}
+ {{#if (eq vtxKey '3.3VTX')}}
+vtxtable bands 5
+vtxtable channels 8
+vtxtable band 1 BAND_A A CUSTOM 3320 3345 3370 3395 3420 3445 3470 3495
+vtxtable band 2 BAND_B B CUSTOM 3310 3330 3355 3380 3405 3430 3455 3480
+vtxtable band 3 BAND_C C CUSTOM 3490 3510 3530 3550 3570 3590 3610 3630
+vtxtable band 4 BAND_D D CUSTOM 3330 3350 3370 3390 3410 3430 3450 3470
+vtxtable band 5 BAND_E E CUSTOM 3170 3190 3210 3230 3250 3270 3290 3310
+vtxtable powerlevels 5
+vtxtable powervalues 25 100 200 400 600
+vtxtable powerlabels 250 500 1W 2W 4W
+set vtx_channel = 1
+set vtx_power = 1
+set vtx_low_power_disarm = ON
+set vtx_freq = 3320
+ {{/if}}
+ {{!-- Video Format Configuration --}}
+ {{#if videoFormat}}
+set vcd_video_system = {{videoFormat}}
+ {{/if}}
+{{/if}}
+
+{{#if (eq failSafe 'Default')}}
+# FAILSAFE CONFIGURATION
+rxfail 0 s 1500
+rxfail 1 s 1500
+rxfail 2 s 1500
+rxfail 3 h
+set failsafe_delay = 10
+set failsafe_landing_time = 200
+set failsafe_throttle = 1800
+set failsafe_procedure = AUTO-LAND
+{{/if}}
+
+{{#if craftName}}
+# CRAFT NAME CONFIGURATION
+set craft_name = {{craftName}}
+{{/if}}
+
+{{#if mbId}}
+# MB CONFIGURATION
+set mb_id = {{mbId}}
+{{/if}}
+
+# COMMON CONFIGURATION
+set blackbox_disable_gps = ON
+set dshot_bidir = ON
+set motor_pwm_protocol = DSHOT300
+set vbat_min_cell_voltage = 280
+set vbat_warning_cell_voltage = 300
+set yaw_motors_reversed = ON
+
+# OSD CONFIGURATION
+{{!-- Common OSD settings for all video formats --}}
+set osd_cap_alarm = 12000
+set osd_alt_alarm = 300
+set osd_vbat_pos = 257
+set osd_rssi_pos = 34
+set osd_link_quality_pos = 2101
+set osd_link_tx_power_pos = 128
+set osd_rssi_dbm_pos = 2133
+set osd_rsnr_pos = 2165
+set osd_tim_1_pos = 363
+set osd_flymode_pos = 2082
+set osd_throttle_pos = 2273
+set osd_crosshairs_pos = 2286
+set osd_ah_sbar_pos = 2287
+set osd_ah_pos = 2159
+set osd_current_pos = 2144
+set osd_mah_drawn_pos = 352
+set osd_altitude_pos = 18681
+set osd_avg_cell_voltage_pos = 2113
+set osd_battery_usage_pos = 33122
+set osd_disarmed_pos = 298
+set osd_esc_rpm_pos = 56
+set osd_stick_overlay_left_pos = 321
+{{!-- Video Format specific OSD positions --}}
+{{#if (eq videoFormat 'PAL')}}
+set osd_tim_2_pos = 2519
+set osd_vtx_channel_pos = 18872
+set osd_craft_name_pos = 2497
+set osd_warnings_pos = 14730
+set osd_efficiency_pos = 22
+{{/if}}
+{{#if (eq videoFormat 'NTSC')}}
+set osd_tim_2_pos = 2455
+set osd_vtx_channel_pos = 18808
+set osd_craft_name_pos = 2433
+set osd_warnings_pos = 14666
+set osd_efficiency_pos = 228
+{{/if}}
+{{!-- Manticore OSD Configuration --}}
+{{#if (eq manticoreKey 'GPIO')}}
+set osd_manticore_initiator_v1_pos = 2090
+{{/if}}
+{{#if (eq manticoreKey 'UART')}}
+set osd_manticore_initiator_v2_pos = 2090
+{{/if}}
+set osd_mb_stats_pos = 0
+{{!-- GPS OSD Configuration --}}
+{{#if gpsEnabled}}
+set osd_gps_speed_pos = 2337
+set osd_gps_sats_pos = 2209
+set osd_home_dir_pos = 2305
+set osd_home_dist_pos = 2306
+set osd_flight_dist_pos = 2241
+{{#if (eq videoFormat 'PAL')}}
+set osd_gps_lon_pos = 18891
+set osd_gps_lat_pos = 18859
+{{/if}}
+{{#if (eq videoFormat 'NTSC')}}
+set osd_gps_lon_pos = 18827
+set osd_gps_lat_pos = 18795
+{{/if}}
+{{/if}}
+
+# PROFILE CONFIGURATION
+{{!-- Profile configuration for 7", 8", and 9" drones --}}
+{{#if (or (eq droneSize '7') (eq droneSize '8') (eq droneSize '9'))}}
+profile 0
+
+set dterm_lpf1_dyn_min_hz = 60
+set dterm_lpf1_dyn_max_hz = 120
+set dterm_lpf1_static_hz = 60
+set dterm_lpf2_static_hz = 120
+set p_pitch = 38
+set i_pitch = 43
+set d_pitch = 29
+set f_pitch = 106
+set p_roll = 37
+set i_roll = 38
+set d_roll = 27
+set f_roll = 101
+set i_yaw = 44
+set f_yaw = 101
+set d_min_roll = 21
+set d_min_pitch = 23
+set auto_profile_cell_count = 6
+set thrust_linear = 20
+set simplified_pids_mode = OFF
+set simplified_i_gain = 90
+set simplified_d_gain = 95
+set simplified_pi_gain = 55
+set simplified_dmax_gain = 10
+set simplified_feedforward_gain = 85
+set simplified_dterm_filter_multiplier = 80
+{{/if}}
+{{!-- Profile configuration for 10" drones --}}
+{{#if (eq droneSize '10')}}
+profile 0
+
+set dterm_lpf1_dyn_min_hz = 60
+set dterm_lpf1_dyn_max_hz = 120
+set dterm_lpf1_static_hz = 60
+set dterm_lpf2_static_hz = 120
+set simplified_dterm_filter_multiplier = 80
+{{/if}}
+{{!-- Profile configuration for 13" drones --}}
+{{#if (eq droneSize '13')}}
+profile 0
+
+set dterm_lpf1_dyn_min_hz = 60
+set dterm_lpf1_dyn_max_hz = 120
+set dterm_lpf1_static_hz = 60
+set dterm_lpf2_static_hz = 105
+set iterm_rotation = ON
+set iterm_windup = 90
+set p_pitch = 56
+set i_pitch = 80
+set d_pitch = 47
+set f_pitch = 74
+set p_roll = 53
+set i_roll = 76
+set d_roll = 42
+set f_roll = 71
+set p_yaw = 50
+set i_yaw = 120
+set f_yaw = 70
+set d_max_roll = 44
+set d_max_pitch = 50
+set thrust_linear = 20
+set dyn_idle_min_rpm = 25
+set simplified_pids_mode = RP
+set simplified_i_gain = 80
+set simplified_d_gain = 140
+set simplified_pi_gain = 120
+set simplified_d_max_gain = 20
+set simplified_feedforward_gain = 60
+set simplified_dterm_filter = OFF
+set simplified_dterm_filter_multiplier = 80
+{{/if}}
+
+profile 1
+profile 2
+profile 3
+
+# restore original profile selection
+profile 0
+
+rateprofile 0
+rateprofile 1
+rateprofile 2
+rateprofile 3
+
+# restore original rateprofile selection
+rateprofile 0
\ No newline at end of file
diff --git a/src/tabs/norn_config.html b/src/tabs/norn_config.html
new file mode 100644
index 0000000000..662dad48fc
--- /dev/null
+++ b/src/tabs/norn_config.html
@@ -0,0 +1,193 @@
+
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index ead226865c..27d1f51072 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4406,7 +4406,7 @@ editorconfig@^1.0.4:
minimatch "9.0.1"
semver "^7.5.3"
-ejs@^3.1.6:
+ejs@^3.1.6, ejs@^3.1.9:
version "3.1.10"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b"
integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==
@@ -5941,6 +5941,18 @@ gulplog@^1.0.0:
dependencies:
glogg "^1.0.0"
+handlebars@^4.7.8:
+ version "4.7.8"
+ resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9"
+ integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==
+ dependencies:
+ minimist "^1.2.5"
+ neo-async "^2.6.2"
+ source-map "^0.6.1"
+ wordwrap "^1.0.0"
+ optionalDependencies:
+ uglify-js "^3.1.4"
+
har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
@@ -7588,7 +7600,7 @@ minimatch@^9.0.4:
dependencies:
brace-expansion "^2.0.1"
-minimist@^1.1.0, minimist@^1.2.6:
+minimist@^1.1.0, minimist@^1.2.5, minimist@^1.2.6:
version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
@@ -7760,6 +7772,11 @@ needle@^3.1.0:
iconv-lite "^0.6.3"
sax "^1.2.4"
+neo-async@^2.6.2:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
+ integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
+
next-tick@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb"
@@ -9630,7 +9647,7 @@ string-argv@^0.3.2:
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
-"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -9648,6 +9665,15 @@ string-width@^1.0.1, string-width@^1.0.2:
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"
+"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+ version "4.2.3"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
string-width@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
@@ -9753,7 +9779,7 @@ stringify-object@^3.3.0:
is-obj "^1.0.1"
is-regexp "^1.0.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -9781,6 +9807,13 @@ strip-ansi@^4.0.0:
dependencies:
ansi-regex "^3.0.0"
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -10308,6 +10341,11 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
+uglify-js@^3.1.4:
+ version "3.19.3"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.19.3.tgz#82315e9bbc6f2b25888858acd1fff8441035b77f"
+ integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==
+
unbox-primitive@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2"
@@ -10895,6 +10933,11 @@ word-wrap@^1.2.5:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
+wordwrap@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
+ integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
+
workbox-background-sync@7.3.0:
version "7.3.0"
resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-7.3.0.tgz#b6340731a8d5b42b9e75a8a87c8806928e6e6303"
@@ -11053,7 +11096,7 @@ workbox-window@7.3.0, workbox-window@^7.3.0:
"@types/trusted-types" "^2.0.2"
workbox-core "7.3.0"
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -11079,6 +11122,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"