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
|
typeof HTMLCanvasElement.prototype.getContext
|
||||||
>jest.fn()
|
>jest.fn()
|
||||||
|
|
||||||
|
if (!HTMLElement.prototype.scrollTo) {
|
||||||
|
HTMLElement.prototype.scrollTo = jest.fn()
|
||||||
|
}
|
||||||
|
|
||||||
jest.mock('uuid', () => ({
|
jest.mock('uuid', () => ({
|
||||||
v4: jest.fn(() =>
|
v4: jest.fn(() =>
|
||||||
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char: string) => {
|
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (char: string) => {
|
||||||
|
|||||||
@@ -41,21 +41,21 @@
|
|||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div [ngbNavOutlet]="nav" class="mt-2"></div>
|
<div #logContainer class="bg-dark text-light font-monospace log-container p-3" (scroll)="onScroll()">
|
||||||
|
|
||||||
<cdk-virtual-scroll-viewport
|
|
||||||
itemSize="20"
|
|
||||||
class="bg-dark p-3 text-light font-monospace log-container"
|
|
||||||
#logContainer>
|
|
||||||
@if (loading && !logFiles.length) {
|
@if (loading && !logFiles.length) {
|
||||||
<div>
|
<div>
|
||||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||||
<ng-container i18n>Loading...</ng-container>
|
<ng-container i18n>Loading...</ng-container>
|
||||||
</div>
|
</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"
|
</div>
|
||||||
class="m-0 p-0"
|
<button
|
||||||
[ngClass]="'log-entry-' + log.level">
|
type="button"
|
||||||
{{log.message}}
|
class="btn btn-sm btn-secondary jump-to-bottom position-fixed bottom-0 end-0 m-5"
|
||||||
</p>
|
[class.visible]="showJumpToBottom"
|
||||||
</cdk-virtual-scroll-viewport>
|
(click)="scrollToBottom()"
|
||||||
|
>
|
||||||
|
↓ <span i18n>Jump to bottom</span>
|
||||||
|
</button>
|
||||||
|
|||||||
@@ -16,11 +16,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.log-container {
|
.log-container {
|
||||||
overflow-y: scroll;
|
height: calc(100vh - 190px);
|
||||||
height: calc(100vh - 200px);
|
overflow-y: auto;
|
||||||
top: 0;
|
|
||||||
|
|
||||||
p {
|
p {
|
||||||
white-space: pre-wrap;
|
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)
|
jest.advanceTimersByTime(1)
|
||||||
expect(reloadSpy).toHaveBeenCalledTimes(initialCalls + 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 { CommonModule } from '@angular/common'
|
||||||
import {
|
import {
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
|
ElementRef,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
@@ -28,8 +25,6 @@ import { LoadingComponentWithPermissions } from '../../loading-component/loading
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
CdkVirtualScrollViewport,
|
|
||||||
ScrollingModule,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class LogsComponent
|
export class LogsComponent
|
||||||
@@ -49,9 +44,11 @@ export class LogsComponent
|
|||||||
|
|
||||||
public limit: number = 5000
|
public limit: number = 5000
|
||||||
|
|
||||||
|
public showJumpToBottom = false
|
||||||
|
|
||||||
private readonly limitChange$ = new Subject<number>()
|
private readonly limitChange$ = new Subject<number>()
|
||||||
|
|
||||||
@ViewChild('logContainer') logContainer: CdkVirtualScrollViewport
|
@ViewChild('logContainer') logContainer: ElementRef<HTMLElement>
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.limitChange$
|
this.limitChange$
|
||||||
@@ -89,6 +86,7 @@ export class LogsComponent
|
|||||||
|
|
||||||
reloadLogs() {
|
reloadLogs() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
const shouldStickToBottom = this.isNearBottom()
|
||||||
this.logService
|
this.logService
|
||||||
.get(this.activeLog, this.limit)
|
.get(this.activeLog, this.limit)
|
||||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
@@ -108,8 +106,11 @@ export class LogsComponent
|
|||||||
})
|
})
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
this.logs = parsed
|
this.logs = parsed
|
||||||
|
if (shouldStickToBottom) {
|
||||||
this.scrollToBottom()
|
this.scrollToBottom()
|
||||||
}
|
}
|
||||||
|
this.showJumpToBottom = !shouldStickToBottom
|
||||||
|
}
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
this.logs = []
|
this.logs = []
|
||||||
@@ -142,9 +143,25 @@ export class LogsComponent
|
|||||||
}
|
}
|
||||||
|
|
||||||
scrollToBottom(): void {
|
scrollToBottom(): void {
|
||||||
|
const viewport = this.logContainer?.nativeElement
|
||||||
|
if (!viewport) {
|
||||||
|
return
|
||||||
|
}
|
||||||
this.changedetectorRef.detectChanges()
|
this.changedetectorRef.detectChanges()
|
||||||
if (this.logContainer) {
|
viewport.scrollTop = viewport.scrollHeight
|
||||||
this.logContainer.scrollToIndex(this.logs.length - 1)
|
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