// deck.gl-community
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import {Component, render} from 'preact';
// import {FlyToInterpolator} from '@deck.gl/core';
import {Widget, type Deck, type Viewport, type WidgetPlacement} from '@deck.gl/core';
import {LongPressButton} from './long-press-button';
export const ViewControlWrapper = ({children}) => (
{' '}
{children}{' '}
);
export const NavigationButtonContainer = ({children}) => (
{' '}
{children}{' '}
);
export type NavigationButtonProps = {
left: any;
top: any;
rotate?: number;
children?: any;
onClick?: () => void;
};
export const NavigationButton = (props: NavigationButtonProps) => (
{' '}
{props.children}{' '}
);
export const ZoomControlWrapper = ({children}) => (
{' '}
{children}{' '}
);
export const VerticalSlider = ({children}) => (
input[type='range'][orient='vertical'] {
// -webkit-appearance: slider-vertical;
// height: 100px;
// padding: 0;
// margin: 0;
// width: 10px;
// }
}}
>
{' '}
{children}{' '}
);
export const ZoomControlButton = ({children}) => (
{' '}
{children}{' '}
);
export type ViewControlProps = {
id?: string;
viewId?: string;
placement?: WidgetPlacement;
fitBounds: () => void;
panBy?: (dx: number, dy: number) => void;
zoomBy?: (delta: number) => void;
zoomLevel: number;
minZoom: number;
maxZoom: number;
deltaPan: number;
deltaZoom: number;
/** CSS inline style overrides. */
style?: Partial;
/** Additional CSS class. */
className?: string;
};
export class ViewControl extends Component {
static displayName = 'ViewControl';
static defaultProps: Required = {
id: undefined,
viewId: undefined,
placement: 'top-left',
fitBounds: () => {},
panBy: () => {},
zoomBy: () => {},
zoomLevel: 1,
deltaPan: 10,
deltaZoom: 0.1,
minZoom: 0.1,
maxZoom: 1,
style: {},
className: ''
};
// pan actions
panUp = () => this.props.panBy(0, this.props.deltaPan);
panDown = () => this.props.panBy(0, -1 * this.props.deltaPan);
panLeft = () => this.props.panBy(this.props.deltaPan, 0);
panRight = () => this.props.panBy(-1 * this.props.deltaPan, 0);
// zoom actions
zoomIn = () => this.props.zoomBy(this.props.deltaZoom);
zoomOut = () => this.props.zoomBy(-1 * this.props.deltaZoom);
onChangeZoomLevel = (evt) => {
const delta = evt.target.value - this.props.zoomLevel;
this.props.zoomBy(delta);
};
render() {
const buttons = [
{top: -2, left: 14, rotate: 0, onClick: this.panUp, content: '▲', key: 'up'},
{top: 12, left: 0, rotate: -90, onClick: this.panLeft, content: '◀', key: 'left'},
{top: 12, left: 28, rotate: 90, onClick: this.panRight, content: '▶', key: 'right'},
{top: 25, left: 14, rotate: 180, onClick: this.panDown, content: '▼', key: 'down'}
];
return (
{buttons.map((b: any) => (
{b.content}
))}
{
// console.log('on click fit bounds') || this.props.fitBounds;
}}
>
{'¤'}
{'+'}
{'-'}
);
}
}
export class ViewControlWidget extends Widget {
id = 'zoom';
placement: WidgetPlacement = 'top-left';
orientation: 'vertical' | 'horizontal' = 'vertical';
viewId?: string | null = null;
viewports: {[id: string]: Viewport} = {};
element?: HTMLDivElement;
className = 'deck-widget-view-control';
constructor(props: ViewControlProps) {
super(props);
this.props = {...ViewControl.defaultProps, ...props};
this.id = props.id || 'zoom';
this.viewId = props.viewId || null;
this.placement = props.placement || 'top-left';
// this.orientation = props.orientation || 'vertical';
// props.transitionDuration = props.transitionDuration || 200;
// props.zoomInLabel = props.zoomInLabel || 'Zoom In';
// props.zoomOutLabel = props.zoomOutLabel || 'Zoom Out';
props.style = props.style || {};
}
onAdd({deck}: {deck: Deck}): HTMLDivElement {
this.deck = deck;
this.element = document.createElement('div');
const {style, className} = this.props;
this.element.classList.add('deck-widget', 'deck-widget-zoom');
if (className) {
this.element.classList.add(className);
}
if (style) {
Object.entries(style).map(([key, value]) =>
this.element.style.setProperty(key, value as string)
);
}
return this.element;
}
onRemove() {
this.deck = undefined;
this.element = undefined;
}
onRenderHTML(rootElement: HTMLElement): void {
const ui = (
);
render(ui, rootElement);
}
setProps(props: Partial) {
Object.assign(this.props, props);
}
onViewportChange(viewport: Viewport) {
this.viewports[viewport.id] = viewport;
}
handleDeltaZoom(deltaZoom: number) {
// console.log('Handle delta zoom');
for (const view of this.deck.getViewports()) {
this.handleZoomView(view, view.zoom + deltaZoom);
}
}
handlePanBy(deltaX: number, deltaY: number) {
// console.log('Handle panby', deltaX, deltaY);
for (const viewport of this.deck.getViewports()) {
this.handlePanViewport(viewport, deltaX, deltaY);
}
}
handleZoomView(viewport: Viewport, nextZoom: number) {
const viewId = this.viewId || viewport?.id || 'default-view';
// @ts-expect-error TODO we lack a proper API for getting viewStates
const viewState = this.deck.viewManager.viewState || viewport;
const nextViewState = {
...viewState,
zoom: nextZoom
// transitionDuration: this.props.transitionDuration,
// transitionInterpolator: new FlyToInterpolator()
};
// @ts-ignore Using private method temporary until there's a public one
this.deck._onViewStateChange({viewId, viewState: nextViewState, interactionState: {}});
}
handlePanViewport(viewport: Viewport, deltaX: number, deltaY: number) {
const viewId = this.viewId || viewport?.id || 'default-view';
// @ts-expect-error TODO we lack a proper API for getting viewStates
const viewState = this.deck.viewManager.viewState || viewport;
// console.log('Handle pan viewport', deltaX, deltaY, viewState);
const nextViewState = {
...viewState,
position: [viewport.position[0] + deltaX, viewport.position[1] + deltaY]
};
// @ts-ignore Using private method temporary until there's a public one
this.deck._onViewStateChange({viewId, viewState: nextViewState, interactionState: {}});
}
}