Allow rotate from doc details page
This commit is contained in:
parent
476de1fac1
commit
c2cec70c9a
@ -461,8 +461,12 @@ Paperless-ngx added the ability to create shareable links to files in version 2.
|
|||||||
Paperless-ngx supports 3 basic editing operations for PDFs (these operations cannot be performed on non-PDF files):
|
Paperless-ngx supports 3 basic editing operations for PDFs (these operations cannot be performed on non-PDF files):
|
||||||
|
|
||||||
- Merging documents: available when selecting multiple documents for 'bulk editing'
|
- Merging documents: available when selecting multiple documents for 'bulk editing'
|
||||||
- Rotating documents: available when selecting multiple documents for 'bulk editing'. Note that rotation alters the source file.
|
- Rotating documents: available when selecting multiple documents for 'bulk editing' and from an individual document's details page.
|
||||||
- Splitting documents: available from an individual documents details page
|
- Splitting documents: available from an individual document's details page
|
||||||
|
|
||||||
|
!!! important
|
||||||
|
|
||||||
|
Note that rotation alters the Paperless-ngx original file.
|
||||||
|
|
||||||
## Best practices {#basic-searching}
|
## Best practices {#basic-searching}
|
||||||
|
|
||||||
|
@ -5,17 +5,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-3 d-flex justify-content-end">
|
<div class="col-2 d-flex justify-content-end">
|
||||||
<button class="btn btn-secondary mt-auto" (click)="rotate(false)">
|
<button class="btn btn-secondary mt-auto" (click)="rotate(false)">
|
||||||
<i-bs name="arrow-counterclockwise"></i-bs>
|
<i-bs name="arrow-counterclockwise"></i-bs>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 d-flex align-items-center">
|
<div class="col-8 d-flex align-items-center">
|
||||||
@if (documentID) {
|
@if (documentID) {
|
||||||
<img class="w-50 m-auto" [ngStyle]="{'transform': 'rotate('+rotation+'deg)'}" [src]="documentService.getThumbUrl(documentID)" />
|
<img class="w-50 m-auto" [ngStyle]="{'transform': 'rotate('+rotation+'deg)'}" [src]="documentService.getThumbUrl(documentID)" />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3 d-flex">
|
<div class="col-2 d-flex">
|
||||||
<button class="btn btn-secondary mt-auto" (click)="rotate()">
|
<button class="btn btn-secondary mt-auto" (click)="rotate()">
|
||||||
<i-bs name="arrow-clockwise"></i-bs>
|
<i-bs name="arrow-clockwise"></i-bs>
|
||||||
</button>
|
</button>
|
||||||
@ -31,13 +31,15 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="small text-muted fst-italic mt-4" i18n>Note that only PDFs will be rotated.</p>
|
@if (showPDFNote) {
|
||||||
|
<p class="small text-muted fst-italic mt-4" i18n>Note that only PDFs will be rotated.</p>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">
|
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">
|
||||||
<span class="d-inline-block" style="padding-bottom: 1px;">{{cancelBtnCaption}}</span>
|
<span class="d-inline-block" style="padding-bottom: 1px;">{{cancelBtnCaption}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled">
|
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="!confirmButtonEnabled || !buttonsEnabled || degrees === 0">
|
||||||
{{btnCaption}}
|
{{btnCaption}}
|
||||||
@if (!confirmButtonEnabled) {
|
@if (!confirmButtonEnabled) {
|
||||||
<ngb-progressbar style="height: 1px;" type="dark" [max]="secondsTotal" [value]="seconds"></ngb-progressbar>
|
<ngb-progressbar style="height: 1px;" type="dark" [max]="secondsTotal" [value]="seconds"></ngb-progressbar>
|
||||||
|
@ -10,6 +10,7 @@ import { DocumentService } from 'src/app/services/rest/document.service'
|
|||||||
})
|
})
|
||||||
export class RotateConfirmDialogComponent extends ConfirmDialogComponent {
|
export class RotateConfirmDialogComponent extends ConfirmDialogComponent {
|
||||||
public documentID: number
|
public documentID: number
|
||||||
|
public showPDFNote: boolean = true
|
||||||
|
|
||||||
// animation is better if we dont normalize yet
|
// animation is better if we dont normalize yet
|
||||||
public rotation: number = 0
|
public rotation: number = 0
|
||||||
|
@ -54,6 +54,10 @@
|
|||||||
<button ngbDropdownItem (click)="splitDocument()" [disabled]="contentRenderType !== ContentRenderType.PDF">
|
<button ngbDropdownItem (click)="splitDocument()" [disabled]="contentRenderType !== ContentRenderType.PDF">
|
||||||
<i-bs width="1em" height="1em" name="scissors"></i-bs> <span i18n>Split</span>
|
<i-bs width="1em" height="1em" name="scissors"></i-bs> <span i18n>Split</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button ngbDropdownItem (click)="rotateDocument()" [disabled]="!userIsOwner || contentRenderType !== ContentRenderType.PDF">
|
||||||
|
<i-bs name="arrow-clockwise"></i-bs> <ng-container i18n>Rotate</ng-container>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -75,6 +75,8 @@ import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service
|
|||||||
import { PdfViewerComponent } from '../common/pdf-viewer/pdf-viewer.component'
|
import { PdfViewerComponent } from '../common/pdf-viewer/pdf-viewer.component'
|
||||||
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
|
||||||
import { environment } from 'src/environments/environment'
|
import { environment } from 'src/environments/environment'
|
||||||
|
import { RotateConfirmDialogComponent } from '../common/confirm-dialog/rotate-confirm-dialog/rotate-confirm-dialog.component'
|
||||||
|
import { SplitConfirmDialogComponent } from '../common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component'
|
||||||
|
|
||||||
const doc: Document = {
|
const doc: Document = {
|
||||||
id: 3,
|
id: 3,
|
||||||
@ -171,6 +173,8 @@ describe('DocumentDetailComponent', () => {
|
|||||||
ShareLinksDropdownComponent,
|
ShareLinksDropdownComponent,
|
||||||
CustomFieldsDropdownComponent,
|
CustomFieldsDropdownComponent,
|
||||||
PdfViewerComponent,
|
PdfViewerComponent,
|
||||||
|
SplitConfirmDialogComponent,
|
||||||
|
RotateConfirmDialogComponent,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
DocumentTitlePipe,
|
DocumentTitlePipe,
|
||||||
@ -1097,6 +1101,31 @@ describe('DocumentDetailComponent', () => {
|
|||||||
req.flush(true)
|
req.flush(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should support rotate', () => {
|
||||||
|
let modal: NgbModalRef
|
||||||
|
modalService.activeInstances.subscribe((m) => (modal = m[0]))
|
||||||
|
initNormally()
|
||||||
|
component.rotateDocument()
|
||||||
|
expect(modal).not.toBeUndefined()
|
||||||
|
modal.componentInstance.documentID = doc.id
|
||||||
|
modal.componentInstance.rotate()
|
||||||
|
modal.componentInstance.confirm()
|
||||||
|
let req = httpTestingController.expectOne(
|
||||||
|
`${environment.apiBaseUrl}documents/bulk_edit/`
|
||||||
|
)
|
||||||
|
expect(req.request.body).toEqual({
|
||||||
|
documents: [doc.id],
|
||||||
|
method: 'rotate',
|
||||||
|
parameters: { degrees: 90 },
|
||||||
|
})
|
||||||
|
req.error(new ProgressEvent('failed'))
|
||||||
|
modal.componentInstance.confirm()
|
||||||
|
req = httpTestingController.expectOne(
|
||||||
|
`${environment.apiBaseUrl}documents/bulk_edit/`
|
||||||
|
)
|
||||||
|
req.flush(true)
|
||||||
|
})
|
||||||
|
|
||||||
function initNormally() {
|
function initNormally() {
|
||||||
jest
|
jest
|
||||||
.spyOn(activatedRoute, 'paramMap', 'get')
|
.spyOn(activatedRoute, 'paramMap', 'get')
|
||||||
|
@ -68,6 +68,7 @@ import { CustomFieldInstance } from 'src/app/data/custom-field-instance'
|
|||||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
import { PDFDocumentProxy } from '../common/pdf-viewer/typings'
|
import { PDFDocumentProxy } from '../common/pdf-viewer/typings'
|
||||||
import { SplitConfirmDialogComponent } from '../common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component'
|
import { SplitConfirmDialogComponent } from '../common/confirm-dialog/split-confirm-dialog/split-confirm-dialog.component'
|
||||||
|
import { RotateConfirmDialogComponent } from '../common/confirm-dialog/rotate-confirm-dialog/rotate-confirm-dialog.component'
|
||||||
|
|
||||||
enum DocumentDetailNavIDs {
|
enum DocumentDetailNavIDs {
|
||||||
Details = 1,
|
Details = 1,
|
||||||
@ -1078,4 +1079,46 @@ export class DocumentDetailComponent
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rotateDocument() {
|
||||||
|
let modal = this.modalService.open(RotateConfirmDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
})
|
||||||
|
modal.componentInstance.title = $localize`Rotate confirm`
|
||||||
|
modal.componentInstance.messageBold = $localize`This operation will permanently rotate the current document.`
|
||||||
|
modal.componentInstance.message = $localize`This will alter the original copy.`
|
||||||
|
modal.componentInstance.btnCaption = $localize`Proceed`
|
||||||
|
modal.componentInstance.documentID = this.document.id
|
||||||
|
modal.componentInstance.showPDFNote = false
|
||||||
|
modal.componentInstance.confirmClicked
|
||||||
|
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe(() => {
|
||||||
|
modal.componentInstance.buttonsEnabled = false
|
||||||
|
this.documentsService
|
||||||
|
.bulkEdit([this.document.id], 'rotate', {
|
||||||
|
degrees: modal.componentInstance.degrees,
|
||||||
|
})
|
||||||
|
.pipe(first(), takeUntil(this.unsubscribeNotifier))
|
||||||
|
.subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.toastService.show({
|
||||||
|
content: $localize`Rotation will begin in the background. Close and re-open the document after the operation has completed to see the changes.`,
|
||||||
|
delay: 8000,
|
||||||
|
action: this.close.bind(this),
|
||||||
|
actionName: $localize`Close`,
|
||||||
|
})
|
||||||
|
modal.close()
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
if (modal) {
|
||||||
|
modal.componentInstance.buttonsEnabled = true
|
||||||
|
}
|
||||||
|
this.toastService.showError(
|
||||||
|
$localize`Error executing rotate operation`,
|
||||||
|
error
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@
|
|||||||
<button ngbDropdownItem (click)="redoOcrSelected()" [disabled]="!userCanEditAll">
|
<button ngbDropdownItem (click)="redoOcrSelected()" [disabled]="!userCanEditAll">
|
||||||
<i-bs name="body-text"></i-bs> <ng-container i18n>Redo OCR</ng-container>
|
<i-bs name="body-text"></i-bs> <ng-container i18n>Redo OCR</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button ngbDropdownItem (click)="rotateSelected()" [disabled]="!userCanEditAll">
|
<button ngbDropdownItem (click)="rotateSelected()" [disabled]="!userOwnsAll">
|
||||||
<i-bs name="arrow-clockwise"></i-bs> <ng-container i18n>Rotate</ng-container>
|
<i-bs name="arrow-clockwise"></i-bs> <ng-container i18n>Rotate</ng-container>
|
||||||
</button>
|
</button>
|
||||||
<button ngbDropdownItem (click)="mergeSelected()" [disabled]="!userCanEditAll || list.selected.size < 2">
|
<button ngbDropdownItem (click)="mergeSelected()" [disabled]="!userCanEditAll || list.selected.size < 2">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user