Skip to content
This repository has been archived by the owner on Feb 16, 2023. It is now read-only.

Feature: merge tool UI #789

Merged
merged 63 commits into from
Apr 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
eb7b767
Basic button for initiating split/merge from bulk editor
shamoon Mar 14, 2021
321fad0
Basic drag-n-drop
shamoon Mar 14, 2021
9e6cb1b
Basic layout elements, loading spinner
shamoon Mar 14, 2021
6a3ad77
Skeleton UI for buttons
shamoon Mar 14, 2021
8e330fc
Basic live preview
shamoon Mar 14, 2021
58cb42b
Typescript
shamoon Mar 14, 2021
7746ab4
Basic document chooser
shamoon Mar 15, 2021
87d969f
Basic action buttons
shamoon Mar 15, 2021
4a61608
Naming
shamoon Mar 15, 2021
274df28
Spacer styling
shamoon Mar 15, 2021
7cbb505
Basic duplicate & delete
shamoon Mar 15, 2021
8d0ac69
Basic page chooser component
shamoon Mar 16, 2021
ea34283
Basic mode toggling
shamoon Mar 16, 2021
bb872ab
Tooltip, random styling stuff
shamoon Mar 16, 2021
ce19525
Change icons
shamoon Mar 17, 2021
86ca379
Basic page support
shamoon Mar 17, 2021
37c0fa4
Merge branch 'feature-merge-tool' into feature/merge-tool-ui
shamoon Mar 18, 2021
b8fabb6
InputDebounce component for page fields
shamoon Mar 21, 2021
33ddd03
Allow additional classes for input-debounce & fix field display
shamoon Mar 21, 2021
35f9989
Allow input-debounce input
shamoon Mar 21, 2021
73c80c3
Page field formatting
shamoon Mar 21, 2021
d88cd05
Allow empty pages field
shamoon Mar 21, 2021
baae3c2
Allow removing last document
shamoon Mar 21, 2021
746c03c
Remove 'mode' concept, basic split UI, many visual changes
shamoon Mar 23, 2021
cfc0925
Split support including tabbed previews
shamoon Mar 23, 2021
af1034f
Visual reorganization
shamoon Mar 23, 2021
3c25595
Separator styling
shamoon Mar 23, 2021
20b95fb
Disable page chooser button if no page chosen
shamoon Mar 23, 2021
14116c8
Hide preview after all documents removed
shamoon Mar 23, 2021
24d07fc
Missing handle
shamoon Mar 23, 2021
38d8b34
Clear pages button
shamoon Mar 23, 2021
7df9e1f
Translation stuff
shamoon Mar 23, 2021
cf5747f
Toggle for source document handling
shamoon Mar 24, 2021
aaa913f
Options popover
shamoon Mar 24, 2021
e0a1ba5
Typescript fixes
shamoon Mar 25, 2021
63b4c33
Metadata options & source options merged into options popover panel
shamoon Mar 25, 2021
a5de83f
Better clear button
shamoon Mar 25, 2021
88bf679
Unified button toolbar
shamoon Mar 25, 2021
142c2ac
Sanitize pages input
shamoon Mar 25, 2021
c07bbcb
No return type on setter
shamoon Mar 25, 2021
c18180f
Remove debug stuff
shamoon Mar 25, 2021
4ddd031
Slightly increasing button spacing on cards
shamoon Mar 25, 2021
c5ae337
Basic error handling
shamoon Mar 25, 2021
9713923
Dont allow separator at the end of document parts list
shamoon Mar 25, 2021
66aee14
Light mode fixes, styling tweaks
shamoon Mar 25, 2021
484edce
Split button should respect pre-selected pages
shamoon Mar 25, 2021
cc3c7ad
Apply danger on delete toggle to entire label
shamoon Mar 25, 2021
ae60fc8
Fix popover shadow
shamoon Mar 25, 2021
7203182
Preview page numbers
shamoon Mar 25, 2021
e957d9a
Changes missed from last commit, enables page numbers in previews
shamoon Mar 30, 2021
b4db388
Dont initialize page number until page is rendered
shamoon Mar 31, 2021
e0231a4
Dont allow separator at start of document
shamoon Mar 31, 2021
846a997
Better button responsive layout
shamoon Mar 31, 2021
b07537a
Merge branch 'feature-merge-tool' into feature/merge-tool-ui
shamoon Apr 2, 2021
caa9a34
Increase height of dropzone
shamoon Apr 2, 2021
e328c43
Change to 5/7 column width layout
shamoon Apr 2, 2021
f07fb6e
Fix tabs in light mode
shamoon Apr 2, 2021
3915149
Remove separators from first / last position when dragged
shamoon Apr 2, 2021
6f3c72e
Fix options text color not visible in light mode
shamoon Apr 2, 2021
9502917
Fix overhang of separator background
shamoon Apr 2, 2021
2b4a0ef
Move add to split merge directly into bulk editor
shamoon Apr 2, 2021
cc5b7eb
Center preview title when empty
shamoon Apr 2, 2021
99bde7b
Dont import entire dark mode theme just for 1 color
shamoon Apr 2, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions src-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"ng2-pdf-viewer": "^6.3.2",
"ngx-color": "^6.2.0",
"ngx-cookie-service": "^10.1.1",
"ngx-drag-drop": "^2.0.0",
"ngx-file-drop": "^10.0.0",
"ngx-infinite-scroll": "^9.1.0",
"rxjs": "~6.6.0",
Expand Down
12 changes: 10 additions & 2 deletions src-ui/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { DocumentCardSmallComponent } from './components/document-list/document-
import { BulkEditorComponent } from './components/document-list/bulk-editor/bulk-editor.component';
import { NgxFileDropModule } from 'ngx-file-drop';
import { TextComponent } from './components/common/input/text/text.component';
import { InputDebounceComponent } from './components/common/input/debounce/input-debounce.component';
import { SelectComponent } from './components/common/input/select/select.component';
import { CheckComponent } from './components/common/input/check/check.component';
import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component';
Expand Down Expand Up @@ -66,6 +67,9 @@ import { ApiVersionInterceptor } from './interceptors/api-version.interceptor';
import { ColorSliderModule } from 'ngx-color/slider';
import { ColorComponent } from './components/common/input/color/color.component';
import { SplitMergeComponent } from './components/split-merge/split-merge.component';
import { DndModule } from 'ngx-drag-drop';
import { DocumentChooserComponent } from './components/common/document-chooser/document-chooser.component';
import { PageChooserComponent } from './components/common/page-chooser/page-chooser.component';

import localeFr from '@angular/common/locales/fr';
import localeNl from '@angular/common/locales/nl';
Expand Down Expand Up @@ -120,6 +124,7 @@ registerLocaleData(localeEs)
DocumentCardSmallComponent,
BulkEditorComponent,
TextComponent,
InputDebounceComponent,
SelectComponent,
CheckComponent,
SaveViewConfigDialogComponent,
Expand All @@ -142,7 +147,9 @@ registerLocaleData(localeEs)
DateComponent,
ColorComponent,
DocumentAsnComponent,
SplitMergeComponent
SplitMergeComponent,
DocumentChooserComponent,
PageChooserComponent
],
imports: [
BrowserModule,
Expand All @@ -155,7 +162,8 @@ registerLocaleData(localeEs)
InfiniteScrollModule,
PdfViewerModule,
NgSelectModule,
ColorSliderModule
ColorSliderModule,
DndModule
],
providers: [
DatePipe,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title" i18n>Choose Documents</h4>
<button type="button" class="close" aria-label="Close" (click)="cancelClicked()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="m-n2 row row-cols-paperless-cards">
<app-document-card-small [simpleCard]="true" [selected]="list.isSelected(d)" (toggleSelected)="toggleSelected(d, $event)" [document]="d" *ngFor="let d of list.documents"></app-document-card-small>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably do a separate selection model for the document chooser and not interfere with the list view at all.

This is also not very difficult, since there's no pagination / hidden items / filtering happenind in the chooser popup.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, so to accomplish this would you basically copy the logic from the list view service e.g. toggleSelected, selectRangeTo and others directly into the chooser component? Just want to make sure I understand. That would mean the code essentially duplicated in two places but yes, would no longer mess with the main list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or another service?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, range selection.

I'll take care of that.

</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-danger" (click)="cancelClicked()" i18n>Cancel</button>
<button type="button" class="btn btn-outline-primary" [disabled]="list.selected.size == 0" (click)="confirmClicked.emit()" i18n>Add Selected</button>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@import "/src/theme";

.modal-body {
max-height: 70vh;
overflow-y: scroll;
}

$paperless-card-breakpoints: (
0: 2, // xs
768px: 3, //md
992px: 4, //lg
1200px: 5, //xl
1400px: 6, // xxl
1600px: 7,
1800px: 8,
2000px: 9
);

.row-cols-paperless-cards {
@each $width, $n_cols in $paperless-card-breakpoints {
@media(min-width: $width) {
> * {
flex: 0 0 auto;
width: 100% / $n_cols;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { DocumentChooserComponent } from './document-chooser.component';

describe('DocumentChooserComponent', () => {
let component: DocumentChooserComponent;
let fixture: ComponentFixture<DocumentChooserComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DocumentChooserComponent ]
})
.compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(DocumentChooserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { PaperlessDocument } from 'src/app/data/paperless-document';
import { DocumentListViewService } from 'src/app/services/document-list-view.service';

@Component({
selector: 'app-document-chooser',
templateUrl: './document-chooser.component.html',
styleUrls: ['./document-chooser.component.scss']
})
export class DocumentChooserComponent implements OnInit {

constructor(
public activeModal: NgbActiveModal,
public list: DocumentListViewService
) { }

@Output()
public confirmClicked = new EventEmitter()

ngOnInit(): void {
this.list.selectNone()
this.list.activateSavedView(null)
this.list.reload()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to do all this here? I've removed the init entirely and it feels quite natural.

}

cancelClicked() {
this.list.selectNone()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as with the above, I'd not rely on the document list for selection state.

this.activeModal.close()
}

toggleSelected(d: PaperlessDocument, event: MouseEvent): void {
if (!event.shiftKey) this.list.toggleSelected(d)
else this.list.selectRangeTo(d)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<input type="text" [class]="cssClasses" [placeholder]="placeholder" [(ngModel)]="inputValue" (keyup)="keyUp($event)" />
<span *ngIf="allowClear && inputValue?.length > 0" class="clear-wrapper" title="Clear all" (click)="clear()"><span aria-hidden="true" class="clear">×</span></span>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.clear-wrapper {
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
cursor: pointer;
position: absolute;
top: 5px;
right: 5px;
user-select: none;
width: 17px;
color: #999;

.clear {
display: inline-block;
font-size: 18px;
line-height: 1;
pointer-events: none;
}

&:hover .clear {
color: #D0021B;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { InputDebounceComponent } from './text.component';

describe('InputDebounceComponent', () => {
let component: InputDebounceComponent;
let fixture: ComponentFixture<InputDebounceComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ InputDebounceComponent ]
})
.compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(InputDebounceComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Component, Input, Output, ElementRef, EventEmitter } from '@angular/core';
import { fromEvent } from 'rxjs';
import { map, debounceTime, distinctUntilChanged } from 'rxjs/operators';

@Component({
selector: 'app-input-debounce',
templateUrl: './input-debounce.component.html',
styleUrls: ['./input-debounce.component.scss']
})
export class InputDebounceComponent {

@Input()
placeholder: string

@Input()
delay: number = 500

@Input()
classes: string

@Input()
allowClear: boolean = true

@Input()
set pattern(regexStr: string) {
this._pattern = new RegExp(regexStr)
}

_pattern: RegExp

@Output()
value: EventEmitter<string> = new EventEmitter<string>()

@Input()
inputValue: string;

constructor(private elementRef: ElementRef) {
fromEvent(elementRef.nativeElement, 'keyup').pipe(
map(() => this.inputValue),
debounceTime(this.delay),
distinctUntilChanged()
).subscribe(input =>
this.value.emit(input)
);
}

keyUp(event: any) {
if (!this._pattern) return
if (event.key.length == 1 && !this._pattern.test(event.key)) { // e.g. dont do for backspace
// invalid character, prevent input
event.preventDefault()
event.stopImmediatePropagation()
this.inputValue = this.inputValue.slice(this.inputValue.length, 1)
}
}

get cssClasses(): string {
return 'form-control ' + this.classes
}

clear() {
this.inputValue = ''
this.value.emit(this.inputValue)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">{{title}}: {{document.title}}</h4>
<button type="button" class="close" aria-label="Close" (click)="cancelClicked()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<pdf-viewer class="page-chooser-viewer" [class.splitting]="splitting" [src]="previewUrl" [original-size]="false" [zoom]=".25" [autoresize]="true" [show-borders]="true" [show-all]="true" [render-text-mode]="2" (page-rendered)="afterPageRendered($event)" (after-load-complete)="pdfLoaded($event)"></pdf-viewer>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-danger" (click)="cancelClicked()" i18n>Cancel</button>
<button type="button" class="btn btn-outline-primary" (click)="confirmClicked()" [disabled]="pages.length == 0">{{title}}</button>
</div>
Loading