- added a persistent 12 hour / 24 hour segmented control under the sidebar’s Time preferences.
Updated: [public/index.php](/Users/tyemeclifford/Documents/GH/weather/public/index.php) [public/assets/js/app.js](/Users/tyemeclifford/Documents/GH/weather/public/assets/js/app.js) [public/assets/css/styles.css](/Users/tyemeclifford/Documents/GH/weather/public/assets/css/styles.css) [README.md](/Users/tyemeclifford/Documents/GH/weather/README.md)
This commit is contained in:
@@ -10,7 +10,7 @@ A PHP, SQLite, HTML5, and JavaScript weather dashboard that uses public weather
|
|||||||
- RainViewer radar tiles on an interactive Leaflet map.
|
- RainViewer radar tiles on an interactive Leaflet map.
|
||||||
- Local SQLite API cache with a five minute weather refresh window.
|
- Local SQLite API cache with a five minute weather refresh window.
|
||||||
- Hourly and daily archive snapshots with search and JSON detail export.
|
- Hourly and daily archive snapshots with search and JSON detail export.
|
||||||
- Cookie-backed display preferences, map layer toggles, dark/light theme, and import/export.
|
- Cookie-backed display preferences, map layer toggles, 12/24 hour time format, dark/light theme, and import/export.
|
||||||
- Current-date history facts comparing the projection to the same date last year.
|
- Current-date history facts comparing the projection to the same date last year.
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|||||||
@@ -302,6 +302,31 @@ body.light .status-line {
|
|||||||
accent-color: var(--pink);
|
accent-color: var(--pink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.segmented {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 0;
|
||||||
|
min-height: var(--control-h);
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.segmented button {
|
||||||
|
min-height: var(--control-h);
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--muted);
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.segmented button[aria-pressed="true"] {
|
||||||
|
background: linear-gradient(135deg, rgba(57, 168, 255, 0.28), rgba(255, 90, 165, 0.24));
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
.control-actions {
|
.control-actions {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|||||||
+27
-2
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
const DEFAULT_PREFS = {
|
const DEFAULT_PREFS = {
|
||||||
theme: "dark",
|
theme: "dark",
|
||||||
|
timeFormat: "12",
|
||||||
locationQuery: "New York, NY",
|
locationQuery: "New York, NY",
|
||||||
sections: {
|
sections: {
|
||||||
current: true,
|
current: true,
|
||||||
@@ -110,6 +111,17 @@
|
|||||||
|
|
||||||
$("refreshButton").addEventListener("click", () => loadWeather(true));
|
$("refreshButton").addEventListener("click", () => loadWeather(true));
|
||||||
$("themeButton").addEventListener("click", toggleTheme);
|
$("themeButton").addEventListener("click", toggleTheme);
|
||||||
|
document.querySelectorAll("[data-time-format]").forEach((button) => {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
state.prefs.timeFormat = button.dataset.timeFormat === "24" ? "24" : "12";
|
||||||
|
savePrefs();
|
||||||
|
applyPreferences();
|
||||||
|
if (state.dashboard) {
|
||||||
|
renderDashboard();
|
||||||
|
renderArchives();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
$("exportPrefs").addEventListener("click", exportPreferences);
|
$("exportPrefs").addEventListener("click", exportPreferences);
|
||||||
$("importPrefsButton").addEventListener("click", () => $("importPrefsFile").click());
|
$("importPrefsButton").addEventListener("click", () => $("importPrefsFile").click());
|
||||||
$("importPrefsFile").addEventListener("change", importPreferences);
|
$("importPrefsFile").addEventListener("change", importPreferences);
|
||||||
@@ -635,11 +647,17 @@
|
|||||||
function applyPreferences() {
|
function applyPreferences() {
|
||||||
document.body.classList.toggle("light", state.prefs.theme === "light");
|
document.body.classList.toggle("light", state.prefs.theme === "light");
|
||||||
$("themeLabel").textContent = state.prefs.theme === "light" ? "Dark" : "Light";
|
$("themeLabel").textContent = state.prefs.theme === "light" ? "Dark" : "Light";
|
||||||
|
state.prefs.timeFormat = state.prefs.timeFormat === "24" ? "24" : "12";
|
||||||
|
|
||||||
document.querySelectorAll("[data-pref]").forEach((input) => {
|
document.querySelectorAll("[data-pref]").forEach((input) => {
|
||||||
input.checked = Boolean(getPath(state.prefs, input.dataset.pref));
|
input.checked = Boolean(getPath(state.prefs, input.dataset.pref));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll("[data-time-format]").forEach((button) => {
|
||||||
|
const isActive = button.dataset.timeFormat === state.prefs.timeFormat;
|
||||||
|
button.setAttribute("aria-pressed", isActive ? "true" : "false");
|
||||||
|
});
|
||||||
|
|
||||||
Object.entries(state.prefs.sections).forEach(([name, visible]) => {
|
Object.entries(state.prefs.sections).forEach(([name, visible]) => {
|
||||||
document.querySelectorAll(`[data-section="${name}"]`).forEach((section) => {
|
document.querySelectorAll(`[data-section="${name}"]`).forEach((section) => {
|
||||||
section.classList.toggle("is-hidden", !visible);
|
section.classList.toggle("is-hidden", !visible);
|
||||||
@@ -827,7 +845,8 @@
|
|||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
hour: "numeric",
|
hour: "numeric",
|
||||||
minute: "2-digit"
|
minute: "2-digit",
|
||||||
|
hour12: useTwelveHourClock()
|
||||||
}).format(date);
|
}).format(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -841,10 +860,16 @@
|
|||||||
}
|
}
|
||||||
return new Intl.DateTimeFormat(undefined, {
|
return new Intl.DateTimeFormat(undefined, {
|
||||||
weekday: "short",
|
weekday: "short",
|
||||||
hour: "numeric"
|
hour: "numeric",
|
||||||
|
minute: "2-digit",
|
||||||
|
hour12: useTwelveHourClock()
|
||||||
}).format(date);
|
}).format(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function useTwelveHourClock() {
|
||||||
|
return state.prefs.timeFormat !== "24";
|
||||||
|
}
|
||||||
|
|
||||||
function formatDay(value) {
|
function formatDay(value) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return "--";
|
return "--";
|
||||||
|
|||||||
@@ -87,6 +87,14 @@ declare(strict_types=1);
|
|||||||
<label><input type="checkbox" data-pref="metrics.pollen"> Pollen</label>
|
<label><input type="checkbox" data-pref="metrics.pollen"> Pollen</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<h3>Time</h3>
|
||||||
|
<div class="segmented" role="group" aria-label="Time format">
|
||||||
|
<button type="button" data-time-format="12" aria-pressed="true">12 hour</button>
|
||||||
|
<button type="button" data-time-format="24" aria-pressed="false">24 hour</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="control-actions">
|
<div class="control-actions">
|
||||||
<button type="button" id="exportPrefs">
|
<button type="button" id="exportPrefs">
|
||||||
<i data-lucide="download"></i>
|
<i data-lucide="download"></i>
|
||||||
|
|||||||
Reference in New Issue
Block a user