remove old members stuff
This commit is contained in:
parent
3c14ecb6ec
commit
971b4c4d79
@ -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, {
|
||||
|
@ -59,6 +59,38 @@ export class Member implements WithEquals<Member> {
|
||||
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<Channel> {
|
||||
|
@ -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<GuildInvitesDisplayProps> = (props: GuildInvitesDi
|
||||
const [ tokens, tokensError ] = GuildSubscriptions.useTokensSubscription(guild);
|
||||
|
||||
const [ guildMeta, guildMetaError ] = GuildSubscriptions.useGuildMetadataSubscription(guild);
|
||||
|
||||
const [ expiresFromNow, setExpiresFromNow ] = useState<Duration | null>(moment.duration(1, 'day'));
|
||||
const [ expiresFromNowText, setExpiresFromNowText ] = useState<string>('1 day');
|
||||
|
||||
const [ iconSrc ] = ReactHelper.useOneTimeAsyncAction(
|
||||
async () => await ElementsUtil.getImageSrcFromResourceFailSoftly(guild, guildMeta?.iconResourceId ?? null),
|
||||
'./img/loading.svg',
|
||||
[ guild, guildMeta?.iconResourceId ]
|
||||
);
|
||||
|
||||
const [ expiresFromNow, setExpiresFromNow ] = useState<Duration | null>(moment.duration(1, 'day'));
|
||||
const [ expiresFromNowText, setExpiresFromNowText ] = useState<string>('1 day');
|
||||
|
||||
useEffect(() => {
|
||||
if (expiresFromNowText === 'never') {
|
||||
setExpiresFromNow(null);
|
||||
@ -48,24 +49,29 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (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 <div className="tokens-failed">Unable to load tokens</div>;
|
||||
}
|
||||
return tokens?.map((token: Token) => {
|
||||
@ -107,7 +113,7 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
|
||||
{ value: '1 month', display: 'a Month' },
|
||||
{ value: 'never', display: 'Never' },
|
||||
]} />
|
||||
<div><Button shaking={tokenButtonShaking} onClick={tokenButtonCallback}>{tokenButtonText}</Button></div>
|
||||
<div><Button shaking={tokenButtonShaking} onClick={createTokenFunc}>{tokenButtonText}</Button></div>
|
||||
</div>
|
||||
<InvitePreview
|
||||
name={guildMeta?.name ?? ''} iconSrc={iconSrc}
|
||||
|
@ -100,11 +100,6 @@ export default function createGuildListGuild(document: Document, q: Q, ui: UI, g
|
||||
(async () => {
|
||||
await Actions.fetchAndUpdateChannels(q, ui, guild);
|
||||
})();
|
||||
|
||||
// Guild Members
|
||||
(async () => {
|
||||
await Actions.fetchAndUpdateMembers(q, ui, guild);
|
||||
})();
|
||||
});
|
||||
|
||||
element.addEventListener('contextmenu', (e) => {
|
||||
|
@ -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<MemberProps> = (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 (
|
||||
<div className={'member ' + member.status} data-id={member.id}>
|
||||
<div className="icon">
|
||||
<img className="avatar" src={avatarSrc} alt={member.displayName}></img>
|
||||
<div className="status-circle"></div>
|
||||
</div>
|
||||
<div className="text">
|
||||
<div className="name" style={nameStyle}>{member.displayName}</div>
|
||||
<div className="status-text">{member.status}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MemberElement;
|
3
src/client/webapp/elements/lists/lists.scss
Normal file
3
src/client/webapp/elements/lists/lists.scss
Normal file
@ -0,0 +1,3 @@
|
||||
@import "./member-list.scss";
|
||||
|
||||
@import "./components/member-element.scss";
|
33
src/client/webapp/elements/lists/member-list.scss
Normal file
33
src/client/webapp/elements/lists/member-list.scss
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
31
src/client/webapp/elements/lists/member-list.tsx
Normal file
31
src/client/webapp/elements/lists/member-list.tsx
Normal file
@ -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<MemberListProps> = (props: MemberListProps) => {
|
||||
const { guild } = props;
|
||||
|
||||
const [ members, fetchError ] = GuildSubscriptions.useMembersSubscription(guild);
|
||||
|
||||
const memberElements = useMemo(() => {
|
||||
if (fetchError) {
|
||||
// TODO: Try Again
|
||||
return <div className="members-failed">Unable to load members</div>
|
||||
}
|
||||
return members?.map((member: Member) => <MemberElement key={member.id} guild={guild} member={member} />);
|
||||
}, [ members, fetchError ]);
|
||||
|
||||
return (
|
||||
<div className="member-list">
|
||||
{memberElements}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MemberList;
|
@ -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(
|
||||
<div className={'member ' + member.status} data-id={member.id}>
|
||||
<div className="icon">
|
||||
<img className="avatar" src="./img/loading.svg" alt={member.displayName}></img>
|
||||
<div className="status-circle"></div>
|
||||
</div>
|
||||
<div className="text">
|
||||
<div className="name" style={nameStyle}>{member.displayName}</div>
|
||||
<div className="status-text">{member.status}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
(async () => {
|
||||
(q.$$$(element, 'img.avatar') as HTMLImageElement).src =
|
||||
await ElementsUtil.getImageSrcFromResourceFailSoftly(guild, member.avatarResourceId);
|
||||
})();
|
||||
return element;
|
||||
}
|
22
src/client/webapp/elements/mounts.tsx
Normal file
22
src/client/webapp/elements/mounts.tsx
Normal file
@ -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'), <MemberList guild={guild} />);
|
||||
|
||||
// channel-list
|
||||
}
|
||||
|
||||
export function mountGuildChannelComponents(guild: CombinedGuild, channel: Channel) {
|
||||
// message-list
|
||||
}
|
@ -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 = <Overlay document={document}>{content}</Overlay>;
|
||||
ReactDOM.render(overlay, document.querySelector('#react-overlays'));
|
||||
|
@ -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<Member, 'new-members', 'update-members', 'remove-members', 'conflict-members'>(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<Member>) => changes,
|
||||
sortFunc: Member.sortForList
|
||||
}, fetchMembersFunc);
|
||||
}
|
||||
|
||||
static useTokensSubscription(guild: CombinedGuild) {
|
||||
const fetchTokensFunc = useCallback(async () => {
|
||||
//LOG.silly('fetching tokens for subscription');
|
||||
|
@ -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<T>(
|
||||
actionFunc: () => Promise<T>,
|
||||
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<T | null>(null);
|
||||
const [ error, setError ] = useState<unknown | null>(null);
|
||||
|
||||
const [ pending, setPending ] = useState<boolean>(false);
|
||||
const [ complete, setComplete ] = useState<boolean>(false);
|
||||
const [ shaking, setShaking ] = useState<boolean>(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 ];
|
||||
}
|
||||
}
|
||||
|
@ -87,7 +87,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="guild-members"></div>
|
||||
<div id="guild-members"></div><!-- TODO: guild-members-react -->
|
||||
<div class="member-list-anchor"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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<Member>) => {
|
||||
//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
|
||||
|
@ -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;
|
||||
}
|
@ -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";
|
||||
|
@ -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<void> {
|
||||
// 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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
await this.addMembers(guild, members, { clear: true });
|
||||
}
|
||||
|
||||
public async setMembersErrorIndicator(guild: CombinedGuild, errorIndicatorElement: Element): Promise<void> {
|
||||
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;
|
||||
|
Loading…
Reference in New Issue
Block a user