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