Added and fixed things

- Added conversations to the sidebar and the ability to switch between
them
- Fixed loader and managed type methods
- Changed unread message style
This commit is contained in:
Norbi Peti 2016-08-17 12:48:31 +02:00
parent 23134e74f0
commit 0055ac8f2b
12 changed files with 286 additions and 138 deletions

View file

@ -62,4 +62,20 @@ body {
html, body {
height: 100%;
margin: 0;
}
#conversations
{
width: 20%;
float: left;
}
#conversation
{
}
#unreadmsg
{
background-color: #DDD;
}

View file

@ -1,7 +1,8 @@
<!DOCTYPE html>
<head>
<title>Chat</title>
<meta charset="UTF-8"/>
<meta charset="UTF-8" />
<script src="js/jquery-3.1.0.js"></script>
<script src="js/moment-with-locales.js" charset="UTF-8"></script>
<script src="js/utils.js"></script>
@ -9,84 +10,87 @@
<script src="js/receivemessage.js"></script>
<script src="js/login.js"></script>
<script src="js/register.js"></script>
<link rel="stylesheet" href="css/style.css"/>
<link rel="stylesheet" href="css/style.css" />
<script src="js/index.js"></script>
</head>
<body>
<div id="errormsg">
</div>
<div id="sidebar">
<div id="userbox">
<p>Logged in as
<username/>
</p>
<a href="/logout">Logout</a>
<div id="errormsg">
</div>
</div>
<div id="loginregisterbox">
<div id="loginbox">
<h2>Login</h2>
<form>
<table>
<tr>
<td>E-mail:</td>
<td>
<input type="email" name="email">
</td>
</tr>
<tr>
<td>Password:</td>
<td>
<input type="password" name="pass">
</td>
</tr>
<tr>
<td>
<input type="button" value="Login" onclick="login(this.form)">
</td>
</tr>
</table>
</form>
<div id="conversations">
</div>
<div id="registerbox">
<h2>Register</h2>
<form name="form">
<table>
<tr>
<td>Name:</td>
<td>
<input type="text" name="name"/>
</td>
</tr>
<tr>
<td>E-mail:</td>
<td>
<input type="email" name="email"/>
</td>
</tr>
<tr>
<td>Password</td>
<td>
<input type="password" name="pass"/>
</td>
</tr>
<tr>
<td>Password confirm</td>
<td>
<input type="password" name="pass2"/>
</td>
</tr>
<tr>
<td>
<input type="button" value="Register" onclick="register(this.form)"/>
</td>
</tr>
</table>
</form>
<div id="sidebar">
<div id="userbox">
<p>Logged in as
<username/>
</p>
<a href="/logout">Logout</a>
</div>
</div>
</div>
<div id="usercontent">
<div id="channelmessages"></div>
<textarea id="msginput" autofocus="autofocus"></textarea>
</div>
</body>
<div id="loginregisterbox">
<div id="loginbox">
<h2>Login</h2>
<form>
<table>
<tr>
<td>E-mail:</td>
<td>
<input type="email" name="email">
</td>
</tr>
<tr>
<td>Password:</td>
<td>
<input type="password" name="pass">
</td>
</tr>
<tr>
<td>
<input type="button" value="Login" onclick="login(this.form)">
</td>
</tr>
</table>
</form>
</div>
<div id="registerbox">
<h2>Register</h2>
<form name="form">
<table>
<tr>
<td>Name:</td>
<td>
<input type="text" name="name" />
</td>
</tr>
<tr>
<td>E-mail:</td>
<td>
<input type="email" name="email" />
</td>
</tr>
<tr>
<td>Password</td>
<td>
<input type="password" name="pass" />
</td>
</tr>
<tr>
<td>Password confirm</td>
<td>
<input type="password" name="pass2" />
</td>
</tr>
<tr>
<td>
<input type="button" value="Register" onclick="register(this.form)" />
</td>
</tr>
</table>
</form>
</div>
</div>
<div id="usercontent">
<div id="channelmessages"></div>
<textarea id="msginput" autofocus="autofocus"></textarea>
</div>
</body>

View file

@ -3,13 +3,32 @@
*/
$(document).ready(function () {
conversationChanged();
});
function conversationChanged() {
var cmsgs = document.getElementById("channelmessages");
if (cmsgs != null && cmsgs.childElementCount > 0) {
var nodes = cmsgs.children;
var nodes = cmsgs.getElementsByClassName("chmessage");
for (var x = 0; x < nodes.length; x++) {
var item = nodes[x];
if (item.tagName == "DIV")
handlereceivedmessage(item);
handlereceivedmessage(item);
}
}
});
if (document.getElementById("convidp").innerText == "-1") {
document.getElementById("msginput").style.display = "none";
stopPoll();
}
else {
document.getElementById("msginput").style.display = "block";
startPoll();
}
}
function changeConversation(convid) {
var convidp = document.getElementById("convidp");
if (convidp.innerText == convid + "")
return;
convidp.innerText = convid;
conversationChanged();
}

View file

@ -23,9 +23,9 @@ var updateUnreadCount = function () {
var msgs = document.getElementById("channelmessages").getElementsByClassName("chmessage");
for (var i = msgs.length - 1; i >= 0; i--) {
if (i >= msgs.length - unreadCount)
msgs[i].style.backgroundColor = "darkgray";
msgs[i].classList.add("unreadmsg");
else
msgs[i].style = "";
msgs[i].classList.remove("unreadmsg");
updatemsgtime(msgs[i]);
}
};
@ -40,29 +40,49 @@ var resetUnread = function resetUnread() {
updateUnreadCount();
};
var shouldpoll = false;
function poll() {
setTimeout(function () {
if (!shouldpoll)
return;
$.ajax({
url: "/receivemessage", data: document.getElementById("convidp").innerText, success: function (data) {
var msgs = document.getElementById("channelmessages");
msgs.innerHTML += data;
var msgelement = msgs.children[msgs.children.length - 1];
handlereceivedmessage(msgelement);
if (justsentmsgread)
justsentmsgread = false;
else
addUnread();
}, error: function (data) {
if (data.responseText) {
if (data.responseText.indexOf("ERROR") == -1)
showError(data.responseText);
else
console.log("Got empty string error...");
}
else
showError("Can't connect to the server!");
}, dataType: "text", complete: poll, method: "POST"
});
}, 100);
};
function startPoll() {
if (!shouldpoll) {
shouldpoll = true;
poll();
}
}
function stopPoll() {
shouldpoll = false;
}
var readTimer = null;
$(document).ready(function () {
$('#msginput').on("focus", function () { readTimer == null ? readTimer = setTimeout(function () { resetUnread(); }, 3000) : readTimer; });
$('#msginput').on("keydown", resetUnread);
$('#msginput').on("blur", function () { readTimer != null ? clearTimeout(readTimer) : readTimer; });
if (isLoggedIn())
(function poll() {
setTimeout(function () {
$.ajax({
url: "/receivemessage", success: function (data) {
var msgs = document.getElementById("channelmessages");
msgs.innerHTML += data;
var msgelement = msgs.children[msgs.children.length - 1];
handlereceivedmessage(msgelement);
if (justsentmsgread)
justsentmsgread = false;
else
addUnread();
}, error: function (data) {
showError(data.responseText);
}, dataType: "text", complete: poll
});
}, 100);
})();
});

View file

@ -38,9 +38,12 @@ var sendmsgonenter = function sendmsgonenter(e) {
var textarea = e.target;
if (textarea.value.trim().length == 0)
return;
textarea.disabled = true; //msginput
window.convid = document.getElementById("convidp").innerText * 1;
sendmsg(textarea);
var convidp = document.getElementById("convidp");
if (convidp != null) {
textarea.disabled = true; //msginput
window.convid = convidp.innerText * 1;
sendmsg(textarea);
}
};
$(document).ready(function () {

View file

@ -88,13 +88,26 @@ public class LoaderCollection<T extends SavedData> extends Loader implements Lis
idlist.clear();
}
@SuppressWarnings("unchecked")
@Override
public boolean contains(Object o) {
if (cl.isAssignableFrom(o.getClass())) {
return idlist.contains(((T) o).getId());
}
return idlist.contains(o);
}
@SuppressWarnings("unchecked")
@Override
public boolean containsAll(Collection<?> c) {
List<Long> list = new ArrayList<>();
for (Object o : c) {
if (cl.isAssignableFrom(o.getClass())) {
list.add(((T) o).getId());
}
}
if (list.size() > 0)
return idlist.containsAll(list);
return idlist.containsAll(c);
}
@ -219,11 +232,14 @@ public class LoaderCollection<T extends SavedData> extends Loader implements Lis
StringBuilder sb = new StringBuilder("[");
for (Long item : idlist) {
if (loaditems)
sb.append(DataManager.load(cl, item, true));
sb.append(DataManager.load(cl, item, true)).append(", ");
else
sb.append(item);
sb.append(item).append(", ");
}
sb.append("]");
if (sb.length() > 2)
sb.replace(sb.length() - 2, sb.length() - 1, "]");
else
sb.append("]");
return sb.toString();
}

View file

@ -32,4 +32,19 @@ public class LoaderRef<T extends SavedData> extends Loader {
public T get() {
return DataManager.load(cl, id, true);
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) {
if (obj == null)
return super.equals(obj);
if (cl.isAssignableFrom(obj.getClass()))
return ((T) obj).getId() == id;
else if (Long.class.isAssignableFrom(obj.getClass()) || long.class.isAssignableFrom(obj.getClass()))
return (Long) obj == id;
else if (LoaderRef.class.isAssignableFrom(obj.getClass()))
return ((LoaderRef<?>) obj).id == id;
else
return super.equals(obj);
}
}

View file

@ -16,6 +16,14 @@ public abstract class ManagedData implements Serializable {
protected ManagedData() {
}
@Override
public boolean equals(Object object) {
if (!this.getClass().equals(object.getClass()))
return false;
ManagedData data = (ManagedData) object;
return this.getId() == data.getId();
}
public static <T extends ManagedData> T create(Class<T> cl) {
T obj;
try {

View file

@ -5,6 +5,7 @@ import java.util.List;
import javax.persistence.*;
import io.github.norbipeti.chat.server.data.LoaderCollection;
import io.github.norbipeti.chat.server.data.LoaderRef;
@Entity
@Table(name = "\"USER\"")
@ -30,6 +31,7 @@ public class User extends SavedData {
// inverseJoinColumns = @JoinColumn(referencedColumnName = "id", unique =
// false))
private LoaderCollection<Conversation> conversations = new LoaderCollection<>(Conversation.class);
private LoaderRef<Conversation> currentconversation;
/**
* Loads all contact data
@ -91,6 +93,14 @@ public class User extends SavedData {
return conversations;
}
public LoaderRef<Conversation> getCurrentConversation() {
return currentconversation;
}
public void setCurrentConversation(LoaderRef<Conversation> currentconversation) {
this.currentconversation = currentconversation;
}
private User() {
}

View file

@ -35,34 +35,46 @@ public class IndexPage extends Page {
Element userbox = doc.getElementById("userbox");
userbox.html(userbox.html().replace("<username />", user.getName()));
Element channelmessages = doc.getElementById("channelmessages");
LogManager.getLogger().log(Level.DEBUG,
"Conversations: " + DataManager.getAll(Conversation.class).size());
LogManager.getLogger().log(Level.DEBUG, "User conversations: " + user.getConversations().size());
LogManager.getLogger().log(Level.DEBUG, "Username: " + user.getName());
if (user.getConversations().size() == 0) {
LoaderCollection<Conversation> convs = DataManager.getAll(Conversation.class);
if (convs.size() == 0) {
Conversation c = ManagedData.create(Conversation.class);
convs.add(c); // TODO: Handle no conversation open
}
user.getConversations().add(convs.get(0));
convs.get(0).getUsers().add(user);
}
Conversation conv = user.getConversations().get(0);
Element cide = channelmessages.appendElement("p");
long convid = -1;
if (user.getCurrentConversation() != null) {
Conversation conv = user.getCurrentConversation().get();
convid = conv.getId();
if (conv.getMesssageChunks().size() > 0) {
MessageChunk chunk = conv.getMesssageChunks().get(conv.getMesssageChunks().size() - 1);
for (Message message : chunk.getMessages()) {
message.getAsHTML(channelmessages);
}
}
}
cide.attr("style", "display: none");
cide.attr("id", "convidp");
cide.text(Long.toString(conv.getId()));
LogManager.getLogger().log(Level.DEBUG, "Messagechunks: " + conv.getMesssageChunks().size());
if (conv.getMesssageChunks().size() > 0) {
MessageChunk chunk = conv.getMesssageChunks().get(conv.getMesssageChunks().size() - 1);
for (Message message : chunk.getMessages()) {
message.getAsHTML(channelmessages);
cide.text(Long.toString(convid));
Element conversations = doc.getElementById("conversations");
for (Conversation conv : user.getConversations()) {
Element conversation = conversations.appendElement("div");
conversation.addClass("conversation");
String users = "";
StringBuilder sb = new StringBuilder();
for (User item : conv.getUsers()) {
sb.append(item.getName()).append(", ");
}
if (sb.length() > 2)
sb.replace(sb.length() - 2, sb.length() - 1, "");
users = sb.toString();
conversation.appendElement("a").text(users).attr("href",
"javascript:changeConversation(" + conv.getId() + ")");
}
return doc;
}, exchange);
} // TODO: Validation at registration (no special chars, etc.)
} // TODO:
// Validation
// at
// registration
// (no
// special
// chars,
// etc.)
@Override
public String GetName() {

View file

@ -14,6 +14,7 @@ import org.jsoup.nodes.Element;
import com.google.gson.JsonObject;
import com.sun.net.httpserver.HttpExchange;
import io.github.norbipeti.chat.server.data.LoaderRef;
import io.github.norbipeti.chat.server.db.domain.Conversation;
import io.github.norbipeti.chat.server.db.domain.Message;
import io.github.norbipeti.chat.server.db.domain.User;
@ -37,11 +38,27 @@ public class ReceiveMessageAjaxPage extends Page {
IOHelper.SendResponse(403, "<p>Please log in to receive messages</p>", exchange);
return;
}
exmap.put(user, exchange);
String post = IOHelper.GetPOST(exchange);
if (post.length() == 0) {
IOHelper.SendResponse(400, "ERROR: Empty string", exchange);
}
long convid = Long.parseLong(post);
LoaderRef<Conversation> currentconversation = convid == -1 ? null : new LoaderRef<>(Conversation.class, convid);
Conversation conv = currentconversation == null ? null : currentconversation.get();
if (conv == null || !conv.getUsers().contains(user))
user.setCurrentConversation(null);
else {
if (user.getCurrentConversation() == null || !user.getCurrentConversation().equals(currentconversation))
sendMessagesToUser(user, conv);
user.setCurrentConversation(currentconversation);
exmap.put(user, exchange);
}
}
public static void sendMessageBack(Message msg, Conversation conv) throws IOException {
for (User user : conv.getUsers()) { // TODO: Load older messages when scrolling up
if (user.getCurrentConversation() == null || !user.getCurrentConversation().get().equals(conv))
continue;
if (unsentmessages.containsKey(user) && unsentmessages.get(user).size() > 10) {
unsentmessages.get(user).clear();
}
@ -50,18 +67,17 @@ public class ReceiveMessageAjaxPage extends Page {
unsentmessages.get(user).add(msg);
if (exmap.containsKey(user)) {
Iterator<Message> it = unsentmessages.get(user).iterator();
String finalmsghtml = "";
Document doc = new Document("");
while (it.hasNext()) {
Message entry = it.next();
Element msgobj = entry.getAsHTML(new Document("")); // TODO: Only send messages if the user's current conversation matches
finalmsghtml += msgobj.toString() + "\n";
entry.getAsHTML(doc); // TODO: Only send messages if the user's current conversation matches
try {
it.remove(); // Remove sent message
} catch (Exception e) { // Remove users even if an error occurs (otherwise they may not be able to send a new/ message due to "headers already sent")
e.printStackTrace();
}
}
IOHelper.SendResponse(200, finalmsghtml, exmap.get(user));
IOHelper.SendResponse(200, doc.toString(), exmap.get(user));
exmap.remove(user);
if (unsentmessages.get(user).size() == 0)
unsentmessages.remove(user);
@ -70,4 +86,17 @@ public class ReceiveMessageAjaxPage extends Page {
}
}
}
public static void sendMessagesToUser(User user, Conversation conv) throws IOException {
LogManager.getLogger().debug("Attempting to send channel messages to user: " + user);
if (exmap.containsKey(user)) {
Document doc = new Document("");
for (Message msg : conv.getMesssageChunks().get(conv.getMesssageChunks().size() - 1).getMessages())
msg.getAsHTML(doc);
IOHelper.SendResponse(200, doc.toString(), exmap.get(user));
exmap.remove(user);
} else {
LogManager.getLogger().warn("User is not listening: " + user);
}
}
}

View file

@ -49,9 +49,7 @@ public class SendMessageAjaxPage extends Page {
}
LoaderCollection<Conversation> convos = user.getConversations();
Conversation conv = null;
LogManager.getLogger().log(Level.DEBUG, "Len: " + convos.size());
for (Conversation con : convos) {
LogManager.getLogger().log(Level.DEBUG, con.getId());
if (con.getId() == conversation) {
conv = con;
break;
@ -68,8 +66,6 @@ public class SendMessageAjaxPage extends Page {
msg.setMessage(message);
msg.setTime(new Date());
DataManager.save(conv);
LogManager.getLogger().log(Level.DEBUG,
"Added conversation's messagechunk count: " + conv.getMesssageChunks().size());
ReceiveMessageAjaxPage.sendMessageBack(msg, conv);