forked from administration/panel
feat: various cards
parent
ef53ec696f
commit
b45973482d
|
@ -0,0 +1,43 @@
|
|||
import { Channel } from "revolt-api";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
|
||||
import Link from "next/link";
|
||||
|
||||
export function ChannelCard({
|
||||
channel,
|
||||
subtitle,
|
||||
}: {
|
||||
channel: Channel;
|
||||
subtitle: string;
|
||||
}) {
|
||||
if (channel.channel_type === "SavedMessages")
|
||||
return <div>Refusing to render.</div>;
|
||||
|
||||
const name =
|
||||
channel.channel_type === "DirectMessage" ? "Direct Message" : channel.name;
|
||||
|
||||
return (
|
||||
<Card className="shadow-none">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Avatar>
|
||||
{channel.channel_type !== "DirectMessage" && (
|
||||
<AvatarImage
|
||||
src={`https://autumn.revolt.chat/icons/${channel.icon?._id}`}
|
||||
/>
|
||||
)}
|
||||
<AvatarFallback>
|
||||
{name
|
||||
.split(" ")
|
||||
.slice(0, 2)
|
||||
.map((x) => String.fromCodePoint(x.codePointAt(0) ?? 32) ?? "")
|
||||
.join("")}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
{name}
|
||||
</CardTitle>
|
||||
<CardDescription>{subtitle}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
"use client";
|
||||
|
||||
import { Message, User } from "revolt-api";
|
||||
import { Avatar, AvatarImage } from "../ui/avatar";
|
||||
import { Image as ImageIcon, Pencil } from "lucide-react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "../ui/alert-dialog";
|
||||
import dayjs from "dayjs";
|
||||
import { decodeTime } from "ulid";
|
||||
|
||||
import calendarPlugin from "dayjs/plugin/calendar";
|
||||
import Link from "next/link";
|
||||
dayjs.extend(calendarPlugin);
|
||||
|
||||
export function CompactMessage({
|
||||
message,
|
||||
users,
|
||||
hideUser,
|
||||
}: {
|
||||
message: Message;
|
||||
users?: Record<string, User>;
|
||||
hideUser?: boolean;
|
||||
}) {
|
||||
const user = users?.[message.author];
|
||||
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger className="w-full">
|
||||
<div
|
||||
className="flex gap-2"
|
||||
onMouseUp={(e) =>
|
||||
e.button === 1 &&
|
||||
window.open(`/panel/inspect/message/${message._id}`, "_blank")
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="flex-shrink-0 w-12 text-center"
|
||||
title={dayjs(decodeTime(message._id)).calendar()}
|
||||
>
|
||||
{dayjs(decodeTime(message._id)).format("HH:mm")}
|
||||
</div>
|
||||
{!hideUser && (
|
||||
<div
|
||||
className="flex-1 min-w-0 overflow-ellipsis overflow-hidden text-right"
|
||||
title={`${user?.username}#${user?.discriminator}`}
|
||||
>
|
||||
{user?.avatar && (
|
||||
<Avatar className="w-4 h-4 inline-block align-middle mr-1">
|
||||
<AvatarImage
|
||||
src={`https://autumn.revolt.chat/avatars/${user.avatar._id}`}
|
||||
/>
|
||||
</Avatar>
|
||||
)}
|
||||
{user?.username}{" "}
|
||||
{message.edited && (
|
||||
<Pencil size={12} className="inline align-middle" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-[2] min-w-0 overflow-ellipsis overflow-hidden text-left">
|
||||
{(message.attachments || message.embeds) && (
|
||||
<ImageIcon size={12} className="inline align-middle" />
|
||||
)}{" "}
|
||||
{message.content ?? <span className="text-gray-500">No text.</span>}
|
||||
</div>
|
||||
</div>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent className="max-h-[100vh] overflow-y-auto">
|
||||
<AlertDialogHeader className="min-w-0">
|
||||
<AlertDialogTitle>
|
||||
{user?.avatar && (
|
||||
<Avatar className="inline-block align-middle mr-1">
|
||||
<AvatarImage
|
||||
src={`https://autumn.revolt.chat/avatars/${user.avatar._id}`}
|
||||
/>
|
||||
</Avatar>
|
||||
)}{" "}
|
||||
{user?.username}#{user?.discriminator}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="flex flex-col gap-2 max-w-full">
|
||||
{message.content && <span>{message.content}</span>}
|
||||
{message.attachments?.map((attachment) =>
|
||||
attachment.metadata.type === "Image" ? (
|
||||
<a
|
||||
target="_blank"
|
||||
className="w-fit"
|
||||
key={attachment._id}
|
||||
href={`https://autumn.revolt.chat/attachments/${attachment._id}`}
|
||||
>
|
||||
<img
|
||||
className="max-h-[240px] object-contain"
|
||||
src={`https://autumn.revolt.chat/attachments/${attachment._id}`}
|
||||
/>
|
||||
</a>
|
||||
) : (
|
||||
"unsupported"
|
||||
)
|
||||
)}
|
||||
{message.embeds?.map((embed, index) => (
|
||||
<pre key={index} className="overflow-auto max-w-full">
|
||||
<code>{JSON.stringify(embed, null, 2)}</code>
|
||||
</pre>
|
||||
))}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<Link href={`/panel/inspect/message/${message._id}`}>
|
||||
<AlertDialogCancel>Inspect</AlertDialogCancel>
|
||||
</Link>
|
||||
<AlertDialogAction>Close</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../ui/card";
|
||||
import { Button } from "../ui/button";
|
||||
|
||||
export function JsonCard({ obj }: { obj: any }) {
|
||||
const [shown, setShown] = useState(false);
|
||||
|
||||
return (
|
||||
<Card className="shadow-none">
|
||||
<CardHeader>
|
||||
<CardTitle>Document</CardTitle>
|
||||
<CardDescription>Raw JSON representation</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{shown ? (
|
||||
<pre>
|
||||
<code>{JSON.stringify(obj, null, 2)}</code>
|
||||
</pre>
|
||||
) : (
|
||||
<Button variant="secondary" onClick={() => setShown(true)}>
|
||||
Show
|
||||
</Button>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { Message, SnapshotContent, User } from "revolt-api";
|
||||
import { SnapshotContent } from "revolt-api";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
|
@ -7,100 +7,7 @@ import {
|
|||
CardTitle,
|
||||
} from "../ui/card";
|
||||
import { fetchUsersById } from "@/lib/db";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
|
||||
import { Image as ImageIcon } from "lucide-react";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "../ui/alert-dialog";
|
||||
|
||||
function Message({
|
||||
message,
|
||||
users,
|
||||
}: {
|
||||
message: Message;
|
||||
users?: Record<string, User>;
|
||||
}) {
|
||||
const user = users?.[message.author];
|
||||
|
||||
return (
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger className="w-full">
|
||||
<div className="flex gap-2">
|
||||
<div
|
||||
className="flex-1 min-w-0 overflow-ellipsis overflow-hidden text-right"
|
||||
title={`${user?.username}#${user?.discriminator}`}
|
||||
>
|
||||
{user?.avatar && (
|
||||
<Avatar className="w-4 h-4 inline-block align-middle mr-1">
|
||||
<AvatarImage
|
||||
src={`https://autumn.revolt.chat/avatars/${user.avatar._id}`}
|
||||
/>
|
||||
</Avatar>
|
||||
)}
|
||||
{user?.username}
|
||||
</div>
|
||||
<div className="flex-[2] min-w-0 overflow-ellipsis overflow-hidden text-left">
|
||||
{(message.attachments || message.embeds) && (
|
||||
<ImageIcon size={16} className="inline align-middle" />
|
||||
)}{" "}
|
||||
{message.content ?? <span className="text-gray-500">No text.</span>}
|
||||
</div>
|
||||
</div>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent className="max-h-[100vh] overflow-y-auto">
|
||||
<AlertDialogHeader className="min-w-0">
|
||||
<AlertDialogTitle>
|
||||
{user?.avatar && (
|
||||
<Avatar className="inline-block align-middle mr-1">
|
||||
<AvatarImage
|
||||
src={`https://autumn.revolt.chat/avatars/${user.avatar._id}`}
|
||||
/>
|
||||
</Avatar>
|
||||
)}{" "}
|
||||
{user?.username}#{user?.discriminator}
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription className="flex flex-col gap-2 max-w-full">
|
||||
{message.content && <span>{message.content}</span>}
|
||||
{message.attachments?.map((attachment) =>
|
||||
attachment.metadata.type === "Image" ? (
|
||||
<a
|
||||
target="_blank"
|
||||
className="w-fit"
|
||||
key={attachment._id}
|
||||
href={`https://autumn.revolt.chat/attachments/${attachment._id}`}
|
||||
>
|
||||
<img
|
||||
className="max-h-[240px] object-contain"
|
||||
src={`https://autumn.revolt.chat/attachments/${attachment._id}`}
|
||||
/>
|
||||
</a>
|
||||
) : (
|
||||
"unsupported"
|
||||
)
|
||||
)}
|
||||
{message.embeds?.map((embed, index) => (
|
||||
<pre key={index} className="overflow-auto max-w-full">
|
||||
<code>{JSON.stringify(embed, null, 2)}</code>
|
||||
</pre>
|
||||
))}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Delete</AlertDialogCancel>
|
||||
<AlertDialogAction>Close</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
import { CompactMessage } from "./CompactMessage";
|
||||
|
||||
export async function MessageContextCard({
|
||||
snapshot,
|
||||
|
@ -133,13 +40,13 @@ export async function MessageContextCard({
|
|||
<CardContent>
|
||||
<div className="opacity-40">
|
||||
{[...(snapshot._prior_context ?? [])].reverse()?.map((message) => (
|
||||
<Message key={message._id} message={message} users={users} />
|
||||
<CompactMessage key={message._id} message={message} users={users} />
|
||||
))}
|
||||
</div>
|
||||
<Message message={snapshot} users={users} />
|
||||
<CompactMessage message={snapshot} users={users} />
|
||||
<div className="opacity-40">
|
||||
{snapshot._leading_context?.map((message) => (
|
||||
<Message key={message._id} message={message} users={users} />
|
||||
<CompactMessage key={message._id} message={message} users={users} />
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
|
|
|
@ -2,13 +2,22 @@ import Link from "next/link";
|
|||
import { Report } from "revolt-api";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card";
|
||||
import { Badge } from "../ui/badge";
|
||||
import dayjs from "dayjs";
|
||||
import { decodeTime } from "ulid";
|
||||
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
export function ReportCard({ report }: { report: Report }) {
|
||||
return (
|
||||
<Link href={`/panel/reports/${report._id}`}>
|
||||
<Card className="transition-all hover:-translate-y-1 hover:shadow-md">
|
||||
<CardHeader>
|
||||
<CardTitle className="overflow-ellipsis whitespace-nowrap overflow-x-clip">
|
||||
<CardTitle
|
||||
className={`overflow-ellipsis whitespace-nowrap overflow-x-clip ${
|
||||
report.status !== "Created" ? "text-gray-500" : ""
|
||||
}`}
|
||||
>
|
||||
{report.content.report_reason.includes("Illegal") && (
|
||||
<Badge className="align-middle" variant="destructive">
|
||||
Urgent
|
||||
|
@ -18,7 +27,8 @@ export function ReportCard({ report }: { report: Report }) {
|
|||
</CardTitle>
|
||||
<CardDescription>
|
||||
{report._id.toString().substring(20, 26)} ·{" "}
|
||||
{report.content.report_reason} · {report.content.type}
|
||||
{report.content.report_reason} · {report.content.type}{" "}
|
||||
· {dayjs(decodeTime(report._id)).fromNow()}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import { Server } from "revolt-api";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
|
||||
import Link from "next/link";
|
||||
|
||||
export function ServerCard({
|
||||
server,
|
||||
subtitle,
|
||||
}: {
|
||||
server: Server;
|
||||
subtitle: string;
|
||||
}) {
|
||||
return (
|
||||
<Card className="shadow-none">
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src={`https://autumn.revolt.chat/icons/${server.icon?._id}`}
|
||||
/>
|
||||
<AvatarFallback>
|
||||
{server.name
|
||||
.split(" ")
|
||||
.slice(0, 2)
|
||||
.map((x) => String.fromCodePoint(x.codePointAt(0) ?? 32) ?? "")
|
||||
.join("")}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
{server.name}
|
||||
</CardTitle>
|
||||
<CardDescription>{subtitle}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
);
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
import { User } from "revolt-api";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
|
||||
import Link from "next/link";
|
||||
import { Badge } from "../ui/badge";
|
||||
|
||||
export function UserCard({ user, subtitle }: { user: User; subtitle: string }) {
|
||||
return (
|
||||
<Link href={`/panel/inspect/${user._id}`}>
|
||||
<Card
|
||||
className="shadow-none bg-no-repeat bg-right text-left"
|
||||
style={{
|
||||
|
@ -16,23 +15,24 @@ export function UserCard({ user, subtitle }: { user: User; subtitle: string }) {
|
|||
}}
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle>
|
||||
<CardTitle className="overflow-hidden overflow-ellipsis whitespace-nowrap">
|
||||
<Avatar>
|
||||
<AvatarImage
|
||||
src={`https://autumn.revolt.chat/avatars/${user.avatar?._id}`}
|
||||
/>
|
||||
<AvatarFallback>
|
||||
<AvatarFallback className="overflow-hidden overflow-ellipsis whitespace-nowrap">
|
||||
{(user.display_name ?? user.username)
|
||||
.split(" ")
|
||||
.slice(0, 2)
|
||||
.map((x) => String.fromCodePoint(x.codePointAt(0) ?? 32) ?? "")
|
||||
.join("")}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
{user.bot && <Badge className="align-middle">Bot</Badge>}{" "}
|
||||
{user.username}#{user.discriminator} {user.display_name}
|
||||
</CardTitle>
|
||||
<CardDescription>{subtitle}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue