Repository: gradio-app/gradio
Pull request: File component: list multiple files and allow for download #1446
# Description
List each file individually and allow for download in a scroll view.

Before:
<img width="608" alt="Screen Shot 2022-07-08 at 9 12 40 AM" src="https://user-images.githubusercontent.com/12725292/177937748-d6ff995a-3d0b-45e1-8f12-0732d2134392.png">

After:
<img width="615" alt="Screen Shot 2022-07-11 at 2 38 44 PM" src="https://user-images.githubusercontent.com/12725292/178267846-6532ac1a-f574-4e8e-a822-9ffcfe9a444a.png">


Closes: #1446

# Checklist:

- [ ] I have performed a self-review of my own code
- [ ] My code follows the style guidelines of this project
- [ ] I have commented my code in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes


Problem statement:
File component: list multiple files and allow for download
As of now, when `file_count` is set to multiple; the file component does not list all uploaded files and does not allow each to be downloaded.

- [x] I have searched to see if a similar issue already exists.


**Is your feature request related to a problem? Please describe.**  
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

**Describe the solution you'd like**  
A clear and concise description of what you want to happen.

**Additional context**  
Add any other context or screenshots about the feature request here.

Hints from issue discussion:

Commit message: format

Patch to review:
diff --git a/demo/zip_two_files/run.py b/demo/zip_two_files/run.py
index 29c1b015ed..3702e35a93 100644
--- a/demo/zip_two_files/run.py
+++ b/demo/zip_two_files/run.py
@@ -13,7 +13,7 @@ def zip_two_files(file1, file2):
 
 demo = gr.Interface(
     zip_two_files,
-    ["file", "file"],
+    [gr.File(file_count="multiple"), gr.File(file_count="multiple")],
     "file",
     examples=[
         [os.path.join(os.path.dirname(__file__),"files/titanic.csv"), 
diff --git a/gradio/components.py b/gradio/components.py
index 1c5afffe26..fb1764ae6b 100644
--- a/gradio/components.py
+++ b/gradio/components.py
@@ -2327,7 +2327,10 @@ def process_single_file(f):
             else:
                 return process_single_file(x)
         else:
-            return [process_single_file(f) for f in x]
+            if isinstance(x, list):
+                return [process_single_file(f) for f in x]
+            else:
+                return process_single_file(x)
 
     def save_flagged(self, dir, label, data, encryption_key):
         """
diff --git a/ui/packages/app/src/components/File/File.svelte b/ui/packages/app/src/components/File/File.svelte
index ae593bfb50..d83c9f8d51 100644
--- a/ui/packages/app/src/components/File/File.svelte
+++ b/ui/packages/app/src/components/File/File.svelte
@@ -53,6 +53,6 @@
 			upload_text={$_("interface.click_to_upload")}
 		/>
 	{:else}
-		<File value={_value} {label} {show_label} />
+		<File value={_value} {label} {show_label} {file_count} />
 	{/if}
 </Block>
diff --git a/ui/packages/file/src/File.svelte b/ui/packages/file/src/File.svelte
index 9c297c49fb..bacf946ff4 100644
--- a/ui/packages/file/src/File.svelte
+++ b/ui/packages/file/src/File.svelte
@@ -11,27 +11,52 @@
 	export let value: FileData | null;
 	export let label: string;
 	export let show_label: boolean;
+	export let file_count: string;
 </script>
 
 <BlockLabel {show_label} Icon={File} label={label || "File"} />
 
 {#if value}
 	<div
-		class="file-preview w-full flex flex-row justify-between overflow-y-auto mt-7 dark:text-slate-200"
+		class="file-preview overflow-y-scroll w-full max-h-60 flex flex-col justify-between mt-7 mb-7 dark:text-slate-200"
 	>
-		<div class="file-name p-2">
-			{display_file_name(value)}
-		</div>
-		<div class="file-size  p-2">
-			{display_file_size(value)}
-		</div>
-		<div class="file-size p-2 hover:underline">
-			<a
-				href={download_files(value)}
-				download
-				class="text-indigo-600 hover:underline dark:text-indigo-300">Download</a
-			>
-		</div>
+		{#if Array.isArray(value)}
+			{#each value as file}
+				<div class="flex flex-row w-1/2 justify-between">
+					<div class="file-name p-2">
+						{display_file_name(file)}
+					</div>
+					<div class="file-size  p-2">
+						{display_file_size(file)}
+					</div>
+					<div class="file-size p-2 hover:underline">
+						<a
+							href={download_files(file)}
+							download
+							class="text-indigo-600 hover:underline dark:text-indigo-300"
+							>Download</a
+						>
+					</div>
+				</div>
+			{/each}
+		{:else}
+			<div class="flex flex-row">
+				<div class="file-name p-2">
+					{display_file_name(value)}
+				</div>
+				<div class="file-size  p-2">
+					{display_file_size(value)}
+				</div>
+				<div class="file-size p-2 hover:underline">
+					<a
+						href={download_files(value)}
+						download
+						class="text-indigo-600 hover:underline dark:text-indigo-300"
+						>Download</a
+					>
+				</div>
+			</div>
+		{/if}
 	</div>
 {:else}
 	<div class="h-full min-h-[15rem] flex justify-center items-center">
diff --git a/ui/packages/file/src/FileUpload.svelte b/ui/packages/file/src/FileUpload.svelte
index a885cb83ad..3af23079b2 100644
--- a/ui/packages/file/src/FileUpload.svelte
+++ b/ui/packages/file/src/FileUpload.svelte
@@ -58,21 +58,45 @@
 	</Upload>
 {:else}
 	<div
-		class="file-preview w-full flex flex-row justify-between overflow-y-auto mt-7 dark:text-slate-200"
+		class="file-preview overflow-y-scroll w-full max-h-60 flex flex-col justify-between mt-7 mb-7 dark:text-slate-200"
 	>
 		<ModifyUpload on:clear={handle_clear} absolute />
-		<div class="file-name p-2">
-			{display_file_name(value)}
-		</div>
-		<div class="file-size  p-2">
-			{display_file_size(value)}
-		</div>
-		<div class="file-size p-2 hover:underline">
-			<a
-				href={download_files(value)}
-				download
-				class="text-indigo-600 hover:underline dark:text-indigo-300">Download</a
-			>
-		</div>
+		{#if Array.isArray(value)}
+			{#each value as file}
+				<div class="flex flex-row w-1/2 justify-between">
+					<div class="file-name p-2">
+						{display_file_name(file)}
+					</div>
+					<div class="file-size  p-2">
+						{display_file_size(file)}
+					</div>
+					<div class="file-size p-2 hover:underline">
+						<a
+							href={download_files(file)}
+							download
+							class="text-indigo-600 hover:underline dark:text-indigo-300"
+							>Download</a
+						>
+					</div>
+				</div>
+			{/each}
+		{:else}
+			<div class="flex flex-row">
+				<div class="file-name p-2">
+					{display_file_name(value)}
+				</div>
+				<div class="file-size  p-2">
+					{display_file_size(value)}
+				</div>
+				<div class="file-size p-2 hover:underline">
+					<a
+						href={download_files(value)}
+						download
+						class="text-indigo-600 hover:underline dark:text-indigo-300"
+						>Download</a
+					>
+				</div>
+			</div>
+		{/if}
 	</div>
 {/if}
diff --git a/ui/packages/file/src/utils.ts b/ui/packages/file/src/utils.ts
index f53d7e8962..64a4a84c68 100644
--- a/ui/packages/file/src/utils.ts
+++ b/ui/packages/file/src/utils.ts
@@ -15,34 +15,18 @@ export const display_file_name = (
 	value: FileData | Array<FileData>
 ): string => {
 	var str: string;
-	if (Array.isArray(value)) {
-		if (value.length > 1) {
-			return value.length + " files";
-		} else {
-			str = value[0].name;
-		}
-	} else {
-		str = value.name;
-	}
+	str = value.name;
 	if (str.length > 30) {
 		return `${str.substr(0, 30)}...`;
 	} else return str;
 };
 
 export const download_files = (value: FileData | Array<FileData>): string => {
-	return Array.isArray(value) ? value[0].data : value.data;
+	return value.data;
 };
 
 export const display_file_size = (
 	value: FileData | Array<FileData>
 ): string => {
-	var total_size = 0;
-	if (Array.isArray(value)) {
-		for (var file of value) {
-			total_size += file.size;
-		}
-	} else {
-		total_size = value.size;
-	}
-	return prettyBytes(total_size || 0);
+	return prettyBytes(value.size || 0);
 };
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index 42cb3a5a0a..4460f7a179 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -1,4 +1,4 @@
-lockfileVersion: 5.4
+lockfileVersion: 5.3
 
 importers:
 
@@ -41,7 +41,7 @@ importers:
       '@tailwindcss/forms': 0.5.0_tailwindcss@3.0.23
       '@testing-library/dom': 8.11.3
       '@testing-library/svelte': 3.1.0_svelte@3.47.0
-      '@testing-library/user-event': 13.5.0_gzufz4q333be4gqfrvipwvqt6a
+      '@testing-library/user-event': 13.5.0_@testing-library+dom@8.11.3
       autoprefixer: 10.4.4_postcss@8.4.6
       babylonjs: 4.2.2
       babylonjs-loaders: 4.2.2
@@ -53,13 +53,13 @@ importers:
       postcss: 8.4.6
       postcss-nested: 5.0.6_postcss@8.4.6
       prettier: 2.6.2
-      prettier-plugin-svelte: 2.7.0_sqtt6dzjlskmywoml5ykunxlce
+      prettier-plugin-svelte: 2.7.0_prettier@2.6.2+svelte@3.47.0
       sirv: 2.0.2
       sirv-cli: 2.0.2
       svelte: 3.47.0
-      svelte-check: 2.7.0_xf35j26wmvzqzzfwect6yhmkcm
+      svelte-check: 2.7.0_postcss@8.4.6+svelte@3.47.0
       svelte-i18n: 3.3.13_svelte@3.47.0
-      svelte-preprocess: 4.10.6_xf35j26wmvzqzzfwect6yhmkcm
+      svelte-preprocess: 4.10.6_postcss@8.4.6+svelte@3.47.0
       tailwindcss: 3.0.23_autoprefixer@10.4.4
       tinyspy: 0.3.0
       vite: 2.9.5
@@ -127,7 +127,7 @@ importers:
       '@gradio/video': link:../video
       mime-types: 2.1.34
       playwright: 1.22.2
-      svelte-i18n: 3.3.13
+      svelte-i18n: 3.3.13_svelte@3.47.0
 
   packages/atoms:
     specifiers:
@@ -379,13 +379,13 @@ importers:
       '@gradio/upload': link:../upload
       '@gradio/video': link:../video
     devDependencies:
-      '@sveltejs/adapter-auto': 1.0.0-next.53
-      '@sveltejs/kit': 1.0.0-next.318
+      '@sveltejs/adapter-auto': 1.0.0-next.54
+      '@sveltejs/kit': 1.0.0-next.318_svelte@3.47.0
       autoprefixer: 10.4.2_postcss@8.4.6
       postcss: 8.4.6
       postcss-load-config: 3.1.1
-      svelte-check: 2.4.1_2y4otvh2n6klv6metqycpfiuzy
-      svelte-preprocess: 4.10.2_bw7ic75prjd4umr4fb55sbospu
+      svelte-check: 2.4.1_1fac02bafd0682bb46b2470c9627a189
+      svelte-preprocess: 4.10.2_7ea8a6d5efea3309cd53b5ce515684ac
       tailwindcss: 3.0.23_autoprefixer@10.4.2
       tslib: 2.3.1
       typescript: 4.5.5
@@ -1076,10 +1076,10 @@ packages:
       estree-walker: 2.0.2
       picomatch: 2.3.1
 
-  /@sveltejs/adapter-auto/1.0.0-next.53:
-    resolution: {integrity: sha512-LyaeU0rkcymGWvV/3K26AZxqG/+ZQHwa+hrx3xsbmOykjQ2WQPTXRVwmH23zV4A5ABvni76LRMsQOoqWzP3G9Q==}
+  /@sveltejs/adapter-auto/1.0.0-next.54:
+    resolution: {integrity: sha512-GT3LmE+Qy4HcMO/CZJJTLWaLrchufo1Z+BVec79u02GNM16y7Q8Jbs1gVg4EKMHV8AqtDxYFsb0j5gUlP8Ol9g==}
     dependencies:
-      '@sveltejs/adapter-cloudflare': 1.0.0-next.24
+      '@sveltejs/adapter-cloudflare': 1.0.0-next.25
       '@sveltejs/adapter-netlify': 1.0.0-next.66
       '@sveltejs/adapter-vercel': 1.0.0-next.59
     transitivePeerDependencies:
@@ -1087,8 +1087,8 @@ packages:
       - supports-color
     dev: true
 
-  /@sveltejs/adapter-cloudflare/1.0.0-next.24:
-    resolution: {integrity: sha512-g1QSrjWYjM6sfJB+pQn52EIfbVFjpk23GYsj5PLt2Gi3zRNfLRbpkFkPeyAOZbAfT4k/9lUqfLW+pkh+W3yxlg==}
+  /@sveltejs/adapter-cloudflare/1.0.0-next.25:
+    resolution: {integrity: sha512-fhjH4LInepYxJ6ubuNO38sJeor04cUBKMPF5hpwrXLhKHrYTVwq1Oi7xNVbnrrIQzGRyiHOCvMy8E/MNnO3xLw==}
     dependencies:
       esbuild: 0.14.42
       worktop: 0.8.0-next.14
@@ -1113,15 +1113,16 @@ packages:
       - supports-color
     dev: true
 
-  /@sveltejs/kit/1.0.0-next.318:
+  /@sveltejs/kit/1.0.0-next.318_svelte@3.47.0:
     resolution: {integrity: sha512-/M/XNvEqK71KCGro1xLuiUuklsMPe+G5DiVMs39tpfFIFhH4oCzAt+YBaIZDKORogGz3QDaYc5BV+eFv9E5cyw==}
     engines: {node: '>=14.13'}
     hasBin: true
     peerDependencies:
       svelte: ^3.44.0
     dependencies:
-      '@sveltejs/vite-plugin-svelte': 1.0.0-next.41_vite@2.9.5
+      '@sveltejs/vite-plugin-svelte': 1.0.0-next.41_svelte@3.47.0+vite@2.9.5
       sade: 1.8.1
+      svelte: 3.47.0
       vite: 2.9.5
     transitivePeerDependencies:
       - diff-match-patch
@@ -1131,7 +1132,7 @@ packages:
       - supports-color
     dev: true
 
-  /@sveltejs/vite-plugin-svelte/1.0.0-next.41_vite@2.9.5:
+  /@sveltejs/vite-plugin-svelte/1.0.0-next.41_svelte@3.47.0+vite@2.9.5:
     resolution: {integrity: sha512-2kZ49mpi/YW1PIPvKaJNSSwIFgmw9QUf1+yaNa4U8yJD6AsfSHXAU3goscWbi1jfWnSg2PhvwAf+bvLCdp2F9g==}
     engines: {node: ^14.13.1 || >= 16}
     peerDependencies:
@@ -1146,7 +1147,8 @@ packages:
       debug: 4.3.4
       kleur: 4.1.4
       magic-string: 0.26.1
-      svelte-hmr: 0.14.11
+      svelte: 3.47.0
+      svelte-hmr: 0.14.11_svelte@3.47.0
       vite: 2.9.5
     transitivePeerDependencies:
       - supports-color
@@ -1222,7 +1224,7 @@ packages:
       svelte: 3.47.0
     dev: false
 
-  /@testing-library/user-event/13.5.0_gzufz4q333be4gqfrvipwvqt6a:
+  /@testing-library/user-event/13.5.0_@testing-library+dom@8.11.3:
     resolution: {integrity: sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==}
     engines: {node: '>=10', npm: '>=6'}
     peerDependencies:
@@ -3521,7 +3523,7 @@ packages:
       picocolors: 1.0.0
       source-map-js: 1.0.2
 
-  /prettier-plugin-svelte/2.7.0_sqtt6dzjlskmywoml5ykunxlce:
+  /prettier-plugin-svelte/2.7.0_prettier@2.6.2+svelte@3.47.0:
     resolution: {integrity: sha512-fQhhZICprZot2IqEyoiUYLTRdumULGRvw0o4dzl5jt0jfzVWdGqeYW27QTWAeXhoupEZJULmNoH3ueJwUWFLIA==}
     peerDependencies:
       prettier: ^1.16.4 || ^2.0.0
@@ -3982,7 +3984,7 @@ packages:
     resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
     engines: {node: '>= 0.4'}
 
-  /svelte-check/2.4.1_2y4otvh2n6klv6metqycpfiuzy:
+  /svelte-check/2.4.1_1fac02bafd0682bb46b2470c9627a189:
     resolution: {integrity: sha512-xhf3ShP5rnRwBokrgTBJ/0cO9QIc1DAVu1NWNRTfCDsDBNjGmkS3HgitgUadRuoMKj1+irZR/yHJ+Uqobnkbrw==}
     hasBin: true
     peerDependencies:
@@ -3995,7 +3997,8 @@ packages:
       picocolors: 1.0.0
       sade: 1.8.1
       source-map: 0.7.3
-      svelte-preprocess: 4.10.2_bw7ic75prjd4umr4fb55sbospu
+      svelte: 3.47.0
+      svelte-preprocess: 4.10.2_7ea8a6d5efea3309cd53b5ce515684ac
       typescript: 4.5.5
     transitivePeerDependencies:
       - '@babel/core'
@@ -4010,7 +4013,7 @@ packages:
       - sugarss
     dev: true
 
-  /svelte-check/2.7.0_xf35j26wmvzqzzfwect6yhmkcm:
+  /svelte-check/2.7.0_postcss@8.4.6+svelte@3.47.0:
     resolution: {integrity: sha512-GrvG24j0+i8AOm0k0KyJ6Dqc+TAR2yzB7rtS4nljHStunVxCTr/1KYlv4EsOeoqtHLzeWMOd5D2O6nDdP/yw4A==}
     hasBin: true
     peerDependencies:
@@ -4023,7 +4026,7 @@ packages:
       sade: 1.8.1
       source-map: 0.7.3
       svelte: 3.47.0
-      svelte-preprocess: 4.10.6_bwx7acail2u6vixfjbtw6klhdi
+      svelte-preprocess: 4.10.6_0daff008085ea9eaa2e548676f29671a
       typescript: 4.5.5
     transitivePeerDependencies:
       - '@babel/core'
@@ -4038,13 +4041,6 @@ packages:
       - sugarss
     dev: false
 
-  /svelte-hmr/0.14.11:
-    resolution: {integrity: sha512-R9CVfX6DXxW1Kn45Jtmx+yUe+sPhrbYSUp7TkzbW0jI5fVPn6lsNG9NEs5dFg5qRhFNAoVdRw5qQDLALNKhwbQ==}
-    engines: {node: ^12.20 || ^14.13.1 || >= 16}
-    peerDependencies:
-      svelte: '>=3.19.0'
-    dev: true
-
   /svelte-hmr/0.14.11_svelte@3.47.0:
     resolution: {integrity: sha512-R9CVfX6DXxW1Kn45Jtmx+yUe+sPhrbYSUp7TkzbW0jI5fVPn6lsNG9NEs5dFg5qRhFNAoVdRw5qQDLALNKhwbQ==}
     engines: {node: ^12.20 || ^14.13.1 || >= 16}
@@ -4052,21 +4048,6 @@ packages:
       svelte: '>=3.19.0'
     dependencies:
       svelte: 3.47.0
-    dev: false
-
-  /svelte-i18n/3.3.13:
-    resolution: {integrity: sha512-RQM+ys4+Y9ztH//tX22H1UL2cniLNmIR+N4xmYygV6QpQ6EyQvloZiENRew8XrVzfvJ8HaE8NU6/yurLkl7z3g==}
-    engines: {node: '>= 11.15.0'}
-    hasBin: true
-    peerDependencies:
-      svelte: ^3.25.1
-    dependencies:
-      deepmerge: 4.2.2
-      estree-walker: 2.0.2
-      intl-messageformat: 9.11.4
-      sade: 1.8.1
-      tiny-glob: 0.2.9
-    dev: false
 
   /svelte-i18n/3.3.13_svelte@3.47.0:
     resolution: {integrity: sha512-RQM+ys4+Y9ztH//tX22H1UL2cniLNmIR+N4xmYygV6QpQ6EyQvloZiENRew8XrVzfvJ8HaE8NU6/yurLkl7z3g==}
@@ -4083,7 +4064,7 @@ packages:
       tiny-glob: 0.2.9
     dev: false
 
-  /svelte-preprocess/4.10.2_bw7ic75prjd4umr4fb55sbospu:
+  /svelte-preprocess/4.10.2_7ea8a6d5efea3309cd53b5ce515684ac:
     resolution: {integrity: sha512-aPpkCreSo8EL/y8kJSa1trhiX0oyAtTjlNNM7BNjRAsMJ8Yy2LtqHt0zyd4pQPXt+D4PzbO3qTjjio3kwOxDlA==}
     engines: {node: '>= 9.11.2'}
     requiresBuild: true
@@ -4132,10 +4113,11 @@ packages:
       postcss-load-config: 3.1.1
       sorcery: 0.10.0
       strip-indent: 3.0.0
+      svelte: 3.47.0
       typescript: 4.5.5
     dev: true
 
-  /svelte-preprocess/4.10.6_bwx7acail2u6vixfjbtw6klhdi:
+  /svelte-preprocess/4.10.6_0daff008085ea9eaa2e548676f29671a:
     resolution: {integrity: sha512-I2SV1w/AveMvgIQlUF/ZOO3PYVnhxfcpNyGt8pxpUVhPfyfL/CZBkkw/KPfuFix5FJ9TnnNYMhACK3DtSaYVVQ==}
     engines: {node: '>= 9.11.2'}
     requiresBuild: true
@@ -4187,7 +4169,7 @@ packages:
       typescript: 4.5.5
     dev: false
 
-  /svelte-preprocess/4.10.6_xf35j26wmvzqzzfwect6yhmkcm:
+  /svelte-preprocess/4.10.6_postcss@8.4.6+svelte@3.47.0:
     resolution: {integrity: sha512-I2SV1w/AveMvgIQlUF/ZOO3PYVnhxfcpNyGt8pxpUVhPfyfL/CZBkkw/KPfuFix5FJ9TnnNYMhACK3DtSaYVVQ==}
     engines: {node: '>= 9.11.2'}
     requiresBuild: true


Code review comments:
diff_hunk: @@ -13,7 +13,7 @@ def zip_two_files(file1, file2):
 
 demo = gr.Interface(
     zip_two_files,
-    ["file", "file"],
+    [gr.File(file_count="multiple"), gr.File(file_count="multiple")],
path: demo/zip_two_files/run.py
text: @user1:
Why did we change both of the input components here to accept multiple files each? We should either have a single component that can accept multiple files (and change the name of this demo) or just change it back to two file components that each accept a single file

@author:
Pushed this by accident, when I was testing the component.