mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-07 19:35:00 -05:00
feat: custom filters
This commit is contained in:
parent
bba5504f5d
commit
7385705c8d
@ -49,6 +49,9 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
webpack: (config) => {
|
||||||
|
return config;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withContentlayer(nextConfig);
|
export default withContentlayer(nextConfig);
|
||||||
|
|||||||
@ -55,6 +55,7 @@
|
|||||||
"mini-svg-data-uri": "^1.4.4",
|
"mini-svg-data-uri": "^1.4.4",
|
||||||
"minimessage-2-html": "1.6.0",
|
"minimessage-2-html": "1.6.0",
|
||||||
"minimessage-js": "^1.1.3",
|
"minimessage-js": "^1.1.3",
|
||||||
|
"monaco-editor": "^0.52.2",
|
||||||
"mongodb": "^6.8.0",
|
"mongodb": "^6.8.0",
|
||||||
"next": "15.2.0",
|
"next": "15.2.0",
|
||||||
"next-contentlayer": "^0.3.4",
|
"next-contentlayer": "^0.3.4",
|
||||||
|
|||||||
95
apps/www/src/app/(sl-modification-frame)/layout.tsx
Normal file
95
apps/www/src/app/(sl-modification-frame)/layout.tsx
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* 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 { 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";
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const search = searchParams?.get("theme") || "light";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<noscript>
|
||||||
|
<main className="flex justify-center items-center text-center min-h-screen h-max">
|
||||||
|
<Placeholder
|
||||||
|
icon={<X />}
|
||||||
|
title="JavaScript is required for MHSF"
|
||||||
|
description="MHSF cannot grab servers or do other external requests without JavaScript."
|
||||||
|
>
|
||||||
|
<Link href="https://www.enable-javascript.com/">
|
||||||
|
<Button>Here's how</Button>
|
||||||
|
</Link>
|
||||||
|
</Placeholder>
|
||||||
|
</main>
|
||||||
|
</noscript>
|
||||||
|
<ThemeProvider
|
||||||
|
attribute="class"
|
||||||
|
defaultTheme="system"
|
||||||
|
enableSystem
|
||||||
|
disableTransitionOnChange
|
||||||
|
>
|
||||||
|
<ClerkProvider>
|
||||||
|
<IsScript>
|
||||||
|
<NuqsAdapter>
|
||||||
|
<FontBoundary className="max-w-[800px]">
|
||||||
|
<IframeProtector>
|
||||||
|
<TooltipProvider>
|
||||||
|
<Toaster richColors position="top-center" />
|
||||||
|
<div className="overflow-x-hidden">{children}</div>
|
||||||
|
</TooltipProvider>
|
||||||
|
</IframeProtector>
|
||||||
|
</FontBoundary>
|
||||||
|
</NuqsAdapter>
|
||||||
|
</IsScript>
|
||||||
|
</ClerkProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
79
apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/category/[category]/modification/[mod]/page.tsx
Normal file
79
apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/category/[category]/modification/[mod]/page.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* 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 { ModificationAction } from "@/components/feat/server-list/modification/modification-action";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Link } from "@/components/util/link";
|
||||||
|
import { serverModDB } from "@/config/sl-mod-db";
|
||||||
|
import { ArrowLeft } from "lucide-react";
|
||||||
|
import { useQueryState } from "nuqs";
|
||||||
|
import { use } from "react";
|
||||||
|
import Markdown from "react-markdown";
|
||||||
|
|
||||||
|
export default function ModificationPage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ category: string; mod: string }>;
|
||||||
|
}) {
|
||||||
|
const { category, mod } = use(params);
|
||||||
|
const [backRoute] = useQueryState("b", {
|
||||||
|
defaultValue: "/servers/embedded/sl-modification-frame",
|
||||||
|
});
|
||||||
|
console.log(mod);
|
||||||
|
const categoryObj = serverModDB.find(
|
||||||
|
(c) => c.displayTitle === atob(decodeURIComponent(category))
|
||||||
|
);
|
||||||
|
let modObj = null;
|
||||||
|
if (categoryObj !== undefined)
|
||||||
|
modObj = categoryObj?.entries.find(
|
||||||
|
(c) => c.name === atob(decodeURIComponent(mod))
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="max-w-[800px] p-4">
|
||||||
|
<div
|
||||||
|
className="h-[150px] w-full rounded-xl p-2"
|
||||||
|
style={{ backgroundColor: modObj?.color }}
|
||||||
|
>
|
||||||
|
<Link href={backRoute}>
|
||||||
|
<ArrowLeft />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span className="p-4">
|
||||||
|
<h1 className="text-xl font-bold w-full">{modObj?.name}</h1>
|
||||||
|
<Markdown className="text-wrap pt-2">{modObj?.description}</Markdown>
|
||||||
|
<ModificationAction value={modObj?.value} />
|
||||||
|
</span>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
91
apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/category/[category]/page.tsx
Normal file
91
apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/category/[category]/page.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* 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 { ModificationAction } from "@/components/feat/server-list/modification/modification-action";
|
||||||
|
import { Material } from "@/components/ui/material";
|
||||||
|
import { Link } from "@/components/util/link";
|
||||||
|
import { serverModDB } from "@/config/sl-mod-db";
|
||||||
|
import { useRouter } from "@/lib/useRouter";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { ArrowLeft } from "lucide-react";
|
||||||
|
import { use } from "react";
|
||||||
|
import Markdown from "react-markdown";
|
||||||
|
|
||||||
|
export default function ServerListCategoryFrame({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ category: string }>;
|
||||||
|
}) {
|
||||||
|
const { category } = use(params);
|
||||||
|
const categoryObj = serverModDB.find(
|
||||||
|
(c) => c.displayTitle === atob(category)
|
||||||
|
);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="max-w-[800px] p-4">
|
||||||
|
<h1 className="text-xl font-bold w-full">
|
||||||
|
<Link href="/servers/embedded/sl-modification-frame">
|
||||||
|
<ArrowLeft />
|
||||||
|
</Link>
|
||||||
|
{categoryObj?.displayTitle}
|
||||||
|
</h1>
|
||||||
|
<Markdown className="text-wrap pt-2">{categoryObj?.description}</Markdown>
|
||||||
|
|
||||||
|
<div className="pt-10 p-4 grid grid-cols-6 gap-2">
|
||||||
|
{categoryObj?.entries.map((m) => (
|
||||||
|
<Material
|
||||||
|
className="p-2 hover:drop-shadow-card-hover cursor-pointer"
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/servers/embedded/sl-modification-frame/category/${category}/modification/${btoa(m.name)}?b=${encodeURIComponent(`/servers/embedded/sl-modification-frame/category/${category}`)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
key={m.name}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"w-full h-[40px] mb-2 rounded-lg items-center text-center justify-center"
|
||||||
|
)}
|
||||||
|
style={{ backgroundColor: m.color }}
|
||||||
|
>
|
||||||
|
<m.icon className="relative top-[calc(50%-12px)] items-center w-full text-center justify-center" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm text-center w-full flex items-center justify-center">
|
||||||
|
{m.name}
|
||||||
|
</span>
|
||||||
|
</Material>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
150
apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/file/[filename]/page.tsx
Normal file
150
apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/file/[filename]/page.tsx
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { use } from "react";
|
||||||
|
import { useUser } from "@clerk/nextjs";
|
||||||
|
import type { ClerkCustomModification } from "@/components/feat/server-list/modification/modification-file-creation-dialog";
|
||||||
|
import { Link } from "@/components/util/link";
|
||||||
|
import { ArrowLeft } from "lucide-react";
|
||||||
|
import Editor from "@monaco-editor/react";
|
||||||
|
import poimandres from "@/theme.json";
|
||||||
|
|
||||||
|
const typeDefs = `
|
||||||
|
export interface Server {
|
||||||
|
staticInfo: {
|
||||||
|
_id: string;
|
||||||
|
serverPlan: string;
|
||||||
|
serviceStartDate: number;
|
||||||
|
platform: string;
|
||||||
|
planMaxPlayers: number;
|
||||||
|
planRam: number;
|
||||||
|
alwaysOnline: boolean;
|
||||||
|
rawPlan: string;
|
||||||
|
connectedServers: any[];
|
||||||
|
};
|
||||||
|
maxPlayers: number;
|
||||||
|
name: string;
|
||||||
|
motd: string;
|
||||||
|
icon: string;
|
||||||
|
playerData: {
|
||||||
|
playerCount: number;
|
||||||
|
timeNoPlayers: number;
|
||||||
|
};
|
||||||
|
connectable: boolean;
|
||||||
|
visibility: boolean;
|
||||||
|
allCategories: string[];
|
||||||
|
usingCosmetics: boolean;
|
||||||
|
author?: string;
|
||||||
|
authorRank: string;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function CustomFilePage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ filename: string }>;
|
||||||
|
}) {
|
||||||
|
const { filename } = use(params);
|
||||||
|
const { user } = useUser();
|
||||||
|
const file = (
|
||||||
|
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ?? []
|
||||||
|
).find((c) => c.name === filename);
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return <>Bruh.</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileContents = file.contents;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="max-w-[800px] p-4">
|
||||||
|
<strong className="font-bold w-full">
|
||||||
|
<Link href="/servers/embedded/sl-modification-frame/files">
|
||||||
|
<ArrowLeft />
|
||||||
|
</Link>
|
||||||
|
{filename}.ts
|
||||||
|
</strong>
|
||||||
|
<div className="h-[400px]">
|
||||||
|
<Editor
|
||||||
|
height="100%"
|
||||||
|
defaultLanguage="typescript"
|
||||||
|
defaultValue={fileContents}
|
||||||
|
theme="vs-dark"
|
||||||
|
onMount={(editor, monaco) => {
|
||||||
|
// Ensure TypeScript is properly configured
|
||||||
|
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
||||||
|
target: monaco.languages.typescript.ScriptTarget.Latest,
|
||||||
|
allowNonTsExtensions: true, // This is important!
|
||||||
|
moduleResolution:
|
||||||
|
monaco.languages.typescript.ModuleResolutionKind.NodeJs,
|
||||||
|
module: monaco.languages.typescript.ModuleKind.CommonJS,
|
||||||
|
noEmit: true,
|
||||||
|
esModuleInterop: true,
|
||||||
|
jsx: monaco.languages.typescript.JsxEmit.React,
|
||||||
|
reactNamespace: "React",
|
||||||
|
allowJs: true,
|
||||||
|
typeRoots: ["node_modules/@types"],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a virtual TS file for the types
|
||||||
|
const libUri = "file:///node_modules/@types/mhsf/index.d.ts";
|
||||||
|
|
||||||
|
// Add typedefs as a library
|
||||||
|
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||||
|
typeDefs,
|
||||||
|
libUri,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a model for the libUri file
|
||||||
|
if (!monaco.editor.getModel(monaco.Uri.parse(libUri))) {
|
||||||
|
monaco.editor.createModel(
|
||||||
|
typeDefs,
|
||||||
|
"typescript",
|
||||||
|
monaco.Uri.parse(libUri),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the current file is using the correct language
|
||||||
|
const currentModel = editor.getModel();
|
||||||
|
if (currentModel) {
|
||||||
|
monaco.editor.setModelLanguage(currentModel, "typescript");
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentUri = monaco.Uri.parse(`file:///${filename}.ts`);
|
||||||
|
if (!monaco.editor.getModel(currentUri)) {
|
||||||
|
monaco.editor.createModel(
|
||||||
|
fileContents,
|
||||||
|
"typescript",
|
||||||
|
currentUri,
|
||||||
|
);
|
||||||
|
editor.setModel(monaco.editor.getModel(currentUri));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
options={{
|
||||||
|
minimap: { enabled: false },
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
fontSize: 14,
|
||||||
|
lineNumbers: "on",
|
||||||
|
roundedSelection: false,
|
||||||
|
scrollbar: {
|
||||||
|
vertical: "visible",
|
||||||
|
horizontal: "visible",
|
||||||
|
},
|
||||||
|
quickSuggestions: true,
|
||||||
|
suggestOnTriggerCharacters: true,
|
||||||
|
acceptSuggestionOnEnter: "on",
|
||||||
|
tabCompletion: "on",
|
||||||
|
wordBasedSuggestions: "currentDocument",
|
||||||
|
parameterHints: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
enabled: true,
|
||||||
|
delay: 300,
|
||||||
|
sticky: true,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
54
apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/files/page.tsx
Normal file
54
apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/files/page.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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 { ClerkCustomModification } from "@/components/feat/server-list/modification/modification-file-creation-dialog";
|
||||||
|
import { Link } from "@/components/util/link";
|
||||||
|
import { useUser } from "@clerk/nextjs";
|
||||||
|
import { File, FileCode } from "lucide-react";
|
||||||
|
|
||||||
|
export default function ServerListModificationFrame() {
|
||||||
|
const { user } = useUser();
|
||||||
|
const files =
|
||||||
|
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ?? [];
|
||||||
|
return (
|
||||||
|
<main className="max-w-[800px] p-4">
|
||||||
|
<h1 className="text-xl font-bold w-full">Files</h1>
|
||||||
|
<div className="grid gap-1">
|
||||||
|
{files.map((c) => (
|
||||||
|
<Link href={`/servers/embedded/sl-modification-frame/file/${c.name}`} className="w-full py-1 px-2 rounded-xl flex items-center gap-1 hover:bg-slate-100" key={c.name}>
|
||||||
|
<FileCode size={16}/>{c.name}.ts
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
101
apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/page.tsx
Normal file
101
apps/www/src/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/page.tsx
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* 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 { Material } from "@/components/ui/material";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
import { Link } from "@/components/util/link";
|
||||||
|
import { serverModDB } from "@/config/sl-mod-db";
|
||||||
|
import { ArrowRight } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useRouter } from "@/lib/useRouter";
|
||||||
|
|
||||||
|
export default function ServerListModificationFrame() {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="max-w-[800px] p-4">
|
||||||
|
<h1 className="text-xl font-bold w-full">Filters & Sorting</h1>
|
||||||
|
<div className="flex items-center gap-2 my-2">
|
||||||
|
<Button size="sm">Active modifications</Button>
|
||||||
|
<Link href="/servers/embedded/sl-modification-frame/files">
|
||||||
|
<Button size="sm">Custom files</Button>
|
||||||
|
</Link>
|
||||||
|
<Button size="sm">Settings</Button>
|
||||||
|
</div>
|
||||||
|
<span className="text-wrap pt-2">
|
||||||
|
Pick out different filters & sorting systems to customize your server
|
||||||
|
viewing experience. We frequently add new filters in accordance to new
|
||||||
|
features, as well.
|
||||||
|
</span>
|
||||||
|
<Separator className="mt-4" />
|
||||||
|
<div className="pt-10 p-4">
|
||||||
|
{serverModDB.map((c) => (
|
||||||
|
<span key={c.displayTitle}>
|
||||||
|
<h2 className="text-lg font-bold pb-3 flex justify-between">
|
||||||
|
{c.displayTitle}
|
||||||
|
<Link
|
||||||
|
href={`/servers/embedded/sl-modification-frame/category/${btoa(c.displayTitle)}`}
|
||||||
|
className="flex gap-2 text-sm font-normal items-center"
|
||||||
|
>
|
||||||
|
<ArrowRight size={16} />
|
||||||
|
View more
|
||||||
|
</Link>
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-6 gap-2">
|
||||||
|
{c.entries.map((m) => (
|
||||||
|
<Material
|
||||||
|
className="p-2 hover:drop-shadow-card-hover cursor-pointer"
|
||||||
|
key={m.name}
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/servers/embedded/sl-modification-frame/category/${btoa(c.displayTitle)}/modification/${btoa(m.name)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="w-full h-[40px] mb-2 rounded-lg items-center text-center justify-center"
|
||||||
|
style={{ backgroundColor: m.color }}
|
||||||
|
>
|
||||||
|
<m.icon className="relative top-[calc(50%-12px)] items-center w-full text-center justify-center" />
|
||||||
|
</div>
|
||||||
|
<span className="text-sm text-center w-full flex items-center justify-center">
|
||||||
|
{m.name}
|
||||||
|
</span>
|
||||||
|
</Material>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
type Action = Filter | Sort | { customAction: string };
|
||||||
|
|
||||||
|
export function ModificationAction({ value }: { value?: Action }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{value !== undefined && "customAction" in value ? (
|
||||||
|
<ModificationFileCreationDialog
|
||||||
|
type={value.customAction.endsWith("sort") ? "sort" : "filter"}
|
||||||
|
>
|
||||||
|
<Button size="sm" className="mt-1">
|
||||||
|
{value.customAction === "custom-sort"
|
||||||
|
? "Create Sort"
|
||||||
|
: "Create Filter"}
|
||||||
|
</Button>
|
||||||
|
</ModificationFileCreationDialog>
|
||||||
|
) : (
|
||||||
|
<Button size="sm">Apply</Button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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 { Button } from "@/components/ui/button";
|
||||||
|
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
|
||||||
|
import { ModificationFrame } from "./modification-frame";
|
||||||
|
|
||||||
|
export function ModificationButton() {
|
||||||
|
return (
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger>
|
||||||
|
<Button>Filters & Sorting</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
|
||||||
|
<DialogContent className="p-0 h-[600px] w-[1000px] !max-w-[800px] overflow-x-hidden">
|
||||||
|
<ModificationFrame />
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
126
apps/www/src/components/feat/server-list/modification/modification-file-creation-dialog.tsx
Normal file
126
apps/www/src/components/feat/server-list/modification/modification-file-creation-dialog.tsx
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* 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 { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { useRouter } from "@/lib/useRouter";
|
||||||
|
import { useUser } from "@clerk/nextjs";
|
||||||
|
import { useState, type ReactNode } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
const sortTemplate = `import type { Server } from "mhsf";
|
||||||
|
|
||||||
|
export function sort(serverA: Server, serverB: Server): number {
|
||||||
|
// Your code here
|
||||||
|
// Use logic like \`Array.sort\` or <V>(a: V, b: V) => number
|
||||||
|
}`;
|
||||||
|
|
||||||
|
const filterTemplate = `import type { Server } from "mhsf";
|
||||||
|
|
||||||
|
export function filter(server: Server): boolean {
|
||||||
|
// Your code here
|
||||||
|
// Returning true indicates the server will stay, while returning false will remove the server from the queue.
|
||||||
|
}`;
|
||||||
|
|
||||||
|
export type ClerkCustomModification = {
|
||||||
|
name: string; // Add .ts to the end
|
||||||
|
active: boolean;
|
||||||
|
contents: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ModificationFileCreationDialog({
|
||||||
|
children,
|
||||||
|
type,
|
||||||
|
}: {
|
||||||
|
children?: ReactNode;
|
||||||
|
type: "filter" | "sort";
|
||||||
|
}) {
|
||||||
|
const { user, isSignedIn } = useUser();
|
||||||
|
const router = useRouter();
|
||||||
|
const [fileName, setFileName] = useState("");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger>{children}</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogTitle>Create new file</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Files can be a new filter or sort, made w/ TypeScript.
|
||||||
|
</DialogDescription>
|
||||||
|
<div className="flex items-center w-full">
|
||||||
|
<Input
|
||||||
|
className="rounded-r-none w-full"
|
||||||
|
placeholder="you-should-use-this-format-for-typescript-files-please"
|
||||||
|
onChange={(e) => setFileName(e.target.value)}
|
||||||
|
value={fileName}
|
||||||
|
/>
|
||||||
|
<span className="px-4 text-sm py-2 border border-l-none rounded-r-md">
|
||||||
|
.ts
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<DialogTrigger>
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
onClick={(e) => {
|
||||||
|
if (!isSignedIn) return toast.error("Please login.");
|
||||||
|
user?.update({
|
||||||
|
unsafeMetadata: {
|
||||||
|
customFiles: [
|
||||||
|
...((user.unsafeMetadata
|
||||||
|
.customFiles as Array<ClerkCustomModification>) ?? []),
|
||||||
|
{
|
||||||
|
name: fileName,
|
||||||
|
active: false,
|
||||||
|
contents:
|
||||||
|
type === "filter" ? filterTemplate : sortTemplate,
|
||||||
|
},
|
||||||
|
] satisfies Array<ClerkCustomModification>,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
toast.success("Created file!")
|
||||||
|
router.push(`/servers/embedded/sl-modification-frame/file/${fileName}`)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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 { useIframeCommunication } from "@/lib/hooks/use-iframe-communication"
|
||||||
|
import { useEffectOnce } from "@/lib/useEffectOnce"
|
||||||
|
import { useEffect, useRef } from "react"
|
||||||
|
|
||||||
|
export function ModificationFrame() {
|
||||||
|
const ref = useRef<HTMLIFrameElement>(null)
|
||||||
|
const communication = useIframeCommunication(ref);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
communication.toIframe.handle("ping", (c) => {
|
||||||
|
if (c.from === "iframe")
|
||||||
|
communication.toIframe.send("ping", {from: "top-layer"})
|
||||||
|
})
|
||||||
|
}, [ref])
|
||||||
|
|
||||||
|
return <iframe ref={ref} src="/servers/embedded/sl-modification-frame" height={800} width={800} title="Server-list Modification Frame" />
|
||||||
|
}
|
||||||
@ -62,11 +62,11 @@ export default function ServerCard({ server }: { server: OnlineServer }) {
|
|||||||
return (
|
return (
|
||||||
<Material
|
<Material
|
||||||
className="min-h-[250px] max-h-[250px] cursor-pointer outline-0 group hover:drop-shadow-card-hover focus:drop-shadow-card-hover transition-all"
|
className="min-h-[250px] max-h-[250px] cursor-pointer outline-0 group hover:drop-shadow-card-hover focus:drop-shadow-card-hover transition-all"
|
||||||
onClick={() => router.push(`/server/${server.staticInfo._id}`)}
|
onClick={() => router.push(`/server/v2/minehut/${server.staticInfo._id}`)}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
// Only send user when they hit "Enter"
|
// Only send user when they hit "Enter"
|
||||||
if (e.key === "Enter") router.push(`/server/${server.staticInfo._id}`);
|
if (e.key === "Enter") router.push(`/server/v2/minehut/${server.staticInfo._id}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="text-sm hidden group-focus-visible:block text-muted-foreground mb-2">
|
<span className="text-sm hidden group-focus-visible:block text-muted-foreground mb-2">
|
||||||
|
|||||||
@ -37,15 +37,12 @@ import { Statistics } from "./statistics";
|
|||||||
import InfiniteScroll from "react-infinite-scroll-component";
|
import InfiniteScroll from "react-infinite-scroll-component";
|
||||||
import { useInfiniteScrolling } from "@/lib/hooks/use-infinite-scrolling";
|
import { useInfiniteScrolling } from "@/lib/hooks/use-infinite-scrolling";
|
||||||
import { useMHSFServer } from "@/lib/hooks/use-mhsf-multiple";
|
import { useMHSFServer } from "@/lib/hooks/use-mhsf-multiple";
|
||||||
|
import { ModificationButton } from "./modification/modification-button";
|
||||||
|
|
||||||
export function ServerList() {
|
export function ServerList() {
|
||||||
const { servers, loading, serverCount, playerCount } = useServers();
|
const { servers, loading, serverCount, playerCount } = useServers();
|
||||||
const { itemsLength, fetchMoreData, hasMoreData, data } =
|
const { itemsLength, fetchMoreData, hasMoreData, data } =
|
||||||
useInfiniteScrolling(servers);
|
useInfiniteScrolling(servers);
|
||||||
const mhsfServers = useMHSFServer(
|
|
||||||
servers.map((s) => s.staticInfo._id),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
if (loading)
|
if (loading)
|
||||||
return (
|
return (
|
||||||
@ -68,6 +65,7 @@ export function ServerList() {
|
|||||||
<h1 className="scroll-m-20 text-2xl font-extrabold tracking-tight lg:text-4xl">
|
<h1 className="scroll-m-20 text-2xl font-extrabold tracking-tight lg:text-4xl">
|
||||||
Servers
|
Servers
|
||||||
</h1>
|
</h1>
|
||||||
|
<ModificationButton />
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
dataLength={itemsLength}
|
dataLength={itemsLength}
|
||||||
next={fetchMoreData}
|
next={fetchMoreData}
|
||||||
|
|||||||
@ -4,6 +4,11 @@ import { TextArea } from "@/components/ui/text-area";
|
|||||||
import { Link } from "@/components/util/link";
|
import { Link } from "@/components/util/link";
|
||||||
import type { useMHSFServer } from "@/lib/hooks/use-mhsf-server";
|
import type { useMHSFServer } from "@/lib/hooks/use-mhsf-server";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { SignedIn, SignedOut, useUser } from "@clerk/nextjs";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export function ReportingDialog({
|
export function ReportingDialog({
|
||||||
server,
|
server,
|
||||||
@ -14,7 +19,9 @@ export function ReportingDialog({
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (newState: boolean) => void;
|
setOpen: (newState: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
|
const { user, isSignedIn } = useUser();
|
||||||
const [reason, setReason] = useState("");
|
const [reason, setReason] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer direction="left" open={open} onOpenChange={setOpen}>
|
<Drawer direction="left" open={open} onOpenChange={setOpen}>
|
||||||
@ -52,9 +59,50 @@ export function ReportingDialog({
|
|||||||
</ul>
|
</ul>
|
||||||
</Alert>
|
</Alert>
|
||||||
<br />
|
<br />
|
||||||
<TextArea label="Reason for reporting" />
|
<TextArea
|
||||||
|
label="Reason for reporting"
|
||||||
|
value={reason}
|
||||||
|
onChange={(e) => setReason(e.target.value)}
|
||||||
|
/>
|
||||||
<br />
|
<br />
|
||||||
<span></span>
|
<SignedIn>
|
||||||
|
<span className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
|
<Image
|
||||||
|
alt="Clerk Image"
|
||||||
|
src={
|
||||||
|
user?.imageUrl === undefined
|
||||||
|
? "https://img.clerk.com/preview.png?size=144&seed=seed&initials=AD&isSquare=true&bgType=marble&bgColor=6c47ff&fgType=silhouette&fgColor=FFFFFF&type=user&w=48&q=75"
|
||||||
|
: user?.imageUrl
|
||||||
|
}
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
className="rounded-full"
|
||||||
|
/>
|
||||||
|
Signed in as @{user?.username}
|
||||||
|
</span>
|
||||||
|
</SignedIn>
|
||||||
|
<SignedOut>
|
||||||
|
<span className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
|
You must be signed in to perform this action.
|
||||||
|
</span>
|
||||||
|
</SignedOut>
|
||||||
|
<br />
|
||||||
|
<Button
|
||||||
|
disabled={!isSignedIn || loading}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
onClick={async () => {
|
||||||
|
if (reason === "" || reason === " ") {
|
||||||
|
toast.error("The reason cannot be empty.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(true);
|
||||||
|
await server.reportServer(reason);
|
||||||
|
setLoading(false);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Report Server {loading && <Spinner />}
|
||||||
|
</Button>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import {
|
|||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
|
||||||
@ -103,6 +104,9 @@ export function ServerPageButtons({
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuSeparator>
|
||||||
|
Destructive
|
||||||
|
</DropdownMenuSeparator>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
className="text-red-400 flex items-center gap-2"
|
className="text-red-400 flex items-center gap-2"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@ -66,6 +66,7 @@ const TextArea: React.FC<TextAreaProps> = ({
|
|||||||
rows = 4,
|
rows = 4,
|
||||||
id = generateID(),
|
id = generateID(),
|
||||||
className = "",
|
className = "",
|
||||||
|
onChange,
|
||||||
...restProps
|
...restProps
|
||||||
}) => {
|
}) => {
|
||||||
const elementRef = useRef<HTMLTextAreaElement | null>(null);
|
const elementRef = useRef<HTMLTextAreaElement | null>(null);
|
||||||
@ -93,7 +94,7 @@ const TextArea: React.FC<TextAreaProps> = ({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
rows={rows}
|
rows={rows}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => restProps.onChange?.(e)}
|
onChange={onChange}
|
||||||
ref={elementRef}
|
ref={elementRef}
|
||||||
className={`${sizeClass[size]} ${borderClass} focus:border-slate-800 dark:focus:border-zinc-200 bg-white dark:bg-zinc-950 focus:outline-hidden focus:ring-2 ring-slate-800/50 rounded-xl dark:ring-zinc-200/50 transition-all text-sm w-full disabled:bg-slate-100 disabled:cursor-not-allowed invalid:border-red-500! peer invalid:text-red-500 z-10 ${className}`}
|
className={`${sizeClass[size]} ${borderClass} focus:border-slate-800 dark:focus:border-zinc-200 bg-white dark:bg-zinc-950 focus:outline-hidden focus:ring-2 ring-slate-800/50 rounded-xl dark:ring-zinc-200/50 transition-all text-sm w-full disabled:bg-slate-100 disabled:cursor-not-allowed invalid:border-red-500! peer invalid:text-red-500 z-10 ${className}`}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -44,8 +44,10 @@ const overflowXHiddenPages = ["/home"];
|
|||||||
|
|
||||||
export function FontBoundary({
|
export function FontBoundary({
|
||||||
children,
|
children,
|
||||||
|
className
|
||||||
}: {
|
}: {
|
||||||
children?: ReactNode | ReactNode[];
|
children?: ReactNode | ReactNode[];
|
||||||
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const [fontFamily, setFontFamily] = useState("inter");
|
const [fontFamily, setFontFamily] = useState("inter");
|
||||||
@ -71,7 +73,7 @@ export function FontBoundary({
|
|||||||
default:
|
default:
|
||||||
return "system-ui-font--font-boundary";
|
return "system-ui-font--font-boundary";
|
||||||
}
|
}
|
||||||
})()} ${pathname !== null && overflowXHiddenPages.includes(pathname) ? "overflow-x-hidden" : ""}`}
|
})()} ${pathname !== null && overflowXHiddenPages.includes(pathname) ? "overflow-x-hidden" : ""} ${className}`}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -28,41 +28,32 @@
|
|||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { LinearClient, LinearFetch, User } from "@linear/sdk";
|
"use client";
|
||||||
|
|
||||||
export async function createReportIssue(
|
import { useIframeCommunication } from "@/lib/hooks/use-iframe-communication";
|
||||||
server: string,
|
import { useEffectOnce } from "@/lib/useEffectOnce";
|
||||||
reportDescription: string,
|
import { type ReactNode, useState } from "react";
|
||||||
userId: string,
|
import { Spinner } from "../ui/spinner";
|
||||||
) {
|
|
||||||
const linearClient = new LinearClient({
|
export function IframeProtector({ children }: { children: ReactNode }) {
|
||||||
apiKey: process.env.LINEAR,
|
const [loading, setLoading] = useState(true);
|
||||||
|
const iframeCommunication = useIframeCommunication();
|
||||||
|
|
||||||
|
useEffectOnce(() => {
|
||||||
|
// Make sure top layer frames are actually coming from MHSF
|
||||||
|
iframeCommunication.fromIframe.send("ping", { from: "iframe" });
|
||||||
|
iframeCommunication.fromIframe.handle("ping", (obj) => {
|
||||||
|
if (obj.from === "top-layer") setLoading(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const allTeams = await linearClient.teams();
|
if (loading)
|
||||||
// Always grabs the first issue category.
|
return (
|
||||||
const team = allTeams.nodes[0];
|
<div className="max-w-[800px]">
|
||||||
|
<div className="absolute top-[50%] left-[50%]">
|
||||||
// Ensure there *actually* is a team there
|
<Spinner />
|
||||||
if (team.id) {
|
</div>
|
||||||
await linearClient.createIssue({
|
</div>
|
||||||
teamId: team.id,
|
);
|
||||||
title: `Issue against server \`${server}\``,
|
return children;
|
||||||
description: desc(userId, server, reportDescription),
|
|
||||||
assigneeId: (await team.members()).nodes[0].id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const desc = (user: string, server: string, reason: string) => `There was a report against the server, submitted by a user.
|
|
||||||
Every issue must be [considered with care](https://list.mlnehut.com/docs/legal/external-content-agreement) before evaluating its outcome.
|
|
||||||
|
|
||||||
|
|
||||||
**User**: \`${user}\`
|
|
||||||
**Server**: \`${server}\`
|
|
||||||
**For reason**:
|
|
||||||
${reason}
|
|
||||||
|
|
||||||
|
|
||||||
*This was an automatically added issue by the report bot. Add the canceled status to remove the issue from the active issues, along with the labels Not Controllable & Spam for their respective values.*
|
|
||||||
`;
|
|
||||||
76
apps/www/src/config/sl-mod-db.ts
Normal file
76
apps/www/src/config/sl-mod-db.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
ArrowDownUpIcon,
|
||||||
|
SlidersHorizontal,
|
||||||
|
type LucideIcon,
|
||||||
|
} from "lucide-react";
|
||||||
|
import type { Filter } from "../lib/types/filter";
|
||||||
|
import type { Sort } from "../lib/types/sort";
|
||||||
|
|
||||||
|
type ModDBCategory = {
|
||||||
|
displayTitle: string;
|
||||||
|
description: string;
|
||||||
|
__custom?: boolean;
|
||||||
|
entries: {
|
||||||
|
name: string;
|
||||||
|
icon: LucideIcon;
|
||||||
|
color: string;
|
||||||
|
value: Filter | Sort | { customAction: string };
|
||||||
|
description: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const serverModDB: ModDBCategory[] = [
|
||||||
|
{
|
||||||
|
displayTitle: "Custom Files",
|
||||||
|
__custom: true,
|
||||||
|
description:
|
||||||
|
`Create custom TypeScript-based filter or sorting systems, completely from the comfort of your own browser.
|
||||||
|
Types used are *builtin* and you can see live type definitions and IntelliSense in the editor.`,
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
name: "Create Sort",
|
||||||
|
icon: ArrowDownUpIcon,
|
||||||
|
value: { customAction: "custom-sort" },
|
||||||
|
color: "#a3a68b",
|
||||||
|
description: "Create a new custom sort system using TypeScript, completely from the comfort of your own browser."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Create Filter",
|
||||||
|
icon: SlidersHorizontal,
|
||||||
|
value: { customAction: "custom-filter" },
|
||||||
|
color: "#a3a68b",
|
||||||
|
description: "Create a new custom filtering system using TypeScript, completely from the comfort of your own browser."
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
98
apps/www/src/lib/discord.ts
Normal file
98
apps/www/src/lib/discord.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* 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 { clerkClient } from "@clerk/nextjs/server";
|
||||||
|
import { MongoClient } from "mongodb";
|
||||||
|
|
||||||
|
const reportObj = (
|
||||||
|
serverName: string,
|
||||||
|
userId: string,
|
||||||
|
userAvatar: string,
|
||||||
|
username: string,
|
||||||
|
reason: string,
|
||||||
|
serverHasCustomizationData: boolean
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
content: "<@&1283912654536314961>",
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
title: `Report on server \`${serverName}\``,
|
||||||
|
description: `There was a report on server \`${serverName}\` by user \`${userId}\`.`,
|
||||||
|
color: 16759796,
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: "Reason",
|
||||||
|
value: reason,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Server has customization data?",
|
||||||
|
value: serverHasCustomizationData ? "Yes" : "No",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
author: {
|
||||||
|
name: username,
|
||||||
|
url: `${process.env.CLERK_USER_PREFIX}/${userId}`,
|
||||||
|
icon_url: userAvatar,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
attachments: [],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function sendDiscordReport(
|
||||||
|
serverName: string,
|
||||||
|
userId: string,
|
||||||
|
reason: string
|
||||||
|
) {
|
||||||
|
const client = await clerkClient();
|
||||||
|
const user = await client.users.getUser(userId);
|
||||||
|
|
||||||
|
const mongo = new MongoClient(process.env.MONGO_DB as string);
|
||||||
|
await mongo.connect();
|
||||||
|
|
||||||
|
const collection = mongo.db("mhsf").collection("customization");
|
||||||
|
const server = await collection.findOne({ server: serverName });
|
||||||
|
|
||||||
|
await fetch(process.env.DISCORD_WEBHOOK as string, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(
|
||||||
|
reportObj(
|
||||||
|
serverName,
|
||||||
|
userId,
|
||||||
|
user.imageUrl,
|
||||||
|
user.username ?? "",
|
||||||
|
reason,
|
||||||
|
server !== null
|
||||||
|
)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
64
apps/www/src/lib/hooks/use-iframe-communication.tsx
Normal file
64
apps/www/src/lib/hooks/use-iframe-communication.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* 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 type { RefObject } from "react";
|
||||||
|
|
||||||
|
export function useIframeCommunication(bottomIframe?: RefObject<HTMLIFrameElement | null>) {
|
||||||
|
return {
|
||||||
|
toIframe: {
|
||||||
|
send: (key: string, object: any) => {
|
||||||
|
if (!bottomIframe) {
|
||||||
|
throw new Error("No hook Iframe")
|
||||||
|
}
|
||||||
|
bottomIframe.current?.contentWindow?.postMessage({__key: key, ...object}, '*');
|
||||||
|
},
|
||||||
|
handle: (key: string, callback: (object: any) => void) => {
|
||||||
|
window.addEventListener('message', (e) => {
|
||||||
|
if (e.data.__key === key) {
|
||||||
|
callback(e.data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fromIframe: {
|
||||||
|
send: (key: string, object: any) => {
|
||||||
|
window.top?.postMessage({__key: key, ...object}, '*')
|
||||||
|
},
|
||||||
|
handle: (key: string, callback: (object: any) => void) => {
|
||||||
|
window.addEventListener('message', (e) => {
|
||||||
|
console.log(e);
|
||||||
|
if (e.data.__key === key) {
|
||||||
|
callback(e.data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -109,6 +109,7 @@ export function useMHSFServer(id: string) {
|
|||||||
|
|
||||||
const response = await fetch(server.actions.report, {
|
const response = await fetch(server.actions.report, {
|
||||||
body: JSON.stringify({ reason }),
|
body: JSON.stringify({ reason }),
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
37
apps/www/src/lib/types/filter.ts
Normal file
37
apps/www/src/lib/types/filter.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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 type { OnlineServer } from "./mh-server";
|
||||||
|
|
||||||
|
export interface Filter {
|
||||||
|
toIdentifier(): string;
|
||||||
|
fromIdentifier(identifier: string): Filter;
|
||||||
|
applyToServer(server: OnlineServer): boolean;
|
||||||
|
}
|
||||||
27
apps/www/src/lib/types/mh-server.d.ts
vendored
Normal file
27
apps/www/src/lib/types/mh-server.d.ts
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
declare type Server = {
|
||||||
|
staticInfo: {
|
||||||
|
_id: string;
|
||||||
|
serverPlan: string;
|
||||||
|
serviceStartDate: number;
|
||||||
|
platform: string;
|
||||||
|
planMaxPlayers: number;
|
||||||
|
planRam: number;
|
||||||
|
alwaysOnline: boolean;
|
||||||
|
rawPlan: string;
|
||||||
|
connectedServers: any[];
|
||||||
|
};
|
||||||
|
maxPlayers: number;
|
||||||
|
name: string;
|
||||||
|
motd: string;
|
||||||
|
icon: string;
|
||||||
|
playerData: {
|
||||||
|
playerCount: number;
|
||||||
|
timeNoPlayers: number;
|
||||||
|
};
|
||||||
|
connectable: boolean;
|
||||||
|
visibility: boolean;
|
||||||
|
allCategories: string[];
|
||||||
|
usingCosmetics: boolean;
|
||||||
|
author?: string;
|
||||||
|
authorRank: string;
|
||||||
|
}
|
||||||
@ -78,6 +78,36 @@ export interface Deletion {
|
|||||||
storage_completed_at: number;
|
storage_completed_at: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const globalType = `
|
||||||
|
export interface OnlineServer {
|
||||||
|
staticInfo: {
|
||||||
|
_id: string;
|
||||||
|
serverPlan: string;
|
||||||
|
serviceStartDate: number;
|
||||||
|
platform: string;
|
||||||
|
planMaxPlayers: number;
|
||||||
|
planRam: number;
|
||||||
|
alwaysOnline: boolean;
|
||||||
|
rawPlan: string;
|
||||||
|
connectedServers: any[];
|
||||||
|
};
|
||||||
|
maxPlayers: number;
|
||||||
|
name: string;
|
||||||
|
motd: string;
|
||||||
|
icon: string;
|
||||||
|
playerData: {
|
||||||
|
playerCount: number;
|
||||||
|
timeNoPlayers: number;
|
||||||
|
};
|
||||||
|
connectable: boolean;
|
||||||
|
visibility: boolean;
|
||||||
|
allCategories: string[];
|
||||||
|
usingCosmetics: boolean;
|
||||||
|
author?: string;
|
||||||
|
authorRank: string;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export interface OnlineServer {
|
export interface OnlineServer {
|
||||||
staticInfo: StaticInfo;
|
staticInfo: StaticInfo;
|
||||||
maxPlayers: number;
|
maxPlayers: number;
|
||||||
|
|||||||
29
apps/www/src/lib/types/mhsf.d.ts
vendored
Normal file
29
apps/www/src/lib/types/mhsf.d.ts
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
declare namespace MHSF {
|
||||||
|
export type Server = {
|
||||||
|
staticInfo: {
|
||||||
|
_id: string;
|
||||||
|
serverPlan: string;
|
||||||
|
serviceStartDate: number;
|
||||||
|
platform: string;
|
||||||
|
planMaxPlayers: number;
|
||||||
|
planRam: number;
|
||||||
|
alwaysOnline: boolean;
|
||||||
|
rawPlan: string;
|
||||||
|
connectedServers: any[];
|
||||||
|
};
|
||||||
|
maxPlayers: number;
|
||||||
|
name: string;
|
||||||
|
motd: string;
|
||||||
|
icon: string;
|
||||||
|
playerData: {
|
||||||
|
playerCount: number;
|
||||||
|
timeNoPlayers: number;
|
||||||
|
};
|
||||||
|
connectable: boolean;
|
||||||
|
visibility: boolean;
|
||||||
|
allCategories: string[];
|
||||||
|
usingCosmetics: boolean;
|
||||||
|
author?: string;
|
||||||
|
authorRank: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
37
apps/www/src/lib/types/sort.ts
Normal file
37
apps/www/src/lib/types/sort.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* 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 type { OnlineServer } from "./mh-server";
|
||||||
|
|
||||||
|
export interface Sort {
|
||||||
|
toIdentifier(): string;
|
||||||
|
fromIdentifier(identifier: string): Sort;
|
||||||
|
sortToServers(serverA: OnlineServer, serverB: OnlineServer): number;
|
||||||
|
}
|
||||||
@ -28,22 +28,63 @@
|
|||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
|
import {
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
clerkClient,
|
||||||
|
clerkMiddleware,
|
||||||
|
createRouteMatcher,
|
||||||
|
} from "@clerk/nextjs/server";
|
||||||
|
import { type NextRequest, NextResponse } from "next/server";
|
||||||
|
import { ServerResponse } from "./lib/types/mh-server";
|
||||||
|
|
||||||
// Thanks for the router matcher API Clerk <3
|
// Thanks for the router matcher API Clerk <3
|
||||||
const isRootRoute = createRouteMatcher(["/"]);
|
const isRootRoute = createRouteMatcher(["/"]);
|
||||||
|
const isOldServerRoute = createRouteMatcher([
|
||||||
|
"/server/:serverName",
|
||||||
|
"/server/:serverName/statistics",
|
||||||
|
]);
|
||||||
|
const apiRoute = createRouteMatcher(["/api/(.*)"]);
|
||||||
|
|
||||||
export default process.env.NEXT_PUBLIC_IS_AUTH === "true"
|
export default process.env.NEXT_PUBLIC_IS_AUTH === "true"
|
||||||
? clerkMiddleware(async (auth, req) => {
|
? clerkMiddleware(async (auth, req) => {
|
||||||
|
const authRes = await auth();
|
||||||
|
const client = await clerkClient();
|
||||||
|
|
||||||
if (isRootRoute(req)) {
|
if (isRootRoute(req)) {
|
||||||
switch ((await auth()).userId === null) {
|
switch (authRes.userId === null) {
|
||||||
case false:
|
case false:
|
||||||
return NextResponse.redirect(new URL("/servers", req.url));
|
return NextResponse.redirect(new URL("/servers", req.url));
|
||||||
case true:
|
case true:
|
||||||
return NextResponse.redirect(new URL("/home", req.url));
|
return NextResponse.redirect(new URL("/home", req.url));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If user is banned, disable all API routes
|
||||||
|
if (authRes.userId !== null) {
|
||||||
|
// User exists
|
||||||
|
const user = await client.users.getUser(authRes.userId);
|
||||||
|
const userBannedMetadata = user.publicMetadata.banned;
|
||||||
|
|
||||||
|
if (userBannedMetadata !== undefined) {
|
||||||
|
// User is banned
|
||||||
|
if (apiRoute(req)) {
|
||||||
|
return NextResponse.json({
|
||||||
|
banned:
|
||||||
|
"You were banned. (and I'm not telling you why) Why are you trying to use the API. Huh? Tell me. Now. You're not funny.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isOldServerRoute(req)) {
|
||||||
|
const minehut = await fetch(
|
||||||
|
`https://api.minehut.com/server/${req.url.split("/server/")[1].split("/")[0]}?byName=true`,
|
||||||
|
);
|
||||||
|
const minehutRes: { server: ServerResponse | null } =
|
||||||
|
await minehut.json();
|
||||||
|
|
||||||
|
if (minehutRes.server !== null)
|
||||||
|
return NextResponse.redirect(
|
||||||
|
new URL(`/server/v2/minehut/${minehutRes.server._id}`, req.url),
|
||||||
|
);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
: (request: NextRequest) => {};
|
: (request: NextRequest) => {};
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,6 @@ import type { OnlineServer } from "@/lib/types/mh-server";
|
|||||||
import { Inngest } from "inngest";
|
import { Inngest } from "inngest";
|
||||||
import { serve } from "inngest/next";
|
import { serve } from "inngest/next";
|
||||||
import { MongoClient } from "mongodb";
|
import { MongoClient } from "mongodb";
|
||||||
import { createReportIssue } from "@/lib/linear";
|
|
||||||
|
|
||||||
// Create a client to send and receive events
|
// Create a client to send and receive events
|
||||||
export const inngest = new Inngest({ id: "mhsf" });
|
export const inngest = new Inngest({ id: "mhsf" });
|
||||||
@ -41,20 +40,6 @@ export const inngest = new Inngest({ id: "mhsf" });
|
|||||||
export default serve({
|
export default serve({
|
||||||
client: inngest,
|
client: inngest,
|
||||||
functions: [
|
functions: [
|
||||||
inngest.createFunction(
|
|
||||||
{ id: "report" },
|
|
||||||
{ event: "report-server" },
|
|
||||||
async ({ event, step }) => {
|
|
||||||
// by the way, I bombed the Discord stuff
|
|
||||||
await createReportIssue(
|
|
||||||
event.data.server,
|
|
||||||
event.data.reason,
|
|
||||||
event.data.userId
|
|
||||||
);
|
|
||||||
|
|
||||||
return { event, body: "Done" };
|
|
||||||
}
|
|
||||||
),
|
|
||||||
inngest.createFunction(
|
inngest.createFunction(
|
||||||
{ id: "short-term-data" },
|
{ id: "short-term-data" },
|
||||||
[{ cron: "*/30 * * * *" }, { event: "test/30-min" }],
|
[{ cron: "*/30 * * * *" }, { event: "test/30-min" }],
|
||||||
|
|||||||
@ -31,8 +31,9 @@
|
|||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { getAuth } from "@clerk/nextjs/server";
|
import { getAuth } from "@clerk/nextjs/server";
|
||||||
import { MongoClient } from "mongodb";
|
import { MongoClient } from "mongodb";
|
||||||
import { inngest } from "@/pages/api/inngest";
|
|
||||||
import { waitUntil } from "@vercel/functions";
|
import { waitUntil } from "@vercel/functions";
|
||||||
|
import { getServerName } from "@/lib/history-util";
|
||||||
|
import { sendDiscordReport } from "@/lib/discord";
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
@ -65,16 +66,11 @@ export default async function handler(
|
|||||||
reason: reason,
|
reason: reason,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Don't wait for this to finish, just continue anyway
|
// Don't wait for this to finish, just continue anyway
|
||||||
inngest.send({
|
waitUntil(
|
||||||
name: "report-server",
|
sendDiscordReport(await getServerName(server as string), userId, reason)
|
||||||
data: {
|
);
|
||||||
_id: entry.insertedId.toString(),
|
|
||||||
server,
|
|
||||||
reason,
|
|
||||||
userId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close the database, but don't close this
|
// Close the database, but don't close this
|
||||||
// serverless instance until it happens
|
// serverless instance until it happens
|
||||||
|
|||||||
1406
apps/www/src/poimandres-monaco.json
Normal file
1406
apps/www/src/poimandres-monaco.json
Normal file
File diff suppressed because it is too large
Load Diff
1393
apps/www/src/theme.json
Normal file
1393
apps/www/src/theme.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -9386,6 +9386,11 @@ mlly@^1.7.1, mlly@^1.7.4:
|
|||||||
pkg-types "^1.3.0"
|
pkg-types "^1.3.0"
|
||||||
ufo "^1.5.4"
|
ufo "^1.5.4"
|
||||||
|
|
||||||
|
monaco-editor@^0.52.2:
|
||||||
|
version "0.52.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.52.2.tgz#53c75a6fcc6802684e99fd1b2700299857002205"
|
||||||
|
integrity sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==
|
||||||
|
|
||||||
mongodb-connection-string-url@^3.0.0:
|
mongodb-connection-string-url@^3.0.0:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz#e223089dfa0a5fa9bf505f8aedcbc67b077b33e7"
|
resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz#e223089dfa0a5fa9bf505f8aedcbc67b077b33e7"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user