]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Initial result display in create dialog
authorshamoon <4887959+shamoon@users.noreply.github.com>
Wed, 5 Nov 2025 05:12:57 +0000 (21:12 -0800)
committershamoon <4887959+shamoon@users.noreply.github.com>
Wed, 5 Nov 2025 05:12:57 +0000 (21:12 -0800)
src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.html
src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.ts
src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts

index 8ff362687f66eff4cdbb8f1f645453cd0bd3a0d7..a1995706b08abc1346f18a62913584813c89624e 100644 (file)
   <button type="button" class="btn-close" aria-label="Close" (click)="cancel()"></button>
 </div>
 <div class="modal-body">
-  <form [formGroup]="form" class="d-flex flex-column gap-3">
-    <div>
-      <p class="mb-1">
-        <ng-container i18n>This dialog gathers the options for sending a single link to multiple documents.</ng-container>
-      </p>
-      <p class="mb-1">
-        <ng-container i18n>Selected documents:</ng-container>
-        {{ selectionCount }}
-      </p>
-      @if (documentPreview.length > 0) {
-        <ul class="list-unstyled small mb-0">
-          @for (docId of documentPreview; track docId) {
-            <li>
-              <code>{{ docId }}</code>
-            </li>
-          }
-          @if (selectionCount > documentPreview.length) {
-            <li>
-              <ng-container i18n>+ {{ selectionCount - documentPreview.length }} more…</ng-container>
-            </li>
-          }
-        </ul>
-      }
-    </div>
+  @if (!createdBundle) {
+    <form [formGroup]="form" class="d-flex flex-column gap-3">
+      <div>
+        <p class="mb-1">
+          <ng-container i18n>This dialog gathers the options for sending a single link to multiple documents.</ng-container>
+        </p>
+        <p class="mb-1">
+          <ng-container i18n>Selected documents:</ng-container>
+          {{ selectionCount }}
+        </p>
+        @if (documentPreview.length > 0) {
+          <ul class="list-unstyled small mb-0">
+            @for (docId of documentPreview; track docId) {
+              <li>
+                <code>{{ docId }}</code>
+              </li>
+            }
+            @if (selectionCount > documentPreview.length) {
+              <li>
+                <ng-container i18n>+ {{ selectionCount - documentPreview.length }} more…</ng-container>
+              </li>
+            }
+          </ul>
+        }
+      </div>
 
-    <div class="d-flex align-items-center justify-content-between">
-      <div class="input-group">
-        <label class="input-group-text" for="expirationDays"><ng-container i18n>Expires</ng-container>:</label>
-        <select class="form-select" id="expirationDays" formControlName="expirationDays">
-          @for (option of expirationOptions; track option.value) {
-            <option [ngValue]="option.value">{{ option.label }}</option>
-          }
-        </select>
+      <div class="d-flex align-items-center justify-content-between">
+        <div class="input-group">
+          <label class="input-group-text" for="expirationDays"><ng-container i18n>Expires</ng-container>:</label>
+          <select class="form-select" id="expirationDays" formControlName="expirationDays">
+            @for (option of expirationOptions; track option.value) {
+              <option [ngValue]="option.value">{{ option.label }}</option>
+            }
+          </select>
+        </div>
+        <div class="form-check form-switch w-100 ms-3">
+          <input
+            class="form-check-input"
+            type="checkbox"
+            role="switch"
+            id="shareArchiveSwitch"
+            formControlName="shareArchiveVersion"
+          />
+          <label class="form-check-label" for="shareArchiveSwitch" i18n>Share archive version (if available)</label>
+        </div>
       </div>
-      <div class="form-check form-switch w-100 ms-3">
-        <input
-          class="form-check-input"
-          type="checkbox"
-          role="switch"
-          id="shareArchiveSwitch"
-          formControlName="shareArchiveVersion"
-        />
-        <label class="form-check-label" for="shareArchiveSwitch" i18n>Share archive version (if available)</label>
+    </form>
+  } @else {
+    <div class="d-flex flex-column gap-3">
+      <div class="alert alert-success mb-0" role="status">
+        <h6 class="alert-heading mb-1" i18n>Share link requested</h6>
+        <p class="mb-0 small" i18n>
+          You can copy the link below or open the management screen to monitor its progress. The link will start working once it is ready.
+        </p>
       </div>
+      <dl class="row mb-0 small">
+        <dt class="col-sm-4" i18n>Status</dt>
+        <dd class="col-sm-8">
+          <span class="badge text-bg-secondary text-uppercase">{{ statusLabel(createdBundle.status) }}</span>
+        </dd>
+        <dt class="col-sm-4" i18n>Slug</dt>
+        <dd class="col-sm-8"><code>{{ createdBundle.slug }}</code></dd>
+        <dt class="col-sm-4" i18n>Link</dt>
+        <dd class="col-sm-8">
+          <div class="input-group input-group-sm">
+            <input class="form-control" type="text" [value]="getShareUrl(createdBundle)" readonly>
+            <button
+              class="btn btn-outline-primary"
+              type="button"
+              (click)="copy(createdBundle)"
+            >
+              @if (copied) {
+                <i-bs name="clipboard-check"></i-bs>
+              }
+              @if (!copied) {
+                <i-bs name="clipboard"></i-bs>
+              }
+              <span class="visually-hidden" i18n>Copy link</span>
+            </button>
+          </div>
+        </dd>
+        <dt class="col-sm-4" i18n>Documents</dt>
+        <dd class="col-sm-8">{{ createdBundle.document_count }}</dd>
+        <dt class="col-sm-4" i18n>Expires</dt>
+        <dd class="col-sm-8">
+          @if (createdBundle.expiration) {
+            {{ createdBundle.expiration | date: 'short' }}
+          }
+          @if (!createdBundle.expiration) {
+            <span i18n>Never</span>
+          }
+        </dd>
+        <dt class="col-sm-4" i18n>File version</dt>
+        <dd class="col-sm-8">{{ fileVersionLabel(createdBundle.file_version) }}</dd>
+        @if (createdBundle.size_bytes !== undefined && createdBundle.size_bytes !== null) {
+          <dt class="col-sm-4" i18n>Size</dt>
+          <dd class="col-sm-8">{{ createdBundle.size_bytes | fileSize }}</dd>
+        }
+      </dl>
     </div>
-
-
-    <div class="alert alert-info mb-0" role="alert">
-      <ng-container i18n>Bulk share link creation is experimental. Saving will attempt to start the process and show the result as a notification.</ng-container>
-    </div>
-  </form>
+  }
 </div>
 <div class="modal-footer">
   <div class="d-flex align-items-center gap-2 w-100">
       <ng-container i18n>Large bundles can take significant time to prepare and / or download.</ng-container>
     </div>
     <button type="button" class="btn btn-outline-secondary btn-sm ms-auto" (click)="cancel()">{{ cancelBtnCaption }}</button>
-    <button
-      type="button"
-      class="btn btn-primary btn-sm d-inline-flex align-items-center gap-2"
-      (click)="submit()"
-      [disabled]="loading || !buttonsEnabled">
-      @if (loading) {
-        <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
-      }
-      <span>{{ btnCaption }}</span>
-    </button>
+    @if (createdBundle) {
+      <button type="button" class="btn btn-outline-secondary btn-sm" (click)="openManage()" i18n>Open manage links</button>
+    }
+
+    @if (!createdBundle) {
+      <button
+        type="button"
+        class="btn btn-primary btn-sm d-inline-flex align-items-center gap-2"
+        (click)="submit()"
+        [disabled]="loading || !buttonsEnabled">
+        @if (loading) {
+          <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
+        }
+        <span>{{ btnCaption }}</span>
+      </button>
+    }
   </div>
 </div>
index 56a20cb91b5d6a86221e1687e5df41296e885bd2..58f3db592776afb4f9cdb376078b4605d1834523 100644 (file)
@@ -1,21 +1,36 @@
+import { Clipboard } from '@angular/cdk/clipboard'
 import { CommonModule } from '@angular/common'
 import { Component, Input, inject } from '@angular/core'
 import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'
-import { ShareBundleCreatePayload } from 'src/app/data/share-bundle'
+import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
+import {
+  ShareBundleCreatePayload,
+  ShareBundleSummary,
+} from 'src/app/data/share-bundle'
 import {
   FileVersion,
   SHARE_LINK_EXPIRATION_OPTIONS,
 } from 'src/app/data/share-link'
+import { FileSizePipe } from 'src/app/pipes/file-size.pipe'
+import { ToastService } from 'src/app/services/toast.service'
+import { environment } from 'src/environments/environment'
 import { ConfirmDialogComponent } from '../confirm-dialog/confirm-dialog.component'
 
 @Component({
   selector: 'pngx-share-bundle-dialog',
   templateUrl: './share-bundle-dialog.component.html',
   standalone: true,
-  imports: [CommonModule, ReactiveFormsModule],
+  imports: [
+    CommonModule,
+    ReactiveFormsModule,
+    NgxBootstrapIconsModule,
+    FileSizePipe,
+  ],
 })
 export class ShareBundleDialogComponent extends ConfirmDialogComponent {
   private formBuilder = inject(FormBuilder)
+  private clipboard = inject(Clipboard)
+  private toastService = inject(ToastService)
 
   private _documentIds: number[] = []
 
@@ -29,10 +44,25 @@ export class ShareBundleDialogComponent extends ConfirmDialogComponent {
 
   readonly expirationOptions = SHARE_LINK_EXPIRATION_OPTIONS
 
+  createdBundle: ShareBundleSummary | null = null
+  copied = false
+  onOpenManage?: () => void
+  readonly statusLabels: Record<ShareBundleSummary['status'], string> = {
+    pending: $localize`Pending`,
+    processing: $localize`Processing`,
+    ready: $localize`Ready`,
+    failed: $localize`Failed`,
+  }
+  readonly fileVersionLabels: Record<FileVersion, string> = {
+    [FileVersion.Archive]: $localize`Archive`,
+    [FileVersion.Original]: $localize`Original`,
+  }
+
   constructor() {
     super()
     this.loading = false
     this.title = $localize`Share Selected Documents`
+    this.btnCaption = $localize`Create`
   }
 
   @Input()
@@ -47,6 +77,7 @@ export class ShareBundleDialogComponent extends ConfirmDialogComponent {
   }
 
   submit() {
+    if (this.createdBundle) return
     this.payload = {
       document_ids: this.documentIds,
       file_version: this.form.value.shareArchiveVersion
@@ -54,6 +85,41 @@ export class ShareBundleDialogComponent extends ConfirmDialogComponent {
         : FileVersion.Original,
       expiration_days: this.form.value.expirationDays,
     }
+    this.buttonsEnabled = false
     super.confirm()
   }
+
+  getShareUrl(bundle: ShareBundleSummary): string {
+    const apiURL = new URL(environment.apiBaseUrl)
+    return `${apiURL.origin}${apiURL.pathname.replace(/\/api\/$/, '/share/')}${
+      bundle.slug
+    }`
+  }
+
+  copy(bundle: ShareBundleSummary): void {
+    const success = this.clipboard.copy(this.getShareUrl(bundle))
+    if (success) {
+      this.copied = true
+      this.toastService.showInfo($localize`Share link copied to clipboard.`)
+      setTimeout(() => {
+        this.copied = false
+      }, 3000)
+    }
+  }
+
+  openManage(): void {
+    if (this.onOpenManage) {
+      this.onOpenManage()
+    } else {
+      this.cancel()
+    }
+  }
+
+  statusLabel(status: ShareBundleSummary['status']): string {
+    return this.statusLabels[status] ?? status
+  }
+
+  fileVersionLabel(version: FileVersion): string {
+    return this.fileVersionLabels[version] ?? version
+  }
 }
index 8f47ac020888f1a01fed0e9985a0b2013dca1e23..1e1a1369a829564df60e7c2aee1aa973d1b35b7d 100644 (file)
@@ -935,10 +935,16 @@ export class BulkEditorComponent
           .createBundle(payload)
           .pipe(first())
           .subscribe({
-            next: () => {
+            next: (result) => {
               dialog.loading = false
-              dialog.buttonsEnabled = true
-              modal.close()
+              dialog.buttonsEnabled = false
+              dialog.createdBundle = result
+              dialog.copied = false
+              dialog.payload = null
+              dialog.onOpenManage = () => {
+                modal.close()
+                this.manageShareLinks()
+              }
               this.toastService.showInfo(
                 $localize`Bulk share link creation requested.`
               )