From a55bd55b46ffb62faf7d7776dc0ec80dcc36f351 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sun, 15 Dec 2024 22:39:22 +0100 Subject: [PATCH] UX: improve display if OSM.org is down --- src/Logic/Osm/OsmConnection.ts | 112 +++++++++++------- src/UI/Base/LoginToggle.svelte | 34 +++--- src/UI/BigComponents/MenuDrawer.svelte | 4 +- src/UI/Popup/MarkAsFavouriteMini.svelte | 2 +- .../TagRendering/TagRenderingEditable.svelte | 4 +- .../TagRendering/TagRenderingQuestion.svelte | 4 +- 6 files changed, 101 insertions(+), 59 deletions(-) diff --git a/src/Logic/Osm/OsmConnection.ts b/src/Logic/Osm/OsmConnection.ts index 4ee3b73b7..ff26a3203 100644 --- a/src/Logic/Osm/OsmConnection.ts +++ b/src/Logic/Osm/OsmConnection.ts @@ -40,19 +40,49 @@ export default class UserDetails { export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable" +interface CapabilityResult { + "version": "0.6" | string, + "generator": "OpenStreetMap server" | string, + "copyright": "OpenStreetMap and contributors" | string, + "attribution": "http://www.openstreetmap.org/copyright" | string, + "license": "http://opendatacommons.org/licenses/odbl/1-0/" | string, + "api": { + "version": { "minimum": "0.6", "maximum": "0.6" }, + "area": { "maximum": 0.25 | number }, + "note_area": { "maximum": 25 | number }, + "tracepoints": { "per_page": 5000 | number }, + "waynodes": { "maximum": 2000 | number }, + "relationmembers": { "maximum": 32000 | number }, + "changesets": { "maximum_elements": 10000 | number, + "default_query_limit": 100 | number, + "maximum_query_limit": 100 |number}, + "notes": { "default_query_limit": 100 | number, "maximum_query_limit": 10000 |number}, + "timeout": { "seconds": 300 |number}, + "status": { + "database": OsmServiceState, + "api": OsmServiceState, + "gpx": OsmServiceState } + }, + "policy": { + "imagery": { + "blacklist":{regex: string}[] + } + } +} + export class OsmConnection { public auth: osmAuth public userDetails: UIEventSource public isLoggedIn: Store public gpxServiceIsOnline: UIEventSource = new UIEventSource( - "unknown" + "unknown", ) public apiIsOnline: UIEventSource = new UIEventSource( - "unknown" + "unknown", ) public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( - "not-attempted" + "not-attempted", ) public preferencesHandler: OsmPreferences public readonly _oauth_config: AuthConfig @@ -96,7 +126,7 @@ export class OsmConnection { this.userDetails = new UIEventSource( new UserDetails(this._oauth_config.url), - "userDetails" + "userDetails", ) if (options.fakeUser) { const ud = this.userDetails.data @@ -117,7 +147,7 @@ export class OsmConnection { (user) => user.loggedIn && (this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"), - [this.apiIsOnline] + [this.apiIsOnline], ) this.isLoggedIn.addCallback((isLoggedIn) => { if (this.userDetails.data.loggedIn == false && isLoggedIn == true) { @@ -160,7 +190,7 @@ export class OsmConnection { defaultValue: string = undefined, options?: { prefix?: string - } + }, ): UIEventSource { const prefix = options?.prefix ?? "mapcomplete-" return >this.preferencesHandler.getPreference(key, defaultValue, prefix) @@ -169,7 +199,7 @@ export class OsmConnection { public getPreference( key: string, defaultValue: string = undefined, - prefix: string = "mapcomplete-" + prefix: string = "mapcomplete-", ): UIEventSource { return >this.preferencesHandler.getPreference(key, defaultValue, prefix) } @@ -213,7 +243,7 @@ export class OsmConnection { this.updateAuthObject() LocalStorageSource.get("location_before_login").setData( - Utils.runningFromConsole ? undefined : window.location.href + Utils.runningFromConsole ? undefined : window.location.href, ) this.auth.xhr( { @@ -251,13 +281,13 @@ export class OsmConnection { data.account_created = userInfo.getAttribute("account_created") data.uid = Number(userInfo.getAttribute("id")) data.languages = Array.from( - userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang") + userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang"), ).map((l) => l.textContent) data.csCount = Number.parseInt( - userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0" + userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0", ) data.tracesCount = Number.parseInt( - userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0" + userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0", ) data.img = undefined @@ -289,7 +319,7 @@ export class OsmConnection { action(this.userDetails.data) } this._onLoggedIn = [] - } + }, ) } @@ -307,7 +337,7 @@ export class OsmConnection { method: "GET" | "POST" | "PUT" | "DELETE", header?: Record, content?: string, - allowAnonymous: boolean = false + allowAnonymous: boolean = false, ): Promise { const connection: osmAuth = this.auth if (allowAnonymous && !this.auth.authenticated()) { @@ -315,7 +345,7 @@ export class OsmConnection { `${this.Backend()}/api/0.6/${path}`, header, method, - content + content, ) if (possibleResult["content"]) { return possibleResult["content"] @@ -332,13 +362,13 @@ export class OsmConnection { content, path: `/api/0.6/${path}`, }, - function (err, response) { + function(err, response) { if (err !== null) { error(err) } else { ok(response) } - } + }, ) }) } @@ -347,7 +377,7 @@ export class OsmConnection { path: string, content?: string, header?: Record, - allowAnonymous: boolean = false + allowAnonymous: boolean = false, ): Promise { return await this.interact(path, "POST", header, content, allowAnonymous) } @@ -355,7 +385,7 @@ export class OsmConnection { public async put( path: string, content?: string, - header?: Record + header?: Record, ): Promise { return await this.interact(path, "PUT", header, content) } @@ -363,7 +393,7 @@ export class OsmConnection { public async get( path: string, header?: Record, - allowAnonymous: boolean = false + allowAnonymous: boolean = false, ): Promise { return await this.interact(path, "GET", header, undefined, allowAnonymous) } @@ -402,7 +432,7 @@ export class OsmConnection { return new Promise<{ id: number }>((ok) => { window.setTimeout( () => ok({ id: Math.floor(Math.random() * 1000) }), - Math.random() * 5000 + Math.random() * 5000, ) }) } @@ -414,7 +444,7 @@ export class OsmConnection { { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", }, - true + true, ) const parsed = JSON.parse(response) console.log("Got result:", parsed) @@ -437,14 +467,14 @@ export class OsmConnection { * Note: these are called 'tags' on the wiki, but I opted to name them 'labels' instead as they aren't "key=value" tags, but just words. */ labels: string[] - } + }, ): Promise<{ id: number }> { if (this._dryRun.data) { console.warn("Dryrun enabled - not actually uploading GPX ", gpx) return new Promise<{ id: number }>((ok) => { window.setTimeout( () => ok({ id: Math.floor(Math.random() * 1000) }), - Math.random() * 5000 + Math.random() * 5000, ) }) } @@ -461,9 +491,9 @@ export class OsmConnection { } const extras = { file: - '; filename="' + + "; filename=\"" + (options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) + - '"\r\nContent-Type: application/gpx+xml', + "\"\r\nContent-Type: application/gpx+xml", } const boundary = "987654" @@ -471,7 +501,7 @@ export class OsmConnection { let body = "" for (const key in contents) { body += "--" + boundary + "\r\n" - body += 'Content-Disposition: form-data; name="' + key + '"' + body += "Content-Disposition: form-data; name=\"" + key + "\"" if (extras[key] !== undefined) { body += extras[key] } @@ -505,13 +535,13 @@ export class OsmConnection { path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`, }, - function (err) { + function(err) { if (err !== null) { error(err) } else { ok() } - } + }, ) }) } @@ -520,7 +550,7 @@ export class OsmConnection { * To be called by land.html */ public finishLogin(callback: (previousURL: string) => void) { - this.auth.authenticate(function () { + this.auth.authenticate(function() { // Fully authed at this point console.log("Authentication successful!") const previousLocation = LocalStorageSource.get("location_before_login") @@ -600,20 +630,22 @@ export class OsmConnection { return parsed } - private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState }> { + private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState; database: OsmServiceState }> { if (Utils.runningFromConsole) { - return { api: "online", gpx: "online" } + return { api: "online", gpx: "online" , database: "online"} } - const result = await Utils.downloadAdvanced(this.Backend() + "/api/0.6/capabilities") - if (result["content"] === undefined) { + try{ + + const result = await Utils.downloadJson(this.Backend() + "/api/0.6/capabilities.json") + if (result?.api?.status === undefined) { console.log("Something went wrong:", result) - return { api: "unreachable", gpx: "unreachable" } + return { api: "unreachable", gpx: "unreachable" , database: "unreachable"} + } + return result.api.status + }catch (e) { + console.error("Could not fetch capabilities") + return { api: "offline", gpx: "offline" , database: "online"} + } - const xmlRaw = result["content"] - const parsed = new DOMParser().parseFromString(xmlRaw, "text/xml") - const statusEl = parsed.getElementsByTagName("status")[0] - const api = statusEl.getAttribute("api") - const gpx = statusEl.getAttribute("gpx") - return { api, gpx } } } diff --git a/src/UI/Base/LoginToggle.svelte b/src/UI/Base/LoginToggle.svelte index 83aba9ac4..c2d6119b7 100644 --- a/src/UI/Base/LoginToggle.svelte +++ b/src/UI/Base/LoginToggle.svelte @@ -5,7 +5,7 @@ import { Translation } from "../i18n/Translation" import Translations from "../i18n/Translations" import Tr from "./Tr.svelte" - import { ImmutableStore, UIEventSource } from "../../Logic/UIEventSource" + import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" import Invalid from "../../assets/svg/Invalid.svelte" import ArrowPath from "@babeard/svelte-heroicons/mini/ArrowPath" @@ -21,6 +21,10 @@ * Only show the 'successful' state, don't show loading or error messages */ export let silentFail: boolean = false + /** + * If set and the OSM-api fails, do _not_ show any error messages nor the successful state, just hide + */ + export let hiddenFail: boolean = false let loadingStatus = state?.osmConnection?.loadingStatus ?? new ImmutableStore("logged-in") let badge = state?.featureSwitches?.featureSwitchEnableLogin ?? new ImmutableStore(true) const t = Translations.t.general @@ -30,7 +34,7 @@ unknown: t.loginFailedUnreachableMode, readonly: t.loginFailedReadonlyMode, } - const apiState = + const apiState: Store = state?.osmConnection?.apiIsOnline ?? new ImmutableStore("online") @@ -39,19 +43,21 @@ - {:else if !silentFail && $loadingStatus === "error"} - -
-
- - + {:else if !silentFail && ($loadingStatus === "error" || $apiState === "readonly" || $apiState === "offline")} + {#if !hiddenFail} + +
+
+ + +
+
- -
- + + {/if} {:else if $loadingStatus === "logged-in"} {:else if !silentFail && $loadingStatus === "not-attempted"} diff --git a/src/UI/BigComponents/MenuDrawer.svelte b/src/UI/BigComponents/MenuDrawer.svelte index d208a5f01..057cdacea 100644 --- a/src/UI/BigComponents/MenuDrawer.svelte +++ b/src/UI/BigComponents/MenuDrawer.svelte @@ -124,7 +124,7 @@ - +
@@ -144,7 +144,7 @@ - + diff --git a/src/UI/Popup/MarkAsFavouriteMini.svelte b/src/UI/Popup/MarkAsFavouriteMini.svelte index 67d4f1899..3a82da306 100644 --- a/src/UI/Popup/MarkAsFavouriteMini.svelte +++ b/src/UI/Popup/MarkAsFavouriteMini.svelte @@ -24,7 +24,7 @@ } - + {#if $isFavourite}