Skip to content

Commit

Permalink
Merge branch 'persist-memo-filter-with-url-query'
Browse files Browse the repository at this point in the history
  • Loading branch information
chriscurrycc committed Dec 29, 2024
2 parents 1b2a484 + db5aa94 commit 402879a
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 10 deletions.
6 changes: 5 additions & 1 deletion web/src/layouts/RootLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import clsx from "clsx";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import { Suspense, useEffect, useState } from "react";
import { Outlet, useLocation } from "react-router-dom";
import { usePrevious } from "react-use";
import useLocalStorage from "react-use/lib/useLocalStorage";
import Navigation from "@/components/Navigation";
import useCurrentUser from "@/hooks/useCurrentUser";
Expand All @@ -21,6 +22,7 @@ const RootLayout = () => {
const memoFilterStore = useMemoFilterStore();
const [collapsed, setCollapsed] = useLocalStorage<boolean>("navigation-collapsed", false);
const [initialized, setInitialized] = useState(false);
const prevPathname = usePrevious(location.pathname);

useEffect(() => {
if (!currentUser) {
Expand All @@ -34,7 +36,9 @@ const RootLayout = () => {

useEffect(() => {
// When the route changes, remove all filters.
memoFilterStore.removeFilter(() => true);
if (prevPathname && prevPathname !== location.pathname) {
memoFilterStore.removeFilter(() => true);
}
}, [location.pathname]);

return !initialized ? (
Expand Down
101 changes: 92 additions & 9 deletions web/src/store/v1/memoFilter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { uniqBy } from "lodash-es";
import { create } from "zustand";
import { combine } from "zustand/middleware";
import { persist, createJSONStorage, StateStorage } from "zustand/middleware";

export type FilterFactor =
| "tagSearch"
Expand All @@ -16,20 +16,103 @@ export interface MemoFilter {
value: string;
}

const getFilterQueryString = (filters: MemoFilter[]) => {
return filters.map(({ factor, value }) => `${factor}:${encodeURIComponent(value)}`).join(",");
};

const getFiltersFromQueryString = (queryString: string) => {
return queryString.split(",").map((str) => {
const [factor, value] = str.split(":");
return {
factor: factor as FilterFactor,
value: decodeURIComponent(value),
};
});
};

const replaceState = (searchParams: URLSearchParams) => {
window.history.replaceState(null, "", `${window.location.pathname}${searchParams.toString() ? "?" + searchParams.toString() : ""}`);
};

export const getMemoFilterKey = (filter: MemoFilter) => `${filter.factor}:${filter.value}`;

const VERSION = 0;

interface State {
filters: MemoFilter[];
orderByTimeAsc: boolean;
}

const urlQueryStorage: StateStorage = {
getItem: () => {
const searchParams = new URLSearchParams(window.location.search);
const filterQuery = searchParams.get("filter");
const orderBy = searchParams.get("orderBy");

return JSON.stringify({
state: {
filters: filterQuery ? getFiltersFromQueryString(filterQuery) : [],
orderByTimeAsc: orderBy === "asc",
} as State,
version: VERSION,
});
},
setItem: (_, newValue) => {
const { state, version } = JSON.parse(newValue);
if (!state || typeof state !== "object" || version !== VERSION) return;

const { filters, orderByTimeAsc } = state as State;
const searchParams = new URLSearchParams(window.location.search);

if (filters.length > 0) {
const filterStr = getFilterQueryString(filters);
searchParams.set("filter", filterStr);
} else {
searchParams.delete("filter");
}

if (orderByTimeAsc) {
searchParams.set("orderBy", "asc");
} else {
searchParams.delete("orderBy");
}

replaceState(searchParams);
},
removeItem: () => {
const searchParams = new URLSearchParams(window.location.search);

searchParams.delete("filter");
searchParams.delete("orderBy");
replaceState(searchParams);
},
};

export const useMemoFilterStore = create(
combine(((): State => ({ filters: [], orderByTimeAsc: false }))(), (set, get) => ({
setState: (state: State) => set(state),
getState: () => get(),
getFiltersByFactor: (factor: FilterFactor) => get().filters.filter((f) => f.factor === factor),
addFilter: (filter: MemoFilter) => set((state) => ({ filters: uniqBy([...state.filters, filter], getMemoFilterKey) })),
removeFilter: (filterFn: (f: MemoFilter) => boolean) => set((state) => ({ filters: state.filters.filter((f) => !filterFn(f)) })),
setOrderByTimeAsc: (orderByTimeAsc: boolean) => set({ orderByTimeAsc }),
})),
persist<
State & {
setState: (state: State) => void;
getState: () => State;
getFiltersByFactor: (factor: FilterFactor) => MemoFilter[];
addFilter: (filter: MemoFilter) => void;
removeFilter: (filterFn: (f: MemoFilter) => boolean) => void;
setOrderByTimeAsc: (orderByTimeAsc: boolean) => void;
}
>(
(set, get) => ({
filters: [],
orderByTimeAsc: false,
setState: (state) => set(state),
getState: () => get(),
getFiltersByFactor: (factor) => get().filters.filter((f) => f.factor === factor),
addFilter: (filter) => set((state) => ({ filters: uniqBy([...state.filters, filter], getMemoFilterKey) })),
removeFilter: (filterFn) => set((state) => ({ filters: state.filters.filter((f) => !filterFn(f)) })),
setOrderByTimeAsc: (orderByTimeAsc) => set({ orderByTimeAsc }),
}),
{
name: "memo-filter-storage",
storage: createJSONStorage(() => urlQueryStorage),
version: VERSION,
},
),
);

0 comments on commit 402879a

Please sign in to comment.