mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-08 01:34:58 -05:00
feat: time for testing!
This commit is contained in:
parent
7385705c8d
commit
e31881e890
@ -35,6 +35,7 @@
|
|||||||
"@radix-ui/react-slot": "^1.1.2",
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
"@radix-ui/react-switch": "1.1.0",
|
"@radix-ui/react-switch": "1.1.0",
|
||||||
"@radix-ui/react-tabs": "^1.1.3",
|
"@radix-ui/react-tabs": "^1.1.3",
|
||||||
|
"@types/lodash": "^4.17.16",
|
||||||
"@types/react": "^19.0.8",
|
"@types/react": "^19.0.8",
|
||||||
"@types/react-dom": "^19.0.3",
|
"@types/react-dom": "^19.0.3",
|
||||||
"@unocss/eslint-plugin": "^0.61.5",
|
"@unocss/eslint-plugin": "^0.61.5",
|
||||||
@ -51,7 +52,9 @@
|
|||||||
"inngest": "^3.21.2",
|
"inngest": "^3.21.2",
|
||||||
"input-otp": "^1.2.4",
|
"input-otp": "^1.2.4",
|
||||||
"json-beautify": "^1.1.1",
|
"json-beautify": "^1.1.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"lucide-react": "^0.479.0",
|
"lucide-react": "^0.479.0",
|
||||||
|
"lz-string": "^1.5.0",
|
||||||
"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",
|
||||||
|
|||||||
@ -81,7 +81,7 @@ export default function RootLayout({
|
|||||||
<FontBoundary className="max-w-[800px]">
|
<FontBoundary className="max-w-[800px]">
|
||||||
<IframeProtector>
|
<IframeProtector>
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Toaster richColors position="top-center" />
|
<Toaster richColors position="bottom-center" />
|
||||||
<div className="overflow-x-hidden">{children}</div>
|
<div className="overflow-x-hidden">{children}</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</IframeProtector>
|
</IframeProtector>
|
||||||
|
|||||||
@ -53,18 +53,19 @@ export default function ServerListCategoryFrame({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="max-w-[800px] p-4">
|
<main className="max-w-[800px] p-4">
|
||||||
<h1 className="text-xl font-bold w-full">
|
<h1 className="text-xl font-bold w-full flex items-center gap-2">
|
||||||
<Link href="/servers/embedded/sl-modification-frame">
|
<Link href="/servers/embedded/sl-modification-frame">
|
||||||
<ArrowLeft />
|
<ArrowLeft size={20} />
|
||||||
</Link>
|
</Link>
|
||||||
{categoryObj?.displayTitle}
|
{categoryObj?.displayTitle}
|
||||||
</h1>
|
</h1>
|
||||||
<Markdown className="text-wrap pt-2">{categoryObj?.description}</Markdown>
|
<Markdown className="text-wrap pt-2">{categoryObj?.description}</Markdown>
|
||||||
|
|
||||||
<div className="pt-10 p-4 grid grid-cols-6 gap-2">
|
<Material className="mt-10 p-4 grid grid-cols-6 gap-2">
|
||||||
{categoryObj?.entries.map((m) => (
|
{categoryObj?.entries.map((m) => (
|
||||||
<Material
|
<Material
|
||||||
className="p-2 hover:drop-shadow-card-hover cursor-pointer"
|
className="p-2 hover:drop-shadow-card-hover cursor-pointer"
|
||||||
|
elevation="high"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
router.push(
|
router.push(
|
||||||
`/servers/embedded/sl-modification-frame/category/${category}/modification/${btoa(m.name)}?b=${encodeURIComponent(`/servers/embedded/sl-modification-frame/category/${category}`)}`
|
`/servers/embedded/sl-modification-frame/category/${category}/modification/${btoa(m.name)}?b=${encodeURIComponent(`/servers/embedded/sl-modification-frame/category/${category}`)}`
|
||||||
@ -85,7 +86,7 @@ export default function ServerListCategoryFrame({
|
|||||||
</span>
|
</span>
|
||||||
</Material>
|
</Material>
|
||||||
))}
|
))}
|
||||||
</div>
|
</Material>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,52 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { use } from "react";
|
import { use, useEffect, useRef, useState } from "react";
|
||||||
import { useUser } from "@clerk/nextjs";
|
import { useUser } from "@clerk/nextjs";
|
||||||
import type { ClerkCustomModification } from "@/components/feat/server-list/modification/modification-file-creation-dialog";
|
import type { ClerkCustomModification } from "@/components/feat/server-list/modification/modification-file-creation-dialog";
|
||||||
import { Link } from "@/components/util/link";
|
import { Link } from "@/components/util/link";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { AlertOctagon, ArrowLeft, ExternalLink } from "lucide-react";
|
||||||
import Editor from "@monaco-editor/react";
|
import Editor from "@monaco-editor/react";
|
||||||
import poimandres from "@/theme.json";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import * as ts from "typescript";
|
||||||
|
import useClipboard from "@/lib/useClipboard";
|
||||||
|
import { useTheme } from "@/lib/hooks/use-theme";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
|
import type { languages, Uri } from "monaco-editor";
|
||||||
|
import {
|
||||||
|
Drawer,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerTitle,
|
||||||
|
DrawerTrigger,
|
||||||
|
} from "@/components/ui/drawer";
|
||||||
|
import { Alert } from "@/components/ui/alert";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { compressToEncodedURIComponent } from "lz-string";
|
||||||
|
import { Geist_Mono } from "next/font/google";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { debounce } from "lodash";
|
||||||
|
import { tryCatch } from "@/lib/try-catch";
|
||||||
|
|
||||||
const typeDefs = `
|
const typeDefs = `// Hi :) how'd you get here?
|
||||||
export interface Server {
|
// Here, in return I'll provide you with a random number: ${Math.ceil(Math.random() * 100)}
|
||||||
|
// I just wanted you to know that people love you
|
||||||
|
// and people are there for you :)
|
||||||
|
// Even when things get sad, just think about the bright side (seriously!)
|
||||||
|
|
||||||
|
export namespace Minehut {
|
||||||
|
/**
|
||||||
|
* A Minehut server that is online. You could get this value by using the \`/servers\` endpoint.
|
||||||
|
*/
|
||||||
|
export interface OnlineServer {
|
||||||
staticInfo: {
|
staticInfo: {
|
||||||
_id: string;
|
_id: string;
|
||||||
serverPlan: string;
|
serverPlan: string;
|
||||||
@ -35,9 +72,30 @@ export interface Server {
|
|||||||
usingCosmetics: boolean;
|
usingCosmetics: boolean;
|
||||||
author?: string;
|
author?: string;
|
||||||
authorRank: string;
|
authorRank: string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const transpileTypeScript = (code: string) => {
|
||||||
|
try {
|
||||||
|
const result = ts.transpileModule(typeDefs + code, {
|
||||||
|
compilerOptions: {
|
||||||
|
module: ts.ModuleKind.ESNext,
|
||||||
|
target: ts.ScriptTarget.ESNext,
|
||||||
|
jsx: ts.JsxEmit.ReactJSX,
|
||||||
|
esModuleInterop: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return result.outputText;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("TypeScript transpilation error:", error);
|
||||||
|
toast.error(`TypeScript error: ${error}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const geistMono = Geist_Mono({ subsets: ["latin"] });
|
||||||
|
|
||||||
export default function CustomFilePage({
|
export default function CustomFilePage({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
@ -45,35 +103,242 @@ export default function CustomFilePage({
|
|||||||
}) {
|
}) {
|
||||||
const { filename } = use(params);
|
const { filename } = use(params);
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
|
const monacoRef =
|
||||||
|
useRef<typeof import("monaco-editor/esm/vs/editor/editor.api")>(null);
|
||||||
|
const { resolvedTheme } = useTheme();
|
||||||
|
const [successfullyLinted, setSuccessfullyLinted] = useState(false);
|
||||||
|
const [syntaxErrors, setSyntaxErrors] = useState<
|
||||||
|
languages.typescript.Diagnostic[] | null
|
||||||
|
>(null);
|
||||||
const file = (
|
const file = (
|
||||||
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ?? []
|
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ?? []
|
||||||
).find((c) => c.name === filename);
|
).findIndex((c) => c.name === filename);
|
||||||
|
|
||||||
if (!file) {
|
if (file === -1) {
|
||||||
return <>Bruh.</>;
|
return <>Bruh.</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileContents = file.contents;
|
const validateCode = (code: string) => {
|
||||||
|
if (!monacoRef.current) return;
|
||||||
|
|
||||||
|
monacoRef.current.languages.typescript
|
||||||
|
.getTypeScriptWorker()
|
||||||
|
.then((worker) => {
|
||||||
|
worker(
|
||||||
|
monacoRef.current?.Uri.parse(`file:///${filename}.ts`) as Uri
|
||||||
|
).then((client) => {
|
||||||
|
client
|
||||||
|
.getSemanticDiagnostics(
|
||||||
|
(
|
||||||
|
monacoRef.current?.Uri.parse(`file:///${filename}.ts`) as Uri
|
||||||
|
).toString()
|
||||||
|
)
|
||||||
|
.then((diags) => {
|
||||||
|
setSyntaxErrors(diags);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileContents = ((user?.unsafeMetadata
|
||||||
|
.customFiles as Array<ClerkCustomModification>) ?? [])[file].contents;
|
||||||
|
const [value, setValue] = useState(fileContents);
|
||||||
|
const clipboard = useClipboard();
|
||||||
|
validateCode(value);
|
||||||
|
|
||||||
|
const saveFile = async () => {
|
||||||
|
const metadata =
|
||||||
|
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ??
|
||||||
|
[];
|
||||||
|
const index = (
|
||||||
|
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ?? []
|
||||||
|
).findIndex((c) => c.name === filename);
|
||||||
|
|
||||||
|
metadata[index].contents = value;
|
||||||
|
|
||||||
|
await user?.update({
|
||||||
|
unsafeMetadata: {
|
||||||
|
customFiles: metadata,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const lintFile = async () => {
|
||||||
|
toast.info("Transpiling TypeScript...");
|
||||||
|
const { error, data: transpiledCode } = await tryCatch(
|
||||||
|
(async () => transpileTypeScript(value))()
|
||||||
|
);
|
||||||
|
if (error) {
|
||||||
|
toast.error("Failed to transpile TypeScript! Error: " + error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const startTime = Date.now();
|
||||||
|
if (transpiledCode === null) {
|
||||||
|
toast.error("Cannot continue.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("[MHSF Filters] Transpiled TypeScript:", transpiledCode ?? "");
|
||||||
|
toast.info("Generating function...");
|
||||||
|
const functionBody = transpiledCode.match(
|
||||||
|
/function\s+filter\s*\([^)]*\)\s*\{([\s\S]*)\}/
|
||||||
|
)?.[1];
|
||||||
|
const { error: filterErr, data: filterFunc } = await tryCatch(
|
||||||
|
(async () => new Function("data", functionBody as string))()
|
||||||
|
);
|
||||||
|
if (filterErr) {
|
||||||
|
toast.error(`Failed to generate function! Error: ${filterErr.message}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof filterFunc === "function") {
|
||||||
|
toast.success("Linted in " + (Date.now() - startTime) + "ms");
|
||||||
|
setSuccessfullyLinted(true);
|
||||||
|
} else {
|
||||||
|
toast.error("Code doesn't have a 'filter' function. Cannot be tested.");
|
||||||
|
toast.error(typeof filterFunc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const debouncedSave = debounce(async () => {
|
||||||
|
const { error } = await tryCatch(saveFile());
|
||||||
|
if (error)
|
||||||
|
toast.error(
|
||||||
|
"Whoa! We encountered an error while auto-saving. Please copy your code locally to ensure you'll keep your code changes."
|
||||||
|
);
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSuccessfullyLinted(false);
|
||||||
|
validateCode(value);
|
||||||
|
debouncedSave();
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="max-w-[800px] p-4">
|
<main className="max-w-[800px] p-4">
|
||||||
<strong className="font-bold w-full">
|
<div className="w-full justify-between flex items-center gap-2 my-2">
|
||||||
|
<strong className="flex items-center gap-1">
|
||||||
<Link href="/servers/embedded/sl-modification-frame/files">
|
<Link href="/servers/embedded/sl-modification-frame/files">
|
||||||
<ArrowLeft />
|
<ArrowLeft size={20} />
|
||||||
</Link>
|
</Link>
|
||||||
{filename}.ts
|
{filename}.ts
|
||||||
</strong>
|
</strong>
|
||||||
<div className="h-[400px]">
|
<span className="flex items-center gap-2">
|
||||||
|
{syntaxErrors !== null && syntaxErrors.length !== 0 && (
|
||||||
|
<Drawer direction="right">
|
||||||
|
<DrawerTrigger>
|
||||||
|
<Button
|
||||||
|
variant="danger-subtle"
|
||||||
|
size="square-md"
|
||||||
|
className="flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<AlertOctagon />
|
||||||
|
</Button>
|
||||||
|
</DrawerTrigger>
|
||||||
|
<DrawerContent className="p-4 min-w-[400px] overflow-x-hidden max-h-screen overflow-y-auto">
|
||||||
|
<DrawerTitle>Type Errors</DrawerTitle>
|
||||||
|
<div className="p-2">
|
||||||
|
{syntaxErrors.map((c, i) => (
|
||||||
|
<Alert
|
||||||
|
variant={
|
||||||
|
c.category === 1
|
||||||
|
? "error"
|
||||||
|
: c.category === 0
|
||||||
|
? "warning"
|
||||||
|
: "info"
|
||||||
|
}
|
||||||
|
key={i}
|
||||||
|
className="gap-1 my-2"
|
||||||
|
>
|
||||||
|
{c.messageText.toString()}{" "}
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger>
|
||||||
|
<small className="flex items-center gap-1 cursor-pointer">
|
||||||
|
(TS{typeof c !== "string" && c.code})
|
||||||
|
<ExternalLink size={16} />
|
||||||
|
</small>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<Link
|
||||||
|
noExtraIcons
|
||||||
|
target="_blank"
|
||||||
|
href={`https://typescript.tv/errors/#ts${c.code}`}
|
||||||
|
>
|
||||||
|
<DropdownMenuItem>typescript.tv</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
noExtraIcons
|
||||||
|
target="_blank"
|
||||||
|
href={`https://ts-error-translator.vercel.app/?error=${compressToEncodedURIComponent(c.messageText.toString())}`}
|
||||||
|
>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
ts-error-translator
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Alert>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
)}
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Button
|
||||||
|
onClick={lintFile}
|
||||||
|
disabled={syntaxErrors === null || syntaxErrors.length !== 0}
|
||||||
|
variant={successfullyLinted ? "success-subtle" : "secondary"}
|
||||||
|
>
|
||||||
|
Lint
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
{syntaxErrors !== null && syntaxErrors.length !== 0
|
||||||
|
? "You must have no type errors in the editor to lint, you have " +
|
||||||
|
syntaxErrors.length +
|
||||||
|
" error(s)."
|
||||||
|
: "Check for possible runtime errors."}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Button
|
||||||
|
disabled={!successfullyLinted}
|
||||||
|
onClick={() => {
|
||||||
|
const array =
|
||||||
|
(user?.unsafeMetadata
|
||||||
|
.customFiles as Array<ClerkCustomModification>) ?? [];
|
||||||
|
array[file].testId = guidGenerator();
|
||||||
|
user?.update({
|
||||||
|
unsafeMetadata: { customFiles: array },
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Test
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
{successfullyLinted
|
||||||
|
? "Open a full server-list instance with your filter activated in test mode."
|
||||||
|
: "You must lint before testing."}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<Editor
|
<Editor
|
||||||
height="100%"
|
className={cn("h-[calc(100vh-100px)]")}
|
||||||
defaultLanguage="typescript"
|
defaultLanguage="typescript"
|
||||||
defaultValue={fileContents}
|
value={value}
|
||||||
theme="vs-dark"
|
theme={resolvedTheme === "dark" ? "vs-dark" : "vs"}
|
||||||
|
onChange={(newValue) => {
|
||||||
|
setValue(newValue || "");
|
||||||
|
}}
|
||||||
onMount={(editor, monaco) => {
|
onMount={(editor, monaco) => {
|
||||||
|
monacoRef.current = monaco;
|
||||||
// Ensure TypeScript is properly configured
|
// Ensure TypeScript is properly configured
|
||||||
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
||||||
target: monaco.languages.typescript.ScriptTarget.Latest,
|
target: monaco.languages.typescript.ScriptTarget.Latest,
|
||||||
allowNonTsExtensions: true, // This is important!
|
allowNonTsExtensions: true,
|
||||||
moduleResolution:
|
moduleResolution:
|
||||||
monaco.languages.typescript.ModuleResolutionKind.NodeJs,
|
monaco.languages.typescript.ModuleResolutionKind.NodeJs,
|
||||||
module: monaco.languages.typescript.ModuleKind.CommonJS,
|
module: monaco.languages.typescript.ModuleKind.CommonJS,
|
||||||
@ -91,15 +356,32 @@ export default function CustomFilePage({
|
|||||||
// Add typedefs as a library
|
// Add typedefs as a library
|
||||||
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
monaco.languages.typescript.typescriptDefaults.addExtraLib(
|
||||||
typeDefs,
|
typeDefs,
|
||||||
libUri,
|
libUri
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Add actions
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: "manually-save-file",
|
||||||
|
label: "MHSF: Manually Save File",
|
||||||
|
run: () => {
|
||||||
|
saveFile();
|
||||||
|
toast.success("Manually saved file!");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "lint-file",
|
||||||
|
label: "MHSF: Lint File",
|
||||||
|
run: lintFile
|
||||||
|
},
|
||||||
|
].forEach((e) => editor.addAction(e));
|
||||||
|
|
||||||
// Create a model for the libUri file
|
// Create a model for the libUri file
|
||||||
if (!monaco.editor.getModel(monaco.Uri.parse(libUri))) {
|
if (!monaco.editor.getModel(monaco.Uri.parse(libUri))) {
|
||||||
monaco.editor.createModel(
|
monaco.editor.createModel(
|
||||||
typeDefs,
|
typeDefs,
|
||||||
"typescript",
|
"typescript",
|
||||||
monaco.Uri.parse(libUri),
|
monaco.Uri.parse(libUri)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,11 +393,7 @@ export default function CustomFilePage({
|
|||||||
|
|
||||||
const currentUri = monaco.Uri.parse(`file:///${filename}.ts`);
|
const currentUri = monaco.Uri.parse(`file:///${filename}.ts`);
|
||||||
if (!monaco.editor.getModel(currentUri)) {
|
if (!monaco.editor.getModel(currentUri)) {
|
||||||
monaco.editor.createModel(
|
monaco.editor.createModel(fileContents, "typescript", currentUri);
|
||||||
fileContents,
|
|
||||||
"typescript",
|
|
||||||
currentUri,
|
|
||||||
);
|
|
||||||
editor.setModel(monaco.editor.getModel(currentUri));
|
editor.setModel(monaco.editor.getModel(currentUri));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -134,6 +412,7 @@ export default function CustomFilePage({
|
|||||||
acceptSuggestionOnEnter: "on",
|
acceptSuggestionOnEnter: "on",
|
||||||
tabCompletion: "on",
|
tabCompletion: "on",
|
||||||
wordBasedSuggestions: "currentDocument",
|
wordBasedSuggestions: "currentDocument",
|
||||||
|
cursorSmoothCaretAnimation: "on",
|
||||||
parameterHints: {
|
parameterHints: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
@ -148,3 +427,23 @@ export default function CustomFilePage({
|
|||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function guidGenerator() {
|
||||||
|
const S4 = () => {
|
||||||
|
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
S4() +
|
||||||
|
S4() +
|
||||||
|
"-" +
|
||||||
|
S4() +
|
||||||
|
"-" +
|
||||||
|
S4() +
|
||||||
|
"-" +
|
||||||
|
S4() +
|
||||||
|
"-" +
|
||||||
|
S4() +
|
||||||
|
S4() +
|
||||||
|
S4()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -31,9 +31,27 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ClerkCustomModification } from "@/components/feat/server-list/modification/modification-file-creation-dialog";
|
import { ClerkCustomModification } from "@/components/feat/server-list/modification/modification-file-creation-dialog";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Material } from "@/components/ui/material";
|
||||||
|
import { Placeholder } from "@/components/ui/placeholder";
|
||||||
import { Link } from "@/components/util/link";
|
import { Link } from "@/components/util/link";
|
||||||
import { useUser } from "@clerk/nextjs";
|
import { useUser } from "@clerk/nextjs";
|
||||||
import { File, FileCode } from "lucide-react";
|
import {
|
||||||
|
ArrowLeft,
|
||||||
|
Braces,
|
||||||
|
EllipsisVertical,
|
||||||
|
File,
|
||||||
|
FileCode,
|
||||||
|
Pencil,
|
||||||
|
Trash,
|
||||||
|
} from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export default function ServerListModificationFrame() {
|
export default function ServerListModificationFrame() {
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
@ -41,14 +59,72 @@ export default function ServerListModificationFrame() {
|
|||||||
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ?? [];
|
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ?? [];
|
||||||
return (
|
return (
|
||||||
<main className="max-w-[800px] p-4">
|
<main className="max-w-[800px] p-4">
|
||||||
<h1 className="text-xl font-bold w-full">Files</h1>
|
<h1 className="text-xl font-bold w-full flex items-center gap-2">
|
||||||
<div className="grid gap-1">
|
<Link href="/servers/embedded/sl-modification-frame">
|
||||||
{files.map((c) => (
|
<ArrowLeft size={16} />
|
||||||
<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}>
|
</Link>
|
||||||
<FileCode size={16}/>{c.name}.ts
|
Files
|
||||||
|
</h1>
|
||||||
|
<Material className="grid gap-1 mt-4">
|
||||||
|
{files.length === 0 && (
|
||||||
|
<Placeholder
|
||||||
|
icon={<Braces />}
|
||||||
|
title="We couldn't find any files"
|
||||||
|
description="Try creating a filter!"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{files.map((c, i) => (
|
||||||
|
<Link
|
||||||
|
href={`/servers/embedded/sl-modification-frame/file/${c.name}`}
|
||||||
|
className="w-full py-1 px-2 rounded-xl flex items-center gap-1 justify-between hover:bg-slate-100"
|
||||||
|
key={c.name}
|
||||||
|
>
|
||||||
|
<span className="flex items-center gap-1">
|
||||||
|
<FileCode size={16} />
|
||||||
|
{c.name}.ts
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger>
|
||||||
|
<Button
|
||||||
|
variant="tertiary"
|
||||||
|
className="flex items-center justify-center hover:bg-slate-200"
|
||||||
|
size="square-sm"
|
||||||
|
>
|
||||||
|
<EllipsisVertical
|
||||||
|
size={16}
|
||||||
|
className="text-muted-foreground"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const startTime = Date.now();
|
||||||
|
files.splice(i, 1);
|
||||||
|
await user?.update({
|
||||||
|
unsafeMetadata: {
|
||||||
|
customFiles: files,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
toast.success(
|
||||||
|
"Deleted file in " + (Date.now() - startTime) + "ms"
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash size={16} /> Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem className="flex items-center gap-2">
|
||||||
|
<Pencil size={16} /> Rename
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</Material>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,8 +56,7 @@ export default function ServerListModificationFrame() {
|
|||||||
viewing experience. We frequently add new filters in accordance to new
|
viewing experience. We frequently add new filters in accordance to new
|
||||||
features, as well.
|
features, as well.
|
||||||
</span>
|
</span>
|
||||||
<Separator className="mt-4" />
|
<Material className="mt-10 p-4">
|
||||||
<div className="pt-10 p-4">
|
|
||||||
{serverModDB.map((c) => (
|
{serverModDB.map((c) => (
|
||||||
<span key={c.displayTitle}>
|
<span key={c.displayTitle}>
|
||||||
<h2 className="text-lg font-bold pb-3 flex justify-between">
|
<h2 className="text-lg font-bold pb-3 flex justify-between">
|
||||||
@ -73,6 +72,7 @@ export default function ServerListModificationFrame() {
|
|||||||
<div className="grid grid-cols-6 gap-2">
|
<div className="grid grid-cols-6 gap-2">
|
||||||
{c.entries.map((m) => (
|
{c.entries.map((m) => (
|
||||||
<Material
|
<Material
|
||||||
|
elevation="high"
|
||||||
className="p-2 hover:drop-shadow-card-hover cursor-pointer"
|
className="p-2 hover:drop-shadow-card-hover cursor-pointer"
|
||||||
key={m.name}
|
key={m.name}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
@ -95,7 +95,7 @@ export default function ServerListModificationFrame() {
|
|||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</Material>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,16 +42,16 @@ import { useUser } from "@clerk/nextjs";
|
|||||||
import { useState, type ReactNode } from "react";
|
import { useState, type ReactNode } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
const sortTemplate = `import type { Server } from "mhsf";
|
const sortTemplate = `import type { Minehut } from "mhsf";
|
||||||
|
|
||||||
export function sort(serverA: Server, serverB: Server): number {
|
export function sort(serverA: Minehut.OnlineServer, serverB: Minehut.OnlineServer): number {
|
||||||
// Your code here
|
// Your code here
|
||||||
// Use logic like \`Array.sort\` or <V>(a: V, b: V) => number
|
// Use logic like \`Array.sort\` or <V>(a: V, b: V) => number
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const filterTemplate = `import type { Server } from "mhsf";
|
const filterTemplate = `import type { Minehut } from "mhsf";
|
||||||
|
|
||||||
export function filter(server: Server): boolean {
|
export function filter(server: Minehut.OnlineServer): boolean {
|
||||||
// Your code here
|
// Your code here
|
||||||
// Returning true indicates the server will stay, while returning false will remove the server from the queue.
|
// Returning true indicates the server will stay, while returning false will remove the server from the queue.
|
||||||
}`;
|
}`;
|
||||||
@ -60,6 +60,7 @@ export type ClerkCustomModification = {
|
|||||||
name: string; // Add .ts to the end
|
name: string; // Add .ts to the end
|
||||||
active: boolean;
|
active: boolean;
|
||||||
contents: string;
|
contents: string;
|
||||||
|
testId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ModificationFileCreationDialog({
|
export function ModificationFileCreationDialog({
|
||||||
|
|||||||
55
apps/www/src/lib/try-catch.ts
Normal file
55
apps/www/src/lib/try-catch.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
// Thanks theo <3
|
||||||
|
|
||||||
|
// Types for the result object with discriminated union
|
||||||
|
type Success<T> = {
|
||||||
|
data: T;
|
||||||
|
error: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Failure<E> = {
|
||||||
|
data: null;
|
||||||
|
error: E;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Result<T, E = Error> = Success<T> | Failure<E>;
|
||||||
|
|
||||||
|
// Main wrapper function
|
||||||
|
export async function tryCatch<T, E = Error>(
|
||||||
|
promise: Promise<T>,
|
||||||
|
): Promise<Result<T, E>> {
|
||||||
|
try {
|
||||||
|
const data = await promise;
|
||||||
|
return { data, error: null };
|
||||||
|
} catch (error) {
|
||||||
|
return { data: null, error: error as E };
|
||||||
|
}
|
||||||
|
}
|
||||||
27
apps/www/src/lib/types/mh-server.d.ts
vendored
27
apps/www/src/lib/types/mh-server.d.ts
vendored
@ -1,27 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@ -34,7 +34,7 @@ import {
|
|||||||
createRouteMatcher,
|
createRouteMatcher,
|
||||||
} from "@clerk/nextjs/server";
|
} from "@clerk/nextjs/server";
|
||||||
import { type NextRequest, NextResponse } from "next/server";
|
import { type NextRequest, NextResponse } from "next/server";
|
||||||
import { ServerResponse } from "./lib/types/mh-server";
|
import type { 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(["/"]);
|
||||||
|
|||||||
10
yarn.lock
10
yarn.lock
@ -3399,6 +3399,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.16.7.tgz#03ab680ab4fa4fbc6cb46ecf987ecad5d8019868"
|
resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.16.7.tgz#03ab680ab4fa4fbc6cb46ecf987ecad5d8019868"
|
||||||
integrity sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==
|
integrity sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==
|
||||||
|
|
||||||
|
"@types/lodash@^4.17.16":
|
||||||
|
version "4.17.16"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.16.tgz#94ae78fab4a38d73086e962d0b65c30d816bfb0a"
|
||||||
|
integrity sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==
|
||||||
|
|
||||||
"@types/luxon@~3.4.0":
|
"@types/luxon@~3.4.0":
|
||||||
version "3.4.2"
|
version "3.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.4.2.tgz#e4fc7214a420173cea47739c33cdf10874694db7"
|
resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.4.2.tgz#e4fc7214a420173cea47739c33cdf10874694db7"
|
||||||
@ -8123,6 +8128,11 @@ luxon@~3.5.0:
|
|||||||
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.5.0.tgz#6b6f65c5cd1d61d1fd19dbf07ee87a50bf4b8e20"
|
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.5.0.tgz#6b6f65c5cd1d61d1fd19dbf07ee87a50bf4b8e20"
|
||||||
integrity sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==
|
integrity sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==
|
||||||
|
|
||||||
|
lz-string@^1.5.0:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
|
||||||
|
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
|
||||||
|
|
||||||
magic-bytes.js@^1.10.0:
|
magic-bytes.js@^1.10.0:
|
||||||
version "1.10.0"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz#c41cf4bc2f802992b05e64962411c9dd44fdef92"
|
resolved "https://registry.yarnpkg.com/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz#c41cf4bc2f802992b05e64962411c9dd44fdef92"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user