Skip to content

Commit f08f9e7

Browse files
Downstream messages are digested and dispatched; chat functionality is operational
The client is now capable of chatting in any joined rooms and updates from the server are digested and dispatched to controllers, enabling those controllers to show received chat messages and maintain dynamic content such as the list of users in a room.
1 parent 50063f6 commit f08f9e7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+1915
-579
lines changed

controllers/Application.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/// <reference path="ControllerBase.ts" />
2+
3+
namespace Controllers {
4+
export class Application extends ControllerBase<any> {
5+
private _layoutController: Controllers.LayoutController;
6+
private _connectionController: Controllers.ConnectionController;
7+
private _channelController: Controllers.ChannelController;
8+
9+
constructor() {
10+
super(null);
11+
12+
this.application = this;
13+
this.client = new KGS.JSONClient((m?: string) => this.logout(m),
14+
(d: KGS.DataDigest) => this.digest(d));
15+
this.database = this.client.database;
16+
17+
this._layoutController = new Controllers.LayoutController(this);
18+
this._connectionController = new Controllers.ConnectionController(this);
19+
this._channelController = new Controllers.ChannelController(this);
20+
}
21+
22+
private static initialise() {
23+
const webComponentsReady: string = 'WebComponentsReady'; // HTML Imports and Web Components are ready - begin the application
24+
let onWebComponentsReady: () => void;
25+
onWebComponentsReady = function() {
26+
$app = new Controllers.Application();
27+
window.removeEventListener(webComponentsReady, onWebComponentsReady);
28+
};
29+
30+
window.addEventListener(webComponentsReady, onWebComponentsReady);
31+
}
32+
33+
public get layout() {
34+
return this._layoutController;
35+
}
36+
}
37+
}
38+
39+
declare var $app: Controllers.Application;

controllers/ChannelController.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/// <reference path="ControllerBase.ts" />
2+
3+
namespace Controllers {
4+
export class ChannelController extends ControllerBase<Application> {
5+
private _channelList: Views.ChannelList;
6+
7+
private _joinedChannelIds: number[];
8+
private _channelControllers: {
9+
[channelId: string]: Controllers.ChannelBase,
10+
[channelId: number]: Controllers.ChannelBase
11+
};
12+
private _activeChannelId: number;
13+
14+
constructor(parent: Application) {
15+
super(parent);
16+
this._joinedChannelIds = [];
17+
this._channelControllers = {};
18+
this._activeChannelId = null;
19+
20+
this._channelList = $('ul[is="channel-list"]')[0] as Views.ChannelList;
21+
22+
// TODO: this.application.client.dispatcher.subscribe(() => this.reset(), KGS.Downstream._LOGOUT);
23+
}
24+
25+
protected digest(digest: KGS.DataDigest) {
26+
if (digest.joinedChannelIds) {
27+
let detachControllers = this._channelControllers;
28+
this._joinedChannelIds = [];
29+
this._channelControllers = {};
30+
31+
let channelIds = this.database.joinedChannelIds;
32+
for (let i = 0; i < channelIds.length; ++i) {
33+
let cid = channelIds[i];
34+
this._joinedChannelIds.push(cid);
35+
36+
let oldController = detachControllers[cid];
37+
if (oldController == null) {
38+
this._channelControllers[cid] = new Controllers.RoomChannel(this, cid);
39+
}
40+
else {
41+
this._channelControllers[cid] = oldController;
42+
delete detachControllers[cid];
43+
}
44+
}
45+
46+
let detachChannelIds = Object.keys(detachControllers);
47+
for (let j = 0; j < detachChannelIds.length; ++j) {
48+
let controller = detachControllers[detachChannelIds[j]];
49+
if (controller) {
50+
if (this._activeChannelId == controller.channelId) this._activeChannelId = null;
51+
controller.detach();
52+
}
53+
}
54+
55+
if ((this._activeChannelId == null) && (this._joinedChannelIds.length > 0))
56+
this.activateChannel(this._joinedChannelIds[0]);
57+
else
58+
this.updateChannelList();
59+
}
60+
61+
super.digest(digest);
62+
}
63+
64+
private updateChannelList() {
65+
this._channelList.update(this.database.channels, this._joinedChannelIds, this._activeChannelId, (channelId: number) => this.activateChannel(channelId));
66+
}
67+
68+
public get activeChannel(): Controllers.ChannelBase {
69+
return (this._activeChannelId != null)? this._channelControllers[this._activeChannelId] : null;
70+
}
71+
72+
private activateChannel(channelId: number) {
73+
if (this._activeChannelId != channelId) {
74+
let controller = this.activeChannel;
75+
if (controller != null) controller.deactivate();
76+
77+
this._activeChannelId = channelId;
78+
this.updateChannelList();
79+
80+
this.activeChannel.activate();
81+
}
82+
}
83+
}
84+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/// <reference path="ControllerBase.ts" />
2+
3+
namespace Controllers {
4+
export class ConnectionController extends ControllerBase<Application> {
5+
constructor(parent: Application) {
6+
super(parent);
7+
8+
this.beginSignIn();
9+
window.setTimeout(() => this.attemptAutomaticSignIn(), 80);
10+
}
11+
12+
private beginSignIn(errorNotice?: string) {
13+
let signInForm = <Views.SignInForm>document.querySelector('sign-in-form');
14+
if (!signInForm) {
15+
signInForm = <Views.SignInForm>document.createElement('sign-in-form');
16+
this.application.layout.showLightbox(signInForm);
17+
}
18+
19+
signInForm.errorNotice = errorNotice;
20+
signInForm.submitCallback = (form) => this.submitSignIn(form);
21+
signInForm.focus();
22+
}
23+
24+
private attemptAutomaticSignIn() {
25+
if (null == Storage) return;
26+
27+
let signInForm = <Views.SignInForm>document.querySelector('sign-in-form');
28+
if (signInForm) {
29+
let lastUsername = localStorage.getItem("KGSUsername");
30+
if ((lastUsername) && (signInForm.username == lastUsername) && (signInForm.password)) {
31+
signInForm.rememberMe = true;
32+
this.submitSignIn(signInForm);
33+
return;
34+
}
35+
}
36+
37+
localStorage.removeItem("KGSUsername");
38+
}
39+
40+
private submitSignIn(signInForm: Views.SignInForm) {
41+
signInForm.disabled = true;
42+
signInForm.errorNotice = null;
43+
44+
let username: string = signInForm.username;
45+
if (typeof (Storage) !== "undefined") {
46+
if (signInForm.rememberMe)
47+
localStorage.setItem("KGSUsername", username);
48+
else
49+
localStorage.removeItem("KGSUsername");
50+
}
51+
52+
let password: string = signInForm.password;
53+
this.client.loginAsync(username, password).done(() => this.signInSuccess(signInForm)).fail(() => this.signInFailed(signInForm));
54+
}
55+
56+
private signInSuccess(signInForm: Views.SignInForm) {
57+
this.application.layout.hideLightbox(signInForm);
58+
}
59+
60+
private signInFailed(signInForm: Views.SignInForm) {
61+
signInForm.errorNotice = "Invalid username or password.";
62+
signInForm.password = "";
63+
signInForm.disabled = false;
64+
signInForm.focus();
65+
}
66+
67+
protected logout(message?: string) {
68+
this.beginSignIn("You have been disconnected.");
69+
}
70+
}
71+
}

controllers/ControllerBase.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
namespace Controllers {
2+
export abstract class ControllerBase<ParentType extends ControllerBase<any>> {
3+
protected application: Controllers.Application;
4+
protected client: KGS.JSONClient;
5+
protected database: KGS.Database;
6+
7+
protected parent: ParentType;
8+
protected children: ControllerBase<any>[];
9+
10+
constructor(parent: ParentType) {
11+
this.attachParent(parent);
12+
this.children = [];
13+
}
14+
15+
protected attachParent(parent: ParentType) {
16+
if (this.parent) {
17+
this.detachParent();
18+
}
19+
20+
this.parent = parent;
21+
if (this.parent) this.parent.children.push(this);
22+
23+
this.resolveApplication();
24+
}
25+
protected resolveApplication() {
26+
this.application = (this.parent)? this.parent.application : null;
27+
this.client = (this.application)? this.application.client : null;
28+
this.database = (this.client)? this.client.database : null;
29+
}
30+
31+
protected detachChildren() {
32+
for (let c = (this.children.length - 1); c >= 0; --c) {
33+
this.children[c].detach();
34+
}
35+
}
36+
protected detachParent() {
37+
if (this.parent) {
38+
for (let c = (this.parent.children.length - 1); c >= 0; --c) {
39+
if (this === this.parent.children[c]) {
40+
this.parent.children.splice(c, 1);
41+
break;
42+
}
43+
}
44+
}
45+
46+
this.parent = null;
47+
this.resolveApplication();
48+
}
49+
public detach() {
50+
this.detachChildren();
51+
this.detachParent();
52+
}
53+
54+
protected logout(message?: string) {
55+
for (let c = 0; c < this.children.length; ++c) {
56+
this.children[c].logout(message);
57+
}
58+
}
59+
60+
protected digest(digest: KGS.DataDigest) {
61+
for (let c = 0; c < this.children.length; ++c) {
62+
this.children[c].digest(digest);
63+
}
64+
}
65+
}
66+
}

controllers/LayoutController.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/// <reference path="ControllerBase.ts" />
2+
3+
namespace Controllers {
4+
export class LayoutController extends ControllerBase<Application> {
5+
private _sidebarContainer: HTMLDivElement;
6+
private _sidebarView: HTMLElement;
7+
8+
constructor(parent: Application) {
9+
super(parent);
10+
$layout = this;
11+
12+
this._sidebarContainer = document.querySelector('#sidebar > div') as HTMLDivElement;
13+
this.clearSidebar();
14+
}
15+
16+
public showLightbox(view: HTMLElement) {
17+
Views.LightboxContainer.showLightbox(view);
18+
}
19+
20+
public hideLightbox(view: HTMLElement) {
21+
Views.LightboxContainer.hideLightbox(view);
22+
}
23+
24+
public showSidebar(view: HTMLElement) {
25+
this.clearSidebar();
26+
27+
this._sidebarView = view;
28+
this._sidebarContainer.appendChild(view);
29+
}
30+
31+
public clearSidebar() {
32+
$(this._sidebarContainer).children().detach();
33+
this._sidebarView = null;
34+
}
35+
}
36+
}
37+
38+
declare var $layout: Controllers.LayoutController;
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/// <reference path="../ControllerBase.ts" />
2+
3+
namespace Controllers {
4+
export abstract class ChannelBase extends ControllerBase<ChannelController> {
5+
private _channelId: number;
6+
private _activated: boolean;
7+
8+
private _chat: Views.ChatForm;
9+
10+
constructor(parent: ChannelController, channelId: number) {
11+
super(parent);
12+
this._channelId = channelId;
13+
this._activated = false;
14+
}
15+
16+
protected digest(digest: KGS.DataDigest) {
17+
if (this._activated) {
18+
if (digest.channelChat[this.channelId]) this.updateChatMessages();
19+
if (digest.channelUsers[this.channelId]) this.updateChatMembers();
20+
}
21+
}
22+
23+
public get channelId(): number {
24+
return this._channelId;
25+
}
26+
27+
public get channel(): Models.Channel {
28+
return this.database.channels[this._channelId];
29+
}
30+
31+
public activate(): boolean {
32+
if (this._activated) return false;
33+
34+
if (this._chat != null) {
35+
this.application.layout.showSidebar(this._chat);
36+
}
37+
38+
this._activated = true;
39+
40+
this.updateChatMessages();
41+
this.updateChatMembers();
42+
43+
return true;
44+
}
45+
46+
public deactivate(): boolean {
47+
if (!this._activated) return false;
48+
this._activated = false;
49+
return true;
50+
}
51+
52+
public get activated(): boolean {
53+
return this._activated;
54+
}
55+
56+
public get chat(): Views.ChatForm {
57+
return this._chat;
58+
}
59+
60+
protected initialiseChat() {
61+
this._chat = document.createElement('chat-form') as Views.ChatForm;
62+
this._chat.submitCallback = (form) => this.submitChatMessage(form);
63+
}
64+
65+
private updateChatMessages() {
66+
if ((!this._activated) || (this._chat == null)) return;
67+
this._chat.messageList.update(this.channel.chats);
68+
}
69+
private updateChatMembers() {
70+
if ((!this._activated) || (this._chat == null)) return;
71+
this._chat.memberList.update(this.database.users, this.channel.users);
72+
}
73+
74+
private submitChatMessage(chatForm: Views.ChatForm) {
75+
let text = chatForm.message;
76+
if ((text) && (text.length > 0) && (text.length <= KGS.Upstream._CHAT_MaxLength)) {
77+
this.client.post(<KGS.Upstream.CHAT>{
78+
type: KGS.Upstream._CHAT,
79+
channelId: this._channelId,
80+
text:text
81+
});
82+
83+
chatForm.message = "";
84+
chatForm.focus();
85+
}
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)