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