1
- import { sortBy } from 'lodash-es' ;
1
+ import isEqual from 'lodash-es/isEqual.js' ;
2
+ import sortBy from 'lodash-es/sortBy.js' ;
2
3
import { useCallback , useEffect , useState } from 'react' ;
4
+ import { create } from 'zustand' ;
5
+ import { useShallow } from 'zustand/react/shallow' ;
3
6
import { runInBackground } from '../lib/async/run-in-background.js' ;
4
7
import type { Character } from '../types/game.types.js' ;
5
8
import { usePubSub , useSubscribe } from './pubsub.jsx' ;
6
9
7
10
/**
8
11
* 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.
10
13
*/
11
14
export const useListCharacters = ( ) : Array < Character > => {
12
15
const [ characters , setCharacters ] = useState < Array < Character > > ( [ ] ) ;
@@ -32,31 +35,18 @@ export const useListCharacters = (): Array<Character> => {
32
35
return characters ;
33
36
} ;
34
37
35
- type SaveCharacterFn = ( options : {
36
- accountName : string ;
37
- characterName : string ;
38
- gameCode : string ;
39
- } ) => Promise < void > ;
38
+ type SaveCharacterFn = ( character : Character ) => Promise < void > ;
40
39
41
40
/**
42
- * Provides a function that when called saves an character.
41
+ * Provides a function that when called saves a character.
43
42
*/
44
43
export const useSaveCharacter = ( ) : SaveCharacterFn => {
45
44
const { publish } = usePubSub ( ) ;
46
45
47
46
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 ) ;
60
50
publish ( 'characters:reload' ) ;
61
51
} ,
62
52
[ publish ]
@@ -65,35 +55,130 @@ export const useSaveCharacter = (): SaveCharacterFn => {
65
55
return fn ;
66
56
} ;
67
57
68
- type RemoveCharacterFn = ( options : {
69
- accountName : string ;
70
- characterName : string ;
71
- gameCode : string ;
72
- } ) => Promise < void > ;
58
+ type RemoveCharacterFn = ( character : Character ) => Promise < void > ;
73
59
74
60
/**
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.
76
63
*/
77
64
export const useRemoveCharacter = ( ) : RemoveCharacterFn => {
78
65
const { publish } = usePubSub ( ) ;
79
66
67
+ const playingCharacter = useGetPlayingCharacter ( ) ;
68
+ const quitCharacter = useQuitCharacter ( ) ;
69
+
80
70
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 ) ;
93
77
publish ( 'characters:reload' ) ;
94
78
} ,
95
- [ publish ]
79
+ [ playingCharacter , quitCharacter , publish ]
96
80
) ;
97
81
98
82
return fn ;
99
83
} ;
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