From 07ab9ca657e226d9e617df83d5aa4103e24491d7 Mon Sep 17 00:00:00 2001 From: Damon Blais Date: Sun, 18 Feb 2024 22:53:34 -0800 Subject: [PATCH] chore(vscode): Set ESLint as default formatter for TypeScript React filesAdd CORS headers for API routes in Next.js configfeat: Implement ModListItem and ProfileListItem componentsfix(api): Correct async function declarations for API routesfeat(api): Add middleware for logging API path accessfeat: Update mods page layout with search functionality and user profile interactionstyle: Adjust formatting and layout in tools page for better readability --- .vscode/settings.json | 3 + next.config.mjs | 16 +- src/components/mods/ModListItem.tsx | 146 ++++ src/components/mods/ProfileListItem.tsx | 102 +++ src/pages/api/concrete/bug-report.ts | 4 +- src/pages/api/middleware.ts | 11 + src/pages/api/ts/package/[...id].ts | 43 + src/pages/mods/index.tsx | 543 ++++++------ src/pages/tools/index.tsx | 1044 +++++++++++++---------- 9 files changed, 1174 insertions(+), 738 deletions(-) create mode 100644 src/components/mods/ModListItem.tsx create mode 100644 src/components/mods/ProfileListItem.tsx create mode 100644 src/pages/api/middleware.ts create mode 100644 src/pages/api/ts/package/[...id].ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 494ae25..c406ccf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -47,4 +47,7 @@ "[typescript]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint" }, + "[typescriptreact]": { + "editor.defaultFormatter": "dbaeumer.vscode-eslint" + }, } diff --git a/next.config.mjs b/next.config.mjs index d225a33..5054039 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -10,7 +10,21 @@ const nextConfig = { pathname: '**', } ] - } + }, + async headers() { + return [ + { + // matching all API routes + source: '/api/:path*', + headers: [ + { key: 'Access-Control-Allow-Credentials', value: 'true' }, + { key: 'Access-Control-Allow-Origin', value: '*' }, + { key: 'Access-Control-Allow-Methods', value: 'GET,OPTIONS,PATCH,DELETE,POST,PUT' }, + { key: 'Access-Control-Allow-Headers', value: 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version' }, + ] + } + ] + }, } export default nextConfig diff --git a/src/components/mods/ModListItem.tsx b/src/components/mods/ModListItem.tsx new file mode 100644 index 0000000..bee10fb --- /dev/null +++ b/src/components/mods/ModListItem.tsx @@ -0,0 +1,146 @@ +/* eslint-disable @next/next/no-img-element */ +import TrashIcon from '@mui/icons-material/Delete' +import DotsIcon from '@mui/icons-material/MoreVert' +import WorldIcon from '@mui/icons-material/Public' +import SettingsIcon from '@mui/icons-material/Settings' +import VerifiedIcon from '@mui/icons-material/VerifiedOutlined' +import Box from '@mui/material/Box' +import Divider from '@mui/material/Divider' +import IconButton from '@mui/material/IconButton' +import ListItemButton from '@mui/material/ListItemButton' +import Menu from '@mui/material/Menu' +import MenuItem from '@mui/material/MenuItem' +import Typography from '@mui/material/Typography' +import { useState } from 'react' + +type Props = { + id: string + name: string + owner: string + summary: string + verified?: boolean +} + +export default function ModListItem(props: Props): JSX.Element { + const { + id, + name, + owner, + summary, + verified, + } = props + + const [menuAnchor, setMenuAnchor] = useState(null) + const menuOpen = Boolean(menuAnchor) + + return + {`Mod + + + + + {name.replaceAll('_', ' ').replaceAll('-', ' ')} + + + {verified && } + + + {owner} + + + + + {summary} + + + + setMenuAnchor(e.currentTarget)} + > + + + + setMenuAnchor(null)} + open={menuOpen} + > + + + + Website + + + + + + + Settings + + + + + + + Uninstall + + + + +} diff --git a/src/components/mods/ProfileListItem.tsx b/src/components/mods/ProfileListItem.tsx new file mode 100644 index 0000000..a6657fa --- /dev/null +++ b/src/components/mods/ProfileListItem.tsx @@ -0,0 +1,102 @@ +/* eslint-disable @next/next/no-img-element */ +import TrashIcon from '@mui/icons-material/Delete' +import DotsIcon from '@mui/icons-material/MoreVert' +import Box from '@mui/material/Box' +import IconButton from '@mui/material/IconButton' +import ListItemButton from '@mui/material/ListItemButton' +import Menu from '@mui/material/Menu' +import MenuItem from '@mui/material/MenuItem' +import Typography from '@mui/material/Typography' +import { useCallback, useState, type Dispatch, type MouseEvent, type SetStateAction } from 'react' +import { type Profile } from 'types/Profile' + +type Props = { + onSelect: Dispatch> + profile: Profile +} + +export default function ProfileListItem(props: Props): JSX.Element { + const { + onSelect, + profile, + } = props + + const handleClick = useCallback(() => onSelect(profile), [onSelect, profile]) + + const [menuAnchor, setMenuAnchor] = useState(null) + const menuOpen = Boolean(menuAnchor) + + const handleMenuClick = useCallback((event: MouseEvent) => { + setMenuAnchor(event.currentTarget) + }, []) + + const handleMenuClose = useCallback(() => { + setMenuAnchor(null) + }, []) + + return + {`Profile + + + + {profile.name} + + + {profile.owner && + {profile.owner} + } + + + + + + + + + + + Delete + + + + +} diff --git a/src/pages/api/concrete/bug-report.ts b/src/pages/api/concrete/bug-report.ts index 3dbc6f0..0aea6ea 100644 --- a/src/pages/api/concrete/bug-report.ts +++ b/src/pages/api/concrete/bug-report.ts @@ -62,10 +62,10 @@ const bugReportTemplate = ` ` // NextJS API Route for submitting bug reports -export default void async function ConcreteBugReport( +export default async function ConcreteBugReport( req: BugReportRequest, res: NextApiResponse -) { +): Promise { try { await limiter.check(res, 10, 'CACHE_TOKEN') // 10 requests per minute } catch { diff --git a/src/pages/api/middleware.ts b/src/pages/api/middleware.ts new file mode 100644 index 0000000..fa549e7 --- /dev/null +++ b/src/pages/api/middleware.ts @@ -0,0 +1,11 @@ +import { NextRequest, NextResponse } from 'next/server' + +export async function middleware(req: NextRequest): Promise { + console.log('middleware.ts', req.nextUrl.pathname) + + return NextResponse.next() +} + +export const config = { + matcher: ['/api/:path*'] +} diff --git a/src/pages/api/ts/package/[...id].ts b/src/pages/api/ts/package/[...id].ts new file mode 100644 index 0000000..6e8a6da --- /dev/null +++ b/src/pages/api/ts/package/[...id].ts @@ -0,0 +1,43 @@ +import { type NextApiRequest, type NextApiResponse } from 'next/types' +import { ofetch } from 'ofetch' + +// https://thunderstore.io/api/experimental/package/ +export default async function TSExperimentalPackage( + req: NextApiRequest, + res: NextApiResponse +): Promise { + const { id } = req.query + + if (req.method === 'OPTIONS') { + return res.status(204).json({ status: 'ok' }) + } + + if (req.method !== 'GET') { + return res.status(405).json({ error: 'Method Not Allowed' }) + } + + if (!Array.isArray(id) || id.length != 2) { + return res.status(400).json({ error: 'Invalid ID' }) + } + + const response = await ofetch( + `https://thunderstore.io/api/experimental/package/${id.join('/')}/`, + { + cache: 'no-store', + credentials: 'omit', + headers: { + 'Accept': 'application/json', + 'User-Agent': 'LethalModding/lethal-modding', + }, + method: 'GET', + referrerPolicy: 'no-referrer', + redirect: 'follow', + } + ) + + if (!response) { + return res.status(500).json({ error: 'Failed to fetch data from Thunderstore' }) + } + + res.status(200).json(response) +} diff --git a/src/pages/mods/index.tsx b/src/pages/mods/index.tsx index 9fc7bc8..d047992 100644 --- a/src/pages/mods/index.tsx +++ b/src/pages/mods/index.tsx @@ -1,324 +1,291 @@ -import OpenInNewIcon from '@mui/icons-material/OpenInNew' +import AccountCircle from '@mui/icons-material/AccountCircle' +import AddIcon from '@mui/icons-material/Add' +import HelpIcon from '@mui/icons-material/Help' +import ImportIcon from '@mui/icons-material/ImportExport' +import PlayIcon from '@mui/icons-material/PlayArrow' +import SearchIcon from '@mui/icons-material/Search' +import SettingsIcon from '@mui/icons-material/Settings' +import AppBar from '@mui/material/AppBar' import Box from '@mui/material/Box' +import Button from '@mui/material/Button' +import IconButton from '@mui/material/IconButton' +import InputBase from '@mui/material/InputBase' +import Paper from '@mui/material/Paper' import Typography from '@mui/material/Typography' -import CornerAccents from 'components/branding/CornerAccents' -import TypedText from 'components/branding/TypedText' -import Link from 'components/mui/Link' +import ModListItem from 'components/mods/ModListItem' +import ProfileListItem from 'components/mods/ProfileListItem' import type { NextPage } from 'next' import Head from 'next/head' -import useGlobalStyles from 'styles/globalStyles' -// import ShareIcon from '@mui/icons-material/Share' -import ComputerIcon from '@mui/icons-material/Computer' -import DownloadIcon from '@mui/icons-material/Download' -import StorageIcon from '@mui/icons-material/Storage' -import WarningIcon from '@mui/icons-material/Warning' -import Fade from '@mui/material/Fade' -import Breadcrumb from 'components/tools/Breadcrumb' +import Image from 'next/image' +import { useState } from 'react' +import { type Profile } from 'types/Profile' -const externalLinks = [ +const allProfiles = [ { - href: 'https://thunderstore.io/c/lethal-company/', - label: 'Find More Mods on Thunderstore', + id: '10', + mods: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'], + name: 'Bug Fix Pack', + owner: '', }, { - href: 'https://github.com/LethalCompany/LethalCompanyTemplate', - label: 'Clone the Template Repository', + id: '20', + mods: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'], + name: 'LethalModding.com Official Pack', + owner: 'LethalModding.com', }, { - href: 'https://discord.gg/nYcQFEpXfU', - label: 'Join the Unofficial Discord', + id: '30', + mods: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'], + name: 'Default', + owner: '', }, ] -const allMods = [ - { - id: 'notnotnotswipez-MoreCompany-1.7.1', - description: 'Allows you to play with up to 32 players in a single game.', - recommended: true, - vanillaBreaker: false, - }, - { - id: 'x753-More_Suits-1.3.3', - description: 'Adds 6+ new suits to the suit rack in the ship.', - recommended: true, - vanillaBreaker: false, - }, - { - id: 'TeamClark-SCP_Foundation_Suit-1.0.1', - description: 'Adds the SCP Foundation Guard suit to the suit rack in the ship.', - vanillaBreaker: false, - }, - { - id: 'Sligili-More_Emotes-1.1.2', - description: 'Adds 5+ emotes, use keys 3-6 to middle finger, golf clap, or gritty.', - vanillaBreaker: false, - }, - { - id: 'malco-Lategame_Upgrades-2.5.1', - description: 'Adds 10+ late-game upgrades to give you something to work towards.', - vanillaBreaker: true, - }, - { - id: 'Verity-TooManySuits-1.0.3', - description: 'Adds a pager to the suit rack, allowing you to have more than 10 suits. (Default: N / B)', - recommended: true, - vanillaBreaker: false, - }, - { - id: 'taffyko-NameplateTweaks-1.0.2', - description: 'Shows a speaking icon above the name of the people who are speaking.', - recommended: true, - onlyClient: true, - vanillaBreaker: false, - }, - { - id: 'Hexnet111-SuitSaver-1.0.3', - description: 'Saves and automatically re-selects your previously used suit.', - onlyClient: true, - vanillaBreaker: false, - }, - { - id: 'RickArg-Helmet_Cameras-2.1.3', - description: 'Adds a remote first-person view of your teammate beside the radar screen.', - onlyClient: true, - vanillaBreaker: false, - }, - { - id: 'PopleZoo-BetterItemScan-2.0.3', - description: 'Improves the item scanner to have a better UI and faster calculation.', - onlyClient: true, - vanillaBreaker: false, - }, - { - id: 'tinyhoot-ShipLoot-1.0.0', - description: 'Gives a cumulative total of all the loot you\'ve brought to the ship.', - recommended: true, - onlyClient: true, - vanillaBreaker: false, - }, - { - id: 'Renegades-FlashlightToggle-1.3.1', - description: 'Adds a keybind to toggle your flashlight on and off. (Default: F)', - recommended: true, - onlyClient: true, - vanillaBreaker: false, - }, - { - id: 'Renegades-WalkieUse-1.2.3', - description: 'Adds a keybind to use your equipped walkie-talkie. (Default: R)', - recommended: true, - onlyClient: true, - vanillaBreaker: false, - }, - { - id: 'FlipMods-LetMeLookDown-1.0.1', - description: 'Allows you to look down, because by default, you can\'t. Weird.', - onlyClient: true, - vanillaBreaker: false, - }, - { - id: 'Drakorle-MoreItems-1.0.1', - description: 'Allows you to have unlimited items in your ship without de-spawning them.', - recommended: true, - onlyServer: true, - vanillaBreaker: false, - }, - { - id: 'tinyhoot-ShipLobby-1.0.2', - description: 'Allows people to join your ship any time that you\'re in orbit, not just when starting.', - recommended: true, - onlyServer: true, - vanillaBreaker: false, - }, - { - id: 'Nips-Brutal_Company_Plus-3.2.0', - description: 'Adds a lot of variety in the form of random landing events, moon heat, and more.', - onlyServer: true, - vanillaBreaker: false, - }, - { - id: 'anormaltwig-TerminalExtras-1.0.1', - description: 'Adds extra commands to the terminal, such as toggling the door, teleporter, or lightswitch.', - vanillaBreaker: true, - }, - { - id: 'Evaisa-LethalThings-0.7.1', - description: 'Adds a LOT, including a rocket launcher and personal teleporter.', - vanillaBreaker: true, - }, - { - id: 'FlipMods-BetterStamina-1.2.1', - description: 'Reworks the stamina system entirely. Configurable sprint duration, regeneration, and weight penalties.', - vanillaBreaker: false, - }, - { - id: 'egeadam-MoreScreams-1.0.2', - description: 'Allows you to hear your teammates scream while they die. (Default: 2 seconds)', - vanillaBreaker: false, - }, - { - id: 'Bibendi-AEIOUCompany-1.2.0', - description: 'Adds moon base alpha text-to-speech to the game. Also increases chat length to 1023.', - onlyClient: true, - vanillaBreaker: false, - } -] - const ModsHome: NextPage = (): JSX.Element => { - const globalStyles = useGlobalStyles() + const [selectedProfile, setSelectedProfile] = useState(allProfiles[0]) return <> Your Source for Lethal Company Mods - - - - - - + LethalModding.com logo - - - + + + LethalModding.com + + - {allMods.map((item, index) => { - const parts = item.id.split('-') - const creator = parts[0] - const version = parts[parts.length - 1] - const name = parts.slice(1, parts.length - 1).join('-') + + + } + sx={{ flex: 1, mx: 1 }} + placeholder="Search online for mods or profiles..." + /> - // const imageURL = `https://gcdn.thunderstore.io/live/repository/icons/${item.id}.png.128x128_q95.png` + + - const downloadURL = `https://thunderstore.io/package/download/${creator}/${name}/${version}/` + - - {/* {'icon'} */} + '& > *': { + borderRadius: 0, + display: 'flex', + flexDirection: 'column', + height: 'calc(100vh - 56px)', + overflow: 'hidden', + } + }} + > + + + + + - - - {item.vanillaBreaker && } - {item.onlyClient && } - {item.onlyServer && } - + + {allProfiles.map((profile) => )} + - - - - + + + + - - - - - - + + - - - - - - - - - - })} + + - - - {externalLinks.map((item, index) => - - - )} + '&::-webkit-scrollbar': { + width: '0.25em', + height: '0.25em', + }, + + '&::-webkit-scrollbar-thumb': { + backgroundColor: 'var(--accent)', + }, + + '& > *': { + backgroundColor: 'background.paper', + gap: 2.25, + }, + }} + > + + + + + + + + + + diff --git a/src/pages/tools/index.tsx b/src/pages/tools/index.tsx index 45fb576..81aca34 100644 --- a/src/pages/tools/index.tsx +++ b/src/pages/tools/index.tsx @@ -18,27 +18,33 @@ import { type NextPage } from 'next' import Head from 'next/head' import Image from 'next/image' import { ofetch } from 'ofetch' -import { useCallback, useEffect, useMemo, useState, type ChangeEvent } from 'react' +import { + useCallback, + useEffect, + useMemo, + useState, + type ChangeEvent, +} from 'react' import useGlobalStyles from 'styles/globalStyles' import { type Mod } from 'types/Mod' import { ModSort } from 'types/ModSort' type Filters = { - hasDonation: boolean | null - hasNSFW: boolean | null - hasWebsite: boolean | null + hasDonation: boolean | null + hasNSFW: boolean | null + hasWebsite: boolean | null isDeprecated: boolean | null isPinned: boolean | null maxDependencies: number minDependencies: number maxDownloads: number minDownloads: number - maxRatings: number - minRatings: number - maxSize: number - minSize: number - name: string - owner: string + maxRatings: number + minRatings: number + maxSize: number + minSize: number + name: string + owner: string [key: string]: string | number | boolean | null | undefined } @@ -50,16 +56,21 @@ const ToolsHome: NextPage = (): JSX.Element => { const [allMods, setAllMods] = useState([]) useEffect(() => { const timeout = setTimeout(() => { - ofetch('https://thunderstore.io/c/lethal-company/api/v1/package/') - .then((data) => { + ofetch('https://thunderstore.io/c/lethal-company/api/v1/package/').then( + data => { if (data.error) { console.error(data.error) return } - console.log('Got data from Thunderstore API', 'entries', data?.length ?? 0) + console.log( + 'Got data from Thunderstore API', + 'entries', + data?.length ?? 0 + ) setAllMods(data) - }) + } + ) }, 100) return () => clearTimeout(timeout) @@ -68,7 +79,9 @@ const ToolsHome: NextPage = (): JSX.Element => { const [allCategories, setAllCategories] = useState([]) useEffect(() => { const newCategories = new Set() - allMods.forEach(mod => mod.categories.forEach(category => newCategories.add(category))) + allMods.forEach(mod => + mod.categories.forEach(category => newCategories.add(category)) + ) setAllCategories(Array.from(newCategories).sort()) }, [allMods]) @@ -97,54 +110,83 @@ const ToolsHome: NextPage = (): JSX.Element => { const { name, value } = e.target setFilters(prev => ({ ...prev, [name]: value })) }, []) - const handleFilterCheckboxChange = useCallback((e: ChangeEvent) => { - const { name } = e.target - const oldValue = filters[name] - let value: boolean | null = null - - if (oldValue === null) { - value = false - } else if (oldValue) { - value = null - } else { - value = true - } + const handleFilterCheckboxChange = useCallback( + (e: ChangeEvent) => { + const { name } = e.target + const oldValue = filters[name] + let value: boolean | null = null + + if (oldValue === null) { + value = false + } else if (oldValue) { + value = null + } else { + value = true + } - setFilters(prev => ({ ...prev, [name]: value })) - }, [filters]) - - const [includesCategoryFilter, setIncludesCategoryFilter] = useState([]) - const handleIncludesCategoryFilterChange = useCallback((e: SelectChangeEvent) => { - if (typeof e.target.value === 'string') { - setIncludesCategoryFilter(e.target.value.split(',')) - } else { - setIncludesCategoryFilter(e.target.value) - } - }, []) - const [excludesCategoryFilter, setExcludesCategoryFilter] = useState([]) - const handleExcludesCategoryFilterChange = useCallback((e: SelectChangeEvent) => { - if (typeof e.target.value === 'string') { - setExcludesCategoryFilter(e.target.value.split(',')) - } else { - setExcludesCategoryFilter(e.target.value) - } - }, []) + setFilters(prev => ({ ...prev, [name]: value })) + }, + [filters] + ) + + const [includesCategoryFilter, setIncludesCategoryFilter] = useState< + string[] + >([]) + const handleIncludesCategoryFilterChange = useCallback( + (e: SelectChangeEvent) => { + if (typeof e.target.value === 'string') { + setIncludesCategoryFilter(e.target.value.split(',')) + } else { + setIncludesCategoryFilter(e.target.value) + } + }, + [] + ) + const [excludesCategoryFilter, setExcludesCategoryFilter] = useState< + string[] + >([]) + const handleExcludesCategoryFilterChange = useCallback( + (e: SelectChangeEvent) => { + if (typeof e.target.value === 'string') { + setExcludesCategoryFilter(e.target.value.split(',')) + } else { + setExcludesCategoryFilter(e.target.value) + } + }, + [] + ) const filteredMods = useMemo(() => { return allMods.filter(mod => { - if (includesCategoryFilter.length > 0 && !mod.categories.some(category => includesCategoryFilter.includes(category))) { + if ( + includesCategoryFilter.length > 0 && + !mod.categories.some(category => + includesCategoryFilter.includes(category) + ) + ) { return false } - if (excludesCategoryFilter.length > 0 && mod.categories.some(category => excludesCategoryFilter.includes(category))) { + if ( + excludesCategoryFilter.length > 0 && + mod.categories.some(category => + excludesCategoryFilter.includes(category) + ) + ) { return false } - if (filters.hasNSFW !== null && mod.has_nsfw_content !== filters.hasNSFW) { + if ( + filters.hasNSFW !== null && + mod.has_nsfw_content !== filters.hasNSFW + ) { return false } - if (filters.isDeprecated !== null && mod.is_deprecated !== filters.isDeprecated) { + if ( + filters.isDeprecated !== null && + mod.is_deprecated !== filters.isDeprecated + ) { return false } @@ -160,28 +202,48 @@ const ToolsHome: NextPage = (): JSX.Element => { return false } - if (filters.hasDonation !== null && (filters.hasDonation ? mod.donation_link === undefined : mod.donation_link !== undefined)) { + if ( + filters.hasDonation !== null && + (filters.hasDonation + ? mod.donation_link === undefined + : mod.donation_link !== undefined) + ) { return false } - if (filters.name && !mod.name.toLowerCase().includes(filters.name.toLowerCase())) { + if ( + filters.name && + !mod.name.toLowerCase().includes(filters.name.toLowerCase()) + ) { return false } - if (filters.owner && !mod.owner.toLowerCase().includes(filters.owner.toLowerCase())) { + if ( + filters.owner && + !mod.owner.toLowerCase().includes(filters.owner.toLowerCase()) + ) { return false } - if (filters.maxDependencies > -1 && mod.versions[0].dependencies.length > filters.maxDependencies) { + if ( + filters.maxDependencies > -1 && + mod.versions[0].dependencies.length > filters.maxDependencies + ) { return false } - if (filters.minDependencies > 0 && mod.versions[0].dependencies.length < filters.minDependencies) { + if ( + filters.minDependencies > 0 && + mod.versions[0].dependencies.length < filters.minDependencies + ) { return false } // downloads across all versions - const totalDownloads = mod.versions.reduce((acc, cur) => acc + cur.downloads, 0) + const totalDownloads = mod.versions.reduce( + (acc, cur) => acc + cur.downloads, + 0 + ) if (filters.maxDownloads > -1 && totalDownloads > filters.maxDownloads) { return false } @@ -190,21 +252,51 @@ const ToolsHome: NextPage = (): JSX.Element => { return false } - if (filters.maxSize > -1 && mod.versions[0].file_size > filters.maxSize * MEBI) { + if ( + filters.maxSize > -1 && + mod.versions[0].file_size > filters.maxSize * MEBI + ) { return false } - if (filters.minSize > 0 && mod.versions[0].file_size < filters.minSize * MEBI) { + if ( + filters.minSize > 0 && + mod.versions[0].file_size < filters.minSize * MEBI + ) { return false } - if (filters.hasWebsite !== null && (filters.hasWebsite ? mod.versions[0].website_url === '' : mod.versions[0].website_url !== '')) { + if ( + filters.hasWebsite !== null && + (filters.hasWebsite + ? mod.versions[0].website_url === '' + : mod.versions[0].website_url !== '') + ) { return false } return true }) - }, [allMods, includesCategoryFilter, excludesCategoryFilter, filters.hasNSFW, filters.isDeprecated, filters.isPinned, filters.maxRatings, filters.minRatings, filters.hasDonation, filters.name, filters.owner, filters.maxDependencies, filters.minDependencies, filters.maxDownloads, filters.minDownloads, filters.maxSize, filters.minSize, filters.hasWebsite]) + }, [ + allMods, + includesCategoryFilter, + excludesCategoryFilter, + filters.hasNSFW, + filters.isDeprecated, + filters.isPinned, + filters.maxRatings, + filters.minRatings, + filters.hasDonation, + filters.name, + filters.owner, + filters.maxDependencies, + filters.minDependencies, + filters.maxDownloads, + filters.minDownloads, + filters.maxSize, + filters.minSize, + filters.hasWebsite, + ]) // // Sorting @@ -220,19 +312,31 @@ const ToolsHome: NextPage = (): JSX.Element => { newMods.sort((a, b) => { if (sort.property === 'name') { - return sort.direction === 'asc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name) + return sort.direction === 'asc' + ? a.name.localeCompare(b.name) + : b.name.localeCompare(a.name) } else if (sort.property === 'owner') { - return sort.direction === 'asc' ? a.owner.localeCompare(b.owner) : b.owner.localeCompare(a.owner) + return sort.direction === 'asc' + ? a.owner.localeCompare(b.owner) + : b.owner.localeCompare(a.owner) } else if (sort.property === 'downloads') { const totalA = a.versions.reduce((acc, cur) => acc + cur.downloads, 0) const totalB = b.versions.reduce((acc, cur) => acc + cur.downloads, 0) return sort.direction === 'asc' ? totalA - totalB : totalB - totalA } else if (sort.property === 'ratings') { - return sort.direction === 'asc' ? a.rating_score - b.rating_score : b.rating_score - a.rating_score + return sort.direction === 'asc' + ? a.rating_score - b.rating_score + : b.rating_score - a.rating_score } else if (sort.property === 'size') { - return sort.direction === 'asc' ? a.versions[0].file_size - b.versions[0].file_size : b.versions[0].file_size - a.versions[0].file_size + return sort.direction === 'asc' + ? a.versions[0].file_size - b.versions[0].file_size + : b.versions[0].file_size - a.versions[0].file_size } else if (sort.property === 'dependencies') { - return sort.direction === 'asc' ? a.versions[0].dependencies.length - b.versions[0].dependencies.length : b.versions[0].dependencies.length - a.versions[0].dependencies.length + return sort.direction === 'asc' + ? a.versions[0].dependencies.length - + b.versions[0].dependencies.length + : b.versions[0].dependencies.length - + a.versions[0].dependencies.length } else { return 0 } @@ -249,415 +353,461 @@ const ToolsHome: NextPage = (): JSX.Element => { const [pageSize, setPageSize] = useState(100) const thisPage = useMemo(() => { - return sortedMods.slice((pageNumber-1)*pageSize, pageNumber*pageSize) + return sortedMods.slice((pageNumber - 1) * pageSize, pageNumber * pageSize) }, [sortedMods, pageNumber, pageSize]) const isMobile = useMediaQuery((theme: Theme) => theme.breakpoints.down('sm')) const isTablet = useMediaQuery((theme: Theme) => theme.breakpoints.down('md')) - const isSmallPC = useMediaQuery((theme: Theme) => theme.breakpoints.down('lg')) - - return <> - - Your Source for Lethal Company Tools - - - - - - - - - + const isSmallPC = useMediaQuery((theme: Theme) => + theme.breakpoints.down('lg') + ) + + return ( + <> + + Your Source for Lethal Company Tools + + + + + + *': { - flexGrow: 1, - }, - }} + className="column" + sx={{ my: 4 }} > - - - + - *': { - flex: 1, - }, - }} - > - - Includes Category - - - - Excludes Category - ( + + {selected.map(value => ( + + ))} + + )} + value={includesCategoryFilter} + variant="outlined" > - {selected.map(value => )} - } - value={excludesCategoryFilter} + {allCategories.map(x => ( + + {x} + + ))} + + + - {allCategories.map(x => - {x} - )} - - - - - *': { - flexGrow: 1, - } - }} - > - - - - - - - - - - - *': { - flexGrow: 1, - } - }} - > - - Donation Link - - - NSFW Content - - - Website - - - Deprecated - - - Pinned - - + Excludes Category + + + + + + 'fieldset legend span': { + fontSize: '0.8em', + }, - *': { - backgroundColor: 'var(--background)', - borderRadius: 2, - flex: '1 0 auto', - maxWidth: isMobile ? 'calc(100% - 1 * 8px)' : isSmallPC ? 'calc(50% - 1 * 8px)' : isTablet ? 'calc(33% - 1 * 8px)' : 'calc(25% - 1 * 8px)', - minWidth: isMobile ? 'calc(100% - 1 * 8px)' : isSmallPC ? 'calc(50% - 1 * 8px)' : isTablet ? 'calc(33% - 1 * 8px)' : 'calc(25% - 1 * 8px)', - px: 2, - py: 1, - - '&:hover': { - backgroundColor: 'var(--accent)', + '& > *': { + flexGrow: 1, }, + }} + > + + + + + + + + + - '.MuiTypography-root': { - lineHeight: 1.5, - overflowWrap: 'anywhere', + *': { + flexGrow: 1, }, - }, - }} - > - {thisPage.map(x => + + {' '} + Donation Link + + + {' '} + NSFW Content + + + {' '} + Website + + + {' '} + Deprecated + + + {' '} + Pinned + + + + + + *': { + backgroundColor: 'var(--background)', + borderRadius: 2, + flex: '1 0 auto', + maxWidth: isMobile + ? 'calc(100% - 1 * 8px)' + : isSmallPC + ? 'calc(50% - 1 * 8px)' + : isTablet + ? 'calc(33% - 1 * 8px)' + : 'calc(25% - 1 * 8px)', + minWidth: isMobile + ? 'calc(100% - 1 * 8px)' + : isSmallPC + ? 'calc(50% - 1 * 8px)' + : isTablet + ? 'calc(33% - 1 * 8px)' + : 'calc(25% - 1 * 8px)', + px: 2, + py: 1, + + '&:hover': { + backgroundColor: 'var(--accent)', + }, + + '.MuiTypography-root': { + lineHeight: 1.5, + overflowWrap: 'anywhere', + }, + + '.MuiTypography-body1': { + lineHeight: 1, + }, + + '.MuiTypography-body2': { + color: 'white', + fontSize: '0.7em', + }, + }, }} - target="_blank" > - {x.name} - - {x.owner} - - - {x.name} - - )} + {thisPage.map(x => ( + + {x.name} + {x.owner} + {x.name} + + ))} + + + - - - - + + ) } export default ToolsHome