UNPKG

worldwind-react-globe-bs4

Version:

Bootstrap 4 UI components for the React worldwind-react-globe virtual globe, including a nav-bar, layer management, markers and settings.

1,260 lines (1,110 loc) 41.9 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var React = require('react'); var React__default = _interopDefault(React); var PropTypes = _interopDefault(require('prop-types')); var Globe = _interopDefault(require('worldwind-react-globe')); var mobxReact = require('mobx-react'); var FontAwesome = _interopDefault(require('react-fontawesome')); var reactstrap = require('reactstrap'); var $ = _interopDefault(require('jquery')); var classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; var createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }; var possibleConstructorReturn = function (self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }; var toConsumableArray = function (arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }; /* * Copyright (c) 2018 Bruce Schubert. * The MIT License * http://www.opensource.org/licenses/mit-license */ /* global WorldWind */ /** * Renders a button for toggling the enabled state of a WorldWind.Layer. * Expects a layer={WorldWind.Layer} property assignment. * @type type */ var LayerButton = function (_Component) { inherits(LayerButton, _Component); function LayerButton(props) { classCallCheck(this, LayerButton); var _this = possibleConstructorReturn(this, (LayerButton.__proto__ || Object.getPrototypeOf(LayerButton)).call(this, props)); _this.onClickHandler = _this.onClickHandler.bind(_this); return _this; } createClass(LayerButton, [{ key: 'onClickHandler', value: function onClickHandler(e) { this.props.globe.toggleLayer(this.props.layer); } }, { key: 'render', value: function render() { var buttonClass = "list-group-item list-group-item-action" + (this.props.enabled ? " active" : ""); return React__default.createElement( 'button', { type: 'button', className: buttonClass, onClick: this.onClickHandler }, this.props.layer.displayName ); } }]); return LayerButton; }(React.Component); LayerButton.propTypes = { layer: PropTypes.instanceOf(WorldWind.Layer).isRequired, globe: PropTypes.instanceOf(Globe).isRequired }; /* * Copyright (c) 2018 Bruce Schubert. * The MIT License * http://www.opensource.org/licenses/mit-license */ var LayerList = function (_Component) { inherits(LayerList, _Component); function LayerList() { classCallCheck(this, LayerList); return possibleConstructorReturn(this, (LayerList.__proto__ || Object.getPrototypeOf(LayerList)).apply(this, arguments)); } createClass(LayerList, [{ key: 'render', value: function render() { var _this2 = this; // Create a list of items for React to render; // Reverse the layers so the top-most layer is displayed first var layerElements = this.props.layers.map(function (layer) { return React__default.createElement(LayerButton, { key: layer.uniqueId, layer: layer, enabled: layer.enabled, globe: _this2.props.globe }); }); layerElements.reverse(); var separator = this.props.separatorAfter ? React__default.createElement('hr', null) : null; return React__default.createElement( 'div', null, React__default.createElement( 'div', { className: 'list-group' }, layerElements ), separator ); } }]); return LayerList; }(React.Component); LayerList.propTypes = { layers: PropTypes.array.isRequired, separatorAfter: PropTypes.bool, globe: PropTypes.instanceOf(Globe) }; var _class; var _temp; /* * Copyright (c) 2018 Bruce Schubert. * The MIT License * http://www.opensource.org/licenses/mit-license */ /** * A collapsable Card for managing layers. */ var LayersCard = mobxReact.observer((_temp = _class = function (_Component) { inherits(LayersCard, _Component); function LayersCard(props) { classCallCheck(this, LayersCard); var _this = possibleConstructorReturn(this, (LayersCard.__proto__ || Object.getPrototypeOf(LayersCard)).call(this, props)); _this.toggle = _this.toggle.bind(_this); _this.state = { isOpen: false }; return _this; } createClass(LayersCard, [{ key: 'toggle', value: function toggle() { this.setState({ isOpen: !this.state.isOpen }); } }, { key: 'render', value: function render() { var _this2 = this; var layerLists = null; if (this.props.globe) { if (this.props.categories.length === 0) { // Use a single list for all layers layerLists = React__default.createElement(LayerList, { layers: this.props.globe.getLayers(), globe: this.props.globe }); } else { // Use an individual layer list for each category var i = 0; layerLists = this.props.categories.map(function (category) { return React__default.createElement(LayerList, { key: category, layers: _this2.props.globe.getLayers(category), globe: _this2.props.globe, separatorAfter: ++i < _this2.props.categories.length, lastUpdate: _this2.props.globe.getCategoryTimestamp(category).get() }); }); } } return React__default.createElement( reactstrap.Collapse, { isOpen: this.state.isOpen }, React__default.createElement( reactstrap.Card, { className: 'globe-card w-100 interactive' }, React__default.createElement( reactstrap.CardHeader, null, React__default.createElement( 'h5', { className: 'card-title' }, React__default.createElement(FontAwesome, { name: this.props.icon }), ' ', this.props.title, React__default.createElement( reactstrap.Button, { className: 'close pull-right', 'aria-label': 'Close', onClick: this.toggle }, React__default.createElement( 'span', { 'aria-hidden': 'true' }, '\xD7' ) ) ) ), React__default.createElement( reactstrap.CardBody, null, layerLists ) ) ); } }]); return LayersCard; }(React.Component), _class.propTypes = { title: PropTypes.string, icon: PropTypes.string, categories: PropTypes.arrayOf(PropTypes.string), globe: PropTypes.instanceOf(Globe) }, _class.defaultProps = { title: 'Layers', icon: 'list', categories: [] }, _temp)); /* * Copyright (c) 2018 Bruce Schubert. * The MIT License * http://www.opensource.org/licenses/mit-license */ /** * A collapsable Card for managing markers. */ var MarkersCard = function (_Component) { inherits(MarkersCard, _Component); function MarkersCard(props) { classCallCheck(this, MarkersCard); var _this = possibleConstructorReturn(this, (MarkersCard.__proto__ || Object.getPrototypeOf(MarkersCard)).call(this, props)); _this.toggle = _this.toggle.bind(_this); _this.state = { markers: [], isOpen: false }; return _this; } createClass(MarkersCard, [{ key: 'toggle', value: function toggle() { this.setState({ isOpen: !this.state.isOpen }); } }, { key: 'addMarker', value: function addMarker(marker) { // Ensure each marker has a unique ID if (!marker.uniqueId) { marker.uniqueId = MarkersCard.nextMarkerId++; } // Create a new array from the previous array + marker this.setState(function (prevState) { return { markers: [].concat(toConsumableArray(prevState.markers), [marker]) }; }); } }, { key: 'gotoMarker', value: function gotoMarker(marker) { this.props.globe.goTo(marker.position.latitude, marker.position.longitude); } }, { key: 'editMarker', value: function editMarker(marker) { alert("TODO: handleEditMarker"); } }, { key: 'removeMarker', value: function removeMarker(marker) { // Find and remove the marker from the layer and the state array var globe = this.props.globe; var layerName = this.props.markersLayerName; var markerLayer = globe.getLayer(layerName); for (var i = 0, max = this.state.markers.length; i < max; i++) { var placemark = markerLayer.renderables[i]; if (placemark === marker) { markerLayer.renderables.splice(i, 1); break; } } var markers = this.state.markers.filter(function (item) { return item !== marker; }); this.setState({ markers: markers }); } }, { key: 'render', value: function render() { if (!this.props.globe) { return null; } var self = this; function GotoButton(props) { return React__default.createElement( 'button', { type: 'button', className: 'btn btn-light', onClick: function onClick(e) { return self.gotoMarker(props.marker, e); } }, React__default.createElement( 'span', null, React__default.createElement('img', { width: '16px', height: '16px', src: props.marker.attributes.imageSource, alt: '' }), ' ' ), props.marker.label ); } function EditButton(props) { return React__default.createElement( 'button', { type: 'button', className: 'btn btn-light', disabled: true, onClick: self.editMarker.bind(self, props.marker) }, React__default.createElement(FontAwesome, { name: 'edit' }) ); } function RemoveButton(props) { return React__default.createElement( 'button', { type: 'button', className: 'btn btn-light', onClick: function onClick(e) { return self.removeMarker(props.marker, e); } }, React__default.createElement(FontAwesome, { name: 'trash' }) ); } function MarkerItem(props) { return React__default.createElement( 'li', { className: 'list-group-item list-group-item-action p-0' }, React__default.createElement( 'div', { className: 'btn-group', role: 'group' }, React__default.createElement(GotoButton, { marker: props.marker }), React__default.createElement(EditButton, { marker: props.marker }), React__default.createElement(RemoveButton, { marker: props.marker }) ) ); } var markerList = this.state.markers.map(function (marker) { return React__default.createElement(MarkerItem, { key: marker.uniqueId, marker: marker }); }); return React__default.createElement( reactstrap.Collapse, { isOpen: this.state.isOpen }, React__default.createElement( reactstrap.Card, { className: 'globe-card w-100 interactive' }, React__default.createElement( reactstrap.CardHeader, null, React__default.createElement( 'h5', { className: 'card-title' }, React__default.createElement(FontAwesome, { name: this.props.icon }), ' ', this.props.title, React__default.createElement( reactstrap.Button, { className: 'close pull-right', 'aria-label': 'Close', onClick: this.toggle }, React__default.createElement( 'span', { 'aria-hidden': 'true' }, '\xD7' ) ) ) ), React__default.createElement( reactstrap.CardBody, null, React__default.createElement( 'ul', { className: 'list-group' }, markerList ) ) ) ); } }]); return MarkersCard; }(React.Component); MarkersCard.propTypes = { title: PropTypes.string, icon: PropTypes.string, globe: PropTypes.instanceOf(Globe), markersLayerName: PropTypes.string.isRequired }; MarkersCard.defaultProps = { title: 'Markers', icon: 'map-marker' }; MarkersCard.nextMarkerId = 1; /* * Copyright (c) 2018 Bruce Schubert. * The MIT License * http://www.opensource.org/licenses/mit-license */ var NavBar = function (_Component) { inherits(NavBar, _Component); function NavBar(props) { classCallCheck(this, NavBar); var _this = possibleConstructorReturn(this, (NavBar.__proto__ || Object.getPrototypeOf(NavBar)).call(this, props)); _this.toggle = _this.toggle.bind(_this); _this.state = { isOpen: false }; return _this; } createClass(NavBar, [{ key: 'toggle', value: function toggle() { this.setState({ isOpen: !this.state.isOpen }); } }, { key: 'componentDidMount', value: function componentDidMount() { // Auto-collapse the _mobile_ main menu when its button items are clicked var self = this; $('.navbar-collapse a[role="button"]').click(function () { self.setState({ isOpen: false }); }); } /** * Renders a BootStrap NavBar with branding, buttons and a search box. * @returns {String} */ }, { key: 'render', value: function render() { var logo = this.props.logo ? React__default.createElement('img', { src: this.props.logo, width: '32', height: '32', className: 'd-inline-block align-top', alt: '' }) : null; return React__default.createElement( reactstrap.Navbar, { dark: true, color: 'dark', fixed: 'top', expand: 'md' }, React__default.createElement( reactstrap.NavbarBrand, { href: this.props.href }, logo, this.props.title ), React__default.createElement(reactstrap.NavbarToggler, { onClick: this.toggle }), React__default.createElement( reactstrap.Collapse, { isOpen: this.state.isOpen, navbar: true }, React__default.createElement( reactstrap.Nav, { className: 'mr-auto', navbar: true }, this.props.items ), React__default.createElement( reactstrap.Nav, { className: 'ml-auto', navbar: true }, this.props.search ) ) ); } }]); return NavBar; }(React.Component); NavBar.propTypes = { title: PropTypes.string.isRequired, href: PropTypes.string, logo: PropTypes.string, items: PropTypes.array, search: PropTypes.object }; /* * Copyright (c) 2018 Bruce Schubert. * The MIT License * http://www.opensource.org/licenses/mit-license */ var NavBarItem = function (_Component) { inherits(NavBarItem, _Component); function NavBarItem(props) { classCallCheck(this, NavBarItem); var _this = possibleConstructorReturn(this, (NavBarItem.__proto__ || Object.getPrototypeOf(NavBarItem)).call(this, props)); _this.toggle = _this.toggle.bind(_this); return _this; } createClass(NavBarItem, [{ key: 'toggle', value: function toggle() { if (this.props.collapse) { this.props.collapse.toggle(); } } /** * Renders a 'reactstrap' NavItem for a Navbar. * @returns {String} */ }, { key: 'render', value: function render() { return React__default.createElement( reactstrap.NavItem, null, React__default.createElement( reactstrap.NavLink, { role: 'button', onClick: this.toggle }, React__default.createElement(FontAwesome, { name: this.props.icon }), React__default.createElement( 'span', { className: 'd-md-none d-lg-inline pl-1', 'aria-hidden': 'true' }, this.props.title ) ) ); } }]); return NavBarItem; }(React.Component); NavBarItem.propTypes = { title: PropTypes.string.isRequired, icon: PropTypes.string.isRequired, collapse: PropTypes.object }; var _class$1; var _temp$1; /* * Copyright (c) 2018 Bruce Schubert. * The MIT License * http://www.opensource.org/licenses/mit-license */ /** * A collapsible Card for managing settings. */ var SettingsCard = mobxReact.observer((_temp$1 = _class$1 = function (_Component) { inherits(SettingsCard, _Component); function SettingsCard(props) { classCallCheck(this, SettingsCard); var _this = possibleConstructorReturn(this, (SettingsCard.__proto__ || Object.getPrototypeOf(SettingsCard)).call(this, props)); _this.toggle = _this.toggle.bind(_this); _this.state = { isOpen: false }; return _this; } createClass(SettingsCard, [{ key: 'toggle', value: function toggle() { this.setState({ isOpen: !this.state.isOpen }); } }, { key: 'render', value: function render() { var _this2 = this; var layerLists = null; if (this.props.globe) { if (this.props.categories.length === 0) { // Use a single list for all layers layerLists = React__default.createElement(LayerList, { layers: this.props.globe.getLayers(), globe: this.props.globe }); } else { // Use an individual layer list for each category var i = 0; layerLists = this.props.categories.map(function (category) { return React__default.createElement(LayerList, { key: category, layers: _this2.props.globe.getLayers(category), globe: _this2.props.globe, separatorAfter: ++i < _this2.props.categories.length, lastUpdate: _this2.props.globe.getCategoryTimestamp(category).get() }); }); } } return React__default.createElement( reactstrap.Collapse, { isOpen: this.state.isOpen }, React__default.createElement( reactstrap.Card, { className: 'globe-card w-100 interactive' }, React__default.createElement( reactstrap.CardHeader, null, React__default.createElement( 'h5', { className: 'card-title' }, React__default.createElement(FontAwesome, { name: this.props.icon }), ' ', this.props.title, React__default.createElement( reactstrap.Button, { className: 'close pull-right', 'aria-label': 'Close', onClick: this.toggle }, React__default.createElement( 'span', { 'aria-hidden': 'true' }, '\xD7' ) ) ) ), React__default.createElement( reactstrap.CardBody, null, layerLists ) ) ); } }]); return SettingsCard; }(React.Component), _class$1.propTypes = { title: PropTypes.string, icon: PropTypes.string, categories: PropTypes.arrayOf(PropTypes.string), globe: PropTypes.instanceOf(Globe) }, _class$1.defaultProps = { title: 'Settings', icon: 'cog', categories: [] }, _temp$1)); function styleInject(css, ref) { if ( ref === void 0 ) ref = {}; var insertAt = ref.insertAt; if (!css || typeof document === 'undefined') { return; } var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; if (insertAt === 'top') { if (head.firstChild) { head.insertBefore(style, head.firstChild); } else { head.appendChild(style); } } else { head.appendChild(style); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } } var css = ".SearchPreview_globe__2OJMW {\n height: 200px;\n}\n\n\n/*Sets the maximum height of the entire modal to 100% of the screen height*/\n.SearchPreview_modal__3x6dh {\n margin: 0 auto;\n max-height: 100vh;\n}\n\n/*Sets the maximum height of the modal body to 90% of the screen height*/\n.SearchPreview_body__2SnNt {\n max-height: 90vh;\n}\n\n\n/*Sets the height of a modal's canvas to 60% minus the height of a footer*/\n.SearchPreview_table__1F00P {\n max-height: calc(60vh - 71px);\n overflow-y: auto;\n}\n\n"; var style = { "globe": "SearchPreview_globe__2OJMW", "modal": "SearchPreview_modal__3x6dh", "body": "SearchPreview_body__2SnNt", "table": "SearchPreview_table__1F00P" }; styleInject(css); /* * Copyright (c) 2018 Bruce Schubert. * The MIT License * http://www.opensource.org/licenses/mit-license */ /* global WorldWind */ var SearchPreview = function (_Component) { inherits(SearchPreview, _Component); function SearchPreview(props) { classCallCheck(this, SearchPreview); var _this = possibleConstructorReturn(this, (SearchPreview.__proto__ || Object.getPrototypeOf(SearchPreview)).call(this, props)); _this.state = { showWarning: _this.props.showApiWarning }; _this.dismissWarning = _this.dismissWarning.bind(_this); _this.handleGotoClick = _this.handleGotoClick.bind(_this); _this.handlePreviewClick = _this.handlePreviewClick.bind(_this); _this.globeRef = React__default.createRef(); _this.globe = null; _this.selection = null; _this.resultsLayer = null; _this.placemarkAttributes = new WorldWind.PlacemarkAttributes(null); _this.placemarkAttributes.imageSource = "https://files.worldwind.arc.nasa.gov/artifactory/web/0.9.0/images/pushpins/castshadow-red.png"; _this.placemarkAttributes.imageScale = 0.5; _this.placemarkAttributes.imageOffset = new WorldWind.Offset(WorldWind.OFFSET_FRACTION, 0.3, WorldWind.OFFSET_FRACTION, 0.0); return _this; } createClass(SearchPreview, [{ key: 'dismissWarning', value: function dismissWarning() { this.setState({ showWarning: false }); } }, { key: 'handlePreviewClick', value: function handlePreviewClick(result) { var latitude = parseFloat(result.lat), longitude = parseFloat(result.lon); this.globeRef.current.goTo(latitude, longitude); // Update the selection used for the Go To button this.selection = result; } }, { key: 'handleGotoClick', value: function handleGotoClick() { // Call the parent's handler to process the selection this.props.handleGotoSelection(this.selection); } }, { key: 'createMarker', value: function createMarker(result) { var marker = new WorldWind.Placemark(new WorldWind.Position(parseFloat(result.lat), parseFloat(result.lon), 100)); marker.attributes = this.placemarkAttributes; marker.altitudeMode = WorldWind.RELATIVE_TO_GROUND; marker.displayName = result.display_name; return marker; } /** * Updates the preview globe AFTER the modal is shown. * This cannot be done in componentDidMount because the modal is not being shown * at that time * @param {type} prevProps * @param {type} prevState */ }, { key: 'componentDidUpdate', value: function componentDidUpdate(prevProps, prevState) { var _this2 = this; if (this.globeRef.current) { var globe = this.globeRef.current; var resultsLayer = globe.addLayer('renderables'); this.props.results.forEach(function (result) { resultsLayer.addRenderable(_this2.createMarker(result)); }); } } }, { key: 'render', value: function render() { var _this3 = this; this.selection = this.props.results[0]; var tableRows = this.props.results.map(function (result) { return React__default.createElement( 'tr', { key: result.place_id, onClick: function onClick() { return _this3.handlePreviewClick(result); } }, React__default.createElement( 'td', null, result.display_name ), React__default.createElement( 'td', null, result.type ) ); }); return React__default.createElement( reactstrap.Modal, { isOpen: this.props.results.length > 0, toggle: this.props.handleHideModal, className: style.modal }, React__default.createElement( reactstrap.ModalHeader, { toggle: this.props.handleHideModal }, this.props.title ), React__default.createElement( reactstrap.ModalBody, null, React__default.createElement( 'div', { className: style.globe }, React__default.createElement(Globe, { id: 'preview-globe', projection: 'Mercator', ref: this.globeRef }) ), React__default.createElement( 'div', { className: style.table }, React__default.createElement( reactstrap.Alert, { color: 'warning', isOpen: this.state.showWarning }, React__default.createElement( reactstrap.Button, { className: 'close pull-right', 'aria-label': 'Close', onClick: this.dismissWarning }, React__default.createElement( 'span', { 'aria-hidden': 'true' }, '\xD7' ) ), 'MapQuest API key missing. Get a free key at ', React__default.createElement( 'a', { href: 'https://developer.globequest.com/', className: 'alert-link', rel: 'noopener noreferrer', target: '_blank' }, 'developer.globequest.com' ), ' and set the MAPQUEST_API_KEY variable to your key.' ), React__default.createElement( 'table', { className: 'table table-hover' }, React__default.createElement( 'thead', null, React__default.createElement( 'tr', null, React__default.createElement( 'th', { scope: 'col' }, 'Name' ), React__default.createElement( 'th', { scope: 'col' }, 'Type' ) ) ), React__default.createElement( 'tbody', null, tableRows ) ) ) ), React__default.createElement( reactstrap.ModalFooter, null, React__default.createElement( reactstrap.Button, { onClick: this.handleGotoClick }, 'Go To' ), ' ', React__default.createElement( reactstrap.Button, { onClick: this.props.handleHideModal }, 'Cancel' ) ) ); } }]); return SearchPreview; }(React.Component); SearchPreview.propTypes = { title: PropTypes.string, results: PropTypes.array.isRequired, handleHideModal: PropTypes.func.isRequired, handleGotoSelection: PropTypes.func.isRequired, showApiWarning: PropTypes.bool }; SearchPreview.defaultProps = { title: 'Search Results', showApiWarning: true }; /* * Copyright (c) 2018 Bruce Schubert. * The MIT License * http://www.opensource.org/licenses/mit-license */ /* global WorldWind */ var SearchBox = function (_React$Component) { inherits(SearchBox, _React$Component); function SearchBox(props) { classCallCheck(this, SearchBox); var _this = possibleConstructorReturn(this, (SearchBox.__proto__ || Object.getPrototypeOf(SearchBox)).call(this, props)); _this.state = { value: '', results: [] }; _this.geocoder = new WorldWind.NominatimGeocoder(); _this.handleChange = _this.handleChange.bind(_this); _this.handleClick = _this.handleClick.bind(_this); _this.handleSubmit = _this.handleSubmit.bind(_this); _this.hideModal = _this.hideModal.bind(_this); _this.gotoSelection = _this.gotoSelection.bind(_this); return _this; } createClass(SearchBox, [{ key: 'handleChange', value: function handleChange(event) { this.setState({ value: event.target.value }); } }, { key: 'handleClick', value: function handleClick(event) { event.preventDefault(); this.performSearch(); } }, { key: 'handleSubmit', value: function handleSubmit(event) { event.preventDefault(); this.performSearch(); return false; // prevent page from refreshing } }, { key: 'hideModal', value: function hideModal() { // Nothing to display this.setState({ results: [] }); } }, { key: 'gotoSelection', value: function gotoSelection(selection) { if (selection) { var latitude = parseFloat(selection.lat); var longitude = parseFloat(selection.lon); // Update the globe this.props.globe.goTo(latitude, longitude); this.hideModal(); } } }, { key: 'performSearch', value: function performSearch() { var globe = this.props.globe; if (!this.props.mapQuestApiKey) { console.error("SearchViewModel: A MapQuest API key is required to use the geocoder in production. Get your API key at https://developer.mapquest.com/"); } var queryString = this.state.value; if (queryString) { if (queryString.match(WorldWind.WWUtil.latLonRegex)) { // Treat the text as a lat, lon pair var tokens = queryString.split(","); var latitude = parseFloat(tokens[0]); var longitude = parseFloat(tokens[1]); // Center the globe on the lat, lon globe.wwd.goTo(new WorldWind.Location(latitude, longitude)); } else { // Treat the text as an address or place name var self = this; this.geocoder.lookup(queryString, function (geocoder, results) { // Open the modal dialog to preview and select a result // The modal is rendered when results > 0 self.setState({ results: results }); }, this.props.mapQuestApiKey); } } } }, { key: 'render', value: function render() { return React__default.createElement( 'form', { className: 'form-inline', onSubmit: this.handleSubmit }, React__default.createElement('input', { className: 'form-control mr-sm-2', type: 'text', placeholder: 'Search', 'aria-label': 'Search', value: this.state.value, onChange: this.handleChange }), React__default.createElement( reactstrap.Button, { color: 'success', onClick: this.handleClick }, React__default.createElement(FontAwesome, { name: 'search' }) ), React__default.createElement(SearchPreview, { results: this.state.results, handleHideModal: this.hideModal, handleGotoSelection: this.gotoSelection, showApiWarning: !this.props.mapQuestApiKey }) ); } }]); return SearchBox; }(React__default.Component); SearchBox.propTypes = { globe: PropTypes.instanceOf(Globe), mapQuestApiKey: PropTypes.string }; var css$1 = "\n.Tools_button__3GjWS {\n background:rgba(255,255,255,0.35);\n padding: 0;\n}\n\n.Tools_toggle___sQ5X {\n background:rgba(255,255,255,0.35) !important;\n width: 30px;\n padding: 0;\n}\n\n.Tools_dropdown__20fz1 {\n background:rgba(255,255,255,0.35) !important;\n min-width: 30px !important;\n}\n\n.Tools_image__2coNL {\n width: 30px;\n height: 30px;\n}"; var style$1 = { "button": "Tools_button__3GjWS", "toggle": "Tools_toggle___sQ5X", "dropdown": "Tools_dropdown__20fz1", "image": "Tools_image__2coNL" }; styleInject(css$1); /* * Copyright (c) 2018 Bruce Schubert. * The MIT License * http://www.opensource.org/licenses/mit-license */ /* global WorldWind */ var Tools = function (_Component) { inherits(Tools, _Component); function Tools(props) { classCallCheck(this, Tools); var _this = possibleConstructorReturn(this, (Tools.__proto__ || Object.getPrototypeOf(Tools)).call(this, props)); _this.state = { selectedMarkerImage: Tools.pushpins[0], dropdownOpen: false }; _this.isDropArmed = false; _this.dropCallback = null; _this.dropMarkerCallback = _this.dropMarkerCallback.bind(_this); _this.toggle = _this.toggle.bind(_this); return _this; } createClass(Tools, [{ key: 'toggle', value: function toggle() { this.setState({ dropdownOpen: !this.state.dropdownOpen }); } }, { key: 'selectPushpin', value: function selectPushpin(pushpin) { this.setState({ selectedMarkerImage: pushpin }); this.armDropMarker(); } }, { key: 'armDropMarker', value: function armDropMarker() { this.props.globe.armClickDrop(this.dropMarkerCallback); } }, { key: 'dropMarkerCallback', value: function dropMarkerCallback(position) { // Create a placemark using the selected marker image var attributes = new WorldWind.PlacemarkAttributes(null); attributes.imageScale = 0.8; attributes.imageOffset = new WorldWind.Offset(WorldWind.OFFSET_FRACTION, 0.3, WorldWind.OFFSET_FRACTION, 0.0); attributes.imageColor = WorldWind.Color.WHITE; attributes.labelAttributes.offset = new WorldWind.Offset(WorldWind.OFFSET_FRACTION, 0.5, WorldWind.OFFSET_FRACTION, 1.0); attributes.labelAttributes.color = WorldWind.Color.YELLOW; attributes.drawLeaderLine = true; attributes.leaderLineAttributes.outlineColor = WorldWind.Color.RED; attributes.imageSource = this.state.selectedMarkerImage; var placemark = new WorldWind.Placemark(position, /*eyeDistanceScaling*/true, attributes); placemark.label = "Lat " + position.latitude.toPrecision(4).toString() + "\nLon " + position.longitude.toPrecision(5).toString(); placemark.altitudeMode = WorldWind.CLAMP_TO_GROUND; placemark.eyeDistanceScalingThreshold = 2500000; // Add the placemark to the layer and to the Markers component var globe = this.props.globe; var layer = globe.getLayer(this.props.markersLayerName); if (layer) { // Add the placemark to the globe layer.addRenderable(placemark); // Add the placemark to the Markers component this.props.markers.addMarker(placemark); } else { console.warn("Renderable layer for markers not found: " + this.props.markersLayerName); } } }, { key: 'render', value: function render() { var _this2 = this; // Wait for the globe to be intialized before rendering this component if (!this.props.globe) { return null; } // Create a tool palette with dropdowns var dropdownItems = Tools.pushpins.map(function (pushpin) { return React__default.createElement( reactstrap.DropdownItem, { key: pushpin, onClick: function onClick() { return _this2.selectPushpin(pushpin); }, className: style$1.button }, React__default.createElement('img', { className: style$1.image, src: pushpin, alt: 'Selected Marker' }) ); }); return React__default.createElement( 'div', { className: 'btn-group interactive p-3' }, React__default.createElement( reactstrap.Button, { className: style$1.button + ' p-1', onClick: function onClick() { return _this2.armDropMarker(); } }, React__default.createElement(FontAwesome, { name: 'plus' }), React__default.createElement('img', { className: style$1.image, src: this.state.selectedMarkerImage, alt: 'Marker' }) ), React__default.createElement( reactstrap.ButtonDropdown, { isOpen: this.state.dropdownOpen, toggle: this.toggle }, React__default.createElement(reactstrap.DropdownToggle, { caret: true, className: style$1.toggle }), React__default.createElement( reactstrap.DropdownMenu, { className: style$1.dropdown }, dropdownItems ) ) ); } }]); return Tools; }(React.Component); Tools.propTypes = { globe: PropTypes.instanceOf(Globe), markers: PropTypes.instanceOf(MarkersCard), markersLayerName: PropTypes.string }; Tools.pushpins = ["https://files.worldwind.arc.nasa.gov/artifactory/web/0.9.0/images/pushpins/castshadow-red.png", "https://files.worldwind.arc.nasa.gov/artifactory/web/0.9.0/images/pushpins/castshadow-green.png", "https://files.worldwind.arc.nasa.gov/artifactory/web/0.9.0/images/pushpins/castshadow-blue.png", "https://files.worldwind.arc.nasa.gov/artifactory/web/0.9.0/images/pushpins/castshadow-orange.png", "https://files.worldwind.arc.nasa.gov/artifactory/web/0.9.0/images/pushpins/castshadow-teal.png", "https://files.worldwind.arc.nasa.gov/artifactory/web/0.9.0/images/pushpins/castshadow-purple.png", "https://files.worldwind.arc.nasa.gov/artifactory/web/0.9.0/images/pushpins/castshadow-white.png", "https://files.worldwind.arc.nasa.gov/artifactory/web/0.9.0/images/pushpins/castshadow-black.png"]; /* * Copyright (c) 2018 Bruce Schubert. * The MIT License * http://www.opensource.org/licenses/mit-license */ exports.LayerList = LayerList; exports.LayerButton = LayerButton; exports.LayersCard = LayersCard; exports.MarkersCard = MarkersCard; exports.NavBar = NavBar; exports.NavBarItem = NavBarItem; exports.SettingsCard = SettingsCard; exports.SearchBox = SearchBox; exports.SearchPreview = SearchPreview; exports.Tools = Tools;