improved single/multiple guild subscription effect - use the { node } destructed vaglue from the effect parameter
This commit is contained in:
parent
09390fad8f
commit
5b708f2a94
@ -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> = T extends (...args: infer A) => unknown ? A : never;
|
||||
// Ensures that a type is not undefined
|
||||
type Defined<T> = T extends undefined ? never : T | Awaited<T>;
|
||||
|
||||
export type UnloadedValue = {
|
||||
@ -101,6 +103,7 @@ export const allGuildsState = atom<CombinedGuild[] | null>({
|
||||
});
|
||||
|
||||
interface RecoilLoadableAtomEffectParams<T> {
|
||||
node: RecoilState<LoadableValue<T>>,
|
||||
trigger: 'get' | 'set';
|
||||
setSelf: (loadableValue: LoadableValue<T>) => void;
|
||||
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>;
|
||||
@ -109,15 +112,15 @@ interface RecoilLoadableAtomEffectParams<T> {
|
||||
function createFetchValueFunc<T>(
|
||||
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>,
|
||||
guildId: number,
|
||||
node: RecoilValue<LoadableValue<T>>,
|
||||
setSelf: (loadableValue: LoadableValue<T>) => void,
|
||||
stateAtomFamily: (guildId: number) => RecoilState<LoadableValue<T>>,
|
||||
fetchFunc: (guild: CombinedGuild) => Promise<Defined<T>>,
|
||||
): () => Promise<void> {
|
||||
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<T>(
|
||||
// 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>,
|
||||
guildId: number,
|
||||
node: RecoilValue<LoadableValue<T>>,
|
||||
setSelf: (loadableValue: LoadableValue<T>) => void,
|
||||
stateAtomFamily: (guildId: number) => RecoilState<LoadableValue<T>>,
|
||||
fetchValueFunc: () => Promise<void>,
|
||||
eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Defined<T>
|
||||
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(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<T extends { id: string }>(value: T[], removedElements: T[]
|
||||
// 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>,
|
||||
guildId: number,
|
||||
node: RecoilValue<LoadableValue<T[]>>,
|
||||
setSelf: (loadableValue: LoadableValue<T[]>) => void,
|
||||
stateAtomFamily: (guildId: number) => RecoilState<LoadableValue<T[]>>,
|
||||
fetchValueFunc: () => Promise<void>,
|
||||
eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Defined<T[]>,
|
||||
sortFunc: (a: T, b: T) => number,
|
||||
@ -178,7 +182,7 @@ function createListConnectableMappedEventHandler<T, XE extends keyof (Connectabl
|
||||
// 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(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<T extends { id: string }>(value: T[], changes: Changes<T>,
|
||||
// Useful for conflict-xxx list events
|
||||
function createListConflictableMappedEventHandler<T, XE extends keyof (Connectable | Conflictable)>(
|
||||
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>,
|
||||
guildId: number,
|
||||
node: RecoilValue<LoadableValue<T[]>>,
|
||||
setSelf: (loadableValue: LoadableValue<T[]>) => void,
|
||||
stateAtomFamily: (guildId: number) => RecoilState<LoadableValue<T[]>>,
|
||||
fetchValueFunc: () => Promise<void>,
|
||||
eventArgsMap: (...args: Arguments<(Connectable & Conflictable)[XE]>) => Changes<T>,
|
||||
sortFunc: (a: T, b: T) => number,
|
||||
@ -210,7 +213,7 @@ function createListConflictableMappedEventHandler<T, XE extends keyof (Connectab
|
||||
): (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 eventArgsResult = eventArgsMap(...args);
|
||||
const value = applyFunc(selfState.value, eventArgsResult, sortFunc);
|
||||
@ -223,8 +226,10 @@ function createListConflictableMappedEventHandler<T, XE extends keyof (Connectab
|
||||
interface SingleEventMappingParams<T, UE extends keyof Connectable, CE extends keyof Conflictable> {
|
||||
updatedEventName: UE;
|
||||
updatedEventArgsMap: (...args: Arguments<(Connectable & Conflictable)[UE]>) => Defined<T>;
|
||||
updatedEventCondition?: (value: T) => boolean;
|
||||
conflictEventName: CE;
|
||||
conflictEventArgsMap: (...args: Arguments<(Connectable & Conflictable)[CE]>) => Defined<T>;
|
||||
conflictEventCondition?: (value: T) => boolean;
|
||||
}
|
||||
|
||||
function listenToSingle<
|
||||
@ -234,8 +239,8 @@ function listenToSingle<
|
||||
>(
|
||||
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>,
|
||||
guildId: number,
|
||||
node: RecoilValue<LoadableValue<T>>,
|
||||
setSelf: (loadableValue: LoadableValue<T>) => void,
|
||||
stateAtomFamily: (guildId: number) => RecoilState<LoadableValue<T>>,
|
||||
fetchValueFunc: () => Promise<void>,
|
||||
eventMapping: SingleEventMappingParams<T, UE, CE>
|
||||
) {
|
||||
@ -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: <S>(recoilValue: RecoilValue<S>) => Promise<S>,
|
||||
guildId: number,
|
||||
node: RecoilValue<LoadableValue<T[]>>,
|
||||
setSelf: (loadableValue: LoadableValue<T[]>) => void,
|
||||
stateAtomFamily: (guildId: number) => RecoilState<LoadableValue<T[]>>,
|
||||
fetchValueFunc: () => Promise<void>,
|
||||
sortFunc: (a: T, b: T) => number,
|
||||
eventMapping: MultipleEventMappingParams<T, NE, UE, RE, CE>
|
||||
@ -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<LoadableValue<T>>,
|
||||
fetchFunc: (guild: CombinedGuild) => Promise<Defined<T>>,
|
||||
eventMapping: SingleEventMappingParams<T, UE, CE>
|
||||
) {
|
||||
return (params: RecoilLoadableAtomEffectParams<T>) => {
|
||||
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<LoadableValue<T[]>>,
|
||||
fetchFunc: (guild: CombinedGuild) => Promise<Defined<T[]>>,
|
||||
sortFunc: (a: T, b: T) => number,
|
||||
eventMapping: MultipleEventMappingParams<T, NE, UE, RE, CE>,
|
||||
) {
|
||||
return (params: RecoilLoadableAtomEffectParams<T[]>) => {
|
||||
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<LoadableValue<GuildMetadata>> = atomFamily<LoadableValue<GuildMetadata>, number>({
|
||||
export const guildMetaState = atomFamily<LoadableValue<GuildMetadata>, 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<LoadableValue<Guil
|
||||
dangerouslyAllowMutability: true
|
||||
});
|
||||
|
||||
const guildMembersState: (guildId: number) => RecoilState<LoadableValue<Member[]>> = atomFamily<LoadableValue<Member[]>, number>({
|
||||
export const guildResourceState = atomFamily<LoadableValue<Resource>, { 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<LoadableValue<Member[]>, 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<LoadableValue<Member>, number
|
||||
dangerouslyAllowMutability: true
|
||||
});
|
||||
|
||||
const guildChannelsState: (guildId: number) => RecoilState<LoadableValue<Channel[]>> = atomFamily<LoadableValue<Channel[]>, number>({
|
||||
const guildChannelsState = atomFamily<LoadableValue<Channel[]>, number>({
|
||||
key: 'guildChannelsState',
|
||||
default: DEF_UNLOADED_VALUE,
|
||||
effects_UNSTABLE: (guildId: number) => [
|
||||
multipleGuildSubscriptionEffect(
|
||||
guildId,
|
||||
guildChannelsState,
|
||||
async (guild: CombinedGuild) => await guild.fetchChannels(),
|
||||
Channel.sortByIndex,
|
||||
{
|
||||
|
@ -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<Member | null> | null
|
||||
] {
|
||||
const [ _fetchRetryCallable, membersResult, _fetchError ] = useMembersSubscription(guild);
|
||||
|
Loading…
Reference in New Issue
Block a user