/* * https://github.com/morethanwords/tweb * Copyright (C) 2019-2021 Eduard Kuzmenko * https://github.com/morethanwords/tweb/blob/master/LICENSE */ import PopupElement, { PopupOptions } from "."; import mediaSizes from "../../helpers/mediaSizes"; import I18n, { i18n, LangPackKey } from "../../lib/langPack"; import InputField from "../inputField"; export default class PopupDatePicker extends PopupElement { protected controlsDiv: HTMLElement; protected monthTitle: HTMLElement; protected prevBtn: HTMLElement; protected nextBtn: HTMLElement; protected monthsContainer: HTMLElement; protected month: HTMLElement; protected minMonth: Date; protected maxMonth: Date; protected minDate: Date; protected maxDate: Date; protected selectedDate: Date; protected selectedMonth: Date; protected selectedEl: HTMLElement; protected timeDiv: HTMLDivElement; protected hoursInputField: InputField; protected minutesInputField: InputField; constructor(initDate: Date, public onPick: (timestamp: number) => void, protected options: Partial<{ noButtons: true, noTitle: true, minDate: Date, maxDate: Date withTime: true, showOverflowMonths: true }> & PopupOptions = {}) { super('popup-date-picker', options.noButtons ? [] : [{ langKey: 'JumpToDate', callback: () => { if(this.onPick) { this.onPick(this.selectedDate.getTime() / 1000 | 0); } } }, { langKey: 'Cancel', isCancel: true }], {body: true, overlayClosable: true, ...options}); this.minDate = options.minDate || new Date('2013-08-01T00:00:00'); if(initDate < this.minDate) { initDate.setFullYear(this.minDate.getFullYear(), this.minDate.getMonth(), this.minDate.getDate()); } // Controls this.controlsDiv = document.createElement('div'); this.controlsDiv.classList.add('date-picker-controls'); this.prevBtn = document.createElement('button'); this.prevBtn.classList.add('btn-icon', 'tgico-down', 'date-picker-prev'); this.prevBtn.addEventListener('click', this.onPrevClick); this.nextBtn = document.createElement('button'); this.nextBtn.classList.add('btn-icon', 'tgico-down', 'date-picker-next'); this.nextBtn.addEventListener('click', this.onNextClick); this.monthTitle = document.createElement('div'); this.monthTitle.classList.add('date-picker-month-title'); this.controlsDiv.append(this.prevBtn, this.monthTitle, this.nextBtn); // Month this.monthsContainer = document.createElement('div'); this.monthsContainer.classList.add('date-picker-months'); this.monthsContainer.addEventListener('click', this.onDateClick); this.body.append(this.controlsDiv, this.monthsContainer); // Time inputs if(options.withTime) { this.timeDiv = document.createElement('div'); this.timeDiv.classList.add('date-picker-time'); const delimiter = document.createElement('div'); delimiter.classList.add('date-picker-time-delimiter'); delimiter.append(':'); const handleTimeInput = (max: number, inputField: InputField, onInput: (length: number) => void, onOverflow?: (number: number) => void) => { const maxString = '' + max; inputField.input.addEventListener('input', (e) => { let value = inputField.value.replace(/\D/g, ''); if(value.length > 2) { value = value.slice(0, 2); } else { if((value.length === 1 && +value[0] > +maxString[0]) || (value.length === 2 && +value > max)) { if(value.length === 2 && onOverflow) { onOverflow(+value[1]); } value = '0' + value[0]; } } inputField.setValueSilently(value); onInput(value.length); }); }; this.hoursInputField = new InputField({plainText: true}); this.minutesInputField = new InputField({plainText: true}); handleTimeInput(23, this.hoursInputField, (length) => { if(length === 2) { this.minutesInputField.input.focus(); } this.setTimeTitle(); }, (number) => { this.minutesInputField.value = (number + this.minutesInputField.value).slice(0, 2); }); handleTimeInput(59, this.minutesInputField, (length) => { if(!length) { this.hoursInputField.input.focus(); } this.setTimeTitle(); }); this.selectedDate = initDate; initDate.setMinutes(initDate.getMinutes() + 10); this.hoursInputField.setValueSilently(('0' + initDate.getHours()).slice(-2)); this.minutesInputField.setValueSilently(('0' + initDate.getMinutes()).slice(-2)); initDate.setHours(0, 0, 0, 0); this.timeDiv.append(this.hoursInputField.container, delimiter, this.minutesInputField.container); this.btnConfirm.addEventListener('click', () => { if(this.onPick) { this.selectedDate.setHours(+this.hoursInputField.value || 0, +this.minutesInputField.value || 0, 0, 0); this.onPick(this.selectedDate.getTime() / 1000 | 0); } this.hide(); }, {once: true}); this.body.append(this.timeDiv); this.prevBtn.classList.add('primary'); this.nextBtn.classList.add('primary'); } const popupCenterer = document.createElement('div'); popupCenterer.classList.add('popup-centerer'); popupCenterer.append(this.container); this.element.append(popupCenterer); //const passed = (initDate.getTime() - (initDate.getTimezoneOffset() * 60000)) % 86400000; //this.selectedDate = this.maxDate = new Date(initDate.getTime() - passed); initDate.setHours(0, 0, 0, 0); this.selectedDate = initDate; this.maxDate = options.maxDate || new Date(); this.maxDate.setHours(0, 0, 0, 0); this.selectedMonth = new Date(this.selectedDate); this.selectedMonth.setDate(1); this.maxMonth = new Date(this.maxDate); this.maxMonth.setDate(1); this.minMonth = new Date(this.minDate); this.minMonth.setHours(0, 0, 0, 0); this.minMonth.setDate(1); if(this.selectedMonth.getTime() === this.minMonth.getTime()) { this.prevBtn.setAttribute('disabled', 'true'); } if(this.selectedMonth.getTime() === this.maxMonth.getTime()) { this.nextBtn.setAttribute('disabled', 'true'); } if(options.noTitle) { this.setTitle = () => {}; } this.setTimeTitle(); this.setTitle(); this.setMonth(); } onPrevClick = (e: MouseEvent) => { this.selectedMonth.setMonth(this.selectedMonth.getMonth() - 1); this.setMonth(); if(this.selectedMonth.getTime() === this.minMonth.getTime()) { this.prevBtn.setAttribute('disabled', 'true'); } this.nextBtn.removeAttribute('disabled'); }; onNextClick = (e: MouseEvent) => { this.selectedMonth.setMonth(this.selectedMonth.getMonth() + 1); this.setMonth(); if(this.selectedMonth.getTime() === this.maxMonth.getTime()) { this.nextBtn.setAttribute('disabled', 'true'); } this.prevBtn.removeAttribute('disabled'); }; onDateClick = (e: MouseEvent) => { //cancelEvent(e); const target = e.target as HTMLElement; if(!target.dataset.timestamp) return; if(this.selectedEl) { if(this.selectedEl === target) return; this.selectedEl.classList.remove('active'); } this.selectedEl = target; target.classList.add('active'); const timestamp = +target.dataset.timestamp; this.selectedDate = new Date(timestamp); this.setTitle(); this.setTimeTitle(); }; public setTimeTitle() { if(this.btnConfirm && this.selectedDate) { let key: LangPackKey, args: any[] = []; const date = new Date(); date.setHours(0, 0, 0, 0); const timeOptions: Intl.DateTimeFormatOptions = { minute: '2-digit', hour: '2-digit' }; const sendDate = new Date(this.selectedDate.getTime()); sendDate.setHours(+this.hoursInputField.value, +this.minutesInputField.value); if(this.selectedDate.getTime() === date.getTime()) { key = 'Schedule.SendToday'; }/* else if(this.selectedDate.getTime() === (date.getTime() + 86400e3)) { dayStr = 'Tomorrow'; } */ else { key = 'Schedule.SendDate'; const dateOptions: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric' }; if(sendDate.getFullYear() !== date.getFullYear()) { dateOptions.year = 'numeric'; } args.push(new I18n.IntlDateElement({ date: sendDate, options: dateOptions }).element); } args.push(new I18n.IntlDateElement({ date: sendDate, options: timeOptions }).element); this.btnConfirm.firstChild.replaceWith(i18n(key, args)); } } public setTitle() { //const splitted = this.selectedDate.toString().split(' ', 3); //this.title.innerText = splitted[0] + ', ' + splitted[1] + ' ' + splitted[2]; this.title.textContent = ''; this.title.append(new I18n.IntlDateElement({ date: this.selectedDate, options: { day: 'numeric', month: 'long', weekday: 'short' } }).element); } private renderElement(disabled: boolean, innerText: string | HTMLElement = '') { const el = document.createElement('button'); el.classList.add('btn-icon', 'date-picker-month-date'); if(disabled) { el.setAttribute('disabled', 'true'); } if(innerText) { el.append(innerText); } return el; } public setMonth() { const firstDate = new Date(this.selectedMonth); const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: this.timeDiv && mediaSizes.isMobile ? 'short' : 'long' }; this.monthTitle.textContent = ''; this.monthTitle.append(new I18n.IntlDateElement({date: firstDate, options}).element); //this.monthTitle.innerText = (this.timeDiv && mediaSizes.isMobile ? monthName.slice(0, 3) : monthName) + ' ' + this.selectedMonth.getFullYear(); if(this.month) { this.month.remove(); } this.month = document.createElement('div'); this.month.classList.add('date-picker-month'); const weekStartDate = new Date(); const day = weekStartDate.getDay(); if(day !== 1) { weekStartDate.setHours(-24 * (day - 1)); } for(let i = 0; i < 7; ++i) { const el = this.renderElement(true, new I18n.IntlDateElement({date: weekStartDate, options: {weekday: 'narrow'}}).element); el.classList.remove('date-picker-month-date'); el.classList.add('date-picker-month-day'); this.month.append(el); weekStartDate.setDate(weekStartDate.getDate() + 1); } // 0 - sunday let dayIndex = firstDate.getDay() - 1; if(dayIndex === -1) dayIndex = 7 - 1; const clonedDate = new Date(firstDate.getTime()); clonedDate.setDate(clonedDate.getDate() - dayIndex - 1); // Padding first week for(let i = 0; i < dayIndex; ++i) { if(this.options.showOverflowMonths) { clonedDate.setDate(clonedDate.getDate() + 1); this.month.append(this.renderElement(true, '' + clonedDate.getDate())); } else { this.month.append(this.renderElement(true)); } } do { const date = firstDate.getDate(); const el = this.renderElement(firstDate > this.maxDate || firstDate < this.minDate, '' + date); el.dataset.timestamp = '' + firstDate.getTime(); if(firstDate.getTime() === this.selectedDate.getTime()) { this.selectedEl = el; el.classList.add('active'); } this.month.append(el); firstDate.setDate(date + 1); } while(firstDate.getDate() !== 1); const remainder = this.month.childElementCount % 7; if(this.options.showOverflowMonths && remainder) { for(let i = remainder; i < 7; ++i) { this.month.append(this.renderElement(true, '' + firstDate.getDate())); firstDate.setDate(firstDate.getDate() + 1); } } const lines = Math.ceil(this.month.childElementCount / 7); this.container.dataset.lines = '' + lines; this.monthsContainer.append(this.month); } }