pure react overlays

This commit is contained in:
Michael Peters 2021-12-29 23:53:04 -06:00
parent 4909998144
commit c90641a14c
29 changed files with 168 additions and 167 deletions

View File

@ -4,35 +4,25 @@ import Logger from '../../../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import React, { FC, RefObject, useCallback, useEffect } from "react";
import ElementsUtil from '../require/elements-util';
import ReactHelper from '../require/react-helper';
interface OverlayProps {
childRootRef?: RefObject<HTMLElement>; // clicks outside this ref will close the overlay
close?: () => void;
close: () => void;
children: React.ReactNode;
}
const Overlay: FC<OverlayProps> = (props: OverlayProps) => {
const { childRootRef, close, children } = props;
const removeSelf = useCallback(() => {
if (close) {
close();
} else {
LOG.warn('closing react overlay with ElementsUtil (deprecated)');
ElementsUtil.closeReactOverlay(document);
}
}, []);
if (childRootRef) {
ReactHelper.useCloseWhenClickedOrContextOutsideEffect(childRootRef, () => { removeSelf(); });
ReactHelper.useCloseWhenClickedOrContextOutsideEffect(childRootRef, close);
}
const keyDownHandler = useCallback((e: KeyboardEvent) => {
if (e.key === 'Escape') {
removeSelf();
close()
}
}, [ removeSelf ]);
}, [ close ]);
useEffect(() => {
window.addEventListener('keydown', keyDownHandler);

View File

@ -1,8 +1,7 @@
import React, { FC, RefObject, useCallback, useMemo } from 'react';
import React, { Dispatch, FC, ReactNode, RefObject, SetStateAction, useCallback, useMemo } from 'react';
import { Member } from '../../data-types';
import CombinedGuild from '../../guild-combined';
import PersonalizeOverlay from '../overlays/overlay-personalize';
import ElementsUtil from '../require/elements-util';
import ContextMenu from './components/context-menu';
export interface ConnectionInfoContextMenuProps {
@ -10,10 +9,11 @@ export interface ConnectionInfoContextMenuProps {
selfMember: Member;
relativeToRef: RefObject<HTMLElement>;
close: () => void;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const ConnectionInfoContextMenu: FC<ConnectionInfoContextMenuProps> = (props: ConnectionInfoContextMenuProps) => {
const { guild, selfMember, relativeToRef, close } = props;
const { guild, selfMember, relativeToRef, close, setOverlay } = props;
const setSelfStatus = useCallback(async (status: string) => {
await guild.requestSetStatus(status);
@ -33,9 +33,7 @@ const ConnectionInfoContextMenu: FC<ConnectionInfoContextMenuProps> = (props: Co
const openPersonalize = useCallback(() => {
close();
// Note: using global document, not very safe >:|
// TODO: Do this in full react (also fixes global document problem)
ElementsUtil.presentReactOverlay(document, <PersonalizeOverlay document={document} guild={guild} selfMember={selfMember} />);
setOverlay(<PersonalizeOverlay document={document} guild={guild} selfMember={selfMember} close={() => setOverlay(null)} />);
}, [ guild, selfMember, close ]);
const alignment = useMemo(() => {

View File

@ -1,10 +1,9 @@
import React, { FC, RefObject, useCallback, useMemo } from 'react';
import React, { Dispatch, FC, ReactNode, RefObject, SetStateAction, useCallback, useMemo } from 'react';
import { GuildMetadata, Member } from '../../data-types';
import CombinedGuild from '../../guild-combined';
import ChannelOverlay from '../overlays/overlay-channel';
import GuildSettingsOverlay from '../overlays/overlay-guild-settings';
import BaseElements from '../require/base-elements';
import ElementsUtil from '../require/elements-util';
import ContextMenu from './components/context-menu';
export interface GuildTitleContextMenuProps {
@ -13,19 +12,20 @@ export interface GuildTitleContextMenuProps {
guild: CombinedGuild;
guildMeta: GuildMetadata;
selfMember: Member;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const GuildTitleContextMenu: FC<GuildTitleContextMenuProps> = (props: GuildTitleContextMenuProps) => {
const { close, relativeToRef, guild, guildMeta, selfMember } = props;
const { close, relativeToRef, guild, guildMeta, selfMember, setOverlay } = props;
const openGuildSettings = useCallback(() => {
close();
ElementsUtil.presentReactOverlay(document, <GuildSettingsOverlay guild={guild} guildMeta={guildMeta} />);
setOverlay(<GuildSettingsOverlay guild={guild} guildMeta={guildMeta} close={() => setOverlay(null)} />);
}, [ guild, guildMeta, close ]);
const openCreateChannel = useCallback(() => {
close();
ElementsUtil.presentReactOverlay(document, <ChannelOverlay guild={guild} />);
setOverlay(<ChannelOverlay guild={guild} close={() => setOverlay(null)} />);
}, [ guild, close ]);
const guildSettingsElement = useMemo(() => {

View File

@ -1,4 +1,4 @@
import React, { Dispatch, FC, SetStateAction, useMemo } from 'react'
import React, { Dispatch, FC, ReactNode, SetStateAction, useMemo } from 'react'
import { Channel, Member } from '../../data-types';
import CombinedGuild from '../../guild-combined';
import ChannelElement from './components/channel-element';
@ -10,10 +10,15 @@ export interface ChannelListProps {
channelsFetchError: unknown | null;
activeChannel: Channel | null;
setActiveChannel: Dispatch<SetStateAction<Channel | null>>;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const ChannelList: FC<ChannelListProps> = (props: ChannelListProps) => {
const { guild, selfMember, channels, channelsFetchError, activeChannel, setActiveChannel } = props;
const { guild, selfMember, channels, channelsFetchError, activeChannel, setActiveChannel, setOverlay } = props;
const hasModifyPrivilege = selfMember && selfMember.privileges.includes('modify_channels');
const baseClassName = hasModifyPrivilege ? 'channel-list modify_channels' : 'channel-list'
const channelElements = useMemo(() => {
if (!selfMember) return null;
@ -29,12 +34,14 @@ const ChannelList: FC<ChannelListProps> = (props: ChannelListProps) => {
key={channel.id} guild={guild}
selfMember={selfMember}
channel={channel} activeChannel={activeChannel}
setActiveChannel={() => { setActiveChannel(channel); }} />
setActiveChannel={() => { setActiveChannel(channel); }}
setOverlay={setOverlay}
/>
));
}, [ selfMember, channelsFetchError, channels, guild, selfMember, activeChannel ]);
return (
<div className="channel-list">
<div className={baseClassName}>
{channelElements}
</div>
);

View File

@ -3,12 +3,11 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
import Logger from '../../../../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import React, { Dispatch, FC, MouseEvent, SetStateAction, useCallback, useRef } from 'react'
import React, { Dispatch, FC, MouseEvent, ReactNode, SetStateAction, useCallback, useRef } from 'react'
import { Channel, Member } from '../../../data-types';
import CombinedGuild from '../../../guild-combined';
import ChannelOverlay from '../../overlays/overlay-channel';
import BaseElements from '../../require/base-elements';
import ElementsUtil from '../../require/elements-util';
import ReactHelper from '../../require/react-helper';
import BasicHover, { BasicHoverSide } from '../../contexts/context-hover-basic';
@ -18,10 +17,11 @@ export interface ChannelElementProps {
selfMember: Member; // Note: Expected to use this later since it may not be best to have css-based hiding
activeChannel: Channel | null;
setActiveChannel: Dispatch<SetStateAction<Channel | null>>;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const ChannelElement: FC<ChannelElementProps> = (props: ChannelElementProps) => {
const { guild, channel, activeChannel, setActiveChannel } = props;
const { guild, channel, selfMember, activeChannel, setActiveChannel, setOverlay } = props;
const modifyRef = useRef<HTMLDivElement>(null);
@ -45,9 +45,8 @@ const ChannelElement: FC<ChannelElementProps> = (props: ChannelElementProps) =>
setActiveChannel(channel);
}, [ modifyRef, channel ]);
// Note: this element will be hidden by CSS
const launchModify = useCallback(() => {
ElementsUtil.presentReactOverlay(document, <ChannelOverlay guild={guild} channel={channel} />);
setOverlay(<ChannelOverlay guild={guild} channel={channel} close={() => { setOverlay(null); }} />);
}, [ guild, channel ]);
return (

View File

@ -1,5 +1,5 @@
import moment from 'moment';
import React, { FC, MouseEvent, useCallback, useMemo, useState } from 'react';
import React, { Dispatch, FC, MouseEvent, ReactNode, SetStateAction, useCallback, useMemo, useState } from 'react';
import { Member, Message } from '../../../data-types';
import CombinedGuild from '../../../guild-combined';
import ImageContextMenu from '../../contexts/context-menu-image';
@ -46,10 +46,11 @@ interface PreviewImageElementProps {
resourcePreviewId: string;
resourceId: string;
resourceName: string;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const PreviewImageElement: FC<PreviewImageElementProps> = (props: PreviewImageElementProps) => {
const { guild, previewWidth, previewHeight, resourcePreviewId, resourceId, resourceName } = props;
const { guild, previewWidth, previewHeight, resourcePreviewId, resourceId, resourceName, setOverlay } = props;
// TODO: Handle resourceError
const [ previewImgSrc, previewResource, previewResourceError ] = GuildSubscriptions.useSoftImageSrcResourceSubscription(guild, resourcePreviewId);
@ -66,8 +67,7 @@ const PreviewImageElement: FC<PreviewImageElementProps> = (props: PreviewImageEl
}, [ previewResource, relativeToPos, resourceName ]);
const openImageOverlay = useCallback(() => {
// Note: document here isn't 100% guaranteed (I think) but we should be getting rid of this eventually anyway
ElementsUtil.presentReactOverlay(document, <ImageOverlay guild={guild} resourceId={resourceId} resourceName={resourceName} />);
setOverlay(<ImageOverlay guild={guild} resourceId={resourceId} resourceName={resourceName} close={() => setOverlay(null)} />);
}, [ guild, resourceId, resourceName ]);
const onContextMenu = useCallback((event: MouseEvent<HTMLImageElement>) => {
@ -90,10 +90,11 @@ export interface MessageElementProps {
guild: CombinedGuild;
message: Message;
prevMessage: Message | null;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const MessageElement: FC<MessageElementProps> = (props: MessageElementProps) => {
const { guild, message, prevMessage } = props;
const { guild, message, prevMessage, setOverlay } = props;
const className = useMemo(() => {
return message.isContinued(prevMessage) ? 'message-react continued' : 'message-react';
@ -148,6 +149,7 @@ const MessageElement: FC<MessageElementProps> = (props: MessageElementProps) =>
resourcePreviewId={message.resourcePreviewId}
resourceId={message.resourceId}
resourceName={message.resourceName ?? 'unknown.unk'}
setOverlay={setOverlay}
/>
);
} else {
@ -157,7 +159,7 @@ const MessageElement: FC<MessageElementProps> = (props: MessageElementProps) =>
/>
);
}
}, [ message ])
}, [ message ]);
return (
<div className={className}>

View File

@ -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, useMemo } from 'react';
import React, { Dispatch, FC, ReactNode, SetStateAction, useMemo } from 'react';
import { Channel, Message } from '../../data-types';
import CombinedGuild from '../../guild-combined';
import MessageElement from './components/message-element';
@ -13,10 +13,11 @@ import InfiniteScroll from '../components/infinite-scroll';
interface MessageListProps {
guild: CombinedGuild;
channel: Channel;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
const { guild, channel } = props;
const { guild, channel, setOverlay } = props;
const [
fetchRetryCallable,
@ -36,7 +37,7 @@ const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
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} />);
result.push(<MessageElement key={message.id} guild={guild} message={message} prevMessage={prevMessage} setOverlay={setOverlay} />);
}
}
return result;

View File

@ -1,6 +1,6 @@
@import "../../styles/theme.scss";
#react-overlays > .overlay > .content.add-guild {
.react-overlays > .overlay > .content.add-guild {
min-width: 350px;
background-color: $background-secondary;
border-radius: 8px;

View File

@ -12,7 +12,6 @@ import Globals from '../../globals';
import SubmitOverlayLower from '../components/submit-overlay-lower';
import path from 'path';
import CombinedGuild from '../../guild-combined';
import ElementsUtil from '../require/elements-util';
import InvitePreview from '../components/invite-preview';
import ReactHelper from '../require/react-helper';
import * as fs from 'fs/promises';
@ -54,10 +53,11 @@ export interface AddGuildOverlayProps {
guildsManager: GuildsManager;
addGuildData: IAddGuildData;
setActiveGuild: Dispatch<SetStateAction<CombinedGuild | null>>;
close: () => void;
}
const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps) => {
const { guildsManager, addGuildData, setActiveGuild } = props;
const { guildsManager, addGuildData, setActiveGuild, close } = props;
const rootRef = useRef<HTMLDivElement>(null);
@ -103,15 +103,15 @@ const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps)
newGuild = await guildsManager.addNewGuild(addGuildData, displayName, avatarBuff);
} catch (e: unknown) {
LOG.error('error adding new guild', e);
return { result: null, errorMessage: 'Error adding new guild' };
return { result: null, errorMessage: (e as Error).message ?? 'Error adding new guild' };
}
setActiveGuild(newGuild);
ElementsUtil.closeReactOverlay(document);
close();
return { result: newGuild, errorMessage: null };
},
[ displayName, avatarBuff, displayNameInputValid, avatarInputValid ]
[ displayName, avatarBuff, displayNameInputValid, avatarInputValid, close ]
);
const errorMessage = useMemo(() => {
@ -121,7 +121,7 @@ const AddGuildOverlay: FC<AddGuildOverlayProps> = (props: AddGuildOverlayProps)
}, [ validationErrorMessage, submitFailMessage ]);
return (
<Overlay childRootRef={rootRef}>
<Overlay childRootRef={rootRef} close={close} >
<div ref={rootRef} className="content add-guild">
<InvitePreview
name={addGuildData.name} iconSrc={addGuildData.iconSrc}

View File

@ -1,6 +1,6 @@
@import "../../styles/theme.scss";
#react-overlays > .overlay > .content.modify-channel {
.react-overlays > .overlay > .content.modify-channel {
min-width: 350px;
max-width: calc(100vw - 80px);

View File

@ -9,7 +9,6 @@ import BaseElements from '../require/base-elements';
import TextInput from '../components/input-text';
import SubmitOverlayLower from '../components/submit-overlay-lower';
import Globals from '../../globals';
import ElementsUtil from '../require/elements-util';
import { Channel } from '../../data-types';
import ReactHelper from '../require/react-helper';
import Button from '../components/button';
@ -18,9 +17,10 @@ import Overlay from '../components/overlay';
export interface ChannelOverlayProps {
guild: CombinedGuild;
channel?: Channel;
close: () => void;
}
const ChannelOverlay: FC<ChannelOverlayProps> = (props: ChannelOverlayProps) => {
const { guild, channel } = props;
const { guild, channel, close } = props;
const rootRef = useRef<HTMLDivElement>(null);
const nameInputRef = useRef<HTMLInputElement>(null);
@ -66,7 +66,7 @@ const ChannelOverlay: FC<ChannelOverlayProps> = (props: ChannelOverlayProps) =>
if (validationErrorMessage) return { result: null, errorMessage: 'Invalid input' };
if (!edited) {
ElementsUtil.closeReactOverlay(document);
close();
return { result: null, errorMessage: null };
}
@ -82,10 +82,10 @@ const ChannelOverlay: FC<ChannelOverlayProps> = (props: ChannelOverlayProps) =>
return { result: null, errorMessage: `Error ${channel ? 'updating' : 'creating'} channel`}
}
ElementsUtil.closeReactOverlay(document);
close();
return { result: null, errorMessage: null };
},
[ edited, validationErrorMessage, name, flavorText ],
[ edited, validationErrorMessage, name, flavorText, close ],
{ start: channel ? 'Modify Channel' : 'Create Channel' }
);
@ -96,7 +96,7 @@ const ChannelOverlay: FC<ChannelOverlayProps> = (props: ChannelOverlayProps) =>
}, [ validationErrorMessage, submitFailMessage ]);
return (
<Overlay childRootRef={rootRef}>
<Overlay childRootRef={rootRef} close={close} >
<div ref={rootRef} className="content submit-dialog modify-channel">
<div className="preview channel-title">
<div className="channel-icon">{BaseElements.TEXT_CHANNEL_ICON}</div>

View File

@ -1,6 +1,6 @@
@import "../../styles/theme.scss";
#react-overlays > .overlay > .content.error-message {
.react-overlays > .overlay > .content.error-message {
background-color: $background-secondary;
padding: 16px;
border-radius: 8px;

View File

@ -4,14 +4,15 @@ import Overlay from '../components/overlay';
export interface ErrorMessageOverlayProps {
title: string;
message: string;
close: () => void;
}
const ErrorMessageOverlay: FC<ErrorMessageOverlayProps> = (props: ErrorMessageOverlayProps) => {
const { title, message } = props;
const { title, message, close } = props;
const rootRef = useRef<HTMLDivElement>(null);
return (
<Overlay childRootRef={rootRef}>
<Overlay childRootRef={rootRef} close={close}>
<div ref={rootRef} className="content error-message">
<div className="icon">
<img src="./img/error.png" alt="error" />

View File

@ -1,6 +1,6 @@
@import "../../styles/theme.scss";
#react-overlays > .overlay > .content.display-swapper.guild-settings {
.react-overlays > .overlay > .content.display-swapper.guild-settings {
min-width: 350px;
.overview {

View File

@ -14,9 +14,10 @@ import Overlay from '../components/overlay';
export interface GuildSettingsOverlayProps {
guild: CombinedGuild;
guildMeta: GuildMetadata;
close: () => void;
}
const GuildSettingsOverlay: FC<GuildSettingsOverlayProps> = (props: GuildSettingsOverlayProps) => {
const { guild, guildMeta } = props;
const { guild, guildMeta, close } = props;
const rootRef = useRef<HTMLDivElement>(null);
@ -30,7 +31,7 @@ const GuildSettingsOverlay: FC<GuildSettingsOverlayProps> = (props: GuildSetting
}, [ selectedId ]);
return (
<Overlay childRootRef={rootRef}>
<Overlay childRootRef={rootRef} close={close}>
<div ref={rootRef} className="content display-swapper guild-settings">
<ChoicesControl title={guildMeta.name} selectedId={selectedId} setSelectedId={setSelectedId} choices={[
{ id: 'overview', display: 'Overview' },

View File

@ -1,6 +1,6 @@
@import "../../styles/theme.scss";
#react-overlays > .overlay > .content.popup-image {
.react-overlays > .overlay > .content.popup-image {
display: flex;
flex-flow: column;
align-items: center;

View File

@ -15,11 +15,12 @@ import Overlay from '../components/overlay';
export interface ImageOverlayProps {
guild: CombinedGuild
resourceId: string,
resourceName: string
resourceName: string,
close: () => void;
}
const ImageOverlay: FC<ImageOverlayProps> = (props: ImageOverlayProps) => {
const { guild, resourceId, resourceName } = props;
const { guild, resourceId, resourceName, close } = props;
const rootRef = useRef<HTMLDivElement>(null);
@ -44,7 +45,7 @@ const ImageOverlay: FC<ImageOverlayProps> = (props: ImageOverlayProps) => {
const sizeText = useMemo(() => resource ? ElementsUtil.humanSize(resource.data.length) : 'Loading Size...', [ resource ]);
return (
<Overlay childRootRef={rootRef}>
<Overlay childRootRef={rootRef} close={close}>
<div ref={rootRef} className="content popup-image">
<img src={imgSrc} alt={resourceName} title={resourceName} onContextMenu={onContextMenu}></img>
<div className="download">

View File

@ -1,6 +1,6 @@
@import "../../styles/theme.scss";
#react-overlays > .overlay > .content.personalize {
.react-overlays > .overlay > .content.personalize {
background-color: $background-primary;
border-radius: 8px;

View File

@ -10,7 +10,6 @@ import CombinedGuild from '../../guild-combined';
import ImageEditInput from '../components/input-image-edit';
import TextInput from '../components/input-text';
import SubmitOverlayLower from '../components/submit-overlay-lower';
import ElementsUtil from '../require/elements-util';
import GuildSubscriptions from '../require/guild-subscriptions';
import ReactHelper from '../require/react-helper';
import Button from '../components/button';
@ -20,9 +19,10 @@ export interface PersonalizeOverlayProps {
document: Document;
guild: CombinedGuild;
selfMember: Member;
close: () => void;
}
const PersonalizeOverlay: FC<PersonalizeOverlayProps> = (props: PersonalizeOverlayProps) => {
const { document, guild, selfMember } = props;
const { document, guild, selfMember, close } = props;
const rootRef = useRef<HTMLDivElement>(null);
@ -94,10 +94,10 @@ const PersonalizeOverlay: FC<PersonalizeOverlayProps> = (props: PersonalizeOverl
}
}
ElementsUtil.closeReactOverlay(document);
close();
return { result: null, errorMessage: null };
},
[ validationErrorMessage, displayName, savedDisplayName, avatarBuff, savedAvatarBuff ]
[ validationErrorMessage, displayName, savedDisplayName, avatarBuff, savedAvatarBuff, close ]
);
//if (saveFailed) return 'Unable to save personalization';
@ -108,7 +108,7 @@ const PersonalizeOverlay: FC<PersonalizeOverlayProps> = (props: PersonalizeOverl
}, [ validationErrorMessage, submitFailMessage ]);
return (
<Overlay childRootRef={rootRef}>
<Overlay childRootRef={rootRef} close={close}>
<div ref={rootRef} className="content personalize">
<div className="personalization">
<div className="avatar">

View File

@ -1,3 +1,4 @@
import React from 'react';
import * as fs from 'fs/promises';
import * as electronRemote from '@electron/remote';
@ -11,9 +12,6 @@ import * as uuid from 'uuid';
import Util from '../../util';
import CombinedGuild from '../../guild-combined';
import React from 'react';
import ReactDOM from 'react-dom';
export interface IAlignment {
left?: string;
centerX?: string;
@ -317,18 +315,4 @@ export default class ElementsUtil {
throw new Error('x-alignment not defined');
}
}
static presentReactOverlay(document: Document, overlay: JSX.Element) {
// for aids reasons, the click event gets sent through to the overlay so we're just adding a sleep
// here to break the event loop. Hopefully this gets better when we don't have to do a seperate render piece
// and we handle overlays through 100% react
(async () => {
await Util.sleep(0);
ReactDOM.render(overlay, document.querySelector('#react-overlays'));
})();
}
static closeReactOverlay(document: Document) {
ReactDOM.unmountComponentAtNode(document.querySelector('#react-overlays') as HTMLElement);
}
}

View File

@ -1,4 +1,4 @@
import React, { FC } from 'react';
import React, { FC, ReactNode, useState } from 'react';
import GuildsManager from '../guilds-manager';
import GuildsManagerElement from './sections/guilds-manager';
import TitleBar from './sections/title-bar';
@ -10,10 +10,13 @@ export interface RootElementProps {
const RootElement: FC<RootElementProps> = (props: RootElementProps) => {
const { guildsManager } = props;
const [ overlay, setOverlay ] = useState<ReactNode>(null);
return (
<div>
<TitleBar />
<GuildsManagerElement guildsManager={guildsManager} />
<GuildsManagerElement guildsManager={guildsManager} setOverlay={setOverlay} />
<div className="react-overlays">{overlay}</div>
</div>
);
}

View File

@ -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, useMemo, useRef } from 'react';
import React, { Dispatch, FC, ReactNode, SetStateAction, useMemo, useRef } from 'react';
import { Member } from '../../data-types';
import CombinedGuild from '../../guild-combined';
import MemberElement, { DummyMember } from '../lists/components/member-element';
@ -13,10 +13,11 @@ import ReactHelper from '../require/react-helper';
export interface ConnectionInfoProps {
guild: CombinedGuild;
selfMember: Member | null;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const ConnectionInfo: FC<ConnectionInfoProps> = (props: ConnectionInfoProps) => {
const { guild, selfMember } = props;
const { guild, selfMember, setOverlay } = props;
const rootRef = useRef<HTMLDivElement>(null);
@ -37,7 +38,8 @@ const ConnectionInfo: FC<ConnectionInfoProps> = (props: ConnectionInfoProps) =>
if (!selfMember) return null;
return (
<ConnectionInfoContextMenu
guild={guild} selfMember={selfMember} relativeToRef={rootRef} close={close}
guild={guild} selfMember={selfMember} relativeToRef={rootRef}
close={close} setOverlay={setOverlay}
/>
);
}, [ guild, selfMember, rootRef ]);

View File

@ -3,13 +3,12 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
import Logger from '../../../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import React, { Dispatch, FC, SetStateAction, useRef } from 'react';
import React, { Dispatch, FC, ReactNode, SetStateAction, useRef } from 'react';
import CombinedGuild from '../../guild-combined';
import GuildsManager from '../../guilds-manager';
import GuildList from '../lists/guild-list';
import ReactHelper from '../require/react-helper';
import fs from 'fs/promises';
import ElementsUtil from '../require/elements-util';
import AddGuildOverlay from '../overlays/overlay-add-guild';
import ErrorMessageOverlay from '../overlays/overlay-error-message';
import BasicHover, { BasicHoverSide } from '../contexts/context-hover-basic';
@ -20,10 +19,11 @@ export interface GuildListContainerProps {
guilds: CombinedGuild[];
activeGuild: CombinedGuild | null;
setActiveGuild: Dispatch<SetStateAction<CombinedGuild | null>>;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const GuildListContainer: FC<GuildListContainerProps> = (props: GuildListContainerProps) => {
const { guildsManager, guilds, activeGuild, setActiveGuild } = props;
const { guildsManager, guilds, activeGuild, setActiveGuild, setOverlay } = props;
const addGuildRef = useRef<HTMLDivElement>(null);
@ -66,9 +66,9 @@ const GuildListContainer: FC<GuildListContainerProps> = (props: GuildListContain
typeof addGuildData?.iconSrc !== 'string'
) {
LOG.debug('bad guild data:', { addGuildData, fileText });
ElementsUtil.presentReactOverlay(document, <ErrorMessageOverlay title="Unable to parse guild file" message={'bad guild data'} />);
setOverlay(<ErrorMessageOverlay title="Unable to parse guild file" message="Bad guild data" close={() => setOverlay(null)} />);
} else {
ElementsUtil.presentReactOverlay(document, <AddGuildOverlay guildsManager={guildsManager} addGuildData={addGuildData} setActiveGuild={setActiveGuild} />);
setOverlay(<AddGuildOverlay guildsManager={guildsManager} addGuildData={addGuildData} setActiveGuild={setActiveGuild} close={() => setOverlay(null)} />);
}
}, [ guildsManager ]);

View File

@ -1,4 +1,4 @@
import React, { FC, useMemo, useRef } from 'react';
import React, { Dispatch, FC, ReactNode, SetStateAction, useMemo, useRef } from 'react';
import { GuildMetadata, Member } from '../../data-types';
import CombinedGuild from '../../guild-combined';
import GuildTitleContextMenu from '../contexts/context-menu-guild-title';
@ -8,10 +8,11 @@ export interface GuildTitleProps {
guild: CombinedGuild;
guildMeta: GuildMetadata | null;
selfMember: Member | null;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const GuildTitle: FC<GuildTitleProps> = (props: GuildTitleProps) => {
const { guild, guildMeta, selfMember } = props;
const { guild, guildMeta, selfMember, setOverlay } = props;
const rootRef = useRef<HTMLDivElement>(null);
@ -32,6 +33,7 @@ const GuildTitle: FC<GuildTitleProps> = (props: GuildTitleProps) => {
<GuildTitleContextMenu
relativeToRef={rootRef} close={close}
guild={guild} guildMeta={guildMeta} selfMember={selfMember}
setOverlay={setOverlay}
/>
);
}, [ guild, guildMeta, selfMember, rootRef ]);

View File

@ -1,4 +1,4 @@
import React, { FC, useEffect, useState } from 'react';
import React, { Dispatch, FC, ReactNode, SetStateAction, useEffect, useState } from 'react';
import { Channel } from '../../data-types';
import CombinedGuild from '../../guild-combined';
import ChannelList from '../lists/channel-list';
@ -12,10 +12,11 @@ import SendMessage from './send-message';
export interface GuildElementProps {
guild: CombinedGuild;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const GuildElement: FC<GuildElementProps> = (props: GuildElementProps) => {
const { guild } = props;
const { guild, setOverlay } = props;
// TODO: Handle fetch errors by allowing for retry
// TODO: Handle fetch errors in message list
@ -48,18 +49,20 @@ const GuildElement: FC<GuildElementProps> = (props: GuildElementProps) => {
return (
<div className="guild-react">
<div className="guild-sidebar">
<GuildTitle guild={guild} selfMember={selfMember} guildMeta={guildMeta} />
<GuildTitle guild={guild} selfMember={selfMember} guildMeta={guildMeta} setOverlay={setOverlay} />
<ChannelList
guild={guild} selfMember={selfMember}
channels={channels} channelsFetchError={channelsFetchError}
activeChannel={activeChannel} setActiveChannel={setActiveChannel} />
<ConnectionInfo guild={guild} selfMember={selfMember} />
activeChannel={activeChannel} setActiveChannel={setActiveChannel}
setOverlay={setOverlay}
/>
<ConnectionInfo guild={guild} selfMember={selfMember} 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} />}
{activeChannel && <MessageList guild={guild} channel={activeChannel} setOverlay={setOverlay} />}
{activeChannel && <SendMessage guild={guild} channel={activeChannel} />}
</div>
<div className="member-list-wrapper">

View File

@ -1,4 +1,4 @@
import React, { FC, useEffect, useState } from 'react';
import React, { Dispatch, FC, ReactNode, SetStateAction, useEffect, useState } from 'react';
import CombinedGuild from '../../guild-combined';
import GuildsManager from '../../guilds-manager';
import { useGuildListSubscription } from '../require/guilds-manager-subscriptions';
@ -7,10 +7,11 @@ import GuildListContainer from './guild-list-container';
export interface GuildsManagerElementProps {
guildsManager: GuildsManager;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const GuildsManagerElement: FC<GuildsManagerElementProps> = (props: GuildsManagerElementProps) => {
const { guildsManager } = props;
const { guildsManager, setOverlay } = props;
const [ guilds ] = useGuildListSubscription(guildsManager);
const [ activeGuild, setActiveGuild ] = useState<CombinedGuild | null>(null);
@ -28,8 +29,10 @@ const GuildsManagerElement: FC<GuildsManagerElementProps> = (props: GuildsManage
<div className="guilds-manager">
<GuildListContainer
guildsManager={guildsManager} guilds={guilds}
activeGuild={activeGuild} setActiveGuild={setActiveGuild} />
{activeGuild && <GuildElement guild={activeGuild} />}
activeGuild={activeGuild} setActiveGuild={setActiveGuild}
setOverlay={setOverlay}
/>
{activeGuild && <GuildElement guild={activeGuild} setOverlay={setOverlay} />}
</div>
);
}

View File

@ -48,4 +48,8 @@
margin-left: 6px;
}
}
.channel:hover .modify {
display: unset;
}
}

View File

@ -10,59 +10,59 @@
height: calc(100vh - 22px);
padding-top: 8px;
overflow-y: scroll;
}
.guild-list {
display: flex;
flex-flow: column;
}
.guild-listguild-list::-webkit-scrollbar {
display: none;
}
.add-guild,
.guild-list .guild {
cursor: pointer;
margin-bottom: 8px;
display: flex;
align-items: center;
}
.add-guild .pill,
.guild-list .guild .pill {
background-color: $header-primary;
width: 8px;
height: 0;
border-radius: 4px 4px;
margin-left: -4px;
margin-right: 8px;
transition: height .1s ease-in-out;
}
.guild-list .guild.active .pill {
height: 40px;
}
.guild-list .guild.unread:not(.active) .pill {
height: 8px;
}
.add-guild:hover .pill,
.guild-list .guild:not(.active):hover .pill {
height: 20px;
}
.add-guild img,
.guild-list .guild img {
width: 48px;
height: 48px;
border-radius: 24px;
transition: border-radius .1s ease-in-out;
}
.add-guild:hover img,
.guild-list .guild:hover img,
.guild-list .guild.active img {
border-radius: 16px;
.guild-list {
display: flex;
flex-flow: column;
}
.guild-listguild-list::-webkit-scrollbar {
display: none;
}
.add-guild,
.guild-list .guild {
cursor: pointer;
margin-bottom: 8px;
display: flex;
align-items: center;
}
.add-guild .pill,
.guild-list .guild .pill {
background-color: $header-primary;
width: 8px;
height: 0;
border-radius: 4px 4px;
margin-left: -4px;
margin-right: 8px;
transition: height .1s ease-in-out;
}
.guild-list .guild.active .pill {
height: 40px;
}
.guild-list .guild.unread:not(.active) .pill {
height: 8px;
}
.add-guild:hover .pill,
.guild-list .guild:not(.active):hover .pill {
height: 20px;
}
.add-guild img,
.guild-list .guild img {
width: 48px;
height: 48px;
border-radius: 24px;
transition: border-radius .1s ease-in-out;
}
.add-guild:hover img,
.guild-list .guild:hover img,
.guild-list .guild.active img {
border-radius: 16px;
}
}

View File

@ -3,7 +3,7 @@
/* Popup Image Overlay */
body > .overlay,
#react-overlays > .overlay {
.react-overlays > .overlay {
/* Note: skip top 22px so we don't overlay on the title bar */
position: absolute;
width: 100vw;