Shell styling

This commit is contained in:
Daniel Pupius
2025-04-23 09:38:07 -07:00
parent 05a9f2e461
commit 7c46944fb6
10 changed files with 59 additions and 60 deletions

View File

@@ -48,7 +48,7 @@ export const ConnectionStatus: React.FC<ConnectionStatusProps> = ({
const { icon, text, colorClass } = getStatusInfo();
return (
<div className="flex items-center space-x-2 bg-neutral-800 p-4 border-inset">
<div className="flex items-center space-x-2 p-4 rounded-xl bg-zinc-800 border border-neutral-950/80 effect-inset">
{icon}
<span className={cn("text-sm tracking-wide font-thin", colorClass)}>
{text}

View File

@@ -75,8 +75,6 @@ export const Nav: React.FC<NavProps> = ({ connectionStatus }) => {
</ul>
</nav>
<Separator />
<div className="px-4 py-2 pb-6">
<ConnectionStatus status={connectionStatus} />
</div>

View File

@@ -2,45 +2,48 @@ import React, { useState, useEffect, useCallback } from "react";
import { useAppSelector, useAppDispatch } from "../hooks";
import { PacketRenderer } from "./packets/PacketRenderer";
import { StreamControl } from "./StreamControl";
import { Trash2, RefreshCw, Archive } from "lucide-react";
import { Trash2, RefreshCw, Archive, Radio } from "lucide-react";
import { clearPackets, toggleStreamPause } from "../store/slices/packetSlice";
import { Packet } from "../lib/types";
import { Separator } from "./Separator";
// Number of packets to show per page
const PACKETS_PER_PAGE = 10;
export const PacketList: React.FC = () => {
const { packets, bufferedPackets, loading, error, streamPaused } = useAppSelector(
(state) => state.packets
);
const { packets, bufferedPackets, loading, error, streamPaused } =
useAppSelector((state) => state.packets);
const dispatch = useAppDispatch();
const [currentPage, setCurrentPage] = useState(1);
// Generate a reproducible hash code for a string
const hashString = useCallback((str: string): string => {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = (hash << 5) - hash + char;
hash = hash & hash; // Convert to 32bit integer
}
// Make sure hash is always positive and convert to string
return Math.abs(hash).toString(36);
}, []);
// Create a packet key using data.id and from address
// This should match the key generation logic in the reducer
const createPacketKey = useCallback((packet: Packet): string => {
if (packet.data.id !== undefined && packet.data.from !== undefined) {
// Use Meshtastic node ID format (! followed by lowercase hex) and packet ID
const nodeId = `!${packet.data.from.toString(16).toLowerCase()}`;
return `${nodeId}_${packet.data.id}`;
} else {
// Fallback to hash-based key if no ID or from (should be rare)
return `hash_${hashString(JSON.stringify(packet))}`;
}
}, [hashString]);
const createPacketKey = useCallback(
(packet: Packet): string => {
if (packet.data.id !== undefined && packet.data.from !== undefined) {
// Use Meshtastic node ID format (! followed by lowercase hex) and packet ID
const nodeId = `!${packet.data.from.toString(16).toLowerCase()}`;
return `${nodeId}_${packet.data.id}`;
} else {
// Fallback to hash-based key if no ID or from (should be rare)
return `hash_${hashString(JSON.stringify(packet))}`;
}
},
[hashString]
);
// We don't need to track packet keys in state anymore since we use data.id
// and it's deterministic - removing this effect to prevent the infinite loop issue
@@ -97,14 +100,10 @@ export const PacketList: React.FC = () => {
return (
<div>
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-normal text-neutral-200">
Packets{" "}
<span className="text-sm text-neutral-400">
({packets.length} total)
</span>
</h2>
<div className="flex justify-between items-end mb-2">
<div className="text-sm text-neutral-400 px-2">
{packets.length} packets received, since 6:00am
</div>
<div className="flex items-center space-x-3">
{/* Show buffered count when paused */}
{streamPaused && bufferedPackets.length > 0 && (
@@ -123,13 +122,14 @@ export const PacketList: React.FC = () => {
{/* Clear button */}
<button
onClick={handleClearPackets}
className="flex items-center px-3 py-1.5 text-sm bg-neutral-700 hover:bg-neutral-600 rounded transition-colors text-neutral-200"
className="flex items-center space-x-2 px-3 py-1.5 effect-outset border border-neutral-950/90 rounded-md text-neutral-400 hover:bg-neutral-700/50"
>
<Trash2 className="h-4 w-4 mr-1.5" />
Clear
<span className="text-sm font-medium">Clear</span>
</button>
</div>
</div>
<Separator className="mx-0" />
<ul className="space-y-3">
{currentPackets.map((packet, index) => (
@@ -156,7 +156,9 @@ export const PacketList: React.FC = () => {
{totalPages > 1 && (
<div className="flex justify-between items-center mt-6 text-sm">
<button
onClick={() => setCurrentPage(currentPage > 1 ? currentPage - 1 : 1)}
onClick={() =>
setCurrentPage(currentPage > 1 ? currentPage - 1 : 1)
}
disabled={currentPage === 1}
className={`px-3 py-1.5 rounded ${
currentPage === 1
@@ -190,4 +192,4 @@ export const PacketList: React.FC = () => {
)}
</div>
);
};
};

View File

@@ -2,24 +2,15 @@ import React, { ReactNode } from "react";
interface PageWrapperProps {
children: ReactNode;
title?: string;
}
/**
* A consistent page wrapper component for all leaf routes
*/
export const PageWrapper: React.FC<PageWrapperProps> = ({
children,
title,
}) => {
export const PageWrapper: React.FC<PageWrapperProps> = ({ children }) => {
return (
<div className="p-4 md:p-6">
{title && (
<h1 className="text-2xl font-medium text-neutral-100 mb-6">{title}</h1>
)}
<div className="bg-neutral-700 border-outset rounded-lg shadow-inner p-6">
{children}
</div>
<div className="bg-neutral-50/5 rounded-tl-3xl rounded-bl-3xl p-4 shadow-md">
{children}
</div>
);
};

View File

@@ -1,3 +1,4 @@
import { cn } from "@/lib/cn";
import React from "react";
interface SeparatorProps {
@@ -7,7 +8,10 @@ interface SeparatorProps {
export const Separator: React.FC<SeparatorProps> = ({ className = "" }) => {
return (
<div
className={`mx-4 h-px border-t-1 border-t-neutral-900 border-b-1 border-b-neutral-700/80 my-3 ${className}`}
className={cn(
"mx-4 h-px border-t-1 border-t-neutral-950/80 border-b-1 border-b-[rgba(255,255,255,0.15)] my-3",
className
)}
></div>
);
};

View File

@@ -13,7 +13,7 @@ export const StreamControl: React.FC<StreamControlProps> = ({
return (
<button
onClick={onToggle}
className={`flex items-center space-x-2 px-3 py-1.5 rounded-md transition-colors ${
className={`flex items-center space-x-2 px-3 py-1.5 effect-outset border border-neutral-950/90 rounded-md transition-colors ${
isPaused
? "bg-neutral-700 text-amber-400 hover:bg-neutral-600"
: "bg-neutral-700 text-green-400 hover:bg-neutral-600"
@@ -22,11 +22,7 @@ export const StreamControl: React.FC<StreamControlProps> = ({
<span className="text-sm font-medium">
{isPaused ? "Paused" : "Streaming"}
</span>
{isPaused ? (
<Play className="h-4 w-4" />
) : (
<Pause className="h-4 w-4" />
)}
{isPaused ? <Play className="h-4 w-4" /> : <Pause className="h-4 w-4" />}
</button>
);
};
};

View File

@@ -19,7 +19,7 @@ export const PacketCard: React.FC<PacketCardProps> = ({
const { data } = packet;
return (
<div className="p-4 border-inset">
<div className="p-4 effect-inset bg-neutral-500/5 rounded-lg border border-neutral-950/60">
<div className="flex justify-between items-start mb-3">
<div className="flex items-center">
<div className={`rounded-md ${iconBgColor} p-1.5 mr-3`}>{icon}</div>

View File

@@ -28,14 +28,14 @@ export default function Root() {
}, []);
return (
<div className="flex min-h-screen bg-neutral-800">
<div className="flex min-h-screen bg-neutral-900">
{/* Sidebar Navigation */}
<Nav connectionStatus={connectionStatus} />
{/* Main Content Area */}
<main className="ml-64 flex-1 p-6">
<main className="ml-64 flex-1 py-6">
<Outlet />
</main>
</div>
);
}
}

View File

@@ -3,7 +3,7 @@ import { SITE_TITLE } from "../lib/config";
export function IndexPage() {
return (
<PageWrapper title={`Welcome to ${SITE_TITLE}`}>
<PageWrapper>
<div>
<p className="mb-4 text-neutral-200">
This application provides a real-time view of Meshtastic network
@@ -42,4 +42,4 @@ export function IndexPage() {
</div>
</PageWrapper>
);
}
}

View File

@@ -38,4 +38,12 @@
@apply rounded-md;
@apply shadow-md;
}
.effect-outset {
@apply shadow-inner;
@apply [box-shadow:inset_0_1px_0_0_rgba(255,255,255,0.15),inset_0_-1px_0_0_rgba(0,0,0,0.3)];
}
.effect-inset {
@apply shadow-inner;
@apply [box-shadow:inset_0_1px_2px_0_rgba(0,0,0,0.3),inset_0_-1px_1px_0_rgba(255,255,255,0.15)];
}
}