The History PR: II - Add URL params (#1157)

* Add location state dependency

Signed-off-by: Erik Michelson <github@erik.michelson.eu>

* Split toolbar state into single location states

Signed-off-by: Erik Michelson <github@erik.michelson.eu>

* Add CHANGELOG entry

Signed-off-by: Erik Michelson <github@erik.michelson.eu>

* Pin dependency

Signed-off-by: Erik Michelson <github@erik.michelson.eu>

* Use react state for view because of side-effects

The locationState was resetted on each search or tags filter update because these updates pushed a change to the location thus resulting in loss of the location state for the view.

Signed-off-by: Erik Michelson <github@erik.michelson.eu>

* Remove unneeded import

Signed-off-by: Erik Michelson <github@erik.michelson.eu>

* Change CHANGELOG entry

Signed-off-by: Erik Michelson <github@erik.michelson.eu>

* Removed unnecessary typecast

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2021-04-24 23:25:01 +02:00 committed by GitHub
parent d6eabae1b1
commit 003658dc4d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 37 deletions

View file

@ -10,7 +10,7 @@ import { Trans, useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { ApplicationState } from '../../redux'
import { HistoryContent } from './history-content/history-content'
import { HistoryToolbar, HistoryToolbarState, initState as toolbarInitState } from './history-toolbar/history-toolbar'
import { HistoryToolbar, HistoryToolbarState, initToolbarState } from './history-toolbar/history-toolbar'
import { sortAndFilterEntries } from './utils'
import { refreshHistoryState } from '../../redux/history/methods'
import { HistoryEntry } from '../../redux/history/types'
@ -20,7 +20,7 @@ export const HistoryPage: React.FC = () => {
const { t } = useTranslation()
const allEntries = useSelector((state: ApplicationState) => state.history)
const [toolbarState, setToolbarState] = useState<HistoryToolbarState>(toolbarInitState)
const [toolbarState, setToolbarState] = useState<HistoryToolbarState>(initToolbarState)
const entriesToShow = useMemo<HistoryEntry[]>(() =>
sortAndFilterEntries(allEntries, toolbarState),

View file

@ -4,11 +4,13 @@
SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'
import equal from 'fast-deep-equal'
import React, { ChangeEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Button, Form, FormControl, InputGroup, ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
import { Typeahead } from 'react-bootstrap-typeahead'
import { Trans, useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { useQueryState } from 'react-router-use-location-state'
import { ApplicationState } from '../../../redux'
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
import { ShowIf } from '../../common/show-if/show-if'
@ -21,16 +23,21 @@ import { HistoryEntryOrigin } from '../../../redux/history/types'
import { importHistoryEntries, refreshHistoryState, setHistoryEntries } from '../../../redux/history/methods'
import { showErrorNotification } from '../../../redux/ui-notifications/methods'
export type HistoryToolbarChange = (settings: HistoryToolbarState) => void;
export type HistoryToolbarChange = (newState: HistoryToolbarState) => void;
export interface HistoryToolbarState {
viewState: ViewStateEnum
interface ToolbarSortState {
titleSortDirection: SortModeEnum
lastVisitedSortDirection: SortModeEnum
}
interface ToolbarFilterState {
viewState: ViewStateEnum
keywordSearch: string
selectedTags: string[]
}
export type HistoryToolbarState = ToolbarSortState & ToolbarFilterState
export enum ViewStateEnum {
CARD,
TABLE
@ -40,17 +47,20 @@ export interface HistoryToolbarProps {
onSettingsChange: HistoryToolbarChange
}
export const initState: HistoryToolbarState = {
viewState: ViewStateEnum.CARD,
const initSortState: ToolbarSortState = {
titleSortDirection: SortModeEnum.no,
lastVisitedSortDirection: SortModeEnum.down,
lastVisitedSortDirection: SortModeEnum.down
}
export const initToolbarState: HistoryToolbarState = {
...initSortState,
viewState: ViewStateEnum.CARD,
keywordSearch: '',
selectedTags: []
}
export const HistoryToolbar: React.FC<HistoryToolbarProps> = ({ onSettingsChange }) => {
const { t } = useTranslation()
const [state, setState] = useState<HistoryToolbarState>(initState)
const historyEntries = useSelector((state: ApplicationState) => state.history)
const userExists = useSelector((state: ApplicationState) => !!state.user)
@ -60,33 +70,37 @@ export const HistoryToolbar: React.FC<HistoryToolbarProps> = ({ onSettingsChange
return [...new Set(allTags)]
}, [historyEntries])
const titleSortChanged = (direction: SortModeEnum) => {
setState(prevState => ({
...prevState,
titleSortDirection: direction,
lastVisitedSortDirection: SortModeEnum.no
}))
}
const previousState = useRef(initToolbarState)
const [searchState, setSearchState] = useQueryState('search', initToolbarState.keywordSearch)
const [tagsState, setTagsState] = useQueryState('tags', initToolbarState.selectedTags)
const [viewState, setViewState] = useState(initToolbarState.viewState)
const [sortState, setSortState] = useState<ToolbarSortState>(initSortState)
const lastVisitedSortChanged = (direction: SortModeEnum) => {
setState(prevState => ({
...prevState,
const titleSortChanged = useCallback((direction: SortModeEnum) => {
setSortState({
lastVisitedSortDirection: SortModeEnum.no,
titleSortDirection: direction
})
}, [setSortState])
const lastVisitedSortChanged = useCallback((direction: SortModeEnum) => {
setSortState({
lastVisitedSortDirection: direction,
titleSortDirection: SortModeEnum.no
}))
}
})
}, [setSortState])
const keywordSearchChanged = (event: ChangeEvent<HTMLInputElement>) => {
setState(prevState => ({ ...prevState, keywordSearch: event.currentTarget.value }))
}
const keywordSearchChanged = useCallback((event: ChangeEvent<HTMLInputElement>) => {
setSearchState(event.currentTarget.value ?? '')
}, [setSearchState])
const toggleViewChanged = (newViewState: ViewStateEnum) => {
setState((prevState) => ({ ...prevState, viewState: newViewState }))
}
const toggleViewChanged = useCallback((newViewState: ViewStateEnum) => {
setViewState(newViewState)
}, [setViewState])
const selectedTagsChanged = (selected: string[]) => {
setState(prevState => ({ ...prevState, selectedTags: selected }))
}
const selectedTagsChanged = useCallback((selected: string[]) => {
setTagsState(selected)
}, [setTagsState])
const refreshHistory = useCallback(() => {
refreshHistoryState()
@ -116,30 +130,45 @@ export const HistoryToolbar: React.FC<HistoryToolbarProps> = ({ onSettingsChange
}, [userExists, historyEntries, t, refreshHistory])
useEffect(() => {
onSettingsChange(state)
}, [onSettingsChange, state])
const newState: HistoryToolbarState = {
selectedTags: tagsState,
keywordSearch: searchState,
viewState: viewState,
...sortState
}
// This is needed because the onSettingsChange triggers a state update in history-page which re-renders the toolbar.
// The re-rendering causes this effect to run again resulting in an infinite state update loop.
if (equal(previousState.current, newState)) {
return
}
onSettingsChange(newState)
previousState.current = newState
}, [onSettingsChange, tagsState, searchState, viewState, sortState])
return (
<Form inline={ true }>
<InputGroup className={ 'mr-1 mb-1' }>
<Typeahead id={ 'tagsSelection' } options={ tags } multiple={ true }
placeholder={ t('landing.history.toolbar.selectTags') }
onChange={ selectedTagsChanged }/>
onChange={ selectedTagsChanged }
selected={ tagsState }
/>
</InputGroup>
<InputGroup className={ 'mr-1 mb-1' }>
<FormControl
placeholder={ t('landing.history.toolbar.searchKeywords') }
aria-label={ t('landing.history.toolbar.searchKeywords') }
onChange={ keywordSearchChanged }
value={ searchState }
/>
</InputGroup>
<InputGroup className={ 'mr-1 mb-1' }>
<SortButton onDirectionChange={ titleSortChanged } direction={ state.titleSortDirection }
<SortButton onDirectionChange={ titleSortChanged } direction={ sortState.titleSortDirection }
variant={ 'light' }><Trans
i18nKey={ 'landing.history.toolbar.sortByTitle' }/></SortButton>
</InputGroup>
<InputGroup className={ 'mr-1 mb-1' }>
<SortButton onDirectionChange={ lastVisitedSortChanged } direction={ state.lastVisitedSortDirection }
<SortButton onDirectionChange={ lastVisitedSortChanged } direction={ sortState.lastVisitedSortDirection }
variant={ 'light' }><Trans i18nKey={ 'landing.history.toolbar.sortByLastVisited' }/></SortButton>
</InputGroup>
<InputGroup className={ 'mr-1 mb-1' }>
@ -164,7 +193,7 @@ export const HistoryToolbar: React.FC<HistoryToolbarProps> = ({ onSettingsChange
</InputGroup>
</ShowIf>
<InputGroup className={ 'mr-1 mb-1' }>
<ToggleButtonGroup type="radio" name="options" dir="ltr" value={ state.viewState } className={ 'button-height' }
<ToggleButtonGroup type="radio" name="options" dir="ltr" value={ viewState } className={ 'button-height' }
onChange={ (newViewState: ViewStateEnum) => {
toggleViewChanged(newViewState)
} }>