forked from MapComplete/MapComplete
Feature(offline): more offline hardening
This commit is contained in:
parent
f1da97285f
commit
561e4cb009
7 changed files with 103 additions and 69 deletions
|
@ -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
|
||||||
|
@ -1154,10 +1155,10 @@ export class TagRenderingConfigUtils {
|
||||||
const extraMappings = tags.bindD((tags) => {
|
const extraMappings = tags.bindD((tags) => {
|
||||||
const country = tags._country
|
const country = tags._country
|
||||||
if (country === undefined) {
|
if (country === undefined) {
|
||||||
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])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,45 +49,47 @@
|
||||||
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}
|
||||||
<button on:click={() => downloadInformation.set(true)}>
|
<button on:click={() => downloadInformation.set(true)}>
|
||||||
Attempt to download information from the website {$sourceUrl}
|
Attempt to download information from the website {$sourceUrl}
|
||||||
</button>
|
</button>
|
||||||
{:else if $externalData === undefined}
|
{:else if $externalData === undefined}
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<Loading />
|
<Loading />
|
||||||
</div>
|
</div>
|
||||||
{:else if $externalData["error"] !== undefined}
|
{:else if $externalData["error"] !== undefined}
|
||||||
<div class="subtle low-interaction rounded p-2 px-4 italic">
|
<div class="subtle low-interaction rounded p-2 px-4 italic">
|
||||||
<Tr t={Translations.t.external.error} />
|
<Tr t={Translations.t.external.error} />
|
||||||
</div>
|
</div>
|
||||||
{:else if $propertyKeysExternal.length === 0 && $knownImages.size + $unknownImages.length === 0}
|
{:else if $propertyKeysExternal.length === 0 && $knownImages.size + $unknownImages.length === 0}
|
||||||
<Tr cls="subtle" t={t.noDataLoaded} />
|
<Tr cls="subtle" t={t.noDataLoaded} />
|
||||||
{:else if !$hasDifferencesAtStart}
|
{:else if !$hasDifferencesAtStart}
|
||||||
<span class="subtle text-sm">
|
<span class="subtle text-sm">
|
||||||
<Tr t={t.allIncluded.Subs({ source: $sourceUrl })} />
|
<Tr t={t.allIncluded.Subs({ source: $sourceUrl })} />
|
||||||
</span>
|
</span>
|
||||||
{:else if $comparisonState !== undefined}
|
{:else if $comparisonState !== undefined}
|
||||||
<AccordionSingle expanded={!collapsed}>
|
<AccordionSingle expanded={!collapsed}>
|
||||||
<span slot="header" class="flex">
|
<span slot="header" class="flex">
|
||||||
<GlobeAlt class="h-6 w-6" />
|
<GlobeAlt class="h-6 w-6" />
|
||||||
<Tr t={Translations.t.external.title} />
|
<Tr t={Translations.t.external.title} />
|
||||||
</span>
|
</span>
|
||||||
<ComparisonTable
|
<ComparisonTable
|
||||||
externalProperties={$externalData["success"]}
|
externalProperties={$externalData["success"]}
|
||||||
{state}
|
{state}
|
||||||
{feature}
|
{feature}
|
||||||
{layer}
|
{layer}
|
||||||
{tags}
|
{tags}
|
||||||
{readonly}
|
{readonly}
|
||||||
sourceUrl={$sourceUrl}
|
sourceUrl={$sourceUrl}
|
||||||
comparisonState={$comparisonState}
|
comparisonState={$comparisonState}
|
||||||
/>
|
/>
|
||||||
</AccordionSingle>
|
</AccordionSingle>
|
||||||
{/if}
|
{/if}
|
||||||
</LoginToggle>
|
</LoginToggle>
|
||||||
|
{/if}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -7,41 +7,45 @@
|
||||||
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)}>
|
||||||
<HeartSolidIcon class="mr-2 w-16 shrink-0" on:click={() => markFavourite(false)} />
|
<HeartSolidIcon class="mr-2 w-16 shrink-0" on:click={() => markFavourite(false)} />
|
||||||
<div class="flex flex-col items-start">
|
<div class="flex flex-col items-start">
|
||||||
<Tr t={t.button.unmark} />
|
<Tr t={t.button.unmark} />
|
||||||
<Tr cls="normal-font subtle" t={t.button.unmarkNotDeleted} />
|
<Tr cls="normal-font subtle" t={t.button.unmarkNotDeleted} />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<Tr cls="font-bold thanks m-2 p-2 block" t={t.button.isFavourite} />
|
||||||
|
{:else}
|
||||||
|
<button class="w-full" on:click={() => markFavourite(true)}>
|
||||||
|
<HeartOutlineIcon class="mr-2 w-16 shrink-0" on:click={() => markFavourite(true)} />
|
||||||
|
<div class="flex w-full flex-col items-start">
|
||||||
|
<Tr t={t.button.markAsFavouriteTitle} />
|
||||||
|
<Tr cls="normal-font subtle" t={t.button.markDescription} />
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
{/if}
|
||||||
<Tr cls="font-bold thanks m-2 p-2 block" t={t.button.isFavourite} />
|
</LoginToggle>
|
||||||
{:else}
|
{/if}
|
||||||
<button class="w-full" on:click={() => markFavourite(true)}>
|
|
||||||
<HeartOutlineIcon class="mr-2 w-16 shrink-0" on:click={() => markFavourite(true)} />
|
|
||||||
<div class="flex w-full flex-col items-start">
|
|
||||||
<Tr t={t.button.markAsFavouriteTitle} />
|
|
||||||
<Tr cls="normal-font subtle" t={t.button.markDescription} />
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</LoginToggle>
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue