19 changed files with 769 additions and 856 deletions
File diff suppressed because it is too large
@ -0,0 +1,21 @@ |
|||
import type React from "react"; |
|||
|
|||
import { GitBranchIcon } from "@primer/octicons-react"; |
|||
|
|||
export interface BottomNavProps { |
|||
children: React.ReactNode; |
|||
} |
|||
|
|||
export const BottomNav = ({ children }: BottomNavProps): JSX.Element => { |
|||
return ( |
|||
<div className="flex bg-backgroundPrimary"> |
|||
<div className="flex h-8 cursor-pointer select-none gap-1 bg-accent px-1 text-textPrimary hover:brightness-hover active:brightness-press"> |
|||
<GitBranchIcon className="my-auto w-4" /> |
|||
<span className="my-auto font-mono text-sm"> |
|||
{process.env.COMMIT_HASH} |
|||
</span> |
|||
</div> |
|||
{children} |
|||
</div> |
|||
); |
|||
}; |
|||
@ -0,0 +1,5 @@ |
|||
import type React from "react"; |
|||
|
|||
export const NavSpacer = (): JSX.Element => { |
|||
return <div className="h-1 w-10 rounded-full bg-accentMuted" />; |
|||
}; |
|||
@ -0,0 +1,76 @@ |
|||
import type React from "react"; |
|||
import type { SVGProps } from "react"; |
|||
|
|||
import { useDevice } from "@app/core/providers/useDevice.js"; |
|||
import type { Page } from "@app/core/stores/deviceStore.js"; |
|||
import { |
|||
BeakerIcon, |
|||
ChatBubbleBottomCenterTextIcon, |
|||
Cog8ToothIcon, |
|||
MapIcon, |
|||
Square3Stack3DIcon, |
|||
UsersIcon |
|||
} from "@heroicons/react/24/outline"; |
|||
|
|||
export const PageNav = (): JSX.Element => { |
|||
const { activePage, setActivePage } = useDevice(); |
|||
|
|||
interface NavLink { |
|||
name: string; |
|||
icon: (props: SVGProps<SVGSVGElement>) => JSX.Element; |
|||
page: Page; |
|||
} |
|||
|
|||
const pages: NavLink[] = [ |
|||
{ |
|||
name: "Messages", |
|||
icon: ChatBubbleBottomCenterTextIcon, |
|||
page: "messages" |
|||
}, |
|||
{ |
|||
name: "Map", |
|||
icon: MapIcon, |
|||
page: "map" |
|||
}, |
|||
{ |
|||
name: "Extensions", |
|||
icon: BeakerIcon, |
|||
page: "extensions" |
|||
}, |
|||
{ |
|||
name: "Config", |
|||
icon: Cog8ToothIcon, |
|||
page: "config" |
|||
}, |
|||
{ |
|||
name: "Channels", |
|||
icon: Square3Stack3DIcon, |
|||
page: "channels" |
|||
}, |
|||
{ |
|||
name: "Peers", |
|||
icon: UsersIcon, |
|||
page: "peers" |
|||
} |
|||
]; |
|||
|
|||
return ( |
|||
<div className="flex text-textPrimary"> |
|||
{pages.map((Link) => ( |
|||
<div |
|||
key={Link.name} |
|||
onClick={() => { |
|||
setActivePage(Link.page); |
|||
}} |
|||
className={`border-x-4 border-backgroundPrimary bg-backgroundPrimary py-5 px-4 hover:brightness-hover active:brightness-press ${ |
|||
Link.page === activePage |
|||
? "border-l-accent text-textPrimary" |
|||
: "text-textSecondary hover:text-textPrimary" |
|||
}`}
|
|||
> |
|||
<Link.icon className="w-4" /> |
|||
</div> |
|||
))} |
|||
</div> |
|||
); |
|||
}; |
|||
@ -1,89 +0,0 @@ |
|||
import type React from "react"; |
|||
|
|||
import { useDevice } from "@app/core/providers/useDevice.js"; |
|||
import type { Page } from "@app/core/stores/deviceStore.js"; |
|||
import { |
|||
BeakerIcon, |
|||
Cog8ToothIcon, |
|||
DocumentTextIcon, |
|||
IdentificationIcon, |
|||
InboxIcon, |
|||
MapIcon, |
|||
Square3Stack3DIcon, |
|||
UsersIcon |
|||
} from "@heroicons/react/24/outline"; |
|||
|
|||
export const PageNav = (): JSX.Element => { |
|||
const { activePage, setActivePage } = useDevice(); |
|||
|
|||
interface NavLink { |
|||
name: string; |
|||
icon: JSX.Element; |
|||
page: Page; |
|||
} |
|||
|
|||
const pages: NavLink[] = [ |
|||
{ |
|||
name: "Messages", |
|||
icon: <InboxIcon />, |
|||
page: "messages" |
|||
}, |
|||
{ |
|||
name: "Map", |
|||
icon: <MapIcon />, |
|||
page: "map" |
|||
}, |
|||
{ |
|||
name: "Extensions", |
|||
icon: <BeakerIcon />, |
|||
page: "extensions" |
|||
}, |
|||
{ |
|||
name: "Config", |
|||
icon: <Cog8ToothIcon />, |
|||
page: "config" |
|||
}, |
|||
{ |
|||
name: "Channels", |
|||
icon: <Square3Stack3DIcon />, |
|||
page: "channels" |
|||
}, |
|||
{ |
|||
name: "Peers", |
|||
icon: <UsersIcon />, |
|||
page: "peers" |
|||
}, |
|||
{ |
|||
name: "Info", |
|||
icon: <IdentificationIcon />, |
|||
page: "info" |
|||
}, |
|||
{ |
|||
name: "Logs", |
|||
icon: <DocumentTextIcon />, |
|||
page: "logs" |
|||
} |
|||
]; |
|||
|
|||
return ( |
|||
<div className="flex h-full flex-shrink-0 whitespace-nowrap bg-backgroundPrimary text-sm [writing-mode:vertical-rl]"> |
|||
<span className="mt-2 flex gap-2 font-bold"> |
|||
{pages.map((Link) => ( |
|||
<div |
|||
key={Link.name} |
|||
onClick={() => { |
|||
setActivePage(Link.page); |
|||
}} |
|||
className={`hover:border-orange-300 h-9 w-9 cursor-pointer border-l-2 p-1.5 ${ |
|||
Link.page === activePage |
|||
? "border-accent text-textPrimary" |
|||
: "border-backgroundPrimary text-textSecondary hover:text-textPrimary" |
|||
}`}
|
|||
> |
|||
{Link.icon} |
|||
</div> |
|||
))} |
|||
</span> |
|||
</div> |
|||
); |
|||
}; |
|||
@ -1,71 +0,0 @@ |
|||
import type React from "react"; |
|||
import { useState } from "react"; |
|||
|
|||
import { JSONTree } from "react-json-tree"; |
|||
|
|||
import { |
|||
TabbedContent, |
|||
TabType |
|||
} from "@app/components/generic/TabbedContent.js"; |
|||
import { useDevice } from "@core/providers/useDevice.js"; |
|||
import { EyeIcon } from "@heroicons/react/24/outline"; |
|||
|
|||
export const InfoPage = (): JSX.Element => { |
|||
const { config, moduleConfig, hardware, nodes, waypoints, connection } = |
|||
useDevice(); |
|||
|
|||
const [serialLogs, setSerialLogs] = useState<string>(""); |
|||
|
|||
connection?.events.onDeviceDebugLog.subscribe((packet) => { |
|||
setSerialLogs(serialLogs + new TextDecoder().decode(packet)); |
|||
}); |
|||
|
|||
const tabs: TabType[] = [ |
|||
{ |
|||
name: "Hardware", |
|||
icon: <EyeIcon className="h-4" />, |
|||
element: () => <JSONTree theme="monokai" data={hardware} /> |
|||
}, |
|||
{ |
|||
name: "Config", |
|||
icon: <EyeIcon className="h-4" />, |
|||
element: () => <JSONTree theme="monokai" data={config} /> |
|||
}, |
|||
{ |
|||
name: "Module Config", |
|||
icon: <EyeIcon className="h-4" />, |
|||
element: () => <JSONTree theme="monokai" data={moduleConfig} /> |
|||
}, |
|||
|
|||
{ |
|||
name: "Nodes", |
|||
icon: <EyeIcon className="h-4" />, |
|||
element: () => <JSONTree theme="monokai" data={nodes} /> |
|||
}, |
|||
{ |
|||
name: "Waypoints", |
|||
icon: <EyeIcon className="h-4" />, |
|||
element: () => <JSONTree theme="monokai" data={waypoints} /> |
|||
}, |
|||
{ |
|||
name: "Connection", |
|||
icon: <EyeIcon className="h-4" />, |
|||
element: () => <JSONTree theme="monokai" data={connection} /> |
|||
}, |
|||
{ |
|||
name: "Serial Logs", |
|||
icon: <EyeIcon className="h-4" />, |
|||
element: () => ( |
|||
<div> |
|||
{serialLogs.split("\n").map((line, index) => ( |
|||
<div key={index} className="text-sm"> |
|||
{line} |
|||
</div> |
|||
))} |
|||
</div> |
|||
) |
|||
} |
|||
]; |
|||
|
|||
return <TabbedContent tabs={tabs} />; |
|||
}; |
|||
@ -1,77 +0,0 @@ |
|||
import type React from "react"; |
|||
import { useEffect, useState } from "react"; |
|||
|
|||
import { Mono } from "@app/components/generic/Mono.js"; |
|||
import { useDevice } from "@core/providers/useDevice.js"; |
|||
import { Protobuf, Types } from "@meshtastic/meshtasticjs"; |
|||
|
|||
export const LogsPage = (): JSX.Element => { |
|||
const { connection } = useDevice(); |
|||
const [logs, setLogs] = useState<Types.LogEvent[]>([]); |
|||
|
|||
useEffect(() => { |
|||
connection?.events.onLogEvent.subscribe((log) => { |
|||
setLogs([...logs, log]); |
|||
}); |
|||
}, [connection, setLogs, logs]); |
|||
|
|||
return ( |
|||
<div className="w-full overflow-y-auto"> |
|||
<div className="ring-black overflow-hidden ring-1 ring-opacity-5"> |
|||
<table className="divide-gray-300 min-w-full divide-y"> |
|||
<thead className="bg-gray-50"> |
|||
<tr> |
|||
<th |
|||
scope="col" |
|||
className="text-gray-900 py-3.5 pr-3 pl-6 text-left text-sm font-semibold" |
|||
> |
|||
Emitter |
|||
</th> |
|||
<th |
|||
scope="col" |
|||
className="text-gray-900 py-3.5 text-left text-sm font-semibold" |
|||
> |
|||
Level |
|||
</th> |
|||
<th |
|||
scope="col" |
|||
className="text-gray-900 py-3.5 text-left text-sm font-semibold" |
|||
> |
|||
Message |
|||
</th> |
|||
<th |
|||
scope="col" |
|||
className="text-gray-900 py-3.5 text-left text-sm font-semibold" |
|||
> |
|||
Scope |
|||
</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody className="bg-white"> |
|||
{logs.map((log, index) => ( |
|||
<tr |
|||
key={index} |
|||
className={index % 2 === 0 ? undefined : "bg-gray-50"} |
|||
> |
|||
<td className="text-gray-500 whitespace-nowrap py-2 pl-6 text-sm"> |
|||
<span className="my-auto">{Types.Emitter[log.emitter]}</span> |
|||
</td> |
|||
<td className="text-gray-500 whitespace-nowrap py-2 text-sm"> |
|||
<span className="bg-slate-200 rounded-md p-1"> |
|||
<Mono>{[Protobuf.LogRecord_Level[log.level]]}</Mono> |
|||
</span> |
|||
</td> |
|||
<td className="text-gray-500 whitespace-nowrap py-2 text-sm"> |
|||
<Mono>{log.message}</Mono> |
|||
</td> |
|||
<td className="text-gray-500 whitespace-nowrap py-2 text-sm"> |
|||
{Types.EmitterScope[log.scope]} |
|||
</td> |
|||
</tr> |
|||
))} |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
Loading…
Reference in new issue