From 1beeec0d339d28d8d05a14c336a24d91799a09a9 Mon Sep 17 00:00:00 2001 From: zoran995 Date: Tue, 19 Jan 2021 13:24:52 +0100 Subject: [PATCH 01/62] convert search providers to utilise the new model architecture - add SearchProviderMixin to connect searchProviders with model system - write architecture decission record - port existing search providers to proposed model --- .../0007-configurable-search-providers.md | 35 ++ lib/ModelMixins/SearchProviderMixin.ts | 92 +++++ .../WebFeatureServiceSearchProviderMixin.ts | 222 ++++++++++++ lib/Models/BingMapsSearchProvider.ts | 201 ----------- lib/Models/SearchProvider.ts | 27 -- .../AustralianGazetteerSearchProvider.ts | 44 +-- .../SearchProvider/BingMapsSearchProvider.ts | 189 +++++++++++ .../CatalogSearchProvider.ts | 34 +- .../SearchProvider/SearchProviderFactory.ts | 4 + .../SearchProviderResults.ts | 6 +- .../{ => SearchProvider}/SearchResult.ts | 8 +- .../SearchProvider/StubSearchProvider.ts | 31 ++ .../createStubSearchProvider.ts | 27 ++ .../SearchProvider/registerSearchProviders.ts | 15 + .../upsertSearchProviderFromJson.ts | 57 ++++ lib/Models/Terria.ts | 129 ++++++- lib/Models/WebFeatureServiceSearchProvider.ts | 221 ------------ lib/ReactViewModels/SearchState.ts | 24 +- lib/ReactViews/Mobile/MobileHeader.jsx | 44 +-- lib/ReactViews/SidePanel/SidePanel.jsx | 8 +- .../BingMapsSearchProviderTraits.ts | 35 ++ .../CatalogSearchProviderTraits.ts | 14 + .../LocationSearchProviderTraits.ts | 37 ++ .../SearchProvider/SearchProviderTraits.ts | 32 ++ .../WebFeatureServiceSearchProviderTraits.ts | 18 + .../CatalogItemNameSearchProviderViewModel.js | 317 ------------------ .../GazetteerSearchProviderViewModel.js | 227 ------------- lib/ViewModels/GnafSearchProviderViewModel.js | 119 ------- .../NominatimSearchProviderViewModel.js | 199 ----------- .../AustralianGazetteerSearchProviderSpec.ts | 4 +- ...alogItemNameSearchProviderViewModelSpec.js | 280 ---------------- 31 files changed, 1023 insertions(+), 1677 deletions(-) create mode 100644 architecture/0007-configurable-search-providers.md create mode 100644 lib/ModelMixins/SearchProviderMixin.ts create mode 100644 lib/ModelMixins/WebFeatureServiceSearchProviderMixin.ts delete mode 100644 lib/Models/BingMapsSearchProvider.ts delete mode 100644 lib/Models/SearchProvider.ts rename lib/Models/{ => SearchProvider}/AustralianGazetteerSearchProvider.ts (84%) create mode 100644 lib/Models/SearchProvider/BingMapsSearchProvider.ts rename lib/Models/{ => SearchProvider}/CatalogSearchProvider.ts (89%) create mode 100644 lib/Models/SearchProvider/SearchProviderFactory.ts rename lib/Models/{ => SearchProvider}/SearchProviderResults.ts (74%) rename lib/Models/{ => SearchProvider}/SearchResult.ts (87%) create mode 100644 lib/Models/SearchProvider/StubSearchProvider.ts create mode 100644 lib/Models/SearchProvider/createStubSearchProvider.ts create mode 100644 lib/Models/SearchProvider/registerSearchProviders.ts create mode 100644 lib/Models/SearchProvider/upsertSearchProviderFromJson.ts delete mode 100644 lib/Models/WebFeatureServiceSearchProvider.ts create mode 100644 lib/Traits/SearchProvider/BingMapsSearchProviderTraits.ts create mode 100644 lib/Traits/SearchProvider/CatalogSearchProviderTraits.ts create mode 100644 lib/Traits/SearchProvider/LocationSearchProviderTraits.ts create mode 100644 lib/Traits/SearchProvider/SearchProviderTraits.ts create mode 100644 lib/Traits/SearchProvider/WebFeatureServiceSearchProviderTraits.ts delete mode 100644 lib/ViewModels/CatalogItemNameSearchProviderViewModel.js delete mode 100644 lib/ViewModels/GazetteerSearchProviderViewModel.js delete mode 100644 lib/ViewModels/GnafSearchProviderViewModel.js delete mode 100644 lib/ViewModels/NominatimSearchProviderViewModel.js delete mode 100644 test/ViewModels/CatalogItemNameSearchProviderViewModelSpec.js diff --git a/architecture/0007-configurable-search-providers.md b/architecture/0007-configurable-search-providers.md new file mode 100644 index 00000000000..87fe3660d02 --- /dev/null +++ b/architecture/0007-configurable-search-providers.md @@ -0,0 +1,35 @@ +# 7. Configuration of search providers + +Date: 2021-01-19 + +## Status + +Proposed + +## Context + +Ticket. +https://github.com/TerriaJS/terriajs/issues/5141. + +### Intro + +The existing approach to the definition of SearchProviders requires the development team's involvement and rebuild of the application, which can be undesired behavior in highly dynamic environments. +It's much better to enable the administrators to maintain the search providers. + +## Proposal +- SearchProviders could greatly use the benefits of the new model system used for Catalog. +- Create a simple base Mixin (`SearchProviderMixin`) to attach SearchProviders to the Model system and enable easier creation of new search providers. +- Make SearchProviders configurable from `config.json`. +- Provide sensible defaults for everything. +- Typescript everything. +- Make everything translateable (administrator can specify i18next keys for all names) + +## Benefits + +- Much easier to implement new search providers. +- Much easier to update existing search providers, `urls` and `keys`. +- Offer administrators an option to decide wheter they want to load group members using `CatalogSearchProvider`. + +## Consequences + +This is quite a large change and should be thoroughly tested to avoid the possible bugs in the search providers migration. \ No newline at end of file diff --git a/lib/ModelMixins/SearchProviderMixin.ts b/lib/ModelMixins/SearchProviderMixin.ts new file mode 100644 index 00000000000..b53a05bd2e3 --- /dev/null +++ b/lib/ModelMixins/SearchProviderMixin.ts @@ -0,0 +1,92 @@ +import { action, observable } from "mobx"; +import { fromPromise } from "mobx-utils"; +import Ellipsoid from "terriajs-cesium/Source/Core/Ellipsoid"; +import CesiumMath from "terriajs-cesium/Source/Core/Math"; +import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; +import Constructor from "../Core/Constructor"; +import Model from "../Models/Model"; +import SearchProviderResults from "../Models/SearchProvider/SearchProviderResults"; +import Terria from "../Models/Terria"; +import SearchProviderTraits from "../Traits/SearchProvider/SearchProviderTraits"; + +type SearchProvider = Model; + +function SearchProviderMixin>(Base: T) { + abstract class SearchProviderMixin extends Base { + abstract get type(): string; + @observable name = "Unknown"; + @observable isOpen = this.openByDefault; + + @action + toggleOpen() { + this.isOpen = !this.isOpen; + } + + @action + search(searchText: string): SearchProviderResults { + const result = new SearchProviderResults(Base); + result.resultsCompletePromise = fromPromise( + this.doSearch(searchText, result) + ); + return result; + } + + protected abstract doSearch( + searchText: string, + results: SearchProviderResults + ): Promise; + + shouldRunSearch(searchText: string) { + if ( + searchText === undefined || + /^\s*$/.test(searchText) || + (this.minCharacters && searchText.length < this.minCharacters) || + (this.minCharacters === undefined && + searchText.length < + this.terria.configParameters.searchBar.minCharacters) + ) { + return false; + } + return true; + } + + get hasSearchProviderMixin() { + return true; + } + } + return SearchProviderMixin; +} + +namespace SearchProviderMixin { + export interface SearchProviderMixin + extends InstanceType> {} + export function isMixedInto(model: any): model is SearchProviderMixin { + return model && model.hasSearchProviderMixin; + } +} + +export default SearchProviderMixin; + +interface MapCenter { + longitude: number; + latitude: number; +} + +export function getMapCenter(terria: Terria): MapCenter { + const view = terria.currentViewer.getCurrentCameraView(); + if (view.position !== undefined) { + const cameraPositionCartographic = Ellipsoid.WGS84.cartesianToCartographic( + view.position + ); + return { + longitude: CesiumMath.toDegrees(cameraPositionCartographic.longitude), + latitude: CesiumMath.toDegrees(cameraPositionCartographic.latitude) + }; + } else { + const center = Rectangle.center(view.rectangle); + return { + longitude: CesiumMath.toDegrees(center.longitude), + latitude: CesiumMath.toDegrees(center.latitude) + }; + } +} diff --git a/lib/ModelMixins/WebFeatureServiceSearchProviderMixin.ts b/lib/ModelMixins/WebFeatureServiceSearchProviderMixin.ts new file mode 100644 index 00000000000..70a0c26bf9e --- /dev/null +++ b/lib/ModelMixins/WebFeatureServiceSearchProviderMixin.ts @@ -0,0 +1,222 @@ +import i18next from "i18next"; +import { runInAction } from "mobx"; +import Resource from "terriajs-cesium/Source/Core/Resource"; +import URI from "urijs"; +import Constructor from "../Core/Constructor"; +import makeRealPromise from "../Core/makeRealPromise"; +import zoomRectangleFromPoint from "../Map/zoomRectangleFromPoint"; +import Model from "../Models/Model"; +import SearchProviderResults from "../Models/SearchProvider/SearchProviderResults"; +import SearchResult from "../Models/SearchProvider/SearchResult"; +import Terria from "../Models/Terria"; +import xml2json from "../ThirdParty/xml2json"; +import WebFeatureServiceSearchProviderTraits from "../Traits/SearchProvider/WebFeatureServiceSearchProviderTraits"; +import SearchProviderMixin from "./SearchProviderMixin"; + +function WebFeatureServiceSearchProviderMixin< + T extends Constructor> +>(Base: T) { + abstract class WebFeatureServiceSearchProviderMixin extends SearchProviderMixin( + Base + ) { + protected abstract featureToSearchResultFunction: ( + feature: any + ) => SearchResult; + protected abstract transformSearchText: + | ((searchText: string) => string) + | undefined; + protected abstract searchResultFilterFunction: + | ((feature: any) => boolean) + | undefined; + protected abstract searchResultScoreFunction: + | ((feature: any, searchText: string) => number) + | undefined; + + cancelRequest?: () => void; + + private _waitingForResults: boolean = false; + + getXml(url: string): Promise { + const resource = new Resource({ url }); + this._waitingForResults = true; + const xmlPromise = resource.fetchXML(); + this.cancelRequest = resource.request.cancelFunction; + return makeRealPromise(xmlPromise).finally(() => { + this._waitingForResults = false; + }); + } + + protected doSearch( + searchText: string, + results: SearchProviderResults + ): Promise { + results.results.length = 0; + results.message = undefined; + + if (this._waitingForResults) { + // There's been a new search! Cancel the previous one. + if (this.cancelRequest !== undefined) { + this.cancelRequest(); + this.cancelRequest = undefined; + } + this._waitingForResults = false; + } + + const originalSearchText = searchText; + + searchText = searchText.trim(); + if (this.transformSearchText !== undefined) { + searchText = this.transformSearchText(searchText); + } + if (searchText.length < 2) { + return Promise.resolve(); + } + + // Support for matchCase="false" is patchy, but we try anyway + const filter = ` + ${this.searchPropertyName} + *${searchText}*`; + + const _wfsServiceUrl = new URI(this.url); + _wfsServiceUrl.setSearch({ + service: "WFS", + request: "GetFeature", + typeName: this.searchPropertyTypeName, + version: "1.1.0", + srsName: "urn:ogc:def:crs:EPSG::4326", // srsName must be formatted like this for correct lat/long order >:( + filter: filter + }); + + return this.getXml(_wfsServiceUrl.toString()) + .then((xml: any) => { + let json: any = xml2json(xml); + let features: any[]; + if (json === undefined) { + results.message = i18next.t("viewModels.searchErrorOccurred"); + return; + } + + if (json.member !== undefined) { + features = json.member; + } else if (json.featureMember !== undefined) { + features = json.featureMember; + } else { + results.message = i18next.t("viewModels.searchNoPlaceNames"); + return; + } + + // if there's only one feature, make it an array + if (!Array.isArray(features)) { + features = [features]; + } + + const resultSet = new Set(); + + runInAction(() => { + if (this.searchResultFilterFunction !== undefined) { + features = features.filter(this.searchResultFilterFunction); + } + + if (features.length === 0) { + results.message = i18next.t("viewModels.searchNoPlaceNames"); + return; + } + + if (this.searchResultScoreFunction !== undefined) { + features = features.sort( + (featureA, featureB) => + this.searchResultScoreFunction!( + featureB, + originalSearchText + ) - + this.searchResultScoreFunction!(featureA, originalSearchText) + ); + } + + let searchResults = features + .map(this.featureToSearchResultFunction) + .map(result => { + result.clickAction = createZoomToFunction( + this, + result.location + ); + return result; + }); + + // If we don't have a scoring function, sort the search results now + // We can't do this earlier because we don't know what the schema of the unprocessed feature looks like + if (this.searchResultScoreFunction === undefined) { + // Put shorter results first + // They have a larger percentage of letters that the user actually typed in them + searchResults = searchResults.sort( + (featureA, featureB) => + featureA.name.length - featureB.name.length + ); + } + + // Remove results that have the same name and are close to each other + searchResults = searchResults.filter(result => { + const hash = `${result.name},${result.location?.latitude.toFixed( + 1 + )},${result.location?.longitude.toFixed(1)}`; + if (resultSet.has(hash)) { + return false; + } + resultSet.add(hash); + return true; + }); + + // append new results to all results + results.results.push(...searchResults); + }); + }) + .catch(e => { + if (results.isCanceled) { + // A new search has superseded this one, so ignore the result. + return; + } + results.message = i18next.t("viewModels.searchErrorOccurred"); + }); + } + get isWebFeatureServiceSearchProviderMixin() { + return true; + } + } + + return WebFeatureServiceSearchProviderMixin; +} + +namespace WebFeatureServiceSearchProviderMixin { + export interface WebFeatureServiceSearchProviderMixin + extends InstanceType< + ReturnType + > {} + export function isMixedInto( + model: any + ): model is WebFeatureServiceSearchProviderMixin { + return model && model.isWebFeatureServiceSearchProviderMixin; + } +} +export default WebFeatureServiceSearchProviderMixin; + +function createZoomToFunction( + model: WebFeatureServiceSearchProviderMixin.WebFeatureServiceSearchProviderMixin, + location: any +) { + // Server does not return information of a bounding box, just a location. + // bboxSize is used to expand a point + var bboxSize = 0.2; + var rectangle = zoomRectangleFromPoint( + location.latitude, + location.longitude, + bboxSize + ); + + const flightDurationSeconds: number = + model.flightDurationSeconds || + model.terria.configParameters.searchBar.flightDurationSeconds; + + return function() { + model.terria.currentViewer.zoomTo(rectangle, flightDurationSeconds); + }; +} diff --git a/lib/Models/BingMapsSearchProvider.ts b/lib/Models/BingMapsSearchProvider.ts deleted file mode 100644 index 4acf5b53bd7..00000000000 --- a/lib/Models/BingMapsSearchProvider.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { observable, runInAction } from "mobx"; -import defaultValue from "terriajs-cesium/Source/Core/defaultValue"; -import defined from "terriajs-cesium/Source/Core/defined"; -import Ellipsoid from "terriajs-cesium/Source/Core/Ellipsoid"; -import CesiumMath from "terriajs-cesium/Source/Core/Math"; -import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; -import Resource from "terriajs-cesium/Source/Core/Resource"; -import loadJsonp from "../Core/loadJsonp"; -import SearchProvider from "./SearchProvider"; -import SearchResult from "./SearchResult"; -import Terria from "./Terria"; -import SearchProviderResults from "./SearchProviderResults"; -import i18next from "i18next"; - -interface BingMapsSearchProviderOptions { - terria: Terria; - url?: string; - key?: string; - flightDurationSeconds?: number; - primaryCountry?: string; - culture?: string; -} - -export default class BingMapsSearchProvider extends SearchProvider { - readonly terria: Terria; - @observable url: string; - @observable key: string | undefined; - @observable flightDurationSeconds: number; - @observable primaryCountry: string; - @observable culture: string; - - constructor(options: BingMapsSearchProviderOptions) { - super(); - - this.terria = options.terria; - this.name = i18next.t("viewModels.searchLocations"); - this.url = defaultValue(options.url, "https://dev.virtualearth.net/"); - if (this.url.length > 0 && this.url[this.url.length - 1] !== "/") { - this.url += "/"; - } - this.key = options.key; - this.flightDurationSeconds = defaultValue( - options.flightDurationSeconds, - 1.5 - ); - this.primaryCountry = defaultValue(options.primaryCountry, "Australia"); - this.culture = defaultValue(options.culture, "en-au"); - - if (!this.key) { - console.warn( - "The " + - this.name + - " geocoder will always return no results because a Bing Maps key has not been provided. Please get a Bing Maps key from bingmapsportal.com and add it to parameters.bingMapsKey in config.json." - ); - } - } - - protected doSearch( - searchText: string, - searchResults: SearchProviderResults - ): Promise { - searchResults.results.length = 0; - searchResults.message = undefined; - - if (searchText === undefined || /^\s*$/.test(searchText)) { - return Promise.resolve(); - } - - this.terria.analytics.logEvent("search", "bing", searchText); - - let longitudeDegrees; - let latitudeDegrees; - - const view = this.terria.currentViewer.getCurrentCameraView(); - if (view.position !== undefined) { - const cameraPositionCartographic = Ellipsoid.WGS84.cartesianToCartographic( - view.position - ); - longitudeDegrees = CesiumMath.toDegrees( - cameraPositionCartographic.longitude - ); - latitudeDegrees = CesiumMath.toDegrees( - cameraPositionCartographic.latitude - ); - } else { - const center = Rectangle.center(view.rectangle); - longitudeDegrees = CesiumMath.toDegrees(center.longitude); - latitudeDegrees = CesiumMath.toDegrees(center.latitude); - } - - const promise: Promise = loadJsonp( - new Resource({ - url: - this.url + - "REST/v1/Locations?culture=" + - this.culture + - "&userLocation=" + - latitudeDegrees + - "," + - longitudeDegrees, - queryParameters: { - query: searchText, - key: this.key - } - }), - "jsonp" - ); - - return promise - .then(result => { - if (searchResults.isCanceled) { - // A new search has superseded this one, so ignore the result. - return; - } - - if (result.resourceSets.length === 0) { - searchResults.message = i18next.t("viewModels.searchNoLocations"); - return; - } - - var resourceSet = result.resourceSets[0]; - if (resourceSet.resources.length === 0) { - searchResults.message = i18next.t("viewModels.searchNoLocations"); - return; - } - - const primaryCountryLocations: any[] = []; - const otherLocations: any[] = []; - - // Locations in the primary country go on top, locations elsewhere go undernearth and we add - // the country name to them. - for (let i = 0; i < resourceSet.resources.length; ++i) { - const resource = resourceSet.resources[i]; - - let name = resource.name; - if (!defined(name)) { - continue; - } - - let list = primaryCountryLocations; - let isImportant = true; - - const country = resource.address - ? resource.address.countryRegion - : undefined; - if (defined(this.primaryCountry) && country !== this.primaryCountry) { - // Add this location to the list of other locations. - list = otherLocations; - isImportant = false; - - // Add the country to the name, if it's not already there. - if ( - defined(country) && - name.lastIndexOf(country) !== name.length - country.length - ) { - name += ", " + country; - } - } - - list.push( - new SearchResult({ - name: name, - isImportant: isImportant, - clickAction: createZoomToFunction(this, resource), - location: { - latitude: resource.point.coordinates[0], - longitude: resource.point.coordinates[1] - } - }) - ); - } - - runInAction(() => { - searchResults.results.push(...primaryCountryLocations); - searchResults.results.push(...otherLocations); - }); - - if (searchResults.results.length === 0) { - searchResults.message = i18next.t("viewModels.searchNoLocations"); - } - }) - .catch(() => { - if (searchResults.isCanceled) { - // A new search has superseded this one, so ignore the result. - return; - } - - searchResults.message = i18next.t("viewModels.searchErrorOccurred"); - }); - } -} - -function createZoomToFunction(model: BingMapsSearchProvider, resource: any) { - const [south, west, north, east] = resource.bbox; - const rectangle = Rectangle.fromDegrees(west, south, east, north); - - return function() { - const terria = model.terria; - terria.currentViewer.zoomTo(rectangle, model.flightDurationSeconds); - }; -} diff --git a/lib/Models/SearchProvider.ts b/lib/Models/SearchProvider.ts deleted file mode 100644 index de29a899952..00000000000 --- a/lib/Models/SearchProvider.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { action, observable } from "mobx"; -import { fromPromise } from "mobx-utils"; -import SearchProviderResults from "./SearchProviderResults"; - -export default abstract class SearchProvider { - @observable name = "Unknown"; - @observable isOpen = true; - - @action - toggleOpen() { - this.isOpen = !this.isOpen; - } - - @action - search(searchText: string): SearchProviderResults { - const result = new SearchProviderResults(this); - result.resultsCompletePromise = fromPromise( - this.doSearch(searchText, result) - ); - return result; - } - - protected abstract doSearch( - searchText: string, - results: SearchProviderResults - ): Promise; -} diff --git a/lib/Models/AustralianGazetteerSearchProvider.ts b/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts similarity index 84% rename from lib/Models/AustralianGazetteerSearchProvider.ts rename to lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts index b0f352ffdea..9d0dbf50954 100644 --- a/lib/Models/AustralianGazetteerSearchProvider.ts +++ b/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts @@ -1,7 +1,7 @@ -import i18next from "i18next"; +import WebFeatureServiceSearchProviderTraits from "../../Traits/SearchProvider/WebFeatureServiceSearchProviderTraits"; +import CreateModel from "../CreateModel"; +import WebFeatureServiceSearchProviderMixin from "./../../ModelMixins/WebFeatureServiceSearchProviderMixin"; import SearchResult from "./SearchResult"; -import Terria from "./Terria"; -import WebFeatureServiceSearchProvider from "./WebFeatureServiceSearchProvider"; const featureCodesToNamesMap = new Map([ ["AF", "Aviation"], @@ -220,23 +220,25 @@ const searchResultScoreFunction = function( return score; }; -const WFS_SERVICE_URL = - "http://services.ga.gov.au/gis/services/Australian_Gazetteer/MapServer/WFSServer"; -const SEARCH_PROPERTY_NAME = "Australian_Gazetteer:NameU"; -const SEARCH_PROPERTY_TYPE_NAME = "Australian_Gazetteer:Gazetteer_of_Australia"; - -export default function createAustralianGazetteerSearchProvider( - terria: Terria +export default class AustralianGazetteerSearchProvider extends WebFeatureServiceSearchProviderMixin( + CreateModel(WebFeatureServiceSearchProviderTraits) ) { - return new WebFeatureServiceSearchProvider({ - terria, - featureToSearchResultFunction, - wfsServiceUrl: WFS_SERVICE_URL, - searchPropertyName: SEARCH_PROPERTY_NAME, - searchPropertyTypeName: SEARCH_PROPERTY_TYPE_NAME, - transformSearchText: searchText => searchText.toUpperCase(), - name: i18next.t("viewModels.searchPlaceNames"), - searchResultFilterFunction: searchResultFilterFunction, - searchResultScoreFunction: searchResultScoreFunction - }); + static readonly type = "australian-gazetteer-search-provider"; + + get type(){ + return AustralianGazetteerSearchProvider.type; + } + + featureToSearchResultFunction: ( + feature: any + ) => SearchResult = featureToSearchResultFunction; + transformSearchText: + | ((searchText: string) => string) + | undefined = searchText => searchText.toUpperCase(); + searchResultFilterFunction: + | ((feature: any) => boolean) + | undefined = searchResultFilterFunction; + searchResultScoreFunction: + | ((feature: any, searchText: string) => number) + | undefined = searchResultScoreFunction; } diff --git a/lib/Models/SearchProvider/BingMapsSearchProvider.ts b/lib/Models/SearchProvider/BingMapsSearchProvider.ts new file mode 100644 index 00000000000..312c873c5e8 --- /dev/null +++ b/lib/Models/SearchProvider/BingMapsSearchProvider.ts @@ -0,0 +1,189 @@ +import i18next from "i18next"; +import { runInAction } from "mobx"; +import defined from "terriajs-cesium/Source/Core/defined"; +import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; +import Resource from "terriajs-cesium/Source/Core/Resource"; +import loadJsonp from "../../Core/loadJsonp"; +import SearchProviderMixin, { + getMapCenter +} from "../../ModelMixins/SearchProviderMixin"; +import BingMapsSearchProviderTraits from "../../Traits/SearchProvider/BingMapsSearchProviderTraits"; +import CreateModel from "../CreateModel"; +import SearchProviderResults from "./SearchProviderResults"; +import SearchResult from "./SearchResult"; +import CommonStrata from "./../CommonStrata"; +import Terria from "../Terria"; + +export default class BingMapsSearchProvider extends SearchProviderMixin( + CreateModel(BingMapsSearchProviderTraits) +) { + static readonly type = "bing-maps-search-provider"; + + get type() { + return BingMapsSearchProvider.type; + } + + constructor(uniqueId: string | undefined, terria: Terria) { + super(uniqueId, terria); + if ( + (!this.key || this.key === "") && + this.terria.configParameters.bingMapsKey + ) { + this.setTrait( + CommonStrata.defaults, + "key", + this.terria.configParameters.bingMapsKey + ); + } + this.showWarning(); + } + + showWarning() { + if (!this.key || this.key === "") { + console.warn( + "The " + + this.name + + " geocoder will always return no results because a Bing Maps key has not been provided. Please get a Bing Maps key from bingmapsportal.com and add it to parameters.bingMapsKey in config.json." + ); + } + } + + protected doSearch( + searchText: string, + searchResults: SearchProviderResults + ): Promise { + console.log(this.key); + searchResults.results.length = 0; + searchResults.message = undefined; + + if (this.shouldRunSearch(searchText)) { + return Promise.resolve(); + } + + this.terria.analytics.logEvent("search", "bing", searchText); + + const searchQuery = new Resource({ + url: this.url + "REST/v1/Locations", + queryParameters: { + culture: this.culture, + query: searchText, + key: this.key + } + }); + + if (this.mapCenter) { + const mapCenter = getMapCenter(this.terria); + + searchQuery.appendQueryParameters({ + userLocation: `${mapCenter.latitude}, ${mapCenter.longitude}` + }); + } + + const promise: Promise = loadJsonp(searchQuery, "jsonp"); + + return promise + .then(result => { + if (searchResults.isCanceled) { + // A new search has superseded this one, so ignore the result. + return; + } + + if (result.resourceSets.length === 0) { + searchResults.message = i18next.t("viewModels.searchNoLocations"); + return; + } + + var resourceSet = result.resourceSets[0]; + if (resourceSet.resources.length === 0) { + searchResults.message = i18next.t("viewModels.searchNoLocations"); + return; + } + + const locations = this.sortByPriority(resourceSet.resources); + + runInAction(() => { + searchResults.results.push(...locations.primaryCountry); + searchResults.results.push(...locations.other); + }); + + if (searchResults.results.length === 0) { + searchResults.message = i18next.t("viewModels.searchNoLocations"); + } + }) + .catch(() => { + if (searchResults.isCanceled) { + // A new search has superseded this one, so ignore the result. + return; + } + + searchResults.message = i18next.t("viewModels.searchErrorOccurred"); + }); + } + + protected sortByPriority(resources: any[]) { + const primaryCountryLocations: any[] = []; + const otherLocations: any[] = []; + + // Locations in the primary country go on top, locations elsewhere go undernearth and we add + // the country name to them. + for (let i = 0; i < resources.length; ++i) { + const resource = resources[i]; + + let name = resource.name; + if (!defined(name)) { + continue; + } + + let list = primaryCountryLocations; + let isImportant = true; + + const country = resource.address + ? resource.address.countryRegion + : undefined; + if (defined(this.primaryCountry) && country !== this.primaryCountry) { + // Add this location to the list of other locations. + list = otherLocations; + isImportant = false; + + // Add the country to the name, if it's not already there. + if ( + defined(country) && + name.lastIndexOf(country) !== name.length - country.length + ) { + name += ", " + country; + } + } + + list.push( + new SearchResult({ + name: name, + isImportant: isImportant, + clickAction: createZoomToFunction(this, resource), + location: { + latitude: resource.point.coordinates[0], + longitude: resource.point.coordinates[1] + } + }) + ); + } + + return { + primaryCountry: primaryCountryLocations, + other: otherLocations + }; + } +} + +function createZoomToFunction(model: BingMapsSearchProvider, resource: any) { + const [south, west, north, east] = resource.bbox; + const rectangle = Rectangle.fromDegrees(west, south, east, north); + + return function() { + const flightDurationSeconds: number = + model.flightDurationSeconds || + model.terria.configParameters.searchBar.flightDurationSeconds; + + const terria = model.terria; + terria.currentViewer.zoomTo(rectangle, flightDurationSeconds); + }; +} diff --git a/lib/Models/CatalogSearchProvider.ts b/lib/Models/SearchProvider/CatalogSearchProvider.ts similarity index 89% rename from lib/Models/CatalogSearchProvider.ts rename to lib/Models/SearchProvider/CatalogSearchProvider.ts index 07dc5658a50..1a1e85c9ba2 100644 --- a/lib/Models/CatalogSearchProvider.ts +++ b/lib/Models/SearchProvider/CatalogSearchProvider.ts @@ -1,14 +1,12 @@ import { autorun, observable, runInAction } from "mobx"; -import SearchProvider from "./SearchProvider"; -import SearchResult from "./SearchResult"; -import Terria from "./Terria"; +import GroupMixin from "../../ModelMixins/GroupMixin"; +import ReferenceMixin from "../../ModelMixins/ReferenceMixin"; +import CatalogSearchProviderTraits from "../../Traits/SearchProvider/CatalogSearchProviderTraits"; +import CreateModel from "../CreateModel"; +import Terria from "../Terria"; +import SearchProviderMixin from "./../../ModelMixins/SearchProviderMixin"; import SearchProviderResults from "./SearchProviderResults"; -import GroupMixin from "../ModelMixins/GroupMixin"; -import ReferenceMixin from "../ModelMixins/ReferenceMixin"; - -interface CatalogSearchProviderOptions { - terria: Terria; -} +import SearchResult from "./SearchResult"; type UniqueIdString = string; type ResultMap = Map; @@ -95,18 +93,18 @@ export function loadAndSearchCatalogRecursively( }); } -export default class CatalogSearchProvider extends SearchProvider { - readonly terria: Terria; - @observable isSearching: boolean = false; - @observable debounceDurationOnceLoaded: number = 300; - - constructor(options: CatalogSearchProviderOptions) { - super(); +export default class CatalogSearchProvider extends SearchProviderMixin( + CreateModel(CatalogSearchProviderTraits) +) { + static readonly type = "catalog-search-provider"; - this.terria = options.terria; - this.name = "Catalog Items"; + get type() { + return CatalogSearchProvider.type; } + @observable isSearching: boolean = false; + @observable debounceDurationOnceLoaded: number = 300; + protected doSearch( searchText: string, searchResults: SearchProviderResults diff --git a/lib/Models/SearchProvider/SearchProviderFactory.ts b/lib/Models/SearchProvider/SearchProviderFactory.ts new file mode 100644 index 00000000000..cc9058c0c6a --- /dev/null +++ b/lib/Models/SearchProvider/SearchProviderFactory.ts @@ -0,0 +1,4 @@ +import ModelFactory from "../ModelFactory"; + +const SearchProviderFactory = new ModelFactory(); +export default SearchProviderFactory; diff --git a/lib/Models/SearchProviderResults.ts b/lib/Models/SearchProvider/SearchProviderResults.ts similarity index 74% rename from lib/Models/SearchProviderResults.ts rename to lib/Models/SearchProvider/SearchProviderResults.ts index 92ddfd1ddca..751e7f7cd60 100644 --- a/lib/Models/SearchProviderResults.ts +++ b/lib/Models/SearchProvider/SearchProviderResults.ts @@ -1,7 +1,7 @@ import { observable } from "mobx"; import SearchResult from "./SearchResult"; import { IPromiseBasedObservable, fromPromise } from "mobx-utils"; -import SearchProvider from "./SearchProvider"; +import SearchProviderMixin from "./../../ModelMixins/SearchProviderMixin"; export default class SearchProviderResults { @observable results: SearchResult[] = []; @@ -11,7 +11,9 @@ export default class SearchProviderResults { Promise.resolve() ); - constructor(readonly searchProvider: SearchProvider) {} + constructor( + readonly searchProvider: SearchProviderMixin.SearchProviderMixin + ) {} get isSearching() { return this.resultsCompletePromise.state === "pending"; diff --git a/lib/Models/SearchResult.ts b/lib/Models/SearchProvider/SearchResult.ts similarity index 87% rename from lib/Models/SearchResult.ts rename to lib/Models/SearchProvider/SearchResult.ts index 6b7a2c906ce..39dbbb7e251 100644 --- a/lib/Models/SearchResult.ts +++ b/lib/Models/SearchProvider/SearchResult.ts @@ -1,9 +1,9 @@ -import { BaseModel } from "./Model"; -import { observable, action } from "mobx"; +import { action, observable } from "mobx"; import defaultValue from "terriajs-cesium/Source/Core/defaultValue"; import defined from "terriajs-cesium/Source/Core/defined"; -import raiseErrorOnRejectedPromise from "./raiseErrorOnRejectedPromise"; -import GroupMixin from "../ModelMixins/GroupMixin"; +import GroupMixin from "../../ModelMixins/GroupMixin"; +import { BaseModel } from "../Model"; +import raiseErrorOnRejectedPromise from "../raiseErrorOnRejectedPromise"; export interface SearchResultOptions { name?: string; diff --git a/lib/Models/SearchProvider/StubSearchProvider.ts b/lib/Models/SearchProvider/StubSearchProvider.ts new file mode 100644 index 00000000000..50b19adb3db --- /dev/null +++ b/lib/Models/SearchProvider/StubSearchProvider.ts @@ -0,0 +1,31 @@ +import LocationSearchProviderTraits from "./../../Traits/SearchProvider/LocationSearchProviderTraits"; +import primitiveTrait from "./../../Traits/primitiveTrait"; +import SearchProviderMixin from "../../ModelMixins/SearchProviderMixin"; +import CreateModel from "../CreateModel"; +import SearchProviderResults from "./SearchProviderResults"; + +export class StubSearchProviderTraits extends LocationSearchProviderTraits { + @primitiveTrait({ + type: "boolean", + name: "Is experiencing issues", + description: + "Whether the search provider is experiencing issues which may cause search results to be unavailable" + }) + isExperiencingIssues: boolean = true; +} + +export default class StubSearchProvider extends SearchProviderMixin( + CreateModel(StubSearchProviderTraits) +) { + static readonly type = "stub-search-provider"; + get type(): string { + return StubSearchProvider.type; + } + + protected doSearch( + searchText: string, + results: SearchProviderResults + ): Promise { + return Promise.resolve(); + } +} diff --git a/lib/Models/SearchProvider/createStubSearchProvider.ts b/lib/Models/SearchProvider/createStubSearchProvider.ts new file mode 100644 index 00000000000..76434e11ba8 --- /dev/null +++ b/lib/Models/SearchProvider/createStubSearchProvider.ts @@ -0,0 +1,27 @@ +import Terria from "./../Terria"; +import StubSearchProvider from "./StubSearchProvider"; +import CommonStrata from "./../CommonStrata"; +import { BaseModel } from "../Model"; + +const getUniqueStubSearchProviderName = (terria: Terria) => { + const stubName = "[StubSearchProvider]"; + let uniqueId = stubName; + let idIncrement = 1; + while (terria.getModelById(BaseModel, uniqueId) !== undefined) { + uniqueId = stubName + " (" + idIncrement + ")"; + idIncrement++; + } + return uniqueId; +}; + +export default function createStubSearchProvider( + terria: Terria, + uniqueId?: string +): StubSearchProvider { + const idToUse = uniqueId || getUniqueStubSearchProviderName(terria); + const stub = new StubSearchProvider(idToUse, terria); + + stub.setTrait(CommonStrata.underride, "name", stub.uniqueId); + terria.addSearchProvider(stub); + return stub; +} diff --git a/lib/Models/SearchProvider/registerSearchProviders.ts b/lib/Models/SearchProvider/registerSearchProviders.ts new file mode 100644 index 00000000000..919a6da697c --- /dev/null +++ b/lib/Models/SearchProvider/registerSearchProviders.ts @@ -0,0 +1,15 @@ +import BingMapsSearchProvider from "./BingMapsSearchProvider"; +import AustralianGazetteerSearchProvider from "./AustralianGazetteerSearchProvider"; +import SearchProviderFactory from "./SearchProviderFactory"; + +export default function registerSearchProviders() { + SearchProviderFactory.register( + BingMapsSearchProvider.type, + BingMapsSearchProvider + ); + + SearchProviderFactory.register( + AustralianGazetteerSearchProvider.type, + AustralianGazetteerSearchProvider + ); +} diff --git a/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts b/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts new file mode 100644 index 00000000000..c52dca54b74 --- /dev/null +++ b/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts @@ -0,0 +1,57 @@ +import Terria from "./../Terria"; +import ModelFactory from "./../ModelFactory"; +import { BaseModel } from "../Model"; +import i18next from "i18next"; +import TerriaError from "../../Core/TerriaError"; +import CommonStrata from "./../CommonStrata"; +import updateModelFromJson from "../updateModelFromJson"; +import createStubSearchProvider from "./createStubSearchProvider"; + +export default function upsertSearchProviderFromJson( + factory: ModelFactory, + terria: Terria, + stratumName: string, + json: any +) { + let uniqueId = json.id; + if (uniqueId === undefined) { + const id = json.localId || json.name; + if (id === undefined) { + throw new TerriaError({ + title: i18next.t("models.catalog.idForMatchingErrorTitle"), + message: i18next.t("models.catalog.idForMatchingErrorMessage") + }); + } + uniqueId = id; + } + + let model = terria.getModelById(BaseModel, uniqueId); + + if (model === undefined) { + model = factory.create(json.type, uniqueId, terria); + if (model === undefined) { + console.log( + new TerriaError({ + title: i18next.t("models.catalog.unsupportedTypeTitle"), + message: i18next.t("models.catalog.unsupportedTypeMessage", { + type: json.type + }) + }) + ); + model = createStubSearchProvider(terria, uniqueId); + const stub = model; + stub.setTrait(CommonStrata.underride, "isExperiencingIssues", true); + stub.setTrait(CommonStrata.override, "name", `${uniqueId} (Stub)`); + } + + model?.terria.addSearchProvider(model); + } + + try { + updateModelFromJson(model, stratumName, json); + } catch (error) { + console.log(`Error updating search provider from JSON`); + console.log(error); + model?.setTrait(CommonStrata.underride, "isExperiencingIssues", true); + } +} diff --git a/lib/Models/Terria.ts b/lib/Models/Terria.ts index a14b7795f26..899e0ced372 100644 --- a/lib/Models/Terria.ts +++ b/lib/Models/Terria.ts @@ -1,6 +1,14 @@ import { convertCatalog, convertShare } from "catalog-converter"; import i18next from "i18next"; -import { action, computed, observable, runInAction, toJS, when } from "mobx"; +import { + action, + computed, + isObservableArray, + observable, + runInAction, + toJS, + when +} from "mobx"; import { createTransformer } from "mobx-utils"; import Clock from "terriajs-cesium/Source/Core/Clock"; import defaultValue from "terriajs-cesium/Source/Core/defaultValue"; @@ -72,6 +80,8 @@ import Mappable, { isDataSource } from "./Mappable"; import { BaseModel } from "./Model"; import NoViewer from "./NoViewer"; import openGroup from "./openGroup"; +import SearchProviderFactory from "./SearchProvider/SearchProviderFactory"; +import upsertSearchProviderFromJson from "./SearchProvider/upsertSearchProviderFromJson"; import ShareDataService from "./ShareDataService"; import SplitItemReference from "./SplitItemReference"; import TimelineStack from "./TimelineStack"; @@ -117,7 +127,50 @@ interface ConfigParameters { helpContent?: HelpContentItem[]; helpContentTerms?: Term[]; languageConfiguration?: LanguageConfiguration; + /** + * Index of which brandBarElements to show for mobile header + */ displayOneBrand?: number; + /** + * The search bar allows requesting information from various search services at once. + */ + searchBar: SearchBar; +} + +interface SearchBar { + /** + * Input text field placeholder shown when no input has been given yet. The string is translateable. + * @default "search.placeholder" + */ + placeholder: string; + /** + * Maximum amount of entries in the suggestion list. + * @default 5 + */ + recommendedListLength: number; + /** + * Defines whether search results are to be sorted alphanumerically. + * @default true + */ + sortByName: boolean; + /** + * The duration of the camera flight to an entered location, in seconds. + * @default 1.5 + */ + flightDurationSeconds: number; + /** + * True if the geocoder should query as the user types to autocomplete. + * @default true + */ + autocomplete: boolean; + /** + * Minimum number of characters to start search. + */ + minCharacters: number; + /** + * Array of search providers to be used. + */ + searchProviders: any[]; } interface StartOptions { @@ -160,6 +213,7 @@ interface HomeCameraInit { export default class Terria { private models = observable.map(); + private locationSearchProviders = observable.map(); /** Map from share key -> id */ readonly shareKeysMap = observable.map(); /** Map from id -> share keys */ @@ -253,7 +307,35 @@ export default class Terria { helpContent: [], helpContentTerms: defaultTerms, languageConfiguration: undefined, - displayOneBrand: 0 // index of which brandBarElements to show for mobile header + displayOneBrand: 0, + searchBar: { + placeholder: "search.placeholder", + recommendedListLength: 5, + sortByName: true, + flightDurationSeconds: 1.5, + autocomplete: true, + minCharacters: 3, + searchProviders: [ + { + id: "search-provider/bing-maps", + type: "bing-maps-search-provider", + name: "search.bingMaps", + url: "https://dev.virtualearth.net/", + flightDurationSeconds: 1.5 + }, + { + id: "search-provider/australian-gazetteer", + type: "australian-gazetteer-search-provider", + name: "viewModels.searchPlaceNames", + url: + "http://services.ga.gov.au/gis/services/Australian_Gazetteer/MapServer/WFSServer", + searchPropertyName: "Australian_Gazetteer:NameU", + searchPropertyTypeName: "Australian_Gazetteer:Gazetteer_of_Australia", + flightDurationSeconds: 1.5, + minCharacters: 3 + } + ] + } }; @observable @@ -401,6 +483,32 @@ export default class Terria { shareKeys?.forEach(shareKey => this.addShareKey(model.uniqueId!, shareKey)); } + /** + * Add new SearchProvider to the list of SearchProviders. + */ + @action + addSearchProvider(model: BaseModel) { + if (model.uniqueId === undefined) { + throw new DeveloperError( + "A SearchProvider without a `uniqueId` cannot be added." + ); + } + + if (this.locationSearchProviders.has(model.uniqueId)) { + throw new RuntimeError( + "A SearchProvider with the specified ID already exists." + ); + } + + this.locationSearchProviders.set(model.uniqueId, model); + } + + get locationSearchProvidersArray() { + return [...this.locationSearchProviders.entries()].map(function(entry) { + return entry[1]; + }); + } + /** * Remove references to a model from Terria. */ @@ -533,6 +641,23 @@ export default class Terria { return this.updateApplicationUrl(options.applicationUrl.href); } }) + .then(() => { + let searchProviders = this.configParameters.searchBar.searchProviders; + if (!isObservableArray(searchProviders)) + throw new TerriaError({ + sender: SearchProviderFactory, + title: "SearchProviders", + message: "" + }); + searchProviders.forEach(searchProvider => { + upsertSearchProviderFromJson( + SearchProviderFactory, + this, + CommonStrata.definition, + searchProvider + ); + }); + }) .then(() => { this.loadPersistedMapSettings(); }); diff --git a/lib/Models/WebFeatureServiceSearchProvider.ts b/lib/Models/WebFeatureServiceSearchProvider.ts deleted file mode 100644 index 4b63b25328f..00000000000 --- a/lib/Models/WebFeatureServiceSearchProvider.ts +++ /dev/null @@ -1,221 +0,0 @@ -import i18next from "i18next"; -import { runInAction } from "mobx"; -import URI from "urijs"; -import zoomRectangleFromPoint from "../Map/zoomRectangleFromPoint"; -import xml2json from "../ThirdParty/xml2json"; -import SearchProvider from "./SearchProvider"; -import SearchProviderResults from "./SearchProviderResults"; -import SearchResult from "./SearchResult"; -import Terria from "./Terria"; -import defaultValue from "terriajs-cesium/Source/Core/defaultValue"; -import Resource from "terriajs-cesium/Source/Core/Resource"; -import makeRealPromise from "../Core/makeRealPromise"; - -export interface WebFeatureServiceSearchProviderOptions { - /** Base url for the service */ - wfsServiceUrl: string; - /** Which property to look for the search text in */ - searchPropertyName: string; - /** Type of the properties to search */ - searchPropertyTypeName: string; - /** Convert a WFS feature to a search result */ - featureToSearchResultFunction: (feature: any) => SearchResult; - terria: Terria; - /** How long it takes to zoom in when a search result is clicked */ - flightDurationSeconds?: number; - /** Apply a function to search text before it gets passed to the service. Useful for changing case */ - transformSearchText?: (searchText: string) => string; - /** Return true if a feature should be included in search results */ - searchResultFilterFunction?: (feature: any) => boolean; - /** Return a score that gets used to sort results (in descending order) */ - searchResultScoreFunction?: (feature: any, searchText: string) => number; - /** name of the search provider */ - name: string; -} - -export default class WebFeatureServiceSearchProvider extends SearchProvider { - private _wfsServiceUrl: uri.URI; - private _searchPropertyName: string; - private _searchPropertyTypeName: string; - private _featureToSearchResultFunction: (feature: any) => SearchResult; - flightDurationSeconds: number; - readonly terria: Terria; - private _transformSearchText: ((searchText: string) => string) | undefined; - private _searchResultFilterFunction: ((feature: any) => boolean) | undefined; - private _searchResultScoreFunction: - | ((feature: any, searchText: string) => number) - | undefined; - cancelRequest?: () => void; - private _waitingForResults: boolean = false; - - constructor(options: WebFeatureServiceSearchProviderOptions) { - super(); - this._wfsServiceUrl = new URI(options.wfsServiceUrl); - this._searchPropertyName = options.searchPropertyName; - this._searchPropertyTypeName = options.searchPropertyTypeName; - this._featureToSearchResultFunction = options.featureToSearchResultFunction; - this.terria = options.terria; - this.flightDurationSeconds = defaultValue( - options.flightDurationSeconds, - 1.5 - ); - this._transformSearchText = options.transformSearchText; - this._searchResultFilterFunction = options.searchResultFilterFunction; - this._searchResultScoreFunction = options.searchResultScoreFunction; - this.name = options.name; - } - - getXml(): Promise { - const resource = new Resource({ url: this._wfsServiceUrl.toString() }); - this._waitingForResults = true; - const xmlPromise = resource.fetchXML(); - this.cancelRequest = resource.request.cancelFunction; - return makeRealPromise(xmlPromise).finally(() => { - this._waitingForResults = false; - }); - } - - protected doSearch( - searchText: string, - results: SearchProviderResults - ): Promise { - results.results.length = 0; - results.message = undefined; - - if (this._waitingForResults) { - // There's been a new search! Cancel the previous one. - if (this.cancelRequest !== undefined) { - this.cancelRequest(); - this.cancelRequest = undefined; - } - this._waitingForResults = false; - } - - const originalSearchText = searchText; - - searchText = searchText.trim(); - if (this._transformSearchText !== undefined) { - searchText = this._transformSearchText(searchText); - } - if (searchText.length < 2) { - return Promise.resolve(); - } - - // Support for matchCase="false" is patchy, but we try anyway - const filter = ` - ${this._searchPropertyName} - *${searchText}*`; - - this._wfsServiceUrl.setSearch({ - service: "WFS", - request: "GetFeature", - typeName: this._searchPropertyTypeName, - version: "1.1.0", - srsName: "urn:ogc:def:crs:EPSG::4326", // srsName must be formatted like this for correct lat/long order >:( - filter: filter - }); - - return this.getXml() - .then((xml: any) => { - let json: any = xml2json(xml); - let features: any[]; - if (json === undefined) { - results.message = i18next.t("viewModels.searchErrorOccurred"); - return; - } - - if (json.member !== undefined) { - features = json.member; - } else if (json.featureMember !== undefined) { - features = json.featureMember; - } else { - results.message = i18next.t("viewModels.searchNoPlaceNames"); - return; - } - - // if there's only one feature, make it an array - if (!Array.isArray(features)) { - features = [features]; - } - - const resultSet = new Set(); - - runInAction(() => { - if (this._searchResultFilterFunction !== undefined) { - features = features.filter(this._searchResultFilterFunction); - } - - if (features.length === 0) { - results.message = i18next.t("viewModels.searchNoPlaceNames"); - return; - } - - if (this._searchResultScoreFunction !== undefined) { - features = features.sort( - (featureA, featureB) => - this._searchResultScoreFunction!(featureB, originalSearchText) - - this._searchResultScoreFunction!(featureA, originalSearchText) - ); - } - - let searchResults = features - .map(this._featureToSearchResultFunction) - .map(result => { - result.clickAction = createZoomToFunction(this, result.location); - return result; - }); - - // If we don't have a scoring function, sort the search results now - // We can't do this earlier because we don't know what the schema of the unprocessed feature looks like - if (this._searchResultScoreFunction === undefined) { - // Put shorter results first - // They have a larger percentage of letters that the user actually typed in them - searchResults = searchResults.sort( - (featureA, featureB) => - featureA.name.length - featureB.name.length - ); - } - - // Remove results that have the same name and are close to each other - searchResults = searchResults.filter(result => { - const hash = `${result.name},${result.location?.latitude.toFixed( - 1 - )},${result.location?.longitude.toFixed(1)}`; - if (resultSet.has(hash)) { - return false; - } - resultSet.add(hash); - return true; - }); - - // append new results to all results - results.results.push(...searchResults); - }); - }) - .catch(e => { - if (results.isCanceled) { - // A new search has superseded this one, so ignore the result. - return; - } - results.message = i18next.t("viewModels.searchErrorOccurred"); - }); - } -} - -function createZoomToFunction( - model: WebFeatureServiceSearchProvider, - location: any -) { - // Server does not return information of a bounding box, just a location. - // bboxSize is used to expand a point - var bboxSize = 0.2; - var rectangle = zoomRectangleFromPoint( - location.latitude, - location.longitude, - bboxSize - ); - - return function() { - model.terria.currentViewer.zoomTo(rectangle, model.flightDurationSeconds); - }; -} diff --git a/lib/ReactViewModels/SearchState.ts b/lib/ReactViewModels/SearchState.ts index 639668806e7..bcd1fe31248 100644 --- a/lib/ReactViewModels/SearchState.ts +++ b/lib/ReactViewModels/SearchState.ts @@ -1,16 +1,15 @@ -// import CatalogItemNameSearchProviderViewModel from "../ViewModels/CatalogItemNameSearchProviderViewModel"; import { - observable, - reaction, - IReactionDisposer, + action, computed, - action + IReactionDisposer, + observable, + reaction } from "mobx"; -import Terria from "../Models/Terria"; -import SearchProviderResults from "../Models/SearchProviderResults"; -import SearchProvider from "../Models/SearchProvider"; import filterOutUndefined from "../Core/filterOutUndefined"; -import CatalogSearchProvider from "../Models/CatalogSearchProvider"; +import CatalogSearchProvider from "../Models/SearchProvider/CatalogSearchProvider"; +import SearchProviderResults from "../Models/SearchProvider/SearchProviderResults"; +import Terria from "../Models/Terria"; +import SearchProviderMixin from "./../ModelMixins/SearchProviderMixin"; interface SearchStateOptions { terria: Terria; @@ -20,9 +19,10 @@ interface SearchStateOptions { export default class SearchState { @observable - catalogSearchProvider: SearchProvider | undefined; + catalogSearchProvider: SearchProviderMixin.SearchProviderMixin | undefined; - @observable locationSearchProviders: SearchProvider[]; + @observable + locationSearchProviders: SearchProviderMixin.SearchProviderMixin[]; @observable catalogSearchText: string = ""; @observable isWaitingToStartCatalogSearch: boolean = false; @@ -48,7 +48,7 @@ export default class SearchState { constructor(options: SearchStateOptions) { this.catalogSearchProvider = options.catalogSearchProvider || - new CatalogSearchProvider({ terria: options.terria }); + new CatalogSearchProvider("catalog-search-provider", options.terria); this.locationSearchProviders = options.locationSearchProviders || []; this._catalogSearchDisposer = reaction( diff --git a/lib/ReactViews/Mobile/MobileHeader.jsx b/lib/ReactViews/Mobile/MobileHeader.jsx index 5e0c64f1b94..372ee34aa6e 100644 --- a/lib/ReactViews/Mobile/MobileHeader.jsx +++ b/lib/ReactViews/Mobile/MobileHeader.jsx @@ -1,20 +1,21 @@ -import React from "react"; +import classNames from "classnames"; import createReactClass from "create-react-class"; +import { runInAction } from "mobx"; +import { observer } from "mobx-react"; import PropTypes from "prop-types"; -import SearchBox from "../Search/SearchBox"; -import MobileModalWindow from "./MobileModalWindow"; -import Branding from "../SidePanel/Branding"; -import Styles from "./mobile-header.scss"; -import Icon, { StyledIcon } from "../Icon"; -import MobileMenu from "./MobileMenu"; -import classNames from "classnames"; -import { removeMarker } from "../../Models/LocationMarkerUtils"; +import React from "react"; import { withTranslation } from "react-i18next"; import { withTheme } from "styled-components"; -import { observer } from "mobx-react"; -import { runInAction } from "mobx"; +import { useTranslationIfExists } from "../../Language/languageHelpers"; +import { removeMarker } from "../../Models/LocationMarkerUtils"; import Box from "../../Styled/Box"; import { RawButton } from "../../Styled/Button"; +import Icon, { StyledIcon } from "../Icon"; +import SearchBox from "../Search/SearchBox"; +import Branding from "../SidePanel/Branding"; +import Styles from "./mobile-header.scss"; +import MobileMenu from "./MobileMenu"; +import MobileModalWindow from "./MobileModalWindow"; const MobileHeader = observer( createReactClass({ @@ -141,10 +142,10 @@ const MobileHeader = observer( render() { const searchState = this.props.viewState.searchState; const displayOne = this.props.terria.configParameters.displayOneBrand; - const { t } = this.props; + const { t, terria } = this.props; const nowViewingLength = - this.props.terria.workbench.items !== undefined - ? this.props.terria.workbench.items.length + terria.workbench.items !== undefined + ? terria.workbench.items.length : 0; return ( @@ -193,7 +194,7 @@ const MobileHeader = observer( /> @@ -240,7 +241,9 @@ const MobileHeader = observer( searchText={searchState.locationSearchText} onSearchTextChanged={this.changeLocationSearchText} onDoSearch={this.searchLocations} - placeholder={t("search.placeholder")} + placeholder={useTranslationIfExists( + terria.configParameters.searchBar.placeholder + )} alwaysShowClear={true} onClear={this.closeLocationSearch} autoFocus={true} @@ -266,13 +269,10 @@ const MobileHeader = observer( menuLeftItems={this.props.menuLeftItems} viewState={this.props.viewState} allBaseMaps={this.props.allBaseMaps} - terria={this.props.terria} - showFeedback={!!this.props.terria.configParameters.feedbackUrl} - /> - + ); } diff --git a/lib/ReactViews/SidePanel/SidePanel.jsx b/lib/ReactViews/SidePanel/SidePanel.jsx index 83b49332499..daa5c7e51aa 100644 --- a/lib/ReactViews/SidePanel/SidePanel.jsx +++ b/lib/ReactViews/SidePanel/SidePanel.jsx @@ -4,13 +4,13 @@ import PropTypes from "prop-types"; import React from "react"; import { withTranslation } from "react-i18next"; import styled, { withTheme } from "styled-components"; +import { useTranslationIfExists } from "../../Language/languageHelpers"; +import { useRefForTerria } from "../Hooks/useRefForTerria"; import Icon, { StyledIcon } from "../Icon"; import SearchBoxAndResults from "../Search/SearchBoxAndResults"; import Workbench from "../Workbench/Workbench"; import FullScreenButton from "./FullScreenButton"; -import { useRefForTerria } from "../Hooks/useRefForTerria"; - import Box from "../../Styled/Box"; import Spacing from "../../Styled/Spacing"; import Text from "../../Styled/Text"; @@ -171,7 +171,9 @@ const SidePanel = observer( diff --git a/lib/Traits/SearchProvider/BingMapsSearchProviderTraits.ts b/lib/Traits/SearchProvider/BingMapsSearchProviderTraits.ts new file mode 100644 index 00000000000..f39d28a1227 --- /dev/null +++ b/lib/Traits/SearchProvider/BingMapsSearchProviderTraits.ts @@ -0,0 +1,35 @@ +import mixTraits from "../mixTraits"; +import primitiveTrait from "../primitiveTrait"; +import LocationSearchProviderTraits, { + SearchProviderMapCenterTraits +} from "./LocationSearchProviderTraits"; + +export default class BingMapsSearchProviderTraits extends mixTraits( + LocationSearchProviderTraits, + SearchProviderMapCenterTraits +) { + url: string = "https://dev.virtualearth.net/"; + + @primitiveTrait({ + type: "string", + name: "Key", + description: "The Bing Maps key." + }) + key?: string; + + @primitiveTrait({ + type: "string", + name: "Primary country", + description: "Name of the country to prioritize the search results." + }) + primaryCountry: string = "Australia"; + + @primitiveTrait({ + type: "string", + name: "Culture", + description: `Use the culture parameter to specify a culture for your request. + The culture parameter provides the result in the language of the culture. + For a list of supported cultures, see [Supported Culture Codes](https://docs.microsoft.com/en-us/bingmaps/rest-services/common-parameters-and-types/supported-culture-codes)` + }) + culture: string = "en-au"; +} diff --git a/lib/Traits/SearchProvider/CatalogSearchProviderTraits.ts b/lib/Traits/SearchProvider/CatalogSearchProviderTraits.ts new file mode 100644 index 00000000000..7808d9d81b4 --- /dev/null +++ b/lib/Traits/SearchProvider/CatalogSearchProviderTraits.ts @@ -0,0 +1,14 @@ +import mixTraits from "../mixTraits"; +import SearchProviderTraits from "./SearchProviderTraits"; +import primitiveTrait from "../primitiveTrait"; + +export default class CatalogSearchProviderTraits extends mixTraits( + SearchProviderTraits +) { + @primitiveTrait({ + type: "string", + name: "Name", + description: "Name of the search provider." + }) + name: string = "Catalog items"; +} diff --git a/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts b/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts new file mode 100644 index 00000000000..b7285613ff5 --- /dev/null +++ b/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts @@ -0,0 +1,37 @@ +import ModelTraits from "../ModelTraits"; +import primitiveTrait from "../primitiveTrait"; +import SearchProviderTraits from "./SearchProviderTraits"; + +export default class LocationSearchProviderTraits extends SearchProviderTraits { + @primitiveTrait({ + type: "string", + name: "URL", + description: "The URL of search provider." + }) + url: string = ""; + + @primitiveTrait({ + type: "boolean", + name: "Open by default", + description: + "True if the geocoder should query as the user types to autocomplete." + }) + autocomplete?: boolean; + + @primitiveTrait({ + type: "number", + name: "URL", + description: "Time to move to the result location." + }) + flightDurationSeconds?: number; +} + +export class SearchProviderMapCenterTraits extends ModelTraits { + @primitiveTrait({ + type: "boolean", + name: "Map center", + description: + "Whether the current location of the map center is supplied with search request" + }) + mapCenter: boolean = true; +} diff --git a/lib/Traits/SearchProvider/SearchProviderTraits.ts b/lib/Traits/SearchProvider/SearchProviderTraits.ts new file mode 100644 index 00000000000..0c2ac76e391 --- /dev/null +++ b/lib/Traits/SearchProvider/SearchProviderTraits.ts @@ -0,0 +1,32 @@ +import ModelTraits from "../ModelTraits"; +import primitiveTrait from "../primitiveTrait"; + +export default class SearchProviderTraits extends ModelTraits { + @primitiveTrait({ + type: "string", + name: "Name", + description: "Name of the search provider." + }) + name: string = "unknown"; + + @primitiveTrait({ + type: "string", + name: "ID", + description: "Unique id of the search provider." + }) + id?: string; + + @primitiveTrait({ + type: "boolean", + name: "Open by default", + description: "Wheter are this search provider results open by default" + }) + openByDefault: boolean = true; + + @primitiveTrait({ + type: "number", + name: "Minimum characters", + description: "Minimum number of characters required for search to start" + }) + minCharacters?: number; +} diff --git a/lib/Traits/SearchProvider/WebFeatureServiceSearchProviderTraits.ts b/lib/Traits/SearchProvider/WebFeatureServiceSearchProviderTraits.ts new file mode 100644 index 00000000000..c95945ff74c --- /dev/null +++ b/lib/Traits/SearchProvider/WebFeatureServiceSearchProviderTraits.ts @@ -0,0 +1,18 @@ +import primitiveTrait from "../primitiveTrait"; +import LocationSearchProviderTraits from "./LocationSearchProviderTraits"; + +export default class WebFeatureServiceSearchProviderTraits extends LocationSearchProviderTraits { + @primitiveTrait({ + type: "string", + name: "Search property name", + description: "Which property to look for the search text in" + }) + searchPropertyName?: string; + + @primitiveTrait({ + type: "string", + name: "Search property type name", + description: "Type of the properties to search" + }) + searchPropertyTypeName?: string; +} diff --git a/lib/ViewModels/CatalogItemNameSearchProviderViewModel.js b/lib/ViewModels/CatalogItemNameSearchProviderViewModel.js deleted file mode 100644 index 91be2973e47..00000000000 --- a/lib/ViewModels/CatalogItemNameSearchProviderViewModel.js +++ /dev/null @@ -1,317 +0,0 @@ -"use strict"; - -/*global require*/ -var inherit = require("../Core/inherit"); -var runLater = require("../Core/runLater"); -var SearchProvider = require("../Models/SearchProvider").default; -var SearchResult = require("../Models/SearchResult").default; - -var defaultValue = require("terriajs-cesium/Source/Core/defaultValue").default; -var defined = require("terriajs-cesium/Source/Core/defined").default; -var when = require("terriajs-cesium/Source/ThirdParty/when").default; -const i18next = require("i18next").default; - -var CatalogItemNameSearchProviderViewModel = function(options) { - SearchProvider.call(this); - - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - this.terria = options.terria; - this._searchInProgress = undefined; - - this.name = i18next.t("viewModels.searchCatalogueItem"); - this.maxResults = defaultValue(options.maxResults, 10000); -}; - -inherit(SearchProvider, CatalogItemNameSearchProviderViewModel); - -CatalogItemNameSearchProviderViewModel.prototype.search = function(searchText) { - function parseSearchFilters() { - /* Filter search by type, eg 'is:wms fish' or '-is:wfs house'. Multiple 'is' clauses are or-ed. */ - var isRE = /(^|\s)(-?)is:([a-zA-Z0-9_-]+)\b/i; - while (searchText.match(isRE)) { - if (!searchText.match(isRE)[2]) { - searchFilters.typeIs.push(searchText.match(isRE)[3].toLowerCase()); - } else { - searchFilters.typeIsNot.push(searchText.match(isRE)[3].toLowerCase()); - } - searchText = searchText.replace(isRE, "").trim(); - } - /* Change number of search results: 'show:20' or 'show:all' */ - var showRE = /\bshow:(all|[0-9]+)\b/i; - while (searchText.match(showRE)) { - searchFilters.maxResults = searchText.match(showRE)[1].toLowerCase(); - if (searchFilters.maxResults === "all") { - searchFilters.maxResults = 10000; - } else { - searchFilters.maxResults = parseInt(searchFilters.maxResults, 10); - } - searchText = searchText.replace(showRE, "").trim(); - } - - /* Filter by URL: 'url:landgate.wa' or '-url:geoserver.nicta.com.au' */ - var urlRE = /(^|\s)(-?)url:([^ ]+)(\b|$)/i; - while (searchText.match(urlRE)) { - if (!searchText.match(urlRE)[2]) { - searchFilters.urlMatches.push(searchText.match(urlRE)[3].toLowerCase()); - } else { - searchFilters.urlDoesNotMatch.push( - searchText.match(urlRE)[3].toLowerCase() - ); - } - searchText = searchText.replace(urlRE, "").trim(); - } - } - - if (this._searchInProgress) { - this._searchInProgress.cancel = true; - this._searchInProgress = undefined; - } - - if (!defined(searchText) || /^\s*$/.test(searchText)) { - this.isSearching = false; - this.searchResults.removeAll(); - return; - } - - var searchFilters = { - typeIs: [], - typeIsNot: [], - urlMatches: [], - urlDoesNotMatch: [] - }; - parseSearchFilters(); - - this.isSearching = true; - this.searchResults.removeAll(); - this.searchMessage = undefined; - - this.terria.analytics.logEvent("search", "catalogue", searchText); - - var searchInProgress = (this._searchInProgress = { - cancel: false - }); - - var path = []; - var topLevelGroup = this.terria.catalog.group; - var promise = findMatchingItemsRecursively( - this, - searchInProgress, - new RegExp(searchText, "i"), - topLevelGroup, - path, - undefined, - searchFilters - ); - - var that = this; - return when(promise).then(function() { - that.isSearching = false; - - if (searchInProgress.cancel) { - return; - } - - if (that.searchResults.length === 0) { - that.searchMessage = i18next.t("viewModels.searchNoCatalogueItem"); - } - }); -}; - -function itemMatchesFilters(item, searchFilters) { - if ( - searchFilters.typeIs.length > 0 && - searchFilters.typeIs.indexOf(item.type.toLowerCase()) < 0 - ) { - return false; - } - if ( - searchFilters.typeIsNot.length > 0 && - searchFilters.typeIsNot.indexOf(item.type.toLowerCase()) >= 0 - ) { - return false; - } - if (!item.url) { - // if no URL, it can't match any positive filters, and can't fail by matching any negative ones. - return searchFilters.urlMatches.length === 0; - } - - var r = true; - // multiple -url: filters are and-ed - searchFilters.urlDoesNotMatch.forEach(function(e) { - // we just do simple string matching, not regex - if (item.url.toLowerCase().indexOf(e) >= 0) { - r = false; - } - }); - if (!r) { - return false; - } - if (searchFilters.urlMatches.length === 0) { - return true; - } - - r = false; - // multiple url: filters are or-ed - searchFilters.urlMatches.forEach(function(e) { - // we just do simple string matching, not regex - if (item.url.toLowerCase().indexOf(e) >= 0) { - r = true; - } - }); - - return r; -} - -function findMatchingItemsRecursively( - viewModel, - searchInProgress, - searchExpression, - group, - path, - promise, - searchFilters -) { - path.push(group); - if (!defined(searchFilters)) { - searchFilters = {}; - } - var items = group.items; - var maxResults = defined(searchFilters.maxResults) - ? searchFilters.maxResults - : viewModel.maxResults; - for ( - var i = 0; - !searchInProgress.cancel && - viewModel.searchResults.length < maxResults && - i < items.length; - ++i - ) { - var item = items[i]; - - if (item.isHidden) { - continue; - } - - // Match non-top-level items whose name contain the search text. - if ( - searchExpression.test(item.name) && - itemMatchesFilters(item, searchFilters) - ) { - addSearchResult(viewModel.searchResults, item, path); - } - - if (defined(item.items)) { - promise = loadAndSearchGroup( - viewModel, - item, - searchInProgress, - searchExpression, - path, - promise, - searchFilters - ); - } - } - - path.pop(); - - return promise; -} - -function loadAndSearchGroup( - viewModel, - group, - searchInProgress, - searchExpression, - path, - promise, - searchFilters -) { - path = path.slice(); - - // Let a previous load (represented by 'promise') finish first. - return when(promise).then(function() { - if (searchInProgress.cancel) { - return; - } - return runLater(function() { - if (searchInProgress.cancel) { - return; - } - var loadPromise = group.load(); - if (defined(loadPromise) && group.isLoading) { - return loadPromise - .then(function() { - return findMatchingItemsRecursively( - viewModel, - searchInProgress, - searchExpression, - group, - path, - undefined, - searchFilters - ); - }) - .otherwise(ignoreLoadErrors); - } else { - return findMatchingItemsRecursively( - viewModel, - searchInProgress, - searchExpression, - group, - path, - undefined, - searchFilters - ); - } - }); - }); -} - -function ignoreLoadErrors() {} - -function addSearchResult(searchResults, item, path) { - // Get the index of an existing search result that refers to the same catalog item (or -1) - var index = -1; - for (var j = 0; j < searchResults.length; ++j) { - if (item === searchResults[j].catalogItem) { - index = j; - break; - } - } - - // If a search result for item already exists, modify the tooltip of that search result - var prefix = i18next.t("viewModels.inMultipleLocations"); - if (index >= 0) { - if (searchResults[index].tooltip.indexOf(prefix) !== 0) { - searchResults[index].tooltip = searchResults[index].tooltip.replace( - /^In /, - prefix - ); - } - } else { - // Otherwise, create a new search result - searchResults.push( - new SearchResult({ - name: item.name, - isImportant: true, - catalogItem: item, - tooltip: pathToTooltip(path) - }) - ); - } -} - -function pathToTooltip(path) { - var result = i18next.t("viewModels.inDataCatalogue"); - - // Start at 1 to skip "Root Group" - for (var i = 1; i < path.length; ++i) { - result += " -> " + path[i].name; - } - - return result; -} - -module.exports = CatalogItemNameSearchProviderViewModel; diff --git a/lib/ViewModels/GazetteerSearchProviderViewModel.js b/lib/ViewModels/GazetteerSearchProviderViewModel.js deleted file mode 100644 index c2866d8c39d..00000000000 --- a/lib/ViewModels/GazetteerSearchProviderViewModel.js +++ /dev/null @@ -1,227 +0,0 @@ -"use strict"; - -/*global require*/ -var inherit = require("../Core/inherit"); -var SearchProviderViewModel = require("./SearchProviderViewModel"); -var SearchResultViewModel = require("../Models/SearchResultViewModel"); -var zoomRectangleFromPoint = require("../Map/zoomRectangleFromPoint"); - -var defaultValue = require("terriajs-cesium/Source/Core/defaultValue").default; -var defined = require("terriajs-cesium/Source/Core/defined").default; -const i18next = require("i18next").default; -var loadJson = require("../Core/loadJson").default; - -var GazetteerSearchProviderViewModel = function(options) { - SearchProviderViewModel.call(this); - - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - this.terria = options.terria; - this._geocodeInProgress = undefined; - - this.name = i18next.t("viewModels.searchPlaceNames"); - this.url = defaultValue( - options.url, - "http://www.ga.gov.au/gazetteer-search/gazetteer2012/select/" - ); - this.forceUseOfProxy = defaultValue(options.forceUseOfProxy, true); - this.flightDurationSeconds = defaultValue(options.flightDurationSeconds, 1.5); -}; - -inherit(SearchProviderViewModel, GazetteerSearchProviderViewModel); - -GazetteerSearchProviderViewModel.prototype.search = function(searchText) { - if (!defined(searchText) || /^\s*$/.test(searchText)) { - this.isSearching = false; - this.searchResults.removeAll(); - return; - } - - this.isSearching = true; - this.searchResults.removeAll(); - this.searchMessage = undefined; - - this.terria.analytics.logEvent("search", "gazetteer", searchText); - - // If there is already a search in progress, cancel it. - if (defined(this._geocodeInProgress)) { - this._geocodeInProgress.cancel = true; - this._geocodeInProgress = undefined; - } - - // I don't know how to get server-side sorting, so we have to retrieve lots of rows, then filter. - // Retrieving only 10 rows (default) means "Sydney" fails to actually retrieve Sydney... - - // Worth considering using "fq=class_code:(R%20X)", which would only return towns, states etc - - var url = this.url + "?q=name:*" + searchText + "*"; - // filter out bores, built structures, caves, landmarks, trig stations - url += "%20-class_code:(D%20E%20G%20J%20T)%20-feature_code:PRS"; - url += "&rows=200"; - - if (this.forceUseOfProxy || this.terria.corsProxy.shouldUseProxy(url)) { - url = this.terria.corsProxy.getURL(url); - } - - var promise = loadJson(url); - - var that = this; - var geocodeInProgress = (this._geocodeInProgress = promise - .then(function(solrQueryResponse) { - if (geocodeInProgress.cancel) { - return; - } - that.isSearching = false; - - if ( - defined(solrQueryResponse.response) && - solrQueryResponse.response.numFound > 0 - ) { - var results = solrQueryResponse.response.docs.sort(function(a, b) { - return sortResults(a, b, searchText); - }); - results = stripDuplicates(results); - results.slice(0, 10).forEach(function(result) { - var locLat = result.location.split(",")[0]; - var locLng = result.location.split(",")[1]; - - that.searchResults.push( - new SearchResultViewModel({ - name: - result.name + - (result.state_id !== "N/A" ? " (" + result.state_id + ")" : ""), - isImportant: !!result.feature_code.match( - "^(CNTY|CONT|DI|PRSH|STAT|LOCB|LOCU|SUB|URBN)$" - ), - clickAction: createZoomToFunction(that, locLat, locLng), - location: { - latitude: locLat, - longitude: locLng - } - }) - ); - }); - } else { - that.searchMessage = i18next.t("viewModels.searchNoPlaceNames"); - } - - that.isSearching = false; - }) - .otherwise(function() { - if (geocodeInProgress.cancel) { - return; - } - - that.isSearching = false; - that.searchMessage = i18next.t("viewModels.searchErrorOccurred"); - })); - - return geocodeInProgress; -}; - -// Given a list of results sorted in decreasing importance, strip results that are close to another result with the same name -function stripDuplicates(results) { - var i; - var placeshash = {}; - var stripped = []; - for (i = 0; i < results.length; i++) { - var lat = Number(results[i].location.split(",")[0]).toFixed(1); - var lng = Number(results[i].location.split(",")[1]).toFixed(1); - - var hash = results[i].name + "_" + lat + " " + lng; - if (!defined(placeshash[hash])) { - placeshash[hash] = results[i]; - stripped.push(results[i]); - } - } - return stripped; -} - -function featureScore(a, searchText) { - // could be further refined using http://www.ga.gov.au/image_cache/GA19367.pdf - // feature_code is defined on p24 - // class_code (A-X) matches a row in the table on p23 (eg, 'C' is 'Bays & Gulfs') - var featureTypes = [ - "CONT", - "STAT", - "URBN", - "LOCB", - "LOCU", - "SUB", - "DI", - "CNTY", - "DI" - ]; - featureTypes.push( - "HBR", - "CAPE", - "PEN", - "PT", - "BAY", - "PORT", - "GULF", - "BGHT", - "COVE", - "MT", - "HILL", - "PEAK", - "OCEN", - "SEA", - "RESV", - "LAKE", - "RES", - "STRM" - ); - featureTypes.push("PLN", "REEF", "VAL", "PRSH"); - - var aScore = 10000 - (featureTypes.indexOf(a.feature_code) + 1) * 100; - if (aScore === 10000) { - aScore = 0; - } - - if (a.name.toUpperCase() === searchText.toUpperCase()) { - // Bonus for exact match - // More testing required to choose this value. Should "Geelong" (parish in Queensland) outrank "North Geelong" (suburb in Vic)? - aScore += 10 * 100; - } else if (a.name.match(new RegExp("^" + searchText + "\\b", "i"))) { - // bonus for first word match ('Steve Bay' better than 'West Steve Bay') - aScore += 8 * 100; - } else if (a.name.match(new RegExp("\\b" + searchText + "\\b", "i"))) { - // bonus for word-boundary match ('Steve' better than 'Steveville') - aScore += 4 * 100; - } else if (a.name.match(new RegExp("^" + searchText, "i"))) { - // bonus for word-boundary match ('Steventon' better than 'Nosteve Town') - aScore += 2 * 100; - } - if (a.state_id === "N/A") { - // seems to be an indicator of a low quality result - aScore -= 10 * 100; - } - if (a.status === "U") { - // Not official? H=historical, U=unofficial. Bleh. - aScore -= 5 * 100; - } - if (a.status === "H") { - aScore -= 10 * 100; - } - - return aScore; -} - -function sortResults(a, b, searchText) { - return featureScore(b, searchText) - featureScore(a, searchText); -} - -function createZoomToFunction(viewModel, locLat, locLng) { - // Server does not return information of a bounding box, just a location. - // bboxSize is used to expand a point - var bboxSize = 0.2; - var rectangle = zoomRectangleFromPoint(locLat, locLng, bboxSize); - - return function() { - var terria = viewModel.terria; - terria.currentViewer.zoomTo(rectangle, viewModel.flightDurationSeconds); - }; -} - -module.exports = GazetteerSearchProviderViewModel; diff --git a/lib/ViewModels/GnafSearchProviderViewModel.js b/lib/ViewModels/GnafSearchProviderViewModel.js deleted file mode 100644 index 1b8412f32cf..00000000000 --- a/lib/ViewModels/GnafSearchProviderViewModel.js +++ /dev/null @@ -1,119 +0,0 @@ -"use strict"; - -/*global require*/ -var inherit = require("../Core/inherit"); -var SearchProviderViewModel = require("./SearchProviderViewModel"); -var SearchResultViewModel = require("../Models/SearchResultViewModel"); -var zoomRectangleFromPoint = require("../Map/zoomRectangleFromPoint"); -var GnafApi = require("../Models/GnafApi"); - -var defaultValue = require("terriajs-cesium/Source/Core/defaultValue").default; -var defined = require("terriajs-cesium/Source/Core/defined").default; -const i18next = require("i18next").default; - -/** - * Search provider that uses the Data61 Elastic Search GNAF service to look up addresses. - * - * @param options.terria Terria instance - * @param [options.gnafApi] The GnafApi object to query - if none is provided one will be created using terria.corsProxy - * and the default settings. - * @param [options.flightDurationSeconds] The number of seconds for the flight animation when zooming to new results. - * @constructor - */ -var GnafSearchProviderViewModel = function(options) { - SearchProviderViewModel.call(this); - - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - this.terria = options.terria; - - var url = defaultValue( - options.url, - this.terria.configParameters.gnafSearchUrl - ); - - this.name = i18next.t("viewModels.searchAddresses"); - this.gnafApi = defaultValue( - options.gnafApi, - new GnafApi(this.terria.corsProxy, url) - ); - this._geocodeInProgress = undefined; - this.flightDurationSeconds = defaultValue(options.flightDurationSeconds, 1.5); -}; - -inherit(SearchProviderViewModel, GnafSearchProviderViewModel); - -GnafSearchProviderViewModel.prototype.search = function(searchText) { - this.isSearching = true; - this.searchResults.removeAll(); - - if (!defined(searchText) || /^\s*$/.test(searchText)) { - return; - } - - this.searchMessage = undefined; - this.terria.analytics.logEvent("search", "gnaf", searchText); - - // If there is already a search in progress, cancel it. - if (defined(this._geocodeInProgress)) { - this._geocodeInProgress.cancel = true; - this._geocodeInProgress = undefined; - } - - var thisGeocode = this.gnafApi - .geoCode(searchText) - .then( - function(hits) { - if (thisGeocode.cancel) { - return; - } - - this.isSearching = false; - - if (hits.length === 0) { - this.searchMessage = i18next.t("viewModels.searchNoLocations"); - return; - } - - this.searchResults = hits.map( - function(hit) { - return new SearchResultViewModel({ - name: hit.name, - isImportant: hit.locational, - clickAction: createZoomToFunction( - this.terria, - hit.location, - this.flightDurationSeconds - ), - location: hit.location - }); - }.bind(this) - ); - }.bind(this) - ) - .otherwise( - function() { - if (thisGeocode.cancel) { - return; - } - - this.isSearching = false; - this.searchMessage = i18next.t("viewModels.searchErrorOccurred"); - }.bind(this) - ); - - this._geocodeInProgress = thisGeocode; -}; - -function createZoomToFunction(terria, location, duration) { - var rectangle = zoomRectangleFromPoint( - location.latitude, - location.longitude, - 0.01 - ); - - return function() { - terria.currentViewer.zoomTo(rectangle, duration); - }; -} - -module.exports = GnafSearchProviderViewModel; diff --git a/lib/ViewModels/NominatimSearchProviderViewModel.js b/lib/ViewModels/NominatimSearchProviderViewModel.js deleted file mode 100644 index 71e248956c1..00000000000 --- a/lib/ViewModels/NominatimSearchProviderViewModel.js +++ /dev/null @@ -1,199 +0,0 @@ -"use strict"; - -/*global require*/ -var inherit = require("../Core/inherit"); -var SearchProviderViewModel = require("./SearchProviderViewModel"); -var SearchResultViewModel = require("../Models/SearchResultViewModel"); - -var CesiumMath = require("terriajs-cesium/Source/Core/Math").default; -var Cartesian2 = require("terriajs-cesium/Source/Core/Cartesian2").default; -var defaultValue = require("terriajs-cesium/Source/Core/defaultValue").default; -var defined = require("terriajs-cesium/Source/Core/defined").default; -var Ellipsoid = require("terriajs-cesium/Source/Core/Ellipsoid").default; -var loadJson = require("../Core/loadJson").default; -var Rectangle = require("terriajs-cesium/Source/Core/Rectangle").default; -var when = require("terriajs-cesium/Source/ThirdParty/when").default; -const i18next = require("i18next").default; - -var NominatimSearchProviderViewModel = function(options) { - SearchProviderViewModel.call(this); - - options = defaultValue(options, defaultValue.EMPTY_OBJECT); - - this.terria = options.terria; - this.countryCodes = defined(options.countryCodes) - ? "&countrycodes=" + options.countryCodes - : ""; - - this._geocodeInProgress = undefined; - - this.name = "Nominatim"; - this.url = defaultValue(options.url, "//nominatim.openstreetmap.org/"); - if (this.url.length > 0 && this.url[this.url.length - 1] !== "/") { - this.url += "/"; - } - this.flightDurationSeconds = defaultValue(options.flightDurationSeconds, 1.5); - this.limitBounded = 2; - this.limitOthers = 2; -}; - -inherit(SearchProviderViewModel, NominatimSearchProviderViewModel); - -NominatimSearchProviderViewModel.prototype.search = function(searchText) { - if (!defined(searchText) || /^\s*$/.test(searchText)) { - this.isSearching = false; - this.searchResults.removeAll(); - return; - } - - this.isSearching = true; - this.searchResults.removeAll(); - this.searchMessage = undefined; - - this.terria.analytics.logEvent("search", "nominatim", searchText); - - // If there is already a search in progress, cancel it. - if (defined(this._geocodeInProgress)) { - this._geocodeInProgress.cancel = true; - this._geocodeInProgress = undefined; - } - - var bboxStr = ""; - - if (defined(this.terria.cesium)) { - var viewer = this.terria.cesium.viewer; - var posUL = viewer.camera.pickEllipsoid( - new Cartesian2(0, 0), - Ellipsoid.WGS84 - ); - var posLR = viewer.camera.pickEllipsoid( - new Cartesian2(viewer.canvas.width, viewer.canvas.height), - Ellipsoid.WGS84 - ); - if (defined(posUL) && defined(posLR)) { - posUL = Ellipsoid.WGS84.cartesianToCartographic(posUL); - posLR = Ellipsoid.WGS84.cartesianToCartographic(posLR); - bboxStr = - "&viewbox=" + - CesiumMath.toDegrees(posUL.longitude) + - "," + - CesiumMath.toDegrees(posUL.latitude) + - "," + - CesiumMath.toDegrees(posLR.longitude) + - "," + - CesiumMath.toDegrees(posLR.latitude); - } else { - bboxStr = ""; - } - } else if (defined(this.terria.leaflet)) { - var bbox = this.terria.leaflet.map.getBounds(); - bboxStr = - "&viewbox=" + - bbox.getWest() + - "," + - bbox.getNorth() + - "," + - bbox.getEast() + - "," + - bbox.getSouth(); - } - var promiseBounded = loadJson( - this.url + - "search?q=" + - searchText + - bboxStr + - "&bounded=1&format=json" + - this.countryCodes + - "&limit=" + - this.limitBounded - ); - var promiseOthers = loadJson( - this.url + - "search?q=" + - searchText + - "&format=json" + - this.countryCodes + - "&limit=" + - this.limitOthers - ); - - var that = this; - var geocodeInProgress = (this._geocodeInProgress = { - cancel: false - }); - - return when - .all([promiseBounded, promiseOthers]) - .then(function(result) { - if (geocodeInProgress.cancel) { - return; - } - that.isSearching = false; - - if (result.length === 0) { - return; - } - - var locations = []; - - // Locations in the bounded query go on top, locations elsewhere go undernearth - var findDbl = function(elts, id) { - return elts.filter(function(elt) { - return elt.id === id; - })[0]; - }; - - for (var i = 0; i < result.length; ++i) { - for (var j = 0; j < result[i].length; ++j) { - var resource = result[i][j]; - - var name = resource.display_name; - if (!defined(name)) { - continue; - } - - if (!findDbl(locations, resource.place_id)) { - locations.push( - new SearchResultViewModel({ - id: resource.place_id, - name: name, - isImportant: true, - clickAction: createZoomToFunction(that, resource) - }) - ); - } - } - } - - that.searchResults.push.apply(that.searchResults, locations); - - if (that.searchResults.length === 0) { - that.searchMessage = i18next.t("viewModels.searchNoLocations"); - } - }) - .otherwise(function() { - if (geocodeInProgress.cancel) { - return; - } - - that.isSearching = false; - that.searchMessage = i18next.t("viewModels.searchErrorOccurred"); - }); -}; - -function createZoomToFunction(viewModel, resource) { - var bbox = resource.boundingbox; - var south = bbox[0]; - var west = bbox[2]; - var north = bbox[1]; - var east = bbox[3]; - - var rectangle = Rectangle.fromDegrees(west, south, east, north); - - return function() { - var terria = viewModel.terria; - terria.currentViewer.zoomTo(rectangle, viewModel.flightDurationSeconds); - }; -} - -module.exports = NominatimSearchProviderViewModel; diff --git a/test/Models/AustralianGazetteerSearchProviderSpec.ts b/test/Models/AustralianGazetteerSearchProviderSpec.ts index b31b8ba790f..33a4a48047c 100644 --- a/test/Models/AustralianGazetteerSearchProviderSpec.ts +++ b/test/Models/AustralianGazetteerSearchProviderSpec.ts @@ -1,7 +1,7 @@ import { configure } from "mobx"; -import createAustralianGazetteerSearchProvider from "../../lib/Models/AustralianGazetteerSearchProvider"; +import createAustralianGazetteerSearchProvider from "../../lib/Models/SearchProvider/AustralianGazetteerSearchProvider"; import Terria from "../../lib/Models/Terria"; -import WebFeatureServiceSearchProvider from "../../lib/Models/WebFeatureServiceSearchProvider"; +import WebFeatureServiceSearchProvider from "../../lib/Models/SearchProvider/WebFeatureServiceSearchProvider"; const wfsResponseXml = require("raw-loader!../../wwwroot/test/WFS/getWithFilter.xml"); diff --git a/test/ViewModels/CatalogItemNameSearchProviderViewModelSpec.js b/test/ViewModels/CatalogItemNameSearchProviderViewModelSpec.js deleted file mode 100644 index b672bbcfae5..00000000000 --- a/test/ViewModels/CatalogItemNameSearchProviderViewModelSpec.js +++ /dev/null @@ -1,280 +0,0 @@ -"use strict"; - -/*global require,describe,it,expect,beforeEach*/ -var CatalogGroup = require("../../lib/Models/CatalogGroup"); -var CatalogItem = require("../../lib/Models/CatalogItem"); -var WebMapServiceCatalogItem = require("../../lib/Models/WebMapServiceCatalogItem"); -var GeoJsonCatalogItem = require("../../lib/Models/GeoJsonCatalogItem"); -var CatalogItemNameSearchProviderViewModel = require("../../lib/ViewModels/CatalogItemNameSearchProviderViewModel"); -var inherit = require("../../lib/Core/inherit"); -var runLater = require("../../lib/Core/runLater"); -var Terria = require("../../lib/Models/Terria"); - -describe("CatalogItemNameSearchProviderViewModel", function() { - var terria; - var searchProvider; - - beforeEach(function() { - terria = new Terria({ - baseUrl: "./" - }); - - searchProvider = new CatalogItemNameSearchProviderViewModel({ - terria: terria - }); - }); - - it("finds catalog items in a case-insensitive manner", function(done) { - var catalogGroup = terria.catalog.group; - - var item = new CatalogItem(terria); - item.name = "Thing to find"; - catalogGroup.add(item); - - searchProvider - .search("thing") - .then(function() { - expect(searchProvider.searchResults.length).toBe(1); - expect(searchProvider.searchResults[0].name).toBe("Thing to find"); - }) - .then(done) - .otherwise(done.fail); - }); - - it("finds catalog groups in a case-insensitive manner", function(done) { - var catalogGroup = terria.catalog.group; - - var item = new CatalogGroup(terria); - item.name = "Group to find"; - catalogGroup.add(item); - - searchProvider - .search("to") - .then(function() { - expect(searchProvider.searchResults.length).toBe(1); - expect(searchProvider.searchResults[0].name).toBe("Group to find"); - }) - .then(done) - .otherwise(done.fail); - }); - - it("does not find catalog items if they do not match", function(done) { - var catalogGroup = terria.catalog.group; - - var item = new CatalogItem(terria); - item.name = "Thing to find"; - catalogGroup.add(item); - - searchProvider - .search("foo") - .then(function() { - expect(searchProvider.searchResults.length).toBe(0); - }) - .then(done) - .otherwise(done.fail); - }); - - it("finds items in asynchronously-loaded groups", function(done) { - var DelayedGroup = function() { - CatalogGroup.call(this, terria); - - this.name = "Delayed Group"; - this._load = function() { - var that = this; - return runLater(function() { - var item = new CatalogItem(terria); - item.name = "Thing to find"; - that.add(item); - }); - }; - }; - inherit(CatalogGroup, DelayedGroup); - - terria.catalog.group.add(new DelayedGroup()); - searchProvider - .search("thing") - .then(function() { - expect(searchProvider.searchResults.length).toBe(1); - expect(searchProvider.searchResults[0].name).toBe("Thing to find"); - }) - .then(done) - .otherwise(done.fail); - }); - - it("finds results of a certain type in a case-insensitive manner", function(done) { - var catalogGroup = terria.catalog.group; - - var item1 = new WebMapServiceCatalogItem(terria); - item1.name = "WMS item to find"; - catalogGroup.add(item1); - - var item2 = new GeoJsonCatalogItem(terria, ""); - item2.name = "GeoJson item to find"; - catalogGroup.add(item2); - - searchProvider - .search("to is:wMs") - .then(function() { - expect(searchProvider.searchResults.length).toBe(1); - expect(searchProvider.searchResults[0].name).toBe("WMS item to find"); - }) - .then(done) - .otherwise(done.fail); - }); - - it("finds results not of a certain type in a case-insensitive manner", function(done) { - var catalogGroup = terria.catalog.group; - - var item1 = new WebMapServiceCatalogItem(terria); - item1.name = "WMS item to find"; - catalogGroup.add(item1); - - var item2 = new GeoJsonCatalogItem(terria, ""); - item2.name = "GeoJson item to find"; - catalogGroup.add(item2); - - searchProvider - .search("to -is:wMs") - .then(function() { - expect(searchProvider.searchResults.length).toBe(1); - expect(searchProvider.searchResults[0].name).toBe( - "GeoJson item to find" - ); - }) - .then(done) - .otherwise(done.fail); - }); - - it("finds results having a certain url", function(done) { - var catalogGroup = terria.catalog.group; - - var item1 = new CatalogItem(terria); - item1.name = "Server 1 item to find"; - item1.url = "http://server1.gov.au/page"; - catalogGroup.add(item1); - - var item2 = new CatalogItem(terria); - item2.name = "Server 2 item to find"; - item2.url = "http://server2.gov.au/page"; - catalogGroup.add(item2); - - searchProvider - .search("to url:server1.gov") - .then(function() { - expect(searchProvider.searchResults.length).toBe(1); - expect(searchProvider.searchResults[0].name).toBe( - "Server 1 item to find" - ); - }) - .then(done) - .otherwise(done.fail); - }); - - it("finds results that do not have a certain url", function(done) { - var catalogGroup = terria.catalog.group; - - var item1 = new CatalogItem(terria); - item1.name = "Server 1 item to find"; - item1.url = "http://server1.gov.au/page"; - catalogGroup.add(item1); - - var item2 = new CatalogItem(terria); - item2.name = "Server 2 item to find"; - item2.url = "http://server2.gov.au/page"; - catalogGroup.add(item2); - - searchProvider - .search("to -url:server1.gov") - .then(function() { - expect(searchProvider.searchResults.length).toBe(1); - expect(searchProvider.searchResults[0].name).toBe( - "Server 2 item to find" - ); - }) - .then(done) - .otherwise(done.fail); - }); - - it("stops searching after the specified number of items", function(done) { - var catalogGroup = terria.catalog.group; - - var maxResults = 9; - - // Add items matching the query. - for (var i = 0; i < maxResults; ++i) { - var item = new CatalogItem(terria); - item.name = "Thing to find " + i; - catalogGroup.add(item); - } - - // Add an 11th item that will flip out if asked to load. - var FlipOutGroup = function(terria) { - CatalogGroup.call(this, terria); - - this.name = "Flip Out Group"; - this._load = function() { - done.fail("This item should not be asked to load."); - }; - }; - inherit(CatalogGroup, FlipOutGroup); - catalogGroup.add(new FlipOutGroup(terria)); - - searchProvider.maxResults = maxResults; - searchProvider - .search("thing") - .then(function() { - expect(searchProvider.searchResults.length).toBe(maxResults); - }) - .then(done) - .otherwise(done.fail); - }); - - it("combines duplicate search entries of the same item in different groups", function(done) { - var catalogGroup = terria.catalog.group; - - var group1 = new CatalogGroup(terria); - group1.name = "Group1"; - catalogGroup.add(group1); - - var item = new CatalogItem(terria); - item.name = "Thing to find"; - catalogGroup.add(item); - group1.add(item); - - searchProvider - .search("to") - .then(function() { - expect(searchProvider.searchResults.length).toBe(1); - expect(searchProvider.searchResults[0].name).toBe("Thing to find"); - expect(searchProvider.searchResults[0].tooltip).toMatch( - /^In multiple locations including: / - ); - }) - .then(done) - .otherwise(done.fail); - }); - - it("does not combine different items with the same item name", function(done) { - var catalogGroup = terria.catalog.group; - - var item1 = new CatalogItem(terria); - item1.name = "Thing to find"; - item1.id = "thing1"; - catalogGroup.add(item1); - - var item2 = new CatalogItem(terria); - item2.name = "Thing to find"; - item2.id = "thing2"; - catalogGroup.add(item2); - - searchProvider - .search("to") - .then(function() { - expect(searchProvider.searchResults.length).toBe(2); - expect(searchProvider.searchResults[0].name).toBe("Thing to find"); - expect(searchProvider.searchResults[1].name).toBe("Thing to find"); - }) - .then(done) - .otherwise(done.fail); - }); -}); From f73539ee4900c3b9500f295853c6ef558dbade83 Mon Sep 17 00:00:00 2001 From: zoran995 Date: Tue, 19 Jan 2021 14:00:46 +0100 Subject: [PATCH 02/62] add SearchProvider folder in ModelMixins --- .../{ => SerchProvider}/SearchProviderMixin.ts | 10 +++++----- .../WebFeatureServiceSearchProviderMixin.ts | 17 ++++++++--------- .../AustralianGazetteerSearchProvider.ts | 6 +++--- .../SearchProvider/BingMapsSearchProvider.ts | 2 +- .../SearchProvider/CatalogSearchProvider.ts | 2 +- .../SearchProvider/SearchProviderResults.ts | 2 +- lib/Models/SearchProvider/StubSearchProvider.ts | 2 +- lib/ReactViewModels/SearchState.ts | 2 +- 8 files changed, 21 insertions(+), 22 deletions(-) rename lib/ModelMixins/{ => SerchProvider}/SearchProviderMixin.ts (89%) rename lib/ModelMixins/{ => SerchProvider}/WebFeatureServiceSearchProviderMixin.ts (92%) diff --git a/lib/ModelMixins/SearchProviderMixin.ts b/lib/ModelMixins/SerchProvider/SearchProviderMixin.ts similarity index 89% rename from lib/ModelMixins/SearchProviderMixin.ts rename to lib/ModelMixins/SerchProvider/SearchProviderMixin.ts index b53a05bd2e3..bb9b056b5c0 100644 --- a/lib/ModelMixins/SearchProviderMixin.ts +++ b/lib/ModelMixins/SerchProvider/SearchProviderMixin.ts @@ -3,11 +3,11 @@ import { fromPromise } from "mobx-utils"; import Ellipsoid from "terriajs-cesium/Source/Core/Ellipsoid"; import CesiumMath from "terriajs-cesium/Source/Core/Math"; import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; -import Constructor from "../Core/Constructor"; -import Model from "../Models/Model"; -import SearchProviderResults from "../Models/SearchProvider/SearchProviderResults"; -import Terria from "../Models/Terria"; -import SearchProviderTraits from "../Traits/SearchProvider/SearchProviderTraits"; +import Constructor from "../../Core/Constructor"; +import Model from "../../Models/Model"; +import SearchProviderResults from "../../Models/SearchProvider/SearchProviderResults"; +import Terria from "../../Models/Terria"; +import SearchProviderTraits from "../../Traits/SearchProvider/SearchProviderTraits"; type SearchProvider = Model; diff --git a/lib/ModelMixins/WebFeatureServiceSearchProviderMixin.ts b/lib/ModelMixins/SerchProvider/WebFeatureServiceSearchProviderMixin.ts similarity index 92% rename from lib/ModelMixins/WebFeatureServiceSearchProviderMixin.ts rename to lib/ModelMixins/SerchProvider/WebFeatureServiceSearchProviderMixin.ts index 70a0c26bf9e..c8c1a814835 100644 --- a/lib/ModelMixins/WebFeatureServiceSearchProviderMixin.ts +++ b/lib/ModelMixins/SerchProvider/WebFeatureServiceSearchProviderMixin.ts @@ -2,15 +2,14 @@ import i18next from "i18next"; import { runInAction } from "mobx"; import Resource from "terriajs-cesium/Source/Core/Resource"; import URI from "urijs"; -import Constructor from "../Core/Constructor"; -import makeRealPromise from "../Core/makeRealPromise"; -import zoomRectangleFromPoint from "../Map/zoomRectangleFromPoint"; -import Model from "../Models/Model"; -import SearchProviderResults from "../Models/SearchProvider/SearchProviderResults"; -import SearchResult from "../Models/SearchProvider/SearchResult"; -import Terria from "../Models/Terria"; -import xml2json from "../ThirdParty/xml2json"; -import WebFeatureServiceSearchProviderTraits from "../Traits/SearchProvider/WebFeatureServiceSearchProviderTraits"; +import Constructor from "../../Core/Constructor"; +import makeRealPromise from "../../Core/makeRealPromise"; +import zoomRectangleFromPoint from "../../Map/zoomRectangleFromPoint"; +import Model from "../../Models/Model"; +import SearchProviderResults from "../../Models/SearchProvider/SearchProviderResults"; +import SearchResult from "../../Models/SearchProvider/SearchResult"; +import xml2json from "../../ThirdParty/xml2json"; +import WebFeatureServiceSearchProviderTraits from "../../Traits/SearchProvider/WebFeatureServiceSearchProviderTraits"; import SearchProviderMixin from "./SearchProviderMixin"; function WebFeatureServiceSearchProviderMixin< diff --git a/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts b/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts index 9d0dbf50954..2b4d2996215 100644 --- a/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts +++ b/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts @@ -1,6 +1,6 @@ import WebFeatureServiceSearchProviderTraits from "../../Traits/SearchProvider/WebFeatureServiceSearchProviderTraits"; import CreateModel from "../CreateModel"; -import WebFeatureServiceSearchProviderMixin from "./../../ModelMixins/WebFeatureServiceSearchProviderMixin"; +import WebFeatureServiceSearchProviderMixin from "../../ModelMixins/SerchProvider/WebFeatureServiceSearchProviderMixin"; import SearchResult from "./SearchResult"; const featureCodesToNamesMap = new Map([ @@ -224,8 +224,8 @@ export default class AustralianGazetteerSearchProvider extends WebFeatureService CreateModel(WebFeatureServiceSearchProviderTraits) ) { static readonly type = "australian-gazetteer-search-provider"; - - get type(){ + + get type() { return AustralianGazetteerSearchProvider.type; } diff --git a/lib/Models/SearchProvider/BingMapsSearchProvider.ts b/lib/Models/SearchProvider/BingMapsSearchProvider.ts index 312c873c5e8..522e3723d3c 100644 --- a/lib/Models/SearchProvider/BingMapsSearchProvider.ts +++ b/lib/Models/SearchProvider/BingMapsSearchProvider.ts @@ -6,7 +6,7 @@ import Resource from "terriajs-cesium/Source/Core/Resource"; import loadJsonp from "../../Core/loadJsonp"; import SearchProviderMixin, { getMapCenter -} from "../../ModelMixins/SearchProviderMixin"; +} from "../../ModelMixins/SerchProvider/SearchProviderMixin"; import BingMapsSearchProviderTraits from "../../Traits/SearchProvider/BingMapsSearchProviderTraits"; import CreateModel from "../CreateModel"; import SearchProviderResults from "./SearchProviderResults"; diff --git a/lib/Models/SearchProvider/CatalogSearchProvider.ts b/lib/Models/SearchProvider/CatalogSearchProvider.ts index 1a1e85c9ba2..946b2a4ec67 100644 --- a/lib/Models/SearchProvider/CatalogSearchProvider.ts +++ b/lib/Models/SearchProvider/CatalogSearchProvider.ts @@ -4,7 +4,7 @@ import ReferenceMixin from "../../ModelMixins/ReferenceMixin"; import CatalogSearchProviderTraits from "../../Traits/SearchProvider/CatalogSearchProviderTraits"; import CreateModel from "../CreateModel"; import Terria from "../Terria"; -import SearchProviderMixin from "./../../ModelMixins/SearchProviderMixin"; +import SearchProviderMixin from "../../ModelMixins/SerchProvider/SearchProviderMixin"; import SearchProviderResults from "./SearchProviderResults"; import SearchResult from "./SearchResult"; diff --git a/lib/Models/SearchProvider/SearchProviderResults.ts b/lib/Models/SearchProvider/SearchProviderResults.ts index 751e7f7cd60..78ee3340855 100644 --- a/lib/Models/SearchProvider/SearchProviderResults.ts +++ b/lib/Models/SearchProvider/SearchProviderResults.ts @@ -1,7 +1,7 @@ import { observable } from "mobx"; import SearchResult from "./SearchResult"; import { IPromiseBasedObservable, fromPromise } from "mobx-utils"; -import SearchProviderMixin from "./../../ModelMixins/SearchProviderMixin"; +import SearchProviderMixin from "../../ModelMixins/SerchProvider/SearchProviderMixin"; export default class SearchProviderResults { @observable results: SearchResult[] = []; diff --git a/lib/Models/SearchProvider/StubSearchProvider.ts b/lib/Models/SearchProvider/StubSearchProvider.ts index 50b19adb3db..0737ffad5ff 100644 --- a/lib/Models/SearchProvider/StubSearchProvider.ts +++ b/lib/Models/SearchProvider/StubSearchProvider.ts @@ -1,6 +1,6 @@ import LocationSearchProviderTraits from "./../../Traits/SearchProvider/LocationSearchProviderTraits"; import primitiveTrait from "./../../Traits/primitiveTrait"; -import SearchProviderMixin from "../../ModelMixins/SearchProviderMixin"; +import SearchProviderMixin from "../../ModelMixins/SerchProvider/SearchProviderMixin"; import CreateModel from "../CreateModel"; import SearchProviderResults from "./SearchProviderResults"; diff --git a/lib/ReactViewModels/SearchState.ts b/lib/ReactViewModels/SearchState.ts index bcd1fe31248..a12ebd79085 100644 --- a/lib/ReactViewModels/SearchState.ts +++ b/lib/ReactViewModels/SearchState.ts @@ -9,7 +9,7 @@ import filterOutUndefined from "../Core/filterOutUndefined"; import CatalogSearchProvider from "../Models/SearchProvider/CatalogSearchProvider"; import SearchProviderResults from "../Models/SearchProvider/SearchProviderResults"; import Terria from "../Models/Terria"; -import SearchProviderMixin from "./../ModelMixins/SearchProviderMixin"; +import SearchProviderMixin from "../ModelMixins/SerchProvider/SearchProviderMixin"; interface SearchStateOptions { terria: Terria; From 26494d926985738ea4fc4d95022b2cb4537f15a3 Mon Sep 17 00:00:00 2001 From: zoran995 Date: Tue, 19 Jan 2021 14:44:01 +0100 Subject: [PATCH 03/62] set default traits --- .../SerchProvider/SearchProviderMixin.ts | 4 +++- .../SearchProvider/BingMapsSearchProvider.ts | 12 ++-------- .../upsertSearchProviderFromJson.ts | 24 +++++++++++++++++++ 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/lib/ModelMixins/SerchProvider/SearchProviderMixin.ts b/lib/ModelMixins/SerchProvider/SearchProviderMixin.ts index bb9b056b5c0..723049bc7df 100644 --- a/lib/ModelMixins/SerchProvider/SearchProviderMixin.ts +++ b/lib/ModelMixins/SerchProvider/SearchProviderMixin.ts @@ -4,9 +4,11 @@ import Ellipsoid from "terriajs-cesium/Source/Core/Ellipsoid"; import CesiumMath from "terriajs-cesium/Source/Core/Math"; import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; import Constructor from "../../Core/Constructor"; -import Model from "../../Models/Model"; +import Model, { BaseModel } from "../../Models/Model"; import SearchProviderResults from "../../Models/SearchProvider/SearchProviderResults"; +import StratumFromTraits from "../../Models/StratumFromTraits"; import Terria from "../../Models/Terria"; +import ModelTraits from "../../Traits/ModelTraits"; import SearchProviderTraits from "../../Traits/SearchProvider/SearchProviderTraits"; type SearchProvider = Model; diff --git a/lib/Models/SearchProvider/BingMapsSearchProvider.ts b/lib/Models/SearchProvider/BingMapsSearchProvider.ts index 522e3723d3c..7adffd9947c 100644 --- a/lib/Models/SearchProvider/BingMapsSearchProvider.ts +++ b/lib/Models/SearchProvider/BingMapsSearchProvider.ts @@ -25,10 +25,7 @@ export default class BingMapsSearchProvider extends SearchProviderMixin( constructor(uniqueId: string | undefined, terria: Terria) { super(uniqueId, terria); - if ( - (!this.key || this.key === "") && - this.terria.configParameters.bingMapsKey - ) { + if (!this.key && this.terria.configParameters.bingMapsKey) { this.setTrait( CommonStrata.defaults, "key", @@ -52,7 +49,6 @@ export default class BingMapsSearchProvider extends SearchProviderMixin( searchText: string, searchResults: SearchProviderResults ): Promise { - console.log(this.key); searchResults.results.length = 0; searchResults.message = undefined; @@ -179,11 +175,7 @@ function createZoomToFunction(model: BingMapsSearchProvider, resource: any) { const rectangle = Rectangle.fromDegrees(west, south, east, north); return function() { - const flightDurationSeconds: number = - model.flightDurationSeconds || - model.terria.configParameters.searchBar.flightDurationSeconds; - const terria = model.terria; - terria.currentViewer.zoomTo(rectangle, flightDurationSeconds); + terria.currentViewer.zoomTo(rectangle, model.flightDurationSeconds!); }; } diff --git a/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts b/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts index c52dca54b74..bfbd63735ab 100644 --- a/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts +++ b/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts @@ -47,6 +47,8 @@ export default function upsertSearchProviderFromJson( model?.terria.addSearchProvider(model); } + addDefaultTraits(model); + try { updateModelFromJson(model, stratumName, json); } catch (error) { @@ -55,3 +57,25 @@ export default function upsertSearchProviderFromJson( model?.setTrait(CommonStrata.underride, "isExperiencingIssues", true); } } + +function addDefaultTraits(model: BaseModel) { + const terria = model.terria; + + model.setTrait( + CommonStrata.defaults, + "flightDurationSeconds", + terria.configParameters.searchBar.flightDurationSeconds + ); + + model.setTrait( + CommonStrata.defaults, + "minCharacters", + terria.configParameters.searchBar.minCharacters + ); + + model.setTrait( + CommonStrata.defaults, + "recommendedListLength", + terria.configParameters.searchBar.recommendedListLength + ); +} From 55f3c4296b6178056d1cdbed8e53922fd439bbea Mon Sep 17 00:00:00 2001 From: zoran995 Date: Thu, 21 Jan 2021 15:06:06 +0100 Subject: [PATCH 04/62] update useTranslationIfExists to request translation# at start of the key and add deprecation warning --- lib/Language/languageHelpers.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/Language/languageHelpers.ts b/lib/Language/languageHelpers.ts index 1b0ea910ac3..3e1834adb5b 100644 --- a/lib/Language/languageHelpers.ts +++ b/lib/Language/languageHelpers.ts @@ -4,5 +4,15 @@ import i18next from "i18next"; * Takes a given string and translates it if it exists, otherwise return */ export function useTranslationIfExists(keyOrString: string) { - return i18next.exists(keyOrString) ? i18next.t(keyOrString) : keyOrString; + if (keyOrString && keyOrString.indexOf("translate#") === 0) { + const translationKey = keyOrString.substr("translate#".length); + return i18next.exists(translationKey) + ? i18next.t(translationKey) + : translationKey; + } else { + console.warn( + "using translation key within config won't work in future unless you prefix it with `translate#`" + ); + return keyOrString; + } } From c7525381394792007ad7829554f05f22797babb5 Mon Sep 17 00:00:00 2001 From: zoran995 Date: Thu, 21 Jan 2021 15:16:58 +0100 Subject: [PATCH 05/62] restore default return for useTranslationIfExists until final decission to remove it, and to have time to update everything --- lib/Language/languageHelpers.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/Language/languageHelpers.ts b/lib/Language/languageHelpers.ts index 3e1834adb5b..01a7a30c3ca 100644 --- a/lib/Language/languageHelpers.ts +++ b/lib/Language/languageHelpers.ts @@ -13,6 +13,8 @@ export function useTranslationIfExists(keyOrString: string) { console.warn( "using translation key within config won't work in future unless you prefix it with `translate#`" ); - return keyOrString; + // after the depreaction + // return keyOrString; + return i18next.exists(keyOrString) ? i18next.t(keyOrString) : keyOrString; } } From 4778f074b847bac2f25fc727b1465d509ccf3f01 Mon Sep 17 00:00:00 2001 From: zoran995 Date: Thu, 21 Jan 2021 15:19:30 +0100 Subject: [PATCH 06/62] remove id from list of traits --- lib/Traits/SearchProvider/SearchProviderTraits.ts | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/Traits/SearchProvider/SearchProviderTraits.ts b/lib/Traits/SearchProvider/SearchProviderTraits.ts index 0c2ac76e391..a023dfcb040 100644 --- a/lib/Traits/SearchProvider/SearchProviderTraits.ts +++ b/lib/Traits/SearchProvider/SearchProviderTraits.ts @@ -9,24 +9,19 @@ export default class SearchProviderTraits extends ModelTraits { }) name: string = "unknown"; - @primitiveTrait({ - type: "string", - name: "ID", - description: "Unique id of the search provider." - }) - id?: string; - @primitiveTrait({ type: "boolean", name: "Open by default", - description: "Wheter are this search provider results open by default" + description: "Wheter are this search provider results open by default", + isNullable: true }) openByDefault: boolean = true; @primitiveTrait({ type: "number", name: "Minimum characters", - description: "Minimum number of characters required for search to start" + description: "Minimum number of characters required for search to start", + isNullable: true }) minCharacters?: number; } From c2608bfc3ea64936a34898b275116ab649214393 Mon Sep 17 00:00:00 2001 From: zoran995 Date: Thu, 21 Jan 2021 15:21:00 +0100 Subject: [PATCH 07/62] make translation of names work correctly --- lib/ModelMixins/SerchProvider/SearchProviderMixin.ts | 9 +++++---- lib/Models/SearchProvider/SearchProviderResults.ts | 5 ++--- lib/Models/Terria.ts | 6 +++--- lib/Models/updateModelFromJson.ts | 4 ++++ lib/ReactViews/Search/SearchBoxAndResults.jsx | 9 +++------ .../SearchProvider/LocationSearchProviderTraits.ts | 6 ++++-- 6 files changed, 21 insertions(+), 18 deletions(-) diff --git a/lib/ModelMixins/SerchProvider/SearchProviderMixin.ts b/lib/ModelMixins/SerchProvider/SearchProviderMixin.ts index 723049bc7df..4c28a48f4f6 100644 --- a/lib/ModelMixins/SerchProvider/SearchProviderMixin.ts +++ b/lib/ModelMixins/SerchProvider/SearchProviderMixin.ts @@ -11,12 +11,13 @@ import Terria from "../../Models/Terria"; import ModelTraits from "../../Traits/ModelTraits"; import SearchProviderTraits from "../../Traits/SearchProvider/SearchProviderTraits"; -type SearchProvider = Model; +type SearchProviderMixin = Model; -function SearchProviderMixin>(Base: T) { +function SearchProviderMixin>( + Base: T +) { abstract class SearchProviderMixin extends Base { abstract get type(): string; - @observable name = "Unknown"; @observable isOpen = this.openByDefault; @action @@ -26,7 +27,7 @@ function SearchProviderMixin>(Base: T) { @action search(searchText: string): SearchProviderResults { - const result = new SearchProviderResults(Base); + const result = new SearchProviderResults(this); result.resultsCompletePromise = fromPromise( this.doSearch(searchText, result) ); diff --git a/lib/Models/SearchProvider/SearchProviderResults.ts b/lib/Models/SearchProvider/SearchProviderResults.ts index 78ee3340855..91946a31771 100644 --- a/lib/Models/SearchProvider/SearchProviderResults.ts +++ b/lib/Models/SearchProvider/SearchProviderResults.ts @@ -11,9 +11,8 @@ export default class SearchProviderResults { Promise.resolve() ); - constructor( - readonly searchProvider: SearchProviderMixin.SearchProviderMixin - ) {} + constructor(readonly searchProvider: SearchProviderMixin) { + } get isSearching() { return this.resultsCompletePromise.state === "pending"; diff --git a/lib/Models/Terria.ts b/lib/Models/Terria.ts index 899e0ced372..95191e61137 100644 --- a/lib/Models/Terria.ts +++ b/lib/Models/Terria.ts @@ -309,7 +309,7 @@ export default class Terria { languageConfiguration: undefined, displayOneBrand: 0, searchBar: { - placeholder: "search.placeholder", + placeholder: "translate#search.placeholder", recommendedListLength: 5, sortByName: true, flightDurationSeconds: 1.5, @@ -319,14 +319,14 @@ export default class Terria { { id: "search-provider/bing-maps", type: "bing-maps-search-provider", - name: "search.bingMaps", + name: "translate#viewModels.searchLocations", url: "https://dev.virtualearth.net/", flightDurationSeconds: 1.5 }, { id: "search-provider/australian-gazetteer", type: "australian-gazetteer-search-provider", - name: "viewModels.searchPlaceNames", + name: "translate#viewModels.searchPlaceNames", url: "http://services.ga.gov.au/gis/services/Australian_Gazetteer/MapServer/WFSServer", searchPropertyName: "Australian_Gazetteer:NameU", diff --git a/lib/Models/updateModelFromJson.ts b/lib/Models/updateModelFromJson.ts index e5d59929a8e..7fc34fa6a2e 100644 --- a/lib/Models/updateModelFromJson.ts +++ b/lib/Models/updateModelFromJson.ts @@ -2,6 +2,7 @@ import { runInAction, isObservableArray } from "mobx"; import TerriaError from "../Core/TerriaError"; import createStratumInstance from "./createStratumInstance"; import { BaseModel } from "./Model"; +import { useTranslationIfExists } from "./../Language/languageHelpers"; export default function updateModelFromJson( model: BaseModel, @@ -48,6 +49,9 @@ export default function updateModelFromJson( newTrait ); } + if (propertyName === "name") { + newTrait = useTranslationIfExists(jsonValue); + } model.setTrait(stratumName, propertyName, newTrait); } }); diff --git a/lib/ReactViews/Search/SearchBoxAndResults.jsx b/lib/ReactViews/Search/SearchBoxAndResults.jsx index dfb628c8d6b..1bd33992a9c 100644 --- a/lib/ReactViews/Search/SearchBoxAndResults.jsx +++ b/lib/ReactViews/Search/SearchBoxAndResults.jsx @@ -198,12 +198,9 @@ export class SearchBoxAndResultsRaw extends React.Component { overflow-y: auto; `} > - + {searchState.locationSearchResults.map(search => ( - + ))} diff --git a/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts b/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts index b7285613ff5..5067a14f335 100644 --- a/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts +++ b/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts @@ -14,14 +14,16 @@ export default class LocationSearchProviderTraits extends SearchProviderTraits { type: "boolean", name: "Open by default", description: - "True if the geocoder should query as the user types to autocomplete." + "True if the geocoder should query as the user types to autocomplete.", + isNullable: true }) autocomplete?: boolean; @primitiveTrait({ type: "number", name: "URL", - description: "Time to move to the result location." + description: "Time to move to the result location.", + isNullable: true }) flightDurationSeconds?: number; } From d8616bd7b83be15a10f2205a00446f48f085279b Mon Sep 17 00:00:00 2001 From: zoran995 Date: Tue, 16 Feb 2021 11:56:18 +0100 Subject: [PATCH 08/62] Continue work on connecting search providers to UI - tsify some of the search UI components - properly separate the catalog and search bar - use the recommendedListLength when rendering - use isOpen when rendering - add boundingBoxLimit search bar config param --- .../LocationSearchProviderMixin.ts | 69 ++++++ .../SearchProviderMixin.ts | 44 +--- .../WebFeatureServiceSearchProviderMixin.ts | 4 +- .../AustralianGazetteerSearchProvider.ts | 2 +- .../SearchProvider/BingMapsSearchProvider.ts | 6 +- .../SearchProvider/CatalogSearchProvider.ts | 2 +- .../SearchProvider/SearchProviderResults.ts | 7 +- .../SearchProvider/StubSearchProvider.ts | 2 +- .../upsertSearchProviderFromJson.ts | 4 +- lib/Models/Terria.ts | 16 +- lib/ReactViewModels/SearchState.ts | 5 +- lib/ReactViews/Loader.tsx | 12 +- .../Search/LocationSearchResults.jsx | 193 ----------------- .../Search/LocationSearchResults.tsx | 204 ++++++++++++++++++ lib/ReactViews/Search/SearchBox.jsx | 8 +- lib/ReactViews/Search/SearchBoxAndResults.jsx | 26 +-- lib/ReactViews/Search/SearchHeader.jsx | 39 ---- lib/ReactViews/Search/SearchHeader.tsx | 32 +++ lib/ReactViews/Search/SearchResult.jsx | 120 ----------- lib/ReactViews/Search/SearchResult.tsx | 107 +++++++++ .../Search/location-search-result.scss | 83 ------- .../Search/location-search-result.scss.d.ts | 13 -- lib/ReactViews/Search/search-box.scss | 60 ------ lib/ReactViews/Search/search-box.scss.d.ts | 10 - lib/ReactViews/Search/search-result.scss | 95 -------- lib/ReactViews/Search/search-result.scss.d.ts | 17 -- lib/Styled/{List.jsx => List.tsx} | 0 .../LocationSearchProviderTraits.ts | 20 +- .../SearchProvider/SearchProviderTraits.ts | 8 - 29 files changed, 481 insertions(+), 727 deletions(-) create mode 100644 lib/ModelMixins/SearchProvider/LocationSearchProviderMixin.ts rename lib/ModelMixins/{SerchProvider => SearchProvider}/SearchProviderMixin.ts (54%) rename lib/ModelMixins/{SerchProvider => SearchProvider}/WebFeatureServiceSearchProviderMixin.ts (97%) delete mode 100644 lib/ReactViews/Search/LocationSearchResults.jsx create mode 100644 lib/ReactViews/Search/LocationSearchResults.tsx delete mode 100644 lib/ReactViews/Search/SearchHeader.jsx create mode 100644 lib/ReactViews/Search/SearchHeader.tsx delete mode 100644 lib/ReactViews/Search/SearchResult.jsx create mode 100644 lib/ReactViews/Search/SearchResult.tsx delete mode 100644 lib/ReactViews/Search/location-search-result.scss delete mode 100644 lib/ReactViews/Search/location-search-result.scss.d.ts delete mode 100644 lib/ReactViews/Search/search-box.scss delete mode 100644 lib/ReactViews/Search/search-box.scss.d.ts delete mode 100644 lib/ReactViews/Search/search-result.scss delete mode 100644 lib/ReactViews/Search/search-result.scss.d.ts rename lib/Styled/{List.jsx => List.tsx} (100%) diff --git a/lib/ModelMixins/SearchProvider/LocationSearchProviderMixin.ts b/lib/ModelMixins/SearchProvider/LocationSearchProviderMixin.ts new file mode 100644 index 00000000000..faa54b3ad92 --- /dev/null +++ b/lib/ModelMixins/SearchProvider/LocationSearchProviderMixin.ts @@ -0,0 +1,69 @@ +import { action, observable } from "mobx"; +import { fromPromise } from "mobx-utils"; +import Ellipsoid from "terriajs-cesium/Source/Core/Ellipsoid"; +import CesiumMath from "terriajs-cesium/Source/Core/Math"; +import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; +import Constructor from "../../Core/Constructor"; +import Model, { BaseModel } from "../../Models/Model"; +import SearchProviderResults from "../../Models/SearchProvider/SearchProviderResults"; +import StratumFromTraits from "../../Models/StratumFromTraits"; +import Terria from "../../Models/Terria"; +import ModelTraits from "../../Traits/ModelTraits"; +import SearchProviderTraits from "../../Traits/SearchProvider/SearchProviderTraits"; +import CommonStrata from "../../Models/CommonStrata"; +import LocationSearchProviderTraits from "../../Traits/SearchProvider/LocationSearchProviderTraits"; +import SearchProviderMixin from "./SearchProviderMixin"; + +type LocationSearchProviderModel = Model; + +function LocationSearchProviderMixin< + T extends Constructor +>(Base: T) { + abstract class LocationSearchProviderMixin extends SearchProviderMixin(Base) { + @action + toggleOpen(stratumId: CommonStrata = CommonStrata.user) { + this.setTrait(stratumId, "isOpen", !this.isOpen); + } + + get hasLocationSearchProviderMixin() { + return true; + } + } + return LocationSearchProviderMixin; +} + +interface MapCenter { + longitude: number; + latitude: number; +} + +export function getMapCenter(terria: Terria): MapCenter { + const view = terria.currentViewer.getCurrentCameraView(); + if (view.position !== undefined) { + const cameraPositionCartographic = Ellipsoid.WGS84.cartesianToCartographic( + view.position + ); + return { + longitude: CesiumMath.toDegrees(cameraPositionCartographic.longitude), + latitude: CesiumMath.toDegrees(cameraPositionCartographic.latitude) + }; + } else { + const center = Rectangle.center(view.rectangle); + return { + longitude: CesiumMath.toDegrees(center.longitude), + latitude: CesiumMath.toDegrees(center.latitude) + }; + } +} + +namespace LocationSearchProviderMixin { + export interface LocationSearchProviderMixin + extends InstanceType> {} + export function isMixedInto( + model: any + ): model is LocationSearchProviderMixin { + return model && model.hasLocationSearchProviderMixin; + } +} + +export default LocationSearchProviderMixin; diff --git a/lib/ModelMixins/SerchProvider/SearchProviderMixin.ts b/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts similarity index 54% rename from lib/ModelMixins/SerchProvider/SearchProviderMixin.ts rename to lib/ModelMixins/SearchProvider/SearchProviderMixin.ts index 4c28a48f4f6..cf3e9066936 100644 --- a/lib/ModelMixins/SerchProvider/SearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts @@ -1,29 +1,17 @@ -import { action, observable } from "mobx"; +import { action } from "mobx"; import { fromPromise } from "mobx-utils"; -import Ellipsoid from "terriajs-cesium/Source/Core/Ellipsoid"; -import CesiumMath from "terriajs-cesium/Source/Core/Math"; -import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; import Constructor from "../../Core/Constructor"; -import Model, { BaseModel } from "../../Models/Model"; +import Model from "../../Models/Model"; import SearchProviderResults from "../../Models/SearchProvider/SearchProviderResults"; -import StratumFromTraits from "../../Models/StratumFromTraits"; -import Terria from "../../Models/Terria"; -import ModelTraits from "../../Traits/ModelTraits"; import SearchProviderTraits from "../../Traits/SearchProvider/SearchProviderTraits"; -type SearchProviderMixin = Model; +type SearchProviderModel = Model; -function SearchProviderMixin>( +function SearchProviderMixin>( Base: T ) { abstract class SearchProviderMixin extends Base { abstract get type(): string; - @observable isOpen = this.openByDefault; - - @action - toggleOpen() { - this.isOpen = !this.isOpen; - } @action search(searchText: string): SearchProviderResults { @@ -69,27 +57,3 @@ namespace SearchProviderMixin { } export default SearchProviderMixin; - -interface MapCenter { - longitude: number; - latitude: number; -} - -export function getMapCenter(terria: Terria): MapCenter { - const view = terria.currentViewer.getCurrentCameraView(); - if (view.position !== undefined) { - const cameraPositionCartographic = Ellipsoid.WGS84.cartesianToCartographic( - view.position - ); - return { - longitude: CesiumMath.toDegrees(cameraPositionCartographic.longitude), - latitude: CesiumMath.toDegrees(cameraPositionCartographic.latitude) - }; - } else { - const center = Rectangle.center(view.rectangle); - return { - longitude: CesiumMath.toDegrees(center.longitude), - latitude: CesiumMath.toDegrees(center.latitude) - }; - } -} diff --git a/lib/ModelMixins/SerchProvider/WebFeatureServiceSearchProviderMixin.ts b/lib/ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin.ts similarity index 97% rename from lib/ModelMixins/SerchProvider/WebFeatureServiceSearchProviderMixin.ts rename to lib/ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin.ts index c8c1a814835..56a1e276079 100644 --- a/lib/ModelMixins/SerchProvider/WebFeatureServiceSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin.ts @@ -10,12 +10,12 @@ import SearchProviderResults from "../../Models/SearchProvider/SearchProviderRes import SearchResult from "../../Models/SearchProvider/SearchResult"; import xml2json from "../../ThirdParty/xml2json"; import WebFeatureServiceSearchProviderTraits from "../../Traits/SearchProvider/WebFeatureServiceSearchProviderTraits"; -import SearchProviderMixin from "./SearchProviderMixin"; +import LocationSearchProviderMixin from "./LocationSearchProviderMixin"; function WebFeatureServiceSearchProviderMixin< T extends Constructor> >(Base: T) { - abstract class WebFeatureServiceSearchProviderMixin extends SearchProviderMixin( + abstract class WebFeatureServiceSearchProviderMixin extends LocationSearchProviderMixin( Base ) { protected abstract featureToSearchResultFunction: ( diff --git a/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts b/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts index 2b4d2996215..986f781134d 100644 --- a/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts +++ b/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts @@ -1,6 +1,6 @@ import WebFeatureServiceSearchProviderTraits from "../../Traits/SearchProvider/WebFeatureServiceSearchProviderTraits"; import CreateModel from "../CreateModel"; -import WebFeatureServiceSearchProviderMixin from "../../ModelMixins/SerchProvider/WebFeatureServiceSearchProviderMixin"; +import WebFeatureServiceSearchProviderMixin from "../../ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin"; import SearchResult from "./SearchResult"; const featureCodesToNamesMap = new Map([ diff --git a/lib/Models/SearchProvider/BingMapsSearchProvider.ts b/lib/Models/SearchProvider/BingMapsSearchProvider.ts index 7adffd9947c..e910f0950ed 100644 --- a/lib/Models/SearchProvider/BingMapsSearchProvider.ts +++ b/lib/Models/SearchProvider/BingMapsSearchProvider.ts @@ -4,9 +4,9 @@ import defined from "terriajs-cesium/Source/Core/defined"; import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; import Resource from "terriajs-cesium/Source/Core/Resource"; import loadJsonp from "../../Core/loadJsonp"; -import SearchProviderMixin, { +import LocationSearchProviderMixin, { getMapCenter -} from "../../ModelMixins/SerchProvider/SearchProviderMixin"; +} from "../../ModelMixins/SearchProvider/LocationSearchProviderMixin"; import BingMapsSearchProviderTraits from "../../Traits/SearchProvider/BingMapsSearchProviderTraits"; import CreateModel from "../CreateModel"; import SearchProviderResults from "./SearchProviderResults"; @@ -14,7 +14,7 @@ import SearchResult from "./SearchResult"; import CommonStrata from "./../CommonStrata"; import Terria from "../Terria"; -export default class BingMapsSearchProvider extends SearchProviderMixin( +export default class BingMapsSearchProvider extends LocationSearchProviderMixin( CreateModel(BingMapsSearchProviderTraits) ) { static readonly type = "bing-maps-search-provider"; diff --git a/lib/Models/SearchProvider/CatalogSearchProvider.ts b/lib/Models/SearchProvider/CatalogSearchProvider.ts index 946b2a4ec67..4bdafa81e5d 100644 --- a/lib/Models/SearchProvider/CatalogSearchProvider.ts +++ b/lib/Models/SearchProvider/CatalogSearchProvider.ts @@ -4,7 +4,7 @@ import ReferenceMixin from "../../ModelMixins/ReferenceMixin"; import CatalogSearchProviderTraits from "../../Traits/SearchProvider/CatalogSearchProviderTraits"; import CreateModel from "../CreateModel"; import Terria from "../Terria"; -import SearchProviderMixin from "../../ModelMixins/SerchProvider/SearchProviderMixin"; +import SearchProviderMixin from "../../ModelMixins/SearchProvider/SearchProviderMixin"; import SearchProviderResults from "./SearchProviderResults"; import SearchResult from "./SearchResult"; diff --git a/lib/Models/SearchProvider/SearchProviderResults.ts b/lib/Models/SearchProvider/SearchProviderResults.ts index 91946a31771..09fcc1aafe5 100644 --- a/lib/Models/SearchProvider/SearchProviderResults.ts +++ b/lib/Models/SearchProvider/SearchProviderResults.ts @@ -1,7 +1,7 @@ import { observable } from "mobx"; import SearchResult from "./SearchResult"; import { IPromiseBasedObservable, fromPromise } from "mobx-utils"; -import SearchProviderMixin from "../../ModelMixins/SerchProvider/SearchProviderMixin"; +import SearchProviderMixin from "../../ModelMixins/SearchProvider/SearchProviderMixin"; export default class SearchProviderResults { @observable results: SearchResult[] = []; @@ -11,8 +11,9 @@ export default class SearchProviderResults { Promise.resolve() ); - constructor(readonly searchProvider: SearchProviderMixin) { - } + constructor( + readonly searchProvider: SearchProviderMixin.SearchProviderMixin + ) {} get isSearching() { return this.resultsCompletePromise.state === "pending"; diff --git a/lib/Models/SearchProvider/StubSearchProvider.ts b/lib/Models/SearchProvider/StubSearchProvider.ts index 0737ffad5ff..28745157278 100644 --- a/lib/Models/SearchProvider/StubSearchProvider.ts +++ b/lib/Models/SearchProvider/StubSearchProvider.ts @@ -1,6 +1,6 @@ import LocationSearchProviderTraits from "./../../Traits/SearchProvider/LocationSearchProviderTraits"; import primitiveTrait from "./../../Traits/primitiveTrait"; -import SearchProviderMixin from "../../ModelMixins/SerchProvider/SearchProviderMixin"; +import SearchProviderMixin from "../../ModelMixins/SearchProvider/SearchProviderMixin"; import CreateModel from "../CreateModel"; import SearchProviderResults from "./SearchProviderResults"; diff --git a/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts b/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts index bfbd63735ab..19474f3f14c 100644 --- a/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts +++ b/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts @@ -47,7 +47,7 @@ export default function upsertSearchProviderFromJson( model?.terria.addSearchProvider(model); } - addDefaultTraits(model); + setDefaultTraits(model); try { updateModelFromJson(model, stratumName, json); @@ -58,7 +58,7 @@ export default function upsertSearchProviderFromJson( } } -function addDefaultTraits(model: BaseModel) { +function setDefaultTraits(model: BaseModel) { const terria = model.terria; model.setTrait( diff --git a/lib/Models/Terria.ts b/lib/Models/Terria.ts index f80853b2cad..ae40c94569b 100644 --- a/lib/Models/Terria.ts +++ b/lib/Models/Terria.ts @@ -246,6 +246,10 @@ interface SearchBar { * Minimum number of characters to start search. */ minCharacters: number; + /** + * Bounding box limits for the search results. + */ + boundingBoxLimit?: number[]; /** * Array of search providers to be used. */ @@ -401,18 +405,8 @@ export default class Terria { type: "bing-maps-search-provider", name: "translate#viewModels.searchLocations", url: "https://dev.virtualearth.net/", - flightDurationSeconds: 1.5 - }, - { - id: "search-provider/australian-gazetteer", - type: "australian-gazetteer-search-provider", - name: "translate#viewModels.searchPlaceNames", - url: - "http://services.ga.gov.au/gis/services/Australian_Gazetteer/MapServer/WFSServer", - searchPropertyName: "Australian_Gazetteer:NameU", - searchPropertyTypeName: "Australian_Gazetteer:Gazetteer_of_Australia", flightDurationSeconds: 1.5, - minCharacters: 3 + isOpen: true } ] } diff --git a/lib/ReactViewModels/SearchState.ts b/lib/ReactViewModels/SearchState.ts index a12ebd79085..fc7373547e0 100644 --- a/lib/ReactViewModels/SearchState.ts +++ b/lib/ReactViewModels/SearchState.ts @@ -6,10 +6,11 @@ import { reaction } from "mobx"; import filterOutUndefined from "../Core/filterOutUndefined"; +import LocationSearchProviderMixin from "../ModelMixins/SearchProvider/LocationSearchProviderMixin"; +import SearchProviderMixin from "../ModelMixins/SearchProvider/SearchProviderMixin"; import CatalogSearchProvider from "../Models/SearchProvider/CatalogSearchProvider"; import SearchProviderResults from "../Models/SearchProvider/SearchProviderResults"; import Terria from "../Models/Terria"; -import SearchProviderMixin from "../ModelMixins/SerchProvider/SearchProviderMixin"; interface SearchStateOptions { terria: Terria; @@ -22,7 +23,7 @@ export default class SearchState { catalogSearchProvider: SearchProviderMixin.SearchProviderMixin | undefined; @observable - locationSearchProviders: SearchProviderMixin.SearchProviderMixin[]; + locationSearchProviders: LocationSearchProviderMixin.LocationSearchProviderMixin[]; @observable catalogSearchText: string = ""; @observable isWaitingToStartCatalogSearch: boolean = false; diff --git a/lib/ReactViews/Loader.tsx b/lib/ReactViews/Loader.tsx index a8a6c816fa0..0d5a62f1d50 100644 --- a/lib/ReactViews/Loader.tsx +++ b/lib/ReactViews/Loader.tsx @@ -10,11 +10,19 @@ interface PropsType extends WithTranslation { message?: string; boxProps?: any; textProps?: any; + hideMessage?: boolean; t: TFunction; [spread: string]: any; } const Loader: React.FC = (props: PropsType) => { - const { message, t, boxProps, textProps, ...rest }: PropsType = props; + const { + message, + t, + boxProps, + textProps, + hideMessage, + ...rest + }: PropsType = props; return ( = (props: PropsType) => { {...rest} /> - {message || t("loader.loadingMessage")} + {!hideMessage && (message || t("loader.loadingMessage"))} ); diff --git a/lib/ReactViews/Search/LocationSearchResults.jsx b/lib/ReactViews/Search/LocationSearchResults.jsx deleted file mode 100644 index 54c1d7320db..00000000000 --- a/lib/ReactViews/Search/LocationSearchResults.jsx +++ /dev/null @@ -1,193 +0,0 @@ -/** - Initially this was written to support various location search providers in master, - however we only have a single location provider at the moment, and how we merge - them in the new design is yet to be resolved, see: - https://github.com/TerriaJS/nsw-digital-twin/issues/248#issuecomment-599919318 - */ - -import { observer } from "mobx-react"; -import React from "react"; -import createReactClass from "create-react-class"; -import styled from "styled-components"; -import PropTypes from "prop-types"; -import { withTranslation } from "react-i18next"; -import SearchHeader from "./SearchHeader"; -import SearchResult from "./SearchResult"; -import classNames from "classnames"; -import Styles from "./location-search-result.scss"; -import isDefined from "../../Core/isDefined"; - -import Icon, { StyledIcon } from "../Icon"; -// import Box, { BoxSpan } from "../../Styled/Box"; -import { BoxSpan } from "../../Styled/Box"; -import Text, { TextSpan } from "../../Styled/Text"; - -import { RawButton } from "../../Styled/Button"; - -const RawButtonAndHighlight = styled(RawButton)` - ${p => ` - &:hover, &:focus { - background-color: ${p.theme.greyLighter}; - ${StyledIcon} { - fill-opacity: 1; - } - }`} -`; - -const MAX_RESULTS_BEFORE_TRUNCATING = 5; - -const LocationSearchResults = observer( - createReactClass({ - displayName: "LocationSearchResults", - - propTypes: { - viewState: PropTypes.object.isRequired, - isWaitingForSearchToStart: PropTypes.bool, - terria: PropTypes.object.isRequired, - search: PropTypes.object.isRequired, - onLocationClick: PropTypes.func.isRequired, - theme: PropTypes.string, - locationSearchText: PropTypes.string, - t: PropTypes.func.isRequired - }, - - getInitialState() { - return { - isOpen: true, - isExpanded: false - }; - }, - - getDefaultProps() { - return { - theme: "light" - }; - }, - - toggleIsOpen() { - this.setState({ - isOpen: !this.state.isOpen - }); - }, - - toggleExpand() { - this.setState({ - isExpanded: !this.state.isExpanded - }); - }, - - renderResultsFooter() { - const { t } = this.props; - if (this.state.isExpanded) { - return t("search.viewLess", { - name: this.props.search.searchProvider.name - }); - } - return t("search.viewMore", { - name: this.props.search.searchProvider.name - }); - }, - - render() { - const search = this.props.search; - const { isOpen, isExpanded } = this.state; - const searchProvider = search.searchProvider; - const locationSearchBoundingBox = this.props.terria.configParameters - .locationSearchBoundingBox; - - const validResults = isDefined(locationSearchBoundingBox) - ? search.results.filter(function(r) { - return ( - r.location.longitude > locationSearchBoundingBox[0] && - r.location.longitude < locationSearchBoundingBox[2] && - r.location.latitude > locationSearchBoundingBox[1] && - r.location.latitude < locationSearchBoundingBox[3] - ); - }) - : search.results; - - const results = - validResults.length > MAX_RESULTS_BEFORE_TRUNCATING - ? isExpanded - ? validResults - : validResults.slice(0, MAX_RESULTS_BEFORE_TRUNCATING) - : validResults; - - return ( -
- {/* */} - - - {`${search.searchProvider.name} (${validResults?.length})`} - - - - - -
    - {results.map((result, i) => ( - - ))} -
- {isOpen && validResults.length > MAX_RESULTS_BEFORE_TRUNCATING && ( - - - - {this.renderResultsFooter()} - - - - )} -
-
- ); - } - }) -); - -module.exports = withTranslation()(LocationSearchResults); diff --git a/lib/ReactViews/Search/LocationSearchResults.tsx b/lib/ReactViews/Search/LocationSearchResults.tsx new file mode 100644 index 00000000000..1ad36202da2 --- /dev/null +++ b/lib/ReactViews/Search/LocationSearchResults.tsx @@ -0,0 +1,204 @@ +/** + Initially this was written to support various location search providers in master, + however we only have a single location provider at the moment, and how we merge + them in the new design is yet to be resolved, see: + https://github.com/TerriaJS/nsw-digital-twin/issues/248#issuecomment-599919318 + */ + +import { observable, computed, action } from "mobx"; +import { observer } from "mobx-react"; +import React from "react"; +import { + useTranslation, + withTranslation, + WithTranslation +} from "react-i18next"; +import styled, { DefaultTheme } from "styled-components"; +import isDefined from "../../Core/isDefined"; +import Terria from "../../Models/Terria"; +import ViewState from "../../ReactViewModels/ViewState"; +import Ul from "../../Styled/List"; +import Icon, { StyledIcon } from "../Icon"; +import LocationSearchProviderMixin from "./../../ModelMixins/SearchProvider/LocationSearchProviderMixin"; +import SearchProviderResults from "../../Models/SearchProvider/SearchProviderResults"; +import SearchHeader from "./SearchHeader"; +import SearchResult from "./SearchResult"; +import Loader from "../Loader"; +const BoxSpan: any = require("../../Styled/Box").BoxSpan; +const Box: any = require("../../Styled/Box").default; +const Text: any = require("../../Styled/Text").default; +const TextSpan: any = require("../../Styled/Text").TextSpan; +const RawButton: any = require("../../Styled/Button").RawButton; + +const RawButtonAndHighlight = styled(RawButton)` + ${p => ` + &:hover, &:focus { + background-color: ${p.theme.greyLighter}; + ${StyledIcon} { + fill-opacity: 1; + } + }`} +`; + +interface PropsType extends WithTranslation { + viewState: ViewState; + isWaitingForSearchToStart: boolean; + terria: Terria; + search: SearchProviderResults; + onLocationClick: () => void; + theme: DefaultTheme; + locationSearchText: string; +} + +@observer +class LocationSearchResults extends React.Component { + @observable isExpanded = false; + + @action.bound + toggleExpand() { + this.isExpanded = !this.isExpanded; + } + + @computed + get validResults() { + const { search, terria } = this.props; + const locationSearchBoundingBox = + terria.configParameters.searchBar.boundingBoxLimit; + const validResults = isDefined(locationSearchBoundingBox) + ? search.results.filter(function(r: any) { + return ( + r.location.longitude > locationSearchBoundingBox[0] && + r.location.longitude < locationSearchBoundingBox[2] && + r.location.latitude > locationSearchBoundingBox[1] && + r.location.latitude < locationSearchBoundingBox[3] + ); + }) + : search.results; + return validResults; + } + + render() { + const { search } = this.props; + const searchProvider: LocationSearchProviderMixin.LocationSearchProviderMixin = search.searchProvider as any; + + const maxResults = searchProvider.recommendedListLength || 5; + const validResults = this.validResults; + const results = + validResults.length > maxResults + ? this.isExpanded + ? validResults + : validResults.slice(0, maxResults) + : validResults; + const isOpen = searchProvider.isOpen; + return ( + + searchProvider.toggleOpen()} + > + + + + + + + {isOpen && ( + <> + +
    + {results.map((result: any, i: number) => ( + + ))} +
+ {validResults.length > maxResults && ( + + + + + + + + )} + + )} +
+
+ ); + } +} + +interface SearchResultsFooterProps { + isExpanded: boolean; + name: string; +} + +const SearchResultsFooter: React.FC = ( + props: SearchResultsFooterProps +) => { + const { t } = useTranslation(); + if (props.isExpanded) { + return t("search.viewLess", { + name: props.name + }); + } + return t("search.viewMore", { + name: props.name + }); +}; + +interface NameWithLoaderProps { + name: string; + length?: number; + isOpen: boolean; + search: SearchProviderResults; + isWaitingForSearchToStart: boolean; +} + +const NameWithLoader: React.FC = observer( + (props: NameWithLoaderProps) => ( + + + {`${props.name} (${props.length || + 0})`} + + {!props.isOpen && + (props.search.isSearching || props.isWaitingForSearchToStart) && ( + + )} + + ) +); +export default withTranslation()(LocationSearchResults); diff --git a/lib/ReactViews/Search/SearchBox.jsx b/lib/ReactViews/Search/SearchBox.jsx index 0027ba02355..74ccf71f097 100644 --- a/lib/ReactViews/Search/SearchBox.jsx +++ b/lib/ReactViews/Search/SearchBox.jsx @@ -1,12 +1,12 @@ -import React from "react"; -import PropTypes from "prop-types"; import createReactClass from "create-react-class"; import debounce from "lodash-es/debounce"; -import Icon, { StyledIcon } from "../Icon"; +import PropTypes from "prop-types"; +import React from "react"; import styled, { withTheme } from "styled-components"; import Box, { BoxSpan } from "../../Styled/Box"; -import Text from "../../Styled/Text"; import { RawButton } from "../../Styled/Button"; +import Text from "../../Styled/Text"; +import Icon, { StyledIcon } from "../Icon"; const SearchInput = styled.input` box-sizing: border-box; diff --git a/lib/ReactViews/Search/SearchBoxAndResults.jsx b/lib/ReactViews/Search/SearchBoxAndResults.jsx index 1bd33992a9c..6765ebe623c 100644 --- a/lib/ReactViews/Search/SearchBoxAndResults.jsx +++ b/lib/ReactViews/Search/SearchBoxAndResults.jsx @@ -1,23 +1,19 @@ -import React from "react"; -import { removeMarker } from "../../Models/LocationMarkerUtils"; import { reaction, runInAction } from "mobx"; -import { Trans } from "react-i18next"; -import PropTypes from "prop-types"; import { observer } from "mobx-react"; +import PropTypes from "prop-types"; +import React from "react"; +import { Trans } from "react-i18next"; import styled from "styled-components"; -// import { ThemeContext } from "styled-components"; - -import SearchBox from "../Search/SearchBox"; -// import SidebarSearch from "../Search/SidebarSearch"; -import LocationSearchResults from "../Search/LocationSearchResults"; -import Icon, { StyledIcon } from "../Icon"; - +import { addMarker, removeMarker } from "../../Models/LocationMarkerUtils"; import Box from "../../Styled/Box"; -import Text from "../../Styled/Text"; -import Spacing from "../../Styled/Spacing"; import { RawButton } from "../../Styled/Button"; - -import { addMarker } from "../../Models/LocationMarkerUtils"; +import Spacing from "../../Styled/Spacing"; +import Text from "../../Styled/Text"; +import Icon, { StyledIcon } from "../Icon"; +// import SidebarSearch from "../Search/SidebarSearch"; +import LocationSearchResults from "./LocationSearchResults"; +// import { ThemeContext } from "styled-components"; +import SearchBox from "./SearchBox"; export function SearchInDataCatalog({ viewState, handleClick }) { const locationSearchText = viewState.searchState.locationSearchText; diff --git a/lib/ReactViews/Search/SearchHeader.jsx b/lib/ReactViews/Search/SearchHeader.jsx deleted file mode 100644 index bbc5f03260c..00000000000 --- a/lib/ReactViews/Search/SearchHeader.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import Loader from "../Loader"; -import { observer } from "mobx-react"; -import React from "react"; -import createReactClass from "create-react-class"; -import PropTypes from "prop-types"; -import Styles from "./search-header.scss"; - -/** Renders either a loader or a message based off search state. */ -export default observer( - createReactClass({ - displayName: "SearchHeader", - - propTypes: { - searchResults: PropTypes.object.isRequired, - isWaitingForSearchToStart: PropTypes.bool - }, - - render() { - if ( - this.props.searchResults.isSearching || - this.props.isWaitingForSearchToStart - ) { - return ( -
- -
- ); - } else if (this.props.searchResults.message) { - return ( -
- {this.props.searchResults.message} -
- ); - } else { - return null; - } - } - }) -); diff --git a/lib/ReactViews/Search/SearchHeader.tsx b/lib/ReactViews/Search/SearchHeader.tsx new file mode 100644 index 00000000000..5ead1157445 --- /dev/null +++ b/lib/ReactViews/Search/SearchHeader.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import Loader from "../Loader"; +import { observer } from "mobx-react"; +const Text = require("../../Styled/Text").default; +const BoxSpan = require("../../Styled/Box").BoxSpan; + +interface SearchHeaderProps { + searchResults: { [key: string]: any }; + isWaitingForSearchToStart: boolean; +} + +const SearchHeader: React.FC = observer( + (props: SearchHeaderProps) => { + if (props.searchResults.isSearching || props.isWaitingForSearchToStart) { + return ( +
+ +
+ ); + } else if (props.searchResults.message) { + return ( + + {props.searchResults.message} + + ); + } else { + return null; + } + } +); + +export default SearchHeader; diff --git a/lib/ReactViews/Search/SearchResult.jsx b/lib/ReactViews/Search/SearchResult.jsx deleted file mode 100644 index 491feb2ffcc..00000000000 --- a/lib/ReactViews/Search/SearchResult.jsx +++ /dev/null @@ -1,120 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import styled, { withTheme } from "styled-components"; -import createReactClass from "create-react-class"; -import Icon, { StyledIcon } from "../Icon"; - -import Box, { BoxSpan } from "../../Styled/Box"; -import { RawButton } from "../../Styled/Button"; -import { TextSpan } from "../../Styled/Text"; -import Hr from "../../Styled/Hr"; -import Spacing, { SpacingSpan } from "../../Styled/Spacing"; - -import highlightKeyword from "../ReactViewHelpers/highlightKeyword"; - -// Not sure how to generalise this or if it should be kept in stlyed/Button.jsx - -// Initially had this as border bottom on the button, but need a HR given it's not a full width border -// // ${p => !p.isLastResult && `border-bottom: 1px solid ${p.theme.greyLighter};`} -const RawButtonAndHighlight = styled(RawButton)` - ${p => ` - &:hover, &:focus { - background-color: ${p.theme.greyLighter}; - ${StyledIcon} { - fill-opacity: 1; - } - }`} -`; - -// A Location item when doing Bing map searvh or Gazetter search -export const SearchResult = createReactClass({ - propTypes: { - name: PropTypes.string.isRequired, - clickAction: PropTypes.func.isRequired, - isLastResult: PropTypes.bool, - locationSearchText: PropTypes.string, - icon: PropTypes.string, - theme: PropTypes.object, - searchResultTheme: PropTypes.string - }, - - getDefaultProps() { - return { - icon: false, - searchResultTheme: "light" - }; - }, - - render() { - const { - searchResultTheme, - theme, - name, - locationSearchText, - icon - // isLastResult - } = this.props; - const isDarkTheme = searchResultTheme === "dark"; - const isLightTheme = searchResultTheme === "light"; - const highlightedResultName = highlightKeyword(name, locationSearchText); - return ( -
  • - - - {/* {!isLastResult && ( */} - - -
    - -
    - {/* )} */} - - - {icon && ( - - )} - - - - {highlightedResultName} - - - - - -
    -
    -
  • - ); - } -}); - -export default withTheme(SearchResult); diff --git a/lib/ReactViews/Search/SearchResult.tsx b/lib/ReactViews/Search/SearchResult.tsx new file mode 100644 index 00000000000..a431a4278cc --- /dev/null +++ b/lib/ReactViews/Search/SearchResult.tsx @@ -0,0 +1,107 @@ +import React from "react"; +import styled, { useTheme } from "styled-components"; +import { Li } from "../../Styled/List"; +import Icon, { StyledIcon } from "../Icon"; +import highlightKeyword from "../ReactViewHelpers/highlightKeyword"; + +const Box = require("../../Styled/Box").default; +const BoxSpan = require("../../Styled/Box").BoxSpan; +const TextSpan = require("../../Styled/Text").TextSpan; +const RawButton = require("../../Styled/Button").RawButton; +const Spacing = require("../../Styled/Spacing").default; +const SpacingSpan = require("../../Styled/Spacing").SpacingSpan; +const Hr = require("../../Styled/Hr").default; + +// Not sure how to generalise this or if it should be kept in stlyed/Button.jsx + +// Initially had this as border bottom on the button, but need a HR given it's not a full width border +// // ${p => !p.isLastResult && `border-bottom: 1px solid ${p.theme.greyLighter};`} +const RawButtonAndHighlight = styled(RawButton)` + ${p => ` + &:hover, &:focus { + background-color: ${p.theme.greyLighter}; + ${StyledIcon} { + fill-opacity: 1; + } + }`} +`; + +interface SearchResultProps { + name: string; + clickAction(): void; + isLastResult: boolean; + locationSearchText: string; + icon: string; +} + +const SearchResult: React.FC = ( + props: SearchResultProps +) => { + const theme = useTheme(); + const highlightedResultName = highlightKeyword( + props.name, + props.locationSearchText + ); + const isLightTheme = true; + const isDarkTheme = false; + return ( +
  • + + + {/* {!isLastResult && ( */} + + +
    + +
    + {/* )} */} + + + {props.icon && ( + + )} + + + + {highlightedResultName} + + + + + +
    +
    +
  • + ); +}; + +export default SearchResult; diff --git a/lib/ReactViews/Search/location-search-result.scss b/lib/ReactViews/Search/location-search-result.scss deleted file mode 100644 index b6455bd65e3..00000000000 --- a/lib/ReactViews/Search/location-search-result.scss +++ /dev/null @@ -1,83 +0,0 @@ -@import "../../Sass/common/mixins"; -@import "~terriajs-variables"; - -.heading { - composes: btn from "../../Sass/common/_buttons.scss"; - padding: $padding; - position: relative; - width: 100%; - text-align: left; - font-weight: bold; - &, - &:hover, - &:focus { - box-shadow: inset 0 -1px 0 0 rgba(255, 255, 255, 0.15); - } - @media (min-width: $md) { - color: $text-light; - } - svg { - height: 10px; - width: 10px; - position: absolute; - right: $padding + $padding-small; - top: $padding + $padding-small; - } -} - -.footer { - composes: btn from "../../Sass/common/_buttons.scss"; - padding: $padding; - font-size: 12px; - position: relative; - width: 100%; - text-align: left; - box-shadow: none; - @media (min-width: $md) { - color: $text-light; - } - svg { - height: 20px; - width: 20px; - position: absolute; - right: $padding; - top: $padding; - } -} - -.provider-result { - margin-top: $padding-small; - @media (min-width: $md) { - color: $text-darker; - } - // background-color: $dark-with-overlay; - .items { - display: none; - } -} - -// on mobile, we don't want the gap between search results -.light { - // background: #fff; - margin-top: 0; - svg { - // fill: #000; - } -} - -.dark { - svg { - // fill: #ffffff; - } -} - -.isOpen { - .items { - display: block; - } -} - -.items { - composes: clearfix from "../../Sass/common/_base.scss"; - composes: list-reset from "../../Sass/common/_base.scss"; -} diff --git a/lib/ReactViews/Search/location-search-result.scss.d.ts b/lib/ReactViews/Search/location-search-result.scss.d.ts deleted file mode 100644 index d53b3b8dd6a..00000000000 --- a/lib/ReactViews/Search/location-search-result.scss.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -// This file is automatically generated. -// Please do not change this file! -interface CssExports { - 'footer': string; - 'heading': string; - 'isOpen': string; - 'items': string; - 'light': string; - 'provider-result': string; - 'providerResult': string; -} -declare var cssExports: CssExports; -export = cssExports; diff --git a/lib/ReactViews/Search/search-box.scss b/lib/ReactViews/Search/search-box.scss deleted file mode 100644 index 204fec49fd4..00000000000 --- a/lib/ReactViews/Search/search-box.scss +++ /dev/null @@ -1,60 +0,0 @@ -@import "~terriajs-variables"; -@import "../../Sass/common/mixins"; - -.formLabel { - position: absolute; - svg { - // height: $input-height; - // width: $input-height; - height: 20px; - width: 20px; - fill: $charcoal-grey; - padding: $padding; - fill-opacity: 0.5; - } -} - -.searchField { - composes: field from "../../Sass/common/_form.scss"; - font-family: $font-base; -} - -.searchData { - position: relative; - width: 100%; -} - -input[type="text"].searchField { - padding-left: $input-height; - padding-right: $input-height; - color: $text-dark; - width: 100%; - overflow: hidden; - border-color: transparent; // if you need to remove borders, always use transparent "X"px borders instead of 0 borders for a11y - @include placeholder { - text-align: center; - @include transition(all, 0.25s, linear); - } - - &:focus { - @include placeholder { - padding-left: 0; - } - } -} - -.searchClear { - composes: btn from "../../Sass/common/_buttons.scss"; - right: 0px; - top: 0px; - position: absolute; - height: $input-height; - width: $input-height; - svg { - height: 15px; - width: 15px; - margin: 0 auto; - fill: $charcoal-grey; - fill-opacity: 0.5; - } -} diff --git a/lib/ReactViews/Search/search-box.scss.d.ts b/lib/ReactViews/Search/search-box.scss.d.ts deleted file mode 100644 index d7e28504b25..00000000000 --- a/lib/ReactViews/Search/search-box.scss.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -// This file is automatically generated. -// Please do not change this file! -interface CssExports { - 'formLabel': string; - 'searchClear': string; - 'searchData': string; - 'searchField': string; -} -declare var cssExports: CssExports; -export = cssExports; diff --git a/lib/ReactViews/Search/search-result.scss b/lib/ReactViews/Search/search-result.scss deleted file mode 100644 index 618d47f3cce..00000000000 --- a/lib/ReactViews/Search/search-result.scss +++ /dev/null @@ -1,95 +0,0 @@ -@import "~terriajs-variables"; - -.search-result { - span { - overflow-wrap: break-word; - word-wrap: break-word; - } -} - -.search-result.light .btn { - // border-bottom: 1px solid rgba($grey, 0.4); - background: transparent; - &:hover { - // border-bottom: 1px solid $color-primary; - color: $color-primary; - } - svg { - fill: #000; - } -} - -.search-result.dark .btn { - color: #fff; - border: 0; - box-shadow: inset 0 -1px 0 0 rgba(255, 255, 255, 0.15); - margin-bottom: 0; - svg { - fill-opacity: 0.5; - } - &:hover { - svg { - fill: #fff; - } - } -} - -.icon { - position: absolute; - top: 5px; - bottom: 0; - // TODO: better icon management - left: -3px; - height: 21px; - width: 17px; - margin: 7px 0; -} - -.resultName { - padding-left: 20px; -} - -.arrow-icon { - position: absolute; - top: 5px; - bottom: 0; - right: 1px; - height: 10px; - width: 10px; - width: 14px; - margin: 8px; - // margin: $padding; - - svg { - fill-opacity: 0; - } -} - -.btn { - composes: btn from "../../Sass/common/_buttons.scss"; - &.btn { - position: relative; - padding: $padding; - padding-left: 0; - margin-bottom: $padding-mini; - padding-right: $padding; - width: 100%; - border: 1px solid transparent; - } - &:hover { - background-color: $grey-lighter; - .arrow-icon svg { - fill-opacity: 1; - } - } - &.btnLocationName { - padding: $padding $padding * 3; - } -} -.btnWithBorderBottom { - &.btnWithBorderBottom { - @media (min-width: $sm) { - border-bottom: 1px solid $grey; - } - } -} diff --git a/lib/ReactViews/Search/search-result.scss.d.ts b/lib/ReactViews/Search/search-result.scss.d.ts deleted file mode 100644 index ba4c634fc8a..00000000000 --- a/lib/ReactViews/Search/search-result.scss.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -// This file is automatically generated. -// Please do not change this file! -interface CssExports { - 'arrow-icon': string; - 'arrowIcon': string; - 'btn': string; - 'btnLocationName': string; - 'btnWithBorderBottom': string; - 'dark': string; - 'icon': string; - 'light': string; - 'resultName': string; - 'search-result': string; - 'searchResult': string; -} -declare var cssExports: CssExports; -export = cssExports; diff --git a/lib/Styled/List.jsx b/lib/Styled/List.tsx similarity index 100% rename from lib/Styled/List.jsx rename to lib/Styled/List.tsx diff --git a/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts b/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts index 5067a14f335..06f73b469a2 100644 --- a/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts +++ b/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts @@ -17,7 +17,14 @@ export default class LocationSearchProviderTraits extends SearchProviderTraits { "True if the geocoder should query as the user types to autocomplete.", isNullable: true }) - autocomplete?: boolean; + autocomplete?: boolean = true; + + @primitiveTrait({ + type: "number", + name: "recommendedListLength", + description: "Maximum amount of entries in the suggestion list." + }) + recommendedListLength: number = 5; @primitiveTrait({ type: "number", @@ -25,7 +32,16 @@ export default class LocationSearchProviderTraits extends SearchProviderTraits { description: "Time to move to the result location.", isNullable: true }) - flightDurationSeconds?: number; + flightDurationSeconds?: number = 1.5; + + @primitiveTrait({ + type: "boolean", + name: "Is open", + description: + "True if the search results of this search provider are visible; otherwise, false.", + isNullable: true + }) + isOpen: boolean = true; } export class SearchProviderMapCenterTraits extends ModelTraits { diff --git a/lib/Traits/SearchProvider/SearchProviderTraits.ts b/lib/Traits/SearchProvider/SearchProviderTraits.ts index a023dfcb040..f19e6449184 100644 --- a/lib/Traits/SearchProvider/SearchProviderTraits.ts +++ b/lib/Traits/SearchProvider/SearchProviderTraits.ts @@ -9,14 +9,6 @@ export default class SearchProviderTraits extends ModelTraits { }) name: string = "unknown"; - @primitiveTrait({ - type: "boolean", - name: "Open by default", - description: "Wheter are this search provider results open by default", - isNullable: true - }) - openByDefault: boolean = true; - @primitiveTrait({ type: "number", name: "Minimum characters", From 030b913918ae35e216376b792737431da576dc3f Mon Sep 17 00:00:00 2001 From: zoran995 Date: Tue, 16 Feb 2021 11:57:18 +0100 Subject: [PATCH 09/62] restore australian-gazetteer-search-provider as example --- lib/Models/Terria.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/Models/Terria.ts b/lib/Models/Terria.ts index ae40c94569b..42d38bdee19 100644 --- a/lib/Models/Terria.ts +++ b/lib/Models/Terria.ts @@ -407,6 +407,19 @@ export default class Terria { url: "https://dev.virtualearth.net/", flightDurationSeconds: 1.5, isOpen: true + }, + { + id: "search-provider/australian-gazetteer", + type: "australian-gazetteer-search-provider", + name: "translate#viewModels.searchPlaceNames", + url: + "http://services.ga.gov.au/gis/services/Australian_Gazetteer/MapServer/WFSServer", + searchPropertyName: "Australian_Gazetteer:NameU", + searchPropertyTypeName: "Australian_Gazetteer:Gazetteer_of_Australia", + flightDurationSeconds: 1.5, + minCharacters: 3, + recommendedListLength: 3, + isOpen: false } ] } From e826dd66195167aba8cd4e01f99f5c56ab12727c Mon Sep 17 00:00:00 2001 From: zoran995 Date: Tue, 16 Feb 2021 13:29:30 +0100 Subject: [PATCH 10/62] move shoudRunSearch to search function so we don't have to implement it for every search provider --- lib/Language/en/translation.json | 2 ++ lib/ModelMixins/SearchProvider/SearchProviderMixin.ts | 10 +++++++++- lib/Models/SearchProvider/BingMapsSearchProvider.ts | 4 ---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/Language/en/translation.json b/lib/Language/en/translation.json index a4394b46fe2..ddd59794964 100644 --- a/lib/Language/en/translation.json +++ b/lib/Language/en/translation.json @@ -607,6 +607,8 @@ "viewModels": { "searchNoLocations": "Sorry, no locations match your search query.", "searchErrorOccurred": "An error occurred while searching. Please check your internet connection or try again later.", + "seachMinCharacters": "You need to enter minimum {{count}} character", + "seachMinCharacters_plural": "You need to enter minimum {{count}} characters", "searchAddresses": "Addresses", "searchPlaceNames": "Official Place Names", "searchNoPlaceNames": "Sorry, no official place names match your search query.", diff --git a/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts b/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts index cf3e9066936..fbadf029f90 100644 --- a/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts @@ -4,6 +4,7 @@ import Constructor from "../../Core/Constructor"; import Model from "../../Models/Model"; import SearchProviderResults from "../../Models/SearchProvider/SearchProviderResults"; import SearchProviderTraits from "../../Traits/SearchProvider/SearchProviderTraits"; +import i18next from "i18next"; type SearchProviderModel = Model; @@ -16,6 +17,13 @@ function SearchProviderMixin>( @action search(searchText: string): SearchProviderResults { const result = new SearchProviderResults(this); + if (!this.shouldRunSearch(searchText)) { + result.resultsCompletePromise = fromPromise(Promise.resolve()); + result.message = i18next.t("viewModels.seachMinCharacters", { + count: this.minCharacters + }); + return result; + } result.resultsCompletePromise = fromPromise( this.doSearch(searchText, result) ); @@ -27,7 +35,7 @@ function SearchProviderMixin>( results: SearchProviderResults ): Promise; - shouldRunSearch(searchText: string) { + private shouldRunSearch(searchText: string) { if ( searchText === undefined || /^\s*$/.test(searchText) || diff --git a/lib/Models/SearchProvider/BingMapsSearchProvider.ts b/lib/Models/SearchProvider/BingMapsSearchProvider.ts index e910f0950ed..4bbd5d2035d 100644 --- a/lib/Models/SearchProvider/BingMapsSearchProvider.ts +++ b/lib/Models/SearchProvider/BingMapsSearchProvider.ts @@ -52,10 +52,6 @@ export default class BingMapsSearchProvider extends LocationSearchProviderMixin( searchResults.results.length = 0; searchResults.message = undefined; - if (this.shouldRunSearch(searchText)) { - return Promise.resolve(); - } - this.terria.analytics.logEvent("search", "bing", searchText); const searchQuery = new Resource({ From e388d1e026f4bf4945614b9ca0607fe6c70390d9 Mon Sep 17 00:00:00 2001 From: zoran995 Date: Tue, 16 Feb 2021 13:30:42 +0100 Subject: [PATCH 11/62] remove autocomplete and sortByName for later implementation if needed --- lib/Models/Terria.ts | 15 ++------------- .../LocationSearchProviderTraits.ts | 9 --------- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/lib/Models/Terria.ts b/lib/Models/Terria.ts index 5347a8db651..fcbb4d3f385 100644 --- a/lib/Models/Terria.ts +++ b/lib/Models/Terria.ts @@ -223,7 +223,7 @@ interface ConfigParameters { interface SearchBar { /** * Input text field placeholder shown when no input has been given yet. The string is translateable. - * @default "search.placeholder" + * @default "translate#search.placeholder" */ placeholder: string; /** @@ -231,21 +231,11 @@ interface SearchBar { * @default 5 */ recommendedListLength: number; - /** - * Defines whether search results are to be sorted alphanumerically. - * @default true - */ - sortByName: boolean; /** * The duration of the camera flight to an entered location, in seconds. * @default 1.5 */ flightDurationSeconds: number; - /** - * True if the geocoder should query as the user types to autocomplete. - * @default true - */ - autocomplete: boolean; /** * Minimum number of characters to start search. */ @@ -400,9 +390,7 @@ export default class Terria { searchBar: { placeholder: "translate#search.placeholder", recommendedListLength: 5, - sortByName: true, flightDurationSeconds: 1.5, - autocomplete: true, minCharacters: 3, searchProviders: [ { @@ -411,6 +399,7 @@ export default class Terria { name: "translate#viewModels.searchLocations", url: "https://dev.virtualearth.net/", flightDurationSeconds: 1.5, + minCharacters: 5, isOpen: true }, { diff --git a/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts b/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts index 06f73b469a2..2893c7ea07f 100644 --- a/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts +++ b/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts @@ -10,15 +10,6 @@ export default class LocationSearchProviderTraits extends SearchProviderTraits { }) url: string = ""; - @primitiveTrait({ - type: "boolean", - name: "Open by default", - description: - "True if the geocoder should query as the user types to autocomplete.", - isNullable: true - }) - autocomplete?: boolean = true; - @primitiveTrait({ type: "number", name: "recommendedListLength", From 34570f3b64093d0f3fcbe4f2d4cdc48af1b0e51d Mon Sep 17 00:00:00 2001 From: zoran995 Date: Wed, 17 Feb 2021 17:55:24 +0100 Subject: [PATCH 12/62] make searchBar optional in config --- lib/Models/Terria.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Models/Terria.ts b/lib/Models/Terria.ts index fcbb4d3f385..c0b5eae898a 100644 --- a/lib/Models/Terria.ts +++ b/lib/Models/Terria.ts @@ -217,7 +217,7 @@ interface ConfigParameters { /** * The search bar allows requesting information from various search services at once. */ - searchBar: SearchBar; + searchBar?: SearchBar; } interface SearchBar { From 53c42107300eae7780630f088ffcd3eabf050e3a Mon Sep 17 00:00:00 2001 From: zoran995 Date: Wed, 17 Feb 2021 18:03:41 +0100 Subject: [PATCH 13/62] resolve issues with optional searchBar --- lib/ModelMixins/SearchProvider/SearchProviderMixin.ts | 2 +- .../WebFeatureServiceSearchProviderMixin.ts | 2 +- .../SearchProvider/upsertSearchProviderFromJson.ts | 6 +++--- lib/Models/Terria.ts | 2 +- lib/ReactViews/Search/LocationSearchResults.tsx | 4 ++-- .../LocationSearchProviderTraitsSpec.ts | 11 +++++++++++ 6 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 test/Traits/SearchProvider/LocationSearchProviderTraitsSpec.ts diff --git a/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts b/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts index fbadf029f90..606fc07735b 100644 --- a/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts @@ -42,7 +42,7 @@ function SearchProviderMixin>( (this.minCharacters && searchText.length < this.minCharacters) || (this.minCharacters === undefined && searchText.length < - this.terria.configParameters.searchBar.minCharacters) + this.terria.configParameters.searchBar!.minCharacters) ) { return false; } diff --git a/lib/ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin.ts b/lib/ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin.ts index 56a1e276079..dad3a5d4a16 100644 --- a/lib/ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin.ts @@ -213,7 +213,7 @@ function createZoomToFunction( const flightDurationSeconds: number = model.flightDurationSeconds || - model.terria.configParameters.searchBar.flightDurationSeconds; + model.terria.configParameters.searchBar!.flightDurationSeconds; return function() { model.terria.currentViewer.zoomTo(rectangle, flightDurationSeconds); diff --git a/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts b/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts index 19474f3f14c..4921a9c561d 100644 --- a/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts +++ b/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts @@ -64,18 +64,18 @@ function setDefaultTraits(model: BaseModel) { model.setTrait( CommonStrata.defaults, "flightDurationSeconds", - terria.configParameters.searchBar.flightDurationSeconds + terria.configParameters.searchBar!.flightDurationSeconds ); model.setTrait( CommonStrata.defaults, "minCharacters", - terria.configParameters.searchBar.minCharacters + terria.configParameters.searchBar!.minCharacters ); model.setTrait( CommonStrata.defaults, "recommendedListLength", - terria.configParameters.searchBar.recommendedListLength + terria.configParameters.searchBar!.recommendedListLength ); } diff --git a/lib/Models/Terria.ts b/lib/Models/Terria.ts index c0b5eae898a..6b96e90f3bd 100644 --- a/lib/Models/Terria.ts +++ b/lib/Models/Terria.ts @@ -730,7 +730,7 @@ export default class Terria { } }) .then(() => { - let searchProviders = this.configParameters.searchBar.searchProviders; + let searchProviders = this.configParameters.searchBar!.searchProviders; if (!isObservableArray(searchProviders)) throw new TerriaError({ sender: SearchProviderFactory, diff --git a/lib/ReactViews/Search/LocationSearchResults.tsx b/lib/ReactViews/Search/LocationSearchResults.tsx index 1ad36202da2..7b8e5bc2abd 100644 --- a/lib/ReactViews/Search/LocationSearchResults.tsx +++ b/lib/ReactViews/Search/LocationSearchResults.tsx @@ -62,8 +62,8 @@ class LocationSearchResults extends React.Component { @computed get validResults() { const { search, terria } = this.props; - const locationSearchBoundingBox = - terria.configParameters.searchBar.boundingBoxLimit; + const locationSearchBoundingBox = terria.configParameters.searchBar! + .boundingBoxLimit; const validResults = isDefined(locationSearchBoundingBox) ? search.results.filter(function(r: any) { return ( diff --git a/test/Traits/SearchProvider/LocationSearchProviderTraitsSpec.ts b/test/Traits/SearchProvider/LocationSearchProviderTraitsSpec.ts new file mode 100644 index 00000000000..781a510e90f --- /dev/null +++ b/test/Traits/SearchProvider/LocationSearchProviderTraitsSpec.ts @@ -0,0 +1,11 @@ +import Terria from "../../../lib/Models/Terria"; + +describe("LocationSearchProviderTraits", function() { + let terria: Terria; + beforeEach(async function() { + terria = new Terria({ + baseUrl: "./" + }); + //geoJsonCatalogItem = new GeoJsonCatalogItem("test", terria); + }); +}); From 5a580a3e539da3054de8ce5b98843a827c6fceba Mon Sep 17 00:00:00 2001 From: zoran995 Date: Wed, 17 Feb 2021 23:09:56 +0100 Subject: [PATCH 14/62] correct name of translation string --- lib/Language/en/translation.json | 4 ++-- lib/ModelMixins/SearchProvider/SearchProviderMixin.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Language/en/translation.json b/lib/Language/en/translation.json index ddd59794964..c289fc50858 100644 --- a/lib/Language/en/translation.json +++ b/lib/Language/en/translation.json @@ -607,8 +607,8 @@ "viewModels": { "searchNoLocations": "Sorry, no locations match your search query.", "searchErrorOccurred": "An error occurred while searching. Please check your internet connection or try again later.", - "seachMinCharacters": "You need to enter minimum {{count}} character", - "seachMinCharacters_plural": "You need to enter minimum {{count}} characters", + "searchMinCharacters": "You need to enter minimum {{count}} character", + "searchMinCharacters_plural": "You need to enter minimum {{count}} characters", "searchAddresses": "Addresses", "searchPlaceNames": "Official Place Names", "searchNoPlaceNames": "Sorry, no official place names match your search query.", diff --git a/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts b/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts index 606fc07735b..349b806a5fb 100644 --- a/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts @@ -19,7 +19,7 @@ function SearchProviderMixin>( const result = new SearchProviderResults(this); if (!this.shouldRunSearch(searchText)) { result.resultsCompletePromise = fromPromise(Promise.resolve()); - result.message = i18next.t("viewModels.seachMinCharacters", { + result.message = i18next.t("viewModels.searchMinCharacters", { count: this.minCharacters }); return result; From 894237bf3869e4231fe6edf2eaf30b717f07ae35 Mon Sep 17 00:00:00 2001 From: zoran995 Date: Thu, 18 Feb 2021 00:44:17 +0100 Subject: [PATCH 15/62] copy deprecationWarning from cesium, move default search providers array to config.json --- lib/Core/deprecationWarning.ts | 94 +++++++++++++++++++ lib/Language/languageHelpers.ts | 11 ++- lib/Models/Terria.ts | 44 ++++----- .../Search/LocationSearchResults.tsx | 4 +- 4 files changed, 119 insertions(+), 34 deletions(-) create mode 100644 lib/Core/deprecationWarning.ts diff --git a/lib/Core/deprecationWarning.ts b/lib/Core/deprecationWarning.ts new file mode 100644 index 00000000000..9b57e432663 --- /dev/null +++ b/lib/Core/deprecationWarning.ts @@ -0,0 +1,94 @@ +import DeveloperError from "terriajs-cesium/Source/Core/DeveloperError"; +import defined from "terriajs-cesium/Source/Core/defined"; +import defaultValue from "terriajs-cesium/Source/Core/defaultValue"; + +/** + * Port of Cesium's functions `deprecationWarning` and `oneTimeWarning`. + */ +const warnings: { [key: string]: boolean } = {}; + +/** + * Logs a one time message to the console. Use this function instead of + * console.log directly since this does not log duplicate messages + * unless it is called from multiple workers. + * + * @function oneTimeWarning + * + * @param {String} identifier The unique identifier for this warning. + * @param {String} [message=identifier] The message to log to the console. + * + * @example + * for(var i=0;i>includeStart('debug', pragmas.debug); + if (!defined(identifier)) { + throw new DeveloperError("identifier is required."); + } + //>>includeEnd('debug'); + + if (!defined(warnings[identifier])) { + warnings[identifier] = true; + console.warn(defaultValue(message, identifier)); + } +} + +/** + * Logs a deprecation message to the console. Use this function instead of + * console.log directly since this does not log duplicate messages + * unless it is called from multiple workers. + * + * @function deprecationWarning + * + * @param {String} identifier The unique identifier for this deprecated API. + * @param {String} message The message to log to the console. + * + * @example + * // Deprecated function or class + * function Foo() { + * deprecationWarning('Foo', 'Foo was deprecated in Cesium 1.01. It will be removed in 1.03. Use newFoo instead.'); + * // ... + * } + * + * // Deprecated function + * Bar.prototype.func = function() { + * deprecationWarning('Bar.func', 'Bar.func() was deprecated in Cesium 1.01. It will be removed in 1.03. Use Bar.newFunc() instead.'); + * // ... + * }; + * + * // Deprecated property + * Object.defineProperties(Bar.prototype, { + * prop : { + * get : function() { + * deprecationWarning('Bar.prop', 'Bar.prop was deprecated in Cesium 1.01. It will be removed in 1.03. Use Bar.newProp instead.'); + * // ... + * }, + * set : function(value) { + * deprecationWarning('Bar.prop', 'Bar.prop was deprecated in Cesium 1.01. It will be removed in 1.03. Use Bar.newProp instead.'); + * // ... + * } + * } + * }); + * + * @private + */ +function deprecationWarning(identifier: string, message: string) { + //>>includeStart('debug', pragmas.debug); + if (!defined(identifier) || !defined(message)) { + throw new DeveloperError("identifier and message are required."); + } + //>>includeEnd('debug'); + + oneTimeWarning(identifier, message); +} + +export default deprecationWarning; diff --git a/lib/Language/languageHelpers.ts b/lib/Language/languageHelpers.ts index 01a7a30c3ca..c8f32ea0c9d 100644 --- a/lib/Language/languageHelpers.ts +++ b/lib/Language/languageHelpers.ts @@ -1,5 +1,5 @@ import i18next from "i18next"; - +import deprecationWarning from "../Core/deprecationWarning"; /** * Takes a given string and translates it if it exists, otherwise return */ @@ -9,12 +9,15 @@ export function useTranslationIfExists(keyOrString: string) { return i18next.exists(translationKey) ? i18next.t(translationKey) : translationKey; - } else { - console.warn( - "using translation key within config won't work in future unless you prefix it with `translate#`" + } else if (keyOrString) { + deprecationWarning( + "useTranslationIfExists", + "Using translation key inside config without `translate#` prefix is deprecated" ); // after the depreaction // return keyOrString; return i18next.exists(keyOrString) ? i18next.t(keyOrString) : keyOrString; + } else { + return keyOrString; } } diff --git a/lib/Models/Terria.ts b/lib/Models/Terria.ts index 6b96e90f3bd..71cae12da5f 100644 --- a/lib/Models/Terria.ts +++ b/lib/Models/Terria.ts @@ -392,30 +392,7 @@ export default class Terria { recommendedListLength: 5, flightDurationSeconds: 1.5, minCharacters: 3, - searchProviders: [ - { - id: "search-provider/bing-maps", - type: "bing-maps-search-provider", - name: "translate#viewModels.searchLocations", - url: "https://dev.virtualearth.net/", - flightDurationSeconds: 1.5, - minCharacters: 5, - isOpen: true - }, - { - id: "search-provider/australian-gazetteer", - type: "australian-gazetteer-search-provider", - name: "translate#viewModels.searchPlaceNames", - url: - "http://services.ga.gov.au/gis/services/Australian_Gazetteer/MapServer/WFSServer", - searchPropertyName: "Australian_Gazetteer:NameU", - searchPropertyTypeName: "Australian_Gazetteer:Gazetteer_of_Australia", - flightDurationSeconds: 1.5, - minCharacters: 3, - recommendedListLength: 3, - isOpen: false - } - ] + searchProviders: [] } }; @@ -576,8 +553,10 @@ export default class Terria { } if (this.locationSearchProviders.has(model.uniqueId)) { - throw new RuntimeError( - "A SearchProvider with the specified ID already exists." + console.log( + new DeveloperError( + "A SearchProvider with the specified ID already exists." + ) ); } @@ -730,7 +709,7 @@ export default class Terria { } }) .then(() => { - let searchProviders = this.configParameters.searchBar!.searchProviders; + let searchProviders = this.configParameters.searchBar?.searchProviders; if (!isObservableArray(searchProviders)) throw new TerriaError({ sender: SearchProviderFactory, @@ -843,7 +822,16 @@ export default class Terria { updateParameters(parameters: ConfigParameters): void { Object.keys(parameters).forEach((key: string) => { if (this.configParameters.hasOwnProperty(key)) { - this.configParameters[key] = parameters[key]; + if (key === "searchBar") { + // merge default and new + //@ts-ignore + this.configParameters[key] = { + ...this.configParameters[key], + ...parameters[key] + }; + } else { + this.configParameters[key] = parameters[key]; + } } }); diff --git a/lib/ReactViews/Search/LocationSearchResults.tsx b/lib/ReactViews/Search/LocationSearchResults.tsx index 7b8e5bc2abd..d9927e669e6 100644 --- a/lib/ReactViews/Search/LocationSearchResults.tsx +++ b/lib/ReactViews/Search/LocationSearchResults.tsx @@ -62,8 +62,8 @@ class LocationSearchResults extends React.Component { @computed get validResults() { const { search, terria } = this.props; - const locationSearchBoundingBox = terria.configParameters.searchBar! - .boundingBoxLimit; + const locationSearchBoundingBox = + terria.configParameters.searchBar?.boundingBoxLimit; const validResults = isDefined(locationSearchBoundingBox) ? search.results.filter(function(r: any) { return ( From 4fba7f8e5ae0cb37cde091c73a641addf5a434fa Mon Sep 17 00:00:00 2001 From: zoran995 Date: Thu, 18 Feb 2021 00:45:08 +0100 Subject: [PATCH 16/62] add tests --- .../AustralianGazetteerSearchProviderSpec.ts | 19 +++++-- .../BingMapsSearchProviderSpec.ts | 55 +++++++++++++++++++ .../LocationSearchProviderTraitsSpec.ts | 23 +++++++- 3 files changed, 90 insertions(+), 7 deletions(-) rename test/Models/{ => SearchProvider}/AustralianGazetteerSearchProviderSpec.ts (55%) create mode 100644 test/Models/SearchProvider/BingMapsSearchProviderSpec.ts diff --git a/test/Models/AustralianGazetteerSearchProviderSpec.ts b/test/Models/SearchProvider/AustralianGazetteerSearchProviderSpec.ts similarity index 55% rename from test/Models/AustralianGazetteerSearchProviderSpec.ts rename to test/Models/SearchProvider/AustralianGazetteerSearchProviderSpec.ts index 33a4a48047c..9ffe6205216 100644 --- a/test/Models/AustralianGazetteerSearchProviderSpec.ts +++ b/test/Models/SearchProvider/AustralianGazetteerSearchProviderSpec.ts @@ -1,9 +1,8 @@ import { configure } from "mobx"; -import createAustralianGazetteerSearchProvider from "../../lib/Models/SearchProvider/AustralianGazetteerSearchProvider"; -import Terria from "../../lib/Models/Terria"; -import WebFeatureServiceSearchProvider from "../../lib/Models/SearchProvider/WebFeatureServiceSearchProvider"; +import Terria from "../../../lib/Models/Terria"; +import AustralianGazetteerSearchProvider from "../../../lib/Models/SearchProvider/AustralianGazetteerSearchProvider"; -const wfsResponseXml = require("raw-loader!../../wwwroot/test/WFS/getWithFilter.xml"); +const wfsResponseXml = require("raw-loader!../../../wwwroot/test/WFS/getWithFilter.xml"); configure({ enforceActions: "observed", @@ -11,10 +10,18 @@ configure({ }); describe("GazetteerSearchProvider", function() { - let searchProvider: WebFeatureServiceSearchProvider; + let searchProvider: AustralianGazetteerSearchProvider; beforeEach(function() { - searchProvider = createAustralianGazetteerSearchProvider(new Terria()); + searchProvider = new AustralianGazetteerSearchProvider( + "test", + new Terria() + ); + }); + + it(" type", function() { + expect(searchProvider.type).toEqual(AustralianGazetteerSearchProvider.type); }); + it("queries the web feature service and returns search results", async function() { spyOn(searchProvider, "getXml").and.returnValue( Promise.resolve(wfsResponseXml) diff --git a/test/Models/SearchProvider/BingMapsSearchProviderSpec.ts b/test/Models/SearchProvider/BingMapsSearchProviderSpec.ts new file mode 100644 index 00000000000..bfa3673ec23 --- /dev/null +++ b/test/Models/SearchProvider/BingMapsSearchProviderSpec.ts @@ -0,0 +1,55 @@ +import BingMapsSearchProvider from "../../../lib/Models/SearchProvider/BingMapsSearchProvider"; +import Terria from "../../../lib/Models/Terria"; + +describe("BingMapsSearchProvider", function() { + let searchProvider: BingMapsSearchProvider; + beforeEach(function() { + searchProvider = new BingMapsSearchProvider("test", new Terria()); + jasmine.Ajax.install(); + jasmine.Ajax.stubRequest(/https:\/\/dev.virtualearth.net/i).andReturn({ + responseText: JSON.stringify({ + resourceSets: [ + { + estimatedTotal: 1, + resources: [ + { + address: { + addressLine: "Borgo Garibaldi", + adminDistrict: "Veneto", + adminDistrict2: "Belluno", + countryRegion: "Italy", + formattedAddress: "Borgo Garibaldi, 32026 Borgo Valbelluna", + locality: "Borgo Valbelluna", + postalCode: "32026" + }, + bbox: [46.06294, 12.08387, 46.06359, 12.08573], + confidence: "Medium", + entityType: "RoadBlock", + name: "Borgo Garibaldi, 32026 Borgo Valbelluna", + point: { type: "Point", coordinates: [46.06323, 12.0848] } + } + ] + } + ] + }) + }); + }); + + afterEach(function() { + jasmine.Ajax.uninstall(); + }); + + it(" type", function() { + expect(searchProvider.type).toEqual(BingMapsSearchProvider.type); + }); + + it("find location", function(done) { + const results = searchProvider.search("melb"); + expect(results).toBeDefined(); + expect(results.results.length).toEqual(1); + expect(results.results[0].name).toEqual( + "Borgo Garibaldi, 32026 Borgo Valbelluna" + ); + done(); + }); +}); diff --git a/test/Traits/SearchProvider/LocationSearchProviderTraitsSpec.ts b/test/Traits/SearchProvider/LocationSearchProviderTraitsSpec.ts index 781a510e90f..e07d7033acf 100644 --- a/test/Traits/SearchProvider/LocationSearchProviderTraitsSpec.ts +++ b/test/Traits/SearchProvider/LocationSearchProviderTraitsSpec.ts @@ -1,11 +1,32 @@ import Terria from "../../../lib/Models/Terria"; +import BingMapsSearchProvider from "../../../lib/Models/SearchProvider/BingMapsSearchProvider"; +import LocationSearchProviderMixin from "../../../lib/ModelMixins/SearchProvider/LocationSearchProviderMixin"; describe("LocationSearchProviderTraits", function() { let terria: Terria; + let bingMapsSearchProvider: BingMapsSearchProvider; beforeEach(async function() { terria = new Terria({ baseUrl: "./" }); - //geoJsonCatalogItem = new GeoJsonCatalogItem("test", terria); + bingMapsSearchProvider = new BingMapsSearchProvider("test", terria); + }); + + it(" - properly mixed", function() { + expect( + LocationSearchProviderMixin.isMixedInto(bingMapsSearchProvider) + ).toBeTruthy(); + }); + + it(" - propperly defines default recommendedListLength", function() { + expect(bingMapsSearchProvider.recommendedListLength).toEqual(5); + }); + + it(" - propperly defines default flightDurationSeconds", function() { + expect(bingMapsSearchProvider.flightDurationSeconds).toEqual(1.5); + }); + + it(" - propperly defines default isOpen", function() { + expect(bingMapsSearchProvider.isOpen).toEqual(true); }); }); From e104c34f439a05adbc932e54fc58a2fe8393732f Mon Sep 17 00:00:00 2001 From: zoran995 Date: Thu, 18 Feb 2021 00:49:54 +0100 Subject: [PATCH 17/62] use propper translation strings --- lib/Language/en/translation.json | 8 ++++++++ lib/Models/SearchProvider/upsertSearchProviderFromJson.ts | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/Language/en/translation.json b/lib/Language/en/translation.json index c289fc50858..92de53a1d3b 100644 --- a/lib/Language/en/translation.json +++ b/lib/Language/en/translation.json @@ -1437,6 +1437,14 @@ "datasetScaleErrorMessage": "The {{name}} dataset will not be shown when zoomed in this close to the map because the data custodian has indicated that the data is not intended or suitable for display at this scale. Click the dataset's Info button on the Now Viewing tab for more information about the dataset and the data custodian." } }, + "searchProvider": { + "models": { + "unsupportedTypeTitle": "Unknown type", + "unsupportedTypeMessage": "Could not create unknown model type {{type}}.", + "idForMatchingErrorTitle": "Missing property", + "idForMatchingErrorMessage": "Model objects must have an `id`, `localId`, or `name` property." + } + }, "deltaTool": { "titlePrefix": "Change Detection", "description": "This tool visualizes the difference between imagery captured at two discrete points in time.", diff --git a/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts b/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts index 4921a9c561d..c9c7427e756 100644 --- a/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts +++ b/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts @@ -18,8 +18,8 @@ export default function upsertSearchProviderFromJson( const id = json.localId || json.name; if (id === undefined) { throw new TerriaError({ - title: i18next.t("models.catalog.idForMatchingErrorTitle"), - message: i18next.t("models.catalog.idForMatchingErrorMessage") + title: i18next.t("searchProvider.models.idForMatchingErrorTitle"), + message: i18next.t("searchProvider.models.idForMatchingErrorMessage") }); } uniqueId = id; @@ -32,8 +32,8 @@ export default function upsertSearchProviderFromJson( if (model === undefined) { console.log( new TerriaError({ - title: i18next.t("models.catalog.unsupportedTypeTitle"), - message: i18next.t("models.catalog.unsupportedTypeMessage", { + title: i18next.t("searchProvider.models.unsupportedTypeTitle"), + message: i18next.t("searchProvider.models.unsupportedTypeMessage", { type: json.type }) }) From 91630c46e9f4e85bcd74d06c7c54bed494c2c004 Mon Sep 17 00:00:00 2001 From: zoran995 Date: Thu, 18 Feb 2021 01:11:09 +0100 Subject: [PATCH 18/62] tests --- .../BingMapsSearchProviderSpec.ts | 55 ------------------- .../BingMapsSearchProviderTraitsSpec.ts | 28 ++++++++++ 2 files changed, 28 insertions(+), 55 deletions(-) delete mode 100644 test/Models/SearchProvider/BingMapsSearchProviderSpec.ts create mode 100644 test/Traits/SearchProvider/BingMapsSearchProviderTraitsSpec.ts diff --git a/test/Models/SearchProvider/BingMapsSearchProviderSpec.ts b/test/Models/SearchProvider/BingMapsSearchProviderSpec.ts deleted file mode 100644 index bfa3673ec23..00000000000 --- a/test/Models/SearchProvider/BingMapsSearchProviderSpec.ts +++ /dev/null @@ -1,55 +0,0 @@ -import BingMapsSearchProvider from "../../../lib/Models/SearchProvider/BingMapsSearchProvider"; -import Terria from "../../../lib/Models/Terria"; - -describe("BingMapsSearchProvider", function() { - let searchProvider: BingMapsSearchProvider; - beforeEach(function() { - searchProvider = new BingMapsSearchProvider("test", new Terria()); - jasmine.Ajax.install(); - jasmine.Ajax.stubRequest(/https:\/\/dev.virtualearth.net/i).andReturn({ - responseText: JSON.stringify({ - resourceSets: [ - { - estimatedTotal: 1, - resources: [ - { - address: { - addressLine: "Borgo Garibaldi", - adminDistrict: "Veneto", - adminDistrict2: "Belluno", - countryRegion: "Italy", - formattedAddress: "Borgo Garibaldi, 32026 Borgo Valbelluna", - locality: "Borgo Valbelluna", - postalCode: "32026" - }, - bbox: [46.06294, 12.08387, 46.06359, 12.08573], - confidence: "Medium", - entityType: "RoadBlock", - name: "Borgo Garibaldi, 32026 Borgo Valbelluna", - point: { type: "Point", coordinates: [46.06323, 12.0848] } - } - ] - } - ] - }) - }); - }); - - afterEach(function() { - jasmine.Ajax.uninstall(); - }); - - it(" type", function() { - expect(searchProvider.type).toEqual(BingMapsSearchProvider.type); - }); - - it("find location", function(done) { - const results = searchProvider.search("melb"); - expect(results).toBeDefined(); - expect(results.results.length).toEqual(1); - expect(results.results[0].name).toEqual( - "Borgo Garibaldi, 32026 Borgo Valbelluna" - ); - done(); - }); -}); diff --git a/test/Traits/SearchProvider/BingMapsSearchProviderTraitsSpec.ts b/test/Traits/SearchProvider/BingMapsSearchProviderTraitsSpec.ts new file mode 100644 index 00000000000..1d4ca1f6ad7 --- /dev/null +++ b/test/Traits/SearchProvider/BingMapsSearchProviderTraitsSpec.ts @@ -0,0 +1,28 @@ +import Terria from "../../../lib/Models/Terria"; +import BingMapsSearchProvider from "../../../lib/Models/SearchProvider/BingMapsSearchProvider"; +import LocationSearchProviderMixin from "../../../lib/ModelMixins/SearchProvider/LocationSearchProviderMixin"; + +describe("BingMapsSearchProviderTraits", function() { + let terria: Terria; + let bingMapsSearchProvider: BingMapsSearchProvider; + beforeEach(async function() { + terria = new Terria({ + baseUrl: "./" + }); + bingMapsSearchProvider = new BingMapsSearchProvider("test", terria); + }); + + it(" - properly mixed", function() { + expect( + LocationSearchProviderMixin.isMixedInto(bingMapsSearchProvider) + ).toBeTruthy(); + }); + + describe(" - default values", function() { + it(" - propperly defines default url", function() { + expect(bingMapsSearchProvider.url).toEqual( + "https://dev.virtualearth.net/" + ); + }); + }); +}); From 907b4cc41ed99ea9693e3c91ba552bd2cb5f22f4 Mon Sep 17 00:00:00 2001 From: zoran995 Date: Thu, 8 Jul 2021 08:30:15 +0200 Subject: [PATCH 19/62] fix bad merge --- lib/Styled/List.tsx | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/Styled/List.tsx b/lib/Styled/List.tsx index 8df57000c29..94a61b3024b 100644 --- a/lib/Styled/List.tsx +++ b/lib/Styled/List.tsx @@ -1,13 +1,37 @@ -import styled from "styled-components"; +import styled, { css } from "styled-components"; -export const Ul = styled.ul` +export const Li = styled.li``; + +type ListProps = Partial<{ + spaced: boolean; + lined: boolean; + fullWidth: boolean; +}>; + +export const Ul = styled.ul` list-style: none; padding: 0; margin: 0; + ${props => props.fullWidth && "width: 100%;"} + ${props => + props.spaced && + css` + ${Li}:not(:first-child) { + padding-top: 5px; + } + `} + ${props => + props.lined && + css` + ${Li}:first-child { + padding-bottom: 5px; + } + ${Li}:not(:first-child) { + border-top: 1px solid grey; + } + `} `; -export const Li = styled.li``; - export const Ol = styled(Ul).attrs({ as: "ol" })``; From 7ec3b1f113460127efeabe1d5da086d24e169dcc Mon Sep 17 00:00:00 2001 From: zoran995 Date: Thu, 8 Jul 2021 09:16:45 +0200 Subject: [PATCH 20/62] connect search providers with analytics and resolve merge conflicts --- lib/Core/AnalyticEvents/analyticEvents.ts | 4 +--- .../LocationSearchProviderMixin.ts | 19 +++++++-------- .../SearchProvider/SearchProviderMixin.ts | 15 ++++++++---- .../WebFeatureServiceSearchProviderMixin.ts | 2 ++ .../AustralianGazetteerSearchProvider.ts | 12 ++++++++++ .../SearchProvider/BingMapsSearchProvider.ts | 14 +++++++++-- .../SearchProvider/CatalogSearchProvider.ts | 21 ++++++++--------- .../SearchProvider/SearchProviderResults.ts | 2 +- .../SearchProvider/StubSearchProvider.ts | 4 ++++ lib/ReactViewModels/SearchState.ts | 16 +++++++------ lib/ReactViews/Mobile/MobileHeader.jsx | 10 +------- .../Search/LocationSearchResults.tsx | 18 +++++++-------- lib/ReactViews/Search/SearchBoxAndResults.jsx | 17 +++++++------- lib/ReactViews/Search/SearchHeader.tsx | 6 ++--- lib/ReactViews/Search/SearchResult.tsx | 23 ++++++------------- lib/Styled/Icon.tsx | 2 +- lib/Styled/Input.tsx | 2 +- lib/Styled/Select.tsx | 2 +- 18 files changed, 99 insertions(+), 90 deletions(-) diff --git a/lib/Core/AnalyticEvents/analyticEvents.ts b/lib/Core/AnalyticEvents/analyticEvents.ts index 51154c1218c..39f9f443cf3 100644 --- a/lib/Core/AnalyticEvents/analyticEvents.ts +++ b/lib/Core/AnalyticEvents/analyticEvents.ts @@ -15,9 +15,7 @@ export enum Category { export enum SearchAction { bing = "Bing", catalog = "Catalog", - gazetteer = "Gazetteer", - gnaf = "gnaf", - nominatim = "nominatim" + gazetteer = "Gazetteer" } export enum LaunchAction { diff --git a/lib/ModelMixins/SearchProvider/LocationSearchProviderMixin.ts b/lib/ModelMixins/SearchProvider/LocationSearchProviderMixin.ts index faa54b3ad92..15e40342c25 100644 --- a/lib/ModelMixins/SearchProvider/LocationSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProvider/LocationSearchProviderMixin.ts @@ -1,15 +1,10 @@ -import { action, observable } from "mobx"; -import { fromPromise } from "mobx-utils"; +import { action } from "mobx"; import Ellipsoid from "terriajs-cesium/Source/Core/Ellipsoid"; import CesiumMath from "terriajs-cesium/Source/Core/Math"; import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; import Constructor from "../../Core/Constructor"; -import Model, { BaseModel } from "../../Models/Model"; -import SearchProviderResults from "../../Models/SearchProvider/SearchProviderResults"; -import StratumFromTraits from "../../Models/StratumFromTraits"; +import Model from "../../Models/Model"; import Terria from "../../Models/Terria"; -import ModelTraits from "../../Traits/ModelTraits"; -import SearchProviderTraits from "../../Traits/SearchProvider/SearchProviderTraits"; import CommonStrata from "../../Models/CommonStrata"; import LocationSearchProviderTraits from "../../Traits/SearchProvider/LocationSearchProviderTraits"; import SearchProviderMixin from "./SearchProviderMixin"; @@ -20,15 +15,16 @@ function LocationSearchProviderMixin< T extends Constructor >(Base: T) { abstract class LocationSearchProviderMixin extends SearchProviderMixin(Base) { + get hasLocationSearchProviderMixin() { + return true; + } + @action toggleOpen(stratumId: CommonStrata = CommonStrata.user) { this.setTrait(stratumId, "isOpen", !this.isOpen); } - - get hasLocationSearchProviderMixin() { - return true; - } } + return LocationSearchProviderMixin; } @@ -59,6 +55,7 @@ export function getMapCenter(terria: Terria): MapCenter { namespace LocationSearchProviderMixin { export interface LocationSearchProviderMixin extends InstanceType> {} + export function isMixedInto( model: any ): model is LocationSearchProviderMixin { diff --git a/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts b/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts index 349b806a5fb..e62d16c54fa 100644 --- a/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts @@ -14,6 +14,13 @@ function SearchProviderMixin>( abstract class SearchProviderMixin extends Base { abstract get type(): string; + protected abstract logEvent(searchText: string): void; + + protected abstract doSearch( + searchText: string, + results: SearchProviderResults + ): Promise; + @action search(searchText: string): SearchProviderResults { const result = new SearchProviderResults(this); @@ -24,17 +31,13 @@ function SearchProviderMixin>( }); return result; } + this.logEvent(searchText); result.resultsCompletePromise = fromPromise( this.doSearch(searchText, result) ); return result; } - protected abstract doSearch( - searchText: string, - results: SearchProviderResults - ): Promise; - private shouldRunSearch(searchText: string) { if ( searchText === undefined || @@ -53,12 +56,14 @@ function SearchProviderMixin>( return true; } } + return SearchProviderMixin; } namespace SearchProviderMixin { export interface SearchProviderMixin extends InstanceType> {} + export function isMixedInto(model: any): model is SearchProviderMixin { return model && model.hasSearchProviderMixin; } diff --git a/lib/ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin.ts b/lib/ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin.ts index dad3a5d4a16..80d5e68e508 100644 --- a/lib/ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin.ts @@ -177,6 +177,7 @@ function WebFeatureServiceSearchProviderMixin< results.message = i18next.t("viewModels.searchErrorOccurred"); }); } + get isWebFeatureServiceSearchProviderMixin() { return true; } @@ -190,6 +191,7 @@ namespace WebFeatureServiceSearchProviderMixin { extends InstanceType< ReturnType > {} + export function isMixedInto( model: any ): model is WebFeatureServiceSearchProviderMixin { diff --git a/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts b/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts index 986f781134d..e9725ca0cfb 100644 --- a/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts +++ b/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts @@ -2,6 +2,10 @@ import WebFeatureServiceSearchProviderTraits from "../../Traits/SearchProvider/W import CreateModel from "../CreateModel"; import WebFeatureServiceSearchProviderMixin from "../../ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin"; import SearchResult from "./SearchResult"; +import { + Category, + SearchAction +} from "../../Core/AnalyticEvents/analyticEvents"; const featureCodesToNamesMap = new Map([ ["AF", "Aviation"], @@ -229,6 +233,14 @@ export default class AustralianGazetteerSearchProvider extends WebFeatureService return AustralianGazetteerSearchProvider.type; } + protected logEvent(searchText: string) { + this.terria.analytics?.logEvent( + Category.search, + SearchAction.gazetteer, + searchText + ); + } + featureToSearchResultFunction: ( feature: any ) => SearchResult = featureToSearchResultFunction; diff --git a/lib/Models/SearchProvider/BingMapsSearchProvider.ts b/lib/Models/SearchProvider/BingMapsSearchProvider.ts index 4bbd5d2035d..6a075946c65 100644 --- a/lib/Models/SearchProvider/BingMapsSearchProvider.ts +++ b/lib/Models/SearchProvider/BingMapsSearchProvider.ts @@ -13,6 +13,10 @@ import SearchProviderResults from "./SearchProviderResults"; import SearchResult from "./SearchResult"; import CommonStrata from "./../CommonStrata"; import Terria from "../Terria"; +import { + Category, + SearchAction +} from "../../Core/AnalyticEvents/analyticEvents"; export default class BingMapsSearchProvider extends LocationSearchProviderMixin( CreateModel(BingMapsSearchProviderTraits) @@ -45,6 +49,14 @@ export default class BingMapsSearchProvider extends LocationSearchProviderMixin( } } + protected logEvent(searchText: string) { + this.terria.analytics?.logEvent( + Category.search, + SearchAction.gazetteer, + searchText + ); + } + protected doSearch( searchText: string, searchResults: SearchProviderResults @@ -52,8 +64,6 @@ export default class BingMapsSearchProvider extends LocationSearchProviderMixin( searchResults.results.length = 0; searchResults.message = undefined; - this.terria.analytics.logEvent("search", "bing", searchText); - const searchQuery = new Resource({ url: this.url + "REST/v1/Locations", queryParameters: { diff --git a/lib/Models/SearchProvider/CatalogSearchProvider.ts b/lib/Models/SearchProvider/CatalogSearchProvider.ts index 810902cfb6e..a5fe35e79a7 100644 --- a/lib/Models/SearchProvider/CatalogSearchProvider.ts +++ b/lib/Models/SearchProvider/CatalogSearchProvider.ts @@ -12,12 +12,9 @@ import Terria from "../Terria"; import SearchProviderResults from "./SearchProviderResults"; import SearchResult from "./SearchResult"; -interface CatalogSearchProviderOptions { - terria: Terria; -} - type UniqueIdString = string; type ResultMap = Map; + export function loadAndSearchCatalogRecursively( terria: Terria, searchTextLowercase: string, @@ -105,13 +102,20 @@ export default class CatalogSearchProvider extends SearchProviderMixin( CreateModel(CatalogSearchProviderTraits) ) { static readonly type = "catalog-search-provider"; + @observable isSearching: boolean = false; + @observable debounceDurationOnceLoaded: number = 300; get type() { return CatalogSearchProvider.type; } - @observable isSearching: boolean = false; - @observable debounceDurationOnceLoaded: number = 300; + protected logEvent(searchText: string) { + this.terria.analytics?.logEvent( + Category.search, + SearchAction.catalog, + searchText + ); + } protected doSearch( searchText: string, @@ -126,11 +130,6 @@ export default class CatalogSearchProvider extends SearchProviderMixin( return Promise.resolve(); } - this.terria.analytics?.logEvent( - Category.search, - SearchAction.catalog, - searchText - ); const resultMap: ResultMap = new Map(); const promise: Promise = loadAndSearchCatalogRecursively( diff --git a/lib/Models/SearchProvider/SearchProviderResults.ts b/lib/Models/SearchProvider/SearchProviderResults.ts index 09fcc1aafe5..8f2c887bd31 100644 --- a/lib/Models/SearchProvider/SearchProviderResults.ts +++ b/lib/Models/SearchProvider/SearchProviderResults.ts @@ -1,6 +1,6 @@ import { observable } from "mobx"; import SearchResult from "./SearchResult"; -import { IPromiseBasedObservable, fromPromise } from "mobx-utils"; +import { fromPromise, IPromiseBasedObservable } from "mobx-utils"; import SearchProviderMixin from "../../ModelMixins/SearchProvider/SearchProviderMixin"; export default class SearchProviderResults { diff --git a/lib/Models/SearchProvider/StubSearchProvider.ts b/lib/Models/SearchProvider/StubSearchProvider.ts index 28745157278..4fec65f6175 100644 --- a/lib/Models/SearchProvider/StubSearchProvider.ts +++ b/lib/Models/SearchProvider/StubSearchProvider.ts @@ -22,6 +22,10 @@ export default class StubSearchProvider extends SearchProviderMixin( return StubSearchProvider.type; } + protected logEvent(searchText: string) { + return; + } + protected doSearch( searchText: string, results: SearchProviderResults diff --git a/lib/ReactViewModels/SearchState.ts b/lib/ReactViewModels/SearchState.ts index fc7373547e0..dfe12ad9bea 100644 --- a/lib/ReactViewModels/SearchState.ts +++ b/lib/ReactViewModels/SearchState.ts @@ -52,21 +52,23 @@ export default class SearchState { new CatalogSearchProvider("catalog-search-provider", options.terria); this.locationSearchProviders = options.locationSearchProviders || []; + const self = this; + this._catalogSearchDisposer = reaction( - () => this.catalogSearchText, + () => self.catalogSearchText, () => { - this.isWaitingToStartCatalogSearch = true; - if (this.catalogSearchProvider) { - this.catalogSearchResults = this.catalogSearchProvider.search(""); + self.isWaitingToStartCatalogSearch = true; + if (self.catalogSearchProvider) { + self.catalogSearchResults = self.catalogSearchProvider.search(""); } } ); this._locationSearchDisposer = reaction( - () => this.locationSearchText, + () => self.locationSearchText, () => { - this.isWaitingToStartLocationSearch = true; - this.locationSearchResults = this.locationSearchProviders.map( + self.isWaitingToStartLocationSearch = true; + self.locationSearchResults = self.locationSearchProviders.map( provider => { return provider.search(""); } diff --git a/lib/ReactViews/Mobile/MobileHeader.jsx b/lib/ReactViews/Mobile/MobileHeader.jsx index 6ed7bc0eb25..8a4599b16a2 100644 --- a/lib/ReactViews/Mobile/MobileHeader.jsx +++ b/lib/ReactViews/Mobile/MobileHeader.jsx @@ -4,21 +4,13 @@ import { runInAction } from "mobx"; import { observer } from "mobx-react"; import PropTypes from "prop-types"; import React from "react"; -import SearchBox from "../Search/SearchBox"; -import MobileModalWindow from "./MobileModalWindow"; -import Branding from "../SidePanel/Branding"; -import Styles from "./mobile-header.scss"; -import Icon, { StyledIcon } from "../../Styled/Icon"; -import MobileMenu from "./MobileMenu"; -import classNames from "classnames"; -import { removeMarker } from "../../Models/LocationMarkerUtils"; import { withTranslation } from "react-i18next"; import { withTheme } from "styled-components"; import { useTranslationIfExists } from "../../Language/languageHelpers"; import { removeMarker } from "../../Models/LocationMarkerUtils"; import Box from "../../Styled/Box"; import { RawButton } from "../../Styled/Button"; -import Icon, { StyledIcon } from "../Icon"; +import Icon, { StyledIcon } from "../../Styled/Icon"; import SearchBox from "../Search/SearchBox"; import Branding from "../SidePanel/Branding"; import Styles from "./mobile-header.scss"; diff --git a/lib/ReactViews/Search/LocationSearchResults.tsx b/lib/ReactViews/Search/LocationSearchResults.tsx index d9927e669e6..8189ab45c71 100644 --- a/lib/ReactViews/Search/LocationSearchResults.tsx +++ b/lib/ReactViews/Search/LocationSearchResults.tsx @@ -5,7 +5,7 @@ https://github.com/TerriaJS/nsw-digital-twin/issues/248#issuecomment-599919318 */ -import { observable, computed, action } from "mobx"; +import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import React from "react"; import { @@ -15,20 +15,18 @@ import { } from "react-i18next"; import styled, { DefaultTheme } from "styled-components"; import isDefined from "../../Core/isDefined"; +import SearchProviderResults from "../../Models/SearchProvider/SearchProviderResults"; import Terria from "../../Models/Terria"; import ViewState from "../../ReactViewModels/ViewState"; +import Box, { BoxSpan } from "../../Styled/Box"; +import { RawButton } from "../../Styled/Button"; +import Icon, { StyledIcon } from "../../Styled/Icon"; import Ul from "../../Styled/List"; -import Icon, { StyledIcon } from "../Icon"; +import Text, { TextSpan } from "../../Styled/Text"; +import Loader from "../Loader"; import LocationSearchProviderMixin from "./../../ModelMixins/SearchProvider/LocationSearchProviderMixin"; -import SearchProviderResults from "../../Models/SearchProvider/SearchProviderResults"; import SearchHeader from "./SearchHeader"; import SearchResult from "./SearchResult"; -import Loader from "../Loader"; -const BoxSpan: any = require("../../Styled/Box").BoxSpan; -const Box: any = require("../../Styled/Box").default; -const Text: any = require("../../Styled/Text").default; -const TextSpan: any = require("../../Styled/Text").TextSpan; -const RawButton: any = require("../../Styled/Button").RawButton; const RawButtonAndHighlight = styled(RawButton)` ${p => ` @@ -109,7 +107,7 @@ class LocationSearchResults extends React.Component { isOpen={isOpen} search={search} isWaitingForSearchToStart={this.props.isWaitingForSearchToStart} - > + /> ); } + SearchInDataCatalog.propTypes = { handleClick: PropTypes.func.isRequired, viewState: PropTypes.object.isRequired @@ -74,11 +66,13 @@ const PresentationBox = styled(Box).attrs({ `; export const LOCATION_SEARCH_INPUT_NAME = "LocationSearchInput"; + export class SearchBoxAndResultsRaw extends React.Component { constructor(props) { super(props); this.locationSearchRef = React.createRef(); } + componentDidMount() { this.props.viewState.updateAppRef( LOCATION_SEARCH_INPUT_NAME, @@ -114,6 +108,7 @@ export class SearchBoxAndResultsRaw extends React.Component { this._nowViewingChangeSubscription = undefined; } } + changeSearchText(newText) { runInAction(() => { this.props.viewState.searchState.locationSearchText = newText; @@ -134,17 +129,21 @@ export class SearchBoxAndResultsRaw extends React.Component { }); } } + search() { this.props.viewState.searchState.searchLocations(); } + toggleShowLocationSearchResults(bool) { runInAction(() => { this.props.viewState.searchState.showLocationSearchResults = bool; }); } + startLocationSearch() { this.toggleShowLocationSearchResults(true); } + render() { const { viewState, placeholder } = this.props; const searchState = viewState.searchState; diff --git a/lib/ReactViews/Search/SearchHeader.tsx b/lib/ReactViews/Search/SearchHeader.tsx index 5ead1157445..89a4202d251 100644 --- a/lib/ReactViews/Search/SearchHeader.tsx +++ b/lib/ReactViews/Search/SearchHeader.tsx @@ -1,8 +1,8 @@ +import { observer } from "mobx-react"; import React from "react"; +import { BoxSpan } from "../../Styled/Box"; +import Text from "../../Styled/Text"; import Loader from "../Loader"; -import { observer } from "mobx-react"; -const Text = require("../../Styled/Text").default; -const BoxSpan = require("../../Styled/Box").BoxSpan; interface SearchHeaderProps { searchResults: { [key: string]: any }; diff --git a/lib/ReactViews/Search/SearchResult.tsx b/lib/ReactViews/Search/SearchResult.tsx index a431a4278cc..681c51acca9 100644 --- a/lib/ReactViews/Search/SearchResult.tsx +++ b/lib/ReactViews/Search/SearchResult.tsx @@ -1,16 +1,13 @@ import React from "react"; import styled, { useTheme } from "styled-components"; import { Li } from "../../Styled/List"; -import Icon, { StyledIcon } from "../Icon"; +import Icon, { StyledIcon } from "../../Styled/Icon"; import highlightKeyword from "../ReactViewHelpers/highlightKeyword"; - -const Box = require("../../Styled/Box").default; -const BoxSpan = require("../../Styled/Box").BoxSpan; -const TextSpan = require("../../Styled/Text").TextSpan; -const RawButton = require("../../Styled/Button").RawButton; -const Spacing = require("../../Styled/Spacing").default; -const SpacingSpan = require("../../Styled/Spacing").SpacingSpan; -const Hr = require("../../Styled/Hr").default; +import Box, { BoxSpan } from "../../Styled/Box"; +import { TextSpan } from "../../Styled/Text"; +import Spacing, { SpacingSpan } from "../../Styled/Spacing"; +import { RawButton } from "../../Styled/Button"; +import Hr from "../../Styled/Hr"; // Not sure how to generalise this or if it should be kept in stlyed/Button.jsx @@ -59,13 +56,7 @@ const SearchResult: React.FC = ( {/* )} */} - + ` -moz-appearance: none; diff --git a/lib/Styled/Select.tsx b/lib/Styled/Select.tsx index 9589283aa80..a7862c88785 100644 --- a/lib/Styled/Select.tsx +++ b/lib/Styled/Select.tsx @@ -27,7 +27,7 @@ or with overrides on icon import React from "react"; import styled, { useTheme } from "styled-components"; -const Box: any = require("./Box").default; +import Box from "./Box"; import { GLYPHS, StyledIcon } from "./Icon"; const StyledSelect = styled.select` From 7bef63def684eab07785b31c11fa8c28b6a9375c Mon Sep 17 00:00:00 2001 From: zoran995 Date: Thu, 8 Jul 2021 12:12:48 +0200 Subject: [PATCH 21/62] resolve conflict trait location --- lib/Models/SearchProvider/StubSearchProvider.ts | 2 +- lib/Traits/SearchProvider/BingMapsSearchProviderTraits.ts | 6 +++--- lib/Traits/SearchProvider/CatalogSearchProviderTraits.ts | 2 +- lib/Traits/SearchProvider/LocationSearchProviderTraits.ts | 2 +- lib/Traits/SearchProvider/SearchProviderTraits.ts | 2 +- .../SearchProvider/WebFeatureServiceSearchProviderTraits.ts | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/Models/SearchProvider/StubSearchProvider.ts b/lib/Models/SearchProvider/StubSearchProvider.ts index 4fec65f6175..bd0e99969e2 100644 --- a/lib/Models/SearchProvider/StubSearchProvider.ts +++ b/lib/Models/SearchProvider/StubSearchProvider.ts @@ -1,5 +1,5 @@ import LocationSearchProviderTraits from "./../../Traits/SearchProvider/LocationSearchProviderTraits"; -import primitiveTrait from "./../../Traits/primitiveTrait"; +import primitiveTrait from "./../../Traits/Decorators/primitiveTrait"; import SearchProviderMixin from "../../ModelMixins/SearchProvider/SearchProviderMixin"; import CreateModel from "../CreateModel"; import SearchProviderResults from "./SearchProviderResults"; diff --git a/lib/Traits/SearchProvider/BingMapsSearchProviderTraits.ts b/lib/Traits/SearchProvider/BingMapsSearchProviderTraits.ts index f39d28a1227..81649828868 100644 --- a/lib/Traits/SearchProvider/BingMapsSearchProviderTraits.ts +++ b/lib/Traits/SearchProvider/BingMapsSearchProviderTraits.ts @@ -1,5 +1,5 @@ import mixTraits from "../mixTraits"; -import primitiveTrait from "../primitiveTrait"; +import primitiveTrait from "../Decorators/primitiveTrait"; import LocationSearchProviderTraits, { SearchProviderMapCenterTraits } from "./LocationSearchProviderTraits"; @@ -27,8 +27,8 @@ export default class BingMapsSearchProviderTraits extends mixTraits( @primitiveTrait({ type: "string", name: "Culture", - description: `Use the culture parameter to specify a culture for your request. - The culture parameter provides the result in the language of the culture. + description: `Use the culture parameter to specify a culture for your request. + The culture parameter provides the result in the language of the culture. For a list of supported cultures, see [Supported Culture Codes](https://docs.microsoft.com/en-us/bingmaps/rest-services/common-parameters-and-types/supported-culture-codes)` }) culture: string = "en-au"; diff --git a/lib/Traits/SearchProvider/CatalogSearchProviderTraits.ts b/lib/Traits/SearchProvider/CatalogSearchProviderTraits.ts index 7808d9d81b4..2dc2ff84927 100644 --- a/lib/Traits/SearchProvider/CatalogSearchProviderTraits.ts +++ b/lib/Traits/SearchProvider/CatalogSearchProviderTraits.ts @@ -1,6 +1,6 @@ import mixTraits from "../mixTraits"; import SearchProviderTraits from "./SearchProviderTraits"; -import primitiveTrait from "../primitiveTrait"; +import primitiveTrait from "../Decorators/primitiveTrait"; export default class CatalogSearchProviderTraits extends mixTraits( SearchProviderTraits diff --git a/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts b/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts index 2893c7ea07f..cb00c39d7c8 100644 --- a/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts +++ b/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts @@ -1,5 +1,5 @@ import ModelTraits from "../ModelTraits"; -import primitiveTrait from "../primitiveTrait"; +import primitiveTrait from "../Decorators/primitiveTrait"; import SearchProviderTraits from "./SearchProviderTraits"; export default class LocationSearchProviderTraits extends SearchProviderTraits { diff --git a/lib/Traits/SearchProvider/SearchProviderTraits.ts b/lib/Traits/SearchProvider/SearchProviderTraits.ts index f19e6449184..175ed48a22e 100644 --- a/lib/Traits/SearchProvider/SearchProviderTraits.ts +++ b/lib/Traits/SearchProvider/SearchProviderTraits.ts @@ -1,5 +1,5 @@ import ModelTraits from "../ModelTraits"; -import primitiveTrait from "../primitiveTrait"; +import primitiveTrait from "../Decorators/primitiveTrait"; export default class SearchProviderTraits extends ModelTraits { @primitiveTrait({ diff --git a/lib/Traits/SearchProvider/WebFeatureServiceSearchProviderTraits.ts b/lib/Traits/SearchProvider/WebFeatureServiceSearchProviderTraits.ts index c95945ff74c..1be4884242e 100644 --- a/lib/Traits/SearchProvider/WebFeatureServiceSearchProviderTraits.ts +++ b/lib/Traits/SearchProvider/WebFeatureServiceSearchProviderTraits.ts @@ -1,4 +1,4 @@ -import primitiveTrait from "../primitiveTrait"; +import primitiveTrait from "../Decorators/primitiveTrait"; import LocationSearchProviderTraits from "./LocationSearchProviderTraits"; export default class WebFeatureServiceSearchProviderTraits extends LocationSearchProviderTraits { From ea9618849d41abf35ba3552d9874dd3cd20dba6e Mon Sep 17 00:00:00 2001 From: zoran995 Date: Thu, 8 Jul 2021 13:29:49 +0200 Subject: [PATCH 22/62] fix lint --- lib/ReactViews/Mobile/MobileHeader.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ReactViews/Mobile/MobileHeader.jsx b/lib/ReactViews/Mobile/MobileHeader.jsx index 8a4599b16a2..d03e128a971 100644 --- a/lib/ReactViews/Mobile/MobileHeader.jsx +++ b/lib/ReactViews/Mobile/MobileHeader.jsx @@ -141,7 +141,7 @@ const MobileHeader = observer( render() { const searchState = this.props.viewState.searchState; - const { t } = this.props; + const { t, terria } = this.props; const nowViewingLength = terria.workbench.items !== undefined ? terria.workbench.items.length From da980b94caea4f8ffce74706470af3bf5f7e1304 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Thu, 2 Sep 2021 19:19:12 +0200 Subject: [PATCH 23/62] rename directory to searchproviders --- .../LocationSearchProviderMixin.ts | 4 ++-- .../SearchProviderMixin.ts | 6 +++--- .../WebFeatureServiceSearchProviderMixin.ts | 6 +++--- .../AustralianGazetteerSearchProvider.ts | 8 ++++---- .../BingMapsSearchProvider.ts | 16 ++++++++-------- .../CatalogSearchProvider.ts | 4 ++-- .../SearchProviderFactory.ts | 0 .../SearchProviderResults.ts | 4 ++-- .../SearchResult.ts | 0 .../StubSearchProvider.ts | 6 +++--- .../createStubSearchProvider.ts | 6 +++--- .../registerSearchProviders.ts | 0 .../upsertSearchProviderFromJson.ts | 8 ++++---- lib/Models/Terria.ts | 8 +++----- lib/ReactViewModels/SearchState.ts | 8 ++++---- lib/ReactViews/Search/LocationSearchResults.tsx | 4 ++-- .../BingMapsSearchProviderTraits.ts | 0 .../CatalogSearchProviderTraits.ts | 0 .../LocationSearchProviderTraits.ts | 0 .../SearchProviderTraits.ts | 0 .../WebFeatureServiceSearchProviderTraits.ts | 0 .../AustralianGazetteerSearchProviderSpec.ts | 2 +- .../BingMapsSearchProviderTraitsSpec.ts | 4 ++-- .../LocationSearchProviderTraitsSpec.ts | 4 ++-- 24 files changed, 48 insertions(+), 50 deletions(-) rename lib/ModelMixins/{SearchProvider => SearchProviders}/LocationSearchProviderMixin.ts (98%) rename lib/ModelMixins/{SearchProvider => SearchProviders}/SearchProviderMixin.ts (91%) rename lib/ModelMixins/{SearchProvider => SearchProviders}/WebFeatureServiceSearchProviderMixin.ts (97%) rename lib/Models/{SearchProvider => SearchProviders}/AustralianGazetteerSearchProvider.ts (98%) rename lib/Models/{SearchProvider => SearchProviders}/BingMapsSearchProvider.ts (97%) rename lib/Models/{SearchProvider => SearchProviders}/CatalogSearchProvider.ts (98%) rename lib/Models/{SearchProvider => SearchProviders}/SearchProviderFactory.ts (100%) rename lib/Models/{SearchProvider => SearchProviders}/SearchProviderResults.ts (96%) rename lib/Models/{SearchProvider => SearchProviders}/SearchResult.ts (100%) rename lib/Models/{SearchProvider => SearchProviders}/StubSearchProvider.ts (82%) rename lib/Models/{SearchProvider => SearchProviders}/createStubSearchProvider.ts (90%) rename lib/Models/{SearchProvider => SearchProviders}/registerSearchProviders.ts (100%) rename lib/Models/{SearchProvider => SearchProviders}/upsertSearchProviderFromJson.ts (94%) rename lib/Traits/{SearchProvider => SearchProviders}/BingMapsSearchProviderTraits.ts (100%) rename lib/Traits/{SearchProvider => SearchProviders}/CatalogSearchProviderTraits.ts (100%) rename lib/Traits/{SearchProvider => SearchProviders}/LocationSearchProviderTraits.ts (100%) rename lib/Traits/{SearchProvider => SearchProviders}/SearchProviderTraits.ts (100%) rename lib/Traits/{SearchProvider => SearchProviders}/WebFeatureServiceSearchProviderTraits.ts (100%) rename test/Models/{SearchProvider => SearchProviders}/AustralianGazetteerSearchProviderSpec.ts (95%) rename test/Traits/{SearchProvider => SearchProviders}/BingMapsSearchProviderTraitsSpec.ts (91%) rename test/Traits/{SearchProvider => SearchProviders}/LocationSearchProviderTraitsSpec.ts (92%) diff --git a/lib/ModelMixins/SearchProvider/LocationSearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/LocationSearchProviderMixin.ts similarity index 98% rename from lib/ModelMixins/SearchProvider/LocationSearchProviderMixin.ts rename to lib/ModelMixins/SearchProviders/LocationSearchProviderMixin.ts index 15e40342c25..51600365987 100644 --- a/lib/ModelMixins/SearchProvider/LocationSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/LocationSearchProviderMixin.ts @@ -3,10 +3,10 @@ import Ellipsoid from "terriajs-cesium/Source/Core/Ellipsoid"; import CesiumMath from "terriajs-cesium/Source/Core/Math"; import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; import Constructor from "../../Core/Constructor"; +import CommonStrata from "../../Models/CommonStrata"; import Model from "../../Models/Model"; import Terria from "../../Models/Terria"; -import CommonStrata from "../../Models/CommonStrata"; -import LocationSearchProviderTraits from "../../Traits/SearchProvider/LocationSearchProviderTraits"; +import LocationSearchProviderTraits from "../../Traits/SearchProviders/LocationSearchProviderTraits"; import SearchProviderMixin from "./SearchProviderMixin"; type LocationSearchProviderModel = Model; diff --git a/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts similarity index 91% rename from lib/ModelMixins/SearchProvider/SearchProviderMixin.ts rename to lib/ModelMixins/SearchProviders/SearchProviderMixin.ts index e62d16c54fa..4e961471f8a 100644 --- a/lib/ModelMixins/SearchProvider/SearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts @@ -1,10 +1,10 @@ +import i18next from "i18next"; import { action } from "mobx"; import { fromPromise } from "mobx-utils"; import Constructor from "../../Core/Constructor"; import Model from "../../Models/Model"; -import SearchProviderResults from "../../Models/SearchProvider/SearchProviderResults"; -import SearchProviderTraits from "../../Traits/SearchProvider/SearchProviderTraits"; -import i18next from "i18next"; +import SearchProviderResults from "../../Models/SearchProviders/SearchProviderResults"; +import SearchProviderTraits from "../../Traits/SearchProviders/SearchProviderTraits"; type SearchProviderModel = Model; diff --git a/lib/ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts similarity index 97% rename from lib/ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin.ts rename to lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts index 80d5e68e508..d205658edd3 100644 --- a/lib/ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts @@ -6,10 +6,10 @@ import Constructor from "../../Core/Constructor"; import makeRealPromise from "../../Core/makeRealPromise"; import zoomRectangleFromPoint from "../../Map/zoomRectangleFromPoint"; import Model from "../../Models/Model"; -import SearchProviderResults from "../../Models/SearchProvider/SearchProviderResults"; -import SearchResult from "../../Models/SearchProvider/SearchResult"; +import SearchProviderResults from "../../Models/SearchProviders/SearchProviderResults"; +import SearchResult from "../../Models/SearchProviders/SearchResult"; import xml2json from "../../ThirdParty/xml2json"; -import WebFeatureServiceSearchProviderTraits from "../../Traits/SearchProvider/WebFeatureServiceSearchProviderTraits"; +import WebFeatureServiceSearchProviderTraits from "../../Traits/SearchProviders/WebFeatureServiceSearchProviderTraits"; import LocationSearchProviderMixin from "./LocationSearchProviderMixin"; function WebFeatureServiceSearchProviderMixin< diff --git a/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts b/lib/Models/SearchProviders/AustralianGazetteerSearchProvider.ts similarity index 98% rename from lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts rename to lib/Models/SearchProviders/AustralianGazetteerSearchProvider.ts index e9725ca0cfb..48f7a21c7d2 100644 --- a/lib/Models/SearchProvider/AustralianGazetteerSearchProvider.ts +++ b/lib/Models/SearchProviders/AustralianGazetteerSearchProvider.ts @@ -1,11 +1,11 @@ -import WebFeatureServiceSearchProviderTraits from "../../Traits/SearchProvider/WebFeatureServiceSearchProviderTraits"; -import CreateModel from "../CreateModel"; -import WebFeatureServiceSearchProviderMixin from "../../ModelMixins/SearchProvider/WebFeatureServiceSearchProviderMixin"; -import SearchResult from "./SearchResult"; import { Category, SearchAction } from "../../Core/AnalyticEvents/analyticEvents"; +import WebFeatureServiceSearchProviderMixin from "../../ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin"; +import WebFeatureServiceSearchProviderTraits from "../../Traits/SearchProviders/WebFeatureServiceSearchProviderTraits"; +import CreateModel from "../CreateModel"; +import SearchResult from "./SearchResult"; const featureCodesToNamesMap = new Map([ ["AF", "Aviation"], diff --git a/lib/Models/SearchProvider/BingMapsSearchProvider.ts b/lib/Models/SearchProviders/BingMapsSearchProvider.ts similarity index 97% rename from lib/Models/SearchProvider/BingMapsSearchProvider.ts rename to lib/Models/SearchProviders/BingMapsSearchProvider.ts index 6a075946c65..b62eb5e62ae 100644 --- a/lib/Models/SearchProvider/BingMapsSearchProvider.ts +++ b/lib/Models/SearchProviders/BingMapsSearchProvider.ts @@ -3,20 +3,20 @@ import { runInAction } from "mobx"; import defined from "terriajs-cesium/Source/Core/defined"; import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; import Resource from "terriajs-cesium/Source/Core/Resource"; +import { + Category, + SearchAction +} from "../../Core/AnalyticEvents/analyticEvents"; import loadJsonp from "../../Core/loadJsonp"; import LocationSearchProviderMixin, { getMapCenter -} from "../../ModelMixins/SearchProvider/LocationSearchProviderMixin"; -import BingMapsSearchProviderTraits from "../../Traits/SearchProvider/BingMapsSearchProviderTraits"; +} from "../../ModelMixins/SearchProviders/LocationSearchProviderMixin"; +import BingMapsSearchProviderTraits from "../../Traits/SearchProviders/BingMapsSearchProviderTraits"; +import CommonStrata from "../CommonStrata"; import CreateModel from "../CreateModel"; +import Terria from "../Terria"; import SearchProviderResults from "./SearchProviderResults"; import SearchResult from "./SearchResult"; -import CommonStrata from "./../CommonStrata"; -import Terria from "../Terria"; -import { - Category, - SearchAction -} from "../../Core/AnalyticEvents/analyticEvents"; export default class BingMapsSearchProvider extends LocationSearchProviderMixin( CreateModel(BingMapsSearchProviderTraits) diff --git a/lib/Models/SearchProvider/CatalogSearchProvider.ts b/lib/Models/SearchProviders/CatalogSearchProvider.ts similarity index 98% rename from lib/Models/SearchProvider/CatalogSearchProvider.ts rename to lib/Models/SearchProviders/CatalogSearchProvider.ts index a5fe35e79a7..9e84d7ebcfd 100644 --- a/lib/Models/SearchProvider/CatalogSearchProvider.ts +++ b/lib/Models/SearchProviders/CatalogSearchProvider.ts @@ -5,8 +5,8 @@ import { } from "../../Core/AnalyticEvents/analyticEvents"; import GroupMixin from "../../ModelMixins/GroupMixin"; import ReferenceMixin from "../../ModelMixins/ReferenceMixin"; -import SearchProviderMixin from "../../ModelMixins/SearchProvider/SearchProviderMixin"; -import CatalogSearchProviderTraits from "../../Traits/SearchProvider/CatalogSearchProviderTraits"; +import SearchProviderMixin from "../../ModelMixins/SearchProviders/SearchProviderMixin"; +import CatalogSearchProviderTraits from "../../Traits/SearchProviders/CatalogSearchProviderTraits"; import CreateModel from "../CreateModel"; import Terria from "../Terria"; import SearchProviderResults from "./SearchProviderResults"; diff --git a/lib/Models/SearchProvider/SearchProviderFactory.ts b/lib/Models/SearchProviders/SearchProviderFactory.ts similarity index 100% rename from lib/Models/SearchProvider/SearchProviderFactory.ts rename to lib/Models/SearchProviders/SearchProviderFactory.ts diff --git a/lib/Models/SearchProvider/SearchProviderResults.ts b/lib/Models/SearchProviders/SearchProviderResults.ts similarity index 96% rename from lib/Models/SearchProvider/SearchProviderResults.ts rename to lib/Models/SearchProviders/SearchProviderResults.ts index 8f2c887bd31..df3cf9e6538 100644 --- a/lib/Models/SearchProvider/SearchProviderResults.ts +++ b/lib/Models/SearchProviders/SearchProviderResults.ts @@ -1,7 +1,7 @@ import { observable } from "mobx"; -import SearchResult from "./SearchResult"; import { fromPromise, IPromiseBasedObservable } from "mobx-utils"; -import SearchProviderMixin from "../../ModelMixins/SearchProvider/SearchProviderMixin"; +import SearchProviderMixin from "../../ModelMixins/SearchProviders/SearchProviderMixin"; +import SearchResult from "./SearchResult"; export default class SearchProviderResults { @observable results: SearchResult[] = []; diff --git a/lib/Models/SearchProvider/SearchResult.ts b/lib/Models/SearchProviders/SearchResult.ts similarity index 100% rename from lib/Models/SearchProvider/SearchResult.ts rename to lib/Models/SearchProviders/SearchResult.ts diff --git a/lib/Models/SearchProvider/StubSearchProvider.ts b/lib/Models/SearchProviders/StubSearchProvider.ts similarity index 82% rename from lib/Models/SearchProvider/StubSearchProvider.ts rename to lib/Models/SearchProviders/StubSearchProvider.ts index bd0e99969e2..b6fe2e47719 100644 --- a/lib/Models/SearchProvider/StubSearchProvider.ts +++ b/lib/Models/SearchProviders/StubSearchProvider.ts @@ -1,6 +1,6 @@ -import LocationSearchProviderTraits from "./../../Traits/SearchProvider/LocationSearchProviderTraits"; -import primitiveTrait from "./../../Traits/Decorators/primitiveTrait"; -import SearchProviderMixin from "../../ModelMixins/SearchProvider/SearchProviderMixin"; +import SearchProviderMixin from "../../ModelMixins/SearchProviders/SearchProviderMixin"; +import primitiveTrait from "../../Traits/Decorators/primitiveTrait"; +import LocationSearchProviderTraits from "../../Traits/SearchProviders/LocationSearchProviderTraits"; import CreateModel from "../CreateModel"; import SearchProviderResults from "./SearchProviderResults"; diff --git a/lib/Models/SearchProvider/createStubSearchProvider.ts b/lib/Models/SearchProviders/createStubSearchProvider.ts similarity index 90% rename from lib/Models/SearchProvider/createStubSearchProvider.ts rename to lib/Models/SearchProviders/createStubSearchProvider.ts index 76434e11ba8..914668b8038 100644 --- a/lib/Models/SearchProvider/createStubSearchProvider.ts +++ b/lib/Models/SearchProviders/createStubSearchProvider.ts @@ -1,7 +1,7 @@ -import Terria from "./../Terria"; -import StubSearchProvider from "./StubSearchProvider"; -import CommonStrata from "./../CommonStrata"; +import CommonStrata from "../CommonStrata"; import { BaseModel } from "../Model"; +import Terria from "../Terria"; +import StubSearchProvider from "./StubSearchProvider"; const getUniqueStubSearchProviderName = (terria: Terria) => { const stubName = "[StubSearchProvider]"; diff --git a/lib/Models/SearchProvider/registerSearchProviders.ts b/lib/Models/SearchProviders/registerSearchProviders.ts similarity index 100% rename from lib/Models/SearchProvider/registerSearchProviders.ts rename to lib/Models/SearchProviders/registerSearchProviders.ts diff --git a/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts similarity index 94% rename from lib/Models/SearchProvider/upsertSearchProviderFromJson.ts rename to lib/Models/SearchProviders/upsertSearchProviderFromJson.ts index c9c7427e756..fbbe43fbde4 100644 --- a/lib/Models/SearchProvider/upsertSearchProviderFromJson.ts +++ b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts @@ -1,9 +1,9 @@ -import Terria from "./../Terria"; -import ModelFactory from "./../ModelFactory"; -import { BaseModel } from "../Model"; import i18next from "i18next"; import TerriaError from "../../Core/TerriaError"; -import CommonStrata from "./../CommonStrata"; +import CommonStrata from "../CommonStrata"; +import { BaseModel } from "../Model"; +import ModelFactory from "../ModelFactory"; +import Terria from "../Terria"; import updateModelFromJson from "../updateModelFromJson"; import createStubSearchProvider from "./createStubSearchProvider"; diff --git a/lib/Models/Terria.ts b/lib/Models/Terria.ts index ff2008b3ad8..072674d6d7f 100644 --- a/lib/Models/Terria.ts +++ b/lib/Models/Terria.ts @@ -57,9 +57,7 @@ import ReferenceMixin from "../ModelMixins/ReferenceMixin"; import TimeVarying from "../ModelMixins/TimeVarying"; import { HelpContentItem } from "../ReactViewModels/defaultHelpContent"; import { defaultTerms, Term } from "../ReactViewModels/defaultTerms"; -import NotificationState, { - Notification -} from "../ReactViewModels/NotificationState"; +import NotificationState from "../ReactViewModels/NotificationState"; import { shareConvertNotification } from "../ReactViews/Notification/shareConvertNotification"; import MappableTraits from "../Traits/TraitsClasses/MappableTraits"; import { BaseMapViewModel } from "../ViewModels/BaseMapViewModel"; @@ -90,8 +88,8 @@ import MapInteractionMode from "./MapInteractionMode"; import { BaseModel } from "./Model"; import NoViewer from "./NoViewer"; import openGroup from "./openGroup"; -import SearchProviderFactory from "./SearchProvider/SearchProviderFactory"; -import upsertSearchProviderFromJson from "./SearchProvider/upsertSearchProviderFromJson"; +import SearchProviderFactory from "./SearchProviders/SearchProviderFactory"; +import upsertSearchProviderFromJson from "./SearchProviders/upsertSearchProviderFromJson"; import ShareDataService from "./ShareDataService"; import SplitItemReference from "./SplitItemReference"; import TimelineStack from "./TimelineStack"; diff --git a/lib/ReactViewModels/SearchState.ts b/lib/ReactViewModels/SearchState.ts index dfe12ad9bea..7fa7acbbc39 100644 --- a/lib/ReactViewModels/SearchState.ts +++ b/lib/ReactViewModels/SearchState.ts @@ -6,10 +6,10 @@ import { reaction } from "mobx"; import filterOutUndefined from "../Core/filterOutUndefined"; -import LocationSearchProviderMixin from "../ModelMixins/SearchProvider/LocationSearchProviderMixin"; -import SearchProviderMixin from "../ModelMixins/SearchProvider/SearchProviderMixin"; -import CatalogSearchProvider from "../Models/SearchProvider/CatalogSearchProvider"; -import SearchProviderResults from "../Models/SearchProvider/SearchProviderResults"; +import LocationSearchProviderMixin from "../ModelMixins/SearchProviders/LocationSearchProviderMixin"; +import SearchProviderMixin from "../ModelMixins/SearchProviders/SearchProviderMixin"; +import CatalogSearchProvider from "../Models/SearchProviders/CatalogSearchProvider"; +import SearchProviderResults from "../Models/SearchProviders/SearchProviderResults"; import Terria from "../Models/Terria"; interface SearchStateOptions { diff --git a/lib/ReactViews/Search/LocationSearchResults.tsx b/lib/ReactViews/Search/LocationSearchResults.tsx index 8189ab45c71..45e83b0e602 100644 --- a/lib/ReactViews/Search/LocationSearchResults.tsx +++ b/lib/ReactViews/Search/LocationSearchResults.tsx @@ -15,7 +15,8 @@ import { } from "react-i18next"; import styled, { DefaultTheme } from "styled-components"; import isDefined from "../../Core/isDefined"; -import SearchProviderResults from "../../Models/SearchProvider/SearchProviderResults"; +import LocationSearchProviderMixin from "../../ModelMixins/SearchProviders/LocationSearchProviderMixin"; +import SearchProviderResults from "../../Models/SearchProviders/SearchProviderResults"; import Terria from "../../Models/Terria"; import ViewState from "../../ReactViewModels/ViewState"; import Box, { BoxSpan } from "../../Styled/Box"; @@ -24,7 +25,6 @@ import Icon, { StyledIcon } from "../../Styled/Icon"; import Ul from "../../Styled/List"; import Text, { TextSpan } from "../../Styled/Text"; import Loader from "../Loader"; -import LocationSearchProviderMixin from "./../../ModelMixins/SearchProvider/LocationSearchProviderMixin"; import SearchHeader from "./SearchHeader"; import SearchResult from "./SearchResult"; diff --git a/lib/Traits/SearchProvider/BingMapsSearchProviderTraits.ts b/lib/Traits/SearchProviders/BingMapsSearchProviderTraits.ts similarity index 100% rename from lib/Traits/SearchProvider/BingMapsSearchProviderTraits.ts rename to lib/Traits/SearchProviders/BingMapsSearchProviderTraits.ts diff --git a/lib/Traits/SearchProvider/CatalogSearchProviderTraits.ts b/lib/Traits/SearchProviders/CatalogSearchProviderTraits.ts similarity index 100% rename from lib/Traits/SearchProvider/CatalogSearchProviderTraits.ts rename to lib/Traits/SearchProviders/CatalogSearchProviderTraits.ts diff --git a/lib/Traits/SearchProvider/LocationSearchProviderTraits.ts b/lib/Traits/SearchProviders/LocationSearchProviderTraits.ts similarity index 100% rename from lib/Traits/SearchProvider/LocationSearchProviderTraits.ts rename to lib/Traits/SearchProviders/LocationSearchProviderTraits.ts diff --git a/lib/Traits/SearchProvider/SearchProviderTraits.ts b/lib/Traits/SearchProviders/SearchProviderTraits.ts similarity index 100% rename from lib/Traits/SearchProvider/SearchProviderTraits.ts rename to lib/Traits/SearchProviders/SearchProviderTraits.ts diff --git a/lib/Traits/SearchProvider/WebFeatureServiceSearchProviderTraits.ts b/lib/Traits/SearchProviders/WebFeatureServiceSearchProviderTraits.ts similarity index 100% rename from lib/Traits/SearchProvider/WebFeatureServiceSearchProviderTraits.ts rename to lib/Traits/SearchProviders/WebFeatureServiceSearchProviderTraits.ts diff --git a/test/Models/SearchProvider/AustralianGazetteerSearchProviderSpec.ts b/test/Models/SearchProviders/AustralianGazetteerSearchProviderSpec.ts similarity index 95% rename from test/Models/SearchProvider/AustralianGazetteerSearchProviderSpec.ts rename to test/Models/SearchProviders/AustralianGazetteerSearchProviderSpec.ts index 9ffe6205216..1c7faea9edc 100644 --- a/test/Models/SearchProvider/AustralianGazetteerSearchProviderSpec.ts +++ b/test/Models/SearchProviders/AustralianGazetteerSearchProviderSpec.ts @@ -1,6 +1,6 @@ import { configure } from "mobx"; +import AustralianGazetteerSearchProvider from "../../../lib/Models/SearchProviders/AustralianGazetteerSearchProvider"; import Terria from "../../../lib/Models/Terria"; -import AustralianGazetteerSearchProvider from "../../../lib/Models/SearchProvider/AustralianGazetteerSearchProvider"; const wfsResponseXml = require("raw-loader!../../../wwwroot/test/WFS/getWithFilter.xml"); diff --git a/test/Traits/SearchProvider/BingMapsSearchProviderTraitsSpec.ts b/test/Traits/SearchProviders/BingMapsSearchProviderTraitsSpec.ts similarity index 91% rename from test/Traits/SearchProvider/BingMapsSearchProviderTraitsSpec.ts rename to test/Traits/SearchProviders/BingMapsSearchProviderTraitsSpec.ts index 1d4ca1f6ad7..96678eab1c1 100644 --- a/test/Traits/SearchProvider/BingMapsSearchProviderTraitsSpec.ts +++ b/test/Traits/SearchProviders/BingMapsSearchProviderTraitsSpec.ts @@ -1,6 +1,6 @@ +import LocationSearchProviderMixin from "../../../lib/ModelMixins/SearchProviders/LocationSearchProviderMixin"; +import BingMapsSearchProvider from "../../../lib/Models/SearchProviders/BingMapsSearchProvider"; import Terria from "../../../lib/Models/Terria"; -import BingMapsSearchProvider from "../../../lib/Models/SearchProvider/BingMapsSearchProvider"; -import LocationSearchProviderMixin from "../../../lib/ModelMixins/SearchProvider/LocationSearchProviderMixin"; describe("BingMapsSearchProviderTraits", function() { let terria: Terria; diff --git a/test/Traits/SearchProvider/LocationSearchProviderTraitsSpec.ts b/test/Traits/SearchProviders/LocationSearchProviderTraitsSpec.ts similarity index 92% rename from test/Traits/SearchProvider/LocationSearchProviderTraitsSpec.ts rename to test/Traits/SearchProviders/LocationSearchProviderTraitsSpec.ts index e07d7033acf..4ef2f6f9d44 100644 --- a/test/Traits/SearchProvider/LocationSearchProviderTraitsSpec.ts +++ b/test/Traits/SearchProviders/LocationSearchProviderTraitsSpec.ts @@ -1,6 +1,6 @@ +import LocationSearchProviderMixin from "../../../lib/ModelMixins/SearchProviders/LocationSearchProviderMixin"; +import BingMapsSearchProvider from "../../../lib/Models/SearchProviders/BingMapsSearchProvider"; import Terria from "../../../lib/Models/Terria"; -import BingMapsSearchProvider from "../../../lib/Models/SearchProvider/BingMapsSearchProvider"; -import LocationSearchProviderMixin from "../../../lib/ModelMixins/SearchProvider/LocationSearchProviderMixin"; describe("LocationSearchProviderTraits", function() { let terria: Terria; From 69b95223548b3dc3792c6473b78b9c285f27c1f0 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Thu, 2 Sep 2021 19:42:48 +0200 Subject: [PATCH 24/62] add default value to zoomTo --- lib/Models/GlobeOrMap.ts | 4 ++-- lib/Models/SearchProviders/BingMapsSearchProvider.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Models/GlobeOrMap.ts b/lib/Models/GlobeOrMap.ts index 120702c91e5..02c8d963bae 100644 --- a/lib/Models/GlobeOrMap.ts +++ b/lib/Models/GlobeOrMap.ts @@ -24,9 +24,9 @@ import TimeVarying from "../ModelMixins/TimeVarying"; import MouseCoords from "../ReactViewModels/MouseCoords"; import CameraView from "./CameraView"; import Cesium3DTilesCatalogItem from "./Catalog/CatalogItems/Cesium3DTilesCatalogItem"; +import GeoJsonCatalogItem from "./Catalog/CatalogItems/GeoJsonCatalogItem"; import CommonStrata from "./Definition/CommonStrata"; import Feature from "./Feature"; -import GeoJsonCatalogItem from "./Catalog/CatalogItems/GeoJsonCatalogItem"; import Terria from "./Terria"; require("./ImageryLayerFeatureInfo"); // overrides Cesium's prototype.configureDescriptionFromProperties @@ -70,7 +70,7 @@ export default abstract class GlobeOrMap { @action zoomTo( target: CameraView | Rectangle | MappableMixin.Instance, - flightDurationSeconds: number + flightDurationSeconds: number = 3.0 ): Promise { this.isMapZooming = true; const zoomId = createGuid(); diff --git a/lib/Models/SearchProviders/BingMapsSearchProvider.ts b/lib/Models/SearchProviders/BingMapsSearchProvider.ts index a35980a1718..ab125b0231e 100644 --- a/lib/Models/SearchProviders/BingMapsSearchProvider.ts +++ b/lib/Models/SearchProviders/BingMapsSearchProvider.ts @@ -182,6 +182,6 @@ function createZoomToFunction(model: BingMapsSearchProvider, resource: any) { return function() { const terria = model.terria; - terria.currentViewer.zoomTo(rectangle, model.flightDurationSeconds!); + terria.currentViewer.zoomTo(rectangle, model.flightDurationSeconds); }; } From 819c7abf8de7f567cbabd8e21826eabda6820b1f Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Thu, 2 Sep 2021 19:50:26 +0200 Subject: [PATCH 25/62] fix bad merge --- .../Ows/WebFeatureServiceSearchProvider.ts | 221 ------------------ lib/Models/SearchProviders/SearchProvider.ts | 27 --- 2 files changed, 248 deletions(-) delete mode 100644 lib/Models/Catalog/Ows/WebFeatureServiceSearchProvider.ts delete mode 100644 lib/Models/SearchProviders/SearchProvider.ts diff --git a/lib/Models/Catalog/Ows/WebFeatureServiceSearchProvider.ts b/lib/Models/Catalog/Ows/WebFeatureServiceSearchProvider.ts deleted file mode 100644 index 55ac7ddeac9..00000000000 --- a/lib/Models/Catalog/Ows/WebFeatureServiceSearchProvider.ts +++ /dev/null @@ -1,221 +0,0 @@ -import i18next from "i18next"; -import { runInAction } from "mobx"; -import URI from "urijs"; -import zoomRectangleFromPoint from "../../../Map/zoomRectangleFromPoint"; -import xml2json from "../../../ThirdParty/xml2json"; -import SearchProvider from "../../SearchProviders/SearchProvider"; -import SearchProviderResults from "../../SearchProviders/SearchProviderResults"; -import SearchResult from "../../SearchProviders/SearchResult"; -import Terria from "../../Terria"; -import defaultValue from "terriajs-cesium/Source/Core/defaultValue"; -import Resource from "terriajs-cesium/Source/Core/Resource"; -import makeRealPromise from "../../../Core/makeRealPromise"; - -export interface WebFeatureServiceSearchProviderOptions { - /** Base url for the service */ - wfsServiceUrl: string; - /** Which property to look for the search text in */ - searchPropertyName: string; - /** Type of the properties to search */ - searchPropertyTypeName: string; - /** Convert a WFS feature to a search result */ - featureToSearchResultFunction: (feature: any) => SearchResult; - terria: Terria; - /** How long it takes to zoom in when a search result is clicked */ - flightDurationSeconds?: number; - /** Apply a function to search text before it gets passed to the service. Useful for changing case */ - transformSearchText?: (searchText: string) => string; - /** Return true if a feature should be included in search results */ - searchResultFilterFunction?: (feature: any) => boolean; - /** Return a score that gets used to sort results (in descending order) */ - searchResultScoreFunction?: (feature: any, searchText: string) => number; - /** name of the search provider */ - name: string; -} - -export default class WebFeatureServiceSearchProvider extends SearchProvider { - private _wfsServiceUrl: uri.URI; - private _searchPropertyName: string; - private _searchPropertyTypeName: string; - private _featureToSearchResultFunction: (feature: any) => SearchResult; - flightDurationSeconds: number; - readonly terria: Terria; - private _transformSearchText: ((searchText: string) => string) | undefined; - private _searchResultFilterFunction: ((feature: any) => boolean) | undefined; - private _searchResultScoreFunction: - | ((feature: any, searchText: string) => number) - | undefined; - cancelRequest?: () => void; - private _waitingForResults: boolean = false; - - constructor(options: WebFeatureServiceSearchProviderOptions) { - super(); - this._wfsServiceUrl = new URI(options.wfsServiceUrl); - this._searchPropertyName = options.searchPropertyName; - this._searchPropertyTypeName = options.searchPropertyTypeName; - this._featureToSearchResultFunction = options.featureToSearchResultFunction; - this.terria = options.terria; - this.flightDurationSeconds = defaultValue( - options.flightDurationSeconds, - 1.5 - ); - this._transformSearchText = options.transformSearchText; - this._searchResultFilterFunction = options.searchResultFilterFunction; - this._searchResultScoreFunction = options.searchResultScoreFunction; - this.name = options.name; - } - - getXml(): Promise { - const resource = new Resource({ url: this._wfsServiceUrl.toString() }); - this._waitingForResults = true; - const xmlPromise = resource.fetchXML(); - this.cancelRequest = resource.request.cancelFunction; - return makeRealPromise(xmlPromise).finally(() => { - this._waitingForResults = false; - }); - } - - protected doSearch( - searchText: string, - results: SearchProviderResults - ): Promise { - results.results.length = 0; - results.message = undefined; - - if (this._waitingForResults) { - // There's been a new search! Cancel the previous one. - if (this.cancelRequest !== undefined) { - this.cancelRequest(); - this.cancelRequest = undefined; - } - this._waitingForResults = false; - } - - const originalSearchText = searchText; - - searchText = searchText.trim(); - if (this._transformSearchText !== undefined) { - searchText = this._transformSearchText(searchText); - } - if (searchText.length < 2) { - return Promise.resolve(); - } - - // Support for matchCase="false" is patchy, but we try anyway - const filter = ` - ${this._searchPropertyName} - *${searchText}*`; - - this._wfsServiceUrl.setSearch({ - service: "WFS", - request: "GetFeature", - typeName: this._searchPropertyTypeName, - version: "1.1.0", - srsName: "urn:ogc:def:crs:EPSG::4326", // srsName must be formatted like this for correct lat/long order >:( - filter: filter - }); - - return this.getXml() - .then((xml: any) => { - let json: any = xml2json(xml); - let features: any[]; - if (json === undefined) { - results.message = i18next.t("viewModels.searchErrorOccurred"); - return; - } - - if (json.member !== undefined) { - features = json.member; - } else if (json.featureMember !== undefined) { - features = json.featureMember; - } else { - results.message = i18next.t("viewModels.searchNoPlaceNames"); - return; - } - - // if there's only one feature, make it an array - if (!Array.isArray(features)) { - features = [features]; - } - - const resultSet = new Set(); - - runInAction(() => { - if (this._searchResultFilterFunction !== undefined) { - features = features.filter(this._searchResultFilterFunction); - } - - if (features.length === 0) { - results.message = i18next.t("viewModels.searchNoPlaceNames"); - return; - } - - if (this._searchResultScoreFunction !== undefined) { - features = features.sort( - (featureA, featureB) => - this._searchResultScoreFunction!(featureB, originalSearchText) - - this._searchResultScoreFunction!(featureA, originalSearchText) - ); - } - - let searchResults = features - .map(this._featureToSearchResultFunction) - .map(result => { - result.clickAction = createZoomToFunction(this, result.location); - return result; - }); - - // If we don't have a scoring function, sort the search results now - // We can't do this earlier because we don't know what the schema of the unprocessed feature looks like - if (this._searchResultScoreFunction === undefined) { - // Put shorter results first - // They have a larger percentage of letters that the user actually typed in them - searchResults = searchResults.sort( - (featureA, featureB) => - featureA.name.length - featureB.name.length - ); - } - - // Remove results that have the same name and are close to each other - searchResults = searchResults.filter(result => { - const hash = `${result.name},${result.location?.latitude.toFixed( - 1 - )},${result.location?.longitude.toFixed(1)}`; - if (resultSet.has(hash)) { - return false; - } - resultSet.add(hash); - return true; - }); - - // append new results to all results - results.results.push(...searchResults); - }); - }) - .catch(e => { - if (results.isCanceled) { - // A new search has superseded this one, so ignore the result. - return; - } - results.message = i18next.t("viewModels.searchErrorOccurred"); - }); - } -} - -function createZoomToFunction( - model: WebFeatureServiceSearchProvider, - location: any -) { - // Server does not return information of a bounding box, just a location. - // bboxSize is used to expand a point - var bboxSize = 0.2; - var rectangle = zoomRectangleFromPoint( - location.latitude, - location.longitude, - bboxSize - ); - - return function() { - model.terria.currentViewer.zoomTo(rectangle, model.flightDurationSeconds); - }; -} diff --git a/lib/Models/SearchProviders/SearchProvider.ts b/lib/Models/SearchProviders/SearchProvider.ts deleted file mode 100644 index de29a899952..00000000000 --- a/lib/Models/SearchProviders/SearchProvider.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { action, observable } from "mobx"; -import { fromPromise } from "mobx-utils"; -import SearchProviderResults from "./SearchProviderResults"; - -export default abstract class SearchProvider { - @observable name = "Unknown"; - @observable isOpen = true; - - @action - toggleOpen() { - this.isOpen = !this.isOpen; - } - - @action - search(searchText: string): SearchProviderResults { - const result = new SearchProviderResults(this); - result.resultsCompletePromise = fromPromise( - this.doSearch(searchText, result) - ); - return result; - } - - protected abstract doSearch( - searchText: string, - results: SearchProviderResults - ): Promise; -} From 28702c809bf2816099819898bcb727cc337911c8 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Thu, 2 Sep 2021 20:00:13 +0200 Subject: [PATCH 26/62] don't update name in updateModelFromJson but when rendering --- lib/Models/Definition/updateModelFromJson.ts | 5 ----- lib/ReactViews/Search/LocationSearchResults.tsx | 6 ++++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/Models/Definition/updateModelFromJson.ts b/lib/Models/Definition/updateModelFromJson.ts index 69c7e6e1ad2..179edc0e2e2 100644 --- a/lib/Models/Definition/updateModelFromJson.ts +++ b/lib/Models/Definition/updateModelFromJson.ts @@ -2,7 +2,6 @@ import { isObservableArray, runInAction } from "mobx"; import isDefined from "../../Core/isDefined"; import Result from "../../Core/Result"; import TerriaError from "../../Core/TerriaError"; -import { useTranslationIfExists } from "./../../Language/languageHelpers"; import createStratumInstance from "./createStratumInstance"; import { BaseModel } from "./Model"; @@ -63,10 +62,6 @@ export default function updateModelFromJson( } model.setTrait(stratumName, propertyName, newTrait); } - if (propertyName === "name") { - newTrait = useTranslationIfExists(jsonValue); - } - model.setTrait(stratumName, propertyName, newTrait); } }); }); diff --git a/lib/ReactViews/Search/LocationSearchResults.tsx b/lib/ReactViews/Search/LocationSearchResults.tsx index 45e83b0e602..05020d7554d 100644 --- a/lib/ReactViews/Search/LocationSearchResults.tsx +++ b/lib/ReactViews/Search/LocationSearchResults.tsx @@ -15,6 +15,7 @@ import { } from "react-i18next"; import styled, { DefaultTheme } from "styled-components"; import isDefined from "../../Core/isDefined"; +import { useTranslationIfExists } from "../../Language/languageHelpers"; import LocationSearchProviderMixin from "../../ModelMixins/SearchProviders/LocationSearchProviderMixin"; import SearchProviderResults from "../../Models/SearchProviders/SearchProviderResults"; import Terria from "../../Models/Terria"; @@ -189,8 +190,9 @@ const NameWithLoader: React.FC = observer( (props: NameWithLoaderProps) => ( - {`${props.name} (${props.length || - 0})`} + {`${useTranslationIfExists( + props.name + )} (${props.length || 0})`} {!props.isOpen && (props.search.isSearching || props.isWaitingForSearchToStart) && ( From 2463af83910db58feda6327644ebd5b0f48c0d06 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Thu, 2 Sep 2021 20:54:04 +0200 Subject: [PATCH 27/62] use new error handling --- .../upsertSearchProviderFromJson.ts | 45 +++++++++++++------ lib/Models/Terria.ts | 36 +++++++++++---- 2 files changed, 58 insertions(+), 23 deletions(-) diff --git a/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts index a54a282c714..f583b591a95 100644 --- a/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts +++ b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts @@ -1,4 +1,5 @@ import i18next from "i18next"; +import Result from "../../Core/Result"; import TerriaError from "../../Core/TerriaError"; import CommonStrata from "../Definition/CommonStrata"; import { BaseModel } from "../Definition/Model"; @@ -6,21 +7,26 @@ import ModelFactory from "../Definition/ModelFactory"; import updateModelFromJson from "../Definition/updateModelFromJson"; import Terria from "../Terria"; import createStubSearchProvider from "./createStubSearchProvider"; +import StubSearchProvider from "./StubSearchProvider"; export default function upsertSearchProviderFromJson( factory: ModelFactory, terria: Terria, stratumName: string, json: any -) { +): Result { + const errors: TerriaError[] = []; + let uniqueId = json.id; if (uniqueId === undefined) { const id = json.localId || json.name; if (id === undefined) { - throw new TerriaError({ - title: i18next.t("searchProvider.models.idForMatchingErrorTitle"), - message: i18next.t("searchProvider.models.idForMatchingErrorMessage") - }); + return Result.error( + new TerriaError({ + title: i18next.t("models.catalog.idForMatchingErrorTitle"), + message: i18next.t("models.catalog.idForMatchingErrorMessage") + }) + ); } uniqueId = id; } @@ -30,7 +36,7 @@ export default function upsertSearchProviderFromJson( if (model === undefined) { model = factory.create(json.type, uniqueId, terria); if (model === undefined) { - console.log( + errors.push( new TerriaError({ title: i18next.t("searchProvider.models.unsupportedTypeTitle"), message: i18next.t("searchProvider.models.unsupportedTypeMessage", { @@ -44,18 +50,29 @@ export default function upsertSearchProviderFromJson( stub.setTrait(CommonStrata.override, "name", `${uniqueId} (Stub)`); } - model?.terria.addSearchProvider(model); + if (model.type !== StubSearchProvider.type) { + try { + model.terria.addSearchProvider(model); + } catch (error) { + errors.push(error); + } + } } setDefaultTraits(model); - try { - updateModelFromJson(model, stratumName, json); - } catch (error) { - console.log(`Error updating search provider from JSON`); - console.log(error); - model?.setTrait(CommonStrata.underride, "isExperiencingIssues", true); - } + updateModelFromJson(model, stratumName, json).catchError(error => { + errors.push(error); + model!.setTrait(CommonStrata.underride, "isExperiencingIssues", true); + }); + + return new Result( + model, + TerriaError.combine( + errors, + `Error upserting search provider JSON: \`${uniqueId}\`` + ) + ); } function setDefaultTraits(model: BaseModel) { diff --git a/lib/Models/Terria.ts b/lib/Models/Terria.ts index aa9cb2ab6e5..34a8a631d97 100644 --- a/lib/Models/Terria.ts +++ b/lib/Models/Terria.ts @@ -914,12 +914,18 @@ export default class Terria { ) ); + this.initializeSearchProviders().catchError(error => + this.raiseErrorToUser( + TerriaError.from(error, "Failed to initialize searchProviders") + ) + ); + if (options.applicationUrl) { (await this.updateApplicationUrl(options.applicationUrl.href)).raiseError( this ); } - this.initializeSearchProviders(); + this.loadPersistedMapSettings(); } @@ -942,21 +948,33 @@ export default class Terria { } initializeSearchProviders() { + const errors: TerriaError[] = []; let searchProviders = this.configParameters.searchBar?.searchProviders; - if (!isObservableArray(searchProviders)) - throw new TerriaError({ - sender: SearchProviderFactory, - title: "SearchProviders", - message: "" - }); - searchProviders.forEach(searchProvider => { + if (!isObservableArray(searchProviders)) { + errors.push( + new TerriaError({ + sender: SearchProviderFactory, + title: "SearchProviders", + message: { key: "searchProvider.noSearchProviders" } + }) + ); + } + searchProviders?.forEach(searchProvider => { upsertSearchProviderFromJson( SearchProviderFactory, this, CommonStrata.definition, searchProvider - ); + ).pushErrorTo(errors); }); + + return new Result( + undefined, + TerriaError.combine( + errors, + "An error occurred while loading search providers" + ) + ); } async loadPersistedOrInitBaseMap() { From 8e15054af1f8d3dc40e0256da7df78334282b22e Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Fri, 3 Sep 2021 00:21:58 +0200 Subject: [PATCH 28/62] properly handle translations, and fix issue with catalog search provider not properly handling errors --- lib/Language/en/translation.json | 1 + lib/Language/languageHelpers.ts | 12 +++-- .../SearchProviders/SearchProviderMixin.ts | 10 ++-- .../WebFeatureServiceSearchProviderMixin.ts | 19 ++++--- .../SearchProviders/BingMapsSearchProvider.ts | 20 +++++--- .../SearchProviders/CatalogSearchProvider.ts | 49 +++++++++++-------- .../SearchProviders/SearchProviderResults.ts | 7 ++- .../Search/LocationSearchResults.tsx | 4 +- lib/ReactViews/Search/SearchHeader.tsx | 11 ++++- 9 files changed, 87 insertions(+), 46 deletions(-) diff --git a/lib/Language/en/translation.json b/lib/Language/en/translation.json index 6aedf624cbe..14c19281190 100644 --- a/lib/Language/en/translation.json +++ b/lib/Language/en/translation.json @@ -1520,6 +1520,7 @@ } }, "searchProvider": { + "noSearchProviders": "There is no configured search providers", "models": { "unsupportedTypeTitle": "Unknown type", "unsupportedTypeMessage": "Could not create unknown model type {{type}}.", diff --git a/lib/Language/languageHelpers.ts b/lib/Language/languageHelpers.ts index 3d57da40cee..065ddd55ae1 100644 --- a/lib/Language/languageHelpers.ts +++ b/lib/Language/languageHelpers.ts @@ -1,14 +1,18 @@ import i18next from "i18next"; + /** * Takes a given string and translates it if it exists, otherwise return */ -export function useTranslationIfExists(keyOrString: string) { - if (keyOrString && keyOrString.indexOf("translate#") === 0) { +export function useTranslationIfExists( + keyOrString: string = "", + options?: { [key: string]: string | number | undefined } +) { + if (keyOrString.indexOf("translate#") === 0) { const translationKey = keyOrString.substr("translate#".length); return i18next.exists(translationKey) - ? i18next.t(translationKey) + ? i18next.t(translationKey, options) : translationKey; } else { - return keyOrString || ""; + return keyOrString; } } diff --git a/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts index aa488842053..fba60fd2b1b 100644 --- a/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts @@ -1,4 +1,3 @@ -import i18next from "i18next"; import { action } from "mobx"; import { fromPromise } from "mobx-utils"; import Constructor from "../../Core/Constructor"; @@ -26,9 +25,12 @@ function SearchProviderMixin>( const result = new SearchProviderResults(this); if (!this.shouldRunSearch(searchText)) { result.resultsCompletePromise = fromPromise(Promise.resolve()); - result.message = i18next.t("viewModels.searchMinCharacters", { - count: this.minCharacters - }); + result.message = { + content: "translate#viewModels.searchMinCharacters", + params: { + count: this.minCharacters + } + }; return result; } this.logEvent(searchText); diff --git a/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts index d205658edd3..3fa2a49af40 100644 --- a/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts @@ -1,11 +1,10 @@ -import i18next from "i18next"; import { runInAction } from "mobx"; import Resource from "terriajs-cesium/Source/Core/Resource"; import URI from "urijs"; import Constructor from "../../Core/Constructor"; import makeRealPromise from "../../Core/makeRealPromise"; import zoomRectangleFromPoint from "../../Map/zoomRectangleFromPoint"; -import Model from "../../Models/Model"; +import Model from "../../Models/Definition/Model"; import SearchProviderResults from "../../Models/SearchProviders/SearchProviderResults"; import SearchResult from "../../Models/SearchProviders/SearchResult"; import xml2json from "../../ThirdParty/xml2json"; @@ -91,7 +90,9 @@ function WebFeatureServiceSearchProviderMixin< let json: any = xml2json(xml); let features: any[]; if (json === undefined) { - results.message = i18next.t("viewModels.searchErrorOccurred"); + results.message = { + content: "translate#viewModels.searchErrorOccurred" + }; return; } @@ -100,7 +101,9 @@ function WebFeatureServiceSearchProviderMixin< } else if (json.featureMember !== undefined) { features = json.featureMember; } else { - results.message = i18next.t("viewModels.searchNoPlaceNames"); + results.message = { + content: "translate#translate#viewModels.searchNoPlaceNames" + }; return; } @@ -117,7 +120,9 @@ function WebFeatureServiceSearchProviderMixin< } if (features.length === 0) { - results.message = i18next.t("viewModels.searchNoPlaceNames"); + results.message = { + content: "translate#viewModels.searchNoPlaceNames" + }; return; } @@ -174,7 +179,9 @@ function WebFeatureServiceSearchProviderMixin< // A new search has superseded this one, so ignore the result. return; } - results.message = i18next.t("viewModels.searchErrorOccurred"); + results.message = { + content: "translate#viewModels.searchErrorOccurred" + }; }); } diff --git a/lib/Models/SearchProviders/BingMapsSearchProvider.ts b/lib/Models/SearchProviders/BingMapsSearchProvider.ts index ab125b0231e..764c3c16574 100644 --- a/lib/Models/SearchProviders/BingMapsSearchProvider.ts +++ b/lib/Models/SearchProviders/BingMapsSearchProvider.ts @@ -1,4 +1,3 @@ -import i18next from "i18next"; import { runInAction } from "mobx"; import defined from "terriajs-cesium/Source/Core/defined"; import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; @@ -8,6 +7,7 @@ import { SearchAction } from "../../Core/AnalyticEvents/analyticEvents"; import loadJsonp from "../../Core/loadJsonp"; +import { useTranslationIfExists } from "../../Language/languageHelpers"; import LocationSearchProviderMixin, { getMapCenter } from "../../ModelMixins/SearchProviders/LocationSearchProviderMixin"; @@ -43,7 +43,7 @@ export default class BingMapsSearchProvider extends LocationSearchProviderMixin( if (!this.key || this.key === "") { console.warn( "The " + - this.name + + useTranslationIfExists(this.name) + " geocoder will always return no results because a Bing Maps key has not been provided. Please get a Bing Maps key from bingmapsportal.com and add it to parameters.bingMapsKey in config.json." ); } @@ -91,13 +91,17 @@ export default class BingMapsSearchProvider extends LocationSearchProviderMixin( } if (result.resourceSets.length === 0) { - searchResults.message = i18next.t("viewModels.searchNoLocations"); + searchResults.message = { + content: "translate#viewModels.searchNoLocations" + }; return; } var resourceSet = result.resourceSets[0]; if (resourceSet.resources.length === 0) { - searchResults.message = i18next.t("viewModels.searchNoLocations"); + searchResults.message = { + content: "translate#viewModels.searchNoLocations" + }; return; } @@ -109,7 +113,9 @@ export default class BingMapsSearchProvider extends LocationSearchProviderMixin( }); if (searchResults.results.length === 0) { - searchResults.message = i18next.t("viewModels.searchNoLocations"); + searchResults.message = { + content: "translate#viewModels.searchNoLocations" + }; } }) .catch(() => { @@ -118,7 +124,9 @@ export default class BingMapsSearchProvider extends LocationSearchProviderMixin( return; } - searchResults.message = i18next.t("viewModels.searchErrorOccurred"); + searchResults.message = { + content: "translate#viewModels.searchErrorOccurred" + }; }); } diff --git a/lib/Models/SearchProviders/CatalogSearchProvider.ts b/lib/Models/SearchProviders/CatalogSearchProvider.ts index e4f7b5b2686..7c02059e02b 100644 --- a/lib/Models/SearchProviders/CatalogSearchProvider.ts +++ b/lib/Models/SearchProviders/CatalogSearchProvider.ts @@ -69,7 +69,7 @@ export function loadAndSearchCatalogRecursively( if (referencesAndGroupsToLoad.length === 0) { return Promise.resolve(terria); } - return new Promise(resolve => { + return new Promise((resolve, reject) => { autorun(reaction => { Promise.all( referencesAndGroupsToLoad.map(async model => { @@ -82,18 +82,22 @@ export function loadAndSearchCatalogRecursively( // return model.loadMembers(); // } }) - ).then(() => { - // Then call this function again to see if new child references were loaded in - resolve( - loadAndSearchCatalogRecursively( - terria, - searchTextLowercase, - searchResults, - resultMap, - iteration + 1 - ) - ); - }); + ) + .then(() => { + // Then call this function again to see if new child references were loaded in + resolve( + loadAndSearchCatalogRecursively( + terria, + searchTextLowercase, + searchResults, + resultMap, + iteration + 1 + ) + ); + }) + .catch(error => { + reject(error); + }); reaction.dispose(); }); }); @@ -133,14 +137,12 @@ export default class CatalogSearchProvider extends SearchProviderMixin( const resultMap: ResultMap = new Map(); - const promise: Promise = loadAndSearchCatalogRecursively( + return loadAndSearchCatalogRecursively( this.terria, searchText.toLowerCase(), searchResults, resultMap - ); - - return promise + ) .then(terria => { runInAction(() => { this.isSearching = false; @@ -156,8 +158,9 @@ export default class CatalogSearchProvider extends SearchProviderMixin( }); if (searchResults.results.length === 0) { - searchResults.message = - "Sorry, no locations match your search query."; + searchResults.message = { + content: "translate#viewModels.searchNoLocations" + }; } }) .catch(() => { @@ -165,9 +168,13 @@ export default class CatalogSearchProvider extends SearchProviderMixin( // A new search has superseded this one, so ignore the result. return; } + runInAction(() => { + this.isSearching = false; + }); - searchResults.message = - "An error occurred while searching. Please check your internet connection or try again later."; + searchResults.message = { + content: "translate#viewModels.searchErrorOccurred" + }; }); } } diff --git a/lib/Models/SearchProviders/SearchProviderResults.ts b/lib/Models/SearchProviders/SearchProviderResults.ts index df3cf9e6538..9718de635d5 100644 --- a/lib/Models/SearchProviders/SearchProviderResults.ts +++ b/lib/Models/SearchProviders/SearchProviderResults.ts @@ -5,7 +5,12 @@ import SearchResult from "./SearchResult"; export default class SearchProviderResults { @observable results: SearchResult[] = []; - @observable message: string | undefined; + @observable message?: { + content: string; + params?: { + [key: string]: string | number | undefined; + }; + }; isCanceled = false; resultsCompletePromise: IPromiseBasedObservable = fromPromise( Promise.resolve() diff --git a/lib/ReactViews/Search/LocationSearchResults.tsx b/lib/ReactViews/Search/LocationSearchResults.tsx index 05020d7554d..4c70a0a9d5a 100644 --- a/lib/ReactViews/Search/LocationSearchResults.tsx +++ b/lib/ReactViews/Search/LocationSearchResults.tsx @@ -170,11 +170,11 @@ const SearchResultsFooter: React.FC = ( const { t } = useTranslation(); if (props.isExpanded) { return t("search.viewLess", { - name: props.name + name: useTranslationIfExists(props.name) }); } return t("search.viewMore", { - name: props.name + name: useTranslationIfExists(props.name) }); }; diff --git a/lib/ReactViews/Search/SearchHeader.tsx b/lib/ReactViews/Search/SearchHeader.tsx index 89a4202d251..ef74ed80732 100644 --- a/lib/ReactViews/Search/SearchHeader.tsx +++ b/lib/ReactViews/Search/SearchHeader.tsx @@ -1,11 +1,13 @@ import { observer } from "mobx-react"; import React from "react"; +import { useTranslationIfExists } from "../../Language/languageHelpers"; +import SearchProviderResults from "../../Models/SearchProviders/SearchProviderResults"; import { BoxSpan } from "../../Styled/Box"; import Text from "../../Styled/Text"; import Loader from "../Loader"; interface SearchHeaderProps { - searchResults: { [key: string]: any }; + searchResults: SearchProviderResults; isWaitingForSearchToStart: boolean; } @@ -20,7 +22,12 @@ const SearchHeader: React.FC = observer( } else if (props.searchResults.message) { return ( - {props.searchResults.message} + + {useTranslationIfExists( + props.searchResults.message.content, + props.searchResults.message.params + )} + ); } else { From c91a2403ed02d538c15e028b5b25159bd46109b7 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Fri, 3 Sep 2021 00:23:04 +0200 Subject: [PATCH 29/62] fix data catalog not showing search result messages --- lib/Models/SearchProviders/CatalogSearchProvider.ts | 10 ++++++++++ lib/ReactViews/DataCatalog/DataCatalog.jsx | 13 ++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/Models/SearchProviders/CatalogSearchProvider.ts b/lib/Models/SearchProviders/CatalogSearchProvider.ts index 7c02059e02b..9b9a550cf2d 100644 --- a/lib/Models/SearchProviders/CatalogSearchProvider.ts +++ b/lib/Models/SearchProviders/CatalogSearchProvider.ts @@ -7,6 +7,7 @@ import GroupMixin from "../../ModelMixins/GroupMixin"; import ReferenceMixin from "../../ModelMixins/ReferenceMixin"; import SearchProviderMixin from "../../ModelMixins/SearchProviders/SearchProviderMixin"; import CatalogSearchProviderTraits from "../../Traits/SearchProviders/CatalogSearchProviderTraits"; +import CommonStrata from "../Definition/CommonStrata"; import CreateModel from "../Definition/CreateModel"; import Terria from "../Terria"; import SearchProviderResults from "./SearchProviderResults"; @@ -110,6 +111,15 @@ export default class CatalogSearchProvider extends SearchProviderMixin( @observable isSearching: boolean = false; @observable debounceDurationOnceLoaded: number = 300; + constructor(id: string | undefined, terria: Terria) { + super(id, terria); + this.setTrait( + CommonStrata.defaults, + "minCharacters", + terria.configParameters.searchBar!.minCharacters + ); + } + get type() { return CatalogSearchProvider.type; } diff --git a/lib/ReactViews/DataCatalog/DataCatalog.jsx b/lib/ReactViews/DataCatalog/DataCatalog.jsx index d4ffbe7a58d..68c9d55db11 100644 --- a/lib/ReactViews/DataCatalog/DataCatalog.jsx +++ b/lib/ReactViews/DataCatalog/DataCatalog.jsx @@ -1,17 +1,12 @@ -import React from "react"; -import { observer } from "mobx-react"; - import createReactClass from "create-react-class"; - +import { observer } from "mobx-react"; import PropTypes from "prop-types"; +import React from "react"; import { withTranslation } from "react-i18next"; - import defined from "terriajs-cesium/Source/Core/defined"; - -import DataCatalogMember from "./DataCatalogMember"; import SearchHeader from "../Search/SearchHeader"; - import Styles from "./data-catalog.scss"; +import DataCatalogMember from "./DataCatalogMember"; // Displays the data catalog. export const DataCatalog = observer( @@ -48,7 +43,7 @@ export const DataCatalog = observer( Date: Fri, 3 Sep 2021 00:28:13 +0200 Subject: [PATCH 30/62] fix search in data catalogue button --- lib/ReactViews/Search/SearchBoxAndResults.jsx | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/ReactViews/Search/SearchBoxAndResults.jsx b/lib/ReactViews/Search/SearchBoxAndResults.jsx index 89b58cc5758..82d12719296 100644 --- a/lib/ReactViews/Search/SearchBoxAndResults.jsx +++ b/lib/ReactViews/Search/SearchBoxAndResults.jsx @@ -2,19 +2,20 @@ import { reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import PropTypes from "prop-types"; import React from "react"; -import { Trans } from "react-i18next"; +import { useTranslation } from "react-i18next"; import styled from "styled-components"; import { addMarker, removeMarker } from "../../Models/LocationMarkerUtils"; -import SearchBox from "../Search/SearchBox"; -import LocationSearchResults from "../Search/LocationSearchResults"; -import Icon, { StyledIcon } from "../../Styled/Icon"; import Box from "../../Styled/Box"; import { RawButton } from "../../Styled/Button"; +import Icon, { StyledIcon } from "../../Styled/Icon"; import Spacing from "../../Styled/Spacing"; import Text from "../../Styled/Text"; +import LocationSearchResults from "../Search/LocationSearchResults"; +import SearchBox from "../Search/SearchBox"; export function SearchInDataCatalog({ viewState, handleClick }) { const locationSearchText = viewState.searchState.locationSearchText; + const { t } = useTranslation(); return ( - - Search {locationSearchText} in the Data Catalogue - + {t("search.searchInDataCatalog", { + locationSearchText: locationSearchText + })} From 4b6221a3309d9248d32fd863d2dbb38e19c2f8d9 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Fri, 3 Sep 2021 00:43:15 +0200 Subject: [PATCH 31/62] update translation file --- lib/Language/en/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Language/en/translation.json b/lib/Language/en/translation.json index 14c19281190..9398615b849 100644 --- a/lib/Language/en/translation.json +++ b/lib/Language/en/translation.json @@ -446,7 +446,7 @@ "resultsLabel": "Search Results", "done": "Done", "data": "Data", - "searchInDataCatalog": "Search <1>'{{locationSearchText}}' in the Data Catalogue", + "searchInDataCatalog": "Search '{{locationSearchText}}' in the Data Catalogue", "search": "Search '{{searchText}}' in the Data Catalogue", "viewLess": "View less {{name}} results", "viewMore": "View more {{name}} results", From cc6ec14aa17e47fc306b40da2f72ae6af61dc26b Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sun, 17 Oct 2021 15:41:21 +0200 Subject: [PATCH 32/62] update bingMapsSearchProvider warning when there is no key --- .../SearchProviders/BingMapsSearchProvider.ts | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/Models/SearchProviders/BingMapsSearchProvider.ts b/lib/Models/SearchProviders/BingMapsSearchProvider.ts index 764c3c16574..f626b11dac7 100644 --- a/lib/Models/SearchProviders/BingMapsSearchProvider.ts +++ b/lib/Models/SearchProviders/BingMapsSearchProvider.ts @@ -29,22 +29,24 @@ export default class BingMapsSearchProvider extends LocationSearchProviderMixin( constructor(uniqueId: string | undefined, terria: Terria) { super(uniqueId, terria); - if (!this.key && this.terria.configParameters.bingMapsKey) { - this.setTrait( - CommonStrata.defaults, - "key", - this.terria.configParameters.bingMapsKey - ); - } - this.showWarning(); + runInAction(() => { + if (!this.key && this.terria.configParameters.bingMapsKey) { + this.setTrait( + CommonStrata.defaults, + "key", + this.terria.configParameters.bingMapsKey + ); + } + this.showWarning(); + }); } showWarning() { if (!this.key || this.key === "") { console.warn( - "The " + - useTranslationIfExists(this.name) + - " geocoder will always return no results because a Bing Maps key has not been provided. Please get a Bing Maps key from bingmapsportal.com and add it to parameters.bingMapsKey in config.json." + `The ${useTranslationIfExists(this.name)}(${ + this.type + }) geocoder will always return no results because a Bing Maps key has not been provided. Please get a Bing Maps key from bingmapsportal.com and add it to parameters.bingMapsKey in config.json.` ); } } From ce884cd1ad5bbe1d76af922cdf58a2e22ced6dff Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sun, 17 Oct 2021 17:25:47 +0200 Subject: [PATCH 33/62] use mixTraits of directly extending Traits --- lib/Traits/SearchProviders/BingMapsSearchProviderTraits.ts | 2 +- lib/Traits/SearchProviders/CatalogSearchProviderTraits.ts | 2 +- lib/Traits/SearchProviders/LocationSearchProviderTraits.ts | 7 +++++-- .../WebFeatureServiceSearchProviderTraits.ts | 5 ++++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/Traits/SearchProviders/BingMapsSearchProviderTraits.ts b/lib/Traits/SearchProviders/BingMapsSearchProviderTraits.ts index 81649828868..d233d921f0d 100644 --- a/lib/Traits/SearchProviders/BingMapsSearchProviderTraits.ts +++ b/lib/Traits/SearchProviders/BingMapsSearchProviderTraits.ts @@ -1,5 +1,5 @@ -import mixTraits from "../mixTraits"; import primitiveTrait from "../Decorators/primitiveTrait"; +import mixTraits from "../mixTraits"; import LocationSearchProviderTraits, { SearchProviderMapCenterTraits } from "./LocationSearchProviderTraits"; diff --git a/lib/Traits/SearchProviders/CatalogSearchProviderTraits.ts b/lib/Traits/SearchProviders/CatalogSearchProviderTraits.ts index 2dc2ff84927..bac19734806 100644 --- a/lib/Traits/SearchProviders/CatalogSearchProviderTraits.ts +++ b/lib/Traits/SearchProviders/CatalogSearchProviderTraits.ts @@ -1,6 +1,6 @@ +import primitiveTrait from "../Decorators/primitiveTrait"; import mixTraits from "../mixTraits"; import SearchProviderTraits from "./SearchProviderTraits"; -import primitiveTrait from "../Decorators/primitiveTrait"; export default class CatalogSearchProviderTraits extends mixTraits( SearchProviderTraits diff --git a/lib/Traits/SearchProviders/LocationSearchProviderTraits.ts b/lib/Traits/SearchProviders/LocationSearchProviderTraits.ts index cb00c39d7c8..8c21cf5f93b 100644 --- a/lib/Traits/SearchProviders/LocationSearchProviderTraits.ts +++ b/lib/Traits/SearchProviders/LocationSearchProviderTraits.ts @@ -1,8 +1,11 @@ -import ModelTraits from "../ModelTraits"; import primitiveTrait from "../Decorators/primitiveTrait"; +import mixTraits from "../mixTraits"; +import ModelTraits from "../ModelTraits"; import SearchProviderTraits from "./SearchProviderTraits"; -export default class LocationSearchProviderTraits extends SearchProviderTraits { +export default class LocationSearchProviderTraits extends mixTraits( + SearchProviderTraits +) { @primitiveTrait({ type: "string", name: "URL", diff --git a/lib/Traits/SearchProviders/WebFeatureServiceSearchProviderTraits.ts b/lib/Traits/SearchProviders/WebFeatureServiceSearchProviderTraits.ts index 1be4884242e..636585348d3 100644 --- a/lib/Traits/SearchProviders/WebFeatureServiceSearchProviderTraits.ts +++ b/lib/Traits/SearchProviders/WebFeatureServiceSearchProviderTraits.ts @@ -1,7 +1,10 @@ import primitiveTrait from "../Decorators/primitiveTrait"; +import mixTraits from "../mixTraits"; import LocationSearchProviderTraits from "./LocationSearchProviderTraits"; -export default class WebFeatureServiceSearchProviderTraits extends LocationSearchProviderTraits { +export default class WebFeatureServiceSearchProviderTraits extends mixTraits( + LocationSearchProviderTraits +) { @primitiveTrait({ type: "string", name: "Search property name", From 9713dedd9199056e6bcef208ce558c9bc7a00f91 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sun, 17 Oct 2021 17:26:38 +0200 Subject: [PATCH 34/62] remove deprecationWarning function as it's not needed anymore --- lib/Core/deprecationWarning.ts | 94 ---------------------------------- 1 file changed, 94 deletions(-) delete mode 100644 lib/Core/deprecationWarning.ts diff --git a/lib/Core/deprecationWarning.ts b/lib/Core/deprecationWarning.ts deleted file mode 100644 index 9b57e432663..00000000000 --- a/lib/Core/deprecationWarning.ts +++ /dev/null @@ -1,94 +0,0 @@ -import DeveloperError from "terriajs-cesium/Source/Core/DeveloperError"; -import defined from "terriajs-cesium/Source/Core/defined"; -import defaultValue from "terriajs-cesium/Source/Core/defaultValue"; - -/** - * Port of Cesium's functions `deprecationWarning` and `oneTimeWarning`. - */ -const warnings: { [key: string]: boolean } = {}; - -/** - * Logs a one time message to the console. Use this function instead of - * console.log directly since this does not log duplicate messages - * unless it is called from multiple workers. - * - * @function oneTimeWarning - * - * @param {String} identifier The unique identifier for this warning. - * @param {String} [message=identifier] The message to log to the console. - * - * @example - * for(var i=0;i>includeStart('debug', pragmas.debug); - if (!defined(identifier)) { - throw new DeveloperError("identifier is required."); - } - //>>includeEnd('debug'); - - if (!defined(warnings[identifier])) { - warnings[identifier] = true; - console.warn(defaultValue(message, identifier)); - } -} - -/** - * Logs a deprecation message to the console. Use this function instead of - * console.log directly since this does not log duplicate messages - * unless it is called from multiple workers. - * - * @function deprecationWarning - * - * @param {String} identifier The unique identifier for this deprecated API. - * @param {String} message The message to log to the console. - * - * @example - * // Deprecated function or class - * function Foo() { - * deprecationWarning('Foo', 'Foo was deprecated in Cesium 1.01. It will be removed in 1.03. Use newFoo instead.'); - * // ... - * } - * - * // Deprecated function - * Bar.prototype.func = function() { - * deprecationWarning('Bar.func', 'Bar.func() was deprecated in Cesium 1.01. It will be removed in 1.03. Use Bar.newFunc() instead.'); - * // ... - * }; - * - * // Deprecated property - * Object.defineProperties(Bar.prototype, { - * prop : { - * get : function() { - * deprecationWarning('Bar.prop', 'Bar.prop was deprecated in Cesium 1.01. It will be removed in 1.03. Use Bar.newProp instead.'); - * // ... - * }, - * set : function(value) { - * deprecationWarning('Bar.prop', 'Bar.prop was deprecated in Cesium 1.01. It will be removed in 1.03. Use Bar.newProp instead.'); - * // ... - * } - * } - * }); - * - * @private - */ -function deprecationWarning(identifier: string, message: string) { - //>>includeStart('debug', pragmas.debug); - if (!defined(identifier) || !defined(message)) { - throw new DeveloperError("identifier and message are required."); - } - //>>includeEnd('debug'); - - oneTimeWarning(identifier, message); -} - -export default deprecationWarning; From b95cf473604f1c5280b8d0608967ac8003c0f8c1 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sun, 17 Oct 2021 19:32:13 +0200 Subject: [PATCH 35/62] add search bar model and make it use traits --- lib/Core/TerriaError.ts | 2 +- .../SearchProviders/SearchProviderMixin.ts | 2 +- .../WebFeatureServiceSearchProviderMixin.ts | 2 +- .../SearchProviders/CatalogSearchProvider.ts | 5 +- lib/Models/SearchProviders/SearchBarModel.ts | 79 ++++++++++ .../createStubSearchProvider.ts | 2 +- .../upsertSearchProviderFromJson.ts | 13 +- lib/Models/Terria.ts | 143 +++--------------- lib/ReactViews/Mobile/MobileHeader.jsx | 2 +- .../Search/LocationSearchResults.tsx | 27 +++- lib/ReactViews/SidePanel/SidePanel.jsx | 13 +- lib/Traits/SearchProviders/SearchBarTraits.ts | 44 ++++++ 12 files changed, 189 insertions(+), 145 deletions(-) create mode 100644 lib/Models/SearchProviders/SearchBarModel.ts create mode 100644 lib/Traits/SearchProviders/SearchBarTraits.ts diff --git a/lib/Core/TerriaError.ts b/lib/Core/TerriaError.ts index e3dcbd17f12..eb8f1009e00 100644 --- a/lib/Core/TerriaError.ts +++ b/lib/Core/TerriaError.ts @@ -218,7 +218,7 @@ export default class TerriaError { // shouldRaiseToUser will be true if at least one error includes shouldRaiseToUser = true const shouldRaiseToUser = filteredErrors - .map(error => error._shouldRaiseToUser ?? false) + .map(error => error.shouldRaiseToUser ?? false) .includes(true); return new TerriaError({ diff --git a/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts index c4c4c015517..36107923d16 100644 --- a/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts @@ -48,7 +48,7 @@ function SearchProviderMixin>( (this.minCharacters && searchText.length < this.minCharacters) || (this.minCharacters === undefined && searchText.length < - this.terria.configParameters.searchBar!.minCharacters) + this.terria.configParameters.searchBarModel!.minCharacters) ) { return false; } diff --git a/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts index 3fa2a49af40..b7c6aa9c600 100644 --- a/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts @@ -222,7 +222,7 @@ function createZoomToFunction( const flightDurationSeconds: number = model.flightDurationSeconds || - model.terria.configParameters.searchBar!.flightDurationSeconds; + model.terria.configParameters.searchBarModel!.flightDurationSeconds; return function() { model.terria.currentViewer.zoomTo(rectangle, flightDurationSeconds); diff --git a/lib/Models/SearchProviders/CatalogSearchProvider.ts b/lib/Models/SearchProviders/CatalogSearchProvider.ts index 81c83c3d34c..b23c93a5d98 100644 --- a/lib/Models/SearchProviders/CatalogSearchProvider.ts +++ b/lib/Models/SearchProviders/CatalogSearchProvider.ts @@ -1,9 +1,8 @@ -import { autorun, computed, observable, runInAction } from "mobx"; +import { autorun, observable, runInAction } from "mobx"; import { Category, SearchAction } from "../../Core/AnalyticEvents/analyticEvents"; -import isDefined from "../../Core/isDefined"; import { TerriaErrorSeverity } from "../../Core/TerriaError"; import GroupMixin from "../../ModelMixins/GroupMixin"; import ReferenceMixin from "../../ModelMixins/ReferenceMixin"; @@ -119,7 +118,7 @@ export default class CatalogSearchProvider extends SearchProviderMixin( this.setTrait( CommonStrata.defaults, "minCharacters", - terria.configParameters.searchBar!.minCharacters + terria.configParameters.searchBarModel!.minCharacters ); } diff --git a/lib/Models/SearchProviders/SearchBarModel.ts b/lib/Models/SearchProviders/SearchBarModel.ts new file mode 100644 index 00000000000..5ba225bce88 --- /dev/null +++ b/lib/Models/SearchProviders/SearchBarModel.ts @@ -0,0 +1,79 @@ +import { action, isObservableArray, observable } from "mobx"; +import { DeveloperError } from "terriajs-cesium"; +import Result from "../../Core/Result"; +import TerriaError from "../../Core/TerriaError"; +import { SearchBarTraits } from "../../Traits/SearchProviders/SearchBarTraits"; +import CommonStrata from "../Definition/CommonStrata"; +import CreateModel from "../Definition/CreateModel"; +import { BaseModel } from "../Definition/Model"; +import Terria from "../Terria"; +import SearchProviderFactory from "./SearchProviderFactory"; +import upsertSearchProviderFromJson from "./upsertSearchProviderFromJson"; + +export class SearchBarModel extends CreateModel(SearchBarTraits) { + private locationSearchProviders = observable.map(); + + constructor(readonly terria: Terria) { + super("search-bar-model", terria); + } + + initializeSearchProviders() { + const errors: TerriaError[] = []; + + const searchProviders = this.terria.configParameters.searchProviders; + + if (!isObservableArray(searchProviders)) { + errors.push( + new TerriaError({ + sender: SearchProviderFactory, + title: "SearchProviders", + message: { key: "searchProvider.noSearchProviders" } + }) + ); + } + searchProviders?.forEach(searchProvider => { + upsertSearchProviderFromJson( + SearchProviderFactory, + this.terria, + CommonStrata.definition, + searchProvider + ).pushErrorTo(errors); + }); + + return new Result( + undefined, + TerriaError.combine( + errors, + "An error occurred while loading search providers" + ) + ); + } + + /** + * Add new SearchProvider to the list of SearchProviders. + */ + @action + addSearchProvider(model: BaseModel) { + if (model.uniqueId === undefined) { + throw new DeveloperError( + "A SearchProvider without a `uniqueId` cannot be added." + ); + } + + if (this.locationSearchProviders.has(model.uniqueId)) { + console.log( + new DeveloperError( + "A SearchProvider with the specified ID already exists." + ) + ); + } + + this.locationSearchProviders.set(model.uniqueId, model); + } + + get locationSearchProvidersArray() { + return [...this.locationSearchProviders.entries()].map(function(entry) { + return entry[1]; + }); + } +} diff --git a/lib/Models/SearchProviders/createStubSearchProvider.ts b/lib/Models/SearchProviders/createStubSearchProvider.ts index e72e219326f..e7098bad3fb 100644 --- a/lib/Models/SearchProviders/createStubSearchProvider.ts +++ b/lib/Models/SearchProviders/createStubSearchProvider.ts @@ -22,6 +22,6 @@ export default function createStubSearchProvider( const stub = new StubSearchProvider(idToUse, terria); stub.setTrait(CommonStrata.underride, "name", stub.uniqueId); - terria.addSearchProvider(stub); + terria.configParameters.searchBarModel?.addSearchProvider(stub); return stub; } diff --git a/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts index f583b591a95..4f669113fb8 100644 --- a/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts +++ b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts @@ -1,6 +1,7 @@ import i18next from "i18next"; import Result from "../../Core/Result"; import TerriaError from "../../Core/TerriaError"; +import { useTranslationIfExists } from "../../Language/languageHelpers"; import CommonStrata from "../Definition/CommonStrata"; import { BaseModel } from "../Definition/Model"; import ModelFactory from "../Definition/ModelFactory"; @@ -52,7 +53,7 @@ export default function upsertSearchProviderFromJson( if (model.type !== StubSearchProvider.type) { try { - model.terria.addSearchProvider(model); + model.terria.configParameters.searchBarModel?.addSearchProvider(model); } catch (error) { errors.push(error); } @@ -70,7 +71,9 @@ export default function upsertSearchProviderFromJson( model, TerriaError.combine( errors, - `Error upserting search provider JSON: \`${uniqueId}\`` + `Error upserting search provider JSON: \`${useTranslationIfExists( + uniqueId + )}\`` ) ); } @@ -81,18 +84,18 @@ function setDefaultTraits(model: BaseModel) { model.setTrait( CommonStrata.defaults, "flightDurationSeconds", - terria.configParameters.searchBar!.flightDurationSeconds + terria.configParameters.searchBarModel?.flightDurationSeconds ); model.setTrait( CommonStrata.defaults, "minCharacters", - terria.configParameters.searchBar!.minCharacters + terria.configParameters.searchBarModel?.minCharacters ); model.setTrait( CommonStrata.defaults, "recommendedListLength", - terria.configParameters.searchBar!.recommendedListLength + terria.configParameters.searchBarModel?.recommendedListLength ); } diff --git a/lib/Models/Terria.ts b/lib/Models/Terria.ts index 245ab5532bc..7ed0426eccc 100644 --- a/lib/Models/Terria.ts +++ b/lib/Models/Terria.ts @@ -1,14 +1,6 @@ import { Share } from "catalog-converter"; import i18next from "i18next"; -import { - action, - computed, - isObservableArray, - observable, - runInAction, - toJS, - when -} from "mobx"; +import { action, computed, observable, runInAction, toJS, when } from "mobx"; import { createTransformer } from "mobx-utils"; import Clock from "terriajs-cesium/Source/Core/Clock"; import defaultValue from "terriajs-cesium/Source/Core/defaultValue"; @@ -101,9 +93,8 @@ import Internationalization, { } from "./Internationalization"; import MapInteractionMode from "./MapInteractionMode"; import NoViewer from "./NoViewer"; -import SearchProviderFactory from "./SearchProviders/SearchProviderFactory"; -import upsertSearchProviderFromJson from "./SearchProviders/upsertSearchProviderFromJson"; import CatalogIndex from "./SearchProviders/CatalogIndex"; +import { SearchBarModel } from "./SearchProviders/SearchBarModel"; import ShareDataService from "./ShareDataService"; import TimelineStack from "./TimelineStack"; import ViewerMode from "./ViewerMode"; @@ -292,36 +283,7 @@ interface ConfigParameters { /** * The search bar allows requesting information from various search services at once. */ - searchBar?: SearchBar; -} - -interface SearchBar { - /** - * Input text field placeholder shown when no input has been given yet. The string is translateable. - * @default "translate#search.placeholder" - */ - placeholder: string; - /** - * Maximum amount of entries in the suggestion list. - * @default 5 - */ - recommendedListLength: number; - /** - * The duration of the camera flight to an entered location, in seconds. - * @default 1.5 - */ - flightDurationSeconds: number; - /** - * Minimum number of characters to start search. - */ - minCharacters: number; - /** - * Bounding box limits for the search results. - */ - boundingBoxLimit?: number[]; - /** - * Array of search providers to be used. - */ + searchBarModel?: SearchBarModel; searchProviders: any[]; } @@ -379,7 +341,7 @@ interface HomeCameraInit { export default class Terria { private readonly models = observable.map(); - private locationSearchProviders = observable.map(); + private searchProviders: any[] = []; /** Map from share key -> id */ readonly shareKeysMap = observable.map(); /** Map from id -> share keys */ @@ -499,13 +461,8 @@ export default class Terria { }, { text: "map.extraCreditLinks.disclaimer", url: "about.html#disclaimer" } ], - searchBar: { - placeholder: "translate#search.placeholder", - recommendedListLength: 5, - flightDurationSeconds: 1.5, - minCharacters: 3, - searchProviders: [] - } + searchBarModel: new SearchBarModel(this), + searchProviders: [] }; @observable @@ -702,34 +659,6 @@ export default class Terria { shareKeys?.forEach(shareKey => this.addShareKey(model.uniqueId!, shareKey)); } - /** - * Add new SearchProvider to the list of SearchProviders. - */ - @action - addSearchProvider(model: BaseModel) { - if (model.uniqueId === undefined) { - throw new DeveloperError( - "A SearchProvider without a `uniqueId` cannot be added." - ); - } - - if (this.locationSearchProviders.has(model.uniqueId)) { - console.log( - new DeveloperError( - "A SearchProvider with the specified ID already exists." - ) - ); - } - - this.locationSearchProviders.set(model.uniqueId, model); - } - - get locationSearchProvidersArray() { - return [...this.locationSearchProviders.entries()].map(function(entry) { - return entry[1]; - }); - } - /** * Remove references to a model from Terria. */ @@ -887,6 +816,8 @@ export default class Terria { if (isJsonObject(config) && isJsonObject(config.parameters)) { this.updateParameters(config.parameters); } + if (isJsonObject(config) && Array.isArray(config.searchProviders)) { + } if (this.configParameters.errorService) { this.setupErrorServiceProvider(this.configParameters.errorService); } @@ -934,11 +865,13 @@ export default class Terria { ) ); - this.initializeSearchProviders().catchError(error => - this.raiseErrorToUser( - TerriaError.from(error, "Failed to initialize searchProviders") - ) - ); + this.configParameters.searchBarModel + ?.initializeSearchProviders() + .catchError(error => + this.raiseErrorToUser( + TerriaError.from(error, "Failed to initialize searchProviders") + ) + ); if (options.applicationUrl) { (await this.updateApplicationUrl(options.applicationUrl.href)).raiseError( @@ -975,36 +908,6 @@ export default class Terria { } } - initializeSearchProviders() { - const errors: TerriaError[] = []; - let searchProviders = this.configParameters.searchBar?.searchProviders; - if (!isObservableArray(searchProviders)) { - errors.push( - new TerriaError({ - sender: SearchProviderFactory, - title: "SearchProviders", - message: { key: "searchProvider.noSearchProviders" } - }) - ); - } - searchProviders?.forEach(searchProvider => { - upsertSearchProviderFromJson( - SearchProviderFactory, - this, - CommonStrata.definition, - searchProvider - ).pushErrorTo(errors); - }); - - return new Result( - undefined, - TerriaError.combine( - errors, - "An error occurred while loading search providers" - ) - ); - } - async loadPersistedOrInitBaseMap() { const baseMapItems = this.baseMapsModel.baseMapItems; // Set baseMap fallback to first option @@ -1095,13 +998,15 @@ export default class Terria { updateParameters(parameters: ConfigParameters | JsonObject): void { Object.entries(parameters).forEach(([key, value]) => { if (this.configParameters.hasOwnProperty(key)) { - if (key === "searchBar") { - // merge default and new - //@ts-ignore - this.configParameters[key] = { - ...this.configParameters[key], - ...value - }; + if (key === "searchBarModel") { + if (!isDefined(this.configParameters.searchBarModel)) { + this.configParameters.searchBarModel = new SearchBarModel(this); + } + updateModelFromJson( + this.configParameters.searchBarModel!, + CommonStrata.definition, + value + ); } else { (this.configParameters as any)[key] = value; } diff --git a/lib/ReactViews/Mobile/MobileHeader.jsx b/lib/ReactViews/Mobile/MobileHeader.jsx index c0becf1bb4c..37edae88734 100644 --- a/lib/ReactViews/Mobile/MobileHeader.jsx +++ b/lib/ReactViews/Mobile/MobileHeader.jsx @@ -245,7 +245,7 @@ const MobileHeader = observer( onSearchTextChanged={this.changeLocationSearchText} onDoSearch={this.searchLocations} placeholder={useTranslationIfExists( - terria.configParameters.searchBar.placeholder + terria.configParameters.searchBarModel.placeholder )} alwaysShowClear={true} onClear={this.closeLocationSearch} diff --git a/lib/ReactViews/Search/LocationSearchResults.tsx b/lib/ReactViews/Search/LocationSearchResults.tsx index 4c70a0a9d5a..c51a240aa77 100644 --- a/lib/ReactViews/Search/LocationSearchResults.tsx +++ b/lib/ReactViews/Search/LocationSearchResults.tsx @@ -62,14 +62,29 @@ class LocationSearchResults extends React.Component { get validResults() { const { search, terria } = this.props; const locationSearchBoundingBox = - terria.configParameters.searchBar?.boundingBoxLimit; - const validResults = isDefined(locationSearchBoundingBox) + terria.configParameters.searchBarModel?.boundingBoxLimit; + let filterResults = false; + let west: number | undefined, + east: number | undefined, + south: number | undefined, + north: number | undefined; + if (locationSearchBoundingBox) { + ({ west, east, south, north } = locationSearchBoundingBox); + + filterResults = + isDefined(west) && + isDefined(east) && + isDefined(south) && + isDefined(north); + } + + const validResults = filterResults ? search.results.filter(function(r: any) { return ( - r.location.longitude > locationSearchBoundingBox[0] && - r.location.longitude < locationSearchBoundingBox[2] && - r.location.latitude > locationSearchBoundingBox[1] && - r.location.latitude < locationSearchBoundingBox[3] + r.location.longitude > west! && + r.location.longitude < east! && + r.location.latitude > south! && + r.location.latitude < north! ); }) : search.results; diff --git a/lib/ReactViews/SidePanel/SidePanel.jsx b/lib/ReactViews/SidePanel/SidePanel.jsx index 56752e409e7..8de9a888293 100644 --- a/lib/ReactViews/SidePanel/SidePanel.jsx +++ b/lib/ReactViews/SidePanel/SidePanel.jsx @@ -5,17 +5,16 @@ import React from "react"; import { withTranslation } from "react-i18next"; import styled, { withTheme } from "styled-components"; import { useTranslationIfExists } from "../../Language/languageHelpers"; -import { useRefForTerria } from "../Hooks/useRefForTerria"; +import Box from "../../Styled/Box"; +import Button from "../../Styled/Button"; import Icon, { StyledIcon } from "../../Styled/Icon"; +import Spacing from "../../Styled/Spacing"; +import Text from "../../Styled/Text"; +import { useRefForTerria } from "../Hooks/useRefForTerria"; import SearchBoxAndResults from "../Search/SearchBoxAndResults"; import Workbench from "../Workbench/Workbench"; import FullScreenButton from "./FullScreenButton"; -import Box from "../../Styled/Box"; -import Spacing from "../../Styled/Spacing"; -import Text from "../../Styled/Text"; -import Button from "../../Styled/Button"; - const BoxHelpfulHints = styled(Box)``; const ResponsiveSpacing = styled(Box)` @@ -172,7 +171,7 @@ const SidePanel = observer( viewState={this.props.viewState} terria={this.props.terria} placeholder={useTranslationIfExists( - this.props.terria.configParameters.searchBar.placeholder + this.props.terria.configParameters.searchBarModel.placeholder )} /> diff --git a/lib/Traits/SearchProviders/SearchBarTraits.ts b/lib/Traits/SearchProviders/SearchBarTraits.ts new file mode 100644 index 00000000000..e54e2b53e67 --- /dev/null +++ b/lib/Traits/SearchProviders/SearchBarTraits.ts @@ -0,0 +1,44 @@ +import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; +import objectTrait from "../Decorators/objectTrait"; +import primitiveTrait from "../Decorators/primitiveTrait"; +import ModelTraits from "../ModelTraits"; +import { RectangleTraits } from "../TraitsClasses/MappableTraits"; + +export class SearchBarTraits extends ModelTraits { + @primitiveTrait({ + type: "string", + name: "placeholder", + description: + "Input text field placeholder shown when no input has been given yet. The string is translateable." + }) + placeholder: string = "translate#search.placeholder"; + + @primitiveTrait({ + type: "number", + name: "Recommended list length", + description: "Maximum amount of entries in the suggestion list." + }) + recommendedListLength: number = 5; + + @primitiveTrait({ + type: "number", + name: "Flight duration seconds", + description: + "The duration of the camera flight to an entered location, in seconds." + }) + flightDurationSeconds: number = 1.5; + + @primitiveTrait({ + type: "number", + name: "Minimum characters", + description: "Minimum number of characters required for search to start" + }) + minCharacters: number = 3; + + @objectTrait({ + type: RectangleTraits, + name: "Minimum characters", + description: "Minimum number of characters required for search to start" + }) + boundingBoxLimit?: RectangleTraits = Rectangle.MAX_VALUE; +} From 5bb733efa74a896afc77914f88c58cc3b8f567d5 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sun, 17 Oct 2021 19:49:58 +0200 Subject: [PATCH 36/62] update docs --- doc/customizing/client-side-config.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/customizing/client-side-config.md b/doc/customizing/client-side-config.md index d3b4c921406..1013fbb661d 100644 --- a/doc/customizing/client-side-config.md +++ b/doc/customizing/client-side-config.md @@ -89,6 +89,8 @@ Specifies various options for configuring TerriaJS: |feedbackPreamble|no|**string**|feedback.feedbackPreamble|Text showing at the top of feedback form, supports the internationalization using the translation key.| |feedbackMinLength|no|**number**|0|Minimum length of feedback comment.| |`theme`|no|**any**|`{}`|An object used to override theme properties - for example `{"logoHeight": "70px"}`.| +|`searchBar`|no|**[SearchBar](#searchbar)**|`new SearchBar()`|Search bar configuration| +|`searchProviders`|no|**[SearchProviders](#searchbarproviders)|`[]`|Search providers that will be used for search| ### MagdaReferenceHeaders @@ -149,6 +151,7 @@ Configuration of items to appear in the search bar *** ### ErrorServiceOptions + |Name|Required|Type|Default|Description| |----|--------|----|-------|-----------| |provider|yes|**string**|`undefined`|A string identifying the error service provider to use. Currently only `rollbar` is supported.| @@ -203,3 +206,16 @@ This file will have to be re-generated manually every time the catalog structure - if items are renamed, or moved - dynamic groups are updated (for example, WMS server publishes new layers) + +### SearchBar + +Configuration for the search bar. Some of the values will be used as default for +search provider values. + +|Name|Required|Type|Default|Description| +|----|--------|----|-------|-----------| +|placeholder|no|**string**|`translate#search.placeholder`|Input text field placeholder shown when no input has been given yet. The string is translateable.| +|recommendedListLength|no|**number**|`5`|Maximum amount of entries in the suggestion list.| +|flightDurationSeconds|no|**number**|`1.5`|The duration of the camera flight to an entered location, in seconds.| +|minCharacters|no|**number**|3|Minimum number of characters required for search to start| +|boundingBoxLimit|no|**Rectangle**|`Cesium.Rectangle.MAX_VALUE`|Bounding box limits for the search results {west, south, east, north}| From 1523c81ede94b5e4cbe757b6de218a4467e7b423 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sun, 17 Oct 2021 19:50:44 +0200 Subject: [PATCH 37/62] update trait description --- lib/Traits/SearchProviders/SearchBarTraits.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Traits/SearchProviders/SearchBarTraits.ts b/lib/Traits/SearchProviders/SearchBarTraits.ts index e54e2b53e67..85c9bbb2da3 100644 --- a/lib/Traits/SearchProviders/SearchBarTraits.ts +++ b/lib/Traits/SearchProviders/SearchBarTraits.ts @@ -37,8 +37,9 @@ export class SearchBarTraits extends ModelTraits { @objectTrait({ type: RectangleTraits, - name: "Minimum characters", - description: "Minimum number of characters required for search to start" + name: "Bounding box limit", + description: + "Bounding box limits for the search results {west, south, east, north}" }) boundingBoxLimit?: RectangleTraits = Rectangle.MAX_VALUE; } From f424f70bf2494b7fc6e7558f54b697d0b9304d36 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sun, 17 Oct 2021 21:54:53 +0200 Subject: [PATCH 38/62] fix import from terriajs-cesium --- lib/Models/SearchProviders/SearchBarModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Models/SearchProviders/SearchBarModel.ts b/lib/Models/SearchProviders/SearchBarModel.ts index 5ba225bce88..8f4ebec25bc 100644 --- a/lib/Models/SearchProviders/SearchBarModel.ts +++ b/lib/Models/SearchProviders/SearchBarModel.ts @@ -1,5 +1,5 @@ import { action, isObservableArray, observable } from "mobx"; -import { DeveloperError } from "terriajs-cesium"; +import DeveloperError from "terriajs-cesium/Source/Core/DeveloperError"; import Result from "../../Core/Result"; import TerriaError from "../../Core/TerriaError"; import { SearchBarTraits } from "../../Traits/SearchProviders/SearchBarTraits"; From cc7fabdb09b85c5f395ce0e4a9946573e9f90508 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sun, 17 Oct 2021 23:48:47 +0200 Subject: [PATCH 39/62] add docs --- doc/customizing/client-side-config.md | 29 +----- doc/customizing/search-providers.md | 127 ++++++++++++++++++++++++++ doc/mkdocs.yml | 1 + 3 files changed, 130 insertions(+), 27 deletions(-) create mode 100644 doc/customizing/search-providers.md diff --git a/doc/customizing/client-side-config.md b/doc/customizing/client-side-config.md index 1013fbb661d..4d141dc934f 100644 --- a/doc/customizing/client-side-config.md +++ b/doc/customizing/client-side-config.md @@ -46,7 +46,7 @@ Specifies various options for configuring TerriaJS: |`supportEmail`|no|**string**|`"info@terria.io"`|The email address shown when things go wrong.| |`defaultMaximumShownFeatureInfos`|no|**number**|`100`|The maximum number of "feature info" boxes that can be displayed when clicking a point.| |`regionMappingDefinitionsUrl`|yes|**string**|`"build/TerriaJS/data/regionMapping.json"`|URL of the JSON file that defines region mapping for CSV files. This option only needs to be changed in unusual deployments. It has to be changed if deploying as static site, for instance.| -|`catalogIndexUrl`|no|**string**||URL of the JSON file that contains index of catalog. See [CatalogIndex](#catalogindex)| +|`catalogIndexUrl`|no|**string**||URL of the JSON file that contains index of catalog. See [CatalogIndex](search-providers.md#catalogindex)| |`conversionServiceBaseUrl`|no|**string**|`"convert/"`|URL of OGR2OGR conversion service (part of TerriaJS-Server). This option only needs to be changed in unusual deployments. It has to be changed if deploying as static site, for instance.| |`proj4ServiceBaseUrl`|no|**string**|`"proj4def/"`|URL of Proj4 projection lookup service (part of TerriaJS-Server). This option only needs to be changed in unusual deployments. It has to be changed if deploying as static site, for instance.| |`corsProxyBaseUrl`|no|**string**|`"proxy/"`|URL of CORS proxy service (part of TerriaJS-Server). This option only needs to be changed in unusual deployments. It has to be changed if deploying as static site, for instance.| @@ -90,7 +90,7 @@ Specifies various options for configuring TerriaJS: |feedbackMinLength|no|**number**|0|Minimum length of feedback comment.| |`theme`|no|**any**|`{}`|An object used to override theme properties - for example `{"logoHeight": "70px"}`.| |`searchBar`|no|**[SearchBar](#searchbar)**|`new SearchBar()`|Search bar configuration| -|`searchProviders`|no|**[SearchProviders](#searchbarproviders)|`[]`|Search providers that will be used for search| +|`searchProviders`|no|**[SearchProviders](search-providers.md)|`[]`|Search providers that will be used for search| ### MagdaReferenceHeaders @@ -182,31 +182,6 @@ Configuration of items to appear in the search bar *** -### CatalogIndex - -If your TerriaMap has many (>50) dynamic groups (groups which need to be loaded - for example CKAN, WMS-group...) it may be worth generating a static catalog index JSON file. This file will contain ID, name and description fields of all catalog items, which can be used to search through the catalog very quickly without needing to load dynamic groups. - -The https://github.com/nextapps-de/flexsearch library is used to index and search the catalog index file. - -**Note** NodeJS v10 is not supported, please use v12 or v14. - -To generate the catalog index: - -- `npm run build-tools` -- `node .\build\generateCatalogIndex.js config-url base-url` where - - `config-url` is URL to client-side-config file - - `base-url` is URL to terriajs-server (this is used to load `server-config` and to proxy requests) - - For example `node .\build\generateCatalogIndex.js http://localhost:3001/config.json http://localhost:3001` -- This will output two files - - `catalog-index.json` - - `catalog-index-errors.json` with any error messages which occurred while loading catalog members -- Set `catalogIndexUrl` config parameter - -This file will have to be re-generated manually every time the catalog structure changes - for example: - -- if items are renamed, or moved -- dynamic groups are updated (for example, WMS server publishes new layers) - ### SearchBar Configuration for the search bar. Some of the values will be used as default for diff --git a/doc/customizing/search-providers.md b/doc/customizing/search-providers.md new file mode 100644 index 00000000000..f2d9fa6935e --- /dev/null +++ b/doc/customizing/search-providers.md @@ -0,0 +1,127 @@ +# Search providers + +Terriajs supports 2 types of search providers + +1. Catalog search provider +2. Location search providers + +Each search provider can be configured using following options + +|Name|Required|Type|Default|Description| +|----|--------|----|-------|-----------| +|name|no|**string**|`unknown`|Name of the search provider.| +|minCharacters|no|**number**|`catalogParameters.searchBar.minCharacters`|Minimum number of characters required for search to start.| + +## Catalog search provider + +`type: catalog-search-provider` + +Catalog search provider is used to find the desired dataset. Catalog search provider can be used with or without static catalog index JSON file. Without catalog index each catalog group and item will be dynamically fetched from remote servers in the moment of the search, and for bigger catalog this will cause poor performance of search. For example when having WMS-group in catalog searching in that catalog will cause catalog to issue `getCapabilities` request, wait for response and then perform the search. TerriaJS supports only search provider of type `catalog-search-provider` + +### CatalogIndex + +If your TerriaMap has many (>50) dynamic groups (groups which need to be loaded - for example CKAN, WMS-group...) it may be worth generating a static catalog index JSON file. This file will contain ID, name and description fields of all catalog items, which can be used to search through the catalog very quickly without needing to load dynamic groups. + +The [flexsearch](https://github.com/nextapps-de/flexsearch) library is used to index and search the catalog index file. + +**Note** NodeJS v10 is not supported, please use v12 or v14. + +To generate the catalog index: + +- `npm run build-tools` +- `node .\build\generateCatalogIndex.js config-url base-url` where + - `config-url` is URL to client-side-config file + - `base-url` is URL to terriajs-server (this is used to load `server-config` and to proxy requests) + - For example `node .\build\generateCatalogIndex.js http://localhost:3001/config.json http://localhost:3001` +- This will output two files + - `catalog-index.json` + - `catalog-index-errors.json` with any error messages which occurred while loading catalog members +- Set `catalogIndexUrl` config parameter + +This file will have to be re-generated manually every time the catalog structure changes - for example: + +- if items are renamed, or moved +- dynamic groups are updated (for example, WMS server publishes new layers) + +## Location search providers + +Location search providers are used to search for locations on the map. TerriaJS currently supports two implementations of search providers: + +- [`BingMapsSearchProvider`](#bingmapssearchprovider) - implementation which in background uses Bing Map search API +- [`AustralianGazetteerSearchProvider`](#australiangazetteersearchprovider) - uses `WebFeatureServiceSearchProvider` + +Each `LocationSearchProvider support following confing options + +|Name|Required|Type|Default|Description| +|----|--------|----|-------|-----------| +|url|yes|**string**|`""`|The URL of search provider.| +|recommendedListLength|no|**number**|`5`|Default amount of entries in the suggestion list.| +|flightDurationSeconds|no|**number**|`1.5`|Time to move to the result location.| +|isOpen|no|**boolean**|`true`|True if the search results of this search provider are visible by default; otherwise, false (user manually open search results).| + +### BingMapsSearchProvider + +`type: bing-maps-search-provider` + +Bing maps search provider is based on commercial API which is provided by BingMaps. To enable it, it is necessary to add an apropriate Bing Maps API key as config parameter. This search provider as results returns addresses and a place name locations. + +|Name|Required|Type|Default|Description| +|----|--------|----|-------|-----------| +|`key`|no|**string**|`configParameters.bingMapsKey`|The Bing Maps key.| +|primaryCountry|no|**string**|`Australia`|Name of the country to prioritize the search results.| +|`culture`|no|**string**|`en-au`|Use the culture parameter to specify a culture for your request.The culture parameter provides the result in the language of the culture. For a list of supported cultures, see [Supported Culture Codes](https://docs.microsoft.com/en-us/bingmaps/rest-services/common-parameters-and-types/supported-culture-codes)| +|`mapCenter`|no|**boolean**|`true`|Whether the current location of the map center is supplied with search request| + +It provides a default value for `url: https://dev.virtualearth.net/` + +**Example** + +```json +{ + "id": "search-provider/bing-maps", + "type": "bing-maps-search-provider", + "name": "translate#viewModels.searchLocations", + "url": "https://dev.virtualearth.net/", + "flightDurationSeconds": 1.5, + "minCharacters": 5, + "isOpen": true +}, +``` + +### AustralianGazetteerSearchProvider + +`type: australian-gazetteer-search-provider` + +Australian gazzetteer search provider is based on web feature service that uses an official place names of Australia. It is based on `WebFeatureServiceProvider`. +It can be configured using following options + +|Name|Required|Type|Default|Description| +|----|--------|----|-------|-----------| +|`searchPropertyName`|yes|**string**|`undefined`|Which property to look for the search text in| +|`searchPropertyTypeName`|yes|**string**|`undefined`|Type of the properties to search| + +**Example** + +```json +{ + "id": "search-provider/australian-gazetteer", + "type": "australian-gazetteer-search-provider", + "name": "translate#viewModels.searchPlaceNames", + "url": "http://services.ga.gov.au/gis/services/Australian_Gazetteer/MapServer/WFSServer", + "searchPropertyName": "Australian_Gazetteer:NameU", + "searchPropertyTypeName": "Australian_Gazetteer:Gazetteer_of_Australia", + "flightDurationSeconds": 1.5, + "minCharacters": 3, + "recommendedListLength": 3, + "isOpen": false +} +``` + +### Implementing new location search provider + +Implementing new location search provider is similar to implementing new `CatalogItems` and `CatalogGroups`. Each of them should be based on the usage of one of the mixins + +- `LocationSearchProviderMixin` - should be used for API based location search providers. Example of such search provider is `BingMapSearchProvider`. +- `WebFeatureServiceSearchProviderMixin` - should be used for location search providers that will rely on data provided by `WebFeatureService`. Example of such search provider is `AustralianGazetteerSearchProvider`. + +Each new `SearchProvider` should be registered inside `registerSearchProvider` so they can be properly upserted from json definition provider in config file. diff --git a/doc/mkdocs.yml b/doc/mkdocs.yml index 638656a8038..3a7e4b8d141 100644 --- a/doc/mkdocs.yml +++ b/doc/mkdocs.yml @@ -17,6 +17,7 @@ nav: - Server-side Config: customizing/server-side-config.md - Skinning: customizing/skinning.md - Translation guide: customizing/translation-guide.md + - Search Providers: customizing/search-providers.md - Connecting to Data: - Overview: connecting-to-data/README.md - Cross-Origin Resource Sharing: connecting-to-data/cross-origin-resource-sharing.md From 7214e69f91b4c745672f53a24214ad6fa236a963 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Fri, 3 Nov 2023 02:56:22 +0100 Subject: [PATCH 40/62] fix: white screen --- lib/Models/SearchProviders/CatalogSearchProvider.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/Models/SearchProviders/CatalogSearchProvider.ts b/lib/Models/SearchProviders/CatalogSearchProvider.ts index e1b113b4fb4..920aa16101d 100644 --- a/lib/Models/SearchProviders/CatalogSearchProvider.ts +++ b/lib/Models/SearchProviders/CatalogSearchProvider.ts @@ -3,7 +3,8 @@ import { computed, observable, runInAction, - makeObservable + makeObservable, + override } from "mobx"; import { fromPromise } from "mobx-utils"; import { @@ -137,7 +138,7 @@ export default class CatalogSearchProvider extends SearchProviderMixin( return CatalogSearchProvider.type; } - @computed get resultsAreReferences() { + @override get resultsAreReferences() { return ( isDefined(this.terria.catalogIndex?.loadPromise) && fromPromise(this.terria.catalogIndex!.loadPromise).state === "fulfilled" From 44236d0e97b78821196f80214479329e790b3877 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Fri, 3 Nov 2023 03:00:09 +0100 Subject: [PATCH 41/62] fix: remove unused property --- lib/Models/Terria.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Models/Terria.ts b/lib/Models/Terria.ts index a352d8f050c..d02935554e8 100644 --- a/lib/Models/Terria.ts +++ b/lib/Models/Terria.ts @@ -417,7 +417,7 @@ interface HomeCameraInit { export default class Terria { private readonly models = observable.map(); - private searchProviders: any[] = []; + /** Map from share key -> id */ readonly shareKeysMap = observable.map(); /** Map from id -> share keys */ From 5ded654c251c0a9582864537c5b38b8414883697 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Fri, 3 Nov 2023 10:06:35 +0100 Subject: [PATCH 42/62] fix: rename ADR --- ...-search-providers.md => 0011-configurable-search-providers.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename architecture/{0007-configurable-search-providers.md => 0011-configurable-search-providers.md} (100%) diff --git a/architecture/0007-configurable-search-providers.md b/architecture/0011-configurable-search-providers.md similarity index 100% rename from architecture/0007-configurable-search-providers.md rename to architecture/0011-configurable-search-providers.md From 97920ceac3f264d9ce6212e3a6874c78108875a8 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sat, 4 Nov 2023 14:18:52 +0100 Subject: [PATCH 43/62] tests: reorganize tests and test search provider mixin --- .../SearchProviderMixinSpec.ts | 76 +++++++++++++++++++ .../BingMapsSearchProviderSpec.ts} | 0 .../LocationSearchProviderSpec.ts} | 0 3 files changed, 76 insertions(+) create mode 100644 test/ModelMixins/SearchProviders/SearchProviderMixinSpec.ts rename test/{Traits/SearchProviders/BingMapsSearchProviderTraitsSpec.ts => Models/SearchProviders/BingMapsSearchProviderSpec.ts} (100%) rename test/{Traits/SearchProviders/LocationSearchProviderTraitsSpec.ts => Models/SearchProviders/LocationSearchProviderSpec.ts} (100%) diff --git a/test/ModelMixins/SearchProviders/SearchProviderMixinSpec.ts b/test/ModelMixins/SearchProviders/SearchProviderMixinSpec.ts new file mode 100644 index 00000000000..54c07c34b38 --- /dev/null +++ b/test/ModelMixins/SearchProviders/SearchProviderMixinSpec.ts @@ -0,0 +1,76 @@ +import { CommonStrata } from "terriajs-plugin-api"; +import SearchProviderMixin from "../../../lib/ModelMixins/SearchProviders/SearchProviderMixin"; +import CreateModel from "../../../lib/Models/Definition/CreateModel"; +import SearchProviderResults from "../../../lib/Models/SearchProviders/SearchProviderResults"; +import Terria from "../../../lib/Models/Terria"; +import BingMapsSearchProviderTraits from "../../../lib/Traits/SearchProviders/BingMapsSearchProviderTraits"; + +class TestSearchProvider extends SearchProviderMixin( + CreateModel(BingMapsSearchProviderTraits) +) { + type = "test"; + + constructor(uniqueId: string | undefined, terria: Terria) { + super(uniqueId, terria); + } + + public override logEvent = jasmine.createSpy(); + public override doSearch = jasmine + .createSpy() + .and.returnValue(Promise.resolve()); +} + +describe("SearchProviderMixin", () => { + let terria: Terria; + let searchProvider: TestSearchProvider; + + beforeEach(() => { + terria = new Terria({ + baseUrl: "./" + }); + searchProvider = new TestSearchProvider("test", terria); + searchProvider.setTrait(CommonStrata.definition, "minCharacters", 3); + searchProvider.logEvent.calls.reset(); + searchProvider.doSearch.calls.reset(); + }); + + it(" - properly mixed", () => { + expect(SearchProviderMixin.isMixedInto(searchProvider)).toBeTruthy(); + }); + + it(" - should not run search if searchText is undefined", () => { + const result = searchProvider.search(undefined as never); + expect(result.resultsCompletePromise).toBeDefined(); + expect(result.message).toBeDefined(); + + expect(searchProvider.logEvent).not.toHaveBeenCalled(); + expect(searchProvider.doSearch).not.toHaveBeenCalled(); + }); + + it(" - should not run search if only spaces", () => { + const result = searchProvider.search(" "); + expect(result.resultsCompletePromise).toBeDefined(); + expect(result.message).toBeDefined(); + + expect(searchProvider.logEvent).not.toHaveBeenCalled(); + expect(searchProvider.doSearch).not.toHaveBeenCalled(); + }); + + it(" - should not run search if searchText less than minCharacters", () => { + const result = searchProvider.search("12"); + expect(result.resultsCompletePromise).toBeDefined(); + expect(result.message).toBeDefined(); + + expect(searchProvider.logEvent).not.toHaveBeenCalled(); + expect(searchProvider.doSearch).not.toHaveBeenCalled(); + }); + + it(" - should run search if searchText is valid", () => { + const result = searchProvider.search("1234"); + expect(result.resultsCompletePromise).toBeDefined(); + expect(result.message).not.toBeDefined(); + + expect(searchProvider.logEvent).toHaveBeenCalled(); + expect(searchProvider.doSearch).toHaveBeenCalled(); + }); +}); diff --git a/test/Traits/SearchProviders/BingMapsSearchProviderTraitsSpec.ts b/test/Models/SearchProviders/BingMapsSearchProviderSpec.ts similarity index 100% rename from test/Traits/SearchProviders/BingMapsSearchProviderTraitsSpec.ts rename to test/Models/SearchProviders/BingMapsSearchProviderSpec.ts diff --git a/test/Traits/SearchProviders/LocationSearchProviderTraitsSpec.ts b/test/Models/SearchProviders/LocationSearchProviderSpec.ts similarity index 100% rename from test/Traits/SearchProviders/LocationSearchProviderTraitsSpec.ts rename to test/Models/SearchProviders/LocationSearchProviderSpec.ts From 6263bb5d5a51c5507e2a12dc4090285bc0e89b33 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sat, 4 Nov 2023 14:19:19 +0100 Subject: [PATCH 44/62] fix: ADR title number --- architecture/0011-configurable-search-providers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/architecture/0011-configurable-search-providers.md b/architecture/0011-configurable-search-providers.md index a80f24246fb..2779eaf5a97 100644 --- a/architecture/0011-configurable-search-providers.md +++ b/architecture/0011-configurable-search-providers.md @@ -1,4 +1,4 @@ -# 7. Configuration of search providers +# 11. Configuration of search providers Date: 2021-01-19 From 07057ab7aa82f96c982bd670acd7751ac2afcacd Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sat, 4 Nov 2023 15:13:46 +0100 Subject: [PATCH 45/62] feat: add CatalogSearchProviderMixin and small types refactor --- .../CatalogSearchProviderMixin.ts | 44 +++++++++++++++++++ .../LocationSearchProviderMixin.ts | 6 +-- .../SearchProviders/SearchProviderMixin.ts | 8 +--- .../WebFeatureServiceSearchProviderMixin.ts | 8 ++-- .../SearchProviders/CatalogSearchProvider.ts | 10 +---- .../SearchProviders/SearchProviderResults.ts | 4 +- lib/ReactViewModels/SearchState.ts | 11 ++--- lib/ReactViewModels/ViewState.ts | 6 ++- .../Search/LocationSearchResults.tsx | 9 ++-- test/Map/StyledHtmlSpec.tsx | 2 +- .../MapNavigation/MapNavigationModelSpec.ts | 2 +- test/Models/TerriaSpec.ts | 6 +-- test/ReactViews/BottomDock/BottomDockSpec.tsx | 2 +- .../BottomDock/MapDataCountSpec.tsx | 2 +- .../DataCatalog/DataCatalogItemSpec.tsx | 2 +- test/ReactViews/DisclaimerSpec.tsx | 2 +- test/ReactViews/FeatureInfoPanelSpec.tsx | 2 +- test/ReactViews/Generic/PromptSpec.tsx | 2 +- .../Map/Navigation/Compass/CompassSpec.tsx | 2 +- .../Compass/GyroscopeGuidanceSpec.tsx | 2 +- .../Map/Panels/HelpPanel/HelpPanelSpec.tsx | 2 +- .../Map/Panels/HelpPanel/VideoGuideSpec.tsx | 2 +- .../Map/Panels/LangPanel/LangPanelSpec.tsx | 2 +- .../Panels/SharePanel/BuildShareLinkSpec.ts | 2 +- test/ReactViews/Search/BreadcrumbsSpec.tsx | 2 +- .../Search/SearchBoxAndResultsSpec.tsx | 2 +- test/ReactViews/Search/SearchBoxSpec.tsx | 2 +- .../TrainerBar/TrainerBarSpec.tsx | 2 +- .../ItemSearchTool/ItemSearchToolSpec.tsx | 2 +- test/ReactViews/Tour/TourPortalSpec.tsx | 2 +- test/ReactViews/WelcomeMessageSpec.tsx | 2 +- 31 files changed, 93 insertions(+), 61 deletions(-) create mode 100644 lib/ModelMixins/SearchProviders/CatalogSearchProviderMixin.ts diff --git a/lib/ModelMixins/SearchProviders/CatalogSearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/CatalogSearchProviderMixin.ts new file mode 100644 index 00000000000..a0a62d02afd --- /dev/null +++ b/lib/ModelMixins/SearchProviders/CatalogSearchProviderMixin.ts @@ -0,0 +1,44 @@ +import { computed, makeObservable } from "mobx"; +import AbstractConstructor from "../../Core/AbstractConstructor"; +import Model from "../../Models/Definition/Model"; +import SearchProviderTraits from "../../Traits/SearchProviders/SearchProviderTraits"; +import SearchProviderMixin from "./SearchProviderMixin"; +import isDefined from "../../Core/isDefined"; +import { fromPromise } from "mobx-utils"; + +type CatalogSearchProviderModel = Model; + +function CatalogSearchProviderMixin< + T extends AbstractConstructor +>(Base: T) { + abstract class CatalogSearchProviderMixin extends SearchProviderMixin(Base) { + constructor(...args: any[]) { + super(...args); + makeObservable(this); + } + + @computed get resultsAreReferences() { + return ( + isDefined(this.terria.catalogIndex?.loadPromise) && + fromPromise(this.terria.catalogIndex!.loadPromise).state === "fulfilled" + ); + } + + get hasCatalogSearchProviderMixin() { + return true; + } + } + + return CatalogSearchProviderMixin; +} + +namespace CatalogSearchProviderMixin { + export interface Instance + extends InstanceType> {} + + export function isMixedInto(model: any): model is Instance { + return model && model.hasCatalogSearchProviderMixin; + } +} + +export default CatalogSearchProviderMixin; diff --git a/lib/ModelMixins/SearchProviders/LocationSearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/LocationSearchProviderMixin.ts index 31c1784d414..33a73f8372c 100644 --- a/lib/ModelMixins/SearchProviders/LocationSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/LocationSearchProviderMixin.ts @@ -59,12 +59,10 @@ export function getMapCenter(terria: Terria): MapCenter { } namespace LocationSearchProviderMixin { - export interface LocationSearchProviderMixin + export interface Instance extends InstanceType> {} - export function isMixedInto( - model: any - ): model is LocationSearchProviderMixin { + export function isMixedInto(model: any): model is Instance { return model && model.hasLocationSearchProviderMixin; } } diff --git a/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts index badb379b831..8825b2102bb 100644 --- a/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts @@ -64,20 +64,16 @@ function SearchProviderMixin< get hasSearchProviderMixin() { return true; } - - @computed get resultsAreReferences() { - return isDefined(this.terria.catalogIndex); - } } return SearchProviderMixin; } namespace SearchProviderMixin { - export interface SearchProviderMixin + export interface Instance extends InstanceType> {} - export function isMixedInto(model: any): model is SearchProviderMixin { + export function isMixedInto(model: any): model is Instance { return model && model.hasSearchProviderMixin; } } diff --git a/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts index c80a3d1abfa..ef81a3e1160 100644 --- a/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts @@ -199,21 +199,19 @@ function WebFeatureServiceSearchProviderMixin< } namespace WebFeatureServiceSearchProviderMixin { - export interface WebFeatureServiceSearchProviderMixin + export interface Instance extends InstanceType< ReturnType > {} - export function isMixedInto( - model: any - ): model is WebFeatureServiceSearchProviderMixin { + export function isMixedInto(model: any): model is Instance { return model && model.isWebFeatureServiceSearchProviderMixin; } } export default WebFeatureServiceSearchProviderMixin; function createZoomToFunction( - model: WebFeatureServiceSearchProviderMixin.WebFeatureServiceSearchProviderMixin, + model: WebFeatureServiceSearchProviderMixin.Instance, location: any ) { // Server does not return information of a bounding box, just a location. diff --git a/lib/Models/SearchProviders/CatalogSearchProvider.ts b/lib/Models/SearchProviders/CatalogSearchProvider.ts index 920aa16101d..175a4600c1a 100644 --- a/lib/Models/SearchProviders/CatalogSearchProvider.ts +++ b/lib/Models/SearchProviders/CatalogSearchProvider.ts @@ -23,6 +23,7 @@ import Terria from "../Terria"; import SearchProviderResults from "./SearchProviderResults"; import SearchResult from "./SearchResult"; import isDefined from "../../Core/isDefined"; +import CatalogSearchProviderMixin from "../../ModelMixins/SearchProviders/CatalogSearchProviderMixin"; type UniqueIdString = string; type ResultMap = Map; @@ -115,7 +116,7 @@ export function loadAndSearchCatalogRecursively( }); } -export default class CatalogSearchProvider extends SearchProviderMixin( +export default class CatalogSearchProvider extends CatalogSearchProviderMixin( CreateModel(CatalogSearchProviderTraits) ) { static readonly type = "catalog-search-provider"; @@ -138,13 +139,6 @@ export default class CatalogSearchProvider extends SearchProviderMixin( return CatalogSearchProvider.type; } - @override get resultsAreReferences() { - return ( - isDefined(this.terria.catalogIndex?.loadPromise) && - fromPromise(this.terria.catalogIndex!.loadPromise).state === "fulfilled" - ); - } - protected logEvent(searchText: string) { this.terria.analytics?.logEvent( Category.search, diff --git a/lib/Models/SearchProviders/SearchProviderResults.ts b/lib/Models/SearchProviders/SearchProviderResults.ts index 6ddfb1cc9bb..1b2ce0cf6d0 100644 --- a/lib/Models/SearchProviders/SearchProviderResults.ts +++ b/lib/Models/SearchProviders/SearchProviderResults.ts @@ -16,9 +16,7 @@ export default class SearchProviderResults { Promise.resolve() ); - constructor( - readonly searchProvider: SearchProviderMixin.SearchProviderMixin - ) { + constructor(readonly searchProvider: SearchProviderMixin.Instance) { makeObservable(this); } diff --git a/lib/ReactViewModels/SearchState.ts b/lib/ReactViewModels/SearchState.ts index 1a64d95d20f..df12571996d 100644 --- a/lib/ReactViewModels/SearchState.ts +++ b/lib/ReactViewModels/SearchState.ts @@ -12,19 +12,20 @@ import SearchProviderMixin from "../ModelMixins/SearchProviders/SearchProviderMi import CatalogSearchProvider from "../Models/SearchProviders/CatalogSearchProvider"; import SearchProviderResults from "../Models/SearchProviders/SearchProviderResults"; import Terria from "../Models/Terria"; +import CatalogSearchProviderMixin from "../ModelMixins/SearchProviders/CatalogSearchProviderMixin"; interface SearchStateOptions { terria: Terria; - catalogSearchProvider?: CatalogSearchProvider; - locationSearchProviders?: LocationSearchProviderMixin.LocationSearchProviderMixin[]; + catalogSearchProvider?: CatalogSearchProviderMixin.Instance; + locationSearchProviders?: LocationSearchProviderMixin.Instance[]; } export default class SearchState { @observable - catalogSearchProvider: SearchProviderMixin.SearchProviderMixin | undefined; + catalogSearchProvider: CatalogSearchProviderMixin.Instance | undefined; @observable - locationSearchProviders: LocationSearchProviderMixin.LocationSearchProviderMixin[]; + locationSearchProviders: LocationSearchProviderMixin.Instance[]; @observable catalogSearchText: string = ""; @observable isWaitingToStartCatalogSearch: boolean = false; @@ -98,7 +99,7 @@ export default class SearchState { } @computed - get unifiedSearchProviders(): SearchProviderMixin.SearchProviderMixin[] { + get unifiedSearchProviders(): SearchProviderMixin.Instance[] { return filterOutUndefined([ this.catalogSearchProvider, ...this.locationSearchProviders diff --git a/lib/ReactViewModels/ViewState.ts b/lib/ReactViewModels/ViewState.ts index caaa9449bcf..360fa325e00 100644 --- a/lib/ReactViewModels/ViewState.ts +++ b/lib/ReactViewModels/ViewState.ts @@ -38,6 +38,8 @@ import { } from "./defaultTourPoints"; import DisclaimerHandler from "./DisclaimerHandler"; import SearchState from "./SearchState"; +import CatalogSearchProviderMixin from "../ModelMixins/SearchProviders/CatalogSearchProviderMixin"; +import LocationSearchProviderMixin from "../ModelMixins/SearchProviders/LocationSearchProviderMixin"; export const DATA_CATALOG_NAME = "data-catalog"; export const USER_DATA_NAME = "my-data"; @@ -48,8 +50,8 @@ export const WORKBENCH_RESIZE_ANIMATION_DURATION = 500; interface ViewStateOptions { terria: Terria; - catalogSearchProvider: any; - locationSearchProviders: any[]; + catalogSearchProvider: CatalogSearchProviderMixin.Instance | undefined; + locationSearchProviders: LocationSearchProviderMixin.Instance[]; errorHandlingProvider?: any; } diff --git a/lib/ReactViews/Search/LocationSearchResults.tsx b/lib/ReactViews/Search/LocationSearchResults.tsx index e3157571b87..1984b20a5b0 100644 --- a/lib/ReactViews/Search/LocationSearchResults.tsx +++ b/lib/ReactViews/Search/LocationSearchResults.tsx @@ -19,6 +19,7 @@ import { applyTranslationIfExists } from "../../Language/languageHelpers"; import LocationSearchProviderMixin from "../../ModelMixins/SearchProviders/LocationSearchProviderMixin"; import SearchProviderResults from "../../Models/SearchProviders/SearchProviderResults"; import Terria from "../../Models/Terria"; +import SearchResultModel from "../../Models/SearchProviders/SearchResult"; import ViewState from "../../ReactViewModels/ViewState"; import Box, { BoxSpan } from "../../Styled/Box"; import { RawButton } from "../../Styled/Button"; @@ -44,7 +45,7 @@ interface PropsType extends WithTranslation { isWaitingForSearchToStart: boolean; terria: Terria; search: SearchProviderResults; - onLocationClick: () => void; + onLocationClick: (result: SearchResultModel) => void; theme: DefaultTheme; locationSearchText: string; } @@ -98,8 +99,8 @@ class LocationSearchResults extends React.Component { render() { const { search } = this.props; - const searchProvider: LocationSearchProviderMixin.LocationSearchProviderMixin = - search.searchProvider as any; + const searchProvider: LocationSearchProviderMixin.Instance = + search.searchProvider as unknown as LocationSearchProviderMixin.Instance; const maxResults = searchProvider.recommendedListLength || 5; const validResults = this.validResults; @@ -144,7 +145,7 @@ class LocationSearchResults extends React.Component { isWaitingForSearchToStart={this.props.isWaitingForSearchToStart} />
      - {results.map((result: any, i: number) => ( + {results.map((result: SearchResultModel, i: number) => ( { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); wmsItem = new WebMapServiceCatalogItem("test", terria); diff --git a/test/ReactViews/DisclaimerSpec.tsx b/test/ReactViews/DisclaimerSpec.tsx index b9672dd256b..f23132f2f1d 100644 --- a/test/ReactViews/DisclaimerSpec.tsx +++ b/test/ReactViews/DisclaimerSpec.tsx @@ -19,7 +19,7 @@ describe("Disclaimer", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); }); diff --git a/test/ReactViews/FeatureInfoPanelSpec.tsx b/test/ReactViews/FeatureInfoPanelSpec.tsx index 8abc2b9d572..5d498d89298 100644 --- a/test/ReactViews/FeatureInfoPanelSpec.tsx +++ b/test/ReactViews/FeatureInfoPanelSpec.tsx @@ -37,7 +37,7 @@ describe("FeatureInfoPanel", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); }); diff --git a/test/ReactViews/Generic/PromptSpec.tsx b/test/ReactViews/Generic/PromptSpec.tsx index 31b7fa6f65b..5bd08e5153e 100644 --- a/test/ReactViews/Generic/PromptSpec.tsx +++ b/test/ReactViews/Generic/PromptSpec.tsx @@ -19,7 +19,7 @@ describe("HelpPrompt", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); }); diff --git a/test/ReactViews/Map/Navigation/Compass/CompassSpec.tsx b/test/ReactViews/Map/Navigation/Compass/CompassSpec.tsx index d88e6da7f4f..426a6456deb 100644 --- a/test/ReactViews/Map/Navigation/Compass/CompassSpec.tsx +++ b/test/ReactViews/Map/Navigation/Compass/CompassSpec.tsx @@ -21,7 +21,7 @@ describe("Compass", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); }); diff --git a/test/ReactViews/Map/Navigation/Compass/GyroscopeGuidanceSpec.tsx b/test/ReactViews/Map/Navigation/Compass/GyroscopeGuidanceSpec.tsx index f7883198777..40797f13eae 100644 --- a/test/ReactViews/Map/Navigation/Compass/GyroscopeGuidanceSpec.tsx +++ b/test/ReactViews/Map/Navigation/Compass/GyroscopeGuidanceSpec.tsx @@ -18,7 +18,7 @@ describe("GyroscopeGuidance", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); }); diff --git a/test/ReactViews/Map/Panels/HelpPanel/HelpPanelSpec.tsx b/test/ReactViews/Map/Panels/HelpPanel/HelpPanelSpec.tsx index 46f29ea5b71..9d1aa56fc21 100644 --- a/test/ReactViews/Map/Panels/HelpPanel/HelpPanelSpec.tsx +++ b/test/ReactViews/Map/Panels/HelpPanel/HelpPanelSpec.tsx @@ -23,7 +23,7 @@ describe("HelpPanel", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); }); diff --git a/test/ReactViews/Map/Panels/HelpPanel/VideoGuideSpec.tsx b/test/ReactViews/Map/Panels/HelpPanel/VideoGuideSpec.tsx index 884d9ffeaf5..30a95b5e0a8 100644 --- a/test/ReactViews/Map/Panels/HelpPanel/VideoGuideSpec.tsx +++ b/test/ReactViews/Map/Panels/HelpPanel/VideoGuideSpec.tsx @@ -23,7 +23,7 @@ describe("VideoGuide", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); }); diff --git a/test/ReactViews/Map/Panels/LangPanel/LangPanelSpec.tsx b/test/ReactViews/Map/Panels/LangPanel/LangPanelSpec.tsx index ba81a1c5ed5..37c04b4be7b 100644 --- a/test/ReactViews/Map/Panels/LangPanel/LangPanelSpec.tsx +++ b/test/ReactViews/Map/Panels/LangPanel/LangPanelSpec.tsx @@ -20,7 +20,7 @@ describe("LangPanel", function () { viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); }); diff --git a/test/ReactViews/Map/Panels/SharePanel/BuildShareLinkSpec.ts b/test/ReactViews/Map/Panels/SharePanel/BuildShareLinkSpec.ts index fef5d94cac1..f1ed6bb81ad 100644 --- a/test/ReactViews/Map/Panels/SharePanel/BuildShareLinkSpec.ts +++ b/test/ReactViews/Map/Panels/SharePanel/BuildShareLinkSpec.ts @@ -36,7 +36,7 @@ beforeEach(function () { viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); }); diff --git a/test/ReactViews/Search/BreadcrumbsSpec.tsx b/test/ReactViews/Search/BreadcrumbsSpec.tsx index f85ce9a5278..c15c314be8f 100644 --- a/test/ReactViews/Search/BreadcrumbsSpec.tsx +++ b/test/ReactViews/Search/BreadcrumbsSpec.tsx @@ -25,7 +25,7 @@ describe("Breadcrumbs", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); catalogGroup = new CatalogGroup("group-of-geospatial-cats", terria); diff --git a/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx b/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx index f62f0fd3a6d..11b936b9f57 100644 --- a/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx +++ b/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx @@ -22,7 +22,7 @@ describe("SearchBoxAndResults", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); diff --git a/test/ReactViews/Search/SearchBoxSpec.tsx b/test/ReactViews/Search/SearchBoxSpec.tsx index bb60465a479..7c06ec6c542 100644 --- a/test/ReactViews/Search/SearchBoxSpec.tsx +++ b/test/ReactViews/Search/SearchBoxSpec.tsx @@ -18,7 +18,7 @@ describe("SearchBox", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); }); diff --git a/test/ReactViews/StandardUserInterface/TrainerBar/TrainerBarSpec.tsx b/test/ReactViews/StandardUserInterface/TrainerBar/TrainerBarSpec.tsx index 7bba0db2c75..86cb68eccff 100644 --- a/test/ReactViews/StandardUserInterface/TrainerBar/TrainerBarSpec.tsx +++ b/test/ReactViews/StandardUserInterface/TrainerBar/TrainerBarSpec.tsx @@ -20,7 +20,7 @@ describe("TrainerBar", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); }); diff --git a/test/ReactViews/Tools/ItemSearchTool/ItemSearchToolSpec.tsx b/test/ReactViews/Tools/ItemSearchTool/ItemSearchToolSpec.tsx index d02a6666e75..7c810247bb1 100644 --- a/test/ReactViews/Tools/ItemSearchTool/ItemSearchToolSpec.tsx +++ b/test/ReactViews/Tools/ItemSearchTool/ItemSearchToolSpec.tsx @@ -58,7 +58,7 @@ describe("ItemSearchTool", function () { const terria: Terria = new Terria(); viewState = new ViewState({ terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); item = new MockSearchableItem("test", terria); diff --git a/test/ReactViews/Tour/TourPortalSpec.tsx b/test/ReactViews/Tour/TourPortalSpec.tsx index 4d0d1108d72..5c16fe79011 100644 --- a/test/ReactViews/Tour/TourPortalSpec.tsx +++ b/test/ReactViews/Tour/TourPortalSpec.tsx @@ -23,7 +23,7 @@ describe("TourPortal", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); }); diff --git a/test/ReactViews/WelcomeMessageSpec.tsx b/test/ReactViews/WelcomeMessageSpec.tsx index ce7fa35d34d..6d4a5be70dd 100644 --- a/test/ReactViews/WelcomeMessageSpec.tsx +++ b/test/ReactViews/WelcomeMessageSpec.tsx @@ -20,7 +20,7 @@ describe("WelcomeMessage", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: null, + catalogSearchProvider: undefined, locationSearchProviders: [] }); }); From 1e841bc8530ab5e8ceeb2d0f5f3720b2df77a9ae Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sat, 4 Nov 2023 17:32:16 +0100 Subject: [PATCH 46/62] fix: organise imports --- .../SearchProviders/CatalogSearchProviderMixin.ts | 4 ++-- .../SearchProviders/LocationSearchProviderMixin.ts | 3 +-- .../SearchProviders/SearchProviderMixin.ts | 6 ++---- .../WebFeatureServiceSearchProviderMixin.ts | 3 +-- .../AustralianGazetteerSearchProvider.ts | 2 +- .../SearchProviders/BingMapsSearchProvider.ts | 6 +++--- lib/Models/SearchProviders/CatalogIndex.ts | 2 +- .../SearchProviders/CatalogSearchProvider.ts | 14 ++------------ .../SearchProviders/SearchProviderResults.ts | 4 ++-- lib/Models/SearchProviders/SearchResult.ts | 2 +- lib/Models/SearchProviders/StubSearchProvider.ts | 2 +- .../SearchProviders/registerSearchProviders.ts | 2 +- .../upsertSearchProviderFromJson.ts | 2 +- 13 files changed, 19 insertions(+), 33 deletions(-) diff --git a/lib/ModelMixins/SearchProviders/CatalogSearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/CatalogSearchProviderMixin.ts index a0a62d02afd..d2c0209c731 100644 --- a/lib/ModelMixins/SearchProviders/CatalogSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/CatalogSearchProviderMixin.ts @@ -1,10 +1,10 @@ import { computed, makeObservable } from "mobx"; +import { fromPromise } from "mobx-utils"; import AbstractConstructor from "../../Core/AbstractConstructor"; +import isDefined from "../../Core/isDefined"; import Model from "../../Models/Definition/Model"; import SearchProviderTraits from "../../Traits/SearchProviders/SearchProviderTraits"; import SearchProviderMixin from "./SearchProviderMixin"; -import isDefined from "../../Core/isDefined"; -import { fromPromise } from "mobx-utils"; type CatalogSearchProviderModel = Model; diff --git a/lib/ModelMixins/SearchProviders/LocationSearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/LocationSearchProviderMixin.ts index 33a73f8372c..80543f209f2 100644 --- a/lib/ModelMixins/SearchProviders/LocationSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/LocationSearchProviderMixin.ts @@ -2,13 +2,12 @@ import { action, makeObservable } from "mobx"; import Ellipsoid from "terriajs-cesium/Source/Core/Ellipsoid"; import CesiumMath from "terriajs-cesium/Source/Core/Math"; import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; -import Constructor from "../../Core/Constructor"; +import AbstractConstructor from "../../Core/AbstractConstructor"; import CommonStrata from "../../Models/Definition/CommonStrata"; import Model from "../../Models/Definition/Model"; import Terria from "../../Models/Terria"; import LocationSearchProviderTraits from "../../Traits/SearchProviders/LocationSearchProviderTraits"; import SearchProviderMixin from "./SearchProviderMixin"; -import AbstractConstructor from "../../Core/AbstractConstructor"; type LocationSearchProviderModel = Model; diff --git a/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts index 8825b2102bb..b2fb69345e9 100644 --- a/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts @@ -1,11 +1,9 @@ -import { action, computed, makeObservable } from "mobx"; +import { action, makeObservable } from "mobx"; import { fromPromise } from "mobx-utils"; -import Constructor from "../../Core/Constructor"; -import isDefined from "../../Core/isDefined"; +import AbstractConstructor from "../../Core/AbstractConstructor"; import Model from "../../Models/Definition/Model"; import SearchProviderResults from "../../Models/SearchProviders/SearchProviderResults"; import SearchProviderTraits from "../../Traits/SearchProviders/SearchProviderTraits"; -import AbstractConstructor from "../../Core/AbstractConstructor"; type SearchProviderModel = Model; diff --git a/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts index ef81a3e1160..7e8bf92b56b 100644 --- a/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts @@ -1,7 +1,7 @@ import { makeObservable, runInAction } from "mobx"; import Resource from "terriajs-cesium/Source/Core/Resource"; import URI from "urijs"; -import Constructor from "../../Core/Constructor"; +import AbstractConstructor from "../../Core/AbstractConstructor"; import zoomRectangleFromPoint from "../../Map/Vector/zoomRectangleFromPoint"; import Model from "../../Models/Definition/Model"; import SearchProviderResults from "../../Models/SearchProviders/SearchProviderResults"; @@ -9,7 +9,6 @@ import SearchResult from "../../Models/SearchProviders/SearchResult"; import xml2json from "../../ThirdParty/xml2json"; import WebFeatureServiceSearchProviderTraits from "../../Traits/SearchProviders/WebFeatureServiceSearchProviderTraits"; import LocationSearchProviderMixin from "./LocationSearchProviderMixin"; -import AbstractConstructor from "../../Core/AbstractConstructor"; function WebFeatureServiceSearchProviderMixin< T extends AbstractConstructor> diff --git a/lib/Models/SearchProviders/AustralianGazetteerSearchProvider.ts b/lib/Models/SearchProviders/AustralianGazetteerSearchProvider.ts index f919f526285..2072841b206 100644 --- a/lib/Models/SearchProviders/AustralianGazetteerSearchProvider.ts +++ b/lib/Models/SearchProviders/AustralianGazetteerSearchProvider.ts @@ -6,8 +6,8 @@ import { import WebFeatureServiceSearchProviderMixin from "../../ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin"; import WebFeatureServiceSearchProviderTraits from "../../Traits/SearchProviders/WebFeatureServiceSearchProviderTraits"; import CreateModel from "../Definition/CreateModel"; -import SearchResult from "./SearchResult"; import { ModelConstructorParameters } from "../Definition/Model"; +import SearchResult from "./SearchResult"; const featureCodesToNamesMap = new Map([ ["AF", "Aviation"], diff --git a/lib/Models/SearchProviders/BingMapsSearchProvider.ts b/lib/Models/SearchProviders/BingMapsSearchProvider.ts index 04feab4bc99..a95be334e0a 100644 --- a/lib/Models/SearchProviders/BingMapsSearchProvider.ts +++ b/lib/Models/SearchProviders/BingMapsSearchProvider.ts @@ -1,7 +1,8 @@ -import { runInAction, makeObservable } from "mobx"; -import defined from "terriajs-cesium/Source/Core/defined"; +import i18next from "i18next"; +import { makeObservable, runInAction } from "mobx"; import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; import Resource from "terriajs-cesium/Source/Core/Resource"; +import defined from "terriajs-cesium/Source/Core/defined"; import { Category, SearchAction @@ -17,7 +18,6 @@ import Terria from "../Terria"; import CommonStrata from "./../Definition/CommonStrata"; import SearchProviderResults from "./SearchProviderResults"; import SearchResult from "./SearchResult"; -import i18next from "i18next"; export default class BingMapsSearchProvider extends LocationSearchProviderMixin( CreateModel(BingMapsSearchProviderTraits) diff --git a/lib/Models/SearchProviders/CatalogIndex.ts b/lib/Models/SearchProviders/CatalogIndex.ts index 5450fdeb2c3..bf3a79bfcd1 100644 --- a/lib/Models/SearchProviders/CatalogIndex.ts +++ b/lib/Models/SearchProviders/CatalogIndex.ts @@ -1,5 +1,5 @@ import { Document } from "flexsearch"; -import { action, observable, runInAction, makeObservable } from "mobx"; +import { action, makeObservable, observable, runInAction } from "mobx"; import { isJsonObject, isJsonString, isJsonStringArray } from "../../Core/Json"; import loadBlob, { isZip, parseZipJsonBlob } from "../../Core/loadBlob"; import loadJson from "../../Core/loadJson"; diff --git a/lib/Models/SearchProviders/CatalogSearchProvider.ts b/lib/Models/SearchProviders/CatalogSearchProvider.ts index 175a4600c1a..b3eff29c5f7 100644 --- a/lib/Models/SearchProviders/CatalogSearchProvider.ts +++ b/lib/Models/SearchProviders/CatalogSearchProvider.ts @@ -1,12 +1,4 @@ -import { - autorun, - computed, - observable, - runInAction, - makeObservable, - override -} from "mobx"; -import { fromPromise } from "mobx-utils"; +import { autorun, makeObservable, observable, runInAction } from "mobx"; import { Category, SearchAction @@ -14,7 +6,7 @@ import { import { TerriaErrorSeverity } from "../../Core/TerriaError"; import GroupMixin from "../../ModelMixins/GroupMixin"; import ReferenceMixin from "../../ModelMixins/ReferenceMixin"; -import SearchProviderMixin from "../../ModelMixins/SearchProviders/SearchProviderMixin"; +import CatalogSearchProviderMixin from "../../ModelMixins/SearchProviders/CatalogSearchProviderMixin"; import CatalogSearchProviderTraits from "../../Traits/SearchProviders/CatalogSearchProviderTraits"; import CommonStrata from "../Definition/CommonStrata"; import CreateModel from "../Definition/CreateModel"; @@ -22,8 +14,6 @@ import { BaseModel } from "../Definition/Model"; import Terria from "../Terria"; import SearchProviderResults from "./SearchProviderResults"; import SearchResult from "./SearchResult"; -import isDefined from "../../Core/isDefined"; -import CatalogSearchProviderMixin from "../../ModelMixins/SearchProviders/CatalogSearchProviderMixin"; type UniqueIdString = string; type ResultMap = Map; diff --git a/lib/Models/SearchProviders/SearchProviderResults.ts b/lib/Models/SearchProviders/SearchProviderResults.ts index 1b2ce0cf6d0..afd03f3f08e 100644 --- a/lib/Models/SearchProviders/SearchProviderResults.ts +++ b/lib/Models/SearchProviders/SearchProviderResults.ts @@ -1,5 +1,5 @@ -import { observable, makeObservable } from "mobx"; -import { fromPromise, IPromiseBasedObservable } from "mobx-utils"; +import { makeObservable, observable } from "mobx"; +import { IPromiseBasedObservable, fromPromise } from "mobx-utils"; import SearchProviderMixin from "../../ModelMixins/SearchProviders/SearchProviderMixin"; import SearchResult from "./SearchResult"; diff --git a/lib/Models/SearchProviders/SearchResult.ts b/lib/Models/SearchProviders/SearchResult.ts index 24582fa6021..55495726876 100644 --- a/lib/Models/SearchProviders/SearchResult.ts +++ b/lib/Models/SearchProviders/SearchResult.ts @@ -1,4 +1,4 @@ -import { action, observable, makeObservable } from "mobx"; +import { action, makeObservable, observable } from "mobx"; import defaultValue from "terriajs-cesium/Source/Core/defaultValue"; import defined from "terriajs-cesium/Source/Core/defined"; import GroupMixin from "../../ModelMixins/GroupMixin"; diff --git a/lib/Models/SearchProviders/StubSearchProvider.ts b/lib/Models/SearchProviders/StubSearchProvider.ts index 70909a4e002..6ba2399bff7 100644 --- a/lib/Models/SearchProviders/StubSearchProvider.ts +++ b/lib/Models/SearchProviders/StubSearchProvider.ts @@ -3,8 +3,8 @@ import SearchProviderMixin from "../../ModelMixins/SearchProviders/SearchProvide import primitiveTrait from "../../Traits/Decorators/primitiveTrait"; import LocationSearchProviderTraits from "../../Traits/SearchProviders/LocationSearchProviderTraits"; import CreateModel from "../Definition/CreateModel"; -import SearchProviderResults from "./SearchProviderResults"; import { ModelConstructorParameters } from "../Definition/Model"; +import SearchProviderResults from "./SearchProviderResults"; export class StubSearchProviderTraits extends LocationSearchProviderTraits { @primitiveTrait({ diff --git a/lib/Models/SearchProviders/registerSearchProviders.ts b/lib/Models/SearchProviders/registerSearchProviders.ts index 919a6da697c..49d2ca854f2 100644 --- a/lib/Models/SearchProviders/registerSearchProviders.ts +++ b/lib/Models/SearchProviders/registerSearchProviders.ts @@ -1,5 +1,5 @@ -import BingMapsSearchProvider from "./BingMapsSearchProvider"; import AustralianGazetteerSearchProvider from "./AustralianGazetteerSearchProvider"; +import BingMapsSearchProvider from "./BingMapsSearchProvider"; import SearchProviderFactory from "./SearchProviderFactory"; export default function registerSearchProviders() { diff --git a/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts index b19664e3def..96fa81654f1 100644 --- a/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts +++ b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts @@ -7,8 +7,8 @@ import { BaseModel } from "../Definition/Model"; import ModelFactory from "../Definition/ModelFactory"; import updateModelFromJson from "../Definition/updateModelFromJson"; import Terria from "../Terria"; -import createStubSearchProvider from "./createStubSearchProvider"; import StubSearchProvider from "./StubSearchProvider"; +import createStubSearchProvider from "./createStubSearchProvider"; export default function upsertSearchProviderFromJson( factory: ModelFactory, From 7a529e312d45a3390f2494231562f00f8e6ca46b Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sat, 4 Nov 2023 18:36:18 +0100 Subject: [PATCH 47/62] fix: replace deprecated substr method --- lib/Language/languageHelpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Language/languageHelpers.ts b/lib/Language/languageHelpers.ts index 396fadcebf4..4e6f2de4d24 100644 --- a/lib/Language/languageHelpers.ts +++ b/lib/Language/languageHelpers.ts @@ -22,7 +22,7 @@ export function applyTranslationIfExists( // keyOrString could be undefined in some cases even if we type it as string if (isJsonString(keyOrString as unknown)) { if (keyOrString.indexOf(TRANSLATE_KEY_PREFIX) === 0) { - const translationKey = keyOrString.substr(TRANSLATE_KEY_PREFIX.length); + const translationKey = keyOrString.substring(TRANSLATE_KEY_PREFIX.length); return i18n.exists(translationKey) ? i18n.t(translationKey, options) : translationKey; From 8013f541fe41a346f5fef6a5ed7c89f2c79d24cc Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sat, 4 Nov 2023 18:39:29 +0100 Subject: [PATCH 48/62] fix: search model type definition --- .../SearchProviders/SearchProviderMixin.ts | 5 +---- .../WebFeatureServiceSearchProviderMixin.ts | 2 +- lib/Models/SearchProviders/CatalogSearchProvider.ts | 2 +- .../SearchProviders/createStubSearchProvider.ts | 2 +- .../SearchProviders/upsertSearchProviderFromJson.ts | 8 ++++---- lib/Models/Terria.ts | 9 ++++----- lib/ReactViews/Mobile/MobileHeader.jsx | 12 ++++-------- lib/ReactViews/Search/LocationSearchResults.tsx | 2 +- lib/ReactViews/SidePanel/SidePanel.tsx | 12 ++++-------- 9 files changed, 21 insertions(+), 33 deletions(-) diff --git a/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts index b2fb69345e9..7d58e2683e2 100644 --- a/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/SearchProviderMixin.ts @@ -49,10 +49,7 @@ function SearchProviderMixin< if ( searchText === undefined || /^\s*$/.test(searchText) || - (this.minCharacters && searchText.length < this.minCharacters) || - (this.minCharacters === undefined && - searchText.length < - this.terria.configParameters.searchBarModel!.minCharacters) + (this.minCharacters && searchText.length < this.minCharacters) ) { return false; } diff --git a/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts index 7e8bf92b56b..082b32176f3 100644 --- a/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts @@ -224,7 +224,7 @@ function createZoomToFunction( const flightDurationSeconds: number = model.flightDurationSeconds || - model.terria.configParameters.searchBarModel!.flightDurationSeconds; + model.terria.configParameters.searchBarModel.flightDurationSeconds; return function () { model.terria.currentViewer.zoomTo(rectangle, flightDurationSeconds); diff --git a/lib/Models/SearchProviders/CatalogSearchProvider.ts b/lib/Models/SearchProviders/CatalogSearchProvider.ts index b3eff29c5f7..8bb3f6f0f74 100644 --- a/lib/Models/SearchProviders/CatalogSearchProvider.ts +++ b/lib/Models/SearchProviders/CatalogSearchProvider.ts @@ -121,7 +121,7 @@ export default class CatalogSearchProvider extends CatalogSearchProviderMixin( this.setTrait( CommonStrata.defaults, "minCharacters", - terria.configParameters.searchBarModel!.minCharacters + terria.configParameters.searchBarModel.minCharacters ); } diff --git a/lib/Models/SearchProviders/createStubSearchProvider.ts b/lib/Models/SearchProviders/createStubSearchProvider.ts index e7098bad3fb..8658a0b3aac 100644 --- a/lib/Models/SearchProviders/createStubSearchProvider.ts +++ b/lib/Models/SearchProviders/createStubSearchProvider.ts @@ -22,6 +22,6 @@ export default function createStubSearchProvider( const stub = new StubSearchProvider(idToUse, terria); stub.setTrait(CommonStrata.underride, "name", stub.uniqueId); - terria.configParameters.searchBarModel?.addSearchProvider(stub); + terria.configParameters.searchBarModel.addSearchProvider(stub); return stub; } diff --git a/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts index 96fa81654f1..31e5a79c32e 100644 --- a/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts +++ b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts @@ -53,7 +53,7 @@ export default function upsertSearchProviderFromJson( if (model.type !== StubSearchProvider.type) { try { - model.terria.configParameters.searchBarModel?.addSearchProvider(model); + model.terria.configParameters.searchBarModel.addSearchProvider(model); } catch (error) { errors.push(TerriaError.from(error)); } @@ -85,18 +85,18 @@ function setDefaultTraits(model: BaseModel) { model.setTrait( CommonStrata.defaults, "flightDurationSeconds", - terria.configParameters.searchBarModel?.flightDurationSeconds + terria.configParameters.searchBarModel.flightDurationSeconds ); model.setTrait( CommonStrata.defaults, "minCharacters", - terria.configParameters.searchBarModel?.minCharacters + terria.configParameters.searchBarModel.minCharacters ); model.setTrait( CommonStrata.defaults, "recommendedListLength", - terria.configParameters.searchBarModel?.recommendedListLength + terria.configParameters.searchBarModel.recommendedListLength ); } diff --git a/lib/Models/Terria.ts b/lib/Models/Terria.ts index d02935554e8..776e08dea20 100644 --- a/lib/Models/Terria.ts +++ b/lib/Models/Terria.ts @@ -345,7 +345,7 @@ export interface ConfigParameters { /** * The search bar allows requesting information from various search services at once. */ - searchBarModel?: SearchBarModel; + searchBarModel: SearchBarModel; searchProviders: any[]; } @@ -995,8 +995,7 @@ export default class Terria { if (isJsonObject(config) && isJsonObject(config.parameters)) { this.updateParameters(config.parameters); } - if (isJsonObject(config) && Array.isArray(config.searchProviders)) { - } + if (this.configParameters.errorService) { this.setupErrorServiceProvider(this.configParameters.errorService); } @@ -1055,7 +1054,7 @@ export default class Terria { ); this.configParameters.searchBarModel - ?.initializeSearchProviders() + .initializeSearchProviders() .catchError((error) => this.raiseErrorToUser( TerriaError.from(error, "Failed to initialize searchProviders") @@ -1302,7 +1301,7 @@ export default class Terria { this.configParameters.searchBarModel = new SearchBarModel(this); } updateModelFromJson( - this.configParameters.searchBarModel!, + this.configParameters.searchBarModel, CommonStrata.definition, value ); diff --git a/lib/ReactViews/Mobile/MobileHeader.jsx b/lib/ReactViews/Mobile/MobileHeader.jsx index d346df0ea96..41a04e3858b 100644 --- a/lib/ReactViews/Mobile/MobileHeader.jsx +++ b/lib/ReactViews/Mobile/MobileHeader.jsx @@ -137,14 +137,10 @@ class MobileHeader extends React.Component { searchText={searchState.locationSearchText} onSearchTextChanged={this.changeLocationSearchText.bind(this)} onDoSearch={this.searchLocations.bind(this)} - placeholder={ - viewState.terria.configParameters.searchBarModel - ? applyTranslationIfExists( - viewState.terria.configParameters.searchBarModel.placeholder, - this.props.i18n - ) - : t("search.placeholder") - } + placeholder={applyTranslationIfExists( + viewState.terria.configParameters.searchBarModel.placeholder, + this.props.i18n + )} alwaysShowClear={true} onClear={this.closeLocationSearch.bind(this)} autoFocus={true} diff --git a/lib/ReactViews/Search/LocationSearchResults.tsx b/lib/ReactViews/Search/LocationSearchResults.tsx index 1984b20a5b0..0c7b5af5623 100644 --- a/lib/ReactViews/Search/LocationSearchResults.tsx +++ b/lib/ReactViews/Search/LocationSearchResults.tsx @@ -68,7 +68,7 @@ class LocationSearchResults extends React.Component { get validResults() { const { search, terria } = this.props; const locationSearchBoundingBox = - terria.configParameters.searchBarModel?.boundingBoxLimit; + terria.configParameters.searchBarModel.boundingBoxLimit; let filterResults = false; let west: number | undefined, east: number | undefined, diff --git a/lib/ReactViews/SidePanel/SidePanel.tsx b/lib/ReactViews/SidePanel/SidePanel.tsx index 9abecaea621..6f6c584cff4 100644 --- a/lib/ReactViews/SidePanel/SidePanel.tsx +++ b/lib/ReactViews/SidePanel/SidePanel.tsx @@ -163,14 +163,10 @@ const SidePanel = observer>( From 64e2f8970c7f55099b32d22fe2c1fb343264e34b Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sat, 4 Nov 2023 18:40:24 +0100 Subject: [PATCH 49/62] fix: remove duplicate code --- lib/Models/SearchProviders/CatalogSearchProvider.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/Models/SearchProviders/CatalogSearchProvider.ts b/lib/Models/SearchProviders/CatalogSearchProvider.ts index 8bb3f6f0f74..a30ec3646d6 100644 --- a/lib/Models/SearchProviders/CatalogSearchProvider.ts +++ b/lib/Models/SearchProviders/CatalogSearchProvider.ts @@ -163,18 +163,6 @@ export default class CatalogSearchProvider extends CatalogSearchProviderMixin( } } - // Load catalogIndex if needed - if (this.terria.catalogIndex && !this.terria.catalogIndex.loadPromise) { - try { - await this.terria.catalogIndex.load(); - } catch (e) { - this.terria.raiseErrorToUser( - e, - "Failed to load catalog index. Searching may be slow/inaccurate" - ); - } - } - const resultMap: ResultMap = new Map(); try { From 34d2dfc3df107eaca435a1b1672f04907c53dc7c Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sat, 4 Nov 2023 18:56:13 +0100 Subject: [PATCH 50/62] fix: search result styling --- lib/ReactViews/Search/LocationSearchResults.tsx | 2 +- lib/Styled/List.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ReactViews/Search/LocationSearchResults.tsx b/lib/ReactViews/Search/LocationSearchResults.tsx index 0c7b5af5623..475715f46aa 100644 --- a/lib/ReactViews/Search/LocationSearchResults.tsx +++ b/lib/ReactViews/Search/LocationSearchResults.tsx @@ -144,7 +144,7 @@ class LocationSearchResults extends React.Component { searchResults={search} isWaitingForSearchToStart={this.props.isWaitingForSearchToStart} /> -
        +
          {results.map((result: SearchResultModel, i: number) => ( ` + padding-left: 0; list-style: none; margin: 0; ${(props) => From 53eaf78abf33b9819e4ec6b004cdf74265c24f9d Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sat, 4 Nov 2023 18:56:37 +0100 Subject: [PATCH 51/62] fix: translation --- .../SearchProviders/WebFeatureServiceSearchProviderMixin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts index 082b32176f3..ef7bc29df00 100644 --- a/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts @@ -106,7 +106,7 @@ function WebFeatureServiceSearchProviderMixin< features = json.featureMember; } else { results.message = { - content: "translate#translate#viewModels.searchNoPlaceNames" + content: "translate#viewModels.searchNoPlaceNames" }; return; } From c08b84dbf68a9c9aea1f8214a210d1011b14d50c Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sat, 4 Nov 2023 19:40:12 +0100 Subject: [PATCH 52/62] change initialization of search state --- .../SearchProviders/BingMapsSearchProvider.ts | 6 ++-- lib/Models/SearchProviders/SearchBarModel.ts | 20 ++++++++--- .../upsertSearchProviderFromJson.ts | 33 ++++++++++--------- lib/ReactViewModels/SearchState.ts | 19 +++++++---- lib/ReactViewModels/ViewState.ts | 6 ++-- lib/Traits/ModelTraitsInterface.ts | 7 ++++ test/Map/StyledHtmlSpec.tsx | 3 +- .../MapNavigation/MapNavigationModelSpec.ts | 3 +- test/Models/TerriaSpec.ts | 9 ++--- .../SelectableDimensionWorkflowSpec.ts | 3 +- test/ReactViewModels/ViewStateSpec.ts | 3 +- test/ReactViews/BottomDock/BottomDockSpec.tsx | 3 +- .../BottomDock/MapDataCountSpec.tsx | 3 +- .../DataCatalog/DataCatalogItemSpec.tsx | 3 +- test/ReactViews/DisclaimerSpec.tsx | 3 +- test/ReactViews/FeatureInfoPanelSpec.tsx | 3 +- test/ReactViews/FeatureInfoSectionSpec.tsx | 3 +- test/ReactViews/Generic/PromptSpec.tsx | 3 +- .../Map/Navigation/Compass/CompassSpec.tsx | 3 +- .../Compass/GyroscopeGuidanceSpec.tsx | 3 +- .../Map/Panels/HelpPanel/HelpPanelSpec.tsx | 3 +- .../Map/Panels/HelpPanel/VideoGuideSpec.tsx | 3 +- .../Map/Panels/LangPanel/LangPanelSpec.tsx | 3 +- .../Panels/SharePanel/BuildShareLinkSpec.ts | 3 +- test/ReactViews/Search/BreadcrumbsSpec.tsx | 3 +- .../Search/SearchBoxAndResultsSpec.tsx | 3 +- test/ReactViews/Search/SearchBoxSpec.tsx | 3 +- test/ReactViews/SidePanel/BrandingSpec.tsx | 3 +- .../TrainerBar/TrainerBarSpec.tsx | 3 +- test/ReactViews/ToolButtonSpec.tsx | 3 +- test/ReactViews/ToolSpec.tsx | 3 +- .../ItemSearchTool/ItemSearchToolSpec.tsx | 3 +- test/ReactViews/Tour/TourPortalSpec.tsx | 3 +- test/ReactViews/WelcomeMessageSpec.tsx | 3 +- .../Workbench/Controls/IdealZoomSpec.tsx | 3 +- .../Controls/ViewingControlsSpec.tsx | 3 +- .../Workflows/WorkflowPanelSpec.tsx | 3 +- test/ViewModels/FeatureInfoPanelSpec.ts | 3 +- .../MapNavigation/MapToolbarSpec.ts | 3 +- test/ViewModels/ViewingControlsMenuSpec.ts | 3 +- 40 files changed, 95 insertions(+), 104 deletions(-) create mode 100644 lib/Traits/ModelTraitsInterface.ts diff --git a/lib/Models/SearchProviders/BingMapsSearchProvider.ts b/lib/Models/SearchProviders/BingMapsSearchProvider.ts index a95be334e0a..6393a7e2f1d 100644 --- a/lib/Models/SearchProviders/BingMapsSearchProvider.ts +++ b/lib/Models/SearchProviders/BingMapsSearchProvider.ts @@ -1,5 +1,5 @@ import i18next from "i18next"; -import { makeObservable, runInAction } from "mobx"; +import { action, makeObservable, runInAction } from "mobx"; import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; import Resource from "terriajs-cesium/Source/Core/Resource"; import defined from "terriajs-cesium/Source/Core/defined"; @@ -111,9 +111,9 @@ export default class BingMapsSearchProvider extends LocationSearchProviderMixin( return; } - const locations = this.sortByPriority(resourceSet.resources); - runInAction(() => { + const locations = this.sortByPriority(resourceSet.resources); + searchResults.results.push(...locations.primaryCountry); searchResults.results.push(...locations.other); }); diff --git a/lib/Models/SearchProviders/SearchBarModel.ts b/lib/Models/SearchProviders/SearchBarModel.ts index ac4463cfa9f..603e3287d85 100644 --- a/lib/Models/SearchProviders/SearchBarModel.ts +++ b/lib/Models/SearchProviders/SearchBarModel.ts @@ -1,4 +1,10 @@ -import { action, isObservableArray, makeObservable, observable } from "mobx"; +import { + action, + computed, + isObservableArray, + makeObservable, + observable +} from "mobx"; import DeveloperError from "terriajs-cesium/Source/Core/DeveloperError"; import Result from "../../Core/Result"; import TerriaError from "../../Core/TerriaError"; @@ -9,6 +15,7 @@ import { BaseModel } from "../Definition/Model"; import Terria from "../Terria"; import SearchProviderFactory from "./SearchProviderFactory"; import upsertSearchProviderFromJson from "./upsertSearchProviderFromJson"; +import LocationSearchProviderMixin from "../../ModelMixins/SearchProviders/LocationSearchProviderMixin"; export class SearchBarModel extends CreateModel(SearchBarTraits) { private locationSearchProviders = observable.map(); @@ -73,9 +80,14 @@ export class SearchBarModel extends CreateModel(SearchBarTraits) { this.locationSearchProviders.set(model.uniqueId, model); } + @computed get locationSearchProvidersArray() { - return [...this.locationSearchProviders.entries()].map(function (entry) { - return entry[1]; - }); + return [...this.locationSearchProviders.entries()] + .filter((entry) => { + return LocationSearchProviderMixin.isMixedInto(entry[1]); + }) + .map(function (entry) { + return entry[1] as LocationSearchProviderMixin.Instance; + }); } } diff --git a/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts index 31e5a79c32e..331cd2e43c2 100644 --- a/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts +++ b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts @@ -9,6 +9,7 @@ import updateModelFromJson from "../Definition/updateModelFromJson"; import Terria from "../Terria"; import StubSearchProvider from "./StubSearchProvider"; import createStubSearchProvider from "./createStubSearchProvider"; +import { runInAction } from "mobx"; export default function upsertSearchProviderFromJson( factory: ModelFactory, @@ -82,21 +83,23 @@ export default function upsertSearchProviderFromJson( function setDefaultTraits(model: BaseModel) { const terria = model.terria; - model.setTrait( - CommonStrata.defaults, - "flightDurationSeconds", - terria.configParameters.searchBarModel.flightDurationSeconds - ); + runInAction(() => { + model.setTrait( + CommonStrata.defaults, + "flightDurationSeconds", + terria.configParameters.searchBarModel.flightDurationSeconds + ); - model.setTrait( - CommonStrata.defaults, - "minCharacters", - terria.configParameters.searchBarModel.minCharacters - ); + model.setTrait( + CommonStrata.defaults, + "minCharacters", + terria.configParameters.searchBarModel.minCharacters + ); - model.setTrait( - CommonStrata.defaults, - "recommendedListLength", - terria.configParameters.searchBarModel.recommendedListLength - ); + model.setTrait( + CommonStrata.defaults, + "recommendedListLength", + terria.configParameters.searchBarModel?.recommendedListLength + ); + }); } diff --git a/lib/ReactViewModels/SearchState.ts b/lib/ReactViewModels/SearchState.ts index df12571996d..9cf3470ac0d 100644 --- a/lib/ReactViewModels/SearchState.ts +++ b/lib/ReactViewModels/SearchState.ts @@ -17,16 +17,12 @@ import CatalogSearchProviderMixin from "../ModelMixins/SearchProviders/CatalogSe interface SearchStateOptions { terria: Terria; catalogSearchProvider?: CatalogSearchProviderMixin.Instance; - locationSearchProviders?: LocationSearchProviderMixin.Instance[]; } export default class SearchState { @observable catalogSearchProvider: CatalogSearchProviderMixin.Instance | undefined; - @observable - locationSearchProviders: LocationSearchProviderMixin.Instance[]; - @observable catalogSearchText: string = ""; @observable isWaitingToStartCatalogSearch: boolean = false; @@ -48,13 +44,16 @@ export default class SearchState { private _locationSearchDisposer: IReactionDisposer; private _unifiedSearchDisposer: IReactionDisposer; + private readonly terria: Terria; + constructor(options: SearchStateOptions) { makeObservable(this); + + this.terria = options.terria; + this.catalogSearchProvider = options.catalogSearchProvider || new CatalogSearchProvider("catalog-search-provider", options.terria); - this.locationSearchProviders = options.locationSearchProviders || []; - const self = this; this._catalogSearchDisposer = reaction( @@ -98,6 +97,14 @@ export default class SearchState { this._unifiedSearchDisposer(); } + @computed + get locationSearchProviders(): LocationSearchProviderMixin.Instance[] { + return ( + this.terria.configParameters.searchBarModel + .locationSearchProvidersArray ?? [] + ); + } + @computed get unifiedSearchProviders(): SearchProviderMixin.Instance[] { return filterOutUndefined([ diff --git a/lib/ReactViewModels/ViewState.ts b/lib/ReactViewModels/ViewState.ts index 360fa325e00..803d3206515 100644 --- a/lib/ReactViewModels/ViewState.ts +++ b/lib/ReactViewModels/ViewState.ts @@ -51,7 +51,6 @@ export const WORKBENCH_RESIZE_ANIMATION_DURATION = 500; interface ViewStateOptions { terria: Terria; catalogSearchProvider: CatalogSearchProviderMixin.Instance | undefined; - locationSearchProviders: LocationSearchProviderMixin.Instance[]; errorHandlingProvider?: any; } @@ -378,9 +377,8 @@ export default class ViewState { makeObservable(this); const terria = options.terria; this.searchState = new SearchState({ - terria: terria, - catalogSearchProvider: options.catalogSearchProvider, - locationSearchProviders: options.locationSearchProviders + terria, + catalogSearchProvider: options.catalogSearchProvider }); this.errorProvider = options.errorHandlingProvider diff --git a/lib/Traits/ModelTraitsInterface.ts b/lib/Traits/ModelTraitsInterface.ts new file mode 100644 index 00000000000..3f71a80c042 --- /dev/null +++ b/lib/Traits/ModelTraitsInterface.ts @@ -0,0 +1,7 @@ +import ModelTraits from "./ModelTraits"; + +export type ModelTraitsInterface = { + [Member in keyof ClassType]: Member extends ModelTraits + ? ModelTraitsInterface> + : ClassType[Member]; +}; diff --git a/test/Map/StyledHtmlSpec.tsx b/test/Map/StyledHtmlSpec.tsx index a223f649e16..861619da303 100644 --- a/test/Map/StyledHtmlSpec.tsx +++ b/test/Map/StyledHtmlSpec.tsx @@ -22,8 +22,7 @@ describe("StyledHtml", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/Models/MapNavigation/MapNavigationModelSpec.ts b/test/Models/MapNavigation/MapNavigationModelSpec.ts index faa55d53ed6..0a7e88ded3f 100644 --- a/test/Models/MapNavigation/MapNavigationModelSpec.ts +++ b/test/Models/MapNavigation/MapNavigationModelSpec.ts @@ -20,8 +20,7 @@ describe("MapNavigationModel", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); item1 = { id: "item1", diff --git a/test/Models/TerriaSpec.ts b/test/Models/TerriaSpec.ts index 7be9acbef87..ed096187703 100644 --- a/test/Models/TerriaSpec.ts +++ b/test/Models/TerriaSpec.ts @@ -584,8 +584,7 @@ describe("Terria", function () { newTerria = new Terria({ appBaseHref: "/", baseUrl: "./" }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); UrlToCatalogMemberMapping.register( @@ -801,8 +800,7 @@ describe("Terria", function () { newTerria = new Terria({ baseUrl: "./" }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); await Promise.all( @@ -913,8 +911,7 @@ describe("Terria", function () { viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); newTerria = new Terria({ baseUrl: "./" }); diff --git a/test/Models/Workflows/SelectableDimensionWorkflowSpec.ts b/test/Models/Workflows/SelectableDimensionWorkflowSpec.ts index 385fd974197..dcac113c67f 100644 --- a/test/Models/Workflows/SelectableDimensionWorkflowSpec.ts +++ b/test/Models/Workflows/SelectableDimensionWorkflowSpec.ts @@ -14,8 +14,7 @@ describe("SelectableDimensionWorkflow", function () { terria = new Terria(); viewState = new ViewState({ terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViewModels/ViewStateSpec.ts b/test/ReactViewModels/ViewStateSpec.ts index ff04eeb9e6b..b403493c6b8 100644 --- a/test/ReactViewModels/ViewStateSpec.ts +++ b/test/ReactViewModels/ViewStateSpec.ts @@ -14,8 +14,7 @@ describe("ViewState", function () { terria = new Terria(); viewState = new ViewState({ terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/BottomDock/BottomDockSpec.tsx b/test/ReactViews/BottomDock/BottomDockSpec.tsx index 5b797a6c53a..d89936a04c5 100644 --- a/test/ReactViews/BottomDock/BottomDockSpec.tsx +++ b/test/ReactViews/BottomDock/BottomDockSpec.tsx @@ -15,8 +15,7 @@ describe("BottomDock", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/BottomDock/MapDataCountSpec.tsx b/test/ReactViews/BottomDock/MapDataCountSpec.tsx index ff35577ff86..fa8a2ee5242 100644 --- a/test/ReactViews/BottomDock/MapDataCountSpec.tsx +++ b/test/ReactViews/BottomDock/MapDataCountSpec.tsx @@ -19,8 +19,7 @@ describe("MapDataCount", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/DataCatalog/DataCatalogItemSpec.tsx b/test/ReactViews/DataCatalog/DataCatalogItemSpec.tsx index 82f551f2505..16ed60e92ff 100644 --- a/test/ReactViews/DataCatalog/DataCatalogItemSpec.tsx +++ b/test/ReactViews/DataCatalog/DataCatalogItemSpec.tsx @@ -26,8 +26,7 @@ describe("DataCatalogItem", () => { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); wmsItem = new WebMapServiceCatalogItem("test", terria); diff --git a/test/ReactViews/DisclaimerSpec.tsx b/test/ReactViews/DisclaimerSpec.tsx index f23132f2f1d..49ed398fb0c 100644 --- a/test/ReactViews/DisclaimerSpec.tsx +++ b/test/ReactViews/DisclaimerSpec.tsx @@ -19,8 +19,7 @@ describe("Disclaimer", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/FeatureInfoPanelSpec.tsx b/test/ReactViews/FeatureInfoPanelSpec.tsx index 5d498d89298..b4d632648fc 100644 --- a/test/ReactViews/FeatureInfoPanelSpec.tsx +++ b/test/ReactViews/FeatureInfoPanelSpec.tsx @@ -37,8 +37,7 @@ describe("FeatureInfoPanel", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/FeatureInfoSectionSpec.tsx b/test/ReactViews/FeatureInfoSectionSpec.tsx index 349b78e67b3..f773fb1025d 100644 --- a/test/ReactViews/FeatureInfoSectionSpec.tsx +++ b/test/ReactViews/FeatureInfoSectionSpec.tsx @@ -66,8 +66,7 @@ describe("FeatureInfoSection", function () { viewState = new ViewState({ terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); const properties = { name: "Kay", diff --git a/test/ReactViews/Generic/PromptSpec.tsx b/test/ReactViews/Generic/PromptSpec.tsx index 5bd08e5153e..d44afbf3cd7 100644 --- a/test/ReactViews/Generic/PromptSpec.tsx +++ b/test/ReactViews/Generic/PromptSpec.tsx @@ -19,8 +19,7 @@ describe("HelpPrompt", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/Map/Navigation/Compass/CompassSpec.tsx b/test/ReactViews/Map/Navigation/Compass/CompassSpec.tsx index 426a6456deb..5daafb1a59a 100644 --- a/test/ReactViews/Map/Navigation/Compass/CompassSpec.tsx +++ b/test/ReactViews/Map/Navigation/Compass/CompassSpec.tsx @@ -21,8 +21,7 @@ describe("Compass", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/Map/Navigation/Compass/GyroscopeGuidanceSpec.tsx b/test/ReactViews/Map/Navigation/Compass/GyroscopeGuidanceSpec.tsx index 40797f13eae..fa870e14cff 100644 --- a/test/ReactViews/Map/Navigation/Compass/GyroscopeGuidanceSpec.tsx +++ b/test/ReactViews/Map/Navigation/Compass/GyroscopeGuidanceSpec.tsx @@ -18,8 +18,7 @@ describe("GyroscopeGuidance", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/Map/Panels/HelpPanel/HelpPanelSpec.tsx b/test/ReactViews/Map/Panels/HelpPanel/HelpPanelSpec.tsx index 9d1aa56fc21..cb81e15d93e 100644 --- a/test/ReactViews/Map/Panels/HelpPanel/HelpPanelSpec.tsx +++ b/test/ReactViews/Map/Panels/HelpPanel/HelpPanelSpec.tsx @@ -23,8 +23,7 @@ describe("HelpPanel", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/Map/Panels/HelpPanel/VideoGuideSpec.tsx b/test/ReactViews/Map/Panels/HelpPanel/VideoGuideSpec.tsx index 30a95b5e0a8..0674dea8b45 100644 --- a/test/ReactViews/Map/Panels/HelpPanel/VideoGuideSpec.tsx +++ b/test/ReactViews/Map/Panels/HelpPanel/VideoGuideSpec.tsx @@ -23,8 +23,7 @@ describe("VideoGuide", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/Map/Panels/LangPanel/LangPanelSpec.tsx b/test/ReactViews/Map/Panels/LangPanel/LangPanelSpec.tsx index 37c04b4be7b..2003f843a51 100644 --- a/test/ReactViews/Map/Panels/LangPanel/LangPanelSpec.tsx +++ b/test/ReactViews/Map/Panels/LangPanel/LangPanelSpec.tsx @@ -20,8 +20,7 @@ describe("LangPanel", function () { viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/Map/Panels/SharePanel/BuildShareLinkSpec.ts b/test/ReactViews/Map/Panels/SharePanel/BuildShareLinkSpec.ts index f1ed6bb81ad..87b5027a8bb 100644 --- a/test/ReactViews/Map/Panels/SharePanel/BuildShareLinkSpec.ts +++ b/test/ReactViews/Map/Panels/SharePanel/BuildShareLinkSpec.ts @@ -36,8 +36,7 @@ beforeEach(function () { viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/Search/BreadcrumbsSpec.tsx b/test/ReactViews/Search/BreadcrumbsSpec.tsx index c15c314be8f..08d15e39fe9 100644 --- a/test/ReactViews/Search/BreadcrumbsSpec.tsx +++ b/test/ReactViews/Search/BreadcrumbsSpec.tsx @@ -25,8 +25,7 @@ describe("Breadcrumbs", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); catalogGroup = new CatalogGroup("group-of-geospatial-cats", terria); terria.addModel(catalogGroup); diff --git a/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx b/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx index 11b936b9f57..a0e0a77d6fb 100644 --- a/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx +++ b/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx @@ -22,8 +22,7 @@ describe("SearchBoxAndResults", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); runInAction(() => { diff --git a/test/ReactViews/Search/SearchBoxSpec.tsx b/test/ReactViews/Search/SearchBoxSpec.tsx index 7c06ec6c542..6c285caa4b1 100644 --- a/test/ReactViews/Search/SearchBoxSpec.tsx +++ b/test/ReactViews/Search/SearchBoxSpec.tsx @@ -18,8 +18,7 @@ describe("SearchBox", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/SidePanel/BrandingSpec.tsx b/test/ReactViews/SidePanel/BrandingSpec.tsx index fbe4094a574..dab452d71ee 100644 --- a/test/ReactViews/SidePanel/BrandingSpec.tsx +++ b/test/ReactViews/SidePanel/BrandingSpec.tsx @@ -15,8 +15,7 @@ describe("Branding", function () { terria = new Terria(); viewState = new ViewState({ terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/StandardUserInterface/TrainerBar/TrainerBarSpec.tsx b/test/ReactViews/StandardUserInterface/TrainerBar/TrainerBarSpec.tsx index 86cb68eccff..d05c06f4652 100644 --- a/test/ReactViews/StandardUserInterface/TrainerBar/TrainerBarSpec.tsx +++ b/test/ReactViews/StandardUserInterface/TrainerBar/TrainerBarSpec.tsx @@ -20,8 +20,7 @@ describe("TrainerBar", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/ToolButtonSpec.tsx b/test/ReactViews/ToolButtonSpec.tsx index c0d52244919..35cdbd1820d 100644 --- a/test/ReactViews/ToolButtonSpec.tsx +++ b/test/ReactViews/ToolButtonSpec.tsx @@ -15,8 +15,7 @@ describe("ToolButton", function () { const terria = new Terria(); viewState = new ViewState({ terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); }); diff --git a/test/ReactViews/ToolSpec.tsx b/test/ReactViews/ToolSpec.tsx index f9a14750a76..97eafd33d5f 100644 --- a/test/ReactViews/ToolSpec.tsx +++ b/test/ReactViews/ToolSpec.tsx @@ -14,8 +14,7 @@ describe("Tool", function () { const terria = new Terria(); viewState = new ViewState({ terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/Tools/ItemSearchTool/ItemSearchToolSpec.tsx b/test/ReactViews/Tools/ItemSearchTool/ItemSearchToolSpec.tsx index 7c810247bb1..84286cc1966 100644 --- a/test/ReactViews/Tools/ItemSearchTool/ItemSearchToolSpec.tsx +++ b/test/ReactViews/Tools/ItemSearchTool/ItemSearchToolSpec.tsx @@ -58,8 +58,7 @@ describe("ItemSearchTool", function () { const terria: Terria = new Terria(); viewState = new ViewState({ terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); item = new MockSearchableItem("test", terria); item.setTrait(CommonStrata.user, "search", { diff --git a/test/ReactViews/Tour/TourPortalSpec.tsx b/test/ReactViews/Tour/TourPortalSpec.tsx index 5c16fe79011..67f5a0576cf 100644 --- a/test/ReactViews/Tour/TourPortalSpec.tsx +++ b/test/ReactViews/Tour/TourPortalSpec.tsx @@ -23,8 +23,7 @@ describe("TourPortal", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/WelcomeMessageSpec.tsx b/test/ReactViews/WelcomeMessageSpec.tsx index 6d4a5be70dd..646ceecb493 100644 --- a/test/ReactViews/WelcomeMessageSpec.tsx +++ b/test/ReactViews/WelcomeMessageSpec.tsx @@ -20,8 +20,7 @@ describe("WelcomeMessage", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/Workbench/Controls/IdealZoomSpec.tsx b/test/ReactViews/Workbench/Controls/IdealZoomSpec.tsx index 34d2e06ed29..bb553cb84f8 100644 --- a/test/ReactViews/Workbench/Controls/IdealZoomSpec.tsx +++ b/test/ReactViews/Workbench/Controls/IdealZoomSpec.tsx @@ -30,8 +30,7 @@ describe("Ideal Zoom", function () { const options = { terria: terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }; viewState = new ViewState(options); }); diff --git a/test/ReactViews/Workbench/Controls/ViewingControlsSpec.tsx b/test/ReactViews/Workbench/Controls/ViewingControlsSpec.tsx index 3df68ffd857..b271fb5f5ce 100644 --- a/test/ReactViews/Workbench/Controls/ViewingControlsSpec.tsx +++ b/test/ReactViews/Workbench/Controls/ViewingControlsSpec.tsx @@ -16,8 +16,7 @@ describe("ViewingControls", function () { terria = new Terria(); viewState = new ViewState({ terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ReactViews/Workflows/WorkflowPanelSpec.tsx b/test/ReactViews/Workflows/WorkflowPanelSpec.tsx index 48bf6dd2242..28f81461995 100644 --- a/test/ReactViews/Workflows/WorkflowPanelSpec.tsx +++ b/test/ReactViews/Workflows/WorkflowPanelSpec.tsx @@ -17,8 +17,7 @@ describe("WorkflowPanel", function () { "./data/regionMapping.json"; viewState = new ViewState({ terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ViewModels/FeatureInfoPanelSpec.ts b/test/ViewModels/FeatureInfoPanelSpec.ts index b82f45d109e..388979f9c84 100644 --- a/test/ViewModels/FeatureInfoPanelSpec.ts +++ b/test/ViewModels/FeatureInfoPanelSpec.ts @@ -12,8 +12,7 @@ describe("FeatureInfoPanel", function () { terria = new Terria(); viewState = new ViewState({ terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ViewModels/MapNavigation/MapToolbarSpec.ts b/test/ViewModels/MapNavigation/MapToolbarSpec.ts index 3df9e1a316e..7e2b533e00b 100644 --- a/test/ViewModels/MapNavigation/MapToolbarSpec.ts +++ b/test/ViewModels/MapNavigation/MapToolbarSpec.ts @@ -11,8 +11,7 @@ describe("MapToolbar", function () { terria = new Terria(); viewState = new ViewState({ terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); }); diff --git a/test/ViewModels/ViewingControlsMenuSpec.ts b/test/ViewModels/ViewingControlsMenuSpec.ts index fa00d863487..4e77287f0e0 100644 --- a/test/ViewModels/ViewingControlsMenuSpec.ts +++ b/test/ViewModels/ViewingControlsMenuSpec.ts @@ -11,8 +11,7 @@ describe("ViewingControlsMenu", function () { const terria = new Terria(); const viewState = new ViewState({ terria, - catalogSearchProvider: undefined, - locationSearchProviders: [] + catalogSearchProvider: undefined }); expect(viewState.globalViewingControlOptions.length).toEqual(0); const generateFunction = (item: CatalogMemberMixin.Instance) => ({ From 7d67dc24dd7830beb459b3a5da72b76d68bbe812 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sat, 4 Nov 2023 19:58:14 +0100 Subject: [PATCH 53/62] test: fix search box result test --- lib/ReactViewModels/SearchState.ts | 6 ++---- test/ReactViews/Search/SearchBoxAndResultsSpec.tsx | 8 ++++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/ReactViewModels/SearchState.ts b/lib/ReactViewModels/SearchState.ts index 9cf3470ac0d..f40998eb9a9 100644 --- a/lib/ReactViewModels/SearchState.ts +++ b/lib/ReactViewModels/SearchState.ts @@ -99,10 +99,8 @@ export default class SearchState { @computed get locationSearchProviders(): LocationSearchProviderMixin.Instance[] { - return ( - this.terria.configParameters.searchBarModel - .locationSearchProvidersArray ?? [] - ); + return this.terria.configParameters.searchBarModel + .locationSearchProvidersArray; } @computed diff --git a/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx b/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx index a0e0a77d6fb..b4dae977cd4 100644 --- a/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx +++ b/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx @@ -9,6 +9,7 @@ import SearchBoxAndResults, { } from "../../../lib/ReactViews/Search/SearchBoxAndResults"; import { ThemeProvider } from "styled-components"; import { terriaTheme } from "../../../lib/ReactViews/StandardUserInterface"; +import CatalogSearchProvider from "../../../lib/Models/SearchProviders/CatalogSearchProvider"; describe("SearchBoxAndResults", function () { let terria: Terria; @@ -26,7 +27,10 @@ describe("SearchBoxAndResults", function () { }); runInAction(() => { - (viewState as any).searchState.catalogSearchProvider = true; + viewState.searchState.catalogSearchProvider = new CatalogSearchProvider( + "catalog", + terria + ); }); }); @@ -88,7 +92,7 @@ describe("SearchBoxAndResults", function () { viewState.searchState.locationSearchText = searchText; viewState.searchState.showLocationSearchResults = true; viewState.searchState.locationSearchResults = []; - (viewState as any).searchState.catalogSearchProvider = false; + (viewState as any).searchState.catalogSearchProvider = undefined; }); act(() => { testRenderer = create( From d75446e4b132096e0f4e50e93e4c751eb82a7c16 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sat, 4 Nov 2023 20:37:02 +0100 Subject: [PATCH 54/62] feat: enforce stronger types for search providers --- lib/Models/SearchProviders/SearchBarModel.ts | 17 +++++++++++++---- .../SearchProviders/SearchModelFactory.ts | 12 ++++++++++++ .../SearchProviders/SearchProviderFactory.ts | 4 ++-- .../upsertSearchProviderFromJson.ts | 7 ++++--- lib/ReactViewModels/SearchState.ts | 13 ++++++++----- .../Search/SearchBoxAndResultsSpec.tsx | 12 +++--------- 6 files changed, 42 insertions(+), 23 deletions(-) create mode 100644 lib/Models/SearchProviders/SearchModelFactory.ts diff --git a/lib/Models/SearchProviders/SearchBarModel.ts b/lib/Models/SearchProviders/SearchBarModel.ts index 603e3287d85..a6364d4d651 100644 --- a/lib/Models/SearchProviders/SearchBarModel.ts +++ b/lib/Models/SearchProviders/SearchBarModel.ts @@ -16,10 +16,15 @@ import Terria from "../Terria"; import SearchProviderFactory from "./SearchProviderFactory"; import upsertSearchProviderFromJson from "./upsertSearchProviderFromJson"; import LocationSearchProviderMixin from "../../ModelMixins/SearchProviders/LocationSearchProviderMixin"; +import CatalogSearchProviderMixin from "../../ModelMixins/SearchProviders/CatalogSearchProviderMixin"; +import RuntimeError from "terriajs-cesium/Source/Core/RuntimeError"; export class SearchBarModel extends CreateModel(SearchBarTraits) { private locationSearchProviders = observable.map(); + @observable + catalogSearchProvider: CatalogSearchProviderMixin.Instance | undefined; + constructor(readonly terria: Terria) { super("search-bar-model", terria); @@ -70,10 +75,14 @@ export class SearchBarModel extends CreateModel(SearchBarTraits) { } if (this.locationSearchProviders.has(model.uniqueId)) { - console.log( - new DeveloperError( - "A SearchProvider with the specified ID already exists." - ) + throw new RuntimeError( + "A SearchProvider with the specified ID already exists." + ); + } + + if (!LocationSearchProviderMixin.isMixedInto(model)) { + throw new RuntimeError( + "SearchProvider must be a LocationSearchProvider." ); } diff --git a/lib/Models/SearchProviders/SearchModelFactory.ts b/lib/Models/SearchProviders/SearchModelFactory.ts new file mode 100644 index 00000000000..e5f05182d8c --- /dev/null +++ b/lib/Models/SearchProviders/SearchModelFactory.ts @@ -0,0 +1,12 @@ +import LocationSearchProviderMixin from "../../ModelMixins/SearchProviders/LocationSearchProviderMixin"; +import { ModelConstructor } from "../Definition/Model"; +import ModelFactory from "../Definition/ModelFactory"; + +export class SearchModelFactory extends ModelFactory { + register( + type: string, + constructor: ModelConstructor + ) { + super.register(type, constructor); + } +} diff --git a/lib/Models/SearchProviders/SearchProviderFactory.ts b/lib/Models/SearchProviders/SearchProviderFactory.ts index 23d93e86ba6..e42059bbb78 100644 --- a/lib/Models/SearchProviders/SearchProviderFactory.ts +++ b/lib/Models/SearchProviders/SearchProviderFactory.ts @@ -1,4 +1,4 @@ -import ModelFactory from "../Definition/ModelFactory"; +import { SearchModelFactory } from "./SearchModelFactory"; -const SearchProviderFactory = new ModelFactory(); +const SearchProviderFactory = new SearchModelFactory(); export default SearchProviderFactory; diff --git a/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts index 331cd2e43c2..08a0efd42e1 100644 --- a/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts +++ b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts @@ -1,18 +1,19 @@ import i18next from "i18next"; +import { runInAction } from "mobx"; import Result from "../../Core/Result"; import TerriaError from "../../Core/TerriaError"; import { applyTranslationIfExists } from "../../Language/languageHelpers"; +import SearchProviderMixin from "../../ModelMixins/SearchProviders/SearchProviderMixin"; import CommonStrata from "../Definition/CommonStrata"; import { BaseModel } from "../Definition/Model"; -import ModelFactory from "../Definition/ModelFactory"; import updateModelFromJson from "../Definition/updateModelFromJson"; import Terria from "../Terria"; +import { SearchModelFactory } from "./SearchModelFactory"; import StubSearchProvider from "./StubSearchProvider"; import createStubSearchProvider from "./createStubSearchProvider"; -import { runInAction } from "mobx"; export default function upsertSearchProviderFromJson( - factory: ModelFactory, + factory: SearchModelFactory, terria: Terria, stratumName: string, json: any diff --git a/lib/ReactViewModels/SearchState.ts b/lib/ReactViewModels/SearchState.ts index f40998eb9a9..0376607d77d 100644 --- a/lib/ReactViewModels/SearchState.ts +++ b/lib/ReactViewModels/SearchState.ts @@ -20,9 +20,6 @@ interface SearchStateOptions { } export default class SearchState { - @observable - catalogSearchProvider: CatalogSearchProviderMixin.Instance | undefined; - @observable catalogSearchText: string = ""; @observable isWaitingToStartCatalogSearch: boolean = false; @@ -51,9 +48,10 @@ export default class SearchState { this.terria = options.terria; - this.catalogSearchProvider = + this.terria.configParameters.searchBarModel.catalogSearchProvider = options.catalogSearchProvider || new CatalogSearchProvider("catalog-search-provider", options.terria); + const self = this; this._catalogSearchDisposer = reaction( @@ -98,11 +96,16 @@ export default class SearchState { } @computed - get locationSearchProviders(): LocationSearchProviderMixin.Instance[] { + private get locationSearchProviders(): LocationSearchProviderMixin.Instance[] { return this.terria.configParameters.searchBarModel .locationSearchProvidersArray; } + @computed + get catalogSearchProvider(): CatalogSearchProviderMixin.Instance | undefined { + return this.terria.configParameters.searchBarModel.catalogSearchProvider; + } + @computed get unifiedSearchProviders(): SearchProviderMixin.Instance[] { return filterOutUndefined([ diff --git a/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx b/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx index b4dae977cd4..0071d4dde8a 100644 --- a/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx +++ b/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx @@ -23,14 +23,7 @@ describe("SearchBoxAndResults", function () { }); viewState = new ViewState({ terria: terria, - catalogSearchProvider: undefined - }); - - runInAction(() => { - viewState.searchState.catalogSearchProvider = new CatalogSearchProvider( - "catalog", - terria - ); + catalogSearchProvider: new CatalogSearchProvider("catalog", terria) }); }); @@ -92,7 +85,8 @@ describe("SearchBoxAndResults", function () { viewState.searchState.locationSearchText = searchText; viewState.searchState.showLocationSearchResults = true; viewState.searchState.locationSearchResults = []; - (viewState as any).searchState.catalogSearchProvider = undefined; + viewState.terria.configParameters.searchBarModel.catalogSearchProvider = + undefined; }); act(() => { testRenderer = create( From 67b585b82d52694bf7b3c59ae3feab8022755520 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sat, 4 Nov 2023 20:39:55 +0100 Subject: [PATCH 55/62] fix: catalog search provider no results translation --- lib/Models/SearchProviders/CatalogSearchProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Models/SearchProviders/CatalogSearchProvider.ts b/lib/Models/SearchProviders/CatalogSearchProvider.ts index a30ec3646d6..dfde8265a3a 100644 --- a/lib/Models/SearchProviders/CatalogSearchProvider.ts +++ b/lib/Models/SearchProviders/CatalogSearchProvider.ts @@ -193,7 +193,7 @@ export default class CatalogSearchProvider extends CatalogSearchProviderMixin( if (searchResults.results.length === 0) { searchResults.message = { - content: "translate#viewModels.searchErrorOccurred" + content: "translate#viewModels.searchNoCatalogueItem" }; } } catch (e) { From 5543250668d61d380ab976157a934e99d5ab9c1e Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sat, 4 Nov 2023 22:21:32 +0100 Subject: [PATCH 56/62] test: write more tests for bing maps search provider --- lib/Core/loadJsonp.js | 25 ---- lib/Core/loadJsonp.ts | 30 +++++ .../SearchProviders/BingMapsSearchProvider.ts | 7 +- lib/ReactViewModels/SearchState.ts | 11 +- .../BingMapsSearchProviderTraits.ts | 7 + .../BingMapsSearchProviderSpec.ts | 122 +++++++++++++++++- .../LocationSearchProviderSpec.ts | 2 +- 7 files changed, 165 insertions(+), 39 deletions(-) delete mode 100644 lib/Core/loadJsonp.js create mode 100644 lib/Core/loadJsonp.ts diff --git a/lib/Core/loadJsonp.js b/lib/Core/loadJsonp.js deleted file mode 100644 index 4b666d219fd..00000000000 --- a/lib/Core/loadJsonp.js +++ /dev/null @@ -1,25 +0,0 @@ -const Resource = require("terriajs-cesium/Source/Core/Resource").default; - -function loadJsonp(urlOrResource, callbackParameterName) { - var resource = Resource.createIfNeeded(urlOrResource); - return resource.fetchJsonp(callbackParameterName); -} - -Object.defineProperties(loadJsonp, { - loadAndExecuteScript: { - get: function () { - return Resource._Implementations.loadAndExecuteScript; - }, - set: function (value) { - Resource._Implementations.loadAndExecuteScript = value; - } - }, - - defaultLoadAndExecuteScript: { - get: function () { - return Resource._DefaultImplementations.loadAndExecuteScript; - } - } -}); - -module.exports = loadJsonp; diff --git a/lib/Core/loadJsonp.ts b/lib/Core/loadJsonp.ts new file mode 100644 index 00000000000..8a4eece71c5 --- /dev/null +++ b/lib/Core/loadJsonp.ts @@ -0,0 +1,30 @@ +import Resource from "terriajs-cesium/Source/Core/Resource"; + +export function loadJsonp( + urlOrResource: Resource, + callbackParameterName: string +): Promise { + const resource = + typeof urlOrResource === "string" + ? new Resource({ url: urlOrResource }) + : urlOrResource; + + return resource.fetchJsonp(callbackParameterName)!; +} + +Object.defineProperties(loadJsonp, { + loadAndExecuteScript: { + get: function () { + return (Resource as any)._Implementations.loadAndExecuteScript; + }, + set: function (value) { + (Resource as any)._Implementations.loadAndExecuteScript = value; + } + }, + + defaultLoadAndExecuteScript: { + get: function () { + return (Resource as any)._DefaultImplementations.loadAndExecuteScript; + } + } +}); diff --git a/lib/Models/SearchProviders/BingMapsSearchProvider.ts b/lib/Models/SearchProviders/BingMapsSearchProvider.ts index 6393a7e2f1d..8e1c6d16a16 100644 --- a/lib/Models/SearchProviders/BingMapsSearchProvider.ts +++ b/lib/Models/SearchProviders/BingMapsSearchProvider.ts @@ -7,7 +7,7 @@ import { Category, SearchAction } from "../../Core/AnalyticEvents/analyticEvents"; -import loadJsonp from "../../Core/loadJsonp"; +import { loadJsonp } from "../../Core/loadJsonp"; import { applyTranslationIfExists } from "../../Language/languageHelpers"; import LocationSearchProviderMixin, { getMapCenter @@ -75,7 +75,8 @@ export default class BingMapsSearchProvider extends LocationSearchProviderMixin( queryParameters: { culture: this.culture, query: searchText, - key: this.key + key: this.key, + maxResults: this.maxResults } }); @@ -103,7 +104,7 @@ export default class BingMapsSearchProvider extends LocationSearchProviderMixin( return; } - var resourceSet = result.resourceSets[0]; + const resourceSet = result.resourceSets[0]; if (resourceSet.resources.length === 0) { searchResults.message = { content: "translate#viewModels.searchNoLocations" diff --git a/lib/ReactViewModels/SearchState.ts b/lib/ReactViewModels/SearchState.ts index 0376607d77d..3cdb884dc0c 100644 --- a/lib/ReactViewModels/SearchState.ts +++ b/lib/ReactViewModels/SearchState.ts @@ -4,7 +4,8 @@ import { IReactionDisposer, observable, reaction, - makeObservable + makeObservable, + runInAction } from "mobx"; import filterOutUndefined from "../Core/filterOutUndefined"; import LocationSearchProviderMixin from "../ModelMixins/SearchProviders/LocationSearchProviderMixin"; @@ -48,9 +49,11 @@ export default class SearchState { this.terria = options.terria; - this.terria.configParameters.searchBarModel.catalogSearchProvider = - options.catalogSearchProvider || - new CatalogSearchProvider("catalog-search-provider", options.terria); + runInAction(() => { + this.terria.configParameters.searchBarModel.catalogSearchProvider = + options.catalogSearchProvider || + new CatalogSearchProvider("catalog-search-provider", options.terria); + }); const self = this; diff --git a/lib/Traits/SearchProviders/BingMapsSearchProviderTraits.ts b/lib/Traits/SearchProviders/BingMapsSearchProviderTraits.ts index d233d921f0d..4ec8f1a0f41 100644 --- a/lib/Traits/SearchProviders/BingMapsSearchProviderTraits.ts +++ b/lib/Traits/SearchProviders/BingMapsSearchProviderTraits.ts @@ -32,4 +32,11 @@ export default class BingMapsSearchProviderTraits extends mixTraits( For a list of supported cultures, see [Supported Culture Codes](https://docs.microsoft.com/en-us/bingmaps/rest-services/common-parameters-and-types/supported-culture-codes)` }) culture: string = "en-au"; + + @primitiveTrait({ + type: "number", + name: "Max results", + description: "The maximum number of results to return." + }) + maxResults: number = 5; } diff --git a/test/Models/SearchProviders/BingMapsSearchProviderSpec.ts b/test/Models/SearchProviders/BingMapsSearchProviderSpec.ts index 4c932a4167f..4035b4bc26b 100644 --- a/test/Models/SearchProviders/BingMapsSearchProviderSpec.ts +++ b/test/Models/SearchProviders/BingMapsSearchProviderSpec.ts @@ -1,10 +1,16 @@ +import Resource from "terriajs-cesium/Source/Core/Resource"; import LocationSearchProviderMixin from "../../../lib/ModelMixins/SearchProviders/LocationSearchProviderMixin"; import BingMapsSearchProvider from "../../../lib/Models/SearchProviders/BingMapsSearchProvider"; import Terria from "../../../lib/Models/Terria"; +import { exp } from "protomaps"; +import { runInAction } from "mobx"; +import { CommonStrata } from "terriajs-plugin-api"; +import * as loadJsonp from "../../../lib/Core/loadJsonp"; -describe("BingMapsSearchProviderTraits", function () { +describe("BingMapsSearchProvider", function () { let terria: Terria; let bingMapsSearchProvider: BingMapsSearchProvider; + beforeEach(async function () { terria = new Terria({ baseUrl: "./" @@ -12,17 +18,121 @@ describe("BingMapsSearchProviderTraits", function () { bingMapsSearchProvider = new BingMapsSearchProvider("test", terria); }); - it(" - properly mixed", function () { + it(" - properly mixed", () => { expect( LocationSearchProviderMixin.isMixedInto(bingMapsSearchProvider) ).toBeTruthy(); }); - describe(" - default values", function () { - it(" - propperly defines default url", function () { - expect(bingMapsSearchProvider.url).toEqual( - "https://dev.virtualearth.net/" + it(" - propperly defines default url", () => { + expect(bingMapsSearchProvider.url).toEqual("https://dev.virtualearth.net/"); + }); + + it(" - propperly sets query parameters", () => { + runInAction(() => { + bingMapsSearchProvider.setTrait( + CommonStrata.definition, + "key", + "test-key" + ); + bingMapsSearchProvider.setTrait( + CommonStrata.definition, + "minCharacters", + 3 + ); + bingMapsSearchProvider.setTrait( + CommonStrata.definition, + "mapCenter", + false ); }); + const test = spyOn(loadJsonp, "loadJsonp").and.returnValue( + Promise.resolve({ + resourceSets: [] + }) + ); + + const searchResult = bingMapsSearchProvider.search("test"); + + expect(test).toHaveBeenCalledWith( + new Resource({ + url: "https://dev.virtualearth.net/REST/v1/Locations", + queryParameters: { + culture: "en-au", + query: "test", + key: "test-key", + maxResults: 5 + } + }), + "jsonp" + ); + }); + + it(" - propperly sort the search results", async () => { + runInAction(() => { + bingMapsSearchProvider.setTrait( + CommonStrata.definition, + "key", + "test-key" + ); + bingMapsSearchProvider.setTrait( + CommonStrata.definition, + "minCharacters", + 3 + ); + bingMapsSearchProvider.setTrait( + CommonStrata.definition, + "mapCenter", + false + ); + }); + const test = spyOn(loadJsonp, "loadJsonp").and.returnValue( + Promise.resolve({ + resourceSets: [ + { + resources: [ + { + name: "test result 1", + address: { + countryRegion: "Italy" + }, + point: { + type: "Point", + coordinates: [46.06452179, 12.08810234] + }, + bbox: [ + 46.06022262573242, 12.072776794433594, 46.06576919555664, + 12.101446151733398 + ] + }, + { + name: "test result 2", + address: { + countryRegion: "Australia" + }, + point: { + type: "Point", + coordinates: [46.06452179, 12.08810234] + }, + bbox: [ + 45.96084213256836, 11.978724479675293, 46.09341049194336, + 12.2274169921875 + ] + }, + { + name: undefined + } + ] + } + ] + }) + ); + + const searchResult = await bingMapsSearchProvider.search("test"); + + expect(searchResult.results.length).toEqual(2); + expect(searchResult.message).toBeUndefined(); + expect(searchResult.results[0].name).toEqual("test result 2"); + expect(searchResult.results[1].name).toEqual("test result 1, Italy"); }); }); diff --git a/test/Models/SearchProviders/LocationSearchProviderSpec.ts b/test/Models/SearchProviders/LocationSearchProviderSpec.ts index e9893902038..0f744648c79 100644 --- a/test/Models/SearchProviders/LocationSearchProviderSpec.ts +++ b/test/Models/SearchProviders/LocationSearchProviderSpec.ts @@ -2,7 +2,7 @@ import LocationSearchProviderMixin from "../../../lib/ModelMixins/SearchProvider import BingMapsSearchProvider from "../../../lib/Models/SearchProviders/BingMapsSearchProvider"; import Terria from "../../../lib/Models/Terria"; -describe("LocationSearchProviderTraits", function () { +describe("LocationSearchProvider", function () { let terria: Terria; let bingMapsSearchProvider: BingMapsSearchProvider; beforeEach(async function () { From 0058106bff2a95e3cbdb433424d9b1a7576f1a16 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Wed, 15 Nov 2023 21:32:47 +0100 Subject: [PATCH 57/62] feat: migrate cesium ion search provider to model system --- .../CesiumIonSearchProvider.ts | 99 ++++++++++--------- .../registerSearchProviders.ts | 6 ++ .../CesiumIonSearchProviderTraits.ts | 19 ++++ .../CesiumIonSearchProviderSpec.ts | 28 ++++-- 4 files changed, 96 insertions(+), 56 deletions(-) create mode 100644 lib/Traits/SearchProviders/CesiumIonSearchProviderTraits.ts diff --git a/lib/Models/SearchProviders/CesiumIonSearchProvider.ts b/lib/Models/SearchProviders/CesiumIonSearchProvider.ts index e0e958c6f68..e414b0a222b 100644 --- a/lib/Models/SearchProviders/CesiumIonSearchProvider.ts +++ b/lib/Models/SearchProviders/CesiumIonSearchProvider.ts @@ -1,16 +1,20 @@ -import SearchProvider from "./SearchProvider"; -import { observable, makeObservable, runInAction } from "mobx"; +import i18next from "i18next"; +import { makeObservable, observable, runInAction } from "mobx"; import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; import defaultValue from "terriajs-cesium/Source/Core/defaultValue"; -import i18next from "i18next"; -import Terria from "../Terria"; -import SearchProviderResults from "./SearchProviderResults"; -import SearchResult from "./SearchResult"; -import loadJson from "../../Core/loadJson"; +import { CommonStrata } from "terriajs-plugin-api"; import { Category, SearchAction } from "../../Core/AnalyticEvents/analyticEvents"; +import loadJson from "../../Core/loadJson"; +import { applyTranslationIfExists } from "../../Language/languageHelpers"; +import LocationSearchProviderMixin from "../../ModelMixins/SearchProviders/LocationSearchProviderMixin"; +import CesiumIonSearchProviderTraits from "../../Traits/SearchProviders/CesiumIonSearchProviderTraits"; +import CreateModel from "../Definition/CreateModel"; +import Terria from "../Terria"; +import SearchProviderResults from "./SearchProviderResults"; +import SearchResult from "./SearchResult"; interface CesiumIonSearchProviderOptions { terria: Terria; @@ -28,51 +32,56 @@ interface CesiumIonGeocodeResult { features: CesiumIonGeocodeResultFeature[]; } -export default class CesiumIonSearchProvider extends SearchProvider { - readonly terria: Terria; - @observable key: string | undefined; - @observable flightDurationSeconds: number; - @observable url: string; +export default class CesiumIonSearchProvider extends LocationSearchProviderMixin( + CreateModel(CesiumIonSearchProviderTraits) +) { + static readonly type = "cesium-ion-search-provider"; + + get type() { + return CesiumIonSearchProvider.type; + } - constructor(options: CesiumIonSearchProviderOptions) { - super(); + constructor(uniqueId: string | undefined, terria: Terria) { + super(uniqueId, terria); makeObservable(this); - this.terria = options.terria; - this.name = i18next.t("viewModels.searchLocations"); - this.url = defaultValue( - options.url, - "https://api.cesium.com/v1/geocode/search" - ); - this.key = options.key; - this.flightDurationSeconds = defaultValue( - options.flightDurationSeconds, - 1.5 - ); + runInAction(() => { + if (!this.key && this.terria.configParameters.cesiumIonAccessToken) { + this.setTrait( + CommonStrata.defaults, + "key", + this.terria.configParameters.cesiumIonAccessToken + ); + } + this.showWarning(); + }); + } - if (!this.key) { + showWarning() { + if (!this.key || this.key === "") { console.warn( - "The " + - this.name + - " geocoder will always return no results because a CesiumIon key has not been provided. Please get a CesiumIon key from ion.cesium.com, ensure it has geocoding permission and add it to parameters.cesiumIonAccessToken in config.json." + `The ${applyTranslationIfExists(this.name, i18next)}(${ + this.type + }) geocoder will always return no results because a CesiumIon key has not been provided. Please get a CesiumIon key from ion.cesium.com, ensure it has geocoding permission and add it to searchProvider.key or parameters.cesiumIonAccessToken in config.json.` ); } } - protected async doSearch( - searchText: string, - searchResults: SearchProviderResults - ): Promise { - if (searchText === undefined || /^\s*$/.test(searchText)) { - return Promise.resolve(); - } - + protected logEvent(searchText: string): void { this.terria.analytics?.logEvent( Category.search, SearchAction.cesium, searchText ); + } + + protected async doSearch( + searchText: string, + searchResults: SearchProviderResults + ): Promise { + searchResults.results.length = 0; + searchResults.message = undefined; let response: CesiumIonGeocodeResult; try { @@ -80,22 +89,20 @@ export default class CesiumIonSearchProvider extends SearchProvider { `${this.url}?text=${searchText}&access_token=${this.key}` ); } catch (e) { - runInAction(() => { - searchResults.message = i18next.t("viewModels.searchErrorOccurred"); - }); + searchResults.message = { + content: "translate#viewModels.searchErrorOccurred" + }; return; } runInAction(() => { - if (!response.features) { - searchResults.message = i18next.t("viewModels.searchNoLocations"); + if (!response.features || response.features.length === 0) { + searchResults.message = { + content: "translate#viewModels.searchNoLocations" + }; return; } - if (response.features.length === 0) { - searchResults.message = i18next.t("viewModels.searchNoLocations"); - } - searchResults.results = response.features.map((feature) => { const [w, s, e, n] = feature.bbox; const rectangle = Rectangle.fromDegrees(w, s, e, n); diff --git a/lib/Models/SearchProviders/registerSearchProviders.ts b/lib/Models/SearchProviders/registerSearchProviders.ts index 49d2ca854f2..5eb5a1e1fdd 100644 --- a/lib/Models/SearchProviders/registerSearchProviders.ts +++ b/lib/Models/SearchProviders/registerSearchProviders.ts @@ -1,5 +1,6 @@ import AustralianGazetteerSearchProvider from "./AustralianGazetteerSearchProvider"; import BingMapsSearchProvider from "./BingMapsSearchProvider"; +import CesiumIonSearchProvider from "./CesiumIonSearchProvider"; import SearchProviderFactory from "./SearchProviderFactory"; export default function registerSearchProviders() { @@ -8,6 +9,11 @@ export default function registerSearchProviders() { BingMapsSearchProvider ); + SearchProviderFactory.register( + CesiumIonSearchProvider.type, + CesiumIonSearchProvider + ); + SearchProviderFactory.register( AustralianGazetteerSearchProvider.type, AustralianGazetteerSearchProvider diff --git a/lib/Traits/SearchProviders/CesiumIonSearchProviderTraits.ts b/lib/Traits/SearchProviders/CesiumIonSearchProviderTraits.ts new file mode 100644 index 00000000000..7c7e7e08c05 --- /dev/null +++ b/lib/Traits/SearchProviders/CesiumIonSearchProviderTraits.ts @@ -0,0 +1,19 @@ +import { mixTraits, primitiveTrait } from "terriajs-plugin-api"; +import LocationSearchProviderTraits, { + SearchProviderMapCenterTraits +} from "./LocationSearchProviderTraits"; + +export default class BingMapsSearchProviderTraits extends mixTraits( + LocationSearchProviderTraits, + SearchProviderMapCenterTraits +) { + url: string = "https://api.cesium.com/v1/geocode/search"; + + @primitiveTrait({ + type: "string", + name: "Key", + description: + "The Cesium ION key. If not provided, will try to use the global cesium ion key." + }) + key?: string; +} diff --git a/test/Models/SearchProviders/CesiumIonSearchProviderSpec.ts b/test/Models/SearchProviders/CesiumIonSearchProviderSpec.ts index f000088d6f9..e0858fc3542 100644 --- a/test/Models/SearchProviders/CesiumIonSearchProviderSpec.ts +++ b/test/Models/SearchProviders/CesiumIonSearchProviderSpec.ts @@ -1,6 +1,7 @@ import CesiumIonSearchProvider from "../../../lib/Models/SearchProviders/CesiumIonSearchProvider"; import Terria from "../../../lib/Models//Terria"; import * as loadJson from "../../../lib/Core/loadJson"; +import CommonStrata from "../../../lib/Models/Definition/CommonStrata"; const fixture = { features: [ @@ -17,15 +18,18 @@ const fixture = { }; describe("CesiumIonSearchProvider", () => { - const searchProvider = new CesiumIonSearchProvider({ - key: "testkey", - url: "api.test.com", - terria: { - currentViewer: { - zoomTo: () => {} - } - } as unknown as Terria + let terria: Terria; + let searchProvider: CesiumIonSearchProvider; + + beforeEach(async function () { + terria = new Terria({ + baseUrl: "./" + }); + searchProvider = new CesiumIonSearchProvider("test-cesium-ion", terria); + searchProvider.setTrait(CommonStrata.definition, "key", "testkey"); + searchProvider.setTrait(CommonStrata.definition, "url", "api.test.com"); }); + it("Handles valid results", async () => { spyOn(loadJson, "default").and.returnValue( new Promise((resolve) => resolve(fixture)) @@ -47,13 +51,17 @@ describe("CesiumIonSearchProvider", () => { const result = await searchProvider.search("test"); console.log(result); expect(result.results.length).toBe(0); - expect(result.message).toBe("viewModels.searchNoLocations"); + expect(result.message?.content).toBe( + "translate#viewModels.searchNoLocations" + ); }); it("Handles error", async () => { spyOn(loadJson, "default").and.throwError("error"); const result = await searchProvider.search("test"); expect(result.results.length).toBe(0); - expect(result.message).toBe("viewModels.searchErrorOccurred"); + expect(result.message?.content).toBe( + "translate#viewModels.searchErrorOccurred" + ); }); }); From 6dc41280f8b6a383e4fdad3d002fdb08bf820e45 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sun, 19 Nov 2023 16:55:34 +0100 Subject: [PATCH 58/62] feat: move searchBarModel from configParametes to terria property --- .../WebFeatureServiceSearchProviderMixin.ts | 2 +- .../SearchProviders/CatalogSearchProvider.ts | 2 +- lib/Models/SearchProviders/SearchBarModel.ts | 27 ++++++++++++++---- .../createStubSearchProvider.ts | 2 +- .../upsertSearchProviderFromJson.ts | 8 +++--- lib/Models/Terria.ts | 28 ++++++++----------- lib/ReactViewModels/SearchState.ts | 7 ++--- lib/ReactViewModels/ViewState.ts | 1 - lib/ReactViews/Mobile/MobileHeader.jsx | 2 +- .../Search/LocationSearchResults.tsx | 3 +- lib/ReactViews/SidePanel/SidePanel.tsx | 2 +- lib/Traits/ModelTraitsInterface.ts | 7 ----- .../Search/SearchBoxAndResultsSpec.tsx | 3 +- 13 files changed, 46 insertions(+), 48 deletions(-) delete mode 100644 lib/Traits/ModelTraitsInterface.ts diff --git a/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts index ef7bc29df00..622a5bddb57 100644 --- a/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/WebFeatureServiceSearchProviderMixin.ts @@ -224,7 +224,7 @@ function createZoomToFunction( const flightDurationSeconds: number = model.flightDurationSeconds || - model.terria.configParameters.searchBarModel.flightDurationSeconds; + model.terria.searchBarModel.flightDurationSeconds; return function () { model.terria.currentViewer.zoomTo(rectangle, flightDurationSeconds); diff --git a/lib/Models/SearchProviders/CatalogSearchProvider.ts b/lib/Models/SearchProviders/CatalogSearchProvider.ts index dfde8265a3a..81063efcc85 100644 --- a/lib/Models/SearchProviders/CatalogSearchProvider.ts +++ b/lib/Models/SearchProviders/CatalogSearchProvider.ts @@ -121,7 +121,7 @@ export default class CatalogSearchProvider extends CatalogSearchProviderMixin( this.setTrait( CommonStrata.defaults, "minCharacters", - terria.configParameters.searchBarModel.minCharacters + terria.searchBarModel.minCharacters ); } diff --git a/lib/Models/SearchProviders/SearchBarModel.ts b/lib/Models/SearchProviders/SearchBarModel.ts index a6364d4d651..2959d38f2b7 100644 --- a/lib/Models/SearchProviders/SearchBarModel.ts +++ b/lib/Models/SearchProviders/SearchBarModel.ts @@ -5,19 +5,23 @@ import { makeObservable, observable } from "mobx"; +import { JsonObject } from "protomaps"; import DeveloperError from "terriajs-cesium/Source/Core/DeveloperError"; +import RuntimeError from "terriajs-cesium/Source/Core/RuntimeError"; import Result from "../../Core/Result"; import TerriaError from "../../Core/TerriaError"; +import CatalogSearchProviderMixin from "../../ModelMixins/SearchProviders/CatalogSearchProviderMixin"; +import LocationSearchProviderMixin from "../../ModelMixins/SearchProviders/LocationSearchProviderMixin"; import { SearchBarTraits } from "../../Traits/SearchProviders/SearchBarTraits"; +import SearchProviderTraits from "../../Traits/SearchProviders/SearchProviderTraits"; import CommonStrata from "../Definition/CommonStrata"; import CreateModel from "../Definition/CreateModel"; import { BaseModel } from "../Definition/Model"; +import ModelPropertiesFromTraits from "../Definition/ModelPropertiesFromTraits"; +import updateModelFromJson from "../Definition/updateModelFromJson"; import Terria from "../Terria"; import SearchProviderFactory from "./SearchProviderFactory"; import upsertSearchProviderFromJson from "./upsertSearchProviderFromJson"; -import LocationSearchProviderMixin from "../../ModelMixins/SearchProviders/LocationSearchProviderMixin"; -import CatalogSearchProviderMixin from "../../ModelMixins/SearchProviders/CatalogSearchProviderMixin"; -import RuntimeError from "terriajs-cesium/Source/Core/RuntimeError"; export class SearchBarModel extends CreateModel(SearchBarTraits) { private locationSearchProviders = observable.map(); @@ -31,10 +35,21 @@ export class SearchBarModel extends CreateModel(SearchBarTraits) { makeObservable(this); } - initializeSearchProviders() { - const errors: TerriaError[] = []; + updateModelConfig(config?: ModelPropertiesFromTraits) { + if (config) { + updateModelFromJson( + this, + CommonStrata.definition, + config as never as JsonObject + ); + } + return this; + } - const searchProviders = this.terria.configParameters.searchProviders; + initializeSearchProviders( + searchProviders: ModelPropertiesFromTraits[] + ) { + const errors: TerriaError[] = []; if (!isObservableArray(searchProviders)) { errors.push( diff --git a/lib/Models/SearchProviders/createStubSearchProvider.ts b/lib/Models/SearchProviders/createStubSearchProvider.ts index 8658a0b3aac..9f8b9a74c88 100644 --- a/lib/Models/SearchProviders/createStubSearchProvider.ts +++ b/lib/Models/SearchProviders/createStubSearchProvider.ts @@ -22,6 +22,6 @@ export default function createStubSearchProvider( const stub = new StubSearchProvider(idToUse, terria); stub.setTrait(CommonStrata.underride, "name", stub.uniqueId); - terria.configParameters.searchBarModel.addSearchProvider(stub); + terria.searchBarModel.addSearchProvider(stub); return stub; } diff --git a/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts index 08a0efd42e1..81c863b1ad0 100644 --- a/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts +++ b/lib/Models/SearchProviders/upsertSearchProviderFromJson.ts @@ -55,7 +55,7 @@ export default function upsertSearchProviderFromJson( if (model.type !== StubSearchProvider.type) { try { - model.terria.configParameters.searchBarModel.addSearchProvider(model); + model.terria.searchBarModel.addSearchProvider(model); } catch (error) { errors.push(TerriaError.from(error)); } @@ -88,19 +88,19 @@ function setDefaultTraits(model: BaseModel) { model.setTrait( CommonStrata.defaults, "flightDurationSeconds", - terria.configParameters.searchBarModel.flightDurationSeconds + terria.searchBarModel.flightDurationSeconds ); model.setTrait( CommonStrata.defaults, "minCharacters", - terria.configParameters.searchBarModel.minCharacters + terria.searchBarModel.minCharacters ); model.setTrait( CommonStrata.defaults, "recommendedListLength", - terria.configParameters.searchBarModel?.recommendedListLength + terria.searchBarModel.recommendedListLength ); }); } diff --git a/lib/Models/Terria.ts b/lib/Models/Terria.ts index 776e08dea20..24773609c63 100644 --- a/lib/Models/Terria.ts +++ b/lib/Models/Terria.ts @@ -124,6 +124,9 @@ import TimelineStack from "./TimelineStack"; import { isViewerMode, setViewerMode } from "./ViewerMode"; import Workbench from "./Workbench"; import SelectableDimensionWorkflow from "./Workflows/SelectableDimensionWorkflow"; +import { SearchBarTraits } from "../Traits/SearchProviders/SearchBarTraits"; +import ModelPropertiesFromTraits from "./Definition/ModelPropertiesFromTraits"; +import SearchProviderTraits from "../Traits/SearchProviders/SearchProviderTraits"; // import overrides from "../Overrides/defaults.jsx"; @@ -345,8 +348,8 @@ export interface ConfigParameters { /** * The search bar allows requesting information from various search services at once. */ - searchBarModel: SearchBarModel; - searchProviders: any[]; + searchBarConfig?: ModelPropertiesFromTraits; + searchProviders: ModelPropertiesFromTraits[]; } interface StartOptions { @@ -442,6 +445,7 @@ export default class Terria { readonly overlays = new Workbench(); readonly catalog = new Catalog(this); readonly baseMapsModel = new BaseMapsModel("basemaps", this); + readonly searchBarModel = new SearchBarModel(this); readonly timelineClock = new Clock({ shouldAnimate: false }); // readonly overrides: any = overrides; // TODO: add options.functionOverrides like in master @@ -560,7 +564,7 @@ export default class Terria { relatedMaps: defaultRelatedMaps, aboutButtonHrefUrl: "about.html", plugins: undefined, - searchBarModel: new SearchBarModel(this), + searchBarConfig: undefined, searchProviders: [] }; @@ -1053,8 +1057,9 @@ export default class Terria { ) ); - this.configParameters.searchBarModel - .initializeSearchProviders() + this.searchBarModel + .updateModelConfig(this.configParameters.searchBarConfig) + .initializeSearchProviders(this.configParameters.searchProviders) .catchError((error) => this.raiseErrorToUser( TerriaError.from(error, "Failed to initialize searchProviders") @@ -1296,18 +1301,7 @@ export default class Terria { updateParameters(parameters: ConfigParameters | JsonObject): void { Object.entries(parameters).forEach(([key, value]) => { if (this.configParameters.hasOwnProperty(key)) { - if (key === "searchBarModel") { - if (!isDefined(this.configParameters.searchBarModel)) { - this.configParameters.searchBarModel = new SearchBarModel(this); - } - updateModelFromJson( - this.configParameters.searchBarModel, - CommonStrata.definition, - value - ); - } else { - (this.configParameters as any)[key] = value; - } + (this.configParameters as any)[key] = value; } }); diff --git a/lib/ReactViewModels/SearchState.ts b/lib/ReactViewModels/SearchState.ts index 3cdb884dc0c..cae172c8b99 100644 --- a/lib/ReactViewModels/SearchState.ts +++ b/lib/ReactViewModels/SearchState.ts @@ -50,7 +50,7 @@ export default class SearchState { this.terria = options.terria; runInAction(() => { - this.terria.configParameters.searchBarModel.catalogSearchProvider = + this.terria.searchBarModel.catalogSearchProvider = options.catalogSearchProvider || new CatalogSearchProvider("catalog-search-provider", options.terria); }); @@ -100,13 +100,12 @@ export default class SearchState { @computed private get locationSearchProviders(): LocationSearchProviderMixin.Instance[] { - return this.terria.configParameters.searchBarModel - .locationSearchProvidersArray; + return this.terria.searchBarModel.locationSearchProvidersArray; } @computed get catalogSearchProvider(): CatalogSearchProviderMixin.Instance | undefined { - return this.terria.configParameters.searchBarModel.catalogSearchProvider; + return this.terria.searchBarModel.catalogSearchProvider; } @computed diff --git a/lib/ReactViewModels/ViewState.ts b/lib/ReactViewModels/ViewState.ts index 803d3206515..0c7b80b4240 100644 --- a/lib/ReactViewModels/ViewState.ts +++ b/lib/ReactViewModels/ViewState.ts @@ -39,7 +39,6 @@ import { import DisclaimerHandler from "./DisclaimerHandler"; import SearchState from "./SearchState"; import CatalogSearchProviderMixin from "../ModelMixins/SearchProviders/CatalogSearchProviderMixin"; -import LocationSearchProviderMixin from "../ModelMixins/SearchProviders/LocationSearchProviderMixin"; export const DATA_CATALOG_NAME = "data-catalog"; export const USER_DATA_NAME = "my-data"; diff --git a/lib/ReactViews/Mobile/MobileHeader.jsx b/lib/ReactViews/Mobile/MobileHeader.jsx index 41a04e3858b..9600c11404c 100644 --- a/lib/ReactViews/Mobile/MobileHeader.jsx +++ b/lib/ReactViews/Mobile/MobileHeader.jsx @@ -138,7 +138,7 @@ class MobileHeader extends React.Component { onSearchTextChanged={this.changeLocationSearchText.bind(this)} onDoSearch={this.searchLocations.bind(this)} placeholder={applyTranslationIfExists( - viewState.terria.configParameters.searchBarModel.placeholder, + viewState.terria.searchBarModel.placeholder, this.props.i18n )} alwaysShowClear={true} diff --git a/lib/ReactViews/Search/LocationSearchResults.tsx b/lib/ReactViews/Search/LocationSearchResults.tsx index 475715f46aa..191e7db1b33 100644 --- a/lib/ReactViews/Search/LocationSearchResults.tsx +++ b/lib/ReactViews/Search/LocationSearchResults.tsx @@ -67,8 +67,7 @@ class LocationSearchResults extends React.Component { @computed get validResults() { const { search, terria } = this.props; - const locationSearchBoundingBox = - terria.configParameters.searchBarModel.boundingBoxLimit; + const locationSearchBoundingBox = terria.searchBarModel.boundingBoxLimit; let filterResults = false; let west: number | undefined, east: number | undefined, diff --git a/lib/ReactViews/SidePanel/SidePanel.tsx b/lib/ReactViews/SidePanel/SidePanel.tsx index 6f6c584cff4..d020e0bdb01 100644 --- a/lib/ReactViews/SidePanel/SidePanel.tsx +++ b/lib/ReactViews/SidePanel/SidePanel.tsx @@ -164,7 +164,7 @@ const SidePanel = observer>( viewState={viewState} terria={terria} placeholder={applyTranslationIfExists( - terria.configParameters.searchBarModel.placeholder, + terria.searchBarModel.placeholder, i18n )} /> diff --git a/lib/Traits/ModelTraitsInterface.ts b/lib/Traits/ModelTraitsInterface.ts deleted file mode 100644 index 3f71a80c042..00000000000 --- a/lib/Traits/ModelTraitsInterface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import ModelTraits from "./ModelTraits"; - -export type ModelTraitsInterface = { - [Member in keyof ClassType]: Member extends ModelTraits - ? ModelTraitsInterface> - : ClassType[Member]; -}; diff --git a/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx b/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx index 0071d4dde8a..20f7a276921 100644 --- a/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx +++ b/test/ReactViews/Search/SearchBoxAndResultsSpec.tsx @@ -85,8 +85,7 @@ describe("SearchBoxAndResults", function () { viewState.searchState.locationSearchText = searchText; viewState.searchState.showLocationSearchResults = true; viewState.searchState.locationSearchResults = []; - viewState.terria.configParameters.searchBarModel.catalogSearchProvider = - undefined; + viewState.terria.searchBarModel.catalogSearchProvider = undefined; }); act(() => { testRenderer = create( From 8d1ad637958618d99bbec04c16af857796dee707 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Sun, 19 Nov 2023 17:35:01 +0100 Subject: [PATCH 59/62] fix: replace terriajs-plugin-api imports with proper ones --- lib/Models/SearchProviders/CesiumIonSearchProvider.ts | 6 +++--- lib/Traits/SearchProviders/CesiumIonSearchProviderTraits.ts | 3 ++- test/ModelMixins/SearchProviders/SearchProviderMixinSpec.ts | 3 +-- test/Models/SearchProviders/BingMapsSearchProviderSpec.ts | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/Models/SearchProviders/CesiumIonSearchProvider.ts b/lib/Models/SearchProviders/CesiumIonSearchProvider.ts index e414b0a222b..37c79972ce6 100644 --- a/lib/Models/SearchProviders/CesiumIonSearchProvider.ts +++ b/lib/Models/SearchProviders/CesiumIonSearchProvider.ts @@ -1,8 +1,7 @@ import i18next from "i18next"; -import { makeObservable, observable, runInAction } from "mobx"; +import { makeObservable, runInAction } from "mobx"; import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; -import defaultValue from "terriajs-cesium/Source/Core/defaultValue"; -import { CommonStrata } from "terriajs-plugin-api"; + import { Category, SearchAction @@ -15,6 +14,7 @@ import CreateModel from "../Definition/CreateModel"; import Terria from "../Terria"; import SearchProviderResults from "./SearchProviderResults"; import SearchResult from "./SearchResult"; +import CommonStrata from "../Definition/CommonStrata"; interface CesiumIonSearchProviderOptions { terria: Terria; diff --git a/lib/Traits/SearchProviders/CesiumIonSearchProviderTraits.ts b/lib/Traits/SearchProviders/CesiumIonSearchProviderTraits.ts index 7c7e7e08c05..5f7da950585 100644 --- a/lib/Traits/SearchProviders/CesiumIonSearchProviderTraits.ts +++ b/lib/Traits/SearchProviders/CesiumIonSearchProviderTraits.ts @@ -1,4 +1,5 @@ -import { mixTraits, primitiveTrait } from "terriajs-plugin-api"; +import primitiveTrait from "../Decorators/primitiveTrait"; +import mixTraits from "../mixTraits"; import LocationSearchProviderTraits, { SearchProviderMapCenterTraits } from "./LocationSearchProviderTraits"; diff --git a/test/ModelMixins/SearchProviders/SearchProviderMixinSpec.ts b/test/ModelMixins/SearchProviders/SearchProviderMixinSpec.ts index 54c07c34b38..44227b87351 100644 --- a/test/ModelMixins/SearchProviders/SearchProviderMixinSpec.ts +++ b/test/ModelMixins/SearchProviders/SearchProviderMixinSpec.ts @@ -1,7 +1,6 @@ -import { CommonStrata } from "terriajs-plugin-api"; import SearchProviderMixin from "../../../lib/ModelMixins/SearchProviders/SearchProviderMixin"; +import CommonStrata from "../../../lib/Models/Definition/CommonStrata"; import CreateModel from "../../../lib/Models/Definition/CreateModel"; -import SearchProviderResults from "../../../lib/Models/SearchProviders/SearchProviderResults"; import Terria from "../../../lib/Models/Terria"; import BingMapsSearchProviderTraits from "../../../lib/Traits/SearchProviders/BingMapsSearchProviderTraits"; diff --git a/test/Models/SearchProviders/BingMapsSearchProviderSpec.ts b/test/Models/SearchProviders/BingMapsSearchProviderSpec.ts index 4035b4bc26b..557fe5b848e 100644 --- a/test/Models/SearchProviders/BingMapsSearchProviderSpec.ts +++ b/test/Models/SearchProviders/BingMapsSearchProviderSpec.ts @@ -1,11 +1,11 @@ +import { runInAction } from "mobx"; import Resource from "terriajs-cesium/Source/Core/Resource"; import LocationSearchProviderMixin from "../../../lib/ModelMixins/SearchProviders/LocationSearchProviderMixin"; import BingMapsSearchProvider from "../../../lib/Models/SearchProviders/BingMapsSearchProvider"; import Terria from "../../../lib/Models/Terria"; -import { exp } from "protomaps"; -import { runInAction } from "mobx"; -import { CommonStrata } from "terriajs-plugin-api"; + import * as loadJsonp from "../../../lib/Core/loadJsonp"; +import CommonStrata from "../../../lib/Models/Definition/CommonStrata"; describe("BingMapsSearchProvider", function () { let terria: Terria; From 04785d4f1e4fd9c3c39decb6463a3f541b4902bf Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Mon, 20 Nov 2023 14:44:52 +0100 Subject: [PATCH 60/62] fix: rename traits class --- lib/Traits/SearchProviders/CesiumIonSearchProviderTraits.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Traits/SearchProviders/CesiumIonSearchProviderTraits.ts b/lib/Traits/SearchProviders/CesiumIonSearchProviderTraits.ts index 5f7da950585..350a3f9ec04 100644 --- a/lib/Traits/SearchProviders/CesiumIonSearchProviderTraits.ts +++ b/lib/Traits/SearchProviders/CesiumIonSearchProviderTraits.ts @@ -4,7 +4,7 @@ import LocationSearchProviderTraits, { SearchProviderMapCenterTraits } from "./LocationSearchProviderTraits"; -export default class BingMapsSearchProviderTraits extends mixTraits( +export default class CesiumIonSearchProviderTraits extends mixTraits( LocationSearchProviderTraits, SearchProviderMapCenterTraits ) { From d3fe2f4eb667a0a15db61d84bb528f460ddecff8 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Mon, 20 Nov 2023 14:45:29 +0100 Subject: [PATCH 61/62] doc: add cesium search provider to docs --- doc/customizing/search-providers.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/doc/customizing/search-providers.md b/doc/customizing/search-providers.md index 1f2d49a97bd..3077a7e3fb5 100644 --- a/doc/customizing/search-providers.md +++ b/doc/customizing/search-providers.md @@ -53,6 +53,7 @@ For more details see [/buildprocess/generateCatalogIndex.ts](/buildprocess/gener Location search providers are used to search for locations on the map. TerriaJS currently supports two implementations of search providers: - [`BingMapsSearchProvider`](#bingmapssearchprovider) - implementation which in background uses Bing Map search API +- [`CesiumIonSearchProvider`](#cesiumionsearchprovider) - implementation which in background use CesiumIon geocoding API - [`AustralianGazetteerSearchProvider`](#australiangazetteersearchprovider) - uses `WebFeatureServiceSearchProvider` Each `LocationSearchProvider support following confing options @@ -93,6 +94,32 @@ It provides a default value for `url: https://dev.virtualearth.net/` }, ``` +### CesiumIonSearchProvider + +`type: cesium-ion-search-provider` + +CesiumIon search provider is based on CesiumIon geocoding API provided by Cesium. To enable it it is necessary to add appropriate cesium API key as config parameter. + +| Name | Required | Type | Default | Description | +| ----- | -------- | ---------- | --------------------------------------- | ------------------ | +| `key` | no | **string** | `configParameters.cesiumIonAccessToken` | The CesiumIon key. | + +It provides a default value for `url: https://api.cesium.com/v1/geocode/search/` + +**Example** + +```json +{ + "id": "search-provider/cesium-ion", + "type": "cesium-ion-search-provider", + "name": "translate#viewModels.searchLocations", + "url": "https://api.cesium.com/v1/geocode/search/", + "flightDurationSeconds": 1.5, + "minCharacters": 5, + "isOpen": true +}, +``` + ### AustralianGazetteerSearchProvider `type: australian-gazetteer-search-provider` From e9853768e791ff17004c19704010ddab4b34f666 Mon Sep 17 00:00:00 2001 From: Zoran Kokeza Date: Mon, 20 Nov 2023 14:48:05 +0100 Subject: [PATCH 62/62] fix: show warning --- .../LocationSearchProviderMixin.ts | 3 +++ .../SearchProviders/BingMapsSearchProvider.ts | 8 ++++---- .../SearchProviders/CesiumIonSearchProvider.ts | 15 ++++----------- lib/Models/SearchProviders/SearchBarModel.ts | 6 +++++- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/ModelMixins/SearchProviders/LocationSearchProviderMixin.ts b/lib/ModelMixins/SearchProviders/LocationSearchProviderMixin.ts index 80543f209f2..d1bd12985e7 100644 --- a/lib/ModelMixins/SearchProviders/LocationSearchProviderMixin.ts +++ b/lib/ModelMixins/SearchProviders/LocationSearchProviderMixin.ts @@ -28,6 +28,9 @@ function LocationSearchProviderMixin< toggleOpen(stratumId: CommonStrata = CommonStrata.user) { this.setTrait(stratumId, "isOpen", !this.isOpen); } + + @action + showWarning() {} } return LocationSearchProviderMixin; diff --git a/lib/Models/SearchProviders/BingMapsSearchProvider.ts b/lib/Models/SearchProviders/BingMapsSearchProvider.ts index 8e1c6d16a16..b9ebb6b0ab7 100644 --- a/lib/Models/SearchProviders/BingMapsSearchProvider.ts +++ b/lib/Models/SearchProviders/BingMapsSearchProvider.ts @@ -1,5 +1,5 @@ import i18next from "i18next"; -import { action, makeObservable, runInAction } from "mobx"; +import { makeObservable, override, runInAction } from "mobx"; import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; import Resource from "terriajs-cesium/Source/Core/Resource"; import defined from "terriajs-cesium/Source/Core/defined"; @@ -34,18 +34,18 @@ export default class BingMapsSearchProvider extends LocationSearchProviderMixin( makeObservable(this); runInAction(() => { - if (!this.key && this.terria.configParameters.bingMapsKey) { + if (!!this.terria.configParameters.bingMapsKey) { this.setTrait( CommonStrata.defaults, "key", this.terria.configParameters.bingMapsKey ); } - this.showWarning(); }); } - showWarning() { + @override + override showWarning() { if (!this.key || this.key === "") { console.warn( `The ${applyTranslationIfExists(this.name, i18next)}(${ diff --git a/lib/Models/SearchProviders/CesiumIonSearchProvider.ts b/lib/Models/SearchProviders/CesiumIonSearchProvider.ts index 37c79972ce6..b1e39b33605 100644 --- a/lib/Models/SearchProviders/CesiumIonSearchProvider.ts +++ b/lib/Models/SearchProviders/CesiumIonSearchProvider.ts @@ -1,5 +1,5 @@ import i18next from "i18next"; -import { makeObservable, runInAction } from "mobx"; +import { makeObservable, override, runInAction } from "mobx"; import Rectangle from "terriajs-cesium/Source/Core/Rectangle"; import { @@ -16,13 +16,6 @@ import SearchProviderResults from "./SearchProviderResults"; import SearchResult from "./SearchResult"; import CommonStrata from "../Definition/CommonStrata"; -interface CesiumIonSearchProviderOptions { - terria: Terria; - url?: string; - key: string; - flightDurationSeconds?: number; -} - interface CesiumIonGeocodeResultFeature { bbox: [number, number, number, number]; properties: { label: string }; @@ -47,18 +40,18 @@ export default class CesiumIonSearchProvider extends LocationSearchProviderMixin makeObservable(this); runInAction(() => { - if (!this.key && this.terria.configParameters.cesiumIonAccessToken) { + if (!!this.terria.configParameters.cesiumIonAccessToken) { this.setTrait( CommonStrata.defaults, "key", this.terria.configParameters.cesiumIonAccessToken ); } - this.showWarning(); }); } - showWarning() { + @override + override showWarning() { if (!this.key || this.key === "") { console.warn( `The ${applyTranslationIfExists(this.name, i18next)}(${ diff --git a/lib/Models/SearchProviders/SearchBarModel.ts b/lib/Models/SearchProviders/SearchBarModel.ts index 2959d38f2b7..3dc1a21a0a9 100644 --- a/lib/Models/SearchProviders/SearchBarModel.ts +++ b/lib/Models/SearchProviders/SearchBarModel.ts @@ -61,12 +61,16 @@ export class SearchBarModel extends CreateModel(SearchBarTraits) { ); } searchProviders?.forEach((searchProvider) => { - upsertSearchProviderFromJson( + const loadedModel = upsertSearchProviderFromJson( SearchProviderFactory, this.terria, CommonStrata.definition, searchProvider ).pushErrorTo(errors); + + if (LocationSearchProviderMixin.isMixedInto(loadedModel)) { + loadedModel.showWarning(); + } }); return new Result(