update settings page with status message area
This commit is contained in:
+41
-1
@@ -525,7 +525,47 @@
|
|||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
border-top: 1px solid rgba(63, 63, 70, 0.7);
|
border-top: 1px solid rgba(63, 63, 70, 0.7);
|
||||||
justify-content: flex-end;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.settings-page-status {
|
||||||
|
min-width: min(100%, 360px);
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 14px;
|
||||||
|
border: 1px solid rgba(63, 63, 70, 0.92);
|
||||||
|
background: rgba(9, 9, 11, 0.78);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.settings-page-status[data-tone="info"] {
|
||||||
|
border-color: rgba(59, 130, 246, 0.55);
|
||||||
|
background: rgba(30, 64, 175, 0.14);
|
||||||
|
}
|
||||||
|
.settings-page-status[data-tone="success"] {
|
||||||
|
border-color: rgba(34, 197, 94, 0.5);
|
||||||
|
background: rgba(20, 83, 45, 0.18);
|
||||||
|
}
|
||||||
|
.settings-page-status[data-tone="warning"] {
|
||||||
|
border-color: rgba(245, 158, 11, 0.55);
|
||||||
|
background: rgba(120, 53, 15, 0.18);
|
||||||
|
}
|
||||||
|
.settings-page-status[data-tone="error"] {
|
||||||
|
border-color: rgba(239, 68, 68, 0.55);
|
||||||
|
background: rgba(127, 29, 29, 0.2);
|
||||||
|
}
|
||||||
|
.settings-page-status-text {
|
||||||
|
color: #f8fafc;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
.settings-page-status-time {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
.calendar-year-control {
|
.calendar-year-control {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const SETTINGS_STORAGE_KEY = "tarot-time-settings-v1";
|
const SETTINGS_STORAGE_KEY = "tarot-time-settings-v1";
|
||||||
|
const SETTINGS_LAST_SAVED_AT_STORAGE_KEY = "tarot-time-settings-last-saved-at-v1";
|
||||||
|
|
||||||
let config = {
|
let config = {
|
||||||
defaultSettings: {
|
defaultSettings: {
|
||||||
@@ -39,11 +40,89 @@
|
|||||||
stellariumBackgroundHintEl: document.getElementById("stellarium-background-hint"),
|
stellariumBackgroundHintEl: document.getElementById("stellarium-background-hint"),
|
||||||
apiBaseUrlEl: document.getElementById("api-base-url"),
|
apiBaseUrlEl: document.getElementById("api-base-url"),
|
||||||
apiKeyEl: document.getElementById("api-key"),
|
apiKeyEl: document.getElementById("api-key"),
|
||||||
|
settingsPageStatusEl: document.getElementById("settings-page-status"),
|
||||||
|
settingsPageStatusTextEl: document.getElementById("settings-page-status-text"),
|
||||||
|
settingsPageStatusTimeEl: document.getElementById("settings-page-status-time"),
|
||||||
saveSettingsEl: document.getElementById("save-settings"),
|
saveSettingsEl: document.getElementById("save-settings"),
|
||||||
useLocationEl: document.getElementById("use-location")
|
useLocationEl: document.getElementById("use-location")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadLastSavedAt() {
|
||||||
|
try {
|
||||||
|
const raw = window.localStorage.getItem(SETTINGS_LAST_SAVED_AT_STORAGE_KEY);
|
||||||
|
const parsed = Number(raw);
|
||||||
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function persistLastSavedAt(timestamp) {
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(SETTINGS_LAST_SAVED_AT_STORAGE_KEY, String(timestamp));
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatLastSavedAt(timestamp) {
|
||||||
|
if (!Number.isFinite(timestamp) || timestamp <= 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new Intl.DateTimeFormat(undefined, {
|
||||||
|
dateStyle: "medium",
|
||||||
|
timeStyle: "short"
|
||||||
|
}).format(new Date(timestamp));
|
||||||
|
} catch {
|
||||||
|
return new Date(timestamp).toLocaleString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSettingsPageStatus(message, tone = "neutral", options = {}) {
|
||||||
|
const {
|
||||||
|
settingsPageStatusEl,
|
||||||
|
settingsPageStatusTextEl,
|
||||||
|
settingsPageStatusTimeEl
|
||||||
|
} = getElements();
|
||||||
|
|
||||||
|
if (settingsPageStatusEl) {
|
||||||
|
settingsPageStatusEl.dataset.tone = String(tone || "neutral");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settingsPageStatusTextEl) {
|
||||||
|
settingsPageStatusTextEl.textContent = String(message || "Settings ready.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedAt = options.savedAt === undefined ? loadLastSavedAt() : options.savedAt;
|
||||||
|
const formattedSavedAt = formatLastSavedAt(savedAt);
|
||||||
|
if (settingsPageStatusTimeEl) {
|
||||||
|
settingsPageStatusTimeEl.hidden = !formattedSavedAt;
|
||||||
|
settingsPageStatusTimeEl.textContent = formattedSavedAt ? `Last saved ${formattedSavedAt}` : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncSavedSettingsStatus(message = "Settings ready.") {
|
||||||
|
setSettingsPageStatus(message, "neutral", { savedAt: loadLastSavedAt() });
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSaveButtonBusy(isBusy) {
|
||||||
|
const { saveSettingsEl } = getElements();
|
||||||
|
if (!saveSettingsEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!saveSettingsEl.dataset.defaultLabel) {
|
||||||
|
saveSettingsEl.dataset.defaultLabel = String(saveSettingsEl.textContent || "Save Settings").trim() || "Save Settings";
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSettingsEl.disabled = Boolean(isBusy);
|
||||||
|
saveSettingsEl.textContent = isBusy ? "Saving..." : saveSettingsEl.dataset.defaultLabel;
|
||||||
|
}
|
||||||
|
|
||||||
function setLocationEntryState(isExplicit) {
|
function setLocationEntryState(isExplicit) {
|
||||||
const { latEl, lngEl } = getElements();
|
const { latEl, lngEl } = getElements();
|
||||||
const normalizedValue = isExplicit ? "true" : "false";
|
const normalizedValue = isExplicit ? "true" : "false";
|
||||||
@@ -466,6 +545,7 @@
|
|||||||
lastNonSettingsSection = activeSection;
|
lastNonSettingsSection = activeSection;
|
||||||
}
|
}
|
||||||
applySettingsToInputs(loadSavedSettings());
|
applySettingsToInputs(loadSavedSettings());
|
||||||
|
syncSavedSettingsStatus();
|
||||||
config.setActiveSection?.("settings");
|
config.setActiveSection?.("settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,6 +554,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleSaveSettings() {
|
async function handleSaveSettings() {
|
||||||
|
setSaveButtonBusy(true);
|
||||||
|
setSettingsPageStatus("Saving settings...", "info");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const settings = getSettingsFromInputs();
|
const settings = getSettingsFromInputs();
|
||||||
const previousConnectionSettings = getConnectionSettings();
|
const previousConnectionSettings = getConnectionSettings();
|
||||||
@@ -501,22 +584,39 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!didPersist || connectionResult.didPersist === false) {
|
if (!didPersist || connectionResult.didPersist === false) {
|
||||||
|
setSettingsPageStatus("Settings applied for this session. Browser storage is unavailable.", "warning", {
|
||||||
|
savedAt: loadLastSavedAt()
|
||||||
|
});
|
||||||
setStatus("Settings applied for this session. Browser storage is unavailable.");
|
setStatus("Settings applied for this session. Browser storage is unavailable.");
|
||||||
} else {
|
} else {
|
||||||
|
const savedAt = Date.now();
|
||||||
|
persistLastSavedAt(savedAt);
|
||||||
|
setSettingsPageStatus("Settings saved.", "success", { savedAt });
|
||||||
setStatus("Settings saved.");
|
setStatus("Settings saved.");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setSettingsPageStatus(error?.message || "Unable to save settings.", "error", {
|
||||||
|
savedAt: loadLastSavedAt()
|
||||||
|
});
|
||||||
setStatus(error?.message || "Unable to save settings.");
|
setStatus(error?.message || "Unable to save settings.");
|
||||||
|
} finally {
|
||||||
|
setSaveButtonBusy(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function requestGeoLocation() {
|
function requestGeoLocation() {
|
||||||
const { latEl, lngEl } = getElements();
|
const { latEl, lngEl } = getElements();
|
||||||
if (!navigator.geolocation) {
|
if (!navigator.geolocation) {
|
||||||
|
setSettingsPageStatus("Geolocation not available in this browser.", "warning", {
|
||||||
|
savedAt: loadLastSavedAt()
|
||||||
|
});
|
||||||
setStatus("Geolocation not available in this browser.");
|
setStatus("Geolocation not available in this browser.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setSettingsPageStatus("Getting your location...", "info", {
|
||||||
|
savedAt: loadLastSavedAt()
|
||||||
|
});
|
||||||
setStatus("Getting your location...");
|
setStatus("Getting your location...");
|
||||||
navigator.geolocation.getCurrentPosition(
|
navigator.geolocation.getCurrentPosition(
|
||||||
({ coords }) => {
|
({ coords }) => {
|
||||||
@@ -531,10 +631,16 @@
|
|||||||
hasExplicitLocation: true
|
hasExplicitLocation: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
setSettingsPageStatus("Location captured. Save Settings to keep it in this browser.", "info", {
|
||||||
|
savedAt: loadLastSavedAt()
|
||||||
|
});
|
||||||
setStatus("Location set from browser. Save Settings to use it across the app.");
|
setStatus("Location set from browser. Save Settings to use it across the app.");
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
const detail = err?.message || `code ${err?.code ?? "unknown"}`;
|
const detail = err?.message || `code ${err?.code ?? "unknown"}`;
|
||||||
|
setSettingsPageStatus(`Could not get location (${detail}).`, "error", {
|
||||||
|
savedAt: loadLastSavedAt()
|
||||||
|
});
|
||||||
setStatus(`Could not get location (${detail}).`);
|
setStatus(`Could not get location (${detail}).`);
|
||||||
},
|
},
|
||||||
{ enableHighAccuracy: true, timeout: 10000 }
|
{ enableHighAccuracy: true, timeout: 10000 }
|
||||||
@@ -612,6 +718,7 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
syncSavedSettingsStatus();
|
||||||
bindInteractions();
|
bindInteractions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -174,6 +174,10 @@
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-actions settings-page-actions">
|
<div class="settings-actions settings-page-actions">
|
||||||
|
<div id="settings-page-status" class="settings-page-status" data-tone="neutral" aria-live="polite">
|
||||||
|
<span id="settings-page-status-text" class="settings-page-status-text">Settings ready.</span>
|
||||||
|
<span id="settings-page-status-time" class="settings-page-status-time" hidden></span>
|
||||||
|
</div>
|
||||||
<button id="save-settings" type="button">Save Settings</button>
|
<button id="save-settings" type="button">Save Settings</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user