Skip to content

Commit c6556bc

Browse files
committed
feat: character service hooks
1 parent efe6c05 commit c6556bc

File tree

3 files changed

+127
-41
lines changed

3 files changed

+127
-41
lines changed

electron/renderer/components/sidebar/characters/table-list-characters.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {
88
EuiTitle,
99
EuiToolTip,
1010
} from '@elastic/eui';
11-
import { groupBy, isNil } from 'lodash-es';
11+
import groupBy from 'lodash-es/groupBy.js';
12+
import isNil from 'lodash-es/isNil.js';
1213
import type { ReactElement, ReactNode } from 'react';
1314
import { Fragment, memo, useMemo } from 'react';
1415
import { useListCharacters } from '../../../hooks/characters.jsx';

electron/renderer/hooks/accounts.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { sortBy } from 'lodash-es';
1+
import sortBy from 'lodash-es/sortBy.js';
22
import { useCallback, useEffect, useState } from 'react';
33
import { runInBackground } from '../lib/async/run-in-background.js';
44
import type { Account } from '../types/game.types.js';
+124-39
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
import { sortBy } from 'lodash-es';
1+
import isEqual from 'lodash-es/isEqual.js';
2+
import sortBy from 'lodash-es/sortBy.js';
23
import { useCallback, useEffect, useState } from 'react';
4+
import { create } from 'zustand';
5+
import { useShallow } from 'zustand/react/shallow';
36
import { runInBackground } from '../lib/async/run-in-background.js';
47
import type { Character } from '../types/game.types.js';
58
import { usePubSub, useSubscribe } from './pubsub.jsx';
69

710
/**
811
* Returns a list of characters.
9-
* Automatically refreshes the list when an character is saved or removed.
12+
* Automatically refreshes the list when a character is saved or removed.
1013
*/
1114
export const useListCharacters = (): Array<Character> => {
1215
const [characters, setCharacters] = useState<Array<Character>>([]);
@@ -32,31 +35,18 @@ export const useListCharacters = (): Array<Character> => {
3235
return characters;
3336
};
3437

35-
type SaveCharacterFn = (options: {
36-
accountName: string;
37-
characterName: string;
38-
gameCode: string;
39-
}) => Promise<void>;
38+
type SaveCharacterFn = (character: Character) => Promise<void>;
4039

4140
/**
42-
* Provides a function that when called saves an character.
41+
* Provides a function that when called saves a character.
4342
*/
4443
export const useSaveCharacter = (): SaveCharacterFn => {
4544
const { publish } = usePubSub();
4645

4746
const fn = useCallback<SaveCharacterFn>(
48-
async (options): Promise<void> => {
49-
const { accountName, characterName, gameCode } = options;
50-
await window.api.saveCharacter({
51-
accountName,
52-
characterName,
53-
gameCode,
54-
});
55-
publish('character:saved', {
56-
accountName,
57-
characterName,
58-
gameCode,
59-
});
47+
async (character): Promise<void> => {
48+
await window.api.saveCharacter(character);
49+
publish('character:saved', character);
6050
publish('characters:reload');
6151
},
6252
[publish]
@@ -65,35 +55,130 @@ export const useSaveCharacter = (): SaveCharacterFn => {
6555
return fn;
6656
};
6757

68-
type RemoveCharacterFn = (options: {
69-
accountName: string;
70-
characterName: string;
71-
gameCode: string;
72-
}) => Promise<void>;
58+
type RemoveCharacterFn = (character: Character) => Promise<void>;
7359

7460
/**
75-
* Provides a function that when called removes an character.
61+
* Provides a function that when called removes a character.
62+
* If the character is currently playing, it will be quit first.
7663
*/
7764
export const useRemoveCharacter = (): RemoveCharacterFn => {
7865
const { publish } = usePubSub();
7966

67+
const playingCharacter = useGetPlayingCharacter();
68+
const quitCharacter = useQuitCharacter();
69+
8070
const fn = useCallback<RemoveCharacterFn>(
81-
async (options): Promise<void> => {
82-
const { accountName, characterName, gameCode } = options;
83-
await window.api.removeCharacter({
84-
accountName,
85-
characterName,
86-
gameCode,
87-
});
88-
publish('character:removed', {
89-
accountName,
90-
characterName,
91-
gameCode,
92-
});
71+
async (character): Promise<void> => {
72+
if (isEqual(playingCharacter, character)) {
73+
await quitCharacter();
74+
}
75+
await window.api.removeCharacter(character);
76+
publish('character:removed', character);
9377
publish('characters:reload');
9478
},
95-
[publish]
79+
[playingCharacter, quitCharacter, publish]
9680
);
9781

9882
return fn;
9983
};
84+
85+
type PlayCharacterFn = (character: Character) => Promise<void>;
86+
87+
/**
88+
* Provides a function that when called plays a character.
89+
* If another character is already playing, it will be quit first.
90+
*/
91+
export const usePlayCharacter = (): PlayCharacterFn => {
92+
const { publish } = usePubSub();
93+
94+
const setPlayingCharacter = useSetPlayingCharacter();
95+
const quitCharacter = useQuitCharacter();
96+
97+
const fn = useCallback<PlayCharacterFn>(
98+
async (character): Promise<void> => {
99+
await quitCharacter(); // quit any currently playing character, if any
100+
await window.api.playCharacter(character);
101+
setPlayingCharacter(character);
102+
publish('character:play:started', character);
103+
publish('characters:reload');
104+
},
105+
[setPlayingCharacter, quitCharacter, publish]
106+
);
107+
108+
return fn;
109+
};
110+
111+
type QuitCharacterFn = () => Promise<void>;
112+
113+
/**
114+
* Provides a function that when called quits the current playing character.
115+
*/
116+
export const useQuitCharacter = (): QuitCharacterFn => {
117+
const { publish } = usePubSub();
118+
119+
const playingCharacter = useGetPlayingCharacter();
120+
const setPlayingCharacter = useSetPlayingCharacter();
121+
122+
const fn = useCallback<QuitCharacterFn>(async (): Promise<void> => {
123+
if (playingCharacter) {
124+
await window.api.sendCommand('quit');
125+
setPlayingCharacter(undefined);
126+
publish('character:play:stopped', playingCharacter);
127+
publish('characters:reload');
128+
}
129+
}, [playingCharacter, setPlayingCharacter, publish]);
130+
131+
return fn;
132+
};
133+
134+
/**
135+
* Returns the character currently being played, if any.
136+
*/
137+
export const useGetPlayingCharacter = (): Character | undefined => {
138+
const { playingCharacter } = characterStore(
139+
useShallow((state) => {
140+
return {
141+
playingCharacter: state.playingCharacter,
142+
};
143+
})
144+
);
145+
146+
return playingCharacter;
147+
};
148+
149+
/**
150+
* Internal only.
151+
* Use the `usePlayCharacter` hook instead.
152+
*/
153+
const useSetPlayingCharacter = (): ((character?: Character) => void) => {
154+
const { setPlayingCharacter } = characterStore(
155+
useShallow((state) => {
156+
return {
157+
setPlayingCharacter: state.setPlayingCharacter,
158+
};
159+
})
160+
);
161+
162+
return setPlayingCharacter;
163+
};
164+
165+
interface CharacterStoreData {
166+
/**
167+
* The character currently being played, if any.
168+
*/
169+
playingCharacter?: Character;
170+
171+
/**
172+
* Sets the character currently being played.
173+
* To signal that no character is playing, pass `undefined`.
174+
*/
175+
setPlayingCharacter: (character?: Character) => void;
176+
}
177+
178+
const characterStore = create<CharacterStoreData>((set) => ({
179+
playingCharacter: undefined,
180+
181+
setPlayingCharacter: (character) => {
182+
set({ playingCharacter: character });
183+
},
184+
}));

0 commit comments

Comments
 (0)