All files / app/identity/user-photos/photo-upload photo-upload.component.ts

100% Statements 17/17
100% Branches 2/2
100% Functions 3/3
100% Lines 17/17

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77                                                        1x   11x   11x   11x             11x 11x     11x             2x 2x   2x 1x               2x 2x 2x     2x 1x     1x      
import { AsyncPipe } from '@angular/common';
import {
  ChangeDetectionStrategy,
  Component,
  inject,
  input,
  viewChild,
} from '@angular/core';
import type { ElementRef, InputSignal, Signal } from '@angular/core';
import { RouterLink } from '@angular/router';
import type { Observable } from 'rxjs';
 
import { MAXIMUM_PHOTOS, UserPhotosService } from '../user-photos.service';
import type { Progress } from '../user-photos.service';
 
/** Template reference to HTML input type=file */
type FileInputRef = ElementRef<HTMLInputElement>;
 
/**
 * Handles Firebase User profile photo upload to Firebase Storage.
 */
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [ AsyncPipe, RouterLink ],
  selector: 'app-photo-upload',
  styleUrl: './photo-upload.component.scss',
  templateUrl: './photo-upload.component.html',
})
export class PhotoUploadComponent {
  /** Current number of files uploaded, from parent Component. */
  public readonly $fileCount: InputSignal<number> = input.required();
  /** Global maximum number of photos allowed for User. */
  public readonly maximumFiles: number = MAXIMUM_PHOTOS;
  /** Current user ID, from parent Component. */
  public readonly uid: InputSignal<string> = input.required();
  /**
   * Tracks the upload progress. Once complete emits a falsy value to clear progress bar and
   * re-display upload button.
   */
  public readonly uploadPercentage$: Observable<Progress | undefined>;
 
  private readonly _$fileInput: Signal<FileInputRef> = viewChild.required<FileInputRef>('photoUpload');
  private readonly _userPhotoService: UserPhotosService = inject(UserPhotosService);
 
  constructor() {
    this.uploadPercentage$ = this._userPhotoService.uploadPercentage$;
  }
 
  /**
   * Open the browser file picker UI if we think there are fewer than maximum uploads.
   */
  public openFilePicker(): void {
    const fileCount = this.$fileCount();
    const fileInpt = this._$fileInput();
 
    if (fileCount < this.maximumFiles) {
      fileInpt.nativeElement.click();
    }
  }
 
  /**
   * Upload the file(s) from the file picker automatically on success
   */
  public uploadFile(): void {
    const fileInpt = this._$fileInput();
    const uid = this.uid();
    const { files } = fileInpt.nativeElement;
 
    // This is just a type sanity check, it should never happen
    if (!files) {
      throw new Error('photoUpload input is not of type file');
    }
 
    this._userPhotoService.uploadPhoto(files, uid);
  }
}