forked from MapComplete/MapComplete
merge develop
This commit is contained in:
commit
3e4708b0b9
506 changed files with 7945 additions and 74587 deletions
65
src/UI/Image/QueuedImage.svelte
Normal file
65
src/UI/Image/QueuedImage.svelte
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<script lang="ts">
|
||||
import type { ImageUploadArguments } from "../../Logic/ImageProviders/ImageUploadQueue"
|
||||
import ImageUploadQueue from "../../Logic/ImageProviders/ImageUploadQueue"
|
||||
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 queue = ImageUploadQueue.singleton
|
||||
export let imageArguments: ImageUploadArguments
|
||||
let confirmDelete = new UIEventSource(false)
|
||||
|
||||
|
||||
function del() {
|
||||
queue.delete(imageArguments)
|
||||
}
|
||||
|
||||
const t = Translations.t
|
||||
let src = undefined
|
||||
try{
|
||||
|
||||
src = URL.createObjectURL(imageArguments.blob)
|
||||
}catch (e) {
|
||||
console.error("Could not create an ObjectURL for blob", imageArguments.blob)
|
||||
}
|
||||
</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} />
|
||||
{imageArguments.featureId} {imageArguments.layoutId}
|
||||
<button class="as-link self-end" on:click={() => {confirmDelete.set(true)}}>
|
||||
<TrashIcon class="w-4" />
|
||||
<Tr t={t.imageQueue.delete} />
|
||||
</button>
|
||||
<Popup shown={confirmDelete} dismissable={true}>
|
||||
<Page shown={confirmDelete}>
|
||||
<svelte:fragment slot="header">
|
||||
<TrashIcon class="w-8 m-1" />
|
||||
<Tr t={t.imageQueue.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(imageArguments.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.imageQueue.confirmDelete} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
</Popup>
|
||||
</div>
|
||||
42
src/UI/Image/QueuedImagesView.svelte
Normal file
42
src/UI/Image/QueuedImagesView.svelte
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<script lang="ts">
|
||||
import QueuedImage from "./QueuedImage.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"
|
||||
import type { ImageUploadArguments } from "../../Logic/ImageProviders/ImageUploadQueue"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import UploadingImageCounter from "./UploadingImageCounter.svelte"
|
||||
export let state: WithImageState
|
||||
let queued: Store<ImageUploadArguments[]> = state.imageUploadManager.queuedArgs
|
||||
let isUploading = state.imageUploadManager.isUploading
|
||||
const t = Translations.t
|
||||
const q = t.imageQueue
|
||||
</script>
|
||||
|
||||
<div class="m-4 flex flex-col">
|
||||
{#if $queued.length === 0}
|
||||
<Tr t={q.noFailedImages} />
|
||||
{:else}
|
||||
<div>
|
||||
<Tr t={q.intro} />
|
||||
</div>
|
||||
|
||||
<UploadingImageCounter {state}/>
|
||||
|
||||
{#if $isUploading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<button class="primary" on:click={() => state.imageUploadManager.uploadQueue()}>
|
||||
<ArrowPathIcon class="w-8 h-8 m-1" />
|
||||
<Tr t={q.retryAll} />
|
||||
</button>
|
||||
{/if}
|
||||
<div class="flex flex-wrap">
|
||||
{#each $queued as i (i.date + i.featureId)}
|
||||
<QueuedImage imageArguments={i} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
import LoginButton from "../Base/LoginButton.svelte"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import NoteCommentElement from "../Popup/Notes/NoteCommentElement"
|
||||
import type { Feature } from "geojson"
|
||||
import Camera from "@babeard/svelte-heroicons/mini/Camera"
|
||||
|
||||
|
|
@ -22,7 +21,6 @@
|
|||
|
||||
export let tags: UIEventSource<OsmTags>
|
||||
export let targetKey: string = undefined
|
||||
export let layer: LayerConfig
|
||||
export let noBlur: boolean = false
|
||||
export let feature: Feature
|
||||
/**
|
||||
|
|
@ -38,7 +36,7 @@
|
|||
|
||||
let errors = new UIEventSource<Translation[]>([])
|
||||
|
||||
async function handleFiles(files: FileList, ignoreGps: boolean = false) {
|
||||
async function handleFiles(files: FileList, ignoreGPS: boolean = false) {
|
||||
const errs = []
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files.item(i)
|
||||
|
|
@ -49,29 +47,7 @@
|
|||
errs.push(canBeUploaded.error)
|
||||
continue
|
||||
}
|
||||
|
||||
if (layer?.id === "note") {
|
||||
const uploadResult = await state?.imageUploadManager.uploadImageWithLicense(
|
||||
tags.data.id,
|
||||
state.osmConnection.userDetails.data?.name ?? "Anonymous",
|
||||
file,
|
||||
"image",
|
||||
noBlur,
|
||||
feature,
|
||||
ignoreGps
|
||||
)
|
||||
if (!uploadResult) {
|
||||
return
|
||||
}
|
||||
const url = uploadResult.absoluteUrl
|
||||
await state.osmConnection.addCommentToNote(tags.data.id, url)
|
||||
NoteCommentElement.addCommentTo(url, <UIEventSource<OsmTags>>tags, {
|
||||
osmConnection: state.osmConnection,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
await state?.imageUploadManager?.uploadImageAndApply(file, tags, targetKey, noBlur, feature)
|
||||
await state?.imageUploadManager?.uploadImageAndApply(file, tags, targetKey, noBlur, feature, { ignoreGPS })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
state.reportError(e, "Could not upload image")
|
||||
|
|
@ -98,7 +74,7 @@
|
|||
<Tr t={error} cls="alert" />
|
||||
{/each}
|
||||
<FileSelector
|
||||
accept="image/*"
|
||||
accept=".jpg,.jpeg,image/jpeg"
|
||||
capture="environment"
|
||||
cls="button border-2 flex flex-col"
|
||||
multiple={true}
|
||||
|
|
|
|||
|
|
@ -7,76 +7,69 @@
|
|||
|
||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import type { OsmTags } from "../../Models/OsmFeature"
|
||||
import type { NoteId, OsmTags, OsmId } from "../../Models/OsmFeature"
|
||||
import Translations from "../i18n/Translations"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import { XCircleIcon } from "@babeard/svelte-heroicons/solid"
|
||||
import UploadFailedMessage from "./UploadFailedMessage.svelte"
|
||||
|
||||
export let state: SpecialVisualizationState
|
||||
export let tags: Store<OsmTags> = undefined
|
||||
export let featureId = tags?.data?.id
|
||||
export let featureId: OsmId | NoteId | "*" = tags?.data?.id ?? "*"
|
||||
if (featureId === undefined) {
|
||||
throw "No tags or featureID given"
|
||||
}
|
||||
export let showThankYou: boolean = true
|
||||
const { uploadStarted, uploadFinished, retried, failed } =
|
||||
state.imageUploadManager.getCountsFor(featureId)
|
||||
|
||||
/*
|
||||
Number of images uploaded succesfully
|
||||
*/
|
||||
function getCount(input: Store<string[]>): Store<number> {
|
||||
if (featureId == "*") {
|
||||
return input.map(inp => inp.length)
|
||||
}
|
||||
return input.map(success => success.filter(item => item === featureId).length)
|
||||
}
|
||||
|
||||
let successfull = getCount(state.imageUploadManager.successfull)
|
||||
/* Number of failed uploads */
|
||||
let failed = getCount(state.imageUploadManager.fails)
|
||||
|
||||
let pending = getCount(state.imageUploadManager.queued)
|
||||
const t = Translations.t.image
|
||||
const debugging = state.featureSwitches.featureSwitchIsDebugging
|
||||
let dismissed = 0
|
||||
failed.addCallbackAndRun(failed => {
|
||||
dismissed = Math.min(failed, dismissed)
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if $debugging}
|
||||
<div class="low-interaction">
|
||||
Started {$uploadStarted} Done {$uploadFinished} Retry {$retried} Err {$failed}
|
||||
Pending {$pending} Done {$successfull} Err {$failed}
|
||||
</div>
|
||||
{/if}
|
||||
{#if dismissed === $uploadStarted}
|
||||
<!-- We don't show anything as we ignore this number of failed items-->
|
||||
{:else if $uploadStarted === 1}
|
||||
{#if $uploadFinished === 1}
|
||||
{#if showThankYou}
|
||||
<Tr cls="thanks" t={t.upload.one.done} />
|
||||
{/if}
|
||||
{:else if $failed === 1}
|
||||
<UploadFailedMessage failed={$failed} on:click={() => (dismissed = $failed)} />
|
||||
{:else if $retried === 1}
|
||||
<div class="alert">
|
||||
<Loading>
|
||||
<Tr t={t.upload.one.retrying} />
|
||||
</Loading>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="alert">
|
||||
<Loading>
|
||||
|
||||
{#if $pending - $failed > 0}
|
||||
<div class="alert">
|
||||
<Loading>
|
||||
{#if $pending - $failed === 1}
|
||||
<Tr t={t.upload.one.uploading} />
|
||||
</Loading>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if $uploadStarted > 1}
|
||||
{#if $uploadFinished + $failed === $uploadStarted}
|
||||
{#if $uploadFinished === 0}
|
||||
<!-- pass -->
|
||||
{:else if showThankYou}
|
||||
<Tr cls="thanks" t={t.upload.multiple.done.Subs({ count: $uploadFinished })} />
|
||||
{/if}
|
||||
{:else if $uploadFinished === 0}
|
||||
<Loading cls="alert">
|
||||
<Tr t={t.upload.multiple.uploading.Subs({ count: $uploadStarted })} />
|
||||
{:else if $pending - $failed > 1}
|
||||
<Tr t={t.upload.multiple.uploading.Subs({count: $pending})} />
|
||||
{/if}
|
||||
</Loading>
|
||||
{:else if $uploadFinished > 0}
|
||||
<Loading cls="alert">
|
||||
<Tr
|
||||
t={t.upload.multiple.partiallyDone.Subs({
|
||||
count: $uploadStarted - $uploadFinished,
|
||||
done: $uploadFinished,
|
||||
})}
|
||||
/>
|
||||
</Loading>
|
||||
{/if}
|
||||
{#if $failed > 0}
|
||||
<UploadFailedMessage failed={$failed} on:click={() => (dismissed = $failed)} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $failed > dismissed}
|
||||
<UploadFailedMessage failed={$failed} on:click={() => (dismissed = $failed)} />
|
||||
{/if}
|
||||
|
||||
{#if showThankYou}
|
||||
{#if $successfull === 1}
|
||||
<Tr cls="thanks" t={t.upload.one.done} />
|
||||
{:else if $successfull > 1}
|
||||
<Tr cls="thanks" t={t.upload.multiple.done.Subs({count: $successfull})} />
|
||||
{/if}
|
||||
{/if}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue