@deck.gl/widgets 9.1.4 → 9.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,91 @@
1
+ // deck.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ /* global document */
6
+ import {WidgetImpl, WidgetImplProps} from './widget-impl';
7
+ import {h, render} from 'preact';
8
+ import {IconButton} from './components';
9
+ import type {WidgetPlacement} from '@deck.gl/core';
10
+
11
+ /** Properties for the ScreenshotWidget */
12
+ export type ScreenshotWidgetProps = WidgetImplProps & {
13
+ /** Widget positioning within the view. Default 'top-left'. */
14
+ placement?: WidgetPlacement;
15
+ /** Tooltip message */
16
+ label?: string;
17
+ /** Filename to save to */
18
+ filename?: string;
19
+ /** Image format */
20
+ imageFormat?: 'image/png' | 'image/jpeg';
21
+ /** Callback, if defined user overrides the capture logic */
22
+ onCapture?: (widget: ScreenshotWidget) => void;
23
+ };
24
+
25
+ /**
26
+ * A button widget that captures a screenshot of the current canvas and downloads it as a (png) file.
27
+ * @note only captures canvas contents, not HTML DOM or CSS styles
28
+ */
29
+ export class ScreenshotWidget extends WidgetImpl<ScreenshotWidgetProps> {
30
+ static defaultProps: Required<ScreenshotWidgetProps> = {
31
+ ...WidgetImpl.defaultProps,
32
+ id: 'screenshot',
33
+ placement: 'top-left',
34
+ label: 'Screenshot',
35
+ filename: 'screenshot.png',
36
+ imageFormat: 'image/png',
37
+ onCapture: undefined!
38
+ };
39
+
40
+ className = 'deck-widget-screenshot';
41
+ placement: WidgetPlacement = 'top-left';
42
+
43
+ constructor(props: ScreenshotWidgetProps = {}) {
44
+ super({...ScreenshotWidget.defaultProps, ...props});
45
+ this.placement = props.placement ?? this.placement;
46
+ }
47
+
48
+ setProps(props: Partial<ScreenshotWidgetProps>) {
49
+ this.placement = props.placement ?? this.placement;
50
+ super.setProps(props);
51
+ }
52
+
53
+ onRenderHTML() {
54
+ const element = this.element;
55
+ if (!element) return;
56
+ render(
57
+ <IconButton
58
+ className="deck-widget-camera"
59
+ label={this.props.label}
60
+ onClick={this.handleClick.bind(this)}
61
+ />,
62
+ element
63
+ );
64
+ }
65
+
66
+ handleClick() {
67
+ // Allow user to override the capture logic
68
+ if (this.props.onCapture) {
69
+ this.props.onCapture(this);
70
+ return;
71
+ }
72
+ const dataURL = this.captureScreenToDataURL(this.props.imageFormat);
73
+ if (dataURL) {
74
+ this.downloadDataURL(dataURL, this.props.filename);
75
+ }
76
+ }
77
+
78
+ /** @note only captures canvas contents, not HTML DOM or CSS styles */
79
+ captureScreenToDataURL(imageFormat: string): string | undefined {
80
+ const canvas = this.deck?.getCanvas();
81
+ return canvas?.toDataURL(imageFormat);
82
+ }
83
+
84
+ /** Download a data URL */
85
+ downloadDataURL(dataURL: string, filename: string): void {
86
+ const link = document.createElement('a');
87
+ link.href = dataURL;
88
+ link.download = filename;
89
+ link.click();
90
+ }
91
+ }
@@ -147,3 +147,15 @@
147
147
  url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 26 26" fill="black"> wBox="0 0 26 26" fill="none"><path d="M7.89332 13.8866C7.64334 13.8866 7.43196 13.8 7.25918 13.6268C7.08639 13.4535 7 13.2416 7 12.9909C7 12.7403 7.08639 12.5291 7.25918 12.3575C7.43196 12.1858 7.64334 12.1 7.89332 12.1H18.099C18.349 12.1 18.5617 12.1866 18.737 12.3598C18.9123 12.5331 19 12.745 19 12.9957C19 13.2463 18.9123 13.4575 18.737 13.6291C18.5617 13.8008 18.349 13.8866 18.099 13.8866H7.89332Z"/></svg>')
148
148
  );
149
149
  }
150
+
151
+ /* Screenshot styles */
152
+ .deck-widget.deck-widget-screenshot button.deck-widget-camera .deck-widget-icon {
153
+ mask-image: var(
154
+ --icon-camera,
155
+ url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="black"><path d="M480-260q75 0 127.5-52.5T660-440q0-75-52.5-127.5T480-620q-75 0-127.5 52.5T300-440q0 75 52.5 127.5T480-260Zm0-80q-42 0-71-29t-29-71q0-42 29-71t71-29q42 0 71 29t29 71q0 42-29 71t-71 29ZM160-120q-33 0-56.5-23.5T80-200v-480q0-33 23.5-56.5T160-760h126l74-80h240l74 80h126q33 0 56.5 23.5T880-680v480q0 33-23.5 56.5T800-120H160Zm0-80h640v-480H638l-73-80H395l-73 80H160v480Zm320-240Z"/></svg>')
156
+ );
157
+ -webkit-mask-image: var(
158
+ --icon-camera,
159
+ url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="black"><path d="M480-260q75 0 127.5-52.5T660-440q0-75-52.5-127.5T480-620q-75 0-127.5 52.5T300-440q0 75 52.5 127.5T480-260Zm0-80q-42 0-71-29t-29-71q0-42 29-71t71-29q42 0 71 29t29 71q0 42-29 71t-71 29ZM160-120q-33 0-56.5-23.5T80-200v-480q0-33 23.5-56.5T160-760h126l74-80h240l74 80h126q33 0 56.5 23.5T880-680v480q0 33-23.5 56.5T800-120H160Zm0-80h640v-480H638l-73-80H395l-73 80H160v480Zm320-240Z"/></svg>')
160
+ );
161
+ }
@@ -0,0 +1,88 @@
1
+ // deck.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ /* global document */
6
+ import {_deepEqual, _applyStyles, _removeStyles} from '@deck.gl/core';
7
+ import type {Deck, Widget} from '@deck.gl/core';
8
+
9
+ export type WidgetImplProps = {
10
+ id?: string;
11
+ /** CSS inline style overrides. */
12
+ style?: Partial<CSSStyleDeclaration>;
13
+ /** Additional CSS class. */
14
+ className?: string;
15
+ };
16
+ export abstract class WidgetImpl<PropsT extends WidgetImplProps> implements Widget<PropsT> {
17
+ id: string;
18
+ props: Required<PropsT>;
19
+
20
+ deck?: Deck<any>;
21
+ element?: HTMLDivElement;
22
+
23
+ static defaultProps: Required<WidgetImplProps> = {
24
+ id: 'widget',
25
+ style: {},
26
+ className: ''
27
+ };
28
+
29
+ abstract className: Required<string>;
30
+
31
+ constructor(props: Required<PropsT>) {
32
+ this.id = props.id || 'widget';
33
+ this.props = props;
34
+ }
35
+
36
+ abstract onRenderHTML(): void;
37
+
38
+ onAdd({deck}: {deck: Deck<any>}): HTMLDivElement {
39
+ this.deck = deck;
40
+ const {style, className} = this.props;
41
+ const el = this._createRootElement({
42
+ widgetClassName: this.className,
43
+ className,
44
+ style
45
+ });
46
+ this.element = el;
47
+ this.onRenderHTML();
48
+ return this.element;
49
+ }
50
+
51
+ onRemove() {
52
+ this.deck = undefined;
53
+ this.element = undefined;
54
+ }
55
+
56
+ setProps(props: Partial<PropsT>) {
57
+ const oldProps = this.props;
58
+ const el = this.element;
59
+ if (el) {
60
+ if (oldProps.className !== props.className) {
61
+ if (oldProps.className) el.classList.remove(oldProps.className);
62
+ if (props.className) el.classList.add(props.className);
63
+ }
64
+
65
+ if (!_deepEqual(oldProps.style, props.style, 1)) {
66
+ _removeStyles(el, oldProps.style);
67
+ _applyStyles(el, props.style);
68
+ }
69
+ }
70
+
71
+ Object.assign(this.props, props);
72
+ this.onRenderHTML();
73
+ }
74
+
75
+ _createRootElement(props: {
76
+ widgetClassName: string;
77
+ className?: string;
78
+ style?: Partial<CSSStyleDeclaration>;
79
+ }) {
80
+ const {widgetClassName, className, style} = props;
81
+ const element = document.createElement('div');
82
+ ['deck-widget', widgetClassName, className]
83
+ .filter((cls): cls is string => typeof cls === 'string' && cls.length > 0)
84
+ .forEach(className => element.classList.add(className));
85
+ _applyStyles(element, style);
86
+ return element;
87
+ }
88
+ }