Just save this

[ci skip]
This commit is contained in:
shamoon
2025-07-01 11:35:08 -07:00
parent 1cd21d0f38
commit d3644463cc
11 changed files with 404 additions and 12 deletions

View File

@@ -0,0 +1,29 @@
<pdf-viewer [src]="pdfSrc" [render-text]="false" zoom="0.4" (after-load-complete)="pdfLoaded($event)"></pdf-viewer>
<div class="modal-header">
<h4 class="modal-title">{{ title }}</h4>
<button type="button" class="btn-close" aria-label="Close" (click)="cancel()"></button>
</div>
<div class="modal-body">
<div cdkDropList (cdkDropListDropped)="drop($event)" cdkDropListOrientation="mixed" class="d-flex flex-wrap row-cols-5">
@for (p of pages; track p.page; let i = $index) {
<div class="page-item p-2" cdkDrag>
<div class="btn-group mb-1">
<button class="btn btn-sm btn-secondary" (click)="rotate(i)"><i-bs name="arrow-clockwise"></i-bs></button>
<button class="btn btn-sm btn-outline-secondary" (click)="toggleSplit(i)"><i-bs name="scissors"></i-bs></button>
<button class="btn btn-sm btn-danger" (click)="remove(i)"><i-bs name="trash"></i-bs></button>
</div>
<div class="pdf-viewer-container w-100 mt-3">
<pdf-viewer [src]="pdfSrc" [page]="p.page" [rotation]="p.rotate" [original-size]="false" [show-all]="false" [render-text]="false"></pdf-viewer>
</div>
</div>
}
</div>
</div>
<div class="modal-footer">
<div class="form-check form-switch me-auto">
<input class="form-check-input" type="checkbox" id="deleteSwitch" [(ngModel)]="deleteOriginal">
<label class="form-check-label" for="deleteSwitch" i18n>Delete original after edit</label>
</div>
<button type="button" class="btn" [class]="cancelBtnClass" (click)="cancel()" [disabled]="!buttonsEnabled">{{ cancelBtnCaption }}</button>
<button type="button" class="btn" [class]="btnClass" (click)="confirm()" [disabled]="pages.length === 0">{{ btnCaption }}</button>
</div>

View File

@@ -0,0 +1,9 @@
.pdf-viewer-container {
background-color: gray;
height: 120px;
pdf-viewer {
width: 100%;
height: 100%;
}
}

View File

@@ -0,0 +1,36 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { PDFEditorComponent } from './pdf-editor.component'
describe('PDFEditorComponent', () => {
let component: PDFEditorComponent
let fixture: ComponentFixture<PDFEditorComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PDFEditorComponent, NgxBootstrapIconsModule.pick(allIcons)],
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
{ provide: NgbActiveModal, useValue: {} },
],
}).compileComponents()
fixture = TestBed.createComponent(PDFEditorComponent)
component = fixture.componentInstance
fixture.detectChanges()
})
it('should rotate and reorder pages', () => {
component.pages = [
{ page: 1, rotate: 0, splitAfter: false },
{ page: 2, rotate: 0, splitAfter: false },
]
component.rotate(0)
expect(component.pages[0].rotate).toBe(90)
component.drop({ previousIndex: 0, currentIndex: 1 } as any)
expect(component.pages[0].page).toBe(2)
})
})

View File

@@ -0,0 +1,90 @@
import {
CdkDragDrop,
DragDropModule,
moveItemInArray,
} from '@angular/cdk/drag-drop'
import { Component, OnInit, inject } from '@angular/core'
import { FormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { PDFDocumentProxy, PdfViewerModule } from 'ng2-pdf-viewer'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { DocumentService } from 'src/app/services/rest/document.service'
import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component'
interface PageOperation {
page: number
rotate: number
splitAfter: boolean
}
@Component({
selector: 'pngx-pdf-editor',
templateUrl: './pdf-editor.component.html',
styleUrl: './pdf-editor.component.scss',
imports: [
DragDropModule,
FormsModule,
PdfViewerModule,
NgxBootstrapIconsModule,
],
})
export class PDFEditorComponent
extends ConfirmDialogComponent
implements OnInit
{
private documentService = inject(DocumentService)
activeModal = inject(NgbActiveModal)
documentID: number
pages: PageOperation[] = []
totalPages = 0
deleteOriginal = false
get pdfSrc(): string {
return this.documentService.getPreviewUrl(this.documentID)
}
ngOnInit() {}
pdfLoaded(pdf: PDFDocumentProxy) {
this.totalPages = pdf.numPages
this.pages = Array.from({ length: this.totalPages }, (_, i) => ({
page: i + 1,
rotate: 0,
splitAfter: false,
}))
}
rotate(i: number) {
this.pages[i].rotate = (this.pages[i].rotate + 90) % 360
}
remove(i: number) {
this.pages.splice(i, 1)
}
toggleSplit(i: number) {
this.pages[i].splitAfter = !this.pages[i].splitAfter
}
drop(event: CdkDragDrop<PageOperation[]>) {
moveItemInArray(this.pages, event.previousIndex, event.currentIndex)
}
getOperations() {
const operations = this.pages.map((p, idx) => ({
page: p.page,
rotate: p.rotate,
doc: this.computeDocIndex(idx),
}))
return operations
}
private computeDocIndex(index: number): number {
let docIndex = 0
for (let i = 0; i <= index; i++) {
if (this.pages[i].splitAfter && i < index) docIndex++
}
return docIndex
}
}