mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-27 00:23:41 +09:00 
			
		
		
		
	Add action auto-scroll (#30057)
Adds an auto-scroll/follow feature to running actions (fix #25186, fix #28535). When new log lines are appended and the bottom of the logs container (`.action-view-right`) is visible at this time, the page automatically scrolls down to the bottom of the logs. --------- Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		| @@ -7,8 +7,8 @@ import {SvgIcon} from '../svg.ts'; | |||||||
|  |  | ||||||
| withDefaults(defineProps<{ | withDefaults(defineProps<{ | ||||||
|   status: 'success' | 'skipped' | 'waiting' | 'blocked' | 'running' | 'failure' | 'cancelled' | 'unknown', |   status: 'success' | 'skipped' | 'waiting' | 'blocked' | 'running' | 'failure' | 'cancelled' | 'unknown', | ||||||
|   size: number, |   size?: number, | ||||||
|   className: string, |   className?: string, | ||||||
|   localeStatus?: string, |   localeStatus?: string, | ||||||
| }>(), { | }>(), { | ||||||
|   size: 16, |   size: 16, | ||||||
|   | |||||||
| @@ -38,6 +38,11 @@ function parseLineCommand(line: LogLine): LogLineCommand | null { | |||||||
|   return null; |   return null; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function isLogElementInViewport(el: HTMLElement): boolean { | ||||||
|  |   const rect = el.getBoundingClientRect(); | ||||||
|  |   return rect.top >= 0 && rect.bottom <= window.innerHeight; // only check height but not width | ||||||
|  | } | ||||||
|  |  | ||||||
| const sfc = { | const sfc = { | ||||||
|   name: 'RepoActionView', |   name: 'RepoActionView', | ||||||
|   components: { |   components: { | ||||||
| @@ -142,9 +147,14 @@ const sfc = { | |||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   methods: { |   methods: { | ||||||
|     // get the active container element, either the `job-step-logs` or the `job-log-list` in the `job-log-group` |     // get the job step logs container ('.job-step-logs') | ||||||
|     getLogsContainer(stepIndex: number) { |     getJobStepLogsContainer(stepIndex: number): HTMLElement { | ||||||
|       const el = this.$refs.logs[stepIndex]; |       return this.$refs.logs[stepIndex]; | ||||||
|  |     }, | ||||||
|  |  | ||||||
|  |     // get the active logs container element, either the `job-step-logs` or the `job-log-list` in the `job-log-group` | ||||||
|  |     getActiveLogsContainer(stepIndex: number): HTMLElement { | ||||||
|  |       const el = this.getJobStepLogsContainer(stepIndex); | ||||||
|       return el._stepLogsActiveContainer ?? el; |       return el._stepLogsActiveContainer ?? el; | ||||||
|     }, |     }, | ||||||
|     // begin a log group |     // begin a log group | ||||||
| @@ -217,9 +227,15 @@ const sfc = { | |||||||
|       ); |       ); | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|  |     shouldAutoScroll(stepIndex: number): boolean { | ||||||
|  |       const el = this.getJobStepLogsContainer(stepIndex); | ||||||
|  |       if (!el.lastChild) return false; | ||||||
|  |       return isLogElementInViewport(el.lastChild); | ||||||
|  |     }, | ||||||
|  |  | ||||||
|     appendLogs(stepIndex: number, startTime: number, logLines: LogLine[]) { |     appendLogs(stepIndex: number, startTime: number, logLines: LogLine[]) { | ||||||
|       for (const line of logLines) { |       for (const line of logLines) { | ||||||
|         const el = this.getLogsContainer(stepIndex); |         const el = this.getActiveLogsContainer(stepIndex); | ||||||
|         const cmd = parseLineCommand(line); |         const cmd = parseLineCommand(line); | ||||||
|         if (cmd?.name === 'group') { |         if (cmd?.name === 'group') { | ||||||
|           this.beginLogGroup(stepIndex, startTime, line, cmd); |           this.beginLogGroup(stepIndex, startTime, line, cmd); | ||||||
| @@ -278,6 +294,14 @@ const sfc = { | |||||||
|             this.currentJobStepsStates[i] = {cursor: null, expanded: false}; |             this.currentJobStepsStates[i] = {cursor: null, expanded: false}; | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // find the step indexes that need to auto-scroll | ||||||
|  |         const autoScrollStepIndexes = new Map<number, boolean>(); | ||||||
|  |         for (const logs of job.logs.stepsLog ?? []) { | ||||||
|  |           if (autoScrollStepIndexes.has(logs.step)) continue; | ||||||
|  |           autoScrollStepIndexes.set(logs.step, this.shouldAutoScroll(logs.step)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // append logs to the UI |         // append logs to the UI | ||||||
|         for (const logs of job.logs.stepsLog ?? []) { |         for (const logs of job.logs.stepsLog ?? []) { | ||||||
|           // save the cursor, it will be passed to backend next time |           // save the cursor, it will be passed to backend next time | ||||||
| @@ -285,6 +309,15 @@ const sfc = { | |||||||
|           this.appendLogs(logs.step, logs.started, logs.lines); |           this.appendLogs(logs.step, logs.started, logs.lines); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // auto-scroll to the last log line of the last step | ||||||
|  |         let autoScrollJobStepElement: HTMLElement; | ||||||
|  |         for (let stepIndex = 0; stepIndex < this.currentJob.steps.length; stepIndex++) { | ||||||
|  |           if (!autoScrollStepIndexes.get(stepIndex)) continue; | ||||||
|  |           autoScrollJobStepElement = this.getJobStepLogsContainer(stepIndex); | ||||||
|  |         } | ||||||
|  |         autoScrollJobStepElement?.lastElementChild.scrollIntoView({behavior: 'smooth', block: 'nearest'}); | ||||||
|  |  | ||||||
|  |         // clear the interval timer if the job is done | ||||||
|         if (this.run.done && this.intervalID) { |         if (this.run.done && this.intervalID) { | ||||||
|           clearInterval(this.intervalID); |           clearInterval(this.intervalID); | ||||||
|           this.intervalID = null; |           this.intervalID = null; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user