forked from MapComplete/MapComplete
UX: indicate incomplete search in offline mode
This commit is contained in:
parent
76dbf50db1
commit
b6366412ea
13 changed files with 69 additions and 5 deletions
|
|
@ -6,6 +6,8 @@ export default class CombinedSearcher implements GeocodingProvider {
|
||||||
public readonly name = "CombinedSearcher"
|
public readonly name = "CombinedSearcher"
|
||||||
private _providers: ReadonlyArray<GeocodingProvider>
|
private _providers: ReadonlyArray<GeocodingProvider>
|
||||||
private _providersWithSuggest: ReadonlyArray<GeocodingProvider>
|
private _providersWithSuggest: ReadonlyArray<GeocodingProvider>
|
||||||
|
public readonly needsInternet
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merges the various providers together; ignores errors.
|
* Merges the various providers together; ignores errors.
|
||||||
|
|
@ -15,6 +17,7 @@ export default class CombinedSearcher implements GeocodingProvider {
|
||||||
constructor(...providers: ReadonlyArray<GeocodingProvider>) {
|
constructor(...providers: ReadonlyArray<GeocodingProvider>) {
|
||||||
this._providers = Utils.NoNull(providers)
|
this._providers = Utils.NoNull(providers)
|
||||||
this._providersWithSuggest = this._providers.filter((pr) => pr.suggest !== undefined)
|
this._providersWithSuggest = this._providers.filter((pr) => pr.suggest !== undefined)
|
||||||
|
this.needsInternet = this._providers.some(p => p.needsInternet)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import CoordinateParser from "coordinate-parser"
|
||||||
*/
|
*/
|
||||||
export default class CoordinateSearch implements GeocodingProvider {
|
export default class CoordinateSearch implements GeocodingProvider {
|
||||||
public readonly name = "CoordinateSearch"
|
public readonly name = "CoordinateSearch"
|
||||||
|
public readonly needsInternet = false
|
||||||
|
|
||||||
private static readonly latLonRegexes: ReadonlyArray<RegExp> = [
|
private static readonly latLonRegexes: ReadonlyArray<RegExp> = [
|
||||||
/^ *(-?[0-9]+\.[0-9]+)[ ,;/\\]+(-?[0-9]+\.[0-9]+)/,
|
/^ *(-?[0-9]+\.[0-9]+)[ ,;/\\]+(-?[0-9]+\.[0-9]+)/,
|
||||||
/^ *(-?[0-9]+,[0-9]+)[ ;/\\]+(-?[0-9]+,[0-9]+)/,
|
/^ *(-?[0-9]+,[0-9]+)[ ;/\\]+(-?[0-9]+,[0-9]+)/,
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ export interface GeocodingOptions {
|
||||||
|
|
||||||
export default interface GeocodingProvider {
|
export default interface GeocodingProvider {
|
||||||
readonly name: string
|
readonly name: string
|
||||||
|
readonly needsInternet: boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs search.
|
* Performs search.
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ export default class LocalElementSearch implements GeocodingProvider {
|
||||||
private readonly _state: ThemeViewState
|
private readonly _state: ThemeViewState
|
||||||
private readonly _limit: number
|
private readonly _limit: number
|
||||||
public readonly name = "LocalElementSearch"
|
public readonly name = "LocalElementSearch"
|
||||||
|
public readonly needsInternet = false
|
||||||
|
|
||||||
constructor(state: ThemeViewState, limit: number) {
|
constructor(state: ThemeViewState, limit: number) {
|
||||||
this._state = state
|
this._state = state
|
||||||
this._limit = limit
|
this._limit = limit
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ export class NominatimGeocoding implements GeocodingProvider {
|
||||||
private readonly _host
|
private readonly _host
|
||||||
private readonly limit: number
|
private readonly limit: number
|
||||||
public readonly name = "Nominatim"
|
public readonly name = "Nominatim"
|
||||||
|
public readonly needsInternet = true
|
||||||
|
|
||||||
constructor(limit: number = 3, host: string = Constants.nominatimEndpoint) {
|
constructor(limit: number = 3, host: string = Constants.nominatimEndpoint) {
|
||||||
this.limit = limit
|
this.limit = limit
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import GeocodingProvider, { GeocodeResult, GeocodingOptions } from "./GeocodingP
|
||||||
import { decode as pluscode_decode } from "pluscodes"
|
import { decode as pluscode_decode } from "pluscodes"
|
||||||
|
|
||||||
export default class OpenLocationCodeSearch implements GeocodingProvider {
|
export default class OpenLocationCodeSearch implements GeocodingProvider {
|
||||||
|
public readonly needsInternet = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A regex describing all plus-codes
|
* A regex describing all plus-codes
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import OsmObjectDownloader from "../Osm/OsmObjectDownloader"
|
||||||
export default class OpenStreetMapIdSearch implements GeocodingProvider {
|
export default class OpenStreetMapIdSearch implements GeocodingProvider {
|
||||||
private static readonly regex =
|
private static readonly regex =
|
||||||
/((https?:\/\/)?(www.)?(osm|openstreetmap).org\/)?(n|node|w|way|r|relation)[/ ]?([0-9]+)/
|
/((https?:\/\/)?(www.)?(osm|openstreetmap).org\/)?(n|node|w|way|r|relation)[/ ]?([0-9]+)/
|
||||||
|
public readonly needsInternet = true
|
||||||
public readonly name = "OpenStreetMapId"
|
public readonly name = "OpenStreetMapId"
|
||||||
private static readonly types: Readonly<Record<string, "node" | "way" | "relation">> = {
|
private static readonly types: Readonly<Record<string, "node" | "way" | "relation">> = {
|
||||||
n: "node",
|
n: "node",
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import GeocodingProvider, {
|
||||||
GeocodingOptions,
|
GeocodingOptions,
|
||||||
GeocodingUtils,
|
GeocodingUtils,
|
||||||
ReverseGeocodingProvider,
|
ReverseGeocodingProvider,
|
||||||
ReverseGeocodingResult,
|
ReverseGeocodingResult
|
||||||
} from "./GeocodingProvider"
|
} from "./GeocodingProvider"
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils"
|
||||||
import { Feature, FeatureCollection } from "geojson"
|
import { Feature, FeatureCollection } from "geojson"
|
||||||
|
|
@ -15,6 +15,7 @@ import { Store, Stores } from "../UIEventSource"
|
||||||
|
|
||||||
export default class PhotonSearch implements GeocodingProvider, ReverseGeocodingProvider {
|
export default class PhotonSearch implements GeocodingProvider, ReverseGeocodingProvider {
|
||||||
private readonly _endpoint: string
|
private readonly _endpoint: string
|
||||||
|
public readonly needsInternet = true
|
||||||
public readonly name = "photon"
|
public readonly name = "photon"
|
||||||
private supportedLanguages = ["en", "de", "fr"]
|
private supportedLanguages = ["en", "de", "fr"]
|
||||||
private static readonly types = {
|
private static readonly types = {
|
||||||
|
|
@ -23,17 +24,14 @@ export default class PhotonSearch implements GeocodingProvider, ReverseGeocoding
|
||||||
N: "node",
|
N: "node",
|
||||||
}
|
}
|
||||||
private readonly ignoreBounds: boolean
|
private readonly ignoreBounds: boolean
|
||||||
private readonly suggestionLimit: number = 5
|
|
||||||
private readonly searchLimit: number = 1
|
private readonly searchLimit: number = 1
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
ignoreBounds: boolean = false,
|
ignoreBounds: boolean = false,
|
||||||
suggestionLimit: number = 5,
|
|
||||||
searchLimit: number = 1,
|
searchLimit: number = 1,
|
||||||
endpoint?: string
|
endpoint?: string
|
||||||
) {
|
) {
|
||||||
this.ignoreBounds = ignoreBounds
|
this.ignoreBounds = ignoreBounds
|
||||||
this.suggestionLimit = suggestionLimit
|
|
||||||
this.searchLimit = searchLimit
|
this.searchLimit = searchLimit
|
||||||
this._endpoint = endpoint ?? Constants.photonEndpoint ?? "https://photon.komoot.io/"
|
this._endpoint = endpoint ?? Constants.photonEndpoint ?? "https://photon.komoot.io/"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { BBox } from "../BBox"
|
||||||
import { QueryParameters } from "../Web/QueryParameters"
|
import { QueryParameters } from "../Web/QueryParameters"
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils"
|
||||||
import { NominatimGeocoding } from "../Search/NominatimGeocoding"
|
import { NominatimGeocoding } from "../Search/NominatimGeocoding"
|
||||||
|
import { IsOnline } from "../Web/IsOnline"
|
||||||
|
|
||||||
export default class SearchState {
|
export default class SearchState {
|
||||||
public readonly feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
|
public readonly feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
|
||||||
|
|
@ -62,7 +63,9 @@ export default class SearchState {
|
||||||
if (search.length === 0) {
|
if (search.length === 0) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
return this.locationSearchers.map((ls) => ({
|
return this.locationSearchers
|
||||||
|
.filter(ls => !ls.needsInternet || IsOnline.isOnline.data)
|
||||||
|
.map((ls) => ({
|
||||||
source: ls,
|
source: ls,
|
||||||
results: ls.suggest(search, { bbox: bounds.data }),
|
results: ls.suggest(search, { bbox: bounds.data }),
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
15
src/UI/Base/CrossedOut.svelte
Normal file
15
src/UI/Base/CrossedOut.svelte
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<!-- Shows a red cross at the bottom of the icon-->
|
||||||
|
<script>
|
||||||
|
import Cross_bottom_right from "../../assets/svg/Cross_bottom_right.svelte"
|
||||||
|
|
||||||
|
export let size = "w-12 h-12"
|
||||||
|
</script>
|
||||||
|
<div class={size+" relative"}>
|
||||||
|
<div class="absolute top-0 left-0">
|
||||||
|
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
<div class="absolute top-0 left-0">
|
||||||
|
<Cross_bottom_right class={size} color="red" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
22
src/UI/BigComponents/ServiceWorkerStatus.svelte
Normal file
22
src/UI/BigComponents/ServiceWorkerStatus.svelte
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
import { Utils } from "../../Utils"
|
||||||
|
import Loading from "../Base/Loading.svelte"
|
||||||
|
|
||||||
|
let loadedAssets = new UIEventSource<any>(undefined)
|
||||||
|
|
||||||
|
async function update() {
|
||||||
|
loadedAssets.set(await Utils.downloadJson("./service-worker/status.json"))
|
||||||
|
}
|
||||||
|
|
||||||
|
update()
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $loadedAssets === undefined}
|
||||||
|
<Loading />
|
||||||
|
{:else}
|
||||||
|
<button on:click={() => update()}>Update</button>
|
||||||
|
{JSON.stringify($loadedAssets)}
|
||||||
|
{/if}
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider"
|
import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider"
|
||||||
import type { MapProperties } from "../../Models/MapProperties"
|
import type { MapProperties } from "../../Models/MapProperties"
|
||||||
import { ExclamationTriangle } from "@babeard/svelte-heroicons/solid/ExclamationTriangle"
|
import { ExclamationTriangle } from "@babeard/svelte-heroicons/solid/ExclamationTriangle"
|
||||||
|
import { IsOnline } from "../../Logic/Web/IsOnline"
|
||||||
|
|
||||||
export let state: {
|
export let state: {
|
||||||
searchState: {
|
searchState: {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@
|
||||||
import DotMenu from "../Base/DotMenu.svelte"
|
import DotMenu from "../Base/DotMenu.svelte"
|
||||||
import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider"
|
import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider"
|
||||||
import { default as GeocodeResultSvelte } from "./GeocodeResult.svelte"
|
import { default as GeocodeResultSvelte } from "./GeocodeResult.svelte"
|
||||||
|
import { IsOnline } from "../../Logic/Web/IsOnline"
|
||||||
|
import CrossedOut from "../Base/CrossedOut.svelte"
|
||||||
|
import Wifi from "@babeard/svelte-heroicons/solid/Wifi"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The big overview of all search bar results
|
* The big overview of all search bar results
|
||||||
|
|
@ -41,6 +44,7 @@
|
||||||
|
|
||||||
let allowOtherThemes = state.featureSwitches.featureSwitchBackToThemeOverview
|
let allowOtherThemes = state.featureSwitches.featureSwitchBackToThemeOverview
|
||||||
let allowFilters = state.featureSwitches.featureSwitchFilter
|
let allowFilters = state.featureSwitches.featureSwitchFilter
|
||||||
|
let isOnline = IsOnline.isOnline
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="low-interaction flex flex-col gap-y-2 p-4">
|
<div class="low-interaction flex flex-col gap-y-2 p-4">
|
||||||
|
|
@ -55,6 +59,15 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if !$isOnline}
|
||||||
|
<div class="alert flex items-center">
|
||||||
|
<CrossedOut size="w-8 h-8">
|
||||||
|
<Wifi class="w-8 h-8" />
|
||||||
|
</CrossedOut>
|
||||||
|
Your device is currently offline. This impacts search results
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if $allowFilters}
|
{#if $allowFilters}
|
||||||
<FilterResults {state} />
|
<FilterResults {state} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue