| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- import BaseFoundation, { DefaultAdapter } from '../base/foundation';
- import { format,
- getWeeksInMonth,
- getWeekOfMonth,
- isSameMonth,
- startOfMonth,
- endOfMonth,
- isBefore,
- isAfter,
- addDays,
- startOfWeek,
- differenceInCalendarDays,
- isSameDay,
- startOfDay,
- isSameWeek,
- Locale } from 'date-fns';
- import {
- parseEvent,
- parseAllDayEvent,
- calcWeekData,
- getCurrDate,
- parseWeeklyAllDayEvent,
- sortDate,
- collectDailyEvents,
- round,
- getPos,
- convertEventsArrToMap,
- filterWeeklyEvents,
- renderDailyEvent,
- calcRangeData,
- filterEvents,
- parseRangeAllDayEvent,
- DateObj,
- checkWeekend
- } from './eventUtil';
- export interface EventObject {
- [x: string]: any;
- key: string;
- allDay?: boolean;
- start?: Date;
- end?: Date;
- children?: React.ReactNode;
- }
- export interface ParsedEventsWithArray {
- day: Array<any>;
- allDay: Array<any>;
- }
- export interface ParsedEvents {
- day?: Map<string, Array<EventObject>>;
- allDay?: Map<string, Array<EventObject>>;
- }
- export interface ParsedRangeEvent extends EventObject {
- leftPos?: number;
- width?: number;
- topInd?: number;
- }
- export interface MonthlyEvent {
- day: ParsedRangeEvent[][];
- display: ParsedRangeEvent[];
- }
- export interface RangeData {
- month: string;
- week: any[];
- }
- export interface WeeklyData {
- month: string;
- week: DateObj[];
- }
- export type MonthData = Record<number, DateObj[]>;
- // export interface CalendarAdapter extends DefaultAdapter {
- // updateScrollHeight: (scrollHeight: number) => void;
- // setParsedEvents: (parsedEvents: ParsedEvents) => void;
- // cacheEventKeys: (cachedKeys: Array<string>) => void;
- // }
- export interface CalendarAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
- updateCurrPos?: (currPos: number) => void;
- updateShowCurrTime?: () => void;
- updateScrollHeight?: (scrollHeight: number) => void;
- setParsedEvents?: (parsedEvents: ParsedEvents | ParsedEventsWithArray | MonthlyEvent) => void;
- cacheEventKeys?: (cachedKeys: Array<string>) => void;
- setRangeData?: (data: RangeData) => void;
- getRangeData?: () => RangeData;
- setWeeklyData?: (data: WeeklyData) => void;
- getWeeklyData?: () => WeeklyData;
- registerClickOutsideHandler?: (key: string, cb: () => void) => void;
- unregisterClickOutsideHandler?: () => void;
- setMonthlyData?: (data: MonthData) => void;
- getMonthlyData?: () => MonthData;
- notifyClose?: (e: any, key: string) => void;
- openCard?: (key: string, spacing: boolean) => void;
- setItemLimit?: (itemLimit: number) => void;
- }
- export default class CalendarFoundation<P = Record<string, any>, S = Record<string, any>> extends BaseFoundation<CalendarAdapter<P, S>, P, S> {
- raf: number;
- constructor(adapter: CalendarAdapter<P, S>) {
- super({ ...adapter });
- }
- // eslint-disable-next-line @typescript-eslint/no-empty-function
- init() {
- }
- destroy() {
- this.raf && cancelAnimationFrame(this.raf);
- }
- initCurrTime() {
- const { showCurrTime, displayValue } = this.getProps();
- if (showCurrTime && isSameDay(displayValue, getCurrDate())) {
- this._adapter.updateShowCurrTime();
- this.getCurrLocation();
- }
- }
- notifyScrollHeight(height: number) {
- this._adapter.updateScrollHeight(height);
- }
- closeCard(e: any, key: string) {
- this._adapter.unregisterClickOutsideHandler();
- this._adapter.notifyClose(e, key);
- }
- _getDate() {
- const { displayValue } = this.getProps();
- return displayValue || getCurrDate();
- }
- showCard(e: any, key: string) {
- this._adapter.unregisterClickOutsideHandler();
- const bodyWidth = document.querySelector('body').clientWidth;
- const popoverWidth = 110;
- const spacing = bodyWidth - e.target.getBoundingClientRect().right - popoverWidth;
- this._adapter.openCard(key, spacing > 0);
- this._adapter.registerClickOutsideHandler(key, () => {
- this.closeCard(null, key);
- });
- }
- formatCbValue(val: [Date, number, number, number] | [Date]) {
- const date = val.shift() as Date;
- const dateArr = [date.getFullYear(), date.getMonth(), date.getDate(), ...val];
- // @ts-ignore skip
- return new Date(...dateArr);
- }
- /**
- *
- * find the location of showCurrTime red line
- */
- getCurrLocation() {
- let startTime: number = null;
- let pos = getPos(getCurrDate());
- this._adapter.updateCurrPos(round(pos));
- const frameFunc = () => {
- const timestamp = Date.now();
- if (!startTime) {
- startTime = timestamp;
- }
- const time = timestamp - startTime;
- if (time > 30000) {
- pos = getPos(getCurrDate());
- this._adapter.updateCurrPos(round(pos));
- startTime = timestamp;
- }
- this.raf = requestAnimationFrame(frameFunc);
- };
- this.raf = requestAnimationFrame(frameFunc);
- }
- getWeeklyData(value: Date, dateFnsLocale: Locale) {
- const data = {} as WeeklyData;
- data.month = format(value, 'LLL', { locale: dateFnsLocale });
- data.week = calcWeekData(value, 'week', dateFnsLocale);
- this._adapter.setWeeklyData(data);
- return data;
- }
- getRangeData(value: Date, dateFnsLocale: Locale) {
- const data = {} as { month: string; week: Array<DateObj> };
- const { range } = this.getProps();
- const len = differenceInCalendarDays(range[1], range[0]);
- data.month = format(value, 'LLL', { locale: dateFnsLocale });
- data.week = calcRangeData(value, range[0], len, 'week', dateFnsLocale);
- this._adapter.setRangeData(data);
- return data;
- }
- getMonthlyData(value: Date, dateFnsLocale: Locale) {
- const monthStart = startOfMonth(value);
- const data = {} as MonthData;
- const numberOfWeek = getWeeksInMonth(value);
- [...Array(numberOfWeek).keys()].map(ind => {
- data[ind] = calcWeekData(addDays(monthStart, ind * 7), 'month', dateFnsLocale);
- });
- this._adapter.setMonthlyData(data);
- return data;
- }
- // ================== Daily Event ==================
- _parseEvents(events: EventObject[]) {
- const parsed: ParsedEventsWithArray = {
- allDay: [],
- day: []
- };
- events.map(event => parseEvent(event)).forEach(item => {
- item.forEach(i => {
- i.allDay ? parsed.allDay.push(i) : parsed.day.push(i);
- });
- });
- return parsed;
- }
- getParseDailyEvents(events: EventObject[], date: Date) {
- if (!date) {
- date = this._getDate();
- }
- const parsed = this._parseEvents(events);
- const { displayValue } = this.getProps();
- const key = startOfDay(date).toString();
- parsed.allDay = convertEventsArrToMap(parsed.allDay, 'date', startOfDay, displayValue).get(key);
- parsed.day = convertEventsArrToMap(parsed.day, 'date', null, displayValue).get(key);
- if (!parsed.allDay) {
- parsed.allDay = [];
- }
- if (!parsed.day) {
- parsed.day = [];
- }
- parsed.day = parsed.day.map(item => renderDailyEvent(item));
- return parsed;
- }
- parseDailyEvents() {
- const { events, displayValue } = this.getProps();
- const parsed = this.getParseDailyEvents(events, displayValue);
- this._adapter.setParsedEvents(parsed);
- this._adapter.cacheEventKeys((events as EventObject[]).map(i => i.key));
- }
- // ================== Weekly Event ==================
- _parseWeeklyEvents(events: ParsedEvents['allDay'], weekStart: Date) {
- let parsed = [[]] as ParsedRangeEvent[][];
- const filtered = filterWeeklyEvents(events, weekStart);
- [...filtered.keys()].sort((a, b) => sortDate(a, b)).forEach(item => {
- const startDate = new Date(item);
- const curr = filtered.get(item).filter(event => isSameDay(event.date, startDate));
- parsed = parseWeeklyAllDayEvent(curr, startDate, weekStart, parsed);
- });
- return parsed;
- }
- _renderWeeklyAllDayEvent(events: ParsedRangeEvent[][]) {
- const res = [] as ParsedRangeEvent[];
- events.forEach(row => {
- const event = row.filter(item => 'leftPos' in item);
- res.push(...event);
- });
- return res;
- }
- // return parsed weekly allday events
- parseWeeklyAllDayEvents(events: ParsedEvents['allDay']) {
- const { week } = this._adapter.getWeeklyData();
- const weekStart = week[0].date;
- const parsed = this._parseWeeklyEvents(events, weekStart);
- const res = this._renderWeeklyAllDayEvent(parsed);
- return res;
- }
- getParsedWeeklyEvents(events: EventObject[]) {
- const parsed = this._parseEvents(events);
- const { displayValue } = this.getProps();
- const result: ParsedEvents = {};
- result.allDay = convertEventsArrToMap(parsed.allDay, 'start', startOfDay, displayValue);
- result.day = convertEventsArrToMap(parsed.day, 'date', null, displayValue);
- return result;
- }
- // return parsed weekly allday events
- parseWeeklyEvents() {
- const { events } = this.getProps();
- const parsed = this.getParsedWeeklyEvents(events);
- this._adapter.setParsedEvents(parsed);
- this._adapter.cacheEventKeys((events as EventObject[]).map(i => i.key));
- }
- // ================== Monthly Event ==================
- pushDayEventIntoWeekMap(item: EventObject, index: number, map: Record<string, EventObject[]>) {
- if (index in map) {
- map[index].push(item);
- } else {
- map[index] = [item];
- }
- }
- convertMapToArray(weekMap: Map<string, EventObject[]>, weekStart: Date) {
- const eventArray = [];
- for (const entry of weekMap.entries()) {
- const [key, value] = entry;
- const map = new Map();
- map.set(key, value);
- const weekEvents = this._parseWeeklyEvents(map, weekStart);
- eventArray.push(...weekEvents);
- }
- return eventArray;
- }
- getParseMonthlyEvents(itemLimit: number) {
- const parsed: any = {};
- const { displayValue, events } = this.getProps();
- const currDate = this._getDate();
- const firstDayOfMonth = startOfMonth(displayValue);
- const lastDayOfMonth = endOfMonth(displayValue);
- const res: EventObject[] = [];
- (events as EventObject[]).sort((prev, next) => {
- if (isBefore(prev.start, next.start)) {
- return -1;
- }
- if (isAfter(prev.start, next.start)) {
- return 1;
- }
- return 0;
- }).forEach(event => {
- const parsedEvent = parseAllDayEvent(event, event.allDay, currDate);
- res.push(...parsedEvent);
- });
- res.filter(item => isSameMonth(item.date, displayValue));
- res.forEach(item => {
- // WeekInd calculation error, need to consider the boundary situation at the beginning/end of the month
- // When the date falls within the month
- if (isSameMonth(item.date, displayValue)) {
- const weekInd = getWeekOfMonth(item.date) - 1;
- this.pushDayEventIntoWeekMap(item, weekInd, parsed);
- return;
- }
- // When the date is within the previous month
- if (isBefore(item.date, firstDayOfMonth)) {
- if (isSameWeek(item.date, firstDayOfMonth)) {
- this.pushDayEventIntoWeekMap(item, 0, parsed);
- }
- return;
- }
- // When the date is within the next month
- if (isAfter(item.date, lastDayOfMonth)) {
- if (isSameWeek(item.date, lastDayOfMonth)) {
- const weekInd = getWeekOfMonth(lastDayOfMonth) - 1;
- this.pushDayEventIntoWeekMap(item, weekInd, parsed);
- }
- return;
- }
- });
- Object.keys(parsed).forEach(key => {
- const week = parsed[key];
- parsed[key] = {};
- const weekStart = startOfWeek(week[0].date);
- const weekMap = convertEventsArrToMap(week, 'start', startOfDay);
- // When there are multiple events in a week, multiple events should be parsed
- // const oldParsedWeeklyEvent = this._parseWeeklyEvents(weekMap, weekStart);
- const parsedWeeklyEvent = this.convertMapToArray(weekMap, weekStart);
- parsed[key].day = collectDailyEvents(parsedWeeklyEvent);
- parsed[key].display = this._renderDisplayEvents(parsedWeeklyEvent);
- });
- return parsed as MonthlyEvent;
- }
- parseMonthlyEvents(itemLimit: number) {
- const { events } = this.getProps();
- const parsed = this.getParseMonthlyEvents(itemLimit);
- this._adapter.setParsedEvents(parsed);
- this._adapter.setItemLimit(itemLimit);
- this._adapter.cacheEventKeys((events as EventObject[]).map(i => i.key));
- }
- _renderDisplayEvents(events: ParsedRangeEvent[][]) {
- // Limits should not be added when calculating the relative position of each event, because there will be calculations that separate two events in the middle of the week
- let displayEvents: ParsedRangeEvent[] | ParsedRangeEvent[][] = events.slice();
- if (displayEvents.length) {
- displayEvents = this._renderWeeklyAllDayEvent(displayEvents);
- }
- return displayEvents;
- }
- // ================== Range Event ==================
- _parseRangeEvents(events: Map<string, EventObject[]>) {
- let parsed: Array<Array<ParsedRangeEvent>> = [[]];
- const [start, end] = this.getProp('range');
- const filtered = filterEvents(events, start, end);
- [...filtered.keys()].sort((a, b) => sortDate(a, b)).forEach(item => {
- const startDate = new Date(item);
- const curr = filtered.get(item).filter(event => isSameDay(event.date, startDate));
- parsed = parseRangeAllDayEvent(curr, startDate, start, end, parsed);
- });
- return parsed;
- }
- _renderRangeAllDayEvent(events: Array<Array<ParsedRangeEvent>>) {
- let res: Array<ParsedRangeEvent> = [];
- events.forEach(row => {
- const event = row.filter(item => 'leftPos' in item);
- res = [...res, ...event];
- });
- return res;
- }
- // return parsed weekly allday events
- parseRangeAllDayEvents(events: ParsedEvents['allDay']) {
- const parsed = this._parseRangeEvents(events);
- const res = this._renderRangeAllDayEvent(parsed);
- return res;
- }
- getParsedRangeEvents(events: EventObject[]) {
- const parsed = this._parseEvents(events);
- const [start] = this.getProp('range');
- (parsed as unknown as ParsedEvents).allDay = convertEventsArrToMap(parsed.allDay, 'start', startOfDay, start);
- ((parsed as unknown as ParsedEvents)).day = convertEventsArrToMap(parsed.day, 'date', null, start);
- return parsed as unknown as ParsedEvents;
- }
- // return parsed weekly allday events
- parseRangeEvents() {
- const { events } = this.getProps() as { events: EventObject[] };
- const parsed = this.getParsedRangeEvents(events);
- this._adapter.setParsedEvents(parsed);
- this._adapter.cacheEventKeys(events.map(i => i.key));
- }
- checkWeekend(val: Date) {
- return checkWeekend(val);
- }
- }
|