mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-25 04:24:43 -04:00
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:
parent
d6eabae1b1
commit
003658dc4d
5 changed files with 87 additions and 37 deletions
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
} }>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue