diff --git a/core/modules/startup/rootwidget.js b/core/modules/startup/rootwidget.js index 41f3fe03f..73f1fda2d 100644 --- a/core/modules/startup/rootwidget.js +++ b/core/modules/startup/rootwidget.js @@ -20,6 +20,11 @@ exports.before = ["story"]; exports.synchronous = true; exports.startup = function() { + // Install the HTTP client event handler + $tw.httpClient = new $tw.utils.HttpClient(); + $tw.rootWidget.addEventListener("tm-http-request",function(event) { + $tw.httpClient.handleHttpRequest(event); + }); // Install the modal message mechanism $tw.modal = new $tw.utils.Modal($tw.wiki); $tw.rootWidget.addEventListener("tm-modal",function(event) { diff --git a/core/modules/utils/dom/http.js b/core/modules/utils/dom/http.js index 6e07b1040..3eca4985b 100644 --- a/core/modules/utils/dom/http.js +++ b/core/modules/utils/dom/http.js @@ -3,7 +3,7 @@ title: $:/core/modules/utils/dom/http.js type: application/javascript module-type: utils -Browser HTTP support +HTTP support \*/ (function(){ @@ -13,11 +13,99 @@ Browser HTTP support "use strict"; /* -A quick and dirty HTTP function; to be refactored later. Options are: +Manage tm-http-request events. Options are: +wiki - the wiki object to use +*/ +function HttpClient(options) { + options = options || {}; + this.wiki = options.wiki || $tw.wiki; +} + +HttpClient.prototype.handleHttpRequest = function(event) { + console.log("Making an HTTP request",event) + var self = this, + paramObject = event.paramObject || {}, + url = paramObject.url, + completionActions = paramObject.oncompletion || "", + progressActions = paramObject.onprogress || "", + bindStatus = paramObject["bind-status"], + bindProgress = paramObject["bind-progress"], + method = paramObject.method || "GET", + HEADER_PARAMETER_PREFIX = "header-", + PASSWORD_HEADER_PARAMETER_PREFIX = "password-header-", + CONTEXT_VARIABLE_PARAMETER_PREFIX = "var-", + requestHeaders = {}, + contextVariables = {}, + setBinding = function(title,text) { + if(title) { + self.wiki.addTiddler(new $tw.Tiddler({title: title, text: text})); + } + }; + if(url) { + setBinding(bindStatus,"pending"); + setBinding(bindProgress,"0"); + $tw.utils.each(paramObject,function(value,name) { + // Look for header- parameters + if(name.substr(0,HEADER_PARAMETER_PREFIX.length) === HEADER_PARAMETER_PREFIX) { + requestHeaders[name.substr(HEADER_PARAMETER_PREFIX.length)] = value; + } + // Look for password-header- parameters + if(name.substr(0,PASSWORD_HEADER_PARAMETER_PREFIX.length) === PASSWORD_HEADER_PARAMETER_PREFIX) { + requestHeaders[name.substr(PASSWORD_HEADER_PARAMETER_PREFIX.length)] = $tw.utils.getPassword(value) || ""; + } + // Look for var- parameters + if(name.substr(0,CONTEXT_VARIABLE_PARAMETER_PREFIX.length) === CONTEXT_VARIABLE_PARAMETER_PREFIX) { + contextVariables[name.substr(CONTEXT_VARIABLE_PARAMETER_PREFIX.length)] = value; + } + }); + $tw.utils.httpRequest({ + url: url, + type: method, + headers: requestHeaders, + data: paramObject.body, + callback: function(err,data,xhr) { + var headers = {}; + $tw.utils.each(xhr.getAllResponseHeaders().split("\r\n"),function(line) { + var parts = line.split(":"); + if(parts.length === 2) { + headers[parts[0].toLowerCase()] = parts[1].trim(); + } + }); + setBinding(bindStatus,xhr.status === 200 ? "complete" : "error"); + setBinding(bindProgress,"100"); + var results = { + status: xhr.status.toString(), + statusText: xhr.statusText, + error: (err || "").toString(), + data: (data || "").toString(), + headers: JSON.stringify(headers) + }; + $tw.rootWidget.invokeActionString(completionActions,undefined,undefined,$tw.utils.extend({},contextVariables,results)); + // console.log("Back!",err,data,xhr); + }, + progress: function(lengthComputable,loaded,total) { + if(lengthComputable) { + setBinding(bindProgress,"" + Math.floor((loaded/total) * 100)) + } + $tw.rootWidget.invokeActionString(progressActions,undefined,undefined,{ + lengthComputable: lengthComputable ? "yes" : "no", + loaded: loaded, + total: total + }); + } + }); + } +}; + +exports.HttpClient = HttpClient; + +/* +Make an HTTP request. Options are: url: URL to retrieve headers: hashmap of headers to send type: GET, PUT, POST etc callback: function invoked with (err,data,xhr) + progress: optional function invoked with (lengthComputable,loaded,total) returnProp: string name of the property to return as first argument of callback */ exports.httpRequest = function(options) { @@ -83,8 +171,16 @@ exports.httpRequest = function(options) { options.callback($tw.language.getString("Error/XMLHttpRequest") + ": " + this.status,null,this); } }; + // Handle progress + if(options.progress) { + request.onprogress = function(event) { + console.log("Progress event",event) + options.progress(event.lengthComputable,event.loaded,event.total); + }; + } // Make the request request.open(type,url,true); + // Headers if(headers) { $tw.utils.each(headers,function(header,headerTitle,object) { request.setRequestHeader(headerTitle,header); @@ -96,6 +192,7 @@ exports.httpRequest = function(options) { if(!hasHeader("X-Requested-With") && !isSimpleRequest(type,headers)) { request.setRequestHeader("X-Requested-With","TiddlyWiki"); } + // Send data try { request.send(data); } catch(e) { diff --git a/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-request.tid b/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-request.tid new file mode 100644 index 000000000..5befb6f1b --- /dev/null +++ b/editions/tw5.com/tiddlers/messages/WidgetMessage_ tm-http-request.tid @@ -0,0 +1,43 @@ +caption: tm-http-request +created: 20220908161746341 +modified: 20220908161746341 +tags: Messages +title: WidgetMessage: tm-http-request +type: text/vnd.tiddlywiki + +The ''tm-http-request'' message is used to make an HTTP request to a server. + +It uses the following properties on the `event` object: + +|!Name |!Description | +|param |Not used | +|paramObject |Hashmap of parameters (see below) | + +The following parameters are used: + +|!Name |!Description | +|method |HTTP method (eg "GET", "POST") | +|body |String data to be sent with the request | +|header-* |Headers with string values| +|password-header-* |Headers with values taken from the password store | +|var-* |Variables to be passed to the completion and progress handlers (without the "var-" prefix) | +|bind-status |Title of tiddler to which the status of the request ("pending", "complete", "error") should be bound | +|bind-progress |Title of tiddler to which the progress of the request (0 to 100) should be bound | +|oncompletion |Action strings to be executed when the request completes | +|onprogress |Action strings to be executed when progress is reported | + +The following variables are passed to the completion handler: + +|!Name |!Description | +|status |HTTP result status code (see [[MDN|https://developer.mozilla.org/en-US/docs/Web/HTTP/Status]]) | +|statusText |HTTP result status text | +|error |Error string | +|data |Returned data | +|headers |Response headers as a JSON object | + +The following variables are passed to the progress handler: + +|!Name |!Description | +|lengthComputable |Whether the progress loaded and total figures are valid - "yes" or "no" | +|loaded |Number of bytes loaded so far | +|total |Total number bytes to be loaded | diff --git a/plugins/tiddlywiki/geospatial/demo/features/us-states.tid b/plugins/tiddlywiki/geospatial/demo/features/us-states.tid index 5f3547b39..bb07278de 100644 --- a/plugins/tiddlywiki/geospatial/demo/features/us-states.tid +++ b/plugins/tiddlywiki/geospatial/demo/features/us-states.tid @@ -1,5 +1,6 @@ title: $:/plugins/geospatial/demo/features/us-states type: application/json +tags: $:/tags/GeoLayer {"type":"FeatureCollection","features":[ {"type":"Feature","id":"01","properties":{"name":"Alabama","density":94.65},"geometry":{"type":"Polygon","coordinates":[[[-87.359296,35.00118],[-85.606675,34.984749],[-85.431413,34.124869],[-85.184951,32.859696],[-85.069935,32.580372],[-84.960397,32.421541],[-85.004212,32.322956],[-84.889196,32.262709],[-85.058981,32.13674],[-85.053504,32.01077],[-85.141136,31.840985],[-85.042551,31.539753],[-85.113751,31.27686],[-85.004212,31.003013],[-85.497137,30.997536],[-87.600282,30.997536],[-87.633143,30.86609],[-87.408589,30.674397],[-87.446927,30.510088],[-87.37025,30.427934],[-87.518128,30.280057],[-87.655051,30.247195],[-87.90699,30.411504],[-87.934375,30.657966],[-88.011052,30.685351],[-88.10416,30.499135],[-88.137022,30.318396],[-88.394438,30.367688],[-88.471115,31.895754],[-88.241084,33.796253],[-88.098683,34.891641],[-88.202745,34.995703],[-87.359296,35.00118]]]}}, diff --git a/plugins/tiddlywiki/geospatial/demo/maps.tid b/plugins/tiddlywiki/geospatial/demo/maps.tid new file mode 100644 index 000000000..671fa6ecf --- /dev/null +++ b/plugins/tiddlywiki/geospatial/demo/maps.tid @@ -0,0 +1,11 @@ +title: $:/plugins/tiddlywiki/geospatial/demo/maps +caption: Maps +tags: $:/tags/GeospatialDemo + +! Map with Layers and Markers + +<$geomap + markers="[all[tiddlers+shadows]tag[$:/tags/GeoMarker]]" + layers="[all[tiddlers+shadows]tag[$:/tags/GeoLayer]]" +/> + diff --git a/plugins/tiddlywiki/geospatial/demo/traveltime.tid b/plugins/tiddlywiki/geospatial/demo/traveltime.tid new file mode 100644 index 000000000..6b20858b7 --- /dev/null +++ b/plugins/tiddlywiki/geospatial/demo/traveltime.tid @@ -0,0 +1,92 @@ +title: $:/plugins/tiddlywiki/geospatial/demo/traveltime +caption: Traveltime +tags: $:/tags/GeospatialDemo + +\define completion-actions() +<$action-log/> +<$action-setfield $tiddler="$:/temp/_StatusCode" text=<>/> +<$action-setfield $tiddler="$:/temp/_StatusText" text=<>/> +<$action-setfield $tiddler="$:/temp/_Error" text=<>/> +<$action-setfield $tiddler="$:/temp/_Result" text=<>/> +<$action-setfield $tiddler="$:/temp/_Headers" text=<>/> +<$list filter="[match[200]]" variable="ignore"> +<$action-setfield $tiddler="$:/temp/_IsochroneLayer" text={{{ [] }}} tags="$:/tags/GeoLayer"/> + +\end + +\define progress-actions() +<$action-log message="In progress-actions"/> +<$action-log/> +\end + +\define payload-source() +\rules only transcludeinline transcludeblock filteredtranscludeinline filteredtranscludeblock +{ + "departure_searches": [ + { + "id": "My first isochrone", + "coords": { + "lat": 51.507609, + "lng": -0.128315 + }, + "departure_time": "2021-09-27T08:00:00Z", + "travel_time": 3600, + "transportation": { + "type": "driving" + } + } + ] +} +\end + +\define get-traveltime-actions() +<$wikify name="payload" text=<>> + <$action-log $message="Making payload"/> + <$action-log/> + <$action-sendmessage + $message="tm-http-request" + url="https://api.traveltimeapp.com/v4/time-map" + method="POST" + header-accept="application/geo+json" + header-Content-Type="application/json" + password-header-X-Api-Key="traveltime-secret-key" + password-header-X-Application-Id="traveltime-application-id" + body=<> + var-context="Context string" + bind-status="$:/temp/plugins/tiddlywiki/geospatial/demo/traveltime/status" + bind-progress="$:/temp/plugins/tiddlywiki/geospatial/demo/traveltime/progress" + oncompletion=<> + onprogress=<> + /> + +\end + + + +<$button actions=<>> +Call ~TravelTime + + +Status: +
<$text text={{$:/temp/plugins/tiddlywiki/geospatial/demo/traveltime/status}}/>
+ +Progress: +
<$text text={{$:/temp/plugins/tiddlywiki/geospatial/demo/traveltime/progress}}/>
+ +Response + +~StatusCode: +
<$text text={{$:/temp/_StatusCode}}/>
+ +~StatusText: +
<$text text={{$:/temp/_StatusText}}/>
+ +Error: +
<$text text={{$:/temp/_Error}}/>
+ +Headers: +
<$text text={{$:/temp/_Headers}}/>
+ +Result: +
<$text text={{$:/temp/_Result}}/>
+ diff --git a/plugins/tiddlywiki/geospatial/plugin.info b/plugins/tiddlywiki/geospatial/plugin.info index 33238c6c9..564cf38ed 100644 --- a/plugins/tiddlywiki/geospatial/plugin.info +++ b/plugins/tiddlywiki/geospatial/plugin.info @@ -2,5 +2,5 @@ "title": "$:/plugins/tiddlywiki/geospatial", "name": "Geospatial Utilities", "description": "Geospatial utilities", - "list": "readme license" + "list": "readme settings license" } diff --git a/plugins/tiddlywiki/geospatial/readme.tid b/plugins/tiddlywiki/geospatial/readme.tid index e6b223124..94963ea3d 100644 --- a/plugins/tiddlywiki/geospatial/readme.tid +++ b/plugins/tiddlywiki/geospatial/readme.tid @@ -1,11 +1,5 @@ title: $:/plugins/tiddlywiki/geospatial/readme -! Examples +! Demos -!! Simple Map - -<$geomap/> - -!! Map with Markers - -<$geomap markers="[all[tiddlers+shadows]tag[$:/tags/GeoMarker]]"/> +<> diff --git a/plugins/tiddlywiki/geospatial/settings.tid b/plugins/tiddlywiki/geospatial/settings.tid new file mode 100644 index 000000000..dfcf4bfeb --- /dev/null +++ b/plugins/tiddlywiki/geospatial/settings.tid @@ -0,0 +1,15 @@ +title: $:/plugins/tiddlywiki/geospatial/settings +tags: $:/tags/ControlPanel +caption: Geospatial Plugin + +
+ +! Geospatial Plugin Settings + +Register for a free account at https://traveltime.com/ and copy and paste the secrets below: + +~TravelTime Application ID: <$password name="traveltime-application-id"/> + +~TravelTime Secret Key: <$password name="traveltime-secret-key"/> + +
diff --git a/plugins/tiddlywiki/geospatial/widgets/geomap.js b/plugins/tiddlywiki/geospatial/widgets/geomap.js index 6c489d023..41e85e260 100644 --- a/plugins/tiddlywiki/geospatial/widgets/geomap.js +++ b/plugins/tiddlywiki/geospatial/widgets/geomap.js @@ -56,6 +56,8 @@ GeomapWidget.prototype.renderMap = function(domNode) { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map); + // Disable Leaflet attribution + map.attributionControl.setPrefix(""); // Create default icon const iconProportions = 365/560, iconHeight = 50; @@ -67,12 +69,18 @@ GeomapWidget.prototype.renderMap = function(domNode) { }); // Add scale L.control.scale().addTo(map); - // Add US states overlay - const layer = L.geoJSON($tw.utils.parseJSONSafe(self.wiki.getTiddlerText("$:/plugins/geospatial/demo/features/us-states"),[])).addTo(map); - // Create markers + // Add overlays + if(this.geomapLayerFilter) { + $tw.utils.each(this.wiki.filterTiddlers(this.geomapLayerFilter),function(title) { + var tiddler = self.wiki.getTiddler(title); + if(tiddler) { + var layer = L.geoJSON($tw.utils.parseJSONSafe(tiddler.fields.text || "[]",[])).addTo(map); + } + }); + } + // Add markers if(this.geomapMarkerFilter) { - var titles = this.wiki.filterTiddlers(this.geomapMarkerFilter); - $tw.utils.each(titles,function(title) { + $tw.utils.each(this.wiki.filterTiddlers(this.geomapMarkerFilter),function(title) { var tiddler = self.wiki.getTiddler(title); if(tiddler) { var lat = $tw.utils.parseNumber(tiddler.fields.lat || "0"), @@ -89,6 +97,7 @@ GeomapWidget.prototype.renderMap = function(domNode) { Compute the internal state of the widget */ GeomapWidget.prototype.execute = function() { + this.geomapLayerFilter = this.getAttribute("layers"); this.geomapMarkerFilter = this.getAttribute("markers"); };