|
|
const app = () => ({
|
|
|
futureTimes: [],
|
|
|
|
|
|
locationId: Alpine.$persist('11002'),
|
|
|
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) {
|
|
|
const response = await fetchJSON(`/api/v1/diyanet/prayertimes?latitude=${this.geolocation.latitude}&longitude=${this.geolocation.longitude}`);
|
|
|
this.futureTimes = response.prayertimes ?? [];
|
|
|
} else {
|
|
|
const response = await fetchJSON(`/api/v1/diyanet/prayertimes?location_id=${this.locationId}`);
|
|
|
this.futureTimes = response.prayertimes ?? [];
|
|
|
}
|
|
|
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)
|
|
|
);
|
|
|
});
|
|
|
}
|