diff --git a/code/static/css/.style.css.swp b/code/static/css/.style.css.swp new file mode 100644 index 0000000..d5704d1 Binary files /dev/null and b/code/static/css/.style.css.swp differ diff --git a/code/static/css/style.css b/code/static/css/style.css new file mode 100644 index 0000000..27d6309 --- /dev/null +++ b/code/static/css/style.css @@ -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;} +} diff --git a/code/static/index.html b/code/static/index.html index 894abce..633703d 100644 --- a/code/static/index.html +++ b/code/static/index.html @@ -1,10 +1,37 @@ - - - Go Chat Application - - - - + + + Go Chat Application + + + +
Warning: This is some message.
+ + +
+
+
+
+
+
+

Online Users

+
+
+ + + +
+
+ + diff --git a/code/static/js/.index.js.swp b/code/static/js/.index.js.swp new file mode 100644 index 0000000..277e908 Binary files /dev/null and b/code/static/js/.index.js.swp differ diff --git a/code/static/js/index.js b/code/static/js/index.js new file mode 100644 index 0000000..d80cf84 --- /dev/null +++ b/code/static/js/index.js @@ -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; +}