mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-07-28 18:24:38 -05:00
Useful buttons for saved view widgets
This commit is contained in:
@@ -6,14 +6,33 @@
|
||||
<table content class="table table-sm table-hover table-borderless mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th i18n>Created</th>
|
||||
<th scope="col" i18n>Created</th>
|
||||
<th scope="col" i18n>Title</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody *appIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
|
||||
<tr *ngFor="let doc of documents">
|
||||
<tr *ngFor="let doc of documents" (mouseleave)="mouseLeaveCard()">
|
||||
<td><a routerLink="/documents/{{doc.id}}" class="d-block text-dark text-decoration-none">{{doc.created_date | customDate}}</a></td>
|
||||
<td><a routerLink="/documents/{{doc.id}}" class="d-block text-dark text-decoration-none">{{doc.title | documentTitle}}<app-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ms-1" (click)="clickTag(t, $event)"></app-tag></a></td>
|
||||
<td class="position-relative">
|
||||
<a routerLink="/documents/{{doc.id}}" title="Edit" i18n-title class="d-block text-dark text-decoration-none">{{doc.title | documentTitle}}<app-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ms-1" (click)="clickTag(t, $event)"></app-tag></a>
|
||||
<div class="btn-group position-absolute top-50 end-0 translate-middle-y">
|
||||
<a [href]="getPreviewUrl(doc)" title="View Preview" i18n-title target="_blank" class="btn btn-sm px-4 py-0 btn-dark border-dark-subtle"
|
||||
[ngbPopover]="previewContent" [popoverTitle]="doc.title | documentTitle"
|
||||
autoClose="true" popoverClass="shadow popover-preview" container="body" (mouseenter)="mouseEnterPreview(doc)" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
||||
<svg class="buttonicon-xs" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#eye"/>
|
||||
</svg>
|
||||
</a>
|
||||
<ng-template #previewContent>
|
||||
<object [data]="getPreviewUrl(doc) | safeUrl" class="preview" width="100%"></object>
|
||||
</ng-template>
|
||||
<a [href]="getDownloadUrl(doc)" class="btn btn-sm px-4 py-0 btn-dark border-dark-subtle" title="Download" i18n-title (click)="$event.stopPropagation()">
|
||||
<svg class="buttonicon-xs" fill="currentColor">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#download"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@@ -10,3 +10,15 @@ th:first-child {
|
||||
tbody app-tag {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
tr .btn-group {
|
||||
margin-right: 2px;
|
||||
box-shadow: -6px 0px 4px -1px rgba(var(--bs-body-bg-rgb), .5);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
tr:hover .btn-group {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
@@ -1,6 +1,11 @@
|
||||
import { DatePipe } from '@angular/common'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { Router } from '@angular/router'
|
||||
import { RouterTestingModule } from '@angular/router/testing'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
@@ -21,6 +26,8 @@ import { PermissionsService } from 'src/app/services/permissions.service'
|
||||
import { DocumentService } from 'src/app/services/rest/document.service'
|
||||
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
|
||||
import { SavedViewWidgetComponent } from './saved-view-widget.component'
|
||||
import { By } from '@angular/platform-browser'
|
||||
import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
|
||||
|
||||
const savedView: PaperlessSavedView = {
|
||||
id: 1,
|
||||
@@ -64,6 +71,7 @@ describe('SavedViewWidgetComponent', () => {
|
||||
IfPermissionsDirective,
|
||||
CustomDatePipe,
|
||||
DocumentTitlePipe,
|
||||
SafeUrlPipe,
|
||||
],
|
||||
providers: [
|
||||
PermissionsGuard,
|
||||
@@ -107,8 +115,39 @@ describe('SavedViewWidgetComponent', () => {
|
||||
fixture.detectChanges()
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain('doc2')
|
||||
expect(fixture.debugElement.nativeElement.textContent).toContain('doc3')
|
||||
// preview + download buttons
|
||||
expect(
|
||||
fixture.debugElement.queryAll(By.css('td a.btn'))[0].attributes['href']
|
||||
).toEqual(component.getPreviewUrl(documentResults[0]))
|
||||
expect(
|
||||
fixture.debugElement.queryAll(By.css('td a.btn'))[1].attributes['href']
|
||||
).toEqual(component.getDownloadUrl(documentResults[0]))
|
||||
})
|
||||
|
||||
it('should show preview on mouseover after delay to preload content', fakeAsync(() => {
|
||||
jest.spyOn(documentService, 'listFiltered').mockReturnValue(
|
||||
of({
|
||||
all: [2, 3],
|
||||
count: 2,
|
||||
results: documentResults,
|
||||
})
|
||||
)
|
||||
component.ngOnInit()
|
||||
fixture.detectChanges()
|
||||
component.mouseEnterPreview(documentResults[0])
|
||||
expect(component.popover.isOpen()).toBeTruthy()
|
||||
expect(component.popoverHidden).toBeTruthy()
|
||||
tick(600)
|
||||
expect(component.popoverHidden).toBeFalsy()
|
||||
component.mouseLeaveCard()
|
||||
|
||||
component.mouseEnterPreview(documentResults[1])
|
||||
tick(100)
|
||||
component.mouseLeavePreview()
|
||||
tick(600)
|
||||
expect(component.popover.isOpen()).toBeFalsy()
|
||||
}))
|
||||
|
||||
it('should call api endpoint and load results', () => {
|
||||
const listAllSpy = jest.spyOn(documentService, 'listFiltered')
|
||||
listAllSpy.mockReturnValue(
|
||||
|
@@ -1,4 +1,12 @@
|
||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core'
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
QueryList,
|
||||
ViewChild,
|
||||
ViewChildren,
|
||||
} from '@angular/core'
|
||||
import { Router } from '@angular/router'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { PaperlessDocument } from 'src/app/data/paperless-document'
|
||||
@@ -10,11 +18,15 @@ import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
|
||||
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
|
||||
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
|
||||
import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
|
||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||
|
||||
@Component({
|
||||
selector: 'app-saved-view-widget',
|
||||
templateUrl: './saved-view-widget.component.html',
|
||||
styleUrls: ['./saved-view-widget.component.scss'],
|
||||
styleUrls: [
|
||||
'./saved-view-widget.component.scss',
|
||||
'../../../document-list/popover-preview/popover-preview.scss',
|
||||
],
|
||||
})
|
||||
export class SavedViewWidgetComponent
|
||||
extends ComponentWithPermissions
|
||||
@@ -39,6 +51,12 @@ export class SavedViewWidgetComponent
|
||||
|
||||
subscription: Subscription
|
||||
|
||||
@ViewChildren('popover') popovers: QueryList<NgbPopover>
|
||||
popover: NgbPopover
|
||||
|
||||
mouseOnPreview = false
|
||||
popoverHidden = true
|
||||
|
||||
ngOnInit(): void {
|
||||
this.reload()
|
||||
this.subscription = this.consumerStatusService
|
||||
@@ -87,4 +105,38 @@ export class SavedViewWidgetComponent
|
||||
{ rule_type: FILTER_HAS_TAGS_ALL, value: tag.id.toString() },
|
||||
])
|
||||
}
|
||||
|
||||
getPreviewUrl(document: PaperlessDocument): string {
|
||||
return this.documentService.getPreviewUrl(document.id)
|
||||
}
|
||||
|
||||
getDownloadUrl(document: PaperlessDocument): string {
|
||||
return this.documentService.getDownloadUrl(document.id)
|
||||
}
|
||||
|
||||
mouseEnterPreview(doc: PaperlessDocument) {
|
||||
this.popover = this.popovers.get(this.documents.indexOf(doc))
|
||||
this.mouseOnPreview = true
|
||||
if (!this.popover.isOpen()) {
|
||||
// we're going to open but hide to pre-load content during hover delay
|
||||
this.popover.open()
|
||||
this.popoverHidden = true
|
||||
setTimeout(() => {
|
||||
if (this.mouseOnPreview) {
|
||||
// show popover
|
||||
this.popoverHidden = false
|
||||
} else {
|
||||
this.popover.close()
|
||||
}
|
||||
}, 600)
|
||||
}
|
||||
}
|
||||
|
||||
mouseLeavePreview() {
|
||||
this.mouseOnPreview = false
|
||||
}
|
||||
|
||||
mouseLeaveCard() {
|
||||
this.popover?.close()
|
||||
}
|
||||
}
|
||||
|
@@ -50,7 +50,7 @@
|
||||
</a>
|
||||
<a class="btn btn-sm btn-outline-secondary" target="_blank" [href]="previewUrl"
|
||||
[ngbPopover]="previewContent" [popoverTitle]="document.title | documentTitle"
|
||||
autoClose="true" popoverClass="shadow" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
||||
autoClose="true" popoverClass="shadow popover-preview" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
||||
<svg class="sidebaricon" fill="currentColor" class="sidebaricon">
|
||||
<use xlink:href="assets/bootstrap-icons.svg#eye"/>
|
||||
</svg> <span class="d-none d-md-inline" i18n>View</span>
|
||||
|
@@ -87,7 +87,7 @@
|
||||
</a>
|
||||
<a [href]="previewUrl" target="_blank" class="btn btn-sm btn-outline-secondary"
|
||||
[ngbPopover]="previewContent" [popoverTitle]="document.title | documentTitle"
|
||||
autoClose="true" popoverClass="shadow" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
||||
autoClose="true" popoverClass="shadow popover-preview" (mouseenter)="mouseEnterPreview()" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16">
|
||||
<path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/>
|
||||
<path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
::ng-deep app-document-list .popover {
|
||||
::ng-deep .popover.popover-preview {
|
||||
max-width: 40rem;
|
||||
|
||||
.preview {
|
||||
@@ -16,7 +16,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .popover-hidden .popover {
|
||||
::ng-deep .popover-hidden .popover {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
@@ -433,6 +433,11 @@ ul.pagination {
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.buttonicon-xs {
|
||||
width: 0.8em;
|
||||
height: 0.8em;
|
||||
}
|
||||
|
||||
.sidebaricon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
@@ -71,6 +71,7 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,<svg xmlns='htt
|
||||
--pngx-focus-alpha: 0.6;
|
||||
--pngx-primary-faded: var(--pngx-primary-darken-15);
|
||||
--pngx-primary-text-contrast: var(--bs-body-color);
|
||||
--bs-dark-border-subtle: var(--pngx-bg-darker);
|
||||
|
||||
.text-dark, .text-light {
|
||||
color: var(--bs-body-color) !important;
|
||||
|
Reference in New Issue
Block a user