|
483 | 483 | variant="soft" |
484 | 484 | /> |
485 | 485 | <UButton :label="$t('receiverButtonBind')" @click="sendBind" v-if="showBindButton" variant="soft" /> |
486 | | - <UButton :label="$t('receiverButtonRefresh')" @click="refreshTab" variant="soft" /> |
487 | | - <UButton :label="$t('receiverButtonSave')" @click="saveConfig(false)" v-if="!needReboot" /> |
488 | | - <UButton :label="$t('receiverButtonSave')" @click="saveConfig(true)" v-else /> |
| 486 | + <UFieldGroup size="sm" orientation="horizontal" class="flex!"> |
| 487 | + <UButton @click="saveConfig(needReboot)" :disabled="!dirty || isSaving"> |
| 488 | + {{ $t("receiverButtonSave") }} |
| 489 | + </UButton> |
| 490 | + <UDropdownMenu v-slot="{ open }" :items="saveMenuItems" :content="{ align: 'end', side: 'top' }"> |
| 491 | + <UButton |
| 492 | + :icon="open ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'" |
| 493 | + :disabled="!dirty || isSaving" |
| 494 | + square |
| 495 | + /> |
| 496 | + </UDropdownMenu> |
| 497 | + </UFieldGroup> |
489 | 498 | </div> |
490 | 499 | </BaseTab> |
491 | 500 | </template> |
@@ -602,6 +611,58 @@ const features = computed(() => fcStore.features); |
602 | 611 |
|
603 | 612 | const rcDeadbandConfig = computed(() => fcStore.rcDeadbandConfig); |
604 | 613 |
|
| 614 | +// Dirty state tracking |
| 615 | +const savedSnapshot = ref(""); |
| 616 | +
|
| 617 | +function takeSnapshot() { |
| 618 | + return JSON.stringify({ |
| 619 | + channelMap: channelMapString.value, |
| 620 | + rxMode: selectedRxMode.value, |
| 621 | + serialrxProvider: rxConfig.value?.serialrx_provider, |
| 622 | + rxSpiProtocol: rxConfig.value?.rxSpiProtocol, |
| 623 | + elrsModelId: rxConfig.value?.elrsModelId, |
| 624 | + stickMin: rxConfig.value?.stick_min, |
| 625 | + stickCenter: rxConfig.value?.stick_center, |
| 626 | + stickMax: rxConfig.value?.stick_max, |
| 627 | + rcSmoothing: rxConfig.value?.rcSmoothing, |
| 628 | + rcSmoothingSetpointCutoff: rxConfig.value?.rcSmoothingSetpointCutoff, |
| 629 | + rcSmoothingAutoFactor: rxConfig.value?.rcSmoothingAutoFactor, |
| 630 | + rcSmoothingThrottleCutoff: rxConfig.value?.rcSmoothingThrottleCutoff, |
| 631 | + rcSmoothingAutoFactorThrottle: rxConfig.value?.rcSmoothingAutoFactorThrottle, |
| 632 | + rcSmoothingFeedforwardCutoff: rxConfig.value?.rcSmoothingFeedforwardCutoff, |
| 633 | + rssiChannel: rssiConfig.value?.channel, |
| 634 | + deadband: rcDeadbandConfig.value?.deadband, |
| 635 | + yawDeadband: rcDeadbandConfig.value?.yaw_deadband, |
| 636 | + deadband3dThrottle: rcDeadbandConfig.value?.deadband3d_throttle, |
| 637 | + featureMask: features.value?.features?._featureMask, |
| 638 | + elrsBindingPhrase: elrsBindingPhrase.value, |
| 639 | + setpointManualMode: setpointManualMode.value, |
| 640 | + throttleManualMode: throttleManualMode.value, |
| 641 | + feedforwardManualMode: feedforwardManualMode.value, |
| 642 | + }); |
| 643 | +} |
| 644 | +
|
| 645 | +const dirty = computed(() => { |
| 646 | + return savedSnapshot.value !== "" && takeSnapshot() !== savedSnapshot.value; |
| 647 | +}); |
| 648 | +
|
| 649 | +const saveMenuItems = computed(() => [ |
| 650 | + [ |
| 651 | + { |
| 652 | + label: t("receiverButtonSave"), |
| 653 | + icon: "i-lucide-save", |
| 654 | + disabled: !dirty.value || isSaving.value, |
| 655 | + onSelect: () => saveConfig(needReboot.value), |
| 656 | + }, |
| 657 | + { |
| 658 | + label: t("receiverButtonRefresh"), |
| 659 | + icon: "i-lucide-refresh-cw", |
| 660 | + disabled: !dirty.value || isSaving.value, |
| 661 | + onSelect: refreshTab, |
| 662 | + }, |
| 663 | + ], |
| 664 | +]); |
| 665 | +
|
605 | 666 | // Decode HTML entities in translations (some use < etc) |
606 | 667 | function decodeHtmlEntities(text) { |
607 | 668 | if (!text) return text; |
@@ -968,6 +1029,9 @@ async function loadConfig() { |
968 | 1029 | if (savedRate?.rx_refresh_rate) { |
969 | 1030 | refreshRate.value = savedRate.rx_refresh_rate; |
970 | 1031 | } |
| 1032 | +
|
| 1033 | + needReboot.value = false; |
| 1034 | + savedSnapshot.value = takeSnapshot(); |
971 | 1035 | } catch (e) { |
972 | 1036 | console.error("Failed to load Receiver configuration", e); |
973 | 1037 | } |
@@ -1025,6 +1089,7 @@ async function saveConfig(withReboot = false) { |
1025 | 1089 | mspHelper.writeConfiguration(false, resolve); |
1026 | 1090 | }); |
1027 | 1091 | gui_log(t("receiverConfigSaved") || "Configuration saved"); |
| 1092 | + savedSnapshot.value = takeSnapshot(); |
1028 | 1093 | } |
1029 | 1094 |
|
1030 | 1095 | needReboot.value = false; |
|
0 commit comments