recoilize resources

This commit is contained in:
Michael Peters 2022-02-04 00:40:28 -06:00
parent 0c1c900724
commit 6da480c36e
8 changed files with 57 additions and 40 deletions

View File

@ -5,9 +5,8 @@ import { Member, Token } from '../../data-types';
import CombinedGuild from '../../guild-combined';
import Util from '../../util';
import { IAddGuildData } from '../overlays/overlay-add-guild';
import { guildMetaState, isLoaded } from '../require/atoms-2';
import { guildMetaState, guildResourceSoftImgSrcState, isLoaded, useRecoilValueSoftImgSrc } from '../require/atoms-2';
import BaseElements from '../require/base-elements';
import { useSoftImageSrcResourceSubscription } from '../require/guild-subscriptions';
import { useAsyncVoidCallback, useDownloadButton, useOneTimeAsyncAction } from '../require/react-helper';
import Button, { ButtonColorType } from './button';
@ -29,7 +28,7 @@ const TokenRow: FC<TokenRowProps> = (props: TokenRowProps) => {
null,
[ guild ]
);
const [ iconSrc ] = useSoftImageSrcResourceSubscription(guild, isLoaded(guildMeta) ? guildMeta.value.iconResourceId : null, guild);
const iconSrc = useRecoilValueSoftImgSrc(guildResourceSoftImgSrcState({ guildId: guild.id, resourceId: isLoaded(guildMeta) ? guildMeta.value.iconResourceId : null }));
const [ revoke ] = useAsyncVoidCallback(async () => {
await guild.requestDoRevokeToken(token.token);

View File

@ -12,11 +12,11 @@ import { Duration } from 'moment';
import moment from 'moment';
import DropdownInput from '../components/input-dropdown';
import Button from '../components/button';
import { useTokensSubscription, useSoftImageSrcResourceSubscription } from '../require/guild-subscriptions';
import { useTokensSubscription } from '../require/guild-subscriptions';
import TokenRow from '../components/token-row';
import CombinedGuild from '../../guild-combined';
import { useRecoilValue } from 'recoil';
import { guildMetaState, isLoaded } from '../require/atoms-2';
import { guildMetaState, guildResourceSoftImgSrcState, isLoaded, useRecoilValueSoftImgSrc } from '../require/atoms-2';
export interface GuildInvitesDisplayProps {
guild: CombinedGuild;
@ -34,7 +34,7 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
const [ expiresFromNow, setExpiresFromNow ] = useState<Duration | null>(moment.duration(1, 'day'));
const [ expiresFromNowText, setExpiresFromNowText ] = useState<string>('1 day');
const [ iconSrc ] = useSoftImageSrcResourceSubscription(guild, isLoaded(guildMeta) ? guildMeta.value.iconResourceId : null, guild);
const iconSrc = useRecoilValueSoftImgSrc(guildResourceSoftImgSrcState({ guildId: guild.id, resourceId: isLoaded(guildMeta) ? guildMeta.value.iconResourceId : null }));
useEffect(() => {
if (expiresFromNowText === 'never') {

View File

@ -3,10 +3,9 @@ import { useRecoilState, useRecoilValue } from 'recoil';
import CombinedGuild from '../../../guild-combined';
import ContextMenu from '../../contexts/components/context-menu';
import BasicHover, { BasicHoverSide } from '../../contexts/context-hover-basic';
import { currGuildIdState, guildMetaState, guildSelfMemberState, guildsManagerState, isLoaded } from '../../require/atoms-2';
import { currGuildIdState, guildMetaState, guildResourceSoftImgSrcState, guildSelfMemberState, guildsManagerState, isLoaded, useRecoilValueSoftImgSrc } from '../../require/atoms-2';
import BaseElements from '../../require/base-elements';
import { IAlignment } from '../../require/elements-util';
import { useSoftImageSrcResourceSubscription } from '../../require/guild-subscriptions';
import { useContextClickContextMenu, useContextHover } from '../../require/react-helper';
export interface GuildListElementProps {
@ -23,9 +22,7 @@ const GuildListElement: FC<GuildListElementProps> = (props: GuildListElementProp
const guildMeta = useRecoilValue(guildMetaState(guild.id));
const selfMember = useRecoilValue(guildSelfMemberState(guild.id));
// TODO: more state higher up
// TODO: handle metadata error
const [ iconSrc ] = useSoftImageSrcResourceSubscription(guild, isLoaded(guildMeta) ? guildMeta.value.iconResourceId : null, guild);
const iconSrc = useRecoilValueSoftImgSrc(guildResourceSoftImgSrcState({ guildId: guild.id, resourceId: isLoaded(guildMeta) ? guildMeta.value.iconResourceId : null }));
const [ contextHover, mouseEnterCallable, mouseLeaveCallable ] = useContextHover(() => {
if (!isLoaded(guildMeta)) return null; // TODO: Loading message here?

View File

@ -1,7 +1,7 @@
import React, { FC, useMemo } from 'react';
import { Member } from '../../../data-types';
import CombinedGuild from '../../../guild-combined';
import { useSoftImageSrcResourceSubscription } from '../../require/guild-subscriptions';
import { guildResourceSoftImgSrcState, useRecoilValueSoftImgSrc } from '../../require/atoms-2';
export interface DummyMember {
id: 'dummy';
@ -20,8 +20,7 @@ export interface MemberProps {
const MemberElement: FC<MemberProps> = (props: MemberProps) => {
const { guild, member } = props;
// TODO: This is a terrible line of code. Make sure to fix it whe we do resources in recoil
const [ avatarSrc ] = useSoftImageSrcResourceSubscription(guild, member.avatarResourceId, guild);
const avatarSrc = useRecoilValueSoftImgSrc(guildResourceSoftImgSrcState({ guildId: guild.id, resourceId: member.avatarResourceId }));
const nameStyle = useMemo(() => member.roleColor ? { color: member.roleColor } : {}, [ member.roleColor ]);

View File

@ -1,13 +1,12 @@
import moment from 'moment';
import React, { FC, ReactNode, useCallback, useMemo } from 'react';
import { useSetRecoilState } from 'recoil';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { Member, Message } from '../../../data-types';
import CombinedGuild from '../../../guild-combined';
import ImageContextMenu from '../../contexts/context-menu-image';
import ImageOverlay from '../../overlays/overlay-image';
import { overlayState } from '../../require/atoms-2';
import { guildResourceSoftImgSrcState, guildResourceState, isLoaded, overlayState, useRecoilValueSoftImgSrc } from '../../require/atoms-2';
import ElementsUtil, { IAlignment } from '../../require/elements-util';
import { useSoftImageSrcResourceSubscription } from '../../require/guild-subscriptions';
import { useContextClickContextMenu, useDownloadButton, useOneTimeAsyncAction } from '../../require/react-helper';
interface ResourceElementProps {
@ -48,7 +47,7 @@ interface PreviewImageElementProps {
resourcePreviewId: string;
resourceId: string;
resourceName: string;
resourceIdGuild: CombinedGuild;
resourceIdGuild: CombinedGuild; // TODO: Remove in favor of just one guild
}
const PreviewImageElement: FC<PreviewImageElementProps> = (props: PreviewImageElementProps) => {
@ -57,20 +56,21 @@ const PreviewImageElement: FC<PreviewImageElementProps> = (props: PreviewImageEl
const setOverlay = useSetRecoilState<ReactNode>(overlayState);
// TODO: Handle resourceError
const [ previewImgSrc, previewResourceResult, previewResourceError ] = useSoftImageSrcResourceSubscription(guild, resourcePreviewId, resourceIdGuild);
const previewResource = useRecoilValue(guildResourceState({ guildId: resourceIdGuild.id, resourceId: resourcePreviewId }));
const previewImgSrc = useRecoilValueSoftImgSrc(guildResourceSoftImgSrcState({ guildId: resourceIdGuild.id, resourceId: resourcePreviewId }));
const [ contextMenu, onContextMenu ] = useContextClickContextMenu((alignment: IAlignment, relativeToPos: { x: number, y: number }, close: () => void) => {
if (!previewResourceResult || !previewResourceResult.value) return null;
const [ contextMenu, onContextMenu ] = useContextClickContextMenu((_alignment: IAlignment, relativeToPos: { x: number, y: number }, close: () => void) => {
if (!isLoaded(previewResource)) return null;
return (
<ImageContextMenu
relativeToPos={relativeToPos} close={close}
resourceName={resourceName} resourceBuff={previewResourceResult.value.data} isPreview={true}
resourceName={resourceName} resourceBuff={previewResource.value.data} isPreview={true}
/>
);
}, [ previewResourceResult, resourceName ]);
}, [ previewResource, resourceName ]);
const openImageOverlay = useCallback(() => {
setOverlay(<ImageOverlay guild={guild} resourceId={resourceId} resourceName={resourceName} resourceIdGuild={resourceIdGuild} />);
setOverlay(<ImageOverlay guild={resourceIdGuild} resourceId={resourceId} resourceName={resourceName} />);
}, [ guild, resourceId, resourceName ]);
return (

View File

@ -10,36 +10,40 @@ import DownloadButton from '../components/button-download';
import { useContextClickContextMenu } from '../require/react-helper';
import ImageContextMenu from '../contexts/context-menu-image';
import Overlay from '../components/overlay';
import { useSoftImageSrcResourceSubscription } from '../require/guild-subscriptions';
import { useRecoilValue } from 'recoil';
import { guildResourceSoftImgSrcState, guildResourceState, isFailed, isLoaded, useRecoilValueSoftImgSrc } from '../require/atoms-2';
export interface ImageOverlayProps {
guild: CombinedGuild;
resourceId: string;
resourceName: string;
resourceIdGuild: CombinedGuild;
}
const ImageOverlay: FC<ImageOverlayProps> = (props: ImageOverlayProps) => {
const { guild, resourceId, resourceName, resourceIdGuild } = props;
const { guild, resourceId, resourceName } = props;
const rootRef = useRef<HTMLDivElement>(null);
const [ imgSrc, resourceResult, resourceError ] = useSoftImageSrcResourceSubscription(guild, resourceId, resourceIdGuild);
const [ contextMenu, onContextMenu ] = useContextClickContextMenu((alignment: IAlignment, relativeToPos: { x: number, y: number }, close: () => void) => {
if (!resourceResult || !resourceResult.value) return null;
// NOTE: These will update together. How convenient!
const resource = useRecoilValue(guildResourceState({ guildId: guild.id, resourceId }));
const imgSrc = useRecoilValueSoftImgSrc(guildResourceSoftImgSrcState({ guildId: guild.id, resourceId }))
// NOTE: Alignment is based on the image element, not the default context-click alignment because of ImageContextMenu
const [ contextMenu, onContextMenu ] = useContextClickContextMenu((_alignment: IAlignment, relativeToPos: { x: number, y: number }, close: () => void) => {
if (!isLoaded(resource)) return null;
return (
<ImageContextMenu
relativeToPos={relativeToPos} close={close}
resourceName={resourceName} resourceBuff={resourceResult.value.data} isPreview={false}
resourceName={resourceName} resourceBuff={resource.value.data} isPreview={false}
/>
);
}, [ resourceResult, resourceName ]);
}, [ resource, resourceName ]);
const sizeText = useMemo(
() => resourceResult?.value ? ElementsUtil.humanSize(resourceResult.value.data.length) : 'Loading Size...',
[ resourceResult ]
);
const sizeText = useMemo(() => {
if (isFailed(resource)) return 'Failed to load';
if (!isLoaded(resource)) return 'Loading size...';
return ElementsUtil.humanSize(resource.value.data.length);
}, [ resource ]);
return (
<Overlay childRootRef={rootRef}>
@ -52,7 +56,7 @@ const ImageOverlay: FC<ImageOverlayProps> = (props: ImageOverlayProps) => {
</div>
{/* TODO: I think this may break if the download button gets clicked before the resource is loaded... */}
<DownloadButton
downloadBuff={resourceResult?.value?.data} downloadBuffErr={!!resourceError}
downloadBuff={isLoaded(resource) ? resource.value.data : undefined} downloadBuffErr={isFailed(resource)}
resourceName={resourceName}></DownloadButton>
</div>
{contextMenu}

View File

@ -4,7 +4,7 @@ import Logger from '../../../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import { ReactNode, useEffect } from "react";
import { atom, atomFamily, GetRecoilValue, RecoilState, RecoilValue, RecoilValueReadOnly, selector, selectorFamily, useSetRecoilState } from "recoil";
import { atom, atomFamily, GetRecoilValue, Loadable, RecoilState, RecoilValue, RecoilValueReadOnly, selector, selectorFamily, useRecoilValueLoadable, useSetRecoilState } from "recoil";
import { Changes, Channel, GuildMetadata, Member, Resource, ShouldNeverHappenError } from "../../data-types";
import CombinedGuild from "../../guild-combined";
import GuildsManager from "../../guilds-manager";
@ -641,6 +641,24 @@ export const currGuildActiveChannelState = selector<LoadableValue<Channel>>({
dangerouslyAllowMutability: true
});
// Helper functions for using softImgSrc states
function useLoadedOrElse<T>(loadable: Loadable<T>, ifLoading: T, ifError: T) {
if (loadable.state === 'hasValue') {
return loadable.contents;
} else if (loadable.state === 'loading') {
return ifLoading;
} else {
return ifError;
}
}
function useRecoilValueLoadableOrElse<T>(recoilValue: RecoilValue<T>, ifLoading: T, ifError: T): T {
const loadableValue = useRecoilValueLoadable(recoilValue);
return useLoadedOrElse(loadableValue, ifLoading, ifError);
}
export function useRecoilValueSoftImgSrc(recoilValue: RecoilValue<string>): string {
return useRecoilValueLoadableOrElse(recoilValue, './img/loading.svg', './img/error.png');
}
// Initialize with a guildsManager
export function initRecoil(guildsManager: GuildsManager) {
const setGuildsManager = useSetRecoilState(guildsManagerState);

View File

@ -760,7 +760,7 @@ function useResourceSubscription(guild: CombinedGuild, resourceId: string | null
* fetchError: Any error from fetching
* ]
*/
export function useSoftImageSrcResourceSubscription(guild: CombinedGuild, resourceId: string | null, resourceIdGuild: CombinedGuild | null): [
function useSoftImageSrcResourceSubscription(guild: CombinedGuild, resourceId: string | null, resourceIdGuild: CombinedGuild | null): [
imgSrc: string,
resourceResult: SubscriptionResult<Resource> | null,
fetchError: unknown | null