diff --git a/src/client/webapp/actions.ts b/src/client/webapp/actions.ts index 1a30cdf..21d81eb 100644 --- a/src/client/webapp/actions.ts +++ b/src/client/webapp/actions.ts @@ -22,21 +22,6 @@ export default class Actions { 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, guild: CombinedGuild) { - await Util.withPotentialErrorWarnOnCancel(q, { - taskFunc: async () => { - if (ui.activeGuild === null || ui.activeGuild.id !== guild.id) return; - const members = await guild.fetchMembers(); - await ui.setMembers(guild, members); - }, - errorIndicatorAddFunc: async (errorIndicatorElement) => { - await ui.setMembersErrorIndicator(guild, errorIndicatorElement); - }, - errorContainer: q.$('#guild-members'), - errorMessage: 'Error loading members' - }); - } static async fetchAndUpdateChannels(q: Q, ui: UI, guild: CombinedGuild) { await Util.withPotentialErrorWarnOnCancel(q, { diff --git a/src/client/webapp/data-types.ts b/src/client/webapp/data-types.ts index e81c0fc..8fb3b00 100644 --- a/src/client/webapp/data-types.ts +++ b/src/client/webapp/data-types.ts @@ -59,6 +59,38 @@ export class Member implements WithEquals { this.privileges.join(',') === other.privileges.join(',') // TODO: sort these first ); } + + // Ordered by online/offline + // Ordered by role priority + // Ordered by status + // Ordered by display name + // Ordered by id + public static sortForList(a: Member, b: Member) { + const statusOrder = new Map(); + statusOrder.set('online', 0); + statusOrder.set('away', 1); + statusOrder.set('busy', 2); + statusOrder.set('offline', 3); + statusOrder.set('invisible', 3); // this status is only shown in the case of the current member. + statusOrder.set('unknown', 100); + + const onlineCmp = (a.status === 'offline' ? 1 : 0) - (b.status === 'offline' ? 1 : 0); + if (onlineCmp !== 0) return onlineCmp; + + const priorityCmp = (a.rolePriority ?? 100) - (b.rolePriority ?? 100); + if (priorityCmp !== 0) return priorityCmp; + + const statusCmp = (statusOrder.get(a.status) ?? 100) - (statusOrder.get(b.status) ?? 100); + if (statusCmp !== 0) return statusCmp; + + const nameCmp = strcmp(a.displayName, b.displayName); + if (nameCmp !== 0) return nameCmp; + + const idCmp = strcmp(a.id, b.id); + if (idCmp !== 0) return idCmp; + + return 0; + } } export class Channel implements WithEquals { diff --git a/src/client/webapp/elements/displays/display-guild-invites.tsx b/src/client/webapp/elements/displays/display-guild-invites.tsx index 936f3cf..9115a9a 100644 --- a/src/client/webapp/elements/displays/display-guild-invites.tsx +++ b/src/client/webapp/elements/displays/display-guild-invites.tsx @@ -3,7 +3,7 @@ const electronConsole = electronRemote.getGlobal('console') as Console; import Logger from '../../../../logger/logger'; const LOG = Logger.create(__filename, electronConsole); -import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; +import React, { FC, useEffect, useMemo, useState } from 'react'; import CombinedGuild from '../../guild-combined'; import Display from '../components/display'; import InvitePreview from '../components/invite-preview'; @@ -29,15 +29,16 @@ const GuildInvitesDisplay: FC = (props: GuildInvitesDi const [ tokens, tokensError ] = GuildSubscriptions.useTokensSubscription(guild); const [ guildMeta, guildMetaError ] = GuildSubscriptions.useGuildMetadataSubscription(guild); + + const [ expiresFromNow, setExpiresFromNow ] = useState(moment.duration(1, 'day')); + const [ expiresFromNowText, setExpiresFromNowText ] = useState('1 day'); + const [ iconSrc ] = ReactHelper.useOneTimeAsyncAction( async () => await ElementsUtil.getImageSrcFromResourceFailSoftly(guild, guildMeta?.iconResourceId ?? null), './img/loading.svg', [ guild, guildMeta?.iconResourceId ] ); - const [ expiresFromNow, setExpiresFromNow ] = useState(moment.duration(1, 'day')); - const [ expiresFromNowText, setExpiresFromNowText ] = useState('1 day'); - useEffect(() => { if (expiresFromNowText === 'never') { setExpiresFromNow(null); @@ -48,24 +49,29 @@ const GuildInvitesDisplay: FC = (props: GuildInvitesDi } }, [ expiresFromNowText ]); - const [ tokenResult, tokenError, tokenButtonText, tokenButtonShaking, tokenButtonCallback ] = ReactHelper.useAsyncButtonSubscription( - async () => await guild.requestDoCreateToken(expiresFromNowText === 'never' ? null : expiresFromNowText), - { start: 'Create Token', pending: 'Creating...', error: 'Try Again', done: 'Create Token' }, - [ guild, expiresFromNowText ] + const [ createTokenFunc, tokenButtonText, tokenButtonShaking, createTokenFailMessage ] = ReactHelper.useSubmitButton( + async () => { + try { + const createdToken = await guild.requestDoCreateToken(expiresFromNowText === 'never' ? null : expiresFromNowText) + return { result: createdToken, errorMessage: null }; + } catch (e: unknown) { + LOG.error('error creating token', e); + return { result: null, errorMessage: 'Error creating token' }; + } + }, + [ guild, expiresFromNowText ], + { start: 'Create Token', done: 'Create Another Token' } ); - const createToken = useCallback(async () => { - await guild.requestDoCreateToken(expiresFromNowText); // note: the text, NOT the duration. The server uses PostgreSQL interval conversion - }, [ expiresFromNowText ]); - const errorMessage = useMemo(() => { if (guildMetaError) return 'Unable to load guild metadata'; - if (tokenError) return 'Unable to create token'; + if (createTokenFailMessage) return createTokenFailMessage; return null; }, [ guildMetaError ]); const tokenElements = useMemo(() => { if (tokensError) { + // TODO: Try Again return
Unable to load tokens
; } return tokens?.map((token: Token) => { @@ -107,7 +113,7 @@ const GuildInvitesDisplay: FC = (props: GuildInvitesDi { value: '1 month', display: 'a Month' }, { value: 'never', display: 'Never' }, ]} /> -
+
{ await Actions.fetchAndUpdateChannels(q, ui, guild); })(); - - // Guild Members - (async () => { - await Actions.fetchAndUpdateMembers(q, ui, guild); - })(); }); element.addEventListener('contextmenu', (e) => { diff --git a/src/client/webapp/elements/lists/components/member-element.scss b/src/client/webapp/elements/lists/components/member-element.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/client/webapp/elements/lists/components/member-element.tsx b/src/client/webapp/elements/lists/components/member-element.tsx new file mode 100644 index 0000000..904a957 --- /dev/null +++ b/src/client/webapp/elements/lists/components/member-element.tsx @@ -0,0 +1,37 @@ +import React, { FC, useMemo } from 'react'; +import { Member } from '../../../data-types'; +import CombinedGuild from '../../../guild-combined'; +import ElementsUtil from '../../require/elements-util'; +import ReactHelper from '../../require/react-helper'; + +export interface MemberProps { + guild: CombinedGuild; + member: Member; +} + +const MemberElement: FC = (props: MemberProps) => { + const { guild, member } = props; + + const [ avatarSrc ] = ReactHelper.useOneTimeAsyncAction( + async () => ElementsUtil.getImageSrcFromResourceFailSoftly(guild, member.avatarResourceId), + './img/loading.svg', + [ guild, member.avatarResourceId ] + ); + + const nameStyle = useMemo(() => member.roleColor ? { color: member.roleColor } : {}, [ member.roleColor ]); + + return ( +
+
+ {member.displayName} +
+
+
+
{member.displayName}
+
{member.status}
+
+
+ ); +} + +export default MemberElement; \ No newline at end of file diff --git a/src/client/webapp/elements/lists/lists.scss b/src/client/webapp/elements/lists/lists.scss new file mode 100644 index 0000000..e6b1962 --- /dev/null +++ b/src/client/webapp/elements/lists/lists.scss @@ -0,0 +1,3 @@ +@import "./member-list.scss"; + +@import "./components/member-element.scss"; diff --git a/src/client/webapp/elements/lists/member-list.scss b/src/client/webapp/elements/lists/member-list.scss new file mode 100644 index 0000000..aea8106 --- /dev/null +++ b/src/client/webapp/elements/lists/member-list.scss @@ -0,0 +1,33 @@ +@import "../../styles/theme.scss"; + +.member-list-anchor { + box-sizing: border-box; + flex: none; /* >:| NOT GONNA SHINK BOI */ + background-color: $background-secondary; + width: 240px; + height: calc(100vh - 71px); + overflow-y: scroll; + padding: 8px 0 8px 8px; + + .member-list { + .member { + background-color: $background-secondary; + padding: 4px 8px; + margin-bottom: 4px; + border-radius: 4px; + } + + .member .name { + width: calc(208px - 40px); + } + + .member .status-circle { + border-color: $background-secondary; + } + + .member:hover { + background-color: $background-modifier-hover; + cursor: pointer; + } + } +} diff --git a/src/client/webapp/elements/lists/member-list.tsx b/src/client/webapp/elements/lists/member-list.tsx new file mode 100644 index 0000000..e2cff50 --- /dev/null +++ b/src/client/webapp/elements/lists/member-list.tsx @@ -0,0 +1,31 @@ +import React, { FC, useMemo } from 'react'; +import { Member } from '../../data-types'; +import CombinedGuild from '../../guild-combined'; +import GuildSubscriptions from '../require/guild-subscriptions'; +import MemberElement from './components/member-element'; + +export interface MemberListProps { + guild: CombinedGuild +} + +const MemberList: FC = (props: MemberListProps) => { + const { guild } = props; + + const [ members, fetchError ] = GuildSubscriptions.useMembersSubscription(guild); + + const memberElements = useMemo(() => { + if (fetchError) { + // TODO: Try Again + return
Unable to load members
+ } + return members?.map((member: Member) => ); + }, [ members, fetchError ]); + + return ( +
+ {memberElements} +
+ ); +}; + +export default MemberList; \ No newline at end of file diff --git a/src/client/webapp/elements/member.tsx b/src/client/webapp/elements/member.tsx deleted file mode 100644 index 78919fa..0000000 --- a/src/client/webapp/elements/member.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import { Member } from "../data-types"; -import CombinedGuild from "../guild-combined"; -import Q from "../q-module"; -import ElementsUtil from "./require/elements-util"; -import ReactHelper from "./require/react-helper"; - - -export default function createMember(q: Q, guild: CombinedGuild, member: Member): Element { - const nameStyle = member.roleColor ? { color: member.roleColor } : {}; - const element = ReactHelper.createElementFromJSX( -
-
- {member.displayName} -
-
-
-
{member.displayName}
-
{member.status}
-
-
- ); - (async () => { - (q.$$$(element, 'img.avatar') as HTMLImageElement).src = - await ElementsUtil.getImageSrcFromResourceFailSoftly(guild, member.avatarResourceId); - })(); - return element; -} diff --git a/src/client/webapp/elements/mounts.tsx b/src/client/webapp/elements/mounts.tsx new file mode 100644 index 0000000..f56b0032 --- /dev/null +++ b/src/client/webapp/elements/mounts.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Channel } from "../data-types"; +import CombinedGuild from "../guild-combined"; +import Q from "../q-module"; +import ElementsUtil from "./require/elements-util"; +import MemberList from "./lists/member-list"; + +export function mountBaseComponents() { + // guild-list +} + +export function mountGuildComponents(q: Q, guild: CombinedGuild) { + // member-list + ElementsUtil.unmountReactComponent(q.$('.member-list-anchor')); + ElementsUtil.mountReactComponent(q.$('.member-list-anchor'), ); + + // channel-list +} + +export function mountGuildChannelComponents(guild: CombinedGuild, channel: Channel) { + // message-list +} \ No newline at end of file diff --git a/src/client/webapp/elements/require/elements-util.tsx b/src/client/webapp/elements/require/elements-util.tsx index d8a16ee..55388f0 100644 --- a/src/client/webapp/elements/require/elements-util.tsx +++ b/src/client/webapp/elements/require/elements-util.tsx @@ -382,6 +382,14 @@ export default class ElementsUtil { } } + static mountReactComponent(element: Element, component: JSX.Element) { + ReactDOM.render(component, element); + } + + static unmountReactComponent(element: Element) { + ReactDOM.unmountComponentAtNode(element); + } + static presentReactOverlay(document: Document, content: JSX.Element) { const overlay = {content}; ReactDOM.render(overlay, document.querySelector('#react-overlays')); diff --git a/src/client/webapp/elements/require/guild-subscriptions.ts b/src/client/webapp/elements/require/guild-subscriptions.ts index ea3537d..0f384da 100644 --- a/src/client/webapp/elements/require/guild-subscriptions.ts +++ b/src/client/webapp/elements/require/guild-subscriptions.ts @@ -3,7 +3,7 @@ const electronConsole = electronRemote.getGlobal('console') as Console; import Logger from '../../../../logger/logger'; const LOG = Logger.create(__filename, electronConsole); -import { Changes, GuildMetadata, Resource } from "../../data-types"; +import { Changes, GuildMetadata, Member, Resource } from "../../data-types"; import CombinedGuild from "../../guild-combined"; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -340,6 +340,23 @@ export default class GuildSubscriptions { }, fetchChannelsFunc); } + static useMembersSubscription(guild: CombinedGuild) { + const fetchMembersFunc = useCallback(async () => { + return await guild.fetchMembers(); + }, [ guild ]); + return GuildSubscriptions.useMultipleGuildSubscription(guild, { + newEventName: 'new-members', + newEventArgsMap: (members: Member[]) => members, + updatedEventName: 'update-members', + updatedEventArgsMap: (updatedMembers: Member[]) => updatedMembers, + removedEventName: 'remove-members', + removedEventArgsMap: (removedMembers: Member[]) => removedMembers, + conflictEventName: 'conflict-members', + conflictEventArgsMap: (changesType: AutoVerifierChangesType, changes: Changes) => changes, + sortFunc: Member.sortForList + }, fetchMembersFunc); + } + static useTokensSubscription(guild: CombinedGuild) { const fetchTokensFunc = useCallback(async () => { //LOG.silly('fetching tokens for subscription'); diff --git a/src/client/webapp/elements/require/react-helper.ts b/src/client/webapp/elements/require/react-helper.ts index ad7c5ac..1d6dcce 100644 --- a/src/client/webapp/elements/require/react-helper.ts +++ b/src/client/webapp/elements/require/react-helper.ts @@ -8,8 +8,6 @@ import ReactDOMServer from "react-dom/server"; import { ShouldNeverHappenError } from "../../data-types"; import Util from '../../util'; -export class ExpectedError extends Error {} - // Helper function so we can use JSX before fully committing to React export default class ReactHelper { @@ -122,51 +120,4 @@ export default class ReactHelper { return [ submitFunc, buttonText, buttonShaking, errorMessage, result ]; } - - static useAsyncButtonSubscription( - actionFunc: () => Promise, - stateText: { start: string, pending: string, error: string, done: string }, - deps: DependencyList - ): [ result: T | null, error: unknown | null, text: string, shaking: boolean, callback: () => void ] { - const isMounted = ReactHelper.useIsMountedRef(); - - const [ result, setResult ] = useState(null); - const [ error, setError ] = useState(null); - - const [ pending, setPending ] = useState(false); - const [ complete, setComplete ] = useState(false); - const [ shaking, setShaking ] = useState(false); - - const text = useMemo(() => { - if (error) return stateText.error; - if (pending) return stateText.pending; - if (complete) return stateText.done; - return stateText.start; - }, [ error, pending, complete ]); - - const callback = useCallback(async () => { - if (pending) return; - setError(null); - setPending(true); - try { - const value = await actionFunc(); - if (!isMounted.current) return; - setResult(value); - setComplete(true); - setError(null); - setPending(false); - } catch (e: unknown) { - LOG.error('unable to perform async button subscription'); - if (!isMounted.current) return; - setError(e); - setShaking(true); - await Util.sleep(400); - if (!isMounted.current) return; - setShaking(false); - setPending(false); - } - }, [ ...deps, pending ]); - - return [ result, error, text, shaking, callback ]; - } } diff --git a/src/client/webapp/index.html b/src/client/webapp/index.html index 2fb670e..a7c2018 100644 --- a/src/client/webapp/index.html +++ b/src/client/webapp/index.html @@ -87,7 +87,8 @@ -
+
+
diff --git a/src/client/webapp/preload.ts b/src/client/webapp/preload.ts index 7712aed..ebbacb9 100644 --- a/src/client/webapp/preload.ts +++ b/src/client/webapp/preload.ts @@ -96,9 +96,6 @@ window.addEventListener('DOMContentLoaded', () => { (async () => { // update connection info await Actions.fetchAndUpdateConnection(ui, guild); })(); - (async () => { // refresh members list - await Actions.fetchAndUpdateMembers(q, ui, guild); - })(); (async () => { // refresh channels list await Actions.fetchAndUpdateChannels(q, ui, guild); })(); @@ -123,9 +120,6 @@ window.addEventListener('DOMContentLoaded', () => { (async () => { await Actions.fetchAndUpdateConnection(ui, guild); })(); - (async () => { - await Actions.fetchAndUpdateMembers(q, ui, guild); - })(); }); // Change Events @@ -147,14 +141,8 @@ window.addEventListener('DOMContentLoaded', () => { } }); - guildsManager.on('remove-members', async (guild: CombinedGuild, members: Member[]) => { - LOG.debug(members.length + ' removed members'); - await ui.deleteMembers(guild, members); - }); - guildsManager.on('update-members', async (guild: CombinedGuild, updatedMembers: Member[]) => { LOG.debug(updatedMembers.length + ' updated members g#' + guild.id); - await ui.updateMembers(guild, updatedMembers); if ( ui.activeConnection !== null && updatedMembers.find(member => member.id === (ui.activeConnection as ConnectionInfo).id) @@ -163,11 +151,6 @@ window.addEventListener('DOMContentLoaded', () => { } }); - guildsManager.on('new-members', async (guild: CombinedGuild, members: Member[]) => { - LOG.debug(members.length + ' new members'); - await ui.addMembers(guild, members); - }); - guildsManager.on('remove-channels', async (guild: CombinedGuild, channels: Channel[]) => { LOG.debug(channels.length + ' removed channels'); await ui.deleteChannels(guild, channels); @@ -229,12 +212,7 @@ window.addEventListener('DOMContentLoaded', () => { guildsManager.on('conflict-members', async (guild: CombinedGuild, changesType: AutoVerifierChangesType, changes: Changes) => { //LOG.debug('members conflict', { changes }); - if (changes.deleted.length > 0) await ui.deleteMembers(guild, changes.deleted); - if (changes.added.length > 0) await ui.addMembers(guild, changes.added); if (changes.updated.length > 0) { - (async () => { - await ui.updateMembers(guild, changes.updated.map(pair => pair.newDataPoint)); - })(); (async () => { LOG.debug('updating conflict members connection...'); // Likely getting called before the ram is updated diff --git a/src/client/webapp/styles/guild-members.scss b/src/client/webapp/styles/guild-members.scss deleted file mode 100644 index 0797d8e..0000000 --- a/src/client/webapp/styles/guild-members.scss +++ /dev/null @@ -1,31 +0,0 @@ -@import "theme.scss"; - -#guild-members { - box-sizing: border-box; - flex: none; /* >:| NOT GONNA SHINK BOI */ - background-color: $background-secondary; - width: 240px; - height: calc(100vh - 71px); - overflow-y: scroll; - padding: 8px 0 8px 8px; -} - -#guild-members .member { - background-color: $background-secondary; - padding: 4px 8px; - margin-bottom: 4px; - border-radius: 4px; -} - -#guild-members .member .name { - width: calc(208px - 40px); -} - -#guild-members .member .status-circle { - border-color: $background-secondary; -} - -#guild-members .member:hover { - background-color: $background-modifier-hover; - cursor: pointer; -} diff --git a/src/client/webapp/styles/styles.scss b/src/client/webapp/styles/styles.scss index 50971c7..d89685b 100644 --- a/src/client/webapp/styles/styles.scss +++ b/src/client/webapp/styles/styles.scss @@ -4,6 +4,8 @@ @import "../elements/components/components.scss"; @import "../elements/overlays/overlays.scss"; +@import "../elements/lists/lists.scss"; + @import "buttons.scss"; @import "channel-feed.scss"; @import "channel-list.scss"; @@ -18,7 +20,6 @@ @import "overlays.scss"; @import "scrollbars.scss"; @import "guild-list.scss"; -@import "guild-members.scss"; @import "guild.scss"; @import "shake.scss"; @import "status-circles.scss"; diff --git a/src/client/webapp/ui.ts b/src/client/webapp/ui.ts index 5978737..2f581f6 100644 --- a/src/client/webapp/ui.ts +++ b/src/client/webapp/ui.ts @@ -10,14 +10,14 @@ import ElementsUtil from './elements/require/elements-util'; import Globals from './globals'; import Util from './util'; import CombinedGuild from './guild-combined'; -import { Message, Member, Channel, ConnectionInfo, ShouldNeverHappenError } from './data-types'; +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 createMember from './elements/member'; import GuildsManager from './guilds-manager'; import createMessage from './elements/message'; - +import { mountGuildComponents } from './elements/mounts'; + interface SetMessageProps { atTop: boolean; atBottom: boolean; @@ -109,6 +109,7 @@ export default class UI { const next = this.q.$('#guild-list .guild[data-id="' + guild.id + '"]'); next.classList.add('active'); this.q.$('#guild').setAttribute('data-id', guild.id + ''); + mountGuildComponents(this.q, guild); this.activeGuild = guild; } @@ -317,91 +318,6 @@ export default class UI { }); } - public async updateMemberPosition(guild: CombinedGuild, memberElement: Element): Promise { - // TODO: Change 100 to a constant? - const statusOrder = new Map(); - statusOrder.set('online', 0); - statusOrder.set('away', 1); - statusOrder.set('busy', 2); - statusOrder.set('offline', 3); - statusOrder.set('invisible', 3); // this status is only shown in the case of the current member. - statusOrder.set('unknown', 100); - - this._updatePosition(memberElement, await guild.grabRAMMembersMap(), (a, b) => { - const onlineCmp = (a.status === 'offline' ? 1 : 0) - (b.status === 'offline' ? 1 : 0); - if (onlineCmp != 0) return onlineCmp; - const rolePriorityCmp = (a.rolePriority ?? 100) - (b.rolePriority ?? 100); - if (rolePriorityCmp != 0) return rolePriorityCmp; - const statusCmp = (statusOrder.get(a.status) ?? 100) - (statusOrder.get(b.status) ?? 100); - if (statusCmp != 0) return statusCmp; - const nameCmp = a.displayName.localeCompare(b.displayName); - return nameCmp; - }); - } - - public async addMembers(guild: CombinedGuild, members: Member[], options?: { clear: boolean }): Promise { - await this.lockMembers(guild, async () => { - if (options?.clear) { - Q.clearChildren(this.q.$('#guild-members')); - } - for (const member of members) { - const element = createMember(this.q, guild, member); - this.q.$('#guild-members').appendChild(element); - await this.updateMemberPosition(guild, element); - } - }); - } - - public async deleteMembers(guild: CombinedGuild, members: Member[]): Promise { - await this.lockMembers(guild, () => { - for (const member of members) { - const element = this.q.$_('#guild-members .member[data-id="' + member.id + '"]'); - element?.parentElement?.removeChild(element); - } - }); - } - - public async updateMembers(guild: CombinedGuild, updatedMembers: Member[]): Promise { - await this.lockMembers(guild, async () => { - for (const member of updatedMembers) { - const oldElement = this.q.$_('#guild-members .member[data-id="' + member.id + '"]'); - if (oldElement) { - const newElement = createMember(this.q, guild, member); - oldElement.parentElement?.replaceChild(newElement, oldElement); - await this.updateMemberPosition(guild, newElement); - } - } - }); - - // Update the messages too - if (this.activeChannel === null) return; - await this.lockMessages(guild, this.activeChannel, () => { - for (const member of updatedMembers) { - const newStyle = member.roleColor ? 'color: ' + member.roleColor : null; - const newName = member.displayName; - // the extra query selectors may be overkill - for (const messageElement of this.q.$$(`.message[data-member-id="${member.id}"]`)) { - const nameElement = this.q.$$$_(messageElement, '.member-name'); - if (nameElement) { // continued messages will still show up but need to be skipped - if (newStyle) nameElement.setAttribute('style', newStyle); - nameElement.innerText = newName; - } - } - } - }); - } - - public async setMembers(guild: CombinedGuild, members: Member[]): Promise { - await this.addMembers(guild, members, { clear: true }); - } - - public async setMembersErrorIndicator(guild: CombinedGuild, errorIndicatorElement: Element): Promise { - await this.lockMembers(guild, () => { - Q.clearChildren(this.q.$('#guild-members')); - this.q.$('#guild-members').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;