]>
Commit | Line | Data |
---|---|---|
1 | /** | |
2 | * Copyright (c) 2016 Luminarys <postmaster@gensok.io> | |
3 | * | |
4 | * Copyright (c) 2021 Eric Johansson (Nekunekus) <neku@pomf.se> | |
5 | * | |
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
7 | * of this software and associated documentation files (the "Software"), to deal | |
8 | * in the Software without restriction, including without limitation the rights | |
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
10 | * copies of the Software, and to permit persons to whom the Software is | |
11 | * furnished to do so, subject to the following conditions: | |
12 | * | |
13 | * The above copyright notice and this permission notice shall be included in | |
14 | * all copies or substantial portions of the Software. | |
15 | * | |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
22 | * SOFTWARE. | |
23 | */ | |
24 | ||
25 | document.addEventListener('DOMContentLoaded', function() { | |
26 | /** | |
27 | * Sets up the elements inside file upload rows. | |
28 | * | |
29 | * @param {File} file | |
30 | * @return {HTMLLIElement} row | |
31 | */ | |
32 | function addRow(file) { | |
33 | var row = document.createElement('li'); | |
34 | ||
35 | var name = document.createElement('span'); | |
36 | name.textContent = file.name; | |
37 | name.className = 'file-name'; | |
38 | ||
39 | var progressIndicator = document.createElement('span'); | |
40 | progressIndicator.className = 'progress-percent'; | |
41 | progressIndicator.textContent = '0%'; | |
42 | ||
43 | var progressBar = document.createElement('progress'); | |
44 | progressBar.className = 'file-progress'; | |
45 | progressBar.setAttribute('max', '100'); | |
46 | progressBar.setAttribute('value', '0'); | |
47 | ||
48 | row.appendChild(name); | |
49 | row.appendChild(progressBar); | |
50 | row.appendChild(progressIndicator); | |
51 | ||
52 | document.getElementById('upload-filelist').appendChild(row); | |
53 | return row; | |
54 | } | |
55 | ||
56 | /** | |
57 | * Updates the page while the file is being uploaded. | |
58 | * | |
59 | * @param {ProgressEvent} evt | |
60 | */ | |
61 | function handleUploadProgress(evt) { | |
62 | var xhr = evt.target; | |
63 | var bar = xhr.bar; | |
64 | var percentIndicator = xhr.percent; | |
65 | ||
66 | /* If we have amounts of work done/left that we can calculate with | |
67 | (which, unless we're uploading dynamically resizing data, is always), calculate the percentage. */ | |
68 | if (evt.lengthComputable) { | |
69 | var progressPercent = Math.floor((evt.loaded / evt.total) * 100); | |
70 | bar.setAttribute('value', progressPercent); | |
71 | percentIndicator.textContent = progressPercent + '%'; | |
72 | } | |
73 | } | |
74 | ||
75 | /** | |
76 | * Complete the uploading process by checking the response status and, if the | |
77 | * upload was successful, writing the URL(s) and creating the copy element(s) | |
78 | * for the files. | |
79 | * | |
80 | * @param {ProgressEvent} evt | |
81 | */ | |
82 | function handleUploadComplete(evt) { | |
83 | var xhr = evt.target; | |
84 | var bar = xhr.bar; | |
85 | var row = xhr.row; | |
86 | var percentIndicator = xhr.percent; | |
87 | ||
88 | percentIndicator.style.visibility = 'hidden'; | |
89 | bar.style.visibility = 'hidden'; | |
90 | row.removeChild(bar); | |
91 | row.removeChild(percentIndicator); | |
92 | var respStatus = xhr.status; | |
93 | ||
94 | var url = document.createElement('span'); | |
95 | url.className = 'file-url'; | |
96 | row.appendChild(url); | |
97 | ||
98 | var link = document.createElement('a'); | |
99 | if (respStatus === 200) { | |
100 | var response = JSON.parse(xhr.responseText); | |
101 | if (response.success) { | |
102 | link.textContent = response.files[0].url.replace(/.*?:\/\//g, ''); | |
103 | link.href = response.files[0].url; | |
104 | url.appendChild(link); | |
105 | var copy = document.createElement('button'); | |
106 | copy.className = 'upload-clipboard-btn'; | |
107 | var glyph = document.createElement('img'); | |
108 | glyph.src = 'img/glyphicons-512-copy.png'; | |
109 | copy.appendChild(glyph); | |
110 | url.appendChild(copy); | |
111 | copy.addEventListener("click", function(event) { | |
112 | /* Why create an element? The text needs to be on screen to be | |
113 | selected and thus copied. The only text we have on-screen is the link | |
114 | without the http[s]:// part. So, this creates an element with the | |
115 | full link for a moment and then deletes it. | |
116 | ||
117 | See the "Complex Example: Copy to clipboard without displaying | |
118 | input" section at: https://stackoverflow.com/a/30810322 */ | |
119 | var element = document.createElement('a'); | |
120 | element.textContent = response.files[0].url; | |
121 | link.appendChild(element); | |
122 | var range = document.createRange(); | |
123 | range.selectNode(element); | |
124 | window.getSelection().removeAllRanges(); | |
125 | window.getSelection().addRange(range); | |
126 | document.execCommand("copy"); | |
127 | link.removeChild(element); | |
128 | }); | |
129 | } else { | |
130 | bar.innerHTML = 'Error: ' + response.description; | |
131 | } | |
132 | } else if (respStatus === 413) { | |
133 | link.textContent = 'File too big!'; | |
134 | url.appendChild(link); | |
135 | } else if (respStatus === 415) { | |
136 | link.textContent = 'Filetype not allowed!'; | |
137 | url.appendChild(link); | |
138 | } else { | |
139 | link.textContent = 'Server error!'; | |
140 | url.appendChild(link); | |
141 | } | |
142 | } | |
143 | ||
144 | /** | |
145 | * Updates the page while the file is being uploaded. | |
146 | * | |
147 | * @param {File} file | |
148 | * @param {HTMLLIElement} row | |
149 | */ | |
150 | function uploadFile(file, row) { | |
151 | var bar = row.querySelector('.file-progress'); | |
152 | var percentIndicator = row.querySelector('.progress-percent'); | |
153 | var xhr = new XMLHttpRequest(); | |
154 | xhr.open('POST', 'upload.php'); | |
155 | ||
156 | xhr['row'] = row; | |
157 | xhr['bar'] = bar; | |
158 | xhr['percent'] = percentIndicator; | |
159 | xhr.upload['bar'] = bar; | |
160 | xhr.upload['percent'] = percentIndicator; | |
161 | ||
162 | xhr.addEventListener('load', handleUploadComplete, false); | |
163 | xhr.upload.onprogress = handleUploadProgress; | |
164 | ||
165 | var form = new FormData(); | |
166 | form.append('files[]', file); | |
167 | xhr.send(form); | |
168 | } | |
169 | ||
170 | /** | |
171 | * Prevents the browser for allowing the normal actions associated with an event. | |
172 | * This is used by event handlers to allow custom functionality without | |
173 | * having to worry about the other consequences of that action. | |
174 | * | |
175 | * @param {Event} evt | |
176 | */ | |
177 | function stopDefaultEvent(evt) { | |
178 | evt.stopPropagation(); | |
179 | evt.preventDefault(); | |
180 | } | |
181 | ||
182 | /** | |
183 | * Adds 1 to the state and changes the text. | |
184 | * | |
185 | * @param {Object} state | |
186 | * @param {HTMLButtonElement} element | |
187 | * @param {DragEvent} evt | |
188 | */ | |
189 | function handleDrag(state, element, evt) { | |
190 | stopDefaultEvent(evt); | |
191 | if (state.dragCount == 1) { | |
192 | element.textContent = 'Drop it here~'; | |
193 | } | |
194 | state.dragCount += 1; | |
195 | } | |
196 | ||
197 | /** | |
198 | * Subtracts 1 from the state and changes the text back. | |
199 | * | |
200 | * @param {Object} state | |
201 | * @param {HTMLButtonElement} element | |
202 | * @param {DragEvent} evt | |
203 | */ | |
204 | function handleDragAway(state, element, evt) { | |
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 | stopDefaultEvent(evt); | |
221 | handleDragAway(state, element, evt); | |
222 | var len = evt.dataTransfer.files.length; | |
223 | for (var i = 0; i < len; i++) { | |
224 | var file = evt.dataTransfer.files[i]; | |
225 | var row = addRow(file); | |
226 | uploadFile(file, row); | |
227 | } | |
228 | } | |
229 | ||
230 | /** | |
231 | * Prepares the files to be uploaded when they're added to the <input> element. | |
232 | * | |
233 | * @param {InputEvent} evt | |
234 | */ | |
235 | function uploadFiles(evt) { | |
236 | var len = evt.target.files.length; | |
237 | // For each file, make a row, and upload the file. | |
238 | for (var i = 0; i < len; i++) { | |
239 | var file = evt.target.files[i]; | |
240 | var row = addRow(file); | |
241 | uploadFile(file, row); | |
242 | } | |
243 | } | |
244 | ||
245 | /** | |
246 | * Opens up a "Select files.." dialog window to allow users to select files to upload. | |
247 | * | |
248 | * @param {HTMLInputElement} target | |
249 | * @param {InputEvent} evt | |
250 | */ | |
251 | function selectFiles(target, evt) { | |
252 | stopDefaultEvent(evt); | |
253 | target.click(); | |
254 | } | |
255 | ||
256 | /* Handles the pasting function */ | |
257 | window.addEventListener("paste", e =>{ | |
258 | var len = e.clipboardData.files.length; | |
259 | for (var i = 0; i < len; i++) { | |
260 | var file = e.clipboardData.files[i]; | |
261 | var row = addRow(file); | |
262 | uploadFile(file, row); | |
263 | } | |
264 | }); | |
265 | ||
266 | ||
267 | /* Set-up the event handlers for the <button>, <input> and the window itself | |
268 | and also set the "js" class on selector "#upload-form", presumably to | |
269 | allow custom styles for clients running javascript. */ | |
270 | var state = { dragCount: 0 }; | |
271 | var uploadButton = document.getElementById('upload-btn'); | |
272 | window.addEventListener('dragenter', handleDrag.bind(this, state, uploadButton), false); | |
273 | window.addEventListener('dragleave', handleDragAway.bind(this, state, uploadButton), false); | |
274 | window.addEventListener('drop', handleDragAway.bind(this, state, uploadButton), false); | |
275 | window.addEventListener('dragover', stopDefaultEvent, false); | |
276 | ||
277 | ||
278 | var uploadInput = document.getElementById('upload-input'); | |
279 | uploadInput.addEventListener('change', uploadFiles); | |
280 | uploadButton.addEventListener('click', selectFiles.bind(this, uploadInput)); | |
281 | uploadButton.addEventListener('drop', handleDragDrop.bind(this, state, uploadButton), false); | |
282 | document.getElementById('upload-form').classList.add('js'); | |
283 | }); |