diff --git a/e2e/board.smoke.spec.ts b/e2e/board.smoke.spec.ts index 6d6f883..931d2e0 100644 --- a/e2e/board.smoke.spec.ts +++ b/e2e/board.smoke.spec.ts @@ -24,7 +24,7 @@ async function waitForSave(page: Page, action: () => Promise) { await response } -test('move, folder expansion, hand pan, desktop wheel zoom, mobile pinch, and reload persistence', async ({ page }) => { +test('move, folder expansion, empty-board pan, desktop wheel zoom, mobile pinch, and reload persistence', async ({ page }) => { await page.goto('/?level=e2e-level&edit=1') await expect(page.getByRole('heading', { name: 'Browser Safety Test' })).toBeVisible() await expect(page.locator('.documents-panel')).toHaveClass(/\bclosed\b/) @@ -65,14 +65,13 @@ test('move, folder expansion, hand pan, desktop wheel zoom, mobile pinch, and re const folderBeforePan = await boardPosition(folder) const transformBeforePan = await board.getAttribute('style') - await page.getByRole('button', { name: 'HAND', exact: true }).click() + await expect(page.getByRole('button', { name: 'MOVE', exact: true })).toHaveClass(/\bactive\b/) const viewportBox = await boardViewport.boundingBox() if (!viewportBox) throw new Error('Board viewport is not visible') await waitForSave(page, async () => { - await page.mouse.move(viewportBox.x + 240, viewportBox.y + 230) - await page.mouse.down() - await page.mouse.move(viewportBox.x + 290, viewportBox.y + 195, { steps: 5 }) - await page.mouse.up() + await boardViewport.dispatchEvent('pointerdown', { pointerId: 31, pointerType: 'mouse', isPrimary: true, button: 0, clientX: viewportBox.x + 240, clientY: viewportBox.y + 230 }) + await boardViewport.dispatchEvent('pointermove', { pointerId: 31, pointerType: 'mouse', isPrimary: true, button: 0, clientX: viewportBox.x + 290, clientY: viewportBox.y + 195 }) + await boardViewport.dispatchEvent('pointerup', { pointerId: 31, pointerType: 'mouse', isPrimary: true, button: 0, clientX: viewportBox.x + 290, clientY: viewportBox.y + 195 }) }) expect(await boardPosition(folder)).toEqual(folderBeforePan) const transformAfterPan = await board.getAttribute('style') diff --git a/src/App.tsx b/src/App.tsx index 9b50f6b..095c962 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -518,7 +518,12 @@ function Board({ state, selected, linkFrom, recentlyCreatedExhibitId, recentlyCr const toggleFolder = (id: string) => update(s => ({ ...s, evidence: s.evidence.map(widget => widget.id === id ? { ...widget, config: { ...(widget.config || {}), open: !folderIsOpen(widget) } } : widget) })) const previewOrigin = linkFrom ? pointForId(linkFrom) : undefined return
{ if (!(e.target as HTMLElement).closest('.thread-tag')) setExpandedThreadTagId(null); if (e.pointerType === 'touch' || tool === 'hand' || e.button === 1) { e.preventDefault(); pointerDown(e) } }} + onPointerDown={e => { + const target = e.target as HTMLElement + if (!target.closest('.thread-tag')) setExpandedThreadTagId(null) + const emptyBoardDrag = e.button === 0 && !target.closest('.evidence-card, .source-file-widget, .thread-tag, button') + if (e.pointerType === 'touch' || tool === 'hand' || e.button === 1 || emptyBoardDrag) { e.preventDefault(); pointerDown(e) } + }} onPointerMoveCapture={trackThreadPointer} onPointerMove={pointerMove} onPointerLeave={() => setThreadPointer(null)} onPointerUp={finishDrag} onPointerCancel={finishDrag} onAuxClick={e => { if (e.button === 1) e.preventDefault() }}>
AUTHORIZED CITIZEN SCIENTIST WORKSTATION GU-NET / 04
diff --git a/src/styles.css b/src/styles.css index 6de4850..29fe5b8 100644 --- a/src/styles.css +++ b/src/styles.css @@ -50,6 +50,8 @@ button:focus-visible { outline: 2px solid #e99a44; outline-offset: 2px; } .case-number { border-left: 1px solid #415750; margin-left: 26px; padding-left: 17px; font: 8px IBM Plex Mono; color: #6d867f; line-height: 1.5; } .case-number b { color: #bdc9c3; font-size: 13px; } .board-viewport { position: absolute; inset: 0; overflow: hidden; touch-action: none; overscroll-behavior: contain; cursor: default; background-image: radial-gradient(#49615a55 1px, transparent 1px), linear-gradient(90deg, #18302a33 1px, transparent 1px), linear-gradient(#18302a33 1px, transparent 1px); background-size: 20px 20px, 100px 100px, 100px 100px; } +.board-viewport.tool-move { cursor: grab; } +.board-viewport.tool-move:active { cursor: grabbing; } .board-viewport.tool-hand, .board-viewport.tool-hand .evidence-card, .board-viewport.tool-hand .source-file-widget { cursor: grab; } .board-viewport.tool-hand:active, .board-viewport.tool-hand .evidence-card:active, .board-viewport.tool-hand .source-file-widget:active { cursor: grabbing; } .board-viewport.threading, .board-viewport.threading .evidence-card, .board-viewport.threading .source-file-widget { cursor: crosshair; }