preparing for channel messages
This commit is contained in:
parent
54ad823ebe
commit
dfd50c7e20
@ -4,14 +4,15 @@ import Logger from '../../../../logger/logger';
|
||||
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, Token } from "../../data-types";
|
||||
import { atom, AtomEffect, atomFamily, GetRecoilValue, Loadable, RecoilState, RecoilValue, RecoilValueReadOnly, selector, selectorFamily, useRecoilValueLoadable, useSetRecoilState } from "recoil";
|
||||
import { Changes, Channel, GuildMetadata, Member, Message, Resource, ShouldNeverHappenError, Token } 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';
|
||||
import { IDQuery, PartialMessageListQuery } from '../../auto-verifier-with-args';
|
||||
import ElementsUtil from './elements-util';
|
||||
import Globals from '../../globals';
|
||||
|
||||
// General typescript type that infers the arguments of a function
|
||||
type Arguments<T> = T extends (...args: infer A) => unknown ? A : never;
|
||||
@ -113,7 +114,7 @@ interface RecoilLoadableAtomEffectParams<T> {
|
||||
function createFetchValueFunc<T>(
|
||||
getPromise: <S>(recoilValue: RecoilValue<S>) => Promise<S>,
|
||||
guildId: number,
|
||||
node: RecoilValue<LoadableValue<T>>,
|
||||
node: RecoilState<LoadableValue<T>>,
|
||||
setSelf: (loadableValue: LoadableValue<T>) => void,
|
||||
fetchFunc: (guild: CombinedGuild) => Promise<Defined<T>>,
|
||||
): () => Promise<void> {
|
||||
@ -185,7 +186,7 @@ function listenToSingle<
|
||||
let closed = false;
|
||||
(async () => {
|
||||
guild = await getPromise(guildState(guildId));
|
||||
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 (guild === null) return; // NOTE: This would only happen if this atom is created before its corresponding guild exists in the guildsManager
|
||||
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
|
||||
@ -193,16 +194,14 @@ function listenToSingle<
|
||||
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));
|
||||
}
|
||||
return (async (...args: Arguments<(Connectable & Conflictable)[XE]>) => {
|
||||
const selfState = await getPromise(node);
|
||||
if (isLoaded(selfState)) {
|
||||
const value = eventArgsMap(...args);
|
||||
if (condition(value)) {
|
||||
setSelf(createLoadedValue(value, fetchValueFunc));
|
||||
}
|
||||
})();
|
||||
}
|
||||
}) as (Connectable & Conflictable)[XE];
|
||||
}
|
||||
|
||||
@ -230,12 +229,16 @@ interface MultipleEventMappingParams<
|
||||
> {
|
||||
newEventName: NE;
|
||||
newEventArgsMap: (...args: Arguments<(Connectable & Conflictable)[NE]>) => Defined<T[]>;
|
||||
newEventCondition?: (eventArgsResult: Defined<T[]>) => boolean;
|
||||
updatedEventName: UE;
|
||||
updatedEventArgsMap: (...args: Arguments<(Connectable & Conflictable)[UE]>) => Defined<T[]>;
|
||||
updatedEventCondition?: (eventArgsResult: Defined<T[]>) => boolean;
|
||||
removedEventName: RE;
|
||||
removedEventArgsMap: (...args: Arguments<(Connectable & Conflictable)[RE]>) => Defined<T[]>;
|
||||
removedEventCondition?: (eventArgsResult: Defined<T[]>) => boolean;
|
||||
conflictEventName: CE;
|
||||
conflictEventArgsMap: (...args: Arguments<(Connectable & Conflictable)[CE]>) => Changes<T>;
|
||||
conflictEventCondition?: (eventArgsResult: Changes<T>) => boolean;
|
||||
}
|
||||
function listenToMultiple<
|
||||
T extends { id: string },
|
||||
@ -261,49 +264,51 @@ function listenToMultiple<
|
||||
let closed = false;
|
||||
(async () => {
|
||||
guild = await getPromise(guildState(guildId));
|
||||
if (guild === null) return;
|
||||
if (guild === null) return; // NOTE: This would only happen if this atom is created before its corresponding guild exists in the guildsManager
|
||||
if (closed) return; // Make sure not to bind events if this closed while we were fetching the guild state
|
||||
|
||||
// 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[]>,
|
||||
condition: (eventArgsResult: Defined<T[]>) => boolean,
|
||||
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);
|
||||
return (async (...args: Arguments<(Connectable & Conflictable)[XE]>) => {
|
||||
const selfState = await getPromise(node);
|
||||
if (isLoaded(selfState)) {
|
||||
const eventArgsResult = eventArgsMap(...args);
|
||||
if (condition(eventArgsResult)) {
|
||||
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>,
|
||||
condition: (eventArgsResult: Changes<T>) => boolean,
|
||||
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);
|
||||
return (async (...args: Arguments<(Connectable & Conflictable)[XE]>) => {
|
||||
const selfState = await getPromise(node);
|
||||
if (isLoaded(selfState)) {
|
||||
const eventArgsResult = eventArgsMap(...args);
|
||||
if (condition(eventArgsResult)) {
|
||||
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);
|
||||
onNewFunc = createConnectableHandler(eventMapping.newEventArgsMap, eventMapping.newEventCondition ?? (() => true), applyNew);
|
||||
onUpdateFunc = createConnectableHandler(eventMapping.updatedEventArgsMap, eventMapping.updatedEventCondition ?? (() => true), applyUpdated);
|
||||
onRemoveFunc = createConnectableHandler(eventMapping.removedEventArgsMap, eventMapping.removedEventCondition ?? (() => true), applyRemoved);
|
||||
onConflictFunc = createConflictableHandler(eventMapping.conflictEventArgsMap, eventMapping.conflictEventCondition ?? (() => true), applyChanges);
|
||||
guild.on(eventMapping.newEventName, onNewFunc);
|
||||
guild.on(eventMapping.updatedEventName, onUpdateFunc);
|
||||
guild.on(eventMapping.removedEventName, onRemoveFunc);
|
||||
@ -319,25 +324,6 @@ 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
|
||||
@ -349,10 +335,9 @@ function singleGuildSubscriptionEffect<
|
||||
eventMapping: SingleEventMappingParams<T, UE, CE>,
|
||||
skipFunc?: () => boolean
|
||||
) {
|
||||
return (params: RecoilLoadableAtomEffectParams<T>) => {
|
||||
const effect: AtomEffect<LoadableValue<T>> = ({ node, trigger, setSelf, getPromise }) => {
|
||||
if (skipFunc && skipFunc()) return; // Don't run if this atom should be skipped for some reason (e.g. null resourceId)
|
||||
|
||||
const { node, trigger, setSelf, getPromise } = params;
|
||||
const fetchValueFunc = createFetchValueFunc(getPromise, guildId, node, setSelf, fetchFunc);
|
||||
|
||||
// Fetch initial value on first get
|
||||
@ -367,6 +352,7 @@ function singleGuildSubscriptionEffect<
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
return effect;
|
||||
}
|
||||
|
||||
function multipleGuildSubscriptionEffect<
|
||||
@ -381,8 +367,7 @@ function multipleGuildSubscriptionEffect<
|
||||
sortFunc: (a: T, b: T) => number,
|
||||
eventMapping: MultipleEventMappingParams<T, NE, UE, RE, CE>,
|
||||
) {
|
||||
return (params: RecoilLoadableAtomEffectParams<T[]>) => {
|
||||
const { node, trigger, setSelf, getPromise } = params;
|
||||
const effect: AtomEffect<LoadableValue<T[]>> = ({ node, trigger, setSelf, getPromise }) => {
|
||||
const fetchValueFunc = createFetchValueFunc(getPromise, guildId, node, setSelf, fetchFunc);
|
||||
|
||||
// Fetch initial value on first get
|
||||
@ -396,7 +381,40 @@ function multipleGuildSubscriptionEffect<
|
||||
return () => {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
};
|
||||
return effect;
|
||||
}
|
||||
|
||||
interface ScrollingList<T> {
|
||||
list: T[];
|
||||
hasMoreAbove: boolean;
|
||||
hasMoreBelow: boolean;
|
||||
}
|
||||
function multipleScrollingGuildSubscriptionEffect<
|
||||
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
|
||||
>(
|
||||
guildId: number,
|
||||
fetchBottomFunc: (guild: CombinedGuild, count: number) => Promise<T[]>,
|
||||
fetchAboveFunc: (guild: CombinedGuild, reference: T, count: number) => Promise<T[] | null>,
|
||||
fetchBelowFunc: (guild: CombinedGuild, reference: T, count: number) => Promise<T[] | null>,
|
||||
fetchCount: number, // NOTE: If a fetch returns less than this number of elements, we will no longer try to get more above/below it
|
||||
maxElements: number, // The maximum number of elements in the scroller. Must be greater than maxFetchElements
|
||||
sortFunc: (a: T, b: T) => number,
|
||||
eventMapping: MultipleEventMappingParams<T, NE, UE, RE, CE>
|
||||
) {
|
||||
const effect: AtomEffect<LoadableValue<ScrollingList<T>>> = ({ node, trigger, setSelf, getPromise }) => {
|
||||
const fetchValueFunc = createFetchValueFunc(getPromise, guildId, node, setSelf, async (guild: CombinedGuild) => {
|
||||
const list = await fetchBottomFunc(guild, fetchCount);
|
||||
return { list, hasMoreBelow: false, hasMoreAbove: list.length <= fetchCount };
|
||||
});
|
||||
|
||||
// TODO: listen for changes
|
||||
};
|
||||
return effect;
|
||||
}
|
||||
|
||||
// You probably want currGuildMetaState
|
||||
@ -528,7 +546,7 @@ const guildChannelsState = atomFamily<LoadableValue<Channel[]>, number>({
|
||||
});
|
||||
|
||||
// You probably want currGuildActiveChannel
|
||||
export const guildActiveChannelIdState: (guildId: number) => RecoilState<string | null> = atomFamily<string | null, number>({
|
||||
export const guildActiveChannelIdState = atomFamily<string | null, number>({
|
||||
key: 'guildActiveChannelIdState',
|
||||
default: null,
|
||||
});
|
||||
@ -551,6 +569,40 @@ const guildActiveChannelState = selectorFamily<LoadableValue<Channel>, number>({
|
||||
dangerouslyAllowMutability: true
|
||||
});
|
||||
|
||||
export const guildChannelMessagesState = atomFamily<LoadableValue<ScrollingList<Message>>, { guildId: number, channelId: string }>({
|
||||
key: 'guildChannelMessagesState',
|
||||
default: DEF_UNLOADED_VALUE,
|
||||
effects_UNSTABLE: ({ guildId, channelId }) => [
|
||||
multipleScrollingGuildSubscriptionEffect(
|
||||
guildId,
|
||||
async (guild: CombinedGuild, count: number) => await guild.fetchMessagesRecent(channelId, count),
|
||||
async (guild: CombinedGuild, reference: Message, count: number) => await guild.fetchMessagesBefore(channelId, reference._order, count),
|
||||
async (guild: CombinedGuild, reference: Message, count: number) => await guild.fetchMessagesAfter(channelId, reference._order, count),
|
||||
Globals.MESSAGES_PER_REQUEST,
|
||||
Globals.MAX_CURRENT_MESSAGES,
|
||||
Message.sortOrder,
|
||||
{
|
||||
newEventName: 'new-messages',
|
||||
newEventArgsMap: (newMessages: Message[]) => newMessages.filter(message => message.channel.id === channelId),
|
||||
newEventCondition: (messages: Message[]) => messages.length > 0,
|
||||
updatedEventName: 'update-messages',
|
||||
updatedEventArgsMap: (updatedMessages: Message[]) => updatedMessages.filter(message => message.channel.id === channelId),
|
||||
updatedEventCondition: (messages: Message[]) => messages.length > 0,
|
||||
removedEventName: 'remove-messages',
|
||||
removedEventArgsMap: (removedMessages: Message[]) => removedMessages.filter(message => message.channel.id === channelId),
|
||||
removedEventCondition: (messages: Message[]) => messages.length > 0,
|
||||
conflictEventName: 'conflict-messages',
|
||||
conflictEventArgsMap: (_query: PartialMessageListQuery, _changesType: AutoVerifierChangesType, changes: Changes<Message>) => ({
|
||||
added: changes.added.filter(message => message.channel.id === channelId),
|
||||
updated: changes.updated.filter(change => change.newDataPoint.channel.id === channelId),
|
||||
deleted: changes.deleted.filter(message => message.channel.id === channelId),
|
||||
}),
|
||||
conflictEventCondition: (changes: Changes<Message>) => changes.added.length + changes.updated.length + changes.deleted.length > 0,
|
||||
}
|
||||
)
|
||||
]
|
||||
});
|
||||
|
||||
export const guildTokensState = atomFamily<LoadableValue<Token[]>, number>({
|
||||
key: 'guildTokensState',
|
||||
default: DEF_UNLOADED_VALUE,
|
||||
|
Loading…
Reference in New Issue
Block a user