mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
Hold Cockpit regional news to the last 24 hours, and tell its states apart
Regional News showed "REGIONAL NEWS UNAVAILABLE" above populated rows and a
static "RECENT" footer over months-old articles. The status banner kept
`display: grid` when hidden, every non-empty answer carried the unavailable
text, and nothing limited or sorted articles by age.
- One window (regionalNewsWindow.js, 24 h): the local proxy asks Google News
with `when:1d` and GDELT for 24 h, drops undated, older or future-dated
articles, sorts newest first, and reports newsWindowHours/newsFetchedAt.
- The Cockpit labels four states: fresh (banner hidden, "LAST 24 H"),
kept after a failed refresh ("CACHED · REFRESH FAILED"), no news in the
window, and unavailable. The footer lives in the news state, so it
survives page and tab changes, and the client drops out-of-window rows.
- `.cockpit-brief-status[hidden]` now hides the banner.
This commit is contained in:
1 parent
2470b39f88
commit
dd83812a21
24 files changed
+637
-85
No files matched your search
+101
-20
@@ -12,7 +12,14 @@ import {
|
||||
COCKPIT_BRIEF_PAGES,
|
||||
CYCLE_HELP_KEYS,
|
||||
createBriefState,
|
||||
newsWindowFooter,
|
||||
} from './cockpitBriefPages.js';
|
||||
import {
|
||||
recentRegionalArticles,
|
||||
regionalNewsWindowHours,
|
||||
} from '../data/regionalNewsWindow.js';
|
||||
|
||||
const NEWS_ROWS = 4;
|
||||
|
||||
// regionalModel.weatherCodeLabel is shared with node code and returns
|
||||
// English; translate it here.
|
||||
@@ -52,9 +59,13 @@ export function showBriefPage(index, { manual = false } = {}) {
|
||||
kicker: t(page.kickerKey),
|
||||
subtitle: t(page.subtitleKey),
|
||||
position: `${this.briefPageIndex + 1} / ${count}`,
|
||||
source: page.sourceKey
|
||||
? t(page.sourceKey, { source: page.sourceName })
|
||||
: page.source,
|
||||
// The news footer follows the news state, so it survives page changes.
|
||||
source:
|
||||
page.id === 'news'
|
||||
? this.brief.news.footer
|
||||
: page.sourceKey
|
||||
? t(page.sourceKey)
|
||||
: page.source,
|
||||
});
|
||||
this.publishBriefView();
|
||||
if (manual && this.briefAutoRotateEnabled)
|
||||
@@ -196,8 +207,8 @@ export function maybeRefreshRegionalBrief(info) {
|
||||
subjectId !== this.regionalBriefSubjectId
|
||||
)
|
||||
return;
|
||||
this.regionalBrief = payload;
|
||||
this.renderRegionalBrief(payload, info);
|
||||
this.regionalBrief = withCachedNews(payload, this.regionalBrief);
|
||||
this.renderRegionalBrief(this.regionalBrief, info);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (
|
||||
@@ -206,7 +217,11 @@ export function maybeRefreshRegionalBrief(info) {
|
||||
requestToken === this.regionalBriefRequestToken &&
|
||||
subjectId === this.regionalBriefSubjectId
|
||||
) {
|
||||
this.renderRegionalBriefStatus('unavailable', info);
|
||||
if (this.regionalBrief) {
|
||||
const previous = this.regionalBrief;
|
||||
this.regionalBrief = cachedNews(previous, previous);
|
||||
this.renderRegionalBrief(this.regionalBrief, info);
|
||||
} else this.renderRegionalBriefStatus('unavailable', info);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -215,6 +230,75 @@ export function maybeRefreshRegionalBrief(info) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A brief whose news comes from `previous` after a refresh that brought none:
|
||||
* the earlier articles stay, marked `cached` so they are labelled as such.
|
||||
* @param {object} payload The brief to keep everything else from.
|
||||
* @param {object} previous The last brief rendered for this subject.
|
||||
*/
|
||||
function cachedNews(payload, previous) {
|
||||
return {
|
||||
...payload,
|
||||
newsStatus: 'cached',
|
||||
newsSource: previous.newsSource,
|
||||
newsWindowHours: previous.newsWindowHours,
|
||||
newsFetchedAt: previous.newsFetchedAt ?? previous.retrievedAt ?? null,
|
||||
articles: previous.articles,
|
||||
};
|
||||
}
|
||||
|
||||
// A refresh whose news read failed keeps the articles from before it.
|
||||
function withCachedNews(payload, previous) {
|
||||
return previous && payload?.newsStatus === 'unavailable'
|
||||
? cachedNews(payload, previous)
|
||||
: payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Regional News state a brief presents. `ready` and `cached` have rows
|
||||
* inside the window; `cached` is a refresh that failed (a client-side failure,
|
||||
* or the local proxy's `stale` answer). `empty` is a source that answered with
|
||||
* nothing in the window; anything else is `unavailable`.
|
||||
* @returns {{state: string, status: string, footer: string, articles: object[]}}
|
||||
*/
|
||||
function regionalNewsView(payload) {
|
||||
const hours = regionalNewsWindowHours(payload?.newsWindowHours);
|
||||
// Old caches and the local path are held to the window here too.
|
||||
const articles = recentRegionalArticles(payload?.articles, {
|
||||
windowHours: hours,
|
||||
limit: NEWS_ROWS,
|
||||
});
|
||||
const source = String(
|
||||
payload?.newsSource || t('REGIONAL NEWS'),
|
||||
).toUpperCase();
|
||||
const refreshFailed =
|
||||
payload?.newsStatus === 'cached' || payload?.status === 'stale';
|
||||
if (articles.length && refreshFailed)
|
||||
return {
|
||||
state: 'cached',
|
||||
status: t('CACHED · REFRESH FAILED'),
|
||||
footer: t('{source} · CACHED {age}', {
|
||||
source,
|
||||
age: formatCockpitBriefAge(
|
||||
payload?.newsFetchedAt ?? payload?.retrievedAt,
|
||||
),
|
||||
}),
|
||||
articles,
|
||||
};
|
||||
const answered =
|
||||
!refreshFailed && ['ready', 'empty'].includes(payload?.newsStatus);
|
||||
return {
|
||||
state: articles.length ? 'ready' : answered ? 'empty' : 'unavailable',
|
||||
status: articles.length
|
||||
? ''
|
||||
: answered
|
||||
? t('NO NEWS IN THE LAST {hours} H', { hours })
|
||||
: t('REGIONAL NEWS UNAVAILABLE'),
|
||||
footer: newsWindowFooter(source, hours),
|
||||
articles,
|
||||
};
|
||||
}
|
||||
|
||||
export function renderRegionalBriefStatus(status, info) {
|
||||
const { news, local } = this.brief;
|
||||
news.statusHidden = false;
|
||||
@@ -223,7 +307,10 @@ export function renderRegionalBriefStatus(status, info) {
|
||||
status === 'loading'
|
||||
? t('ACQUIRING REGIONAL NEWS')
|
||||
: t('REGIONAL NEWS UNAVAILABLE');
|
||||
if (status === 'unavailable') news.articles = [];
|
||||
news.footer = newsWindowFooter();
|
||||
// Only called with no brief for this subject: any rows are another's.
|
||||
news.articles = [];
|
||||
if (this.briefPageIndex === 1) this.brief.source = news.footer;
|
||||
if (status === 'loading') local.place = t('RESOLVING REGION');
|
||||
if (status === 'unavailable') local.place = t('REGION UNAVAILABLE');
|
||||
local.coordinates = localCoordinates(info);
|
||||
@@ -231,7 +318,7 @@ export function renderRegionalBriefStatus(status, info) {
|
||||
}
|
||||
|
||||
export function renderRegionalBrief(payload, info) {
|
||||
const articles = Array.isArray(payload?.articles) ? payload.articles : [];
|
||||
const { state, status, footer, articles } = regionalNewsView(payload);
|
||||
const seen = new Map();
|
||||
const storyKey = (url) => {
|
||||
const count = seen.get(url) ?? 0;
|
||||
@@ -244,13 +331,11 @@ export function renderRegionalBrief(payload, info) {
|
||||
const placeLabel =
|
||||
payload?.place?.label || payload?.place?.country || t('REGION UNAVAILABLE');
|
||||
this.brief.news = {
|
||||
statusHidden: articles.length > 0,
|
||||
state: payload?.newsStatus || 'unavailable',
|
||||
status:
|
||||
payload?.newsStatus === 'empty'
|
||||
? t('NO RECENT LOCATION MATCHES')
|
||||
: t('REGIONAL NEWS UNAVAILABLE'),
|
||||
articles: articles.slice(0, 4).map((article) => ({
|
||||
statusHidden: state === 'ready',
|
||||
state,
|
||||
status,
|
||||
footer,
|
||||
articles: articles.map((article) => ({
|
||||
// A row keeps its node, and so its focus, only for the same story.
|
||||
key: storyKey(article.url),
|
||||
url: article.url,
|
||||
@@ -284,11 +369,7 @@ export function renderRegionalBrief(payload, info) {
|
||||
: '—',
|
||||
};
|
||||
this.brief.regionalStatus = payload?.status || 'partial';
|
||||
if (this.briefPageIndex === 1) {
|
||||
this.brief.source = t('{source} · LOCATION QUERY', {
|
||||
source: String(payload?.newsSource || t('REGIONAL NEWS')).toUpperCase(),
|
||||
});
|
||||
}
|
||||
if (this.briefPageIndex === 1) this.brief.source = footer;
|
||||
this.publishBriefView();
|
||||
this.scheduleContextLayout();
|
||||
}
|
||||
Reference in new issue
Block a user