Files
watchparty/src/Chat.js
T
2020-04-28 21:20:14 -04:00

281 lines
9.4 KiB
JavaScript

/*global chrome */
import React, { Component } from 'react';
import {
Typography,
TextField,
InputAdornment,
IconButton,
ButtonGroup,
Button,
} from '@material-ui/core';
import Thread from './Thread';
import Send from '@material-ui/icons/Send';
import Exit from '@material-ui/icons/ExitToApp';
import Share from '@material-ui/icons/Share';
import Delete from '@material-ui/icons/Delete';
import Gif from '@material-ui/icons/Gif';
import { Carousel } from '@giphy/react-components';
import { GiphyFetch } from '@giphy/js-fetch-api';
import {
SendMessageApi,
LeaveRoomApi,
DeleteRoomApi,
UpdatePlayerApi,
getPlayerSource,
PLAYER_STATUS,
} from './Api';
import { goTo } from 'route-lite';
import Create from './Create';
import VideoListener from './VideoListener';
const giphyFetch = new GiphyFetch('meKO7qbULHC9Q26DRGt2yZguIn3LB6z7');
export default class Chat extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
messages: [],
newMessage: '',
admin: null,
player: {},
localPlayer: {},
gif: false,
};
this.sendMessage = this.sendMessage.bind(this);
this.sendMessageInvoker = this.sendMessageInvoker.bind(this);
this.endParty = this.endParty.bind(this);
this.leaveParty = this.leaveParty.bind(this);
this.shareParty = this.shareParty.bind(this);
this.syncPlayer = this.syncPlayer.bind(this);
this.videoEventListener = this.videoEventListener.bind(this);
this.mVideoListener = new VideoListener(this.videoEventListener);
this.port = null;
this.getCarousel = this.getCarousel.bind(this);
this.gifClick = this.gifClick.bind(this);
}
componentDidMount() {
window.onkeypress = (ev) => {
if (ev.code === 'Enter') {
if (ev.target === document.getElementById('chat-message-box-input')) {
if (ev.shiftKey === false && this.state.gif === true) ev.preventDefault();
if (ev.shiftKey === false && this.state.gif === false) {
ev.preventDefault();
this.sendMessageInvoker();
}
}
}
};
this.port = chrome.runtime.connect({ name: window.localStorage.getItem('watchPartyRoomId') });
this.port.postMessage({
type: 'INIT',
roomId: window.localStorage.getItem('watchPartyRoomId'),
});
this.port.onMessage.addListener((response) => {
if (response.isError === true) {
this.props.notify('Uh oh. Got lost from the party.');
goTo(Create, this.props);
return;
}
this.setState(response);
if (response.player && this.state.localPlayer.status !== response.player.status) {
if (response.player.source !== getPlayerSource()) {
this.props.updateLoading(true);
this.props.notify('This party is for a different website');
goTo(Create, this.props);
this.props.updateLoading(false);
} else {
if (this.mVideoListener.isUpdating === false)
this.mVideoListener.doUpdate(response.player);
}
}
const pcc = document.getElementsByClassName('thread-container')[0];
if (pcc) pcc.scrollTop = pcc.scrollHeight;
});
this.mVideoListener.start();
this.props.updateLoading(false);
}
videoEventListener(video) {
this.setState({ localPlayer: video });
if (
this.state.player.status !== video.status &&
video.showName &&
video.status !== PLAYER_STATUS.NOT_PLAYING
)
this.syncPlayer(video);
}
syncPlayer(video) {
const { status, showName, time, source, element } = video;
if (element) {
UpdatePlayerApi({
roomId: window.localStorage.getItem('watchPartyRoomId'),
showChanged: showName !== this.state.player.showName,
userName: window.localStorage.getItem('watchPartyUserName'),
player: {
status: showName !== this.state.player.showName ? PLAYER_STATUS.PAUSED : status,
showName,
time,
source,
updatedBy: window.localStorage.getItem('watchPartyUserUid'),
},
}).catch((err) => {
this.props.updateLoading(false);
this.props.notify(err.message || err.statusText);
console.error('Sync Party API error', err);
});
} else {
this.mVideoListener.start();
}
}
endParty() {
this.props.notify('Ending party...');
this.props.updateLoading(true);
const data = {
roomId: window.localStorage.getItem('watchPartyRoomId'),
};
DeleteRoomApi(data)
.then((res) => {
this.props.notify('Ended party');
window.localStorage.removeItem('watchPartyRoomId');
goTo(Create, this.props);
this.props.updateLoading(false);
})
.catch((err) => {
this.props.updateLoading(false);
this.props.notify(err.message || err.statusText);
console.error('End Party API error', err);
});
}
leaveParty() {
this.props.notify('Leaving party...');
this.props.updateLoading(true);
const data = {
roomId: window.localStorage.getItem('watchPartyRoomId'),
uid: window.localStorage.getItem('watchPartyUserUid'),
};
LeaveRoomApi(data)
.then((res) => {
this.props.notify('Left party');
window.localStorage.removeItem('watchPartyRoomId');
goTo(Create, this.props);
this.props.updateLoading(false);
})
.catch((err) => {
this.props.updateLoading(false);
this.props.notify(err.message || err.statusText);
console.error('End Party API error', err);
});
}
shareParty() {
const el = document.createElement('textarea');
el.value = window.localStorage.getItem('watchPartyRoomId');
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
this.props.notify('Copied room code');
}
sendMessage(message) {
const data = {
roomId: window.localStorage.getItem('watchPartyRoomId'),
name: window.localStorage.getItem('watchPartyUserName'),
uid: window.localStorage.getItem('watchPartyUserUid'),
text: message,
time: new Date().toISOString(),
type: '',
};
if (this.state.gif === true) data.type = 'giphy';
SendMessageApi(data).catch((err) => {
this.props.notify(err.message || err.statusText);
console.error('Send Message API error', err);
});
}
sendMessageInvoker(overrideMessage) {
if (overrideMessage) this.sendMessage(overrideMessage);
else {
const trim = this.state.newMessage.replace(/^\s+|\s+$/g, '');
if (trim !== '') this.sendMessage(trim);
}
this.setState({ newMessage: '' });
}
gifClick(iGif, ev) {
ev.preventDefault();
this.sendMessageInvoker(iGif.images.original.url);
}
getCarousel() {
const trendingFetch = (offset) => giphyFetch.trending({ offset, limit: 5 });
const searchFetch = (offset) => giphyFetch.search(this.state.newMessage, { offset, limit: 5 });
return (
<Carousel
fetchGifs={this.state.newMessage === '' ? trendingFetch : searchFetch}
gifHeight={200}
gutter={6}
onGifClick={this.gifClick}
hideAttribution
/>
);
}
render() {
const { admin, name, messages, newMessage, gif } = this.state;
return (
<div className="party-chat-container">
<div className="party-chat-header">
<div className="party-chat-header-text">
<Typography className="brand-title" variant="overline" display="block">
WATCH PARTY
</Typography>
<Typography className="room-title" variant="h4" gutterBottom>
{name}
</Typography>
</div>
<ButtonGroup className="party-controls" size="small" color="secondary">
<Button onClick={this.shareParty} startIcon={<Share />}>
<span className="party-control-button-text">Share</span>
</Button>
<Button onClick={this.leaveParty} startIcon={<Exit />}>
<span className="party-control-button-text">Leave</span>
</Button>
{admin && (
<Button onClick={this.endParty} startIcon={<Delete />}>
{/* <span className="party-control-button-text">End Party</span> */}
</Button>
)}
</ButtonGroup>
</div>
<div className="thread-container">
<Thread
messages={messages.sort((a, b) => new Date(b).getTime() - new Date(a).getTime())}
/>
</div>
<TextField
multiline
rowsMax="3"
size="small"
placeholder="New message... (Enter to send)"
variant="outlined"
className="chat-message-box addFocus"
value={newMessage}
onChange={(e) => this.setState({ newMessage: e.target.value, gif: false })}
InputProps={{
id: 'chat-message-box-input',
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={this.sendMessageInvoker} edge="end">
<Send />
</IconButton>
</InputAdornment>
),
startAdornment: (
<InputAdornment position="start">
<IconButton onClick={() => this.setState({ gif: !gif })} edge="start">
<Gif />
</IconButton>
</InputAdornment>
),
}}
/>
{gif ? this.getCarousel() : null}
</div>
);
}
}