mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-06 11:24:00 +00:00
fix(street-level): keep cones on low ground, and gekh's P3s
From gekh's review of db79027, each with a test that fails before:
- P2: image cones and the photo marker no longer disappear where the
ground lies below the WGS84 ellipsoid (NYC -22 m, Colombo -95 m). The
horizon cull's occluder is WGS84 shrunk by 1 km (HORIZON_CULL_DEPTH_M),
below any real surface, so a camera near or under the ellipsoid no
longer treats every lower point as behind the horizon; the far side of
the Earth is still hidden from orbit (tested).
- Mesh range across the date line: the camera's longitude is taken to the
box's side of +-180 before clamping (meshBoxInRange), so a tile just
across the line is not measured from its far edge.
- MapillaryJS no longer tracks window resizes (trackResize: false): the
panel's ResizeObserver resizes the viewer and skips it while hidden, so
a window resize after closing a photo cannot ask for a tile at z=NaN.
- A key-less install sends no tile requests before the key status is
known: coverage waits for the first status answer and the provider
refreshes it once the status lands.
- constructCatalog takes the source method list from MAPILLARY_SOURCE_
METHODS; sequences.js imports view.js once; the provider's unused
re-exports are gone.
Verified: unit 5970 pass; strict fixture gate on a production build 28 ok
+ 2 "no Google 3D" skips and the panel gate 6/6 (QA_FAIL_ON_RETRY=1);
live gate with a real token 25 ok + 3 fixture-only skips.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
db79027998
commit
c65feda4b1
10 files changed
+208
-19
No files matched your search
@@ -3,6 +3,7 @@ import { createWeatherLayer } from '../layers/weather/index.js';
|
|||||||
import { createCyclonesLayer } from '../layers/cyclones/index.js';
|
import { createCyclonesLayer } from '../layers/cyclones/index.js';
|
||||||
import { createWindLayer } from '../layers/wind/index.js';
|
import { createWindLayer } from '../layers/wind/index.js';
|
||||||
import { createLayerCatalog } from './catalog.js';
|
import { createLayerCatalog } from './catalog.js';
|
||||||
|
import { MAPILLARY_SOURCE_METHODS } from '../layers/streetLevel/providers/mapillary/policy.js';
|
||||||
import { LAYER_STATE_REGISTRY } from '../data/layerState.js';
|
import { LAYER_STATE_REGISTRY } from '../data/layerState.js';
|
||||||
import { createMilitaryRegistry } from '../layers/aircraft/classification.js';
|
import { createMilitaryRegistry } from '../layers/aircraft/classification.js';
|
||||||
import { createApplicationFlights } from './layers/flights.js';
|
import { createApplicationFlights } from './layers/flights.js';
|
||||||
@@ -56,7 +57,7 @@ const SOURCE_METHODS = Object.freeze({
|
|||||||
earthquakes: ['getSnapshot'],
|
earthquakes: ['getSnapshot'],
|
||||||
'fire-perimeters': ['getSnapshot'],
|
'fire-perimeters': ['getSnapshot'],
|
||||||
cables: ['fetch'],
|
cables: ['fetch'],
|
||||||
mapillary: ['getStatus', 'getTile', 'getSequenceImages', 'nearestImages'],
|
mapillary: MAPILLARY_SOURCE_METHODS,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -95,6 +95,24 @@ export function sequenceIdFromPick(pickId) {
|
|||||||
* and tree tops. A tile is drawn draped first and swapped for its cast lines
|
* and tree tops. A tile is drawn draped first and swapped for its cast lines
|
||||||
* once the terrain heights are in, so coverage never waits on the terrain.
|
* once the terrain heights are in, so coverage never waits on the terrain.
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* Whether any point of a line's bounding `box` is within the mesh sampler's
|
||||||
|
* range of the camera ground point `centre`, measured the short way round
|
||||||
|
* the date line.
|
||||||
|
*/
|
||||||
|
export function meshBoxInRange(box, centre) {
|
||||||
|
// Take the camera's longitude to the box's side of the date line first:
|
||||||
|
// clamping 179.999 into a box at -179.99 would pick its far edge.
|
||||||
|
const lon =
|
||||||
|
centre.lon +
|
||||||
|
360 * Math.round(((box.west + box.east) / 2 - centre.lon) / 360);
|
||||||
|
const nearest = {
|
||||||
|
lon: Math.min(box.east, Math.max(box.west, lon)),
|
||||||
|
lat: Math.min(box.north, Math.max(box.south, centre.lat)),
|
||||||
|
};
|
||||||
|
return metresBetween(centre, nearest) <= MESH_SAMPLE_RADIUS_M;
|
||||||
|
}
|
||||||
|
|
||||||
export function createCoverage({ state, source }) {
|
export function createCoverage({ state, source }) {
|
||||||
const { render } = state.services;
|
const { render } = state.services;
|
||||||
|
|
||||||
@@ -268,13 +286,6 @@ export function createCoverage({ state, source }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Whether any of a {west, south, east, north} box is within the sampler's range. */
|
/** Whether any of a {west, south, east, north} box is within the sampler's range. */
|
||||||
function inMeshRange(box, centre) {
|
|
||||||
const nearest = {
|
|
||||||
lon: Math.min(box.east, Math.max(box.west, centre.lon)),
|
|
||||||
lat: Math.min(box.north, Math.max(box.south, centre.lat)),
|
|
||||||
};
|
|
||||||
return metresBetween(centre, nearest) <= MESH_SAMPLE_RADIUS_M;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A tile's drawn lines with their bounding boxes, built once per filter;
|
* A tile's drawn lines with their bounding boxes, built once per filter;
|
||||||
@@ -310,10 +321,11 @@ export function createCoverage({ state, source }) {
|
|||||||
const sampler = state.context.meshSampler;
|
const sampler = state.context.meshSampler;
|
||||||
if (!sampler || entry.kind !== 'sequence' || !terrainMode()) return;
|
if (!sampler || entry.kind !== 'sequence' || !terrainMode()) return;
|
||||||
const centre = meshCentre();
|
const centre = meshCentre();
|
||||||
if (!centre || !entry.bounds || !inMeshRange(entry.bounds, centre)) return;
|
if (!centre || !entry.bounds || !meshBoxInRange(entry.bounds, centre))
|
||||||
|
return;
|
||||||
const points = [];
|
const points = [];
|
||||||
for (const line of meshParts(entry)) {
|
for (const line of meshParts(entry)) {
|
||||||
if (!inMeshRange(line.box, centre)) continue;
|
if (!meshBoxInRange(line.box, centre)) continue;
|
||||||
line.points ||= densifyLine(line.part, MESH_DENSIFY_DEG);
|
line.points ||= densifyLine(line.part, MESH_DENSIFY_DEG);
|
||||||
for (const point of line.points) points.push(point);
|
for (const point of line.points) points.push(point);
|
||||||
}
|
}
|
||||||
@@ -668,6 +680,7 @@ export function createCoverage({ state, source }) {
|
|||||||
function refresh() {
|
function refresh() {
|
||||||
const viewer = state.viewer;
|
const viewer = state.viewer;
|
||||||
if (!viewer || !state.context.isActive() || state.keyRequired) return;
|
if (!viewer || !state.context.isActive() || state.keyRequired) return;
|
||||||
|
if (state.statusKnown === false) return;
|
||||||
if (state.keyRejected || state.coverage.holdUntil > Date.now()) return;
|
if (state.keyRejected || state.coverage.holdUntil > Date.now()) return;
|
||||||
const ground = groundUnderCamera(viewer, {
|
const ground = groundUnderCamera(viewer, {
|
||||||
groundAt: state.context.groundCaster?.groundAt,
|
groundAt: state.context.groundCaster?.groundAt,
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import * as Cesium from 'cesium';
|
import * as Cesium from 'cesium';
|
||||||
import { SWAP_MAX_WAIT_MS, createCoverage } from './coverage.js';
|
import {
|
||||||
|
SWAP_MAX_WAIT_MS,
|
||||||
|
createCoverage,
|
||||||
|
meshBoxInRange,
|
||||||
|
} from './coverage.js';
|
||||||
import { encodeCoverageTile } from './coverageFixture.mjs';
|
import { encodeCoverageTile } from './coverageFixture.mjs';
|
||||||
import { rayCamera } from '../../../../testSupport/streetLevelFakes.mjs';
|
import { rayCamera } from '../../../../testSupport/streetLevelFakes.mjs';
|
||||||
import {
|
import {
|
||||||
@@ -893,3 +897,16 @@ test('a rate limit holds every refresh inside its wait, not only the first (revi
|
|||||||
coverage.clear();
|
coverage.clear();
|
||||||
coverage.resetErrors();
|
coverage.resetErrors();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a line across the date line from the camera is in mesh range (gekh P3)', () => {
|
||||||
|
// A z14 tile's width of lines just past -180, the camera 111 m short of it.
|
||||||
|
const camera = { lon: 179.999, lat: 0 };
|
||||||
|
const east = { west: -180, east: -179.978, south: -0.001, north: 0.001 };
|
||||||
|
assert.equal(meshBoxInRange(east, camera), true);
|
||||||
|
// The mirror case, from the other side.
|
||||||
|
const west = { west: 179.978, east: 180, south: -0.001, north: 0.001 };
|
||||||
|
assert.equal(meshBoxInRange(west, { lon: -179.999, lat: 0 }), true);
|
||||||
|
// ~2.2 km away across the line: out of range either way.
|
||||||
|
const far = { west: -179.99, east: -179.97, south: -0.001, north: 0.001 };
|
||||||
|
assert.equal(meshBoxInRange(far, camera), false);
|
||||||
|
});
|
||||||
@@ -16,8 +16,6 @@ import {
|
|||||||
PICK_PREFIX,
|
PICK_PREFIX,
|
||||||
} from './policy.js';
|
} from './policy.js';
|
||||||
|
|
||||||
export { MAPILLARY_PROVIDER_ID, mapillaryImageUrl } from './policy.js';
|
|
||||||
|
|
||||||
/** Per-provider mutable state, created once per `create()`. */
|
/** Per-provider mutable state, created once per `create()`. */
|
||||||
function createProviderState(context) {
|
function createProviderState(context) {
|
||||||
return {
|
return {
|
||||||
@@ -25,6 +23,11 @@ function createProviderState(context) {
|
|||||||
services: context.services,
|
services: context.services,
|
||||||
viewer: null,
|
viewer: null,
|
||||||
keyRequired: false,
|
keyRequired: false,
|
||||||
|
/**
|
||||||
|
* False until the first status answer: coverage asks for nothing before
|
||||||
|
* it, so a key-less install never sends tile requests that get 503.
|
||||||
|
*/
|
||||||
|
statusKnown: false,
|
||||||
/** Mapillary refused the configured token: nothing is requested until re-enabled. */
|
/** Mapillary refused the configured token: nothing is requested until re-enabled. */
|
||||||
keyRejected: false,
|
keyRejected: false,
|
||||||
status: null,
|
status: null,
|
||||||
@@ -101,6 +104,10 @@ export function createMapillaryProvider({ source }) {
|
|||||||
state.status = null;
|
state.status = null;
|
||||||
state.keyRequired = !source.hasToken();
|
state.keyRequired = !source.hasToken();
|
||||||
}
|
}
|
||||||
|
const first = !state.statusKnown;
|
||||||
|
state.statusKnown = true;
|
||||||
|
// Coverage held back until now: draw it if the layer is on.
|
||||||
|
if (first) parts.coverage.refresh();
|
||||||
context.notify();
|
context.notify();
|
||||||
return { configured: !state.keyRequired };
|
return { configured: !state.keyRequired };
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import { createMapillaryProvider, mapillaryImageUrl } from './index.js';
|
import { createMapillaryProvider } from './index.js';
|
||||||
|
import { mapillaryImageUrl } from './policy.js';
|
||||||
import { sequenceIdFromPick } from './coverage.js';
|
import { sequenceIdFromPick } from './coverage.js';
|
||||||
import { thinImages } from './sequences.js';
|
import { thinImages } from './sequences.js';
|
||||||
import { validateProviders } from '../../registry.js';
|
import { validateProviders } from '../../registry.js';
|
||||||
import { PROVIDER_COLORS } from '../../policy.js';
|
import { PROVIDER_COLORS } from '../../policy.js';
|
||||||
import { resolveFilter } from '../../filter.js';
|
import { resolveFilter } from '../../filter.js';
|
||||||
|
import * as Cesium from 'cesium';
|
||||||
|
import { rayCamera } from '../../../../testSupport/streetLevelFakes.mjs';
|
||||||
|
|
||||||
function fakeSource({ nearest = [] } = {}) {
|
function fakeSource({ nearest = [] } = {}) {
|
||||||
return {
|
return {
|
||||||
@@ -180,3 +183,70 @@ test('cone thinning across the date line drops images metres apart (review IC8 P
|
|||||||
['a', 'c'],
|
['a', 'c'],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** A viewer looking straight down on downtown Sacramento from 900 m. */
|
||||||
|
function streetViewer() {
|
||||||
|
const collection = () => ({
|
||||||
|
add: (item) => item,
|
||||||
|
remove: () => true,
|
||||||
|
contains: () => false,
|
||||||
|
get length() {
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const event = () => ({ addEventListener: () => () => {} });
|
||||||
|
const camera = Object.assign(
|
||||||
|
rayCamera({
|
||||||
|
lon: -121.4944,
|
||||||
|
lat: 38.5816,
|
||||||
|
altitude: 900,
|
||||||
|
pitch: -90,
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
}),
|
||||||
|
{ changed: event(), moveEnd: event() },
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
camera,
|
||||||
|
scene: {
|
||||||
|
canvas: { clientWidth: 100, clientHeight: 100 },
|
||||||
|
globe: { show: false, ellipsoid: Cesium.Ellipsoid.WGS84 },
|
||||||
|
primitives: collection(),
|
||||||
|
groundPrimitives: collection(),
|
||||||
|
preRender: event(),
|
||||||
|
postRender: event(),
|
||||||
|
requestRender() {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test('coverage asks for no tiles before the key status is known (gekh P3)', async () => {
|
||||||
|
for (const configured of [false, true]) {
|
||||||
|
let answer;
|
||||||
|
const tiles = [];
|
||||||
|
const source = {
|
||||||
|
...fakeSource(),
|
||||||
|
hasToken: () => configured,
|
||||||
|
getStatus: () => new Promise((resolve) => (answer = resolve)),
|
||||||
|
getTile: (...args) => {
|
||||||
|
tiles.push(args);
|
||||||
|
return new Promise(() => {});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const instance = createMapillaryProvider({ source }).create(fakeContext());
|
||||||
|
const viewer = streetViewer();
|
||||||
|
instance.init(viewer);
|
||||||
|
const status = instance.status();
|
||||||
|
instance.activate(viewer);
|
||||||
|
assert.equal(tiles.length, 0, 'nothing before the status answers');
|
||||||
|
answer({ configured });
|
||||||
|
await status;
|
||||||
|
if (configured)
|
||||||
|
assert.ok(tiles.length > 0, 'coverage loads as soon as the key is known');
|
||||||
|
else {
|
||||||
|
assert.equal(tiles.length, 0, 'a key-less install never asks');
|
||||||
|
assert.equal(instance.coverageStats().keyRequired, true);
|
||||||
|
}
|
||||||
|
instance.destroy(viewer);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -2,9 +2,8 @@ import * as Cesium from 'cesium';
|
|||||||
import { imageConeGlyph } from '../../glyphs.js';
|
import { imageConeGlyph } from '../../glyphs.js';
|
||||||
import { passesImageryFilter } from '../../filter.js';
|
import { passesImageryFilter } from '../../filter.js';
|
||||||
import { refineHeights } from '../../groundCast.js';
|
import { refineHeights } from '../../groundCast.js';
|
||||||
import { createHorizonCull } from '../../view.js';
|
import { createHorizonCull, metresBetween } from '../../view.js';
|
||||||
import { meshCellKey } from '../../meshSampler.js';
|
import { meshCellKey } from '../../meshSampler.js';
|
||||||
import { metresBetween } from '../../view.js';
|
|
||||||
import {
|
import {
|
||||||
COLORS,
|
COLORS,
|
||||||
IMAGE_CONE_MIN_SPACING_M,
|
IMAGE_CONE_MIN_SPACING_M,
|
||||||
|
|||||||
@@ -124,7 +124,10 @@ export function createMapillaryViewer({ source, render } = {}) {
|
|||||||
zoom: true,
|
zoom: true,
|
||||||
attribution: true,
|
attribution: true,
|
||||||
},
|
},
|
||||||
trackResize: true,
|
// The panel's ResizeObserver resizes the viewer, and skips it while it
|
||||||
|
// is hidden (0x0); MapillaryJS's own window tracking would resize a
|
||||||
|
// hidden viewer and ask for a tile at z=NaN.
|
||||||
|
trackResize: false,
|
||||||
renderMode: libraryRenderMode(renderMode),
|
renderMode: libraryRenderMode(renderMode),
|
||||||
});
|
});
|
||||||
container = host;
|
container = host;
|
||||||
|
|||||||
@@ -251,6 +251,14 @@ export function viewFocus(
|
|||||||
return { nadir, ahead };
|
return { nadir, ahead };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** How far below WGS84 the horizon cull's occluder sits, in metres. */
|
||||||
|
export const HORIZON_CULL_DEPTH_M = 1000;
|
||||||
|
const HORIZON_CULL_ELLIPSOID = new Cesium.Ellipsoid(
|
||||||
|
Cesium.Ellipsoid.WGS84.radii.x - HORIZON_CULL_DEPTH_M,
|
||||||
|
Cesium.Ellipsoid.WGS84.radii.y - HORIZON_CULL_DEPTH_M,
|
||||||
|
Cesium.Ellipsoid.WGS84.radii.z - HORIZON_CULL_DEPTH_M,
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide billboards and points behind the horizon, as the cyclones layer does.
|
* Hide billboards and points behind the horizon, as the cyclones layer does.
|
||||||
* Street Level's skip the depth test, so Google 3D and clouds never hide the
|
* Street Level's skip the depth test, so Google 3D and clouds never hide the
|
||||||
@@ -258,10 +266,16 @@ export function viewFocus(
|
|||||||
* the globe, and a finite skip distance cannot, as the horizon's distance
|
* the globe, and a finite skip distance cannot, as the horizon's distance
|
||||||
* moves with the camera. Re-culls on pre-render only once the camera moved,
|
* moves with the camera. Re-culls on pre-render only once the camera moved,
|
||||||
* and stops listening when there is nothing left to cull.
|
* and stops listening when there is nothing left to cull.
|
||||||
|
*
|
||||||
|
* The occluder is the WGS84 ellipsoid shrunk by HORIZON_CULL_DEPTH_M, below
|
||||||
|
* any real surface: on the ellipsoid itself a camera standing on ground that
|
||||||
|
* lies below it (NYC −22 m, Colombo −95 m) would see every lower point as
|
||||||
|
* behind the horizon and hide the cones around it. A kilometre changes
|
||||||
|
* nothing for the far side of the Earth seen from orbit.
|
||||||
* @param {{getViewer: () => object|null, items: () => Iterable<{position: object, show: boolean}>, onChange?: () => void}} options
|
* @param {{getViewer: () => object|null, items: () => Iterable<{position: object, show: boolean}>, onChange?: () => void}} options
|
||||||
*/
|
*/
|
||||||
export function createHorizonCull({ getViewer, items, onChange }) {
|
export function createHorizonCull({ getViewer, items, onChange }) {
|
||||||
const occluder = new Cesium.EllipsoidalOccluder(Cesium.Ellipsoid.WGS84);
|
const occluder = new Cesium.EllipsoidalOccluder(HORIZON_CULL_ELLIPSOID);
|
||||||
let from = null;
|
let from = null;
|
||||||
let stopListening = null;
|
let stopListening = null;
|
||||||
|
|
||||||
|
|||||||
@@ -265,3 +265,58 @@ test('the horizon cull stops listening once nothing is left, and starts again fo
|
|||||||
assert.equal(h.changes(), 2, 'a redraw for each change');
|
assert.equal(h.changes(), 2, 'a redraw for each change');
|
||||||
h.cull.stop();
|
h.cull.stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/** A cull over `points` ([lon, lat, height]) seen from a camera at `camera`. */
|
||||||
|
function cullFrom(camera, points) {
|
||||||
|
const items = points.map(([lon, lat, height]) => ({
|
||||||
|
position: Cesium.Cartesian3.fromDegrees(lon, lat, height),
|
||||||
|
show: true,
|
||||||
|
}));
|
||||||
|
const horizon = createHorizonCull({
|
||||||
|
getViewer: () => ({
|
||||||
|
camera: { positionWC: Cesium.Cartesian3.fromDegrees(...camera) },
|
||||||
|
scene: { preRender: { addEventListener: () => () => {} } },
|
||||||
|
}),
|
||||||
|
items: () => items,
|
||||||
|
});
|
||||||
|
horizon.update();
|
||||||
|
horizon.stop();
|
||||||
|
return items.map((item) => item.show);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('cones and the marker stay visible on ground below the WGS84 ellipsoid (gekh P2)', () => {
|
||||||
|
// NYC in FOLLOW: eye 2.4 m above ground at -22 m, a cone 10 m away.
|
||||||
|
assert.deepEqual(
|
||||||
|
cullFrom([-74.006, 40.7128, -19.6], [[-74.006, 40.71289, -22]]),
|
||||||
|
[true],
|
||||||
|
);
|
||||||
|
// Colombo framing view: camera at -21 m, cones at -95 m about 80 m away.
|
||||||
|
assert.deepEqual(
|
||||||
|
cullFrom(
|
||||||
|
[79.8612, 6.9271, -21],
|
||||||
|
[
|
||||||
|
[79.8612, 6.92782, -95],
|
||||||
|
[79.86192, 6.9271, -95],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
[true, true],
|
||||||
|
);
|
||||||
|
// Austin, ground well above the ellipsoid: as before.
|
||||||
|
assert.deepEqual(
|
||||||
|
cullFrom([-97.7431, 30.2672, 132], [[-97.7431, 30.2681, 130]]),
|
||||||
|
[true],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the horizon cull still hides the far side of the Earth from orbit (gekh P2)', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
cullFrom(
|
||||||
|
[0, 0, 20_000_000],
|
||||||
|
[
|
||||||
|
[10, 10, 0],
|
||||||
|
[180, 0, 0],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
[true, false],
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -365,7 +365,8 @@ function fakeLibrary() {
|
|||||||
positionGate: null,
|
positionGate: null,
|
||||||
};
|
};
|
||||||
class Viewer {
|
class Viewer {
|
||||||
constructor() {
|
constructor(options) {
|
||||||
|
this.options = options;
|
||||||
viewers.created++;
|
viewers.created++;
|
||||||
viewers.live++;
|
viewers.live++;
|
||||||
viewers.instances.push(this);
|
viewers.instances.push(this);
|
||||||
@@ -457,6 +458,15 @@ test('a prewarm that gave up when the layer went off and on mid-download is foll
|
|||||||
adapter.unmount();
|
adapter.unmount();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the Mapillary viewer leaves resizing to the panel, so a hidden one never asks for z=NaN (gekh P3)', async () => {
|
||||||
|
const { gate, viewers } = fakeLibrary();
|
||||||
|
gate.resolve();
|
||||||
|
const adapter = createMapillaryViewer({ source: { token: 't' } });
|
||||||
|
await adapter.mount({});
|
||||||
|
assert.equal(viewers.instances[0].options.trackResize, false);
|
||||||
|
adapter.unmount();
|
||||||
|
});
|
||||||
|
|
||||||
test('closing the photo stops sequence playback in the hidden viewer (review P2-6)', async () => {
|
test('closing the photo stops sequence playback in the hidden viewer (review P2-6)', async () => {
|
||||||
const { gate, viewers } = fakeLibrary();
|
const { gate, viewers } = fakeLibrary();
|
||||||
gate.resolve();
|
gate.resolve();
|
||||||
|
|||||||
Reference in new issue
Block a user