feat(cockpit): the owner flags load at startup from one generated 15 KB sheet; the loading status names the place packs while they load on first lookup

This commit is contained in:
Sameh Khamis committed 2026-11-08 19:35:30 -08:00
1 parent 63aa45c520
commit 5a6cf22be1
35 files changed
+643 -216

No files matched your search

+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env node
/**
* Build the owner flag sheet the orbital Cockpit marks a satellite's owner
* country with (`src/ui/ownerFlag.js`): every flag in OWNER_COUNTRY_CODES
* (`src/layers/satellites/classes.js`), drawn from the flag-icons 4:3 SVGs
* at twice the readout's 20 × 15 px, in one PNG the app loads at startup.
*
* src/data/local_data/owner_flags/owner_flags.png the sheet
* src/ui/ownerFlagSheet.js its layout (generated)
*
* Flag i (in the sorted code list) sits at column i % COLUMNS, row
* floor(i / COLUMNS). A rerun with the same flag-icons and sharp versions
* reproduces both files.
*
* Usage: node scripts/build-owner-flags.mjs
*/
import { readFile, writeFile } from 'node:fs/promises';
import { createRequire } from 'node:module';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import * as prettier from 'prettier';
import sharp from 'sharp';
import { OWNER_COUNTRY_CODES } from '../src/layers/satellites/classes.js';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const require = createRequire(import.meta.url);
const FLAGS_DIR = path.join(
path.dirname(require.resolve('flag-icons/package.json')),
'flags/4x3',
);
const FLAG_ICONS_VERSION = require('flag-icons/package.json').version;
/** One flag's cell, CSS pixels × 2. */
export const CELL = Object.freeze({ width: 40, height: 30 });
export const COLUMNS = 16;
const SHEET = path.join(
ROOT,
'src/data/local_data/owner_flags/owner_flags.png',
);
const LAYOUT = path.join(ROOT, 'src/ui/ownerFlagSheet.js');
async function flagCell(code) {
const svg = await readFile(path.join(FLAGS_DIR, `${code.toLowerCase()}.svg`));
return sharp(svg, { density: 300 })
.resize(CELL.width, CELL.height, { fit: 'fill' })
.png()
.toBuffer();
}
const codes = [...OWNER_COUNTRY_CODES];
const rows = Math.ceil(codes.length / COLUMNS);
const cells = await Promise.all(codes.map(flagCell));
const sheet = await sharp({
create: {
width: COLUMNS * CELL.width,
height: rows * CELL.height,
channels: 4,
background: { r: 0, g: 0, b: 0, alpha: 0 },
},
})
.composite(
cells.map((input, i) => ({
input,
left: (i % COLUMNS) * CELL.width,
top: Math.floor(i / COLUMNS) * CELL.height,
})),
)
.png({ compressionLevel: 9, palette: true, quality: 100, effort: 10 })
.toBuffer();
await writeFile(SHEET, sheet);
const layout = `// Generated by scripts/build-owner-flags.mjs from flag-icons ${FLAG_ICONS_VERSION}; do not edit.
/** The owner flag sheet's layout: flag i at column i % columns, row i / columns. */
export const OWNER_FLAG_SHEET = Object.freeze({
cellWidthPx: ${CELL.width},
cellHeightPx: ${CELL.height},
columns: ${COLUMNS},
rows: ${rows},
codes: Object.freeze(${JSON.stringify(codes)}),
});
`;
await writeFile(
LAYOUT,
await prettier.format(layout, {
...(await prettier.resolveConfig(LAYOUT)),
filepath: LAYOUT,
}),
);
console.log(
`Wrote ${codes.length} flags (${COLUMNS} × ${rows}), ${(sheet.length / 1024).toFixed(1)} KB.`,
);
+1 -8
View File
@@ -109,14 +109,7 @@ export async function checkPackageBoundaries(root) {
// template blocks belong to the component file itself.
if (info.id.startsWith('\0')) return;
const id = info.id.split('?')[0];
// A declared dependency also owns the asset files it ships (a
// flag-icons SVG imported as a URL) that a glob resolves in place.
const dependencyAsset = group.external.some((external) =>
id.startsWith(
`${normalizePath(path.join(root, 'node_modules', external))}/`,
),
);
if (!allowed.has(id) && !dependencyAsset) {
if (!allowed.has(id)) {
throw new Error(
`Package boundary ${name} imports an unowned module: ${path.relative(root, id)}`,
);
+4 -1
View File
@@ -1363,5 +1363,8 @@
"src/layers/wildlifeSightings/cache.integration.test.mjs",
"src/layers/wildlifeSightings/density.test.mjs",
"src/layers/wildlifeSightings/selection.test.mjs",
"server/providers/inaturalist/proxy.fixture.mjs"
"server/providers/inaturalist/proxy.fixture.mjs",
"scripts/build-owner-flags.mjs",
"src/ui/ownerFlagSheet.js",
"src/data/placePacksLoading.test.mjs"
]
+6 -1
View File
@@ -42,6 +42,7 @@
"src/ui/views/panels/CockpitRadar.vue",
"src/ui/views/panels/CockpitOrbital.vue",
"src/ui/ownerFlag.js",
"src/ui/ownerFlagSheet.js",
"src/ui/cockpitPresentation.js",
"src/data/headingDegrees.js",
"src/data/distanceFormat.js",
@@ -164,7 +165,7 @@
"src/ui/views/panels/WorldTimeline.vue",
"src/ui/surfaceKeyboard.js"
],
"external": ["vue", "pinia", "vue-i18n", "flag-icons"]
"external": ["vue", "pinia", "vue-i18n"]
},
"time": {
"exports": ["./time"],
@@ -1068,6 +1069,8 @@
"src/cockpitOrbitalSatellites.js",
"src/layers/installations/classLabel.js",
"src/data/placeBelow.js",
"src/ui/ownerFlag.js",
"src/ui/ownerFlagSheet.js",
"src/layers/satellites/sensors.js",
"src/data/adminBoundaries.js",
"src/data/bundledJson.js",
@@ -2913,6 +2916,8 @@
"src/ui/cockpitOrbitalSatellites.js",
"src/cockpitOrbitalSatellites.js",
"src/data/placeBelow.js",
"src/ui/ownerFlag.js",
"src/ui/ownerFlagSheet.js",
"src/ui/cockpitContext.js",
"src/ui/cockpitController.js",
"src/ui/cockpitDisplayPortal.js",