8787 <div id= " fileList" class= " file-list" ></div>
8888
8989 <div class= " text-center mt-4" >
90- <button class= " btn btn-secondary" disabled >
91- Upload (TODO)
90+ <button class= " btn btn-secondary" onClick = " initUpload(); " id = " uploadbutton " >
91+ Upload
9292 </button>
9393 </div>
9494
101101
102102<script>
103103 const fileInput = document.getElementById ('fileInput');
104- const fileList = document.getElementById ('fileList');
105-
106- fileInput.addEventListener ('change', () = > {
107- fileList.innerHTML = '';
108-
109- Array.from (fileInput.files ).forEach (file = > {
110- const item = document.createElement ('div');
111- item.className = 'file-item';
112-
113- const name = document.createElement ('span');
114- name.textContent = file.name ;
115-
116- const size = document.createElement ('span');
117- size.className = 'file-size';
118- size.textContent = formatSize(file.size );
119-
120- const removeBtn = document.createElement ('button');
121- removeBtn.type = 'button';
122- removeBtn.title = " Remove" ;
123- removeBtn.className = 'btn btn-sm btn-link text-light p-0 remove-entry-btn';
124- removeBtn.innerHTML = '<i class= " bi bi-x-circle" ></i>';
125-
126- removeBtn.addEventListener ('click', () = > {
127- item.remove ();
128- });
104+ const fileList = document.getElementById ('fileList');
105+
106+ const filesMap = new Map();
107+
108+ fileInput.addEventListener ('change', () = > {
109+ Array.from (fileInput.files ).forEach (file = > {
110+ const uuid = getUuid();
111+
112+ const item = document.createElement ('div');
113+ item.className = 'file-item';
114+ item.dataset.uuid = uuid;
115+
116+ const name = document.createElement ('span');
117+ name.textContent = file.name ;
118+
119+ const progressText = document.createElement ('span');
120+ progressText.className = 'file-size';
121+ progressText.textContent = '0%';
122+
123+ const progressBar = document.createElement ('progress');
124+ progressBar.max = file.size ;
125+ progressBar.value = 0;
126+ progressBar.style.width = '120px';
127+ progressBar.style.marginRight = '10px';
128+
129+ const size = document.createElement ('span');
130+ size.className = 'file-size';
131+ size.textContent = formatSize(file.size );
132+
133+ const removeBtn = document.createElement ('button');
134+ removeBtn.type = 'button';
135+ removeBtn.title = 'Remove';
136+ removeBtn.className = 'btn btn-sm btn-link text-light p-0';
137+ removeBtn.innerHTML = '<i class= " bi bi-x-circle" ></i>';
138+
139+ removeBtn.addEventListener ('click', () = > {
140+ filesMap.get (uuid).removed = true;
141+ item.remove ();
142+ });
129143
130- item.appendChild (name);
131- item.appendChild (size);
132- item.appendChild (removeBtn);
133- fileList.appendChild (item);
144+ item.append (name, progressBar, progressText, size, removeBtn);
145+ fileList.appendChild (item);
146+
147+ filesMap.set (uuid, {
148+ uuid,
149+ file,
150+ removed: false,
151+ elements: { progressBar, progressText, removeBtn, item }
134152 });
135153 });
136154
155+ // Allow re-selecting same files
156+ fileInput.value = '';
157+ });
158+
159+
137160 function formatSize(bytes) {
138161 const units = ['B', 'KB', 'MB', 'GB'];
139162 let i = 0;
@@ -154,26 +177,24 @@ const COMPLETE_URL = "./api/chunk/uploadRequestComplete";
154177const FILE_REQUEST_ID = " {{ .FileRequest.Id }}" ;
155178const API_KEY = " {{ .FileRequest.ApiKey }}" ;
156179
180+ function initUpload() {
181+ const btn = document.getElementById (" uploadbutton" );
182+ btn.disabled = true;
183+
184+ startUpload()
185+ .catch (err = > console.error (err))
186+ .finally (() = > btn.disabled = false);
187+ }
157188
158189async function startUpload() {
159- const files = document.getElementById (" fileInput" ).files ;
160- const status = document.getElementById (" status" );
161- status.innerHTML = " " ;
162-
163- for (const file of files) {
164- const fileDiv = document.createElement (" div" );
165- fileDiv.className = " file" ;
166- fileDiv.innerHTML = `
167- <strong>${file.name}</strong><br>
168- <progress value="0" max="${file.size}"></progress>
169- <span class="progressText">0%</span>
170- ` ;
171- status.appendChild (fileDiv);
172-
173- const progressBar = fileDiv.querySelector (" progress" );
174- const progressText = fileDiv.querySelector (" .progressText" );
190+ for (const entry of filesMap.values ()) {
191+ if (entry.removed ) continue;
192+
193+ const { file, uuid, elements } = entry;
194+
195+ // Disable delete during upload
196+ elements.removeBtn.remove ();
175197
176- const uuid = getUuid();
177198 let offset = 0;
178199
179200 while (offset < file.size ) {
@@ -189,53 +210,52 @@ async function startUpload() {
189210 method: " POST" ,
190211 body: formData,
191212 headers: {
192- ' apikey' : API_KEY,
193- " fileRequestId" : FILE_REQUEST_ID
213+ apikey: API_KEY,
214+ fileRequestId: FILE_REQUEST_ID
194215 }
195216 });
196217
197218 if (!response.ok ) {
219+ elements.progressText.textContent = " Error" ;
198220 throw new Error(` Chunk upload failed: ${response.status}` );
199221 }
200222
201223 offset += chunk.size ;
202- progressBar.value = offset;
203- progressText.textContent = Math.floor ((offset / file.size ) * 100) + " %" ;
224+ elements.progressBar.value = offset;
225+ elements.progressText.textContent =
226+ Math.floor ((offset / file.size ) * 100) + " %" ;
204227 }
205228
206- // Finalize upload
207229 await finalizeUpload(file, uuid);
208- progressText.textContent = " ✔ Completed" ;
230+
231+ elements.progressText.textContent = " Completed" ;
232+ elements.item.style.opacity = " 0.6" ;
233+ filesMap.get (uuid).removed = true;
209234 }
210235}
211236
212237async function finalizeUpload(file, uuid) {
213- const headers = {
214- " uuid" : uuid,
215- " fileRequestId" : FILE_REQUEST_ID,
216- " filename" : encodeFilename(file.name ),
217- " filesize" : file.size ,
218- 'apikey': API_KEY,
219- " contenttype" : file.type || " application/octet-stream"
220- };
221-
222238 const response = await fetch(COMPLETE_URL, {
223239 method: " POST" ,
224- headers: headers
240+ headers: {
241+ uuid: uuid,
242+ fileRequestId: FILE_REQUEST_ID,
243+ filename: encodeFilename(file.name ),
244+ filesize: file.size ,
245+ contenttype: file.type || " application/octet-stream" ,
246+ apikey: API_KEY
247+ }
225248 });
226249
227250 if (!response.ok ) {
228251 throw new Error(` Finalize failed: ${response.status}` );
229252 }
230-
231- return response;
232253}
233254
234255function encodeFilename(name) {
235- return " base64:" + btoa(unescape(encodeURIComponent( name)) );
256+ return " base64:" + Base64 .encode ( name);
236257}
237258</script>
238- <script src= " ./js/min/uuid.min.js" ></script>
239259
240260{{ template " pagename" " PublicUpload" }}
241261{{ template " customjs" . }}
0 commit comments