Files
watchparty/app/src/Thread.js
T
2020-09-09 18:01:44 -04:00

250 lines
8.6 KiB
JavaScript

/************************************************************************
Copyright (C) Ayushya Amitabh - All Rights Reserved
Unauthorized copying of this file, via any medium is strictly prohibited
Proprietary and confidential
Written by Ayushya Amitabh <admin@chromewatchparty.com>, 2020
https://www.chromewatchparty.com/copyright
=========================================================================
ALL CONTENT ON WATCH PARTY'S WEBSITE OR IN WATCH PARTY'S CHROME EXTENSION APP, UNLESS OTHERWISE
CITED ARE THE SOLE INTELLECTUAL PROPERTY OF AYUSHYA AMITABH, AND MAY NOT BE
COPIED, EDITED, OR REPRODUCED WITHOUT PERMISSION.
************************************************************************/
import React, { Component, createRef } from 'react';
import {
Typography,
IconButton,
Paper,
TextField,
InputAdornment,
Button,
} from '@material-ui/core';
import { Reply, Send } from '@material-ui/icons';
import './Thread.css';
import { LocalData, webhookFetcher } from './Api';
import { CellMeasurer, CellMeasurerCache, AutoSizer, List } from 'react-virtualized';
var cache = new CellMeasurerCache({
fixedWidth: true,
});
export default class Thread extends Component {
constructor(props) {
super(props);
this.state = {
autoSizeWidth: 0,
};
this.listRef = createRef();
this.getMessage = this.getMessage.bind(this);
this.getTimeString = this.getTimeString.bind(this);
this.getReplyMessage = this.getReplyMessage.bind(this);
this.getMessageTextOrImage = this.getMessageTextOrImage.bind(this);
}
scrollToMessage(msgIdx, messages) {
this.listRef.current.scrollToRow(msgIdx);
setTimeout(() => {
const id = messages[msgIdx].id || `sending-${msgIdx}`;
const msg = document.getElementById(id);
msg.classList.add('highlight');
setTimeout(() => msg.classList.remove('highlight'), 2000);
}, 500);
}
handleInteractiveMessageCall(msg) {
const thanksElement = document.createElement('div');
thanksElement.innerText = 'THANK YOU FOR THE FEEDBACK';
thanksElement.className = 'thread-interactive-thankyou';
if (msg.uid === '<system>') {
if (msg.endpoint) {
var bodyData = {
question: msg.text,
...LocalData(),
};
if (msg.type === 'interactive/text') {
const answerElement = document.getElementById(`reply-${msg.id}`);
bodyData.answer = answerElement ? answerElement.value : 'ANSWER_ELEM_NOT_FOUND';
answerElement.disabled = true;
answerElement.parentElement.parentElement.parentElement.firstElementChild.remove();
answerElement.parentElement.replaceWith(thanksElement);
}
webhookFetcher({
endpoint: msg.endpoint,
body: JSON.stringify(bodyData),
});
}
}
}
getItemClassNames(uid, userUid, msgIdx, messages, isSending = false) {
const lastUid = msgIdx >= 1 ? messages[msgIdx - 1].uid : '';
return `thread-item ${uid === userUid ? 'my-message ' : ''}${
isSending ? 'sending-message ' : ''
}${uid === '<system>' ? 'system-message ' : ''}${lastUid === uid ? 'continued-message' : ''}`;
}
getMessageTextOrImage(msg, measure) {
if (msg.type === 'giphy') {
return <img onLoad={measure} alt="gif" src={msg.text} />;
}
if (msg.type === 'interactive/text') {
return (
<Paper onLoad={measure} elevation={0} className="thread-interactive-text">
<Typography className="thread-interactive-question" variant="overline">
{msg.text}
</Typography>
<TextField
className="thread-interactive-answer addFocus"
size="small"
id={`reply-${msg.id}`}
multiline
variant="outlined"
color="secondary"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={() => this.handleInteractiveMessageCall(msg)}>
<Send />
</IconButton>
</InputAdornment>
),
}}
rowsMax={2}
/>
</Paper>
);
}
if (msg.type === 'interactive/link') {
return (
<Paper onLoad={measure} elevation={0} className="thread-interactive-text">
<Typography className="thread-interactive-question" variant="overline">
{msg.text}
</Typography>
<Button
className="thread-interactive-link"
fullWidth
target="_blank"
href={msg.endpoint}
color="secondary"
>
{msg.name}
</Button>
</Paper>
);
}
return <p onLoad={measure}>{msg.text}</p>;
}
getTimeString(time) {
return new Date(time.seconds * 1000).toLocaleTimeString();
}
getReplyMessage(replyTo, messages) {
if (replyTo !== undefined && replyTo !== null) {
const replyMessage = messages.filter((v) => v.id === replyTo)[0];
if (replyMessage) {
const msgIdx = messages.indexOf(replyMessage);
return (
<div className="thread-reply" onClick={() => this.scrollToMessage(msgIdx, messages)}>
<p className="thread-reply-name">Reply to {replyMessage.name}:</p>
{this.getMessageTextOrImage(replyMessage)}
</div>
);
}
}
return null;
}
getMessage(params) {
const { autoSizeWidth } = this.state;
const { sentMessages, setReplyTo } = this.props;
const { userUid } = LocalData();
const messages = sentMessages;
const msg = messages[params.index];
const msgIdx = params.index;
return (
<CellMeasurer
cache={cache}
columnIndex={0}
key={`msg-${msgIdx}`}
parent={params.parent}
rowIndex={msgIdx}
width={autoSizeWidth}
>
{({ measure, registerChild }) => (
<div
ref={registerChild}
style={params.style}
id={msg.id || `sending-${msgIdx}`}
key={`msg-${msgIdx}-item`}
className={this.getItemClassNames(msg.uid, userUid, msgIdx, messages, !!!msg.id)}
>
<div className="thread-name">{msg.name}</div>
{this.getReplyMessage(msg.replyTo, messages)}
<div className="thread-message">
<div className="thread-overline">
<span>{this.getTimeString(msg.time)}</span>
<div className="thread-reply-icon">
<IconButton onClick={() => setReplyTo(msg)}>
<Reply />
</IconButton>
</div>
</div>
{this.getMessageTextOrImage(msg, measure)}
</div>
</div>
)}
</CellMeasurer>
);
}
render() {
const { userUid } = LocalData();
const { autoSizeWidth } = this.state;
const { sentMessages, isSending, sendingMessages, setReplyTo } = this.props;
return !isSending ? (
<AutoSizer style={{ flex: '1 auto', minWidth: '100%' }}>
{({ height, width }) => {
if (autoSizeWidth !== width) {
cache.clearAll();
this.listRef.current.recomputeRowHeights();
this.setState({ autoSizeWidth: width });
}
return (
<List
className="thread-wrapper"
deferredMeasurementCache={cache}
height={height}
width={width}
ref={this.listRef}
estimatedRowSize={40}
rowCount={sentMessages.length}
rowHeight={({ index }) => cache.rowHeight({ index }) + 10}
rowRenderer={this.getMessage}
/>
);
}}
</AutoSizer>
) : (
<div className="thread-wrapper sending-wrapper">
{sendingMessages.map((msg, msgIdx) => (
<div
id={msg.id || `sending-${msgIdx}`}
key={`sending-msg-${msgIdx}-item`}
className={this.getItemClassNames(msg.uid, userUid, msgIdx, sendingMessages, true)}
>
<div className="thread-name">{msg.name}</div>
{this.getReplyMessage(msg.replyTo, sentMessages)}
<div className="thread-message">
<div className="thread-overline">
<span>{this.getTimeString(msg.time)}</span>
<div className="thread-reply-icon">
<IconButton onClick={() => setReplyTo(msg)}>
<Reply />
</IconButton>
</div>
</div>
{this.getMessageTextOrImage(msg, null)}
</div>
</div>
))}
</div>
);
}
}
Thread.defaultProps = {
setReplyTo: () => {},
};