feat(web): refine media gallery UX in chat info
Some checks failed
CI / test (push) Failing after 18s
Some checks failed
CI / test (push) Failing after 18s
- add per-tab counters and sticky media tabs - normalize media ordering by newest first - improve links tab readability with short host/path preview
This commit is contained in:
@@ -63,11 +63,14 @@ export function ChatInfoPanel({ chatId, open, onClose }: Props) {
|
||||
const isGroupLike = chat?.type === "group" || chat?.type === "channel";
|
||||
const showMembersSection = Boolean(chat && isGroupLike && !chat.is_saved);
|
||||
const canManageMembers = Boolean(isGroupLike && (myRole === "owner" || myRole === "admin"));
|
||||
const photoAttachments = useMemo(() => attachments.filter((item) => item.file_type.startsWith("image/")), [attachments]);
|
||||
const videoAttachments = useMemo(() => attachments.filter((item) => item.file_type.startsWith("video/")), [attachments]);
|
||||
const voiceAttachments = useMemo(() => attachments.filter((item) => item.message_type === "voice"), [attachments]);
|
||||
const photoAttachments = useMemo(() => attachments.filter((item) => item.file_type.startsWith("image/")).sort((a, b) => b.id - a.id), [attachments]);
|
||||
const videoAttachments = useMemo(() => attachments.filter((item) => item.file_type.startsWith("video/")).sort((a, b) => b.id - a.id), [attachments]);
|
||||
const voiceAttachments = useMemo(() => attachments.filter((item) => item.message_type === "voice").sort((a, b) => b.id - a.id), [attachments]);
|
||||
const audioAttachments = useMemo(
|
||||
() => attachments.filter((item) => item.message_type === "audio" || (item.file_type.startsWith("audio/") && item.message_type !== "voice")),
|
||||
() =>
|
||||
attachments
|
||||
.filter((item) => item.message_type === "audio" || (item.file_type.startsWith("audio/") && item.message_type !== "voice"))
|
||||
.sort((a, b) => b.id - a.id),
|
||||
[attachments]
|
||||
);
|
||||
const allAttachmentItems = useMemo(() => [...attachments].sort((a, b) => b.id - a.id), [attachments]);
|
||||
@@ -386,48 +389,48 @@ export function ChatInfoPanel({ chatId, open, onClose }: Props) {
|
||||
<p className="mb-2 text-xs font-semibold uppercase tracking-wide text-slate-300">
|
||||
Media & Files {attachments.length > 0 ? `(${attachments.length})` : ""}
|
||||
</p>
|
||||
<div className="mb-2 flex items-center gap-2 border-b border-slate-700/60 pb-2 text-xs">
|
||||
<div className="sticky top-0 z-10 mb-2 flex items-center gap-2 border-b border-slate-700/60 bg-slate-800/90 pb-2 pt-1 text-xs backdrop-blur">
|
||||
<button
|
||||
className={`rounded px-2 py-1 ${attachmentsTab === "all" ? "bg-sky-500/30 text-sky-200" : "text-slate-300"}`}
|
||||
onClick={() => setAttachmentsTab("all")}
|
||||
type="button"
|
||||
>
|
||||
All
|
||||
All ({attachments.length})
|
||||
</button>
|
||||
<button
|
||||
className={`rounded px-2 py-1 ${attachmentsTab === "photos" ? "bg-sky-500/30 text-sky-200" : "text-slate-300"}`}
|
||||
onClick={() => setAttachmentsTab("photos")}
|
||||
type="button"
|
||||
>
|
||||
Photos
|
||||
Photos ({photoAttachments.length})
|
||||
</button>
|
||||
<button
|
||||
className={`rounded px-2 py-1 ${attachmentsTab === "videos" ? "bg-sky-500/30 text-sky-200" : "text-slate-300"}`}
|
||||
onClick={() => setAttachmentsTab("videos")}
|
||||
type="button"
|
||||
>
|
||||
Videos
|
||||
Videos ({videoAttachments.length})
|
||||
</button>
|
||||
<button
|
||||
className={`rounded px-2 py-1 ${attachmentsTab === "audio" ? "bg-sky-500/30 text-sky-200" : "text-slate-300"}`}
|
||||
onClick={() => setAttachmentsTab("audio")}
|
||||
type="button"
|
||||
>
|
||||
Audio
|
||||
Audio ({audioAttachments.length})
|
||||
</button>
|
||||
<button
|
||||
className={`rounded px-2 py-1 ${attachmentsTab === "voice" ? "bg-sky-500/30 text-sky-200" : "text-slate-300"}`}
|
||||
onClick={() => setAttachmentsTab("voice")}
|
||||
type="button"
|
||||
>
|
||||
Voice
|
||||
Voice ({voiceAttachments.length})
|
||||
</button>
|
||||
<button
|
||||
className={`rounded px-2 py-1 ${attachmentsTab === "links" ? "bg-sky-500/30 text-sky-200" : "text-slate-300"}`}
|
||||
onClick={() => setAttachmentsTab("links")}
|
||||
type="button"
|
||||
>
|
||||
Links
|
||||
Links ({linkItems.length})
|
||||
</button>
|
||||
</div>
|
||||
{attachmentsLoading ? <p className="text-xs text-slate-400">Loading attachments...</p> : null}
|
||||
@@ -518,8 +521,9 @@ export function ChatInfoPanel({ chatId, open, onClose }: Props) {
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<p className="truncate text-xs font-semibold text-sky-300">{item.url}</p>
|
||||
<p className="text-[11px] text-slate-400">{new Date(item.createdAt).toLocaleString()}</p>
|
||||
<p className="truncate text-xs font-semibold text-sky-300">{shortLink(item.url)}</p>
|
||||
<p className="truncate text-[11px] text-slate-400">{item.url}</p>
|
||||
<p className="text-[11px] text-slate-500">{new Date(item.createdAt).toLocaleString()}</p>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -821,3 +825,12 @@ function extractLinkItems(messages: Message[]): Array<{ url: string; messageId:
|
||||
out.sort((a, b) => b.messageId - a.messageId);
|
||||
return out;
|
||||
}
|
||||
|
||||
function shortLink(url: string): string {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
return `${parsed.hostname}${parsed.pathname === "/" ? "" : parsed.pathname}`;
|
||||
} catch {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user