Files
app/frontend/src/components/Dashboard/PanelSurface.tsx
T
stroblme 1faf7f7c11 Touch is a panel setting, and the rail grows with it
It described the wrong object. A dashboard is a document that may hang on a
hallway tablet and in a desk browser at the same time, and only one of those
has fingers on it — so the flag moves off `DashboardDef.settings` and onto
`PanelDef` as a plain bool, ticked in the Panels dialog. `useCanvasRoot` takes
it as an argument rather than reading the document, and `/panel/{id}` is the
only surface with a panel to ask.

Dropping the message binding with it is deliberate: nothing drove it, and a
flow deciding whether a screen has fingers on it was never the point. A stored
`settings.touch` is inert rather than migrated, which `_check_settings`
skipping unknown names already guaranteed.

The rail was the other half. It had no touch behaviour at all and its 40px
buttons met neither branch of the 44/32 rule. `[data-touch] .dui-rail{-item}`
in `ui/core/core.css` spends the padding and the gap on the buttons instead,
so they reach the 44px target and the rail comes out taller at exactly the
same width — `RAIL_INSET` never moves, and the arrangement under it does not
either.

Also closes the panels-dialog icon gap: `DashboardSummary` carries the `icon`
now, so the dialog draws each assigned dashboard's rail glyph beside its
checkbox. `initials()` went from three identical copies in the looks to one in
`Dashboard/icons.ts`, so the dialog and the rail fall back the same way.
2026-08-31 19:04:20 +02:00

77 lines
2.8 KiB
TypeScript

import type { Dashboard } from "@/components/Dashboard/DashboardView"
import {
CanvasSurface,
DashboardView,
} from "@/components/Dashboard/DashboardView"
import { useDashboardLocked } from "@/components/Dashboard/settings"
import { useUi } from "@/components/Dashboard/ui"
import { LookProvider } from "@/components/Dashboard/ui/core/look"
/**
* One dashboard filling whatever screen it landed on.
*
* The whole of what a wall panel draws, shared by the single-dashboard route
* (`/view/{name}`) and the paired-panel one (`/panel/{id}`) so a device shows
* the same thing either way — the second merely has a rail beside it.
*
* The dashboard's theme is stated by the route rather than here: on these two
* the screen *is* the dashboard, so it has to cover the letterbox and the rail
* as well as the canvas.
*/
export function PanelSurface({
dashboard,
stacked,
rail,
touch,
}: {
dashboard: Dashboard
/** A landscape arrangement scaled onto a phone comes out at about a fifth of
* its size, which reads as nothing at all. Stack it instead. */
stacked?: boolean
/** The way between this panel's dashboards, drawn on the canvas itself. */
rail?: React.ReactNode
/** Whether the screen this hangs on is touched. Stated by the panel route. */
touch?: boolean
}) {
return (
<div className="relative size-full">
{stacked ? (
<DashboardView dashboard={dashboard} stacked />
) : (
// The panel's own surface, scaled to fit. No dots: nothing is being
// arranged here.
<CanvasSurface dashboard={dashboard} rail={rail} touch={touch}>
{() => <DashboardView dashboard={dashboard} rail={Boolean(rail)} />}
</CanvasSurface>
)}
{/* Outside the canvas, so it needs the look stated for it. */}
<LookProvider dashboard={dashboard} touch={touch}>
<LockNotice dashboard={dashboard} />
</LookProvider>
</div>
)
}
/**
* What a locked dashboard looks like, beyond controls that read as disabled.
*
* Shared with the editor's view mode, which shows the same locked dashboard
* from inside the app.
*
* Without it a read-only panel is a panel whose buttons do nothing, which
* reads as broken rather than as locked. Frosted chrome over content, like
* every other floating surface, and it says the state in words — a glyph on
* its own is not a label.
*
* Live, because `locked` may be driven by a flow: the notice appears and goes
* with the lock rather than with the page load.
*/
export function LockNotice({ dashboard }: { dashboard: Dashboard }) {
const { Notice } = useUi()
// Read straight from the document rather than through `useLocked`: that
// provider sits inside the grid, and this notice is beside it.
const locked = useDashboardLocked(dashboard)
if (!locked) return null
return <Notice>Read-only</Notice>
}