Sweet, live updating

This commit is contained in:
shamoon
2026-02-10 13:13:21 -08:00
parent 3a5a32771e
commit 667e4b81eb
3 changed files with 84 additions and 16 deletions

View File

@@ -8,14 +8,40 @@
</button>
<div class="dropdown-menu shadow" ngbDropdownMenu>
<div class="px-3 py-2">
<div class="input-group input-group-sm mb-2">
<span class="input-group-text" i18n>Label</span>
<input class="form-control" type="text" [(ngModel)]="newVersionLabel" i18n-placeholder placeholder="Optional" [disabled]="!userIsOwner || !userCanEdit" />
</div>
<input #versionFileInput type="file" class="visually-hidden" (change)="onVersionFileSelected($event)" />
<button class="btn btn-sm btn-outline-secondary w-100" (click)="versionFileInput.click()" [disabled]="!userIsOwner || !userCanEdit">
<i-bs name="file-earmark-plus"></i-bs><span class="ps-1" i18n>Add new version</span>
</button>
@if (versionUploadState === UploadState.Idle) {
<div class="input-group input-group-sm mb-2">
<span class="input-group-text" i18n>Label</span>
<input class="form-control" type="text" [(ngModel)]="newVersionLabel" i18n-placeholder placeholder="Optional" [disabled]="!userIsOwner || !userCanEdit" />
</div>
<input #versionFileInput type="file" class="visually-hidden" (change)="onVersionFileSelected($event)" />
<button class="btn btn-sm btn-outline-secondary w-100" (click)="versionFileInput.click()" [disabled]="!userIsOwner || !userCanEdit">
<i-bs name="file-earmark-plus"></i-bs><span class="ps-1" i18n>Add new version</span>
</button>
} @else {
@switch (versionUploadState) {
@case (UploadState.Uploading) {
<div class="small text-muted mt-1 d-flex align-items-center">
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
<span i18n>Uploading version...</span>
</div>
}
@case (UploadState.Processing) {
<div class="small text-muted mt-1 d-flex align-items-center">
<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
<span i18n>Processing version...</span>
</div>
}
@case (UploadState.Failed) {
<div class="small text-danger mt-1 d-flex align-items-center justify-content-between">
<span i18n>Version upload failed.</span>
<button type="button" class="btn btn-link btn-sm p-0 ms-2" (click)="clearVersionUploadStatus()" i18n>Dismiss</button>
</div>
@if (versionUploadError) {
<div class="small text-muted mt-1">{{ versionUploadError }}</div>
}
}
}
}
</div>
<div class="dropdown-divider"></div>
@for (version of document.versions; track version.id) {

View File

@@ -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(

View File

@@ -89,6 +89,13 @@ export class FileStatus {
}
}
export enum UploadState {
Idle = 'idle',
Uploading = 'uploading',
Processing = 'processing',
Failed = 'failed',
}
@Injectable({
providedIn: 'root',
})