Files
watchparty/src/Chat.js
T

232 lines
7.3 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/SendRounded';
import Exit from '@material-ui/icons/ExitToAppRounded';
import Share from '@material-ui/icons/ShareRounded';
import Delete from '@material-ui/icons/Delete';
import {
SendMessageApi,
LeaveRoomApi,
DeleteRoomApi,
UpdatePlayerApi,
getPlayerSource,
PLAYER_STATUS,
} from './Api';
import { goTo } from 'route-lite';
import Create from './Create';
import VideoListener from './VideoListener';
export default class Chat extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
messages: [],
newMessage: '',
admin: null,
player: {},
localPlayer: {},
};
this.sendMessage = this.sendMessage.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;
}
componentDidMount() {
window.onkeypress = (ev) => {
if (ev.code === 'Enter') {
if (ev.target === document.getElementById('chat-message-box-input')) {
if (ev.shiftKey === false) {
ev.preventDefault();
const trim = this.state.newMessage.replace(/^\s+|\s+$/g, '');
if (trim !== '') this.sendMessage(trim);
this.setState({ newMessage: '' });
}
}
}
};
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,
},
}).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(),
};
SendMessageApi(data).catch((err) => {
this.props.notify(err.message || err.statusText);
console.error('Send Message API error', err);
});
}
render() {
const { admin, name, messages, newMessage } = this.state;
return (
<div className="party-chat-container">
<Typography className="brand-title" variant="overline" display="block">
WATCH PARTY
</Typography>
<Typography className="room-title" variant="h4" gutterBottom>
{name}
</Typography>
<ButtonGroup className="party-controls" size="small" color="secondary" fullWidth>
<Button onClick={this.shareParty} startIcon={<Share />}>
Share
</Button>
<Button onClick={this.leaveParty} startIcon={<Exit />}>
Leave
</Button>
{admin && (
<Button onClick={this.endParty} startIcon={<Delete />}>
End Party
</Button>
)}
</ButtonGroup>
<div className="thread-container">
<Thread messages={messages} />
</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 })}
InputProps={{
id: 'chat-message-box-input',
endAdornment: (
<InputAdornment position="end">
<IconButton edge="end">
<Send />
</IconButton>
</InputAdornment>
),
}}
/>
</div>
);
}
}