Skip to content
Snippets Groups Projects
Commit 5c58b435 authored by RMidhunSuresh's avatar RMidhunSuresh
Browse files

Implement unread count

parent 452c5c6d
No related branches found
No related tags found
No related merge requests found
...@@ -29,6 +29,10 @@ async function fetchConfig(): Promise<IChatterboxConfig> { ...@@ -29,6 +29,10 @@ async function fetchConfig(): Promise<IChatterboxConfig> {
return config; return config;
} }
function shouldStartMinimized(): boolean {
return !!new URLSearchParams(window.location.search).get("minimized");
}
async function main() { async function main() {
const root = document.querySelector(rootDivId) as HTMLDivElement; const root = document.querySelector(rootDivId) as HTMLDivElement;
if (!root) { if (!root) {
...@@ -40,7 +44,8 @@ async function main() { ...@@ -40,7 +44,8 @@ async function main() {
const navigation = new Navigation(allowsChild); const navigation = new Navigation(allowsChild);
platform.setNavigation(navigation); platform.setNavigation(navigation);
const urlRouter = createRouter({ navigation, history: platform.history }); const urlRouter = createRouter({ navigation, history: platform.history });
const rootViewModel = new RootViewModel(config, {platform, navigation, urlCreator: urlRouter}); const startMinimized = shouldStartMinimized();
const rootViewModel = new RootViewModel(config, {platform, navigation, urlCreator: urlRouter, startMinimized});
rootViewModel.start(); rootViewModel.start();
const rootView = new RootView(rootViewModel); const rootView = new RootView(rootViewModel);
root.appendChild(rootView.mount()); root.appendChild(rootView.mount());
...@@ -67,4 +72,8 @@ function allowsChild(parent, child) { ...@@ -67,4 +72,8 @@ function allowsChild(parent, child) {
window.parent?.postMessage({ action: "minimize" }, "*"); window.parent?.postMessage({ action: "minimize" }, "*");
}; };
(window as any).sendNotificationCount = function (count: number) {
window.parent?.postMessage({ action: "unread-message", count }, "*");
};
main(); main();
import { EventEmitter } from "hydrogen-view-sdk";
export class MessageFromParent extends EventEmitter {
constructor() {
super();
window.addEventListener("message", (event) => {
const { action } = event.data;
this.emit(action, event.data);
});
}
}
...@@ -17,11 +17,13 @@ export function toggleIframe() { ...@@ -17,11 +17,13 @@ export function toggleIframe() {
if (iframeElement.style.display !== "none") { if (iframeElement.style.display !== "none") {
iframeElement.style.display = "none"; iframeElement.style.display = "none";
document.querySelector(".start-chat-btn").classList.remove("start-background-minimized"); document.querySelector(".start-chat-btn").classList.remove("start-background-minimized");
iframeElement.contentWindow.postMessage({ action: "minimize" }, "*");;
if (isMobile()) { if (isMobile()) {
startButtonDiv.style.display = "block"; startButtonDiv.style.display = "block";
} }
} }
else { else {
iframeElement.contentWindow.postMessage({ action: "maximize" }, "*");;
iframeElement.style.display = "block"; iframeElement.style.display = "block";
document.querySelector(".start-chat-btn").classList.add("start-background-minimized"); document.querySelector(".start-chat-btn").classList.add("start-background-minimized");
if (isMobile()) { if (isMobile()) {
......
...@@ -8,11 +8,31 @@ export function loadStartButton() { ...@@ -8,11 +8,31 @@ export function loadStartButton() {
loadCSS(); loadCSS();
const container = document.createElement("div"); const container = document.createElement("div");
container.className = "start"; container.className = "start";
const button = createStartButton();
container.appendChild(button);
document.body.appendChild(container);
if (window.localStorage.getItem("chatterbox-should-load-in-background")) {
/**
* If chatterbox made it to the timeline before, load the chatterbox app in background.
* This will let us watch for new messages and show a notification badge as needed.
*/
loadIframe(true);
toggleIframe();
}
}
function createStartButton() {
const button = document.createElement("button"); const button = document.createElement("button");
button.className = "start-chat-btn"; button.className = "start-chat-btn";
button.onclick = () => (window as any).isIframeLoaded? toggleIframe() : loadIframe(); button.onclick = () => (window as any).isIframeLoaded? toggleIframe() : loadIframe();
container.appendChild(button); button.appendChild(createNotificationBadge());
document.body.appendChild(container); return button;
}
function createNotificationBadge() {
const notificationBadge = document.createElement("span");
notificationBadge.className = "notification-badge hidden";
return notificationBadge;
} }
function loadCSS() { function loadCSS() {
...@@ -22,22 +42,20 @@ function loadCSS() { ...@@ -22,22 +42,20 @@ function loadCSS() {
document.head.appendChild(linkElement); document.head.appendChild(linkElement);
} }
function loadIframe() { function loadIframe(minimized = false) {
const iframe = document.createElement("iframe"); const iframe = document.createElement("iframe");
const configLocation = (window as any).CHATTERBOX_CONFIG_LOCATION; const configLocation = (window as any).CHATTERBOX_CONFIG_LOCATION;
if (!configLocation) { if (!configLocation) {
throw new Error("CHATTERBOX_CONFIG_LOCATION is not set"); throw new Error("CHATTERBOX_CONFIG_LOCATION is not set");
} }
iframe.src = new URL( iframe.src = new URL(
"../chatterbox.html?config=" + configLocation, `../chatterbox.html?config=${configLocation}${minimized? "&minimized=true": ""}`,
hostRoot hostRoot
).href; ).href;
iframe.className = "chatterbox-iframe"; iframe.className = "chatterbox-iframe";
document.body.appendChild(iframe); document.body.appendChild(iframe);
(window as any).isIframeLoaded = true; (window as any).isIframeLoaded = true;
document document .querySelector(".start-chat-btn") .classList.add("start-background-minimized");
.querySelector(".start-chat-btn")
.classList.add("start-background-minimized");
if (isMobile()) { if (isMobile()) {
(document.querySelector(".start") as HTMLDivElement).style.display = (document.querySelector(".start") as HTMLDivElement).style.display =
"none"; "none";
......
...@@ -39,3 +39,24 @@ ...@@ -39,3 +39,24 @@
.start-background-minimized { .start-background-minimized {
background: no-repeat center url("../ui/res/chevron-down-button.svg"), linear-gradient(180deg, #7657F2 0%, #5C56F5 100%); background: no-repeat center url("../ui/res/chevron-down-button.svg"), linear-gradient(180deg, #7657F2 0%, #5C56F5 100%);
} }
.notification-badge {
position: absolute;
width: 20px;
height: 20px;
color: white;
background-color: #FF5B55;
left: 47px;
bottom: 49px;
border-radius: 100%;
font-size: 12px;
font-weight: bold;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.25);
}
.hidden {
display: none;
}
...@@ -4,15 +4,35 @@ import "./parent-style.css"; ...@@ -4,15 +4,35 @@ import "./parent-style.css";
(window as any).isIframeLoaded = false; (window as any).isIframeLoaded = false;
function setUnreadCount(count) {
const notification = document.querySelector(".notification-badge") as HTMLSpanElement;
if (count === 0) {
notification.classList.add("hidden");
}
else {
notification.innerText = count;
notification.classList.remove("hidden");
}
}
window.addEventListener("message", event => { window.addEventListener("message", event => {
const { action } = event.data; const { action } = event.data;
switch (action) { switch (action) {
case "resize-iframe": case "resize-iframe":
if (event.data.view === "timeline") {
// Chatterbox has made it to the timeline!
// Store this is info in localStorage so that we know to load chatterbox in background
// in subsequent visits.
window.localStorage.setItem("chatterbox-should-load-in-background", "true");
}
resizeIframe(event.data); resizeIframe(event.data);
break; break;
case "minimize": case "minimize":
toggleIframe(); toggleIframe();
break; break;
case "unread-message":
setUnreadCount(event.data.count);
break;
} }
}); });
......
...@@ -10,7 +10,8 @@ export class ChatterboxView extends TemplateView<ChatterboxViewModel> { ...@@ -10,7 +10,8 @@ export class ChatterboxView extends TemplateView<ChatterboxViewModel> {
} }
render(t) { render(t) {
return t.div({ className: "ChatterboxView" }, [ return t.div({ className: "ChatterboxView", },
[
t.mapView( t.mapView(
(vm) => (vm.roomViewModel ? vm : null), (vm) => (vm.roomViewModel ? vm : null),
(vm) => (vm ? new RoomHeaderView(vm) : null) (vm) => (vm ? new RoomHeaderView(vm) : null)
...@@ -39,7 +40,11 @@ class RoomHeaderView extends TemplateView<ChatterboxViewModel> { ...@@ -39,7 +40,11 @@ class RoomHeaderView extends TemplateView<ChatterboxViewModel> {
avatar, avatar,
t.div({ className: "RoomHeaderView_name" }, vm => vm.roomName), t.div({ className: "RoomHeaderView_name" }, vm => vm.roomName),
t.div({ className: "RoomHeaderView_menu" }, [ t.div({ className: "RoomHeaderView_menu" }, [
t.button({ className: "RoomHeaderView_menu_minimize", onClick: () => (window as any).sendMinimizeToParent() }) t.button({
className: "RoomHeaderView_menu_minimize", onClick: () => {
vm.minimize();
}
})
]), ]),
]); ]);
} }
......
...@@ -3,11 +3,13 @@ import { RoomViewModel, ViewModel, RoomStatus} from "hydrogen-view-sdk"; ...@@ -3,11 +3,13 @@ import { RoomViewModel, ViewModel, RoomStatus} from "hydrogen-view-sdk";
export class ChatterboxViewModel extends ViewModel { export class ChatterboxViewModel extends ViewModel {
private _roomViewModel?: typeof RoomViewModel; private _roomViewModel?: typeof RoomViewModel;
private _loginPromise: Promise<void>; private _loginPromise: Promise<void>;
private _minimize: () => void;
constructor(options) { constructor(options) {
super(options); super(options);
this._client = options.client; this._client = options.client;
this._loginPromise = options.loginPromise; this._loginPromise = options.loginPromise;
this._minimize = options.minimize;
} }
async load() { async load() {
...@@ -22,13 +24,13 @@ export class ChatterboxViewModel extends ViewModel { ...@@ -22,13 +24,13 @@ export class ChatterboxViewModel extends ViewModel {
else { else {
throw new Error("ConfigError: You must either specify 'invite_user' or 'auto_join_room'"); throw new Error("ConfigError: You must either specify 'invite_user' or 'auto_join_room'");
} }
this._roomViewModel = new RoomViewModel({ this._roomViewModel = this.track(new RoomViewModel({
room, room,
ownUserId: this._session.userId, ownUserId: this._session.userId,
platform: this.platform, platform: this.platform,
urlCreator: this.urlCreator, urlCreator: this.urlCreator,
navigation: this.navigation, navigation: this.navigation,
}); }));
await this._roomViewModel.load(); await this._roomViewModel.load();
this.emitChange("roomViewModel"); this.emitChange("roomViewModel");
} }
...@@ -87,6 +89,11 @@ export class ChatterboxViewModel extends ViewModel { ...@@ -87,6 +89,11 @@ export class ChatterboxViewModel extends ViewModel {
return promise; return promise;
} }
minimize() {
(window as any).sendMinimizeToParent();
this._minimize();
}
get timelineViewModel() { get timelineViewModel() {
return this._roomViewModel?.timelineViewModel; return this._roomViewModel?.timelineViewModel;
} }
......
import { ViewModel, Client, Navigation, createRouter, Platform } from "hydrogen-view-sdk"; import { ViewModel, Client, Navigation, createRouter, Platform } from "hydrogen-view-sdk";
import { IChatterboxConfig } from "../types/IChatterboxConfig"; import { IChatterboxConfig } from "../types/IChatterboxConfig";
import { ChatterboxViewModel } from "./ChatterboxViewModel"; import { ChatterboxViewModel } from "./ChatterboxViewModel";
import "hydrogen-view-sdk/style.css"; import "hydrogen-view-sdk/style.css";
import { AccountSetupViewModel } from "./AccountSetupViewModel"; import { AccountSetupViewModel } from "./AccountSetupViewModel";
import { MessageFromParent } from "../observables/MessageFromParent";
type Options = { platform: typeof Platform, navigation: typeof Navigation, urlCreator: ReturnType<typeof createRouter> }; type Options = { platform: typeof Platform, navigation: typeof Navigation, urlCreator: ReturnType<typeof createRouter>, startMinimized: boolean };
export class RootViewModel extends ViewModel { export class RootViewModel extends ViewModel {
private _config: IChatterboxConfig; private _config: IChatterboxConfig;
...@@ -12,12 +13,17 @@ export class RootViewModel extends ViewModel { ...@@ -12,12 +13,17 @@ export class RootViewModel extends ViewModel {
private _chatterBoxViewModel?: ChatterboxViewModel; private _chatterBoxViewModel?: ChatterboxViewModel;
private _accountSetupViewModel?: AccountSetupViewModel; private _accountSetupViewModel?: AccountSetupViewModel;
private _activeSection?: string; private _activeSection?: string;
private _messageFromParent: MessageFromParent = new MessageFromParent();
private _startMinimized: boolean;
constructor(config: IChatterboxConfig, options: Options) { constructor(config: IChatterboxConfig, options: Options) {
super(options); super(options);
this._startMinimized = options.startMinimized;
this._config = config; this._config = config;
this._client = new Client(this.platform); this._client = new Client(this.platform);
this._setupNavigation(); this._setupNavigation();
this._messageFromParent.on("maximize", () => this._showTimeline(Promise.resolve()));
this._messageFromParent.on("minimize", () => this.minimizeChatterbox());
} }
private _setupNavigation() { private _setupNavigation() {
...@@ -28,6 +34,10 @@ export class RootViewModel extends ViewModel { ...@@ -28,6 +34,10 @@ export class RootViewModel extends ViewModel {
async start() { async start() {
const sessionAlreadyExists = await this.attemptStartWithExistingSession(); const sessionAlreadyExists = await this.attemptStartWithExistingSession();
if (sessionAlreadyExists) { if (sessionAlreadyExists) {
this._watchNotificationCount();
if (this._startMinimized) {
return;
}
this.navigation.push("timeline"); this.navigation.push("timeline");
return; return;
} }
...@@ -43,6 +53,7 @@ export class RootViewModel extends ViewModel { ...@@ -43,6 +53,7 @@ export class RootViewModel extends ViewModel {
config: this._config, config: this._config,
state: this._state, state: this._state,
loginPromise, loginPromise,
minimize: () => this.minimizeChatterbox()
}) })
)); ));
this._chatterBoxViewModel.load(); this._chatterBoxViewModel.load();
...@@ -77,6 +88,32 @@ export class RootViewModel extends ViewModel { ...@@ -77,6 +88,32 @@ export class RootViewModel extends ViewModel {
return false; return false;
} }
private _watchNotificationCount() {
const [room] = this._client.session.rooms.values();
let previousCount = room.notificationCount;
(window as any).sendNotificationCount(previousCount);
const subscription = {
onUpdate(_: unknown, room) {
const newCount = room.notificationCount;
if (newCount !== previousCount) {
if (!room.isUnread && newCount !== 0) {
room.clearUnread();
return;
}
(window as any).sendNotificationCount(newCount);
previousCount = newCount;
}
},
};
this.track(this._client.session.rooms.subscribe(subscription));
}
minimizeChatterbox() {
this._chatterBoxViewModel = this.disposeTracked(this._chatterBoxViewModel);
this._activeSection = "";
this.emitChange("chatterboxViewModel");
}
get chatterboxViewModel() { get chatterboxViewModel() {
return this._chatterBoxViewModel; return this._chatterBoxViewModel;
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment