Fix intraday modal races and METAR temperature fallbacks
This commit is contained in:
@@ -336,6 +336,7 @@ export function DashboardStoreProvider({
|
|||||||
const proAccessRef = useRef<ProAccessState>(getInitialProAccessState());
|
const proAccessRef = useRef<ProAccessState>(getInitialProAccessState());
|
||||||
|
|
||||||
const mapStopMotionRef = useRef<() => void>(() => {});
|
const mapStopMotionRef = useRef<() => void>(() => {});
|
||||||
|
const modalOpenSeqRef = useRef(0);
|
||||||
const hydratedSelectionRef = useRef(false);
|
const hydratedSelectionRef = useRef(false);
|
||||||
const hydratedProCacheRef = useRef(false);
|
const hydratedProCacheRef = useRef(false);
|
||||||
const backgroundSummaryCheckAtRef = useRef<Record<string, number>>({});
|
const backgroundSummaryCheckAtRef = useRef<Record<string, number>>({});
|
||||||
@@ -1003,7 +1004,10 @@ export function DashboardStoreProvider({
|
|||||||
cities,
|
cities,
|
||||||
cityDetailsByName,
|
cityDetailsByName,
|
||||||
citySummariesByName,
|
citySummariesByName,
|
||||||
closeFutureModal: () => setFutureModalDate(null),
|
closeFutureModal: () => {
|
||||||
|
modalOpenSeqRef.current += 1;
|
||||||
|
setFutureModalDate(null);
|
||||||
|
},
|
||||||
closeHistory: () =>
|
closeHistory: () =>
|
||||||
setHistoryState((current) => ({ ...current, isOpen: false })),
|
setHistoryState((current) => ({ ...current, isOpen: false })),
|
||||||
closePanel: () => {
|
closePanel: () => {
|
||||||
@@ -1020,17 +1024,22 @@ export function DashboardStoreProvider({
|
|||||||
mapStopMotionRef.current();
|
mapStopMotionRef.current();
|
||||||
if (!selectedCity || !proAccess.subscriptionActive) return;
|
if (!selectedCity || !proAccess.subscriptionActive) return;
|
||||||
const cityName = selectedCity;
|
const cityName = selectedCity;
|
||||||
|
const modalSeq = (modalOpenSeqRef.current += 1);
|
||||||
|
const isLatestModalRequest = () =>
|
||||||
|
modalOpenSeqRef.current === modalSeq &&
|
||||||
|
selectedCityRef.current === cityName;
|
||||||
let cachedDetail = cityDetailsByName[selectedCity];
|
let cachedDetail = cityDetailsByName[selectedCity];
|
||||||
if (!cachedDetail) {
|
if (!cachedDetail) {
|
||||||
setLoadingState((current) => ({ ...current, cityDetail: true }));
|
setLoadingState((current) => ({ ...current, cityDetail: true }));
|
||||||
try {
|
try {
|
||||||
cachedDetail = await ensureCityDetail(cityName, false, "panel");
|
cachedDetail = await ensureCityDetail(cityName, false, "panel");
|
||||||
} finally {
|
} finally {
|
||||||
if (selectedCityRef.current === cityName) {
|
if (isLatestModalRequest()) {
|
||||||
setLoadingState((current) => ({ ...current, cityDetail: false }));
|
setLoadingState((current) => ({ ...current, cityDetail: false }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!isLatestModalRequest()) return;
|
||||||
const hasFullCachedDetail =
|
const hasFullCachedDetail =
|
||||||
detailSatisfiesDepth(cachedDetail, "full") &&
|
detailSatisfiesDepth(cachedDetail, "full") &&
|
||||||
!hasSparseDetailCoverage(cachedDetail, dateStr);
|
!hasSparseDetailCoverage(cachedDetail, dateStr);
|
||||||
@@ -1040,6 +1049,7 @@ export function DashboardStoreProvider({
|
|||||||
dateStr,
|
dateStr,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setSelectedForecastDate(dateStr);
|
||||||
setFutureModalDate(dateStr);
|
setFutureModalDate(dateStr);
|
||||||
if (!hasMarketCachedDetail || forceRefresh) {
|
if (!hasMarketCachedDetail || forceRefresh) {
|
||||||
void ensureCityDetail(cityName, forceRefresh, "market").catch(() => {});
|
void ensureCityDetail(cityName, forceRefresh, "market").catch(() => {});
|
||||||
@@ -1052,7 +1062,7 @@ export function DashboardStoreProvider({
|
|||||||
void ensureCityDetail(cityName, true, "full")
|
void ensureCityDetail(cityName, true, "full")
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
if (selectedCityRef.current !== cityName) return;
|
if (!isLatestModalRequest()) return;
|
||||||
setLoadingState((current) => ({
|
setLoadingState((current) => ({
|
||||||
...current,
|
...current,
|
||||||
futureDeep: false,
|
futureDeep: false,
|
||||||
@@ -1068,17 +1078,22 @@ export function DashboardStoreProvider({
|
|||||||
|
|
||||||
mapStopMotionRef.current();
|
mapStopMotionRef.current();
|
||||||
const cityName = selectedCity;
|
const cityName = selectedCity;
|
||||||
|
const modalSeq = (modalOpenSeqRef.current += 1);
|
||||||
|
const isLatestModalRequest = () =>
|
||||||
|
modalOpenSeqRef.current === modalSeq &&
|
||||||
|
selectedCityRef.current === cityName;
|
||||||
let cachedDetail = cityDetailsByName[cityName];
|
let cachedDetail = cityDetailsByName[cityName];
|
||||||
if (!cachedDetail) {
|
if (!cachedDetail) {
|
||||||
setLoadingState((current) => ({ ...current, cityDetail: true }));
|
setLoadingState((current) => ({ ...current, cityDetail: true }));
|
||||||
try {
|
try {
|
||||||
cachedDetail = await ensureCityDetail(cityName, false, "panel");
|
cachedDetail = await ensureCityDetail(cityName, false, "panel");
|
||||||
} finally {
|
} finally {
|
||||||
if (selectedCityRef.current === cityName) {
|
if (isLatestModalRequest()) {
|
||||||
setLoadingState((current) => ({ ...current, cityDetail: false }));
|
setLoadingState((current) => ({ ...current, cityDetail: false }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!isLatestModalRequest()) return;
|
||||||
const hasFullCachedDetail =
|
const hasFullCachedDetail =
|
||||||
detailSatisfiesDepth(cachedDetail, "full") &&
|
detailSatisfiesDepth(cachedDetail, "full") &&
|
||||||
!hasSparseDetailCoverage(cachedDetail, cachedDetail?.local_date);
|
!hasSparseDetailCoverage(cachedDetail, cachedDetail?.local_date);
|
||||||
@@ -1088,7 +1103,9 @@ export function DashboardStoreProvider({
|
|||||||
cachedDetail?.local_date,
|
cachedDetail?.local_date,
|
||||||
);
|
);
|
||||||
const targetDate =
|
const targetDate =
|
||||||
cachedDetail?.local_date || selectedForecastDate || null;
|
cachedDetail?.local_date ||
|
||||||
|
cachedDetail?.forecast?.daily?.[0]?.date ||
|
||||||
|
null;
|
||||||
if (targetDate) {
|
if (targetDate) {
|
||||||
setSelectedForecastDate(targetDate);
|
setSelectedForecastDate(targetDate);
|
||||||
setFutureModalDate(targetDate);
|
setFutureModalDate(targetDate);
|
||||||
@@ -1116,19 +1133,19 @@ export function DashboardStoreProvider({
|
|||||||
"full",
|
"full",
|
||||||
)
|
)
|
||||||
.then((detail) => {
|
.then((detail) => {
|
||||||
if (selectedCityRef.current !== cityName) return;
|
if (!isLatestModalRequest()) return;
|
||||||
setSelectedForecastDate(detail.local_date);
|
setSelectedForecastDate(detail.local_date);
|
||||||
setFutureModalDate(detail.local_date);
|
setFutureModalDate(detail.local_date);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
if (selectedCityRef.current !== cityName) return;
|
if (!isLatestModalRequest()) return;
|
||||||
if (cachedDetail?.local_date) {
|
if (cachedDetail?.local_date) {
|
||||||
setSelectedForecastDate(cachedDetail.local_date);
|
setSelectedForecastDate(cachedDetail.local_date);
|
||||||
setFutureModalDate(cachedDetail.local_date);
|
setFutureModalDate(cachedDetail.local_date);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
if (selectedCityRef.current !== cityName) return;
|
if (!isLatestModalRequest()) return;
|
||||||
setLoadingState((current) => ({
|
setLoadingState((current) => ({
|
||||||
...current,
|
...current,
|
||||||
futureDeep: false,
|
futureDeep: false,
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ function pickMarkerTemperature(
|
|||||||
currentTemp,
|
currentTemp,
|
||||||
detail.airport_current?.temp,
|
detail.airport_current?.temp,
|
||||||
isNmcStation(detail.airport_primary) ? null : detail.airport_primary?.temp,
|
isNmcStation(detail.airport_primary) ? null : detail.airport_primary?.temp,
|
||||||
detail.mgm_nearby?.[0]?.temp,
|
|
||||||
];
|
];
|
||||||
for (const value of candidates) {
|
for (const value of candidates) {
|
||||||
const numeric = Number(value);
|
const numeric = Number(value);
|
||||||
|
|||||||
@@ -410,27 +410,6 @@ function buildCurrentObservationFallback(
|
|||||||
temp: detail.airport_current?.temp,
|
temp: detail.airport_current?.temp,
|
||||||
time: detail.airport_current?.obs_time || detail.airport_current?.report_time,
|
time: detail.airport_current?.obs_time || detail.airport_current?.report_time,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
sourceLabel:
|
|
||||||
detail.center_station_candidate?.source_label ||
|
|
||||||
detail.center_station_candidate?.source_code,
|
|
||||||
temp: detail.center_station_candidate?.temp,
|
|
||||||
time: String((detail.center_station_candidate as Record<string, unknown> | null | undefined)?.obs_time || ""),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sourceLabel:
|
|
||||||
detail.official_nearby?.[0]?.source_label ||
|
|
||||||
detail.official_nearby?.[0]?.source_code,
|
|
||||||
temp: detail.official_nearby?.[0]?.temp,
|
|
||||||
time: String((detail.official_nearby?.[0] as Record<string, unknown> | null | undefined)?.obs_time || ""),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sourceLabel:
|
|
||||||
detail.mgm_nearby?.[0]?.source_label ||
|
|
||||||
detail.mgm_nearby?.[0]?.source_code,
|
|
||||||
temp: detail.mgm_nearby?.[0]?.temp,
|
|
||||||
time: String((detail.mgm_nearby?.[0] as Record<string, unknown> | null | undefined)?.obs_time || ""),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const first = candidates.find((item) => {
|
const first = candidates.find((item) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user