Show exterior cells edited in ModList

Also abstracts jsonFetcher and csvFetcher into common lib functions.
This commit is contained in:
Tyler Hallada 2022-03-18 01:06:20 -04:00
parent f120090039
commit 1a39f7c5e4
8 changed files with 70 additions and 76 deletions

View File

@ -5,6 +5,7 @@ import useSWRImmutable from "swr/immutable";
import styles from "../styles/CellData.module.css";
import ModList from "./ModList";
import PluginList from "./PluginsList";
import { jsonFetcher } from "../lib/api";
export interface Mod {
id: number;
@ -36,19 +37,6 @@ export interface Cell {
mods: Mod[];
}
const jsonFetcher = async (url: string): Promise<Cell | null> => {
const res = await fetch(url);
if (!res.ok) {
if (res.status === 404) {
return null;
}
const error = new Error("An error occurred while fetching the data.");
throw error;
}
return res.json();
};
type Props = {
selectedCell: { x: number; y: number };
counts: Record<number, [number, number, number]> | null;
@ -57,7 +45,7 @@ type Props = {
const CellData: React.FC<Props> = ({ selectedCell, counts }) => {
const { data, error } = useSWRImmutable(
`https://cells.modmapper.com/${selectedCell.x}/${selectedCell.y}.json`,
jsonFetcher
(_) => jsonFetcher<Cell>(_)
);
if (error && error.status === 404) {

View File

@ -5,8 +5,6 @@ import MiniSearch from "minisearch";
import styles from "../styles/CellList.module.css";
import type { CellCoord } from "./ModData";
const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition";
type Props = {
cells: CellCoord[];
};

View File

@ -10,6 +10,7 @@ import styles from "../styles/Map.module.css";
import Sidebar from "./Sidebar";
import ToggleLayersControl from "./ToggleLayersControl";
import SearchBar from "./SearchBar";
import { csvFetcher, jsonFetcherWithLastModified } from "../lib/api";
mapboxgl.accessToken = process.env.NEXT_PUBLIC_MAPBOX_TOKEN ?? "";
@ -26,13 +27,6 @@ colorGradient.setMidpoint(360);
const LIVE_DOWNLOAD_COUNTS_URL =
"https://staticstats.nexusmods.com/live_download_counts/mods/1704.csv";
const jsonFetcher = (url: string) =>
fetch(url).then(async (res) => ({
lastModified: res.headers.get("Last-Modified"),
data: await res.json(),
}));
const csvFetcher = (url: string) => fetch(url).then((res) => res.text());
const Map: React.FC = () => {
const router = useRouter();
const mapContainer = useRef<HTMLDivElement | null>(
@ -67,7 +61,7 @@ const Map: React.FC = () => {
const { data: cellsData, error: cellsError } = useSWRImmutable(
"https://cells.modmapper.com/edits.json",
jsonFetcher
(_) => jsonFetcherWithLastModified<Record<string, number>>(_)
);
// The live download counts are not really immutable, but I'd still rather load them once per session
const [counts, setCounts] = useState<Record<
@ -560,9 +554,7 @@ const Map: React.FC = () => {
x * cellSize + viewportNW.x,
y * cellSize + viewportNW.y + cellSize,
]);
const editCount = (cellsData.data as Record<string, number>)[
`${x - 57},${50 - y}`
];
const editCount = cellsData.data[`${x - 57},${50 - y}`];
grid.features.push({
type: "Feature",
id: x * 1000 + y,

View File

@ -5,6 +5,7 @@ import useSWRImmutable from "swr/immutable";
import CellList from "./CellList";
import styles from "../styles/ModData.module.css";
import { jsonFetcher } from "../lib/api";
export interface CellCoord {
x: number;
@ -33,19 +34,6 @@ export interface Mod {
const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition";
const jsonFetcher = async (url: string): Promise<Mod | null> => {
const res = await fetch(url);
if (!res.ok) {
if (res.status === 404) {
return null;
}
const error = new Error("An error occurred while fetching the data.");
throw error;
}
return res.json();
};
type Props = {
selectedMod: number;
counts: Record<number, [number, number, number]> | null;
@ -59,7 +47,7 @@ const ModData: React.FC<Props> = ({
}) => {
const { data, error } = useSWRImmutable(
`https://mods.modmapper.com/${selectedMod}.json`,
jsonFetcher
(_) => jsonFetcher<Mod>(_)
);
if (error && error.status === 404) {

View File

@ -2,11 +2,13 @@ import { format } from "date-fns";
import React, { useEffect, useRef, useState } from "react";
import MiniSearch from "minisearch";
import Link from "next/link";
import useSWRImmutable from "swr/immutable";
import styles from "../styles/ModList.module.css";
import type { Mod } from "./CellData";
import type { File } from "../slices/plugins";
import { formatBytes } from "../lib/plugins";
import { jsonFetcher } from "../lib/api";
const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition";
@ -20,6 +22,7 @@ type ModWithCounts = Mod & {
total_downloads: number;
unique_downloads: number;
views: number;
exterior_cells_edited: number;
};
const ModList: React.FC<Props> = ({ mods, files, counts }) => {
@ -29,6 +32,11 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
const [category, setCategory] = useState<string>("All");
const [filterResults, setFilterResults] = useState<Set<number>>(new Set());
const { data: cellCounts, error: cellCountsError } = useSWRImmutable(
`https://mods.modmapper.com/mod_cell_counts.json`,
(_) => jsonFetcher<Record<string, number>>(_)
);
const modsWithCounts: ModWithCounts[] = mods
.map((mod) => {
const modCounts = counts && counts[mod.nexus_mod_id];
@ -37,6 +45,9 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
total_downloads: modCounts ? modCounts[0] : 0,
unique_downloads: modCounts ? modCounts[1] : 0,
views: modCounts ? modCounts[2] : 0,
exterior_cells_edited: cellCounts
? cellCounts[mod.nexus_mod_id] ?? 0
: 0,
};
})
.filter(
@ -106,14 +117,17 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
setSortBy(event.target.value as keyof ModWithCounts)
}
>
<option value="unique_downloads">Unique Downloads</option>
<option value="total_downloads">Total Downloads</option>
<option value="views">Views</option>
<option value="name">Name</option>
<option value="nexus_mod_id">ID</option>
<option value="author_name">Author</option>
<option value="first_upload_at">Upload Date</option>
<option value="last_update_at">Last Update</option>
<option value="total_downloads">Total Downloads</option>
<option value="unique_downloads">Unique Downloads</option>
<option value="views">Views</option>
<option value="exterior_cells_edited">
Exterior Cells Edited
</option>
<option value="nexus_mod_id">ID</option>
</select>
</div>
<div className={styles["filter-row"]}>
@ -221,6 +235,12 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
<strong>Unique Downloads:</strong>{" "}
{numberFmt.format(mod.unique_downloads)}
</div>
{cellCounts && (
<div>
<strong>Exterior Cells Edited:</strong>{" "}
{numberFmt.format(mod.exterior_cells_edited)}
</div>
)}
<ul className={styles["file-list"]}>
{files &&
files

View File

@ -12,21 +12,7 @@ import CellList from "./CellList";
import type { CellCoord } from "./ModData";
import PluginData, { Plugin as PluginProps } from "./PluginData";
import styles from "../styles/PluginData.module.css";
const jsonFetcher = async (
url: string
): Promise<PluginsByHashWithMods | null> => {
const res = await fetch(url);
if (!res.ok) {
if (res.status === 404) {
return null;
}
const error = new Error("An error occurred while fetching the data.");
throw error;
}
return res.json();
};
import { jsonFetcher } from "../lib/api";
const buildPluginProps = (
data?: PluginsByHashWithMods | null,
@ -65,7 +51,7 @@ type Props = {
const PluginDetail: React.FC<Props> = ({ hash, counts }) => {
const { data, error } = useSWRImmutable(
`https://plugins.modmapper.com/${hash}.json`,
jsonFetcher
(_) => jsonFetcher<PluginsByHashWithMods>(_)
);
const dispatch = useAppDispatch();

View File

@ -5,6 +5,7 @@ import MiniSearch, { SearchResult } from "minisearch";
import useSWRImmutable from "swr/immutable";
import styles from "../styles/SearchBar.module.css";
import { jsonFetcher } from "../lib/api";
type Props = {
counts: Record<number, [number, number, number]> | null;
@ -16,19 +17,6 @@ interface Mod {
id: number;
}
const jsonFetcher = async (url: string): Promise<Mod | null> => {
const res = await fetch(url);
if (!res.ok) {
if (res.status === 404) {
return null;
}
const error = new Error("An error occurred while fetching the data.");
throw error;
}
return res.json();
};
let cells = [];
for (let x = -77; x < 76; x++) {
@ -58,7 +46,7 @@ const SearchBar: React.FC<Props> = ({ counts, sidebarOpen }) => {
const { data, error } = useSWRImmutable(
`https://mods.modmapper.com/mod_search_index.json`,
jsonFetcher
(_) => jsonFetcher<Mod[]>(_)
);
useEffect(() => {
@ -72,7 +60,7 @@ const SearchBar: React.FC<Props> = ({ counts, sidebarOpen }) => {
prefix: true,
},
});
modSearch.current.addAll(data as unknown as Mod[]);
modSearch.current.addAll(data);
}
}, [data]);

34
lib/api.ts Normal file
View File

@ -0,0 +1,34 @@
export async function jsonFetcher<T>(url: string): Promise<T | null> {
const res = await fetch(url);
if (!res.ok) {
if (res.status === 404) {
return null;
}
const error = new Error("An error occurred while fetching the data.");
throw error;
}
return res.json();
};
export async function jsonFetcherWithLastModified<T>(url: string): Promise<{ data: T, lastModified: string | null } | null> {
const res = await fetch(url);
if (!res.ok) {
if (res.status === 404) {
return null;
}
const error = new Error("An error occurred while fetching the data.");
throw error;
}
return {
lastModified: res.headers.get("Last-Modified"),
data: await res.json(),
};
}
export async function csvFetcher(url: string): Promise<string> {
return (await fetch(url)).text();
}