UX: add pending changes indicator

This commit is contained in:
Pieter Vander Vennet 2023-10-16 13:38:11 +02:00
parent 09e50464b8
commit d7d6c3142c
6 changed files with 56 additions and 16 deletions

View file

@ -316,6 +316,7 @@
}, },
"skip": "Skip this question", "skip": "Skip this question",
"testing": "Testing - changes won't be saved", "testing": "Testing - changes won't be saved",
"uploadError": "Error while uploading changes: {error}",
"uploadGpx": { "uploadGpx": {
"choosePermission": "Choose below if your track should be shared:", "choosePermission": "Choose below if your track should be shared:",
"confirm": "Confirm upload", "confirm": "Confirm upload",
@ -343,6 +344,9 @@
"uploadFinished": "Your track has been uploaded!", "uploadFinished": "Your track has been uploaded!",
"uploading": "Uploading your trace…" "uploading": "Uploading your trace…"
}, },
"uploadPending": "{count} changes pending",
"uploadPendingSingle": "One change pending",
"uploadingChanges": "Uploading changes…",
"useSearch": "Use the search above to see presets", "useSearch": "Use the search above to see presets",
"useSearchForMore": "Use the search function to search within {total} more values…", "useSearchForMore": "Use the search function to search within {total} more values…",
"waitingForGeopermission": "Waiting for your permission to use the geolocation…", "waitingForGeopermission": "Waiting for your permission to use the geolocation…",

View file

@ -5,23 +5,11 @@ import { Utils } from "../../Utils"
import { Feature } from "geojson" import { Feature } from "geojson"
export default class PendingChangesUploader { export default class PendingChangesUploader {
private lastChange: Date
constructor(changes: Changes, selectedFeature: UIEventSource<Feature>) { constructor(changes: Changes, selectedFeature: UIEventSource<Feature>) {
const self = this changes.pendingChanges.stabilized(Constants.updateTimeoutSec * 1000).addCallback(() => changes.flushChanges("Flushing changes due to timeout"))
this.lastChange = new Date()
changes.pendingChanges.addCallback(() => {
self.lastChange = new Date()
window.setTimeout(() => { selectedFeature.stabilized(1000).addCallback((feature) => {
const diff = (new Date().getTime() - self.lastChange.getTime()) / 1000
if (Constants.updateTimeoutSec >= diff - 1) {
changes.flushChanges("Flushing changes due to timeout")
}
}, Constants.updateTimeoutSec * 1000)
})
selectedFeature.stabilized(10000).addCallback((feature) => {
if (feature === undefined) { if (feature === undefined) {
// The popup got closed - we flush // The popup got closed - we flush
changes.flushChanges("Flushing changes due to popup closed") changes.flushChanges("Flushing changes due to popup closed")

View file

@ -26,6 +26,7 @@ export class Changes {
public readonly extraComment: UIEventSource<string> = new UIEventSource(undefined) public readonly extraComment: UIEventSource<string> = new UIEventSource(undefined)
public readonly backend: string public readonly backend: string
public readonly isUploading = new UIEventSource(false) public readonly isUploading = new UIEventSource(false)
public readonly errors = new UIEventSource<string[]>([], "upload-errors")
private readonly historicalUserLocations?: FeatureSource private readonly historicalUserLocations?: FeatureSource
private _nextId: number = -1 // Newly assigned ID's are negative private _nextId: number = -1 // Newly assigned ID's are negative
private readonly previouslyCreated: OsmObject[] = [] private readonly previouslyCreated: OsmObject[] = []
@ -128,8 +129,11 @@ export class Changes {
const csNumber = await this.flushChangesAsync() const csNumber = await this.flushChangesAsync()
this.isUploading.setData(false) this.isUploading.setData(false)
console.log("Changes flushed. Your changeset is " + csNumber) console.log("Changes flushed. Your changeset is " + csNumber)
this.errors.setData([])
} catch (e) { } catch (e) {
this.isUploading.setData(false) this.isUploading.setData(false)
this.errors.data.push(e)
this.errors.ping()
console.error("Flushing changes failed due to", e) console.error("Flushing changes failed due to", e)
} }
} }
@ -415,6 +419,8 @@ export class Changes {
id, id,
" dropping it from the changes (" + e + ")" " dropping it from the changes (" + e + ")"
) )
this.errors.data.push(e)
this.errors.ping()
return undefined return undefined
} }
}) })
@ -572,9 +578,15 @@ export class Changes {
openChangeset.data openChangeset.data
) )
return await self.flushSelectChanges(pendingChanges, openChangeset) const result = await self.flushSelectChanges(pendingChanges, openChangeset)
if(result){
this.errors.setData([])
}
return result
} catch (e) { } catch (e) {
console.error("Could not upload some changes:", e) console.error("Could not upload some changes:", e)
this.errors.data.push(e)
this.errors.ping()
return false return false
} }
}) })
@ -589,6 +601,8 @@ export class Changes {
"Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those", "Could not handle changes - probably an old, pending changeset in localstorage with an invalid format; erasing those",
e e
) )
this.errors.data.push(e)
this.errors.ping()
self.pendingChanges.setData([]) self.pendingChanges.setData([])
} finally { } finally {
self.isUploading.setData(false) self.isUploading.setData(false)

View file

@ -63,7 +63,7 @@ export default class Constants {
* Used by 'PendingChangesUploader', which waits this amount of seconds to upload changes. * Used by 'PendingChangesUploader', which waits this amount of seconds to upload changes.
* (Note that pendingChanges might upload sooner if the popup is closed or similar) * (Note that pendingChanges might upload sooner if the popup is closed or similar)
*/ */
static updateTimeoutSec: number = 30 static updateTimeoutSec: number = 15
/** /**
* If the contributor has their GPS location enabled and makes a change, * If the contributor has their GPS location enabled and makes a change,
* the points visited less then `nearbyVisitTime`-seconds ago will be inspected. * the points visited less then `nearbyVisitTime`-seconds ago will be inspected.

View file

@ -0,0 +1,32 @@
<script lang="ts">
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Store } from "../../Logic/UIEventSource"
import { Changes } from "../../Logic/Osm/Changes"
import Loading from "../Base/Loading.svelte"
import Translations from "../i18n/Translations"
import Tr from "../Base/Tr.svelte"
export let state: SpecialVisualizationState
const changes: Changes = state.changes
const isUploading: Store<boolean> = changes.isUploading
const pendingChangesCount: Store<number> = changes.pendingChanges.map(ls => ls.length)
const errors = changes.errors
</script>
<div class="flex flex-col pointer-events-auto" on:click={() => changes.flushChanges("Pending changes indicator clicked")}>
{#if $isUploading}
<Loading>
<Tr cls="thx" t={Translations.t.general.uploadingChanges} />
</Loading>
{:else if $pendingChangesCount === 1}
<Tr cls="alert" t={Translations.t.general.uploadPendingSingle} />
{:else if $pendingChangesCount > 1}
<Tr cls="alert" t={Translations.t.general.uploadPending.Subs({count: $pendingChangesCount})} />
{/if}
{#each $errors as error}
<Tr cls="alert" t={Translations.t.general.uploadError.Subs({error})} />
{/each}
</div>

View file

@ -53,6 +53,7 @@
import Locale from "./i18n/Locale"; import Locale from "./i18n/Locale";
import ShareScreen from "./BigComponents/ShareScreen.svelte"; import ShareScreen from "./BigComponents/ShareScreen.svelte";
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte" import UploadingImageCounter from "./Image/UploadingImageCounter.svelte"
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte"
export let state: ThemeViewState; export let state: ThemeViewState;
let layout = state.layout; let layout = state.layout;
@ -156,6 +157,7 @@
construct={() => new ExtraLinkButton(state, layout.extraLink).SetClass("pointer-events-auto")} construct={() => new ExtraLinkButton(state, layout.extraLink).SetClass("pointer-events-auto")}
/> />
<UploadingImageCounter {state} featureId="*" showThankYou={false}/> <UploadingImageCounter {state} featureId="*" showThankYou={false}/>
<PendingChangesIndicator {state}/>
<If condition={state.featureSwitchIsTesting}> <If condition={state.featureSwitchIsTesting}>
<div class="alert w-fit">Testmode</div> <div class="alert w-fit">Testmode</div>
</If> </If>