Store ModList filter states in redux
So that navigating between cells and back from other pages will persist the filters.
This commit is contained in:
parent
0b85e30043
commit
68796f6f9c
@ -10,6 +10,15 @@ import type { Mod } from "./CellData";
|
|||||||
import type { File } from "../slices/plugins";
|
import type { File } from "../slices/plugins";
|
||||||
import { formatBytes } from "../lib/plugins";
|
import { formatBytes } from "../lib/plugins";
|
||||||
import { jsonFetcher } from "../lib/api";
|
import { jsonFetcher } from "../lib/api";
|
||||||
|
import {
|
||||||
|
ModWithCounts,
|
||||||
|
setSortBy,
|
||||||
|
setSortAsc,
|
||||||
|
setFilter,
|
||||||
|
setCategory,
|
||||||
|
setIncludeTranslations,
|
||||||
|
} from "../slices/modListFilters";
|
||||||
|
import { useAppDispatch, useAppSelector } from "../lib/hooks";
|
||||||
|
|
||||||
const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition";
|
const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition";
|
||||||
|
|
||||||
@ -19,19 +28,10 @@ type Props = {
|
|||||||
counts: Record<number, [number, number, number]> | null;
|
counts: Record<number, [number, number, number]> | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ModWithCounts = Mod & {
|
|
||||||
total_downloads: number;
|
|
||||||
unique_downloads: number;
|
|
||||||
views: number;
|
|
||||||
exterior_cells_edited: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ModList: React.FC<Props> = ({ mods, files, counts }) => {
|
const ModList: React.FC<Props> = ({ mods, files, counts }) => {
|
||||||
const [includeTranslations, setIncludeTranslations] = useState(true);
|
const dispatch = useAppDispatch();
|
||||||
const [sortBy, setSortBy] = useState<keyof ModWithCounts>("unique_downloads");
|
const { sortBy, sortAsc, filter, category, includeTranslations } =
|
||||||
const [sortAsc, setSortAsc] = useState<boolean>(false);
|
useAppSelector((state) => state.modListFilters);
|
||||||
const [filter, setFilter] = useState<string>("");
|
|
||||||
const [category, setCategory] = useState<string>("All");
|
|
||||||
const [filterResults, setFilterResults] = useState<Set<number>>(new Set());
|
const [filterResults, setFilterResults] = useState<Set<number>>(new Set());
|
||||||
|
|
||||||
const { data: cellCounts, error: cellCountsError } = useSWRImmutable(
|
const { data: cellCounts, error: cellCountsError } = useSWRImmutable(
|
||||||
@ -99,7 +99,9 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (modSearch.current) {
|
if (modSearch.current) {
|
||||||
setFilterResults(
|
setFilterResults(
|
||||||
new Set(modSearch.current.search(filter).map((result) => result.id))
|
new Set(
|
||||||
|
modSearch.current.search(filter ?? "").map((result) => result.id)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [filter]);
|
}, [filter]);
|
||||||
@ -118,7 +120,7 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
|
|||||||
className={styles["sort-by"]}
|
className={styles["sort-by"]}
|
||||||
value={sortBy}
|
value={sortBy}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
setSortBy(event.target.value as keyof ModWithCounts)
|
dispatch(setSortBy(event.target.value as keyof ModWithCounts))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<option value="name">Name</option>
|
<option value="name">Name</option>
|
||||||
@ -137,7 +139,7 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
|
|||||||
<button
|
<button
|
||||||
title="Sort ascending"
|
title="Sort ascending"
|
||||||
className={sortAsc ? styles.active : ""}
|
className={sortAsc ? styles.active : ""}
|
||||||
onClick={() => setSortAsc(true)}
|
onClick={() => dispatch(setSortAsc(true))}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Sort ascending"
|
alt="Sort ascending"
|
||||||
@ -152,7 +154,7 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
|
|||||||
<button
|
<button
|
||||||
title="Sort descending"
|
title="Sort descending"
|
||||||
className={!sortAsc ? styles.active : ""}
|
className={!sortAsc ? styles.active : ""}
|
||||||
onClick={() => setSortAsc(false)}
|
onClick={() => dispatch(setSortAsc(false))}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="Sort descending"
|
alt="Sort descending"
|
||||||
@ -173,7 +175,7 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
|
|||||||
id="filter"
|
id="filter"
|
||||||
className={styles.filter}
|
className={styles.filter}
|
||||||
value={filter}
|
value={filter}
|
||||||
onChange={(event) => setFilter(event.target.value)}
|
onChange={(event) => dispatch(setFilter(event.target.value))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["filter-row"]}>
|
<div className={styles["filter-row"]}>
|
||||||
@ -183,7 +185,7 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
|
|||||||
id="category"
|
id="category"
|
||||||
className={styles["category"]}
|
className={styles["category"]}
|
||||||
value={category}
|
value={category}
|
||||||
onChange={(event) => setCategory(event.target.value)}
|
onChange={(event) => dispatch(setCategory(event.target.value))}
|
||||||
>
|
>
|
||||||
<option value="All">All</option>
|
<option value="All">All</option>
|
||||||
{(
|
{(
|
||||||
@ -211,7 +213,9 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
|
|||||||
id="include-translations"
|
id="include-translations"
|
||||||
className={styles["include-translations"]}
|
className={styles["include-translations"]}
|
||||||
checked={includeTranslations}
|
checked={includeTranslations}
|
||||||
onChange={() => setIncludeTranslations(!includeTranslations)}
|
onChange={() =>
|
||||||
|
dispatch(setIncludeTranslations(!includeTranslations))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -2,10 +2,11 @@ import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit"
|
|||||||
|
|
||||||
import pluginsReducer from "../slices/plugins"
|
import pluginsReducer from "../slices/plugins"
|
||||||
import pluginsTxtReducer from "../slices/pluginsTxt"
|
import pluginsTxtReducer from "../slices/pluginsTxt"
|
||||||
|
import modListFiltersReducer from "../slices/modListFilters"
|
||||||
|
|
||||||
export function makeStore() {
|
export function makeStore() {
|
||||||
return configureStore({
|
return configureStore({
|
||||||
reducer: { pluginsTxt: pluginsTxtReducer, plugins: pluginsReducer },
|
reducer: { pluginsTxt: pluginsTxtReducer, plugins: pluginsReducer, modListFilters: modListFiltersReducer },
|
||||||
middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false }),
|
middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false }),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
59
slices/modListFilters.ts
Normal file
59
slices/modListFilters.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit"
|
||||||
|
|
||||||
|
import type { AppState, AppThunk } from "../lib/store"
|
||||||
|
import type { Mod } from '../components/CellData';
|
||||||
|
|
||||||
|
export type ModWithCounts = Mod & {
|
||||||
|
total_downloads: number;
|
||||||
|
unique_downloads: number;
|
||||||
|
views: number;
|
||||||
|
exterior_cells_edited: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ModListFiltersState = {
|
||||||
|
sortBy: keyof ModWithCounts,
|
||||||
|
sortAsc: boolean,
|
||||||
|
filter?: string,
|
||||||
|
category: string,
|
||||||
|
includeTranslations: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState: ModListFiltersState = {
|
||||||
|
sortBy: "unique_downloads",
|
||||||
|
sortAsc: false,
|
||||||
|
filter: undefined,
|
||||||
|
category: "All",
|
||||||
|
includeTranslations: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const modListFiltersSlice = createSlice({
|
||||||
|
name: "modListFilters",
|
||||||
|
initialState,
|
||||||
|
reducers: {
|
||||||
|
setSortBy: (state, action: PayloadAction<keyof ModWithCounts>) => ({
|
||||||
|
...state,
|
||||||
|
sortBy: action.payload,
|
||||||
|
}),
|
||||||
|
setSortAsc: (state, action: PayloadAction<boolean>) => ({
|
||||||
|
...state,
|
||||||
|
sortAsc: action.payload,
|
||||||
|
}),
|
||||||
|
setFilter: (state, action: PayloadAction<string | undefined>) => ({
|
||||||
|
...state,
|
||||||
|
filter: action.payload,
|
||||||
|
}),
|
||||||
|
setCategory: (state, action: PayloadAction<string>) => ({
|
||||||
|
...state,
|
||||||
|
category: action.payload,
|
||||||
|
}),
|
||||||
|
setIncludeTranslations: (state, action: PayloadAction<boolean>) => ({
|
||||||
|
...state,
|
||||||
|
includeTranslations: action.payload,
|
||||||
|
}),
|
||||||
|
clearFilters: () => initialState,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const { setSortBy, setSortAsc, setFilter, setCategory, setIncludeTranslations, clearFilters } = modListFiltersSlice.actions
|
||||||
|
|
||||||
|
export default modListFiltersSlice.reducer
|
Loading…
Reference in New Issue
Block a user