From 6d5ad656b6f91319267bd2b5d1638edc2f78db06 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 17 Feb 2022 20:40:48 -0600 Subject: [PATCH] Add month/year selector and use UTC date functions --- public/styles/styles.css | 55 +++++++++++++++++-- shared/CalendarView.js | 85 ++++++++++++++++++++++++----- shared/RightPanelContentView.js | 9 +-- shared/hydrogen-vm-render-script.js | 34 ++++++++++-- 4 files changed, 152 insertions(+), 31 deletions(-) diff --git a/public/styles/styles.css b/public/styles/styles.css index fcce0a9..5adb8e6 100644 --- a/public/styles/styles.css +++ b/public/styles/styles.css @@ -30,13 +30,13 @@ html { font: 100% system-ui; } -.CalendarView_heading { +.CalendarView_header { display: flex; justify-content: space-between; } -.CalendarView_heading_prevButton, -.CalendarView_heading_nextButton { +.CalendarView_header_prevButton, +.CalendarView_header_nextButton { padding-left: 2em; padding-right: 2em; @@ -44,12 +44,57 @@ html { border: 0; } -.CalendarView_heading_prevButton:hover, -.CalendarView_heading_nextButton:hover { +.CalendarView_header_prevButton:hover, +.CalendarView_header_nextButton:hover { cursor: pointer; } +.CalendarView_heading { + position: relative; +} + .CalendarView_heading_text { + display: inline-flex; + align-items: center; +} + +.CalendarView_heading_monthInput { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + + opacity: 0; +} + +.fallback-input-month .CalendarView_heading_monthInput { + display: none; +} + +.CalendarView_heading_monthInput::-webkit-calendar-picker-indicator { + width: 100%; + height: 100%; + background-color: #f00; +} + +.CalendarView_heading_yearSelectFallback { + display: none; + + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + + width: 100%; + height: 100%; + + opacity: 0; +} + +.fallback-input-month .CalendarView_heading_yearSelectFallback { + display: block; } .CalendarView_calendar { diff --git a/shared/CalendarView.js b/shared/CalendarView.js index 6ffbbf7..04a51e9 100644 --- a/shared/CalendarView.js +++ b/shared/CalendarView.js @@ -16,7 +16,7 @@ function sameDay(date1, date2) { // // via https://stackoverflow.com/a/1184359/796832 function numDaysInMonthForDate(date) { - return new Date(date.getYear(), date.getMonth() + 1, 0).getDate(); + return new Date(date.getUTCFullYear(), date.getUTCMonth() + 1, 0).getUTCDate(); } // Map from day of week to the localized name of the day @@ -34,10 +34,10 @@ const DAYS_OF_WEEK = { const today = new Date(); for (let i = 0; i < 7; i++) { const lookupDate = new Date(today); - lookupDate.setDate(i + 1); + lookupDate.setUTCDate(i + 1); - const lookup = lookupDate.toLocaleString('en-US', { weekday: 'short' }); - const localized = lookupDate.toLocaleString('default', { weekday: 'short' }); + const lookup = lookupDate.toLocaleString('en-US', { weekday: 'short', timeZone: 'UTC' }); + const localized = lookupDate.toLocaleString('default', { weekday: 'short', timeZone: 'UTC' }); DAYS_OF_WEEK[lookup] = localized; } @@ -45,25 +45,84 @@ for (let i = 0; i < 7; i++) { class CalendarView extends TemplateView { render(t, vm) { return t.div({ className: { CalendarView: true } }, [ - t.div({ className: { CalendarView_heading: true } }, [ + t.div({ className: { CalendarView_header: true } }, [ t.button( { - className: { CalendarView_heading_prevButton: true }, + className: { CalendarView_header_prevButton: true }, onClick: () => vm.prevMonth(), }, ['\u276E'] ), t.map( (vm) => vm.calendarDate, - (date, t) => { - return t.h4({ className: { CalendarView_heading_text: true } }, [ - date.toLocaleString('default', { year: 'numeric', month: 'long' }), + (calendarDate, t) => { + return t.h4({ className: { CalendarView_heading: true } }, [ + t.span({ className: { CalendarView_heading_text: true } }, [ + calendarDate.toLocaleString('default', { + year: 'numeric', + month: 'long', + timeZone: 'UTC', + }), + t.svg( + { + xmlns: 'http://www.w3.org/2000/svg', + width: '18', + height: '18', + viewBox: '0 0 18 18', + fill: 'none', + }, + [ + t.path({ + d: 'M6 7.5L9 10.5L12 7.5', + stroke: 'currentColor', + 'stroke-width': '1.5', + 'stroke-linecap': 'round', + 'stroke-linejoin': 'round', + }), + ] + ), + ]), + + t.input({ + type: 'month', + className: { CalendarView_heading_monthInput: true }, + value: `${calendarDate.getUTCFullYear()}-${calendarDate.getUTCMonth() + 1}`, + onChange: (e) => vm.onMonthInputChange(e), + }), + + t.select( + { + className: { + CalendarView_heading_yearSelectFallback: true, + }, + onChange: (e) => vm.onYearFallbackSelectChange(e), + }, + [].concat( + (() => { + let yearSelectNodes = []; + const today = new Date(); + for (let year = today.getUTCFullYear(); year > 1960; year--) { + yearSelectNodes.push( + t.option( + { + value: year, + selected: year === calendarDate.getUTCFullYear(), + }, + [`${year}`] + ) + ); + } + + return yearSelectNodes; + })() + ) + ), ]); } ), t.button( { - className: { CalendarView_heading_nextButton: true }, + className: { CalendarView_header_nextButton: true }, onClick: () => vm.nextMonth(), }, ['\u276F'] @@ -84,14 +143,14 @@ class CalendarView extends TemplateView { let dayNodes = []; for (let i = 0; i < numDaysInMonthForDate(calendarDate); i++) { const dayNumberDate = new Date(calendarDate); - dayNumberDate.setDate(i); + dayNumberDate.setUTCDate(i); const isDayInFuture = dayNumberDate.getTime() - todayTs > 0; // The current day displayed in the archive const isActive = sameDay(dayNumberDate, vm.activeDate); // day number from 0 (monday) to 6 (sunday) - const dayNumber = dayNumberDate.getDay(); + const dayNumber = dayNumberDate.getUTCDay(); // +1 because we're going from 0-based day to 1-based `grid-column-start` // +1 because we actually start the week on Sunday(6) instead of Monday(0) @@ -113,7 +172,7 @@ class CalendarView extends TemplateView { CalendarView_dayLink_disabled: isDayInFuture, }, // Disable navigation to future days - href: isDayInFuture ? null : vm.linkForDate(dayNumberDate), + href: isDayInFuture ? null : vm.archiveUrlForDate(dayNumberDate), }, [String(i + 1)] ), diff --git a/shared/RightPanelContentView.js b/shared/RightPanelContentView.js index 95c1e7a..b67c804 100644 --- a/shared/RightPanelContentView.js +++ b/shared/RightPanelContentView.js @@ -12,14 +12,7 @@ class RightPanelContentView extends TemplateView { todo: true, }, }, - [ - t.div('test'), - t.input({ - type: 'month', - value: vm.calendarViewModel.activeDate.toISOString().split('T')[0], - }), - t.view(new CalendarView(vm.calendarViewModel)), - ] + [t.view(new CalendarView(vm.calendarViewModel))] ); } } diff --git a/shared/hydrogen-vm-render-script.js b/shared/hydrogen-vm-render-script.js index 91d897a..7a31376 100644 --- a/shared/hydrogen-vm-render-script.js +++ b/shared/hydrogen-vm-render-script.js @@ -38,6 +38,15 @@ assert(config); assert(config.matrixServerUrl); assert(config.basePath); +function addSupportClasses() { + const input = document.createElement('input'); + input.type = 'month'; + const isMonthTypeSupported = input.type === 'month'; + + // Signal `` support to our CSS + document.body.classList.toggle('fallback-input-month', !isMonthTypeSupported); +} + let eventIndexCounter = 0; const fragmentIdComparer = new FragmentIdComparer([]); function makeEventEntryFromEventJson(eventJson, memberEvent) { @@ -225,7 +234,7 @@ async function mountHydrogen() { return this._calendarDate; } - linkForDate(date) { + archiveUrlForDate(date) { // Gives the date in YYYY-mm-dd format. // date.toISOString() -> 2022-02-16T23:20:04.709Z const urlDate = date.toISOString().split('T')[0].replaceAll('-', '/'); @@ -235,16 +244,29 @@ async function mountHydrogen() { prevMonth() { const prevMonthDate = new Date(this._calendarDate); - prevMonthDate.setMonth(this._date.getMonth() - 1); + prevMonthDate.setUTCMonth(this._calendarDate.getUTCMonth() - 1); this._calendarDate = prevMonthDate; - this.emitChange('date'); + this.emitChange('calendarDate'); } nextMonth() { const nextMonthDate = new Date(this._calendarDate); - nextMonthDate.setMonth(this._date.getMonth() + 1); + nextMonthDate.setUTCMonth(this._calendarDate.getUTCMonth() + 1); + console.log('nextMonthDate', nextMonthDate); this._calendarDate = nextMonthDate; - this.emitChange('date'); + this.emitChange('calendarDate'); + } + + onMonthInputChange(e) { + const selectedDate = new Date(e.target.valueAsDate); + console.log('onMonthInputChange selectedDate', selectedDate, e.target.valueAsDate); + this._calendarDate = selectedDate; + this.emitChange('calendarDate'); + } + + onYearFallbackSelectChange(e) { + // TODO + console.log('onYearFallbackSelectChange', e); } } @@ -269,6 +291,8 @@ async function mountHydrogen() { //console.log('view.mount()', view.mount()); app.replaceChildren(view.mount()); + + addSupportClasses(); } // N.B.: When we run this in a `vm`, it will return the last statement. It's