From 636b756dd57bcd43f6f0fcdca07cbb72dcf6fd60 Mon Sep 17 00:00:00 2001 From: Robert Bartel Date: Fri, 2 Aug 2024 14:53:20 -0400 Subject: [PATCH 01/18] Deprecate non-working communication pkg funcs. Marking several utility functions (used by some broken places in GUI code) as deprecated, largely because they don't actually work. --- .../dmod/communication/maas_request/utilities.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/lib/communication/dmod/communication/maas_request/utilities.py b/python/lib/communication/dmod/communication/maas_request/utilities.py index dc78924c5..fe1b5f69f 100644 --- a/python/lib/communication/dmod/communication/maas_request/utilities.py +++ b/python/lib/communication/dmod/communication/maas_request/utilities.py @@ -1,6 +1,8 @@ from .model_exec_request import ModelExecRequest, get_available_models +from deprecated import deprecated +@deprecated("Avoid broken util functions not updated for recent message changes") def get_available_outputs() -> set: """ :return: A collection of all valid outputs across any model @@ -13,6 +15,7 @@ def get_available_outputs() -> set: return all_outputs +@deprecated("Avoid broken util functions not updated for recent message changes") def get_distribution_types() -> set: """ :return: The distribution types used across any model @@ -25,6 +28,7 @@ def get_distribution_types() -> set: return all_types +@deprecated("Avoid broken util functions not updated for recent message changes") def get_parameters() -> dict: """ Maps each model to the natural and human readable forms of all of their parameters From f2133970e749ccf88f86c9eb6f2da84b3e60bfbd Mon Sep 17 00:00:00 2001 From: Robert Bartel Date: Fri, 2 Aug 2024 14:54:02 -0400 Subject: [PATCH 02/18] Bump communication version to 0.21.1. --- python/lib/communication/dmod/communication/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/lib/communication/dmod/communication/_version.py b/python/lib/communication/dmod/communication/_version.py index e45337122..8c306aa66 100644 --- a/python/lib/communication/dmod/communication/_version.py +++ b/python/lib/communication/dmod/communication/_version.py @@ -1 +1 @@ -__version__ = '0.21.0' +__version__ = '0.21.1' From fb33c68e1ccf2ffc8eac0a2cbc32afea72de8417 Mon Sep 17 00:00:00 2001 From: Robert Bartel Date: Fri, 2 Aug 2024 14:59:38 -0400 Subject: [PATCH 03/18] Fix GUI config for django.contrib.staticfiles. Adding required STATIC_ROOT to be main static directory, and removing the addition of this directory in things scanned by the static file finders (i.e., since root, have other found things put here); this was needed to fix issues with static files from main model exec GUI app, which were not being found with previous config. --- python/gui/maas_experiment/application_values.py | 2 ++ python/gui/maas_experiment/settings.py | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/python/gui/maas_experiment/application_values.py b/python/gui/maas_experiment/application_values.py index 39f7344a8..297a6ae36 100644 --- a/python/gui/maas_experiment/application_values.py +++ b/python/gui/maas_experiment/application_values.py @@ -173,6 +173,8 @@ def get_full_localtimezone(): BASE_DIRECTORY = Path(__file__).resolve().parent.parent """The path to the base directory of the service""" +STATIC_ROOT = BASE_DIRECTORY / "static" + STATIC_RESOURCE_DIRECTORY = BASE_DIRECTORY / "static" / "resources" APPLICATION_NAME = os.environ.get("APPLICATION_NAME", "Model as a Service") diff --git a/python/gui/maas_experiment/settings.py b/python/gui/maas_experiment/settings.py index 42938835a..589b5e444 100644 --- a/python/gui/maas_experiment/settings.py +++ b/python/gui/maas_experiment/settings.py @@ -134,5 +134,4 @@ STATIC_URL = '/static/' STATICFILES_DIRS = [ - BASE_DIRECTORY / "static" ] From 48cf79f8852eb54542bf6598f0fb5a34ba69614c Mon Sep 17 00:00:00 2001 From: Robert Bartel Date: Fri, 2 Aug 2024 15:01:06 -0400 Subject: [PATCH 04/18] Adding required daphne dependency for GUI. Adding daphne to dependencies.txt to install this requirment in GUI image build. --- python/gui/dependencies.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/python/gui/dependencies.txt b/python/gui/dependencies.txt index 82c66ba24..63daec4a2 100644 --- a/python/gui/dependencies.txt +++ b/python/gui/dependencies.txt @@ -18,3 +18,4 @@ channels-redis djangorestframework psycopg2-binary # TODO: get source package in future. Note that psycopg2 cannot be used on Mac; psycopg2-binary must be used numpy +daphne From 7975aaa4557b6df71955f872effe193bc35c0118 Mon Sep 17 00:00:00 2001 From: Robert Bartel Date: Fri, 2 Aug 2024 15:03:49 -0400 Subject: [PATCH 05/18] Have base maas GUI template use new CSS standard. Moving base maas config html template to use css/master.css instead of common/css/base.css for consistent presentation. --- python/gui/MaaS/templates/base.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/gui/MaaS/templates/base.html b/python/gui/MaaS/templates/base.html index 2d7d4fc7c..103ca5684 100644 --- a/python/gui/MaaS/templates/base.html +++ b/python/gui/MaaS/templates/base.html @@ -1,12 +1,12 @@ +{% load static %} {{ title|default:"Distributed Model on Demand" }} - {% load static %} - + {% block styles %}{% endblock styles %} + + +{% endblock scripts %} + +{% block content %} +
+
+ Fabric + + +
+
+
+ +{% include "data_pane.html" %} + + + + + +{% endblock content %} From 565557b49ccbf2520d28f7040cbc41b444858e93 Mon Sep 17 00:00:00 2001 From: Robert Bartel Date: Tue, 6 Aug 2024 10:50:00 -0400 Subject: [PATCH 11/18] Add domain.js, initially a copy of map.js. --- python/gui/MaaS/static/common/js/domain.js | 486 +++++++++++++++++++++ 1 file changed, 486 insertions(+) create mode 100644 python/gui/MaaS/static/common/js/domain.js diff --git a/python/gui/MaaS/static/common/js/domain.js b/python/gui/MaaS/static/common/js/domain.js new file mode 100644 index 000000000..1bb3e0508 --- /dev/null +++ b/python/gui/MaaS/static/common/js/domain.js @@ -0,0 +1,486 @@ + +const maxRows = 10; + +var layerUrls = []; + +var mymap = null; + +var layerStyle = { + color: "#555", + weight: 5, + fillColor: "#00ad79", + fillOpacity: 0.6 +}; + +var lineStyle = { + color: "#0328fc", + weight: 5, + fillColor: "#0328fc", + fillOpacity: 0.6 +}; + +var selectedShapeStyle = { + fillColor: "#fce803" +}; + +var selectedLineStyle = { + weight: 10, + color: "#ffc130" +} + +var incidentalShapeStyle = { + fillColor: "#3a34eb" +}; + +var incidentalLineStyle = { + +}; + +var activeLayer = null; +var activeLayerName = null; +var loadedLayers = {}; + +var selectedFeatures = {}; + +// Features that were only selected because another feature was selected +var incidentalFeatures = {}; + +var selectedLayers = {}; + +startup_scripts.push( + function(){ + mymap = L.map('mapid').setView(centerLine, zoom); + + L.tileLayer(mapUrl, { + maxZoom: maxZoom, + attribution: attribution, + tileSize: 512, + zoomOffset: -1 + }).addTo(mymap); + } +); + +function getLayers() { + var layers = []; + + if (layerUrls != null && layerUrls.length > 0) { + // Get geojson layers and place on map + layerUrls.forEach( + layerUrl => function(layerUrl){ + $.ajax( + { + url: layerUrl.replaceAll("&", "&"), + type: 'GET', + async: false, + error: function(xhr,status,error) { + console.error(error); + }, + success: function(result,status,xhr) { + layers.push(result); + } + } + ); + }(layerUrl) + ); + } + return layers; +} + +function getServerData(serverUrl) { + var data = []; + + $.ajax( + { + url: serverUrl, + type: 'GET', + async: false, + error: function(xhr,status,error) { + console.error(error); + }, + success: function(result,status,xhr) { + data.push(result); + } + } + ); + + if (data.length == 0) { + return null; + } + + return data[0]; +} + +function layerCreation(geoJsonPoint, latlng) { + var colorIndex = 0; + + return L.circleMarker(latlng); +} + +function plotMapLayers(featureDocuments, map) { + var layers = []; + + var addTooltip = function(feature, layer) { + if ("id" in feature) { + layer.bindTooltip(feature["id"]); + } + else if ("properties" in feature && "Name" in feature["properties"]) { + layer.bindTooltip(feature['properties']['Name']); + } + } + + var buildProperties = function(feature, layer) { + layer.bindPopup(propertiesToHTML(feature, crosswalk), {"maxWidth": 2000}) + addTooltip(feature, layer); + }; + + var addFeature = function(featureDocument) { + var layer = L.geoJSON( + featureDocument.features, + { + onEachFeature: buildProperties, + style: function() {return layerStyle;}, + pointToLayer: layerCreation + } + ).addTo(map); + layers.push(layer); + } + + featureDocuments.forEach(featureDocument => addFeature(featureDocument)); + return layers; +} + +function propertiesToHTML(geojson, xwalk) { + var properties = geojson.properties; + var markup = ""; + if ("Name" in properties) { + markup += "

" + properties.Name + "

"; + } + else if ("id" in geojson) { + markup += "

" + geojson.id + "

"; + } + + if (geojson.id in xwalk) { + var cross_walk = xwalk[geojson.id]; + if ("COMID" in cross_walk) { + var comids = cross_walk['COMID']; + markup += "

COMID

"; + markup += "
    "; + for (comid of comids) { + markup += "
  • " + comid + "
  • "; + } + markup += "
"; + } + } + + markup += ""; + + var propertyKeys = []; + + for (const property in properties) { + var propertyIsNotName = property.toLowerCase() != "name"; + var propertyIsNotBlank = properties[property] != null && properties[property] != ""; + var propertyIsNotAnObject = typeof properties[property] != 'object'; + if (propertyIsNotName && propertyIsNotBlank && propertyIsNotAnObject) { + propertyKeys.push(property); + } + } + + var columnCount = Math.ceil(propertyKeys.length / maxRows); + var rowCount = Math.min(propertyKeys.length, maxRows); + + for(rowIndex = 0; rowIndex < rowCount; rowIndex++) { + if (rowIndex % 2 == 0) { + markup += ""; + } + else { + markup += ""; + } + + for (columnIndex = 0; columnIndex < columnCount; columnIndex++) { + var keyIndex = rowIndex * columnCount + columnIndex; + + if (keyIndex < propertyKeys.length) { + var key = propertyKeys[keyIndex]; + + markup += ""; + markup += ""; + } + } + + markup += ""; + } + + markup += "
" + key + ":" + properties[key] + "
"; + return markup; +} + +function getSelectedShapeStyle(feature) { + var featureExists = feature != null && typeof feature != 'undefined'; + var featureType = feature.geometry.type.toLowerCase(); + + if (featureExists && featureType.includes("linestring")) { + return selectedLineStyle; + } + + return selectedShapeStyle; +} + +function getShapeStyle(feature) { + var featureExists = feature != null && typeof feature != 'undefined'; + var featureType = feature.geometry.type.toLowerCase(); + + if (featureExists && featureType.includes("linestring")) { + return lineStyle; + } + + return layerStyle; +} + +function getIncidentalStyle(feature) { + var featureExists = feature != null && typeof feature != 'undefined'; + var featureType = feature.geometry.type.toLowerCase(); + + if (featureExists && featureType.includes("linestring")) { + return incidentalLineStyle; + } + + return incidentalShapeStyle; +} + +function formSelectionPane() { + dataPane.update(selectedFeatures); +} + +function layerClicked(event) { + var layer = event.layer; + var feature = layer.feature; + console.log('clicked feature: '+ feature.id); + + if (feature.id in incidentalFeatures) { + removeFeature(feature); + removeIncidentalFeatures(feature.id); + } + else { + // Add the selected feature to the list + selectedLayers[feature.id] = layer; + addFeature(feature.id, feature.id); + addIncidentalFeatures(feature.id); + } +} + +function addIncidentalFeatures(featureID) { + $.ajax({ + url: "map/connections?id=" + featureID, + type: 'GET', + error: function(xhr,status,error) { + console.error(error); + }, + success: function(result,status,xhr) { + incidentalFeatures[featureID] = []; + + for (feature of result['connected_locations']) { + var featureLayers = Object.values(mymap._layers).filter( + layer => "feature" in layer && layer.feature.id == feature + ); + + if (featureLayers.length == 0) { + continue; + } + + var featureLayer = featureLayers[0]; + var incidentalFeature = featureLayer.feature; + + // We want to color and select the feature if it wasn't manually clicked beforehand + if (Object.keys(incidentalFeatures).indexOf(feature) < 0) { + featureLayer.setStyle(getIncidentalStyle(incidentalFeature)); + } + + // Reset the selection panel + selectedLayers[feature] = featureLayer; + selectedFeatures[feature] = feature; + incidentalFeatures[featureID].push(feature); + } + + formSelectionPane(); + } + }); +} + +function removeIncidentalFeatures(featureID) { + var featuresToRemove = incidentalFeatures[featureID].filter(feature => !(feature in incidentalFeatures)); + + for (feature of featuresToRemove) { + var layer = selectedLayers[feature]; + layer.setStyle(getShapeStyle(layer.feature)) + delete selectedFeatures[feature]; + delete selectedFeatures[feature]; + } + + delete incidentalFeatures[featureID]; + formSelectionPane(); +} + +function addFeature(descriptor, id) { + selectedFeatures[id] = descriptor; + selectedLayers[id].setStyle(getSelectedShapeStyle(selectedLayers[id].feature)); + formSelectionPane(); +} + +function removeFeature(feature) { + selectedLayers[feature.id].setStyle(getShapeStyle(feature)); + + delete selectedLayers[feature.id]; + delete selectedFeatures[feature.id]; + + formSelectionPane(); +} + +function submitFeatures(event) { + var featuresToConfigure = []; + + $("#value-list span.selected-value-label").each(function(index) { + featuresToConfigure.push($(this).attr("value")); + }); + + if (featuresToConfigure.length == 0) { + event.preventDefault(); + alert("Select a location to configure before continuing."); + return; + } + + var ids = featuresToConfigure.join("|"); + + document.forms['location-selection-form']['feature-ids'].value = ids; + document.forms['location-selection-form'].submit(); +} + +function getFabricNames() { + var names = []; + var fabricNames = getServerData("fabric/names"); + + if (fabricNames != null) { + names = fabricNames['fabric_names']; + } + + return names; +} + +function titleCase(str) { + return str.replaceAll("_", " ").toLowerCase().split(' ').map(function(word) { + return word.replace(word[0], word[0].toUpperCase()); + }).join(' '); +} + +function loadFabricNames() { + $.ajax( + { + url: "fabric/names", + type: 'GET', + error: function(xhr,status,error) { + console.error(error); + }, + success: function(result,status,xhr) { + if (result != null) { + result['fabric_names'].forEach(function(name) { + $("#fabric-selector").append(""); + }); + $("#fabric-selector option")[0].setAttribute("selected", ""); + loadFabric(); + } + } + } + ); +} + +function loadFabricTypes() { + $.ajax( + { + url: "fabric/types", + type: 'GET', + error: function(xhr,status,error) { + console.error(error); + }, + success: function(result,status,xhr) { + if (result != null) { + result['fabric_types'].forEach(function(name) { + $("#fabric-type-selector").append(""); + }); + $("#fabric-type-selector option")[0].setAttribute("selected", ""); + loadFabric(); + } + } + } + ); +} + +function loadFabric(event) { + var name = $("#fabric-selector").val(); + var type = $("#fabric-type-selector").val(); + + $("input[name=fabric]").val(name); + + var crosswalk = getServerData("crosswalk/"+name); + + var addTooltip = function(feature, layer) { + if ("id" in feature) { + layer.bindTooltip(feature["id"]); + } + else if ("properties" in feature && "Name" in feature["properties"]) { + layer.bindTooltip(feature['properties']['Name']); + } + } + + var buildProperties = function(feature, layer) { + layer.bindPopup(propertiesToHTML(feature, crosswalk), {"maxWidth": 2000}) + addTooltip(feature, layer); + }; + + var addDocument = function(document) { + var layer = L.geoJSON( + document.features, + { + onEachFeature: buildProperties, + style: getShapeStyle, + pointToLayer: layerCreation + } + ).addTo(mymap); + activeLayer = layer; + + //click popup + activeLayer.on('click', layerClicked); + + mymap.fitBounds(activeLayer.getBounds()); + } + var name_type = name+"|"+type; + if (name && type && (name_type != activeLayerName || activeLayer == null)) { + activeLayerName = name_type; + + if (activeLayer) { + Object.values(selectedLayers).forEach(layer => removeFeature(layer.feature.id)); + activeLayer.remove(); + } + + if (name_type in loadedLayers) { + addDocument(loadedLayers[name_type]); + } + else { + var url = "fabric/" + name; + $.ajax( + { + url: url, + type: "GET", + data: {"fabric_type":type}, + error: function(xhr, status, error) { + console.error(error); + }, + success: function(result, status, xhr) { + if (result) { + loadedLayers[name_type] = result; + addDocument(result); + } + } + } + ) + } + } +} From 478fa5a56f20bac520d729975cb61684c8037a51 Mon Sep 17 00:00:00 2001 From: Robert Bartel Date: Fri, 9 Aug 2024 10:49:48 -0400 Subject: [PATCH 12/18] Fix/update GUI hydrofabric API views. Cleaning up existing view implementations, making default example match example data in repo, adding support to only query for feature ids, and adding support for geopackage-type hydrofabrics. --- python/gui/MaaS/cbv/MapView.py | 80 +++++++++++++++++++++++++++------- 1 file changed, 65 insertions(+), 15 deletions(-) diff --git a/python/gui/MaaS/cbv/MapView.py b/python/gui/MaaS/cbv/MapView.py index 40e62b847..ee187159b 100644 --- a/python/gui/MaaS/cbv/MapView.py +++ b/python/gui/MaaS/cbv/MapView.py @@ -17,12 +17,14 @@ from pathlib import Path from .. import datapane from .. import configuration +from dmod.modeldata.hydrofabric import GeoPackageHydrofabric import logging logger = logging.getLogger("gui_log") _resolution_regex = re.compile("(.+) \((.+)\)") + def _build_fabric_path(fabric, type): """ build a qualified path from the hydrofabric name and type @@ -34,52 +36,100 @@ def _build_fabric_path(fabric, type): resolution = resolution_match.group(2) else: name = fabric - resolution='' + resolution = '' + + hyfab_data_dir = Path(PROJECT_ROOT, 'static', 'ngen', 'hydrofabric', name, resolution) + + geojson_file = hyfab_data_dir.joinpath(f"{type}_data.geojson") + if geojson_file.exists(): + return geojson_file + + if hyfab_data_dir.joinpath("hydrofabric.gpkg").exists(): + geopackage_file = hyfab_data_dir.joinpath("hydrofabric.gpkg") + elif hyfab_data_dir.joinpath(f"{name}.gpkg").exists(): + geopackage_file = hyfab_data_dir.joinpath(f"{name}.gpkg") + else: + logger.error(f"Can't build fabric path: can't find hydrofabric data file in directory {hyfab_data_dir!s}") + return None + + return geopackage_file - path = Path(PROJECT_ROOT, 'static', 'ngen', 'hydrofabric', name, resolution, type+'_data.geojson') - return path class Fabrics(APIView): def get(self, request: HttpRequest, fabric: str = None) -> typing.Optional[JsonResponse]: if fabric is None: - fabric = 'example' + fabric = 'example_fabric_name' type = request.GET.get('fabric_type', 'catchment') if not type: - type="catchment" + type = "catchment" + + id_only = request.GET.get("id_only", "false") + if isinstance(id_only, str): + id_only = id_only.strip().lower() == "true" + else: + id_only = bool(id_only) path = _build_fabric_path(fabric, type) if path is None: return None + elif path.name == f"{type}_data.geojson": + with open(path) as fp: + data = json.load(fp) + if id_only: + return JsonResponse(sorted([feature["id"] for feature in data["features"]]), safe=False) + else: + return JsonResponse(data) + elif path.name[-5:] == ".gpkg": + hf = GeoPackageHydrofabric.from_file(geopackage_file=path) + if id_only: + if type == "catchment": + return JsonResponse(sorted(hf.get_all_catchment_ids()), safe=False) + elif type == "nexus": + return JsonResponse(sorted(hf.get_all_nexus_ids()), safe=False) + else: + logger.error(f"Unsupported fabric type '{type}' for id_only geopackage in Fabrics API view") + return None + else: + if type == "catchment": + df = hf._dataframes[hf._DIVIDES_LAYER_NAME] + elif type == "nexus": + df = hf._dataframes[hf._NEXUS_LAYER_NAME] + else: + logger.error(f"Unsupported fabric type '{type}' for geopackage in Fabrics API view") + return None + return JsonResponse(json.loads(df.to_json())) + else: + logger.error(f"Can't make API request for hydrofabric '{fabric!s}'") + return None - with open(path) as fp: - data = json.load(fp) - return JsonResponse(data) class FabricNames(APIView): - _fabric_dir = Path(PROJECT_ROOT, 'static', 'ngen', 'hydrofabric') + _fabrics_root_dir = Path(PROJECT_ROOT, 'static', 'ngen', 'hydrofabric') def get(self, request: HttpRequest) -> JsonResponse: names = [] - for f_name in self._fabric_dir.iterdir(): - if f_name.is_dir(): + for fabric_subdir in self._fabrics_root_dir.iterdir(): + if fabric_subdir.is_dir(): #Check for sub dirs/resolution sub = False - for r_name in f_name.iterdir(): + for r_name in fabric_subdir.iterdir(): if r_name.is_dir(): - names.append( '{} ({})'.format(f_name.name, r_name.name)) + names.append(f'{fabric_subdir.name} ({r_name.name})') sub = True if not sub: - names.append( '{}'.format(f_name.name) ) + names.append(f'{fabric_subdir.name}') return JsonResponse(data={ "fabric_names": names }) + class FabricTypes(APIView): def get(self, rquest: HttpRequest) -> JsonResponse: return JsonResponse( data={ "fabric_types": ['catchment', 'flowpath', 'nexus'] - }) + }) + class ConnectedFeatures(APIView): def get(self, request: HttpRequest) -> JsonResponse: From 3cb65a35c777f19e3848be3e3c17a538d67e14b4 Mon Sep 17 00:00:00 2001 From: Robert Bartel Date: Fri, 9 Aug 2024 10:54:13 -0400 Subject: [PATCH 13/18] Most of a working, simple domain select GUI view. Adding most of what is required for basic working domain selection view that can select hydrofabrics and the desired catchments to execute from the hydrofabric, though not yet with the form fields to proceed to the next job config step. --- python/gui/MaaS/cbv/MapView.py | 53 +++++++++ python/gui/MaaS/static/common/js/domain.js | 120 ++++++++++++++++++++- python/gui/MaaS/templates/maas/domain.html | 45 ++++++-- python/gui/MaaS/urls.py | 3 +- 4 files changed, 207 insertions(+), 14 deletions(-) diff --git a/python/gui/MaaS/cbv/MapView.py b/python/gui/MaaS/cbv/MapView.py index ee187159b..e2db3a101 100644 --- a/python/gui/MaaS/cbv/MapView.py +++ b/python/gui/MaaS/cbv/MapView.py @@ -192,3 +192,56 @@ def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: # Return the rendered page return render(request, 'maas/map.html', payload) + + +class DomainView(View): + + """ + A view used to render the map + """ + def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: + """ + The handler for 'get' requests. This will render the 'map.html' template + + :param HttpRequest request: The request asking to render this page + :param args: An ordered list of arguments + :param kwargs: A dictionary of named arguments + :return: A rendered page + """ + # If a list of error messages wasn't passed, create one + if 'errors' not in kwargs: + errors = list() + else: + # Otherwise continue to use the passed in list + errors = kwargs['errors'] # type: list + + # If a list of warning messages wasn't passed create one + if 'warnings' not in kwargs: + warnings = list() + else: + # Otherwise continue to use the passed in list + warnings = kwargs['warnings'] # type: list + + # If a list of basic messages wasn't passed, create one + if 'info' not in kwargs: + info = list() + else: + # Otherwise continue to us the passed in list + info = kwargs['info'] # type: list + + framework_selector = datapane.Input("framework", "select", "The framework within which to run models") + for editor in configuration.get_editors(): + framework_selector.add_choice(editor['name'], editor['description'], editor['friendly_name']) + + pprint(framework_selector.__dict__) + + # Package everything up to be rendered for the client + payload = { + 'errors': errors, + 'info': info, + 'warnings': warnings, + 'pane_inputs': [framework_selector] + } + + # Return the rendered page + return render(request, 'maas/domain.html', payload) \ No newline at end of file diff --git a/python/gui/MaaS/static/common/js/domain.js b/python/gui/MaaS/static/common/js/domain.js index 1bb3e0508..c1251121c 100644 --- a/python/gui/MaaS/static/common/js/domain.js +++ b/python/gui/MaaS/static/common/js/domain.js @@ -47,6 +47,7 @@ var incidentalFeatures = {}; var selectedLayers = {}; +/* startup_scripts.push( function(){ mymap = L.map('mapid').setView(centerLine, zoom); @@ -59,6 +60,7 @@ startup_scripts.push( }).addTo(mymap); } ); +*/ function getLayers() { var layers = []; @@ -116,6 +118,7 @@ function layerCreation(geoJsonPoint, latlng) { return L.circleMarker(latlng); } +/* function plotMapLayers(featureDocuments, map) { var layers = []; @@ -148,6 +151,7 @@ function plotMapLayers(featureDocuments, map) { featureDocuments.forEach(featureDocument => addFeature(featureDocument)); return layers; } +*/ function propertiesToHTML(geojson, xwalk) { var properties = geojson.properties; @@ -413,12 +417,123 @@ function loadFabricTypes() { ); } +function addDomainChoicesOption(values) { + var select = document.getElementById('domainChoices'); + for (var i = 0; i < values.length; i++) { + var option = document.createElement('option'); + option.value = values[i]; + option.innerHTML = values[i]; + select.appendChild(option); + } +} + +function insertOptionInOrder(option, newParentSelect) { + for (var i = 0; i < newParentSelect.options.length; i++) { + if (option.value < newParentSelect.options[i].value) { + newParentSelect.options.add(option, newParentSelect.options[i]); + return; + } + } + newParentSelect.appendChild(option); +} + +function controlSelectAdd() { + var choices = document.getElementById('domainChoices'), + selected = document.getElementById('domainSelections'); + for (var i = choices.options.length - 1; i >=0; i--) { + var opt = choices.options[i]; + if (opt.selected) { + opt.selected = false; + choices.removeChild(opt); + insertOptionInOrder(opt, selected); + } + } +} + +function controlSelectRemove() { + var choices = document.getElementById('domainChoices'), + selected = document.getElementById('domainSelections'), + i, + opt; + for (i = selected.options.length - 1; i >=0; i--) { + opt = selected.options[i]; + if (opt.selected) { + opt.selected = false; + selected.removeChild(opt); + insertOptionInOrder(opt, choices); + } + } +} + +function controlSelectAll() { + var choices = document.getElementById('domainChoices'), + selected = document.getElementById('domainSelections'), + i, + opt; + for (i = choices.options.length - 1; i >= 0 ; i--) { + opt = choices.options[i]; + if (opt.selected) { + opt.selected = false; + } + choices.removeChild(opt); + insertOptionInOrder(opt, selected); + } +} + +function controlSelectClear() { + var choices = document.getElementById('domainChoices'), + selected = document.getElementById('domainSelections'), + i, + opt; + for (i = selected.options.length - 1; i >= 0 ; i--) { + opt = selected.options[i]; + if (opt.selected) { + opt.selected = false; + } + selected.removeChild(opt); + insertOptionInOrder(opt, choices); + } +} + function loadFabric(event) { - var name = $("#fabric-selector").val(); - var type = $("#fabric-type-selector").val(); + var name = $("#fabric-selector").val(), + type = $("#fabric-type-selector").val(), + catLists = [document.getElementById('domainChoices'), + document.getElementById('domainSelections')], + select, + l, + i; $("input[name=fabric]").val(name); + // Clear any existing