Widgets bind to a message name and read it live off the socket the editor already had — lifted out of the flow editor so a dashboard route gets the same values, which also gives the home page live data for free. The input widgets close the loop the other way: a slider publishes into the graph and whatever consumes that message runs. Verified end to end in the running app — moving a slider set a flow input, and the stat bound to what the flow computed from it followed. View mode is plain CSS grid. A wall panel that only displays should not download the code that lets someone drag things around, and it now does not. Editing is a widget picker, a per-widget width control and a settings card fed by the message catalog. No new dependencies: the slider is a range input, the gauge is an arc, and the markdown is a five-line subset. Charts are the one widget still missing — they need a charting library and the chart tokens the design guidelines reserved — so they are stored and validated but not offered.
50 lines
1.4 KiB
TypeScript
50 lines
1.4 KiB
TypeScript
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"
|
|
|
|
import { Footer } from "@/components/Common/Footer"
|
|
import { useFlowSocket } from "@/components/Flow/useFlowSocket"
|
|
import AppSidebar from "@/components/Sidebar/AppSidebar"
|
|
import {
|
|
SidebarInset,
|
|
SidebarProvider,
|
|
SidebarTrigger,
|
|
} from "@/components/ui/sidebar"
|
|
import { isLoggedIn } from "@/hooks/useAuth"
|
|
|
|
export const Route = createFileRoute("/_layout")({
|
|
component: Layout,
|
|
beforeLoad: async () => {
|
|
if (!isLoggedIn()) {
|
|
throw redirect({
|
|
to: "/login",
|
|
})
|
|
}
|
|
},
|
|
})
|
|
|
|
function Layout() {
|
|
// Dashboards live in this shell and read live values, so the socket belongs
|
|
// here rather than only inside the editor.
|
|
useFlowSocket()
|
|
|
|
return (
|
|
<SidebarProvider className="bg-card">
|
|
<AppSidebar />
|
|
<SidebarInset className="bg-card">
|
|
{/* The sidebar carries its own collapse control; a phone has no
|
|
sidebar on screen to carry it. */}
|
|
<header className="sticky top-0 z-10 flex h-16 shrink-0 items-center gap-2 px-4 md:hidden">
|
|
<SidebarTrigger className="-ml-1 text-muted-foreground" />
|
|
</header>
|
|
<main className="flex-1 p-6 md:p-8">
|
|
<div className="mx-auto max-w-7xl">
|
|
<Outlet />
|
|
</div>
|
|
</main>
|
|
<Footer />
|
|
</SidebarInset>
|
|
</SidebarProvider>
|
|
)
|
|
}
|
|
|
|
export default Layout
|