diff --git a/README.md b/README.md index 673107b..2e19b64 100644 --- a/README.md +++ b/README.md @@ -112,8 +112,8 @@ The prebuilt image is at `git.draic.info/nils/photos` (single image contains `server`, `worker`, and the built frontend). To build your own instead: ```sh -docker build -t registry.example.com/you/photos:0.5.0 . -docker push registry.example.com/you/photos:0.5.0 +docker build -t registry.example.com/you/photos:0.5.1 . +docker push registry.example.com/you/photos:0.5.1 ``` Install the chart, pointing it at your existing Postgres and S3: @@ -121,7 +121,7 @@ Install the chart, pointing it at your existing Postgres and S3: ```sh helm install photos deploy/chart \ --set image.repository=git.draic.info/nils/photos \ - --set image.tag=0.5.0 \ + --set image.tag=0.5.1 \ --set publicUrl=https://photos.example.com \ --set ingress.host=photos.example.com \ --set config.oidcIssuer=https://auth.example.com \ diff --git a/frontend/src/components/Gallery.jsx b/frontend/src/components/Gallery.jsx index 7ea5cc0..3e63f66 100644 --- a/frontend/src/components/Gallery.jsx +++ b/frontend/src/components/Gallery.jsx @@ -24,24 +24,21 @@ export default function Gallery({ }) { const selecting = selected && selected.size > 0 const containerRef = useRef(null) - const [cursor, setCursor] = useState(-1) - // The outline only shows once the keyboard (or the viewer) was actually - // used — a fresh page must not present a photo as pre-marked. - const [cursorVisible, setCursorVisible] = useState(false) + // The cursor is always a valid index; `revealed` says whether the user has + // interacted yet. The first key press reveals the cursor where it is + // (nothing pre-marked on a fresh page, first arrow doesn't skip a photo). + const [cursor, setCursor] = useState(0) + const [revealed, setRevealed] = useState(false) const live = useRef({}) - live.current = { cursor, photos, onOpen, onToggleSelect, actions } + live.current = { cursor, revealed, photos, onOpen, onToggleSelect, actions } useEffect(() => { if (externalIndex >= 0) { setCursor(externalIndex) - setCursorVisible(true) + setRevealed(true) } }, [externalIndex]) - useEffect(() => { - if (keyboard && cursor < 0 && photos.length > 0) setCursor(0) - }, [keyboard, cursor, photos.length]) - useEffect(() => { if (cursor >= photos.length) setCursor(Math.max(0, photos.length - 1)) }, [cursor, photos.length]) @@ -58,37 +55,48 @@ export default function Gallery({ if (e.metaKey || e.ctrlKey || e.altKey) return const s = live.current if (s.photos.length === 0) return - const photo = s.cursor >= 0 ? s.photos[s.cursor] : null + const photo = s.photos[s.cursor] const el = containerRef.current const columns = el ? getComputedStyle(el).gridTemplateColumns.split(' ').length : 1 const count = s.photos.length let next = null - if (e.key === 'ArrowRight') next = Math.min(count - 1, Math.max(0, s.cursor + 1)) + if (e.key === 'ArrowRight') next = Math.min(count - 1, s.cursor + 1) else if (e.key === 'ArrowLeft') next = Math.max(0, s.cursor - 1) - else if (e.key === 'ArrowDown') next = s.cursor < 0 ? 0 : Math.min(count - 1, s.cursor + columns) - else if (e.key === 'ArrowUp') next = s.cursor < 0 ? 0 : Math.max(0, s.cursor - columns) + else if (e.key === 'ArrowDown') next = Math.min(count - 1, s.cursor + columns) + else if (e.key === 'ArrowUp') next = Math.max(0, s.cursor - columns) else if (e.key === ' ') { if (photo) { e.preventDefault() + setRevealed(true) s.onOpen?.(s.cursor) } return } else if (e.key === 'Enter') { if (photo && s.onToggleSelect) { e.preventDefault() + setRevealed(true) s.onToggleSelect(photo.id) } return } else { const key = e.key.toLowerCase() const action = s.actions?.find((a) => a.keys.includes(key)) - if (action && photo) action.run(photo, key) + if (action && photo) { + setRevealed(true) + action.run(photo, key) + } return } e.preventDefault() - setCursor(next) - setCursorVisible(true) - el?.children[next]?.scrollIntoView({ block: 'nearest' }) + setRevealed(true) + // The first arrow press only reveals the cursor; movement starts with + // the second. + if (s.revealed) { + setCursor(next) + el?.children[next]?.scrollIntoView({ block: 'nearest' }) + } else { + el?.children[s.cursor]?.scrollIntoView({ block: 'nearest' }) + } } window.addEventListener('keydown', handler) return () => window.removeEventListener('keydown', handler) @@ -101,7 +109,7 @@ export default function Gallery({ return (