From f34a5524fdcc618c28f7db121d2e8b9c0c87ceeb Mon Sep 17 00:00:00 2001 From: Rohan Sircar Date: Mon, 27 Jan 2020 21:43:38 +0530 Subject: [PATCH] Chat area is cleared when changing user Also frontend code has een refactored significantly --- .../ros/chatto/config/DataSourceConfig.java | 1 - .../chatto/service/DBInitializerService.java | 1 - chatto/src/main/javascript/package.json | 2 +- chatto/src/main/javascript/ts/src/main.ts | 142 ++++++------------ .../javascript/ts/src/model/AbstractModel.ts | 4 +- .../main/javascript/ts/src/model/ChatModel.ts | 126 +++++++++------- .../ts/src/model/ChatModelHelper.ts | 54 ++++--- .../ts/src/model/FetchErrorHandler.ts | 8 +- .../main/javascript/ts/src/model/UserModel.ts | 86 +++++------ .../javascript/ts/src/observe/Observable.ts | 9 +- .../javascript/ts/src/observe/Observer.ts | 5 +- .../javascript/ts/src/observe/ObserverData.ts | 4 + .../javascript/ts/src/view/AbstractView.ts | 2 +- .../main/javascript/ts/src/view/ChatView.ts | 135 ++++++++++------- .../javascript/ts/src/view/ChatViewDeps.ts | 3 +- .../javascript/ts/src/view/FetchHandler.ts | 4 +- .../main/javascript/ts/src/view/UserView.ts | 42 +++--- 17 files changed, 310 insertions(+), 318 deletions(-) create mode 100644 chatto/src/main/javascript/ts/src/observe/ObserverData.ts diff --git a/chatto/src/main/java/org/ros/chatto/config/DataSourceConfig.java b/chatto/src/main/java/org/ros/chatto/config/DataSourceConfig.java index b094ee2..f37db6a 100644 --- a/chatto/src/main/java/org/ros/chatto/config/DataSourceConfig.java +++ b/chatto/src/main/java/org/ros/chatto/config/DataSourceConfig.java @@ -9,7 +9,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import lombok.Getter; -import lombok.Setter; import lombok.extern.slf4j.Slf4j; @Configuration diff --git a/chatto/src/main/java/org/ros/chatto/service/DBInitializerService.java b/chatto/src/main/java/org/ros/chatto/service/DBInitializerService.java index a6b0ce0..18b84df 100644 --- a/chatto/src/main/java/org/ros/chatto/service/DBInitializerService.java +++ b/chatto/src/main/java/org/ros/chatto/service/DBInitializerService.java @@ -3,7 +3,6 @@ package org.ros.chatto.service; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.sql.SQLException; import javax.persistence.EntityManager; diff --git a/chatto/src/main/javascript/package.json b/chatto/src/main/javascript/package.json index 61f69ee..c6827d8 100644 --- a/chatto/src/main/javascript/package.json +++ b/chatto/src/main/javascript/package.json @@ -45,6 +45,6 @@ "alertifyjs": "global:alertify" }, "scripts": { - "watch": "watchify ts/src/main.ts -p [ tsify --target es2017 --noImplicitAny ] --debug -o ../resources/static/js/bundle.js" + "watch": "watchify ts/src/main.ts -p [ tsify --target ES6 --noImplicitAny ] --debug -o ../resources/static/js/bundle.js" } } diff --git a/chatto/src/main/javascript/ts/src/main.ts b/chatto/src/main/javascript/ts/src/main.ts index 8fc0c09..d280466 100644 --- a/chatto/src/main/javascript/ts/src/main.ts +++ b/chatto/src/main/javascript/ts/src/main.ts @@ -1,51 +1,58 @@ -import { Controller } from "./controller/AbstractController"; -import { UserModel } from "./model/UserModel" -import { Model } from "./model/AbstractModel"; -import { View } from "./view/AbstractView"; -import { UserView } from "./view/UserView"; -import { UserController } from "./controller/UserController"; -import { ModelFactory } from "./model/ModelFactory"; -import { ActiveUserViewModel } from "./viewmodel/ActiveUserViewModel"; -import { ChatMessageViewModel } from "./viewmodel/ChatMessageViewModel"; -import * as Handlebars from "handlebars"; -import { ChatModel } from "./model/ChatModel"; -import { ChatView } from "./view/ChatView"; -import { ChatController } from "./controller/ChatController"; -import { JsonAPI } from "./singleton/JsonAPI"; -import * as log from 'loglevel'; -import { EncryptionService } from "./service/EncryptionService"; -import { SJCLEncryptionService } from "./service/SJCLEncryptionService"; -import { MessageCipherDTO } from "./dto/MessageCipherDTO"; -import { SearchService } from "./service/SearchService"; -import { FuseSearchService } from "./service/FuseSearchService"; -import { ChatMessageDTO } from "./dto/ChatMessageDTO"; -import { NotificationService } from "./service/NotificationService"; -import { AlertifyNotificationService } from "./service/AlertifyNotificationService"; import { Builder } from "builder-pattern"; - -// import "./SprintfTest.d.ts" -// import { sprintf } from "sprintf-js"; -// import sprintf = require('sprintf-js'); -import { TemplateFactory } from "./template/TemplateFactory"; -import { UserViewDeps } from "./view/UserViewDeps"; -import { ChatViewDeps } from "./view/ChatViewDeps"; -import { MarkDownItMarkDownService } from "./service/MarkDownItMarkDownService"; -import { Sprintf } from "./singleton/Sprintf"; +import * as Handlebars from "handlebars"; +import * as log from 'loglevel'; +import { ChatController } from "./controller/ChatController"; +import { UserController } from "./controller/UserController"; +import { ChatModel } from "./model/ChatModel"; +import { ChatModelHelper } from "./model/ChatModelHelper"; +import { UserModel } from "./model/UserModel"; +import { AlertifyNotificationService } from "./service/AlertifyNotificationService"; import { EncryptionServiceFactory } from "./service/EncryptionServiceFactory"; +import { FuseSearchService } from "./service/FuseSearchService"; +import { MarkDownItMarkDownService } from "./service/MarkDownItMarkDownService"; +import { NotificationService } from "./service/NotificationService"; +import { SearchService } from "./service/SearchService"; +import { TemplateFactory } from "./template/TemplateFactory"; +import { ChatView } from "./view/ChatView"; +import { ChatViewDeps } from "./view/ChatViewDeps"; +import { UserView } from "./view/UserView"; +import { UserViewDeps } from "./view/UserViewDeps"; +import { ActiveUserViewModel } from "./viewmodel/ActiveUserViewModel"; + +log.setLevel("TRACE"); const usersListElement = document.getElementById('contacts-box'); const userSearchButton = document.getElementById('user-search'); const userSearchInputElement = document.getElementById('user-search-term') as HTMLInputElement; const userSearchCancelButton = document.getElementById('user-search-cancel'); +const chatArea = document.getElementById('chat-area-new'); + + const activeUserSearchService: SearchService = new FuseSearchService(["userName"]); +const ns: NotificationService = new AlertifyNotificationService(); +const encryptionService = EncryptionServiceFactory.getEncryptionService(); -log.setLevel("TRACE") -const chatModel = new ChatModel(); -const userModel = new UserModel(); -// const userModel = ModelFactory.createModel("USER"); +const chatModelHelper = new ChatModelHelper(encryptionService, ns); +const chatModel = new ChatModel(chatModelHelper); +const cvDeps: ChatViewDeps = { + chatModel: chatModel, + // @ts-ignore: Argument of type 'HTMLElement | null' is not assignable to parameter of type 'HTMLElement'. Type 'null' is not assignable to type 'HTMLElement'. + messageContainer: chatArea, + messageSendTemplate: TemplateFactory.getTemplate('msg_container_send_template'), + messageReceiveTemplate: TemplateFactory.getTemplate('msg_container_template'), + markdownService: new MarkDownItMarkDownService, + encryptionService: encryptionService, + notificationService: ns +} +const chatView = new ChatView(cvDeps); +chatModel.attach(chatView); +const chatController = new ChatController(chatModel, chatView); + + +const userModel = new UserModel(ns); const uvDeps: UserViewDeps = { model: userModel, chatModel: chatModel, @@ -61,82 +68,21 @@ const uvDeps: UserViewDeps = { userContactOfflineTemplate: TemplateFactory.getTemplate('user-contact-offline-template') } const userView = new UserView(uvDeps); - -// console.log(userBox); - userModel.attach(userView); -// userView.model - - const userController = new UserController(userModel, userView); -// userController.test(); - - - -// userModel.someBusinessMethod(activeUsersMock); -log.info("hello"); - -const chatArea = document.getElementById('chat-area-new'); -// @ts-ignore: Argument of type 'HTMLElement | null' is not assignable to parameter of type 'HTMLElement'. Type 'null' is not assignable to type 'HTMLElement'. -const cvDeps: ChatViewDeps = { - chatModel: chatModel, - // @ts-ignore: Argument of type 'HTMLElement | null' is not assignable to parameter of type 'HTMLElement'. Type 'null' is not assignable to type 'HTMLElement'. - messageContainer: chatArea, - messageSendTemplate: TemplateFactory.getTemplate('msg_container_send_template'), - messageReceiveTemplate: TemplateFactory.getTemplate('msg_container_template'), - markdownService: new MarkDownItMarkDownService, - encryptionService: EncryptionServiceFactory.getEncryptionService() - -} -const chatView = new ChatView(cvDeps); -chatModel.attach(chatView); -const chatController = new ChatController(chatModel, chatView); - - -function someFunc(vm: ActiveUserViewModel): void { - // log.info(vm); - // logger.info(vm) -} - userController.getActiveUsers(); -log.info("test"); -// someFunc(activeUserViewModelMock); -// @ts-ignore: Object is possibly 'null'. -var source = document.getElementById("msg_container_template").innerHTML; - -var msgContainerTemplate = Handlebars.compile(source); - -JsonAPI.ACTIVE_USERS_GET + 'aef'; - -const encryptionService: EncryptionService = EncryptionServiceFactory.getEncryptionService(); -let messageCipherDTO: MessageCipherDTO = encryptionService.encrypt("password", "data"); -console.log(encryptionService.decrypt("password", messageCipherDTO)); - - -async function func(): Promise { - const text = await encryptionService.decryptAsPromise("password", messageCipherDTO) - log.debug(text); -} - -func(); Handlebars.registerHelper('avatar', function () { return '
'; }); -const testList: ChatMessageDTO[] = []; -// @ts-ignore -console.log() -log.info(Sprintf("test sprintf")) -// log.info(sprintf2.sprintf("test sprintf")) -const ns: NotificationService = new AlertifyNotificationService(); + ns.success("Welcome"); // ns.errorWithDelay("Hmm very long error notif", 10); -const ss = FuseSearchService.getInstance([""]); const test = Builder().build(); diff --git a/chatto/src/main/javascript/ts/src/model/AbstractModel.ts b/chatto/src/main/javascript/ts/src/model/AbstractModel.ts index 1272f0e..aba36ec 100644 --- a/chatto/src/main/javascript/ts/src/model/AbstractModel.ts +++ b/chatto/src/main/javascript/ts/src/model/AbstractModel.ts @@ -1,7 +1,7 @@ import { Subject } from "../observe/Observable"; import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel"; -export interface Model extends Subject{ +export interface Model extends Subject { someBusinessMethod(data: Object): void; - + } \ No newline at end of file diff --git a/chatto/src/main/javascript/ts/src/model/ChatModel.ts b/chatto/src/main/javascript/ts/src/model/ChatModel.ts index 1f14929..085b267 100644 --- a/chatto/src/main/javascript/ts/src/model/ChatModel.ts +++ b/chatto/src/main/javascript/ts/src/model/ChatModel.ts @@ -1,114 +1,126 @@ import { Subject } from "../observe/Observable"; -import { Model } from "./AbstractModel"; import { Observer } from "../observe/Observer"; -import { fetchErrorHandler } from "./FetchErrorHandler"; -import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel"; -import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel"; import { JsonAPI } from "../singleton/JsonAPI"; -import log = require('loglevel'); -import { EncryptionService } from "../service/EncryptionService"; -import { SJCLEncryptionService } from "../service/SJCLEncryptionService"; -import { ChatMessageDTO } from "../dto/ChatMessageDTO"; +import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel"; import { ChatModelHelper } from "./ChatModelHelper"; +import log = require('loglevel'); +import { ObserverData } from "../observe/ObserverData"; -export class ChatModel implements Subject { +interface Params { + userName: string, + data: ChatMessageViewModel[], + op: string +} + +export class ChatModel implements Subject { /** * @type {Observer[]} List of subscribers. In real life, the list of * subscribers can be stored more comprehensively (categorized by event * type, etc.). */ - private readonly _observers: Observer[] = []; - private state: ChatMessageViewModel[] | null; + private readonly _observers: Observer[] = []; private readonly _messagePageMap: Map; private readonly _messagesMap: Map; + private readonly _chatModelHelper: ChatModelHelper; - constructor() { - this.state = null; + constructor(chatModelHelper: ChatModelHelper) { this._messagePageMap = new Map(); this._messagesMap = new Map(); + this._chatModelHelper = chatModelHelper; } /** * The subscription management methods. */ - public attach(observer: Observer): void { - console.log('Subject: Attached an observer.'); + public attach(observer: Observer): void { + log.info('Subject: Attached an observer.'); this._observers.push(observer); } - public detach(observer: Observer): void { + public detach(observer: Observer): void { const observerIndex = this._observers.indexOf(observer); this._observers.splice(observerIndex, 1); - console.log('Subject: Detached an observer.'); + log.info('Subject: Detached an observer.'); } - private storeUserMessages(username: string, messages: ChatMessageViewModel[]) { - this._messagesMap.set(username, messages); + private storeUserMessages(username: string, messages: ChatMessageViewModel[], op: string) { + switch (op) { + case "clear": this._messagesMap.set(username, []); + case "page": this._messagesMap.set(username, messages.concat(this.getStoredUserMessages(username))); break; + // case "page": this._messagesMap.set(username, messages); + case "new": this._messagesMap.set(username, this.getStoredUserMessages(username).concat(messages)); break; + default: new Error("Invalid option"); + } + } private getStoredUserMessages(username: string): ChatMessageViewModel[] { - return this._messagesMap.get(username)!; + let temp = this._messagesMap.get(username); + if (temp == null) + return []; + else { + return temp; + } } /** * Trigger an update in each subscriber. */ - public notify(userName: string): void { - console.log('Subject: Notifying observers...'); - for (const observer of this._observers) { - observer.update(this._messagesMap.get(userName)); + public notify(p: Params): void { + log.info('Subject: Notifying observers...'); + switch (p.op) { + case "clear": { + const od: ObserverData = { data: [], op: "clear" } + for (const observer of this._observers) { + observer.update(od); + } + } break; + case "new": { + const od: ObserverData = { data: p.data, op: p.op } + for (const observer of this._observers) { + observer.update(od); + } + } break; + case "page": { + const od: ObserverData = { data: p.data, op: p.op } + for (const observer of this._observers) { + observer.update(od); + } + } break; + default: { log.error("error") } } } public someBusinessMethod(chatMessageList: ChatMessageViewModel[]): void { - this.state = chatMessageList; - console.log(`Subject: My state has just changed`); - console.log(chatMessageList); - this.notify("some user"); } - public async getMessages(contactName: string, passphrase: string, lastMessageTime: string | null): Promise { + public clear(): void { + this._messagePageMap.set(JsonAPI.contactName!, 0); + this.storeUserMessages(JsonAPI.contactName!, [], "clear"); + this.notify({ userName: "", data: [], op: "clear" }) + } + + public async getMessages(contactName: string, passphrase: string, lastMessageTime: string | null, op: string): Promise { 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); + const cVMs = await this._chatModelHelper.getMessages(contactName, passphrase, pageNumber!, lastMessageTime, op); if (cVMs != null) { log.info('Subject: My state has just changed') - - // this._messagesMap.set(userName, cVMs); const existingMessages = this.getStoredUserMessages(contactName); + log.debug('existing message:') log.debug(existingMessages); + log.debug('new messages:') log.debug(cVMs); - if (existingMessages != null) { - // existingMessages.forEach(function (elem) { - // cVMs.push(elem); - // }) - const newArr = cVMs.concat(existingMessages) - // log.debug(newArr); - this.storeUserMessages(contactName, cVMs); - // this.storeUserMessages(contactName, cVMs); - } - else { - this.storeUserMessages(contactName, cVMs); - } - JsonAPI.contactName = contactName; - this.notify(contactName); + this.storeUserMessages(contactName, cVMs, op); + this.notify({ userName: contactName, data: cVMs, op: op }); } else { log.error('Messages were null'); } - if(this._messagePageMap.get(contactName) == 0) - { - log.info(cVMs[cVMs.length - 1].messageTime) - } + if (cVMs.length != 0) { - // 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)!) } return cVMs; diff --git a/chatto/src/main/javascript/ts/src/model/ChatModelHelper.ts b/chatto/src/main/javascript/ts/src/model/ChatModelHelper.ts index ed44a5f..c49856f 100644 --- a/chatto/src/main/javascript/ts/src/model/ChatModelHelper.ts +++ b/chatto/src/main/javascript/ts/src/model/ChatModelHelper.ts @@ -1,30 +1,38 @@ import * as log from "loglevel"; import { ChatMessageDTO } from "../dto/ChatMessageDTO"; import { EncryptionService } from "../service/EncryptionService"; -import { EncryptionServiceFactory } from "../service/EncryptionServiceFactory"; +import { NotificationService } from "../service/NotificationService"; import { JsonAPI } from "../singleton/JsonAPI"; import { Sprintf } from "../singleton/Sprintf"; import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel"; import { fetchErrorHandler } from "./FetchErrorHandler"; export class ChatModelHelper { - private static readonly _encryptionService: EncryptionService = EncryptionServiceFactory.getEncryptionService(); + private readonly _encryptionService: EncryptionService; + private readonly _notificationService: NotificationService; - public static async getMessages(userName: string, passphrase: string, page: number | null, lastMessageTime: string | null): Promise { + + constructor(encryptionService: EncryptionService, notificationService: NotificationService) { + this._encryptionService = encryptionService; + this._notificationService = notificationService; + } + + + public async getMessages(userName: string, passphrase: string, page: number | null, lastMessageTime: string | null, op: string): Promise { switch (lastMessageTime) { case null: { - const data: ChatMessageDTO[] = await this.getPaginatedMessagesAjax(userName, page!); - const cVMs = Promise.all(data.map(vm => this.toChatMessageVMAsync(vm, passphrase)).reverse()); + const data: ChatMessageDTO[] = await this._getPaginatedMessagesAjax(userName, page!); + const cVMs = Promise.all(data.map(vm => this._toChatMessageVMAsync(vm, passphrase)).reverse()); return cVMs; } default: { - const data: ChatMessageDTO[] = await this.getNewMessagesAjax(userName, lastMessageTime); - return data.map(vm => this.toChatMessageVM(vm, passphrase)); + const data: ChatMessageDTO[] = await this._getNewMessagesAjax(userName, lastMessageTime); + return data.map(vm => this._toChatMessageVM(vm, passphrase)); } } } - private static async toChatMessageVMAsync(chatMessageDTO: ChatMessageDTO, passphrase: string): Promise { + private async _toChatMessageVMAsync(chatMessageDTO: ChatMessageDTO, passphrase: string): Promise { const vm = new ChatMessageViewModel(); vm.fromUser = chatMessageDTO.fromUser; vm.toUser = chatMessageDTO.toUser; @@ -34,7 +42,7 @@ export class ChatModelHelper { return vm; } - private static toChatMessageVM(chatMessageDTO: ChatMessageDTO, passphrase: string): ChatMessageViewModel { + private _toChatMessageVM(chatMessageDTO: ChatMessageDTO, passphrase: string): ChatMessageViewModel { const vm = new ChatMessageViewModel(); vm.fromUser = chatMessageDTO.fromUser; vm.toUser = chatMessageDTO.toUser; @@ -44,7 +52,7 @@ export class ChatModelHelper { return vm; } - private static async getAllMessagesAjax(toUser: string): Promise { + private async _getAllMessagesAjax(toUser: string): Promise { const headers = new Headers(); if (JsonAPI.authToken == null) { log.error("authToken null"); @@ -52,51 +60,41 @@ export class ChatModelHelper { }; 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())) { + log.debug(response.clone()); + if (fetchErrorHandler(response.clone(), this._notificationService)) { return null; } const data: Promise = await response.json(); return data; } - private static async getPaginatedMessagesAjax(toUser: string, page: number): Promise { + private async _getPaginatedMessagesAjax(toUser: string, page: number): Promise { 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, page, 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())) { + log.debug(response.clone()); + if (fetchErrorHandler(response.clone(), this._notificationService)) { return null; } const data: Promise = await response.json(); return data; } - private static async getNewMessagesAjax(toUser: string, lastMessageTimeStamp: string): Promise { + private async _getNewMessagesAjax(toUser: string, lastMessageTimeStamp: string): Promise { const headers = new Headers(); if (JsonAPI.authToken == null) { log.error("authToken null"); @@ -107,8 +105,8 @@ export class ChatModelHelper { method: 'GET', headers: headers }); - console.log(response.clone()); - if (fetchErrorHandler(response.clone())) { + log.debug(response.clone()); + if (fetchErrorHandler(response.clone(), this._notificationService)) { return null; } const data: Promise = await response.json(); diff --git a/chatto/src/main/javascript/ts/src/model/FetchErrorHandler.ts b/chatto/src/main/javascript/ts/src/model/FetchErrorHandler.ts index 890d544..ecf61af 100644 --- a/chatto/src/main/javascript/ts/src/model/FetchErrorHandler.ts +++ b/chatto/src/main/javascript/ts/src/model/FetchErrorHandler.ts @@ -1,17 +1,19 @@ import log = require("loglevel"); +import { NotificationService } from "../service/NotificationService"; +import { Sprintf } from "../singleton/Sprintf"; // import { sprintf } from "sprintf-js"; /// // import sprintf = require('sprintf-js').sprintf; -export function fetchErrorHandler(response: Response) { +export function fetchErrorHandler(response: Response, ns: NotificationService) { // alertify.success('Current position : ' + alertify.get('notifier', 'position')); if (!response.ok) { return response.text().catch(err => { // the status was not ok and there is no json body // throw new Error(response.statusText); // window.alert(sprintf('Some error occured. Http code is %s', response.status)); - // alertify.error(sprintf('Some error occured. Http code is %s', response.status)); + ns.error(Sprintf('Some error occured. Http code is %s', response.status)); // @ts-ignore log.error(sprintf('Some error occured. Http code is %s', response.status)); log.error(); @@ -20,7 +22,7 @@ export function fetchErrorHandler(response: Response) { // the status was not ok but there is a json body // throw new Error(json.error.message); // example error message returned by a REST API // window.alert(sprintf('Error: %s (Http code %s)', json, response.status)); - // alertify.error(sprintf('Some error occured. Http code is %s', response.status)); + ns.error(Sprintf('Some error occured. Http code is %s', response.status)); // @ts-ignore log.error(sprintf('Some error occured. Http code is %s', response.status)); log.error(json); diff --git a/chatto/src/main/javascript/ts/src/model/UserModel.ts b/chatto/src/main/javascript/ts/src/model/UserModel.ts index 68d98bc..93373da 100644 --- a/chatto/src/main/javascript/ts/src/model/UserModel.ts +++ b/chatto/src/main/javascript/ts/src/model/UserModel.ts @@ -1,64 +1,66 @@ -import { Subject } from "../observe/Observable"; -import { Model } from "./AbstractModel"; -import { Observer } from "../observe/Observer"; -import { fetchErrorHandler } from "./FetchErrorHandler"; -import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel"; -import { JsonAPI } from "../singleton/JsonAPI"; -import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel"; import * as log from "loglevel"; +import { Subject } from "../observe/Observable"; +import { Observer } from "../observe/Observer"; +import { JsonAPI } from "../singleton/JsonAPI"; +import { ActiveUserViewModel } from "../viewmodel/ActiveUserViewModel"; +import { fetchErrorHandler } from "./FetchErrorHandler"; +import { NotificationService } from "../service/NotificationService"; -export class UserModel implements Subject { +export class UserModel implements Subject { /** * @type {Observer[]} List of subscribers. In real life, the list of * subscribers can be stored more comprehensively (categorized by event * type, etc.). */ - private readonly observers: Observer[] = []; + private readonly observers: Observer[] = []; - - private _activeUsersList: ActiveUserViewModel[] | undefined; - // @ts-ignore: Cannot find name 'hostAddress'. - constructor() { } + private _activeUsersList: ActiveUserViewModel[]; + private readonly _notificationService: NotificationService; - /** - * Getter activeUsersList - * @return {ActiveUserViewModel[] } - */ - public get activeUsersList(): ActiveUserViewModel[] | undefined { - return this._activeUsersList; + constructor(notificationService: NotificationService) { + this._activeUsersList = []; + this._notificationService = notificationService; } - - + + /** + * Getter activeUsersList + * @return {ActiveUserViewModel[] } + */ + public get activeUsersList(): ActiveUserViewModel[] { + return this._activeUsersList; + } + + /** * The subscription management methods. */ - public attach(observer: Observer): void { - console.log('Subject: Attached an observer.'); + public attach(observer: Observer): void { + log.info('Subject: Attached an observer.'); this.observers.push(observer); } - public detach(observer: Observer): void { + public detach(observer: Observer): void { const observerIndex = this.observers.indexOf(observer); this.observers.splice(observerIndex, 1); - console.log('Subject: Detached an observer.'); + log.info('Subject: Detached an observer.'); } /** * Trigger an update in each subscriber. */ public notify(): void { - console.log('Subject: Notifying observers...'); + log.info('Subject: Notifying observers...'); for (const observer of this.observers) { - observer.update(this._activeUsersList); + observer.update({ data: this._activeUsersList!, op: "" }); } } public someBusinessMethod(activeuserList: ActiveUserViewModel[]): void { this._activeUsersList = activeuserList; this.helperMethod(); - console.log(`Subject: My state has just changed`); - console.log(activeuserList); + log.info(`Subject: My state has just changed`); + log.trace(activeuserList); this.notify(); } @@ -66,31 +68,31 @@ export class UserModel implements Subject { * getActiveUsers */ public getActiveUsers(): void { - if(JsonAPI.authToken!= null){ - this.getActiveUsersAjax(JsonAPI.authToken) - .then(data => { - // // activeUsers = data; - // sessionStorage.setItem('activeUsers', JSON.stringify(data)); - // console.log(sessionStorage.getItem('activeUsers')); - console.log(`Subject: received ajax active users`); - this._activeUsersList = data; - this.notify(); - }) + if (JsonAPI.authToken != null) { + this._getActiveUsersAjax(JsonAPI.authToken) + .then(data => { + // // activeUsers = data; + // sessionStorage.setItem('activeUsers', JSON.stringify(data)); + // log.trace(sessionStorage.getItem('activeUsers')); + log.info(`Subject: received ajax active users`); + this._activeUsersList = data; + this.notify(); + }) } else { log.error('Auth token is null'); } } - async getActiveUsersAjax(authToken: string): Promise { + private async _getActiveUsersAjax(authToken: string): Promise { let headers = new Headers(); headers.append('X-AUTH-TOKEN', authToken); let response = await fetch(JsonAPI.ACTIVE_USERS_GET, { method: 'GET', headers: headers }); - console.log(response.clone()); - if (fetchErrorHandler(response.clone())) { + log.debug(response.clone()); + if (fetchErrorHandler(response.clone(), this._notificationService)) { return null; } let data = await response.json(); diff --git a/chatto/src/main/javascript/ts/src/observe/Observable.ts b/chatto/src/main/javascript/ts/src/observe/Observable.ts index a6305b9..708f5d9 100644 --- a/chatto/src/main/javascript/ts/src/observe/Observable.ts +++ b/chatto/src/main/javascript/ts/src/observe/Observable.ts @@ -1,17 +1,18 @@ import { Observer } from "./Observer"; +import { ObserverData } from "./ObserverData"; /** * The Subject interface declares a set of methods for managing subscribers. */ -export interface Subject { +export interface Subject { // Attach an observer to the subject. - attach(observer: Observer): void; + attach(observer: Observer): void; // Detach an observer from the subject. - detach(observer: Observer): void; + detach(observer: Observer): void; // Notify all observers about an event. - notify(param: any | null): void; + notify(param: any): void; } /** diff --git a/chatto/src/main/javascript/ts/src/observe/Observer.ts b/chatto/src/main/javascript/ts/src/observe/Observer.ts index 2d59f7b..3098c60 100644 --- a/chatto/src/main/javascript/ts/src/observe/Observer.ts +++ b/chatto/src/main/javascript/ts/src/observe/Observer.ts @@ -1,8 +1,9 @@ import { Subject } from "./Observable"; +import { ObserverData } from "./ObserverData"; -export interface Observer { +export interface Observer { // Receive update from subject. - update(data: any): void; + update(data: ObserverData): void; } // /** diff --git a/chatto/src/main/javascript/ts/src/observe/ObserverData.ts b/chatto/src/main/javascript/ts/src/observe/ObserverData.ts new file mode 100644 index 0000000..f920292 --- /dev/null +++ b/chatto/src/main/javascript/ts/src/observe/ObserverData.ts @@ -0,0 +1,4 @@ +export interface ObserverData { + data: Array + op: string +} \ No newline at end of file diff --git a/chatto/src/main/javascript/ts/src/view/AbstractView.ts b/chatto/src/main/javascript/ts/src/view/AbstractView.ts index 072d52d..fe4ee27 100644 --- a/chatto/src/main/javascript/ts/src/view/AbstractView.ts +++ b/chatto/src/main/javascript/ts/src/view/AbstractView.ts @@ -2,7 +2,7 @@ import { Model } from "../model/AbstractModel"; import { Controller } from "../controller/AbstractController"; import { Observer } from "../observe/Observer"; -export interface View extends Observer { +export interface View extends Observer { readonly model: Model, readonly element: any } \ No newline at end of file diff --git a/chatto/src/main/javascript/ts/src/view/ChatView.ts b/chatto/src/main/javascript/ts/src/view/ChatView.ts index 5f77dfc..7c8db66 100644 --- a/chatto/src/main/javascript/ts/src/view/ChatView.ts +++ b/chatto/src/main/javascript/ts/src/view/ChatView.ts @@ -10,14 +10,17 @@ import { JsonAPI } from "../singleton/JsonAPI"; import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel"; import { ChatViewDeps } from "./ChatViewDeps"; import { fetchHandler } from "./FetchHandler"; +import { ObserverData } from '../observe/ObserverData'; +import { NotificationService } from '../service/NotificationService'; -export class ChatView implements Observer { +export class ChatView implements Observer { private readonly _chatModel: ChatModel; private readonly _messageContainer: HTMLElement; private readonly _messageSendTemplate: Handlebars.TemplateDelegate; private readonly _messageReceiveTemplate: Handlebars.TemplateDelegate; private readonly _markdownService: MarkDownService; private readonly _encryptionService: EncryptionService; + private readonly _notificationService: NotificationService; constructor(deps: ChatViewDeps) { @@ -27,7 +30,8 @@ export class ChatView implements Observer { this._messageReceiveTemplate = deps.messageReceiveTemplate; this._markdownService = deps.markdownService; this._encryptionService = deps.encryptionService; - this.addEventListeners(); + this._notificationService = deps.notificationService; + this._initEventListeners(); $(document).ready(function () { $('#action_menu_btn').click(function () { @@ -35,67 +39,83 @@ export class ChatView implements Observer { }); }); - this.chatMessagePageLoadAjax(); + this._chatMessagePageLoadAjax(); } - update(data: ChatMessageViewModel[]): void { + update(cd: ObserverData): void { log.info('ChatView: updating view'); - // let html: string = ""; - // let currentMsg = $('.msg:first'); - // this._messageContainer.innerHTML = ""; - const rev: ChatMessageViewModel[] = Object.create(data) - rev.reverse(); - let arr: string[] = []; - rev.forEach((vm: ChatMessageViewModel) => { - const vmTemp: ChatMessageViewModel = { ...vm }; - vmTemp.message = this._markdownService.render(vm.message); - /** Very Important!!! - * Sanitizing HTML before displaying on webpage to prevent XSS attacks!! - */ - let rendered; - if (vmTemp.fromUser == JsonAPI.principleName) { - rendered = DOMPurify.sanitize(this._messageSendTemplate(vmTemp)); + switch (cd.op) { + case "clear": { + $(this._messageContainer).html(""); + } break; + case "new": { + const rev: ChatMessageViewModel[] = Object.create(cd.data) + rev.reverse(); + let arr: string[] = []; + rev.forEach((vm: ChatMessageViewModel) => { + const vmTemp: ChatMessageViewModel = { ...vm }; + vmTemp.message = this._markdownService.render(vm.message); + /** Very Important!!! + * Sanitizing HTML before displaying on webpage to prevent XSS attacks!! + */ + let rendered; + if (vmTemp.fromUser == JsonAPI.principleName) { + rendered = DOMPurify.sanitize(this._messageSendTemplate(vmTemp)); + + } + else { + rendered = DOMPurify.sanitize(this._messageReceiveTemplate(vmTemp)); + } + $(this._messageContainer).prepend(rendered); + }); + $(this._messageContainer).stop().animate({ + scrollTop: $(this._messageContainer)[0].scrollHeight + }, 1500); + } break; + default: { + const rev: ChatMessageViewModel[] = Object.create(cd.data) + rev.reverse(); + let arr: string[] = []; + rev.forEach((vm: ChatMessageViewModel) => { + const vmTemp: ChatMessageViewModel = { ...vm }; + vmTemp.message = this._markdownService.render(vm.message); + /** Very Important!!! + * Sanitizing HTML before displaying on webpage to prevent XSS attacks!! + */ + let rendered; + if (vmTemp.fromUser == JsonAPI.principleName) { + rendered = DOMPurify.sanitize(this._messageSendTemplate(vmTemp)); + + } + else { + rendered = DOMPurify.sanitize(this._messageReceiveTemplate(vmTemp)); + } + $(this._messageContainer).prepend(rendered); + }); } - else { - rendered = DOMPurify.sanitize(this._messageReceiveTemplate(vmTemp)); - } - $(this._messageContainer).prepend(rendered); - // if (currentMsg.position() != null) { - // $(this._messageContainer).scrollTop(currentMsg.position().top) - // } + } - // log.debug(vm) - // log.debug(vmTemp) - // html += this._messageSendTemplate(vm); - }); - // if (currentMsg.position() != null) { - // $(this._messageContainer).scrollTop(currentMsg.position().top) - // } - - // html = DOMPurify.sanitize(md.render(html)); - // this._element.innerHTML = html; - // log.debug(this._element.innerHTML); } - private addEventListeners(): void { - this.addChatFormEL(); + private _initEventListeners(): void { + this._addChatFormEL(); } - private addChatFormEL() { + private _addChatFormEL() { const chatForm = document.getElementById('chatMessageForm') as HTMLSelectElement; if (chatForm == null) { log.error("Chat form is null"); } else { - chatForm.addEventListener('submit', (e) => this.createChatMessageDTO(e, chatForm)) + chatForm.addEventListener('submit', (e) => this._createChatMessageDTO(e, chatForm)) } } - private createChatMessageDTO(e: Event, chatForm: HTMLSelectElement): void { + private _createChatMessageDTO(e: Event, chatForm: HTMLSelectElement): void { e.preventDefault(); let contactName = JsonAPI.contactName; @@ -106,7 +126,7 @@ export class ChatView implements Observer { } if (!chatForm.checkValidity()) { - console.log("error"); + log.error("Form is not valid"); chatForm.classList.add('was-validated'); return; } @@ -117,18 +137,24 @@ export class ChatView implements Observer { const passphraseInput = document.getElementById('passphrase') as HTMLInputElement; if (chatInput.value == '' || chatInput.value == null) { + this._notificationService.error("Please enter a message"); log.error("Chat input is null."); return; } if (passphraseInput.value == '' || passphraseInput.value == null) { - log.error("Chat input is null."); + this._notificationService.error("Please enter a passphrase"); + log.error("Passphrase is null."); return; } // @ts-ignore const messageContent = chatInput.value; - const context = { fromUser: JsonAPI.principleName, toUser: "", message: this._markdownService.render(messageContent), messageTime: new Date().toLocaleString() }; + const context = { + fromUser: JsonAPI.principleName, toUser: "", + message: this._markdownService.render(messageContent), + messageTime: new Date().toLocaleString() + }; // @ts-ignore const msgContainer: string = this._messageSendTemplate(context); $(this._messageContainer).append(DOMPurify.sanitize(msgContainer)); @@ -143,10 +169,10 @@ export class ChatView implements Observer { // "messageTime": null } // @ts-ignore - this.sendMessageAJAX(chatMessageDTO); + this._sendMessageAJAX(chatMessageDTO); } - private sendMessageAJAX(chatMessageDTO: ChatMessageDTO): void { + private _sendMessageAJAX(chatMessageDTO: ChatMessageDTO): void { let headers = new Headers(); // console.log("Token = " + btoa("hmm" + ":" + "hmm")) @@ -164,16 +190,15 @@ export class ChatView implements Observer { log.debug(response); return response.clone(); }) - .then(response => fetchHandler(response)); + .then(response => fetchHandler(response, this._notificationService)); } - chatMessagePageLoadAjax() { + private _chatMessagePageLoadAjax() { this._messageContainer.addEventListener('scroll', (e) => { - if ($(this._messageContainer).scrollTop() == 0) { + if ($(this._messageContainer).scrollTop() == 0 && $(this._messageContainer).html() != "") { let currentMsg = $('.msg:first'); log.debug('Reached top') - // @ts-ignore let passphrase: string; let passphraseInput = document.getElementById('passphrase') as HTMLInputElement; @@ -189,9 +214,11 @@ export class ChatView implements Observer { return; } if (JsonAPI.contactName != null) - this._chatModel.getMessages(JsonAPI.contactName, passphrase, null).then(() => { - log.debug(currentMsg.offset()!.top) - $(this._messageContainer).scrollTop(currentMsg.position()!.top - $('.msg').position()!.top) + this._chatModel.getMessages(JsonAPI.contactName, passphrase, null, "page").then(() => { + if (currentMsg != null) { + // log.debug(currentMsg.offset()!.top) + $(this._messageContainer).scrollTop(currentMsg.position().top - $('.msg').position()!.top) + } }); } }) diff --git a/chatto/src/main/javascript/ts/src/view/ChatViewDeps.ts b/chatto/src/main/javascript/ts/src/view/ChatViewDeps.ts index 1f55720..9b127c8 100644 --- a/chatto/src/main/javascript/ts/src/view/ChatViewDeps.ts +++ b/chatto/src/main/javascript/ts/src/view/ChatViewDeps.ts @@ -2,6 +2,7 @@ import { ChatMessageViewModel } from "../viewmodel/ChatMessageViewModel"; import { ChatModel } from "../model/ChatModel"; import { MarkDownService } from "../service/MarkDownService"; import { EncryptionService } from "../service/EncryptionService"; +import { NotificationService } from "../service/NotificationService"; export interface ChatViewDeps { chatModel: ChatModel; @@ -10,5 +11,5 @@ export interface ChatViewDeps { messageReceiveTemplate: Handlebars.TemplateDelegate; markdownService: MarkDownService; encryptionService: EncryptionService; - + notificationService: NotificationService } \ No newline at end of file diff --git a/chatto/src/main/javascript/ts/src/view/FetchHandler.ts b/chatto/src/main/javascript/ts/src/view/FetchHandler.ts index 67e18ee..3a9ac7e 100644 --- a/chatto/src/main/javascript/ts/src/view/FetchHandler.ts +++ b/chatto/src/main/javascript/ts/src/view/FetchHandler.ts @@ -1,9 +1,7 @@ import { NotificationService } from "../service/NotificationService"; -import { AlertifyNotificationService } from "../service/AlertifyNotificationService"; import { Sprintf } from "../singleton/Sprintf"; -export function fetchHandler(response: any) { - const ns: NotificationService = new AlertifyNotificationService(); +export function fetchHandler(response: Response, ns: NotificationService) { if (response.ok) { return response.json().then((json: any) => { // the status was ok and there is a json body diff --git a/chatto/src/main/javascript/ts/src/view/UserView.ts b/chatto/src/main/javascript/ts/src/view/UserView.ts index 3d6e6e7..c2ef9ba 100644 --- a/chatto/src/main/javascript/ts/src/view/UserView.ts +++ b/chatto/src/main/javascript/ts/src/view/UserView.ts @@ -6,8 +6,10 @@ import * as DOMPurify from "dompurify"; import { SearchService } from "../service/SearchService"; import { UserModel } from "../model/UserModel"; import { UserViewDeps } from "./UserViewDeps"; +import { ObserverData } from "../observe/ObserverData"; +import { JsonAPI } from "../singleton/JsonAPI"; -export class UserView implements Observer { +export class UserView implements Observer { private readonly _model: UserModel; @@ -31,35 +33,35 @@ export class UserView implements Observer { this._userContactOnlineTemplate = deps.userContactOnlineTemplate; this._userContactOfflineTemplate = deps.userContactOfflineTemplate; - this.addSearchEventListeners(); + this._addSearchEventListeners(); } - update(data: ActiveUserViewModel[]): void { + update(d: ObserverData): void { let html: string = ""; - data.forEach((element: ActiveUserViewModel) => { + d.data.forEach((element: ActiveUserViewModel) => { element.online ? html += this._userContactOnlineTemplate(element) : html += this._userContactOfflineTemplate(element); }); $(this._usersListElement).html(DOMPurify.sanitize(html)); - this.addUserCallBacks(); - console.log(this._usersListElement.innerHTML); + this._addUserCallBacks(); } - private addSearchEventListeners(): void { - this.addSearchButtonEL(); - this.addSearchCancelEL(); - this.addSearchInputEL(); + private _addSearchEventListeners(): void { + this._addSearchButtonEL(); + this._addSearchCancelEL(); + this._addSearchInputEL(); } - private addUserCallBacks(): void { + private _addUserCallBacks(): void { let userBoxes = document.getElementsByClassName('user-box'); for (let i = 0; i < userBoxes.length; i++) { let userBox = userBoxes[i]; - userBoxes[i].addEventListener('click', this.userCallBack.bind(this, userBox)); + userBoxes[i].addEventListener('click', this._userCallBack.bind(this, userBox)); } } - private userCallBack(el: Element): void { + private _userCallBack(el: Element): void { + this._chatModel.clear(); let current = document.getElementsByClassName('user-box active'); let passphrase: string = ''; @@ -104,13 +106,14 @@ export class UserView implements Observer { // console.log(this.getElementsByClassName('to-user-span')); let elem = el.getElementsByClassName('to-user-span')[0] as HTMLElement; let userName = elem.innerText; + JsonAPI.contactName = userName; // @ts-ignore: Object is possibly 'null'. document.getElementById('user-name-span').innerText = userName; - this._chatModel.getMessages(userName, passphrase, null); + this._chatModel.getMessages(userName, passphrase, null, "new"); el.className += " active"; } - private addSearchButtonEL() { + private _addSearchButtonEL() { this._userSearchButton.addEventListener('submit', (e) => { e.preventDefault(); // log.trace(temp); @@ -124,12 +127,12 @@ export class UserView implements Observer { return; } let searchResult = this._searchService.search(list, searchTerm); - this.update(searchResult); + this.update({data: searchResult, op: ""}); log.debug(searchResult); }) } - private addSearchInputEL() { + private _addSearchInputEL() { this._userSearchInputElement.addEventListener('input', (e) => { e.preventDefault(); if (this._userSearchInputElement.value.length < 2) { @@ -139,14 +142,13 @@ export class UserView implements Observer { }) } - private addSearchCancelEL() { + private _addSearchCancelEL() { this._userSearchCancelButton.addEventListener('click', (e) => { e.preventDefault(); this._userSearchInputElement.value = ""; this._userSearchCancelButton.hidden = true; let list = this._model.activeUsersList; - // @ts-ignore - this.update(list) + this.update({data: list, op: ""}) }) }