UNPKG

devextreme

Version:

HTML5 JavaScript Component Suite for Responsive Web Development

595 lines (592 loc) • 27.5 kB
/** * DevExtreme (cjs/__internal/scheduler/appointments/m_settings_generator.js) * Version: 24.2.6 * Build date: Mon Mar 17 2025 * * Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/ */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DateGeneratorVirtualStrategy = exports.DateGeneratorBaseStrategy = exports.AppointmentSettingsGenerator = void 0; var _date = _interopRequireDefault(require("../../../core/utils/date")); var _extend = require("../../../core/utils/extend"); var _type = require("../../../core/utils/type"); var _date2 = require("../../core/utils/date"); var _index = require("../../scheduler/r1/utils/index"); var _m_appointment_adapter = require("../m_appointment_adapter"); var _m_expression_utils = require("../m_expression_utils"); var _m_recurrence = require("../m_recurrence"); var _m_utils_time_zone = _interopRequireDefault(require("../m_utils_time_zone")); var _m_utils = require("../resources/m_utils"); var _m_cell_position_calculator = require("./m_cell_position_calculator"); var _m_text_utils = require("./m_text_utils"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e } } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function(n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) { ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]) } } return n }, _extends.apply(null, arguments) } const toMs = _date.default.dateToMilliseconds; const APPOINTMENT_DATE_TEXT_FORMAT = "TIME"; class DateGeneratorBaseStrategy { constructor(options) { this.options = options } get rawAppointment() { return this.options.rawAppointment } get timeZoneCalculator() { return this.options.timeZoneCalculator } get viewDataProvider() { return this.options.viewDataProvider } get appointmentTakesAllDay() { return this.options.appointmentTakesAllDay } get supportAllDayRow() { return this.options.supportAllDayRow } get isAllDayRowAppointment() { return this.options.isAllDayRowAppointment } get timeZone() { return this.options.timeZone } get dateRange() { return this.options.dateRange } get firstDayOfWeek() { return this.options.firstDayOfWeek } get viewStartDayHour() { return this.options.viewStartDayHour } get viewEndDayHour() { return this.options.viewEndDayHour } get endViewDate() { return this.options.endViewDate } get viewType() { return this.options.viewType } get isGroupedByDate() { return this.options.isGroupedByDate } get isVerticalOrientation() { return this.options.isVerticalGroupOrientation } get dataAccessors() { return this.options.dataAccessors } get loadedResources() { return this.options.loadedResources } get isDateAppointment() { return !(0, _index.isDateAndTimeView)(this.viewType) && this.appointmentTakesAllDay } getIntervalDuration() { return this.appointmentTakesAllDay ? this.options.allDayIntervalDuration : this.options.intervalDuration } generate(appointmentAdapter) { const { isRecurrent: isRecurrent } = appointmentAdapter; const itemGroupIndices = this._getGroupIndices(this.rawAppointment); let appointmentList = this._createAppointments(appointmentAdapter, itemGroupIndices); appointmentList = this._getProcessedByAppointmentTimeZone(appointmentList, appointmentAdapter); if (this._canProcessNotNativeTimezoneDates(appointmentAdapter)) { appointmentList = this._getProcessedNotNativeTimezoneDates(appointmentList, appointmentAdapter) } let dateSettings = this._createGridAppointmentList(appointmentList, appointmentAdapter); const firstViewDates = this._getAppointmentsFirstViewDate(dateSettings); dateSettings = this._fillNormalizedStartDate(dateSettings, firstViewDates); dateSettings = this._cropAppointmentsByStartDayHour(dateSettings, firstViewDates); dateSettings = this._fillNormalizedEndDate(dateSettings, this.rawAppointment); if (this._needSeparateLongParts()) { dateSettings = this._separateLongParts(dateSettings, appointmentAdapter) } dateSettings = this.shiftSourceAppointmentDates(dateSettings); return { dateSettings: dateSettings, itemGroupIndices: itemGroupIndices, isRecurrent: isRecurrent } } shiftSourceAppointmentDates(dateSettings) { const { viewOffset: viewOffset } = this.options; return dateSettings.map((item => _extends({}, item, { source: _extends({}, item.source, { startDate: _date2.dateUtilsTs.addOffsets(item.source.startDate, [viewOffset]), endDate: _date2.dateUtilsTs.addOffsets(item.source.endDate, [viewOffset]) }) }))) } _getProcessedByAppointmentTimeZone(appointmentList, appointment) { const hasAppointmentTimeZone = !(0, _type.isEmptyObject)(appointment.startDateTimeZone) || !(0, _type.isEmptyObject)(appointment.endDateTimeZone); if (hasAppointmentTimeZone) { const appointmentOffsets = { startDate: this.timeZoneCalculator.getOffsets(appointment.startDate, appointment.startDateTimeZone), endDate: this.timeZoneCalculator.getOffsets(appointment.endDate, appointment.endDateTimeZone) }; appointmentList.forEach((a => { const sourceOffsets_startDate = this.timeZoneCalculator.getOffsets(a.startDate, appointment.startDateTimeZone), sourceOffsets_endDate = this.timeZoneCalculator.getOffsets(a.endDate, appointment.endDateTimeZone); const startDateOffsetDiff = appointmentOffsets.startDate.appointment - sourceOffsets_startDate.appointment; const endDateOffsetDiff = appointmentOffsets.endDate.appointment - sourceOffsets_endDate.appointment; if (sourceOffsets_startDate.appointment !== sourceOffsets_startDate.common) { a.startDate = new Date(a.startDate.getTime() + startDateOffsetDiff * toMs("hour")) } if (sourceOffsets_endDate.appointment !== sourceOffsets_endDate.common) { a.endDate = new Date(a.endDate.getTime() + endDateOffsetDiff * toMs("hour")) } })) } return appointmentList } _createAppointments(appointment, groupIndices) { let appointments = this._createRecurrenceAppointments(appointment, groupIndices); if (!appointment.isRecurrent && 0 === appointments.length) { appointments.push({ startDate: appointment.startDate, endDate: appointment.endDate }) } appointments = appointments.map((item => { var _item$endDate; const resultEndTime = null === (_item$endDate = item.endDate) || void 0 === _item$endDate ? void 0 : _item$endDate.getTime(); if (item.startDate.getTime() === resultEndTime) { item.endDate.setTime(resultEndTime + toMs("minute")) } return _extends({}, item, { exceptionDate: new Date(item.startDate) }) })); return appointments } _canProcessNotNativeTimezoneDates(appointment) { const isTimeZoneSet = !(0, _type.isEmptyObject)(this.timeZone); if (!isTimeZoneSet) { return false } if (!appointment.isRecurrent) { return false } return !_m_utils_time_zone.default.isEqualLocalTimeZone(this.timeZone, appointment.startDate) } _getDateOffsetDST(date) { const dateMinusHour = new Date(date); dateMinusHour.setHours(dateMinusHour.getHours() - 1); const dateCommonOffset = this.timeZoneCalculator.getOffsets(date).common; const dateMinusHourCommonOffset = this.timeZoneCalculator.getOffsets(dateMinusHour).common; return dateMinusHourCommonOffset - dateCommonOffset } _getProcessedNotNativeDateIfCrossDST(date, offset) { return offset < 0 && 0 !== this._getDateOffsetDST(date) ? 0 : offset } _getCommonOffset(date) { return this.timeZoneCalculator.getOffsets(date).common } _getProcessedNotNativeTimezoneDates(appointmentList, appointment) { return appointmentList.map((item => { let diffStartDateOffset = this._getCommonOffset(appointment.startDate) - this._getCommonOffset(item.startDate); let diffEndDateOffset = this._getCommonOffset(appointment.endDate) - this._getCommonOffset(item.endDate); if (0 === diffStartDateOffset && 0 === diffEndDateOffset) { return item } diffStartDateOffset = this._getProcessedNotNativeDateIfCrossDST(item.startDate, diffStartDateOffset); diffEndDateOffset = this._getProcessedNotNativeDateIfCrossDST(item.endDate, diffEndDateOffset); const newStartDate = new Date(item.startDate.getTime() + diffStartDateOffset * toMs("hour")); let newEndDate = new Date(item.endDate.getTime() + diffEndDateOffset * toMs("hour")); const testNewStartDate = this.timeZoneCalculator.createDate(newStartDate, { path: "toGrid" }); const testNewEndDate = this.timeZoneCalculator.createDate(newEndDate, { path: "toGrid" }); if (appointment.duration > testNewEndDate.getTime() - testNewStartDate.getTime()) { newEndDate = new Date(newStartDate.getTime() + appointment.duration) } return _extends({}, item, { startDate: newStartDate, endDate: newEndDate, exceptionDate: new Date(newStartDate) }) })) } _needSeparateLongParts() { return this.isVerticalOrientation ? this.isGroupedByDate : this.isGroupedByDate && this.appointmentTakesAllDay } normalizeEndDateByViewEnd(rawAppointment, endDate) { let result = new Date(endDate.getTime()); const isAllDay = (0, _index.isDateAndTimeView)(this.viewType) && this.appointmentTakesAllDay; if (!isAllDay) { const roundedEndViewDate = _date.default.roundToHour(this.endViewDate); if (result > roundedEndViewDate) { result = roundedEndViewDate } } const endDayHour = this.viewEndDayHour; const allDay = _m_expression_utils.ExpressionUtils.getField(this.dataAccessors, "allDay", rawAppointment); const currentViewEndTime = new Date(new Date(endDate.getTime()).setHours(endDayHour, 0, 0, 0)); if (result.getTime() > currentViewEndTime.getTime() || allDay && result.getHours() < endDayHour) { result = currentViewEndTime } return result } _fillNormalizedEndDate(dateSettings, rawAppointment) { return dateSettings.map((item => _extends({}, item, { normalizedEndDate: this.normalizeEndDateByViewEnd(rawAppointment, item.endDate) }))) } _separateLongParts(gridAppointmentList, appointmentAdapter) { let result = []; gridAppointmentList.forEach((gridAppointment => { const maxDate = new Date(this.dateRange[1]); const { startDate: startDate, normalizedEndDate: endDateOfPart } = gridAppointment; const longStartDateParts = _date.default.getDatesOfInterval(startDate, endDateOfPart, { milliseconds: this.getIntervalDuration() }); const list = longStartDateParts.filter((startDatePart => new Date(startDatePart) < maxDate)).map((date => { const endDate = new Date(new Date(date).setMilliseconds(appointmentAdapter.duration)); const normalizedEndDate = this.normalizeEndDateByViewEnd(this.rawAppointment, endDate); return { startDate: date, endDate: endDate, normalizedEndDate: normalizedEndDate, source: gridAppointment.source } })); result = result.concat(list) })); return result } _createGridAppointmentList(appointmentList, appointmentAdapter) { return appointmentList.map((source => { const offsetDifference = appointmentAdapter.startDate.getTimezoneOffset() - source.startDate.getTimezoneOffset(); if (0 !== offsetDifference && this._canProcessNotNativeTimezoneDates(appointmentAdapter)) { source.startDate = _date2.dateUtilsTs.addOffsets(source.startDate, [offsetDifference * toMs("minute")]); source.endDate = _date2.dateUtilsTs.addOffsets(source.endDate, [offsetDifference * toMs("minute")]); source.exceptionDate = new Date(source.startDate) } const duration = source.endDate.getTime() - source.startDate.getTime(); const startDate = this.timeZoneCalculator.createDate(source.startDate, { path: "toGrid" }); const endDate = _date2.dateUtilsTs.addOffsets(startDate, [duration]); return { startDate: startDate, endDate: endDate, allDay: appointmentAdapter.allDay || false, source: source } })) } _createExtremeRecurrenceDates(groupIndex) { let startViewDate = this.appointmentTakesAllDay ? _date.default.trimTime(this.dateRange[0]) : this.dateRange[0]; let endViewDateByEndDayHour = this.dateRange[1]; if (this.timeZone) { startViewDate = this.timeZoneCalculator.createDate(startViewDate, { path: "fromGrid" }); endViewDateByEndDayHour = this.timeZoneCalculator.createDate(endViewDateByEndDayHour, { path: "fromGrid" }); const daylightOffset = _m_utils_time_zone.default.getDaylightOffsetInMs(startViewDate, endViewDateByEndDayHour); if (daylightOffset) { endViewDateByEndDayHour = new Date(endViewDateByEndDayHour.getTime() + daylightOffset) } } return [startViewDate, endViewDateByEndDayHour] } _createRecurrenceOptions(appointment, groupIndex) { const { viewOffset: viewOffset } = this.options; const originalAppointmentStartDate = _date2.dateUtilsTs.addOffsets(appointment.startDate, [viewOffset]); const originalAppointmentEndDate = _date2.dateUtilsTs.addOffsets(appointment.endDate, [viewOffset]); const [minRecurrenceDate, maxRecurrenceDate] = this._createExtremeRecurrenceDates(groupIndex); const shiftedMinRecurrenceDate = _date2.dateUtilsTs.addOffsets(minRecurrenceDate, [viewOffset]); const shiftedMaxRecurrenceDate = _date2.dateUtilsTs.addOffsets(maxRecurrenceDate, [viewOffset]); return { rule: appointment.recurrenceRule, exception: appointment.recurrenceException, min: shiftedMinRecurrenceDate, max: shiftedMaxRecurrenceDate, firstDayOfWeek: this.firstDayOfWeek, start: originalAppointmentStartDate, end: originalAppointmentEndDate, appointmentTimezoneOffset: this.timeZoneCalculator.getOriginStartDateOffsetInMs(originalAppointmentStartDate, appointment.rawAppointment.startDateTimeZone, true), getExceptionDateTimezoneOffsets: date => { const localMachineTimezoneOffset = -_m_utils_time_zone.default.getClientTimezoneOffset(date); const appointmentTimezoneOffset = this.timeZoneCalculator.getOriginStartDateOffsetInMs(date, appointment.rawAppointment.startDateTimeZone, true); const offsetDST = this._getDateOffsetDST(date); const extraSummerTimeChangeOffset = offsetDST < 0 ? offsetDST * toMs("hour") : 0; return [localMachineTimezoneOffset, appointmentTimezoneOffset, extraSummerTimeChangeOffset] } } } _createRecurrenceAppointments(appointment, groupIndices) { const { duration: duration } = appointment; const { viewOffset: viewOffset } = this.options; const option = this._createRecurrenceOptions(appointment); const generatedStartDates = (0, _m_recurrence.getRecurrenceProcessor)().generateDates(option); return generatedStartDates.map((date => { const utcDate = _m_utils_time_zone.default.createUTCDateWithLocalOffset(date); utcDate.setTime(utcDate.getTime() + duration); const endDate = _m_utils_time_zone.default.createDateFromUTCWithLocalOffset(utcDate); return { startDate: new Date(date), endDate: endDate } })).map((_ref => { let { startDate: startDate, endDate: endDate } = _ref; return { startDate: _date2.dateUtilsTs.addOffsets(startDate, [-viewOffset]), endDate: _date2.dateUtilsTs.addOffsets(endDate, [-viewOffset]) } })) } _getAppointmentsFirstViewDate(appointments) { const { viewOffset: viewOffset } = this.options; return appointments.map((appointment => { const tableFirstDate = this._getAppointmentFirstViewDate(_extends({}, appointment, { startDate: _date2.dateUtilsTs.addOffsets(appointment.startDate, [viewOffset]), endDate: _date2.dateUtilsTs.addOffsets(appointment.endDate, [viewOffset]) })); if (!tableFirstDate) { return appointment.startDate } const firstDate = _date2.dateUtilsTs.addOffsets(tableFirstDate, [-viewOffset]); return firstDate > appointment.startDate ? firstDate : appointment.startDate })) } _fillNormalizedStartDate(appointments, firstViewDates, rawAppointment) { return appointments.map(((item, idx) => _extends({}, item, { startDate: this._getAppointmentResultDate({ appointment: item, rawAppointment: rawAppointment, startDate: new Date(item.startDate), startDayHour: this.viewStartDayHour, firstViewDate: firstViewDates[idx] }) }))) } _cropAppointmentsByStartDayHour(appointments, firstViewDates) { return appointments.filter(((appointment, idx) => { if (!firstViewDates[idx]) { return false } if (this.appointmentTakesAllDay) { return true } return appointment.endDate > appointment.startDate })) } _getAppointmentResultDate(options) { const { appointment: appointment, startDayHour: startDayHour, firstViewDate: firstViewDate } = options; let { startDate: startDate } = options; let resultDate; if (this.appointmentTakesAllDay) { resultDate = _date.default.normalizeDate(startDate, firstViewDate) } else { if (startDate < firstViewDate) { startDate = firstViewDate } resultDate = _date.default.normalizeDate(appointment.startDate, startDate) } return !this.isDateAppointment ? _date.default.roundDateByStartDayHour(resultDate, startDayHour) : resultDate } _getAppointmentFirstViewDate(appointment) { const groupIndex = appointment.source.groupIndex || 0; const { startDate: startDate, endDate: endDate } = appointment; if (this.isAllDayRowAppointment || appointment.allDay) { return this.viewDataProvider.findAllDayGroupCellStartDate(groupIndex) } return this.viewDataProvider.findGroupCellStartDate(groupIndex, startDate, endDate, this.isDateAppointment) } _getGroupIndices(rawAppointment) { let result = []; if (rawAppointment && this.loadedResources.length) { const tree = (0, _m_utils.createResourcesTree)(this.loadedResources); result = (0, _m_utils.getResourceTreeLeaves)(((field, action) => (0, _m_utils.getDataAccessors)(this.options.dataAccessors.resources, field, action)), tree, rawAppointment) } return result } } exports.DateGeneratorBaseStrategy = DateGeneratorBaseStrategy; class DateGeneratorVirtualStrategy extends DateGeneratorBaseStrategy { get groupCount() { return (0, _index.getGroupCount)(this.loadedResources) } _createRecurrenceAppointments(appointment, groupIndices) { const { duration: duration } = appointment; const result = []; const validGroupIndices = this.groupCount ? groupIndices : [0]; validGroupIndices.forEach((groupIndex => { const option = this._createRecurrenceOptions(appointment, groupIndex); const generatedStartDates = (0, _m_recurrence.getRecurrenceProcessor)().generateDates(option); const recurrentInfo = generatedStartDates.map((date => { const startDate = new Date(date); const utcDate = _m_utils_time_zone.default.createUTCDateWithLocalOffset(date); utcDate.setTime(utcDate.getTime() + duration); const endDate = _m_utils_time_zone.default.createDateFromUTCWithLocalOffset(utcDate); return { startDate: startDate, endDate: endDate, groupIndex: groupIndex } })); result.push(...recurrentInfo) })); return result } _updateGroupIndices(appointments, groupIndices) { const result = []; groupIndices.forEach((groupIndex => { const groupStartDate = this.viewDataProvider.getGroupStartDate(groupIndex); if (groupStartDate) { appointments.forEach((appointment => { const appointmentCopy = (0, _extend.extend)({}, appointment); appointmentCopy.groupIndex = groupIndex; result.push(appointmentCopy) })) } })); return result } _getGroupIndices(resources) { var _groupIndices; let groupIndices = super._getGroupIndices(resources); const viewDataGroupIndices = this.viewDataProvider.getGroupIndices(); if (!(null !== (_groupIndices = groupIndices) && void 0 !== _groupIndices && _groupIndices.length)) { groupIndices = [0] } return groupIndices.filter((groupIndex => -1 !== viewDataGroupIndices.indexOf(groupIndex))) } _createAppointments(appointment, groupIndices) { const appointments = super._createAppointments(appointment, groupIndices); return !appointment.isRecurrent ? this._updateGroupIndices(appointments, groupIndices) : appointments } } exports.DateGeneratorVirtualStrategy = DateGeneratorVirtualStrategy; class AppointmentSettingsGenerator { constructor(options) { this.options = options; this.appointmentAdapter = (0, _m_appointment_adapter.createAppointmentAdapter)(this.rawAppointment, this.dataAccessors, this.timeZoneCalculator) } get rawAppointment() { return this.options.rawAppointment } get dataAccessors() { return this.options.dataAccessors } get timeZoneCalculator() { return this.options.timeZoneCalculator } get isAllDayRowAppointment() { return this.options.appointmentTakesAllDay && this.options.supportAllDayRow } get groups() { return this.options.groups } get dateSettingsStrategy() { const options = _extends({}, this.options, { isAllDayRowAppointment: this.isAllDayRowAppointment }); return this.options.isVirtualScrolling ? new DateGeneratorVirtualStrategy(options) : new DateGeneratorBaseStrategy(options) } create() { const { dateSettings: dateSettings, itemGroupIndices: itemGroupIndices, isRecurrent: isRecurrent } = this._generateDateSettings(); const cellPositions = this._calculateCellPositions(dateSettings, itemGroupIndices); const result = this._prepareAppointmentInfos(dateSettings, cellPositions, isRecurrent); return result } _generateDateSettings() { return this.dateSettingsStrategy.generate(this.appointmentAdapter) } _calculateCellPositions(dateSettings, itemGroupIndices) { const cellPositionCalculator = new _m_cell_position_calculator.CellPositionCalculator(_extends({}, this.options, { dateSettings: dateSettings })); return cellPositionCalculator.calculateCellPositions(itemGroupIndices, this.isAllDayRowAppointment, this.appointmentAdapter.isRecurrent) } _prepareAppointmentInfos(dateSettings, cellPositions, isRecurrent) { const infos = []; cellPositions.forEach((_ref2 => { let { coordinates: coordinates, dateSettingIndex: dateSettingIndex } = _ref2; const dateSetting = dateSettings[dateSettingIndex]; const dateText = this._getAppointmentDateText(dateSetting); const info = { appointment: dateSetting, sourceAppointment: dateSetting.source, dateText: dateText, isRecurrent: isRecurrent }; infos.push(_extends({}, coordinates, { info: info })) })); return infos } _getAppointmentDateText(sourceAppointment) { const { startDate: startDate, endDate: endDate, allDay: allDay } = sourceAppointment; return (0, _m_text_utils.createFormattedDateText)({ startDate: startDate, endDate: endDate, allDay: allDay, format: "TIME" }) } } exports.AppointmentSettingsGenerator = AppointmentSettingsGenerator;