Select file and plugin on ModData page

This commit is contained in:
Tyler Hallada 2022-08-19 23:50:16 -04:00
parent d76751f495
commit 75643f89ae
4 changed files with 174 additions and 31 deletions

View File

@ -850,6 +850,24 @@ const Map: React.FC = () => {
open={sidebarOpen} open={sidebarOpen}
setOpen={setSidebarOpenWithResize} setOpen={setSidebarOpenWithResize}
lastModified={cellsData && cellsData.lastModified} lastModified={cellsData && cellsData.lastModified}
onSelectFile={(selectedFile) => {
if (selectedFile) {
router.push({ query: { ...router.query, file: selectedFile } });
} else {
const { file, ...rest } = router.query;
router.push({ query: { ...rest } });
}
}}
onSelectPlugin={(selectedPlugin) => {
if (selectedPlugin) {
router.push({
query: { ...router.query, plugin: selectedPlugin },
});
} else {
const { plugin, ...rest } = router.query;
router.push({ query: { ...rest } });
}
}}
/> />
<ToggleLayersControl map={map} /> <ToggleLayersControl map={map} />
<SearchBar <SearchBar

View File

@ -1,6 +1,6 @@
import { format } from "date-fns"; import { format } from "date-fns";
import Head from "next/head"; import Head from "next/head";
import React, { useEffect } from "react"; import React, { useCallback, useEffect, useState } from "react";
import useSWRImmutable from "swr/immutable"; import useSWRImmutable from "swr/immutable";
import CellList from "./CellList"; import CellList from "./CellList";
@ -74,114 +74,151 @@ export const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition";
type Props = { type Props = {
selectedMod: number; selectedMod: number;
selectedFile: number;
selectedPlugin: string;
counts: Record<number, [number, number, number]> | null; counts: Record<number, [number, number, number]> | null;
setSelectedCells: (cells: { x: number; y: number }[] | null) => void; setSelectedCells: (cells: { x: number; y: number }[] | null) => void;
onSelectFile: (fileId: number) => void;
onSelectPlugin: (hash: string) => void;
}; };
const ModData: React.FC<Props> = ({ const ModData: React.FC<Props> = ({
selectedMod, selectedMod,
selectedFile,
selectedPlugin,
counts, counts,
setSelectedCells, setSelectedCells,
onSelectFile,
onSelectPlugin,
}) => { }) => {
const { data, error } = useSWRImmutable( const { data: modData, error: modError } = useSWRImmutable(
`https://mods.modmapper.com/${selectedMod}.json`, `https://mods.modmapper.com/${selectedMod}.json`,
(_) => jsonFetcher<Mod>(_) (_) => jsonFetcher<Mod>(_)
); );
useEffect(() => { const { data: fileData, error: fileError } = useSWRImmutable(
if (data) setSelectedCells(data.cells); selectedFile ? `https://files.modmapper.com/${selectedFile}.json` : null,
}, [data, setSelectedCells]); (_) => jsonFetcher<File>(_)
);
if (error && error.status === 404) { const { data: pluginData, error: pluginError } = useSWRImmutable(
selectedPlugin
? `https://files.modmapper.com/${selectedPlugin}.json`
: null,
(_) => jsonFetcher<File>(_)
);
const handleFileChange = useCallback(
(event) => {
onSelectFile(event.target.value);
},
[onSelectFile]
);
const handlePluginChange = useCallback(
(event) => {
onSelectPlugin(event.target.value);
},
[onSelectPlugin]
);
useEffect(() => {
if (modData && !selectedFile) setSelectedCells(modData.cells);
}, [modData, setSelectedCells, selectedFile]);
useEffect(() => {
if (fileData) setSelectedCells(fileData.cells);
}, [fileData, setSelectedCells]);
if (modError && modError.status === 404) {
return <div>Mod could not be found.</div>; return <div>Mod could not be found.</div>;
} else if (error) { } else if (modError) {
return <div>{`Error loading mod data: ${error.message}`}</div>; return <div>{`Error loading mod modData: ${modError.message}`}</div>;
} }
if (data === undefined) if (modData === undefined)
return <div className={styles.status}>Loading...</div>; return <div className={styles.status}>Loading...</div>;
if (data === null) if (modData === null)
return <div className={styles.status}>Mod could not be found.</div>; return <div className={styles.status}>Mod could not be found.</div>;
let numberFmt = new Intl.NumberFormat("en-US"); let numberFmt = new Intl.NumberFormat("en-US");
const modCounts = counts && counts[data.nexus_mod_id]; const modCounts = counts && counts[modData.nexus_mod_id];
const total_downloads = modCounts ? modCounts[0] : 0; const total_downloads = modCounts ? modCounts[0] : 0;
const unique_downloads = modCounts ? modCounts[1] : 0; const unique_downloads = modCounts ? modCounts[1] : 0;
const views = modCounts ? modCounts[2] : 0; const views = modCounts ? modCounts[2] : 0;
if (selectedMod && data) { if (selectedMod && modData) {
return ( return (
<> <>
<Head> <Head>
<title key="title">{`Modmapper - ${data.name}`}</title> <title key="title">{`Modmapper - ${modData.name}`}</title>
<meta <meta
key="description" key="description"
name="description" name="description"
content={`Map of Skyrim showing ${data.cells.length} cell edits from the mod: ${data.name}`} content={`Map of Skyrim showing ${modData.cells.length} cell edits from the mod: ${modData.name}`}
/> />
<meta <meta
key="og:title" key="og:title"
property="og:title" property="og:title"
content={`Modmapper - ${data.name}`} content={`Modmapper - ${modData.name}`}
/> />
<meta <meta
key="og:description" key="og:description"
property="og:description" property="og:description"
content={`Map of Skyrim showing ${data.cells.length} cell edits from the mod: ${data.name}`} content={`Map of Skyrim showing ${modData.cells.length} cell edits from the mod: ${modData.name}`}
/> />
<meta <meta
key="twitter:title" key="twitter:title"
name="twitter:title" name="twitter:title"
content={`Modmapper - ${data.name}`} content={`Modmapper - ${modData.name}`}
/> />
<meta <meta
key="twitter:description" key="twitter:description"
name="twitter:description" name="twitter:description"
content={`Map of Skyrim showing ${data.cells.length} cell edits from the mod: ${data.name}`} content={`Map of Skyrim showing ${modData.cells.length} cell edits from the mod: ${modData.name}`}
/> />
<meta <meta
key="og:url" key="og:url"
property="og:url" property="og:url"
content={`https://modmapper.com/?mod=${data.nexus_mod_id}`} content={`https://modmapper.com/?mod=${modData.nexus_mod_id}`}
/> />
</Head> </Head>
<h1> <h1>
<a <a
href={`${NEXUS_MODS_URL}/mods/${data.nexus_mod_id}`} href={`${NEXUS_MODS_URL}/mods/${modData.nexus_mod_id}`}
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
className={styles.name} className={styles.name}
> >
{data.name} {modData.name}
</a> </a>
</h1> </h1>
<div> <div>
<strong>Category:&nbsp;</strong> <strong>Category:&nbsp;</strong>
<a <a
href={`${NEXUS_MODS_URL}/mods/categories/${data.category_id}`} href={`${NEXUS_MODS_URL}/mods/categories/${modData.category_id}`}
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
> >
{data.category_name} {modData.category_name}
</a> </a>
{data.is_translation && <strong>&nbsp;(translation)</strong>} {modData.is_translation && <strong>&nbsp;(translation)</strong>}
</div> </div>
<div> <div>
<strong>Author:&nbsp;</strong> <strong>Author:&nbsp;</strong>
<a <a
href={`${NEXUS_MODS_URL}/users/${data.author_id}`} href={`${NEXUS_MODS_URL}/users/${modData.author_id}`}
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
> >
{data.author_name} {modData.author_name}
</a> </a>
</div> </div>
<div> <div>
<strong>Uploaded:</strong>{" "} <strong>Uploaded:</strong>{" "}
{format(new Date(data.first_upload_at), "d MMM y")} {format(new Date(modData.first_upload_at), "d MMM y")}
</div> </div>
<div> <div>
<strong>Last Update:</strong>{" "} <strong>Last Update:</strong>{" "}
{format(new Date(data.last_update_at), "d MMM y")} {format(new Date(modData.last_update_at), "d MMM y")}
</div> </div>
<div> <div>
<strong>Total Downloads:</strong> {numberFmt.format(total_downloads)} <strong>Total Downloads:</strong> {numberFmt.format(total_downloads)}
@ -190,7 +227,61 @@ const ModData: React.FC<Props> = ({
<strong>Unique Downloads:</strong>{" "} <strong>Unique Downloads:</strong>{" "}
{numberFmt.format(unique_downloads)} {numberFmt.format(unique_downloads)}
</div> </div>
<CellList cells={data.cells} /> <div className={styles["select-container"]}>
<label htmlFor="mod-file-select" className={styles.label}>
Select file:
</label>
<select
name="file"
id="mod-file-select"
className={styles.select}
onChange={handleFileChange}
value={selectedFile ?? ""}
>
<option value="">--Select file--</option>
{[...modData.files].reverse().map((file) => (
<option key={file.nexus_file_id} value={file.nexus_file_id}>
{file.name} (v{file.version}) ({file.category})
</option>
))}
</select>
</div>
{fileData && (
<div className={styles["select-container"]}>
<label htmlFor="file-plugin-select" className={styles.label}>
Select plugin:
</label>
<select
name="plugin"
id="file-plugin-select"
className={styles.select}
onChange={handlePluginChange}
value={selectedPlugin ?? ""}
>
<option value="">--Select plugin--</option>
{fileData.plugins.map((plugin) => (
<option key={plugin.hash} value={plugin.hash}>
{plugin.file_path}
</option>
))}
</select>
</div>
)}
{fileError &&
(fileError.status === 404 ? (
<div>File cound not be found.</div>
) : (
<div>{`Error loading file data: ${fileError.message}`}</div>
))}
{pluginError &&
(pluginError.status === 404 ? (
<div>Plugin cound not be found.</div>
) : (
<div>{`Error loading plugin data: ${pluginError.message}`}</div>
))}
<CellList
cells={pluginData?.cells ?? fileData?.cells ?? modData.cells}
/>
</> </>
); );
} }

View File

@ -24,6 +24,8 @@ type Props = {
open: boolean; open: boolean;
setOpen: (open: boolean) => void; setOpen: (open: boolean) => void;
lastModified: string | null | undefined; lastModified: string | null | undefined;
onSelectFile: (fileId: number) => void;
onSelectPlugin: (hash: string) => void;
}; };
const Sidebar: React.FC<Props> = ({ const Sidebar: React.FC<Props> = ({
@ -35,6 +37,8 @@ const Sidebar: React.FC<Props> = ({
open, open,
setOpen, setOpen,
lastModified, lastModified,
onSelectFile,
onSelectPlugin,
}) => { }) => {
const router = useRouter(); const router = useRouter();
@ -51,15 +55,23 @@ const Sidebar: React.FC<Props> = ({
return <CellData selectedCell={selectedCell} counts={counts} />; return <CellData selectedCell={selectedCell} counts={counts} />;
}; };
const renderModData = (selectedMod: number) => { const renderModData = (
selectedMod: number,
selectedFile: number,
selectedPlugin: string
) => {
if (countsError) return renderLoadError(countsError); if (countsError) return renderLoadError(countsError);
if (!counts) return renderLoading(); if (!counts) return renderLoading();
return ( return (
<ModData <ModData
selectedMod={selectedMod} selectedMod={selectedMod}
selectedFile={selectedFile}
selectedPlugin={selectedPlugin}
counts={counts} counts={counts}
setSelectedCells={setSelectedCells} setSelectedCells={setSelectedCells}
onSelectFile={onSelectFile}
onSelectPlugin={onSelectPlugin}
/> />
); );
}; };
@ -105,6 +117,8 @@ const Sidebar: React.FC<Props> = ({
); );
} else if (router.query.mod) { } else if (router.query.mod) {
const modId = parseInt(router.query.mod as string, 10); const modId = parseInt(router.query.mod as string, 10);
const fileId = parseInt(router.query.file as string, 10);
const pluginHash = router.query.plugin as string;
return ( return (
<div <div
className={styles.sidebar} className={styles.sidebar}
@ -116,7 +130,7 @@ const Sidebar: React.FC<Props> = ({
<img src="/img/close.svg" width={24} height={24} alt="close" /> <img src="/img/close.svg" width={24} height={24} alt="close" />
</button> </button>
</div> </div>
{!Number.isNaN(modId) && renderModData(modId)} {!Number.isNaN(modId) && renderModData(modId, fileId, pluginHash)}
{renderLastModified(lastModified)} {renderLastModified(lastModified)}
</div> </div>
</div> </div>

View File

@ -6,3 +6,23 @@ a.name {
line-height: 1.75rem; line-height: 1.75rem;
word-wrap: break-word; word-wrap: break-word;
} }
.select-container {
margin-top: 12px;
}
.select {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
.label {
white-space: nowrap;
margin-right: 12px;
font-weight: bold;
width: 100%;
display: block;
margin-bottom: 4px;
}