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