demo: add files for demo

master
ALI Hamza 2020-09-02 12:15:57 +07:00
parent 558b489e4f
commit 04f1eaa9d4
Signed by: hamza
GPG Key ID: 22473A32291F8CB6
7 changed files with 648 additions and 1 deletions

@ -4,7 +4,6 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Go Chat Application</title> <title>Go Chat Application</title>
<link rel="stylesheet" type="text/css" href="/css/style.css"> <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> </head>
<div id="snackbar"></div> <div id="snackbar"></div>
<body> <body>

@ -0,0 +1,116 @@
package main
import (
"strings"
"time"
"github.com/gorilla/websocket"
)
var usernames = make(map[*websocket.Conn]string)
var nameToConn = make(map[string]*websocket.Conn)
func handleIncomingMessage(sender *websocket.Conn, msg string) {
if _, ok := usernames[sender]; ok {
sendChatMessage(sender, msg)
return
}
// Registering a new user
username := strings.TrimSpace(msg)
if username == "" || username == "server" {
sender.WriteJSON(newError("You have an illegal username."))
return
}
if _, ok := nameToConn[username]; ok {
sender.WriteJSON(newError("The specified username is already taken."))
return
}
sendUserList(sender)
usernames[sender] = username
nameToConn[username] = sender
m := newMessage(msgJoin, "server", username)
m.dispatch()
}
func handleDisconnection(sender *websocket.Conn) {
username, ok := usernames[sender]
if !ok {
return
}
m := newMessage(msgLeave, "server", username)
m.dispatch()
delete(usernames, sender)
delete(nameToConn, username)
}
type messageType string
const (
msgChat messageType = "message"
msgJoin messageType = "join"
msgLeave messageType = "leave"
msgErr messageType = "error"
msgUserList messageType = "users"
)
type message struct {
Type messageType `json:"type"`
Sender string `json:"sender"`
Content interface{} `json:"content"`
Date time.Time `json:"date"`
Success bool `json:"success"`
}
func newError(content string) message {
return message{
Type: msgErr,
Sender: "",
Content: content,
Date: time.Now().UTC(),
Success: false,
}
}
func newMessage(msgType messageType, sender string, content string) message {
return message{
Type: msgType,
Sender: sender,
Content: content,
Date: time.Now().UTC(),
Success: true,
}
}
func (m message) dispatch() {
for client := range usernames {
_ = client.WriteJSON(m)
}
}
func sendUserList(who *websocket.Conn) {
list := []string{}
for _, username := range usernames {
list = append(list, username)
}
m := message{
Type: msgUserList,
Sender: "",
Content: list,
Date: time.Now().UTC(),
Success: true,
}
_ = who.WriteJSON(m)
}
func sendChatMessage(sender *websocket.Conn, msg string) {
m := newMessage(msgChat, usernames[sender], msg)
m.dispatch()
}

@ -0,0 +1,48 @@
package main
import (
"fmt"
"net/http"
"strconv"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
func websocketConnection(w http.ResponseWriter, r *http.Request) {
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
_, _ = fmt.Fprint(w, "You must use the web socket protocol to connect to this endpoint. ", err)
return
}
defer ws.Close()
for {
_, p, err := ws.ReadMessage()
if err != nil {
handleDisconnection(ws)
break
}
msg := string(p)
handleIncomingMessage(ws, msg)
}
}
const port = 8080
func main() {
http.HandleFunc("/websocket", websocketConnection)
http.Handle("/", http.FileServer(http.Dir("./static")))
fmt.Println("Chat server opening on port", port)
err := http.ListenAndServe(":"+strconv.Itoa(port), nil)
if err != nil {
_ = fmt.Errorf("Server could not start up: %s", err.Error())
}
}

@ -0,0 +1,253 @@
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,700;0,900;1,400&display=swap');
body {
width: 100vw;
height: 100vh;
padding: 0;
margin: 0;
font-family: 'Roboto', sans-serif;
}
#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 {
width: 100%;
height: 100%;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.lhs {
width: calc(100% - 300px);
display: flex;
flex-direction: column-reverse;
background-color: #39363F;
}
.rhs {
width: calc(300px - 24px);
background-color: #28272D;
padding: 12px;
color: #eeeeee;
}
.messages {
overflow: auto;
max-height: calc(100vh - 128px);
flex-grow: 1;
}
.messages::-webkit-scrollbar {
width: 8px;
}
.messages::-webkit-scrollbar-track {
background: #322F37;
border-radius: 12px;
}
.messages::-webkit-scrollbar-thumb {
background: #504D56;
border-radius: 12px;
}
.messages::-webkit-scrollbar-thumb:hover {
background: #5C5766;
}
/* */
.chat {
height: 64px;
padding: 32px;
}
.chat-inner {
height:64px;
display: flex;
align-items: center;
}
#chatI {
height: 64px;
width: 100%;
padding: 0 12px;
font-size: 20px;
color: #eeeeee;
border: none;
border-radius: 6px 0 0 6px;
background: #28272D;
}
#chatI:focus {
outline: none;
}
#chatI::placeholder {
color: #908D9B;
}
#chatB {
height: 64px;
padding: 0;
width: 128px;
font-size: 24px;
color: #000;
border: none;
border-radiuS: 0 6px 6px 0;
background: #8EFFD6;
}
i {
font-size: 22px;
transition: color 0.25s;
color: #AAB3FF;
}
i:hover {
color: #EAF0CE;
}
/* Chat Messages */
.message {
margin: 32px 32px;
}
.server-message {
color: #908D9B;
font-style: italic;
font-size: 21px;
}
.author {
color: #fcfcfc;
font-weight: 700;
display: inline-block;
margin-right: 8px;
}
.author.self {
color: #8EFFD6;
}
.timestamp {
display: inline-block;
font-weight: 400;
font-size: 14px;
color: #7D7A87;
}
.content {
color: #eee;
margin-top: 2px;
}
.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: #eeeeee;
position: relative;
background-color: #39363F;
margin: auto;
padding: 18px;
padding-bottom: 36px;
border-radius: 4px;
width: 60%;
-webkit-animation-name: animatetop;
-webkit-animation-duration: 0.6s;
}
.modal-content h1 {
padding: 18px 0;
}
#nickI {
height: 48px;
padding: 0 12px;
font-size: 16px;
color: #eeeeee;
border: none;
border-radius: 6px;
background: #28272D;
}
#nickI:focus {
outline: none;
}
#nickI::placeholder {
color: #908D9B;
}
#nickB {
height: 48px;
padding: 0;
width: 96px;
font-size: 18px;
color: #000;
border: none;
border-radiuS: 6px;
background: #8EFFD6;
}
@-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;}
}

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Go Chat Application</title>
<link rel="stylesheet" type="text/css" href="/css/style.css">
</head>
<div id="snackbar"></div>
<body>
<div id="modal" class="modal">
<div class="modal-content">
<h1>Enter a username:</h1>
<input type="text" id="nickI" placeholder="Nickname">
<button type="submit" id="nickB">Join </button>
</div>
</div>
<div class="container">
<div class="lhs">
<div class="chat">
<div class="chat-inner">
<input type="text" id="chatI" placeholder="Enter your message">
<button id="chatB">Send </button>
</div>
</div>
<div class="messages" id="messages">
</div>
</div>
<div class="rhs" id="users">
<h3>Online Users</h3>
</div>
</div>
<script src="/js/index.js"></script>
</body>
</html>

@ -0,0 +1,196 @@
//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");
//Chatting button and input
let chatI = document.getElementById("chatI");
let chatB = document.getElementById("chatB");
let registered = false;
let modal = document.getElementById("modal");
if (nickname != null) {
nickI.value = nickname;
}
//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);
if(!data.success) {
toast("Error: " + data.content);
return;
}
// Not Registered + Non error message means we have a new message!
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, data.date);
break;
case "leave":
userLeave(data.content, data.date);
break;
case "message":
chatMessage(data.sender, data.content, data.date)
break;
}
}
/**
* Join/leave events
*/
function userJoin(user, date) {
let p = document.createElement("p");
p.className = user;
p.innerText = user;
document.getElementById("users").appendChild(p);
serverMessage(data.content + " has joined the server", data.date);
}
function userLeave(user, date) {
users = document.getElementById("users");
users.removeChild(users.getElementsByClassName(user)[0]);
serverMessage(user + " has left the server", date);
}
function chatMessage(sender, content, date) {
let msg = create("div", "message");
aClasses = "author"
if (sender === nickname) {
aClasses += " self"
}
let author = create("div", aClasses);
author.innerText = sender;
let timestamp = create("div", "timestamp");
timestamp.innerText = dateToString(new Date(date));
let text = create("div", "content");
text.innerText = content;
msg.append(author, timestamp, text);
insertMessage(msg);
}
function serverMessage(content, date) {
let msg = create("div", "message");
let text = create("div", "server-message");
text.innerText = content;
msg.append(text);
insertMessage(msg);
}
function insertMessage(div) {
//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(div);
if (currentScroll === maxScroll) {
div.scrollIntoView();
}
}
/*
* 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 = "";
}
chatB.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;
}

@ -0,0 +1 @@
hello world!