mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-02-11 23:59:31 -06:00
Sweet, live updating
This commit is contained in:
@@ -8,6 +8,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu shadow" ngbDropdownMenu>
|
<div class="dropdown-menu shadow" ngbDropdownMenu>
|
||||||
<div class="px-3 py-2">
|
<div class="px-3 py-2">
|
||||||
|
@if (versionUploadState === UploadState.Idle) {
|
||||||
<div class="input-group input-group-sm mb-2">
|
<div class="input-group input-group-sm mb-2">
|
||||||
<span class="input-group-text" i18n>Label</span>
|
<span class="input-group-text" i18n>Label</span>
|
||||||
<input class="form-control" type="text" [(ngModel)]="newVersionLabel" i18n-placeholder placeholder="Optional" [disabled]="!userIsOwner || !userCanEdit" />
|
<input class="form-control" type="text" [(ngModel)]="newVersionLabel" i18n-placeholder placeholder="Optional" [disabled]="!userIsOwner || !userCanEdit" />
|
||||||
@@ -16,6 +17,31 @@
|
|||||||
<button class="btn btn-sm btn-outline-secondary w-100" (click)="versionFileInput.click()" [disabled]="!userIsOwner || !userCanEdit">
|
<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>
|
<i-bs name="file-earmark-plus"></i-bs><span class="ps-1" i18n>Add new version</span>
|
||||||
</button>
|
</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>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
@for (version of document.versions; track version.id) {
|
@for (version of document.versions; track version.id) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'
|
import { dirtyCheck, DirtyComponent } from '@ngneat/dirty-check-forms'
|
||||||
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector'
|
import { DeviceDetectorService } from 'ngx-device-detector'
|
||||||
import { BehaviorSubject, Observable, of, Subject, timer } from 'rxjs'
|
import { BehaviorSubject, merge, Observable, of, Subject, timer } from 'rxjs'
|
||||||
import {
|
import {
|
||||||
catchError,
|
catchError,
|
||||||
debounceTime,
|
debounceTime,
|
||||||
@@ -81,7 +81,10 @@ import { TagService } from 'src/app/services/rest/tag.service'
|
|||||||
import { UserService } from 'src/app/services/rest/user.service'
|
import { UserService } from 'src/app/services/rest/user.service'
|
||||||
import { SettingsService } from 'src/app/services/settings.service'
|
import { SettingsService } from 'src/app/services/settings.service'
|
||||||
import { ToastService } from 'src/app/services/toast.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 { getFilenameFromContentDisposition } from 'src/app/utils/http'
|
||||||
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
|
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
|
||||||
import * as UTIF from 'utif'
|
import * as UTIF from 'utif'
|
||||||
@@ -188,6 +191,8 @@ export class DocumentDetailComponent
|
|||||||
implements OnInit, OnDestroy, DirtyComponent
|
implements OnInit, OnDestroy, DirtyComponent
|
||||||
{
|
{
|
||||||
PdfRenderMode = PdfRenderMode
|
PdfRenderMode = PdfRenderMode
|
||||||
|
UploadState = UploadState
|
||||||
|
|
||||||
documentsService = inject(DocumentService)
|
documentsService = inject(DocumentService)
|
||||||
private route = inject(ActivatedRoute)
|
private route = inject(ActivatedRoute)
|
||||||
private tagService = inject(TagService)
|
private tagService = inject(TagService)
|
||||||
@@ -237,6 +242,8 @@ export class DocumentDetailComponent
|
|||||||
// Versioning: which document ID to use for file preview/download
|
// Versioning: which document ID to use for file preview/download
|
||||||
selectedVersionId: number
|
selectedVersionId: number
|
||||||
newVersionLabel: string = ''
|
newVersionLabel: string = ''
|
||||||
|
versionUploadState: UploadState = UploadState.Idle
|
||||||
|
versionUploadError: string | null = null
|
||||||
previewText: string
|
previewText: string
|
||||||
previewLoaded: boolean = false
|
previewLoaded: boolean = false
|
||||||
tiffURL: string
|
tiffURL: string
|
||||||
@@ -1239,6 +1246,8 @@ export class DocumentDetailComponent
|
|||||||
// Reset input to allow re-selection of the same file later
|
// Reset input to allow re-selection of the same file later
|
||||||
input.value = ''
|
input.value = ''
|
||||||
const label = this.newVersionLabel?.trim()
|
const label = this.newVersionLabel?.trim()
|
||||||
|
this.versionUploadState = UploadState.Uploading
|
||||||
|
this.versionUploadError = null
|
||||||
this.documentsService
|
this.documentsService
|
||||||
.uploadVersion(this.documentId, file, label)
|
.uploadVersion(this.documentId, file, label)
|
||||||
.pipe(
|
.pipe(
|
||||||
@@ -1248,6 +1257,7 @@ export class DocumentDetailComponent
|
|||||||
$localize`Uploading new version. Processing will happen in the background.`
|
$localize`Uploading new version. Processing will happen in the background.`
|
||||||
)
|
)
|
||||||
this.newVersionLabel = ''
|
this.newVersionLabel = ''
|
||||||
|
this.versionUploadState = UploadState.Processing
|
||||||
}),
|
}),
|
||||||
map((taskId) =>
|
map((taskId) =>
|
||||||
typeof taskId === 'string'
|
typeof taskId === 'string'
|
||||||
@@ -1256,17 +1266,31 @@ export class DocumentDetailComponent
|
|||||||
),
|
),
|
||||||
switchMap((taskId) => {
|
switchMap((taskId) => {
|
||||||
if (!taskId) {
|
if (!taskId) {
|
||||||
|
this.versionUploadState = UploadState.Failed
|
||||||
|
this.versionUploadError = $localize`Missing task ID.`
|
||||||
return of(null)
|
return of(null)
|
||||||
}
|
}
|
||||||
return this.websocketStatusService
|
return merge(
|
||||||
.onDocumentConsumptionFinished()
|
this.websocketStatusService.onDocumentConsumptionFinished().pipe(
|
||||||
.pipe(
|
|
||||||
filter((status) => status.taskId === taskId),
|
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) => {
|
switchMap((result) => {
|
||||||
if (!status) {
|
if (!result || result.state !== 'success') {
|
||||||
|
if (result?.state === 'failed') {
|
||||||
|
this.versionUploadState = UploadState.Failed
|
||||||
|
this.versionUploadError =
|
||||||
|
result.message || $localize`Upload failed.`
|
||||||
|
}
|
||||||
return of(null)
|
return of(null)
|
||||||
}
|
}
|
||||||
return this.documentsService.getVersions(this.documentId)
|
return this.documentsService.getVersions(this.documentId)
|
||||||
@@ -1285,9 +1309,15 @@ export class DocumentDetailComponent
|
|||||||
openDoc.versions = doc.versions
|
openDoc.versions = doc.versions
|
||||||
this.openDocumentService.save()
|
this.openDocumentService.save()
|
||||||
}
|
}
|
||||||
|
this.selectVersion(
|
||||||
|
Math.max(...doc.versions.map((version) => version.id))
|
||||||
|
)
|
||||||
|
this.clearVersionUploadStatus()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
|
this.versionUploadState = UploadState.Failed
|
||||||
|
this.versionUploadError = error?.message || $localize`Upload failed.`
|
||||||
this.toastService.showError(
|
this.toastService.showError(
|
||||||
$localize`Error uploading new version`,
|
$localize`Error uploading new version`,
|
||||||
error
|
error
|
||||||
@@ -1296,6 +1326,11 @@ export class DocumentDetailComponent
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearVersionUploadStatus() {
|
||||||
|
this.versionUploadState = UploadState.Idle
|
||||||
|
this.versionUploadError = null
|
||||||
|
}
|
||||||
|
|
||||||
download(original: boolean = false) {
|
download(original: boolean = false) {
|
||||||
this.downloading = true
|
this.downloading = true
|
||||||
const downloadUrl = this.documentsService.getDownloadUrl(
|
const downloadUrl = this.documentsService.getDownloadUrl(
|
||||||
|
|||||||
@@ -89,6 +89,13 @@ export class FileStatus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum UploadState {
|
||||||
|
Idle = 'idle',
|
||||||
|
Uploading = 'uploading',
|
||||||
|
Processing = 'processing',
|
||||||
|
Failed = 'failed',
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user