diff --git a/src/client/webapp/actions.ts b/src/client/webapp/actions.ts
index 21d81eb..d157933 100644
--- a/src/client/webapp/actions.ts
+++ b/src/client/webapp/actions.ts
@@ -4,11 +4,9 @@ import Logger from '../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import Util from './util';
-import Globals from './globals';
import UI from './ui';
import CombinedGuild from './guild-combined';
-import { Channel } from './data-types';
import Q from './q-module';
export default class Actions {
@@ -45,66 +43,4 @@ export default class Actions {
errorMessage: 'Error fetching channels'
});
}
-
- static async fetchAndUpdateMessagesRecent(q: Q, ui: UI, guild: CombinedGuild, channel: Channel | { id: string }) {
- await Util.withPotentialErrorWarnOnCancel(q, {
- taskFunc: async () => {
- if (ui.activeGuild === null || ui.activeGuild.id !== guild.id) return;
- if (ui.activeChannel === null || ui.activeChannel.id !== channel.id) return;
- const 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(guild, channel, errorIndicatorElement);
- },
- errorContainer: q.$('#channel-feed'),
- errorMessage: 'Error fetching messages'
- });
- }
-
- static async fetchAndUpdateMessagesBefore(q: Q, ui: UI, guild: CombinedGuild, channel: Channel) {
- await Util.withPotentialErrorWarnOnCancel(q, {
- taskFunc: async () => {
- if (ui.activeGuild === null || ui.activeGuild.id !== guild.id) return;
- if (ui.activeChannel === null || ui.activeChannel.id !== channel.id) return;
- const topPair = ui.getTopMessagePair();
- if (topPair == null) return;
- const messages = await guild.fetchMessagesBefore(channel.id, topPair.message.id, Globals.MESSAGES_PER_REQUEST);
- if (messages && messages.length > 0) {
- await ui.addMessagesBefore(guild, channel, messages, topPair.message);
- } else {
- ui.messagesAtTop = true;
- }
- },
- errorIndicatorAddFunc: async (errorIndicatorElement) => {
- await ui.addMessagesErrorIndicatorBefore(guild, channel, errorIndicatorElement);
- },
- errorContainer: q.$('#channel-feed'),
- errorClasses: [ 'before' ],
- errorMessage: 'Error loading older messages'
- });
- }
-
- static async fetchAndUpdateMessagesAfter(q: Q, ui: UI, guild: CombinedGuild, channel: Channel) {
- await Util.withPotentialErrorWarnOnCancel(q, {
- taskFunc: async () => {
- if (ui.activeGuild === null || ui.activeGuild.id !== guild.id) return;
- if (ui.activeChannel === null || ui.activeChannel.id !== channel.id) return;
- const bottomPair = ui.getBottomMessagePair();
- if (bottomPair == null) return;
- const messages = await guild.fetchMessagesAfter(channel.id, bottomPair.message.id, Globals.MESSAGES_PER_REQUEST);
- if (messages && messages.length > 0) {
- await ui.addMessagesAfter(guild, channel, messages, bottomPair.message);
- } else {
- ui.messagesAtBottom = true;
- }
- },
- errorIndicatorAddFunc: async (errorIndicatorElement) => {
- await ui.addMessagesErrorIndicatorAfter(guild, channel, errorIndicatorElement);
- },
- errorContainer: q.$('#channel-feed'),
- errorClasses: [ 'after' ],
- errorMessage: 'Error loading newer messages'
- });
- }
}
diff --git a/src/client/webapp/elements/channel.tsx b/src/client/webapp/elements/channel.tsx
index fee8b47..c009622 100644
--- a/src/client/webapp/elements/channel.tsx
+++ b/src/client/webapp/elements/channel.tsx
@@ -5,7 +5,6 @@ import ElementsUtil from './require/elements-util';
import BaseElements from './require/base-elements';
import { Channel } from '../data-types';
import UI from '../ui';
-import Actions from '../actions';
import Q from '../q-module';
import CombinedGuild from '../guild-combined';
import ChannelOverlay from './overlays/overlay-channel';
@@ -22,7 +21,6 @@ export default function createChannel(document: Document, q: Q, ui: UI, guild: C
element.addEventListener('click', async () => {
if (element.classList.contains('active')) return;
await ui.setActiveChannel(guild, channel);
- await Actions.fetchAndUpdateMessagesRecent(q, ui, guild, channel);
q.$('#text-input').focus();
});
diff --git a/src/client/webapp/elements/events-infinite-scroll.ts b/src/client/webapp/elements/events-infinite-scroll.ts
deleted file mode 100644
index bb686a1..0000000
--- a/src/client/webapp/elements/events-infinite-scroll.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import * as electronRemote from '@electron/remote';
-const electronConsole = electronRemote.getGlobal('console') as Console;
-import Logger from '../../../logger/logger';
-const LOG = Logger.create(__filename, electronConsole);
-
-import Actions from "../actions";
-import Q from "../q-module";
-import UI from "../ui";
-
-export default function bindInfiniteScrollEvents(q: Q, ui: UI): void {
- // Update current channel messages as the pane is scrolled
- let loadingBefore = false;
- let loadingAfter = false;
- async function updateInfiniteScroll() {
- const scrollTop = q.$('#channel-feed-content-wrapper').scrollTop;
- const scrollHeight = q.$('#channel-feed-content-wrapper').scrollHeight;
- const clientHeight = q.$('#channel-feed-content-wrapper').clientHeight;
-
- // WARNING
- // There's likely an inconsistency between browsers on this so have fun when you're working
- // on the cross-platform implementation of this
- // scrollTop apparantly is negative for column-reverse divs (this actually kindof makes sense if you flip your head upside down)
- // have to reverse this
- // I expect this was a change with some version of chromium.
- // MDN documentation issue: https://github.com/mdn/content/issues/10968
-
- const distToTop = -(clientHeight - scrollHeight - scrollTop); // keep in mind scrollTop is negative >:]
- const distToBottom = -scrollTop;
-
- //LOG.debug('update infinite scroll', { scrollTop, scrollHeight, clientHeight, distToTop, distToBottom, loadingBefore, loadingAfter });
-
- if (ui.activeGuild === null) return;
- if (ui.activeChannel === null) return;
-
- if (!loadingBefore && !ui.messagesAtTop && distToTop < 600) { // Approaching the unloaded top of the page
- // Fetch more messages to add above
- LOG.debug('fetching messages before', { loadingBefore, messagesAtTop: ui.messagesAtTop, distToTop });
- loadingBefore = true;
- await Actions.fetchAndUpdateMessagesBefore(q, ui, ui.activeGuild, ui.activeChannel);
- loadingBefore = false;
- } else if (!loadingAfter && !ui.messagesAtBottom && distToBottom < 600) { // Approaching the unloaded bottom of the page
- // Fetch more messages to add below
- LOG.debug('fetching messages after', { loadingAfter, messagesAtBottom: ui.messagesAtBottom, distToBottom: distToBottom });
- loadingAfter = true;
- await Actions.fetchAndUpdateMessagesAfter(q, ui, ui.activeGuild, ui.activeChannel);
- loadingAfter = false;
- }
- }
-
- q.$('#channel-feed-content-wrapper').addEventListener('scroll', updateInfiniteScroll);
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (q.$('#channel-feed-content-wrapper') as any).updateInfiniteScroll = updateInfiniteScroll; // custom element function
-}
diff --git a/src/client/webapp/elements/message.tsx b/src/client/webapp/elements/message.tsx
deleted file mode 100644
index ce9051d..0000000
--- a/src/client/webapp/elements/message.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { Message } from '../data-types';
-import CombinedGuild from '../guild-combined';
-import Q from '../q-module';
-import createImageResourceMessage from './msg-img-res';
-import createImageResourceMessageContinued from './msg-img-res-cont';
-import createResourceMessage from './msg-res';
-import createResourceMessageContinued from './msg-res-cont';
-import createTextMessage from './msg-txt';
-import createTextMessageContinued from './msg-txt-cont';
-
-// TODO: This is probably best as a react class
-export default function createMessage(document: Document, q: Q, guild: CombinedGuild, message: Message, lastMessage: Message | null): Element {
- let element: Element;
- if (message.hasResource()) {
- if (message.isImageResource()) {
- if (message.isContinued(lastMessage)) {
- element = createImageResourceMessageContinued(document, q, guild, message);
- } else {
- element = createImageResourceMessage(document, q, guild, message);
- }
- } else {
- if (message.isContinued(lastMessage)) {
- element = createResourceMessageContinued(q, guild, message);
- } else {
- element = createResourceMessage(q, guild, message);
- }
- }
- } else {
- if (message.isContinued(lastMessage)) {
- element = createTextMessageContinued(q, guild, message);
- } else {
- element = createTextMessage(q, guild, message);
- }
- }
- return element;
-}
diff --git a/src/client/webapp/elements/msg-img-res-cont.tsx b/src/client/webapp/elements/msg-img-res-cont.tsx
deleted file mode 100644
index 5a0ddd2..0000000
--- a/src/client/webapp/elements/msg-img-res-cont.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import * as electronRemote from '@electron/remote';
-const electronConsole = electronRemote.getGlobal('console') as Console;
-import Logger from '../../../logger/logger';
-const LOG = Logger.create(__filename, electronConsole);
-
-import moment from 'moment';
-import * as FileType from 'file-type';
-
-import ElementsUtil from './require/elements-util.js';
-
-import { Message, ShouldNeverHappenError } from '../data-types';
-import Q from '../q-module';
-import createImageContextMenu from './context-menu-img';
-import CombinedGuild from '../guild-combined';
-
-import React from 'react';
-import ReactHelper from './require/react-helper';
-import ImageOverlay from './overlays/overlay-image';
-
-export default function createImageResourceMessageContinued(document: Document, q: Q, guild: CombinedGuild, message: Message): Element {
- if (!message.resourceId || !message.resourcePreviewId || !message.resourceName) {
- throw new ShouldNeverHappenError('Message is not a resource message');
- }
-
- const element = ReactHelper.createElementFromJSX(
-
-
{moment(message.sent).format('HH:mm')}
-
-
-
-
-
{ElementsUtil.parseMessageText(message.text ?? '')}
-
-
- );
-
- q.$$$(element, '.content.image').addEventListener('click', () => {
- ElementsUtil.presentReactOverlay(document,
-
- );
- });
- (async () => {
- try {
- const resource = await guild.fetchResource(message.resourcePreviewId as string);
- const src = await ElementsUtil.getImageBufferSrc(resource.data);
- (q.$$$(element, '.content.image img') as HTMLImageElement).src = src;
-
- const { mime, ext } = (await FileType.fromBuffer(resource.data)) ?? { mime: null, ext: null };
-
- if (mime === null || ext === null) throw new Error('unable to get mime/ext');
-
- q.$$$(element, '.content.image').addEventListener('contextmenu', (e) => {
- const contextMenu = createImageContextMenu(document, q, guild, message.resourceName as string, resource.data, mime as string, ext as string, true);
- document.body.appendChild(contextMenu);
- const relativeTo = { x: e.pageX, y: e.pageY };
- ElementsUtil.alignContextElement(contextMenu, relativeTo, { top: 'centerY', left: 'right' });
- });
- } catch (e) {
- LOG.error('error loading preview image', e);
- (q.$$$(element, '.content.image img') as HTMLImageElement).src = './img/error.png';
- }
- })();
-
- return element;
-}
diff --git a/src/client/webapp/elements/msg-img-res.tsx b/src/client/webapp/elements/msg-img-res.tsx
deleted file mode 100644
index 03d95bf..0000000
--- a/src/client/webapp/elements/msg-img-res.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import * as electronRemote from '@electron/remote';
-const electronConsole = electronRemote.getGlobal('console') as Console;
-import Logger from '../../../logger/logger';
-const LOG = Logger.create(__filename, electronConsole);
-
-import moment from 'moment';
-import * as FileType from 'file-type';
-
-import ElementsUtil from './require/elements-util.js';
-
-import { Message, Member, ShouldNeverHappenError } from '../data-types';
-import Q from '../q-module';
-import createImageContextMenu from './context-menu-img';
-import CombinedGuild from '../guild-combined';
-
-import React from 'react';
-import ReactHelper from './require/react-helper';
-import ImageOverlay from './overlays/overlay-image';
-
-export default function createImageResourceMessage(document: Document, q: Q, guild: CombinedGuild, message: Message): Element {
- if (!message.resourceId || !message.resourcePreviewId || !message.resourceName) {
- throw new ShouldNeverHappenError('Message is not a resource message');
- }
-
- let memberInfo: {
- roleColor: string | null,
- displayName: string,
- avatarResourceId: string | null
- };
- if (message.member instanceof Member) {
- memberInfo = {
- roleColor: message.member.roleColor,
- displayName: message.member.displayName,
- avatarResourceId: message.member.avatarResourceId
- };
- } else {
- memberInfo = {
- roleColor: null,
- displayName: 'Unknown Member',
- avatarResourceId: null
- };
- }
-
- const nameStyle = memberInfo.roleColor != null ? { color: memberInfo.roleColor } : {};
- const element = ReactHelper.createElementFromJSX(
-
-
-
-
-
-
-
{memberInfo.displayName}
-
{moment(message.sent).calendar(ElementsUtil.calendarFormats)}
-
-
-
-
-
{ElementsUtil.parseMessageText(message.text ?? '')}
-
-
- );
-
- q.$$$(element, '.content.image').addEventListener('click', (e) => {
- ElementsUtil.presentReactOverlay(document,
-
- );
- //document.body.appendChild(createImageOverlay(document, q, guild, message.resourceId as string, message.resourceName as string));
- });
- (async () => {
- (q.$$$(element, '.member-avatar img') as HTMLImageElement).src =
- await ElementsUtil.getImageSrcFromResourceFailSoftly(guild, memberInfo.avatarResourceId);
- })();
- (async () => {
- try {
- const resource = await guild.fetchResource(message.resourcePreviewId as string);
- const src = await ElementsUtil.getImageBufferSrc(resource.data);
- (q.$$$(element, '.content.image img') as HTMLImageElement).src = src;
-
- const { mime, ext } = (await FileType.fromBuffer(resource.data)) ?? { mime: null, ext: null };
- if (mime === null || ext === null) throw new Error('unable to get mime/ext');
-
- q.$$$(element, '.content.image').addEventListener('contextmenu', (e) => {
- const contextMenu = createImageContextMenu(document, q, guild, message.resourceName as string, resource.data, mime as string, ext as string, true);
- document.body.appendChild(contextMenu);
- const relativeTo = { x: e.pageX, y: e.pageY };
- ElementsUtil.alignContextElement(contextMenu, relativeTo, { top: 'centerY', left: 'right' });
- });
- } catch (e) {
- LOG.error('error loading preview image', e);
- (q.$$$(element, '.content.image img') as HTMLImageElement).src = './img/error.png';
- }
- })();
- return element;
-}
diff --git a/src/client/webapp/elements/msg-res-cont.tsx b/src/client/webapp/elements/msg-res-cont.tsx
deleted file mode 100644
index 72eb150..0000000
--- a/src/client/webapp/elements/msg-res-cont.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import moment from 'moment';
-import { Message, ShouldNeverHappenError } from '../data-types';
-import CombinedGuild from "../guild-combined";
-import Q from "../q-module";
-
-import ElementsUtil from "./require/elements-util";
-
-import React from 'react';
-import ReactHelper from './require/react-helper';
-
-export default function createResourceMessageContinued(q: Q, guild: CombinedGuild, message: Message): Element {
- if (!message.resourceId || !message.resourceName) {
- throw new ShouldNeverHappenError('Message is not a resource message');
- }
-
- const element = ReactHelper.createElementFromJSX(
-
-
{moment(message.sent).format('HH:mm')}
-
-
-
-
-
{message.resourceName}
-
Click to Download
-
-
-
{ElementsUtil.parseMessageText(message.text ?? '')}
-
-
- );
-
- q.$$$(element, '.resource').addEventListener('click', ElementsUtil.createDownloadListener({
- guild: guild, resourceId: message.resourceId, resourceName: message.resourceName,
- downloadStartFunc: () => {
- q.$$$(element, '.resource .download-status').innerText = 'Downloading...';
- },
- downloadFailFunc: async () => {
- q.$$$(element, '.resource .download-status').innerText = 'Error Downloading. Click to Try Again';
- await ElementsUtil.shakeElement(q.$$$(element, '.resource .download-status'), 400);
- },
- writeStartFunc: () => {
- q.$$$(element, '.resource .download-status').innerText = 'Writing...';
- },
- writeFailFunc: async () => {
- q.$$$(element, '.resource .download-status').innerText = 'Error Writing. Click to Try Again';
- await ElementsUtil.shakeElement(q.$$$(element, '.resource .download-status'), 400);
- },
- successFunc: (downloadPath) => {
- q.$$$(element, '.resource .download-status').innerText = 'Click to Open in Explorer';
- }
- }));
- return element;
-}
diff --git a/src/client/webapp/elements/msg-res.tsx b/src/client/webapp/elements/msg-res.tsx
deleted file mode 100644
index 4582de6..0000000
--- a/src/client/webapp/elements/msg-res.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import moment from 'moment';
-import { Message, Member, ShouldNeverHappenError } from '../data-types';
-import CombinedGuild from '../guild-combined';
-import Q from '../q-module';
-
-import ElementsUtil from './require/elements-util';
-
-import React from 'react';
-import ReactHelper from './require/react-helper';
-
-export default function createResourceMessage(q: Q, guild: CombinedGuild, message: Message): Element {
- if (!message.resourceId || !message.resourceName) {
- throw new ShouldNeverHappenError('Message is not a resource message');
- }
-
- let memberInfo: {
- roleColor: string | null,
- displayName: string,
- avatarResourceId: string | null
- };
- if (message.member instanceof Member) {
- memberInfo = {
- roleColor: message.member.roleColor,
- displayName: message.member.displayName,
- avatarResourceId: message.member.avatarResourceId
- };
- } else {
- memberInfo = {
- roleColor: null,
- displayName: 'Unknown Member',
- avatarResourceId: null
- };
- }
-
- const nameStyle = memberInfo.roleColor != null ? { color: memberInfo.roleColor } : {};
- const element = ReactHelper.createElementFromJSX(
-
-
-
-
-
-
-
{memberInfo.displayName}
-
{moment(message.sent).calendar(ElementsUtil.calendarFormats)}
-
-
-
-
-
{message.resourceName}
-
Click to Download
-
-
-
{ElementsUtil.parseMessageText(message.text ?? '')}
-
-
- );
-
- q.$$$(element, '.resource').addEventListener('click', ElementsUtil.createDownloadListener({
- guild: guild, resourceId: message.resourceId, resourceName: message.resourceName,
- downloadStartFunc: () => {
- q.$$$(element, '.resource .download-status').innerText = 'Downloading...';
- },
- downloadFailFunc: async () => {
- q.$$$(element, '.resource .download-status').innerText = 'Error Downloading. Click to Try Again';
- await ElementsUtil.shakeElement(q.$$$(element, '.resource .download-status'), 400);
- },
- writeStartFunc: () => {
- q.$$$(element, '.resource .download-status').innerText = 'Writing...';
- },
- writeFailFunc: async () => {
- q.$$$(element, '.resource .download-status').innerText = 'Error Writing. Click to Try Again';
- await ElementsUtil.shakeElement(q.$$$(element, '.resource .download-status'), 400);
- },
- successFunc: (_downloadPath: string) => {
- q.$$$(element, '.resource .download-status').innerText = 'Click to Open in Explorer';
- }
- }));
- (async () => {
- (q.$$$(element, '.member-avatar img') as HTMLImageElement).src =
- await ElementsUtil.getImageSrcFromResourceFailSoftly(guild, memberInfo.avatarResourceId);
- })();
- return element;
-}
diff --git a/src/client/webapp/elements/msg-txt-cont.tsx b/src/client/webapp/elements/msg-txt-cont.tsx
deleted file mode 100644
index 0d01840..0000000
--- a/src/client/webapp/elements/msg-txt-cont.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import moment from 'moment';
-import { Message } from '../data-types';
-import CombinedGuild from '../guild-combined';
-import Q from '../q-module.js';
-
-import ElementsUtil from './require/elements-util.js';
-
-import React from 'react';
-import ReactHelper from './require/react-helper';
-
-export default function createTextMessageContinued(q: Q, guild: CombinedGuild, message: Message): Element {
- return ReactHelper.createElementFromJSX(
-
-
{moment(message.sent).format('HH:mm')}
-
-
{ElementsUtil.parseMessageText(message.text ?? '')}
-
-
- );
-}
diff --git a/src/client/webapp/elements/msg-txt.tsx b/src/client/webapp/elements/msg-txt.tsx
deleted file mode 100644
index 57a9594..0000000
--- a/src/client/webapp/elements/msg-txt.tsx
+++ /dev/null
@@ -1,60 +0,0 @@
-import moment from 'moment';
-
-import ElementsUtil from './require/elements-util';
-
-import { Message, Member, IDummyTextMessage } from '../data-types';
-import Q from '../q-module';
-import CombinedGuild from '../guild-combined';
-
-import React from 'react';
-import ReactHelper from './require/react-helper';
-
-export default function createTextMessage(q: Q, guild: CombinedGuild, message: Message | IDummyTextMessage): Element {
- let memberInfo: {
- roleColor: string | null,
- displayName: string,
- avatarResourceId: string | null
- };
- if (message instanceof Message) {
- if (message.member instanceof Member) {
- memberInfo = {
- roleColor: message.member.roleColor,
- displayName: message.member.displayName,
- avatarResourceId: message.member.avatarResourceId
- };
- } else {
- memberInfo = {
- roleColor: null,
- displayName: 'Unknown Member',
- avatarResourceId: null
- };
- }
- } else {
- memberInfo = {
- roleColor: null,
- displayName: message.member.displayName,
- avatarResourceId: message.member.avatarResourceId
- };
- }
-
- const nameStyle = memberInfo.roleColor != null ? { color: memberInfo.roleColor } : {};
- const element = ReactHelper.createElementFromJSX(
-
-
-
-
-
-
-
{memberInfo.displayName}
-
{moment(message.sent).calendar(ElementsUtil.calendarFormats)}
-
-
{ElementsUtil.parseMessageText(message.text ?? '')}
-
-
- );
- (async () => {
- (q.$$$(element, '.member-avatar img') as HTMLImageElement).src =
- await ElementsUtil.getImageSrcFromResourceFailSoftly(guild, memberInfo.avatarResourceId);
- })();
- return element;
-}
diff --git a/src/client/webapp/preload.ts b/src/client/webapp/preload.ts
index ebbacb9..6c5b920 100644
--- a/src/client/webapp/preload.ts
+++ b/src/client/webapp/preload.ts
@@ -14,11 +14,10 @@ import Globals from './globals';
import UI from './ui';
import Actions from './actions';
-import { Changes, Channel, ConnectionInfo, GuildMetadata, Member, Message, Resource, Token } from './data-types';
+import { Changes, Channel, ConnectionInfo, GuildMetadata, Member, Resource, Token } from './data-types';
import Q from './q-module';
import bindWindowButtonEvents from './elements/events-window-buttons';
import bindTextInputEvents from './elements/events-text-input';
-import bindInfiniteScrollEvents from './elements/events-infinite-scroll';
import bindConnectionEvents from './elements/events-connection';
import bindAddGuildTitleEvents from './elements/events-guild-title';
import bindAddGuildEvents from './elements/events-add-guild';
@@ -27,7 +26,7 @@ import MessageRAMCache from './message-ram-cache';
import ResourceRAMCache from './resource-ram-cache';
import CombinedGuild from './guild-combined';
import { AutoVerifierChangesType } from './auto-verifier';
-import { IDQuery, PartialMessageListQuery } from './auto-verifier-with-args';
+import { IDQuery } from './auto-verifier-with-args';
LOG.silly('modules loaded');
@@ -75,7 +74,6 @@ window.addEventListener('DOMContentLoaded', () => {
bindWindowButtonEvents(q);
bindTextInputEvents(document, q, ui);
- bindInfiniteScrollEvents(q, ui);
bindConnectionEvents(document, q, ui);
bindAddGuildTitleEvents(document, q, ui);
bindAddGuildEvents(document, q, ui, guildsManager);
@@ -99,20 +97,7 @@ window.addEventListener('DOMContentLoaded', () => {
(async () => { // refresh channels list
await Actions.fetchAndUpdateChannels(q, ui, guild);
})();
- (async () => { // refresh current channel messages
- if (ui.activeChannel === null) return;
- if (ui.messagePairs.size == 0) {
- // fetch messages again since there are no messages yet
- await Actions.fetchAndUpdateMessagesRecent(q, ui, guild, ui.activeChannel);
- } else {
- // If we already have messages, just update the infinite scroll.
- // NOTE: this will not add/remove new/deleted messages
- ui.messagesAtTop = false;
- ui.messagesAtBottom = false;
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- (q.$('#channel-feed-content-wrapper') as any).updateInfiniteScroll();
- }
- })();
+ // TODO: React set hasMessagesAbove and hasMessagesBelow when re-verified
});
guildsManager.on('disconnect', (guild: CombinedGuild) => {
@@ -166,31 +151,7 @@ window.addEventListener('DOMContentLoaded', () => {
await ui.addChannels(guild, channels);
});
- guildsManager.on('remove-messages', async (guild: CombinedGuild, messages: Message[]) => {
- LOG.debug(messages.length + ' deleted messages');
- await ui.deleteMessages(guild, messages);
- });
-
- guildsManager.on('update-messages', async (guild: CombinedGuild, updatedMessages: Message[]) => {
- LOG.debug(updatedMessages.length + ' updated messages');
- await ui.updateMessages(guild, updatedMessages);
- });
-
- guildsManager.on('new-messages', async (guild: CombinedGuild, messages: Message[]) => {
- if (ui.activeGuild === null || ui.activeGuild.id !== guild.id) return;
- for (const message of messages) {
- if (ui.activeChannel === null || ui.activeChannel.id !== message.channel.id) return;
- if (ui.messagesAtBottom) {
- // add the message to the bottom of the message feed
- await ui.addMessages(guild, [ message ]);
- ui.jumpMessagesToBottom();
- } else if (message.member.id == guild.memberId) {
- // this set of messages will include the new messageguildId
- LOG.debug('not at bottom, jumping down since message was sent by the current user');
- await Actions.fetchAndUpdateMessagesRecent(q, ui, guild, message.channel);
- }
- }
- });
+ // TODO: React jump messages to bottom when the current user sent a message
// Conflict Events
@@ -221,13 +182,6 @@ window.addEventListener('DOMContentLoaded', () => {
}
});
- guildsManager.on('conflict-messages', async (guild: CombinedGuild, query: PartialMessageListQuery, changesType: AutoVerifierChangesType, changes: Changes) => {
- //LOG.debug('messages conflict', { changes });
- if (changes.deleted.length > 0) await ui.deleteMessages(guild, changes.deleted);
- if (changes.added.length > 0) await ui.addMessages(guild, changes.added);
- if (changes.updated.length > 0) await ui.updateMessages(guild, changes.updated.map(pair => pair.newDataPoint));
- });
-
guildsManager.on('conflict-tokens', async (guild: CombinedGuild, changesType: AutoVerifierChangesType, changes: Changes) => {
LOG.debug('tokens conflict', { changes });
// TODO
diff --git a/src/client/webapp/styles/messages.scss b/src/client/webapp/styles/messages.scss
deleted file mode 100644
index 1b46c5c..0000000
--- a/src/client/webapp/styles/messages.scss
+++ /dev/null
@@ -1,144 +0,0 @@
-@import "theme.scss";
-
-.message {
- display: flex;
- padding: 4px 16px;
-
- &.continued {
- margin-top: -4px;
-
- .timestamp {
- flex: none; /* >:| NOT GONNA SHRINK BOI */
- margin: 0;
- width: 40px;
- visibility: hidden;
- }
-
- &:hover .timestamp {
- visibility: visible;
- }
- }
-
- .member-avatar img {
- width: 40px;
- height: 40px;
- border-radius: 20px;
- cursor: pointer;
- user-select: none;
- }
-
- .right {
- flex: 1;
- display: flex;
- flex-flow: column;
- align-items: flex-start;
- margin-left: 16px; /* putting the margin here rather than on the avatar makes selecting better */
- user-select: text;
- }
-
- .header {
- display: flex;
- align-items: baseline;
- }
-
- .member-name {
- font-size: 16px;
- line-height: 22px;
- font-weight: 500;
- color: $header-primary;
- cursor: pointer;
- white-space: nowrap;
- }
-
- .member-name:hover {
- text-decoration: underline;
- }
-
- .timestamp {
- font-size: 12px;
- line-height: 22px;
- font-weight: 500;
- color: $text-muted;
- margin-left: 4px;
- white-space: nowrap;
- }
-
- .content {
- font-size: 16px;
- color: $text-normal;
- overflow-wrap: anywhere;
-
- &.text {
- white-space: pre-wrap;
-
- .bold {
- font-weight: 600;
- color: $header-primary; /* a bit brighter for some more emphasis */
- }
-
- .underline {
- text-decoration: underline;
- }
-
- .italic {
- font-style: italic;
- }
- }
-
- &.image {
- /* Center the loading icon */
- display: flex;
- justify-content: center;
- align-items: center;
- border-radius: 8px;
- background-color: $background-secondary-alt;
- cursor: pointer;
- overflow: hidden;
-
- img {
- max-width: 400px;
- max-height: 300px;
- border-radius: 8px;
- }
- }
-
- /* TODO: ellipse the overflow */
- &.resource {
- min-width: 300px;
- max-width: 400px;
- display: flex;
- align-items: center;
- background-color: $background-secondary-alt;
- padding: 8px;
- border-radius: 8px;
- cursor: pointer;
-
- &:hover .download-status {
- text-decoration: underline;
- }
-
- > :not(:last-child) {
- margin-right: 8px;
- }
-
- .icon {
- width: 26px;
- height: 26px;
- -webkit-user-select: none;
- user-select: none;
- }
-
- .filename {
- color: $text-link;
- }
-
- .download-status {
- font-size: 12px;
- font-weight: 500;
- color: $text-muted;
- -webkit-user-select: none;
- user-select: none;
- }
- }
- }
-}
diff --git a/src/client/webapp/styles/styles.scss b/src/client/webapp/styles/styles.scss
index d89685b..5674025 100644
--- a/src/client/webapp/styles/styles.scss
+++ b/src/client/webapp/styles/styles.scss
@@ -16,7 +16,6 @@
@import "error-indicator.scss";
@import "general.scss";
@import "members.scss";
-@import "messages.scss";
@import "overlays.scss";
@import "scrollbars.scss";
@import "guild-list.scss";
diff --git a/src/client/webapp/ui.ts b/src/client/webapp/ui.ts
index f7cf706..e119a1a 100644
--- a/src/client/webapp/ui.ts
+++ b/src/client/webapp/ui.ts
@@ -7,15 +7,12 @@ import ConcurrentQueue from '../../concurrent-queue/concurrent-queue';
import ElementsUtil from './elements/require/elements-util';
-import Globals from './globals';
-import Util from './util';
import CombinedGuild from './guild-combined';
import { Message, Channel, ConnectionInfo, ShouldNeverHappenError } from './data-types';
import Q from './q-module';
import createGuildListGuild from './elements/guild-list-guild';
import createChannel from './elements/channel';
import GuildsManager from './guilds-manager';
-import createMessage from './elements/message';
import { mountGuildChannelComponents, mountGuildComponents } from './elements/mounts';
interface SetMessageProps {
@@ -321,308 +318,4 @@ export default class UI {
this.q.$('#channel-list').appendChild(errorIndicatorElement);
});
}
-
- public getTopMessagePair(): { message: Message, element: Element } | null {
- const element = this.q.$$('#channel-feed .message')[0];
- return element && this.messagePairs.get(element.getAttribute('data-id')) || null;
- }
-
- public getBottomMessagePair(): { message: Message, element: Element } | null {
- const messageElements = this.q.$$('#channel-feed .message');
- const element = messageElements[messageElements.length - 1];
- return element && this.messagePairs.get(element.getAttribute('data-id')) || null;
- }
-
- public async addMessages(guild: CombinedGuild, messages: Message[]) {
- const channelIds = new Set(messages.map(message => message.channel.id));
- for (const channelId of channelIds) {
- let channelMessages = messages.filter(message => message.channel.id === channelId);
- channelMessages = channelMessages.sort(Message.sortOrder);
-
- // No Previous Messages is an easy case
- if (this.messagePairs.size === 0) {
- await this.addMessagesBefore(guild, { id: channelId }, channelMessages, null);
- continue;
- }
-
- const topMessagePair = this.getTopMessagePair() as { message: Message, element: HTMLElement };
- const bottomMessagePair = this.getBottomMessagePair() as { message: Message, element: HTMLElement };
-
- const aboveMessages = messages.filter(message => message.sortsBefore(topMessagePair.message));
- const belowMessages = messages.filter(message => message.sortsAfter(bottomMessagePair.message));
- const betweenMessages = messages.filter(message => !message.sortsBefore(topMessagePair.message) && !message.sortsAfter(bottomMessagePair.message));
-
- if (aboveMessages.length > 0) await this.addMessagesBefore(guild, { id: channelId }, aboveMessages, topMessagePair.message);
- if (belowMessages.length > 0) await this.addMessagesAfter(guild, { id: channelId }, belowMessages, bottomMessagePair.message);
- if (betweenMessages.length > 0) await this.addMessagesBetween(guild, { id: channelId }, betweenMessages, topMessagePair.element, bottomMessagePair.element);
- }
- }
-
- public async addMessagesBefore(guild: CombinedGuild, channel: Channel | { id: string }, messages: Message[], prevTopMessage: Message | null): Promise {
- this.lockMessages(guild, channel, () => {
- if (prevTopMessage && this.getTopMessagePair()?.message.id !== prevTopMessage.id) return;
-
- this.messagesAtTop = false;
-
- // There are a maximum of MAX_CURRENT_MESSAGES messages in the channel at a time
- // Remove messages at the bottom to make space for new messages
- if (this.messagePairs.size + messages.length > Globals.MAX_CURRENT_MESSAGES) {
- const currentMessageElements = this.q.$$('#channel-feed .message');
- if (this.messagePairs.size !== currentMessageElements.length) throw new Error(`message lengths disjointed, ${this.messagePairs.size} != ${currentMessageElements.length}`); // sanity check
- const toRemove = currentMessageElements.slice(-(this.messagePairs.size + messages.length - Globals.MAX_CURRENT_MESSAGES));
- for (const element of toRemove) {
- const id = element.getAttribute('data-id');
- this.messagePairs.delete(id);
- element.parentElement?.removeChild(element);
- }
- this.messagesAtBottom = false;
- }
-
- // Relies on error indicators being in top-to-bottom order in the list
- Util.removeErrorIndicators(this.q, this.q.$('#channel-feed'), [ 'recent' ]);
- Util.removeErrorIndicators(this.q, this.q.$('#channel-feed'), [ 'before' ]);
-
- // Keep track of the top messages before we add new messages
- const prevTopPair = this.getTopMessagePair();
-
- // Add the messages to the channel feed
- // Using reverse order so that resources are loaded from bottom to top
- // and the client starts at the bottom
- for (let i = messages.length - 1; i >= 0; --i) {
- const message = messages[i] as Message;
- const priorMessage = messages[i - 1] || null;
- const 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
- const newPrevTopElement = createMessage(this.document, this.q, guild, prevTopPair.message, messages[messages.length - 1] as Message);
- prevTopPair.element.parentElement?.replaceChild(newPrevTopElement, prevTopPair.element);
- this.messagePairs.set(prevTopPair.message.id, { message: prevTopPair.message, element: newPrevTopElement });
- }
- });
- }
-
- public async addMessagesAfter(guild: CombinedGuild, channel: Channel | { id: string }, messages: Message[], prevBottomMessage: Message | null): Promise {
- await this.lockMessages(guild, channel, () => {
- if (prevBottomMessage && this.getBottomMessagePair()?.message.id !== prevBottomMessage.id) return;
-
- this.messagesAtBottom = false;
-
- // There are a maximum of MAX_CURRENT_MESSAGES messages in the channel at a time
- // Remove messages at the top to make space for new messages
- if (this.messagePairs.size + messages.length > Globals.MAX_CURRENT_MESSAGES) {
- const currentMessageElements = this.q.$$('#channel-feed .message');
- if (this.messagePairs.size !== currentMessageElements.length) throw new Error('message lengths disjointed'); // sanity check
- const toRemove = currentMessageElements.slice(0, this.messagePairs.size + messages.length - Globals.MAX_CURRENT_MESSAGES);
- for (const element of toRemove) {
- const id = element.getAttribute('data-id');
- this.messagePairs.delete(id);
- element.parentElement?.removeChild(element);
- }
- this.messagesAtTop = false;
- }
-
- Util.removeErrorIndicators(this.q, this.q.$('#channel-feed'), [ 'recent' ]);
- Util.removeErrorIndicators(this.q, this.q.$('#channel-feed'), [ 'after' ]);
-
- // Get the bottom message to use as the prior message to the first new message
- const prevBottomPair = this.getBottomMessagePair();
-
- // Add new messages to the bottom of the channel feed
- // Using forward-order so that resources are loaded from oldest messages to newest messages
- // since we are expecting the user to scroll down (to newer messages)
- for (let i = 0; i < messages.length; ++i) { // add in-order since we will be scrolling from oldest to newest
- const message = messages[i] as Message;
- const priorMessage = messages[i - 1] || (prevBottomPair && prevBottomPair.message);
- const element = createMessage(this.document, this.q, guild, message, priorMessage);
- this.messagePairs.set(message.id, { message: message, element: element });
- this.q.$('#channel-feed').appendChild(element);
- }
- });
- }
-
- // TODO: use topMessage, bottomMessage / topMessageId, bottomMessageId instead?
- private async addMessagesBetween(guild: CombinedGuild, channel: Channel | { id: string }, messages: Message[], topElement: HTMLElement, bottomElement: HTMLElement): Promise {
- 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) as HTMLElement | null)?.innerText });
- throw new Error('invalid messages between');
- }
-
- if (this.messagePairs.size + messages.length > Globals.MAX_CURRENT_MESSAGES) {
- const currentMessageElements = this.q.$$('#channel-feed .message');
- if (this.messagePairs.size !== currentMessageElements.length) throw new Error('message lengths disjointed'); // sanity check
- const totalToRemove = this.messagePairs.size + messages.length - Globals.MAX_CURRENT_MESSAGES
- const toRemove: HTMLElement[] = [];
- // figure out if the elements are getting added above or below the scroll box.
- // NOT TESTED YET: elements added within the scroll box are assumed to make the box resize downward
- const above = bottomElement.offsetTop > this.q.$('#channel-feed-wrapper').scrollTop;
- if (above) {
- // remove elements at the top first
- for (const messageElement of currentMessageElements) {
- if (toRemove.length == totalToRemove) {
- break;
- }
- if (messageElement.getAttribute('data-id') == topElement.getAttribute('data-id')) {
- break;
- }
- toRemove.push(messageElement);
- }
- // remove elements at the bottom if still needed
- for (const messageElement of currentMessageElements.reverse()) {
- if (toRemove.length == totalToRemove) {
- break;
- }
- if (messageElement.getAttribute('data-id') == bottomElement.getAttribute('data-id')) {
- break;
- }
- toRemove.push(messageElement);
- }
- } else {
- // remove elements at the bottom first
- for (const messageElement of currentMessageElements.reverse()) {
- if (toRemove.length == totalToRemove) {
- break;
- }
- if (messageElement.getAttribute('data-id') == topElement.getAttribute('data-id')) {
- break;
- }
- toRemove.push(messageElement);
- }
- // remove elements at the top if still needed
- for (const messageElement of currentMessageElements.reverse()) {
- if (toRemove.length == totalToRemove) {
- break;
- }
- if (messageElement.getAttribute('data-id') == bottomElement.getAttribute('data-id')) {
- break;
- }
- toRemove.push(messageElement);
- }
- }
- for (const element of toRemove) {
- const id = element.getAttribute('data-id');
- if (!id) continue;
- this.messagePairs.delete(id);
- element.parentElement?.removeChild(element);
- }
- }
-
- const topElementId = topElement.getAttribute('data-id');
- const topMessage = this.messagePairs.get(topElementId)?.message;
- for (let i = 0; i < messages.length; ++i) {
- const message = messages[i] as Message;
- const priorMessage = messages[i - 1] || topMessage || null;
- const 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);
- }
-
- if (messages.length > 0) {
- // update the bottom element since the element above it changed
- const bottomMessage = this.messagePairs.get(bottomElement.getAttribute('data-id'))?.message;
- if (!bottomMessage) throw new ShouldNeverHappenError('could not find bottom message');
- const newBottomElement = createMessage(this.document, this.q, guild, bottomMessage, messages[messages.length - 1] as Message);
- bottomElement.parentElement?.replaceChild(newBottomElement, bottomElement);
- this.messagePairs.set(bottomMessage.id, { element: newBottomElement, message: bottomMessage });
- }
- });
- }
-
- public async setMessages(guild: CombinedGuild, channel: Channel | { id: string }, messages: Message[], props: SetMessageProps): Promise {
- const { atTop, atBottom } = props;
- await this.lockMessages(guild, channel, () => {
- this.messagesAtTop = atTop;
- this.messagesAtBottom = atBottom;
-
- Util.removeErrorIndicators(this.q, this.q.$('#channel-feed'), [ 'recent' ]);
- Util.removeErrorIndicators(this.q, this.q.$('#channel-feed'), [ 'before' ]);
- Util.removeErrorIndicators(this.q, this.q.$('#channel-feed'), [ 'after' ]);
-
- this.messagePairsGuild = guild;
- this.messagePairsChannel = channel;
-
- this.messagePairs.clear();
- Q.clearChildren(this.q.$('#channel-feed'));
-
- // Add the messages to the channel feed
- // Using reverse order so that resources are loaded from bottom to top
- // and the client starts at the bottom
- for (let i = messages.length - 1; i >= 0; --i) {
- const message = messages[i] as Message;
- const priorMessage = messages[i - 1] || null;
- const element = createMessage(this.document, this.q, guild, message, priorMessage);
- this.messagePairs.set(message.id, { message: message, element: element });
- this.q.$('#channel-feed').prepend(element);
- }
-
- this.jumpMessagesToBottom();
- });
- }
-
- public jumpMessagesToBottom(): void {
- this.q.$('#channel-feed-content-wrapper').scrollTop = this.q.$('#channel-feed-content-wrapper').scrollHeight;
- this.messagesAtBottom = true;
- }
-
- public async deleteMessages(guild: CombinedGuild, messages: Message[]) {
- const channelIds = new Set(messages.map(message => message.channel.id));
- for (const channelId of channelIds) {
- const channelMessages = messages.filter(message => message.channel.id === channelId);
- await this.lockMessages(guild, { id: channelId }, () => {
- for (const message of channelMessages) {
- if (this.messagePairs.has(message.id)) {
- const messagePair = this.messagePairs.get(message.id) as { message: Message, element: HTMLElement };
- messagePair.element.parentElement?.removeChild(messagePair.element);
- // TODO: we should be updating messages sent below this message
- // however, these events should be relatively rare so that's for the future
- this.messagePairs.delete(message.id);
- }
- }
- });
- }
- }
-
- public async updateMessages(guild: CombinedGuild, updatedMessages: Message[]): Promise {
- const channelIds = new Set(updatedMessages.map(message => message.channel.id));
- for (const channelId of channelIds) {
- const channelMessages = updatedMessages.filter(message => message.channel.id === channelId);
- await this.lockMessages(guild, { id: channelId }, () => {
- for (const message of channelMessages) {
- if (this.messagePairs.has(message.id)) {
- const oldElement = (this.messagePairs.get(message.id) as { message: Message, element: HTMLElement }).element;
- const prevElement = Q.previousElement(oldElement);
- const prevMessage = prevElement && (this.messagePairs.get(prevElement.getAttribute('data-id')) as { message: Message, element: HTMLElement }).message;
- const newElement = createMessage(this.document, this.q, guild, message, 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
- this.messagePairs.set(message.id, { message: message, element: newElement });
- }
- }
- });
- }
- }
-
- public async addMessagesErrorIndicatorBefore(guild: CombinedGuild, channel: Channel, errorIndicatorElement: Element): Promise {
- await this.lockMessages(guild, channel, () => {
- this.q.$('#channel-feed').prepend(errorIndicatorElement);
- });
- }
-
- public async addMessagesErrorIndicatorAfter(guild: CombinedGuild, channel: Channel, errorIndicatorElement: Element): Promise {
- await this.lockMessages(guild, channel, () => {
- this.q.$('#channel-feed').appendChild(errorIndicatorElement);
- });
- }
-
- public async setMessagesErrorIndicator(guild: CombinedGuild, channel: Channel | { id: string }, errorIndicatorElement: Element): Promise {
- await this.lockMessages(guild, channel, () => {
- Q.clearChildren(this.q.$('#channel-feed'));
- this.q.$('#channel-feed').appendChild(errorIndicatorElement);
- });
- }
}