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 });
|
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) {
|
static async fetchAndUpdateChannels(q: Q, ui: UI, guild: CombinedGuild) {
|
||||||
await Util.withPotentialErrorWarnOnCancel(q, {
|
await Util.withPotentialErrorWarnOnCancel(q, {
|
||||||
|
@ -59,6 +59,38 @@ export class Member implements WithEquals<Member> {
|
|||||||
this.privileges.join(',') === other.privileges.join(',') // TODO: sort these first
|
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> {
|
export class Channel implements WithEquals<Channel> {
|
||||||
|
@ -3,7 +3,7 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
|
|||||||
import Logger from '../../../../logger/logger';
|
import Logger from '../../../../logger/logger';
|
||||||
const LOG = Logger.create(__filename, electronConsole);
|
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 CombinedGuild from '../../guild-combined';
|
||||||
import Display from '../components/display';
|
import Display from '../components/display';
|
||||||
import InvitePreview from '../components/invite-preview';
|
import InvitePreview from '../components/invite-preview';
|
||||||
@ -29,15 +29,16 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
|
|||||||
const [ tokens, tokensError ] = GuildSubscriptions.useTokensSubscription(guild);
|
const [ tokens, tokensError ] = GuildSubscriptions.useTokensSubscription(guild);
|
||||||
|
|
||||||
const [ guildMeta, guildMetaError ] = GuildSubscriptions.useGuildMetadataSubscription(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(
|
const [ iconSrc ] = ReactHelper.useOneTimeAsyncAction(
|
||||||
async () => await ElementsUtil.getImageSrcFromResourceFailSoftly(guild, guildMeta?.iconResourceId ?? null),
|
async () => await ElementsUtil.getImageSrcFromResourceFailSoftly(guild, guildMeta?.iconResourceId ?? null),
|
||||||
'./img/loading.svg',
|
'./img/loading.svg',
|
||||||
[ guild, guildMeta?.iconResourceId ]
|
[ guild, guildMeta?.iconResourceId ]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [ expiresFromNow, setExpiresFromNow ] = useState<Duration | null>(moment.duration(1, 'day'));
|
|
||||||
const [ expiresFromNowText, setExpiresFromNowText ] = useState<string>('1 day');
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (expiresFromNowText === 'never') {
|
if (expiresFromNowText === 'never') {
|
||||||
setExpiresFromNow(null);
|
setExpiresFromNow(null);
|
||||||
@ -48,24 +49,29 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
|
|||||||
}
|
}
|
||||||
}, [ expiresFromNowText ]);
|
}, [ expiresFromNowText ]);
|
||||||
|
|
||||||
const [ tokenResult, tokenError, tokenButtonText, tokenButtonShaking, tokenButtonCallback ] = ReactHelper.useAsyncButtonSubscription(
|
const [ createTokenFunc, tokenButtonText, tokenButtonShaking, createTokenFailMessage ] = ReactHelper.useSubmitButton(
|
||||||
async () => await guild.requestDoCreateToken(expiresFromNowText === 'never' ? null : expiresFromNowText),
|
async () => {
|
||||||
{ start: 'Create Token', pending: 'Creating...', error: 'Try Again', done: 'Create Token' },
|
try {
|
||||||
[ guild, expiresFromNowText ]
|
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(() => {
|
const errorMessage = useMemo(() => {
|
||||||
if (guildMetaError) return 'Unable to load guild metadata';
|
if (guildMetaError) return 'Unable to load guild metadata';
|
||||||
if (tokenError) return 'Unable to create token';
|
if (createTokenFailMessage) return createTokenFailMessage;
|
||||||
return null;
|
return null;
|
||||||
}, [ guildMetaError ]);
|
}, [ guildMetaError ]);
|
||||||
|
|
||||||
const tokenElements = useMemo(() => {
|
const tokenElements = useMemo(() => {
|
||||||
if (tokensError) {
|
if (tokensError) {
|
||||||
|
// TODO: Try Again
|
||||||
return <div className="tokens-failed">Unable to load tokens</div>;
|
return <div className="tokens-failed">Unable to load tokens</div>;
|
||||||
}
|
}
|
||||||
return tokens?.map((token: Token) => {
|
return tokens?.map((token: Token) => {
|
||||||
@ -107,7 +113,7 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
|
|||||||
{ value: '1 month', display: 'a Month' },
|
{ value: '1 month', display: 'a Month' },
|
||||||
{ value: 'never', display: 'Never' },
|
{ value: 'never', display: 'Never' },
|
||||||
]} />
|
]} />
|
||||||
<div><Button shaking={tokenButtonShaking} onClick={tokenButtonCallback}>{tokenButtonText}</Button></div>
|
<div><Button shaking={tokenButtonShaking} onClick={createTokenFunc}>{tokenButtonText}</Button></div>
|
||||||
</div>
|
</div>
|
||||||
<InvitePreview
|
<InvitePreview
|
||||||
name={guildMeta?.name ?? ''} iconSrc={iconSrc}
|
name={guildMeta?.name ?? ''} iconSrc={iconSrc}
|
||||||
|
@ -100,11 +100,6 @@ export default function createGuildListGuild(document: Document, q: Q, ui: UI, g
|
|||||||
(async () => {
|
(async () => {
|
||||||
await Actions.fetchAndUpdateChannels(q, ui, guild);
|
await Actions.fetchAndUpdateChannels(q, ui, guild);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Guild Members
|
|
||||||
(async () => {
|
|
||||||
await Actions.fetchAndUpdateMembers(q, ui, guild);
|
|
||||||
})();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
element.addEventListener('contextmenu', (e) => {
|
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) {
|
static presentReactOverlay(document: Document, content: JSX.Element) {
|
||||||
const overlay = <Overlay document={document}>{content}</Overlay>;
|
const overlay = <Overlay document={document}>{content}</Overlay>;
|
||||||
ReactDOM.render(overlay, document.querySelector('#react-overlays'));
|
ReactDOM.render(overlay, document.querySelector('#react-overlays'));
|
||||||
|
@ -3,7 +3,7 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
|
|||||||
import Logger from '../../../../logger/logger';
|
import Logger from '../../../../logger/logger';
|
||||||
const LOG = Logger.create(__filename, electronConsole);
|
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 CombinedGuild from "../../guild-combined";
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
@ -340,6 +340,23 @@ export default class GuildSubscriptions {
|
|||||||
}, fetchChannelsFunc);
|
}, 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) {
|
static useTokensSubscription(guild: CombinedGuild) {
|
||||||
const fetchTokensFunc = useCallback(async () => {
|
const fetchTokensFunc = useCallback(async () => {
|
||||||
//LOG.silly('fetching tokens for subscription');
|
//LOG.silly('fetching tokens for subscription');
|
||||||
|
@ -8,8 +8,6 @@ import ReactDOMServer from "react-dom/server";
|
|||||||
import { ShouldNeverHappenError } from "../../data-types";
|
import { ShouldNeverHappenError } from "../../data-types";
|
||||||
import Util from '../../util';
|
import Util from '../../util';
|
||||||
|
|
||||||
export class ExpectedError extends Error {}
|
|
||||||
|
|
||||||
// Helper function so we can use JSX before fully committing to React
|
// Helper function so we can use JSX before fully committing to React
|
||||||
|
|
||||||
export default class ReactHelper {
|
export default class ReactHelper {
|
||||||
@ -122,51 +120,4 @@ export default class ReactHelper {
|
|||||||
|
|
||||||
return [ submitFunc, buttonText, buttonShaking, errorMessage, result ];
|
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>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -96,9 +96,6 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
(async () => { // update connection info
|
(async () => { // update connection info
|
||||||
await Actions.fetchAndUpdateConnection(ui, guild);
|
await Actions.fetchAndUpdateConnection(ui, guild);
|
||||||
})();
|
})();
|
||||||
(async () => { // refresh members list
|
|
||||||
await Actions.fetchAndUpdateMembers(q, ui, guild);
|
|
||||||
})();
|
|
||||||
(async () => { // refresh channels list
|
(async () => { // refresh channels list
|
||||||
await Actions.fetchAndUpdateChannels(q, ui, guild);
|
await Actions.fetchAndUpdateChannels(q, ui, guild);
|
||||||
})();
|
})();
|
||||||
@ -123,9 +120,6 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
(async () => {
|
(async () => {
|
||||||
await Actions.fetchAndUpdateConnection(ui, guild);
|
await Actions.fetchAndUpdateConnection(ui, guild);
|
||||||
})();
|
})();
|
||||||
(async () => {
|
|
||||||
await Actions.fetchAndUpdateMembers(q, ui, guild);
|
|
||||||
})();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Change Events
|
// 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[]) => {
|
guildsManager.on('update-members', async (guild: CombinedGuild, updatedMembers: Member[]) => {
|
||||||
LOG.debug(updatedMembers.length + ' updated members g#' + guild.id);
|
LOG.debug(updatedMembers.length + ' updated members g#' + guild.id);
|
||||||
await ui.updateMembers(guild, updatedMembers);
|
|
||||||
if (
|
if (
|
||||||
ui.activeConnection !== null &&
|
ui.activeConnection !== null &&
|
||||||
updatedMembers.find(member => member.id === (ui.activeConnection as ConnectionInfo).id)
|
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[]) => {
|
guildsManager.on('remove-channels', async (guild: CombinedGuild, channels: Channel[]) => {
|
||||||
LOG.debug(channels.length + ' removed channels');
|
LOG.debug(channels.length + ' removed channels');
|
||||||
await ui.deleteChannels(guild, 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>) => {
|
guildsManager.on('conflict-members', async (guild: CombinedGuild, changesType: AutoVerifierChangesType, changes: Changes<Member>) => {
|
||||||
//LOG.debug('members conflict', { 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) {
|
if (changes.updated.length > 0) {
|
||||||
(async () => {
|
|
||||||
await ui.updateMembers(guild, changes.updated.map(pair => pair.newDataPoint));
|
|
||||||
})();
|
|
||||||
(async () => {
|
(async () => {
|
||||||
LOG.debug('updating conflict members connection...');
|
LOG.debug('updating conflict members connection...');
|
||||||
// Likely getting called before the ram is updated
|
// 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/components/components.scss";
|
||||||
@import "../elements/overlays/overlays.scss";
|
@import "../elements/overlays/overlays.scss";
|
||||||
|
|
||||||
|
@import "../elements/lists/lists.scss";
|
||||||
|
|
||||||
@import "buttons.scss";
|
@import "buttons.scss";
|
||||||
@import "channel-feed.scss";
|
@import "channel-feed.scss";
|
||||||
@import "channel-list.scss";
|
@import "channel-list.scss";
|
||||||
@ -18,7 +20,6 @@
|
|||||||
@import "overlays.scss";
|
@import "overlays.scss";
|
||||||
@import "scrollbars.scss";
|
@import "scrollbars.scss";
|
||||||
@import "guild-list.scss";
|
@import "guild-list.scss";
|
||||||
@import "guild-members.scss";
|
|
||||||
@import "guild.scss";
|
@import "guild.scss";
|
||||||
@import "shake.scss";
|
@import "shake.scss";
|
||||||
@import "status-circles.scss";
|
@import "status-circles.scss";
|
||||||
|
@ -10,14 +10,14 @@ import ElementsUtil from './elements/require/elements-util';
|
|||||||
import Globals from './globals';
|
import Globals from './globals';
|
||||||
import Util from './util';
|
import Util from './util';
|
||||||
import CombinedGuild from './guild-combined';
|
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 Q from './q-module';
|
||||||
import createGuildListGuild from './elements/guild-list-guild';
|
import createGuildListGuild from './elements/guild-list-guild';
|
||||||
import createChannel from './elements/channel';
|
import createChannel from './elements/channel';
|
||||||
import createMember from './elements/member';
|
|
||||||
import GuildsManager from './guilds-manager';
|
import GuildsManager from './guilds-manager';
|
||||||
import createMessage from './elements/message';
|
import createMessage from './elements/message';
|
||||||
|
import { mountGuildComponents } from './elements/mounts';
|
||||||
|
|
||||||
interface SetMessageProps {
|
interface SetMessageProps {
|
||||||
atTop: boolean;
|
atTop: boolean;
|
||||||
atBottom: boolean;
|
atBottom: boolean;
|
||||||
@ -109,6 +109,7 @@ export default class UI {
|
|||||||
const next = this.q.$('#guild-list .guild[data-id="' + guild.id + '"]');
|
const next = this.q.$('#guild-list .guild[data-id="' + guild.id + '"]');
|
||||||
next.classList.add('active');
|
next.classList.add('active');
|
||||||
this.q.$('#guild').setAttribute('data-id', guild.id + '');
|
this.q.$('#guild').setAttribute('data-id', guild.id + '');
|
||||||
|
mountGuildComponents(this.q, guild);
|
||||||
this.activeGuild = 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 {
|
public getTopMessagePair(): { message: Message, element: Element } | null {
|
||||||
const element = this.q.$$('#channel-feed .message')[0];
|
const element = this.q.$$('#channel-feed .message')[0];
|
||||||
return element && this.messagePairs.get(element.getAttribute('data-id')) || null;
|
return element && this.messagePairs.get(element.getAttribute('data-id')) || null;
|
||||||
|
Loading…
Reference in New Issue
Block a user