forked from MapComplete/MapComplete
Feature: add emergency image backup. If uploading images fails, they are saved into local storage and uploaded later on. Part of #2111, but also #2342
This commit is contained in:
parent
7380841205
commit
9f3d198068
9 changed files with 313 additions and 8 deletions
|
|
@ -61,6 +61,9 @@
|
|||
import Hotkeys from "../Base/Hotkeys"
|
||||
import { ArrowTrendingUp } from "@babeard/svelte-heroicons/solid/ArrowTrendingUp"
|
||||
import ArrowTopRightOnSquare from "@babeard/svelte-heroicons/mini/ArrowTopRightOnSquare"
|
||||
import FailedImagesView from "../Image/FailedImagesView.svelte"
|
||||
import { PhotoIcon } from "@babeard/svelte-heroicons/outline"
|
||||
import EmergencyImageBackup from "../../Logic/ImageProviders/EmergencyImageBackup"
|
||||
|
||||
export let state: {
|
||||
favourites: FavouritesFeatureSource
|
||||
|
|
@ -97,6 +100,8 @@
|
|||
}
|
||||
})
|
||||
let isAndroid = AndroidPolyfill.inAndroid
|
||||
let nrOfFailedImages = EmergencyImageBackup.singleton.failedImages
|
||||
let failedImagesOpen = pg.failedImages
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
|
@ -156,6 +161,16 @@
|
|||
/>
|
||||
</Page>
|
||||
|
||||
{#if $nrOfFailedImages.length > 0 || $failedImagesOpen}
|
||||
<Page {onlyLink} shown={pg.failedImages} bodyPadding="p-0 pb-4">
|
||||
<svelte:fragment slot="header">
|
||||
<PhotoIcon />
|
||||
<Tr t={Translations.t.failedImages.menu.Subs({count: $nrOfFailedImages.length})} />
|
||||
</svelte:fragment>
|
||||
<FailedImagesView {state} />
|
||||
</Page>
|
||||
{/if}
|
||||
|
||||
<LoginToggle {state} silentFail>
|
||||
{#if state.favourites}
|
||||
<Page {onlyLink} shown={pg.favourites}>
|
||||
|
|
|
|||
88
src/UI/Image/FailedImage.svelte
Normal file
88
src/UI/Image/FailedImage.svelte
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<script lang="ts">
|
||||
import type { FailedImageArgs } from "../../Logic/ImageProviders/EmergencyImageBackup"
|
||||
import ThemeViewState from "../../Models/ThemeViewState"
|
||||
import EmergencyImageBackup from "../../Logic/ImageProviders/EmergencyImageBackup"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import { TrashIcon } from "@babeard/svelte-heroicons/mini"
|
||||
import Popup from "../Base/Popup.svelte"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import Page from "../Base/Page.svelte"
|
||||
import BackButton from "../Base/BackButton.svelte"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
|
||||
let emergencyBackup = EmergencyImageBackup.singleton
|
||||
let isUploading = emergencyBackup.isUploading
|
||||
export let state: ThemeViewState
|
||||
let _state: "idle" | "retrying" | "failed" | "success" = "idle"
|
||||
export let failedImage: FailedImageArgs
|
||||
let confirmDelete = new UIEventSource(false)
|
||||
|
||||
async function retry() {
|
||||
_state = "retrying"
|
||||
const success = await emergencyBackup.retryUploading(state, failedImage)
|
||||
if (success) {
|
||||
_state = "success"
|
||||
} else {
|
||||
_state = "failed"
|
||||
}
|
||||
}
|
||||
|
||||
function del() {
|
||||
emergencyBackup.delete(failedImage)
|
||||
}
|
||||
|
||||
const t = Translations.t
|
||||
</script>
|
||||
|
||||
<div class="low-interaction rounded border-interactive w-fit p-2 m-1 flex flex-col">
|
||||
|
||||
<img class="max-w-64 w-auto max-h-64 w-auto" src={URL.createObjectURL(failedImage.blob)} />
|
||||
{failedImage.featureId} {failedImage.layoutId}
|
||||
{#if $isUploading || _state === "retrying"}
|
||||
<Loading>
|
||||
<Tr t={t.image.upload.one.uploading} />
|
||||
</Loading>
|
||||
{:else if _state === "idle" || _state === "failed"}
|
||||
<button on:click={() => retry()}>
|
||||
<Tr t={t.failedImages.retry} />
|
||||
</button>
|
||||
{#if _state === "failed"}
|
||||
<span class="alert"><Tr t={t.image.upload.one.failed} /></span>
|
||||
{/if}
|
||||
{:else if _state === "success"}
|
||||
<div class="thanks">
|
||||
<Tr t={t.image.upload.one.done} />
|
||||
</div>
|
||||
{/if}
|
||||
<button class="as-link self-end" on:click={() => {confirmDelete.set(true)}}>
|
||||
<TrashIcon class="w-4" />
|
||||
<Tr t={t.failedImages.delete} />
|
||||
</button>
|
||||
<Popup shown={confirmDelete} dismissable={true}>
|
||||
<Page shown={confirmDelete}>
|
||||
<svelte:fragment slot="header">
|
||||
<TrashIcon class="w-8 m-1" />
|
||||
<Tr t={t.failedImages.confirmDeleteTitle} />
|
||||
</svelte:fragment>
|
||||
|
||||
<div class="flex flex-col ">
|
||||
|
||||
<div class="flex justify-center">
|
||||
<img class="max-w-128 w-auto max-h-128 w-auto" src={URL.createObjectURL(failedImage.blob)} />
|
||||
</div>
|
||||
|
||||
<div class="flex w-full">
|
||||
<BackButton clss="w-full" on:click={() => confirmDelete.set(false)}>
|
||||
<Tr t={t.general.back} />
|
||||
</BackButton>
|
||||
<button on:click={() => del()} class="primary w-full">
|
||||
|
||||
<TrashIcon class="w-8 m-1" />
|
||||
<Tr t={t.failedImages.confirmDelete} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
</Popup>
|
||||
</div>
|
||||
41
src/UI/Image/FailedImagesView.svelte
Normal file
41
src/UI/Image/FailedImagesView.svelte
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<script lang="ts">
|
||||
import EmergencyImageBackup from "../../Logic/ImageProviders/EmergencyImageBackup"
|
||||
import ThemeViewState from "../../Models/ThemeViewState"
|
||||
import FailedImage from "./FailedImage.svelte"
|
||||
import { ArrowPathIcon } from "@babeard/svelte-heroicons/mini"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import { WithImageState } from "../../Models/ThemeViewState/WithImageState"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
|
||||
let emergencyBackup = EmergencyImageBackup.singleton
|
||||
let failed = emergencyBackup.failedImages
|
||||
export let state: WithImageState
|
||||
let isUploading = emergencyBackup.isUploading
|
||||
|
||||
const t = Translations.t
|
||||
</script>
|
||||
|
||||
<div class="m-4 flex flex-col">
|
||||
{#if $failed.length === 0}
|
||||
<Tr t={t.failedImages.noFailedImages} />
|
||||
{:else}
|
||||
<div>
|
||||
<Tr t={t.failedImages.intro} />
|
||||
</div>
|
||||
|
||||
{#if $isUploading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<button class="primary" on:click={() => emergencyBackup.retryAll(state)}>
|
||||
<ArrowPathIcon class="w-8 h-8 m-1" />
|
||||
<Tr t={t.failedImages.retryAll} />
|
||||
</button>
|
||||
{/if}
|
||||
<div class="flex flex-wrap">
|
||||
{#each $failed as failedImage (failedImage.date + failedImage.featureId)}
|
||||
<FailedImage {failedImage} {state} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -58,7 +58,9 @@
|
|||
"image",
|
||||
noBlur,
|
||||
feature,
|
||||
ignoreGps
|
||||
{
|
||||
ignoreGps
|
||||
}
|
||||
)
|
||||
if (!uploadResult) {
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue