AnonSec Shell
Server IP : 85.193.89.191  /  Your IP : 18.218.13.27
Web Server : Apache
System : Linux 956367-cx40159.tmweb.ru 3.10.0-1160.105.1.el7.x86_64 #1 SMP Thu Dec 7 15:39:45 UTC 2023 x86_64
User : bitrix ( 600)
PHP Version : 8.1.27
Disable Function : NONE
MySQL : OFF  |  cURL : OFF  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : ON  |  Pkexec : ON
Directory :  /home/bitrix/www/bitrix/js/calendar/planner/src/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ HOME ]     

Current File : /home/bitrix/www/bitrix/js/calendar/planner/src/planner.js
// @flow
import {Runtime, Type, Event, Loc, Dom, Tag, Text, Browser} from 'main.core';
import {Util} from 'calendar.util';
import {EventEmitter, BaseEvent} from 'main.core.events';
import {Selector} from './selector.js';
import {PopupWindowManager} from "main.popup";

export class Planner extends EventEmitter
{
	DOM = {};
	config = {};
	entryStatusMap = {
		h : 'user-status-h',
		y : 'user-status-y',
		q : 'user-status-q',
		n : 'user-status-n',
		tzAll: 'user-status-different-timezone',
	};
	scaleTypes = ['15min','30min','1hour', '2hour', '1day'];
	savedScaleType = null;
	SCALE_OFFSET_BEFORE = 0;  // in days
	SCALE_OFFSET_AFTER = 13;  // in days
	EXPAND_OFFSET = 3; // in days
	EXPAND_DELAY = 2000; // ms
	REBUILD_DELAY = 100;
	maxTimelineSize = 300;
	initialMinEntryRows = 3;
	MIN_ENTRY_ROWS = this.initialMinEntryRows;
	MAX_ENTRY_ROWS = 300;
	width = 700;
	height = 84;
	minWidth = 700;
	minHeight = 84;
	workTime = [8, 18];
	warningHoursFrom = 9;
	warningHoursTo = 18;
	scrollStep = 10;
	shown = false;
	built = false;
	locked = false;
	shownScaleTimeFrom = 24;
	shownScaleTimeTo = 0;
	timelineCellWidthOrig = false;
	proposeTimeLimit = 14; // in days
	expandTimelineDelay = 600;
	limitScaleSizeMode = false;
	useAnimation = true;
	checkTimeCache = {};
	entriesIndex = new Map();
	solidStatus = false;

	constructor(params = {})
	{
		super();
		this.setEventNamespace('BX.Calendar.Planner');
		this.config = params;
		this.id = params.id;
		this.dayOfWeekMonthFormat = params.dayOfWeekMonthFormat || 'd F, l';
		this.userId = parseInt(params.userId || Loc.getMessage('USER_ID'));
		this.DOM.wrap = params.wrap;
		this.SCALE_TIME_FORMAT = BX.isAmPmMode() ? 'g a' : 'G';
		this.userTimezone = Util.getUserSettings().timezoneName;
		this.currentTimezone = Type.isStringFilled(params.entryTimezone) ? params.entryTimezone : this.userTimezone;

		this.expandTimelineDebounce = Runtime.debounce(this.expandTimeline, this.EXPAND_DELAY, this);
		this.showMoreUsersBind = this.showMoreUsers.bind(this);
		this.hideMoreUsersBind = this.hideMoreUsers.bind(this);
		this.setConfig(params);
	}

	show()
	{
		if (this.currentFromDate && this.currentToDate)
		{
			const hourFrom = this.currentFromDate.getHours();
			const hourTo = this.currentToDate.getHours() + Math.ceil(this.currentToDate.getMinutes() / 60);
			this.extendScaleTimeLimits(hourFrom, hourTo);
		}

		if (this.currentFromDate && this.currentToDate)
		{
			this.updateScaleLimitsFromEntry(this.currentFromDate, this.currentToDate);
		}

		let animation = false;

		if (this.hideAnimation)
		{
			this.hideAnimation.stop();
			this.hideAnimation = null;
		}

		if (!this.isBuilt())
		{
			this.build();
			this.bindEventHandlers();
		}
		else
		{
			this.resizePlannerWidth(this.width);
		}

		this.buildTimeline();
		this.DOM.wrap.style.display = '';

		if (this.adjustWidth)
		{
			this.resizePlannerWidth(this.DOM.timelineInnerWrap.offsetWidth);
		}

		this.selector.update({
			from: this.currentFromDate,
			to: this.currentToDate,
			animation: false
		});
		if (this.currentFromDate && this.currentToDate
			&& this.currentFromDate.getTime() >= this.scaleDateFrom.getTime()
			&& this.currentToDate.getTime() <= this.scaleDateTo.getTime()
		)
		{
			this.selector.focus(false, 0, true);
		}

		if (this.readonly)
		{
			Dom.addClass(this.DOM.mainWrap, 'calendar-planner-readonly');
		}
		else
		{
			Dom.removeClass(this.DOM.mainWrap, 'calendar-planner-readonly');
		}

		if (this.compactMode)
		{
			Dom.addClass(this.DOM.mainWrap, 'calendar-planner-compact');
		}
		else
		{
			Dom.removeClass(this.DOM.mainWrap, 'calendar-planner-compact');
		}

		this.DOM.entriesOuterWrap.style.display = this.compactMode ? 'none' : '';

		if (animation)
		{
			if (this.showAnimation)
			{
				this.showAnimation.stop();
			}
			this.showAnimation = new BX.easing({
				duration: 300,
				start: {height: 0},
				finish: {height: this.height},
				transition: BX.easing.makeEaseOut(BX.easing.transitions.quart),
				step: (state) => {this.DOM.wrap.style.height = state.height + 'px';},
				complete: () => {
					if (parseInt(this.DOM.wrap.style.height) < this.height)
					{
						this.DOM.wrap.style.height = this.height + 'px';
					}
					this.showAnimation = null;
					this.selector.focus();
				}
			});
			this.showAnimation.animate();
		}
		else
		{
			if (parseInt(this.DOM.wrap.style.height) < this.height)
			{
				this.DOM.wrap.style.height = this.height + 'px';
			}
			this.adjustHeight();
		}

		this.shown = true;
	}

	setConfig(params)
	{
		this.todayLocMessage = Loc.getMessage('EC_PLANNER_TODAY');

		this.setScaleType(params.scaleType);

		// showTimelineDayTitle
		if (params.showTimelineDayTitle !== undefined)
		{
			this.showTimelineDayTitle = !!params.showTimelineDayTitle;
		}
		else if(this.showTimelineDayTitle === undefined)
		{
			this.showTimelineDayTitle = true;
		}

		// compactMode
		if (params.compactMode !== undefined)
		{
			this.compactMode = !!params.compactMode;
		}
		else if (this.compactMode === undefined)
		{
			this.compactMode = false;
		}

		// readonly
		if (params.readonly !== undefined)
		{
			this.readonly = !!params.readonly;
		}
		else if (this.readonly === undefined)
		{
			this.readonly = false;
		}

		if (this.compactMode)
		{
			let compactHeight = 50;
			if (this.showTimelineDayTitle && !this.isOneDayScale())
				compactHeight += 20;
			this.height = this.minHeight = compactHeight;
		}

		if (Type.isInteger(params.SCALE_OFFSET_BEFORE))
		{
			this.SCALE_OFFSET_BEFORE = parseInt(params.SCALE_OFFSET_BEFORE);
		}
		if (Type.isInteger(params.SCALE_OFFSET_AFTER))
		{
			this.SCALE_OFFSET_AFTER = parseInt(params.SCALE_OFFSET_AFTER);
		}
		if (Type.isInteger(params.maxTimelineSize))
		{
			this.maxTimelineSize = parseInt(params.maxTimelineSize);
		}
		if (Type.isInteger(params.minEntryRows))
		{
			this.MIN_ENTRY_ROWS = parseInt(params.minEntryRows);
		}
		if (Type.isInteger(params.maxEntryRows))
		{
			this.MAX_ENTRY_ROWS = parseInt(params.maxEntryRows);
		}

		if (Type.isInteger(params.width))
		{
			this.width = parseInt(params.width);
		}
		if (Type.isInteger(params.height))
		{
			this.height = parseInt(params.height);
		}
		if (Type.isInteger(params.minWidth))
		{
			this.minWidth = parseInt(params.minWidth);
		}
		if (Type.isInteger(params.minHeight))
		{
			this.minHeight = parseInt(params.minHeight);
		}
		this.width = Math.max(this.minWidth, this.width);
		this.height = Math.max(this.minHeight, this.height);

		if (Type.isArray(params.workTime))
		{
			this.workTime = params.workTime;
		}
		this.extendScaleTime(this.workTime[0], this.workTime[1]);

		this.weekHolidays = params.weekHolidays || this.weekHolidays || [];
		this.yearHolidays = params.yearHolidays || this.yearHolidays || [];
		this.accuracy = params.accuracy || this.accuracy || 300; // 5 min
		this.clickSelectorScaleAccuracy = params.clickSelectorScaleAccuracy || this.accuracy; // 5 min
		this.selectorAccuracy = parseInt(params.selectorAccuracy) || this.selectorAccuracy || 300; // 5 min
		this.entriesListWidth = parseInt(params.entriesListWidth) || this.entriesListWidth || 200;
		this.timelineCellWidth = params.timelineCellWidth || this.timelineCellWidth || 40;
		this.solidStatus = params.solidStatus === true;

		this.showEntiesHeader = params.showEntiesHeader === undefined ? true : !!params.showEntiesHeader;
		this.showEntryName = params.showEntryName === undefined ? true : !!params.showEntryName;

		if (this.isOneDayScale() && this.timelineCellWidth < 100)
		{
			this.timelineCellWidthOrig = this.timelineCellWidth;
			this.timelineCellWidth = 100;
		}
		else if(this.timelineCellWidthOrig && !this.isOneDayScale())
		{
			this.timelineCellWidth = this.timelineCellWidthOrig;
			this.timelineCellWidthOrig = false;
		}

		if (this.allowAdjustCellWidth === undefined || params.allowAdjustCellWidth !== undefined)
		{
			this.allowAdjustCellWidth = this.readonly
				&& this.compactMode
				&& params.allowAdjustCellWidth !== false;
		}

		if (params.locked !== undefined)
		{
			this.locked = params.locked;
		}

		this.adjustCellWidth();

		// Scale params
		this.setScaleLimits(params.scaleDateFrom, params.scaleDateTo);

		const warningTimeFrom = Util.config?.work_time_start ?? 9;
		const warningTimeTo = Util.config?.work_time_end ?? 18;

		const date = new Date().toDateString();
		const warningDateFrom = new Date(`${date} ${`${warningTimeFrom}`.replace('.', ':')}:00`);
		const warningDateTo = new Date(`${date} ${`${warningTimeTo}`.replace('.', ':')}:00`);

		this.warningHoursFrom = this.getDateHours(warningDateFrom);
		this.warningHoursTo = this.getDateHours(warningDateTo);
	}

	updateScaleLimitsFromEntry(from, to)
	{
		if (from.getTime() > this.scaleDateTo.getTime() || to.getTime() < this.scaleDateFrom.getTime())
		{
			this.setScaleLimits(new Date(from.getTime()), new Date(to.getTime() + Util.getDayLength() * this.SCALE_OFFSET_AFTER));
		}
	}

	setScaleLimits(scaleDateFrom, scaleDateTo)
	{
		if (scaleDateFrom !== undefined)
		{
			this.scaleDateFrom = Type.isDate(scaleDateFrom) ? scaleDateFrom : Util.parseDate(scaleDateFrom);
		}

		if (!Type.isDate(this.scaleDateFrom))
		{
			if (this.compactMode && this.readonly)
			{
				this.scaleDateFrom = new Date();
			}
			else
			{
				this.scaleDateFrom = new Date(new Date().getTime() - Util.getDayLength() * this.SCALE_OFFSET_BEFORE);
			}
		}
		this.scaleDateFrom.setHours(this.isOneDayScale() ? 0 : this.shownScaleTimeFrom, 0, 0, 0);

		if (scaleDateTo !== undefined)
		{
			this.scaleDateTo = BX.type.isString(scaleDateTo) ? Util.parseDate(scaleDateTo) : scaleDateTo;
		}

		if (!Type.isDate(this.scaleDateTo))
		{
			if (this.compactMode && this.readonly)
			{
				this.scaleDateTo = new Date();
			}
			else
			{
				this.scaleDateTo = new Date(new Date().getTime() + Util.getDayLength() * this.SCALE_OFFSET_AFTER);
			}
		}
		this.scaleDateTo.setHours(this.isOneDayScale() ? 0 : this.shownScaleTimeTo, 0, 0, 0);
	}

	extendScaleTimeLimits(fromTime, toTime)
	{
		if (fromTime !== false && !isNaN(parseInt(fromTime)))
		{
			this.shownScaleTimeFrom = Math.min(parseInt(fromTime), this.shownScaleTimeFrom, 23);
			this.shownScaleTimeFrom = Math.max(this.shownScaleTimeFrom, 0);

			if (this.scaleDateFrom)
			{
				this.scaleDateFrom.setHours(this.shownScaleTimeFrom, 0, 0 ,0);
			}
		}

		if (toTime !== false && !isNaN(parseInt(toTime)))
		{
			this.shownScaleTimeTo = Math.max(parseInt(toTime), this.shownScaleTimeTo, 1);
			this.shownScaleTimeTo = Math.min(this.shownScaleTimeTo, 24);

			if (this.scaleDateTo)
			{
				this.scaleDateTo.setHours(this.shownScaleTimeTo, 0, 0, 0);
			}
		}

		if (this.shownScaleTimeFrom % 2 !== 0)
		{
			this.shownScaleTimeFrom--;
		}

		if (this.shownScaleTimeTo % 2 !== 0)
		{
			this.shownScaleTimeTo++;
		}
	}

	SetLoadedDataLimits(from, to)
	{
		if (from)
		{
			this.loadedDataFrom = from.getTime ? from : Util.parseDate(from);
		}
		if (to)
		{
			this.loadedDataTo = to.getTime ? to : Util.parseDate(to);
		}
	}

	extendScaleTime(fromTime, toTime)
	{
		const savedTimeFrom = this.shownScaleTimeFrom;
		const savedTimeTo = this.shownScaleTimeTo;

		this.extendScaleTimeLimits(fromTime, toTime);

		if (fromTime === false && toTime !== false)
		{
			setTimeout(() => {
				this.extendTimelineToRight(savedTimeTo, this.shownScaleTimeTo);
			}, 200);
		}
		if (fromTime !== false && toTime === false)
		{
			setTimeout(() => {
				this.extendTimelineToLeft(this.shownScaleTimeFrom, savedTimeFrom);
			}, 200);
		}
		if (fromTime !== false && toTime !== false)
		{
			this.rebuildDebounce();
		}
	}

	adjustCellWidth()
	{
		if (this.allowAdjustCellWidth)
		{
			this.timelineCellWidth = Math.round(this.width / ((this.shownScaleTimeTo - this.shownScaleTimeFrom) * 3600 / this.scaleSize));
		}
	}

	build()
	{
		this.DOM.wrap.style.width = this.width + 'px';
		this.DOM.wrap.append(this.render());

		if (this.isLocked())
		{
			this.lock();
		}

		this.built = true;
		window.plannerr = this;
	}

	render()
	{
		this.selector = this.createSelector();

		this.DOM.mainWrap = Tag.render`
			<div
				class="calendar-planner-main-container calendar-planner-main-container-resource"
				style="
					min-height: ${this.minHeight}px;
					height: ${this.height}px;
					width: ${this.width}px;
				"
			>
				${this.renderEntriesOuterWrap()}
				${this.renderTimelineFixedWrap()}
				${this.renderSelectorPopup()}
				${this.renderTimezoneNoticeCount()}
				${this.selector.getTitleNode()}
				${this.renderSettingsButton()}
			</div>
		`;

		this.selector.DOM.timelineFixedWrap = this.DOM.timelineFixedWrap;
		this.selector.DOM.timelineWrap = this.DOM.timelineVerticalConstraint;

		if (!this.showEntryName)
		{
			this.DOM.entriesOuterWrap.style.width = '55px';
			Dom.addClass(this.DOM.mainWrap, 'calendar-planner-entry-icons-only');
		}

		if (this.readonly)
		{
			Dom.addClass(this.DOM.mainWrap, 'calendar-planner-readonly');
		}

		if (this.compactMode)
		{
			Dom.addClass(this.DOM.mainWrap, 'calendar-planner-compact');
		}

		return this.DOM.mainWrap;
	}

	createSelector()
	{
		const selector = new Selector({
			getPosByDate: this.getPosByDate.bind(this),
			getDateByPos: this.getDateByPos.bind(this),
			getEvents: this.getAllEvents.bind(this),
			getPosDateMap: () => {
				return this.posDateMap;
			},
			useAnimation: this.useAnimation,
			solidStatus: this.solidStatus,
			getScaleInfo: () => {return {
				scale: this.scaleType,
				shownTimeFrom: this.shownScaleTimeFrom,
				shownTimeTo: this.shownScaleTimeTo,
				scaleDateFrom: this.scaleDateFrom,
				scaleDateTo: this.scaleDateTo,
			}},
			getTimelineWidth: () => {
				return parseInt(this.DOM.timelineInnerWrap.style.width)
			},
		});
		selector.subscribe('onChange', this.handleSelectorChanges.bind(this));
		selector.subscribe('doCheckStatus', this.doCheckSelectorStatus.bind(this));
		selector.subscribe('onBeginChange', this.onBeginChangeHandler.bind(this));
		selector.subscribe('onStopAutoScroll', this.onStopAutoScrollHandler.bind(this));
		selector.subscribe('onStartTransit', this.hideTimezoneNotice.bind(this));

		return selector;
	}

	renderEntriesOuterWrap()
	{
		this.DOM.entriesOuterWrap = Tag.render`
			<div
				class="calendar-planner-user-container"
				style="
					width: ${this.entriesListWidth}px;
					height: ${this.height}px;
				"
			>
				${this.renderEntriesListHeader()}
				${this.renderEntriesListWrap()}
			</div>
		`;

		Util.preventSelection(this.DOM.entriesOuterWrap);
		if (this.compactMode)
		{
			this.DOM.entriesOuterWrap.style.display = 'none';
		}

		if (this.isOneDayScale())
		{
			Dom.addClass(this.DOM.entriesOuterWrap, 'calendar-planner-no-daytitle');
		}
		else
		{
			Dom.removeClass(this.DOM.entriesOuterWrap, 'calendar-planner-no-daytitle');
		}

		return this.DOM.entriesOuterWrap;
	}

	renderEntriesListHeader()
	{
		if (!this.showEntiesHeader)
		{
			return '';
		}

		return Tag.render`
			<div class="calendar-planner-header">
				<div class="calendar-planner-general-info">
					<div class="calendar-planner-users-header">
						<span class="calendar-planner-users-item">
							${Loc.getMessage('EC_PL_ATTENDEES_TITLE') + ' '}
							${this.renderEntriesListTitleCounter()}
						</span>
					</div>
				</div>
			</div>
		`;
	}

	renderEntriesListTitleCounter()
	{
		this.entriesListTitleCounter = Tag.render`
			<span></span>
		`;

		return this.entriesListTitleCounter;
	}

	renderEntriesListWrap()
	{
		this.DOM.entrieListWrap = Tag.render`
			<div
				class="calendar-planner-user-container-inner"
				style="
					width: ${this.entriesListWidth - 25}px;
				"
			></div>
		`;

		return this.DOM.entrieListWrap;
	}

	renderTimelineFixedWrap()
	{
		this.DOM.timelineFixedWrap = Tag.render`
			<div class="calendar-planner-timeline-wrapper" style="height: ${this.height}px;">
				${this.renderTimelineVerticalConstraint()}
			</div>
		`;

		return this.DOM.timelineFixedWrap;
	}

	renderTimelineVerticalConstraint()
	{
		this.DOM.timelineVerticalConstraint = Tag.render`
			<div class="calendar-planner-timeline-constraint">
				${this.renderTimelineInnerWrap()}
			</div>
		`;

		if (this.isTodayButtonEnabled())
		{
			this.DOM.timelineVerticalConstraint.addEventListener('scroll', this.onScrollHandler.bind(this));
		}

		return this.DOM.timelineVerticalConstraint;
	}

	renderTimelineInnerWrap()
	{
		this.DOM.timelineInnerWrap = Tag.render`
			<div class="calendar-planner-timeline-inner-wrapper" data-bx-planner-meta="timeline">
				${this.renderTimelineScaleWrap()}
				${this.renderTimelineDataWrap()}
			</div>
		`;

		return this.DOM.timelineInnerWrap;
	}

	renderTimelineScaleWrap()
	{
		this.DOM.timelineScaleWrap = Tag.render`
			<div class="calendar-planner-time"></div>
		`;
		Util.preventSelection(this.DOM.timelineScaleWrap);

		return this.DOM.timelineScaleWrap;
	}

	renderTimelineDataWrap()
	{
		this.DOM.timelineDataWrap = Tag.render`
			<div class="calendar-planner-timeline-container" style="height: ${this.height}px">
				${this.renderTimelineAccessibilityWrap()}
				${this.selector.getWrap()}
			</div>
		`;

		return this.DOM.timelineDataWrap;
	}

	renderTimelineAccessibilityWrap()
	{
		this.DOM.accessibilityWrap = Tag.render`
			<div class="calendar-planner-acc-wrap"></div>
		`;

		return this.DOM.accessibilityWrap;
	}

	renderSettingsButton()
	{
		if (this.compactMode)
		{
			return ''
		}

		this.DOM.settingsButton = Tag.render`
			<div class="calendar-planner-settings-icon-container" title="${Loc.getMessage('EC_PL_SETTINGS_SCALE')}">
				<span class="calendar-planner-settings-title">
					${Loc.getMessage('EC_PL_SETTINGS_SCALE')}
				</span>
				<span class="calendar-planner-settings-icon"></span>
			</div>
		`;

		Event.bind(this.DOM.settingsButton, 'click', () => this.showSettingsPopup());

		return this.DOM.settingsButton;
	}

	renderSelectorPopup()
	{
		this.DOM.selectorPopup = Tag.render`
			<div class="calendar-planner-selector-notice-popup" style="display: none;">
				${Loc.getMessage('EC_PLANNER_TIMEZONE_NOTICE')}
			</div>
		`;

		Event.bind(this.DOM.selectorPopup, 'click', () => this.hideSelectorPopup());

		this.doShowTimezoneNoticePopup = true;

		return this.DOM.selectorPopup;
	}

	renderTimezoneNoticeCount()
	{
		this.DOM.timezoneNoticeCount = Tag.render`
			<div class="calendar-planner-timezone-count-notice" style="display: none;">
				${this.renderTimezoneNoticeText(1)}
			</div>
		`;

		return this.DOM.timezoneNoticeCount;
	}

	renderTimezoneNoticeText(count, isWarning = false)
	{
		const warningClass = isWarning ? '--warning' : '';
		return Loc.getMessage('EC_PLANNER_TIMEZONE_NOTICE_TEXT', {
			'#CLASS#': `calendar-planner-timezone-count-notice-text ${warningClass}`,
			'#COUNT#': count,
		});
	}

	renderVacationNode()
	{
		const vacationNode = Tag.render`
			<div class="calendar-planner-timeline-side-notice --vacation" style="display: none;">
				${Loc.getMessage('EC_PLANNER_IN_VACATION')}
			</div>
		`;

		Event.bind(vacationNode, 'mouseenter', this.showHintPopup.bind(this, vacationNode));
		Event.bind(vacationNode, 'mouseleave', this.hideHintPopup.bind(this, vacationNode));

		return vacationNode;
	}

	showHintPopup(node)
	{
		node.hintPopup = Tag.render`
			<div class="calendar-planner-selector-notice-popup --hint">
				${node.dataHint}
			</div>
		`;

		Event.bind(this.DOM.selectorPopup, 'click', () => this.hideHintPopup(node));

		this.DOM.entrieListWrap.style.zIndex = '';
		this.DOM.entrieListWrap.style.overflow = '';
		this.DOM.entrieListWrap.style.clipPath = '';

		node.append(node.hintPopup);
	}

	hideHintPopup(node)
	{
		node.hintPopup.remove();
	}

	buildTimeline(clearCache)
	{
		if (
			this.isBuilt()
			&& (this.lastTimelineKey !== this.getTimelineShownKey()
			|| clearCache === true)
		)
		{
			Dom.clean(this.DOM.timelineScaleWrap);

			this.scaleData = this.getScaleData();

			let
				outerDayCont,
				dayTitle,
				cont = this.DOM.timelineScaleWrap;

			this.futureDayTitles = [];
			this.todayButtonPivotDay = undefined;
			for (let i = 0; i < this.scaleData.length; i++)
			{
				if (this.showTimelineDayTitle && !this.isOneDayScale())
				{
					if (this.scaleDayTitles[this.scaleData[i].daystamp])
					{
						cont = this.scaleDayTitles[this.scaleData[i].daystamp];
					}
					else
					{
						const date = new Date(this.scaleData[i].timestamp);
						date.setHours(0, 0, 0, 0);
						const today = new Date();
						today.setHours(0, 0, 0, 0);

						outerDayCont = this.DOM.timelineScaleWrap.appendChild(Tag.render`
							<div class="calendar-planner-time-day-outer"></div>
						`);

						let dayTitleClass = 'calendar-planner-time-day-title';
						if (date.getTime() < today.getTime())
						{
							dayTitleClass += ' calendar-planner-time-day-past';
						}

						//F d, l
						dayTitle = outerDayCont.appendChild(Tag.render`
							<div class="${dayTitleClass}">
								<span>${BX.date.format(this.dayOfWeekMonthFormat, this.scaleData[i].timestamp / 1000)}</span>
								<div class="calendar-planner-time-day-border"></div>
							</div>
						`);

						if (date.getTime() > today.getTime())
						{
							this.futureDayTitles.push(dayTitle.querySelector('span'));
						}

						if (date.getTime() === today.getTime() && this.isTodayButtonEnabled())
						{
							this.todayTitleButton = dayTitle.firstElementChild.appendChild(Tag.render`
								<div class="calendar-planner-today-button"></div>
							`);
							this.todayTitleButton.innerHTML = this.todayLocMessage;
							this.todayTitleButton.addEventListener('click', this.todayButtonClickHandler.bind(this));
							this.todayButtonPivotDay = outerDayCont;
						}

						cont = outerDayCont.appendChild(Tag.render`
							<div class="calendar-planner-time-day"></div>
						`);

						this.scaleDayTitles[this.scaleData[i].daystamp] = cont;

					}
				}

				let className = 'calendar-planner-time-hour-item' + (this.scaleData[i].dayStart ? ' calendar-planner-day-start' : '');

				if (
					(this.scaleType === '15min' || this.scaleType === '30min')
					&& this.scaleData[i].title !== ''
				)
				{
					className += ' calendar-planner-time-hour-bold';
				}

				this.scaleData[i].cell = cont.appendChild(BX.create('DIV', {
					props: {
						className: className
					},
					style: {
						width: this.timelineCellWidth + 'px',
						minWidth: this.timelineCellWidth + 'px'
					},
					html: this.scaleData[i].title ? '<i>' + this.scaleData[i].title + '</i>' : ''
				}));

				if (!this.isOneDayScale() && this.scaleData[i + 1] && this.scaleData[i + 1].dayStart)
				{
					cont.appendChild(Tag.render`
						<div class="calendar-planner-timeline-border"></div>
					`);
				}
			}

			let mapDatePosRes = this.mapDatePos();
			this.posDateMap = mapDatePosRes.posDateMap;

			const timelineOffset = this.DOM.timelineScaleWrap.offsetWidth;
			this.DOM.timelineInnerWrap.style.width = timelineOffset + 'px';
			this.DOM.entrieListWrap.style.top = (parseInt(this.DOM.timelineDataWrap.offsetTop) + 10) + 'px';

			this.lastTimelineKey = this.getTimelineShownKey();
			this.checkRebuildTimeout(timelineOffset);
			this.buildTodayButtonWrap();

			this.scrollLeft = this.DOM.timelineVerticalConstraint.scrollLeft;
		}
	}

	buildTodayButtonWrap()
	{
		if (!this.isTodayButtonEnabled())
		{
			return;
		}

		if (this.todayButton)
		{
			this.todayButton.remove();
		}
		if (this.todayRightButton)
		{
			this.todayRightButton.remove();
		}
		if (this.DOM.todayButtonContainer)
		{
			this.DOM.todayButtonContainer.remove();
		}
		if (this.isOneDayScale())
		{
			return;
		}

		const todayButton = this.DOM.entriesOuterWrap.appendChild(Tag.render`
			<div class="calendar-planner-today-button">${this.todayLocMessage}</div>
		`);
		this.todayButtonWidth = todayButton.offsetWidth;
		todayButton.innerHTML = this.todayLocMessage + ' &rarr;';
		this.todayButtonRightWidth = todayButton.offsetWidth;
		todayButton.innerHTML = this.todayLocMessage + ' &larr;';
		this.todayButtonLeftWidth = todayButton.offsetWidth;
		const top = BX.pos(todayButton).top - BX.pos(this.DOM.timelineScaleWrap).top;
		todayButton.remove();

		let left = 0;
		if (this.todayButtonPivotDay)
		{
			left = this.todayButtonPivotDay.offsetLeft + this.todayButtonPivotDay.offsetWidth - 10 - this.todayButtonWidth + 1;
		}
		const width = this.DOM.timelineScaleWrap.offsetWidth - left;
		this.DOM.todayButtonContainer = this.DOM.timelineScaleWrap.appendChild(Tag.render`
			<div class="calendar-planner-today-button-container" style="width: ${width}px; left: ${left}px; top: ${top}px;"></div>
		`);
		this.todayButton = this.DOM.todayButtonContainer.appendChild(Tag.render`
			<div class="calendar-planner-today-button" style="width: ${this.todayButtonWidth}px; direction: rtl;">${this.todayLocMessage}</div>
		`);
		this.todayRightButton = this.DOM.timelineVerticalConstraint.appendChild(Tag.render`
			<div class="calendar-planner-today-button" style="right: 0; top: 5px; position: absolute;">${this.todayLocMessage}</div>
		`);

		this.todayButton.addEventListener('click', this.todayButtonClickHandler.bind(this));
		this.todayRightButton.addEventListener('click', this.todayButtonClickHandler.bind(this));
		this.updateTodayButtonVisibility(false);

		if (this.isLocked() && this.DOM.todayButtonContainer)
		{
			Dom.addClass(this.DOM.todayButtonContainer, '--lock');
		}
	}

	getTimelineShownKey()
	{
		return 'tm_' + this.scaleDateFrom.getTime() + '_' + this.scaleDateTo.getTime();
	}

	checkRebuildTimeout(timelineOffset, timeout = 300)
	{
		if (!this._checkRebuildTimeoutCount)
		{
			this._checkRebuildTimeoutCount = 0;
		}

		if (this.rebuildTimeout)
		{
			this.rebuildTimeout = !!clearTimeout(this.rebuildTimeout);
		}

		if (
			this._checkRebuildTimeoutCount <= 10
			&& Type.isElementNode(this.DOM.timelineScaleWrap)
			&& Dom.isShown(this.DOM.timelineScaleWrap)
		)
		{
			this._checkRebuildTimeoutCount++;
			this.rebuildTimeout = setTimeout(() => {
				if (timelineOffset !== this.DOM.timelineScaleWrap.offsetWidth)
				{
					if (this.rebuildTimeout)
					{
						this.rebuildTimeout = !!clearTimeout(this.rebuildTimeout);
					}

					this.rebuild();
					if (this.selector)
					{
						this.selector.focus(true, 300);
					}
				}
				else
				{
					this.checkRebuildTimeout(timelineOffset, timeout);
				}
			}, timeout);
		}
		else
		{
			delete this._checkRebuildTimeoutCount;
		}
	}

	rebuildDebounce(params)
	{
		Runtime.debounce(this.rebuild, this.REBUILD_DELAY, this)(params);
	}

	extendTimelineToLeft(extendedTimeFrom, extendedTimeTo)
	{
		this.extendTimeline(extendedTimeFrom, extendedTimeTo);
	}

	extendTimelineToRight(extendedTimeFrom, extendedTimeTo)
	{
		this.extendTimeline(extendedTimeFrom, extendedTimeTo, true)
	}

	extendTimeline(extendedTimeFrom, extendedTimeTo, isToRight = false)
	{
		if (!this.DOM.timelineScaleWrap)
		{
			return;
		}
		const isToLeft = !isToRight;
		const dayNodeList = this.DOM.timelineScaleWrap.querySelectorAll('.calendar-planner-time-day');
		const dayCount = dayNodeList.length;
		const nodeCountInDay = this.scaleData.length / dayCount;
		const extendCount = extendedTimeTo - extendedTimeFrom;

		let cellsInsertedOnLeftCount = 0;
		const insertedNodes = [];
		let pivotScaleDatumOfDayIndex = isToRight ? nodeCountInDay - 1 : 0;

		for (const dayNode of dayNodeList)
		{
			const pivotNodeOfDay = isToLeft
				? dayNode.children[0]
				: dayNode.querySelector('.calendar-planner-timeline-border');
			if (isToLeft)
			{
				this.scaleData[pivotScaleDatumOfDayIndex].dayStart = false;
			}

			const daystamp = this.scaleData[pivotScaleDatumOfDayIndex].daystamp;
			let toTimestamp, fromTimestamp;
			if (isToLeft)
			{
				toTimestamp = this.scaleData[pivotScaleDatumOfDayIndex].timestamp / 1000;
				fromTimestamp = toTimestamp - 3600 * extendCount;
				if (new Date(fromTimestamp * 1000).getHours() !== extendedTimeFrom)
				{
					return;
				}
			}
			else
			{
				fromTimestamp = this.scaleData[pivotScaleDatumOfDayIndex].timestamp / 1000 + this.scaleSize;
				toTimestamp = fromTimestamp + 3600 * extendCount;
				if (new Date(fromTimestamp * 1000).getHours() !== extendedTimeFrom)
				{
					return;
				}
			}

			for (let insertedTimestamp = fromTimestamp, i = 0; insertedTimestamp < toTimestamp; insertedTimestamp += this.scaleSize, i++)
			{
				const title = BX.date.format('i', insertedTimestamp) === '00'
					? BX.date.format(this.SCALE_TIME_FORMAT, insertedTimestamp)
					: '';
				if (insertedTimestamp < this.currentFromDate.getTime() / 1000 - (isToLeft ? 3600 * 12 : 0))
				{
					cellsInsertedOnLeftCount++;
				}
				let animationClass = 'expand-width-no-animation';
				if (
					(
						isToLeft
						&& insertedTimestamp > this.currentFromDate.getTime() / 1000 - 3600 * 12
						&& insertedTimestamp < this.currentFromDate.getTime() / 1000 + 3600 * 12
					)
					||
					(
						isToRight
						&& insertedTimestamp > this.currentFromDate.getTime() / 1000
						&& insertedTimestamp < this.currentFromDate.getTime() / 1000 + 3600 * 24
					)
				)
				{
					animationClass = 'expand-width-0-40';
				}

				const insertedCell = BX.create('DIV', {
					props: {
						className: 'calendar-planner-time-hour-item' + ' ' + animationClass,
					},
					style: {
						width: this.timelineCellWidth + 'px',
						minWidth: this.timelineCellWidth + 'px'
					},
					html: '<i>' + title + '</i>'
				});
				insertedNodes.push(insertedCell);
				dayNode.insertBefore(insertedCell, pivotNodeOfDay);

				const insertedScaleDatum = {
					daystamp: daystamp,
					timestamp: insertedTimestamp * 1000,
					value: insertedTimestamp * 1000,
					title: title,
					dayStart: isToLeft && i === 0,
					cell: insertedCell
				};
				this.scaleData.splice(i + pivotScaleDatumOfDayIndex + (isToRight ? 1 : 0), 0, insertedScaleDatum);
			}
			if (isToLeft)
			{
				pivotNodeOfDay.classList.remove('calendar-planner-day-start');
				dayNode.children[0].classList.add('calendar-planner-day-start');
			}
			pivotScaleDatumOfDayIndex += nodeCountInDay + extendCount * 3600 / this.scaleSize;
		}

		// set scroll for timeline to compensate width of static inserted cells
		const scroll = this.DOM.timelineVerticalConstraint.scrollLeft;
		this.DOM.timelineVerticalConstraint.scrollLeft = scroll + cellsInsertedOnLeftCount * this.timelineCellWidth;

		// get accessibility events for animation
		const midnight = new Date(this.currentFromDate.getTime());
		midnight.setHours(0,0,0,0);
		if (isToRight)
		{
			midnight.setDate(midnight.getDate() + 1);
		}
		const visibleEvents = this.getVisibleEvents();
		const animatedEvents = this.getEventsAfter(visibleEvents, midnight);
		this.update(this.entries, this.accessibility);

		// update selector and visible events position during the css animation
		new BX.easing({
			duration: 200,
			start: {},
			finish: {},
			transition: BX.easing.makeEaseOut(BX.easing.transitions.linear),
			step: () => {
				this.buildTodayButtonWrap();
				this.selector.update();
				for (const event of animatedEvents)
				{
					event.node.style.left = this.getPosByDate(new Date(event.fromTimestamp)) + 'px';
				}
			},
			complete: () => {
				for (const node of insertedNodes)
				{
					node.classList.remove('expand-width-no-animation');
					node.classList.remove('expand-width-0-40');
				}
				this.updateTimelineAfterExtend();
			}
		}).animate();
	}

	updateTimelineAfterExtend()
	{
		let mapDatePosRes = this.mapDatePos();
		this.posDateMap = mapDatePosRes.posDateMap;
		const timelineOffset = this.DOM.timelineScaleWrap.offsetWidth;
		this.DOM.timelineInnerWrap.style.width = timelineOffset + 'px';
		this.DOM.entrieListWrap.style.top = (parseInt(this.DOM.timelineDataWrap.offsetTop) + 10) + 'px';
		this.lastTimelineKey = this.getTimelineShownKey();
		this.update(this.entries, this.accessibility);
		this.adjustHeight();
		this.resizePlannerWidth(this.width);
		this.selector.update();
		this.clearCacheTime();
		this.buildTodayButtonWrap();
	}

	rebuild(params = {})
	{
		if (this.isBuilt())
		{
			this.buildTimeline(true);
			this.update(this.entries, this.accessibility);
			this.resizePlannerWidth(this.width);

			if (params.updateSelector !== false)
			{
				this.selector.update(params.selectorParams);
				if (params.dontFocus !== true)
				{
					this.selector.focus(false, 0, true);
				}
			}

			this.clearCacheTime();
		}
	}

	getScaleData()
	{
		this.scaleData = [];
		this.scaleDayTitles = {};

		let
			ts, scaleFrom, scaleTo,
			time, dayStamp, title,
			curDayStamp = false,
			timeFrom = this.isOneDayScale() ? 0 : this.shownScaleTimeFrom,
			timeTo = this.isOneDayScale() ? 0 : this.shownScaleTimeTo;

		this.scaleDateFrom.setHours(timeFrom, 0, 0, 0);
		this.scaleDateTo.setHours(timeTo, 0, 0, 0);
		scaleFrom = this.scaleDateFrom.getTime();
		scaleTo = this.scaleDateTo.getTime();

		for (ts = scaleFrom; ts < scaleTo; ts += this.scaleSize * 1000)
		{
			time = parseFloat(BX.date.format('H.i', ts / 1000));

			if (this.isOneDayScale())
				title = BX.date.format('d F, D', ts / 1000);
			else
				title = BX.date.format('i', ts / 1000) === '00'
					? BX.date.format(this.SCALE_TIME_FORMAT, ts / 1000)
					: '';

			if (this.isOneDayScale() || (time >= timeFrom && time < timeTo))
			{
				dayStamp = BX.date.format('d.m.Y', ts / 1000);
				this.scaleData.push({
					daystamp: dayStamp,
					timestamp: ts,
					value: ts,
					title: title,
					dayStart: curDayStamp !== dayStamp
				});
				curDayStamp = dayStamp;
			}
		}

		return this.scaleData;
	}

	isOneDayScale()
	{
		return this.scaleType === '1day';
	}

	prepareAccessibilityItem(entry)
	{
		const userOffset = Util.getTimeZoneOffset(this.userTimezone);
		const timezoneOffset = Util.getTimeZoneOffset(this.currentTimezone);
		const timeOffset = (userOffset - timezoneOffset) * 60 * 1000;

		return Planner.prepareAccessibilityItem(entry, timeOffset);
	}

	static prepareAccessibilityItem(entry, timeOffset = 0)
	{
		if (!Type.isDate(entry.from))
		{
			entry.from = Util.parseDate(entry.dateFrom);
		}

		if (!Type.isDate(entry.to))
		{
			entry.to = Util.parseDate(entry.dateTo);
		}

		if (!Type.isDate(entry.from) || !Type.isDate(entry.to))
		{
			return false;
		}

		let from = new Date(entry.from.getTime());
		let to = new Date(entry.to.getTime());
		from.setSeconds(0,0);
		to.setSeconds(0,0);
		if (!entry.isFullDay)
		{
			from = new Date(from.getTime() + timeOffset);
			to = new Date(to.getTime() + timeOffset);
		}

		const fromTimestamp = from.getTime();
		const toTimestamp = to.getTime();
		const toReal = new Date(to.getTime());
		const toTimestampReal = toTimestamp;
		const name = entry.name || entry.title;
		const type = entry.isVacation ? 'hr' : 'event';

		entry.fromTimestamp = fromTimestamp;
		entry.toTimestamp = toTimestamp;
		entry.toTimestampReal = toTimestamp;

		return { from, to, fromTimestamp, toTimestamp, toReal, toTimestampReal, name, type };
	}

	addAccessibilityItem(entry, wrap)
	{
		let
			timeFrom, timeTo,
			hidden = false,
			fromTimestamp = entry.fromTimestamp,
			toTimestamp = entry.toTimestampReal || entry.toTimestamp,
			shownScaleTimeFrom = this.isOneDayScale() ? 0 : this.shownScaleTimeFrom,
			shownScaleTimeTo = this.isOneDayScale() ? 24 : this.shownScaleTimeTo,
			from = new Date(fromTimestamp),
			to = new Date(toTimestamp);

		timeFrom = parseInt(from.getHours()) + from.getMinutes() / 60;
		timeTo = parseInt(to.getHours()) + to.getMinutes() / 60;

		if (timeFrom > shownScaleTimeTo)
		{
			from = new Date(from.getTime() + Util.getDayLength() - 1);
			from.setHours(shownScaleTimeFrom, 0, 0, 0);
			if (from.getTime() >= to.getTime())
			{
				hidden = true;
			}
		}

		if (!hidden && timeFrom < shownScaleTimeFrom)
		{
			from.setHours(shownScaleTimeFrom, 0, 0, 0);
			if (from.getTime() >= to.getTime())
			{
				hidden = true;
			}
		}

		if (!hidden && timeTo > shownScaleTimeTo)
		{
			to.setHours(shownScaleTimeTo, 0, 0, 0);
			if (from.getTime() >= to.getTime())
			{
				hidden = true;
			}
		}

		if (!hidden && timeTo < shownScaleTimeFrom)
		{
			to = new Date(to.getTime() - Util.getDayLength() + 1);
			to.setHours(shownScaleTimeTo, 0, 0, 0);
			if (from.getTime() >= to.getTime())
			{
				hidden = true;
			}
		}

		if (!hidden && from.getTime() < this.scaleDateTo)
		{
			let
				fromPos = this.getPosByDate(from),
				toPos = Math.min(this.getPosByDate(to), this.DOM.timelineScaleWrap.offsetWidth);

			entry.node = wrap.appendChild(BX.create('DIV', {
				props: {
					className: 'calendar-planner-acc-entry',
				},
				style: {
					left: fromPos + 'px',
					width: Math.max((toPos - fromPos), 3) + 'px'
				}
			}));

			if (entry.name)
			{
				entry.node.title = entry.name;
			}
		}
	}

	displayEntryRow(entry, accessibility = [])
	{
		let rowWrap;
		if (entry.type === 'moreLink')
		{
			rowWrap = this.DOM.entrieListWrap.appendChild(Tag.render`
				<div class="calendar-planner-user"></div>
			`);

			this.DOM.statusNodeAll = this.getStatusNode('tzAll');
			if (!entry.hasDifferentTimezone || this.readonly)
			{
				this.DOM.statusNodeAll.style.display = 'none';
			}

			if (this.showEntryName)
			{
				this.DOM.showMoreUsersLink = rowWrap.appendChild(Tag.render`
					<div class="calendar-planner-all-users" title="${entry.title || ''}">
						${entry.name}
						${this.DOM.statusNodeAll}
					</div>
				`);
			}
			else
			{
				this.DOM.showMoreUsersLink = rowWrap.appendChild(Tag.render`
					<div class="calendar-planner-users-more" title="${entry.name || ''}">
						<span class="calendar-planner-users-more-btn">
							${this.DOM.statusNodeAll}
						</span>
					</div>
				`);
			}
			Event.bind(this.DOM.showMoreUsersLink, 'click', this.showMoreUsersBind);
			Event.bind(this.selector.DOM.moreButton, 'click', this.showMoreUsersBind);
			this.selector.DOM.moreButton.style.display = '';
		}
		else if (entry.type === 'lastUsers')
		{
			rowWrap = this.DOM.entrieListWrap.appendChild(Tag.render`	
				<div class="calendar-planner-user"></div>
			`);

			if (this.showEntryName)
			{
				this.DOM.showMoreUsersLink = rowWrap.appendChild(Tag.render`
					<div class="calendar-planner-all-users calendar-planner-last-users" title="${entry.title || ''}">
						${entry.name}
					</div>
				`);
			}
			else
			{
				this.DOM.showMoreUsersLink = rowWrap.appendChild(Tag.render`
					<div class="calendar-planner-users-more" title="${entry.title || entry.name}">
						<span class="calendar-planner-users-last-btn"></span>
					</div>
				`);
			}
		}
		else if (entry.id && entry.type === 'user')
		{
			rowWrap = this.DOM.entrieListWrap.appendChild(BX.create('DIV', {
				attrs: {
					'data-bx-planner-entry' : entry.uid,
					className: 'calendar-planner-user'
						+ (entry.emailUser ? ' calendar-planner-email-user' : '')
				}
			}));

			entry.vacationNode = this.renderVacationNode();

			if (entry.timezoneName)
			{
				entry.statusNode = this.getStatusNode(entry.status, entry.timezoneName);
				rowWrap.append(entry.statusNode);
			}

			if (!this.showEntryName)
			{
				rowWrap.append(entry.vacationNode);
			}

			if (!this.hasCorrectStatus(entry) && entry.statusNode)
			{
				entry.statusNode.style.display = 'none';
			}

			rowWrap.appendChild(Planner.getEntryAvatarNode(entry));

			if (this.showEntryName)
			{
				rowWrap.append(Tag.render`
					<span class="calendar-planner-user-name">
						<span
							class="calendar-planner-entry-name"
							bx-tooltip-user-id="${entry.id}"
							bx-tooltip-classname="calendar-planner-user-tooltip"
						>
							${Text.encode(entry.name)}
						</span>
						${entry.vacationNode}
					</span>
				`);
			}
		}
		else if (entry.id && entry.type === 'room')
		{
			rowWrap = this.DOM.entrieListWrap.appendChild(Tag.render`
				<div class="calendar-planner-user"></div>
			`);
			if (this.showEntryName)
			{
				rowWrap.appendChild(Tag.render`
					<span class="calendar-planner-user-name"></span>
				`)
				.appendChild(Tag.render`
					<span class="calendar-planner-entry-name" style="width: ${this.entriesListWidth - 20}px;">
						${Text.encode(entry.name)}
					</span>
				`);
			}
			else
			{
				rowWrap.appendChild(Tag.render`
					<div class="calendar-planner-location-image-icon" title="${Text.encode(entry.name)}"></div>
				`);
			}
		}
		else if (entry.type === 'resource')
		{
			if (!this.entriesResourceListWrap || !BX.isNodeInDom(this.entriesResourceListWrap))
			{
				this.entriesResourceListWrap = this.DOM.entrieListWrap.appendChild(Tag.render`
					<div class="calendar-planner-container-resource">
						<div class="calendar-planner-resource-header">
							<span class="calendar-planner-users-item">${Loc.getMessage('EC_PL_RESOURCE_TITLE')}</span>
						</div>
					</div>
				`);
			}

			rowWrap = this.entriesResourceListWrap.appendChild(Tag.render`
				<div class="calendar-planner-user" data-bx-planner-entry="${entry.uid}"></div>
			`);

			if (this.showEntryName)
			{
				rowWrap.appendChild(Tag.render`
					<span class="calendar-planner-user-name"></span>
				`)
				.appendChild(Tag.render`
					<span class="calendar-planner-entry-name" style="width: ${this.entriesListWidth - 20}px;">
						${Text.encode(entry.name)}
					<span>
				`);
			}
			else
			{
				rowWrap.appendChild(Tag.render`
					<div class="calendar-planner-location-image-icon" title="${Text.encode(entry.name)}"></div>
				`);
			}
		}
		else
		{
			rowWrap = this.DOM.entrieListWrap.appendChild(Tag.render`
				<div class="calendar-planner-user"></div>
			`);
			rowWrap.appendChild(Tag.render`
				<div class="calendar-planner-all-users">${Text.encode(entry.name)}</div>
			`);
		}

		let top = rowWrap.offsetTop + 13;

		let dataRowWrap = this.DOM.accessibilityWrap.appendChild(Tag.render`
			<div class="calendar-planner-timeline-space" style="top:${top}px" data-bx-planner-entry="${entry.uid||0}"></div>
		`);

		//this.entriesRowMap.set(entry, rowWrap);
		this.entriesDataRowMap.set(entry.uid, dataRowWrap);
		accessibility.forEach((item) => this.addAccessibilityItem(item, dataRowWrap));
	}

	hasCorrectStatus(entry)
	{
		return entry.status && this.entryStatusMap[entry.status];
	}

	getStatusNode(status, timezoneName = '')
	{
		const statusMessage = 'EC_PL_STATUS_' + status.toUpperCase();
		const title = Loc.hasMessage(statusMessage) ? Loc.getMessage(statusMessage) : Util.getFormattedTimezone(timezoneName);

		return Tag.render`
			<span
				class="calendar-planner-user-status-icon ${this.entryStatusMap[status]}"
				title="${title}"
			></span>
		`;
	}

	static getEntryAvatarNode(entry)
	{
		let imageNode;
		const img = entry.avatar;

		if (!img || img === "/bitrix/images/1.gif")
		{
			let defaultAvatarClass = 'ui-icon-common-user';
			if (entry.emailUser)
			{
				defaultAvatarClass = 'ui-icon-common-user-mail';
			}
			if (entry.sharingUser)
			{
				defaultAvatarClass += ' ui-icon-common-user-sharing';
			}
			imageNode = Tag.render`<div bx-tooltip-user-id="${entry.id}" bx-tooltip-classname="calendar-planner-user-tooltip" title="${Text.encode(entry.name)}" class="ui-icon calendar-planner-user-image-icon ${defaultAvatarClass}"><i></i></div>`;
		}
		else
		{
			imageNode = Tag.render`<div bx-tooltip-user-id="${entry.id}" bx-tooltip-classname="calendar-planner-user-tooltip" title="${Text.encode(entry.name)}" class="ui-icon calendar-planner-user-image-icon"><i style="background-image: url('${encodeURI(entry.avatar)}')"></i></div>`;
		}
		return imageNode;
	}

	selectEntryRow(entry)
	{
		if (BX.type.isPlainObject(entry))
		{
			let top = parseInt(entry.dataRowWrap.offsetTop);
			if (
				!entry.selectWrap
				|| !BX.isParentForNode(this.selectedEntriesWrap, entry.selectWrap)
			)
			{
				entry.selectWrap = this.selectedEntriesWrap.appendChild(Tag.render`
					<div class="calendar-planner-timeline-selected"></div>
				`);
			}

			entry.selectWrap.style.display = '';
			entry.selectWrap.style.top = (top + 36) + 'px';
			entry.selectWrap.style.width = (parseInt(this.DOM.mainWrap.offsetWidth) + 5) + 'px';

			Dom.addClass(entry.selectorControlWrap, 'active');
			entry.selected = true;

			this.clearCacheTime();
		}
	}

	isEntrySelected(entry)
	{
		return entry && entry.selected;
	}

	deSelectEntryRow(entry)
	{
		if (BX.type.isPlainObject(entry))
		{
			if (entry.selectWrap)
			{
				entry.selectWrap.style.display = 'none';
			}
			if (entry.selectorControlWrap)
			{
				Dom.removeClass(entry.selectorControlWrap, 'active');
			}
			entry.selected = false;
			this.clearCacheTime();
		}
	}

	static getEntryUniqueId(entry)
	{
		return ['user', 'room'].includes(entry.type) ? entry.id : entry.type + '-' + entry.id;
	}

	getEntryByUniqueId(entryUniqueId)
	{
		if (BX.type.isArray(this.entries))
		{
			return this.entries.find(function(entry){return entry.uid == entryUniqueId;})
		}
		return null;
	}

	bindEventHandlers()
	{
		Event.bind(this.DOM.wrap, 'click', this.handleClick.bind(this));
		Event.bind(this.DOM.wrap, 'contextmenu', this.handleClick.bind(this));
		Event.bind(this.DOM.wrap, 'mousedown', this.handleMousedown.bind(this));
		Event.bind(document, 'mousemove', this.handleMousemove.bind(this));
		Event.bind(document, 'mouseup', this.handleMouseup.bind(this));

		Event.bind(
			this.DOM.timelineFixedWrap,
			'onwheel' in document ? 'wheel' : 'mousewheel',
			this.mouseWheelTimelineHandler.bind(this)
		);

	}

	handleClick(e)
	{
		if (!e)
		{
			e = window.event;
		}
		e.preventDefault();
		const isRightClick = e.which === 3;
		if (isRightClick || e.target.className === 'calendar-planner-today-button')
		{
			return;
		}

		this.clickMousePos = this.getMousePos(e);
		let
			nodeTarget = e.target || e.srcElement,
			accuracyMouse = 5;

		if (!this.readonly)
		{
			let
				timeline = this.findTarget(nodeTarget, 'timeline'),
				selector = this.findTarget(nodeTarget, 'selector');

			if (timeline && !selector && Math.abs(this.clickMousePos.x - this.mouseDownMousePos.x) < accuracyMouse && Math.abs(this.clickMousePos.y - this.mouseDownMousePos.y) < accuracyMouse)
			{
				const left = this.clickMousePos.x - BX.pos(this.DOM.timelineFixedWrap).left + this.DOM.timelineVerticalConstraint.scrollLeft;
				const mapDatePosRes = this.mapDatePos(this.clickSelectorScaleAccuracy);
				let selectedDateFrom = this.getDateByPos(left, false, mapDatePosRes.posDateMap);
				if (!selectedDateFrom)
				{
					return;
				}
				const selectorTimeLength = this.currentToDate - this.currentFromDate;
				let selectedDateTo = new Date(selectedDateFrom.getTime() + selectorTimeLength);
				this.currentFromDate = selectedDateFrom;
				this.currentToDate = selectedDateTo;

				this.selector.transit({
					toX: this.getPosByDate(selectedDateFrom),
					leftDate: this.currentFromDate,
					rightDate: this.currentToDate
				});
			}
		}
	}

	handleMousedown(e)
	{
		if (!e)
		{
			e = window.event;
		}

		let nodeTarget = e.target || e.srcElement;

		if (this.selector.DOM.timeWrap.contains(nodeTarget))
		{
			return;
		}

		this.mouseDownMousePos = this.getMousePos(e);
		this.mouseDown = true;

		if (!this.readonly)
		{
			let selector = this.findTarget(nodeTarget, 'selector');
			this.startMousePos = this.mouseDownMousePos;

			if (selector)
			{
				if (this.findTarget(nodeTarget, 'selector-resize-right'))
				{
					this.selector.startResize();
				}
				else
				{
					this.selector.startMove();
				}
			}
			else if (this.findTarget(nodeTarget, 'timeline'))
			{
				this.startScrollTimeline();
			}
		}

		if (this.shouldShakeSelector(nodeTarget))
		{
			this.showSelectorPopup(Loc.getMessage('EC_PLANNER_CANT_DRAG_SHARED_EVENT'));
			this.selector.shake();
		}
	}

	shouldShakeSelector(nodeTarget)
	{
		const isSelector = this.findTarget(nodeTarget, 'selector');
		const isNotMoreButton = nodeTarget !== this.selector.DOM.moreButton;

		return this.readonly && !this.solidStatus && isSelector && isNotMoreButton;
	}

	handleMouseup()
	{
		if (this.selector.isDragged())
		{
			this.selector.endMove();
			this.selector.endResize();
		}

		if(this.timelineIsDraged)
		{
			this.endScrollTimeline();
		}

		if (this.shown && !this.readonly && this.mouseDown)
		{
			this.checkTimelineScroll();
		}

		this.mouseDown = false;
		Dom.removeClass(document.body, 'calendar-planner-unselectable');
	}

	handleMousemove(e)
	{
		let mousePos;

		if (this.selector.isDragged())
		{
			mousePos = this.getMousePos(e);
			this.selector.move(mousePos.x - this.startMousePos.x);
			this.selector.resize(mousePos.x - this.startMousePos.x);
		}

		if (this.timelineIsDraged)
		{
			mousePos = this.getMousePos(e);
			this.scrollTimeline(mousePos.x - this.startMousePos.x);
		}
	}

	mouseWheelTimelineHandler(e)
	{
		e = e || window.event;
		if (this.shown && !this.readonly)
		{
			if (Browser.isMac())
			{
				this.checkTimelineScroll();
			}
			else
			{
				const delta = e.deltaY || e.detail || e.wheelDelta;
				if (Math.abs(delta) > 0)
				{
					this.DOM.timelineVerticalConstraint.scrollLeft = Math.max(
						this.DOM.timelineVerticalConstraint.scrollLeft + Math.round(delta / 3),
						0
					);
					this.checkTimelineScroll();
					return BX.PreventDefault(e);
				}
			}
		}
	}

	onScrollHandler()
	{
		this.scrollLeft = this.DOM.timelineVerticalConstraint.scrollLeft;
		this.updateTodayButtonVisibility();
		this.updateWorkTimeNotice();
	}

	updateTodayButtonVisibility(animation = true)
	{
		if (!this.isTodayButtonEnabled() || this.isOneDayScale())
		{
			return;
		}

		this.todayButton.style.transition = animation ? '' : 'none';

		const today = new Date();
		today.setHours(this.shownScaleTimeFrom, 0, 0, 0);

		let parent = this.DOM.entriesOuterWrap;
		if (this.todayTitleButton)
		{
			parent = this.todayTitleButton.parentElement;
		}

		const doDisplayTodayButton = today.getTime() < this.scaleDateTo.getTime()
			&& BX.pos(parent).left + 30 < BX.pos(this.DOM.entriesOuterWrap).right;
		if (doDisplayTodayButton && this.todayButton.style.display !== '')
		{
			this.todayButton.style.display = '';
			this.setFutureDayTitlesOffset(false);
		}
		if (!doDisplayTodayButton && this.todayButton.style.display !== 'none')
		{
			this.todayButton.style.display = 'none';
			this.setFutureDayTitlesOffset(false);
		}

		const doAddLeftArrow = BX.pos(this.todayTitleButton).right + (this.todayButtonLeftWidth - this.todayButtonWidth) < BX.pos(this.DOM.entriesOuterWrap).right;
		if (doAddLeftArrow && this.todayButton.innerHTML === this.todayLocMessage)
		{
			this.todayButton.innerHTML = this.todayLocMessage + ' &larr;';
			this.todayButton.style.width = this.todayButtonLeftWidth + 'px';
			this.setFutureDayTitlesOffset(animation);
		}
		if (!doAddLeftArrow && this.todayButton.innerHTML !== this.todayLocMessage)
		{
			this.todayButton.innerHTML = this.todayLocMessage;
			this.todayButton.style.width = this.todayButtonWidth + 'px';
			this.setFutureDayTitlesOffset(animation);
		}

		const isTodayInFuture = today.getTime() > this.scaleDateTo.getTime();
		const doDisplayTodayRightButton = isTodayInFuture || BX.pos(parent).right > BX.pos(this.DOM.timelineVerticalConstraint).right;
		if (doDisplayTodayRightButton && this.todayRightButton.style.display !== '')
		{
			this.todayRightButton.style.display = '';
		}
		if (!doDisplayTodayRightButton && this.todayRightButton.style.display !== 'none')
		{
			this.todayRightButton.style.display = 'none';
		}

		if (this.todayTitleButton)
		{
			if (BX.pos(this.todayTitleButton).right < BX.pos(this.DOM.timelineVerticalConstraint).right)
			{
				this.todayTitleButton.style.position = 'sticky';
			}
			if (BX.pos(this.todayTitleButton).right > BX.pos(this.DOM.timelineVerticalConstraint).right)
			{
				this.todayTitleButton.style.position = '';
			}
		}

		const doAddRightArrow = BX.pos(parent).left > BX.pos(this.DOM.timelineVerticalConstraint).right || isTodayInFuture;
		if (doAddRightArrow && this.todayRightButton.innerHTML === this.todayLocMessage)
		{
			this.todayRightButton.innerHTML = this.todayLocMessage + ' &rarr;';
			this.todayRightButton.style.width = this.todayButtonRightWidth + 'px';
		}
		if (!doAddRightArrow && this.todayRightButton.innerHTML !== this.todayLocMessage)
		{
			this.todayRightButton.innerHTML = this.todayLocMessage;
			this.todayRightButton.style.width = this.todayButtonWidth + 'px';
		}
	}

	setFutureDayTitlesOffset(animation = true)
	{
		const left = this.todayButton.style.display === 'none' ? '' : (parseInt(this.todayButton.style.width) + 4) + 'px';
		for (const title of this.futureDayTitles)
		{
			title.style.transition = animation ? '' : 'none';
			title.style.left = left;
		}
	}

	todayButtonClickHandler()
	{
		if (!this.isTodayButtonEnabled())
		{
			return;
		}

		if (this.todayButtonPivotDay)
		{
			const today = new Date();
			today.setHours(this.shownScaleTimeFrom, 0, 0, 0);
			new BX.easing({
				duration: 300,
				start: {scrollLeft: this.DOM.timelineVerticalConstraint.scrollLeft},
				finish: {scrollLeft: this.getPosByDate(today)},
				transition: BX.easing.makeEaseOut(BX.easing.transitions.quad),
				step: (state)=>{this.DOM.timelineVerticalConstraint.scrollLeft = state.scrollLeft;},
				complete: ()=>{}
			}).animate();
		}
		else
		{
			this.scaleDateFrom = new Date();
			this.scaleDateFrom.setHours(this.shownScaleTimeFrom, 0, 0, 0);

			this.scaleDateTo = new Date(new Date().getTime() + Util.getDayLength() * this.SCALE_OFFSET_AFTER);
			this.scaleDateTo.setHours(this.shownScaleTimeTo, 0, 0, 0);

			this.rebuild();
			this.DOM.timelineVerticalConstraint.scrollLeft = 0;

			this.emit('onExpandTimeline', new BaseEvent({
				data: {
					reload: true,
					dateFrom: this.scaleDateFrom,
					dateTo: this.scaleDateTo
				}
			}));
		}
	}

	isTodayButtonEnabled()
	{
		return !this.readonly && !this.compactMode;
	}

	checkTimelineScroll()
	{
		const minScroll = this.scrollStep;
		const maxScroll = this.DOM.timelineVerticalConstraint.scrollWidth
							- this.DOM.timelineFixedWrap.offsetWidth
							- this.scrollStep;

		// Check and expand only if it is visible
		if (this.DOM.timelineFixedWrap.offsetWidth > 0)
		{
			const today = new Date();
			today.setHours(this.scaleDateFrom.getHours(), 0, 0, 0);
			if ((this.DOM.timelineVerticalConstraint.scrollLeft <= minScroll) && (this.scaleDateFrom.getTime() > today.getTime()))
			{
				this.expandTimelineDirection = 'past';
			}
			if (this.DOM.timelineVerticalConstraint.scrollLeft >= maxScroll)
			{
				this.expandTimelineDirection = 'future';
			}

			if (this.expandTimelineDirection)
			{
				if (!this.isLoaderShown())
				{
					this.showLoader();
				}
				this.expandTimelineDebounce();
			}
		}
	}

	startScrollTimeline()
	{
		this.timelineIsDraged = true;
		this.timelineStartScrollLeft = this.DOM.timelineVerticalConstraint.scrollLeft;
	}
	scrollTimeline(x)
	{
		this.DOM.timelineVerticalConstraint.scrollLeft = Math.max(this.timelineStartScrollLeft - x, 0);
	}
	endScrollTimeline()
	{
		this.timelineIsDraged = false;
	}

	findTarget(node, nodeMetaType, parentCont)
	{
		if (!parentCont)
			parentCont = this.DOM.mainWrap;

		let type = (node && node.getAttribute) ? node.getAttribute('data-bx-planner-meta') : null;

		if (type !== nodeMetaType)
		{
			if (node)
			{
				node = BX.findParent(node, function(n)
				{
					return n.getAttribute && n.getAttribute('data-bx-planner-meta') === nodeMetaType;
				}, parentCont);
			}
			else
			{
				node = null;
			}
		}

		return node;
	}

	getMousePos(e)
	{
		if (!e)
			e = window.event;

		let x = 0, y = 0;
		if (e.pageX || e.pageY)
		{
			x = e.pageX;
			y = e.pageY;
		}
		else if (e.clientX || e.clientY)
		{
			x = e.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft) - document.documentElement.clientLeft;
			y = e.clientY + (document.documentElement.scrollTop || document.body.scrollTop) - document.documentElement.clientTop;
		}

		return {x: x, y: y};
	}

	setScaleType(scaleType)
	{
		if (!this.scaleTypes.includes(scaleType))
		{
			scaleType = '1hour';
		}

		this.scaleType = scaleType;
		this.scaleSize = Planner.getScaleSize(scaleType);

		if (this.isOneDayScale() && this.timelineCellWidth < 100)
		{
			this.timelineCellWidthOrig = this.timelineCellWidth;
			this.timelineCellWidth = 100;
		}
		else if (!this.isOneDayScale() && this.timelineCellWidthOrig)
		{
			this.timelineCellWidth = this.timelineCellWidthOrig;
			this.timelineCellWidthOrig = false;
		}

		if (this.isOneDayScale())
		{
			Dom.addClass(this.DOM.mainWrap, 'calendar-planner-fulldaymode');
			if (this.DOM.entriesOuterWrap)
			{
				Dom.addClass(this.DOM.entriesOuterWrap, 'calendar-planner-no-daytitle');
			}
		}
		else
		{
			Dom.removeClass(this.DOM.mainWrap, 'calendar-planner-fulldaymode');
			if (this.DOM.entriesOuterWrap)
			{
				Dom.removeClass(this.DOM.entriesOuterWrap, 'calendar-planner-no-daytitle');
			}
		}
	}

	static getScaleSize(scaleType)
	{
		let
			hour = 3600,
			map = {
				'15min' : Math.round(hour / 4),
				'30min' : Math.round(hour / 2),
				'1hour' : hour,
				'2hour' : hour * 2,
				'1day' : hour * 24
			};

		return map[scaleType] || hour;
	}

	mapDatePos(accuracy)
	{
		if (!accuracy)
		{
			accuracy = this.accuracy;
		}

		let datePosMap = {};
		let posDateMap = {};
		let i, j, tsi, xi, tsj, xj, cellWidth;

		this.substeps = Math.round(this.scaleSize / accuracy);
		this.posAccuracy = this.timelineCellWidth / this.substeps;

		accuracy = accuracy * 1000;
		let scaleSize = this.scaleData[1].timestamp - this.scaleData[0].timestamp;

		for (i = 0; i < this.scaleData.length; i++)
		{
			tsi = this.scaleData[i].timestamp;
			xi = parseInt(this.scaleData[i].cell.offsetLeft);
			cellWidth = parseInt(this.scaleData[i].cell.offsetWidth);

			if (!datePosMap[tsi])
			{
				datePosMap[tsi] = xi;
			}
			posDateMap[xi] = tsi;

			for (j = 1; j <= cellWidth; j++)
			{
				tsj = tsi + Math.round((j * scaleSize / cellWidth) / accuracy) * accuracy;
				xj = xi + j;
				if (!datePosMap[tsi])
				{
					datePosMap[tsj] = xj;
				}
				posDateMap[xj] = tsj;

				if (j === cellWidth &&
					(!this.scaleData[i + 1] || this.scaleData[i + 1].dayStart))
				{
					datePosMap[xj + '_end'] = tsj;
				}
			}

			if (i + 1 < this.scaleData.length && this.scaleData[i + 1].dayStart)
			{
				const borderStart = xi + cellWidth;
				const borderEnd = parseInt(this.scaleData[i + 1].cell.offsetLeft);
				const borderTimestamp = tsi + scaleSize;
				for (let borderX = borderStart; borderX < borderEnd; borderX++)
				{
					posDateMap[borderX] = borderTimestamp;
				}
			}
		}

		return {
			datePosMap: datePosMap,
			posDateMap: posDateMap
		}
	}

	getPosByDate(date)
	{
		let x = 0;
		if (date && typeof date !== 'object')
		{
			date = Util.parseDate(date);
		}

		if (date && typeof date === 'object')
		{
			let curInd = 0;
			const timestamp = date.getTime();

			for (let i = 0; i < this.scaleData.length; i++)
			{
				if (timestamp >= this.scaleData[i].timestamp)
				{
					curInd = i;
				}
				else
				{
					break;
				}
			}

			if (this.scaleData[curInd] && this.scaleData[curInd].cell)
			{
				x = this.scaleData[curInd].cell.offsetLeft;
				const cellWidth = this.scaleData[curInd].cell.offsetWidth;
				const deltaTs = Math.round((timestamp - this.scaleData[curInd].timestamp) / 1000);

				if (deltaTs > 0)
				{
					x += Math.round(deltaTs * 10 / this.scaleSize * cellWidth) / 10;
				}
			}
		}

		return x;
	}

	getDateByPos(x, end, posDateMap)
	{
		if (!posDateMap)
		{
			posDateMap = this.posDateMap;
		}
		let
			date,
			timestamp = (end && posDateMap[x + '_end']) ? posDateMap[x + '_end'] : posDateMap[x];

		if (!timestamp)
		{
			x = Math.round(x);
			timestamp = (end && posDateMap[x + '_end']) ?  posDateMap[x + '_end'] : posDateMap[x];
		}

		if (timestamp)
		{
			date = new Date(timestamp);
		}

		return date;
	}

	showMoreUsers()
	{
		this.MIN_ENTRY_ROWS = this.MAX_ENTRY_ROWS;
		this.rebuild({ dontFocus: true });

		Dom.addClass(this.selector.DOM.moreButton, '--close');

		Event.unbind(this.selector.DOM.moreButton, 'click', this.showMoreUsersBind);
		Event.bind(this.selector.DOM.moreButton, 'click', this.hideMoreUsersBind);
	}

	hideMoreUsers()
	{
		this.MIN_ENTRY_ROWS = this.initialMinEntryRows;
		this.rebuild({ dontFocus: true });

		Dom.removeClass(this.selector.DOM.moreButton, '--close');

		Event.unbind(this.selector.DOM.moreButton, 'click', this.hideMoreUsersBind);
		Event.bind(this.selector.DOM.moreButton, 'click', this.showMoreUsersBind);
	}

	adjustHeight()
	{
		let
			newHeight = this.DOM.entrieListWrap.offsetHeight + this.DOM.entrieListWrap.offsetTop + 30,
			currentHeight = parseInt(this.DOM.wrap.style.height) || this.height;

		if (this.compactMode && currentHeight < newHeight || !this.compactMode)
		{
			this.resizePlannerHeight(newHeight, Math.abs(newHeight - currentHeight) > 10);
		}
	}

	resizePlannerHeight(height, animation = false)
	{
		if (animation)
		{
			const animationDuration = 300;
			const top = parseInt(this.DOM.entrieListWrap.style.top);

			this.updateHeightTransition(animationDuration);
			this.DOM.entrieListWrap.style.zIndex = '10';
			this.DOM.entrieListWrap.style.overflow = 'hidden';
			this.DOM.entrieListWrap.style.clipPath = `inset(0 0 calc(100% - ${this.height - top}px))`;

			setTimeout(() => {
				this.updateHeight(height);
				this.DOM.entrieListWrap.style.clipPath = `inset(0 0 calc(100% - ${height - top}px))`;

				setTimeout(() => {
					this.updateHeightTransition(0);
					this.DOM.entrieListWrap.style.zIndex = '';
					this.DOM.entrieListWrap.style.overflow = '';
					this.DOM.entrieListWrap.style.clipPath = '';
				}, animationDuration);
			}, 0);
		}
		else
		{
			this.updateHeight(height);
		}

		this.height = height;
		let timelineDataContHeight = this.DOM.entrieListWrap.offsetHeight + 3;
		this.DOM.timelineDataWrap.style.height = timelineDataContHeight + 'px';
		if (this.DOM.proposeTimeButton && this.DOM.proposeTimeButton.style.display !== "none")
		{
			this.DOM.proposeTimeButton.style.top = (this.DOM.timelineDataWrap.offsetTop + timelineDataContHeight / 2 - 16) + "px";
		}
	}

	updateHeightTransition(duration)
	{
		this.DOM.wrap.style.transition = `height ${duration}ms ease`;
		this.DOM.mainWrap.style.transition = `height ${duration}ms ease`;
		this.DOM.timelineFixedWrap.style.transition = `height ${duration}ms ease`;
		this.DOM.entriesOuterWrap.style.transition = `height ${duration}ms ease`;
		this.DOM.entrieListWrap.style.transition = `clip-path ${duration}ms ease`;
	}

	updateHeight(height)
	{
		this.DOM.wrap.style.height = height + 'px';
		this.DOM.mainWrap.style.height = height + 'px';
		this.DOM.timelineFixedWrap.style.height = height + 'px';
		this.DOM.entriesOuterWrap.style.height = height + 'px';
	}

	resizePlannerWidth(width, animation)
	{
		if (!animation && this.DOM.wrap && this.DOM.mainWrap)
		{
			this.DOM.wrap.style.width = width + 'px';
			let entriesListWidth = this.compactMode ? 0 : this.entriesListWidth;

			if (!this.showEntryName)
			{
				entriesListWidth = 55;
			}

			this.DOM.mainWrap.style.width = width + 'px';
			this.DOM.entriesOuterWrap.style.width = entriesListWidth + 'px';
		}
	}

	expandTimeline(scaleDateFrom, scaleDateTo)
	{
		let loadedTimelineSize;
		let scrollLeft;
		const prevScaleDateFrom = this.scaleDateFrom;
		const prevScaleDateTo = this.scaleDateTo;

		if (!scaleDateFrom)
		{
			scaleDateFrom = this.scaleDateFrom;
		}
		if (!scaleDateTo)
		{
			scaleDateTo = this.scaleDateTo;
		}

		if (this.expandTimelineDirection === 'past')
		{
			scrollLeft = this.DOM.timelineVerticalConstraint.scrollLeft;
			this.scaleDateFrom = new Date(scaleDateFrom.getTime() - Util.getDayLength()  * this.EXPAND_OFFSET);
			const today = new Date();
			today.setHours(this.scaleDateFrom.getHours(), 0, 0, 0);
			if (this.scaleDateFrom.getTime() < today)
			{
				this.scaleDateFrom = today;
			}

			loadedTimelineSize = (this.scaleDateTo.getTime() - this.scaleDateFrom.getTime()) / Util.getDayLength();
			if (loadedTimelineSize > this.maxTimelineSize)
			{
				this.scaleDateTo = new Date(this.scaleDateFrom.getTime() + Util.getDayLength()  * this.maxTimelineSize);
				this.loadedDataFrom = this.scaleDateFrom;
				this.loadedDataTo = this.scaleDateTo;
				this.limitScaleSizeMode = true;
			}
		}
		else if (this.expandTimelineDirection === 'future')
		{
			let oldDateTo = this.scaleDateTo;
			scrollLeft = this.DOM.timelineVerticalConstraint.scrollLeft;
			this.scaleDateTo = new Date(scaleDateTo.getTime() + Util.getDayLength() * this.EXPAND_OFFSET);
			loadedTimelineSize = (this.scaleDateTo.getTime() - this.scaleDateFrom.getTime()) / Util.getDayLength();

			if (loadedTimelineSize > this.maxTimelineSize)
			{
				this.scaleDateFrom = new Date(this.scaleDateTo.getTime() - Util.getDayLength()  * this.maxTimelineSize);
				this.loadedDataFrom = this.scaleDateFrom;
				this.loadedDataTo = this.scaleDateTo;

				scrollLeft = this.getPosByDate(oldDateTo) - this.DOM.timelineFixedWrap.offsetWidth;
				setTimeout(() => {
					this.DOM.timelineVerticalConstraint.scrollLeft = this.getPosByDate(oldDateTo) - this.DOM.timelineFixedWrap.offsetWidth;
				}, 10);

				this.limitScaleSizeMode = true;
			}
		}
		else
		{
			this.scaleDateFrom = new Date(scaleDateFrom.getTime() - Util.getDayLength()  * this.SCALE_OFFSET_BEFORE);
			this.scaleDateTo = new Date(scaleDateTo.getTime() + Util.getDayLength() * this.SCALE_OFFSET_AFTER);
		}

		const reloadData = this.scaleDateFrom.getTime() < prevScaleDateFrom.getTime()
		|| this.scaleDateTo.getTime() > prevScaleDateTo.getTime();

		this.hideLoader();
		this.emit('onExpandTimeline', new BaseEvent({
			data: {
				reload: reloadData,
				dateFrom: this.scaleDateFrom,
				dateTo: this.scaleDateTo
			} }));

		const currentPlannerWidth = this.DOM.timelineInnerWrap.offsetWidth;
		this.rebuild({
			updateSelector: true
		});

		if (this.expandTimelineDirection === 'past')
		{
			const widthDiff = this.DOM.timelineInnerWrap.offsetWidth - currentPlannerWidth;
			this.DOM.timelineVerticalConstraint.scrollLeft = scrollLeft + widthDiff;
		}
		else if (scrollLeft !== undefined)
		{
			this.DOM.timelineVerticalConstraint.scrollLeft = scrollLeft;
		}

		this.expandTimelineDirection = null;
	}

	getVisibleEvents()
	{
		const visibleEvents = [];

		const timelineFromPosition = this.DOM.timelineVerticalConstraint.scrollLeft;
		const timelineToPosition = timelineFromPosition + this.DOM.timelineFixedWrap.offsetWidth;

		for (const index in this.accessibility)
		{
			for (const event of this.accessibility[index])
			{
				const eventFromPosition = this.getPosByDate(new Date(event.fromTimestamp));
				const eventToPosition = this.getPosByDate(new Date(event.toTimestamp));
				if (
					this.doSegmentsIntersect(eventFromPosition, eventToPosition, timelineFromPosition, timelineToPosition)
					&& event.node
				)
				{
					visibleEvents.push(event);
				}
			}
		}

		return visibleEvents;
	}

	getEventsAfter(events, timestamp)
	{
		const eventsAfter = [];
		for (const event of events)
		{
			if (event.fromTimestamp >= timestamp)
			{
				eventsAfter.push(event);
			}
		}
		return eventsAfter;
	}

	updateTimezone(timezone)
	{
		const currentOffset = Util.getTimeZoneOffset(this.currentTimezone);
		const timezoneOffset = Util.getTimeZoneOffset(timezone);
		this.currentTimezone = timezone;

		if (currentOffset === timezoneOffset)
		{
			return;
		}

		if (this.isBuilt())
		{
			this.update(this.entries, this.accessibility);
		}
	}

	update(entries = [], accessibility = {})
	{
		Dom.clean(this.DOM.entrieListWrap);

		Dom.clean(this.DOM.accessibilityWrap);
		this.entriesDataRowMap = new Map();

		if (!Type.isArray(entries))
		{
			return;
		}

		if (this.entries?.length !== entries.length)
		{
			this.doShowTimezoneNoticePopup = true;
		}

		this.entries = entries;
		this.accessibility = [];
		this.preparedAccessibility = [];
		this.allEvents = [];

		const currentOffset = Util.getTimeZoneOffset(this.currentTimezone);
		this.entries.forEach((entry) => {
			this.accessibility[entry.id] = accessibility[entry.id];
			this.preparedAccessibility[entry.id] = accessibility[entry.id].map((it) => this.prepareAccessibilityItem(it));
			this.allEvents.push(...this.preparedAccessibility[entry.id]);
			entry.timezoneOffset = Util.getTimeZoneOffset(entry.timezoneName);
			entry.timezoneNameFormatted = Util.getFormattedTimezone(entry.timezoneName);
			entry.offset = currentOffset - entry.timezoneOffset;
		});

		const userId = parseInt(this.userId);

		// sort entries list by amount of accessibility data
		// Entries without accessibility data should be in the end of the array
		// But first in the list will be meeting room
		// And second (or first) will be owner-host of the event
		entries.sort((a, b) => {
			if (b.status === 'h' || parseInt(b.id) === userId && a.status !== 'h')
			{
				return 1;
			}
			if (a.status === 'h' || parseInt(a.id) === userId && b.status !== 'h')
			{
				return  -1;
			}
			if (parseInt(a.id) < parseInt(b.id))
			{
				return -1;
			}
			return 1;
		});

		if (this.selectedEntriesWrap)
		{
			Dom.clean(this.selectedEntriesWrap);
			if (this.selector && this.selector.controlWrap)
			{
				Dom.clean(this.selector.controlWrap);
			}
		}

		const cutData = [];
		const cutDataTitle = [];
		let usersCount = 0;
		let cutEntries = [];
		let dispDataCount = 0;

		if (entries.length <= this.initialMinEntryRows + 1)
		{
			this.selector.DOM.moreButton.style.display = 'none';
		}
		else
		{
			this.selector.DOM.moreButton.style.display = '';
		}

		entries.forEach((entry, ind) => {
			entry.uid = Planner.getEntryUniqueId(entry);

			const accData = this.preparedAccessibility[entry.uid];
			this.entriesIndex.set(entry.uid, entry);

			if (entry.type === 'user')
			{
				usersCount++;
			}

			if (ind < this.MIN_ENTRY_ROWS || entries.length === this.MIN_ENTRY_ROWS + 1)
			{
				dispDataCount++;
				this.displayEntryRow(entry, accData);
			}
			else
			{
				cutEntries.push(entry);
				cutDataTitle.push(entry.name);
				cutData.push(...accData);
			}
		});

		// Update entries title count
		if (this.entriesListTitleCounter)
		{
			this.entriesListTitleCounter.innerHTML = usersCount > this.MAX_ENTRY_ROWS ? '(' + usersCount + ')' : '';
		}

		this.emit('onDisplayAttendees', new BaseEvent({
			data:  {
				usersCount: usersCount
			}
		}));

		if (cutEntries.length > 0)
		{
			if (dispDataCount === this.MAX_ENTRY_ROWS)
			{
				this.displayEntryRow({
					name: Loc.getMessage('EC_PL_ATTENDEES_LAST') + ' (' + cutEntries.length + ')',
					type: 'lastUsers',
					title: cutDataTitle.join(', ')
				}, cutData);
			}
			else
			{
				this.displayEntryRow({
					name: Loc.getMessage('EC_PL_ATTENDEES_SHOW_MORE') + ' (' + cutEntries.length + ')',
					type: 'moreLink',
					hasDifferentTimezone: cutEntries.filter(entry => entry.offset !== 0).length > 0,
				}, cutData);
			}
		}

		this.clearCacheTime();
		const status = this.checkTimePeriod(this.currentFromDate, this.currentToDate) === true;
		this.updateSelectorFromStatus(status);

		Util.extendPlannerWatches({entries: entries, userId: this.userId});

		this.adjustHeight();
		this.updateWorkTimeNotice();
	}

	updateSelector(from, to, fullDay, options = {})
	{
		if (this.shown && this.selector)
		{
			this.setFullDayMode(fullDay);

			// Update limits of scale
			if (!this.isOneDayScale())
			{
				if (Util.formatDate(from) !== Util.formatDate(to))
				{
					this.extendScaleTime(0, 24);
				}
				else
				{
					let timeFrom = parseInt(from.getHours()) + Math.floor(from.getMinutes() / 60);
					let timeTo = parseInt(to.getHours()) + Math.ceil(to.getMinutes() / 60);
					let scale = 2;

					if (timeFrom <= this.shownScaleTimeFrom)
					{
						this.extendScaleTime(timeFrom - scale, false);
					}

					if (timeTo >= this.shownScaleTimeTo)
					{
						this.extendScaleTime(false, timeTo + scale);
					}
				}
			}

			if (this.isNeedToExpandTimeline(from, to))
			{
				this.expandTimelineDirection = false;
				this.expandTimeline(from, to);
			}

			this.currentFromDate = from;
			this.currentToDate = to;
			if (!this.selector)
			{
				return;
			}

			if (from.getTime() < this.scaleDateFrom.getTime())
			{
				this.selector.update({
					from: from,
					to: to,
					fullDay: fullDay,
					focus: options.focus !== false
				});
				return;
			}

			this.selector.update({
				from: from,
				to: to,
				fullDay: fullDay
			});

			if (options.focus !== false)
			{
				this.selector.focus(true, 300);
			}

			this.updateWorkTimeNotice();
		}
	}

	isNeedToExpandTimeline(from, to)
	{
		return to.getTime() > this.scaleDateTo.getTime()
			|| from.getTime() < this.scaleDateFrom.getTime();
	}

	handleSelectorChanges(event)
	{
		if (event instanceof BaseEvent)
		{
			let data = event.getData();
			this.emit('onDateChange', new BaseEvent({data: data}));
			this.currentFromDate = data.dateFrom;
			this.currentToDate = data.dateTo;

			if (this.currentToDate.getHours() < this.shownScaleTimeFrom
				&& !(this.currentToDate.getHours() === 0 && this.currentToDate.getMinutes() === 0))
			{
				this.extendScaleTime(this.currentToDate.getHours(), false);
			}

			this.updateWorkTimeNotice();
		}
	}

	onStopAutoScrollHandler()
	{
		this.hideWorkTimeNotice();
	}

	onBeginChangeHandler()
	{
		this.hideWorkTimeNotice();
	}

	updateWorkTimeNotice()
	{
		if (!this.isWorkTimeNoticeEnabled())
		{
			return;
		}

		const selectorTime = this.selector.boundaryFrom ?? this.currentFromDate;
		this.updateVacationNotice(selectorTime);
		this.updateTimezoneNotice(selectorTime);
	}

	hideWorkTimeNotice()
	{
		this.hideVacationNotice();
		this.hideTimezoneNotice();
	}

	updateVacationNotice(selectorTime)
	{
		for (const entry of this.entries.filter(entry => Type.isDomNode(entry.vacationNode)))
		{
			const currentVacations = this.accessibility[entry.id].filter((acc) => {
				const from = acc.from.getTime();
				const to = acc.to.getTime();
				return acc.isVacation && from < selectorTime.getTime() && selectorTime.getTime() < to;
			});

			if (currentVacations.length > 0)
			{
				const to = Math.max(...currentVacations.map(vacation => vacation.to));
				entry.vacationNode.dataHint = Loc.getMessage('EC_PLANNER_IN_VACATION_UNTIL', {
					'#UNTIL#': Util.formatDate(to),
				});
				entry.vacationNode.style.display = '';
			}
			else
			{
				entry.vacationNode.style.display = 'none';
			}
		}
	}

	hideVacationNotice()
	{
		for (const entry of this.entries.filter(entry => Type.isDomNode(entry.vacationNode)))
		{
			entry.vacationNode.style.display = 'none';
		}
	}

	updateTimezoneNotice(selectorTime)
	{
		if (!this.isSelectorVisible())
		{
			this.hideTimezoneNotice();
			return;
		}

		const otherTimezoneEntries = this.entries.filter((entry) => this.isInternalUser(entry) && entry.offset !== 0);
		const warningTimezoneEntries = otherTimezoneEntries.filter((entry) => {
			const entryTime = new Date(selectorTime.getTime() + entry.offset * 60 * 1000);
			const entryHours = this.getDateHours(entryTime);
			return entryHours < this.warningHoursFrom || entryHours >= this.warningHoursTo;
		});

		if (Type.isDomNode(this.DOM.statusNodeAll))
		{
			Dom.removeClass(this.DOM.statusNodeAll, '--warning');
		}

		this.selector.clearTimeNodes();
		for (const entry of otherTimezoneEntries)
		{
			const entryNode = this.entriesDataRowMap.get(entry.uid);
			const entryTime = new Date(selectorTime.getTime() + entry.offset * 60 * 1000);
			const entryHours = this.getDateHours(entryTime);
			const isWarning = entryHours < this.warningHoursFrom || entryHours >= this.warningHoursTo;

			if (Type.isDomNode(entryNode))
			{
				const top = parseInt(entryNode.style.top);
				this.selector.showTimeNode(top, Util.formatTime(entryTime), entry.timezoneNameFormatted, isWarning);
			}

			this.showEntryStatusTimezone(entry, isWarning);
		}

		const isWarning = warningTimezoneEntries.length > 0;
		if (isWarning)
		{
			Dom.addClass(this.selector.DOM.moreButton, '--warning');
		}
		else
		{
			Dom.removeClass(this.selector.DOM.moreButton, '--warning');
		}

		if (otherTimezoneEntries.length > 0)
		{
			this.showTimezoneNotice(otherTimezoneEntries.length, isWarning);
		}
		else
		{
			this.hideTimezoneNotice();
		}
	}

	isInternalUser(entry)
	{
		return entry.type === 'user' && !entry.sharingUser && !entry.emailUser;
	}

	getDateHours(date)
	{
		return date.getHours() + date.getMinutes() / 60;
	}

	isSelectorVisible()
	{
		const timelineLeft = this.DOM.timelineVerticalConstraint.scrollLeft;
		const timelineRight = timelineLeft + this.DOM.timelineFixedWrap.offsetWidth;

		const selectorWrap = this.selector.getWrap();
		const selectorLeft = selectorWrap.offsetLeft;
		const selectorRight = selectorWrap.offsetLeft + selectorWrap.offsetWidth;

		return this.doSegmentsIntersect(selectorLeft, selectorRight, timelineLeft, timelineRight);
	}

	showTimezoneNotice(count, isWarning)
	{
		this.showTimezoneNoticeCount(count, isWarning);
		if (isWarning)
		{
			this.showTimezoneNoticePopup();
		}
		else
		{
			this.hideTimezoneNoticePopup();
		}
	}

	hideTimezoneNotice()
	{
		this.selector.clearTimeNodes();
		this.hideTimezoneNoticeCount();
		this.hideTimezoneNoticePopup();
	}

	showTimezoneNoticeCount(count, isWarning)
	{
		this.DOM.timezoneNoticeCount.innerHTML = this.renderTimezoneNoticeText(count, isWarning);

		const left = this.getSelectorOffset();
		this.DOM.timezoneNoticeCount.style.left = `${left}px`;
		this.DOM.timezoneNoticeCount.style.display = 'block';
		this.DOM.wrap.style.marginBottom = `${20}px`;
	}

	hideTimezoneNoticeCount()
	{
		this.DOM.timezoneNoticeCount.style.display = 'none';
	}

	showTimezoneNoticePopup()
	{
		if (!this.doShowTimezoneNoticePopup || this.isTimezoneNoticePopupShown)
		{
			return;
		}

		this.showSelectorPopup(Loc.getMessage('EC_PLANNER_TIMEZONE_NOTICE'));
	}

	hideTimezoneNoticePopup()
	{
		if (this.DOM.selectorPopup.style.display !== 'none')
		{
			this.doShowTimezoneNoticePopup = false;
			this.isTimezoneNoticePopupShown = true;
		}
		this.hideSelectorPopup();
	}

	showSelectorPopup(text)
	{
		if (this.DOM.selectorPopup.style.display === 'block' && this.DOM.selectorPopup.innerText !== text)
		{
			this.DOM.selectorPopup.style.transition = 'color 200ms ease';
			this.DOM.selectorPopup.style.color = '#ffffff00';
			setTimeout(() => {
				this.DOM.selectorPopup.innerText = text;
				this.DOM.selectorPopup.style.color = '';
			}, 200);
		}
		else
		{
			this.DOM.selectorPopup.style.transition = 'none';
			this.DOM.selectorPopup.innerText = text;
			this.DOM.selectorPopup.style.color = '';
		}

		const left = this.getSelectorOffset();
		this.DOM.selectorPopup.style.left = `${left}px`;
		this.DOM.selectorPopup.style.display = 'block';

		clearTimeout(this.selectorPopupTimeout);
		this.selectorPopupTimeout = setTimeout(() => this.hideTimezoneNoticePopup(), 3000);
	}

	hideSelectorPopup()
	{
		this.DOM.selectorPopup.style.display = 'none';
	}

	getSelectorOffset()
	{
		const scroll = this.scrollLeft;
		const selectorWrap = this.selector.getWrap();
		const selectorCenter = parseInt(selectorWrap.style.width) / 2 + parseInt(selectorWrap.style.left);
		const userWrapWidth = parseInt(this.DOM.entriesOuterWrap.style.width);

		return selectorCenter - scroll + userWrapWidth;
	}

	showEntryStatusTimezone(entry, isWarning)
	{
		if (!Type.isDomNode(entry.statusNode) || !this.DOM.wrap.contains(entry.statusNode))
		{
			if (Type.isDomNode(this.DOM.statusNodeAll))
			{
				this.DOM.statusNodeAll.style.display = '';
			}

			if (isWarning)
			{
				Dom.addClass(this.DOM.statusNodeAll, '--warning');
			}

			return;
		}

		entry.statusNode.style.display = '';
		Dom.addClass(entry.statusNode, 'user-status-different-timezone');

		if (isWarning)
		{
			Dom.addClass(entry.statusNode, '--warning');
		}
		else
		{
			Dom.removeClass(entry.statusNode, '--warning');
		}
	}

	isWorkTimeNoticeEnabled()
	{
		return !this.solidStatus && Type.isArrayFilled(this.entries);
	}

	getAllEvents()
	{
		return this.allEvents;
	}

	doCheckSelectorStatus(event)
	{
		if (event instanceof BaseEvent)
		{
			const data = event.getData();
			this.clearCacheTime();
			const selectorStatus = this.checkTimePeriod(data.dateFrom, data.dateTo) === true;
			this.updateSelectorFromStatus(selectorStatus)
		}
	}

	updateSelectorFromStatus(status)
	{
		this.selector.setSelectorStatus(status);
		if (this.selector.isDragged())
		{
			this.hideProposeControl();
		}
		if (status)
		{
			Dom.removeClass(this.DOM.mainWrap, 'calendar-planner-selector-warning');
			this.hideProposeControl();
		}
		else
		{
			Dom.addClass(this.DOM.mainWrap, 'calendar-planner-selector-warning');
			if (!this.selector.isDragged())
			{
				this.showProposeControl();
			}
		}
	}

	proposeTime(params = {})
	{
		if (!Type.isPlainObject(params))
		{
			params = {};
		}

		let
			curTimestamp = Math.round(this.selector.getDateFrom().getTime() / (this.accuracy * 1000)) * this.accuracy * 1000,
			curDate = new Date(curTimestamp),
			duration = this.selector.getDuration();

		curDate.setSeconds(0,0);
		curTimestamp = curDate.getTime();

		const data = [...this.allEvents];
		data.sort(function(a, b){return a.fromTimestamp - b.fromTimestamp});

		let ts = curTimestamp;
		while (true)
		{
			let dateFrom = new Date(ts);
			let dateTo = new Date(ts + duration);

			if (!this.isOneDayScale())
			{
				let timeFrom = parseInt(dateFrom.getHours() + dateFrom.getMinutes() / 60);
				let timeTo = parseInt(dateTo.getHours() + dateTo.getMinutes() / 60);
				if (timeTo === 0)
				{
					timeTo = 24;
				}

				if (timeFrom <= this.shownScaleTimeFrom)
				{
					dateFrom.setHours(this.shownScaleTimeFrom, 0, 0, 0);
					ts = dateFrom.getTime();
					dateTo = new Date(ts + duration);
				}

				if (timeTo > this.shownScaleTimeTo)
				{
					dateFrom = new Date(ts + Util.getDayLength() - 1000); // next day
					dateFrom.setHours(this.shownScaleTimeFrom, 0, 0, 0);
					ts = dateFrom.getTime();
					dateTo = new Date(ts + duration);
				}
			}

			if (this.fullDayMode)
			{
				dateFrom.setHours(0, 0, 0, 0);
				dateTo.setHours(0, 0, 0, 0);
			}

			const checkRes = this.checkTimePeriod(dateFrom, dateTo, data);

			if (checkRes === true)
			{
				if (dateTo.getTime() > this.scaleDateTo.getTime())
				{
					if ((dateTo.getTime() - this.scaleDateTo.getTime()) > this.proposeTimeLimit * Util.getDayLength()
						||
						params.checkedFuture === true)
					{
						Planner.showNoResultNotification();
					}
					else if (params.checkedFuture !== true)
					{
						this.scaleDateTo = new Date(this.scaleDateTo.getTime() + Util.getDayLength() * this.proposeTimeLimit);
						this.expandTimeline(this.scaleDateFrom, this.scaleDateTo);
					}
				}
				else
				{
					if (this.fullDayMode)
						dateTo = new Date(dateTo.getTime() - Util.getDayLength());

					this.currentFromDate = dateFrom;
					this.currentToDate = dateTo;

					this.selector.update({
						from: dateFrom,
						to: dateTo,
						updateScaleType:false,
						updateScaleLimits:true,
						animation: true,
						focus: true
					});

					this.emit('onDateChange', new BaseEvent({data: {
						dateFrom: dateFrom,
						dateTo: dateTo,
						fullDay: this.fullDayMode
					}}));
				}
				break;
			}
			else if (checkRes && checkRes.toTimestampReal)
			{
				ts = checkRes.toTimestampReal;
				if (this.fullDayMode)
				{
					let dt = new Date(ts + Util.getDayLength() - 1000); // next day
					dt.setHours(0, 0, 0, 0);
					ts = dt.getTime();
				}
			}
		}
	}

	checkTimePeriod(fromDate, toDate, data)
	{
		if (!this.currentFromDate)
		{
			return true;
		}

		const timelineFrom = new Date();
		timelineFrom.setHours(this.shownScaleTimeFrom, 0, 0, 0);
		if (this.fullDayMode)
		{
			timelineFrom.setHours(0, 0, 0, 0);
		}
		if (fromDate && fromDate.getTime() < timelineFrom.getTime())
		{
			return true;
		}

		let result = true;
		let entry;

		if (!Type.isDate(fromDate) || !Type.isDate(toDate))
		{
			return result;
		}

		let fromTimestamp = fromDate.getTime();
		let toTimestamp = toDate.getTime();
		const cacheKey = fromTimestamp + '_' + toTimestamp;
		const accuracy = 3 * 60 * 1000; // 3min

		if (Type.isArray(data))
		{
			for (let i = 0; i < data.length; i++)
			{
				let item = data[i];

				if ((item.fromTimestamp + accuracy) <= toTimestamp && ((item.toTimestampReal || item.toTimestamp) - accuracy) >= fromTimestamp)
				{
					result = item;
					break;
				}
			}
		}
		else if (Type.isArray(this.entries))
		{
			let
				entriesAccessibleIndex = {},
				selectorAccuracy = this.selectorAccuracy * 1000,
				entryId;

			if (this.checkTimeCache[cacheKey] !== undefined)
			{
				result = this.checkTimeCache[cacheKey];
			}
			else
			{
				for (entryId in this.accessibility)
				{
					if (this.accessibility.hasOwnProperty(entryId))
					{
						entry = this.entries.find(function(el){return el.id === entryId.toString();});

						if (!entry)
						{
							continue;
						}

						entriesAccessibleIndex[entryId] = true;
						if (Type.isArray(this.accessibility[entryId]))
						{
							for (let i = 0; i < this.accessibility[entryId].length; i++)
							{
								let item = this.accessibility[entryId][i];

								if ((item.fromTimestamp + selectorAccuracy) <= toTimestamp && ((item.toTimestampReal || item.toTimestamp) - selectorAccuracy) >= fromTimestamp)
								{
									entriesAccessibleIndex[entryId] = false;
									result = item;
									break;
								}
							}
						}
					}
				}

				this.checkTimeCache[cacheKey] = result;
			}
		}

		return result;
	}

	clearCacheTime()
	{
		this.checkTimeCache = {};
	}

	showSettingsPopup()
	{
		let	settingsDialogCont = Tag.render`<div class="calendar-planner-settings-popup"></div>`;
		let scaleRow = settingsDialogCont.appendChild(Tag.render`
			<div class="calendar-planner-settings-row">
				<i>${Loc.getMessage('EC_PL_SETTINGS_SCALE')}:</i>
			</div>
		`);
		let scaleWrap = scaleRow.appendChild(Tag.render`
			<span class="calendar-planner-option-container"></span>
		`);


		if (this.fullDayMode)
		{
			scaleRow.title = Loc.getMessage('EC_PL_SETTINGS_SCALE_READONLY_TITLE');
			Dom.addClass(scaleRow, 'calendar-planner-option-container-disabled');
		}

		this.scaleTypes.forEach((scale)=>{
			scaleWrap.appendChild(Tag.render`<span class="calendar-planner-option-tab ${(scale === this.scaleType ? ' calendar-planner-option-tab-active' : '')}" data-bx-planner-scale="${scale}">${Loc.getMessage('EC_PL_SETTINGS_SCALE_' + scale.toUpperCase())}</span>`);
		});


		// Create and show settings popup
		let popup = PopupWindowManager.create(
			this.id + "-settings-popup",
			this.DOM.settingsButton,
			{
				autoHide: true,
				closeByEsc: true,
				offsetTop: -1,
				offsetLeft: 7,
				lightShadow: true,
				content: settingsDialogCont,
				zIndex: 4000,
				angle: {postion: 'top'},
				cacheable: false
			});
		popup.show(true);

		Event.bind(scaleWrap, 'click', (e) => {
			if (!this.fullDayMode)
			{
				let
					nodeTarget = e.target || e.srcElement,
					scale = nodeTarget && nodeTarget.getAttribute && nodeTarget.getAttribute('data-bx-planner-scale');

				if (scale)
				{
					this.changeScaleType(scale);
					popup.close();
				}
			}
		});
	}

	changeScaleType(scaleType)
	{
		if (scaleType !== this.scaleType)
		{
			this.setScaleType(scaleType);
			this.rebuild();
		}
	}

	setFullDayMode(fullDayMode)
	{
		if (fullDayMode !== this.fullDayMode)
		{
			this.fullDayMode = fullDayMode;
			if (fullDayMode && !this.isOneDayScale())
			{
				this.savedScaleType = this.scaleType;
				this.changeScaleType('1day');
			}
			else if (!fullDayMode && this.isOneDayScale() && this.savedScaleType)
			{
				this.changeScaleType(this.savedScaleType);
				this.savedScaleType = null;
			}
		}
	}

	static showNoResultNotification()
	{
		alert(Loc.getMessage('EC_PL_PROPOSE_NO_RESULT'));
	}

	showProposeControl()
	{
		if (!this.DOM.proposeTimeButton)
		{
			this.DOM.proposeTimeButton = this.DOM.mainWrap.appendChild(Tag.render`
				<div class="calendar-planner-time-arrow-right">
					<span class="calendar-planner-time-arrow-right-text">
						${Loc.getMessage('EC_PL_PROPOSE')}
					</span>
					<span class="calendar-planner-time-arrow-right-item"></span>
				</div>
			`);
			Event.bind(this.DOM.proposeTimeButton, 'click', this.proposeTime.bind(this));

			if (this.isLocked())
			{
				Dom.addClass(this.DOM.proposeTimeButton, '--lock');
			}
		}
		this.DOM.proposeTimeButton.style.display = "block";
		this.DOM.proposeTimeButton.style.top = (this.DOM.timelineDataWrap.offsetTop + this.DOM.timelineDataWrap.offsetHeight / 2 - 16) + "px";
	}

	hideProposeControl()
	{
		if (this.DOM.proposeTimeButton)
		{
			this.DOM.proposeTimeButton.style.display = "none";
		}
	}

	mouseMoveHandler(e)
	{
		let
			i, nodes,
			entryUid, parentTarget,
			prevEntry,
			mainContWrap = this.DOM.mainWrap,
			target = e.target || e.srcElement;

		entryUid = target.getAttribute('data-bx-planner-entry');
		if (!entryUid)
		{
			parentTarget = BX.findParent(target,
				function(node)
				{
					if (node == mainContWrap ||
						node.getAttribute && node.getAttribute('data-bx-planner-entry')
					)
					{
						return true;
					}
				},
				mainContWrap
			);

			if (parentTarget)
			{
				entryUid = target.getAttribute('data-bx-planner-entry')
			}
			else
			{
				Dom.removeClass(this.hoverRow, 'show');
				nodes = this.selector.controlWrap.querySelectorAll('.calendar-planner-selector-control-row.hover');
				for (i = 0; i < nodes.length; i++)
				{
					Dom.removeClass(nodes[i], 'hover');
				}
				prevEntry = this.getEntryByUniqueId(this.howerEntryId);
				if (prevEntry && prevEntry.selectWrap)
				{
					prevEntry.selectWrap.style.opacity = 1;
				}
			}
		}

		if (entryUid)
		{
			if (this.howerEntryId !== entryUid)
			{
				this.howerEntryId = entryUid;
				let entry = this.getEntryByUniqueId(entryUid);
				if (entry)
				{
					let top = parseInt(entry.dataRowWrap.offsetTop);
					Dom.addClass(this.hoverRow, 'show');
					this.hoverRow.style.top = (top + 36) + 'px';
					this.hoverRow.style.width = (parseInt(this.DOM.mainWrap.offsetWidth) + 5) + 'px';

					if (entry.selectorControlWrap)
					{
						nodes = this.selector.controlWrap.querySelectorAll('.calendar-planner-selector-control-row.hover');
						for (i = 0; i < nodes.length; i++)
						{
							Dom.removeClass(nodes[i], 'hover');
						}
						Dom.addClass(entry.selectorControlWrap, 'hover');
					}
				}
			}
		}
	}

	showLoader()
	{
		this.hideLoader();
		this.DOM.loader = this.DOM.mainWrap.appendChild(Util.getLoader(50));
		Dom.addClass(this.DOM.loader, 'calendar-planner-main-loader');
		this.loaderShown = true;
	}

	hideLoader()
	{
		if(Type.isDomNode(this.DOM.loader))
		{
			Dom.remove(this.DOM.loader);
		}
		this.loaderShown = false;
	}

	isLoaderShown()
	{
		return this.loaderShown;
	}

	isShown()
	{
		return this.shown;
	}

	isBuilt()
	{
		return this.built;
	}

	isLocked()
	{
		return this.locked;
	}

	lock()
	{
		if (!this.DOM.lockScreen)
		{
			this.DOM.lockScreen = Tag.render`
				<div class="calendar-planner-timeline-locker">
					<div class="calendar-planner-timeline-locker-container">
						<div class="calendar-planner-timeline-locker-top">
							<div class="calendar-planner-timeline-locker-icon"></div>
							<div class="calendar-planner-timeline-text">${Loc.getMessage('EC_PL_LOCKED_TITLE')}</div>
						</div>
						<div class="calendar-planner-timeline-locker-button">
							<a href="javascript:void(0)" onclick="top.BX.UI.InfoHelper.show('limit_crm_calender_planner');" class="ui-btn ui-btn-sm ui-btn-light-border ui-btn-round">${Loc.getMessage('EC_PL_UNLOCK_FEATURE')}</a>
						</div>
					</div>
				</div>
			`;
		}

		Dom.addClass(this.DOM.timelineFixedWrap, '--lock');
		this.DOM.timelineFixedWrap.appendChild(this.DOM.lockScreen);
	}

	doSegmentsIntersect(x1, x2, y1, y2)
	{
		return (x1 >= y1 && x1 <= y2)
			|| (x2 >= y1 && x2 <= y2)
			|| (x1 <= y1 && x2 >= y2);
	}

	setReadonly()
	{
		this.readonly = true;
		Dom.addClass(this.DOM.mainWrap, 'calendar-planner-readonly');
	}
}

Anon7 - 2022
AnonSec Team