Feature(offline): more offline hardening

This commit is contained in:
Pieter Vander Vennet 2025-08-08 13:19:49 +02:00
parent f1da97285f
commit 561e4cb009
7 changed files with 103 additions and 69 deletions

View file

@ -19,6 +19,7 @@ import LayerConfig from "./LayerConfig"
import ComparingTag from "../../Logic/Tags/ComparingTag" import ComparingTag from "../../Logic/Tags/ComparingTag"
import { Unit } from "../Unit" import { Unit } from "../Unit"
import { Lists } from "../../Utils/Lists" import { Lists } from "../../Utils/Lists"
import { IsOnline } from "../../Logic/Web/IsOnline"
export interface Mapping { export interface Mapping {
readonly if: UploadableTag readonly if: UploadableTag
@ -1157,7 +1158,7 @@ export class TagRenderingConfigUtils {
return undefined return undefined
} }
const center = GeoOperations.centerpointCoordinates(feature) const center = GeoOperations.centerpointCoordinates(feature)
return UIEventSource.fromPromise( return UIEventSource.fromPromiseWithErr(
NameSuggestionIndex.generateMappings( NameSuggestionIndex.generateMappings(
config.freeform.key, config.freeform.key,
tags, tags,
@ -1167,7 +1168,20 @@ export class TagRenderingConfigUtils {
) )
) )
}) })
return extraMappings.mapD((extraMappings) => { return extraMappings.map((extraMappingsErr) => {
if(extraMappingsErr?.["error"]){
console.log("Could not download the NSI: ", extraMappingsErr["error"])
return config
}
const extraMappings = extraMappingsErr?.success
if(extraMappings === undefined){
if(!IsOnline.isOnline.data){
// The 'extraMappings' will still attempt to download the NSI - it might be in the service worker's cache
// As such, if they happen to come through anyway, they'll be shown
return config
}
return undefined
}
if (extraMappings.length == 0) { if (extraMappings.length == 0) {
return config return config
} }
@ -1187,6 +1201,6 @@ export class TagRenderingConfigUtils {
}) ?? [] }) ?? []
clone.mappings = [...oldMappingsCloned, ...extraMappings] clone.mappings = [...oldMappingsCloned, ...extraMappings]
return clone return clone
}) }, [IsOnline.isOnline])
} }
} }

View file

@ -13,9 +13,10 @@
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte" import Tr from "../Base/Tr.svelte"
import AccordionSingle from "../Flowbite/AccordionSingle.svelte" import AccordionSingle from "../Flowbite/AccordionSingle.svelte"
import GlobeAlt from "@babeard/svelte-heroicons/mini/GlobeAlt"
import { ComparisonState } from "./ComparisonState" import { ComparisonState } from "./ComparisonState"
import LoginToggle from "../Base/LoginToggle.svelte" import LoginToggle from "../Base/LoginToggle.svelte"
import { IsOnline } from "../../Logic/Web/IsOnline"
import GlobeAlt from "@babeard/svelte-heroicons/mini/GlobeAlt"
export let externalData: Store< export let externalData: Store<
| { success: { content: Record<string, string> } } | { success: { content: Record<string, string> } }
@ -33,7 +34,7 @@
* A switch that signals that the information should be downloaded. * A switch that signals that the information should be downloaded.
* The actual 'download' code is _not_ implemented here * The actual 'download' code is _not_ implemented here
*/ */
export let downloadInformation : UIEventSource<boolean> export let downloadInformation: UIEventSource<boolean>
export let collapsed: boolean export let collapsed: boolean
const t = Translations.t.external const t = Translations.t.external
@ -48,9 +49,10 @@
let propertyKeysExternal = comparisonState.mapD((ct) => ct.propertyKeysExternal) let propertyKeysExternal = comparisonState.mapD((ct) => ct.propertyKeysExternal)
let hasDifferencesAtStart = comparisonState.mapD((ct) => ct.hasDifferencesAtStart) let hasDifferencesAtStart = comparisonState.mapD((ct) => ct.hasDifferencesAtStart)
let enableLogin = state.featureSwitches.featureSwitchEnableLogin let enableLogin = state.featureSwitches.featureSwitchEnableLogin
const online = IsOnline.isOnline
</script> </script>
{#if $online}
<LoginToggle {state} silentFail> <LoginToggle {state} hiddenFail>
{#if !$sourceUrl || !$enableLogin} {#if !$sourceUrl || !$enableLogin}
<!-- empty block --> <!-- empty block -->
{:else if !$downloadInformation} {:else if !$downloadInformation}
@ -89,4 +91,5 @@
/> />
</AccordionSingle> </AccordionSingle>
{/if} {/if}
</LoginToggle> </LoginToggle>
{/if}

View file

@ -93,7 +93,7 @@
</Loading> </Loading>
</div> </div>
{/if} {/if}
{#if !$online} {#if !$online && $pending > 0}
<div class="alert"> <div class="alert">
<Tr t={t.upload.offline} /> <Tr t={t.upload.offline} />
</div> </div>

View file

@ -17,20 +17,18 @@ export class DeleteFlowState {
private readonly _id: OsmId private readonly _id: OsmId
private readonly _allowDeletionAtChangesetCount: number private readonly _allowDeletionAtChangesetCount: number
private readonly _osmConnection: OsmConnection private readonly _osmConnection: OsmConnection
private readonly state: SpecialVisualizationState
constructor( constructor(
id: OsmId, id: OsmId,
state: SpecialVisualizationState, state: SpecialVisualizationState,
allowDeletionAtChangesetCount?: number allowDeletionAtChangesetCount?: number
) { ) {
this.state = state
this.objectDownloader = state.osmObjectDownloader this.objectDownloader = state.osmObjectDownloader
this._id = id this._id = id
this._osmConnection = state.osmConnection this._osmConnection = state.osmConnection
this._allowDeletionAtChangesetCount = allowDeletionAtChangesetCount ?? Number.MAX_VALUE this._allowDeletionAtChangesetCount = allowDeletionAtChangesetCount ?? Number.MAX_VALUE
this.CheckDeleteability(false) this.checkDeleteability(false)
} }
/** /**
@ -39,7 +37,7 @@ export class DeleteFlowState {
* @constructor * @constructor
* @private * @private
*/ */
public CheckDeleteability(useTheInternet: boolean): void { public checkDeleteability(useTheInternet: boolean): void {
console.log("Checking deleteability (internet?", useTheInternet, ")") console.log("Checking deleteability (internet?", useTheInternet, ")")
const t = Translations.t.delete const t = Translations.t.delete
const id = this._id const id = this._id

View file

@ -22,6 +22,7 @@
import Invalid from "../../../assets/svg/Invalid.svelte" import Invalid from "../../../assets/svg/Invalid.svelte"
import { And } from "../../../Logic/Tags/And" import { And } from "../../../Logic/Tags/And"
import type { UploadableTag } from "../../../Logic/Tags/TagTypes" import type { UploadableTag } from "../../../Logic/Tags/TagTypes"
import { IsOnline } from "../../../Logic/Web/IsOnline"
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
export let deleteConfig: DeleteConfig export let deleteConfig: DeleteConfig
@ -39,9 +40,10 @@
const canBeDeletedReason = deleteAbility.canBeDeletedReason const canBeDeletedReason = deleteAbility.canBeDeletedReason
const hasSoftDeletion = deleteConfig.softDeletionTags !== undefined const hasSoftDeletion = deleteConfig.softDeletionTags !== undefined
const online = IsOnline.isOnline
let currentState: "confirm" | "applying" | "deleted" = "confirm" let currentState: "confirm" | "applying" | "deleted" = "confirm"
$: { $: {
deleteAbility.CheckDeleteability(true) deleteAbility.checkDeleteability(true)
} }
const t = Translations.t.delete const t = Translations.t.delete
@ -97,8 +99,10 @@
currentState = "deleted" currentState = "deleted"
} }
</script> </script>
{#if !$online}
<LoginToggle ignoreLoading={true} {state} silentFail> <div class="subtle">You are offline. Deleting points is not possible</div>
{:else}
<LoginToggle ignoreLoading={true} {state} hiddenFail>
{#if $canBeDeleted === false && !hasSoftDeletion} {#if $canBeDeleted === false && !hasSoftDeletion}
<div class="low-interaction subtle flex gap-x-1 rounded p-2 text-sm italic"> <div class="low-interaction subtle flex gap-x-1 rounded p-2 text-sm italic">
<div class="relative h-fit"> <div class="relative h-fit">
@ -171,3 +175,4 @@
</AccordionSingle> </AccordionSingle>
{/if} {/if}
</LoginToggle> </LoginToggle>
{/if}

View file

@ -7,23 +7,26 @@
import LoginToggle from "../Base/LoginToggle.svelte" import LoginToggle from "../Base/LoginToggle.svelte"
import type { Feature } from "geojson" import type { Feature } from "geojson"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { IsOnline } from "../../Logic/Web/IsOnline"
import { Store } from "../../Logic/UIEventSource.js"
/** /**
* A full-blown 'mark as favourite'-button * A full-blown 'mark as favourite'-button
*/ */
export let state: SpecialVisualizationState export let state: SpecialVisualizationState
export let feature: Feature export let feature: Feature
export let tags: Record<string, string> export let tags: Store<Record<string, string>>
export let layer: LayerConfig export let layer: LayerConfig
let isFavourite = tags?.map((tags) => tags._favourite === "yes") let isFavourite = tags?.map((tags) => tags._favourite === "yes")
const t = Translations.t.favouritePoi const t = Translations.t.favouritePoi
const online = IsOnline.isOnline
function markFavourite(isFavourite: boolean) { function markFavourite(isFavourite: boolean) {
state.favourites.markAsFavourite(feature, layer.id, state.theme.id, tags, isFavourite) state.favourites.markAsFavourite(feature, layer.id, state.theme.id, tags, isFavourite)
} }
</script> </script>
{#if $online}
<LoginToggle ignoreLoading={true} {state}> <LoginToggle ignoreLoading hiddenFail {state}>
{#if $isFavourite} {#if $isFavourite}
<div class="flex h-fit items-start"> <div class="flex h-fit items-start">
<button class="w-full" on:click={() => markFavourite(false)}> <button class="w-full" on:click={() => markFavourite(false)}>
@ -44,4 +47,5 @@
</div> </div>
</button> </button>
{/if} {/if}
</LoginToggle> </LoginToggle>
{/if}

View file

@ -71,6 +71,16 @@
window.requestIdleCallback(() => { window.requestIdleCallback(() => {
InstallServiceWorker.precache(layer["_usedImages"]?.filter(i => i.startsWith("./"))) InstallServiceWorker.precache(layer["_usedImages"]?.filter(i => i.startsWith("./")))
}) })
// The NSI
window.requestIdleCallback(() => {
InstallServiceWorker.precache(
[Constants.nsiLogosEndpoint + "nsi.min.json",
Constants.nsiLogosEndpoint + "featureCollection.min.json",
],
)
})
} }
}).catch(e => console.error("Could not install service worker:", e)) }).catch(e => console.error("Could not install service worker:", e))
</script> </script>