mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-11-23 23:49:08 -06:00
Fixhancement: more log viewer improvements (#11426)
This commit is contained in:
@@ -145,6 +145,10 @@ HTMLCanvasElement.prototype.getContext = <
|
||||
typeof HTMLCanvasElement.prototype.getContext
|
||||
>jest.fn()
|
||||
|
||||
if (!HTMLElement.prototype.scrollTo) {
|
||||
HTMLElement.prototype.scrollTo = jest.fn()
|
||||
}
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: jest.fn(() =>
|
||||
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char: string) => {
|
||||
|
||||
@@ -41,21 +41,21 @@
|
||||
}
|
||||
</ul>
|
||||
|
||||
<div [ngbNavOutlet]="nav" class="mt-2"></div>
|
||||
|
||||
<cdk-virtual-scroll-viewport
|
||||
itemSize="20"
|
||||
class="bg-dark p-3 text-light font-monospace log-container"
|
||||
#logContainer>
|
||||
<div #logContainer class="bg-dark text-light font-monospace log-container p-3" (scroll)="onScroll()">
|
||||
@if (loading && !logFiles.length) {
|
||||
<div>
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
</div>
|
||||
} @else {
|
||||
<p *ngFor="let log of logs" class="m-0 p-0" [ngClass]="'log-entry-' + log.level">{{log.message}}</p>
|
||||
}
|
||||
<p *cdkVirtualFor="let log of logs"
|
||||
class="m-0 p-0"
|
||||
[ngClass]="'log-entry-' + log.level">
|
||||
{{log.message}}
|
||||
</p>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-secondary jump-to-bottom position-fixed bottom-0 end-0 m-5"
|
||||
[class.visible]="showJumpToBottom"
|
||||
(click)="scrollToBottom()"
|
||||
>
|
||||
↓ <span i18n>Jump to bottom</span>
|
||||
</button>
|
||||
|
||||
@@ -16,11 +16,21 @@
|
||||
}
|
||||
|
||||
.log-container {
|
||||
overflow-y: scroll;
|
||||
height: calc(100vh - 200px);
|
||||
top: 0;
|
||||
height: calc(100vh - 190px);
|
||||
overflow-y: auto;
|
||||
|
||||
p {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.jump-to-bottom {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 120ms ease-in-out;
|
||||
}
|
||||
|
||||
.jump-to-bottom.visible {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
@@ -110,4 +110,11 @@ describe('LogsComponent', () => {
|
||||
jest.advanceTimersByTime(1)
|
||||
expect(reloadSpy).toHaveBeenCalledTimes(initialCalls + 1)
|
||||
})
|
||||
|
||||
it('should update jump to bottom visibility on scroll', () => {
|
||||
component.showJumpToBottom = false
|
||||
jest.spyOn(component as any, 'isNearBottom').mockReturnValue(false)
|
||||
component.onScroll()
|
||||
expect(component.showJumpToBottom).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import {
|
||||
CdkVirtualScrollViewport,
|
||||
ScrollingModule,
|
||||
} from '@angular/cdk/scrolling'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
@@ -28,8 +25,6 @@ import { LoadingComponentWithPermissions } from '../../loading-component/loading
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
CdkVirtualScrollViewport,
|
||||
ScrollingModule,
|
||||
],
|
||||
})
|
||||
export class LogsComponent
|
||||
@@ -49,9 +44,11 @@ export class LogsComponent
|
||||
|
||||
public limit: number = 5000
|
||||
|
||||
public showJumpToBottom = false
|
||||
|
||||
private readonly limitChange$ = new Subject<number>()
|
||||
|
||||
@ViewChild('logContainer') logContainer: CdkVirtualScrollViewport
|
||||
@ViewChild('logContainer') logContainer: ElementRef<HTMLElement>
|
||||
|
||||
ngOnInit(): void {
|
||||
this.limitChange$
|
||||
@@ -89,6 +86,7 @@ export class LogsComponent
|
||||
|
||||
reloadLogs() {
|
||||
this.loading = true
|
||||
const shouldStickToBottom = this.isNearBottom()
|
||||
this.logService
|
||||
.get(this.activeLog, this.limit)
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
@@ -108,7 +106,10 @@ export class LogsComponent
|
||||
})
|
||||
if (hasChanges) {
|
||||
this.logs = parsed
|
||||
this.scrollToBottom()
|
||||
if (shouldStickToBottom) {
|
||||
this.scrollToBottom()
|
||||
}
|
||||
this.showJumpToBottom = !shouldStickToBottom
|
||||
}
|
||||
},
|
||||
error: () => {
|
||||
@@ -142,9 +143,25 @@ export class LogsComponent
|
||||
}
|
||||
|
||||
scrollToBottom(): void {
|
||||
this.changedetectorRef.detectChanges()
|
||||
if (this.logContainer) {
|
||||
this.logContainer.scrollToIndex(this.logs.length - 1)
|
||||
const viewport = this.logContainer?.nativeElement
|
||||
if (!viewport) {
|
||||
return
|
||||
}
|
||||
this.changedetectorRef.detectChanges()
|
||||
viewport.scrollTop = viewport.scrollHeight
|
||||
this.showJumpToBottom = false
|
||||
}
|
||||
|
||||
private isNearBottom(): boolean {
|
||||
if (!this.logContainer?.nativeElement) return true
|
||||
const distanceFromBottom =
|
||||
this.logContainer.nativeElement.scrollHeight -
|
||||
this.logContainer.nativeElement.scrollTop -
|
||||
this.logContainer.nativeElement.clientHeight
|
||||
return distanceFromBottom <= 40
|
||||
}
|
||||
|
||||
onScroll(): void {
|
||||
this.showJumpToBottom = !this.isNearBottom()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user