From 108b84f920009c71b7cc28432048ba2d1bf3c241 Mon Sep 17 00:00:00 2001 From: stefano bovio Date: Wed, 19 Jan 2022 11:44:36 +0100 Subject: [PATCH] add new upload dataset page (#715) --- .../client/js/api/geonode/v2/index.js | 69 +- .../client/js/apps/gn-catalogue.js | 6 + .../client/js/epics/gnsync.js | 2 +- .../client/js/routes/UploadDataset.jsx | 622 ++++++++++++++++++ .../themes/geonode/less/_main-event.less | 3 + .../client/themes/geonode/less/_upload.less | 169 +++++ .../client/themes/geonode/less/geonode.less | 1 + geonode_mapstore_client/context_processors.py | 18 +- .../_geonode_config.html | 4 +- 9 files changed, 888 insertions(+), 6 deletions(-) create mode 100644 geonode_mapstore_client/client/js/routes/UploadDataset.jsx create mode 100644 geonode_mapstore_client/client/themes/geonode/less/_upload.less diff --git a/geonode_mapstore_client/client/js/api/geonode/v2/index.js b/geonode_mapstore_client/client/js/api/geonode/v2/index.js index feaecec407..945d7ed688 100644 --- a/geonode_mapstore_client/client/js/api/geonode/v2/index.js +++ b/geonode_mapstore_client/client/js/api/geonode/v2/index.js @@ -20,6 +20,7 @@ import get from 'lodash/get'; import { getUserInfo } from '@js/api/geonode/user'; import { setFilterById } from '@js/utils/SearchUtils'; import { ResourceTypes, availableResourceTypes, setAvailableResourceTypes } from '@js/utils/ResourceUtils'; +import { getConfigProp } from '@mapstore/framework/utils/ConfigUtils'; /** * Actions for GeoNode save workflow @@ -39,7 +40,8 @@ let endpoints = { 'owners': '/api/v2/owners', 'keywords': '/api/v2/keywords', 'regions': '/api/v2/regions', - 'groups': '/api/v2/groups' + 'groups': '/api/v2/groups', + 'uploads': '/api/v2/uploads' }; const RESOURCES = 'resources'; @@ -54,7 +56,7 @@ const REGIONS = 'regions'; const CATEGORIES = 'categories'; const KEYWORDS = 'keywords'; const GROUPS = 'groups'; - +const UPLOADS = 'uploads'; function addCountToLabel(name, count) { return `${name} (${count || 0})`; @@ -707,6 +709,64 @@ export const copyResource = (resource) => { .then(({ data }) => data); }; +export const getPendingUploads = () => { + return axios.get(parseDevHostname(endpoints[UPLOADS]), { + params: { + 'filter{-state}': 'PROCESSED', + 'page': 1, + 'page_size': 99999 + } + }) + .then(({ data }) => data?.uploads); +}; + +export const getProcessedUploadsById = (ids) => { + return axios.get(parseDevHostname(endpoints[UPLOADS]), { + params: { + 'filter{state}': 'PROCESSED', + 'page': 1, + 'page_size': ids.length, + 'filter{id.in}': ids + } + }) + .then(({ data }) => data?.uploads); +}; + +export const getProcessedUploadsByImportId = (importIds) => { + return axios.get(parseDevHostname(endpoints[UPLOADS]), { + params: { + 'filter{state}': 'PROCESSED', + 'page': 1, + 'page_size': importIds.length, + 'filter{import_id.in}': importIds + } + }) + .then(({ data }) => data?.uploads); +}; + +export const uploadDataset = ({ + file, + auxiliaryFiles, + ext, + charset = 'UTF-8', + permissions = { users: { AnonymousUser: [] }, groups: {}} +}) => { + const formData = new FormData(); + formData.append('base_file', file); + formData.append('permissions', JSON.stringify(permissions)); + formData.append('charset', charset); + const { timeEnabled } = getConfigProp('geoNodeSettings') || {}; + if (timeEnabled) { + formData.append('time', ['csv', 'shp'].includes(ext) ? true : false); + } + Object.keys(auxiliaryFiles) + .forEach((auxExt) => { + formData.append(auxExt + '_file', auxiliaryFiles[auxExt]); + }); + return axios.post(`${parseDevHostname(endpoints[UPLOADS])}/upload`, formData) + .then(({ data }) => (data)); +}; + export default { getEndpoints, getResources, @@ -737,5 +797,8 @@ export default { updateCompactPermissionsByPk, deleteResource, copyResource, - getDatasets + getDatasets, + getPendingUploads, + getProcessedUploadsById, + getProcessedUploadsByImportId }; diff --git a/geonode_mapstore_client/client/js/apps/gn-catalogue.js b/geonode_mapstore_client/client/js/apps/gn-catalogue.js index 01d6770c11..85a70c5e5d 100644 --- a/geonode_mapstore_client/client/js/apps/gn-catalogue.js +++ b/geonode_mapstore_client/client/js/apps/gn-catalogue.js @@ -40,6 +40,7 @@ import annotations from '@mapstore/framework/reducers/annotations'; import SearchRoute from '@js/routes/Search'; import DetailRoute from '@js/routes/Detail'; import ViewerRoute from '@js/routes/Viewer'; +import UploadDatasetRoute from '@js/routes/UploadDataset'; import gnsearch from '@js/reducers/gnsearch'; import gnresource from '@js/reducers/gnresource'; @@ -182,6 +183,11 @@ const routes = [ '/detail/:ctype/:pk' ], component: DetailRoute + }, + { + name: 'upload_dataset', + path: ['/upload/dataset'], + component: UploadDatasetRoute } ]; diff --git a/geonode_mapstore_client/client/js/epics/gnsync.js b/geonode_mapstore_client/client/js/epics/gnsync.js index e0040bc7e1..2fa13bc17b 100644 --- a/geonode_mapstore_client/client/js/epics/gnsync.js +++ b/geonode_mapstore_client/client/js/epics/gnsync.js @@ -50,7 +50,7 @@ const setResourceApi = { * Get resource type and data for state update in sync process * @param {String} appType geostory or dashboard * @param {Object} resourceData Resource Object - * @param {Optional: Array} successArr Array of success responses only used in case of dashboard + * @param {Array} successArr Array of success responses only used in case of dashboard * @returns {Object} */ const getSyncInfo = (appType, resourceData, successArr = []) => { diff --git a/geonode_mapstore_client/client/js/routes/UploadDataset.jsx b/geonode_mapstore_client/client/js/routes/UploadDataset.jsx new file mode 100644 index 0000000000..44f28b8f05 --- /dev/null +++ b/geonode_mapstore_client/client/js/routes/UploadDataset.jsx @@ -0,0 +1,622 @@ +/* + * Copyright 2020, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. +*/ + +import React, { useState, useEffect, useRef } from 'react'; +import PropTypes from 'prop-types'; +import uniqBy from 'lodash/uniqBy'; +import orderBy from 'lodash/orderBy'; +import omit from 'lodash/omit'; +import pick from 'lodash/pick'; +import merge from 'lodash/merge'; +import { connect } from 'react-redux'; +import { createSelector } from 'reselect'; +import Dropzone from 'react-dropzone'; +import ViewerLayout from '@js/components/ViewerLayout'; +import FaIcon from '@js/components/FaIcon'; +import Button from '@js/components/Button'; +import Spinner from '@js/components/Spinner'; +import Badge from '@js/components/Badge'; +import { + getPendingUploads, + getProcessedUploadsById, + getProcessedUploadsByImportId, + uploadDataset +} from '@js/api/geonode/v2'; +import axios from '@mapstore/framework/libs/ajax'; +import moment from 'moment'; +import { FormControl as FormControlRB } from 'react-bootstrap'; +import localizedProps from '@mapstore/framework/components/misc/enhancers/localizedProps'; +// import withDebounceOnCallback from '@mapstore/framework/components/misc/enhancers/withDebounceOnCallback'; +const FormControl = localizedProps('placeholder')(FormControlRB); + +function InputControl({ onChange, value, ...props }) { + return onChange(event.target.value)}/>; +} +const InputControlWithDebounce = InputControl; + +const supportedDatasetTypes = [ + { + id: 'shp', + label: 'ESRI Shapefile', + format: 'vector', + ext: ['shp'], + requires: ['shp', 'prj', 'dbf', 'shx'] + }, + { + id: 'asc', + label: 'ASCII Text File', + format: 'raster', + ext: ['asc'] + }, + { + id: 'tiff', + label: 'GeoTIFF', + format: 'raster', + ext: ['tiff', 'tif'], + mimeType: ['image/tiff'] + }, + { + id: 'csv', + label: 'Comma Separated Value', + format: 'vector', + ext: ['csv'], + mimeType: ['text/csv'] + }, + { + id: 'kml', + label: 'Google Earth KML', + format: 'archive', + ext: ['kml'] + }, + { + id: 'kmz', + label: 'Google Earth KMZ', + format: 'archive', + ext: ['kmz'] + }, + { + id: 'geojson', + label: 'GeoJSON', + format: 'vector', + ext: ['json', 'geojson'], + mimeType: ['application/json', 'application/geo+json'] + }, + { + id: 'zip', + label: 'Zip Archive', + format: 'archive', + ext: ['zip'], + mimeType: ['application/zip'] + } +]; + + +const supportedExtensions = supportedDatasetTypes.map(({ ext }) => ext || []).flat(); +const supportedMimeTypes = supportedDatasetTypes.map(({ mimeType }) => mimeType || []).flat(); +const supportedRequiresExtensions = supportedDatasetTypes.map(({ requires }) => requires || []).flat(); +const supportedLabels = supportedDatasetTypes.map(({ label }) => label ).join(', '); + +function getFileNameParts(file) { + const { name } = file; + const nameParts = name.split('.'); + const ext = nameParts[nameParts.length - 1]; + const baseName = [...nameParts].splice(0, nameParts.length - 1).join('.'); + return { ext, baseName }; +} + +function getDatasetFileType(file) { + const { type } = file; + const { ext } = getFileNameParts(file); + const datasetFileType = supportedDatasetTypes.find((fileType) => + (fileType.ext || []).includes(ext) + || (fileType.mimeType || []).includes(type) + || (fileType.requires || []).includes(ext) + ); + return datasetFileType?.id; +} + +function PendingUploadCard({ + missingExt, + baseName, + onRemove, + filesExt +}) { + return ( +
+
+ {missingExt.length > 0 ?
: null} +
{baseName}
+ {onRemove + ? + : null} +
+ {missingExt.length > 0 &&
+ Missing files: {missingExt.join(', ')} +
} + {
    + {filesExt.map(ext => { + return ( +
  • + .{ext} +
  • + ); + })} +
} +
+ ); +} + +function UploadCard({ + name, + state, + detailUrl, + progress, + createDate, + resumeUrl, + onRemove +}) { + return ( +
+
+ {state === 'INVALID' ?
: null} +
+ {detailUrl + ? + {name} + + : name} +
+ {(state === 'PENDING' || state === 'COMPLETE') && progress < 100 ? : null} + {onRemove + ? + : null} +
+
+
{moment(createDate).format('MMMM Do YYYY')}
+
+
+
+ {resumeUrl + ? + : null} +
+
+
+
+
+
+
+ ); +} + +function UploadList({ + children, + onSuccess +}) { + + const [waitingUploads, setWaitingUploads] = useState({}); + const [readyUploads, setReadyUploads] = useState({}); + const [loading, setLoading] = useState(false); + + function parseUploadFiles(uploadFiles) { + return Object.keys(uploadFiles) + .reduce((acc, baseName) => { + const uploadFile = uploadFiles[baseName]; + const { requires = [], ext = []} = supportedDatasetTypes.find(({ id }) => id === uploadFile.type) || {}; + const cleanedFiles = pick(uploadFiles[baseName].files, [...requires, ...ext]); + const filesKeys = Object.keys(cleanedFiles); + const files = requires.length > 0 + ? cleanedFiles + : filesKeys.length > 1 + ? pick(cleanedFiles, ext[0]) + : cleanedFiles; + const missingExt = requires.filter((fileExt) => !filesKeys.includes(fileExt)); + return { + ...acc, + [baseName]: { + ...uploadFile, + mainExt: filesKeys.find(key => ext.includes(key)), + files, + missingExt + } + }; + }, {}); + } + + function updateWaitingUploads(uploadFiles) { + const newWaitingUploads = parseUploadFiles(uploadFiles); + setWaitingUploads(newWaitingUploads); + const newReadyUploads = Object.keys(newWaitingUploads) + .reduce((acc, baseName) => { + if (newWaitingUploads[baseName]?.missingExt?.length > 0) { + return acc; + } + return { + ...acc, + [baseName]: newWaitingUploads[baseName] + }; + }, {}); + setReadyUploads(newReadyUploads); + } + + function handleDrop(files) { + const checkedFiles = files.map((file) => { + const { type } = file; + const { ext } = getFileNameParts(file); + return { + supported: !!(supportedMimeTypes.includes(type) || supportedExtensions.includes(ext) || supportedRequiresExtensions.includes(ext)), + file + }; + }); + const uploadsGroupedByName = checkedFiles + .filter(({ supported }) => supported) + .reduce((acc, { file }) => { + const { ext, baseName } = getFileNameParts(file); + const type = getDatasetFileType(file); + return { + ...acc, + [baseName]: { + type, + files: { + ...acc[baseName]?.files, + [ext]: file + } + } + }; + }, {}); + + const newWaitingUploads = { ...merge(waitingUploads, uploadsGroupedByName) }; + updateWaitingUploads(newWaitingUploads); + } + + const inputFile = useRef(); + + function handleUploadProcess() { + if (!loading) { + setLoading(true); + axios.all(Object.keys(readyUploads).map((baseName) => { + const readyUpload = readyUploads[baseName]; + return uploadDataset({ + file: readyUpload.files[readyUpload.mainExt], + ext: readyUpload.mainExt, + auxiliaryFiles: readyUpload.files + }) + .then((data) => ({ status: 'success', data, baseName })) + .catch(({ data: error }) => ({ status: 'error', error, baseName })); + })) + .then((responses) => { + const successfulUploads = responses.filter(({ status }) => status === 'success'); + if (successfulUploads.length > 0) { + const successfulUploadsIds = successfulUploads.map(({ data }) => data?.id); + const successfulUploadsNames = successfulUploads.map(({ baseName }) => baseName); + updateWaitingUploads(omit(waitingUploads, successfulUploadsNames)); + getProcessedUploadsByImportId(successfulUploadsIds) + .then((successfulUploadProcesses) => { + onSuccess(successfulUploadProcesses); + setLoading(false); + }) + .catch(() => { + setLoading(false); + }); + } else { + setLoading(false); + } + }) + .catch(() => { + setLoading(false); + }); + } + } + + const waitingUploadNames = Object.keys(waitingUploads); + return ( + + +
+ handleDrop([...event?.target?.files])} style={{ display: 'none' }}/> + +
+ {waitingUploadNames.length > 0 ? ( +
    + {waitingUploadNames.map((baseName) => { + const { files, missingExt = [] } = waitingUploads[baseName]; + const filesExt = Object.keys(files); + return ( +
  • + updateWaitingUploads(omit(waitingUploads, baseName))} + filesExt={filesExt} + /> +
  • + ); + })} +
+ ) : ( +
+ Supported files: {supportedLabels} +
+ )} +
+ +
+ {loading && ( +
+ +
+ )} + + } + > + {children} +
+
+ ); +} + +function ProcessingUploadList({ + uploads: pendingUploads, + onChange, + refreshTime = 3000 +}) { + + const [loading, setLoading] = useState(false); + const [filterText, setFilterText] = useState(''); + const isMounted = useRef(true); + const updatePending = useRef(); + updatePending.current = () => { + if (!loading) { + setLoading(true); + getPendingUploads() + .then((newPendingUploads) => { + if (isMounted.current) { + const newIds = newPendingUploads.map(({ id }) => id); + const missingIds = pendingUploads + .filter(upload => upload.state !== 'PROCESSED' && !newIds.includes(upload.id)) + .map(({ id }) => id); + const currentProcessed = pendingUploads.filter((upload) => upload.state === 'PROCESSED'); + if (missingIds.length > 0) { + getProcessedUploadsById(missingIds) + .then((processed) => { + onChange([ + ...processed, + ...currentProcessed, + ...newPendingUploads + ]); + setLoading(false); + }) + .catch(() => { + onChange([ + ...currentProcessed, + ...newPendingUploads + ]); + setLoading(false); + }); + } else { + onChange([ + ...currentProcessed, + ...newPendingUploads + ]); + setLoading(false); + } + } + }) + .catch(() => { + if (isMounted.current) { + setLoading(false); + } + }); + } + }; + + function handleDelete({ id, deleteUrl }) { + axios.get(deleteUrl) + .then(() => { + if (isMounted.current) { + onChange(pendingUploads.filter(upload => upload.id !== id)); + } + }); + } + + useEffect(() => { + isMounted.current = true; + updatePending.current(); + const interval = setInterval(() => { + updatePending.current(); + }, refreshTime); + return () => { + clearInterval(interval); + isMounted.current = false; + }; + }, []); + + const filteredPendingUploads = pendingUploads.filter(({ name }) => !filterText || name.includes(filterText)); + + return ( +
+ {pendingUploads.length === 0 + ? ( +
+
+
+
+ +
+

Dataset Upload

+
drag and drop a dataset file
+ {/* */} +
+
+
+ ) + : ( + <> +
+ +
+
+ {filteredPendingUploads.length > 0 + ?
    + {filteredPendingUploads + .map(({ + id, + name, + progress = 0, + state, + create_date: createDate, + detail_url: detailUrl, + resume_url: resumeUrl, + delete_url: deleteUrl + }) => { + return ( +
  • + handleDelete({ id, deleteUrl }) : null} + /> +
  • + ); + })} +
+ : ( +
+
+
+
+ +
+
Filter does not match a pending upload
+ {/* */} +
+
+
+ )} +
+ + )} +
+ ); +} + +function UploadDataset({ + refreshTime = 3000 +}) { + + const [pendingUploads, setPendingUploads] = useState([]); + + function parseUploadResponse(response) { + return orderBy(uniqBy([...response], 'id'), 'create_date', 'desc'); + } + + return ( + setPendingUploads(parseUploadResponse([...successfulUploads, ...pendingUploads]))} + > + setPendingUploads(parseUploadResponse(uploads))} + refreshTime={refreshTime} + /> + + ); +} + +UploadDataset.propTypes = { + location: PropTypes.object +}; + +UploadDataset.defaultProps = { + +}; + +const ConnectedUploadDataset = connect( + createSelector([], () => ({})) +)(UploadDataset); + +export default ConnectedUploadDataset; diff --git a/geonode_mapstore_client/client/themes/geonode/less/_main-event.less b/geonode_mapstore_client/client/themes/geonode/less/_main-event.less index 4003ce3c68..64832ad599 100644 --- a/geonode_mapstore_client/client/themes/geonode/less/_main-event.less +++ b/geonode_mapstore_client/client/themes/geonode/less/_main-event.less @@ -58,6 +58,9 @@ transform: translate(-50%, -50%); font-size: 1.5em; text-align: center; + h1 { + font-size: 2em; + } } @-webkit-keyframes gn-main-loader-animation { 0% { diff --git a/geonode_mapstore_client/client/themes/geonode/less/_upload.less b/geonode_mapstore_client/client/themes/geonode/less/_upload.less new file mode 100644 index 0000000000..ee3e3bcffc --- /dev/null +++ b/geonode_mapstore_client/client/themes/geonode/less/_upload.less @@ -0,0 +1,169 @@ + + + + +// ************** +// Theme +// ************** + +#gn-components-theme(@theme-vars) { + .gn-dropzone-active { + .border-color-var(@theme-vars[success]); + } + .gn-upload-card { + .border-bottom-color-var(@theme-vars[main-border-color]); + } + .gn-upload-list-header { + .border-bottom-color-var(@theme-vars[main-border-color]); + } + .gn-upload-card-progress { + .background-color-var(@theme-vars[main-variant-bg]); + } + .gn-upload-card-progress > div { + .background-color-var(@theme-vars[success]); + } + .gn-upload-card-progress.waiting > div { + .background-color-var(@theme-vars[warning]); + } + .gn-upload-card-progress.invalid { + .background-color-var(@theme-vars[danger]); + } + .gn-upload-card-error { + .color-var(@theme-vars[danger]); + } +} + +// ************** +// Layout +// ************** + +.gn-upload-dataset { + position: relative; + width: 100%; + height: 100%; + border-width: 0.25rem; + border-style: dashed; + margin: 0; + display: flex; + align-items: center; + justify-content: center; + border-color: transparent; + ul { + padding: 0; + margin: 0; + list-style-type: none; + li { + padding: 0; + margin: 0; + } + } + .gn-main-event-container { + margin: 1rem; + width: ~"calc(100% - 1rem)"; + height: ~"calc(100% - 1rem)"; + .gn-main-event-content { + width: 100%; + padding: 3rem; + } + } + +} + +.gn-upload-list { + position: relative; + max-height: 500px; + height: 100%; + width: 300px; + display: flex; + flex-direction: column; + border-radius: 0.2rem; + margin: 1rem; + margin-right: 0; + .shadow-soft(); + ul { + flex: 1; + overflow: auto; + } + .gn-upload-list-header { + .btn { + width: 100%; + text-align: left; + } + border-bottom-width: 1px; + border-bottom-style: solid; + } + .gn-upload-list-footer { + padding: 0.25rem; + text-align: center; + } +} + +.gn-upload-card { + border-bottom-width: 1px; + border-bottom-style: solid; + .gn-upload-card-header { + display: flex; + white-space: nowrap; + align-items: center; + padding: 0.5rem; + > .gn-upload-card-title { + flex: 1; + text-overflow: ellipsis; + overflow: hidden; + } + .gn-upload-card-error { + padding-right: 0.5rem; + } + } + .gn-upload-card-body { + padding: 0 0.5rem; + font-size: @font-size-sm; + } + .gn-upload-card-footer { + padding: 0 0.5rem; + padding-bottom: 0.5rem; + display: flex; + flex-direction: row-reverse; + min-height: 1.4rem; + } + + ul { + display: flex; + padding: 0.5rem; + padding-top: 0; + li + li { + margin-left: 0.25rem; + } + .badge { + font-size: 0.7rem; + font-weight: normal; + } + } +} + +.gn-upload-processing { + position: absolute; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + .gn-upload-processing-header { + margin: 1.0rem; + } + + .gn-upload-processing-list { + flex: 1; + position: relative; + width: 100%; + overflow: auto; + .gn-upload-card { + margin: 1.0rem; + margin-top: 0; + .shadow-soft(); + border: none; + .gn-upload-card-title { + font-size: @font-size-lg; + } + } + } +} diff --git a/geonode_mapstore_client/client/themes/geonode/less/geonode.less b/geonode_mapstore_client/client/themes/geonode/less/geonode.less index df7148d1a2..bbadc6b562 100644 --- a/geonode_mapstore_client/client/themes/geonode/less/geonode.less +++ b/geonode_mapstore_client/client/themes/geonode/less/geonode.less @@ -21,6 +21,7 @@ @import '_spinner.less'; @import '_home-container.less'; @import '_sub-flat-menu.less'; +@import '_upload.less'; @import '_visual-style-editor.less'; @import '_mixins.less'; diff --git a/geonode_mapstore_client/context_processors.py b/geonode_mapstore_client/context_processors.py index 04abe2e4bb..2775f886b4 100644 --- a/geonode_mapstore_client/context_processors.py +++ b/geonode_mapstore_client/context_processors.py @@ -26,6 +26,22 @@ def resource_urls(request): 'DEFAULT_MAP_CRS': getattr(settings, "DEFAULT_MAP_CRS", 'EPSG:3857'), 'DEFAULT_MAP_ZOOM': getattr(settings, "DEFAULT_MAP_ZOOM", 0), 'DEFAULT_TILE_SIZE': getattr(settings, "DEFAULT_TILE_SIZE", 512), - 'DEFAULT_LAYER_FORMAT': getattr(settings, "DEFAULT_LAYER_FORMAT", 'image/png') + 'DEFAULT_LAYER_FORMAT': getattr(settings, "DEFAULT_LAYER_FORMAT", 'image/png'), + 'TIME_ENABLED': getattr( + settings, + 'UPLOADER', + dict()).get( + 'OPTIONS', + dict()).get( + 'TIME_ENABLED', + False), + 'MOSAIC_ENABLED': getattr( + settings, + 'UPLOADER', + dict()).get( + 'OPTIONS', + dict()).get( + 'MOSAIC_ENABLED', + False) } return defaults diff --git a/geonode_mapstore_client/templates/geonode-mapstore-client/_geonode_config.html b/geonode_mapstore_client/templates/geonode-mapstore-client/_geonode_config.html index b1b9c05c95..7f3cad2c74 100644 --- a/geonode_mapstore_client/templates/geonode-mapstore-client/_geonode_config.html +++ b/geonode_mapstore_client/templates/geonode-mapstore-client/_geonode_config.html @@ -45,6 +45,7 @@ const defaultLayerFormat = geoNodeSettings.DEFAULT_LAYER_FORMAT || 'image/png'; const catalogueServices = geoNodeSettings.CATALOGUE_SERVICES || {}; const catalogueSelectedService = geoNodeSettings.CATALOGUE_SELECTED_SERVICE || ''; + const timeEnabled = geoNodeSettings.TIME_ENABLED || false; const isEmbed = checkBoolean('{{ is_embed }}') || false; const pluginsConfigKey = '{{ plugins_config_key }}'; @@ -78,7 +79,8 @@ geoserverUrl: geoServerPublicLocation, siteName: siteName, defaultTileSize: defaultTileSize, - defaultLayerFormat: defaultLayerFormat + defaultLayerFormat: defaultLayerFormat, + timeEnabled: timeEnabled }, geoNodeConfiguration: { cardsMenu: {