Skip to content

Commit

Permalink
💄 Use MUI for HomeView
Browse files Browse the repository at this point in the history
  • Loading branch information
0x46616c6b committed Oct 28, 2022
1 parent 646443f commit 3116663
Show file tree
Hide file tree
Showing 8 changed files with 583 additions and 527 deletions.
163 changes: 40 additions & 123 deletions src/components/ticker/LocationSearch.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import React, {
FC,
MouseEvent,
useCallback,
useEffect,
useReducer,
useRef,
} from 'react'
import { Search, SearchProps } from 'semantic-ui-react'
import React, { FC, useRef, useState } from 'react'
import { Autocomplete, TextField } from '@mui/material'

interface SearchResult {
place_id: number
Expand All @@ -21,136 +14,60 @@ export interface Result {
lon: number
}

interface State {
loading: boolean
results: Result[]
value: string
}

const initialState: State = {
loading: false,
results: [],
value: '',
}

enum SearchActionType {
CLEAN_QUERY = 'CLEAN_QUERY',
START_SEARCH = 'START_SEARCH',
FINISH_SEARCH = 'FINISH_SEARCH',
UPDATE_SELECTION = 'UPDATE_SELECTION',
}

interface SearchAction {
type: SearchActionType
query?: string
results?: Result[]
selection?: string
}

async function api(value: string): Promise<SearchResult[]> {
async function api(
value: string,
signal: AbortSignal
): Promise<SearchResult[]> {
const url =
'https://nominatim.openstreetmap.org/search?format=json&limit=5&q=' + value

const timeout = (time: number) => {
const controller = new AbortController()
setTimeout(() => controller.abort(), time * 1000)
return controller
}

return fetch(url, { signal: timeout(30).signal }).then(res => res.json())
}

function searchReducer(state: any, action: SearchAction) {
switch (action.type) {
case SearchActionType.CLEAN_QUERY:
return initialState
case SearchActionType.START_SEARCH:
return { ...state, loading: true, value: action.query, results: [] }
case SearchActionType.FINISH_SEARCH:
return { ...state, loading: false, results: action.results }
case SearchActionType.UPDATE_SELECTION:
return { ...state, value: action.selection }

default:
throw new Error()
}
return fetch(url, { signal }).then(res => res.json())
}

interface Props {
callback: (result: Result) => void
}

const LocationSearch: FC<Props> = props => {
const [state, dispatch] = useReducer(searchReducer, initialState)
const { loading, results, value } = state
const timeoutRef: { current: NodeJS.Timeout | null } = useRef(null)

const handleResultSelect = useCallback(
(event: MouseEvent, data: any) => {
dispatch({
type: SearchActionType.UPDATE_SELECTION,
selection: data.result.title,
})

props.callback(data.result)
},
[props]
)

const handleSearchChange = useCallback(
(event: MouseEvent, data: SearchProps) => {
const value = data.value
if (value === undefined) {
return
}
const LocationSearch: FC<Props> = ({ callback }) => {
const [options, setOptions] = useState<SearchResult[]>([])
const previousController = useRef<AbortController>()

clearTimeout(timeoutRef.current as NodeJS.Timeout)
dispatch({ type: SearchActionType.START_SEARCH, query: value })
const handleInputChange = (event: React.SyntheticEvent, value: string) => {
if (previousController.current) {
previousController.current.abort()
}

timeoutRef.current = setTimeout(() => {
if (value.length === 0) {
dispatch({ type: SearchActionType.CLEAN_QUERY })
return
}
const controller = new AbortController()
const signal = controller.signal
previousController.current = controller

const results: Result[] = []
api(value)
.then(data => {
data.forEach(entry => {
results.push({
title: entry.display_name,
lat: entry.lat,
lon: entry.lon,
})
})
})
.finally(() => {
dispatch({
type: SearchActionType.FINISH_SEARCH,
results: results,
})
})
}, 300)
},
[]
)
api(value, signal)
.then(options => setOptions(options))
.catch(() => {
// We ignore the error
})
}

useEffect(() => {
return () => {
clearTimeout(timeoutRef.current as NodeJS.Timeout)
const handleChange = (
event: React.SyntheticEvent,
value: SearchResult | null
) => {
if (value) {
callback({ title: value?.display_name, lat: value?.lat, lon: value?.lon })
}
}, [])
}

return (
<React.Fragment>
<Search
loading={loading}
onResultSelect={handleResultSelect}
onSearchChange={handleSearchChange}
results={results}
value={value}
/>
</React.Fragment>
<Autocomplete
fullWidth
getOptionLabel={option => option.display_name}
onChange={handleChange}
onInputChange={handleInputChange}
options={options}
renderInput={params => (
<TextField {...params} label="Location" variant="outlined" />
)}
/>
)
}

Expand Down
Loading

0 comments on commit 3116663

Please sign in to comment.