Files
localsend/packages/core/assets/web/download.html
T
Tien Do Nam 8a91809811
CI / format (push) Has been cancelled
CI / test (push) Has been cancelled
CI / packaging (push) Has been cancelled
refactor: merge download.js into download.html
2026-08-03 14:41:40 +02:00

279 lines
7.3 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LocalSend</title>
<style>
body {
font-family: sans-serif, system-ui;
margin: 0;
line-height: 1.5;
text-align: center;
}
a {
text-decoration: none;
color: #FFFFFF;
}
#file-list {
box-sizing: border-box;
width: 100%;
max-width: 1000px;
padding: 0 1em;
margin: 0 auto;
text-align: left;
}
.file-item {
display: table;
width: 100%;
padding: 0.5em 1em;
background-color: #00796B;
border-radius: 0.5em;
margin-bottom: 1em;
float: left;
box-sizing: border-box;
}
.file-item:hover {
background-color: #009688;
}
.file-name-cell, .file-size-cell, .file-index-cell {
display: table-cell;
vertical-align: middle;
}
.file-name-cell {
width: 100%;
padding: 0 0.5em;
}
.file-size-cell, .file-index-cell {
font-size: 0.8em;
white-space: nowrap;
color: #E0E0E0;
}
/* Two columns layout */
.file-item {
width: 100%;
}
@media screen and (min-width: 768px) {
#file-list.file-item {
width: 48%;
margin-right: 2%;
}
#file-list.file-item:nth-child(2n) {
margin-right: 0;
}
}
/* Single file mode */
#single-file {
box-sizing: border-box;
width: 100%;
text-align: left;
max-width: 400px;
padding: 0 1em;
margin: 0 auto;
}
</style>
</head>
<body>
<noscript>
LocalSend requires JavaScript, which is currently disabled. Please enable it and try again.
</noscript>
<h1 style="padding: 0.5em">LocalSend</h1>
<p id="status-text"></p>
<div id="file-list"></div>
<div id="single-file"></div>
<script>
// IMPORTANT: This script works in Internet Explorer 8!
var BASE_URL = '/api/localsend/v2';
var i18n = {};
var sessionId = sessionStorage.getItem('sessionId');
var queryParams = location.search.slice(1).split('&');
var queryPin = null;
// Parse query parameters manually for IE
for (var i = 0; i < queryParams.length; i++) {
var pair = queryParams[i].split('=');
if (pair[0] === 'pin') {
queryPin = decodeURIComponent(pair[1]);
break;
}
}
function firstRequestFiles() {
document.getElementById('status-text').innerText = i18n.waiting;
var initialUrl = BASE_URL + '/prepare-download';
if (sessionId) {
initialUrl += '?sessionId=' + encodeURIComponent(sessionId);
if (queryPin) {
initialUrl += '&pin=' + encodeURIComponent(queryPin);
}
} else if (queryPin) {
initialUrl += '?pin=' + encodeURIComponent(queryPin);
}
makeRequest(initialUrl, 'POST', function (response) {
if (response.status === 401) {
pinRequestFiles(true);
return;
}
if (response.status === 403) {
document.getElementById('status-text').innerText = i18n.rejected;
return;
}
if (response.status === 429) {
document.getElementById('status-text').innerText = i18n.tooManyAttempts;
return;
}
if (response.status !== 200) {
document.getElementById('status-text').innerText = 'Error: ' + response.status;
return;
}
handleSuccess(response);
});
}
function pinRequestFiles(firstAttempt) {
var pin = prompt(i18n.enterPin + (firstAttempt ? '' : '\n' + i18n.invalidPin));
if (!pin) {
document.getElementById('status-text').innerText = i18n.invalidPin;
return;
}
makeRequest(BASE_URL + '/prepare-download?pin=' + encodeURIComponent(pin), 'POST', function (response) {
if (response.status === 401) {
pinRequestFiles(false);
return;
}
if (response.status === 403) {
document.getElementById('status-text').innerText = i18n.rejected;
return;
}
if (response.status === 429) {
document.getElementById('status-text').innerText = i18n.tooManyAttempts;
return;
}
if (response.status !== 200) {
document.getElementById('status-text').innerText = 'Error: ' + response.status;
return;
}
handleSuccess(response);
});
}
function makeRequest(url, method, callback) {
var xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
callback(xhr);
}
};
xhr.send();
}
function fetchI18n(then) {
makeRequest('/i18n.json', 'GET', function (response) {
if (response.status === 200) {
i18n = JSON.parse(response.responseText);
then();
}
});
}
function init() {
fetchI18n(firstRequestFiles);
}
function handleSuccess(response) {
var data = JSON.parse(response.responseText);
var files = data.files;
sessionId = data.sessionId;
sessionStorage.setItem('sessionId', sessionId);
document.getElementById('status-text').innerText = i18n.files + ' (' + getKeys(data.files).length + ')';
// Handling file display
handleFilesDisplay(files, sessionId);
}
function handleFilesDisplay(files, sessionId) {
var html = '';
var fileKeys = getKeys(files);
for (var i = 0; i < fileKeys.length; i++) {
var file = files[fileKeys[i]];
html += '<a class="file-item" href="' + BASE_URL + '/download?sessionId=' + encodeURIComponent(sessionId) + '&fileId=' + encodeURIComponent(fileKeys[i]) + '">' +
'<div class="file-index-cell">' + (i + 1) + '</div>' +
'<div class="file-name-cell">' + escapeHtml(file.fileName) + '</div>' +
'<div class="file-size-cell">' + formatBytes(file.size) + '</div>' +
'</a>';
}
if (fileKeys.length === 1) {
document.getElementById('single-file').innerHTML = html;
} else {
document.getElementById('file-list').innerHTML = html;
}
}
function escapeHtml(text) {
if (text === null || text === undefined) {
return '';
}
var map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return String(text).replace(/[&<>"']/g, function (m) { return map[m]; });
}
function formatBytes(bytes) {
if (bytes < 1024) {
return bytes + ' B';
} else if (bytes < 1024 * 1024) {
return (bytes / 1024).toFixed(1) + ' KB';
} else if (bytes < 1024 * 1024 * 1024) {
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
} else {
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + ' GB';
}
}
function getKeys(obj) {
var keys = [];
for (var key in obj) {
keys.push(key);
}
return keys;
}
init();
</script>
</body>
</html>