mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-03-28 17:43:05 +01:00
Modal-ify the room region override
This commit is contained in:
105
frontend/src/components/ChannelFloodScopeOverrideModal.tsx
Normal file
105
frontend/src/components/ChannelFloodScopeOverrideModal.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { stripRegionScopePrefix } from '../utils/regionScope';
|
||||
import { Button } from './ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from './ui/dialog';
|
||||
import { Input } from './ui/input';
|
||||
import { Label } from './ui/label';
|
||||
|
||||
interface ChannelFloodScopeOverrideModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
roomName: string;
|
||||
currentOverride: string | null;
|
||||
onSetOverride: (value: string) => void;
|
||||
}
|
||||
|
||||
export function ChannelFloodScopeOverrideModal({
|
||||
open,
|
||||
onClose,
|
||||
roomName,
|
||||
currentOverride,
|
||||
onSetOverride,
|
||||
}: ChannelFloodScopeOverrideModalProps) {
|
||||
const [region, setRegion] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
setRegion(stripRegionScopePrefix(currentOverride));
|
||||
}, [currentOverride, open]);
|
||||
|
||||
const trimmedRegion = region.trim();
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}>
|
||||
<DialogContent className="sm:max-w-[520px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Regional Override</DialogTitle>
|
||||
<DialogDescription>
|
||||
Room-level regional routing temporarily changes the radio flood scope before send and
|
||||
restores it after. This can noticeably slow room sends.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-md border border-border bg-muted/20 p-3 text-sm">
|
||||
<div className="font-medium">{roomName}</div>
|
||||
<div className="mt-1 text-muted-foreground">
|
||||
Current regional override:{' '}
|
||||
{currentOverride ? stripRegionScopePrefix(currentOverride) : 'none'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="channel-region-input">Region</Label>
|
||||
<Input
|
||||
id="channel-region-input"
|
||||
value={region}
|
||||
onChange={(event) => setRegion(event.target.value)}
|
||||
placeholder="Esperance"
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter className="gap-2 sm:block sm:space-x-0">
|
||||
<div className="space-y-2">
|
||||
<Button
|
||||
type="button"
|
||||
className="w-full"
|
||||
disabled={trimmedRegion.length === 0}
|
||||
onClick={() => {
|
||||
onSetOverride(trimmedRegion);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{trimmedRegion.length > 0
|
||||
? `Use ${trimmedRegion} region for ${roomName}`
|
||||
: `Use region for ${roomName}`}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={() => {
|
||||
onSetOverride('');
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
Do not use region routing for {roomName}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { Bell, Globe2, Info, Route, Star, Trash2 } from 'lucide-react';
|
||||
import { toast } from './ui/sonner';
|
||||
import { DirectTraceIcon } from './DirectTraceIcon';
|
||||
import { ContactPathDiscoveryModal } from './ContactPathDiscoveryModal';
|
||||
import { ChannelFloodScopeOverrideModal } from './ChannelFloodScopeOverrideModal';
|
||||
import { isFavorite } from '../utils/favorites';
|
||||
import { handleKeyboardActivate } from '../utils/a11y';
|
||||
import { stripRegionScopePrefix } from '../utils/regionScope';
|
||||
@@ -60,11 +61,13 @@ export function ChatHeader({
|
||||
const [showKey, setShowKey] = useState(false);
|
||||
const [contactStatusInline, setContactStatusInline] = useState(true);
|
||||
const [pathDiscoveryOpen, setPathDiscoveryOpen] = useState(false);
|
||||
const [channelOverrideOpen, setChannelOverrideOpen] = useState(false);
|
||||
const keyTextRef = useRef<HTMLSpanElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setShowKey(false);
|
||||
setPathDiscoveryOpen(false);
|
||||
setChannelOverrideOpen(false);
|
||||
}, [conversation.id]);
|
||||
|
||||
const activeChannel =
|
||||
@@ -100,12 +103,7 @@ export function ChatHeader({
|
||||
|
||||
const handleEditFloodScopeOverride = () => {
|
||||
if (conversation.type !== 'channel' || !onSetChannelFloodScopeOverride) return;
|
||||
const nextValue = window.prompt(
|
||||
'Enter regional override flood scope for this room. This temporarily changes the radio flood scope before send and restores it after, which significantly slows room sends. Leave blank to clear.',
|
||||
activeFloodScopeLabel ?? ''
|
||||
);
|
||||
if (nextValue === null) return;
|
||||
onSetChannelFloodScopeOverride(conversation.id, nextValue);
|
||||
setChannelOverrideOpen(true);
|
||||
};
|
||||
|
||||
const handleOpenConversationInfo = () => {
|
||||
@@ -408,6 +406,15 @@ export function ChatHeader({
|
||||
onDiscover={onPathDiscovery}
|
||||
/>
|
||||
)}
|
||||
{conversation.type === 'channel' && onSetChannelFloodScopeOverride && (
|
||||
<ChannelFloodScopeOverrideModal
|
||||
open={channelOverrideOpen}
|
||||
onClose={() => setChannelOverrideOpen(false)}
|
||||
roomName={conversation.name}
|
||||
currentOverride={activeFloodScopeDisplay}
|
||||
onSetOverride={(value) => onSetChannelFloodScopeOverride(conversation.id, value)}
|
||||
/>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -252,12 +252,11 @@ describe('ChatHeader key visibility', () => {
|
||||
expect(screen.getByText(/clearing the forced route afterward is enough/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('prompts for regional override when globe button is clicked', () => {
|
||||
it('opens the regional override modal and applies the entered region', async () => {
|
||||
const key = 'CD'.repeat(16);
|
||||
const channel = makeChannel(key, '#flightless', true);
|
||||
const conversation: Conversation = { type: 'channel', id: key, name: '#flightless' };
|
||||
const onSetChannelFloodScopeOverride = vi.fn();
|
||||
const promptSpy = vi.spyOn(window, 'prompt').mockReturnValue('Esperance');
|
||||
|
||||
render(
|
||||
<ChatHeader
|
||||
@@ -270,8 +269,10 @@ describe('ChatHeader key visibility', () => {
|
||||
|
||||
fireEvent.click(screen.getByTitle('Set regional override'));
|
||||
|
||||
expect(promptSpy).toHaveBeenCalled();
|
||||
expect(await screen.findByRole('dialog')).toBeInTheDocument();
|
||||
fireEvent.change(screen.getByLabelText('Region'), { target: { value: 'Esperance' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Use Esperance region for #flightless' }));
|
||||
|
||||
expect(onSetChannelFloodScopeOverride).toHaveBeenCalledWith(key, 'Esperance');
|
||||
promptSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user