implemented paging in the frontend. todo animations

This commit is contained in:
Rohan Sircar 2019-12-12 17:59:42 +05:30 committed by nova
parent 57ca9d7380
commit 718e47c3ab
7 changed files with 115 additions and 34 deletions

View File

@ -26,7 +26,7 @@ public interface ChatMessageRepository extends JpaRepository<ChatMessage, Long>
public List<ChatMessage> getNewMessages(String fromUser, String toUser, Date lastMessageTime); public List<ChatMessage> getNewMessages(String fromUser, String toUser, Date lastMessageTime);
@Query("select m from ChatMessage m join fetch m.messageCipher where (m.toUser.userName = ?1 or m.toUser.userName = ?2) and " @Query("select m from ChatMessage m join fetch m.messageCipher where (m.toUser.userName = ?1 or m.toUser.userName = ?2) and "
+ "(m.fromUser.userName = ?1 or m.fromUser.userName = ?2) order by m.messageTime asc") + "(m.fromUser.userName = ?1 or m.fromUser.userName = ?2) order by m.messageTime desc")
public List<ChatMessage> getAllMessages(String fromUser, String toUser, PageRequest pageRequest); public List<ChatMessage> getAllMessages(String fromUser, String toUser, PageRequest pageRequest);

View File

@ -14,6 +14,7 @@ import org.ros.chatto.repository.ChatMessageRepository;
import org.ros.chatto.repository.MessageCipherRepository; import org.ros.chatto.repository.MessageCipherRepository;
import org.ros.chatto.repository.UserRepository; import org.ros.chatto.repository.UserRepository;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;

View File

@ -19,10 +19,12 @@ export class ChatModel implements Subject {
*/ */
private readonly _observers: Observer[] = []; private readonly _observers: Observer[] = [];
private state: ChatMessageViewModel[] | null; private state: ChatMessageViewModel[] | null;
private readonly _messagePageMap: Map<string, number>;
private readonly _messagesMap: Map<string, ChatMessageViewModel[]>; private readonly _messagesMap: Map<string, ChatMessageViewModel[]>;
constructor() { constructor() {
this.state = null; this.state = null;
this._messagePageMap = new Map();
this._messagesMap = new Map(); this._messagesMap = new Map();
} }
/** /**
@ -39,10 +41,14 @@ export class ChatModel implements Subject {
console.log('Subject: Detached an observer.'); console.log('Subject: Detached an observer.');
} }
private setUserMessages(username: string, messages: ChatMessageViewModel[]) { private storeUserMessages(username: string, messages: ChatMessageViewModel[]) {
this._messagesMap.set(username, messages); this._messagesMap.set(username, messages);
} }
private getStoredUserMessages(username: string): ChatMessageViewModel[] {
return this._messagesMap.get(username)!;
}
/** /**
* Trigger an update in each subscriber. * Trigger an update in each subscriber.
*/ */
@ -60,13 +66,35 @@ export class ChatModel implements Subject {
this.notify("some user"); this.notify("some user");
} }
public async getmessages(contactName: string, passphrase: string, lastMessageTime: string | null): Promise<ChatMessageViewModel[]> { public async getMessages(contactName: string, passphrase: string, lastMessageTime: string | null): Promise<ChatMessageViewModel[]> {
const cVMs = await ChatModelHelper.getMessages(contactName, passphrase, lastMessageTime, this); if(this._messagePageMap.get(contactName) == null)
this._messagePageMap.set(contactName, 0);
else {
log.debug('page number before = ' + this._messagePageMap.get(contactName)!)
this._messagePageMap.set(contactName, this._messagePageMap.get(contactName)! + 1);
log.debug('page number after = ' + this._messagePageMap.get(contactName)!)
}
const pageNumber = this._messagePageMap.get(contactName)
const cVMs = await ChatModelHelper.getMessages(contactName, passphrase, pageNumber!, lastMessageTime, this);
if (cVMs != null) { if (cVMs != null) {
log.info('Subject: My state has just changed') log.info('Subject: My state has just changed')
log.debug(cVMs);
// this._messagesMap.set(userName, cVMs); // this._messagesMap.set(userName, cVMs);
this.setUserMessages(contactName, cVMs); const existingMessages = this.getStoredUserMessages(contactName);
log.debug(existingMessages);
log.debug(cVMs);
if (existingMessages != null) {
// existingMessages.forEach(function (elem) {
// cVMs.push(elem);
// })
const newArr = cVMs.concat(existingMessages)
// log.debug(newArr);
this.storeUserMessages(contactName, newArr);
// this.storeUserMessages(contactName, cVMs);
}
else {
this.storeUserMessages(contactName, cVMs);
}
JsonAPI.contactName = contactName; JsonAPI.contactName = contactName;
this.notify(contactName); this.notify(contactName);
} }

View File

@ -15,11 +15,11 @@ import { Sprintf } from "../singleton/Sprintf";
export class ChatModelHelper { export class ChatModelHelper {
private static readonly _encryptionService: EncryptionService = new SJCLEncryptionService(); private static readonly _encryptionService: EncryptionService = new SJCLEncryptionService();
public static async getMessages(userName: string, passphrase: string, lastMessageTime: string | null, chatModel: ChatModel): Promise<ChatMessageViewModel[]> { public static async getMessages(userName: string, passphrase: string, page: number | null, lastMessageTime: string | null, chatModel: ChatModel): Promise<ChatMessageViewModel[]> {
switch (lastMessageTime) { switch (lastMessageTime) {
case null: { case null: {
const data: ChatMessageDTO[] = await this.getAllMessagesAjax(userName); const data: ChatMessageDTO[] = await this.getPaginatedMessagesAjax(userName, page!);
return data.map(vm => this.toChatMessageVM(vm, passphrase)); return data.map(vm => this.toChatMessageVM(vm, passphrase)).reverse();
} }
default: { default: {
const data: ChatMessageDTO[] = await this.getNewMessagesAjax(userName, lastMessageTime); const data: ChatMessageDTO[] = await this.getNewMessagesAjax(userName, lastMessageTime);
@ -41,6 +41,32 @@ export class ChatModelHelper {
} }
private static async getAllMessagesAjax(toUser: string): Promise<any> { private static async getAllMessagesAjax(toUser: string): Promise<any> {
const headers = new Headers();
if (JsonAPI.authToken == null) {
log.error("authToken null");
return;
};
headers.append('X-AUTH-TOKEN', JsonAPI.authToken);
const url = Sprintf(JsonAPI.CHAT_MESSAGES_GET, toUser);
// const url = Sprintf(JsonAPI.CHAT_MESSAGE_PAGE_GET, toUser,1,5);
log.debug(url)
// const response = await fetch(`${JsonAPI.CHAT_MESSAGES_GET}/${toUser}`, {
// method: 'GET',
// headers: headers
// });
const response = await fetch(url, {
method: 'GET',
headers: headers
});
console.log(response.clone());
if (fetchErrorHandler(response.clone())) {
return null;
}
const data: Promise<any> = await response.json();
return data;
}
private static async getPaginatedMessagesAjax(toUser: string, page: number): Promise<any> {
const headers = new Headers(); const headers = new Headers();
if (JsonAPI.authToken == null) { if (JsonAPI.authToken == null) {
log.error("authToken null"); log.error("authToken null");
@ -48,7 +74,7 @@ export class ChatModelHelper {
}; };
headers.append('X-AUTH-TOKEN', JsonAPI.authToken); headers.append('X-AUTH-TOKEN', JsonAPI.authToken);
// const url = Sprintf(JsonAPI.CHAT_MESSAGES_GET, toUser); // const url = Sprintf(JsonAPI.CHAT_MESSAGES_GET, toUser);
const url = Sprintf(JsonAPI.CHAT_MESSAGE_PAGE_GET, toUser,1,5); const url = Sprintf(JsonAPI.CHAT_MESSAGE_PAGE_GET, toUser, page, 5);
log.debug(url) log.debug(url)
// const response = await fetch(`${JsonAPI.CHAT_MESSAGES_GET}/${toUser}`, { // const response = await fetch(`${JsonAPI.CHAT_MESSAGES_GET}/${toUser}`, {
// method: 'GET', // method: 'GET',

View File

@ -7,29 +7,28 @@ export namespace JsonAPI {
/** /**
* Json API URL for retrieving all messages between two users * Json API URL for retrieving all messages between two users
* *
* @format /api/chat/get/messages/some-user * @format /api/chat/get/messages/{contactName}
* @example /api/chat/get/messages/some-user
* *
* ### With sprintf * ### With sprintf
* const url = Sprintf(JsonAPI.CHAT_MESSAGES_GET, toUser); * const url = Sprintf(JsonAPI.CHAT_MESSAGES_GET, contactName);
*
* @param toUser
* The user whose messages wish to retrieve. The 'from user' part of the message is given by the current user logged in.
* /api/chat/get/messages/{toUser}
* *
* @param contactName
* The user whose messages wish to retrieve.
*/ */
export const CHAT_MESSAGES_GET = `/api/chat/get/messages/%s`; export const CHAT_MESSAGES_GET = `/api/chat/get/messages/%s`;
export const MESSAGE_POST = '/api/chat/post/message'; export const MESSAGE_POST = '/api/chat/post/message';
/** /**
* Json API URL for retrieving paginated messages between two users * Json API URL for retrieving paginated messages between two users
* * Page index starts with 0
* @format /api/chat/get/messages/some-user?page=1&size=5 will give the first page with size 5 * @example /api/chat/get/messages/some-user?page=0&size=5 will give the first page where each page has size 5
* *
* ### With sprintf * ### With sprintf
* const url = Sprintf(JsonAPI.CHAT_MESSAGES_GET, toUser); * const url = Sprintf(JsonAPI.CHAT_MESSAGES_GET, contactName);
* *
* @param toUser The user whose messages wish to retrieve. The 'from user' part of the message is given by the current user logged in. * @param contactName the user whose messages wish to retrieve
* @param page Denotes the page required * @param page denotes the page required
* @param denotes the size of each page * @param size denotes the size of each page
*/ */
export const CHAT_MESSAGE_PAGE_GET = `/api/chat/get/messages/%s?page=%d&size=%d`; export const CHAT_MESSAGE_PAGE_GET = `/api/chat/get/messages/%s?page=%d&size=%d`;

View File

@ -32,24 +32,28 @@ export class ChatView implements Observer {
this._messageContainer = deps.messageContainer; this._messageContainer = deps.messageContainer;
this._chatModel = deps.chatModel; this._chatModel = deps.chatModel;
this._messageSendTemplate = deps.messageSendTemplate; this._messageSendTemplate = deps.messageSendTemplate;
this._messageReceiveTemplate = deps.messageReceiveTemplate; this._messageReceiveTemplate = deps.messageReceiveTemplate;
this._markdownService = deps.markdownService; this._markdownService = deps.markdownService;
this._encryptionService = deps.encryptionService; this._encryptionService = deps.encryptionService;
this.addEventListeners(); this.addEventListeners();
$(document).ready(function() { $(document).ready(function () {
$('#action_menu_btn').click(function() { $('#action_menu_btn').click(function () {
$('.action_menu').toggle(); $('.action_menu').toggle();
}); });
}); });
this.chatMessagePageLoadAjax();
} }
update(data: ChatMessageViewModel[]): void { update(data: ChatMessageViewModel[]): void {
log.info('ChatView: updating view'); log.info('ChatView: updating view');
// let html: string = ""; // let html: string = "";
this._messageContainer.innerHTML = ""; this._messageContainer.innerHTML = "";
const rev: ChatMessageViewModel[] = Object.create(data)
rev.reverse();
data.forEach((vm: ChatMessageViewModel) => { data.forEach((vm: ChatMessageViewModel) => {
const vmTemp = vm; const vmTemp: ChatMessageViewModel = {...vm};
vmTemp.message = this._markdownService.render(vm.message); vmTemp.message = this._markdownService.render(vm.message);
/** Very Important!!! /** Very Important!!!
* Sanitizing HTML before displaying on webpage to prevent XSS attacks!! * Sanitizing HTML before displaying on webpage to prevent XSS attacks!!
@ -63,7 +67,8 @@ export class ChatView implements Observer {
rendered = DOMPurify.sanitize(this._messageReceiveTemplate(vmTemp)); rendered = DOMPurify.sanitize(this._messageReceiveTemplate(vmTemp));
} }
$(this._messageContainer).append(rendered); $(this._messageContainer).append(rendered);
log.debug() // log.debug(vm)
// log.debug(vmTemp)
// html += this._messageSendTemplate(vm); // html += this._messageSendTemplate(vm);
}); });
@ -94,8 +99,7 @@ export class ChatView implements Observer {
let contactName = JsonAPI.contactName; let contactName = JsonAPI.contactName;
if(contactName == null) if (contactName == null) {
{
log.error("Contact name is null"); log.error("Contact name is null");
return; return;
} }
@ -162,4 +166,29 @@ export class ChatView implements Observer {
.then(response => fetchHandler(response)); .then(response => fetchHandler(response));
} }
chatMessagePageLoadAjax() {
this._messageContainer.addEventListener('scroll', (e) => {
if ($(this._messageContainer).scrollTop() == 0) {
log.debug('Reached top')
// @ts-ignore
let passphrase: string;
let passphraseInput = document.getElementById('passphrase') as HTMLInputElement;
if (passphraseInput == null) {
log.error('passphraseInput element reference is null');
return;
}
passphrase = passphraseInput.value
if (passphrase == '' || passphrase == null) {
// alert('Please input passphrase')
// alertify.error('Please enter a passphrase');
log.error('passphrase is empty or null');
return;
}
if(JsonAPI.contactName != null)
this._chatModel.getMessages(JsonAPI.contactName , passphrase, null);
}
})
}
} }

View File

@ -106,9 +106,7 @@ export class UserView implements Observer {
let userName = elem.innerText; let userName = elem.innerText;
// @ts-ignore: Object is possibly 'null'. // @ts-ignore: Object is possibly 'null'.
document.getElementById('user-name-span').innerText = userName; document.getElementById('user-name-span').innerText = userName;
this._chatModel.getmessages(userName, passphrase, null); this._chatModel.getMessages(userName, passphrase, null);
// populateMessages(userName, passphrase);
sessionStorage.setItem('selectedUser', userName);
el.className += " active"; el.className += " active";
} }