bump: 0.9 - new sorting

This commit is contained in:
dvelo 2024-08-15 23:24:15 -05:00
parent 0a347d3eac
commit 4e4dd9cfe6
11 changed files with 541 additions and 69 deletions

@ -3,7 +3,10 @@ import { Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "the MHSF project by dvelo", title: "the MHSF project by dvelo",
description: `currently running in ${process.env.NEXT_PUBLIC_VERCEL_ENV} | commit (${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA}) ${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE} by ${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME}`, description:
process.env.NEXT_PUBLIC_VERCEL_ENV != undefined
? `currently running in ${process.env.NEXT_PUBLIC_VERCEL_ENV} | commit (${(process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA as string).substring(0, 7)}}) "${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_MESSAGE}" by ${process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_AUTHOR_NAME}`
: "currently running in dev",
}; };
export default function Home() { export default function Home() {

@ -0,0 +1,11 @@
import FavoriteSortView from "@/components/FavoritesSortView";
export default function FavoritesSort() {
return (
<main>
<div className="pt-[60px] p-4">
<FavoriteSortView />
</div>
</main>
);
}

@ -167,6 +167,7 @@ export function OfflineServerCB() {
const [customized, setCustomized] = useState(false); const [customized, setCustomized] = useState(false);
const [obj, setObj] = useState<ServerResponse | object>({}); const [obj, setObj] = useState<ServerResponse | object>({});
const [vb, setVB] = useState(false); const [vb, setVB] = useState(false);
const { isSignedIn } = useUser();
const router = useRouter(); const router = useRouter();
const [starred, setStarred] = useState(false); const [starred, setStarred] = useState(false);
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
@ -257,9 +258,10 @@ export function OfflineServerCB() {
)} )}
<CommandGroup heading="Server Actions"> <CommandGroup heading="Server Actions">
<CommandItem <CommandItem
onSelect={() => onSelect={() => {
router.push("/server/" + (obj as ServerResponse).name + "/") router.push("/server/" + (obj as ServerResponse).name + "/");
} setOpen(false);
}}
> >
<Server className="mr-2 h-4 w-4" /> <Server className="mr-2 h-4 w-4" />
Open Server Page Open Server Page
@ -271,6 +273,7 @@ export function OfflineServerCB() {
toast.success("Done!"); toast.success("Done!");
}); });
}} }}
disabled={!isSignedIn}
> >
<Star <Star
className="mr-2 h-4 w-4" className="mr-2 h-4 w-4"
@ -285,11 +288,12 @@ export function OfflineServerCB() {
{!starred ? "F" : "Unf"}avorite Server {!starred ? "F" : "Unf"}avorite Server
</CommandItem> </CommandItem>
<CommandItem <CommandItem
onSelect={() => onSelect={() => {
router.push( router.push(
"/server/" + (obj as ServerResponse).name + "/statistics" "/server/" + (obj as ServerResponse).name + "/statistics"
) );
} setOpen(false);
}}
> >
<Database className="mr-2 h-4 w-4" /> <Database className="mr-2 h-4 w-4" />
See Statistics See Statistics
@ -330,6 +334,7 @@ export function ServerCommandBar() {
const [vb, setVB] = useState(false); const [vb, setVB] = useState(false);
const router = useRouter(); const router = useRouter();
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
const { isSignedIn } = useUser();
const [owned, setOwned] = useState(false); const [owned, setOwned] = useState(false);
const [starred, setStarred] = useState(false); const [starred, setStarred] = useState(false);
const [serverSingle, setSingle] = useState<ServerSingle>( const [serverSingle, setSingle] = useState<ServerSingle>(
@ -406,7 +411,10 @@ export function ServerCommandBar() {
<CommandGroup heading="Server Actions"> <CommandGroup heading="Server Actions">
<CommandItem <CommandItem
onSelect={() => router.push("/server/" + serverName + "/")} onSelect={() => {
router.push("/server/" + serverName + "/");
setOpen(false);
}}
> >
<Server className="mr-2 h-4 w-4" /> <Server className="mr-2 h-4 w-4" />
Open Server Page Open Server Page
@ -418,6 +426,7 @@ export function ServerCommandBar() {
toast.success("Done!"); toast.success("Done!");
}); });
}} }}
disabled={!isSignedIn}
> >
<Star <Star
className="mr-2 h-4 w-4" className="mr-2 h-4 w-4"
@ -432,9 +441,10 @@ export function ServerCommandBar() {
{!starred ? "F" : "Unf"}avorite Server {!starred ? "F" : "Unf"}avorite Server
</CommandItem> </CommandItem>
<CommandItem <CommandItem
onSelect={() => onSelect={() => {
router.push("/server/" + serverName + "/statistics") router.push("/server/" + serverName + "/statistics");
} setOpen(false);
}}
> >
<Database className="mr-2 h-4 w-4" /> <Database className="mr-2 h-4 w-4" />
See Statistics See Statistics
@ -509,11 +519,14 @@ export function CommandBar() {
+Shift+K +Shift+K
</CommandShortcut> </CommandShortcut>
</CommandItem> </CommandItem>
<CommandItem disabled> <CommandItem
onSelect={() => {
events.emit("cmd-event-sort");
setOpen(false);
}}
>
<ArrowDown01 className="mr-2 h-4 w-4" /> <ArrowDown01 className="mr-2 h-4 w-4" />
<span> Sort Servers
Sort Servers - <i>coming soon</i>
</span>
</CommandItem> </CommandItem>
<CommandItem <CommandItem
onSelect={() => { onSelect={() => {
@ -672,6 +685,49 @@ export function RandomServerDialog() {
); );
} }
export function SubSortCommandBar() {
const [open, setOpen] = useState(false);
const router = useRouter();
useEffectOnce(() => {
events.on("cmd-event-sort", () => {
setOpen(true);
});
});
return (
<CommandDialog open={open} onOpenChange={setOpen}>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Sorts">
<CommandItem
onSelect={() => {
router.push("/sort/favorites");
setOpen(false);
}}
>
<Star className="mr-2 h-4 w-4" />
<span>Favorites</span>
</CommandItem>
</CommandGroup>
<CommandGroup heading="Hierarchy">
<CommandItem
onSelect={() => {
setOpen(false);
events.emit("cmd-event");
}}
>
<ArrowLeft className="mr-2 h-4 w-4" />
Go back
</CommandItem>
</CommandGroup>
</CommandList>
</CommandDialog>
);
}
export function SubLinkCommandBar() { export function SubLinkCommandBar() {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
@ -771,6 +827,7 @@ export function FavoriteBar() {
key={c} key={c}
onSelect={() => { onSelect={() => {
router.push("/server/" + c); router.push("/server/" + c);
setOpen(false);
}} }}
> >
{c} {c}
@ -802,6 +859,7 @@ export function CommandBarer() {
<CommandBar /> <CommandBar />
<SearchCommandBar /> <SearchCommandBar />
<ServerCommandBar /> <ServerCommandBar />
<SubSortCommandBar />
<OfflineServerCB /> <OfflineServerCB />
<RandomServerDialog /> <RandomServerDialog />
</> </>

@ -0,0 +1,267 @@
"use client";
import { useEffect, useState } from "react";
import { Spinner } from "./ui/spinner";
import { sortedFavorites } from "@/lib/api";
import InfiniteScroll from "react-infinite-scroll-component";
import { Button } from "./ui/button";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { RadioGroup, RadioGroupItem } from "./ui/radio-group";
import { Label } from "./ui/label";
import { useRouter } from "@/lib/useRouter";
import { OnlineServer } from "@/lib/types/mh-server";
import ServerCard from "./ServerCard";
import { CircleUser, Copy, Info, Layers, Network } from "lucide-react";
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
import { BorderBeam } from "./effects/border-beam";
import Stat from "./Stat";
import { Separator } from "./ui/separator";
import { Card, CardDescription, CardHeader, CardTitle } from "./ui/card";
import Link from "next/link";
import toast from "react-hot-toast";
export default function FavoriteSortView() {
const [loading, setLoading] = useState(true);
const [list, setList] = useState<{ server: string; favorites: number }[]>([]);
const [allItems, setAllItems] = useState<
Array<{ server: string; favorites: number }>
>([]);
const [upNumber, setUpNumber] = useState(0);
const [hasMore, setHasMore] = useState(true);
const [totalServers, setTotalServers] = useState(0);
const [totalPlayers, setTotalPlayers] = useState(0);
const [online, setOnline] = useState<any>({});
const router = useRouter();
useEffect(() => {
sortedFavorites().then((c) => {
if (typeof c !== "boolean") {
const slicedArray = c.slice(0, 20);
setAllItems(slicedArray);
setList(c);
fetch("https://api.minehut.com/servers").then((b) =>
b.json().then((c) => {
c.servers.forEach((v: OnlineServer) =>
setOnline(setManipulate(online, v.name, v))
);
setLoading(false);
setTotalPlayers(c.total_players);
setTotalServers(c.total_servers);
})
);
console.log(list);
}
});
}, []);
if (loading) {
return <Spinner className="flex items-center" />;
}
return (
<div>
<div className="max-lg:grid-cols-2 grid grid-cols-2 gap-4 ">
<Stat
title="Players online"
desc={totalPlayers.toString()}
icon={CircleUser}
/>
<Stat
title={
<div
className={
totalServers >= 3200
? "bg-clip-text text-transparent bg-gradient-to-r from-cyan-500 to-blue-500"
: ""
}
>
Servers online{" "}
</div>
}
className="relative z-0"
desc={
<div className="flex items-center">
<div
className={
totalServers >= 3200
? "bg-clip-text text-transparent bg-gradient-to-r from-cyan-500 to-blue-500 "
: ""
}
>
{totalServers.toString()}
</div>
{totalServers >= 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}
>
{totalServers >= 3200 && (
<BorderBeam
size={135}
duration={12}
delay={9}
colorFrom="rgb(6 182 212)"
colorTo="rgb(59 130 246)"
/>
)}
</Stat>
</div>
<br />
<Separator />
<br />
<Popover>
<PopoverTrigger>
<Button className="ml-2" variant="secondary">
Sort
</Button>
</PopoverTrigger>
<PopoverContent>
<RadioGroup
defaultValue="option-two"
onValueChange={() => router.push("/")}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="option-one" id="option-one" />
<Label htmlFor="option-one">Online Players</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="option-two" id="option-two" />
<Label htmlFor="option-two">Favorites</Label>
</div>
</RadioGroup>
</PopoverContent>
</Popover>
<Button
variant="secondary"
className="ml-2"
onClick={() => {
setLoading(true);
sortedFavorites().then((c) => {
if (typeof c !== "boolean") {
const slicedArray = c.slice(0, 20);
setAllItems(slicedArray);
setList(c);
fetch("https://api.minehut.com/servers").then((b) =>
b.json().then((c) => {
c.servers.forEach((v: OnlineServer) =>
setOnline(setManipulate(online, v.name, v))
);
setLoading(false);
setTotalPlayers(c.total_players);
setTotalServers(c.total_servers);
})
);
console.log(list);
}
});
}}
>
Refresh
</Button>
<br />
<br />
<InfiniteScroll
dataLength={list.length}
hasMore={hasMore}
next={() => {
const newUpNumber = upNumber + 1;
const slicedArray = list.slice(
newUpNumber * 20,
newUpNumber * 20 + 20
);
console.log(slicedArray.length);
setAllItems((prev) => [...prev, ...slicedArray]);
if (slicedArray.length !== 20) setHasMore(false);
setUpNumber(newUpNumber);
}}
loader={<br />}
endMessage={
<p style={{ textAlign: "center" }}>
<br />
<strong>You've seen it all</strong>
</p>
}
style={{ overflow: "hidden", paddingLeft: 6 }}
>
<div className="grid sm:grid-cols-4 gap-4">
{allItems.map((v) => {
if (v.favorites == 0) {
return <></>;
}
if (online[v.server] != undefined)
return (
<ServerCard mini b={online[v.server]} favs={v.favorites} />
);
else
return (
<Card className="h-[226px]">
<CardHeader>
<CardTitle>{v.server}</CardTitle>
<CardDescription>
{v.favorites} favorited
<br />
<Button
size="icon"
variant="secondary"
className="min-w-[128px] max-w-[328px] h-[32px] mt-2 ml-2 max-md:hidden"
onClick={() => {
navigator.clipboard.writeText(
v.server + ".mshf.minehut.gg"
);
toast.success("Copied IP to clipboard");
}}
>
<Copy size={18} />
<code className="ml-2">{v.server}</code>
</Button>
<Tooltip>
<TooltipTrigger>
<Link href={"/server/" + v.server}>
<Button
size="icon"
variant="secondary"
className="w-[32px] h-[32px] mt-2 ml-2 max-md:hidden"
>
<Layers size={18} />
</Button>
</Link>
</TooltipTrigger>
<TooltipContent>
Open up the server page to see more information about
the server
</TooltipContent>
</Tooltip>
</CardDescription>
</CardHeader>
</Card>
);
})}
</div>
</InfiniteScroll>
</div>
);
}
function setManipulate(object: any, change: string, value: any) {
let newObject = object;
newObject[change] = value;
return newObject;
}

@ -5,7 +5,7 @@ import {
ContextMenuContent, ContextMenuContent,
ContextMenuSeparator, ContextMenuSeparator,
} from "@/components/ui/context-menu"; } from "@/components/ui/context-menu";
import toast from "react-hot-toast"; import toast, { LoaderIcon } from "react-hot-toast";
import { import {
CardHeader, CardHeader,
CardTitle, CardTitle,
@ -15,7 +15,7 @@ import {
} from "./ui/card"; } from "./ui/card";
import IconDisplay from "./IconDisplay"; import IconDisplay from "./IconDisplay";
import { TagShower } from "./ServerList"; import { TagShower } from "./ServerList";
import { Copy, EllipsisVertical, Layers } from "lucide-react"; import { Copy, EllipsisVertical, Layers, Star } from "lucide-react";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { import {
Drawer, Drawer,
@ -29,15 +29,35 @@ import { Tooltip } from "@radix-ui/react-tooltip";
import { TooltipContent, TooltipTrigger } from "./ui/tooltip"; import { TooltipContent, TooltipTrigger } from "./ui/tooltip";
import { useRouter } from "@/lib/useRouter"; import { useRouter } from "@/lib/useRouter";
import Link from "next/link"; import Link from "next/link";
import { useState } from "react";
import { favoriteServer, isFavorited } from "@/lib/api";
import { useUser } from "@clerk/nextjs";
import { useTheme } from "next-themes";
export default function ServerCard({ b, motd }: any) { export default function ServerCard({ b, motd, mini, favs }: any) {
const router = useRouter(); const router = useRouter();
const [favoriteStar, setFavoriteStar] = useState(false);
const [favoriteLoading, setFavoriteLoading] = useState(true);
const { isSignedIn } = useUser();
const { resolvedTheme } = useTheme();
return ( return (
<ContextMenu> <ContextMenu
onOpenChange={(open) => {
if (open && isSignedIn)
isFavorited(b.name).then((c) => {
setFavoriteStar(c);
setFavoriteLoading(false);
});
}}
>
<ContextMenuTrigger> <ContextMenuTrigger>
<Card <Card
key={b.name} key={b.name}
className="min-h-[450px] max-h-[450px] mb-4 flex items-start" className={
(!mini ? "min-h-[450px] max-h-[450px]" : "") +
" mb-4 flex items-start"
}
> >
<CardHeader> <CardHeader>
<CardTitle className="m-0"> <CardTitle className="m-0">
@ -115,7 +135,7 @@ export default function ServerCard({ b, motd }: any) {
<span className="pl-1"> <span className="pl-1">
{b.playerData.playerCount}{" "} {b.playerData.playerCount}{" "}
{b.playerData.playerCount == 1 ? "player" : "players"}{" "} {b.playerData.playerCount == 1 ? "player" : "players"}{" "}
currently online currently online {favs && <> {favs} favorited</>}
</span> </span>
</span> </span>
@ -177,10 +197,12 @@ export default function ServerCard({ b, motd }: any) {
</ContextMenu> </ContextMenu>
</CardDescription> </CardDescription>
<CardContent> <CardContent>
<span {motd && (
dangerouslySetInnerHTML={{ __html: motd }} <span
className="w-[30px] text-center break-all overflow-hidden" dangerouslySetInnerHTML={{ __html: motd }}
/> className="w-[30px] text-center break-all overflow-hidden"
/>
)}
</CardContent> </CardContent>
</CardHeader> </CardHeader>
</Card> </Card>
@ -193,9 +215,6 @@ export default function ServerCard({ b, motd }: any) {
}} }}
> >
Copy server IP Copy server IP
<div className="RightSlot">
<Copy size={18} />
</div>
</ContextMenuItem> </ContextMenuItem>
<ContextMenuSeparator /> <ContextMenuSeparator />
<ContextMenuItem <ContextMenuItem
@ -205,6 +224,40 @@ export default function ServerCard({ b, motd }: any) {
> >
Open server page Open server page
</ContextMenuItem> </ContextMenuItem>
<ContextMenuItem
onClick={() => {
router.push("/server/" + b.name + "/statistics");
}}
>
Open statistics page
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem
onClick={() => {
setFavoriteLoading(true);
favoriteServer(b.name).then(() => {
setFavoriteLoading(false);
setFavoriteStar(!favoriteStar);
});
}}
disabled={!isSignedIn || favoriteLoading}
>
{!favoriteLoading && (
<Star
size={16}
className="mr-2 text-white"
fill={
favoriteStar
? resolvedTheme == "dark"
? "white"
: "black"
: "transparent"
}
/>
)}{" "}
{favoriteLoading && <LoaderIcon className="mr-2" />}
{favoriteStar && isSignedIn ? "Unf" : "F"}avorite Server
</ContextMenuItem>
</ContextMenuContent> </ContextMenuContent>
</ContextMenu> </ContextMenu>
); );

@ -37,6 +37,8 @@ import { useEffectOnce } from "@/lib/useEffectOnce";
import ServerCard from "./ServerCard"; import ServerCard from "./ServerCard";
import events from "@/lib/commandEvent"; import events from "@/lib/commandEvent";
import { BorderBeam } from "@/components/effects/border-beam"; import { BorderBeam } from "@/components/effects/border-beam";
import { Label } from "./ui/label";
import { useRouter } from "@/lib/useRouter";
export default function ServerList() { export default function ServerList() {
const [loading, setLoading]: any = useState(true); const [loading, setLoading]: any = useState(true);
@ -59,6 +61,7 @@ export default function ServerList() {
const [nameFilters, setNameFilters] = useState<any>({}); const [nameFilters, setNameFilters] = useState<any>({});
const [inErrState, setErrState] = useState(false); const [inErrState, setErrState] = useState(false);
const [servers, setServers] = useState<Array<OnlineServer>>([]); const [servers, setServers] = useState<Array<OnlineServer>>([]);
const router = useRouter();
const [filters, setFilters] = useState< const [filters, setFilters] = useState<
Array<(server: OnlineServer) => Promise<boolean>> Array<(server: OnlineServer) => Promise<boolean>>
>([]); >([]);
@ -125,7 +128,7 @@ export default function ServerList() {
title="Players online" title="Players online"
desc={serverList.getExtraData().total_players.toString()} desc={serverList.getExtraData().total_players.toString()}
icon={CircleUser} icon={CircleUser}
></Stat> />
<Stat <Stat
title={ title={
<div <div
@ -522,6 +525,28 @@ export default function ServerList() {
))} ))}
</PopoverContent> </PopoverContent>
</Popover> </Popover>
<Popover>
<PopoverTrigger>
<Button className="ml-2" variant="secondary">
Sort
</Button>
</PopoverTrigger>
<PopoverContent>
<RadioGroup
defaultValue="option-one"
onValueChange={() => router.push("/sort/favorites")}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="option-one" id="option-one" />
<Label htmlFor="option-one">Online Players</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="option-two" id="option-two" />
<Label htmlFor="option-two">Favorites</Label>
</div>
</RadioGroup>
</PopoverContent>
</Popover>
<Button <Button
variant="secondary" variant="secondary"
className="md:ml-3 " className="md:ml-3 "
@ -629,7 +654,7 @@ export default function ServerList() {
<br /> <br />
<br /> <br />
<code className="border p-3 rounded"> <code className="border p-3 rounded">
{randomData.name}.mshf.minehut.gg{" "} {randomData.name}.minehut.gg{" "}
<Button <Button
size="icon" size="icon"
className="ml-1 h-[20px]" className="ml-1 h-[20px]"

@ -84,6 +84,16 @@ export default function TextFromPathname() {
</BreadcrumbItem> </BreadcrumbItem>
</> </>
)} )}
{pathname == "/sort/favorites" && (
<>
<BreadcrumbSeparator className="max-sm:hidden" />
<BreadcrumbItem>Sort</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Favorites</BreadcrumbPage>
</BreadcrumbItem>
</>
)}
</> </>
); );
} }

@ -1,27 +1,27 @@
"use client" "use client";
import * as React from "react" import * as React from "react";
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu" import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
import { Check, ChevronRight, Circle } from "lucide-react" import { Check, ChevronRight, Circle } from "lucide-react";
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils";
const ContextMenu = ContextMenuPrimitive.Root const ContextMenu = ContextMenuPrimitive.Root;
const ContextMenuTrigger = ContextMenuPrimitive.Trigger const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
const ContextMenuGroup = ContextMenuPrimitive.Group const ContextMenuGroup = ContextMenuPrimitive.Group;
const ContextMenuPortal = ContextMenuPrimitive.Portal const ContextMenuPortal = ContextMenuPrimitive.Portal;
const ContextMenuSub = ContextMenuPrimitive.Sub const ContextMenuSub = ContextMenuPrimitive.Sub;
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
const ContextMenuSubTrigger = React.forwardRef< const ContextMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>, React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & { React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, children, ...props }, ref) => ( >(({ className, inset, children, ...props }, ref) => (
<ContextMenuPrimitive.SubTrigger <ContextMenuPrimitive.SubTrigger
@ -36,8 +36,8 @@ const ContextMenuSubTrigger = React.forwardRef<
{children} {children}
<ChevronRight className="ml-auto h-4 w-4" /> <ChevronRight className="ml-auto h-4 w-4" />
</ContextMenuPrimitive.SubTrigger> </ContextMenuPrimitive.SubTrigger>
)) ));
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
const ContextMenuSubContent = React.forwardRef< const ContextMenuSubContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.SubContent>, React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
@ -51,8 +51,8 @@ const ContextMenuSubContent = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
const ContextMenuContent = React.forwardRef< const ContextMenuContent = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Content>, React.ElementRef<typeof ContextMenuPrimitive.Content>,
@ -62,19 +62,19 @@ const ContextMenuContent = React.forwardRef<
<ContextMenuPrimitive.Content <ContextMenuPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className className
)} )}
{...props} {...props}
/> />
</ContextMenuPrimitive.Portal> </ContextMenuPrimitive.Portal>
)) ));
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
const ContextMenuItem = React.forwardRef< const ContextMenuItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Item>, React.ElementRef<typeof ContextMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & { React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Item <ContextMenuPrimitive.Item
@ -86,8 +86,8 @@ const ContextMenuItem = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
const ContextMenuCheckboxItem = React.forwardRef< const ContextMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>, React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
@ -109,9 +109,9 @@ const ContextMenuCheckboxItem = React.forwardRef<
</span> </span>
{children} {children}
</ContextMenuPrimitive.CheckboxItem> </ContextMenuPrimitive.CheckboxItem>
)) ));
ContextMenuCheckboxItem.displayName = ContextMenuCheckboxItem.displayName =
ContextMenuPrimitive.CheckboxItem.displayName ContextMenuPrimitive.CheckboxItem.displayName;
const ContextMenuRadioItem = React.forwardRef< const ContextMenuRadioItem = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>, React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
@ -132,13 +132,13 @@ const ContextMenuRadioItem = React.forwardRef<
</span> </span>
{children} {children}
</ContextMenuPrimitive.RadioItem> </ContextMenuPrimitive.RadioItem>
)) ));
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
const ContextMenuLabel = React.forwardRef< const ContextMenuLabel = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Label>, React.ElementRef<typeof ContextMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & { React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
inset?: boolean inset?: boolean;
} }
>(({ className, inset, ...props }, ref) => ( >(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label <ContextMenuPrimitive.Label
@ -150,8 +150,8 @@ const ContextMenuLabel = React.forwardRef<
)} )}
{...props} {...props}
/> />
)) ));
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
const ContextMenuSeparator = React.forwardRef< const ContextMenuSeparator = React.forwardRef<
React.ElementRef<typeof ContextMenuPrimitive.Separator>, React.ElementRef<typeof ContextMenuPrimitive.Separator>,
@ -162,8 +162,8 @@ const ContextMenuSeparator = React.forwardRef<
className={cn("-mx-1 my-1 h-px bg-border", className)} className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props} {...props}
/> />
)) ));
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
const ContextMenuShortcut = ({ const ContextMenuShortcut = ({
className, className,
@ -177,9 +177,9 @@ const ContextMenuShortcut = ({
)} )}
{...props} {...props}
/> />
) );
} };
ContextMenuShortcut.displayName = "ContextMenuShortcut" ContextMenuShortcut.displayName = "ContextMenuShortcut";
export { export {
ContextMenu, ContextMenu,
@ -197,4 +197,4 @@ export {
ContextMenuSubContent, ContextMenuSubContent,
ContextMenuSubTrigger, ContextMenuSubTrigger,
ContextMenuRadioGroup, ContextMenuRadioGroup,
} };

@ -30,12 +30,12 @@ export async function increaseNum(client: MongoClient, server: string) {
const find = await collection.find({ server: server }).toArray(); const find = await collection.find({ server: server }).toArray();
if (find.length == 0) { if (find.length == 0) {
collection.insertOne({ server: server, favorites: 1 }); collection.insertOne({ server: server, favorites: 1, date: new Date() });
} else { } else {
const entry = find[0]; const entry = find[0];
collection.findOneAndReplace( collection.findOneAndReplace(
{ server: server }, { server: server },
{ server: server, favorites: entry.favorites + 1 } { server: server, favorites: entry.favorites + 1, date: new Date() }
); );
} }
} }

@ -0,0 +1,20 @@
import { NextApiRequest, NextApiResponse } from "next";
import { MongoClient } from "mongodb";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const client = new MongoClient(process.env.MONGO_DB as string);
await client.connect();
const db = client.db("mhsf");
const collection = db.collection("meta");
const all = await collection.find().toArray();
const sorted = all.sort((a, b) => a.favorites - b.favorites);
sorted.reverse();
res.send({ results: sorted });
client.close();
}

@ -1,7 +1,7 @@
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
export const version = "b-0.8.0"; export const version = "b-0.9.0";
const User = ({ user }: { user: string }) => ( const User = ({ user }: { user: string }) => (
<span className="cursor-pointer bg-[rgba(255,165,0,0.25);] rounded p-[2.5px]"> <span className="cursor-pointer bg-[rgba(255,165,0,0.25);] rounded p-[2.5px]">
@ -44,6 +44,25 @@ export const Changelog = () => (
</code> </code>
</div> </div>
<br /> <br />
<div>
<strong className="flex items-center">
Version b-0.9.0 (August 15th 2024)
</strong>
<ul>
<li> Adding favorites sorting option</li>
<li> Fixed right-click context menu on the server list</li>
<li> Fixed metadata bugs</li>
</ul>
<br />
<i>
Hey! Update on statistics. Recently, we have figured out the Minehut API
is blocked to Vercel servers (atleast the <code>/servers</code>{" "}
endpoint). I'm actively trying to find a loop-hole so that statistics
works correctly. Thank you {":)"}
</i>
<br />
</div>
<br />
<div> <div>
<strong className="flex items-center"> <strong className="flex items-center">
Version b-0.8.0 (August 11th 2024) Version b-0.8.0 (August 11th 2024)
@ -135,7 +154,13 @@ export const Changelog = () => (
<div> <div>
<strong>All developers that helped out:</strong> <strong>All developers that helped out:</strong>
<Link href="https://dvelo.vercel.app"> <Link href="https://dvelo.vercel.app">
<Image src="/imgs/badge1.png" alt="cool badge" width={88} height={31} /> <Image
src="/imgs/badge1.png"
alt="cool badge"
width={88}
height={31}
className="w-[88px] h-[31px]"
/>
</Link> </Link>
</div> </div>
</> </>