935 lines
32 KiB
JavaScript
935 lines
32 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 INTELECTUAL PROPERTY OF AYUSHYA AMITABH, AND MAY NOT BE
|
|
COPIED, EDITED, OR REPRODUCED WITHOUT PERMISSION.
|
|
************************************************************************/
|
|
/*global chrome */
|
|
import React, { Component } from 'react';
|
|
import {
|
|
Typography,
|
|
TextField,
|
|
InputAdornment,
|
|
IconButton,
|
|
ButtonGroup,
|
|
Button,
|
|
ExpansionPanel,
|
|
ExpansionPanelSummary,
|
|
ExpansionPanelDetails,
|
|
Card,
|
|
CardContent,
|
|
Tooltip,
|
|
Switch,
|
|
FormControlLabel,
|
|
Chip,
|
|
CircularProgress,
|
|
Paper,
|
|
} from '@material-ui/core';
|
|
import Thread from './Thread';
|
|
import {
|
|
SendMessageApi,
|
|
LeaveRoomApi,
|
|
DeleteRoomApi,
|
|
UpdatePlayerApi,
|
|
LocalData,
|
|
PLAYER_STATUS,
|
|
updateLocalData,
|
|
UpdateAdminOnly,
|
|
scrollThread,
|
|
removeLocalData,
|
|
PLAYER_SOURCE,
|
|
getPlayerSource,
|
|
} from './Api';
|
|
import {
|
|
Send,
|
|
ExitToApp as Exit,
|
|
Share,
|
|
Delete,
|
|
CallRounded as PhoneEnable,
|
|
CallEndRounded as PhoneDisable,
|
|
MicOff,
|
|
Mic as MicOn,
|
|
Gif,
|
|
KeyboardArrowDown,
|
|
PauseCircleFilled,
|
|
LinkOutlined,
|
|
PlayCircleFilled,
|
|
Cancel,
|
|
Error,
|
|
FullscreenExit,
|
|
Fullscreen,
|
|
CancelRounded,
|
|
VideocamRounded,
|
|
VideocamOffRounded,
|
|
} from '@material-ui/icons';
|
|
import Create from './Create';
|
|
import { goTo } from 'route-lite';
|
|
import CallManager from './CallManager';
|
|
import VideoListener from './VideoListener';
|
|
import ReactGiphySearchbox from 'react-giphy-searchbox';
|
|
|
|
export default class Chat extends Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
name: '',
|
|
messages: [],
|
|
newMessage: '',
|
|
admin: null,
|
|
adminOnly: false,
|
|
adminModeUpdate: false,
|
|
users: [],
|
|
gif: false,
|
|
onCall: false,
|
|
muted: false,
|
|
callActionPending: false,
|
|
menuPopover: true,
|
|
allowChatNotification:
|
|
LocalData().allowChatNotification !== undefined ? LocalData().allowChatNotification : true,
|
|
isFullscreen: document.fullscreen,
|
|
allowAutoRejoin:
|
|
LocalData().allowAutoRejoin !== undefined ? LocalData().allowAutoRejoin : true,
|
|
allowAutoFollow:
|
|
LocalData().allowAutoFollow !== undefined ? LocalData().allowAutoFollow : false,
|
|
replyTo: null,
|
|
sendingMessages: [],
|
|
usingVideo: false,
|
|
};
|
|
this.port = null;
|
|
this.gifClick = this.gifClick.bind(this);
|
|
this.endParty = this.endParty.bind(this);
|
|
this.handleMute = this.handleMute.bind(this);
|
|
this.leaveParty = this.leaveParty.bind(this);
|
|
this.shareParty = this.shareParty.bind(this);
|
|
this.syncPlayer = this.syncPlayer.bind(this);
|
|
this.setReplyTo = this.setReplyTo.bind(this);
|
|
this.sendMessage = this.sendMessage.bind(this);
|
|
this.updateAdminOnly = this.updateAdminOnly.bind(this);
|
|
this.handlePhoneCall = this.handlePhoneCall.bind(this);
|
|
this.getReplyToContent = this.getReplyToContent.bind(this);
|
|
this.callEventListener = this.callEventListener.bind(this);
|
|
this.sendMessageInvoker = this.sendMessageInvoker.bind(this);
|
|
this.videoEventListener = this.videoEventListener.bind(this);
|
|
this.mVideoListener = new VideoListener(
|
|
this.videoEventListener,
|
|
props.selectors,
|
|
props.nameSelectors
|
|
);
|
|
this.updateAllowAutoRejoin = this.updateAllowAutoRejoin.bind(this);
|
|
this.updateAllowAutoFollow = this.updateAllowAutoFollow.bind(this);
|
|
this.updateAllowChatNotification = this.updateAllowChatNotification.bind(this);
|
|
this.mCallManager = new CallManager(this.callEventListener, this.props.notify);
|
|
}
|
|
componentDidMount() {
|
|
// EVENT CAPTURE FOR ENTER TO SEND
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// FULLSCREEN LISTENER
|
|
document.addEventListener('fullscreenchange', (ev) => {
|
|
this.setState({ isFullscreen: document.fullscreen });
|
|
});
|
|
|
|
// ACTIVE TRACKER
|
|
if (LocalData().allowAutoRejoin === undefined || LocalData().allowAutoRejoin === null) {
|
|
updateLocalData({ allowAutoRejoin: this.state.allowAutoRejoin });
|
|
}
|
|
if (this.state.allowAutoRejoin === true) {
|
|
updateLocalData({ lastActive: new Date().getTime() });
|
|
}
|
|
setInterval(() => {
|
|
if (this.state.allowAutoRejoin === true) {
|
|
updateLocalData({ lastActive: new Date().getTime() });
|
|
}
|
|
}, 60000);
|
|
|
|
// VIDEO LISTENER START
|
|
this.mVideoListener.start();
|
|
|
|
// CHROME FIREBASE FIRESTORE LISTENER
|
|
this.port = chrome.runtime.connect({ name: LocalData().partyName });
|
|
// Initialize listener
|
|
this.port.postMessage({
|
|
type: 'INIT',
|
|
roomId: LocalData().partyName,
|
|
roomPin: LocalData().partyPin,
|
|
});
|
|
// Listener
|
|
this.port.onMessage.addListener((response) => {
|
|
if (response.isError === true) {
|
|
this.props.notify('Uh oh. Got lost from the party.');
|
|
goTo(Create, this.props);
|
|
return;
|
|
} else {
|
|
// CONTINUE TUTORIAL
|
|
if (this.props.tutorialComplete === false) {
|
|
this.props.resetTutorial('chat');
|
|
}
|
|
}
|
|
|
|
if (response.messages) {
|
|
scrollThread();
|
|
}
|
|
|
|
// IF CHAT IS HIDDEN AND ALLOWS NOTIFICATION
|
|
if (!document.querySelector('body.visible') && this.state.allowChatNotification === true) {
|
|
if (response.messages && response.messages.length !== this.state.messages.length) {
|
|
const lastMessage = response.messages[response.messages.length - 1];
|
|
if (lastMessage.uid !== '<system>') {
|
|
this.props.notify(
|
|
`${lastMessage.name}${
|
|
lastMessage.type === 'giphy' ? ' sent a gif.' : ': ' + lastMessage.text
|
|
}`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
// ADMIN MODE UPDATE COMPLETE
|
|
if (response.adminOnly !== this.state.adminOnly) {
|
|
this.setState({ adminModeUpdate: false });
|
|
}
|
|
this.setState(response);
|
|
if (response.player) this.mVideoListener.doUpdate(response.player);
|
|
});
|
|
|
|
// GIPHY IPC
|
|
const giphyInject = `var giphySearchInteraction=function(e){if(e.source===window){const{data}=e;if(data.watchPartyDirection==='TO_SCRIPT'){if(data.type==='WATCH_PARTY_SEARCH_GIF'){const giphyInput=document.querySelector('.giphy-search-form input');if(giphyInput){giphyInput.value=data.value;giphyInput.dispatchEvent(new Event('change',{bubbles:true}))}}}}};window.addEventListener("message",giphySearchInteraction);`;
|
|
const giphyInjectScript = document.createElement('script');
|
|
giphyInjectScript.text = giphyInject;
|
|
document.head.appendChild(giphyInjectScript);
|
|
|
|
// DONE WITH MOUNTING TASKS
|
|
this.props.updateLoading(false);
|
|
|
|
setInterval(() => {
|
|
if (
|
|
this.state.player &&
|
|
this.state.player.time &&
|
|
this.state.player.status === PLAYER_STATUS.PLAYING
|
|
) {
|
|
const oldPlayer = this.state.player;
|
|
oldPlayer.time += 1;
|
|
this.setState({
|
|
player: oldPlayer,
|
|
});
|
|
}
|
|
}, 1000);
|
|
}
|
|
componentWillUnmount() {
|
|
this.mVideoListener.end();
|
|
this.leaveParty();
|
|
}
|
|
videoEventListener(video) {
|
|
const { adminOnly, admin } = this.state;
|
|
if (!adminOnly || (adminOnly && admin)) {
|
|
this.syncPlayer(video);
|
|
}
|
|
}
|
|
callEventListener(event) {
|
|
this.setState(event);
|
|
}
|
|
syncPlayer(video) {
|
|
const { status, showName, time, source, element, showChanged } = video;
|
|
if (element) {
|
|
UpdatePlayerApi({
|
|
roomId: LocalData().partyName || this.state.name,
|
|
showChanged,
|
|
userName: LocalData().userName,
|
|
player: {
|
|
status,
|
|
showName,
|
|
time,
|
|
source,
|
|
updatedBy: LocalData().userUid,
|
|
url: window.location.href,
|
|
},
|
|
}).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 = {
|
|
partyName: LocalData().partyName,
|
|
};
|
|
if (this.state.onCall)
|
|
this.mCallManager.toggleCall(
|
|
this.state.onCall,
|
|
this.state.conference,
|
|
this.state.admin,
|
|
this.state.adminOnly,
|
|
false
|
|
);
|
|
DeleteRoomApi(data)
|
|
.then((res) => {
|
|
this.props.notify('Ended party');
|
|
removeLocalData('partyPin');
|
|
removeLocalData('partyName');
|
|
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 = {
|
|
partyName: LocalData().partyName,
|
|
userUid: LocalData().userUid,
|
|
};
|
|
if (this.state.onCall)
|
|
this.mCallManager.toggleCall(
|
|
this.state.onCall,
|
|
this.state.conference,
|
|
this.state.admin,
|
|
this.state.adminOnly,
|
|
false
|
|
);
|
|
LeaveRoomApi(data)
|
|
.then((res) => {
|
|
this.props.notify('Left party');
|
|
removeLocalData('partyPin');
|
|
removeLocalData('partyName');
|
|
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 = `Join my party on Watch Party!\nParty Name: ${LocalData().partyName}\nParty Pin: ${
|
|
LocalData().partyPin
|
|
}\n${window.location.href}\n---\nGet Watch Party at https:\\\\get.chromewatchparty.com`;
|
|
document.body.appendChild(el);
|
|
el.select();
|
|
document.execCommand('copy');
|
|
document.body.removeChild(el);
|
|
this.props.notify('Copied party invitation');
|
|
}
|
|
sendMessage(message) {
|
|
const { sendingMessages } = this.state;
|
|
const data = {
|
|
id: `sending-${sendingMessages.length}`,
|
|
roomId: LocalData().partyName,
|
|
name: LocalData().userName,
|
|
uid: LocalData().userUid,
|
|
text: message,
|
|
time: new Date(),
|
|
type: '',
|
|
};
|
|
if (this.state.gif === true) data.type = 'giphy';
|
|
if (this.state.replyTo !== null) data.replyTo = this.state.replyTo.id;
|
|
sendingMessages.push(data);
|
|
this.setState({
|
|
sendingMessages,
|
|
});
|
|
SendMessageApi(data)
|
|
.then(() => {
|
|
const { sendingMessages: sm } = this.state;
|
|
const rmi = sm.indexOf(data);
|
|
sm.splice(rmi, 1);
|
|
this.setState({ sendingMessages: sm });
|
|
scrollThread();
|
|
})
|
|
.catch((err) => {
|
|
const { sendingMessages: sm } = this.state;
|
|
const rmi = sm.indexOf(data);
|
|
sm.splice(rmi, 1);
|
|
this.setState({ sendingMessages: sm });
|
|
scrollThread();
|
|
this.props.notify(err.message || err.statusText);
|
|
console.error('Send Message API error', err);
|
|
});
|
|
this.setState({ replyTo: null });
|
|
scrollThread();
|
|
}
|
|
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) {
|
|
this.sendMessageInvoker(iGif.images.original.url);
|
|
}
|
|
handlePhoneCall(useVideo = false) {
|
|
const { onCall, conference, admin, adminOnly } = this.state;
|
|
if (window.navigator && onCall === false) {
|
|
try {
|
|
const overlay = document.createElement('div');
|
|
overlay.innerHTML = `
|
|
<div id="mic-highlight"></div>
|
|
<h4 id="mic-text">Grant Mic / Camera Permission</h4>
|
|
`;
|
|
overlay.id = 'mic-permission-overlay';
|
|
document.body.appendChild(overlay);
|
|
window.navigator
|
|
.getUserMedia({
|
|
audio: true,
|
|
})
|
|
.then(() => {
|
|
if (useVideo === true) {
|
|
window.navigator
|
|
.getUserMedia({
|
|
video: true,
|
|
})
|
|
.then(() => {
|
|
document.getElementById('mic-permission-overlay').remove();
|
|
this.mCallManager.toggleCall(onCall, conference, admin, adminOnly, useVideo);
|
|
})
|
|
.catch(() => {
|
|
document.getElementById('mic-permission-overlay').remove();
|
|
this.props.notify(
|
|
'Check your camera permission for this website. You can modify site permission by clicking on the lock (🔒) icon in the top left, then clicking site permissions.',
|
|
15000
|
|
);
|
|
});
|
|
} else {
|
|
document.getElementById('mic-permission-overlay').remove();
|
|
this.mCallManager.toggleCall(onCall, conference, admin, adminOnly, useVideo);
|
|
}
|
|
})
|
|
.catch(() => {
|
|
document.getElementById('mic-permission-overlay').remove();
|
|
this.props.notify(
|
|
'Check your mic permission for this website. You can modify site permission by clicking on the lock (🔒) icon in the top left, then clicking site permissions.',
|
|
15000
|
|
);
|
|
});
|
|
} catch (err) {
|
|
console.error('Handle Phone Call Error', err);
|
|
}
|
|
} else this.mCallManager.toggleCall(onCall, conference, admin, adminOnly, useVideo);
|
|
}
|
|
handleMute() {
|
|
this.mCallManager.toggleMute(this.state.muted);
|
|
}
|
|
getPrettyTime(t) {
|
|
return t > 3600
|
|
? new Date(t * 1000).toISOString().substr(11, 8)
|
|
: new Date(t * 1000).toISOString().substr(14, 5);
|
|
}
|
|
updateAllowChatNotification() {
|
|
updateLocalData({
|
|
allowChatNotification: !this.state.allowChatNotification,
|
|
});
|
|
this.setState({ allowChatNotification: !this.state.allowChatNotification });
|
|
}
|
|
updateAllowAutoRejoin() {
|
|
updateLocalData({ allowAutoRejoin: !this.state.allowAutoRejoin });
|
|
this.setState({ allowAutoRejoin: !this.state.allowAutoRejoin });
|
|
}
|
|
updateAllowAutoFollow() {
|
|
updateLocalData({ allowAutoFollow: !this.state.allowAutoFollow });
|
|
this.setState({ allowAutoFollow: !this.state.allowAutoFollow });
|
|
}
|
|
toggleFullscreen() {
|
|
if (document.fullscreen) {
|
|
document.exitFullscreen();
|
|
} else {
|
|
document.documentElement.requestFullscreen();
|
|
}
|
|
}
|
|
updateAdminOnly() {
|
|
this.setState({ adminModeUpdate: true });
|
|
UpdateAdminOnly({
|
|
partyName: this.state.name,
|
|
adminOnly: !this.state.adminOnly,
|
|
}).catch((err) => {
|
|
this.props.notify(err.message || err.statusText);
|
|
console.error('Update Admin Only API error', err);
|
|
});
|
|
}
|
|
setReplyTo(message) {
|
|
this.setState({
|
|
replyTo: message,
|
|
});
|
|
scrollThread();
|
|
}
|
|
getReplyToContent() {
|
|
const { replyTo } = this.state;
|
|
if (replyTo) {
|
|
if (replyTo.type === 'giphy') {
|
|
return <img alt="gif" src={replyTo.text} />;
|
|
}
|
|
return (
|
|
<Typography variant="body2" gutterBottom>
|
|
{replyTo.text}
|
|
</Typography>
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
handleVideo() {
|
|
const { usingVideo } = this.state;
|
|
this.mCallManager.toggleVideo(!usingVideo);
|
|
}
|
|
render() {
|
|
const {
|
|
admin,
|
|
name,
|
|
messages,
|
|
newMessage,
|
|
gif,
|
|
onCall,
|
|
muted,
|
|
player,
|
|
allowChatNotification,
|
|
users,
|
|
callActionPending,
|
|
allowAutoRejoin,
|
|
allowAutoFollow,
|
|
isFullscreen,
|
|
adminModeUpdate,
|
|
adminOnly,
|
|
replyTo,
|
|
sendingMessages,
|
|
usingVideo,
|
|
} = this.state;
|
|
const { status } = this.props;
|
|
const { callEnabled } = status;
|
|
const alerts = [
|
|
{
|
|
condition: () => adminOnly,
|
|
message: 'Party is currently in admin only mode.',
|
|
},
|
|
{
|
|
condition: () => getPlayerSource() === PLAYER_SOURCE.FUNIMATION,
|
|
message: 'If sync stops working, try (Ctrl + Shift + R) or (⌘ + Shift + R)',
|
|
},
|
|
{
|
|
condition: () => window.location.href.includes(PLAYER_SOURCE.PRIMEVIDEO),
|
|
message: 'Support on Prime Video is untested, and may be buggy.',
|
|
},
|
|
];
|
|
return (
|
|
<div className="party-chat-container">
|
|
<div className="party-chat-header">
|
|
<ExpansionPanel elevation={0} className="expander">
|
|
<ExpansionPanelSummary
|
|
expandIcon={
|
|
<Tooltip
|
|
placement="bottom-end"
|
|
title="Share, leave, and other controls"
|
|
aria-label="Share, leave, and other controls"
|
|
>
|
|
<KeyboardArrowDown />
|
|
</Tooltip>
|
|
}
|
|
>
|
|
<div className="party-chat-header-text">
|
|
<Typography
|
|
color="secondary"
|
|
className="brand-title"
|
|
variant="caption"
|
|
display="block"
|
|
>
|
|
WATCH PARTY
|
|
</Typography>
|
|
<Typography color="textPrimary" className="room-title" variant="h4" gutterBottom>
|
|
{name}
|
|
</Typography>
|
|
</div>
|
|
</ExpansionPanelSummary>
|
|
<ExpansionPanelDetails>
|
|
<TextField
|
|
className="share-textbox"
|
|
label="Party Details"
|
|
value={`Party Name: ${LocalData().partyName}\nParty Pin: ${LocalData().partyPin}\n${
|
|
window.location.href
|
|
}`}
|
|
multiline
|
|
rowsMax={2}
|
|
variant="outlined"
|
|
fullWidth
|
|
color="secondary"
|
|
InputProps={{
|
|
endAdornment: (
|
|
<Tooltip title="Share party" aria-label="Share party">
|
|
<InputAdornment position="end">
|
|
<IconButton onClick={this.shareParty}>
|
|
<Share />
|
|
</IconButton>
|
|
</InputAdornment>
|
|
</Tooltip>
|
|
),
|
|
}}
|
|
/>
|
|
<ButtonGroup
|
|
fullWidth
|
|
className="party-controls"
|
|
size="small"
|
|
disableElevation
|
|
variant="contained"
|
|
color="secondary"
|
|
>
|
|
<Button onClick={this.leaveParty} startIcon={<Exit />}>
|
|
<Typography variant="caption" className="party-control-button-text">
|
|
Leave
|
|
</Typography>
|
|
</Button>
|
|
{admin && (
|
|
<Button onClick={this.endParty} startIcon={<Delete />}>
|
|
<Typography variant="caption" className="party-control-button-text">
|
|
End
|
|
</Typography>
|
|
</Button>
|
|
)}
|
|
</ButtonGroup>
|
|
<FormControlLabel
|
|
className="notification-switch"
|
|
control={
|
|
<Switch
|
|
checked={allowChatNotification}
|
|
onChange={this.updateAllowChatNotification}
|
|
name="allowChatNotification"
|
|
color={allowChatNotification ? 'secondary' : 'primary'}
|
|
/>
|
|
}
|
|
label={
|
|
<span>
|
|
Allow chat notification when side panel is closed
|
|
<br />
|
|
<span style={{ color: '#8a8a8a' }}>
|
|
{allowChatNotification ? 'Notifications Enabled' : 'Notifications Disabled'}
|
|
</span>
|
|
</span>
|
|
}
|
|
labelPlacement="end"
|
|
/>
|
|
<FormControlLabel
|
|
className="notification-switch"
|
|
control={
|
|
<Switch
|
|
checked={allowAutoRejoin}
|
|
onChange={this.updateAllowAutoRejoin}
|
|
name="allowAutoRejoin"
|
|
color={allowAutoRejoin ? 'secondary' : 'primary'}
|
|
/>
|
|
}
|
|
label={
|
|
<span>
|
|
Allow Watch Party to auto rejoin (within 1 minute)
|
|
<br />
|
|
<span style={{ color: '#8a8a8a' }}>
|
|
{allowAutoRejoin ? 'Auto Rejoin Enabled' : 'Auto Rejoin Disabled'}
|
|
</span>
|
|
</span>
|
|
}
|
|
labelPlacement="end"
|
|
/>
|
|
{admin ? (
|
|
<FormControlLabel
|
|
className="notification-switch"
|
|
control={
|
|
<Switch
|
|
checked={adminOnly}
|
|
onChange={this.updateAdminOnly}
|
|
name="adminOnly"
|
|
color={adminOnly ? 'secondary' : 'primary'}
|
|
disabled={adminModeUpdate}
|
|
/>
|
|
}
|
|
label={
|
|
<span>
|
|
Admin Only Mode
|
|
<br />
|
|
<span style={{ color: '#8a8a8a' }}>
|
|
{adminOnly
|
|
? 'Only admin can update player and start calls'
|
|
: 'All party members can update player and start calls'}
|
|
</span>
|
|
</span>
|
|
}
|
|
labelPlacement="end"
|
|
/>
|
|
) : null}
|
|
<FormControlLabel
|
|
className="notification-switch"
|
|
control={
|
|
<Switch
|
|
checked={allowAutoFollow}
|
|
onChange={this.updateAllowAutoFollow}
|
|
name="allowAutoFollow"
|
|
/>
|
|
}
|
|
label={
|
|
<span>
|
|
Allow Watch Party to change to the currently playing
|
|
<br />
|
|
<span style={{ color: '#8a8a8a' }}>
|
|
{allowAutoFollow ? 'Auto Follow Enabled' : 'Auto Follow Disabled'}
|
|
</span>
|
|
</span>
|
|
}
|
|
labelPlacement="end"
|
|
/>
|
|
<Typography className="party-controls-users-title" variant="overline">
|
|
PARTY MEMBERS
|
|
</Typography>
|
|
<div className="party-controls-users-container">
|
|
{users.map((user) => (
|
|
<Chip className="party-controls-users-chip" clickable size="small" label={user} />
|
|
))}
|
|
</div>
|
|
</ExpansionPanelDetails>
|
|
</ExpansionPanel>
|
|
{player ? (
|
|
<Card elevation={0} className="media-info-card">
|
|
{player.showName && player.status !== PLAYER_STATUS.NOT_PLAYING ? (
|
|
<CardContent>
|
|
<Typography className="media-info-at" variant="overline">
|
|
{player.status === PLAYER_STATUS.PAUSED
|
|
? `PAUSED AT ${this.getPrettyTime(player.time)}`
|
|
: player.status === PLAYER_STATUS.PLAYING
|
|
? `CURRENTLY PLAYING (${this.getPrettyTime(player.time)})`
|
|
: `SHOW IS AT ${this.getPrettyTime(player.time)}`}
|
|
</Typography>
|
|
<div className="media-info-container">
|
|
{player.status === PLAYER_STATUS.PAUSED ? (
|
|
<PauseCircleFilled className="media-info-icon" />
|
|
) : (
|
|
<PlayCircleFilled className="media-info-icon" />
|
|
)}
|
|
<div className="media-info-right">
|
|
<Typography className="show-name" variant="subtitle1">
|
|
{player.showName}
|
|
</Typography>
|
|
</div>
|
|
</div>
|
|
<Typography
|
|
variant="overline"
|
|
style={{ fontSize: 9, display: 'block', marginTop: 12 }}
|
|
>
|
|
Watch Party will not automatically change the show for you.
|
|
</Typography>
|
|
</CardContent>
|
|
) : (
|
|
<CardContent>
|
|
<Typography style={{ fontSize: 12, marginBottom: 12 }} variant="h4">
|
|
Nothing playing right now
|
|
</Typography>
|
|
<Typography variant="overline" style={{ fontSize: 9 }}>
|
|
Pause / Play once after starting the show to start sync.
|
|
<br />
|
|
Watch Party will not automatically change the show for you.
|
|
</Typography>
|
|
</CardContent>
|
|
)}
|
|
{player && player.showName && player.status ? (
|
|
<Button className="media-info-link" href={player.url} startIcon={<LinkOutlined />}>
|
|
Link to currently playing
|
|
</Button>
|
|
) : null}
|
|
</Card>
|
|
) : null}
|
|
</div>
|
|
<div className="alerts-container">
|
|
{alerts.map((a) => {
|
|
if (a.condition() === true) {
|
|
return (
|
|
<Card className="alert-card" variant="outlined">
|
|
<CardContent className="alert-card-content">
|
|
<Error />
|
|
<Typography variant="body2">{a.message}</Typography>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
} else return null;
|
|
})}
|
|
</div>
|
|
<div className="vxt-video-container"></div>
|
|
<Thread setReplyTo={this.setReplyTo} sentMessages={messages} />
|
|
<Thread
|
|
isSending={true}
|
|
setReplyTo={this.setReplyTo}
|
|
sentMessages={messages}
|
|
sendingMessages={sendingMessages}
|
|
/>
|
|
<Paper
|
|
square
|
|
variant="outlined"
|
|
className={`replying-to-card ${replyTo !== null ? 'replying-to-card-up' : ''}`}
|
|
>
|
|
<div className="replying-to-content">
|
|
<Typography variant="overline" display="block" gutterBottom>
|
|
REPLYING TO
|
|
</Typography>
|
|
{this.getReplyToContent()}
|
|
</div>
|
|
<IconButton onClick={() => this.setReplyTo(null)}>
|
|
<CancelRounded />
|
|
</IconButton>
|
|
</Paper>
|
|
<div className="text-phone-panel">
|
|
{gif || newMessage ? null : (
|
|
<Tooltip title="Fullscreen with Watch Party" aria-label="Fullscreen">
|
|
<IconButton className="icon-button" color="secondary" onClick={this.toggleFullscreen}>
|
|
{isFullscreen ? <FullscreenExit /> : <Fullscreen />}
|
|
</IconButton>
|
|
</Tooltip>
|
|
)}
|
|
{gif || newMessage || !callEnabled ? null : callActionPending ? (
|
|
<CircularProgress className="icon-button" color="secondary" />
|
|
) : (
|
|
<Tooltip
|
|
placement="top"
|
|
title={onCall ? (usingVideo ? 'Stop Video' : 'Start Video') : 'Join Video Call'}
|
|
>
|
|
<IconButton
|
|
onClick={onCall ? () => this.handleVideo() : () => this.handlePhoneCall(true)}
|
|
color={usingVideo ? 'primary' : 'secondary'}
|
|
>
|
|
{usingVideo ? <VideocamOffRounded /> : <VideocamRounded />}
|
|
</IconButton>
|
|
</Tooltip>
|
|
)}
|
|
{gif || newMessage || !callEnabled ? null : callActionPending ? (
|
|
<CircularProgress className="icon-button" color="secondary" />
|
|
) : (
|
|
<Tooltip placement="top" title={onCall ? 'Leave Call' : 'Join Call'}>
|
|
<IconButton
|
|
disabled={callActionPending}
|
|
onClick={() => this.handlePhoneCall(false)}
|
|
color={!onCall ? 'secondary' : 'primary'}
|
|
className="icon-button"
|
|
>
|
|
{onCall ? <PhoneDisable /> : <PhoneEnable />}
|
|
</IconButton>
|
|
</Tooltip>
|
|
)}
|
|
{!gif && !newMessage && onCall && !muted ? (
|
|
<Tooltip title="Mute Mic" placement="top">
|
|
<IconButton
|
|
className="icon-button"
|
|
disabled={callActionPending}
|
|
onClick={this.handleMute}
|
|
color="primary"
|
|
>
|
|
<MicOff />
|
|
</IconButton>
|
|
</Tooltip>
|
|
) : !gif && !newMessage && onCall && muted ? (
|
|
<Tooltip title="Unmute Mic" placement="top">
|
|
<IconButton
|
|
className="icon-button"
|
|
disabled={callActionPending}
|
|
onClick={this.handleMute}
|
|
color="secondary"
|
|
>
|
|
<MicOn />
|
|
</IconButton>
|
|
</Tooltip>
|
|
) : null}
|
|
<TextField
|
|
color="secondary"
|
|
multiline
|
|
rowsMax="3"
|
|
size="small"
|
|
placeholder={this.state.gif ? 'Search something...' : 'Say something...'}
|
|
helperText={
|
|
this.state.newMessage && !this.state.gif
|
|
? 'Enter to send / Shift+Enter for new line'
|
|
: this.state.gif
|
|
? 'Click on gif to send / Scroll to load more'
|
|
: null
|
|
}
|
|
variant="outlined"
|
|
className="chat-message-box addFocus"
|
|
value={newMessage}
|
|
onChange={(e) => {
|
|
this.setState({ newMessage: e.target.value });
|
|
scrollThread();
|
|
if (gif) {
|
|
window.postMessage({
|
|
watchPartyDirection: 'TO_SCRIPT',
|
|
type: 'WATCH_PARTY_SEARCH_GIF',
|
|
value: e.target.value,
|
|
});
|
|
}
|
|
}}
|
|
InputProps={{
|
|
id: 'chat-message-box-input',
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
<IconButton
|
|
className={`${this.state.gif ? 'gif-active' : ''} ${
|
|
this.state.newMessage ? 'message-active' : ''
|
|
} message-send`}
|
|
size="small"
|
|
onClick={() => this.sendMessageInvoker(newMessage)}
|
|
edge="end"
|
|
>
|
|
<Send />
|
|
</IconButton>
|
|
</InputAdornment>
|
|
),
|
|
startAdornment: (
|
|
<InputAdornment position="start">
|
|
<IconButton
|
|
className={`gif-button ${this.state.gif ? 'gif-close' : ''}`}
|
|
onClick={() => {
|
|
this.setState({ gif: !gif });
|
|
if (!gif) {
|
|
window.postMessage({
|
|
watchPartyDirection: 'TO_SCRIPT',
|
|
type: 'WATCH_PARTY_SEARCH_GIF',
|
|
value: newMessage,
|
|
});
|
|
scrollThread();
|
|
}
|
|
}}
|
|
edge="start"
|
|
>
|
|
{gif ? <Cancel /> : <Gif />}
|
|
</IconButton>
|
|
</InputAdornment>
|
|
),
|
|
}}
|
|
/>
|
|
</div>
|
|
<ReactGiphySearchbox
|
|
apiKey="ojJbIMUN6XRMn4mHiFFav3rMCpXUg8sq"
|
|
onSelect={this.gifClick}
|
|
masonryConfig={[{ columns: 2, imageWidth: '50%', gutter: 5 }]}
|
|
searchFormClassName="giphy-search-form"
|
|
wrapperClassName={`giphy-wrapper ${gif ? '' : 'giphy-wrapper-down'}`}
|
|
listWrapperClassName="giphy-list-wrapper"
|
|
listItemClassName="giphy-list-item"
|
|
poweredByGiphy
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
}
|