import React from 'react'; import './Calendar.scss'; import {formatJG, jdnGregorian, JulianDay, JulianMonth, monthName, weekdayNames} from '@common/gregorian'; import {jdnLongCount} from '@common/longCount'; import {jdnJulian, julianJDN, julianMonthDays} from '@common/julian'; import {frDateFormat, frEndJD, frStartJD, jdnFrench} from '@common/french'; import {useMobileTooltipProps} from '@common/MobileTooltip'; type MonthProps = { year: number; month: JulianMonth; }; type DateProps = MonthProps & { day: JulianDay; }; function WeekdayName({name}: { name: string }): JSX.Element { return
{name}
; } function DayDetail({jdn}: { jdn: number }): JSX.Element { const lc = jdnLongCount(jdn); const mobile = useMobileTooltipProps(); return
JD {jdn}
G.{' '} {formatJG(jdnGregorian(jdn))}
{lc &&
LC{' '} {lc.join('.\u200b')}
} {jdn >= frStartJD && jdn <= frEndJD &&
FR{' '} {frDateFormat(jdnFrench(jdn))}
}
; } function Day({year, month, day, todayJDN}: DateProps & { todayJDN: number }): JSX.Element { const jdn = julianJDN(year, month, day); return
{day}
{weekdayNames[(jdn + 1) % 7]}
; } function Month({year, month, todayJDN}: MonthProps & { todayJDN: number }): JSX.Element { const decadeHeads = weekdayNames.map((name, i) => ); const firstJDN = julianJDN(year, month, 1); const firstWeekday = (firstJDN + 1) % 7; const daysTotal = julianMonthDays(year, month); return
{decadeHeads}
{ Array.from(Array(6).keys()).flatMap(i => { if (i * 7 - firstWeekday + 1 > daysTotal) return []; return Array.from(Array(7).keys()).map(j => { const day = i * 7 + j - firstWeekday + 1 as JulianDay; if (day < 1 || day > daysTotal) return
; return
; }); }) }
; } export type CalendarProps = MonthProps & { todayJDN: number; onSwitch?: (year: number, month: JulianMonth) => void, }; type CalendarState = { selecting: boolean, yearStr: string, }; export class Calendar extends React.Component { selection: React.RefObject; constructor(props: CalendarProps) { super(props); this.state = { selecting: false, yearStr: this.props.year.toString(), }; this.selection = React.createRef(); } componentDidMount() { document.addEventListener('click', this.handleClickOutside, true); } componentWillUnmount() { document.removeEventListener('click', this.handleClickOutside, true); } private goToNormalized(year: number, month: number) { if (month < 1) { --year; month += 12; } if (month > 12) { ++year; month -= 12; } this.props.onSwitch && this.props.onSwitch(year, month as JulianMonth); } prevYear = () => { this.goToNormalized(this.props.year - 1, this.props.month); }; prevMonth = () => { this.goToNormalized(this.props.year, this.props.month - 1); }; nextYear = () => { this.goToNormalized(this.props.year + 1, this.props.month); }; nextMonth = () => { this.goToNormalized(this.props.year, this.props.month + 1); }; startSelection = () => { this.setState({selecting: true}); }; handleClickOutside = (event: any) => { if (this.state.selecting && this.selection.current && !this.selection.current.contains(event.target)) this.setState({selecting: false}); }; handleKeyUp = (event: any) => { if (event.key === 'Escape') this.setState({selecting: false}); }; monthChange = (event: any) => { this.goToNormalized(this.props.year, +event.target.value as JulianMonth); }; yearChange = (event: any) => { if (/^-?\d+/.test(event.target.value)) { this.goToNormalized(+event.target.value, this.props.month); } this.setState({yearStr: event.target.value}); }; goToToday = () => { const [year, month] = jdnJulian(this.props.todayJDN); this.goToNormalized(year, month); this.setState({selecting: false}); }; componentDidUpdate(prevProps: CalendarProps) { if (prevProps.year !== this.props.year) { const yearStr = this.props.year.toString(); if (this.state.yearStr !== yearStr) { this.setState({ yearStr: yearStr, }); } } } render(): JSX.Element { return
{!this.state.selecting &&
{monthName(this.props.month)} {this.props.year}
} {this.state.selecting &&
}
; } }