113 lines
4.4 KiB
TypeScript
113 lines
4.4 KiB
TypeScript
import { HttpEventType } from '@angular/common/http';
|
||
import { computed, Injectable, inject, signal } from '@angular/core';
|
||
import { finalize } from 'rxjs';
|
||
import { FileDownloadService } from './file-download.service';
|
||
import { TenantAttachment, TenantService } from './tenant.service';
|
||
import { withoutRecordKey, withoutSetValue } from './immutable-state';
|
||
|
||
@Injectable({ providedIn: 'root' })
|
||
export class TenantAttachmentTransferService {
|
||
private readonly tenantService = inject(TenantService);
|
||
private readonly downloads = inject(FileDownloadService);
|
||
readonly errors = signal<Record<string, string>>({});
|
||
readonly uploadingTenantIds = signal<ReadonlySet<string>>(new Set());
|
||
readonly uploadProgress = signal<Record<string, number | null>>({});
|
||
readonly deletingIds = signal<ReadonlySet<string>>(new Set());
|
||
readonly deleteErrors = signal<Record<string, string>>({});
|
||
readonly downloadingIds = computed<ReadonlySet<string>>(() => {
|
||
const prefix = 'attachment:';
|
||
return new Set(
|
||
[...this.downloads.pendingKeys()]
|
||
.filter((key) => key.startsWith(prefix))
|
||
.map((key) => key.slice(prefix.length)),
|
||
);
|
||
});
|
||
readonly downloadErrors = computed<Record<string, string>>(() => {
|
||
const prefix = 'attachment:';
|
||
return Object.fromEntries(
|
||
Object.entries(this.downloads.errors())
|
||
.filter(([key]) => key.startsWith(prefix))
|
||
.map(([key, error]) => [key.slice(prefix.length), error]),
|
||
);
|
||
});
|
||
|
||
upload(tenantId: string, file: File, onUploaded: (attachment: TenantAttachment) => void): void {
|
||
if (this.uploadingTenantIds().has(tenantId)) return;
|
||
this.errors.update((errors) => withoutRecordKey(errors, tenantId));
|
||
this.uploadingTenantIds.update((ids) => new Set(ids).add(tenantId));
|
||
this.uploadProgress.update((progress) => ({ ...progress, [tenantId]: null }));
|
||
this.tenantService
|
||
.uploadAttachment(tenantId, file)
|
||
.pipe(
|
||
finalize(() => {
|
||
this.uploadingTenantIds.update((ids) => withoutSetValue(ids, tenantId));
|
||
this.uploadProgress.update((progress) => withoutRecordKey(progress, tenantId));
|
||
}),
|
||
)
|
||
.subscribe({
|
||
next: (event) => {
|
||
if (event.type === HttpEventType.UploadProgress) {
|
||
const percent = event.total ? Math.round((event.loaded / event.total) * 100) : null;
|
||
this.uploadProgress.update((progress) => ({ ...progress, [tenantId]: percent }));
|
||
} else if (event.type === HttpEventType.Response && event.body) {
|
||
onUploaded(event.body);
|
||
}
|
||
},
|
||
error: (error: unknown) => {
|
||
const message = this.errorMessage(error, 'Impossible d’envoyer cette pièce jointe.');
|
||
this.errors.update((errors) => ({ ...errors, [tenantId]: message }));
|
||
},
|
||
});
|
||
}
|
||
|
||
download(tenantId: string, attachment: { id: string; fileName: string }): void {
|
||
this.downloads.download(
|
||
this.downloadKey(attachment.id),
|
||
this.tenantService.downloadAttachment(tenantId, attachment.id),
|
||
attachment.fileName,
|
||
'Impossible de télécharger cette pièce jointe.',
|
||
);
|
||
}
|
||
|
||
downloading(attachmentId: string): boolean {
|
||
return this.downloads.isPending(this.downloadKey(attachmentId));
|
||
}
|
||
|
||
downloadError(attachmentId: string): string {
|
||
return this.downloads.error(this.downloadKey(attachmentId));
|
||
}
|
||
|
||
delete(tenantId: string, attachmentId: string, onDeleted: () => void): void {
|
||
if (this.deletingIds().has(attachmentId)) return;
|
||
this.deletingIds.update((ids) => new Set(ids).add(attachmentId));
|
||
this.deleteErrors.update((errors) => withoutRecordKey(errors, attachmentId));
|
||
this.tenantService
|
||
.deleteAttachment(tenantId, attachmentId)
|
||
.pipe(finalize(() => this.deletingIds.update((ids) => withoutSetValue(ids, attachmentId))))
|
||
.subscribe({
|
||
next: () => onDeleted(),
|
||
error: () =>
|
||
this.deleteErrors.update((errors) => ({
|
||
...errors,
|
||
[attachmentId]: 'Impossible de supprimer cette pièce jointe.',
|
||
})),
|
||
});
|
||
}
|
||
|
||
private downloadKey(attachmentId: string): string {
|
||
return `attachment:${attachmentId}`;
|
||
}
|
||
|
||
private errorMessage(error: unknown, fallback: string): string {
|
||
return typeof error === 'object' &&
|
||
error !== null &&
|
||
'error' in error &&
|
||
typeof error.error === 'object' &&
|
||
error.error !== null &&
|
||
'message' in error.error &&
|
||
typeof error.error.message === 'string'
|
||
? error.error.message
|
||
: fallback;
|
||
}
|
||
}
|