swimlane/ngx-ui

View on GitHub
projects/swimlane/ngx-ui/src/lib/components/time-display/time-display.component.ts

Summary

Maintainability
C
1 day
Test Coverage
import { Component, Input, HostListener, ViewEncapsulation, OnChanges, HostBinding, OnInit } from '@angular/core';
import { Clipboard } from '@angular/cdk/clipboard';

import moment from 'moment-timezone';

import { CoerceBooleanProperty } from '../../utils/coerce/coerce-boolean';
import { NotificationService } from '../notification/notification.service';
import { NotificationStyleType } from '../notification/notification-style-type.enum';

import { DATE_DISPLAY_TYPES, DATE_DISPLAY_INPUT_FORMATS, DATE_DISPLAY_FORMATS } from '../../enums/date-formats.enum';
import { Datelike } from '../date-time/date-like.type';
import { DateTimeType } from '../date-time/date-time-type.enum';
import { defaultDisplayFormat, defaultInputFormat } from '../../utils/date-formats/default-formats';

const guessTimeZone = moment.tz.guess();

@Component({
  selector: 'ngx-time',
  templateUrl: './time-display.component.html',
  styleUrls: ['./time-display.component.scss'],
  encapsulation: ViewEncapsulation.None
})
export class NgxTimeDisplayComponent implements OnInit, OnChanges {
  @Input()
  datetime: Datelike = new Date();

  @Input() precision: moment.unitOfTime.StartOf;

  @Input()
  timezone: string = guessTimeZone;

  @Input()
  defaultInputTimeZone: string;

  @Input()
  set mode(val: DATE_DISPLAY_TYPES) {
    this._mode = val;
  }
  get mode(): DATE_DISPLAY_TYPES {
    if (typeof this._mode === 'string') {
      return this._mode;
    }
    return DATE_DISPLAY_TYPES.TIMEZONE;
  }

  // date, time, dateTime
  @Input()
  set type(val: string) {
    this._type = val;
  }
  get type(): string {
    if (this._type) return this._type;
    return DateTimeType.datetime;
  }

  @Input()
  set format(val: string) {
    this._format = val;
  }
  get format(): string {
    if (this._format) return DATE_DISPLAY_FORMATS[this._format] || this._format;
    return defaultDisplayFormat(this.mode, this.type as DateTimeType, this.precision);
  }

  @Input()
  set tooltipFormat(val: string) {
    this._tooltipFormat = val;
  }
  get tooltipFormat(): string {
    if (this._tooltipFormat) return DATE_DISPLAY_FORMATS[this._tooltipFormat] || this._tooltipFormat;
    return this.format;
  }

  @Input()
  set clipFormat(val: string) {
    this._clipFormat = val;
  }
  get clipFormat(): string {
    if (this._clipFormat) return DATE_DISPLAY_FORMATS[this._clipFormat] || this._clipFormat;
    return defaultInputFormat(this.mode, this.type as DateTimeType, this.precision);
  }

  @Input()
  timezones: Record<string, string> = {
    UTC: 'Etc/UTC',
    Local: ''
  };

  @Input()
  tooltipTemplate: string;

  @HostBinding('class.ngx-time--tooltip-disabled')
  @Input()
  @CoerceBooleanProperty()
  tooltipDisabled = false;

  @Input()
  tooltipCssClass = 'date-tip-tooltip';

  @Input()
  tooltipPlacement = 'top';

  @Input()
  defaultCopyKey = 'Local';

  @Input()
  invalidDateMessage = 'Invalid date';

  @HostBinding('class.ngx-time--clickable')
  @Input()
  get clickable(): boolean {
    if (typeof this._clickable !== 'undefined') {
      return this._clickable;
    }
    return !!this.defaultCopyKey && !!this.timeValues[this.defaultCopyKey];
  }
  set clickable(val: boolean) {
    this._clickable = val;
  }

  @HostBinding('class.ngx-time--has-popup')
  get hasPopup() {
    return !this.dateInvalid && DATE_DISPLAY_TYPES.LOCAL !== this.mode;
  }

  @HostBinding('class.ngx-time--date-invalid')
  dateInvalid = true;

  timeValues = {};
  titleValue = '';
  internalDatetime: Date;
  utcDatetime: string;

  readonly DATE_DISPLAY_TYPES = DATE_DISPLAY_TYPES;
  readonly DATE_DISPLAY_FORMATS = DATE_DISPLAY_FORMATS;

  private _mode: DATE_DISPLAY_TYPES;
  private _format: string;
  private _tooltipFormat: string;
  private _clipFormat: string;
  private _clickable: boolean;
  private _type: string;

  constructor(private readonly clipboard: Clipboard, private readonly notificationService: NotificationService) {}

  ngOnInit() {
    this.update();
  }

  ngOnChanges() {
    this.update();
  }

  @HostListener('click')
  click() {
    if (this.clickable) {
      this.onClick({
        key: this.defaultCopyKey,
        value: this.timeValues[this.defaultCopyKey]
      });
    }
  }

  onClick(item: any) {
    this.clipboard.copy(item.value.clip);
    this.notificationService.create({
      body: `${item.key} date copied to clipboard`,
      styleType: NotificationStyleType.success,
      timeout: 3000
    });
  }

  private update() {
    this.internalDatetime = undefined;
    this.timeValues = {};
    this.titleValue = '';
    this.dateInvalid = true;
    this.utcDatetime = '';

    if (!this.datetime) {
      return;
    }

    if (DATE_DISPLAY_TYPES.LOCAL === this.mode) {
      const mdate = moment(this.datetime);
      this.dateInvalid = !mdate.isValid();
      this.internalDatetime = this.dateInvalid ? undefined : mdate.toDate();
      this.utcDatetime = this.dateInvalid ? '' : mdate.format('YYYY-MM-DD[T]HH:mm:ss.SSS');
      return;
    }

    const localTimezone = this.timezone || guessTimeZone;
    const inputTimezone = this.defaultInputTimeZone || localTimezone;

    const mdate = moment.tz(this.datetime as string, DATE_DISPLAY_INPUT_FORMATS, inputTimezone);
    this.dateInvalid = !mdate.isValid();
    this.internalDatetime = this.dateInvalid ? undefined : mdate.toDate();
    this.utcDatetime = this.dateInvalid ? '' : mdate.toISOString();

    if (this.dateInvalid) {
      return;
    }

    const titleValue = [];

    for (const key in this.timezones) {
      const tz = this.timezones[key] || guessTimeZone;
      const date = mdate.clone().tz(tz);
      const clip = date.format(this.clipFormat);
      const display = date.format(this.tooltipFormat);
      this.timeValues[key] = {
        key,
        clip,
        display
      };
      titleValue.push(`${display} [${key}]`);
    }
    this.titleValue = titleValue.join('\n');
  }
}