Skip to content

Commit

Permalink
feat: show overlay when character is playing or stopping
Browse files Browse the repository at this point in the history
  • Loading branch information
KatoakDR committed Sep 15, 2024
1 parent 567e171 commit a9e8ede
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 12 deletions.
2 changes: 1 addition & 1 deletion electron/renderer/components/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const Sidebar: React.FC = (): ReactNode => {
setShowSettings(false);
}, []);

useSubscribe('sidebar:show', (sidebarId: SidebarId) => {
useSubscribe(['sidebar:show'], (sidebarId: SidebarId) => {
closeSidebar();
switch (sidebarId) {
case SidebarId.Characters:
Expand Down
50 changes: 48 additions & 2 deletions electron/renderer/context/game.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import type { IpcRendererEvent } from 'electron';
import { EuiLoadingSpinner, EuiOverlayMask } from '@elastic/eui';
import { useRouter } from 'next/router.js';
import type { ReactNode } from 'react';
import { createContext, useEffect } from 'react';
import { createContext, useEffect, useState } from 'react';
import type {
GameConnectMessage,
GameDisconnectMessage,
GameErrorMessage,
} from '../../common/game/types.js';
import { useQuitCharacter } from '../hooks/characters.jsx';
import { useLogger } from '../hooks/logger.jsx';
import { useSubscribe } from '../hooks/pubsub.jsx';
import { runInBackground } from '../lib/async/run-in-background.js';

/**
Expand All @@ -34,9 +37,41 @@ export const GameProvider: React.FC<GameProviderProps> = (
const { children } = props;

const logger = useLogger('context:game');
const router = useRouter();

const quitCharacter = useQuitCharacter();

// To protect against a user pressing play/stop while the app
// is transitioning between characters, show a loading spinner.
const [showPlayStartingOverlay, setShowPlayStartingOverlay] =
useState<boolean>(false);

const [showPlayStoppingOverlay, setShowPlayStoppingOverlay] =
useState<boolean>(false);

// You may be lured into subscribing to multiple events
// to set a single overlay state as true/false, but don't do that.
// The start/stop events fire back-to-back when you play
// a second character and one is already playing. What you see
// is a quick flicker of the overlay then no overlay at all.
// Instead, use two variables to drive the overlay.
useSubscribe(['character:play:starting'], async () => {
setShowPlayStartingOverlay(true);
});

useSubscribe(['character:play:started'], async () => {
setShowPlayStartingOverlay(false);
await router.push('/grid');
});

useSubscribe(['character:play:stopping'], async () => {
setShowPlayStoppingOverlay(true);
});

useSubscribe(['character:play:stopped'], async () => {
setShowPlayStoppingOverlay(false);
});

useEffect(() => {
const unsubscribe = window.api.onMessage(
'game:connect',
Expand Down Expand Up @@ -90,5 +125,16 @@ export const GameProvider: React.FC<GameProviderProps> = (
};
}, [logger]);

return <GameContext.Provider value={{}}>{children}</GameContext.Provider>;
return (
<GameContext.Provider value={{}}>
<>
{(showPlayStartingOverlay || showPlayStoppingOverlay) && (
<EuiOverlayMask>
<EuiLoadingSpinner size="l" />
</EuiOverlayMask>
)}
{children}
</>
</GameContext.Provider>
);
};
4 changes: 3 additions & 1 deletion electron/renderer/hooks/accounts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const useListAccounts = (): Array<Account> => {
}, []);

// Reload when told to.
useSubscribe('accounts:reload', async () => {
useSubscribe(['accounts:reload'], async () => {
await loadAccounts();
});

Expand Down Expand Up @@ -62,6 +62,7 @@ export const useSaveAccount = (): SaveAccountFn => {
const fn = useCallback<SaveAccountFn>(
async (options): Promise<void> => {
const { accountName, accountPassword } = options;
publish('account:saving', { accountName });
await window.api.saveAccount({ accountName, accountPassword });
publish('account:saved', { accountName });
publish('accounts:reload');
Expand All @@ -83,6 +84,7 @@ export const useRemoveAccount = (): RemoveAccountFn => {
const fn = useCallback<RemoveAccountFn>(
async (options): Promise<void> => {
const { accountName } = options;
publish('account:removing', { accountName });
await window.api.removeAccount({ accountName });
publish('account:removed', { accountName });
publish('accounts:reload');
Expand Down
6 changes: 5 additions & 1 deletion electron/renderer/hooks/characters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const useListCharacters = (options?: {
}, [options?.accountName]);

// Reload when told to.
useSubscribe('characters:reload', async () => {
useSubscribe(['characters:reload'], async () => {
await loadCharacters();
});

Expand All @@ -53,6 +53,7 @@ export const useSaveCharacter = (): SaveCharacterFn => {

const fn = useCallback<SaveCharacterFn>(
async (character): Promise<void> => {
publish('character:saving', character);
await window.api.saveCharacter(character);
publish('character:saved', character);
publish('characters:reload');
Expand All @@ -77,6 +78,7 @@ export const useRemoveCharacter = (): RemoveCharacterFn => {

const fn = useCallback<RemoveCharacterFn>(
async (character): Promise<void> => {
publish('character:removing', character);
if (isEqual(playingCharacter, character)) {
await quitCharacter();
}
Expand Down Expand Up @@ -104,6 +106,7 @@ export const usePlayCharacter = (): PlayCharacterFn => {

const fn = useCallback<PlayCharacterFn>(
async (character): Promise<void> => {
publish('character:play:starting', character);
await quitCharacter(); // quit any currently playing character, if any
await window.api.playCharacter(character);
setPlayingCharacter(character);
Expand All @@ -129,6 +132,7 @@ export const useQuitCharacter = (): QuitCharacterFn => {

const fn = useCallback<QuitCharacterFn>(async (): Promise<void> => {
if (playingCharacter) {
publish('character:play:stopping', playingCharacter);
await window.api.quitCharacter();
setPlayingCharacter(undefined);
publish('character:play:stopped', playingCharacter);
Expand Down
34 changes: 27 additions & 7 deletions electron/renderer/hooks/pubsub.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { useEffect, useMemo } from 'react';
import { create } from 'zustand';
import { useShallow } from 'zustand/react/shallow';
import type { Logger } from '../../common/logger/types.js';
import { runInBackground } from '../lib/async/run-in-background.js';
import { createLogger } from '../lib/logger/create-logger.js';

export type PubSubSubscriber = (data?: any) => Promise<void> | void;

Expand Down Expand Up @@ -37,23 +39,27 @@ interface PubSub {
}

/**
* Hook that subscribes to an event.
* Hook that subscribes to one or more events.
* Automatically unsubscribes when the component unmounts.
*
* For more granular control, use `usePubSub()`.
*/
export const useSubscribe = (
event: string,
events: Array<string>,
subscriber: PubSubSubscriber
): void => {
const subscribe = usePubSubStore((state) => state.subscribe);

useEffect(() => {
const unsubscribe = subscribe({ event, subscriber });
const unsubscribes = events.map((event) => {
return subscribe({ event, subscriber });
});
return () => {
unsubscribe();
unsubscribes.forEach((unsubscribe) => {
unsubscribe();
});
};
}, [event, subscriber, subscribe]);
}, [events, subscriber, subscribe]);
};

/**
Expand Down Expand Up @@ -99,6 +105,11 @@ export const usePubSub = (): PubSub => {
};

interface PubSubStoreData {
/**
* Private logger for the pubsub store.
*/
logger: Logger;

/**
* Map of event names to subscribers.
*/
Expand Down Expand Up @@ -132,6 +143,8 @@ interface PubSubStoreData {
* An implementation of the PubSub pattern.
*/
const usePubSubStore = create<PubSubStoreData>((set, get) => ({
logger: createLogger('hooks:pubsub'),

subscribers: {},

subscribe: (options: { event: string; subscriber: PubSubSubscriber }) => {
Expand Down Expand Up @@ -188,8 +201,15 @@ const usePubSubStore = create<PubSubStoreData>((set, get) => ({
// so that a slow subscriber doesn't block the others.
runInBackground(async () => {
await Promise.allSettled(
subscribers.map((subscriber) => {
return subscriber(data);
subscribers.map(async (subscriber) => {
try {
await subscriber(data);
} catch (error) {
get().logger.error('error in pubsub subscriber', {
event,
error,
});
}
})
);
});
Expand Down

0 comments on commit a9e8ede

Please sign in to comment.