Add pagination to cell and mod lists
Speeds up showing and hiding of the sidebar.
This commit is contained in:
parent
47e95f4d59
commit
5491894e00
@ -1,10 +1,13 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import MiniSearch from "minisearch";
|
import MiniSearch from "minisearch";
|
||||||
|
import ReactPaginate from "react-paginate";
|
||||||
|
|
||||||
import styles from "../styles/CellList.module.css";
|
import styles from "../styles/CellList.module.css";
|
||||||
import type { CellCoord } from "./ModData";
|
import type { CellCoord } from "./ModData";
|
||||||
|
|
||||||
|
const PAGE_SIZE = 100;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
cells: CellCoord[];
|
cells: CellCoord[];
|
||||||
};
|
};
|
||||||
@ -32,6 +35,7 @@ const CellList: React.FC<Props> = ({ cells }) => {
|
|||||||
|
|
||||||
const [filter, setFilter] = useState<string>("");
|
const [filter, setFilter] = useState<string>("");
|
||||||
const [filterResults, setFilterResults] = useState<Set<string>>(new Set());
|
const [filterResults, setFilterResults] = useState<Set<string>>(new Set());
|
||||||
|
const [page, setPage] = useState<number>(0);
|
||||||
|
|
||||||
const filteredCells = cells
|
const filteredCells = cells
|
||||||
.filter((cell) => !filter || filterResults.has(`${cell.x},${cell.y}`))
|
.filter((cell) => !filter || filterResults.has(`${cell.x},${cell.y}`))
|
||||||
@ -45,6 +49,11 @@ const CellList: React.FC<Props> = ({ cells }) => {
|
|||||||
}
|
}
|
||||||
}, [filter]);
|
}, [filter]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPage(0);
|
||||||
|
document.getElementById("sidebar")?.scrollTo(0, 0);
|
||||||
|
}, [filterResults]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
filteredCells && (
|
filteredCells && (
|
||||||
<>
|
<>
|
||||||
@ -64,7 +73,9 @@ const CellList: React.FC<Props> = ({ cells }) => {
|
|||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
<ul className={styles["cell-list"]}>
|
<ul className={styles["cell-list"]}>
|
||||||
{filteredCells.map((cell) => (
|
{filteredCells
|
||||||
|
.slice(page * PAGE_SIZE, page * PAGE_SIZE + PAGE_SIZE)
|
||||||
|
.map((cell) => (
|
||||||
<li
|
<li
|
||||||
key={`cell-${cell.x},${cell.y}`}
|
key={`cell-${cell.x},${cell.y}`}
|
||||||
className={styles["cell-list-item"]}
|
className={styles["cell-list-item"]}
|
||||||
@ -72,7 +83,9 @@ const CellList: React.FC<Props> = ({ cells }) => {
|
|||||||
<div className={styles["cell-title"]}>
|
<div className={styles["cell-title"]}>
|
||||||
<strong>
|
<strong>
|
||||||
<Link
|
<Link
|
||||||
href={`/?cell=${encodeURIComponent(`${cell.x},${cell.y}`)}`}
|
href={`/?cell=${encodeURIComponent(
|
||||||
|
`${cell.x},${cell.y}`
|
||||||
|
)}`}
|
||||||
>
|
>
|
||||||
<a>
|
<a>
|
||||||
{cell.x}, {cell.y}
|
{cell.x}, {cell.y}
|
||||||
@ -83,6 +96,23 @@ const CellList: React.FC<Props> = ({ cells }) => {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
<ReactPaginate
|
||||||
|
breakLabel="..."
|
||||||
|
nextLabel=">"
|
||||||
|
forcePage={page}
|
||||||
|
onPageChange={(event) => {
|
||||||
|
setPage(event.selected);
|
||||||
|
document.getElementById("sidebar")?.scrollTo(0, 0);
|
||||||
|
}}
|
||||||
|
pageRangeDisplayed={3}
|
||||||
|
marginPagesDisplayed={2}
|
||||||
|
pageCount={Math.ceil(filteredCells.length / PAGE_SIZE)}
|
||||||
|
previousLabel="<"
|
||||||
|
renderOnZeroPageCount={() => null}
|
||||||
|
className={styles.pagination}
|
||||||
|
activeClassName={styles["active-page"]}
|
||||||
|
hrefBuilder={() => "#"}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -4,6 +4,7 @@ import React, { useEffect, useRef, useState } from "react";
|
|||||||
import MiniSearch from "minisearch";
|
import MiniSearch from "minisearch";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import useSWRImmutable from "swr/immutable";
|
import useSWRImmutable from "swr/immutable";
|
||||||
|
import ReactPaginate from "react-paginate";
|
||||||
|
|
||||||
import styles from "../styles/ModList.module.css";
|
import styles from "../styles/ModList.module.css";
|
||||||
import type { Mod } from "./CellData";
|
import type { Mod } from "./CellData";
|
||||||
@ -21,6 +22,7 @@ import {
|
|||||||
import { useAppDispatch, useAppSelector } from "../lib/hooks";
|
import { useAppDispatch, useAppSelector } from "../lib/hooks";
|
||||||
|
|
||||||
const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition";
|
const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition";
|
||||||
|
const PAGE_SIZE = 50;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
mods: Mod[];
|
mods: Mod[];
|
||||||
@ -33,6 +35,7 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
|
|||||||
const { sortBy, sortAsc, filter, category, includeTranslations } =
|
const { sortBy, sortAsc, filter, category, includeTranslations } =
|
||||||
useAppSelector((state) => state.modListFilters);
|
useAppSelector((state) => state.modListFilters);
|
||||||
const [filterResults, setFilterResults] = useState<Set<number>>(new Set());
|
const [filterResults, setFilterResults] = useState<Set<number>>(new Set());
|
||||||
|
const [page, setPage] = useState<number>(0);
|
||||||
|
|
||||||
const { data: cellCounts, error: cellCountsError } = useSWRImmutable(
|
const { data: cellCounts, error: cellCountsError } = useSWRImmutable(
|
||||||
`https://mods.modmapper.com/mod_cell_counts.json`,
|
`https://mods.modmapper.com/mod_cell_counts.json`,
|
||||||
@ -106,6 +109,11 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
|
|||||||
}
|
}
|
||||||
}, [filter]);
|
}, [filter]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setPage(0);
|
||||||
|
document.getElementById("sidebar")?.scrollTo(0, 0);
|
||||||
|
}, [filterResults, category, includeTranslations, sortBy, sortAsc]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
mods && (
|
mods && (
|
||||||
<>
|
<>
|
||||||
@ -219,7 +227,9 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
|
|||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
<ul className={styles["mod-list"]}>
|
<ul className={styles["mod-list"]}>
|
||||||
{modsWithCounts.map((mod) => (
|
{modsWithCounts
|
||||||
|
.slice(page * PAGE_SIZE, page * PAGE_SIZE + PAGE_SIZE)
|
||||||
|
.map((mod) => (
|
||||||
<li key={mod.id} className={styles["mod-list-item"]}>
|
<li key={mod.id} className={styles["mod-list-item"]}>
|
||||||
<div className={styles["mod-title"]}>
|
<div className={styles["mod-title"]}>
|
||||||
<strong>
|
<strong>
|
||||||
@ -319,6 +329,23 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
<ReactPaginate
|
||||||
|
breakLabel="..."
|
||||||
|
nextLabel=">"
|
||||||
|
forcePage={page}
|
||||||
|
onPageChange={(event) => {
|
||||||
|
setPage(event.selected);
|
||||||
|
document.getElementById("sidebar")?.scrollTo(0, 0);
|
||||||
|
}}
|
||||||
|
pageRangeDisplayed={3}
|
||||||
|
marginPagesDisplayed={2}
|
||||||
|
pageCount={Math.ceil(modsWithCounts.length / PAGE_SIZE)}
|
||||||
|
previousLabel="<"
|
||||||
|
renderOnZeroPageCount={() => null}
|
||||||
|
className={styles.pagination}
|
||||||
|
activeClassName={styles["active-page"]}
|
||||||
|
hrefBuilder={() => "#"}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -100,6 +100,7 @@ const Sidebar: React.FC<Props> = ({
|
|||||||
<div
|
<div
|
||||||
className={styles.sidebar}
|
className={styles.sidebar}
|
||||||
style={!open ? { display: "none" } : {}}
|
style={!open ? { display: "none" } : {}}
|
||||||
|
id="sidebar"
|
||||||
>
|
>
|
||||||
<div className={styles["sidebar-content"]}>
|
<div className={styles["sidebar-content"]}>
|
||||||
<div className={styles["sidebar-header"]}>
|
<div className={styles["sidebar-header"]}>
|
||||||
@ -123,6 +124,7 @@ const Sidebar: React.FC<Props> = ({
|
|||||||
<div
|
<div
|
||||||
className={styles.sidebar}
|
className={styles.sidebar}
|
||||||
style={!open ? { display: "none" } : {}}
|
style={!open ? { display: "none" } : {}}
|
||||||
|
id="sidebar"
|
||||||
>
|
>
|
||||||
<div className={styles["sidebar-content"]}>
|
<div className={styles["sidebar-content"]}>
|
||||||
<div className={styles["sidebar-header"]}>
|
<div className={styles["sidebar-header"]}>
|
||||||
@ -140,6 +142,7 @@ const Sidebar: React.FC<Props> = ({
|
|||||||
<div
|
<div
|
||||||
className={styles.sidebar}
|
className={styles.sidebar}
|
||||||
style={!open ? { display: "none" } : {}}
|
style={!open ? { display: "none" } : {}}
|
||||||
|
id="sidebar"
|
||||||
>
|
>
|
||||||
<div className={styles["sidebar-content"]}>
|
<div className={styles["sidebar-content"]}>
|
||||||
<div className={styles["sidebar-header"]}>
|
<div className={styles["sidebar-header"]}>
|
||||||
@ -161,6 +164,7 @@ const Sidebar: React.FC<Props> = ({
|
|||||||
<div
|
<div
|
||||||
className={styles.sidebar}
|
className={styles.sidebar}
|
||||||
style={!open ? { display: "none" } : {}}
|
style={!open ? { display: "none" } : {}}
|
||||||
|
id="sidebar"
|
||||||
>
|
>
|
||||||
<div className={styles["sidebar-content"]}>
|
<div className={styles["sidebar-content"]}>
|
||||||
<h1 className={styles.title}>Modmapper</h1>
|
<h1 className={styles.title}>Modmapper</h1>
|
||||||
|
20
package-lock.json
generated
20
package-lock.json
generated
@ -18,6 +18,7 @@
|
|||||||
"next": "12.1.1-canary.15",
|
"next": "12.1.1-canary.15",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
|
"react-paginate": "^8.1.3",
|
||||||
"react-redux": "^7.2.6",
|
"react-redux": "^7.2.6",
|
||||||
"skyrim-cell-dump-wasm": "0.1.0",
|
"skyrim-cell-dump-wasm": "0.1.0",
|
||||||
"swr": "^1.1.2"
|
"swr": "^1.1.2"
|
||||||
@ -2949,6 +2950,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/react-paginate": {
|
||||||
|
"version": "8.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-paginate/-/react-paginate-8.1.3.tgz",
|
||||||
|
"integrity": "sha512-zBp80DBRcaeBnAeHUfbGKD0XHfbGNUolQ+S60Ymfs8o7rusYaJYZMAt1j93ADDNLlzRmJ0tMF/NeTlcdKf7dlQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"prop-types": "^15.6.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16 || ^17 || ^18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-redux": {
|
"node_modules/react-redux": {
|
||||||
"version": "7.2.6",
|
"version": "7.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz",
|
||||||
@ -5619,6 +5631,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||||
},
|
},
|
||||||
|
"react-paginate": {
|
||||||
|
"version": "8.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-paginate/-/react-paginate-8.1.3.tgz",
|
||||||
|
"integrity": "sha512-zBp80DBRcaeBnAeHUfbGKD0XHfbGNUolQ+S60Ymfs8o7rusYaJYZMAt1j93ADDNLlzRmJ0tMF/NeTlcdKf7dlQ==",
|
||||||
|
"requires": {
|
||||||
|
"prop-types": "^15.6.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-redux": {
|
"react-redux": {
|
||||||
"version": "7.2.6",
|
"version": "7.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.6.tgz",
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"next": "12.1.1-canary.15",
|
"next": "12.1.1-canary.15",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
|
"react-paginate": "^8.1.3",
|
||||||
"react-redux": "^7.2.6",
|
"react-redux": "^7.2.6",
|
||||||
"skyrim-cell-dump-wasm": "0.1.0",
|
"skyrim-cell-dump-wasm": "0.1.0",
|
||||||
"swr": "^1.1.2"
|
"swr": "^1.1.2"
|
||||||
|
@ -48,3 +48,27 @@
|
|||||||
.filter {
|
.filter {
|
||||||
width: 175px;
|
width: 175px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination li {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination li a {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-page a {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
@ -107,3 +107,27 @@
|
|||||||
.desc {
|
.desc {
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination li {
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination li a {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-page a {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user