mirror of
https://github.com/immich-app/immich.git
synced 2025-01-16 00:36:47 +01:00
refactor(web): upload panel (#12326)
Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
parent
0d6bef2c05
commit
f4ec842577
6 changed files with 184 additions and 140 deletions
|
@ -16,13 +16,14 @@
|
|||
export let ariaLabelledby: string | undefined = undefined;
|
||||
export let strokeWidth: number = 0;
|
||||
export let strokeColor: string = 'currentColor';
|
||||
export let spin = false;
|
||||
</script>
|
||||
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
{viewBox}
|
||||
class="{className} {flipped ? '-scale-x-100' : ''}"
|
||||
class="{className} {flipped ? '-scale-x-100' : ''} {spin ? 'animate-spin' : ''}"
|
||||
{role}
|
||||
stroke={strokeColor}
|
||||
stroke-width={strokeWidth}
|
||||
|
|
|
@ -1,21 +1,32 @@
|
|||
<script lang="ts">
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import type { UploadAsset } from '$lib/models/upload-asset';
|
||||
import { UploadState } from '$lib/models/upload-asset';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { getByteUnitString } from '$lib/utils/byte-units';
|
||||
import { fade } from 'svelte/transition';
|
||||
import ImmichLogo from './immich-logo.svelte';
|
||||
import { getFilenameExtension } from '$lib/utils/asset-utils';
|
||||
import { uploadAssetsStore } from '$lib/stores/upload';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import { getByteUnitString } from '$lib/utils/byte-units';
|
||||
import { fileUploadHandler } from '$lib/utils/file-uploader';
|
||||
import { mdiRefresh, mdiCancel } from '@mdi/js';
|
||||
import {
|
||||
mdiAlertCircle,
|
||||
mdiCheckCircle,
|
||||
mdiCircleOutline,
|
||||
mdiClose,
|
||||
mdiLoading,
|
||||
mdiOpenInNew,
|
||||
mdiRestart,
|
||||
} from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { fade } from 'svelte/transition';
|
||||
|
||||
export let uploadAsset: UploadAsset;
|
||||
|
||||
const handleDismiss = (uploadAsset: UploadAsset) => {
|
||||
uploadAssetsStore.removeItem(uploadAsset.id);
|
||||
};
|
||||
|
||||
const handleRetry = async (uploadAsset: UploadAsset) => {
|
||||
uploadAssetsStore.removeUploadAsset(uploadAsset.id);
|
||||
uploadAssetsStore.removeItem(uploadAsset.id);
|
||||
await fileUploadHandler([uploadAsset.file], uploadAsset.albumId);
|
||||
};
|
||||
</script>
|
||||
|
@ -23,86 +34,69 @@
|
|||
<div
|
||||
in:fade={{ duration: 250 }}
|
||||
out:fade={{ duration: 100 }}
|
||||
class="flex flex-col rounded-lg bg-immich-bg text-xs dark:bg-immich-dark-bg"
|
||||
class="flex flex-col rounded-lg bg-immich-bg text-xs dark:bg-immich-dark-bg p-2 gap-1"
|
||||
>
|
||||
<div class="grid grid-cols-[65px_auto_auto] max-h-[70px]">
|
||||
<div class="relative">
|
||||
<div in:fade={{ duration: 250 }}>
|
||||
<ImmichLogo noText class="h-[65px] w-[65px] rounded-bl-lg rounded-tl-lg object-cover p-2" />
|
||||
</div>
|
||||
<div class="absolute bottom-0 left-0 h-[25px] w-full rounded-bl-md bg-immich-primary/30">
|
||||
<p
|
||||
class="absolute bottom-1 right-1 stroke-immich-primary object-right-bottom font-semibold uppercase text-white/95 dark:text-gray-100"
|
||||
>
|
||||
.{getFilenameExtension(uploadAsset.file.name)}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="flex items-center justify-center">
|
||||
{#if uploadAsset.state === UploadState.PENDING}
|
||||
<Icon path={mdiCircleOutline} size="24" class="text-immich-primary" title={$t('pending')} />
|
||||
{:else if uploadAsset.state === UploadState.STARTED}
|
||||
<Icon path={mdiLoading} size="24" spin class="text-immich-primary" title={$t('asset_skipped')} />
|
||||
{:else if uploadAsset.state === UploadState.ERROR}
|
||||
<Icon path={mdiAlertCircle} size="24" class="text-immich-error" title={$t('error')} />
|
||||
{:else if uploadAsset.state === UploadState.DUPLICATED}
|
||||
<Icon path={mdiAlertCircle} size="24" class="text-immich-warning" title={$t('asset_skipped')} />
|
||||
{:else if uploadAsset.state === UploadState.DONE}
|
||||
<Icon path={mdiCheckCircle} size="24" class="text-immich-success" title={$t('asset_uploaded')} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-col justify-between p-2 pr-2">
|
||||
<input
|
||||
disabled
|
||||
class="w-full rounded-md border bg-gray-100 p-1 px-2 text-[10px] dark:border-immich-dark-gray dark:bg-gray-900"
|
||||
value={`[${getByteUnitString(uploadAsset.file.size, $locale)}] ${uploadAsset.file.name}`}
|
||||
/>
|
||||
<!-- <span>[{getByteUnitString(uploadAsset.file.size, $locale)}]</span> -->
|
||||
<span class="grow break-all">{uploadAsset.file.name}</span>
|
||||
|
||||
<div
|
||||
class="relative mt-[5px] h-[15px] w-full rounded-md bg-gray-300 text-white dark:bg-immich-dark-gray"
|
||||
class:dark:text-black={uploadAsset.state === UploadState.STARTED}
|
||||
>
|
||||
{#if uploadAsset.state === UploadState.STARTED}
|
||||
<div class="h-[15px] rounded-md bg-immich-primary transition-all" style={`width: ${uploadAsset.progress}%`} />
|
||||
<p class="absolute top-0 h-full w-full text-center text-[10px]">
|
||||
{#if uploadAsset.message}
|
||||
{uploadAsset.message}
|
||||
{:else}
|
||||
{uploadAsset.progress}% - {getByteUnitString(uploadAsset.speed || 0, $locale)}/s - {uploadAsset.eta}s
|
||||
{/if}
|
||||
</p>
|
||||
{:else if uploadAsset.state === UploadState.PENDING}
|
||||
<div class="h-[15px] rounded-md bg-immich-dark-gray transition-all dark:bg-immich-gray" style="width: 100%" />
|
||||
<p class="absolute top-0 h-full w-full text-center text-[10px]">{$t('pending')}</p>
|
||||
{:else if uploadAsset.state === UploadState.ERROR}
|
||||
<div class="h-[15px] rounded-md bg-immich-error transition-all" style="width: 100%" />
|
||||
<p class="absolute top-0 h-full w-full text-center text-[10px]">{$t('error')}</p>
|
||||
{:else if uploadAsset.state === UploadState.DUPLICATED}
|
||||
<div class="h-[15px] rounded-md bg-immich-warning transition-all" style="width: 100%" />
|
||||
<p class="absolute top-0 h-full w-full text-center text-[10px]">
|
||||
{$t('asset_skipped')}
|
||||
{#if uploadAsset.message}
|
||||
({uploadAsset.message})
|
||||
{/if}
|
||||
</p>
|
||||
{:else if uploadAsset.state === UploadState.DONE}
|
||||
<div class="h-[15px] rounded-md bg-immich-success transition-all" style="width: 100%" />
|
||||
<p class="absolute top-0 h-full w-full text-center text-[10px]">
|
||||
{$t('asset_uploaded')}
|
||||
{#if uploadAsset.message}
|
||||
({uploadAsset.message})
|
||||
{/if}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if uploadAsset.state === UploadState.ERROR}
|
||||
<div class="flex h-full flex-col place-content-evenly place-items-center justify-items-center pr-2">
|
||||
<button type="button" on:click={() => handleRetry(uploadAsset)} title={$t('retry_upload')} class="flex text-sm">
|
||||
<span class="text-immich-dark-gray dark:text-immich-dark-fg"><Icon path={mdiRefresh} size="20" /></span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => uploadAssetsStore.removeUploadAsset(uploadAsset.id)}
|
||||
title={$t('dismiss_error')}
|
||||
class="flex text-sm"
|
||||
{#if uploadAsset.state === UploadState.DUPLICATED && uploadAsset.assetId}
|
||||
<div class="flex items-center justify-between gap-1">
|
||||
<a
|
||||
href="{AppRoute.PHOTOS}/{uploadAsset.assetId}"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class=""
|
||||
aria-hidden="true"
|
||||
tabindex={-1}
|
||||
>
|
||||
<span class="text-immich-error"><Icon path={mdiCancel} size="20" /></span>
|
||||
<Icon path={mdiOpenInNew} size="20" />
|
||||
</a>
|
||||
<button type="button" on:click={() => handleDismiss(uploadAsset)} class="" aria-hidden="true" tabindex={-1}>
|
||||
<Icon path={mdiClose} size="20" />
|
||||
</button>
|
||||
</div>
|
||||
{:else if uploadAsset.state === UploadState.ERROR}
|
||||
<div class="flex items-center justify-between gap-1">
|
||||
<button type="button" on:click={() => handleRetry(uploadAsset)} class="" aria-hidden="true" tabindex={-1}>
|
||||
<Icon path={mdiRestart} size="20" />
|
||||
</button>
|
||||
<button type="button" on:click={() => handleDismiss(uploadAsset)} class="" aria-hidden="true" tabindex={-1}>
|
||||
<Icon path={mdiClose} size="20" />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if uploadAsset.state === UploadState.STARTED}
|
||||
<div class="text-black relative mt-[5px] h-[15px] w-full rounded-md bg-gray-300 dark:bg-immich-dark-gray">
|
||||
<div class="h-[15px] rounded-md bg-immich-primary transition-all" style={`width: ${uploadAsset.progress}%`} />
|
||||
<p class="absolute top-0 h-full w-full text-center text-[10px]">
|
||||
{#if uploadAsset.message}
|
||||
{uploadAsset.message}
|
||||
{:else}
|
||||
{uploadAsset.progress}% - {getByteUnitString(uploadAsset.speed || 0, $locale)}/s - {uploadAsset.eta}s
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if uploadAsset.state === UploadState.ERROR}
|
||||
<div class="flex flex-row justify-between">
|
||||
<p class="w-full rounded-md py-1 px-2 text-justify text-[10px] text-immich-error">
|
||||
<p class="w-full rounded-md text-justify text-immich-error">
|
||||
{uploadAsset.error}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
let showOptions = false;
|
||||
let concurrency = uploadExecutionQueue.concurrency;
|
||||
|
||||
let { isUploading, hasError, remainingUploads, errorCounter, duplicateCounter, successCounter, totalUploadCounter } =
|
||||
uploadAssetsStore;
|
||||
let { stats, isDismissible, isUploading, remainingUploads } = uploadAssetsStore;
|
||||
|
||||
const autoHide = () => {
|
||||
if (!$isUploading && showDetail) {
|
||||
|
@ -33,29 +32,29 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
{#if $hasError || $isUploading}
|
||||
{#if $isUploading}
|
||||
<div
|
||||
in:fade={{ duration: 250 }}
|
||||
out:fade={{ duration: 250 }}
|
||||
on:outroend={() => {
|
||||
if ($errorCounter > 0) {
|
||||
if ($stats.errors > 0) {
|
||||
notificationController.show({
|
||||
message: $t('upload_errors', { values: { count: $errorCounter } }),
|
||||
message: $t('upload_errors', { values: { count: $stats.errors } }),
|
||||
type: NotificationType.Warning,
|
||||
});
|
||||
} else if ($successCounter > 0) {
|
||||
} else if ($stats.success > 0) {
|
||||
notificationController.show({
|
||||
message: $t('upload_success'),
|
||||
type: NotificationType.Info,
|
||||
});
|
||||
}
|
||||
if ($duplicateCounter > 0) {
|
||||
if ($stats.duplicates > 0) {
|
||||
notificationController.show({
|
||||
message: $t('upload_skipped_duplicates', { values: { count: $duplicateCounter } }),
|
||||
message: $t('upload_skipped_duplicates', { values: { count: $stats.duplicates } }),
|
||||
type: NotificationType.Warning,
|
||||
});
|
||||
}
|
||||
uploadAssetsStore.resetStore();
|
||||
uploadAssetsStore.reset();
|
||||
}}
|
||||
class="fixed bottom-6 right-6 z-[10000]"
|
||||
>
|
||||
|
@ -70,20 +69,20 @@
|
|||
{$t('upload_progress', {
|
||||
values: {
|
||||
remaining: $remainingUploads,
|
||||
processed: $successCounter + $errorCounter,
|
||||
total: $totalUploadCounter,
|
||||
processed: $stats.total - $remainingUploads,
|
||||
total: $stats.total,
|
||||
},
|
||||
})}
|
||||
</p>
|
||||
<p class="immich-form-label text-xs">
|
||||
{$t('upload_status_uploaded')}
|
||||
<span class="text-immich-success">{$successCounter.toLocaleString($locale)}</span>
|
||||
<span class="text-immich-success">{$stats.success.toLocaleString($locale)}</span>
|
||||
-
|
||||
{$t('upload_status_errors')}
|
||||
<span class="text-immich-error">{$errorCounter.toLocaleString($locale)}</span>
|
||||
<span class="text-immich-error">{$stats.errors.toLocaleString($locale)}</span>
|
||||
-
|
||||
{$t('upload_status_duplicates')}
|
||||
<span class="text-immich-warning">{$duplicateCounter.toLocaleString($locale)}</span>
|
||||
<span class="text-immich-warning">{$stats.duplicates.toLocaleString($locale)}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-col items-end">
|
||||
|
@ -103,7 +102,7 @@
|
|||
on:click={() => (showDetail = false)}
|
||||
/>
|
||||
</div>
|
||||
{#if $hasError}
|
||||
{#if $isDismissible}
|
||||
<CircleIconButton
|
||||
title={$t('dismiss_all_errors')}
|
||||
icon={mdiCancel}
|
||||
|
@ -115,7 +114,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{#if showOptions}
|
||||
<div class="immich-scrollbar mb-4 max-h-[400px] overflow-y-auto rounded-lg pr-2">
|
||||
<div class="immich-scrollbar mb-4 max-h-[400px] overflow-y-auto rounded-lg">
|
||||
<div class="flex h-[26px] place-items-center gap-1">
|
||||
<label class="immich-form-label" for="upload-concurrency">{$t('upload_concurrency')}</label>
|
||||
</div>
|
||||
|
@ -133,7 +132,7 @@
|
|||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="immich-scrollbar flex max-h-[400px] flex-col gap-2 overflow-y-auto rounded-lg pr-2">
|
||||
<div class="immich-scrollbar flex max-h-[400px] flex-col gap-2 overflow-y-auto rounded-lg">
|
||||
{#each $uploadAssetsStore as uploadAsset (uploadAsset.id)}
|
||||
<UploadAssetPreview {uploadAsset} />
|
||||
{/each}
|
||||
|
@ -149,14 +148,14 @@
|
|||
>
|
||||
{$remainingUploads.toLocaleString($locale)}
|
||||
</button>
|
||||
{#if $hasError}
|
||||
{#if $stats.errors > 0}
|
||||
<button
|
||||
type="button"
|
||||
in:scale={{ duration: 250, easing: quartInOut }}
|
||||
on:click={() => (showDetail = true)}
|
||||
class="absolute -right-4 -top-4 flex h-10 w-10 place-content-center place-items-center rounded-full bg-immich-error p-5 text-xs text-gray-200"
|
||||
>
|
||||
{$errorCounter.toLocaleString($locale)}
|
||||
{$stats.errors.toLocaleString($locale)}
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
|
|
|
@ -9,8 +9,8 @@ export enum UploadState {
|
|||
export type UploadAsset = {
|
||||
id: string;
|
||||
file: File;
|
||||
albumId?: string;
|
||||
assetId?: string;
|
||||
albumId?: string;
|
||||
progress?: number;
|
||||
state?: UploadState;
|
||||
startDate?: number;
|
||||
|
|
|
@ -3,32 +3,36 @@ import { UploadState, type UploadAsset } from '../models/upload-asset';
|
|||
|
||||
function createUploadStore() {
|
||||
const uploadAssets = writable<Array<UploadAsset>>([]);
|
||||
|
||||
const duplicateCounter = writable(0);
|
||||
const successCounter = writable(0);
|
||||
const totalUploadCounter = writable(0);
|
||||
const stats = writable<{ errors: number; duplicates: number; success: number; total: number }>({
|
||||
errors: 0,
|
||||
duplicates: 0,
|
||||
success: 0,
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const { subscribe } = uploadAssets;
|
||||
|
||||
const isUploading = derived(uploadAssets, ($uploadAssets) => {
|
||||
return $uploadAssets.length > 0;
|
||||
});
|
||||
const errorsAssets = derived(uploadAssets, (a) => a.filter((e) => e.state === UploadState.ERROR));
|
||||
const errorCounter = derived(errorsAssets, (values) => values.length);
|
||||
const hasError = derived(errorCounter, (values) => values > 0);
|
||||
const isUploading = derived(uploadAssets, (items) => items.length > 0);
|
||||
const isDismissible = derived(uploadAssets, (items) =>
|
||||
items.some((item) => item.state === UploadState.ERROR || item.state === UploadState.DUPLICATED),
|
||||
);
|
||||
const remainingUploads = derived(
|
||||
uploadAssets,
|
||||
(values) => values.filter((a) => a.state === UploadState.PENDING || a.state === UploadState.STARTED).length,
|
||||
);
|
||||
|
||||
const addNewUploadAsset = (newAsset: UploadAsset) => {
|
||||
const addItem = (newAsset: UploadAsset) => {
|
||||
uploadAssets.update(($assets) => {
|
||||
const duplicate = $assets.find((asset) => asset.id === newAsset.id);
|
||||
if (duplicate) {
|
||||
return $assets.map((asset) => (asset.id === newAsset.id ? newAsset : asset));
|
||||
}
|
||||
|
||||
totalUploadCounter.update((c) => c + 1);
|
||||
stats.update((stats) => {
|
||||
stats.total++;
|
||||
return stats;
|
||||
});
|
||||
|
||||
$assets.push({
|
||||
...newAsset,
|
||||
speed: 0,
|
||||
|
@ -36,6 +40,7 @@ function createUploadStore() {
|
|||
progress: 0,
|
||||
eta: 0,
|
||||
});
|
||||
|
||||
return $assets;
|
||||
});
|
||||
};
|
||||
|
@ -53,7 +58,7 @@ function createUploadStore() {
|
|||
};
|
||||
|
||||
const markStarted = (id: string) => {
|
||||
updateAsset(id, {
|
||||
updateItem(id, {
|
||||
state: UploadState.STARTED,
|
||||
startDate: Date.now(),
|
||||
});
|
||||
|
@ -70,39 +75,61 @@ function createUploadStore() {
|
|||
});
|
||||
};
|
||||
|
||||
const updateAsset = (id: string, partialObject: Partial<UploadAsset>) => {
|
||||
const updateItem = (id: string, partialObject: Partial<UploadAsset>) => {
|
||||
updateAssetMap(id, (v) => ({ ...v, ...partialObject }));
|
||||
};
|
||||
|
||||
const removeUploadAsset = (id: string) => {
|
||||
const removeItem = (id: string) => {
|
||||
uploadAssets.update((uploadingAsset) => uploadingAsset.filter((a) => a.id != id));
|
||||
};
|
||||
|
||||
const dismissErrors = () => uploadAssets.update((value) => value.filter((e) => e.state !== UploadState.ERROR));
|
||||
const dismissErrors = () =>
|
||||
uploadAssets.update((value) =>
|
||||
value.filter((e) => e.state !== UploadState.ERROR && e.state !== UploadState.DUPLICATED),
|
||||
);
|
||||
|
||||
const resetStore = () => {
|
||||
const reset = () => {
|
||||
uploadAssets.set([]);
|
||||
duplicateCounter.set(0);
|
||||
successCounter.set(0);
|
||||
totalUploadCounter.set(0);
|
||||
stats.set({ errors: 0, duplicates: 0, success: 0, total: 0 });
|
||||
};
|
||||
|
||||
const track = (value: 'success' | 'duplicate' | 'error') => {
|
||||
stats.update((stats) => {
|
||||
switch (value) {
|
||||
case 'success': {
|
||||
stats.success++;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'duplicate': {
|
||||
stats.duplicates++;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'error': {
|
||||
stats.errors++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
errorCounter,
|
||||
duplicateCounter,
|
||||
successCounter,
|
||||
totalUploadCounter,
|
||||
stats,
|
||||
remainingUploads,
|
||||
hasError,
|
||||
dismissErrors,
|
||||
isDismissible,
|
||||
isUploading,
|
||||
resetStore,
|
||||
addNewUploadAsset,
|
||||
track,
|
||||
dismissErrors,
|
||||
reset,
|
||||
markStarted,
|
||||
addItem,
|
||||
updateItem,
|
||||
removeItem,
|
||||
updateProgress,
|
||||
updateAsset,
|
||||
removeUploadAsset,
|
||||
subscribe,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,25 @@ import { t } from 'svelte-i18n';
|
|||
import { get } from 'svelte/store';
|
||||
import { getServerErrorMessage, handleError } from './handle-error';
|
||||
|
||||
export const addDummyItems = () => {
|
||||
uploadAssetsStore.addItem({ id: 'asset-0', file: { name: 'asset0.jpg', size: 123_456 } as File });
|
||||
uploadAssetsStore.updateItem('asset-0', { state: UploadState.PENDING });
|
||||
uploadAssetsStore.addItem({ id: 'asset-1', file: { name: 'asset1.jpg', size: 123_456 } as File });
|
||||
uploadAssetsStore.updateItem('asset-1', { state: UploadState.STARTED });
|
||||
uploadAssetsStore.updateProgress('asset-1', 75, 100);
|
||||
uploadAssetsStore.addItem({ id: 'asset-2', file: { name: 'asset2.jpg', size: 123_456 } as File });
|
||||
uploadAssetsStore.updateItem('asset-2', { state: UploadState.ERROR, error: new Error('Internal server error') });
|
||||
uploadAssetsStore.addItem({ id: 'asset-3', file: { name: 'asset3.jpg', size: 123_456 } as File });
|
||||
uploadAssetsStore.updateItem('asset-3', { state: UploadState.DUPLICATED, assetId: 'asset-2' });
|
||||
uploadAssetsStore.addItem({ id: 'asset-4', file: { name: 'asset3.jpg', size: 123_456 } as File });
|
||||
uploadAssetsStore.updateItem('asset-4', { state: UploadState.DONE });
|
||||
uploadAssetsStore.track('error');
|
||||
uploadAssetsStore.track('success');
|
||||
uploadAssetsStore.track('duplicate');
|
||||
};
|
||||
|
||||
// addDummyItems();
|
||||
|
||||
let _extensions: string[];
|
||||
|
||||
export const uploadExecutionQueue = new ExecutorQueue({ concurrency: 2 });
|
||||
|
@ -68,7 +87,7 @@ export const fileUploadHandler = async (files: File[], albumId?: string, assetId
|
|||
for (const file of files) {
|
||||
const name = file.name.toLowerCase();
|
||||
if (extensions.some((extension) => name.endsWith(extension))) {
|
||||
uploadAssetsStore.addNewUploadAsset({ id: getDeviceAssetId(file), file, albumId, assetId });
|
||||
uploadAssetsStore.addItem({ id: getDeviceAssetId(file), file, albumId });
|
||||
promises.push(uploadExecutionQueue.addTask(() => fileUploader(file, albumId, assetId)));
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +125,7 @@ async function fileUploader(assetFile: File, albumId?: string, replaceAssetId?:
|
|||
let responseData: AssetMediaResponseDto | undefined;
|
||||
const key = getKey();
|
||||
if (crypto?.subtle?.digest && !key) {
|
||||
uploadAssetsStore.updateAsset(deviceAssetId, { message: $t('asset_hashing') });
|
||||
uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_hashing') });
|
||||
await tick();
|
||||
try {
|
||||
const bytes = await assetFile.arrayBuffer();
|
||||
|
@ -127,7 +146,7 @@ async function fileUploader(assetFile: File, albumId?: string, replaceAssetId?:
|
|||
}
|
||||
|
||||
if (!responseData) {
|
||||
uploadAssetsStore.updateAsset(deviceAssetId, { message: $t('asset_uploading') });
|
||||
uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_uploading') });
|
||||
if (replaceAssetId) {
|
||||
const response = await uploadRequest<AssetMediaResponseDto>({
|
||||
url: getBaseUrl() + getAssetOriginalPath(replaceAssetId) + (key ? `?key=${key}` : ''),
|
||||
|
@ -152,30 +171,34 @@ async function fileUploader(assetFile: File, albumId?: string, replaceAssetId?:
|
|||
}
|
||||
|
||||
if (responseData.status === AssetMediaStatus.Duplicate) {
|
||||
uploadAssetsStore.duplicateCounter.update((count) => count + 1);
|
||||
uploadAssetsStore.track('duplicate');
|
||||
} else {
|
||||
uploadAssetsStore.successCounter.update((c) => c + 1);
|
||||
uploadAssetsStore.track('success');
|
||||
}
|
||||
|
||||
if (albumId) {
|
||||
uploadAssetsStore.updateAsset(deviceAssetId, { message: $t('asset_adding_to_album') });
|
||||
uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_adding_to_album') });
|
||||
await addAssetsToAlbum(albumId, [responseData.id], false);
|
||||
uploadAssetsStore.updateAsset(deviceAssetId, { message: $t('asset_added_to_album') });
|
||||
uploadAssetsStore.updateItem(deviceAssetId, { message: $t('asset_added_to_album') });
|
||||
}
|
||||
|
||||
uploadAssetsStore.updateAsset(deviceAssetId, {
|
||||
uploadAssetsStore.updateItem(deviceAssetId, {
|
||||
state: responseData.status === AssetMediaStatus.Duplicate ? UploadState.DUPLICATED : UploadState.DONE,
|
||||
assetId: responseData.id,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
uploadAssetsStore.removeUploadAsset(deviceAssetId);
|
||||
}, 1000);
|
||||
if (responseData.status !== AssetMediaStatus.Duplicate) {
|
||||
setTimeout(() => {
|
||||
uploadAssetsStore.removeItem(deviceAssetId);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
return responseData.id;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_upload_file'));
|
||||
const reason = getServerErrorMessage(error) || error;
|
||||
uploadAssetsStore.updateAsset(deviceAssetId, { state: UploadState.ERROR, error: reason });
|
||||
uploadAssetsStore.track('error');
|
||||
uploadAssetsStore.updateItem(deviceAssetId, { state: UploadState.ERROR, error: reason });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue