diff --git a/pw_web/log-viewer/src/components/log-list/log-list.styles.ts b/pw_web/log-viewer/src/components/log-list/log-list.styles.ts index 8b6fee61f4..bc45c0de53 100644 --- a/pw_web/log-viewer/src/components/log-list/log-list.styles.ts +++ b/pw_web/log-viewer/src/components/log-list/log-list.styles.ts @@ -26,13 +26,14 @@ export const styles = css` font-family: 'Roboto Mono', monospace; font-size: 1rem; height: 100%; + overflow: hidden; position: relative; } .table-container { + display: grid; height: 100%; - overflow: auto; - padding-bottom: 3rem; + overflow: scroll; scroll-behavior: auto; width: 100%; } @@ -118,7 +119,8 @@ export const styles = css` } th[hidden], - td[hidden] { + td[hidden], + .jump-to-bottom-btn[hidden] { display: none; } @@ -134,6 +136,15 @@ export const styles = css` align-items: flex-start; } + .jump-to-bottom-btn { + --md-filled-button-container-elevation: 4; + --md-filled-button-hover-container-elevation: 4; + bottom: 2rem; + left: 50%; + position: absolute; + transform: translate(-50%); + } + .resize-handle { background-color: var(--sys-log-viewer-color-table-cell-outline); bottom: 0; @@ -186,17 +197,20 @@ export const styles = css` height: 100%; pointer-events: none; position: absolute; - top: 0; width: 8rem; } - .right-indicator { + .bottom-indicator { + align-self: flex-end; background: linear-gradient( - to right, + to bottom, transparent, var(--sys-log-viewer-color-overflow-indicator) ); - right: 0; + height: 8rem; + pointer-events: none; + position: absolute; + width: 100%; } .left-indicator { @@ -205,7 +219,16 @@ export const styles = css` transparent, var(--sys-log-viewer-color-overflow-indicator) ); - left: 0; + justify-self: flex-start; + } + + .right-indicator { + background: linear-gradient( + to right, + transparent, + var(--sys-log-viewer-color-overflow-indicator) + ); + justify-self: flex-end; } mark { @@ -214,4 +237,40 @@ export const styles = css` color: var(--md-sys-color-on-primary-container); outline: 1px solid var(--sys-log-viewer-color-table-mark); } + + ::-webkit-scrollbar { + -webkit-appearance: auto; + } + + ::-webkit-scrollbar-corner { + background: var(--md-sys-color-surface-container-low); + } + + ::-webkit-scrollbar-thumb { + min-height: 3rem; + } + + ::-webkit-scrollbar-thumb:horizontal { + border-radius: 20px; + box-shadow: inset 0 0 2rem 2rem var(--md-sys-color-outline-variant); + border: inset 3px transparent; + border-top: inset 4px transparent; + } + + ::-webkit-scrollbar-thumb:vertical { + border-radius: 20px; + box-shadow: inset 0 0 2rem 2rem var(--md-sys-color-outline-variant); + border: inset 3px transparent; + border-left: inset 4px transparent; + } + + ::-webkit-scrollbar-track:horizontal { + box-shadow: inset 0 0 2rem 2rem var(--md-sys-color-surface-container-low); + border-top: solid 1px var(--md-sys-color-outline-variant); + } + + ::-webkit-scrollbar-track:vertical { + box-shadow: inset 0 0 2rem 2rem var(--md-sys-color-surface-container-low); + border-left: solid 1px var(--md-sys-color-outline-variant); + } `; diff --git a/pw_web/log-viewer/src/components/log-list/log-list.ts b/pw_web/log-viewer/src/components/log-list/log-list.ts index 2f0343ff1d..89d0a7fa41 100644 --- a/pw_web/log-viewer/src/components/log-list/log-list.ts +++ b/pw_web/log-viewer/src/components/log-list/log-list.ts @@ -60,10 +60,14 @@ export class LogList extends LitElement { @state() private _isOverflowingToRight = false; - /** A number reprecenting the scroll percentage in the horizontal direction. */ + /** A number representing the scroll percentage in the horizontal direction. */ @state() private _scrollPercentageLeft = 0; + /** A number representing visibility of vertical scroll indicator. */ + @state() + private _scrollDownOpacity = 0; + /** * Indicates whether to automatically scroll the table container to the * bottom when new log entries are added. @@ -71,6 +75,7 @@ export class LogList extends LitElement { @state() private _autoscrollIsEnabled = true; + @query('.jump-to-bottom-btn') private _jumpBottomBtn!: HTMLButtonElement; @query('.table-container') private _tableContainer!: HTMLDivElement; @query('table') private _table!: HTMLTableElement; @query('tbody') private _tableBody!: HTMLTableSectionElement; @@ -115,7 +120,7 @@ export class LogList extends LitElement { if (changedProperties.has('logs')) { this.setFieldNames(this.logs); - this.scrollTableToBottom(); + this.handleTableScroll(); } if (changedProperties.has('colsHidden')) { @@ -151,19 +156,22 @@ export class LogList extends LitElement { /** Called when the Lit virtualizer updates its range of entries. */ private onRangeChanged = () => { this.updateGridTemplateColumns(); - this.scrollTableToBottom(); + if (this._autoscrollIsEnabled) { + this.scrollTableToBottom(); + } }; - /** Scrolls to the bottom of the table container if autoscroll is enabled. */ + /** Scrolls to the bottom of the table container. */ private scrollTableToBottom() { - if (this._autoscrollIsEnabled) { - const container = this._tableContainer; + const container = this._tableContainer; - // TODO(b/289101398): Refactor `setTimeout` usage - setTimeout(() => { - container.scrollTop = container.scrollHeight; - }, 0); // Complete any rendering tasks before scrolling - } + // TODO(b/289101398): Refactor `setTimeout` usage + setTimeout(() => { + container.scrollTop = container.scrollHeight; + this._autoscrollIsEnabled = true; + this._jumpBottomBtn.hidden = true; + this._scrollDownOpacity = 0; + }, 0); // Complete any rendering tasks before scrolling } /** Clears the `gridTemplateColumns` value for all rows in the table. */ @@ -275,13 +283,15 @@ export class LogList extends LitElement { this._scrollPercentageLeft = scrollLeft / maxScrollLeft || 0; if ( - container.scrollHeight - container.scrollTop <= - container.offsetHeight + Math.floor(container.scrollHeight - container.scrollTop) <= + Math.floor(container.offsetHeight) ) { - this._autoscrollIsEnabled = true; + this.scrollTableToBottom(); return; } this._autoscrollIsEnabled = false; + this._jumpBottomBtn.hidden = false; + this._scrollDownOpacity = 1; this.requestUpdate(); }; @@ -343,7 +353,7 @@ export class LogList extends LitElement { for (let i = 0; i < totalColumns; i++) { if (i === columnIndex) { gridTemplateColumns += `${newWidth}px `; - return; + continue; } const otherColumnHeader = this._table.querySelector( `th:nth-child(${i + 1})`, @@ -378,9 +388,17 @@ export class LogList extends LitElement { })} - ${this.overflowIndicators()} + + + Jump to Bottom + `; } @@ -482,6 +500,11 @@ export class LogList extends LitElement { } private overflowIndicators = () => html` +
+
this.id === i.viewID); + switch (event.type) { case 'input-change': if (this._debounceTimeout) { clearTimeout(this._debounceTimeout); } - this.searchText = event.detail.inputValue; + if (index !== -1) { + viewConfigArr[index].search = this.searchText; + this._state = { logViewConfig: viewConfigArr }; + this._stateStore.setState({ logViewConfig: viewConfigArr }); + } if (!this.searchText) { this._stringFilter = () => true; @@ -171,14 +179,6 @@ export class LogView extends LitElement { break; } - const viewConfigArr = this._state.logViewConfig; - const index = viewConfigArr.findIndex((i) => this.id === i.viewID); - if (index !== -1) { - viewConfigArr[index].search = this.searchText; - this._state = { logViewConfig: viewConfigArr }; - this._stateStore.setState({ logViewConfig: viewConfigArr }); - } - this.filterLogs(); this.requestUpdate(); }