mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-02-20 00:39:32 -06:00
Actually use a modal
This commit is contained in:
@@ -95,22 +95,6 @@
|
|||||||
<div class="col-md-6 col-xl-5 mb-4">
|
<div class="col-md-6 col-xl-5 mb-4">
|
||||||
|
|
||||||
<form [formGroup]='documentForm' (ngSubmit)="save()">
|
<form [formGroup]='documentForm' (ngSubmit)="save()">
|
||||||
@if (remoteUpdateDetected) {
|
|
||||||
<div class="alert alert-warning d-flex flex-column flex-md-row align-items-md-center gap-2" role="alert">
|
|
||||||
<div class="flex-fill">
|
|
||||||
<div class="fw-semibold" i18n>Document was updated.</div>
|
|
||||||
@if (remoteUpdateModified) {
|
|
||||||
<div class="small" i18n>Remote update detected at {{ remoteUpdateModified | date:'medium' }}.</div>
|
|
||||||
}
|
|
||||||
<div class="small" i18n>Saving your local edits now may overwrite remote changes.</div>
|
|
||||||
</div>
|
|
||||||
<div class="btn-group btn-group-sm">
|
|
||||||
<button type="button" class="btn btn-warning" (click)="reloadRemoteVersion()" i18n>Reload</button>
|
|
||||||
<button type="button" class="btn btn-outline-secondary" (click)="dismissRemoteUpdateWarning()" i18n>Dismiss</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="btn-toolbar mb-1 border-bottom">
|
<div class="btn-toolbar mb-1 border-bottom">
|
||||||
<div class="btn-group pb-3">
|
<div class="btn-group pb-3">
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Close" (click)="close()">
|
<button type="button" class="btn btn-sm btn-outline-secondary" i18n-title title="Close" (click)="close()">
|
||||||
|
|||||||
@@ -1205,7 +1205,9 @@ describe('DocumentDetailComponent', () => {
|
|||||||
expect(errorSpy).toHaveBeenCalled()
|
expect(errorSpy).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show remote update warning when open local draft is older than backend on init', () => {
|
it('should show incoming update modal when open local draft is older than backend on init', () => {
|
||||||
|
let openModal: NgbModalRef
|
||||||
|
modalService.activeInstances.subscribe((modals) => (openModal = modals[0]))
|
||||||
const modalSpy = jest.spyOn(modalService, 'open')
|
const modalSpy = jest.spyOn(modalService, 'open')
|
||||||
const openDoc = Object.assign({}, doc, {
|
const openDoc = Object.assign({}, doc, {
|
||||||
__changedFields: ['title'],
|
__changedFields: ['title'],
|
||||||
@@ -1227,11 +1229,11 @@ describe('DocumentDetailComponent', () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
fixture.detectChanges() // calls ngOnInit
|
fixture.detectChanges() // calls ngOnInit
|
||||||
expect(component.remoteUpdateDetected).toBeTruthy()
|
expect(modalSpy).toHaveBeenCalledWith(ConfirmDialogComponent, {
|
||||||
expect(component.remoteUpdateModified).toEqual(
|
backdrop: 'static',
|
||||||
remoteDoc.modified.toISOString()
|
})
|
||||||
)
|
const confirmDialog = openModal.componentInstance as ConfirmDialogComponent
|
||||||
expect(modalSpy).not.toHaveBeenCalledWith(ConfirmDialogComponent)
|
expect(confirmDialog.messageBold).toContain('Remote update detected at')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should change preview element by render type', () => {
|
it('should change preview element by render type', () => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { AsyncPipe, DatePipe, NgTemplateOutlet } from '@angular/common'
|
import { AsyncPipe, NgTemplateOutlet } from '@angular/common'
|
||||||
import { HttpClient, HttpResponse } from '@angular/common/http'
|
import { HttpClient, HttpResponse } from '@angular/common/http'
|
||||||
import { Component, inject, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
import { Component, inject, OnDestroy, OnInit, ViewChild } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
NgbDateStruct,
|
NgbDateStruct,
|
||||||
NgbDropdownModule,
|
NgbDropdownModule,
|
||||||
NgbModal,
|
NgbModal,
|
||||||
|
NgbModalRef,
|
||||||
NgbNav,
|
NgbNav,
|
||||||
NgbNavChangeEvent,
|
NgbNavChangeEvent,
|
||||||
NgbNavModule,
|
NgbNavModule,
|
||||||
@@ -164,7 +165,6 @@ enum ContentRenderType {
|
|||||||
MonetaryComponent,
|
MonetaryComponent,
|
||||||
UrlComponent,
|
UrlComponent,
|
||||||
SuggestionsDropdownComponent,
|
SuggestionsDropdownComponent,
|
||||||
DatePipe,
|
|
||||||
CustomDatePipe,
|
CustomDatePipe,
|
||||||
FileSizePipe,
|
FileSizePipe,
|
||||||
IfPermissionsDirective,
|
IfPermissionsDirective,
|
||||||
@@ -264,6 +264,7 @@ export class DocumentDetailComponent
|
|||||||
isDirty$: Observable<boolean>
|
isDirty$: Observable<boolean>
|
||||||
unsubscribeNotifier: Subject<any> = new Subject()
|
unsubscribeNotifier: Subject<any> = new Subject()
|
||||||
docChangeNotifier: Subject<any> = new Subject()
|
docChangeNotifier: Subject<any> = new Subject()
|
||||||
|
private incomingUpdateModal: NgbModalRef
|
||||||
|
|
||||||
requiresPassword: boolean = false
|
requiresPassword: boolean = false
|
||||||
password: string
|
password: string
|
||||||
@@ -273,8 +274,6 @@ export class DocumentDetailComponent
|
|||||||
customFields: CustomField[]
|
customFields: CustomField[]
|
||||||
|
|
||||||
public downloading: boolean = false
|
public downloading: boolean = false
|
||||||
remoteUpdateDetected: boolean = false
|
|
||||||
remoteUpdateModified: string | null = null
|
|
||||||
|
|
||||||
public readonly CustomFieldDataType = CustomFieldDataType
|
public readonly CustomFieldDataType = CustomFieldDataType
|
||||||
|
|
||||||
@@ -443,8 +442,49 @@ export class DocumentDetailComponent
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private showIncomingUpdateModal(modified?: string | Date): void {
|
||||||
|
if (this.incomingUpdateModal) return
|
||||||
|
|
||||||
|
const modal = this.modalService.open(ConfirmDialogComponent, {
|
||||||
|
backdrop: 'static',
|
||||||
|
})
|
||||||
|
this.incomingUpdateModal = modal
|
||||||
|
|
||||||
|
let formattedModified = null
|
||||||
|
if (modified) {
|
||||||
|
const parsed = new Date(modified)
|
||||||
|
if (!isNaN(parsed.getTime())) {
|
||||||
|
formattedModified = parsed.toLocaleString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.componentInstance.title = $localize`Document was updated.`
|
||||||
|
modal.componentInstance.messageBold = formattedModified
|
||||||
|
? $localize`Document was updated at ${formattedModified}.`
|
||||||
|
: $localize`This document was updated elsewhere.`
|
||||||
|
modal.componentInstance.message = $localize`Reload to discard your local unsaved edits and load the latest remote version.`
|
||||||
|
modal.componentInstance.btnClass = 'btn-warning'
|
||||||
|
modal.componentInstance.btnCaption = $localize`Reload`
|
||||||
|
modal.componentInstance.cancelBtnCaption = $localize`Dismiss`
|
||||||
|
|
||||||
|
modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => {
|
||||||
|
modal.componentInstance.buttonsEnabled = false
|
||||||
|
modal.close()
|
||||||
|
this.reloadRemoteVersion()
|
||||||
|
})
|
||||||
|
modal.result.finally(() => {
|
||||||
|
this.incomingUpdateModal = null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private closeIncomingUpdateModal() {
|
||||||
|
if (!this.incomingUpdateModal) return
|
||||||
|
this.incomingUpdateModal.close()
|
||||||
|
this.incomingUpdateModal = null
|
||||||
|
}
|
||||||
|
|
||||||
private loadDocument(documentId: number, forceRemote: boolean = false): void {
|
private loadDocument(documentId: number, forceRemote: boolean = false): void {
|
||||||
this.dismissRemoteUpdateWarning()
|
this.closeIncomingUpdateModal()
|
||||||
this.previewUrl = this.documentsService.getPreviewUrl(documentId)
|
this.previewUrl = this.documentsService.getPreviewUrl(documentId)
|
||||||
this.updatePdfSource()
|
this.updatePdfSource()
|
||||||
this.http
|
this.http
|
||||||
@@ -499,10 +539,7 @@ export class DocumentDetailComponent
|
|||||||
} else if (openDocument) {
|
} else if (openDocument) {
|
||||||
if (new Date(doc.modified) > new Date(openDocument.modified)) {
|
if (new Date(doc.modified) > new Date(openDocument.modified)) {
|
||||||
if (this.hasLocalEdits(openDocument)) {
|
if (this.hasLocalEdits(openDocument)) {
|
||||||
this.remoteUpdateDetected = true
|
this.showIncomingUpdateModal(doc.modified)
|
||||||
this.remoteUpdateModified = doc.modified
|
|
||||||
? new Date(doc.modified).toISOString()
|
|
||||||
: null
|
|
||||||
} else {
|
} else {
|
||||||
// No local edits to preserve, so keep the tab in sync automatically.
|
// No local edits to preserve, so keep the tab in sync automatically.
|
||||||
Object.assign(openDocument, doc)
|
Object.assign(openDocument, doc)
|
||||||
@@ -549,8 +586,7 @@ export class DocumentDetailComponent
|
|||||||
if (!this.document || this.networkActive) return
|
if (!this.document || this.networkActive) return
|
||||||
|
|
||||||
if (this.openDocumentService.isDirty(this.document)) {
|
if (this.openDocumentService.isDirty(this.document)) {
|
||||||
this.remoteUpdateDetected = true
|
this.showIncomingUpdateModal(data.modified)
|
||||||
this.remoteUpdateModified = data.modified ?? null
|
|
||||||
} else {
|
} else {
|
||||||
this.docChangeNotifier.next(this.documentId)
|
this.docChangeNotifier.next(this.documentId)
|
||||||
this.loadDocument(this.documentId, true)
|
this.loadDocument(this.documentId, true)
|
||||||
@@ -560,14 +596,10 @@ export class DocumentDetailComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dismissRemoteUpdateWarning() {
|
private reloadRemoteVersion() {
|
||||||
this.remoteUpdateDetected = false
|
|
||||||
this.remoteUpdateModified = null
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadRemoteVersion() {
|
|
||||||
if (!this.documentId) return
|
if (!this.documentId) return
|
||||||
|
|
||||||
|
this.closeIncomingUpdateModal()
|
||||||
this.docChangeNotifier.next(this.documentId)
|
this.docChangeNotifier.next(this.documentId)
|
||||||
this.loadDocument(this.documentId, true)
|
this.loadDocument(this.documentId, true)
|
||||||
this.toastService.showInfo($localize`Document reloaded.`)
|
this.toastService.showInfo($localize`Document reloaded.`)
|
||||||
@@ -970,7 +1002,7 @@ export class DocumentDetailComponent
|
|||||||
)
|
)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (doc) => {
|
next: (doc) => {
|
||||||
this.dismissRemoteUpdateWarning()
|
this.closeIncomingUpdateModal()
|
||||||
Object.assign(this.document, doc)
|
Object.assign(this.document, doc)
|
||||||
doc['permissions_form'] = {
|
doc['permissions_form'] = {
|
||||||
owner: doc.owner,
|
owner: doc.owner,
|
||||||
@@ -1017,7 +1049,7 @@ export class DocumentDetailComponent
|
|||||||
.pipe(first())
|
.pipe(first())
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (docValues) => {
|
next: (docValues) => {
|
||||||
this.dismissRemoteUpdateWarning()
|
this.closeIncomingUpdateModal()
|
||||||
// in case data changed while saving eg removing inbox_tags
|
// in case data changed while saving eg removing inbox_tags
|
||||||
this.documentForm.patchValue(docValues)
|
this.documentForm.patchValue(docValues)
|
||||||
const newValues = Object.assign({}, this.documentForm.value)
|
const newValues = Object.assign({}, this.documentForm.value)
|
||||||
@@ -1097,7 +1129,7 @@ export class DocumentDetailComponent
|
|||||||
.pipe(first())
|
.pipe(first())
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: ({ updateResult, nextDocId, closeResult }) => {
|
next: ({ updateResult, nextDocId, closeResult }) => {
|
||||||
this.dismissRemoteUpdateWarning()
|
this.closeIncomingUpdateModal()
|
||||||
this.error = null
|
this.error = null
|
||||||
this.networkActive = false
|
this.networkActive = false
|
||||||
if (closeResult && updateResult && nextDocId) {
|
if (closeResult && updateResult && nextDocId) {
|
||||||
|
|||||||
Reference in New Issue
Block a user