Select file and plugin on ModData page
This commit is contained in:
parent
d76751f495
commit
75643f89ae
@ -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
|
||||||
|
@ -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: </strong>
|
<strong>Category: </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> (translation)</strong>}
|
{modData.is_translation && <strong> (translation)</strong>}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>Author: </strong>
|
<strong>Author: </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}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user