151 lines
5.9 KiB
TypeScript
151 lines
5.9 KiB
TypeScript
import * as electronRemote from '@electron/remote';
|
|
const electronConsole = electronRemote.getGlobal('console') as Console;
|
|
import Logger from '../../../../logger';
|
|
const LOG = Logger.create(__filename, electronConsole);
|
|
|
|
import { ReactNode } from 'react';
|
|
import { atom, selector } from 'recoil';
|
|
import { Channel, GuildMetadata, Member, Message } from '../../data-types';
|
|
import CombinedGuild from '../../guild-combined';
|
|
import GuildsManager from '../../guilds-manager';
|
|
|
|
export type WithUnloadedValue = {
|
|
value: undefined;
|
|
hasValueError: false;
|
|
valueError: undefined;
|
|
};
|
|
export type WithLoadedValue<T> = T extends undefined ? never : {
|
|
value: T;
|
|
hasValueError: false;
|
|
valueError: undefined;
|
|
};
|
|
export type WithFailedValue = {
|
|
value: undefined;
|
|
hasValueError: true;
|
|
valueError: unknown;
|
|
};
|
|
export type WithLoadableValue<T> = WithUnloadedValue | WithLoadedValue<T> | WithFailedValue;
|
|
|
|
// NOTE: Should not extend this type. It is for type guards
|
|
|
|
export type GuildWithValue<T> = WithLoadedValue<T> & { guild: CombinedGuild };
|
|
export type GuildWithErrorableValue<T> = WithLoadableValue<T> & { guild: CombinedGuild };
|
|
export type ChannelWithErrorableValue<T> = WithLoadableValue<T> & { channel: Channel };
|
|
|
|
// Devoid is when we don't have a selected guild yet.
|
|
export function isDevoid<T>(state: NonNullable<T> | null): state is null {
|
|
return state === null;
|
|
}
|
|
// NOTE: Using "pended" instead of "pending" since it has 6 letters like the rest of these functions. It will make the code look nicer :)
|
|
export function isPended<T>(withLoadableValue: WithLoadableValue<T>): withLoadableValue is WithUnloadedValue {
|
|
return withLoadableValue.value === undefined && withLoadableValue.hasValueError === false;
|
|
}
|
|
export function isLoaded<T>(withLoadableValue: WithLoadableValue<T>): withLoadableValue is WithLoadedValue<T> {
|
|
return withLoadableValue.value !== undefined;
|
|
}
|
|
export function isFailed<T>(withLoadableValue: WithLoadableValue<T>): withLoadableValue is WithFailedValue {
|
|
return withLoadableValue.hasValueError;
|
|
}
|
|
|
|
// WARNING: This must NOT be used in a place that could prevent hooks from running
|
|
// NOTE: This is useful to skip rendering elements that require an atom if its value is not available yet. Using this in the parent element will prevent the child eleemnt from
|
|
// needing to implement the complexity of loading without a value
|
|
export function isDevoidPendedOrFailed<T>(withNullableLoadableValue: WithLoadableValue<T> | null): withNullableLoadableValue is null | WithUnloadedValue | WithFailedValue {
|
|
return withNullableLoadableValue === null || isPended(withNullableLoadableValue) || isFailed(withNullableLoadableValue);
|
|
}
|
|
|
|
export const overlayState = atom<ReactNode>({
|
|
key: 'overlayState',
|
|
default: null
|
|
});
|
|
|
|
export const guildsManagerState = atom<GuildsManager | null>({
|
|
key: 'guildsManagerState',
|
|
default: null,
|
|
// Allow mutability so that we can have changes to internal guild stuff
|
|
// We will still listen to new/update/delete/conflict events to maintain the UI's state
|
|
dangerouslyAllowMutability: true
|
|
});
|
|
|
|
export const guildsState = atom<GuildWithErrorableValue<GuildMetadata>[] | null>({
|
|
key: 'guildsState',
|
|
default: null,
|
|
dangerouslyAllowMutability: true
|
|
});
|
|
|
|
export const selectedGuildIdState = atom<number | null>({
|
|
key: 'selectedGuildIdState',
|
|
default: null
|
|
});
|
|
|
|
export const selectedGuildWithMetaState = selector<GuildWithErrorableValue<GuildMetadata> | null>({
|
|
key: 'selectedGuildWithMetaState',
|
|
get: ({ get }) => {
|
|
const guildsWithMeta = get(guildsState);
|
|
if (guildsWithMeta === null) return null;
|
|
|
|
const guildId = get(selectedGuildIdState);
|
|
if (guildId === null) return null;
|
|
|
|
return guildsWithMeta.find(guildWithMeta => guildWithMeta.guild.id === guildId) ?? null;
|
|
},
|
|
dangerouslyAllowMutability: true
|
|
});
|
|
|
|
// Note: You likely want selectedGuildWithMetaState
|
|
export const selectedGuildState = selector<CombinedGuild | null>({
|
|
key: 'selectedGuildState',
|
|
get: ({ get }) => {
|
|
const guildWithMeta = get(selectedGuildWithMetaState);
|
|
if (isDevoid(guildWithMeta)) return null;
|
|
|
|
return guildWithMeta.guild;
|
|
},
|
|
dangerouslyAllowMutability: true
|
|
});
|
|
|
|
export const selectedGuildWithMembersState = atom<GuildWithErrorableValue<Member[]> | null>({
|
|
key: 'selectedGuildWithMembersState',
|
|
default: null,
|
|
dangerouslyAllowMutability: true
|
|
});
|
|
|
|
export const selectedGuildWithSelfMemberState = selector<GuildWithErrorableValue<Member> | null>({
|
|
key: 'selectedGuildWithSelfMemberState',
|
|
get: ({ get }) => {
|
|
const guildWithMembers = get(selectedGuildWithMembersState);
|
|
if (isDevoid(guildWithMembers)) return null;
|
|
if (isFailed(guildWithMembers)) return { guild: guildWithMembers.guild, value: undefined, hasValueError: true, valueError: 'members fetch error' };
|
|
if (isPended(guildWithMembers)) return { guild: guildWithMembers.guild, value: undefined, hasValueError: false, valueError: undefined };
|
|
const selfMember = guildWithMembers.value.find(member => member.id === guildWithMembers.guild.memberId) ?? null;
|
|
if (selfMember === null) {
|
|
LOG.warn('unable to find self member in members');
|
|
return { guild: guildWithMembers.guild, value: undefined, hasValueError: true, valueError: 'unable to find self in members' };
|
|
}
|
|
return { guild: guildWithMembers.guild, value: selfMember, hasValueError: false, valueError: undefined };
|
|
},
|
|
dangerouslyAllowMutability: true
|
|
});
|
|
|
|
export const selectedGuildWithChannelsState = atom<GuildWithErrorableValue<Channel[]> | null>({
|
|
key: 'selectedGuildChannelsState',
|
|
default: null,
|
|
dangerouslyAllowMutability: true
|
|
});
|
|
|
|
export const selectedGuildWithActiveChannelIdState = atom<GuildWithValue<string | null> | null>({
|
|
key: 'selectedGuildWithActiveChannelIdState',
|
|
default: null,
|
|
dangerouslyAllowMutability: true
|
|
});
|
|
|
|
|
|
export const selectedGuildWithActiveChannelMessagesState = atom<GuildWithValue<ChannelWithErrorableValue<Message[]>> | null>({
|
|
key: 'selectedGuildWithActiveChannelMessagesState',
|
|
default: null,
|
|
dangerouslyAllowMutability: true
|
|
});
|
|
|
|
// TODO: selectedGuildWithActiveChannelState
|
|
|