Skip to content

Commit

Permalink
Add preview
Browse files Browse the repository at this point in the history
  • Loading branch information
AdityaHegde committed Feb 3, 2025
1 parent 6ea8c93 commit c980acc
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 49 deletions.
114 changes: 65 additions & 49 deletions web-common/src/components/forms/FileInput.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
<script lang="ts">
import LoadingSpinner from "@rilldata/web-common/components/icons/LoadingSpinner.svelte";
import Viz from "@rilldata/web-common/components/icons/Viz.svelte";
import Attachment from "@rilldata/web-common/components/icons/Attachment.svelte";
import { extractFileName } from "@rilldata/web-common/features/entity-management/file-path-utils";
import { slide } from "svelte/transition";
export let value: string | string[] | undefined = undefined;
export let error: string | Record<string | number, string[]> | undefined =
Expand All @@ -21,37 +18,49 @@
$: uploadErrors = {};
let fileInput: HTMLInputElement;
// maintain a list of filenames to show in error messages.
// since the final uploaded url set in `value` is usually not the same this is needed.
let fileNames: string[] = value
? multiple
? (value as string[])
: [value as string]
: [];
$: hasValue = values.length > 0 || Object.values(uploading).some((u) => u);
function uploadFiles(files: FileList) {
uploading = {};
uploadErrors = {};
if (multiple) {
value = new Array(files.length).fill("");
fileNames = new Array<string>(files.length).fill("");
} else {
value = "";
fileNames = [""];
}
for (let i = 0; i < files.length; i++) {
void uploadFileWrapper(files[i], i);
}
}
async function uploadFileWrapper(file: File, i: number) {
setFileUrl(file.name, i);
uploading[i] = true;
try {
fileNames[i] = file.name;
const url = await uploadFile(file);
setFileUrl(url, i);
if (multiple) {
if (value === undefined) {
value = [];
}
(value as string[])[i] = url;
} else {
value = url;
}
} catch (err) {
uploadErrors[i] = err.message;
}
uploading[i] = false;
}
function setFileUrl(fileUrl: string, i: number) {
if (multiple) {
if (value === undefined) {
value = [];
}
(value as string[])[i] = fileUrl;
} else {
value = fileUrl;
}
}
function handleInput() {
if (!fileInput.files) return;
uploadFiles(fileInput.files);
Expand All @@ -65,6 +74,12 @@
}
let dragOver = false;
$: errorMessages = Object.values({
...(errors as Record<string, any>),
...uploadErrors,
})
.map((e, i) => (fileNames[i] && e ? `${fileNames[i]}:${e}` : ""))
.filter(Boolean);
</script>

<div class="container grid">
Expand All @@ -78,41 +93,42 @@
class:bg-neutral-100={!dragOver}
class:bg-primary-100={dragOver}
>
<Viz size="28px" class="text-gray-400 pointer-events-none" />
<div class="container-flex-col pointer-events-none">
<span class="upload-title"> Upload an image </span>
{#if multiple}
<span class="upload-description">
Support for a single or bulk upload.
</span>
{/if}
</div>
</button>
{#if values}
{#each values as val, i (i)}
{@const isUploading = !!uploading[i]}
{@const hasError = (!!uploadErrors[i] || !!errors?.[i]) && !isUploading}
<div class="container-flex-col">
<div class="file-entry">
{#if isUploading}
<LoadingSpinner size="14px" />
{:else}
<Attachment size="14px" />
{#if hasValue}
<div class="upload-preview">
{#each fileNames as _, i (i)}
{@const isUploading = !!uploading[i]}
{@const hasError =
(!!uploadErrors[i] || !!errors?.[i]) && !isUploading}
{@const val = values[i]}
{#if (val || isUploading) && !hasError}
<div class="border border-neutral-400 p-1">
{#if isUploading}
<LoadingSpinner size="36px" />
{:else}
<img src={val} alt="upload" class="h-10 w-fit" />
{/if}
</div>
{/if}
<span
class:text-primary-500={!hasError}
class:text-red-600={hasError}
>
{extractFileName(val)}
{/each}
</div>
{:else}
<Viz size="28px" class="text-gray-400 pointer-events-none" />
<div class="container-flex-col pointer-events-none">
<span class="upload-title"> Upload an image </span>
{#if multiple}
<span class="upload-description">
Support for a single or bulk upload.
</span>
</div>
{#if hasError}
<div in:slide={{ duration: 200 }} class="error">
<div>{uploadErrors[i] ?? errors[i]}</div>
</div>
{/if}
</div>
{/each}
{/if}
</button>
{#if errorMessages.length > 0}
<div class="error">
{#each errorMessages as errorMessage, i (i)}
<div>{errorMessage}</div>
{/each}
</div>
{/if}
<input
type="file"
Expand Down Expand Up @@ -146,8 +162,8 @@
@apply text-xs font-normal text-gray-400;
}
.file-entry {
@apply flex flex-row items-center gap-x-1.5;
.upload-preview {
@apply flex flex-wrap w-full gap-x-1 items-center justify-center pointer-events-none px-4;
}
.error {
Expand Down
40 changes: 40 additions & 0 deletions web-local/src/routes/(misc)/file-input/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script lang="ts">
import FileInput from "@rilldata/web-common/components/forms/FileInput.svelte";
import { asyncWait } from "@rilldata/web-common/lib/waitUtils";
const Assets = [
"http://localhost:8080/v1/assets/681780a1-12c1-4bfc-bdec-9b9acb8b64a3/download",
"http://localhost:8080/v1/assets/87e4afd6-f7ce-4b65-b554-b33962d59e5c/download",
"http://localhost:8080/v1/assets/4bc1415c-df7d-4172-8c90-1b4240b03582/download",
"http://localhost:8080/v1/assets/dbb6ec8b-28f8-44cf-850b-01d1a50f745c/download",
"http://localhost:8080/v1/assets/d6ea0113-634d-41d4-9540-3cd2f0d4f84c/download",
"http://localhost:8080/v1/assets/e9f7095d-dd4f-4c5c-a88d-c18ec4cf0038/download",
];
let i = 0;
let singleInput: string =
"http://localhost:8080/v1/assets/681780a1-12c1-4bfc-bdec-9b9acb8b64a3/download";
let multipleInput: string[] = [];

Check failure on line 17 in web-local/src/routes/(misc)/file-input/+page.svelte

View workflow job for this annotation

GitHub Actions / build

'multipleInput' is assigned a value but never used
async function uploadFile() {
await asyncWait(2500);
i = (i + 1) % (Assets.length * 2);
const assetLink = Assets[Math.floor(i / 2)];
if (i % 2 === 0) {
throw new Error("Error loading " + assetLink);
}
return assetLink;
}
</script>

<div class="flex flex-col gap-y-5 p-5">
<div class="flex flex-col gap-y-2">
<div>Single: {singleInput}</div>
<FileInput bind:value={singleInput} {uploadFile} />
</div>

<!-- <div class="flex flex-col gap-y-2">-->
<!-- <div>Multi: {multipleInput.join(",")}</div>-->
<!-- <FileInput bind:value={multipleInput} multiple {uploadFile} />-->
<!-- </div>-->
</div>

0 comments on commit c980acc

Please sign in to comment.