UNPKG

terriajs

Version:

Geospatial data visualization platform.

314 lines (295 loc) 10.2 kB
import createReactClass from "create-react-class"; import PropTypes from "prop-types"; import React from "react"; import { Trans, withTranslation } from "react-i18next"; import { Category, DatatabAction } from "../../../../Core/AnalyticEvents/analyticEvents"; import getDataType from "../../../../Core/getDataType"; import TimeVarying from "../../../../ModelMixins/TimeVarying"; import addUserCatalogMember from "../../../../Models/Catalog/addUserCatalogMember"; import addUserFiles from "../../../../Models/Catalog/addUserFiles"; import CatalogMemberFactory from "../../../../Models/Catalog/CatalogMemberFactory"; import createCatalogItemFromFileOrUrl from "../../../../Models/Catalog/createCatalogItemFromFileOrUrl"; import CommonStrata from "../../../../Models/Definition/CommonStrata"; import upsertModelFromJson from "../../../../Models/Definition/upsertModelFromJson"; import Icon from "../../../../Styled/Icon"; import Dropdown from "../../../Generic/Dropdown"; import Loader from "../../../Loader"; import Styles from "./add-data.scss"; import FileInput from "./FileInput"; import { parseCustomMarkdownToReactWithOptions } from "../../../Custom/parseCustomMarkdownToReact"; import loadJson from "../../../../Core/loadJson"; import TerriaError from "../../../../Core/TerriaError"; /** * Add data panel in modal window -> My data tab */ const AddData = createReactClass({ displayName: "AddData", propTypes: { terria: PropTypes.object, viewState: PropTypes.object, resetTab: PropTypes.func, activeTab: PropTypes.string, // localDataTypes & remoteDataTypes specifies the file types to show in dropdowns for local and remote data uploads. // These default to the lists defined in getDataType.ts // Some external components use these props to customize the types shown. localDataTypes: PropTypes.arrayOf(PropTypes.object), remoteDataTypes: PropTypes.arrayOf(PropTypes.object), onFileAddFinished: PropTypes.func.isRequired, onUrlAddFinished: PropTypes.func.isRequired, t: PropTypes.func.isRequired }, getInitialState() { const remoteDataTypes = this.props.remoteDataTypes ?? getDataType().remoteDataType; // Automatically suffix supported extension types to localDataType names const localDataTypes = ( this.props.localDataTypes ?? getDataType().localDataType ).map((dataType) => { const extensions = dataType.extensions?.length ? ` (${buildExtensionsList(dataType.extensions)})` : ""; return { ...dataType, name: `${dataType.name}${extensions}` }; }); return { remoteDataTypes, localDataTypes, remoteDataType: remoteDataTypes[0], // By default select the first item (auto) localDataType: localDataTypes[0], remoteUrl: "", // By default there's no remote url isLoading: false }; }, selectLocalOption(option) { this.setState({ localDataType: option }); }, selectRemoteOption(option) { this.setState({ remoteDataType: option }); }, handleUploadFile(e) { this.setState({ isLoading: true }); addUserFiles( e.target.files, this.props.terria, this.props.viewState, this.state.localDataType ).then((addedCatalogItems) => { if (addedCatalogItems && addedCatalogItems.length > 0) { this.props.onFileAddFinished(addedCatalogItems); } this.setState({ isLoading: false }); // reset active tab when file handling is done this.props.resetTab(); }); }, async handleUrl(e) { const url = this.state.remoteUrl; e.preventDefault(); this.props.terria.analytics?.logEvent( Category.dataTab, DatatabAction.addDataUrl, url ); this.setState({ isLoading: true }); let promise; if (this.state.remoteDataType.value === "auto") { promise = createCatalogItemFromFileOrUrl( this.props.terria, this.props.viewState, this.state.remoteUrl, this.state.remoteDataType.value ); } else if (this.state.remoteDataType.value === "json") { promise = loadJson(this.state.remoteUrl) .then((data) => { if (data.error) { return Promise.reject(data.error); } this.props.terria.catalog.group .addMembersFromJson(CommonStrata.user, data.catalog) .raiseError(this.props.terria, "Failed to load catalog from file"); }) .then(() => { this.props.onUrlAddFinished(); }) .catch((error) => TerriaError.from(error).raiseError( this.props.terria, `An error occurred trying to add data from URL: ${this.state.remoteUrl}` ) ) .finally(() => { this.setState({ isLoading: false }); }); } else { try { const newItem = upsertModelFromJson( CatalogMemberFactory, this.props.terria, "", CommonStrata.defaults, { type: this.state.remoteDataType.value, name: url }, {} ).throwIfUndefined({ message: `An error occurred trying to add data from URL: ${url}` }); newItem.setTrait(CommonStrata.user, "url", url); promise = newItem.loadMetadata().then((result) => { if (result.error) { return Promise.reject(result.error); } return Promise.resolve(newItem); }); } catch (e) { promise = Promise.reject(e); } } addUserCatalogMember(this.props.terria, promise).then((addedItem) => { if (addedItem) { this.props.onFileAddFinished([addedItem]); if (TimeVarying.is(addedItem)) { this.props.terria.timelineStack.addToTop(addedItem); } } // FIXME: Setting state here might result in a react warning if the // component unmounts before the promise finishes this.setState({ isLoading: false }); this.props.resetTab(); }); }, onRemoteUrlChange(event) { this.setState({ remoteUrl: event.target.value }); }, renderPanels() { const { t } = this.props; const dropdownTheme = { dropdown: Styles.dropdown, list: Styles.dropdownList, isOpen: Styles.dropdownListIsOpen, icon: <Icon glyph={Icon.GLYPHS.opened} /> }; const dataTypes = this.state.localDataTypes.reduce(function ( result, currentDataType ) { if (currentDataType.extensions) { return result.concat( currentDataType.extensions.map((extension) => "." + extension) ); } else { return result; } }, []); return ( <div className={Styles.tabPanels}> <If condition={this.props.activeTab === "local"}> <div className={Styles.tabHeading}>{t("addData.localAdd")}</div> <section className={Styles.tabPanel}> <label className={Styles.label}> <Trans i18nKey="addData.localFileType"> <strong>Step 1:</strong> Select file type </Trans> </label> <Dropdown options={this.state.localDataTypes} selected={this.state.localDataType} selectOption={this.selectLocalOption} matchWidth={true} theme={dropdownTheme} /> {this.state.localDataType?.description ? parseCustomMarkdownToReactWithOptions( this.state.localDataType?.description ) : null} <label className={Styles.label}> <Trans i18nKey="addData.localFile"> <strong>Step 2:</strong> Select file </Trans> </label> <FileInput accept={dataTypes.join(",")} onChange={this.handleUploadFile} /> {this.state.isLoading && <Loader />} </section> </If> <If condition={this.props.activeTab === "web"}> <div className={Styles.tabHeading}>{t("addData.webAdd")}</div> <section className={Styles.tabPanel}> <label className={Styles.label}> <Trans i18nKey="addData.webFileType"> <strong>Step 1:</strong> Select file or web service type </Trans> </label> <Dropdown options={this.state.remoteDataTypes} selected={this.state.remoteDataType} selectOption={this.selectRemoteOption} matchWidth={true} theme={dropdownTheme} /> {this.state.remoteDataType?.description ? parseCustomMarkdownToReactWithOptions( this.state.remoteDataType?.description ) : null} <label className={Styles.label}> <Trans i18nKey="addData.webFile"> <strong>Step 2:</strong> Enter the URL of the data file or web service </Trans> </label> <form className={Styles.urlInput}> <input value={this.state.remoteUrl} onChange={this.onRemoteUrlChange} className={Styles.urlInputTextBox} type="text" placeholder="e.g. http://data.gov.au/geoserver/wms" /> <button type="submit" onClick={this.handleUrl} className={Styles.urlInputBtn} > {t("addData.urlInputBtn")} </button> {this.state.isLoading && <Loader />} </form> </section> </If> </div> ); }, render() { return <div className={Styles.inner}>{this.renderPanels()}</div>; } }); /** * @param extensions - string[] * @returns Comma separated string of extensions */ function buildExtensionsList(extensions) { return extensions.map((ext) => `.${ext}`).join(", "); } module.exports = withTranslation()(AddData);