Commit 8db14d81 authored by Pedro Eduardo Trujillo's avatar Pedro Eduardo Trujillo
Browse files

Refactoriza y abstrae uso de widgets dinámicos

Mueve la lógica de lectura de configuración externa y la generación de
configuraciones de widget a un nuevo componente WidgetProvider, que
centraliza la importación de dependencias y las llamadas para gestionar
los permisos de acceso. Solamente devuelve una clave y un objeto de
configuración (que contiene el tipo de widgets y las propiedades que
necesita para construirse), para que desde la vista de detalle se pueda
instanciar.

Evita incluir en todas las vistas detalle la lógica de gestión de
widgets dinámicos, para evitar cargar dependencias innecesarias. En su
lugar, se define un nuevo mixin _CustomLayout que habilita esta
funcionalidad para la vista.

Abstrae conceptos para que otras entidades diferentes a activity puedan
hacer uso de widgets dinámicos en el futuro. Renombra callbacks de
preparación de widgets.

Simplifica comunicación entre widgets dependientes. En lugar de crear
nuevos canales de comunicación en ellos o en la vista de detalle como
hasta ahora, se hace uso de RestManager con target local para inyectar
los datos. El manejo de datos pasa a formar parte de los widget que lo
necesitan.
parent 3bd7c2d7
Loading
Loading
Loading
Loading
+11 −10
Original line number Diff line number Diff line
@@ -35,7 +35,7 @@ define([
					ACCEPT_COOKIES: 'acceptCookies',
					REQUEST_FAILED: 'requestFailed',
					USER_HAS_EDITION_CAPABILITIES: 'userHasEditionCapabilities',
					GOT_USER_GRANTS_FOR_ACTIVITY: 'gotUserGrantsForActivity'
					GOT_USER_GRANTS_FOR_ENTITY: 'gotUserGrantsForEntity'
				},
				actions: {
					GET_CREDENTIALS: 'getCredentials',
@@ -50,8 +50,8 @@ define([
					REQUEST_FAILED: 'requestFailed',
					HAS_USER_EDITION_CAPABILITIES: 'hasUserEditionCapabilities',
					USER_HAS_EDITION_CAPABILITIES: 'userHasEditionCapabilities',
					GET_USER_GRANTS_FOR_ACTIVITY: 'getUserGrantsForActivity',
					GOT_USER_GRANTS_FOR_ACTIVITY: 'gotUserGrantsForActivity'
					GET_USER_GRANTS_FOR_ENTITY: 'getUserGrantsForEntity',
					GOT_USER_GRANTS_FOR_ENTITY: 'gotUserGrantsForEntity'
				},

				target: redmicConfig.services.profile,
@@ -102,8 +102,8 @@ define([
				channel : this.getChannel('HAS_USER_EDITION_CAPABILITIES'),
				callback: '_subHasUserEditionCapabilities'
			},{
				channel : this.getChannel('GET_USER_GRANTS_FOR_ACTIVITY'),
				callback: '_subGetUserGrantsForActivity'
				channel : this.getChannel('GET_USER_GRANTS_FOR_ENTITY'),
				callback: '_subGetUserGrantsForEntity'
			});
		},

@@ -131,8 +131,8 @@ define([
				event: 'USER_HAS_EDITION_CAPABILITIES',
				channel: this.getChannel('USER_HAS_EDITION_CAPABILITIES')
			},{
				event: 'GOT_USER_GRANTS_FOR_ACTIVITY',
				channel: this.getChannel('GOT_USER_GRANTS_FOR_ACTIVITY')
				event: 'GOT_USER_GRANTS_FOR_ENTITY',
				channel: this.getChannel('GOT_USER_GRANTS_FOR_ENTITY')
			});
		},

@@ -178,13 +178,14 @@ define([
			});
		},

		_subGetUserGrantsForActivity: function(req) {
		_subGetUserGrantsForEntity: function(req) {
			// TODO consultar contra el servidor utilizando id de actividad y usuario

			var activityId = req.activityId,
			const entityId = req.entityId,
				entityName = req.entityName,
				accessGranted = Credentials.userIsEditor();

			this._emitEvt('GOT_USER_GRANTS_FOR_ACTIVITY', {
			this._emitEvt('GOT_USER_GRANTS_FOR_ENTITY', {
				accessGranted: accessGranted
			});
		},
+87 −0
Original line number Diff line number Diff line
define([
	'dojo/_base/declare'
	, 'dojo/_base/lang'
	, 'src/component/base/_ExternalConfig'
	, 'src/component/base/_Module'
	, 'src/component/layout/widgetProvider/_LayoutWidgetDefinition'
	, 'src/component/layout/widgetProvider/_PrepareLayoutWidget'
], function(
	declare
	, lang
	, _ExternalConfig
	, _Module
	, _LayoutWidgetDefinition
	, _PrepareLayoutWidget
) {

	return declare([_Module, _LayoutWidgetDefinition, _PrepareLayoutWidget, _ExternalConfig], {
		// summary:
		//   Componente que centraliza las definiciones de widgets, para ser provistos bajo demanda.

		postMixInProperties: function() {

			const defaultConfig = {
				ownChannel: 'widgetProvider',
				events: {
					GOT_WIDGET_CONFIG: 'gotWidgetConfig'
				},
				actions: {
					GET_WIDGETS_CONFIG: 'getWidgetsConfig',
					GOT_WIDGET_CONFIG: 'gotWidgetConfig'
				}
			};

			this._mergeOwnAttributes(defaultConfig);

			this.inherited(arguments);
		},

		_setOwnCallbacksForEvents: function() {

			this._onEvt('GOT_EXTERNAL_CONFIG', lang.hitch(this._onGotExternalConfig));
		},

		_defineSubscriptions: function() {

			this.subscriptionsConfig.push({
				channel : this.getChannel('GET_WIDGETS_CONFIG'),
				callback: '_subGetWidgetsConfig'
			});
		},

		_definePublications: function() {

			this.publicationsConfig.push({
				event: 'GOT_WIDGET_CONFIG',
				channel: this.getChannel('GOT_WIDGET_CONFIG')
			});
		},

		_subGetWidgetsConfig: function(req) {

			const propertyName = req.externalConfigPropName;

			this._currentPropertyName = propertyName;

			this._currentEntityData = req;

			this._emitEvt('GET_EXTERNAL_CONFIG', {propertyName});
		},


		_onGotExternalConfig: function(evt) {

			const configValue = evt[this._currentEntityData.externalConfigPropName];
			delete this._currentEntityData.externalConfigPropName;

			this._processDetailLayouts(this._currentEntityData, configValue);
		},

		_publishLayoutWidget(key, config) {

			this._emitEvt('GOT_WIDGET_CONFIG', {
				widgetConfig: {key, config}
			});
		}
	});
});
+194 −0
Original line number Diff line number Diff line
define([
	'dojo/_base/declare'
	, 'src/detail/activity/widget/ActivityAreaMap'
	, 'src/detail/activity/widget/ActivityCitationMap'
	, 'src/detail/activity/widget/ActivityFixedObservationSeriesList'
	, 'src/detail/activity/widget/ActivityFixedObservationSeriesMap'
	, 'src/detail/activity/widget/ActivityFixedTimeseriesLineCharts'
	, 'src/detail/activity/widget/ActivityFixedTimeseriesMap'
	, 'src/detail/activity/widget/ActivityFixedTimeseriesWindrose'
	, 'src/detail/activity/widget/ActivityInfrastructureMap'
	, 'src/detail/activity/widget/ActivityLayerMap'
	, 'src/detail/activity/widget/ActivityTrackingMap'
	, 'src/component/base/_Filter'
	, 'src/component/base/_Module'
	, 'src/component/base/_Show'
	, 'src/component/base/_Store'
	, 'src/component/browser/_ButtonsInRow'
	, 'src/component/browser/_Framework'
	, 'src/component/layout/genericDisplayer/GenericDisplayer'
	, 'src/component/layout/SupersetDisplayer'
	, 'src/component/map/_ImportWkt'
	, 'src/design/browser/_AddTotalBarComponent'
	, 'src/design/browser/_BrowserFullSizeDesignLayout'
], function(
	declare
	, ActivityAreaMap
	, ActivityCitationMap
	, ActivityFixedObservationSeriesList
	, ActivityFixedObservationSeriesMap
	, ActivityFixedTimeseriesLineCharts
	, ActivityFixedTimeseriesMap
	, ActivityFixedTimeseriesWindrose
	, ActivityInfrastructureMap
	, ActivityLayerMap
	, ActivityTrackingMap
	, _Filter
	, _Module
	, _Show
	, _Store
	, _ButtonsInRow
	, _Framework
	, GenericDisplayer
	, SupersetDisplayer
	, _ImportWkt
	, _AddTotalBarComponent
	, _BrowserFullSizeDesignLayout
) {

	return declare(null, {
		// summary:
		//   Métodos para obtener la configuración (definición y propiedades) de cada widget configurable para layouts.

		_getCitationMapConfig: function(config) {

			return {
				type: ActivityCitationMap,
				props: {
					title: 'citations',
					pathVariableId: config.pathVariableId
				}
			};
		},

		_getOgcLayerMapConfig: function(config) {

			return {
				type: ActivityLayerMap,
				props: {
					title: 'layers',
					pathVariableId: config.pathVariableId
				}
			};
		},

		_getTrackingMapConfig: function(config) {

			return {
				type: ActivityTrackingMap,
				props: {
					title: 'tracking',
					pathVariableId: config.pathVariableId,
					usePrivateTarget: config.accessGranted ?? false
				}
			};
		},

		_getInfrastructureMapConfig: function(config) {

			return {
				type: ActivityInfrastructureMap,
				props: {
					title: 'infrastructures',
					pathVariableId: config.pathVariableId
				}
			};
		},

		_getAreaMapConfig: function(config) {

			return {
				type: ActivityAreaMap,
				props: {
					title: 'area',
					pathVariableId: config.pathVariableId
				}
			};
		},

		_getFixedObservationSeriesMapConfig: function(config) {

			return {
				type: ActivityFixedObservationSeriesMap,
				props: {
					title: 'associatedObservationStations',
					pathVariableId: config.pathVariableId,
					stationDataTarget: config.stationDataTarget
				}
			};
		},

		_getFixedObservationSeriesListConfig: function(config) {

			return {
				type: ActivityFixedObservationSeriesList,
				props: {
					title: 'associatedObservationRegisters',
					pathVariableId: config.pathVariableId,
					stationDataTarget: config.stationDataTarget
				}
			};
		},

		_getFixedTimeseriesMapConfig: function(config) {

			return {
				type: ActivityFixedTimeseriesMap,
				props: {
					title: 'associatedSurveyStation',
					pathVariableId: config.pathVariableId,
					stationDataTarget: config.stationDataTarget
				}
			};
		},

		_getFixedTimeseriesLineChartsConfig: function(config) {

			return {
				type: ActivityFixedTimeseriesLineCharts,
				props: {
					title: 'charts',
					pathVariableId: config.pathVariableId,
					stationDataTarget: config.stationDataTarget
				}
			};
		},

		_getEmbeddedContentConfig: function(config) {

			const contentNode = globalThis.document.createElement('object');
			contentNode.innerHTML = config.content;

			return {
				type: GenericDisplayer,
				props: {
					title: 'embeddedContent',
					content: contentNode
				}
			};
		},

		_getSupersetDashboardConfig: function(config) {

			return {
				type: SupersetDisplayer,
				props: {
					title: config.title || 'supersetDashboard',
					pathVariableId: config.pathVariableId,
					dashboardConfig: config.dashboardConfig
				}
			};
		},

		_getFixedTimeseriesWindroseChartsConfig: function(config) {

			return {
				type: ActivityFixedTimeseriesWindrose,
				props: {
					title: 'windrose',
					pathVariableId: config.pathVariableId
				}
			};
		}
	});
});
+341 −0
Original line number Diff line number Diff line
define([
	'dojo/_base/declare'
	, 'dojo/Deferred'
], function(
	declare
	, Deferred
) {

	return declare(null, {
		// summary:
		//   Procesamiento de configuración para aplicar widgets adicionales a vistas detalle, en función del tipo
		//   de layout establecido para su identificador. Si no está establecido, se decide según su categoría.

		postMixInProperties: function() {

			const defaultConfig = {
				actions: {
					// credentials actions
					GET_USER_GRANTS_FOR_ENTITY: 'getUserGrantsForEntity',
					GOT_USER_GRANTS_FOR_ENTITY: 'gotUserGrantsForEntity'
				}
			};

			this._mergeOwnAttributes(defaultConfig);

			this.inherited(arguments);
		},

		_processDetailLayouts: function(entityData, layoutsConfig) {

			const currentElementId = entityData.entityId,
				detailLayouts = layoutsConfig[currentElementId];

			if (!detailLayouts?.length) {
				// retrocompatibilidad con activityCategory
				if (entityData.entityName === 'activity' && entityData.activityCategory) {
					this._prepareActivityCategoryLayoutWidgets(entityData);
				}
				return;
			}

			detailLayouts.forEach((layout) => this._processDetailLayout(entityData, layout));
		},

		_processDetailLayout: function(entityData, layout) {

			const layoutType = layout?.type;

			if (!layoutType) {
				return;
			}

			const layoutConfig = layout?.config ?? {},
				layoutCheckGrants = layout?.checkGrants ?? false;

			layoutConfig.pathVariableId = entityData.entityId;

			const processedLayout = {
				type: layoutType,
				config: layoutConfig
			}

			if (!layoutCheckGrants) {
				this._prepareLayoutWidgets(processedLayout);
				return;
			}

			const dfd = new Deferred();

			dfd.then(
				(resolvedGrants) => this._onLayoutGranted(processedLayout, resolvedGrants),
				(rejectedGrants) => this._onLayoutNotGranted(processedLayout, rejectedGrants));

			this._checkUserGrantsForEntityData(entityData, dfd);
		},

		_checkUserGrantsForEntityData: function(entityData, dfd) {

			this._once(this._buildChannel(this.credentialsChannel, 'GOT_USER_GRANTS_FOR_ENTITY'), (res) => {
				res?.accessGranted ? dfd.resolve(res) : dfd.reject(res);
			});

			this._publish(this._buildChannel(this.credentialsChannel, 'GET_USER_GRANTS_FOR_ENTITY'), entityData);
		},

		_onLayoutGranted: function(layout, resolvedGrants) {

			layout.config.accessGranted = true;

			this._prepareLayoutWidgets(layout);
		},

		_onLayoutNotGranted: function(layout, rejectedGrants) {

			// TODO mostrar al usuario un aviso de que existen datos en la actividad, pero no tiene permiso.
			// Ofrecerle identificarse.
		},

		_prepareLayoutWidgets: function(layout) {

			const prepareWidgetsMethod = `_${layout.type}PrepareLayoutWidgets`;

			this[prepareWidgetsMethod]?.(layout.config);
		},

		_prepareActivityCategoryLayoutWidgets: function(entityData) {

			const activityCategory = entityData.activityCategory;
			let layoutType;

			if (activityCategory === 'ci') {
				layoutType = 'citationMap';
			} else if (activityCategory === 'ml') {
				layoutType = 'ogcLayerMap';
			} else if (['tr', 'at', 'pt'].indexOf(activityCategory) !== -1) {
				layoutType = 'trackingMap';
			} else if (activityCategory === 'if') {
				layoutType = 'infrastructureMap';
			} else if (activityCategory === 'ar') {
				layoutType = 'areaMap';
			} else if (activityCategory === 'ft') {
				layoutType = 'featureTimeseriesMapChart';
			} else if (activityCategory === 'ec') {
				layoutType = 'embeddedContent';
			} else {
				return;
			}

			const layoutConfig = {
				pathVariableId: entityData.entityId
			};

			this._prepareLayoutWidgets({
				type: layoutType,
				config: layoutConfig
			});
		},

		_citationMapPrepareLayoutWidgets: function(layoutConfig) {

			const key = 'citationMap';

			const windowConfig = {
				width: 6,
				height: 6
			};

			const config = this._merge([windowConfig, this._getCitationMapConfig(layoutConfig)]);

			this._publishLayoutWidget(key, config);
		},

		_ogcLayerMapPrepareLayoutWidgets: function(layoutConfig) {

			const key = 'ogcLayerMap';

			const windowConfig = {
				width: 6,
				height: 6
			};

			const config = this._merge([windowConfig, this._getOgcLayerMapConfig(layoutConfig)]);

			this._publishLayoutWidget(key, config);
		},

		_trackingMapPrepareLayoutWidgets: function(layoutConfig) {

			const key = 'trackingMap';

			const windowConfig = {
				width: 6,
				height: 6
			};

			const config = this._merge([windowConfig, this._getTrackingMapConfig(layoutConfig)]);

			this._publishLayoutWidget(key, config);
		},

		_infrastructureMapPrepareLayoutWidgets: function(layoutConfig) {

			const key = 'infrastructureMap';

			const windowConfig = {
				width: 6,
				height: 6
			};

			const config = this._merge([windowConfig, this._getInfrastructureMapConfig(layoutConfig)]);

			this._publishLayoutWidget(key, config);
		},

		_areaMapPrepareLayoutWidgets: function(layoutConfig) {

			const key = 'areaMap';

			const windowConfig = {
				width: 6,
				height: 6
			};

			const config = this._merge([windowConfig, this._getAreaMapConfig(layoutConfig)]);

			this._publishLayoutWidget(key, config);
		},

		_featureMapPrepareLayoutWidgets: function(layoutConfig) {

			// configuración común

			layoutConfig.stationDataTarget = `observationStationData${layoutConfig.pathVariableId}`;

			// widget de mapa

			const mapKey = 'fixedObservationSeriesMap';

			const mapWindowConfig = {
				width: 6,
				height: 6
			};

			const mapConfig = this._merge([
				mapWindowConfig,
				this._getFixedObservationSeriesMapConfig(layoutConfig)
			]);

			this._publishLayoutWidget(mapKey, mapConfig);

			// widget de listado

			const listKey = 'fixedObservationSeriesList';

			const listWindowConfig = {
				width: 6,
				height: 6,
				hidden: true
			};

			const listConfig = this._merge([
				listWindowConfig,
				this._getFixedObservationSeriesListConfig(layoutConfig)
			]);

			this._publishLayoutWidget(listKey, listConfig);
		},

		_featureTimeseriesMapChartPrepareLayoutWidgets: function(layoutConfig) {

			// configuración común

			layoutConfig.stationDataTarget = `timeseriesStationData${layoutConfig.pathVariableId}`;

			// widget de mapa

			const mapKey = 'fixedTimeseriesMap';

			const mapWindowConfig = {
				width: 6,
				height: 6
			};

			const mapConfig = this._merge([mapWindowConfig, this._getFixedTimeseriesMapConfig(layoutConfig)]);

			this._publishLayoutWidget(mapKey, mapConfig);

			// widget de gráficas lineales

			const lineChartsKey = 'fixedTimeseriesLineCharts';

			const lineChartsWindowConfig = {
				width: 6,
				height: 5,
				hidden: true
			};

			const lineChartsConfig = this._merge([
				lineChartsWindowConfig,
				this._getFixedTimeseriesLineChartsConfig(layoutConfig)
			]);

			if (this._lineChartsWidgetKey) {
				this._destroyWidget(this._lineChartsWidgetKey);
			}
			this._lineChartsWidgetKey = lineChartsKey;

			this._publishLayoutWidget(lineChartsKey, lineChartsConfig);

			// widgets de gráfica windrose

			const windroseChartsKey = 'fixedTimeseriesWindroseCharts';

			const windroseChartsWindowConfig = {
				width: 3,
				height: 5,
				hidden: true
			};

			const windroseChartsConfig = this._merge([
				windroseChartsWindowConfig,
				this._getFixedTimeseriesWindroseChartsConfig(layoutConfig)
			]);

			if (this._windroseChartsWidgetKey) {
				this._destroyWidget(this._windroseChartsWidgetKey);
			}
			this._windroseChartsWidgetKey = windroseChartsKey;

			this._publishLayoutWidget(windroseChartsKey, windroseChartsConfig);
		},

		_embeddedContentPrepareLayoutWidgets: function(layoutConfig) {

			const key = 'embeddedContent';

			const windowConfig = {
				width: 6,
				height: 6
			};

			const config = this._merge([windowConfig, this._getEmbeddedContentConfig(layoutConfig)]);

			this._publishLayoutWidget(key, config);
		},

		_supersetDashboardPrepareLayoutWidgets: function(layoutConfig) {

			const key = `superset-${layoutConfig.dashboardConfig?.id}`;

			const windowConfig = {
				width: 6,
				height: 6
			};

			const config = this._merge([windowConfig, this._getSupersetDashboardConfig(layoutConfig)]);

			this._publishLayoutWidget(key, config);
		}
	});
});
+98 −0
Original line number Diff line number Diff line
define([
	'dojo/_base/declare'
	, 'dojo/_base/lang'
	, 'src/component/layout/widgetProvider/WidgetProvider'
], function(
	declare
	, lang
	, WidgetProvider
) {

	return declare(null, {
		//	summary:
		//		Aplicación de componentes adicionales para la vista detalle de Activity, en función del tipo de layout
		//		establecido según su identificador. Si no está establecido, se decide según su categoría.

		postMixInProperties: function() {

			const defaultConfig = {
				events: {
					GET_WIDGETS_CONFIG: 'getWidgetsConfig'
				},
				actions: {
				},
				_layoutWidgets: []
			};

			this._mergeOwnAttributes(defaultConfig);

			this.inherited(arguments);
		},

		_setOwnCallbacksForEvents: function() {

			this.inherited(arguments);

			this._onEvt('ME_OR_ANCESTOR_HIDDEN', lang.hitch(this, this._onCustomLayoutHidden));
		},

		_initialize: function() {

			this.inherited(arguments);

			this._widgetProvider = new WidgetProvider({
				parentChannel: this.getChannel()
			});
		},

		_defineSubscriptions: function() {

			this.inherited(arguments);

			this.subscriptionsConfig.push({
				channel: this._widgetProvider.getChannel('GOT_WIDGET_CONFIG'),
				callback: '_subGotWidgetConfig'
			});
		},

		_definePublications: function() {

			this.inherited(arguments);

			this.publicationsConfig.push({
				event: 'GET_WIDGETS_CONFIG',
				channel: this._widgetProvider.getChannel('GET_WIDGETS_CONFIG')
			});
		},

		_subGotWidgetConfig: function(res) {

			const widgetConfig = res.widgetConfig;

			this._addLayoutWidget(widgetConfig.key, widgetConfig.config);
		},

		_addLayoutWidget: function(key, config) {

			this._addWidget(key, config);
			this._layoutWidgets.push(key);
		},

		_onCustomLayoutHidden: function() {

			this._removeLayoutWidgets();
		},

		_removeLayoutWidgets: function() {

			if (!this._layoutWidgets) {
				return;
			}

			while (this._layoutWidgets.length) {
				const key = this._layoutWidgets.pop();
				this._destroyWidget(key);
			}
		}
	});
});
Loading