mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-07 22:14:59 -05:00
feat: clean up code
This commit is contained in:
parent
ed2d6f0ac1
commit
db992412d0
@ -38,6 +38,10 @@ const nextConfig = {
|
|||||||
protocol: "https",
|
protocol: "https",
|
||||||
hostname: "img.clerk.com",
|
hostname: "img.clerk.com",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
protocol: "https",
|
||||||
|
hostname: "avatars.githubusercontent.com"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
async redirects() {
|
async redirects() {
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.24.7",
|
"@babel/parser": "^7.24.7",
|
||||||
"@biomejs/biome": "^1.8.3",
|
"@biomejs/biome": "^1.9.4",
|
||||||
"@clerk/elements": "^0.22.2",
|
"@clerk/elements": "^0.22.2",
|
||||||
"@clerk/nextjs": "^6.9.2",
|
"@clerk/nextjs": "^6.9.2",
|
||||||
"@emotion/is-prop-valid": "^1.3.0",
|
"@emotion/is-prop-valid": "^1.3.0",
|
||||||
|
|||||||
@ -30,8 +30,7 @@
|
|||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ModificationAction } from "@/components/feat/server-list/modification/modification-action";
|
import type { ClerkCustomActivatedModification } from "@/components/feat/server-list/modification/modification-file-creation-dialog";
|
||||||
import { ClerkCustomActivatedModification } from "@/components/feat/server-list/modification/modification-file-creation-dialog";
|
|
||||||
import {
|
import {
|
||||||
Setting,
|
Setting,
|
||||||
SettingContent,
|
SettingContent,
|
||||||
@ -40,15 +39,30 @@ import {
|
|||||||
SettingTitle,
|
SettingTitle,
|
||||||
} from "@/components/feat/settings/setting";
|
} from "@/components/feat/settings/setting";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Material } from "@/components/ui/material";
|
import { Material } from "@/components/ui/material";
|
||||||
|
import { Placeholder } from "@/components/ui/placeholder";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Link } from "@/components/util/link";
|
import { Link } from "@/components/util/link";
|
||||||
import { serverModDB } from "@/config/sl-mod-db";
|
import { serverModDB } from "@/config/sl-mod-db";
|
||||||
import { useUser } from "@clerk/nextjs";
|
import { useUser } from "@clerk/nextjs";
|
||||||
import { ArrowLeft, Filter, SortAsc } from "lucide-react";
|
import {
|
||||||
|
ArrowLeft,
|
||||||
|
EllipsisVertical,
|
||||||
|
FileQuestion,
|
||||||
|
Filter,
|
||||||
|
SortAsc,
|
||||||
|
Trash,
|
||||||
|
} from "lucide-react";
|
||||||
import { useQueryState } from "nuqs";
|
import { useQueryState } from "nuqs";
|
||||||
import { use } from "react";
|
import { use } from "react";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export default function ModificationPage({
|
export default function ModificationPage({
|
||||||
params,
|
params,
|
||||||
@ -61,13 +75,28 @@ export default function ModificationPage({
|
|||||||
defaultValue: "/servers/embedded/sl-modification-frame",
|
defaultValue: "/servers/embedded/sl-modification-frame",
|
||||||
});
|
});
|
||||||
console.log(mod);
|
console.log(mod);
|
||||||
const modObj = (
|
const modIndex = (
|
||||||
(user?.unsafeMetadata
|
(user?.unsafeMetadata
|
||||||
.activatedModifications as ClerkCustomActivatedModification[]) ?? []
|
.activatedModifications as ClerkCustomActivatedModification[]) ?? []
|
||||||
).find((c) => c.friendlyName === atob(decodeURIComponent(mod)));
|
).findIndex((c) => c.friendlyName === atob(decodeURIComponent(mod)));
|
||||||
|
|
||||||
if (modObj === undefined)
|
if (modIndex === -1)
|
||||||
return <>We couldn't find the modification you were looking for.</>;
|
return (
|
||||||
|
<div className="w-full h-full flex justify-center items-center absolute top-[0%]">
|
||||||
|
<Link href={backRoute}>
|
||||||
|
<ArrowLeft className="absolute left-[10px] top-[10px]" />
|
||||||
|
</Link>
|
||||||
|
<Placeholder
|
||||||
|
title="We couldn't find the file you were looking for."
|
||||||
|
icon={<FileQuestion />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const modObj = ((user?.unsafeMetadata
|
||||||
|
.activatedModifications as ClerkCustomActivatedModification[]) ?? [])[
|
||||||
|
modIndex
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="max-w-[800px] p-4">
|
<main className="max-w-[800px] p-4">
|
||||||
@ -86,13 +115,48 @@ export default function ModificationPage({
|
|||||||
This is a custom modification. Enable it! (or not) It's your own! (are
|
This is a custom modification. Enable it! (or not) It's your own! (are
|
||||||
you proud?)
|
you proud?)
|
||||||
</Markdown>
|
</Markdown>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
<Button className="mt-2">
|
<Button className="mt-2">
|
||||||
{modObj?.active ? "Disable" : "Enable"}
|
{modObj?.active ? "Disable" : "Enable"}
|
||||||
</Button>
|
</Button>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
className="flex items-center"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<EllipsisVertical size={16} />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent>
|
||||||
|
<DropdownMenuItem
|
||||||
|
className="gap-2"
|
||||||
|
onClick={async () => {
|
||||||
|
const time = Date.now();
|
||||||
|
const array =
|
||||||
|
(user?.unsafeMetadata
|
||||||
|
.activatedModifications as ClerkCustomActivatedModification[]) ??
|
||||||
|
[];
|
||||||
|
array.splice(modIndex, 1);
|
||||||
|
await user?.update({
|
||||||
|
unsafeMetadata: {
|
||||||
|
...user.unsafeMetadata,
|
||||||
|
activatedModifications: array,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
toast.success(`Deleted in ${Date.now() - time}ms`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash size={16} /> Delete
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Separator className="mt-3" />
|
<Separator className="mt-3" />
|
||||||
|
|
||||||
<Material className="mt-6">
|
<Material className="mt-6 grid gap-3">
|
||||||
<Setting>
|
<Setting>
|
||||||
<SettingContent className="flex items-center">
|
<SettingContent className="flex items-center">
|
||||||
<SettingMeta>
|
<SettingMeta>
|
||||||
@ -115,6 +179,23 @@ export default function ModificationPage({
|
|||||||
</div>
|
</div>
|
||||||
</SettingContent>
|
</SettingContent>
|
||||||
</Setting>
|
</Setting>
|
||||||
|
<Setting>
|
||||||
|
<SettingContent className="flex items-center">
|
||||||
|
<SettingMeta>
|
||||||
|
<SettingTitle>File name</SettingTitle>
|
||||||
|
</SettingMeta>
|
||||||
|
<Link
|
||||||
|
href={
|
||||||
|
`"/servers/embedded/sl-modification-frame/file/${modObj.originalFileName}`
|
||||||
|
}
|
||||||
|
className="text-blue-600"
|
||||||
|
>
|
||||||
|
<code className="flex items-center">
|
||||||
|
{modObj.originalFileName}.ts
|
||||||
|
</code>
|
||||||
|
</Link>
|
||||||
|
</SettingContent>
|
||||||
|
</Setting>
|
||||||
</Material>
|
</Material>
|
||||||
</span>
|
</span>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -28,9 +28,8 @@
|
|||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { ModificationAction } from "@/components/feat/server-list/modification/modification-action";
|
import { ModificationAction } from "@/components/feat/server-list/modification/modification-action";
|
||||||
|
import { ModificationCustomModificationRow } from "@/components/feat/server-list/modification/modification-custom-modification-row";
|
||||||
import { ClerkCustomActivatedModification } from "@/components/feat/server-list/modification/modification-file-creation-dialog";
|
import { ClerkCustomActivatedModification } from "@/components/feat/server-list/modification/modification-file-creation-dialog";
|
||||||
import { Material } from "@/components/ui/material";
|
import { Material } from "@/components/ui/material";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
@ -43,17 +42,15 @@ import { ArrowLeft, Binary } from "lucide-react";
|
|||||||
import { use } from "react";
|
import { use } from "react";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
|
|
||||||
export default function ServerListCategoryFrame({
|
export default async function ServerListCategoryFrame({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
params: Promise<{ category: string }>;
|
params: Promise<{ category: string }>;
|
||||||
}) {
|
}) {
|
||||||
const { user } = useUser();
|
const { category } = await params;
|
||||||
const { category } = use(params);
|
|
||||||
const categoryObj = serverModDB.find(
|
const categoryObj = serverModDB.find(
|
||||||
(c) => c.displayTitle === atob(category),
|
(c) => c.displayTitle === atob(decodeURIComponent(category)),
|
||||||
);
|
);
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="max-w-[800px] p-4">
|
<main className="max-w-[800px] p-4">
|
||||||
@ -67,15 +64,13 @@ export default function ServerListCategoryFrame({
|
|||||||
|
|
||||||
<Material className="mt-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) => (
|
||||||
|
<Link
|
||||||
|
key={m.name}
|
||||||
|
href={`/servers/embedded/sl-modification-frame/category/${category}/modification/${btoa(m.name)}?b=${encodeURIComponent(`/servers/embedded/sl-modification-frame/category/${category}`)}`}
|
||||||
|
>
|
||||||
<Material
|
<Material
|
||||||
className="p-2 hover:drop-shadow-card-hover cursor-pointer"
|
className="p-2 hover:drop-shadow-card-hover cursor-pointer"
|
||||||
elevation="high"
|
elevation="high"
|
||||||
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
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@ -89,35 +84,12 @@ export default function ServerListCategoryFrame({
|
|||||||
{m.name}
|
{m.name}
|
||||||
</span>
|
</span>
|
||||||
</Material>
|
</Material>
|
||||||
|
</Link>
|
||||||
))}
|
))}
|
||||||
<SignedIn>
|
<SignedIn>
|
||||||
{categoryObj?.__custom &&
|
{categoryObj?.__custom && (
|
||||||
(
|
<ModificationCustomModificationRow category={category} />
|
||||||
(user?.unsafeMetadata
|
)}
|
||||||
.activatedModifications as ClerkCustomActivatedModification[]) ??
|
|
||||||
[]
|
|
||||||
).map((m) => (
|
|
||||||
<Material
|
|
||||||
elevation="high"
|
|
||||||
className="p-2 hover:drop-shadow-card-hover cursor-pointer"
|
|
||||||
key={m.friendlyName}
|
|
||||||
onClick={() =>
|
|
||||||
router.push(
|
|
||||||
`/servers/embedded/sl-modification-frame/category/${category}/modification/_custom/${btoa(m.friendlyName)}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="w-full h-[40px] mb-2 rounded-lg items-center text-center justify-center"
|
|
||||||
style={{ backgroundColor: m.color }}
|
|
||||||
>
|
|
||||||
<Binary 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.friendlyName}
|
|
||||||
</span>
|
|
||||||
</Material>
|
|
||||||
))}
|
|
||||||
</SignedIn>
|
</SignedIn>
|
||||||
</Material>
|
</Material>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -2,14 +2,10 @@
|
|||||||
|
|
||||||
import { use, useEffect, useRef, useState } from "react";
|
import { use, useEffect, useRef, useState } from "react";
|
||||||
import { useUser } from "@clerk/nextjs";
|
import { useUser } from "@clerk/nextjs";
|
||||||
import type {
|
import type { ClerkCustomModification } from "@/components/feat/server-list/modification/modification-file-creation-dialog";
|
||||||
ClerkCustomActivatedModification,
|
|
||||||
ClerkCustomModification,
|
|
||||||
} from "@/components/feat/server-list/modification/modification-file-creation-dialog";
|
|
||||||
import { Link } from "@/components/util/link";
|
import { Link } from "@/components/util/link";
|
||||||
import { AlertOctagon, ArrowLeft, Check, ExternalLink } from "lucide-react";
|
import { ArrowLeft, FileQuestion } from "lucide-react";
|
||||||
import Editor from "@monaco-editor/react";
|
import Editor from "@monaco-editor/react";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import * as ts from "typescript";
|
import * as ts from "typescript";
|
||||||
import useClipboard from "@/lib/useClipboard";
|
import useClipboard from "@/lib/useClipboard";
|
||||||
@ -19,50 +15,18 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import type { languages, Uri } from "monaco-editor";
|
import type { languages } 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 { cn } from "@/lib/utils";
|
||||||
import { debounce } from "lodash";
|
import { debounce } from "lodash";
|
||||||
import { tryCatch } from "@/lib/try-catch";
|
import { tryCatch } from "@/lib/try-catch";
|
||||||
import {
|
import { Placeholder } from "@/components/ui/placeholder";
|
||||||
Popover,
|
import { CustomErrors } from "@/components/feat/server-list/modification/custom-files/custom-errors";
|
||||||
PopoverContent,
|
import { CustomLint } from "@/components/feat/server-list/modification/custom-files/custom-lint";
|
||||||
PopoverTrigger,
|
import { CustomTest } from "@/components/feat/server-list/modification/custom-files/custom-test";
|
||||||
} from "@/components/ui/popover";
|
|
||||||
import { Material } from "@/components/ui/material";
|
export type MonacoRefType = typeof import(
|
||||||
import {
|
"monaco-editor/esm/vs/editor/editor.api"
|
||||||
Setting,
|
);
|
||||||
SettingContent,
|
|
||||||
SettingDescription,
|
|
||||||
SettingMeta,
|
|
||||||
SettingTitle,
|
|
||||||
} from "@/components/feat/settings/setting";
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectGroup,
|
|
||||||
SelectItem,
|
|
||||||
SelectLabel,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { DialogTrigger } from "@/components/ui/dialog";
|
|
||||||
import { useRouter } from "@/lib/useRouter";
|
|
||||||
|
|
||||||
const typeDefs = `// Hi :) how'd you get here?
|
const typeDefs = `// Hi :) how'd you get here?
|
||||||
// Here, in return I'll provide you with a random number: ${Math.ceil(Math.random() * 100)}
|
// Here, in return I'll provide you with a random number: ${Math.ceil(Math.random() * 100)}
|
||||||
@ -122,8 +86,6 @@ export const transpileTypeScript = (code: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const geistMono = Geist_Mono({ subsets: ["latin"] });
|
|
||||||
|
|
||||||
export default function CustomFilePage({
|
export default function CustomFilePage({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
@ -138,43 +100,28 @@ export default function CustomFilePage({
|
|||||||
const [syntaxErrors, setSyntaxErrors] = useState<
|
const [syntaxErrors, setSyntaxErrors] = useState<
|
||||||
languages.typescript.Diagnostic[] | null
|
languages.typescript.Diagnostic[] | null
|
||||||
>(null);
|
>(null);
|
||||||
const [testMode, setTestMode] = useState("");
|
|
||||||
const router = useRouter();
|
|
||||||
const file = (
|
const file = (
|
||||||
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ?? []
|
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ?? []
|
||||||
).findIndex((c) => c.name === filename);
|
).findIndex((c) => c.name === filename);
|
||||||
|
|
||||||
if (file === -1) {
|
if (file === -1) {
|
||||||
return <>Bruh.</>;
|
return (
|
||||||
|
<div className="w-full h-full flex justify-center items-center absolute top-[0%]">
|
||||||
|
<Link href="/servers/embedded/sl-modification-frame">
|
||||||
|
<ArrowLeft className="absolute left-[10px] top-[10px]" />
|
||||||
|
</Link>
|
||||||
|
<Placeholder
|
||||||
|
title="We couldn't find the file you were looking for."
|
||||||
|
icon={<FileQuestion />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
const fileContents = ((user?.unsafeMetadata
|
||||||
.customFiles as Array<ClerkCustomModification>) ?? [])[file].contents;
|
.customFiles as Array<ClerkCustomModification>) ?? [])[file].contents;
|
||||||
const [value, setValue] = useState(fileContents);
|
const [value, setValue] = useState(fileContents);
|
||||||
const clipboard = useClipboard();
|
const clipboard = useClipboard();
|
||||||
validateCode(value);
|
|
||||||
|
|
||||||
const saveFile = async () => {
|
const saveFile = async () => {
|
||||||
const metadata =
|
const metadata =
|
||||||
@ -194,40 +141,6 @@ export default function CustomFilePage({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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 debouncedSave = debounce(async () => {
|
||||||
const { error } = await tryCatch(saveFile());
|
const { error } = await tryCatch(saveFile());
|
||||||
if (error)
|
if (error)
|
||||||
@ -236,9 +149,9 @@ export default function CustomFilePage({
|
|||||||
);
|
);
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
|
// biome-ignore lint: L
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSuccessfullyLinted(false);
|
setSuccessfullyLinted(false);
|
||||||
validateCode(value);
|
|
||||||
debouncedSave();
|
debouncedSave();
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
@ -253,350 +166,33 @@ export default function CustomFilePage({
|
|||||||
</strong>
|
</strong>
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
{syntaxErrors !== null && syntaxErrors.length !== 0 && (
|
{syntaxErrors !== null && syntaxErrors.length !== 0 && (
|
||||||
<Drawer direction="right">
|
<CustomErrors
|
||||||
<DrawerTrigger>
|
filename={filename}
|
||||||
<Button
|
value={value}
|
||||||
variant="danger-subtle"
|
monacoRef={monacoRef}
|
||||||
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>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<Button
|
<CustomLint
|
||||||
onClick={lintFile}
|
successfullyLinted={successfullyLinted}
|
||||||
disabled={syntaxErrors === null || syntaxErrors.length !== 0}
|
setSuccessfullyLinted={setSuccessfullyLinted}
|
||||||
variant={successfullyLinted ? "success-subtle" : "secondary"}
|
syntaxErrors={syntaxErrors}
|
||||||
>
|
value={value}
|
||||||
Lint
|
/>
|
||||||
</Button>
|
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
{syntaxErrors !== null && syntaxErrors.length !== 0
|
{syntaxErrors !== null && syntaxErrors.length !== 0
|
||||||
? "You must have no type errors in the editor to lint, you have " +
|
? `You must have no type errors in the editor to lint, you have ${syntaxErrors.length} error(s).`
|
||||||
syntaxErrors.length +
|
|
||||||
" error(s)."
|
|
||||||
: "Check for possible runtime errors."}
|
: "Check for possible runtime errors."}
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
{(() => {
|
<CustomTest
|
||||||
const [open, setOpen] = useState(false);
|
value={value}
|
||||||
const [filterEnabled, setFilterEnabled] = useState(true);
|
successfullyLinted={successfullyLinted}
|
||||||
const [sortEnabled, setSortEnabled] = useState(true);
|
|
||||||
const [success, setSuccess] = useState(false);
|
|
||||||
const [fileName, setFileName] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setFilterEnabled(true);
|
|
||||||
setSortEnabled(true);
|
|
||||||
setTestMode("");
|
|
||||||
(async () => {
|
|
||||||
const transpiledValue = transpileTypeScript(value);
|
|
||||||
const functionBody = transpiledValue
|
|
||||||
?.replace(/export default(?!.*[;])/g, "") // Avoid replacing if followed by a semicolon
|
|
||||||
.replace(/export(?!.*[;])/g, ""); // Avoid replacing if followed by a semicolon
|
|
||||||
const { error: filterErr, data: filterFunc } =
|
|
||||||
await tryCatch(
|
|
||||||
(async () =>
|
|
||||||
new Function(
|
|
||||||
"server",
|
|
||||||
`${functionBody}
|
|
||||||
return filter(server)`,
|
|
||||||
))(),
|
|
||||||
);
|
|
||||||
const { error: sortErr, data: sortFunc } = await tryCatch(
|
|
||||||
(async () =>
|
|
||||||
new Function(
|
|
||||||
"serverA",
|
|
||||||
"serverB",
|
|
||||||
`${functionBody}
|
|
||||||
return sort(serverA, serverB)`,
|
|
||||||
))(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (filterErr) setFilterEnabled(false);
|
|
||||||
if (sortErr) setSortEnabled(false);
|
|
||||||
|
|
||||||
try {
|
|
||||||
filterFunc?.({});
|
|
||||||
} catch (e) {
|
|
||||||
if (
|
|
||||||
String(e).startsWith(
|
|
||||||
"ReferenceError: filter is not defined",
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
setFilterEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
sortFunc?.({}, {});
|
|
||||||
} catch (e) {
|
|
||||||
if (
|
|
||||||
String(e).startsWith(
|
|
||||||
"ReferenceError: sort is not defined",
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
setSortEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [open]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button
|
|
||||||
disabled={!successfullyLinted}
|
|
||||||
onClick={() => setOpen(true)}
|
|
||||||
>
|
|
||||||
Test
|
|
||||||
</Button>
|
|
||||||
<Drawer
|
|
||||||
direction="right"
|
|
||||||
open={open}
|
|
||||||
onOpenChange={setOpen}
|
|
||||||
>
|
|
||||||
<DrawerContent className="p-4 min-w-[400px] overflow-x-hidden max-h-screen overflow-y-auto">
|
|
||||||
<p className="text-sm mb-2">
|
|
||||||
You can run an interactive server-list environment
|
|
||||||
with actual online servers to test your modifications.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<Material>
|
|
||||||
<Setting>
|
|
||||||
<SettingContent>
|
|
||||||
<SettingMeta>
|
|
||||||
<SettingTitle>Function to test</SettingTitle>
|
|
||||||
<SettingDescription>
|
|
||||||
You can pick to either test a sorting system
|
|
||||||
or a filter.
|
|
||||||
</SettingDescription>
|
|
||||||
</SettingMeta>
|
|
||||||
<Select
|
|
||||||
value={testMode}
|
|
||||||
onValueChange={setTestMode}
|
|
||||||
disabled={success}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="w-[180px]">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectGroup>
|
|
||||||
<SelectItem
|
|
||||||
value="filter"
|
|
||||||
disabled={!filterEnabled}
|
|
||||||
>
|
|
||||||
<code>filter</code>
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem
|
|
||||||
value="sort"
|
|
||||||
disabled={!sortEnabled}
|
|
||||||
>
|
|
||||||
<code>sort</code>
|
|
||||||
</SelectItem>
|
|
||||||
</SelectGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</SettingContent>
|
|
||||||
</Setting>
|
|
||||||
</Material>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
className="w-full mt-2 flex items-center gap-2"
|
|
||||||
disabled={testMode === "" || success}
|
|
||||||
variant={success ? "success-subtle" : "default"}
|
|
||||||
onClick={() => {
|
|
||||||
const t = btoa(value);
|
|
||||||
|
|
||||||
const newTab = window.open(
|
|
||||||
`/servers?tm=${encodeURIComponent(t)}`,
|
|
||||||
);
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
newTab?.dispatchEvent(
|
|
||||||
new Event("test-mode.enable." + testMode),
|
|
||||||
);
|
|
||||||
}, 500);
|
|
||||||
toast.info(
|
|
||||||
"Waiting for server tab to pick up thread...",
|
|
||||||
);
|
|
||||||
|
|
||||||
newTab?.addEventListener(
|
|
||||||
"test-mode.enabled",
|
|
||||||
() => {
|
|
||||||
clearInterval(interval);
|
|
||||||
toast.success(
|
|
||||||
"Connected to new tab; continue.",
|
|
||||||
);
|
|
||||||
newTab?.addEventListener(
|
|
||||||
"test-mode.success",
|
|
||||||
() => {
|
|
||||||
toast.success(
|
|
||||||
"Resolved success from thread!",
|
|
||||||
);
|
|
||||||
setSuccess(true);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{success && <Check size={16} />}Test
|
|
||||||
</Button>
|
|
||||||
{success && (
|
|
||||||
<>
|
|
||||||
<p className="text-sm my-2">
|
|
||||||
You can now activate this custom modification.
|
|
||||||
Please note that the filter and sort versions of
|
|
||||||
your modifications will be different, and the one
|
|
||||||
used will be selected based on what type you
|
|
||||||
tested on.
|
|
||||||
</p>
|
|
||||||
{(
|
|
||||||
(user?.unsafeMetadata
|
|
||||||
.activatedModifications as ClerkCustomActivatedModification[]) ??
|
|
||||||
[]
|
|
||||||
).find((c) => c.originalFileName === filename && c.testMode === testMode) !==
|
|
||||||
undefined && (
|
|
||||||
<Alert className="mb-2 gap-2" variant="warning">
|
|
||||||
This modification was already activated! Hitting
|
|
||||||
activate here will just overwrite the contents
|
|
||||||
and the new friendly name.
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Material>
|
|
||||||
<Setting>
|
|
||||||
<SettingContent>
|
|
||||||
<SettingMeta>
|
|
||||||
<SettingTitle>Name</SettingTitle>
|
|
||||||
<SettingDescription>
|
|
||||||
Set a friendly name for your modification.
|
|
||||||
</SettingDescription>
|
|
||||||
</SettingMeta>
|
|
||||||
<Input
|
|
||||||
placeholder="My cool mod"
|
|
||||||
value={fileName}
|
|
||||||
onChange={(c) =>
|
|
||||||
setFileName(c.target.value)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</SettingContent>
|
|
||||||
</Setting>
|
|
||||||
</Material>
|
|
||||||
<DialogTrigger>
|
|
||||||
<Button
|
|
||||||
className="w-full my-2"
|
|
||||||
disabled={fileName === ""}
|
|
||||||
onClick={async () => {
|
|
||||||
const array =
|
|
||||||
(user?.unsafeMetadata
|
|
||||||
.activatedModifications as ClerkCustomActivatedModification[]) ??
|
|
||||||
[];
|
|
||||||
const index = array.findIndex(
|
|
||||||
(c) => c.originalFileName === filename && c.testMode === testMode,
|
|
||||||
);
|
|
||||||
const color = '#' + Math.floor(Math.random() * 16777215).toString(16);
|
|
||||||
const transpiledValue =
|
|
||||||
transpileTypeScript(value);
|
|
||||||
|
|
||||||
if (transpiledValue === null)
|
|
||||||
return toast.error("Error transpiling");
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
// Original already exists
|
|
||||||
array[index] = {
|
|
||||||
originalFileName: filename,
|
|
||||||
// I'm too lazy to change this
|
|
||||||
friendlyName: fileName,
|
|
||||||
transpiledContents: transpiledValue,
|
|
||||||
active: true,
|
|
||||||
testMode: testMode as "filter" | "sort",
|
|
||||||
color
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
array.push({
|
|
||||||
originalFileName: filename,
|
|
||||||
// ... and this too
|
|
||||||
friendlyName: fileName,
|
|
||||||
transpiledContents: transpiledValue,
|
|
||||||
active: true,
|
|
||||||
testMode: testMode as "filter" | "sort",
|
|
||||||
color
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await user?.update({
|
|
||||||
unsafeMetadata: {
|
|
||||||
...user.unsafeMetadata,
|
|
||||||
activatedModifications: array,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
toast.success("Activated!")
|
|
||||||
router.push("/servers/embedded/sl-modification-frame")
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Activate
|
|
||||||
</Button>
|
|
||||||
</DialogTrigger>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</DrawerContent>
|
|
||||||
</Drawer>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})()}
|
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
{successfullyLinted
|
{successfullyLinted
|
||||||
@ -641,23 +237,6 @@ export default function CustomFilePage({
|
|||||||
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(
|
||||||
@ -756,23 +335,3 @@ export async function findSupportedOperations(
|
|||||||
|
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
function guidGenerator() {
|
|
||||||
const S4 = () => {
|
|
||||||
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
S4() +
|
|
||||||
S4() +
|
|
||||||
"-" +
|
|
||||||
S4() +
|
|
||||||
"-" +
|
|
||||||
S4() +
|
|
||||||
"-" +
|
|
||||||
S4() +
|
|
||||||
"-" +
|
|
||||||
S4() +
|
|
||||||
S4() +
|
|
||||||
S4()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -52,7 +52,7 @@ import {
|
|||||||
SortAsc,
|
SortAsc,
|
||||||
Trash,
|
Trash,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { use } from "react";
|
import { use, useEffect, useState } from "react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { findSupportedOperations } from "../file/[filename]/page";
|
import { findSupportedOperations } from "../file/[filename]/page";
|
||||||
|
|
||||||
@ -60,8 +60,9 @@ export default function ServerListModificationFrame() {
|
|||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const files =
|
const files =
|
||||||
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ?? [];
|
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ?? [];
|
||||||
const operations = use((async () => await Promise.all(files.map(async (c) => await findSupportedOperations(c.contents))))())
|
const operations = usePlatforms(files);
|
||||||
console.log(operations)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="max-w-[800px] p-4">
|
<main className="max-w-[800px] p-4">
|
||||||
<h1 className="text-xl font-bold w-full flex items-center gap-2">
|
<h1 className="text-xl font-bold w-full flex items-center gap-2">
|
||||||
@ -93,6 +94,7 @@ export default function ServerListModificationFrame() {
|
|||||||
<span>
|
<span>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
|
<DropdownMenu></DropdownMenu>
|
||||||
<Button
|
<Button
|
||||||
variant="tertiary"
|
variant="tertiary"
|
||||||
className="flex items-center justify-center hover:bg-slate-200 dark:hover:bg-zinc-700/60"
|
className="flex items-center justify-center hover:bg-slate-200 dark:hover:bg-zinc-700/60"
|
||||||
@ -118,7 +120,7 @@ export default function ServerListModificationFrame() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
toast.success(
|
toast.success(
|
||||||
"Deleted file in " + (Date.now() - startTime) + "ms"
|
"Deleted file in " + (Date.now() - startTime) + "ms",
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -136,3 +138,21 @@ export default function ServerListModificationFrame() {
|
|||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function usePlatforms(files: Array<ClerkCustomModification>) {
|
||||||
|
const [result, setResult] = useState<
|
||||||
|
Array<{ sort: boolean; filter: boolean }>
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
setResult(
|
||||||
|
await Promise.all(
|
||||||
|
files.map(async (c) => await findSupportedOperations(c.contents)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
}, [files]);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
import { BrandingGenericIcon } from "../icons/branding-icons";
|
import { BrandingGenericIcon, Discord } from "../icons/branding-icons";
|
||||||
import { Link } from "../../util/link";
|
import { Link } from "../../util/link";
|
||||||
import { FooterStatus } from "./status";
|
import { FooterStatus } from "./status";
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import Github from "@/components/ui/github";
|
||||||
|
import Image from "next/image"
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer className="w-full border-t p-[20px] mt-15">
|
<footer className="w-full border-t p-[20px] mt-15">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-start">
|
||||||
<span className="flex items-center gap-4 text-muted-foreground">
|
<span className="flex items-center gap-4 text-muted-foreground">
|
||||||
<Link href="Special:Root">
|
<Link href="Special:Root">
|
||||||
<BrandingGenericIcon className="max-w-[32px] max-h-[32px]" />
|
<BrandingGenericIcon className="max-w-[32px] max-h-[32px]" />
|
||||||
@ -36,32 +39,48 @@ export function Footer() {
|
|||||||
Contact
|
Contact
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="text-sm">
|
</ul>
|
||||||
<DropdownMenu
|
</span>
|
||||||
|
<div className="block">
|
||||||
|
<div className="flex items-center mb-2 justify-end gap-2">
|
||||||
|
|
||||||
>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<a className="text-muted-foreground hover:text-shadcn-primary transition-colors cursor-pointer">Discord</a>
|
<Button variant="tertiary" size="square-md" className="flex items-center">
|
||||||
|
<Discord className="w-[1.25em] h-[1.25em]" />
|
||||||
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<Link href="https://t.mhsf.app/d/m" noExtraIcons>
|
<Link href="https://t.mhsf.app/d/m" noExtraIcons>
|
||||||
<DropdownMenuItem className="block py-2">
|
<DropdownMenuItem className="py-2 flex items-center gap-2">
|
||||||
|
<Image className="max-w-[30px] max-h-[30px] rounded border border-muted-foreground" src="https://avatars.githubusercontent.com/u/16529253?s=200&v=4" alt="Minehut" width={30} height={30} />
|
||||||
|
<span className="block">
|
||||||
Minehut Discord
|
Minehut Discord
|
||||||
<small className="flex">Not officially owned by MHSF, however conversations about MHSF and related take place there.</small>
|
<small className="flex">Not officially owned by MHSF, however conversations about MHSF and related take place there.</small>
|
||||||
|
</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</Link>
|
</Link>
|
||||||
<Link href="https://t.mhsf.app/d/u" noExtraIcons>
|
<Link href="https://t.mhsf.app/d/u" noExtraIcons>
|
||||||
<DropdownMenuItem className="block py-2">
|
<DropdownMenuItem className="py-2 flex items-center gap-2">
|
||||||
|
<BrandingGenericIcon className="max-w-[30px] max-h-[30px] rounded border border-muted-foreground" width={30} height={30} />
|
||||||
|
<span className="block">
|
||||||
MHSF Discord
|
MHSF Discord
|
||||||
<small className="flex">A read-only server for updates related to MHSF.</small>
|
<small className="flex">A read-only server for updates related to MHSF.</small>
|
||||||
|
</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</li>
|
<Link href="https://github.com/DeveloLongScript/MHSF" noExtraIcons>
|
||||||
</ul>
|
<Button variant="tertiary" size="square-md" className="flex items-center">
|
||||||
</span>
|
<Github className="w-[1.25em] h-[1.25em]" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
</div>
|
||||||
<FooterStatus />
|
<FooterStatus />
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="block mt-4">
|
<span className="block mt-4">
|
||||||
<small className="text-muted-foreground text-[0.75rem]">
|
<small className="text-muted-foreground text-[0.75rem]">
|
||||||
|
|||||||
153
apps/www/src/components/feat/server-list/modification/custom-files/custom-errors.tsx
Normal file
153
apps/www/src/components/feat/server-list/modification/custom-files/custom-errors.tsx
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
Drawer,
|
||||||
|
DrawerTrigger,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerTitle,
|
||||||
|
} from "@/components/ui/drawer";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Alert } from "@/components/ui/alert";
|
||||||
|
import { compressToEncodedURIComponent } from "lz-string";
|
||||||
|
import { AlertOctagon, ExternalLink } from "lucide-react";
|
||||||
|
import type { languages, Uri } from "monaco-editor";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { Link } from "@/components/util/link";
|
||||||
|
import { type RefObject, useEffect, useState } from "react";
|
||||||
|
import type { MonacoRefType } from "@/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/file/[filename]/page";
|
||||||
|
|
||||||
|
export type SyntaxErrorInterface = languages.typescript.Diagnostic[] | null;
|
||||||
|
|
||||||
|
export function CustomErrors({
|
||||||
|
value,
|
||||||
|
monacoRef,
|
||||||
|
filename
|
||||||
|
}: {
|
||||||
|
value: string;
|
||||||
|
monacoRef: RefObject<MonacoRefType | null>;
|
||||||
|
filename: string;
|
||||||
|
}) {
|
||||||
|
const [syntaxErrors, setSyntaxErrors] = useState<SyntaxErrorInterface>();
|
||||||
|
|
||||||
|
const validateCode = () => {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
validateCode();
|
||||||
|
|
||||||
|
// biome-ignore lint: L
|
||||||
|
useEffect(validateCode, [value]);
|
||||||
|
|
||||||
|
if (syntaxErrors !== null && syntaxErrors !== undefined)
|
||||||
|
return (
|
||||||
|
<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"
|
||||||
|
}
|
||||||
|
/* biome-ignore lint: No. */
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@ -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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { transpileTypeScript } from "@/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/file/[filename]/page";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { tryCatch } from "@/lib/try-catch";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import type { SyntaxErrorInterface } from "./custom-errors";
|
||||||
|
|
||||||
|
export function CustomLint({
|
||||||
|
successfullyLinted,
|
||||||
|
setSuccessfullyLinted,
|
||||||
|
value,
|
||||||
|
syntaxErrors,
|
||||||
|
}: {
|
||||||
|
successfullyLinted: boolean;
|
||||||
|
setSuccessfullyLinted: (change: boolean) => void;
|
||||||
|
value: string;
|
||||||
|
syntaxErrors: SyntaxErrorInterface;
|
||||||
|
}) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={lintFile}
|
||||||
|
disabled={syntaxErrors === null || syntaxErrors.length !== 0}
|
||||||
|
variant={successfullyLinted ? "success-subtle" : "secondary"}
|
||||||
|
>
|
||||||
|
Lint
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
150
apps/www/src/components/feat/server-list/modification/custom-files/custom-test-success.tsx
Normal file
150
apps/www/src/components/feat/server-list/modification/custom-files/custom-test-success.tsx
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
* 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 { toast } from "sonner";
|
||||||
|
import type { ClerkCustomActivatedModification } from "../modification-file-creation-dialog";
|
||||||
|
import { useUser } from "@clerk/nextjs";
|
||||||
|
import { Alert } from "@/components/ui/alert";
|
||||||
|
import { Material } from "@/components/ui/material";
|
||||||
|
import {
|
||||||
|
Setting,
|
||||||
|
SettingContent,
|
||||||
|
SettingDescription,
|
||||||
|
SettingMeta,
|
||||||
|
SettingTitle,
|
||||||
|
} from "@/components/feat/settings/setting";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { DialogTrigger } from "@/components/ui/dialog";
|
||||||
|
import { transpileTypeScript } from "@/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/file/[filename]/page";
|
||||||
|
import { useRouter } from "@/lib/useRouter";
|
||||||
|
|
||||||
|
export function CustomTestSuccess({
|
||||||
|
filename,
|
||||||
|
testMode,
|
||||||
|
value,
|
||||||
|
}: { filename: string; testMode: "filter" | "sort" | ""; value: string }) {
|
||||||
|
const { user } = useUser();
|
||||||
|
const [friendlyName, setFriendlyName] = useState("");
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p className="text-sm my-2">
|
||||||
|
You can now activate this custom modification. Please note that the
|
||||||
|
filter and sort versions of your modifications will be different, and
|
||||||
|
the one used will be selected based on what type you tested on.
|
||||||
|
</p>
|
||||||
|
{(
|
||||||
|
(user?.unsafeMetadata
|
||||||
|
.activatedModifications as ClerkCustomActivatedModification[]) ?? []
|
||||||
|
).find(
|
||||||
|
(c) => c.originalFileName === filename && c.testMode === testMode,
|
||||||
|
) !== undefined && (
|
||||||
|
<Alert className="mb-2 gap-2" variant="warning">
|
||||||
|
This modification was already activated! Hitting activate here will
|
||||||
|
just overwrite the contents and the new friendly name.
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Material>
|
||||||
|
<Setting>
|
||||||
|
<SettingContent>
|
||||||
|
<SettingMeta>
|
||||||
|
<SettingTitle>Name</SettingTitle>
|
||||||
|
<SettingDescription>
|
||||||
|
Set a friendly name for your modification.
|
||||||
|
</SettingDescription>
|
||||||
|
</SettingMeta>
|
||||||
|
<Input
|
||||||
|
placeholder="My cool mod"
|
||||||
|
value={friendlyName}
|
||||||
|
onChange={(c) => setFriendlyName(c.target.value)}
|
||||||
|
/>
|
||||||
|
</SettingContent>
|
||||||
|
</Setting>
|
||||||
|
</Material>
|
||||||
|
<DialogTrigger>
|
||||||
|
<Button
|
||||||
|
className="w-full my-2"
|
||||||
|
disabled={friendlyName === "" || friendlyName.replace(" ", "") === ""}
|
||||||
|
onClick={async () => {
|
||||||
|
const array =
|
||||||
|
(user?.unsafeMetadata
|
||||||
|
.activatedModifications as ClerkCustomActivatedModification[]) ??
|
||||||
|
[];
|
||||||
|
const index = array.findIndex(
|
||||||
|
(c) => c.originalFileName === filename && c.testMode === testMode,
|
||||||
|
);
|
||||||
|
const color = `#${Math.floor(Math.random() * 16777215).toString(16)}`;
|
||||||
|
const transpiledValue = transpileTypeScript(value);
|
||||||
|
|
||||||
|
if (transpiledValue === null)
|
||||||
|
return toast.error("Error transpiling");
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
// Original already exists
|
||||||
|
array[index] = {
|
||||||
|
originalFileName: filename,
|
||||||
|
friendlyName,
|
||||||
|
transpiledContents: transpiledValue,
|
||||||
|
active: true,
|
||||||
|
testMode: testMode as "filter" | "sort",
|
||||||
|
color,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
array.push({
|
||||||
|
originalFileName: filename,
|
||||||
|
friendlyName,
|
||||||
|
transpiledContents: transpiledValue,
|
||||||
|
active: true,
|
||||||
|
testMode: testMode as "filter" | "sort",
|
||||||
|
color,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await user?.update({
|
||||||
|
unsafeMetadata: {
|
||||||
|
...user.unsafeMetadata,
|
||||||
|
activatedModifications: array,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.success("Activated!");
|
||||||
|
router.push("/servers/embedded/sl-modification-frame");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Activate
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* 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 { transpileTypeScript } from "@/app/(sl-modification-frame)/servers/embedded/sl-modification-frame/file/[filename]/page";
|
||||||
|
import { Setting, SettingContent, SettingDescription, SettingMeta, SettingTitle } from "@/components/feat/settings/setting";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Drawer, DrawerContent } from "@/components/ui/drawer";
|
||||||
|
import { Material } from "@/components/ui/material";
|
||||||
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import { tryCatch } from "@/lib/try-catch";
|
||||||
|
import { Check } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { CustomTestSuccess } from "./custom-test-success";
|
||||||
|
|
||||||
|
export function CustomTest({value, successfullyLinted}: {value: string, successfullyLinted: boolean}) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [filterEnabled, setFilterEnabled] = useState(true);
|
||||||
|
const [sortEnabled, setSortEnabled] = useState(true);
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
const [fileName, setFileName] = useState("");
|
||||||
|
const [testMode, setTestMode] = useState<"filter" | "sort" | "">("");
|
||||||
|
|
||||||
|
// biome-ignore lint: values needed (but not shown by linter)
|
||||||
|
useEffect(() => setSuccess(false), [value]);
|
||||||
|
|
||||||
|
// biome-ignore lint: values not needed
|
||||||
|
useEffect(() => {
|
||||||
|
setFilterEnabled(true);
|
||||||
|
setSortEnabled(true);
|
||||||
|
setTestMode("");
|
||||||
|
(async () => {
|
||||||
|
const transpiledValue = transpileTypeScript(value);
|
||||||
|
const functionBody = transpiledValue
|
||||||
|
?.replace(/export default(?!.*[;])/g, "") // Avoid replacing if followed by a semicolon
|
||||||
|
.replace(/export(?!.*[;])/g, ""); // Avoid replacing if followed by a semicolon
|
||||||
|
const { error: filterErr, data: filterFunc } = await tryCatch(
|
||||||
|
(async () =>
|
||||||
|
new Function(
|
||||||
|
"server",
|
||||||
|
`${functionBody}
|
||||||
|
return filter(server)`,
|
||||||
|
))(),
|
||||||
|
);
|
||||||
|
const { error: sortErr, data: sortFunc } = await tryCatch(
|
||||||
|
(async () =>
|
||||||
|
new Function(
|
||||||
|
"serverA",
|
||||||
|
"serverB",
|
||||||
|
`${functionBody}
|
||||||
|
return sort(serverA, serverB)`,
|
||||||
|
))(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (filterErr) setFilterEnabled(false);
|
||||||
|
if (sortErr) setSortEnabled(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
filterFunc?.({});
|
||||||
|
} catch (e) {
|
||||||
|
if (String(e).startsWith("ReferenceError: filter is not defined")) {
|
||||||
|
setFilterEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
sortFunc?.({}, {});
|
||||||
|
} catch (e) {
|
||||||
|
if (String(e).startsWith("ReferenceError: sort is not defined")) {
|
||||||
|
setSortEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [open, value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button disabled={!successfullyLinted} onClick={() => setOpen(true)}>
|
||||||
|
Test
|
||||||
|
</Button>
|
||||||
|
<Drawer direction="right" open={open} onOpenChange={setOpen}>
|
||||||
|
<DrawerContent className="p-4 min-w-[400px] overflow-x-hidden max-h-screen overflow-y-auto">
|
||||||
|
<p className="text-sm mb-2">
|
||||||
|
You can run an interactive server-list environment with actual
|
||||||
|
online servers to test your modifications.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<Material>
|
||||||
|
<Setting>
|
||||||
|
<SettingContent>
|
||||||
|
<SettingMeta>
|
||||||
|
<SettingTitle>Function to test</SettingTitle>
|
||||||
|
<SettingDescription>
|
||||||
|
You can pick to either test a sorting system or a filter.
|
||||||
|
</SettingDescription>
|
||||||
|
</SettingMeta>
|
||||||
|
<Select
|
||||||
|
value={testMode}
|
||||||
|
onValueChange={setTestMode as (change: string) => void}
|
||||||
|
disabled={success}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[180px]">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="filter" disabled={!filterEnabled}>
|
||||||
|
<code>filter</code>
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="sort" disabled={!sortEnabled}>
|
||||||
|
<code>sort</code>
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</SettingContent>
|
||||||
|
</Setting>
|
||||||
|
</Material>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="w-full mt-2 flex items-center gap-2"
|
||||||
|
disabled={testMode === "" || success}
|
||||||
|
variant={success ? "success-subtle" : "default"}
|
||||||
|
onClick={() => {
|
||||||
|
const t = btoa(value);
|
||||||
|
|
||||||
|
const newTab = window.open(
|
||||||
|
`/servers?tm=${encodeURIComponent(t)}`,
|
||||||
|
);
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
newTab?.dispatchEvent(
|
||||||
|
new Event(`test-mode.enable.${testMode}`),
|
||||||
|
);
|
||||||
|
}, 500);
|
||||||
|
toast.info("Waiting for server tab to pick up thread...");
|
||||||
|
|
||||||
|
newTab?.addEventListener("test-mode.enabled", () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
toast.success("Connected to new tab; continue.");
|
||||||
|
newTab?.addEventListener("test-mode.success", () => {
|
||||||
|
toast.success("Resolved success from thread!");
|
||||||
|
setSuccess(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{success && <Check size={16} />}Test
|
||||||
|
</Button>
|
||||||
|
{success && (
|
||||||
|
<CustomTestSuccess filename={fileName} testMode={testMode} value={value} />
|
||||||
|
)}
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -28,22 +28,39 @@
|
|||||||
* OTHER DEALINGS IN THE SOFTWARE.
|
* OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createContext } from "@/server/context";
|
"use client";
|
||||||
import { appRouter } from "@/server/router/_app";
|
|
||||||
import { getAuth } from "@clerk/nextjs/server";
|
|
||||||
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
|
|
||||||
// I still have no clue why this works.
|
|
||||||
import type { NextRequest } from "../../../../../../../node_modules/@clerk/nextjs/node_modules/next/dist/server/web/spec-extension/request";
|
|
||||||
|
|
||||||
const handler = (request: NextRequest) => {
|
import { useUser } from "@clerk/nextjs";
|
||||||
const {userId} = getAuth(request);
|
import { ClerkCustomActivatedModification } from "./modification-file-creation-dialog";
|
||||||
|
import { Link } from "@/components/util/link";
|
||||||
|
import { Material } from "@/components/ui/material";
|
||||||
|
import { Binary } from "lucide-react";
|
||||||
|
|
||||||
return fetchRequestHandler({
|
export function ModificationCustomModificationRow({category}: {category: string}) {
|
||||||
endpoint: "/api/trpc",
|
const { user } = useUser();
|
||||||
req: request,
|
|
||||||
router: appRouter,
|
|
||||||
createContext: createContext(userId),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export { handler as GET, handler as POST };
|
return (
|
||||||
|
(user?.unsafeMetadata
|
||||||
|
.activatedModifications as ClerkCustomActivatedModification[]) ?? []
|
||||||
|
).map((m) => (
|
||||||
|
<Link
|
||||||
|
href={`/servers/embedded/sl-modification-frame/category/${category}/modification/_custom/${btoa(m.friendlyName)}`}
|
||||||
|
key={m.friendlyName}
|
||||||
|
>
|
||||||
|
<Material
|
||||||
|
elevation="high"
|
||||||
|
className="p-2 hover:drop-shadow-card-hover cursor-pointer"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="w-full h-[40px] mb-2 rounded-lg items-center text-center justify-center"
|
||||||
|
style={{ backgroundColor: m.color }}
|
||||||
|
>
|
||||||
|
<Binary 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.friendlyName}
|
||||||
|
</span>
|
||||||
|
</Material>
|
||||||
|
</Link>
|
||||||
|
));
|
||||||
|
}
|
||||||
@ -47,13 +47,13 @@ export function DebugMenu({
|
|||||||
await codeToHtml(JSON.stringify(debugOptions.mhsfData, null, 2), {
|
await codeToHtml(JSON.stringify(debugOptions.mhsfData, null, 2), {
|
||||||
lang: "json",
|
lang: "json",
|
||||||
theme: resolvedTheme === "dark" ? "vitesse-dark" : "vitesse-light",
|
theme: resolvedTheme === "dark" ? "vitesse-dark" : "vitesse-light",
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
setMHShikiParsed(
|
setMHShikiParsed(
|
||||||
await codeToHtml(JSON.stringify(debugOptions.serverData, null, 2), {
|
await codeToHtml(JSON.stringify(debugOptions.serverData, null, 2), {
|
||||||
lang: "json",
|
lang: "json",
|
||||||
theme: resolvedTheme === "dark" ? "vitesse-dark" : "vitesse-light",
|
theme: resolvedTheme === "dark" ? "vitesse-dark" : "vitesse-light",
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
}, [debugOptions]);
|
}, [debugOptions]);
|
||||||
@ -147,7 +147,7 @@ export function DebugMenu({
|
|||||||
</SettingContent>
|
</SettingContent>
|
||||||
</Setting>
|
</Setting>
|
||||||
<Setting className="py-3">
|
<Setting className="py-3">
|
||||||
<SettingContent>
|
{debugOptions.mhsfData !== undefined && <SettingContent>
|
||||||
<SettingMeta>
|
<SettingMeta>
|
||||||
<SettingTitle>Total Statistical Data Count</SettingTitle>
|
<SettingTitle>Total Statistical Data Count</SettingTitle>
|
||||||
<SettingDescription>
|
<SettingDescription>
|
||||||
@ -155,12 +155,24 @@ export function DebugMenu({
|
|||||||
</SettingDescription>
|
</SettingDescription>
|
||||||
</SettingMeta>
|
</SettingMeta>
|
||||||
{convert(
|
{convert(
|
||||||
debugOptions.mhsfData.achievements.historically.length +
|
(
|
||||||
debugOptions.mhsfData.playerData.historically.length +
|
debugOptions.mhsfData.achievements ?? {
|
||||||
debugOptions.mhsfData.favoriteData.favoriteHistoricalData
|
historically: { length: 0 },
|
||||||
.length
|
}
|
||||||
|
).historically.length +
|
||||||
|
(
|
||||||
|
debugOptions.mhsfData.playerData ?? {
|
||||||
|
historically: { length: 0 },
|
||||||
|
}
|
||||||
|
).historically.length +
|
||||||
|
(
|
||||||
|
debugOptions.mhsfData.favoriteData ?? {
|
||||||
|
favoriteHistoricalData: { length: 0 },
|
||||||
|
}
|
||||||
|
).favoriteHistoricalData.length,
|
||||||
)}
|
)}
|
||||||
</SettingContent>
|
</SettingContent>}
|
||||||
|
|
||||||
</Setting>
|
</Setting>
|
||||||
|
|
||||||
<Setting className="py-3">
|
<Setting className="py-3">
|
||||||
|
|||||||
@ -15,6 +15,9 @@ import { useQueryState } from "nuqs";
|
|||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { convert } from "../util";
|
import { convert } from "../util";
|
||||||
import { Material } from "@/components/ui/material";
|
import { Material } from "@/components/ui/material";
|
||||||
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
|
import { Placeholder } from "@/components/ui/placeholder";
|
||||||
|
import { CircleSlash } from "lucide-react";
|
||||||
|
|
||||||
export function StatisticsMainRow({
|
export function StatisticsMainRow({
|
||||||
server,
|
server,
|
||||||
@ -41,7 +44,7 @@ export function StatisticsMainRow({
|
|||||||
"text-sm cursor-pointer hover:bg-slate-100 dark:hover:bg-zinc-700/30 transition-all duration-75 disabled:opacity-50 disabled:pointer-events-none",
|
"text-sm cursor-pointer hover:bg-slate-100 dark:hover:bg-zinc-700/30 transition-all duration-75 disabled:opacity-50 disabled:pointer-events-none",
|
||||||
"rounded-xl px-2 flex items-center gap-2",
|
"rounded-xl px-2 flex items-center gap-2",
|
||||||
statisticType === "playerCount" &&
|
statisticType === "playerCount" &&
|
||||||
"bg-slate-100 dark:bg-zinc-700/30 font-medium"
|
"bg-slate-100 dark:bg-zinc-700/30 font-medium",
|
||||||
)}
|
)}
|
||||||
onClick={() => setStatisticType("playerCount")}
|
onClick={() => setStatisticType("playerCount")}
|
||||||
>
|
>
|
||||||
@ -56,7 +59,7 @@ export function StatisticsMainRow({
|
|||||||
"text-sm cursor-pointer hover:bg-slate-100 dark:hover:bg-zinc-700/30 transition-all duration-75 disabled:opacity-50 disabled:pointer-events-none",
|
"text-sm cursor-pointer hover:bg-slate-100 dark:hover:bg-zinc-700/30 transition-all duration-75 disabled:opacity-50 disabled:pointer-events-none",
|
||||||
"rounded-xl px-2 flex items-center gap-2",
|
"rounded-xl px-2 flex items-center gap-2",
|
||||||
statisticType === "favorites" &&
|
statisticType === "favorites" &&
|
||||||
"bg-slate-100 dark:bg-zinc-700/30 font-medium"
|
"bg-slate-100 dark:bg-zinc-700/30 font-medium",
|
||||||
)}
|
)}
|
||||||
onClick={() => setStatisticType("favorites")}
|
onClick={() => setStatisticType("favorites")}
|
||||||
>
|
>
|
||||||
@ -64,7 +67,7 @@ export function StatisticsMainRow({
|
|||||||
<Badge className="px-1">
|
<Badge className="px-1">
|
||||||
<code>
|
<code>
|
||||||
{convert(
|
{convert(
|
||||||
mhsfData.server?.favoriteData.favoriteNumber as number
|
mhsfData.server?.favoriteData.favoriteNumber as number,
|
||||||
)}
|
)}
|
||||||
</code>
|
</code>
|
||||||
</Badge>
|
</Badge>
|
||||||
@ -73,7 +76,12 @@ export function StatisticsMainRow({
|
|||||||
<Separator />
|
<Separator />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
{!mhsfData.loading && (
|
{!mhsfData.loading ? (
|
||||||
|
<>
|
||||||
|
{(statisticType === "playerCount"
|
||||||
|
? mhsfData.server?.playerData.historically
|
||||||
|
: mhsfData.server?.favoriteData.favoriteHistoricalData
|
||||||
|
)?.length !== 0 ? (
|
||||||
<StatisticsChart
|
<StatisticsChart
|
||||||
data={
|
data={
|
||||||
statisticType === "playerCount"
|
statisticType === "playerCount"
|
||||||
@ -82,6 +90,18 @@ export function StatisticsMainRow({
|
|||||||
}
|
}
|
||||||
mainDataPoint={statisticType}
|
mainDataPoint={statisticType}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<span className="w-full h-full items-center justify-center flex">
|
||||||
|
<Placeholder
|
||||||
|
icon={<CircleSlash />}
|
||||||
|
title="There is no data to be collected"
|
||||||
|
description="This server probably never had any data collected in your choosen timespan."
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Spinner />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Material>
|
</Material>
|
||||||
|
|||||||
@ -112,11 +112,10 @@ export const allTags: Array<{
|
|||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
condition: async (c) =>
|
condition: async (c) =>
|
||||||
(c.online === undefined ? c.server?.playerCount : c.online.playerData) ===
|
(c.online === undefined ? c.server?.playerCount: c.online.playerData.playerCount) ===
|
||||||
0,
|
0,
|
||||||
htmlDocs: "Nobody is online this server.",
|
htmlDocs: "Nobody is online this server.",
|
||||||
tooltipDesc: "Nobody is online this server.",
|
tooltipDesc: "Nobody is online this server.",
|
||||||
|
|
||||||
role: "gray-subtle",
|
role: "gray-subtle",
|
||||||
docsName: "Nobody Online",
|
docsName: "Nobody Online",
|
||||||
__filter: true,
|
__filter: true,
|
||||||
|
|||||||
@ -99,7 +99,7 @@
|
|||||||
"@babel/helper-string-parser" "^7.25.9"
|
"@babel/helper-string-parser" "^7.25.9"
|
||||||
"@babel/helper-validator-identifier" "^7.25.9"
|
"@babel/helper-validator-identifier" "^7.25.9"
|
||||||
|
|
||||||
"@biomejs/biome@^1.8.3":
|
"@biomejs/biome@^1.9.4":
|
||||||
version "1.9.4"
|
version "1.9.4"
|
||||||
resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-1.9.4.tgz#89766281cbc3a0aae865a7ff13d6aaffea2842bf"
|
resolved "https://registry.yarnpkg.com/@biomejs/biome/-/biome-1.9.4.tgz#89766281cbc3a0aae865a7ff13d6aaffea2842bf"
|
||||||
integrity sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==
|
integrity sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user