controller is guildized
This commit is contained in:
parent
a6097f9ccb
commit
64490d027f
@ -7,44 +7,44 @@ import Util from './util';
|
||||
import Globals from './globals';
|
||||
|
||||
import UI from './ui';
|
||||
import ClientController from './client-controller';
|
||||
import CombinedGuild from './guild-combined';
|
||||
import { Channel } from './data-types';
|
||||
import Q from './q-module';
|
||||
|
||||
export default class Actions {
|
||||
static async fetchAndUpdateConnection(ui: UI, server: ClientController) {
|
||||
static async fetchAndUpdateConnection(ui: UI, guild: CombinedGuild) {
|
||||
// Explicitly not using withPotentialError to make this simpler
|
||||
try {
|
||||
let connection = await server.fetchConnectionInfo();
|
||||
ui.setActiveConnection(server, connection);
|
||||
let connection = await guild.fetchConnectionInfo();
|
||||
ui.setActiveConnection(guild, connection);
|
||||
} catch (e) {
|
||||
LOG.error('Error updating current connection', e);
|
||||
ui.setActiveConnection(server, { id: null, avatarResourceId: null, displayName: 'Error', status: 'Error', privileges: [], roleName: null, roleColor: null, rolePriority: null });
|
||||
ui.setActiveConnection(guild, { id: null, avatarResourceId: null, displayName: 'Error', status: 'Error', privileges: [], roleName: null, roleColor: null, rolePriority: null });
|
||||
}
|
||||
}
|
||||
|
||||
static async fetchAndUpdateMembers(q: Q, ui: UI, server: ClientController) {
|
||||
static async fetchAndUpdateMembers(q: Q, ui: UI, guild: CombinedGuild) {
|
||||
await Util.withPotentialErrorWarnOnCancel(q, {
|
||||
taskFunc: async () => {
|
||||
if (ui.activeServer === null || ui.activeServer.id !== server.id) return;
|
||||
let members = await server.grabMembers();
|
||||
await ui.setMembers(server, members);
|
||||
if (ui.activeGuild === null || ui.activeGuild.id !== guild.id) return;
|
||||
let members = await guild.fetchMembers();
|
||||
await ui.setMembers(guild, members);
|
||||
},
|
||||
errorIndicatorAddFunc: async (errorIndicatorElement) => {
|
||||
await ui.setMembersErrorIndicator(server, errorIndicatorElement);
|
||||
await ui.setMembersErrorIndicator(guild, errorIndicatorElement);
|
||||
},
|
||||
errorContainer: q.$('#server-members'),
|
||||
errorMessage: 'Error loading members'
|
||||
});
|
||||
}
|
||||
|
||||
static async fetchAndUpdateChannels(q: Q, ui: UI, server: ClientController) {
|
||||
static async fetchAndUpdateChannels(q: Q, ui: UI, guild: CombinedGuild) {
|
||||
await Util.withPotentialErrorWarnOnCancel(q, {
|
||||
taskFunc: async () => {
|
||||
if (ui.activeServer === null || ui.activeServer.id !== server.id) return;
|
||||
let channels = await server.grabChannels();
|
||||
await ui.setChannels(server, channels);
|
||||
if (ui.activeServer === null || ui.activeServer.id !== server.id) return;
|
||||
if (ui.activeGuild === null || ui.activeGuild.id !== guild.id) return;
|
||||
let channels = await guild.fetchChannels();
|
||||
await ui.setChannels(guild, channels);
|
||||
if (ui.activeGuild === null || ui.activeGuild.id !== guild.id) return;
|
||||
if (ui.activeChannel === null) {
|
||||
// click on the first channel in the list if no channel is active yet
|
||||
let element = q.$_('#channel-list .channel');
|
||||
@ -54,45 +54,45 @@ export default class Actions {
|
||||
}
|
||||
},
|
||||
errorIndicatorAddFunc: async (errorIndicatorElement) => {
|
||||
await ui.setChannelsErrorIndicator(server, errorIndicatorElement);
|
||||
await ui.setChannelsErrorIndicator(guild, errorIndicatorElement);
|
||||
},
|
||||
errorContainer: q.$('#channel-list'),
|
||||
errorMessage: 'Error fetching channels'
|
||||
});
|
||||
}
|
||||
|
||||
static async fetchAndUpdateMessagesRecent(q: Q, ui: UI, server: ClientController, channel: Channel | { id: string }) {
|
||||
static async fetchAndUpdateMessagesRecent(q: Q, ui: UI, guild: CombinedGuild, channel: Channel | { id: string }) {
|
||||
await Util.withPotentialErrorWarnOnCancel(q, {
|
||||
taskFunc: async () => {
|
||||
if (ui.activeServer === null || ui.activeServer.id !== server.id) return;
|
||||
if (ui.activeGuild === null || ui.activeGuild.id !== guild.id) return;
|
||||
if (ui.activeChannel === null || ui.activeChannel.id !== channel.id) return;
|
||||
let messages = await server.grabRecentMessages(channel.id, Globals.MESSAGES_PER_REQUEST);
|
||||
await ui.setMessages(server, channel, messages, { atTop: messages.length < Globals.MESSAGES_PER_REQUEST, atBottom: true });
|
||||
let messages = await guild.fetchMessagesRecent(channel.id, Globals.MESSAGES_PER_REQUEST);
|
||||
await ui.setMessages(guild, channel, messages, { atTop: messages.length < Globals.MESSAGES_PER_REQUEST, atBottom: true });
|
||||
},
|
||||
errorIndicatorAddFunc: async (errorIndicatorElement) => {
|
||||
await ui.setMessagesErrorIndicator(server, channel, errorIndicatorElement);
|
||||
await ui.setMessagesErrorIndicator(guild, channel, errorIndicatorElement);
|
||||
},
|
||||
errorContainer: q.$('#channel-feed'),
|
||||
errorMessage: 'Error fetching messages'
|
||||
});
|
||||
}
|
||||
|
||||
static async fetchAndUpdateMessagesBefore(q: Q, ui: UI, server: ClientController, channel: Channel) {
|
||||
static async fetchAndUpdateMessagesBefore(q: Q, ui: UI, guild: CombinedGuild, channel: Channel) {
|
||||
await Util.withPotentialErrorWarnOnCancel(q, {
|
||||
taskFunc: async () => {
|
||||
if (ui.activeServer === null || ui.activeServer.id !== server.id) return;
|
||||
if (ui.activeGuild === null || ui.activeGuild.id !== guild.id) return;
|
||||
if (ui.activeChannel === null || ui.activeChannel.id !== channel.id) return;
|
||||
let topPair = ui.getTopMessagePair();
|
||||
if (topPair == null) return;
|
||||
let messages = await server.fetchMessagesBefore(channel.id, topPair.message.id, Globals.MESSAGES_PER_REQUEST);
|
||||
let messages = await guild.fetchMessagesBefore(channel.id, topPair.message.id, Globals.MESSAGES_PER_REQUEST);
|
||||
if (messages && messages.length > 0) {
|
||||
await ui.addMessagesBefore(server, channel, messages, topPair.message);
|
||||
await ui.addMessagesBefore(guild, channel, messages, topPair.message);
|
||||
} else {
|
||||
ui.messagesAtTop = true;
|
||||
}
|
||||
},
|
||||
errorIndicatorAddFunc: async (errorIndicatorElement) => {
|
||||
await ui.addMessagesErrorIndicatorBefore(server, channel, errorIndicatorElement);
|
||||
await ui.addMessagesErrorIndicatorBefore(guild, channel, errorIndicatorElement);
|
||||
},
|
||||
errorContainer: q.$('#channel-feed'),
|
||||
errorClasses: [ 'before' ],
|
||||
@ -100,22 +100,22 @@ export default class Actions {
|
||||
});
|
||||
}
|
||||
|
||||
static async fetchAndUpdateMessagesAfter(q: Q, ui: UI, server: ClientController, channel: Channel) {
|
||||
static async fetchAndUpdateMessagesAfter(q: Q, ui: UI, guild: CombinedGuild, channel: Channel) {
|
||||
await Util.withPotentialErrorWarnOnCancel(q, {
|
||||
taskFunc: async () => {
|
||||
if (ui.activeServer === null || ui.activeServer.id !== server.id) return;
|
||||
if (ui.activeGuild === null || ui.activeGuild.id !== guild.id) return;
|
||||
if (ui.activeChannel === null || ui.activeChannel.id !== channel.id) return;
|
||||
let bottomPair = ui.getBottomMessagePair();
|
||||
if (bottomPair == null) return;
|
||||
let messages = await server.fetchMessagesAfter(channel.id, bottomPair.message.id, Globals.MESSAGES_PER_REQUEST);
|
||||
let messages = await guild.fetchMessagesAfter(channel.id, bottomPair.message.id, Globals.MESSAGES_PER_REQUEST);
|
||||
if (messages && messages.length > 0) {
|
||||
await ui.addMessagesAfter(server, channel, messages, bottomPair.message);
|
||||
await ui.addMessagesAfter(guild, channel, messages, bottomPair.message);
|
||||
} else {
|
||||
ui.messagesAtBottom = true;
|
||||
}
|
||||
},
|
||||
errorIndicatorAddFunc: async (errorIndicatorElement) => {
|
||||
await ui.addMessagesErrorIndicatorAfter(server, channel, errorIndicatorElement);
|
||||
await ui.addMessagesErrorIndicatorAfter(guild, channel, errorIndicatorElement);
|
||||
},
|
||||
errorContainer: q.$('#channel-feed'),
|
||||
errorClasses: [ 'after' ],
|
||||
|
@ -142,6 +142,9 @@ export class AutoVerifier<T> {
|
||||
return await new Promise<T | null>(async (resolve: (result: T | null) => void, reject: (error: Error) => void) => {
|
||||
let resolved = false;
|
||||
try {
|
||||
let origTrustedStatus = this.trustedStatus;
|
||||
let origTrustedPromise = this.trustedPromise;
|
||||
|
||||
let primaryPromise = this.primaryFunc();
|
||||
|
||||
if (this.trustedStatus === 'none') {
|
||||
@ -154,6 +157,9 @@ export class AutoVerifier<T> {
|
||||
if (primaryResult) {
|
||||
resolve(primaryResult);
|
||||
resolved = true;
|
||||
} else if (origTrustedStatus === 'verified' && origTrustedPromise === this.trustedPromise) {
|
||||
// Unverify if the primary returns null after it was verified
|
||||
this.unverify();
|
||||
}
|
||||
|
||||
//@ts-ignore (could be changed by an unverify during primaryPromise)
|
||||
|
@ -5,73 +5,87 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
|
||||
import Logger from '../../logger/logger';
|
||||
const LOG = Logger.create(__filename, electronConsole);
|
||||
|
||||
import { EventEmitter } from "stream";
|
||||
|
||||
import ClientController from "./client-controller";
|
||||
|
||||
import * as socketio from 'socket.io-client';
|
||||
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
import DBCache from './db-cache';
|
||||
import { ServerConfig } from './data-types';
|
||||
import { Changes, Channel, GuildMetadata, GuildMetadataWithIds, Member, Message, Resource, ServerConfig, SocketConfig, Token } from './data-types';
|
||||
import { IAddServerData } from './elements/overlay-add-server';
|
||||
import { DefaultEventMap, EventEmitter } from 'tsee';
|
||||
import CombinedGuild from './guild-combined';
|
||||
import PersonalDB from './personal-db';
|
||||
import MessageRAMCache from './message-ram-cache';
|
||||
import ResourceRAMCache from './resource-ram-cache';
|
||||
|
||||
export default class Controller extends EventEmitter {
|
||||
public servers: ClientController[] = [];
|
||||
export default class Controller extends EventEmitter<{
|
||||
'connect': (guild: CombinedGuild) => void;
|
||||
'disconnect': (guild: CombinedGuild) => void;
|
||||
|
||||
'update-metadata': (guild: CombinedGuild, guildMeta: GuildMetadata) => void;
|
||||
|
||||
'new-channels': (guild: CombinedGuild, channels: Channel[]) => void;
|
||||
'update-channels': (guild: CombinedGuild, updatedChannels: Channel[]) => void;
|
||||
|
||||
'new-members': (guild: CombinedGuild, members: Member[]) => void;
|
||||
'update-members': (guild: CombinedGuild, updatedMembers: Member[]) => void;
|
||||
|
||||
'new-messages': (guild: CombinedGuild, messages: Message[]) => void;
|
||||
'update-messages': (guild: CombinedGuild, updatedMessages: Message[]) => void;
|
||||
|
||||
'conflict-metadata': (guild: CombinedGuild, oldGuildMeta: GuildMetadata, newGuildMeta: GuildMetadata) => void;
|
||||
'conflict-channels': (guild: CombinedGuild, changes: Changes<Channel>) => void;
|
||||
'conflict-members': (guild: CombinedGuild, changes: Changes<Member>) => void;
|
||||
'conflict-messages': (guild: CombinedGuild, changes: Changes<Message>) => void;
|
||||
'conflict-tokens': (guild: CombinedGuild, changes: Changes<Token>) => void;
|
||||
'conflict-resource': (guild: CombinedGuild, oldResource: Resource, newResource: Resource) => void;
|
||||
}> {
|
||||
public guilds: CombinedGuild[] = [];
|
||||
|
||||
constructor(
|
||||
private dbCache: DBCache
|
||||
private messageRAMCache: MessageRAMCache,
|
||||
private resourceRAMCache: ResourceRAMCache,
|
||||
private personalDB: PersonalDB
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async _connectFromConfig(serverConfig: ServerConfig): Promise<ClientController> {
|
||||
LOG.debug(`connecting to server#${serverConfig.guildId} at ${serverConfig.url}`);
|
||||
async _connectFromConfig(guildMetadata: GuildMetadataWithIds, socketConfig: SocketConfig): Promise<CombinedGuild> {
|
||||
LOG.debug(`connecting to server#${guildMetadata.id} at ${socketConfig.url}`);
|
||||
|
||||
let server = new ClientController(this.dbCache, serverConfig);
|
||||
let guild = await CombinedGuild.create(
|
||||
guildMetadata,
|
||||
socketConfig,
|
||||
this.messageRAMCache,
|
||||
this.resourceRAMCache,
|
||||
this.personalDB
|
||||
);
|
||||
|
||||
await this.dbCache.clearAllMemberStatus(server.id);
|
||||
await this.personalDB.clearAllMembersStatus(guild.id);
|
||||
|
||||
this.servers.push(server);
|
||||
this.guilds.push(guild);
|
||||
|
||||
// Forward server events through this event emitter
|
||||
let serverEvents = [
|
||||
'connected',
|
||||
'disconnected',
|
||||
'verified',
|
||||
'new-message',
|
||||
'update-server',
|
||||
'deleted-members',
|
||||
'updated-members',
|
||||
'added-members',
|
||||
'deleted-channels',
|
||||
'updated-channels',
|
||||
'added-channels',
|
||||
'deleted-messages',
|
||||
'updated-messages',
|
||||
'added-messages',
|
||||
];
|
||||
for (let event of serverEvents) {
|
||||
server.on(event, (...args) => {
|
||||
this.emit(event, server, ...args);
|
||||
// Forward guild events through this event emitter
|
||||
for (let eventName of guild.eventNames()) {
|
||||
guild.on(eventName as any, (...args: any) => {
|
||||
this.emit(eventName as any, guild, ...args);
|
||||
});
|
||||
}
|
||||
return server;
|
||||
|
||||
return guild;
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
this.servers = [];
|
||||
this.guilds = [];
|
||||
|
||||
let serverConfigs = await this.dbCache.getServerConfigs();
|
||||
|
||||
// TODO: HTML prompt if no server configs
|
||||
if (serverConfigs.length == 0) {
|
||||
LOG.warn('no server configs found in client-side db');
|
||||
// TODO: connect concurrently
|
||||
for (let guildMeta of await this.personalDB.fetchGuilds()) {
|
||||
for (let guildSocket of await this.personalDB.fetchGuildSockets(guildMeta.id)) {
|
||||
await this._connectFromConfig(guildMeta, guildSocket);
|
||||
}
|
||||
}
|
||||
|
||||
for (let serverConfig of serverConfigs) {
|
||||
await this._connectFromConfig(serverConfig);
|
||||
if (this.guilds.length === 0) {
|
||||
LOG.warn('no guilds found in client-side db');
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,7 +108,7 @@ export default class Controller extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
async addNewServer(serverConfig: IAddServerData, displayName: string, avatarBuff: Buffer): Promise<ClientController> {
|
||||
async addNewGuild(serverConfig: IAddServerData, displayName: string, avatarBuff: Buffer): Promise<CombinedGuild> {
|
||||
const { name, url, cert, token } = serverConfig;
|
||||
LOG.debug('Adding new server', { name, url, cert, token, displayName, avatarBuff });
|
||||
|
||||
@ -128,24 +142,25 @@ export default class Controller extends EventEmitter {
|
||||
return await new Promise((resolve, reject) => {
|
||||
let clientPublicKeyDerBuff = publicKey.export({ type: 'spki', format: 'der' });
|
||||
Controller._socketEmitTimeout(socket, 5000, 'register-with-token',
|
||||
token, clientPublicKeyDerBuff, displayName, avatarBuff, async (errStr, member) => {
|
||||
token, clientPublicKeyDerBuff, displayName, avatarBuff, async (errStr: string, dataMember: any, dataMetadata: any) => {
|
||||
if (errStr) {
|
||||
reject(new Error(errStr));
|
||||
} else {
|
||||
try {
|
||||
let serverConfig: ServerConfig | null = null;
|
||||
await this.dbCache.queueTransaction(async () => {
|
||||
let publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' });
|
||||
let privateKeyPem = privateKey.export({ type: 'pkcs8', format: 'pem' });
|
||||
let identityId = await this.dbCache.addIdentity(publicKeyPem, privateKeyPem);
|
||||
let guildId = await this.dbCache.addServer(url, cert, name);
|
||||
await this.dbCache.addServerIdentity(guildId, identityId);
|
||||
serverConfig = await this.dbCache.getServerConfig(guildId, identityId);
|
||||
const member = Member.fromDBData(dataMember);
|
||||
const meta = GuildMetadata.fromDBData(dataMetadata);
|
||||
let guildMeta: GuildMetadataWithIds | null = null;
|
||||
let socketConfig: SocketConfig | null = null;
|
||||
await this.personalDB.queueTransaction(async () => {
|
||||
let guildId = await this.personalDB.addGuild(meta.name, meta.iconResourceId, member.id);
|
||||
let guildSocketId = await this.personalDB.addGuildSocket(guildId, url, cert, publicKey, privateKey);
|
||||
guildMeta = await this.personalDB.fetchGuild(guildId);
|
||||
socketConfig = await this.personalDB.fetchGuildSocket(guildId, guildSocketId);
|
||||
});
|
||||
if (serverConfig == null) {
|
||||
throw new Error('unable to get server config');
|
||||
if (!guildMeta || !socketConfig) {
|
||||
throw new Error('unable to properly add guild');
|
||||
}
|
||||
let server = await this._connectFromConfig(serverConfig);
|
||||
let server = await this._connectFromConfig(guildMeta, socketConfig);
|
||||
resolve(server);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
@ -159,8 +174,11 @@ export default class Controller extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
async removeServer(server: ClientController): Promise<void> {
|
||||
await this.dbCache.removeServer(server.id);
|
||||
this.servers = this.servers.filter(s => s != server);
|
||||
async removeServer(guild: CombinedGuild): Promise<void> {
|
||||
await this.personalDB.queueTransaction(async () => {
|
||||
await this.personalDB.removeGuildSockets(guild.id);
|
||||
await this.personalDB.removeGuild(guild.id);
|
||||
});
|
||||
this.guilds = this.guilds.filter(g => g.id != guild.id);
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +214,8 @@ export class Message implements WithEquals<Message> {
|
||||
|
||||
export class SocketConfig {
|
||||
private constructor(
|
||||
public readonly id: number,
|
||||
public readonly id: number | null,
|
||||
public readonly guildId: number,
|
||||
public readonly url: string,
|
||||
public readonly cert: string,
|
||||
public readonly publicKey: crypto.KeyObject,
|
||||
@ -225,6 +226,7 @@ export class SocketConfig {
|
||||
static fromDBData(data: any): SocketConfig {
|
||||
return new SocketConfig(
|
||||
data.id,
|
||||
data.guild_id,
|
||||
data.url,
|
||||
data.cert,
|
||||
crypto.createPublicKey(data.public_key),
|
||||
|
@ -7,7 +7,7 @@ import UI from '../ui';
|
||||
import Actions from '../actions';
|
||||
import Q from '../q-module';
|
||||
|
||||
export default function createChannel(document: Document, q: Q, ui: UI, server: ClientController, channel: Channel) {
|
||||
export default function createChannel(document: Document, q: Q, ui: UI, guild: CombinedGuild, channel: Channel) {
|
||||
let element = q.create({ class: 'channel text', 'meta-id': channel.id, 'meta-server-id': server.id, content: [
|
||||
// Scraped directly from discord (#)
|
||||
{ class: 'icon', content: BaseElements.TEXT_CHANNEL_ICON },
|
||||
|
@ -6,7 +6,7 @@ import createPersonalizeOverlay from './overlay-personalize.js';
|
||||
import Q from '../q-module.js';
|
||||
import UI from '../ui.js';
|
||||
|
||||
export default function createConnectionContextMenu(document: Document, q: Q, ui: UI, server: ClientController) {
|
||||
export default function createConnectionContextMenu(document: Document, q: Q, ui: UI, guild: CombinedGuild) {
|
||||
let statuses = [ 'online', 'away', 'busy', 'invisible' ];
|
||||
let content: any[] = [
|
||||
{ class: 'item personalize', content: [
|
||||
|
@ -12,7 +12,7 @@ import Q from '../q-module';
|
||||
export default function createImageContextMenu(
|
||||
document: Document,
|
||||
q: Q,
|
||||
server: ClientController,
|
||||
guild: CombinedGuild,
|
||||
resourceName: string,
|
||||
buffer: Buffer,
|
||||
mime: string,
|
||||
|
@ -16,7 +16,7 @@ import createCreateInviteTokenOverlay from './overlay-create-invite-token';
|
||||
import createCreateChannelOverlay from './overlay-create-channel';
|
||||
import createTokenLogOverlay from './overlay-token-log';
|
||||
|
||||
export default function createServerTitleContextMenu(document: Document, q: Q, ui: UI, server: ClientController): HTMLElement {
|
||||
export default function createServerTitleContextMenu(document: Document, q: Q, ui: UI, guild: CombinedGuild): HTMLElement {
|
||||
if (ui.activeConnection === null) {
|
||||
LOG.warn('no active connection when creating server title context menu');
|
||||
return q.create({}) as HTMLElement;
|
||||
|
@ -10,7 +10,7 @@ import Q from '../q-module';
|
||||
import UI from '../ui';
|
||||
import Controller from '../controller';
|
||||
|
||||
export default function createServerContextMenu(document: Document, q: Q, ui: UI, controller: Controller, server: ClientController) {
|
||||
export default function createServerContextMenu(document: Document, q: Q, ui: UI, controller: Controller, guild: CombinedGuild) {
|
||||
let element = BaseElements.createContextMenu(document, {
|
||||
class: 'server-context', content: [
|
||||
{ class: 'item red leave-server', content: 'Leave Server' }
|
||||
|
@ -6,10 +6,10 @@ import createConnectionContextMenu from './context-menu-conn';
|
||||
|
||||
export default function bindConnectionEvents(document: Document, q: Q, ui: UI): void {
|
||||
q.$('#connection').addEventListener('click', () => {
|
||||
if (ui.activeServer === null) return;
|
||||
if (!ui.activeServer.isVerified) return;
|
||||
if (ui.activeGuild === null) return;
|
||||
if (!ui.activeGuild.isVerified) return;
|
||||
|
||||
let contextMenu = createConnectionContextMenu(document, q, ui, ui.activeServer);
|
||||
let contextMenu = createConnectionContextMenu(document, q, ui, ui.activeGuild);
|
||||
document.body.appendChild(contextMenu);
|
||||
ElementsUtil.alignContextElement(contextMenu, q.$('#connection'), { bottom: 'top', centerX: 'centerX' });
|
||||
});
|
||||
|
@ -11,18 +11,18 @@ export default function bindInfiniteScrollEvents(q: Q, ui: UI): void {
|
||||
let scrollHeight = q.$('#channel-feed-content-wrapper').scrollHeight;
|
||||
let clientHeight = q.$('#channel-feed-content-wrapper').clientHeight;
|
||||
|
||||
if (ui.activeServer === null) return;
|
||||
if (ui.activeGuild === null) return;
|
||||
if (ui.activeChannel === null) return;
|
||||
|
||||
if (!loadingBefore && !ui.messagesAtTop && scrollTop < 600) { // Approaching the unloaded top of the page
|
||||
// Fetch more messages to add above
|
||||
loadingBefore = true;
|
||||
await Actions.fetchAndUpdateMessagesBefore(q, ui, ui.activeServer, ui.activeChannel);
|
||||
await Actions.fetchAndUpdateMessagesBefore(q, ui, ui.activeGuild, ui.activeChannel);
|
||||
loadingBefore = false;
|
||||
} else if (!loadingAfter && !ui.messagesAtBottom && scrollHeight - clientHeight - scrollTop < 600) { // Approaching the unloaded bottom of the page
|
||||
// Fetch more messages to add below
|
||||
loadingAfter = true;
|
||||
await Actions.fetchAndUpdateMessagesAfter(q, ui, ui.activeServer, ui.activeChannel);
|
||||
await Actions.fetchAndUpdateMessagesAfter(q, ui, ui.activeGuild, ui.activeChannel);
|
||||
loadingAfter = false;
|
||||
}
|
||||
}
|
||||
|
@ -6,14 +6,14 @@ import ElementsUtil from './require/elements-util';
|
||||
export default function bindAddServerTitleEvents(document: Document, q: Q, ui: UI) {
|
||||
q.$('#server-name-container').addEventListener('click', () => {
|
||||
if (ui.activeConnection === null) return;
|
||||
if (ui.activeServer === null) return;
|
||||
if (!ui.activeServer.isVerified) return;
|
||||
if (ui.activeGuild === null) return;
|
||||
if (!ui.activeGuild.isVerified) return;
|
||||
if (
|
||||
!ui.activeConnection.privileges.includes('modify_profile') &&
|
||||
!ui.activeConnection.privileges.includes('modify_members')
|
||||
) return;
|
||||
|
||||
let contextMenu = createServerTitleContextMenu(document, q, ui, ui.activeServer);
|
||||
let contextMenu = createServerTitleContextMenu(document, q, ui, ui.activeGuild);
|
||||
document.body.appendChild(contextMenu);
|
||||
ElementsUtil.alignContextElement(contextMenu, q.$('#server-name-container'), { top: 'bottom', centerX: 'centerX' });
|
||||
});
|
||||
|
@ -19,7 +19,7 @@ export default function bindTextInputEvents(document: Document, q: Q, ui: UI): v
|
||||
let sendingMessage = false;
|
||||
async function sendCurrentTextMessage() {
|
||||
if (sendingMessage) return;
|
||||
if (ui.activeServer === null) return;
|
||||
if (ui.activeGuild === null) return;
|
||||
if (ui.activeChannel === null) return;
|
||||
|
||||
let text = q.$('#text-input').innerText.trim(); // trimming is not done server-side, just a client-side 'feature'
|
||||
@ -27,7 +27,7 @@ export default function bindTextInputEvents(document: Document, q: Q, ui: UI): v
|
||||
|
||||
sendingMessage = true;
|
||||
|
||||
let server = ui.activeServer as ClientController;
|
||||
let server = ui.activeGuild as ClientController;
|
||||
let channel = ui.activeChannel as Channel;
|
||||
|
||||
if (!server.isVerified) {
|
||||
@ -92,7 +92,7 @@ export default function bindTextInputEvents(document: Document, q: Q, ui: UI): v
|
||||
// Open resource select dialog when resource-input-button is clicked
|
||||
let selectingResources = false;
|
||||
q.$('#resource-input-button').addEventListener('click', async () => {
|
||||
if (ui.activeServer === null) return;
|
||||
if (ui.activeGuild === null) return;
|
||||
if (ui.activeChannel === null) return;
|
||||
|
||||
if (selectingResources) {
|
||||
@ -106,7 +106,7 @@ export default function bindTextInputEvents(document: Document, q: Q, ui: UI): v
|
||||
});
|
||||
// TODO: multiple files do consecutive overlays?
|
||||
if (!result.canceled) {
|
||||
let element = createUploadOverlayFromPath(document, ui.activeServer, ui.activeChannel, result.filePaths[0]);
|
||||
let element = createUploadOverlayFromPath(document, ui.activeGuild, ui.activeChannel, result.filePaths[0]);
|
||||
document.body.appendChild(element);
|
||||
q.$$$(element, '.text-input').focus();
|
||||
}
|
||||
@ -115,7 +115,7 @@ export default function bindTextInputEvents(document: Document, q: Q, ui: UI): v
|
||||
|
||||
// Open upload resource dialog when an image is pasted
|
||||
window.addEventListener('paste', (e) => {
|
||||
if (ui.activeServer === null) return;
|
||||
if (ui.activeGuild === null) return;
|
||||
if (ui.activeChannel === null) return;
|
||||
|
||||
let fileTransferItem: DataTransferItem | null = null;
|
||||
@ -127,7 +127,7 @@ export default function bindTextInputEvents(document: Document, q: Q, ui: UI): v
|
||||
}
|
||||
}
|
||||
if (fileTransferItem) {
|
||||
let element = createUploadOverlayFromDataTransferItem(document, ui.activeServer, ui.activeChannel, fileTransferItem);
|
||||
let element = createUploadOverlayFromDataTransferItem(document, ui.activeGuild, ui.activeChannel, fileTransferItem);
|
||||
document.body.appendChild(element);
|
||||
q.$$$(element, '.text-input').focus();
|
||||
}
|
||||
@ -135,11 +135,11 @@ export default function bindTextInputEvents(document: Document, q: Q, ui: UI): v
|
||||
|
||||
// TODO: drag+drop new server files?
|
||||
document.addEventListener('dragenter', () => {
|
||||
if (ui.activeServer === null) return;
|
||||
if (ui.activeGuild === null) return;
|
||||
if (ui.activeChannel === null) return;
|
||||
|
||||
if (q.$('.overlay .drop-target')) return;
|
||||
let element = createUploadDropTarget(document, q, ui.activeServer, ui.activeChannel);
|
||||
let element = createUploadDropTarget(document, q, ui.activeGuild, ui.activeChannel);
|
||||
if (!element) return;
|
||||
document.body.appendChild(element);
|
||||
});
|
||||
|
@ -6,41 +6,41 @@ const LOG = Logger.create(__filename, electronConsole);
|
||||
import BaseElements from './require/base-elements';
|
||||
import ElementsUtil from './require/elements-util';
|
||||
|
||||
import ClientController from '../client-controller';
|
||||
import { CacheServerData, ServerMetaData } from '../data-types';
|
||||
import { GuildMetadata } from '../data-types';
|
||||
import Q from '../q-module';
|
||||
import UI from '../ui';
|
||||
import Actions from '../actions';
|
||||
import createServerContextMenu from './context-menu-srv';
|
||||
import createGuildContextMenu from './context-menu-srv';
|
||||
import Controller from '../controller';
|
||||
import CombinedGuild from '../guild-combined';
|
||||
|
||||
export default function createServerListServer(document: Document, q: Q, ui: UI, controller: Controller, server: ClientController) {
|
||||
let element = q.create({ class: 'server', 'meta-id': server.id, 'meta-name': server.id, content: [
|
||||
export default function createGuildListGuild(document: Document, q: Q, ui: UI, controller: Controller, guild: CombinedGuild) {
|
||||
let element = q.create({ class: 'guild', 'meta-id': guild.id, 'meta-name': guild.id, content: [
|
||||
{ class: 'pill' },
|
||||
{ tag: 'img', src: './img/loading.svg', alt: 'server' }, // src is set later by script.js
|
||||
{ tag: 'img', src: './img/loading.svg', alt: 'guild' }, // src is set later by script.js
|
||||
] }) as HTMLElement;
|
||||
|
||||
// Hover over for name + connection info
|
||||
(async () => {
|
||||
let serverData: ServerMetaData | CacheServerData;
|
||||
let guildData: GuildMetadata;
|
||||
try {
|
||||
serverData = await server.grabMetadata();
|
||||
if (!serverData.iconResourceId) throw new Error('server icon not identified yet');
|
||||
let serverIcon = await server.fetchResource(serverData.iconResourceId);
|
||||
let serverIconSrc = await ElementsUtil.getImageBufferSrc(serverIcon);
|
||||
(q.$$$(element, 'img') as HTMLImageElement).src = serverIconSrc;
|
||||
guildData = await guild.grabMetadata();
|
||||
if (!guildData.iconResourceId) throw new Error('guild icon not identified yet');
|
||||
let guildIcon = await guild.fetchResource(guildData.iconResourceId);
|
||||
let guildIconSrc = await ElementsUtil.getImageBufferSrc(guildIcon);
|
||||
(q.$$$(element, 'img') as HTMLImageElement).src = guildIconSrc;
|
||||
} catch (e) {
|
||||
LOG.error('Error fetching server icon', e);
|
||||
LOG.error('Error fetching guild icon', e);
|
||||
(q.$$$(element, 'img') as HTMLImageElement).src = './img/error.png';
|
||||
return;
|
||||
}
|
||||
|
||||
element.setAttribute('meta-name', serverData.name);
|
||||
element.setAttribute('meta-name', guildData.name);
|
||||
|
||||
let contextElement = q.create({ class: 'context', content: {
|
||||
class: 'info', content: [
|
||||
BaseElements.TAB_LEFT,
|
||||
{ class: 'content server' } // populated later
|
||||
{ class: 'content guild' } // populated later
|
||||
]
|
||||
} }) as HTMLElement;
|
||||
|
||||
@ -52,7 +52,7 @@ export default function createServerListServer(document: Document, q: Q, ui: UI,
|
||||
document.body.appendChild(contextElement);
|
||||
ElementsUtil.alignContextElement(contextElement, element, { left: 'right', centerY: 'centerY' });
|
||||
(async () => {
|
||||
let connection = await server.fetchConnectionInfo();
|
||||
let connection = await guild.fetchConnectionInfo();
|
||||
let connectionElement = q.create({ class: 'connection ' + connection.status, content: [
|
||||
{ class: 'status-circle' },
|
||||
{ class: 'display-name', content: connection.displayName }
|
||||
@ -72,38 +72,38 @@ export default function createServerListServer(document: Document, q: Q, ui: UI,
|
||||
element.addEventListener('click', async () => {
|
||||
if (element.classList.contains('active')) return;
|
||||
|
||||
ui.setActiveServer(server);
|
||||
ui.setActiveGuild(guild);
|
||||
|
||||
// Connection information
|
||||
(async () => {
|
||||
await Actions.fetchAndUpdateConnection(ui, server);
|
||||
await Actions.fetchAndUpdateConnection(ui, guild);
|
||||
})();
|
||||
|
||||
// Server Channel Name
|
||||
// Guild Channel Name
|
||||
(async () => {
|
||||
// Explicitly not using a withPotentialError to make this simpler
|
||||
try {
|
||||
let serverData = await server.grabMetadata();
|
||||
ui.updateServerName(server, serverData.name);
|
||||
let guildData = await guild.grabMetadata();
|
||||
ui.updateGuildName(guild, guildData.name);
|
||||
} catch (e) {
|
||||
LOG.error('Error fetching server name', e);
|
||||
ui.updateServerName(server, 'ERROR');
|
||||
LOG.error('Error fetching guild name', e);
|
||||
ui.updateGuildName(guild, 'ERROR');
|
||||
}
|
||||
})();
|
||||
|
||||
// Server Channel List
|
||||
// Guild Channel List
|
||||
(async () => {
|
||||
await Actions.fetchAndUpdateChannels(q, ui, server);
|
||||
await Actions.fetchAndUpdateChannels(q, ui, guild);
|
||||
})();
|
||||
|
||||
// Server Members
|
||||
// Guild Members
|
||||
(async () => {
|
||||
await Actions.fetchAndUpdateMembers(q, ui, server);
|
||||
await Actions.fetchAndUpdateMembers(q, ui, guild);
|
||||
})();
|
||||
});
|
||||
|
||||
element.addEventListener('contextmenu', (e) => {
|
||||
let contextMenu = createServerContextMenu(document, q, ui, controller, server);
|
||||
let contextMenu = createGuildContextMenu(document, q, ui, controller, guild);
|
||||
document.body.appendChild(contextMenu);
|
||||
let relativeTo = { x: e.pageX, y: e.pageY };
|
||||
ElementsUtil.alignContextElement(contextMenu, relativeTo, { top: 'centerY', left: 'centerX' });
|
@ -4,7 +4,7 @@ import Q from "../q-module";
|
||||
|
||||
import ElementsUtil from "./require/elements-util";
|
||||
|
||||
export default function createMember(q: Q, server: ClientController, member: Member): HTMLElement {
|
||||
export default function createMember(q: Q, guild: CombinedGuild, member: Member): HTMLElement {
|
||||
let nameStyle = member.roleColor ? 'color: ' + member.roleColor : '';
|
||||
let element = q.create({ class: 'member ' + member.status, 'meta-id': member.id, content: [
|
||||
{ class: 'icon', content: [
|
||||
|
@ -8,7 +8,7 @@ import createResourceMessageContinued from './msg-res-cont';
|
||||
import createTextMessage from './msg-txt';
|
||||
import createTextMessageContinued from './msg-txt-cont';
|
||||
|
||||
export default function createMessage(document: Document, q: Q, server: ClientController, message: Message, lastMessage: Message | null): HTMLElement {
|
||||
export default function createMessage(document: Document, q: Q, guild: CombinedGuild, message: Message, lastMessage: Message | null): HTMLElement {
|
||||
let element: HTMLElement;
|
||||
if (message.hasResource()) {
|
||||
if (message.isImageResource()) {
|
||||
|
@ -14,7 +14,7 @@ import Q from '../q-module';
|
||||
import createImageOverlay from './overlay-image';
|
||||
import createImageContextMenu from './context-menu-img';
|
||||
|
||||
export default function createImageResourceMessageContinued(document: Document, q: Q, server: ClientController, message: Message): HTMLElement {
|
||||
export default function createImageResourceMessageContinued(document: Document, q: Q, guild: CombinedGuild, message: Message): HTMLElement {
|
||||
if (!message.resourceId || !message.resourcePreviewId || !message.resourceName) {
|
||||
throw new ShouldNeverHappenError('Message is not a resource message');
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import Q from '../q-module';
|
||||
import createImageOverlay from './overlay-image';
|
||||
import createImageContextMenu from './context-menu-img';
|
||||
|
||||
export default function createImageResourceMessage(document: Document, q: Q, server: ClientController, message: Message) {
|
||||
export default function createImageResourceMessage(document: Document, q: Q, guild: CombinedGuild, message: Message) {
|
||||
if (!message.resourceId || !message.resourcePreviewId || !message.resourceName) {
|
||||
throw new ShouldNeverHappenError('Message is not a resource message');
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ class ShouldNeverHappenError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export default function createResourceMessageContinued(q: Q, server: ClientController, message: Message): HTMLElement {
|
||||
export default function createResourceMessageContinued(q: Q, guild: CombinedGuild, message: Message): HTMLElement {
|
||||
if (!message.resourceId || !message.resourceName) {
|
||||
throw new ShouldNeverHappenError('Message is not a resource message');
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ class ShouldNeverHappenError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export default function createResourceMessage(q: Q, server: ClientController, message: Message): HTMLElement {
|
||||
export default function createResourceMessage(q: Q, guild: CombinedGuild, message: Message): HTMLElement {
|
||||
if (!message.resourceId || !message.resourceName) {
|
||||
throw new ShouldNeverHappenError('Message is not a resource message');
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import Q from '../q-module.js';
|
||||
|
||||
import ElementsUtil from './require/elements-util.js';
|
||||
|
||||
export default function createTextMessageContinued(q: Q, server: ClientController, message: Message): HTMLElement {
|
||||
export default function createTextMessageContinued(q: Q, guild: CombinedGuild, message: Message): HTMLElement {
|
||||
return q.create({ class: 'message continued', 'meta-id': message.id, 'meta-member-id': message.member.id, 'meta-server-id': server.id, content: [
|
||||
{ class: 'timestamp', content: moment(message.sent).format('HH:mm') },
|
||||
{ class: 'right', content: [
|
||||
|
@ -6,7 +6,7 @@ import { Message, Member, IDummyTextMessage } from '../data-types';
|
||||
import ClientController from '../client-controller';
|
||||
import Q from '../q-module';
|
||||
|
||||
export default function createTextMessage(q: Q, server: ClientController, message: Message | IDummyTextMessage): HTMLElement {
|
||||
export default function createTextMessage(q: Q, guild: CombinedGuild, message: Message | IDummyTextMessage): HTMLElement {
|
||||
let memberInfo: {
|
||||
roleColor: string | null,
|
||||
displayName: string,
|
||||
|
@ -177,7 +177,7 @@ export default function createAddServerOverlay(document: Document, q: Q, ui: UI,
|
||||
|
||||
q.$$$(element, '.display-name-input').removeAttribute('contenteditable');
|
||||
|
||||
let newServer: ClientController | null = null;
|
||||
let newguild: CombinedGuild | null = null;
|
||||
if (addServerData == null) {
|
||||
q.$$$(element, '.error').innerText = 'Very bad server file';
|
||||
q.$$$(element, '.submit').innerText = 'Try Again';
|
||||
@ -201,7 +201,7 @@ export default function createAddServerOverlay(document: Document, q: Q, ui: UI,
|
||||
} else { // NOTE: Avatar size is checked above
|
||||
q.$$$(element, '.submit').innerText = 'Registering...';
|
||||
try {
|
||||
newServer = await controller.addNewServer(addServerData, displayName, avatarBuff);
|
||||
newServer = await controller.addNewGuild(addServerData, displayName, avatarBuff);
|
||||
} catch (e) {
|
||||
LOG.warn('error adding new server: ' + e.message, e); // explicitly not printing stack trace here
|
||||
q.$$$(element, '.error').innerText = e.message;
|
||||
|
@ -10,7 +10,7 @@ import ElementsUtil from "./require/elements-util";
|
||||
import BaseElements from "./require/base-elements";
|
||||
import Q from '../q-module';
|
||||
|
||||
export default function createCreateChannelOverlay(document: Document, q: Q, server: ClientController): HTMLElement {
|
||||
export default function createCreateChannelOverlay(document: Document, q: Q, guild: CombinedGuild): HTMLElement {
|
||||
|
||||
// See also overlay-modify-channel
|
||||
|
||||
|
@ -2,7 +2,7 @@ import ClientController from "../client-controller";
|
||||
|
||||
import BaseElements from "./require/base-elements";
|
||||
|
||||
export default function createCreateInviteTokenOverlay(document: Document, server: ClientController): HTMLElement {
|
||||
export default function createCreateInviteTokenOverlay(document: Document, guild: CombinedGuild): HTMLElement {
|
||||
let element = BaseElements.createOverlay(document, { class: 'content submit-dialog', content: [
|
||||
{ class: 'role-select category-select', content: [
|
||||
{ class: 'label', content: 'Select Starting Roles' },
|
||||
|
@ -12,7 +12,7 @@ import ClientController from '../client-controller';
|
||||
import Q from '../q-module';
|
||||
import createImageContextMenu from './context-menu-img';
|
||||
|
||||
export default function createImageOverlay(document: Document, q: Q, server: ClientController, resourceId: string, resourceName: string): HTMLElement {
|
||||
export default function createImageOverlay(document: Document, q: Q, guild: CombinedGuild, resourceId: string, resourceName: string): HTMLElement {
|
||||
let element = BaseElements.createOverlay(document, { class: 'content popup-image', content: [
|
||||
{ tag: 'img', src: './img/loading.svg', alt: resourceName, title: resourceName },
|
||||
{ class: 'download', content: [
|
||||
|
@ -11,7 +11,7 @@ import BaseElements from './require/base-elements.js';
|
||||
import ElementsUtil from './require/elements-util.js';
|
||||
import Q from '../q-module';
|
||||
|
||||
export default function createModifyChannelOverlay(document: Document, q: Q, server: ClientController, channel: Channel): HTMLElement {
|
||||
export default function createModifyChannelOverlay(document: Document, q: Q, guild: CombinedGuild, channel: Channel): HTMLElement {
|
||||
// See also overlay-create-channel
|
||||
|
||||
let element = BaseElements.createOverlay(document, { class: 'content submit-dialog modify-channel', content: [
|
||||
|
@ -12,7 +12,7 @@ import ClientController from '../client-controller';
|
||||
import Q from '../q-module';
|
||||
import createTextMessage from './msg-txt';
|
||||
|
||||
export default function createPersonalizeOverlay(document: Document, q: Q, server: ClientController, connection: any): HTMLElement {
|
||||
export default function createPersonalizeOverlay(document: Document, q: Q, guild: CombinedGuild, connection: any): HTMLElement {
|
||||
let element = BaseElements.createOverlay(document, {
|
||||
class: 'content submit-dialog personalize', content: [
|
||||
createTextMessage(q, server, { id: 'test-message', member: connection, sent: new Date(), text: 'Example Message' }),
|
||||
|
@ -12,7 +12,7 @@ import ClientController from '../client-controller';
|
||||
import { CacheServerData, ServerMetaData } from '../data-types';
|
||||
import Q from '../q-module';
|
||||
|
||||
export default function createServerSettingsOverlay(document: Document, q: Q, server: ClientController, serverMeta: ServerMetaData | CacheServerData): HTMLElement {
|
||||
export default function createServerSettingsOverlay(document: Document, q: Q, guild: CombinedGuild, serverMeta: ServerMetaData | CacheServerData): HTMLElement {
|
||||
let element = BaseElements.createOverlay(document, {
|
||||
class: 'content submit-dialog server-settings', content: [
|
||||
{ class: 'server preview', content: [
|
||||
|
@ -13,7 +13,7 @@ import ClientController from '../client-controller';
|
||||
import { Member } from '../data-types';
|
||||
import Q from '../q-module';
|
||||
|
||||
export default function createTokenLogOverlay(document: Document, q: Q, server: ClientController): HTMLElement {
|
||||
export default function createTokenLogOverlay(document: Document, q: Q, guild: CombinedGuild): HTMLElement {
|
||||
let element = BaseElements.createOverlay(document, {
|
||||
class: 'content token-log', content: [
|
||||
{ class: 'tokens', content: { tag: 'img', src: './img/loading.svg', alt: 'loading...' } },
|
||||
|
@ -3,7 +3,7 @@ import BaseElements from './require/base-elements.js';
|
||||
import { Channel, ShouldNeverHappenError } from '../data-types';
|
||||
import ClientController from '../client-controller.js';
|
||||
|
||||
export default function createUploadOverlayFromDataTransferItem(document: Document, server: ClientController, channel: Channel, dataTransferItem: DataTransferItem): HTMLElement {
|
||||
export default function createUploadOverlayFromDataTransferItem(document: Document, guild: CombinedGuild, channel: Channel, dataTransferItem: DataTransferItem): HTMLElement {
|
||||
let file = dataTransferItem.getAsFile();
|
||||
if (file === null) throw new ShouldNeverHappenError('no file in the data transfer item');
|
||||
let element = BaseElements.createUploadOverlay(document, {
|
||||
|
@ -9,7 +9,7 @@ import ClientController from '../client-controller';
|
||||
import Q from '../q-module';
|
||||
import createUploadOverlayFromDataTransferItem from './overlay-upload-datatransfer';
|
||||
|
||||
export default function createUploadDropTarget(document: Document, q: Q, server: ClientController, channel: Channel): HTMLElement {
|
||||
export default function createUploadDropTarget(document: Document, q: Q, guild: CombinedGuild, channel: Channel): HTMLElement {
|
||||
let element = BaseElements.createOverlay(document, { class: 'content drop-target', content: [
|
||||
// TODO: icon?
|
||||
{ class: 'message', content: 'Upload to #' + channel.name }
|
||||
|
@ -6,7 +6,7 @@ import BaseElements from './require/base-elements';
|
||||
import { Channel } from '../data-types';
|
||||
import ClientController from '../client-controller';
|
||||
|
||||
export default function createUploadOverlayFromPath(document: Document, server: ClientController, channel: Channel, resourcePath: string): HTMLElement {
|
||||
export default function createUploadOverlayFromPath(document: Document, guild: CombinedGuild, channel: Channel, resourcePath: string): HTMLElement {
|
||||
let resourceName = path.basename(resourcePath);
|
||||
let element = BaseElements.createUploadOverlay(document, {
|
||||
server: server, channel: channel, resourceName: resourceName,
|
||||
|
@ -18,7 +18,7 @@ interface HTMLElementWithRemoveSelf extends HTMLElement {
|
||||
}
|
||||
|
||||
interface CreateUploadOverlayProps {
|
||||
server: ClientController;
|
||||
guild: CombinedGuild;
|
||||
channel: Channel;
|
||||
resourceName: string;
|
||||
resourceBuffFunc: (() => Promise<Buffer>);
|
||||
|
@ -99,7 +99,7 @@ export default class ElementsUtil {
|
||||
}
|
||||
}
|
||||
|
||||
static async getImageBufferFromResourceFailSoftly(server: ClientController, resourceId: string | null): Promise<string> {
|
||||
static async getImageBufferFromResourceFailSoftly(guild: CombinedGuild, resourceId: string | null): Promise<string> {
|
||||
if (!resourceId) {
|
||||
LOG.warn('no server resource specified, showing error instead', new Error());
|
||||
return './img/error.png';
|
||||
|
@ -4,7 +4,7 @@ import { AutoVerifier, AutoVerifierChangesType } from './auto-verifier';
|
||||
import { AutoVerifierWithArg, PartialMessageListQuery, IDQuery } from './auto-verifier-with-args';
|
||||
import { EventEmitter } from 'tsee';
|
||||
|
||||
export default class GuildPairVerifierGuild extends EventEmitter<Conflictable> implements AsyncFetchable {
|
||||
export default class PairVerifierFetchable extends EventEmitter<Conflictable> implements AsyncFetchable {
|
||||
|
||||
private readonly fetchMetadataVerifier: AutoVerifier<GuildMetadata>;
|
||||
private readonly fetchMembersVerifier: AutoVerifier<Member[]>;
|
||||
|
@ -7,7 +7,7 @@ import * as socketio from 'socket.io-client';
|
||||
import PersonalDBGuild from './guild-personal-db';
|
||||
import RAMGuild from './guild-ram';
|
||||
import SocketGuild from './guild-socket';
|
||||
import { Changes, Channel, GuildMetadata, Member, Message, Resource, ServerMetaData, SocketConfig, Token } from './data-types';
|
||||
import { Changes, Channel, GuildMetadata, GuildMetadataWithIds, Member, Message, Resource, ServerMetaData, SocketConfig, Token } from './data-types';
|
||||
|
||||
import MessageRAMCache from "./message-ram-cache";
|
||||
import PersonalDB from "./personal-db";
|
||||
@ -27,7 +27,7 @@ export default class CombinedGuild extends EventEmitter<Connectable & Conflictab
|
||||
private readonly fetchable: AsyncGuaranteedFetchable;
|
||||
|
||||
constructor(
|
||||
private readonly id: number,
|
||||
public readonly id: number,
|
||||
private readonly memberId: string,
|
||||
socket: socketio.Socket,
|
||||
socketVerifier: SocketVerifier,
|
||||
@ -149,7 +149,7 @@ export default class CombinedGuild extends EventEmitter<Connectable & Conflictab
|
||||
}
|
||||
|
||||
static async create(
|
||||
personalGuildId: number,
|
||||
guildMetadata: GuildMetadataWithIds,
|
||||
socketConfig: SocketConfig,
|
||||
messageRAMCache: MessageRAMCache,
|
||||
resourceRAMCache: ResourceRAMCache,
|
||||
@ -161,8 +161,11 @@ export default class CombinedGuild extends EventEmitter<Connectable & Conflictab
|
||||
});
|
||||
let socketVerifier = new SocketVerifier(socket, socketConfig.publicKey, socketConfig.privateKey);
|
||||
let memberId = await socketVerifier.verify();
|
||||
if (guildMetadata.memberId && memberId !== guildMetadata.memberId) {
|
||||
throw new Error('Verified member differs from original member');
|
||||
}
|
||||
return new CombinedGuild(
|
||||
personalGuildId,
|
||||
guildMetadata.id,
|
||||
memberId,
|
||||
socket,
|
||||
socketVerifier,
|
||||
@ -172,25 +175,35 @@ export default class CombinedGuild extends EventEmitter<Connectable & Conflictab
|
||||
);
|
||||
}
|
||||
|
||||
private unverify() {
|
||||
private unverify(): void {
|
||||
for (let pairVerifier of this.pairVerifiers) {
|
||||
pairVerifier.unverify();
|
||||
}
|
||||
}
|
||||
|
||||
async ensureRAMMembers() {
|
||||
private async ensureRAMMembers(): Promise<void> {
|
||||
if (this.ramGuild.getMembers().size === 0) {
|
||||
await this.fetchMembers();
|
||||
}
|
||||
if (this.ramGuild.getMembers().size === 0) throw new Error('RAM Members was not updated through fetchMembers');
|
||||
}
|
||||
|
||||
async ensureRAMChannels() {
|
||||
private async ensureRAMChannels(): Promise<void> {
|
||||
if (this.ramGuild.getChannels().size === 0) {
|
||||
await this.fetchChannels();
|
||||
}
|
||||
if (this.ramGuild.getChannels().size === 0) throw new Error('RAM Channels was not updated through fetchChannels');
|
||||
}
|
||||
|
||||
public async grabRAMMembersMap(): Promise<Map<string, Member>> {
|
||||
await this.ensureRAMMembers();
|
||||
return this.ramGuild.getMembers();
|
||||
}
|
||||
|
||||
public async grabRAMChannelsMap(): Promise<Map<string, Channel>> {
|
||||
await this.ensureRAMChannels();
|
||||
return this.ramGuild.getChannels();
|
||||
}
|
||||
|
||||
// Fetched through the triple-cache system (RAM -> Disk -> Server)
|
||||
async fetchMetadata(): Promise<GuildMetadata> {
|
||||
|
@ -28,7 +28,7 @@ export default class MessageRAMCache {
|
||||
}
|
||||
|
||||
// Removes the oldest messages from the channel until the channel is under the max cached character limit
|
||||
private trimOldChannelMessagesIfNeeded(guildId: string, channelId: string) {
|
||||
private trimOldChannelMessagesIfNeeded(guildId: number, channelId: string) {
|
||||
let id = `g#${guildId}/c#${channelId}`;
|
||||
let value = this.data.get(id);
|
||||
if (!value) return;
|
||||
|
@ -1,3 +1,5 @@
|
||||
import * as crypto from 'crypto';
|
||||
|
||||
import ConcurrentQueue from "../../concurrent-queue/concurrent-queue";
|
||||
|
||||
import * as sqlite from 'sqlite';
|
||||
@ -150,10 +152,10 @@ export default class PersonalDB {
|
||||
|
||||
// Guilds
|
||||
|
||||
async addGuild(name: string | null, icon: Resource | null): Promise<number> {
|
||||
async addGuild(name: string, iconResourceId: string, memberId: string): Promise<number> {
|
||||
let result = await this.db.run(
|
||||
`INSERT INTO guilds (name, icon_resource_id) VALUES (:name, :icon_resource_id)`,
|
||||
{ ':name': name, ':icon_resource_id': icon?.id ?? null }
|
||||
`INSERT INTO guilds (name, icon_resource_id, member_id) VALUES (:name, :icon_resource_id, :member_id)`,
|
||||
{ ':name': name, ':icon_resource_id': iconResourceId, ':member_id': memberId }
|
||||
);
|
||||
if (result.changes !== 1) throw new Error('unable to add guild');
|
||||
if (result.lastID === undefined) throw new Error('unable to get guild last id');
|
||||
@ -200,24 +202,33 @@ export default class PersonalDB {
|
||||
|
||||
// Guild Sockets
|
||||
|
||||
async addGuildSocket(guildId: number, socketConfig: SocketConfig): Promise<void> {
|
||||
let publicKeyPem = socketConfig.publicKey.export({ type: 'spki', format: 'pem' });
|
||||
let privateKeyPem = socketConfig.privateKey.export({ type: 'pkcs8', format: 'pem' });
|
||||
let result = await this.db.all(
|
||||
async addGuildSocket(guildId: number, url: string, cert: string, publicKey: crypto.KeyObject, privateKey: crypto.KeyObject): Promise<number> {
|
||||
let publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' });
|
||||
let privateKeyPem = privateKey.export({ type: 'pkcs8', format: 'pem' });
|
||||
let result = await this.db.run(
|
||||
`INSERT INTO guild_sockets (guild_id, url, cert, public_key, private_key) VALUES (:guild_id, :url, :cert, :public_key, :private_key)`,
|
||||
{ ':guild_id': guildId, ':url': socketConfig.url, ':cert': socketConfig.cert, ':public_key': publicKeyPem, ':private_key': privateKeyPem }
|
||||
{ ':guild_id': guildId, ':url': url, ':cert': cert, ':public_key': publicKeyPem, ':private_key': privateKeyPem }
|
||||
);
|
||||
if (result.length !== 1) throw new Error('unable to add guild');
|
||||
if (result.changes !== 1) throw new Error('unable to add guild');
|
||||
return result.lastID as number;
|
||||
}
|
||||
|
||||
async removeGuildSocket(guildId: number, guildSocketId: number): Promise<void> {
|
||||
let result = await this.db.run(
|
||||
`DELETE FROM guilds WHERE id=:guild_socket_id AND guild_id=:guild_id`,
|
||||
`DELETE FROM guild_sockets WHERE id=:guild_socket_id AND guild_id=:guild_id`,
|
||||
{ ':guild_id': guildId, ':guild_socket_id': guildSocketId }
|
||||
);
|
||||
if (result?.changes !== 1) throw new Error('unable to remove guild');
|
||||
}
|
||||
|
||||
async removeGuildSockets(guildId: number): Promise<void> {
|
||||
let result = await this.db.run(
|
||||
`DELETE FROM guild_sockets WHERE guild_id=:guild_id`,
|
||||
{ ':guild_id': guildId }
|
||||
);
|
||||
if (result?.changes !== 1) throw new Error('unable to remove guild');
|
||||
}
|
||||
|
||||
async fetchGuildSockets(guildId: number): Promise<SocketConfig[]> {
|
||||
let result = await this.db.all(
|
||||
`SELECT * FROM guild_sockets WHERE guild_id=:guild_id`,
|
||||
@ -226,6 +237,15 @@ export default class PersonalDB {
|
||||
return result.map(dataGuildSocket => SocketConfig.fromDBData(dataGuildSocket));
|
||||
}
|
||||
|
||||
async fetchGuildSocket(guildId: number, guildSocketId: number): Promise<SocketConfig> {
|
||||
let result = await this.db.get(
|
||||
`SELECT * FROM guild_sockets WHERE id=:guild_socket_id AND guild_id=:guild_id`,
|
||||
{ ':guild_socket_id': guildSocketId, ':guild_id': guildId }
|
||||
);
|
||||
if (!result) throw new Error('unable to fetch specific guild socket');
|
||||
return SocketConfig.fromDBData(result);
|
||||
}
|
||||
|
||||
// Members
|
||||
|
||||
async addMembers(guildId: number, members: Member[]): Promise<void> {
|
||||
@ -285,6 +305,10 @@ export default class PersonalDB {
|
||||
return result.map(dataMember => Member.fromDBData(dataMember));
|
||||
}
|
||||
|
||||
async clearAllMembersStatus(guildId: number): Promise<void> {
|
||||
await this.db.run(`UPDATE members SET status='unknown' WHERE guild_id=:guild_id`, { ':guild_id': guildId });
|
||||
}
|
||||
|
||||
// Channels
|
||||
|
||||
async addChannels(guildId: number, channels: Channel[]) {
|
||||
|
@ -74,8 +74,8 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
|
||||
// Receive Current Channel Messages
|
||||
controller.on('new-message', async (server: ClientController, message: Message) => {
|
||||
if (ui.activeServer === null || ui.activeServer.id !== server.id) return;
|
||||
controller.on('new-message', async (guild: CombinedGuild, message: Message) => {
|
||||
if (ui.activeGuild === null || ui.activeGuild.id !== server.id) return;
|
||||
if (ui.activeChannel === null || ui.activeChannel.id !== message.channel.id) return;
|
||||
if (ui.messagesAtBottom) {
|
||||
// add the message to the bottom of the message feed
|
||||
@ -88,7 +88,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
|
||||
controller.on('verified', async (server: ClientController) => {
|
||||
controller.on('verified', async (guild: CombinedGuild) => {
|
||||
(async () => { // update connection info
|
||||
await Actions.fetchAndUpdateConnection(ui, server);
|
||||
})();
|
||||
@ -120,7 +120,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
})();
|
||||
});
|
||||
|
||||
controller.on('disconnected', (server: ClientController) => {
|
||||
controller.on('disconnected', (guild: CombinedGuild) => {
|
||||
(async () => {
|
||||
await Actions.fetchAndUpdateConnection(ui, server);
|
||||
})();
|
||||
@ -129,7 +129,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
})();
|
||||
});
|
||||
|
||||
controller.on('update-server', async (server: ClientController, serverData: ServerMetaData | CacheServerData) => {
|
||||
controller.on('update-server', async (guild: CombinedGuild, serverData: ServerMetaData | CacheServerData) => {
|
||||
LOG.debug(`s#${server.id} metadata updated`)
|
||||
await ui.updateServerName(server, serverData.name);
|
||||
|
||||
@ -146,12 +146,12 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
|
||||
controller.on('deleted-members', async (server: ClientController, members: Member[]) => {
|
||||
controller.on('deleted-members', async (guild: CombinedGuild, members: Member[]) => {
|
||||
LOG.debug(members.length + ' deleted members');
|
||||
await ui.deleteMembers(server, members);
|
||||
});
|
||||
|
||||
controller.on('updated-members', async (server: ClientController, data: { oldMember: Member, newMember: Member }[]) => {
|
||||
controller.on('updated-members', async (guild: CombinedGuild, data: { oldMember: Member, newMember: Member }[]) => {
|
||||
LOG.debug(data.length + ' updated members s#' + server.id);
|
||||
await ui.updateMembers(server, data);
|
||||
if (
|
||||
@ -162,40 +162,40 @@ window.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
});
|
||||
|
||||
controller.on('added-members', async (server: ClientController, members: Member[]) => {
|
||||
controller.on('added-members', async (guild: CombinedGuild, members: Member[]) => {
|
||||
LOG.debug(members.length + ' added members');
|
||||
await ui.addMembers(server, members);
|
||||
});
|
||||
|
||||
controller.on('deleted-channels', async (server: ClientController, channels: Channel[]) => {
|
||||
controller.on('deleted-channels', async (guild: CombinedGuild, channels: Channel[]) => {
|
||||
LOG.debug(channels.length + ' deleted channels');
|
||||
await ui.deleteChannels(server, channels);
|
||||
});
|
||||
|
||||
controller.on('updated-channels', async (server: ClientController, data: { oldChannel: Channel, newChannel: Channel }[]) => {
|
||||
controller.on('updated-channels', async (guild: CombinedGuild, data: { oldChannel: Channel, newChannel: Channel }[]) => {
|
||||
LOG.debug(data.length + ' updated channels');
|
||||
await ui.updateChannels(server, data);
|
||||
});
|
||||
|
||||
controller.on('added-channels', async (server: ClientController, channels: Channel[]) => {
|
||||
controller.on('added-channels', async (guild: CombinedGuild, channels: Channel[]) => {
|
||||
LOG.debug(channels.length + ' added channels');
|
||||
await ui.addChannels(server, channels);
|
||||
});
|
||||
|
||||
controller.on('deleted-messages', async (server: ClientController, channel: Channel, messages: Message[]) => {
|
||||
controller.on('deleted-messages', async (guild: CombinedGuild, channel: Channel, messages: Message[]) => {
|
||||
LOG.debug(messages.length + ' deleted messages');
|
||||
//LOG.debug('deleted messages:', { messages: deletedMessages.map(message => message.text) });
|
||||
// messages were deleted but the cache still had them
|
||||
await ui.deleteMessages(server, channel, messages);
|
||||
});
|
||||
|
||||
controller.on('updated-messages', async (server: ClientController, channel: Channel, data: { oldMessage: Message, newMessage: Message }[]) => {
|
||||
controller.on('updated-messages', async (guild: CombinedGuild, channel: Channel, data: { oldMessage: Message, newMessage: Message }[]) => {
|
||||
LOG.debug(data.length + ' updated messages');
|
||||
// messages were updated on the server-side
|
||||
await ui.updateMessages(server, channel, data);
|
||||
});
|
||||
|
||||
controller.on('added-messages', async (server: ClientController, channel: Channel, addedAfter: Map<string, Message>, addedBefore: Map<string, Message>) => {
|
||||
controller.on('added-messages', async (guild: CombinedGuild, channel: Channel, addedAfter: Map<string, Message>, addedBefore: Map<string, Message>) => {
|
||||
LOG.debug(addedAfter.size + ' added messages'); // addedBefore.size should equal addedAfter.size
|
||||
//LOG.debug('added messages', { messages: Array.from(addedAfter.values()).map(message => message.text) });
|
||||
// messages were added in a place that the cache did not have them
|
||||
|
@ -4,6 +4,7 @@ import * as socketio from 'socket.io-client';
|
||||
import DedupAwaiter from "./dedup-awaiter";
|
||||
import Util from './util';
|
||||
|
||||
// Automatically re-verifies the socket when connected
|
||||
export default class SocketVerifier {
|
||||
public isVerified = false;
|
||||
private memberId: string | null = null;
|
||||
|
@ -9,13 +9,12 @@ import ElementsUtil from './elements/require/elements-util';
|
||||
|
||||
import Globals from './globals';
|
||||
import Util from './util';
|
||||
import ClientController from './client-controller';
|
||||
import CombinedGuild from './guild-combined';
|
||||
import { Message, Member, Channel, ConnectionInfo, ShouldNeverHappenError } from './data-types';
|
||||
import Q from './q-module';
|
||||
import createServerListServer from './elements/server-list-server';
|
||||
import createGuildListGuild from './elements/guild-list-guild';
|
||||
import createChannel from './elements/channel';
|
||||
import createMember from './elements/member';
|
||||
import Actions from './actions';
|
||||
import Controller from './controller';
|
||||
import createMessage from './elements/message';
|
||||
|
||||
@ -25,14 +24,14 @@ interface SetMessageProps {
|
||||
}
|
||||
|
||||
export default class UI {
|
||||
public activeServer: ClientController | null = null;
|
||||
public activeGuild: CombinedGuild | null = null;
|
||||
public activeChannel: Channel | null = null;
|
||||
public activeConnection: ConnectionInfo | null = null;
|
||||
|
||||
public messagesAtTop = false;
|
||||
public messagesAtBottom = false;
|
||||
|
||||
public messagePairsServer: ClientController | null = null;
|
||||
public messagePairsGuild: CombinedGuild | null = null;
|
||||
public messagePairsChannel: Channel | { id: string } | null = null;
|
||||
public messagePairs = new Map<string | null, { message: Message, element: HTMLElement }>(); // messageId -> { message: Message, element: HTMLElement }
|
||||
|
||||
@ -44,8 +43,8 @@ export default class UI {
|
||||
this.q = q;
|
||||
}
|
||||
|
||||
public isMessagePairsServer(server: ClientController): boolean {
|
||||
return this.messagePairsServer !== null && server.id === this.messagePairsServer.id;
|
||||
public isMessagePairsGuild(guild: CombinedGuild): boolean {
|
||||
return this.messagePairsGuild !== null && guild.id === this.messagePairsGuild.id;
|
||||
}
|
||||
|
||||
public isMessagePairsChannel(channel: Channel): boolean {
|
||||
@ -55,66 +54,66 @@ export default class UI {
|
||||
// Use non-concurrent queues to prevent concurrent updates to parts of the view
|
||||
// This is effectively a javascript version of a 'lock'
|
||||
// These 'locks' should be called from working code rather than the updating functions themselves to work properly
|
||||
private _serversLock = new ConcurrentQueue<void>(1);
|
||||
private _serverNameLock = new ConcurrentQueue<void>(1);
|
||||
private _guildsLock = new ConcurrentQueue<void>(1);
|
||||
private _guildNameLock = new ConcurrentQueue<void>(1);
|
||||
private _connectionLock = new ConcurrentQueue<void>(1);
|
||||
private _channelsLock = new ConcurrentQueue<void>(1);
|
||||
private _membersLock = new ConcurrentQueue<void>(1);
|
||||
private _messagesLock = new ConcurrentQueue<void>(1);
|
||||
|
||||
private async _lockWithServer(server: ClientController, task: (() => Promise<void> | void), lock: ConcurrentQueue<void>): Promise<void> {
|
||||
if (this.activeServer === null || this.activeServer.id !== server.id) return;
|
||||
private async _lockWithGuild(guild: CombinedGuild, task: (() => Promise<void> | void), lock: ConcurrentQueue<void>): Promise<void> {
|
||||
if (this.activeGuild === null || this.activeGuild.id !== guild.id) return;
|
||||
await lock.push(async () => {
|
||||
if (this.activeServer === null || this.activeServer.id !== server.id) return;
|
||||
if (this.activeGuild === null || this.activeGuild.id !== guild.id) return;
|
||||
await task();
|
||||
});
|
||||
}
|
||||
|
||||
private async _lockWithServerChannel(server: ClientController, channel: Channel | { id: string }, task: (() => Promise<void> | void), lock: ConcurrentQueue<void>): Promise<void> {
|
||||
if (this.activeServer === null || this.activeServer.id !== server.id) return;
|
||||
private async _lockWithGuildChannel(guild: CombinedGuild, channel: Channel | { id: string }, task: (() => Promise<void> | void), lock: ConcurrentQueue<void>): Promise<void> {
|
||||
if (this.activeGuild === null || this.activeGuild.id !== guild.id) return;
|
||||
if (this.activeChannel === null || this.activeChannel.id !== channel.id) return;
|
||||
await lock.push(async () => {
|
||||
if (this.activeServer === null || this.activeServer.id !== server.id) return;
|
||||
if (this.activeGuild === null || this.activeGuild.id !== guild.id) return;
|
||||
if (this.activeChannel === null || this.activeChannel.id !== channel.id) return;
|
||||
await task();
|
||||
});
|
||||
}
|
||||
|
||||
public async lockServerName(server: ClientController, task: (() => Promise<void> | void)): Promise<void> {
|
||||
await this._lockWithServer(server, task, this._serverNameLock);
|
||||
public async lockGuildName(guild: CombinedGuild, task: (() => Promise<void> | void)): Promise<void> {
|
||||
await this._lockWithGuild(guild, task, this._guildNameLock);
|
||||
}
|
||||
|
||||
public async lockConnection(server: ClientController, task: (() => Promise<void> | void)): Promise<void> {
|
||||
await this._lockWithServer(server, task, this._connectionLock);
|
||||
public async lockConnection(guild: CombinedGuild, task: (() => Promise<void> | void)): Promise<void> {
|
||||
await this._lockWithGuild(guild, task, this._connectionLock);
|
||||
}
|
||||
|
||||
public async lockChannels(server: ClientController, task: (() => Promise<void> | void)): Promise<void> {
|
||||
await this._lockWithServer(server, task, this._channelsLock);
|
||||
public async lockChannels(guild: CombinedGuild, task: (() => Promise<void> | void)): Promise<void> {
|
||||
await this._lockWithGuild(guild, task, this._channelsLock);
|
||||
}
|
||||
|
||||
public async lockMembers(server: ClientController, task: (() => Promise<void> | void)): Promise<void> {
|
||||
await this._lockWithServer(server, task, this._membersLock);
|
||||
public async lockMembers(guild: CombinedGuild, task: (() => Promise<void> | void)): Promise<void> {
|
||||
await this._lockWithGuild(guild, task, this._membersLock);
|
||||
}
|
||||
|
||||
public async lockMessages(server: ClientController, channel: Channel | { id: string }, task: (() => Promise<void> | void)): Promise<void> {
|
||||
await this._lockWithServerChannel(server, channel, task, this._messagesLock);
|
||||
public async lockMessages(guild: CombinedGuild, channel: Channel | { id: string }, task: (() => Promise<void> | void)): Promise<void> {
|
||||
await this._lockWithGuildChannel(guild, channel, task, this._messagesLock);
|
||||
}
|
||||
|
||||
public setActiveServer(server: ClientController): void {
|
||||
if (this.activeServer !== null) {
|
||||
let prev = this.q.$_('#server-list .server[meta-id="' + this.activeServer.id + '"]');
|
||||
public setActiveGuild(guild: CombinedGuild): void {
|
||||
if (this.activeGuild !== null) {
|
||||
let prev = this.q.$_('#guild-list .guild[meta-id="' + this.activeGuild.id + '"]');
|
||||
if (prev) {
|
||||
prev.classList.remove('active');
|
||||
}
|
||||
}
|
||||
let next = this.q.$('#server-list .server[meta-id="' + server.id + '"]');
|
||||
let next = this.q.$('#guild-list .guild[meta-id="' + guild.id + '"]');
|
||||
next.classList.add('active');
|
||||
this.q.$('#server').setAttribute('meta-id', server.id);
|
||||
this.activeServer = server;
|
||||
this.q.$('#guild').setAttribute('meta-id', guild.id + '');
|
||||
this.activeGuild = guild;
|
||||
}
|
||||
|
||||
public async setActiveChannel(server: ClientController, channel: Channel): Promise<void> {
|
||||
await this.lockChannels(server, () => {
|
||||
public async setActiveChannel(guild: CombinedGuild, channel: Channel): Promise<void> {
|
||||
await this.lockChannels(guild, () => {
|
||||
// Channel List Highlight
|
||||
if (this.activeChannel !== null) {
|
||||
let prev = this.q.$_('#channel-list .channel[meta-id="' + this.activeChannel.id + '"]');
|
||||
@ -135,25 +134,25 @@ export default class UI {
|
||||
});
|
||||
}
|
||||
|
||||
public async setActiveConnection(server: ClientController, connection: ConnectionInfo): Promise<void> {
|
||||
await this.lockConnection(server, () => {
|
||||
public async setActiveConnection(guild: CombinedGuild, connection: ConnectionInfo): Promise<void> {
|
||||
await this.lockConnection(guild, () => {
|
||||
this.activeConnection = connection;
|
||||
|
||||
this.q.$('#connection').className = 'member ' + connection.status;
|
||||
this.q.$('#member-name').innerText = connection.displayName;
|
||||
this.q.$('#member-status-text').innerText = connection.status;
|
||||
|
||||
this.q.$('#server').className = '';
|
||||
this.q.$('#guild').className = '';
|
||||
for (let privilege of connection.privileges) {
|
||||
this.q.$('#server').classList.add('privilege-' + privilege);
|
||||
this.q.$('#guild').classList.add('privilege-' + privilege);
|
||||
}
|
||||
|
||||
if (connection.avatarResourceId) {
|
||||
(async () => {
|
||||
// Update avatar
|
||||
if (this.activeServer === null || this.activeServer.id !== server.id) return;
|
||||
let src = await ElementsUtil.getImageBufferFromResourceFailSoftly(server, connection.avatarResourceId);
|
||||
if (this.activeServer === null || this.activeServer.id !== server.id) return;
|
||||
if (this.activeGuild === null || this.activeGuild.id !== guild.id) return;
|
||||
let src = await ElementsUtil.getImageBufferFromResourceFailSoftly(guild, connection.avatarResourceId);
|
||||
if (this.activeGuild === null || this.activeGuild.id !== guild.id) return;
|
||||
(this.q.$('#member-avatar') as HTMLImageElement).src = src;
|
||||
})();
|
||||
} else {
|
||||
@ -162,55 +161,55 @@ export default class UI {
|
||||
});
|
||||
}
|
||||
|
||||
public async setServers(controller: Controller, servers: ClientController[]): Promise<void> {
|
||||
await this._serversLock.push(() => {
|
||||
Q.clearChildren(this.q.$('#server-list'));
|
||||
for (let server of servers) {
|
||||
let element = createServerListServer(this.document, this.q, this, controller, server);
|
||||
this.q.$('#server-list').appendChild(element);
|
||||
public async setGuilds(controller: Controller, guilds: CombinedGuild[]): Promise<void> {
|
||||
await this._guildsLock.push(() => {
|
||||
Q.clearChildren(this.q.$('#guild-list'));
|
||||
for (let guild of guilds) {
|
||||
let element = createGuildListGuild(this.document, this.q, this, controller, guild);
|
||||
this.q.$('#guild-list').appendChild(element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async addServer(controller: Controller, server: ClientController): Promise<HTMLElement> {
|
||||
public async addGuild(controller: Controller, guild: CombinedGuild): Promise<HTMLElement> {
|
||||
let element: HTMLElement | null = null;
|
||||
await this._serversLock.push(() => {
|
||||
element = createServerListServer(this.document, this.q, this, controller, server) as HTMLElement;
|
||||
this.q.$('#server-list').appendChild(element);
|
||||
await this._guildsLock.push(() => {
|
||||
element = createGuildListGuild(this.document, this.q, this, controller, guild) as HTMLElement;
|
||||
this.q.$('#guild-list').appendChild(element);
|
||||
});
|
||||
if (element == null) throw new ShouldNeverHappenError('element was not set');
|
||||
return element;
|
||||
}
|
||||
|
||||
public async removeServer(server: ClientController): Promise<void> {
|
||||
await this._serversLock.push(() => {
|
||||
let element = this.q.$_('#server-list .server[meta-id="' + server.id + '"]');
|
||||
public async removeGuild(guild: CombinedGuild): Promise<void> {
|
||||
await this._guildsLock.push(() => {
|
||||
let element = this.q.$_('#guild-list .guild[meta-id="' + guild.id + '"]');
|
||||
element?.parentElement?.removeChild(element);
|
||||
});
|
||||
}
|
||||
|
||||
public async updateServerIcon(server: ClientController, iconBuff: Buffer): Promise<void> {
|
||||
await this._serversLock.push(async () => {
|
||||
let iconElement = this.q.$('#server-list .server[meta-id="' + server.id + '"] img') as HTMLImageElement;
|
||||
public async updateGuildIcon(guild: CombinedGuild, iconBuff: Buffer): Promise<void> {
|
||||
await this._guildsLock.push(async () => {
|
||||
let iconElement = this.q.$('#guild-list .guild[meta-id="' + guild.id + '"] img') as HTMLImageElement;
|
||||
iconElement.src = await ElementsUtil.getImageBufferSrc(iconBuff);
|
||||
});
|
||||
}
|
||||
|
||||
public async updateServerName(server: ClientController, name: string): Promise<void>{
|
||||
await this.lockServerName(server, () => {
|
||||
this.q.$('#server-name').innerText = name;
|
||||
let baseElement = this.q.$('#server-list .server[meta-id="' + server.id + '"]');
|
||||
public async updateGuildName(guild: CombinedGuild, name: string): Promise<void>{
|
||||
await this.lockGuildName(guild, () => {
|
||||
this.q.$('#guild-name').innerText = name;
|
||||
let baseElement = this.q.$('#guild-list .guild[meta-id="' + guild.id + '"]');
|
||||
baseElement.setAttribute('meta-name', name);
|
||||
});
|
||||
}
|
||||
|
||||
private _updatePosition<T>(element: HTMLElement, serverCacheMap: Map<string | null, T>, getDirection: ((prevData: T, data: T) => number)) {
|
||||
let data = serverCacheMap.get(element.getAttribute('meta-id'));
|
||||
private _updatePosition<T>(element: HTMLElement, guildCacheMap: Map<string | null, T>, getDirection: ((prevData: T, data: T) => number)) {
|
||||
let data = guildCacheMap.get(element.getAttribute('meta-id'));
|
||||
if (!data) throw new ShouldNeverHappenError('unable to get data from cache map');
|
||||
// TODO: do-while may be a bit cleaner?
|
||||
let prev = Q.previousElement(element);
|
||||
while (prev != null) {
|
||||
let prevData = serverCacheMap.get(prev.getAttribute('meta-id'));
|
||||
let prevData = guildCacheMap.get(prev.getAttribute('meta-id'));
|
||||
if (!prevData) throw new ShouldNeverHappenError('unable to get prevData from cache map');
|
||||
if (getDirection(prevData, data) > 0) { // this element comes before previous element
|
||||
prev.parentElement?.insertBefore(element, prev);
|
||||
@ -221,7 +220,7 @@ export default class UI {
|
||||
}
|
||||
let next = Q.nextElement(element);
|
||||
while (next != null) {
|
||||
let nextData = serverCacheMap.get(next.getAttribute('meta-id'));
|
||||
let nextData = guildCacheMap.get(next.getAttribute('meta-id'));
|
||||
if (!nextData) throw new ShouldNeverHappenError('unable to get nextData from cache map');
|
||||
if (getDirection(data, nextData) > 0) { // this element comes after next element
|
||||
next.parentElement?.insertBefore(next, element);
|
||||
@ -232,27 +231,27 @@ export default class UI {
|
||||
}
|
||||
}
|
||||
|
||||
public updateChannelPosition(server: ClientController, channelElement: HTMLElement): void {
|
||||
this._updatePosition(channelElement, server.channels, (a, b) => {
|
||||
public async updateChannelPosition(guild: CombinedGuild, channelElement: HTMLElement): Promise<void> {
|
||||
this._updatePosition(channelElement, await guild.grabRAMChannelsMap(), (a, b) => {
|
||||
return a.index - b.index;
|
||||
});
|
||||
}
|
||||
|
||||
public async addChannels(server: ClientController, channels: Channel[], options?: { clear: boolean }): Promise<void> {
|
||||
await this.lockChannels(server, () => {
|
||||
public async addChannels(guild: CombinedGuild, channels: Channel[], options?: { clear: boolean }): Promise<void> {
|
||||
await this.lockChannels(guild, async () => {
|
||||
if (options?.clear) {
|
||||
Q.clearChildren(this.q.$('#channel-list'));
|
||||
}
|
||||
for (let channel of channels) {
|
||||
let element = createChannel(this.document, this.q, this, server, channel);
|
||||
let element = createChannel(this.document, this.q, this, guild, channel);
|
||||
this.q.$('#channel-list').appendChild(element);
|
||||
this.updateChannelPosition(server, element);
|
||||
await this.updateChannelPosition(guild, element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async deleteChannels(server: ClientController, channels: Channel[]): Promise<void> {
|
||||
await this.lockChannels(server, () => {
|
||||
public async deleteChannels(guild: CombinedGuild, channels: Channel[]): Promise<void> {
|
||||
await this.lockChannels(guild, () => {
|
||||
for (let channel of channels) {
|
||||
let element = this.q.$_('#channel-list .channel[meta-id="' + channel.id + '"]');
|
||||
element?.parentElement?.removeChild(element);
|
||||
@ -263,13 +262,13 @@ export default class UI {
|
||||
});
|
||||
}
|
||||
|
||||
public async updateChannels(server: ClientController, data: { oldChannel: Channel, newChannel: Channel }[]): Promise<void> {
|
||||
await this.lockChannels(server, () => {
|
||||
public async updateChannels(guild: CombinedGuild, data: { oldChannel: Channel, newChannel: Channel }[]): Promise<void> {
|
||||
await this.lockChannels(guild, async () => {
|
||||
for (const { oldChannel, newChannel } of data) {
|
||||
let oldElement = this.q.$('#channel-list .channel[meta-id="' + newChannel.id + '"]');
|
||||
let newElement = createChannel(this.document, this.q, this, server, newChannel);
|
||||
let newElement = createChannel(this.document, this.q, this, guild, newChannel);
|
||||
oldElement.parentElement?.replaceChild(newElement, oldElement);
|
||||
this.updateChannelPosition(server, newElement);
|
||||
await this.updateChannelPosition(guild, newElement);
|
||||
|
||||
if (this.activeChannel !== null && this.activeChannel.id === newChannel.id) {
|
||||
newElement.classList.add('active');
|
||||
@ -284,38 +283,39 @@ export default class UI {
|
||||
});
|
||||
}
|
||||
|
||||
public async setChannels(server: ClientController, channels: Channel[]): Promise<void> {
|
||||
// check if an element with the same channel and server exists before adding the new channels
|
||||
// this is nescessary to make sure that if two servers have channels with the same id, the channel list is still
|
||||
public async setChannels(guild: CombinedGuild, channels: Channel[]): Promise<void> {
|
||||
// check if an element with the same channel and guild exists before adding the new channels
|
||||
// this is nescessary to make sure that if two guilds have channels with the same id, the channel list is still
|
||||
// properly refreshed and the active channel is not improperly set.
|
||||
let oldMatchingElement: HTMLElement | null = null;
|
||||
if (this.activeServer !== null && this.activeChannel !== null) {
|
||||
oldMatchingElement = this.q.$_('#channel-list .channel[meta-id="' + this.activeChannel.id + '"][meta-server-id="' + this.activeServer.id + '"]');
|
||||
if (this.activeGuild !== null && this.activeChannel !== null) {
|
||||
oldMatchingElement = this.q.$_('#channel-list .channel[meta-id="' + this.activeChannel.id + '"][meta-guild-id="' + this.activeGuild.id + '"]');
|
||||
}
|
||||
|
||||
await this.addChannels(server, channels, { clear: true });
|
||||
await this.addChannels(guild, channels, { clear: true });
|
||||
|
||||
if (this.activeServer !== null && this.activeServer.id === server.id && this.activeChannel !== null) {
|
||||
let newActiveElement = this.q.$_('#channel-list .channel[meta-id="' + this.activeChannel.id + '"][meta-server-id="' + this.activeServer.id + '"]');
|
||||
if (this.activeGuild !== null && this.activeGuild.id === guild.id && this.activeChannel !== null) {
|
||||
let newActiveElement = this.q.$_('#channel-list .channel[meta-id="' + this.activeChannel.id + '"][meta-guild-id="' + this.activeGuild.id + '"]');
|
||||
if (newActiveElement && oldMatchingElement) {
|
||||
let activeChannelId = this.activeChannel.id;
|
||||
let channel = channels.find(channel => channel.id === activeChannelId);
|
||||
if (channel === undefined) throw new ShouldNeverHappenError('current channel does not exist in channels list')
|
||||
this.setActiveChannel(server, channel);
|
||||
this.setActiveChannel(guild, channel);
|
||||
} else {
|
||||
this.activeChannel = null; // the active channel was removed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async setChannelsErrorIndicator(server: ClientController, errorIndicatorElement: HTMLElement): Promise<void> {
|
||||
await this.lockChannels(server, () => {
|
||||
public async setChannelsErrorIndicator(guild: CombinedGuild, errorIndicatorElement: HTMLElement): Promise<void> {
|
||||
await this.lockChannels(guild, () => {
|
||||
Q.clearChildren(this.q.$('#channel-list'));
|
||||
this.q.$('#channel-list').appendChild(errorIndicatorElement);
|
||||
});
|
||||
}
|
||||
|
||||
public updateMemberPosition(server: ClientController, memberElement: HTMLElement): void {
|
||||
public async updateMemberPosition(guild: CombinedGuild, memberElement: HTMLElement): Promise<void> {
|
||||
// TODO: Change 100 to a constant?
|
||||
let statusOrder = {
|
||||
'online': 0,
|
||||
'away': 1,
|
||||
@ -324,7 +324,7 @@ export default class UI {
|
||||
'invisible': 3, // this would only be shown in the case of the current member.
|
||||
'unknown': 100,
|
||||
};
|
||||
this._updatePosition(memberElement, server.members, (a, b) => {
|
||||
this._updatePosition(memberElement, await guild.grabRAMMembersMap(), (a, b) => {
|
||||
let onlineCmp = (a.status == 'offline' ? 1 : 0) - (b.status == 'offline' ? 1 : 0);
|
||||
if (onlineCmp != 0) return onlineCmp;
|
||||
let rolePriorityCmp = (a.rolePriority == null ? 100 : a.rolePriority) - (b.rolePriority == null ? 100 : b.rolePriority);
|
||||
@ -336,41 +336,41 @@ export default class UI {
|
||||
});
|
||||
}
|
||||
|
||||
public async addMembers(server: ClientController, members: Member[], options?: { clear: boolean }): Promise<void> {
|
||||
await this.lockMembers(server, () => {
|
||||
public async addMembers(guild: CombinedGuild, members: Member[], options?: { clear: boolean }): Promise<void> {
|
||||
await this.lockMembers(guild, async () => {
|
||||
if (options?.clear) {
|
||||
Q.clearChildren(this.q.$('#server-members'));
|
||||
Q.clearChildren(this.q.$('#guild-members'));
|
||||
}
|
||||
for (let member of members) {
|
||||
let element = createMember(this.q, server, member);
|
||||
this.q.$('#server-members').appendChild(element);
|
||||
this.updateMemberPosition(server, element);
|
||||
let element = createMember(this.q, guild, member);
|
||||
this.q.$('#guild-members').appendChild(element);
|
||||
await this.updateMemberPosition(guild, element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async deleteMembers(server: ClientController, members: Member[]): Promise<void> {
|
||||
await this.lockMembers(server, () => {
|
||||
public async deleteMembers(guild: CombinedGuild, members: Member[]): Promise<void> {
|
||||
await this.lockMembers(guild, () => {
|
||||
for (let member of members) {
|
||||
let element = this.q.$_('#server-members .member[meta-id="' + member.id + '"]');
|
||||
let element = this.q.$_('#guild-members .member[meta-id="' + member.id + '"]');
|
||||
element?.parentElement?.removeChild(element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async updateMembers(server: ClientController, data: { oldMember: Member, newMember: Member }[]): Promise<void> {
|
||||
await this.lockMembers(server, () => {
|
||||
public async updateMembers(guild: CombinedGuild, data: { oldMember: Member, newMember: Member }[]): Promise<void> {
|
||||
await this.lockMembers(guild, async () => {
|
||||
for (const { oldMember, newMember } of data) {
|
||||
let oldElement = this.q.$_('#server-members .member[meta-id="' + newMember.id + '"]');
|
||||
let oldElement = this.q.$_('#guild-members .member[meta-id="' + newMember.id + '"]');
|
||||
if (oldElement) {
|
||||
let newElement = createMember(this.q, server, newMember);
|
||||
let newElement = createMember(this.q, guild, newMember);
|
||||
oldElement.parentElement?.replaceChild(newElement, oldElement);
|
||||
this.updateMemberPosition(server, newElement);
|
||||
await this.updateMemberPosition(guild, newElement);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (this.activeChannel === null) return;
|
||||
await this.lockMessages(server, this.activeChannel, () => {
|
||||
await this.lockMessages(guild, this.activeChannel, () => {
|
||||
for (const { oldMember, newMember } of data) {
|
||||
let newStyle = newMember.roleColor ? 'color: ' + newMember.roleColor : null;
|
||||
let newName = newMember.displayName;
|
||||
@ -386,14 +386,14 @@ export default class UI {
|
||||
});
|
||||
}
|
||||
|
||||
public async setMembers(server: ClientController, members: Member[]): Promise<void> {
|
||||
await this.addMembers(server, members, { clear: true });
|
||||
public async setMembers(guild: CombinedGuild, members: Member[]): Promise<void> {
|
||||
await this.addMembers(guild, members, { clear: true });
|
||||
}
|
||||
|
||||
public async setMembersErrorIndicator(server: ClientController, errorIndicatorElement: HTMLElement): Promise<void> {
|
||||
await this.lockMembers(server, () => {
|
||||
Q.clearChildren(this.q.$('#server-members'));
|
||||
this.q.$('#server-members').appendChild(errorIndicatorElement);
|
||||
public async setMembersErrorIndicator(guild: CombinedGuild, errorIndicatorElement: HTMLElement): Promise<void> {
|
||||
await this.lockMembers(guild, () => {
|
||||
Q.clearChildren(this.q.$('#guild-members'));
|
||||
this.q.$('#guild-members').appendChild(errorIndicatorElement);
|
||||
});
|
||||
}
|
||||
|
||||
@ -408,8 +408,8 @@ export default class UI {
|
||||
return element && this.messagePairs.get(element.getAttribute('meta-id')) || null;
|
||||
}
|
||||
|
||||
public async addMessagesBefore(server: ClientController, channel: Channel, messages: Message[], prevTopMessage: Message | null): Promise<void> {
|
||||
this.lockMessages(server, channel, () => {
|
||||
public async addMessagesBefore(guild: CombinedGuild, channel: Channel, messages: Message[], prevTopMessage: Message | null): Promise<void> {
|
||||
this.lockMessages(guild, channel, () => {
|
||||
if (prevTopMessage && this.getTopMessagePair()?.message.id !== prevTopMessage.id) return;
|
||||
|
||||
this.messagesAtTop = false;
|
||||
@ -441,22 +441,22 @@ export default class UI {
|
||||
for (let i = messages.length - 1; i >= 0; --i) {
|
||||
let message = messages[i];
|
||||
let priorMessage = messages[i - 1] || null;
|
||||
let element = createMessage(this.document, this.q, server, message, priorMessage);
|
||||
let element = createMessage(this.document, this.q, guild, message, priorMessage);
|
||||
this.messagePairs.set(message.id, { message: message, element: element });
|
||||
this.q.$('#channel-feed').prepend(element);
|
||||
}
|
||||
|
||||
if (messages.length > 0 && prevTopPair) {
|
||||
// Update the previous top message since it may have changed format
|
||||
let newPrevTopElement = createMessage(this.document, this.q, server, prevTopPair.message, messages[messages.length - 1]);
|
||||
let newPrevTopElement = createMessage(this.document, this.q, guild, prevTopPair.message, messages[messages.length - 1]);
|
||||
prevTopPair.element.parentElement?.replaceChild(newPrevTopElement, prevTopPair.element);
|
||||
this.messagePairs.set(prevTopPair.message.id, { message: prevTopPair.message, element: newPrevTopElement });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async addMessagesAfter(server: ClientController, channel: Channel | { id: string }, messages: Message[], prevBottomMessage: Message | null): Promise<void> {
|
||||
await this.lockMessages(server, channel, () => {
|
||||
public async addMessagesAfter(guild: CombinedGuild, channel: Channel | { id: string }, messages: Message[], prevBottomMessage: Message | null): Promise<void> {
|
||||
await this.lockMessages(guild, channel, () => {
|
||||
if (prevBottomMessage && this.getBottomMessagePair()?.message.id !== prevBottomMessage.id) return;
|
||||
|
||||
this.messagesAtBottom = false;
|
||||
@ -487,7 +487,7 @@ export default class UI {
|
||||
for (let i = 0; i < messages.length; ++i) { // add in-order since we will be scrolling from oldest to newest
|
||||
let message = messages[i];
|
||||
let priorMessage = messages[i - 1] || (prevBottomPair && prevBottomPair.message);
|
||||
let element = createMessage(this.document, this.q, server, message, priorMessage);
|
||||
let element = createMessage(this.document, this.q, guild, message, priorMessage);
|
||||
this.messagePairs.set(message.id, { message: message, element: element });
|
||||
this.q.$('#channel-feed').appendChild(element);
|
||||
}
|
||||
@ -495,8 +495,8 @@ export default class UI {
|
||||
}
|
||||
|
||||
// TODO: use topMessage, bottomMessage / topMessageId, bottomMessageId instead?
|
||||
public async addMessagesBetween(server: ClientController, channel: Channel, messages: Message[], topElement: HTMLElement, bottomElement: HTMLElement): Promise<void> {
|
||||
await this.lockMessages(server, channel, () => {
|
||||
public async addMessagesBetween(guild: CombinedGuild, channel: Channel, messages: Message[], topElement: HTMLElement, bottomElement: HTMLElement): Promise<void> {
|
||||
await this.lockMessages(guild, channel, () => {
|
||||
if (!(messages.length > 0 && topElement != null && bottomElement != null && bottomElement == Q.nextElement(topElement))) {
|
||||
LOG.error('invalid messages between', { messages, top: topElement.innerText, bottom: bottomElement.innerText, afterTop: Q.nextElement(topElement)?.innerText });
|
||||
throw new Error('invalid messages between');
|
||||
@ -566,7 +566,7 @@ export default class UI {
|
||||
for (let i = 0; i < messages.length; ++i) {
|
||||
let message = messages[i];
|
||||
let priorMessage = messages[i - 1] || topMessage;
|
||||
let element = createMessage(this.document, this.q, server, message, priorMessage);
|
||||
let element = createMessage(this.document, this.q, guild, message, priorMessage);
|
||||
this.messagePairs.set(message.id, { message: message, element: element });
|
||||
this.q.$('#channel-feed').insertBefore(element, bottomElement);
|
||||
}
|
||||
@ -575,16 +575,16 @@ export default class UI {
|
||||
// update the bottom element since the element above it changed
|
||||
let bottomMessage = this.messagePairs.get(bottomElement.getAttribute('meta-id'))?.message;
|
||||
if (!bottomMessage) throw new ShouldNeverHappenError('could not find bottom message');
|
||||
let newBottomElement = createMessage(this.document, this.q, server, bottomMessage, messages[messages.length - 1]);
|
||||
let newBottomElement = createMessage(this.document, this.q, guild, bottomMessage, messages[messages.length - 1]);
|
||||
bottomElement.parentElement?.replaceChild(newBottomElement, bottomElement);
|
||||
this.messagePairs.set(bottomMessage.id, { element: newBottomElement, message: bottomMessage });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async setMessages(server: ClientController, channel: Channel | { id: string }, messages: Message[], props: SetMessageProps): Promise<void> {
|
||||
public async setMessages(guild: CombinedGuild, channel: Channel | { id: string }, messages: Message[], props: SetMessageProps): Promise<void> {
|
||||
const { atTop, atBottom } = props;
|
||||
await this.lockMessages(server, channel, () => {
|
||||
await this.lockMessages(guild, channel, () => {
|
||||
this.messagesAtTop = atTop;
|
||||
this.messagesAtBottom = atBottom;
|
||||
|
||||
@ -592,7 +592,7 @@ export default class UI {
|
||||
Util.removeErrorIndicators(this.q, this.q.$('#channel-feed'), [ 'before' ]);
|
||||
Util.removeErrorIndicators(this.q, this.q.$('#channel-feed'), [ 'after' ]);
|
||||
|
||||
this.messagePairsServer = server;
|
||||
this.messagePairsGuild = guild;
|
||||
this.messagePairsChannel = channel;
|
||||
|
||||
this.messagePairs.clear();
|
||||
@ -604,7 +604,7 @@ export default class UI {
|
||||
for (let i = messages.length - 1; i >= 0; --i) {
|
||||
let message = messages[i];
|
||||
let priorMessage = messages[i - 1] || null;
|
||||
let element = createMessage(this.document, this.q, server, message, priorMessage);
|
||||
let element = createMessage(this.document, this.q, guild, message, priorMessage);
|
||||
this.messagePairs.set(message.id, { message: message, element: element });
|
||||
this.q.$('#channel-feed').prepend(element);
|
||||
}
|
||||
@ -618,8 +618,8 @@ export default class UI {
|
||||
this.messagesAtBottom = true;
|
||||
}
|
||||
|
||||
public async deleteMessages(server: ClientController, channel: Channel, messages: Message[]) {
|
||||
await this.lockMessages(server, channel, () => {
|
||||
public async deleteMessages(guild: CombinedGuild, channel: Channel, messages: Message[]) {
|
||||
await this.lockMessages(guild, channel, () => {
|
||||
for (let message of messages) {
|
||||
if (this.messagePairs.has(message.id)) {
|
||||
let messagePair = this.messagePairs.get(message.id) as { message: Message, element: HTMLElement };
|
||||
@ -632,14 +632,14 @@ export default class UI {
|
||||
});
|
||||
}
|
||||
|
||||
public async updateMessages(server: ClientController, channel: Channel, data: { oldMessage: Message, newMessage: Message }[]): Promise<void> {
|
||||
await this.lockMessages(server, channel, () => {
|
||||
public async updateMessages(guild: CombinedGuild, channel: Channel, data: { oldMessage: Message, newMessage: Message }[]): Promise<void> {
|
||||
await this.lockMessages(guild, channel, () => {
|
||||
for (const { oldMessage, newMessage } of data) {
|
||||
if (this.messagePairs.has(oldMessage.id)) {
|
||||
let oldElement = (this.messagePairs.get(oldMessage.id) as { message: Message, element: HTMLElement }).element;
|
||||
let prevElement = Q.previousElement(oldElement);
|
||||
let prevMessage = prevElement && (this.messagePairs.get(prevElement.getAttribute('meta-id')) as { message: Message, element: HTMLElement }).message;
|
||||
let newElement = createMessage(this.document, this.q, server, newMessage, prevMessage);
|
||||
let newElement = createMessage(this.document, this.q, guild, newMessage, prevMessage);
|
||||
oldElement.parentElement?.replaceChild(newElement, oldElement);
|
||||
// TODO: we should be updating messages sent below this message
|
||||
// however, these events should be relatively rare so that's for the future
|
||||
@ -649,20 +649,20 @@ export default class UI {
|
||||
});
|
||||
}
|
||||
|
||||
public async addMessagesErrorIndicatorBefore(server: ClientController, channel: Channel, errorIndicatorElement: HTMLElement): Promise<void> {
|
||||
await this.lockMessages(server, channel, () => {
|
||||
public async addMessagesErrorIndicatorBefore(guild: CombinedGuild, channel: Channel, errorIndicatorElement: HTMLElement): Promise<void> {
|
||||
await this.lockMessages(guild, channel, () => {
|
||||
this.q.$('#channel-feed').prepend(errorIndicatorElement);
|
||||
});
|
||||
}
|
||||
|
||||
public async addMessagesErrorIndicatorAfter(server: ClientController, channel: Channel, errorIndicatorElement: HTMLElement): Promise<void> {
|
||||
await this.lockMessages(server, channel, () => {
|
||||
public async addMessagesErrorIndicatorAfter(guild: CombinedGuild, channel: Channel, errorIndicatorElement: HTMLElement): Promise<void> {
|
||||
await this.lockMessages(guild, channel, () => {
|
||||
this.q.$('#channel-feed').appendChild(errorIndicatorElement);
|
||||
});
|
||||
}
|
||||
|
||||
public async setMessagesErrorIndicator(server: ClientController, channel: Channel | { id: string }, errorIndicatorElement: HTMLElement): Promise<void> {
|
||||
await this.lockMessages(server, channel, () => {
|
||||
public async setMessagesErrorIndicator(guild: CombinedGuild, channel: Channel | { id: string }, errorIndicatorElement: HTMLElement): Promise<void> {
|
||||
await this.lockMessages(guild, channel, () => {
|
||||
Q.clearChildren(this.q.$('#channel-feed'));
|
||||
this.q.$('#channel-feed').appendChild(errorIndicatorElement);
|
||||
});
|
||||
|
@ -178,10 +178,11 @@ function bindRegistrationEvents(io: socketio.Server, client: socketio.Socket): v
|
||||
const { guildId, memberId } = await DB.registerWithToken(token, publicKeyBuff, displayName, avatarBuff);
|
||||
|
||||
const member = await DB.getMember(guildId, memberId);
|
||||
const meta = await DB.getGuild(guildId);
|
||||
|
||||
LOG.info(`c#${client.id}: registered with t#${token} as u#${member.id} / ${member.display_name}`);
|
||||
|
||||
respond(null, member);
|
||||
respond(null, member, meta);
|
||||
|
||||
io.to(guildId).emit('new-member', member);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user