mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
fix(panels): a collapsed window keeps its chosen height; handle early tile failures
- A floating portable panel saved its measured box, so dragging a collapsed window stored its 50 px header strip and a reload clamped it to the minimum. It now saves the inline size the user chose. A unit test and a collapse -> drag -> reload -> expand step in the panel gate pin it. - Coverage awaits the tile fetch and the one-time terrain table load together, so a tile that fails before the table loads is reported at once instead of surfacing as an unhandled rejection. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
1fa129ea8e
commit
f33cd54e2b
5 files changed
+136
-10
No files matched your search
@@ -356,6 +356,61 @@ async function main() {
|
|||||||
near(restored[key], record[key], 2, `restored ${key}`);
|
near(restored[key], record[key], 2, `restored ${key}`);
|
||||||
console.log('PASS: reload restores position and size');
|
console.log('PASS: reload restores position and size');
|
||||||
|
|
||||||
|
// A collapsed window keeps the height chosen for it, through a drag of
|
||||||
|
// its header strip and a reload.
|
||||||
|
const setCollapsed = async (collapsed) => {
|
||||||
|
await page.evaluate(
|
||||||
|
(id, want) => {
|
||||||
|
const panel = document.getElementById(id);
|
||||||
|
if (panel.classList.contains('collapsed') !== want)
|
||||||
|
document
|
||||||
|
.querySelector(
|
||||||
|
`.panel-collapse-btn[data-collapse-target="${id}"]`,
|
||||||
|
)
|
||||||
|
.click();
|
||||||
|
},
|
||||||
|
PANEL_ID,
|
||||||
|
collapsed,
|
||||||
|
);
|
||||||
|
await page.waitForFunction(
|
||||||
|
(id, want) =>
|
||||||
|
document.getElementById(id).classList.contains('collapsed') === want,
|
||||||
|
{},
|
||||||
|
PANEL_ID,
|
||||||
|
collapsed,
|
||||||
|
);
|
||||||
|
await nextFrames(page);
|
||||||
|
};
|
||||||
|
await drag(handlePoint(await readPanel(), 'se'), 0, 200);
|
||||||
|
const chosen = await readPanel();
|
||||||
|
assert.ok(
|
||||||
|
chosen.height > MIN_SIZE.height + 150,
|
||||||
|
`the window grew to ${chosen.height}px`,
|
||||||
|
);
|
||||||
|
await setCollapsed(true);
|
||||||
|
const strip = await readPanel();
|
||||||
|
assert.ok(strip.height < chosen.height - 100, 'collapsed to its header');
|
||||||
|
await drag(await headerPoint(), 40, 30);
|
||||||
|
const movedStrip = await readPanel();
|
||||||
|
assert.equal(movedStrip.floating, true, 'the strip stays a window');
|
||||||
|
near(
|
||||||
|
JSON.parse(movedStrip.stored).height,
|
||||||
|
chosen.height,
|
||||||
|
2,
|
||||||
|
'stored height while collapsed',
|
||||||
|
);
|
||||||
|
await boot();
|
||||||
|
await setCollapsed(false);
|
||||||
|
near(
|
||||||
|
(await readPanel()).height,
|
||||||
|
chosen.height,
|
||||||
|
2,
|
||||||
|
'height after expanding',
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
'PASS: a collapsed window keeps its height through a drag and a reload',
|
||||||
|
);
|
||||||
|
|
||||||
const dock = await dockPanelByDoubleClick(page, PANEL_ID);
|
const dock = await dockPanelByDoubleClick(page, PANEL_ID);
|
||||||
const snapped = await readPanel();
|
const snapped = await readPanel();
|
||||||
assert.equal(
|
assert.equal(
|
||||||
|
|||||||
@@ -510,12 +510,14 @@ export function createCoverage({ state, source }) {
|
|||||||
state.coverage.pending.set(key, controller);
|
state.coverage.pending.set(key, controller);
|
||||||
notify();
|
notify();
|
||||||
try {
|
try {
|
||||||
// Fetch and the one-time terrain-height table load run side by side.
|
// Fetch and the one-time terrain-height table load run side by side,
|
||||||
const fetching = source.getTile('coverage', tile.z, tile.x, tile.y, {
|
// both handled from the start so an early tile failure is not unhandled.
|
||||||
signal: controller.signal,
|
const [bytes] = await Promise.all([
|
||||||
});
|
source.getTile('coverage', tile.z, tile.x, tile.y, {
|
||||||
if (kind === 'sequence') await ensureTerrainReady();
|
signal: controller.signal,
|
||||||
const bytes = await fetching;
|
}),
|
||||||
|
kind === 'sequence' ? ensureTerrainReady() : null,
|
||||||
|
]);
|
||||||
// Only an abort, a retire, a clear or a newer request for this tile
|
// Only an abort, a retire, a clear or a newer request for this tile
|
||||||
// discards the bytes; a refresh that still wants the tile keeps them.
|
// discards the bytes; a refresh that still wants the tile keeps them.
|
||||||
const current = () =>
|
const current = () =>
|
||||||
|
|||||||
@@ -906,3 +906,27 @@ test('a line across the date line from the camera is in mesh range', () => {
|
|||||||
const far = { west: -179.99, east: -179.97, south: -0.001, north: 0.001 };
|
const far = { west: -179.99, east: -179.97, south: -0.001, north: 0.001 };
|
||||||
assert.equal(meshBoxInRange(far, camera), false);
|
assert.equal(meshBoxInRange(far, camera), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a tile that fails before the terrain table loads is reported at once, never unhandled', async () => {
|
||||||
|
const { source, state, coverage, tileKey } = setup();
|
||||||
|
let terrainLoaded;
|
||||||
|
state.coverage.terrainReady = new Promise((resolve) => {
|
||||||
|
terrainLoaded = resolve;
|
||||||
|
});
|
||||||
|
const unhandled = [];
|
||||||
|
const onUnhandled = (reason) => unhandled.push(reason);
|
||||||
|
process.on('unhandledRejection', onUnhandled);
|
||||||
|
try {
|
||||||
|
coverage.refresh();
|
||||||
|
source.calls
|
||||||
|
.find((call) => call.key === tileKey)
|
||||||
|
.reject(new Error('Mapillary tiles HTTP 500'));
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||||
|
assert.equal(state.coverage.lastError, 'Mapillary tiles HTTP 500');
|
||||||
|
assert.deepEqual(unhandled, []);
|
||||||
|
} finally {
|
||||||
|
terrainLoaded();
|
||||||
|
await settle();
|
||||||
|
process.off('unhandledRejection', onUnhandled);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -540,3 +540,41 @@ test('a floating header never lands under the command dock or voice pill', () =>
|
|||||||
f.restore();
|
f.restore();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a collapsed floating window keeps its chosen height through a drag and a reload', () => {
|
||||||
|
const f = fixture();
|
||||||
|
try {
|
||||||
|
const { panel, handle } = floatingPanel(f, 'cctv-panel');
|
||||||
|
panel.style.height = '500px';
|
||||||
|
panel.classList.add('collapsed');
|
||||||
|
// Collapsed, the window measures only its header strip.
|
||||||
|
panel.getBoundingClientRect = () => {
|
||||||
|
const left = parseFloat(panel.style.left);
|
||||||
|
const top = parseFloat(panel.style.top);
|
||||||
|
return {
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
width: 360,
|
||||||
|
height: 50,
|
||||||
|
right: left + 360,
|
||||||
|
bottom: top + 50,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
pointer(handle, 'pointerdown', { x: 200, y: 90 });
|
||||||
|
pointer(window, 'pointermove', { x: 260, y: 140 });
|
||||||
|
pointer(window, 'pointerup', { x: 260, y: 140 });
|
||||||
|
const record = JSON.parse(
|
||||||
|
f.store.get('godsEyeView.v8.panelPos.cctv-panel'),
|
||||||
|
);
|
||||||
|
assert.equal(record.height, 500, 'the chosen height, not the strip');
|
||||||
|
assert.equal(record.width, 360);
|
||||||
|
|
||||||
|
// Reload: a fresh panel restores the size it expands to.
|
||||||
|
const reloaded = f.element();
|
||||||
|
reloaded.id = 'cctv-panel';
|
||||||
|
f.owner._restorePanelPosition('cctv-panel', reloaded);
|
||||||
|
assert.equal(reloaded.style.height, '500px');
|
||||||
|
} finally {
|
||||||
|
f.restore();
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -429,10 +429,17 @@ export class PanelPositionControls {
|
|||||||
top: Math.round(rect.top),
|
top: Math.round(rect.top),
|
||||||
};
|
};
|
||||||
if (this._portablePanels.has(panelId)) {
|
if (this._portablePanels.has(panelId)) {
|
||||||
// Only an explicitly set size is remembered: freezing a collapsed
|
// Only an explicitly set size is remembered, and the inline value
|
||||||
// panel's measured height would stop it from ever expanding again.
|
// rather than the measured box: a collapsed window measures only its
|
||||||
if (panelEl.style.width) record.width = Math.round(rect.width);
|
// header but keeps the size the user chose for when it expands.
|
||||||
if (panelEl.style.height) record.height = Math.round(rect.height);
|
const chosen = (inline, measured) => {
|
||||||
|
const px = parseFloat(inline);
|
||||||
|
return Math.round(Number.isFinite(px) ? px : measured);
|
||||||
|
};
|
||||||
|
if (panelEl.style.width)
|
||||||
|
record.width = chosen(panelEl.style.width, rect.width);
|
||||||
|
if (panelEl.style.height)
|
||||||
|
record.height = chosen(panelEl.style.height, rect.height);
|
||||||
record.floating = true;
|
record.floating = true;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in new issue
Block a user