Add sort/filter controls to mod list
This commit is contained in:
parent
351785713c
commit
914fbbb540
@ -3,7 +3,7 @@ import React from "react";
|
|||||||
import useSWRImmutable from "swr/immutable";
|
import useSWRImmutable from "swr/immutable";
|
||||||
|
|
||||||
import styles from "../styles/CellData.module.css";
|
import styles from "../styles/CellData.module.css";
|
||||||
import CellModList from "./CellModList";
|
import ModList from "./ModList";
|
||||||
import PluginList from "./PluginsList";
|
import PluginList from "./PluginsList";
|
||||||
|
|
||||||
export interface Mod {
|
export interface Mod {
|
||||||
@ -123,7 +123,7 @@ const CellData: React.FC<Props> = ({ selectedCell, counts }) => {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<PluginList selectedCell={selectedCell} />
|
<PluginList selectedCell={selectedCell} />
|
||||||
<CellModList mods={data.mods} counts={counts} />
|
<ModList mods={data.mods} counts={counts} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -1,115 +0,0 @@
|
|||||||
import { format } from "date-fns";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
import styles from "../styles/CellModList.module.css";
|
|
||||||
import type { Mod } from "./CellData";
|
|
||||||
|
|
||||||
const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
mods: Mod[];
|
|
||||||
counts: Record<number, [number, number, number]> | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ModWithCounts = Mod & {
|
|
||||||
total_downloads: number;
|
|
||||||
unique_downloads: number;
|
|
||||||
views: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const CellModList: React.FC<Props> = ({ mods, counts }) => {
|
|
||||||
const [includeTranslations, setIncludeTranslations] = useState(true);
|
|
||||||
|
|
||||||
const modsWithCounts: ModWithCounts[] = mods
|
|
||||||
.map((mod) => {
|
|
||||||
const modCounts = counts && counts[mod.nexus_mod_id];
|
|
||||||
return {
|
|
||||||
...mod,
|
|
||||||
total_downloads: modCounts ? modCounts[0] : 0,
|
|
||||||
unique_downloads: modCounts ? modCounts[1] : 0,
|
|
||||||
views: modCounts ? modCounts[2] : 0,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter((mod) => includeTranslations || !mod.is_translation);
|
|
||||||
|
|
||||||
let numberFmt = new Intl.NumberFormat("en-US");
|
|
||||||
|
|
||||||
return (
|
|
||||||
mods && (
|
|
||||||
<>
|
|
||||||
<h2>Nexus Mods ({modsWithCounts.length})</h2>
|
|
||||||
<label className={styles["include-translations"]}>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={includeTranslations}
|
|
||||||
onChange={() => setIncludeTranslations(!includeTranslations)}
|
|
||||||
/>
|
|
||||||
Include translations
|
|
||||||
</label>
|
|
||||||
<ul className={styles["mod-list"]}>
|
|
||||||
{modsWithCounts
|
|
||||||
.sort((a, b) => b.unique_downloads - a.unique_downloads)
|
|
||||||
.map((mod) => (
|
|
||||||
<li key={mod.id} className={styles["mod-list-item"]}>
|
|
||||||
<div className={styles["mod-title"]}>
|
|
||||||
<strong>
|
|
||||||
<Link href={`/?mod=${mod.nexus_mod_id}`}>
|
|
||||||
<a>{mod.name}</a>
|
|
||||||
</Link>
|
|
||||||
</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
href={`${NEXUS_MODS_URL}/mods/${mod.nexus_mod_id}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
>
|
|
||||||
View on Nexus Mods
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Category: </strong>
|
|
||||||
<a
|
|
||||||
href={`${NEXUS_MODS_URL}/mods/categories/${mod.category_id}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
>
|
|
||||||
{mod.category_name}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Author: </strong>
|
|
||||||
<a
|
|
||||||
href={`${NEXUS_MODS_URL}/users/${mod.author_id}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
>
|
|
||||||
{mod.author_name}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Uploaded:</strong>{" "}
|
|
||||||
{format(new Date(mod.first_upload_at), "d MMM y")}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Last Update:</strong>{" "}
|
|
||||||
{format(new Date(mod.last_update_at), "d MMM y")}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Total Downloads:</strong>{" "}
|
|
||||||
{numberFmt.format(mod.total_downloads)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Unique Downloads:</strong>{" "}
|
|
||||||
{numberFmt.format(mod.unique_downloads)}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CellModList;
|
|
269
components/ModList.tsx
Normal file
269
components/ModList.tsx
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
import { format } from "date-fns";
|
||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import MiniSearch from "minisearch";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import styles from "../styles/ModList.module.css";
|
||||||
|
import type { Mod } from "./CellData";
|
||||||
|
import type { File } from "../slices/plugins";
|
||||||
|
import { formatBytes } from "../lib/plugins";
|
||||||
|
|
||||||
|
const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
mods: Mod[];
|
||||||
|
files?: File[];
|
||||||
|
counts: Record<number, [number, number, number]> | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ModWithCounts = Mod & {
|
||||||
|
total_downloads: number;
|
||||||
|
unique_downloads: number;
|
||||||
|
views: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModList: React.FC<Props> = ({ mods, files, counts }) => {
|
||||||
|
const [includeTranslations, setIncludeTranslations] = useState(true);
|
||||||
|
const [sortBy, setSortBy] = useState<keyof ModWithCounts>("unique_downloads");
|
||||||
|
const [filter, setFilter] = useState<string>("");
|
||||||
|
const [category, setCategory] = useState<string>("All");
|
||||||
|
const [filterResults, setFilterResults] = useState<Set<number>>(new Set());
|
||||||
|
|
||||||
|
const modsWithCounts: ModWithCounts[] = mods
|
||||||
|
.map((mod) => {
|
||||||
|
const modCounts = counts && counts[mod.nexus_mod_id];
|
||||||
|
return {
|
||||||
|
...mod,
|
||||||
|
total_downloads: modCounts ? modCounts[0] : 0,
|
||||||
|
unique_downloads: modCounts ? modCounts[1] : 0,
|
||||||
|
views: modCounts ? modCounts[2] : 0,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(
|
||||||
|
(mod) =>
|
||||||
|
(includeTranslations || !mod.is_translation) &&
|
||||||
|
(!filter || filterResults.has(mod.id)) &&
|
||||||
|
(category === "All" || mod.category_name === category)
|
||||||
|
)
|
||||||
|
.sort((a, b) => {
|
||||||
|
const aVal = a[sortBy];
|
||||||
|
const bVal = b[sortBy];
|
||||||
|
if (typeof aVal === "number" && typeof bVal === "number") {
|
||||||
|
return bVal - aVal;
|
||||||
|
} else if (
|
||||||
|
typeof aVal === "string" &&
|
||||||
|
typeof bVal === "string" &&
|
||||||
|
["first_upload_at", "last_update_at"].includes(sortBy)
|
||||||
|
) {
|
||||||
|
return new Date(bVal).getTime() - new Date(aVal).getTime();
|
||||||
|
} else if (typeof aVal === "string" && typeof bVal === "string") {
|
||||||
|
return aVal.localeCompare(bVal);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
let numberFmt = new Intl.NumberFormat("en-US");
|
||||||
|
|
||||||
|
const modSearch = useRef<MiniSearch<Mod> | null>(
|
||||||
|
null
|
||||||
|
) as React.MutableRefObject<MiniSearch<Mod>>;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
modSearch.current = new MiniSearch({
|
||||||
|
fields: ["name"],
|
||||||
|
storeFields: ["name", "id"],
|
||||||
|
searchOptions: {
|
||||||
|
fields: ["name"],
|
||||||
|
fuzzy: 0.2,
|
||||||
|
prefix: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
modSearch.current.addAll(mods);
|
||||||
|
}, [mods]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (modSearch.current) {
|
||||||
|
setFilterResults(
|
||||||
|
new Set(modSearch.current.search(filter).map((result) => result.id))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [filter]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
mods && (
|
||||||
|
<>
|
||||||
|
<h2>Nexus Mods ({modsWithCounts.length})</h2>
|
||||||
|
<div className={styles.filters}>
|
||||||
|
<hr />
|
||||||
|
<div className={styles["filter-row"]}>
|
||||||
|
<label htmlFor="sort-by">Sort by:</label>
|
||||||
|
<select
|
||||||
|
name="sort-by"
|
||||||
|
id="sort-by"
|
||||||
|
className={styles["sort-by"]}
|
||||||
|
value={sortBy}
|
||||||
|
onChange={(event) =>
|
||||||
|
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>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className={styles["filter-row"]}>
|
||||||
|
<label htmlFor="filter">Filter:</label>
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
id="filter"
|
||||||
|
className={styles.filter}
|
||||||
|
value={filter}
|
||||||
|
onChange={(event) => setFilter(event.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles["filter-row"]}>
|
||||||
|
<label htmlFor="category">Category:</label>
|
||||||
|
<select
|
||||||
|
name="category"
|
||||||
|
id="category"
|
||||||
|
className={styles["category"]}
|
||||||
|
value={category}
|
||||||
|
onChange={(event) => setCategory(event.target.value)}
|
||||||
|
>
|
||||||
|
<option value="All">All</option>
|
||||||
|
{(
|
||||||
|
Array.from(
|
||||||
|
mods
|
||||||
|
.reduce((categories, mod) => {
|
||||||
|
categories.add(mod.category_name);
|
||||||
|
return categories;
|
||||||
|
}, new Set())
|
||||||
|
.values()
|
||||||
|
) as string[]
|
||||||
|
)
|
||||||
|
.sort()
|
||||||
|
.map((category) => (
|
||||||
|
<option key={category} value={category}>
|
||||||
|
{category}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className={styles["filter-row"]}>
|
||||||
|
<label htmlFor="include-translations">Include translations:</label>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="include-translations"
|
||||||
|
className={styles["include-translations"]}
|
||||||
|
checked={includeTranslations}
|
||||||
|
onChange={() => setIncludeTranslations(!includeTranslations)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
<ul className={styles["mod-list"]}>
|
||||||
|
{modsWithCounts.map((mod) => (
|
||||||
|
<li key={mod.id} className={styles["mod-list-item"]}>
|
||||||
|
<div className={styles["mod-title"]}>
|
||||||
|
<strong>
|
||||||
|
<Link href={`/?mod=${mod.nexus_mod_id}`}>
|
||||||
|
<a>{mod.name}</a>
|
||||||
|
</Link>
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
href={`${NEXUS_MODS_URL}/mods/${mod.nexus_mod_id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>
|
||||||
|
View on Nexus Mods
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Category: </strong>
|
||||||
|
<a
|
||||||
|
href={`${NEXUS_MODS_URL}/mods/categories/${mod.category_id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>
|
||||||
|
{mod.category_name}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Author: </strong>
|
||||||
|
<a
|
||||||
|
href={`${NEXUS_MODS_URL}/users/${mod.author_id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>
|
||||||
|
{mod.author_name}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Uploaded:</strong>{" "}
|
||||||
|
{format(new Date(mod.first_upload_at), "d MMM y")}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Last Update:</strong>{" "}
|
||||||
|
{format(new Date(mod.last_update_at), "d MMM y")}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Total Downloads:</strong>{" "}
|
||||||
|
{numberFmt.format(mod.total_downloads)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Unique Downloads:</strong>{" "}
|
||||||
|
{numberFmt.format(mod.unique_downloads)}
|
||||||
|
</div>
|
||||||
|
<ul className={styles["file-list"]}>
|
||||||
|
{files &&
|
||||||
|
files
|
||||||
|
.filter((file) => file.mod_id === mod.id)
|
||||||
|
.sort((a, b) => b.nexus_file_id - a.nexus_file_id)
|
||||||
|
.map((file) => (
|
||||||
|
<li key={file.id}>
|
||||||
|
<div>
|
||||||
|
<strong>File:</strong> {file.name}
|
||||||
|
</div>
|
||||||
|
{file.mod_version && (
|
||||||
|
<div>
|
||||||
|
<strong>Version:</strong> {file.mod_version}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{file.version && file.mod_version !== file.version && (
|
||||||
|
<div>
|
||||||
|
<strong>File Version:</strong> {file.version}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{file.category && (
|
||||||
|
<div>
|
||||||
|
<strong>Category:</strong> {file.category}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div>
|
||||||
|
<strong>Size:</strong> {formatBytes(file.size)}
|
||||||
|
</div>
|
||||||
|
{file.uploaded_at && (
|
||||||
|
<div>
|
||||||
|
<strong>Uploaded:</strong>{" "}
|
||||||
|
{format(new Date(file.uploaded_at), "d MMM y")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModList;
|
@ -7,7 +7,7 @@ import {
|
|||||||
PluginFile,
|
PluginFile,
|
||||||
PluginsByHashWithMods,
|
PluginsByHashWithMods,
|
||||||
} from "../slices/plugins";
|
} from "../slices/plugins";
|
||||||
import PluginModList from "./PluginModList";
|
import ModList from "./ModList";
|
||||||
import CellList from "./CellList";
|
import CellList from "./CellList";
|
||||||
import type { CellCoord } from "./ModData";
|
import type { CellCoord } from "./ModData";
|
||||||
import PluginData, { Plugin as PluginProps } from "./PluginData";
|
import PluginData, { Plugin as PluginProps } from "./PluginData";
|
||||||
@ -92,9 +92,7 @@ const PluginDetail: React.FC<Props> = ({ hash, counts }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PluginData plugin={buildPluginProps(data, plugin)} counts={counts} />
|
<PluginData plugin={buildPluginProps(data, plugin)} counts={counts} />
|
||||||
{data && (
|
{data && <ModList mods={data.mods} files={data.files} counts={counts} />}
|
||||||
<PluginModList mods={data.mods} files={data.files} counts={counts} />
|
|
||||||
)}
|
|
||||||
<CellList
|
<CellList
|
||||||
cells={
|
cells={
|
||||||
(plugin?.parsed?.cells.filter(
|
(plugin?.parsed?.cells.filter(
|
||||||
|
@ -1,154 +0,0 @@
|
|||||||
import { format } from "date-fns";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import Link from "next/link";
|
|
||||||
|
|
||||||
import styles from "../styles/PluginModList.module.css";
|
|
||||||
import type { Mod } from "./CellData";
|
|
||||||
import type { File } from "../slices/plugins";
|
|
||||||
import { formatBytes } from "../lib/plugins";
|
|
||||||
|
|
||||||
const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
mods: Mod[];
|
|
||||||
files: File[];
|
|
||||||
counts: Record<number, [number, number, number]> | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ModWithCounts = Mod & {
|
|
||||||
total_downloads: number;
|
|
||||||
unique_downloads: number;
|
|
||||||
views: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const PluginModList: React.FC<Props> = ({ mods, files, counts }) => {
|
|
||||||
const [includeTranslations, setIncludeTranslations] = useState(true);
|
|
||||||
|
|
||||||
const modsWithCounts: ModWithCounts[] = mods
|
|
||||||
.map((mod) => {
|
|
||||||
const modCounts = counts && counts[mod.nexus_mod_id];
|
|
||||||
return {
|
|
||||||
...mod,
|
|
||||||
total_downloads: modCounts ? modCounts[0] : 0,
|
|
||||||
unique_downloads: modCounts ? modCounts[1] : 0,
|
|
||||||
views: modCounts ? modCounts[2] : 0,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter((mod) => includeTranslations || !mod.is_translation);
|
|
||||||
|
|
||||||
let numberFmt = new Intl.NumberFormat("en-US");
|
|
||||||
|
|
||||||
return (
|
|
||||||
mods && (
|
|
||||||
<>
|
|
||||||
<h2>Nexus Mods ({modsWithCounts.length})</h2>
|
|
||||||
<label className={styles["include-translations"]}>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={includeTranslations}
|
|
||||||
onChange={() => setIncludeTranslations(!includeTranslations)}
|
|
||||||
/>
|
|
||||||
Include translations
|
|
||||||
</label>
|
|
||||||
<ul className={styles["mod-list"]}>
|
|
||||||
{modsWithCounts
|
|
||||||
.sort((a, b) => b.unique_downloads - a.unique_downloads)
|
|
||||||
.map((mod) => (
|
|
||||||
<li key={mod.id} className={styles["mod-list-item"]}>
|
|
||||||
<div className={styles["mod-title"]}>
|
|
||||||
<strong>
|
|
||||||
<Link href={`/?mod=${mod.nexus_mod_id}`}>
|
|
||||||
<a>{mod.name}</a>
|
|
||||||
</Link>
|
|
||||||
</strong>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a
|
|
||||||
href={`${NEXUS_MODS_URL}/mods/${mod.nexus_mod_id}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
>
|
|
||||||
View on Nexus Mods
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Category: </strong>
|
|
||||||
<a
|
|
||||||
href={`${NEXUS_MODS_URL}/mods/categories/${mod.category_id}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
>
|
|
||||||
{mod.category_name}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Author: </strong>
|
|
||||||
<a
|
|
||||||
href={`${NEXUS_MODS_URL}/users/${mod.author_id}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer noopener"
|
|
||||||
>
|
|
||||||
{mod.author_name}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Uploaded:</strong>{" "}
|
|
||||||
{format(new Date(mod.first_upload_at), "d MMM y")}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Last Update:</strong>{" "}
|
|
||||||
{format(new Date(mod.last_update_at), "d MMM y")}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Total Downloads:</strong>{" "}
|
|
||||||
{numberFmt.format(mod.total_downloads)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Unique Downloads:</strong>{" "}
|
|
||||||
{numberFmt.format(mod.unique_downloads)}
|
|
||||||
</div>
|
|
||||||
<ul className={styles["file-list"]}>
|
|
||||||
{files
|
|
||||||
.filter((file) => file.mod_id === mod.id)
|
|
||||||
.sort((a, b) => b.nexus_file_id - a.nexus_file_id)
|
|
||||||
.map((file) => (
|
|
||||||
<li key={file.id}>
|
|
||||||
<div>
|
|
||||||
<strong>File:</strong> {file.name}
|
|
||||||
</div>
|
|
||||||
{file.mod_version && (
|
|
||||||
<div>
|
|
||||||
<strong>Version:</strong> {file.mod_version}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{file.version && file.mod_version !== file.version && (
|
|
||||||
<div>
|
|
||||||
<strong>File Version:</strong> {file.version}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{file.category && (
|
|
||||||
<div>
|
|
||||||
<strong>Category:</strong> {file.category}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<strong>Size:</strong> {formatBytes(file.size)}
|
|
||||||
</div>
|
|
||||||
{file.uploaded_at && (
|
|
||||||
<div>
|
|
||||||
<strong>Uploaded:</strong>{" "}
|
|
||||||
{format(new Date(file.uploaded_at), "d MMM y")}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PluginModList;
|
|
@ -44,7 +44,11 @@ const PluginsList: React.FC<Props> = ({ selectedCell }) => {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<ol className={styles["plugin-list"]}>
|
<ol
|
||||||
|
className={`${styles["plugin-list"]} ${
|
||||||
|
plugins.length > 0 ? styles["bottom-spacing"] : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
{plugins.map((plugin) => (
|
{plugins.map((plugin) => (
|
||||||
<li key={plugin.filename} title={plugin.filename}>
|
<li key={plugin.filename} title={plugin.filename}>
|
||||||
<input
|
<input
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
.mod-list {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mod-list-item {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mod-title {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.include-translations {
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.include-translations input {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
73
styles/ModList.module.css
Normal file
73
styles/ModList.module.css
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
.mod-list {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-list-item {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-title {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-left: 8px;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-list li {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters hr {
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters hr:first-child {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters hr:last-child {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-row label,
|
||||||
|
.filter-row input,
|
||||||
|
.filter-row select {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-by {
|
||||||
|
min-width: 175px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category {
|
||||||
|
min-width: 175px;
|
||||||
|
width: 175px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter {
|
||||||
|
width: 175px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.include-translations input {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
.plugin-list {
|
.plugin-list {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin-top: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.plugin-list li {
|
.plugin-list li {
|
||||||
@ -10,6 +10,10 @@
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom-spacing {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.plugin-error {
|
.plugin-error {
|
||||||
color: #ff0000;
|
color: #ff0000;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user