chat layout
Some checks failed
continuous-integration/drone/pr Build is failing

This commit is contained in:
Martti Malmi 2023-11-27 11:45:04 +02:00
parent ecd5ea111d
commit ae73e2b383
10 changed files with 78 additions and 200 deletions

View File

@ -10,5 +10,12 @@ export function ChatParticipantProfile({ participant }: { participant: ChatParti
if (participant.id === publicKey) {
return <NoteToSelf className="grow" />;
}
return <ProfileImage pubkey={participant.id} className="grow" profile={participant.profile as MetadataCache} />;
return (
<ProfileImage
showNip05={false}
pubkey={participant.id}
className="grow"
profile={participant.profile as MetadataCache}
/>
);
}

View File

@ -1,37 +1,7 @@
.dm {
margin-top: 16px;
min-width: 100px;
max-width: 90%;
white-space: pre-wrap;
color: var(--font-color);
}
.dm a {
color: var(--font-color) !important;
}
.dm > div:last-child {
color: var(--gray-light);
font-size: small;
margin-top: 3px;
}
.dm.other > div:first-child {
padding: 12px 16px;
background: var(--gray-secondary);
border-radius: 16px 16px 16px 0px;
}
.dm.me {
align-self: flex-end;
}
.dm.me > div:first-child {
padding: 12px 16px;
.dm-gradient {
background: var(--dm-gradient);
border-radius: 16px 16px 0px 16px;
}
.dm.me > div:last-child {
text-align: end;
.other {
background: var(--gray-superdark);
}

View File

@ -1,4 +1,5 @@
import "./DM.css";
import { useEffect, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useInView } from "react-intersection-observer";
@ -55,8 +56,19 @@ export default function DM(props: DMProps) {
}, [inView]);
return (
<div className={isMe ? "dm me" : "dm other"} ref={ref}>
<div>
<div
className={
isMe
? "self-end mt-4 min-w-[100px] max-w-[90%] whitespace-pre-wrap align-self-end"
: "mt-4 min-w-[100px] max-w-[90%] whitespace-pre-wrap"
}
ref={ref}>
<div
className={
isMe
? "p-3 dm-gradient rounded-tl-lg rounded-tr-lg rounded-br-none"
: "p-3 bg-gray-300 rounded-tl-lg rounded-tr-lg rounded-br-lg rounded-bl-none other"
}>
{sender()}
{content ? (
<Text id={msg.id} content={content} tags={[]} creator={otherPubkey} />
@ -64,7 +76,7 @@ export default function DM(props: DMProps) {
<FormattedMessage defaultMessage="Loading..." id="gjBiyj" />
)}
</div>
<div>
<div className={isMe ? "text-end text-gray-400 text-sm mt-1" : "text-gray-400 text-sm mt-1"}>
<NoteTime from={msg.created_at * 1000} fallback={formatMessage(messages.JustNow)} />
</div>
</div>

View File

@ -1,32 +0,0 @@
.dm-window {
display: flex;
flex-direction: column;
height: calc(100vh - 62px);
}
.dm-window > div:nth-child(1) {
padding: 12px 0;
}
.dm-window > div:nth-child(2) {
overflow-y: auto;
padding: 0 10px 10px 10px;
flex-grow: 1;
display: flex;
flex-direction: column-reverse;
}
.dm-window > div:nth-child(3) {
display: flex;
align-items: center;
gap: 10px;
padding: 5px 10px;
}
.pfp-overlap .pfp:not(:last-of-type) {
margin-right: -20px;
}
.pfp-overlap .avatar {
width: 32px;
height: 32px;
}

View File

@ -1,6 +1,4 @@
import "./DmWindow.css";
import { useMemo } from "react";
import { useEffect, useMemo, useRef } from "react";
import ProfileImage from "@/Element/User/ProfileImage";
import DM from "@/Element/Chat/DM";
import useLogin from "@/Hooks/useLogin";
@ -18,7 +16,7 @@ export default function DmWindow({ id }: { id: string }) {
return <ChatParticipantProfile participant={chat.participants[0]} />;
} else {
return (
<div className="flex pfp-overlap mb10">
<div className="flex -space-x-5 mb-2.5">
{chat.participants.map(v => (
<ProfileImage pubkey={v.id} showUsername={false} />
))}
@ -29,12 +27,12 @@ export default function DmWindow({ id }: { id: string }) {
}
return (
<div className="dm-window">
<div>{sender()}</div>
<div>
<div className="flex flex-1 flex-col h-[calc(100vh-62px)] md:h-screen">
<div className="p-3">{sender()}</div>
<div className="overflow-y-auto hide-scrollbar p-2.5 flex-grow">
<div className="flex flex-col">{chat && <DmChatSelected chat={chat} />}</div>
</div>
<div className="flex g8">
<div className="flex items-center gap-2.5 p-2.5">
<WriteMessage chat={chat} />
</div>
</div>
@ -43,6 +41,9 @@ export default function DmWindow({ id }: { id: string }) {
function DmChatSelected({ chat }: { chat: Chat }) {
const { publicKey: myPubKey } = useLogin(s => ({ publicKey: s.publicKey }));
const messagesContainerRef = useRef<HTMLDivElement>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const sortedDms = useMemo(() => {
const myDms = chat?.messages;
if (myPubKey && myDms) {
@ -52,11 +53,37 @@ function DmChatSelected({ chat }: { chat: Chat }) {
return [];
}, [chat, myPubKey]);
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "instant" });
};
useEffect(() => {
const observer = new ResizeObserver(() => {
scrollToBottom();
});
// Start observing the element that you want to keep in view
if (messagesContainerRef.current) {
observer.observe(messagesContainerRef.current);
}
// Make sure to scroll to bottom on initial load
scrollToBottom();
// Clean up the observer on component unmount
return () => {
if (messagesContainerRef.current) {
observer.unobserve(messagesContainerRef.current);
}
};
}, [sortedDms]);
return (
<>
<div className="flex flex-col" ref={messagesContainerRef}>
{sortedDms.map(a => (
<DM data={a} key={a.id} chat={chat} />
))}
</>
<div ref={messagesEndRef} />
</div>
);
}

View File

@ -7,7 +7,7 @@
text-overflow: ellipsis;
white-space: pre-wrap;
display: inline;
overflow-wrap: break-word;
overflow-wrap: anywhere;
}
.text .text-frag > a {

View File

@ -20,6 +20,7 @@ export interface ProfileImageProps {
link?: string;
defaultNip?: string;
verifyNip?: boolean;
showNip05?: boolean;
overrideUsername?: string;
profile?: UserMetadata;
size?: number;
@ -38,6 +39,7 @@ export default function ProfileImage({
link,
defaultNip,
verifyNip,
showNip05 = true,
overrideUsername,
profile,
size,
@ -93,7 +95,7 @@ export default function ProfileImage({
<div className="f-ellipsis">
<div className="flex g4 username">
{overrideUsername ? overrideUsername : <DisplayName pubkey={pubkey} user={user} />}
{nip05 && <Nip05 nip05={nip05} pubkey={pubkey} verifyNip={verifyNip} />}
{showNip05 && nip05 && <Nip05 nip05={nip05} pubkey={pubkey} verifyNip={verifyNip} />}
</div>
<div className="subheader">{subHeader}</div>
</div>

View File

@ -38,7 +38,10 @@ export default function ProfilePreview(props: ProfilePreviewProps) {
return (
<>
<div className={`profile-preview${props.className ? ` ${props.className}` : ""}`} ref={ref} onClick={handleClick}>
<div
className={`justify-between profile-preview${props.className ? ` ${props.className}` : ""}`}
ref={ref}
onClick={handleClick}>
{inView && (
<>
<ProfileImage

View File

@ -1,109 +0,0 @@
.dm-page {
display: grid;
grid-template-columns: 350px auto;
height: 100vh;
/* 100vh - header - padding */
overflow: hidden;
padding: 4px;
}
.dm-page > div:nth-child(1)::-webkit-scrollbar-track {
background: transparent !important;
}
/* These should match what is in code too */
@media (max-width: 768px) {
.dm-page {
grid-template-columns: 100vw;
height: calc(100vh - 62px);
}
.dm-page > div:nth-child(1) {
margin: 0 !important;
}
}
@media (min-width: 1500px) {
.dm-page {
grid-template-columns: 400px auto 400px;
}
}
/* User list */
.dm-page > div:nth-child(1) {
overflow-y: auto;
padding: 0 5px;
}
/* Chat window */
.dm-page > div:nth-child(2) {
padding: 0 12px;
margin: 0 4px;
height: 100vh;
background-color: var(--gray-superdark);
border-radius: 16px;
}
/* Profile pannel */
.dm-page > div:nth-child(3) {
margin: 16px;
}
.dm-page > div:nth-child(3) .avatar {
margin-left: auto;
margin-right: auto;
}
.dm-page > div:nth-child(3) .card {
cursor: pointer;
}
.dm-page .new-chat {
min-width: 100px;
}
.dm-page .chat-list > div.active {
background-color: var(--gray-superdark);
border-radius: 16px;
}
.new-chat-modal .user-list {
max-height: 50vh;
overflow-y: auto;
}
.new-chat-modal .user-list > div {
padding: 8px 12px;
cursor: pointer;
}
/* user in list selected */
.new-chat-modal .user-list > div.active {
background-color: var(--gray-dark);
border-radius: 16px;
}
.new-chat-modal .modal-body {
padding: 24px 32px;
}
.new-chat-modal h2,
.new-chat-modal h3,
.new-chat-modal p {
font-weight: 600;
margin: 0;
}
.new-chat-modal h2 {
font-size: 21px;
}
.new-chat-modal h3 {
font-size: 16px;
}
.new-chat-modal p {
font-size: 11px;
letter-spacing: 1.21px;
text-transform: uppercase;
}

View File

@ -1,5 +1,3 @@
import "./MessagesPage.css";
import React, { useEffect, useMemo, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useNavigate, useParams } from "react-router-dom";
@ -74,7 +72,7 @@ export default function MessagesPage() {
const isActive = cx.id === chat;
return (
<div
className={classNames("flex items-center p cursor-pointer", { active: isActive })}
className={classNames("flex items-center p cursor-pointer justify-between", { active: isActive })}
key={cx.id}
onClick={e => openChat(e, cx.type, cx.id)}>
{conversationIdent(cx)}
@ -92,11 +90,11 @@ export default function MessagesPage() {
}
return (
<div className="dm-page">
<div className="flex flex-1 h-screen overflow-hidden">
{(pageWidth >= TwoCol || !chat) && (
<div className="chat-list">
<div className="flex items-center p justify-between">
<button disabled={unreadCount <= 0} type="button">
<div className="overflow-y-auto h-screen p-1 w-full md:w-1/3 flex-shrink-0">
<div className="flex items-center justify-between p-2">
<button disabled={unreadCount <= 0} type="button" className="text-sm font-semibold">
<FormattedMessage defaultMessage="Mark all read" id="ShdEie" />
</button>
<NewChatWindow />
@ -113,9 +111,9 @@ export default function MessagesPage() {
.map(conversation)}
</div>
)}
{chat ? <DmWindow id={chat} /> : pageWidth >= TwoCol && <div></div>}
{chat ? <DmWindow id={chat} /> : pageWidth >= TwoCol && <div className="flex-1"></div>}
{pageWidth >= ThreeCol && chat && (
<div>
<div className="m-4">
<ProfileDmActions id={chat} />
</div>
)}