Fix: replace drag drop & clipboard dependencies with Angular CDK (#4362)

* Swap ngx-drag-drop dependency for Angular CDK

* Swap ngx-clipboard dependency for Angular CDK
This commit is contained in:
shamoon 2023-10-16 19:46:16 -07:00 committed by GitHub
parent 27772257a8
commit fd8de5b1ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 223 additions and 161 deletions

View File

@ -464,11 +464,11 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/dashboard.component.html</context> <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.html</context>
<context context-type="linenumber">15</context> <context context-type="linenumber">14</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html</context> <context context-type="sourcefile">src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html</context>
<context context-type="linenumber">21</context> <context context-type="linenumber">14</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context> <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
@ -1482,7 +1482,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context> <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
<context context-type="linenumber">17</context> <context context-type="linenumber">13</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context> <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
@ -1706,7 +1706,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context> <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
<context context-type="linenumber">27</context> <context context-type="linenumber">23</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context> <context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
@ -2078,7 +2078,7 @@
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context> <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
<context context-type="linenumber">19</context> <context context-type="linenumber">15</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context> <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
@ -3612,35 +3612,35 @@
<source>Hello <x id="PH" equiv-text="this.settingsService.displayName"/>, welcome to Paperless-ngx</source> <source>Hello <x id="PH" equiv-text="this.settingsService.displayName"/>, welcome to Paperless-ngx</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context> <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
<context context-type="linenumber">48</context> <context context-type="linenumber">53</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="5334686081082652461" datatype="html"> <trans-unit id="5334686081082652461" datatype="html">
<source>Welcome to Paperless-ngx</source> <source>Welcome to Paperless-ngx</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context> <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
<context context-type="linenumber">50</context> <context context-type="linenumber">55</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1325877348738783391" datatype="html"> <trans-unit id="1325877348738783391" datatype="html">
<source>Dashboard updated</source> <source>Dashboard updated</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context> <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
<context context-type="linenumber">73</context> <context context-type="linenumber">86</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3214475953924351473" datatype="html"> <trans-unit id="3214475953924351473" datatype="html">
<source>Error updating dashboard</source> <source>Error updating dashboard</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context> <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
<context context-type="linenumber">76</context> <context context-type="linenumber">89</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="2946624699882754313" datatype="html"> <trans-unit id="2946624699882754313" datatype="html">
<source>Show all</source> <source>Show all</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context> <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
<context context-type="linenumber">12</context> <context context-type="linenumber">8</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context> <context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
@ -3651,7 +3651,7 @@
<source>Title</source> <source>Title</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context> <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
<context context-type="linenumber">18</context> <context context-type="linenumber">14</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
@ -3674,7 +3674,7 @@
<source>Correspondent</source> <source>Correspondent</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context> <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
<context context-type="linenumber">20</context> <context context-type="linenumber">16</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
@ -3701,14 +3701,14 @@
<source>View Preview</source> <source>View Preview</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context> <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
<context context-type="linenumber">35</context> <context context-type="linenumber">31</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="3099741642167775297" datatype="html"> <trans-unit id="3099741642167775297" datatype="html">
<source>Download</source> <source>Download</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context> <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
<context context-type="linenumber">45</context> <context context-type="linenumber">41</context>
</context-group> </context-group>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context> <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
@ -3731,7 +3731,7 @@
<source>No documents</source> <source>No documents</source>
<context-group purpose="location"> <context-group purpose="location">
<context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context> <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
<context context-type="linenumber">57</context> <context context-type="linenumber">53</context>
</context-group> </context-group>
</trans-unit> </trans-unit>
<trans-unit id="1069523139277190436" datatype="html"> <trans-unit id="1069523139277190436" datatype="html">

View File

@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@angular/cdk": "^16.2.7",
"@angular/common": "~16.2.7", "@angular/common": "~16.2.7",
"@angular/compiler": "~16.2.7", "@angular/compiler": "~16.2.7",
"@angular/core": "~16.2.7", "@angular/core": "~16.2.7",
@ -25,10 +26,8 @@
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"mime-names": "^1.0.0", "mime-names": "^1.0.0",
"ng2-pdf-viewer": "^10.0.0", "ng2-pdf-viewer": "^10.0.0",
"ngx-clipboard": "^16.0.0",
"ngx-color": "^9.0.0", "ngx-color": "^9.0.0",
"ngx-cookie-service": "^16.0.1", "ngx-cookie-service": "^16.0.1",
"ngx-drag-drop": "^16.1.0",
"ngx-file-drop": "^16.0.0", "ngx-file-drop": "^16.0.0",
"ngx-ui-tour-ng-bootstrap": "^13.0.4", "ngx-ui-tour-ng-bootstrap": "^13.0.4",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
@ -1160,6 +1159,22 @@
"typescript": "*" "typescript": "*"
} }
}, },
"node_modules/@angular/cdk": {
"version": "16.2.7",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-16.2.7.tgz",
"integrity": "sha512-LLbCu0pHHsZWGjSpQW0qRoKYRCm09TuFH2vzsViyaQF7umDKlk52QcDFB/nMioyiWPgqXkyHyGMFG1vFBNSIeg==",
"dependencies": {
"tslib": "^2.3.0"
},
"optionalDependencies": {
"parse5": "^7.1.2"
},
"peerDependencies": {
"@angular/common": "^16.0.0 || ^17.0.0",
"@angular/core": "^16.0.0 || ^17.0.0",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/cli": { "node_modules/@angular/cli": {
"version": "16.2.4", "version": "16.2.4",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.2.4.tgz", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.2.4.tgz",
@ -8918,7 +8933,7 @@
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"dev": true, "devOptional": true,
"engines": { "engines": {
"node": ">=0.12" "node": ">=0.12"
}, },
@ -14073,19 +14088,6 @@
"pdfjs-dist": "~2.16.105" "pdfjs-dist": "~2.16.105"
} }
}, },
"node_modules/ngx-clipboard": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/ngx-clipboard/-/ngx-clipboard-16.0.0.tgz",
"integrity": "sha512-rZ/Eo1PqiKMiyF8tdjhmUkoUu68f7OzBJ7YH1YFeh2RAaNrerTaW8XfFOzppSckjFQqA1fwGSYuTTJlDhDag5w==",
"dependencies": {
"ngx-window-token": ">=7.0.0",
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/common": ">=13.0.0",
"@angular/core": ">=13.0.0"
}
},
"node_modules/ngx-color": { "node_modules/ngx-color": {
"version": "9.0.0", "version": "9.0.0",
"resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-9.0.0.tgz", "resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-9.0.0.tgz",
@ -14112,18 +14114,6 @@
"@angular/core": "^16.0.0" "@angular/core": "^16.0.0"
} }
}, },
"node_modules/ngx-drag-drop": {
"version": "16.1.0",
"resolved": "https://registry.npmjs.org/ngx-drag-drop/-/ngx-drag-drop-16.1.0.tgz",
"integrity": "sha512-y2l9pJGD7OupsIRkCElN/JqTgzjg2V9ZxymKGQR7ZjjcdjaP1wKkiFWIgVEvLNtb8wgm10U+9tkGwLClGaHkQA==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/common": "^16.0.0",
"@angular/core": "^16.0.0"
}
},
"node_modules/ngx-file-drop": { "node_modules/ngx-file-drop": {
"version": "16.0.0", "version": "16.0.0",
"resolved": "https://registry.npmjs.org/ngx-file-drop/-/ngx-file-drop-16.0.0.tgz", "resolved": "https://registry.npmjs.org/ngx-file-drop/-/ngx-file-drop-16.0.0.tgz",
@ -14168,21 +14158,6 @@
"@ng-bootstrap/ng-bootstrap": "^15.0.0" "@ng-bootstrap/ng-bootstrap": "^15.0.0"
} }
}, },
"node_modules/ngx-window-token": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/ngx-window-token/-/ngx-window-token-7.0.0.tgz",
"integrity": "sha512-5+XfRVSY7Dciu8xyCNMkOlH2UfwR9W2P1Pirz7caaZgOZDjFbL8aEO2stjfJJm2FFf1D6dlVHNzhLWGk9HGkqA==",
"dependencies": {
"tslib": "^2.0.0"
},
"engines": {
"node": "^14.20.0 || ^16.13.0 || >=18.10.0"
},
"peerDependencies": {
"@angular/common": ">=13.0.0",
"@angular/core": ">=13.0.0"
}
},
"node_modules/nice-napi": { "node_modules/nice-napi": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz",
@ -15048,7 +15023,7 @@
"version": "7.1.2", "version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"entities": "^4.4.0" "entities": "^4.4.0"
}, },

View File

@ -11,6 +11,7 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/cdk": "^16.2.7",
"@angular/common": "~16.2.7", "@angular/common": "~16.2.7",
"@angular/compiler": "~16.2.7", "@angular/compiler": "~16.2.7",
"@angular/core": "~16.2.7", "@angular/core": "~16.2.7",
@ -27,10 +28,8 @@
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"mime-names": "^1.0.0", "mime-names": "^1.0.0",
"ng2-pdf-viewer": "^10.0.0", "ng2-pdf-viewer": "^10.0.0",
"ngx-clipboard": "^16.0.0",
"ngx-color": "^9.0.0", "ngx-color": "^9.0.0",
"ngx-cookie-service": "^16.0.1", "ngx-cookie-service": "^16.0.1",
"ngx-drag-drop": "^16.1.0",
"ngx-file-drop": "^16.0.0", "ngx-file-drop": "^16.0.0",
"ngx-ui-tour-ng-bootstrap": "^13.0.4", "ngx-ui-tour-ng-bootstrap": "^13.0.4",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",

View File

@ -99,7 +99,7 @@ import { ConsumptionTemplatesComponent } from './components/manage/consumption-t
import { ConsumptionTemplateEditDialogComponent } from './components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component' import { ConsumptionTemplateEditDialogComponent } from './components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component'
import { MailComponent } from './components/manage/mail/mail.component' import { MailComponent } from './components/manage/mail/mail.component'
import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component' import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component'
import { DndModule } from 'ngx-drag-drop' import { DragDropModule } from '@angular/cdk/drag-drop'
import { FileDropComponent } from './components/file-drop/file-drop.component' import { FileDropComponent } from './components/file-drop/file-drop.component'
import localeAf from '@angular/common/locales/af' import localeAf from '@angular/common/locales/af'
@ -257,7 +257,7 @@ function initializeApp(settings: SettingsService) {
NgSelectModule, NgSelectModule,
ColorSliderModule, ColorSliderModule,
TourNgBootstrapModule, TourNgBootstrapModule,
DndModule, DragDropModule,
], ],
providers: [ providers: [
{ {

View File

@ -18,7 +18,7 @@ import { ShareLinkService } from 'src/app/services/rest/share-link.service'
import { ToastService } from 'src/app/services/toast.service' import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
import { ShareLinksDropdownComponent } from './share-links-dropdown.component' import { ShareLinksDropdownComponent } from './share-links-dropdown.component'
import { ClipboardService } from 'ngx-clipboard' import { Clipboard } from '@angular/cdk/clipboard'
describe('ShareLinksDropdownComponent', () => { describe('ShareLinksDropdownComponent', () => {
let component: ShareLinksDropdownComponent let component: ShareLinksDropdownComponent
@ -26,7 +26,7 @@ describe('ShareLinksDropdownComponent', () => {
let shareLinkService: ShareLinkService let shareLinkService: ShareLinkService
let toastService: ToastService let toastService: ToastService
let httpController: HttpTestingController let httpController: HttpTestingController
let clipboardService: ClipboardService let clipboard: Clipboard
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@ -38,7 +38,7 @@ describe('ShareLinksDropdownComponent', () => {
shareLinkService = TestBed.inject(ShareLinkService) shareLinkService = TestBed.inject(ShareLinkService)
toastService = TestBed.inject(ToastService) toastService = TestBed.inject(ToastService)
httpController = TestBed.inject(HttpTestingController) httpController = TestBed.inject(HttpTestingController)
clipboardService = TestBed.inject(ClipboardService) clipboard = TestBed.inject(Clipboard)
component = fixture.componentInstance component = fixture.componentInstance
fixture.detectChanges() fixture.detectChanges()
@ -102,7 +102,7 @@ describe('ShareLinksDropdownComponent', () => {
const expiration = new Date() const expiration = new Date()
expiration.setDate(expiration.getDate() + 7) expiration.setDate(expiration.getDate() + 7)
const copySpy = jest.spyOn(clipboardService, 'copy') const copySpy = jest.spyOn(clipboard, 'copy')
const refreshSpy = jest.spyOn(component, 'refresh') const refreshSpy = jest.spyOn(component, 'refresh')
component.createLink() component.createLink()

View File

@ -7,7 +7,7 @@ import {
import { ShareLinkService } from 'src/app/services/rest/share-link.service' import { ShareLinkService } from 'src/app/services/rest/share-link.service'
import { ToastService } from 'src/app/services/toast.service' import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment' import { environment } from 'src/environments/environment'
import { ClipboardService } from 'ngx-clipboard' import { Clipboard } from '@angular/cdk/clipboard'
@Component({ @Component({
selector: 'pngx-share-links-dropdown', selector: 'pngx-share-links-dropdown',
@ -51,7 +51,7 @@ export class ShareLinksDropdownComponent implements OnInit {
constructor( constructor(
private shareLinkService: ShareLinkService, private shareLinkService: ShareLinkService,
private toastService: ToastService, private toastService: ToastService,
private clipboardService: ClipboardService private clipboard: Clipboard
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
@ -91,7 +91,7 @@ export class ShareLinksDropdownComponent implements OnInit {
} }
copy(link: PaperlessShareLink) { copy(link: PaperlessShareLink) {
this.clipboardService.copy(this.getShareUrl(link)) this.clipboard.copy(this.getShareUrl(link))
this.copied = link.id this.copied = link.id
setTimeout(() => { setTimeout(() => {
this.copied = null this.copied = null

View File

@ -10,7 +10,7 @@ import { ComponentFixture } from '@angular/core/testing'
import { HttpClientTestingModule } from '@angular/common/http/testing' import { HttpClientTestingModule } from '@angular/common/http/testing'
import { of } from 'rxjs' import { of } from 'rxjs'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap' import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { ClipboardService } from 'ngx-clipboard' import { Clipboard } from '@angular/cdk/clipboard'
const toasts = [ const toasts = [
{ {
@ -42,7 +42,7 @@ describe('ToastsComponent', () => {
let component: ToastsComponent let component: ToastsComponent
let fixture: ComponentFixture<ToastsComponent> let fixture: ComponentFixture<ToastsComponent>
let toastService: ToastService let toastService: ToastService
let clipboardService: ClipboardService let clipboard: Clipboard
beforeEach(async () => { beforeEach(async () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@ -60,7 +60,7 @@ describe('ToastsComponent', () => {
fixture = TestBed.createComponent(ToastsComponent) fixture = TestBed.createComponent(ToastsComponent)
toastService = TestBed.inject(ToastService) toastService = TestBed.inject(ToastService)
clipboardService = TestBed.inject(ClipboardService) clipboard = TestBed.inject(Clipboard)
component = fixture.componentInstance component = fixture.componentInstance
@ -117,7 +117,7 @@ describe('ToastsComponent', () => {
'Error 2 message details' 'Error 2 message details'
) )
const copySpy = jest.spyOn(clipboardService, 'copy') const copySpy = jest.spyOn(clipboard, 'copy')
component.copyError(toasts[2].error) component.copyError(toasts[2].error)
expect(copySpy).toHaveBeenCalled() expect(copySpy).toHaveBeenCalled()

View File

@ -1,7 +1,7 @@
import { Component, OnDestroy, OnInit } from '@angular/core' import { Component, OnDestroy, OnInit } from '@angular/core'
import { Subscription } from 'rxjs' import { Subscription } from 'rxjs'
import { Toast, ToastService } from 'src/app/services/toast.service' import { Toast, ToastService } from 'src/app/services/toast.service'
import { ClipboardService } from 'ngx-clipboard' import { Clipboard } from '@angular/cdk/clipboard'
@Component({ @Component({
selector: 'pngx-toasts', selector: 'pngx-toasts',
@ -11,7 +11,7 @@ import { ClipboardService } from 'ngx-clipboard'
export class ToastsComponent implements OnInit, OnDestroy { export class ToastsComponent implements OnInit, OnDestroy {
constructor( constructor(
private toastService: ToastService, private toastService: ToastService,
private clipboardService: ClipboardService private clipboard: Clipboard
) {} ) {}
private subscription: Subscription private subscription: Subscription
@ -49,7 +49,7 @@ export class ToastsComponent implements OnInit, OnDestroy {
} }
public copyError(error: any) { public copyError(error: any) {
this.clipboardService.copy(JSON.stringify(error)) this.clipboard.copy(JSON.stringify(error))
this.copied = true this.copied = true
setTimeout(() => { setTimeout(() => {
this.copied = false this.copied = false

View File

@ -5,10 +5,9 @@
<div class="row"> <div class="row">
<div class="col-auto col-lg-8 col-xl-9 mb-4"> <div class="col-auto col-lg-8 col-xl-9 mb-4">
<div class="row row-cols-1 g-4" tourAnchor="tour.dashboard" <div class="row row-cols-1 g-4" tourAnchor="tour.dashboard"
dndDropzone cdkDropList
[dndDisableIf]="settingsService.globalDropzoneActive" [cdkDropListDisabled]="settingsService.globalDropzoneActive"
dndEffectAllowed="move" (cdkDropListDropped)="onDrop($event)"
(dndDrop)="onDrop($event)"
> >
<div *ngIf="savedViewService.loading" class="col"> <div *ngIf="savedViewService.loading" class="col">
<div class="spinner-border spinner-border-sm me-2" role="status"></div> <div class="spinner-border spinner-border-sm me-2" role="status"></div>
@ -23,14 +22,12 @@
<div *ngFor="let v of dashboardViews" class="col"> <div *ngFor="let v of dashboardViews" class="col">
<pngx-saved-view-widget <pngx-saved-view-widget
[savedView]="v" [savedView]="v"
(dndStart)="onDragStart($event)" (cdkDragStarted)="onDragStart($event)"
(dndMoved)="onDragged(v)" (cdkDragEnded)="onDragEnd($event)"
(dndEnd)="onDragEnd($event)" >
>
</pngx-saved-view-widget> </pngx-saved-view-widget>
</div> </div>
</ng-container> </ng-container>
<div class="p-1" dndPlaceholderRef></div>
</div> </div>
</div> </div>
<div class="col-auto col-lg-4 col-xl-3 col-sidebar"> <div class="col-auto col-lg-4 col-xl-3 col-sidebar">

View File

@ -1,3 +1,19 @@
.col-sidebar .row { .col-sidebar .row {
top: 3.5rem; top: 3.5rem;
} }
:host ::ng-deep {
.cdk-drag-placeholder {
opacity: .5;
}
/* Animate items as they're being sorted. */
.cdk-drop-list-dragging .cdk-drag {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
/* Animate an item that has been dropped. */
.cdk-drag-animating {
transition: transform 300ms cubic-bezier(0, 0, 0.2, 1);
}
}

View File

@ -1,5 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing' import { ComponentFixture, TestBed } from '@angular/core/testing'
import { NgbAlertModule, NgbAlert } from '@ng-bootstrap/ng-bootstrap' import { NgbAlertModule } from '@ng-bootstrap/ng-bootstrap'
import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { DashboardComponent } from './dashboard.component' import { DashboardComponent } from './dashboard.component'
import { HttpClientTestingModule } from '@angular/common/http/testing' import { HttpClientTestingModule } from '@angular/common/http/testing'
@ -17,9 +17,10 @@ import { RouterTestingModule } from '@angular/router/testing'
import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap' import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap'
import { LogoComponent } from '../common/logo/logo.component' import { LogoComponent } from '../common/logo/logo.component'
import { of, throwError } from 'rxjs' import { of, throwError } from 'rxjs'
import { DndDropEvent, DndModule } from 'ngx-drag-drop'
import { ToastService } from 'src/app/services/toast.service' import { ToastService } from 'src/app/services/toast.service'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import { CdkDragDrop, DragDropModule } from '@angular/cdk/drag-drop'
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
const saved_views = [ const saved_views = [
{ {
@ -105,7 +106,7 @@ describe('DashboardComponent', () => {
HttpClientTestingModule, HttpClientTestingModule,
RouterTestingModule, RouterTestingModule,
TourNgBootstrapModule, TourNgBootstrapModule,
DndModule, DragDropModule,
], ],
}).compileComponents() }).compileComponents()
@ -165,18 +166,18 @@ describe('DashboardComponent', () => {
const settingsSpy = jest.spyOn(settingsService, 'updateDashboardViewsSort') const settingsSpy = jest.spyOn(settingsService, 'updateDashboardViewsSort')
const toastSpy = jest.spyOn(toastService, 'showInfo') const toastSpy = jest.spyOn(toastService, 'showInfo')
jest.spyOn(settingsService, 'storeSettings').mockReturnValue(of(true)) jest.spyOn(settingsService, 'storeSettings').mockReturnValue(of(true))
component.onDrop({ index: 2, data: saved_views[0] } as DndDropEvent) component.onDrop({ previousIndex: 0, currentIndex: 1 } as CdkDragDrop<
component.onDragged(saved_views[0]) PaperlessSavedView[]
>)
expect(settingsSpy).toHaveBeenCalledWith([ expect(settingsSpy).toHaveBeenCalledWith([
saved_views[2], saved_views[2],
saved_views[0], saved_views[0],
saved_views[3], saved_views[3],
]) ])
expect(toastSpy).toHaveBeenCalled() expect(toastSpy).toHaveBeenCalled()
component.onDrop({ data: saved_views[3] } as DndDropEvent)
}) })
it('should update saved view sorting on drag + drop, show info2', () => { it('should update saved view sorting on drag + drop, show error', () => {
jest.spyOn(settingsService, 'get').mockImplementation((key) => { jest.spyOn(settingsService, 'get').mockImplementation((key) => {
if (key === SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER) return [] if (key === SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER) return []
}) })
@ -188,8 +189,9 @@ describe('DashboardComponent', () => {
jest jest
.spyOn(settingsService, 'storeSettings') .spyOn(settingsService, 'storeSettings')
.mockReturnValue(throwError(() => new Error('unable to save'))) .mockReturnValue(throwError(() => new Error('unable to save')))
component.onDrop({ index: 2, data: saved_views[0] } as DndDropEvent) component.onDrop({ previousIndex: 0, currentIndex: 2 } as CdkDragDrop<
component.onDragged(saved_views[0]) PaperlessSavedView[]
>)
expect(toastSpy).toHaveBeenCalled() expect(toastSpy).toHaveBeenCalled()
}) })
}) })

View File

@ -4,9 +4,14 @@ import { SettingsService } from 'src/app/services/settings.service'
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component' import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
import { TourService } from 'ngx-ui-tour-ng-bootstrap' import { TourService } from 'ngx-ui-tour-ng-bootstrap'
import { PaperlessSavedView } from 'src/app/data/paperless-saved-view' import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
import { DndDropEvent } from 'ngx-drag-drop'
import { ToastService } from 'src/app/services/toast.service' import { ToastService } from 'src/app/services/toast.service'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings' import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import {
CdkDragDrop,
CdkDragEnd,
CdkDragStart,
moveItemInArray,
} from '@angular/cdk/drag-drop'
@Component({ @Component({
selector: 'pngx-dashboard', selector: 'pngx-dashboard',
@ -59,13 +64,21 @@ export class DashboardComponent extends ComponentWithPermissions {
} }
} }
onDragStart(event: DragEvent) { onDragStart(event: CdkDragStart) {
this.settingsService.globalDropzoneEnabled = false this.settingsService.globalDropzoneEnabled = false
} }
onDragged(v: PaperlessSavedView) { onDragEnd(event: CdkDragEnd) {
const index = this.dashboardViews.indexOf(v) this.settingsService.globalDropzoneEnabled = true
this.dashboardViews.splice(index, 1) }
onDrop(event: CdkDragDrop<PaperlessSavedView[]>) {
moveItemInArray(
this.dashboardViews,
event.previousIndex,
event.currentIndex
)
this.settingsService this.settingsService
.updateDashboardViewsSort(this.dashboardViews) .updateDashboardViewsSort(this.dashboardViews)
.subscribe({ .subscribe({
@ -77,16 +90,4 @@ export class DashboardComponent extends ComponentWithPermissions {
}, },
}) })
} }
onDragEnd(event: DragEvent) {
this.settingsService.globalDropzoneEnabled = true
}
onDrop(event: DndDropEvent) {
if (typeof event.index === 'undefined') {
event.index = this.dashboardViews.length
}
this.dashboardViews.splice(event.index, 0, event.data)
}
} }

View File

@ -3,10 +3,6 @@
[title]="savedView.name" [title]="savedView.name"
[loading]="loading" [loading]="loading"
[draggable]="savedView" [draggable]="savedView"
(dndStart)="dndStart.emit($event)"
(dndMoved)="dndMoved.emit($event)"
(dndCanceled)="dndCanceled.emit($event)"
(dndEnd)="dndEnd.emit($event)"
> >
<a *ngIf="documents.length" class="btn-link text-decoration-none" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a> <a *ngIf="documents.length" class="btn-link text-decoration-none" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a>

View File

@ -28,7 +28,7 @@ import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
import { SavedViewWidgetComponent } from './saved-view-widget.component' import { SavedViewWidgetComponent } from './saved-view-widget.component'
import { By } from '@angular/platform-browser' import { By } from '@angular/platform-browser'
import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe' import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
import { DndModule } from 'ngx-drag-drop' import { DragDropModule } from '@angular/cdk/drag-drop'
const savedView: PaperlessSavedView = { const savedView: PaperlessSavedView = {
id: 1, id: 1,
@ -91,7 +91,7 @@ describe('SavedViewWidgetComponent', () => {
HttpClientTestingModule, HttpClientTestingModule,
NgbModule, NgbModule,
RouterTestingModule.withRoutes(routes), RouterTestingModule.withRoutes(routes),
DndModule, DragDropModule,
], ],
}).compileComponents() }).compileComponents()

View File

@ -1,10 +1,8 @@
import { import {
Component, Component,
EventEmitter,
Input, Input,
OnDestroy, OnDestroy,
OnInit, OnInit,
Output,
QueryList, QueryList,
ViewChildren, ViewChildren,
} from '@angular/core' } from '@angular/core'
@ -53,18 +51,6 @@ export class SavedViewWidgetComponent
@Input() @Input()
savedView: PaperlessSavedView savedView: PaperlessSavedView
@Output()
dndStart: EventEmitter<DragEvent> = new EventEmitter()
@Output()
dndMoved: EventEmitter<DragEvent> = new EventEmitter()
@Output()
dndCanceled: EventEmitter<DragEvent> = new EventEmitter()
@Output()
dndEnd: EventEmitter<DragEvent> = new EventEmitter()
documents: PaperlessDocument[] = [] documents: PaperlessDocument[] = []
unsubscribeNotifier: Subject<any> = new Subject() unsubscribeNotifier: Subject<any> = new Subject()

View File

@ -12,12 +12,12 @@ import { RouterTestingModule } from '@angular/router/testing'
import { routes } from 'src/app/app-routing.module' import { routes } from 'src/app/app-routing.module'
import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
import { DndModule } from 'ngx-drag-drop'
import { import {
ConsumerStatusService, ConsumerStatusService,
FileStatus, FileStatus,
} from 'src/app/services/consumer-status.service' } from 'src/app/services/consumer-status.service'
import { Subject } from 'rxjs' import { Subject } from 'rxjs'
import { DragDropModule } from '@angular/cdk/drag-drop'
describe('StatisticsWidgetComponent', () => { describe('StatisticsWidgetComponent', () => {
let component: StatisticsWidgetComponent let component: StatisticsWidgetComponent
@ -38,7 +38,7 @@ describe('StatisticsWidgetComponent', () => {
HttpClientTestingModule, HttpClientTestingModule,
NgbModule, NgbModule,
RouterTestingModule.withRoutes(routes), RouterTestingModule.withRoutes(routes),
DndModule, DragDropModule,
], ],
}).compileComponents() }).compileComponents()

View File

@ -25,7 +25,7 @@ import { PermissionsService } from 'src/app/services/permissions.service'
import { UploadDocumentsService } from 'src/app/services/upload-documents.service' import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
import { WidgetFrameComponent } from '../widget-frame/widget-frame.component' import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
import { UploadFileWidgetComponent } from './upload-file-widget.component' import { UploadFileWidgetComponent } from './upload-file-widget.component'
import { DndModule } from 'ngx-drag-drop' import { DragDropModule } from '@angular/cdk/drag-drop'
describe('UploadFileWidgetComponent', () => { describe('UploadFileWidgetComponent', () => {
let component: UploadFileWidgetComponent let component: UploadFileWidgetComponent
@ -54,7 +54,7 @@ describe('UploadFileWidgetComponent', () => {
NgbModule, NgbModule,
RouterTestingModule.withRoutes(routes), RouterTestingModule.withRoutes(routes),
NgbAlertModule, NgbAlertModule,
DndModule, DragDropModule,
], ],
}).compileComponents() }).compileComponents()

View File

@ -1,15 +1,8 @@
<div class="card shadow-sm bg-light" <div class="card shadow-sm bg-light" cdkDrag [cdkDragDisabled]="!draggable" cdkDragPreviewContainer="parent">
[dndDraggable]="draggable"
dndEffectAllowed="move"
[dndDisableIf]="!draggable"
(dndStart)="dndStart.emit($event)"
(dndMoved)="dndMoved.emit($event)"
(dndCanceled)="dndCanceled.emit($event)"
(dndEnd)="dndEnd.emit($event)">
<div class="card-header"> <div class="card-header">
<div class="d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center">
<div class="d-flex"> <div class="d-flex">
<div *ngIf="draggable" class="ms-n2 me-1" dndHandle> <div *ngIf="draggable" class="ms-n2 me-1" cdkDragHandle>
<svg class="sidebaricon text-muted" fill="currentColor"> <svg class="sidebaricon text-muted" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#grip-vertical"/> <use xlink:href="assets/bootstrap-icons.svg#grip-vertical"/>
</svg> </svg>

View File

@ -1,10 +1,10 @@
import { Component } from '@angular/core' import { Component } from '@angular/core'
import { ComponentFixture, TestBed } from '@angular/core/testing' import { ComponentFixture, TestBed } from '@angular/core/testing'
import { By } from '@angular/platform-browser' import { By } from '@angular/platform-browser'
import { NgbAlertModule, NgbAlert } from '@ng-bootstrap/ng-bootstrap' import { NgbAlertModule } from '@ng-bootstrap/ng-bootstrap'
import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { WidgetFrameComponent } from './widget-frame.component' import { WidgetFrameComponent } from './widget-frame.component'
import { DndModule } from 'ngx-drag-drop' import { DragDropModule } from '@angular/cdk/drag-drop'
@Component({ @Component({
template: ` template: `
@ -30,7 +30,7 @@ describe('WidgetFrameComponent', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [WidgetFrameComponent, WidgetFrameComponent], declarations: [WidgetFrameComponent, WidgetFrameComponent],
providers: [PermissionsGuard], providers: [PermissionsGuard],
imports: [NgbAlertModule, DndModule], imports: [NgbAlertModule, DragDropModule],
}).compileComponents() }).compileComponents()
fixture = TestBed.createComponent(WidgetFrameComponent) fixture = TestBed.createComponent(WidgetFrameComponent)

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, Output } from '@angular/core' import { Component, Input } from '@angular/core'
@Component({ @Component({
selector: 'pngx-widget-frame', selector: 'pngx-widget-frame',
@ -16,16 +16,4 @@ export class WidgetFrameComponent {
@Input() @Input()
draggable: any draggable: any
@Output()
dndStart: EventEmitter<DragEvent> = new EventEmitter()
@Output()
dndMoved: EventEmitter<DragEvent> = new EventEmitter()
@Output()
dndCanceled: EventEmitter<DragEvent> = new EventEmitter()
@Output()
dndEnd: EventEmitter<DragEvent> = new EventEmitter()
} }

View File

@ -333,6 +333,97 @@ describe('DocumentListViewService', () => {
}) })
}) })
it('should not return next doc when documents is null', () => {
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue(null)
const complete = jest.fn()
documentListViewService.getNext(1).subscribe({
next: () => fail('Observable should not emit any value'),
complete: complete(),
})
expect(complete).toHaveBeenCalled()
})
it('should return next doc when exists', () => {
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue(documents)
const next = jest.fn()
documentListViewService.getNext(3).subscribe({
next: (id) => next(id),
complete: () => {},
})
expect(next).toHaveBeenCalledWith(4)
})
it('should increase page on get next doc if needed', () => {
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue(documents)
expect(documentListViewService.currentPage).toEqual(1)
documentListViewService.currentPageSize = 3
jest
.spyOn(documentListViewService, 'getLastPage')
.mockReturnValue(Math.ceil(documents.length / 3))
const reloadSpy = jest.spyOn(documentListViewService, 'reload')
documentListViewService
.getNext(documents[documents.length - 1].id)
.subscribe({
next: () => {},
complete: () => {},
})
expect(reloadSpy).toHaveBeenCalled()
expect(documentListViewService.currentPage).toEqual(2)
const reqs = httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=2&page_size=3&ordering=-created&truncate_content=true`
)
expect(reqs.length).toBeGreaterThan(0)
})
it('should not return previous doc when documents is null', () => {
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue(null)
const complete = jest.fn()
documentListViewService.getPrevious(1).subscribe({
next: () => fail('Observable should not emit any value'),
complete: complete(),
})
expect(complete).toHaveBeenCalled()
})
it('should return previous doc when exists', () => {
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue(documents)
const next = jest.fn()
documentListViewService.getPrevious(3).subscribe({
next: (id) => next(id),
complete: () => {},
})
expect(next).toHaveBeenCalledWith(2)
})
it('should decrease page on get previous doc if needed', () => {
jest
.spyOn(documentListViewService, 'documents', 'get')
.mockReturnValue(documents)
documentListViewService.currentPage = 2
documentListViewService.currentPageSize = 3
const reloadSpy = jest.spyOn(documentListViewService, 'reload')
documentListViewService.getPrevious(1).subscribe({
next: () => {},
complete: () => {},
})
expect(reloadSpy).toHaveBeenCalled()
expect(documentListViewService.currentPage).toEqual(1)
const reqs = httpTestingController.match(
`${environment.apiBaseUrl}documents/?page=1&page_size=3&ordering=-created&truncate_content=true`
)
expect(reqs.length).toBeGreaterThan(0)
})
it('should update page size from settings', () => { it('should update page size from settings', () => {
settingsService.set(SETTINGS_KEYS.DOCUMENT_LIST_SIZE, 10) settingsService.set(SETTINGS_KEYS.DOCUMENT_LIST_SIZE, 10)
documentListViewService.updatePageSize() documentListViewService.updatePageSize()
@ -435,4 +526,22 @@ describe('DocumentListViewService', () => {
) )
expect(cancelSpy).toHaveBeenCalled() expect(cancelSpy).toHaveBeenCalled()
}) })
it('should reset sort field if changing from search result', () => {
const view2 = {
id: 22,
name: 'Saved View 2',
sort_field: 'score',
sort_reverse: true,
filter_rules: filterRules,
}
documentListViewService.loadSavedView(view2)
expect(documentListViewService.sortField).toEqual('score')
documentListViewService.filterRules = []
expect(documentListViewService.sortField).toEqual('created')
httpTestingController.expectOne(
`${environment.apiBaseUrl}documents/?page=1&page_size=50&ordering=-created&truncate_content=true`
)
})
}) })