Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 47 additions & 6 deletions modules/core/src/lib/view-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ type ViewManagerProps<ViewsT extends ViewOrViews> = {
pickPosition?: (x: number, y: number) => {coordinate?: number[]} | null;
width?: number;
height?: number;
/** CSS pixel dimensions for each presentation canvas, keyed by canvas id. */
canvasMetrics?: Record<string, {width: number; height: number}>;
/** Event managers keyed by presentation canvas id. */
eventManagers?: Record<string, EventManager>;
};
Expand All @@ -79,6 +81,7 @@ export default class ViewManager<ViewsT extends View[]> {
onInteractionStateChange?: (state: InteractionState) => void;
};
private _pickPosition?: (x: number, y: number) => {coordinate?: number[]} | null;
private _canvasMetrics: Record<string, {width: number; height: number}>;

constructor(
props: ViewManagerProps<ViewsT> & {
Expand Down Expand Up @@ -109,6 +112,7 @@ export default class ViewManager<ViewsT extends View[]> {
onInteractionStateChange: props.onInteractionStateChange
};
this._pickPosition = props.pickPosition;
this._canvasMetrics = props.canvasMetrics || {};

Object.seal(this);

Expand Down Expand Up @@ -163,10 +167,21 @@ export default class ViewManager<ViewsT extends View[]> {
* + not provided - return all viewports
* + {x, y} - only return viewports that contain this pixel
* + {x, y, width, height} - only return viewports that overlap with this rectangle
* + {canvasId} - only return viewports associated with this canvas
*/
getViewports(rect?: {x: number; y: number; width?: number; height?: number}): Viewport[] {
getViewports(rect?: {
x: number;
y: number;
width?: number;
height?: number;
canvasId?: string;
}): Viewport[] {
if (rect) {
return this._viewports.filter(viewport => viewport.containsPixel(rect));
return this._viewports.filter(
viewport =>
(!rect.canvasId || this.getCanvasId(viewport.id) === rect.canvasId) &&
viewport.containsPixel(rect)
Comment thread
ibgreen-openai marked this conversation as resolved.
Outdated
);
}
return this._viewports;
}
Expand Down Expand Up @@ -206,7 +221,7 @@ export default class ViewManager<ViewsT extends View[]> {
getCanvasId(viewOrViewId: string | View): string | undefined {
const view = typeof viewOrViewId === 'string' ? this.getView(viewOrViewId) : viewOrViewId;
return view
? this._viewEventManagers[view.id]?.canvasId || view.props.canvasId || DEFAULT_CANVAS_ID
? this._viewEventManagers[view.id]?.canvasId || this._resolveCanvasId(view)
: undefined;
}

Expand Down Expand Up @@ -253,6 +268,10 @@ export default class ViewManager<ViewsT extends View[]> {
this._pickPosition = props.pickPosition;
}

if ('canvasMetrics' in props) {
this._setCanvasMetrics(props.canvasMetrics || {});
}

if ('eventManagers' in props) {
this._setEventManagers(props.eventManagers || {});
}
Expand Down Expand Up @@ -331,6 +350,27 @@ export default class ViewManager<ViewsT extends View[]> {
}
}

private _setCanvasMetrics(canvasMetrics: Record<string, {width: number; height: number}>): void {
if (!deepEqual(canvasMetrics, this._canvasMetrics, 2)) {
this._canvasMetrics = canvasMetrics;
this.setNeedsUpdate('canvasMetrics changed');
}
}

private _resolveCanvasId(view: View): string {
const canvasIds = Object.keys(this._canvasMetrics);
return view.props.canvasId || canvasIds[0] || DEFAULT_CANVAS_ID;
}

private _getCanvasMetrics(view: View): {width: number; height: number} {
const canvasId = this._resolveCanvasId(view);
const metrics = this._canvasMetrics[canvasId];
return {
width: metrics?.width ?? this.width,
height: metrics?.height ?? this.height
};
}

private _getViewEventManager(view: View): ViewEventManager {
const canvasId = this.getCanvasId(view) || DEFAULT_CANVAS_ID;
return {
Expand Down Expand Up @@ -382,8 +422,8 @@ export default class ViewManager<ViewsT extends View[]> {
makeViewport: viewState =>
this.getView(view.id)?.makeViewport({
viewState,
width: this.width,
height: this.height
width: this._getCanvasMetrics(view).width,
height: this._getCanvasMetrics(view).height
}),
pickPosition: this._pickPosition
});
Expand Down Expand Up @@ -432,10 +472,11 @@ export default class ViewManager<ViewsT extends View[]> {
// Create controllers in reverse order, so that views on top receive events first
for (let i = views.length; i--; ) {
const view = views[i];
const {width, height} = this._getCanvasMetrics(view);
const viewEventManager = this._getViewEventManager(view);
this._viewEventManagers[view.id] = viewEventManager;
const viewState = this.getViewState(view);
const viewport = view.makeViewport({viewState, width: this.width, height: this.height});
const viewport = view.makeViewport({viewState, width, height});

let oldController = this._getReusableController(
oldControllers[view.id],
Expand Down
77 changes: 77 additions & 0 deletions test/modules/core/lib/view-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,83 @@ test('ViewManager#routes controllers by canvas event manager', () => {
});

/* eslint-disable max-statements */
test('ViewManager#layouts and filters views by canvas', () => {
const leftEventManager = new EventManager(document.createElement('div'));
const rightEventManager = new EventManager(document.createElement('div'));
const leftView = new MapView({id: 'left', canvasId: 'left-canvas'});
const rightView = new MapView({id: 'right', canvasId: 'right-canvas'});

const viewManager = new ViewManager({
views: [leftView, rightView],
viewState: {
left: {longitude: -122, latitude: 38, zoom: 10},
right: {longitude: -74, latitude: 40.7, zoom: 11}
},
width: 1,
height: 1,
canvasMetrics: {
'left-canvas': {width: 200, height: 100},
'right-canvas': {width: 120, height: 180}
},
eventManager: leftEventManager,
eventManagers: {
'left-canvas': leftEventManager,
'right-canvas': rightEventManager
}
});

expect(viewManager.getViewport('left')?.width).toBe(200);
expect(viewManager.getViewport('right')?.height).toBe(180);
expect(viewManager.getViewports({x: 1, y: 1, canvasId: 'left-canvas'})).toEqual([
viewManager.getViewport('left')
]);
expect(viewManager.getViewports({x: 1, y: 1, canvasId: 'unknown-canvas'})).toEqual([]);

const viewports = viewManager.getViewports();
viewManager.setProps({
canvasMetrics: {
'left-canvas': {width: 200, height: 100},
'right-canvas': {width: 120, height: 180}
}
});
expect(viewManager.getViewports(), 'unchanged canvas dimensions preserve viewports').toBe(
viewports
);

viewManager.setProps({
canvasMetrics: {
'left-canvas': {width: 240, height: 140},
'right-canvas': {width: 120, height: 180}
}
});
expect(viewManager.getViewport('left')?.width, 'updated dimensions rebuild the viewport').toBe(
240
);

viewManager.finalize();
leftEventManager.destroy();
rightEventManager.destroy();
});

test('ViewManager#uses the first canvas for views without an explicit canvas id', () => {
const eventManager = new EventManager(document.createElement('div'));
const viewManager = new ViewManager({
views: [new MapView({id: 'main'})],
viewState: {longitude: -122, latitude: 38, zoom: 10},
width: 1,
height: 1,
canvasMetrics: {'first-canvas': {width: 160, height: 90}},
eventManager
});

expect(viewManager.getCanvasId('main')).toBe('first-canvas');
expect(viewManager.getViewport('main')?.width).toBe(160);
expect(viewManager.getViewports({x: 1, y: 1, canvasId: 'first-canvas'})).toHaveLength(1);

viewManager.finalize();
eventManager.destroy();
});

test('ViewManager#zero-size', () => {
const mainView = new MapView({id: 'main', controller: true});

Expand Down
Loading