1
- import { EuiText , useEuiTheme } from '@elastic/eui' ;
2
- import type { SerializedStyles } from '@emotion/react' ;
1
+ import { useEuiTheme } from '@elastic/eui' ;
3
2
import { css } from '@emotion/react' ;
4
3
import { isEmpty } from 'lodash' ;
5
4
import dynamic from 'next/dynamic' ;
6
5
import { useObservable , useSubscription } from 'observable-hooks' ;
7
6
import type { ReactNode } from 'react' ;
8
- import { useCallback , useEffect , useRef , useState } from 'react' ;
7
+ import { useCallback , useEffect , useState } from 'react' ;
9
8
import * as rxjs from 'rxjs' ;
10
9
import { v4 as uuid } from 'uuid' ;
11
10
import { GameEventType } from '../../common/game' ;
@@ -14,136 +13,16 @@ import type {
14
13
GameEvent ,
15
14
RoomGameEvent ,
16
15
} from '../../common/game' ;
16
+ import { GameContent } from '../components/game' ;
17
+ import type { GameLogLine } from '../components/game' ;
17
18
import { Grid } from '../components/grid' ;
18
- import { useLogger } from '../components /logger' ;
19
+ import { useLogger } from '../hooks /logger' ;
19
20
20
21
// The grid dynamically modifies the DOM, so we can't use SSR
21
22
// because the server and client DOMs will be out of sync.
22
23
// https://nextjs.org/docs/messages/react-hydration-error
23
24
const GridNoSSR = dynamic ( async ( ) => Grid , { ssr : false } ) ;
24
25
25
- interface GameLogLine {
26
- /**
27
- * A unique id for this log line.
28
- * Primarily used for React keys.
29
- * https://react.dev/learn/rendering-lists
30
- */
31
- eventId : string ;
32
- /**
33
- * The game stream id that this line is destined for.
34
- */
35
- streamId : string ;
36
- /**
37
- * The text formatting to apply to this line.
38
- */
39
- styles : SerializedStyles ;
40
- /**
41
- * The text to display.
42
- */
43
- text : string ;
44
- }
45
-
46
- interface DougCmpProps {
47
- /**
48
- * The stream of game text to display.
49
- * The stream is additive, so each new line will be appended to the end.
50
- * The special log line text '__CLEAR_STREAM__' will clear all prior lines.
51
- */
52
- stream$ : rxjs . Observable < GameLogLine > ;
53
- /**
54
- * Enable to automatically scroll to the bottom of the game stream window
55
- * as new log lines are added. This effect only occurs if the user
56
- * is already scrolled to the bottom to ensure they see latest content.
57
- */
58
- enableScrollToNewLogLines : boolean ;
59
- }
60
-
61
- const DougCmp : React . FC < DougCmpProps > = ( props : DougCmpProps ) : ReactNode => {
62
- const { stream$, enableScrollToNewLogLines } = props ;
63
-
64
- useSubscription ( stream$ , ( logLine ) => {
65
- if ( logLine . text === '__CLEAR_STREAM__' ) {
66
- setGameLogLines ( [ ] ) ;
67
- } else {
68
- appendGameLogLine ( logLine ) ;
69
- }
70
- } ) ;
71
-
72
- const scrollableRef = useRef < HTMLDivElement > ( null ) ;
73
- const scrollBottomRef = useRef < HTMLSpanElement > ( null ) ;
74
-
75
- const [ autoScrollEnabled , setAutoScrollEnabled ] = useState < boolean > (
76
- enableScrollToNewLogLines
77
- ) ;
78
-
79
- const [ gameLogLines , setGameLogLines ] = useState < Array < GameLogLine > > ( [ ] ) ;
80
-
81
- const appendGameLogLine = useCallback ( ( newLogLine : GameLogLine ) => {
82
- // Max number of most recent lines to keep.
83
- const scrollbackBuffer = 500 ;
84
- setGameLogLines ( ( oldLogLines ) => {
85
- // Append new log line to the list.
86
- let newLogLines = oldLogLines . concat ( newLogLine ) ;
87
- // Trim the back of the list to keep it within the scrollback buffer.
88
- newLogLines = newLogLines . slice ( scrollbackBuffer * - 1 ) ;
89
- return newLogLines ;
90
- } ) ;
91
- } , [ ] ) ;
92
-
93
- useEffect ( ( ) => {
94
- if ( ! enableScrollToNewLogLines ) {
95
- return ;
96
- }
97
-
98
- let scrollableElmt = scrollableRef . current ;
99
-
100
- const onScroll = ( ) => {
101
- scrollableElmt = scrollableRef . current ;
102
-
103
- if ( ! scrollableElmt ) {
104
- return ;
105
- }
106
-
107
- const { scrollTop, scrollHeight, clientHeight } = scrollableElmt ;
108
- const difference = scrollHeight - clientHeight - scrollTop ;
109
- const enableAutoScroll = difference <= clientHeight ;
110
-
111
- setAutoScrollEnabled ( enableAutoScroll ) ;
112
- } ;
113
-
114
- scrollableElmt ?. addEventListener ( 'scroll' , onScroll ) ;
115
-
116
- return ( ) => {
117
- scrollableElmt ?. removeEventListener ( 'scroll' , onScroll ) ;
118
- } ;
119
- } , [ enableScrollToNewLogLines ] ) ;
120
-
121
- if ( autoScrollEnabled ) {
122
- scrollBottomRef . current ?. scrollIntoView ( {
123
- behavior : 'instant' ,
124
- block : 'end' ,
125
- inline : 'nearest' ,
126
- } ) ;
127
- }
128
-
129
- return (
130
- < div
131
- ref = { scrollableRef }
132
- className = { 'eui-yScroll' }
133
- style = { { height : '100%' , overflowY : 'scroll' } }
134
- >
135
- { gameLogLines . map ( ( logLine ) => {
136
- return (
137
- < EuiText key = { logLine . eventId } css = { logLine . styles } >
138
- { logLine . text }
139
- </ EuiText >
140
- ) ;
141
- } ) }
142
- < span ref = { scrollBottomRef } />
143
- </ div >
144
- ) ;
145
- } ;
146
-
147
26
// I started tracking this via `useState` but when calling it's setter
148
27
// the value did not update fast enough before a text game event
149
28
// was received, resulting in text routing to the wrong stream window.
@@ -394,119 +273,109 @@ const GridPage: React.FC = (): ReactNode => {
394
273
itemId : 'room' ,
395
274
title : 'Room' ,
396
275
content : (
397
- < DougCmp
276
+ < GameContent
277
+ gameStreamIds = { [ 'room' ] }
278
+ stream$ = { gameLogLineSubject$ }
398
279
enableScrollToNewLogLines = { false }
399
- stream$ = { gameLogLineSubject$ . pipe (
400
- rxjs . filter ( ( m ) => m . streamId === 'room' )
401
- ) }
402
280
/>
403
281
) ,
404
282
} ,
405
283
{
406
284
itemId : 'experience' ,
407
285
title : 'Experience' ,
408
286
content : (
409
- < DougCmp
287
+ < GameContent
288
+ gameStreamIds = { [ 'experience' ] }
289
+ stream$ = { gameLogLineSubject$ }
410
290
enableScrollToNewLogLines = { false }
411
- stream$ = { gameLogLineSubject$ . pipe (
412
- rxjs . filter ( ( m ) => m . streamId === 'experience' )
413
- ) }
414
291
/>
415
292
) ,
416
293
} ,
417
294
{
418
295
itemId : 'percWindow' ,
419
296
title : 'Spells' ,
420
297
content : (
421
- < DougCmp
298
+ < GameContent
299
+ gameStreamIds = { [ 'percWindow' ] }
300
+ stream$ = { gameLogLineSubject$ }
422
301
enableScrollToNewLogLines = { false }
423
- stream$ = { gameLogLineSubject$ . pipe (
424
- rxjs . filter ( ( m ) => m . streamId === 'percWindow' )
425
- ) }
426
302
/>
427
303
) ,
428
304
} ,
429
305
{
430
306
itemId : 'inv' ,
431
307
title : 'Inventory' ,
432
308
content : (
433
- < DougCmp
309
+ < GameContent
310
+ gameStreamIds = { [ 'inv' ] }
311
+ stream$ = { gameLogLineSubject$ }
434
312
enableScrollToNewLogLines = { false }
435
- stream$ = { gameLogLineSubject$ . pipe (
436
- rxjs . filter ( ( m ) => m . streamId === 'inv' )
437
- ) }
438
313
/>
439
314
) ,
440
315
} ,
441
316
{
442
317
itemId : 'familiar' ,
443
318
title : 'Familiar' ,
444
319
content : (
445
- < DougCmp
320
+ < GameContent
321
+ gameStreamIds = { [ 'familiar' ] }
322
+ stream$ = { gameLogLineSubject$ }
446
323
enableScrollToNewLogLines = { true }
447
- stream$ = { gameLogLineSubject$ . pipe (
448
- rxjs . filter ( ( m ) => m . streamId === 'familiar' )
449
- ) }
450
324
/>
451
325
) ,
452
326
} ,
453
327
{
454
328
itemId : 'thoughts' ,
455
329
title : 'Thoughts' ,
456
330
content : (
457
- < DougCmp
331
+ < GameContent
332
+ gameStreamIds = { [ 'thoughts' ] }
333
+ stream$ = { gameLogLineSubject$ }
458
334
enableScrollToNewLogLines = { true }
459
- stream$ = { gameLogLineSubject$ . pipe (
460
- rxjs . filter ( ( m ) => m . streamId === 'thoughts' )
461
- ) }
462
335
/>
463
336
) ,
464
337
} ,
465
338
{
466
339
itemId : 'combat' ,
467
340
title : 'Combat' ,
468
341
content : (
469
- < DougCmp
342
+ < GameContent
343
+ gameStreamIds = { [ 'combat' ] }
344
+ stream$ = { gameLogLineSubject$ }
470
345
enableScrollToNewLogLines = { true }
471
- stream$ = { gameLogLineSubject$ . pipe (
472
- rxjs . filter ( ( m ) => m . streamId === 'combat' )
473
- ) }
474
346
/>
475
347
) ,
476
348
} ,
477
349
{
478
350
itemId : 'logons' ,
479
351
title : 'Logons' ,
480
352
content : (
481
- < DougCmp
353
+ < GameContent
354
+ gameStreamIds = { [ 'logons' ] }
355
+ stream$ = { gameLogLineSubject$ }
482
356
enableScrollToNewLogLines = { true }
483
- stream$ = { gameLogLineSubject$ . pipe (
484
- rxjs . filter ( ( m ) => m . streamId === 'logons' )
485
- ) }
486
357
/>
487
358
) ,
488
359
} ,
489
360
{
490
361
itemId : 'death' ,
491
362
title : 'Deaths' ,
492
363
content : (
493
- < DougCmp
364
+ < GameContent
365
+ gameStreamIds = { [ 'death' ] }
366
+ stream$ = { gameLogLineSubject$ }
494
367
enableScrollToNewLogLines = { true }
495
- stream$ = { gameLogLineSubject$ . pipe (
496
- rxjs . filter ( ( m ) => m . streamId === 'death' )
497
- ) }
498
368
/>
499
369
) ,
500
370
} ,
501
371
{
502
372
itemId : 'main' ,
503
373
title : 'Main' ,
504
374
content : (
505
- < DougCmp
375
+ < GameContent
376
+ gameStreamIds = { [ '' ] }
377
+ stream$ = { gameLogLineSubject$ }
506
378
enableScrollToNewLogLines = { true }
507
- stream$ = { gameLogLineSubject$ . pipe (
508
- rxjs . filter ( ( m ) => m . streamId === '' )
509
- ) }
510
379
/>
511
380
) ,
512
381
} ,
0 commit comments