fix(shipwrecks): small row and query fixes: era counts in one pass, pickers list only years with a wreck, no Portuguese default

- The legend counted each era twice, scanning every record eight times per
  row refresh; it now counts once.
- Each year picker listed every year from the oldest dated wreck (well over
  a thousand entries); it lists the years that have a wreck, plus the chosen
  ends.
- Labels fell back to Portuguese and links to Portuguese Wikipedia while the
  app ships 11 locales: labels now fall back through the app's languages
  after English, and a wreck with no English article links its Wikidata
  item, which lists the article in every language.
This commit is contained in:
Sameh Khamis committed 2026-11-10 13:29:25 -08:00
1 parent 38cf1e2be5
commit 0c159186f9
5 files changed
+71 -17

No files matched your search

+1 -1
View File
@@ -178,7 +178,7 @@ cull GhostMaps uses, sized by deaths (a bigger toll draws on top) and coloured
by era (a legend under the row): before 1900
(ancient wrecks dated only by period count here), 1900–1945, after 1945, and
date unknown. A period filter under the row picks a start year and an end year
side by side (two pickers over the years in the data; a reversed pair is
side by side (two pickers over the years that have a wreck; a reversed pair is
stored swapped); undated wrecks show only when every year
is selected. The filter is session-only (not in the share link). Clicking a marker opens a `stateless` card: deaths and
survivors first, then the sinking date or year (or the archaeological dating) and
+24 -10
View File
@@ -277,11 +277,20 @@ export function createShipwrecksLayer({
picking?.unregisterPickOwner?.(SHIPWRECKS_LAYER_ID);
}
function countOf(era) {
let count = 0;
/** Wrecks shown per era, in one pass over the records. */
function eraCounts() {
const counts = Object.fromEntries(SHIPWRECK_ERAS.map((era) => [era, 0]));
for (const record of _records.values())
if (eraOf(record) === era && inPeriod(record, _range)) count++;
return count;
if (inPeriod(record, _range)) counts[eraOf(record)]++;
return counts;
}
/** The years that have a dated wreck, plus `extra` ones, newest first. */
function yearsInData(extra = []) {
const years = new Set(extra);
for (const record of _records.values())
if (Number.isInteger(record.year)) years.add(record.year);
return [...years].sort((a, b) => b - a);
}
/** The start and end year pickers, side by side. */
@@ -293,9 +302,13 @@ export function createShipwrecksLayer({
: range.from === range.to
? String(range.from)
: `${range.from}–${range.to}`;
const options = [];
for (let year = _extent.max; year >= _extent.min; year--)
options.push({ value: year, label: String(year) });
// Only years with a wreck: a picker of every year since the oldest
// dated wreck would run to well over a thousand entries.
// The chosen ends stay listed even when no wreck sank that year.
const options = yearsInData([range.from, range.to]).map((year) => ({
value: year,
label: String(year),
}));
return {
range: {
id: 'period',
@@ -425,16 +438,17 @@ export function createShipwrecksLayer({
},
getRowControls() {
const counts = eraCounts();
return {
chips: [],
...periodControls(),
// The colours are a legend: when the ship sank.
legend: SHIPWRECK_ERAS.map((era) => ({
label: t(ERA_LABELS[era]),
count: countOf(era),
count: counts[era],
color: ERA_COLORS[era],
blurb: tn(countOf(era), '{count} wreck', '{count} wrecks', {
count: formatNumber(countOf(era)),
blurb: tn(counts[era], '{count} wreck', '{count} wrecks', {
count: formatNumber(counts[era]),
}),
})),
info: t('EMODnet (European waters) · Wikidata (worldwide)'),
+36
View File
@@ -208,3 +208,39 @@ test('a click opens the card; a period that excludes the wreck closes it', async
);
assert.equal(h.layer.getStats().count, 1);
});
test('the year pickers list only years with a wreck, and each era is counted', async () => {
const h = harness({
snapshots: [
snapshot([
wreck('a', { year: 300 }),
wreck('b', { year: 1912 }),
wreck('c', { year: 1912 }),
wreck('d', { year: 1990 }),
wreck('e', { year: null }),
]),
],
});
h.layer.enable();
await h.layer.update();
let controls = h.layer.getRowControls();
assert.deepEqual(
controls.range.options.map((option) => option.value),
[1990, 1912, 300],
);
assert.deepEqual(
controls.legend.map((era) => era.count),
[1, 2, 1, 1],
);
h.layer.setParams({ from: 1900, to: 1950 });
controls = h.layer.getRowControls();
assert.deepEqual(
controls.range.options.map((option) => option.value),
[1990, 1950, 1912, 1900, 300],
'the chosen ends are listed too',
);
assert.deepEqual(
controls.legend.map((era) => era.count),
[0, 2, 0, 0],
);
});
+5 -6
View File
@@ -20,7 +20,7 @@ export const SHIPWRECK_SOURCES = Object.freeze(['emodnet', 'wikidata']);
* come with their precision so a month-precise date is not shown as the 1st.
* Only ship and event facts are read: no items about people.
*/
export const WIKIDATA_QUERY = `SELECT ?item ?itemLabel ?itemDescription ?coord ?sunk ?sunkPrecision ?end ?endPrecision ?deaths ?survivors ?typeLabel ?enwiki ?ptwiki ?event WHERE {
export const WIKIDATA_QUERY = `SELECT ?item ?itemLabel ?itemDescription ?coord ?sunk ?sunkPrecision ?end ?endPrecision ?deaths ?survivors ?typeLabel ?enwiki ?event WHERE {
{
?item wdt:P31/wdt:P279* wd:Q852190 ; wdt:P625 ?coord ; wikibase:sitelinks ?links .
FILTER(?links > 0)
@@ -35,8 +35,7 @@ export const WIKIDATA_QUERY = `SELECT ?item ?itemLabel ?itemDescription ?coord ?
}
OPTIONAL { ?item wdt:P1120 ?deaths }
OPTIONAL { ?enwiki schema:about ?item ; schema:isPartOf <https://en.wikipedia.org/> }
OPTIONAL { ?ptwiki schema:about ?item ; schema:isPartOf <https://pt.wikipedia.org/> }
SERVICE wikibase:label { bd:serviceParam wikibase:language "en,pt,mul". }
SERVICE wikibase:label { bd:serviceParam wikibase:language "en,mul,fr,de,es,it,pt,ru,uk,ja,zh,ar". }
}`;
const MAX_TEXT = 160;
@@ -300,9 +299,9 @@ export function normalizeWikidataResults(payload) {
country: null,
originator: null,
url:
wikiUrl(value('enwiki')) ||
wikiUrl(value('ptwiki')) ||
`https://www.wikidata.org/wiki/${qid}`,
// English Wikipedia, else the Wikidata item, which links the
// article in every language that has one.
wikiUrl(value('enwiki')) || `https://www.wikidata.org/wiki/${qid}`,
});
}
return foldSinkingEvents([...byItem.values()]);
+5
View File
@@ -307,3 +307,8 @@ test('the query reads the end of service with its precision', () => {
);
assert.doesNotMatch(WIKIDATA_QUERY, /wdt:P576/);
});
test('labels and links do not default to one other language', () => {
assert.doesNotMatch(WIKIDATA_QUERY, /ptwiki|pt\.wikipedia/);
assert.match(WIKIDATA_QUERY, /wikibase:language "en,mul,/);
});