]>
Commit | Line | Data |
---|---|---|
d8c46ff7 GJ |
1 | /** |
2 | * Copyright (c) 2016 Luminarys <postmaster@gensok.io> | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
5 | * of this software and associated documentation files (the "Software"), to deal | |
6 | * in the Software without restriction, including without limitation the rights | |
7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
8 | * copies of the Software, and to permit persons to whom the Software is | |
9 | * furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice shall be included in | |
12 | * all copies or substantial portions of the Software. | |
13 | * | |
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
20 | * SOFTWARE. | |
21 | */ | |
22 | ||
23 | document.addEventListener('DOMContentLoaded', function() { | |
24 | /** | |
25 | * Sets up the elements inside file upload rows. | |
26 | * | |
27 | * @param {File} file | |
28 | * @return {HTMLLIElement} row | |
29 | */ | |
30 | function addRow(file) { | |
31 | var row = document.createElement('li'); | |
32 | ||
33 | var name = document.createElement('span'); | |
34 | name.textContent = file.name; | |
35 | name.className = 'file-name'; | |
36 | ||
37 | var progressIndicator = document.createElement('span'); | |
38 | progressIndicator.className = 'progress-percent'; | |
39 | progressIndicator.textContent = '0%'; | |
40 | ||
41 | var progressBar = document.createElement('progress'); | |
42 | progressBar.className = 'file-progress'; | |
43 | progressBar.setAttribute('max', '100'); | |
44 | progressBar.setAttribute('value', '0'); | |
45 | ||
46 | row.appendChild(name); | |
47 | row.appendChild(progressBar); | |
48 | row.appendChild(progressIndicator); | |
49 | ||
50 | document.getElementById('upload-filelist').appendChild(row); | |
51 | return row; | |
52 | } | |
53 | ||
54 | /** | |
55 | * Updates the page while the file is being uploaded. | |
56 | * | |
57 | * @param {ProgressEvent} evt | |
58 | */ | |
59 | function handleUploadProgress(evt) { | |
60 | var xhr = evt.target; | |
61 | var bar = xhr.bar; | |
62 | var percentIndicator = xhr.percent; | |
63 | ||
64 | /* If we have amounts of work done/left that we can calculate with | |
65 | (which, unless we're uploading dynamically resizing data, is always), calculate the percentage. */ | |
66 | if (evt.lengthComputable) { | |
67 | var progressPercent = Math.floor((evt.loaded / evt.total) * 100); | |
68 | bar.setAttribute('value', progressPercent); | |
69 | percentIndicator.textContent = progressPercent + '%'; | |
70 | } | |
71 | } | |
72 | ||
73 | /** | |
74 | * Complete the uploading process by checking the response status and, if the | |
75 | * upload was successful, writing the URL(s) and creating the copy element(s) | |
76 | * for the files. | |
77 | * | |
78 | * @param {ProgressEvent} evt | |
79 | */ | |
80 | function handleUploadComplete(evt) { | |
81 | var xhr = evt.target; | |
82 | var bar = xhr.bar; | |
83 | var row = xhr.row; | |
84 | var percentIndicator = xhr.percent; | |
85 | ||
86 | percentIndicator.style.visibility = 'hidden'; | |
87 | bar.style.visibility = 'hidden'; | |
88 | row.removeChild(bar); | |
89 | row.removeChild(percentIndicator); | |
90 | var respStatus = xhr.status; | |
91 | ||
92 | var url = document.createElement('span'); | |
93 | url.className = 'file-url'; | |
94 | row.appendChild(url); | |
95 | ||
96 | var link = document.createElement('a'); | |
97 | if (respStatus === 200) { | |
98 | var 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 | var copy = document.createElement('button'); | |
104 | copy.className = 'upload-clipboard-btn'; | |
105 | var 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(event) { | |
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 | var element = document.createElement('a'); | |
118 | element.textContent = response.files[0].url; | |
119 | link.appendChild(element); | |
120 | var 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); | |
4b7727f7 | 133 | } else if (respStatus === 415) { |
95b5e1a7 | 134 | link.textContent = 'Filetype not allowed!'; |
4b7727f7 | 135 | url.appendChild(link); |
d8c46ff7 | 136 | } else { |
95b5e1a7 | 137 | link.textContent = 'Server error!'; |
d8c46ff7 GJ |
138 | url.appendChild(link); |
139 | } | |
140 | } | |
141 | ||
142 | /** | |
143 | * Updates the page while the file is being uploaded. | |
144 | * | |
145 | * @param {File} file | |
146 | * @param {HTMLLIElement} row | |
147 | */ | |
148 | function uploadFile(file, row) { | |
149 | var bar = row.querySelector('.file-progress'); | |
150 | var percentIndicator = row.querySelector('.progress-percent'); | |
151 | var xhr = new XMLHttpRequest(); | |
152 | xhr.open('POST', 'upload.php'); | |
153 | ||
154 | xhr['row'] = row; | |
155 | xhr['bar'] = bar; | |
156 | xhr['percent'] = percentIndicator; | |
157 | xhr.upload['bar'] = bar; | |
158 | xhr.upload['percent'] = percentIndicator; | |
159 | ||
160 | xhr.addEventListener('load', handleUploadComplete, false); | |
161 | xhr.upload.onprogress = handleUploadProgress; | |
162 | ||
163 | var form = new FormData(); | |
164 | form.append('files[]', file); | |
165 | xhr.send(form); | |
166 | } | |
167 | ||
168 | /** | |
169 | * Prevents the browser for allowing the normal actions associated with an event. | |
170 | * This is used by event handlers to allow custom functionality without | |
171 | * having to worry about the other consequences of that action. | |
172 | * | |
173 | * @param {Event} evt | |
174 | */ | |
175 | function stopDefaultEvent(evt) { | |
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 | stopDefaultEvent(evt); | |
189 | if (state.dragCount == 1) { | |
190 | element.textContent = 'Drop it here~'; | |
191 | } | |
192 | state.dragCount += 1; | |
193 | } | |
194 | ||
195 | /** | |
196 | * Subtracts 1 from the state and changes the text back. | |
197 | * | |
198 | * @param {Object} state | |
199 | * @param {HTMLButtonElement} element | |
200 | * @param {DragEvent} evt | |
201 | */ | |
202 | function handleDragAway(state, element, evt) { | |
203 | stopDefaultEvent(evt); | |
204 | state.dragCount -= 1; | |
205 | if (state.dragCount == 0) { | |
206 | element.textContent = 'Select or drop file(s)'; | |
207 | } | |
208 | } | |
209 | ||
210 | /** | |
211 | * Prepares files for uploading after being added via drag-drop. | |
212 | * | |
213 | * @param {Object} state | |
214 | * @param {HTMLButtonElement} element | |
215 | * @param {DragEvent} evt | |
216 | */ | |
217 | function handleDragDrop(state, element, evt) { | |
218 | stopDefaultEvent(evt); | |
219 | handleDragAway(state, element, evt); | |
220 | var len = evt.dataTransfer.files.length; | |
221 | for (var i = 0; i < len; i++) { | |
222 | var file = evt.dataTransfer.files[i]; | |
223 | var row = addRow(file); | |
224 | uploadFile(file, row); | |
225 | } | |
226 | } | |
227 | ||
228 | /** | |
229 | * Prepares the files to be uploaded when they're added to the <input> element. | |
230 | * | |
231 | * @param {InputEvent} evt | |
232 | */ | |
233 | function uploadFiles(evt) { | |
234 | var len = evt.target.files.length; | |
235 | // For each file, make a row, and upload the file. | |
236 | for (var i = 0; i < len; i++) { | |
237 | var file = evt.target.files[i]; | |
238 | var row = addRow(file); | |
239 | uploadFile(file, row); | |
240 | } | |
241 | } | |
242 | ||
243 | /** | |
244 | * Opens up a "Select files.." dialog window to allow users to select files to upload. | |
245 | * | |
246 | * @param {HTMLInputElement} target | |
247 | * @param {InputEvent} evt | |
248 | */ | |
249 | function selectFiles(target, evt) { | |
250 | stopDefaultEvent(evt); | |
251 | target.click(); | |
252 | } | |
253 | ||
254 | /* Set-up the event handlers for the <button>, <input> and the window itself | |
255 | and also set the "js" class on selector "#upload-form", presumably to | |
256 | allow custom styles for clients running javascript. */ | |
257 | var state = { dragCount: 0 }; | |
258 | var uploadButton = document.getElementById('upload-btn'); | |
259 | window.addEventListener('dragenter', handleDrag.bind(this, state, uploadButton), false); | |
260 | window.addEventListener('dragleave', handleDragAway.bind(this, state, uploadButton), false); | |
261 | window.addEventListener('drop', handleDragAway.bind(this, state, uploadButton), false); | |
262 | window.addEventListener('dragover', stopDefaultEvent, false); | |
263 | ||
264 | var uploadInput = document.getElementById('upload-input'); | |
265 | uploadInput.addEventListener('change', uploadFiles); | |
266 | uploadButton.addEventListener('click', selectFiles.bind(this, uploadInput)); | |
267 | uploadButton.addEventListener('drop', handleDragDrop.bind(this, state, uploadButton), false); | |
268 | document.getElementById('upload-form').classList.add('js'); | |
269 | }); |