function-scoping functions for less globalism in atoms-2
This commit is contained in:
parent
6da480c36e
commit
54ad823ebe
@ -12,11 +12,10 @@ import { Duration } from 'moment';
|
||||
import moment from 'moment';
|
||||
import DropdownInput from '../components/input-dropdown';
|
||||
import Button from '../components/button';
|
||||
import { useTokensSubscription } from '../require/guild-subscriptions';
|
||||
import TokenRow from '../components/token-row';
|
||||
import CombinedGuild from '../../guild-combined';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { guildMetaState, guildResourceSoftImgSrcState, isLoaded, useRecoilValueSoftImgSrc } from '../require/atoms-2';
|
||||
import { guildMetaState, guildResourceSoftImgSrcState, guildTokensState, isFailed, isLoaded, useRecoilValueSoftImgSrc } from '../require/atoms-2';
|
||||
|
||||
export interface GuildInvitesDisplayProps {
|
||||
guild: CombinedGuild;
|
||||
@ -25,12 +24,10 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
|
||||
const { guild } = props;
|
||||
|
||||
const guildMeta = useRecoilValue(guildMetaState(guild.id));
|
||||
const tokens = useRecoilValue(guildTokensState(guild.id));
|
||||
|
||||
const url = 'https://localhost:3030'; // TODO: this will likely be a dropdown list at some point
|
||||
|
||||
// TODO: Recoilize tokens :)
|
||||
const [ _fetchRetryCallable, tokensResult, tokensError ] = useTokensSubscription(guild);
|
||||
|
||||
const [ expiresFromNow, setExpiresFromNow ] = useState<Duration | null>(moment.duration(1, 'day'));
|
||||
const [ expiresFromNowText, setExpiresFromNowText ] = useState<string>('1 day');
|
||||
|
||||
@ -66,13 +63,10 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
|
||||
}, [ createTokenFailMessage ]);
|
||||
|
||||
const tokenElements = useMemo(() => {
|
||||
if (tokensError) {
|
||||
// TODO: Try Again
|
||||
return <div className="tokens-failed">Unable to load tokens</div>;
|
||||
}
|
||||
// TODO: Try again?
|
||||
return tokensResult?.value?.map((token: Token) => <TokenRow key={guild.id + token.token} url={url} token={token} guild={guild} />);
|
||||
}, [ url, guild, tokensResult, tokensError ]);
|
||||
if (isFailed(tokens)) return <div className="tokens-failed">Unable to load tokens</div>; // TODO: Try Again
|
||||
if (!isLoaded(tokens)) return null; // TODO: Pending indicator
|
||||
return tokens.value.map((token: Token) => <TokenRow key={guild.id + token.token} url={url} token={token} guild={guild} />);
|
||||
}, [ url, guild, tokens ]);
|
||||
|
||||
return (
|
||||
<Display
|
||||
|
@ -5,7 +5,7 @@ const LOG = Logger.create(__filename, electronConsole);
|
||||
|
||||
import { ReactNode, useEffect } from "react";
|
||||
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 { Changes, Channel, GuildMetadata, Member, Resource, ShouldNeverHappenError, Token } from "../../data-types";
|
||||
import CombinedGuild from "../../guild-combined";
|
||||
import GuildsManager from "../../guilds-manager";
|
||||
import { AutoVerifierChangesType } from '../../auto-verifier';
|
||||
@ -137,28 +137,6 @@ function createFetchValueFunc<T>(
|
||||
return fetchValueFunc;
|
||||
}
|
||||
|
||||
// Creates an event handler that directly applies the result of the eventArgsMap as a loadedValue into self
|
||||
function createDirectMappedEventHandler<T, XE extends keyof (Connectable | Conflictable)>(
|
||||
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>,
|
||||
node: RecoilValue<LoadableValue<T>>,
|
||||
setSelf: (loadableValue: LoadableValue<T>) => void,
|
||||
fetchValueFunc: () => Promise<void>,
|
||||
eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Defined<T>,
|
||||
condition: (value: T) => boolean
|
||||
): (Connectable & Conflictable)[XE] {
|
||||
return ((...args: Arguments<(Connectable & Conflictable)[XE]>) => {
|
||||
(async () => {
|
||||
const selfState = await getPromise(node);
|
||||
if (isLoaded(selfState)) {
|
||||
const value = eventArgsMap(...args);
|
||||
if (condition(value)) {
|
||||
setSelf(createLoadedValue(value, fetchValueFunc));
|
||||
}
|
||||
}
|
||||
})();
|
||||
}) as (Connectable & Conflictable)[XE];
|
||||
}
|
||||
|
||||
function applyNew<T>(value: T[], newElements: T[], sortFunc: (a: T, b: T) => number): T[] {
|
||||
return value.concat(newElements).sort(sortFunc);
|
||||
}
|
||||
@ -170,30 +148,6 @@ function applyRemoved<T extends { id: string }>(value: T[], removedElements: T[]
|
||||
return value.filter(element => !removedIds.has(element.id));
|
||||
}
|
||||
|
||||
// Useful for new-xxx, update-xxx, remove-xxx list events
|
||||
function createListConnectableMappedEventHandler<T, XE extends keyof (Connectable | Conflictable)>(
|
||||
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>,
|
||||
node: RecoilValue<LoadableValue<T[]>>,
|
||||
setSelf: (loadableValue: LoadableValue<T[]>) => void,
|
||||
fetchValueFunc: () => Promise<void>,
|
||||
eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Defined<T[]>,
|
||||
sortFunc: (a: T, b: T) => number,
|
||||
applyFunc: (value: T[], eventArgsResult: T[], sortFunc: (a: T, b: T) => number) => T[],
|
||||
): (Connectable & Conflictable)[XE] {
|
||||
// I think the typed EventEmitter class isn't ready for this level of insane type safety
|
||||
// otherwise, I may have done this wrong. Forcing it to work with these calls
|
||||
return ((...args: Arguments<(Connectable & Conflictable)[XE]>) => {
|
||||
(async () => {
|
||||
const selfState = await getPromise(node);
|
||||
if (isLoaded(selfState)) {
|
||||
const eventArgsResult = eventArgsMap(...args);
|
||||
const value = applyFunc(selfState.value, eventArgsResult, sortFunc);
|
||||
setSelf(createLoadedValue(value, fetchValueFunc));
|
||||
}
|
||||
})();
|
||||
}) as (Connectable & Conflictable)[XE];
|
||||
}
|
||||
|
||||
function applyChanges<T extends { id: string }>(value: T[], changes: Changes<T>, sortFunc: (a: T, b: T) => number): T[] {
|
||||
const removedIds = new Set<string>(changes.deleted.map(deletedElement => deletedElement.id));
|
||||
return value
|
||||
@ -203,28 +157,6 @@ function applyChanges<T extends { id: string }>(value: T[], changes: Changes<T>,
|
||||
.sort(sortFunc);
|
||||
}
|
||||
|
||||
// Useful for conflict-xxx list events
|
||||
function createListConflictableMappedEventHandler<T, XE extends keyof (Connectable | Conflictable)>(
|
||||
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>,
|
||||
node: RecoilValue<LoadableValue<T[]>>,
|
||||
setSelf: (loadableValue: LoadableValue<T[]>) => void,
|
||||
fetchValueFunc: () => Promise<void>,
|
||||
eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Changes<T>,
|
||||
sortFunc: (a: T, b: T) => number,
|
||||
applyFunc: (value: T[], eventArgsResult: Changes<T>, sortFunc: (a: T, b: T) => number) => T[],
|
||||
): (Connectable & Conflictable)[XE] {
|
||||
return ((...args: Arguments<(Connectable & Conflictable)[XE]>) => {
|
||||
(async () => {
|
||||
const selfState = await getPromise(node);
|
||||
if (isLoaded(selfState)) {
|
||||
const eventArgsResult = eventArgsMap(...args);
|
||||
const value = applyFunc(selfState.value, eventArgsResult, sortFunc);
|
||||
setSelf(createLoadedValue(value, fetchValueFunc));
|
||||
}
|
||||
})();
|
||||
}) as (Connectable & Conflictable)[XE];
|
||||
}
|
||||
|
||||
interface SingleEventMappingParams<T, UE extends keyof Connectable, CE extends keyof Conflictable> {
|
||||
updatedEventName: UE;
|
||||
updatedEventArgsMap: (...args: Arguments<(Connectable & Conflictable)[UE]>) => Defined<T>;
|
||||
@ -253,13 +185,31 @@ function listenToSingle<
|
||||
let closed = false;
|
||||
(async () => {
|
||||
guild = await getPromise(guildState(guildId));
|
||||
if (guild === null) return;
|
||||
if (guild === null) return; // TODO: This would put the atom in an infinite loading state. Look into useCallback for a potential way to prevent this...
|
||||
if (closed) return; // Make sure not to bind events if this closed while we were fetching the guild state
|
||||
|
||||
// Creates an event handler that directly applies the result of the eventArgsMap as a loadedValue into self
|
||||
function createConnectableHandler<XE extends keyof (Connectable | Conflictable)>(
|
||||
eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Defined<T>,
|
||||
condition: (value: T) => boolean
|
||||
): (Connectable & Conflictable)[XE] {
|
||||
return ((...args: Arguments<(Connectable & Conflictable)[XE]>) => {
|
||||
(async () => {
|
||||
const selfState = await getPromise(node);
|
||||
if (isLoaded(selfState)) {
|
||||
const value = eventArgsMap(...args);
|
||||
if (condition(value)) {
|
||||
setSelf(createLoadedValue(value, fetchValueFunc));
|
||||
}
|
||||
}
|
||||
})();
|
||||
}) as (Connectable & Conflictable)[XE];
|
||||
}
|
||||
|
||||
// I think the typed EventEmitter class isn't ready for this level of insane type safety
|
||||
// otherwise, I may have done this wrong. Forcing it to work with these calls
|
||||
onUpdateFunc = createDirectMappedEventHandler(getPromise, node, setSelf, fetchValueFunc, eventMapping.updatedEventArgsMap, eventMapping.updatedEventCondition ?? (() => true));
|
||||
onConflictFunc = createDirectMappedEventHandler(getPromise, node, setSelf, fetchValueFunc, eventMapping.conflictEventArgsMap, eventMapping.conflictEventCondition ?? (() => true));
|
||||
onUpdateFunc = createConnectableHandler(eventMapping.updatedEventArgsMap, eventMapping.updatedEventCondition ?? (() => true));
|
||||
onConflictFunc = createConnectableHandler(eventMapping.conflictEventArgsMap, eventMapping.conflictEventCondition ?? (() => true));
|
||||
guild.on(eventMapping.updatedEventName, onUpdateFunc);
|
||||
guild.on(eventMapping.conflictEventName, onConflictFunc);
|
||||
})();
|
||||
@ -314,11 +264,49 @@ function listenToMultiple<
|
||||
if (guild === null) return;
|
||||
if (closed) return; // Make sure not to bind events if this closed while we were fetching the guild state
|
||||
|
||||
onNewFunc = createListConnectableMappedEventHandler(getPromise, node, setSelf, fetchValueFunc, eventMapping.newEventArgsMap, sortFunc, applyNew);
|
||||
onUpdateFunc = createListConnectableMappedEventHandler(getPromise, node, setSelf, fetchValueFunc, eventMapping.updatedEventArgsMap, sortFunc, applyUpdated);
|
||||
onRemoveFunc = createListConnectableMappedEventHandler(getPromise, node, setSelf, fetchValueFunc, eventMapping.updatedEventArgsMap, sortFunc, applyRemoved);
|
||||
onConflictFunc = createListConflictableMappedEventHandler(getPromise, node, setSelf, fetchValueFunc, eventMapping.conflictEventArgsMap, sortFunc, applyChanges);
|
||||
// Useful for new-xxx, update-xxx, remove-xxx list events
|
||||
function createConnectableHandler<XE extends keyof (Connectable | Conflictable)>(
|
||||
eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Defined<T[]>,
|
||||
applyFunc: (value: T[], eventArgsResult: T[], sortFunc: (a: T, b: T) => number) => T[],
|
||||
): (Connectable & Conflictable)[XE] {
|
||||
// I think the typed EventEmitter class isn't ready for this level of insane type safety
|
||||
// otherwise, I may have done this wrong. Forcing it to work with these calls
|
||||
return ((...args: Arguments<(Connectable & Conflictable)[XE]>) => {
|
||||
(async () => {
|
||||
const selfState = await getPromise(node);
|
||||
if (isLoaded(selfState)) {
|
||||
const eventArgsResult = eventArgsMap(...args);
|
||||
const value = applyFunc(selfState.value, eventArgsResult, sortFunc);
|
||||
setSelf(createLoadedValue(value, fetchValueFunc));
|
||||
}
|
||||
})();
|
||||
}) as (Connectable & Conflictable)[XE];
|
||||
}
|
||||
|
||||
// Useful for conflict-xxx list events
|
||||
function createConflictableHandler<XE extends keyof (Connectable | Conflictable)>(
|
||||
eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Changes<T>,
|
||||
applyFunc: (value: T[], eventArgsResult: Changes<T>, sortFunc: (a: T, b: T) => number) => T[],
|
||||
): (Connectable & Conflictable)[XE] {
|
||||
return ((...args: Arguments<(Connectable & Conflictable)[XE]>) => {
|
||||
(async () => {
|
||||
const selfState = await getPromise(node);
|
||||
if (isLoaded(selfState)) {
|
||||
const eventArgsResult = eventArgsMap(...args);
|
||||
const value = applyFunc(selfState.value, eventArgsResult, sortFunc);
|
||||
setSelf(createLoadedValue(value, fetchValueFunc));
|
||||
}
|
||||
})();
|
||||
}) as (Connectable & Conflictable)[XE];
|
||||
}
|
||||
|
||||
onNewFunc = createConnectableHandler(eventMapping.newEventArgsMap, applyNew);
|
||||
onUpdateFunc = createConnectableHandler(eventMapping.updatedEventArgsMap, applyUpdated);
|
||||
onRemoveFunc = createConnectableHandler(eventMapping.removedEventArgsMap, applyRemoved);
|
||||
onConflictFunc = createConflictableHandler(eventMapping.conflictEventArgsMap, applyChanges);
|
||||
guild.on(eventMapping.newEventName, onNewFunc);
|
||||
guild.on(eventMapping.updatedEventName, onUpdateFunc);
|
||||
guild.on(eventMapping.removedEventName, onRemoveFunc);
|
||||
guild.on(eventMapping.conflictEventName, onConflictFunc);
|
||||
})();
|
||||
const cleanup = () => {
|
||||
@ -331,6 +319,26 @@ function listenToMultiple<
|
||||
return cleanup;
|
||||
}
|
||||
|
||||
// function listenToMultipleScrolling<
|
||||
// T extends { id: string },
|
||||
// NE extends keyof Connectable, // New Event
|
||||
// UE extends keyof Connectable, // Update Event
|
||||
// RE extends keyof Connectable, // Remove Event
|
||||
// CE extends keyof Conflictable // Conflict Event
|
||||
// >(
|
||||
// getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>,
|
||||
// guildId: number,
|
||||
// node: RecoilValue<LoadableValue<T[]>>,
|
||||
// setSelf: (loadableValue: LoadableValue<T[]>) => void,
|
||||
// fetchValueFunc: () => Promise<void>,
|
||||
// fetchAboveFunc: (reference: T) => Promise<T | null>,
|
||||
// fetchBelowFunc: (reference: T) => Promise<T | null>,
|
||||
// sortFunc: (a: T, b: T) => number,
|
||||
// eventMapping: MultipleEventMappingParams<T, NE, UE, RE, CE>
|
||||
// ) {
|
||||
// // TODO
|
||||
// }
|
||||
|
||||
function singleGuildSubscriptionEffect<
|
||||
T, // e.g. GuildMetadata
|
||||
UE extends keyof Connectable, // Update Event
|
||||
@ -543,6 +551,28 @@ const guildActiveChannelState = selectorFamily<LoadableValue<Channel>, number>({
|
||||
dangerouslyAllowMutability: true
|
||||
});
|
||||
|
||||
export const guildTokensState = atomFamily<LoadableValue<Token[]>, number>({
|
||||
key: 'guildTokensState',
|
||||
default: DEF_UNLOADED_VALUE,
|
||||
effects_UNSTABLE: (guildId: number) => [
|
||||
multipleGuildSubscriptionEffect(
|
||||
guildId,
|
||||
async (guild: CombinedGuild) => await guild.fetchTokens(),
|
||||
Token.sortRecentCreatedFirst,
|
||||
{
|
||||
newEventName: 'new-tokens',
|
||||
newEventArgsMap: (newTokens: Token[]) => newTokens,
|
||||
updatedEventName: 'update-tokens',
|
||||
updatedEventArgsMap: (updatedTokens: Token[]) => updatedTokens,
|
||||
removedEventName: 'remove-tokens',
|
||||
removedEventArgsMap: (removedTokens: Token[]) => removedTokens,
|
||||
conflictEventName: 'conflict-tokens',
|
||||
conflictEventArgsMap: (_changesType: AutoVerifierChangesType, changes: Changes<Token>) => changes
|
||||
}
|
||||
)
|
||||
]
|
||||
});
|
||||
|
||||
const guildState = selectorFamily<CombinedGuild | null, number>({
|
||||
key: 'guildState',
|
||||
get: (guildId: number) => ({ get }) => {
|
||||
@ -640,6 +670,10 @@ export const currGuildActiveChannelState = selector<LoadableValue<Channel>>({
|
||||
get: createCurrentGuildLoadableStateGetter(guildActiveChannelState),
|
||||
dangerouslyAllowMutability: true
|
||||
});
|
||||
export const currGuildTokensState = selector<LoadableValue<Token[]>>({
|
||||
key: 'currGuildTokensState',
|
||||
get: createCurrentGuildLoadableStateGetter(guildTokensState)
|
||||
});
|
||||
|
||||
// Helper functions for using softImgSrc states
|
||||
function useLoadedOrElse<T>(loadable: Loadable<T>, ifLoading: T, ifError: T) {
|
||||
|
@ -869,7 +869,7 @@ function useSelfMemberSubscription(guild: CombinedGuild): [
|
||||
* fetchError: Any error from fetching
|
||||
* ]
|
||||
*/
|
||||
export function useTokensSubscription(guild: CombinedGuild) {
|
||||
function useTokensSubscription(guild: CombinedGuild) {
|
||||
const fetchTokensFunc = useCallback(async () => {
|
||||
//LOG.silly('fetching tokens for subscription');
|
||||
return await guild.fetchTokens();
|
||||
|
Loading…
Reference in New Issue
Block a user