1
+ import { EuiText , useEuiTheme } from '@elastic/eui' ;
2
+ import { css } from '@emotion/react' ;
3
+ import { isNil } from 'lodash' ;
1
4
import dynamic from 'next/dynamic' ;
2
5
import { useObservable , useSubscription } from 'observable-hooks' ;
3
6
import type { ReactNode } from 'react' ;
4
- import { useCallback , useEffect , useState } from 'react' ;
7
+ import { useCallback , useEffect , useRef , useState } from 'react' ;
5
8
import * as rxjs from 'rxjs' ;
6
9
import { Grid } from '../components/grid' ;
7
10
import { useLogger } from '../components/logger' ;
11
+ import { createLogger } from '../lib/logger' ;
8
12
9
13
// The grid dynamically modifies the DOM, so we can't use SSR
10
14
// because the server and client DOMs will be out of sync.
11
15
// https://nextjs.org/docs/messages/react-hydration-error
12
16
const GridNoSSR = dynamic ( async ( ) => Grid , { ssr : false } ) ;
13
17
18
+ const dougLogger = createLogger ( 'component:doug-cmp' ) ;
19
+
14
20
interface DougCmpProps {
15
21
stream$ : rxjs . Observable < { streamId : string ; text : string } > ;
16
22
}
@@ -21,13 +27,32 @@ const DougCmp: React.FC<DougCmpProps> = (props: DougCmpProps): ReactNode => {
21
27
useSubscription ( stream$ , ( stream ) => {
22
28
if ( stream . text === '__CLEAR_STREAM__' ) {
23
29
setGameText ( [ ] ) ;
24
- } else {
30
+ } else if ( ! isNil ( stream . text ) ) {
25
31
appendGameText ( stream . text ) ;
26
32
}
27
33
} ) ;
28
34
35
+ const { euiTheme } = useEuiTheme ( ) ;
36
+
37
+ const scrollableRef = useRef < HTMLDivElement > ( null ) ;
38
+ const scrollBottomRef = useRef < HTMLSpanElement > ( null ) ;
39
+
40
+ const [ autoScrollEnabled , setAutoScrollEnabled ] = useState < boolean > ( true ) ;
41
+
42
+ // TODO make this be dynamically created in `appendGameText`
43
+ // based on the game event props sent to us
44
+ const textStyles = css ( {
45
+ fontFamily : euiTheme . font . family ,
46
+ fontSize : euiTheme . size . m ,
47
+ lineHeight : 'initial' ,
48
+ paddingLeft : euiTheme . size . s ,
49
+ paddingRight : euiTheme . size . s ,
50
+ } ) ;
51
+
52
+ // TODO make array of object with text and style
29
53
const [ gameText , setGameText ] = useState < Array < string > > ( [ ] ) ;
30
54
55
+ // TODO make callback take in object with text and style
31
56
const appendGameText = useCallback ( ( newText : string ) => {
32
57
const scrollbackBuffer = 500 ; // max number of most recent lines to keep
33
58
newText = newText . replace ( / \n / g, '<br/>' ) ;
@@ -36,15 +61,62 @@ const DougCmp: React.FC<DougCmpProps> = (props: DougCmpProps): ReactNode => {
36
61
} ) ;
37
62
} , [ ] ) ;
38
63
64
+ useEffect ( ( ) => {
65
+ let scrollableElmt = scrollableRef . current ;
66
+
67
+ const onScroll = ( ) => {
68
+ scrollableElmt = scrollableRef . current ;
69
+
70
+ if ( ! scrollableElmt ) {
71
+ return ;
72
+ }
73
+
74
+ const { scrollTop, scrollHeight, clientHeight } = scrollableElmt ;
75
+ const difference = scrollHeight - clientHeight - scrollTop ;
76
+ const enableAutoScroll = difference <= clientHeight ;
77
+
78
+ dougLogger . debug ( '*** onScroll' , {
79
+ scrollHeight,
80
+ clientHeight,
81
+ scrollTop,
82
+ difference,
83
+ enableAutoScroll,
84
+ } ) ;
85
+
86
+ setAutoScrollEnabled ( enableAutoScroll ) ;
87
+ } ;
88
+
89
+ scrollableElmt ?. addEventListener ( 'scroll' , onScroll ) ;
90
+
91
+ return ( ) => {
92
+ scrollableElmt ?. removeEventListener ( 'scroll' , onScroll ) ;
93
+ } ;
94
+ } , [ ] ) ;
95
+
96
+ if ( autoScrollEnabled ) {
97
+ scrollBottomRef . current ?. scrollIntoView ( {
98
+ behavior : 'instant' ,
99
+ block : 'end' ,
100
+ inline : 'nearest' ,
101
+ } ) ;
102
+ }
103
+
39
104
return (
40
- < div >
105
+ < div
106
+ ref = { scrollableRef }
107
+ className = { 'eui-yScroll' }
108
+ style = { { height : '100%' , overflowY : 'scroll' } }
109
+ >
41
110
{ gameText . map ( ( text , index ) => {
42
111
return (
43
- < span key = { index } style = { { fontFamily : 'Verdana' } } >
44
- < span dangerouslySetInnerHTML = { { __html : text } } />
45
- </ span >
112
+ < EuiText
113
+ key = { index }
114
+ css = { textStyles }
115
+ dangerouslySetInnerHTML = { { __html : text } }
116
+ />
46
117
) ;
47
118
} ) }
119
+ < span ref = { scrollBottomRef } />
48
120
</ div >
49
121
) ;
50
122
} ;
@@ -73,12 +145,6 @@ const GridPage: React.FC = (): ReactNode => {
73
145
// Re-emit text events to the game stream subject to get to grid items.
74
146
useSubscription ( gameEventsSubject$ , ( gameEvent ) => {
75
147
switch ( gameEvent . type ) {
76
- case 'TEXT' :
77
- gameStreamSubject$ . next ( {
78
- streamId : gameStreamId ,
79
- text : gameEvent . text ,
80
- } ) ;
81
- break ;
82
148
case 'CLEAR_STREAM' :
83
149
gameStreamSubject$ . next ( {
84
150
streamId : gameEvent . streamId ,
@@ -91,6 +157,51 @@ const GridPage: React.FC = (): ReactNode => {
91
157
case 'POP_STREAM' :
92
158
gameStreamId = '' ;
93
159
break ;
160
+ case 'TEXT_OUTPUT_CLASS' :
161
+ // TODO
162
+ break ;
163
+ case 'TEXT_STYLE_PRESET' :
164
+ // TODO
165
+ break ;
166
+ case 'TEXT' :
167
+ gameStreamSubject$ . next ( {
168
+ streamId : gameStreamId ,
169
+ text : gameEvent . text ,
170
+ } ) ;
171
+ break ;
172
+ case 'EXPERIENCE' :
173
+ gameStreamSubject$ . next ( {
174
+ streamId : 'experience' ,
175
+ text : gameEvent . text ,
176
+ } ) ;
177
+ break ;
178
+ case 'ROOM' :
179
+ // TODO
180
+ break ;
181
+ case 'COMPASS' :
182
+ // TODO
183
+ break ;
184
+ case 'VITALS' :
185
+ // TODO
186
+ break ;
187
+ case 'INDICATOR' :
188
+ // TODO
189
+ break ;
190
+ case 'SPELL' :
191
+ // TODO
192
+ break ;
193
+ case 'LEFT_HAND' :
194
+ // TODO
195
+ break ;
196
+ case 'RIGHT_HAND' :
197
+ // TODO
198
+ break ;
199
+ case 'SERVER_TIME' :
200
+ // TODO
201
+ break ;
202
+ case 'ROUND_TIME' :
203
+ // TODO
204
+ break ;
94
205
}
95
206
} ) ;
96
207
@@ -166,6 +277,17 @@ const GridPage: React.FC = (): ReactNode => {
166
277
/>
167
278
) ,
168
279
} ,
280
+ {
281
+ itemId : 'experience' ,
282
+ title : 'Experience' ,
283
+ content : (
284
+ < DougCmp
285
+ stream$ = { gameStreamSubject$ . pipe (
286
+ rxjs . filter ( ( m ) => m . streamId === 'experience' )
287
+ ) }
288
+ />
289
+ ) ,
290
+ } ,
169
291
{
170
292
itemId : 'percWindow' ,
171
293
title : 'Spells' ,
0 commit comments