Factor out <GregorianJumper>

This commit is contained in:
Quantum 2023-07-15 15:37:25 -04:00
parent f67bd1bbef
commit 9a69b0ba55
6 changed files with 77 additions and 117 deletions

View file

@ -0,0 +1,43 @@
import {DateJumperProps} from './base';
import React from 'react';
import {gregorianJDN, jdnGregorian} from '../gregorian';
export default function GregorianJumper({minJDN, maxJDN, todayJDN, onJump}: DateJumperProps): JSX.Element {
const {todayYear, todayMonth, todayDay, startYear, endYear} = React.useMemo(() => {
const [todayYear, todayMonth, todayDay] = jdnGregorian(todayJDN);
const [startYear] = jdnGregorian(minJDN);
const [endYear] = jdnGregorian(maxJDN);
return {todayYear, todayMonth, todayDay, startYear, endYear};
}, [minJDN, maxJDN, todayJDN]);
const [year, setYear] = React.useState(todayYear.toString());
const [month, setMonth] = React.useState(todayMonth.toString());
const [day, setDay] = React.useState(todayDay.toString());
const validYear = /^-?\d+$/.test(year) && startYear <= +year && +year <= endYear;
const validMonth = /^\d+$/.test(month) && 1 <= +month && +month <= 12;
const validDay = /^\d+$/.test(day) && 1 <= +day && +day <= 31;
function goToGregorian(event: React.FormEvent) {
event.preventDefault();
if (!validYear || !validMonth || !validDay)
return;
onJump(gregorianJDN(+year, +month, +day));
}
return <form className="input-group" onSubmit={goToGregorian}>
<span className="input-group-text">Gregorian<span className="hide-small">&nbsp;Date</span></span>
<input type="number" className={`form-control go-year ${validYear ? '' : 'is-invalid'}`}
onChange={e => setYear(e.target.value)} value={year}
min={startYear} max={endYear}/>
<input type="number" className={`form-control go-month ${validMonth ? '' : 'is-invalid'}`}
onChange={e => setMonth(e.target.value)} value={month}
min={1} max={12}/>
<input type="number" className={`form-control go-day ${validDay ? '' : 'is-invalid'}`}
onChange={e => setDay(e.target.value)} value={day}
min={1} max={31}/>
<button type="submit" className="form-control btn btn-primary go-button">Go</button>
</form>;
}

View file

@ -0,0 +1,6 @@
export type DateJumperProps = {
minJDN: number;
maxJDN: number;
todayJDN: number;
onJump: (jdn: number) => void;
};

View file

@ -0,0 +1,3 @@
import GregorianJumper from './GregorianJumper';
export {GregorianJumper};

View file

@ -1,6 +1,5 @@
import data from './cal.json'; import data from './cal.json';
import ruralName from './rural-days.json'; import ruralName from './rural-days.json';
import {jdnGregorian} from '../gregorian';
// Month 13 is for the complementary days // Month 13 is for the complementary days
export type FrenchMonth = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13; export type FrenchMonth = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13;
@ -145,9 +144,6 @@ export function dateRuralName(month: FrenchMonth, day: FrenchDay): { name: strin
return {name, title}; return {name, title};
} }
export const startGregorian = jdnGregorian(frStartJD);
export const endGregorian = jdnGregorian(frEndJD);
export function frDateFormat({year, month, day}: { year: number, month: FrenchMonth, day: FrenchDay }): string { export function frDateFormat({year, month, day}: { year: number, month: FrenchMonth, day: FrenchDay }): string {
return `${dateName(month, day)} ${year}`; return `${dateName(month, day)} ${year}`;
} }

View file

@ -1,16 +1,9 @@
import React, {FormEvent} from 'react'; import React from 'react';
import {Calendar} from './Calendar'; import {Calendar} from './Calendar';
import { import {FrenchMonth, frEndJD, frStartJD, frSupportedYear, jdnFrench} from '@common/french';
endGregorian, import {dateJDN} from '@common/gregorian';
frEndJD,
FrenchMonth,
frSupportedYear,
jdnFrench,
startGregorian,
frStartJD,
} from '@common/french';
import {dateJDN, gregorianJDN} from '@common/gregorian';
import {TimeOfDay} from './TimeOfDay'; import {TimeOfDay} from './TimeOfDay';
import {GregorianJumper} from '@common/dateJump';
type YearMonth = { type YearMonth = {
year: number; year: number;
@ -31,9 +24,6 @@ function parseURL(): YearMonth | null {
type AppState = YearMonth & { type AppState = YearMonth & {
todayJDN: number, todayJDN: number,
goYear: string,
goMonth: string,
goDay: string,
}; };
class App extends React.Component<{}, AppState> { class App extends React.Component<{}, AppState> {
@ -41,16 +31,12 @@ class App extends React.Component<{}, AppState> {
constructor(props: {}) { constructor(props: {}) {
super(props); super(props);
const today = new Date(); const todayJDN = dateJDN(new Date());
const todayJDN = dateJDN(today);
const {year, month} = jdnFrench(todayJDN); const {year, month} = jdnFrench(todayJDN);
this.state = { this.state = {
...(parseURL() || {year, month}), ...(parseURL() || {year, month}),
todayJDN, todayJDN,
goYear: today.getFullYear().toString(),
goMonth: (today.getMonth() + 1).toString(),
goDay: today.getDate().toString(),
}; };
this.updateStateFromURL = this.updateStateFromURL.bind(this); this.updateStateFromURL = this.updateStateFromURL.bind(this);
} }
@ -75,38 +61,10 @@ class App extends React.Component<{}, AppState> {
} }
} }
changeField(field: keyof AppState, event: any) {
this.setState({[field]: event.target.value});
}
validYear() {
return /^-?\d+$/.test(this.state.goYear) && startGregorian[0] <= +this.state.goYear &&
+this.state.goYear <= endGregorian[0];
}
validMonth() {
return /^\d+$/.test(this.state.goMonth) && 1 <= +this.state.goMonth && +this.state.goMonth <= 12;
}
validDay() {
return /^\d+$/.test(this.state.goDay) && 1 <= +this.state.goDay && +this.state.goDay <= 31;
}
goToGregorian(event: FormEvent) {
event.preventDefault();
if (!this.validYear() || !this.validMonth() || !this.validDay())
return;
const jdn = gregorianJDN(+this.state.goYear, +this.state.goMonth, +this.state.goDay);
const {year, month} = jdnFrench(Math.min(Math.max(frStartJD, jdn), frEndJD));
this.setState({year, month});
}
setState(state: any, callback?: () => void) { setState(state: any, callback?: () => void) {
super.setState(state, () => { super.setState(state, () => {
this.updateURL(); this.updateURL();
callback && callback(); callback?.();
}); });
} }
@ -114,6 +72,11 @@ class App extends React.Component<{}, AppState> {
this.setState({todayJDN}); this.setState({todayJDN});
}; };
goToJDN = (jdn: number) => {
const {year, month} = jdnFrench(Math.min(Math.max(frStartJD, jdn), frEndJD));
this.setState({year, month});
};
render() { render() {
return <> return <>
<Calendar <Calendar
@ -126,19 +89,8 @@ class App extends React.Component<{}, AppState> {
<div className="navigate"> <div className="navigate">
<h4>Go to a date</h4> <h4>Go to a date</h4>
<form className="input-group" onSubmit={this.goToGregorian.bind(this)}> <GregorianJumper minJDN={frStartJD} maxJDN={frEndJD} todayJDN={this.state.todayJDN}
<span className="input-group-text">Gregorian<span className="hide-small">&nbsp;Date</span></span> onJump={this.goToJDN}/>
<input type="number" className={`form-control go-year ${this.validYear() ? '' : 'is-invalid'}`}
onChange={this.changeField.bind(this, 'goYear')} value={this.state.goYear}
min={startGregorian[0]} max={endGregorian[0]}/>
<input type="number" className={`form-control go-month ${this.validMonth() ? '' : 'is-invalid'}`}
onChange={this.changeField.bind(this, 'goMonth')} value={this.state.goMonth}
min={1} max={12}/>
<input type="number" className={`form-control go-day ${this.validDay() ? '' : 'is-invalid'}`}
onChange={this.changeField.bind(this, 'goDay')} value={this.state.goDay}
min={1} max={31}/>
<button type="submit" className="form-control btn btn-primary go-button">Go</button>
</form>
</div> </div>
</>; </>;
} }

View file

@ -1,13 +1,13 @@
import React, {FormEvent} from 'react'; import React from 'react';
import {Calendar} from './Calendar'; import {Calendar} from './Calendar';
import {frEndJD, frStartJD} from '@common/french';
import {dateJDN, gregorianJDN, JulianMonth} from '@common/gregorian'; import {dateJDN, gregorianJDN, JulianMonth} from '@common/gregorian';
import {DayChanger} from '@common/DayChanger'; import {DayChanger} from '@common/DayChanger';
import {jdnJulian} from '@common/julian'; import {jdnJulian} from '@common/julian';
import {GregorianJumper} from '@common/dateJump';
// Not real limitations other than JS number precision. // Not real limitations other than JS number precision.
const START_YEAR = -10_000_000_000_000; const START_JDN = gregorianJDN(-10_000_000_000_000, 1, 1);
const END_YEAR = 10_000_000_000_000; const END_JDN = gregorianJDN(10_000_000_000_000, 12, 31);
type YearMonth = { type YearMonth = {
year: number; year: number;
@ -28,9 +28,6 @@ function parseURL(): YearMonth | null {
type AppState = YearMonth & { type AppState = YearMonth & {
todayJDN: number, todayJDN: number,
goYear: string,
goMonth: string,
goDay: string,
}; };
class App extends React.Component<{}, AppState> { class App extends React.Component<{}, AppState> {
@ -38,16 +35,12 @@ class App extends React.Component<{}, AppState> {
constructor(props: {}) { constructor(props: {}) {
super(props); super(props);
const today = new Date(); const todayJDN = dateJDN(new Date());
const todayJDN = dateJDN(today);
const [year, month] = jdnJulian(todayJDN); const [year, month] = jdnJulian(todayJDN);
this.state = { this.state = {
...(parseURL() || {year, month}), ...(parseURL() || {year, month}),
todayJDN, todayJDN,
goYear: today.getFullYear().toString(),
goMonth: (today.getMonth() + 1).toString(),
goDay: today.getDate().toString(),
}; };
this.updateStateFromURL = this.updateStateFromURL.bind(this); this.updateStateFromURL = this.updateStateFromURL.bind(this);
} }
@ -72,33 +65,6 @@ class App extends React.Component<{}, AppState> {
} }
} }
changeField(field: keyof AppState, event: any) {
this.setState({[field]: event.target.value});
}
validYear() {
return /^-?\d+$/.test(this.state.goYear) && START_YEAR <= +this.state.goYear && +this.state.goYear <= END_YEAR;
}
validMonth() {
return /^\d+$/.test(this.state.goMonth) && 1 <= +this.state.goMonth && +this.state.goMonth <= 12;
}
validDay() {
return /^\d+$/.test(this.state.goDay) && 1 <= +this.state.goDay && +this.state.goDay <= 31;
}
goToGregorian(event: FormEvent) {
event.preventDefault();
if (!this.validYear() || !this.validMonth() || !this.validDay())
return;
const jdn = gregorianJDN(+this.state.goYear, +this.state.goMonth, +this.state.goDay);
const [year, month] = jdnJulian(Math.min(Math.max(frStartJD, jdn), frEndJD));
this.setState({year, month});
}
setState(state: any, callback?: () => void) { setState(state: any, callback?: () => void) {
super.setState(state, () => { super.setState(state, () => {
this.updateURL(); this.updateURL();
@ -110,6 +76,11 @@ class App extends React.Component<{}, AppState> {
this.setState({todayJDN}); this.setState({todayJDN});
}; };
goToJDN = (jdn: number) => {
const [year, month] = jdnJulian(jdn);
this.setState({year, month});
};
render() { render() {
return <> return <>
<Calendar <Calendar
@ -122,19 +93,8 @@ class App extends React.Component<{}, AppState> {
<div className="navigate"> <div className="navigate">
<h4>Go to a date</h4> <h4>Go to a date</h4>
<form className="input-group" onSubmit={this.goToGregorian.bind(this)}> <GregorianJumper minJDN={START_JDN} maxJDN={END_JDN} todayJDN={this.state.todayJDN}
<span className="input-group-text">Gregorian<span className="hide-small">&nbsp;Date</span></span> onJump={this.goToJDN}/>
<input type="number" className={`form-control go-year ${this.validYear() ? '' : 'is-invalid'}`}
onChange={this.changeField.bind(this, 'goYear')} value={this.state.goYear}
min={START_YEAR} max={END_YEAR}/>
<input type="number" className={`form-control go-month ${this.validMonth() ? '' : 'is-invalid'}`}
onChange={this.changeField.bind(this, 'goMonth')} value={this.state.goMonth}
min={1} max={12}/>
<input type="number" className={`form-control go-day ${this.validDay() ? '' : 'is-invalid'}`}
onChange={this.changeField.bind(this, 'goDay')} value={this.state.goDay}
min={1} max={31}/>
<button type="submit" className="form-control btn btn-primary go-button">Go</button>
</form>
</div> </div>
</>; </>;
} }