import { useMemo } from 'react' ; import { format } from './serializer' ; import { SDKError } from '../utils/error' ; export type SearchSort = 'price_desc' | 'price_asc' | 'orders_desc' | 'name_desc' | 'name_asc' | 'release_desc' | 'discount_desc' | 'score_desc' ; export interface Facet { key : string ; value : string ; } export interface State { description sort : SearchSort ; description selectedFacets : Facet [ ] ; description term : string | null ; description base : string ; description page : number ; } const sortKeys = new Set < SearchSort > ( [ 'price_desc' , 'price_asc' , 'orders_desc' , 'name_desc' , 'name_asc' , 'release_desc' , 'discount_desc' , 'score_desc' , ] ) ; export const initialize = ( { sort = 'score_desc' , selectedFacets = [ ] , term = null , base = '/' , page = 0 , } = { } ) => ( { sort , selectedFacets , term , base : base . endsWith ( '/' ) ? base : `${ base }/` , page , } ) ; const isSearchSort = ( x ) : x is SearchSort => sortKeys . has ( x as any ) ; type Action = { type : 'setSort' ; payload : SearchSort ; } | { type : 'setTerm' ; payload : string | null ; } | { type : 'setPage' ; payload : number ; } | { type : 'setFacet' ; payload : { facet : Facet ; unique : boolean ; } ; } | { type : 'removeFacet' ; payload : Facet ; } | { type : 'toggleFacet' ; payload : Facet ; } | { type : 'toggleFacets' ; payload : Facet [ ] ; } ; const equals = ( s1 , s2 ) => format ( s1 ) . href === format ( s2 ) . href ; const removeFacet = ( state , facet ) : State => { const { value } = facet ; const index = state . selectedFacets . findIndex ( ( x ) => x . value === value ) ; if ( index < 0 ) { throw new SDKError ( `Cannot remove ${ value } from search params` ) ; } return { ... state , selectedFacets : state . selectedFacets . filter ( ( _ , it ) => it === 0 || it !== index ) , } ; } ; export const setFacet = ( state , facet , unique ) : State => { if ( unique === true ) { const index = state . selectedFacets . findIndex ( ( f ) => f . key === facet . key ) ; if ( index > - 1 ) { return { ... state , selectedFacets : state . selectedFacets . map ( ( f , it ) => it === index ? facet : f ) , } ; } } return { ... state , selectedFacets : [ ... state . selectedFacets , facet ] , } ; } ; const toggleFacet = ( state , item ) => { const found = state . selectedFacets . find ( ( facet ) => facet . key === item . key && facet . value === item . value ) ; if ( found !== undefined ) { return removeFacet ( state , item ) ; } return setFacet ( state , item , false ) ; } ; const toggleFacets = ( state , items ) => items . reduce ( ( item , s ) => toggleFacet ( item , s ) , state ) ; export const reducer = ( state , action ) => { switch ( action . type ) { case 'setSort' : if ( ! isSearchSort ( action . payload ) ) { throw new SDKError ( `Sort param ${ action . payload } is unknown` ) ; } return state . sort === action . payload ? state : { ... state , sort : action . payload , } ; case 'setTerm' : return state . term === action . payload ? state : { ... state , term : action . payload , } ; case 'setPage' : return state . page === action . payload ? state : { ... state , page : action . payload , } ; case 'setFacet' : return setFacet ( state , action . payload . facet , action . payload . unique ) ; case 'removeFacet' : return removeFacet ( state , action . payload ) ; case 'toggleFacet' : return toggleFacet ( state , action . payload ) ; case 'toggleFacets' : return toggleFacets ( state , action . payload ) ; default : throw new SDKError ( `Uknown action of search state machine` ) ; } } ; const dispatcher = ( onChange , state ) => ( action ) => { const newState = reducer ( state , action ) ; if ( ! equals ( newState , state ) ) { onChange ( format ( newState ) ) ; } } ; export const useSearchState = ( initialState , onChange ) => { const state = useMemo ( ( ) => initialize ( initialState ) , [ initialState ] ) ; return useMemo ( ( ) => { const dispatch = dispatcher ( onChange , state ) ; return { state , setSort : ( sort ) => dispatch ( { type : 'setSort' , payload : sort } ) , setTerm : ( term ) => dispatch ( { type : 'setTerm' , payload : term } ) , setPage : ( page ) => dispatch ( { type : 'setPage' , payload : page } ) , setFacet : ( facet , unique = false ) => dispatch ( { type : 'setFacet' , payload : { facet , unique } } ) , removeFacet : ( facet ) => dispatch ( { type : 'removeFacet' , payload : facet } ) , toggleFacet : ( facet ) => dispatch ( { type : 'toggleFacet' , payload : facet , } ) , toggleFacets : ( facets ) => dispatch ( { type : 'toggleFacets' , payload : facets } ) , } ; } , [ onChange , state ] ) ; } ; export type UseSearchState = ReturnType < typeof useSearchState > ;