Redone UI, add leave and delete logic
This commit is contained in:
+182
-56
@@ -4,7 +4,7 @@ const secret = require('./FirebaseSecret.json');
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
|
|
||||||
const authenticator = (req,res,next) => {
|
const authenticator = (req, res, next) => {
|
||||||
if (req.method === 'OPTIONS') {
|
if (req.method === 'OPTIONS') {
|
||||||
res.status(200).send();
|
res.status(200).send();
|
||||||
return next();
|
return next();
|
||||||
@@ -13,86 +13,212 @@ const authenticator = (req,res,next) => {
|
|||||||
const token = req.headers.authorization.split(' ')[1];
|
const token = req.headers.authorization.split(' ')[1];
|
||||||
const reqToken = token.split(':')[0];
|
const reqToken = token.split(':')[0];
|
||||||
const reqUid = token.split(':')[1];
|
const reqUid = token.split(':')[1];
|
||||||
admin.auth().verifyIdToken(reqToken).then( user => {
|
admin
|
||||||
if (user.uid === reqUid) {
|
.auth()
|
||||||
console.log('Verified Token - UID - ', reqUid);
|
.verifyIdToken(reqToken)
|
||||||
if (req.method === 'POST') {
|
.then((user) => {
|
||||||
const bodydata = JSON.parse(req.body);
|
if (user.uid === reqUid) {
|
||||||
bodydata.uid = user.uid;
|
console.log('Verified Token - UID - ', reqUid);
|
||||||
req.body = JSON.stringify(bodydata);
|
if (req.method === 'POST') {
|
||||||
|
const bodydata = JSON.parse(req.body);
|
||||||
|
bodydata.uid = user.uid;
|
||||||
|
req.body = JSON.stringify(bodydata);
|
||||||
|
}
|
||||||
|
if (req.method === 'GET' || req.method === 'DELETE') {
|
||||||
|
req.body = JSON.stringify({ uid: user.uid });
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
} else {
|
||||||
|
return res.status(401).send('Unauthorized');
|
||||||
}
|
}
|
||||||
if (req.method === 'GET') {
|
})
|
||||||
req.body = JSON.stringify({uid: user.uid});
|
.catch((err) => {
|
||||||
}
|
console.log('Failed Auth - ', req.headers.authorization, '\n', err);
|
||||||
return next();
|
return res.status(500).send(err);
|
||||||
} else {
|
});
|
||||||
return res.status(401).send('Unauthorized');
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
console.log('Failed Auth - ', req.headers.authorization, '\n', err);
|
|
||||||
return res.status(500).send(err);
|
|
||||||
});
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createRoom = async (req,res) => {
|
const createRoom = async (req, res) => {
|
||||||
const bodydata = JSON.parse(req.body);
|
const bodydata = JSON.parse(req.body);
|
||||||
console.log('Requested Create - UID - ', bodydata.uid);
|
console.log('Requested Create - UID - ', bodydata.uid);
|
||||||
if (!bodydata.userName) res.status(400).send('Missing user name');
|
if (!bodydata.userName) res.status(400).send('Missing user name');
|
||||||
const roomId = await admin.firestore().collection('rooms').add({
|
const roomId = await admin
|
||||||
name: bodydata.roomName,
|
.firestore()
|
||||||
createdAt: new Date().toISOString(),
|
.collection('rooms')
|
||||||
updatedAt: new Date().toISOString(),
|
.add({
|
||||||
}).then(ref => ref.id)
|
name: bodydata.roomName,
|
||||||
.catch(err => {
|
createdAt: new Date().toISOString(),
|
||||||
console.error(err);
|
updatedAt: new Date().toISOString(),
|
||||||
});
|
})
|
||||||
await admin.firestore().collection('rooms').doc(roomId).collection('messages').add({
|
.then((ref) => ref.id)
|
||||||
uid: '<system>',
|
.catch((err) => {
|
||||||
name: '',
|
console.error(err);
|
||||||
text: `${bodydata.userName} started the party.`,
|
});
|
||||||
time: new Date().toISOString(),
|
await admin
|
||||||
});
|
.firestore()
|
||||||
await admin.firestore().collection('rooms').doc(roomId).collection('users').doc(bodydata.uid).set({
|
.collection('rooms')
|
||||||
admin: true,
|
.doc(roomId)
|
||||||
name: bodydata.userName,
|
.collection('messages')
|
||||||
});
|
.add({
|
||||||
|
uid: '<system>',
|
||||||
|
name: '',
|
||||||
|
text: `${bodydata.userName} started the party.`,
|
||||||
|
time: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
await admin
|
||||||
|
.firestore()
|
||||||
|
.collection('rooms')
|
||||||
|
.doc(roomId)
|
||||||
|
.collection('users')
|
||||||
|
.doc(bodydata.uid)
|
||||||
|
.set({
|
||||||
|
admin: true,
|
||||||
|
name: bodydata.userName,
|
||||||
|
});
|
||||||
res.status(201).send({ roomId });
|
res.status(201).send({ roomId });
|
||||||
}
|
};
|
||||||
|
|
||||||
const joinRoom = async (req, res) => {
|
const joinRoom = async (req, res) => {
|
||||||
const bodydata = JSON.parse(req.body);
|
const bodydata = JSON.parse(req.body);
|
||||||
console.log('Requested Create - UID - ', bodydata.uid, '\n\tRoom - ', bodydata.roomId);
|
console.log('Requested Join - UID - ', bodydata.uid, '\n\tRoom - ', bodydata.roomId);
|
||||||
const exists = await admin.firestore().collection('rooms').doc(bodydata.roomId).get().then(roomSnap => roomSnap.exists);
|
const exists = await admin
|
||||||
const userExists = await admin.firestore().collection('rooms').doc(bodydata.roomId).collection('users').doc(bodydata.uid).get().then(userSnap => userSnap.exists);
|
.firestore()
|
||||||
if (userExists) return res.status(200).send({roomId: bodydata.roomId});
|
.collection('rooms')
|
||||||
|
.doc(bodydata.roomId)
|
||||||
|
.get()
|
||||||
|
.then((roomSnap) => roomSnap.exists);
|
||||||
|
const userExists = await admin
|
||||||
|
.firestore()
|
||||||
|
.collection('rooms')
|
||||||
|
.doc(bodydata.roomId)
|
||||||
|
.collection('users')
|
||||||
|
.doc(bodydata.uid)
|
||||||
|
.get()
|
||||||
|
.then((userSnap) => userSnap.exists);
|
||||||
|
if (userExists) return res.status(200).send({ roomId: bodydata.roomId });
|
||||||
if (exists && bodydata.roomId && bodydata.userName) {
|
if (exists && bodydata.roomId && bodydata.userName) {
|
||||||
await admin.firestore().collection('rooms').doc(bodydata.roomId).collection('messages').add({
|
await admin
|
||||||
uid: '<system>',
|
.firestore()
|
||||||
name: '',
|
.collection('rooms')
|
||||||
text: `${bodydata.userName} joined the party.`,
|
.doc(bodydata.roomId)
|
||||||
time: new Date().toISOString(),
|
.collection('messages')
|
||||||
|
.add({
|
||||||
|
uid: '<system>',
|
||||||
|
name: '',
|
||||||
|
text: `${bodydata.userName} joined the party.`,
|
||||||
|
time: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
await admin
|
||||||
|
.firestore()
|
||||||
|
.collection('rooms')
|
||||||
|
.doc(bodydata.roomId)
|
||||||
|
.collection('users')
|
||||||
|
.doc(bodydata.uid)
|
||||||
|
.set({
|
||||||
|
admin: false,
|
||||||
|
name: bodydata.userName,
|
||||||
|
});
|
||||||
|
await admin.firestore().collection('rooms').doc(bodydata.roomId).update({
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
await admin.firestore().collection('rooms').doc(bodydata.roomId).collection('users').doc(bodydata.uid).set({
|
res.status(200).send({ roomId: bodydata.roomId });
|
||||||
admin: false,
|
|
||||||
name: bodydata.userName,
|
|
||||||
});
|
|
||||||
res.status(200).send({roomId: bodydata.roomId});
|
|
||||||
}
|
}
|
||||||
if (!bodydata.userName) res.status(400).send('Missing user name');
|
if (!bodydata.userName) res.status(400).send('Missing user name');
|
||||||
else res.status(404).send('Party Not Found');
|
else res.status(404).send('Party Not Found');
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const sendMessage = async (req, res) => {
|
||||||
|
const { name, text, time, uid, roomId } = JSON.parse(req.body);
|
||||||
|
await admin
|
||||||
|
.firestore()
|
||||||
|
.collection('rooms')
|
||||||
|
.doc(roomId)
|
||||||
|
.collection('messages')
|
||||||
|
.add({
|
||||||
|
name,
|
||||||
|
text,
|
||||||
|
time,
|
||||||
|
uid,
|
||||||
|
})
|
||||||
|
.catch((messageErr) => res.status(500).send(messageErr));
|
||||||
|
await admin.firestore().collection('rooms').doc(bodydata.roomId).update({
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const leaveRoom = async (req, res) => {
|
||||||
|
console.log('Requested Leave - UID - ', req.params.uid, '\n\tRoom - ', req.params.rid);
|
||||||
|
await admin
|
||||||
|
.firestore()
|
||||||
|
.collection('rooms')
|
||||||
|
.doc(req.params.rid)
|
||||||
|
.collection('users')
|
||||||
|
.doc(req.params.uid)
|
||||||
|
.delete()
|
||||||
|
.catch((err) => res.status(500).send(err));
|
||||||
|
res.status(200).send();
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteRoom = async (req, res) => {
|
||||||
|
console.log('Requested Delete - Room - ', req.params.rid);
|
||||||
|
console.log(req.body);
|
||||||
|
const isAdmin = await admin
|
||||||
|
.firestore()
|
||||||
|
.collection('rooms')
|
||||||
|
.doc(req.params.rid)
|
||||||
|
.collection('users')
|
||||||
|
.doc(JSON.parse(req.body).uid)
|
||||||
|
.get()
|
||||||
|
.then((v) => {
|
||||||
|
if (v.exists) {
|
||||||
|
return v.data().admin;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (!isAdmin) {
|
||||||
|
console.log('[UNAUTHORIZED] Requested Delete - Room - ', req.params.rid);
|
||||||
|
res.status(401).send();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await admin
|
||||||
|
.firestore()
|
||||||
|
.collection('rooms')
|
||||||
|
.doc(req.params.rid)
|
||||||
|
.collection('messages')
|
||||||
|
.listDocuments()
|
||||||
|
.then((messages) => messages.forEach((message) => message.delete()))
|
||||||
|
.catch((err) => res.status(500).send(err));
|
||||||
|
await admin
|
||||||
|
.firestore()
|
||||||
|
.collection('rooms')
|
||||||
|
.doc(req.params.rid)
|
||||||
|
.collection('users')
|
||||||
|
.listDocuments()
|
||||||
|
.then((users) => users.forEach((user) => user.delete()))
|
||||||
|
.catch((err) => res.status(500).send(err));
|
||||||
|
await admin
|
||||||
|
.firestore()
|
||||||
|
.collection('rooms')
|
||||||
|
.doc(req.params.rid)
|
||||||
|
.delete()
|
||||||
|
.catch((err) => res.status(500).send(err));
|
||||||
|
res.status(200).send();
|
||||||
|
};
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(cors({origin: true}));
|
app.use(cors({ origin: true }));
|
||||||
app.use(authenticator);
|
app.use(authenticator);
|
||||||
|
|
||||||
admin.initializeApp({
|
admin.initializeApp({
|
||||||
credential: admin.credential.cert(secret),
|
credential: admin.credential.cert(secret),
|
||||||
databaseURL: "https://watch-party-crx.firebaseio.com"
|
databaseURL: 'https://watch-party-crx.firebaseio.com',
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/create', createRoom);
|
app.post('/create', createRoom);
|
||||||
app.post('/join', joinRoom);
|
app.post('/join', joinRoom);
|
||||||
|
app.post('/message', sendMessage);
|
||||||
|
app.delete('/leave/:rid/users/:uid', leaveRoom);
|
||||||
|
app.delete('/delete/:rid', deleteRoom);
|
||||||
|
|
||||||
exports.widgets = functions.https.onRequest(app);
|
exports.widgets = functions.https.onRequest(app);
|
||||||
Generated
+353
-1601
File diff suppressed because it is too large
Load Diff
+4
-5
@@ -3,10 +3,8 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^4.0.4",
|
"@material-ui/core": "^4.9.10",
|
||||||
"@giphy/js-fetch-api": "^1.5.2",
|
"@material-ui/icons": "^4.9.1",
|
||||||
"@giphy/react-components": "^0.7.1",
|
|
||||||
"antd": "^4.0.4",
|
|
||||||
"autoprefixer": "7.1.6",
|
"autoprefixer": "7.1.6",
|
||||||
"babel-core": "6.26.3",
|
"babel-core": "6.26.3",
|
||||||
"babel-eslint": "10.x",
|
"babel-eslint": "10.x",
|
||||||
@@ -26,7 +24,7 @@
|
|||||||
"eslint-plugin-jsx-a11y": "6.x",
|
"eslint-plugin-jsx-a11y": "6.x",
|
||||||
"eslint-plugin-react": "7.x",
|
"eslint-plugin-react": "7.x",
|
||||||
"file-loader": "4.2.0",
|
"file-loader": "4.2.0",
|
||||||
"firebase": "^7.12.0",
|
"firebase": "^7.14.0",
|
||||||
"fs-extra": "3.0.1",
|
"fs-extra": "3.0.1",
|
||||||
"html-webpack-plugin": "^4.0.0-beta.8",
|
"html-webpack-plugin": "^4.0.0-beta.8",
|
||||||
"jest": "20.0.4",
|
"jest": "20.0.4",
|
||||||
@@ -42,6 +40,7 @@
|
|||||||
"react-dom": "^16.4.1",
|
"react-dom": "^16.4.1",
|
||||||
"react-frame-component": "^4.0.0",
|
"react-frame-component": "^4.0.0",
|
||||||
"resolve": "1.6.0",
|
"resolve": "1.6.0",
|
||||||
|
"route-lite": "^0.3.0",
|
||||||
"style-loader": "0.19.0",
|
"style-loader": "0.19.0",
|
||||||
"sw-precache-webpack-plugin": "^0.11.5",
|
"sw-precache-webpack-plugin": "^0.11.5",
|
||||||
"url-loader": "0.6.2",
|
"url-loader": "0.6.2",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"browser_action": {
|
"browser_action": {
|
||||||
"default_popup": "popup.html"
|
"default_popup": "popup.html"
|
||||||
},
|
},
|
||||||
"permissions": ["https://*.hulu.com/*", "https://*.netflix.com/*"],
|
"permissions": ["https://*.hulu.com/*", "https://*.netflix.com/*", "https://*.amazon.com/*"],
|
||||||
"content_scripts": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"matches": ["https://*.hulu.com/*"],
|
"matches": ["https://*.hulu.com/*"],
|
||||||
@@ -22,8 +22,15 @@
|
|||||||
"run_at": "document_idle",
|
"run_at": "document_idle",
|
||||||
"js": ["/static/js/content.js"],
|
"js": ["/static/js/content.js"],
|
||||||
"css": ["/static/css/app.css"]
|
"css": ["/static/css/app.css"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matches": ["https://*.amazon.com/*"],
|
||||||
|
"run_at": "document_idle",
|
||||||
|
"js": ["/static/js/content.js"],
|
||||||
|
"css": ["/static/css/app.css"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"web_accessible_resources": ["logo192.png"],
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "favicon.ico",
|
"16": "favicon.ico",
|
||||||
"32": "favicon.ico",
|
"32": "favicon.ico",
|
||||||
|
|||||||
+9
-14
@@ -7,7 +7,7 @@ process.env.NODE_ENV = 'development';
|
|||||||
// Makes the script crash on unhandled rejections instead of silently
|
// Makes the script crash on unhandled rejections instead of silently
|
||||||
// ignoring them. In the future, promise rejections that are not handled will
|
// ignoring them. In the future, promise rejections that are not handled will
|
||||||
// terminate the Node.js process with a non-zero exit code.
|
// terminate the Node.js process with a non-zero exit code.
|
||||||
process.on('unhandledRejection', err => {
|
process.on('unhandledRejection', (err) => {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -51,9 +51,7 @@ if (process.env.HOST) {
|
|||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
console.log(
|
console.log(`If this was unintentional, check that you haven't mistakenly set it in your shell.`);
|
||||||
`If this was unintentional, check that you haven't mistakenly set it in your shell.`
|
|
||||||
);
|
|
||||||
console.log(`Learn more here: ${chalk.yellow('http://bit.ly/2mwWSwH')}`);
|
console.log(`Learn more here: ${chalk.yellow('http://bit.ly/2mwWSwH')}`);
|
||||||
console.log();
|
console.log();
|
||||||
}
|
}
|
||||||
@@ -61,7 +59,7 @@ if (process.env.HOST) {
|
|||||||
// We attempt to use the default port but if it is busy, we offer the user to
|
// We attempt to use the default port but if it is busy, we offer the user to
|
||||||
// run on a different port. `choosePort()` Promise resolves to the next free port.
|
// run on a different port. `choosePort()` Promise resolves to the next free port.
|
||||||
choosePort(HOST, DEFAULT_PORT)
|
choosePort(HOST, DEFAULT_PORT)
|
||||||
.then(port => {
|
.then((port) => {
|
||||||
if (port == null) {
|
if (port == null) {
|
||||||
// We have not found a port.
|
// We have not found a port.
|
||||||
return;
|
return;
|
||||||
@@ -70,18 +68,15 @@ choosePort(HOST, DEFAULT_PORT)
|
|||||||
const appName = require(paths.appPackageJson).name;
|
const appName = require(paths.appPackageJson).name;
|
||||||
const urls = prepareUrls(protocol, HOST, port);
|
const urls = prepareUrls(protocol, HOST, port);
|
||||||
// Create a webpack compiler that is configured with custom messages.
|
// Create a webpack compiler that is configured with custom messages.
|
||||||
const compiler = createCompiler({webpack, config, appName, urls, useYarn});
|
const compiler = createCompiler({ webpack, config, appName, urls, useYarn });
|
||||||
// Load proxy config
|
// Load proxy config
|
||||||
const proxySetting = require(paths.appPackageJson).proxy;
|
const proxySetting = require(paths.appPackageJson).proxy;
|
||||||
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
|
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
|
||||||
// Serve webpack assets generated by the compiler over a web sever.
|
// Serve webpack assets generated by the compiler over a web sever.
|
||||||
const serverConfig = createDevServerConfig(
|
const serverConfig = createDevServerConfig(proxyConfig, urls.lanUrlForConfig);
|
||||||
proxyConfig,
|
|
||||||
urls.lanUrlForConfig
|
|
||||||
);
|
|
||||||
const devServer = new WebpackDevServer(compiler, serverConfig);
|
const devServer = new WebpackDevServer(compiler, serverConfig);
|
||||||
// Launch WebpackDevServer.
|
// Launch WebpackDevServer.
|
||||||
devServer.listen(port, HOST, err => {
|
devServer.listen(port, HOST, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return console.log(err);
|
return console.log(err);
|
||||||
}
|
}
|
||||||
@@ -92,14 +87,14 @@ choosePort(HOST, DEFAULT_PORT)
|
|||||||
openBrowser(urls.localUrlForBrowser);
|
openBrowser(urls.localUrlForBrowser);
|
||||||
});
|
});
|
||||||
|
|
||||||
['SIGINT', 'SIGTERM'].forEach(function(sig) {
|
['SIGINT', 'SIGTERM'].forEach(function (sig) {
|
||||||
process.on(sig, function() {
|
process.on(sig, function () {
|
||||||
devServer.close();
|
devServer.close();
|
||||||
process.exit();
|
process.exit();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err) => {
|
||||||
if (err && err.message) {
|
if (err && err.message) {
|
||||||
console.log(err.message);
|
console.log(err.message);
|
||||||
}
|
}
|
||||||
|
|||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
import firebase from 'firebase/app';
|
||||||
|
import 'firebase/auth';
|
||||||
|
|
||||||
|
const API_HOST = window.location.href.startsWith('http://localhost')
|
||||||
|
? 'http://localhost:5001/watch-party-crx/us-central1/widgets'
|
||||||
|
: 'https://us-central1-watch-party-crx.cloudfunctions.net/widgets';
|
||||||
|
|
||||||
|
export const isAuthed = () => true && firebase.auth().currentUser;
|
||||||
|
|
||||||
|
export const doAuth = () => firebase.auth().signInAnonymously();
|
||||||
|
|
||||||
|
export const fetcher = async ({ ENDPOINT, METHOD, ...args }) => {
|
||||||
|
const idToken = await firebase.auth().currentUser.getIdToken();
|
||||||
|
return fetch(`${API_HOST}/${ENDPOINT}`, {
|
||||||
|
method: METHOD,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Token ${idToken}:${firebase.auth().currentUser.uid}`,
|
||||||
|
},
|
||||||
|
...args,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CreateRoomApi = async (data) => {
|
||||||
|
if (!isAuthed()) await doAuth();
|
||||||
|
return fetcher({
|
||||||
|
ENDPOINT: 'create',
|
||||||
|
METHOD: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const JoinRoomApi = async (data) => {
|
||||||
|
if (!isAuthed()) await doAuth();
|
||||||
|
return fetcher({
|
||||||
|
ENDPOINT: 'join',
|
||||||
|
METHOD: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SendMessageApi = async (data) => {
|
||||||
|
if (!isAuthed()) await doAuth();
|
||||||
|
return fetcher({
|
||||||
|
ENDPOINT: 'message',
|
||||||
|
METHOD: 'post',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LeaveRoomApi = async (data) => {
|
||||||
|
if (!isAuthed()) await doAuth();
|
||||||
|
return fetcher({
|
||||||
|
ENDPOINT: `leave/${data.roomId}/users/${data.uid}`,
|
||||||
|
METHOD: 'DELETE',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DeleteRoomApi = async (data) => {
|
||||||
|
if (!isAuthed()) await doAuth();
|
||||||
|
return fetcher({
|
||||||
|
ENDPOINT: `delete/${data.roomId}`,
|
||||||
|
METHOD: 'DELETE',
|
||||||
|
});
|
||||||
|
};
|
||||||
+40
-60
@@ -1,65 +1,45 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useState } from 'react';
|
||||||
import firebase from 'firebase/app';
|
import Router from 'route-lite';
|
||||||
import Chat from './Chat';
|
import { Fab, createMuiTheme, ThemeProvider } from '@material-ui/core';
|
||||||
import Create from './Create';
|
import Create from './Create';
|
||||||
import Loading from './Loading';
|
|
||||||
|
|
||||||
export default class App extends Component {
|
const darkTheme = createMuiTheme({
|
||||||
constructor(props) {
|
palette: {
|
||||||
super(props);
|
type: 'dark',
|
||||||
this.state = {
|
primary: {
|
||||||
loading: false,
|
main: '#a6d4fa',
|
||||||
loggedIn: false,
|
},
|
||||||
page: 'create',
|
},
|
||||||
};
|
});
|
||||||
this.updateLoading = this.updateLoading.bind(this);
|
|
||||||
this.changePage = this.changePage.bind(this);
|
const Routes = (props) => (
|
||||||
|
<Router>
|
||||||
|
<Create />
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const PartyRoutesWrapper = document.getElementById('party-routes-wrapper');
|
||||||
|
if (visible) {
|
||||||
|
document.body.classList.add('visible');
|
||||||
|
PartyRoutesWrapper && PartyRoutesWrapper.classList.add('visible');
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('visible');
|
||||||
|
PartyRoutesWrapper && PartyRoutesWrapper.classList.remove('visible');
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
return (
|
||||||
firebase.auth().onAuthStateChanged(user => {
|
<ThemeProvider theme={darkTheme}>
|
||||||
const roomId = window.localStorage.getItem('roomId');
|
|
||||||
if (user) {
|
|
||||||
this.setState({
|
|
||||||
loggedIn: true,
|
|
||||||
});
|
|
||||||
if (roomId)
|
|
||||||
this.setState({
|
|
||||||
page: 'chat',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (roomId) {
|
|
||||||
window.localStorage.removeItem('roomId');
|
|
||||||
window.localStorage.removeItem('watchPartyUserName');
|
|
||||||
window.localStorage.removeItem('watchPartyUser');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
changePage(page) {
|
|
||||||
this.setState({
|
|
||||||
page,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
updateLoading(loading) {
|
|
||||||
this.setState({
|
|
||||||
loading,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
const { loading, loggedIn, page } = this.state;
|
|
||||||
return (
|
|
||||||
<div className="party-content-wrapper">
|
<div className="party-content-wrapper">
|
||||||
{loading && <Loading />}
|
<div id="party-routes-wrapper">
|
||||||
{page === 'chat' ? (
|
<Routes />
|
||||||
<Chat changePage={this.changePage} updateLoading={this.updateLoading} />
|
</div>
|
||||||
) : page === 'create' ? (
|
<Fab onClick={() => setVisible(!visible)} className="watch-party-fab">
|
||||||
<Create
|
<span />
|
||||||
changePage={this.changePage}
|
</Fab>
|
||||||
loggedIn={loggedIn}
|
|
||||||
updateLoading={this.updateLoading}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
</ThemeProvider>
|
||||||
}
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|||||||
+149
-40
@@ -1,61 +1,170 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { Typography, Input, message } from 'antd';
|
import {
|
||||||
|
Typography,
|
||||||
|
TextField,
|
||||||
|
InputAdornment,
|
||||||
|
IconButton,
|
||||||
|
ButtonGroup,
|
||||||
|
Button,
|
||||||
|
} from '@material-ui/core';
|
||||||
import firebase from 'firebase/app';
|
import firebase from 'firebase/app';
|
||||||
import 'firebase/firestore';
|
import 'firebase/firestore';
|
||||||
import { RightCircleFilled } from '@ant-design/icons';
|
import 'firebase/auth';
|
||||||
import Thread from './Thread';
|
import Thread from './Thread';
|
||||||
|
import Send from '@material-ui/icons/SendRounded';
|
||||||
|
import Sync from '@material-ui/icons/SyncOutlined';
|
||||||
|
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 } from './Api';
|
||||||
|
import { goTo } from 'route-lite';
|
||||||
|
import Create from './Create';
|
||||||
|
|
||||||
export default class Chat extends Component {
|
export default class Chat extends Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
messages: []
|
name: '',
|
||||||
|
messages: [],
|
||||||
|
newMessage: '',
|
||||||
|
admin: false,
|
||||||
};
|
};
|
||||||
|
this.sendMessage = this.sendMessage.bind(this);
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
firebase.firestore()
|
window.addEventListener('keyup', (ev) => {
|
||||||
.collection('rooms')
|
if (ev.code === 'Enter') {
|
||||||
.doc(window.localStorage.getItem('roomId'))
|
if (ev.target === document.getElementById('chat-message-box-input')) {
|
||||||
.onSnapshot(snap => {
|
if (ev.shiftKey === false) {
|
||||||
this.setState(snap.data());
|
const trim = this.state.newMessage.replace(/^\s+|\s+$/g, '');
|
||||||
}, err => {
|
if (trim !== '') this.sendMessage(trim);
|
||||||
this.props.updateLoading(true);
|
this.setState({ newMessage: '' });
|
||||||
if (err.code === 'permission-denied') {
|
}
|
||||||
message.error('Couldn\'t join old party');
|
}
|
||||||
this.props.changePage('create');
|
}
|
||||||
this.props.updateLoading(false);
|
});
|
||||||
|
firebase
|
||||||
|
.firestore()
|
||||||
|
.collection('rooms')
|
||||||
|
.doc(window.localStorage.getItem('watchPartyRoomId'))
|
||||||
|
.onSnapshot(
|
||||||
|
(snap) => {
|
||||||
|
this.setState(snap.data());
|
||||||
|
},
|
||||||
|
(err) => console.error(err)
|
||||||
|
);
|
||||||
|
firebase
|
||||||
|
.firestore()
|
||||||
|
.collection('rooms')
|
||||||
|
.doc(window.localStorage.getItem('watchPartyRoomId'))
|
||||||
|
.collection('users')
|
||||||
|
.onSnapshot((snap) => {
|
||||||
|
const isAdmin = snap.docs
|
||||||
|
.filter((user) => user.id === firebase.auth().currentUser.uid)[0]
|
||||||
|
.data().admin;
|
||||||
|
this.setState({ admin: isAdmin });
|
||||||
|
});
|
||||||
|
firebase
|
||||||
|
.firestore()
|
||||||
|
.collection('rooms')
|
||||||
|
.doc(window.localStorage.getItem('watchPartyRoomId'))
|
||||||
|
.collection('messages')
|
||||||
|
.orderBy('time')
|
||||||
|
.onSnapshot((snap) => {
|
||||||
|
this.setState({ messages: snap.docs.map((v) => ({ ...v.data(), id: v.id })) });
|
||||||
|
const pcc = document.getElementsByClassName('thread-container')[0];
|
||||||
|
if (pcc) pcc.scrollTop = pcc.scrollHeight;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
endParty() {
|
||||||
|
const data = {
|
||||||
|
roomId: window.localStorage.getItem('watchPartyRoomId'),
|
||||||
|
};
|
||||||
|
DeleteRoomApi(data).then((res) => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
window.localStorage.removeItem('watchPartyRoomId');
|
||||||
|
goTo(Create);
|
||||||
}
|
}
|
||||||
})
|
|
||||||
firebase.firestore()
|
|
||||||
.collection('rooms')
|
|
||||||
.doc(window.localStorage.getItem('roomId'))
|
|
||||||
.collection('messages')
|
|
||||||
.onSnapshot(snap => {
|
|
||||||
this.setState({messages: snap.docs.map(v => ({ ...v.data(), id: v.id}) )});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
leaveParty() {
|
||||||
|
const data = {
|
||||||
|
roomId: window.localStorage.getItem('watchPartyRoomId'),
|
||||||
|
uid: firebase.auth().currentUser.uid,
|
||||||
|
};
|
||||||
|
LeaveRoomApi(data).then((res) => {
|
||||||
|
if (res.status === 200) {
|
||||||
|
window.localStorage.removeItem('watchPartyRoomId');
|
||||||
|
goTo(Create);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
sendMessage(message) {
|
||||||
|
const data = {
|
||||||
|
roomId: window.localStorage.getItem('watchPartyRoomId'),
|
||||||
|
name: window.localStorage.getItem('watchPartyUserName'),
|
||||||
|
uid: firebase.auth().currentUser.uid,
|
||||||
|
text: message,
|
||||||
|
time: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
SendMessageApi(data);
|
||||||
|
}
|
||||||
render() {
|
render() {
|
||||||
const {messages, name} = this.state;
|
const { admin, name, messages, newMessage } = this.state;
|
||||||
return (
|
return (
|
||||||
<div className="content__wrapper">
|
<div className="party-chat-container">
|
||||||
<div>
|
<Typography className="brand-title" variant="overline" display="block">
|
||||||
<Typography.Title level={3}>{name}</Typography.Title>
|
WATCH PARTY
|
||||||
<Typography.Text underline>Watch Party</Typography.Text>
|
</Typography>
|
||||||
</div>
|
<Typography className="room-title" variant="h4" gutterBottom>
|
||||||
<div className="party_chat_container">
|
{name}
|
||||||
|
</Typography>
|
||||||
|
<ButtonGroup className="party-controls" size="small" color="secondary" fullWidth>
|
||||||
|
<Button onClick={this.shareParty} startIcon={<Share />}>
|
||||||
|
Share
|
||||||
|
</Button>
|
||||||
|
<Button startIcon={<Sync />}>Sync</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} />
|
<Thread messages={messages} />
|
||||||
</div>
|
</div>
|
||||||
<div className="party_chat_input">
|
<TextField
|
||||||
<Input.TextArea
|
onSubmit={(e) => console.log(e)}
|
||||||
autoSize={{
|
multiline
|
||||||
maxRows:3,
|
rowsMax="3"
|
||||||
minRows:1,
|
size="small"
|
||||||
}}
|
placeholder="New message... (<Enter> to send)"
|
||||||
id="chat_input"
|
variant="outlined"
|
||||||
placeholder="Begin typing..."
|
className="chat-message-box"
|
||||||
/>
|
value={newMessage}
|
||||||
<RightCircleFilled id="chat_send" />
|
onChange={(e) => this.setState({ newMessage: e.target.value })}
|
||||||
</div>
|
InputProps={{
|
||||||
|
id: 'chat-message-box-input',
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton edge="end">
|
||||||
|
<Send />
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+117
-131
@@ -1,143 +1,129 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import firebase from 'firebase/app';
|
import {
|
||||||
import 'firebase/auth';
|
Button,
|
||||||
import { Button, Input, Divider, Typography, message } from 'antd';
|
InputAdornment,
|
||||||
import { PlusCircleFilled, PlayCircleFilled } from '@ant-design/icons';
|
TextField,
|
||||||
|
Typography,
|
||||||
|
IconButton,
|
||||||
|
Paper,
|
||||||
|
} from '@material-ui/core';
|
||||||
|
import Save from '@material-ui/icons/Save';
|
||||||
|
import { JoinRoomApi, CreateRoomApi } from './Api';
|
||||||
|
import { goTo } from 'route-lite';
|
||||||
|
import Chat from './Chat';
|
||||||
|
|
||||||
const login = async () => firebase.auth().signInAnonymously();
|
const saveName = (name) => window.localStorage.setItem('watchPartyUserName', name);
|
||||||
|
const getName = () => window.localStorage.getItem('watchPartyUserName');
|
||||||
|
|
||||||
const Create = props => {
|
const saveRoomId = (rid) => window.localStorage.setItem('watchPartyRoomId', rid);
|
||||||
const [userName, setUserName] = useState(window.localStorage.getItem('watchPartyUserName') || '');
|
const getRoomId = () => window.localStorage.getItem('watchPartyRoomId');
|
||||||
const [roomName, setRoomName] = useState('');
|
|
||||||
const [roomCode, setRoomCode] = useState(window.localStorage.getItem('roomId') || '');
|
|
||||||
|
|
||||||
const requestCreateRoom = async () => {
|
const joinRoom = async (data) => {
|
||||||
props.updateLoading(true);
|
const apidata = {
|
||||||
if (userName === '') {
|
roomId: data.joinName.trim(),
|
||||||
message.error('You forgot your name');
|
userName: data.name.trim(),
|
||||||
props.updateLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (roomName === '') {
|
|
||||||
message.error('You forgot to give a party name');
|
|
||||||
props.updateLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!props.loggedIn) await login();
|
|
||||||
firebase
|
|
||||||
.auth()
|
|
||||||
.currentUser.getIdToken()
|
|
||||||
.then(idToken => {
|
|
||||||
fetch('http://localhost:5001/watch-party-crx/us-central1/widgets/create', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Token ${idToken}:${firebase.auth().currentUser.uid}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
roomName,
|
|
||||||
userName,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(res => {
|
|
||||||
res.roomId && window.localStorage.setItem('roomId', res.roomId);
|
|
||||||
window.localStorage.setItem('watchPartyUserName', userName);
|
|
||||||
window.localStorage.setItem('watchPartyUser', firebase.auth().currentUser.uid);
|
|
||||||
props.changePage('chat');
|
|
||||||
props.updateLoading(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
const confirmedRoomId = await JoinRoomApi(apidata)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((res) => res.roomId)
|
||||||
|
.catch((err) => console.error('Join Room API error', err));
|
||||||
|
if (confirmedRoomId === apidata.roomId) {
|
||||||
|
saveRoomId(confirmedRoomId);
|
||||||
|
saveName(apidata.userName);
|
||||||
|
goTo(Chat);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const requestJoinRoom = async () => {
|
const createRoom = async (data) => {
|
||||||
props.updateLoading(true);
|
const apidata = {
|
||||||
if (userName === '') {
|
roomName: data.createName,
|
||||||
message.error('You forgot your name');
|
userName: data.name,
|
||||||
props.updateLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (roomCode === '') {
|
|
||||||
message.error('Please enter a party code');
|
|
||||||
props.updateLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!props.loggedIn) await login();
|
|
||||||
firebase
|
|
||||||
.auth()
|
|
||||||
.currentUser.getIdToken()
|
|
||||||
.then(idToken => {
|
|
||||||
fetch(`http://localhost:5001/watch-party-crx/us-central1/widgets/join`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Token ${idToken}:${firebase.auth().currentUser.uid}`,
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
roomId: roomCode.trim(),
|
|
||||||
userName,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
if (res.status === 200) return res.json();
|
|
||||||
if (res.status === 404) message.error("That party doesn't exist");
|
|
||||||
return {};
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
res.roomId && window.localStorage.setItem('roomId', res.roomId);
|
|
||||||
window.localStorage.setItem('watchPartyUserName', userName);
|
|
||||||
window.localStorage.setItem('watchPartyUser', firebase.auth().currentUser.uid);
|
|
||||||
props.changePage('chat');
|
|
||||||
props.updateLoading(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
CreateRoomApi(apidata)
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((res) => {
|
||||||
|
saveRoomId(res.roomId);
|
||||||
|
saveName(data.name);
|
||||||
|
goTo(Chat);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const Create = (props) => {
|
||||||
|
const [name, setName] = useState(getName() || '');
|
||||||
|
const [createName, setCreateName] = useState('');
|
||||||
|
const [joinName, setJoinName] = useState(getRoomId() || '');
|
||||||
|
if (getName() && getRoomId())
|
||||||
|
joinRoom({
|
||||||
|
joinName: getRoomId(),
|
||||||
|
name: getName(),
|
||||||
|
});
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="party-create-container">
|
||||||
style={{
|
<Typography className="brand-title" variant="h6" display="block" gutterBottom>
|
||||||
padding: 20,
|
WATCH PARTY
|
||||||
flex: 1,
|
</Typography>
|
||||||
display: 'flex',
|
<div className="party-create-inputs">
|
||||||
justifyContent: 'center',
|
<TextField
|
||||||
alignItems: 'center',
|
fullWidth
|
||||||
flexDirection: 'column',
|
value={name}
|
||||||
position: 'relative',
|
onChange={(e) => setName(e.target.value)}
|
||||||
}}
|
className="name-field"
|
||||||
>
|
label="Name"
|
||||||
<Typography.Title style={{ position: 'absolute', top: 20, left: 20 }} level={3}>
|
variant="filled"
|
||||||
Watch Party
|
InputProps={{
|
||||||
</Typography.Title>
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
<Input
|
<IconButton onClick={() => saveName(name)}>
|
||||||
style={{ borderRadius: 23, marginBottom: 15 }}
|
<Save />
|
||||||
value={userName}
|
</IconButton>
|
||||||
onChange={e => setUserName(e.target.value)}
|
</InputAdornment>
|
||||||
placeholder="Your Name"
|
),
|
||||||
size="large"
|
}}
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Paper className="join-card" elevation={3}>
|
||||||
<Input
|
<Typography variant="button" display="block" gutterBottom>
|
||||||
style={{ borderRadius: 23, marginBottom: 15 }}
|
CREATE PARTY
|
||||||
value={roomName}
|
</Typography>
|
||||||
onChange={e => setRoomName(e.target.value)}
|
<TextField
|
||||||
placeholder="Room Name"
|
fullWidth
|
||||||
size="large"
|
value={createName}
|
||||||
/>
|
className="card-field"
|
||||||
<Button onClick={requestCreateRoom} block size="large" type="primary" shape="round">
|
onChange={(e) => setCreateName(e.target.value)}
|
||||||
<PlusCircleFilled style={{ color: 'white' }} />
|
label="Party Name"
|
||||||
Start Watch Party
|
/>
|
||||||
</Button>
|
<Button
|
||||||
<Divider />
|
onClick={() => createRoom({ name, createName })}
|
||||||
<Input
|
disabled={createName === ''}
|
||||||
style={{ borderRadius: 23, marginBottom: 15 }}
|
color="primary"
|
||||||
value={roomCode}
|
fullWidth
|
||||||
onChange={e => setRoomCode(e.target.value)}
|
variant="contained"
|
||||||
placeholder="Room Code"
|
>
|
||||||
size="large"
|
CREATE
|
||||||
/>
|
</Button>
|
||||||
<Button onClick={requestJoinRoom} block size="large" shape="round">
|
</Paper>
|
||||||
<PlayCircleFilled />
|
<Paper className="join-card" elevation={3}>
|
||||||
Join Watch Party
|
<Typography variant="button" display="block" gutterBottom>
|
||||||
</Button>
|
JOIN PARTY
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
color="secondary"
|
||||||
|
fullWidth
|
||||||
|
value={joinName}
|
||||||
|
className="card-field"
|
||||||
|
onChange={(e) => setJoinName(e.target.value)}
|
||||||
|
label="Party Name"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() => joinRoom({ joinName, name })}
|
||||||
|
disabled={joinName === ''}
|
||||||
|
variant="contained"
|
||||||
|
color="secondary"
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
JOIN
|
||||||
|
</Button>
|
||||||
|
</Paper>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+55
-1
@@ -1,10 +1,64 @@
|
|||||||
.thread-item {
|
.thread-item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
margin-top: 25px !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.thread-item.no-space {
|
||||||
|
margin-top: 0 !important;
|
||||||
}
|
}
|
||||||
.system-message {
|
.system-message {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: rgba(100,100,100,0.8) !important;
|
font-size: 12px;
|
||||||
|
margin-top: 7.5px !important;
|
||||||
|
color: rgba(100, 100, 100, 0.8) !important;
|
||||||
|
}
|
||||||
|
.my-message {
|
||||||
|
display: block;
|
||||||
|
width: fit-content;
|
||||||
|
text-align: right;
|
||||||
|
margin-left: auto !important;
|
||||||
|
margin-right: 5px !important;
|
||||||
|
margin-top: 5px !important;
|
||||||
|
padding: 2.5px 10px !important;
|
||||||
|
border-radius: 13px;
|
||||||
|
max-width: 60%;
|
||||||
|
word-wrap: break-word;
|
||||||
|
background-color: rgb(49, 49, 49);
|
||||||
|
}
|
||||||
|
.party-message {
|
||||||
|
display: block;
|
||||||
|
width: fit-content;
|
||||||
|
max-width: 60%;
|
||||||
|
word-wrap: break-word;
|
||||||
|
margin-top: 5px !important;
|
||||||
|
padding: 2.5px 10px !important;
|
||||||
|
border-radius: 13px;
|
||||||
|
background-color: rgb(88, 88, 88);
|
||||||
|
}
|
||||||
|
.thread-item .name {
|
||||||
|
color: rgb(149, 149, 149);
|
||||||
|
font-size: 10px !important;
|
||||||
|
}
|
||||||
|
.time {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 50%;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all ease-in-out 0.3s;
|
||||||
|
}
|
||||||
|
.party-message ~ .time {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.party-message:hover ~ .time {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.my-message ~ .time {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.my-message:hover ~ .time {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
+42
-6
@@ -1,13 +1,49 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Typography } from 'antd';
|
import { Typography } from '@material-ui/core';
|
||||||
import './Thread.css';
|
import './Thread.css';
|
||||||
|
import firebase from 'firebase/app';
|
||||||
|
import 'firebase/auth';
|
||||||
|
|
||||||
const Thread = props =>
|
const Thread = (props) =>
|
||||||
props.messages.map(m => {
|
props.messages.map((m, midx) => {
|
||||||
console.log(m);
|
|
||||||
if (m.uid === '<system>') {
|
if (m.uid === '<system>') {
|
||||||
return <Typography.Text className="thread-item system-message" key={m.id} disabled>{m.text}</Typography.Text>
|
return (
|
||||||
|
<Typography variant="body2" className="thread-item system-message" key={m.id} disabled>
|
||||||
|
{m.text}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
} else if (m.uid === firebase.auth().currentUser.uid) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`thread-item ${
|
||||||
|
midx > 0 ? (props.messages[midx - 1].uid !== m.uid ? '' : 'no-space') : ''
|
||||||
|
}`}
|
||||||
|
key={m.id}
|
||||||
|
>
|
||||||
|
<Typography variant="body1" className="my-message">
|
||||||
|
{m.text}
|
||||||
|
</Typography>
|
||||||
|
<span className="time">{new Date(m.time).toLocaleTimeString()}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`thread-item ${
|
||||||
|
midx > 0 ? (props.messages[midx - 1].uid !== m.uid ? '' : 'no-space') : ''
|
||||||
|
}`}
|
||||||
|
key={m.id}
|
||||||
|
>
|
||||||
|
<Typography variant="caption" className="name">
|
||||||
|
{midx > 0 ? (props.messages[midx - 1].uid !== m.uid ? m.name : '') : m.name}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" className="party-message">
|
||||||
|
{m.text}
|
||||||
|
</Typography>
|
||||||
|
<span className="time">{new Date(m.time).toLocaleTimeString()}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Thread;
|
export default Thread;
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
/* #party_close {
|
|
||||||
display: inline;
|
|
||||||
margin-right: 0.625rem;
|
|
||||||
}
|
|
||||||
#__player__ .Player__container {
|
|
||||||
width: 75%;
|
|
||||||
resize: horizontal;
|
|
||||||
}
|
|
||||||
#__next {
|
|
||||||
width: 75%;
|
|
||||||
resize: horizontal;
|
|
||||||
}
|
|
||||||
#party_create {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
} */
|
|
||||||
+135
-4
@@ -1,7 +1,138 @@
|
|||||||
@import '~antd/dist/antd.css';
|
body {
|
||||||
.party-content-wrapper {
|
margin: 0 !important;
|
||||||
position: relative;
|
background-color: black;
|
||||||
|
transition: max-width ease-in-out 0.35s;
|
||||||
|
}
|
||||||
|
body.visible {
|
||||||
|
width: 65%;
|
||||||
|
}
|
||||||
|
body.visible .watch-party-fab {
|
||||||
|
right: calc(35% - 4rem) !important;
|
||||||
|
top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.netflix.visible .AkiraPlayer, /* NETFLIX */
|
||||||
|
body.hulu.visible .Player__container.addFocus,/* HULU */
|
||||||
|
body.amazon-prime.visible #dv-web-player.dv-player-fullscreen /* AMAZON PRIME */ {
|
||||||
|
max-width: 65% !important;
|
||||||
|
}
|
||||||
|
/* HULU MINIMIZED VIDEO */
|
||||||
|
body.hulu.visible #__player__.Player__container.Player__container--modal {
|
||||||
|
right: calc(35% + 2rem) !important;
|
||||||
|
}
|
||||||
|
/* HULU & AMAZON PRIME FIX */
|
||||||
|
body.hulu #party-routes-wrapper,
|
||||||
|
body.amazon-prime #party-routes-wrapper {
|
||||||
|
width: 35% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
/* NETFLIX HEADER FIX */
|
||||||
|
body.netflix .pinning-header-container {
|
||||||
|
right: 35% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#party-routes-wrapper {
|
||||||
|
margin: 0;
|
||||||
|
position: fixed;
|
||||||
|
background-color: rgb(27, 27, 27);
|
||||||
|
bottom: 0;
|
||||||
|
width: calc(35% - 2rem);
|
||||||
|
right: -35%;
|
||||||
|
transition: all ease-in-out 0.3s;
|
||||||
|
height: calc(100% - 2rem);
|
||||||
|
padding: 1rem;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
#party-routes-wrapper.visible {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.watch-party-fab {
|
||||||
|
z-index: 9999;
|
||||||
|
position: fixed !important;
|
||||||
|
bottom: 1rem !important;
|
||||||
|
right: -2rem !important;
|
||||||
|
background-color: black !important;
|
||||||
|
/* background-image: url('/logo192.png'); */
|
||||||
|
background-image: url('chrome-extension://__MSG_@@extension_id__/logo192.png');
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 2rem;
|
||||||
|
transition: all ease-in-out 0.3s !important;
|
||||||
|
}
|
||||||
|
.watch-party-fab:hover {
|
||||||
|
right: 1rem !important;
|
||||||
|
}
|
||||||
|
.watch-party-fab .fab-logo {
|
||||||
|
max-width: 65%;
|
||||||
|
max-height: 65%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.party-create-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
}
|
||||||
|
.party-create-container .brand-title {
|
||||||
|
padding-top: 1.4rem !important;
|
||||||
|
padding-left: 3.5rem !important;
|
||||||
|
}
|
||||||
|
.party-create-container .name-field {
|
||||||
|
margin-bottom: 2rem !important;
|
||||||
|
}
|
||||||
|
.party-create-container .join-card {
|
||||||
|
padding: 1rem !important;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
.party-create-container .join-card .card-field {
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
}
|
||||||
|
.party-create-container .party-create-inputs {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.party-chat-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
max-height: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thread-container {
|
||||||
|
max-height: 100%;
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
.room-title,
|
||||||
|
.brand-title {
|
||||||
|
padding-left: 3.5rem !important;
|
||||||
|
}
|
||||||
|
.thread-container::-webkit-scrollbar {
|
||||||
|
width: 5px;
|
||||||
|
}
|
||||||
|
.thread-container::-webkit-scrollbar-track {
|
||||||
|
background-color: rgb(90, 90, 90);
|
||||||
|
border-radius: 1rem;
|
||||||
|
}
|
||||||
|
.thread-container::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgb(118, 182, 255);
|
||||||
|
border-radius: 13px;
|
||||||
|
}
|
||||||
|
textarea::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.chat-message-box > div {
|
||||||
|
border-radius: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.party-controls {
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
+10
-18
@@ -1,11 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import firebase from 'firebase/app';
|
import firebase from 'firebase/app';
|
||||||
import 'antd/dist/antd.css';
|
|
||||||
import './netflix.css';
|
|
||||||
import './hulu.css';
|
|
||||||
import './index.css';
|
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
const firebaseConfig = {
|
const firebaseConfig = {
|
||||||
apiKey: 'AIzaSyCJLuYpzXLDRmu2ttzFuSGmYcpFxqToWFU',
|
apiKey: 'AIzaSyCJLuYpzXLDRmu2ttzFuSGmYcpFxqToWFU',
|
||||||
@@ -24,20 +21,15 @@ const PartyWrapper = document.createElement('div');
|
|||||||
PartyWrapper.id = 'party_wrapper';
|
PartyWrapper.id = 'party_wrapper';
|
||||||
document.body.appendChild(PartyWrapper);
|
document.body.appendChild(PartyWrapper);
|
||||||
|
|
||||||
// STYLINGS
|
const HREF = window.location.href;
|
||||||
if (window.location.href.startsWith('https://www.hulu.com')) {
|
|
||||||
// HULU
|
// https://www.netflix. -- netflix
|
||||||
} else if (window.location.href.startsWith('https://www.netflix.com')) {
|
// https://www.hulu. -- hulu
|
||||||
// NETFLIX
|
// https://www.amazon. -- amazon prime
|
||||||
PartyWrapper.classList.add('netflix');
|
|
||||||
document.body.classList.add('netflix');
|
if (HREF.startsWith('https://www.amazon')) document.body.classList.add('amazon-prime');
|
||||||
} else if (window.location.href.includes('amazon.com')) {
|
if (HREF.startsWith('https://www.hulu')) document.body.classList.add('hulu');
|
||||||
// PRIME VIDEO
|
if (HREF.startsWith('https://www.netflix')) document.body.classList.add('netflix');
|
||||||
} else {
|
|
||||||
// LOCAL HOST
|
|
||||||
PartyWrapper.classList.add('netflix');
|
|
||||||
document.body.classList.add('netflix');
|
|
||||||
}
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|||||||
+23
-2
@@ -2,6 +2,7 @@ body.netflix {
|
|||||||
display: flex;
|
display: flex;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background-color: black;
|
||||||
}
|
}
|
||||||
body.netflix #appMountPoint {
|
body.netflix #appMountPoint {
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
@@ -41,11 +42,28 @@ body.netflix #appMountPoint {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
#party_wrapper.netflix .party_chat_container {
|
#party_wrapper.netflix .party_chat_container {
|
||||||
|
max-height: max-content;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-bottom: 1rem;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
padding-right: 1rem;
|
||||||
}
|
}
|
||||||
#party_wrapper.netflix h3{
|
#party_wrapper.netflix h3 {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
#party_wrapper.netflix h4 {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 200;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#party_wrapper.netflix h4 button {
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
#party_wrapper.netflix h4 span > svg {
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
.party_chat_input {
|
.party_chat_input {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -80,8 +98,11 @@ body.netflix #appMountPoint {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
#loading_container svg{
|
#loading_container svg {
|
||||||
color: wheat;
|
color: wheat;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
.ant-popover-inner-content {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user