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
|
<DashboardView
|
||||||
dashboard={draft}
|
dashboard={draft}
|
||||||
stacked
|
stacked
|
||||||
|
editing={edit}
|
||||||
renderWidget={edit ? pickable : undefined}
|
renderWidget={edit ? pickable : undefined}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -306,6 +306,7 @@ export function DashboardView({
|
|||||||
renderWidget,
|
renderWidget,
|
||||||
stacked,
|
stacked,
|
||||||
rail,
|
rail,
|
||||||
|
editing,
|
||||||
}: {
|
}: {
|
||||||
dashboard: Dashboard
|
dashboard: Dashboard
|
||||||
renderWidget?: (widget: WidgetDef) => React.ReactNode
|
renderWidget?: (widget: WidgetDef) => React.ReactNode
|
||||||
@@ -313,6 +314,9 @@ export function DashboardView({
|
|||||||
stacked?: boolean
|
stacked?: boolean
|
||||||
/** Whether the panel carries a rail, which takes a column of the canvas. */
|
/** Whether the panel carries a rail, which takes a column of the canvas. */
|
||||||
rail?: boolean
|
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)
|
const all = widgetsOf(dashboard)
|
||||||
if (all.length === 0) {
|
if (all.length === 0) {
|
||||||
@@ -337,9 +341,7 @@ export function DashboardView({
|
|||||||
: all
|
: all
|
||||||
const columns = columnsOf(dashboard)
|
const columns = columnsOf(dashboard)
|
||||||
|
|
||||||
return (
|
const grid = (
|
||||||
<LookProvider dashboard={dashboard}>
|
|
||||||
<LockedProvider dashboard={dashboard}>
|
|
||||||
<PaletteProvider dashboard={dashboard}>
|
<PaletteProvider dashboard={dashboard}>
|
||||||
<WidgetGrid
|
<WidgetGrid
|
||||||
dashboard={dashboard}
|
dashboard={dashboard}
|
||||||
@@ -350,7 +352,15 @@ export function DashboardView({
|
|||||||
renderWidget={renderWidget}
|
renderWidget={renderWidget}
|
||||||
/>
|
/>
|
||||||
</PaletteProvider>
|
</PaletteProvider>
|
||||||
</LockedProvider>
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LookProvider dashboard={dashboard}>
|
||||||
|
{editing ? (
|
||||||
|
grid
|
||||||
|
) : (
|
||||||
|
<LockedProvider dashboard={dashboard}>{grid}</LockedProvider>
|
||||||
|
)}
|
||||||
</LookProvider>
|
</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
|
/** 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. */
|
* editor lays out above stays the shape those assertions were written for. */
|
||||||
const feedName = `${flowName}_feed`
|
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" })
|
test.describe.configure({ mode: "serial" })
|
||||||
|
|
||||||
@@ -129,6 +131,7 @@ test.beforeAll(async ({ browser }) => {
|
|||||||
// this whole name in the chart's legend.
|
// this whole name in the chart's legend.
|
||||||
{ name: "climate_series_reading", dtype: "float" },
|
{ name: "climate_series_reading", dtype: "float" },
|
||||||
{ name: "mode", dtype: "str" },
|
{ name: "mode", dtype: "str" },
|
||||||
|
{ name: "lamp", dtype: "bool" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -269,12 +272,37 @@ test.beforeAll(async ({ browser }) => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
data: { version: draft.version },
|
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()
|
await page.close()
|
||||||
})
|
})
|
||||||
|
|
||||||
test.afterAll(async ({ browser }) => {
|
test.afterAll(async ({ browser }) => {
|
||||||
await deleteAll(browser, [
|
await deleteAll(browser, [
|
||||||
`/dashboards/${dashboardName}`,
|
`/dashboards/${dashboardName}`,
|
||||||
|
`/dashboards/${lockedName}`,
|
||||||
`/flows/${flowName}`,
|
`/flows/${flowName}`,
|
||||||
`/flows/${feedName}`,
|
`/flows/${feedName}`,
|
||||||
])
|
])
|
||||||
@@ -360,6 +388,27 @@ test("a dashboard stacks instead of shrinking", async ({ page }) => {
|
|||||||
expect(second!.y).toBeGreaterThanOrEqual(first!.y + first!.height - 1)
|
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 }) => {
|
test("the panel view fits the viewport", async ({ page }) => {
|
||||||
await page.goto(`/view/${dashboardName}`)
|
await page.goto(`/view/${dashboardName}`)
|
||||||
await page.waitForSelector("[data-testid=widget-frame]", { timeout: 15000 })
|
await page.waitForSelector("[data-testid=widget-frame]", { timeout: 15000 })
|
||||||
|
|||||||
Reference in New Issue
Block a user