mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-08-10 00:18:57 +00:00
Enhancement: improved loading visuals (#8435)
This commit is contained in:
@@ -11,8 +11,19 @@
|
||||
>
|
||||
@if (savedViewService.loading) {
|
||||
<div class="col">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
<ng-container i18n>Loading...</ng-container>
|
||||
<div class="card shadow-sm bg-light">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex">
|
||||
<div class="ms-n2 me-1">
|
||||
<i-bs name="grip-vertical"></i-bs>
|
||||
</div>
|
||||
<h6 class="card-title mb-0" i18n></h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body"> </div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
@@ -9,8 +9,9 @@
|
||||
<a class="btn-link text-decoration-none" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a>
|
||||
}
|
||||
|
||||
@if (documents.length && displayMode === DisplayMode.TABLE) {
|
||||
<table content class="table table-hover mb-0 mt-n2 align-middle">
|
||||
<div content class="wrapper" [class.reveal]="reveal">
|
||||
@if (displayMode === DisplayMode.TABLE) {
|
||||
<table class="table table-hover mb-0 mt-n2 align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
@for (field of displayFields; track field; let i = $index) {
|
||||
@@ -28,53 +29,59 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (doc of documents; track doc.id) {
|
||||
@for (doc of documents; track doc.id; let i = $index) {
|
||||
<tr>
|
||||
@for (field of displayFields; track field; let i = $index) {
|
||||
<td class="py-2 py-md-3 position-relative" [ngClass]="{ 'd-none d-md-table-cell': i > 1 }">
|
||||
@switch (field) {
|
||||
@case (DisplayField.ADDED) {
|
||||
<a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3" title="Open document" i18n-title>{{doc.added | customDate}}</a>
|
||||
}
|
||||
@case (DisplayField.CREATED) {
|
||||
<a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3" title="Open document" i18n-title>{{doc.created_date | customDate}}</a>
|
||||
}
|
||||
@case (DisplayField.TITLE) {
|
||||
<a routerLink="/documents/{{doc.id}}" title="Open document" i18n-title class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.title | documentTitle}}</a>
|
||||
}
|
||||
@case (DisplayField.CORRESPONDENT) {
|
||||
@if (doc.correspondent) {
|
||||
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickCorrespondent(doc.correspondent, $event)" title="Filter by correspondent" i18n-title>{{(doc.correspondent$ | async)?.name}}</a>
|
||||
}
|
||||
}
|
||||
@case (DisplayField.TAGS) {
|
||||
@for (t of doc.tags$ | async; track t) {
|
||||
<pngx-tag [tag]="t" class="ms-1" (click)="clickTag(t.id, $event)" [clickable]="true" linkTitle="Filter by tag" i18n-title></pngx-tag>
|
||||
}
|
||||
}
|
||||
@case (DisplayField.DOCUMENT_TYPE) {
|
||||
@if (doc.document_type) {
|
||||
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickDocType(doc.document_type, $event)" title="Filter by document type" i18n-title>{{(doc.document_type$ | async)?.name}}</a>
|
||||
}
|
||||
}
|
||||
@case (DisplayField.STORAGE_PATH) {
|
||||
@if (doc.storage_path) {
|
||||
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickStoragePath(doc.storage_path, $event)" title="Filter by storage path" i18n-title>{{(doc.storage_path$ | async)?.name}}</a>
|
||||
}
|
||||
}
|
||||
}
|
||||
@if (field.startsWith(DisplayField.CUSTOM_FIELD)) {
|
||||
<pngx-custom-field-display [document]="doc" [fieldDisplayKey]="field"></pngx-custom-field-display>
|
||||
}
|
||||
@if (i === displayFields.length - 1) {
|
||||
<div class="btn-group position-absolute top-50 end-0 translate-middle-y" (mouseleave)="popupPreview.close()">
|
||||
<pngx-preview-popup [document]="doc" linkClasses="btn px-4 btn-dark border-dark-subtle" #popupPreview>
|
||||
<i-bs width="0.8em" height="0.8em" name="eye"></i-bs>
|
||||
</pngx-preview-popup>
|
||||
<a [href]="getDownloadUrl(doc)" class="btn px-4 btn-dark border-dark-subtle" title="Download" i18n-title (click)="$event.stopPropagation()">
|
||||
<i-bs width="0.8em" height="0.8em" name="download"></i-bs>
|
||||
</a>
|
||||
@for (field of displayFields; track field; let j = $index) {
|
||||
<td class="py-2 py-md-3 position-relative" [ngClass]="{ 'd-none d-md-table-cell': j > 1 }">
|
||||
@if (loading && reveal) {
|
||||
<div class="placeholder-glow text-start">
|
||||
<span class="placeholder bg-secondary w-50" [ngStyle]="{ opacity: 1 - (i * 1/documents.length) }"></span>
|
||||
</div>
|
||||
} @else {
|
||||
@switch (field) {
|
||||
@case (DisplayField.ADDED) {
|
||||
<a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3" title="Open document" i18n-title>{{doc.added | customDate}}</a>
|
||||
}
|
||||
@case (DisplayField.CREATED) {
|
||||
<a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none py-2 py-md-3" title="Open document" i18n-title>{{doc.created_date | customDate}}</a>
|
||||
}
|
||||
@case (DisplayField.TITLE) {
|
||||
<a routerLink="/documents/{{doc.id}}" title="Open document" i18n-title class="btn-link text-dark text-decoration-none py-2 py-md-3">{{doc.title | documentTitle}}</a>
|
||||
}
|
||||
@case (DisplayField.CORRESPONDENT) {
|
||||
@if (doc.correspondent) {
|
||||
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickCorrespondent(doc.correspondent, $event)" title="Filter by correspondent" i18n-title>{{(doc.correspondent$ | async)?.name}}</a>
|
||||
}
|
||||
}
|
||||
@case (DisplayField.TAGS) {
|
||||
@for (t of doc.tags$ | async; track t) {
|
||||
<pngx-tag [tag]="t" class="ms-1" (click)="clickTag(t.id, $event)" [clickable]="true" linkTitle="Filter by tag" i18n-title></pngx-tag>
|
||||
}
|
||||
}
|
||||
@case (DisplayField.DOCUMENT_TYPE) {
|
||||
@if (doc.document_type) {
|
||||
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickDocType(doc.document_type, $event)" title="Filter by document type" i18n-title>{{(doc.document_type$ | async)?.name}}</a>
|
||||
}
|
||||
}
|
||||
@case (DisplayField.STORAGE_PATH) {
|
||||
@if (doc.storage_path) {
|
||||
<a class="btn-link text-dark text-decoration-none" type="button" (click)="clickStoragePath(doc.storage_path, $event)" title="Filter by storage path" i18n-title>{{(doc.storage_path$ | async)?.name}}</a>
|
||||
}
|
||||
}
|
||||
}
|
||||
@if (field.startsWith(DisplayField.CUSTOM_FIELD)) {
|
||||
<pngx-custom-field-display [document]="doc" [fieldDisplayKey]="field"></pngx-custom-field-display>
|
||||
}
|
||||
@if (j === displayFields.length - 1) {
|
||||
<div class="btn-group position-absolute top-50 end-0 translate-middle-y" (mouseleave)="popupPreview.close()">
|
||||
<pngx-preview-popup [document]="doc" linkClasses="btn px-4 btn-dark border-dark-subtle" #popupPreview>
|
||||
<i-bs width="0.8em" height="0.8em" name="eye"></i-bs>
|
||||
</pngx-preview-popup>
|
||||
<a [href]="getDownloadUrl(doc)" class="btn px-4 btn-dark border-dark-subtle" title="Download" i18n-title (click)="$event.stopPropagation()">
|
||||
<i-bs width="0.8em" height="0.8em" name="download"></i-bs>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
}
|
||||
@@ -82,13 +89,14 @@
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
} @else if (documents.length && displayMode === DisplayMode.SMALL_CARDS) {
|
||||
<div content class="row row-cols-paperless-cards my-n2">
|
||||
@for (d of documents; track d.id) {
|
||||
} @else if (displayMode === DisplayMode.SMALL_CARDS) {
|
||||
<div class="row row-cols-paperless-cards my-n2">
|
||||
@for (d of documents; track d.id; let i = $index) {
|
||||
<pngx-document-card-small
|
||||
class="p-0"
|
||||
[ngStyle]="{ opacity: !loading && reveal ? 1 : 1 - (i * 1/documents.length) }"
|
||||
(dblClickDocument)="openDocumentDetail(d)"
|
||||
[document]="d"
|
||||
[document]="!loading && reveal ? d : null"
|
||||
[displayFields]="displayFields"
|
||||
(clickTag)="clickTag($event)"
|
||||
(clickCorrespondent)="clickCorrespondent($event)"
|
||||
@@ -97,12 +105,13 @@
|
||||
</pngx-document-card-small>
|
||||
}
|
||||
</div>
|
||||
} @else if (documents.length && displayMode === DisplayMode.LARGE_CARDS) {
|
||||
<div content class="row my-n2">
|
||||
@for (d of documents; track d.id) {
|
||||
} @else if (displayMode === DisplayMode.LARGE_CARDS) {
|
||||
<div class="row my-n2">
|
||||
@for (d of documents; track d.id; let i = $index) {
|
||||
<pngx-document-card-large
|
||||
(dblClickDocument)="openDocumentDetail(d)"
|
||||
[document]="d"
|
||||
[document]="!loading && reveal ? d : null"
|
||||
[ngStyle]="{ opacity: !loading && reveal ? 1 : 1 - (i * 1/documents.length) }"
|
||||
[displayFields]="displayFields"
|
||||
(clickTag)="clickTag($event)"
|
||||
(clickCorrespondent)="clickCorrespondent($event)"
|
||||
@@ -113,8 +122,8 @@
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<p content i18n class="text-center text-muted mb-0 fst-italic">No documents</p>
|
||||
<p i18n class="text-center text-muted mb-0 fst-italic">No documents</p>
|
||||
}
|
||||
|
||||
|
||||
</div>
|
||||
</pngx-widget-frame>
|
||||
|
@@ -1,3 +1,17 @@
|
||||
.wrapper {
|
||||
transition: all .3s ease-out;
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
opacity: .1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.reveal {
|
||||
max-height: 1000px;
|
||||
opacity: 1;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
table {
|
||||
overflow-wrap: anywhere;
|
||||
table-layout: fixed;
|
||||
|
@@ -187,7 +187,7 @@ describe('SavedViewWidgetComponent', () => {
|
||||
fixture.detectChanges()
|
||||
})
|
||||
|
||||
it('should show a list of documents', () => {
|
||||
it('should show a list of documents', fakeAsync(() => {
|
||||
jest.spyOn(documentService, 'listFiltered').mockReturnValue(
|
||||
of({
|
||||
all: [2, 3],
|
||||
@@ -196,6 +196,7 @@ describe('SavedViewWidgetComponent', () => {
|
||||
})
|
||||
)
|
||||
component.ngOnInit()
|
||||
tick(500)
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain('doc2')
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain('doc3')
|
||||
@@ -206,7 +207,7 @@ describe('SavedViewWidgetComponent', () => {
|
||||
expect(
|
||||
fixture.debugElement.queryAll(By.css('td a.btn'))[1].attributes['href']
|
||||
).toEqual(component.getDownloadUrl(documentResults[0]))
|
||||
})
|
||||
}))
|
||||
|
||||
it('should call api endpoint and load results', () => {
|
||||
const listAllSpy = jest.spyOn(documentService, 'listFiltered')
|
||||
|
@@ -7,7 +7,7 @@ import {
|
||||
ViewChildren,
|
||||
} from '@angular/core'
|
||||
import { Router } from '@angular/router'
|
||||
import { Subject, takeUntil } from 'rxjs'
|
||||
import { delay, Subject, takeUntil, tap } from 'rxjs'
|
||||
import {
|
||||
DEFAULT_DASHBOARD_DISPLAY_FIELDS,
|
||||
DEFAULT_DASHBOARD_VIEW_PAGE_SIZE,
|
||||
@@ -52,7 +52,8 @@ export class SavedViewWidgetComponent
|
||||
public DisplayField = DisplayField
|
||||
public CustomFieldDataType = CustomFieldDataType
|
||||
|
||||
loading: boolean = true
|
||||
public loading: boolean = true
|
||||
public reveal: boolean = false
|
||||
|
||||
private customFields: CustomField[] = []
|
||||
|
||||
@@ -133,16 +134,22 @@ export class SavedViewWidgetComponent
|
||||
this.documentService
|
||||
.listFiltered(
|
||||
1,
|
||||
this.savedView.page_size ?? DEFAULT_DASHBOARD_VIEW_PAGE_SIZE,
|
||||
this.savedView?.page_size ?? DEFAULT_DASHBOARD_VIEW_PAGE_SIZE,
|
||||
this.savedView.sort_field,
|
||||
this.savedView.sort_reverse,
|
||||
this.savedView.filter_rules,
|
||||
{ truncate_content: true }
|
||||
)
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
.pipe(
|
||||
takeUntil(this.unsubscribeNotifier),
|
||||
tap((result) => {
|
||||
this.reveal = true
|
||||
this.documents = result.results
|
||||
}),
|
||||
delay(500)
|
||||
)
|
||||
.subscribe((result) => {
|
||||
this.loading = false
|
||||
this.documents = result.results
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -1,25 +1,49 @@
|
||||
<pngx-widget-frame title="Statistics" [loading]="loading" i18n-title>
|
||||
<ng-container content>
|
||||
<div class="list-group border-light">
|
||||
@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>
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
@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>
|
||||
<div class="list-group border-light placeholder-glow">
|
||||
@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>
|
||||
</div>
|
||||
<div class="list-group-item d-flex">
|
||||
<div class="placeholder w-25"></div>
|
||||
<span class="placeholder badge rounded-pill ms-auto" style="width: 25px;"> </span>
|
||||
</div>
|
||||
<div class="list-group-item d-flex">
|
||||
<div class="placeholder w-25"></div>
|
||||
<span class="placeholder badge rounded-pill ms-auto" style="width: 25px;"> </span>
|
||||
</div>
|
||||
<div class="list-group-item d-flex">
|
||||
<div class="placeholder w-25"></div>
|
||||
<span class="placeholder badge rounded-pill ms-auto" style="width: 25px;"> </span>
|
||||
</div>
|
||||
<div class="list-group-item filetypes">
|
||||
<div class="placeholder w-100 d-block mb-2"></div>
|
||||
<div class="placeholder w-100 d-block mb-2"></div>
|
||||
<div class="placeholder w-100 d-block"></div>
|
||||
</div>
|
||||
} @else {
|
||||
@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>
|
||||
</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>
|
||||
</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>
|
||||
</div>
|
||||
@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>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if (statistics?.document_file_type_counts?.length > 1) {
|
||||
<div class="list-group-item filetypes">
|
||||
@@ -59,6 +83,11 @@
|
||||
|
||||
<div class="list-group border-light mt-3">
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }">
|
||||
@if (loading) {
|
||||
<div class="placeholder-glow list-group-item">
|
||||
<span class="placeholder w-100"></span>
|
||||
</div>
|
||||
}
|
||||
@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>:
|
||||
@@ -67,6 +96,11 @@
|
||||
}
|
||||
</ng-container>
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }">
|
||||
@if (loading) {
|
||||
<div class="placeholder-glow list-group-item">
|
||||
<span class="placeholder w-100"></span>
|
||||
</div>
|
||||
}
|
||||
@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>:
|
||||
@@ -75,6 +109,11 @@
|
||||
}
|
||||
</ng-container>
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
|
||||
@if (loading) {
|
||||
<div class="placeholder-glow list-group-item">
|
||||
<span class="placeholder w-100"></span>
|
||||
</div>
|
||||
}
|
||||
@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>:
|
||||
@@ -83,6 +122,11 @@
|
||||
}
|
||||
</ng-container>
|
||||
<ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
|
||||
@if (loading) {
|
||||
<div class="placeholder-glow list-group-item">
|
||||
<span class="placeholder w-100"></span>
|
||||
</div>
|
||||
}
|
||||
@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>:
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<div class="card shadow-sm bg-light" cdkDrag [cdkDragDisabled]="!draggable" cdkDragPreviewContainer="parent">
|
||||
<div class="card shadow-sm bg-light fade" [class.reveal]="reveal" cdkDrag [cdkDragDisabled]="!draggable" cdkDragPreviewContainer="parent">
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="d-flex">
|
||||
|
@@ -1,3 +1,12 @@
|
||||
i-bs {
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.card {
|
||||
opacity: 0;
|
||||
transition: opacity .2s;
|
||||
}
|
||||
|
||||
.reveal {
|
||||
opacity: 1;
|
||||
}
|
||||
|
@@ -35,6 +35,7 @@ describe('WidgetFrameComponent', () => {
|
||||
|
||||
fixture = TestBed.createComponent(WidgetFrameComponent)
|
||||
component = fixture.componentInstance
|
||||
jest.useFakeTimers()
|
||||
|
||||
fixture.detectChanges()
|
||||
})
|
||||
@@ -51,4 +52,10 @@ describe('WidgetFrameComponent', () => {
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.query(By.css('.spinner-border'))).not.toBeNull()
|
||||
})
|
||||
|
||||
it('should reveal', () => {
|
||||
expect(component.reveal).toBeFalsy()
|
||||
jest.advanceTimersByTime(100)
|
||||
expect(component.reveal).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
@@ -1,11 +1,11 @@
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { AfterViewInit, Component, Input } from '@angular/core'
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-widget-frame',
|
||||
templateUrl: './widget-frame.component.html',
|
||||
styleUrls: ['./widget-frame.component.scss'],
|
||||
})
|
||||
export class WidgetFrameComponent {
|
||||
export class WidgetFrameComponent implements AfterViewInit {
|
||||
constructor() {}
|
||||
|
||||
@Input()
|
||||
@@ -16,4 +16,12 @@ export class WidgetFrameComponent {
|
||||
|
||||
@Input()
|
||||
draggable: any
|
||||
|
||||
public reveal: boolean = false
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
setTimeout(() => {
|
||||
this.reveal = true
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user