mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-07-08 21:15:09 +00:00
Ugh, lets make these direct signals
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
|
||||
<li ngbDropdown class="nav-item mx-1" (openChange)="onOpenChange($event)">
|
||||
@if (toasts.length) {
|
||||
<span class="badge rounded-pill z-3 pe-none bg-secondary me-2 position-absolute top-0 left-0">{{ toasts.length }}</span>
|
||||
@if (toasts().length) {
|
||||
<span class="badge rounded-pill z-3 pe-none bg-secondary me-2 position-absolute top-0 left-0">{{ toasts().length }}</span>
|
||||
}
|
||||
<button class="btn border-0" id="notificationsDropdown" ngbDropdownToggle>
|
||||
<i-bs width="1.3em" height="1.3em" name="bell"></i-bs>
|
||||
@@ -12,15 +12,15 @@
|
||||
<div class="btn-group ms-auto">
|
||||
<button class="btn btn-sm btn-outline-secondary mb-2 ms-auto"
|
||||
(click)="toastService.clearToasts()"
|
||||
[disabled]="toasts.length === 0"
|
||||
[disabled]="toasts().length === 0"
|
||||
i18n>Clear All</button>
|
||||
</div>
|
||||
</div>
|
||||
@if (toasts.length === 0) {
|
||||
@if (toasts().length === 0) {
|
||||
<p class="text-center mb-0 small text-muted"><em i18n>No notifications</em></p>
|
||||
}
|
||||
<div class="scroll-list">
|
||||
@for (toast of toasts; track toast.id) {
|
||||
@for (toast of toasts(); track toast.id) {
|
||||
<pngx-toast [autohide]="false" [toast]="toast" (hidden)="onHidden(toast)" (closed)="toastService.closeToast(toast)"></pngx-toast>
|
||||
}
|
||||
</div>
|
||||
|
||||
+9
-14
@@ -42,7 +42,8 @@ describe('ToastsDropdownComponent', () => {
|
||||
let component: ToastsDropdownComponent
|
||||
let fixture: ComponentFixture<ToastsDropdownComponent>
|
||||
let toastService: ToastService
|
||||
let toastsSubject: Subject<Toast[]> = new Subject()
|
||||
let toastsSubject: Subject<Toast[]>
|
||||
let getToastsSpy: jest.SpyInstance
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -56,48 +57,43 @@ describe('ToastsDropdownComponent', () => {
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(ToastsDropdownComponent)
|
||||
toastService = TestBed.inject(ToastService)
|
||||
jest.spyOn(toastService, 'getToasts').mockReturnValue(toastsSubject)
|
||||
toastsSubject = new Subject()
|
||||
getToastsSpy = jest
|
||||
.spyOn(toastService, 'getToasts')
|
||||
.mockReturnValue(toastsSubject)
|
||||
|
||||
fixture = TestBed.createComponent(ToastsDropdownComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should call getToasts and return toasts', fakeAsync(() => {
|
||||
const spy = jest.spyOn(toastService, 'getToasts')
|
||||
|
||||
component.ngOnInit()
|
||||
toastsSubject.next(toasts)
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(spy).toHaveBeenCalled()
|
||||
expect(component.toasts).toContainEqual({
|
||||
expect(getToastsSpy).toHaveBeenCalled()
|
||||
expect(component.toasts()).toContainEqual({
|
||||
id: 'abc-123',
|
||||
content: 'foo bar',
|
||||
delay: 5000,
|
||||
})
|
||||
|
||||
component.ngOnDestroy()
|
||||
flush()
|
||||
discardPeriodicTasks()
|
||||
}))
|
||||
|
||||
it('should show a toast', fakeAsync(() => {
|
||||
component.ngOnInit()
|
||||
toastsSubject.next(toasts)
|
||||
fixture.detectChanges()
|
||||
|
||||
expect(fixture.nativeElement.textContent).toContain('foo bar')
|
||||
|
||||
component.ngOnDestroy()
|
||||
flush()
|
||||
discardPeriodicTasks()
|
||||
}))
|
||||
|
||||
it('should toggle suppressPopupToasts', fakeAsync((finish) => {
|
||||
component.ngOnInit()
|
||||
fixture.detectChanges()
|
||||
toastsSubject.next(toasts)
|
||||
|
||||
@@ -105,7 +101,6 @@ describe('ToastsDropdownComponent', () => {
|
||||
component.onOpenChange(true)
|
||||
expect(spy).toHaveBeenCalledWith(true)
|
||||
|
||||
component.ngOnDestroy()
|
||||
flush()
|
||||
discardPeriodicTasks()
|
||||
}))
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core'
|
||||
import { Component, inject } from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import {
|
||||
NgbDropdownModule,
|
||||
NgbProgressbarModule,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { map } from 'rxjs'
|
||||
import { Toast, ToastService } from 'src/app/services/toast.service'
|
||||
import { ToastComponent } from '../../common/toast/toast.component'
|
||||
|
||||
@@ -19,30 +20,13 @@ import { ToastComponent } from '../../common/toast/toast.component'
|
||||
NgxBootstrapIconsModule,
|
||||
],
|
||||
})
|
||||
export class ToastsDropdownComponent implements OnInit, OnDestroy {
|
||||
export class ToastsDropdownComponent {
|
||||
toastService = inject(ToastService)
|
||||
|
||||
private subscription: Subscription
|
||||
|
||||
private toastsSignal = signal<Toast[]>([])
|
||||
|
||||
public get toasts(): Toast[] {
|
||||
return this.toastsSignal()
|
||||
}
|
||||
|
||||
public set toasts(toasts: Toast[]) {
|
||||
this.toastsSignal.set(toasts)
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscription?.unsubscribe()
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.subscription = this.toastService.getToasts().subscribe((toasts) => {
|
||||
this.toasts = [...toasts]
|
||||
})
|
||||
}
|
||||
readonly toasts = toSignal(
|
||||
this.toastService.getToasts().pipe(map((toasts) => [...toasts])),
|
||||
{ initialValue: [] as Toast[] }
|
||||
)
|
||||
|
||||
onOpenChange(open: boolean): void {
|
||||
this.toastService.suppressPopupToasts = open
|
||||
|
||||
@@ -34,10 +34,10 @@
|
||||
<div class="row">
|
||||
<div class="col offset-sm-3">
|
||||
<button class="btn btn-sm btn-outline-secondary" (click)="copyError(toast.error)">
|
||||
@if (!copied) {
|
||||
@if (!copied()) {
|
||||
<i-bs name="clipboard" class="me-1"></i-bs>
|
||||
}
|
||||
@if (copied) {
|
||||
@if (copied()) {
|
||||
<i-bs name="clipboard-check" class="me-1"></i-bs>
|
||||
}
|
||||
<ng-container i18n>Copy Raw Error</ng-container>
|
||||
|
||||
@@ -29,7 +29,7 @@ import { Toast } from 'src/app/services/toast.service'
|
||||
})
|
||||
export class ToastComponent {
|
||||
private clipboard = inject(Clipboard)
|
||||
private copiedSignal = signal(false)
|
||||
readonly copied = signal(false)
|
||||
|
||||
@Input() toast: Toast
|
||||
|
||||
@@ -39,14 +39,6 @@ export class ToastComponent {
|
||||
|
||||
@Output() closed: EventEmitter<Toast> = new EventEmitter<Toast>()
|
||||
|
||||
public get copied(): boolean {
|
||||
return this.copiedSignal()
|
||||
}
|
||||
|
||||
public set copied(copied: boolean) {
|
||||
this.copiedSignal.set(copied)
|
||||
}
|
||||
|
||||
onShown(toast: Toast) {
|
||||
if (!this.autohide) return
|
||||
|
||||
@@ -76,9 +68,9 @@ export class ToastComponent {
|
||||
|
||||
public copyError(error: any) {
|
||||
this.clipboard.copy(JSON.stringify(error))
|
||||
this.copied = true
|
||||
this.copied.set(true)
|
||||
setTimeout(() => {
|
||||
this.copied = false
|
||||
this.copied.set(false)
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
@for (toast of toasts; track toast.id) {
|
||||
@for (toast of toasts(); track toast.id) {
|
||||
<pngx-toast [toast]="toast" [autohide]="true" (closed)="closeToast()"></pngx-toast>
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('ToastsComponent', () => {
|
||||
let component: ToastsComponent
|
||||
let fixture: ComponentFixture<ToastsComponent>
|
||||
let toastService: ToastService
|
||||
let toastSubject: Subject<Toast> = new Subject()
|
||||
let toastSubject: Subject<Toast>
|
||||
|
||||
beforeEach(async () => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -33,10 +33,11 @@ describe('ToastsComponent', () => {
|
||||
],
|
||||
}).compileComponents()
|
||||
|
||||
fixture = TestBed.createComponent(ToastsComponent)
|
||||
toastService = TestBed.inject(ToastService)
|
||||
toastSubject = new Subject()
|
||||
jest.replaceProperty(toastService, 'showToast', toastSubject)
|
||||
|
||||
fixture = TestBed.createComponent(ToastsComponent)
|
||||
component = fixture.componentInstance
|
||||
|
||||
fixture.detectChanges()
|
||||
@@ -47,25 +48,15 @@ describe('ToastsComponent', () => {
|
||||
})
|
||||
|
||||
it('should close toast', () => {
|
||||
component.toasts = [toast]
|
||||
toastSubject.next(toast)
|
||||
const closeToastSpy = jest.spyOn(toastService, 'closeToast')
|
||||
component.closeToast()
|
||||
expect(component.toasts).toEqual([])
|
||||
expect(component.toasts()).toEqual([])
|
||||
expect(closeToastSpy).toHaveBeenCalledWith(toast)
|
||||
})
|
||||
|
||||
it('should unsubscribe', () => {
|
||||
const unsubscribeSpy = jest.spyOn(
|
||||
(component as any).subscription,
|
||||
'unsubscribe'
|
||||
)
|
||||
component.ngOnDestroy()
|
||||
expect(unsubscribeSpy).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should subscribe to toastService', () => {
|
||||
component.ngOnInit()
|
||||
it('should update from toastService', () => {
|
||||
toastSubject.next(toast)
|
||||
expect(component.toasts).toEqual([toast])
|
||||
expect(component.toasts()).toEqual([toast])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Component, OnDestroy, OnInit, inject, signal } from '@angular/core'
|
||||
import { Component, inject } from '@angular/core'
|
||||
import { toSignal } from '@angular/core/rxjs-interop'
|
||||
import {
|
||||
NgbAccordionModule,
|
||||
NgbProgressbarModule,
|
||||
} from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { map, merge, Subject } from 'rxjs'
|
||||
import { Toast, ToastService } from 'src/app/services/toast.service'
|
||||
import { ToastComponent } from '../toast/toast.component'
|
||||
|
||||
@@ -19,33 +20,21 @@ import { ToastComponent } from '../toast/toast.component'
|
||||
NgxBootstrapIconsModule,
|
||||
],
|
||||
})
|
||||
export class ToastsComponent implements OnInit, OnDestroy {
|
||||
export class ToastsComponent {
|
||||
toastService = inject(ToastService)
|
||||
|
||||
private subscription: Subscription
|
||||
private readonly closedToast = new Subject<void>()
|
||||
|
||||
private toastsSignal = signal<Toast[]>([])
|
||||
|
||||
public get toasts(): Toast[] {
|
||||
return this.toastsSignal()
|
||||
}
|
||||
|
||||
public set toasts(toasts: Toast[]) {
|
||||
this.toastsSignal.set(toasts)
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscription?.unsubscribe()
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.subscription = this.toastService.showToast.subscribe((toast) => {
|
||||
this.toasts = toast ? [toast] : []
|
||||
})
|
||||
}
|
||||
readonly toasts = toSignal(
|
||||
merge(
|
||||
this.toastService.showToast.pipe(map((toast) => (toast ? [toast] : []))),
|
||||
this.closedToast.pipe(map(() => [] as Toast[]))
|
||||
),
|
||||
{ initialValue: [] as Toast[] }
|
||||
)
|
||||
|
||||
closeToast() {
|
||||
this.toastService.closeToast(this.toasts[0])
|
||||
this.toasts = []
|
||||
this.toastService.closeToast(this.toasts()[0])
|
||||
this.closedToast.next()
|
||||
}
|
||||
}
|
||||
|
||||
+23
-23
@@ -1,7 +1,7 @@
|
||||
<pngx-widget-frame title="Statistics" [loading]="loading" i18n-title>
|
||||
<pngx-widget-frame title="Statistics" [loading]="loading()" i18n-title>
|
||||
<ng-container content>
|
||||
<div class="list-group border-light placeholder-glow">
|
||||
@if (loading) {
|
||||
@if (loading()) {
|
||||
<div class="list-group-item d-flex">
|
||||
<div class="placeholder w-50"></div>
|
||||
<span class="placeholder badge rounded-pill ms-auto" style="width: 25px;"> </span>
|
||||
@@ -24,32 +24,32 @@
|
||||
<div class="placeholder w-100 d-block"></div>
|
||||
</div>
|
||||
} @else {
|
||||
@if (statistics?.documents_inbox !== null) {
|
||||
@if (statistics()?.documents_inbox !== null) {
|
||||
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" title="Go to inbox" i18n-title href="javascript:void(0)" (click)="goToInbox()">
|
||||
<ng-container i18n>Documents in inbox</ng-container>:
|
||||
<span class="badge rounded-pill" [class.bg-primary]="statistics?.documents_inbox > 0" [class.bg-muted]="statistics?.documents_inbox === 0">{{statistics?.documents_inbox}}</span>
|
||||
<span class="badge rounded-pill" [class.bg-primary]="statistics()?.documents_inbox > 0" [class.bg-muted]="statistics()?.documents_inbox === 0">{{statistics()?.documents_inbox}}</span>
|
||||
</a>
|
||||
}
|
||||
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" title="Go to documents" i18n-title routerLink="/documents/">
|
||||
<ng-container i18n>Total documents</ng-container>:
|
||||
<span class="badge bg-primary rounded-pill">{{statistics?.documents_total}}</span>
|
||||
<span class="badge bg-primary rounded-pill">{{statistics()?.documents_total}}</span>
|
||||
</a>
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center" routerLink="/documents/">
|
||||
<ng-container i18n>Total characters</ng-container>:
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.character_count | number}}</span>
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics()?.character_count | number}}</span>
|
||||
</div>
|
||||
@if (statistics?.current_asn) {
|
||||
@if (statistics()?.current_asn) {
|
||||
<div class="list-group-item d-flex justify-content-between align-items-center" routerLink="/documents/">
|
||||
<ng-container i18n>Current ASN</ng-container>:
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.current_asn}}</span>
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics()?.current_asn}}</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if (statistics?.document_file_type_counts?.length > 1) {
|
||||
@if (statistics()?.document_file_type_counts?.length > 1) {
|
||||
<div class="list-group-item filetypes">
|
||||
<div class="d-flex justify-content-between align-items-center my-2">
|
||||
<div class="progress flex-grow-1">
|
||||
@for (filetype of statistics?.document_file_type_counts; track filetype; let i = $index; let last = $last) {
|
||||
@for (filetype of statistics()?.document_file_type_counts; track filetype; let i = $index; let last = $last) {
|
||||
<div
|
||||
class="progress-bar bg-primary"
|
||||
role="progressbar"
|
||||
@@ -69,7 +69,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap align-items-start">
|
||||
@for (filetype of statistics?.document_file_type_counts; track filetype; let i = $index) {
|
||||
@for (filetype of statistics()?.document_file_type_counts; track filetype; let i = $index) {
|
||||
<div class="d-flex">
|
||||
<div class="text-nowrap me-2" [class.cursor-pointer]="!filetype.is_other" (click)="filterByFileType(filetype)">
|
||||
<span class="badge rounded-pill bg-primary d-inline-block p-0 me-1" [style.opacity]="getItemOpacity(i)"></span>
|
||||
@@ -84,54 +84,54 @@
|
||||
|
||||
<div class="list-group border-light mt-3">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }">
|
||||
@if (loading) {
|
||||
@if (loading()) {
|
||||
<div class="placeholder-glow list-group-item">
|
||||
<span class="placeholder w-100"></span>
|
||||
</div>
|
||||
}
|
||||
@if (statistics?.tag_count > 0) {
|
||||
@if (statistics()?.tag_count > 0) {
|
||||
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/tags/">
|
||||
<ng-container i18n>Tags</ng-container>:
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.tag_count | number}}</span>
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics()?.tag_count | number}}</span>
|
||||
</a>
|
||||
}
|
||||
</ng-container>
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }">
|
||||
@if (loading) {
|
||||
@if (loading()) {
|
||||
<div class="placeholder-glow list-group-item">
|
||||
<span class="placeholder w-100"></span>
|
||||
</div>
|
||||
}
|
||||
@if (statistics?.correspondent_count > 0) {
|
||||
@if (statistics()?.correspondent_count > 0) {
|
||||
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/correspondents/">
|
||||
<ng-container i18n>Correspondents</ng-container>:
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.correspondent_count | number}}</span>
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics()?.correspondent_count | number}}</span>
|
||||
</a>
|
||||
}
|
||||
</ng-container>
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
|
||||
@if (loading) {
|
||||
@if (loading()) {
|
||||
<div class="placeholder-glow list-group-item">
|
||||
<span class="placeholder w-100"></span>
|
||||
</div>
|
||||
}
|
||||
@if (statistics?.document_type_count > 0) {
|
||||
@if (statistics()?.document_type_count > 0) {
|
||||
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/documenttypes/">
|
||||
<ng-container i18n>Document Types</ng-container>:
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.document_type_count | number}}</span>
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics()?.document_type_count | number}}</span>
|
||||
</a>
|
||||
}
|
||||
</ng-container>
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
|
||||
@if (loading) {
|
||||
@if (loading()) {
|
||||
<div class="placeholder-glow list-group-item">
|
||||
<span class="placeholder w-100"></span>
|
||||
</div>
|
||||
}
|
||||
@if (statistics?.storage_path_count > 0) {
|
||||
@if (statistics()?.storage_path_count > 0) {
|
||||
<a class="list-group-item d-flex justify-content-between align-items-center" routerLink="/storagepaths/">
|
||||
<ng-container i18n>Storage Paths</ng-container>:
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics?.storage_path_count | number}}</span>
|
||||
<span class="badge bg-secondary text-light rounded-pill">{{statistics()?.storage_path_count | number}}</span>
|
||||
</a>
|
||||
}
|
||||
</ng-container>
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ describe('StatisticsWidgetComponent', () => {
|
||||
|
||||
it('should not call statistics endpoint on reload if already loading', () => {
|
||||
httpTestingController.expectOne(`${environment.apiBaseUrl}statistics/`)
|
||||
component.loading = true
|
||||
component.loading.set(true)
|
||||
component.reload()
|
||||
httpTestingController.expectNone(`${environment.apiBaseUrl}statistics/`)
|
||||
})
|
||||
|
||||
+10
-26
@@ -55,36 +55,20 @@ export class StatisticsWidgetComponent
|
||||
private websocketConnectionService = inject(WebsocketStatusService)
|
||||
private documentListViewService = inject(DocumentListViewService)
|
||||
|
||||
private loadingSignal = signal(false)
|
||||
private statisticsSignal = signal<Statistics>({})
|
||||
|
||||
get loading(): boolean {
|
||||
return this.loadingSignal()
|
||||
}
|
||||
|
||||
set loading(value: boolean) {
|
||||
this.loadingSignal.set(value)
|
||||
}
|
||||
|
||||
get statistics(): Statistics {
|
||||
return this.statisticsSignal()
|
||||
}
|
||||
|
||||
set statistics(value: Statistics) {
|
||||
this.statisticsSignal.set(value)
|
||||
}
|
||||
readonly loading = signal(false)
|
||||
readonly statistics = signal<Statistics>({})
|
||||
|
||||
subscription: Subscription
|
||||
private unsubscribeNotifer: Subject<any> = new Subject()
|
||||
|
||||
reload() {
|
||||
if (this.loading) return
|
||||
this.loading = true
|
||||
if (this.loading()) return
|
||||
this.loading.set(true)
|
||||
this.http
|
||||
.get<Statistics>(`${environment.apiBaseUrl}statistics/`)
|
||||
.pipe(takeUntil(this.unsubscribeNotifer), first())
|
||||
.subscribe((statistics) => {
|
||||
this.loading = false
|
||||
this.loading.set(false)
|
||||
const fileTypeMax = 5
|
||||
if (statistics.document_file_type_counts?.length > fileTypeMax) {
|
||||
const others = statistics.document_file_type_counts.slice(fileTypeMax)
|
||||
@@ -100,7 +84,7 @@ export class StatisticsWidgetComponent
|
||||
),
|
||||
})
|
||||
}
|
||||
this.statistics = statistics
|
||||
this.statistics.set(statistics)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -116,11 +100,11 @@ export class StatisticsWidgetComponent
|
||||
}
|
||||
|
||||
getFileTypePercent(filetype: DocumentFileType): number {
|
||||
return (filetype.mime_type_count / this.statistics?.documents_total) * 100
|
||||
return (filetype.mime_type_count / this.statistics()?.documents_total) * 100
|
||||
}
|
||||
|
||||
getItemOpacity(i: number): number {
|
||||
return 1 - i / this.statistics?.document_file_type_counts.length
|
||||
return 1 - i / this.statistics()?.document_file_type_counts.length
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -142,8 +126,8 @@ export class StatisticsWidgetComponent
|
||||
this.documentListViewService.quickFilter([
|
||||
{
|
||||
rule_type: FILTER_HAS_TAGS_ANY,
|
||||
value: this.statistics.inbox_tags
|
||||
.map((tagID) => tagID.toString())
|
||||
value: this.statistics()
|
||||
.inbox_tags.map((tagID) => tagID.toString())
|
||||
.join(','),
|
||||
},
|
||||
])
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
@if (!cardless()) {
|
||||
<div class="card shadow-sm bg-light fade" [class.show]="show" cdkDrag [cdkDragDisabled]="!draggable()" cdkDragPreviewContainer="parent">
|
||||
<div class="card shadow-sm bg-light fade" [class.show]="show()" cdkDrag [cdkDragDisabled]="!draggable()" cdkDragPreviewContainer="parent">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex align-items-center">
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="fade" [class.show]="show">
|
||||
<div class="fade" [class.show]="show()">
|
||||
<ng-container [ngTemplateOutlet]="content"></ng-container>
|
||||
</div>
|
||||
}
|
||||
|
||||
+1
-1
@@ -58,6 +58,6 @@ describe('WidgetFrameComponent', () => {
|
||||
})
|
||||
|
||||
it('should show', () => {
|
||||
expect(component.show).toBeTruthy()
|
||||
expect(component.show()).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -10,18 +10,10 @@ import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||
imports: [DragDropModule, NgxBootstrapIconsModule, NgTemplateOutlet],
|
||||
})
|
||||
export class WidgetFrameComponent implements AfterViewInit {
|
||||
private showSignal = signal(false)
|
||||
readonly show = signal(false)
|
||||
|
||||
loading = input(false)
|
||||
|
||||
get show(): boolean {
|
||||
return this.showSignal()
|
||||
}
|
||||
|
||||
set show(value: boolean) {
|
||||
this.showSignal.set(value)
|
||||
}
|
||||
|
||||
title = input<string>()
|
||||
|
||||
draggable = input<any>()
|
||||
@@ -31,6 +23,6 @@ export class WidgetFrameComponent implements AfterViewInit {
|
||||
badge = input<string | number>(null)
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.show = true
|
||||
this.show.set(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('FileDropComponent', () => {
|
||||
component.onDragOver(new Event('dragover') as DragEvent)
|
||||
tick(1)
|
||||
fixture.detectChanges()
|
||||
expect(component.fileIsOver).toBeFalsy()
|
||||
expect(component.fileIsOver()).toBeFalsy()
|
||||
const dropzone = fixture.debugElement.query(
|
||||
By.css('.global-dropzone-overlay')
|
||||
)
|
||||
@@ -85,13 +85,13 @@ describe('FileDropComponent', () => {
|
||||
|
||||
it('should support drag drop, initiate upload', fakeAsync(() => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
expect(component.fileIsOver).toBeFalsy()
|
||||
expect(component.fileIsOver()).toBeFalsy()
|
||||
const overEvent = new Event('dragover') as DragEvent
|
||||
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
||||
component.onDragOver(overEvent)
|
||||
tick(1)
|
||||
fixture.detectChanges()
|
||||
expect(component.fileIsOver).toBeTruthy()
|
||||
expect(component.fileIsOver()).toBeTruthy()
|
||||
component.onDragLeave(new Event('dragleave') as DragEvent)
|
||||
tick(700)
|
||||
fixture.detectChanges()
|
||||
@@ -121,13 +121,13 @@ describe('FileDropComponent', () => {
|
||||
|
||||
it('should support drag drop, initiate upload with webkitGetAsEntry', fakeAsync(() => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
expect(component.fileIsOver).toBeFalsy()
|
||||
expect(component.fileIsOver()).toBeFalsy()
|
||||
const overEvent = new Event('dragover') as DragEvent
|
||||
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
||||
component.onDragOver(overEvent)
|
||||
tick(1)
|
||||
fixture.detectChanges()
|
||||
expect(component.fileIsOver).toBeTruthy()
|
||||
expect(component.fileIsOver()).toBeTruthy()
|
||||
component.onDragLeave(new Event('dragleave') as DragEvent)
|
||||
tick(700)
|
||||
fixture.detectChanges()
|
||||
@@ -202,13 +202,13 @@ describe('FileDropComponent', () => {
|
||||
|
||||
it('should support drag drop, initiate upload without DataTransfer API support', fakeAsync(() => {
|
||||
jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
|
||||
expect(component.fileIsOver).toBeFalsy()
|
||||
expect(component.fileIsOver()).toBeFalsy()
|
||||
const overEvent = new Event('dragover') as DragEvent
|
||||
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
||||
component.onDragOver(overEvent)
|
||||
tick(1)
|
||||
fixture.detectChanges()
|
||||
expect(component.fileIsOver).toBeTruthy()
|
||||
expect(component.fileIsOver()).toBeTruthy()
|
||||
component.onDragLeave(new Event('dragleave') as DragEvent)
|
||||
tick(700)
|
||||
fixture.detectChanges()
|
||||
@@ -315,8 +315,8 @@ describe('FileDropComponent', () => {
|
||||
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
||||
component.onDragOver(overEvent)
|
||||
tick(1)
|
||||
expect(component.hidden).toBeFalsy()
|
||||
expect(component.fileIsOver).toBeTruthy()
|
||||
expect(component.hidden()).toBeFalsy()
|
||||
expect(component.fileIsOver()).toBeTruthy()
|
||||
jest.spyOn(document, 'hidden', 'get').mockReturnValue(true)
|
||||
component.onVisibilityChange()
|
||||
expect(leaveSpy).toHaveBeenCalled()
|
||||
@@ -331,8 +331,8 @@ describe('FileDropComponent', () => {
|
||||
;(overEvent as any).dataTransfer = { types: ['Files'] }
|
||||
component.onDragOver(overEvent)
|
||||
tick(1)
|
||||
expect(component.hidden).toBeFalsy()
|
||||
expect(component.fileIsOver).toBeTruthy()
|
||||
expect(component.hidden()).toBeFalsy()
|
||||
expect(component.fileIsOver()).toBeTruthy()
|
||||
jest.spyOn(document, 'hidden', 'get').mockReturnValue(true)
|
||||
component.onWindowBlur()
|
||||
expect(leaveSpy).toHaveBeenCalled()
|
||||
|
||||
Reference in New Issue
Block a user