Add month/year selector and use UTC date functions

This commit is contained in:
Eric Eastwood 2022-02-17 20:40:48 -06:00
parent fe3f515862
commit 6d5ad656b6
4 changed files with 152 additions and 31 deletions

View File

@ -30,13 +30,13 @@ html {
font: 100% system-ui; font: 100% system-ui;
} }
.CalendarView_heading { .CalendarView_header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.CalendarView_heading_prevButton, .CalendarView_header_prevButton,
.CalendarView_heading_nextButton { .CalendarView_header_nextButton {
padding-left: 2em; padding-left: 2em;
padding-right: 2em; padding-right: 2em;
@ -44,12 +44,57 @@ html {
border: 0; border: 0;
} }
.CalendarView_heading_prevButton:hover, .CalendarView_header_prevButton:hover,
.CalendarView_heading_nextButton:hover { .CalendarView_header_nextButton:hover {
cursor: pointer; cursor: pointer;
} }
.CalendarView_heading {
position: relative;
}
.CalendarView_heading_text { .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 { .CalendarView_calendar {

View File

@ -16,7 +16,7 @@ function sameDay(date1, date2) {
// //
// via https://stackoverflow.com/a/1184359/796832 // via https://stackoverflow.com/a/1184359/796832
function numDaysInMonthForDate(date) { 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 // Map from day of week to the localized name of the day
@ -34,10 +34,10 @@ const DAYS_OF_WEEK = {
const today = new Date(); const today = new Date();
for (let i = 0; i < 7; i++) { for (let i = 0; i < 7; i++) {
const lookupDate = new Date(today); const lookupDate = new Date(today);
lookupDate.setDate(i + 1); lookupDate.setUTCDate(i + 1);
const lookup = lookupDate.toLocaleString('en-US', { weekday: 'short' }); const lookup = lookupDate.toLocaleString('en-US', { weekday: 'short', timeZone: 'UTC' });
const localized = lookupDate.toLocaleString('default', { weekday: 'short' }); const localized = lookupDate.toLocaleString('default', { weekday: 'short', timeZone: 'UTC' });
DAYS_OF_WEEK[lookup] = localized; DAYS_OF_WEEK[lookup] = localized;
} }
@ -45,25 +45,84 @@ for (let i = 0; i < 7; i++) {
class CalendarView extends TemplateView { class CalendarView extends TemplateView {
render(t, vm) { render(t, vm) {
return t.div({ className: { CalendarView: true } }, [ return t.div({ className: { CalendarView: true } }, [
t.div({ className: { CalendarView_heading: true } }, [ t.div({ className: { CalendarView_header: true } }, [
t.button( t.button(
{ {
className: { CalendarView_heading_prevButton: true }, className: { CalendarView_header_prevButton: true },
onClick: () => vm.prevMonth(), onClick: () => vm.prevMonth(),
}, },
['\u276E'] ['\u276E']
), ),
t.map( t.map(
(vm) => vm.calendarDate, (vm) => vm.calendarDate,
(date, t) => { (calendarDate, t) => {
return t.h4({ className: { CalendarView_heading_text: true } }, [ return t.h4({ className: { CalendarView_heading: true } }, [
date.toLocaleString('default', { year: 'numeric', month: 'long' }), 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( t.button(
{ {
className: { CalendarView_heading_nextButton: true }, className: { CalendarView_header_nextButton: true },
onClick: () => vm.nextMonth(), onClick: () => vm.nextMonth(),
}, },
['\u276F'] ['\u276F']
@ -84,14 +143,14 @@ class CalendarView extends TemplateView {
let dayNodes = []; let dayNodes = [];
for (let i = 0; i < numDaysInMonthForDate(calendarDate); i++) { for (let i = 0; i < numDaysInMonthForDate(calendarDate); i++) {
const dayNumberDate = new Date(calendarDate); const dayNumberDate = new Date(calendarDate);
dayNumberDate.setDate(i); dayNumberDate.setUTCDate(i);
const isDayInFuture = dayNumberDate.getTime() - todayTs > 0; const isDayInFuture = dayNumberDate.getTime() - todayTs > 0;
// The current day displayed in the archive // The current day displayed in the archive
const isActive = sameDay(dayNumberDate, vm.activeDate); const isActive = sameDay(dayNumberDate, vm.activeDate);
// day number from 0 (monday) to 6 (sunday) // 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'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) // +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, CalendarView_dayLink_disabled: isDayInFuture,
}, },
// Disable navigation to future days // Disable navigation to future days
href: isDayInFuture ? null : vm.linkForDate(dayNumberDate), href: isDayInFuture ? null : vm.archiveUrlForDate(dayNumberDate),
}, },
[String(i + 1)] [String(i + 1)]
), ),

View File

@ -12,14 +12,7 @@ class RightPanelContentView extends TemplateView {
todo: true, todo: true,
}, },
}, },
[ [t.view(new CalendarView(vm.calendarViewModel))]
t.div('test'),
t.input({
type: 'month',
value: vm.calendarViewModel.activeDate.toISOString().split('T')[0],
}),
t.view(new CalendarView(vm.calendarViewModel)),
]
); );
} }
} }

View File

@ -38,6 +38,15 @@ assert(config);
assert(config.matrixServerUrl); assert(config.matrixServerUrl);
assert(config.basePath); assert(config.basePath);
function addSupportClasses() {
const input = document.createElement('input');
input.type = 'month';
const isMonthTypeSupported = input.type === 'month';
// Signal `<input type="month">` support to our CSS
document.body.classList.toggle('fallback-input-month', !isMonthTypeSupported);
}
let eventIndexCounter = 0; let eventIndexCounter = 0;
const fragmentIdComparer = new FragmentIdComparer([]); const fragmentIdComparer = new FragmentIdComparer([]);
function makeEventEntryFromEventJson(eventJson, memberEvent) { function makeEventEntryFromEventJson(eventJson, memberEvent) {
@ -225,7 +234,7 @@ async function mountHydrogen() {
return this._calendarDate; return this._calendarDate;
} }
linkForDate(date) { archiveUrlForDate(date) {
// Gives the date in YYYY-mm-dd format. // Gives the date in YYYY-mm-dd format.
// date.toISOString() -> 2022-02-16T23:20:04.709Z // date.toISOString() -> 2022-02-16T23:20:04.709Z
const urlDate = date.toISOString().split('T')[0].replaceAll('-', '/'); const urlDate = date.toISOString().split('T')[0].replaceAll('-', '/');
@ -235,16 +244,29 @@ async function mountHydrogen() {
prevMonth() { prevMonth() {
const prevMonthDate = new Date(this._calendarDate); const prevMonthDate = new Date(this._calendarDate);
prevMonthDate.setMonth(this._date.getMonth() - 1); prevMonthDate.setUTCMonth(this._calendarDate.getUTCMonth() - 1);
this._calendarDate = prevMonthDate; this._calendarDate = prevMonthDate;
this.emitChange('date'); this.emitChange('calendarDate');
} }
nextMonth() { nextMonth() {
const nextMonthDate = new Date(this._calendarDate); 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._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()); //console.log('view.mount()', view.mount());
app.replaceChildren(view.mount()); app.replaceChildren(view.mount());
addSupportClasses();
} }
// N.B.: When we run this in a `vm`, it will return the last statement. It's // N.B.: When we run this in a `vm`, it will return the last statement. It's