Skip to content

Commit b9334ab

Browse files
committed
feat: support multiple file uploads in bucket
Allow selecting and dropping multiple files at once when uploading to a storage bucket. Files are uploaded in parallel with a concurrency limit of 5 to avoid overwhelming the browser and server.
1 parent 35c23ea commit b9334ab

3 files changed

Lines changed: 340 additions & 201 deletions

File tree

src/lib/stores/uploader.ts

Lines changed: 76 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ const temporaryFunctions = (region: string, projectId: string) => {
3939
return new Functions(clientProject);
4040
};
4141

42+
const MAX_CONCURRENT_UPLOADS = 5;
43+
4244
const createUploader = () => {
4345
const { subscribe, set, update } = writable<Uploader>({
4446
isOpen: false,
@@ -58,6 +60,78 @@ const createUploader = () => {
5860
});
5961
};
6062

63+
const uploadFile = async (
64+
region: string,
65+
projectId: string,
66+
bucketId: string,
67+
id: string,
68+
file: File,
69+
permissions: string[]
70+
) => {
71+
const newFile: UploaderFile = {
72+
$id: id,
73+
resourceId: bucketId,
74+
name: file.name,
75+
size: file.size,
76+
progress: 0,
77+
status: 'pending'
78+
};
79+
update((n) => {
80+
n.isOpen = true;
81+
n.isCollapsed = false;
82+
n.files.unshift(newFile);
83+
return n;
84+
});
85+
const uploadedFile = await temporaryStorage(region, projectId).createFile({
86+
bucketId,
87+
fileId: id ?? ID.unique(),
88+
file,
89+
permissions,
90+
onProgress: (progress) => {
91+
newFile.$id = progress.$id;
92+
newFile.progress = progress.progress;
93+
newFile.status = progress.progress === 100 ? 'success' : 'pending';
94+
updateFile(progress.$id, newFile);
95+
}
96+
});
97+
newFile.$id = uploadedFile.$id;
98+
newFile.progress = 100;
99+
newFile.status = 'success';
100+
updateFile(newFile.$id, newFile);
101+
};
102+
103+
const uploadFiles = async (
104+
region: string,
105+
projectId: string,
106+
bucketId: string,
107+
files: { id: string; file: File }[],
108+
permissions: string[]
109+
) => {
110+
const results: PromiseSettledResult<void>[] = [];
111+
const executing = new Set<Promise<void>>();
112+
113+
for (const { id, file } of files) {
114+
const task = uploadFile(region, projectId, bucketId, id, file, permissions).then(
115+
() => {
116+
results.push({ status: 'fulfilled', value: undefined });
117+
executing.delete(task);
118+
},
119+
(reason) => {
120+
results.push({ status: 'rejected', reason });
121+
executing.delete(task);
122+
}
123+
);
124+
executing.add(task);
125+
126+
if (executing.size >= MAX_CONCURRENT_UPLOADS) {
127+
await Promise.race(executing);
128+
}
129+
}
130+
131+
await Promise.all(executing);
132+
return results;
133+
};
134+
61135
return {
62136
subscribe,
63137

@@ -78,45 +152,8 @@ const createUploader = () => {
78152
isCollapsed: false,
79153
files: []
80154
}),
81-
uploadFile: async (
82-
region: string,
83-
projectId: string,
84-
bucketId: string,
85-
id: string,
86-
file: File,
87-
permissions: string[]
88-
) => {
89-
const newFile: UploaderFile = {
90-
$id: id,
91-
resourceId: bucketId,
92-
name: file.name,
93-
size: file.size,
94-
progress: 0,
95-
status: 'pending'
96-
};
97-
update((n) => {
98-
n.isOpen = true;
99-
n.isCollapsed = false;
100-
n.files.unshift(newFile);
101-
return n;
102-
});
103-
const uploadedFile = await temporaryStorage(region, projectId).createFile({
104-
bucketId,
105-
fileId: id ?? ID.unique(),
106-
file,
107-
permissions,
108-
onProgress: (progress) => {
109-
newFile.$id = progress.$id;
110-
newFile.progress = progress.progress;
111-
newFile.status = progress.progress === 100 ? 'success' : 'pending';
112-
updateFile(progress.$id, newFile);
113-
}
114-
});
115-
newFile.$id = uploadedFile.$id;
116-
newFile.progress = 100;
117-
newFile.status = 'success';
118-
updateFile(newFile.$id, newFile);
119-
},
155+
uploadFile,
156+
uploadFiles,
120157
uploadSiteDeployment: async ({
121158
siteId,
122159
code,

0 commit comments

Comments
 (0)