From efa372ca119e7fc75dd747fe6055b26866089a31 Mon Sep 17 00:00:00 2001 From: Zak Nesler Date: Sat, 18 May 2024 21:31:52 -0400 Subject: [PATCH] add entry list arrow navigation (#29) * add arrow nav to entry list * wip --- ui/src/components/entry/entry-item.tsx | 1 + ui/src/components/entry/entry-list.tsx | 27 ++++++++++++++----- ui/src/components/entry/entry-panel.tsx | 4 +-- ui/src/hooks/use-list-nav.ts | 36 ++++++++++++------------- ui/src/hooks/use-viewport.ts | 12 ++++----- ui/src/routes/feed.tsx | 6 ++--- 6 files changed, 50 insertions(+), 36 deletions(-) diff --git a/ui/src/components/entry/entry-item.tsx b/ui/src/components/entry/entry-item.tsx index a30d3059..278da076 100644 --- a/ui/src/components/entry/entry-item.tsx +++ b/ui/src/components/entry/entry-item.tsx @@ -33,6 +33,7 @@ export const EntryItem: Component = props => { return ( ; @@ -28,8 +29,9 @@ export const EntryList: Component = props => { const [localFeedKey, setLocalFeedKey] = createSignal(filter.getFeedUrl()); createEffect(() => { - const bottomOfListVisible = - props.containerBounds?.bottom && listBounds.bottom && listBounds.bottom <= props.containerBounds?.bottom; + if (!listBounds.bottom || !props.containerBounds?.bottom) return; + + const bottomOfListVisible = listBounds.bottom <= props.containerBounds.bottom; if (!bottomOfListVisible) return; entries.fetchMore(); @@ -46,6 +48,21 @@ export const EntryList: Component = props => { setLocalEntries([...localEntries(), ...newEntries].sort(getEntryComparator(filter.getSort()))); }); + createEffect(() => { + if (!filter.params.entry_uuid || !props.containerBounds?.bottom) return; + + const activeItem = document.querySelector(`[data-entry-item-uuid="${filter.params.entry_uuid}"]`); + if (!(activeItem instanceof HTMLElement)) return; + + const bounds = activeItem.getBoundingClientRect(); + const containerBottom = props.containerBounds.bottom; + + const nearBounds = bounds.top <= containerBottom * 0.25 || bounds.bottom >= containerBottom * 0.9; + if (!nearBounds) return; + + activeItem.scrollIntoView({ block: 'center' }); + }); + createEffect(() => { const feedKey = filter.getFeedUrl(); @@ -57,11 +74,7 @@ export const EntryList: Component = props => { setLocalEntries(entries.getAllEntries()); }); - // useListNav(() => ({ - // entries: entries.data || [], - // current_entry_uuid: props.current_entry_uuid, - // getUrl, - // })); + useListNav(() => ({ entries: localEntries() })); return ( diff --git a/ui/src/components/entry/entry-panel.tsx b/ui/src/components/entry/entry-panel.tsx index 4c9820ed..463de366 100644 --- a/ui/src/components/entry/entry-panel.tsx +++ b/ui/src/components/entry/entry-panel.tsx @@ -14,7 +14,7 @@ import { Spinner } from '../ui/spinner'; export const EntryPanel = () => { const filter = useFilterParams(); const queryClient = useQueryClient(); - const { aboveBreakpoint } = useViewport(); + const { gtBreakpoint } = useViewport(); const entry = createQuery(() => ({ enabled: !!filter.params.entry_uuid, @@ -42,7 +42,7 @@ export const EntryPanel = () => { diff --git a/ui/src/hooks/use-list-nav.ts b/ui/src/hooks/use-list-nav.ts index 9998d4be..862c79c3 100644 --- a/ui/src/hooks/use-list-nav.ts +++ b/ui/src/hooks/use-list-nav.ts @@ -2,44 +2,44 @@ import { useNavigate } from '@solidjs/router'; import { createEffect } from 'solid-js'; import type { Entry } from '~/types/bindings'; import { useKeyDownEvent } from '@solid-primitives/keyboard'; +import { useFilterParams } from './use-filter-params'; +import { debounce } from '@solid-primitives/scheduled'; +import { useViewport } from './use-viewport'; type UseListNavParams = { entries: Entry[]; - current_entry_uuid?: string; - getUrl: (uuid: string) => string; }; export const useListNav = (params: () => UseListNavParams) => { + const filter = useFilterParams(); const keyDownEvent = useKeyDownEvent(); const navigate = useNavigate(); + const viewport = useViewport(); - createEffect(() => { - if (!params().entries.length) return; - - const e = keyDownEvent(); - if (!e) return; + const maybeNavigate = debounce((direction: 'up' | 'down') => { + const currentIndex = params().entries.findIndex(entry => entry.uuid === filter.params.entry_uuid); - const maybeNavigate = (direction: 'up' | 'down') => { - e.preventDefault(); + const offset = direction === 'up' ? -1 : 1; + const entry = params().entries[currentIndex + offset]; + if (!entry) return; - const currentIndex = params().entries.findIndex(entry => entry.uuid === params().current_entry_uuid); - if (currentIndex === -1) return; + navigate(filter.getEntryUrl(entry.uuid)); + }, 30); - const offset = direction === 'up' ? -1 : 1; - const entry = params().entries[currentIndex + offset]; - if (!entry) return; - - console.log('navigate to', entry.uuid); + createEffect(() => { + if (!params().entries.length || viewport.lteBreakpoint('md')) return; - navigate(params().getUrl(entry.uuid)); - }; + const e = keyDownEvent(); + if (!e) return; switch (e.key) { case 'ArrowDown': + e.preventDefault(); maybeNavigate('down'); break; case 'ArrowUp': + e.preventDefault(); maybeNavigate('up'); break; } diff --git a/ui/src/hooks/use-viewport.ts b/ui/src/hooks/use-viewport.ts index 8595f814..b72013ca 100644 --- a/ui/src/hooks/use-viewport.ts +++ b/ui/src/hooks/use-viewport.ts @@ -1,18 +1,18 @@ import { screens } from '~/constants/screens'; -import { createWindowSize } from '@solid-primitives/resize-observer'; +import { useWindowSize } from '@solid-primitives/resize-observer'; export type ScreenSize = keyof typeof screens; export const getBreakpoint = (screen: ScreenSize) => +screens[screen].replace('px', ''); export const useViewport = () => { - const size = createWindowSize(); + const size = useWindowSize(); - const belowBreakpoint = (screen: ScreenSize) => size.width <= getBreakpoint(screen); - const aboveBreakpoint = (screen: ScreenSize) => size.width > getBreakpoint(screen); + const lteBreakpoint = (screen: ScreenSize) => size.width <= getBreakpoint(screen); + const gtBreakpoint = (screen: ScreenSize) => size.width > getBreakpoint(screen); return { size, - belowBreakpoint, - aboveBreakpoint, + lteBreakpoint, + gtBreakpoint, }; }; diff --git a/ui/src/routes/feed.tsx b/ui/src/routes/feed.tsx index f6e6086d..eca605d4 100644 --- a/ui/src/routes/feed.tsx +++ b/ui/src/routes/feed.tsx @@ -19,7 +19,7 @@ import { MenuFeeds } from '~/components/menus/menu-feeds'; export default () => { const filter = useFilterParams(); const isRouting = useIsRouting(); - const { belowBreakpoint } = useViewport(); + const { lteBreakpoint } = useViewport(); const [_showFeeds, setShowFeeds] = createSignal(false); const [allFeedsMenuOpen, setAllFeedsMenuOpen] = createSignal(false); @@ -41,9 +41,9 @@ export default () => { const viewingEntry = () => !!filter.params.entry_uuid; - const isMobile = () => belowBreakpoint('md'); + const isMobile = () => lteBreakpoint('md'); const showPanel = () => !isMobile() || (isMobile() && !viewingEntry()); - const showFeeds = () => belowBreakpoint('xl') && _showFeeds(); + const showFeeds = () => lteBreakpoint('xl') && _showFeeds(); return ( <>