@@ -9,7 +9,7 @@ Show the last latex build log, i.e., output from last time we ran the LaTeX buil
9
9
10
10
import Ansi from "@cocalc/frontend/components/ansi-to-react" ;
11
11
import { Button , Flex , Tooltip } from "antd" ;
12
- import { Fragment , useState } from "react" ;
12
+ import { Fragment , useEffect , useMemo , useRef , useState } from "react" ;
13
13
14
14
import { AntdTabItem , Tab , Tabs } from "@cocalc/frontend/antd-bootstrap" ;
15
15
import { CSS , React , Rendered , useRedux } from "@cocalc/frontend/app-framework" ;
@@ -52,6 +52,25 @@ export const Build: React.FC<Props> = React.memo((props) => {
52
52
BUILD_SPECS . latex . label ,
53
53
) ;
54
54
const [ error_tab , set_error_tab ] = useState < string | null > ( null ) ;
55
+ const logContainerRef = useRef < HTMLDivElement > ( null ) ;
56
+ const [ shownLog , setShownLog ] = useState < string > ( "" ) ;
57
+
58
+ // Compute whether we have running jobs - this determines UI precedence
59
+ const hasRunningJobs = useMemo ( ( ) => {
60
+ return (
61
+ build_logs ?. some ( ( job ) => {
62
+ const jobJS : BuildLog = job ?. toJS ( ) ;
63
+ return jobJS ?. type === "async" && jobJS ?. status === "running" ;
64
+ } ) ?? false
65
+ ) ;
66
+ } , [ build_logs ] ) ;
67
+
68
+ // Auto-scroll to bottom when log updates
69
+ useEffect ( ( ) => {
70
+ if ( logContainerRef . current ) {
71
+ logContainerRef . current . scrollTop = logContainerRef . current . scrollHeight ;
72
+ }
73
+ } , [ shownLog ] ) ;
55
74
56
75
let no_errors = true ;
57
76
@@ -179,7 +198,8 @@ export const Build: React.FC<Props> = React.memo((props) => {
179
198
}
180
199
181
200
function render_logs ( ) : Rendered {
182
- if ( status ) return ;
201
+ // Hide logs when jobs are running - show live output instead
202
+ if ( hasRunningJobs ) return ;
183
203
184
204
const items : AntdTabItem [ ] = [ ] ;
185
205
@@ -230,12 +250,17 @@ export const Build: React.FC<Props> = React.memo((props) => {
230
250
const infos : React . JSX . Element [ ] = [ ] ;
231
251
let isLongRunning = false ;
232
252
let logTail = "" ;
253
+
233
254
build_logs . forEach ( ( infoI , key ) => {
234
255
const info : ExecuteCodeOutput = infoI ?. toJS ( ) ;
235
256
if ( ! info || info . type !== "async" || info . status !== "running" ) return ;
236
257
const stats_str = getResourceUsage ( info . stats , "last" ) ;
237
258
const start = info . start ;
238
- logTail = tail ( ( info . stdout ?? "" ) + ( info . stderr ?? "" ) , 6 ) ;
259
+ logTail = tail ( ( info . stdout ?? "" ) + ( info . stderr ?? "" ) , 100 ) ;
260
+ // Update state for auto-scrolling effect
261
+ if ( logTail !== shownLog ) {
262
+ setShownLog ( logTail ) ;
263
+ }
239
264
isLongRunning ||=
240
265
typeof start === "number" &&
241
266
webapp_client . server_time ( ) - start > WARN_LONG_RUNNING_S * 1000 ;
@@ -287,19 +312,36 @@ export const Build: React.FC<Props> = React.memo((props) => {
287
312
</ Tooltip >
288
313
</ Flex >
289
314
</ div >
290
- < div style = { logStyle } >
315
+ < div
316
+ style = { {
317
+ ...logStyle ,
318
+ maxHeight : "300px" ,
319
+ overflow : "hidden" ,
320
+ display : "flex" ,
321
+ flexDirection : "column" ,
322
+ } }
323
+ >
291
324
< div
292
325
style = { {
293
326
fontWeight : "bold" ,
294
327
whiteSpace : "nowrap" ,
295
328
overflow : "hidden" ,
296
329
textOverflow : "ellipsis" ,
330
+ flexShrink : 0 ,
297
331
} }
298
332
>
299
333
{ status }
300
334
{ "\n" }
301
335
</ div >
302
- { logTail }
336
+ < div
337
+ ref = { logContainerRef }
338
+ style = { {
339
+ overflowY : "auto" ,
340
+ flexGrow : 1 ,
341
+ } }
342
+ >
343
+ < Ansi > { shownLog } </ Ansi >
344
+ </ div >
303
345
</ div >
304
346
</ >
305
347
) ;
0 commit comments