Recent locations save
This commit is contained in:
fraxle
2026-07-04 21:53:14 +01:00
parent bf64122bc2
commit 318e532d10
7 changed files with 140 additions and 2 deletions
+3
View File
@@ -0,0 +1,3 @@
{
"topic_20260629-150638_cda0606c7e7a865d": 1782745598290
}
@@ -0,0 +1,3 @@
{
"topic_20260629-150638_cda0606c7e7a865d": "auto"
}
+3
View File
@@ -0,0 +1,3 @@
{
"topic_20260629-150638_cda0606c7e7a865d": "新的会话"
}
+58
View File
@@ -1122,6 +1122,57 @@
line-height: 1;
}
/* Recent locations — quick-click stacked list beneath the last-update line. */
.utci-recent-locs {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 3px;
margin-top: 0.5rem;
}
.utci-recent-label {
font-family: Manrope, sans-serif;
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #b09870;
margin-bottom: 1px;
}
.utci-recent-chip {
display: flex;
align-items: center;
gap: 5px;
width: 100%;
max-width: 220px;
padding: 4px 10px 4px 8px;
font-family: Manrope, sans-serif;
font-size: 0.74rem;
font-weight: 600;
text-align: left;
color: #7a5e2a;
background: #fdf8ee;
border: 1.5px solid #d9c39b;
border-radius: 8px;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.utci-recent-chip:hover {
background: #f6ecd6;
border-color: #c8922a;
color: #6a4f1e;
}
/* Keep long names on one line with an ellipsis rather than wrapping. */
.utci-recent-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.utci-recent-pin {
flex-shrink: 0;
color: #c8922a;
}
/* ── 10. LEGEND (thermal-stress band colours at the bottom) ────────── */
.utci-legend {
@@ -1306,6 +1357,13 @@
text-align: center;
}
.utci-recent-locs {
align-items: center;
}
.utci-recent-chip {
max-width: 260px;
}
.utci-search-wrap {
width: 100%;
}
+30 -1
View File
@@ -175,7 +175,7 @@ export function UTCIForecast() {
// All useState, useEffect, useCallback and useRef logic lives in
// useAppState. See hooks/useAppState.js for the full reading order.
const {
location, setLocationAndSave,
location, setLocationAndSave, recentLocations,
forecast, airQuality, loading, error, now, fetchedAt,
searchQuery, setSearchQuery, searchResults, setSearchResults, searching,
selectedDay, setSelectedDay,
@@ -675,6 +675,35 @@ export function UTCIForecast() {
<div class=${'utci-fetch-time' + (isStale ? ' is-stale' : '')}>
${isStale ? html`<span class="stale-dot" title="Data is older than expected - retrying">● </span>` : ''}Last update: ${fetchedAt.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</div>`}
${(() => {
const curKey = `${location.lat.toFixed(3)},${location.lon.toFixed(3)}`;
const recents = (recentLocations || [])
.filter((l) => `${l.lat.toFixed(3)},${l.lon.toFixed(3)}` !== curKey)
.slice(0, 3);
if (!recents.length) return null;
return html`
<div class="utci-recent-locs">
<span class="utci-recent-label">Recent</span>
${recents.map((l) => html`
<button
key=${`${l.lat.toFixed(3)}-${l.lon.toFixed(3)}`}
type="button"
class="utci-recent-chip"
title=${`See the forecast for ${l.name}`}
onClick=${() => {
setLocationAndSave(l);
setSelectedDay(0);
closeSearch();
}}
>
<svg class="utci-recent-pin" viewBox="0 0 24 24" width="11" height="11" aria-hidden="true">
<path fill="currentColor" d="M12 2c-3.87 0-7 3.13-7 7 0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z" />
<circle cx="12" cy="9" r="2.5" fill="#fffcf2" />
</svg>
<span class="utci-recent-name">${l.name}</span>
</button>`)}
</div>`;
})()}
${(searchOpen || searchClosing) && html`
<div
class=${'utci-search-wrap utci-search-overlay' + (searchOpen ? '' : ' is-closing')}
+21 -1
View File
@@ -57,9 +57,29 @@ export function useAppState() {
return { name: 'London, England', lat: 51.509, lon: -0.126, country: 'GB' };
});
// Recent locations: the most recently viewed spots, so the user can
// hop back with one click. The list is kept newest-first with the
// current location at the front; the UI shows the others as chips.
const [recentLocations, setRecentLocations] = useState(() => {
try {
const saved = localStorage.getItem('sunscope_recent_locations');
if (saved) return JSON.parse(saved);
} catch (e) { /* ignore */ }
return [];
});
const locKey = (l) => `${l.lat.toFixed(3)},${l.lon.toFixed(3)}`;
const setLocationAndSave = (loc) => {
try { localStorage.setItem('sunscope_last_location', JSON.stringify(loc)); } catch (e) { /* ignore */ }
setLocation(loc);
setRecentLocations((prev) => {
// Keep the current spot at the front and the 3 previous distinct
// ones behind it (4 total, so 3 chips remain after excluding current).
const next = [loc, ...prev.filter((l) => locKey(l) !== locKey(loc))].slice(0, 4);
try { localStorage.setItem('sunscope_recent_locations', JSON.stringify(next)); } catch (e) { /* ignore */ }
return next;
});
};
// Pro tier flag is read early so useForecast can pick its refresh cadence
@@ -589,7 +609,7 @@ export function useAppState() {
// ── RETURN ALL STATE + HANDLERS ───────────────────────────────────────
return {
// location
location, setLocationAndSave,
location, setLocationAndSave, recentLocations,
// forecast
forecast, airQuality, loading, error, now, fetchedAt,
// search
+22
View File
@@ -115,5 +115,27 @@
"farming": 3,
"basic": 1
}
},
"2026-06-29": {
"visits": 6,
"profiles": {
"basic": 6,
"home": 6,
"vehicle": 4,
"alltemps": 3,
"showall": 4,
"custom": 1
}
},
"2026-07-04": {
"visits": 4,
"profiles": {
"home": 3,
"vehicle": 3,
"alltemps": 4,
"showall": 4,
"custom": 2,
"basic": 2
}
}
}