import {Et2Date, parseDate} from "../../api/js/etemplate/Et2Date/Et2Date"; import {CalendarApp} from "./app"; import {css, html, render} from "lit"; import {app} from "../../api/js/jsapi/egw_global"; export class SidemenuDate extends Et2Date { static get styles() { return [ ...super.styles, css` /** Hide input **/ ::slotted(input) { display: none; } /** Special sizing for headers **/ .flatpickr-months > * { padding: 3px; height: 20px; } .flatpickr-current-month { height: 20px; font-size: 110% } div.flatpickr-calendar.inline .flatpickr-current-month .flatpickr-monthDropdown-months { width: 70%; } div.flatpickr-calendar.inline { width: 100% !important; } /** Responsive resize is in etemplate2.css since we can't reach that far inside **/ ` ]; } constructor() { super(); this._onDayCreate = this._onDayCreate.bind(this); this._handleChange = this._handleChange.bind(this); this._handleDayHover = this._handleDayHover.bind(this); this._clearHover = this._clearHover.bind(this); this._handleHeaderChange = this._handleHeaderChange.bind(this); } async connectedCallback() { render(this._inputTemplate(), this); super.connectedCallback(); this.removeEventListener("change", this._oldChange); this.addEventListener("change", this._handleChange); } disconnectedCallback() { super.disconnectedCallback(); this.removeEventListener("change", this._handleChange); if(this._instance && this._instance.daysContainer !== undefined) { this._instance.weekNumbers.removeEventListener("mouseover", this._handleDayHover); this._instance.weekNumbers.removeEventListener("mouseout", this._clearHover); } } /** * Initialze flatpickr, and bind to any internal elements we're interested in * * Normal pre-creation config goes in this.getOptions() * * @returns {Promise} */ async init() { await super.init(); // This needs to wait until after everything is created if(this._instance?.daysContainer !== undefined) { this._instance.weekNumbers.addEventListener("mouseover", this._handleDayHover); this._instance.weekNumbers.addEventListener("mouseout", this._clearHover); } } /** * Override some flatpickr defaults to get things how we like it * * @see https://flatpickr.js.org/options/ * @returns {any} */ public getOptions() { let options = super.getOptions(); // No scroll - remove scroll plugin options.plugins = []; // Add "Ok" and "today" buttons back in, if desired const buttons = this._buttonPlugin(); if(buttons) { options.plugins.push(buttons) } options.allowInput = false; options.inline = true; options.dateFormat = "Y-m-dT00:00:00\\Z"; options.onMonthChange = this._handleHeaderChange; options.onYearChange = this._handleHeaderChange; options.wrap = false; return options } protected _buttonPlugin() { // No buttons return null; } /** * Override from parent - This is the node we tell flatpickr to use * It must be an , flatpickr doesn't understand anything else * @returns {any} */ findInputField() : HTMLInputElement { return this._inputNode; } set_value(value) { if(typeof value === "string" && value.length == 8) { super.set_value(parseDate(value, "Ymd")); } else { super.set_value(value); } } /** * Handler for change events. Re-bound to be able to cancel month changes, since it's an input and emits them * * @param dates * @param {string} dateString * @param instance * @protected */ protected _handleChange(_ev) { if(_ev.target == this._instance.monthsDropdownContainer) { _ev.preventDefault(); _ev.stopPropagation(); return false; } let view_change = app.calendar.sidebox_changes_views.indexOf(app.calendar.state.view); let update = {date: this.getValue()}; if(view_change >= 0) { update.view = app.calendar.sidebox_changes_views[view_change ? view_change - 1 : view_change]; } else if(app.calendar.state.view == 'listview') { update.filter = 'after'; } else if(app.calendar.state.view == 'planner') { update.planner_view = 'day'; } app.calendar.update_state(update); } /** * Handle click on shortcut button(s) like "Today" * * @param button_index * @param fp Flatpickr instance */ public _handleShortcutButtonClick(button_index, fp) { // This just changes the calendar to today super._handleShortcutButtonClick(button_index, fp); let temp_date = new Date("" + this._instance.currentYear + "-" + (this._instance.currentMonth + 1) + "-" + (this._instance.selectedDates[0].getDate() || "01")); // Go directly let update = {date: temp_date}; app.calendar.update_state(update); } _handleClick(_ev : MouseEvent) : boolean { //@ts-ignore if(this._instance.weekNumbers.contains(_ev.target)) { return this._handleWeekClick(_ev); } return super._handleClick(_ev); } /** * Handle clicking on the week number * * @param {MouseEvent} _ev * @returns {boolean} */ _handleWeekClick(_ev : MouseEvent) { let view = app.calendar.state.view; let days = app.calendar.state.days; let fp = this._instance; // Avoid a full state update, we just want the calendar to update // Directly update to avoid change event from the sidebox calendar let week_index = Array.prototype.indexOf.call(fp.weekNumbers.children, _ev.target); if(week_index == -1) { return false; } let weekStartDay = fp.days.childNodes[7 * week_index].dateObj; fp.setDate(weekStartDay); let date = new Date(weekStartDay); date.setUTCMinutes(date.getUTCMinutes() - date.getTimezoneOffset()); date = app.calendar.date.toString(date); // Set to week view, if in one of the views where we change view if(app.calendar.sidebox_changes_views.indexOf(view) >= 0) { app.calendar.update_state({view: 'week', date: date, days: days}); } else if(view == 'planner') { // Clicked a week, show just a week app.calendar.update_state({date: date, planner_view: 'week'}); } else if(view == 'listview') { app.calendar.update_state({ date: date, end_date: app.calendar.date.toString(CalendarApp.views.week.end_date({date: date})), filter: 'week' }); } else { app.calendar.update_state({date: date}); } return true; } /** * Handle a hover over a day * @param {MouseEvent} _ev * @returns {boolean} */ _handleDayHover(_ev : MouseEvent) { if(this._instance.weekNumbers.contains(_ev.target)) { return this._highlightWeek(_ev.target); } } /** * Highlight a week based on the given week number HTMLElement */ _highlightWeek(weekElement) { let fp = this._instance; let week_index = Array.prototype.indexOf.call(fp.weekNumbers.children, weekElement); if(week_index == -1) { return false; } fp.weekStartDay = fp.days.childNodes[7 * week_index].dateObj; fp.weekEndDay = fp.days.childNodes[7 * (week_index + 1) - 1].dateObj; let days = fp.days.childNodes; for(let i = days.length; i--;) { let date = days[i].dateObj; if(date >= fp.weekStartDay && date <= fp.weekEndDay) { days[i].classList.add("inRange"); } else { days[i].classList.remove("inRange") } } } _clearHover() { let days = this._instance.days.childNodes; for(var i = days.length; i--;) days[i].classList.remove("inRange"); } get _headerNode() { return this._instance?.monthNav; } /** * Year or month changed * * @protected */ protected _handleHeaderChange(selectedDates : Date[], value : string) { const update = {}; if(this.formatDate(selectedDates[0], this._instance.config.dateFormat) == value) { // Header changed, selected date did not - get a date in the new month const maxDays = new Date(this._instance.currentYear, this._instance.currentMonth + 1, 0).getDate(); let temp_date = new Date("" + this._instance.currentYear + "-" + (this._instance.currentMonth + 1) + "-" + ("" + Math.min(maxDays, new Date(this.value).getUTCDate())).padStart(2, "0") ); temp_date.setUTCMinutes(temp_date.getUTCMinutes() + temp_date.getTimezoneOffset()); update['date'] = temp_date; } else { // User selected a date in a different month, header hasn't changed update['date'] = value; } // Go directly app.calendar.update_state(update); } /** * The interactive (form) element. * @protected */ get _inputNode() { return this.querySelector('input'); } protected _inputTemplate() { // Plain input return html` `; } } customElements.define("calendar-date", SidemenuDate);