Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 183 additions & 0 deletions projects/gp-2/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<style>
html, body, #app, #main {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}

#app {
display: flex;
justify-content: center;
}

#main {
display: flex;
}

.left-side {
flex-grow: 1;
max-width: 320px;
background: linear-gradient(to bottom, #f19b6d, #EB8665);
color: #FEFEFD;
padding: 10px;
box-sizing: border-box;
}

.user-info {
margin-bottom: 20px;
display: flex;
align-items: center;
}

.user-name {
font-size: 18px;
font-weight: bold;
}

.user-photo, .message-item-photo {
margin-right: 15px;
background: url(/projects/mega-chat-3/no-photo.png);
background-size: cover;
border-radius: 50%;
width: 50px;
height: 50px;
}

.user-list-header {
text-transform: uppercase;
font-weight: bold;
}

.user-list {
padding: 5px 0 0 10px
}

.right-side {
display: flex;
flex-grow: 1;
flex-direction: column;
padding: 10px;
}

.messages-container {
flex-grow: 1;
overflow: scroll;
}

.input-container {
margin-top: 10px;
display: flex;
align-items: center;
}

.message-input {
height: 30px;
flex-grow: 1;
margin: 0 10px 0 0;
}

.send-button {
height: 25px;
}

#login {
position: absolute;
top: 20vh;
width: 500px;
height: 300px;
background: linear-gradient(to bottom, #f19b6d, #EB8665);
border-radius: 10px;
box-shadow: 0 0 10px #7f7f7f;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

.login-field {
margin: 15px;
width: 300px;
display: flex;
justify-content: center;
}

.login-field input {
font-size: 16px;
height: 30px;
width: 100%;
}

.login-submit {
height: 30px;
width: 100px;
}

.message-item {
display: flex;
}

.message-item-system {
font-size: 14px;
color: gray;
}

.message-item ~ .message-item {
margin-top: 15px;
}

.message-item-right {
flex-grow: 1;
}

.message-item-header {
display: flex;
align-items: center;
}

.message-item-header-name {
font-weight: bold;
margin-right: 15px;
}

.message-item-header-time {
color: gray;
font-size: 12px;
}
</style>
<div id="app">
<div id="login" class="hidden">
<div class="login-field">
<input type="text" class="login-name-input" data-role="login-name-input" placeholder="никнейм">
</div>
<div class="login-field">
<button class="login-submit" data-role="login-submit">Войти</button>
</div>
<div class="login-error" data-role="login-error"></div>
</div>

<div id="main" class="hidden">
<div class="left-side">
<div class="user-info">
<div class="user-photo" data-role="user-photo"></div>
<div class="user-name" data-role="user-name"></div>
</div>

<div class="user-list-container">
<div class="user-list-header">
Все пользователи:
</div>

<div class="user-list" data-role="user-list">
</div>
</div>
</div>
<div class="right-side">
<div class="messages-container" data-role="messages-list"></div>
<div class="input-container" data-role="message-sender">
<input class="message-input" data-role="message-input" type="text" placeholder="сообщение"/>
<button class="send-button" data-role="message-send-button">Отправить</button>
</div>
</div>
</div>
</div>
4 changes: 4 additions & 0 deletions projects/gp-2/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import './index.html';
import MegaChat from './megaChat';

new MegaChat();
92 changes: 92 additions & 0 deletions projects/gp-2/megaChat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import LoginWindow from './ui/loginWindow';
import MainWindow from './ui/mainWindow';
import UserName from './ui/userName';
import UserList from './ui/userList';
import UserPhoto from './ui/userPhoto';
import MessageList from './ui/messageList';
import MessageSender from './ui/messageSender';
import WSClient from './wsClient';

export default class MegaChat {
constructor() {
this.wsClient = new WSClient(
`ws://${location.host}/mega-chat-3/ws`,
this.onMessage.bind(this)
);

this.ui = {
loginWindow: new LoginWindow(
document.querySelector('#login'),
this.onLogin.bind(this)
),
mainWindow: new MainWindow(document.querySelector('#main')),
userName: new UserName(document.querySelector('[data-role=user-name]')),
userList: new UserList(document.querySelector('[data-role=user-list]')),
messageList: new MessageList(document.querySelector('[data-role=messages-list]')),
messageSender: new MessageSender(
document.querySelector('[data-role=message-sender]'),
this.onSend.bind(this)
),
userPhoto: new UserPhoto(
document.querySelector('[data-role=user-photo]'),
this.onUpload.bind(this)
),
};

this.ui.loginWindow.show();
}

onUpload(data) {
this.ui.userPhoto.set(data);

fetch('/mega-chat-3/upload-photo', {
method: 'post',
body: JSON.stringify({
name: this.ui.userName.get(),
image: data,
}),
});
}

onSend(message) {
this.wsClient.sendTextMessage(message);
this.ui.messageSender.clear();
}

async onLogin(name) {
await this.wsClient.connect();
this.wsClient.sendHello(name);
this.ui.loginWindow.hide();
this.ui.mainWindow.show();
this.ui.userName.set(name);
this.ui.userPhoto.set(`/mega-chat-3/photos/${name}.png?t=${Date.now()}`);
}

onMessage({ type, from, data }) {
console.log(type, from, data);

if (type === 'hello') {
this.ui.userList.add(from);
this.ui.messageList.addSystemMessage(`${from} вошел в чат`);
} else if (type === 'user-list') {
for (const item of data) {
this.ui.userList.add(item);
}
} else if (type === 'bye-bye') {
this.ui.userList.remove(from);
this.ui.messageList.addSystemMessage(`${from} вышел из чата`);
} else if (type === 'text-message') {
this.ui.messageList.add(from, data.message);
} else if (type === 'photo-changed') {
const avatars = document.querySelectorAll(
`[data-role=user-avatar][data-user=${data.name}]`
);

for (const avatar of avatars) {
avatar.style.backgroundImage = `url(/mega-chat-3/photos/${
data.name
}.png?t=${Date.now()})`;
}
}
}
}
Binary file added projects/gp-2/no-photo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file added projects/gp-2/photos/.gitkeep
Empty file.
110 changes: 110 additions & 0 deletions projects/gp-2/server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// не забудьте сделать npm install ;)

const fs = require('fs');
const path = require('path');
const http = require('http');
const { Server } = require('ws');

function readBody(req) {
return new Promise((resolve, reject) => {
let dataRaw = '';

req.on('data', (chunk) => (dataRaw += chunk));
req.on('error', reject);
req.on('end', () => resolve(JSON.parse(dataRaw)));
});
}

const server = http.createServer(async (req, res) => {
try {
if (/\/photos\/.+\.png/.test(req.url)) {
const [, imageName] = req.url.match(/\/photos\/(.+\.png)/) || [];
const fallBackPath = path.resolve(__dirname, '../no-photo.png');
const filePath = path.resolve(__dirname, '../photos', imageName);

if (fs.existsSync(filePath)) {
return fs.createReadStream(filePath).pipe(res);
} else {
return fs.createReadStream(fallBackPath).pipe(res);
}
} else if (req.url.endsWith('/upload-photo')) {
const body = await readBody(req);
const name = body.name.replace(/\.\.\/|\//, '');
const [, content] = body.image.match(/data:image\/.+?;base64,(.+)/) || [];
const filePath = path.resolve(__dirname, '../photos', `${name}.png`);

if (name && content) {
fs.writeFileSync(filePath, content, 'base64');

broadcast(connections, { type: 'photo-changed', data: { name } });
} else {
return res.end('fail');
}
}

res.end('ok');
} catch (e) {
console.error(e);
res.end('fail');
}
});
const wss = new Server({ server });
const connections = new Map();

wss.on('connection', (socket) => {
connections.set(socket, {});

socket.on('message', (messageData) => {
const message = JSON.parse(messageData);
let excludeItself = false;

if (message.type === 'hello') {
excludeItself = true;
connections.get(socket).userName = message.data.name;
sendMessageTo(
{
type: 'user-list',
data: [...connections.values()].map((item) => item.userName).filter(Boolean),
},
socket
);
}

sendMessageFrom(connections, message, socket, excludeItself);
});

socket.on('close', () => {
sendMessageFrom(connections, { type: 'bye-bye' }, socket);
connections.delete(socket);
});
});

function sendMessageTo(message, to) {
to.send(JSON.stringify(message));
}

function broadcast(connections, message) {
for (const connection of connections.keys()) {
connection.send(JSON.stringify(message));
}
}

function sendMessageFrom(connections, message, from, excludeSelf) {
const socketData = connections.get(from);

if (!socketData) {
return;
}

message.from = socketData.userName;

for (const connection of connections.keys()) {
if (connection === from && excludeSelf) {
continue;
}

connection.send(JSON.stringify(message));
}
}

server.listen(8282);
Loading