All files / app/components/page-cv/cv-about-me cv-about-me.component.ts

100% Statements 43/43
100% Branches 7/7
100% Functions 13/13
100% Lines 42/42

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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211                                                            1x                 96x   96x   96x       96x   96x         96x   96x                         96x 96x   96x 96x   96x 96x 96x                 6x                     49x             34x 34x   34x 34x 272x 34x   34x                   99x               8x         7x     1x       1x 1x 1x 1x 1x         1x                 16x 12x   4x                           6x                       6x               15x 15x                 44x      
import {
  Component,
  ElementRef,
  HostListener,
  OnChanges,
  OnDestroy,
  ViewChild,
} from '@angular/core';
import { of } from 'rxjs';
import { ComponentWithText } from 'src/app/interfaces/ComponentWithText';
import { TextService } from 'src/app/services/db/text/text.service';
import { LogService } from 'src/app/services/log/log.service';
import { PreloaderService } from 'src/app/services/preloader/preloader.service';
import { scriptVar } from 'src/scripts/template/tools/setUp';
import { debounce } from 'src/scripts/tools/debounce/debounce';
import { Paragraph } from '../../classes/paragraph/paragraph';
import { CommonModule } from '@angular/common';
import { TextParagraphSetComponent } from '../../utilities/text-paragraph-set/text-paragraph-set.component';
import { VisibleToLoadTextService } from 'src/app/services/visibletoloadtext/visible-to-load-text.service';
import { Preloaders } from 'src/app/services/preloader/preloaders/preloaders';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
 
/** Component used to display About Me information. */
@Component({
  selector: 'app-cv-about-me',
  templateUrl: './cv-about-me.component.html',
  styleUrls: ['./cv-about-me.component.css'],
  standalone: true,
  imports: [CommonModule, TextParagraphSetComponent, MatProgressSpinnerModule],
})
export class CvAboutMeComponent
  implements ComponentWithText, OnDestroy, OnChanges
{
  /** The main div element of the component. */
  @ViewChild('mainDiv') mainDiv!: ElementRef<HTMLElement>;
  /**
   * Width of the bar underneath the title. It will be animated when the bar
   * enter/exits the client's view.
   */
  width = '0';
  /** Minimal scroll value to have the bar in view */
  posElementMin = 0;
  /** Maximal scroll value to have the bar in view */
  posElementMax = 0;
  /** Logger. See {@link LogService} */
  logger: LogService;
  /** About me title */
  aboutMe = of('');
  /** Link to CV (is dependent on the current language) */
  linkToCv = '';
  /**
   * Array of {@link Paragraph} used to display the content of the about me
   * section. Will be retrieved by the {@link TextService}.
   */
  paragraphs: Paragraph[] = [];
  /** Preloader for texts. */
  loaderTexts = Preloaders.TEXTS;
 
  /**
   * About me component constructor
   *
   * @param preloader The {@link PreloaderService}
   * @param elementRef The `ElementRef`
   * @param logService The {@link LogService}
   * @param languageService The {@link LanguageService}
   * @param textService The {@link TextService}
   * @param visibleToLoadTextService The {@link VisibleToLoadTextService}
   */
  constructor(
    public preloader: PreloaderService,
    private elementRef: ElementRef,
    logService: LogService,
    private textService: TextService,
    public visibleToLoadTextService: VisibleToLoadTextService
  ) {
    this.logger = logService.withClassName('CvAboutMeComponent');
    setTimeout(() => {
      this.visibleToLoadTextService.subscribe(this);
    }, 0);
  }
 
  /**
   * If the input changes, the position has to be computed again and the width
   * has to be updated again.
   */
  ngOnChanges() {
    this.updateAnimation();
  }
 
  /**
   * Update the component's texts when the language is updated. See
   * {@link VisibleToLoadTextService}. The subscriber design pattern is used and
   * this function is used when the service notifies its subscribers to update
   * the text contents after a language change. Uses {@link TextService} to get
   * those contents from the database.
   */
  updateTexts(): void {
    this.textService
      .getMultiSomeSplit([
        { selector: 'about-me-title', isSplit: false },
        { selector: 'cv-file-name', isSplit: false },
        { selector: 'about-me-content', isSplit: true },
      ])
      .subscribe((r) => {
        this.aboutMe = of(r[0] as string);
        this.linkToCv = 'pdf/' + (r[1] as string);
 
        this.paragraphs = r[2] as Paragraph[];
        this.paragraphs?.splice(1, 0, new Paragraph([]));
        this.paragraphs?.forEach((p) => (p.cssClass = 'lead'));
        this.paragraphs[7].els[1].assetHref = this.linkToCv;
 
        this.visibleToLoadTextService.textLoaded(this);
      });
  }
 
  /**
   * On destroy, the component has to be unsubscribed rom the
   * {@link VisibleToLoadTextService} to avoid having the service try to notify a
   * destroyed subscriber.
   */
  ngOnDestroy(): void {
    this.visibleToLoadTextService.unsubscribe(this);
  }
 
  /**
   * Computes the position precise position of the element in the page so that
   * the animation triggers exactly as the bar enters or leaves the viewport.
   */
  getElPos() {
    if (
      !this.elementRef.nativeElement ||
      this.elementRef.nativeElement.firstElementChild.tagName ==
        'MAT-PROGRESS-SPINNER'
    ) {
      return;
    }
    const posViewPort =
      this.elementRef.nativeElement.firstElementChild.firstElementChild.firstElementChild.getBoundingClientRect()
        .y +
      this.elementRef.nativeElement.firstElementChild.firstElementChild.firstElementChild.firstElementChild.getBoundingClientRect()
        .height;
    const viewPortOffset = scrollY;
    const viewPortHeight = window.innerHeight;
    this.posElementMax = posViewPort + viewPortOffset;
    this.posElementMin = this.posElementMax - viewPortHeight;
    this.posElementMax -= parseInt(
      getComputedStyle(document.documentElement)
        .getPropertyValue(scriptVar.cssHeaderHeight)
        .split('px')[0]
    );
    this.posElementMax +=
      this.elementRef.nativeElement.firstElementChild.firstElementChild.firstElementChild.firstElementChild.nextElementSibling.getBoundingClientRect().height;
  }
 
  /**
   * Checks if the width has to be changed (and thus, the animation started) and
   * do the change if it is the case.
   */
  updateWidth() {
    if (scrollY < this.posElementMin || scrollY > this.posElementMax) {
      this.width = '0';
    } else {
      this.width = '75%';
    }
  }
 
  /**
   * Update the trigger when the window is resized. Indeed, the bar position
   * will change since it is tied to viewport height. Uses the {@link debounce}
   * annotation to avoid firing this too much : resize events fire a lot during
   * resizing. Calls {@link updateAnimation} which does the actual update once
   * everything is loaded.
   */
  @HostListener('window:resize', ['$event'])
  @debounce()
  onResize() {
    this.updateAnimation();
  }
 
  /**
   * Update the trigger when client scrolls. Uses the {@link debounce} annotation
   * to avoid firing this too much : resize events fire a lot during resizing.
   * Calls {@link updateAnimation} which does the actual update once everything
   * is loaded.
   */
  @HostListener('window:scroll', ['$event'])
  @debounce()
  onScroll() {
    this.updateAnimation();
  }
 
  /**
   * Does the actual update of the scroll trigger amount and the possible width
   * modification (to create the animation).
   */
  updateAnimation() {
    this.getElPos();
    this.updateWidth();
  }
 
  /**
   * Get the main component element.
   *
   * @returns The element.
   */
  getElement(): ElementRef<HTMLElement> {
    return this.mainDiv;
  }
}