Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC: PopupWidget #8958

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
33 changes: 24 additions & 9 deletions examples/get-started/pure-js/widgets/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import {GeoJsonLayer, ArcLayer} from '@deck.gl/layers';
import {
CompassWidget,
PopupWidget,
ZoomWidget,
FullscreenWidget,
DarkGlassTheme,
Expand All @@ -27,9 +28,29 @@
pitch: 30
};

new Deck({
const UI_WIDGETS = [
new ZoomWidget({style: widgetTheme}),
new CompassWidget({style: widgetTheme}),
new FullscreenWidget({style: widgetTheme})
];

function updatePopup(object) {
const widgets = [...UI_WIDGETS];
if (object) {
const position = object.geometry.coordinates;
const text = `${object.properties.name} (${object.properties.abbrev})`;
const style = {width: 200, boxShadow: 'rgba(0, 0, 0, 0.5) 2px 2px 5px'};
widgets.push(new PopupWidget({position, text, style}));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the PopupWidget just take a callback instead, so we don't need to mess with it like this?

}

deck.setProps({widgets});

Check failure on line 46 in examples/get-started/pure-js/widgets/app.js

View workflow job for this annotation

GitHub Actions / test-node

'deck' was used before it was defined
return true;
}

const deck = new Deck({
initialViewState: INITIAL_VIEW_STATE,
controller: true,
onClick: () => updatePopup(),
layers: [
new GeoJsonLayer({
id: 'base-map',
Expand All @@ -54,9 +75,7 @@
// Interactive props
pickable: true,
autoHighlight: true,
onClick: info =>
// eslint-disable-next-line
info.object && alert(`${info.object.properties.name} (${info.object.properties.abbrev})`)
onClick: info => updatePopup(info.object)
}),
new ArcLayer({
id: 'arcs',
Expand All @@ -70,9 +89,5 @@
getWidth: 1
})
],
widgets: [
new ZoomWidget({style: widgetTheme}),
new CompassWidget({style: widgetTheme}),
new FullscreenWidget({style: widgetTheme})
]
widgets: UI_WIDGETS
});
1 change: 1 addition & 0 deletions modules/widgets/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export {FullscreenWidget} from './fullscreen-widget';
export {CompassWidget} from './compass-widget';
export {PopupWidget} from './popup-widget';
export {ZoomWidget} from './zoom-widget';

export * from './themes';
97 changes: 97 additions & 0 deletions modules/widgets/src/popup-widget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/* global document */
import {FlyToInterpolator, WebMercatorViewport, _GlobeViewport} from '@deck.gl/core';
import type {Deck, Viewport, Widget, WidgetPlacement} from '@deck.gl/core';
import {render} from 'preact';

interface PopupWidgetProps {
id: string;
/**
* View to attach to and interact with. Required when using multiple views.
*/
viewId?: string | null;
/**
* CSS inline style overrides.
*/
style?: Partial<CSSStyleDeclaration>;
/**
* Additional CSS class.
*/
className?: string;
/**
* Position at which to place popup
*/
position: [number, number];
/**
* Text of popup
*/
text: string;
}

export class PopupWidget implements Widget<PopupWidgetProps> {
id = 'popup';
props: PopupWidgetProps;
viewId?: string | null = null;
viewport?: Viewport;
deck?: Deck<any>;
element?: HTMLDivElement;

constructor(props: PopupWidgetProps) {
this.id = props.id || 'popup';
this.viewId = props.viewId || null;
props.style = props.style || {};
props.position = props.position || [0, 0];
props.text = props.text || '';
this.props = props;
}

setProps(props: Partial<PopupWidgetProps>) {
Object.assign(this.props, props);
this.update();
}

onViewportChange(viewport) {
this.viewport = viewport;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A broader question since we have eye balls on widgets.. How should we handle if this has been added to no specific viewport, and there are multiple viewports? This remains an unsolved question in all of our viewport-dependent widgets. Currently, only the last viewport in the list will get the popup even if the position is visible in multiple viewports.

I wonder if we a widget should be able to return multiple UI elements if there are multiple viewports around L66 in onAdd? Or if there should be some warning or documentation to say "you're using multiple viewports, so you should instantiate a widget-per-viewport and supply viewId"

this.update();
}

onAdd({deck, viewId}: {deck: Deck<any>, viewId: string | null}): HTMLDivElement {
const {className} = this.props;
const element = document.createElement('div');
element.classList.add('deck-widget', 'deck-widget-popup');
if (className) element.classList.add(className);
const style = {margin: '0px', top: '0px', left: '0px', position: 'absolute'};
Object.entries(style).map(([key, value]) => element.style.setProperty(key, value as string));

Check failure on line 63 in modules/widgets/src/popup-widget.tsx

View workflow job for this annotation

GitHub Actions / test-node

This assertion is unnecessary since it does not change the type of the expression
this.deck = deck;
if (!viewId) {
this.viewport = deck.getViewports()[0];
} else {
this.viewport = deck.getViewports().find(viewport => viewport.id === viewId);
}
this.element = element;
this.update();
return element;
}

update() {
const [longitude, latitude] = this.props.position;
const [x, y] = this.viewport!.project([longitude, latitude]);
const element = this.element;
if (!element) {
return;
}
const style = {
background: 'rgba(255, 255, 255, 0.9)',
padding: 10,
Comment on lines +83 to +84
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to use CSS variables and add theming for these. --popup-background and --popup-padding could be good here, though I might want to go further and suggest to incorporate other aesthetics like a shadow and inner/outer stroke like the other widget designs. Reason being we can offer an appealing OOB design, and make it easy to customize with vars to match a user's design system.

My approach has been to put styles that aren't 100% necessary for functionality in the stylesheet and theme examples in here.

...this.props.style as any,
perspective: 100,
transform: `translate(${x}px, ${y}px)`
}
const ui = (<div style={style}>{this.props.text}</div>);
render(ui, element);
}

onRemove() {
this.deck = undefined;
this.element = undefined;
}
}
Loading