2021-08-04 18:52:59 +09:00
|
|
|
import React, { useState, useEffect, useRef } from 'react';
|
2021-07-28 22:15:52 +09:00
|
|
|
import PropTypes from 'prop-types';
|
2021-08-31 22:13:31 +09:00
|
|
|
import './RoomView.scss';
|
2021-07-28 22:15:52 +09:00
|
|
|
|
|
|
|
import EventEmitter from 'events';
|
|
|
|
|
|
|
|
import RoomTimeline from '../../../client/state/RoomTimeline';
|
2021-11-18 17:02:12 +09:00
|
|
|
import { Debounce, getScrollInfo } from '../../../util/common';
|
2021-07-28 22:15:52 +09:00
|
|
|
|
|
|
|
import ScrollView from '../../atoms/scroll/ScrollView';
|
|
|
|
|
2021-08-31 22:13:31 +09:00
|
|
|
import RoomViewHeader from './RoomViewHeader';
|
|
|
|
import RoomViewContent from './RoomViewContent';
|
|
|
|
import RoomViewFloating from './RoomViewFloating';
|
|
|
|
import RoomViewInput from './RoomViewInput';
|
|
|
|
import RoomViewCmdBar from './RoomViewCmdBar';
|
2021-07-28 22:15:52 +09:00
|
|
|
|
2021-08-04 18:52:59 +09:00
|
|
|
const viewEvent = new EventEmitter();
|
2021-07-28 22:15:52 +09:00
|
|
|
|
2021-08-31 22:13:31 +09:00
|
|
|
function RoomView({ roomId }) {
|
2021-07-28 22:15:52 +09:00
|
|
|
const [roomTimeline, updateRoomTimeline] = useState(null);
|
2021-11-18 17:02:12 +09:00
|
|
|
const [debounce] = useState(new Debounce());
|
2021-07-28 22:15:52 +09:00
|
|
|
const timelineSVRef = useRef(null);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
roomTimeline?.removeInternalListeners();
|
|
|
|
updateRoomTimeline(new RoomTimeline(roomId));
|
|
|
|
}, [roomId]);
|
|
|
|
|
|
|
|
const timelineScroll = {
|
|
|
|
reachBottom() {
|
2021-11-18 17:02:12 +09:00
|
|
|
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 22:15:52 +09:00
|
|
|
},
|
|
|
|
autoReachBottom() {
|
2021-11-18 17:02:12 +09:00
|
|
|
if (timelineScroll.position === 'BOTTOM') timelineScroll.reachBottom();
|
2021-07-28 22:15:52 +09:00
|
|
|
},
|
|
|
|
tryRestoringScroll() {
|
2021-11-18 17:02:12 +09:00
|
|
|
timelineScroll.isOngoing = true;
|
2021-07-28 22:15:52 +09:00
|
|
|
const sv = timelineSVRef.current;
|
2021-11-18 17:02:12 +09:00
|
|
|
const {
|
|
|
|
lastTopMsg, lastBottomMsg,
|
|
|
|
diff, isInTopHalf, lastTop,
|
|
|
|
} = timelineScroll;
|
|
|
|
|
|
|
|
if (lastTopMsg === null) {
|
|
|
|
sv.scrollTop = sv.scrollHeight;
|
|
|
|
return;
|
2021-07-28 22:15:52 +09:00
|
|
|
}
|
2021-11-18 17:02:12 +09:00
|
|
|
|
|
|
|
const ot = isInTopHalf ? lastTopMsg?.offsetTop : lastBottomMsg?.offsetTop;
|
|
|
|
if (!ot) sv.scrollTop = lastTop;
|
|
|
|
else sv.scrollTop = ot - diff;
|
2021-07-28 22:15:52 +09:00
|
|
|
},
|
2021-11-18 17:02:12 +09: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 22:15:52 +09:00
|
|
|
};
|
|
|
|
|
2021-11-18 17:02:12 +09:00
|
|
|
const calcScroll = (target) => {
|
|
|
|
if (timelineScroll.isOngoing) {
|
|
|
|
timelineScroll.isOngoing = false;
|
|
|
|
return;
|
2021-07-28 22:15:52 +09:00
|
|
|
}
|
2021-11-18 17:02:12 +09: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 22:15:52 +09:00
|
|
|
}
|
2021-11-18 17:02:12 +09: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 22:15:52 +09:00
|
|
|
}
|
|
|
|
|
2021-11-18 17:02:12 +09: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 22:15:52 +09:00
|
|
|
}
|
2021-11-18 17:02:12 +09:00
|
|
|
};
|
|
|
|
|
|
|
|
const handleTimelineScroll = (event) => {
|
|
|
|
const { target } = event;
|
|
|
|
if (!target) return;
|
|
|
|
debounce._(calcScroll, 200)(target);
|
|
|
|
};
|
2021-07-28 22:15:52 +09:00
|
|
|
|
|
|
|
return (
|
2021-08-31 22:13:31 +09:00
|
|
|
<div className="room-view">
|
|
|
|
<RoomViewHeader roomId={roomId} />
|
|
|
|
<div className="room-view__content-wrapper">
|
|
|
|
<div className="room-view__scrollable">
|
2021-11-18 17:02:12 +09:00
|
|
|
<ScrollView onScroll={handleTimelineScroll} ref={timelineSVRef} autoHide>
|
2021-07-28 22:15:52 +09:00
|
|
|
{roomTimeline !== null && (
|
2021-08-31 22:13:31 +09:00
|
|
|
<RoomViewContent
|
2021-07-28 22:15:52 +09:00
|
|
|
roomId={roomId}
|
|
|
|
roomTimeline={roomTimeline}
|
|
|
|
timelineScroll={timelineScroll}
|
2021-08-04 18:52:59 +09:00
|
|
|
viewEvent={viewEvent}
|
2021-07-28 22:15:52 +09:00
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</ScrollView>
|
|
|
|
{roomTimeline !== null && (
|
2021-08-31 22:13:31 +09:00
|
|
|
<RoomViewFloating
|
2021-07-28 22:15:52 +09:00
|
|
|
roomId={roomId}
|
|
|
|
roomTimeline={roomTimeline}
|
2021-08-04 18:52:59 +09:00
|
|
|
viewEvent={viewEvent}
|
2021-07-28 22:15:52 +09:00
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
{roomTimeline !== null && (
|
2021-08-31 22:13:31 +09:00
|
|
|
<div className="room-view__sticky">
|
|
|
|
<RoomViewInput
|
2021-07-28 22:15:52 +09:00
|
|
|
roomId={roomId}
|
|
|
|
roomTimeline={roomTimeline}
|
|
|
|
timelineScroll={timelineScroll}
|
2021-08-04 18:52:59 +09:00
|
|
|
viewEvent={viewEvent}
|
2021-07-28 22:15:52 +09:00
|
|
|
/>
|
2021-08-31 22:13:31 +09:00
|
|
|
<RoomViewCmdBar
|
2021-07-28 22:15:52 +09:00
|
|
|
roomId={roomId}
|
|
|
|
roomTimeline={roomTimeline}
|
2021-08-04 18:52:59 +09:00
|
|
|
viewEvent={viewEvent}
|
2021-07-28 22:15:52 +09:00
|
|
|
/>
|
2021-08-04 18:52:59 +09:00
|
|
|
</div>
|
2021-07-28 22:15:52 +09:00
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2021-08-31 22:13:31 +09:00
|
|
|
RoomView.propTypes = {
|
2021-07-28 22:15:52 +09:00
|
|
|
roomId: PropTypes.string.isRequired,
|
|
|
|
};
|
|
|
|
|
2021-08-31 22:13:31 +09:00
|
|
|
export default RoomView;
|