UNPKG

kepler.gl

Version:

kepler.gl is a webgl based application to visualize large scale location data in the browser

382 lines (343 loc) 11.1 kB
// Copyright (c) 2021 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import React, {Component} from 'react'; import styled from 'styled-components'; import moment from 'moment'; import LoadingDialog from './loading-dialog'; import {Button} from 'components/common/styled-components'; import CloudTile from './cloud-tile'; import {Base, ArrowLeft} from 'components/common/icons'; import ProviderModalContainer from './provider-modal-container'; import {FormattedMessage} from 'localization'; const StyledProviderSection = styled.div.attrs({ className: 'provider-selection' })` display: flex; `; const StyledSpinner = styled.div` text-align: center; span { margin: 0 auto; } `; const StyledVisualizationSection = styled.div` display: flex; flex-direction: column; align-items: stretch; `; const StyledStorageHeader = styled.div` display: flex; flex-direction: row; justify-content: space-between; align-items: center; margin-bottom: 16px; font-size: 12px; line-height: 14px; `; const StyledBackBtn = styled.a` margin-bottom: 16px; color: #3a414c; cursor: pointer; &:hover { font-weight: 500; } `; const StyledProviderVisSection = styled.div` flex: 1 1 auto; background-color: #f8f8f9; padding: 20px 24px; min-height: 280px; .title { font-size: 14px; line-height: 16px; font-weight: 500; margin-bottom: 16px; span { text-transform: capitalize; } } `; const StyledSeparator = styled.hr` border: solid #bfbfbf; border-width: 0 0 1px 0; margin-bottom: 16px; `; const StyledVisualizationList = styled.div` display: flex; flex-flow: row wrap; align-items: stretch; justify-content: space-between; `; const StyledVisualizationItem = styled.div` flex: 0 0 auto; width: 208px; display: flex; flex-direction: column; padding: 16px 8px; color: #3a414c; cursor: pointer; font-size: 12px; line-height: 18px; &:hover { .vis_item-icon, .vis_item-thumb, .vis_item-description, .vis_item-modification-date { opacity: 1; } } .vis_item-icon, .vis_item-thumb, .vis_item-description, .vis_item-modification-date { opacity: 0.9; transition: opacity 0.4s ease; } .vis_item-icon { position: relative; flex: 0 0 108px; background-color: #6a7484; border-radius: 4px; display: flex; flex-direction: row; align-items: center; justify-content: center; } .vis_item-thumb { position: relative; flex: 0 0 108px; background-size: cover; background-position: center; border-radius: 4px; } .vis_item-privacy { position: absolute; top: 0; left: 0; padding: 3px 6px; border-radius: 4px 0; background-color: rgba(58, 65, 76, 0.7); color: #fff; font-size: 11px; line-height: 18px; } .vis_item-title { margin-top: 16px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .vis_item-description { flex: 1 1 auto; margin-top: 8px; } .vis_item-modification-date { margin-top: 16px; flex: 1 0 auto; color: #6a7484; line-height: 15px; } `; const MapIcon = props => { return ( <div {...props}> {props.children} <Base height="32px" viewBox={'0 0 16 16'}> <path fill="#d3d8d6" d="m13.6 11.572-3.2 2.1336v-9.2776l3.2-2.1336zm-12-7.144 3.2-2.1336v9.2776l-3.2 2.1336zm13.244 8.2376c0.2224-0.148 0.356-0.3984 0.356-0.6656v-11.2c0-0.2952-0.1624-0.5664-0.4224-0.7048-0.26-0.14-0.576-0.1248-0.8216 0.0392l-4.3128 2.876-3.5432-2.8352c-0.1208-0.0936-0.2952-0.1624-0.472-0.1688-0.1648-0.0064-0.348 0.0464-0.472 0.128l-4.8 3.2c-0.2224 0.1488-0.356 0.3984-0.356 0.6656v11.2c0 0.2952 0.1624 0.5664 0.4224 0.7056 0.1184 0.0632 0.248 0.0944 0.3776 0.0944 0.1552 0 0.3096-0.0448 0.444-0.1344l4.3128-2.876 3.5432 2.8352c0.1448 0.116 0.3216 0.1752 0.5 0.1752 0.1184 0 0.236-0.0248 0.3464-0.0784z" /> </Base> </div> ); }; const PrivacyBadge = ({privateMap}) => ( <span className="vis_item-privacy">{privateMap ? 'Private' : 'Public'}</span> ); const VisualizationItem = ({vis, onClick}) => { return ( <StyledVisualizationItem onClick={onClick}> {vis.thumbnail ? ( <div className="vis_item-thumb" style={{backgroundImage: `url(${vis.thumbnail})`}}> {vis.hasOwnProperty('privateMap') ? <PrivacyBadge privateMap={vis.privateMap} /> : null} </div> ) : ( <MapIcon className="vis_item-icon"> {vis.hasOwnProperty('privateMap') ? <PrivacyBadge privateMap={vis.privateMap} /> : null} </MapIcon> )} <span className="vis_item-title">{vis.title}</span> {vis.description && vis.description.length && ( <span className="vis_item-description">{vis.description}</span> )} <span className="vis_item-modification-date"> Last modified {moment.utc(vis.lastModification).fromNow()} </span> </StyledVisualizationItem> ); }; export const ProviderSelect = ({ cloudProviders = [], onSelect, onSetCloudProvider, currentProvider }) => cloudProviders.length ? ( <StyledProviderSection> {cloudProviders.map(provider => ( <CloudTile key={provider.name} onSelect={() => onSelect(provider.name)} onSetCloudProvider={onSetCloudProvider} cloudProvider={provider} isSelected={provider.name === currentProvider} isConnected={Boolean(provider.getAccessToken && provider.getAccessToken())} /> ))} </StyledProviderSection> ) : ( <p>No storage provider available</p> ); function LoadStorageMapFactory() { class LoadStorageMap extends Component { state = { showProviderSelect: true }; componentDidMount() { this._getSavedMaps(); } componentDidUpdate(prevProps) { if (prevProps.currentProvider !== this.props.currentProvider) { this._getSavedMaps(); } } _getProvider = () => { const {currentProvider, cloudProviders} = this.props; return (cloudProviders || []).find(p => p.name === currentProvider); }; _getSavedMaps() { const provider = this._getProvider(); if (provider) { this.props.getSavedMaps(provider); this.setState({showProviderSelect: false}); } } _onLoadCloudMap(provider, vis) { this.props.onLoadCloudMap({ loadParams: vis.loadParams, provider }); } _clickBack = () => { this.setState({showProviderSelect: true}); }; _selectProvider = providerName => { this.props.onSetCloudProvider(providerName); const provider = (this.props.cloudProviders || []).find(p => p.name === providerName); this.props.getSavedMaps(provider); this.setState({showProviderSelect: false}); }; render() { const { visualizations, cloudProviders, currentProvider, isProviderLoading, onSetCloudProvider } = this.props; const provider = this._getProvider(); return ( <ProviderModalContainer onSetCloudProvider={onSetCloudProvider} cloudProviders={cloudProviders} currentProvider={currentProvider} > {this.state.showProviderSelect ? ( <ProviderSelect onSelect={this._selectProvider} cloudProviders={cloudProviders} onSetCloudProvider={onSetCloudProvider} currentProvider={currentProvider} /> ) : ( <> {isProviderLoading && ( <StyledSpinner> <LoadingDialog size={64} /> </StyledSpinner> )} {!isProviderLoading && visualizations && ( <StyledVisualizationSection> <StyledStorageHeader> <StyledBackBtn> <Button link onClick={this._clickBack}> <ArrowLeft height="14px" /> <FormattedMessage id={'modal.loadStorageMap.back'} /> </Button> </StyledBackBtn> {provider.getManagementUrl && ( <a key={1} href={provider.getManagementUrl()} target="_blank" rel="noopener noreferrer" style={{textDecoration: 'underline'}} > <FormattedMessage id={'modal.loadStorageMap.back'} values={{displayName: provider.displayName}} /> </a> )} </StyledStorageHeader> <StyledProviderVisSection> <span className="title"> <span>{currentProvider}</span> <FormattedMessage id={'modal.loadStorageMap.storageMaps'} /> </span> <StyledSeparator /> <StyledVisualizationList> {visualizations.length ? ( visualizations.map(vis => ( <VisualizationItem key={vis.id} onClick={() => this._onLoadCloudMap(provider, vis)} vis={vis} /> )) ) : ( <div className="visualization-list__message"> <FormattedMessage id={'modal.loadStorageMap.noSavedMaps'} /> </div> )} </StyledVisualizationList> </StyledProviderVisSection> </StyledVisualizationSection> )} </> )} </ProviderModalContainer> ); } } return LoadStorageMap; } export default LoadStorageMapFactory;