mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix(street-level): one keydown listener on the photo root; format scope listed a test twice
Esc moved to the viewer's box (`.sl-viewer-wrap`) in the last commit, but without one it was a second keydown listener on the root; Esc is now taken by the root's own handler there. format-scope.json listed panorama.test.mjs twice, which made the formatter refuse the scope; the files it skipped are formatted. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
cf502429a3
commit
1a546bec72
14 files changed
+96
-30
No files matched your search
@@ -937,7 +937,6 @@
|
||||
"src/testSupport/panoramaxFixtures.mjs",
|
||||
"src/layers/streetLevel/photoViewer.test.mjs",
|
||||
"src/layers/streetLevel/limiter.test.mjs",
|
||||
"src/layers/streetLevel/panorama.test.mjs",
|
||||
"src/layers/streetLevel/lineCoverage.test.mjs",
|
||||
"src/testSupport/kartaViewFakes.mjs",
|
||||
"src/testSupport/googleStreetViewFakes.mjs",
|
||||
|
||||
@@ -1276,7 +1276,10 @@ async function main() {
|
||||
},
|
||||
)
|
||||
.catch(() => null);
|
||||
assert.ok(aimed, 'the line is pickable over the other’s pictures');
|
||||
assert.ok(
|
||||
aimed,
|
||||
'the line is pickable over the other’s pictures',
|
||||
);
|
||||
at = await aimed.jsonValue();
|
||||
}
|
||||
const parked = await camera();
|
||||
|
||||
@@ -70,7 +70,11 @@ test('a task already aborted when its turn comes is skipped', async () => {
|
||||
const gate = createLimiter(1);
|
||||
const first = held(log, 'a');
|
||||
const running = gate.run(first.task);
|
||||
const signal = { aborted: false, reason: new Error('gone'), addEventListener() {} };
|
||||
const signal = {
|
||||
aborted: false,
|
||||
reason: new Error('gone'),
|
||||
addEventListener() {},
|
||||
};
|
||||
const queued = gate.run(held(log, 'b').task, { signal });
|
||||
signal.aborted = true;
|
||||
first.finish();
|
||||
|
||||
@@ -217,17 +217,29 @@ test('overview points take the provider’s size, each its own', async (t) => {
|
||||
|
||||
test('a point budget skips lines past it, and a shorter one later may still fit', async (t) => {
|
||||
const { state, coverage } = setup(t, {
|
||||
tuning: { ...TUNING, maxTiles: 1, maxSequences: 10, maxPoints: 5, keepOrder: true },
|
||||
tuning: {
|
||||
...TUNING,
|
||||
maxTiles: 1,
|
||||
maxSequences: 10,
|
||||
maxPoints: 5,
|
||||
keepOrder: true,
|
||||
},
|
||||
decoded: (centre) => {
|
||||
const line = (id, n) => ({
|
||||
id,
|
||||
parts: [
|
||||
Array.from({ length: n }, (_, i) => [centre.lon - 0.001 + i * 0.0001, centre.lat]),
|
||||
Array.from({ length: n }, (_, i) => [
|
||||
centre.lon - 0.001 + i * 0.0001,
|
||||
centre.lat,
|
||||
]),
|
||||
],
|
||||
capturedAt: 0,
|
||||
isPano: false,
|
||||
});
|
||||
return { sequences: [line('three', 3), line('four', 4), line('two', 2)], overview: [] };
|
||||
return {
|
||||
sequences: [line('three', 3), line('four', 4), line('two', 2)],
|
||||
overview: [],
|
||||
};
|
||||
},
|
||||
});
|
||||
coverage.refresh();
|
||||
|
||||
@@ -178,6 +178,8 @@ export function createPhotoViewer({
|
||||
let documentOf = null;
|
||||
/** A context loss's look direction, for the reopen of that photo. */
|
||||
let restoreView = null;
|
||||
/** The panel's viewer box, where Esc is listened for (null: the root). */
|
||||
let escapeTarget = null;
|
||||
/** Width of the panorama texture on screen (0: the preview). */
|
||||
let panoWidth = 0;
|
||||
/** The sharper panorama on its way, for which open (resizes come in bursts). */
|
||||
@@ -450,6 +452,7 @@ export function createPhotoViewer({
|
||||
}
|
||||
|
||||
function onKeyDown(event) {
|
||||
if (!escapeTarget) onEscapeKey(event);
|
||||
if (
|
||||
event.defaultPrevented ||
|
||||
event.altKey ||
|
||||
@@ -528,7 +531,10 @@ export function createPhotoViewer({
|
||||
root.append(picture, canvas, prevButton, nextButton);
|
||||
host.append(root);
|
||||
listen(root, 'keydown', onKeyDown);
|
||||
listen(host.closest?.('.sl-viewer-wrap') ?? root, 'keydown', onEscapeKey);
|
||||
// The viewer's box in the panel; without one, the root's own handler
|
||||
// takes Esc (one listener per element and type).
|
||||
escapeTarget = host.closest?.('.sl-viewer-wrap') ?? null;
|
||||
if (escapeTarget) listen(escapeTarget, 'keydown', onEscapeKey);
|
||||
applyRenderMode();
|
||||
}
|
||||
|
||||
@@ -564,9 +570,7 @@ export function createPhotoViewer({
|
||||
if (size[0] <= panoWidth) return;
|
||||
if (panoComing?.seq === seq && size[0] <= panoComing.width) return;
|
||||
const sharp = sources.panoSharp(target, size, {
|
||||
expanded: Boolean(
|
||||
root?.closest?.('.sl-viewer-wrap-expanded'),
|
||||
),
|
||||
expanded: Boolean(root?.closest?.('.sl-viewer-wrap-expanded')),
|
||||
});
|
||||
if (!sharp?.url) return;
|
||||
const coming = { seq, width: size[0], controller: new AbortController() };
|
||||
|
||||
@@ -178,7 +178,9 @@ test('a sharp panorama still downloading when another photo opens is aborted', a
|
||||
url.endsWith('-sharp')
|
||||
? new Promise((_, reject) => {
|
||||
signals.push(options.signal);
|
||||
options.signal?.addEventListener('abort', () => reject(new Error('aborted')));
|
||||
options.signal?.addEventListener('abort', () =>
|
||||
reject(new Error('aborted')),
|
||||
);
|
||||
})
|
||||
: Promise.resolve({ src: url }),
|
||||
createRenderer: () => ({
|
||||
@@ -278,9 +280,8 @@ test('docked, Esc anywhere in the viewer’s box (toolbar, caption) is offered t
|
||||
});
|
||||
|
||||
test('a picture file or bitmap past any real photo’s size is refused', async (t) => {
|
||||
const { loadImage, MAX_IMAGE_BYTES, MAX_IMAGE_SIDE } = await import(
|
||||
'./photoViewer.js'
|
||||
);
|
||||
const { loadImage, MAX_IMAGE_BYTES, MAX_IMAGE_SIDE } =
|
||||
await import('./photoViewer.js');
|
||||
const saved = {
|
||||
fetch: globalThis.fetch,
|
||||
createImageBitmap: globalThis.createImageBitmap,
|
||||
|
||||
@@ -78,8 +78,7 @@ export function decodeCoverageTile(bytes, address) {
|
||||
const props = feature.properties || {};
|
||||
const parts = [];
|
||||
let length = 0;
|
||||
const tolerance =
|
||||
(SIMPLIFY_TOLERANCE_UNITS * sequences.extent) / 4096;
|
||||
const tolerance = (SIMPLIFY_TOLERANCE_UNITS * sequences.extent) / 4096;
|
||||
for (const line of feature.loadGeometry()) {
|
||||
if (line.length < 2) continue;
|
||||
for (let k = 1; k < line.length; k++)
|
||||
|
||||
@@ -191,8 +191,15 @@ test('a z15 tile’s pictures of one sequence become cones; other sequences’ a
|
||||
});
|
||||
|
||||
test('lines are simplified within a few tile units, ends and corners kept', () => {
|
||||
const zigzag = Array.from({ length: 1000 }, (_, i) => ({ x: i, y: i % 2 ? 1 : 0 }));
|
||||
assert.deepEqual(simplifyLine(zigzag, 4), [zigzag[0], zigzag.at(-1)], 'a 1-unit wobble goes');
|
||||
const zigzag = Array.from({ length: 1000 }, (_, i) => ({
|
||||
x: i,
|
||||
y: i % 2 ? 1 : 0,
|
||||
}));
|
||||
assert.deepEqual(
|
||||
simplifyLine(zigzag, 4),
|
||||
[zigzag[0], zigzag.at(-1)],
|
||||
'a 1-unit wobble goes',
|
||||
);
|
||||
const corner = [
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 50, y: 1 },
|
||||
@@ -221,6 +228,9 @@ test('a decoded GPS track keeps a fraction of its points', () => {
|
||||
encodePanoramaxTile(tile, { sequences: [{ id: 'gps', parts: [track] }] }),
|
||||
tile,
|
||||
).sequences;
|
||||
assert.ok(sequence.parts[0].length < 100, `${sequence.parts[0].length} points`);
|
||||
assert.ok(
|
||||
sequence.parts[0].length < 100,
|
||||
`${sequence.parts[0].length} points`,
|
||||
);
|
||||
assert.ok(sequence.parts[0].length > 5);
|
||||
});
|
||||
@@ -227,7 +227,12 @@ test('hostile item data: a malformed link escape, null coordinates, a huge produ
|
||||
});
|
||||
|
||||
test('the instance holding a picture is named (its via host), never as its author', () => {
|
||||
const item = stacItem({ id: 'a', lon: 1, lat: 2, producer: '[email protected]' });
|
||||
const item = stacItem({
|
||||
id: 'a',
|
||||
lon: 1,
|
||||
lat: 2,
|
||||
producer: '[email protected]',
|
||||
});
|
||||
item.links.push({ rel: 'via', href: 'https://panoramax.ign.fr' });
|
||||
assert.equal(normalizeItem(item).creator, null);
|
||||
assert.equal(normalizeItem(item).via, 'panoramax.ign.fr');
|
||||
|
||||
@@ -543,7 +543,12 @@ test('a broken neighbour z15 tile only thins the cones; an unreadable home tile
|
||||
const box = tileBounds(home.tile.x, home.tile.y, 15);
|
||||
const at = { lon: box.west + 0.0003, lat: box.north - 0.0002 };
|
||||
const items = [0, 1, 2].map((i) =>
|
||||
stacItem({ id: `c-${i}`, sequenceId: 'c', lon: at.lon + i * 0.00005, lat: at.lat }),
|
||||
stacItem({
|
||||
id: `c-${i}`,
|
||||
sequenceId: 'c',
|
||||
lon: at.lon + i * 0.00005,
|
||||
lat: at.lat,
|
||||
}),
|
||||
);
|
||||
const tiles = pictureTiles(items);
|
||||
const broken = new TextEncoder().encode('<html>oops</html>');
|
||||
@@ -835,7 +840,10 @@ test('a search hit outside the radius (a bounding box’s corner) is not the nea
|
||||
});
|
||||
|
||||
test('a line clicked from low down still searches its own sequence within 50 m', async () => {
|
||||
const items = [...sequence('other', 1, { offset: 2 }), ...sequence('clicked', 1, { offset: 30 })];
|
||||
const items = [
|
||||
...sequence('other', 1, { offset: 2 }),
|
||||
...sequence('clicked', 1, { offset: 30 }),
|
||||
];
|
||||
const { instance, source } = setup({ items });
|
||||
instance.init({
|
||||
camera: { positionCartographic: { height: 300 } },
|
||||
@@ -885,7 +893,9 @@ test('a filter that hides every density dot says so', async (t) => {
|
||||
grid: [{ lon: ORIGIN.lon, lat: ORIGIN.lat, pano: 0, flat: 5 }],
|
||||
});
|
||||
env.instance.activate(env.viewer);
|
||||
assert.ok(await until(() => env.instance.coverageStats().hint === FILTERED_HINT));
|
||||
assert.ok(
|
||||
await until(() => env.instance.coverageStats().hint === FILTERED_HINT),
|
||||
);
|
||||
});
|
||||
|
||||
test('dots are drawn at their own sizes, and the busiest is the biggest', async (t) => {
|
||||
|
||||
@@ -338,7 +338,9 @@ test('a Retry-After is honoured up to five minutes', async () => {
|
||||
headers: { 'retry-after': header },
|
||||
})).fetchImpl,
|
||||
});
|
||||
return source.getTile({ x: 0, y: 0, z: 0 }).catch((error) => error.retryAfterSec);
|
||||
return source
|
||||
.getTile({ x: 0, y: 0, z: 0 })
|
||||
.catch((error) => error.retryAfterSec);
|
||||
};
|
||||
assert.equal(await wait('3600'), 300);
|
||||
assert.equal(await wait('99999999999'), 300);
|
||||
|
||||
@@ -179,10 +179,19 @@ test('a 360° picture shows its SD equirectangular image, then HD shrunk to 4,09
|
||||
test('HD panoramas too big to decode, or no bigger than SD, are never fetched; nor while docked', () => {
|
||||
const size = [4096, 2048];
|
||||
const expanded = { expanded: true };
|
||||
assert.equal(PANORAMAX_SOURCES.panoSharp(PHOTOS.pano, size, { expanded: false }), null);
|
||||
assert.equal(
|
||||
PANORAMAX_SOURCES.panoSharp(PHOTOS.pano, size, { expanded: false }),
|
||||
null,
|
||||
);
|
||||
assert.equal(PANORAMAX_SOURCES.panoSharp(PHOTOS.pano, size), null);
|
||||
assert.equal(PANORAMAX_SOURCES.panoSharp(PHOTOS.bigPano, size, expanded), null);
|
||||
assert.equal(PANORAMAX_SOURCES.panoSharp(PHOTOS.smallPano, size, expanded), null);
|
||||
assert.equal(
|
||||
PANORAMAX_SOURCES.panoSharp(PHOTOS.bigPano, size, expanded),
|
||||
null,
|
||||
);
|
||||
assert.equal(
|
||||
PANORAMAX_SOURCES.panoSharp(PHOTOS.smallPano, size, expanded),
|
||||
null,
|
||||
);
|
||||
assert.deepEqual(
|
||||
PANORAMAX_SOURCES.panoSharp({ ...PHOTOS.pano, width: 3000 }, size, expanded)
|
||||
.resize,
|
||||
|
||||
@@ -816,12 +816,17 @@ test('Panoramax fixtures: two sequences on one street, searches honour sequence
|
||||
const boxed = ask(
|
||||
`bbox=${at.lon - d},${at.lat - d},${at.lon + d},${at.lat + d}&limit=100`,
|
||||
);
|
||||
assert.ok(boxed.some((p) => p.lon < at.lon) && boxed.some((p) => p.lon > at.lon));
|
||||
assert.ok(
|
||||
boxed.some((p) => p.lon < at.lon) && boxed.some((p) => p.lon > at.lon),
|
||||
);
|
||||
const placed = ask(
|
||||
`place_position=${at.lon},${at.lat}&place_distance=0-30&limit=50`,
|
||||
);
|
||||
assert.ok(placed.length > 0);
|
||||
assert.ok(placed.every((p) => p.lon <= at.lon + 1e-7), 'none it is behind');
|
||||
assert.ok(
|
||||
placed.every((p) => p.lon <= at.lon + 1e-7),
|
||||
'none it is behind',
|
||||
);
|
||||
const one = ask(`ids=${at.id}&limit=1`);
|
||||
assert.equal(one[0].id, at.id);
|
||||
// Its pictures, and only those, are served; HD downloads are counted.
|
||||
|
||||
@@ -555,6 +555,9 @@ test('the caption names an image’s own licence after its author, and leaves it
|
||||
caption({ creator: 'Fixture Mapper', license: 'CC BY-SA 4.0' }),
|
||||
'Image by Fixture Mapper · CC BY-SA 4.0',
|
||||
);
|
||||
assert.equal(caption({ license: 'Licence Ouverte 2.0' }), 'Licence Ouverte 2.0');
|
||||
assert.equal(
|
||||
caption({ license: 'Licence Ouverte 2.0' }),
|
||||
'Licence Ouverte 2.0',
|
||||
);
|
||||
assert.equal(caption({ creator: 'Someone' }), 'Image by Someone');
|
||||
});
|
||||
Reference in new issue
Block a user