[CORL-666] Viewer Events (#2681)

* feat: viewer event system

* feat: more events

* feat: MORE events

* fix: tests

* fix: rte focus events

* chore: add comments

* fix: remove listening to events

* chore: update RTE

* fix: tests

* feature: generate event docs

* fix: remove obsolete line in docs

* chore: improve docs

* chore: improve formatting

* feature: protect events.md from getting out of sync

* chore: small improvements

* fix: removing redundant lambda
This commit is contained in:
Vinh
2019-11-08 17:17:01 +00:00
committed by Wyatt Johnson
parent ce4a3408fc
commit 18346d1683
99 changed files with 3839 additions and 1330 deletions
+140
View File
@@ -0,0 +1,140 @@
import { EventEmitter2 } from "eventemitter2";
import { useCoralContext } from "./bootstrap";
/**
* _Viewer Events_ are emitted when the viewer performs certain actions.
* They can be subscribed to using the `events` parameter in
* `Coral.createStreamEmbed`.
*/
export interface ViewerEvent<T> {
emit: keyof T extends never
? (eventEmitter: EventEmitter2) => void
: (eventEmitter: EventEmitter2, data: T) => void;
}
/**
* A ViewerNetworkEventStarted represents ViewerNetworkEvent that has
* started and is waiting for the response.
*/
export interface ViewerNetworkEventStarted<
T extends { success: object; error: object }
> {
/**
* Emits a success event and include the rtt time.
*/
success: keyof T["success"] extends never
? () => void
: (success: T["success"]) => void;
/**
* Emits an error event and include the rtt time.
*/
error: keyof T["error"] extends never
? () => void
: (error: T["error"]) => void;
}
/**
* _Viewer Network Events_ are _Viewer Events_ that involves a network request and
* thus can succeed or fail. Succeeding events have the suffix `.success`
* while failing events an `.error` suffix.
*
* Moreover _Viewer Network Events_ contain the `rtt` field which indicates
* the time it needed from initiating the request until the _UI_ has been
* updated with the response data.
*/
export interface ViewerNetworkEvent<
T extends { success: object; error: object }
> {
/**
* Mark the network request as started. This will also start tracking the rtt time.
*/
begin: keyof T extends "success" | "error"
? (eventEmitter: EventEmitter2) => ViewerNetworkEventStarted<T>
: (
eventEmitter: EventEmitter2,
data: Pick<T, Exclude<keyof T, "success" | "error">>
) => ViewerNetworkEventStarted<T>;
}
/**
* createViewerEvent creates a ViewerNetworkEvent object.
*
* @param name name of the event
*/
export function createViewerNetworkEvent<
T extends { success: object; error: object }
>(name: string): ViewerNetworkEvent<T> {
return {
begin: ((eventEmitter, data) => {
const ms = Date.now();
return {
success: (success => {
const final: any = {
...data,
rtt: Date.now() - ms,
};
if (success) {
final.success = success;
}
eventEmitter.emit(`${name}.success`, final);
}) as ViewerNetworkEventStarted<T>["success"],
error: (error => {
const final: any = {
...data,
rtt: Date.now() - ms,
};
if (error) {
final.error = error;
}
eventEmitter.emit(`${name}.error`, final);
}) as ViewerNetworkEventStarted<T>["error"],
};
}) as ViewerNetworkEvent<T>["begin"],
};
}
/**
* createViewerEvent creates a ViewerEvent object.
*
* @param name name of the event
*/
export function createViewerEvent<T>(name: string): ViewerEvent<T> {
return {
emit: ((eventEmitter, data) => {
eventEmitter.emit(name, data);
}) as ViewerEvent<T>["emit"],
};
}
/**
* useViewerEvent inject the eventEmitter and returns a simple
* callback to emit the event.
*/
export function useViewerEvent<T>(
viewerEvent: ViewerEvent<T>
): keyof T extends never ? () => void : (data: T) => void {
const { eventEmitter } = useCoralContext();
return ((data?: T) => {
viewerEvent.emit(eventEmitter, data as any);
}) as any;
}
/**
* useViewerNetworkEvent injects the eventEmitter into a ViewNetworkEvent
* and returns a simple callback to begin the event.
*/
export function useViewerNetworkEvent<
T extends { success: object; error: object }
>(
viewerNetworkEvent: ViewerNetworkEvent<T>
): keyof T extends "success" | "error"
? () => ViewerNetworkEventStarted<T>
: (
data: Pick<T, Exclude<keyof T, "success" | "error">>
) => ViewerNetworkEventStarted<T> {
const { eventEmitter } = useCoralContext();
return ((data?: T) => {
return viewerNetworkEvent.begin(eventEmitter, data as any);
}) as any;
}
@@ -38,7 +38,6 @@ function createMutationContainer<T extends string, I, R>(
);
private commit = (input: I) => {
this.props.context.eventEmitter.emit(`mutation.${propName}`, input);
return commit(
this.props.context.relayEnvironment,
input,
@@ -69,7 +69,6 @@ export function useFetch<V, R>(
const context = useCoralContext();
return useCallback<FetchProp<typeof fetch>>(
((variables: V) => {
context.eventEmitter.emit(`fetch.${fetch.name}`, variables);
return fetch.fetch(context.relayEnvironment, variables, context);
}) as any,
[context]
@@ -94,10 +93,6 @@ export function withFetch<N extends string, V, R>(
public static displayName = wrapDisplayName(BaseComponent, "withFetch");
private fetch = (variables: V) => {
this.props.context.eventEmitter.emit(
`fetch.${fetch.name}`,
variables
);
return fetch.fetch(
this.props.context.relayEnvironment,
variables,
@@ -71,7 +71,6 @@ export function useMutation<I, R>(
const context = useCoralContext();
return useCallback<MutationProp<typeof mutation>>(
((input: I) => {
context.eventEmitter.emit(`mutation.${mutation.name}`, input);
return mutation.commit(context.relayEnvironment, input, context);
}) as any,
[context]
@@ -99,10 +98,6 @@ export function withMutation<N extends string, I, R>(
);
private commit = (input: I) => {
this.props.context.eventEmitter.emit(
`mutation.${mutation.name}`,
input
);
return mutation.commit(
this.props.context.relayEnvironment,
input,
@@ -50,7 +50,6 @@ export function useSubscription<V>(
const context = useCoralContext();
return useCallback<SubscriptionProp<typeof subscription>>(
((variables: V) => {
context.eventEmitter.emit(`subscription.${subscription.name}`, variables);
return subscription.subscribe(
context.relayEnvironment,
variables,
@@ -4,23 +4,29 @@ import { RelayPaginationProp } from "react-relay";
/**
* useLoadMore is a react hook that returns a `loadMore` callback
* and a `isLoadingMore` boolean.
*
* @param relay {RelayPaginationProp}
* @param count {number}
*/
export default function useLoadMore(
relay: RelayPaginationProp,
count: number
): [() => void, boolean] {
): [() => Promise<void>, boolean] {
const [isLoadingMore, setIsLoadingMore] = useState(false);
const loadMore = useCallback(() => {
if (!relay.hasMore() || relay.isLoading()) {
return;
return Promise.resolve();
}
setIsLoadingMore(true);
relay.loadMore(count, error => {
setIsLoadingMore(false);
if (error) {
// eslint-disable-next-line no-console
console.error(error);
}
return new Promise<void>((resolve, reject) => {
relay.loadMore(count, error => {
setIsLoadingMore(false);
if (error) {
reject(error);
} else {
resolve();
}
});
});
}, [relay]);
return [loadMore, isLoadingMore];