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

Add Zoom and Compass widgets #8072

Merged
merged 49 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
fc6be2c
add(deck) _widget prop
chrisgervang Jul 25, 2023
cd67293
add(widgets) fullscreen widget scaffold
chrisgervang Jul 25, 2023
18835f1
Update modules/widgets/src/fullscreen-widget.ts
chrisgervang Jul 27, 2023
2fa64ad
Update modules/widgets/src/fullscreen-widget.ts
chrisgervang Jul 27, 2023
fceab53
add(deck) _widget prop
chrisgervang Jul 25, 2023
bfa10b6
declarative widgets api
Pessimistress Aug 3, 2023
adfc21a
Add comments
Pessimistress Aug 4, 2023
46be41a
fix test
Pessimistress Aug 4, 2023
f37b35e
simplify widget api
Pessimistress Aug 4, 2023
024d1bc
docs
chrisgervang Aug 8, 2023
3caa7e8
Merge branch 'master' into chr/deck-widgets-prop
chrisgervang Aug 8, 2023
e07fd97
Merge branch 'chr/deck-widgets-prop' into chr/fullscreen-widget
chrisgervang Aug 8, 2023
debbf1e
add(core) export types
chrisgervang Aug 8, 2023
f43cfdd
Merge branch 'chr/deck-widgets-prop' into chr/fullscreen-widget
chrisgervang Aug 8, 2023
ed185ff
add(examples) widgets example
chrisgervang Aug 15, 2023
f135eca
add(widgets) build with preact jsx
chrisgervang Aug 16, 2023
a5cd7b4
add(widget) fullscreen button + styles
chrisgervang Aug 16, 2023
efd612e
add(widget) fullscreen optional container
chrisgervang Aug 16, 2023
2f31eca
add(widget) theming with CSS variables
chrisgervang Aug 17, 2023
004c0aa
Merge branch 'master' into chr/fullscreen-widget
chrisgervang Aug 17, 2023
e02624b
add(widget) applications set theme
chrisgervang Aug 17, 2023
a87c7f9
comments
chrisgervang Aug 21, 2023
ee7a365
spell out widget
chrisgervang Aug 21, 2023
bda7bcd
add(widget) built-in themes
chrisgervang Aug 21, 2023
f9cc7ab
add(widget) add className prop
chrisgervang Aug 22, 2023
8df067a
add(widget) fullscreen and theme docs
chrisgervang Aug 22, 2023
a7aabfe
cleanup
chrisgervang Aug 22, 2023
5e2dac1
Adding Zoom widget
ilyabo Aug 22, 2023
c1e2a13
PR feedback
ilyabo Aug 22, 2023
9577fd3
Button group styling
ilyabo Aug 22, 2023
4e900fe
Adding compass widget
ilyabo Aug 28, 2023
4e3753d
Adding globe viewport rotation
ilyabo Sep 7, 2023
24ce65d
style and className support modifications
chrisgervang Sep 9, 2023
4791fb8
icons, icon variables, onfullscreen handler, pseudo-fullscreen
chrisgervang Sep 9, 2023
a6f6268
docs and style examples
chrisgervang Sep 9, 2023
24bf325
comments
chrisgervang Sep 9, 2023
73afafd
Merge branch 'master' into chr/fullscreen-widget
chrisgervang Sep 9, 2023
61d2f55
types
chrisgervang Sep 9, 2023
61eeaf4
Merge fullscreen widget
ilyabo Sep 12, 2023
33dd198
Merge branch 'master' into ilya/zoom-widget
chrisgervang Mar 16, 2024
92c861d
chore(widgets) fix widgets after merge
chrisgervang Mar 16, 2024
d420e9d
add(widgets) zoom icons and horizontal option
chrisgervang Mar 18, 2024
0767d0d
add(widgets) compass icon, button group component
chrisgervang Mar 19, 2024
54f8ce7
chore(widgets) revert eslint
chrisgervang Mar 19, 2024
25c7bde
Merge branch 'master' into ilya/zoom-widget
chrisgervang Mar 19, 2024
85ef942
add(widgets) test app and simpler getting started
chrisgervang Mar 19, 2024
942be8f
comment
chrisgervang Mar 19, 2024
c4a395f
add(widgets) compass and zoom docs
chrisgervang Mar 19, 2024
6f2d415
add(widgets) compass icon color vars
chrisgervang Mar 19, 2024
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
3 changes: 2 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ module.exports = getESLintConfig({
files: ['modules/widgets/**/*.tsx'],
rules: {
// For widgets module. Disable React-style JSX linting since they conflict with Preact JSX.
'react/react-in-jsx-scope': 0
'react/react-in-jsx-scope': 0,
'no-unused-expressions': ['error', { 'allowShortCircuit': true }]
chrisgervang marked this conversation as resolved.
Show resolved Hide resolved
}
}
],
Expand Down
13 changes: 11 additions & 2 deletions examples/get-started/pure-js/widgets/app.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import {Deck} from '@deck.gl/core';
import {GeoJsonLayer, ArcLayer} from '@deck.gl/layers';
import {FullscreenWidget, DarkGlassTheme, LightGlassTheme} from '@deck.gl/widgets';
import {
CompassWidget,
ZoomWidget,
FullscreenWidget,
DarkGlassTheme,
LightGlassTheme
} from '@deck.gl/widgets';
import '@deck.gl/widgets/stylesheet.css';

const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");
Expand Down Expand Up @@ -64,8 +70,11 @@ new Deck({
})
],
widgets: [
new ZoomWidget({style: widgetTheme}),
new CompassWidget({style: widgetTheme}),

new FullscreenWidget({}),
new FullscreenWidget({id: 'themed', style: widgetTheme}),
new FullscreenWidget({id: 'themed', style: widgetTheme}),
new FullscreenWidget({id: 'purple', className: 'purple'})
]
});
6 changes: 3 additions & 3 deletions examples/get-started/pure-js/widgets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
"build": "vite build"
},
"dependencies": {
"@deck.gl/core": "^9.0.0-beta.2",
"@deck.gl/layers": "^9.0.0-beta.2",
"@deck.gl/widgets": "^9.0.0-beta.2"
"@deck.gl/core": "^9.0.0-beta",
"@deck.gl/layers": "^9.0.0-beta",
"@deck.gl/widgets": "^9.0.0-beta"
},
"devDependencies": {
"vite": "^4.0.0"
Expand Down
2 changes: 1 addition & 1 deletion modules/core/src/lib/widget-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface Widget<PropsT = any> {
props: PropsT;
/**
* The view id that this widget is being attached to. Default `null`.
* If assigned, this widget will only respond to events occured inside the specific view that matches this id.
* If assigned, this widget will only respond to events occurred inside the specific view that matches this id.
*/
viewId?: string | null;
/** Widget positioning within the view. Default 'top-left'. */
Expand Down
117 changes: 117 additions & 0 deletions modules/widgets/src/compass-widget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/* 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 CompassWidgetProps {
id: string;
viewId?: string | null;
placement?: WidgetPlacement;
label?: string;
transitionDuration?: number;
style?: Partial<CSSStyleDeclaration>;
className?: string;
}

export class CompassWidget implements Widget<CompassWidgetProps> {
id = 'compass';
props: CompassWidgetProps;
placement: WidgetPlacement = 'top-left';
viewId?: string | null = null;
viewport?: Viewport;
deck?: Deck;
element?: HTMLDivElement;

constructor(props: CompassWidgetProps) {
this.id = props.id || 'compass';
this.viewId = props.viewId || null;
this.placement = props.placement || 'top-left';
props.transitionDuration = props.transitionDuration || 200;
props.label = props.label || 'Compass';
props.style = props.style || {};
this.props = props;
}

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

onViewportChange(viewport) {
this.viewport = viewport;
this.update();
}

onAdd({deck}: {deck: Deck}): HTMLDivElement {
const {style, className} = this.props;
const element = document.createElement('div');
element.classList.add('deck-widget', 'deck-widget-compass');
if (className) element.classList.add(className);
if (style) {
Object.entries(style).map(([key, value]) => element.style.setProperty(key, value as string));
}
this.deck = deck;
this.element = element;
this.update();
return element;
}

getRotation() {
if (this.viewport instanceof WebMercatorViewport) {
return [-this.viewport.bearing, this.viewport.pitch];
} else if (this.viewport instanceof _GlobeViewport) {
return [0, Math.max(-80, Math.min(80, this.viewport.latitude))];
}
return [0, 0];
}

update() {
const [rz, rx] = this.getRotation();
const element = this.element;
if (!element) {
return;
}
const ui = (
<div className="deck-widget-button">
<div className="deck-widget-button-border" style={{perspective: 100}}>
<button
type="button"
onClick={() => this.handleCompassReset()}
label={this.props.label}
style={{transform: `rotateX(${rx}deg)`}}
>
<svg fill="none" width="100%" height="100%" viewBox={'0 0 16 16'}>
<g transform={`rotate(${rz},8,8)`}>
<path d="M5 8.00006L7.99987 0L10.9997 8.00006H5Z" fill="#F05C44" />
<path
d="M11.0002 7.99994L8.00038 16L5.00051 7.99994L11.0002 7.99994Z"
fill="#ccc"
/>
</g>
</svg>
</button>
</div>
</div>
);
render(ui, element);
}

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

handleCompassReset() {
const viewId = this.viewId || 'default-view';
if (this.viewport instanceof WebMercatorViewport) {
const nextViewState = {
...this.viewport,
bearing: 0,
...(this.getRotation()[0] === 0 ? {pitch: 0} : {}),
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: {}});
}
}
}
2 changes: 2 additions & 0 deletions modules/widgets/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export {FullscreenWidget} from './fullscreen-widget';
export {CompassWidget} from './compass-widget';
export {ZoomWidget} from './zoom-widget';

export * from './themes';
17 changes: 17 additions & 0 deletions modules/widgets/src/stylesheet.css
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,20 @@
width: 100% !important;
z-index: 99999;
}

.deck-widget-button-group {
display: flex;
flex-direction: column;
}

.deck-widget-button-group > *:not(:last-child),
.deck-widget-button-group > *:not(:last-child) > button {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}

.deck-widget-button-group > *:not(:first-child),
.deck-widget-button-group > *:not(:first-child) > button {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
115 changes: 115 additions & 0 deletions modules/widgets/src/zoom-widget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/* global document */
import {FlyToInterpolator} from '@deck.gl/core';
import type {Deck, Viewport, Widget, WidgetPlacement} from '@deck.gl/core';
import {render} from 'preact';

interface ZoomWidgetProps {
id: string;
viewId?: string | null;
placement?: WidgetPlacement;
zoomInLabel?: string;
zoomOutLabel?: string;
transitionDuration?: number;
style?: Partial<CSSStyleDeclaration>;
className?: string;
}

export class ZoomWidget implements Widget<ZoomWidgetProps> {
id = 'zoom';
props: ZoomWidgetProps;
placement: WidgetPlacement = 'top-left';
viewId?: string | null = null;
viewport?: Viewport;
deck?: Deck;
element?: HTMLDivElement;

constructor(props: ZoomWidgetProps) {
this.id = props.id || 'zoom';
this.viewId = props.viewId || null;
this.placement = props.placement || 'top-left';
props.transitionDuration = props.transitionDuration || 200;
props.zoomInLabel = props.zoomInLabel || 'Zoom In';
props.zoomOutLabel = props.zoomOutLabel || 'Zoom Out';
props.style = props.style || {};
this.props = props;
}

onAdd({deck}: {deck: Deck}): HTMLDivElement {
const {style, className} = this.props;
const element = document.createElement('div');
element.classList.add('deck-widget', 'deck-widget-zoom');
if (className) element.classList.add(className);
if (style) {
Object.entries(style).map(([key, value]) => element.style.setProperty(key, value as string));
}
const ui = (
<div className="deck-widget-button-group">
<Button onClick={() => this.handleZoomIn()} label={this.props.zoomInLabel}>
<path d="M12 4.5v15m7.5-7.5h-15" />
chrisgervang marked this conversation as resolved.
Show resolved Hide resolved
</Button>
<Button onClick={() => this.handleZoomOut()} label={this.props.zoomOutLabel}>
<path d="M19.5 12h-15" />
</Button>
</div>
);
render(ui, element);

this.deck = deck;
this.element = element;

return element;
}

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

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

onViewportChange(viewport) {
this.viewport = viewport;
}

handleZoom(nextZoom: number) {
const viewId = this.viewId || 'default-view';
const nextViewState = {
...this.viewport,
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: {}});
}

handleZoomIn() {
this.viewport && this.handleZoom(this.viewport.zoom + 1);
}

handleZoomOut() {
this.viewport && this.handleZoom(this.viewport.zoom - 1);
}
}

const Button = props => {
const {label, onClick} = props;
return (
<div className="deck-widget-button-border">
<button className="deck-widget-button" type="button" onClick={onClick} title={label}>
<svg
fill="none"
width="100%"
height="100%"
viewBox="0 0 24 24"
style="stroke-width: 2px"
stroke="currentColor"
>
{props.children}
</svg>
</button>
</div>
);
};
Loading