Fixed glaring bugs when switching between guilds

This commit is contained in:
Michael Peters 2022-01-20 19:54:36 -06:00
parent fa10c8a7a9
commit e35187b877
20 changed files with 1264 additions and 1310 deletions

View File

@ -2,4 +2,4 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
};

2363
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,18 +13,19 @@ export interface TokenRowProps {
url: string;
guild: CombinedGuild;
guildMeta: GuildMetadata;
guildMetaGuild: CombinedGuild;
token: Token;
}
const TokenRow: FC<TokenRowProps> = (props: TokenRowProps) => {
const { guild, guildMeta, token } = props;
const { guild, guildMeta, guildMetaGuild, token } = props;
const [ guildSocketConfigs ] = useOneTimeAsyncAction(
async () => await guild.fetchSocketConfigs(),
null,
[ guild ]
);
const [ iconSrc ] = useSoftImageSrcResourceSubscription(guild, guildMeta.iconResourceId);
const [ iconSrc ] = useSoftImageSrcResourceSubscription(guild, guildMeta.iconResourceId, guildMetaGuild);
const [ revoke ] = useAsyncVoidCallback(async () => {
await guild.requestDoRevokeToken(token.token);
@ -51,7 +52,7 @@ const TokenRow: FC<TokenRowProps> = (props: TokenRowProps) => {
const userText = (token.member instanceof Member ? 'Used by ' + token.member.displayName : token.member?.id) ?? 'Unused Token';
return (
<div key={token.token} className="token-row">
<div key={guild.id + token.token} className="token-row">
<div className="user-token">
<div className="user">{userText}</div>
<div className="token">{token.token}</div>

View File

@ -7,13 +7,14 @@ import ContextMenu from './components/context-menu';
export interface ConnectionInfoContextMenuProps {
guild: CombinedGuild;
selfMember: Member;
selfMemberGuild: CombinedGuild;
relativeToRef: RefObject<HTMLElement>;
close: () => void;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const ConnectionInfoContextMenu: FC<ConnectionInfoContextMenuProps> = (props: ConnectionInfoContextMenuProps) => {
const { guild, selfMember, relativeToRef, close, setOverlay } = props;
const { guild, selfMember, selfMemberGuild, relativeToRef, close, setOverlay } = props;
const setSelfStatus = useCallback(async (status: string) => {
if (selfMember.status !== status) {
@ -35,8 +36,8 @@ const ConnectionInfoContextMenu: FC<ConnectionInfoContextMenuProps> = (props: Co
const openPersonalize = useCallback(() => {
close();
setOverlay(<PersonalizeOverlay guild={guild} selfMember={selfMember} close={() => setOverlay(null)} />);
}, [ guild, selfMember, close ]);
setOverlay(<PersonalizeOverlay guild={guild} selfMember={selfMember} selfMemberGuild={selfMemberGuild} close={() => setOverlay(null)} />);
}, [ guild, selfMember, selfMemberGuild, close ]);
const alignment = useMemo(() => {
return { bottom: 'top', centerX: 'centerX' }

View File

@ -11,17 +11,18 @@ export interface GuildTitleContextMenuProps {
relativeToRef: RefObject<HTMLElement>;
guild: CombinedGuild;
guildMeta: GuildMetadata;
guildMetaGuild: CombinedGuild;
selfMember: Member;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const GuildTitleContextMenu: FC<GuildTitleContextMenuProps> = (props: GuildTitleContextMenuProps) => {
const { close, relativeToRef, guild, guildMeta, selfMember, setOverlay } = props;
const { close, relativeToRef, guild, guildMeta, guildMetaGuild, selfMember, setOverlay } = props;
const openGuildSettings = useCallback(() => {
close();
setOverlay(<GuildSettingsOverlay guild={guild} guildMeta={guildMeta} close={() => setOverlay(null)} />);
}, [ guild, guildMeta, close ]);
setOverlay(<GuildSettingsOverlay guild={guild} guildMeta={guildMeta} guildMetaGuild={guildMetaGuild} close={() => setOverlay(null)} />);
}, [ guild, guildMeta, guildMetaGuild, close ]);
const openCreateChannel = useCallback(() => {
close();

View File

@ -7,32 +7,32 @@ import React, { FC, useEffect, useMemo, useState } from 'react';
import CombinedGuild from '../../guild-combined';
import Display from '../components/display';
import InvitePreview from '../components/invite-preview';
import { Token } from '../../data-types';
import { GuildMetadata, Token } from '../../data-types';
import { useAsyncSubmitButton } from '../require/react-helper';
import { Duration } from 'moment';
import moment from 'moment';
import DropdownInput from '../components/input-dropdown';
import Button from '../components/button';
import { useTokensSubscription, useGuildMetadataSubscription, useSoftImageSrcResourceSubscription } from '../require/guild-subscriptions';
import { useTokensSubscription, useSoftImageSrcResourceSubscription } from '../require/guild-subscriptions';
import TokenRow from '../components/token-row';
export interface GuildInvitesDisplayProps {
guild: CombinedGuild;
guildMeta: GuildMetadata;
guildMetaGuild: CombinedGuild;
}
const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDisplayProps) => {
const { guild } = props;
const { guild, guildMeta, guildMetaGuild } = props;
const url = 'https://localhost:3030'; // TODO: this will likely be a dropdown list at some point
const [ fetchRetryCallable, tokens, tokensGuild, tokensError ] = useTokensSubscription(guild);
const [ guildMeta, guildMetaGuild, guildMetaError ] = useGuildMetadataSubscription(guild);
const [ expiresFromNow, setExpiresFromNow ] = useState<Duration | null>(moment.duration(1, 'day'));
const [ expiresFromNowText, setExpiresFromNowText ] = useState<string>('1 day');
const [ iconSrc ] = useSoftImageSrcResourceSubscription(guild, guildMeta?.iconResourceId ?? null);
const [ iconSrc ] = useSoftImageSrcResourceSubscription(guild, guildMeta?.iconResourceId ?? null, guildMetaGuild);
useEffect(() => {
if (expiresFromNowText === 'never') {
@ -59,10 +59,9 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
);
const errorMessage = useMemo(() => {
if (guildMetaError) return 'Unable to load guild metadata';
if (createTokenFailMessage) return createTokenFailMessage;
return null;
}, [ guildMetaError ]);
}, [ createTokenFailMessage ]);
const tokenElements = useMemo(() => {
if (tokensError) {
@ -72,7 +71,7 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
if (!guildMeta) {
return <div className="no-guild-meta">No Guild Metadata</div>;
}
return tokens?.map((token: Token) => <TokenRow key={token.token} url={url} guild={guild} token={token} guildMeta={guildMeta} />);
return tokens?.map((token: Token) => <TokenRow key={guild.id + token.token} url={url} guild={guild} token={token} guildMeta={guildMeta} guildMetaGuild={guildMetaGuild} />);
}, [ url, guild, tokens, tokensError ]);
return (

View File

@ -10,17 +10,19 @@ import CombinedGuild from '../../guild-combined';
import Display from '../components/display';
import TextInput from '../components/input-text';
import ImageEditInput from '../components/input-image-edit';
import { useGuildMetadataSubscription, useResourceSubscription } from '../require/guild-subscriptions';
import { useResourceSubscription } from '../require/guild-subscriptions';
import { GuildMetadata } from '../../data-types';
export interface GuildOverviewDisplayProps {
guild: CombinedGuild;
guildMeta: GuildMetadata;
guildMetaGuild: CombinedGuild;
}
const GuildOverviewDisplay: FC<GuildOverviewDisplayProps> = (props: GuildOverviewDisplayProps) => {
const { guild } = props;
const { guild, guildMeta, guildMetaGuild } = props;
// TODO: Use the one from guild.tsx (for both of these?)
const [ guildMeta, guildMetaGuild, guildMetaError ] = useGuildMetadataSubscription(guild);
const [ iconResource, iconResourceGuild, iconResourceError ] = useResourceSubscription(guild, guildMeta?.iconResourceId ?? null);
const [ iconResource, iconResourceGuild, iconResourceError ] = useResourceSubscription(guild, guildMeta?.iconResourceId ?? null, guildMetaGuild);
const [ savedName, setSavedName ] = useState<string | null>(null);
const [ savedIconBuff, setSavedIconBuff ] = useState<Buffer | null>(null);
@ -56,12 +58,11 @@ const GuildOverviewDisplay: FC<GuildOverviewDisplayProps> = (props: GuildOvervie
}, [ name, savedName, iconBuff, savedIconBuff ]);
const errorMessage = useMemo(() => {
if (guildMetaError) return 'Unable to load guild metadata';
if (iconResourceError) return 'Unable to load icon';
if (!iconInputValid && iconInputMessage) return iconInputMessage;
if (!nameInputValid && nameInputMessage) return nameInputMessage;
return null;
}, [ guildMetaError, iconResourceError, iconInputValid, iconInputMessage, nameInputValid, nameInputMessage ]);
}, [ iconResourceError, iconInputValid, iconInputMessage, nameInputValid, nameInputMessage ]);
const infoMessage = useMemo(() => {
if (iconInputValid && iconInputMessage) return iconInputMessage;

View File

@ -31,7 +31,7 @@ const ChannelList: FC<ChannelListProps> = (props: ChannelListProps) => {
}
return channels.map((channel: Channel) => (
<ChannelElement
key={channel.id} guild={guild}
key={guild.id + channel.id} guild={guild}
selfMember={selfMember}
channel={channel} activeChannel={activeChannel}
setActiveChannel={() => { setActiveChannel(channel); }}

View File

@ -24,7 +24,7 @@ const GuildListElement: FC<GuildListElementProps> = (props: GuildListElementProp
// TODO: handle metadata error
const [ guildMeta, guildMetaGuild, guildMetaError ] = useGuildMetadataSubscription(guild);
const [ selfMember ] = useSelfMemberSubscription(guild);
const [ iconSrc ] = useSoftImageSrcResourceSubscription(guild, guildMeta?.iconResourceId ?? null);
const [ iconSrc ] = useSoftImageSrcResourceSubscription(guild, guildMeta?.iconResourceId ?? null, guildMetaGuild);
const [ contextHover, mouseEnterCallable, mouseLeaveCallable ] = useContextHover(() => {
if (!guildMeta) return null;

View File

@ -1,4 +1,9 @@
import React, { FC, useMemo } from 'react';
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 React, { FC, useMemo, useEffect } from 'react';
import { Member } from '../../../data-types';
import CombinedGuild from '../../../guild-combined';
import { useSoftImageSrcResourceSubscription } from '../../require/guild-subscriptions';
@ -14,12 +19,13 @@ export interface DummyMember {
export interface MemberProps {
guild: CombinedGuild;
member: Member | DummyMember;
memberGuild: CombinedGuild | null;
}
const MemberElement: FC<MemberProps> = (props: MemberProps) => {
const { guild, member } = props;
const { guild, member, memberGuild } = props;
const [ avatarSrc ] = useSoftImageSrcResourceSubscription(guild, member.avatarResourceId);
const [ avatarSrc ] = useSoftImageSrcResourceSubscription(guild, member.avatarResourceId, memberGuild);
const nameStyle = useMemo(() => member.roleColor ? { color: member.roleColor } : {}, [ member.roleColor ]);
@ -37,4 +43,4 @@ const MemberElement: FC<MemberProps> = (props: MemberProps) => {
);
}
export default MemberElement;
export default MemberElement;

View File

@ -46,14 +46,15 @@ interface PreviewImageElementProps {
resourcePreviewId: string;
resourceId: string;
resourceName: string;
resourceIdGuild: CombinedGuild;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const PreviewImageElement: FC<PreviewImageElementProps> = (props: PreviewImageElementProps) => {
const { guild, previewWidth, previewHeight, resourcePreviewId, resourceId, resourceName, setOverlay } = props;
const { guild, previewWidth, previewHeight, resourcePreviewId, resourceId, resourceName, resourceIdGuild, setOverlay } = props;
// TODO: Handle resourceError
const [ previewImgSrc, previewResource, previewResourceGuild, previewResourceError ] = useSoftImageSrcResourceSubscription(guild, resourcePreviewId);
const [ previewImgSrc, previewResource, previewResourceGuild, previewResourceError ] = useSoftImageSrcResourceSubscription(guild, resourcePreviewId, resourceIdGuild);
const [ contextMenu, onContextMenu ] = useContextClickContextMenu((alignment: IAlignment, relativeToPos: { x: number, y: number }, close: () => void) => {
if (!previewResource) return null;
@ -66,7 +67,7 @@ const PreviewImageElement: FC<PreviewImageElementProps> = (props: PreviewImageEl
}, [ previewResource, resourceName ]);
const openImageOverlay = useCallback(() => {
setOverlay(<ImageOverlay guild={guild} resourceId={resourceId} resourceName={resourceName} close={() => setOverlay(null)} />);
setOverlay(<ImageOverlay guild={guild} resourceId={resourceId} resourceName={resourceName} resourceIdGuild={resourceIdGuild} close={() => setOverlay(null)} />);
}, [ guild, resourceId, resourceName ]);
return (
@ -84,11 +85,12 @@ export interface MessageElementProps {
guild: CombinedGuild;
message: Message;
prevMessage: Message | null;
messageGuild: CombinedGuild;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const MessageElement: FC<MessageElementProps> = (props: MessageElementProps) => {
const { guild, message, prevMessage, setOverlay } = props;
const { guild, message, prevMessage, messageGuild, setOverlay } = props;
const className = useMemo(() => {
return message.isContinued(prevMessage) ? 'message-react continued' : 'message-react';
@ -142,6 +144,7 @@ const MessageElement: FC<MessageElementProps> = (props: MessageElementProps) =>
previewWidth={message.previewWidth} previewHeight={message.previewHeight}
resourcePreviewId={message.resourcePreviewId}
resourceId={message.resourceId}
resourceIdGuild={messageGuild}
resourceName={message.resourceName ?? 'unknown.unk'}
setOverlay={setOverlay}
/>

View File

@ -6,22 +6,23 @@ import MemberElement from './components/member-element';
export interface MemberListProps {
guild: CombinedGuild;
members: Member[] | null;
membersGuild: CombinedGuild | null;
membersFetchError: unknown | null;
}
const MemberList: FC<MemberListProps> = (props: MemberListProps) => {
const { guild, members, membersFetchError } = props;
const { guild, members, membersGuild, membersFetchError } = props;
const memberElements = useMemo(() => {
if (membersFetchError) {
// TODO: Try Again
return <div className="members-failed">Unable to load members</div>
}
if (!members) {
if (!members || !membersGuild) {
return <div className="members-loading">Loading members...</div>
}
return members?.map((member: Member) => <MemberElement key={member.id} guild={guild} member={member} />);
}, [ guild, members, membersFetchError ]);
return members?.map((member: Member) => <MemberElement key={guild.id + member.id} guild={guild} member={member} memberGuild={membersGuild} />);
}, [ guild, members, membersGuild, membersFetchError ]);
return (
<div className="member-list">
@ -30,4 +31,4 @@ const MemberList: FC<MemberListProps> = (props: MemberListProps) => {
);
};
export default MemberList;
export default MemberList;

View File

@ -13,11 +13,12 @@ import { useMessagesScrollingSubscription } from '../require/guild-subscriptions
interface MessageListProps {
guild: CombinedGuild;
channel: Channel;
channelGuild: CombinedGuild;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
const { guild, channel, setOverlay } = props;
const { guild, channel, channelGuild, setOverlay } = props;
const [
fetchRetryCallable,
@ -30,19 +31,19 @@ const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
messagesFetchError,
messagesFetchAboveError,
messagesFetchBelowError
] = useMessagesScrollingSubscription(guild, channel);
] = useMessagesScrollingSubscription(guild, channel, channelGuild);
const messageElements = useMemo(() => {
const result = [];
if (messages) {
if (messages && messagesGuild) {
for (let i = 0; i < messages.length; ++i) {
const prevMessage = messages[i - 1] ?? null;
const message = messages[i] as Message;
result.push(<MessageElement key={message.id} guild={guild} message={message} prevMessage={prevMessage} setOverlay={setOverlay} />);
result.push(<MessageElement key={guild.id + message.id} guild={guild} message={message} prevMessage={prevMessage} messageGuild={messagesGuild} setOverlay={setOverlay} />);
}
}
return result;
}, [ messages ]);
}, [ messages, messagesGuild ]);
return (
<div className="message-list">

View File

@ -14,10 +14,11 @@ import Overlay from '../components/overlay';
export interface GuildSettingsOverlayProps {
guild: CombinedGuild;
guildMeta: GuildMetadata;
guildMetaGuild: CombinedGuild;
close: () => void;
}
const GuildSettingsOverlay: FC<GuildSettingsOverlayProps> = (props: GuildSettingsOverlayProps) => {
const { guild, guildMeta, close } = props;
const { guild, guildMeta, guildMetaGuild, close } = props;
const rootRef = useRef<HTMLDivElement>(null);
@ -25,9 +26,9 @@ const GuildSettingsOverlay: FC<GuildSettingsOverlayProps> = (props: GuildSetting
const [ display, setDisplay ] = useState<JSX.Element>();
useEffect(() => {
if (selectedId === 'overview') setDisplay(<GuildOverviewDisplay guild={guild} />);
if (selectedId === 'overview') setDisplay(<GuildOverviewDisplay guild={guild} guildMeta={guildMeta} guildMetaGuild={guildMetaGuild} />);
//if (selectedId === 'roles' ) setDisplay(<GuildOverviewDisplay guild={guild} guildMeta={guildMeta} />);
if (selectedId === 'invites' ) setDisplay(<GuildInvitesDisplay guild={guild} />);
if (selectedId === 'invites' ) setDisplay(<GuildInvitesDisplay guild={guild} guildMeta={guildMeta} guildMetaGuild={guildMetaGuild} />);
}, [ selectedId ]);
return (

View File

@ -13,18 +13,19 @@ import Overlay from '../components/overlay';
import { useSoftImageSrcResourceSubscription } from '../require/guild-subscriptions';
export interface ImageOverlayProps {
guild: CombinedGuild
resourceId: string,
resourceName: string,
guild: CombinedGuild;
resourceId: string;
resourceName: string;
resourceIdGuild: CombinedGuild;
close: () => void;
}
const ImageOverlay: FC<ImageOverlayProps> = (props: ImageOverlayProps) => {
const { guild, resourceId, resourceName, close } = props;
const { guild, resourceId, resourceName, resourceIdGuild, close } = props;
const rootRef = useRef<HTMLDivElement>(null);
const [ imgSrc, resource, resourceGuild, resourceError ] = useSoftImageSrcResourceSubscription(guild, resourceId);
const [ imgSrc, resource, resourceGuild, resourceError ] = useSoftImageSrcResourceSubscription(guild, resourceId, resourceIdGuild);
const [ contextMenu, onContextMenu ] = useContextClickContextMenu((alignment: IAlignment, relativeToPos: { x: number, y: number }, close: () => void) => {
if (!resource) return null;

View File

@ -18,14 +18,15 @@ import { useResourceSubscription } from '../require/guild-subscriptions';
export interface PersonalizeOverlayProps {
guild: CombinedGuild;
selfMember: Member;
selfMemberGuild: CombinedGuild;
close: () => void;
}
const PersonalizeOverlay: FC<PersonalizeOverlayProps> = (props: PersonalizeOverlayProps) => {
const { guild, selfMember, close } = props;
const { guild, selfMember, selfMemberGuild, close } = props;
const rootRef = useRef<HTMLDivElement>(null);
const [ avatarResource, avatarResourceGuild, avatarResourceError ] = useResourceSubscription(guild, selfMember.avatarResourceId)
const [ avatarResource, avatarResourceGuild, avatarResourceError ] = useResourceSubscription(guild, selfMember.avatarResourceId, selfMemberGuild);
const displayNameInputRef = createRef<HTMLInputElement>();

View File

@ -234,6 +234,7 @@ function useMultipleGuildSubscription<
const onFetch = useCallback((fetchValue: T[] | null, fetchValueGuild: CombinedGuild) => {
if (fetchValue) fetchValue.sort(sortFunc);
/* LOG.debug('onFetch', { valueType: (fetchValue?.length && (fetchValue[0] as T).constructor.name) ?? 'null', guild: fetchValueGuild.id }); */
setValue(fetchValue);
setValueGuild(fetchValueGuild);
setFetchError(null);
@ -488,6 +489,7 @@ function useMultipleGuildSubscriptionScrolling<
if (fetchValue.length >= maxFetchElements) hasMoreAbove = true;
fetchValue = fetchValue.slice(Math.max(fetchValue.length - maxElements)).sort(sortFunc);
}
//LOG.debug('Got items: ', { fetchValueLength: fetchValue?.length ?? '<empty>' })
setFetchResult({ hasMoreAbove, hasMoreBelow: false });
setValue(fetchValue);
setValueGuild(fetchValueGuild);
@ -630,12 +632,15 @@ export function useGuildMetadataSubscription(guild: CombinedGuild) {
}, fetchMetadataFunc);
}
export function useResourceSubscription(guild: CombinedGuild, resourceId: string | null) {
export function useResourceSubscription(guild: CombinedGuild, resourceId: string | null, resourceIdGuild: CombinedGuild | null) {
const fetchResourceFunc = useCallback(async () => {
// TODO: This function isn't working for the members list
//LOG.silly('fetching resource for subscription (resourceId: ' + resourceId + ')');
if (resourceId === null) return null;
if (resourceIdGuild === null) return null;
if (resourceIdGuild !== guild) return null;
return await guild.fetchResource(resourceId);
}, [ guild, resourceId ]);
}, [ guild, resourceIdGuild, resourceId ]);
return useSingleGuildSubscription<Resource, 'update-resource', 'conflict-resource'>(guild, {
updatedEventName: 'update-resource',
updatedEventArgsMap: (resource: Resource) => resource,
@ -644,11 +649,17 @@ export function useResourceSubscription(guild: CombinedGuild, resourceId: string
}, fetchResourceFunc);
}
export function useSoftImageSrcResourceSubscription(guild: CombinedGuild, resourceId: string | null): [ imgSrc: string, resource: Resource | null, resourceGuild: CombinedGuild | null, fetchError: unknown | null ] {
const [ resource, resourceGuild, fetchError ] = useResourceSubscription(guild, resourceId);
export function useSoftImageSrcResourceSubscription(guild: CombinedGuild, resourceId: string | null, resourceIdGuild: CombinedGuild | null): [
imgSrc: string,
resource: Resource | null,
resourceGuild: CombinedGuild | null,
fetchError: unknown | null
] {
const [ resource, resourceGuild, fetchError ] = useResourceSubscription(guild, resourceId, resourceIdGuild);
const [ imgSrc ] = useOneTimeAsyncAction(
async () => {
//LOG.debug(`Fetching soft imgSrc for g#${guild.id} r#${resource?.id ?? '<null>'}`, { fetchError });
if (fetchError) return './img/error.png';
if (!resource) return './img/loading.svg';
return await ElementsUtil.getImageSrcFromBufferFailSoftly(resource.data);
@ -732,18 +743,24 @@ export function useTokensSubscription(guild: CombinedGuild) {
}, fetchTokensFunc);
}
export function useMessagesScrollingSubscription(guild: CombinedGuild, channel: Channel) {
export function useMessagesScrollingSubscription(guild: CombinedGuild, channel: Channel, channelGuild: CombinedGuild) {
const maxFetchElements = Globals.MESSAGES_PER_REQUEST;
const maxElements = Globals.MAX_CURRENT_MESSAGES;
const fetchMessagesFunc = useCallback(async () => {
if (guild !== channelGuild) {
LOG.debug('not loading messages from guild differences');
return [];
}
return await guild.fetchMessagesRecent(channel.id, maxFetchElements);
}, [ guild, channel.id, maxFetchElements ]);
}, [ guild, channelGuild, channel.id, maxFetchElements ]);
const fetchAboveFunc = useCallback(async (reference: Message) => {
if (guild !== channelGuild) return [];
return await guild.fetchMessagesBefore(channel.id, reference.id, maxFetchElements);
}, [ guild, channel.id, maxFetchElements ]);
}, [ guild, channelGuild, channel.id, maxFetchElements ]);
const fetchBelowFunc = useCallback(async (reference: Message) => {
if (guild !== channelGuild) return [];
return await guild.fetchMessagesAfter(channel.id, reference.id, maxFetchElements);
}, [ guild, channel.id, maxFetchElements ]);
}, [ guild, channelGuild, channel.id, maxFetchElements ]);
return useMultipleGuildSubscriptionScrolling(
guild, {
newEventName: 'new-messages',

View File

@ -13,11 +13,12 @@ import { useContextMenu } from '../require/react-helper';
export interface ConnectionInfoProps {
guild: CombinedGuild;
selfMember: Member | null;
selfMemberGuild: CombinedGuild | null;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const ConnectionInfo: FC<ConnectionInfoProps> = (props: ConnectionInfoProps) => {
const { guild, selfMember, setOverlay } = props;
const { guild, selfMember, selfMemberGuild, setOverlay } = props;
const rootRef = useRef<HTMLDivElement>(null);
@ -35,10 +36,10 @@ const ConnectionInfo: FC<ConnectionInfoProps> = (props: ConnectionInfoProps) =>
}, [ selfMember ]);
const [ contextMenu, toggleContextMenu ] = useContextMenu((close: () => void) => {
if (!selfMember) return null;
if (!selfMember || !selfMemberGuild) return null;
return (
<ConnectionInfoContextMenu
guild={guild} selfMember={selfMember} relativeToRef={rootRef}
guild={guild} selfMember={selfMember} selfMemberGuild={selfMemberGuild} relativeToRef={rootRef}
close={close} setOverlay={setOverlay}
/>
);
@ -46,7 +47,7 @@ const ConnectionInfo: FC<ConnectionInfoProps> = (props: ConnectionInfoProps) =>
return (
<div ref={rootRef} className="connection-info">
<div onClick={toggleContextMenu}><MemberElement guild={guild} member={displayMember} /></div>
<div onClick={toggleContextMenu}><MemberElement guild={guild} member={displayMember} memberGuild={selfMemberGuild} /></div>
{contextMenu}
</div>
);

View File

@ -7,12 +7,14 @@ import { useContextMenu } from '../require/react-helper';
export interface GuildTitleProps {
guild: CombinedGuild;
guildMeta: GuildMetadata | null;
guildMetaGuild: CombinedGuild | null;
selfMember: Member | null;
selfMemberGuild: CombinedGuild | null;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const GuildTitle: FC<GuildTitleProps> = (props: GuildTitleProps) => {
const { guild, guildMeta, selfMember, setOverlay } = props;
const { guild, guildMeta, guildMetaGuild, selfMember, selfMemberGuild, setOverlay } = props;
const rootRef = useRef<HTMLDivElement>(null);
@ -27,16 +29,16 @@ const GuildTitle: FC<GuildTitleProps> = (props: GuildTitleProps) => {
}, [ selfMember ]);
const [ contextMenu, toggleContextMenu ] = useContextMenu((close: () => void) => {
if (!guildMeta) return null;
if (!selfMember) return null;
if (!guildMeta || !guildMetaGuild) return null;
if (!selfMember || !selfMemberGuild) return null;
return (
<GuildTitleContextMenu
relativeToRef={rootRef} close={close}
guild={guild} guildMeta={guildMeta} selfMember={selfMember}
guild={guild} guildMeta={guildMeta} guildMetaGuild={guildMetaGuild} selfMember={selfMember}
setOverlay={setOverlay}
/>
);
}, [ guild, guildMeta, selfMember, rootRef ]);
}, [ guild, guildMeta, guildMetaGuild, selfMember, selfMemberGuild, rootRef ]);
const nameStyle = useMemo(() => {
if (hasContextMenu) {

View File

@ -1,3 +1,8 @@
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 React, { Dispatch, FC, ReactNode, SetStateAction, useEffect, useState } from 'react';
import { Channel } from '../../data-types';
import CombinedGuild from '../../guild-combined';
@ -27,8 +32,8 @@ const GuildElement: FC<GuildElementProps> = (props: GuildElementProps) => {
const [ guildMeta, guildMetaGuild, guildMetaFetchError ] = useGuildMetadataSubscription(guild);
const [ membersRetry, members, membersGuild, membersFetchError ] = useMembersSubscription(guild);
const [ channelsRetry, channels, channelsGuild, channelsFetchError ] = useChannelsSubscription(guild);
const [ activeChannel, setActiveChannel ] = useState<Channel | null>(null);
const [ activeChannelGuild, setActiveChannelGuild ] = useState<CombinedGuild | null>(null);
useEffect(() => {
setActiveChannel(null);
@ -37,36 +42,41 @@ const GuildElement: FC<GuildElementProps> = (props: GuildElementProps) => {
useEffect(() => {
if (activeChannel === null) {
// initial active channel is the first one in the list
if (channels && channels.length > 0) {
if (channels && channelsGuild && channels.length > 0) {
setActiveChannel(channels[0] as Channel);
setActiveChannelGuild(channelsGuild);
//LOG.debug('Active channel guild enabled', { channel: channels[0]?.id, channelsGuild: channelsGuild?.id });
}
} else if (channels && activeChannel) {
} else if (channels && channelsGuild && activeChannel) {
// in the active channel was updated
setActiveChannel(channels.find(channel => channel.id === activeChannel.id) ?? null);
const newActiveChannel = channels.find(channel => channel.id === activeChannel.id) ?? null
setActiveChannel(newActiveChannel);
setActiveChannelGuild(channelsGuild);
//LOG.debug('Active channel was updated...', { channel: newActiveChannel?.id, channelsGuild: channelsGuild?.id });
}
}, [ channels, activeChannel ]);
}, [ channels, channelsGuild, activeChannel ]);
return (
<div className="guild-react">
<div className="guild-sidebar">
<GuildTitle guild={guild} selfMember={selfMember} guildMeta={guildMeta} setOverlay={setOverlay} />
<GuildTitle guild={guild} selfMember={selfMember} selfMemberGuild={selfMemberGuild} guildMeta={guildMeta} guildMetaGuild={guildMetaGuild} setOverlay={setOverlay} />
<ChannelList
guild={guild} selfMember={selfMember}
channels={channels} channelsFetchError={channelsFetchError}
activeChannel={activeChannel} setActiveChannel={setActiveChannel}
setOverlay={setOverlay}
/>
<ConnectionInfo guild={guild} selfMember={selfMember} setOverlay={setOverlay} />
<ConnectionInfo guild={guild} selfMember={selfMember} selfMemberGuild={selfMemberGuild} setOverlay={setOverlay} />
</div>
<div className="guild-channel">
<ChannelTitle channel={activeChannel} />
<div className="guild-channel-content">
<div className="guild-channel-feed-wrapper">
{activeChannel && <MessageList guild={guild} channel={activeChannel} setOverlay={setOverlay} />}
{activeChannel && <SendMessage guild={guild} channel={activeChannel} />}
{activeChannel && activeChannelGuild && <MessageList guild={guild} channel={activeChannel} channelGuild={activeChannelGuild} setOverlay={setOverlay} />}
{activeChannel && activeChannelGuild && <SendMessage guild={guild} channel={activeChannel} />}
</div>
<div className="member-list-wrapper">
<MemberList guild={guild} members={members} membersFetchError={membersFetchError} />
<MemberList guild={guild} members={members} membersGuild={membersGuild} membersFetchError={membersFetchError} />
</div>
</div>
</div>