Skip to content

Commit

Permalink
Get config from url
Browse files Browse the repository at this point in the history
  • Loading branch information
ychoquet committed May 6, 2024
1 parent ba9c807 commit 96abc1a
Show file tree
Hide file tree
Showing 12 changed files with 369 additions and 34 deletions.
41 changes: 22 additions & 19 deletions packages/geoview-core/public/templates/config-sandbox.html
Original file line number Diff line number Diff line change
Expand Up @@ -194,27 +194,30 @@ <h4 id="HLCONF1">Sanbox Map</h4>
const configOutput = document.getElementById('configOutput');

// get config and test if JSON is valid
const mapConfig = cgpv.api.configApi.getMapConfig(configArea.value, langue);
configOutput.value = mapConfig.getIndentedJsonString();
// const mapConfig = cgpv.api.configApi.getMapConfig(configArea.value, langue);
const returnedValue = cgpv.api.configApi.getConfigFromUrl('p=3857&z=4&c=-100,40&l=en&t=dark&b={basemapId:transport,shaded:false,labeled:true}&i=dynamic&cc=overview-map&keys=12acd145-626a-49eb-b850-0a59c9bc7506,ccc75c12-5acc-4a6a-959f-ef6f621147b9');
returnedValue.then((mapConfig) => {
configOutput.value = mapConfig.getIndentedJsonString();

// Generate line numbers
(() => {
const textarea = document.getElementById('configOutput');
const lineNumbersContainer = document.getElementById('outputLineNumbers');
const lines = textarea.value.split('\n').length;
const lineNumbers = Array.from({ length: lines }, (_, index) => '').join('<span />');
lineNumbersContainer.innerHTML = lineNumbers;
})();
// Generate line numbers
(() => {
const textarea = document.getElementById('configOutput');
const lineNumbersContainer = document.getElementById('outputLineNumbers');
const lines = textarea.value.split('\n').length;
const lineNumbers = Array.from({ length: lines }, (_, index) => '').join('<span />');
lineNumbersContainer.innerHTML = lineNumbers;
})();

// set class and message
message.classList.add('config-json-valid');
message.classList.remove('config-error');
if (mapConfig.isValid) {
message.innerHTML = 'File is valid, see console for details...';
document.getElementById('createMap').disabled = true;
} else {
message.innerHTML = 'File is invalid, see console for details...';
}
// set class and message
message.classList.add('config-json-valid');
message.classList.remove('config-error');
if (mapConfig.isValid) {
message.innerHTML = 'File is valid, see console for details...';
document.getElementById('createMap').disabled = true;
} else {
message.innerHTML = 'File is invalid, see console for details...';
}
});
});

// Create Button============================================================================================================
Expand Down
157 changes: 156 additions & 1 deletion packages/geoview-core/src/api/config/config-api.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,176 @@
import { CV_DEFAULT_MAP_FEATURE_CONFIG } from '@config/types/config-constants';
import { TypeJsonObject, toJsonObject } from '@config/types/config-types';
import { Cast, TypeJsonValue, TypeJsonObject, toJsonObject, TypeJsonArray } from '@config/types/config-types';
import { TypeDisplayLanguage } from '@config/types/map-schema-types';
import { MapFeatureConfig } from '@config/types/classes/map-feature-config';
import { UUIDmapConfigReader } from '@config/uuid-config-reader';
import { logger } from '@/core//utils/logger';

/**
* The API class that create configuration object. It is used to validate and read the service and layer metadata.
* @exports
* @class DefaultConfig
*/
export class ConfigApi {
/**
* Parse the parameters obtained from a url.
*
* @param {string} urlParams The parameters found on the url after the ?.
*
* @returns {TypeJsonObject} Object containing the parsed params.
* @static @private
*/
static #getMapPropsFromUrlParams(urlParams: string): TypeJsonObject {
// Get parameters from path. Ex: x=123&y=456 will get {"x": 123, "z": "456"}
const obj: TypeJsonObject = {};

if (urlParams !== undefined) {
const params = urlParams.split('&');

for (let i = 0; i < params.length; i += 1) {
const param = params[i].split('=');
const key = param[0];
const value = param[1] as TypeJsonValue;

obj[key] = Cast<TypeJsonObject>(value);
}
}

return obj;
}

/**
* Get url parameters from url param search string.
*
* @param {objStr} objStr the url parameter string.
*
* @returns {TypeJsonObject} an object containing url parameters.
* @staric @private
*/
static #parseObjectFromUrl(objStr: string): TypeJsonObject {
const obj: TypeJsonObject = {};

if (objStr && objStr.length) {
// get the text in between { }
const objStrPropRegex = /(?:[{_.])(.*?)(?=[}_.])/g;

const objStrProps = objStr.match(objStrPropRegex);

if (objStrProps && objStrProps.length) {
// first { is kept with regex, remove
const objProps = objStrProps[0].replace(/{/g, '').split(',');

if (objProps) {
for (let i = 0; i < objProps.length; i += 1) {
const prop = objProps[i].split(':');
if (prop && prop.length) {
const key: string = prop[0];
const value: string = prop[1];

if (prop[1] === 'true') {
obj[key] = Cast<TypeJsonObject>(true);
} else if (prop[1] === 'false') {
obj[key] = Cast<TypeJsonObject>(false);
} else {
obj[key] = Cast<TypeJsonObject>(value);
}
}
}
}
}
}

return obj;
}

/**
* Get a map feature config from url parameters.
* @param {string} urlStringParams The url parameters.
*
* @returns {Promise<MapFeatureConfig | undefined>} A map feature configuration object generated from url parameters.
* @static @async
*/
static async getConfigFromUrl(urlStringParams: string): Promise<MapFeatureConfig | undefined> {
// return the parameters as an object if url contains any params
const urlParams = ConfigApi.#getMapPropsFromUrlParams(urlStringParams);

// if user provided any url parameters update
const jsonConfig = {} as TypeJsonObject;

// update the language if provided from the map configuration.
const displayLanguage = (urlParams.l as TypeDisplayLanguage) || 'en';

if (Object.keys(urlParams).length && !urlParams.geoms) {
// Ex: p=3857&z=4&c=40,-100&l=en&t=dark&b={basemapId:transport,shaded:false,labeled:true}&i=dynamic&cp=details-panel,layers-panel&cc=overview-map&keys=12acd145-626a-49eb-b850-0a59c9bc7506,ccc75c12-5acc-4a6a-959f-ef6f621147b9

// get center
let center: string[] = [];
if (urlParams.c) center = (urlParams.c as string).split(',');
if (center.length !== 2)
center = [
CV_DEFAULT_MAP_FEATURE_CONFIG.map.viewSettings.initialView!.zoomAndCenter![1][0]!.toString(),
CV_DEFAULT_MAP_FEATURE_CONFIG.map.viewSettings.initialView!.zoomAndCenter![1][1].toString(),
];

// get zoom
let zoom = CV_DEFAULT_MAP_FEATURE_CONFIG.map.viewSettings.initialView!.zoomAndCenter![0].toString();
if (urlParams.z) zoom = urlParams.z as string;

jsonConfig.map = {
interaction: urlParams.i as TypeJsonObject,
viewSettings: {
initialView: {
zoomAndCenter: [parseInt(zoom, 10), [parseInt(center[0], 10), parseInt(center[1], 10)]] as TypeJsonObject,
},
projection: parseInt(urlParams.p as string, 10) as TypeJsonObject,
},
basemapOptions: ConfigApi.#parseObjectFromUrl(urlParams.b as string),
listOfGeoviewLayerConfig: Cast<TypeJsonObject>([]),
};

// get layer information from catalog using their uuid's if any passed from url params
if (urlParams.keys) {
try {
// Get the layers config
const promise = UUIDmapConfigReader.getGVConfigFromUUIDs(
CV_DEFAULT_MAP_FEATURE_CONFIG.serviceUrls.geocoreUrl,
displayLanguage.split('-')[0],
urlParams.keys.toString().split(',')
);
(jsonConfig.map.listOfGeoviewLayerConfig as TypeJsonObject[]) = await promise;
} catch (error) {
// Log
logger.logError('Failed to get the GeoView layers from url keys', urlParams.keys, error);
}
}

// get core components
if (urlParams.cc) {
(jsonConfig.components as TypeJsonArray) = (urlParams.cc as string).split(',') as TypeJsonArray;
}

// get core packages if any
if (urlParams.cp) {
(jsonConfig.corePackages as TypeJsonArray) = (urlParams.cp as string).split(',') as TypeJsonArray;
}

// update the version if provided from the map configuration.
jsonConfig.schemaVersionUsed = urlParams.v as TypeJsonObject;
}

// Trace the detail config read from url
logger.logTraceDetailed('URL Config - ', jsonConfig);

return new MapFeatureConfig(jsonConfig, displayLanguage);
}

/**
* @static
* Get the default values that are applied to the map feature configuration when the user doesn't provide a value for a field
* that is covered by a default value.
* @param {TypeDisplayLanguage} language The language of the map feature config we want to produce.
*
* @returns {MapFeatureConfig} The map feature configuration default values.
* @static
*/
static getDefaultMapFeatureConfig(language: TypeDisplayLanguage): MapFeatureConfig {
return new MapFeatureConfig(toJsonObject(CV_DEFAULT_MAP_FEATURE_CONFIG), language);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ export abstract class AbstractGeoviewLayerConfig {
/** The GeoView layer identifier. */
geoviewLayerId: string;

/** Type of GeoView layer. */
abstract geoviewLayerType: TypeGeoviewLayerType;

/**
* The display name of the layer (English/French). If it is not present the viewer will make an attempt to scrape this
* information.
Expand Down Expand Up @@ -97,18 +94,21 @@ export abstract class AbstractGeoviewLayerConfig {
.filter((subLayerConfig) => {
return subLayerConfig;
}) as ConfigBaseClass[];
this.#validate();
}

/**
* Validate the object properties. Layer name and type must be set.
* @private
*/
#validate(): void {
if (!this.geoviewLayerName)
protected validate(): void {
if (!this.geoviewLayerName) {
logger.logError(`Property geoviewLayerName is mandatory for GeoView layer ${this.geoviewLayerId} of type ${this.geoviewLayerType}.`);
if (!this.geoviewLayerType)
this.propagateError();
}
if (!this.geoviewLayerType) {
logger.logError(`Property geoviewLayerType is mandatory for GeoView layer ${this.geoviewLayerId} of type ${this.geoviewLayerType}.`);
this.propagateError();
}
}

/**
Expand All @@ -126,6 +126,14 @@ export abstract class AbstractGeoviewLayerConfig {
*/
protected abstract get geoviewLayerSchema(): string;

/**
* The getter method that returns the geoview layer type to use for the validation.
*
* @returns {string} The GeoView layer schema associated to the config.
* @protected @abstract
*/
abstract get geoviewLayerType(): TypeGeoviewLayerType;

/**
* The method used to implement the class factory model that returns the instance of the class based on the sublayer
* type needed.
Expand Down Expand Up @@ -155,4 +163,13 @@ export abstract class AbstractGeoviewLayerConfig {
this.#errorDetected = true;
this.#mapFeatureConfig?.propagateError();
}

/**
* The getter method that returns the isValid flag (true when the map feature config is valid).
*
* @returns {boolean} The isValid property associated to map feature config.
*/
get isValid(): boolean {
return !this.#errorDetected;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class EsriDynamicLayerConfig extends AbstractGeoviewLayerConfig {
if (!this.metadataAccessPath) {
throw new Error(`metadataAccessPath is mandatory for GeoView layer ${this.geoviewLayerId} of type ${this.geoviewLayerType}.`);
}
this.validate();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export class EsriFeatureLayerConfig extends AbstractGeoviewLayerConfig {
if (!this.metadataAccessPath) {
throw new Error(`metadataAccessPath is mandatory for GeoView layer ${this.geoviewLayerId} of type ${this.geoviewLayerType}.`);
}
this.validate();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export class MapFeatureConfig {
this.theme = (clonedJsonConfig.theme || CV_DEFAULT_MAP_FEATURE_CONFIG.theme) as TypeDisplayTheme;
this.navBar = [...((clonedJsonConfig.navBar || CV_DEFAULT_MAP_FEATURE_CONFIG.navBar) as TypeNavBarProps)];
this.appBar = Cast<TypeAppBarProps>(defaultsDeep(clonedJsonConfig.appBar, CV_DEFAULT_MAP_FEATURE_CONFIG.appBar));
this.footerBar = Cast<TypeFooterBarProps>(defaultsDeep(clonedJsonConfig.footerBar, CV_DEFAULT_MAP_FEATURE_CONFIG.footerBar));
this.footerBar = Cast<TypeFooterBarProps>(clonedJsonConfig.footerBar);
this.overviewMap = Cast<TypeOverviewMapProps>(defaultsDeep(clonedJsonConfig.overviewMap, CV_DEFAULT_MAP_FEATURE_CONFIG.overviewMap));
this.components = [...((clonedJsonConfig.components || CV_DEFAULT_MAP_FEATURE_CONFIG.components) as TypeMapComponents)];
this.corePackages = [...((clonedJsonConfig.corePackages || CV_DEFAULT_MAP_FEATURE_CONFIG.corePackages) as TypeMapCorePackages)];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export abstract class AbstractBaseLayerEntryConfig extends ConfigBaseClass {
initialSettings: TypeLayerInitialSettings,
language: TypeDisplayLanguage,
geoviewLayerConfig: AbstractGeoviewLayerConfig,
parentNode: ConfigBaseClass
parentNode?: ConfigBaseClass
) {
super(layerConfig, initialSettings, language, geoviewLayerConfig, parentNode);
// If the user has provided a source then keep it, else create an empty one.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { TypeJsonArray, TypeJsonObject } from '@config/types/config-types';
import { TypeDisplayLanguage, TypeLayerEntryType, TypeLayerInitialSettings } from '@config/types/map-schema-types';
import { AbstractGeoviewLayerConfig } from '@config/types/classes/geoview-config/abstract-geoview-layer-config';
import { ConfigBaseClass } from '@config/types/classes/sub-layer-config/config-base-class';
import { layerEntryIsGroupLayer } from '../../type-guards';
import { layerEntryIsGroupLayer } from '@config/types/type-guards';

/**
* Type used to define a group of layers. It can be either subgroups or sublayers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class EsriDynamicLayerEntryConfig extends AbstractBaseLayerEntryConfig {
initialSettings: TypeLayerInitialSettings,
language: TypeDisplayLanguage,
geoviewLayerConfig: AbstractGeoviewLayerConfig,
parentNode: ConfigBaseClass
parentNode?: ConfigBaseClass
) {
super(layerConfig, initialSettings, language, geoviewLayerConfig, parentNode);
this.layerFilter = layerConfig.layerFilter as string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class EsriFeatureLayerEntryConfig extends AbstractBaseLayerEntryConfig {
initialSettings: TypeLayerInitialSettings,
language: TypeDisplayLanguage,
geoviewLayerConfig: AbstractGeoviewLayerConfig,
parentNode: ConfigBaseClass
parentNode?: ConfigBaseClass
) {
super(layerConfig, initialSettings, language, geoviewLayerConfig, parentNode);
this.layerFilter = layerConfig.layerFilter as string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
TypeBasemapOptions,
TypeDisplayTheme,
TypeExternalPackages,
TypeFooterBarProps,
TypeInteraction,
TypeLayerEntryType,
TypeListOfLocalizedLanguages,
Expand Down Expand Up @@ -162,7 +161,6 @@ export const CV_DEFAULT_MAP_FEATURE_CONFIG = {
theme: 'dark' as TypeDisplayTheme,
components: ['north-arrow', 'overview-map'],
appBar: { tabs: { core: ['geolocator'] } } as TypeAppBarProps,
footerBar: {} as TypeFooterBarProps,
navBar: ['zoom', 'fullscreen', 'home'] as TypeNavBarProps,
corePackages: [],
overviewMap: undefined as TypeOverviewMapProps | undefined,
Expand Down
Loading

0 comments on commit 96abc1a

Please sign in to comment.