angular-calendar-scheduler
Version:
This project provide a scheduler view component for [mattlewis92/angular-calendar](https://github.com/mattlewis92/angular-calendar).
1,169 lines (1,158 loc) • 108 kB
JavaScript
import { __decorate, __metadata, __rest, __param } from 'tslib';
import { Injectable, EventEmitter, Input, TemplateRef, Output, Component, ViewEncapsulation, Inject, LOCALE_ID, ChangeDetectorRef, Pipe, InjectionToken, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { adapterFactory } from 'angular-calendar/date-adapters/date-fns';
import { DateAdapter, CalendarEventTimesChangedEventType, CalendarEventTitleFormatter, CalendarDateFormatter, CalendarModule } from 'angular-calendar';
import { Subject } from 'rxjs';
import { isBefore } from 'date-fns';
import { CalendarDragHelper } from 'angular-calendar/esm2015/modules/common/calendar-drag-helper.provider';
import { CalendarResizeHelper } from 'angular-calendar/esm2015/modules/common/calendar-resize-helper.provider';
import * as momentImported from 'moment';
/**
* Auth configuration.
*/
let SchedulerConfig = class SchedulerConfig {
constructor(config = {}) {
this.locale = 'en';
this.headerDateFormat = 'daysRange';
function use(source, defaultValue) {
return config && source !== undefined ? source : defaultValue;
}
this.locale = use(config.locale, this.locale);
this.headerDateFormat = use(config.headerDateFormat, this.headerDateFormat);
}
};
SchedulerConfig = __decorate([
Injectable(),
__metadata("design:paramtypes", [SchedulerConfig])
], SchedulerConfig);
var DAYS_OF_WEEK;
(function (DAYS_OF_WEEK) {
DAYS_OF_WEEK[DAYS_OF_WEEK["SUNDAY"] = 0] = "SUNDAY";
DAYS_OF_WEEK[DAYS_OF_WEEK["MONDAY"] = 1] = "MONDAY";
DAYS_OF_WEEK[DAYS_OF_WEEK["TUESDAY"] = 2] = "TUESDAY";
DAYS_OF_WEEK[DAYS_OF_WEEK["WEDNESDAY"] = 3] = "WEDNESDAY";
DAYS_OF_WEEK[DAYS_OF_WEEK["THURSDAY"] = 4] = "THURSDAY";
DAYS_OF_WEEK[DAYS_OF_WEEK["FRIDAY"] = 5] = "FRIDAY";
DAYS_OF_WEEK[DAYS_OF_WEEK["SATURDAY"] = 6] = "SATURDAY";
})(DAYS_OF_WEEK || (DAYS_OF_WEEK = {}));
const DEFAULT_WEEKEND_DAYS = [
DAYS_OF_WEEK.SUNDAY,
DAYS_OF_WEEK.SATURDAY
];
const DAYS_IN_WEEK = 7;
const MINUTES_IN_HOUR = 60;
const DEFAULT_HOUR_SEGMENT_HEIGHT_PX = 40;
const DEFAULT_EVENT_WIDTH_PERCENT = 100;
const DEFAULT_HOUR_SEGMENTS = 2;
function getSchedulerViewHourGrid(dateAdapter, args) {
const viewDate = args.viewDate, hourSegments = args.hourSegments, dayStart = args.dayStart, dayEnd = args.dayEnd;
const hours = [];
const startOfView = dateAdapter.setMinutes(dateAdapter.setHours(dateAdapter.startOfDay(viewDate), dayStart.hour), dayStart.minute);
const endOfView = dateAdapter.setMinutes(dateAdapter.setHours(dateAdapter.startOfMinute(dateAdapter.endOfDay(viewDate)), dayEnd.hour), dayEnd.minute);
const segmentDuration = MINUTES_IN_HOUR / hourSegments;
const startOfViewDay = dateAdapter.startOfDay(viewDate);
const range = (start, end) => Array.from({ length: ((end + 1) - start) }, (v, k) => k + start);
const hoursInView = range(dayStart.hour, dayEnd.hour);
hoursInView.forEach((hour, i) => {
const segments = [];
for (let j = 0; j < hourSegments; j++) {
const date = dateAdapter.addMinutes(dateAdapter.addHours(startOfViewDay, hour), j * segmentDuration);
if (date >= startOfView && date < endOfView) {
segments.push({
date: date,
isStart: j === 0
});
}
}
if (segments.length > 0) {
hours.push({ segments: segments });
}
});
return hours;
}
function getSchedulerView(dateAdapter, args) {
let events = args.events || [];
if (!events) {
events = [];
}
const viewDate = args.viewDate;
const weekStartsOn = args.weekStartsOn;
const startsWithToday = args.startsWithToday;
const excluded = args.excluded || [];
const hourSegments = args.hourSegments || DEFAULT_HOUR_SEGMENTS;
const hourSegmentHeight = args.hourSegmentHeight || DEFAULT_HOUR_SEGMENT_HEIGHT_PX;
const eventWidth = args.eventWidth || DEFAULT_EVENT_WIDTH_PERCENT;
const dayStart = args.dayStart, dayEnd = args.dayEnd;
const startOfViewWeek = startsWithToday ? dateAdapter.startOfDay(viewDate) : dateAdapter.startOfWeek(viewDate, { weekStartsOn: weekStartsOn });
const endOfViewWeek = startsWithToday ? dateAdapter.addDays(dateAdapter.endOfDay(viewDate), 6) : dateAdapter.endOfWeek(viewDate, { weekStartsOn: weekStartsOn });
const eventsInWeek = getEventsInPeriod(dateAdapter, {
events: events,
periodStart: startOfViewWeek,
periodEnd: endOfViewWeek
});
const days = getSchedulerViewDays(dateAdapter, {
viewDate: viewDate,
weekStartsOn: weekStartsOn,
startsWithToday: startsWithToday,
excluded: excluded
});
days.forEach((day) => {
const startOfView = dateAdapter.setMinutes(dateAdapter.setHours(dateAdapter.startOfDay(day.date), dayStart.hour), dayStart.minute);
const endOfView = dateAdapter.setMinutes(dateAdapter.setHours(dateAdapter.startOfMinute(dateAdapter.endOfDay(day.date)), dayEnd.hour), dayEnd.minute);
const previousDayEvents = [];
const eventsInDay = getEventsInPeriod(dateAdapter, {
events: eventsInWeek,
periodStart: startOfView,
periodEnd: endOfView
});
day.events = eventsInDay
.sort((eventA, eventB) => eventA.start.valueOf() - eventB.start.valueOf())
.map((ev) => {
const eventStart = ev.start;
const eventEnd = ev.end || eventStart;
const startsBeforeDay = eventStart < startOfView;
const endsAfterDay = dateAdapter.addMinutes(eventEnd, -1) > endOfView;
const hourHeightModifier = ((hourSegments * hourSegmentHeight) + 1) / MINUTES_IN_HOUR; // +1 for the 1px segment bottom border
let top = 0;
if (eventStart > startOfView) {
top += dateAdapter.differenceInMinutes(eventStart, startOfView);
}
top *= hourHeightModifier;
const startDate = startsBeforeDay ? startOfView : eventStart;
const endDate = endsAfterDay ? endOfView : eventEnd;
let height = dateAdapter.differenceInMinutes(endDate, startDate);
if (!ev.end) {
height = hourSegmentHeight;
}
else {
height *= hourHeightModifier;
}
const bottom = top + height;
const overlappingPreviousEvents = getOverLappingDayViewEvents(previousDayEvents, top, bottom);
let left = 0;
while (overlappingPreviousEvents.some(previousEvent => previousEvent.left === left)) {
left += eventWidth;
}
const event = {
event: ev,
top: top,
height: height,
width: eventWidth,
left: left,
startsBeforeDay: startsBeforeDay,
endsAfterDay: endsAfterDay,
isProcessed: false
};
previousDayEvents.push(event);
return event;
});
day.hours = getSchedulerViewHourGrid(dateAdapter, {
viewDate: viewDate,
hourSegments: hourSegments,
dayStart: {
hour: dayStart.hour,
minute: dayStart.minute
},
dayEnd: {
hour: dayEnd.hour,
minute: dayEnd.minute
}
}).map((hour) => {
const date = new Date(day.date.getFullYear(), day.date.getMonth(), day.date.getDate(), hour.segments[0].date.getHours());
const startOfHour = new Date(day.date.getFullYear(), day.date.getMonth(), day.date.getDate(), hour.segments[0].date.getHours());
const endOfHour = dateAdapter.addMinutes(dateAdapter.addHours(startOfHour, 1), -1);
const eventsInHour = getEventsInPeriod(dateAdapter, {
events: eventsInDay,
periodStart: startOfHour,
periodEnd: endOfHour
});
const segments = hour.segments.map((segment) => {
segment.date = dateAdapter.setDate(dateAdapter.setMonth(dateAdapter.setYear(segment.date, day.date.getFullYear()), day.date.getMonth()), day.date.getDate());
const startOfSegment = segment.date;
const endOfSegment = dateAdapter.addMinutes(segment.date, MINUTES_IN_HOUR / hourSegments);
const eventsInSegment = getEventsInPeriod(dateAdapter, {
events: eventsInHour,
periodStart: startOfSegment,
periodEnd: endOfSegment
});
return {
segment: segment,
date: new Date(segment.date),
events: eventsInSegment
};
});
return {
hour: hour,
date: date,
events: eventsInHour,
segments: segments
};
});
});
return {
days: days,
period: {
events: eventsInWeek,
start: startOfViewWeek,
end: endOfViewWeek
}
};
}
function getSchedulerViewDays(dateAdapter, args) {
const viewDate = args.viewDate;
const weekStartsOn = args.weekStartsOn;
const startsWithToday = args.startsWithToday;
const excluded = args.excluded || [];
const weekendDays = args.weekendDays || DEFAULT_WEEKEND_DAYS;
const start = startsWithToday ? new Date(viewDate) : dateAdapter.startOfWeek(viewDate, { weekStartsOn: weekStartsOn });
const days = [];
const loop = (i) => {
const date = dateAdapter.addDays(start, i);
if (!excluded.some((e) => date.getDay() === e)) {
days.push(getSchedulerDay(dateAdapter, { date, weekendDays }));
}
};
for (let i = 0; i < DAYS_IN_WEEK; i++) {
loop(i);
}
return days;
}
function getSchedulerDay(dateAdapter, args) {
const date = args.date;
const today = dateAdapter.startOfDay(new Date());
return {
date: date,
isPast: date < today,
isToday: dateAdapter.isSameDay(date, today),
isFuture: date >= dateAdapter.addDays(today, 1),
isWeekend: args.weekendDays.indexOf(dateAdapter.getDay(date)) > -1,
inMonth: dateAdapter.isSameMonth(date, today),
hours: []
};
}
function getEventsInPeriod(dateAdapter, args) {
const events = args.events, periodStart = args.periodStart, periodEnd = args.periodEnd;
return events.filter((event) => isEventInPeriod(dateAdapter, { event: event, periodStart: periodStart, periodEnd: periodEnd }));
}
function isEventInPeriod(dateAdapter, args) {
const { isSameSecond } = dateAdapter;
const event = args.event, periodStart = args.periodStart, periodEnd = args.periodEnd;
const eventStart = event.start;
const eventEnd = event.end || event.start;
if (eventStart > periodStart && eventStart < periodEnd) {
return true;
}
if (eventEnd > periodStart && eventEnd < periodEnd) {
return true;
}
if (eventStart < periodStart && eventEnd > periodEnd) {
return true;
}
if (isSameSecond(eventStart, periodStart) || isSameSecond(eventStart, periodEnd)) {
return true;
}
if (isSameSecond(eventEnd, periodStart) || isSameSecond(eventEnd, periodEnd)) {
return true;
}
return false;
}
function getOverLappingDayViewEvents(events, top, bottom) {
return events.filter((previousEvent) => {
const previousEventTop = previousEvent.top;
const previousEventBottom = previousEvent.top + previousEvent.height;
if (top < previousEventBottom && previousEventBottom < bottom) {
return true;
}
else if (previousEventTop <= top && bottom <= previousEventBottom) {
return true;
}
return false;
});
}
function addPeriod(dateAdapter, period, date, amount) {
return {
day: dateAdapter.addDays,
week: dateAdapter.addWeeks,
month: dateAdapter.addMonths
}[period](date, amount);
}
function subPeriod(dateAdapter, period, date, amount) {
return {
day: dateAdapter.subDays,
week: dateAdapter.subWeeks,
month: dateAdapter.subMonths
}[period](date, amount);
}
function startOfPeriod(dateAdapter, period, date) {
return {
day: dateAdapter.startOfDay,
week: dateAdapter.startOfWeek,
month: dateAdapter.startOfMonth
}[period](date);
}
function endOfPeriod(dateAdapter, period, date) {
return {
day: dateAdapter.endOfDay,
week: dateAdapter.endOfWeek,
month: dateAdapter.endOfMonth
}[period](date);
}
const trackByDayOrEvent = (index, event) => (event.event.id ? event.event.id : event.event);
const trackByHourColumn = (index, day) => day.hours[0] ? day.hours[0].segments[0].date.toISOString() : day;
const trackByHour = (index, hour) => hour.segments[0].date.toISOString();
const trackByHourSegment = (index, segment) => segment.date.toISOString();
function getMinimumEventHeightInMinutes(hourSegments, hourSegmentHeight) {
return (MINUTES_IN_HOUR / (hourSegments * hourSegmentHeight)) * hourSegmentHeight;
}
function getDefaultEventEnd(dateAdapter, event, minimumMinutes) {
return event.end ? event.end : dateAdapter.addMinutes(event.start, minimumMinutes);
}
function roundToNearest(amount, precision) {
return Math.round(amount / precision) * precision;
}
function getMinutesMoved(movedY, hourSegments, hourSegmentHeight, eventSnapSize) {
const draggedInPixelsSnapSize = roundToNearest(movedY, eventSnapSize || hourSegmentHeight);
const pixelAmountInMinutes = MINUTES_IN_HOUR / (hourSegments * hourSegmentHeight);
return draggedInPixelsSnapSize * pixelAmountInMinutes;
}
function isDraggedWithinPeriod(newStart, newEnd, period) {
const end = newEnd || newStart;
return ((period.start <= newStart && newStart <= period.end) ||
(period.start <= end && end <= period.end));
}
function shouldFireDroppedEvent(dropEvent, date, calendarId) {
return (dropEvent.dropData &&
dropEvent.dropData.event &&
dropEvent.dropData.calendarId !== calendarId);
}
let CalendarSchedulerUtils = class CalendarSchedulerUtils {
constructor(dateAdapter) {
this.dateAdapter = dateAdapter;
}
getSchedulerViewHourGrid(args) {
return getSchedulerViewHourGrid(this.dateAdapter, args);
}
getSchedulerViewDays(args) {
return getSchedulerViewDays(this.dateAdapter, args);
}
getSchedulerView(args) {
return getSchedulerView(this.dateAdapter, args);
}
};
CalendarSchedulerUtils = __decorate([
Injectable(),
__metadata("design:paramtypes", [DateAdapter])
], CalendarSchedulerUtils);
/**
* [ngClass]="getPositioningClasses(event)"
*
* [style.top.px]="event.top"
* [style.height.px]="event.height"
* [style.left.%]="event.left"
* [style.width.%]="event.width"
*
* DRAG & DROP & RESIZE -> https://github.com/mattlewis92/angular-calendar/blob/master/projects/angular-calendar/src/modules/week/calendar-week-view.component.ts
* FLEXBOX -> https://css-tricks.com/snippets/css/a-guide-to-flexbox/
*/
let CalendarSchedulerViewComponent = class CalendarSchedulerViewComponent {
/**
* @hidden
*/
constructor(cdr, locale, config, utils, dateAdapter) {
this.cdr = cdr;
this.config = config;
this.utils = utils;
this.dateAdapter = dateAdapter;
/**
* An array of events to display on view
*/
this.events = [];
/**
* The number of segments in an hour. Must be one of 1, 2, 4, 6
*/
this.hourSegments = DEFAULT_HOUR_SEGMENTS;
/**
* The height in pixels of each hour segment
*/
this.hourSegmentHeight = DEFAULT_HOUR_SEGMENT_HEIGHT_PX;
/**
* An array of day indexes (0 = sunday, 1 = monday etc) that will be hidden on the view
*/
this.excludeDays = [];
/**
* Specify if the first day of current scheduler view has to be today or the first day of the week
*/
this.startsWithToday = false;
/**
* Specify if content must be shown or not
*/
this.showEventContent = true;
/**
* Specify if actions must be shown or not
*/
this.showEventActions = true;
/**
* Specify if status must be shown or not
*/
this.showEventStatus = true;
/**
* Specify if hour must be shown on segment or not
*/
this.showSegmentHour = false;
/**
* The grid size to snap resizing and dragging of events to
*/
this.eventSnapSize = this.hourSegmentHeight;
/**
* Whether to snap events to a grid when dragging
*/
this.snapDraggedEvents = true;
/**
* The day start hours in 24 hour time. Must be 0-23
*/
this.dayStartHour = 0;
/**
* The day start minutes. Must be 0-59
*/
this.dayStartMinute = 0;
/**
* The day end hours in 24 hour time. Must be 0-23
*/
this.dayEndHour = 23;
/**
* The day end minutes. Must be 0-59
*/
this.dayEndMinute = 59;
/**
* The width in pixels of each event on the view
*/
this.eventWidthPercent = DEFAULT_EVENT_WIDTH_PERCENT;
/**
* Called when a header week day is clicked
*/
this.dayHeaderClicked = new EventEmitter();
/**
* Called when the hour is clicked
*/
this.hourClicked = new EventEmitter();
/**
* Called when the segment is clicked
*/
this.segmentClicked = new EventEmitter();
/**
* Called when the event is clicked
*/
this.eventClicked = new EventEmitter();
/**
* Called when an event is resized or dragged and dropped
*/
this.eventTimesChanged = new EventEmitter();
/**
* @hidden
*/
this.hours = [];
/**
* @hidden
*/
// resizes: Map<CalendarSchedulerEvent, SchedulerResizeEvent> = new Map();
this.resizes = new Map();
/**
* @hidden
*/
this.eventDragEnter = 0;
/**
* @hidden
*/
this.dragActive = false;
/**
* @hidden
*/
this.calendarId = Symbol('angular calendar scheduler view id');
/**
* @hidden
*/
this.trackByHourColumn = trackByHourColumn;
/**
* @hidden
*/
this.trackByDayOrEvent = trackByDayOrEvent;
/**
* @hidden
*/
this.trackByHour = trackByHour;
/**
* @hidden
*/
this.trackByHourSegment = trackByHourSegment;
this.locale = this.config.locale || locale;
}
/**
* @hidden
*/
ngOnInit() {
if (this.refresh) {
this.refreshSubscription = this.refresh.subscribe(() => {
this.refreshAll();
this.cdr.markForCheck();
});
}
}
/**
* @hidden
*/
ngOnChanges(changes) {
if (changes.viewDate || changes.excludeDays || changes.weekendDays) {
this.refreshHeader();
}
if (changes.viewDate ||
changes.events ||
changes.dayStartHour ||
changes.dayEndHour ||
changes.dayStartMinute ||
changes.dayEndMinute ||
changes.excludeDays ||
changes.eventWidth) {
this.refreshHourGrid();
this.refreshBody();
}
}
/**
* @hidden
*/
ngOnDestroy() {
if (this.refreshSubscription) {
this.refreshSubscription.unsubscribe();
}
}
getPositioningClasses(day, event) {
const classes = [
this.getDayClass(event.start),
this.getTimeClass(day.date, event),
this.getLengthClass(day.date, event)
];
return classes.join(' ');
}
getDayClass(date) {
return '';
}
getTimeClass(date, event) {
if (this.dateAdapter.isSameDay(date, event.start)) {
let hours = event.start.getHours();
if (this.dayStartHour > 0) {
hours = event.start.getHours() - this.dayStartHour;
}
const hoursString = hours < 10 ? `0${hours}` : `${hours}`;
const minutesString = event.start.getMinutes() < 10 ? `0${event.start.getMinutes()}` : `${event.start.getMinutes()}`;
return `time${hoursString}${minutesString}`;
}
else if (isBefore(event.start, this.dateAdapter.startOfDay(date))) {
return `time0000`;
}
}
getLengthClass(date, event) {
if (this.dateAdapter.isSameDay(date, event.start)) {
const durationInMinutes = this.dateAdapter.differenceInMinutes(event.end, event.start);
const leftToEndOfDay = this.dateAdapter.differenceInMinutes(this.dateAdapter.setMinutes(this.dateAdapter.setHours(event.start, this.dayEndHour + 1), 0), event.start);
return leftToEndOfDay > durationInMinutes ? `length${durationInMinutes}` : `length${leftToEndOfDay}`;
}
else if (isBefore(event.start, this.dateAdapter.startOfDay(date))) {
let leftDurationInMinutes = 0;
if (this.dateAdapter.isSameDay(date, event.end)) {
leftDurationInMinutes = this.dateAdapter.differenceInMinutes(event.end, this.dateAdapter.startOfDay(date));
if (this.dayStartHour > 0) {
leftDurationInMinutes = (event.end.getHours() - this.dayStartHour) * MINUTES_IN_HOUR;
}
}
else {
leftDurationInMinutes = ((this.dayEndHour + 1) - this.dayStartHour) * MINUTES_IN_HOUR;
}
return `length${leftDurationInMinutes}`;
}
}
refreshHourGrid() {
this.hours = this.utils.getSchedulerViewHourGrid({
viewDate: this.viewDate,
hourSegments: this.hourSegments,
dayStart: {
hour: this.dayStartHour,
minute: this.dayStartMinute
},
dayEnd: {
hour: this.dayEndHour,
minute: this.dayEndMinute
}
});
}
refreshHeader() {
this.days = this.utils.getSchedulerViewDays({
viewDate: this.viewDate,
weekStartsOn: this.weekStartsOn,
startsWithToday: this.startsWithToday,
excluded: this.excludeDays,
weekendDays: this.weekendDays
});
}
refreshBody(events) {
this.view = this.getSchedulerView(events || this.events);
this.view.days.forEach((day) => {
day.events.forEach((event) => {
this.scaleOverlappingEvents(event.event.start, event.event.end, day.events);
});
});
if (this.dayModifier) {
this.days.forEach(day => this.dayModifier(day));
}
if (this.dayModifier || this.hourModifier || this.segmentModifier) {
this.view.days.forEach(day => {
if (this.dayModifier) {
this.dayModifier(day);
}
day.hours.forEach((hour) => {
if (this.hourModifier) {
this.hourModifier(hour);
}
hour.segments.forEach((segment) => {
if (this.segmentModifier) {
this.segmentModifier(segment);
}
});
});
});
}
if (this.eventModifier) {
this.events.forEach(event => this.eventModifier(event));
}
}
refreshAll() {
this.refreshHeader();
this.refreshHourGrid();
this.refreshBody();
}
getSchedulerView(events) {
return this.utils.getSchedulerView({
events: events,
viewDate: this.viewDate,
hourSegments: this.hourSegments,
weekStartsOn: this.weekStartsOn,
startsWithToday: this.startsWithToday,
dayStart: {
hour: this.dayStartHour,
minute: this.dayStartMinute
},
dayEnd: {
hour: this.dayEndHour,
minute: this.dayEndMinute
},
excluded: this.excludeDays,
eventWidth: this.eventWidthPercent,
hourSegmentHeight: this.hourSegmentHeight
});
}
scaleOverlappingEvents(startTime, endTime, events) {
let newStartTime = startTime;
let newEndTime = endTime;
const overlappingEvents = [];
let maxLeft = 0;
events.forEach((event) => {
if (event.isProcessed) {
return;
}
if (event.event.start < startTime && event.event.end > startTime) {
newStartTime = event.event.start;
}
else if (event.event.end > endTime && event.event.start < endTime) {
newEndTime = event.event.end;
}
else if (event.event.end <= endTime && event.event.start >= startTime) ;
else {
return;
}
if (event.left > maxLeft) {
maxLeft = event.left;
}
overlappingEvents.push(event);
});
if (startTime === newStartTime && endTime === newEndTime) {
const divisorFactor = Math.floor(maxLeft / this.eventWidthPercent) + 1;
overlappingEvents.forEach((event) => {
event.isProcessed = true;
event.left /= divisorFactor;
event.width /= divisorFactor;
});
}
else {
this.scaleOverlappingEvents(newStartTime, newEndTime, events);
}
}
//#region RESIZE
/**
* @hidden
*/
resizeStarted(eventsContainer, event, resizeEvent) {
this.resizes.set(event.event, resizeEvent);
this.dayColumnWidth = Math.floor(eventsContainer.offsetWidth / this.days.length);
const resizeHelper = new CalendarResizeHelper(eventsContainer);
this.validateResize = ({ rectangle }) => resizeHelper.validateResize({ rectangle });
this.cdr.markForCheck();
}
/**
* @hidden
*/
resizing(event, resizeEvent) {
this.resizes.set(event.event, resizeEvent);
const adjustedEvents = new Map();
const tempEvents = [...this.events];
this.resizes.forEach((lastResizeEvent, ev) => {
const newEventDates = this.getResizedEventDates(ev, lastResizeEvent);
const adjustedEvent = Object.assign({}, ev, newEventDates);
adjustedEvents.set(adjustedEvent, ev);
const eventIndex = tempEvents.indexOf(ev);
tempEvents[eventIndex] = adjustedEvent;
});
this.restoreOriginalEvents(tempEvents, adjustedEvents);
}
/**
* @hidden
*/
resizeEnded(event) {
this.view = this.getSchedulerView(this.events);
const lastResizeEvent = this.resizes.get(event.event);
this.resizes.delete(event.event);
const newEventDates = this.getResizedEventDates(event.event, lastResizeEvent);
this.eventTimesChanged.emit({
newStart: newEventDates.start,
newEnd: newEventDates.end,
event: event.event,
type: CalendarEventTimesChangedEventType.Resize
});
}
getResizedEventDates(event, resizeEvent) {
const minimumEventHeight = getMinimumEventHeightInMinutes(this.hourSegments, this.hourSegmentHeight);
const newEventDates = {
start: event.start,
end: getDefaultEventEnd(this.dateAdapter, event, minimumEventHeight)
};
const eventWithoutEnd = __rest(event, ["end"]);
const smallestResizes = {
start: this.dateAdapter.addMinutes(newEventDates.end, minimumEventHeight * -1),
end: getDefaultEventEnd(this.dateAdapter, eventWithoutEnd, minimumEventHeight)
};
if (resizeEvent.edges.left) {
const daysDiff = Math.round(+resizeEvent.edges.left / this.dayColumnWidth);
const newStart = this.dateAdapter.addDays(newEventDates.start, daysDiff);
if (newStart < smallestResizes.start) {
newEventDates.start = newStart;
}
else {
newEventDates.start = smallestResizes.start;
}
}
else if (resizeEvent.edges.right) {
const daysDiff = Math.round(+resizeEvent.edges.right / this.dayColumnWidth);
const newEnd = this.dateAdapter.addDays(newEventDates.end, daysDiff);
if (newEnd > smallestResizes.end) {
newEventDates.end = newEnd;
}
else {
newEventDates.end = smallestResizes.end;
}
}
if (resizeEvent.edges.top) {
const precision = this.eventSnapSize || this.hourSegmentHeight;
const draggedInPixelsSnapSize = Math.round(resizeEvent.edges.top / precision) * precision;
const pixelAmountInMinutes = MINUTES_IN_HOUR / (this.hourSegments * this.hourSegmentHeight);
const minutesMoved = draggedInPixelsSnapSize * pixelAmountInMinutes;
const newStart = this.dateAdapter.addMinutes(newEventDates.start, minutesMoved);
if (newStart < smallestResizes.start) {
newEventDates.start = newStart;
}
else {
newEventDates.start = smallestResizes.start;
}
}
else if (resizeEvent.edges.bottom) {
const precision = this.eventSnapSize || this.hourSegmentHeight;
const draggedInPixelsSnapSize = Math.round(resizeEvent.edges.bottom / precision) * precision;
const pixelAmountInMinutes = MINUTES_IN_HOUR / (this.hourSegments * this.hourSegmentHeight);
const minutesMoved = draggedInPixelsSnapSize * pixelAmountInMinutes;
const newEnd = this.dateAdapter.addMinutes(newEventDates.end, minutesMoved);
if (newEnd > smallestResizes.end) {
newEventDates.end = newEnd;
}
else {
newEventDates.end = smallestResizes.end;
}
}
return newEventDates;
}
//#endregion
//#region DRAG & DROP
/**
* @hidden
*/
eventDropped(dropEvent, date) {
if (shouldFireDroppedEvent(dropEvent, date, this.calendarId)) {
this.eventTimesChanged.emit({
type: CalendarEventTimesChangedEventType.Drop,
event: dropEvent.dropData.event,
newStart: date,
newEnd: null
});
}
}
/**
* @hidden
*/
dragStarted(eventsContainer, eventContainer, event) {
this.dayColumnWidth = Math.floor(eventsContainer.offsetWidth / this.days.length);
const dragHelper = new CalendarDragHelper(eventsContainer, eventContainer);
this.validateDrag = ({ x, y }) => this.resizes.size === 0 && dragHelper.validateDrag({ x, y, snapDraggedEvents: this.snapDraggedEvents });
this.dragActive = true;
this.eventDragEnter = 0;
if (!this.snapDraggedEvents && event) {
this.view.days.forEach((day) => {
const linkedEvent = day.events.find(ev => ev.event === event.event && ev !== event);
// hide any linked events while dragging
if (linkedEvent) {
linkedEvent.width = 0;
linkedEvent.height = 0;
}
});
}
this.cdr.markForCheck();
}
/**
* @hidden
*/
dragMove(event, dragEvent) {
if (this.snapDraggedEvents) {
const newEventTimes = this.getDragMovedEventTimes(event, dragEvent, this.dayColumnWidth, true);
const originalEvent = event.event;
const adjustedEvent = Object.assign({}, originalEvent, newEventTimes);
const tempEvents = this.events.map(ev => {
if (ev === originalEvent) {
return adjustedEvent;
}
return ev;
});
this.restoreOriginalEvents(tempEvents, new Map([[adjustedEvent, originalEvent]]));
}
}
dragEnded(event, dragEndEvent, dayWidth, useY = false) {
this.view = this.getSchedulerView(this.events);
this.dragActive = false;
const { start, end } = this.getDragMovedEventTimes(event, dragEndEvent, dayWidth, useY);
if (this.eventDragEnter > 0 && isDraggedWithinPeriod(start, end, this.view.period)) {
this.eventTimesChanged.emit({
newStart: start,
newEnd: end,
event: event.event,
type: CalendarEventTimesChangedEventType.Drag
});
}
}
getDragMovedEventTimes(event, dragEndEvent, dayWidth, useY) {
const daysDragged = roundToNearest(dragEndEvent.x, dayWidth) / dayWidth;
const minutesMoved = useY ?
getMinutesMoved(dragEndEvent.y, this.hourSegments, this.hourSegmentHeight, this.eventSnapSize)
: 0;
const start = this.dateAdapter.addMinutes(this.dateAdapter.addDays(event.event.start, daysDragged), minutesMoved);
let end;
if (event.event.end) {
end = this.dateAdapter.addMinutes(this.dateAdapter.addDays(event.event.end, daysDragged), minutesMoved);
}
return { start, end };
}
restoreOriginalEvents(tempEvents, adjustedEvents) {
this.refreshBody(tempEvents);
const adjustedEventsArray = tempEvents.filter(event => adjustedEvents.has(event));
this.view.days.forEach(day => {
adjustedEventsArray.forEach(adjustedEvent => {
const originalEvent = adjustedEvents.get(adjustedEvent);
const existingColumnEvent = day.events.find(ev => ev.event === adjustedEvent);
if (existingColumnEvent) {
// restore the original event so trackBy kicks in and the dom isn't changed
existingColumnEvent.event = originalEvent;
}
else {
// add a dummy event to the drop so if the event was removed from the original column the drag doesn't end early
day.events.push({
event: originalEvent,
left: 0,
top: 0,
height: 0,
width: 0,
startsBeforeDay: false,
endsAfterDay: false
});
}
});
});
adjustedEvents.clear();
}
};
__decorate([
Input(),
__metadata("design:type", Date)
], CalendarSchedulerViewComponent.prototype, "viewDate", void 0);
__decorate([
Input(),
__metadata("design:type", Array)
], CalendarSchedulerViewComponent.prototype, "events", void 0);
__decorate([
Input(),
__metadata("design:type", Number)
], CalendarSchedulerViewComponent.prototype, "hourSegments", void 0);
__decorate([
Input(),
__metadata("design:type", Number)
], CalendarSchedulerViewComponent.prototype, "hourSegmentHeight", void 0);
__decorate([
Input(),
__metadata("design:type", Array)
], CalendarSchedulerViewComponent.prototype, "excludeDays", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], CalendarSchedulerViewComponent.prototype, "startsWithToday", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], CalendarSchedulerViewComponent.prototype, "showEventContent", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], CalendarSchedulerViewComponent.prototype, "showEventActions", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], CalendarSchedulerViewComponent.prototype, "showEventStatus", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], CalendarSchedulerViewComponent.prototype, "showSegmentHour", void 0);
__decorate([
Input(),
__metadata("design:type", Function)
], CalendarSchedulerViewComponent.prototype, "dayModifier", void 0);
__decorate([
Input(),
__metadata("design:type", Function)
], CalendarSchedulerViewComponent.prototype, "hourModifier", void 0);
__decorate([
Input(),
__metadata("design:type", Function)
], CalendarSchedulerViewComponent.prototype, "segmentModifier", void 0);
__decorate([
Input(),
__metadata("design:type", Function)
], CalendarSchedulerViewComponent.prototype, "eventModifier", void 0);
__decorate([
Input(),
__metadata("design:type", Subject)
], CalendarSchedulerViewComponent.prototype, "refresh", void 0);
__decorate([
Input(),
__metadata("design:type", String)
], CalendarSchedulerViewComponent.prototype, "locale", void 0);
__decorate([
Input(),
__metadata("design:type", Number)
], CalendarSchedulerViewComponent.prototype, "eventSnapSize", void 0);
__decorate([
Input(),
__metadata("design:type", Boolean)
], CalendarSchedulerViewComponent.prototype, "snapDraggedEvents", void 0);
__decorate([
Input(),
__metadata("design:type", Number)
], CalendarSchedulerViewComponent.prototype, "weekStartsOn", void 0);
__decorate([
Input(),
__metadata("design:type", TemplateRef)
], CalendarSchedulerViewComponent.prototype, "headerTemplate", void 0);
__decorate([
Input(),
__metadata("design:type", TemplateRef)
], CalendarSchedulerViewComponent.prototype, "cellTemplate", void 0);
__decorate([
Input(),
__metadata("design:type", TemplateRef)
], CalendarSchedulerViewComponent.prototype, "eventTemplate", void 0);
__decorate([
Input(),
__metadata("design:type", TemplateRef)
], CalendarSchedulerViewComponent.prototype, "eventTitleTemplate", void 0);
__decorate([
Input(),
__metadata("design:type", TemplateRef)
], CalendarSchedulerViewComponent.prototype, "allDayEventTemplate", void 0);
__decorate([
Input(),
__metadata("design:type", Array)
], CalendarSchedulerViewComponent.prototype, "weekendDays", void 0);
__decorate([
Input(),
__metadata("design:type", Number)
], CalendarSchedulerViewComponent.prototype, "dayStartHour", void 0);
__decorate([
Input(),
__metadata("design:type", Number)
], CalendarSchedulerViewComponent.prototype, "dayStartMinute", void 0);
__decorate([
Input(),
__metadata("design:type", Number)
], CalendarSchedulerViewComponent.prototype, "dayEndHour", void 0);
__decorate([
Input(),
__metadata("design:type", Number)
], CalendarSchedulerViewComponent.prototype, "dayEndMinute", void 0);
__decorate([
Input(),
__metadata("design:type", Number)
], CalendarSchedulerViewComponent.prototype, "eventWidthPercent", void 0);
__decorate([
Output(),
__metadata("design:type", EventEmitter)
], CalendarSchedulerViewComponent.prototype, "dayHeaderClicked", void 0);
__decorate([
Output(),
__metadata("design:type", EventEmitter)
], CalendarSchedulerViewComponent.prototype, "hourClicked", void 0);
__decorate([
Output(),
__metadata("design:type", EventEmitter)
], CalendarSchedulerViewComponent.prototype, "segmentClicked", void 0);
__decorate([
Output(),
__metadata("design:type", EventEmitter)
], CalendarSchedulerViewComponent.prototype, "eventClicked", void 0);
__decorate([
Output(),
__metadata("design:type", EventEmitter)
], CalendarSchedulerViewComponent.prototype, "eventTimesChanged", void 0);
CalendarSchedulerViewComponent = __decorate([
Component({
selector: 'calendar-scheduler-view',
template: `
<div class="cal-scheduler-view">
<calendar-scheduler-header
[days]="days"
[locale]="locale"
[customTemplate]="headerTemplate"
(dayHeaderClicked)="dayHeaderClicked.emit($event)">
</calendar-scheduler-header>
<div class="cal-scheduler" #calendarContainer>
<div class="cal-scheduler-hour-rows aside">
<div class="cal-scheduler-hour align-center horizontal" *ngFor="let hour of hours; trackBy:trackByHour">
<div class="cal-scheduler-time">
<div class="cal-scheduler-time-segment" *ngFor="let segment of hour.segments"
[style.height.px]="hourSegmentHeight">
{{ segment.date | calendarDate:'dayViewHour':locale }}
</div>
</div>
</div>
</div>
<div class="cal-scheduler-cols aside" #dayColumns
[class.cal-resize-active]="resizes.size > 0"
mwlDroppable
(dragEnter)="eventDragEnter = eventDragEnter + 1"
(dragLeave)="eventDragEnter = eventDragEnter - 1">
<div class="cal-scheduler-col"
*ngFor="let day of view.days; trackBy:trackByHourColumn"
[ngClass]="day?.cssClass"
[style.backgroundColor]="day.backgroundColor">
<div #eventContainer
class="cal-scheduler-event-container"
*ngFor="let event of day.events; trackBy:trackByDayOrEvent"
[ngClass]="event.event?.cssClass"
[hidden]="event.height === 0 && event.width === 0"
[style.top.px]="event.top"
[style.height.px]="event.height"
[style.left.%]="event.left"
[style.width.%]="event.width"
mwlResizable
[resizeSnapGrid]="{left: dayColumnWidth, right: dayColumnWidth, top: eventSnapSize || hourSegmentHeight, bottom: eventSnapSize || hourSegmentHeight}"
[validateResize]="validateResize"
[allowNegativeResizes]="true"
(resizeStart)="resizeStarted(dayColumns, event, $event)"
(resizing)="resizing(event, $event)"
(resizeEnd)="resizeEnded(event)"
mwlDraggable
dragActiveClass="cal-drag-active"
[dropData]="{event: event.event, calendarId: calendarId}"
[dragAxis]="{
x: event.event.draggable && resizes.size === 0,
y: event.event.draggable && resizes.size === 0
}"
[dragSnapGrid]="snapDraggedEvents ? {x: dayColumnWidth, y: eventSnapSize || hourSegmentHeight} : {}"
[ghostDragEnabled]="!snapDraggedEvents"
[validateDrag]="validateDrag"
(dragPointerDown)="dragStarted(dayColumns, eventContainer, event)"
(dragging)="dragMove(event, $event)"
(dragEnd)="dragEnded(event, $event, dayColumnWidth, true)">
<div *ngIf="event.event?.resizable?.beforeStart && !event.startsBeforeDay"
class="cal-resize-handle cal-resize-handle-before-start"
mwlResizeHandle
[resizeEdges]="{
left: true,
top: true
}">
</div>
<calendar-scheduler-event
[day]="day"
[event]="event"
[showContent]="showEventContent && event.height >= 75"
[showActions]="showEventActions"
[showStatus]="showEventStatus"
[customTemplate]="eventTemplate"
[eventTitleTemplate]="eventTitleTemplate"
(eventClicked)="eventClicked.emit($event)">
</calendar-scheduler-event>
<div *ngIf="event.event?.resizable?.afterEnd && !event.endsAfterDay"
class="cal-resize-handle cal-resize-handle-after-end"
mwlResizeHandle
[resizeEdges]="{
right: true,
bottom: true
}">
</div>
</div>
<div class="cal-scheduler-hour"
*ngFor="let hour of day.hours; let i = index; trackBy:trackByHour"
[class.cal-even]="i % 2 === 0"
[class.cal-odd]="i % 2 === 1"
[ngClass]="hour.cssClass"
[style.backgroundColor]="hour.backgroundColor"
(mwlClick)="hourClicked.emit({hour: hour})"
[class.cal-past]="day.isPast"
[class.cal-today]="day.isToday"
[class.cal-future]="day.isFuture"
[class.cal-weekend]="day.isWeekend"
[class.cal-in-month]="day.inMonth"
[class.cal-out-month]="!day.inMonth">
<div class="cal-scheduler-hour-segments">
<calendar-scheduler-hour-segment
*ngFor="let segment of hour.segments; trackBy:trackByHourSegment"
[day]="day"
[segment]="segment"
[locale]="locale"
[customTemplate]="cellTemplate"
[hourSegmentHeight]="hourSegmentHeight"
[showHour]="showSegmentHour"
(segmentClicked)="segmentClicked.emit($event)"
mwlDroppable
[dragOverClass]="!dragActive || !snapDraggedEvents ? 'cal-drag-over' : 'null'"
dragActiveClass="cal-drag-active"
(drop)="eventDropped($event, segment.date)">
</calendar-scheduler-hour-segment>
</div>
</div>
</div>
</div>
</div>
</div>
`,
encapsulation: ViewEncapsulation.None,
styles: [".cal-scheduler-view *{box-sizing:border-box}.cal-scheduler-view .cal-scheduler-headers{display:flex;flex-flow:row wrap;margin-bottom:3px;border:1px solid #e1e1e1}.cal-scheduler-view .cal-scheduler-headers .aside{flex:1 0}.cal-scheduler-view .cal-scheduler-headers .aside.cal-header-clock{max-width:5em}.cal-scheduler-view .cal-scheduler-headers .cal-header{flex:1;text-align:center;padding:5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:default}.cal-scheduler-view .cal-scheduler-headers .cal-header:not(:last-child){border-right:1px solid #e1e1e1}.cal-scheduler-view .cal-scheduler-headers .cal-header:hover{background-color:#ededed}.cal-scheduler-view .cal-scheduler-headers .cal-header.cal-today{background-color:#e8fde7}.cal-scheduler-view .cal-scheduler-headers .cal-header.cal-weekend span{color:#8b0000}.cal-scheduler-view .cal-scheduler-headers .cal-header span{font-weight:400;opacity:.5}.cal-scheduler-view .cal-scheduler,.cal-scheduler-view .cal-scheduler-headers .cal-header-cols{display:flex;flex-flow:row wrap}.cal-scheduler-view .cal-scheduler .aside{flex:1 0}.cal-scheduler-view .cal-scheduler .aside.cal-scheduler-hour-rows{max-width:5em}.cal-scheduler-view .cal-scheduler .cal-scheduler-hour-rows{width:auto!important;border:1px solid #e1e1e1;overflow:h