From e3c2b87c70558ecec54418a873dcd1bdf7b9a0bf Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Tue, 4 Nov 2025 09:33:19 -0800
Subject: [PATCH] Skeleton share bundle component
---
.../share-bundle-dialog.component.html | 67 +++++++++++++
.../share-bundle-dialog.component.scss | 0
.../share-bundle-dialog.component.spec.ts | 7 ++
.../share-bundle-dialog.component.ts | 98 +++++++++++++++++++
.../share-links-dialog.component.ts | 13 ++-
.../bulk-editor/bulk-editor.component.ts | 15 ++-
src-ui/src/app/data/share-link.ts | 12 +++
7 files changed, 203 insertions(+), 9 deletions(-)
create mode 100644 src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.html
create mode 100644 src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.scss
create mode 100644 src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.spec.ts
create mode 100644 src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.ts
diff --git a/src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.html b/src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.html
new file mode 100644
index 000000000..4635f3bec
--- /dev/null
+++ b/src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.html
@@ -0,0 +1,67 @@
+
+
+
diff --git a/src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.scss b/src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.spec.ts b/src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.spec.ts
new file mode 100644
index 000000000..baae428ec
--- /dev/null
+++ b/src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.spec.ts
@@ -0,0 +1,7 @@
+describe('ShareBundleDialogComponent', () => {
+ it('is pending implementation', () => {
+ pending(
+ 'ShareBundleDialogComponent tests will be implemented once the dialog logic is finalized.'
+ )
+ })
+})
diff --git a/src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.ts b/src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.ts
new file mode 100644
index 000000000..148e55041
--- /dev/null
+++ b/src-ui/src/app/components/common/share-bundle-dialog/share-bundle-dialog.component.ts
@@ -0,0 +1,98 @@
+import { CommonModule } from '@angular/common'
+import { Component, Input, inject } from '@angular/core'
+import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms'
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
+import {
+ FileVersion,
+ SHARE_LINK_EXPIRATION_OPTIONS,
+} from 'src/app/data/share-link'
+
+@Component({
+ selector: 'pngx-share-bundle-dialog',
+ templateUrl: './share-bundle-dialog.component.html',
+ standalone: true,
+ imports: [CommonModule, ReactiveFormsModule],
+})
+export class ShareBundleDialogComponent {
+ private activeModal = inject(NgbActiveModal)
+ private formBuilder = inject(FormBuilder)
+
+ private _documentIds: number[] = []
+ private _documentsWithArchive = 0
+
+ selectionCount = 0
+ documentPreview: number[] = []
+ form: FormGroup = this.formBuilder.group({
+ shareArchiveVersion: [true],
+ expirationDays: [7],
+ })
+
+ readonly expirationOptions = SHARE_LINK_EXPIRATION_OPTIONS
+
+ @Input()
+ set documentIds(ids: number[]) {
+ this._documentIds = ids ?? []
+ this.selectionCount = this._documentIds.length
+ this.documentPreview = this._documentIds.slice(0, 10)
+ this.syncArchiveOption()
+ }
+
+ get documentIds(): number[] {
+ return this._documentIds
+ }
+
+ @Input()
+ set documentsWithArchive(count: number) {
+ this._documentsWithArchive = count ?? 0
+ this.syncArchiveOption()
+ }
+
+ get documentsWithArchive(): number {
+ return this._documentsWithArchive
+ }
+
+ get archiveOptionDisabled(): boolean {
+ return (
+ this.selectionCount === 0 ||
+ this._documentsWithArchive !== this.selectionCount
+ )
+ }
+
+ get missingArchiveCount(): number {
+ return Math.max(this.selectionCount - this._documentsWithArchive, 0)
+ }
+
+ close() {
+ this.activeModal.close()
+ }
+
+ submit() {
+ // Placeholder until the backend workflow is wired up.
+ this.activeModal.close({
+ documentIds: this.documentIds,
+ options: {
+ fileVersion: this.form.value.shareArchiveVersion
+ ? FileVersion.Archive
+ : FileVersion.Original,
+ expirationDays: this.form.value.expirationDays,
+ },
+ })
+ }
+
+ private syncArchiveOption() {
+ const control = this.form.get('shareArchiveVersion')
+ if (!control) return
+
+ const canUseArchive =
+ this.selectionCount > 0 &&
+ this._documentsWithArchive === this.selectionCount
+
+ if (canUseArchive) {
+ control.enable({ emitEvent: false })
+ control.patchValue(true, { emitEvent: false })
+ } else {
+ control.disable({ emitEvent: false })
+ control.patchValue(false, { emitEvent: false })
+ }
+ }
+}
diff --git a/src-ui/src/app/components/common/share-links-dialog/share-links-dialog.component.ts b/src-ui/src/app/components/common/share-links-dialog/share-links-dialog.component.ts
index ffe11808c..813dbd2cf 100644
--- a/src-ui/src/app/components/common/share-links-dialog/share-links-dialog.component.ts
+++ b/src-ui/src/app/components/common/share-links-dialog/share-links-dialog.component.ts
@@ -4,7 +4,11 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { first } from 'rxjs'
-import { FileVersion, ShareLink } from 'src/app/data/share-link'
+import {
+ FileVersion,
+ SHARE_LINK_EXPIRATION_OPTIONS,
+ ShareLink,
+} from 'src/app/data/share-link'
import { ShareLinkService } from 'src/app/services/rest/share-link.service'
import { ToastService } from 'src/app/services/toast.service'
import { environment } from 'src/environments/environment'
@@ -21,12 +25,7 @@ export class ShareLinksDialogComponent implements OnInit {
private toastService = inject(ToastService)
private clipboard = inject(Clipboard)
- EXPIRATION_OPTIONS = [
- { label: $localize`1 day`, value: 1 },
- { label: $localize`7 days`, value: 7 },
- { label: $localize`30 days`, value: 30 },
- { label: $localize`Never`, value: null },
- ]
+ EXPIRATION_OPTIONS = SHARE_LINK_EXPIRATION_OPTIONS
@Input()
title = $localize`Share Links`
diff --git a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts
index f3b3f9b0e..0364ef006 100644
--- a/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts
+++ b/src-ui/src/app/components/document-list/bulk-editor/bulk-editor.component.ts
@@ -54,6 +54,7 @@ import {
} from '../../common/filterable-dropdown/filterable-dropdown.component'
import { ToggleableItemState } from '../../common/filterable-dropdown/toggleable-dropdown-button/toggleable-dropdown-button.component'
import { PermissionsDialogComponent } from '../../common/permissions-dialog/permissions-dialog.component'
+import { ShareBundleDialogComponent } from '../../common/share-bundle-dialog/share-bundle-dialog.component'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
import { CustomFieldsBulkEditDialogComponent } from './custom-fields-bulk-edit-dialog/custom-fields-bulk-edit-dialog.component'
@@ -909,9 +910,19 @@ export class BulkEditorComponent
}
shareSelected() {
- this.toastService.showInfo(
- $localize`Bulk share link creation is coming soon.`
+ const selectedDocuments = this.list.documents.filter((d) =>
+ this.list.selected.has(d.id)
)
+ const documentsWithArchive = selectedDocuments.filter(
+ (doc) => !!doc.archived_file_name
+ ).length
+
+ const modal = this.modalService.open(ShareBundleDialogComponent, {
+ backdrop: 'static',
+ size: 'lg',
+ })
+ modal.componentInstance.documentIds = Array.from(this.list.selected)
+ modal.componentInstance.documentsWithArchive = documentsWithArchive
}
manageShareLinks() {
diff --git a/src-ui/src/app/data/share-link.ts b/src-ui/src/app/data/share-link.ts
index debc8c111..d9710bd47 100644
--- a/src-ui/src/app/data/share-link.ts
+++ b/src-ui/src/app/data/share-link.ts
@@ -5,6 +5,18 @@ export enum FileVersion {
Original = 'original',
}
+export interface ShareLinkExpirationOption {
+ label: string
+ value: number | null
+}
+
+export const SHARE_LINK_EXPIRATION_OPTIONS: ShareLinkExpirationOption[] = [
+ { label: $localize`1 day`, value: 1 },
+ { label: $localize`7 days`, value: 7 },
+ { label: $localize`30 days`, value: 30 },
+ { label: $localize`Never`, value: null },
+]
+
export interface ShareLink extends ObjectWithPermissions {
created: string // Date