Files
watchparty/app/src/Create.js
T
ayushya 16a11f7163 v1.7.8/release
- Video Listener Optimizations
- Auto Leave Fix
- Edge support
- Admin Dashboard Update
- Vimeo & Dailymotion
2020-11-16 23:06:44 -05:00

673 lines
21 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.
************************************************************************/
/*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,
setTutorialStatus,
} 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,
publicParty: 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,
allowAutoFollow: LocalData().allowAutoFollow || false,
expanded: "alerts",
};
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.updateAllowAutoFollow = this.updateAllowAutoFollow.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();
// CONTINUE TUTORIAL
if (this.props.tutorialComplete === false) {
this.props.resetTutorial();
}
if (
this.props.showFab === true &&
allowAutoRejoin === true &&
lastActive &&
userName &&
partyName &&
partyPin
) {
if (
Math.abs(new Date(parseInt(lastActive)) - new Date().getTime()) <= 60000
) {
this.props.updateLoading(true);
this.props.setVisible(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 || this.state.publicParty,
publicParty: this.state.publicParty,
};
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 });
}
updateAllowAutoFollow() {
updateLocalData({ allowAutoFollow: !this.state.allowAutoFollow });
this.setState({ allowAutoFollow: !this.state.allowAutoFollow });
}
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 });
}
handleExpanders(e) {
const { expanded } = this.state;
if (e === expanded) this.setState({ expanded: "" });
else this.setState({ expanded: e });
}
render() {
const {
mode,
name,
pin,
userName,
parties,
adminOnly,
publicParty,
allowAutoRejoin,
allowAutoFollow,
allowChatNotification,
alertStep,
expanded,
} = this.state;
const { status, resetTutorial } = 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 || publicParty}
onChange={() => this.setState({ adminOnly: !adminOnly })}
name="adminOnly"
color={adminOnly || publicParty ? "secondary" : "primary"}
/>
}
label={
<span>
Admin Only Mode (can be changed later)
<br />
<span style={{ color: "#8a8a8a" }}>
{adminOnly || publicParty
? "Only admin will update currently playing and start calls"
: "All members will update currently playing and start calls"}
</span>
</span>
}
labelPlacement="end"
/>
{/* <FormControlLabel
className={`notification-switch admin-only-switch ${
mode !== "create" ? "admin-only-switch-hidden" : ""
}`}
control={
<Switch
checked={publicParty}
onChange={() => this.setState({ publicParty: !publicParty })}
name="publicParty"
color={publicParty ? "secondary" : "primary"}
/>
}
label={
<span>
Public Party (can be changed later)
<br />
<span style={{ color: "#8a8a8a" }}>
{publicParty
? "Party will be listed and available to join from Watch Party website"
: "Only people you share with can join"}
</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
expanded={expanded === "alerts"}
className="create-settings-card create-alerts-card"
>
<ExpansionPanelSummary
onClick={() => this.handleExpanders("alerts")}
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
expanded={expanded === "settings"}
className="create-settings-card create-settings-expander"
>
<ExpansionPanelSummary
onClick={() => this.handleExpanders("settings")}
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"
/>
<FormControlLabel
className="notification-switch"
control={
<Switch
checked={allowAutoFollow}
onChange={this.updateAllowAutoFollow}
name="allowAutoFollow"
/>
}
label={
<span>
Allow Watch Party to change to the currently playing show
<br />
<span style={{ color: "#8a8a8a" }}>
{allowAutoFollow
? "Auto Follow Enabled"
: "Auto Follow Disabled"}
</span>
</span>
}
labelPlacement="end"
/>
<Button
color="secondary"
fullWidth
variant="contained"
onClick={() => {
setTutorialStatus(false);
resetTutorial();
}}
size="small"
>
START TUTORIAL
</Button>
<Button
color="secondary"
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://www.chromewatchparty.com/donate"
>
Donate
</Button>
</div>
</div>
);
}
}