@deck.gl/core
Version:
deck.gl core library
226 lines • 8.38 kB
JavaScript
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import { EVENT_HANDLERS } from "./constants.js";
import { deepEqual } from "../utils/deep-equal.js";
const PLACEMENTS = {
'top-left': { top: 0, left: 0 },
'top-right': { top: 0, right: 0 },
'bottom-left': { bottom: 0, left: 0 },
'bottom-right': { bottom: 0, right: 0 },
fill: { top: 0, left: 0, bottom: 0, right: 0 }
};
const DEFAULT_PLACEMENT = 'top-left';
const ROOT_CONTAINER_ID = '__root';
export class WidgetManager {
constructor({ deck, parentElement }) {
/** Widgets added via the imperative API */
this.defaultWidgets = [];
/** Widgets received from the declarative API */
this.widgets = [];
/** Resolved widgets from both imperative and declarative APIs */
this.resolvedWidgets = [];
/** Mounted HTML containers */
this.containers = {};
/** Viewport provided to widget on redraw */
this.lastViewports = {};
this.deck = deck;
parentElement?.classList.add('deck-widget-container');
this.parentElement = parentElement;
}
getWidgets() {
return this.resolvedWidgets;
}
/** Declarative API to configure widgets */
setProps(props) {
if (props.widgets && !deepEqual(props.widgets, this.widgets, 1)) {
// Allow application to supply null widgets
const nextWidgets = props.widgets.filter(Boolean);
this._setWidgets(nextWidgets);
}
}
finalize() {
for (const widget of this.getWidgets()) {
this._removeWidget(widget);
}
this.defaultWidgets.length = 0;
this.resolvedWidgets.length = 0;
for (const id in this.containers) {
this.containers[id].remove();
}
}
/** Imperative API. Widgets added this way are not affected by the declarative prop. */
addDefault(widget) {
if (!this.defaultWidgets.find(w => w.id === widget.id)) {
this._addWidget(widget);
this.defaultWidgets.push(widget);
// Update widget list
this._setWidgets(this.widgets);
}
}
onRedraw({ viewports, layers }) {
const viewportsById = viewports.reduce((acc, v) => {
acc[v.id] = v;
return acc;
}, {});
for (const widget of this.getWidgets()) {
const { viewId } = widget;
if (viewId) {
// Attached to a specific view
const viewport = viewportsById[viewId];
if (viewport) {
if (widget.onViewportChange) {
widget.onViewportChange(viewport);
}
widget.onRedraw?.({ viewports: [viewport], layers });
}
}
else {
// Not attached to a specific view
if (widget.onViewportChange) {
for (const viewport of viewports) {
widget.onViewportChange(viewport);
}
}
widget.onRedraw?.({ viewports, layers });
}
}
this.lastViewports = viewportsById;
this._updateContainers();
}
onHover(info, event) {
for (const widget of this.getWidgets()) {
const { viewId } = widget;
if (!viewId || viewId === info.viewport?.id) {
widget.onHover?.(info, event);
}
}
}
onEvent(info, event) {
const eventHandlerProp = EVENT_HANDLERS[event.type];
if (!eventHandlerProp) {
return;
}
for (const widget of this.getWidgets()) {
const { viewId } = widget;
if (!viewId || viewId === info.viewport?.id) {
widget[eventHandlerProp]?.(info, event);
}
}
}
// INTERNAL METHODS
/**
* Resolve widgets from the declarative prop
* Initialize new widgets and remove old ones
* Update props of existing widgets
*/
_setWidgets(nextWidgets) {
const oldWidgetMap = {};
for (const widget of this.resolvedWidgets) {
oldWidgetMap[widget.id] = widget;
}
// Clear and rebuild the list
this.resolvedWidgets.length = 0;
// Add all default widgets
for (const widget of this.defaultWidgets) {
oldWidgetMap[widget.id] = null;
this.resolvedWidgets.push(widget);
}
for (let widget of nextWidgets) {
const oldWidget = oldWidgetMap[widget.id];
if (!oldWidget) {
// Widget is new
this._addWidget(widget);
}
else if (
// Widget placement changed
oldWidget.viewId !== widget.viewId ||
oldWidget.placement !== widget.placement) {
this._removeWidget(oldWidget);
this._addWidget(widget);
}
else if (widget !== oldWidget) {
// Widget props changed
oldWidget.setProps(widget.props);
widget = oldWidget;
}
// mark as matched
oldWidgetMap[widget.id] = null;
this.resolvedWidgets.push(widget);
}
for (const id in oldWidgetMap) {
const oldWidget = oldWidgetMap[id];
if (oldWidget) {
// No longer exists
this._removeWidget(oldWidget);
}
}
this.widgets = nextWidgets;
}
/** Initialize new widget */
_addWidget(widget) {
const { viewId = null, placement = DEFAULT_PLACEMENT } = widget;
widget.widgetManager = this;
widget.deck = this.deck;
// Create an attach the HTML root element
widget.rootElement = widget._onAdd({ deck: this.deck, viewId });
if (widget.rootElement) {
this._getContainer(viewId, placement).append(widget.rootElement);
}
widget.updateHTML();
}
/** Destroy an old widget */
_removeWidget(widget) {
widget.onRemove?.();
if (widget.rootElement) {
widget.rootElement.remove();
}
widget.rootElement = undefined;
widget.deck = undefined;
widget.widgetManager = undefined;
}
/** Get a container element based on view and placement */
_getContainer(viewId, placement) {
const containerId = viewId || ROOT_CONTAINER_ID;
let viewContainer = this.containers[containerId];
if (!viewContainer) {
viewContainer = document.createElement('div');
viewContainer.style.pointerEvents = 'none';
viewContainer.style.position = 'absolute';
viewContainer.style.overflow = 'hidden';
this.parentElement?.append(viewContainer);
this.containers[containerId] = viewContainer;
}
let container = viewContainer.querySelector(`.${placement}`);
if (!container) {
container = globalThis.document.createElement('div');
container.className = placement;
container.style.position = 'absolute';
container.style.zIndex = '2';
Object.assign(container.style, PLACEMENTS[placement]);
viewContainer.append(container);
}
return container;
}
_updateContainers() {
const canvasWidth = this.deck.width;
const canvasHeight = this.deck.height;
for (const id in this.containers) {
const viewport = this.lastViewports[id] || null;
const visible = id === ROOT_CONTAINER_ID || viewport;
const container = this.containers[id];
if (visible) {
container.style.display = 'block';
// Align the container with the view
container.style.left = `${viewport ? viewport.x : 0}px`;
container.style.top = `${viewport ? viewport.y : 0}px`;
container.style.width = `${viewport ? viewport.width : canvasWidth}px`;
container.style.height = `${viewport ? viewport.height : canvasHeight}px`;
}
else {
container.style.display = 'none';
}
}
}
}
//# sourceMappingURL=widget-manager.js.map