cinny/src/app/organisms/room/RoomViewContent.jsx

278 lines
9.1 KiB
React
Raw Normal View History

/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
2021-08-04 09:52:59 +00:00
/* eslint-disable react/prop-types */
import React, { useState, useEffect, useLayoutEffect } from 'react';
import PropTypes from 'prop-types';
2021-08-31 13:13:31 +00:00
import './RoomViewContent.scss';
2021-08-04 09:52:59 +00:00
import dateFormat from 'dateformat';
import initMatrix from '../../../client/initMatrix';
import cons from '../../../client/state/cons';
import { diffMinutes, isNotInSameDay } from '../../../util/common';
import { openProfileViewer } from '../../../client/action/navigation';
2021-08-04 09:52:59 +00:00
import Divider from '../../atoms/divider/Divider';
import { Message, PlaceholderMessage } from '../../molecules/message/Message';
2021-08-31 13:13:31 +00:00
import RoomIntro from '../../molecules/room-intro/RoomIntro';
2021-08-04 09:52:59 +00:00
import TimelineChange from '../../molecules/message/TimelineChange';
import { parseTimelineChange } from './common';
2021-08-04 09:52:59 +00:00
const MAX_MSG_DIFF_MINUTES = 5;
function genPlaceholders(key) {
2021-08-15 08:29:09 +00:00
return (
<React.Fragment key={`placeholder-container${key}`}>
<PlaceholderMessage key={`placeholder-1${key}`} />
<PlaceholderMessage key={`placeholder-2${key}`} />
</React.Fragment>
2021-08-15 08:29:09 +00:00
);
}
2021-08-31 13:13:31 +00:00
function genRoomIntro(mEvent, roomTimeline) {
2021-08-17 11:21:22 +00:00
const mx = initMatrix.matrixClient;
2021-08-15 08:29:09 +00:00
const roomTopic = roomTimeline.room.currentState.getStateEvents('m.room.topic')[0]?.getContent().topic;
2021-08-17 11:21:22 +00:00
const isDM = initMatrix.roomList.directs.has(roomTimeline.roomId);
let avatarSrc = roomTimeline.room.getAvatarUrl(mx.baseUrl, 80, 80, 'crop');
avatarSrc = isDM ? roomTimeline.room.getAvatarFallbackMember()?.getAvatarUrl(mx.baseUrl, 80, 80, 'crop') : avatarSrc;
2021-08-15 08:29:09 +00:00
return (
2021-08-31 13:13:31 +00:00
<RoomIntro
key={mEvent ? mEvent.getId() : 'room-intro'}
roomId={roomTimeline.roomId}
2021-08-17 11:21:22 +00:00
avatarSrc={avatarSrc}
2021-08-15 08:29:09 +00:00
name={roomTimeline.room.name}
heading={`Welcome to ${roomTimeline.room.name}`}
2021-08-31 13:13:31 +00:00
desc={`This is the beginning of ${roomTimeline.room.name} room.${typeof roomTopic !== 'undefined' ? (` Topic: ${roomTopic}`) : ''}`}
2021-08-15 08:29:09 +00:00
time={mEvent ? `Created at ${dateFormat(mEvent.getDate(), 'dd mmmm yyyy, hh:MM TT')}` : null}
/>
);
}
const scroll = {
from: 0,
limit: 0,
getEndIndex() {
return (this.from + this.limit);
},
isNewEvent: false,
};
2021-08-31 13:13:31 +00:00
function RoomViewContent({
2021-08-04 09:52:59 +00:00
roomId, roomTimeline, timelineScroll, viewEvent,
}) {
const [isReachedTimelineEnd, setIsReachedTimelineEnd] = useState(false);
const [onStateUpdate, updateState] = useState(null);
2021-08-04 09:52:59 +00:00
const mx = initMatrix.matrixClient;
const noti = initMatrix.notifications;
if (scroll.limit === 0) {
const from = roomTimeline.timeline.size - timelineScroll.maxEvents;
scroll.from = (from < 0) ? 0 : from;
scroll.limit = timelineScroll.maxEvents;
}
2021-08-04 09:52:59 +00:00
function autoLoadTimeline() {
if (timelineScroll.isScrollable === true) return;
2021-08-04 09:52:59 +00:00
roomTimeline.paginateBack();
}
function trySendingReadReceipt() {
const { timeline } = roomTimeline.room;
2021-09-13 14:17:40 +00:00
if (
(noti.doesRoomHaveUnread(roomTimeline.room) || noti.hasNoti(roomId))
2021-09-13 14:17:40 +00:00
&& timeline.length !== 0) {
2021-08-04 09:52:59 +00:00
mx.sendReadReceipt(timeline[timeline.length - 1]);
}
}
const getNewFrom = (position) => {
let newFrom = scroll.from;
const tSize = roomTimeline.timeline.size;
const doPaginate = tSize > timelineScroll.maxEvents;
if (!doPaginate || scroll.from < 0) newFrom = 0;
const newEventCount = Math.round(timelineScroll.maxEvents / 2);
scroll.limit = timelineScroll.maxEvents;
2021-08-04 09:52:59 +00:00
if (position === 'TOP' && doPaginate) newFrom -= newEventCount;
if (position === 'BOTTOM' && doPaginate) newFrom += newEventCount;
if (newFrom >= tSize || scroll.getEndIndex() >= tSize) newFrom = tSize - scroll.limit - 1;
if (newFrom < 0) newFrom = 0;
return newFrom;
};
const handleTimelineScroll = (position) => {
const tSize = roomTimeline.timeline.size;
if (position === 'BETWEEN') return;
if (position === 'BOTTOM' && scroll.getEndIndex() + 1 === tSize) return;
if (scroll.from === 0 && position === 'TOP') {
// Fetch back history.
if (roomTimeline.isOngoingPagination || isReachedTimelineEnd) return;
roomTimeline.paginateBack();
return;
}
scroll.from = getNewFrom(position);
updateState({});
if (scroll.getEndIndex() + 1 >= tSize) {
trySendingReadReceipt();
2021-08-04 09:52:59 +00:00
}
};
const updatePAG = (canPagMore, loaded) => {
if (canPagMore) {
scroll.from += loaded;
scroll.from = getNewFrom(timelineScroll.position);
if (roomTimeline.ongoingDecryptionCount === 0) updateState({});
} else setIsReachedTimelineEnd(true);
};
// force update RoomTimeline
2021-08-04 09:52:59 +00:00
const updateRT = () => {
if (timelineScroll.position === 'BOTTOM') {
2021-08-04 09:52:59 +00:00
trySendingReadReceipt();
scroll.from = roomTimeline.timeline.size - scroll.limit - 1;
if (scroll.from < 0) scroll.from = 0;
scroll.isNewEvent = true;
2021-08-04 09:52:59 +00:00
}
updateState({});
};
const handleScrollToLive = () => {
trySendingReadReceipt();
scroll.from = roomTimeline.timeline.size - scroll.limit - 1;
if (scroll.from < 0) scroll.from = 0;
scroll.isNewEvent = true;
updateState({});
};
2021-08-04 09:52:59 +00:00
useEffect(() => {
trySendingReadReceipt();
return () => {
setIsReachedTimelineEnd(false);
scroll.limit = 0;
};
2021-08-04 09:52:59 +00:00
}, [roomId]);
// init room setup completed.
// listen for future. setup stateUpdate listener.
useEffect(() => {
roomTimeline.on(cons.events.roomTimeline.EVENT, updateRT);
roomTimeline.on(cons.events.roomTimeline.PAGINATED, updatePAG);
viewEvent.on('timeline-scroll', handleTimelineScroll);
viewEvent.on('scroll-to-live', handleScrollToLive);
2021-08-04 09:52:59 +00:00
return () => {
roomTimeline.removeListener(cons.events.roomTimeline.EVENT, updateRT);
roomTimeline.removeListener(cons.events.roomTimeline.PAGINATED, updatePAG);
viewEvent.removeListener('timeline-scroll', handleTimelineScroll);
viewEvent.removeListener('scroll-to-live', handleScrollToLive);
2021-08-04 09:52:59 +00:00
};
}, [roomTimeline, isReachedTimelineEnd]);
2021-08-04 09:52:59 +00:00
useLayoutEffect(() => {
timelineScroll.reachBottom();
autoLoadTimeline();
trySendingReadReceipt();
2021-08-04 09:52:59 +00:00
}, [roomTimeline]);
useLayoutEffect(() => {
if (onStateUpdate === null || scroll.isNewEvent) {
scroll.isNewEvent = false;
timelineScroll.reachBottom();
return;
}
if (timelineScroll.isScrollable) {
timelineScroll.tryRestoringScroll();
} else {
timelineScroll.reachBottom();
autoLoadTimeline();
}
2021-08-04 09:52:59 +00:00
}, [onStateUpdate]);
const handleOnClickCapture = (e) => {
const { target } = e;
const userId = target.getAttribute('data-mx-pill');
if (!userId) return;
openProfileViewer(userId, roomId);
};
2021-08-04 09:52:59 +00:00
let prevMEvent = null;
const renderMessage = (mEvent) => {
const isContentOnly = (prevMEvent !== null && prevMEvent.getType() !== 'm.room.member'
2021-08-20 13:42:57 +00:00
&& diffMinutes(mEvent.getDate(), prevMEvent.getDate()) <= MAX_MSG_DIFF_MINUTES
&& prevMEvent.getSender() === mEvent.getSender()
);
let DividerComp = null;
2021-08-04 09:52:59 +00:00
if (prevMEvent !== null && isNotInSameDay(mEvent.getDate(), prevMEvent.getDate())) {
DividerComp = <Divider key={`divider-${mEvent.getId()}`} text={`${dateFormat(mEvent.getDate(), 'mmmm dd, yyyy')}`} />;
2021-08-04 09:52:59 +00:00
}
prevMEvent = mEvent;
2021-08-04 09:52:59 +00:00
if (mEvent.getType() === 'm.room.member') {
const timelineChange = parseTimelineChange(mEvent);
if (timelineChange === null) return false;
2021-08-15 08:29:09 +00:00
return (
<React.Fragment key={`box-${mEvent.getId()}`}>
{DividerComp}
<TimelineChange
key={mEvent.getId()}
variant={timelineChange.variant}
content={timelineChange.content}
time={`${dateFormat(mEvent.getDate(), 'hh:MM TT')}`}
/>
2021-08-15 08:29:09 +00:00
</React.Fragment>
);
2021-08-04 09:52:59 +00:00
}
return (
<React.Fragment key={`box-${mEvent.getId()}`}>
{DividerComp}
<Message mEvent={mEvent} isBodyOnly={isContentOnly} roomTimeline={roomTimeline} />
2021-08-04 09:52:59 +00:00
</React.Fragment>
);
};
2021-08-04 09:52:59 +00:00
const renderTimeline = () => {
const { timeline } = roomTimeline;
const tl = [];
if (timeline.size === 0) return tl;
let i = 0;
// eslint-disable-next-line no-restricted-syntax
for (const [, mEvent] of timeline.entries()) {
if (i >= scroll.from) {
if (i === scroll.from) {
if (mEvent.getType() !== 'm.room.create' && !isReachedTimelineEnd) tl.push(genPlaceholders(1));
if (mEvent.getType() !== 'm.room.create' && isReachedTimelineEnd) tl.push(genRoomIntro(undefined, roomTimeline));
}
if (mEvent.getType() === 'm.room.create') tl.push(genRoomIntro(mEvent, roomTimeline));
else tl.push(renderMessage(mEvent));
}
i += 1;
if (i > scroll.getEndIndex()) break;
}
if (i < timeline.size) tl.push(genPlaceholders(2));
return tl;
};
2021-08-04 09:52:59 +00:00
return (
<div className="room-view__content" onClick={handleOnClickCapture}>
2021-08-04 09:52:59 +00:00
<div className="timeline__wrapper">
{ renderTimeline() }
2021-08-04 09:52:59 +00:00
</div>
</div>
);
}
2021-08-31 13:13:31 +00:00
RoomViewContent.propTypes = {
2021-08-04 09:52:59 +00:00
roomId: PropTypes.string.isRequired,
roomTimeline: PropTypes.shape({}).isRequired,
2021-08-15 08:29:09 +00:00
timelineScroll: PropTypes.shape({}).isRequired,
2021-08-04 09:52:59 +00:00
viewEvent: PropTypes.shape({}).isRequired,
};
2021-08-31 13:13:31 +00:00
export default RoomViewContent;