mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	Document popover previews
This commit is contained in:
		| @@ -37,12 +37,30 @@ | |||||||
|               <path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/> |               <path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/> | ||||||
|             </svg> |             </svg> | ||||||
|           </a> |           </a> | ||||||
|           <a [href]="getPreviewUrl()" class="btn btn-sm btn-outline-secondary" title="View in browser" i18n-title> |           <a [href]="previewUrl" target="_blank" class="btn btn-sm btn-outline-secondary" title="Hover to preview, click to view in browser" i18n-title | ||||||
|  |           [ngbPopover]="previewContent" [popoverTitle]="document.title | documentTitle" | ||||||
|  |           autoClose="true" popoverClass="shadow" (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"> |             <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="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"/> |               <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"/> | ||||||
|             </svg> |             </svg> | ||||||
|           </a> |           </a> | ||||||
|  |           <ng-template #previewContent> | ||||||
|  |             <ng-container *ngIf="getContentType() == 'application/pdf'"> | ||||||
|  |                 <div class="preview pdf-viewer-container" *ngIf="!useNativePdfViewer ; else nativePdfViewer"> | ||||||
|  |                   <div class="spinner-border spinner-border-sm" role="status"> | ||||||
|  |                     <span class="sr-only">Loading...</span> | ||||||
|  |                   </div> | ||||||
|  |                   <pdf-viewer [src]="previewUrl" [original-size]="false" [show-borders]="false" [show-all]="true" [render-text-mode]="2" (after-load-complete)="pdfPreviewLoaded($event)"></pdf-viewer> | ||||||
|  |                 </div> | ||||||
|  |                 <ng-template #nativePdfViewer> | ||||||
|  |                     <object [data]="previewUrl | safe" type="application/pdf" class="preview" width="100%"></object> | ||||||
|  |                 </ng-template> | ||||||
|  |             </ng-container> | ||||||
|  |             <ng-container *ngIf="getContentType() == 'text/plain'"> | ||||||
|  |                 <object [data]="previewUrl | safe" type="text/plain" class="preview" width="100%"></object> | ||||||
|  |             </ng-container> | ||||||
|  |           </ng-template> | ||||||
|           <a [href]="getDownloadUrl()" class="btn btn-sm btn-outline-secondary" title="Download" (click)="$event.stopPropagation()" i18n-title> |           <a [href]="getDownloadUrl()" class="btn btn-sm btn-outline-secondary" title="Download" (click)="$event.stopPropagation()" i18n-title> | ||||||
|             <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-download" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> |             <svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-download" fill="currentColor" xmlns="http://www.w3.org/2000/svg"> | ||||||
|               <path fill-rule="evenodd" d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/> |               <path fill-rule="evenodd" d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/> | ||||||
|   | |||||||
| @@ -34,3 +34,21 @@ | |||||||
| .doc-img-background-selected { | .doc-img-background-selected { | ||||||
|   background-color: $primaryFaded; |   background-color: $primaryFaded; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | ::ng-deep .popover { | ||||||
|  |   max-width: 40rem; | ||||||
|  |  | ||||||
|  |   .preview { | ||||||
|  |     min-width: 10rem; | ||||||
|  |     min-height: 10rem; | ||||||
|  |     max-height: 25rem; | ||||||
|  |     overflow-y: scroll; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   .spinner-border { | ||||||
|  |     position: absolute; | ||||||
|  |     top: 4rem; | ||||||
|  |     left: calc(50% - 0.5rem); | ||||||
|  |     z-index: 0; | ||||||
|  |   } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,7 +1,10 @@ | |||||||
| import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; | import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; | ||||||
| import { map } from 'rxjs/operators'; | import { map } from 'rxjs/operators'; | ||||||
| import { PaperlessDocument } from 'src/app/data/paperless-document'; | import { PaperlessDocument } from 'src/app/data/paperless-document'; | ||||||
|  | import { PaperlessDocumentMetadata } from 'src/app/data/paperless-document-metadata'; | ||||||
| import { DocumentService } from 'src/app/services/rest/document.service'; | import { DocumentService } from 'src/app/services/rest/document.service'; | ||||||
|  | import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; | ||||||
|  | import { SettingsService, SETTINGS_KEYS } from 'src/app/services/settings.service'; | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-document-card-small', |   selector: 'app-document-card-small', | ||||||
| @@ -10,7 +13,7 @@ import { DocumentService } from 'src/app/services/rest/document.service'; | |||||||
| }) | }) | ||||||
| export class DocumentCardSmallComponent implements OnInit { | export class DocumentCardSmallComponent implements OnInit { | ||||||
|  |  | ||||||
|   constructor(private documentService: DocumentService) { } |   constructor(private documentService: DocumentService, private settings: SettingsService) { } | ||||||
|  |  | ||||||
|   @Input() |   @Input() | ||||||
|   selected = false |   selected = false | ||||||
| @@ -29,7 +32,17 @@ export class DocumentCardSmallComponent implements OnInit { | |||||||
|  |  | ||||||
|   moreTags: number = null |   moreTags: number = null | ||||||
|  |  | ||||||
|  |   @Output() | ||||||
|  |   showPreview = new EventEmitter<DocumentCardSmallComponent>() | ||||||
|  |  | ||||||
|  |   @ViewChild('popover') popover: NgbPopover | ||||||
|  |  | ||||||
|  |   metadata: PaperlessDocumentMetadata | ||||||
|  |  | ||||||
|   ngOnInit(): void { |   ngOnInit(): void { | ||||||
|  |     this.documentService.getMetadata(this.document?.id).subscribe(result => { | ||||||
|  |       this.metadata = result | ||||||
|  |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getThumbUrl() { |   getThumbUrl() { | ||||||
| @@ -40,7 +53,7 @@ export class DocumentCardSmallComponent implements OnInit { | |||||||
|     return this.documentService.getDownloadUrl(this.document.id) |     return this.documentService.getDownloadUrl(this.document.id) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getPreviewUrl() { |   get previewUrl() { | ||||||
|     return this.documentService.getPreviewUrl(this.document.id) |     return this.documentService.getPreviewUrl(this.document.id) | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -57,4 +70,27 @@ export class DocumentCardSmallComponent implements OnInit { | |||||||
|     ) |     ) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   get useNativePdfViewer(): boolean { | ||||||
|  |     return this.settings.get(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   getContentType() { | ||||||
|  |     return this.metadata?.has_archive_version ? 'application/pdf' : this.metadata?.original_mime_type | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   mouseEnterPreview() { | ||||||
|  |     this.mouseOnPreview = true | ||||||
|  |     if (!this.popover.isOpen()) { | ||||||
|  |       setTimeout(() => { | ||||||
|  |         if (this.mouseOnPreview) { | ||||||
|  |           this.showPreview.emit(this) | ||||||
|  |           this.popover.open() | ||||||
|  |         } | ||||||
|  |       }, 600); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   mouseLeavePreview() { | ||||||
|  |     this.mouseOnPreview = false | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -170,5 +170,5 @@ | |||||||
| </table> | </table> | ||||||
|  |  | ||||||
| <div class="m-n2 row row-cols-paperless-cards" *ngIf="displayMode == 'smallCards'"> | <div class="m-n2 row row-cols-paperless-cards" *ngIf="displayMode == 'smallCards'"> | ||||||
|   <app-document-card-small [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)"  [document]="d" *ngFor="let d of list.documents; trackBy: trackByDocumentId" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)"></app-document-card-small> |   <app-document-card-small [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" (showPreview)="closeAllPopovers($event)"  [document]="d" *ngFor="let d of list.documents; trackBy: trackByDocumentId" (clickTag)="clickTag($event)" (clickCorrespondent)="clickCorrespondent($event)"></app-document-card-small> | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ import { SavedViewService } from 'src/app/services/rest/saved-view.service'; | |||||||
| import { Toast, ToastService } from 'src/app/services/toast.service'; | import { Toast, ToastService } from 'src/app/services/toast.service'; | ||||||
| import { FilterEditorComponent } from './filter-editor/filter-editor.component'; | import { FilterEditorComponent } from './filter-editor/filter-editor.component'; | ||||||
| import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'; | import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'; | ||||||
|  | import { DocumentCardSmallComponent } from './document-card-small/document-card-small.component'; | ||||||
|  |  | ||||||
| @Component({ | @Component({ | ||||||
|   selector: 'app-document-list', |   selector: 'app-document-list', | ||||||
| @@ -41,6 +42,8 @@ export class DocumentListComponent implements OnInit, OnDestroy { | |||||||
|  |  | ||||||
|   private consumptionFinishedSubscription: Subscription |   private consumptionFinishedSubscription: Subscription | ||||||
|  |  | ||||||
|  |   @ViewChildren(DocumentCardSmallComponent) smallCards: QueryList<DocumentCardSmallComponent> | ||||||
|  |  | ||||||
|   get isFiltered() { |   get isFiltered() { | ||||||
|     return this.list.filterRules?.length > 0 |     return this.list.filterRules?.length > 0 | ||||||
|   } |   } | ||||||
| @@ -204,4 +207,10 @@ export class DocumentListComponent implements OnInit, OnDestroy { | |||||||
|   trackByDocumentId(index, item: PaperlessDocument) { |   trackByDocumentId(index, item: PaperlessDocument) { | ||||||
|     return item.id |     return item.id | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   closeAllPopovers(cardOpening: DocumentCardSmallComponent) { | ||||||
|  |     this.smallCards.forEach(card => { | ||||||
|  |       if (card !== cardOpening) card.popover.close() | ||||||
|  |     }) | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -366,6 +366,18 @@ $border-color-dark-mode: #47494f; | |||||||
|   .progress-bar.bg-primary { |   .progress-bar.bg-primary { | ||||||
|     background-color: darken($primary-dark-mode, 5%) !important; |     background-color: darken($primary-dark-mode, 5%) !important; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   .popover { | ||||||
|  |     .popover-header, | ||||||
|  |     .popover-body { | ||||||
|  |       background-color: $bg-light-dark-mode; | ||||||
|  |       border-color: $border-color-dark-mode; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     .arrow::after { | ||||||
|  |       border-top-color: $bg-light-dark-mode; | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| body.color-scheme-dark { | body.color-scheme-dark { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Michael Shamoon
					Michael Shamoon