11import React from 'react' ;
22
33import { idle , useQueryData } from '@gravity-ui/data-source' ;
4- import { Flex , Text , sp } from '@gravity-ui/uikit' ;
4+ import { ArrowUp } from '@gravity-ui/icons' ;
5+ import { Button , Flex , Icon , Text , Tooltip , sp } from '@gravity-ui/uikit' ;
56import { useParams } from 'react-router-dom' ;
67
78import type { ListLogsResponse } from '../../../../../shared/api/listLogs' ;
@@ -14,6 +15,8 @@ import {i18nInstance} from '../../../../i18n-common/i18nInstance';
1415import { generateInstanceHref } from '../../../../utils/common' ;
1516import { InstanceLayout } from '../../layouts/InstanceLayout' ;
1617
18+ import { BUILD_LOGS_PAGE_ID } from './constants' ;
19+ import { useAutoscrollingBehavior } from './hooks' ;
1720import { i18n } from './i18n' ;
1821
1922import * as styles from './BuildLogs.module.scss' ;
@@ -41,8 +44,14 @@ interface LogsContentProps {
4144}
4245
4346const LogsContent = ( { instance, listLogs} : LogsContentProps ) => {
47+ const logsContainerRef = React . useRef < HTMLDivElement > ( null ) ;
48+ const LogsBottomRef = React . useRef < HTMLAnchorElement | HTMLDivElement > ( null ) ;
49+
50+ const { isScrollTopButtonVisible} = useAutoscrollingBehavior ( listLogs , LogsBottomRef ) ;
51+
4452 const renderLog = ( item : Output , index : number ) => {
4553 const { command, duration, stdout, stderr} = item ;
54+ const isLastLog = index === ( listLogs ?. logs ?. length || 0 ) - 1 ;
4655
4756 return (
4857 < React . Fragment key = { `log-${ index } ` } >
@@ -80,6 +89,7 @@ const LogsContent = ({instance, listLogs}: LogsContentProps) => {
8089 < hr />
8190 </ div >
8291 ) }
92+ { isLastLog && < div ref = { LogsBottomRef as React . RefObject < HTMLDivElement > } /> }
8393 </ React . Fragment >
8494 ) ;
8595 } ;
@@ -105,16 +115,48 @@ const LogsContent = ({instance, listLogs}: LogsContentProps) => {
105115 } ) ;
106116
107117 return (
108- < a className = { styles . instanceLink } href = { href } >
118+ < a
119+ ref = { LogsBottomRef as React . RefObject < HTMLAnchorElement > }
120+ className = { styles . instanceLink }
121+ href = { href }
122+ >
109123 { i18nInstance ( 'go-to-instance' ) }
110124 </ a >
111125 ) ;
112126 } ;
113127
128+ const renderScrollToTopButton = ( ) => {
129+ const handleScrollToTop = ( ) => {
130+ logsContainerRef . current ?. scrollIntoView ( {
131+ behavior : 'smooth' ,
132+ block : 'start' ,
133+ } ) ;
134+ } ;
135+
136+ if ( ! isScrollTopButtonVisible ) {
137+ return null ;
138+ }
139+
140+ return (
141+ < Tooltip content = { i18n ( 'return-to-start-of-logs' ) } placement = "left" openDelay = { 0 } >
142+ < Button
143+ view = "raised"
144+ pin = "circle-circle"
145+ size = "xl"
146+ className = { styles . scrollToUpButton }
147+ onClick = { handleScrollToTop }
148+ >
149+ < Icon data = { ArrowUp } size = { 24 } />
150+ </ Button >
151+ </ Tooltip >
152+ ) ;
153+ } ;
154+
114155 return (
115- < div >
156+ < div ref = { logsContainerRef } id = "logs" >
116157 { listLogs ?. logs ?. map ( renderLog ) }
117158 { renderInstanceLink ( ) }
159+ { renderScrollToTopButton ( ) }
118160 </ div >
119161 ) ;
120162} ;
@@ -135,6 +177,7 @@ export const InstanceBuildLogsPage = () => {
135177 listLogs : listLogsQuery . data ,
136178 } ) }
137179 className = { styles . buildLogs }
180+ id = { BUILD_LOGS_PAGE_ID }
138181 >
139182 < DataLoader
140183 status = { instanceQuery . status }
0 commit comments