code/static: Add frontend layer to the code example
parent
803f64c0d8
commit
9e05b41888
Binary file not shown.
@ -0,0 +1,231 @@
|
||||
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,700&display=swap');
|
||||
|
||||
body {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #19180A;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#snackbar {
|
||||
visibility: hidden;
|
||||
min-width: 250px;
|
||||
margin-left: -125px;
|
||||
background-color: #4D5359;
|
||||
color: #BBC4FF;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
left: 50%;
|
||||
bottom: 30px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
#snackbar.snackshow {
|
||||
visibility: visible;
|
||||
-webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;
|
||||
animation: fadein 0.5s, fadeout 0.5s 2.5s;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
margin: 32px;
|
||||
width: 90%;
|
||||
height: 90%;
|
||||
}
|
||||
|
||||
.messages {
|
||||
width: 82%;
|
||||
height: 80%;
|
||||
overflow-y: scroll;
|
||||
border-radius: 4px;
|
||||
background-color: #4D5359;
|
||||
}
|
||||
|
||||
.messages > div {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.message hr {
|
||||
border: none;
|
||||
border-bottom: 1px solid transparent;
|
||||
border-bottom-color: #6c6882;
|
||||
margin: 14px 0;
|
||||
}
|
||||
|
||||
.message .new{
|
||||
border-bottom-color: #BA5C5C;
|
||||
}
|
||||
|
||||
.message .author {
|
||||
line-height: 24px;
|
||||
line-height: 24px;
|
||||
color: #AAB3FF;
|
||||
display: inline;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.message .timestamp {
|
||||
line-height: 24px;
|
||||
color: #888888;
|
||||
display: inline;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.message .content {
|
||||
color: #C9CEB1;
|
||||
}
|
||||
|
||||
.users {
|
||||
width: 15%;
|
||||
height: 80%;
|
||||
border-radius: 4px;
|
||||
background-color: #4D5359;
|
||||
background: -moz-linear-gradient(left, #4d5359 0%, #324a59 100%);
|
||||
background: -webkit-linear-gradient(left, #4d5359 0%,#324a59 100%);
|
||||
background: linear-gradient(to bottom, #4d5359 0%,#324a59 100%);
|
||||
font-weight: 200px;
|
||||
text-align: center;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.users h3 {
|
||||
color: #EAF0CE;
|
||||
font-weight: 100px;
|
||||
}
|
||||
|
||||
.users p {
|
||||
text-align: left;
|
||||
color: #AAB3FF;
|
||||
margin: 16px 6%;
|
||||
padding: 3px 9%;
|
||||
border-left: 4px solid #EAF0CE;
|
||||
background-color: #EAF0CE22;
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 96%;
|
||||
height: 8%;
|
||||
padding: 9px;
|
||||
border-radius: 8px;
|
||||
background-color: #4d5359;
|
||||
}
|
||||
|
||||
.primary {
|
||||
width: 100%;
|
||||
background: -moz-linear-gradient(left, #4d5359 0%, #324a59 100%);
|
||||
background: -webkit-linear-gradient(left, #4d5359 0%,#324a59 100%);
|
||||
background: linear-gradient(to bottom, #4d5359 0%,#324a59 100%);
|
||||
padding: 12px 32px 12px;
|
||||
}
|
||||
|
||||
input {
|
||||
font-family: Helvetica;
|
||||
font-weight: 300;
|
||||
width: 100%;
|
||||
line-height: 2.2rem;
|
||||
font-size: 1.1rem;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: #AAB3FF;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input::placeholder{
|
||||
color: #999;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 22px;
|
||||
transition: color 0.25s;
|
||||
color: #AAB3FF;
|
||||
}
|
||||
|
||||
i:hover {
|
||||
color: #EAF0CE;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
padding-top: 100px;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #00000088;
|
||||
animation-name: animatetop;
|
||||
animation-duration: 0.6s;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
color: #AAB3FF;
|
||||
position: relative;
|
||||
background-color: #324a59;
|
||||
margin: auto;
|
||||
padding: 18px;
|
||||
padding-bottom: 36px;
|
||||
border-radius: 4px;
|
||||
width: 60%;
|
||||
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
|
||||
-webkit-animation-name: animatetop;
|
||||
-webkit-animation-duration: 0.6s;
|
||||
}
|
||||
|
||||
.modal-content h1 {
|
||||
padding: 18px 0;
|
||||
}
|
||||
|
||||
@-webkit-keyframes animatetop {
|
||||
from {top:-300px; opacity:0}
|
||||
to {top:0; opacity:1}
|
||||
}
|
||||
|
||||
@keyframes animatetop {
|
||||
from {top:-300px; opacity:0}
|
||||
to {top:0; opacity:1}
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadein {
|
||||
from {bottom: 0; opacity: 0;}
|
||||
to {bottom: 30px; opacity: 1;}
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from {bottom: 0; opacity: 0;}
|
||||
to {bottom: 30px; opacity: 1;}
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeout {
|
||||
from {bottom: 30px; opacity: 1;}
|
||||
to {bottom: 0; opacity: 0;}
|
||||
}
|
||||
|
||||
@keyframes fadeout {
|
||||
from {bottom: 30px; opacity: 1;}
|
||||
to {bottom: 0; opacity: 0;}
|
||||
}
|
@ -1,10 +1,37 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Go Chat Application</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
<link rel="stylesheet" type="text/css" href="/css/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
|
||||
</head>
|
||||
<div id="snackbar">Warning: This is some message.</div>
|
||||
<body>
|
||||
<div id="modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h1>Enter a username:</h1>
|
||||
<div class="chat">
|
||||
<input type="text" id="nickI" placeholder="Nickname">
|
||||
<button type="submit" id="nickB"><i class="icon ion-android-arrow-forward"></i></button>
|
||||
</input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="messages">
|
||||
<div id="messages">
|
||||
</div>
|
||||
</div>
|
||||
<div class="users" id="users">
|
||||
<h3>Online Users</h3>
|
||||
</div>
|
||||
<div class="chat primary">
|
||||
<input type="text" id="chatI" placeholder="Enter your message">
|
||||
<button type="submit" id="chatB"><i class="icon ion-android-arrow-forward"></i></button>
|
||||
</input>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/js/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
Binary file not shown.
@ -0,0 +1,173 @@
|
||||
//Saved nickname from previous usage
|
||||
let nickname = window.localStorage.getItem("nickname");
|
||||
|
||||
//Nickname button and input
|
||||
let nickI = document.getElementById("nickI");
|
||||
let nickB = document.getElementById("nickB");
|
||||
|
||||
if (nickname != null) {
|
||||
nickI.value = nickname;
|
||||
}
|
||||
|
||||
//Chatting button and input
|
||||
let chatI = document.getElementById("chatI");
|
||||
let chatB = document.getElementById("chatB");
|
||||
|
||||
let registered = false;
|
||||
let modal = document.getElementById("modal");
|
||||
|
||||
//Gets websocket url by replacing http with ws and https with wss, and appending /websocket to the host
|
||||
let protocol = location.protocol == "http:" ? "ws:" : "wss:";
|
||||
let url = protocol + "//" + location.host + "/websocket";
|
||||
|
||||
let ws = new WebSocket(url);
|
||||
|
||||
ws.onopen = function() {
|
||||
modal.style.display = "block";
|
||||
nickI.focus();
|
||||
}
|
||||
|
||||
ws.onmessage = function(info) {
|
||||
let data = JSON.parse(info.data);
|
||||
console.log(data);
|
||||
if(!data.success) {
|
||||
toast("Error: " + data.content);
|
||||
return;
|
||||
}
|
||||
if (!registered) {
|
||||
registered = true;
|
||||
window.localStorage.setItem("nickname", nickname);
|
||||
modal.style.display = "none";
|
||||
chatI.focus()
|
||||
let users = data.content;
|
||||
for(let i = 0; i < users.length; i++) {
|
||||
userJoin(users[i]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
newMessage(data);
|
||||
}
|
||||
|
||||
/*
|
||||
* Message receive handling
|
||||
*/
|
||||
|
||||
function newMessage(data) {
|
||||
switch(data.type) {
|
||||
case "join":
|
||||
userJoin(data.content);
|
||||
break;
|
||||
case "leave":
|
||||
userLeave(data.content);
|
||||
break;
|
||||
case "message":
|
||||
chatMessage(data.sender, data.content, data.date)
|
||||
}
|
||||
}
|
||||
|
||||
function chatMessage(sender, content, date) {
|
||||
let msg = create("div", "message");
|
||||
let hr = create("hr", "seperator");
|
||||
|
||||
let author = create("div", "author");
|
||||
author.innerText = sender;
|
||||
|
||||
let timestamp = create("div", "timestamp");
|
||||
timestamp.innerText = dateToString(new Date(date));
|
||||
|
||||
let text = create("div", "content");
|
||||
text.innerText = content;
|
||||
|
||||
msg.append(hr, author, timestamp, text);
|
||||
|
||||
//If message scrolling at the bottom, move to bottom again to update
|
||||
let scroller = document.getElementsByClassName("messages")[0];
|
||||
let currentScroll = scroller.scrollTop;
|
||||
let maxScroll = scroller.scrollHeight - scroller.clientHeight;
|
||||
|
||||
document.getElementById("messages").append(msg);
|
||||
|
||||
if (currentScroll === maxScroll) {
|
||||
msg.scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
function userJoin(user) {
|
||||
let p = document.createElement("p");
|
||||
p.className = user;
|
||||
p.innerText = user;
|
||||
document.getElementById("users").appendChild(p);
|
||||
}
|
||||
|
||||
function userLeave(user) {
|
||||
users = document.getElementById("users");
|
||||
users.removeChild(users.getElementsByClassName(user)[0]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Registration
|
||||
*/
|
||||
function register() {
|
||||
let nick = nickI.value;
|
||||
if (nick.trim().length == 0) return;
|
||||
nickname = nick;
|
||||
ws.send(nick);
|
||||
}
|
||||
|
||||
nickI.onkeydown = function(ev) {
|
||||
if(ev.key !== "Enter") {
|
||||
return;
|
||||
}
|
||||
register();
|
||||
}
|
||||
|
||||
nickB.onclick = function() {
|
||||
register();
|
||||
}
|
||||
|
||||
/**
|
||||
* Message sending
|
||||
*/
|
||||
|
||||
chatI.onkeydown = function(ev) {
|
||||
if(ev.key !== "Enter") {
|
||||
return;
|
||||
}
|
||||
if(chatI.value.trim().length == 0) return;
|
||||
|
||||
ws.send(chatI.value);
|
||||
chatI.value = "";
|
||||
}
|
||||
nickB.onclick = function() {
|
||||
if(chatI.value.trim().length == 0) return;
|
||||
|
||||
ws.send(chatI.value);
|
||||
chatI.value = "";
|
||||
}
|
||||
|
||||
/*
|
||||
* Util functions
|
||||
*/
|
||||
function toast(message) {
|
||||
var x = document.getElementById("snackbar");
|
||||
x.innerText = message;
|
||||
x.className = "snackshow";
|
||||
setTimeout(function(){ x.className = x.className.replace("snackshow", ""); }, 3000);
|
||||
}
|
||||
|
||||
let months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
|
||||
function dateToString(date) {
|
||||
let year = date.getFullYear();
|
||||
let month = months[date.getMonth()];
|
||||
let day = date.getDate();
|
||||
let hour = date.getHours();
|
||||
let minute = date.getMinutes();
|
||||
return ` ${day} ${month} ${year} at ${hour}:${minute}`;
|
||||
}
|
||||
|
||||
function create(name, classes) {
|
||||
let x = document.createElement(name);
|
||||
x.className = classes;
|
||||
return x;
|
||||
}
|
Loading…
Reference in New Issue