2024-07-23 18:49:21 -05:00
|
|
|
"use client";
|
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
|
import {
|
|
|
|
|
Card,
|
|
|
|
|
CardContent,
|
|
|
|
|
CardDescription,
|
|
|
|
|
CardFooter,
|
|
|
|
|
CardHeader,
|
|
|
|
|
CardTitle,
|
|
|
|
|
} from "@/components/ui/card";
|
|
|
|
|
import { Separator } from "@/components/ui/separator";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
import { Badge } from "./ui/badge";
|
|
|
|
|
import ServersList from "@/lib/list";
|
|
|
|
|
import {
|
|
|
|
|
CircleUser,
|
|
|
|
|
Network,
|
|
|
|
|
Copy,
|
|
|
|
|
Sun,
|
|
|
|
|
SlidersHorizontal,
|
|
|
|
|
Check,
|
|
|
|
|
XIcon,
|
|
|
|
|
} from "lucide-react";
|
|
|
|
|
import Stat from "./Stat";
|
|
|
|
|
import {
|
|
|
|
|
Dialog,
|
|
|
|
|
DialogContent,
|
|
|
|
|
DialogDescription,
|
|
|
|
|
DialogHeader,
|
|
|
|
|
DialogTitle,
|
|
|
|
|
DialogTrigger,
|
|
|
|
|
} from "@/components/ui/dialog";
|
|
|
|
|
import {
|
|
|
|
|
ResizableHandle,
|
|
|
|
|
ResizablePanel,
|
|
|
|
|
ResizablePanelGroup,
|
|
|
|
|
} from "@/components/ui/resizable";
|
|
|
|
|
import {
|
|
|
|
|
ContextMenu,
|
|
|
|
|
ContextMenuCheckboxItem,
|
|
|
|
|
ContextMenuContent,
|
|
|
|
|
ContextMenuItem,
|
|
|
|
|
ContextMenuLabel,
|
|
|
|
|
ContextMenuRadioGroup,
|
|
|
|
|
ContextMenuRadioItem,
|
|
|
|
|
ContextMenuSeparator,
|
|
|
|
|
ContextMenuShortcut,
|
|
|
|
|
ContextMenuSub,
|
|
|
|
|
ContextMenuSubContent,
|
|
|
|
|
ContextMenuSubTrigger,
|
|
|
|
|
ContextMenuTrigger,
|
|
|
|
|
} from "@/components/ui/context-menu";
|
|
|
|
|
import {
|
|
|
|
|
Command,
|
|
|
|
|
CommandDialog,
|
|
|
|
|
CommandEmpty,
|
|
|
|
|
CommandGroup,
|
|
|
|
|
CommandInput,
|
|
|
|
|
CommandItem,
|
|
|
|
|
CommandList,
|
|
|
|
|
CommandSeparator,
|
|
|
|
|
CommandShortcut,
|
|
|
|
|
} from "@/components/ui/command";
|
|
|
|
|
import {
|
|
|
|
|
Tooltip,
|
|
|
|
|
TooltipContent,
|
|
|
|
|
TooltipTrigger,
|
|
|
|
|
} from "@/components/ui/tooltip";
|
|
|
|
|
import toast from "react-hot-toast";
|
|
|
|
|
import { allTags, allCategories } from "@/allTags";
|
|
|
|
|
import IconDisplay from "./IconDisplay";
|
|
|
|
|
import InfiniteScroll from "react-infinite-scroll-component";
|
|
|
|
|
import Markdown from "react-markdown";
|
|
|
|
|
import {
|
|
|
|
|
Popover,
|
|
|
|
|
PopoverContent,
|
|
|
|
|
PopoverTrigger,
|
|
|
|
|
} from "@/components/ui/popover";
|
|
|
|
|
import remarkGfm from "remark-gfm";
|
|
|
|
|
import { Checkbox } from "@/components/ui/checkbox";
|
|
|
|
|
import { Spinner } from "./ui/spinner";
|
|
|
|
|
import { Input } from "@/components/ui/input";
|
|
|
|
|
import { Label } from "@/components/ui/label";
|
|
|
|
|
import { OnlineServer, ServerResponse } from "./ServerView";
|
|
|
|
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
|
|
|
import { useEffectOnce } from "@/lib/useEffectOnce";
|
|
|
|
|
import ServerCard from "./ServerCard";
|
|
|
|
|
import { useHotkeys } from "react-hotkeys-hook";
|
|
|
|
|
|
|
|
|
|
export default function ServerList() {
|
|
|
|
|
const [loading, setLoading]: any = useState(true);
|
|
|
|
|
const [command, setCommand] = useState(false);
|
|
|
|
|
const [randomText, setRandomText] = useState("");
|
|
|
|
|
const [motdList, setMotdList] = useState<any>({});
|
|
|
|
|
const allText = [""];
|
|
|
|
|
const getRandomText = () => {
|
|
|
|
|
return allText[Math.floor(Math.random() * allText.length)];
|
|
|
|
|
};
|
|
|
|
|
const [searchRes, setSearchRes] = useState<any>(undefined);
|
|
|
|
|
const [templateFilter, setTemplateFilter] = useState(false);
|
|
|
|
|
const [random, setRandom] = useState(false);
|
|
|
|
|
const [serverList, setServerList] = useState(new ServersList([]));
|
|
|
|
|
const [textCopied, setTextCopied] = useState(false);
|
|
|
|
|
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>({});
|
|
|
|
|
useHotkeys("ctrl+k", () => setCommand(true), []);
|
|
|
|
|
const [inErrState, setErrState] = useState(false);
|
|
|
|
|
const [servers, setServers] = useState<Array<OnlineServer>>([]);
|
|
|
|
|
const [filters, setFilters] = useState<
|
|
|
|
|
Array<(server: OnlineServer) => Promise<boolean>>
|
|
|
|
|
>([]);
|
|
|
|
|
const [randomData, setRandomData] = useState<OnlineServer | undefined>(
|
|
|
|
|
undefined,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
});
|
|
|
|
|
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 (
|
|
|
|
|
<>
|
|
|
|
|
<Spinner className=" flex items-center" />
|
|
|
|
|
<br />
|
|
|
|
|
<div
|
|
|
|
|
className=" flex justify-center"
|
|
|
|
|
dangerouslySetInnerHTML={{ __html: randomText }}
|
|
|
|
|
></div>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<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="Servers online"
|
|
|
|
|
desc={serverList.getExtraData().total_servers.toString()}
|
|
|
|
|
icon={Network}
|
|
|
|
|
/>
|
|
|
|
|
<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>
|
|
|
|
|
<br />
|
|
|
|
|
<Separator />
|
|
|
|
|
<div className=" mt-3 ml-3">
|
|
|
|
|
<Button
|
|
|
|
|
onClick={() => setCommand(true)}
|
|
|
|
|
variant="secondary"
|
|
|
|
|
className=" max-lg:mb-3"
|
|
|
|
|
>
|
|
|
|
|
Search <code className="ml-2">Ctrl+K</code>
|
|
|
|
|
</Button>
|
|
|
|
|
<Popover>
|
|
|
|
|
<PopoverTrigger>
|
|
|
|
|
<Button className=" ml-3" variant="secondary">
|
|
|
|
|
Filter
|
|
|
|
|
<code className="ml-2">{filters.length}</code>
|
|
|
|
|
</Button>
|
|
|
|
|
</PopoverTrigger>
|
|
|
|
|
<PopoverContent className=" w-[390px] z-3">
|
|
|
|
|
<RadioGroup
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
} 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);
|
|
|
|
|
|
|
|
|
|
filt2.splice(filt2.indexOf(smaller), 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);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
var filt = nameFilters;
|
|
|
|
|
filt["smaller-tf"] = false;
|
|
|
|
|
filt["bigger-tf"] = false;
|
|
|
|
|
setNameFilters(filt);
|
|
|
|
|
setTemplateFilter(false);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
{
|
|
|
|
|
error: "Error while changing filters",
|
|
|
|
|
loading: "Changing filters...",
|
|
|
|
|
success: "Changed filters!",
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}}
|
|
|
|
|
defaultValue={(() => {
|
|
|
|
|
if (nameFilters["smaller-tf"]) {
|
|
|
|
|
return "smaller";
|
|
|
|
|
} else if (nameFilters["bigger-tf"]) {
|
|
|
|
|
return "bigger";
|
|
|
|
|
} else {
|
|
|
|
|
return "none";
|
|
|
|
|
}
|
|
|
|
|
})()}
|
|
|
|
|
>
|
|
|
|
|
<div className=" items-top flex space-x-2">
|
|
|
|
|
<RadioGroupItem id="smaller" value="smaller" />
|
|
|
|
|
<div className=" grid gap-1.5 leading-none">
|
|
|
|
|
<label
|
|
|
|
|
htmlFor="smaller"
|
|
|
|
|
className=" text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
|
|
|
>
|
|
|
|
|
Only allow smaller servers
|
|
|
|
|
</label>
|
|
|
|
|
<p className=" text-sm text-muted-foreground">
|
|
|
|
|
Server have 15-7 players, cannot be{" "}
|
|
|
|
|
<Badge variant="secondary">Always Online</Badge>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className=" items-top flex space-x-2">
|
|
|
|
|
<RadioGroupItem id="bigger" value="bigger" />
|
|
|
|
|
<div className=" grid gap-1.5 leading-none">
|
|
|
|
|
<label
|
|
|
|
|
htmlFor="bigger"
|
|
|
|
|
className=" text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
|
|
|
>
|
|
|
|
|
Only allow bigger servers
|
|
|
|
|
</label>
|
|
|
|
|
<p className=" text-sm text-muted-foreground">
|
|
|
|
|
Server has 16 players or more
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className=" items-top flex space-x-2">
|
|
|
|
|
<RadioGroupItem id="none" value="none" />
|
|
|
|
|
<div className=" grid gap-1.5 leading-none">
|
|
|
|
|
<label
|
|
|
|
|
htmlFor="none"
|
|
|
|
|
className=" text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
|
|
|
>
|
|
|
|
|
Custom/no requirements
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</RadioGroup>
|
|
|
|
|
<br />
|
|
|
|
|
<br />
|
|
|
|
|
<strong className=" pb-2">Tags</strong>
|
|
|
|
|
<br />
|
|
|
|
|
|
|
|
|
|
{allTags.map((tag) => (
|
|
|
|
|
<div key={tag.docsName}>
|
|
|
|
|
{tag.docsName && tag.__filter == undefined && (
|
|
|
|
|
<div className=" items-top flex space-x-2 pb-1">
|
|
|
|
|
<Checkbox
|
|
|
|
|
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 });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<div className=" grid gap-1.5 leading-none">
|
|
|
|
|
<label
|
|
|
|
|
htmlFor={tag.docsName}
|
|
|
|
|
className=" text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
|
|
|
>
|
|
|
|
|
<Badge variant={tag.role} className=" mr-1">
|
|
|
|
|
{tag.docsName}
|
|
|
|
|
</Badge>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
<br />
|
|
|
|
|
<strong>Categories</strong>
|
|
|
|
|
<br />
|
|
|
|
|
{allCategories.map((categorie) => (
|
|
|
|
|
<div
|
|
|
|
|
className=" items-top flex space-x-2 pb-1"
|
|
|
|
|
key={categorie.name}
|
|
|
|
|
>
|
|
|
|
|
<Checkbox
|
|
|
|
|
id={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();
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}}
|
|
|
|
|
checked={(() => {
|
|
|
|
|
return nameFilters["c-" + categorie.name];
|
|
|
|
|
})()}
|
|
|
|
|
/>
|
|
|
|
|
<div className=" grid gap-1.5 leading-none">
|
|
|
|
|
<label
|
|
|
|
|
htmlFor={categorie.name}
|
|
|
|
|
className=" text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
|
|
|
|
>
|
|
|
|
|
<Badge variant={categorie.role} className=" mr-1">
|
|
|
|
|
{categorie.name}
|
|
|
|
|
</Badge>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
</PopoverContent>
|
|
|
|
|
</Popover>
|
|
|
|
|
<Button
|
|
|
|
|
variant="secondary"
|
|
|
|
|
className=" md:ml-3 "
|
|
|
|
|
onClick={() => {
|
|
|
|
|
setRandomData(serverList.getRandomServer());
|
|
|
|
|
setRandom(true);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Pick Random Server
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant="secondary"
|
|
|
|
|
className=" ml-3"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
toast.promise(
|
|
|
|
|
new Promise((s, e) => {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
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);
|
|
|
|
|
s(false);
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {
|
|
|
|
|
e();
|
|
|
|
|
});
|
|
|
|
|
}),
|
|
|
|
|
{
|
|
|
|
|
success: "Succesfully refreshed servers",
|
|
|
|
|
loading: "Refreshing...",
|
|
|
|
|
error: "Error while refreshing",
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Refresh
|
|
|
|
|
</Button>
|
|
|
|
|
<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>
|
|
|
|
|
) : (
|
|
|
|
|
<br />
|
|
|
|
|
)}
|
|
|
|
|
<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",
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<div
|
|
|
|
|
className=" items-center"
|
|
|
|
|
style={{
|
|
|
|
|
backgroundColor: "#0cce6b",
|
|
|
|
|
width: ".5rem",
|
|
|
|
|
height: ".5rem",
|
|
|
|
|
borderRadius: "9999px",
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<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">
|
2024-07-26 00:46:53 -05:00
|
|
|
{randomData.name}.mshf.minehut.gg{" "}
|
2024-07-23 18:49:21 -05:00
|
|
|
<Button
|
|
|
|
|
size="icon"
|
|
|
|
|
className=" ml-1 h-[20px]"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
setTextCopied(true);
|
|
|
|
|
navigator.clipboard.writeText(
|
2024-07-26 00:46:53 -05:00
|
|
|
randomData.name + ".mshf.minehut.gg",
|
2024-07-23 18:49:21 -05:00
|
|
|
);
|
|
|
|
|
toast.success("Copied!");
|
|
|
|
|
setTimeout(() => setTextCopied(false), 1000);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{textCopied ? (
|
|
|
|
|
<Check size={16} className=" flex items-center" />
|
|
|
|
|
) : (
|
|
|
|
|
<p>Copy</p>
|
|
|
|
|
)}
|
|
|
|
|
</Button>
|
|
|
|
|
</code>
|
|
|
|
|
</DialogDescription>
|
|
|
|
|
</DialogHeader>
|
|
|
|
|
)}
|
|
|
|
|
</DialogContent>
|
|
|
|
|
</Dialog>
|
|
|
|
|
</div>
|
|
|
|
|
<CommandDialog open={command} onOpenChange={setCommand}>
|
|
|
|
|
<CommandInput
|
|
|
|
|
placeholder="Search for a server (offline or online)"
|
|
|
|
|
onValueChange={(c) => {
|
|
|
|
|
fetch("https://api.minehut.com/server/" + c + "?byName=true").then(
|
|
|
|
|
(l) => {
|
|
|
|
|
if (l.ok) {
|
|
|
|
|
console.log("found!");
|
|
|
|
|
l.json().then((m: any) => {
|
|
|
|
|
setSearchRes(m.server);
|
|
|
|
|
console.log(searchRes);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
setSearchRes(undefined);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<CommandList>
|
|
|
|
|
<CommandEmpty>
|
|
|
|
|
No results found. (Minehut deleted legacy servers)
|
|
|
|
|
</CommandEmpty>
|
|
|
|
|
{searchRes == undefined ? (
|
|
|
|
|
""
|
|
|
|
|
) : (
|
|
|
|
|
<CommandGroup heading="Search Results">
|
|
|
|
|
<CommandItem
|
|
|
|
|
onSelect={() => {
|
|
|
|
|
window.location.replace("/server/" + searchRes.name);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div className="block">
|
|
|
|
|
<span className="font-medium">{searchRes.name}</span> <br />
|
|
|
|
|
<code className="text-gray-500 text-[14px]">
|
|
|
|
|
{searchRes.joins} total joins •{" "}
|
|
|
|
|
{searchRes.online ? "Online" : "Offline"}
|
|
|
|
|
</code>
|
|
|
|
|
</div>
|
|
|
|
|
</CommandItem>
|
|
|
|
|
</CommandGroup>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<CommandGroup heading="Popular Servers">
|
|
|
|
|
{serverList.currentServers.map((b: OnlineServer) => (
|
|
|
|
|
<CommandItem
|
|
|
|
|
key={b.name}
|
|
|
|
|
onSelect={() => {
|
|
|
|
|
window.location.replace("/server/" + b.name);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<div className="block">
|
|
|
|
|
<span className="font-medium">{b.name}</span> <br />
|
|
|
|
|
<code className="text-gray-500 text-[14px]">
|
|
|
|
|
<TagShower server={b} />
|
|
|
|
|
</code>
|
|
|
|
|
</div>
|
|
|
|
|
</CommandItem>
|
|
|
|
|
))}
|
|
|
|
|
</CommandGroup>
|
|
|
|
|
</CommandList>
|
|
|
|
|
</CommandDialog>
|
|
|
|
|
<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 });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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", paddingLeft: 6 }}
|
|
|
|
|
>
|
2024-07-23 22:07:54 -05:00
|
|
|
<div className=" grid sm:grid-cols-4 gap-4">
|
2024-07-23 18:49:21 -05:00
|
|
|
{servers.map((b: any) => (
|
|
|
|
|
<>
|
|
|
|
|
<ServerCard b={b} motd={motdList[b.name]} />
|
|
|
|
|
</>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</InfiniteScroll>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function TagShower(props: { server: OnlineServer; className?: string }) {
|
|
|
|
|
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 (
|
|
|
|
|
<>
|
|
|
|
|
{compatiableTags.map((t) => (
|
|
|
|
|
<>
|
|
|
|
|
<Dialog key={t.name}>
|
|
|
|
|
<DialogTrigger>
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger>
|
2024-07-26 00:46:53 -05:00
|
|
|
<Badge variant={t.role} className={props.className}>
|
2024-07-23 18:49:21 -05:00
|
|
|
{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>
|
|
|
|
|
</>
|
|
|
|
|
))}
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|