messages screen improvement
This commit is contained in:
parent
280321b902
commit
ae2cc3ba90
12 changed files with 438 additions and 111 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import { ArrowLeft } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { api, errorMessage } from "../api";
|
||||
import { MessageView } from "../components/MessageView";
|
||||
import { Alert, Badge, EmptyRow, JsonBlock, LoadingSurface, PageFrame, SectionHead, Summary } from "../components/ui";
|
||||
import { formatUnix } from "../lib/format";
|
||||
import type { Navigate } from "../routing";
|
||||
|
|
@ -38,32 +39,38 @@ export function GroupMessageDetailPage({ channelID, msgID, navigate }: { channel
|
|||
actions={<button className="btn icon-text" onClick={() => navigate("/messages/groups")}><ArrowLeft size={15} /> {"Back to group messages"}</button>}
|
||||
>
|
||||
<div className="stacked-sections">
|
||||
<section className="entity-head">
|
||||
<div>
|
||||
<div className="entity-title">{`Channel / Group ${msg.ChannelID}`}</div>
|
||||
<div className="entity-subtitle">{`Sender ${msg.SenderUserID} · ${formatUnix(msg.Date)}`}</div>
|
||||
</div>
|
||||
<div className="entity-badges">
|
||||
{msg.Deleted ? <Badge tone="danger">{"Deleted"}</Badge> : <Badge>{"Live"}</Badge>}
|
||||
{msg.Pinned && <Badge tone="warn">{"Pinned"}</Badge>}
|
||||
{msg.Post && <Badge>{"Channel post"}</Badge>}
|
||||
<Badge>pts {msg.PTS}</Badge>
|
||||
</div>
|
||||
</section>
|
||||
<MessageView
|
||||
body={msg.Body}
|
||||
media={msg.Media}
|
||||
sender={msg.Post ? `Channel post in ${msg.ChannelID}` : `From ${msg.SenderUserID}`}
|
||||
meta={`${formatUnix(msg.Date)}${msg.EditDate ? ` · edited ${formatUnix(msg.EditDate)}` : ""}${msg.ViewsCount ? ` · ${msg.ViewsCount} views` : ""}`}
|
||||
badges={
|
||||
<>
|
||||
{msg.Deleted ? <Badge tone="danger">{"Deleted"}</Badge> : <Badge>{"Live"}</Badge>}
|
||||
{msg.Pinned && <Badge tone="warn">{"Pinned"}</Badge>}
|
||||
{msg.Post && <Badge>{"Channel post"}</Badge>}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<div className="summary-grid">
|
||||
<Summary label={"Message ID"} value={String(msg.ID)} mono />
|
||||
<Summary label={"Channel / Group"} value={String(msg.ChannelID)} mono />
|
||||
<Summary label="From Peer" value={`${msg.FromPeerType}:${msg.FromPeerID}`} mono />
|
||||
<Summary label={"Views"} value={String(msg.ViewsCount)} />
|
||||
<Summary label={"pts"} value={String(msg.PTS)} mono />
|
||||
</div>
|
||||
<section className="section-block">
|
||||
<SectionHead title={"Channel Message Row"} text={"channel_messages read-only snapshot"} />
|
||||
<JsonBlock value={detail.MessageJSON} />
|
||||
</section>
|
||||
<section className="section-block">
|
||||
<SectionHead title={"Channel Row"} text={"channels read-only snapshot"} />
|
||||
<JsonBlock value={detail.ChannelJSON} />
|
||||
</section>
|
||||
<details className="raw-details">
|
||||
<summary>{"Stored rows (JSON)"}</summary>
|
||||
<div className="stacked-sections">
|
||||
<section className="section-block">
|
||||
<SectionHead title={"Channel Message Row"} text={"channel_messages read-only snapshot"} />
|
||||
<JsonBlock value={detail.MessageJSON} />
|
||||
</section>
|
||||
<section className="section-block">
|
||||
<SectionHead title={"Channel Row"} text={"channels read-only snapshot"} />
|
||||
<JsonBlock value={detail.ChannelJSON} />
|
||||
</section>
|
||||
</div>
|
||||
</details>
|
||||
<section className="section-block">
|
||||
<SectionHead title={"Channel Update Events"} text={"durable channel_update_events"} />
|
||||
<div className="table-wrap">
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import { ChevronRight, Search } from "lucide-react";
|
|||
import { useState } from "react";
|
||||
import { api, errorMessage } from "../api";
|
||||
import { ChannelPicker } from "../components/EntityPicker";
|
||||
import { Alert, Badge, EmptyRow, Metric, PageFrame, QueryPanel } from "../components/ui";
|
||||
import { Alert, Badge, EmptyRow, Metric, QueryPanel } from "../components/ui";
|
||||
import { channelKind, formatUnix } from "../lib/format";
|
||||
import type { Navigate } from "../routing";
|
||||
import type { ChannelRow, GroupMessageListResponse } from "../types";
|
||||
|
||||
export function GroupMessagesPage({ navigate }: { navigate: Navigate }) {
|
||||
export function GroupMessagesTab({ navigate }: { navigate: Navigate }) {
|
||||
const [channel, setChannel] = useState<ChannelRow | null>(null);
|
||||
const [beforeDate, setBeforeDate] = useState("");
|
||||
const [beforeID, setBeforeID] = useState("");
|
||||
|
|
@ -52,7 +52,7 @@ export function GroupMessagesPage({ navigate }: { navigate: Navigate }) {
|
|||
const rows = data?.rows ?? [];
|
||||
|
||||
return (
|
||||
<PageFrame title={"Group Messages"} eyebrow={"Supergroup / channel messages"}>
|
||||
<>
|
||||
{error && <Alert>{error}</Alert>}
|
||||
<QueryPanel>
|
||||
<div className="message-selector-grid single">
|
||||
|
|
@ -114,6 +114,6 @@ export function GroupMessagesPage({ navigate }: { navigate: Navigate }) {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</PageFrame>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { useEffect, useState } from "react";
|
|||
import { api, errorMessage } from "../api";
|
||||
import { ActionButton } from "../components/ActionButton";
|
||||
import { Alert, Badge, EmptyRow, JsonBlock, LoadingSurface, PageFrame, SectionHead, SplitLayout, Summary } from "../components/ui";
|
||||
import { MessageView } from "../components/MessageView";
|
||||
import { formatDate, formatUnix } from "../lib/format";
|
||||
import type { Navigate } from "../routing";
|
||||
import type { MessageDetail } from "../types";
|
||||
|
|
@ -41,37 +42,46 @@ export function MessageDetailPage({ ownerUserID, msgID, navigate }: { ownerUserI
|
|||
<SplitLayout
|
||||
main={
|
||||
<div className="stacked-sections">
|
||||
<section className="entity-head">
|
||||
<div>
|
||||
<div className="entity-title">{`Owner ${msg.OwnerUserID} · Peer ${msg.PeerID}`}</div>
|
||||
<div className="entity-subtitle">{`Sender ${msg.FromUserID} · ${formatUnix(msg.Date)}`}</div>
|
||||
</div>
|
||||
<div className="entity-badges">
|
||||
{msg.Deleted ? <Badge tone="danger">{"Deleted"}</Badge> : <Badge>{"Live"}</Badge>}
|
||||
<Badge>pts {msg.PTS}</Badge>
|
||||
<Badge>{msg.Outgoing ? "Outgoing" : "Incoming"}</Badge>
|
||||
</div>
|
||||
</section>
|
||||
<MessageView
|
||||
body={msg.Body}
|
||||
media={msg.Media}
|
||||
sender={`From ${msg.FromUserID}`}
|
||||
meta={`${msg.Outgoing ? "Sent to" : "Received from"} ${msg.PeerID} · ${formatUnix(msg.Date)}`}
|
||||
badges={
|
||||
<>
|
||||
{msg.Deleted ? <Badge tone="danger">{"Deleted"}</Badge> : <Badge>{"Live"}</Badge>}
|
||||
<Badge>{msg.Outgoing ? "Outgoing" : "Incoming"}</Badge>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<div className="summary-grid">
|
||||
<Summary label={"Message box ID"} value={String(msg.BoxID)} mono />
|
||||
<Summary label={"Private message ID"} value={String(msg.PrivateMessageID)} mono />
|
||||
<Summary label={"Message sender"} value={String(msg.MessageSenderID)} mono />
|
||||
<Summary label={"Time"} value={formatUnix(msg.Date)} />
|
||||
</div>
|
||||
<section className="section-block">
|
||||
<SectionHead title={"Message Box"} text={"message_boxes read-only snapshot"} />
|
||||
<JsonBlock value={detail.MessageJSON} />
|
||||
</section>
|
||||
<div className="raw-grid">
|
||||
<section className="section-block">
|
||||
<SectionHead title={"Dialog Row"} text={"dialogs read-only snapshot"} />
|
||||
<JsonBlock value={detail.DialogJSON} />
|
||||
</section>
|
||||
<section className="section-block">
|
||||
<SectionHead title={"Private Message Row"} text={"private_messages read-only snapshot"} />
|
||||
<JsonBlock value={detail.PrivateJSON} />
|
||||
</section>
|
||||
<Summary label={"pts"} value={String(msg.PTS)} mono />
|
||||
</div>
|
||||
{/* The stored rows stay reachable, but folded: they answer "why is
|
||||
this message in this state", which is a rarer question than
|
||||
"what does it say". */}
|
||||
<details className="raw-details">
|
||||
<summary>{"Stored rows (JSON)"}</summary>
|
||||
<div className="stacked-sections">
|
||||
<section className="section-block">
|
||||
<SectionHead title={"Message Box"} text={"message_boxes read-only snapshot"} />
|
||||
<JsonBlock value={detail.MessageJSON} />
|
||||
</section>
|
||||
<div className="raw-grid">
|
||||
<section className="section-block">
|
||||
<SectionHead title={"Dialog Row"} text={"dialogs read-only snapshot"} />
|
||||
<JsonBlock value={detail.DialogJSON} />
|
||||
</section>
|
||||
<section className="section-block">
|
||||
<SectionHead title={"Private Message Row"} text={"private_messages read-only snapshot"} />
|
||||
<JsonBlock value={detail.PrivateJSON} />
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
<section className="section-block">
|
||||
<SectionHead title={"Update Events"} text={"durable user_update_events"} />
|
||||
<div className="table-wrap">
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ import { Alert, Badge, EmptyRow, Metric, PageFrame, QueryPanel } from "../compon
|
|||
import { displayName, formatUnix, parseIDs, toInt } from "../lib/format";
|
||||
import type { Navigate } from "../routing";
|
||||
import type { AccountRow, MessageListResponse } from "../types";
|
||||
import { GroupMessagesTab } from "./GroupMessagesPage";
|
||||
|
||||
export function MessagesPage({ navigate }: { navigate: Navigate }) {
|
||||
export function PrivateMessagesTab({ navigate }: { navigate: Navigate }) {
|
||||
const [owner, setOwner] = useState<AccountRow | null>(null);
|
||||
const [peer, setPeer] = useState<AccountRow | null>(null);
|
||||
const [beforeDate, setBeforeDate] = useState("");
|
||||
|
|
@ -65,7 +66,7 @@ export function MessagesPage({ navigate }: { navigate: Navigate }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<PageFrame title={"Private Messages"} eyebrow={"Private message boxes"}>
|
||||
<>
|
||||
{error && <Alert>{error}</Alert>}
|
||||
<QueryPanel>
|
||||
<div className="message-selector-grid">
|
||||
|
|
@ -152,6 +153,42 @@ export function MessagesPage({ navigate }: { navigate: Navigate }) {
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// The two message stores are one screen with two tabs rather than two sidebar
|
||||
// entries: they are the same job ("look at what was said") over different peer
|
||||
// kinds, and a nested menu made that look like two unrelated sections.
|
||||
export function MessagesPage({ navigate, tab, onTab }: {
|
||||
navigate: Navigate;
|
||||
tab: "private" | "groups";
|
||||
onTab: (tab: "private" | "groups") => void;
|
||||
}) {
|
||||
return (
|
||||
<PageFrame title={"Messages"} eyebrow={"Message boxes and channel history"}>
|
||||
<div className="tab-bar" role="tablist" aria-label={"Message sections"}>
|
||||
<button
|
||||
className={`tab-btn ${tab === "private" ? "active" : ""}`}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={tab === "private"}
|
||||
onClick={() => onTab("private")}
|
||||
>
|
||||
{"Private"}
|
||||
</button>
|
||||
<button
|
||||
className={`tab-btn ${tab === "groups" ? "active" : ""}`}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={tab === "groups"}
|
||||
onClick={() => onTab("groups")}
|
||||
>
|
||||
{"Groups and channels"}
|
||||
</button>
|
||||
</div>
|
||||
{tab === "private" ? <PrivateMessagesTab navigate={navigate} /> : <GroupMessagesTab navigate={navigate} />}
|
||||
</PageFrame>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import { BotsPage } from "./BotsPage";
|
|||
import { BroadcastsPage } from "./BroadcastsPage";
|
||||
import { Dashboard } from "./Dashboard";
|
||||
import { GroupMessageDetailPage } from "./GroupMessageDetailPage";
|
||||
import { GroupMessagesPage } from "./GroupMessagesPage";
|
||||
import { MessageDetailPage } from "./MessageDetailPage";
|
||||
import { MessagesPage } from "./MessagesPage";
|
||||
import { StickerSetsPage } from "./StickerSetsPage";
|
||||
|
|
@ -159,11 +158,16 @@ export function Routes({ route, navigate }: { route: RouteState; navigate: Navig
|
|||
/>
|
||||
);
|
||||
}
|
||||
if (route.path === "/messages/groups") {
|
||||
return <GroupMessagesPage navigate={navigate} />;
|
||||
}
|
||||
if (route.path === "/messages" || route.path === "/messages/private") {
|
||||
return <MessagesPage navigate={navigate} />;
|
||||
// Both tabs keep their own path so a link to one still opens on it -- the
|
||||
// tab is a view of /messages, not a hidden bit of component state.
|
||||
if (route.path === "/messages" || route.path === "/messages/private" || route.path === "/messages/groups") {
|
||||
return (
|
||||
<MessagesPage
|
||||
navigate={navigate}
|
||||
tab={route.path === "/messages/groups" ? "groups" : "private"}
|
||||
onTab={(tab) => navigate(tab === "groups" ? "/messages/groups" : "/messages/private")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <Dashboard navigate={navigate} />;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue