From 768ff76c76cdf75ad4deb7657e9737672868b9d5 Mon Sep 17 00:00:00 2001 From: Max Frederiksen Date: Thu, 31 Oct 2024 11:14:38 +0100 Subject: [PATCH] CSSTUDIO-2728: Add keyboard navigation of entries --- .../SearchResultList/SearchResultList.jsx | 69 +++++++++++++++---- src/beta/components/search/SearchResults.js | 11 --- src/hooks/useKeyPress.js | 28 ++++++++ 3 files changed, 84 insertions(+), 24 deletions(-) create mode 100644 src/hooks/useKeyPress.js diff --git a/src/beta/components/search/SearchResultList/SearchResultList.jsx b/src/beta/components/search/SearchResultList/SearchResultList.jsx index 9da0a2a..29453d1 100644 --- a/src/beta/components/search/SearchResultList/SearchResultList.jsx +++ b/src/beta/components/search/SearchResultList/SearchResultList.jsx @@ -1,16 +1,27 @@ -import React from "react"; +import React, { useEffect } from "react"; import { Divider, Stack, styled } from "@mui/material"; import { getLogEntryGroupId } from "components/Properties"; import { SearchResultGroupItem } from "./SearchResultGroupItem/SearchResultGroupItem"; import { SearchResultSingleItem } from "./SearchResultSingleItem"; import { sortByCreatedDate } from "components/log/sort"; +import { useKeyPress } from "hooks/useKeyPress"; +import { updateCurrentLogEntry, useCurrentLogEntry } from "features/currentLogEntryReducer"; +import { useDispatch } from "react-redux"; +import useBetaNavigate from "hooks/useBetaNavigate"; -export const SearchResultList = styled(({logs, dateDescending, selectedId, onRowClick, className}) => { +export const SearchResultList = styled(({ logs, dateDescending, className }) => { + const dispatch = useDispatch(); + const navigate = useBetaNavigate(); + + const currentLogEntry = useCurrentLogEntry(); + const currentLogEntryId = Number(currentLogEntry?.id); + const arrowUpPressed = useKeyPress('ArrowUp'); + const arrowDownPressed = useKeyPress('ArrowDown'); const removeSubsequentReplies = (logs) => { const visitedGroups = [] return logs.reduce((res, log) => { - if(log.groupId && visitedGroups.includes(log.groupId)) { + if (log.groupId && visitedGroups.includes(log.groupId)) { return [...res]; } else { visitedGroups.push(log.groupId); @@ -18,26 +29,58 @@ export const SearchResultList = styled(({logs, dateDescending, selectedId, onRow } }, []); } + const logsAndReplies = logs.map(log => ({ + ...log, + groupId: getLogEntryGroupId(log.properties) + })).toSorted(sortByCreatedDate(dateDescending)) - const transformedLogs = removeSubsequentReplies( - logs.map(log => ({ - ...log, - groupId: getLogEntryGroupId(log.properties) - })) - .toSorted(sortByCreatedDate(dateDescending)) + const logsNoReplies = removeSubsequentReplies( + logsAndReplies ); + const navigateToEntry = (log) => { + dispatch(updateCurrentLogEntry(log)); + navigate(`/logs/${log.id}`); + } + + const keyboardNavigate = (nextEntryId) => { + const nextEntry = logsAndReplies.find(log => log.id === nextEntryId); + if (nextEntry) { + navigateToEntry(nextEntry); + } + } + + const setDefaultLogEntry = () => { + if (!currentLogEntryId) { + dispatch(updateCurrentLogEntry(logsNoReplies[0])); + } + } + + useEffect(() => { + if (arrowUpPressed) { + setDefaultLogEntry(); + keyboardNavigate(currentLogEntryId + 1); + } + }, [arrowUpPressed]); + + useEffect(() => { + if (arrowDownPressed) { + setDefaultLogEntry(); + keyboardNavigate(currentLogEntryId - 1); + } + }, [arrowDownPressed]); + return ( } > - {transformedLogs?.map(log => { - if(log.groupId) { - return + {logsNoReplies?.map(log => { + if (log.groupId) { + return } else { - return + return } })} diff --git a/src/beta/components/search/SearchResults.js b/src/beta/components/search/SearchResults.js index 7003dcc..5fa019f 100644 --- a/src/beta/components/search/SearchResults.js +++ b/src/beta/components/search/SearchResults.js @@ -1,7 +1,6 @@ import { Alert, Badge, Box, IconButton, LinearProgress, Stack, TablePagination, Typography, styled } from "@mui/material"; import { ologApi, removeEmptyKeys } from "api/ologApi"; import customization from "config/customization"; -import { updateCurrentLogEntry, useCurrentLogEntry } from "features/currentLogEntryReducer"; import { updateSearchPageParams, useSearchPageParams } from "features/searchPageParamsReducer"; import { updateSearchParams, useSearchParams } from "features/searchParamsReducer"; import React, { useEffect, useMemo, useState } from "react"; @@ -14,15 +13,12 @@ import { SearchParamsBadges } from "./SearchParamsBadges"; import { AdvancedSearchDrawer } from "./SearchResultList/AdvancedSearchDrawer"; import { useAdvancedSearch } from "features/advancedSearchReducer"; import { withCacheBust } from "hooks/useSanitizedSearchParams"; -import useBetaNavigate from "hooks/useBetaNavigate"; export const SearchResults = styled(({ className }) => { const dispatch = useDispatch(); - const navigate = useBetaNavigate(); const { active: advancedSearchActive, fieldCount: advancedSearchFieldCount } = useAdvancedSearch(); - const currentLogEntry = useCurrentLogEntry(); const searchParams = useSearchParams(); const searchPageParams = useSearchPageParams(); const rowsPerPageOptions = customization.defaultRowsPerPageOptions; @@ -86,11 +82,6 @@ export const SearchResults = styled(({ className }) => { const count = searchResults?.hitCount ?? 0; - const onRowClick = (log) => { - dispatch(updateCurrentLogEntry(log)); - navigate(`/logs/${log.id}`); - } - const onPageChange = (event, page) => { setPage(page); }; @@ -159,8 +150,6 @@ export const SearchResults = styled(({ className }) => { ? : No records found diff --git a/src/hooks/useKeyPress.js b/src/hooks/useKeyPress.js new file mode 100644 index 0000000..4ba8236 --- /dev/null +++ b/src/hooks/useKeyPress.js @@ -0,0 +1,28 @@ +import { useEffect, useState } from "react"; +export const useKeyPress = (targetKey) => { + const [keyPressed, setKeyPressed] = useState(false); + + useEffect(() => { + const downHandler = ({ key }) => { + if (key === targetKey) { + setKeyPressed(true); + } + }; + + const upHandler = ({ key }) => { + if (key === targetKey) { + setKeyPressed(false); + } + }; + + window.addEventListener('keydown', downHandler); + window.addEventListener('keyup', upHandler); + + return () => { + window.removeEventListener('keydown', downHandler); + window.removeEventListener('keyup', upHandler); + }; + }, [targetKey]); + + return keyPressed; +}; \ No newline at end of file