fix: biome, test mode selection

This commit is contained in:
dvelo 2025-03-26 23:58:46 -05:00
parent 2078e5fba1
commit bddf5f1528
9 changed files with 138 additions and 27 deletions

@ -1,5 +1,5 @@
import { ServerProvider } from "@/components/feat/server-page/server-provider"; import { ServerProvider } from "@/components/feat/server-page/server-provider";
import { ServerResponse } from "@/lib/types/mh-server"; import type { ServerResponse } from "@/lib/types/mh-server";
import type { Metadata } from "next"; import type { Metadata } from "next";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";

@ -46,7 +46,6 @@ import {
ArrowLeft, ArrowLeft,
Braces, Braces,
EllipsisVertical, EllipsisVertical,
File,
FileCode, FileCode,
Pencil, Pencil,
Trash, Trash,
@ -76,7 +75,7 @@ export default function ServerListModificationFrame() {
{files.map((c, i) => ( {files.map((c, i) => (
<Link <Link
href={`/servers/embedded/sl-modification-frame/file/${c.name}`} 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" className="w-full py-1 px-2 rounded-xl flex items-center gap-1 justify-between hover:bg-slate-100 dark:hover:bg-zinc-700/30"
key={c.name} key={c.name}
> >
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
@ -88,7 +87,7 @@ export default function ServerListModificationFrame() {
<DropdownMenuTrigger> <DropdownMenuTrigger>
<Button <Button
variant="tertiary" variant="tertiary"
className="flex items-center justify-center hover:bg-slate-200" className="flex items-center justify-center hover:bg-slate-200 dark:hover:bg-zinc-700/60"
size="square-sm" size="square-sm"
> >
<EllipsisVertical <EllipsisVertical

@ -225,6 +225,34 @@
} }
} }
@keyframes loading-shimmer {
0% {
background-position: 125% 0;
}
100% {
background-position: -150% 0;
}
}
.loading-shimmer {
-webkit-text-fill-color: transparent;
animation-duration: 2.5s;
animation-iteration-count: infinite;
animation-name: loading-shimmer;
background: var(--color-muted-foreground)
linear-gradient(
90deg,
var(--color-muted-foreground) 0%,
var(--color-shadcn-primary) 50%,
var(--color-muted-foreground) 100%
);
background-clip: text;
-webkit-background-clip: text;
background-repeat: no-repeat;
background-size: 50% 200%;
display: inline-block;
}
/* /*
The default border color has changed to `currentColor` in Tailwind CSS v4, The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still so we've added these compatibility styles to make sure everything still

@ -32,11 +32,11 @@ import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { ModificationFrame } from "./modification-frame"; import { ModificationFrame } from "./modification-frame";
export function ModificationButton() { export function ModificationButton({disabled}: {disabled?: boolean}) {
return ( return (
<Dialog> <Dialog>
<DialogTrigger> <DialogTrigger>
<Button>Filters & Sorting</Button> <Button disabled={disabled}>Filters & Sorting</Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="p-0 h-[600px] w-[1000px] !max-w-[800px] overflow-x-hidden"> <DialogContent className="p-0 h-[600px] w-[1000px] !max-w-[800px] overflow-x-hidden">

@ -36,13 +36,14 @@ import { Separator } from "@/components/ui/separator";
import { Statistics } from "./statistics"; import { Statistics } from "./statistics";
import InfiniteScroll from "react-infinite-scroll-component"; import InfiniteScroll from "react-infinite-scroll-component";
import { useInfiniteScrolling } from "@/lib/hooks/use-infinite-scrolling"; import { useInfiniteScrolling } from "@/lib/hooks/use-infinite-scrolling";
import { useMHSFServer } from "@/lib/hooks/use-mhsf-multiple";
import { ModificationButton } from "./modification/modification-button"; import { ModificationButton } from "./modification/modification-button";
import { useFilters } from "@/lib/hooks/use-filters"; import { useFilters } from "@/lib/hooks/use-filters";
import { ServerTestModeSelector } from "./server-test-mode-selector";
export function ServerList() { export function ServerList() {
const { servers, loading, serverCount, playerCount } = useServers(); const { servers, loading, serverCount, playerCount } = useServers();
const { filteredData } = useFilters(servers); const { filteredData, testModeEnabled, testModeLoading, testModeStatus } =
useFilters(servers);
const { itemsLength, fetchMoreData, hasMoreData, data } = const { itemsLength, fetchMoreData, hasMoreData, data } =
useInfiniteScrolling(filteredData); useInfiniteScrolling(filteredData);
@ -67,7 +68,14 @@ export function ServerList() {
<h1 className="scroll-m-20 text-2xl font-extrabold tracking-tight lg:text-4xl"> <h1 className="scroll-m-20 text-2xl font-extrabold tracking-tight lg:text-4xl">
Servers Servers
</h1> </h1>
<ModificationButton /> <div className="flex items-center">
<ModificationButton disabled={testModeEnabled} />
<ServerTestModeSelector
testModeStatus={testModeStatus}
testModeEnabled={testModeEnabled}
testModeLoading={testModeLoading}
/>
</div>
<InfiniteScroll <InfiniteScroll
dataLength={itemsLength} dataLength={itemsLength}
next={fetchMoreData} next={fetchMoreData}

@ -0,0 +1,36 @@
import { AnimatedText } from "@/components/ui/animated-text";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { useQueryState } from "nuqs";
export function ServerTestModeSelector({
testModeStatus,
testModeLoading,
testModeEnabled,
}: {
testModeStatus: string;
testModeLoading: boolean;
testModeEnabled: boolean;
}) {
const [tm] = useQueryState("tm", { defaultValue: "" });
if (testModeEnabled || tm !== "")
return (
<div className="pl-5 flex items-center gap-1">
<Tooltip>
<TooltipTrigger>
<span className="relative flex size-2.5 pt-[1px] items-center cursor-pointer">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-orange-400 opacity-75" />
<span className="relative inline-flex size-2.5 rounded-full bg-orange-500" />
</span></TooltipTrigger>
<TooltipContent>Test mode was enabled.</TooltipContent>
</Tooltip>
<AnimatedText
className="text-muted-foreground top-[2.5px] left-[6px] min-w-[70vw]"
text={testModeStatus}
glimmer
/>
</div>
);
return null;
}

@ -6,9 +6,10 @@ import { cn } from "@/lib/utils";
interface AnimatedTextProps { interface AnimatedTextProps {
text: string; text: string;
className?: string; className?: string;
glimmer?: boolean;
} }
export function AnimatedText({ text, className }: AnimatedTextProps) { export function AnimatedText({ text, className, glimmer }: AnimatedTextProps) {
const [currentText, setCurrentText] = useState(text); const [currentText, setCurrentText] = useState(text);
useEffect(() => { useEffect(() => {
@ -24,7 +25,9 @@ export function AnimatedText({ text, className }: AnimatedTextProps) {
animate={{ y: 0, opacity: 1 }} animate={{ y: 0, opacity: 1 }}
exit={{ y: -20, opacity: 0 }} exit={{ y: -20, opacity: 0 }}
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
className={cn("absolute left-0", className)} className={cn("absolute left-0", className, {
"loading-shimmer": glimmer,
})}
> >
{currentText} {currentText}
</motion.span> </motion.span>

@ -39,6 +39,8 @@ export function useFilters(data: OnlineServer[]) {
const [filteredData, setFilteredData] = useState<OnlineServer[]>(data); const [filteredData, setFilteredData] = useState<OnlineServer[]>(data);
const [t] = useQueryState("tm"); const [t] = useQueryState("tm");
const [testModeEnabled, setTestModeEnabled] = useState(false); const [testModeEnabled, setTestModeEnabled] = useState(false);
const [testModeStatus, setTestModeStatus] = useState("Haven't connected thread yet (if stuck, select the other tab, and come back)");
const [testModeLoading, setTestModeLoading] = useState(true);
useEffect(() => { useEffect(() => {
if (filteredData.length === 0) setFilteredData(data); if (filteredData.length === 0) setFilteredData(data);
@ -54,68 +56,91 @@ export function useFilters(data: OnlineServer[]) {
setTestModeEnabled(true); setTestModeEnabled(true);
const code = atob(t); const code = atob(t);
(async () => { (async () => {
toast.info("Transpiling TypeScript..."); setTestModeStatus("Transpiling TypeScript...");
const startTime = Date.now(); const startTime = Date.now();
const { error, data: transpiledCode } = await tryCatch( const { error, data: transpiledCode } = await tryCatch(
(async () => transpileTypeScript(code))() (async () => transpileTypeScript(code))()
); );
if (error) { if (error) {
toast.error( setTestModeStatus(
"Failed to transpile TypeScript! Error: " + error.message "Failed to transpile TypeScript! Error: " + error.message
); );
setTestModeLoading(false);
return; return;
} }
if (transpiledCode === null) { if (transpiledCode === null) {
toast.error("Cannot continue."); setTestModeStatus("Cannot continue.");
setTestModeLoading(false);
return; return;
} }
console.log( console.log(
"[MHSF Filters] Transpiled TypeScript:", "[MHSF Filters] Transpiled TypeScript:",
transpiledCode ?? "" transpiledCode ?? ""
); );
toast.info("Generating function..."); setTestModeStatus("Generating function...");
const functionBody = transpiledCode.match( if (
/function\s+filter\s*\([^)]*\)\s*\{([\s\S]*)\}/ !transpiledCode.includes("export default") &&
)?.[1]; !transpiledCode.includes("export")
) {
setTestModeStatus(
"Transpiled code does not contain any export statements."
);
setTestModeLoading(false);
return;
}
const functionBody = transpiledCode
.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( const { error: filterErr, data: filterFunc } = await tryCatch(
(async () => new Function("server", functionBody as string))() (async () =>
new Function(
"server",
`${functionBody}
return filter(server)`
))()
); );
if (filterErr) { if (filterErr) {
toast.error( setTestModeStatus(
`Failed to generate function! Error: ${filterErr.message}` `Failed to generate function! Error: ${filterErr.message}`
); );
setTestModeLoading(false);
return; return;
} }
if (typeof filterFunc === "function") { if (typeof filterFunc === "function") {
toast.success("Compiled in " + (Date.now() - startTime) + "ms"); setTestModeStatus(
"Compiled in " + (Date.now() - startTime) + "ms"
);
toast.promise( toast.promise(
async () => { async () => {
let newServers = []; let newServers = [];
newServers = data.filter((c) => filterFunc(c)); newServers = data.filter((c) => filterFunc(c));
toast.info( setTestModeStatus(
"Server count " + data.length + " -> " + newServers.length "Server count " + data.length + " -> " + newServers.length
); );
setFilteredData(() => [...newServers]); setFilteredData(() => [...newServers]);
setTestModeLoading(false);
}, },
{ {
loading: "Manipulating data...", loading: "Manipulating data...",
success: "Manipulated data; test mode finished!", success: "Manipulated data; test mode finished!",
error: (e) => error: (e) =>
`Error while manipulating data; go back to your editor and run again. ${es}`, `Error while manipulating data; go back to your editor and run again. ${e}`,
} }
); );
} else { } else {
toast.error( setTestModeStatus(
"Code doesn't have a 'filter' function. Cannot be tested." "Code doesn't have a 'filter' function. Cannot be tested."
); );
toast.error(typeof filterFunc); setTestModeLoading(false);
} }
})(); })();
} }
}); });
}, [t, data]); }, [t, data]);
console.log(filteredData); console.log(filteredData, testModeStatus);
return { filteredData, testModeEnabled }; return { filteredData, testModeEnabled, testModeLoading, testModeStatus };
} }

12
biome.json Normal file

@ -0,0 +1,12 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"linter": {
"rules": {
"style": {
"useImportType": {
"level": "warn"
}
}
}
}
}