rustream/templates/
upload.rs1pub fn get_content() -> String {
11 r#"<!DOCTYPE html>
12<html lang="en">
13<head>
14 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
15 <title>RuStream - Self-hosted Streaming Engine - v{{ version }}</title>
16 <meta property="og:type" content="MediaStreaming">
17 <meta name="keywords" content="Rust, streaming, actix, JavaScript, HTML, CSS">
18 <meta name="author" content="Vignesh Rao">
19 <meta content="width=device-width, initial-scale=1" name="viewport">
20 <!-- Favicon.ico and Apple Touch Icon -->
21 <link rel="icon" href="https://thevickypedia.github.io/open-source/images/logo/actix.ico">
22 <link rel="apple-touch-icon" href="https://thevickypedia.github.io/open-source/images/logo/actix.png">
23 <!-- Font Awesome icons -->
24 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/fontawesome.min.css">
25 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/solid.css">
26 <style>
27 /* Google fonts with a backup alternative */
28 @import url('https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;500;700&display=swap');
29 * {
30 font-family: 'Ubuntu', 'PT Serif', sans-serif;
31 }
32 body {
33 background-color: #6c7dac;
34 padding: 30px;
35 margin: 0;
36 }
37 .container {
38 text-align: center;
39 width: 100%;
40 max-width: 500px;
41 min-height: 435px;
42 margin: auto;
43 background-color: white;
44 border-radius: 16px;
45 box-shadow: rgba(255, 255, 255, 0.1) 0 1px 1px 0 inset, rgba(50, 50, 93, 0.25) 0 50px 100px -20px, rgba(0, 0, 0, 0.3) 0 30px 60px -30px;
46 }
47 .header-section {
48 padding: 25px 0;
49 }
50 .header-section h1 {
51 font-weight: 500;
52 font-size: 1.7rem;
53 text-transform: uppercase;
54 color: #707EA0;
55 margin: 0;
56 margin-bottom: 8px;
57 }
58 .header-section p,
59 .header-section label {
60 margin: 5px;
61 font-size: 0.95rem;
62 color: #707EA0;
63 }
64 .drop-section {
65 min-height: 250px;
66 border: 1px dashed #A8B3E3;
67 background-image: linear-gradient(180deg, white, #F1F6FF);
68 margin: 5px 35px 35px 35px;
69 border-radius: 12px;
70 position: relative;
71 }
72 .drop-section div.col:first-child {
73 opacity: 1;
74 visibility: visible;
75 transition-duration: 0.2s;
76 transform: scale(1);
77 width: 200px;
78 margin: auto;
79 }
80 .drop-section div.col:last-child {
81 font-size: 40px;
82 font-weight: 700;
83 color: #c0cae1;
84 position: absolute;
85 top: 0;
86 bottom: 0;
87 left: 0;
88 right: 0;
89 margin: auto;
90 width: 200px;
91 height: 55px;
92 pointer-events: none;
93 opacity: 0;
94 visibility: hidden;
95 transform: scale(0.6);
96 transition-duration: 0.2s;
97 }
98 /* use "drag-over-effect" class in js */
99 .drag-over-effect div.col:first-child {
100 opacity: 0;
101 visibility: hidden;
102 pointer-events: none;
103 transform: scale(1.1);
104 }
105 .drag-over-effect div.col:last-child {
106 opacity: 1;
107 visibility: visible;
108 transform: scale(1);
109 }
110 .drop-section .cloud-icon {
111 margin-top: 25px;
112 margin-bottom: 20px;
113 }
114 .drop-section span,
115 .drop-section button {
116 display: block;
117 margin: auto;
118 color: #707EA0;
119 margin-bottom: 10px;
120 }
121 .drop-section button {
122 color: white;
123 background-color: #5874C6;
124 border: none;
125 outline: none;
126 padding: 7px 20px;
127 border-radius: 8px;
128 margin-top: 20px;
129 cursor: pointer;
130 box-shadow: rgba(50, 50, 93, 0.25) 0 13px 27px -5px, rgba(0, 0, 0, 0.3) 0 8px 16px -8px;
131 }
132 .drop-section input {
133 display: none;
134 }
135 .list-section {
136 display: none;
137 text-align: left;
138 margin: 0 35px;
139 padding-bottom: 20px;
140 }
141 .list-section .list-title {
142 font-size: 0.95rem;
143 color: #707EA0;
144 }
145 .list-section li {
146 display: flex;
147 margin: 15px 0;
148 padding-top: 4px;
149 padding-bottom: 2px;
150 border-radius: 8px;
151 transition-duration: 0.2s;
152 }
153 .list-section li:hover {
154 box-shadow: #E3EAF9 0 0 4px 0, #E3EAF9 0 12px 16px 0;
155 }
156 .list-section li .col {
157 flex: .1;
158 }
159 .list-section li .col:nth-child(1) {
160 flex: .15;
161 text-align: center;
162 }
163 .list-section li .col:nth-child(2) {
164 flex: .75;
165 text-align: left;
166 font-size: 0.9rem;
167 color: #3e4046;
168 padding: 8px 10px;
169 }
170 .list-section li .col:nth-child(2) div.name {
171 overflow: hidden;
172 white-space: nowrap;
173 text-overflow: ellipsis;
174 max-width: 250px;
175 display: inline-block;
176 }
177 .list-section li .col .file-name span {
178 color: #707EA0;
179 float: right;
180 }
181 .list-section li .file-progress {
182 width: 100%;
183 height: 5px;
184 margin-top: 8px;
185 border-radius: 8px;
186 background-color: #dee6fd;
187 }
188 .list-section li .file-progress span {
189 display: block;
190 width: 0%;
191 height: 100%;
192 border-radius: 8px;
193 background-image: linear-gradient(120deg, #6b99fd, #9385ff);
194 transition-duration: 0.4s;
195 }
196 .list-section li .col .file-size {
197 font-size: 0.75rem;
198 margin-top: 3px;
199 color: #707EA0;
200 }
201 .list-section li .col svg.cross,
202 .list-section li .col svg.tick {
203 fill: #8694d2;
204 background-color: #dee6fd;
205 position: relative;
206 left: 50%;
207 top: 50%;
208 transform: translate(-50%, -50%);
209 border-radius: 50%;
210 }
211 .list-section li .col svg.tick {
212 fill: #50a156;
213 background-color: transparent;
214 }
215 .list-section li.complete span,
216 .list-section li.complete .file-progress,
217 .list-section li.complete svg.cross {
218 display: none;
219 }
220 .list-section li.in-prog .file-size,
221 .list-section li.in-prog svg.tick {
222 display: none;
223 }
224 </style>
225 <style>
226 a,
227 button {
228 color: white;
229 background-color: #6c7dac;
230 }
231 .upload {
232 position: absolute;
233 top: 3.8%;
234 right: 313px;
235 border: none;
236 padding: 10px 14px;
237 font-size: 16px;
238 cursor: pointer;
239 }
240 .home {
241 position: absolute;
242 top: 3.8%;
243 right: 217px;
244 border: none;
245 padding: 10px 14px;
246 font-size: 16px;
247 cursor: pointer;
248 }
249 .back {
250 position: absolute;
251 top: 3.8%;
252 right: 132px;
253 border: none;
254 padding: 10px 14px;
255 font-size: 16px;
256 cursor: pointer;
257 }
258 </style>
259 <style>
260 .dropbtn {
261 position: absolute;
262 top: 3.8%;
263 right: 30px;
264 padding: 10px 24px;
265 font-size: 16px;
266 border: none;
267 cursor: pointer;
268 }
269 .dropdown {
270 position: absolute;
271 top: 3.8%;
272 right: 30px;
273 padding: 10px 24px;
274 display: inline-block;
275 }
276 .dropdown-content {
277 display: none;
278 position: absolute;
279 top: 40px; /* Distance from the user icon button */
280 right: 30px;
281 width: 160px;
282 min-width: auto;
283 box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2); /* Basically, black with 20% opacity */
284 z-index: 1;
285 }
286 .dropdown-content a {
287 padding: 12px 16px;
288 text-decoration: none;
289 display: block;
290 }
291 .dropdown:hover .dropdown-content {display: block;}
292 </style>
293</head>
294<noscript>
295 <style>
296 body {
297 width: 100%;
298 height: 100%;
299 overflow: hidden;
300 }
301 </style>
302 <div style="position: fixed; text-align:center; height: 100%; width: 100%; background-color: #151515;">
303 <h2 style="margin-top:5%">This page requires JavaScript
304 to be enabled.
305 <br><br>
306 Please refer <a href="https://www.enable-javascript.com/">enable-javascript</a> for how to.
307 </h2>
308 <form>
309 <button type="submit" onClick="<meta httpEquiv='refresh' content='0'>">RETRY</button>
310 </form>
311 </div>
312</noscript>
313<body>
314 <button class="upload" onclick="upload()"><i class="fa-solid fa-cloud-arrow-up"></i> Upload</button>
315 <button class="home" onclick="goHome()"><i class="fa fa-home"></i> Home</button>
316 <button class="back" onclick="goBack()"><i class="fa fa-backward"></i> Back</button>
317 <div class="dropdown">
318 <button class="dropbtn"><i class="fa fa-user"></i></button>
319 <div class="dropdown-content">
320 <a onclick="goProfile()" style="cursor: pointer;"><i class="fa-solid fa-user-lock"></i> {{ user }}</a>
321 <a onclick="logOut()" style="cursor: pointer"><i class="fa fa-sign-out"></i> logout</a>
322 </div>
323 </div>
324 <br><br><br>
325 <div class="container">
326 <div class="header-section">
327 <h1>Upload Files</h1>
328 <p>PDF, Images, Videos and Subtitles are allowed</p>
329 <br>
330 <input type="checkbox" id="dedicated" name="dedicated" title="Files will be stored in a secured location, which can only be accessed by '{{ user }}'" checked>
331 <label for="dedicated" title="Files will be stored in a secured location, which can only be accessed by '{{ user }}'"><i class="fa-solid fa-lock"></i></i> Upload files to '{{ user }}' directory</label>
332 </div>
333 <div class="drop-section">
334 <div class="col">
335 <div class="cloud-icon">
336 <img src="https://thevickypedia.github.io/open-source/images/icons/cloud.png" alt="cloud">
337 </div>
338 <span>Drag & Drop your files here</span>
339 <span>OR</span>
340 <button class="file-selector">Browse Files</button>
341 <input type="file" class="file-selector-input" multiple>
342 </div>
343 <div class="col">
344 <div class="drop-here">Drop Here</div>
345 </div>
346 </div>
347 <div class="list-section">
348 <div class="list-title">Uploaded Files</div>
349 <div class="list"></div>
350 </div>
351 </div>
352 <script>
353 const dropArea = document.querySelector('.drop-section')
354 const listSection = document.querySelector('.list-section')
355 const listContainer = document.querySelector('.list')
356 const fileSelector = document.querySelector('.file-selector')
357 const fileSelectorInput = document.querySelector('.file-selector-input')
358
359 // Upload files with browse button
360 fileSelector.onclick = () => fileSelectorInput.click()
361 fileSelectorInput.onchange = () => {
362 [...fileSelectorInput.files].forEach((file) => {
363 if (typeValidation(file.type)) {
364 uploadFile(file)
365 }
366 })
367 }
368
369 // Check the file type
370 function typeValidation(type) {
371 let splitType = type.split('/')[0]
372 if (type === 'application/pdf' || type === 'text/vtt' || splitType === 'image' || splitType === 'video') {
373 return true
374 }
375 }
376
377 // When file is over the drag area
378 dropArea.ondragover = (e) => {
379 e.preventDefault();
380 [...e.dataTransfer.items].forEach((item) => {
381 if (typeValidation(item.type)) {
382 dropArea.classList.add('drag-over-effect')
383 }
384 })
385 }
386 // When file leave the drag area
387 dropArea.ondragleave = () => {
388 dropArea.classList.remove('drag-over-effect')
389 }
390 // When file drop on the drag area
391 dropArea.ondrop = (e) => {
392 e.preventDefault();
393 dropArea.classList.remove('drag-over-effect')
394 if (e.dataTransfer.items) {
395 [...e.dataTransfer.items].forEach((item) => {
396 if (item.kind === 'file') {
397 const file = item.getAsFile();
398 if (typeValidation(file.type)) {
399 uploadFile(file)
400 }
401 }
402 })
403 } else {
404 [...e.dataTransfer.files].forEach((file) => {
405 if (typeValidation(file.type)) {
406 uploadFile(file)
407 }
408 })
409 }
410 }
411 // upload file function
412 function uploadFile(file) {
413 listSection.style.display = 'block'
414 let li = document.createElement('li')
415 li.classList.add('in-prog')
416 li.innerHTML = `
417 <div class="col">
418 <img src="https://thevickypedia.github.io/open-source/images/icons/${iconSelector(file.type)}" alt="">
419 </div>
420 <div class="col">
421 <div class="file-name">
422 <div class="name">${file.name}</div>
423 <span>0%</span>
424 </div>
425 <div class="file-progress">
426 <span></span>
427 </div>
428 <div class="file-size">${(file.size / (1024 * 1024)).toFixed(2)} MB</div>
429 </div>
430 <div class="col">
431 <svg xmlns="http://www.w3.org/2000/svg" class="cross" height="20" width="20"><path d="m5.979 14.917-.854-.896 4-4.021-4-4.062.854-.896 4.042 4.062 4-4.062.854.896-4 4.062 4 4.021-.854.896-4-4.063Z"/></svg>
432 <svg xmlns="http://www.w3.org/2000/svg" class="tick" height="20" width="20"><path d="m8.229 14.438-3.896-3.917 1.438-1.438 2.458 2.459 6-6L15.667 7Z"/></svg>
433 </div>
434 `
435 listContainer.prepend(li)
436 let http = new XMLHttpRequest()
437 let checkbox = document.getElementById('dedicated');
438 let data = new FormData()
439 data.append('file', file)
440 http.onload = () => {
441 checkbox.disabled = false;
442 if (http.status === 200) {
443 // Successful response from the server
444 li.classList.add('complete');
445 li.classList.remove('in-prog');
446 } else {
447 // Handle error responses
448 alert('Error uploading file. Status:' + http.status);
449 return false;
450 }
451 }
452 http.onerror = (error) => {
453 // Handle network errors
454 console.log(error);
455 alert('Network error during file upload.');
456 checkbox.disabled = false;
457 return false;
458 };
459 http.upload.onprogress = (e) => {
460 checkbox.disabled = true;
461 let percent_complete = (e.loaded / e.total) * 100
462 li.querySelectorAll('span')[0].innerHTML = Math.round(percent_complete) + '%'
463 li.querySelectorAll('span')[1].style.width = percent_complete + '%'
464 }
465 http.open('POST', window.location.origin + '/upload', true); // asynchronous session
466 http.setRequestHeader('secure-flag', checkbox.checked);
467 http.send(data)
468 li.querySelector('.cross').onclick = () => http.abort()
469 http.onabort = () => {
470 checkbox.disabled = false;
471 let crossElement = li.querySelector('.cross');
472 // Insert a red cross sign
473 crossElement.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" class="cross-solid" height="20" width="20"><path d="m5.979 14.917-.854-.896 4-4.021-4-4.062.854-.896 4.042 4.062 4-4.062.854.896-4 4.062 4 4.021-.854.896-4-4.063Z" stroke="red" stroke-width="2"></path></svg>';
474 let context = li.querySelector('.file-name');
475 let spanElement = context.querySelector('span');
476 // Change uploaded percentage into a text
477 spanElement.innerHTML = 'ABORTED!!';
478 }
479 }
480
481 // find icon for file
482 function iconSelector(type) {
483 let splitType = (type.split('/')[0] === 'application') ? type.split('/')[1] : type.split('/')[0];
484 return splitType + '.png'
485 }
486 </script>
487 <script>
488 function goHome() {
489 window.location.href = "/home";
490 }
491 function goProfile() {
492 window.location.href = '/profile';
493 }
494 function logOut() {
495 window.location.href = "/logout";
496 }
497 function upload() {
498 window.location.href = "/upload";
499 }
500 function goBack() {
501 window.history.back();
502 }
503 </script>
504 <script>
505 document.getElementById("dedicated").addEventListener("change", function() {
506 if (!this.checked) {
507 let confirmation = confirm(
508 "Uploading to public space will not only make your uploads accessible " +
509 "to other users, but it will also overwrite any existing files " +
510 "with the same name.\n\nAre you sure you want to proceed?"
511 );
512 if (!confirmation) {
513 this.checked = true;
514 }
515 }
516 });
517 </script>
518</body>
519</html>"#.to_string()
520}