Add code dialogs
This commit is contained in:
parent
060e991741
commit
fb0b82edd3
75
Frontend/src/app/components/code/code-dialog.tsx
Normal file
75
Frontend/src/app/components/code/code-dialog.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import CodeHighlighter from "@/components/code/code-highlighter";
|
||||
import { ReactElement } from "react";
|
||||
|
||||
/**
|
||||
* Props for the code dialog.
|
||||
*/
|
||||
type CodeDialogProps = {
|
||||
/**
|
||||
* The title of this dialog.
|
||||
*/
|
||||
title: string;
|
||||
|
||||
/**
|
||||
* The description of this dialog.
|
||||
*/
|
||||
description: string;
|
||||
|
||||
/**
|
||||
* The language of the code, if any
|
||||
*/
|
||||
language: string | undefined;
|
||||
|
||||
/**
|
||||
* The trigger to open this dialog.
|
||||
*/
|
||||
trigger: ReactElement;
|
||||
|
||||
/**
|
||||
* The code in the dialog.
|
||||
*/
|
||||
children: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A dialog to display code.
|
||||
*
|
||||
* @param title the title of this dialog
|
||||
* @param description the description of this dialog
|
||||
* @param trigger the trigger to open this dialog
|
||||
* @param language the language of the code
|
||||
* @param children the children (code) to display
|
||||
* @return the dialog jsx
|
||||
*/
|
||||
const CodeDialog = ({
|
||||
title,
|
||||
description,
|
||||
language,
|
||||
trigger,
|
||||
children,
|
||||
}: CodeDialogProps) => (
|
||||
<Dialog>
|
||||
<DialogTrigger>{trigger}</DialogTrigger>
|
||||
<DialogContent className="max-w-none w-[90vw] lg:w-[85vw] xl:w-[80vw] 2xl:w-[70vw] max-h-[90vh]">
|
||||
{/* Header */}
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{/* Code */}
|
||||
<CodeHighlighter className="max-h-[70vh]" language={language}>
|
||||
{children}
|
||||
</CodeHighlighter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
export default CodeDialog;
|
@ -3,11 +3,17 @@ import SyntaxHighlighter from "react-syntax-highlighter";
|
||||
import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs";
|
||||
import Image from "next/image";
|
||||
import { capitalize } from "@/lib/string-utils";
|
||||
import { cn } from "@/lib";
|
||||
|
||||
/**
|
||||
* Props for the code highlighter.
|
||||
*/
|
||||
type CodeHighlighterProps = {
|
||||
/**
|
||||
* The class name for the code block.
|
||||
*/
|
||||
className?: string | undefined;
|
||||
|
||||
/**
|
||||
* The language of the code, if any
|
||||
*/
|
||||
@ -22,26 +28,33 @@ type CodeHighlighterProps = {
|
||||
/**
|
||||
* A code highlighter component.
|
||||
*
|
||||
* @param className the class name for the code block
|
||||
* @param language the language for syntax highlighting
|
||||
* @param children the children (code) to highlight
|
||||
* @return the highlighter jsx
|
||||
*/
|
||||
const CodeHighlighter = ({
|
||||
className,
|
||||
language,
|
||||
children,
|
||||
}: CodeHighlighterProps): ReactElement => {
|
||||
// Return a basic code block if no language is provided
|
||||
if (!language) {
|
||||
return (
|
||||
<code className="p-2 max-w-5xl block bg-muted/70 break-all rounded-lg">
|
||||
<code
|
||||
className={cn(
|
||||
"p-2 block bg-muted/70 break-all rounded-lg",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="relative max-w-5xl">
|
||||
<div className="relative">
|
||||
<SyntaxHighlighter
|
||||
className="!bg-muted/70 break-all rounded-lg"
|
||||
className={cn("!bg-muted/70 break-all rounded-lg", className)}
|
||||
language={language}
|
||||
style={atomOneDark}
|
||||
wrapLongLines
|
@ -10,10 +10,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import SyntaxHighlighter from "react-syntax-highlighter";
|
||||
import { atomOneDark } from "react-syntax-highlighter/dist/esm/styles/hljs";
|
||||
import Image from "next/image";
|
||||
import CodeHighlighter from "@/components/code-highlighter";
|
||||
import CodeHighlighter from "@/components/code/code-highlighter";
|
||||
|
||||
/**
|
||||
* The MDX components to style.
|
||||
@ -73,7 +70,10 @@ const components: any = {
|
||||
className: string;
|
||||
children: any;
|
||||
}): ReactElement => (
|
||||
<CodeHighlighter language={className?.replace("language-", "")}>
|
||||
<CodeHighlighter
|
||||
className="max-w-5xl"
|
||||
language={className?.replace("language-", "")}
|
||||
>
|
||||
{children}
|
||||
</CodeHighlighter>
|
||||
),
|
||||
|
@ -1,5 +1,4 @@
|
||||
import CopyButton from "@/components/copy-button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
@ -11,6 +10,8 @@ import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { ReactElement } from "react";
|
||||
import { CachedPlayer, SkinPart } from "restfulmc-lib";
|
||||
import CodeDialog from "@/components/code/code-dialog";
|
||||
import RawJsonBadge from "@/components/raw-json-badge";
|
||||
|
||||
/**
|
||||
* The props for a player result.
|
||||
@ -30,41 +31,35 @@ type PlayerResultProps = {
|
||||
/**
|
||||
* The result of a player search.
|
||||
*
|
||||
* @param query the original query to lookup this player
|
||||
* @param player the player to display
|
||||
* @returns the player result jsx
|
||||
*/
|
||||
const PlayerResult = ({
|
||||
query,
|
||||
player: {
|
||||
uniqueId,
|
||||
username,
|
||||
skin: { parts },
|
||||
legacy,
|
||||
},
|
||||
}: PlayerResultProps): ReactElement => (
|
||||
const PlayerResult = ({ query, player }: PlayerResultProps): ReactElement => (
|
||||
<ContextMenu>
|
||||
<ContextMenuTrigger>
|
||||
<div className="relative px-2 py-7 flex flex-col items-center bg-muted rounded-xl">
|
||||
{/* Raw Json */}
|
||||
<div className="absolute top-2 right-2">
|
||||
<Link
|
||||
href={`${config.apiEndpoint}/player/${username}`}
|
||||
target="_blank"
|
||||
<CodeDialog
|
||||
title="Raw Player Data"
|
||||
description={`The raw JSON data for the player ${player.username}:`}
|
||||
language="json"
|
||||
trigger={<RawJsonBadge />}
|
||||
>
|
||||
<Badge className="bg-minecraft-green-2 hover:bg-minecraft-green-2 hover:opacity-85 text-white font-semibold uppercase transition-all transform-gpu">
|
||||
Raw Json
|
||||
</Badge>
|
||||
</Link>
|
||||
{JSON.stringify(player, undefined, 4)}
|
||||
</CodeDialog>
|
||||
</div>
|
||||
|
||||
{/* Result */}
|
||||
<div className="w-full flex flex-col gap-3 justify-center items-center divide-y divide-zinc-700">
|
||||
{/* Details */}
|
||||
<div className="flex gap-5 items-center">
|
||||
{/* Player Head */}
|
||||
<Image
|
||||
className="w-24 h-24 sm:w-28 sm:h-28 md:w-32 md:h-32 pointer-events-none"
|
||||
src={parts.HEAD}
|
||||
alt={`${username}'s Head`}
|
||||
src={player.skin.parts.HEAD}
|
||||
alt={`${player.username}'s Head`}
|
||||
width={128}
|
||||
height={128}
|
||||
/>
|
||||
@ -72,14 +67,14 @@ const PlayerResult = ({
|
||||
{/* Name, Unique ID, and Badges */}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<h1 className="text-xl font-bold text-minecraft-green-3">
|
||||
{username}
|
||||
{player.username}
|
||||
</h1>
|
||||
<code className="text-xs xs:text-sm text-zinc-300">
|
||||
{uniqueId}
|
||||
{player.uniqueId}
|
||||
</code>
|
||||
|
||||
{/* Legacy Badge */}
|
||||
{legacy && (
|
||||
{player.legacy && (
|
||||
<p className="text-sm font-semibold uppercase">
|
||||
Legacy
|
||||
</p>
|
||||
@ -94,7 +89,7 @@ const PlayerResult = ({
|
||||
|
||||
{/* Skin Parts */}
|
||||
<div className="flex gap-5">
|
||||
{Object.entries(parts)
|
||||
{Object.entries(player.skin.parts)
|
||||
.filter(
|
||||
([part]) =>
|
||||
part === SkinPart.HEAD ||
|
||||
@ -114,7 +109,7 @@ const PlayerResult = ({
|
||||
<img
|
||||
className="h-20 sm:h-24 md:h-28 hover:scale-[1.02] transition-all transform-gpu"
|
||||
src={url}
|
||||
alt={`${username}'s ${part}`}
|
||||
alt={`${player.username}'s ${part}`}
|
||||
/>
|
||||
</Link>
|
||||
)
|
||||
@ -125,11 +120,11 @@ const PlayerResult = ({
|
||||
</div>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent className="flex flex-col">
|
||||
<CopyButton content={username} showToast>
|
||||
<CopyButton content={player.username} showToast>
|
||||
<ContextMenuItem>Copy Player Username</ContextMenuItem>
|
||||
</CopyButton>
|
||||
|
||||
<CopyButton content={uniqueId} showToast>
|
||||
<CopyButton content={player.uniqueId} showToast>
|
||||
<ContextMenuItem>Copy Player UUID</ContextMenuItem>
|
||||
</CopyButton>
|
||||
|
||||
|
14
Frontend/src/app/components/raw-json-badge.tsx
Normal file
14
Frontend/src/app/components/raw-json-badge.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { ReactElement } from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
/**
|
||||
* A badge for raw json.
|
||||
*
|
||||
* @returns the raw json badge jsx
|
||||
*/
|
||||
const RawJsonBadge = (): ReactElement => (
|
||||
<Badge className="bg-minecraft-green-2 hover:bg-minecraft-green-2 hover:opacity-85 text-white font-semibold uppercase transition-all transform-gpu">
|
||||
Raw Json
|
||||
</Badge>
|
||||
);
|
||||
export default RawJsonBadge;
|
@ -7,6 +7,8 @@ import {
|
||||
} from "restfulmc-lib";
|
||||
import config from "@/config";
|
||||
import { minecraft } from "@/font/fonts";
|
||||
import CodeDialog from "@/components/code/code-dialog";
|
||||
import RawJsonBadge from "@/components/raw-json-badge";
|
||||
|
||||
/**
|
||||
* The props for a server result.
|
||||
@ -28,58 +30,75 @@ const ServerResult = ({ server }: ServerResultProps): ReactElement => {
|
||||
const favicon: string | undefined = (server as CachedJavaMinecraftServer)
|
||||
.favicon?.url; // The favicon of the server
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"w-[37.5rem] relative p-1.5 flex gap-2 items-center pointer-events-none",
|
||||
`bg-[url("/media/dirt-background.png")] bg-center bg-repeat ${minecraft.className}`
|
||||
)}
|
||||
>
|
||||
{/* Favicon */}
|
||||
<Image
|
||||
className="h-16 w-16"
|
||||
src={favicon || `${config.apiEndpoint}/server/icon/fallback`}
|
||||
alt={`${server.hostname}'s Favicon`}
|
||||
width={64}
|
||||
height={64}
|
||||
/>
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* Result */}
|
||||
<div
|
||||
className={cn(
|
||||
"w-[37.5rem] relative p-1.5 flex gap-2 items-center pointer-events-none",
|
||||
`bg-[url("/media/dirt-background.png")] bg-center bg-repeat ${minecraft.className}`
|
||||
)}
|
||||
>
|
||||
{/* Favicon */}
|
||||
<Image
|
||||
className="h-16 w-16"
|
||||
src={
|
||||
favicon || `${config.apiEndpoint}/server/icon/fallback`
|
||||
}
|
||||
alt={`${server.hostname}'s Favicon`}
|
||||
width={64}
|
||||
height={64}
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<div className="-translate-y-1 w-full h-full flex flex-col gap-1">
|
||||
{/* Name & Ping */}
|
||||
<div className="w-full flex justify-between">
|
||||
<h1>{server.hostname}</h1>
|
||||
{/* Content */}
|
||||
<div className="-translate-y-1 w-full h-full flex flex-col gap-1">
|
||||
{/* Name & Ping */}
|
||||
<div className="w-full flex justify-between">
|
||||
<h1>{server.hostname}</h1>
|
||||
|
||||
{/* Players & Ping */}
|
||||
<div className="flex gap-1 items-center">
|
||||
<p className="text-[#AAAAAA]">
|
||||
{server.players.online}
|
||||
<span className="text-[#555555]">/</span>
|
||||
{server.players.max}
|
||||
</p>
|
||||
<Image
|
||||
src="/media/ping-full.png"
|
||||
alt="Ping!"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
{/* Players & Ping */}
|
||||
<div className="flex gap-1 items-center">
|
||||
<p className="text-[#AAAAAA]">
|
||||
{server.players.online}
|
||||
<span className="text-[#555555]">/</span>
|
||||
{server.players.max}
|
||||
</p>
|
||||
<Image
|
||||
src="/media/ping-full.png"
|
||||
alt="Ping!"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* MOTD */}
|
||||
<div className="w-full flex flex-col">
|
||||
{server.motd.html.map(
|
||||
(line: string, index: number): ReactElement => {
|
||||
return (
|
||||
<p
|
||||
key={index}
|
||||
className="leading-[1.15rem] pointer-events-auto"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: line,
|
||||
}}
|
||||
></p>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* MOTD */}
|
||||
<div className="w-full flex flex-col">
|
||||
{server.motd.html.map(
|
||||
(line: string, index: number): ReactElement => {
|
||||
return (
|
||||
<p
|
||||
key={index}
|
||||
className="leading-[1.15rem] pointer-events-auto"
|
||||
dangerouslySetInnerHTML={{ __html: line }}
|
||||
></p>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Raw Json */}
|
||||
<CodeDialog
|
||||
title="Raw Server Data"
|
||||
description={`The raw JSON data for the player ${server.hostname}:`}
|
||||
language="json"
|
||||
trigger={<RawJsonBadge />}
|
||||
>
|
||||
{JSON.stringify(server, undefined, 4)}
|
||||
</CodeDialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user