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 { html, body {
height: 100%; height: 100%;
margin: 0; margin: 0;
}
#conversations
{
width: 20%;
float: left;
}
#conversation
{
}
#unreadmsg
{
background-color: #DDD;
} }

View file

@ -1,7 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<head> <head>
<title>Chat</title> <title>Chat</title>
<meta charset="UTF-8"/> <meta charset="UTF-8" />
<script src="js/jquery-3.1.0.js"></script> <script src="js/jquery-3.1.0.js"></script>
<script src="js/moment-with-locales.js" charset="UTF-8"></script> <script src="js/moment-with-locales.js" charset="UTF-8"></script>
<script src="js/utils.js"></script> <script src="js/utils.js"></script>
@ -9,84 +10,87 @@
<script src="js/receivemessage.js"></script> <script src="js/receivemessage.js"></script>
<script src="js/login.js"></script> <script src="js/login.js"></script>
<script src="js/register.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> <script src="js/index.js"></script>
</head> </head>
<body> <body>
<div id="errormsg"> <div id="errormsg">
</div>
<div id="sidebar">
<div id="userbox">
<p>Logged in as
<username/>
</p>
<a href="/logout">Logout</a>
</div> </div>
</div> <div id="conversations">
<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>
<div id="registerbox"> <div id="sidebar">
<h2>Register</h2> <div id="userbox">
<form name="form"> <p>Logged in as
<table> <username/>
<tr> </p>
<td>Name:</td> <a href="/logout">Logout</a>
<td> </div>
<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> <div id="loginregisterbox">
<div id="usercontent"> <div id="loginbox">
<div id="channelmessages"></div> <h2>Login</h2>
<textarea id="msginput" autofocus="autofocus"></textarea> <form>
</div> <table>
</body> <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 () { $(document).ready(function () {
conversationChanged();
});
function conversationChanged() {
var cmsgs = document.getElementById("channelmessages"); var cmsgs = document.getElementById("channelmessages");
if (cmsgs != null && cmsgs.childElementCount > 0) { if (cmsgs != null && cmsgs.childElementCount > 0) {
var nodes = cmsgs.children; var nodes = cmsgs.getElementsByClassName("chmessage");
for (var x = 0; x < nodes.length; x++) { for (var x = 0; x < nodes.length; x++) {
var item = nodes[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"); var msgs = document.getElementById("channelmessages").getElementsByClassName("chmessage");
for (var i = msgs.length - 1; i >= 0; i--) { for (var i = msgs.length - 1; i >= 0; i--) {
if (i >= msgs.length - unreadCount) if (i >= msgs.length - unreadCount)
msgs[i].style.backgroundColor = "darkgray"; msgs[i].classList.add("unreadmsg");
else else
msgs[i].style = ""; msgs[i].classList.remove("unreadmsg");
updatemsgtime(msgs[i]); updatemsgtime(msgs[i]);
} }
}; };
@ -40,29 +40,49 @@ var resetUnread = function resetUnread() {
updateUnreadCount(); 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; var readTimer = null;
$(document).ready(function () { $(document).ready(function () {
$('#msginput').on("focus", function () { readTimer == null ? readTimer = setTimeout(function () { resetUnread(); }, 3000) : readTimer; }); $('#msginput').on("focus", function () { readTimer == null ? readTimer = setTimeout(function () { resetUnread(); }, 3000) : readTimer; });
$('#msginput').on("keydown", resetUnread); $('#msginput').on("keydown", resetUnread);
$('#msginput').on("blur", function () { readTimer != null ? clearTimeout(readTimer) : readTimer; }); $('#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; var textarea = e.target;
if (textarea.value.trim().length == 0) if (textarea.value.trim().length == 0)
return; return;
textarea.disabled = true; //msginput var convidp = document.getElementById("convidp");
window.convid = document.getElementById("convidp").innerText * 1; if (convidp != null) {
sendmsg(textarea); textarea.disabled = true; //msginput
window.convid = convidp.innerText * 1;
sendmsg(textarea);
}
}; };
$(document).ready(function () { $(document).ready(function () {

View file

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

View file

@ -32,4 +32,19 @@ public class LoaderRef<T extends SavedData> extends Loader {
public T get() { public T get() {
return DataManager.load(cl, id, true); 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() { 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) { public static <T extends ManagedData> T create(Class<T> cl) {
T obj; T obj;
try { try {

View file

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

View file

@ -35,34 +35,46 @@ public class IndexPage extends Page {
Element userbox = doc.getElementById("userbox"); Element userbox = doc.getElementById("userbox");
userbox.html(userbox.html().replace("<username />", user.getName())); userbox.html(userbox.html().replace("<username />", user.getName()));
Element channelmessages = doc.getElementById("channelmessages"); 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"); 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("style", "display: none");
cide.attr("id", "convidp"); cide.attr("id", "convidp");
cide.text(Long.toString(conv.getId())); cide.text(Long.toString(convid));
LogManager.getLogger().log(Level.DEBUG, "Messagechunks: " + conv.getMesssageChunks().size()); Element conversations = doc.getElementById("conversations");
if (conv.getMesssageChunks().size() > 0) { for (Conversation conv : user.getConversations()) {
MessageChunk chunk = conv.getMesssageChunks().get(conv.getMesssageChunks().size() - 1); Element conversation = conversations.appendElement("div");
for (Message message : chunk.getMessages()) { conversation.addClass("conversation");
message.getAsHTML(channelmessages); 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; return doc;
}, exchange); }, exchange);
} // TODO: Validation at registration (no special chars, etc.) } // TODO:
// Validation
// at
// registration
// (no
// special
// chars,
// etc.)
@Override @Override
public String GetName() { public String GetName() {

View file

@ -14,6 +14,7 @@ import org.jsoup.nodes.Element;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.sun.net.httpserver.HttpExchange; 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.Conversation;
import io.github.norbipeti.chat.server.db.domain.Message; import io.github.norbipeti.chat.server.db.domain.Message;
import io.github.norbipeti.chat.server.db.domain.User; 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); IOHelper.SendResponse(403, "<p>Please log in to receive messages</p>", exchange);
return; 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 { public static void sendMessageBack(Message msg, Conversation conv) throws IOException {
for (User user : conv.getUsers()) { // TODO: Load older messages when scrolling up 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) { if (unsentmessages.containsKey(user) && unsentmessages.get(user).size() > 10) {
unsentmessages.get(user).clear(); unsentmessages.get(user).clear();
} }
@ -50,18 +67,17 @@ public class ReceiveMessageAjaxPage extends Page {
unsentmessages.get(user).add(msg); unsentmessages.get(user).add(msg);
if (exmap.containsKey(user)) { if (exmap.containsKey(user)) {
Iterator<Message> it = unsentmessages.get(user).iterator(); Iterator<Message> it = unsentmessages.get(user).iterator();
String finalmsghtml = ""; Document doc = new Document("");
while (it.hasNext()) { while (it.hasNext()) {
Message entry = it.next(); Message entry = it.next();
Element msgobj = entry.getAsHTML(new Document("")); // TODO: Only send messages if the user's current conversation matches entry.getAsHTML(doc); // TODO: Only send messages if the user's current conversation matches
finalmsghtml += msgobj.toString() + "\n";
try { try {
it.remove(); // Remove sent message 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") } 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(); e.printStackTrace();
} }
} }
IOHelper.SendResponse(200, finalmsghtml, exmap.get(user)); IOHelper.SendResponse(200, doc.toString(), exmap.get(user));
exmap.remove(user); exmap.remove(user);
if (unsentmessages.get(user).size() == 0) if (unsentmessages.get(user).size() == 0)
unsentmessages.remove(user); 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(); LoaderCollection<Conversation> convos = user.getConversations();
Conversation conv = null; Conversation conv = null;
LogManager.getLogger().log(Level.DEBUG, "Len: " + convos.size());
for (Conversation con : convos) { for (Conversation con : convos) {
LogManager.getLogger().log(Level.DEBUG, con.getId());
if (con.getId() == conversation) { if (con.getId() == conversation) {
conv = con; conv = con;
break; break;
@ -68,8 +66,6 @@ public class SendMessageAjaxPage extends Page {
msg.setMessage(message); msg.setMessage(message);
msg.setTime(new Date()); msg.setTime(new Date());
DataManager.save(conv); DataManager.save(conv);
LogManager.getLogger().log(Level.DEBUG,
"Added conversation's messagechunk count: " + conv.getMesssageChunks().size());
ReceiveMessageAjaxPage.sendMessageBack(msg, conv); ReceiveMessageAjaxPage.sendMessageBack(msg, conv);