diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html
index e5663acfe..13f7640a6 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.html
+++ b/src-ui/src/app/components/document-detail/document-detail.component.html
@@ -8,14 +8,40 @@
-
- Label
-
-
-
-
+ @if (versionUploadState === UploadState.Idle) {
+
+ Label
+
+
+
+
+ } @else {
+ @switch (versionUploadState) {
+ @case (UploadState.Uploading) {
+
+
+ Uploading version...
+
+ }
+ @case (UploadState.Processing) {
+
+
+ Processing version...
+
+ }
+ @case (UploadState.Failed) {
+
+ Version upload failed.
+
+
+ @if (versionUploadError) {
+
{{ versionUploadError }}
+ }
+ }
+ }
+ }
@for (version of document.versions; track version.id) {
diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts
index 9bd026831..a85be20f3 100644
--- a/src-ui/src/app/components/document-detail/document-detail.component.ts
+++ b/src-ui/src/app/components/document-detail/document-detail.component.ts
@@ -20,7 +20,7 @@ import {
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { DeviceDetectorService } from 'ngx-device-detector'
-import { BehaviorSubject, Observable, of, Subject, timer } from 'rxjs'
+import { BehaviorSubject, merge, Observable, of, Subject, timer } from 'rxjs'
import {
catchError,
debounceTime,
@@ -81,7 +81,10 @@ import { TagService } from 'src/app/services/rest/tag.service'
import { UserService } from 'src/app/services/rest/user.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
-import { WebsocketStatusService } from 'src/app/services/websocket-status.service'
+import {
+ UploadState,
+ WebsocketStatusService,
+} from 'src/app/services/websocket-status.service'
import { getFilenameFromContentDisposition } from 'src/app/utils/http'
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
import * as UTIF from 'utif'
@@ -188,6 +191,8 @@ export class DocumentDetailComponent
implements OnInit, OnDestroy, DirtyComponent
{
PdfRenderMode = PdfRenderMode
+ UploadState = UploadState
+
documentsService = inject(DocumentService)
private route = inject(ActivatedRoute)
private tagService = inject(TagService)
@@ -237,6 +242,8 @@ export class DocumentDetailComponent
// Versioning: which document ID to use for file preview/download
selectedVersionId: number
newVersionLabel: string = ''
+ versionUploadState: UploadState = UploadState.Idle
+ versionUploadError: string | null = null
previewText: string
previewLoaded: boolean = false
tiffURL: string
@@ -1239,6 +1246,8 @@ export class DocumentDetailComponent
// Reset input to allow re-selection of the same file later
input.value = ''
const label = this.newVersionLabel?.trim()
+ this.versionUploadState = UploadState.Uploading
+ this.versionUploadError = null
this.documentsService
.uploadVersion(this.documentId, file, label)
.pipe(
@@ -1248,6 +1257,7 @@ export class DocumentDetailComponent
$localize`Uploading new version. Processing will happen in the background.`
)
this.newVersionLabel = ''
+ this.versionUploadState = UploadState.Processing
}),
map((taskId) =>
typeof taskId === 'string'
@@ -1256,17 +1266,31 @@ export class DocumentDetailComponent
),
switchMap((taskId) => {
if (!taskId) {
+ this.versionUploadState = UploadState.Failed
+ this.versionUploadError = $localize`Missing task ID.`
return of(null)
}
- return this.websocketStatusService
- .onDocumentConsumptionFinished()
- .pipe(
+ return merge(
+ this.websocketStatusService.onDocumentConsumptionFinished().pipe(
filter((status) => status.taskId === taskId),
- take(1)
+ map(() => ({ state: 'success' as const }))
+ ),
+ this.websocketStatusService.onDocumentConsumptionFailed().pipe(
+ filter((status) => status.taskId === taskId),
+ map((status) => ({
+ state: 'failed' as const,
+ message: status.message,
+ }))
)
+ ).pipe(take(1))
}),
- switchMap((status) => {
- if (!status) {
+ switchMap((result) => {
+ if (!result || result.state !== 'success') {
+ if (result?.state === 'failed') {
+ this.versionUploadState = UploadState.Failed
+ this.versionUploadError =
+ result.message || $localize`Upload failed.`
+ }
return of(null)
}
return this.documentsService.getVersions(this.documentId)
@@ -1285,9 +1309,15 @@ export class DocumentDetailComponent
openDoc.versions = doc.versions
this.openDocumentService.save()
}
+ this.selectVersion(
+ Math.max(...doc.versions.map((version) => version.id))
+ )
+ this.clearVersionUploadStatus()
}
},
error: (error) => {
+ this.versionUploadState = UploadState.Failed
+ this.versionUploadError = error?.message || $localize`Upload failed.`
this.toastService.showError(
$localize`Error uploading new version`,
error
@@ -1296,6 +1326,11 @@ export class DocumentDetailComponent
})
}
+ clearVersionUploadStatus() {
+ this.versionUploadState = UploadState.Idle
+ this.versionUploadError = null
+ }
+
download(original: boolean = false) {
this.downloading = true
const downloadUrl = this.documentsService.getDownloadUrl(
diff --git a/src-ui/src/app/services/websocket-status.service.ts b/src-ui/src/app/services/websocket-status.service.ts
index f9084c88c..5675060d1 100644
--- a/src-ui/src/app/services/websocket-status.service.ts
+++ b/src-ui/src/app/services/websocket-status.service.ts
@@ -89,6 +89,13 @@ export class FileStatus {
}
}
+export enum UploadState {
+ Idle = 'idle',
+ Uploading = 'uploading',
+ Processing = 'processing',
+ Failed = 'failed',
+}
+
@Injectable({
providedIn: 'root',
})