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
				
			
		
							
								
								
									
										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