import { sendToMain } from "../../renderer/sendToMain.js";
import { AbstractStateSubscriberWebComponent } from "../util/AbstractStateSubscriberWebComponent.js";
import { registerElement } from "../../renderer/registerElement.js";
import { registerShortcut, shortcutString } from "../../renderer/registerShortcut.js";
import type { IconWebComponent } from "../util/IconWebComponent.js";
import type { AeroflyFlightFormatterSunPosition } from "../../../core/formatter/AeroflyFlightFormatter.js";
export type TimeAndDateWebComponentState = {
utcDate: string; // YYYY-MM-DD
utcTime: string; // HH:mm
};
export class TimeAndDateWebComponent extends AbstractStateSubscriberWebComponent {
private isInitialized = false;
private shortcut: (() => void) | undefined = undefined;
private readonly shortcutKey = "n";
private elements!: {
dateUtc: HTMLInputElement;
timeUtc: HTMLInputElement;
dateLocal: HTMLInputElement;
timeLocal: HTMLInputElement;
timeZoneLocal: HTMLElement;
nowButton: HTMLButtonElement;
sunPosition: IconWebComponent;
};
private initialize() {
this.setAttribute("aria-role", "region");
this.innerHTML = `\
Time & date
`;
this.elements = {
dateUtc: this.querySelector("#date-utc") as HTMLInputElement,
timeUtc: this.querySelector("#time-utc") as HTMLInputElement,
dateLocal: this.querySelector("#date-local") as HTMLInputElement,
timeLocal: this.querySelector("#time-local") as HTMLInputElement,
timeZoneLocal: this.querySelector("#timezone-local") as HTMLElement,
nowButton: this.querySelector("#synchronize-time") as HTMLButtonElement,
sunPosition: this.querySelector("#sun-position") as IconWebComponent,
};
}
get state(): TimeAndDateWebComponentState {
return {
utcDate: this.elements.dateUtc.value,
utcTime: this.elements.timeUtc.value,
};
}
get utcDate() {
return new Date(this.elements.dateUtc.value + "T" + this.elements.timeUtc.value + "Z");
}
connectedCallback() {
if (!this.isInitialized) {
this.initialize();
this.isInitialized = true;
}
[this.elements.dateUtc, this.elements.timeUtc].forEach((e) =>
e.addEventListener("input", this.setLocalFromUtc),
);
[this.elements.dateLocal, this.elements.timeLocal].forEach((e) =>
e.addEventListener("input", this.setUtcFromLocal),
);
this.elements.nowButton.addEventListener("click", this.setNow);
this.subscribeToStateUpdates((state) => {
this.elements.dateUtc.value = state.dateTime.utc.date;
this.elements.timeUtc.value = state.dateTime.utc.time;
this.elements.timeZoneLocal.dataset.value = state.dateTime.local.timeZoneOffset_h.toString();
this.elements.timeZoneLocal.textContent =
(state.dateTime.local.timeZoneOffset_h >= 0 ? "+" : "") + state.dateTime.local.timeZoneOffset_h;
this.elements.dateLocal.value = state.dateTime.local.date;
this.elements.timeLocal.value = state.dateTime.local.time;
// Sun Position
this.elements.sunPosition.title = state.dateTime.local.sunPosition;
this.elements.sunPosition.icon = this.convertSunPositionToIcon(state.dateTime.local.sunPosition);
this.elements.sunPosition.classList.toggle("has-warning", state.dateTime.local.sunPosition === "Twilight");
this.elements.sunPosition.classList.toggle("has-info", state.dateTime.local.sunPosition === "Night");
this.checkWarning();
});
this.addEventListener("input", this.handleChange);
this.shortcut = registerShortcut(this.shortcutKey, this.setNow);
}
convertSunPositionToIcon(sunPosition: AeroflyFlightFormatterSunPosition): string {
switch (sunPosition) {
case "Day":
return "sun";
case "Twilight":
return "sunset";
case "Night":
return "moon";
}
}
disconnectedCallback(): void {
super.disconnectedCallback();
[this.elements.dateUtc, this.elements.timeUtc].forEach((e) =>
e.removeEventListener("input", this.setLocalFromUtc),
);
[this.elements.dateLocal, this.elements.timeLocal].forEach((e) =>
e.removeEventListener("input", this.setUtcFromLocal),
);
this.elements.nowButton.removeEventListener("click", this.setNow);
this.removeEventListener("input", this.handleChange);
if (this.shortcut) {
this.shortcut();
}
}
private checkWarning() {
const now = new Date();
const hasWarning = this.utcDate > now;
const warningClass = "input-warning";
this.elements.dateUtc.classList.toggle(warningClass, hasWarning);
this.elements.timeUtc.classList.toggle(warningClass, hasWarning);
this.elements.dateLocal.classList.toggle(warningClass, hasWarning);
this.elements.timeLocal.classList.toggle(warningClass, hasWarning);
}
private handleChange = () => {
this.checkWarning();
sendToMain("date-time:set", this.state);
};
private setLocalFromUtc = () => {
const d = this.utcDate;
d.setUTCHours(d.getUTCHours() + Number(this.elements.timeZoneLocal.dataset.value ?? "0"));
this.elements.timeLocal.value = this.pad(d.getUTCHours()) + ":" + this.pad(d.getUTCMinutes());
this.elements.dateLocal.value =
d.getFullYear().toString() + "-" + this.pad(d.getUTCMonth() + 1) + "-" + this.pad(d.getUTCDate());
};
private setUtcFromLocal = () => {
const d = new Date(this.elements.dateLocal.value + "T" + this.elements.timeLocal.value + "Z");
d.setUTCHours(d.getUTCHours() - Number(this.elements.timeZoneLocal.dataset.value ?? "0"));
this.elements.timeUtc.value = this.pad(d.getUTCHours()) + ":" + this.pad(d.getUTCMinutes());
this.elements.dateUtc.value =
d.getFullYear().toString() + "-" + this.pad(d.getUTCMonth() + 1) + "-" + this.pad(d.getUTCDate());
};
private setNow = () => {
const now = new Date();
this.elements.dateUtc.value =
now.getUTCFullYear().toString() + "-" + this.pad(now.getUTCMonth() + 1) + "-" + this.pad(now.getUTCDate());
this.elements.timeUtc.value = this.pad(now.getUTCHours()) + ":" + this.pad(now.getUTCMinutes());
this.setLocalFromUtc();
this.handleChange();
};
private pad(t: string | number) {
return String(t).padStart(2, "0");
}
static registerElement() {
registerElement("startgeraet-time-and-date", TimeAndDateWebComponent);
}
}