]>
Commit | Line | Data |
---|---|---|
82202428 GJ |
1 | /* |
2 | * Uguu | |
d8c46ff7 | 3 | * |
82202428 | 4 | * @copyright Copyright (c) 2022 Go Johansson (nekunekus) <neku@pomf.se> <github.com/nokonoko> |
d8c46ff7 | 5 | * |
82202428 GJ |
6 | * This program is free software: you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation, either version 3 of the License, or | |
9 | * (at your option) any later version. | |
d8c46ff7 | 10 | * |
82202428 GJ |
11 | * This program is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with this program. If not, see <https://www.gnu.org/licenses/>. | |
d8c46ff7 GJ |
18 | */ |
19 | ||
82202428 GJ |
20 | document.addEventListener('DOMContentLoaded', function () { |
21 | /** | |
22 | * Sets up the elements inside file upload rows. | |
23 | * | |
24 | * @param {File} file | |
25 | * @return {HTMLLIElement} row | |
26 | */ | |
e480c0e5 GJ |
27 | function addRow(file) |
28 | { | |
29 | const row = document.createElement('li'); | |
82202428 | 30 | |
e480c0e5 | 31 | const name = document.createElement('span'); |
82202428 GJ |
32 | name.textContent = file.name; |
33 | name.className = 'file-name'; | |
d8c46ff7 | 34 | |
e480c0e5 | 35 | const progressIndicator = document.createElement('span'); |
82202428 GJ |
36 | progressIndicator.className = 'progress-percent'; |
37 | progressIndicator.textContent = '0%'; | |
d8c46ff7 | 38 | |
e480c0e5 | 39 | const progressBar = document.createElement('progress'); |
82202428 GJ |
40 | progressBar.className = 'file-progress'; |
41 | progressBar.setAttribute('max', '100'); | |
42 | progressBar.setAttribute('value', '0'); | |
d8c46ff7 | 43 | |
82202428 GJ |
44 | row.appendChild(name); |
45 | row.appendChild(progressBar); | |
46 | row.appendChild(progressIndicator); | |
d8c46ff7 | 47 | |
82202428 GJ |
48 | document.getElementById('upload-filelist').appendChild(row); |
49 | return row; | |
50 | } | |
d8c46ff7 | 51 | |
82202428 GJ |
52 | /** |
53 | * Updates the page while the file is being uploaded. | |
54 | * | |
55 | * @param {ProgressEvent} evt | |
56 | */ | |
e480c0e5 GJ |
57 | function handleUploadProgress(evt) |
58 | { | |
59 | let xhr = evt.target; | |
60 | let bar = xhr.bar; | |
61 | let percentIndicator = xhr.percent; | |
d8c46ff7 | 62 | |
82202428 GJ |
63 | /* If we have amounts of work done/left that we can calculate with |
64 | (which, unless we're uploading dynamically resizing data, is always), calculate the percentage. */ | |
65 | if (evt.lengthComputable) { | |
e480c0e5 | 66 | let progressPercent = Math.floor((evt.loaded / evt.total) * 100); |
82202428 GJ |
67 | bar.setAttribute('value', progressPercent); |
68 | percentIndicator.textContent = progressPercent + '%'; | |
69 | } | |
d8c46ff7 | 70 | } |
d8c46ff7 | 71 | |
82202428 GJ |
72 | /** |
73 | * Complete the uploading process by checking the response status and, if the | |
74 | * upload was successful, writing the URL(s) and creating the copy element(s) | |
75 | * for the files. | |
76 | * | |
77 | * @param {ProgressEvent} evt | |
78 | */ | |
e480c0e5 GJ |
79 | function handleUploadComplete(evt) |
80 | { | |
81 | let xhr = evt.target; | |
82 | let bar = xhr.bar; | |
83 | let row = xhr.row; | |
84 | let percentIndicator = xhr.percent; | |
d8c46ff7 | 85 | |
82202428 GJ |
86 | percentIndicator.style.visibility = 'hidden'; |
87 | bar.style.visibility = 'hidden'; | |
88 | row.removeChild(bar); | |
89 | row.removeChild(percentIndicator); | |
e480c0e5 | 90 | let respStatus = xhr.status; |
d8c46ff7 | 91 | |
e480c0e5 | 92 | let url = document.createElement('span'); |
82202428 GJ |
93 | url.className = 'file-url'; |
94 | row.appendChild(url); | |
d8c46ff7 | 95 | |
e480c0e5 | 96 | let link = document.createElement('a'); |
82202428 | 97 | if (respStatus === 200) { |
e480c0e5 | 98 | let response = JSON.parse(xhr.responseText); |
82202428 GJ |
99 | if (response.success) { |
100 | link.textContent = response.files[0].url.replace(/.*?:\/\//g, ''); | |
101 | link.href = response.files[0].url; | |
102 | url.appendChild(link); | |
e480c0e5 | 103 | const copy = document.createElement('button'); |
82202428 | 104 | copy.className = 'upload-clipboard-btn'; |
e480c0e5 | 105 | const glyph = document.createElement('img'); |
82202428 GJ |
106 | glyph.src = 'img/glyphicons-512-copy.png'; |
107 | copy.appendChild(glyph); | |
108 | url.appendChild(copy); | |
e480c0e5 | 109 | copy.addEventListener("click", function () { |
82202428 GJ |
110 | /* Why create an element? The text needs to be on screen to be |
111 | selected and thus copied. The only text we have on-screen is the link | |
112 | without the http[s]:// part. So, this creates an element with the | |
113 | full link for a moment and then deletes it. | |
114 | ||
115 | See the "Complex Example: Copy to clipboard without displaying | |
116 | input" section at: https://stackoverflow.com/a/30810322 */ | |
e480c0e5 | 117 | const element = document.createElement('a'); |
82202428 GJ |
118 | element.textContent = response.files[0].url; |
119 | link.appendChild(element); | |
e480c0e5 | 120 | let range = document.createRange(); |
82202428 GJ |
121 | range.selectNode(element); |
122 | window.getSelection().removeAllRanges(); | |
123 | window.getSelection().addRange(range); | |
124 | document.execCommand("copy"); | |
125 | link.removeChild(element); | |
126 | }); | |
127 | } else { | |
128 | bar.innerHTML = 'Error: ' + response.description; | |
129 | } | |
130 | } else if (respStatus === 413) { | |
131 | link.textContent = 'File too big!'; | |
132 | url.appendChild(link); | |
133 | } else { | |
e480c0e5 | 134 | let response = JSON.parse(xhr.responseText); |
82202428 GJ |
135 | link.textContent = response.description; |
136 | url.appendChild(link); | |
137 | } | |
d8c46ff7 | 138 | } |
d8c46ff7 | 139 | |
82202428 GJ |
140 | /** |
141 | * Updates the page while the file is being uploaded. | |
142 | * | |
143 | * @param {File} file | |
144 | * @param {HTMLLIElement} row | |
145 | */ | |
e480c0e5 GJ |
146 | function uploadFile(file, row) |
147 | { | |
148 | let bar = row.querySelector('.file-progress'); | |
149 | let percentIndicator = row.querySelector('.progress-percent'); | |
150 | let xhr = new XMLHttpRequest(); | |
82202428 | 151 | xhr.open('POST', 'upload.php'); |
d8c46ff7 | 152 | |
82202428 GJ |
153 | xhr['row'] = row; |
154 | xhr['bar'] = bar; | |
155 | xhr['percent'] = percentIndicator; | |
156 | xhr.upload['bar'] = bar; | |
157 | xhr.upload['percent'] = percentIndicator; | |
d8c46ff7 | 158 | |
82202428 GJ |
159 | xhr.addEventListener('load', handleUploadComplete, false); |
160 | xhr.upload.onprogress = handleUploadProgress; | |
d8c46ff7 | 161 | |
e480c0e5 | 162 | let form = new FormData(); |
82202428 GJ |
163 | form.append('files[]', file); |
164 | xhr.send(form); | |
165 | } | |
d8c46ff7 | 166 | |
82202428 GJ |
167 | /** |
168 | * Prevents the browser for allowing the normal actions associated with an event. | |
169 | * This is used by event handlers to allow custom functionality without | |
170 | * having to worry about the other consequences of that action. | |
171 | * | |
172 | * @param {Event} evt | |
173 | */ | |
e480c0e5 GJ |
174 | function stopDefaultEvent(evt) |
175 | { | |
82202428 GJ |
176 | evt.stopPropagation(); |
177 | evt.preventDefault(); | |
178 | } | |
179 | ||
180 | /** | |
181 | * Adds 1 to the state and changes the text. | |
182 | * | |
183 | * @param {Object} state | |
184 | * @param {HTMLButtonElement} element | |
185 | * @param {DragEvent} evt | |
186 | */ | |
e480c0e5 GJ |
187 | function handleDrag(state, element, evt) |
188 | { | |
82202428 | 189 | stopDefaultEvent(evt); |
e480c0e5 | 190 | if (state.dragCount === 1) { |
82202428 GJ |
191 | element.textContent = 'Drop it here~'; |
192 | } | |
193 | state.dragCount += 1; | |
194 | } | |
d8c46ff7 | 195 | |
82202428 GJ |
196 | /** |
197 | * Subtracts 1 from the state and changes the text back. | |
198 | * | |
199 | * @param {Object} state | |
200 | * @param {HTMLButtonElement} element | |
201 | * @param {DragEvent} evt | |
202 | */ | |
e480c0e5 GJ |
203 | function handleDragAway(state, element, evt) |
204 | { | |
82202428 GJ |
205 | stopDefaultEvent(evt); |
206 | state.dragCount -= 1; | |
e480c0e5 | 207 | if (state.dragCount === 0) { |
82202428 GJ |
208 | element.textContent = 'Select or drop file(s)'; |
209 | } | |
d8c46ff7 | 210 | } |
d8c46ff7 | 211 | |
82202428 GJ |
212 | /** |
213 | * Prepares files for uploading after being added via drag-drop. | |
214 | * | |
215 | * @param {Object} state | |
216 | * @param {HTMLButtonElement} element | |
217 | * @param {DragEvent} evt | |
218 | */ | |
e480c0e5 GJ |
219 | function handleDragDrop(state, element, evt) |
220 | { | |
82202428 GJ |
221 | stopDefaultEvent(evt); |
222 | handleDragAway(state, element, evt); | |
e480c0e5 GJ |
223 | let len = evt.dataTransfer.files.length; |
224 | for (let i = 0; i < len; i++) { | |
225 | let file = evt.dataTransfer.files[i]; | |
226 | let row = addRow(file); | |
82202428 GJ |
227 | uploadFile(file, row); |
228 | } | |
d8c46ff7 | 229 | } |
d8c46ff7 | 230 | |
82202428 GJ |
231 | /** |
232 | * Prepares the files to be uploaded when they're added to the <input> element. | |
233 | * | |
234 | * @param {InputEvent} evt | |
235 | */ | |
e480c0e5 GJ |
236 | function uploadFiles(evt) |
237 | { | |
238 | let len = evt.target.files.length; | |
82202428 | 239 | // For each file, make a row, and upload the file. |
e480c0e5 GJ |
240 | for (let i = 0; i < len; i++) { |
241 | let file = evt.target.files[i]; | |
242 | let row = addRow(file); | |
82202428 GJ |
243 | uploadFile(file, row); |
244 | } | |
d8c46ff7 | 245 | } |
82202428 GJ |
246 | |
247 | /** | |
248 | * Opens up a "Select files.." dialog window to allow users to select files to upload. | |
249 | * | |
250 | * @param {HTMLInputElement} target | |
251 | * @param {InputEvent} evt | |
252 | */ | |
e480c0e5 GJ |
253 | function selectFiles(target, evt) |
254 | { | |
82202428 GJ |
255 | stopDefaultEvent(evt); |
256 | target.click(); | |
d8c46ff7 | 257 | } |
d8c46ff7 | 258 | |
82202428 GJ |
259 | /* Handles the pasting function */ |
260 | window.addEventListener("paste", e => { | |
e480c0e5 GJ |
261 | let len = e.clipboardData.files.length; |
262 | for (let i = 0; i < len; i++) { | |
263 | let file = e.clipboardData.files[i]; | |
264 | let row = addRow(file); | |
82202428 GJ |
265 | uploadFile(file, row); |
266 | } | |
267 | }); | |
c036012e GJ |
268 | |
269 | ||
e480c0e5 | 270 | /* Set up the event handlers for the <button>, <input> and the window itself |
82202428 GJ |
271 | and also set the "js" class on selector "#upload-form", presumably to |
272 | allow custom styles for clients running javascript. */ | |
e480c0e5 GJ |
273 | let state = {dragCount: 0}; |
274 | let uploadButton = document.getElementById('upload-btn'); | |
82202428 GJ |
275 | window.addEventListener('dragenter', handleDrag.bind(this, state, uploadButton), false); |
276 | window.addEventListener('dragleave', handleDragAway.bind(this, state, uploadButton), false); | |
277 | window.addEventListener('drop', handleDragAway.bind(this, state, uploadButton), false); | |
278 | window.addEventListener('dragover', stopDefaultEvent, false); | |
d8c46ff7 | 279 | |
c036012e | 280 | |
e480c0e5 | 281 | let uploadInput = document.getElementById('upload-input'); |
82202428 GJ |
282 | uploadInput.addEventListener('change', uploadFiles); |
283 | uploadButton.addEventListener('click', selectFiles.bind(this, uploadInput)); | |
284 | uploadButton.addEventListener('drop', handleDragDrop.bind(this, state, uploadButton), false); | |
285 | document.getElementById('upload-form').classList.add('js'); | |
286 | }); |