143 lines
6.0 KiB
TypeScript
143 lines
6.0 KiB
TypeScript
|
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);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|