forked from MapComplete/MapComplete
UX: improve display if OSM.org is down
This commit is contained in:
parent
d4dc5f3548
commit
a55bd55b46
6 changed files with 101 additions and 59 deletions
|
@ -40,19 +40,49 @@ export default class UserDetails {
|
||||||
|
|
||||||
export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable"
|
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 {
|
export class OsmConnection {
|
||||||
public auth: osmAuth
|
public auth: osmAuth
|
||||||
public userDetails: UIEventSource<UserDetails>
|
public userDetails: UIEventSource<UserDetails>
|
||||||
public isLoggedIn: Store<boolean>
|
public isLoggedIn: Store<boolean>
|
||||||
public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
|
public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
|
||||||
"unknown"
|
"unknown",
|
||||||
)
|
)
|
||||||
public apiIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
|
public apiIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
|
||||||
"unknown"
|
"unknown",
|
||||||
)
|
)
|
||||||
|
|
||||||
public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">(
|
public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">(
|
||||||
"not-attempted"
|
"not-attempted",
|
||||||
)
|
)
|
||||||
public preferencesHandler: OsmPreferences
|
public preferencesHandler: OsmPreferences
|
||||||
public readonly _oauth_config: AuthConfig
|
public readonly _oauth_config: AuthConfig
|
||||||
|
@ -96,7 +126,7 @@ export class OsmConnection {
|
||||||
|
|
||||||
this.userDetails = new UIEventSource<UserDetails>(
|
this.userDetails = new UIEventSource<UserDetails>(
|
||||||
new UserDetails(this._oauth_config.url),
|
new UserDetails(this._oauth_config.url),
|
||||||
"userDetails"
|
"userDetails",
|
||||||
)
|
)
|
||||||
if (options.fakeUser) {
|
if (options.fakeUser) {
|
||||||
const ud = this.userDetails.data
|
const ud = this.userDetails.data
|
||||||
|
@ -117,7 +147,7 @@ export class OsmConnection {
|
||||||
(user) =>
|
(user) =>
|
||||||
user.loggedIn &&
|
user.loggedIn &&
|
||||||
(this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"),
|
(this.apiIsOnline.data === "unknown" || this.apiIsOnline.data === "online"),
|
||||||
[this.apiIsOnline]
|
[this.apiIsOnline],
|
||||||
)
|
)
|
||||||
this.isLoggedIn.addCallback((isLoggedIn) => {
|
this.isLoggedIn.addCallback((isLoggedIn) => {
|
||||||
if (this.userDetails.data.loggedIn == false && isLoggedIn == true) {
|
if (this.userDetails.data.loggedIn == false && isLoggedIn == true) {
|
||||||
|
@ -160,7 +190,7 @@ export class OsmConnection {
|
||||||
defaultValue: string = undefined,
|
defaultValue: string = undefined,
|
||||||
options?: {
|
options?: {
|
||||||
prefix?: string
|
prefix?: string
|
||||||
}
|
},
|
||||||
): UIEventSource<T | undefined> {
|
): UIEventSource<T | undefined> {
|
||||||
const prefix = options?.prefix ?? "mapcomplete-"
|
const prefix = options?.prefix ?? "mapcomplete-"
|
||||||
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
|
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
|
||||||
|
@ -169,7 +199,7 @@ export class OsmConnection {
|
||||||
public getPreference<T extends string = string>(
|
public getPreference<T extends string = string>(
|
||||||
key: string,
|
key: string,
|
||||||
defaultValue: string = undefined,
|
defaultValue: string = undefined,
|
||||||
prefix: string = "mapcomplete-"
|
prefix: string = "mapcomplete-",
|
||||||
): UIEventSource<T | undefined> {
|
): UIEventSource<T | undefined> {
|
||||||
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
|
return <UIEventSource<T>>this.preferencesHandler.getPreference(key, defaultValue, prefix)
|
||||||
}
|
}
|
||||||
|
@ -213,7 +243,7 @@ export class OsmConnection {
|
||||||
this.updateAuthObject()
|
this.updateAuthObject()
|
||||||
|
|
||||||
LocalStorageSource.get("location_before_login").setData(
|
LocalStorageSource.get("location_before_login").setData(
|
||||||
Utils.runningFromConsole ? undefined : window.location.href
|
Utils.runningFromConsole ? undefined : window.location.href,
|
||||||
)
|
)
|
||||||
this.auth.xhr(
|
this.auth.xhr(
|
||||||
{
|
{
|
||||||
|
@ -251,13 +281,13 @@ export class OsmConnection {
|
||||||
data.account_created = userInfo.getAttribute("account_created")
|
data.account_created = userInfo.getAttribute("account_created")
|
||||||
data.uid = Number(userInfo.getAttribute("id"))
|
data.uid = Number(userInfo.getAttribute("id"))
|
||||||
data.languages = Array.from(
|
data.languages = Array.from(
|
||||||
userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang")
|
userInfo.getElementsByTagName("languages")[0].getElementsByTagName("lang"),
|
||||||
).map((l) => l.textContent)
|
).map((l) => l.textContent)
|
||||||
data.csCount = Number.parseInt(
|
data.csCount = Number.parseInt(
|
||||||
userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0"
|
userInfo.getElementsByTagName("changesets")[0].getAttribute("count") ?? "0",
|
||||||
)
|
)
|
||||||
data.tracesCount = Number.parseInt(
|
data.tracesCount = Number.parseInt(
|
||||||
userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0"
|
userInfo.getElementsByTagName("traces")[0].getAttribute("count") ?? "0",
|
||||||
)
|
)
|
||||||
|
|
||||||
data.img = undefined
|
data.img = undefined
|
||||||
|
@ -289,7 +319,7 @@ export class OsmConnection {
|
||||||
action(this.userDetails.data)
|
action(this.userDetails.data)
|
||||||
}
|
}
|
||||||
this._onLoggedIn = []
|
this._onLoggedIn = []
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,7 +337,7 @@ export class OsmConnection {
|
||||||
method: "GET" | "POST" | "PUT" | "DELETE",
|
method: "GET" | "POST" | "PUT" | "DELETE",
|
||||||
header?: Record<string, string>,
|
header?: Record<string, string>,
|
||||||
content?: string,
|
content?: string,
|
||||||
allowAnonymous: boolean = false
|
allowAnonymous: boolean = false,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const connection: osmAuth = this.auth
|
const connection: osmAuth = this.auth
|
||||||
if (allowAnonymous && !this.auth.authenticated()) {
|
if (allowAnonymous && !this.auth.authenticated()) {
|
||||||
|
@ -315,7 +345,7 @@ export class OsmConnection {
|
||||||
`${this.Backend()}/api/0.6/${path}`,
|
`${this.Backend()}/api/0.6/${path}`,
|
||||||
header,
|
header,
|
||||||
method,
|
method,
|
||||||
content
|
content,
|
||||||
)
|
)
|
||||||
if (possibleResult["content"]) {
|
if (possibleResult["content"]) {
|
||||||
return possibleResult["content"]
|
return possibleResult["content"]
|
||||||
|
@ -332,13 +362,13 @@ export class OsmConnection {
|
||||||
content,
|
content,
|
||||||
path: `/api/0.6/${path}`,
|
path: `/api/0.6/${path}`,
|
||||||
},
|
},
|
||||||
function (err, response) {
|
function(err, response) {
|
||||||
if (err !== null) {
|
if (err !== null) {
|
||||||
error(err)
|
error(err)
|
||||||
} else {
|
} else {
|
||||||
ok(response)
|
ok(response)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -347,7 +377,7 @@ export class OsmConnection {
|
||||||
path: string,
|
path: string,
|
||||||
content?: string,
|
content?: string,
|
||||||
header?: Record<string, string>,
|
header?: Record<string, string>,
|
||||||
allowAnonymous: boolean = false
|
allowAnonymous: boolean = false,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
return <T>await this.interact(path, "POST", header, content, allowAnonymous)
|
return <T>await this.interact(path, "POST", header, content, allowAnonymous)
|
||||||
}
|
}
|
||||||
|
@ -355,7 +385,7 @@ export class OsmConnection {
|
||||||
public async put<T extends string>(
|
public async put<T extends string>(
|
||||||
path: string,
|
path: string,
|
||||||
content?: string,
|
content?: string,
|
||||||
header?: Record<string, string>
|
header?: Record<string, string>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
return <T>await this.interact(path, "PUT", header, content)
|
return <T>await this.interact(path, "PUT", header, content)
|
||||||
}
|
}
|
||||||
|
@ -363,7 +393,7 @@ export class OsmConnection {
|
||||||
public async get(
|
public async get(
|
||||||
path: string,
|
path: string,
|
||||||
header?: Record<string, string>,
|
header?: Record<string, string>,
|
||||||
allowAnonymous: boolean = false
|
allowAnonymous: boolean = false,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return await this.interact(path, "GET", header, undefined, allowAnonymous)
|
return await this.interact(path, "GET", header, undefined, allowAnonymous)
|
||||||
}
|
}
|
||||||
|
@ -402,7 +432,7 @@ export class OsmConnection {
|
||||||
return new Promise<{ id: number }>((ok) => {
|
return new Promise<{ id: number }>((ok) => {
|
||||||
window.setTimeout(
|
window.setTimeout(
|
||||||
() => ok({ id: Math.floor(Math.random() * 1000) }),
|
() => 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",
|
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
||||||
},
|
},
|
||||||
true
|
true,
|
||||||
)
|
)
|
||||||
const parsed = JSON.parse(response)
|
const parsed = JSON.parse(response)
|
||||||
console.log("Got result:", parsed)
|
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.
|
* 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[]
|
labels: string[]
|
||||||
}
|
},
|
||||||
): Promise<{ id: number }> {
|
): Promise<{ id: number }> {
|
||||||
if (this._dryRun.data) {
|
if (this._dryRun.data) {
|
||||||
console.warn("Dryrun enabled - not actually uploading GPX ", gpx)
|
console.warn("Dryrun enabled - not actually uploading GPX ", gpx)
|
||||||
return new Promise<{ id: number }>((ok) => {
|
return new Promise<{ id: number }>((ok) => {
|
||||||
window.setTimeout(
|
window.setTimeout(
|
||||||
() => ok({ id: Math.floor(Math.random() * 1000) }),
|
() => ok({ id: Math.floor(Math.random() * 1000) }),
|
||||||
Math.random() * 5000
|
Math.random() * 5000,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -461,9 +491,9 @@ export class OsmConnection {
|
||||||
}
|
}
|
||||||
const extras = {
|
const extras = {
|
||||||
file:
|
file:
|
||||||
'; filename="' +
|
"; filename=\"" +
|
||||||
(options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) +
|
(options.filename ?? "gpx_track_mapcomplete_" + new Date().toISOString()) +
|
||||||
'"\r\nContent-Type: application/gpx+xml',
|
"\"\r\nContent-Type: application/gpx+xml",
|
||||||
}
|
}
|
||||||
|
|
||||||
const boundary = "987654"
|
const boundary = "987654"
|
||||||
|
@ -471,7 +501,7 @@ export class OsmConnection {
|
||||||
let body = ""
|
let body = ""
|
||||||
for (const key in contents) {
|
for (const key in contents) {
|
||||||
body += "--" + boundary + "\r\n"
|
body += "--" + boundary + "\r\n"
|
||||||
body += 'Content-Disposition: form-data; name="' + key + '"'
|
body += "Content-Disposition: form-data; name=\"" + key + "\""
|
||||||
if (extras[key] !== undefined) {
|
if (extras[key] !== undefined) {
|
||||||
body += extras[key]
|
body += extras[key]
|
||||||
}
|
}
|
||||||
|
@ -505,13 +535,13 @@ export class OsmConnection {
|
||||||
|
|
||||||
path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`,
|
path: `/api/0.6/notes/${id}/comment?text=${encodeURIComponent(text)}`,
|
||||||
},
|
},
|
||||||
function (err) {
|
function(err) {
|
||||||
if (err !== null) {
|
if (err !== null) {
|
||||||
error(err)
|
error(err)
|
||||||
} else {
|
} else {
|
||||||
ok()
|
ok()
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -520,7 +550,7 @@ export class OsmConnection {
|
||||||
* To be called by land.html
|
* To be called by land.html
|
||||||
*/
|
*/
|
||||||
public finishLogin(callback: (previousURL: string) => void) {
|
public finishLogin(callback: (previousURL: string) => void) {
|
||||||
this.auth.authenticate(function () {
|
this.auth.authenticate(function() {
|
||||||
// Fully authed at this point
|
// Fully authed at this point
|
||||||
console.log("Authentication successful!")
|
console.log("Authentication successful!")
|
||||||
const previousLocation = LocalStorageSource.get("location_before_login")
|
const previousLocation = LocalStorageSource.get("location_before_login")
|
||||||
|
@ -600,20 +630,22 @@ export class OsmConnection {
|
||||||
return parsed
|
return parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState }> {
|
private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState; database: OsmServiceState }> {
|
||||||
if (Utils.runningFromConsole) {
|
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")
|
try{
|
||||||
if (result["content"] === undefined) {
|
|
||||||
|
const result = await Utils.downloadJson<CapabilityResult>(this.Backend() + "/api/0.6/capabilities.json")
|
||||||
|
if (result?.api?.status === undefined) {
|
||||||
console.log("Something went wrong:", result)
|
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 = <OsmServiceState>statusEl.getAttribute("api")
|
|
||||||
const gpx = <OsmServiceState>statusEl.getAttribute("gpx")
|
|
||||||
return { api, gpx }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
import { Translation } from "../i18n/Translation"
|
import { Translation } from "../i18n/Translation"
|
||||||
import Translations from "../i18n/Translations"
|
import Translations from "../i18n/Translations"
|
||||||
import Tr from "./Tr.svelte"
|
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 Invalid from "../../assets/svg/Invalid.svelte"
|
||||||
import ArrowPath from "@babeard/svelte-heroicons/mini/ArrowPath"
|
import ArrowPath from "@babeard/svelte-heroicons/mini/ArrowPath"
|
||||||
|
|
||||||
|
@ -21,6 +21,10 @@
|
||||||
* Only show the 'successful' state, don't show loading or error messages
|
* Only show the 'successful' state, don't show loading or error messages
|
||||||
*/
|
*/
|
||||||
export let silentFail: boolean = false
|
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 loadingStatus = state?.osmConnection?.loadingStatus ?? new ImmutableStore("logged-in")
|
||||||
let badge = state?.featureSwitches?.featureSwitchEnableLogin ?? new ImmutableStore(true)
|
let badge = state?.featureSwitches?.featureSwitchEnableLogin ?? new ImmutableStore(true)
|
||||||
const t = Translations.t.general
|
const t = Translations.t.general
|
||||||
|
@ -30,7 +34,7 @@
|
||||||
unknown: t.loginFailedUnreachableMode,
|
unknown: t.loginFailedUnreachableMode,
|
||||||
readonly: t.loginFailedReadonlyMode,
|
readonly: t.loginFailedReadonlyMode,
|
||||||
}
|
}
|
||||||
const apiState =
|
const apiState: Store<string> =
|
||||||
state?.osmConnection?.apiIsOnline ?? new ImmutableStore<OsmServiceState>("online")
|
state?.osmConnection?.apiIsOnline ?? new ImmutableStore<OsmServiceState>("online")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -39,19 +43,21 @@
|
||||||
<slot name="loading">
|
<slot name="loading">
|
||||||
<Loading />
|
<Loading />
|
||||||
</slot>
|
</slot>
|
||||||
{:else if !silentFail && $loadingStatus === "error"}
|
{:else if !silentFail && ($loadingStatus === "error" || $apiState === "readonly" || $apiState === "offline")}
|
||||||
<slot name="error">
|
{#if !hiddenFail}
|
||||||
<div class="alert flex flex-col items-center">
|
<slot name="error">
|
||||||
<div class="max-w-64 flex items-center">
|
<div class="alert flex flex-col items-center">
|
||||||
<Invalid class="m-2 h-8 w-8 shrink-0" />
|
<div class="max-w-64 flex items-center">
|
||||||
<Tr t={offlineModes[$apiState] ?? t.loginFailedUnreachableMode} />
|
<Invalid class="m-2 h-8 w-8 shrink-0" />
|
||||||
|
<Tr t={offlineModes[$apiState] ?? t.loginFailedUnreachableMode} />
|
||||||
|
</div>
|
||||||
|
<button class="h-fit" on:click={() => state.osmConnection.AttemptLogin()}>
|
||||||
|
<ArrowPath class="h-6 w-6" />
|
||||||
|
<Tr t={t.retry} />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button class="h-fit" on:click={() => state.osmConnection.AttemptLogin()}>
|
</slot>
|
||||||
<ArrowPath class="h-6 w-6" />
|
{/if}
|
||||||
<Tr t={t.retry} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</slot>
|
|
||||||
{:else if $loadingStatus === "logged-in"}
|
{:else if $loadingStatus === "logged-in"}
|
||||||
<slot />
|
<slot />
|
||||||
{:else if !silentFail && $loadingStatus === "not-attempted"}
|
{:else if !silentFail && $loadingStatus === "not-attempted"}
|
||||||
|
|
|
@ -124,7 +124,7 @@
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
||||||
<!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it -->
|
<!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it -->
|
||||||
<LoginToggle {state}>
|
<LoginToggle {state} silentFail>
|
||||||
<div class="flex flex-col" slot="not-logged-in">
|
<div class="flex flex-col" slot="not-logged-in">
|
||||||
<LanguagePicker availableLanguages={theme.language} />
|
<LanguagePicker availableLanguages={theme.language} />
|
||||||
<Tr cls="alert" t={Translations.t.userinfo.notLoggedIn} />
|
<Tr cls="alert" t={Translations.t.userinfo.notLoggedIn} />
|
||||||
|
@ -144,7 +144,7 @@
|
||||||
</LoginToggle>
|
</LoginToggle>
|
||||||
</Page>
|
</Page>
|
||||||
|
|
||||||
<LoginToggle {state}>
|
<LoginToggle {state} silentFail>
|
||||||
<Page {onlyLink} shown={pg.favourites}>
|
<Page {onlyLink} shown={pg.favourites}>
|
||||||
<svelte:fragment slot="header">
|
<svelte:fragment slot="header">
|
||||||
<HeartIcon />
|
<HeartIcon />
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LoginToggle ignoreLoading={true} {state}>
|
<LoginToggle ignoreLoading={true} hiddenFail {state}>
|
||||||
{#if $isFavourite}
|
{#if $isFavourite}
|
||||||
<button
|
<button
|
||||||
class="soft no-image-background m-0 h-8 w-8 p-0"
|
class="soft no-image-background m-0 h-8 w-8 p-0"
|
||||||
|
|
|
@ -85,6 +85,8 @@
|
||||||
}
|
}
|
||||||
let answerId = "answer-" + Utils.randomString(5)
|
let answerId = "answer-" + Utils.randomString(5)
|
||||||
let debug = state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false)
|
let debug = state?.featureSwitches?.featureSwitchIsDebugging ?? new ImmutableStore(false)
|
||||||
|
|
||||||
|
let apiState: Store<string> = state?.osmConnection?.apiIsOnline ?? new ImmutableStore("online")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div bind:this={htmlElem} class={twMerge(clss, "tr-" + config.id)}>
|
<div bind:this={htmlElem} class={twMerge(clss, "tr-" + config.id)}>
|
||||||
|
@ -126,7 +128,7 @@
|
||||||
{layer}
|
{layer}
|
||||||
extraClasses="my-2"
|
extraClasses="my-2"
|
||||||
/>
|
/>
|
||||||
{#if !editingEnabled || $editingEnabled}
|
{#if (!editingEnabled || $editingEnabled) && $apiState !== "readonly" && $apiState !== "offline"}
|
||||||
<EditButton
|
<EditButton
|
||||||
arialabel={config.editButtonAriaLabel}
|
arialabel={config.editButtonAriaLabel}
|
||||||
ariaLabelledBy={answerId}
|
ariaLabelledBy={answerId}
|
||||||
|
|
|
@ -355,9 +355,11 @@
|
||||||
disabledInTheme.set(newList)
|
disabledInTheme.set(newList)
|
||||||
menuIsOpened.set(false)
|
menuIsOpened.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let apiState = state.osmConnection.apiIsOnline
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if question !== undefined}
|
{#if question !== undefined && $apiState !== "readonly" && $apiState !== "offline"}
|
||||||
<div class={clss}>
|
<div class={clss}>
|
||||||
{#if layer.isNormal()}
|
{#if layer.isNormal()}
|
||||||
<LoginToggle {state}>
|
<LoginToggle {state}>
|
||||||
|
|
Loading…
Reference in a new issue