Keep the lock off the stacked editor while a dashboard is arranged
The phone path draws through DashboardView in both modes, so a locked dashboard came out with dead controls while it was being arranged — the canvas path hands editing to its own grid and never mounts the lock. DashboardView takes `editing` and skips LockedProvider for it; reading a locked dashboard stacked still locks. Guarded in mobile.spec.ts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1moruzue2kTJd3uVisgNk
This commit is contained in:
@@ -461,6 +461,7 @@ export function DashboardEditor({
|
||||
<DashboardView
|
||||
dashboard={draft}
|
||||
stacked
|
||||
editing={edit}
|
||||
renderWidget={edit ? pickable : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -306,6 +306,7 @@ export function DashboardView({
|
||||
renderWidget,
|
||||
stacked,
|
||||
rail,
|
||||
editing,
|
||||
}: {
|
||||
dashboard: Dashboard
|
||||
renderWidget?: (widget: WidgetDef) => React.ReactNode
|
||||
@@ -313,6 +314,9 @@ export function DashboardView({
|
||||
stacked?: boolean
|
||||
/** Whether the panel carries a rail, which takes a column of the canvas. */
|
||||
rail?: boolean
|
||||
/** Being arranged rather than used, so the lock is not mounted — the
|
||||
* stacked editor draws through here, where the canvas draws its own grid. */
|
||||
editing?: boolean
|
||||
}) {
|
||||
const all = widgetsOf(dashboard)
|
||||
if (all.length === 0) {
|
||||
@@ -337,20 +341,26 @@ export function DashboardView({
|
||||
: all
|
||||
const columns = columnsOf(dashboard)
|
||||
|
||||
const grid = (
|
||||
<PaletteProvider dashboard={dashboard}>
|
||||
<WidgetGrid
|
||||
dashboard={dashboard}
|
||||
widgets={widgets}
|
||||
columns={columns}
|
||||
stacked={stacked}
|
||||
rail={rail}
|
||||
renderWidget={renderWidget}
|
||||
/>
|
||||
</PaletteProvider>
|
||||
)
|
||||
|
||||
return (
|
||||
<LookProvider dashboard={dashboard}>
|
||||
<LockedProvider dashboard={dashboard}>
|
||||
<PaletteProvider dashboard={dashboard}>
|
||||
<WidgetGrid
|
||||
dashboard={dashboard}
|
||||
widgets={widgets}
|
||||
columns={columns}
|
||||
stacked={stacked}
|
||||
rail={rail}
|
||||
renderWidget={renderWidget}
|
||||
/>
|
||||
</PaletteProvider>
|
||||
</LockedProvider>
|
||||
{editing ? (
|
||||
grid
|
||||
) : (
|
||||
<LockedProvider dashboard={dashboard}>{grid}</LockedProvider>
|
||||
)}
|
||||
</LookProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ const dashboardName = `${flowName}_panel`
|
||||
/** What the bar and the forecast read. A flow of its own, so the graph the
|
||||
* editor lays out above stays the shape those assertions were written for. */
|
||||
const feedName = `${flowName}_feed`
|
||||
/** A read-only dashboard, on its own so the width checks above keep a live one. */
|
||||
const lockedName = `${flowName}_locked`
|
||||
|
||||
test.describe.configure({ mode: "serial" })
|
||||
|
||||
@@ -129,6 +131,7 @@ test.beforeAll(async ({ browser }) => {
|
||||
// this whole name in the chart's legend.
|
||||
{ name: "climate_series_reading", dtype: "float" },
|
||||
{ name: "mode", dtype: "str" },
|
||||
{ name: "lamp", dtype: "bool" },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -269,12 +272,37 @@ test.beforeAll(async ({ browser }) => {
|
||||
method: "POST",
|
||||
data: { version: draft.version },
|
||||
})
|
||||
|
||||
const locked = await (
|
||||
await api(page, `/dashboards/${lockedName}`, { method: "POST" })
|
||||
).json()
|
||||
locked.settings = { ...(locked.settings ?? {}), locked: { value: true } }
|
||||
locked.widgets = [
|
||||
{
|
||||
id: "lamp",
|
||||
type: "switch",
|
||||
title: "Lamp",
|
||||
layout: { lg: { x: 0, y: 0, w: 3, h: 2 } },
|
||||
config: { target: `${feedName}.lamp`, dtype: "bool", style: "button" },
|
||||
},
|
||||
]
|
||||
const lockedDraft = await (
|
||||
await api(page, `/dashboards/${lockedName}`, {
|
||||
method: "PUT",
|
||||
data: locked,
|
||||
})
|
||||
).json()
|
||||
await api(page, `/dashboards/${lockedName}/publish`, {
|
||||
method: "POST",
|
||||
data: { version: lockedDraft.version },
|
||||
})
|
||||
await page.close()
|
||||
})
|
||||
|
||||
test.afterAll(async ({ browser }) => {
|
||||
await deleteAll(browser, [
|
||||
`/dashboards/${dashboardName}`,
|
||||
`/dashboards/${lockedName}`,
|
||||
`/flows/${flowName}`,
|
||||
`/flows/${feedName}`,
|
||||
])
|
||||
@@ -360,6 +388,27 @@ test("a dashboard stacks instead of shrinking", async ({ page }) => {
|
||||
expect(second!.y).toBeGreaterThanOrEqual(first!.y + first!.height - 1)
|
||||
})
|
||||
|
||||
/**
|
||||
* A read-only dashboard, read and arranged at a phone's width.
|
||||
*
|
||||
* Stacked, both modes draw through `DashboardView` — where the canvas hands
|
||||
* editing to its own grid instead — so the lock has to follow the mode rather
|
||||
* than the page. Arranging a dashboard is not using it.
|
||||
*/
|
||||
test("a locked dashboard locks when read, not while arranged", async ({
|
||||
page,
|
||||
}) => {
|
||||
const lamp = page.getByRole("button", { name: "Lamp" })
|
||||
|
||||
await page.goto(`/dashboards/${lockedName}`)
|
||||
await lamp.waitFor({ timeout: 15000 })
|
||||
await expect(lamp, "a locked dashboard being read").toBeDisabled()
|
||||
|
||||
await page.goto(`/dashboards/${lockedName}?edit=true`)
|
||||
await lamp.waitFor({ timeout: 15000 })
|
||||
await expect(lamp, "a locked dashboard being arranged").toBeEnabled()
|
||||
})
|
||||
|
||||
test("the panel view fits the viewport", async ({ page }) => {
|
||||
await page.goto(`/view/${dashboardName}`)
|
||||
await page.waitForSelector("[data-testid=widget-frame]", { timeout: 15000 })
|
||||
|
||||
Reference in New Issue
Block a user