mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-07-05 09:22:04 +02:00
Phase 3
This commit is contained in:
@@ -43,6 +43,7 @@ export function SettingsRadioSection({
|
||||
const [lat, setLat] = useState('');
|
||||
const [lon, setLon] = useState('');
|
||||
const [txPower, setTxPower] = useState('');
|
||||
const [pathHashMode, setPathHashMode] = useState('0');
|
||||
const [freq, setFreq] = useState('');
|
||||
const [bw, setBw] = useState('');
|
||||
const [sf, setSf] = useState('');
|
||||
@@ -73,6 +74,7 @@ export function SettingsRadioSection({
|
||||
setLat(String(config.lat));
|
||||
setLon(String(config.lon));
|
||||
setTxPower(String(config.tx_power));
|
||||
setPathHashMode(String(config.path_hash_mode));
|
||||
setFreq(String(config.radio.freq));
|
||||
setBw(String(config.radio.bw));
|
||||
setSf(String(config.radio.sf));
|
||||
@@ -145,6 +147,7 @@ export function SettingsRadioSection({
|
||||
const parsedLat = parseFloat(lat);
|
||||
const parsedLon = parseFloat(lon);
|
||||
const parsedTxPower = parseInt(txPower, 10);
|
||||
const parsedPathHashMode = parseInt(pathHashMode, 10);
|
||||
const parsedFreq = parseFloat(freq);
|
||||
const parsedBw = parseFloat(bw);
|
||||
const parsedSf = parseInt(sf, 10);
|
||||
@@ -159,11 +162,20 @@ export function SettingsRadioSection({
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
config.path_hash_mode_supported &&
|
||||
(isNaN(parsedPathHashMode) || parsedPathHashMode < 0 || parsedPathHashMode > 2)
|
||||
) {
|
||||
setError('Path hash mode must be between 0 and 2');
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
lat: parsedLat,
|
||||
lon: parsedLon,
|
||||
tx_power: parsedTxPower,
|
||||
...(config.path_hash_mode_supported && { path_hash_mode: parsedPathHashMode }),
|
||||
radio: {
|
||||
freq: parsedFreq,
|
||||
bw: parsedBw,
|
||||
@@ -384,6 +396,26 @@ export function SettingsRadioSection({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="path-hash-mode">Path Hash Mode</Label>
|
||||
<select
|
||||
id="path-hash-mode"
|
||||
value={pathHashMode}
|
||||
onChange={(e) => setPathHashMode(e.target.value)}
|
||||
disabled={!config.path_hash_mode_supported}
|
||||
className="w-full h-10 px-3 rounded-md border border-input bg-background text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
<option value="0">1 byte per hop</option>
|
||||
<option value="1">2 bytes per hop</option>
|
||||
<option value="2">3 bytes per hop</option>
|
||||
</select>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{config.path_hash_mode_supported
|
||||
? 'Controls the default hop hash width your radio uses for outbound routed paths.'
|
||||
: 'Connected radio or firmware does not expose this setting.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -173,6 +173,8 @@ const baseConfig = {
|
||||
lon: 0,
|
||||
tx_power: 17,
|
||||
max_tx_power: 22,
|
||||
path_hash_mode: 0,
|
||||
path_hash_mode_supported: true,
|
||||
radio: { freq: 910.525, bw: 62.5, sf: 7, cr: 5 },
|
||||
};
|
||||
|
||||
|
||||
@@ -201,6 +201,8 @@ describe('App search jump target handling', () => {
|
||||
lon: 0,
|
||||
tx_power: 17,
|
||||
max_tx_power: 22,
|
||||
path_hash_mode: 0,
|
||||
path_hash_mode_supported: true,
|
||||
radio: { freq: 910.525, bw: 62.5, sf: 7, cr: 5 },
|
||||
});
|
||||
mocks.api.getSettings.mockResolvedValue({
|
||||
|
||||
@@ -157,6 +157,8 @@ describe('App startup hash resolution', () => {
|
||||
lon: 0,
|
||||
tx_power: 17,
|
||||
max_tx_power: 22,
|
||||
path_hash_mode: 0,
|
||||
path_hash_mode_supported: true,
|
||||
radio: { freq: 910.525, bw: 62.5, sf: 7, cr: 5 },
|
||||
});
|
||||
mocks.api.getSettings.mockResolvedValue({
|
||||
|
||||
@@ -40,6 +40,8 @@ function createConfig(overrides: Partial<RadioConfig> = {}): RadioConfig {
|
||||
lon: -74.006,
|
||||
tx_power: 10,
|
||||
max_tx_power: 20,
|
||||
path_hash_mode: 0,
|
||||
path_hash_mode_supported: true,
|
||||
radio: { freq: 915, bw: 250, sf: 10, cr: 8 },
|
||||
...overrides,
|
||||
};
|
||||
|
||||
@@ -24,6 +24,8 @@ const baseConfig: RadioConfig = {
|
||||
lon: 2,
|
||||
tx_power: 17,
|
||||
max_tx_power: 22,
|
||||
path_hash_mode: 1,
|
||||
path_hash_mode_supported: true,
|
||||
radio: {
|
||||
freq: 910.525,
|
||||
bw: 62.5,
|
||||
@@ -57,6 +59,7 @@ const baseSettings: AppSettings = {
|
||||
};
|
||||
|
||||
function renderModal(overrides?: {
|
||||
config?: RadioConfig;
|
||||
appSettings?: AppSettings;
|
||||
health?: HealthStatus;
|
||||
onSaveAppSettings?: (update: AppSettingsUpdate) => Promise<void>;
|
||||
@@ -83,7 +86,7 @@ function renderModal(overrides?: {
|
||||
const commonProps = {
|
||||
open: overrides?.open ?? true,
|
||||
pageMode: overrides?.pageMode,
|
||||
config: baseConfig,
|
||||
config: overrides?.config ?? baseConfig,
|
||||
health: overrides?.health ?? baseHealth,
|
||||
appSettings: overrides?.appSettings ?? baseSettings,
|
||||
onClose,
|
||||
@@ -218,6 +221,36 @@ describe('SettingsModal', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('saves radio path hash mode through onSave', async () => {
|
||||
const { onSave } = renderModal();
|
||||
openRadioSection();
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Path Hash Mode'), {
|
||||
target: { value: '2' },
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Save' }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSave).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
path_hash_mode: 2,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('disables path hash mode when the connected radio does not expose it', () => {
|
||||
renderModal({
|
||||
config: { ...baseConfig, path_hash_mode_supported: false },
|
||||
});
|
||||
openRadioSection();
|
||||
|
||||
expect(screen.getByLabelText('Path Hash Mode')).toBeDisabled();
|
||||
expect(
|
||||
screen.getByText('Connected radio or firmware does not expose this setting.')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders selected section from external sidebar nav on desktop mode', async () => {
|
||||
renderModal({
|
||||
externalSidebarNav: true,
|
||||
|
||||
@@ -12,6 +12,8 @@ export interface RadioConfig {
|
||||
lon: number;
|
||||
tx_power: number;
|
||||
max_tx_power: number;
|
||||
path_hash_mode: number;
|
||||
path_hash_mode_supported: boolean;
|
||||
radio: RadioSettings;
|
||||
}
|
||||
|
||||
@@ -20,6 +22,7 @@ export interface RadioConfigUpdate {
|
||||
lat?: number;
|
||||
lon?: number;
|
||||
tx_power?: number;
|
||||
path_hash_mode?: number;
|
||||
radio?: RadioSettings;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user