Revert "fix(settings): fix unsaved change detection for non-admin-message con…" (#1100)

This reverts commit 87043f35cb.
This commit is contained in:
Dan Ditomaso
2026-06-14 20:03:50 -04:00
committed by GitHub
parent 87043f35cb
commit 7a8529b841
6 changed files with 13 additions and 102 deletions
@@ -9,7 +9,7 @@ import { PkiRegenerateDialog } from "@components/Dialog/PkiRegenerateDialog.tsx"
import { createZodResolver } from "@components/Form/createZodResolver.ts"; import { createZodResolver } from "@components/Form/createZodResolver.ts";
import { DynamicForm, type DynamicFormFormInit } from "@components/Form/DynamicForm.tsx"; import { DynamicForm, type DynamicFormFormInit } from "@components/Form/DynamicForm.tsx";
import { useDevice } from "@core/stores"; import { useDevice } from "@core/stores";
import { deepCompareConfig, normalizeBytes } from "@core/utils/deepCompareConfig.ts"; import { deepCompareConfig } from "@core/utils/deepCompareConfig.ts";
import { getX25519PrivateKey, getX25519PublicKey } from "@core/utils/x25519.ts"; import { getX25519PrivateKey, getX25519PublicKey } from "@core/utils/x25519.ts";
import { fromByteArray, toByteArray } from "base64-js"; import { fromByteArray, toByteArray } from "base64-js";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@@ -87,20 +87,7 @@ export const Security = ({ onFormInit }: SecurityConfigProps) => {
], ],
}; };
// Normalize empty byte arrays -> undefined for comparison so if (deepCompareConfig(config.security, payload, true)) {
// empty base64 strings from the form match undefined/empty fields
// in the existing config and allow removeChange to work.
const normalizeSecurity = (s: ParsedSecurity | undefined) => {
if (!s) return s;
return {
...s,
privateKey: normalizeBytes(s.privateKey),
publicKey: normalizeBytes(s.publicKey),
adminKey: s.adminKey?.map((b) => normalizeBytes(b)) as unknown,
} as ParsedSecurity;
};
if (deepCompareConfig(normalizeSecurity(config.security), normalizeSecurity(payload), true)) {
removeChange({ type: "config", variant: "security" }); removeChange({ type: "config", variant: "security" });
return; return;
} }
@@ -1,17 +1,15 @@
import type { Types } from "@meshtastic/core"; import type { Types } from "@meshtastic/core";
// Config type discriminators // Config type discriminators
export type ValidRadioConfigType = "lora" | "security"; export type ValidConfigType =
export type ValidDeviceConfigType =
| "device" | "device"
| "position" | "position"
| "power" | "power"
| "network" | "network"
| "display" | "display"
| "bluetooth"; | "lora"
| "bluetooth"
export type ValidConfigType = ValidRadioConfigType | ValidDeviceConfigType; | "security";
export type ValidModuleConfigType = export type ValidModuleConfigType =
| "mqtt" | "mqtt"
@@ -159,47 +157,6 @@ export function getConfigChangeCount(registry: ChangeRegistry): number {
return count; return count;
} }
/**
* Get count of radio config changes
*/
export function getRadioConfigChangeCount(registry: ChangeRegistry): number {
let count = 0;
for (const keyStr of registry.changes.keys()) {
const key = deserializeKey(keyStr);
if (key.type === "config" && (key.variant === "lora" || key.variant === "security")) {
count++;
}
}
// Channel is displayed under Radio section in UI, so include channel changes in the count
count += getChannelChangeCount(registry);
return count;
}
/**
* Get count of device config changes
*/
export function getDeviceConfigChangeCount(registry: ChangeRegistry): number {
let count = 0;
for (const keyStr of registry.changes.keys()) {
const key = deserializeKey(keyStr);
if (
key.type === "config" &&
(key.variant === "device" ||
key.variant === "position" ||
key.variant === "power" ||
key.variant === "network" ||
key.variant === "display" ||
key.variant === "bluetooth")
) {
count++;
}
}
if (hasUserChange(registry)) {
count++;
}
return count;
}
/** /**
* Get count of module config changes * Get count of module config changes
*/ */
@@ -91,8 +91,6 @@ export const mockDeviceStore: Device = {
hasChannelChange: vi.fn().mockReturnValue(false), hasChannelChange: vi.fn().mockReturnValue(false),
hasUserChange: vi.fn().mockReturnValue(false), hasUserChange: vi.fn().mockReturnValue(false),
getConfigChangeCount: vi.fn().mockReturnValue(0), getConfigChangeCount: vi.fn().mockReturnValue(0),
getRadioConfigChangeCount: vi.fn().mockReturnValue(0),
getDeviceConfigChangeCount: vi.fn().mockReturnValue(0),
getModuleConfigChangeCount: vi.fn().mockReturnValue(0), getModuleConfigChangeCount: vi.fn().mockReturnValue(0),
getChannelChangeCount: vi.fn().mockReturnValue(0), getChannelChangeCount: vi.fn().mockReturnValue(0),
getAllConfigChanges: vi.fn().mockReturnValue([]), getAllConfigChanges: vi.fn().mockReturnValue([]),
@@ -15,8 +15,6 @@ import {
getAllModuleConfigChanges, getAllModuleConfigChanges,
getChannelChangeCount, getChannelChangeCount,
getConfigChangeCount, getConfigChangeCount,
getRadioConfigChangeCount,
getDeviceConfigChangeCount,
getModuleConfigChangeCount, getModuleConfigChangeCount,
hasChannelChange, hasChannelChange,
hasConfigChange, hasConfigChange,
@@ -121,8 +119,6 @@ export interface Device extends DeviceData {
hasChannelChange: (index: Types.ChannelNumber) => boolean; hasChannelChange: (index: Types.ChannelNumber) => boolean;
hasUserChange: () => boolean; hasUserChange: () => boolean;
getConfigChangeCount: () => number; getConfigChangeCount: () => number;
getRadioConfigChangeCount: () => number;
getDeviceConfigChangeCount: () => number;
getModuleConfigChangeCount: () => number; getModuleConfigChangeCount: () => number;
getChannelChangeCount: () => number; getChannelChangeCount: () => number;
getAllConfigChanges: () => Protobuf.Config.Config[]; getAllConfigChanges: () => Protobuf.Config.Config[];
@@ -821,24 +817,6 @@ function deviceFactory(
return getConfigChangeCount(device.changeRegistry); return getConfigChangeCount(device.changeRegistry);
}, },
getRadioConfigChangeCount: () => {
const device = get().devices.get(id);
if (!device) {
return 0;
}
return getRadioConfigChangeCount(device.changeRegistry);
},
getDeviceConfigChangeCount: () => {
const device = get().devices.get(id);
if (!device) {
return 0;
}
return getDeviceConfigChangeCount(device.changeRegistry);
},
getModuleConfigChangeCount: () => { getModuleConfigChangeCount: () => {
const device = get().devices.get(id); const device = get().devices.get(id);
if (!device) { if (!device) {
@@ -6,13 +6,6 @@ function isUint8Array(v: unknown): v is Uint8Array {
return v instanceof Uint8Array; return v instanceof Uint8Array;
} }
export function normalizeBytes(value: unknown): unknown {
if (value instanceof Uint8Array && value.byteLength === 0) {
return undefined;
}
return value;
}
function bytesEqual(a: Uint8Array, b: Uint8Array): boolean { function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
if (a.byteLength !== b.byteLength) { if (a.byteLength !== b.byteLength) {
return false; return false;
+7 -9
View File
@@ -36,8 +36,6 @@ const ConfigPage = () => {
setModuleConfig, setModuleConfig,
addChannel, addChannel,
getConfigChangeCount, getConfigChangeCount,
getRadioConfigChangeCount,
getDeviceConfigChangeCount,
getModuleConfigChangeCount, getModuleConfigChangeCount,
getChannelChangeCount, getChannelChangeCount,
getAdminMessageChangeCount, getAdminMessageChangeCount,
@@ -52,9 +50,9 @@ const ConfigPage = () => {
const routerState = useRouterState(); const routerState = useRouterState();
const { t } = useTranslation("config"); const { t } = useTranslation("config");
const radioConfigChangeCount = getRadioConfigChangeCount(); const configChangeCount = getConfigChangeCount();
const deviceConfigChangeCount = getDeviceConfigChangeCount();
const moduleConfigChangeCount = getModuleConfigChangeCount(); const moduleConfigChangeCount = getModuleConfigChangeCount();
const channelChangeCount = getChannelChangeCount();
const adminMessageChangeCount = getAdminMessageChangeCount(); const adminMessageChangeCount = getAdminMessageChangeCount();
const sections = useMemo( const sections = useMemo(
@@ -64,7 +62,7 @@ const ConfigPage = () => {
route: radioRoute, route: radioRoute,
label: t("navigation.radioConfig"), label: t("navigation.radioConfig"),
icon: RadioTowerIcon, icon: RadioTowerIcon,
changeCount: radioConfigChangeCount, changeCount: configChangeCount,
component: RadioConfig, component: RadioConfig,
}, },
{ {
@@ -72,7 +70,7 @@ const ConfigPage = () => {
route: deviceRoute, route: deviceRoute,
label: t("navigation.deviceConfig"), label: t("navigation.deviceConfig"),
icon: RouterIcon, icon: RouterIcon,
changeCount: deviceConfigChangeCount, changeCount: moduleConfigChangeCount,
component: DeviceConfig, component: DeviceConfig,
}, },
{ {
@@ -80,11 +78,11 @@ const ConfigPage = () => {
route: moduleRoute, route: moduleRoute,
label: t("navigation.moduleConfig"), label: t("navigation.moduleConfig"),
icon: LayersIcon, icon: LayersIcon,
changeCount: moduleConfigChangeCount, changeCount: channelChangeCount,
component: ModuleConfig, component: ModuleConfig,
}, },
], ],
[t, radioConfigChangeCount, deviceConfigChangeCount, moduleConfigChangeCount], [t, configChangeCount, moduleConfigChangeCount, channelChangeCount],
); );
const activeSection = const activeSection =
@@ -265,7 +263,7 @@ const ConfigPage = () => {
getModuleConfigChangeCount() > 0 || getModuleConfigChangeCount() > 0 ||
getChannelChangeCount() > 0 || getChannelChangeCount() > 0 ||
adminMessageChangeCount > 0; adminMessageChangeCount > 0;
const hasPending = hasDrafts; const hasPending = hasDrafts || rhfState.isDirty;
const buttonOpacity = hasPending ? "opacity-100" : "opacity-0"; const buttonOpacity = hasPending ? "opacity-100" : "opacity-0";
const saveDisabled = isSaving || !rhfState.isValid || !hasPending; const saveDisabled = isSaving || !rhfState.isValid || !hasPending;