You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

208 lines
5.7 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

const app = () => ({
futureTimes: [],
locationId: Alpine.$persist('11104'),
lastUpdated: Alpine.$persist(null),
userMinutes: 0,
now: new Date(),
debug: location.hash === '#debug',
geolocation: null,
async init() {
await this.refreshIfStale();
setInterval(() => {
this.now = new Date();
}, 500);
getUserLocation()
.then(loc => {
this.geolocation = {latitude: loc.latitude, longitude: loc.longitude};
this.refreshIfStale();
})
.catch(() => this.geolocation = null)
},
onHash() {
this.debug = location.hash === '#debug'
},
get userNow() {
if (!this.debug) {
return this.now;
}
const d = new Date();
d.setHours(0, this.userMinutes, 0, 0);
return d;
},
get userTime() {
return formatTime(this.userNow);
},
async refreshIfStale() {
const updatedAt = new Date(this.lastUpdated);
const now = new Date();
const elapsedSeconds = (now - updatedAt) / 1000;
if (this.geolocation !== null) {
this.futureTimes = await fetchJSON(`/api/v1/diyanet/prayertimes?latitude=${this.geolocation.latitude}&longitude=${this.geolocation.longitude}`);
} else {
this.futureTimes = await fetchJSON(`/api/v1/diyanet/prayertimes?location_id=${this.locationId}`);
}
this.lastUpdated = now.toISOString();
},
get todayTimes() {
if (this.futureTimes.length === 0) {
return null;
}
return new PrayerTimes(this.futureTimes[0], () => this.userNow);
},
translate(key, lang) {
return translations[key][lang] ?? key
}
});
class PrayerTimes {
static salaths = ['fajr', 'sunrise', 'dhuhr', 'asr', 'maghrib', 'isha'];
static translations = {
fajr: {tr: 'İmsak', de: 'Frühgebet', ar: 'صلاة الفجر'},
sunrise: {tr: 'Güneş', de: 'Sonnenaufgang', ar: 'الشروق'},
dhuhr: {tr: 'Öğle', de: 'Mittagsgebet', ar: 'صلاة الظهر'},
asr: {tr: 'İkindi', de: 'Nachmittagsgebet', ar: 'صلاة العصر'},
maghrib: {tr: 'Akşam', de: 'Abendgebet', ar: 'صلاة المغرب'},
isha: {tr: 'Yatsı', de: 'Nachtgebet', ar: 'صلاة العشاء'},
}
constructor({date, ...rest}, clock = () => new Date()) {
this.date = date;
this.clock = clock
this.salathTimes = rest
}
get times() {
const now = this.clock()
return PrayerTimes.salaths.map(k => {
// "2023-03-05T00:00:00Z"
const startsAt = new Date(this.date.replace('T00:00', `T${this.salathTimes[k]}`).replace(/Z$/, ''));
return {
salath: k,
name: lang => PrayerTimes.translations[k][lang] ?? '??',
startsAt,
timeLocal: this.salathTimes[k],
get untilSeconds() {
let untilSeconds = (startsAt - now) / 1000;
return now > startsAt ? 0 : untilSeconds;
},
get untilHuman() {
return formatDuration(this.untilSeconds)
},
}
})
}
get currentSalath() {
let current = this.times.filter(it => it.untilSeconds === 0).at(-1);
if (current === undefined) {
// we're in isha -> today's fajr is almost the same as tomorrows
const prevDay = new Date(this.date);
prevDay.setDate(prevDay.getDate() - 1);
current = new PrayerTimes({date: prevDay.toISOString(), ...this.salathTimes}, this.clock).times.at(-1);
}
return current
}
get nextSalath() {
let next = this.times
.filter(it => it.untilSeconds > 0)[0]
if (next === undefined) {
// we're in isha -> today's fajr is almost the same as tomorrows
const nextDay = new Date(this.date);
nextDay.setDate(nextDay.getDate() + 1);
next = new PrayerTimes({date: nextDay.toISOString(), ...this.salathTimes}, this.clock).times[0];
}
return {
...next,
}
}
}
/**
* @param {number} seconds
* @return {string}
* */
function formatDuration(seconds) {
const d = new Date(0, 0, 0, 0, 0, seconds);
return formatTime(d);
}
/**
* @param {Date} then
* @return {string}
* */
function formatTime(then) {
return new Intl.DateTimeFormat(navigator.language, {
hour: "numeric",
minute: "numeric",
second: "numeric"
}).format(then);
}
/**
* @param {Date} then
* @return {string}
* */
function formatDate(then) {
return new Intl.DateTimeFormat(navigator.language, {
year: "numeric",
month: "2-digit",
day: "2-digit"
}).format(then);
}
/**
* @param {Date} then
* @return {string}
* */
function formatDateHijri(then) {
return new Intl.DateTimeFormat("en-u-ca-islamic-umalqura-nu-latn", {
year: "numeric",
month: "long",
day: "numeric",
}).format(then);
}
/**
* @param {string} url
* @param {RequestInit} req
* */
async function fetchJSON(url, req = {}) {
const res = await fetch(url, {
...req,
})
return res.json()
}
/**
* @return {Promise<GeolocationCoordinates>}
* */
function getUserLocation() {
return new Promise((resolve, reject) => {
if (!navigator.geolocation) {
reject("Geolocation is not supported by this browser.");
return;
}
navigator.geolocation.getCurrentPosition(
(position) => resolve(position.coords),
(error) => reject(error.message)
);
});
}