feat(v2): some changes

This commit is contained in:
dvelo 2025-04-29 19:00:26 -05:00
parent f21ce33b27
commit b62f79e010
23 changed files with 580 additions and 494 deletions

@ -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 &&