0'
@click.outside='searchOpen = false'>
+
+
+
+
- Searching...
+
+ x-text='t(searchError)'>
- No results
+
diff --git a/templates/static/main.js b/templates/static/main.js
index c9d6bbc..899984e 100644
--- a/templates/static/main.js
+++ b/templates/static/main.js
@@ -12,6 +12,13 @@ const app = () => ({
userMinutes: 0,
now: new Date(),
debug: hasDebugHashFlag(),
+ locating: false,
+ locateError: "",
+
+ t(key) {
+ const lang = this.preferredSalathLangs[0];
+ return UI_STRINGS[key]?.[lang] ?? UI_STRINGS[key]?.en ?? key;
+ },
async init() {
const urlCoords = getURLCoords();
@@ -45,6 +52,19 @@ const app = () => ({
}
},
+ async useCurrentLocation() {
+ this.locateError = "";
+ this.locating = true;
+ try {
+ const coords = await getUserLocation();
+ await this.selectNearestLocation(coords.latitude, coords.longitude, {updateURL: true});
+ } catch (_error) {
+ this.locateError = "locateFailed";
+ } finally {
+ this.locating = false;
+ }
+ },
+
onSearchInput() {
this.searchError = "";
if (this.searchDebounceTimer) {
@@ -55,6 +75,9 @@ const app = () => ({
if (query === "") {
this.searchResults = [];
this.searchOpen = false;
+ if (this.selectedLocation) {
+ this.clearLocation();
+ }
return;
}
@@ -71,7 +94,7 @@ const app = () => ({
this.searchResults = response.locations ?? [];
} catch (_error) {
this.searchResults = [];
- this.searchError = "Failed to search locations.";
+ this.searchError = "searchFailed";
} finally {
this.searchLoading = false;
}
@@ -97,6 +120,16 @@ const app = () => ({
await this.refreshPrayerTimes();
},
+ clearLocation() {
+ this.selectedLocation = null;
+ this.searchQuery = "";
+ this.searchResults = [];
+ this.searchOpen = false;
+ this.futureTimes = [];
+ this.lastUpdated = null;
+ clearURLCoords();
+ },
+
formatLocationLabel(location) {
if (!location) {
return "";
@@ -173,6 +206,51 @@ const app = () => ({
}
});
+const UI_STRINGS = {
+ appTitle: {
+ tr: "Namaz Vakitleri",
+ en: "Prayer Times",
+ de: "Gebetszeiten",
+ ar: "مواقيت الصلاة"
+ },
+ searchPlaceholder: {
+ tr: "Konum ara...",
+ en: "Search location...",
+ de: "Ort suchen...",
+ ar: "ابحث عن موقع..."
+ },
+ useCurrentLocation: {
+ tr: "Mevcut konumumu kullan",
+ en: "Use my current location",
+ de: "Meinen aktuellen Standort verwenden",
+ ar: "استخدم موقعي الحالي"
+ },
+ searching: {
+ tr: "Aranıyor...",
+ en: "Searching...",
+ de: "Suche läuft...",
+ ar: "جارٍ البحث..."
+ },
+ noResults: {
+ tr: "Sonuç bulunamadı",
+ en: "No results",
+ de: "Keine Ergebnisse",
+ ar: "لا توجد نتائج"
+ },
+ locateFailed: {
+ tr: "Konumunuz alınamadı.",
+ en: "Could not get your location.",
+ de: "Standort konnte nicht ermittelt werden.",
+ ar: "تعذر تحديد موقعك."
+ },
+ searchFailed: {
+ tr: "Konum araması başarısız oldu.",
+ en: "Failed to search locations.",
+ de: "Ortssuche fehlgeschlagen.",
+ ar: "فشل البحث عن الموقع."
+ }
+};
+
class PrayerTimes {
static salaths = ["fajr", "sunrise", "dhuhr", "asr", "maghrib", "isha"];
static translations = {
@@ -367,3 +445,12 @@ function setURLCoords(latitude, longitude) {
url.searchParams.delete("lon");
window.history.replaceState({}, "", url.toString());
}
+
+function clearURLCoords() {
+ const url = new URL(window.location.href);
+ url.searchParams.delete("latitude");
+ url.searchParams.delete("longitude");
+ url.searchParams.delete("lat");
+ url.searchParams.delete("lon");
+ window.history.replaceState({}, "", url.toString());
+}
diff --git a/templates/static/style.css b/templates/static/style.css
index c02f770..a0d0cfc 100644
--- a/templates/static/style.css
+++ b/templates/static/style.css
@@ -45,7 +45,7 @@ body {
align-items: center;
justify-content: center;
gap: 1.25rem;
- padding: 0.875rem 1.25rem;
+ padding: clamp(1rem, 4vh, 2rem) 1.25rem 0.875rem;
}
.app-bar__brand {
@@ -72,11 +72,16 @@ body {
.location-search {
position: relative;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
width: 100%;
max-width: 24rem;
}
.location-search__input {
+ flex: 1 1 auto;
+ min-width: 0;
width: 100%;
border: 1px solid var(--color-border);
border-radius: 0.75rem;
@@ -86,6 +91,33 @@ body {
color: var(--color-ink);
}
+.location-search__locate {
+ flex: 0 0 auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 2.25rem;
+ height: 2.25rem;
+ border: 1px solid var(--color-border);
+ border-radius: 0.75rem;
+ background: var(--color-surface);
+ color: var(--color-primary);
+ font-size: 1.1rem;
+ line-height: 1;
+ cursor: pointer;
+}
+
+.location-search__locate:hover {
+ background: #f4f1e4;
+ border-color: var(--color-primary);
+}
+
+.location-search__locate:disabled,
+.location-search__locate--busy {
+ opacity: 0.6;
+ cursor: wait;
+}
+
.location-search__input:focus {
outline: none;
border-color: var(--color-primary);
@@ -175,7 +207,7 @@ p + p {
align-items: center;
justify-content: space-evenly;
gap: clamp(0.5rem, 2vh, 1.25rem);
- padding: 0.5rem 1rem;
+ padding: clamp(0.5rem, 2vh, 1rem) 1rem;
overflow: hidden;
}
@@ -282,7 +314,7 @@ tr.current .cell-names {
.app-footer {
flex: 0 0 auto;
- padding: 0.25rem 1rem 0.75rem;
+ padding: clamp(0.5rem, 2vh, 1rem) 1rem clamp(1rem, 4vh, 2rem);
}
.clock {