This commit is contained in:
wxiaoguang 2024-12-21 10:19:49 +08:00
parent a0ca89e72c
commit 5d0ecccbdd
2 changed files with 40 additions and 18 deletions

View File

@ -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,

View File

@ -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,14 +227,15 @@ const sfc = {
); );
}, },
appendLogs(stepIndex: number, startTime: number, logLines: LogLine[]) { shouldAutoScroll(stepIndex: number): boolean {
// position of the client view relative to the website top const el = this.getJobStepLogsContainer(stepIndex);
const clientHeight = document.documentElement.clientHeight + window.scrollY; if (!el.lastChild) return false;
// height of the logs container relative to the website top return isLogElementInViewport(el.lastChild);
const logsContainerHeight = this.$refs.stepsContainer.getBoundingClientRect().bottom + window.scrollY; },
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);
@ -235,12 +246,6 @@ const sfc = {
} }
el.append(this.createLogLine(stepIndex, startTime, line)); el.append(this.createLogLine(stepIndex, startTime, line));
} }
// scrolls to the bottom if job is running and the bottom of the logs container is visible
if (!this.run.done && logLines.length && clientHeight >= logsContainerHeight) {
const newLogsContainerHeight = this.$refs.stepsContainer.getBoundingClientRect().bottom + window.scrollY;
window.scrollTo({top: clientHeight + (newLogsContainerHeight - logsContainerHeight), behavior: 'smooth'});
}
}, },
async deleteArtifact(name: string) { async deleteArtifact(name: string) {
@ -289,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
@ -296,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;
@ -478,7 +500,7 @@ export function initRepositoryActionView() {
</div> </div>
</div> </div>
<div class="action-view-right" ref="stepsContainer"> <div class="action-view-right">
<div class="job-info-header"> <div class="job-info-header">
<div class="job-info-header-left gt-ellipsis"> <div class="job-info-header-left gt-ellipsis">
<h3 class="job-info-header-title gt-ellipsis"> <h3 class="job-info-header-title gt-ellipsis">