UNPKG

kepler.gl

Version:

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

302 lines (264 loc) 9.11 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, createRef} from 'react'; import styled from 'styled-components'; import {injectIntl} from 'react-intl'; import UploadButton from './upload-button'; import {DragNDrop, FileType} from 'components/common/icons'; import FileUploadProgress from 'components/common/file-uploader/file-upload-progress'; import FileDrop from './file-drop'; import {isChrome} from 'utils/utils'; import {GUIDES_FILE_FORMAT_DOC} from 'constants/user-guides'; import ReactMarkdown from 'react-markdown'; // Breakpoints import {media} from 'styles/media-breakpoints'; import {FormattedMessage} from 'localization'; /** @typedef {import('./file-upload').FileUploadProps} FileUploadProps */ const fileIconColor = '#D3D8E0'; const LinkRenderer = props => { return ( <a href={props.href} target="_blank" rel="noopener noreferrer"> {props.children} </a> ); }; const StyledUploadMessage = styled.div` color: ${props => props.theme.textColorLT}; font-size: 14px; margin-bottom: 12px; ${media.portable` font-size: 12px; `}; `; export const WarningMsg = styled.span` margin-top: 10px; color: ${props => props.theme.errorColor}; font-weight: 500; `; const StyledFileDrop = styled.div` background-color: white; border-radius: 4px; border-style: ${props => (props.dragOver ? 'solid' : 'dashed')}; border-width: 1px; border-color: ${props => (props.dragOver ? props.theme.textColorLT : props.theme.subtextColorLT)}; text-align: center; width: 100%; padding: 48px 8px 0; height: 360px; .file-upload-or { color: ${props => props.theme.linkBtnColor}; padding-right: 4px; } .file-type-row { opacity: 0.5; } ${media.portable` padding: 16px 4px 0; `}; `; const MsgWrapper = styled.div` color: ${props => props.theme.modalTitleColor}; font-size: 20px; height: 36px; `; const StyledDragNDropIcon = styled.div` color: ${fileIconColor}; margin-bottom: 48px; ${media.portable` margin-bottom: 16px; `}; ${media.palm` margin-bottom: 8px; `}; `; const StyledFileTypeFow = styled.div` margin-bottom: 24px; ${media.portable` margin-bottom: 16px; `}; ${media.palm` margin-bottom: 8px; `}; `; const StyledFileUpload = styled.div` .file-drop { position: relative; } `; const StyledMessage = styled.div` display: flex; justify-content: center; align-items: center; margin-bottom: 32px; .loading-action { margin-right: 10px; } .loading-spinner { margin-left: 10px; } `; const StyledDragFileWrapper = styled.div` margin-bottom: 32px; ${media.portable` margin-bottom: 24px; `}; ${media.portable` margin-bottom: 16px; `}; `; const StyledDisclaimer = styled(StyledMessage)` margin: 0 auto; `; function FileUploadFactory() { /** @augments {Component<FileUploadProps>} */ class FileUpload extends Component { state = { dragOver: false, fileLoading: false, files: [], errorFiles: [] }; static getDerivedStateFromProps(props, state) { if (state.fileLoading && props.fileLoading === false && state.files.length) { return { files: [], fileLoading: props.fileLoading }; } return { fileLoading: props.fileLoading }; } frame = createRef(); _isValidFileType = filename => { const {fileExtensions = []} = this.props; const fileExt = fileExtensions.find(ext => filename.endsWith(ext)); return Boolean(fileExt); }; /** @param {FileList} fileList */ _handleFileInput = (fileList, event) => { if (event) { event.stopPropagation(); } const files = [...fileList].filter(Boolean); const {disableExtensionFilter = false} = this.props; // TODO - move this code out of the component const filesToLoad = []; const errorFiles = []; for (const file of files) { if (disableExtensionFilter || this._isValidFileType(file.name)) { filesToLoad.push(file); } else { errorFiles.push(file.name); } } const nextState = {files: filesToLoad, errorFiles, dragOver: false}; this.setState(nextState, () => nextState.files.length ? this.props.onFileUpload(nextState.files) : null ); }; _toggleDragState = newState => { this.setState({dragOver: newState}); }; render() { const {dragOver, files, errorFiles} = this.state; const {fileLoading, fileLoadingProgress, theme, intl} = this.props; const {fileExtensions = [], fileFormatNames = []} = this.props; return ( <StyledFileUpload className="file-uploader" ref={this.frame}> {FileDrop ? ( <FileDrop frame={this.frame.current || document} onDragOver={() => this._toggleDragState(true)} onDragLeave={() => this._toggleDragState(false)} onDrop={this._handleFileInput} className="file-uploader__file-drop" > <StyledUploadMessage className="file-upload__message"> <ReactMarkdown source={`${intl.formatMessage( { id: 'fileUploader.configUploadMessage' }, { fileFormatNames: fileFormatNames.map(format => `**${format}**`).join(', ') } )}(${GUIDES_FILE_FORMAT_DOC}).`} renderers={{link: LinkRenderer}} /> </StyledUploadMessage> <StyledFileDrop dragOver={dragOver}> <StyledFileTypeFow className="file-type-row"> {fileExtensions.map(ext => ( <FileType key={ext} ext={ext} height="50px" fontSize="9px" /> ))} </StyledFileTypeFow> {fileLoading ? ( <FileUploadProgress fileLoadingProgress={fileLoadingProgress} theme={theme} /> ) : ( <> <div style={{opacity: dragOver ? 0.5 : 1}} className="file-upload-display-message" > <StyledDragNDropIcon> <DragNDrop height="44px" /> </StyledDragNDropIcon> {errorFiles.length ? ( <WarningMsg> <FormattedMessage id={'fileUploader.fileNotSupported'} values={{errorFiles: errorFiles.join(', ')}} /> </WarningMsg> ) : null} </div> {!files.length ? ( <StyledDragFileWrapper> <MsgWrapper> <FormattedMessage id={'fileUploader.message'} /> </MsgWrapper> <span className="file-upload-or"> <FormattedMessage id={'fileUploader.or'} /> </span> <UploadButton onUpload={this._handleFileInput}> <FormattedMessage id={'fileUploader.browseFiles'} /> </UploadButton> </StyledDragFileWrapper> ) : null} <StyledDisclaimer> <FormattedMessage id={'fileUploader.disclaimer'} /> </StyledDisclaimer> </> )} </StyledFileDrop> </FileDrop> ) : null} <WarningMsg> {isChrome() ? <FormattedMessage id={'fileUploader.chromeMessage'} /> : ''} </WarningMsg> </StyledFileUpload> ); } } return injectIntl(FileUpload); } export default FileUploadFactory; export const FileUpload = FileUploadFactory();