@hicoder/angular-cli
Version: 
Angular UI componenets and service generator. It works with the mean-rest-express package to generate the end to end web application. The input to this generator is the Mongoose schema defined for the express application. mean-rest-express exposes the Res
386 lines (350 loc) • 12.3 kB
text/typescript
import { Component, OnChanges, OnInit, Input, SimpleChanges, LOCALE_ID, Inject } from '@angular/core';
import { formatDate } from '@angular/common';
import { MddsCommonService } from '@hicoder/angular-core';
import {
  CalendarService,
  CalendarControlType,
  CalendarEventType,
  CalendarMessage,
  getRepeatTimeSlots,
  sortAndUniqueExcludes,
} from '@hicoder/angular-calendar';
import { <%-SchemaName%>ListViewComponent } from './<%-schemaName%>-list-view.component';
export class <%-SchemaName%><%-ComponentClassName%>Component extends <%-SchemaName%>ListViewComponent implements OnInit, OnChanges {
  public defaultCalendarView: string = '<%-calendarOptions.defaultView%>';
  public weekHourRange: number[] = [<%-calendarOptions.weekHourRange.join(',')%>];
  public isReadOnly: boolean = <%-calendarOptions.isReadOnly%>;
  public groupName: string = 'Categories';
  private calendarFields = {<% for (let field of briefView) {%><%
      if (field.calendarGroup) {%>
    group: '<%-field.fieldName%>',<%
      }%><%
      if (field.calendarTitle) {%>
    title: '<%-field.fieldName%>',<%
      }%><%
      if (field.calendarStartTime) {%>
    start: '<%-field.fieldName%>',<%
      }%><%
      if (field.calendarEndTime) {%>
    end: '<%-field.fieldName%>',<%
      }%><%
      if (field.calendarRepeat) {%>
    repeat: '<%-field.fieldName%>',<%
      }%><%
      if (field.calendarRepeatStart) {%>
    repeatStart: '<%-field.fieldName%>',<%
      }%><%
      if (field.calendarRepeatEnd) {%>
    repeatEnd: '<%-field.fieldName%>',<%
      }%><%
    }%>
  };
  private allGroups = new Set<string>();
  private groups: string[] = [];
  private calendarFetchTime = new Map<string, number>(); // timestamp, keyed by month.
  private allDetails = new Map<string, any>();
  private allSchedules = new Map<string, string[]>(); // schedule ids, keyed by detail id.
  private newSchedules: any[] = [];
  private updatedSchedules: any[] = [];
  private deletedSchedules: any[] = [];
  private calendarReady: boolean = false;
  private timeZone: string;
  private locale: string;
  private calendarID: string = '';
  constructor(
    private commonService: MddsCommonService,
    public calendarService: CalendarService) {
    super();
    this.locale = this.commonService.getLocale();
    this.timeZone = this.commonService.getTimeZone();
    this.groupName = this.calendarFields.group.charAt(0).toUpperCase() + this.calendarFields.group.slice(1);
  }
  override ngOnInit() {
    super.ngOnInit();
  }
  ngOnChanges(changes: SimpleChanges) {
    // Take originalList.
    if (changes['originalList']) {
      this.generateSchedules(this.originalList as any[], false);
    }
    if (changes['editHintFields']) {
      this.createGroups(this.editHintFields);
    }
    if (changes['deletedItemIds']) {
      this.deleteSchedules(this.deletedItemIds);
    }
  }
  generateSchedules(list: any[], force: boolean) {
    for (let detail of list) {
      let d = this.allDetails.get(detail['_id']);
      if (!force && d &&
          d[this.calendarFields.title] == detail[this.calendarFields.title] &&
          d[this.calendarFields.group] == detail[this.calendarFields.group] &&
          d[this.calendarFields.start] == detail[this.calendarFields.start] &&
          d[this.calendarFields.end] == detail[this.calendarFields.end] &&
          d[this.calendarFields.repeat] == detail[this.calendarFields.repeat]
        ) {
         // Schedule not changed. Skip;
         continue; 
      }
      // Update the saved details.
      this.allDetails.set(detail['_id'], detail);
      // Delete the existing schedules.
      if (d) {
        let oldSchedules = this.allSchedules.get(detail['_id']);
        if (oldSchedules) {
          for (let sId of oldSchedules) {
            this.deletedSchedules.push({
              id: sId,
              calendarId: d[this.calendarFields.group],
            });
          }
        }
        this.allSchedules.delete(detail['_id']);
      }
      let grp = detail[this.calendarFields.group];
      if (grp && !this.allGroups.has(grp)) {
        this.groups.push(grp);
        this.allGroups.add(grp);
      }
      let timeSlots: string[][] = [[detail[this.calendarFields.start], detail[this.calendarFields.end]]];
      if (detail[this.calendarFields.repeat]) {
        timeSlots = getRepeatTimeSlots(
          this.timeZone,
          detail[this.calendarFields.start], detail[this.calendarFields.end],
          detail[this.calendarFields.repeat],
        )
      }
      let sIds: string[] = [];
      for (let [i, slot] of timeSlots.entries()) {
        let sId = `${detail['_id']}_${i}`;
        let schedule: any = {
          id: sId,
          calendarId: grp,
          title: detail[this.calendarFields.title],
          category: 'time',
          dueDateClass: '',
          isAllDay: false,
          start: slot[0],
          end: slot[1],
          recurrenceRule: detail[this.calendarFields.repeat],
        }
        this.newSchedules.push(schedule);
        sIds.push(sId);
      }
      this.allSchedules.set(detail['_id'], sIds);
    }
    this.notifyCalendar();
  }
  deleteSchedules(ids: string[]) {
    for (let id of ids) {
      let d = this.allDetails.get(id);
      if (!d) {
        continue;
      }
      this.allDetails.delete(id);
      let oldSchedules = this.allSchedules.get(id);
      if (!oldSchedules) {
        continue;
      }
      for (let sId of oldSchedules) {
        this.deletedSchedules.push({
          id: sId,
          calendarId: d[this.calendarFields.group],
        });
      }
      this.allSchedules.delete(id);
    }
    this.notifyCalendar();
  }
  // Schedule has been updated.
  changedSchedule(changes: any) {
    if (!changes['changes'] || !changes['schedule']) {
      return;
    }
    let detailId = this.formatID(changes['schedule']['id']);
    let d = this.allDetails.get(detailId);
    if (!d) {
      return;
    }
    // Start or end time has changed for this schedule. Probably through UI drag.
    if (changes['changes']['start']) {
      d[this.calendarFields.start] = changes['changes']['start'].toDate().toISOString();
    }
    if (changes['changes']['end'] ) {
      d[this.calendarFields.end] = changes['changes']['end'].toDate().toISOString();
    }
    // Notify the parent of the change.
    this.onChanged(d);
    // Update the schedule in the calendar.
    this.generateSchedules([d], true);
  }
  viewSchedule(id: string) {
    this.onEmbeddedDetail(this.formatID(id));
  }
  updateSchedule(id: string) {
    this.onEmbeddedEdit(this.formatID(id));
  }
  deleteSchedule(id: string) {
    this.onDelete(this.formatID(id), null);
  }
  deleteOneSchedule(schedule: any) {
    let detailId = this.formatID(schedule.id);
    let d = this.allDetails.get(detailId);
    if (!d || !d.repeat) {
      return;
    }
    let repeatObj = JSON.parse(d.repeat);
    if (!repeatObj.exclude) {
      repeatObj.exclude = [];
    }
    repeatObj.exclude.push(schedule['start'].toDate().toISOString());
    repeatObj.exclude = sortAndUniqueExcludes(repeatObj.exclude, this.timeZone);
    d.repeat = JSON.stringify(repeatObj);
    this.onChanged(d);
    this.generateSchedules([d], true);
  }
  addSchedule(schedule: any) {
    let detail: any = {};
    // Start or end time should be available for this schedule.
    if (schedule['start']) {
      detail[this.calendarFields.start] = schedule['start'].toDate().toISOString();
    }
    if (schedule['end'] ) {
      detail[this.calendarFields.end] = schedule['end'].toDate().toISOString();
    }
    // Notify the parent of the new added schedule. Allow parent to change the start/end date.
    this.onEmbeddedAdd(detail, true);
  }
  createGroups(editHintFields: any) {
    let grps = this.editHintFields[this.calendarFields.group]||[];
    grps = grps.map((x: any) => {return x['_id']});
    for (let grp of grps) {
      if (!grp || this.allGroups.has(grp)) {
        continue;
      }
      this.groups.push(grp);
      this.allGroups.add(grp);
    }
    this.notifyCalendar();
  }
  getCalendarData(d: Date) {
    this.notifyCalendar();
    let date = new Date(d);
    date.setDate(1);
    let year = date.getFullYear();
    let month = date.getMonth();
    let monthStr = formatDate(date, 'y-MM', 'en-US');
    date.setMonth(month+1);
    let monthStrNext = formatDate(date, 'y-MM', 'en-US');
    date.setMonth(month-1);
    let monthStrPre = formatDate(date, 'y-MM', 'en-US');
    
    let monthCache = this.calendarFetchTime.get(monthStr);
    let monthCacheNext = this.calendarFetchTime.get(monthStrNext);
    let monthCachePre = this.calendarFetchTime.get(monthStrPre);
    let currentTime = new Date().getTime();
    let invalidateTime = currentTime - 1000 * 120; // 120 seconds.
    let monthPreCached = monthCachePre && monthCachePre > invalidateTime;
    let monthCached = monthCache && monthCache > invalidateTime;
    let monthNextCached = monthCacheNext && monthCacheNext > invalidateTime;
    let dateFrom = new Date(year, month-1, 1);
    let dateTo = new Date(year, month+2, 1);
    let detail:any = {};
    detail[this.calendarFields.repeatStart] = {
        to: {year:dateTo.getFullYear(), month: dateTo.getMonth()+1, day: 1},
    }
    detail[this.calendarFields.repeatEnd] = {
      from: {year:dateFrom.getFullYear(), month: dateFrom.getMonth()+1, day: 1},
    }
    if (monthCached && monthPreCached && monthNextCached) {
      this.processSearchContext(detail);
      return;
    }
    this.searchListAll(detail);
    this.calendarFetchTime.set(monthStrPre, currentTime);
    this.calendarFetchTime.set(monthStr, currentTime);
    this.calendarFetchTime.set(monthStrNext, currentTime);
  }
  public onCalendarMessage(message: CalendarMessage) {
    // console.log('calendarEvent: ', message);
    switch (message.type) {
      case CalendarEventType.viewSchedule:
        this.viewSchedule(message.event.id);
        break;
      case CalendarEventType.addSchedule:
        this.addSchedule(message.event);
        break;
      case CalendarEventType.changedSchedule:
        this.changedSchedule(message.event);
        break;
      case CalendarEventType.updateSchedule:
        this.updateSchedule(message.event.id);
        break;
      case CalendarEventType.deleteSchedule:
        this.deleteSchedule(message.event.id);
        break;
      case CalendarEventType.deleteOneSchedule:
        this.deleteOneSchedule(message.event);
        break;
      case CalendarEventType.newDate:
        if (!this.calendarReady) {
          this.calendarReady = true;
          this.calendarID = message.id;
          // Get data with timeout to avoid list change during initialization.
          setTimeout(()=> {
            this.getCalendarData(message.event as Date);
          }, 50)
        } else {
          this.getCalendarData(message.event as Date);
        }
        break;
    }
  }
  private notifyCalendar() {
    if (!this.calendarReady || !this.calendarID) {
      return;
    }
    if (this.deletedSchedules.length > 0) {
      let schedules = this.deletedSchedules;
      this.deletedSchedules = [];
      this.calendarService.publishCalendarEvent({
        type: CalendarControlType.delSchedules,
        id: this.calendarID,
        event: schedules,
      });
    }
    if (this.newSchedules.length > 0) {
      let schedules = this.newSchedules;
      this.newSchedules = [];
      this.calendarService.publishCalendarEvent({
        type: CalendarControlType.newSchedules,
        id: this.calendarID,
        event: schedules,
      });
    }
    if (this.updatedSchedules.length > 0) {
      let schedules = this.updatedSchedules;
      this.updatedSchedules = [];
      this.calendarService.publishCalendarEvent({
        type: CalendarControlType.updSchedules,
        id: this.calendarID,
        event: schedules,
      });
    }
    if (this.groups.length > 0) {
      let groups = this.groups;
      this.groups = [];
      this.calendarService.publishCalendarEvent({
        type: CalendarControlType.newGroups,
        id: this.calendarID,
        event: groups,
      });
    }
  }
  private formatID(id: string): string {
    return id.split('_')[0];
  }
}