Initial comit ver2

This commit is contained in:
Kacper Maj 2021-01-25 14:50:08 +01:00
commit 69b3aa215d
15 changed files with 2273 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
node_modules/
src/utils/roomsIDs.json
src/utils/roomsIDs.json

Binary file not shown.

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# Watch With Friends
**Watch with friends** to projekt, który ma za zadanie umożliwić „spotkanie” się znajomych na wspólnym oglądaniu filmów w dobie pandemii. Postawiłem sobie za zadanie umożliwić połączenie możliwie wielu przyjaciół jednocześnie w jednym, generowanym losowo pokoju do którego dostęp otrzymują osoby, które dostaną unikatowy link. W pokoju tym użytkownicy poproszeni zostaną o ustawienie swojej nazwy a następnie mogą zarządzać puszczanym aktualnie filmem z YouTube, wkleić link do wybranego przez siebie filmu, dowolnie zatrzymywać i puszczać film. Jako dodatkowy element pokoju dodany zostanie chat, który będzie pozwalał na komunikację pomiędzy użytkownikami serwisu w czasie rzeczywistym.

1526
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

21
package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "chat-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bad-words": "^3.0.4",
"express": "^4.17.1",
"socket.io": "^3.1.0"
},
"devDependencies": {
"nodemon": "^2.0.7"
}
}

42
public/chat.html Normal file
View File

@ -0,0 +1,42 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Chat</title>
<link rel="icon" href="/img/favicon.png">
<link rel="stylesheet" href="/css/styles.min.css">
<link rel="stylesheet" href="/css/styles.css">
<meta name="description" content="Chat">
<meta name="author" content="SitePoint">
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/3.0.1/mustache.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/qs/6.6.0/qs.min.js"></script>
<script defer src="/socket.io/socket.io.js"></script>
<script defer src="/js/chat.js"></script>
</head>
<body>
<div class="chat">
<div class="chat__sidebar">
</div>
<div class="chat__main">
<div id="messages-container" class="chat__messages"> </div>
<div class="compose">
<form action="POST" id="sendMessForm">
<input placeholder="Write your message">
<button type="submit" id="formButton">Send</button>
</form>
<button id="sendLocation">Send location!</button>
</div>
</div>
</div>
</body>
</html>

BIN
public/css/.DS_Store vendored Normal file

Binary file not shown.

196
public/css/styles.css Normal file
View File

@ -0,0 +1,196 @@
/* General Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-size: 14px;
}
input {
font-size: 14px;
}
body {
line-height: 1.4;
color: #333333;
font-family: Helvetica, Arial, sans-serif;
}
h1 {
margin-bottom: 16px;
}
label {
display: block;
font-size: 14px;
margin-bottom: 8px;
color: #777;
}
input {
border: 1px solid #eeeeee;
padding: 12px;
outline: none;
}
button {
cursor: pointer;
padding: 12px;
background: #7c5cbf;
border: none;
color: white;
font-size: 16px;
transition: background 0.3s ease;
}
button:hover {
background: #6b47b8;
}
button:disabled {
cursor: default;
background: #7c5cbf94;
}
/* Join Page Styles */
.centered-form {
background: #333744;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.centered-form__box {
box-shadow: 0px 0px 17px 1px #1d1f26;
background: #f7f7fa;
padding: 24px;
width: 250px;
}
.centered-form button {
width: 100%;
}
.centered-form input {
margin-bottom: 16px;
width: 100%;
}
/* Chat Page Layout */
.chat {
display: flex;
}
.chat__sidebar {
height: 100vh;
color: rgb(223, 223, 223);
background: #333744;
width: 225px;
overflow-y: scroll;
scroll-behavior: smooth;
}
/* Chat styles */
.chat__main {
flex-grow: 1;
display: flex;
flex-direction: column;
max-height: 100vh;
}
.chat__messages {
flex-grow: 1;
padding: 24px 24px 0 24px;
overflow-y: scroll;
scroll-behavior: smooth;
}
/* Message Styles */
.message {
margin-bottom: 16px;
}
.message__name {
font-weight: 600;
font-size: 14px;
margin-right: 8px;
}
.message__meta {
color: #777;
font-size: 14px;
}
.message a {
color: #0070cc;
}
/* Message Composition Styles */
.compose {
display: flex;
flex-shrink: 0;
margin-top: 16px;
padding: 24px;
}
.compose form {
display: flex;
flex-grow: 1;
margin-right: 16px;
}
.compose input {
border: 1px solid #eeeeee;
width: 100%;
padding: 12px;
margin: 0 16px 0 0;
flex-grow: 1;
}
.compose button {
font-size: 14px;
}
/* Chat Sidebar Styles */
.room-title {
font-weight: 400;
font-size: 20px;
background: #2c2f3a;
padding: 24px;
text-align: center;
}
.list-title {
font-weight: 500;
text-align: center;
font-size: 20px;
margin-bottom: 4px;
padding: 12px 24px 0 24px;
}
.users {
text-align: center;
list-style-type: none;
font-weight: 100;
font-style: italic;
padding: 12px 24px 0 24px;
}
.welcome-mess {
font-weight: 300;
font-size: 16px;
margin-right: 8px;
color: #333333;
}

147
public/css/styles.min.css vendored Normal file
View File

@ -0,0 +1,147 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-size: 16px;
}
input {
font-size: 14px;
}
body {
line-height: 1.4;
color: #333;
font-family: Helvetica, Arial, sans-serif;
}
h1 {
margin-bottom: 16px;
}
label {
display: block;
font-size: 14px;
margin-bottom: 8px;
color: #777;
}
input {
border: 1px solid #eee;
padding: 12px;
outline: none;
}
button {
cursor: pointer;
padding: 12px;
background: #7c5cbf;
border: none;
color: #fff;
font-size: 16px;
transition: background 0.3s ease;
}
button:hover {
background: #6b47b8;
}
button:disabled {
cursor: default;
background: #7c5cbf94;
}
.centered-form {
background: #333744;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.centered-form__box {
box-shadow: 0 0 17px 1px #1d1f26;
background: #f7f7fa;
padding: 24px;
width: 250px;
}
.centered-form button {
width: 100%;
}
.centered-form input {
margin-bottom: 16px;
width: 100%;
}
.chat {
display: flex;
}
.chat__sidebar {
height: 100vh;
color: #fff;
background: #333744;
width: 225px;
overflow-y: scroll;
}
.chat__main {
flex-grow: 1;
display: flex;
flex-direction: column;
max-height: 100vh;
}
.chat__messages {
flex-grow: 1;
padding: 24px 24px 0;
overflow-y: scroll;
}
.message {
margin-bottom: 16px;
}
.message__name {
font-weight: 600;
font-size: 14px;
margin-right: 8px;
}
.message__meta {
color: #777;
font-size: 14px;
}
.message a {
color: #0070cc;
}
.compose {
display: flex;
flex-shrink: 0;
margin-top: 16px;
padding: 24px;
}
.compose form {
display: flex;
flex-grow: 1;
margin-right: 16px;
}
.compose input {
border: 1px solid #eee;
width: 100%;
padding: 12px;
margin: 0 16px 0 0;
flex-grow: 1;
}
.compose button {
font-size: 14px;
}
.room-title {
font-weight: 400;
font-size: 22px;
background: #2c2f3a;
padding: 24px;
}
.list-title {
font-weight: 500;
font-size: 18px;
margin-bottom: 4px;
padding: 12px 24px 0;
}
.users {
list-style-type: none;
font-weight: 300;
padding: 12px 24px 0;
}
.myPar {
text-align: center;
font-family: Helvetica, sans-serif;
font-size: 15px;
font-weight: 400;
}

BIN
public/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

33
public/index.html Normal file
View File

@ -0,0 +1,33 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Chat</title>
<link rel="icon" href="/img/favicon.png">
<link rel="stylesheet" href="/css/styles.min.css">
<meta name="description" content="Chat">
<meta name="author" content="SitePoint">
</head>
<body>
<div class="centered-form">
<div class="centered-form__box">
<h1>Join</h1>
<form action="/chat.html">
<label>Display name</label>
<input type="text" name="username" placeholder="Display name" required>
<label>Room</label>
<input type="text" name="room" placeholder="Room" required>
<button type="submit">Join</button>
</form>
<p class="myPar">or</p>
<button type="submit">Generate room</button>
</div>
</div>
</body>
</html>

152
public/js/chat.js Normal file
View File

@ -0,0 +1,152 @@
'use strict';
const socket = io();
const buttonLocation = document.querySelector('#sendLocation');
const form = document.querySelector('#sendMessForm');
const formButton = document.querySelector('#formButton');
const input = form.childNodes[1];
const button = form.childNodes[3];
const messageContainer = document.querySelector('#messages-container');
const sidebarContainer = document.querySelector('.chat__sidebar');
// TEMPLATES
const sidebarTemplate = `<h2 class="room-title">{{room}}</h2>
<h3 class="list-title">Users</h3>
<ul class="users">
{{#users}}
<li>{{username}}</li>
{{/users}}
</ul>`;
const htmlTemplate = [
`<div class="message">
<p>
<span class="message__name"> {{user}} </span>
<span class="message__meta"> {{createdAt}} </span>
</p>
<p>{{string}}</p>
</div>`,
`<div class="message">
<p>
<span class="message__name"> {{user}} </span>
<span class="message__meta"> {{createdAt}} </span>
</p>
<p><a href="{{string}}">Link</a></p>
</div>`,
`<div class="message">
<p class="welcome-mess">{{string}}</p>
</div>`,
];
let message = '';
// let user = window.prompt('Please state your username', 'User');
const autoscroll = () => {
// New message elemnt
const newMessage = messageContainer.lastElementChild;
// Height of the new message
const newMessageStyles = getComputedStyle(newMessage);
const newMessageMargin = parseInt(newMessageStyles.marginBottom);
const newMessageHeight = newMessage.offsetHeight + newMessageMargin;
// Visible height
const visibleHeight = messageContainer.offsetHeight;
// Height of messages container
const containerHeight = messageContainer.scrollHeight;
// scrolloffset
const scrolloffset = messageContainer.scrollTop + visibleHeight + 15;
if (containerHeight - newMessageHeight <= scrolloffset) {
console.log('should scroll down');
messageContainer.scrollTop = messageContainer.scrollHeight;
}
};
const { username, room } = Qs.parse(location.search, {
ignoreQueryPrefix: true,
});
console.log(username, room);
socket.on('message', (object) => {
console.log(object);
const string = object.text;
const user = object.username;
const createdAt = moment(object.createdAt).format('HH:mm:ss');
if (string.includes('https') || string.includes('http')) {
const html = Mustache.render(htmlTemplate[1], {
user,
string,
createdAt,
});
messageContainer.insertAdjacentHTML('beforeend', html);
autoscroll();
console.log('not includes');
} else if (string.includes('Welcome to the server')) {
const html = Mustache.render(htmlTemplate[2], {
string,
});
messageContainer.insertAdjacentHTML('beforeend', html);
autoscroll();
} else {
const html = Mustache.render(htmlTemplate[0], {
string,
user,
createdAt,
});
messageContainer.insertAdjacentHTML('beforeend', html);
autoscroll();
}
});
form.addEventListener('submit', (e) => {
e.preventDefault();
input.focus();
// Disable form
if (input.value === '') return;
button.setAttribute('disabled', 'disabled');
socket.emit('message', input.value, (error) => {
// Re-enable form
button.removeAttribute('disabled');
if (error) return console.log(error);
console.log('Message delivered');
input.value = '';
input.focus();
});
});
// Send location
buttonLocation.addEventListener('click', (e) => {
e.preventDefault();
if (!navigator.geolocation) return alert('No geolocation');
buttonLocation.setAttribute('disabled', 'disabled');
navigator.geolocation.getCurrentPosition((position) => {
const { latitude, longitude } = position.coords;
const coordsObj = `https://www.google.com/maps/?q=${latitude},${longitude}`;
socket.emit('message', coordsObj, () => {
console.log('Location delivered');
buttonLocation.removeAttribute('disabled');
});
});
});
socket.on('roomData', ({ room, users }) => {
const html = Mustache.render(sidebarTemplate, { room, users });
sidebarContainer.innerHTML = html;
});
socket.emit('join', { username, room }, (error) => {
if (error) {
alert(error);
location.href = '/';
}
});

86
src/index.js Normal file
View File

@ -0,0 +1,86 @@
const express = require('express');
const http = require('http');
const path = require('path');
const socketio = require('socket.io');
const Filter = require('bad-words');
const { generateMessage } = require('./utils/messages');
const {
addUser,
removeUser,
getUser,
getUsersInRoom,
} = require('./utils/users');
const app = express();
const server = http.createServer(app);
const io = socketio(server);
const port = process.env.PORT || 3000;
const publicDirectoryPath = path.join(__dirname, '../public');
app.use(express.static(publicDirectoryPath));
io.on('connection', (socket) => {
// Welcome message
socket.on('join', ({ username, room }, callback) => {
const { error, user } = addUser({ id: socket.id, username, room });
if (error) return callback(error);
socket.join(user.room); //Join the room
// emit the message to() specific room
socket.emit(
'message',
generateMessage(`Welcome to the server, ${username}!`)
);
socket.broadcast
.to(user.room)
.emit('message', generateMessage(`${user.username} has joined`));
io.to(user.room).emit('roomData', {
room: user.room,
users: getUsersInRoom(user.room),
});
callback();
});
// On recv message
socket.on('message', (message, callback) => {
const user = getUser(socket.id);
const filter = new Filter();
if (filter.isProfane(message)) message = filter.clean(message);
//callback('Profanity is not allowed');
console.log(user);
try {
io.to(user.room).emit(
'message',
generateMessage(message, user.username)
);
} catch (error) {
console.log(error);
}
callback();
});
socket.on('disconnect', () => {
const user = removeUser(socket.id);
if (user)
io.to(user.room).emit(
'message',
generateMessage(`${user.username} has left!`)
);
io.to(user.room).emit('roomData', {
room: user.room,
users: getUsersInRoom(user.room),
});
});
socket.on('sendLocation', (object, callback) => {
callback('Location shared');
socket.broadcast.emit('recvObject', generateMessage(object));
});
});
server.listen(port, () => console.log(`Server is listening at ${port}`));

11
src/utils/messages.js Normal file
View File

@ -0,0 +1,11 @@
const generateMessage = (text, username) => {
return {
text,
username,
createdAt: new Date().getTime(),
};
};
module.exports = {
generateMessage,
};

54
src/utils/users.js Normal file
View File

@ -0,0 +1,54 @@
const users = [];
// addUser, removeUser, getUser, getUsersInRoom
const addUser = ({ id, username, room }) => {
// Clean the data
// username = username.trim().toLowerCase();
// room = room.trim().toLowerCase();
if (!room || !username)
return {
error: 'Username and room are required',
};
// Check for existing user
const existingUser = users.find((user) => {
return user.room === room && user.username === username;
});
// Validate username
if (existingUser)
return {
error: 'Username is in use!',
};
// Store user
const user = { id, username, room };
users.push(user);
return { user };
};
const removeUser = (id) => {
const index = users.findIndex((user) => user.id === id);
if (index !== -1) {
// console.log(users[id])
return users.splice(index, 1)[0];
}
};
const getUser = (id) => {
const foundUser = users.find((user) => user.id === id);
if (!foundUser) return undefined;
return foundUser;
};
const getUsersInRoom = (room) => {
const usersInRoom = users.filter((user) => user.room === room);
if (!usersInRoom) return [];
return usersInRoom;
};
module.exports = {
addUser,
removeUser,
getUser,
getUsersInRoom,
};