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 {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
|
@ -7,100 +7,7 @@ import {
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "../ui/card";
|
} from "../ui/card";
|
||||||
import { fetchUsersById } from "@/lib/db";
|
import { fetchUsersById } from "@/lib/db";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
|
import { CompactMessage } from "./CompactMessage";
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function MessageContextCard({
|
export async function MessageContextCard({
|
||||||
snapshot,
|
snapshot,
|
||||||
|
@ -133,13 +40,13 @@ export async function MessageContextCard({
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="opacity-40">
|
<div className="opacity-40">
|
||||||
{[...(snapshot._prior_context ?? [])].reverse()?.map((message) => (
|
{[...(snapshot._prior_context ?? [])].reverse()?.map((message) => (
|
||||||
<Message key={message._id} message={message} users={users} />
|
<CompactMessage key={message._id} message={message} users={users} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<Message message={snapshot} users={users} />
|
<CompactMessage message={snapshot} users={users} />
|
||||||
<div className="opacity-40">
|
<div className="opacity-40">
|
||||||
{snapshot._leading_context?.map((message) => (
|
{snapshot._leading_context?.map((message) => (
|
||||||
<Message key={message._id} message={message} users={users} />
|
<CompactMessage key={message._id} message={message} users={users} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
@ -2,13 +2,22 @@ import Link from "next/link";
|
||||||
import { Report } from "revolt-api";
|
import { Report } from "revolt-api";
|
||||||
import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card";
|
import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card";
|
||||||
import { Badge } from "../ui/badge";
|
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 }) {
|
export function ReportCard({ report }: { report: Report }) {
|
||||||
return (
|
return (
|
||||||
<Link href={`/panel/reports/${report._id}`}>
|
<Link href={`/panel/reports/${report._id}`}>
|
||||||
<Card className="transition-all hover:-translate-y-1 hover:shadow-md">
|
<Card className="transition-all hover:-translate-y-1 hover:shadow-md">
|
||||||
<CardHeader>
|
<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") && (
|
{report.content.report_reason.includes("Illegal") && (
|
||||||
<Badge className="align-middle" variant="destructive">
|
<Badge className="align-middle" variant="destructive">
|
||||||
Urgent
|
Urgent
|
||||||
|
@ -18,7 +27,8 @@ export function ReportCard({ report }: { report: Report }) {
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
{report._id.toString().substring(20, 26)} ·{" "}
|
{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>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</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,38 +1,38 @@
|
||||||
import { User } from "revolt-api";
|
import { User } from "revolt-api";
|
||||||
import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card";
|
import { Card, CardDescription, CardHeader, CardTitle } from "../ui/card";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar";
|
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 }) {
|
export function UserCard({ user, subtitle }: { user: User; subtitle: string }) {
|
||||||
return (
|
return (
|
||||||
<Link href={`/panel/inspect/${user._id}`}>
|
<Card
|
||||||
<Card
|
className="shadow-none bg-no-repeat bg-right text-left"
|
||||||
className="shadow-none bg-no-repeat bg-right text-left"
|
style={{
|
||||||
style={{
|
backgroundImage: user.profile?.background
|
||||||
backgroundImage: user.profile?.background
|
? `linear-gradient(to right, white, rgba(255,0,0,0)), url('https://autumn.revolt.chat/backgrounds/${user.profile.background._id}')`
|
||||||
? `linear-gradient(to right, white, rgba(255,0,0,0)), url('https://autumn.revolt.chat/backgrounds/${user.profile.background._id}')`
|
: "",
|
||||||
: "",
|
backgroundSize: "75%",
|
||||||
backgroundSize: "75%",
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<CardHeader>
|
||||||
<CardHeader>
|
<CardTitle className="overflow-hidden overflow-ellipsis whitespace-nowrap">
|
||||||
<CardTitle>
|
<Avatar>
|
||||||
<Avatar>
|
<AvatarImage
|
||||||
<AvatarImage
|
src={`https://autumn.revolt.chat/avatars/${user.avatar?._id}`}
|
||||||
src={`https://autumn.revolt.chat/avatars/${user.avatar?._id}`}
|
/>
|
||||||
/>
|
<AvatarFallback className="overflow-hidden overflow-ellipsis whitespace-nowrap">
|
||||||
<AvatarFallback>
|
{(user.display_name ?? user.username)
|
||||||
{(user.display_name ?? user.username)
|
.split(" ")
|
||||||
.split(" ")
|
.slice(0, 2)
|
||||||
.slice(0, 2)
|
.map((x) => String.fromCodePoint(x.codePointAt(0) ?? 32) ?? "")
|
||||||
.join("")}
|
.join("")}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
{user.username}#{user.discriminator} {user.display_name}
|
{user.bot && <Badge className="align-middle">Bot</Badge>}{" "}
|
||||||
</CardTitle>
|
{user.username}#{user.discriminator} {user.display_name}
|
||||||
<CardDescription>{subtitle}</CardDescription>
|
</CardTitle>
|
||||||
</CardHeader>
|
<CardDescription>{subtitle}</CardDescription>
|
||||||
</Card>
|
</CardHeader>
|
||||||
</Link>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue