<commit_before>import {
  AfterViewInit,
  Directive,
  ElementRef,
  HostBinding,
  HostListener,
  Input,
  OnDestroy,
} from '@angular/core';
import { decode } from 'blurhash';
import { timer } from 'rxjs';
import { ActivityEntity } from '../../../modules/newsfeed/activity/activity.service';

/**
 * Blurhash directive. apply it to any <img /> tag like so: <img m-blurhash="entity" />
 */
@Directive({
  selector: 'img[m-blurhash]',
})
export class BlurhashDirective implements AfterViewInit, OnDestroy {
  private RESOLUTION = 128;

  @HostBinding('attr.src')
  @Input()
  src: string;

  @HostListener('load')
  onLoad() {
    if (this.canvas && !this.paywalled) {
      this.removeCanvas();
    }
  }

  @HostListener('error')
  onError() {}

  @HostBinding('style.width.px')
  width: number;

  @HostBinding('style.height.px')
  height: number;

  canvas: HTMLCanvasElement;

  @Input('m-blurhash')
  entity: ActivityEntity;

  @Input('paywalled')
  paywalled;

  constructor(private el: ElementRef) {}

  ngAfterViewInit() {
    if (this.el.nativeElement.complete && !this.paywalled) {
      return null;
    }

    // preventing an ugly case where the canvas appears outside of image container
    timer(0)
      .toPromise()
      .then(() => this.drawCanvas());
  }

  /**
   * draws the blurhash canvas over the image
   * @returns { void }
   */
  drawCanvas() {
    const elementWidth = this.el?.nativeElement?.width;
    const elementHeight = this.el?.nativeElement?.height;

    let [blurhash, width, height] = [
      this.entity.blurhash || this.entity.custom_data[0]?.blurhash,
      elementWidth || this.entity.custom_data[0]?.width,
      elementHeight || this.entity.custom_data[0]?.height,
    ];

    if (!blurhash) {
      return null;
    }

    const aspecRatio = width / height;
    width = elementWidth;
    height = elementHeight || aspecRatio * elementWidth;

    if (!width || !height) {
      return null;
    }

    try {
      const pixels = decode(blurhash, this.RESOLUTION, this.RESOLUTION);
      this.canvas = document.createElement('canvas');
      const ctx = this.canvas.getContext('2d');

      ctx.canvas.width = this.RESOLUTION;
      ctx.canvas.height = this.RESOLUTION;

      const imageData = ctx.createImageData(this.RESOLUTION, this.RESOLUTION);
      imageData.data.set(pixels);
      ctx.putImageData(imageData, 0, 0);

      const scaleX = width / this.RESOLUTION;
      const scaleY = height / this.RESOLUTION;

      this.canvas.setAttribute(
        'style',
        `position: absolute; top: 0; left: 0; transition: all 0.3s; transition-timing-function: ease-out; transform-origin: top left; transform: scaleX(${scaleX}) scaleY(${scaleY})`
      );
      this.el.nativeElement.parentElement.append(this.canvas);
    } catch (e) {
      console.error('Blurhash: failed to draw canvas', e);
    }
  }

  /**
   * removes the canvas element from DOM with a transition
   */
  removeCanvas() {
    this.canvas.style.opacity = '0';
    setTimeout(() => {
      this.canvas?.remove();
    }, 300);
  }

  ngOnDestroy() {
    this.canvas?.remove();
  }
}
<commit_msg>Fix issue with blurhash race condition causing blurred canvas not to be<commit_after>import {
  AfterViewInit,
  Directive,
  ElementRef,
  HostBinding,
  HostListener,
  Input,
  OnDestroy,
} from '@angular/core';
import { decode } from 'blurhash';
import { timer } from 'rxjs';
import { ActivityEntity } from '../../../modules/newsfeed/activity/activity.service';

/**
 * Blurhash directive. apply it to any <img /> tag like so: <img m-blurhash="entity" />
 */
@Directive({
  selector: 'img[m-blurhash]',
})
export class BlurhashDirective implements AfterViewInit, OnDestroy {
  private RESOLUTION = 128;

  @HostBinding('attr.src')
  @Input()
  src: string;

  @HostListener('load')
  onLoad() {
    if (this.canvas && !this.paywalled) {
      this.removeCanvas();
    }
  }

  @HostListener('error')
  onError() {}

  @HostBinding('style.width.px')
  width: number;

  @HostBinding('style.height.px')
  height: number;

  canvas: HTMLCanvasElement;

  @Input('m-blurhash')
  entity: ActivityEntity;

  @Input('paywalled')
  paywalled;

  constructor(private el: ElementRef) {}

  ngAfterViewInit() {
    if (this.el.nativeElement.complete && !this.paywalled) {
      return null;
    }

    // preventing an ugly case where the canvas appears outside of image container
    timer(0)
      .toPromise()
      .then(() => this.drawCanvas());
  }

  /**
   * draws the blurhash canvas over the image
   * @returns { void }
   */
  drawCanvas() {
    const elementWidth = this.el?.nativeElement?.width;
    const elementHeight = this.el?.nativeElement?.height;

    let [blurhash, width, height] = [
      this.entity.blurhash || this.entity.custom_data[0]?.blurhash,
      elementWidth || this.entity.custom_data[0]?.width,
      elementHeight || this.entity.custom_data[0]?.height,
    ];

    if (!blurhash) {
      return null;
    }

    const aspecRatio = width / height;
    width = elementWidth;
    height = elementHeight || aspecRatio * elementWidth;

    if (!width || !height) {
      return null;
    }

    try {
      const pixels = decode(blurhash, this.RESOLUTION, this.RESOLUTION);
      this.canvas = document.createElement('canvas');
      const ctx = this.canvas.getContext('2d');

      ctx.canvas.width = this.RESOLUTION;
      ctx.canvas.height = this.RESOLUTION;

      const imageData = ctx.createImageData(this.RESOLUTION, this.RESOLUTION);
      imageData.data.set(pixels);
      ctx.putImageData(imageData, 0, 0);

      const scaleX = width / this.RESOLUTION;
      const scaleY = height / this.RESOLUTION;

      // if image has loaded in the time we've been processing - do not set canvas.
      if (this.el.nativeElement?.complete) {
        return;
      }

      this.canvas.setAttribute(
        'style',
        `position: absolute; top: 0; left: 0; transition: all 0.3s; transition-timing-function: ease-out; transform-origin: top left; transform: scaleX(${scaleX}) scaleY(${scaleY})`
      );
      this.el.nativeElement.parentElement.append(this.canvas);
    } catch (e) {
      console.error('Blurhash: failed to draw canvas', e);
    }
  }

  /**
   * removes the canvas element from DOM with a transition
   */
  removeCanvas() {
    this.canvas.style.opacity = '0';
    setTimeout(() => {
      this.canvas?.remove();
    }, 300);
  }

  ngOnDestroy() {
    this.canvas?.remove();
  }
}

{'fraction_non_alphanumeric': 0.1413953488372093, 'fraction_numerical': 0.005581395348837209, 'mean_word_length': 2.989486703772418, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5041218', 'n_tokens_mistral': 2121, 'n_tokens_neox': 2017, 'n_words': 462}