2021-08-04 12:52:59 +03:00
|
|
|
/* eslint-disable react/prop-types */
|
|
|
|
import React, { useState, useEffect, useLayoutEffect } from 'react';
|
|
|
|
import PropTypes from 'prop-types';
|
2021-08-31 16:13:31 +03:00
|
|
|
import './RoomViewContent.scss';
|
2021-08-04 12:52:59 +03:00
|
|
|
|
|
|
|
import dateFormat from 'dateformat';
|
|
|
|
|
|
|
|
import initMatrix from '../../../client/initMatrix';
|
|
|
|
import cons from '../../../client/state/cons';
|
2021-11-20 09:59:32 +02:00
|
|
|
import { diffMinutes, isNotInSameDay } from '../../../util/common';
|
2021-08-04 12:52:59 +03:00
|
|
|
|
|
|
|
import Divider from '../../atoms/divider/Divider';
|
2021-11-20 09:59:32 +02:00
|
|
|
import { Message, PlaceholderMessage } from '../../molecules/message/Message';
|
2021-08-31 16:13:31 +03:00
|
|
|
import RoomIntro from '../../molecules/room-intro/RoomIntro';
|
2021-08-04 12:52:59 +03:00
|
|
|
import TimelineChange from '../../molecules/message/TimelineChange';
|
|
|
|
|
2021-11-20 09:59:32 +02:00
|
|
|
import { parseTimelineChange } from './common';
|
2021-08-04 12:52:59 +03:00
|
|
|
|
|
|
|
const MAX_MSG_DIFF_MINUTES = 5;
|
|
|
|
|
2021-11-18 10:02:12 +02:00
|
|
|
function genPlaceholders(key) {
|
2021-08-15 11:29:09 +03:00
|
|
|
return (
|
2021-11-18 10:02:12 +02:00
|
|
|
<React.Fragment key={`placeholder-container${key}`}>
|
|
|
|
<PlaceholderMessage key={`placeholder-1${key}`} />
|
|
|
|
<PlaceholderMessage key={`placeholder-2${key}`} />
|
|
|
|
</React.Fragment>
|
2021-08-15 11:29:09 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-08-31 16:13:31 +03:00
|
|
|
function genRoomIntro(mEvent, roomTimeline) {
|
2021-08-17 14:21:22 +03:00
|
|
|
const mx = initMatrix.matrixClient;
|
2021-08-15 11:29:09 +03:00
|
|
|
const roomTopic = roomTimeline.room.currentState.getStateEvents('m.room.topic')[0]?.getContent().topic;
|
2021-08-17 14:21:22 +03: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 11:29:09 +03:00
|
|
|
return (
|
2021-08-31 16:13:31 +03:00
|
|
|
<RoomIntro
|
|
|
|
key={mEvent ? mEvent.getId() : 'room-intro'}
|
2021-08-17 14:34:21 +03:00
|
|
|
roomId={roomTimeline.roomId}
|
2021-08-17 14:21:22 +03:00
|
|
|
avatarSrc={avatarSrc}
|
2021-08-15 11:29:09 +03:00
|
|
|
name={roomTimeline.room.name}
|
|
|
|
heading={`Welcome to ${roomTimeline.room.name}`}
|
2021-08-31 16:13:31 +03:00
|
|
|
desc={`This is the beginning of ${roomTimeline.room.name} room.${typeof roomTopic !== 'undefined' ? (` Topic: ${roomTopic}`) : ''}`}
|
2021-08-15 11:29:09 +03:00
|
|
|
time={mEvent ? `Created at ${dateFormat(mEvent.getDate(), 'dd mmmm yyyy, hh:MM TT')}` : null}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-11-18 10:02:12 +02:00
|
|
|
const scroll = {
|
|
|
|
from: 0,
|
|
|
|
limit: 0,
|
|
|
|
getEndIndex() {
|
|
|
|
return (this.from + this.limit);
|
|
|
|
},
|
|
|
|
isNewEvent: false,
|
|
|
|
};
|
2021-08-31 16:13:31 +03:00
|
|
|
function RoomViewContent({
|
2021-08-04 12:52:59 +03:00
|
|
|
roomId, roomTimeline, timelineScroll, viewEvent,
|
|
|
|
}) {
|
|
|
|
const [isReachedTimelineEnd, setIsReachedTimelineEnd] = useState(false);
|
|
|
|
const [onStateUpdate, updateState] = useState(null);
|
2021-11-20 09:59:32 +02:00
|
|
|
|
2021-08-04 12:52:59 +03:00
|
|
|
const mx = initMatrix.matrixClient;
|
2021-11-14 08:01:22 +02:00
|
|
|
const noti = initMatrix.notifications;
|
2021-11-20 09:59:32 +02:00
|
|
|
|
2021-11-18 10:02:12 +02:00
|
|
|
if (scroll.limit === 0) {
|
|
|
|
const from = roomTimeline.timeline.size - timelineScroll.maxEvents;
|
|
|
|
scroll.from = (from < 0) ? 0 : from;
|
|
|
|
scroll.limit = timelineScroll.maxEvents;
|
|
|
|
}
|
2021-08-04 12:52:59 +03:00
|
|
|
|
|
|
|
function autoLoadTimeline() {
|
2021-11-18 10:02:12 +02:00
|
|
|
if (timelineScroll.isScrollable === true) return;
|
2021-08-04 12:52:59 +03:00
|
|
|
roomTimeline.paginateBack();
|
|
|
|
}
|
|
|
|
function trySendingReadReceipt() {
|
2021-11-18 10:02:12 +02:00
|
|
|
const { timeline } = roomTimeline.room;
|
2021-09-13 17:17:40 +03:00
|
|
|
if (
|
2021-11-18 10:02:12 +02:00
|
|
|
(noti.doesRoomHaveUnread(roomTimeline.room) || noti.hasNoti(roomId))
|
2021-09-13 17:17:40 +03:00
|
|
|
&& timeline.length !== 0) {
|
2021-08-04 12:52:59 +03:00
|
|
|
mx.sendReadReceipt(timeline[timeline.length - 1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-18 10:02:12 +02:00
|
|
|
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 12:52:59 +03:00
|
|
|
|
2021-11-18 10:02:12 +02: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 12:52:59 +03:00
|
|
|
}
|
|
|
|
};
|
2021-11-18 10:02:12 +02:00
|
|
|
|
|
|
|
const updatePAG = (canPagMore, loaded) => {
|
|
|
|
if (canPagMore) {
|
|
|
|
scroll.from += loaded;
|
|
|
|
scroll.from = getNewFrom(timelineScroll.position);
|
|
|
|
if (roomTimeline.ongoingDecryptionCount === 0) updateState({});
|
|
|
|
} else setIsReachedTimelineEnd(true);
|
|
|
|
};
|
2021-11-20 09:59:32 +02:00
|
|
|
// force update RoomTimeline
|
2021-08-04 12:52:59 +03:00
|
|
|
const updateRT = () => {
|
2021-11-18 10:02:12 +02:00
|
|
|
if (timelineScroll.position === 'BOTTOM') {
|
2021-08-04 12:52:59 +03:00
|
|
|
trySendingReadReceipt();
|
2021-11-18 10:02:12 +02:00
|
|
|
scroll.from = roomTimeline.timeline.size - scroll.limit - 1;
|
|
|
|
if (scroll.from < 0) scroll.from = 0;
|
|
|
|
scroll.isNewEvent = true;
|
2021-08-04 12:52:59 +03:00
|
|
|
}
|
|
|
|
updateState({});
|
|
|
|
};
|
|
|
|
|
2021-11-18 10:02:12 +02:00
|
|
|
const handleScrollToLive = () => {
|
2021-11-19 09:50:34 +02:00
|
|
|
trySendingReadReceipt();
|
2021-11-18 10:02:12 +02:00
|
|
|
scroll.from = roomTimeline.timeline.size - scroll.limit - 1;
|
|
|
|
if (scroll.from < 0) scroll.from = 0;
|
|
|
|
scroll.isNewEvent = true;
|
|
|
|
updateState({});
|
|
|
|
};
|
|
|
|
|
2021-08-04 12:52:59 +03:00
|
|
|
useEffect(() => {
|
2021-11-18 10:02:12 +02:00
|
|
|
trySendingReadReceipt();
|
|
|
|
return () => {
|
|
|
|
setIsReachedTimelineEnd(false);
|
|
|
|
scroll.limit = 0;
|
|
|
|
};
|
2021-08-04 12:52:59 +03: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);
|
2021-11-18 10:02:12 +02:00
|
|
|
viewEvent.on('timeline-scroll', handleTimelineScroll);
|
|
|
|
viewEvent.on('scroll-to-live', handleScrollToLive);
|
2021-08-04 12:52:59 +03:00
|
|
|
|
|
|
|
return () => {
|
|
|
|
roomTimeline.removeListener(cons.events.roomTimeline.EVENT, updateRT);
|
|
|
|
roomTimeline.removeListener(cons.events.roomTimeline.PAGINATED, updatePAG);
|
2021-11-18 10:02:12 +02:00
|
|
|
viewEvent.removeListener('timeline-scroll', handleTimelineScroll);
|
|
|
|
viewEvent.removeListener('scroll-to-live', handleScrollToLive);
|
2021-08-04 12:52:59 +03:00
|
|
|
};
|
2021-11-18 10:02:12 +02:00
|
|
|
}, [roomTimeline, isReachedTimelineEnd]);
|
2021-08-04 12:52:59 +03:00
|
|
|
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
timelineScroll.reachBottom();
|
|
|
|
autoLoadTimeline();
|
2021-11-18 10:02:12 +02:00
|
|
|
trySendingReadReceipt();
|
2021-08-04 12:52:59 +03:00
|
|
|
}, [roomTimeline]);
|
|
|
|
|
|
|
|
useLayoutEffect(() => {
|
2021-11-18 10:02:12 +02:00
|
|
|
if (onStateUpdate === null || scroll.isNewEvent) {
|
|
|
|
scroll.isNewEvent = false;
|
|
|
|
timelineScroll.reachBottom();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (timelineScroll.isScrollable) {
|
|
|
|
timelineScroll.tryRestoringScroll();
|
|
|
|
} else {
|
|
|
|
timelineScroll.reachBottom();
|
|
|
|
autoLoadTimeline();
|
|
|
|
}
|
2021-08-04 12:52:59 +03:00
|
|
|
}, [onStateUpdate]);
|
|
|
|
|
|
|
|
let prevMEvent = null;
|
2021-11-20 09:59:32 +02:00
|
|
|
function renderMessage(mEvent) {
|
|
|
|
const isContentOnly = (prevMEvent !== null && prevMEvent.getType() !== 'm.room.member'
|
2021-08-20 16:42:57 +03:00
|
|
|
&& diffMinutes(mEvent.getDate(), prevMEvent.getDate()) <= MAX_MSG_DIFF_MINUTES
|
|
|
|
&& prevMEvent.getSender() === mEvent.getSender()
|
|
|
|
);
|
|
|
|
|
2021-11-20 09:59:32 +02:00
|
|
|
let DividerComp = null;
|
2021-08-04 12:52:59 +03:00
|
|
|
if (prevMEvent !== null && isNotInSameDay(mEvent.getDate(), prevMEvent.getDate())) {
|
2021-11-20 09:59:32 +02:00
|
|
|
DividerComp = <Divider key={`divider-${mEvent.getId()}`} text={`${dateFormat(mEvent.getDate(), 'mmmm dd, yyyy')}`} />;
|
2021-08-04 12:52:59 +03:00
|
|
|
}
|
2021-11-20 09:59:32 +02:00
|
|
|
prevMEvent = mEvent;
|
2021-08-04 12:52:59 +03:00
|
|
|
|
2021-11-20 09:59:32 +02:00
|
|
|
if (mEvent.getType() === 'm.room.member') {
|
|
|
|
const timelineChange = parseTimelineChange(mEvent);
|
|
|
|
if (timelineChange === null) return false;
|
2021-08-15 11:29:09 +03:00
|
|
|
return (
|
|
|
|
<React.Fragment key={`box-${mEvent.getId()}`}>
|
2021-11-20 09:59:32 +02:00
|
|
|
{DividerComp}
|
|
|
|
<TimelineChange
|
|
|
|
key={mEvent.getId()}
|
|
|
|
variant={timelineChange.variant}
|
|
|
|
content={timelineChange.content}
|
|
|
|
time={`${dateFormat(mEvent.getDate(), 'hh:MM TT')}`}
|
|
|
|
/>
|
2021-08-15 11:29:09 +03:00
|
|
|
</React.Fragment>
|
|
|
|
);
|
2021-08-04 12:52:59 +03:00
|
|
|
}
|
|
|
|
return (
|
|
|
|
<React.Fragment key={`box-${mEvent.getId()}`}>
|
2021-11-20 09:59:32 +02:00
|
|
|
{DividerComp}
|
|
|
|
<Message mEvent={mEvent} isBodyOnly={isContentOnly} roomTimeline={roomTimeline} />
|
2021-08-04 12:52:59 +03:00
|
|
|
</React.Fragment>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-11-18 10:02:12 +02: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));
|
|
|
|
}
|
2021-11-20 09:59:32 +02:00
|
|
|
if (mEvent.getType() === 'm.room.create') tl.push(genRoomIntro(mEvent, roomTimeline));
|
|
|
|
else tl.push(renderMessage(mEvent));
|
2021-11-18 10:02:12 +02:00
|
|
|
}
|
|
|
|
i += 1;
|
|
|
|
if (i > scroll.getEndIndex()) break;
|
|
|
|
}
|
|
|
|
if (i < timeline.size) tl.push(genPlaceholders(2));
|
|
|
|
|
|
|
|
return tl;
|
|
|
|
};
|
|
|
|
|
2021-08-04 12:52:59 +03:00
|
|
|
return (
|
2021-08-31 16:13:31 +03:00
|
|
|
<div className="room-view__content">
|
2021-08-04 12:52:59 +03:00
|
|
|
<div className="timeline__wrapper">
|
2021-11-18 10:02:12 +02:00
|
|
|
{ renderTimeline() }
|
2021-08-04 12:52:59 +03:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2021-08-31 16:13:31 +03:00
|
|
|
RoomViewContent.propTypes = {
|
2021-08-04 12:52:59 +03:00
|
|
|
roomId: PropTypes.string.isRequired,
|
|
|
|
roomTimeline: PropTypes.shape({}).isRequired,
|
2021-08-15 11:29:09 +03:00
|
|
|
timelineScroll: PropTypes.shape({}).isRequired,
|
2021-08-04 12:52:59 +03:00
|
|
|
viewEvent: PropTypes.shape({}).isRequired,
|
|
|
|
};
|
|
|
|
|
2021-08-31 16:13:31 +03:00
|
|
|
export default RoomViewContent;
|