mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-07-03 08:21:25 +02:00
Frontend color overhaul
This commit is contained in:
@@ -170,7 +170,10 @@ export const MessageInput = forwardRef<MessageInputHandle, MessageInputProps>(fu
|
||||
const showCharCounter = !isRepeaterMode && limits !== null;
|
||||
|
||||
return (
|
||||
<form className="px-4 py-3 border-t border-border flex flex-col gap-1" onSubmit={handleSubmit}>
|
||||
<form
|
||||
className="px-4 py-2.5 border-t border-border flex flex-col gap-1"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
|
||||
@@ -461,14 +461,14 @@ export function MessageList({
|
||||
<div
|
||||
className={cn(
|
||||
'py-1.5 px-3 rounded-lg min-w-0',
|
||||
msg.outgoing ? 'bg-[#1e3a29]' : 'bg-muted'
|
||||
msg.outgoing ? 'bg-msg-outgoing' : 'bg-msg-incoming'
|
||||
)}
|
||||
>
|
||||
{showAvatar && (
|
||||
<div className="text-[13px] font-bold text-foreground mb-0.5">
|
||||
<div className="text-[13px] font-semibold text-foreground mb-0.5">
|
||||
{canClickSender ? (
|
||||
<span
|
||||
className="cursor-pointer hover:text-primary hover:underline"
|
||||
className="cursor-pointer hover:text-primary transition-colors"
|
||||
onClick={() => onSenderClick(displaySender)}
|
||||
title={`Mention ${displaySender}`}
|
||||
>
|
||||
@@ -587,13 +587,13 @@ export function MessageList({
|
||||
{showScrollToBottom && (
|
||||
<button
|
||||
onClick={scrollToBottom}
|
||||
className="absolute bottom-4 right-4 w-10 h-10 rounded-full bg-muted hover:bg-accent border border-border flex items-center justify-center shadow-lg transition-opacity"
|
||||
className="absolute bottom-4 right-4 w-9 h-9 rounded-full bg-card hover:bg-accent border border-border flex items-center justify-center shadow-lg transition-all hover:scale-105"
|
||||
title="Scroll to bottom"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useMemo } from 'react';
|
||||
import { MeshCoreDecoder, PayloadType, Utils } from '@michaelhart/meshcore-decoder';
|
||||
import type { RawPacket } from '../types';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface RawPacketListProps {
|
||||
packets: RawPacket[];
|
||||
@@ -204,9 +205,9 @@ export function RawPacketList({ packets }: RawPacketListProps) {
|
||||
const sortedPackets = [...decodedPackets].sort((a, b) => a.packet.timestamp - b.packet.timestamp);
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-y-auto p-4 flex flex-col gap-3" ref={listRef}>
|
||||
<div className="h-full overflow-y-auto p-4 flex flex-col gap-2" ref={listRef}>
|
||||
{sortedPackets.map(({ packet, decoded }) => (
|
||||
<div key={packet.id} className="py-2 px-3 bg-muted rounded">
|
||||
<div key={packet.id} className="py-2 px-3 bg-card rounded-md border border-border/50">
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Route type badge */}
|
||||
<span
|
||||
@@ -220,25 +221,27 @@ export function RawPacketList({ packets }: RawPacketListProps) {
|
||||
{!packet.decrypted && <span title="Encrypted">🔒</span>}
|
||||
|
||||
{/* Summary */}
|
||||
<span className={packet.decrypted ? 'text-primary' : 'text-foreground'}>
|
||||
<span
|
||||
className={cn('text-[13px]', packet.decrypted ? 'text-primary' : 'text-foreground')}
|
||||
>
|
||||
{decoded.summary}
|
||||
</span>
|
||||
|
||||
{/* Time */}
|
||||
<span className="text-muted-foreground ml-auto text-sm">
|
||||
<span className="text-muted-foreground ml-auto text-[12px] tabular-nums">
|
||||
{formatTime(packet.timestamp)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Signal info */}
|
||||
{(packet.snr !== null || packet.rssi !== null) && (
|
||||
<div className="text-[11px] text-muted-foreground mt-0.5">
|
||||
<div className="text-[11px] text-muted-foreground mt-0.5 tabular-nums">
|
||||
{formatSignalInfo(packet)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Raw hex data (always visible) */}
|
||||
<div className="font-mono text-[10px] break-all text-muted-foreground mt-1 p-1 bg-background/50 rounded">
|
||||
<div className="font-mono text-[10px] break-all text-muted-foreground mt-1.5 p-1.5 bg-background/60 rounded">
|
||||
{packet.data.toUpperCase()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -380,9 +380,9 @@ export function Sidebar({
|
||||
<div
|
||||
key={row.key}
|
||||
className={cn(
|
||||
'px-3 py-2.5 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent',
|
||||
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors',
|
||||
isActive(row.type, row.id) && 'bg-accent border-l-primary',
|
||||
row.unreadCount > 0 && '[&_.name]:font-bold [&_.name]:text-foreground'
|
||||
row.unreadCount > 0 && '[&_.name]:font-semibold [&_.name]:text-foreground'
|
||||
)}
|
||||
onClick={() =>
|
||||
handleSelectConversation({
|
||||
@@ -400,14 +400,14 @@ export function Sidebar({
|
||||
contactType={row.contact.type}
|
||||
/>
|
||||
)}
|
||||
<span className="name flex-1 truncate">{row.name}</span>
|
||||
<span className="name flex-1 truncate text-[13px]">{row.name}</span>
|
||||
{row.unreadCount > 0 && (
|
||||
<span
|
||||
className={cn(
|
||||
'text-[10px] font-semibold px-1.5 py-0.5 rounded-full min-w-[18px] text-center',
|
||||
row.isMention
|
||||
? 'bg-destructive text-destructive-foreground'
|
||||
: 'bg-primary text-primary-foreground'
|
||||
: 'bg-primary/90 text-primary-foreground'
|
||||
)}
|
||||
>
|
||||
{row.unreadCount}
|
||||
@@ -443,10 +443,10 @@ export function Sidebar({
|
||||
const effectiveCollapsed = isSearching ? false : collapsed;
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center px-3 py-2 pt-3">
|
||||
<div className="flex justify-between items-center px-3 py-2 pt-3.5">
|
||||
<button
|
||||
className={cn(
|
||||
'flex items-center gap-1 text-[11px] uppercase text-muted-foreground hover:text-foreground',
|
||||
'flex items-center gap-1.5 text-[10px] uppercase tracking-wider text-muted-foreground hover:text-foreground transition-colors',
|
||||
isSearching && 'cursor-default'
|
||||
)}
|
||||
onClick={() => {
|
||||
@@ -454,14 +454,14 @@ export function Sidebar({
|
||||
}}
|
||||
title={effectiveCollapsed ? `Expand ${title}` : `Collapse ${title}`}
|
||||
>
|
||||
<span className="text-[10px]">{effectiveCollapsed ? '▸' : '▾'}</span>
|
||||
<span className="text-[9px]">{effectiveCollapsed ? '▸' : '▾'}</span>
|
||||
<span>{title}</span>
|
||||
</button>
|
||||
{(showSortToggle || unreadCount > 0) && (
|
||||
<div className="ml-auto flex items-center gap-1.5">
|
||||
{showSortToggle && (
|
||||
<button
|
||||
className="bg-transparent border border-border text-muted-foreground px-1.5 py-0.5 text-[10px] rounded hover:bg-accent hover:text-foreground mr-0.5"
|
||||
className="bg-transparent text-muted-foreground/60 px-1 py-0.5 text-[10px] rounded hover:text-foreground transition-colors"
|
||||
onClick={handleSortToggle}
|
||||
title={sortOrder === 'alpha' ? 'Sort by recent' : 'Sort alphabetically'}
|
||||
>
|
||||
@@ -469,7 +469,7 @@ export function Sidebar({
|
||||
</button>
|
||||
)}
|
||||
{unreadCount > 0 && (
|
||||
<span className="text-[10px] font-medium px-1.5 py-0.5 rounded-full bg-muted text-muted-foreground">
|
||||
<span className="text-[10px] font-medium px-1.5 py-0.5 rounded-full bg-secondary text-muted-foreground">
|
||||
{unreadCount}
|
||||
</span>
|
||||
)}
|
||||
@@ -482,31 +482,33 @@ export function Sidebar({
|
||||
return (
|
||||
<div className="sidebar w-60 h-full min-h-0 bg-card border-r border-border flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center px-3 py-3 border-b border-border">
|
||||
<h2 className="text-xs uppercase text-muted-foreground font-medium">Conversations</h2>
|
||||
<div className="flex justify-between items-center px-3 py-2.5 border-b border-border">
|
||||
<h2 className="text-[10px] uppercase tracking-wider text-muted-foreground font-medium">
|
||||
Conversations
|
||||
</h2>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onNewMessage}
|
||||
title="New Message"
|
||||
className="h-6 w-6 p-0 text-muted-foreground hover:text-foreground"
|
||||
className="h-6 w-6 p-0 text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className="relative px-3 py-2 border-b border-border">
|
||||
<div className="relative px-3 py-2">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="h-8 text-sm pr-8"
|
||||
className="h-7 text-[13px] pr-8 bg-background/50"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground text-lg leading-none"
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground text-lg leading-none transition-colors"
|
||||
onClick={() => setSearchQuery('')}
|
||||
title="Clear search"
|
||||
>
|
||||
@@ -521,7 +523,7 @@ export function Sidebar({
|
||||
{!query && (
|
||||
<div
|
||||
className={cn(
|
||||
'px-3 py-2.5 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent',
|
||||
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px]',
|
||||
isActive('raw', 'raw') && 'bg-accent border-l-primary'
|
||||
)}
|
||||
onClick={() =>
|
||||
@@ -533,7 +535,7 @@ export function Sidebar({
|
||||
}
|
||||
>
|
||||
<span className="text-muted-foreground text-xs">📡</span>
|
||||
<span className="flex-1 truncate">Packet Feed</span>
|
||||
<span className="flex-1 truncate text-muted-foreground">Packet Feed</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -541,7 +543,7 @@ export function Sidebar({
|
||||
{!query && (
|
||||
<div
|
||||
className={cn(
|
||||
'px-3 py-2.5 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent',
|
||||
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px]',
|
||||
isActive('map', 'map') && 'bg-accent border-l-primary'
|
||||
)}
|
||||
onClick={() =>
|
||||
@@ -553,7 +555,7 @@ export function Sidebar({
|
||||
}
|
||||
>
|
||||
<span className="text-muted-foreground text-xs">🗺️</span>
|
||||
<span className="flex-1 truncate">Node Map</span>
|
||||
<span className="flex-1 truncate text-muted-foreground">Node Map</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -561,7 +563,7 @@ export function Sidebar({
|
||||
{!query && (
|
||||
<div
|
||||
className={cn(
|
||||
'px-3 py-2.5 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent',
|
||||
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px]',
|
||||
isActive('visualizer', 'visualizer') && 'bg-accent border-l-primary'
|
||||
)}
|
||||
onClick={() =>
|
||||
@@ -573,7 +575,7 @@ export function Sidebar({
|
||||
}
|
||||
>
|
||||
<span className="text-muted-foreground text-xs">✨</span>
|
||||
<span className="flex-1 truncate">Mesh Visualizer</span>
|
||||
<span className="flex-1 truncate text-muted-foreground">Mesh Visualizer</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -581,18 +583,18 @@ export function Sidebar({
|
||||
{!query && (
|
||||
<div
|
||||
className={cn(
|
||||
'px-3 py-2.5 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent',
|
||||
'px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px]',
|
||||
showCracker && 'bg-accent border-l-primary'
|
||||
)}
|
||||
onClick={onToggleCracker}
|
||||
>
|
||||
<span className="text-muted-foreground text-xs">🔓</span>
|
||||
<span className="flex-1 truncate">
|
||||
<span className="flex-1 truncate text-muted-foreground">
|
||||
{showCracker ? 'Hide' : 'Show'} Room Finder
|
||||
<span
|
||||
className={cn(
|
||||
'ml-1 text-xs',
|
||||
crackerRunning ? 'text-green-500' : 'text-muted-foreground'
|
||||
'ml-1 text-[11px]',
|
||||
crackerRunning ? 'text-primary' : 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
({crackerRunning ? 'running' : 'idle'})
|
||||
@@ -604,7 +606,7 @@ export function Sidebar({
|
||||
{/* Mark All Read */}
|
||||
{!query && Object.keys(unreadCounts).length > 0 && (
|
||||
<div
|
||||
className="px-3 py-2.5 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent"
|
||||
className="px-3 py-2 cursor-pointer flex items-center gap-2 border-l-2 border-transparent hover:bg-accent transition-colors text-[13px]"
|
||||
onClick={onMarkAllRead}
|
||||
>
|
||||
<span className="text-muted-foreground text-xs">✓</span>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Menu } from 'lucide-react';
|
||||
import type { HealthStatus, RadioConfig } from '../types';
|
||||
import { api } from '../api';
|
||||
import { toast } from './ui/sonner';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface StatusBarProps {
|
||||
health: HealthStatus | null;
|
||||
@@ -39,34 +40,39 @@ export function StatusBar({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-4 px-4 py-2 bg-card border-b border-border text-xs">
|
||||
<div className="flex items-center gap-3 px-4 py-2.5 bg-card border-b border-border text-xs">
|
||||
{/* Mobile menu button - only visible on small screens */}
|
||||
{onMenuClick && (
|
||||
<button
|
||||
onClick={onMenuClick}
|
||||
className="md:hidden p-1 bg-transparent border-none text-foreground cursor-pointer"
|
||||
className="md:hidden p-1 bg-transparent border-none text-muted-foreground hover:text-foreground cursor-pointer transition-colors"
|
||||
aria-label="Open menu"
|
||||
>
|
||||
<Menu className="h-5 w-5" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
<h1 className="text-base font-semibold mr-auto">RemoteTerm</h1>
|
||||
<h1 className="text-sm font-semibold tracking-tight mr-auto text-foreground">RemoteTerm</h1>
|
||||
|
||||
<div className="flex items-center gap-1 text-muted-foreground">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${connected ? 'bg-primary' : 'bg-muted-foreground'}`}
|
||||
className={cn(
|
||||
'w-2 h-2 rounded-full transition-colors',
|
||||
connected
|
||||
? 'bg-primary shadow-[0_0_6px_hsl(var(--primary)/0.5)]'
|
||||
: 'bg-muted-foreground'
|
||||
)}
|
||||
/>
|
||||
<span className="hidden lg:inline text-foreground">
|
||||
<span className="hidden lg:inline text-muted-foreground">
|
||||
{connected ? 'Connected' : 'Disconnected'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{config && (
|
||||
<div className="hidden lg:flex items-center gap-2 text-muted-foreground">
|
||||
<span className="text-foreground">{config.name || 'Unnamed'}</span>
|
||||
<span className="text-foreground font-medium">{config.name || 'Unnamed'}</span>
|
||||
<span
|
||||
className="font-mono text-muted-foreground cursor-pointer hover:text-primary"
|
||||
className="font-mono text-[11px] text-muted-foreground cursor-pointer hover:text-primary transition-colors"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(config.public_key);
|
||||
toast.success('Public key copied!');
|
||||
@@ -82,19 +88,16 @@ export function StatusBar({
|
||||
<button
|
||||
onClick={handleReconnect}
|
||||
disabled={reconnecting}
|
||||
className="px-3 py-1 bg-amber-950 border border-amber-800 text-amber-400 rounded text-xs cursor-pointer hover:bg-amber-900 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="px-3 py-1 bg-amber-500/10 border border-amber-500/20 text-amber-400 rounded-md text-xs cursor-pointer hover:bg-amber-500/15 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{reconnecting ? 'Reconnecting...' : 'Reconnect'}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={onSettingsClick}
|
||||
className="px-3 py-1 bg-secondary border border-border text-foreground rounded text-xs cursor-pointer hover:bg-accent"
|
||||
className="px-3 py-1.5 bg-secondary border border-border text-muted-foreground rounded-md text-xs cursor-pointer hover:bg-accent hover:text-foreground transition-colors"
|
||||
>
|
||||
<span role="img" aria-label="Settings">
|
||||
🔧
|
||||
</span>{' '}
|
||||
{settingsMode ? 'Back to Chat' : 'Radio & Config'}
|
||||
{settingsMode ? 'Back to Chat' : 'Settings'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user