mirror of
https://github.com/irislib/iris-messenger.git
synced 2024-10-18 14:13:21 +00:00
chat layout
This commit is contained in:
parent
2c10597009
commit
2f05584995
@ -21,6 +21,7 @@ abstract class View extends Component {
|
||||
observer: ResizeObserver | null = null;
|
||||
scrollPosition = 0;
|
||||
hideSideBar = false;
|
||||
hideHeader = false;
|
||||
|
||||
abstract renderView(): JSX.Element;
|
||||
|
||||
@ -28,7 +29,9 @@ abstract class View extends Component {
|
||||
return (
|
||||
<div className="flex flex-row h-full w-full">
|
||||
<div className={`flex flex-col w-full h-full ${this.hideSideBar ? '' : 'lg:w-2/3'}`}>
|
||||
<Header />
|
||||
<Show when={!this.hideHeader}>
|
||||
<Header />
|
||||
</Show>
|
||||
<div class={this.class} id={this.id} className="h-full">
|
||||
<ErrorBoundary>{this.renderView()}</ErrorBoundary>
|
||||
</div>
|
||||
|
@ -6,6 +6,7 @@ import View from '../View';
|
||||
|
||||
import ChatList from './ChatList';
|
||||
import ChatMessages from './ChatMessages';
|
||||
import Header from './Header';
|
||||
import NewChat, { addChatWithInputKey } from './NewChat';
|
||||
|
||||
class Chat extends View {
|
||||
@ -15,6 +16,7 @@ class Chat extends View {
|
||||
super();
|
||||
this.id = 'chat-view';
|
||||
this.hideSideBar = true;
|
||||
this.hideHeader = true;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -50,9 +52,18 @@ class Chat extends View {
|
||||
const { id } = this.props;
|
||||
|
||||
return (
|
||||
<div className="flex flex-row h-full">
|
||||
<ChatList activeChat={id} className={id ? 'hidden md:flex' : 'flex'} />
|
||||
{this.renderContent(id)}
|
||||
<div className="flex flex-col h-screen">
|
||||
<Header />
|
||||
<div className="flex flex-row flex-grow overflow-hidden">
|
||||
<div
|
||||
className={`flex-shrink-0 ${
|
||||
id ? 'hidden md:flex overflow-y-auto h-screen' : 'flex overflow-y-auto h-screen'
|
||||
}`}
|
||||
>
|
||||
<ChatList activeChat={id} className={id ? 'hidden md:flex' : 'flex'} />
|
||||
</div>
|
||||
<div className="flex-grow min-h-screen overflow-y-scroll">{this.renderContent(id)}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ const ChatList = ({ activeChat, className }) => {
|
||||
|
||||
return (
|
||||
<section
|
||||
className={`border-r border-neutral-800 overflow-x-hidden overflow-y-auto h-full px-4 md:px-0 w-full md:w-64 ${className}`}
|
||||
className={`border-r border-neutral-800 overflow-x-hidden overflow-y-auto h-full md:px-0 w-full md:w-64 ${className}`}
|
||||
>
|
||||
<div id="enable-notifications-prompt" className="hidden" onClick={enableDesktopNotifications}>
|
||||
<div className="title">{t('get_notified_new_messages')}</div>
|
||||
|
@ -146,7 +146,7 @@ function ChatMessages({ id }) {
|
||||
|
||||
mainView = (
|
||||
<div
|
||||
className="main-view p-2 overflow-y-auto overflow-x-hidden flex-grow"
|
||||
className="main-view p-2 overflow-y-auto overflow-x-hidden flex-grow min-h-screen"
|
||||
id="message-view"
|
||||
onScroll={() => onMessageViewScroll()}
|
||||
>
|
||||
|
212
src/js/views/chat/Header.tsx
Normal file
212
src/js/views/chat/Header.tsx
Normal file
@ -0,0 +1,212 @@
|
||||
import { Cog8ToothIcon, HeartIcon } from '@heroicons/react/24/outline';
|
||||
import { ArrowLeftIcon, HeartIcon as HeartIconFull } from '@heroicons/react/24/solid';
|
||||
import { route } from 'preact-router';
|
||||
|
||||
import Component from '../../BaseComponent';
|
||||
import Show from '../../components/helpers/Show';
|
||||
import SearchBox from '../../components/SearchBox';
|
||||
import Name from '../../components/user/Name';
|
||||
import Icons from '../../Icons';
|
||||
import localState from '../../LocalState';
|
||||
import Key from '../../nostr/Key';
|
||||
import Relays from '../../nostr/Relays';
|
||||
import { translate as t } from '../../translations/Translation.mjs';
|
||||
|
||||
export default class Header extends Component {
|
||||
userId = null as string | null;
|
||||
iv = null as any;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = { latest: {} };
|
||||
this.escFunction = this.escFunction.bind(this);
|
||||
}
|
||||
|
||||
escFunction(event) {
|
||||
if (event.keyCode === 27) {
|
||||
this.state.showMobileSearch && this.setState({ showMobileSearch: false });
|
||||
}
|
||||
}
|
||||
|
||||
backButtonClicked() {
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
super.componentWillUnmount();
|
||||
this.iv && clearInterval(this.iv);
|
||||
document.removeEventListener('keydown', this.escFunction, false);
|
||||
}
|
||||
|
||||
setTitle(activeRoute: string) {
|
||||
let title: any = activeRoute.split('/')[1] || t('home');
|
||||
if (title.startsWith('note')) {
|
||||
title = t('post');
|
||||
} else if (title.startsWith('npub')) {
|
||||
this.userId = title;
|
||||
title = <Name key={`${this.userId}title`} pub={this.userId || ''} />;
|
||||
} else {
|
||||
title = title.charAt(0).toUpperCase() + title.slice(1);
|
||||
}
|
||||
|
||||
const replaced = activeRoute.replace('/chat/new', '').replace('/chat/', '');
|
||||
|
||||
if (activeRoute.indexOf('/chat/') === 0 && activeRoute.indexOf('/chat/new') !== 0) {
|
||||
this.userId = replaced.length < activeRoute.length ? replaced : null;
|
||||
if (activeRoute.indexOf('/chat/') === 0 && this.userId === Key.getPubKey()) {
|
||||
title = (
|
||||
<>
|
||||
<b className="mr-5">📝</b> <b>{t('note_to_self')}</b>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
title = <Name key={`${this.userId}title`} pub={this.userId || ''} />;
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ title });
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('keydown', this.escFunction, false);
|
||||
localState.get('showParticipants').on(this.inject());
|
||||
localState.get('unseenMsgsTotal').on(this.inject());
|
||||
localState.get('unseenNotificationCount').on(this.inject());
|
||||
localState.get('showConnectedRelays').on(this.inject());
|
||||
localState.get('isMyProfile').on(this.inject());
|
||||
localState.get('activeRoute').on(
|
||||
this.sub((activeRoute) => {
|
||||
this.setState({
|
||||
about: null,
|
||||
activeRoute,
|
||||
showMobileSearch: false,
|
||||
});
|
||||
this.setTitle(activeRoute);
|
||||
}),
|
||||
);
|
||||
this.updateRelayCount();
|
||||
this.iv = setInterval(() => this.updateRelayCount(), 1000);
|
||||
}
|
||||
|
||||
onTitleClicked() {
|
||||
window.scrollTo(0, 0);
|
||||
if (this.userId && this.userId.indexOf('hashtag') === -1) {
|
||||
route('/' + this.userId);
|
||||
}
|
||||
}
|
||||
|
||||
updateRelayCount() {
|
||||
this.setState({ connectedRelays: Relays.getConnectedRelayCount() });
|
||||
}
|
||||
|
||||
renderSearchBox() {
|
||||
return !this.userId ? <SearchBox onSelect={(item) => route(`/${item.key}`)} /> : '';
|
||||
}
|
||||
|
||||
renderConnectedRelays() {
|
||||
return (
|
||||
<a
|
||||
href="/settings/network"
|
||||
className={`ml-2 tooltip tooltip-bottom mobile-search-hidden ${
|
||||
this.state.showMobileSearch ? 'hidden-xs' : ''
|
||||
} ${this.state.connectedRelays > 0 ? 'connected' : ''}`}
|
||||
data-tip={t('connected_relays')}
|
||||
>
|
||||
<small className="flex items-center gap-2">
|
||||
<span class="icon">{Icons.network}</span>
|
||||
<span>{this.state.connectedRelays}</span>
|
||||
</small>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
renderTitle() {
|
||||
const isHome = this.state.activeRoute === '/';
|
||||
return (
|
||||
<div
|
||||
className={`flex-1 text-center ${isHome ? 'invisible md:visible' : ''}`}
|
||||
onClick={() => this.onTitleClicked()}
|
||||
>
|
||||
{this.state.title}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderNotifications() {
|
||||
return (
|
||||
<>
|
||||
<Show when={this.state.isMyProfile}>
|
||||
<a href="/settings" className="md:hidden">
|
||||
<Cog8ToothIcon width={28} />
|
||||
</a>
|
||||
</Show>
|
||||
<a
|
||||
href="/notifications"
|
||||
className={`relative inline-block ${this.state.isMyProfile ? 'hidden md:flex' : ''}`}
|
||||
>
|
||||
{this.state.activeRoute === '/notifications' ? (
|
||||
<HeartIconFull width={28} />
|
||||
) : (
|
||||
<HeartIcon width={28} />
|
||||
)}
|
||||
<Show when={this.state.unseenNotificationCount}>
|
||||
<span className="absolute top-0 right-0 transform translate-x-1/2 -translate-y-1/2 bg-iris-purple text-white text-sm rounded-full h-5 w-5 flex items-center justify-center">
|
||||
{this.state.unseenNotificationCount > 99 ? '' : this.state.unseenNotificationCount}
|
||||
</span>
|
||||
</Show>
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
renderLoginBtns() {
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={() => localState.get('showLoginModal').put(true)}
|
||||
>
|
||||
{t('log_in')}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-neutral"
|
||||
onClick={() => localState.get('showLoginModal').put(true)}
|
||||
>
|
||||
{t('sign_up')}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderBackBtnOrLogo() {
|
||||
const isHome = this.state.activeRoute === '/';
|
||||
return isHome ? (
|
||||
<div className="flex flex-row items-center gap-2 md:hidden">
|
||||
<img src="/img/icon128.png" width="30" height="30" />
|
||||
<h1 className=" text-3xl">iris</h1>
|
||||
</div>
|
||||
) : (
|
||||
<ArrowLeftIcon width={24} onClick={() => this.backButtonClicked()} />
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const pub = Key.getPubKey();
|
||||
const loggedIn = !!pub;
|
||||
return (
|
||||
<div className="sticky top-0 z-10 cursor-pointer flex flex-wrap">
|
||||
<div className="w-full overflow-x-hidden bg-black md:bg-opacity-50 md:shadow-lg md:backdrop-blur-lg px-2">
|
||||
<div className="flex items-center justify-between h-12">
|
||||
{this.renderBackBtnOrLogo()}
|
||||
<Show when={loggedIn && this.state.showConnectedRelays}>
|
||||
{this.renderConnectedRelays()}
|
||||
</Show>
|
||||
{this.renderTitle()}
|
||||
<Show when={loggedIn}>{this.renderNotifications()}</Show>
|
||||
<Show when={!loggedIn}>{this.renderLoginBtns()}</Show>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user