/*
* Copyright 2021, 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 {
setConfigProp,
getConfigProp,
setLocalConfigurationFile
} from '@mapstore/framework/utils/ConfigUtils';
import {
getSupportedLocales,
setSupportedLocales
} from '@mapstore/framework/utils/LocaleUtils';
import { getState } from '@mapstore/framework/utils/StateUtils';
import { generateActionTrigger } from '@mapstore/framework/epics/jsapi';
import { LOCATION_CHANGE } from 'connected-react-router';
import { setRegGeoserverRule } from '@mapstore/framework/utils/LayersUtils';
import { mapSelector } from '@mapstore/framework/selectors/map';
import isArray from 'lodash/isArray';
import isObject from 'lodash/isObject';
import isString from 'lodash/isString';
import isFunction from 'lodash/isFunction';
import url from 'url';
import axios from '@mapstore/framework/libs/ajax';
import moment from 'moment';
import { addLocaleData } from 'react-intl';
import { setViewer } from '@mapstore/framework/utils/MapInfoUtils';
// we need this configuration set for specific components that use recompose/rxjs streams
import { setObservableConfig } from 'recompose';
import rxjsConfig from 'recompose/rxjsObservableConfig';
import { getGeoNodeConfig, getGeoNodeLocalConfig } from "@js/utils/APIUtils";
setObservableConfig(rxjsConfig);
let actionListeners = {};
// Target url here to fix proxy issue
let targetURL = '';
const getTargetUrl = () => {
if (!__DEVTOOLS__) {
return '';
}
if (targetURL) {
return targetURL;
}
const geonodeUrl = getConfigProp('geoNodeSettings')?.geonodeUrl || '';
if (!geonodeUrl) {
return '';
}
const { host, protocol } = url.parse(geonodeUrl);
targetURL = `${protocol}//${host}/`;
return targetURL;
};
export function getVersion() {
if (!__DEVTOOLS__) {
return __MAPSTORE_PROJECT_CONFIG__.version;
}
return 'dev';
}
export function initializeApp() {
// Set X-CSRFToken in axios;
axios.defaults.xsrfHeaderName = "X-CSRFToken";
axios.defaults.xsrfCookieName = "csrftoken";
setLocalConfigurationFile('');
setRegGeoserverRule(/\/[\w- ]*geoserver[\w- ]*\/|\/[\w- ]*gs[\w- ]*\//);
const pathsNeedVersion = [
'static/mapstore/',
'print.json'
];
axios.interceptors.request.use(
config => {
if (config.url && pathsNeedVersion.filter(pathNeedVersion => config.url.match(pathNeedVersion))[0]) {
return {
...config,
params: {
...config.params,
v: getVersion()
}
};
}
const tUrl = getTargetUrl();
if (tUrl && config.url?.match(tUrl)?.[0]) {
return {
...config,
url: `/${config.url.replace(tUrl, '')}`
};
}
return config;
}
);
// Set proxy and authentication from geonode config
['proxyUrl', 'useAuthenticationRules', 'authenticationRules'].forEach(key=> {
setConfigProp(key, getGeoNodeLocalConfig(key));
});
}
export function getPluginsConfiguration(pluginsConfig, key) {
if (isArray(pluginsConfig)) {
return pluginsConfig;
}
if (isObject(pluginsConfig)) {
const pluginsConfigSection = pluginsConfig[key];
if (pluginsConfigSection) {
// use string to link duplicated configurations
return isString(pluginsConfigSection)
? pluginsConfig[pluginsConfigSection]
: pluginsConfigSection;
}
return pluginsConfig;
}
return [];
}
function getLanguageKey(languageCode) {
const parts = languageCode.split('-');
return parts[0];
}
function parseLanguageCode(languageCode) {
const parts = languageCode.split('-');
if (parts.length === 1) {
return parts[0].toLowerCase();
}
return `${parts[0].toLowerCase()}-${parts[1].toUpperCase()}`;
}
function languagesToSupportedLocales(languages) {
if (!languages || languages.length === 0) {
return null;
}
return languages.reduce((acc, [code, description]) => ({
...acc,
[getLanguageKey(code)]: {
code: parseLanguageCode(code),
description
}
}), {});
}
function setupLocale(locale) {
return import(`react-intl/locale-data/${locale}`)
.then((localeDataMod) => {
const localeData = localeDataMod.default;
addLocaleData([...localeData]);
if (!global.Intl) {
return import('intl')
.then((intlMod) => {
global.Intl = intlMod.default;
return import(`intl/locale-data/jsonp/${locale}.js`).then(() => {
return locale;
});
});
}
// setup locale for moment
moment.locale(locale);
return locale;
});
}
let apiPluginsConfig;
export function setupConfiguration({
localConfig,
user,
resourcesTotalCount
}) {
const { query } = url.parse(window.location.href, true);
// set the extensions path before get the localConfig
// so it's possible to override in a custom project
setConfigProp('extensionsRegistry', '/static/mapstore/extensions/index.json');
const {
supportedLocales: defaultSupportedLocales,
...config
} = localConfig;
const geoNodePageConfig = getGeoNodeConfig();
Object.keys(config).forEach((key) => {
setConfigProp(key, config[key]);
});
setConfigProp('translationsPath', geoNodePageConfig.translationsPath
? geoNodePageConfig.translationsPath
: config.translationsPath
? config.translationsPath
: ['/static/mapstore/ms-translations', '/static/mapstore/gn-translations']
);
const supportedLocales = languagesToSupportedLocales(geoNodePageConfig.languages) || defaultSupportedLocales || getSupportedLocales();
setSupportedLocales(supportedLocales);
const locale = supportedLocales[getLanguageKey(geoNodePageConfig.languageCode)]?.code || 'en-US';
setConfigProp('locale', locale);
const geoNodeResourcesInfo = getConfigProp('geoNodeResourcesInfo') || {};
setConfigProp('geoNodeResourcesInfo', { ...geoNodeResourcesInfo, ...resourcesTotalCount });
const securityState = user?.info?.access_token
? {
security: {
user: user,
token: user.info.access_token
}
}
: undefined;
// globlal window interface to interact with the django page
const actionTrigger = generateActionTrigger(LOCATION_CHANGE);
// similar implementation of MapStore2 API without the create part
/**
* @global
* @property {function} getMapState return the map state if available
* @property {function} triggerAction dispatch an action
* @property {function} onAction add listener to an action
* @property {function} offAction remove listener to an action
* @example
* <!--
* access to mapstore api
* -->
* <script>
* window.addEventListener('mapstore:ready', function(event) {
* const msAPI = event.detail;
* });
* </script>
*
* @example
* <!--
* use mapstore api onAction method to listen to an action
* this example works only in a page with the map plugin (eg. dataset and map viewers)
* -->
* <script>
* window.addEventListener('mapstore:ready', function(event) {
* const msAPI = event.detail;
* function onChangeMapView(action) {
* // read parameters dispatched by the action
* const center = action.center;
* console.log(center);
* // get all the current stored map state
* const currentMapState = msAPI.getMapState();
* console.log(currentMapState);
* }
* // listen on map view changes
* msAPI.onAction('CHANGE_MAP_VIEW', onChangeMapView);
* });
* </script>
*
* @example
* <!--
* use mapstore api offAction method to listen to an action only once
* this example works only in a page with the map plugin (eg. dataset and map viewers)
* -->
* <script>
* window.addEventListener('mapstore:ready', function(event) {
* const msAPI = event.detail;
* function onChangeMapView(action) {
* // read parameters dispatched by the action
* const center = action.center;
* console.log(center);
* // ...
* // remove the same action
* msAPI.offAction('CHANGE_MAP_VIEW', onChangeMapView);
* }
* // listen on map view changes
* msAPI.onAction('CHANGE_MAP_VIEW', onChangeMapView);
* });
* </script>
*
* @example
* <!--
* use mapstore api triggerAction method to dispatch an action
* this example works only in a page with the map plugin (eg. dataset and map viewers)
* -->
* <button id="custom-zoom-button">Zoom to extent</button>
* <script>
* window.addEventListener('mapstore:ready', function(event) {
* const msAPI = event.detail;
* const button = document.querySelector('#custom-zoom-button');
* button.addEventListener('click', function() {
* msAPI.triggerAction({
* type: 'ZOOM_TO_EXTENT',
* crs: 'EPSG:4326',
* extent: {
* minx: -10,
* miny: -10,
* maxx: 10,
* maxy: 10
* }
* });
* });
* });
* </script>
*/
window.MapStoreAPI = {
ready: true,
getMapState: function() {
return mapSelector(getState());
},
triggerAction: actionTrigger.trigger,
onAction: (type, listener) => {
const listeners = actionListeners[type] || [];
listeners.push(listener);
actionListeners[type] = listeners;
},
offAction: (type, listener) => {
const listeners = (actionListeners[type] || [])
.filter((l) => l !== listener);
actionListeners[type] = listeners;
},
setGetFeatureInfoViewer: setViewer,
setPluginsConfig: (pluginsConfig) => { apiPluginsConfig = pluginsConfig; }
};
const mapstoreReady = new CustomEvent('mapstore:ready', {
detail: window.MapStoreAPI
});
window.dispatchEvent(mapstoreReady);
if (window.onInitMapStoreAPI) {
window.onInitMapStoreAPI(window.MapStoreAPI, geoNodePageConfig);
}
return setupLocale(getLanguageKey(geoNodePageConfig.languageCode))
.then(() => ({
query,
securityState,
geoNodeConfiguration: localConfig.geoNodeConfiguration,
geoNodePageConfig,
pluginsConfigKey: query.config || geoNodePageConfig.pluginsConfigKey,
mapType: geoNodePageConfig.mapType,
settings: localConfig.geoNodeSettings,
MapStoreAPI: window.MapStoreAPI,
onStoreInit: (store) => {
store.addActionListener((action) => {
const act = action.type === 'PERFORM_ACTION' && action.action || action; // Needed to works also in debug
(actionListeners[act.type] || [])
.concat(actionListeners['*'] || [])
.forEach((listener) => {
listener.call(null, act);
});
});
},
configEpics: {
gnMapStoreApiEpic: actionTrigger.epic
}
}));
}
export const getPluginsConfigOverride = (pluginsConfig) => isFunction(apiPluginsConfig)
? apiPluginsConfig(pluginsConfig)
: isObject(apiPluginsConfig)
? apiPluginsConfig
: pluginsConfig;
/* this function adds plugin based on the current query, used mainly for embed pages*/
export const addQueryPlugins = (pluginsConfig, query) => {
if (isArray(pluginsConfig)) {
return [
...(query?.allowFullscreen === 'true'
? [{
mandatory: true, // needed for custom viewers
name: 'FullScreen',
cfg: {
showText: true
}
},
{
mandatory: true, // needed for custom viewers
name: 'ActionNavbar',
cfg: {
containerPosition: 'footer',
variant: 'default',
leftMenuItems: [{
type: 'placeholder'
}],
rightMenuItems: [
{
type: 'plugin',
name: 'FullScreen',
size: 'xs'
}
]
}
}] : []),
...pluginsConfig
];
}
return pluginsConfig;
};