feat: upgrade dm styles

This commit is contained in:
2023-05-11 15:25:01 +01:00
parent fdcf77ad55
commit 663c2ea433
19 changed files with 388 additions and 230 deletions

View File

@ -1,36 +1,3 @@
.dm-list {
overflow-y: auto;
overflow-x: hidden;
--header-height: 72px;
--profile-container-height: 58px;
--write-dm-input-height: 86px;
height: calc(100vh - var(--header-height) - var(--profile-container-height) - var(--write-dm-input-height));
}
.dm-list > div {
display: flex;
flex-direction: column;
margin-bottom: 10px;
scroll-padding-bottom: 40px;
}
.write-dm {
position: fixed;
bottom: 0;
background-color: var(--gray);
width: inherit;
border-radius: 5px 5px 0 0;
}
.write-dm .inner {
display: flex;
align-items: center;
padding: 10px 5px;
}
.write-dm textarea {
resize: none;
}
.write-dm-spacer {
margin-bottom: 80px;
.chat-page {
height: calc(100vh - 57px);
}

View File

@ -1,86 +1,15 @@
import "./ChatPage.css";
import { KeyboardEvent, useEffect, useMemo, useRef, useState } from "react";
import DmWindow from "Element/DmWindow";
import { useParams } from "react-router-dom";
import { FormattedMessage } from "react-intl";
import { TaggedRawEvent } from "@snort/nostr";
import ProfileImage from "Element/ProfileImage";
import { bech32ToHex } from "Util";
import useEventPublisher from "Feed/EventPublisher";
import DM from "Element/DM";
import { dmsForLogin, dmsInChat, isToSelf } from "Pages/MessagesPage";
import NoteToSelf from "Element/NoteToSelf";
import { useDmCache } from "Hooks/useDmsCache";
import useLogin from "Hooks/useLogin";
type RouterParams = {
id: string;
};
import "./ChatPage.css";
export default function ChatPage() {
const params = useParams<RouterParams>();
const publisher = useEventPublisher();
const id = bech32ToHex(params.id ?? "");
const pubKey = useLogin().publicKey;
const [content, setContent] = useState<string>();
const dmListRef = useRef<HTMLDivElement>(null);
const dms = useDmCache();
const sortedDms = useMemo(() => {
if (pubKey) {
const myDms = dmsForLogin(dms, pubKey);
// filter dms in this chat, or dms to self
const thisDms = id === pubKey ? myDms.filter(d => isToSelf(d, pubKey)) : myDms;
return [...dmsInChat(thisDms, id)].sort((a, b) => a.created_at - b.created_at);
}
return [];
}, [dms, pubKey]);
useEffect(() => {
if (dmListRef.current) {
dmListRef.current.scroll(0, dmListRef.current.scrollHeight);
}
}, [dmListRef.current?.scrollHeight]);
async function sendDm() {
if (content && publisher) {
const ev = await publisher.sendDm(content, id);
publisher.broadcast(ev);
setContent("");
}
}
async function onEnter(e: KeyboardEvent) {
const isEnter = e.code === "Enter";
if (isEnter && !e.shiftKey) {
await sendDm();
}
}
const { id } = useParams();
return (
<>
{(id === pubKey && <NoteToSelf className="f-grow mb-10" pubkey={id} />) || (
<ProfileImage pubkey={id} className="f-grow mb10" />
)}
<div className="dm-list" ref={dmListRef}>
<div>
{sortedDms.map(a => (
<DM data={a as TaggedRawEvent} key={a.id} />
))}
</div>
</div>
<div className="write-dm">
<div className="inner">
<textarea
className="f-grow mr10"
value={content}
onChange={e => setContent(e.target.value)}
onKeyDown={e => onEnter(e)}></textarea>
<button type="button" onClick={() => sendDm()}>
<FormattedMessage defaultMessage="Send" description="Send DM button" />
</button>
</div>
</div>
</>
<div className="chat-page">
<DmWindow id={bech32ToHex(id ?? "")} />
</div>
);
}

View File

@ -57,7 +57,8 @@ export default function Layout() {
}, [location]);
useEffect(() => {
if (location.pathname.startsWith("/login")) {
const widePage = ["/login", "/messages"];
if (widePage.some(a => location.pathname.startsWith(a))) {
setPageClass("");
} else {
setPageClass("page");
@ -162,7 +163,7 @@ export default function Layout() {
{!shouldHideNoteCreator && (
<>
<button className="note-create-button" type="button" onClick={handleNoteCreatorButtonClick}>
<button className="note-create-button" onClick={handleNoteCreatorButtonClick}>
<Icon name="plus" size={16} />
</button>
<NoteCreator />

View File

@ -0,0 +1,39 @@
.dm-page {
display: grid;
grid-template-columns: 350px auto;
height: calc(100vh - 57px);
/* 100vh - header - padding */
overflow: hidden;
}
/* These should match what is in code too */
@media (max-width: 768px) {
.dm-page {
grid-template-columns: 100vw;
}
.dm-page > div:nth-child(1) {
margin: 0 !important;
}
}
@media (min-width: 1500px) {
.dm-page {
grid-template-columns: 350px auto 350px;
}
}
/* User list */
.dm-page > div:nth-child(1) {
overflow-y: auto;
margin: 0 10px;
padding: 0 10px 0 0;
}
/* Chat window */
.dm-page > div:nth-child(2) {
height: calc(100vh - 57px);
}
/* Profile pannel */
.dm-page > div:nth-child(3) {
margin: 0 10px;
}

View File

@ -1,6 +1,7 @@
import { useMemo } from "react";
import { FormattedMessage } from "react-intl";
import { HexKey, RawEvent } from "@snort/nostr";
import React, { useMemo, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useNavigate } from "react-router-dom";
import { HexKey, RawEvent, NostrPrefix } from "@snort/nostr";
import UnreadCount from "Element/UnreadCount";
import ProfileImage from "Element/ProfileImage";
@ -9,9 +10,16 @@ import NoteToSelf from "Element/NoteToSelf";
import useModeration from "Hooks/useModeration";
import { useDmCache } from "Hooks/useDmsCache";
import useLogin from "Hooks/useLogin";
import usePageWidth from "Hooks/usePageWidth";
import NoteTime from "Element/NoteTime";
import DmWindow from "Element/DmWindow";
import "./MessagesPage.css";
import messages from "./messages";
const TwoCol = 768;
const ThreeCol = 1500;
type DmChat = {
pubkey: HexKey;
unreadMessages: number;
@ -21,7 +29,11 @@ type DmChat = {
export default function MessagesPage() {
const login = useLogin();
const { isMuted } = useModeration();
const { formatMessage } = useIntl();
const navigate = useNavigate();
const dms = useDmCache();
const [chat, setChat] = useState<string>();
const pageWidth = usePageWidth();
const chats = useMemo(() => {
if (login.publicKey) {
@ -35,25 +47,39 @@ export default function MessagesPage() {
const unreadCount = useMemo(() => chats.reduce((p, c) => p + c.unreadMessages, 0), [chats]);
function openChat(e: React.MouseEvent<HTMLDivElement>, pubkey: string) {
e.stopPropagation();
e.preventDefault();
if (pageWidth < TwoCol) {
navigate(`/messages/${hexToBech32(NostrPrefix.PublicKey, pubkey)}`);
} else {
setChat(pubkey);
}
}
function noteToSelf(chat: DmChat) {
return (
<div className="flex mb10" key={chat.pubkey}>
<NoteToSelf
clickable={true}
className="f-grow"
link={`/messages/${hexToBech32("npub", chat.pubkey)}`}
pubkey={chat.pubkey}
/>
<div className="flex mb10" key={chat.pubkey} onClick={e => openChat(e, chat.pubkey)}>
<NoteToSelf clickable={true} className="f-grow" link="" pubkey={chat.pubkey} />
</div>
);
}
function person(chat: DmChat) {
if (!login.publicKey) return null;
if (chat.pubkey === login.publicKey) return noteToSelf(chat);
return (
<div className="flex mb10" key={chat.pubkey}>
<ProfileImage pubkey={chat.pubkey} className="f-grow" link={`/messages/${hexToBech32("npub", chat.pubkey)}`} />
<UnreadCount unread={chat.unreadMessages} />
<div className="flex mb10" key={chat.pubkey} onClick={e => openChat(e, chat.pubkey)}>
<ProfileImage pubkey={chat.pubkey} className="f-grow" link="" />
<div className="nowrap">
<small>
<NoteTime
from={newestMessage(dms, login.publicKey, chat.pubkey) * 1000}
fallback={formatMessage({ defaultMessage: "Just now" })}
/>
</small>
{chat.unreadMessages > 0 && <UnreadCount unread={chat.unreadMessages} />}
</div>
</div>
);
}
@ -65,24 +91,28 @@ export default function MessagesPage() {
}
return (
<div className="main-content">
<div className="flex">
<h3 className="f-grow">
<FormattedMessage {...messages.Messages} />
</h3>
<button disabled={unreadCount <= 0} type="button" onClick={() => markAllRead()}>
<FormattedMessage {...messages.MarkAllRead} />
</button>
<div className="dm-page">
<div>
<div className="flex">
<h3 className="f-grow">
<FormattedMessage {...messages.Messages} />
</h3>
<button disabled={unreadCount <= 0} type="button" onClick={() => markAllRead()}>
<FormattedMessage {...messages.MarkAllRead} />
</button>
</div>
{chats
.sort((a, b) => {
return a.pubkey === login.publicKey
? -1
: b.pubkey === login.publicKey
? 1
: b.newestMessage - a.newestMessage;
})
.map(person)}
</div>
{chats
.sort((a, b) => {
return a.pubkey === login.publicKey
? -1
: b.pubkey === login.publicKey
? 1
: b.newestMessage - a.newestMessage;
})
.map(person)}
{pageWidth >= TwoCol && chat && <DmWindow id={chat} />}
{pageWidth >= ThreeCol && <div></div>}
</div>
);
}
@ -126,7 +156,7 @@ function unreadDms(dms: RawEvent[], myPubKey: HexKey, pk: HexKey) {
return dmsInChat(dms, pk).filter(a => a.created_at >= lastRead && a.pubkey !== myPubKey).length;
}
function newestMessage(dms: RawEvent[], myPubKey: HexKey, pk: HexKey) {
function newestMessage(dms: readonly RawEvent[], myPubKey: HexKey, pk: HexKey) {
if (pk === myPubKey) {
return dmsInChat(
dms.filter(d => isToSelf(d, myPubKey)),