Add channel store

This commit is contained in:
Midgard 2022-06-17 23:04:16 +02:00
parent e63d8f0305
commit c3a24ee300
Signed by: midgard
GPG key ID: 511C112F1331BBB4
7 changed files with 189 additions and 81 deletions

View file

@ -63,6 +63,7 @@
<script type="text/javascript" src="/js/util.js"></script>
<script type="text/javascript" src="/js/pubsub.js"></script>
<script type="text/javascript" src="/js/localstorage_credentials.js"></script>
<script type="text/javascript" src="/js/store.js"></script>
<script type="text/javascript" src="/js/mm_client.js"></script>
<script type="text/javascript" src="/js/view/messages.js"></script>
<script type="text/javascript" src="/js/view/sidebar.js"></script>

View file

@ -62,33 +62,32 @@ function logOut(endpoint, button) {
}
function channelNameElements(team, channel) {
const teamName = team ? team.name : "";
const inTeam = teamName ? " in team " + teamName : "";
let icon = "";
let teamName = team.name;
let channelName = channel.name;
let title = "";
switch (channel.type) {
case "O": // Public channel
title = `${channel.name} in team ${team.name} (public)`;
title = `${channel.name}${inTeam} (public)`;
break;
case "P": // Private channel
icon = "🔒";
title = `${channel.name} in team ${team.name} (private)`;
title = `${channel.name}${inTeam} (private)`;
break;
case "D": // Direct message
return undefined; // XXX Because they clutter the list
teamName = "";
channelName = `👤 ...`;
title = `Direct message`;
break;
case "G": // Group chat
teamName = "";
channelName = `👥 ${channel.display_name}`;
title = `Group chat with ${channel.display_name}`;
break;
default: // Unsupported
icon = channel.type;
title = `${channel.name} in team ${team.name} (type ${channel.type})`;
title = `${channel.name}${inTeam} (type ${channel.type})`;
break;
}
@ -210,16 +209,19 @@ function checkKeyPress(event) {
}
pubsub.subscribe("MESSAGES_NEW", post => {
if (!window.hasFocus) {
return;
}
const curChan = currentChannel();
if (post.endpoint === curChan.endpoint && post.channel_id === curChan.channel_id) {
mm_client.getOrCreate(post.endpoint).markChannelRead(post.channel_id);
const client = mm_client.getOrCreate(post.endpoint);
if (window.hasFocus && post.endpoint === curChan.endpoint && post.channel_id === curChan.channel_id) {
client.markChannelRead(post.channel_id);
} else {
client.channelStore.increaseUnread(post);
}
});
pubsub.subscribe("CHANNEL_READ", ({endpoint, channel_id}) => {
mm_client.getOrCreate(endpoint).channelStore.setUnread(channel_id, 0, 0);
});
pubsub.subscribe("WINDOW_FOCUSED", () => {
const curChan = currentChannel();
if (!curChan.channel_id) return;

View file

@ -9,6 +9,8 @@ class MattermostClient {
const creds = this.credentials.get(this.endpoint);
this.token = creds ? creds.token : null;
console.info(`Created MattermostClient for ${this.endpoint}, ${this.token ? "found token" : "did not find token"}`);
this.channelStore = new store.ChannelStore(this);
}
async get(path, queryParams) {
@ -56,11 +58,16 @@ class MattermostClient {
start() {
assert(this.token);
let _ = this.getUsers();
return this.userMe().then(data => {
this.me = data;
this.websocket();
});
const users = this.getUsers();
const me = this.userMe().then(data => {this.me = data;});
const channels = this.channelStore.get();
return Promise.all([
users,
me,
channels
])
.then(() => {this.websocket();});
}
async loggedIn() {

View file

@ -3,8 +3,9 @@ const pubsub = (function() { "use strict";
const topics = [
"MESSAGES_NEW", // {endpoint, channel_id, create_at, user_id, message}
"MESSAGES_CHANGED",
"CHANNELS_NEW",
"CHANNELS_CHANGED",
"CHANNELS_RELOADED", //
"CHANNEL_UPDATED", // {endpoint, channel}
"CHANNEL_UNREAD_UPDATED", // {endpoint, channel_id, unread, mentions}
"USERS_NEW",
"USERS_CHANGED",
"CHANNEL_MEMBERS_NEW",

106
js/store.js Normal file
View file

@ -0,0 +1,106 @@
const store = (function() { "use strict";
class ChannelStore {
constructor(client) {
this.client = client;
this._channels = null;
this._unread = null;
this._promise = null;
this._fetching = false;
}
fetch() {
if (this._fetching) {
return;
}
this._fetching = true;
this._channels = null;
this._unread = null;
const teams = this.client.myTeams();
const channels = teams
.then(teams => Promise.all(teams.map(team => this.client.myChannels(team.id))))
.then(teams => teams.map(channels => arrayToHashmap(channels, "id")))
.then(flattenHashmaps);
const unread = teams
.then(teams => Promise.all(teams.map(team => this.client.getUnread(team.id))))
.then(flattenArrays);
function processUnread(channels, unread) {
// In the Mattermost API, not the number of *unread* messages but the number of *read*
// messages is returned, so we have to subtract that from the number of total messages.
let result = Object.create(null);
for (let x of unread) {
if (!(x["channel_id"] in channels)) continue;
let object = Object.create(null);
object.unread = channels[x["channel_id"]]["total_msg_count"] - x["msg_count"];
object.mentions = x["mention_count"];
result[x["channel_id"]] = object;
}
return result;
}
this._promise = Promise.all([teams, channels, unread])
.then(([teams, channels, unread]) => {
this._teams = arrayToHashmap(teams, "id");
this._channels = channels;
this._unread = processUnread(channels, unread);
this._fetching = false;
pubsub.publish("CHANNELS_RELOADED");
});
}
async get() {
if (this._promise === null) {
this.fetch();
}
await this._promise;
return {teams: this._teams, channels: this._channels, unread: this._unread};
}
async updateChannel(channel) {
if (this._promise === null) {
// Data does not exist yet and is not being requested
return;
}
await this._promise;
this._channels[channel["id"]] = channel;
}
async increaseUnread(post) {
if (this._promise === null) {
// Data does not exist yet and is not being requested
return;
}
await this._promise;
// TODO Check if post is mention and increase mentions counter if it is
this._unread[post["channel_id"]].unread += 1;
const unread = this._unread[post["channel_id"]].unread;
const mentions = this._unread[post["channel_id"]].mentions;
pubsub.publish("CHANNEL_UNREAD_UPDATED", {endpoint: this.client.endpoint, channel_id: post["channel_id"], unread, mentions});
}
async setUnread(channel_id, unread, mentions) {
if (this._promise === null) {
// Data does not exist yet and is not being requested
return;
}
await this._promise;
this._unread[channel_id].unread = unread;
this._unread[channel_id].mentions = mentions;
pubsub.publish("CHANNEL_UNREAD_UPDATED", {endpoint: this.client.endpoint, channel_id, unread, mentions});
}
}
return {ChannelStore};
})();

View file

@ -71,6 +71,23 @@ function arrayToHashmap(array, key) {
return result;
}
function flattenHashmaps(hashmaps) {
let result = Object.create(null);
for (let x of hashmaps) {
Object.assign(result, x);
}
return result;
}
function flattenArrays(arrays) {
let result = [];
for (let x of arrays) {
Object.assign(result, x);
Array.prototype.push.apply(result, x)
}
return result;
}
function startsWith(string, start) {
return string.slice(0, start.length) == start;

View file

@ -21,20 +21,16 @@ function populateServerSelectionList() {
function populateChannelList() {
async function addChannelItems(client) {
const teams = await client.myTeams();
const {teams, channels, unread} = await client.channelStore.get();
for (let team of teams) {
let nodes = [];
const [channels, unreadsList] = await Promise.all([
client.myChannels(team.id),
client.getUnread(team.id)
]);
const unreads = arrayToHashmap(unreadsList, "channel_id");
for (let channel_id in channels) {
const channel = channels[channel_id];
const team = teams[channel["team_id"]];
for (let channel of channels) {
const chanUnreads = channel["total_msg_count"] - unreads[channel.id]["msg_count"];
const chanMentions = unreads[channel.id]["mention_count"];
const chanUnread = unread[channel.id].unread;
const chanMentions = unread[channel.id].mentions;
const li = document.createElement("li");
const a = document.createElement("a");
@ -56,15 +52,12 @@ function populateChannelList() {
li.appendChild(a);
li.dataset["id"] = channel.id;
li.dataset["server"] = client.endpoint;
li.dataset["unreads"] = chanUnreads;
li.dataset["mentions"] = chanMentions;
if (chanUnreads > 0) li.className = "unread";
if (chanUnread > 0) li.className = "unread";
nodes.push(li);
}
byId("channel_list").append(...nodes);
}
}
byId("channel_list").innerHTML = "";
const endpoints = localstorage_credentials.getServers().map(server => server.endpoint);
@ -73,18 +66,7 @@ function populateChannelList() {
}
}
function increaseUnreadCount(el, post) {
// TODO Check if post is mention and increase mentions counter if it is
setUnreadCount(
el,
el.dataset["unreads"] * 1 + 1,
el.dataset["mentions"] * 1
);
}
function setUnreadCount(el, unreads, mentions) {
el.dataset["unreads"] = unreads;
el.dataset["mentions"] = mentions;
function updateUnreadCount(el, unread, mentions) {
let msgCountGem = el.querySelector('.msg_count_gem');
if (mentions > 0) {
msgCountGem.style.display = "inline-block";
@ -92,29 +74,21 @@ function setUnreadCount(el, unreads, mentions) {
} else {
msgCountGem.style.display = "none";
}
if (unreads > 0) {
if (unread > 0) {
addClass(el, "unread");
} else {
removeClass(el, "unread");
}
}
pubsub.subscribe("MESSAGES_NEW", post => {
const curChan = currentChannel();
if (!(post.endpoint === curChan.endpoint && post.channel_id === curChan.channel_id)) {
pubsub.subscribe("CHANNEL_UNREAD_UPDATED", ({endpoint, channel_id, unread, mentions}) => {
for (let el of byId("channel_list").childNodes) {
if (el.dataset["server"] == post.endpoint && el.dataset["id"] == post.channel_id) {
increaseUnreadCount(el, post);
}
if (el.dataset["server"] == endpoint && el.dataset["id"] == channel_id) {
updateUnreadCount(el, unread, mentions);
}
}
});
pubsub.subscribe("CHANNEL_READ", channel => {
for (let el of byId("channel_list").childNodes) {
if (el.dataset["server"] == channel.endpoint && el.dataset["id"] == channel.channel_id) {
setUnreadCount(el, 0, 0);
}
}
pubsub.subscribe("CHANNELS_RELOADED", () => {
populateChannelList();
});