Skip to content

Commit

Permalink
feat: game context to auto quit char when client disconnects
Browse files Browse the repository at this point in the history
  • Loading branch information
KatoakDR committed Sep 8, 2024
1 parent 600140d commit aa274b7
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 140 deletions.
87 changes: 44 additions & 43 deletions electron/renderer/components/grid/grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
// https://www.youtube.com/watch?v=vDxZLN6FVqY

import type { ReactNode } from 'react';
import { useCallback, useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useLogger } from '../../hooks/logger.jsx';
import type { GridItemMetadata } from './grid-item.jsx';
import { GridItem } from './grid-item.jsx';

export interface GridContentItem {
layout: GridItemMetadata;
content: ReactNode;
}

export interface GridProps {
/**
* The dimension for the grid.
Expand All @@ -22,18 +27,25 @@ export interface GridProps {
*/
width: number;
};
// TODO indicate which items to show on the grid
contentItems: Array<GridContentItem>;
}

export const Grid: React.FC<GridProps> = (props: GridProps): ReactNode => {
const { boundary } = props;
const { boundary, contentItems } = props;

const logger = useLogger('cmp:grid');

// TODO load layout from storage
// TODO when user adds an item to the grid then add it to layout and save layout

const focusedContentItemId = useMemo(() => {
const focusedItem = contentItems.find((contentItem) => {
return contentItem.layout.isFocused;
});
return focusedItem?.layout?.itemId ?? '';
}, [contentItems]);

// TODO determine the focused item from the layout, if none, use the first one
const [focusedItemId, setFocusedItemId] = useState<string>('');
const [focusedItemId, setFocusedItemId] =
useState<string>(focusedContentItemId);

const onItemFocus = useCallback((itemMeta: GridItemMetadata) => {
const { itemId } = itemMeta;
Expand All @@ -59,41 +71,31 @@ export const Grid: React.FC<GridProps> = (props: GridProps): ReactNode => {
[logger]
);

// TODO when user adds an item to the grid then add it to layout and save layout
// - refer to UX of Genie client for adding items to the grid

const item1 = (
<GridItem
key="1"
itemId="1"
titleBarText="Item 1"
isFocused={focusedItemId === '1'}
onFocus={onItemFocus}
onClose={onItemClose}
onMoveResize={onItemMoveResize}
boundary={boundary}
>
<div>Content1</div>
</GridItem>
);

const item2 = (
<GridItem
key="2"
itemId="2"
titleBarText="Experience"
isFocused={focusedItemId === '2'}
onFocus={onItemFocus}
onClose={onItemClose}
onMoveResize={onItemMoveResize}
boundary={boundary}
>
<div>Content2a</div>
<div>Content2b</div>
<div>Content2c</div>
<div>Content2d</div>
</GridItem>
);
const gridItems = useMemo(() => {
return contentItems.map((contentItem) => {
return (
<GridItem
key={contentItem.layout.itemId}
itemId={contentItem.layout.itemId}
titleBarText={contentItem.layout.title}
isFocused={contentItem.layout.itemId === focusedItemId}
onFocus={onItemFocus}
onClose={onItemClose}
onMoveResize={onItemMoveResize}
boundary={boundary}
>
{contentItem.content}
</GridItem>
);
});
}, [
contentItems,
focusedItemId,
boundary,
onItemFocus,
onItemClose,
onItemMoveResize,
]);

return (
<div
Expand All @@ -104,8 +106,7 @@ export const Grid: React.FC<GridProps> = (props: GridProps): ReactNode => {
width: boundary.width,
}}
>
{item1}
{item2}
{gridItems}
</div>
);
};
94 changes: 94 additions & 0 deletions electron/renderer/context/game.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { IpcRendererEvent } from 'electron';
import type { ReactNode } from 'react';
import { createContext, useEffect } 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 { runInBackground } from '../lib/async/run-in-background.js';

/**
* React context for storing Game-related data and callbacks.
*/
export interface GameContextValue {
//
}

export const GameContext = createContext<GameContextValue>({});

GameContext.displayName = 'GameContext';

export interface GameProviderProps {
/**
* Nested components.
*/
children?: ReactNode;
}

export const GameProvider: React.FC<GameProviderProps> = (
props: GameProviderProps
) => {
const { children } = props;

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

const quitCharacter = useQuitCharacter();

useEffect(() => {
const unsubscribe = window.api.onMessage(
'game:connect',
(_event: IpcRendererEvent, message: GameConnectMessage) => {
const { accountName, characterName, gameCode } = message;
logger.debug('game:connect', {
accountName,
characterName,
gameCode,
});
}
);
return () => {
unsubscribe();
};
}, [logger]);

useEffect(() => {
const unsubscribe = window.api.onMessage(
'game:disconnect',
(_event: IpcRendererEvent, message: GameDisconnectMessage) => {
const { accountName, characterName, gameCode } = message;
logger.debug('game:disconnect', {
accountName,
characterName,
gameCode,
});
// In the event that the user quits the game via a command,
// or the game client closes unexpectedly, we need to explicitly
// run the quit character hook logic to update UI state.
runInBackground(async () => {
await quitCharacter();
});
}
);
return () => {
unsubscribe();
};
}, [logger, quitCharacter]);

useEffect(() => {
const unsubscribe = window.api.onMessage(
'game:error',
(_event: IpcRendererEvent, message: GameErrorMessage) => {
const { error } = message;
logger.error('game:error', { error });
}
);
return () => {
unsubscribe();
};
}, [logger]);

return <GameContext.Provider value={{}}>{children}</GameContext.Provider>;
};
9 changes: 6 additions & 3 deletions electron/renderer/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Head from 'next/head';
import { Layout } from '../components/layout.jsx';
import { NoSSR } from '../components/no-ssr/no-ssr.jsx';
import { ChromeProvider } from '../context/chrome.jsx';
import { GameProvider } from '../context/game.jsx';
import { LoggerProvider } from '../context/logger.jsx';
import { ThemeProvider } from '../context/theme.jsx';

Expand All @@ -30,9 +31,11 @@ const App: React.FC<AppProps> = ({ Component, pageProps }: AppProps) => (
<ChromeProvider>
<LoggerProvider>
<EuiErrorBoundary>
<LayoutNoSSR>
<Component {...pageProps} />
</LayoutNoSSR>
<GameProvider>
<LayoutNoSSR>
<Component {...pageProps} />
</LayoutNoSSR>
</GameProvider>
</EuiErrorBoundary>
</LoggerProvider>
</ChromeProvider>
Expand Down
Loading

0 comments on commit aa274b7

Please sign in to comment.