mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-07 15:54:58 -05:00
feat(v2): some changes
This commit is contained in:
parent
f21ce33b27
commit
b62f79e010
@ -1,135 +0,0 @@
|
||||
/*
|
||||
* MHSF, Minehut Server List
|
||||
* All external content is rather licensed under the ECA Agreement
|
||||
* located here: https://mhsf.app/docs/legal/external-content-agreement
|
||||
*
|
||||
* All code under MHSF is licensed under the MIT License
|
||||
* by open source contributors
|
||||
*
|
||||
* Copyright (c) 2025 dvelo
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
"use client";
|
||||
import "../../globals.css";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Placeholder } from "@/components/ui/placeholder";
|
||||
import { Command, X } from "lucide-react";
|
||||
import { IsScript } from "@/components/util/is-script";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
import { NavBar } from "@/components/feat/navbar/navbar";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { ThemeProvider } from "@/components/util/theme-provider";
|
||||
import { FontBoundary } from "@/components/util/font-boundary";
|
||||
import { ClerkProvider } from "@/components/util/clerk-provider";
|
||||
import { Toaster } from "sonner";
|
||||
import { Footer } from "@/components/feat/footer/footer";
|
||||
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
||||
import { IframeProtector } from "@/components/util/iframe-protector";
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarGroup,
|
||||
SidebarGroupContent,
|
||||
SidebarHeader,
|
||||
SidebarInset,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarProvider,
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const searchParams = useSearchParams();
|
||||
const search = searchParams?.get("theme") || "light";
|
||||
|
||||
return (
|
||||
<>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<ClerkProvider>
|
||||
<IsScript>
|
||||
<NuqsAdapter>
|
||||
<FontBoundary>
|
||||
<TooltipProvider>
|
||||
<SidebarProvider>
|
||||
<Sidebar variant="inset">
|
||||
<SidebarHeader>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton size="lg" asChild>
|
||||
<a href="#">
|
||||
<div className="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sidebar-primary-foreground">
|
||||
<Command className="size-4" />
|
||||
</div>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-semibold">
|
||||
Acme Inc
|
||||
</span>
|
||||
<span className="truncate text-xs">
|
||||
Enterprise
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild size="sm">
|
||||
<a href="#">
|
||||
<span>a</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
</Sidebar>
|
||||
<SidebarInset>
|
||||
<Toaster richColors position="bottom-center" />
|
||||
<div className="overflow-x-hidden">{children}</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
</TooltipProvider>
|
||||
</FontBoundary>
|
||||
</NuqsAdapter>
|
||||
</IsScript>
|
||||
</ClerkProvider>
|
||||
</ThemeProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -28,14 +28,8 @@
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
"use client";
|
||||
import "../globals.css";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Placeholder } from "@/components/ui/placeholder";
|
||||
import { X } from "lucide-react";
|
||||
import { IsScript } from "@/components/util/is-script";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
import { NavBar } from "@/components/feat/navbar/navbar";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { ThemeProvider } from "@/components/util/theme-provider";
|
||||
@ -50,8 +44,6 @@ export default function RootLayout({
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const searchParams = useSearchParams();
|
||||
const search = searchParams?.get("theme") || "light";
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -134,6 +134,7 @@ export default function ModificationPage({
|
||||
activatedModifications: modificationArray
|
||||
}
|
||||
});
|
||||
communicator.send("rerender-servers", {});
|
||||
}}>
|
||||
{modObj?.active ? "Disable" : "Enable"}
|
||||
</Button>
|
||||
@ -165,6 +166,7 @@ export default function ModificationPage({
|
||||
});
|
||||
toast.success(`Deleted in ${Date.now() - time}ms`);
|
||||
router.push(backRoute);
|
||||
communicator.send("rerender-servers", {});
|
||||
}}
|
||||
>
|
||||
<Trash size={16} /> Delete
|
||||
|
||||
@ -1,69 +0,0 @@
|
||||
import { ComponentPropsWithoutRef, ReactNode } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface BentoGridProps extends ComponentPropsWithoutRef<"div"> {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface BentoCardProps extends ComponentPropsWithoutRef<"div"> {
|
||||
name: string;
|
||||
className: string;
|
||||
background: ReactNode;
|
||||
Icon: React.ElementType;
|
||||
description: string;
|
||||
href: string;
|
||||
cta: string;
|
||||
}
|
||||
|
||||
const BentoGrid = ({ children, className, ...props }: BentoGridProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"grid w-full auto-rows-[22rem] grid-cols-3 gap-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const BentoCard = ({
|
||||
name,
|
||||
className,
|
||||
background,
|
||||
Icon,
|
||||
description,
|
||||
href,
|
||||
cta,
|
||||
...props
|
||||
}: BentoCardProps) => (
|
||||
<div
|
||||
key={name}
|
||||
className={cn(
|
||||
"group relative col-span-3 flex flex-col justify-between overflow-hidden rounded-xl",
|
||||
// light styles
|
||||
"bg-background [box-shadow:0_0_0_1px_rgba(0,0,0,.03),0_2px_4px_rgba(0,0,0,.05),0_12px_24px_rgba(0,0,0,.05)]",
|
||||
// dark styles
|
||||
"transform-gpu dark:bg-background dark:[border:1px_solid_rgba(255,255,255,.1)] dark:[box-shadow:0_-20px_80px_-20px_#ffffff1f_inset]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div>{background}</div>
|
||||
<div className="pointer-events-none z-10 flex transform-gpu flex-col gap-1 p-6 transition-all duration-300 group-hover:-translate-y-10">
|
||||
<Icon className="h-12 w-12 origin-left transform-gpu text-neutral-700 transition-all duration-300 ease-in-out group-hover:scale-75" />
|
||||
<h3 className="text-xl font-semibold text-neutral-700 dark:text-neutral-300">
|
||||
{name}
|
||||
</h3>
|
||||
<p className="max-w-lg text-neutral-400">{description}</p>
|
||||
</div>
|
||||
|
||||
<div className="pointer-events-none absolute inset-0 transform-gpu transition-all duration-300 group-hover:bg-black/[.03] group-hover:dark:bg-neutral-800/10" />
|
||||
</div>
|
||||
);
|
||||
|
||||
export { BentoCard, BentoGrid };
|
||||
@ -1,3 +1,4 @@
|
||||
"use client";
|
||||
import { BrandingGenericIcon, Discord } from "../icons/branding-icons";
|
||||
import { Link } from "../../util/link";
|
||||
import { FooterStatus } from "./status";
|
||||
@ -7,10 +8,11 @@ import Github from "@/components/ui/github";
|
||||
import Image from "next/image"
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
const hideFooterPages = ["/home"];
|
||||
const hideFooterPages = ["/home"]
|
||||
|
||||
export function Footer() {
|
||||
const pathname = usePathname();
|
||||
|
||||
if (!hideFooterPages.includes(pathname ?? ""))
|
||||
return (
|
||||
<footer className="w-full mt-15 border-t border-neutral-500/20 bg-neutral-100 dark:border-neutral-700/50 dark:bg-neutral-900 text-muted-foreground">
|
||||
@ -50,7 +52,7 @@ export function Footer() {
|
||||
<div className="flex items-center mb-2 justify-end gap-2">
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="tertiary" size="square-md" className="flex items-center">
|
||||
<Discord className="w-[1.25em] h-[1.25em]" />
|
||||
</Button>
|
||||
|
||||
@ -90,8 +90,6 @@ export function FontChanger({
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
console.log(position);
|
||||
|
||||
return (
|
||||
<div className="h-[1.2em] overflow-hidden flex items-center justify-center">
|
||||
<span
|
||||
|
||||
@ -106,13 +106,13 @@ export default function HomePageComponent() {
|
||||
style={
|
||||
{
|
||||
"--gradient-color-1":
|
||||
resolvedTheme === "dark" ? "#043D5D" : "#1F9EA3",
|
||||
resolvedTheme === "dark" ? "#470061" : "#610034",
|
||||
"--gradient-color-2":
|
||||
resolvedTheme === "dark" ? "#032E46" : "#F8BD97",
|
||||
resolvedTheme === "dark" ? "#001299" : "#700099",
|
||||
"--gradient-color-3":
|
||||
resolvedTheme === "dark" ? "#23B684" : "#9E5428",
|
||||
resolvedTheme === "dark" ? "#8d00eb" : "#eb00ac",
|
||||
"--gradient-color-4":
|
||||
resolvedTheme === "dark" ? "#0F595E" : "#EEEEEE",
|
||||
resolvedTheme === "dark" ? "#009de0" : "#0007e0",
|
||||
webKitMaskImage:
|
||||
"linear-gradient(to top, black, black, transparent)",
|
||||
maskImage: "linear-gradient(to top, black, black, transparent)",
|
||||
|
||||
@ -74,10 +74,12 @@ export function NavBar() {
|
||||
"w-screen h-[3rem] grid-cols-3 fixed z-10 flex",
|
||||
"items-center justify-self-start me-auto pl-4 flex-1 transition-all justify-between",
|
||||
"lg:top-0 max-lg:bottom-0",
|
||||
showBorder ? "border-b backdrop-blur-xl" : "max-lg:border-b max-lg:backdrop-blur-xl",
|
||||
showBorder
|
||||
? "border-b backdrop-blur-xl"
|
||||
: "max-lg:border-b max-lg:backdrop-blur-xl",
|
||||
pathname !== null && animatedTopbarPages.includes(pathname)
|
||||
? "[--animation-delay:1000ms] opacity-0 animate-fade-in"
|
||||
: ""
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
<span>
|
||||
@ -142,22 +144,24 @@ export function NavBar() {
|
||||
</span>
|
||||
<span className="mr-3 flex items-center">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<SignedOut>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
className={cn(
|
||||
"rounded-full flex items-center",
|
||||
pathname !== null && animatedTopbarPages.includes(pathname)
|
||||
? "[--animation-delay:2000ms] opacity-0 animate-fade-in"
|
||||
: ""
|
||||
: "",
|
||||
)}
|
||||
size="square-lg"
|
||||
variant="secondary"
|
||||
>
|
||||
<Menu size={16} />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</SignedOut>
|
||||
<SignedIn>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
size="square-lg"
|
||||
variant="tertiary"
|
||||
@ -165,7 +169,7 @@ export function NavBar() {
|
||||
"rounded-full flex items-center",
|
||||
pathname !== null && animatedTopbarPages.includes(pathname)
|
||||
? "[--animation-delay:2000ms] opacity-0 animate-fade-in"
|
||||
: ""
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
<NextImage
|
||||
@ -180,8 +184,8 @@ export function NavBar() {
|
||||
className="rounded-full"
|
||||
/>
|
||||
</Button>
|
||||
</SignedIn>
|
||||
</DropdownMenuTrigger>
|
||||
</SignedIn>
|
||||
<DropdownMenuContent className="max-w-[280px] w-[280px] mt-2 mr-2">
|
||||
<MenuDropdown />
|
||||
</DropdownMenuContent>
|
||||
|
||||
@ -2,10 +2,54 @@ import { Button } from "@/components/ui/button";
|
||||
import type { Filter } from "@/lib/types/filter";
|
||||
import type { Sort } from "@/lib/types/sort";
|
||||
import { ModificationFileCreationDialog } from "./modification-file-creation-dialog";
|
||||
import { useUser } from "@clerk/nextjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useIframeCommunication } from "@/lib/hooks/use-iframe-communication";
|
||||
|
||||
type Action = Filter | Sort | { customAction: string };
|
||||
|
||||
export type ClerkEmbeddedFilter<T> = {
|
||||
type: string;
|
||||
metadata: T;
|
||||
};
|
||||
|
||||
export function ModificationAction({ value }: { value?: Action }) {
|
||||
const { isSignedIn, user } = useUser();
|
||||
const [applied, setApplied] = useState<number | undefined>();
|
||||
const communication = useIframeCommunication();
|
||||
|
||||
const findExisting = () => {
|
||||
if (!(value !== undefined && "customAction" in value)) {
|
||||
const filter = value as Filter;
|
||||
let existing = -1;
|
||||
if (isSignedIn)
|
||||
existing = (
|
||||
(user.unsafeMetadata.filters as Array<
|
||||
ClerkEmbeddedFilter<unknown>
|
||||
>) ?? []
|
||||
).findIndex(
|
||||
(c) =>
|
||||
JSON.stringify(c.metadata) ===
|
||||
JSON.stringify(filter.toIdentifier()) &&
|
||||
c.type === filter.getSpecificFilterId(),
|
||||
);
|
||||
else
|
||||
existing = (
|
||||
(JSON.parse(localStorage.getItem("mhsf__filters") ?? "[]") as Array<
|
||||
ClerkEmbeddedFilter<unknown>
|
||||
>) ?? []
|
||||
).findIndex(
|
||||
(c) =>
|
||||
JSON.stringify(c.metadata) === JSON.stringify(filter.toIdentifier()) &&
|
||||
c.type === filter.getSpecificFilterId(),
|
||||
);
|
||||
return existing;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
useEffect(() => setApplied(findExisting()))
|
||||
|
||||
return (
|
||||
<>
|
||||
{value !== undefined && "customAction" in value ? (
|
||||
@ -19,7 +63,73 @@ export function ModificationAction({ value }: { value?: Action }) {
|
||||
</Button>
|
||||
</ModificationFileCreationDialog>
|
||||
) : (
|
||||
<Button size="sm">Apply</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
className="mt-1"
|
||||
onClick={async () => {
|
||||
if (value?.type() === "filter") {
|
||||
const filter = value as Filter;
|
||||
const existing = findExisting();
|
||||
|
||||
if (isSignedIn) {
|
||||
const existingArray =
|
||||
(user.unsafeMetadata.filters as Array<
|
||||
ClerkEmbeddedFilter<unknown>
|
||||
>) ?? [];
|
||||
existingArray.splice(existing, 1);
|
||||
if (existing === -1)
|
||||
await user.update({
|
||||
unsafeMetadata: {
|
||||
...user.unsafeMetadata,
|
||||
filters: [
|
||||
{
|
||||
type: filter.getSpecificFilterId(),
|
||||
metadata: filter.toIdentifier(),
|
||||
},
|
||||
...((user.unsafeMetadata.filters as Array<
|
||||
ClerkEmbeddedFilter<unknown>
|
||||
>) ?? []),
|
||||
] as Array<ClerkEmbeddedFilter<unknown>>,
|
||||
},
|
||||
});
|
||||
else
|
||||
await user.update({
|
||||
unsafeMetadata: {
|
||||
filters: existingArray,
|
||||
...user.unsafeMetadata,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const existingArray =
|
||||
(JSON.parse(
|
||||
localStorage.getItem("mhsf__filters") ?? "[]",
|
||||
) as Array<ClerkEmbeddedFilter<unknown>>) ?? [];
|
||||
existingArray.splice(existing, 1);
|
||||
|
||||
if (existing === -1)
|
||||
localStorage.setItem(
|
||||
"mhsf__filters",
|
||||
JSON.stringify([
|
||||
{
|
||||
type: filter.getSpecificFilterId(),
|
||||
metadata: filter.toIdentifier(),
|
||||
},
|
||||
...((JSON.parse(
|
||||
localStorage.getItem("mhsf__filters") ?? "[]",
|
||||
) as Array<ClerkEmbeddedFilter<unknown>>) ?? []),
|
||||
]),
|
||||
);
|
||||
else
|
||||
localStorage.setItem("mhsf__filters", JSON.stringify(existingArray));
|
||||
}
|
||||
|
||||
setApplied(findExisting());
|
||||
}
|
||||
communication.fromIframe.send("rerender-servers", {});
|
||||
}}
|
||||
>
|
||||
{applied === -1 ? "A" : "Una"}pply
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@ -41,6 +41,9 @@ export function ModificationFrame() {
|
||||
if (c.from === "iframe")
|
||||
communication.toIframe.send("ping", {from: "top-layer"})
|
||||
})
|
||||
communication.toIframe.handle("rerender-servers", (c) => {
|
||||
window.dispatchEvent(new Event("update-modification-stack"))
|
||||
})
|
||||
}, [ref])
|
||||
|
||||
return <iframe ref={ref} src="/servers/embedded/sl-modification-frame" height={800} title="Server-list Modification Frame" />
|
||||
|
||||
@ -89,28 +89,6 @@ export function MOTDRow({
|
||||
</button>
|
||||
)}
|
||||
</span>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem
|
||||
className="flex gap-2"
|
||||
onSelect={() =>
|
||||
window.dispatchEvent(new Event("open-rearrange-menu"))
|
||||
}
|
||||
>
|
||||
<Shuffle size={16} /> Rearrange server items
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
<DropdownMenuTrigger>
|
||||
<Button
|
||||
className="flex items-center"
|
||||
size="square-md"
|
||||
variant="secondary"
|
||||
>
|
||||
<EllipsisVertical size={16} />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</DropdownMenu>
|
||||
</span>
|
||||
<Separator className="my-2" />
|
||||
{tab === "motd" && (
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* MHSF, Minehut Server List
|
||||
* All external content is rather licensed under the ECA Agreement
|
||||
* located here: https://mhsf.app/docs/legal/external-content-agreement
|
||||
*
|
||||
* All code under MHSF is licensed under the MIT License
|
||||
* by open source contributors
|
||||
*
|
||||
* Copyright (c) 2025 dvelo
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
|
||||
import { useUser } from "@clerk/nextjs";
|
||||
import { Editor } from "@monaco-editor/react";
|
||||
import { useEffect, useState, type ReactNode } from "react";
|
||||
|
||||
export function ClerkMetadataPopup({children}: {children: ReactNode | ReactNode[] | undefined}) {
|
||||
const [mdType, setMDType] = useState<"public" | "unsafe" | null>(null)
|
||||
const [open, setOpen] = useState(false);
|
||||
const {user} = useUser();
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("open-public-clerkmd", () => {
|
||||
setMDType("public")
|
||||
setOpen(true);
|
||||
})
|
||||
window.addEventListener("open-unsafe-clerkmd", () => {
|
||||
setMDType("unsafe")
|
||||
setOpen(true);
|
||||
})
|
||||
})
|
||||
|
||||
return <>
|
||||
{children}
|
||||
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent className="min-w-[850px]">
|
||||
<DialogTitle>{mdType?.toLocaleUpperCase()} metadata</DialogTitle>
|
||||
<Editor options={{domReadOnly: true, readOnly: true}} height={500} width={800} language="json" value={JSON.stringify(mdType === "public" ? user?.publicMetadata : user?.unsafeMetadata, null, 2)}/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
}
|
||||
@ -29,14 +29,30 @@
|
||||
*/
|
||||
|
||||
import { Material } from "@/components/ui/material";
|
||||
import { Setting, SettingContent, SettingDescription, SettingMeta, SettingTitle } from "./setting";
|
||||
import {
|
||||
Setting,
|
||||
SettingContent,
|
||||
SettingDescription,
|
||||
SettingMeta,
|
||||
SettingTitle,
|
||||
} from "./setting";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { AnimatedText } from "@/components/ui/animated-text";
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { loadingList } from "../server-page/util";
|
||||
import { ClerkMetadataPopup } from "./clerk-metadata-popup";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Link } from "@/components/util/link";
|
||||
import { useSettingsStore } from "@/lib/hooks/use-settings-store";
|
||||
|
||||
export function DebugSettings() {
|
||||
const [randomText, setRandomText] = useState("")
|
||||
const [randomText, setRandomText] = useState("");
|
||||
|
||||
return (
|
||||
<Material className="mt-6 grid gap-4">
|
||||
@ -44,23 +60,46 @@ export function DebugSettings() {
|
||||
<Setting>
|
||||
<SettingContent>
|
||||
<SettingMeta>
|
||||
<SettingTitle>
|
||||
Generate loading text
|
||||
</SettingTitle>
|
||||
<SettingTitle>Generate loading text</SettingTitle>
|
||||
<SettingDescription>
|
||||
Generate a random loading text
|
||||
</SettingDescription>
|
||||
</SettingMeta>
|
||||
<div className="block pb-6">
|
||||
<Button onClick={() => {
|
||||
setRandomText(loadingList[Math.floor(Math.random() * loadingList.length)])
|
||||
}}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setRandomText(
|
||||
loadingList[Math.floor(Math.random() * loadingList.length)],
|
||||
);
|
||||
}}
|
||||
>
|
||||
Generate
|
||||
</Button>
|
||||
<AnimatedText className="font-bold" text={randomText + "..."}/>
|
||||
<AnimatedText className="font-bold" text={randomText + "..."} />
|
||||
</div>
|
||||
</SettingContent>
|
||||
</Setting>
|
||||
<ClerkMetadataPopup>
|
||||
<Setting>
|
||||
<SettingContent>
|
||||
<SettingMeta>
|
||||
<SettingTitle>View Clerk metadata</SettingTitle>
|
||||
<SettingDescription>
|
||||
View any Clerk metadata for your user.
|
||||
</SettingDescription>
|
||||
</SettingMeta>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<Button>Open metadata</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={() => window.dispatchEvent(new Event("open-public-clerkmd"))}>Public</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => window.dispatchEvent(new Event("open-unsafe-clerkmd"))}>Unsafe</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SettingContent>
|
||||
</Setting>
|
||||
</ClerkMetadataPopup>
|
||||
</Material>
|
||||
);
|
||||
}
|
||||
|
||||
@ -28,6 +28,8 @@
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
"use client";
|
||||
|
||||
import { useTheme } from "@/lib/hooks/use-theme";
|
||||
import type { SVGProps } from "react";
|
||||
const Github = (props: SVGProps<SVGSVGElement>) => {
|
||||
|
||||
@ -89,7 +89,7 @@ export const serverModDB: ModDBCategory[] = [
|
||||
name: "Always Online",
|
||||
description: "All servers that are always online.",
|
||||
color: "#a380e0",
|
||||
value: new TagFilter(0),
|
||||
value: new TagFilter(2),
|
||||
icon: ServerCog
|
||||
}
|
||||
]
|
||||
|
||||
@ -36,10 +36,12 @@ import { tryCatch } from "../try-catch";
|
||||
import { transpileTypeScript } from "@/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/file/[filename]/page";
|
||||
import { useUser } from "@clerk/nextjs";
|
||||
import type { ClerkCustomActivatedModification } from "@/components/feat/server-list/modification/modification-file-creation-dialog";
|
||||
import { ClerkEmbeddedFilter } from "@/components/feat/server-list/modification/modification-action";
|
||||
import { supportedFilters } from "../types/filter";
|
||||
|
||||
type EmbeddedFilter = {
|
||||
identifier: string;
|
||||
functionFilter: (server: OnlineServer) => boolean;
|
||||
functionFilter: (server: OnlineServer) => (boolean | Promise<boolean>);
|
||||
};
|
||||
|
||||
type SortFunction<K> = (object1: K, object2: K) => number;
|
||||
@ -57,23 +59,20 @@ export function useFilters(data: OnlineServer[]) {
|
||||
const [sort, setSort] = useState<SortFunction<OnlineServer> | null>(null);
|
||||
const { user, isSignedIn } = useUser();
|
||||
|
||||
const updateServers = (newFilters: EmbeddedFilter[]) => {
|
||||
const modificationMap = data.map((v) =>
|
||||
newFilters.map((c) => c.functionFilter(v)),
|
||||
);
|
||||
const updateServers = async (newFilters: EmbeddedFilter[]) => {
|
||||
const modificationMap = await Promise.all(data.map((v) =>
|
||||
Promise.all(newFilters.map(async (c) => c.functionFilter(v))),
|
||||
));
|
||||
const resultData = data.filter(
|
||||
(_, i) => !modificationMap[i].includes(false),
|
||||
);
|
||||
const sortedData = sort === null ? resultData : resultData.sort(sort);
|
||||
|
||||
console.table({ sortedData, modificationMap, resultData, data });
|
||||
console.log({ sortedData, modificationMap, resultData, data, newFilters });
|
||||
|
||||
|
||||
if (sortedData.length !== 0)
|
||||
setFilteredData(sortedData);
|
||||
if (sortedData.length !== 0) setFilteredData(sortedData);
|
||||
};
|
||||
|
||||
// biome-ignore lint: bruh
|
||||
useEffect(() => {
|
||||
if (filteredData.length === 0 || data.length === 0) {
|
||||
window.dispatchEvent(new Event("update-modification-stack"));
|
||||
@ -213,7 +212,13 @@ export function useFilters(data: OnlineServer[]) {
|
||||
if (!t)
|
||||
window.addEventListener("update-modification-stack", async () => {
|
||||
await user?.reload();
|
||||
setLoading(true);
|
||||
let newFilters: EmbeddedFilter[] = [];
|
||||
const filters =
|
||||
((isSignedIn ? user.unsafeMetadata.filters : JSON.parse(localStorage.getItem("mhsf__filters") ?? "[]")) as Array<
|
||||
ClerkEmbeddedFilter<unknown>
|
||||
>) ?? [];
|
||||
|
||||
if (isSignedIn) {
|
||||
const activatedModifications =
|
||||
(user.unsafeMetadata
|
||||
@ -250,40 +255,66 @@ export function useFilters(data: OnlineServer[]) {
|
||||
toast.error(
|
||||
`Couldn't enable modification '${c.friendlyName}'. Please lint and test again.`,
|
||||
);
|
||||
return { identifier: `file-${c.originalFileName}.ts`, functionFilter: () => true };
|
||||
return {
|
||||
identifier: `file-${c.originalFileName}.ts`,
|
||||
functionFilter: () => true,
|
||||
};
|
||||
}
|
||||
|
||||
if (typeof filterFunc === "function") {
|
||||
return { identifier: `file-${c.originalFileName}.ts`, functionFilter: filterFunc };
|
||||
return {
|
||||
identifier: `file-${c.originalFileName}.ts`,
|
||||
functionFilter: filterFunc,
|
||||
};
|
||||
}
|
||||
|
||||
toast.error(
|
||||
`Couldn't enable modification '${c.friendlyName}'. Please lint and test again.`,
|
||||
);
|
||||
return { identifier: `file-${c.originalFileName}.ts`, functionFilter: () => true };
|
||||
return {
|
||||
identifier: `file-${c.originalFileName}.ts`,
|
||||
functionFilter: () => true,
|
||||
};
|
||||
}),
|
||||
)) as EmbeddedFilter[];
|
||||
|
||||
// avoid duplicates
|
||||
// biome-ignore lint/complexity/noForEach:
|
||||
resolvedModifications.forEach((item) => {
|
||||
setFilters((c) => {
|
||||
if (c.findIndex((i) => i.identifier === item.identifier) === -1)
|
||||
return [
|
||||
...c,
|
||||
item
|
||||
]
|
||||
else return c;
|
||||
return [...c, item];
|
||||
return c;
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
newFilters = resolvedModifications.map((item) => {
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
// biome-ignore lint/complexity/noForEach:
|
||||
filters.forEach((filter) => {
|
||||
// Get back the filter type from the namespace
|
||||
const filterType = supportedFilters.find(
|
||||
(t) => filter.type === t.ns,
|
||||
);
|
||||
// Get back a filter with associated metadata
|
||||
const parsedFilter = filterType?.fi(
|
||||
filter.metadata as {
|
||||
[key: string]: string | number | boolean;
|
||||
},
|
||||
);
|
||||
|
||||
newFilters.push({
|
||||
identifier: filterType?.ns + (Math.random() * Math.random() * Math.random()).toString(),
|
||||
functionFilter: (server: OnlineServer) => parsedFilter?.applyToServer({ online: server }) ?? true
|
||||
})
|
||||
});
|
||||
|
||||
console.log(newFilters);
|
||||
|
||||
updateServers(newFilters);
|
||||
await updateServers(newFilters);
|
||||
});
|
||||
}, [data]);
|
||||
console.log(filters);
|
||||
@ -293,7 +324,9 @@ export function useFilters(data: OnlineServer[]) {
|
||||
testModeEnabled,
|
||||
testModeLoading,
|
||||
testModeStatus,
|
||||
filterCount: filters.filter((item, index, array) => array.indexOf(item) === index).length + (sort === null ? 1 : 0),
|
||||
filterCount:
|
||||
filters.filter((item, index, array) => array.indexOf(item) === index)
|
||||
.length + (sort === null ? 1 : 0),
|
||||
loading,
|
||||
};
|
||||
}
|
||||
|
||||
@ -28,6 +28,17 @@
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
export default function Dashboard() {
|
||||
return <>Hello world</>
|
||||
import { allTags } from "@/config/tags"
|
||||
import { useUser } from "@clerk/nextjs"
|
||||
|
||||
export function useModificationsChange() {
|
||||
const {isSignedIn} = useUser()
|
||||
|
||||
return {
|
||||
addFilterData: async (namespace: string, identifier: { [key: string]: string | number | boolean }) => {
|
||||
if (isSignedIn) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -74,3 +74,24 @@ export function useSettingsStore() {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// An exact copy of the API without Clerk
|
||||
export function useNoClerkSettingsStore() {
|
||||
return {
|
||||
get: (key: string) => {
|
||||
if (localStorage.getItem(key) === "true")
|
||||
return true;
|
||||
if (localStorage.getItem(key) === "false")
|
||||
return false;
|
||||
return localStorage.getItem(key);
|
||||
},
|
||||
set: async (
|
||||
key: string,
|
||||
value: string | boolean,
|
||||
userEntry: boolean,
|
||||
__unsafeMetadata = false
|
||||
) => {
|
||||
localStorage.setItem(key, value.toString());
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -32,6 +32,7 @@ import { allTags } from "@/config/tags";
|
||||
import type { OnlineServer, ServerResponse } from "./mh-server";
|
||||
import type { MHSFData } from "./data";
|
||||
import { TagFilter } from "./filters/tag-filter";
|
||||
import { CategoryFilter } from "./filters/category-filter";
|
||||
|
||||
/* Any filter that can be converted back and forth from a string or a Filter object */
|
||||
export interface Filter {
|
||||
@ -49,11 +50,17 @@ export interface Filter {
|
||||
}
|
||||
|
||||
export const supportedFilters: {
|
||||
t: (implementation: unknown) => Filter;
|
||||
ns: string;
|
||||
fi: (identifier: {
|
||||
[key: string]: string | number | boolean;
|
||||
}) => Filter;
|
||||
}[] = [
|
||||
{
|
||||
t: (i) => new TagFilter(i as string | number),
|
||||
ns: "app.mhsf.filter.tagFilter",
|
||||
fi: new TagFilter(0, false).fromIdentifier
|
||||
},
|
||||
{
|
||||
ns: "app.mhsf.filter.categoryFilter",
|
||||
fi: new CategoryFilter(0).fromIdentifier
|
||||
}
|
||||
];
|
||||
|
||||
@ -28,25 +28,37 @@
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getAuth, clerkClient } from "@clerk/nextjs/server";
|
||||
import { MongoClient } from "mongodb";
|
||||
import { waitUntil } from "@vercel/functions";
|
||||
import { supportedFilters } from "@/lib/types/filter";
|
||||
import { allCategories } from "@/config/tags";
|
||||
import type { MHSFData } from "../data";
|
||||
import type { Filter } from "../filter";
|
||||
import type { OnlineServer, ServerResponse } from "../mh-server";
|
||||
|
||||
const supportedNamespaces = supportedFilters.map((c) => c.ns);
|
||||
export class CategoryFilter implements Filter {
|
||||
categoryIndex: number;
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
) {
|
||||
const { userId } = getAuth(req);
|
||||
|
||||
if (!userId) {
|
||||
return res.status(401).json({ error: "Unauthorized" });
|
||||
type(): "filter" {
|
||||
return "filter";
|
||||
}
|
||||
const client = new MongoClient(process.env.MONGO_DB as string);
|
||||
await client.connect();
|
||||
|
||||
toIdentifier(): { [key: string]: string | number | boolean } {
|
||||
return { categoryIndex: this.categoryIndex };
|
||||
}
|
||||
|
||||
fromIdentifier(identifier: { [key: string]: string | number | boolean; }): Filter {
|
||||
return new CategoryFilter(identifier.categoryIndex as number);
|
||||
}
|
||||
|
||||
getSpecificFilterId(): string {
|
||||
return "app.mhsf.filter.categoryFilter";
|
||||
}
|
||||
|
||||
applyToServer(server: { online?: OnlineServer; server?: ServerResponse; mhsfData?: MHSFData; }): Promise<boolean> {
|
||||
if (server.online !== undefined)
|
||||
return allCategories[this.categoryIndex].condition(server.online);
|
||||
return new Promise((r) => r(true));
|
||||
}
|
||||
|
||||
constructor(categoryIndex: number) {
|
||||
this.categoryIndex = categoryIndex;
|
||||
}
|
||||
}
|
||||
@ -35,13 +35,14 @@ import type { Filter } from "../filter";
|
||||
|
||||
export class TagFilter implements Filter {
|
||||
tagId: string;
|
||||
opposite: boolean;
|
||||
|
||||
type(): "filter" {
|
||||
return "filter";
|
||||
}
|
||||
|
||||
toIdentifier(): { [key: string]: string | number | boolean } {
|
||||
return { tagId: this.tagId };
|
||||
return { tagId: this.tagId, opposite: this.opposite };
|
||||
}
|
||||
|
||||
getSpecificFilterId(): string {
|
||||
@ -51,12 +52,13 @@ export class TagFilter implements Filter {
|
||||
fromIdentifier(identifier: {
|
||||
[key: string]: string | number | boolean;
|
||||
}): Filter {
|
||||
return new TagFilter(identifier.tagId as string);
|
||||
return new TagFilter(identifier.tagId as string, identifier.opposite as boolean);
|
||||
}
|
||||
|
||||
constructor(tagIndex: number | string) {
|
||||
constructor(tagIndex: number | string, opposite: boolean) {
|
||||
if (typeof tagIndex === "string") this.tagId = tagIndex;
|
||||
else this.tagId = btoa(allTags[tagIndex].docsName);
|
||||
this.opposite = opposite;
|
||||
}
|
||||
|
||||
applyToServer(server: {
|
||||
@ -72,8 +74,14 @@ export class TagFilter implements Filter {
|
||||
).condition ?? (() => true)
|
||||
)(server);
|
||||
|
||||
console.log(result, server.online?.name, (
|
||||
allTags.find((c) => btoa(c.docsName) === this.tagId) ?? {
|
||||
condition: () => true,
|
||||
}
|
||||
));
|
||||
|
||||
if (typeof result === "boolean")
|
||||
return new Promise((r) => r(result))
|
||||
return new Promise((r) => r(!result))
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -49,6 +49,8 @@ export default process.env.NEXT_PUBLIC_IS_AUTH === "true"
|
||||
? clerkMiddleware(async (auth, req) => {
|
||||
const authRes = await auth();
|
||||
const client = await clerkClient();
|
||||
const requestHeaders = new Headers(req.headers);
|
||||
requestHeaders.set("x-url", req.url);
|
||||
|
||||
if (isRootRoute(req)) {
|
||||
switch (authRes.userId === null) {
|
||||
@ -70,6 +72,12 @@ export default process.env.NEXT_PUBLIC_IS_AUTH === "true"
|
||||
new URL(`/server/v2/minehut/${minehutRes.server._id}`, req.url),
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.next({
|
||||
request: {
|
||||
headers: requestHeaders,
|
||||
},
|
||||
});
|
||||
})
|
||||
: (request: NextRequest) => {};
|
||||
|
||||
|
||||
@ -45,8 +45,6 @@ export default async function handler(
|
||||
const result = await betterStackResult.json();
|
||||
const url = await betterStackURL.json();
|
||||
|
||||
console.log(result)
|
||||
|
||||
const filtered = result.data.filter(
|
||||
(c: any) =>
|
||||
c.attributes.ends_at === null &&
|
||||
|
||||
Loading…
Reference in New Issue
Block a user