tokens subscription
This commit is contained in:
parent
2a79af8a0e
commit
1c1d3209bf
@ -332,7 +332,7 @@ export class Token implements WithEquals<Token> {
|
|||||||
public readonly token: string,
|
public readonly token: string,
|
||||||
public member: Member | { id: string } | null,
|
public member: Member | { id: string } | null,
|
||||||
public readonly created: Date,
|
public readonly created: Date,
|
||||||
public readonly expires: Date,
|
public readonly expires: Date | null,
|
||||||
public readonly source?: unknown
|
public readonly source?: unknown
|
||||||
) {
|
) {
|
||||||
this.id = token; // for comparison purposes
|
this.id = token; // for comparison purposes
|
||||||
@ -365,6 +365,19 @@ export class Token implements WithEquals<Token> {
|
|||||||
this.expires === other.expires
|
this.expires === other.expires
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static sortFurthestExpiresFirst(a: Token, b: Token) {
|
||||||
|
// reverse-expire time order (most future expires comes first)
|
||||||
|
if (a.expires && b.expires) {
|
||||||
|
return b.expires.getTime() - a.expires.getTime();
|
||||||
|
} else if (a.expires) {
|
||||||
|
return -1;
|
||||||
|
} else if (b.expires) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NotInitializedError extends Error {
|
export class NotInitializedError extends Error {
|
||||||
|
@ -1,63 +0,0 @@
|
|||||||
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 moment from 'moment';
|
|
||||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { Member, Token } from '../../data-types';
|
|
||||||
import CombinedGuild from '../../guild-combined';
|
|
||||||
import BaseElements from '../require/base-elements';
|
|
||||||
import Button, { ButtonColorType } from './button';
|
|
||||||
|
|
||||||
export interface InvitesTableProps {
|
|
||||||
guild: CombinedGuild
|
|
||||||
}
|
|
||||||
|
|
||||||
const InvitesTable: FC<InvitesTableProps> = (props: InvitesTableProps) => {
|
|
||||||
const { guild } = props;
|
|
||||||
|
|
||||||
const [ tokens, setTokens ] = useState<Token[] | null>(null);
|
|
||||||
const [ tokensFailed, setTokensFailed ] = useState<boolean>(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
const tokens = await guild.fetchTokens();
|
|
||||||
setTokens(tokens);
|
|
||||||
} catch (e: unknown) {
|
|
||||||
LOG.error('unable to fetch tokens', e);
|
|
||||||
setTokensFailed(true);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const tokenElements = useMemo(() => {
|
|
||||||
if (tokensFailed) {
|
|
||||||
return <div className="tokens-failed">Unable to load tokens</div>;
|
|
||||||
}
|
|
||||||
return tokens?.map((token: Token) => {
|
|
||||||
const userText = (token.member instanceof Member ? 'Used by ' + token.member.displayName : token.member?.id) ?? 'Unused Token';
|
|
||||||
return (
|
|
||||||
<div key={token.token} className="token-row">
|
|
||||||
<div className="user-token">
|
|
||||||
<div className="user">{userText}</div>
|
|
||||||
<div className="token">{token.token}</div>
|
|
||||||
</div>
|
|
||||||
<div className="created-expires">
|
|
||||||
<div className="created">Created {moment(token.created).fromNow()}</div>
|
|
||||||
<div className="expires">Expires {moment(token.expires).fromNow()}</div>
|
|
||||||
</div>
|
|
||||||
<div className="actions">
|
|
||||||
<Button colorType={ButtonColorType.BRAND}>{BaseElements.DOWNLOAD}</Button>
|
|
||||||
<Button colorType={ButtonColorType.NEGATIVE}>{BaseElements.TRASHCAN}</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}, [ tokens, tokensFailed ]);
|
|
||||||
|
|
||||||
return <div className="invites-table">{tokenElements}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default InvitesTable;
|
|
@ -8,15 +8,15 @@ import { FC } from 'react';
|
|||||||
import CombinedGuild from '../../guild-combined';
|
import CombinedGuild from '../../guild-combined';
|
||||||
import Display from '../components/display';
|
import Display from '../components/display';
|
||||||
import InvitePreview from '../components/invite-preview';
|
import InvitePreview from '../components/invite-preview';
|
||||||
import { GuildMetadata } from '../../data-types';
|
import { Member, Token } from '../../data-types';
|
||||||
import ReactHelper from '../require/react-helper';
|
import ReactHelper from '../require/react-helper';
|
||||||
import { Duration } from 'moment';
|
import { Duration } from 'moment';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import DropdownInput from '../components/input-dropdown';
|
import DropdownInput from '../components/input-dropdown';
|
||||||
import Button from '../components/button';
|
import Button, { ButtonColorType } from '../components/button';
|
||||||
import InvitesTable from '../components/table-invites';
|
|
||||||
import GuildSubscriptions from '../require/guild-subscriptions';
|
import GuildSubscriptions from '../require/guild-subscriptions';
|
||||||
import ElementsUtil from '../require/elements-util';
|
import ElementsUtil from '../require/elements-util';
|
||||||
|
import BaseElements from '../require/base-elements';
|
||||||
|
|
||||||
|
|
||||||
export interface GuildInvitesDisplayProps {
|
export interface GuildInvitesDisplayProps {
|
||||||
@ -27,6 +27,8 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
|
|||||||
|
|
||||||
const url = 'https://localhost:3030'; // TODO: this will likely be a dropdown list at some point
|
const url = 'https://localhost:3030'; // TODO: this will likely be a dropdown list at some point
|
||||||
|
|
||||||
|
const [ tokens, tokensError ] = GuildSubscriptions.useTokensSubscription(guild);
|
||||||
|
|
||||||
const [ guildMeta, guildMetaError ] = GuildSubscriptions.useGuildMetadataSubscription(guild);
|
const [ guildMeta, guildMetaError ] = GuildSubscriptions.useGuildMetadataSubscription(guild);
|
||||||
const [ iconSrc ] = ReactHelper.useAsyncActionSubscription(
|
const [ iconSrc ] = ReactHelper.useAsyncActionSubscription(
|
||||||
async () => await ElementsUtil.getImageSrcFromResourceFailSoftly(guild, guildMeta?.iconResourceId ?? null),
|
async () => await ElementsUtil.getImageSrcFromResourceFailSoftly(guild, guildMeta?.iconResourceId ?? null),
|
||||||
@ -52,6 +54,31 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
|
|||||||
return null;
|
return null;
|
||||||
}, [ guildMetaError ]);
|
}, [ guildMetaError ]);
|
||||||
|
|
||||||
|
const tokenElements = useMemo(() => {
|
||||||
|
if (tokensError) {
|
||||||
|
return <div className="tokens-failed">Unable to load tokens</div>;
|
||||||
|
}
|
||||||
|
return tokens?.map((token: Token) => {
|
||||||
|
const userText = (token.member instanceof Member ? 'Used by ' + token.member.displayName : token.member?.id) ?? 'Unused Token';
|
||||||
|
return (
|
||||||
|
<div key={token.token} className="token-row">
|
||||||
|
<div className="user-token">
|
||||||
|
<div className="user">{userText}</div>
|
||||||
|
<div className="token">{token.token}</div>
|
||||||
|
</div>
|
||||||
|
<div className="created-expires">
|
||||||
|
<div className="created">Created {moment(token.created).fromNow()}</div>
|
||||||
|
<div className="expires">Expires {moment(token.expires).fromNow()}</div>
|
||||||
|
</div>
|
||||||
|
<div className="actions">
|
||||||
|
<Button colorType={ButtonColorType.BRAND}>{BaseElements.DOWNLOAD}</Button>
|
||||||
|
<Button colorType={ButtonColorType.NEGATIVE}>{BaseElements.TRASHCAN}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [ tokens, tokensError ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Display
|
<Display
|
||||||
infoMessage={null} errorMessage={errorMessage}
|
infoMessage={null} errorMessage={errorMessage}
|
||||||
@ -78,7 +105,7 @@ const GuildInvitesDisplay: FC<GuildInvitesDisplayProps> = (props: GuildInvitesDi
|
|||||||
<div className="divider" />
|
<div className="divider" />
|
||||||
<div className="view-invites">
|
<div className="view-invites">
|
||||||
<div className="title">Invite History</div>
|
<div className="title">Invite History</div>
|
||||||
<InvitesTable guild={guild} />
|
<div className="invites-table">{tokenElements}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Display>
|
</Display>
|
||||||
|
@ -32,7 +32,7 @@ export default function createTokenLogOverlay(document: Document, q: Q, guild: C
|
|||||||
// if (token.member != null) {
|
// if (token.member != null) {
|
||||||
// continue;
|
// continue;
|
||||||
// }
|
// }
|
||||||
const expired = token.expires < new Date();
|
const expired = token.expires && token.expires < new Date();
|
||||||
// NOTE: It may be nice to be able to click-to-copy the token
|
// NOTE: It may be nice to be able to click-to-copy the token
|
||||||
let memberText: string;
|
let memberText: string;
|
||||||
if (token.member instanceof Member) {
|
if (token.member instanceof Member) {
|
||||||
|
@ -3,7 +3,7 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
|
|||||||
import Logger from '../../../../logger/logger';
|
import Logger from '../../../../logger/logger';
|
||||||
const LOG = Logger.create(__filename, electronConsole);
|
const LOG = Logger.create(__filename, electronConsole);
|
||||||
|
|
||||||
import { GuildMetadata, Resource } from "../../data-types";
|
import { Changes, GuildMetadata, Resource } from "../../data-types";
|
||||||
import CombinedGuild from "../../guild-combined";
|
import CombinedGuild from "../../guild-combined";
|
||||||
|
|
||||||
import React, { DependencyList, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { DependencyList, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
@ -11,14 +11,24 @@ import { AutoVerifierChangesType } from "../../auto-verifier";
|
|||||||
import { Conflictable, Connectable } from "../../guild-types";
|
import { Conflictable, Connectable } from "../../guild-types";
|
||||||
import { EventEmitter } from 'tsee';
|
import { EventEmitter } from 'tsee';
|
||||||
import { IDQuery } from '../../auto-verifier-with-args';
|
import { IDQuery } from '../../auto-verifier-with-args';
|
||||||
|
import { Token } from '../../data-types';
|
||||||
|
|
||||||
export type SubscriptionEvents = {
|
export type SingleSubscriptionEvents = {
|
||||||
'fetch': () => void;
|
'fetch': () => void;
|
||||||
'update': () => void;
|
'updated': () => void;
|
||||||
'conflict': () => void;
|
'conflict': () => void;
|
||||||
'fetch-error': () => void;
|
'fetch-error': () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MultipleSubscriptionEvents<T> = {
|
||||||
|
'fetch': () => void;
|
||||||
|
'fetch-error': () => void;
|
||||||
|
'new': (newValues: T[]) => void;
|
||||||
|
'updated': (updatedValues: T[]) => void;
|
||||||
|
'removed': (removedValues: T[]) => void;
|
||||||
|
'conflict': (changes: Changes<T>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
interface EffectParams<T> {
|
interface EffectParams<T> {
|
||||||
guild: CombinedGuild;
|
guild: CombinedGuild;
|
||||||
onFetch: (value: T | null) => void;
|
onFetch: (value: T | null) => void;
|
||||||
@ -29,11 +39,31 @@ interface EffectParams<T> {
|
|||||||
|
|
||||||
type Arguments<T> = T extends (...args: infer A) => unknown ? A : never;
|
type Arguments<T> = T extends (...args: infer A) => unknown ? A : never;
|
||||||
|
|
||||||
interface EventMappingParams<T, UE extends keyof Connectable, CE extends keyof Conflictable> {
|
interface SingleEventMappingParams<T, UE extends keyof Connectable, CE extends keyof Conflictable> {
|
||||||
updateEventName: UE;
|
updatedEventName: UE;
|
||||||
updateEventArgsMap: (...args: Arguments<Connectable[UE]>) => T; // should be same as the params list from Connectable
|
updatedEventArgsMap: (...args: Arguments<Connectable[UE]>) => T;
|
||||||
conflictEventName: CE;
|
conflictEventName: CE;
|
||||||
conflictEventArgsMap: (...args: Arguments<Conflictable[CE]>) => T; // should be the same as the params list from Conflictable
|
conflictEventArgsMap: (...args: Arguments<Conflictable[CE]>) => T;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MultipleEventMappingParams<
|
||||||
|
T,
|
||||||
|
NE extends keyof Connectable,
|
||||||
|
UE extends keyof Connectable,
|
||||||
|
RE extends keyof Connectable,
|
||||||
|
CE extends keyof Conflictable
|
||||||
|
> {
|
||||||
|
newEventName: NE;
|
||||||
|
newEventArgsMap: (...args: Arguments<Connectable[NE]>) => T[]; // list of new elements
|
||||||
|
updatedEventName: UE;
|
||||||
|
updatedEventArgsMap: (...args: Arguments<Connectable[UE]>) => T[]; // list of updated elements
|
||||||
|
removedEventName: RE;
|
||||||
|
removedEventArgsMap: (...args: Arguments<Connectable[RE]>) => T[]; // list of removed elements
|
||||||
|
|
||||||
|
conflictEventName: CE;
|
||||||
|
conflictEventArgsMap: (...args: Arguments<Conflictable[CE]>) => Changes<T>;
|
||||||
|
|
||||||
|
sortFunc: (a: T, b: T) => number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class GuildSubscriptions {
|
export default class GuildSubscriptions {
|
||||||
@ -79,16 +109,19 @@ export default class GuildSubscriptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static useSingleGuildSubscription<T, UE extends keyof Connectable, CE extends keyof Conflictable>(
|
private static useSingleGuildSubscription<T, UE extends keyof Connectable, CE extends keyof Conflictable>(
|
||||||
guild: CombinedGuild, eventMappingParams: EventMappingParams<T, UE, CE>, fetchFunc: (() => Promise<T>) | (() => Promise<T | null>), fetchDeps: DependencyList
|
guild: CombinedGuild,
|
||||||
): [value: T | null, fetchError: unknown | null, events: EventEmitter<SubscriptionEvents>] {
|
eventMappingParams: SingleEventMappingParams<T, UE, CE>,
|
||||||
const { updateEventName, updateEventArgsMap, conflictEventName, conflictEventArgsMap } = eventMappingParams;
|
fetchFunc: (() => Promise<T>) | (() => Promise<T | null>),
|
||||||
|
fetchDeps: DependencyList
|
||||||
|
): [value: T | null, fetchError: unknown | null, events: EventEmitter<SingleSubscriptionEvents>] {
|
||||||
|
const { updatedEventName, updatedEventArgsMap, conflictEventName, conflictEventArgsMap } = eventMappingParams;
|
||||||
|
|
||||||
const isMountedRef = useRef<boolean>(false);
|
const isMountedRef = useRef<boolean>(false);
|
||||||
|
|
||||||
const [ fetchError, setFetchError ] = useState<unknown | null>(null);
|
const [ fetchError, setFetchError ] = useState<unknown | null>(null);
|
||||||
const [ value, setValue ] = useState<T | null>(null);
|
const [ value, setValue ] = useState<T | null>(null);
|
||||||
|
|
||||||
const events = useMemo(() => new EventEmitter<SubscriptionEvents>(), []);
|
const events = useMemo(() => new EventEmitter<SingleSubscriptionEvents>(), []);
|
||||||
|
|
||||||
const onFetch = useCallback((fetchValue: T | null) => {
|
const onFetch = useCallback((fetchValue: T | null) => {
|
||||||
setValue(fetchValue);
|
setValue(fetchValue);
|
||||||
@ -102,9 +135,9 @@ export default class GuildSubscriptions {
|
|||||||
events.emit('fetch-error');
|
events.emit('fetch-error');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onUpdate = useCallback((updateValue: T) => {
|
const onUpdated = useCallback((updateValue: T) => {
|
||||||
setValue(updateValue);
|
setValue(updateValue);
|
||||||
events.emit('update');
|
events.emit('updated');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onConflict = useCallback((conflictValue: T) => {
|
const onConflict = useCallback((conflictValue: T) => {
|
||||||
@ -116,8 +149,8 @@ export default class GuildSubscriptions {
|
|||||||
// otherwise, I may have done this wrong. Forcing it to work with these calls
|
// otherwise, I may have done this wrong. Forcing it to work with these calls
|
||||||
const boundUpdateFunc = useCallback((...args: Arguments<Connectable[UE]>): void => {
|
const boundUpdateFunc = useCallback((...args: Arguments<Connectable[UE]>): void => {
|
||||||
if (!isMountedRef.current) return;
|
if (!isMountedRef.current) return;
|
||||||
const value = updateEventArgsMap(...args);
|
const value = updatedEventArgsMap(...args);
|
||||||
onUpdate(value);
|
onUpdated(value);
|
||||||
}, []) as (Connectable & Conflictable)[UE];
|
}, []) as (Connectable & Conflictable)[UE];
|
||||||
const boundConflictFunc = useCallback((...args: Arguments<Conflictable[CE]>): void => {
|
const boundConflictFunc = useCallback((...args: Arguments<Conflictable[CE]>): void => {
|
||||||
if (!isMountedRef.current) return;
|
if (!isMountedRef.current) return;
|
||||||
@ -126,11 +159,129 @@ export default class GuildSubscriptions {
|
|||||||
}, []) as (Connectable & Conflictable)[CE];
|
}, []) as (Connectable & Conflictable)[CE];
|
||||||
|
|
||||||
const bindEventsFunc = useCallback(() => {
|
const bindEventsFunc = useCallback(() => {
|
||||||
guild.on(updateEventName, boundUpdateFunc);
|
guild.on(updatedEventName, boundUpdateFunc);
|
||||||
guild.on(conflictEventName, boundConflictFunc);
|
guild.on(conflictEventName, boundConflictFunc);
|
||||||
}, []);
|
}, []);
|
||||||
const unbindEventsFunc = useCallback(() => {
|
const unbindEventsFunc = useCallback(() => {
|
||||||
guild.off(updateEventName, boundUpdateFunc);
|
guild.off(updatedEventName, boundUpdateFunc);
|
||||||
|
guild.off(conflictEventName, boundConflictFunc);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
GuildSubscriptions.useGuildSubscriptionEffect(isMountedRef, {
|
||||||
|
guild,
|
||||||
|
onFetch,
|
||||||
|
onFetchError,
|
||||||
|
bindEventsFunc,
|
||||||
|
unbindEventsFunc
|
||||||
|
}, fetchFunc, fetchDeps);
|
||||||
|
|
||||||
|
return [ value, fetchError, events ];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static useMultipleGuildSubscription<
|
||||||
|
T extends { id: string },
|
||||||
|
NE extends keyof Connectable,
|
||||||
|
UE extends keyof Connectable,
|
||||||
|
RE extends keyof Connectable,
|
||||||
|
CE extends keyof Conflictable
|
||||||
|
>(
|
||||||
|
guild: CombinedGuild,
|
||||||
|
eventMappingParams: MultipleEventMappingParams<T, NE, UE, RE, CE>,
|
||||||
|
fetchFunc: (() => Promise<T[]>) | (() => Promise<T[] | null>),
|
||||||
|
fetchDeps: DependencyList
|
||||||
|
): [value: T[] | null, fetchError: unknown | null, events: EventEmitter<MultipleSubscriptionEvents<T>>] {
|
||||||
|
const {
|
||||||
|
newEventName, newEventArgsMap,
|
||||||
|
updatedEventName, updatedEventArgsMap,
|
||||||
|
removedEventName, removedEventArgsMap,
|
||||||
|
conflictEventName, conflictEventArgsMap,
|
||||||
|
sortFunc
|
||||||
|
} = eventMappingParams;
|
||||||
|
|
||||||
|
const isMountedRef = useRef<boolean>(false);
|
||||||
|
|
||||||
|
const [ fetchError, setFetchError ] = useState<unknown | null>(null);
|
||||||
|
const [ value, setValue ] = useState<T[] | null>(null);
|
||||||
|
|
||||||
|
const events = useMemo(() => new EventEmitter<MultipleSubscriptionEvents<T>>(), []);
|
||||||
|
|
||||||
|
const onFetch = useCallback((fetchValue: T[] | null) => {
|
||||||
|
setValue(fetchValue);
|
||||||
|
setFetchError(null);
|
||||||
|
events.emit('fetch');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onFetchError = useCallback((e: unknown) => {
|
||||||
|
setFetchError(e);
|
||||||
|
setValue(null);
|
||||||
|
events.emit('fetch-error');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onNew = useCallback((newElements: T[]) => {
|
||||||
|
setValue(currentValue => {
|
||||||
|
if (currentValue === null) return null;
|
||||||
|
return currentValue.concat(newElements).sort(sortFunc);
|
||||||
|
})
|
||||||
|
events.emit('new', newElements);
|
||||||
|
}, []);
|
||||||
|
const onUpdated = useCallback((updatedElements: T[]) => {
|
||||||
|
setValue(currentValue => {
|
||||||
|
if (currentValue === null) return null;
|
||||||
|
return currentValue.map(element => updatedElements.find(updatedElement => updatedElement.id === element.id) ?? element).sort(sortFunc);
|
||||||
|
});
|
||||||
|
events.emit('updated', updatedElements);
|
||||||
|
}, []);
|
||||||
|
const onRemoved = useCallback((removedElements: T[]) => {
|
||||||
|
setValue(currentValue => {
|
||||||
|
if (currentValue === null) return null;
|
||||||
|
const deletedIds = new Set(removedElements.map(deletedElement => deletedElement.id));
|
||||||
|
return currentValue.filter(element => !deletedIds.has(element.id)).sort(sortFunc);
|
||||||
|
});
|
||||||
|
events.emit('removed', removedElements);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onConflict = useCallback((changes: Changes<T>) => {
|
||||||
|
setValue(currentValue => {
|
||||||
|
if (currentValue === null) return null;
|
||||||
|
const deletedIds = new Set(changes.deleted.map(deletedElement => deletedElement.id));
|
||||||
|
return currentValue
|
||||||
|
.concat(changes.added)
|
||||||
|
.map(element => changes.updated.find(change => change.newDataPoint.id === element.id)?.newDataPoint ?? element)
|
||||||
|
.filter(element => !deletedIds.has(element.id))
|
||||||
|
.sort(sortFunc);
|
||||||
|
});
|
||||||
|
events.emit('conflict', changes);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 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
|
||||||
|
const boundNewFunc = useCallback((...args: Arguments<Connectable[NE]>): void => {
|
||||||
|
if (!isMountedRef.current) return;
|
||||||
|
onNew(newEventArgsMap(...args));
|
||||||
|
}, []) as (Connectable & Conflictable)[NE];
|
||||||
|
const boundUpdateFunc = useCallback((...args: Arguments<Connectable[UE]>): void => {
|
||||||
|
if (!isMountedRef.current) return;
|
||||||
|
onUpdated(updatedEventArgsMap(...args));
|
||||||
|
}, []) as (Connectable & Conflictable)[UE];
|
||||||
|
const boundRemovedFunc = useCallback((...args: Arguments<Connectable[RE]>): void => {
|
||||||
|
if (!isMountedRef.current) return;
|
||||||
|
onRemoved(removedEventArgsMap(...args));
|
||||||
|
}, []) as (Connectable & Conflictable)[RE];
|
||||||
|
const boundConflictFunc = useCallback((...args: Arguments<Conflictable[CE]>): void => {
|
||||||
|
if (!isMountedRef.current) return;
|
||||||
|
onConflict(conflictEventArgsMap(...args));
|
||||||
|
}, []) as (Connectable & Conflictable)[CE];
|
||||||
|
|
||||||
|
const bindEventsFunc = useCallback(() => {
|
||||||
|
guild.on(newEventName, boundNewFunc);
|
||||||
|
guild.on(updatedEventName, boundUpdateFunc);
|
||||||
|
guild.on(removedEventName, boundRemovedFunc);
|
||||||
|
guild.on(conflictEventName, boundConflictFunc);
|
||||||
|
}, []);
|
||||||
|
const unbindEventsFunc = useCallback(() => {
|
||||||
|
guild.off(newEventName, boundNewFunc);
|
||||||
|
guild.off(updatedEventName, boundUpdateFunc);
|
||||||
|
guild.off(removedEventName, boundRemovedFunc);
|
||||||
guild.off(conflictEventName, boundConflictFunc);
|
guild.off(conflictEventName, boundConflictFunc);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -147,17 +298,19 @@ export default class GuildSubscriptions {
|
|||||||
|
|
||||||
static useGuildMetadataSubscription(guild: CombinedGuild) {
|
static useGuildMetadataSubscription(guild: CombinedGuild) {
|
||||||
return GuildSubscriptions.useSingleGuildSubscription<GuildMetadata, 'update-metadata', 'conflict-metadata'>(guild, {
|
return GuildSubscriptions.useSingleGuildSubscription<GuildMetadata, 'update-metadata', 'conflict-metadata'>(guild, {
|
||||||
updateEventName: 'update-metadata',
|
updatedEventName: 'update-metadata',
|
||||||
updateEventArgsMap: (guildMeta: GuildMetadata) => guildMeta,
|
updatedEventArgsMap: (guildMeta: GuildMetadata) => guildMeta,
|
||||||
conflictEventName: 'conflict-metadata',
|
conflictEventName: 'conflict-metadata',
|
||||||
conflictEventArgsMap: (changesType: AutoVerifierChangesType, oldGuildMeta: GuildMetadata, newGuildMeta: GuildMetadata) => newGuildMeta
|
conflictEventArgsMap: (changesType: AutoVerifierChangesType, oldGuildMeta: GuildMetadata, newGuildMeta: GuildMetadata) => newGuildMeta
|
||||||
}, async () => await guild.fetchMetadata(), [ guild ]);
|
}, async () => {
|
||||||
|
return await guild.fetchMetadata()
|
||||||
|
}, [ guild ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static useResourceSubscription(guild: CombinedGuild, resourceId: string | null) {
|
static useResourceSubscription(guild: CombinedGuild, resourceId: string | null) {
|
||||||
return GuildSubscriptions.useSingleGuildSubscription<Resource, 'update-resource', 'conflict-resource'>(guild, {
|
return GuildSubscriptions.useSingleGuildSubscription<Resource, 'update-resource', 'conflict-resource'>(guild, {
|
||||||
updateEventName: 'update-resource',
|
updatedEventName: 'update-resource',
|
||||||
updateEventArgsMap: (resource: Resource) => resource,
|
updatedEventArgsMap: (resource: Resource) => resource,
|
||||||
conflictEventName: 'conflict-resource',
|
conflictEventName: 'conflict-resource',
|
||||||
conflictEventArgsMap: (query: IDQuery, changesType: AutoVerifierChangesType, oldResource: Resource, newResource: Resource) => newResource
|
conflictEventArgsMap: (query: IDQuery, changesType: AutoVerifierChangesType, oldResource: Resource, newResource: Resource) => newResource
|
||||||
}, async () => {
|
}, async () => {
|
||||||
@ -165,4 +318,20 @@ export default class GuildSubscriptions {
|
|||||||
return await guild.fetchResource(resourceId);
|
return await guild.fetchResource(resourceId);
|
||||||
}, [ guild, resourceId ]);
|
}, [ guild, resourceId ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static useTokensSubscription(guild: CombinedGuild) {
|
||||||
|
return GuildSubscriptions.useMultipleGuildSubscription<Token, 'new-tokens', 'update-tokens', 'remove-tokens', 'conflict-tokens'>(guild, {
|
||||||
|
newEventName: 'new-tokens',
|
||||||
|
newEventArgsMap: (tokens: Token[]) => tokens,
|
||||||
|
updatedEventName: 'update-tokens',
|
||||||
|
updatedEventArgsMap: (updatedTokens: Token[]) => updatedTokens,
|
||||||
|
removedEventName: 'remove-tokens',
|
||||||
|
removedEventArgsMap: (removedTokens: Token[]) => removedTokens,
|
||||||
|
conflictEventName: 'conflict-tokens',
|
||||||
|
conflictEventArgsMap: (changesType: AutoVerifierChangesType, changes: Changes<Token>) => changes,
|
||||||
|
sortFunc: Token.sortFurthestExpiresFirst
|
||||||
|
}, async () => {
|
||||||
|
return await guild.fetchTokens();
|
||||||
|
}, [ guild ]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,10 @@ export type Connectable = {
|
|||||||
|
|
||||||
// TODO: Implement these in server/combined guild
|
// TODO: Implement these in server/combined guild
|
||||||
'update-resource': (updatedResource: Resource) => void;
|
'update-resource': (updatedResource: Resource) => void;
|
||||||
'remove-resource': (removedResource: Resource) => void;
|
|
||||||
|
'new-tokens': (tokens: Token[]) => void;
|
||||||
|
'update-tokens': (updatedTokens: Token[]) => void;
|
||||||
|
'remove-tokens': (removedTokens: Token[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Conflictable could emit conflict-based events if data changed based on verification
|
// A Conflictable could emit conflict-based events if data changed based on verification
|
||||||
@ -165,4 +168,4 @@ export const GuildEventNames = [
|
|||||||
'conflict-messages',
|
'conflict-messages',
|
||||||
'conflict-tokens',
|
'conflict-tokens',
|
||||||
'conflict-resource',
|
'conflict-resource',
|
||||||
]
|
];
|
||||||
|
Loading…
Reference in New Issue
Block a user