]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Uguu | |
3 | * | |
4 | * @copyright Copyright (c) 2022 Go Johansson (nekunekus) <neku@pomf.se> <github.com/nokonoko> | |
5 | * | |
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. | |
10 | * | |
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/>. | |
18 | */ | |
19 | ||
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 | */ | |
27 | function addRow(file) | |
28 | { | |
29 | const row = document.createElement('li'); | |
30 | ||
31 | const name = document.createElement('span'); | |
32 | name.textContent = file.name; | |
33 | name.className = 'file-name'; | |
34 | ||
35 | const progressIndicator = document.createElement('span'); | |
36 | progressIndicator.className = 'progress-percent'; | |
37 | progressIndicator.textContent = '0%'; | |
38 | ||
39 | const progressBar = document.createElement('progress'); | |
40 | progressBar.className = 'file-progress'; | |
41 | progressBar.setAttribute('max', '100'); | |
42 | progressBar.setAttribute('value', '0'); | |
43 | ||
44 | row.appendChild(name); | |
45 | row.appendChild(progressBar); | |
46 | row.appendChild(progressIndicator); | |
47 | ||
48 | document.getElementById('upload-filelist').appendChild(row); | |
49 | return row; | |
50 | } | |
51 | ||
52 | /** | |
53 | * Updates the page while the file is being uploaded. | |
54 | * | |
55 | * @param {ProgressEvent} evt | |
56 | */ | |
57 | function handleUploadProgress(evt) | |
58 | { | |
59 | let xhr = evt.target; | |
60 | let bar = xhr.bar; | |
61 | let percentIndicator = xhr.percent; | |
62 | ||
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) { | |
66 | let progressPercent = Math.floor((evt.loaded / evt.total) * 100); | |
67 | bar.setAttribute('value', progressPercent); | |
68 | percentIndicator.textContent = progressPercent + '%'; | |
69 | } | |
70 | } | |
71 | ||
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 | */ | |
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; | |
85 | ||
86 | percentIndicator.style.visibility = 'hidden'; | |
87 | bar.style.visibility = 'hidden'; | |
88 | row.removeChild(bar); | |
89 | row.removeChild(percentIndicator); | |
90 | let respStatus = xhr.status; | |
91 | ||
92 | let url = document.createElement('span'); | |
93 | url.className = 'file-url'; | |
94 | row.appendChild(url); | |
95 | ||
96 | let link = document.createElement('a'); | |
97 | if (respStatus === 200) { | |
98 | let response = JSON.parse(xhr.responseText); | |
99 | if (response.success) { | |
100 | link.textContent = response.files[0].url.replace(/.*?:\/\//g, ''); | |
101 | link.href = response.files[0].url; | |
102 | url.appendChild(link); | |
103 | const copy = document.createElement('button'); | |
104 | copy.className = 'upload-clipboard-btn'; | |
105 | const glyph = document.createElement('img'); | |
106 | glyph.src = 'img/glyphicons-512-copy.png'; | |
107 | copy.appendChild(glyph); | |
108 | url.appendChild(copy); | |
109 | copy.addEventListener("click", function () { | |
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 */ | |
117 | const element = document.createElement('a'); | |
118 | element.textContent = response.files[0].url; | |
119 | link.appendChild(element); | |
120 | let range = document.createRange(); | |
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 { | |
134 | let response = JSON.parse(xhr.responseText); | |
135 | link.textContent = response.description; | |
136 | url.appendChild(link); | |
137 | } | |
138 | } | |
139 | ||
140 | /** | |
141 | * Updates the page while the file is being uploaded. | |
142 | * | |
143 | * @param {File} file | |
144 | * @param {HTMLLIElement} row | |
145 | */ | |
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(); | |
151 | xhr.open('POST', 'upload.php'); | |
152 | ||
153 | xhr['row'] = row; | |
154 | xhr['bar'] = bar; | |
155 | xhr['percent'] = percentIndicator; | |
156 | xhr.upload['bar'] = bar; | |
157 | xhr.upload['percent'] = percentIndicator; | |
158 | ||
159 | xhr.addEventListener('load', handleUploadComplete, false); | |
160 | xhr.upload.onprogress = handleUploadProgress; | |
161 | ||
162 | let form = new FormData(); | |
163 | form.append('files[]', file); | |
164 | xhr.send(form); | |
165 | } | |
166 | ||
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 | */ | |
174 | function stopDefaultEvent(evt) | |
175 | { | |
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 | */ | |
187 | function handleDrag(state, element, evt) | |
188 | { | |
189 | stopDefaultEvent(evt); | |
190 | if (state.dragCount === 1) { | |
191 | element.textContent = 'Drop it here~'; | |
192 | } | |
193 | state.dragCount += 1; | |
194 | } | |
195 | ||
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 | */ | |
203 | function handleDragAway(state, element, evt) | |
204 | { | |
205 | stopDefaultEvent(evt); | |
206 | state.dragCount -= 1; | |
207 | if (state.dragCount === 0) { | |
208 | element.textContent = 'Select or drop file(s)'; | |
209 | } | |
210 | } | |
211 | ||
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 | */ | |
219 | function handleDragDrop(state, element, evt) | |
220 | { | |
221 | stopDefaultEvent(evt); | |
222 | handleDragAway(state, element, evt); | |
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); | |
227 | uploadFile(file, row); | |
228 | } | |
229 | } | |
230 | ||
231 | /** | |
232 | * Prepares the files to be uploaded when they're added to the <input> element. | |
233 | * | |
234 | * @param {InputEvent} evt | |
235 | */ | |
236 | function uploadFiles(evt) | |
237 | { | |
238 | let len = evt.target.files.length; | |
239 | // For each file, make a row, and upload the file. | |
240 | for (let i = 0; i < len; i++) { | |
241 | let file = evt.target.files[i]; | |
242 | let row = addRow(file); | |
243 | uploadFile(file, row); | |
244 | } | |
245 | } | |
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 | */ | |
253 | function selectFiles(target, evt) | |
254 | { | |
255 | stopDefaultEvent(evt); | |
256 | target.click(); | |
257 | } | |
258 | ||
259 | /* Handles the pasting function */ | |
260 | window.addEventListener("paste", e => { | |
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); | |
265 | uploadFile(file, row); | |
266 | } | |
267 | }); | |
268 | ||
269 | ||
270 | /* Set up the event handlers for the <button>, <input> and the window itself | |
271 | and also set the "js" class on selector "#upload-form", presumably to | |
272 | allow custom styles for clients running javascript. */ | |
273 | let state = {dragCount: 0}; | |
274 | let uploadButton = document.getElementById('upload-btn'); | |
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); | |
279 | ||
280 | ||
281 | let uploadInput = document.getElementById('upload-input'); | |
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 | }); |