This commit is contained in:
Andras Schmelczer 2026-05-13 12:12:11 +01:00
parent b98f0e3904
commit a8165249a4
24 changed files with 1486 additions and 105 deletions

View file

@ -105,17 +105,18 @@ export class DashboardRecorder {
const mapBox = await this.page.locator('[data-tutorial="map"]').boundingBox();
if (!mapBox) throw new Error('Map container has no bounding box');
const clear = await this.clickableBox(mapBox);
const projected = snapshot.features
.filter((feature) => feature.count > 0)
.map((feature) => {
const point = projectFromBounds(feature, snapshot.bounds, mapBox);
if (!point) return null;
const centerX = mapBox.x + mapBox.width / 2;
const centerY = mapBox.y + mapBox.height / 2;
const centerX = clear.left + (clear.right - clear.left) / 2;
const centerY = clear.top + (clear.bottom - clear.top) / 2;
const distanceFromCenter = Math.hypot(
(point.x - centerX) / (mapBox.width / 2),
(point.y - centerY) / (mapBox.height / 2)
(point.x - centerX) / Math.max(1, (clear.right - clear.left) / 2),
(point.y - centerY) / Math.max(1, (clear.bottom - clear.top) / 2)
);
return {
h3: feature.h3,
@ -136,10 +137,10 @@ export class DashboardRecorder {
);
const clearOfChrome = onScreen.filter(
(target) =>
target.x >= mapBox.x + 80 &&
target.x <= mapBox.x + mapBox.width - 130 &&
target.y >= mapBox.y + 105 &&
target.y <= mapBox.y + mapBox.height - 115
target.x >= clear.left &&
target.x <= clear.right &&
target.y >= clear.top &&
target.y <= clear.bottom
);
const candidates = (clearOfChrome.length > 0 ? clearOfChrome : onScreen).sort(
@ -151,6 +152,37 @@ export class DashboardRecorder {
return candidates.slice(0, limit).map(({ score: _score, ...target }) => target);
}
/**
* The pixel rect inside `mapBox` that's safe to click i.e. not under
* the dashboard's left filters pane, right details pane, or (on mobile)
* the floating MobileBottomSheet. We detect the sheet via the only
* `section.rounded-t-2xl` in the DOM and treat its top as a hard
* bottom-clear limit; without that, hex() would happily return a
* polygon hidden under the sheet on 9x16 cuts.
*/
private async clickableBox(mapBox: {
x: number;
y: number;
width: number;
height: number;
}): Promise<{ top: number; bottom: number; left: number; right: number }> {
let bottomClear = mapBox.y + mapBox.height - 115;
const sheet = await this.page
.locator('section[class*="rounded-t-2xl"]')
.first()
.boundingBox()
.catch(() => null);
if (sheet && sheet.y > mapBox.y && sheet.y < bottomClear) {
bottomClear = sheet.y - 16;
}
return {
top: mapBox.y + 105,
bottom: bottomClear,
left: mapBox.x + 80,
right: mapBox.x + mapBox.width - 130,
};
}
private async captureResponse(response: Response): Promise<void> {
const kind = classifyApiRequest(response.url());
if (!kind || !response.ok()) return;
@ -249,6 +281,7 @@ export class DashboardRecorder {
const mapBox = await this.page.locator('[data-tutorial="map"]').boundingBox();
if (!mapBox) throw new Error('Map container has no bounding box');
const clear = await this.clickableBox(mapBox);
const projected = snapshot.features
.filter((feature) => feature.properties.count > 0)
@ -256,11 +289,11 @@ export class DashboardRecorder {
const [lon, lat] = feature.properties.centroid;
const point = projectFromBounds({ lat, lon }, snapshot.bounds, mapBox);
if (!point) return null;
const centerX = mapBox.x + mapBox.width / 2;
const centerY = mapBox.y + mapBox.height / 2;
const centerX = clear.left + (clear.right - clear.left) / 2;
const centerY = clear.top + (clear.bottom - clear.top) / 2;
const distanceFromCenter = Math.hypot(
(point.x - centerX) / (mapBox.width / 2),
(point.y - centerY) / (mapBox.height / 2)
(point.x - centerX) / Math.max(1, (clear.right - clear.left) / 2),
(point.y - centerY) / Math.max(1, (clear.bottom - clear.top) / 2)
);
return {
h3: feature.properties.postcode,
@ -281,10 +314,10 @@ export class DashboardRecorder {
);
const clearOfChrome = onScreen.filter(
(target) =>
target.x >= mapBox.x + 80 &&
target.x <= mapBox.x + mapBox.width - 130 &&
target.y >= mapBox.y + 105 &&
target.y <= mapBox.y + mapBox.height - 115
target.x >= clear.left &&
target.x <= clear.right &&
target.y >= clear.top &&
target.y <= clear.bottom
);
const candidates = (clearOfChrome.length > 0 ? clearOfChrome : onScreen).sort(