Add scroll to repeater infobox on visualizer

This commit is contained in:
Jack Kingsman
2026-02-25 16:03:47 -08:00
parent 6ec2350b9a
commit 56f8b796e6
2 changed files with 53 additions and 0 deletions

View File

@@ -52,6 +52,7 @@ import { getStateKey } from './utils/conversationState';
import { appendRawPacketUnique } from './utils/rawPacketIdentity';
import { messageContainsMention } from './utils/messageParser';
import { mergeContactIntoList } from './utils/contactMerge';
import { getLocalLabel, getContrastTextColor } from './utils/localLabel';
import { cn } from '@/lib/utils';
import type { Contact, Conversation, HealthStatus, Message, MessagePath, RawPacket } from './types';
@@ -66,6 +67,7 @@ export function App() {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [showCracker, setShowCracker] = useState(false);
const [crackerRunning, setCrackerRunning] = useState(false);
const [localLabel, setLocalLabel] = useState(getLocalLabel);
// Defer CrackerPanel mount until first opened (lazy-loaded, but keep mounted after for state)
const crackerMounted = useRef(false);
@@ -467,6 +469,17 @@ export function App() {
return (
<div className="flex flex-col h-full">
{localLabel.text && (
<div
style={{
backgroundColor: localLabel.color,
color: getContrastTextColor(localLabel.color),
}}
className="px-4 py-1 text-center text-sm font-medium"
>
{localLabel.text}
</div>
)}
<StatusBar
health={health}
config={config}
@@ -621,6 +634,7 @@ export function App() {
onAdvertise={handleAdvertise}
onHealthRefresh={handleHealthRefresh}
onRefreshAppSettings={fetchAppSettings}
onLocalLabelChange={setLocalLabel}
/>
</Suspense>
</div>

View File

@@ -25,6 +25,7 @@ import {
setReopenLastConversationEnabled,
} from '../utils/lastViewedConversation';
import { RADIO_PRESETS } from '../utils/radioPresets';
import { getLocalLabel, setLocalLabel, type LocalLabel } from '../utils/localLabel';
import { SETTINGS_SECTION_LABELS, type SettingsSection } from './settingsConstants';
@@ -42,6 +43,7 @@ interface SettingsModalBaseProps {
onAdvertise: () => Promise<void>;
onHealthRefresh: () => Promise<void>;
onRefreshAppSettings: () => Promise<void>;
onLocalLabelChange?: (label: LocalLabel) => void;
}
type SettingsModalProps = SettingsModalBaseProps &
@@ -65,6 +67,7 @@ export function SettingsModal(props: SettingsModalProps) {
onAdvertise,
onHealthRefresh,
onRefreshAppSettings,
onLocalLabelChange,
} = props;
const externalSidebarNav = props.externalSidebarNav === true;
const desktopSection = props.externalSidebarNav ? props.desktopSection : undefined;
@@ -118,6 +121,8 @@ export function SettingsModal(props: SettingsModalProps) {
const [reopenLastConversation, setReopenLastConversation] = useState(
getReopenLastConversationEnabled
);
const [localLabelText, setLocalLabelText] = useState(() => getLocalLabel().text);
const [localLabelColor, setLocalLabelColor] = useState(() => getLocalLabel().color);
// Advertisement interval state (displayed in hours, stored as seconds in DB)
const [advertIntervalHours, setAdvertIntervalHours] = useState('0');
@@ -1104,6 +1109,40 @@ export function SettingsModal(props: SettingsModalProps) {
</p>
</div>
<Separator />
<div className="space-y-3">
<Label>Local Label</Label>
<div className="flex items-center gap-2">
<Input
value={localLabelText}
onChange={(e) => {
const text = e.target.value;
setLocalLabelText(text);
setLocalLabel(text, localLabelColor);
onLocalLabelChange?.({ text, color: localLabelColor });
}}
placeholder="e.g. Home Base, Field Radio 2"
className="flex-1"
/>
<input
type="color"
value={localLabelColor}
onChange={(e) => {
const color = e.target.value;
setLocalLabelColor(color);
setLocalLabel(localLabelText, color);
onLocalLabelChange?.({ text: localLabelText, color });
}}
className="w-10 h-9 rounded border border-input cursor-pointer bg-transparent p-0.5"
/>
</div>
<p className="text-xs text-muted-foreground">
Display a colored banner at the top of the page to identify this instance. This
applies only to this device/browser.
</p>
</div>
{getSectionError('database') && (
<div className="text-sm text-destructive">{getSectionError('database')}</div>
)}