@promoboxx/use-filter 1.11.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/main.yml +38 -0
- package/.vscode/settings.json +3 -0
- package/CHANGELOG.md +185 -0
- package/Makefile +25 -0
- package/eslint.config.js +30 -0
- package/mise.toml +3 -0
- package/package.json +33 -43
- package/prettier.config.js +3 -0
- package/src/lib/buildDefaultFilterInfo.ts +36 -0
- package/src/lib/getOffsetFromPage.ts +5 -0
- package/src/lib/getPageFromOffset.ts +5 -0
- package/src/lib/shallowEqual.test.ts +71 -0
- package/src/lib/shallowEqual.ts +26 -0
- package/src/store/index.ts +30 -0
- package/src/store/localStorageStore.ts +36 -0
- package/src/store/memoryStore.ts +27 -0
- package/src/store/reduxHelpers/createActions.test.ts +32 -0
- package/src/store/reduxHelpers/createActions.ts +56 -0
- package/src/store/reduxHelpers/createReducer.test.ts +65 -0
- package/src/store/reduxHelpers/createReducer.ts +47 -0
- package/src/store/reduxStore.ts +78 -0
- package/src/store/urlParamStore.test.ts +131 -0
- package/src/store/urlParamStore.ts +85 -0
- package/src/useFilter.test.tsx +822 -0
- package/src/useFilter.ts +524 -0
- package/src/useSimpleFilter.test.tsx +676 -0
- package/src/useSimpleFilter.ts +397 -0
- package/src/vitest-env.d.ts +1 -0
- package/tsconfig.json +76 -0
- package/tsdown.config.ts +30 -0
- package/vite.config.ts +9 -0
- package/dist/lib/buildDefaultFilterInfo.d.ts +0 -3
- package/dist/lib/buildDefaultFilterInfo.js +0 -35
- package/dist/lib/getOffsetFromPage.d.ts +0 -2
- package/dist/lib/getOffsetFromPage.js +0 -6
- package/dist/lib/getPageFromOffset.d.ts +0 -2
- package/dist/lib/getPageFromOffset.js +0 -6
- package/dist/lib/shallowEqual.d.ts +0 -2
- package/dist/lib/shallowEqual.js +0 -23
- package/dist/store/index.d.ts +0 -10
- package/dist/store/index.js +0 -16
- package/dist/store/localStorageStore.d.ts +0 -3
- package/dist/store/localStorageStore.js +0 -31
- package/dist/store/memoryStore.d.ts +0 -3
- package/dist/store/memoryStore.js +0 -23
- package/dist/store/reduxHelpers/createActions.d.ts +0 -16
- package/dist/store/reduxHelpers/createActions.js +0 -27
- package/dist/store/reduxHelpers/createReducer.d.ts +0 -8
- package/dist/store/reduxHelpers/createReducer.js +0 -26
- package/dist/store/reduxStore.d.ts +0 -15
- package/dist/store/reduxStore.js +0 -67
- package/dist/store/urlParamStore.d.ts +0 -4
- package/dist/store/urlParamStore.js +0 -91
- package/dist/useFilter.d.ts +0 -103
- package/dist/useFilter.js +0 -254
- package/dist/useSimpleFilter.d.ts +0 -86
- package/dist/useSimpleFilter.js +0 -173
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
interface CreateActions {
|
|
2
|
+
<Namespace extends string, PayloadCreatorMap extends ReduxPayloadCreatorMap>(
|
|
3
|
+
namespace: Namespace,
|
|
4
|
+
actions: PayloadCreatorMap,
|
|
5
|
+
): ReduxActionCreatorMap<Namespace, PayloadCreatorMap>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
type ReduxPayloadCreatorMap = Record<string, (...args: any[]) => any>
|
|
9
|
+
|
|
10
|
+
export type ReduxActionCreatorMap<
|
|
11
|
+
Namespace extends string,
|
|
12
|
+
PayloadCreatorMap extends ReduxPayloadCreatorMap,
|
|
13
|
+
> = {
|
|
14
|
+
[Type in keyof PayloadCreatorMap]: Type extends string
|
|
15
|
+
? ReduxActionCreator<Namespace, Type, PayloadCreatorMap[Type]>
|
|
16
|
+
: undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Provide defaults for types in here cause it's used a few places and subbing
|
|
20
|
+
// in the defaults is long and repetitive.
|
|
21
|
+
export interface ReduxActionCreator<
|
|
22
|
+
Namespace extends string = string,
|
|
23
|
+
Type extends string = string,
|
|
24
|
+
Fn extends (...args: any[]) => any = (...args: any[]) => any,
|
|
25
|
+
> {
|
|
26
|
+
(...args: Parameters<Fn>): {
|
|
27
|
+
type: `${Namespace}/${Type}`
|
|
28
|
+
payload: ReturnType<Fn>
|
|
29
|
+
}
|
|
30
|
+
actionType: `${Namespace}/${Type}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const createActions: CreateActions = (
|
|
34
|
+
namespace: string,
|
|
35
|
+
payloadCreatorMap: ReduxPayloadCreatorMap,
|
|
36
|
+
) => {
|
|
37
|
+
const actionCreators: Record<string, ReduxActionCreator> = {}
|
|
38
|
+
|
|
39
|
+
for (const key in payloadCreatorMap) {
|
|
40
|
+
actionCreators[key] = ((...args: any[]) => ({
|
|
41
|
+
type: `${namespace}/${key}`,
|
|
42
|
+
payload: payloadCreatorMap[key](...args),
|
|
43
|
+
|
|
44
|
+
// Need to cast here because there's no way to assign a function + extra
|
|
45
|
+
// attributes in one go.
|
|
46
|
+
})) as any
|
|
47
|
+
|
|
48
|
+
actionCreators[key].actionType = `${namespace}/${key}`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// TODO There's probably a better type to use here, but it's not super
|
|
52
|
+
// important since `CreateActions` already specifies one.
|
|
53
|
+
return actionCreators as any
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default createActions
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import createActions from './createActions'
|
|
2
|
+
import createReducer from './createReducer'
|
|
3
|
+
|
|
4
|
+
const initialState = {
|
|
5
|
+
number: 0,
|
|
6
|
+
string: '',
|
|
7
|
+
foo: '',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe('createReducer', () => {
|
|
11
|
+
it('uses a builder pattern', () => {
|
|
12
|
+
const actions = createActions('test', {
|
|
13
|
+
action1: () => undefined,
|
|
14
|
+
action2: () => 'asdf',
|
|
15
|
+
action3: (arg1: number, arg2: string) => ({ arg1, arg2 }),
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const reducer = createReducer(initialState, (builder) => {
|
|
19
|
+
builder
|
|
20
|
+
.addHandler(actions.action2, (state, action) => ({
|
|
21
|
+
...state,
|
|
22
|
+
string: action.payload,
|
|
23
|
+
}))
|
|
24
|
+
.addHandler(actions.action3, (state, action) => ({
|
|
25
|
+
...state,
|
|
26
|
+
number: action.payload.arg1,
|
|
27
|
+
string: action.payload.arg2,
|
|
28
|
+
}))
|
|
29
|
+
.addCase('foo', (state, action: ReturnType<typeof actions.action1>) => {
|
|
30
|
+
return {
|
|
31
|
+
...state,
|
|
32
|
+
foo: 'updated',
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
let state = reducer(undefined, { type: '' })
|
|
38
|
+
expect(state).toEqual({
|
|
39
|
+
number: 0,
|
|
40
|
+
string: '',
|
|
41
|
+
foo: '',
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
state = reducer(state, actions.action2())
|
|
45
|
+
expect(state).toEqual({
|
|
46
|
+
number: 0,
|
|
47
|
+
string: 'asdf',
|
|
48
|
+
foo: '',
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
state = reducer(state, actions.action3(42, 'foo'))
|
|
52
|
+
expect(state).toEqual({
|
|
53
|
+
number: 42,
|
|
54
|
+
string: 'foo',
|
|
55
|
+
foo: '',
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
state = reducer(state, { type: 'foo' })
|
|
59
|
+
expect(state).toEqual({
|
|
60
|
+
number: 42,
|
|
61
|
+
string: 'foo',
|
|
62
|
+
foo: 'updated',
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Action } from 'redux'
|
|
2
|
+
|
|
3
|
+
import type { ReduxActionCreator } from './createActions'
|
|
4
|
+
|
|
5
|
+
interface ReduxReducerBuilder<State> {
|
|
6
|
+
addHandler: <ActionCreator extends ReduxActionCreator>(
|
|
7
|
+
actionCreator: ActionCreator,
|
|
8
|
+
handler: (state: State, action: ReturnType<ActionCreator>) => State,
|
|
9
|
+
) => ReduxReducerBuilder<State>
|
|
10
|
+
|
|
11
|
+
addCase: <HandlerAction extends Action>(
|
|
12
|
+
actionType: string,
|
|
13
|
+
handler: (state: State, action: HandlerAction) => State,
|
|
14
|
+
) => ReduxReducerBuilder<State>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function createReducer<State>(
|
|
18
|
+
initialState: State,
|
|
19
|
+
builderFn: (builder: ReduxReducerBuilder<State>) => void,
|
|
20
|
+
) {
|
|
21
|
+
const handlers: Record<string, (state: State, action: Action) => State> = {}
|
|
22
|
+
|
|
23
|
+
const builder: ReduxReducerBuilder<State> = {
|
|
24
|
+
addHandler: (actionCreator, handler) => {
|
|
25
|
+
// TODO There's probably a better type to use here.
|
|
26
|
+
handlers[actionCreator.actionType] = handler as any
|
|
27
|
+
return builder
|
|
28
|
+
},
|
|
29
|
+
addCase: (actionType, handler) => {
|
|
30
|
+
// TODO There's probably a better type to use here.
|
|
31
|
+
handlers[actionType] = handler as any
|
|
32
|
+
return builder
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
builderFn(builder)
|
|
37
|
+
|
|
38
|
+
return (state = initialState, action: Action) => {
|
|
39
|
+
if (handlers[action.type]) {
|
|
40
|
+
return handlers[action.type](state, action)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return state
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default createReducer
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Store } from 'redux'
|
|
2
|
+
|
|
3
|
+
import type { FilterInfo } from '../useFilter'
|
|
4
|
+
|
|
5
|
+
import type { FilterStore } from '.'
|
|
6
|
+
import createActions from './reduxHelpers/createActions'
|
|
7
|
+
import createReducer from './reduxHelpers/createReducer'
|
|
8
|
+
|
|
9
|
+
interface CreateReduxStoreConfig {
|
|
10
|
+
store: Store<{ useFilter: UseFilterReduxState }>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createReduxStore(config: CreateReduxStoreConfig) {
|
|
14
|
+
const reduxStoreStore: FilterStore = {
|
|
15
|
+
getFilter: (namespace) => {
|
|
16
|
+
return config.store.getState().useFilter.filters[namespace]
|
|
17
|
+
},
|
|
18
|
+
saveFilter: (namespace, filter) => {
|
|
19
|
+
config.store.dispatch(actions.saveFilter(namespace, filter))
|
|
20
|
+
},
|
|
21
|
+
getData: (namespace) => {
|
|
22
|
+
return config.store.getState().useFilter.data[namespace]
|
|
23
|
+
},
|
|
24
|
+
saveData: (namespace, data) => {
|
|
25
|
+
config.store.dispatch(actions.saveData(namespace, data))
|
|
26
|
+
},
|
|
27
|
+
clear: () => {
|
|
28
|
+
config.store.dispatch(actions.clear())
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return reduxStoreStore
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const actions = createActions('useFilter', {
|
|
36
|
+
saveData: (namespace: string, data: any) => ({
|
|
37
|
+
namespace,
|
|
38
|
+
data,
|
|
39
|
+
}),
|
|
40
|
+
saveFilter: (namespace: string, filter: FilterInfo<any>) => ({
|
|
41
|
+
namespace,
|
|
42
|
+
filter,
|
|
43
|
+
}),
|
|
44
|
+
clear: () => undefined,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
interface UseFilterReduxState {
|
|
48
|
+
data: Record<string, any>
|
|
49
|
+
filters: Record<string, FilterInfo<any>>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const initialState: UseFilterReduxState = {
|
|
53
|
+
data: {},
|
|
54
|
+
filters: {},
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const useFilterReduxReducer = createReducer(initialState, (builder) => {
|
|
58
|
+
builder
|
|
59
|
+
.addHandler(actions.saveFilter, (state, action) => ({
|
|
60
|
+
...state,
|
|
61
|
+
filters: {
|
|
62
|
+
...state.filters,
|
|
63
|
+
[action.payload.namespace]: action.payload.filter,
|
|
64
|
+
},
|
|
65
|
+
}))
|
|
66
|
+
.addHandler(actions.saveData, (state, action) => ({
|
|
67
|
+
...state,
|
|
68
|
+
data: {
|
|
69
|
+
...state.data,
|
|
70
|
+
[action.payload.namespace]: action.payload.data,
|
|
71
|
+
},
|
|
72
|
+
}))
|
|
73
|
+
.addHandler(actions.clear, (state) => ({
|
|
74
|
+
...state,
|
|
75
|
+
filters: {},
|
|
76
|
+
data: {},
|
|
77
|
+
}))
|
|
78
|
+
})
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import qs from 'qs'
|
|
2
|
+
|
|
3
|
+
import buildDefaultFilterInfo from '../lib/buildDefaultFilterInfo'
|
|
4
|
+
|
|
5
|
+
import { createUrlParamStore, replaceQueryParams } from './urlParamStore'
|
|
6
|
+
|
|
7
|
+
const FILTER_NAME = 'test'
|
|
8
|
+
|
|
9
|
+
describe('urlParamStore', () => {
|
|
10
|
+
it('reads params from the url', () => {
|
|
11
|
+
const urlParamStore = createUrlParamStore()
|
|
12
|
+
const originalFilterInfo = buildDefaultFilterInfo({
|
|
13
|
+
filter: {
|
|
14
|
+
foo: 'bar',
|
|
15
|
+
baz: 'qux',
|
|
16
|
+
},
|
|
17
|
+
page: 20,
|
|
18
|
+
pageSize: 5,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// Nothing has been saved, so `getFilter` should return nothing.
|
|
22
|
+
expect(urlParamStore.getFilter(FILTER_NAME)).toBeFalsy()
|
|
23
|
+
|
|
24
|
+
// We need some params to actually test, so calling `.saveFilter` sets this
|
|
25
|
+
// up for us.
|
|
26
|
+
urlParamStore.saveFilter(FILTER_NAME, originalFilterInfo)
|
|
27
|
+
|
|
28
|
+
// Now we can see what we get back.
|
|
29
|
+
const parsedFilterInfo = urlParamStore.getFilter(FILTER_NAME)
|
|
30
|
+
|
|
31
|
+
expect(parsedFilterInfo).not.toBeFalsy()
|
|
32
|
+
|
|
33
|
+
// We save a subset of the original filterInfo, so we just need to check
|
|
34
|
+
// that the original contains whatever we've parsed.
|
|
35
|
+
expect(originalFilterInfo).toMatchObject(parsedFilterInfo!)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('updates url params on save', () => {
|
|
39
|
+
const urlParamStore = createUrlParamStore()
|
|
40
|
+
urlParamStore.saveFilter(
|
|
41
|
+
FILTER_NAME,
|
|
42
|
+
buildDefaultFilterInfo({ filter: { foo: 'bar' } }),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const newUrl = window.location.toString()
|
|
46
|
+
expect(newUrl).toContain(encodeURI(`filter.test[foo]=bar`))
|
|
47
|
+
expect(newUrl).toContain(encodeURI(`info.test[pageSize]=20`))
|
|
48
|
+
expect(newUrl).toContain(encodeURI(`info.test[offset]=0`))
|
|
49
|
+
expect(newUrl).toContain(encodeURI(`info.test[page]=1`))
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('supports .clear()', () => {
|
|
53
|
+
const urlParamStore = createUrlParamStore()
|
|
54
|
+
const originalFilterInfo = buildDefaultFilterInfo({
|
|
55
|
+
filter: {
|
|
56
|
+
foo: 'bar',
|
|
57
|
+
baz: 'qux',
|
|
58
|
+
},
|
|
59
|
+
page: 20,
|
|
60
|
+
pageSize: 5,
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// We need some params to actually test, so calling `.saveFilter` sets this
|
|
64
|
+
// up for us.
|
|
65
|
+
urlParamStore.saveFilter(FILTER_NAME, originalFilterInfo)
|
|
66
|
+
|
|
67
|
+
// Since we've saved the filter, there should be query params.
|
|
68
|
+
expect(window.location.search).toBeTruthy()
|
|
69
|
+
|
|
70
|
+
// And when we clear it, it should be empty.
|
|
71
|
+
urlParamStore.clear()
|
|
72
|
+
expect(window.location.search).toBeFalsy()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('reads a valid filter with incomplete filter info from url', () => {
|
|
76
|
+
const urlParamStore = createUrlParamStore()
|
|
77
|
+
const originalFilterInfo = {
|
|
78
|
+
filter: {
|
|
79
|
+
foo: 'bar',
|
|
80
|
+
},
|
|
81
|
+
info: undefined,
|
|
82
|
+
}
|
|
83
|
+
const { filter, ...info } = originalFilterInfo
|
|
84
|
+
replaceQueryParams(
|
|
85
|
+
qs.stringify({
|
|
86
|
+
[`filter.${FILTER_NAME}`]: filter,
|
|
87
|
+
[`info.${FILTER_NAME}`]: info,
|
|
88
|
+
}),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
const parsedFilterInfo = urlParamStore.getFilter(FILTER_NAME)
|
|
92
|
+
expect(parsedFilterInfo).not.toBeFalsy()
|
|
93
|
+
expect(originalFilterInfo).toEqual(parsedFilterInfo!)
|
|
94
|
+
})
|
|
95
|
+
it('reads a valid filter info with incomplete filter from url', () => {
|
|
96
|
+
const urlParamStore = createUrlParamStore()
|
|
97
|
+
const originalFilterInfo = {
|
|
98
|
+
filter: undefined,
|
|
99
|
+
page: 2,
|
|
100
|
+
}
|
|
101
|
+
const { filter, ...info } = originalFilterInfo
|
|
102
|
+
replaceQueryParams(
|
|
103
|
+
qs.stringify({
|
|
104
|
+
[`filter.${FILTER_NAME}`]: filter,
|
|
105
|
+
[`info.${FILTER_NAME}`]: info,
|
|
106
|
+
}),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
const parsedFilterInfo = urlParamStore.getFilter(FILTER_NAME)
|
|
110
|
+
expect(parsedFilterInfo).not.toBeFalsy()
|
|
111
|
+
expect(originalFilterInfo).toEqual(parsedFilterInfo!)
|
|
112
|
+
})
|
|
113
|
+
it('reads a unrelated filter props without breaking', () => {
|
|
114
|
+
const urlParamStore = createUrlParamStore()
|
|
115
|
+
const originalFilterInfo = {
|
|
116
|
+
filter: undefined,
|
|
117
|
+
info: undefined,
|
|
118
|
+
}
|
|
119
|
+
const { filter, ...info } = originalFilterInfo
|
|
120
|
+
replaceQueryParams(
|
|
121
|
+
qs.stringify({
|
|
122
|
+
[`filter.${FILTER_NAME}`]: filter,
|
|
123
|
+
[`info.${FILTER_NAME}`]: info,
|
|
124
|
+
someBogusProp: 'foo',
|
|
125
|
+
}),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
const parsedFilterInfo = urlParamStore.getFilter(FILTER_NAME)
|
|
129
|
+
expect(parsedFilterInfo).toBeFalsy()
|
|
130
|
+
})
|
|
131
|
+
})
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import qs from 'qs'
|
|
2
|
+
|
|
3
|
+
import type { FilterStore } from './index'
|
|
4
|
+
|
|
5
|
+
export const createUrlParamStore = () => {
|
|
6
|
+
const urlParamStore: FilterStore = {
|
|
7
|
+
getFilter(namespace) {
|
|
8
|
+
const parsed = naivelyParseExistingParams()
|
|
9
|
+
|
|
10
|
+
const parsedInfo = parsed[`info.${namespace}`]
|
|
11
|
+
const parsedFilter = parsed[`filter.${namespace}`]
|
|
12
|
+
|
|
13
|
+
// useFilter doesn't really support returning a partial FilterInfo, so both
|
|
14
|
+
// need to be present,
|
|
15
|
+
if (parsedInfo || parsedFilter) {
|
|
16
|
+
const { page, offset, pageSize, ...rest } = parsedInfo || {}
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
...{
|
|
20
|
+
// cast string to number or undefined
|
|
21
|
+
page: 1 / page ? +page : undefined,
|
|
22
|
+
pageSize: 1 / pageSize ? +pageSize : undefined,
|
|
23
|
+
offset: 1 / offset ? +offset : undefined,
|
|
24
|
+
...rest,
|
|
25
|
+
},
|
|
26
|
+
filter: parsedFilter,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
saveFilter(namespace, filterInfo) {
|
|
32
|
+
const parsed = naivelyParseExistingParams()
|
|
33
|
+
|
|
34
|
+
// Setting the params to the equivalent of { [namespace]: filterInfo }
|
|
35
|
+
// takes up more room. The idea is that this ...
|
|
36
|
+
// 'filter.filterName%5BfilterValueKey%5D=filterValueValue'
|
|
37
|
+
// ... is smaller than ...
|
|
38
|
+
// filterName%5Bfilter%5D%5BfilterValueKey%5D=filterValueValue
|
|
39
|
+
// ... by 5 characters per value in a filter. So excuse us for a second ...
|
|
40
|
+
const clonedInfo: Partial<typeof filterInfo> = { ...filterInfo }
|
|
41
|
+
const clonedFilter = filterInfo.filter
|
|
42
|
+
delete clonedInfo.filter
|
|
43
|
+
delete clonedInfo.lastRefreshAt
|
|
44
|
+
delete clonedInfo.shouldRunImmediately
|
|
45
|
+
delete clonedInfo.totalResults
|
|
46
|
+
delete clonedInfo.totalPages
|
|
47
|
+
delete clonedInfo.nextCursor
|
|
48
|
+
parsed[`filter.${namespace}`] = clonedFilter
|
|
49
|
+
parsed[`info.${namespace}`] = clonedInfo
|
|
50
|
+
|
|
51
|
+
replaceQueryParams(qs.stringify(parsed))
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
clear() {
|
|
55
|
+
const parsed = naivelyParseExistingParams()
|
|
56
|
+
|
|
57
|
+
for (const key in parsed) {
|
|
58
|
+
if (key.startsWith('filter.') || key.startsWith('info.')) {
|
|
59
|
+
delete parsed[key]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
replaceQueryParams(qs.stringify(parsed))
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
getData() {
|
|
67
|
+
return undefined
|
|
68
|
+
},
|
|
69
|
+
saveData() {},
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return urlParamStore
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function replaceQueryParams(newParams: string) {
|
|
76
|
+
const nextUrl = new URL(window.location.toString())
|
|
77
|
+
nextUrl.search = newParams
|
|
78
|
+
window.history.replaceState(undefined, '', nextUrl.toString())
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function naivelyParseExistingParams() {
|
|
82
|
+
// Don't really want to do any casting, especially not to `any`, but the type
|
|
83
|
+
// of `qs.parse` is so wide we'd have to litter checks all over the place.
|
|
84
|
+
return qs.parse(window.location.search.substring(1)) as any
|
|
85
|
+
}
|