From 5987bf64443770624f12936190d9590fce735b2e Mon Sep 17 00:00:00 2001 From: jenstandstad Date: Sun, 23 Aug 2026 23:29:10 +0200 Subject: [PATCH] Improve document tray navigation --- e2e/board.smoke.spec.ts | 8 ++++++-- src/App.tsx | 29 ++++++++++++++++++++++++----- src/styles.css | 10 +++++----- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/e2e/board.smoke.spec.ts b/e2e/board.smoke.spec.ts index 2cfbbca..0e5bbc3 100644 --- a/e2e/board.smoke.spec.ts +++ b/e2e/board.smoke.spec.ts @@ -51,11 +51,15 @@ test('move, folder expansion, empty-board pan, desktop wheel zoom, mobile pinch, await page.getByRole('button', { name: 'EVIDENCE', exact: true }).click() const documentRow = page.locator('[data-document-row-id="22222222-2222-4222-8222-222222222222"]') - await documentRow.click() + await documentRow.locator('.doc-row-main').click() await expect(documentRow).toHaveClass(/\bselected\b/) await expect(page.locator('[data-temporal-id="widget:11111111-1111-4111-8111-111111111111"]')).toHaveClass(/\bdocument-located\b/) await expect(page.locator('.document-locator-beam .document-locator-ray')).toBeVisible() - await documentRow.dblclick() + const transformBeforeLocate = await page.locator('.board').getAttribute('style') + await waitForSave(page, () => page.getByRole('button', { name: 'Locate Dated source image on board', exact: true }).click()) + await expect.poll(() => page.locator('.board').getAttribute('style')).not.toEqual(transformBeforeLocate) + await expect(documentRow).toHaveClass(/\bselected\b/) + await documentRow.locator('.doc-row-main').dblclick() await expect(page.getByRole('button', { name: 'Close document', exact: true })).toBeVisible() await page.getByRole('button', { name: 'Close document', exact: true }).click() await page.getByRole('button', { name: 'Close documents', exact: true }).click() diff --git a/src/App.tsx b/src/App.tsx index bbce2b7..373095f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -249,6 +249,23 @@ export function App() { : s.viewport })) } + const focusDocument = (id: string) => { + if (!caseState) return + const document = caseState.exhibits.find((exhibit): exhibit is CaseDocument => exhibit.id === id && exhibit.type === 'document') + if (!document) return + const membership = caseState.relations.find(relation => relation.type === 'contains' && relation.toExhibitId === document.id) + const folder = membership ? caseState.exhibits.find((exhibit): exhibit is FolderExhibit => exhibit.id === membership.fromExhibitId && exhibit.type === 'folder') : undefined + const visibleTarget = folder && !folder.isOpen ? folder : document + const bounds = boardRef.current?.getBoundingClientRect() + setSelected(document.id) + update(state => { + if (!bounds) return state + const focusViewport = { ...state.viewport, zoom: Math.max(state.viewport.zoom, .9) } + return { ...state, viewport: viewportCenteredOnExhibit(focusViewport, visibleTarget, { width: bounds.width, height: bounds.height }) } + }) + setStatus(`LOCATED EXHIBIT ${document.displayNumber || ''}`.trim()) + } + const extract = (doc: CaseDocument, regionId: string) => { if (!caseState) return const region = doc.regions.find(r => r.id === regionId)! @@ -668,13 +685,15 @@ export function App() { <> { if (e.target.files) void uploadFiles(e.target.files); e.target.value = '' }} />
- {filteredDocuments.map((doc, index) => )} + {filteredDocuments.map((doc, index) =>
+ + +
)} {normalizedDocumentQuery && filteredDocuments.length === 0 &&
NO MATCHING DOCUMENTSSearches titles, contents, extracts, and metadata.
}
-
ARCHIVE MOUNTED {requestedEditMode && caseState.editingAllowed ? 'AUTHORING' : 'READ ONLY'}
{ if (e.dataTransfer.types.includes('Files')) { e.preventDefault(); setDraggingFiles(true) } }} onDragOver={e => { e.preventDefault(); e.dataTransfer.dropEffect = 'copy' }} onDragLeave={e => { if (!e.currentTarget.contains(e.relatedTarget as Node)) setDraggingFiles(false) }} onDrop={e => { e.preventDefault(); if (e.dataTransfer.files.length) void uploadFiles(e.dataTransfer.files) }}> diff --git a/src/styles.css b/src/styles.css index c8a4c0f..2b0acc4 100644 --- a/src/styles.css +++ b/src/styles.css @@ -34,19 +34,19 @@ button:focus-visible { outline: 2px solid #e99a44; outline-offset: 2px; } .import-document { margin: 0 15px 12px; height: 34px; border: 1px dashed #a26d3d; background: #19322c; color: #d79754; display: flex; justify-content: center; align-items: center; gap: 8px; font: 600 9px IBM Plex Mono; letter-spacing: .08em; cursor: pointer; }.import-document:hover { background: #244039; border-style: solid; }.file-input { display: none; } .doc-list { overflow: auto; border-top: 1px solid #243d36; } .no-document-results { min-height: 150px; padding: 32px 20px; display: grid; place-items: center; align-content: center; gap: 9px; text-align: center; color: #617d75; }.no-document-results b { color: #91a59f; font: 600 9px IBM Plex Mono; letter-spacing: .08em; }.no-document-results span { font: 8px/1.5 IBM Plex Mono; } -.doc-row { width: 100%; min-height: 76px; border: 0; border-bottom: 1px solid #243d36; background: transparent; padding: 11px 13px; display: grid; grid-template-columns: 45px 1fr 18px; text-align: left; align-items: center; gap: 10px; cursor: pointer; } +.doc-row { position: relative; width: 100%; min-height: 76px; border-bottom: 1px solid #243d36; background: transparent; display: grid; grid-template-columns: minmax(0, 1fr) 38px; text-align: left; align-items: stretch; } .doc-row:hover { background: #18342e; } -.doc-row.selected { position: relative; background: #304534; box-shadow: inset 4px 0 #f1cf55, inset 0 0 20px #d8b94722; } +.doc-row.selected { background: #304534; box-shadow: inset 4px 0 #f1cf55, inset 0 0 20px #d8b94722; } .doc-row.selected::after { content: 'LOCATED'; position: absolute; top: 7px; right: 9px; color: #f0ce57; font: 600 6px IBM Plex Mono; letter-spacing: .12em; } .doc-row.selected .doc-icon { border-color: #e7c750; box-shadow: 0 0 12px #efd44f66, 3px 3px #081a16; } +.doc-row-main { min-width: 0; min-height: 76px; padding: 11px 7px 11px 13px; display: grid; grid-template-columns: 45px minmax(0, 1fr); align-items: center; gap: 10px; border: 0; background: transparent; color: inherit; text-align: left; cursor: pointer; } +.doc-row-locate { display: grid; place-items: center; margin: 25px 5px 8px 0; padding: 0; border: 1px solid transparent; background: transparent; color: #6f8f85; cursor: pointer; } +.doc-row-locate:hover { border-color: #9a683d; background: #102722; color: #e0a25d; } .doc-row strong { display: block; color: #d8dfda; font-size: 12px; margin-bottom: 6px; } .doc-row span { display: block; font: 9px IBM Plex Mono; color: #708b83; } -.doc-row > svg { color: #527068; } .doc-icon { height: 49px; border: 1px solid #61736d; display: grid; place-items: center; position: relative; color: #ccd2cd; background: #263c36; box-shadow: 3px 3px #081a16; } .doc-icon b { position: absolute; bottom: 2px; right: 2px; background: #d58e42; color: #14231f; font: 600 7px IBM Plex Mono; padding: 1px 3px; } .tint-1 { background: #38352d; } .tint-2 { background: #263c3f; } -.panel-foot { height: 42px; border-top: 1px solid #354b45; padding: 0 16px; display: flex; align-items: center; gap: 8px; font: 9px IBM Plex Mono; color: #718d84; } -.panel-foot span { margin-left: auto; color: #b27b43; } .board-shell { flex: 1; min-width: 0; position: relative; background: #0b1d19; } .case-heading { position:absolute;top:20px;left:27px;z-index:2;color:#dde2de;pointer-events:none; } .case-heading h1 { margin:0;font:500 24px Special Elite,serif;letter-spacing:.02em; }