mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-11-03 03:16:10 -06:00 
			
		
		
		
	Unified toolbar w select, hover buttons
This commit is contained in:
		@@ -4,15 +4,37 @@
 | 
			
		||||
  <button type="button" class="btn-close" aria-label="Close" (click)="cancel()"></button>
 | 
			
		||||
</div>
 | 
			
		||||
<div class="modal-body">
 | 
			
		||||
  <div class="btn-group toolbar mb-2">
 | 
			
		||||
    <button class="btn btn-sm btn-secondary" (click)="rotateSelected(-90)" [disabled]="!hasSelection()">
 | 
			
		||||
      <i-bs name="arrow-counterclockwise"></i-bs>
 | 
			
		||||
    </button>
 | 
			
		||||
    <button class="btn btn-sm btn-secondary" (click)="rotateSelected(90)" [disabled]="!hasSelection()">
 | 
			
		||||
      <i-bs name="arrow-clockwise"></i-bs>
 | 
			
		||||
    </button>
 | 
			
		||||
    <button class="btn btn-sm btn-danger" (click)="deleteSelected()" [disabled]="!hasSelection()">
 | 
			
		||||
      <i-bs name="trash"></i-bs>
 | 
			
		||||
    </button>
 | 
			
		||||
  </div>
 | 
			
		||||
  <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 class="page-item rounded p-2" cdkDrag (click)="toggleSelection(i)" [class.selected]="p.selected">
 | 
			
		||||
        <div class="btn-toolbar hover-actions z-10">
 | 
			
		||||
          <div class="btn-group">
 | 
			
		||||
            <button class="btn btn-sm btn-dark text-danger" (click)="remove(i); $event.stopPropagation()">
 | 
			
		||||
              <i-bs name="trash"></i-bs>
 | 
			
		||||
            </button>
 | 
			
		||||
            <button class="btn btn-sm btn-dark" (click)="toggleSplit(i); $event.stopPropagation()">
 | 
			
		||||
              <i-bs name="scissors"></i-bs>
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="pdf-viewer-container w-100 mt-3">
 | 
			
		||||
        <div class="border-end border-bottom bg-light py-1 px-2 document-check z-10">
 | 
			
		||||
          <div class="form-check">
 | 
			
		||||
            <input type="checkbox" class="form-check-input" id="page{{i}}"  [checked]="p.selected" (click)="toggleSelection(i); $event.stopPropagation()">
 | 
			
		||||
            <label class="form-check-label" for="page{{i}}"></label>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="pdf-viewer-container w-100" [class.selected]="p.selected">
 | 
			
		||||
          <pdf-viewer [src]="pdfSrc" [page]="p.page" [rotation]="p.rotate" [original-size]="false" [show-all]="false" [render-text]="false"></pdf-viewer>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,62 @@
 | 
			
		||||
.pdf-viewer-container {
 | 
			
		||||
    background-color: gray;
 | 
			
		||||
    height: 120px;
 | 
			
		||||
 | 
			
		||||
    pdf-viewer {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
 | 
			
		||||
.page-item {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  border: 1px solid transparent;
 | 
			
		||||
  background-origin: border-box;
 | 
			
		||||
 | 
			
		||||
  &.selected {
 | 
			
		||||
    background-color: var(--pngx-primary-darken-5);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pdf-viewer-container {
 | 
			
		||||
  background-color: gray;
 | 
			
		||||
  height: 200px;
 | 
			
		||||
 | 
			
		||||
  pdf-viewer {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.hover-actions {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.page-item:hover .hover-actions {
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.document-check {
 | 
			
		||||
  display: none;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  padding: 0.5rem;
 | 
			
		||||
  border-top-left-radius: 0.25rem;
 | 
			
		||||
  border-bottom-right-radius: 0.25rem;
 | 
			
		||||
  pointer-events: none;
 | 
			
		||||
 | 
			
		||||
  .form-check {
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    min-height: 0;
 | 
			
		||||
    margin-bottom: 0;
 | 
			
		||||
 | 
			
		||||
    .form-check-input {
 | 
			
		||||
      margin-left: 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.page-item:hover .document-check, .selected .document-check {
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.z-10 {
 | 
			
		||||
    z-index: 10;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,13 +23,19 @@ describe('PDFEditorComponent', () => {
 | 
			
		||||
    fixture.detectChanges()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('should rotate and reorder pages', () => {
 | 
			
		||||
  it('should rotate, delete and reorder pages', () => {
 | 
			
		||||
    component.pages = [
 | 
			
		||||
      { page: 1, rotate: 0, splitAfter: false },
 | 
			
		||||
      { page: 2, rotate: 0, splitAfter: false },
 | 
			
		||||
      { page: 1, rotate: 0, splitAfter: false, selected: false },
 | 
			
		||||
      { page: 2, rotate: 0, splitAfter: false, selected: false },
 | 
			
		||||
    ]
 | 
			
		||||
    component.rotate(0)
 | 
			
		||||
    component.toggleSelection(0)
 | 
			
		||||
    component.rotateSelected(90)
 | 
			
		||||
    expect(component.pages[0].rotate).toBe(90)
 | 
			
		||||
    component.toggleSelection(0) // deselect
 | 
			
		||||
    component.toggleSelection(1)
 | 
			
		||||
    component.deleteSelected()
 | 
			
		||||
    expect(component.pages.length).toBe(1)
 | 
			
		||||
    component.pages.push({ page: 2, rotate: 0, splitAfter: false })
 | 
			
		||||
    component.drop({ previousIndex: 0, currentIndex: 1 } as any)
 | 
			
		||||
    expect(component.pages[0].page).toBe(2)
 | 
			
		||||
  })
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import {
 | 
			
		||||
  DragDropModule,
 | 
			
		||||
  moveItemInArray,
 | 
			
		||||
} from '@angular/cdk/drag-drop'
 | 
			
		||||
import { CommonModule } from '@angular/common'
 | 
			
		||||
import { Component, OnInit, inject } from '@angular/core'
 | 
			
		||||
import { FormsModule } from '@angular/forms'
 | 
			
		||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
 | 
			
		||||
@@ -15,6 +16,7 @@ interface PageOperation {
 | 
			
		||||
  page: number
 | 
			
		||||
  rotate: number
 | 
			
		||||
  splitAfter: boolean
 | 
			
		||||
  selected?: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
@@ -22,6 +24,7 @@ interface PageOperation {
 | 
			
		||||
  templateUrl: './pdf-editor.component.html',
 | 
			
		||||
  styleUrl: './pdf-editor.component.scss',
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
    DragDropModule,
 | 
			
		||||
    FormsModule,
 | 
			
		||||
    PdfViewerModule,
 | 
			
		||||
@@ -52,11 +55,16 @@ export class PDFEditorComponent
 | 
			
		||||
      page: i + 1,
 | 
			
		||||
      rotate: 0,
 | 
			
		||||
      splitAfter: false,
 | 
			
		||||
      selected: false,
 | 
			
		||||
    }))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  rotate(i: number) {
 | 
			
		||||
    this.pages[i].rotate = (this.pages[i].rotate + 90) % 360
 | 
			
		||||
  rotateSelected(dir: number) {
 | 
			
		||||
    for (let p of this.pages) {
 | 
			
		||||
      if (p.selected) {
 | 
			
		||||
        p.rotate = (p.rotate + dir + 360) % 360
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  remove(i: number) {
 | 
			
		||||
@@ -67,6 +75,18 @@ export class PDFEditorComponent
 | 
			
		||||
    this.pages[i].splitAfter = !this.pages[i].splitAfter
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleSelection(i: number) {
 | 
			
		||||
    this.pages[i].selected = !this.pages[i].selected
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  deleteSelected() {
 | 
			
		||||
    this.pages = this.pages.filter((p) => !p.selected)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  hasSelection(): boolean {
 | 
			
		||||
    return this.pages.some((p) => p.selected)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  drop(event: CdkDragDrop<PageOperation[]>) {
 | 
			
		||||
    moveItemInArray(this.pages, event.previousIndex, event.currentIndex)
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user