From 5b708f2a94bc908e3d6b50141b0b35a78be22815 Mon Sep 17 00:00:00 2001 From: Michael Peters Date: Thu, 3 Feb 2022 23:08:02 -0600 Subject: [PATCH] improved single/multiple guild subscription effect - use the { node } destructed vaglue from the effect parameter --- src/client/webapp/elements/require/atoms-2.ts | 92 +++++++++++-------- .../elements/require/guild-subscriptions.ts | 4 +- 2 files changed, 58 insertions(+), 38 deletions(-) diff --git a/src/client/webapp/elements/require/atoms-2.ts b/src/client/webapp/elements/require/atoms-2.ts index ebe0c54..27238ce 100644 --- a/src/client/webapp/elements/require/atoms-2.ts +++ b/src/client/webapp/elements/require/atoms-2.ts @@ -5,14 +5,16 @@ const LOG = Logger.create(__filename, electronConsole); import { ReactNode, useEffect } from "react"; import { atom, atomFamily, GetCallback, GetRecoilValue, RecoilState, RecoilValue, RecoilValueReadOnly, selector, selectorFamily, useSetRecoilState } from "recoil"; -import { Changes, Channel, GuildMetadata, Member } from "../../data-types"; +import { Changes, Channel, GuildMetadata, Member, Resource } from "../../data-types"; import CombinedGuild from "../../guild-combined"; import GuildsManager from "../../guilds-manager"; import { AutoVerifierChangesType } from '../../auto-verifier'; import { Conflictable, Connectable } from '../../guild-types'; +import { IDQuery } from '../../auto-verifier-with-args'; // General typescript type that infers the arguments of a function type Arguments = T extends (...args: infer A) => unknown ? A : never; +// Ensures that a type is not undefined type Defined = T extends undefined ? never : T | Awaited; export type UnloadedValue = { @@ -101,6 +103,7 @@ export const allGuildsState = atom({ }); interface RecoilLoadableAtomEffectParams { + node: RecoilState>, trigger: 'get' | 'set'; setSelf: (loadableValue: LoadableValue) => void; getPromise: (recoilValue: RecoilValue) => Promise; @@ -109,15 +112,15 @@ interface RecoilLoadableAtomEffectParams { function createFetchValueFunc( getPromise: (recoilValue: RecoilValue) => Promise, guildId: number, + node: RecoilValue>, setSelf: (loadableValue: LoadableValue) => void, - stateAtomFamily: (guildId: number) => RecoilState>, fetchFunc: (guild: CombinedGuild) => Promise>, ): () => Promise { const fetchValueFunc = async () => { const guild = await getPromise(guildState(guildId)); if (guild === null) return; // Can't send a request without an associated guild - const selfState = await getPromise(stateAtomFamily(guildId)); + const selfState = await getPromise(node); if (isPended(selfState)) return; // Don't send another request if we're already loading setSelf(DEF_PENDED_VALUE); @@ -135,18 +138,20 @@ function createFetchValueFunc( // Creates an event handler that directly applies the result of the eventArgsMap as a loadedValue into self function createDirectMappedEventHandler( getPromise: (recoilValue: RecoilValue) => Promise, - guildId: number, + node: RecoilValue>, setSelf: (loadableValue: LoadableValue) => void, - stateAtomFamily: (guildId: number) => RecoilState>, fetchValueFunc: () => Promise, - eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Defined + eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Defined, + condition: (value: T) => boolean ): (Connectable & Conflictable)[XE] { return ((...args: Arguments<(Connectable & Conflictable)[XE]>) => { (async () => { - const selfState = await getPromise(stateAtomFamily(guildId)); + const selfState = await getPromise(node); if (isLoaded(selfState)) { const value = eventArgsMap(...args); - setSelf(createLoadedValue(value, fetchValueFunc)); + if (condition(value)) { + setSelf(createLoadedValue(value, fetchValueFunc)); + } } })(); }) as (Connectable & Conflictable)[XE]; @@ -166,9 +171,8 @@ function applyRemoved(value: T[], removedElements: T[] // Useful for new-xxx, update-xxx, remove-xxx list events function createListConnectableMappedEventHandler( getPromise: (recoilValue: RecoilValue) => Promise, - guildId: number, + node: RecoilValue>, setSelf: (loadableValue: LoadableValue) => void, - stateAtomFamily: (guildId: number) => RecoilState>, fetchValueFunc: () => Promise, eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Defined, sortFunc: (a: T, b: T) => number, @@ -178,7 +182,7 @@ function createListConnectableMappedEventHandler) => { (async () => { - const selfState = await getPromise(stateAtomFamily(guildId)); + const selfState = await getPromise(node); if (isLoaded(selfState)) { const eventArgsResult = eventArgsMap(...args); const value = applyFunc(selfState.value, eventArgsResult, sortFunc); @@ -200,9 +204,8 @@ function applyChanges(value: T[], changes: Changes, // Useful for conflict-xxx list events function createListConflictableMappedEventHandler( getPromise: (recoilValue: RecoilValue) => Promise, - guildId: number, + node: RecoilValue>, setSelf: (loadableValue: LoadableValue) => void, - stateAtomFamily: (guildId: number) => RecoilState>, fetchValueFunc: () => Promise, eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Changes, sortFunc: (a: T, b: T) => number, @@ -210,7 +213,7 @@ function createListConflictableMappedEventHandler) => { (async () => { - const selfState = await getPromise(stateAtomFamily(guildId)); + const selfState = await getPromise(node); if (isLoaded(selfState)) { const eventArgsResult = eventArgsMap(...args); const value = applyFunc(selfState.value, eventArgsResult, sortFunc); @@ -223,8 +226,10 @@ function createListConflictableMappedEventHandler { updatedEventName: UE; updatedEventArgsMap: (...args: Arguments<(Connectable & Conflictable)[UE]>) => Defined; + updatedEventCondition?: (value: T) => boolean; conflictEventName: CE; conflictEventArgsMap: (...args: Arguments<(Connectable & Conflictable)[CE]>) => Defined; + conflictEventCondition?: (value: T) => boolean; } function listenToSingle< @@ -234,8 +239,8 @@ function listenToSingle< >( getPromise: (recoilValue: RecoilValue) => Promise, guildId: number, + node: RecoilValue>, setSelf: (loadableValue: LoadableValue) => void, - stateAtomFamily: (guildId: number) => RecoilState>, fetchValueFunc: () => Promise, eventMapping: SingleEventMappingParams ) { @@ -251,8 +256,8 @@ function listenToSingle< // 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, guildId, setSelf, stateAtomFamily, fetchValueFunc, eventMapping.updatedEventArgsMap); - onConflictFunc = createDirectMappedEventHandler(getPromise, guildId, setSelf, stateAtomFamily, fetchValueFunc, eventMapping.conflictEventArgsMap); + onUpdateFunc = createDirectMappedEventHandler(getPromise, node, setSelf, fetchValueFunc, eventMapping.updatedEventArgsMap, eventMapping.updatedEventCondition ?? (() => true)); + onConflictFunc = createDirectMappedEventHandler(getPromise, node, setSelf, fetchValueFunc, eventMapping.conflictEventArgsMap, eventMapping.conflictEventCondition ?? (() => true)); guild.on(eventMapping.updatedEventName, onUpdateFunc); guild.on(eventMapping.conflictEventName, onConflictFunc); })(); @@ -289,8 +294,8 @@ function listenToMultiple< >( getPromise: (recoilValue: RecoilValue) => Promise, guildId: number, + node: RecoilValue>, setSelf: (loadableValue: LoadableValue) => void, - stateAtomFamily: (guildId: number) => RecoilState>, fetchValueFunc: () => Promise, sortFunc: (a: T, b: T) => number, eventMapping: MultipleEventMappingParams @@ -307,10 +312,10 @@ 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, guildId, setSelf, stateAtomFamily, fetchValueFunc, eventMapping.newEventArgsMap, sortFunc, applyNew); - onUpdateFunc = createListConnectableMappedEventHandler(getPromise, guildId, setSelf, stateAtomFamily, fetchValueFunc, eventMapping.updatedEventArgsMap, sortFunc, applyUpdated); - onRemoveFunc = createListConnectableMappedEventHandler(getPromise, guildId, setSelf, stateAtomFamily, fetchValueFunc, eventMapping.updatedEventArgsMap, sortFunc, applyRemoved); - onConflictFunc = createListConflictableMappedEventHandler(getPromise, guildId, setSelf, stateAtomFamily, fetchValueFunc, eventMapping.conflictEventArgsMap, sortFunc, applyChanges); + 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); guild.on(eventMapping.updatedEventName, onUpdateFunc); guild.on(eventMapping.conflictEventName, onConflictFunc); })(); @@ -330,13 +335,12 @@ function singleGuildSubscriptionEffect< CE extends keyof Conflictable, // Conflict Event >( guildId: number, - stateAtomFamily: (guildId: number) => RecoilState>, fetchFunc: (guild: CombinedGuild) => Promise>, eventMapping: SingleEventMappingParams ) { return (params: RecoilLoadableAtomEffectParams) => { - const { trigger, setSelf, getPromise } = params; - const fetchValueFunc = createFetchValueFunc(getPromise, guildId, setSelf, stateAtomFamily, fetchFunc); + const { node, trigger, setSelf, getPromise } = params; + const fetchValueFunc = createFetchValueFunc(getPromise, guildId, node, setSelf, fetchFunc); // Fetch initial value on first get if (trigger === 'get') { @@ -344,7 +348,7 @@ function singleGuildSubscriptionEffect< } // Listen to changes - const cleanup = listenToSingle(getPromise, guildId, setSelf, stateAtomFamily, fetchValueFunc, eventMapping); + const cleanup = listenToSingle(getPromise, guildId, node, setSelf, fetchValueFunc, eventMapping); return () => { cleanup(); @@ -360,14 +364,13 @@ function multipleGuildSubscriptionEffect< CE extends keyof Conflictable >( guildId: number, - stateAtomFamily: (guildId: number) => RecoilState>, fetchFunc: (guild: CombinedGuild) => Promise>, sortFunc: (a: T, b: T) => number, eventMapping: MultipleEventMappingParams, ) { return (params: RecoilLoadableAtomEffectParams) => { - const { trigger, setSelf, getPromise } = params; - const fetchValueFunc = createFetchValueFunc(getPromise, guildId, setSelf, stateAtomFamily, fetchFunc); + const { node, trigger, setSelf, getPromise } = params; + const fetchValueFunc = createFetchValueFunc(getPromise, guildId, node, setSelf, fetchFunc); // Fetch initial value on first get if (trigger === 'get') { @@ -375,7 +378,7 @@ function multipleGuildSubscriptionEffect< } // Listen to changes - const cleanup = listenToMultiple(getPromise, guildId, setSelf, stateAtomFamily, fetchValueFunc, sortFunc, eventMapping); + const cleanup = listenToMultiple(getPromise, guildId, node, setSelf, fetchValueFunc, sortFunc, eventMapping); return () => { cleanup(); @@ -384,13 +387,12 @@ function multipleGuildSubscriptionEffect< } // You probably want currGuildMetaState -export const guildMetaState: (guildId: number) => RecoilState> = atomFamily, number>({ +export const guildMetaState = atomFamily, number>({ key: 'guildMetaState', default: DEF_UNLOADED_VALUE, effects_UNSTABLE: (guildId: number) => [ singleGuildSubscriptionEffect( guildId, - guildMetaState, async (guild: CombinedGuild) => await guild.fetchMetadata(), { updatedEventName: 'update-metadata', @@ -403,13 +405,32 @@ export const guildMetaState: (guildId: number) => RecoilState RecoilState> = atomFamily, number>({ +export const guildResourceState = atomFamily, { guildId: number, resourceId: string }>({ + key: 'guildResourceState', + default: DEF_UNLOADED_VALUE, + effects_UNSTABLE: (param: { guildId: number, resourceId: string }) => [ + singleGuildSubscriptionEffect( + param.guildId, + async (guild: CombinedGuild) => await guild.fetchResource(param.resourceId), + { + updatedEventName: 'update-resource', + updatedEventArgsMap: (updatedResource: Resource) => updatedResource, + updatedEventCondition: (resource: Resource) => resource.id === param.resourceId, + conflictEventName: 'conflict-resource', + conflictEventArgsMap: (_query: IDQuery, _changeType: AutoVerifierChangesType, _oldResource: Resource, newResource: Resource) => newResource, + conflictEventCondition: (resource: Resource) => resource.id === param.resourceId + } + ) + ], + dangerouslyAllowMutability: true +}); + +const guildMembersState = atomFamily, number>({ key: 'guildMembersState', default: DEF_UNLOADED_VALUE, effects_UNSTABLE: (guildId: number) => [ multipleGuildSubscriptionEffect( guildId, - guildMembersState, async (guild: CombinedGuild) => await guild.fetchMembers(), Member.sortForList, { @@ -451,13 +472,12 @@ export const guildSelfMemberState = selectorFamily, number dangerouslyAllowMutability: true }); -const guildChannelsState: (guildId: number) => RecoilState> = atomFamily, number>({ +const guildChannelsState = atomFamily, number>({ key: 'guildChannelsState', default: DEF_UNLOADED_VALUE, effects_UNSTABLE: (guildId: number) => [ multipleGuildSubscriptionEffect( guildId, - guildChannelsState, async (guild: CombinedGuild) => await guild.fetchChannels(), Channel.sortByIndex, { diff --git a/src/client/webapp/elements/require/guild-subscriptions.ts b/src/client/webapp/elements/require/guild-subscriptions.ts index 0ab8c6c..0164a4b 100644 --- a/src/client/webapp/elements/require/guild-subscriptions.ts +++ b/src/client/webapp/elements/require/guild-subscriptions.ts @@ -788,7 +788,7 @@ export function useSoftImageSrcResourceSubscription(guild: CombinedGuild, resour * fetchError: Any error from fetching * ] */ -export function useChannelsSubscription(guild: CombinedGuild) { +function useChannelsSubscription(guild: CombinedGuild) { const fetchChannelsFunc = useCallback(async () => { return await guild.fetchChannels(); }, [ guild ]); @@ -838,7 +838,7 @@ function useMembersSubscription(guild: CombinedGuild) { * TODO: __fetchError: Any error from fetching * ] */ -export function useSelfMemberSubscription(guild: CombinedGuild): [ +function useSelfMemberSubscription(guild: CombinedGuild): [ selfMemberResult: SubscriptionResult | null ] { const [ _fetchRetryCallable, membersResult, _fetchError ] = useMembersSubscription(guild);