Created
April 8, 2024 18:38
-
-
Save pioh/35a7eb2ecef31860510e5666ac213c06 to your computer and use it in GitHub Desktop.
QueryStore.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {action, computed, observable, reaction, toJS} from "mobx"; | |
import base64url from "base64url"; | |
import qs from "qs"; | |
import {RealtyField} from "proto"; | |
import {Provide} from "store/feature/DependencyManager"; | |
import {routerStore} from "store/RouterStore"; | |
export enum QueryParam { | |
OFFERS_LIST_VIEW_MODE = "ol", | |
OFFER_LIST_SORT_BY = "s", | |
OFFER_LIST_SORT_REVERSE = "r", | |
OFFER_LIST_PAGE = "p", | |
OFFER_LIST_COUNT_ON_PAGE = "c", | |
STATS_PANEL_FILTER_TYPE = "sp", | |
CURRENT_DRAFT = "cd", | |
FOCUS_DRAFT = "fd", | |
DEV_PPO = "dev_ppo", | |
STATS_PANEL_VIEW_TYPE = "spv", | |
} | |
export enum StatsPanelFilterType { | |
MAP = "M", | |
CLUSTER = "C", | |
ADDED = "A", | |
FAVORITE = "F", | |
} | |
export enum StatsPanelViewType { | |
SHORT = "S", | |
FULL = "F", | |
} | |
export enum OffersListViewMode { | |
CARD = "c", | |
DETAILED_LIST = "d", | |
} | |
export const defaultSearch = { | |
reportID: "", | |
from: "" as "" | "map" | "favorite", | |
hid: "", | |
secondary: false, | |
offerUrl: "", | |
bounds: "", | |
filter: "", | |
comparable: "", | |
tab: "", | |
cluster: 0, | |
calculationScroll: 0, | |
plclpsd: false, | |
prclpsd: false, | |
pbclpsd: true, | |
plsz: 300, | |
prsz: 470, | |
pbsz: 300, | |
requestId: "", | |
snapshotId: "", | |
showReports: false, | |
[QueryParam.OFFERS_LIST_VIEW_MODE]: OffersListViewMode.DETAILED_LIST, | |
[QueryParam.OFFER_LIST_SORT_BY]: RealtyField.pricePerMeter, | |
[QueryParam.OFFER_LIST_SORT_REVERSE]: true, | |
[QueryParam.OFFER_LIST_PAGE]: 1, | |
[QueryParam.OFFER_LIST_COUNT_ON_PAGE]: 0, | |
[QueryParam.STATS_PANEL_FILTER_TYPE]: StatsPanelFilterType.MAP, | |
[QueryParam.CURRENT_DRAFT]: "", | |
[QueryParam.FOCUS_DRAFT]: "", | |
[QueryParam.DEV_PPO]: 0, // as -1 | 0 | 1, | |
[QueryParam.STATS_PANEL_VIEW_TYPE]: StatsPanelViewType.SHORT, | |
}; | |
export type ISearch = typeof defaultSearch; | |
type searchField = keyof ISearch; | |
interface IQueryStoreProps { | |
dispose: (disposer: () => void) => void; | |
} | |
export class QueryStore { | |
props: IQueryStoreProps; | |
constructor(props: IQueryStoreProps) { | |
this.props = props; | |
this.setUrlToSearch(); | |
this.props.dispose( | |
reaction( | |
() => this.urlSearchString, | |
() => this.setUrlToSearch(), | |
{fireImmediately: false} | |
) | |
); | |
this.props.dispose( | |
reaction( | |
() => this.searchString, | |
() => this.setSearchToUrl() | |
) | |
); | |
Provide(this, this.props.dispose); | |
} | |
@observable search: ISearch = {...defaultSearch}; | |
@computed private get searchString() { | |
return this.searchToString(toJS(this.search)); | |
} | |
@computed private get urlSearchString() { | |
return (routerStore.location.search || "").replace(/^\??/, "?"); | |
} | |
@action bind<T extends searchField>( | |
name: T, | |
get: () => ISearch[T], | |
set: (v: ISearch[T]) => void, | |
overwriteFromUrl = false | |
): () => void { | |
let defaultVal = defaultSearch[name]; | |
let storeVal = get(); | |
if (this.search[name] === defaultVal) { | |
if (storeVal !== defaultVal) { | |
if (overwriteFromUrl) { | |
set(this.search[name]); | |
} else { | |
this.search[name] = storeVal; | |
} | |
} | |
} else { | |
if (storeVal !== this.search[name]) { | |
try { | |
set(this.search[name]); | |
} catch (e) { | |
console.error(e.stack); | |
} | |
} | |
} | |
let fromSearch = reaction( | |
() => this.search[name], | |
action((searchVal: ISearch[T]) => { | |
let storeVal = get(); | |
if (searchVal === storeVal) return; | |
try { | |
set(searchVal); | |
} catch (e) { | |
console.error(e.stack); | |
} | |
}), | |
{fireImmediately: false} | |
); | |
let fromStore = reaction( | |
() => get(), | |
action((storeVal: ISearch[T]) => { | |
let searchVal = this.search[name]; | |
if (searchVal === storeVal) return; | |
this.search[name] = storeVal; | |
}), | |
{fireImmediately: false, delay: 1000} | |
); | |
return () => { | |
fromStore(); | |
fromSearch(); | |
}; | |
} | |
@computed get ppoMode(): "force_enable" | "force_disable" | "default" { | |
let devPpo = this.search[QueryParam.DEV_PPO]; | |
switch (devPpo) { | |
case 1: | |
return "force_enable"; | |
case -1: | |
return "force_disable"; | |
default: | |
return "default"; | |
} | |
} | |
private searchToString(search: any) { | |
let omited: any = {}; | |
for (let k in search) { | |
let v = search[k]; | |
let defaultV = (defaultSearch as any)[k]; | |
if (v === defaultV && typeof defaultV !== "boolean") { | |
continue; | |
} | |
if (v === null || v === void 0) { | |
continue; | |
} | |
// console.log(JSON.stringify({k, v, defaultV})); | |
if (v === true) { | |
omited[k] = null; // key without value | |
continue; | |
} | |
if (v === false && defaultV !== true) { | |
continue; | |
} | |
omited[k] = v; | |
} | |
if (omited.offerUrl) omited.offerUrl = btoa(omited.offerUrl); | |
let url = qs.stringify(omited, {strictNullHandling: true, addQueryPrefix: true}); | |
// console.log("url", url); | |
return url; | |
} | |
searchStringWith(query: Partial<ISearch>): string { | |
return this.searchToString({ | |
...this.search, | |
...query, | |
}); | |
} | |
link(query: Partial<ISearch>, pathname = routerStore.location.pathname) { | |
return { | |
search: this.searchStringWith(query), | |
pathname, | |
}; | |
} | |
@action private setSearchToUrl() { | |
if (routerStore.location.search !== this.searchString) { | |
routerStore.replace({ | |
pathname: routerStore.location.pathname, | |
search: this.searchString, | |
}); | |
} | |
} | |
@action private setUrlToSearch() { | |
let searchString = this.urlSearchString; | |
let search = {...defaultSearch} as any; | |
let omited = qs.parse(searchString, {ignoreQueryPrefix: true, strictNullHandling: true}); | |
try { | |
if (omited.offerUrl) omited.offerUrl = base64url.decode(omited.offerUrl as string); | |
} catch (e) { | |
console.error(e.stack); | |
delete omited.offerUrl; | |
} | |
let wasSet = new Set<string>(); | |
for (let k in omited) { | |
let v = omited[k]; | |
wasSet.add(k); | |
let d = search[k]; | |
let n = (this.search as any)[k]; | |
if (typeof d === "string") { | |
if (v && v !== n) (this.search as any)[k] = v; | |
} else if (typeof d === "boolean") { | |
if (String(v) !== String(n)) { | |
(this.search as any)[k] = | |
String(v) === "true" || String(v) === "null" ? true : String(v) === "false" ? false : d; | |
} | |
// if (n !== true) (this.search as any)[k] = true; | |
} else if (typeof d === "number") { | |
if (Number.isFinite(Number(v))) { | |
let num = Number(v); | |
if (n !== num) { | |
(this.search as any)[k] = num; | |
} | |
} | |
} else if ((this.search as any)[k] !== v) { | |
(this.search as any)[k] = v; | |
} | |
} | |
for (let k in this.search as any) { | |
if (wasSet.has(k)) continue; | |
if ((this.search as any)[k] !== (defaultSearch as any)[k]) { | |
(this.search as any)[k] = (defaultSearch as any)[k]; | |
} | |
} | |
} | |
@computed get requestIdFromReportId() { | |
const result = this.search.reportID.match(/9r-(\d{6,})-RESIDENTIAL/); | |
if (result) { | |
return result[1]; | |
} | |
return ""; | |
} | |
} | |
export const queryStore = new QueryStore({dispose: () => {}}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment