cordis/src/client/webapp/elements/require/setup-guild-recoil.ts

143 lines
6.0 KiB
TypeScript
Raw Normal View History

import * as electronRemote from '@electron/remote';
const electronConsole = electronRemote.getGlobal('console') as Console;
import Logger from '../../../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import { useCallback, useEffect, useRef } from "react";
import { useRecoilState, useRecoilValue, useSetRecoilState } from "recoil";
import { GuildMetadata } from "../../data-types";
import GuildsManager from "../../guilds-manager";
import { guildsManagerState, guildsState, GuildWithErrorableValue, selectedGuildIdState, selectedGuildState } from "./atoms";
import { useIsMountedRef } from "./react-helper";
import CombinedGuild from '../../guild-combined';
import { AutoVerifierChangesType } from '../../auto-verifier';
/** Manages the guildsManager to ensure that guildsState is up to date with a list of guilds
* and that the guilds attempt to fetch their corresponding guildMetas
* This class it a bit more complicated since it has to fetch/listen to guild metadata changes on many guilds at once */
function useManagedGuildsManager(guildsManager: GuildsManager) {
const isMounted = useIsMountedRef();
const metadataInProgress = useRef<Set<number>>(new Set<number>());
const [ guildsWithMeta, setGuildsWithMeta ] = useRecoilState(guildsState);
// Update the guild list
// If new guilds are added, add them with null metadata
const onChange = useCallback(() => {
const guilds = guildsManager.guilds;
setGuildsWithMeta((prevGuildsWithMeta) => {
if (!prevGuildsWithMeta) {
return guilds.map(guild => ({ guild, value: null, valueError: null }));
} else {
const newGuilds = guilds.filter(guild => !prevGuildsWithMeta.find(guildWithMeta => guildWithMeta.guild.id === guild.id));
const newGuildsWithMeta = prevGuildsWithMeta
.filter(guildWithMeta => guilds.find(guild => guildWithMeta.guild.id === guild.id)) // Remove old guilds
.concat(newGuilds.map(guild => ({ guild, value: null, valueError: null }))); // Add new guilds
return newGuildsWithMeta;
}
});
}, [ guildsManager, setGuildsWithMeta ]);
// Listen for changes to the guild manager
useEffect(() => {
// Set initial guilds
onChange();
// Listen for updates
guildsManager.on('update-guilds', onChange);
return () => {
guildsManager.off('update-guilds', onChange);
}
}, [ guildsManager, onChange ]);
const setGuildWithMeta = useCallback((guildId: number, newGuildWithMeta: GuildWithErrorableValue<GuildMetadata>) => {
setGuildsWithMeta(prevGuildsWithMeta => {
if (!prevGuildsWithMeta) return null;
return prevGuildsWithMeta.map(guildWithMeta => guildWithMeta.guild.id === guildId ? newGuildWithMeta : guildWithMeta);
});
}, [ setGuildsWithMeta ]);
useEffect(() => {
if (guildsWithMeta === null) return;
// Load guild metadatas
for (const { guild, value, valueError } of guildsWithMeta) {
// Only fetch if the value has not been fetched yet and we are not currently fetching it.
if (!metadataInProgress.current.has(guild.id) && value === null && valueError === null) {
(async () => {
metadataInProgress.current.add(guild.id);
try {
const guildMeta = await guild.fetchMetadata();
if (!isMounted.current) return;
setGuildWithMeta(guild.id, { guild, value: guildMeta, valueError: null });
} catch (e: unknown) {
LOG.error(`error fetching metadata for g#${guild.id}`, e);
if (!isMounted.current) return;
setGuildWithMeta(guild.id, { guild, value: null, valueError: e });
}
metadataInProgress.current.delete(guild.id);
})();
}
}
}, [ guildsWithMeta, setGuildWithMeta ]);
// Listen for changes to the guild metadata
useEffect(() => {
if (guildsWithMeta === null) return;
// Listen for changes to metadata
function handleUpdateOrConflict(guild: CombinedGuild, guildMeta: GuildMetadata) {
setGuildWithMeta(guild.id, { guild, value: guildMeta, valueError: null });
}
const callbacks = new Map<CombinedGuild, {
updateCallback: (guildMeta: GuildMetadata) => void,
conflictCallback: (changesType: AutoVerifierChangesType, oldGuildMeta: GuildMetadata, guildMeta: GuildMetadata) => void
}>();
for (const { guild } of guildsWithMeta) {
const updateCallback = (guildMeta: GuildMetadata) => handleUpdateOrConflict(guild, guildMeta);
const conflictCallback = (_changesType: AutoVerifierChangesType, _oldGuildMeta: GuildMetadata, guildMeta: GuildMetadata) => handleUpdateOrConflict(guild, guildMeta);
guild.on('update-metadata', updateCallback);
guild.on('conflict-metadata', conflictCallback);
callbacks.set(guild, { updateCallback, conflictCallback });
}
return () => {
for (const { guild } of guildsWithMeta) {
const { updateCallback, conflictCallback } = callbacks.get(guild) as {
updateCallback: (guildMeta: GuildMetadata) => void,
conflictCallback: (changesType: AutoVerifierChangesType, oldGuildMeta: GuildMetadata, guildMeta: GuildMetadata) => void
};
guild.off('update-metadata', updateCallback);
guild.off('conflict-metadata', conflictCallback);
}
}
}, [ guildsWithMeta ]);
//useEffect(() => {
// LOG.debug(`guildsWithMeta: `, { guildsWithMeta });
//}, [ guildsWithMeta ]);
}
// Entrypoint for recoil setup with guilds
export function useGuildsManagerWithRecoil(guildsManager: GuildsManager): void {
const setGuildsManager = useSetRecoilState(guildsManagerState);
const guilds = useRecoilValue(guildsState);
const [ selectedGuildId, setSelectedGuildId ] = useRecoilState(selectedGuildIdState);
// Set the guilds manager atom
useEffect(() => { setGuildsManager(guildsManager); }, [ guildsManager, setGuildsManager ]);
// Manage the guilds within the manager (adds them to the guilds atom and automatically loads and updates their metadata)
useManagedGuildsManager(guildsManager);
// Make sure that the first guild is set to the active guild once we get some guilds
useEffect(() => {
if (guilds && guilds.length > 0 && selectedGuildId === null) {
setSelectedGuildId((guilds[0] as GuildWithErrorableValue<GuildMetadata>).guild.id);
}
});
}