feat: Add Diyanet API provider

feat: Add UI
This commit is contained in:
abdus committed 2023-03-05 14:41:38 +01:00
1 parent d4ff42387f
commit 93af84cef8
17 files changed
+642 -43

No files matched your search

+1
View File
@@ -0,0 +1 @@
(()=>{function d(t){let e=()=>{let n,l=localStorage;return t.interceptor((r,i,s,a,m)=>{let o=n||`_x_${a}`,u=f(o,l)?g(o,l):r;return s(u),t.effect(()=>{let c=i();p(o,c,l),s(c)}),u},r=>{r.as=i=>(n=i,r),r.using=i=>(l=i,r)})};Object.defineProperty(t,"$persist",{get:()=>e()}),t.magic("persist",e),t.persist=(n,{get:l,set:r},i=localStorage)=>{let s=f(n,i)?g(n,i):l();r(s),t.effect(()=>{let a=l();p(n,a,i),r(a)})}}function f(t,e){return e.getItem(t)!==null}function g(t,e){return JSON.parse(e.getItem(t,e))}function p(t,e,n){n.setItem(t,JSON.stringify(e))}document.addEventListener("alpine:init",()=>{window.Alpine.plugin(d)});})();
+5
View File
@@ -0,0 +1,5 @@
(()=>{var Ye=!1,Ze=!1,V=[];function Ft(e){mn(e)}function mn(e){V.includes(e)||V.push(e),hn()}function xe(e){let t=V.indexOf(e);t!==-1&&V.splice(t,1)}function hn(){!Ze&&!Ye&&(Ye=!0,queueMicrotask(_n))}function _n(){Ye=!1,Ze=!0;for(let e=0;e<V.length;e++)V[e]();V.length=0,Ze=!1}var T,I,$,Qe,Xe=!0;function Bt(e){Xe=!1,e(),Xe=!0}function Kt(e){T=e.reactive,$=e.release,I=t=>e.effect(t,{scheduler:r=>{Xe?Ft(r):r()}}),Qe=e.raw}function et(e){I=e}function zt(e){let t=()=>{};return[n=>{let i=I(n);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),$(i))},i},()=>{t()}]}var Vt=[],Ht=[],qt=[];function Ut(e){qt.push(e)}function ye(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,Ht.push(t))}function Wt(e){Vt.push(e)}function Gt(e,t,r){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(r)}function tt(e,t){!e._x_attributeCleanups||Object.entries(e._x_attributeCleanups).forEach(([r,n])=>{(t===void 0||t.includes(r))&&(n.forEach(i=>i()),delete e._x_attributeCleanups[r])})}var nt=new MutationObserver(rt),it=!1;function ie(){nt.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),it=!0}function ot(){gn(),nt.disconnect(),it=!1}var oe=[],st=!1;function gn(){oe=oe.concat(nt.takeRecords()),oe.length&&!st&&(st=!0,queueMicrotask(()=>{xn(),st=!1}))}function xn(){rt(oe),oe.length=0}function h(e){if(!it)return e();ot();let t=e();return ie(),t}var at=!1,be=[];function Jt(){at=!0}function Yt(){at=!1,rt(be),be=[]}function rt(e){if(at){be=be.concat(e);return}let t=[],r=[],n=new Map,i=new Map;for(let o=0;o<e.length;o++)if(!e[o].target._x_ignoreMutationObserver&&(e[o].type==="childList"&&(e[o].addedNodes.forEach(s=>s.nodeType===1&&t.push(s)),e[o].removedNodes.forEach(s=>s.nodeType===1&&r.push(s))),e[o].type==="attributes")){let s=e[o].target,a=e[o].attributeName,c=e[o].oldValue,l=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},u=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&c===null?l():s.hasAttribute(a)?(u(),l()):u()}i.forEach((o,s)=>{tt(s,o)}),n.forEach((o,s)=>{Vt.forEach(a=>a(s,o))});for(let o of r)if(!t.includes(o)&&(Ht.forEach(s=>s(o)),o._x_cleanups))for(;o._x_cleanups.length;)o._x_cleanups.pop()();t.forEach(o=>{o._x_ignoreSelf=!0,o._x_ignore=!0});for(let o of t)r.includes(o)||!o.isConnected||(delete o._x_ignoreSelf,delete o._x_ignore,qt.forEach(s=>s(o)),o._x_ignore=!0,o._x_ignoreSelf=!0);t.forEach(o=>{delete o._x_ignoreSelf,delete o._x_ignore}),t=null,r=null,n=null,i=null}function ve(e){return j(L(e))}function M(e,t,r){return e._x_dataStack=[t,...L(r||e)],()=>{e._x_dataStack=e._x_dataStack.filter(n=>n!==t)}}function ct(e,t){let r=e._x_dataStack[0];Object.entries(t).forEach(([n,i])=>{r[n]=i})}function L(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?L(e.host):e.parentNode?L(e.parentNode):[]}function j(e){let t=new Proxy({},{ownKeys:()=>Array.from(new Set(e.flatMap(r=>Object.keys(r)))),has:(r,n)=>e.some(i=>i.hasOwnProperty(n)),get:(r,n)=>(e.find(i=>{if(i.hasOwnProperty(n)){let o=Object.getOwnPropertyDescriptor(i,n);if(o.get&&o.get._x_alreadyBound||o.set&&o.set._x_alreadyBound)return!0;if((o.get||o.set)&&o.enumerable){let s=o.get,a=o.set,c=o;s=s&&s.bind(t),a=a&&a.bind(t),s&&(s._x_alreadyBound=!0),a&&(a._x_alreadyBound=!0),Object.defineProperty(i,n,{...c,get:s,set:a})}return!0}return!1})||{})[n],set:(r,n,i)=>{let o=e.find(s=>s.hasOwnProperty(n));return o?o[n]=i:e[e.length-1][n]=i,!0}});return t}function we(e){let t=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach(([o,{value:s,enumerable:a}])=>{if(a===!1||s===void 0)return;let c=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(e,c,o):t(s)&&s!==n&&!(s instanceof Element)&&r(s,c)})};return r(e)}function Ee(e,t=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return e(this.initialValue,()=>yn(n,i),s=>lt(n,i,s),i,o)}};return t(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let c=n.initialize(o,s,a);return r.initialValue=c,i(o,s,a)}}else r.initialValue=n;return r}}function yn(e,t){return t.split(".").reduce((r,n)=>r[n],e)}function lt(e,t,r){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=r;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),lt(e[t[0]],t.slice(1),r)}}var Zt={};function y(e,t){Zt[e]=t}function se(e,t){return Object.entries(Zt).forEach(([r,n])=>{Object.defineProperty(e,`$${r}`,{get(){let[i,o]=ut(t);return i={interceptor:Ee,...i},ye(t,o),n(t,i)},enumerable:!1})}),e}function Qt(e,t,r,...n){try{return r(...n)}catch(i){Z(i,e,t)}}function Z(e,t,r=void 0){Object.assign(e,{el:t,expression:r}),console.warn(`Alpine Expression E Line truncated
${r?'Expression: "'+r+`"
`:""}`,t),setTimeout(()=>{throw e},0)}var Se=!0;function Xt(e){let t=Se;Se=!1,e(),Se=t}function P(e,t,r={}){let n;return x(e,t)(i=>n=i,r),n}function x(...e){return er(...e)}var er=ft;function tr(e){er=e}function ft(e,t){let r={};se(r,e);let n=[r,...L(e)];if(typeof t=="function")return bn(n,t);let i=vn(n,t,e);return Qt.bind(null,e,t,i)}function bn(e,t){return(r=()=>{},{scope:n={},params:i=[]}={})=>{let o=t.apply(j([n,...e]),i);Ae(r,o)}}var dt={};function wn(e,t){if(dt[e])return dt[e];let r=Object.getPrototypeOf(async function(){}).constructor,n=/^[\n\s]*if.*\(.*\)/.test(e)||/^(let|const)\s/.test(e)?`(async()=>{ ${e} })()`:e,o=(()=>{try{return new r(["__self","scope"],`with (scope) { __self.result = ${n} }; __self.finished = true; return __self.result;`)}catch(s){return Z(s,t,e),Promise.resolve()}})();return dt[e]=o,o}function vn(e,t,r){let n=wn(t,r);return(i=()=>{},{scope:o={},params:s=[]}={})=>{n.result=void 0,n.finished=!1;let a=j([o,...e]);if(typeof n=="function"){let c=n(n,a).catch(l=>Z(l,r,t));n.finished?(Ae(i,n.result,a,s,r),n.result=void 0):c.then(l=>{Ae(i,l,a,s,r)}).catch(l=>Z(l,r,t)).finally(()=>n.result=void 0)}}}function Ae(e,t,r,n,i){if(Se&&typeof t=="function"){let o=t.apply(r,n);o instanceof Promise?o.then(s=>Ae(e,s,r,n)).catch(s=>Z(s,i,t)):e(o)}else typeof t=="object"&&t instanceof Promise?t.then(o=>e(o)):e(t)}var pt="x-";function S(e=""){return pt+e}function rr(e){pt=e}var mt={};function p(e,t){return mt[e]=t,{before(r){if(!mt[r]){console.warn("Cannot find directive `${directive}`. `${name}` will use the default order of execution");return}let n=H.indexOf(r)??H.indexOf("DEFAULT");n>=0&&H.splice(n,0,e)}}}function ae(e,t,r){if(t=Array.from(t),e._x_virtualDirectives){let o=Object.entries(e._x_virtualDirectives).map(([a,c])=>({name:a,value:c})),s=ht(o);o=o.map(a=>s.find(c=>c.name===a.name)?{name:`x-bind:${a.name}`,value:`"${a.value}"`}:a),t=t.concat(o)}let n={};return t.map(nr((o,s)=>n[o]=s)).filter(ir).map(Sn(n,r)).sort(An).map(o=>En(e,o))}function ht(e){return Array.from(e).map(nr()).filter(t=>!ir(t))}var _t=!1,ce=new Map,or=Symbol();function sr(e){_t=!0;let t=Symbol();or=t,ce.set(t,[]);let r=()=>{for(;ce.get(t).length;)ce.get(t).shift()();ce.delete(t)},n=()=>{_t=!1,r()};e(r),n()}function ut(e){let t=[],r=a=>t.push(a),[n,i]=zt(e);return t.push(i),[{Alpine:F,effect:n,cleanup:r,evaluateLater:x.bind(x,e),evaluate:P.bind(P,e)},()=>t.forEach(a=>a())]}function En(e,t){let r=()=>{},n=mt[t.type]||r,[i,o]=ut(e);Gt(e,t.original,o);let s=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,i),n=n.bind(n,e,t,i),_t?ce.get(or).push(n):n())};return s.runCleanups=o,s}var Oe=(e,t)=>({name:r,value:n})=>(r.startsWith(e)&&(r=r.replace(e,t)),{name:r,value:n}),Te=e=>e;function nr(e=()=>{}){return({name:t,value:r})=>{let{name:n,value:i}=ar.reduce((o,s)=>s(o),{name:t,value:r});return n!==t&&e(n,t),{name:n,value:i}}}var ar=[];function Q(e){ar.push(e)}function ir({name:e}){return cr().test(e)}var cr=()=>new RegExp(`^${pt}([^:^.]+)\\b`);function Sn(e,t){return({name:r,value:n})=>{let i=r.match(cr()),o=r.match(/:([a-zA-Z0-9\-:]+)/),s=r.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=t||e[r]||r;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:s.map(c=>c.replace(".","")),expression:n,original:a}}}var gt="DEFAULT",H=["ignore","ref","data","id","radio","tabs","switch","disclosure","menu","listbox","combobox","bind","init","for","mask","model","modelable","transition","show","if",gt,"teleport"];function An(e,t){let r=H.indexOf(e.type)===-1?gt:e.type,n=H.indexOf(t.type)===-1?gt:t.type;return H.indexOf(r)-H.indexOf(n)}function q(e,t,r={}){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}function A(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>A(i,t));return}let r=!1;if(t(e,()=>r=!0),r)return;let n=e.firstElementChild;for(;n;)A(n,t,!1),n=n.nextElementSibling}function C(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}function ur(){document.body||C("Unable to initialize. Trying to load Alpine before `<body>` is available. Did you forget to add `defer` in Alpine's `<script>` tag?"),q(document,"alpine:init"),q(document,"alpine:initializing"),ie(),Ut(t=>E(t,A)),ye(t=>xt(t)),Wt((t,r)=>{ae(t,r).forEach(n=>n())});let e=t=>!U(t.parentElement,!0);Array.from(document.querySelectorAll(lr())).filter(e).forEach(t=>{E(t)}),q(document,"alpine:initialized")}var yt=[],fr=[];function dr(){return yt.map(e=>e())}function lr(){return yt.concat(fr).map(e=>e())}function Ce(e){yt.push(e)}function Re(e){fr.push(e)}function U(e,t=!1){return X(e,r=>{if((t?lr():dr()).some(i=>r.matches(i)))return!0})}function X(e,t){if(!!e){if(t(e))return e;if(e._x_teleportBack&&(e=e._x_teleportBack),!!e.parentElement)return X(e.parentElement,t)}}function pr(e){return dr().some(t=>e.matches(t))}var mr=[];function hr(e){mr.push(e)}function E(e,t=A,r=()=>{}){sr(()=>{t(e,(n,i)=>{r(n,i),mr.forEach(o=>o(n,i)),ae(n,n.attributes).forEach(o=>o()),n._x_ignore&&i()})})}function xt(e){A(e,t=>tt(t))}vLine truncated
+98
View File
@@ -0,0 +1,98 @@
<!doctype html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<meta name='viewport'
content='width=device-width, initial-scale=1.0'>
<title>Document</title>
<script defer
src='main.js'></script>
<script defer
src='alpine.persist@3.x.x.min.js'></script>
<script defer
src='alpine@3.11.1.min.js'></script>
<link rel='preconnect'
href='https://fonts.googleapis.com'>
<link rel='preconnect'
href='https://fonts.gstatic.com'
crossorigin>
<link href='https://fonts.googleapis.com/css2?family=Inter:wght@400..900&family=Noto+Sans+Arabic:wght@400..700&display=swap'
rel='stylesheet'>
<link rel='stylesheet'
href='style.css'>
</head>
<body>
<div x-data='app()'
@hashchange.window='onHash'>
<template x-if='debug'>
<div>
<label>clock: <input type='range'
step='1'
min='0'
max='1439'
x-model='userMinutes'> <span x-text='userTime'></span></label>
</div>
</template>
<input type='text'
x-model='locationId'>
<template x-if='todayTimes'>
<div class='current-salath text--center'>
<p>
<time :datetime='userNow'
x-text='formatDate(userNow)'></time>
</p>
<p>
<span class='salath-name'
lang='tr'
x-text='todayTimes.currentSalath.name("tr")'></span>
<span class='salath-name'
lang='de'
x-text='todayTimes.currentSalath.name("de")'></span>
<span class='salath-name text--arabic'
lang='ar'
x-text='todayTimes.currentSalath.name("ar")'></span>
</p>
<p>
<time :datetime='todayTimes.nextSalath.startsAt'
class='text--numeric text--time'
x-text='todayTimes.nextSalath.untilHuman'></time>
</p>
</div>
</template>
<template x-if='todayTimes'>
<div class='table-wrapper'>
<table class='salath-table'>
<tbody>
<template x-for='it in todayTimes.times'>
<tr class='salath'
:class='{current: it.salath === todayTimes.currentSalath.salath}'>
<td class='cell-names'>
<span class='salath-name'
x-text='it.name("tr")'></span>
<span class='salath-name'
x-text='it.name("de")'></span>
<span class='salath-name text--arabic'
x-text='it.name("ar")'></span>
</td>
<td class='cell-times'>
<span class='salath-time text--time text--numeric'
x-text='it.timeLocal'></span>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</template>
<div class='text--center text--numeric'>
<p><span class='clock'
x-text='formatTime(now)'></span></p>
</div>
</div>
</body>
</html>
+208
View File
@@ -0,0 +1,208 @@
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)
);
});
}
+88
View File
@@ -0,0 +1,88 @@
*, *:before, *:after {
box-sizing: border-box;
}
body {
line-height: 1.6;
font-family: 'Inter', sans-serif;
}
p {
margin: 0;
}
p + p {
margin-top: 1rem;
}
.text--numeric {
font-variant: tabular-nums;
letter-spacing: -0.03em;
}
.text--center {
text-align: center;
}
.text--arabic {
font-family: 'Noto Sans Arabic', Inter, sans-serif;
line-height: 1;
}
.text--time {
font-size: 1.5em;
}
.table-wrapper {
padding: 0 2rem;
max-width: 66%;
margin: auto;
}
.salath-table {
font-size: 2rem;
font-weight: 600;
width: 100%;
vertical-align: middle;
}
td {
padding: 0.25rem 0.5rem;
}
.cell-names {
white-space: nowrap;
}
.cell-times {
white-space: nowrap;
text-align: right;
}
.salath-name {
}
.current {
color: tomato;
}
.salath-name + .salath-name:before {
content: '/';
opacity: 0.25;
margin: 0 0.5em;
}
.salath-time {
font-weight: 700;
}
.clock {
font-size: 4rem;
font-weight: bold;
}
.current-salath {
font-weight: bold;
font-size: 1.5rem;
}