diff --git a/index.html b/index.html
index bd4e4e001..b854a54f1 100644
--- a/index.html
+++ b/index.html
@@ -244,83 +244,174 @@
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
- >
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
+
+
-
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
- -
-
+
+
+
-
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
- -
-
+
+
+
-
+
+
- -
-
+
+
+
-
+
+
- -
-
+
+
+
-
+
+
- -
-
+
+
+
-
+
+
- -
-
+
+
+
-
+
+
+
+ -
+
+
+
-
+
+ -
+
+
+
+ Expand All
+
+
diff --git a/js/configurator_main.js b/js/configurator_main.js
index bf6164be5..e80214f4a 100644
--- a/js/configurator_main.js
+++ b/js/configurator_main.js
@@ -269,7 +269,87 @@ $(function() {
$('#tabs ul.mode-disconnected li a:first').trigger( "click" );
-
+ // Accordion Navigation Groups
+ $('.group-header').on('click', function(e) {
+ e.stopPropagation(); // Prevent triggering tab click
+ const header = $(this);
+ const items = header.next('.group-items');
+
+ // Toggle this group
+ header.toggleClass('active');
+ items.toggleClass('expanded');
+
+ // Update aria-expanded for accessibility
+ header.attr('aria-expanded', header.hasClass('active'));
+
+ // Update the expand/collapse all button state
+ updateToggleAllButton();
+ });
+
+ // Keyboard accessibility for accordion headers
+ $('.group-header').on('keydown', function(e) {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ $(this).trigger('click');
+ }
+ });
+
+ // Function to update toggle all button state
+ function updateToggleAllButton() {
+ const allExpanded = $('.nav-group .group-header.active').length === $('.nav-group .group-header').length;
+ const $expandIcon = $('#toggleAllGroups .expand-icon');
+ const $collapseIcon = $('#toggleAllGroups .collapse-icon');
+ const $toggleText = $('#toggleAllGroups .toggle-text');
+
+ if (allExpanded) {
+ $expandIcon.hide();
+ $collapseIcon.show();
+ $toggleText.attr('data-i18n', 'navCollapseAll');
+ $toggleText.text(i18n.getMessage('navCollapseAll'));
+ } else {
+ $expandIcon.show();
+ $collapseIcon.hide();
+ $toggleText.attr('data-i18n', 'navExpandAll');
+ $toggleText.text(i18n.getMessage('navExpandAll'));
+ }
+ }
+
+ // Expand/Collapse All Toggle
+ $('#toggleAllGroups').on('click', function(e) {
+ e.preventDefault();
+ const allExpanded = $('.nav-group .group-header.active').length === $('.nav-group .group-header').length;
+
+ if (allExpanded) {
+ // Collapse all except first
+ $('.nav-group .group-header').removeClass('active').attr('aria-expanded', 'false');
+ $('.nav-group .group-items').removeClass('expanded');
+ $('#tabs ul.mode-connected .nav-group:first-child .group-header').addClass('active').attr('aria-expanded', 'true');
+ $('#tabs ul.mode-connected .nav-group:first-child .group-items').addClass('expanded');
+ store.set('expand_all_groups', false);
+ } else {
+ // Expand all
+ $('.nav-group .group-header').addClass('active').attr('aria-expanded', 'true');
+ $('.nav-group .group-items').addClass('expanded');
+ store.set('expand_all_groups', true);
+ }
+
+ updateToggleAllButton();
+ });
+
+ // Initialize: apply saved expand all preference or expand first group by default
+ if (store.get('expand_all_groups', false)) {
+ // Expand all groups
+ $('.nav-group .group-header').addClass('active').attr('aria-expanded', 'true');
+ $('.nav-group .group-items').addClass('expanded');
+ } else {
+ // Expand first group only
+ $('#tabs ul.mode-connected .nav-group:first-child .group-header').addClass('active').attr('aria-expanded', 'true');
+ $('#tabs ul.mode-connected .nav-group:first-child .group-items').addClass('expanded');
+ }
+
+ // Update button state on initialization
+ updateToggleAllButton();
+
// options
$('#options').on('click', function() {
diff --git a/js/transpiler/transpiler/action_decompiler.js b/js/transpiler/transpiler/action_decompiler.js
index aa2ebc788..3a51ce3da 100644
--- a/js/transpiler/transpiler/action_decompiler.js
+++ b/js/transpiler/transpiler/action_decompiler.js
@@ -51,14 +51,60 @@ class ActionDecompiler {
return this.handleOverrideThrottle(lc, allConditions);
}
+ // Operations that only use operandA (operandB is unused/ignored)
+ // These should not decompile operandB to avoid incorrect validation warnings
+ const operandAOnlyOperations = [
+ OPERATION.SET_VTX_POWER_LEVEL,
+ OPERATION.SET_VTX_BAND,
+ OPERATION.SET_VTX_CHANNEL,
+ OPERATION.SET_OSD_LAYOUT,
+ OPERATION.LOITER_OVERRIDE,
+ OPERATION.OVERRIDE_MIN_GROUND_SPEED,
+ OPERATION.SET_HEADING_TARGET,
+ OPERATION.SET_PROFILE,
+ OPERATION.SET_GIMBAL_SENSITIVITY
+ ];
+
+ // Operations that use no operands (boolean flags only)
+ const noOperandOperations = [
+ OPERATION.OVERRIDE_ARMING_SAFETY,
+ OPERATION.SWAP_ROLL_YAW,
+ OPERATION.INVERT_ROLL,
+ OPERATION.INVERT_PITCH,
+ OPERATION.INVERT_YAW,
+ OPERATION.DISABLE_GPS_FIX,
+ OPERATION.RESET_MAG_CALIBRATION
+ ];
+
// INAV operand pattern (confirmed by logic_condition.c):
- // - Most overrides: operandA = value, operandB = 0
+ // - Most overrides: operandA = value, operandB = 0 (unused)
// - GVAR_INC/DEC: operandA = gvar index, operandB = increment/decrement
// - FLIGHT_AXIS: operandA = axis index, operandB = angle/rate
// - RC_CHANNEL: operandA = channel, operandB = value
// - PORT_SET: operandA = pin, operandB = value
- const valueA = this.decompileOperand(lc.operandAType, lc.operandAValue, allConditions);
- const valueB = this.decompileOperand(lc.operandBType, lc.operandBValue, allConditions);
+
+ // Warn about unexpected operands (version detection for new firmware features)
+ if (noOperandOperations.includes(lc.operation)) {
+ if (lc.operandAType !== 0 || lc.operandAValue !== 0) {
+ this.addWarning(`Unexpected operand A to ${getOperationName(lc.operation)} operation (type=${lc.operandAType}, value=${lc.operandAValue}). This may indicate a firmware version mismatch.`);
+ }
+ if (lc.operandBType !== 0 || lc.operandBValue !== 0) {
+ this.addWarning(`Unexpected operand B to ${getOperationName(lc.operation)} operation (type=${lc.operandBType}, value=${lc.operandBValue}). This may indicate a firmware version mismatch.`);
+ }
+ } else if (operandAOnlyOperations.includes(lc.operation)) {
+ if (lc.operandBType !== 0 || lc.operandBValue !== 0) {
+ this.addWarning(`Unexpected operand B to ${getOperationName(lc.operation)} operation (type=${lc.operandBType}, value=${lc.operandBValue}). This may indicate a firmware version mismatch.`);
+ }
+ }
+
+ // Only decompile operands that are actually used by the operation
+ // This prevents incorrect validation warnings (e.g., PID range check on unused operands)
+ const valueA = noOperandOperations.includes(lc.operation)
+ ? null
+ : this.decompileOperand(lc.operandAType, lc.operandAValue, allConditions);
+ const valueB = (operandAOnlyOperations.includes(lc.operation) || noOperandOperations.includes(lc.operation))
+ ? null
+ : this.decompileOperand(lc.operandBType, lc.operandBValue, allConditions);
switch (lc.operation) {
// GVAR operations: operandA = index, operandB = value
diff --git a/locale/en/messages.json b/locale/en/messages.json
index c16540af6..e9261dd26 100644
--- a/locale/en/messages.json
+++ b/locale/en/messages.json
@@ -47,6 +47,12 @@
"options_cliAutocomplete": {
"message": "Advanced CLI AutoComplete"
},
+ "navExpandAll": {
+ "message": "Expand All"
+ },
+ "navCollapseAll": {
+ "message": "Collapse All"
+ },
"options_unit_type": {
"message": "Set how the units render on the configurator only"
},
@@ -119,6 +125,30 @@
"tabHelp": {
"message": "Documentation & Support"
},
+ "navGroupSetup": {
+ "message": "Setup & Configuration"
+ },
+ "navGroupFlight": {
+ "message": "Flight Control"
+ },
+ "navGroupTuning": {
+ "message": "Tuning"
+ },
+ "navGroupNavigation": {
+ "message": "Navigation & Mission"
+ },
+ "navGroupSensors": {
+ "message": "Sensors & Peripherals"
+ },
+ "navGroupLogging": {
+ "message": "Data Logging"
+ },
+ "navGroupProgramming": {
+ "message": "Programming"
+ },
+ "navGroupTools": {
+ "message": "Tools"
+ },
"tabSetup": {
"message": "Status"
},
diff --git a/src/css/main.css b/src/css/main.css
index 43dab6ed9..430a25103 100644
--- a/src/css/main.css
+++ b/src/css/main.css
@@ -680,11 +680,129 @@ input[type="number"]::-webkit-inner-spin-button {
background-color: #37a8db;
}
+/* Accordion Navigation Groups */
+#tabs .nav-group {
+ border-bottom: 1px solid rgba(0, 0, 0, 0.30);
+}
+
+#tabs .group-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px 12px;
+ cursor: pointer;
+ background-color: #2d2d2d;
+ transition: background-color 0.2s;
+ user-select: none;
+ border-top: solid 1px rgba(255, 255, 255, 0.05);
+}
+
+#tabs .group-header:hover {
+ background-color: #353535;
+}
+
+#tabs .group-header.active {
+ background-color: transparent;
+ padding: 0;
+ min-height: 1px;
+ border-top: 1px solid rgba(55, 168, 219, 0.3);
+}
+
+#tabs .group-title {
+ font-family: 'open_sanssemibold', Arial, serif;
+ font-size: 12px;
+ font-weight: 600;
+ color: #b0b0b0;
+ transition: opacity 0.2s;
+}
+
+#tabs .group-header.active .group-title {
+ display: none;
+}
+
+#tabs .chevron {
+ font-size: 10px;
+ transition: transform 0.2s, opacity 0.2s;
+ color: #808080;
+}
+
+#tabs .group-header.active .chevron {
+ display: none;
+}
+
+#tabs .group-items {
+ max-height: 0;
+ overflow: hidden;
+ transition: max-height 0.3s ease;
+ background-color: #252525;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+#tabs .group-items.expanded {
+ max-height: 500px;
+}
+
+#tabs .group-items li {
+ border-bottom: 1px solid rgba(0, 0, 0, 0.20);
+}
+
+#tabs .group-items li:last-child {
+ border-bottom: 0;
+}
+
+#tabs .group-items li a {
+ padding-left: 40px;
+}
+
+/* Expand/Collapse All Toggle */
+#tabs .nav-toggle-all {
+ margin-top: 4px;
+ padding: 0;
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+#tabs .nav-toggle-all a {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ padding: 6px 12px;
+ color: #37a8db;
+ font-family: 'open_sanssemibold', Arial, serif;
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ background-color: rgba(55, 168, 219, 0.05);
+ transition: background-color 0.2s, color 0.2s;
+}
+
+#tabs .nav-toggle-all a:hover {
+ background-color: rgba(55, 168, 219, 0.15);
+ color: #4db8eb;
+}
+
+#tabs .nav-toggle-all svg {
+ width: 20px;
+ height: 20px;
+ flex-shrink: 0;
+}
+
+#tabs .nav-toggle-all .toggle-text {
+ display: none;
+}
+
.tabicon {
background: no-repeat 13px 7px;
background-size: 15px;
}
+/* Adjust icon position for accordion tabs */
+#tabs .group-items .tabicon {
+ background-position: 20px 7px;
+}
+
/* Tab-Icons */
.ic_setup {
background-image: url("./../../images/icons/cf_icon_setup_grey.svg");
diff --git a/tabs/options.html b/tabs/options.html
index 38aecc647..33f36d61a 100644
--- a/tabs/options.html
+++ b/tabs/options.html
@@ -29,7 +29,7 @@
-
+