Files
watchparty/app/src/Create.js
T

496 lines
17 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 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.
Netflix logo is a trademark and intellectual property of Netflix, Inc.
Disney+ logo is a trademark and intellectual property of The Walt Disney Company
Prime logo is a trademark and intellectual property of Amazon.com, Inc.
YouTube logo is a trademark and intellectual property of Google, Inc.
Hulu logo is a trademark and intellectual property of Hulu, LLC.
HBO Now and HBO Max logos are trademark and intellectual property of WarnerMedia Direct,LLC.
************************************************************************/
/*global chrome */
import React, { Component } from 'react';
import { Button, FormControlLabel, Switch, TextField, Typography, Paper, ButtonGroup, ExpansionPanel, ExpansionPanelSummary, ExpansionPanelDetails, MobileStepper, IconButton, Card, CardContent, CardActions } from '@material-ui/core';
import {
JoinRoomApi,
CreateRoomApi,
PLAYER_STATUS,
getPlayerSource,
LocalData,
updateLocalData,
getUserUid,
emptyLocalData,
} from './Api';
import { goTo } from 'route-lite';
import Chat from './Chat';
import {
ChevronRight,
InfoRounded,
MonetizationOnRounded,
RateReviewRounded,
Settings,
ExpandLess,
KeyboardArrowLeft,
KeyboardArrowRight,
Notifications
} from '@material-ui/icons';
import { Autocomplete } from "@material-ui/lab";
const CONSTANTS = {
USER_NAME_LENGTH: 12,
NAME_LENGTH: 24,
PIN_LENGTH: 9,
};
export default class Create extends Component {
constructor(props) {
super(props);
const defaultMode = LocalData().partyName ? 'join' : 'create';
this.state = {
userName: LocalData().userName || '',
name: LocalData().partyName || '',
pin: LocalData().partyPin || '',
mode: defaultMode,
parties: [],
adminOnly: false,
alertStep: this.props.status && this.props.status.importantAlert ? this.props.status.importantAlert : 0,
allowAutoRejoin:
LocalData().allowAutoRejoin !== undefined ? LocalData().allowAutoRejoin : true,
allowChatNotification:
LocalData().allowChatNotification !== undefined ? LocalData().allowChatNotification : true,
};
this.createCall = this.createCall.bind(this);
this.joinCall = this.joinCall.bind(this);
this.success = this.success.bind(this);
this.partyNameAutocompleteProcessor = this.partyNameAutocompleteProcessor.bind(this);
this.updateAllowAutoRejoin = this.updateAllowAutoRejoin.bind(this);
this.updateAllowChatNotification = this.updateAllowChatNotification.bind(this);
this.clearPartyHistory = this.clearPartyHistory.bind(this);
this.handleSteps = this.handleSteps.bind(this);
}
componentDidMount() {
const { lastActive, userName, partyName, partyPin, allowAutoRejoin } = LocalData();
if (allowAutoRejoin === true && lastActive && userName && partyName && partyPin) {
if (Math.abs(new Date(parseInt(lastActive)) - new Date().getTime()) <= 60000) {
this.props.updateLoading(true);
this.joinCall(userName, partyName, partyPin);
}
}
chrome.runtime.sendMessage(
{
type: 'GET_PARTIES'
},
(response) => {
this.setState({ parties: response });
}
);
}
partyNameChange(value) {
if (value.length <= CONSTANTS.NAME_LENGTH) this.setState({ name: value });
}
partyPinChange(value) {
if (value.length <= CONSTANTS.PIN_LENGTH) this.setState({ pin: value });
}
userNameChange(value) {
if (value.length <= CONSTANTS.USER_NAME_LENGTH) this.setState({ userName: value });
}
createOrJoin() {
this.props.updateLoading(true);
getUserUid();
const { userName, name, pin, mode } = this.state;
if (!userName.replace(/\s+/g, '')) {
this.props.notify('Missing user name');
this.props.updateLoading(false);
return;
}
if (!name.replace(/\s+/g, '')) {
this.props.notify('Missing party name');
this.props.updateLoading(false);
return;
}
if (!pin.replace(/\s+/g, '')) {
this.props.notify('Missing party pin');
this.props.updateLoading(false);
return;
}
if (userName && name && pin) {
switch (mode) {
case 'create':
this.createCall(userName.trim(), name.trim(), pin);
break;
case 'join':
this.joinCall(userName.trim(), name.trim(), pin);
break;
default:
this.props.updateLoading(false);
break;
}
} else {
this.props.notify('Double check your inputs');
}
}
createCall(userName, partyName, partyPin) {
this.props.updateLoading(true);
getUserUid();
const apidata = {
userName,
partyName,
partyPin,
player: {
source: getPlayerSource(),
status: PLAYER_STATUS.PAUSED,
time: 0,
url: window.location.href,
},
adminOnly: this.state.adminOnly,
};
CreateRoomApi(apidata)
.then((res) => res.json())
.then((createResult) => {
if (createResult.status <= 299) {
this.success(userName, partyName, partyPin);
} else {
this.props.updateLoading(false);
this.props.notify(createResult.message || createResult.statusText);
}
})
.catch((err) => {
console.error('Create Party API Error', err);
this.props.notify(err.message || 'Something went wrong');
this.props.updateLoading(false);
});
}
joinCall(userName, partyName, partyPin) {
const apidata = {
userName,
partyName,
partyPin,
source: getPlayerSource(),
};
getUserUid();
JoinRoomApi(apidata)
.then((res) => res.json())
.then((joinResult) => {
if (joinResult.status <= 299) {
this.success(userName, partyName, partyPin);
} else {
this.props.updateLoading(false);
this.props.notify(joinResult.message || joinResult.statusText);
}
})
.catch((err) => {
emptyLocalData();
console.error('Join Party API Error', err);
this.props.notify(err.message || 'Something went wrong');
this.props.updateLoading(false);
});
}
success(userName, partyName, partyPin) {
updateLocalData({ userName, partyName, partyPin });
this.props.updateLoading(false);
goTo(Chat, this.props);
}
partyNameAutocompleteProcessor(v) {
if (v !== null && v !== undefined && typeof (v) === 'object') {
if (v.name) this.partyNameChange(v.name);
else this.partyNameChange('');
if (v.pin) this.partyPinChange(v.pin);
else this.partyPinChange('');
} else if (typeof (v) === 'object' && (v === null || v === undefined)) {
this.partyNameChange('');
this.partyPinChange('');
} else if (v !== undefined && v !== null && typeof (v) === 'string') {
this.partyNameChange(v);
}
}
updateAllowChatNotification() {
updateLocalData({ allowChatNotification: !this.state.allowChatNotification });
this.setState({ allowChatNotification: !this.state.allowChatNotification });
}
updateAllowAutoRejoin() {
updateLocalData({ allowAutoRejoin: !this.state.allowAutoRejoin });
this.setState({ allowAutoRejoin: !this.state.allowAutoRejoin });
}
clearPartyHistory () {
this.setState({
parties: [],
});
chrome.runtime.sendMessage({type: 'EMPTY_PARTIES'});
}
handleSteps (direction) {
const { alertStep } = this.state;
if (alertStep + direction < 0) return;
if (alertStep + direction >= this.props.status.alerts.length) return;
this.setState({ alertStep: alertStep + direction });
}
render() {
const {
mode,
name,
pin,
userName,
parties,
adminOnly,
allowAutoRejoin,
allowChatNotification,
alertStep
} = this.state;
const { status } = this.props;
return (
<div className="party-create-container">
<Typography
color="secondary"
className="brand-title"
variant="h6"
display="block"
gutterBottom
>
WATCH PARTY
</Typography>
<Paper className="create-input-card" elevation={3}>
<ButtonGroup className="create-mode-select" size="small" fullWidth>
<Button
id="mode-select-left"
variant={mode === 'create' ? 'contained' : null}
color={mode === 'create' ? 'secondary' : null}
disableElevation={mode === 'create'}
onClick={() => this.setState({ mode: 'create' })}
>
Create
</Button>
<Button
id="mode-select-right"
variant={mode === 'join' ? 'contained' : null}
color={mode === 'join' ? 'secondary' : null}
disableElevation={mode === 'join'}
onClick={() => this.setState({ mode: 'join' })}
>
Join
</Button>
</ButtonGroup>
<TextField
color="secondary"
size="small"
className="create-name addFocus"
label="Name"
variant="outlined"
value={userName}
onChange={(e) => this.userNameChange(e.target.value)}
helperText={
userName ? `${userName.length}/${CONSTANTS.USER_NAME_LENGTH} Characters` : null
}
/>
<Autocomplete
color="secondary"
options={parties}
getOptionLabel={o => o.name}
autoComplete
freeSolo
value={{name:name, pin: pin}}
onInputChange={(e,v,r) => this.partyNameAutocompleteProcessor({name: v, pin: pin})}
onChange={(e, v) => this.partyNameAutocompleteProcessor(v)}
renderInput={(params) =>
<TextField
color="secondary"
size="small"
{...params}
className="create-name addFocus"
label="Party Name"
variant="outlined"
helperText={name ? `${name.length}/${CONSTANTS.NAME_LENGTH} Characters` : null}
/>}
/>
<TextField
color="secondary"
size="small"
className="create-pin addFocus"
label="Party Pin"
variant="outlined"
type="number"
value={pin}
onChange={(e) => this.partyPinChange(e.target.value)}
helperText={pin ? `${pin.length}/${CONSTANTS.PIN_LENGTH} Digits` : null}
/>
<FormControlLabel
className={`notification-switch admin-only-switch ${mode !== 'create' ? 'admin-only-switch-hidden' : ''}`}
control={
<Switch
checked={adminOnly}
onChange={() => this.setState({ adminOnly: !adminOnly })}
name="adminOnly"
color={adminOnly ? 'secondary' : 'primary'}
/>
}
label={
<span>
Admin Only Mode (can be changed later)
<br />
<span style={{ color: '#8a8a8a' }}>
{adminOnly ? 'Only admin will update currently playing and start calls' : 'All members will update currently playing and start calls'}
</span>
</span>
}
labelPlacement="end"
/>
<Button
size="small"
variant="contained"
color="secondary"
onClick={() => this.createOrJoin()}
>
<span>{mode === 'create' ? 'Start Party' : 'Join Party'}</span> <ChevronRight />
</Button>
</Paper>
<ExpansionPanel defaultExpanded className="create-settings-card" >
<ExpansionPanelSummary expandIcon={<ExpandLess />}>
<Notifications />
<Typography className="create-settings-title" variant="button">
Alerts
</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
{status ?
<div className="alerts-stepper-container">
<Card className="alerts-stepper-card" elevation={10}>
<CardContent>
<Typography color="textSecondary" gutterBottom variant="overline">
{status.alerts[alertStep].title}
</Typography>
<Typography variant="body2" component="p">
{status.alerts[alertStep].description}
</Typography>
</CardContent>
{
status.alerts[alertStep].link &&
status.alerts[alertStep].linkText &&
<CardActions>
<Button href={status.alerts[alertStep].link} target="_blank" size="small">{status.alerts[alertStep].linkText}</Button>
</CardActions>
}
</Card>
<MobileStepper
className="alerts-stepper-controls"
variant="dots"
color="secondary"
activeStep={alertStep}
backButton={<IconButton disabled={alertStep === 0} onClick={() => this.handleSteps(-1)}> <KeyboardArrowLeft /> </IconButton>}
nextButton={<IconButton disabled={alertStep === (status.alerts.length - 1)} onClick={() => this.handleSteps(1)}> <KeyboardArrowRight /> </IconButton>}
steps={status.alerts ? status.alerts.length : 0}
/>
</div>
: (<Typography color="textSecondary" gutterBottom variant="overline">
No alerts right now
</Typography>)
}
</ExpansionPanelDetails>
</ExpansionPanel>
<ExpansionPanel className="create-settings-card" >
<ExpansionPanelSummary expandIcon={<ExpandLess />}>
<Settings />
<Typography className="create-settings-title" variant="button">
Settings
</Typography>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<FormControlLabel
className="notification-switch"
control={
<Switch
checked={allowChatNotification}
onChange={this.updateAllowChatNotification}
name="allowChatNotification"
/>
}
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"
/>
}
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"
/>
<Button
fullWidth
variant="contained"
onClick={this.clearPartyHistory}
size="small"
>
CLEAR SAVED PARTY DETAILS
</Button>
</ExpansionPanelDetails>
</ExpansionPanel>
<Button
target="_blank"
href={`http://www.chromewatchparty.com/changes/v${this.props.appVersion}`}
fullWidth
color="secondary"
variant="contained"
size="small"
className="whats-new-button"
>
See what's new in V{this.props.appVersion}
</Button>
<div className="create-bottom-card">
<Button
color="secondary"
target="_blank"
startIcon={<InfoRounded />}
size="small"
href="https://www.chromewatchparty.com/welcome/#tips"
>
FAQ
</Button>
<Button
color="secondary"
target="_blank"
startIcon={<RateReviewRounded />}
size="small"
href="https://get.chromewatchparty.com"
>
Review
</Button>
<Button
color="secondary"
target="_blank"
startIcon={<MonetizationOnRounded />}
size="small"
href="https://paypal.me/Ayushya"
>
Donate
</Button>
</div>
</div>
);
}
}