mhsf-dev/src/components/ServerList.tsx

1201 lines
47 KiB
TypeScript
Raw Normal View History

2024-07-23 18:49:21 -05:00
"use client";
2024-08-24 12:37:08 -05:00
import { useEffect, useRef, useState } from "react";
2024-07-23 18:49:21 -05:00
import { Separator } from "@/components/ui/separator";
import { Button } from "@/components/ui/button";
import { Badge } from "./ui/badge";
import ServersList from "@/lib/list";
2024-08-24 12:37:08 -05:00
import {
CircleUser,
Network,
Sun,
Check,
XIcon,
Info,
ArrowDownZA,
LogIn,
ImageIcon,
} from "lucide-react";
2024-07-23 18:49:21 -05:00
import Stat from "./Stat";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import toast from "react-hot-toast";
2024-09-03 23:56:15 -05:00
import { allTags, allCategories } from "@/config/tags";
2024-07-23 18:49:21 -05:00
import IconDisplay from "./IconDisplay";
import InfiniteScroll from "react-infinite-scroll-component";
import { Spinner } from "./ui/spinner";
2024-08-03 09:51:45 -05:00
import { CommandIcon } from "lucide-react";
import { OnlineServer } from "@/lib/types/mh-server";
2024-07-23 18:49:21 -05:00
import { useEffectOnce } from "@/lib/useEffectOnce";
import ServerCard from "./ServerCard";
2024-08-03 09:51:45 -05:00
import events from "@/lib/commandEvent";
import { BorderBeam } from "@/components/effects/border-beam";
2024-08-15 23:24:15 -05:00
import { useRouter } from "@/lib/useRouter";
2024-08-17 14:15:30 -05:00
import {
Menubar,
MenubarCheckboxItem,
MenubarContent,
MenubarItem,
MenubarMenu,
MenubarRadioGroup,
MenubarRadioItem,
MenubarSeparator,
MenubarShortcut,
MenubarSub,
MenubarSubContent,
MenubarSubTrigger,
MenubarTrigger,
} from "@/components/ui/menubar";
2024-08-18 01:15:27 -05:00
import ClientFadeIn from "./ClientFadeIn";
import { Skeleton } from "./ui/skeleton";
2024-08-22 23:44:00 -05:00
import useClipboard from "@/lib/useClipboard";
2024-08-24 12:37:08 -05:00
import { SignedIn, SignedOut, useUser } from "@clerk/nextjs";
import Link from "next/link";
import SparklesText from "./effects/sparkles-text";
import Particles from "./effects/particles";
import { useTheme } from "next-themes";
import { ChatBubbleIcon, InputIcon } from "@radix-ui/react-icons";
import Marquee from "./effects/marquee";
import { cn } from "@/lib/utils";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { SignInPopover } from "./clerk/SignInPopoverButton";
import { BentoCard, BentoGrid } from "./effects/bento-grid";
const features = [
{
Icon: ChatBubbleIcon,
name: "Add a Discord widget",
description:
"Show where your players talk to each-other, including an online users count.",
2024-09-03 23:56:15 -05:00
href: "/docs/guides/customization",
2024-08-24 12:37:08 -05:00
cta: "Learn more",
background: <span />,
className: "lg:row-start-1 lg:row-end-2 lg:col-start-2 lg:col-end-3",
},
{
Icon: InputIcon,
name: "Descriptions",
2024-09-03 23:56:15 -05:00
href: "/docs/guides/customization",
2024-08-24 12:37:08 -05:00
cta: "Learn more",
description:
"Format your descriptions using Markdown to show what your server has to offer.",
background: <span />,
className: "lg:col-start-1 lg:col-end-2 lg:row-start-1 lg:row-end-2",
},
{
Icon: ImageIcon,
name: "Banners",
2024-09-03 23:56:15 -05:00
href: "/docs/guides/customization",
2024-08-24 12:37:08 -05:00
cta: "Learn more",
description:
"Show a banner with can contain images that show on your server page.",
background: <span />,
className: "lg:col-start-3 lg:col-end-4 lg:row-start-1 lg:row-end-2",
},
];
2024-07-23 18:49:21 -05:00
export default function ServerList() {
const [loading, setLoading]: any = useState(true);
const [randomText, setRandomText] = useState("");
const [motdList, setMotdList] = useState<any>({});
const allText = [""];
const getRandomText = () => {
return allText[Math.floor(Math.random() * allText.length)];
};
const [templateFilter, setTemplateFilter] = useState(false);
const [random, setRandom] = useState(false);
const [serverList, setServerList] = useState(new ServersList([]));
const [textCopied, setTextCopied] = useState(false);
2024-09-03 23:56:15 -05:00
const [padding, setPadding] = useState<string>("0");
2024-07-23 18:49:21 -05:00
const bigger = async (server: OnlineServer) =>
server.playerData.playerCount > 15;
const smaller = async (server: OnlineServer) =>
!server.staticInfo.alwaysOnline &&
server.playerData.playerCount < 15 &&
server.playerData.playerCount > 7;
const [nameFilters, setNameFilters] = useState<any>({});
const [inErrState, setErrState] = useState(false);
const [servers, setServers] = useState<Array<OnlineServer>>([]);
2024-08-22 23:44:00 -05:00
const clipboard = useClipboard();
2024-08-15 23:24:15 -05:00
const router = useRouter();
2024-09-03 23:56:15 -05:00
const { user, isSignedIn } = useUser();
2024-08-24 12:37:08 -05:00
const [pOS, setpOS] = useState(false);
2024-09-03 23:56:15 -05:00
const [ipr, setIPR] = useState<string>("4");
2024-09-06 20:14:59 -05:00
const [am, setAM] = useState(false);
2024-07-23 18:49:21 -05:00
const [filters, setFilters] = useState<
Array<(server: OnlineServer) => Promise<boolean>>
>([]);
const [randomData, setRandomData] = useState<OnlineServer | undefined>(
2024-08-03 09:51:45 -05:00
undefined
2024-07-23 18:49:21 -05:00
);
2024-08-24 12:37:08 -05:00
const { resolvedTheme } = useTheme();
const [color, setColor] = useState("#ffffff");
useEffect(() => {
setColor(resolvedTheme === "dark" ? "#ffffff" : "#000000");
}, [resolvedTheme]);
2024-07-23 18:49:21 -05:00
2024-09-03 23:56:15 -05:00
useEffect(() => {
if (isSignedIn) {
2024-09-06 20:14:59 -05:00
setAM(true);
2024-09-03 23:56:15 -05:00
console.log(user.publicMetadata);
2024-09-06 20:14:59 -05:00
2024-09-03 23:56:15 -05:00
setIPR((user.publicMetadata.ipr as string | undefined) || "4");
setPadding((user.publicMetadata.pad as string | undefined) || "0");
setpOS((user.publicMetadata.srv as boolean | undefined) || false);
}
}, [isSignedIn, user]);
2024-07-23 18:49:21 -05:00
useEffectOnce(() => {
setRandomText(getRandomText());
serverList
.fetchDataAndFilter()
.then(() => {
serverList.moveListDown();
let stringList: Array<{ server: string; motd: string }> = [];
let obj: any = {};
serverList.currentServers.forEach((b) => {
stringList.push({ motd: b.motd, server: b.name });
});
serverList.getMOTDs(stringList).then((c) => {
var updatedSL = motdList;
c.forEach((b: { server: string; motd: string }) => {
updatedSL[b.server] = b.motd;
});
setMotdList(updatedSL);
setServers(serverList.currentServers);
setLoading(false);
});
})
.catch(() => setErrState(true));
});
2024-08-24 12:37:08 -05:00
const ref = useRef(null);
const [clickedPage, setClickedPage] = useState("banners");
const [hero, setHero] = useState(false);
2024-07-23 18:49:21 -05:00
if (inErrState) {
return (
<>
<div className="flex justify-center">
<XIcon />
<br />
</div>
<div className="flex justify-center">
Hmm. Something is wrong. Reload the page.
</div>
</>
);
}
if (loading) {
return (
2024-09-06 20:14:59 -05:00
<div className="p-4">
2024-08-24 12:37:08 -05:00
<SignedIn>
<div className="md:grid md:grid-cols-3 gap-4 max-lg:grid-cols-2">
<Skeleton className="h-[112px] rounded-xl" />
<Skeleton className="h-[112px] rounded-xl" />
<Skeleton className="h-[112px] rounded-xl" />
</div>
<br />
<Separator />
<br />
<div className="md:grid md:grid-cols-4 gap-4">
<Skeleton className="h-[450px] rounded-xl" />
<Skeleton className="h-[450px] rounded-xl" />
<Skeleton className="h-[450px] rounded-xl" />
<Skeleton className="h-[450px] rounded-xl" />
</div>
</SignedIn>
2024-09-06 20:14:59 -05:00
</div>
2024-07-23 18:49:21 -05:00
);
}
return (
2024-09-03 23:56:15 -05:00
<div style={!pOS ? { padding: `${padding}px` } : undefined}>
2024-08-24 12:37:08 -05:00
<div className="p-0 branding-hero">
<>
{(!isSignedIn || hero) && (
<div className=" py-[300px] relative mx-auto mt-20 max-w-7xl px-6 text-center md:px-8 ">
2024-08-24 12:37:08 -05:00
<Particles
className="absolute inset-0 -z-10 block"
quantity={100}
ease={70}
size={0.05}
staticity={40}
color={color}
/>
<SparklesText className="animate-fade-in -translate-y-4 text-balance bg-gradient-to-br from-black from-30% to-black/40 bg-clip-text pb-6 text-5xl font-semibold leading-none tracking-tighter text-transparent opacity-0 [--animation-delay:200ms] sm:text-6xl md:text-7xl lg:text-8xl dark:from-white dark:to-white/40">
<>
Meet MHSF, <br className="hidden md:block" /> the modern
server finder
</>
</SparklesText>
<p className="animate-fade-in mb-12 -translate-y-4 text-balance text-lg tracking-tight text-gray-400 opacity-0 [--animation-delay:400ms] md:text-xl">
MHSF is the next generation server list for Minehut, with
interactive filters, <br className="hidden md:block" />{" "}
intuitive keyboard shortcuts, and everything between.
</p>
<Link href="#serverlist">
<Button className="animate-fade-in -translate-y-4 gap-1 rounded-lg text-white opacity-0 ease-in-out [--animation-delay:600ms] dark:text-black">
Look and see <ArrowDownZA size={18} />
</Button>
</Link>
<Popover>
<PopoverTrigger>
<SignedOut>
<Button
variant="outline"
className="animate-fade-in -translate-y-4 ml-2 gap-1 rounded-lg opacity-0 ease-in-out [--animation-delay:600ms]"
>
Login <LogIn size={18} />
</Button>
</SignedOut>
</PopoverTrigger>
<PopoverContent className="w-full">
<SignInPopover />
</PopoverContent>
</Popover>
<div
ref={ref}
className="relative mt-[8rem] animate-fade-up opacity-0 [--animation-delay:400ms] [perspective:2000px] after:absolute after:inset-0 after:z-50 after:[background:linear-gradient(to_top,hsl(var(--background))_30%,transparent)]"
2024-08-07 16:37:54 -05:00
>
2024-08-24 12:37:08 -05:00
<div
className={` rounded-xl border border-white/10 bg-white bg-opacity-[0.01] before:absolute before:bottom-1/2 before:left-0 before:top-0 before:h-full before:w-full before:opacity-0 before:[filter:blur(180px)] before:[background-image:linear-gradient(to_bottom,var(--color-one),var(--color-one),transparent_40%)] before:animate-image-glow`}
>
<BorderBeam
size={200}
duration={12}
delay={11}
colorFrom="var(--color-one)"
colorTo="var(--color-two)"
/>
<img
src="/branding/full-desktop.png"
alt="Hero Image"
className="relative hidden size-full rounded-[inherit] border object-contain dark:block"
/>
<img
src="/branding/full-desktop-light.png"
alt="Hero Image"
className="relative block size-full rounded-[inherit] border object-contain dark:hidden"
/>
</div>
2024-08-07 16:37:54 -05:00
</div>
2024-08-24 12:37:08 -05:00
<br />
<br />
<span className="animate-fade-in -translate-y-4 bg-green-400/60 px-4 py-2 rounded">
For players
</span>
<br />
<br />
<h1 className="animate-fade-in -translate-y-4 text-balance bg-gradient-to-br from-black from-30% to-black/40 bg-clip-text pb-6 text-2xl font-semibold leading-none tracking-tighter text-transparent opacity-0 [--animation-delay:200ms] sm:text-2xl md:text-3xl lg:text-4xl dark:from-white dark:to-white/40">
Find what you want now, not later
</h1>
<p className="animate-fade-in mb-12 -translate-y-4 text-balance text-lg tracking-tight text-gray-400 opacity-0 [--animation-delay:400ms] md:text-xl">
Use interactive filters and customization modes to find the
server of your choice
<br className="hidden md:block" /> in less than 10 minutes.
</p>
<div className="relative flex h-[300px] w-full flex-col items-center justify-center overflow-hidden rounded-lg bg-background ">
<Marquee className="[--duration:30s]">
{serverList.currentServers.slice(0, 20).map((server) => (
<div
2024-08-24 12:40:02 -05:00
key={server.name}
2024-08-24 12:37:08 -05:00
className={cn(
"relative w-64 cursor-pointer overflow-hidden rounded-xl border no-underline " +
"border-gray-950/[.1] bg-gray-950/[.01] hover:bg-gray-950/[.05] " +
"dark:border-gray-50/[.1] dark:bg-gray-50/[.10] dark:hover:bg-gray-50/[.15]"
)}
onClick={() => router.push(`/server/${server.name}`)}
>
<div className="items-center gap-2 p-4">
<div>
<div
className={
"text-lg font-bold dark:text-white" +
(server.author == undefined ? " mt-2" : "")
}
>
<IconDisplay server={server} className="mr-2" />
{server.name}
</div>
{server.author && (
<p className="text-sm dark:text-white/40">
by {server.author}
</p>
)}
</div>
</div>
</div>
))}
</Marquee>
<Marquee reverse className="[--duration:30s]">
{serverList.currentServers.slice(0, 20).map((server) => (
<div
2024-08-24 12:40:02 -05:00
key={server.name}
2024-08-24 12:37:08 -05:00
className={cn(
"relative w-64 cursor-pointer overflow-hidden rounded-xl border no-underline " +
"border-gray-950/[.1] bg-gray-950/[.01] hover:bg-gray-950/[.05] " +
"dark:border-gray-50/[.1] dark:bg-gray-50/[.10] dark:hover:bg-gray-50/[.15]"
)}
onClick={() => router.push(`/server/${server.name}`)}
>
<div className="items-center gap-2 p-4">
<div>
<div
className={
"text-lg font-bold dark:text-white" +
(server.author == undefined ? " mt-2" : "")
}
>
<IconDisplay server={server} className="mr-2" />
{server.name}
</div>
{server.author && (
<p className="text-sm dark:text-white/40">
by {server.author}
</p>
)}
</div>
</div>
</div>
))}
</Marquee>
<div className="pointer-events-none absolute inset-y-0 left-0 w-1/3 bg-gradient-to-r from-white dark:from-background" />
<div className="pointer-events-none absolute inset-y-0 right-0 w-1/3 bg-gradient-to-l from-white dark:from-background" />
</div>
<br />
<span className="animate-fade-in -translate-y-4 bg-yellow-400/60 px-4 py-2 rounded">
For server owners
</span>
<br />
<br />
<h1 className="animate-fade-in -translate-y-4 text-balance bg-gradient-to-br from-black from-30% to-black/40 bg-clip-text pb-6 text-2xl font-semibold leading-none tracking-tighter text-transparent opacity-0 [--animation-delay:200ms] sm:text-2xl md:text-3xl lg:text-4xl dark:from-white dark:to-white/40">
Make your server stand out
</h1>
<p className="animate-fade-in mb-12 -translate-y-4 text-balance text-lg tracking-tight text-gray-400 opacity-0 [--animation-delay:400ms] md:text-xl">
Servers can have custom banners, Discord widgets, color schemes,
and descriptions, making your server stand out with information
that can be shown to players.
</p>
<BentoGrid className="max-h-[100px]">
{features.map((feature, idx) => (
<BentoCard key={idx} {...feature} />
))}
</BentoGrid>
2024-09-06 20:14:59 -05:00
<Separator />
2024-08-24 12:37:08 -05:00
</div>
)}
<br />
</>
</div>
<div className="p-4">
<ClientFadeIn id="serverlist">
<div className="max-lg:grid-cols-2 grid grid-cols-3 gap-4">
<Stat
title="Players online"
desc={serverList.getExtraData().total_players.toString()}
icon={CircleUser}
/>
<Stat
title={
2024-08-18 01:15:27 -05:00
<div
className={
serverList.getExtraData().total_servers >= 3200
2024-08-24 12:37:08 -05:00
? "bg-clip-text text-transparent bg-gradient-to-r from-cyan-500 to-blue-500"
2024-08-18 01:15:27 -05:00
: ""
}
>
2024-08-24 12:37:08 -05:00
Servers online{" "}
2024-08-18 01:15:27 -05:00
</div>
2024-08-24 12:37:08 -05:00
}
className="relative z-0"
desc={
<div className="flex items-center">
<div
className={
serverList.getExtraData().total_servers >= 3200
? "bg-clip-text text-transparent bg-gradient-to-r from-cyan-500 to-blue-500 "
: ""
}
>
{serverList.getExtraData().total_servers.toString()}
</div>
{serverList.getExtraData().total_servers >= 3200 && (
<Tooltip>
<TooltipTrigger>
<Info size={16} className="ml-2" />
</TooltipTrigger>
<TooltipContent className="font-normal">
The server amount is over 3.2k, meaning that new servers
have to go into a queue before being able to be online.{" "}
<br />
(the server count isn't entirely accurate, so sometimes
you might not go into a queue even when the server count
is at 3.2k)
</TooltipContent>
</Tooltip>
)}
</div>
}
icon={Network}
>
{serverList.getExtraData().total_servers >= 3200 && (
<BorderBeam
size={135}
duration={12}
delay={9}
colorFrom="rgb(6 182 212)"
colorTo="rgb(59 130 246)"
/>
)}
</Stat>
<Stat
title="Current most popular server (in filter)"
className="max-lg:col-span-2"
desc={
<>
{serverList.currentServers[0] != undefined
? serverList.currentServers[0].name
: "None"}{" "}
{serverList.currentServers[0] != undefined && (
<IconDisplay server={serverList.currentServers[0]} />
)}
</>
}
icon={Sun}
/>
</div>
</ClientFadeIn>
<br id="serverlist" className="pb-14" />
<Separator />
<ClientFadeIn delay={100}>
2024-09-03 23:56:15 -05:00
<Menubar className="mt-3 ml-2 border rounded p-2 shadow">
2024-08-24 12:37:08 -05:00
<MenubarMenu>
<MenubarTrigger>Servers</MenubarTrigger>
<MenubarContent>
<MenubarItem
onSelect={() => events.emit("search-request-event")}
>
Search Servers
<MenubarShortcut className="flex items-center ml-3">
<CommandIcon size={14} />
+Shift+K
</MenubarShortcut>
</MenubarItem>
<MenubarItem
onSelect={() => {
setRandomData(serverList.getRandomServer());
setRandom(true);
}}
>
Pick Random Server
</MenubarItem>
<MenubarSeparator />
<MenubarItem
onSelect={() => {
toast.promise(
new Promise((s, e) => {
setLoading(true);
serverList
.fetchDataAndFilter()
.then(() => {
serverList.moveListDown();
let stringList: Array<{
server: string;
motd: string;
}> = [];
let obj: any = {};
2024-08-17 14:15:30 -05:00
2024-08-24 12:37:08 -05:00
serverList.currentServers.forEach((b) => {
stringList.push({ motd: b.motd, server: b.name });
2024-08-18 01:15:27 -05:00
});
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
serverList.getMOTDs(stringList).then((c) => {
var updatedSL = motdList;
c.forEach(
(b: { server: string; motd: string }) => {
updatedSL[b.server] = b.motd;
}
);
setMotdList(updatedSL);
setServers(serverList.currentServers);
setLoading(false);
s(false);
});
})
.catch(() => {
e();
2024-08-18 01:15:27 -05:00
});
2024-08-24 12:37:08 -05:00
}),
{
success: "Succesfully refreshed servers",
loading: "Refreshing...",
error: "Error while refreshing",
}
);
}}
>
Refresh
</MenubarItem>
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>Filter</MenubarTrigger>
<MenubarContent className="max-h-[400px] overflow-auto">
<MenubarRadioGroup
onValueChange={(v) => {
toast.promise(
new Promise((g, b) => {
if (v == "smaller") {
setTemplateFilter(true);
var filt = nameFilters;
filt["smaller-tf"] = true;
filt["bigger-tf"] = false;
setNameFilters(filt);
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
var filt2 = filters;
filt2.push(smaller);
if (filt2.includes(bigger)) {
filt2.splice(filt2.indexOf(bigger), 1);
}
setFilters(filt2);
serverList.editFilters(filters);
serverList.fetchDataAndFilter().then(() => {
serverList.moveListDown();
let stringList: Array<{
server: string;
motd: string;
}> = [];
let obj: any = {};
serverList.currentServers.forEach((b) => {
stringList.push({ motd: b.motd, server: b.name });
});
serverList.getMOTDs(stringList).then((c) => {
var updatedSL = motdList;
c.forEach(
(b: { server: string; motd: string }) => {
updatedSL[b.server] = b.motd;
}
);
setMotdList(updatedSL);
setServers(serverList.currentServers);
g(undefined);
2024-08-18 01:15:27 -05:00
});
2024-07-23 18:49:21 -05:00
});
2024-08-24 12:37:08 -05:00
} else if (v == "bigger") {
setTemplateFilter(true);
var filt = nameFilters;
filt["smaller-tf"] = false;
filt["bigger-tf"] = true;
setNameFilters(filt);
var filt2 = filters;
filt2.push(bigger);
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
filt2.splice(filt2.indexOf(smaller), 1);
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
setFilters(filt2);
serverList.editFilters(filters);
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
serverList.fetchDataAndFilter().then(() => {
serverList.moveListDown();
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
let stringList: Array<{
server: string;
motd: string;
}> = [];
let obj: any = {};
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
serverList.currentServers.forEach((b) => {
stringList.push({ motd: b.motd, server: b.name });
});
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
serverList.getMOTDs(stringList).then((c) => {
var updatedSL = motdList;
c.forEach(
(b: { server: string; motd: string }) => {
updatedSL[b.server] = b.motd;
}
);
setMotdList(updatedSL);
setServers(serverList.currentServers);
g(undefined);
2024-08-18 01:15:27 -05:00
});
2024-07-23 18:49:21 -05:00
});
2024-08-24 12:37:08 -05:00
} else {
var filt = nameFilters;
filt["smaller-tf"] = false;
filt["bigger-tf"] = false;
setNameFilters(filt);
setTemplateFilter(false);
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
var filt2 = filters;
filt2.splice(filt2.indexOf(smaller), 1);
filt2.splice(filt2.indexOf(bigger), 1);
setFilters(filt2);
console.log(filters, filters.includes(smaller));
serverList.editFilters(filters);
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
serverList.fetchDataAndFilter().then(() => {
serverList.moveListDown();
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
let stringList: Array<{
server: string;
motd: string;
}> = [];
let obj: any = {};
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
serverList.currentServers.forEach((b) => {
stringList.push({ motd: b.motd, server: b.name });
});
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
serverList.getMOTDs(stringList).then((c) => {
var updatedSL = motdList;
c.forEach(
(b: { server: string; motd: string }) => {
updatedSL[b.server] = b.motd;
}
);
setMotdList(updatedSL);
setServers(serverList.currentServers);
g(undefined);
2024-08-18 01:15:27 -05:00
});
2024-07-23 18:49:21 -05:00
});
2024-08-24 12:37:08 -05:00
}
}),
{
error: "Error while changing filters",
loading: "Changing filters...",
success: "Changed filters!",
2024-08-18 01:15:27 -05:00
}
2024-08-24 12:37:08 -05:00
);
}}
value={(() => {
if (nameFilters["smaller-tf"]) {
return "smaller";
} else if (nameFilters["bigger-tf"]) {
return "bigger";
} else {
return "none";
2024-07-23 18:49:21 -05:00
}
2024-08-24 12:37:08 -05:00
})()}
>
<MenubarRadioItem value="smaller">
<div className="block">
Only allow smaller servers
<br />
<span className="text-sm text-muted-foreground">
Only allow servers that have the player range 7-15, and
cannot <br />
be Always Online.
</span>
</div>
</MenubarRadioItem>
<MenubarRadioItem value="bigger">
<div className="block">
Only allow bigger servers
<br />
<span className="text-sm text-muted-foreground">
Only allow servers with more than 15 players.
</span>
</div>
</MenubarRadioItem>
<MenubarRadioItem value="none">
No/custom requirements
</MenubarRadioItem>
</MenubarRadioGroup>
<MenubarSeparator />
<MenubarSub>
<span className="text-sm text-muted-foreground ml-2">
Tags
</span>
</MenubarSub>
{allTags.map((tag) => (
<div key={tag.docsName}>
{tag.docsName && tag.__filter == undefined && (
<MenubarCheckboxItem
disabled={templateFilter && tag.__disab != undefined}
id={tag.docsName}
checked={(() => {
return nameFilters["t-" + tag.docsName];
})()}
onCheckedChange={async (b) => {
var filt = nameFilters;
filt["t-" + tag.docsName] = b;
setNameFilters(filt);
if (b) {
var filt2 = filters;
filt2.push(tag.condition);
setFilters(filt2);
} else {
var filt2 = filters;
filt2.splice(filt2.indexOf(tag.condition), 1);
setFilters(filt2);
}
serverList.editFilters(filters);
serverList.fetchDataAndFilter().then(() => {
serverList.moveListDown();
let stringList: Array<{
server: string;
motd: string;
}> = [];
let obj: any = {};
serverList.currentServers.forEach((b) => {
stringList.push({ motd: b.motd, server: b.name });
});
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
serverList.getMOTDs(stringList).then((c) => {
var updatedSL = motdList;
c.forEach(
(b: { server: string; motd: string }) => {
updatedSL[b.server] = b.motd;
}
);
setMotdList(updatedSL);
setServers(serverList.currentServers);
2024-08-18 01:15:27 -05:00
});
2024-08-17 14:15:30 -05:00
});
2024-08-24 12:37:08 -05:00
}}
>
<Badge variant={tag.role} className="mr-1">
{tag.docsName}
</Badge>
</MenubarCheckboxItem>
)}
</div>
))}
<MenubarSeparator />
<MenubarSub>
<span className="text-sm text-muted-foreground ml-2">
Categories
</span>
</MenubarSub>
{allCategories.map((categorie) => (
<MenubarCheckboxItem
id={categorie.name}
key={categorie.name}
onCheckedChange={async (b) => {
var filt = nameFilters;
filt["c-" + categorie.name] = b;
setNameFilters(filt);
if (b) {
var filt2 = filters;
filt2.push(categorie.condition);
setFilters(filt2);
} else {
var filt2 = filters;
filt2.splice(filt2.indexOf(categorie.condition), 1);
setFilters(filt2);
}
serverList.editFilters(filters);
serverList.fetchDataAndFilter().then(() => {
serverList.moveListDown();
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
let stringList: Array<{
server: string;
motd: string;
}> = [];
let obj: any = {};
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
serverList.currentServers.forEach((b) => {
stringList.push({ motd: b.motd, server: b.name });
});
2024-07-23 18:49:21 -05:00
2024-08-24 12:37:08 -05:00
serverList.getMOTDs(stringList).then((c) => {
var updatedSL = motdList;
c.forEach((b: { server: string; motd: string }) => {
updatedSL[b.server] = b.motd;
});
setMotdList(updatedSL);
setServers(serverList.currentServers);
2024-08-18 01:15:27 -05:00
});
2024-07-23 18:49:21 -05:00
});
2024-08-24 12:37:08 -05:00
}}
checked={(() => {
return nameFilters["c-" + categorie.name];
})()}
2024-08-18 01:15:27 -05:00
>
2024-08-24 12:37:08 -05:00
<Badge variant={categorie.role} className="mr-1">
{categorie.name}
</Badge>
</MenubarCheckboxItem>
))}
</MenubarContent>
</MenubarMenu>
<MenubarMenu>
<MenubarTrigger>View</MenubarTrigger>
<MenubarContent>
<MenubarSub>
<MenubarSubTrigger>Grid</MenubarSubTrigger>
<MenubarSubContent>
<MenubarRadioGroup value={ipr} onValueChange={setIPR}>
<MenubarRadioItem value="4">
4 items per row
</MenubarRadioItem>
<MenubarRadioItem value="5">
5 items per row
</MenubarRadioItem>
<MenubarRadioItem value="6">
6 items per row
</MenubarRadioItem>
</MenubarRadioGroup>
</MenubarSubContent>
</MenubarSub>
<MenubarSub>
<MenubarSubTrigger>Padding</MenubarSubTrigger>
<MenubarSubContent>
<MenubarRadioGroup
value={padding.toString()}
2024-09-06 20:14:59 -05:00
onValueChange={(v) => {
if (am)
toast(
<span>
These settings will not change over reloads
because you have account specific options enabled
<Button
variant="link"
className="p-0 m-0"
onClick={() =>
router.push("/account/settings/options")
}
>
Change your preferences
</Button>
</span>,
{ icon: "⚠️" }
);
setPadding(v);
}}
2024-08-24 12:37:08 -05:00
>
<MenubarRadioItem value="0">Default</MenubarRadioItem>
<MenubarSeparator />
<MenubarRadioItem value="15">15px</MenubarRadioItem>
<MenubarRadioItem value="30">30px</MenubarRadioItem>
<MenubarRadioItem value="40">40px</MenubarRadioItem>
<MenubarRadioItem value="60">60px</MenubarRadioItem>
<MenubarRadioItem value="100">100px</MenubarRadioItem>
<MenubarRadioItem value="200">200px</MenubarRadioItem>
</MenubarRadioGroup>
<MenubarSeparator />
<MenubarCheckboxItem checked={pOS} onCheckedChange={setpOS}>
Only use padding on servers
</MenubarCheckboxItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSub>
<MenubarSubTrigger>Sort</MenubarSubTrigger>
<MenubarSubContent>
<MenubarRadioGroup
value="joins"
2024-09-06 20:14:59 -05:00
onValueChange={(c) => {
if (c === "favorites") router.push("/sort/favorites");
}}
2024-08-24 12:37:08 -05:00
>
<MenubarRadioItem value="joins">
Players Online
</MenubarRadioItem>
<MenubarRadioItem value="favorites">
Favorites
</MenubarRadioItem>
</MenubarRadioGroup>
</MenubarSubContent>
</MenubarSub>
2024-09-03 23:56:15 -05:00
<MenubarSeparator />
2024-08-24 12:37:08 -05:00
<SignedIn>
<MenubarCheckboxItem checked={hero} onCheckedChange={setHero}>
Show Hero
</MenubarCheckboxItem>
</SignedIn>
2024-09-03 23:56:15 -05:00
<MenubarItem onClick={() => router.push("/docs")}>
View the docs
</MenubarItem>
{am && (
2024-09-06 20:14:59 -05:00
<MenubarItem
onClick={() => router.push("/account/settings")}
className="block"
>
Using saved settings in Preferences
<br />
<span className="text-muted-foreground text-xs">
Your using settings stored on your account, that are not
temporary.
</span>
2024-09-03 23:56:15 -05:00
</MenubarItem>
)}
2024-08-24 12:37:08 -05:00
</MenubarContent>
</MenubarMenu>
</Menubar>
</ClientFadeIn>
<Dialog open={random} onOpenChange={setRandom}>
<DialogContent>
{randomData == undefined && <>No data to randomize</>}
{randomData != undefined && (
<DialogHeader>
<DialogTitle>
<IconDisplay server={randomData} /> {randomData.name}
{randomData.author != undefined ? (
<div className="text-sm text-muted-foreground">
by {randomData.author}
</div>
2024-07-23 18:49:21 -05:00
) : (
2024-08-24 12:37:08 -05:00
<br />
2024-07-23 18:49:21 -05:00
)}
2024-08-24 12:37:08 -05:00
<TagShower server={randomData} />
</DialogTitle>
<DialogDescription className="float-left inline">
<span className="flex items-center">
{randomData.playerData.playerCount == 0 ? (
<div
className="items-center border"
style={{
width: ".5rem",
height: ".5rem",
borderRadius: "9999px",
}}
/>
2024-07-23 18:49:21 -05:00
) : (
2024-08-24 12:37:08 -05:00
<div
className="items-center"
style={{
backgroundColor: "#0cce6b",
width: ".5rem",
height: ".5rem",
borderRadius: "9999px",
}}
/>
2024-07-23 18:49:21 -05:00
)}
2024-08-24 12:37:08 -05:00
<span className="pl-1">
{randomData.playerData.playerCount}{" "}
{randomData.playerData.playerCount == 1
? "player"
: "players"}{" "}
currently online
</span>
</span>
<br />
<strong>Server IP</strong>
<br />
<br />
<code className="border p-3 rounded">
{randomData.name}.minehut.gg{" "}
<Button
size="icon"
className="ml-1 h-[20px]"
onClick={() => {
setTextCopied(true);
clipboard.writeText(
randomData.name + ".mshf.minehut.gg"
);
toast.success("Copied!");
setTimeout(() => setTextCopied(false), 1000);
}}
>
{textCopied ? (
<Check size={16} className="flex items-center" />
) : (
<p>Copy</p>
)}
</Button>
</code>
</DialogDescription>
</DialogHeader>
)}
</DialogContent>
</Dialog>
<br />
<InfiniteScroll
dataLength={serverList.currentServers.length}
hasMore={serverList.hasMore}
next={() => {
serverList.moveListDown();
let stringList: Array<{ server: string; motd: string }> = [];
serverList.currentServers.forEach((b) => {
stringList.push({ motd: b.motd, server: b.name });
2024-07-23 18:49:21 -05:00
});
2024-08-24 12:37:08 -05:00
serverList.getMOTDs(stringList).then((c) => {
var updatedSL = motdList;
c.forEach((b: { server: string; motd: string }) => {
updatedSL[b.server] = b.motd;
});
setMotdList(updatedSL);
setServers(serverList.currentServers);
setLoading(false);
});
}}
loader={<Spinner className="flex items-center" />}
endMessage={
<p
style={{ textAlign: "center" }}
dangerouslySetInnerHTML={{
__html:
randomText + "<br /> <strong>You've seen it all</strong>",
}}
/>
}
style={{
overflow: "hidden !important",
2024-09-03 23:56:15 -05:00
paddingLeft: pOS ? `${padding}px` : 6,
paddingRight: pOS ? `${padding}px` : 6,
2024-08-24 12:37:08 -05:00
}}
>
<ClientFadeIn delay={200}>
{/** This looks stupid, but its the only way that works */}
<div
className={
" sm:grid " +
(ipr == "4"
? "lg:grid-cols-4"
: ipr == "5"
? "lg:grid-cols-5"
: ipr == "6"
? "lg:grid-cols-6"
: "") +
" gap-4 sm:grid-cols-2"
}
>
{servers.map((b: any) => (
<>
<ServerCard b={b} motd={motdList[b.name]} />
</>
))}
</div>
</ClientFadeIn>
</InfiniteScroll>
</div>
</div>
2024-07-23 18:49:21 -05:00
);
}
2024-08-11 22:14:36 -05:00
export function TagShower(props: {
server: OnlineServer;
className?: string;
unclickable?: boolean;
}) {
2024-07-23 18:49:21 -05:00
const [loading, setLoading] = useState(true);
const [compatiableTags, setCompatiableTags] = useState<
Array<{
name: string;
docsName?: string;
tooltip: string;
htmlDocs: string;
role:
| "default"
| "destructive"
| "outline"
| "secondary"
| "red"
| "orange"
| "yellow"
| "green"
| "lime"
| "blue"
| "teal"
| "cyan"
| "violet"
| "indigo"
| "purple"
| "fuchsia"
| "pink";
}>
>([]);
useEffectOnce(() => {
if (loading) {
allTags.forEach((tag) => {
tag.condition(props.server).then((b) => {
if (b && tag.primary) {
tag.name(props.server).then((n) => {
compatiableTags.push({
name: n,
docsName: tag.docsName,
tooltip: tag.tooltipDesc,
htmlDocs: tag.htmlDocs,
role: tag.role == undefined ? "secondary" : tag.role,
});
setLoading(false);
});
}
});
});
}
});
if (loading) {
return <></>;
}
return (
2024-08-22 23:44:00 -05:00
<div className="font-normal tracking-normal">
2024-07-23 18:49:21 -05:00
{compatiableTags.map((t) => (
<>
2024-08-11 22:14:36 -05:00
{props.unclickable && (
<Badge variant={t.role} className={props.className}>
{t.name}
</Badge>
)}
{!props.unclickable && (
<Dialog key={t.name}>
<DialogTrigger>
<Tooltip>
<TooltipTrigger>
<Badge variant={t.role} className={props.className}>
{t.name}
</Badge>
</TooltipTrigger>
<TooltipContent>
<div className="font-normal">
{t.tooltip}
<br />
Click the tag to learn more about it.
</div>
</TooltipContent>
</Tooltip>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>
{'"'}
{t.docsName == undefined ? t.name : t.docsName}
{'"'} documentation
</DialogTitle>
<DialogDescription
dangerouslySetInnerHTML={{
__html: t.htmlDocs,
}}
/>
</DialogHeader>
</DialogContent>
</Dialog>
)}
2024-07-23 18:49:21 -05:00
</>
))}
2024-08-22 23:44:00 -05:00
</div>
2024-07-23 18:49:21 -05:00
);
}