UX: add progress bar for uploading images

This commit is contained in:
Pieter Vander Vennet 2025-05-07 16:31:00 +02:00
parent bb33c43950
commit 221b572a1f
8 changed files with 45 additions and 18 deletions

8
package-lock.json generated
View file

@ -72,7 +72,7 @@
"osm-auth": "^2.6.0", "osm-auth": "^2.6.0",
"osmtogeojson": "^3.0.0-beta.5", "osmtogeojson": "^3.0.0-beta.5",
"pannellum": "^2.5.6", "pannellum": "^2.5.6",
"panoramax-js": "^0.4.12", "panoramax-js": "^0.5.5",
"panzoom": "^9.4.3", "panzoom": "^9.4.3",
"papaparse": "^5.5.2", "papaparse": "^5.5.2",
"pg": "^8.11.3", "pg": "^8.11.3",
@ -22012,9 +22012,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/panoramax-js": { "node_modules/panoramax-js": {
"version": "0.4.12", "version": "0.5.5",
"resolved": "https://registry.npmjs.org/panoramax-js/-/panoramax-js-0.4.12.tgz", "resolved": "https://registry.npmjs.org/panoramax-js/-/panoramax-js-0.5.5.tgz",
"integrity": "sha512-BFNMGFumMmBfL7QC3IQxgWjy1nr2AWrcNcQY9n1y0UGGNLrDxv9+i6EzuZ3slgGP+qvb7tKx8mqfZaN6U/O/4g==", "integrity": "sha512-gIGeBFszIvtW2DhOHHQ9FtTzwkOv8MEBY2Lu9n+QxbGOlkiCbUpfA7afcv6XEbbEJ4HM8ffAVpcZgHLJFIFkvQ==",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {
"@ogcapi-js/features": "^1.1.1", "@ogcapi-js/features": "^1.1.1",

View file

@ -235,7 +235,7 @@
"osm-auth": "^2.6.0", "osm-auth": "^2.6.0",
"osmtogeojson": "^3.0.0-beta.5", "osmtogeojson": "^3.0.0-beta.5",
"pannellum": "^2.5.6", "pannellum": "^2.5.6",
"panoramax-js": "^0.4.12", "panoramax-js": "^0.5.5",
"panzoom": "^9.4.3", "panzoom": "^9.4.3",
"papaparse": "^5.5.2", "papaparse": "^5.5.2",
"pg": "^8.11.3", "pg": "^8.11.3",

View file

@ -57,6 +57,9 @@ export class ImageUploadManager {
extramessage?: string extramessage?: string
) => Promise<void> ) => Promise<void>
private readonly _progressCurrentImage: UIEventSource<number> = new UIEventSource(0)
public readonly progressCurrentImage: Store<number> = this._progressCurrentImage
constructor( constructor(
layout: ThemeConfig, layout: ThemeConfig,
uploader: ImageUploader, uploader: ImageUploader,
@ -276,11 +279,12 @@ export class ImageUploadManager {
let absoluteUrl: string let absoluteUrl: string
try { try {
;({ key, value, absoluteUrl } = await this._uploader.uploadImage( ({ key, value, absoluteUrl } = await this._uploader.uploadImage(
blob, blob,
location, location,
author, author,
noblur noblur,
this._progressCurrentImage
)) ))
} catch (e) { } catch (e) {
console.error("Could again not upload image due to", e) console.error("Could again not upload image due to", e)

View file

@ -1,3 +1,5 @@
import { UIEventSource } from "../UIEventSource"
export interface ImageUploader { export interface ImageUploader {
maxFileSizeInMegabytes?: number maxFileSizeInMegabytes?: number
/** /**
@ -8,7 +10,8 @@ export interface ImageUploader {
blob: File, blob: File,
currentGps: [number, number], currentGps: [number, number],
author: string, author: string,
noblur: boolean noblur: boolean,
progress?: UIEventSource<number>
): Promise<UploadResult> ): Promise<UploadResult>
} }

View file

@ -252,6 +252,7 @@ export class PanoramaxUploader implements ImageUploader {
currentGps: [number, number], currentGps: [number, number],
author: string, author: string,
noblur: boolean = false, noblur: boolean = false,
progress?: UIEventSource<number>,
sequenceId?: string, sequenceId?: string,
datetime?: string datetime?: string
): Promise<{ ): Promise<{
@ -319,15 +320,25 @@ export class PanoramaxUploader implements ImageUploader {
const sequence: { id: string; "stats:items": { count: number } } = ( const sequence: { id: string; "stats:items": { count: number } } = (
await p.mySequences() await p.mySequences()
).find((s) => s.id === sequenceId) ).find((s) => s.id === sequenceId)
const img = <ImageData>await p.addImage(blob, sequence, { const options = {
lon, lon,
lat, lat,
datetime, datetime,
isBlurred: noblur, isBlurred: noblur,
onProgress: undefined,
exifOverride: { exifOverride: {
Artist: author, Artist: author,
}, },
}) }
if (progress) {
options.onProgress = (e: ProgressEvent) => {
if (e.lengthComputable) {
const percentage = (e.loaded / e.total) * 100
progress.set(Math.round(percentage))
}
}
}
const img = <ImageData>await p.addImage(blob, sequence, options)
PanoramaxImageProvider.singleton.addKnownMeta(img) PanoramaxImageProvider.singleton.addKnownMeta(img)
return { return {
key: "panoramax", key: "panoramax",

View file

@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { twMerge } from "tailwind-merge"
import Loading from "../../assets/svg/Loading.svelte" import Loading from "../../assets/svg/Loading.svelte"
export let cls: string = "flex p-1 pl-2" export let cls: string = "flex p-1 pl-2"
@ -9,7 +8,7 @@
<div class="h-6 w-6 min-w-6 shrink-0 animate-spin self-center"> <div class="h-6 w-6 min-w-6 shrink-0 animate-spin self-center">
<Loading /> <Loading />
</div> </div>
<div class="ml-2"> <div class="ml-2 w-full">
<slot /> <slot />
</div> </div>
</div> </div>

View file

@ -42,6 +42,7 @@
failed.addCallbackAndRun((failed) => { failed.addCallbackAndRun((failed) => {
dismissed = Math.min(failed, dismissed) dismissed = Math.min(failed, dismissed)
}) })
let progress = state.imageUploadManager.progressCurrentImage
</script> </script>
{#if $debugging} {#if $debugging}
@ -53,11 +54,20 @@
{#if $pending - $failed > 0} {#if $pending - $failed > 0}
<div class="alert"> <div class="alert">
<Loading> <Loading>
<div class="w-full flex flex-col">
<div class="w-full flex justify-between">
{#if $pending - $failed === 1} {#if $pending - $failed === 1}
<Tr t={t.upload.one.uploading} /> <Tr t={t.upload.one.uploading} />
{:else if $pending - $failed > 1} {:else if $pending - $failed > 1}
<Tr t={t.upload.multiple.uploading.Subs({ count: $pending })} /> <Tr t={t.upload.multiple.uploading.Subs({ count: $pending })} />
{/if} {/if}
{$progress}%
</div>
<div class="w-full low-interaction h-1">
<div class="bg-black h-1" style={`width: calc(${$progress}%)`}></div>
</div>
</div>
</Loading> </Loading>
</div> </div>
{/if} {/if}

View file

@ -18,7 +18,7 @@
import LevelSelector from "./BigComponents/LevelSelector.svelte" import LevelSelector from "./BigComponents/LevelSelector.svelte"
import type { RasterLayerPolygon } from "../Models/RasterLayers" import type { RasterLayerPolygon } from "../Models/RasterLayers"
import { AvailableRasterLayers } from "../Models/RasterLayers" import { AvailableRasterLayers } from "../Models/RasterLayers"
import { onDestroy, setContext } from "svelte" import { onDestroy } from "svelte"
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte" import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"
import StateIndicator from "./BigComponents/StateIndicator.svelte" import StateIndicator from "./BigComponents/StateIndicator.svelte"
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte" import UploadingImageCounter from "./Image/UploadingImageCounter.svelte"