Skip to content

Commit

Permalink
Move search state to the main store; improve UX
Browse files Browse the repository at this point in the history
I remove `react-virtuoso` because sometimes it does not show results. I've updated it and ran into even more issues (runtime errors). Dropped it. Crossing fingers (lol) it's not gonna break too much.

I update `truncate` so that it does truncate node ids nicely.
  • Loading branch information
mondoreale committed Mar 28, 2024
1 parent 89bb426 commit 00259ed
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 118 deletions.
57 changes: 0 additions & 57 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
"react-router-dom": "^6.22.3",
"react-scripts": "^5.0.1",
"react-spring": "^9.0.0",
"react-virtuoso": "^2.2.2",
"recharts": "^2.0.9",
"streamr-client-react": "^3.2.0",
"styled-components": "^6.1.8",
Expand Down
28 changes: 24 additions & 4 deletions src/Store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,21 @@ import { useGlobalKeyDownEffect, useStreamIdParam } from './hooks'
import { useDebounced } from './hooks/wrapCallback'
import { ActiveView, OperatorNode } from './types'
import { useOperatorNodesForStreamQuery } from './utils/nodes'
import { truncate } from './utils/text'

interface Store {
activeView: ActiveView
displaySearchPhrase: string
invalidateLocationParamKey(): void
invalidateNodeIdParamKey(): void
locationParamKey: number
mapRef: RefObject<MapRef>
nodeIdParamkey: number
nodeIdParamKey: number
resetViewport(): void
searchPhrase: string
selectedNode: OperatorNode | null
setActiveView(value: ActiveView): void
setSearchPhrase(value: string): void
setViewport(fn: (viewport: ViewportProps) => ViewportProps): void
setViewportDebounced(fn: (viewport: ViewportProps) => ViewportProps): void
viewport: ViewportProps
Expand All @@ -51,14 +55,17 @@ const defaultViewport: ViewportProps = {

const StoreContext = createContext<Store>({
activeView: ActiveView.Map,
displaySearchPhrase: '',
invalidateLocationParamKey: () => {},
invalidateNodeIdParamKey: () => {},
locationParamKey: -1,
mapRef: { current: null },
nodeIdParamkey: -1,
nodeIdParamKey: -1,
resetViewport: () => {},
searchPhrase: '',
selectedNode: null,
setActiveView: () => {},
setSearchPhrase: () => {},
setViewport: () => {},
setViewportDebounced: () => {},
viewport: defaultViewport,
Expand All @@ -74,7 +81,7 @@ export function StoreProvider({ mapRef, ...props }: StoreProviderProps) {

const [locationParamKey, invalidateLocationParamKey] = useReducer((x: number) => x + 1, 0)

const [nodeIdParamkey, invalidateNodeIdParamKey] = useReducer((x: number) => x + 1, 0)
const [nodeIdParamKey, invalidateNodeIdParamKey] = useReducer((x: number) => x + 1, 0)

const [viewport, setViewport] = useState(defaultViewport)

Expand All @@ -93,19 +100,32 @@ export function StoreProvider({ mapRef, ...props }: StoreProviderProps) {

const [activeView, setActiveView] = useState<ActiveView>(ActiveView.Map)

const [rawSearchPhrase, setRawSearchPhrase] = useState('')

const [displaySearchPhrase, setDisplaySearchPhrase] = useState('')

const setSearchPhrase = useCallback((value: string) => {
setRawSearchPhrase(value)

setDisplaySearchPhrase(truncate(value))
}, [])

return (
<StoreContext.Provider
{...props}
value={{
activeView,
displaySearchPhrase,
invalidateLocationParamKey,
invalidateNodeIdParamKey,
locationParamKey,
mapRef,
nodeIdParamkey,
nodeIdParamKey,
resetViewport,
searchPhrase: rawSearchPhrase,
selectedNode,
setActiveView,
setSearchPhrase,
setViewport,
setViewportDebounced,
viewport,
Expand Down
10 changes: 9 additions & 1 deletion src/components/NodeList/NodeListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export const NodeListItem = ({
}
}, [isActive])

const { mapRef } = useStore()
const { mapRef, invalidateNodeIdParamKey } = useStore()

return (
<NodeElement
Expand All @@ -180,6 +180,14 @@ export const NodeListItem = ({
<TitleRow
onClick={() => {
onClick?.(nodeId)

/**
* If the page address includes the node id already and the user
* panned away then the above won't be sufficient to get node's location
* back into viewport. To address it, we have to invalidate the node id
* param key.
*/
invalidateNodeIdParamKey()
}}
onMouseEnter={() => {
if (highlightPointOnHover) {
Expand Down
38 changes: 31 additions & 7 deletions src/components/SearchBox/SearchInput.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
/* eslint-disable jsx-a11y/label-has-associated-control */
import React, { ChangeEvent, InputHTMLAttributes, RefObject, useState } from 'react'
import styled from 'styled-components'

import uniqueId from 'lodash/uniqueId'
import React, { ChangeEvent, FocusEvent, InputHTMLAttributes, RefObject, useState } from 'react'
import styled from 'styled-components'
import { MD, SANS, SM } from '../../utils/styled'

export const SearchInputInner = styled.div`
Expand Down Expand Up @@ -161,18 +160,23 @@ const ClearIcon = () => (
)

interface SearchInputProps
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'id' | 'type' | 'autoComplete'> {
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'id' | 'type' | 'autoComplete' | 'value'> {
displayValue?: string
inputRef: RefObject<HTMLInputElement>
onClearButtonClick?(): void
value?: string
}

const UnstyledSearchInput = ({
inputRef,
className,
displayValue = '',
inputRef,
onBlur: onBlurProp,
onChange: onChangeProp,
onClearButtonClick,
onFocus: onFocusProp,
placeholder = 'Search Streamr Network',
value = '',
onChange: onChangeProp,
...props
}: SearchInputProps) => {
const inputId = useState(uniqueId('input-'))[0]
Expand All @@ -181,6 +185,24 @@ const UnstyledSearchInput = ({
onChangeProp?.(e)
}

const [focused, setFocused] = useState(false)

function onFocus(e: FocusEvent<HTMLInputElement>) {
onFocusProp?.(e)

setFocused(true)

setTimeout(() => {
inputRef.current?.setSelectionRange(0, value.length)
})
}

function onBlur(e: FocusEvent<HTMLInputElement>) {
onBlurProp?.(e)

setFocused(false)
}

return (
<div className={className}>
<SearchInputInner>
Expand All @@ -191,13 +213,15 @@ const UnstyledSearchInput = ({
</Logo>
<Input
{...props}
onFocus={onFocus}
onBlur={onBlur}
ref={inputRef}
autoComplete="off"
id={inputId}
onChange={onChange}
placeholder={placeholder}
type="text"
value={value}
value={focused ? value : displayValue}
/>
<ButtonWrapper>
{value ? (
Expand Down
30 changes: 8 additions & 22 deletions src/components/SearchBox/SearchResults.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import React from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { Virtuoso } from 'react-virtuoso'
import styled from 'styled-components'
import { useStore } from '../../Store'
import { useNavigateToNodeCallback } from '../../hooks'
import { SearchResultItem } from '../../types'
import { getNodeLocationId, setNodeFeatureState } from '../../utils/map'
import { MD, SANS, SM } from '../../utils/styled'
import Highlight from '../Highlight'
import { LocationIcon, NodeIcon, StreamIcon } from './Icons'
import { useNavigateToNodeCallback } from '../../hooks'

const IconWrapper = styled.div`
display: flex;
Expand Down Expand Up @@ -107,17 +106,6 @@ const Details = styled.div`
}
`

const List = styled.div`
display: grid;
a,
a:hover,
a:active,
a:visited {
text-decoration: none;
}
`

type Props = {
results: SearchResultItem[]
highlight?: string
Expand All @@ -127,12 +115,9 @@ type Props = {
export function SearchResults({ results, highlight, onItemClick, ...props }: Props) {
return (
<SearchResultsRoot {...props}>
<Virtuoso
data={results}
itemContent={(_, result) => (
<Item value={result} highlight={highlight} onClick={onItemClick} />
)}
/>
{results.map((value) => (
<Item key={value.payload.id} value={value} highlight={highlight} onClick={onItemClick} />
))}
</SearchResultsRoot>
)
}
Expand Down Expand Up @@ -228,10 +213,11 @@ function Item({ highlight, value, onClick }: ItemProps) {
}

export const SearchResultsRoot = styled.div`
max-height: 512px;
overflow: auto;
@media (max-width: ${SM}px) {
${List} {
grid-row-gap: 8px;
}
max-height: 336px;
${Row} {
border: 1px solid #efefef;
Expand Down
Loading

0 comments on commit 00259ed

Please sign in to comment.