|
- {{ document.title }} |
+
+ {{ document.title }}
+
+
+
+ |
{{ getDaysRemaining(document) }} days |
diff --git a/src-ui/src/app/components/common/preview-popup/preview-popup.component.html b/src-ui/src/app/components/common/preview-popup/preview-popup.component.html
index f9a8b9771..18b7cb94d 100644
--- a/src-ui/src/app/components/common/preview-popup/preview-popup.component.html
+++ b/src-ui/src/app/components/common/preview-popup/preview-popup.component.html
@@ -1,30 +1,37 @@
-
+
diff --git a/src-ui/src/app/components/common/preview-popup/preview-popup.component.spec.ts b/src-ui/src/app/components/common/preview-popup/preview-popup.component.spec.ts
index 2b9f71cef..12021fc90 100644
--- a/src-ui/src/app/components/common/preview-popup/preview-popup.component.spec.ts
+++ b/src-ui/src/app/components/common/preview-popup/preview-popup.component.spec.ts
@@ -1,4 +1,9 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing'
+import {
+ ComponentFixture,
+ fakeAsync,
+ TestBed,
+ tick,
+} from '@angular/core/testing'
import { PreviewPopupComponent } from './preview-popup.component'
import { By } from '@angular/platform-browser'
@@ -15,6 +20,8 @@ import {
withInterceptorsFromDi,
} from '@angular/common/http'
import { of, throwError } from 'rxjs'
+import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
+import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
const doc = {
id: 10,
@@ -34,8 +41,12 @@ describe('PreviewPopupComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
- declarations: [PreviewPopupComponent, SafeUrlPipe],
- imports: [NgxBootstrapIconsModule.pick(allIcons), PdfViewerModule],
+ declarations: [PreviewPopupComponent, SafeUrlPipe, DocumentTitlePipe],
+ imports: [
+ NgxBootstrapIconsModule.pick(allIcons),
+ PdfViewerModule,
+ NgbPopoverModule,
+ ],
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
@@ -70,12 +81,14 @@ describe('PreviewPopupComponent', () => {
it('should render object if native PDF viewer enabled', () => {
settingsService.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, true)
+ component.popover.open()
fixture.detectChanges()
expect(fixture.debugElement.query(By.css('object'))).not.toBeNull()
})
it('should render pngx viewer if native PDF viewer disabled', () => {
settingsService.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, false)
+ component.popover.open()
fixture.detectChanges()
expect(fixture.debugElement.query(By.css('object'))).toBeNull()
expect(fixture.debugElement.query(By.css('pdf-viewer'))).not.toBeNull()
@@ -83,6 +96,7 @@ describe('PreviewPopupComponent', () => {
it('should show lock icon on password error', () => {
settingsService.set(SETTINGS_KEYS.USE_NATIVE_PDF_VIEWER, false)
+ component.popover.open()
component.onError({ name: 'PasswordException' })
fixture.detectChanges()
expect(component.requiresPassword).toBeTruthy()
@@ -93,16 +107,18 @@ describe('PreviewPopupComponent', () => {
component.document.original_file_name = 'sample.png'
component.document.mime_type = 'image/png'
component.document.archived_file_name = undefined
+ component.popover.open()
fixture.detectChanges()
expect(fixture.debugElement.query(By.css('object'))).not.toBeNull()
})
it('should show message on error', () => {
+ component.popover.open()
component.onError({})
fixture.detectChanges()
- expect(fixture.debugElement.nativeElement.textContent).toContain(
- 'Error loading preview'
- )
+ expect(
+ fixture.debugElement.query(By.css('.popover')).nativeElement.textContent
+ ).toContain('Error loading preview')
})
it('should get text content from http if appropriate', () => {
@@ -122,4 +138,17 @@ describe('PreviewPopupComponent', () => {
component.init()
expect(component.previewText).toEqual('Preview text')
})
+
+ it('should show preview on mouseover after delay to preload content', fakeAsync(() => {
+ component.mouseEnterPreview()
+ expect(component.popover.isOpen()).toBeTruthy()
+ tick(600)
+ component.close()
+
+ component.mouseEnterPreview()
+ tick(100)
+ component.mouseLeavePreview()
+ tick(600)
+ expect(component.popover.isOpen()).toBeFalsy()
+ }))
})
diff --git a/src-ui/src/app/components/common/preview-popup/preview-popup.component.ts b/src-ui/src/app/components/common/preview-popup/preview-popup.component.ts
index 6d2ede266..75f3cbb86 100644
--- a/src-ui/src/app/components/common/preview-popup/preview-popup.component.ts
+++ b/src-ui/src/app/components/common/preview-popup/preview-popup.component.ts
@@ -1,5 +1,6 @@
import { HttpClient } from '@angular/common/http'
-import { Component, Input, OnDestroy } from '@angular/core'
+import { Component, Input, OnDestroy, ViewChild } from '@angular/core'
+import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
import { first, Subject, takeUntil } from 'rxjs'
import { Document } from 'src/app/data/document'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
@@ -23,6 +24,18 @@ export class PreviewPopupComponent implements OnDestroy {
return this._document
}
+ @Input()
+ link: string
+
+ @Input()
+ linkClasses: string = 'btn btn-sm btn-outline-secondary'
+
+ @Input()
+ linkTarget: string = '_blank'
+
+ @Input()
+ linkTitle: string = $localize`Open preview`
+
unsubscribeNotifier: Subject = new Subject()
error = false
@@ -31,6 +44,12 @@ export class PreviewPopupComponent implements OnDestroy {
previewText: string
+ @ViewChild('popover') popover: NgbPopover
+
+ mouseOnPreview: boolean
+
+ popoverClass: string = 'shadow popover-preview'
+
get renderAsObject(): boolean {
return (this.isPdf && this.useNativePdfViewer) || !this.isPdf
}
@@ -83,4 +102,33 @@ export class PreviewPopupComponent implements OnDestroy {
this.error = true
}
}
+
+ get previewUrl() {
+ return this.documentService.getPreviewUrl(this.document.id)
+ }
+
+ mouseEnterPreview() {
+ 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.popoverClass = 'shadow popover-preview pe-none opacity-0'
+ setTimeout(() => {
+ if (this.mouseOnPreview) {
+ // show popover
+ this.popoverClass = this.popoverClass.replace('pe-none opacity-0', '')
+ } else {
+ this.popover.close()
+ }
+ }, 600)
+ }
+ }
+
+ mouseLeavePreview() {
+ this.mouseOnPreview = false
+ }
+
+ public close() {
+ this.popover.close(false)
+ }
}
diff --git a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html
index 04f3a236a..34557be31 100644
--- a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html
+++ b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.html
@@ -1,4 +1,4 @@
-
+
![]()
@@ -56,14 +56,9 @@
Open
-
+
View
-
-
-
-
+
Download
diff --git a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.spec.ts b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.spec.ts
index efd5076be..95b12d7ec 100644
--- a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.spec.ts
+++ b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.spec.ts
@@ -1,11 +1,6 @@
import { DatePipe } from '@angular/common'
import { provideHttpClientTesting } from '@angular/common/http/testing'
-import {
- ComponentFixture,
- TestBed,
- fakeAsync,
- tick,
-} from '@angular/core/testing'
+import { ComponentFixture, TestBed } from '@angular/core/testing'
import { By } from '@angular/platform-browser'
import { RouterTestingModule } from '@angular/router/testing'
import {
@@ -84,21 +79,6 @@ describe('DocumentCardLargeComponent', () => {
expect(fixture.nativeElement.textContent).toContain('8 pages')
})
- it('should show preview on mouseover after delay to preload content', fakeAsync(() => {
- component.mouseEnterPreview()
- expect(component.popover.isOpen()).toBeTruthy()
- expect(component.popoverHidden).toBeTruthy()
- tick(600)
- expect(component.popoverHidden).toBeFalsy()
- component.mouseLeaveCard()
-
- component.mouseEnterPreview()
- tick(100)
- component.mouseLeavePreview()
- tick(600)
- expect(component.popover.isOpen()).toBeFalsy()
- }))
-
it('should trim content', () => {
expect(component.contentTrimmed).toHaveLength(503) // includes ...
})
diff --git a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts
index a3d57d950..99597ca5a 100644
--- a/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts
+++ b/src-ui/src/app/components/document-list/document-card-large/document-card-large.component.ts
@@ -12,9 +12,9 @@ import {
} from 'src/app/data/document'
import { DocumentService } from 'src/app/services/rest/document.service'
import { SettingsService } from 'src/app/services/settings.service'
-import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
+import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
@Component({
selector: 'pngx-document-card-large',
@@ -65,7 +65,7 @@ export class DocumentCardLargeComponent extends ComponentWithPermissions {
@Output()
clickMoreLike = new EventEmitter()
- @ViewChild('popover') popover: NgbPopover
+ @ViewChild('popupPreview') popupPreview: PreviewPopupComponent
mouseOnPreview = false
popoverHidden = true
@@ -112,29 +112,8 @@ export class DocumentCardLargeComponent extends ComponentWithPermissions {
return this.documentService.getPreviewUrl(this.document.id)
}
- mouseEnterPreview() {
- 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()
+ this.popupPreview.close()
}
get contentTrimmed() {
diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html
index 57bd6048b..60713ef02 100644
--- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html
+++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.html
@@ -1,5 +1,5 @@
-
+
![]()
@@ -129,14 +129,9 @@
-
+
-
-
-
-
+
diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.spec.ts b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.spec.ts
index b86453a25..0c0c82103 100644
--- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.spec.ts
+++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.spec.ts
@@ -1,11 +1,6 @@
import { DatePipe } from '@angular/common'
import { provideHttpClientTesting } from '@angular/common/http/testing'
-import {
- ComponentFixture,
- TestBed,
- fakeAsync,
- tick,
-} from '@angular/core/testing'
+import { ComponentFixture, TestBed } from '@angular/core/testing'
import { RouterTestingModule } from '@angular/router/testing'
import {
NgbPopoverModule,
@@ -116,19 +111,4 @@ describe('DocumentCardSmallComponent', () => {
fixture.debugElement.queryAll(By.directive(TagComponent))
).toHaveLength(6)
})
-
- it('should show preview on mouseover after delay to preload content', fakeAsync(() => {
- component.mouseEnterPreview()
- expect(component.popover.isOpen()).toBeTruthy()
- expect(component.popoverHidden).toBeTruthy()
- tick(600)
- expect(component.popoverHidden).toBeFalsy()
- component.mouseLeaveCard()
-
- component.mouseEnterPreview()
- tick(100)
- component.mouseLeavePreview()
- tick(600)
- expect(component.popover.isOpen()).toBeFalsy()
- }))
})
diff --git a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts
index 5cd583fb0..7397159af 100644
--- a/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts
+++ b/src-ui/src/app/components/document-list/document-card-small/document-card-small.component.ts
@@ -13,9 +13,9 @@ import {
} from 'src/app/data/document'
import { DocumentService } from 'src/app/services/rest/document.service'
import { SettingsService } from 'src/app/services/settings.service'
-import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
+import { PreviewPopupComponent } from '../../common/preview-popup/preview-popup.component'
@Component({
selector: 'pngx-document-card-small',
@@ -61,10 +61,7 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
moreTags: number = null
- @ViewChild('popover') popover: NgbPopover
-
- mouseOnPreview = false
- popoverHidden = true
+ @ViewChild('popupPreview') popupPreview: PreviewPopupComponent
getIsThumbInverted() {
return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
@@ -78,10 +75,6 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
return this.documentService.getDownloadUrl(this.document.id)
}
- get previewUrl() {
- return this.documentService.getPreviewUrl(this.document.id)
- }
-
get privateName() {
return $localize`Private`
}
@@ -100,29 +93,8 @@ export class DocumentCardSmallComponent extends ComponentWithPermissions {
)
}
- mouseEnterPreview() {
- 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()
+ this.popupPreview.close()
}
get notesEnabled(): boolean {
diff --git a/src-ui/src/app/components/document-list/document-list.component.html b/src-ui/src/app/components/document-list/document-list.component.html
index 4eb9d179e..ebe3536e5 100644
--- a/src-ui/src/app/components/document-list/document-list.component.html
+++ b/src-ui/src/app/components/document-list/document-list.component.html
@@ -292,7 +292,12 @@
@if (activeDisplayFields.includes(DisplayField.TITLE) || activeDisplayFields.includes(DisplayField.TAGS)) {
@if (activeDisplayFields.includes(DisplayField.TITLE)) {
- {{d.title | documentTitle}}
+
}
@if (activeDisplayFields.includes(DisplayField.TAGS)) {
@for (t of d.tags$ | async; track t) {
diff --git a/src-ui/src/styles.scss b/src-ui/src/styles.scss
index 331f6e6d8..fe1466d58 100644
--- a/src-ui/src/styles.scss
+++ b/src-ui/src/styles.scss
@@ -564,11 +564,6 @@ table.table {
}
}
-.popover-hidden .popover {
- opacity: 0;
- pointer-events: none;
-}
-
// Tour
.tour-active .popover {
min-width: 360px;
@@ -728,3 +723,27 @@ i-bs svg {
vertical-align: middle;
}
}
+
+// fixes for buttons in preview popup
+.btn-group pngx-preview-popup:not(:last-child) {
+ // Prevent double borders when buttons are next to each other
+ > .btn {
+ margin-left: calc(#{$btn-border-width} * -1);
+ }
+ > .btn {
+ @include border-end-radius(0);
+ }
+}
+.btn-group pngx-preview-popup:not(:first-child) {
+ > .btn {
+ @include border-start-radius(0);
+ }
+}
+.btn-group pngx-preview-popup {
+ position: relative;
+ flex: 1 1 auto;
+
+ > .btn {
+ display: block;
+ }
+}
diff --git a/src/documents/views.py b/src/documents/views.py
index 35fa8eafc..367559c6d 100644
--- a/src/documents/views.py
+++ b/src/documents/views.py
@@ -426,7 +426,7 @@ class DocumentViewSet(
)
def file_response(self, pk, request, disposition):
- doc = Document.objects.select_related("owner").get(id=pk)
+ doc = Document.global_objects.select_related("owner").get(id=pk)
if request.user is not None and not has_perms_owner_aware(
request.user,
"view_document",
From 961452803322e733c19173507c09eb9aa5c7c7a5 Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Fri, 29 Nov 2024 22:36:40 -0800
Subject: [PATCH 06/11] Enhancement: filterable list count sorting and
opacification (#8386)
---
src-ui/messages.xlf | 74 +++++++++----------
.../filterable-dropdown.component.html | 10 ++-
.../filterable-dropdown.component.spec.ts | 31 ++++++++
.../filterable-dropdown.component.ts | 29 +++++++-
.../toggleable-dropdown-button.component.html | 9 ++-
.../toggleable-dropdown-button.component.ts | 7 ++
6 files changed, 116 insertions(+), 44 deletions(-)
diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf
index bd8b89095..33e9aacb8 100644
--- a/src-ui/messages.xlf
+++ b/src-ui/messages.xlf
@@ -2300,7 +2300,7 @@
src/app/components/document-detail/document-detail.component.ts
- 846
+ 847
@@ -2577,19 +2577,19 @@
src/app/components/document-detail/document-detail.component.ts
- 870
+ 871
src/app/components/document-detail/document-detail.component.ts
- 1169
+ 1170
src/app/components/document-detail/document-detail.component.ts
- 1207
+ 1208
src/app/components/document-detail/document-detail.component.ts
- 1248
+ 1249
src/app/components/document-list/bulk-editor/bulk-editor.component.ts
@@ -3172,7 +3172,7 @@
src/app/components/document-detail/document-detail.component.ts
- 823
+ 824
src/app/components/document-list/bulk-editor/bulk-editor.component.ts
@@ -4901,7 +4901,7 @@
Create
src/app/components/common/filterable-dropdown/filterable-dropdown.component.html
- 50
+ 58
src/app/components/common/share-links-dropdown/share-links-dropdown.component.html
@@ -4928,21 +4928,21 @@
Apply
src/app/components/common/filterable-dropdown/filterable-dropdown.component.html
- 56
+ 64
Click again to exclude items.
src/app/components/common/filterable-dropdown/filterable-dropdown.component.html
- 63
+ 71
Not assigned
src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
- 351
+ 370
Filter drop down element to filter for documents with no correspondent/type/tag assigned
@@ -4950,7 +4950,7 @@
Open filter
src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
- 463
+ 486
@@ -6209,7 +6209,7 @@
src/app/components/document-detail/document-detail.component.ts
- 1225
+ 1226
src/app/guards/dirty-saved-view.guard.ts
@@ -6573,36 +6573,36 @@
Document saved successfully.
src/app/components/document-detail/document-detail.component.ts
- 737
+ 738
src/app/components/document-detail/document-detail.component.ts
- 751
+ 752
Error saving document
src/app/components/document-detail/document-detail.component.ts
- 755
+ 756
src/app/components/document-detail/document-detail.component.ts
- 796
+ 797
Do you really want to move the document "" to the trash?
src/app/components/document-detail/document-detail.component.ts
- 824
+ 825
Documents can be restored prior to permanent deletion.
src/app/components/document-detail/document-detail.component.ts
- 825
+ 826
src/app/components/document-list/bulk-editor/bulk-editor.component.ts
@@ -6613,7 +6613,7 @@
Move to trash
src/app/components/document-detail/document-detail.component.ts
- 827
+ 828
src/app/components/document-list/bulk-editor/bulk-editor.component.ts
@@ -6624,7 +6624,7 @@
Reprocess confirm
src/app/components/document-detail/document-detail.component.ts
- 866
+ 867
src/app/components/document-list/bulk-editor/bulk-editor.component.ts
@@ -6635,70 +6635,70 @@
This operation will permanently recreate the archive file for this document.
src/app/components/document-detail/document-detail.component.ts
- 867
+ 868
The archive file will be re-generated with the current settings.
src/app/components/document-detail/document-detail.component.ts
- 868
+ 869
Reprocess operation will begin in the background. Close and re-open or reload this document after the operation has completed to see new content.
src/app/components/document-detail/document-detail.component.ts
- 878
+ 879
Error executing operation
src/app/components/document-detail/document-detail.component.ts
- 889
+ 890
Page Fit
src/app/components/document-detail/document-detail.component.ts
- 962
+ 963
Split confirm
src/app/components/document-detail/document-detail.component.ts
- 1167
+ 1168
This operation will split the selected document(s) into new documents.
src/app/components/document-detail/document-detail.component.ts
- 1168
+ 1169
Split operation will begin in the background.
src/app/components/document-detail/document-detail.component.ts
- 1184
+ 1185
Error executing split operation
src/app/components/document-detail/document-detail.component.ts
- 1193
+ 1194
Rotate confirm
src/app/components/document-detail/document-detail.component.ts
- 1205
+ 1206
src/app/components/document-list/bulk-editor/bulk-editor.component.ts
@@ -6709,49 +6709,49 @@
This operation will permanently rotate the original version of the current document.
src/app/components/document-detail/document-detail.component.ts
- 1206
+ 1207
Rotation will begin in the background. Close and re-open the document after the operation has completed to see the changes.
src/app/components/document-detail/document-detail.component.ts
- 1222
+ 1223
Error executing rotate operation
src/app/components/document-detail/document-detail.component.ts
- 1234
+ 1235
Delete pages confirm
src/app/components/document-detail/document-detail.component.ts
- 1246
+ 1247
This operation will permanently delete the selected pages from the original document.
src/app/components/document-detail/document-detail.component.ts
- 1247
+ 1248
Delete pages operation will begin in the background. Close and re-open or reload this document after the operation has completed to see the changes.
src/app/components/document-detail/document-detail.component.ts
- 1262
+ 1263
Error executing delete pages operation
src/app/components/document-detail/document-detail.component.ts
- 1271
+ 1272
diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html
index a3b49cf62..28ce03ad6 100644
--- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html
+++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.html
@@ -38,7 +38,15 @@
@for (item of selectionModel.items | filter: filterText:'name'; track item; let i = $index) {
@if (allowSelectNone || item.id) {
+ [item]="item"
+ [hideCount]="hideCount(item)"
+ [opacifyCount]="!editing"
+ [state]="selectionModel.get(item.id)"
+ [count]="getUpdatedDocumentCount(item.id)"
+ (toggled)="selectionModel.toggle(item.id)"
+ (exclude)="excludeClicked(item.id)"
+ (click)="setButtonItemIndex(i - 1)"
+ [disabled]="disabled">
}
}
diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.spec.ts b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.spec.ts
index 0e2999742..78af75607 100644
--- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.spec.ts
+++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.spec.ts
@@ -509,6 +509,37 @@ describe('FilterableDropdownComponent & FilterableDropdownSelectionModel', () =>
])
})
+ it('selection model should sort items by state and document counts, if set', () => {
+ component.items = items.concat([{ id: 4, name: 'Item D' }])
+ component.selectionModel = selectionModel
+ component.documentCounts = [
+ { id: 1, document_count: 0 }, // Tag1
+ { id: 2, document_count: 1 }, // Tag2
+ { id: 4, document_count: 2 },
+ ]
+ component.selectionModel.apply()
+ expect(selectionModel.items).toEqual([
+ nullItem,
+ { id: 4, name: 'Item D' },
+ items[1], // Tag2
+ items[0], // Tag1
+ ])
+
+ selectionModel.toggle(items[1].id)
+ component.documentCounts = [
+ { id: 1, document_count: 0 },
+ { id: 2, document_count: 1 },
+ { id: 4, document_count: 0 },
+ ]
+ selectionModel.apply()
+ expect(selectionModel.items).toEqual([
+ nullItem,
+ items[1], // Tag2
+ { id: 4, name: 'Item D' },
+ items[0], // Tag1
+ ])
+ })
+
it('should set support create, keep open model and call createRef method', fakeAsync(() => {
component.items = items
component.icon = 'tag-fill'
diff --git a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
index a23d413d7..2351dc0da 100644
--- a/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
+++ b/src-ui/src/app/components/common/filterable-dropdown/filterable-dropdown.component.ts
@@ -43,6 +43,11 @@ export class FilterableDropdownSelectionModel {
private _intersection: Intersection = Intersection.Include
temporaryIntersection: Intersection = this._intersection
+ private _documentCounts: SelectionDataItem[] = []
+ public set documentCounts(counts: SelectionDataItem[]) {
+ this._documentCounts = counts
+ }
+
private _items: MatchingModel[] = []
get items(): MatchingModel[] {
return this._items
@@ -69,6 +74,16 @@ export class FilterableDropdownSelectionModel {
this.getNonTemporary(b.id) == ToggleableItemState.NotSelected
) {
return -1
+ } else if (
+ this._documentCounts.length &&
+ this.getDocumentCount(a.id) > this.getDocumentCount(b.id)
+ ) {
+ return -1
+ } else if (
+ this._documentCounts.length &&
+ this.getDocumentCount(a.id) < this.getDocumentCount(b.id)
+ ) {
+ return 1
} else {
return a.name.localeCompare(b.name)
}
@@ -286,6 +301,10 @@ export class FilterableDropdownSelectionModel {
)
}
+ getDocumentCount(id: number) {
+ return this._documentCounts.find((c) => c.id === id)?.document_count
+ }
+
init(map: Map) {
this.temporarySelectionStates = map
this.apply()
@@ -431,7 +450,11 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit {
}
@Input()
- documentCounts: SelectionDataItem[]
+ set documentCounts(counts: SelectionDataItem[]) {
+ if (counts) {
+ this.selectionModel.documentCounts = counts
+ }
+ }
@Input()
shortcutKey: string
@@ -544,9 +567,7 @@ export class FilterableDropdownComponent implements OnDestroy, OnInit {
}
getUpdatedDocumentCount(id: number) {
- if (this.documentCounts) {
- return this.documentCounts.find((c) => c.id === id)?.document_count
- }
+ return this.selectionModel.getDocumentCount(id)
}
listKeyDown(event: KeyboardEvent) {
diff --git a/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html b/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html
index 348393ced..1c7dad499 100644
--- a/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html
+++ b/src-ui/src/app/components/common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component.html
@@ -1,4 +1,9 @@
-
@@ -6209,7 +6209,7 @@
src/app/components/document-detail/document-detail.component.ts
- 1226
+ 1228
src/app/guards/dirty-saved-view.guard.ts
@@ -6670,88 +6670,88 @@
Split confirm
src/app/components/document-detail/document-detail.component.ts
- 1168
+ 1169
This operation will split the selected document(s) into new documents.
src/app/components/document-detail/document-detail.component.ts
- 1169
+ 1170
Split operation will begin in the background.
src/app/components/document-detail/document-detail.component.ts
- 1185
+ 1186
Error executing split operation
src/app/components/document-detail/document-detail.component.ts
- 1194
+ 1195
Rotate confirm
src/app/components/document-detail/document-detail.component.ts
- 1206
+ 1208
src/app/components/document-list/bulk-editor/bulk-editor.component.ts
- 787
+ 788
This operation will permanently rotate the original version of the current document.
src/app/components/document-detail/document-detail.component.ts
- 1207
+ 1209
Rotation will begin in the background. Close and re-open the document after the operation has completed to see the changes.
src/app/components/document-detail/document-detail.component.ts
- 1223
+ 1225
Error executing rotate operation
src/app/components/document-detail/document-detail.component.ts
- 1235
+ 1237
Delete pages confirm
src/app/components/document-detail/document-detail.component.ts
- 1247
+ 1249
This operation will permanently delete the selected pages from the original document.
src/app/components/document-detail/document-detail.component.ts
- 1248
+ 1250
Delete pages operation will begin in the background. Close and re-open or reload this document after the operation has completed to see the changes.
src/app/components/document-detail/document-detail.component.ts
- 1263
+ 1265
Error executing delete pages operation
src/app/components/document-detail/document-detail.component.ts
- 1272
+ 1274
@@ -7096,13 +7096,6 @@
This operation will permanently rotate the original version of document(s).
-
- src/app/components/document-list/bulk-editor/bulk-editor.component.ts
- 788
-
-
-
- This will alter the original copy.
src/app/components/document-list/bulk-editor/bulk-editor.component.ts
789
diff --git a/src-ui/src/app/components/common/confirm-dialog/delete-pages-confirm-dialog/delete-pages-confirm-dialog.component.scss b/src-ui/src/app/components/common/confirm-dialog/delete-pages-confirm-dialog/delete-pages-confirm-dialog.component.scss
index f74de973d..4ddd79bfa 100644
--- a/src-ui/src/app/components/common/confirm-dialog/delete-pages-confirm-dialog/delete-pages-confirm-dialog.component.scss
+++ b/src-ui/src/app/components/common/confirm-dialog/delete-pages-confirm-dialog/delete-pages-confirm-dialog.component.scss
@@ -1,6 +1,6 @@
.pdf-viewer-container {
background-color: gray;
- height: 350px;
+ height: 550px;
pdf-viewer {
width: 100%;
diff --git a/src-ui/src/app/components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component.html b/src-ui/src/app/components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component.html
index 7fb68218a..47e4c137c 100644
--- a/src-ui/src/app/components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component.html
+++ b/src-ui/src/app/components/common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component.html
@@ -6,7 +6,7 @@
{{message}}
-
+
-
+
-
+
+
-
}
+ @case (ContentRenderType.TIFF) {
+ @if (!tiffError) {
+
+ ![{{title}}]()
+
+ } @else {
+ {{tiffError}}
+ }
+ }
@case (ContentRenderType.Other) {
}
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.scss b/src-ui/src/app/components/document-detail/document-detail.component.scss
index f61e20e83..e3d17476b 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.scss
+++ b/src-ui/src/app/components/document-detail/document-detail.component.scss
@@ -61,6 +61,7 @@ textarea.rtl {
width: 100%;
height: 100%;
object-fit: contain;
+ object-position: top;
}
.thumb-preview {
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts
index 41a576f01..46b72cb4e 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts
+++ b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts
@@ -1270,4 +1270,46 @@ describe('DocumentDetailComponent', () => {
expect(component.createDisabled(DataType.StoragePath)).toBeFalsy()
expect(component.createDisabled(DataType.Tag)).toBeFalsy()
})
+
+ it('should call tryRenderTiff when no archive and file is tiff', () => {
+ initNormally()
+ const tiffRenderSpy = jest.spyOn(
+ DocumentDetailComponent.prototype as any,
+ 'tryRenderTiff'
+ )
+ const doc = Object.assign({}, component.document)
+ doc.archived_file_name = null
+ doc.mime_type = 'image/tiff'
+ jest
+ .spyOn(documentService, 'getMetadata')
+ .mockReturnValue(
+ of({ has_archive_version: false, original_mime_type: 'image/tiff' })
+ )
+ component.updateComponent(doc)
+ fixture.detectChanges()
+ expect(component.archiveContentRenderType).toEqual(
+ component.ContentRenderType.TIFF
+ )
+ expect(tiffRenderSpy).toHaveBeenCalled()
+ })
+
+ it('should try to render tiff and show error if failed', () => {
+ initNormally()
+ // just the text request
+ httpTestingController.expectOne(component.previewUrl)
+
+ // invalid tiff
+ component['tryRenderTiff']()
+ httpTestingController
+ .expectOne(component.previewUrl)
+ .flush(new ArrayBuffer(100)) // arraybuffer
+ expect(component.tiffError).not.toBeUndefined()
+
+ // http error
+ component['tryRenderTiff']()
+ httpTestingController
+ .expectOne(component.previewUrl)
+ .error(new ErrorEvent('failed'))
+ expect(component.tiffError).not.toBeUndefined()
+ })
})
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts
index 2842509fc..f1afd95c0 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.ts
+++ b/src-ui/src/app/components/document-detail/document-detail.component.ts
@@ -72,6 +72,7 @@ import { DeletePagesConfirmDialogComponent } from '../common/confirm-dialog/dele
import { HotKeyService } from 'src/app/services/hot-key.service'
import { PDFDocumentProxy } from 'ng2-pdf-viewer'
import { DataType } from 'src/app/data/datatype'
+import * as UTIF from 'utif'
enum DocumentDetailNavIDs {
Details = 1,
@@ -89,6 +90,7 @@ enum ContentRenderType {
Text = 'text',
Other = 'other',
Unknown = 'unknown',
+ TIFF = 'tiff',
}
enum ZoomSetting {
@@ -136,6 +138,8 @@ export class DocumentDetailComponent
downloadUrl: string
downloadOriginalUrl: string
previewLoaded: boolean = false
+ tiffURL: string
+ tiffError: string
correspondents: Correspondent[]
documentTypes: DocumentType[]
@@ -244,6 +248,8 @@ export class DocumentDetailComponent
['text/plain', 'application/csv', 'text/csv'].includes(mimeType)
) {
return ContentRenderType.Text
+ } else if (mimeType.indexOf('tiff') >= 0) {
+ return ContentRenderType.TIFF
} else if (mimeType?.indexOf('image/') === 0) {
return ContentRenderType.Image
}
@@ -542,6 +548,9 @@ export class DocumentDetailComponent
this.document = doc
this.requiresPassword = false
this.updateFormForCustomFields()
+ if (this.archiveContentRenderType === ContentRenderType.TIFF) {
+ this.tryRenderTiff()
+ }
this.documentsService
.getMetadata(doc.id)
.pipe(
@@ -1278,4 +1287,45 @@ export class DocumentDetailComponent
})
})
}
+
+ private tryRenderTiff() {
+ this.http.get(this.previewUrl, { responseType: 'arraybuffer' }).subscribe({
+ next: (res) => {
+ /* istanbul ignore next */
+ try {
+ // See UTIF.js > _imgLoaded
+ const tiffIfds: any[] = UTIF.decode(res)
+ var vsns = tiffIfds,
+ ma = 0,
+ page = vsns[0]
+ if (tiffIfds[0].subIFD) vsns = vsns.concat(tiffIfds[0].subIFD)
+ for (var i = 0; i < vsns.length; i++) {
+ var img = vsns[i]
+ if (img['t258'] == null || img['t258'].length < 3) continue
+ var ar = img['t256'] * img['t257']
+ if (ar > ma) {
+ ma = ar
+ page = img
+ }
+ }
+ UTIF.decodeImage(res, page, tiffIfds)
+ const rgba = UTIF.toRGBA8(page)
+ const { width: w, height: h } = page
+ var cnv = document.createElement('canvas')
+ cnv.width = w
+ cnv.height = h
+ var ctx = cnv.getContext('2d'),
+ imgd = ctx.createImageData(w, h)
+ for (var i = 0; i < rgba.length; i++) imgd.data[i] = rgba[i]
+ ctx.putImageData(imgd, 0, 0)
+ this.tiffURL = cnv.toDataURL()
+ } catch (err) {
+ this.tiffError = $localize`An error occurred loading tiff: ${err.toString()}`
+ }
+ },
+ error: (err) => {
+ this.tiffError = $localize`An error occurred loading tiff: ${err.toString()}`
+ },
+ })
+ }
}
| |