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

178 lines
5.6 KiB
React
Raw Normal View History

2021-08-04 09:52:59 +00:00
import React, { useState, useEffect, useRef } from 'react';
2021-07-28 13:15:52 +00:00
import PropTypes from 'prop-types';
2021-08-31 13:13:31 +00:00
import './RoomView.scss';
2021-07-28 13:15:52 +00:00
import EventEmitter from 'events';
import RoomTimeline from '../../../client/state/RoomTimeline';
import { Debounce, getScrollInfo } from '../../../util/common';
2021-07-28 13:15:52 +00:00
import ScrollView from '../../atoms/scroll/ScrollView';
2021-08-31 13:13:31 +00:00
import RoomViewHeader from './RoomViewHeader';
import RoomViewContent from './RoomViewContent';
import RoomViewFloating from './RoomViewFloating';
import RoomViewInput from './RoomViewInput';
import RoomViewCmdBar from './RoomViewCmdBar';
2021-07-28 13:15:52 +00:00
2021-08-04 09:52:59 +00:00
const viewEvent = new EventEmitter();
2021-07-28 13:15:52 +00:00
2021-08-31 13:13:31 +00:00
function RoomView({ roomId }) {
2021-07-28 13:15:52 +00:00
const [roomTimeline, updateRoomTimeline] = useState(null);
const [debounce] = useState(new Debounce());
2021-07-28 13:15:52 +00:00
const timelineSVRef = useRef(null);
useEffect(() => {
roomTimeline?.removeInternalListeners();
updateRoomTimeline(new RoomTimeline(roomId));
}, [roomId]);
const timelineScroll = {
reachBottom() {
timelineScroll.isOngoing = true;
const target = timelineSVRef?.current;
if (!target) return;
const maxScrollTop = target.scrollHeight - target.offsetHeight;
target.scrollTop = maxScrollTop;
timelineScroll.position = 'BOTTOM';
timelineScroll.isScrollable = maxScrollTop > 0;
timelineScroll.isInTopHalf = false;
timelineScroll.lastTopMsg = null;
timelineScroll.lastBottomMsg = null;
2021-07-28 13:15:52 +00:00
},
autoReachBottom() {
if (timelineScroll.position === 'BOTTOM') timelineScroll.reachBottom();
2021-07-28 13:15:52 +00:00
},
tryRestoringScroll() {
timelineScroll.isOngoing = true;
2021-07-28 13:15:52 +00:00
const sv = timelineSVRef.current;
const {
lastTopMsg, lastBottomMsg,
diff, isInTopHalf, lastTop,
} = timelineScroll;
if (lastTopMsg === null) {
sv.scrollTop = sv.scrollHeight;
return;
2021-07-28 13:15:52 +00:00
}
const ot = isInTopHalf ? lastTopMsg?.offsetTop : lastBottomMsg?.offsetTop;
if (!ot) sv.scrollTop = lastTop;
else sv.scrollTop = ot - diff;
2021-07-28 13:15:52 +00:00
},
position: 'BOTTOM',
isScrollable: false,
isInTopHalf: false,
maxEvents: 50,
lastTop: 0,
lastHeight: 0,
lastViewHeight: 0,
lastTopMsg: null,
lastBottomMsg: null,
diff: 0,
isOngoing: false,
2021-07-28 13:15:52 +00:00
};
const calcScroll = (target) => {
if (timelineScroll.isOngoing) {
timelineScroll.isOngoing = false;
return;
2021-07-28 13:15:52 +00:00
}
const PLACEHOLDER_COUNT = 2;
const PLACEHOLDER_HEIGHT = 96 * PLACEHOLDER_COUNT;
const SMALLEST_MSG_HEIGHT = 32;
const scroll = getScrollInfo(target);
const isPaginateBack = scroll.top < PLACEHOLDER_HEIGHT;
const isPaginateForward = scroll.bottom > (scroll.height - PLACEHOLDER_HEIGHT);
timelineScroll.isInTopHalf = scroll.top + (scroll.viewHeight / 2) < scroll.height / 2;
if (timelineScroll.lastViewHeight !== scroll.viewHeight) {
timelineScroll.maxEvents = Math.round(scroll.viewHeight / SMALLEST_MSG_HEIGHT) * 3;
timelineScroll.lastViewHeight = scroll.viewHeight;
2021-07-28 13:15:52 +00:00
}
timelineScroll.isScrollable = scroll.isScrollable;
timelineScroll.lastTop = scroll.top;
timelineScroll.lastHeight = scroll.height;
const tChildren = target.lastElementChild.lastElementChild.children;
const lCIndex = tChildren.length - 1;
timelineScroll.lastTopMsg = tChildren[0]?.className === 'ph-msg'
? tChildren[PLACEHOLDER_COUNT]
: tChildren[0];
timelineScroll.lastBottomMsg = tChildren[lCIndex]?.className === 'ph-msg'
? tChildren[lCIndex - PLACEHOLDER_COUNT]
: tChildren[lCIndex];
if (timelineScroll.isInTopHalf && timelineScroll.lastBottomMsg) {
timelineScroll.diff = timelineScroll.lastTopMsg.offsetTop - scroll.top;
} else {
timelineScroll.diff = timelineScroll.lastBottomMsg.offsetTop - scroll.top;
2021-07-28 13:15:52 +00:00
}
if (isPaginateBack) {
timelineScroll.position = 'TOP';
viewEvent.emit('timeline-scroll', timelineScroll.position);
} else if (isPaginateForward) {
timelineScroll.position = 'BOTTOM';
viewEvent.emit('timeline-scroll', timelineScroll.position);
} else {
timelineScroll.position = 'BETWEEN';
viewEvent.emit('timeline-scroll', timelineScroll.position);
2021-07-28 13:15:52 +00:00
}
};
const handleTimelineScroll = (event) => {
const { target } = event;
if (!target) return;
debounce._(calcScroll, 200)(target);
};
2021-07-28 13:15:52 +00:00
return (
2021-08-31 13:13:31 +00:00
<div className="room-view">
<RoomViewHeader roomId={roomId} />
<div className="room-view__content-wrapper">
<div className="room-view__scrollable">
<ScrollView onScroll={handleTimelineScroll} ref={timelineSVRef} autoHide>
2021-07-28 13:15:52 +00:00
{roomTimeline !== null && (
2021-08-31 13:13:31 +00:00
<RoomViewContent
2021-07-28 13:15:52 +00:00
roomId={roomId}
roomTimeline={roomTimeline}
timelineScroll={timelineScroll}
2021-08-04 09:52:59 +00:00
viewEvent={viewEvent}
2021-07-28 13:15:52 +00:00
/>
)}
</ScrollView>
{roomTimeline !== null && (
2021-08-31 13:13:31 +00:00
<RoomViewFloating
2021-07-28 13:15:52 +00:00
roomId={roomId}
roomTimeline={roomTimeline}
2021-08-04 09:52:59 +00:00
viewEvent={viewEvent}
2021-07-28 13:15:52 +00:00
/>
)}
</div>
{roomTimeline !== null && (
2021-08-31 13:13:31 +00:00
<div className="room-view__sticky">
<RoomViewInput
2021-07-28 13:15:52 +00:00
roomId={roomId}
roomTimeline={roomTimeline}
timelineScroll={timelineScroll}
2021-08-04 09:52:59 +00:00
viewEvent={viewEvent}
2021-07-28 13:15:52 +00:00
/>
2021-08-31 13:13:31 +00:00
<RoomViewCmdBar
2021-07-28 13:15:52 +00:00
roomId={roomId}
roomTimeline={roomTimeline}
2021-08-04 09:52:59 +00:00
viewEvent={viewEvent}
2021-07-28 13:15:52 +00:00
/>
2021-08-04 09:52:59 +00:00
</div>
2021-07-28 13:15:52 +00:00
)}
</div>
</div>
);
}
2021-08-31 13:13:31 +00:00
RoomView.propTypes = {
2021-07-28 13:15:52 +00:00
roomId: PropTypes.string.isRequired,
};
2021-08-31 13:13:31 +00:00
export default RoomView;